The 2026-08-04 count (27 gated / 15 ungated across 7 plugins) was correct and is
not overturned: re-running the measurement against catalog commit c7fbbd3
reproduces it exactly. Coverage fell to 24/18 across 8 because three axes lost
their badge when a ref moved — okr v1.8.2 -> v1.10.0 dropped agents-7 and
hooks-3, graceful-handoff v3.1.0 -> v3.2.1 dropped tests-30. 27 - 3 = 24.
All 18 ungated values are now measured at their refs in the same pass rather
than inherited: 16 exact, 1 correct-but-split (voyage's 6 commands + 1 helper is
the plugin's own split, 7 files), 1 defect (repo-standard states 170 tests,
measures 243 — the catalog faithfully mirrors the plugin's own stale README, so
the fix belongs in repo-standard first). repo-standard's checks axis stays open:
catalog bullet says twelve, stat line says 14, plugin README says twelve,
measurement finds 19 exported check functions — ask, do not guess.
Coverage is now read by importing check-versions.mjs's own extractStatBadges and
extractCatalogStats rather than a second parser that could drift from the gate.
Also records two measurement defects found in the pass itself: git ls-tree quotes
non-ASCII paths, which silently dropped okr's two Norwegian-named commands and
read as a defect in okr; and a zsh loop whose set -- did not word-split reported
13/13 MATCH by comparing empty strings.
No ref, tag or stat line was touched.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011wF4LZtSrfBFZr2h5ZPVHu
13 KiB
13 KiB
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 pageCONVENTIONS.md— marketplace-wide conventions inherited by every plugin repoGOVERNANCE.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(externalsource: "url"with a pinnedref) 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.
- Releasing a plugin (canonical path —
scripts/release-plugin.mjs): since the polyrepo split, a release is a TWO-repo act — tag the plugin repo AND bump the catalogref. Forgetting the second step strands users on the old version (the exact drift this helper exists to prevent). Runnode scripts/release-plugin.mjs <plugin> [--version X.Y.Z]— dry-run by default; it REFUSES unlessplugin.json== README badge == the target version AND thevX.Y.Ztag exists, then prints the planned bump. Apply with--write [--commit] [--push];--create-tag --writemints+pushes a missing plugin tag first (--create-tagis a WRITE and obeys--write— on a dry-run it only reports what it would mint). On--writeit bumps the catalogrefAND the catalog README's per-plugin`vX.Y.Z`label together (andgit adds both on--commit). Because it only moves both to a verified, tagged, consistent version,check-versions.mjsis green by construction. Never hand-edit arefor a README label for a release — use this. Pure planner + label reconciler + pre-flight/write step covered byscripts/release-plugin.test.mjs. - Pre-flight gate (
--writerunscheck-versionsBEFORE it writes): the helper callsrunGate()first and aborts with exit 1 — nothing written — if ANY plugin is ERROR, not just the one being released (check-versions' exit code is catalog-wide). Previously the gate ran after both writes, so a red catalog left a half-applied release in the working tree for a parallel session to carry to the public remote. The pre-flight reads the ERROR set only, neverfailed/--strict: pre-bump, the plugin being released is supposed to be WARN (catalogrefbehindplugin.json), so gating on WARN would brick every release. The post-write gate at the end stays — pre-flight validates the old state, that one validates the new state.--create-tagis deliberately NOT behind this gate (decided 2026-08-10): it mints and pushes the plugin tag before the pre-flight runs, but every precondition it checks is local to that one plugin (plugin.json== target, badge agrees, tag absent), so the tag is correct by construction. A red other plugin can only make the tag EARLY, never WRONG, and the tag-absent check makes the retry idempotent — gating it would let plugin Y block the tagging of plugin X, the same over-coupling that reading ERROR-only avoids. What WAS closed is the worse half:--create-tagused to push on the documented dry-run path, with no--writeat all. It now requires--write(shouldCreateTag, tested). - Version-consistency gate: run
node scripts/check-versions.mjsbefore committing anyrefchange. For each plugin it checks (against the sibling repo) that the catalogrefresolves to a real git tag (ERROR if dangling — breaks install), thatplugin.jsonversion == README version-badge (ERROR), that the catalog README's per-plugin`vX.Y.Z`label == the catalogref(ERROR — the human-facing doc must not misstate the installed version), and that the catalogrefmatchesplugin.jsonversion (WARN — catalog lags or an unreleased bump). Exit 1 on any ERROR;--strictalso fails on WARN. Pure-function core covered byscripts/check-versions.test.mjs(node --test scripts/check-versions.test.mjs). - Stat-badge mirroring (part of the same gate): each plugin block in the catalog README ends in a
stat line (
7 agents · 16 scanners · 21 commands · 1398 tests · [Full documentation →]). The gate compares every number on that line against the plugin's own shields badge for the same axis, and ERRORs when they disagree — the catalog must not overstate a plugin. The rule is per-AXIS, not per-plugin: an axis the plugin does not badge is skipped silently, so there is no exception list to maintain. Re-measured 2026-08-13 at the pinned refs, all 12 plugins: 42 catalog axis-claims — 24 badge-covered · 18 badge-less across 8 of the 12 plugins and therefore ungated, with 0 disagreements on the gated set. The measurement importscheck-versions.mjs's OWN exportedextractStatBadges/extractCatalogStatsand feeds themgit show <ref>:README.md, so coverage is read by the same code that gates it rather than by a second parser that could drift. The prior27 · 15 across 7(2026-08-04) was correct and is not overturned — re-running the measurement against catalog commitc7fbbd3reproduces27 · 15 across 7exactly. Coverage FELL because three axes lost their badge when a ref moved, each verified at both tags:okrv1.8.2 → v1.10.0 droppedagents-7,hooks-3andreferences-17(the catalog restates the first two → −2 gated), andgraceful-handoffv3.1.0 → v3.2.1 droppedtests-30andhooks-0(the catalog restates onlytest→ −1 gated). 27 − 3 = 24, 15 + 3 = 18, +1 ungated plugin (okr). This is the CLAUDE.md-documented graceful-handoff badge-drop reaching a release: it was visible onmainon 2026-08-04 and is now what installs. All 18 ungated values were measured at their refs in the same pass (2026-08-13) — 16 exact, 1 correct-but-split, 1 defect. Exact: voyage24 agents/7 hooks/832 tests, linkedin-studio6 skills, graceful-handoff1 pipeline/48 tests, ai-psychosis1 skill/1 command, ms-ai-architect29 commands/5 skills/2 hooks, okr7 agents/3 hooks, claude-design5 tests. Split: voyage6 commands (+1 helper)measures 7 command files, and the split is the plugin's own — its README atv5.9.1reads "6 slash commands (…) + trekendsession helper", and that file'sdescription:calls itself a helper. Defect: repo-standard170 testsmeasures 243 atv0.11.1(node --test scripts/*.test.mjs, the version itspackage.jsondeclares). The catalog is faithfully mirroring repo-standard's own README, which says "170 tests over the pure classifiers" at that tag — so the plugin's prose is the stale source and the fix belongs there first. Understating, not overstating, which is why nothing screamed. Also unresolved atv0.11.1: repo-standard's14 checks. The catalog block contradicts itself — the bullet says "Twelve checks", the stat line says 14 — and the plugin's own README says "twelve checks". Measured: 19 exportedcheck*functions inscripts/repo-standard-check.mjs. Three numbers, no agreement; "exported function" is not established as the unit a user-visible check is counted in. Do not pick one — ask repo-standard, and carry the measured 19 into the ask. ⚠️ The251 selftest checkscorrection is not fully settled, though the weight is on 370: this file and the catalog's own 08-02 stat line both record the axis as corrected to 370,check-versions.mjssaid 374 (most likely a transcription slip), and repo-mailbox's README at v0.19.0 implies 390 (183 + 134 + 73) — its own prose being ungated too. Moot for the gate (they badge the axis now, 398 at v0.20.2, green) and not re-measured. It stands as the proof of the cost: an ungated number rots, and so does the record of having fixed it.N+in the catalog is read as a lower bound, not an equality, so500+was never gate-visible — ungated axes rot in silence and need a re-run of this pass whenever a ref moves. Never hand-edit a stat line to silence the gate — the plugin's badge is the source for every stat number; fix the catalog to match it. - Counting rules for a badge-less axis (calibrated against the badged plugins, 2026-08-02). When
the catalog must count an axis itself, count it the way the badges do, or the numbers stop being
comparable across plugin blocks: hooks = hook ENTRIES in
hooks/hooks.json(not events, not matchers — the three diverge), and tests =ℹ testsfromnode --test, notℹ pass(config-audit's badge 1398 is itstestscount;passwas 1375). Measure in an extraction of the tag (git archive <ref> | tar -x -C <tmp>), never the sibling working tree.- Re-calibrated 2026-08-13 — the file-counting rules now have a measured referent, 11/11.
agents=agents/**/*.md(4/4: llm-security 6, config-audit 7, ms-ai-architect 12, linkedin-studio 20),commands=commands/**/*.md(3/3: config-audit 21, okr 16, linkedin-studio 30),skills=skills/*/SKILL.md(4/4: repo-mailbox 3, claude-design 1, graceful-handoff 1, repo-standard 1).tests = ℹ testsre-confirmed against config-audit (ℹ tests 1398== badge 1398,pass1374 — counttests, and note the suite need not be green for the census to be valid).scannersis NOT calibrated: neither files-under-scanners/(llm-security 55, config-audit 61) nor top-level.mjs(27 / 32) reproduces the badges (23 / 16), so the axis counts something the tree does not name. Harmless today — both scanner claims are gated and there is no ungated one to count — but do not invent a rule if that changes. - ⚠️
git ls-treeQUOTES non-ASCII paths, and a naive count silently drops them. okr'scommands/innføring.mdandcommands/møter.mdcome back as"commands/innf\303\270ring.md", so a^commands/.+\.md$match counted 14 against a badge of 16 and read as a defect in okr. It was a defect in the measurement. Always pass-c core.quotePath=false. The same pass also produced a fully bogus "13/13 MATCH" from a zsh loop whereset -- $specdid not word-split, leaving every field empty so"" == ""passed — a verification that cannot fail has verified nothing. Count in Node, not in a shell loop. type: promptentries COUNT (decided 2026-08-13). The rule read "hook COMMAND entries" until now. That wording was calibrated in 2026-08 against plugins that predate prompt-hooks, so it was never a ruling on them — it had no case to rule on. A prompt entry fires on the same event and does the same job from the reader's side, so excluding it would understate the plugin. Re-measured 2026-08-13 at all 12 pinned refs before the rewrite, because restating an old measurement in new words is itself a claim: every plugin that both badgeshooksand ships ahooks.jsonhas entries == command-entries (llm-security 9/9, config-audit 4/4, linkedin-studio 9/9, ai-psychosis 4/4, repo-mailbox 1/1), so the two phrasings agree on the whole badged set and the rewrite contradicts nothing.okr@v1.10.0is the only plugin with a non-command entry — 3 entries, 2command+ 1prompt(PreCompact) — and it does not badge the axis, which is precisely why the badged set never tested the rule. The catalog's3 hooksstands, measured.- The
agentsaxis IS badge-covered — 4 of the 6 plugins that claim it (measured 2026-08-13).llm-securityagents-6,config-auditagents-7,ms-ai-architectagents-12,linkedin-studioagents-20; all four match the catalog exactly and are gated. Ungated:voyage(24 agents) andokr(7 agents) — badge-less. Both measured 2026-08-13 at their refs: 24 and 7, exact. Do not record this axis as uncalibrated: it has a referent. - A badge whose value is not an integer does not gate the axis.
extractStatBadgeskeeps only integer-valued badges, so graceful-handoff'sPipeline/STATE--helper-deterministicbadge leaves1 pipelineungated even though a badge for the axis visibly exists. When asking "is this axis gated?", read the badge's VALUE, not its label.
- Re-calibrated 2026-08-13 — the file-counting rules now have a measured referent, 11/11.
- The stat mirror reads the plugin README AT THE PINNED
ref, never the sibling working tree. The catalog documents what installs, and that is the tag. A plugin that commits past its tag without bumping its version — measured 2026-08-02 on both llm-security (scanners 23→22, tests 2013→2034) and config-audit (tests 1398→1441) — would otherwise make the gate demand that the catalog restate unreleased numbers, which is exactly backwards. When the gate flags a stat, checkgit show <ref>:README.mdin the plugin repo before believing the working tree.