ktg-plugin-marketplace/docs/marketplace-polyrepo-migration/migration/10-extract.sh
Kjell Tore Guttormsen fef4b33c97 fix(migration): remediate 6 MAJOR + 3 MINOR trekreview findings + stale rename test
MAJOR
- 9e97cd5 40-validate-standalone.sh: route a target's sc2_gate to its dedicated
  gate (config-audit → 50-config-audit-sc2.sh), mirroring 99-dryrun.sh, so --all
  no longer falsely FAILs config-audit on the machine-locked v5.0.0 tests.
- 1708e90 99-dryrun.sh: assert EXACTLY one tag survives (F5); a partial tag-strip
  no longer silently reports the wrong tag via head -1.
- 4e494c8 99-dryrun.sh: capture the SC2 standalone failing set from the dry-run's
  own prepped extract ($dest), not the 40-validate side-effect clean room.
- aeb6292 00-preflight.sh: assert every map path is whitespace/glob-free, making
  the word-split path handling in 99-dryrun.sh sound.
- 5d112cb extract the SC6 DROP + SC2 regression detectors into sc6-check.sh /
  sc2-regression.sh and add sc-checks.test.mjs — a negative test proving each
  detector FIRES (force-fresh re-extraction would undo a planted file-drop).
- 9e588ca 10-extract.sh re-asserts git filter-repo before use (self-heal runs
  preflight only on a missing mirror); RUNBOOK lists git-filter-repo + python3>=3.6.

MINOR
- bc0f8a7 plugin-map.json: reset ms-ai-architect blob_strip_safe to null
  (00-preflight.sh populates it per run).
- 8d649e9 99-dryrun.sh: gate SC6 behind extract success; a failed extract is
  labelled (extract failed), not a content DROP.
- 4044c49 99-dryrun.sh: guard mktemp — an empty capture is an error, not a
  false zero-regression PASS.

Also: 00-preflight.test.mjs asserted all 3 'renamed' plugins carry >=2 paths, but
llm-security became single-path in 836b8e9 (copilot was a coexisting plugin, not a
rename) — a stale pre-existing failure. Aligned the test to the ratified map and
added a positive single-path lock against re-introducing the 87-file-drop defect.

Verified: full dry-run 11/11, 0 pushes; sc-checks/99-dryrun/40-validate/00-preflight/
60-rewrite suites green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 16:17:12 +02:00

86 lines
4 KiB
Bash

#!/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/<key>, 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<version> at the rewritten HEAD (F5). Idempotent (wipes $WORK/<key> 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 <target-key> (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 <target-key>" >&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)"