docs(migration): polyrepo migration plan (11 steps, adversarially reviewed + revised)
Plan for splitting the monorepo into per-plugin Forgejo repos + a DS repo + a thin catalog. Built from the ratified brief via /trekplan: 6-agent exploration verified the brief and surfaced 6 under-scoped findings (F1-F6: plugin renames, ssh->https, 148MB blob bloat, config-audit snapshot path-coupling, tag namespace, README link rot). plan-critic REVISE on first draft (3 blockers + 8 majors) all addressed after re-verifying contested facts; scope-guardian ALIGNED. Local Claude half (Steps 1-11) is fully reversible NULL-push; operator window is a separate runbook. Held unpushed per D8. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
f2d41c8781
commit
a44e37b71f
1 changed files with 985 additions and 0 deletions
985
docs/marketplace-polyrepo-migration/plan.md
Normal file
985
docs/marketplace-polyrepo-migration/plan.md
Normal file
|
|
@ -0,0 +1,985 @@
|
|||
# Marketplace Polyrepo Migration — Implementation Plan
|
||||
|
||||
> **Plan quality: B+** (85/100) — APPROVE_WITH_NOTES (revised after adversarial review — Phase 9)
|
||||
>
|
||||
> Generated by trekplan v5.1.1 on 2026-06-17 — `plan_version: 1.7`
|
||||
> Brief: `docs/marketplace-polyrepo-migration/brief.md` (RATIFIED, D1–D8). Profile: premium (Opus).
|
||||
>
|
||||
> **⚠ Brief corrections requiring operator ratification** (the plan overrides two literal values
|
||||
> in the RATIFIED brief — surface these for explicit annotation, do not absorb silently):
|
||||
> 1. **`ssh://` → `https://`** (F2). Brief SC1 (§4) and the §6 entry shape literally specify
|
||||
> `ssh://git@…`; D4 ratified only *pin-to-tag*, silent on scheme. Claude Code rejects SSH
|
||||
> marketplace sources (issue #9740) → the plan uses `https://git.fromaitochitta.com/open/<repo>.git`
|
||||
> everywhere. Correct engineering call, but it edits a ratified literal.
|
||||
> 2. **config-audit SC2 carve-out** (F4). Two back-compat test files
|
||||
> (`json-backcompat.test.mjs`, `raw-backcompat.test.mjs`) assert byte-stability of a capture that
|
||||
> embeds the original machine's absolute path + a sibling marketplace + deleted plugins; the plan
|
||||
> **excludes them by name** from config-audit's SC2 gate (Step 7) — touches test fixtures, edges
|
||||
> the §5 non-goal. Operator must accept the carve-out.
|
||||
|
||||
## Context
|
||||
|
||||
The `ktg-plugin-marketplace` monorepo (10 plugins under `plugins/`, a shared design system under
|
||||
`shared/`, a catalog at the root) is split into **one Forgejo repo per plugin + a versioned
|
||||
design-system repo + a thin catalog repo at the same URL**. The plugins are already independent
|
||||
units (independent versions, self-contained `plugin.json`/`README`/`CLAUDE.md`, **no live
|
||||
cross-plugin runtime imports** — confirmed hard by exploration), so the split aligns structure with
|
||||
reality and removes concurrent-session git contention structurally rather than mitigating it. The
|
||||
marketplace must stay installable and live at **every** step (mixed in-repo + external sources).
|
||||
|
||||
This plan covers the **Claude-run, local, reversible half**: it builds the migration tooling, runs a
|
||||
full **local dry-run extraction with standalone validation**, and stages (but never runs) the
|
||||
outward steps — all with **NULL push** (honoring D8). The **operator-run irreversible window**
|
||||
(Forgejo repo creation, first push, live `marketplace.json` cutover) is captured as a runbook
|
||||
deliverable (Step 10) + the Execution Strategy, not as machine-verified steps, because those are not
|
||||
local file edits.
|
||||
|
||||
Every decision below traces to the brief: the goal (§3), success criteria SC1–SC8 (§4), the scope
|
||||
fence (§5), decisions D1–D8 (§7), risks R1–R8 (§8), the five §9 open questions, and the execution
|
||||
model (§10). Exploration surfaced **six findings the brief under-scoped** (F1–F6 below); the plan
|
||||
absorbs all six.
|
||||
|
||||
### Exploration deltas the brief did not have (load-bearing)
|
||||
|
||||
- **F1 — Plugin renames split history.** `voyage`←`ultraplan-local` (136 commits), `llm-security`←
|
||||
`llm-security-copilot` (3), `linkedin-studio`←`linkedin-thought-leadership` (50). A single-path
|
||||
filter (`--subdirectory-filter plugins/voyage`) **silently drops** the pre-rename history. Renamed
|
||||
plugins MUST be extracted with multiple `--path` arguments (both names) → SC6.
|
||||
- **F2 — HTTPS, not `ssh://`.** The brief's §6 `ssh://git@…` `url` source is **unverified/likely
|
||||
rejected** (Claude Code issue #9740 shows `marketplace add` rejecting SSH). HTTPS
|
||||
(`https://git.fromaitochitta.com/open/<repo>.git`) is the only form verified-accepted on both the
|
||||
`url` source schema and `marketplace add`. The discriminator key is **`source`** (brief correct),
|
||||
not `type`. Forgejo has **no** background auto-update token → **public** repos sidestep the gap →
|
||||
answers §9 visibility.
|
||||
- **F3 — `ms-ai-architect` is a 148 MB screenshot blob-bomb** (86% of `.git`; 26 blobs already
|
||||
deleted from HEAD but alive in history). filter-repo resurrects them → blob-strip decision.
|
||||
- **F4 — `config-audit` snapshot tests embed the absolute path** (verified directly): the
|
||||
`default-output/` snapshots assert byte-equal output whose deep per-file paths (e.g.
|
||||
`…/tests/fixtures/marketplace-medium/CLAUDE.md`) are **not** normalized
|
||||
(`normalizeScanOrchestrator` scrubs only `meta.target`/`timestamp`/`duration_ms`), so they break at
|
||||
a new clone path; the `v5.0.0/` back-compat snapshots (loaded by `json-backcompat.test.mjs` +
|
||||
`raw-backcompat.test.mjs`) additionally embed a sibling marketplace (`ktg-privat`) + deleted
|
||||
plugins from their original capture. SC2 blocker → real fix uses the existing `UPDATE_SNAPSHOT=1`
|
||||
seam for the rebasable suite + by-name exclusion of the 2 frozen back-compat tests (Step 7).
|
||||
- **F5 — Tag namespace is inconsistent** (3 schemes; bare `v5.0.0`/`v7.7.2` carry no plugin
|
||||
identity; `v3.4.1`→deleted `ultraplan-local`). filter-repo copies ALL tags into EACH extract →
|
||||
strip-all-then-re-tag.
|
||||
- **F6 — README/metadata reference rot.** Every plugin `README.md:7` links `../../README.md`
|
||||
(AI-disclosure) → 404 standalone; root-only `.mailmap`/`.gitleaks.*` don't travel with
|
||||
`--path plugins/x`; `ai-psychosis/plugin.json` already points at a standalone URL while others
|
||||
point at the monorepo (reconcile).
|
||||
|
||||
### The five §9 open questions — RESOLVED with evidence
|
||||
|
||||
1. **Exact filter-repo invocation + `shared/` history into DS.** For non-renamed plugins:
|
||||
`git filter-repo --path plugins/<name>/ --path-rename plugins/<name>/:` (≡
|
||||
`--subdirectory-filter plugins/<name>`). For **renamed** plugins: explicit multi-`--path` with a
|
||||
`--path-rename` per old+new name (F1). DS: `git filter-repo --subdirectory-filter shared` —
|
||||
**yes**, preserve `shared/` history into the DS repo (clean: 10/12 `shared/`-touching commits are
|
||||
pure-shared, 2 are single-plugin-entangled but conflict-free). All extractions run on a
|
||||
`git clone --no-local` fresh clone (avoids filter-repo's spurious freshness abort on local
|
||||
clones; do **not** paper over with `--force`).
|
||||
2. **True DS consumers.** Exactly **2** — `llm-security`, `ms-ai-architect` (only two with a
|
||||
`playground/vendor/` dir). `claude-design` + `human-friendly-style` have **no** `playground/` →
|
||||
don't need DS. The "6" in `shared/PLAYGROUND-MAINTENANCE.md:5` is stale (one entry,
|
||||
`ultraplan-local`, is the ghost pre-rename name for `voyage`). DS-repo standup scope = 2.
|
||||
3. **Repo visibility.** **Public** (recommended) — sidesteps the Forgejo no-auto-update-token gap
|
||||
(F2), matches the existing `open/` model, lets HTTPS clone work without credential setup.
|
||||
Operator confirms at the window.
|
||||
4. **`playground-examples/` home.** **DS repo.** It is DS-coupled (every HTML references
|
||||
`playground-design-system`; `PLAYGROUND-MAINTENANCE.md` anchors `ros-lier-kommune.html` to
|
||||
ms-ai-architect renderers). Travels with the DS repo, not the thin catalog.
|
||||
5. **Per-repo CHANGELOG.** **Carry existing** — filter-repo preserves each plugin's `CHANGELOG.md`
|
||||
and its commit history (D2); tags are re-created cleanly per repo (F5).
|
||||
|
||||
## Architecture Diagram
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
subgraph BEFORE["Monorepo (today) — open/ktg-plugin-marketplace"]
|
||||
MJ0[".claude-plugin/marketplace.json<br/>10x ./plugins/x"]
|
||||
PLUGS["plugins/ x10"]
|
||||
SH["shared/<br/>design-system + examples"]
|
||||
SYNC["scripts/sync-design-system.mjs"]
|
||||
SYNC -->|vendors| PLUGS
|
||||
SH -->|source| SYNC
|
||||
end
|
||||
|
||||
subgraph AFTER["Polyrepo (target) — same URL"]
|
||||
CAT["open/ktg-plugin-marketplace<br/>THIN CATALOG<br/>marketplace.json (external+ref) · README · GOVERNANCE · CONVENTIONS · .mailmap · .gitleaks"]
|
||||
DS["open/playground-design-system<br/>design-system + examples + sync-design-system.mjs(--source/--check)"]
|
||||
R1["open/voyage (v5.1.1)"]
|
||||
R2["open/llm-security (v7.7.2)"]
|
||||
R3["open/linkedin-studio (v0.4.0)"]
|
||||
Rn["open/<plugin> ... x10 total"]
|
||||
CAT -.->|url + ref pin HTTPS| R1
|
||||
CAT -.->|url + ref pin HTTPS| R2
|
||||
CAT -.->|url + ref pin HTTPS| R3
|
||||
CAT -.->|url + ref pin HTTPS| Rn
|
||||
DS -->|vendor via re-homed sync| R2
|
||||
DS -->|vendor via re-homed sync| Rn
|
||||
end
|
||||
|
||||
BEFORE ==>|filter-repo extraction<br/>NULL push until window| AFTER
|
||||
```
|
||||
|
||||
## Codebase Analysis
|
||||
|
||||
- **Tech stack:** Node.js `.mjs` scripts (zero npm deps house style, `node:test`), bash 3.2 hooks/
|
||||
validators, one TypeScript analytics subtree (`linkedin-studio/scripts/analytics`, needs `npm
|
||||
install` + `tsx`). Git on Forgejo (`git.fromaitochitta.com`, org `open/`). 1890 source files,
|
||||
741 commits, `.git` 144 MB.
|
||||
- **Key patterns:** Each plugin self-contained (`.claude-plugin/plugin.json` + `README` + `CLAUDE.md`,
|
||||
optional `hooks/ tests/ scripts/ playground/`). Hooks resolve via `${CLAUDE_PLUGIN_ROOT}`
|
||||
(relocation-invariant — confirmed). DS is **vendored** (`playground/vendor/`), never live-linked.
|
||||
- **Relevant files (verified):**
|
||||
- `.claude-plugin/marketplace.json` — 10 entries, all `source: "./plugins/<name>"`, entry shape
|
||||
`{name, source, description}` (no `version`/`ref` today).
|
||||
- `scripts/sync-design-system.mjs` — SOURCE at `:26-27` (`MARKETPLACE_ROOT/shared/…`), target at
|
||||
`:146` (`plugins/<name>/playground/vendor/…`), SHA-256 `MANIFEST.json` + drift-guard at
|
||||
`:118-127,151-156`. **No `--source` flag today** (`parseArgs` `:30-46`).
|
||||
- `.gitignore` — `plugins/*/` patterns at `:9` (`plugins/*/.claude/`) and `:12`
|
||||
(`plugins/*/reports/*-beskrivelse.*`); global patterns (`STATE.md`, `*.local.md`, …) stay.
|
||||
- `.gitleaksignore` — 2 plugin-prefixed fingerprints (`:2` llm-security, `:5` linkedin-studio);
|
||||
`.gitleaks.toml` allowlist `:13` already plugin-relative.
|
||||
- `plugins/linkedin-studio/scripts/analytics/src/utils/storage.ts:18-33,48` — marker resolver
|
||||
(standalone-safe primary) + `../../../../` fallback (latent depth assumption, dead branch).
|
||||
- `plugins/voyage/tests/helpers/hook-helper.mjs:4` — verbatim copy (C7), stays a copy.
|
||||
- **Reusable code:** the existing SHA-256 `MANIFEST.json` + `detectDrift` in
|
||||
`sync-design-system.mjs` already implements SC5's hash mechanism — extend, don't rebuild. The
|
||||
`validate-plugin.sh` structure-validator in `claude-design/tests/` + `ms-ai-architect/tests/` is
|
||||
the pattern to port to the two test-less plugins (okr, human-friendly-style).
|
||||
- **Recent git activity:** HEAD `f2d41c8` (ratified brief, **3 commits ahead of origin, unpushed**).
|
||||
Only `main`, no feature branches. Cross-plugin bleed commits confirmed (`f460814` touches 10
|
||||
plugins; `69610d4` 2) — ~1.8% of commits span 3+ plugins, mostly marketplace-wide doc sweeps.
|
||||
|
||||
## Research Sources
|
||||
|
||||
| Topic | Source | Key findings | Confidence |
|
||||
|---|---|---|---|
|
||||
| `git filter-repo` subdir extract | [git-filter-repo man page](https://manpages.debian.org/testing/git-filter-repo/git-filter-repo.1.en.html) | `--subdirectory-filter X` ≡ `--path X/ --path-rename X/:`; removes `origin`; rewrites tags; aborts on non-fresh clone | High |
|
||||
| Local-clone freshness | [filter-repo #38](https://github.com/newren/git-filter-repo/issues/38) | `git clone --no-local` from a local source (not `--force`) | High |
|
||||
| marketplace.json external/mixed/ref | [plugin-marketplaces](https://code.claude.com/docs/en/plugin-marketplaces), [plugins-reference](https://code.claude.com/docs/en/plugins-reference) | discriminator is `source` not `type`; `{source:"url",url,ref}`; mixed in-repo+external in one file **supported**; `/plugin marketplace update` refreshes a bumped ref | High |
|
||||
| SSH rejection | [claude-code #9740](https://github.com/anthropics/claude-code/issues/9740) (open) | `marketplace add` rejects SSH URLs → **use HTTPS** | High (use HTTPS) |
|
||||
| Forgejo auto-update token | plugin-marketplaces token table | no `FORGEJO_TOKEN` for bg auto-update of private repos → prefer **public** | Medium |
|
||||
| Forgejo repo create | [Forgejo api-usage](https://forgejo.org/docs/latest/user/api-usage/) | `POST /api/v1/orgs/{org}/repos`, header `Authorization: token …`, body `{name,private,auto_init:false,default_branch}` | High |
|
||||
|
||||
## Implementation Plan
|
||||
|
||||
> **Execution invariants (apply to every step):**
|
||||
> - **D8 — NULL PUSH.** Every `git commit` here is **local only**; no `git push` until the authorized
|
||||
> operator window. Checkpoints are local commits held on `main` ahead of origin.
|
||||
> - **All extraction runs from a `git clone --no-local` fresh clone** in a throwaway workspace
|
||||
> (`$WORK`, default `/tmp/polyrepo-migration`), never the working checkout (R4). The working repo
|
||||
> is read-only to every extraction.
|
||||
> - Migration tooling lives under `docs/marketplace-polyrepo-migration/migration/` so it sits beside
|
||||
> the brief/plan and is removed/archived at thinning, never polluting the live catalog.
|
||||
> - The single source of truth for per-plugin paths/versions/flags is `migration/plugin-map.json`
|
||||
> (Step 1); every later script reads it.
|
||||
|
||||
### Step 1: Preflight, archive tag, and the plugin map
|
||||
|
||||
- **Files:** `docs/marketplace-polyrepo-migration/migration/00-preflight.sh`,
|
||||
`docs/marketplace-polyrepo-migration/migration/plugin-map.json`,
|
||||
`docs/marketplace-polyrepo-migration/migration/00-preflight.test.mjs`
|
||||
- **Changes:** (new files) `00-preflight.sh` asserts tooling and repo hygiene: `git filter-repo
|
||||
--version` returns a real version, `python3 --version` ≥ 3.6, HEAD is `f2d41c8` **or a descendant**
|
||||
on `main`, `origin/main` is behind (unpushed state expected). **Hygiene check is relaxed and
|
||||
idempotent (M12 fix):** it aborts only if `git status --porcelain plugins shared
|
||||
.claude-plugin/marketplace.json scripts` is non-empty (uncommitted change to a migration-sensitive
|
||||
surface) — committed `migration/` files and unrelated untracked docs are tolerated, so re-running
|
||||
preflight mid-migration does not abort. It creates the **local** annotated tag
|
||||
`pre-polyrepo-archive` at HEAD (D2; pushed later in the window) idempotently. **It establishes the
|
||||
dedicated extraction source (R4 / M6 fix):** `git clone --no-local "$REPO_ROOT"
|
||||
"$WORK/_mirror"` **once**, at the pinned HEAD — `$MIRROR` is the single source every per-target
|
||||
extraction clones from, so concurrent commits to the operator's working checkout can never affect
|
||||
any extraction (the working checkout is never read by filter-repo). It also **enumerates
|
||||
blob-strip candidates (M4 fix):** lists every blob >1 MB in ms-ai-architect history with its path,
|
||||
and emits `blob_strip_safe: true` into `plugin-map.json` iff **all** >1 MB blobs are under
|
||||
`playground/screenshots/` (else `false` → the surgical `--path-glob` strategy is selected instead
|
||||
of size-strip, decided deterministically, no runtime prompt). `plugin-map.json` encodes, per
|
||||
extraction target: the rename-aware `--path` list, the `--path-rename` map, the release tag (from
|
||||
`plugin.json` `version`), the new **HTTPS** repo URL, `has_vendor_ds` (true only for llm-security +
|
||||
ms-ai-architect), `blob_strip` + `blob_strip_safe` (ms-ai-architect), `test_cmd`, and
|
||||
`standalone_caveat`. Includes the DS target (`shared/` → `open/playground-design-system`) and an
|
||||
explicit `drop: ["plugins/ultra-cc-architect/"]` note (removed plugin, already its own repo — its
|
||||
history is dropped, not extracted).
|
||||
- **Reuses:** version strings from each `plugins/<name>/.claude-plugin/plugin.json` (task-finder
|
||||
table); churn order from git-historian (voyage→llm-security→linkedin-studio→ms-ai-architect).
|
||||
- **Test first:**
|
||||
- File: `docs/marketplace-polyrepo-migration/migration/00-preflight.test.mjs` (new)
|
||||
- Verifies: `plugin-map.json` parses; has exactly 11 targets (10 plugins + DS); the 3 renamed
|
||||
plugins carry ≥2 `--path` entries each; every plugin's `repo_url` is `https://` (not `ssh://`);
|
||||
ms-ai-architect has `blob_strip: true`; only llm-security + ms-ai-architect have
|
||||
`has_vendor_ds: true`.
|
||||
- Pattern: `plugins/voyage/tests/validators/*.test.mjs` (node:test style)
|
||||
- **Verify:** `bash docs/marketplace-polyrepo-migration/migration/00-preflight.sh --dry-run` →
|
||||
expected: prints `PREFLIGHT OK` and the 11-target table, creates no tag in `--dry-run`; then
|
||||
`node --test docs/marketplace-polyrepo-migration/migration/00-preflight.test.mjs` → expected:
|
||||
`pass`.
|
||||
- **On failure:** escalate — if tooling is missing (`git filter-repo` absent), stop and instruct
|
||||
`brew install git-filter-repo`; do not proceed.
|
||||
- **Checkpoint:** `git commit -m "chore(migration): preflight script + rename-aware plugin map"`
|
||||
- **Manifest:**
|
||||
```yaml
|
||||
manifest:
|
||||
expected_paths:
|
||||
- docs/marketplace-polyrepo-migration/migration/00-preflight.sh
|
||||
- docs/marketplace-polyrepo-migration/migration/plugin-map.json
|
||||
- docs/marketplace-polyrepo-migration/migration/00-preflight.test.mjs
|
||||
min_file_count: 3
|
||||
commit_message_pattern: "^chore\\(migration\\): preflight script \\+ rename-aware plugin map$"
|
||||
bash_syntax_check:
|
||||
- docs/marketplace-polyrepo-migration/migration/00-preflight.sh
|
||||
forbidden_paths:
|
||||
- .claude-plugin/marketplace.json
|
||||
- scripts/sync-design-system.mjs
|
||||
must_contain:
|
||||
- path: docs/marketplace-polyrepo-migration/migration/plugin-map.json
|
||||
pattern: "ultraplan-local"
|
||||
- path: docs/marketplace-polyrepo-migration/migration/plugin-map.json
|
||||
pattern: "https://git.fromaitochitta.com/open/"
|
||||
```
|
||||
|
||||
### Step 2: Re-home and extend sync-design-system.mjs (--source + --check)
|
||||
|
||||
- **Files:** `scripts/sync-design-system.mjs`, `scripts/sync-design-system.test.mjs`
|
||||
- **Changes:** Add a `--source <dir>` flag so the script vendors from an arbitrary DS checkout
|
||||
(the re-homed DS repo) instead of the hardcoded `MARKETPLACE_ROOT/shared/playground-design-system`
|
||||
(`:26-27`); keep the in-repo path as the default fallback so the script still works pre-migration.
|
||||
Add a `--check` mode that re-hashes a target plugin's vendored tree against its committed
|
||||
`MANIFEST.json` and exits non-zero on drift, with **no** source required — making SC5 a single
|
||||
runnable command in a clean clone (D3). Resolve the plugin/target dir from a `--target <dir>`
|
||||
arg when `MARKETPLACE_ROOT/plugins/<name>` no longer exists. This is migration **rewiring**, not a
|
||||
plugin-functionality change (non-goal §5 untouched). The modified script + test travel into the DS
|
||||
repo at standup (Step on the DS target) and are removed from the catalog at thinning (Step 9).
|
||||
- **Reuses:** existing `detectDrift` + SHA-256 `MANIFEST.json` writer (`:118-127`) — `--check`
|
||||
reuses the same hashing.
|
||||
- **Test first:**
|
||||
- File: `scripts/sync-design-system.test.mjs` (new)
|
||||
- Verifies: `--source <tmpDS>` vendors byte-identically to the default in-repo source (SC5 parity);
|
||||
`--check` passes on an unmodified vendored tree and fails (exit 2) after a tampered byte;
|
||||
omitting `--source` still resolves the in-repo default.
|
||||
- Pattern: `plugins/voyage/tests/scripts/*.test.mjs`
|
||||
- **Verify:** `node --test scripts/sync-design-system.test.mjs` → expected: `pass` (all assertions);
|
||||
then `node scripts/sync-design-system.mjs --check ms-ai-architect` → expected: `MANIFEST OK`
|
||||
against the current vendored tree.
|
||||
- **On failure:** revert — `git checkout -- scripts/sync-design-system.mjs
|
||||
scripts/sync-design-system.test.mjs`; the legacy in-repo sync still works, so this is non-blocking
|
||||
for the rest until retried.
|
||||
- **Checkpoint:** `git commit -m "feat(migration): sync-design-system --source + --check for DS re-home"`
|
||||
- **Manifest:**
|
||||
```yaml
|
||||
manifest:
|
||||
expected_paths:
|
||||
- scripts/sync-design-system.mjs
|
||||
- scripts/sync-design-system.test.mjs
|
||||
min_file_count: 2
|
||||
commit_message_pattern: "^feat\\(migration\\): sync-design-system --source \\+ --check for DS re-home$"
|
||||
bash_syntax_check: []
|
||||
forbidden_paths:
|
||||
- .claude-plugin/marketplace.json
|
||||
must_contain:
|
||||
- path: scripts/sync-design-system.mjs
|
||||
pattern: "--source"
|
||||
- path: scripts/sync-design-system.mjs
|
||||
pattern: "--check"
|
||||
```
|
||||
|
||||
### Step 3: Rename-aware extraction driver
|
||||
|
||||
- **Files:** `docs/marketplace-polyrepo-migration/migration/10-extract.sh`,
|
||||
`docs/marketplace-polyrepo-migration/migration/10-extract.test.mjs`
|
||||
- **Changes:** (new files) Given a target key from `plugin-map.json`, it: `git clone --no-local
|
||||
"$WORK/_mirror" "$WORK/<key>"` (clones from the **dedicated mirror** built in Step 1, never the
|
||||
working checkout — R4/M6); runs **a single `git filter-repo` invocation** that composes all history
|
||||
rewrites in one pass (M5 fix — avoids the second-pass `--force` abort): the rename-aware
|
||||
`--path`/`--path-rename` set from the map (single path for non-renamed; **both** names for
|
||||
voyage/llm-security/linkedin-studio — F1) **plus**, for ms-ai-architect, the blob strip selected
|
||||
deterministically from `blob_strip_safe` (Step 1): `--strip-blobs-bigger-than 1M` when all >1 MB
|
||||
blobs are screenshots, else `--path-glob '!plugins/ms-ai-architect/playground/screenshots/*'` —
|
||||
no runtime prompt (M4/F3). filter-repo composes `--path`, `--path-rename`, and
|
||||
`--strip-blobs-bigger-than` in one invocation (verified against the man page). **After** the filter
|
||||
completes (so the tag points at the final rewritten HEAD — M5 ordering fix), it strips every tag
|
||||
filter-repo carried over and re-creates **exactly one** annotated tag `v<version>` at HEAD (F5).
|
||||
Idempotent: re-running wipes `$WORK/<key>` first. **No push.**
|
||||
- **Reuses:** `plugin-map.json` (Step 1); `git clone --no-local` per research-scout.
|
||||
- **Test first:**
|
||||
- File: `docs/marketplace-polyrepo-migration/migration/10-extract.test.mjs` (new)
|
||||
- Verifies (runs the driver against the live monorepo into a temp `$WORK`): the `voyage` extract's
|
||||
`git log --all` contains commits whose original paths were under **both** `plugins/voyage/` and
|
||||
`plugins/ultraplan-local/` (≥150 commits, proving F1 handled); the extract `git tag` lists
|
||||
exactly `v5.1.1` and nothing else; the extract has **no** `plugins/` prefix at root (contents at
|
||||
repo root); `origin` remote is absent post-filter.
|
||||
- Pattern: integration test style in `plugins/voyage/tests/integration/*.test.mjs`
|
||||
- **Verify (self-contained — M6):** `bash docs/marketplace-polyrepo-migration/migration/00-preflight.sh
|
||||
&& bash docs/marketplace-polyrepo-migration/migration/10-extract.sh voyage` (preflight builds
|
||||
`$WORK/_mirror` if absent; extract is self-healing) → expected:
|
||||
`EXTRACT OK voyage → $WORK/voyage (257 commits, tag v5.1.1)`; then
|
||||
`git -C "$WORK/voyage" log --oneline | wc -l` → expected: `≥ 250`; `git -C "$WORK/voyage" tag` →
|
||||
expected: exactly `v5.1.1`.
|
||||
- **On failure:** retry — if filter-repo aborts on freshness, confirm the source is `$WORK/_mirror`
|
||||
(a fresh `--no-local` clone); if a rename path is missing, the test catches the dropped-history
|
||||
count. Revert leaves no working-tree change (extract is in `$WORK`).
|
||||
- **Checkpoint:** `git commit -m "chore(migration): rename-aware filter-repo extraction driver"`
|
||||
- **Manifest:**
|
||||
```yaml
|
||||
manifest:
|
||||
expected_paths:
|
||||
- docs/marketplace-polyrepo-migration/migration/10-extract.sh
|
||||
- docs/marketplace-polyrepo-migration/migration/10-extract.test.mjs
|
||||
min_file_count: 2
|
||||
commit_message_pattern: "^chore\\(migration\\): rename-aware filter-repo extraction driver$"
|
||||
bash_syntax_check:
|
||||
- docs/marketplace-polyrepo-migration/migration/10-extract.sh
|
||||
forbidden_paths:
|
||||
- .claude-plugin/marketplace.json
|
||||
- plugins
|
||||
must_contain:
|
||||
- path: docs/marketplace-polyrepo-migration/migration/10-extract.sh
|
||||
pattern: "no-local"
|
||||
- path: docs/marketplace-polyrepo-migration/migration/10-extract.sh
|
||||
pattern: "ultraplan-local"
|
||||
```
|
||||
|
||||
### Step 4: Per-repo config re-rooting (.gitignore, .gitleaks, .mailmap)
|
||||
|
||||
- **Files:** `docs/marketplace-polyrepo-migration/migration/20-rehome-config.sh`,
|
||||
`docs/marketplace-polyrepo-migration/migration/templates/gitignore.plugin.tmpl`,
|
||||
`docs/marketplace-polyrepo-migration/migration/20-rehome-config.test.mjs`
|
||||
- **Changes:** (new files) Operates on an extracted repo in `$WORK/<key>`: writes a re-rooted
|
||||
`.gitignore` from the template (the `plugins/*/` prefixes stripped → `.claude/`,
|
||||
`reports/*-beskrivelse.*`; the global `STATE.md`/`*.local.md`/OS patterns kept — F6/C3); generates
|
||||
a per-plugin `.gitleaks.toml` from the root baseline and a `.gitleaksignore` with the matching
|
||||
fingerprint **re-rooted by dropping ONLY the `plugins/<name>/` path prefix and keeping the full
|
||||
`:rule-id:line` suffix** (M7 fix — the gitleaks fingerprint format is `path:rule-id:line`, not
|
||||
`path:line`; dropping the rule-id yields a fingerprint that no longer suppresses) — only for
|
||||
llm-security (`examples/malicious-skill-demo/evil-project-health/lib/telemetry.mjs:generic-api-key:18`)
|
||||
and linkedin-studio (`hooks/prompts/content-quality-gate.md:linkedin-client-id:14`); copies the
|
||||
root `.mailmap` into every extract (F6 — it does not travel with `--path`). For the DS target it
|
||||
also copies the modified `sync-design-system.mjs` + test (Step 2) and
|
||||
`shared/PLAYGROUND-MAINTENANCE.md` + `playground-examples/` into the DS repo root (§9 Q4).
|
||||
- **Reuses:** root `.gitignore` (`:9,:12`), `.gitleaks.toml` (`:13`), `.gitleaksignore` (`:2,:5`),
|
||||
`.mailmap`.
|
||||
- **Test first:**
|
||||
- File: `docs/marketplace-polyrepo-migration/migration/20-rehome-config.test.mjs` (new)
|
||||
- Verifies (against a freshly extracted llm-security in `$WORK`): the new `.gitignore` contains
|
||||
`.claude/` but **no** `plugins/*/` prefix; the re-rooted `.gitleaksignore` fingerprint is exactly
|
||||
`examples/malicious-skill-demo/evil-project-health/lib/telemetry.mjs:generic-api-key:18` (no
|
||||
`plugins/llm-security/` prefix, **rule-id `generic-api-key` retained**); `.mailmap` exists; a
|
||||
plugin with no fingerprint (graceful-handoff) gets a `.gitleaks.toml` but an empty/absent
|
||||
`.gitleaksignore`.
|
||||
- Pattern: `plugins/config-audit/tests/lib/*.test.mjs`
|
||||
- **Verify:** `bash …/20-rehome-config.sh llm-security` then `git -C /tmp/polyrepo-migration/llm-security
|
||||
check-ignore .claude/x` → expected: `.claude/x` (ignored); `grep -c 'plugins/' /tmp/polyrepo-migration/llm-security/.gitleaksignore`
|
||||
→ expected: `0`.
|
||||
- **On failure:** retry — config generation is idempotent; revert leaves working tree clean.
|
||||
- **Checkpoint:** `git commit -m "chore(migration): per-repo gitignore/gitleaks/mailmap re-rooting"`
|
||||
- **Manifest:**
|
||||
```yaml
|
||||
manifest:
|
||||
expected_paths:
|
||||
- docs/marketplace-polyrepo-migration/migration/20-rehome-config.sh
|
||||
- docs/marketplace-polyrepo-migration/migration/templates/gitignore.plugin.tmpl
|
||||
- docs/marketplace-polyrepo-migration/migration/20-rehome-config.test.mjs
|
||||
min_file_count: 3
|
||||
commit_message_pattern: "^chore\\(migration\\): per-repo gitignore/gitleaks/mailmap re-rooting$"
|
||||
bash_syntax_check:
|
||||
- docs/marketplace-polyrepo-migration/migration/20-rehome-config.sh
|
||||
forbidden_paths:
|
||||
- .gitignore
|
||||
- .gitleaks.toml
|
||||
- .gitleaksignore
|
||||
must_contain:
|
||||
- path: docs/marketplace-polyrepo-migration/migration/20-rehome-config.sh
|
||||
pattern: "mailmap"
|
||||
```
|
||||
|
||||
### Step 5: Reference-rot rewriter (README links + plugin.json/package.json URLs)
|
||||
|
||||
- **Files:** `docs/marketplace-polyrepo-migration/migration/30-fix-references.mjs`,
|
||||
`docs/marketplace-polyrepo-migration/migration/30-fix-references.test.mjs`
|
||||
- **Changes:** (new files) Operates on an extracted repo in `$WORK/<key>`: replaces the
|
||||
`[Full disclosure →](../../README.md#ai-generated-code-disclosure)` footnote at every plugin
|
||||
`README.md:7` with the **inlined** disclosure text (M13 — inline chosen over a cross-repo link:
|
||||
deterministic, self-contained, no dangling anchor); fixes `graceful-handoff/README.md:355` and any
|
||||
other `../../README.md` / `../../.claude-plugin/marketplace.json` reference (e.g. linkedin-studio
|
||||
remediation docs); sets `plugin.json` `repository` and `package.json` `homepage`/`repository` to
|
||||
the new standalone `open/<plugin>` HTTPS URL. **Scope note (M14):** `ai-psychosis/plugin.json`
|
||||
**already** points at `open/ai-psychosis` (verified) — for it this is a verify-only no-op; the real
|
||||
reconcile work is the **9 other plugins** still pointing at the monorepo URL/subpath (e.g.
|
||||
linkedin-studio, llm-security `package.json` homepage). Reads URLs from `plugin-map.json`. Reports
|
||||
every file it rewrote.
|
||||
- **Reuses:** `plugin-map.json` repo URLs.
|
||||
- **Test first:**
|
||||
- File: `docs/marketplace-polyrepo-migration/migration/30-fix-references.test.mjs` (new)
|
||||
- Verifies (against an extracted graceful-handoff): no `../../README.md` substring remains in any
|
||||
`*.md`; `plugin.json.repository` ends with `/open/graceful-handoff`; an idempotent second run
|
||||
makes zero changes.
|
||||
- Pattern: `plugins/linkedin-studio/render/__tests__/*.test.mjs`
|
||||
- **Verify:** `node …/30-fix-references.mjs graceful-handoff` then `grep -rn "\.\./\.\./README.md"
|
||||
/tmp/polyrepo-migration/graceful-handoff` → expected: no matches (exit 1 from grep).
|
||||
- **On failure:** retry — rewriter is idempotent and reports diffs; revert leaves working tree clean.
|
||||
- **Checkpoint:** `git commit -m "chore(migration): standalone reference-rot rewriter"`
|
||||
- **Manifest:**
|
||||
```yaml
|
||||
manifest:
|
||||
expected_paths:
|
||||
- docs/marketplace-polyrepo-migration/migration/30-fix-references.mjs
|
||||
- docs/marketplace-polyrepo-migration/migration/30-fix-references.test.mjs
|
||||
min_file_count: 2
|
||||
commit_message_pattern: "^chore\\(migration\\): standalone reference-rot rewriter$"
|
||||
bash_syntax_check: []
|
||||
forbidden_paths:
|
||||
- plugins
|
||||
must_contain:
|
||||
- path: docs/marketplace-polyrepo-migration/migration/30-fix-references.mjs
|
||||
pattern: "ai-generated-code-disclosure"
|
||||
```
|
||||
|
||||
### Step 6: Standalone validation harness (SC2 + SC7)
|
||||
|
||||
- **Files:** `docs/marketplace-polyrepo-migration/migration/40-validate-standalone.sh`,
|
||||
`docs/marketplace-polyrepo-migration/migration/40-validate-standalone.test.mjs`
|
||||
- **Changes:** (new files) Per `plugin-map.json` `test_cmd`: clones the extracted repo to a clean
|
||||
`/tmp/claude-*` dir (no marketplace parent) and runs its self-validate command in **the runner the
|
||||
plugin itself declares** — `node --test 'tests/**/*.test.mjs'` (quoted globstar, Node-≥21 native;
|
||||
config-audit/llm-security/voyage) or `node --test <dir>/*.test.mjs` glob for plugins with a flat
|
||||
layout — **never `node --test <dir>`** (Node 25 gotcha). Per-plugin caveats baked in:
|
||||
`ai-psychosis` runs from repo root (relative-fixture cwd); **`linkedin-studio` SC2 is explicitly
|
||||
two-tier (M11):** the `.mjs` core (`node --test hooks/scripts/__tests__/*.test.mjs
|
||||
render/__tests__/*.test.mjs agents/__tests__/*.test.mjs`) is the **hard gate** (must pass with zero
|
||||
deps); the TS analytics suite (`cd scripts/analytics && npm install && npm test`) is reported as a
|
||||
**separate ADVISORY line — network-required, NOT a clean-room hard gate** (not silently waived,
|
||||
explicitly labelled non-blocking for SC2). `claude-design` + `ms-ai-architect` run `bash
|
||||
tests/validate-plugin.sh`; `okr` + `human-friendly-style` run the ported
|
||||
`validate-plugin.generic.sh` structure check (Step 7 provides it) since they have no unit suite
|
||||
(H2). **`voyage/.forgejo/` survival (M17):** for voyage, assert the extract still contains
|
||||
`.forgejo/ISSUE_TEMPLATE/` (it travels under `--path plugins/voyage/`) and that no file in it
|
||||
references a monorepo-relative path (it holds only issue templates, no workflows — confirmed).
|
||||
SC7 assertions for every repo: `git ls-files | grep -E 'STATE\.md|\.local\.md$'` is empty
|
||||
**except** the intentional tracked `okr/templates/okr.local.md.template`; no `../../README.md`
|
||||
remains; `.gitignore` ignores `.claude/`. Emits a per-repo PASS/FAIL table.
|
||||
- **Reuses:** the per-plugin command table (test-strategist); `validate-plugin.sh` pattern.
|
||||
- **Test first:**
|
||||
- File: (the harness is itself the test surface) — assert via a smoke run that the harness exits
|
||||
non-zero when fed a deliberately broken extract (a planted tracked `STATE.md`) and zero on a
|
||||
clean one. Encoded as `docs/marketplace-polyrepo-migration/migration/40-validate-standalone.test.mjs`.
|
||||
- Verifies: harness FAILs a repo with a tracked `*.local.md`; PASSes a clean repo; uses glob test
|
||||
form (asserts the command string contains `*.test.mjs`, not a bare dir).
|
||||
- Pattern: `plugins/voyage/tests/synthetic/*.test.mjs`
|
||||
- **Verify:** `bash …/40-validate-standalone.sh graceful-handoff` → expected: `graceful-handoff:
|
||||
PASS (6 tests, standalone-safe)`; `node --test …/40-validate-standalone.test.mjs` → expected:
|
||||
`pass`.
|
||||
- **On failure:** escalate per repo — a FAIL is a genuine SC2/SC7 blocker for that plugin (e.g.
|
||||
config-audit until Step 7); record it, continue other repos, do not mask.
|
||||
- **Checkpoint:** `git commit -m "chore(migration): standalone SC2/SC7 validation harness"`
|
||||
- **Manifest:**
|
||||
```yaml
|
||||
manifest:
|
||||
expected_paths:
|
||||
- docs/marketplace-polyrepo-migration/migration/40-validate-standalone.sh
|
||||
- docs/marketplace-polyrepo-migration/migration/40-validate-standalone.test.mjs
|
||||
min_file_count: 2
|
||||
commit_message_pattern: "^chore\\(migration\\): standalone SC2/SC7 validation harness$"
|
||||
bash_syntax_check:
|
||||
- docs/marketplace-polyrepo-migration/migration/40-validate-standalone.sh
|
||||
forbidden_paths:
|
||||
- plugins
|
||||
must_contain:
|
||||
- path: docs/marketplace-polyrepo-migration/migration/40-validate-standalone.sh
|
||||
pattern: "\\*.test.mjs"
|
||||
```
|
||||
|
||||
### Step 7: Resolve the config-audit SC2 blocker + port a structure-validator to okr/human-friendly-style
|
||||
|
||||
- **Files:** `docs/marketplace-polyrepo-migration/migration/50-config-audit-sc2.sh`,
|
||||
`docs/marketplace-polyrepo-migration/migration/templates/validate-plugin.generic.sh`,
|
||||
`docs/marketplace-polyrepo-migration/migration/50-config-audit-sc2.test.mjs`
|
||||
- **Changes:** (new files) **config-audit (F4 — corrected against verified test code):** config-audit
|
||||
declares its runner as `node --test 'tests/**/*.test.mjs'` (52 files, CLAUDE.md:109). Two distinct
|
||||
portability defects exist (both verified directly):
|
||||
(a) The **rebasable** suite `snapshot-default-output.test.mjs` asserts byte-equal CLI output whose
|
||||
deep per-file paths are **not** normalized (`normalizeScanOrchestrator` scrubs only
|
||||
`meta.target`/`timestamp`/`duration_ms`, `normalizeTokenHotspots` only `duration_ms`), so it breaks
|
||||
at a new clone path. **Fix:** the script runs `UPDATE_SNAPSHOT=1 node --test
|
||||
tests/snapshot-default-output.test.mjs` **inside the clean clone** to re-seed those snapshots at the
|
||||
clone's own path — it then passes (the seam exists, line 33; this is the intended re-approval path).
|
||||
(b) The **frozen back-compat** tests `json-backcompat.test.mjs` + `raw-backcompat.test.mjs` load the
|
||||
`tests/snapshots/v5.0.0/` fixtures, which embed the **original** capture machine's absolute path + a
|
||||
sibling marketplace (`ktg-privat`) + deleted plugins — regenerating them would defeat their
|
||||
byte-stability purpose. **Fix:** these two files are **excluded by name** from config-audit's SC2
|
||||
gate, which runs over the file list `tests/**/*.test.mjs` **minus** those two (deterministic file
|
||||
enumeration, no invented env var). The exclusion + rationale is recorded in the config-audit repo's
|
||||
test README. **This edges the §5 non-goal "NOT changing plugin code"** (it re-seeds fixtures + names
|
||||
an exclusion; no functionality changes) — surfaced as a **brief-correction requiring operator
|
||||
ratification** (header) + Assumption 4. **okr + human-friendly-style (H2):**
|
||||
`validate-plugin.generic.sh` is a ported, parameterized copy of the existing
|
||||
`claude-design/tests/validate-plugin.sh` (valid `plugin.json`, expected command/skill/agent files
|
||||
present, frontmatter parses) so SC2 has a runnable gate for the two test-less plugins.
|
||||
- **Reuses:** `plugins/claude-design/tests/validate-plugin.sh` (the pattern); config-audit's own
|
||||
`UPDATE_SNAPSHOT=1` seam (line 33) + declared runner (CLAUDE.md:109).
|
||||
- **Test first:**
|
||||
- File: `docs/marketplace-polyrepo-migration/migration/50-config-audit-sc2.test.mjs` (new)
|
||||
- Verifies: after the script runs against an extracted config-audit, the SC2 gate (full
|
||||
`tests/**/*.test.mjs` **minus** json-backcompat + raw-backcompat) passes; the gate file list does
|
||||
NOT include the two excluded files; the generic validator PASSes okr and FAILs a plugin with a
|
||||
deleted `plugin.json`.
|
||||
- Pattern: `plugins/config-audit/tests/snapshot-default-output.test.mjs` (the rebasable one)
|
||||
- **Verify:** `bash …/50-config-audit-sc2.sh` then in the extracted clone the emitted SC2 gate
|
||||
command (e.g. ``node --test $(ls tests/**/*.test.mjs | grep -vE 'json-backcompat|raw-backcompat')``)
|
||||
→ expected: `pass`; `bash validate-plugin.generic.sh okr` → expected: `okr: STRUCTURE OK`.
|
||||
- **On failure:** escalate — surface to the operator the choice: accept config-audit SC2 as
|
||||
"full suite minus 2 frozen back-compat tests" (recommended), or invest in normalizing the v5.0.0
|
||||
snapshots' embedded paths (config-audit's own backlog, out of migration scope).
|
||||
- **Checkpoint:** `git commit -m "fix(migration): config-audit SC2 gate (re-seed + back-compat exclusion) + generic validator"`
|
||||
- **Manifest:**
|
||||
```yaml
|
||||
manifest:
|
||||
expected_paths:
|
||||
- docs/marketplace-polyrepo-migration/migration/50-config-audit-sc2.sh
|
||||
- docs/marketplace-polyrepo-migration/migration/templates/validate-plugin.generic.sh
|
||||
- docs/marketplace-polyrepo-migration/migration/50-config-audit-sc2.test.mjs
|
||||
min_file_count: 3
|
||||
commit_message_pattern: "^fix\\(migration\\): config-audit SC2 gate \\(re-seed \\+ back-compat exclusion\\) \\+ generic validator$"
|
||||
bash_syntax_check:
|
||||
- docs/marketplace-polyrepo-migration/migration/50-config-audit-sc2.sh
|
||||
- docs/marketplace-polyrepo-migration/migration/templates/validate-plugin.generic.sh
|
||||
forbidden_paths:
|
||||
- plugins/config-audit
|
||||
must_contain:
|
||||
- path: docs/marketplace-polyrepo-migration/migration/50-config-audit-sc2.sh
|
||||
pattern: "UPDATE_SNAPSHOT"
|
||||
- path: docs/marketplace-polyrepo-migration/migration/50-config-audit-sc2.sh
|
||||
pattern: "json-backcompat"
|
||||
```
|
||||
|
||||
### Step 8: marketplace.json rewriter (mixed-source aware, HTTPS + ref pin)
|
||||
|
||||
- **Files:** `docs/marketplace-polyrepo-migration/migration/60-rewrite-marketplace.mjs`,
|
||||
`docs/marketplace-polyrepo-migration/migration/60-rewrite-marketplace.test.mjs`
|
||||
- **Changes:** (new files — **authored, not run against the live file until the window**) Rewrites a
|
||||
`marketplace.json` so a named subset of entries become external
|
||||
`{"source":"url","url":"https://git.fromaitochitta.com/open/<name>.git","ref":"v<version>"}`
|
||||
(F2 — HTTPS + discriminator `source`, ref = the plugin's release tag, D4) while the rest stay
|
||||
`./plugins/<name>` — enabling the mixed-source intermediate states that keep the marketplace live
|
||||
(SC3/SC8). `--only <names>` flips a subset; `--all` flips everything (final state). Reads versions
|
||||
from `plugin-map.json`; validates output against the marketplace schema (each entry has
|
||||
`name`+`source`+`description`; external entries have a valid `url`+`ref`). Writes to a `--out` path
|
||||
by default (never the live file) so Step 11's dry-run exercises it without violating D8.
|
||||
- **Reuses:** `plugin-map.json`; the current `.claude-plugin/marketplace.json` as the input shape.
|
||||
- **Test first:**
|
||||
- File: `docs/marketplace-polyrepo-migration/migration/60-rewrite-marketplace.test.mjs` (new)
|
||||
- Verifies: `--only voyage` produces a file where `voyage` is an external `url`+`ref:v5.1.1`
|
||||
entry and the other 9 stay `./plugins/…` (mixed-source proven); every external `url` starts
|
||||
`https://` (no `ssh://`); `--all` leaves zero `./plugins/` sources; schema-validates.
|
||||
- Pattern: `plugins/voyage/tests/parsers/*.test.mjs`
|
||||
- **Verify:** `node …/60-rewrite-marketplace.mjs --only voyage --out /tmp/mp-voyage.json` then
|
||||
`grep -c '"./plugins/' /tmp/mp-voyage.json` → expected: `9`; `grep -c 'ssh://' /tmp/mp-voyage.json`
|
||||
→ expected: `0`.
|
||||
- **On failure:** retry — pure transform, idempotent; revert leaves working tree clean.
|
||||
- **Checkpoint:** `git commit -m "chore(migration): mixed-source marketplace.json rewriter (HTTPS+ref)"`
|
||||
- **Manifest:**
|
||||
```yaml
|
||||
manifest:
|
||||
expected_paths:
|
||||
- docs/marketplace-polyrepo-migration/migration/60-rewrite-marketplace.mjs
|
||||
- docs/marketplace-polyrepo-migration/migration/60-rewrite-marketplace.test.mjs
|
||||
min_file_count: 2
|
||||
commit_message_pattern: "^chore\\(migration\\): mixed-source marketplace.json rewriter \\(HTTPS\\+ref\\)$"
|
||||
bash_syntax_check: []
|
||||
forbidden_paths:
|
||||
- .claude-plugin/marketplace.json
|
||||
must_contain:
|
||||
- path: docs/marketplace-polyrepo-migration/migration/60-rewrite-marketplace.mjs
|
||||
pattern: "https://git.fromaitochitta.com/open/"
|
||||
```
|
||||
|
||||
### Step 9: Catalog-thinning script (staged for the window)
|
||||
|
||||
- **Files:** `docs/marketplace-polyrepo-migration/migration/70-thin-catalog.sh`,
|
||||
`docs/marketplace-polyrepo-migration/migration/70-thin-catalog.test.mjs`
|
||||
- **Changes:** (new files — **staged, not run until the window**) Produces the thin-catalog state in a
|
||||
workspace: removes `plugins/` + `shared/` + `scripts/sync-design-system.mjs` (+ its test); extracts
|
||||
the marketplace-wide conventions from the root `CLAUDE.md` into a new `CONVENTIONS.md` (D6); thins
|
||||
the catalog `CLAUDE.md` to catalog-maintenance only; rewrites the landing `README.md` to reference
|
||||
the external plugin repos. **The landing-README rewrite is the place the stale version/count drift
|
||||
is corrected (M15 — coupled explicitly, not a side ride-along):** D6 mandates the README rewrite,
|
||||
and a rewrite that re-states the plugin roster MUST state the verified counts/versions (10 plugins,
|
||||
not ~8; linkedin-studio 0.4.0 not "4.1.0"; config-audit 5.1.0; voyage 5.1.1; …) or it ships a
|
||||
known-false document. The correction is therefore part of the D6 README deliverable, coupled to it,
|
||||
not a separable doc-fix. Verifies the result:
|
||||
`ls` shows **no** `plugins/`, **no** `shared/` (SC4); the catalog retains `marketplace.json`,
|
||||
`README.md`, `GOVERNANCE.md`, `CONVENTIONS.md`, `.mailmap`, a `.gitleaks` baseline.
|
||||
- **Reuses:** root `CLAUDE.md` conventions block; `GOVERNANCE.md` as the conventions sibling.
|
||||
- **Test first:**
|
||||
- File: `docs/marketplace-polyrepo-migration/migration/70-thin-catalog.test.mjs` (new)
|
||||
- Verifies (against a workspace copy): post-thin tree has no `plugins/`, no `shared/`, no
|
||||
`scripts/sync-design-system.mjs`; `CONVENTIONS.md` exists and contains the Forgejo/conventional-
|
||||
commits/three-doc rules; `marketplace.json` + `README.md` + `GOVERNANCE.md` remain.
|
||||
- Pattern: `plugins/config-audit/tests/*.test.mjs`
|
||||
- **Verify:** `bash …/70-thin-catalog.sh --workspace /tmp/thin-catalog` then `ls /tmp/thin-catalog`
|
||||
→ expected: no `plugins`/`shared` entries; `test -f /tmp/thin-catalog/CONVENTIONS.md` → expected:
|
||||
exit 0.
|
||||
- **On failure:** revert — operates only in `--workspace`; the live tree is untouched (forbidden
|
||||
path enforced).
|
||||
- **Checkpoint:** `git commit -m "chore(migration): catalog-thinning script (staged for window)"`
|
||||
- **Manifest:**
|
||||
```yaml
|
||||
manifest:
|
||||
expected_paths:
|
||||
- docs/marketplace-polyrepo-migration/migration/70-thin-catalog.sh
|
||||
- docs/marketplace-polyrepo-migration/migration/70-thin-catalog.test.mjs
|
||||
min_file_count: 2
|
||||
commit_message_pattern: "^chore\\(migration\\): catalog-thinning script \\(staged for window\\)$"
|
||||
bash_syntax_check:
|
||||
- docs/marketplace-polyrepo-migration/migration/70-thin-catalog.sh
|
||||
forbidden_paths:
|
||||
- plugins
|
||||
- shared
|
||||
- CLAUDE.md
|
||||
- README.md
|
||||
must_contain:
|
||||
- path: docs/marketplace-polyrepo-migration/migration/70-thin-catalog.sh
|
||||
pattern: "CONVENTIONS.md"
|
||||
```
|
||||
|
||||
### Step 10: Operator-window runbook (pilot gate + batch order)
|
||||
|
||||
- **Files:** `docs/marketplace-polyrepo-migration/migration/RUNBOOK.md`
|
||||
- **Changes:** (new file) The single document the operator follows in the authorized window. It
|
||||
encodes: **(0) Pilot gate (B3 ordering fix — the flip MUST precede the install-smoke, else the
|
||||
test is vacuous).** Recommend `graceful-handoff` (low-churn, has tests, no DS, no blob bloat).
|
||||
Steps, in order: **(0a)** create it on Forgejo (`POST /api/v1/orgs/open/repos` with
|
||||
`{"name":"graceful-handoff","private":false,"auto_init":false,"default_branch":"main"}`, header
|
||||
`Authorization: token $FORGEJO_TOKEN`); **(0b)** `git remote add origin https://… && git push
|
||||
origin --all && git push origin --tags`; **(0c) flip graceful-handoff's entry in the catalog
|
||||
`marketplace.json` to the external `url`+`ref` form** (`60-rewrite-marketplace.mjs --only
|
||||
graceful-handoff`) and push the catalog in its still-mixed state (9× `./plugins/x` + 1× external;
|
||||
`plugins/` still present, thinning comes last); **(0d)** in a fresh Claude Code session `/plugin
|
||||
marketplace add https://git.fromaitochitta.com/open/ktg-plugin-marketplace.git` + `/plugin install
|
||||
graceful-handoff@ktg-plugin-marketplace`. **Because the entry is now external, the install resolves
|
||||
the Forgejo repo over HTTPS at `ref: v2.1.0` — this is the actual test of the HTTPS + external-source
|
||||
+ ref-pin + Forgejo chain** (F2 / Assumption 1, the highest-stakes unverified link). If 0d fails,
|
||||
STOP — revert the flip (graceful-handoff back to `./plugins/graceful-handoff`, marketplace stays
|
||||
live) and diagnose before touching the other 10. **(1) DS repo first**, **(2) high-churn** (voyage →
|
||||
llm-security → linkedin-studio → ms-ai-architect — corrected order), **(3) low-churn** (config-audit,
|
||||
okr, ai-psychosis, human-friendly-style, claude-design), each: create repo → push → flip its
|
||||
`marketplace.json` entry to external (`--only`) → push catalog → **install-smoke** (SC8) before the
|
||||
next. **(4) Thin the catalog last** (Step 9), push catalog, push the `pre-polyrepo-archive` tag.
|
||||
Includes the visibility decision (public — §9), the `auto_init:false` requirement, the ref-update
|
||||
flow (`/plugin marketplace update`), the per-plugin install-smoke checklist ordered by surface
|
||||
(linkedin-studio 29 cmds first), and the rollback note (a flip is reversible until the `./plugins/x`
|
||||
source is removed; never remove it before the external repo is pushed+verified — R1/SC8).
|
||||
- **Reuses:** all prior scripts (the runbook invokes them); research-scout's verified Forgejo/git
|
||||
commands.
|
||||
- **Test first:** *(documentation deliverable — no unit test; verified by review + the dry-run in
|
||||
Step 11 exercising every script the runbook references)*
|
||||
- **Verify:** `test -f docs/marketplace-polyrepo-migration/migration/RUNBOOK.md && grep -q
|
||||
"auto_init" docs/marketplace-polyrepo-migration/migration/RUNBOOK.md && grep -q "Pilot"
|
||||
docs/marketplace-polyrepo-migration/migration/RUNBOOK.md` → expected: exit 0.
|
||||
- **On failure:** revert — `git checkout -- docs/marketplace-polyrepo-migration/migration/RUNBOOK.md`.
|
||||
- **Checkpoint:** `git commit -m "docs(migration): operator-window runbook (pilot gate + batch order)"`
|
||||
- **Manifest:**
|
||||
```yaml
|
||||
manifest:
|
||||
expected_paths:
|
||||
- docs/marketplace-polyrepo-migration/migration/RUNBOOK.md
|
||||
min_file_count: 1
|
||||
commit_message_pattern: "^docs\\(migration\\): operator-window runbook \\(pilot gate \\+ batch order\\)$"
|
||||
bash_syntax_check: []
|
||||
forbidden_paths:
|
||||
- .claude-plugin/marketplace.json
|
||||
must_contain:
|
||||
- path: docs/marketplace-polyrepo-migration/migration/RUNBOOK.md
|
||||
pattern: "auto_init"
|
||||
- path: docs/marketplace-polyrepo-migration/migration/RUNBOOK.md
|
||||
pattern: "Pilot"
|
||||
```
|
||||
|
||||
### Step 11: Full local dry-run (all 11 targets, NULL push)
|
||||
|
||||
- **Files:** `docs/marketplace-polyrepo-migration/migration/99-dryrun.sh`,
|
||||
`docs/marketplace-polyrepo-migration/migration/99-dryrun.test.mjs`
|
||||
- **Changes:** (new files) `99-dryrun.sh` runs Steps 3→4→5→6 (+ Step 7 for config-audit/okr/
|
||||
human-friendly-style) across **all 11 targets** (10 plugins + DS) into `$WORK`, then exercises
|
||||
Step 8's rewriter (`--all` → `--out`) and Step 9's thinning (`--workspace`), and writes a generated
|
||||
`dryrun-report.md` (an output artifact, not committed): per-target extract commit count (F1 history
|
||||
retention), tag list (F5 clean re-tag), standalone PASS/FAIL (SC2), SC7 state-file check, and the
|
||||
marketplace.json/catalog-thin previews — **with zero pushes and zero live-file mutation**. **It also
|
||||
runs the cross-repo DS-vendor validation (M9 — the gap the pilot can't cover):** with both the DS
|
||||
extract and the ms-ai-architect extract present in `$WORK`, it runs the re-homed sync
|
||||
`node scripts/sync-design-system.mjs ms-ai-architect --source "$WORK/playground-design-system" --target "$WORK/ms-ai-architect"`
|
||||
then `--check` against the committed `MANIFEST.json` and asserts byte-parity — exercising the actual
|
||||
two-extracted-repo `--source`/`--target` invocation (SC5) locally, before the window.
|
||||
**Honest scope of what the dry-run proves (M8):** it proves the **local mechanics** — extraction,
|
||||
rename-aware history retention, re-rooting, reference rewrites, standalone validation, marketplace/
|
||||
catalog transforms, and the DS-vendor chain. It **cannot** prove the **window-only** steps:
|
||||
(i) Forgejo accepting the push of post-strip history, (ii) `auto_init:false` repo creation via the
|
||||
API, (iii) Claude Code resolving an HTTPS `url`+`ref` source from self-hosted Forgejo
|
||||
(Assumption 1 — covered instead by the Step 10 **pilot**, not the dry-run). The dry-run + the pilot
|
||||
together gate the batch; neither alone is sufficient.
|
||||
- **Reuses:** every prior script.
|
||||
- **Test first:**
|
||||
- File: `docs/marketplace-polyrepo-migration/migration/99-dryrun.test.mjs` (new)
|
||||
- Verifies: `dryrun-report.md` lists all 11 targets; voyage shows ≥250 commits; config-audit shows
|
||||
PASS under its SC2 gate (full suite minus `json-backcompat`/`raw-backcompat`, after re-seed); no
|
||||
target shows a tracked state-file leak; the previewed all-external marketplace.json has zero
|
||||
`./plugins/` and zero `ssh://`.
|
||||
- Pattern: `plugins/voyage/tests/integration/*.test.mjs`
|
||||
- **Verify:** `bash docs/marketplace-polyrepo-migration/migration/99-dryrun.sh` → expected:
|
||||
`DRY-RUN OK — 11/11 targets extracted + validated, 0 pushes`; `git -C $REPO_ROOT status` →
|
||||
expected: working tree shows only the committed `migration/` additions, **no** change to
|
||||
`plugins/`, `shared/`, or `.claude-plugin/marketplace.json` (D8 held).
|
||||
- **On failure:** escalate — any target FAIL is a real blocker to surface before the window; the
|
||||
dry-run is reversible (everything in `$WORK`/`--out`/`--workspace`).
|
||||
- **Checkpoint:** `git commit -m "chore(migration): full local dry-run harness + report"`
|
||||
- **Manifest:**
|
||||
```yaml
|
||||
manifest:
|
||||
expected_paths:
|
||||
- docs/marketplace-polyrepo-migration/migration/99-dryrun.sh
|
||||
- docs/marketplace-polyrepo-migration/migration/99-dryrun.test.mjs
|
||||
min_file_count: 2
|
||||
commit_message_pattern: "^chore\\(migration\\): full local dry-run harness \\+ report$"
|
||||
bash_syntax_check:
|
||||
- docs/marketplace-polyrepo-migration/migration/99-dryrun.sh
|
||||
forbidden_paths:
|
||||
- .claude-plugin/marketplace.json
|
||||
- plugins
|
||||
- shared
|
||||
must_contain:
|
||||
- path: docs/marketplace-polyrepo-migration/migration/99-dryrun.sh
|
||||
pattern: "no-local"
|
||||
```
|
||||
|
||||
### Manifest — objective completion predicate
|
||||
|
||||
Every step above carries a Manifest. The defining invariant across all of them:
|
||||
**`forbidden_paths` always includes the live `.claude-plugin/marketplace.json`** (and, for
|
||||
extraction/validation/thinning steps, `plugins/` + `shared/`) — this is the machine-checkable
|
||||
enforcement of **D8 (NULL push / no live-marketplace mutation)** during the entire Claude-run local
|
||||
phase. The live catalog and the plugins source are mutated **only** in the operator window
|
||||
(Step 10's runbook), which is outside trekexecute's verified surface by design.
|
||||
|
||||
### Failure recovery rules
|
||||
|
||||
- **revert** — script-authoring steps: `git checkout -- <files>`; extraction/validation operate in
|
||||
`$WORK` so reverting is `rm -rf $WORK/<key>`.
|
||||
- **retry** — idempotent transforms (Steps 3–9, 11) re-run cleanly.
|
||||
- **escalate** — a standalone-validation FAIL (Step 6/7/11) or a missing tool (Step 1) stops and
|
||||
asks the operator; never mask an SC2/SC6 failure.
|
||||
- **Checkpoint** — each step commits **locally** (D8: no push), so a later failure cannot corrupt
|
||||
completed tooling.
|
||||
|
||||
## Alternatives Considered
|
||||
|
||||
| Approach | Pros | Cons | Why rejected |
|
||||
|---|---|---|---|
|
||||
| `--subdirectory-filter plugins/<name>` for every plugin (brief's literal §9 form) | Simplest one-flag invocation | **Silently drops** 136+50+3 commits of pre-rename history for voyage/llm-security/linkedin-studio (F1) | Violates SC6 for 3 high-churn plugins; multi-`--path` is mandatory for renamed plugins |
|
||||
| `ssh://git@…` `url` sources (brief §6) | Matches the operator's SSH push workflow | `marketplace add` rejects SSH (#9740); `url` schema names only `https://`/`git@` | F2 — HTTPS is the only verified-accepted form; SSH risks a dead marketplace |
|
||||
| Hard-cut history (`git init` from current tree) for all repos | Fast; no filter-repo | Loses granular blame/history | D2 ratified filter-repo preservation; only the archived monorepo keeps full history under hard-cut |
|
||||
| Big-bang all-at-once cutover | One window, less bookkeeping | No reversible checkpoints; marketplace dark during cutover | D5 ratified incremental mixed-source; R1 |
|
||||
| Run extraction in the working checkout | No extra clone | Races concurrent sessions on `.git/index` (R4); destructive to live tree | §10 mandates a dedicated `--no-local` fresh clone |
|
||||
| Skip the pilot, push all 11 then test | Fewer window steps | If Forgejo HTTPS/external-source resolution fails, all 11 are already pushed | F2 — the Forgejo external-source chain is the highest-stakes unverified link; pilot de-risks it on 1 repo |
|
||||
|
||||
## Test Strategy
|
||||
|
||||
- **Framework:** Node `node:test` (glob form `node --test path/*.test.mjs` — Node 25 gotcha), zero
|
||||
npm deps (house style); bash `validate-plugin.sh` for structure checks.
|
||||
- **Existing patterns:** per-plugin `tests/**/*.test.mjs`; `claude-design`/`ms-ai-architect`
|
||||
`validate-plugin.sh`; config-audit `UPDATE_SNAPSHOT` rebasing seam; the SHA-256 `MANIFEST.json` +
|
||||
`detectDrift` in `sync-design-system.mjs`.
|
||||
- **New tests in this plan:** one test per script step (Steps 1–9, 11), each asserting the script's
|
||||
contract against the **live monorepo** or a freshly-extracted repo in `$WORK` — so the tooling is
|
||||
proven before the dry-run, and the dry-run (Step 11) is the integration test over all 11 targets.
|
||||
|
||||
### Tests to write
|
||||
|
||||
| Type | File | Verifies | Model test |
|
||||
|---|---|---|---|
|
||||
| Unit | `migration/00-preflight.test.mjs` | plugin-map shape, rename-awareness, HTTPS URLs | `voyage/tests/validators/*.test.mjs` |
|
||||
| Unit | `scripts/sync-design-system.test.mjs` | `--source` parity, `--check` drift | `voyage/tests/scripts/*.test.mjs` |
|
||||
| Integration | `migration/10-extract.test.mjs` | renamed-plugin history retained, clean re-tag | `voyage/tests/integration/*.test.mjs` |
|
||||
| Unit | `migration/20-rehome-config.test.mjs` | re-rooted gitignore/gitleaks, mailmap copied | `config-audit/tests/lib/*.test.mjs` |
|
||||
| Unit | `migration/30-fix-references.test.mjs` | no `../../README.md`, URLs reconciled | `linkedin-studio/render/__tests__/*.test.mjs` |
|
||||
| Integration | `migration/40-validate-standalone.test.mjs` | SC2/SC7 gate fails on planted leak | `voyage/tests/synthetic/*.test.mjs` |
|
||||
| Unit | `migration/50-config-audit-snapshots.test.mjs` | portable snapshots pass; generic validator | `config-audit/tests/snapshot-default-output.test.mjs` |
|
||||
| Unit | `migration/60-rewrite-marketplace.test.mjs` | mixed-source, HTTPS, ref pin | `voyage/tests/parsers/*.test.mjs` |
|
||||
| Unit | `migration/70-thin-catalog.test.mjs` | no plugins/shared, CONVENTIONS.md present | `config-audit/tests/*.test.mjs` |
|
||||
| Integration | `migration/99-dryrun.test.mjs` | 11/11 targets, SC2/SC6/SC7, NULL push | `voyage/tests/integration/*.test.mjs` |
|
||||
|
||||
## Risks and Mitigations
|
||||
|
||||
| Priority | Risk | Location | Impact | Mitigation |
|
||||
|---|---|---|---|---|
|
||||
| Critical | Renamed-plugin history silently dropped (F1) | `10-extract.sh` | SC6 fails; voyage/linkedin-studio lose 30–60% history | Multi-`--path` per `plugin-map.json`; Step 3 test asserts commit count from both names |
|
||||
| Critical | Forgejo HTTPS external-source resolution unverified (F2) | window / `RUNBOOK.md` | Marketplace could go dark if `url` source can't resolve self-hosted Forgejo | **Pilot gate** (Step 10) proves the chain on 1 repo before the other 10; HTTPS not SSH; public repos |
|
||||
| High | ms-ai-architect 148 MB blob-bomb (F3) | `10-extract.sh` `blob_strip` | Extracted repo bloated; slow push | `--strip-blobs-bigger-than 1M` (default, operator-confirmed); decision surfaced in Step 3 |
|
||||
| High | config-audit snapshots embed absolute path (F4) | Step 7 | SC2 blocker | `UPDATE_SNAPSHOT=1` re-seed of the rebasable `snapshot-default-output` in-clone; **by-name exclude** the 2 frozen back-compat tests (`json-backcompat`/`raw-backcompat`) from the SC2 gate; flagged for operator ratification (edges §5) |
|
||||
| High | Tag collision/leak across extracts (F5) | `10-extract.sh` | Wrong tags in each repo; ambiguous `v5.0.0` | Strip all tags, re-create exactly `v<version>`; Step 3 test asserts single tag |
|
||||
| High | Marketplace goes dark mid-migration (R1/SC8) | window | Broken installs | Mixed sources (Step 8 `--only`); never remove `./plugins/x` before external repo verified; install-smoke each step |
|
||||
| Medium | README/metadata reference rot (F6) | `30-fix-references.mjs` | 404 disclosure links, wrong repo URLs standalone | Rewriter inlines disclosure + reconciles `repository`/`homepage`; Step 5 test asserts none remain |
|
||||
| Medium | Root-only `.mailmap`/`.gitleaks.*` don't travel (F6/M5) | `20-rehome-config.sh` | Lost author dedup; re-flagged false positives | Copy `.mailmap`; generate per-plugin `.gitleaks` with re-rooted fingerprints |
|
||||
| Medium | Concurrent-session corruption during surgery (R4) | all extraction | Index race in working tree | `git clone --no-local` fresh clone; working checkout read-only; quiesce other writers in the window |
|
||||
| Medium | Untracked non-gitignored files leak on blanket add (M2) | extraction | `progress.json` + 2 briefs enter an extract | filter-repo is history-based (untracked files excluded); Step 6 SC7 check catches leaks |
|
||||
| Low | `okr.local.md.template` is intentionally tracked | Step 6 | False SC7 failure | Explicit allow-exception in the SC7 check |
|
||||
| Low | Forgejo no auto-update token for private repos (F2) | window | Background update of a private marketplace unsupported | Recommend **public** repos (§9); manual `/plugin marketplace update` otherwise |
|
||||
|
||||
## Assumptions
|
||||
|
||||
| # | Assumption | Why unverifiable | Impact if wrong |
|
||||
|---|---|---|---|
|
||||
| 1 | Claude Code resolves a self-hosted Forgejo `url` source over HTTPS with a pinned `ref` | Not testable until a repo exists on Forgejo (push is operator-gated, D8) | If false, the whole external-source model fails — **the Step 10 pilot is the explicit test that gates the rest** |
|
||||
| 2 | `git filter-repo` carries each plugin's `CHANGELOG.md` + history intact under multi-`--path` | filter-repo tag/history edge-cases ("tags not on path") lack a crisp documented rule | Verified empirically by Step 3/11 tests (commit counts, tag list) before the window |
|
||||
| 3 | Stripping blobs >1 MB from ms-ai-architect loses only screenshots, not source | Depends on whether any non-screenshot blob exceeds 1 MB | Step 3 surfaces the strip list as a decision; operator confirms before the window |
|
||||
| 4 | Excluding config-audit's 2 frozen back-compat tests (`json-backcompat`/`raw-backcompat`) from SC2, plus re-seeding `snapshot-default-output` in-clone, is acceptable (edges §5 non-goal) | A judgment on "what SC2 means" for fixtures that embed the original capture machine's path + a sibling marketplace + deleted plugins | Flagged for operator ratification (header + Step 7); fallback = the config-audit-internal work of normalizing the v5.0.0 snapshot paths (out of migration scope) |
|
||||
| 5 | Public visibility is acceptable for all `open/` plugin repos | Visibility is a policy choice, not technical | Operator confirms at the window; private repos lose background auto-update (F2) |
|
||||
|
||||
## Verification
|
||||
|
||||
*Per-step manifests run during execution (Steps 1–11). These are the cross-step, end-to-end checks
|
||||
mapping to the brief's SC1–SC8.*
|
||||
|
||||
- [ ] **SC6 (F1):** `git -C /tmp/polyrepo-migration/voyage log --oneline | wc -l` → expected: `≥ 250`
|
||||
(both `voyage` + `ultraplan-local` history present).
|
||||
- [ ] **SC6 (F5):** `git -C /tmp/polyrepo-migration/voyage tag` → expected: exactly `v5.1.1`.
|
||||
- [ ] **SC2/SC7:** `bash docs/marketplace-polyrepo-migration/migration/40-validate-standalone.sh
|
||||
--all` → expected: every target `PASS` (config-audit gate = full `tests/**/*.test.mjs` **minus**
|
||||
`json-backcompat` + `raw-backcompat`, after the Step 7 re-seed; linkedin-studio `.mjs` core is the
|
||||
hard gate, TS analytics ADVISORY); no tracked `*.local.md`/`STATE.md` except
|
||||
`okr/templates/okr.local.md.template`.
|
||||
- [ ] **SC5 (two extracted repos):** with the DS + ms-ai-architect extracts in `$WORK`,
|
||||
`node scripts/sync-design-system.mjs ms-ai-architect --source "$WORK/playground-design-system"
|
||||
--target "$WORK/ms-ai-architect"` then `node scripts/sync-design-system.mjs --check ms-ai-architect`
|
||||
→ expected: `MANIFEST OK`; `git -C "$WORK/ms-ai-architect" diff --exit-code playground/vendor/` →
|
||||
expected: empty (byte-identical re-vendor).
|
||||
- [ ] **SC3 (F2):** `node …/60-rewrite-marketplace.mjs --all --out /tmp/mp-all.json && grep -c
|
||||
'"./plugins/' /tmp/mp-all.json` → expected: `0`; `grep -c 'ssh://' /tmp/mp-all.json` → `0`.
|
||||
- [ ] **SC4:** `bash …/70-thin-catalog.sh --workspace /tmp/thin && ls /tmp/thin` → expected: no
|
||||
`plugins/`, no `shared/`; `CONVENTIONS.md` present.
|
||||
- [ ] **D8 held:** after the full dry-run, `git -C $REPO_ROOT status --porcelain
|
||||
.claude-plugin/marketplace.json plugins shared` → expected: **no output** (live surfaces
|
||||
unchanged); `git log origin/main..main --oneline` shows only local `migration/` commits, **none
|
||||
pushed**.
|
||||
- [ ] **SC1/SC8 (operator window, post-pilot):** per `RUNBOOK.md`, `/plugin marketplace add
|
||||
https://…/open/ktg-plugin-marketplace.git` + `/plugin install <plugin>@ktg-plugin-marketplace`
|
||||
succeeds for the pilot, then after each graduation.
|
||||
|
||||
## Estimated Scope
|
||||
|
||||
- **Files to modify:** 1 (`scripts/sync-design-system.mjs`).
|
||||
- **Files to create:** ~24 (11 migration scripts/docs + 11 tests + 2 templates), all under
|
||||
`docs/marketplace-polyrepo-migration/migration/` + 1 root test.
|
||||
- **Repos produced (local, unpushed):** 11 (10 plugins + DS) in `$WORK` during the dry-run.
|
||||
- **Complexity:** high (rename-aware history surgery, mixed-source live-marketplace choreography,
|
||||
operator-gated irreversibility) — but the **Claude-run local half is fully reversible** (NULL push,
|
||||
throwaway clones); only Step 10's window is irreversible and operator-run.
|
||||
|
||||
## Execution Strategy
|
||||
|
||||
Steps exceed 5 → grouped into sessions/waves. The local half (Sessions 1–5) is trekexecutable now;
|
||||
the window (Session 6) is operator-run from `RUNBOOK.md`, **not** via trekexecute.
|
||||
|
||||
### Session 1: Foundation
|
||||
- **Steps:** 1, 2
|
||||
- **Wave:** 1
|
||||
- **Depends on:** none
|
||||
- **Scope fence:**
|
||||
- Touch: `docs/marketplace-polyrepo-migration/migration/`, `scripts/sync-design-system.mjs(.test)`
|
||||
- Never touch: `.claude-plugin/marketplace.json`, `plugins/`, `shared/`
|
||||
|
||||
### Session 2: Extraction + per-repo prep scripts
|
||||
- **Steps:** 3, 4, 5
|
||||
- **Wave:** 2
|
||||
- **Depends on:** Session 1 (reads `plugin-map.json`)
|
||||
- **Scope fence:**
|
||||
- Touch: `docs/marketplace-polyrepo-migration/migration/`
|
||||
- Never touch: `plugins/`, `shared/`, `.claude-plugin/marketplace.json` (extraction operates in `$WORK`)
|
||||
|
||||
### Session 3: Validation + blockers
|
||||
- **Steps:** 6, 7
|
||||
- **Wave:** 3
|
||||
- **Depends on:** Session 2 (validates extracted repos)
|
||||
- **Scope fence:**
|
||||
- Touch: `docs/marketplace-polyrepo-migration/migration/`
|
||||
- Never touch: `plugins/config-audit/` live (Step 7 operates on the extract in `$WORK`)
|
||||
|
||||
### Session 4: Catalog + window staging
|
||||
- **Steps:** 8, 9, 10
|
||||
- **Wave:** 3 (parallel with Session 3 — independent scripts)
|
||||
- **Depends on:** Session 1 (versions/URLs)
|
||||
- **Scope fence:**
|
||||
- Touch: `docs/marketplace-polyrepo-migration/migration/`
|
||||
- Never touch: live `.claude-plugin/marketplace.json`, `CLAUDE.md`, `README.md` (rewriters write to `--out`/`--workspace`)
|
||||
|
||||
### Session 5: Local dry-run (de-risk proof)
|
||||
- **Steps:** 11
|
||||
- **Wave:** 4
|
||||
- **Depends on:** Sessions 2, 3, 4 (exercises every script)
|
||||
- **Scope fence:**
|
||||
- Touch: `docs/marketplace-polyrepo-migration/migration/`, `$WORK`
|
||||
- Never touch: any live source/catalog surface (D8 asserted by the step's own verify)
|
||||
|
||||
### Session 6: Operator window (NOT trekexecute — operator-run from RUNBOOK.md)
|
||||
- **Steps:** runbook only (pilot → DS → high-churn → low-churn → thin catalog)
|
||||
- **Wave:** 5 (authorized weekend window, D8)
|
||||
- **Depends on:** Session 5 green dry-run + operator authorization
|
||||
- **Scope fence:**
|
||||
- Touch: Forgejo (repo creation + push), live `.claude-plugin/marketplace.json` (mixed→external), catalog thinning
|
||||
- Never touch: anything before the pilot passes
|
||||
|
||||
### Execution Order
|
||||
|
||||
- **Wave 1:** Session 1
|
||||
- **Wave 2:** Session 2 (after Session 1)
|
||||
- **Wave 3:** Sessions 3 + 4 (parallel, after Session 2 / Session 1 respectively)
|
||||
- **Wave 4:** Session 5 (after Sessions 2–4)
|
||||
- **Wave 5:** Session 6 — operator window, gated on the dry-run + explicit authorization
|
||||
|
||||
### Grouping rules applied
|
||||
|
||||
- Steps sharing the `plugin-map.json` contract → Session 1 first.
|
||||
- Extraction/prep scripts (independent files) → Session 2; validation depends on them → Session 3.
|
||||
- Catalog/window staging is file-independent → Session 4 parallel with Session 3.
|
||||
- The dry-run gates everything before the irreversible window.
|
||||
|
||||
## Plan Quality Score
|
||||
|
||||
| Dimension | Weight | Score | Notes |
|
||||
|---|---|---|---|
|
||||
| Structural integrity | 0.15 | 88 | `$WORK` lifecycle now defined via a dedicated mirror clone; blob/re-tag ordering fixed (single-pass filter-repo) |
|
||||
| Step quality | 0.20 | 84 | Deterministic blob decision (no runtime prompt); config-audit step corrected to the verified `UPDATE_SNAPSHOT=1` seam; gitleaks fingerprint format fixed |
|
||||
| Coverage completeness | 0.20 | 86 | SC1–SC8 + D1–D8 + R1–R8 + §9 traced; F1–F6 absorbed; DS cross-repo chain + `.forgejo` survival now covered |
|
||||
| Specification quality | 0.15 | 85 | filter-repo/Forgejo/marketplace commands verified; config-audit framing corrected to the real test behaviour |
|
||||
| Risk & pre-mortem | 0.15 | 86 | Pilot now flips-then-installs (actually tests the ref-pin chain); dry-run honestly scoped to local mechanics |
|
||||
| Headless readiness | 0.10 | 80 | Local steps deterministic & headless-ready; window operator-run by design; TS analytics explicitly advisory |
|
||||
| Manifest quality | 0.05 | 85 | Every step has a checkable manifest; `expected_paths ⊆ Files:`; D8 enforced via `forbidden_paths` |
|
||||
| **Weighted total** | **1.00** | **85** | **Grade: B+** |
|
||||
|
||||
**Adversarial review (Phase 9):**
|
||||
- **Plan critic:** REVISE on the first draft (63/C) — 3 blockers + 8 major + 6 minor. **All 3 blockers
|
||||
+ all 8 majors + the actionable minors addressed** (see Revisions). Contested config-audit facts
|
||||
re-verified directly against the test code before revising.
|
||||
- **Scope guardian:** **ALIGNED** — 0 scope gaps; all SC1–SC8 / D1–D8 / §9 / R1–R8 covered; 4 creep
|
||||
items all ACCEPTABLE (2 forced by relocation, 2 operator-ratified); **1 DEVIATION** (ssh→https)
|
||||
surfaced for operator ratification in the header.
|
||||
|
||||
## Revisions
|
||||
|
||||
| # | Finding (reviewer) | Severity | Resolution |
|
||||
|---|---|---|---|
|
||||
| 1 | Step 7 misdescribed config-audit tests; invented `SKIP_ENV_SNAPSHOTS`; wrong-glob under-ran the suite (critic) | blocker | Re-verified the real tests. Rewrote Step 7 to use the actual `UPDATE_SNAPSHOT=1` seam (re-seed `snapshot-default-output`) + **by-name exclusion** of the 2 frozen back-compat tests + the declared runner `node --test 'tests/**/*.test.mjs'` |
|
||||
| 2 | Pilot couldn't test the ref-pin chain — install resolved the in-repo source (critic) | blocker | Step 10 pilot reordered: create→push→**flip the entry to external `url`+`ref` & push catalog**→then install-smoke, so the HTTPS+ref chain is actually exercised |
|
||||
| 3 | `blob_strip` left as a runtime prompt (non-headless) (critic) | major | Step 1 preflight enumerates >1 MB blobs and emits `blob_strip_safe`; Step 3 picks size-strip vs surgical path-glob deterministically — no prompt |
|
||||
| 4 | filter-repo second-pass + re-tag ordering hazard (critic) | major | Single filter-repo invocation composing `--path`/`--path-rename`/`--strip-blobs-bigger-than`; re-tag as the final op |
|
||||
| 5 | `$WORK`/`$REPO_ROOT` undefined; concurrency race reintroduced (critic) | major | Step 1 builds a dedicated `$WORK/_mirror` once; all extractions clone from it; per-step verifies self-contained |
|
||||
| 6 | `.gitleaksignore` re-rooting used wrong format `path:line` (critic) | major | Step 4 fixed to `path:rule-id:line` (keep `generic-api-key`/`linkedin-client-id` + line); test assertion corrected |
|
||||
| 7 | DS cross-repo vendor chain never validated before window (critic) | major | Step 11 runs the re-homed sync `--source`/`--target` across the two extracted repos + `--check` parity (SC5) |
|
||||
| 8 | Dry-run oversold as proving the migration (critic) | major | Step 11 reworded to local-mechanics only; window-only modes (push/create/resolve) explicitly listed as pilot-covered |
|
||||
| 9 | `expected_paths` not ⊆ `Files:` in every step (critic) | major | Added each `.test.mjs` to its step's `Files:` line |
|
||||
| 10 | linkedin-studio TS suite silently waived for SC2 (critic) | major | Step 6 states two-tier SC2: `.mjs` core = hard gate; TS analytics = explicit ADVISORY (network-gated) |
|
||||
| 11 | Preflight HEAD/status assertions go stale (critic) | minor | Relaxed to porcelain on `plugins/ shared/ marketplace.json scripts` only; idempotent |
|
||||
| 12 | Step 5 inline-vs-link either/or; ai-psychosis mis-scoped (critic) | minor | Inline chosen; ai-psychosis noted verify-only, real work = 9 monorepo-pointing plugins |
|
||||
| 13 | Step 9 doc-fix coupling; `.forgejo` CI uncovered; pre-claimed grade (critic) | minor | Version correction coupled to the D6 README rewrite; Step 6 asserts `.forgejo/` survives; grade set post-review |
|
||||
| 14 | ssh→https overrides a ratified brief literal without annotation (guardian) | deviation | Surfaced as a **brief correction requiring operator ratification** in the header; kept HTTPS (correct per #9740) |
|
||||
Loading…
Add table
Add a link
Reference in a new issue