A stat-line axis the plugin does not badge was skipped in silence, so "checked and right" and "never checked" printed the same thing. Each plugin now gets an INFO line naming its ungated axes, and the run ends with "stat claims - N gated - M NOT gated (no badge)". INFO never changes status or exit code, not even under --strict. Suggested by repo-standard (reply on BADGE-COUNT, 2026-11-03); operator decision 2026-11-04. First live run: voyage 0 gated / 5 not gated. Suite 244/244. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
323 lines
29 KiB
Markdown
323 lines
29 KiB
Markdown
# 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.
|
||
- **Public-link gate:** `node scripts/check-public-links.mjs` fails when README.md or the manifest
|
||
links to an `open/<name>` repository missing from `PUBLIC_REPOS` in that script — a repository
|
||
made private or removed turns those links into dead links and a manifest entry into a broken
|
||
install. Adding a plugin means adding its repository to `PUBLIC_REPOS` in the same commit;
|
||
removing one means taking it out of both.
|
||
- Plugin source, issues, and releases live in each plugin's own repository — not here.
|
||
- **Every tag this repo cuts is ANNOTATED — `git tag -a`, never a bare `git tag`.** A lightweight tag
|
||
is a branch-like ref: it can be moved to another commit with nothing recorded that it ever pointed
|
||
elsewhere. The catalog pins every plugin to `ref: vX.Y.Z`, so a movable tag is a movable pin — a
|
||
supply chain property, not a tidiness one (repo-standard `TAG-ANNOTATED`, ERROR).
|
||
**Two tagging sites, and only one of them was ever enforced:**
|
||
- *Plugin tags* are cut by `scripts/release-plugin.mjs` (`--create-tag --write`), which has used
|
||
`git tag -a` since its first commit (`9b1838f`) and has never had the defect. Verified 2026-08-17
|
||
against the published surface, not the source: `git ls-remote --tags` on repo-mailbox returns both
|
||
`refs/tags/v0.25.0` (tag object `a9b5eb8`) and the peeled `refs/tags/v0.25.0^{}` (commit `95ac710`)
|
||
— a lightweight tag has no peeled ref. **Do not "fix" `release-plugin.mjs`; it is not the drift site.**
|
||
- *The catalog's own tags* are cut BY HAND, and that is the site with no enforcement. All four
|
||
newest (`v7.7.2`, `v7.7.1`, `v7.7.0`, `v5.0.3`) are lightweight, from the monorepo era. Control
|
||
proving the measurement discriminates: `pre-polyrepo-archive` reports `tag`, and a local probe pair
|
||
(`git tag -a` → `tag`, `git update-ref refs/tags/x` → `commit`) reproduces both outcomes.
|
||
**The published lightweight tags STAY** (decided 2026-08-17, forward-only). The only remedy for an
|
||
already-published tag is `git tag -a -f <name> <name>^{}`, which force-moves a ref others may have
|
||
fetched — precisely the traceless move the finding warns about. Applying it as the fix would
|
||
demonstrate the defect. **Consequence to state plainly, not to hide:** `TAG-ANNOTATED` stays ERROR
|
||
until a NEW annotated tag becomes the newest, because `checkTagIntegrity`'s
|
||
`tags_lightweight_accepted` register key reaches tag HISTORY only and cannot excuse the newest tag.
|
||
And "newest" is `compareTags` order (semver triple), so a fresh-start `v1.0.0` would NOT clear it —
|
||
a clearing tag must sort above `v7.7.2`. Non-semver tags (`config-audit/v5.0.0`,
|
||
`pre-polyrepo-archive`) parse to `[0,0,0,…]` and never rank.
|
||
**The ERROR is one the org ALREADY ACCEPTED, and the acceptance cannot land — measured 2026-08-17.**
|
||
repo-standard's register (`register/repos.json:231`) lists `v7.7.2` under
|
||
`tags_lightweight_accepted["ktg-plugin-marketplace"]`, accepted 2026-08-14 with a rationale recorded
|
||
at `:211-223`: it is a monorepo-era llm-security tag (2026-05-19, pre-split), not a catalog release;
|
||
the README install block pins no ref and all 12 `marketplace.json` refs are plugin-repo tags, so
|
||
**zero install paths consume it**. But `checkTagIntegrity` judges the newest tag at `:711` **before**
|
||
it reads the register at `:719`, and `accepted` is only ever applied to `tags.slice(0, -1)`. So the
|
||
entry for `v7.7.2` is inert: the decision is written down and the gate cannot honour it.
|
||
Run against this repo (importing repo-standard's own exported `checkTagIntegrity`, its real register,
|
||
and `git for-each-ref refs/tags/v*`), the catalog scores exactly two findings:
|
||
`[ERROR] TAG-ANNOTATED` on `v7.7.2`, and `[OK] TAG-ANNOTATED-ACCEPTED` covering the other 7
|
||
lightweight tags. **There is no `TAG-ANNOTATED-HISTORY` WARN — history is fully claimed.**
|
||
Consequence for whoever reads the next org-wide run: this repo's single ERROR is not an unmade
|
||
decision, it is a made decision the checker structurally cannot see. That is the same
|
||
"we decided this" / "nobody looked" collapse the register exists to prevent, one level up. The fix
|
||
belongs in repo-standard, not here — reported, not patched.
|
||
Note: `~/.gitconfig` sets `tag.gpgsign true`, which makes a bare `git tag <name>` **fail**
|
||
("no tag message?") rather than silently cut a lightweight one.
|
||
**CORRECTED 2026-09-18 — annotated tags on this machine ARE signed.** The 2026-08-17 note here
|
||
said the probe's `-a` tag "came out unsigned all the same (0 PGP blocks)". That was a defect in
|
||
the MEASUREMENT, not a fact about the tag: `~/.gitconfig` sets `gpg.format ssh` with
|
||
`user.signingkey ~/.ssh/id_ed25519.pub`, so the signature is an **SSH** signature and counting
|
||
PGP blocks reads 0 on a perfectly signed tag. Re-probed in a fresh temp repo: `git tag -a` →
|
||
**0 `BEGIN PGP`, 1 `BEGIN SSH SIGNATURE`**. The live `llm-security` `v8.0.0` tag object carries
|
||
the same SSH signature. Verifiseringsloven ansikt 4 in this repo's own notes — "found nothing"
|
||
was the wrong question asked, not an absence.
|
||
**What this still does NOT buy:** Forgejo shows "no known key found for this signature in the
|
||
database" on the release page, because the signing key is not registered as a *verification*
|
||
key on the Forgejo account. The tag is signed; nothing upstream can check it. And signing is
|
||
orthogonal to `TAG-ANNOTATED`: it is the `-a` that makes the tag a real tag object, not the
|
||
signature. Do not treat `tag.gpgsign` as the enforcement for annotation.
|
||
- **Releasing a plugin (canonical path — `scripts/release-plugin.mjs`):** since the polyrepo split,
|
||
a release is a THREE-part act — tag the plugin repo, bump the catalog `ref`, AND file the
|
||
Forgejo **release object**. Forgetting the catalog `ref` strands users on the old version;
|
||
forgetting the release object leaves the plugin's public releases page a version behind
|
||
(both are 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`.
|
||
- **Pass `--model "<your model>"` with `--commit`** (operator decision 2026-11-04). The catalog
|
||
commit's `Co-Authored-By` trailer names the model the running session states; without the flag
|
||
it reads `Claude` and no model. It used to hardcode `Claude Opus 4.8 (1M context)` regardless
|
||
of who ran it. A value with a newline or `<>` is refused before any tag or write.
|
||
- **The Forgejo RELEASE OBJECT is the third part of a release (added 2026-09-18, order
|
||
`20260917T235642Z-730962924-from-from-ai-to-chitta`).** A pushed git tag is filed by Forgejo
|
||
under `/tags`; **only an explicit release object appears under `/releases`**. Until this was
|
||
added, `release-plugin.mjs` made a tag and nothing else — `grep releases scripts/*.mjs`
|
||
(excluding `.test.`) returned **0**, against **6** hits for `tags` in the same file as the
|
||
control that the query could find. Consequence, measured 2026-09-18 against the instance's
|
||
own API: **24 repos in org `open`, 21 with at least one tag, and 11 of those 21 had NO release
|
||
object for their newest tag** — llm-security's releases page said `v7.8.3` while the catalog
|
||
pinned `v8.0.0`. That is the same tag-vs-published drift the catalog-`ref` bump exists to
|
||
prevent, one surface further out, and it is what a human reads to answer "what version is out?".
|
||
- **The release body is the plugin's own CHANGELOG section for that version** — falling back
|
||
to the tag message, falling back to empty. Never generated prose: an invented release note
|
||
is a claim about the release that nobody made.
|
||
**CORRECTED 2026-09-18, same day, after the first cut shipped.** The order said "use the
|
||
tag's own message", and that was followed literally — which produced a `v8.0.0` release
|
||
page reading `llm-security v8.0.0` and nothing else. The defect is structural, not a typo:
|
||
`--create-tag` mints `-m "<name> v<version>"`, so **the tag message is mechanical exactly
|
||
where this helper made the tag.** The right source was already proven on the instance —
|
||
`llm-security` `v7.8.3`'s release body is **byte-for-byte** its CHANGELOG `## [7.8.3]`
|
||
section — so the CHANGELOG is the org's established source, not a new invention. All 10
|
||
backfilled repos ship one (measured 2026-09-18, **10/10**).
|
||
- **Three heading dialects are live and all three are load-bearing:** `## [6.0.0] - date`,
|
||
`## [0.2.0] — date` (em-dash), `## v1.0 (date)`, and voyage's
|
||
`## v5.10.1 — date — trailing prose`. The version token is matched **exactly**, so
|
||
`0.1.0-pre` is not `0.1.0`, `1.1.0` is not `1.10.0`, and an EMPTY section (the standing
|
||
`## [Unreleased]`) returns null so the caller falls through instead of publishing a blank.
|
||
- The **tag message** remains the fallback, and where it is used it is read with
|
||
`%(contents:subject)` + `%(contents:body)`, **never `%(contents)`**: `~/.gitconfig` sets
|
||
`tag.gpgsign` with `gpg.format ssh`, so `%(contents)` drags the whole
|
||
`-----BEGIN SSH SIGNATURE-----` block into the release notes (verified on `llm-security`
|
||
`v8.0.0`).
|
||
- The run **reports which source it used** (`changelog` / `tag-message` / `none`). `none`
|
||
is the one worth seeing: the page went up with an empty body — honest, but useless.
|
||
- **It fires only on a run that PUBLISHES** (`--create-tag --write`, or `--push`). Filing a
|
||
release object is itself a publish, so it must not ride along on a local `--write` and slip
|
||
past the operator's one-shot push token. A non-publishing `--write` says so and names the
|
||
backfill instead of going quiet.
|
||
- **The step is synchronous (curl via `execFileSync`), like `check-versions.mjs`'s
|
||
`checkHomepage`, and for a sharper reason:** `runRelease` is called from `main()` WITHOUT an
|
||
`await` and returns the code `main()` hands to `process.exit`. An async step would return a
|
||
Promise nobody awaits, so a rejected POST would surface as an unhandled rejection *after* the
|
||
run had already exited 0 and called the release complete.
|
||
- **`429` and the `502`/`503`/`504` gateway family are retried with backoff, never swallowed.**
|
||
Both were seen live on 2026-09-18 — an unthrottled sweep of the 24 repos drew **17 HTTP
|
||
429s**, and the first version of that sweep coerced every one of them to an empty list, so
|
||
"verified nothing" and "verified everything, all clean" were indistinguishable. Anything
|
||
else, including a `403` from a token without `write:repository`, is an ANSWER and is raised.
|
||
- **The token reaches curl through a 0600 header file, never through `argv`** — `argv` is
|
||
readable via `ps(1)`, and this token can write to every repository in the org.
|
||
- **Backfill / retry: `scripts/backfill-forgejo-releases.mjs`** (dry-run by default, `--write`
|
||
applies, `--repo <owner>/<name>` narrows). It reuses `release-plugin.mjs`'s own planner and API
|
||
shell rather than reimplementing them, so the backlog and the release path cannot drift apart.
|
||
It considers **only the newest tag** (that is the FERDIG-criterion the order set), and reports
|
||
the denominator. **`--repair` is the second mode:** it replaces a release body that should
|
||
have been the CHANGELOG section but isn't — the backlog the first cut created. Because it
|
||
PATCHes a **published** page, the bar is deliberately *strictly more informative*, never
|
||
merely *different*: no CHANGELOG section means no update (a body is never blanked), and a
|
||
hand-written body at least as long as the CHANGELOG section is left exactly as it is
|
||
(measured 2026-09-18: that rule is what protects `portfolio-optimiser` `v1.1.0`, whose
|
||
4750-char hand-written body beats its 3704-char CHANGELOG section).
|
||
**Documented exception:** `ktg-plugin-marketplace` `pre-polyrepo-archive` —
|
||
an archive marker for the monorepo before the polyrepo split, not a release (non-semver, and
|
||
no install path consumes it). The exclusion register is keyed by repo **AND tag**, never by
|
||
repo alone, so that repo's next real release is still backfilled.
|
||
- **`--create-tag --write` and `--push` each require the operator's push-approval token FIRST**
|
||
(Q3, decided 2026-09-12): `~/.claude/hooks/pre-push-gate.sh` matches `git push` in command
|
||
text and cannot see a push this script issues via `execFileSync` inside node — the script
|
||
mints+pushes a plugin tag and pushes the catalog itself, both invisible to that gate. So
|
||
before either push, run:
|
||
`mkdir -p "$HOME/.claude/runtime/push-approvals" && touch "$HOME/.claude/runtime/push-approvals/$(pwd | sed 's|/|_|g')"`
|
||
(`pwd` must be this catalog directory — tag-push and catalog-push share ONE token, one
|
||
publish from the operator's perspective). The script consumes the token itself right after
|
||
a push succeeds, the same way `post-push-consume.sh` does for a direct push; the token stays
|
||
intact for a retry only when the run makes ZERO successful pushes — once any push in the run
|
||
succeeds (e.g. the tag push in `--create-tag --write --commit --push`), the token is spent even
|
||
if a later push in that same run then fails. Covered by `pushAuthorisation`/`requirePushAuthorisation`/
|
||
`pushWithToken`/`consumeToken` in `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), that the catalog
|
||
`ref` matches `plugin.json` version (WARN — catalog lags or an unreleased bump), and that
|
||
`plugin.json`'s `homepage`, if present, actually resolves (ERROR if dead — a live `curl` check,
|
||
synchronous by design, see the comment at `checkHomepage` for why it must never become async).
|
||
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`).
|
||
- **`homepage` is OPTIONAL, not required** (decided 2026-08-26): measured that 11 of 12
|
||
plugins omit the field. Requiring it would flip those 11 to ERROR and, via
|
||
`release-plugin.mjs`'s ERROR-set pre-flight (catalog-wide), block every release in the
|
||
marketplace until each of those 11 sibling repos re-tags — a marketplace-wide freeze
|
||
delivered by a script fix. The gate only fires when the field is present AND resolves
|
||
to a definitive 4xx/5xx; a network failure (timeout/DNS) reads as "not checked", never
|
||
as a confirmed dead link.
|
||
- **SKIP is UNVERIFIED, not clean, and now blocks the gate by default.** Measured 2026-08-18: a
|
||
fresh clone with zero sibling repos printed `12 plugins — 0 OK, 0 WARN, 0 ERROR, 12 SKIP` at
|
||
**exit 0** — a run that verified nothing was indistinguishable from a run that verified
|
||
everything and found it clean (Verifiseringsloven ansikt 4, in the org's own release gate).
|
||
Any SKIP now fails the run unless `--allow-skip` is passed explicitly, and the summary line
|
||
always reports the denominator: `— verified N/M`. `runGate`'s `hasError`/`hasWarn`/`failed`
|
||
fields are unchanged in meaning; the new SKIP-based failure is a separate `unverified` field
|
||
folded into `failed` — `release-plugin.mjs`'s pre-flight (`preflightErrors`) reads the
|
||
ERROR set directly off `results`, never `failed`, so it is untouched by this.
|
||
- **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 not checked, so there is no exception list
|
||
to maintain — but since 2026-11-04 it is **named, not silent**: each plugin prints an INFO line
|
||
listing its ungated axes, and the run ends with `stat claims — N gated · M NOT gated (no badge)`
|
||
(operator decision, after repo-standard's BADGE-COUNT reply). INFO never changes status or the
|
||
exit code, not even under `--strict`. First live run: voyage, the catalog's one plugin, `0 gated ·
|
||
5 NOT gated` — its whole stat line rests on hand re-measurement. **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` (claude-design has since left the catalog, 2026-09-23). 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.
|
||
**RESOLVED 2026-08-14/15 (commit `3f5afee`): repo-standard's checks axis is now consistent at 20.**
|
||
The three-way disagreement at `v0.11.1` — catalog bullet said "Twelve", catalog stat line said 14,
|
||
plugin's own README said "twelve checks" — is gone, and it was resolved on repo-standard's side, not
|
||
by the catalog picking a number: `v0.11.2`'s CHANGELOG records the plugin removing its own stale prose
|
||
counts ("170 tests" and "twelve checks") rather than correcting them, making the README's own
|
||
"Check | What fails it" table the sole source of truth. That table is unchanged between `v0.11.1` and
|
||
`v0.11.2` (verified: `diff` of the table is empty) and has **20 rows**, counted independently this
|
||
session (`git show v0.11.2:README.md` in the sibling repo, First screen through Description). The
|
||
catalog's repo-standard block (`README.md:206` bullet, `README.md:212` stat line) now reads "20
|
||
checks" in both places, matching. The earlier "19 exported `check*` functions" count was a different
|
||
unit (exported functions, not documented table rows) and is superseded by the table now being
|
||
canonical — no further ask to repo-standard is needed.
|
||
**⚠️ 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 was **not** re-measured for years. **Re-measured
|
||
2026-09-05 against the now-pinned `v0.33.1`:** badge reads `selftest_checks-868`
|
||
(`git show v0.33.1:README.md` in the sibling repo), the catalog's own stat line already agrees
|
||
(corrected 09-04), gate green. Repo-mailbox's own listed per-script counts still only sum to 792
|
||
(220+360+73+99+40) against the 868 badge — the same badge-less-prose pattern recurring. 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; 3/3 since claude-design left the catalog 2026-09-23). `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.**
|