ktg-plugin-marketplace/CLAUDE.md
Kjell Tore Guttormsen ac7ad424d1 fix(scripts): run check-versions BEFORE release-plugin writes, not after
release-plugin.mjs --write wrote marketplace.json and the catalog README label
first, and only THEN ran check-versions via execFileSync (which throws on exit
1). A red catalog therefore left a half-applied release in the working tree —
exactly the state a parallel session has already been observed carrying to the
public remote.

Adds applyRelease() with an injected io seam so the ORDER is testable: runGate()
runs first, and any ERROR aborts with nothing written. The pre-flight reads the
ERROR set only, never failed/--strict — pre-bump the released plugin is SUPPOSED
to be WARN (catalog ref behind plugin.json), so a WARN gate would brick every
release. Verified against the real classifier, not synthetic data.

The post-write gate stays: pre-flight validates the old state, that one
validates the new state.

Known remaining hole, documented not built: --create-tag mints and pushes the
plugin tag before the pre-flight runs.

Tests 14 -> 19 (120 -> 125 across the six suites); check-versions 12 OK.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018Ga5tZ3AgUxAcdLtWB8Kig
2026-08-10 20:34:38 +02:00

88 lines
7.5 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` mints+pushes a missing plugin
tag first. 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. **Known remaining hole:** `--create-tag` mints and pushes
the plugin tag *before* the pre-flight runs, so that irreversible side effect is still unprotected.
- **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-04 at the pinned refs, all 12 plugins: 42 catalog axis-claims —
27 badge-covered · 15 badge-less across 7 of the 12 plugins** and therefore ungated. That supersedes
the 2026-08-02 reading (`24 · 15 across 7 of 11`); only two entries moved in between and both
reconcile exactly — repo-mailbox now badges the two axes it did not (`+2` gated, `-2` ungated) and
repo-standard entered the catalog (`+1` gated, `+2` ungated). The 15 ungated axes were hand-audited
at their pinned tags — 13 on 2026-08-02 (**14 of that pass's 15 were exact**; the defects were
repo-mailbox `6 CLI scripts` (true 8) / `251 selftest checks`, and voyage `500+ tests` against a
measured 832), and repo-standard's two on 2026-08-04. Every ungated value currently stands measured
at the ref it is pinned to — verified on **both** sides 2026-08-04, because a ref that has not moved
only proves the *badge source* is unchanged and catalog prose is itself ungated: no ungated plugin's
ref has moved since its audit, **and** diffing `extractCatalogStats` against the 08-02 README shows
the only ungated numbers the catalog has restated since are repo-standard's two (absent then).
**⚠️ 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 COMMAND entries** in `hooks/hooks.json` (not
events, not matchers — the three diverge, and `commands` is what matched the badge on all 6 badged
plugins), 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.
- **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.**