Follow-up measurement to acdd606. The ERROR this repo carries is not an
unmade decision -- it is a made decision the checker structurally cannot see.
repo-standard's register already lists v7.7.2 under
tags_lightweight_accepted["ktg-plugin-marketplace"] (register/repos.json:231),
accepted 2026-08-14, with the rationale recorded at :211-223: monorepo-era
llm-security tag, pre-split, no install path consumes it. But
checkTagIntegrity judges the newest tag at :711 BEFORE reading the register at
:719, and applies `accepted` only to tags.slice(0, -1). The entry is therefore
inert.
Measured, not reasoned: importing repo-standard's own exported
checkTagIntegrity with its real register and `git for-each-ref refs/tags/v*`
returns exactly two findings for this repo -- [ERROR] TAG-ANNOTATED on v7.7.2,
and [OK] TAG-ANNOTATED-ACCEPTED covering the other 7 lightweight tags. No
TAG-ANNOTATED-HISTORY WARN: tag history here is fully claimed already.
That is the "we decided this" / "nobody looked" collapse the register was
built to prevent, reappearing one level up. Fix belongs in repo-standard;
reported to them and to .claude, not patched from here.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FLwHvGkH2kV4XLjUHorhLu
198 lines
18 KiB
Markdown
198 lines
18 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.
|
||
- 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. Measured 2026-08-17, the probe's
|
||
`-a` tag came out **unsigned** all the same (0 PGP blocks) — so this config is an accident that
|
||
happens to block one path, not a signing guarantee. Do not treat it as the enforcement.
|
||
- **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.
|
||
**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 **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.**
|