# Marketplace Polyrepo Migration · Brief > **Voyage `/trekbrief`-style task brief.** Foundation document for splitting the > `ktg-plugin-marketplace` monorepo into **one git repo per plugin + a thin catalog repo**. > This brief defines *what the migration is and how we verify it* — it does **not** execute it. > Decisions are surfaced as ratify-by-annotation `DECISION` blocks. > > - **Written:** 2026-06-17 > - **Status:** RATIFIED 2026-06-17 — all `DECISION` blocks D1–D8 ratified as recommended (see each block). Next: run `/trekplan` in a fresh session at the repo root. > - **Workstream:** Infrastructure (marketplace-wide, root-scoped). This is the first root-level workstream; all prior briefs were plugin-scoped. > - **Grounded by:** (1) `.claude-plugin/marketplace.json` read; (2) official Claude Code marketplace docs verified via `claude-code-guide` agent ([plugin-marketplaces](https://code.claude.com/docs/en/plugin-marketplaces.md), [plugins-reference](https://code.claude.com/docs/en/plugins-reference.md)); (3) a read-only coupling inventory (Explore) of root infra + cross-references + per-plugin standalone-readiness, all on 2026-06-17. Counts/paths below are tool-verified, not recalled. --- ## 1. Context & motivation The marketplace is a **monorepo**: 10 plugins under `plugins/`, a shared design system under `shared/`, and a catalog (`marketplace.json` + landing `README.md`) at the root. Two findings make the monorepo the wrong fit for how this repo is actually developed: 1. **A Claude Code marketplace does NOT require a monorepo.** Verified against official docs: a plugin entry's `source` can be an external git repo (`github` / `url` / `git-subdir` / `npm`), a single `marketplace.json` can **mix** in-repo and external sources, the "thin catalog that points to many plugin repos" is an **officially supported pattern**, and each entry can **pin** to a `ref` (tag/branch) or `sha`. The monorepo is a *packaging convenience, not an architectural necessity* (Explore verdict). 2. **The operator works on multiple plugins concurrently as the permanent norm, and plugin count is growing.** 10 plugins today (up from the ~8 the root CLAUDE.md still documents — `human-friendly-style` and `claude-design` are new). A single working tree shares **one `.git/index` and one `HEAD`**, so concurrent sessions race on global mutable git-state. Observed symptoms: cross-plugin commit bleed (`f460814` "WIP across plugins", `69610d4` "roll up in-progress changes across plugins"); root-`README.md` merge pressure (the three-doc gate forces *every* feature to touch the root README); noisy `git status`. **The cost grows with each new plugin.** The plugins are already **independent units in a monorepo costume**: each versions independently (v1.15.0 / v4.1.0 / v7.7.2 …), each is self-contained (own `plugin.json` / `README` / `CLAUDE.md`), the design system is **vendored, not live-linked**, and there are **no cross-plugin runtime imports**. Splitting aligns the structure with that reality and removes the concurrency pain structurally rather than mitigating it. --- ## 2. Problem statement (grounded) **Inventory (tool-verified 2026-06-17):** | Surface | Finding | |---|---| | Plugins | **10**, `marketplace.json` ↔ `plugins/` dirs are a **perfect match** (no drift) | | Forgejo remote | `ssh://git@git.fromaitochitta.com/open/ktg-plugin-marketplace.git` (org `open/`) | | Root catalog | `.claude-plugin/marketplace.json` (all 10 `source: "./plugins/"`) | | Root shared infra | `README.md` (landing, hand-maintained version badges), `GOVERNANCE.md`, `CLAUDE.md` (marketplace-wide conventions + a stale plugin overview), `.gitignore`, `.gitleaks.toml`, `.gitleaksignore`, `.mailmap`, `STATE.md` (gitignored) | | Shared code | `shared/playground-design-system/` + `shared/playground-examples/` + `shared/PLAYGROUND-MAINTENANCE.md`; `scripts/sync-design-system.mjs` (the **only** root script) | | Local state | 13 gitignored `STATE.md` / `*.local.md` files across plugins | | Root CI/cron | **NONE** (no `.github/`, no `.forgejo/` at root; `voyage` has its own `.forgejo/`) | **The coupling is minimal and resolvable — there are no live cross-plugin imports.** The only break-points a split must address: | # | Break-point | Detail | |---|---|---| | C1 | DS sync reaches the monorepo root | `scripts/sync-design-system.mjs:26-27` resolves `MARKETPLACE_ROOT/shared/playground-design-system` via `path.resolve(__dirname, '..')`; vendors into `plugins//playground/vendor/`. Runtime is fully decoupled — plugins load CSS from their own `vendor/`, **never** from `shared/`. Sync is a dev-time tool only. | | C2 | `marketplace.json` source paths | All 10 are `./plugins/`; must become external repo URLs. | | C3 | `.gitignore` / `.gitleaks.*` per-plugin rewrite | Root `.gitignore` uses `plugins/*/` prefixes (e.g. `plugins/*/.claude/`, `plugins/*/reports/*-beskrivelse.*`) that must be re-rooted per plugin; `.gitleaks.toml`/`.gitleaksignore` carry per-plugin false-positive paths. | | C4 | Root README version badges | Hand-maintained (`llm-security v7.7.2`, etc.); become cross-repo references in the thin catalog. | | C5 | `shared/` relocation | DS + examples need a home that isn't a plugin and isn't the catalog's `plugins/`. | | C6 | `linkedin-studio` storage resolver | `scripts/analytics/src/utils/storage.ts:18-33,48` — marker-based `findPluginRoot` (walks up to `.claude-plugin/plugin.json`) with a hardcoded `../../../../` **fallback**. Primary path is resilient across the repo boundary; the fallback must be re-checked at the new repo root. | | C7 | `voyage` test helper | `tests/helpers/hook-helper.mjs:4` is a **verbatim copy** of llm-security's, with a provenance comment — not a live import; stays as a copy. | --- ## 3. Goal (definition of done) **Each of the 10 plugins lives in its own Forgejo repo; the design system lives in its own versioned repo; and `ktg-plugin-marketplace` becomes a thin catalog repo at the same URL users already added — installable and live at every step of the migration, with no plugin functionality or version changed.** After the migration: - Each plugin is its own repo with its own history (per D2), `.gitignore`, `.gitleaks.*`, and continuity (`STATE.md` + session hooks). - The catalog repo has **no `plugins/` subdir** and **no `shared/`** — only `marketplace.json` (external sources), the landing `README.md`, `GOVERNANCE.md`, and marketplace-wide conventions. - `/plugin marketplace add ` + `/plugin install @ktg-plugin-marketplace` works for all 10. - Concurrent multi-plugin work is **structurally isolated** — separate repos, separate `index`/`HEAD`, zero shared-state contention. - The design system has a single source repo; consumers vendor from it via the (re-homed) sync script. --- ## 4. Success criteria (testable) > Each is a concrete check; the planning phase turns these into commands. - **SC1 — Install works post-migration.** `/plugin marketplace add ssh://…/open/ktg-plugin-marketplace.git` then `/plugin install @ktg-plugin-marketplace` succeeds for all 10. Verify: fresh-session install smoke test per plugin. - **SC2 — Each plugin repo is standalone.** A clean clone of each new repo (no marketplace parent) passes that plugin's own validation/test suite. Verify: clone to `/tmp`, run its `tests/`/`validate`/`test-runner`. - **SC3 — Catalog resolves externally at every step.** No `./plugins/…` source remains at completion; every *intermediate* mixed-source state also resolves. Verify: `marketplace.json` schema check + install smoke after each plugin graduates. - **SC4 — Catalog is thin.** After completion the catalog repo contains **no** `plugins/` and **no** `shared/`. Verify: `ls`. - **SC5 — Design-system parity.** A DS change in the DS repo + re-sync produces byte-identical vendored output in a consumer (sync script rewired to the DS-repo source; `MANIFEST.json` SHA-256 matches). Verify: run sync, diff manifest. - **SC6 — History per D2.** Each new repo's `git log` reflects the ratified history decision (filter-repo preserved, or hard-cut with the pre-migration monorepo archived). Verify: `git log` per repo. - **SC7 — Continuity intact.** Each plugin repo's `.gitignore` (prefix re-rooted) ignores its local state; a session started in the repo injects *its* `STATE.md`; hooks run via `${CLAUDE_PLUGIN_ROOT}`. Verify: session start in each repo. - **SC8 — Marketplace never goes dark.** Install works at **every** intermediate commit (mixed sources). Verify: install smoke after each graduation. --- ## 5. Non-goals (scope fence) - **NOT changing plugin code, functionality, or versions.** Pure relocation + rewiring. - **NOT changing the marketplace URL.** `open/ktg-plugin-marketplace` stays — existing user installs keep their update path. - **NOT GitHub.** Forgejo only (`git.fromaitochitta.com`); never `gh` CLI. - **NOT touching content repos** (`~/repos/from-ai-to-chitta/…`). - **NOT building new CI.** None exists at root; per-plugin CI (`voyage/.forgejo/`) moves with its plugin unchanged. - **NOT merging, splitting, or renaming plugin functionality.** - **NOT a design-system redesign** — `shared/` is relocated, not rewritten. --- ## 6. Target architecture ``` open/ktg-plugin-marketplace ← THIN CATALOG (same URL) .claude-plugin/marketplace.json → external source + ref-pin per plugin README.md GOVERNANCE.md CONVENTIONS .mailmap .gitleaks (baseline) open/playground-design-system ← DS repo (versioned) playground-design-system/ playground-examples/ PLAYGROUND-MAINTENANCE.md scripts/sync-design-system.mjs (re-homed; --source points here) open/ × 10 ← one repo each .claude-plugin/plugin.json README CLAUDE CHANGELOG .gitignore (re-rooted) .gitleaks.* (per-plugin) hooks/ tests/ scripts/ playground/ vendor/playground-design-system/ … ``` `marketplace.json` entry shape (per D4 pinning): ```json { "name": "linkedin-studio", "source": { "source": "url", "url": "ssh://git@git.fromaitochitta.com/open/linkedin-studio.git", "ref": "v0.4.0" }, "description": "…" } ``` --- ## 7. Decisions (ratify by annotation) > Each block: **recommendation** + rationale + alternatives. **All eight ratified as recommended on 2026-06-17** — D1, D2, D4, D8 via explicit operator selection; D3, D5, D6, D7 ratified-as-recommended. ### D1 — Forgejo org + repo naming **✓ Ratified 2026-06-17 — bare plugin slug under `open/` (`open/`).** **Recommendation:** Org `open/` (matches the catalog); repo name = the **bare plugin slug** (`open/linkedin-studio`, `open/ms-ai-architect`, …). *Rationale:* the marketplace already namespaces; a `ktg-`/`plugin-` prefix is redundant. *Alternatives:* (a) prefix `open/ktg-`; (b) a dedicated org (e.g. `plugins/`). ### D2 — History preservation **✓ Ratified 2026-06-17 — preserve per-plugin history via `git filter-repo` on a fresh clone; tag the pre-migration monorepo `pre-polyrepo-archive` regardless.** **Recommendation:** Preserve per-plugin history with `git filter-repo --path plugins// --path-rename plugins//:` run on a **fresh clone** (never the working repo), keeping blame intact. Regardless of choice, tag the pre-migration monorepo (`pre-polyrepo-archive`) and keep it as a read-only archive. *Alternatives:* (a) **hard-cut** — `git init` a new repo from the current tree, lose granular history but keep it in the archived monorepo (fast); (b) hybrid — filter-repo the high-value plugins, hard-cut the rest. *This decision is the single biggest effort lever.* ### D3 — Design-system distribution **✓ Ratified 2026-06-17 (as recommended) — DS becomes `open/playground-design-system`; consumers keep vendoring; `sync-design-system.mjs` re-homed with a `--source`/env pointer.** **Recommendation:** DS becomes its own repo `open/playground-design-system`; `sync-design-system.mjs` moves there (or to the catalog) and takes a `--source`/env pointing at the DS checkout; **consumers keep vendoring** (runtime model unchanged — plugins load from `vendor/`). Pin the DS version per consumer in the vendored `MANIFEST.json`. *Rationale:* single source of truth, minimal change to a proven sync. *Alternatives:* (a) `git-subdir` source in each plugin (rejected — DS isn't a plugin); (b) npm package (adds a publish step); (c) git submodule per plugin (rejected — submodule friction; vendoring already works). **Note for planning:** only ~5 plugins consume DS and only **2** (`ms-ai-architect`, `llm-security`) currently have a `vendor/` dir — enumerate true consumers first. ### D4 — Marketplace pinning strategy **✓ Ratified 2026-06-17 — pin each plugin to a release tag (`ref: "vX.Y.Z"`).** **Recommendation:** Pin each plugin to a release **tag** (`ref: "vX.Y.Z"`) — reproducible installs, explicit upgrades (bump the tag in `marketplace.json` to release). *Alternative:* floating (track default branch — simpler, but installs move under users without a catalog change). ### D5 — Migration order + live strategy **✓ Ratified 2026-06-17 (as recommended) — DS repo first → high-churn plugins → low-churn → thin the catalog last; mixed sources keep the marketplace live throughout.** **Recommendation:** (0) Stand up the **DS repo first** (it's the spine 5 plugins vendor from). (1) Graduate **high-churn** plugins first — `linkedin-studio`, `voyage`, `ms-ai-architect`, `llm-security` — each as one reversible unit: create repo → push → flip its `marketplace.json` entry to external. (2) Mixed sources keep the marketplace live throughout. (3) Low-churn plugins next (`okr`, `config-audit`, `graceful-handoff`, `ai-psychosis`, `human-friendly-style`, `claude-design`). (4) **Thin the catalog last** — remove `plugins/` and `shared/`, rewrite the landing README. *Alternative:* big-bang all-at-once (rejected — no reversible checkpoints, marketplace dark during the cutover). ### D6 — What stays in the catalog repo **✓ Ratified 2026-06-17 (as recommended) — catalog keeps `marketplace.json`, landing README, `GOVERNANCE.md`, `.mailmap`, a `.gitleaks` baseline, and the extracted marketplace-wide conventions; `shared/` + `sync-design-system.mjs` leave to the DS repo.** **Recommendation:** `marketplace.json`, landing `README.md`, `GOVERNANCE.md`, `.mailmap`, a shared `.gitleaks` baseline, and the **marketplace-wide conventions** extracted from the root `CLAUDE.md` (move them to `GOVERNANCE.md` or a new `CONVENTIONS.md`). `shared/` + `scripts/sync-design-system.mjs` leave to the DS repo. The catalog `CLAUDE.md` becomes thin (catalog-maintenance only). *Alternative:* keep `shared/` in the catalog and skip a separate DS repo (rejected — re-creates a mini-monorepo and the cross-cutting README pressure). ### D7 — Gitignored state + continuity **✓ Ratified 2026-06-17 (as recommended) — per-repo `.gitignore` with `plugins/*/` prefixes stripped; the 13 local state files stay uncommitted; `STATE.md` regenerates per repo; zero hook changes.** **Recommendation:** Each new repo gets its own `.gitignore` with the `plugins/*/` prefixes stripped (`.claude/`, `reports/*-beskrivelse.*`, `STATE.md`, `*.local.md`, OS files). The 13 local state files stay **local and uncommitted**; `STATE.md` regenerates per repo (it is untracked, so `filter-repo`/clone won't carry it — that is fine). The global `~/.claude` session-start hook already injects the nearest `STATE.md` from cwd, so continuity works in any repo with zero hook changes. *Alternative:* seed an empty `STATE.md` template per repo. ### D8 — Push timing + irreversibility gate **✓ Ratified 2026-06-17 — all extraction + validation stays local with NO push; creating Forgejo repos + the first push + the live `marketplace.json` rewrite are batched into one explicitly-authorized window (the weekend).** **Recommendation:** All extraction + local validation happens with **NO push** (honoring "ikke push før helgen"). The outward, irreversible steps — **creating Forgejo repos, the first push, and rewriting the live `marketplace.json`** — are **batched into one explicitly-authorized window** (the weekend). Forgejo repo creation is operator-run (API/UI). *Rationale:* the marketplace is live; half-pushed external sources would break installs. *Alternative:* graduate-and-push incrementally during the week (rejected — violates push policy + risks a half-migrated public marketplace). --- ## 8. Risks - **R1 — Marketplace goes dark mid-migration.** *Mitigation:* mixed sources (D5) + install smoke after each step (SC8); never remove a `./plugins/x` source before its external repo is pushed and verified. - **R2 — History loss.** *Mitigation:* `filter-repo` on a **clone** (D2); archive the pre-migration monorepo as a tag/read-only repo regardless of D2. - **R3 — DS drift across consumers.** *Mitigation:* single DS repo (D3); `MANIFEST.json` SHA verification (SC5). - **R4 — Concurrent-session corruption during destructive git surgery.** The thinning + any history rewrite mutate the working tree/`.git`; a second session committing there races on the shared index. *Mitigation:* run the migration **from a dedicated fresh clone**, and quiesce other writers in the original checkout during the destructive phase (see §10 + the session-hygiene answer). - **R5 — `linkedin-studio` resolver fallback misfires.** The `../../../../` fallback in `storage.ts:48` assumes the monorepo depth. *Mitigation:* verify the marker-based primary path finds `.claude-plugin/plugin.json` at the new repo root (it will — the manifest sits at the standalone repo root); add a regression test. - **R6 — Forgejo repo-creation irreversibility.** *Mitigation:* operator-gated, batched, one authorized window (D8). - **R7 — `.gitignore`/`.gitleaks` path-rewrite errors.** *Mitigation:* generate from a template; verify per repo (a committed local-state file is a failure). - **R8 — Root README version badges go stale.** *Mitigation:* the thin catalog README references plugin repos; a version-fetch helper is noted but out of scope. --- ## 9. Open questions for the planning phase - Exact `filter-repo` invocation; whether to also preserve `shared/` history into the DS repo. - True DS consumers — only 2 have `vendor/` dirs today vs 5 named; enumerate before standing up the DS repo, and decide whether `claude-design` / `human-friendly-style` (no playground) need DS at all. - Forgejo repo **visibility** per repo (the marketplace is under `open/` — confirm public vs private). - Does `shared/playground-examples/` belong in the DS repo or stay referenced from the catalog? - Per-repo `CHANGELOG` strategy (carry the plugin's existing history vs start fresh). --- ## 10. Execution model (for the planning phase) - **Run from a dedicated fresh clone** of `ktg-plugin-marketplace`, not the operator's working checkout — so other sessions' working trees are untouched and the destructive steps (filter-repo, thinning) can't race a concurrent committer (R4). - **Operator-run steps:** Forgejo repo creation (API/UI), the authorized push window (D8). - **Claude-run steps:** filter-repo/extraction scripting, `.gitignore`/`.gitleaks` re-rooting, `marketplace.json` rewrites, the DS-repo standup, validation per SC. - **Reversible until push:** everything before D8's window is local and discardable; the migration is a sequence of per-plugin units, each independently verifiable (SC2) and revertible. --- ## 11. Verification plan 1. **Per-plugin standalone:** clone each new repo to `/tmp`, run its own validate/test suite → **SC2**. 2. **Install smoke:** `/plugin marketplace add ` + install each plugin in a fresh session, at the final state **and** after each graduation → **SC1, SC3, SC8**. 3. **Catalog thinness:** `ls` the catalog — no `plugins/`, no `shared/` → **SC4**. 4. **DS parity:** change a DS token in the DS repo, re-sync a consumer, diff `MANIFEST.json` SHA → **SC5**. 5. **History:** `git log` per new repo matches D2 → **SC6**. 6. **Continuity:** start a session in each repo; confirm its `STATE.md` injects and hooks run; `git status` shows no committed local-state files → **SC7**. --- ## 12. References (tool-verified anchors, 2026-06-17) - `.claude-plugin/marketplace.json` — 10 plugins, all `source: "./plugins/"`. - `scripts/sync-design-system.mjs:26-27` — `MARKETPLACE_ROOT` relative resolution + vendoring target. - `plugins/linkedin-studio/scripts/analytics/src/utils/storage.ts:18-33,48` — marker-based `findPluginRoot` + `../../../../` fallback. - `plugins/voyage/tests/helpers/hook-helper.mjs:4` — verbatim-copy provenance comment. - Root `.gitignore` — `plugins/*/` prefixed patterns to re-root. - Forgejo remote: `ssh://git@git.fromaitochitta.com/open/ktg-plugin-marketplace.git`. - Docs: [plugin-marketplaces](https://code.claude.com/docs/en/plugin-marketplaces.md), [plugins-reference](https://code.claude.com/docs/en/plugins-reference.md) — external/mixed sources, thin catalog, `ref`/`sha` pinning all officially supported. --- _Counts and paths verified by direct codebase mapping + official-doc check on 2026-06-17, not recalled. The root CLAUDE.md's plugin overview (~8 plugins) is stale — there are 10._