ktg-plugin-marketplace/CLAUDE.md
Kjell Tore Guttormsen aff807c101 docs(catalog): re-measure badge coverage at every pinned ref — 24 gated, 18 ungated
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
2026-08-13 21:06:55 +02:00

148 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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.
- **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 catalog `ref`. Forgetting the second
step strands users on the old version (the exact drift this helper exists to prevent). Run
`node scripts/release-plugin.mjs <plugin> [--version X.Y.Z]` — dry-run by default; it REFUSES unless
`plugin.json` == README badge == the target version AND the `vX.Y.Z` tag exists, then prints the
planned bump. Apply with `--write [--commit] [--push]`; `--create-tag --write` mints+pushes a missing
plugin tag first (**`--create-tag` is a WRITE and obeys `--write`** — on a dry-run it only reports
what it would mint). On `--write` it bumps the catalog `ref` AND the catalog README's per-plugin `` `vX.Y.Z` ``
label together (and `git add`s both on `--commit`). Because it only moves both to a verified, tagged,
consistent version, `check-versions.mjs` is green by construction. Never hand-edit a `ref` or a
README label for a release — use this. Pure planner + label reconciler + pre-flight/write step
covered by `scripts/release-plugin.test.mjs`.
- **Pre-flight gate (`--write` runs `check-versions` BEFORE it writes):** the helper calls `runGate()`
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**, never `failed`/`--strict`: pre-bump,
the plugin being released is *supposed* to be WARN (catalog `ref` behind `plugin.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-tag` is 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-tag` used to push on the documented dry-run path, with no
`--write` at all. It now requires `--write` (`shouldCreateTag`, tested).
- **Version-consistency gate:** run `node scripts/check-versions.mjs` before committing any `ref`
change. For each plugin it checks (against the sibling repo) that the catalog `ref` resolves to a
real git tag (ERROR if dangling — breaks install), that `plugin.json` version == README
version-badge (ERROR), that the catalog README's per-plugin `` `vX.Y.Z` `` label == the catalog
`ref` (ERROR — the human-facing doc must not misstate the installed version), and that the catalog
`ref` matches `plugin.json` version (WARN — catalog lags or an unreleased bump). Exit 1 on any
ERROR; `--strict` also fails on WARN. Pure-function core covered by
`scripts/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 imports `check-versions.mjs`'s OWN exported
`extractStatBadges` / `extractCatalogStats` and feeds them `git 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 prior `27 · 15 across 7` (2026-08-04) was correct and is not overturned** — re-running the
measurement against catalog commit `c7fbbd3` reproduces `27 · 15 across 7` exactly. Coverage FELL
because three axes lost their badge when a ref moved, each verified at both tags: `okr` v1.8.2 →
v1.10.0 dropped `agents-7`, `hooks-3` and `references-17` (the catalog restates the first two →
**2 gated**), and `graceful-handoff` v3.1.0 → v3.2.1 dropped `tests-30` and `hooks-0` (the catalog
restates only `test` → **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 on `main`
on 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: voyage `24 agents` / `7 hooks` / `832 tests`, linkedin-studio
`6 skills`, graceful-handoff `1 pipeline` / `48 tests`, ai-psychosis `1 skill` / `1 command`,
ms-ai-architect `29 commands` / `5 skills` / `2 hooks`, okr `7 agents` / `3 hooks`, claude-design
`5 tests`. Split: voyage `6 commands (+1 helper)` measures 7 command files, and the split is the
plugin's own — its README at `v5.9.1` reads "6 slash commands (…) + trekendsession helper", and that
file's `description:` calls itself a helper. **Defect: repo-standard `170 tests` measures 243** at
`v0.11.1` (`node --test scripts/*.test.mjs`, the version its `package.json` declares). 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 at `v0.11.1`: repo-standard's `14 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 exported `check*` functions** in `scripts/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.
**⚠️ The `251 selftest checks` correction 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.mjs` said 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, so `500+` 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 = ` tests` from `node --test`**, not ` pass`
(config-audit's badge 1398 is its `tests` count; `pass` was 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 = tests` re-confirmed against config-audit
(` tests 1398` == badge 1398, `pass` 1374 — count `tests`, and note the suite need not be green
for the census to be valid). **`scanners` is 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-tree` QUOTES non-ASCII paths, and a naive count silently drops them.** okr's
`commands/innføring.md` and `commands/møter.md` come 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 where `set -- $spec` did 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: prompt` entries 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 badges `hooks` and ships a
`hooks.json` has 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.0` is the **only** plugin with a non-command
entry — 3 entries, 2 `command` + 1 `prompt` (`PreCompact`) — and it does **not** badge the axis,
which is precisely why the badged set never tested the rule. The catalog's `3 hooks` stands, measured.
- **The `agents` axis IS badge-covered — 4 of the 6 plugins that claim it (measured 2026-08-13).**
`llm-security` `agents-6`, `config-audit` `agents-7`, `ms-ai-architect` `agents-12`,
`linkedin-studio` `agents-20`; all four match the catalog exactly and are gated. Ungated:
`voyage` (`24 agents`) and `okr` (`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.** `extractStatBadges` keeps only
integer-valued badges, so graceful-handoff's `Pipeline`/`STATE--helper-deterministic` badge leaves
`1 pipeline` ungated even though a badge for the axis visibly exists. When asking "is this axis
gated?", read the badge's VALUE, not its label.
- **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,
check `git show <ref>:README.md` in the plugin repo before believing the working tree.**