ktg-plugin-marketplace/docs/marketplace-polyrepo-migration/brief.md
Kjell Tore Guttormsen f2d41c8781 docs(migration): ratify polyrepo-migration brief D1-D8
Flip status DRAFT -> RATIFIED. All eight decision blocks ratified as
recommended on 2026-06-17:
- D1 bare slug open/<plugin>
- D2 filter-repo preserve on a fresh clone + pre-polyrepo-archive tag
- D3 own DS repo, consumers keep vendoring
- D4 tag-pin (ref vX.Y.Z)
- D5 DS first -> high-churn -> low-churn -> thin catalog last
- D6 catalog keeps json/README/GOVERNANCE/conventions; shared+sync leave
- D7 per-repo .gitignore re-rooted; local state stays uncommitted
- D8 no push until one authorized weekend window

Next: /trekplan --brief in a fresh session at repo root.

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

21 KiB
Raw Blame History

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 D1D8 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, plugins-reference); (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.jsonplugins/ 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/<name>")
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/<name>/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/<name>; 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 <same URL> + /plugin install <plugin>@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 <plugin>@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 redesignshared/ 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/<plugin>  × 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):

{ "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/<plugin>). 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-<slug>; (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/<name>/ --path-rename plugins/<name>/: 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-cutgit 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 <URL> + 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/<name>".
  • scripts/sync-design-system.mjs:26-27MARKETPLACE_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 .gitignoreplugins/*/ prefixed patterns to re-root.
  • Forgejo remote: ssh://git@git.fromaitochitta.com/open/ktg-plugin-marketplace.git.
  • Docs: plugin-marketplaces, plugins-reference — 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.