#!/usr/bin/env bash # Step 3 — Rename-aware extraction driver. # Given a target key from plugin-map.json, clones the dedicated mirror ($WORK/_mirror, built by # 00-preflight.sh — never the working checkout, R4/M6) into $WORK/, runs a SINGLE git filter-repo # pass composing --path / --path-rename (both names for renamed plugins, F1) + the deterministic # ms-ai-architect blob strip (F3), then strips carried-over tags and re-creates exactly one annotated # tag v at the rewritten HEAD (F5). Idempotent (wipes $WORK/ first). NULL push (D8). # # Renamed plugins are extracted under BOTH historical names (F1), driven by plugin-map.json: # voyage ← ultraplan-local, llm-security ← llm-security-copilot, linkedin-studio ← linkedin-thought-leadership. # A single-path filter would silently drop the pre-rename history, so the map carries >=2 --path entries each. # # Usage: 10-extract.sh (e.g. voyage, llm-security, playground-design-system) # Override WORK= to relocate the workspace (default /tmp/polyrepo-migration). set -euo pipefail KEY="${1:-}" [ -n "$KEY" ] || { echo "usage: 10-extract.sh " >&2; exit 2; } SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" MAP="$SCRIPT_DIR/plugin-map.json" WORK="${WORK:-/tmp/polyrepo-migration}" MIRROR="$WORK/_mirror" DEST="$WORK/$KEY" fail() { printf 'EXTRACT FAIL: %s\n' "$*" >&2; exit 1; } [ -f "$MAP" ] || fail "plugin-map.json missing at $MAP" python3 -c "import json,sys; m=json.load(open('$MAP')); sys.exit(0 if '$KEY' in m['targets'] else 1)" \ || fail "unknown target '$KEY' (not in plugin-map.json)" # Self-heal: if the mirror is absent, run preflight to build it (extraction never reads the working checkout). if [ ! -e "$MIRROR/HEAD" ] && [ ! -d "$MIRROR/.git" ]; then echo " mirror absent → running preflight to build it" WORK="$WORK" bash "$SCRIPT_DIR/00-preflight.sh" >/dev/null fi [ -e "$MIRROR/HEAD" ] || [ -d "$MIRROR/.git" ] || fail "mirror still absent after preflight: $MIRROR" # Compose the filter-repo arg list from the map (one arg per line → bash 3.2 array). ARGS_FILE="$(mktemp)" python3 - "$MAP" "$KEY" >"$ARGS_FILE" <<'PY' import json, sys m = json.load(open(sys.argv[1])) t = m["targets"][sys.argv[2]] out = [] for p in t["paths"]: out += ["--path", p] for old, new in t.get("path_renames", {}).items(): out += ["--path-rename", old + ":" + new] if t.get("blob_strip"): if t.get("blob_strip_safe"): out += ["--strip-blobs-bigger-than", "1M"] else: # Surgical fallback (unreached while blob_strip_safe is true): drop only the screenshots. out += ["--path-glob", "!plugins/ms-ai-architect/playground/screenshots/*"] for a in out: print(a) PY FR_ARGS=() while IFS= read -r line; do FR_ARGS+=("$line"); done <"$ARGS_FILE" rm -f "$ARGS_FILE" TAG="$(python3 -c "import json; print(json.load(open('$MAP'))['targets']['$KEY']['tag'])")" # Re-assert the critical extension here too: the self-heal above runs preflight ONLY on a missing mirror, # so a present mirror + since-uninstalled git-filter-repo would otherwise die with a raw git error instead # of this actionable message (9e588ca — RUNBOOK lists it under Preconditions). git filter-repo --version >/dev/null 2>&1 \ || fail "git filter-repo not available — brew install git-filter-repo (see RUNBOOK Preconditions)" # Fresh --no-local clone from the mirror, then the single composing filter-repo pass. rm -rf "$DEST" git clone --no-local --quiet "$MIRROR" "$DEST" git -C "$DEST" filter-repo --force "${FR_ARGS[@]}" # F5: strip every carried-over tag, re-create exactly one annotated tag at the rewritten HEAD. OLD_TAGS="$(git -C "$DEST" tag)" if [ -n "$OLD_TAGS" ]; then printf '%s\n' "$OLD_TAGS" | while IFS= read -r tg; do [ -n "$tg" ] && git -C "$DEST" tag -d "$tg" >/dev/null done fi git -C "$DEST" tag -a "$TAG" -m "Release $TAG (extracted from ktg-plugin-marketplace monorepo)" COMMITS="$(git -C "$DEST" rev-list --count HEAD)" echo "EXTRACT OK $KEY → $DEST ($COMMITS commits, tag $TAG)"