#!/usr/bin/env bash # Step 9 — catalog-thinning script (staged for the operator window). # # Renders the THIN-CATALOG end-state into a --workspace. NEVER mutates the live tree # (forbidden_paths: plugins, shared, CLAUDE.md, README.md) — every write lands inside --workspace. # After the operator window the catalog repo hosts only the marketplace manifest + the catalog-level # docs; the 10 plugins + the design-system live in their own Forgejo repos under # https://git.fromaitochitta.com/open/. This script produces that state for inspection/staging: # # 1. archives the tracked catalog (git archive HEAD) into --workspace (clean: no .git, no untracked) # 2. removes plugins/, shared/, scripts/sync-design-system.mjs (+ its test) # 3. extracts the marketplace conventions from CLAUDE.md into CONVENTIONS.md (D6) # 4. thins the catalog CLAUDE.md to catalog-maintenance only # 5. rewrites the landing README.md to point at the external plugin repos, re-stating every version # from plugin-map.json (the verified source). M15 is COUPLED to this D6 rewrite, not a ride-along: # a roster that re-states versions/counts must state the verified ones or it ships a false document. # # NULL push (D8): writes only inside --workspace. # # Usage: 70-thin-catalog.sh --workspace set -uo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" REPO_ROOT="$(cd "$SCRIPT_DIR/../../.." && pwd)" MAP="$SCRIPT_DIR/plugin-map.json" WS="" while [ $# -gt 0 ]; do case "$1" in --workspace) WS="${2:-}"; shift 2 ;; *) echo "unknown arg: $1" >&2; exit 2 ;; esac done [ -n "$WS" ] || { echo "usage: 70-thin-catalog.sh --workspace " >&2; exit 2; } [ -f "$MAP" ] || { echo "plugin-map.json missing at $MAP" >&2; exit 1; } # --- 1. Archive the tracked catalog into the workspace (no .git, no untracked) --- rm -rf "$WS" mkdir -p "$WS" git -C "$REPO_ROOT" archive HEAD | tar -x -C "$WS" # --- 2. Remove the components that move out to their own repos --- rm -rf "$WS/plugins" "$WS/shared" \ "$WS/scripts/sync-design-system.mjs" "$WS/scripts/sync-design-system.test.mjs" rmdir "$WS/scripts" 2>/dev/null || true # --- 3. Extract conventions -> CONVENTIONS.md (D6) --- { echo "# Conventions — ktg-plugin-marketplace catalog" echo echo "> Extracted from the marketplace CLAUDE.md (D6). These are the marketplace-wide conventions" echo "> every plugin repo inherits under fork-and-own. See GOVERNANCE.md for the governance model." echo awk ' /^## Konvensjoner/ { f=1; print; next } f && /^## / { exit } f { print } ' "$WS/CLAUDE.md" } > "$WS/CONVENTIONS.md" # --- 4. Thin the catalog CLAUDE.md to catalog-maintenance only --- cat > "$WS/CLAUDE.md" <<'MD' # ktg-plugin-marketplace (catalog) Catalog repository for the ktg-plugin-marketplace. After the polyrepo migration this repo hosts only the marketplace manifest and the catalog-level docs; every plugin and the shared design-system live in their own Forgejo repositories under `https://git.fromaitochitta.com/open/`. ## What lives here - `.claude-plugin/marketplace.json` — the marketplace manifest (plugin entries point at external repos) - `README.md` — the landing/catalog page - `CONVENTIONS.md` — marketplace-wide conventions inherited by every plugin repo - `GOVERNANCE.md` — governance + fork-and-own model - `.mailmap`, `.gitleaks.toml`, `.gitleaksignore` — shared git-hygiene baselines ## Catalog maintenance - Marketplace conventions: see CONVENTIONS.md. - Adding/updating a plugin entry: edit `.claude-plugin/marketplace.json` (external `source: "url"` with a pinned `ref`) and re-state the plugin in README.md with its verified version. - Plugin source, issues, and releases live in each plugin's own repository — not here. MD # --- 5. Rewrite the landing README.md: external repo links + versions re-stated from plugin-map.json --- python3 - "$MAP" "$WS/README.md" <<'PY' import json, re, sys map_path, readme_path = sys.argv[1], sys.argv[2] targets = json.load(open(map_path))["targets"] def browse(key): u = targets[key]["repo_url"] return u[:-4] if u.endswith(".git") else u tag = {k: targets[k].get("tag", "") for k in targets} # shared/playground-examples/ is bundled into the playground-design-system repo (plugin-map path_renames) EXAMPLES_OWNER = "playground-design-system" src = open(readme_path).read() def link_repl(m): container, key, sub = m.group(1), m.group(2), (m.group(3) or "") sub = sub.lstrip("/") if container == "shared" and key == "playground-examples": owner = EXAMPLES_OWNER subpath = ("playground-examples/" + sub) if sub else "" elif key in targets: owner = key subpath = sub else: return m.group(0) # unknown — leave as-is (surfaced by the no-local-links assertion) base = browse(owner) if not subpath or subpath == "README.md": return "](%s)" % base return "](%s/src/branch/main/%s)" % (base, subpath) # swap every local ](plugins//...) / ](shared//...) link to its external repo src = re.sub(r"\]\((plugins|shared)/([a-z0-9-]+)(/[^)]*)?\)", link_repl, src) # re-state the version backtick on heading lines that now link to a known external repo def fix_version(line): m = re.search(r"\]\(https://git\.fromaitochitta\.com/open/([a-z0-9-]+)\)", line) if m and m.group(1) in tag and tag[m.group(1)]: line = re.sub(r"`v[0-9][^`]*`", "`%s`" % tag[m.group(1)], line, count=1) return line out = "\n".join(fix_version(l) for l in src.split("\n")) open(readme_path, "w").write(out) PY echo "thin-catalog ready at $WS" echo " removed: plugins/ shared/ scripts/sync-design-system.mjs" echo " added: CONVENTIONS.md (extracted from CLAUDE.md conventions)" echo " README.md rewritten: external repo links + versions re-stated from plugin-map.json"