Compare commits

..

No commits in common. "main" and "v0.7.1" have entirely different histories.

11 changed files with 76 additions and 2977 deletions

View file

@ -1,6 +1,6 @@
{ {
"name": "repo-standard", "name": "repo-standard",
"version": "0.11.2", "version": "0.7.1",
"description": "Per-repo gate for the open/ presentation standard: README first screen, install block, files required by the repo's class, and dead repo references.", "description": "Per-repo gate for the open/ presentation standard: README first screen, install block, files required by the repo's class, and dead repo references.",
"author": { "author": {
"name": "Kjell Tore Guttormsen" "name": "Kjell Tore Guttormsen"

View file

@ -4,367 +4,6 @@ All notable changes to this project are documented here.
Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/);
versioning is [Semantic Versioning](https://semver.org/spec/v2.0.0.html). versioning is [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
## [0.11.2] — 2026-08-14
### Changed
- **Register: `ms-ai-architect` removed from `locales`.** Four sweeps in a row
(0407) held a correct `README-LANGUAGE` `WARN` against an entry that was
itself wrong: the repo's published README disavows a Norway-only audience in
its own words ("useful for any enterprise context", line 17, verified against
the forge), and its 24 Norway markers are traits — what the code does — not
audience. The register was the wrong side; the prose stands. Decided on
org-ops' measured case (their D35).
- **Register: `ktg-plugin-marketplace v7.7.2` accepted as lightweight
history.** Previously deliberately absent because the newest tag has the one
safe remedy. The catalog's measured counter: v7.7.2 is a monorepo-era
llm-security tag (2026-05-19, pre-split), not a catalog release, and zero
install paths consume it — force-moving a published ref for no consumer costs
more than the finding. The catalog committed to annotating every future tag,
which the check itself enforces.
- **README: the two prose counts that had gone stale are now count-free.**
"170 tests" (actual: 245) and "twelve checks" survived two releases
unchanged — the ungated copy of a number is the defect, so the copies are
removed rather than corrected. `npm test` prints the test count; the check
table (20 rows) is the canonical check list.
### Fixed
- **`FILE-MISSING` named the class even when a TRAIT required the file.**
`security``SECURITY.md` read as "missing required file for class
`standalone`", sending the operator looking for a requirement the class
never listed. `requirementsFor` now carries the source (`class` vs. `trait`)
alongside each required file, and the message names whichever actually
required it. 245 tests, from 243.
## [0.11.1] — 2026-08-13
### Fixed
- **`TAGS-SIGNED`, the affirmative verdict, was near-unreachable.** It was gated
on `findings.length === 0`, so the presence of a `TAG-SIGNED-PREPOLICY` `OK`
suppressed it — and every repo in the org has pre-policy tags. Caught by
dogfooding the v0.11.0 release: this repo signed the first tag the check ever
judged, and the gate did not say so.
The status was green either way, which is what made it worth fixing rather
than shrugging at: a reader could not tell *this repo signed its new tag* from
*this repo has cut no tag since the policy*. Two different facts wearing one
silence — the same defect as an exemption nobody can see, which this engine
already refuses to ship one check over. The gate now keys the affirmative on
the JUDGED findings only, and the two cases carry different sentences.
Measured after: 18 repos emit both `TAGS-SIGNED` and `TAG-SIGNED-PREPOLICY`,
3 `TAG-SIGNED-NONE`, still 0 ERROR and 0 WARN. 243 tests, from 241.
## [0.11.0] — 2026-08-13
### Added
- **`TAG-SIGNED` — a release tag that names a signer, not just whoever could
write to the forge.** The catalog pins plugins by tag, so this sits on the
same supply-chain axis as `TAG-ANNOTATED`: annotation makes a tag
immovable-without-a-trace, a signature makes it attributable.
The check had been parked for a release as "blocked on an operator decision",
on the strength of 0 of 18 repos signing. That zero was never evidence against
it — it is what a practice nobody has adopted yet looks like, and reasoning
from it makes the gate ratify the status quo it exists to move. The rule that
settled it (operator, 2026-08-13) asks something else entirely: *would a public
AAA+ repository do this?* Yes. Unlike the two legitimate conventions that
killed `VERSION-DRIFT`, there is no second convention under which release tags
are better left unattributable.
**The policy is a DATE, and the date is what made the check shippable.**
Measured across 20 clones before the rule was locked: 158 `v*` tags, 144
annotated, 14 lightweight, 0 signed, and not one tag dated after 2026-08-12. A
boolean "this org signs" would have failed 20 correct repositories the day it
landed, with force-moving 144 published refs as its only remedy — the mechanism
that gets gates switched off. `tags_signed_from` lives in the register, never
in the engine (the rule that keeps the org-profile exemption a flag rather than
a class name in a classifier); absent it, the check is a `notRun` SKIP, because
the gate never assumes a policy nobody recorded.
Verified both directions: a sweep of 21 repos yields 18 `TAG-SIGNED-PREPOLICY`
+ 3 `TAG-SIGNED-NONE`, 0 ERROR, 0 WARN — then, against real git objects, an
unsigned post-policy newest tag does fire `ERROR`. A sweep returning zero
proves nothing alone; a dead check returns zero too.
Two exclusions keep one defect from being counted twice under two codes: a
**lightweight** tag has no tag object to carry a signature at all
(`TAG-SIGNED-LIGHTWEIGHT`, a `byDesign` skip naming `TAG-ANNOTATED` as the
owner), and a **pre-policy** tag was correct when it was cut
(`TAG-SIGNED-PREPOLICY`, an `OK` — said out loud, never as silence).
It answers *is this tag signed* and never *does the forge vouch for it*: the
signature is read from the clone, while the forge's "Verified" badge needs a
key registered there.
### Changed
- The engine reads tag signature and creation date alongside the object type, in
the same single `for-each-ref`. The signature is read with
`%(if)%(contents:signature)%(then)…` rather than by printing the block, which
is multi-line and would break the line-oriented parser on exactly the tags the
check cares about. No new API call — this stays at three.
- 241 tests, from 230.
## [0.10.1] — 2026-08-12
### Fixed
- **The skill had drifted behind the engine in the release that banned exactly
that.** 0.10.0 canonized "a new check is not shipped until its row exists" and
shipped with `SKILL.md` still saying "the two network calls" after the third
had been added, and with no mention of `VERIFY-COMMAND` at all. The catalog
pins by tag, so the stale skill was the one an installed reader actually got —
which is why this is a release rather than a note.
It also now names the trap in fixing a `VERIFY-MISSING`: document the command
that already exists, after running it. Writing `npm test` into a README
because the finding mentioned it, with no `scripts.test` behind it, turns a
`WARN` about missing documentation into a false claim on the first screen — a
worse defect than the one being fixed, and one this gate can never catch,
because it runs nothing.
## [0.10.0] — 2026-08-12
### Added
- **`VERIFY-COMMAND` — can a stranger check that this repository works?** The
forge has no Actions runners registered, so this org publishes no CI badge;
the stated substitute is one command a stranger can run from a clean clone. A
repository with something runnable and no such command in its README is a
`WARN` in the `missing` bucket, and the finding names what the repo already
has, so the remedy is one line.
| outcome | level | code |
|---|---|---|
| the README shows a verification command | `OK` | `VERIFY-COMMAND` |
| nothing runnable exists in the repository | `OK` | `VERIFY-NONE` |
| a suite exists and the README shows no command | `WARN` | `VERIFY-MISSING` |
The subject is **measured**, never read off a class: a `scripts.test` entry, or
a tracked file that is unambiguously an executable test — not merely a file
living under `tests/`, which counted golden transcripts and fixtures and would
have made "you have tests a stranger cannot run" false on the loudest findings
it produced. Measured across all 21 registered clones: 10 document a command,
6 do not, and 5 have nothing runnable at all. Those five span three different
classes, so every class-level phrasing of this rule fails a correct repository
somewhere.
The 6-of-21 rate is acceptable for a reason that travels with the rule: the
org publishes exactly one stance on verification, so an undocumented suite is
not a second legitimate convention the way bump-first vs bump-at-release was
for the rejected `VERSION-DRIFT`.
It adds **no API call** — it reads the README and `package.json` — and so has
no `SKIP` at all, unlike every check added since `PIN-DEAD`. It can only ever
fell a MISSING command: nothing here runs anything, so its `OK` says
*documented*, never *passing*.
- **`RELEASE-CURRENT` — does the release page show the version the code is on?**
A stranger who wants a version they can name lands on the forge's release
page, and it is the one surface refs cannot answer: a release is not a ref, so
`git ls-remote` has nothing to report. This is therefore the third API call
per invocation, and the only new one the acquisition model adds.
Both sides are read from the FORGE, never from the clone. Comparing a local
tag against a published release would report `portfolio-optimiser` as having a
stale release when the actual defect is a tag that was never pushed (v1.0.0
local, v0.1.0 published) — a different finding, owned by a different check.
Four outcomes, and three of them are `OK`:
| outcome | level | code |
|---|---|---|
| releases or refs unreadable | `SKIP`/`notRun` | `RELEASE-UNAVAILABLE` |
| no tags on the forge | `OK` | `RELEASE-NONE` |
| tags, no release published | `OK` | `RELEASE-TAGS-ONLY` |
| newest release is the newest tag | `OK` | `RELEASE-CURRENT` |
| newest release lags the newest tag | `WARN`/`weakening` | `RELEASE-STALE` |
The levels come from a measurement across all 22 registered repos
(2026-08-12): 4 have no tags, 2 tag without ever publishing a release, 11 are
current, 5 lag. Those 2 — `ktg-plugin-marketplace` and `llm-security-commons`
— are why zero releases is an `OK` and not a finding. Nothing in a repository
says which of the two legitimate conventions it follows, and a gate that fails
a correct repository is the mechanism that gets gates switched off. That is
the same measurement that rejected `VERSION-DRIFT` one check over.
Lagging is a `WARN` rather than an `ERROR` for two reasons. The remedy is
safe: publishing a release for a tag that already exists moves no published
ref, unlike the remedy `TAG-ANNOTATED` has to withhold from tag history. And
this repository is the worst offender in the org — v0.9.0 tagged, v0.3.0
published — so an `ERROR` would have let the gate settle an operator
question, whether this org publishes releases at all, by exiting 1 on its own
author.
- **`REMOTE-SYNC` — a tag that exists only in the operator's clone.** This is
the blind spot in `VERSION-TAG` rather than a duplicate of it: `VERSION-TAG`
reads LOCAL tags, so a manifest claiming `1.0.0` against an unpushed `v1.0.0`
reads as a clean pass while no stranger can resolve that version.
`portfolio-optimiser` is the measured case, and it read `OK` until this check
existed.
`ERROR`/`broken`, with the remedy named: `git push origin <tag>`. Measured
across all 21 registered clones (2026-08-12): exactly one repo has an unpushed
tag, and none is behind the forge. One subject is what got `BRANCH-STALE`
rejected — the difference is that an unpushed tag is never one of two
legitimate conventions the way tag-only releasing is, the remedy moves no
published ref, and the finding recurs at every release rather than once.
The reverse direction is deliberately not a finding. A clone that has not
fetched lately is behind the forge and nothing about the repository is wrong;
firing there would fail correct repositories on the reader's machine state.
### Changed
- **The API-call count in `CLAUDE.md` is three, updated in the same commit as
the check that changed it.** That line has now gone stale twice; the first
time it stayed stale until a 13-repo shell loop trusted it and tripped the
rate limiter.
- **The "429 at ~40 requests" figure is marked unusable.** The run that produced
it did not record which endpoint class it hit, and org-ops measured ~110
sequential raw reads at 0.4s with zero 429 the same day. Either those are two
different limiters or the older figure is wrong, and the note cannot tell them
apart. One API-axis figure this repo can stand behind was recorded instead: 22
sequential `/api/v1/` calls at 2s pacing, anonymous, zero 429.
- **The README's own surface, which had drifted behind the engine.** Four checks
had shipped with no row in the check table (`PIN-DEAD`, `TAG-ANNOTATED`,
`RELEASE-CURRENT`, `REMOTE-SYNC`), and the Requirements section still said
"two network calls" after the third had been added. A stale count on the
README of the repo whose job is presentation is the defect this gate exists to
find, one file over.
### Not built
- **`RELEASE-ASSETS` — rejected permanently, for having no subject.** No release
in the org carries an asset, but the deciding measurement is the other one: 0
of 21 READMEs mention an asset download at all. Every install path here runs
through `marketplace add`, `plugin install`, `pip`/`uv` or a clone, so no
reader is harmed and no remedy would ever be performed. The fire rate alone
(18 of 18) is a proxy and must not be quoted as the reason.
- **`TAG-SIGNED` — blocked on an operator decision, which is not the same as
rejected.** No repo signs its tags today. If the operator decides this org
signs them, the check acquires its entire subject the same day; a rejection is
permanent, and this is one yes/no away.
## [0.9.0] — 2026-08-09
### Changed
- **`VERSION-UNAVAILABLE` (`SKIP`/`notRun`) is now `VERSION-NONE` at `OK`.** A
repository that claims no version in any of the four places one can be written
down has not left a check un-run — the check ran, saw all four, and found no
subject. That is the shape `checkReadmeLanguage` has always answered with `OK`
("no running prose to judge"), and filing it as a skip told three
correctly-built repositories that something about them was unread.
0.8.0 deferred this deliberately and recorded the reason in the engine:
re-levelling "moves a repo's status". That was measured false. An added `OK`
cannot worsen the worst *judged* finding, so `status` moves only for a repo
whose entire finding set was skips — and all three repos that emit this code
(`ktg-plugin-marketplace`, `playground-design-system`,
`portfolio-optimiser-commons`) already read `OK`. The comment is gone with the
code it justified.
Nor can `OK` here bless a real gap: no class in the register requires a
version file, and a `plugin` missing its manifest is an independent
`FILE-MISSING` `ERROR`.
**The code was renamed, not just re-levelled.** `-UNAVAILABLE` is this
engine's naming for a `notRun` skip (`DESC-UNAVAILABLE`), and one `OK`-level
`-UNAVAILABLE` would mislead exactly the reader this change is for. A source
scan holds the old name out of the engine, so a re-introduction fails the
suite rather than the next census.
Verified before release: 170 tests; 19 clones swept with both engines offline,
223 judged findings, every `status` and `buckets` identical. Exactly three
findings converted — `notChecked` 14→13, 3→2, 14→13, `notRun` 3→2 in each, and
`byDesign` untouched. No repository's `status` moved. Remaining `notRun` across
the org is actionable by construction: `INSTALL-TRUTH` (12), `VERSION-TAG` (1),
plus the two that only appear under `--offline`.
## [0.8.1] — 2026-08-09
### Fixed
- `SKILL.md` glossed `SKIP` as "the check could not run" in its opening list and
then contradicted itself five lines later, where 0.8.0 had added the two
kinds. A session reading only the list would report every `byDesign` skip as
something that failed to run, and send someone to fix fixture links that are
deliberate. Shipping 0.7.0 without its skill update produced the same class of
defect; this is the check that was supposed to stop it happening twice.
- README's `--offline` note said the dependent checks "report `SKIP`, not `OK`"
without saying which kind, now that the summary line only names one of them.
They are `notRun` and clear on a networked re-run — an offline pass is never a
fully judged one, which is the whole reason the qualifier exists.
- README's bilingual-undecidable paragraph likewise names its kind, and the
action that resolves it.
## [0.8.0] — 2026-08-09
### Added
- **Skips split into the two facts they always were.** Every `SKIP` finding now
carries `skip: 'byDesign' | 'notRun'` beside its level, and results carry
`skips: { byDesign, notRun }` beside `status`. `byDesign` is a check that saw
the thing and declined — a link leaving the repository, a dead link in a
fixture path; it can never become a verdict and nobody has an action.
`notRun` is one a re-run or an operator action resolves — an unreachable
catalog, an unregistered repo, a repo with no tags.
0.7.0 took `SKIP` out of the severity lattice, which fixed "clean repos look
skipped". It left both kinds inside one number, which said the same thing
more quietly: measured across 19 clones, 35 of 39 skips were deliberate, so
`portfolio-optimiser — OK · 11 not checked` announced eleven unread checks
when all eleven were fixture and out-of-repo links. org-ops named the split
in its census-05 report without a name for it.
The kind is set where the finding is emitted, never looked up from its code:
`VERSION-TAG` is emitted at `SKIP` with no tags and at `ERROR` with the wrong
one, so a code→kind map would have to re-derive a reason the emission site
already had. An untagged skip counts as `notRun` — the loud side, because a
skip of unknown kind must not inherit "deliberate, nothing to see".
### Changed
- **The summary line names only what someone has an action on.** A repo whose
every skip was deliberate prints an unqualified verdict; `· N not run`
appears only when N > 0. The deliberate ones are not silenced — the detail
output gained a `NOT JUDGED — deliberately outside what this gate decides`
heading beside `NOT CHECKED`. Landing the split in `--json` alone would have
left the human-facing symptom exactly where it was.
- `notChecked` is untouched: still present, still a **number**, still the total
of both kinds. A consumer doing `notChecked > 0` against an object would get
a silent false — the same class of quiet wrong answer this axis exists to
remove. Absent `skips` means an engine older than 0.8.0; absent `notChecked`
one older than 0.7.0. Neither absence is zero.
- `VERSION-UNAVAILABLE` stays `notRun` by decision, not by default. "No package
version to compare against" is arguably a third thing — no *subject* to
judge, the shape `checkReadmeLanguage` answers with `OK` rather than `SKIP`
but re-levelling it moves a repo's `status`, and that is a second behaviour
change. Recorded in the engine so the next reader knows it was decided.
- README example output cites `@<sha>` instead of a commit hash. Three releases
running, those examples went stale against the behaviour they demonstrate; a
placeholder cannot.
Verified before release: 169 tests; 19 clones swept with both engines offline,
220 judged findings and every `status`, `notChecked` and `buckets` identical,
with no finding differing beyond the new tag. The exhaustiveness test scans the
engine source rather than the finding set — a corpus test only sees the sites it
manages to trigger, and the next emission site added would inherit the reader's
default invisibly.
## [0.7.1] — 2026-08-09 ## [0.7.1] — 2026-08-09
### Fixed ### Fixed

237
CLAUDE.md
View file

@ -39,36 +39,6 @@ would recreate, in data, exactly the drift this plugin exists to remove.
clean", so coverage rides its own axis: `notChecked` in `--json`, a `· N not clean", so coverage rides its own axis: `notChecked` in `--json`, a `· N not
checked` qualifier on the summary line. Absent `notChecked` means an older checked` qualifier on the summary line. Absent `notChecked` means an older
engine and prints the pre-0.7.0 line — it is not zero. engine and prints the pre-0.7.0 line — it is not zero.
- **A skip is one of TWO facts, and merging them says the same wrong thing more
quietly.** `byDesign` (the check saw it and declined — out-of-repo links,
fixture paths; it can never become a verdict, nobody has an action) versus
`notRun` (a re-run or an operator action resolves it). Measured across 19
clones, 35 of 39 skips were the first kind, which is why `portfolio-optimiser
— OK · 11 not checked` read as eleven unread checks. The kind is set at the
EMISSION site, never looked up from the code: `VERSION-TAG` is emitted at
`SKIP` with no tags and at `ERROR` with the wrong one, so a code→kind map has
to re-derive a reason the emission site already had. Untagged falls to
`notRun`, the loud side. The summary line names only `notRun`; the deliberate
ones keep their own `NOT JUDGED` heading, because the wanted side effect is
exposure, not silence — landing the split in `--json` alone would have left
the symptom untouched. The test that holds this is a scan of the engine
SOURCE, not of a finding set: a corpus test only sees the sites it manages to
trigger, and the next site added would take the reader's default invisibly.
- **"Nothing to judge" is an `OK`, not a skip — and it is a THIRD shape, not a
third bucket.** A check that ran, saw everything, and found no subject has
produced a verdict: there is nothing here to be wrong. `checkReadmeLanguage`
already answered that way for a README with no prose; `VERSION-NONE` (0.9.0,
was `VERSION-UNAVAILABLE`/`notRun`) does for a repo that claims no version.
The deferral that kept it a skip for one release was recorded in the engine as
"re-levelling moves a repo's status" — measured false: an added `OK` cannot
worsen the worst *judged* finding, and all three affected repos already read
`OK`. Status moves only for a repo whose *entire* finding set was skips. The
lesson is not about this code: **a recorded decision that is wrong is worse
than no record**, because the next session re-derives the measurement instead
of reading it. And the guard against `OK` blessing a real gap is that the gap
is caught elsewhere — no class requires a version file, and a `plugin` missing
its manifest is an independent `FILE-MISSING` `ERROR`. Without that, this
would have been "SKIP is never a pass" one level up.
- **When a check fires many times in one repo, suspect the CHECK.** The first - **When a check fires many times in one repo, suspect the CHECK.** The first
link pass produced ~30 findings against `llm-security` and all were noise — link pass produced ~30 findings against `llm-security` and all were noise —
regexes in code spans, `file:` URLs, relative paths resolved against the wrong regexes in code spans, `file:` URLs, relative paths resolved against the wrong
@ -88,38 +58,20 @@ would recreate, in data, exactly the drift this plugin exists to remove.
registered (measured). The substitute is one command from a clean clone, said registered (measured). The substitute is one command from a clean clone, said
plainly. A static badge asserting a run is the anti-pattern this gate flags — plainly. A static badge asserting a run is the anti-pattern this gate flags —
and an early draft of this README carried one. and an early draft of this README carried one.
- **A dead pin is not a dead link, and it is not a wrong manifest.**
`PIN-DEAD` asks whether the ref a README install command pins actually
resolves — `LINK-DEAD` asks whether the repo exists, `VERSION-TAG` asks
whether the MANIFEST's version was tagged. All three coincided on guard only
because one wrong number was written in three places. It is an `ERROR`
because a dead documentation link costs a stranger a 404 while a dead pin
costs them the install. Resolved against the FORGE, never the clone: a local
tag can exist unpushed, which portfolio-optimiser demonstrates. A pin at a
branch or a sha is a `byDesign` skip — `ls-remote --tags` cannot answer it,
and looseness is a different finding from deadness.
- **Three outcomes on references.** "No match" and "match on a known non-repo" - **Three outcomes on references.** "No match" and "match on a known non-repo"
must stay distinct findings. Collapsing them hides real loss inside correct must stay distinct findings. Collapsing them hides real loss inside correct
text — the exact defect class this gate exists to catch. text — the exact defect class this gate exists to catch.
- **Three API calls per invocation, anonymous, with 429 retry.** The org listing - **Two API calls per invocation, anonymous, with 429 retry.** The org listing
(description + topics) is one; the catalog's `marketplace.json` for (description + topics) is one; the catalog's `marketplace.json` for
INSTALL-TRUTH is the second (added after this used to say "one call" — that INSTALL-TRUTH is the other (added after this used to say "one call" — that
line went stale and stayed stale until a 13-repo shell loop trusted it and line went stale and stayed stale until a 13-repo shell loop trusted it and
tripped the rate limiter at 26 requests); the repo's releases listing for tripped the rate limiter at 26 requests). Both go through `fetchWithRetry`,
`RELEASE-CURRENT` is the third, and is the only subject the cheaper git which retries HTTP 429 rather than silently reporting SKIP. Both are
channel cannot cover, because a release is not a ref. This count is the line
that has now gone stale twice — it is updated in the SAME commit as the check
that changes it, never afterwards. All go through `fetchWithRetry`,
which retries HTTP 429 rather than silently reporting SKIP. All are
anonymous — no token, confirmed no different with one — so the gate works anonymous — no token, confirmed no different with one — so the gate works
for any reader, not only someone holding one. A sweep across every repo for any reader, not only someone holding one. A sweep across every repo
still does not belong here: it needs the listing fetched once, not once per still does not belong here: it needs the listing fetched once, not once per
invocation, which is a different shape of caller (org-ops), not a flag on invocation, which is a different shape of caller (org-ops), not a flag on
this engine. **The arithmetic moved with the count**: a full online sweep of this engine.
22 repos is now 66 API calls, not 44. Nobody has measured 66, and the only
knee ever measured is the ~40 marked unusable below. A sweeping caller paces
or fetches once — this sentence exists so the number is not re-derived from a
stale "two calls", which is how that line failed twice.
**The "13 calls in a loop" explanation was incomplete** (2026-08-04): the **The "13 calls in a loop" explanation was incomplete** (2026-08-04): the
forge's nginx never sends `Retry-After` on its 429s (measured directly), so forge's nginx never sends `Retry-After` on its 429s (measured directly), so
`fetchWithRetry` always falls back to exponential backoff — the `fetchWithRetry` always falls back to exponential backoff — the
@ -131,72 +83,6 @@ would recreate, in data, exactly the drift this plugin exists to remove.
is a leaky bucket, not a fixed ban — a 20-25 request burst took up to ~15s is a leaky bucket, not a fixed ban — a 20-25 request burst took up to ~15s
to fully drain. `fetchWithRetry` defaults to `retries: 5` / to fully drain. `fetchWithRetry` defaults to `retries: 5` /
`maxDelayMs: 8000` (23s worst case) to cover that. `maxDelayMs: 8000` (23s worst case) to cover that.
**Burst is not the only way in** (2026-08-12): a *steady* 1 request/second
tripped 429 at around request 40, so pacing alone does not buy immunity —
only a smaller total does. Recovery measured at 35s drain plus 3s pacing:
12/12 through. **That ~40 is unmarked and cannot be quoted as a forge
number**: the run did not record which endpoint class it hit, and org-ops
measured ~110 sequential `/raw/branch/main/` reads at 0.4s with zero 429 the
same day. Either they are two different limiters — the likelier reading —
or the ~40 is wrong, and nothing in the note distinguishes those. The one
API-axis figure this repo can stand behind: 22 sequential `/api/v1/` calls
at 2s pacing, anonymous, zero 429 (the `RELEASE-CURRENT` baseline run). That
is below the suspected knee, so it bounds nothing — it is a floor, not a
ceiling. Every future measurement records its endpoint class.
- **Two acquisition channels, and refs are not one of the API calls.** Four
queued checks need state the local clone does not have, and answering the
mechanism question once is the point — asked per check, it gets four
inconsistent answers and the budget grows by accretion. Anything answerable
from *refs* goes over the git protocol: `git ls-remote --tags` is anonymous,
reports annotated-vs-lightweight for free (the peeled `^{}` ref), and does
**not** share the API's bucket — 20 rapid calls all returned, and an API call
immediately after still got 200. Measured sequentially and only to 20, so it
is "does not consume the API budget", not "unlimited". Releases have no git
equivalent, so `RELEASE-CURRENT` is the third API call and the only new one.
**Derive the URL from the register (`forge` + `org` + name), never from
`origin`.** `app-creator`'s origin is `ssh://git@…`, which needs the
operator's key — it would work on this machine and fail for every other
reader, which is the anonymity invariant failing silently rather than loudly.
Signature status splits the same way: `git cat-file tag` reads the signature
block locally, while the forge's "Verified" badge needs a key registered
there — so `TAG-SIGNED` answers *is it signed*, never *does the forge vouch
for it*.
- **A stale release page is a WARN, and "no releases" is not a finding at all.**
`RELEASE-CURRENT` compares the newest release the forge publishes against the
newest tag the forge lists — both sides from the FORGE, never the clone.
Comparing a local tag against a published release would report
portfolio-optimiser as stale when the real defect is a tag that was never
pushed (v1.0.0 local, v0.1.0 published) — REMOTE-SYNC's subject, not this
one. Measured across all 22 registered repos (2026-08-12): 4 have no tags,
2 tag without ever publishing a release, 11 are current, 5 lag. Those 2 —
`ktg-plugin-marketplace` and `llm-security-commons` — are why zero releases
is an `OK` (`RELEASE-TAGS-ONLY`) and not a finding: nothing in a repo says
which of the two legitimate conventions it follows, which is the measurement
that rejected `VERSION-DRIFT` one check over. Lagging is a `WARN` because
the remedy is safe — publishing a release for a tag that already exists
moves no published ref, unlike the remedy `TAG-ANNOTATED` has to withhold.
It is also `WARN` because this repo is the worst offender in the org
(v0.9.0 tagged, v0.3.0 published), and an `ERROR` would have let the gate
decide an operator question — whether this org publishes releases at all —
by exiting 1 on its own author.
- **An unpushed tag is a version that exists for nobody, and it is
`VERSION-TAG`'s blind spot, not its duplicate.** `VERSION-TAG` reads LOCAL
tags, so a manifest claiming `1.0.0` against an unpushed `v1.0.0` reads as a
clean pass — `portfolio-optimiser` read `OK` until `REMOTE-SYNC` existed.
One subject in the corpus is what got `BRANCH-STALE` rejected; the difference
is that an unpushed tag is never one of two legitimate conventions the way
tag-only releasing is, the remedy (`git push origin <tag>`) moves no published
ref, and the finding recurs at every release rather than once. The reverse
direction — a clone behind the forge — is deliberately not a finding: that is
the reader's machine state, not the repository's, and failing correct repos on
it is how gates get switched off.
- **Version order, in the measurement as well as in the code.** The shell that
measured this check's baseline sorted tags with `sort -t. -k1,1V` and put
`v0.9.0` above `v0.10.0` — the exact defect `compareTags` already exists to
fix, reproduced in the tool that was supposed to validate it. It named
`repo-mailbox`'s newest tag as v0.9.0 when it is v0.21.0. A baseline derived
with different ordering than the rule cannot validate the rule. Derive
"newest" with the engine's own comparator, or the measurement is fiction.
- **Codepoints, not bytes, not UTF-16 units.** Use `[...s].length`. An em-dash - **Codepoints, not bytes, not UTF-16 units.** Use `[...s].length`. An em-dash
exposes only the byte layer; astral characters expose the rest. exposes only the byte layer; astral characters expose the rest.
- **The reader decides a link's level, not just what is required.** Root - **The reader decides a link's level, not just what is required.** Root
@ -225,21 +111,6 @@ would recreate, in data, exactly the drift this plugin exists to remove.
stands. What the gate must never do is make "we decided this" and "nobody stands. What the gate must never do is make "we decided this" and "nobody
looked" the same output. The wanted side effect is exposure, not silence — looked" the same output. The wanted side effect is exposure, not silence —
`ai-psychosis` is deliberately unregistered so it stands alone. `ai-psychosis` is deliberately unregistered so it stands alone.
- **A fixed-language contract has a blind spot the `titles` pattern already
knows how to close.** `## Non-goals` is one literal English string, matched
case-insensitively but not translated, so it cannot be satisfied by a repo
whose readers were declared `nb` in `locales` — the only way to go green was
planting an English heading inside an otherwise-Norwegian document. Measured
on `ki-produktivitetsmodell` (order, 2026-08-18, census 09 on that repo):
`nb` locale, `## Virkeområde og forbehold` already doing the job Non-goals
exists for (a stranger sees what the repo does not try to be), `HEADING-
MISSING` `ERROR` regardless. `heading_aliases` in the register is the same
shape `titles` already is for the H1 — the decision is taken in the repo, the
bookkeeping happens here — and satisfying a requirement through it is its own
`OK` (`HEADING-ALIAS`), never folded silently into the aggregate. Keyed PER
REPO, not per locale: a locale-wide translation table would force every
nb-repo into the same Norwegian phrasing for the same section, exactly the
constraint `titles` already rejects for H1s one requirement over.
- **An exemption is a finding, not a deletion.** `readme_desc_match: false` - **An exemption is a finding, not a deletion.** `readme_desc_match: false`
turns off README-DESC equality for a class, and the check still emits an `OK` turns off README-DESC equality for a class, and the check still emits an `OK`
naming why. An exception nobody can see reads exactly like a check that naming why. An exception nobody can see reads exactly like a check that
@ -257,109 +128,13 @@ would recreate, in data, exactly the drift this plugin exists to remove.
`0.5.0`. `engineCommit` closes that, derived from the same checkout with no `0.5.0`. `engineCommit` closes that, derived from the same checkout with no
network call. It is present-and-`null` when underivable, never absent — an network call. It is present-and-`null` when underivable, never absent — an
absent key means an older engine, `null` means this one ran without a HEAD. absent key means an older engine, `null` means this one ran without a HEAD.
- **A finding must name a remedy the operator can safely perform.** A
lightweight tag is movable without a trace, and the catalog pins plugins by
tag — so it is a supply-chain property, not tidiness. But the levels come
from a measurement: 155 tags across 19 clones, 14 lightweight, and only ONE
repo whose *newest* tag is lightweight. The newest can be re-cut at no cost
(`ERROR`); the older ones can only be "fixed" by force-moving an already
published ref — the very act the check warns about — so they are exposed
once, as a count (`WARN`), never as fourteen findings. Left unrecorded, that
`WARN` can never be cleared, which is the `titles` problem again; no
acceptance record is built until a repo actually needs one.
- **`VERSION-DRIFT` was approved, measured, and not built.** The proposed rule
— the manifest carries a released version string while HEAD sits N commits
past that tag — fires on **15 of the 18 tagged repos**, twelve of which are
simply between releases (ms-ai-architect 131 commits, the catalog 262). A
gate that fails a correct repository is the mechanism that gets gates
switched off, and this one fails almost all of them. The two repos that
motivated it are both already answered: guard's manifest claims `0.7.0` with
no such tag, which is an existing `VERSION-TAG` `ERROR`; okf's case
turns on *behaviour-changing* commits past the tag, which no classifier reads
off git. Two legitimate conventions coexist here — bump-at-release and
bump-first — and nothing in a clone says which one a repo follows.
This says nothing about org-ops' `PIN-DEAD`, which is a different check on a
different subject: `VERSION-TAG` reads the MANIFEST and asks whether that
version was ever tagged, `PIN-DEAD` reads a README INSTALL COMMAND and asks
whether the ref it pins resolves. They coincide on guard only because the
same wrong number was written in both places; a README pinning a bad ref in a
repo with a correct manifest is invisible to `VERSION-TAG`. Recorded
rather than deferred: a decision that is wrong is worse than no record, and
the next session should not re-derive this measurement.
- **`RELEASE-ASSETS` is rejected permanently, and the reason is NO SUBJECT — not
the fire rate.** All 18 releases in the org carry no assets, but that number is
the weaker half of the measurement. The other half: **0 of 21 READMEs mention
an asset download at all** — no `releases/download` link, no tarball, nothing.
Every install path in this org goes through `marketplace add`, `plugin
install`, `pip`/`uv`, or a clone. So there is no reader who is harmed and no
remedy anyone would perform. That is a stronger rejection than VERSION-DRIFT's,
which was "fires on repos following the other legitimate convention"; this one
has no convention on either side because it has no subject. Do not re-derive
it from "18/18" alone — the fire rate is a proxy, and reasoning from the proxy
is what would resurrect this check the first time one repo publishes an asset.
- **The question is "would a public AAA+ repo do this", NOT "how many of ours
already do".** Operator rule, 2026-08-13, and it settled `TAG-SIGNED` — which
had been parked as "blocked on an operator decision" on the strength of 0 of
18 repos signing. That zero was never evidence against the check: it is what a
practice nobody has adopted yet looks like, and reasoning from it makes the
gate ratify the status quo it exists to move. The operator's word for being
asked at all was that it "should almost be self-evident". This is genuinely
different from the VERSION-DRIFT rejection, where two legitimate conventions
coexisted; there is no second convention under which release tags are better
left unattributable. **The test decides WHETHER; the corpus measurement then
decides only HOW TO SCOPE** so the rule does not fail correct repositories on
day one. Do not let a low adoption rate veto a yes.
This does not reopen `RELEASE-ASSETS`, which was rejected for the stronger
reason — no subject at all, 0 of 21 READMEs referencing an asset download.
- **`TAG-SIGNED` ships as a policy DATE, and the date is what made it
shippable.** Measured across 20 clones before the rule was locked (2026-08-13):
158 `v*` tags, 144 annotated, 14 lightweight, **0 signed**, and not one tag
dated after 2026-08-12. A boolean "this org signs" would have failed 20
correct repositories the day it landed, with force-moving 144 published refs
as its only remedy — the mechanism that gets gates switched off. The date
(`tags_signed_from` in the register, never in the engine, the same rule that
keeps the org-profile exemption a flag) fires nothing today and acquires teeth
at each repo's next release, which is how signing is adopted in public.
Verified by sweep: 21 repos, 18 `TAG-SIGNED-PREPOLICY` + 3 `TAG-SIGNED-NONE`,
0 ERROR, 0 WARN — then positively controlled against real git objects, where
an unsigned post-policy newest tag does fire `ERROR`. A sweep returning zero
proves nothing on its own; a dead check returns zero too.
Two exclusions keep one defect from being counted twice: a **lightweight** tag
has no tag object to carry a signature (`TAG-ANNOTATED` owns it, and the skip
names that owner), and a **pre-policy** tag was correct when it was cut — said
out loud as an `OK`, never as silence. Scope fixed in advance and held:
the signature is read from the clone, so the check answers *is it signed* and
never *does the forge vouch for it* — that badge needs a key registered there.
Read with `%(if)%(contents:signature)%(then)…` rather than by printing the
block, which is multi-line and would break the line-oriented parser on exactly
the tags that matter.
- **A gate whose subject varies per repo MEASURES the subject; it does not read
it off the class.** `VERIFY-COMMAND` fires only where something runnable
exists (`scripts.test`, or a tracked executable test file), and answers
`VERIFY-NONE` at `OK` where nothing does. The five subject-less repos —
human-friendly-style, llm-security-commons, playground-design-system,
portfolio-optimiser-commons, app-creator — span `plugin`, `shared-asset` AND
`standalone`, so every class-level phrasing of this rule fails a correct
repository somewhere. The rate is fine (6 of 21 `WARN`) for a reason that must
travel with the rule: the org publishes exactly ONE stance on verification
("no CI badge, so one command from a clean clone"), so an undocumented suite
is not a second convention the way bump-first vs bump-at-release was for
VERSION-DRIFT. And the check runs nothing, so its `OK` says *documented*,
never *passing* — a green line implying a passing suite would be a claim on
the surface that nobody verified.
- **The check table in the README is part of the surface it measures.** Four
checks shipped without a row (`PIN-DEAD`, `TAG-ANNOTATED`, `RELEASE-CURRENT`,
`REMOTE-SYNC`), and the Requirements section still said "two network calls"
after the third was added — the same stale-count defect the invariant above
warns about, one file over, on the README of the repo whose entire job is
presentation. A new check is not shipped until its row exists.
- **No hook until the rule is precise.** A blocking gate that fails a correct - **No hook until the rule is precise.** A blocking gate that fails a correct
repository is the mechanism that gets gates switched off. repository is the mechanism that gets gates switched off.
## Commands ## Commands
```bash ```bash
npm test # 245 tests npm test # 147 tests
node scripts/repo-standard-check.mjs --dir "$PWD" # gate one repo node scripts/repo-standard-check.mjs --dir "$PWD" # gate one repo
node scripts/repo-standard-check.mjs --offline # no network call node scripts/repo-standard-check.mjs --offline # no network call
node scripts/repo-standard-check.mjs --json # machine output node scripts/repo-standard-check.mjs --json # machine output

View file

@ -1,131 +0,0 @@
# Governance
How the repositories in this organisation are maintained, what you can expect from upstream, and how they're meant to be used.
## TL;DR
- Solo-maintained, AI-assisted development, MIT licensed.
- **Fork-and-own is the default model.** Upstream is a starting point, not a vendor.
- Issues welcome as signals. Pull requests are not accepted — see [Why no PRs](#pull-requests--no).
- No SLA. Best-effort bug fixes and security advisories. Breaking changes happen and are noted in each repository's CHANGELOG.
---
## Can I trust this?
Be honest with yourself about what you're adopting:
- **One maintainer.** If I get hit by a bus, the bus wins. The repos stay up under MIT, but no one owes you a fix.
- **AI-generated code with human review.** Everything here is built through dialog-driven development with Claude Code. I read, test, and judge the output before it ships, but I'm not auditing every line the way a security firm would. Treat it accordingly.
- **No commercial interests.** I'm not selling a SaaS, not steering you toward a paid tier, not collecting telemetry. Nothing here runs as a hosted service; code runs locally where you install it.
- **MIT licensed.** Fork it, modify it, ship it under your own name.
If you work somewhere that needs vendor accountability, support contracts, or signed assurances — **this isn't that.** Use it as a reference implementation, fork it into your own organization, and own the result.
---
## How this is meant to be used
### Fork-and-own
The intended workflow:
1. **Fork** the repository you need into your own organization or namespace.
2. **Tailor** it to your context — terminology, integrations, cycle lengths, regulatory framing, whatever doesn't fit out of the box.
3. **Maintain it yourself.** Treat your fork as the canonical version for your team.
4. **Watch upstream selectively.** Cherry-pick changes that help, ignore changes that don't. There's no obligation to stay in sync.
This isn't a workaround for not accepting PRs. It's the actual recommended adoption pattern, especially for plugins like `okr` and `ms-ai-architect` where every Norwegian public sector organization will need its own tildelingsbrev mappings, terminology, and integrations. A central "one true version" would be wrong for everyone.
### What to change first when you fork
Each repository differs, but the common edits are:
- **Identity** — rename, replace authorship, update README.
- **External integrations** — issue trackers, knowledge bases, dashboards, observability backends. Everything ships as a starting point, not pre-wired. Every organization must configure its own integrations.
- **Norwegian-specific framing** — relevant for `okr` and `ms-ai-architect`. Most repositories are jurisdiction-neutral. Rewrite for your jurisdiction if you're outside Norway.
- **Reference docs** — the knowledge bases reflect my reading. Replace with your organization's authoritative sources.
- **Hooks and policies** — security thresholds, blocked commands, and audit gates are tuned to my taste. Tune them to yours.
### Staying current with upstream
If you want to pull in upstream changes later:
- **Cherry-pick, don't merge.** Each repository moves independently and breaking changes land without ceremony.
- **Read the CHANGELOG first.** Every repository has one.
- **Keep your customizations in clearly-named files.** The harder upstream is to merge cleanly, the more painful staying current becomes. A `local/` directory or `*.local.md` convention helps.
---
## What upstream provides
| | What I do | What I don't |
|---|---|---|
| **Bug fixes** | Best-effort when I notice or get a clear report | No SLA, no triage commitment |
| **Security issues** | Investigate within reasonable time, document in CHANGELOG | No CVE process, no embargo coordination |
| **New features** | When they fit my own usage | Not on request |
| **Norwegian public sector context** | Kept current as long as the project lives | If I lose interest or change jobs, the framing freezes |
| **Breaking changes** | Documented in CHANGELOG | They happen — version pin if you need stability |
| **Compatibility** | Tracked against current Claude Code releases where relevant | No long-term support branches |
If any of this is a dealbreaker — fork now, version-pin, and stop reading upstream.
---
## How to contribute
### Issues — yes, please
Issues are the most valuable thing you can send me:
- **Bug reports** with reproduction steps. Even a screenshot helps.
- **Use-case feedback.** "I tried to use this in my organization and X didn't fit" is genuinely useful, even if I can't fix it for you.
- **Pointers to better sources.** If you know a DFØ veileder, an NSM guideline, or an academic paper that contradicts what's in a knowledge base, tell me.
- **Security findings.** See the repository's `SECURITY.md` for disclosure preference where one exists; otherwise email rather than open a public issue.
### Pull requests — no
This is deliberate, not laziness:
- **Solo review is a bottleneck.** Honest PR review takes me longer than rewriting from scratch. The math doesn't work.
- **Forks are where the value is.** The fork-and-own model means upstream consolidation isn't the point. Your organization's adaptations belong in your fork, not mine.
- **AI-generated code complicates provenance.** Every line here is produced through dialog with Claude Code, with me as the judge. Mixing in PRs from contributors with different processes and licensing assumptions creates a mess I'd rather not untangle.
If you've built something useful on top of a fork, **publish it under your own name and link back.** I'll happily list notable forks here once they exist.
### Notable forks
*(To be populated as forks emerge. If you've forked one of these repositories for production use, open an issue and I'll add a link.)*
---
## Relationship between repositories
These repositories are **independent**. Install one without the others, fork one without the others. They share conventions (naming, hook patterns, AI-generated disclosure) but no runtime dependencies unless a repository's manifest declares one explicitly.
The plugin marketplace among them is a **catalog**, not a suite. Don't fork the whole thing unless you actually want to maintain everything.
---
## Versioning and stability
- **Semantic versioning per repository.** Each repository has its own `CHANGELOG.md` and version number.
- **Breaking changes happen.** I bump the major version when they do, but I don't run an LTS branch.
- **Pin your version.** If stability matters more than features, install a specific version and stay there until you choose to upgrade.
---
## Public sector adoption notes
For Norwegian etater specifically:
- **DPIA-relevant data flows are documented in the relevant README where applicable.** Read them before installation.
- **No data leaves your machine** beyond what Claude Code itself sends to Anthropic. Nothing here calls external services unless you configure an integration.
- **Drøftingsplikt and ledelsesansvar** are not replaced by these tools. The `okr` plugin coaches; it does not decide. The `ms-ai-architect` plugin advises; it does not approve.
- **Choose your Claude deployment carefully.** claude.ai vs. API direct vs. Bedrock in EU region have different data residency profiles. These tools don't choose for you.
---
## License
MIT is the default across the organisation. See each repository's `LICENSE` file for the authoritative terms.

135
README.md
View file

@ -6,11 +6,11 @@ almost never the code — it is the first screen, an install path that stops
halfway, and references to a name that was retired two renames ago. This plugin halfway, and references to a name that was retired two renames ago. This plugin
checks that surface in one repository and reports what it finds. checks that surface in one repository and reports what it finds.
> **Solo-maintained, fork-and-own.** This plugin is a starting point, not a vendor product. Issues are welcome as signals; pull requests are not accepted. See [GOVERNANCE.md](GOVERNANCE.md) for the full model. > **Solo-maintained, fork-and-own.** This plugin is a starting point, not a vendor product. Issues are welcome as signals; pull requests are not accepted. See the [marketplace governance](https://git.fromaitochitta.com/open/ktg-plugin-marketplace/src/branch/main/GOVERNANCE.md) for the full model.
*AI-generated: all code produced by Claude Code through dialog-driven development.* *AI-generated: all code produced by Claude Code through dialog-driven development.*
![Version](https://img.shields.io/badge/version-0.11.2-blue) ![Version](https://img.shields.io/badge/version-0.7.1-blue)
![Platform](https://img.shields.io/badge/platform-Claude_Code_Plugin-purple) ![Platform](https://img.shields.io/badge/platform-Claude_Code_Plugin-purple)
![Skills](https://img.shields.io/badge/skills-1-orange) ![Skills](https://img.shields.io/badge/skills-1-orange)
![License](https://img.shields.io/badge/license-MIT-lightgrey) ![License](https://img.shields.io/badge/license-MIT-lightgrey)
@ -21,15 +21,6 @@ has no Actions runner — so the test claim is one you run yourself, in one
command, from a clean clone: `npm test`. A badge asserting it would be a claim command, from a clean clone: `npm test`. A badge asserting it would be a claim
dressed as evidence. dressed as evidence.
## Table of Contents
- [Install](#install)
- [Requirements](#requirements)
- [What it does](#what-it-does)
- [Non-goals](#non-goals)
- [Tests](#tests)
- [Changelog](#changelog)
## Install ## Install
Use the `https://` form. The forge UI's clone button hands out an `ssh://` URL, Use the `https://` form. The forge UI's clone button hands out an `ssh://` URL,
@ -50,17 +41,10 @@ a replacement for the two commands above:
## Requirements ## Requirements
Node 18 or newer. No dependencies. Three API calls: the org listing (for a Node 18 or newer. No dependencies. Two network calls: the org listing (for a
repo's published description), the catalog manifest (to confirm the install repo's published description) and the catalog manifest (to confirm the install
command resolves), and the repo's releases listing (to see the version page a command resolves). Both read anonymously, so no token is needed, and `--offline`
stranger reads). Refs come over the git protocol instead — `git ls-remote skips both — the checks that depended on them then report `SKIP`, not `OK`.
--tags`, which does not spend the API budget — and a release is not a ref, which
is why it is the one subject that needs a call of its own.
All of it reads anonymously, so no token is needed, and `--offline` skips every
one — the checks that depended on them then report `SKIP`, not `OK`. Those land
as `notRun` and clear on a networked re-run, so an offline pass is never a fully
judged one.
## What it does ## What it does
@ -77,8 +61,7 @@ The repository's **class** decides what each check means:
| First screen | no H1 on line 1, or the line under it is not the published description. An H1 that merely differs from the repo name is a `WARN` — that is a naming choice, not a defect | | First screen | no H1 on line 1, or the line under it is not the published description. An H1 that merely differs from the repo name is a `WARN` — that is a naming choice, not a defect |
| Install block | the form for this class is missing, incomplete, shown over `ssh://`, or points at the wrong marketplace | | Install block | the form for this class is missing, incomplete, shown over `ssh://`, or points at the wrong marketplace |
| Install truth | the plugin is not pinned in the catalog, so the documented command cannot succeed for anyone | | Install truth | the plugin is not pinned in the catalog, so the documented command cannot succeed for anyone |
| Install pins | a `@v…` or `--branch v…` in an install command names a tag the forge does not have, so the command a stranger copies 404s | | Required headings | `## Install`, `## Non-goals`, `## Changelog` — per class. Present at the wrong depth is its own finding |
| Required headings | `## Install`, `## Non-goals`, `## Changelog` — per class. Present at the wrong depth is its own finding. A registered per-repo heading alias (`register/repos.json`'s `heading_aliases`) satisfies it too, for a repo whose readers were declared to speak another language |
| Required files | a file this class (or trait) needs is absent | | Required files | a file this class (or trait) needs is absent |
| Repo references | an `open/<name>` in URL position resolves to nothing | | Repo references | an `open/<name>` in URL position resolves to nothing |
| Relative links | a link points at a file that is not tracked | | Relative links | a link points at a file that is not tracked |
@ -88,11 +71,6 @@ The repository's **class** decides what each check means:
| README language | the prose is not in the language this repo's readers were declared to speak | | README language | the prose is not in the language this repo's readers were declared to speak |
| Boilerplate | template text nobody filled in | | Boilerplate | template text nobody filled in |
| Version consistency | manifest, README badge, newest CHANGELOG entry and the git tag disagree | | Version consistency | manifest, README badge, newest CHANGELOG entry and the git tag disagree |
| Tag integrity | the newest tag is lightweight — movable without a trace, under a catalog that pins by tag. Older ones are exposed once as a count, because their only "fix" is force-moving a published ref |
| Tag signing | a tag cut on or after the org's signing date carries no signature, so nothing ties the released ref to a signer rather than to whoever could write to the forge. Tags predating the date are never judged — signing them now would mean force-moving published refs. Answers *is it signed*, never *does the forge vouch for it* |
| Release current | the newest release the forge publishes is older than the newest tag it lists, so the version page shows a version older than the code. `WARN` — publishing a release for an existing tag moves nothing |
| Remote sync | a tag exists only in this clone. The manifest can read as correctly tagged while no stranger can resolve the version |
| Verify command | the repo has something runnable and the README shows no command to run it. With no CI badge in this org, that command is the only evidence a stranger gets |
| Description | empty, or past the length bound | | Description | empty, or past the length bound |
Findings carry two independent things: a **level** (`ERROR`, `WARN`, `SKIP`, Findings carry two independent things: a **level** (`ERROR`, `WARN`, `SKIP`,
@ -102,51 +80,26 @@ Findings carry two independent things: a **level** (`ERROR`, `WARN`, `SKIP`,
- **missing** — an expected artefact is absent - **missing** — an expected artefact is absent
- **weakening** — present and working, but it reads as amateur - **weakening** — present and working, but it reads as amateur
The process exits 1 on any `ERROR`. A `SKIP` is never a pass and never a The process exits 1 on any `ERROR`. A `SKIP` means the check could not run — an
severity — it is the absence of a verdict, printed apart from the judged unreachable forge, an untagged repo, a link that leaves the repository. It is
findings. not a pass, and the output prints those separately under a heading that says so.
A repository's own `status` is the worst of the findings that were *judged*, and A repository's own `status` is the worst of the findings that were *judged*, and
`SKIP` only when nothing was. A skip cannot outrank the checks that did run. `SKIP` only when nothing was. A skip is not a severity — it is the absence of a
verdict, so it cannot outrank twelve checks that did run. Coverage rides beside
Coverage rides beside it on its own axis, and since 0.8.0 that axis has two it on its own axis: `notChecked` in `--json`, and a qualifier on the summary
values, because a skip is one of two unrelated facts: line, so a pass that had checks it could not run never prints unqualified.
- **not judged, by design** — the check saw the thing and declined. A link that
leaves the repository; a dead link inside a fixture path. It can never become
a verdict, and nobody has an action.
- **could not run** — a re-run or an operator action turns it into a verdict.
An unreachable catalog, an unregistered repo, a repo with no tags.
Merging them made clean repositories read as unread. Measured across 19 clones,
35 of 39 skips were the first kind, so `portfolio-optimiser — OK · 11 not
checked` announced eleven unread checks when all eleven were links the gate
declines to judge on purpose.
A third shape is not a skip at all and must not be filed as one: the check ran,
saw everything, and found **no subject** to judge. A repository that claims no
version anywhere is `VERSION-NONE` at `OK` since 0.9.0 — the same answer
`LANGUAGE` gives a README with no running prose. It had been `notRun`, which
told three correctly-built repositories that something about them was still
unread. What remains under `notRun` is genuinely actionable: an unreachable
catalog, an unregistered repo, a repo that has not been tagged yet.
The summary line names only what someone has an action on:
``` ```
✓ portfolio-optimiser [standalone] — OK (repo-standard v0.10.0 @<sha>) ✓ okr [plugin] — OK · 1 not checked (repo-standard v0.7.0 @10ad125)
✗ some-plugin [plugin] — ERROR · 2 not run (repo-standard v0.10.0 @<sha>) · stranger — SKIP · 1 not checked (repo-standard v0.7.0 @10ad125)
· stranger — SKIP · 1 not run (repo-standard v0.10.0 @<sha>)
``` ```
The deliberate ones are not silenced: they keep their own `NOT JUDGED` heading The count is **findings**, not checks — one per un-judged item, the same list the
in the detail output, beside `NOT CHECKED`. A decision nobody can see reads detail output prints under `NOT CHECKED`. A repo with many links leaving it
exactly like a check that silently stopped running. scores a high `notChecked` without a single check having failed to run, which is
why the next thing this number needs is to distinguish *deliberately not judged*
`--json` carries `skips: { byDesign, notRun }` beside `status`. `notChecked` is from *could not run*.
unchanged, and still the numeric total of both — the counts are **findings**,
not checks, one per un-judged item. Absent `skips` means an engine older than
0.8.0 and absent `notChecked` one older than 0.7.0; neither absence is zero.
### Traits — a second axis ### Traits — a second axis
@ -168,8 +121,7 @@ Detection is a stopword-frequency comparison over prose with code stripped, so a
Norwegian flag name in a shell example cannot decide what the document is. It Norwegian flag name in a shell example cannot decide what the document is. It
answers which language dominates and nothing else: a README can pass this and answers which language dominates and nothing else: a README can pass this and
still be badly written. Where the prose is too evenly bilingual to call, the still be badly written. Where the prose is too evenly bilingual to call, the
finding is a `SKIP`, and a `notRun` one — the question is live and unanswered, finding is a `SKIP` — the question is live and unanswered. Where there is no
and fixing the prose or the register's `locales` entry answers it. Where there is no
running prose at all, it is an `OK`: nothing claims a language, and a thin README running prose at all, it is an `OK`: nothing claims a language, and a thin README
is the first-screen check's business, not this one's. is the first-screen check's business, not this one's.
@ -189,26 +141,6 @@ An H1 that matches neither the repo name nor a registered title is still a
warning, and the message names both — that is drift in one of the two, not a warning, and the message names both — that is drift in one of the two, not a
title. title.
### Registered heading aliases — the same asymmetry, one requirement over
`## Non-goals` (and every other required heading) is one fixed English string,
matched case-insensitively but never translated. A repository whose readers
were declared `nb` in the register's `locales` cannot satisfy it honestly —
the only way to go green was planting an English heading inside an otherwise
Norwegian document, which is worse than the warning it silences.
A `heading_aliases` entry in the register is the fix, shaped exactly like
`titles`: the decision — this section, in this repo's own language, does the
job the English heading names — is made in the repo, and the bookkeeping
happens here. Set one, and a README carrying the aliased heading instead of
the literal one is an `OK` (`HEADING-ALIAS`) that names the alias, never a
silent pass. Leave it out, and `HEADING-MISSING` stands exactly as before.
It is keyed **per repository**, not per locale. A locale-wide translation
table would force every `nb` repository into the same Norwegian phrasing for
the same section — precisely the constraint `titles` already rejects for H1s,
where a human title is a valid choice, not a slot filled from a fixed list.
### When equality is the wrong demand ### When equality is the wrong demand
`README-DESC` requires the README's opening line to be the forge description `README-DESC` requires the README's opening line to be the forge description
@ -226,25 +158,6 @@ A class turns the demand off with `readme_desc_match: false` in the register.
The exemption is still reported as a finding that names the reason: an exception The exemption is still reported as a finding that names the reason: an exception
nobody can see is indistinguishable from a check that silently stopped running. nobody can see is indistinguishable from a check that silently stopped running.
### One command, because there is no CI
The forge has no Actions runners registered. A green badge here would assert a
run that never happens, which is the dishonest-badge finding one row up. The
substitute this project publishes is one command a stranger can run from a clean
clone, said plainly — so a repository with something runnable and no such command
in its README is a `WARN`.
Two things bound it. It fires only where a subject was **measured**: a
`scripts.test` entry, or a tracked file that is unambiguously an executable
test. Five of the twenty-one clones have neither — they hold prose, output styles
and domain packs — and they answer `VERIFY-NONE` at `OK`, because a check that
ran and found nothing to judge has produced a verdict. Those five span three
different classes, which is why the subject is measured and not read off a class.
And it can only ever fell a **missing** command. Nothing here runs anything, so a
green line means documented, never passing. Measured across the org: six
repositories have a suite their README never mentions.
### What is deliberately not required ### What is deliberately not required
`CONTRIBUTING.md`, `CODE_OF_CONDUCT.md` and `MAINTAINERS.md` are required by no `CONTRIBUTING.md`, `CODE_OF_CONDUCT.md` and `MAINTAINERS.md` are required by no
@ -326,9 +239,7 @@ distinct from "an older engine that never tried".
npm test npm test
``` ```
The suite pins the pure classifiers; `npm test` prints the current count, which 147 tests over the pure classifiers. The reference fixtures are measured false
is the only copy of that number this README keeps — a prose count here went
stale twice. The reference fixtures are measured false
positives, each with its expected verdict — the six that produced the positives, each with its expected verdict — the six that produced the
three-outcome reference rule, plus the noise sources found by running the gate three-outcome reference rule, plus the noise sources found by running the gate
against a real repository: regexes inside code spans that are markdown links to against a real repository: regexes inside code spans that are markdown links to

View file

@ -1,34 +0,0 @@
# Security policy
## Reporting a vulnerability
Report privately to <security@fromaitochitta.com> — do not open a
public issue.
Canonical repository: https://git.fromaitochitta.com/open/repo-standard
Please include the affected version or commit, a minimal reproduction,
and the impact you see. We acknowledge every report within 5 working
days, agree a fix and disclosure timeline with the reporter, and aim to
disclose within 90 days of the initial report.
## Response process
1. Acknowledge within 5 working days.
2. Triage and confirm severity within 10 working days.
3. Develop and test a fix.
4. Publish an advisory and credit the reporter unless they prefer
to remain anonymous.
## Supported versions
| Version | Supported |
| ------- | --------- |
| 0.11.x | :white_check_mark: |
| < 0.11 | :x: |
This project has not reached 1.0 yet; only the latest 0.x release line
receives security fixes. See `CHANGELOG.md` for release history.
## Advisories
No advisories have been published yet.

View file

@ -1,6 +1,6 @@
{ {
"name": "repo-standard", "name": "repo-standard",
"version": "0.11.2", "version": "0.7.1",
"private": true, "private": true,
"type": "module", "type": "module",
"engines": { "engines": {

View file

@ -32,13 +32,11 @@
"playground-design-system": "shared-asset", "playground-design-system": "shared-asset",
"portfolio-optimiser-commons": "shared-asset", "portfolio-optimiser-commons": "shared-asset",
"llm-security-commons": "shared-asset", "llm-security-commons": "shared-asset",
"ki-produktivitetsmodell": "shared-asset",
".profile": "org-profile", ".profile": "org-profile",
"portfolio-optimiser": "standalone", "portfolio-optimiser": "standalone",
"portfolio-optimiser-claude": "standalone", "portfolio-optimiser-claude": "standalone",
"llm-ingestion-pipeline-security": "standalone", "llm-ingestion-pipeline-security": "standalone",
"llm-ingestion-okf": "standalone", "llm-ingestion-okf": "standalone"
"app-creator": "standalone"
}, },
"$comment_non_repos": [ "$comment_non_repos": [
@ -135,27 +133,14 @@
"", "",
"`ai-psychosis` (`# Interaction Awareness`) is DELIBERATELY ABSENT: it is the", "`ai-psychosis` (`# Interaction Awareness`) is DELIBERATELY ABSENT: it is the",
"one where a reader cannot connect the title to the repo. Leaving it standing", "one where a reader cannot connect the title to the repo. Leaving it standing",
"alone is the wanted effect of registering the others.", "alone is the wanted effect of registering the others."
"",
"`.profile` UPDATED 2026-08-18 (org-ops coord, census 09): the org landing",
"page was rebuilt the same day (commit 9898a6e, 'rebuild org landing page,",
"add LICENSE and SECURITY.md', per that repo's own STATE.md — order from",
"`.claude`, AAA+ round 5). The H1 became `# From AI to Chitta — open`; the",
"old value here was the H1 it replaced, not drift. Verified against the",
"clone, not the coord message — the em-dash is U+2014, not a hyphen.",
"",
"`ki-produktivitetsmodell` ADDED 2026-08-18 (order from that repo, on its own",
"operator decision recorded in its STATE.md the same day): `# Tre nivå av",
"organisatorisk produktivitet med KI` is the document's real title, the repo",
"name is a slug. Verified against the clone before writing it down."
], ],
"titles": { "titles": {
".profile": "From AI to Chitta — open", ".profile": "fromaitochitta / open",
"llm-ingestion-pipeline-security": "llm-ingestion-guard", "llm-ingestion-pipeline-security": "llm-ingestion-guard",
"linkedin-studio": "LinkedIn Studio Plugin for Claude Code", "linkedin-studio": "LinkedIn Studio Plugin for Claude Code",
"llm-security": "LLM Security Plugin for Claude Code", "llm-security": "LLM Security Plugin for Claude Code",
"ms-ai-architect": "AI Architect Plugin for Claude Code", "ms-ai-architect": "AI Architect Plugin for Claude Code"
"ki-produktivitetsmodell": "Tre nivå av organisatorisk produktivitet med KI"
}, },
"$comment_traits": [ "$comment_traits": [
@ -189,132 +174,17 @@
"Norwegian readership is `nb`, and is then WRONG in English, not right.", "Norwegian readership is `nb`, and is then WRONG in English, not right.",
"", "",
"PROPOSED BY THE OPERATOR, NOT MEASURED — like `traits`, this list is a", "PROPOSED BY THE OPERATOR, NOT MEASURED — like `traits`, this list is a",
"judgement and no remote can report it. Both original entries were named by", "judgement and no remote can report it. Both entries below were named by",
"the operator on 2026-08-04 as Norway-only in their audience.", "the operator on 2026-08-04 as Norway-only in their audience.",
"", "",
"`ms-ai-architect` was REMOVED 2026-08-14 (org-ops coord, their D35): its",
"published README disavows the exclusivity this axis requires — line 17,",
"'while remaining useful for any enterprise context' — verified against the",
"forge before removal. Its 24 Norway markers are TRAITS (what the code",
"does: Digdir, NSM, DPIA), which is the axis confusion this comment warns",
"about two paragraphs up. The register was the wrong side, not the prose.",
"`okr` stands: its README IS Norwegian, rewritten to close census 05.",
"",
"`ki-produktivitetsmodell` ADDED 2026-08-18 (operator decision, on the",
"session's recommendation after registering the repo as `shared-asset`):",
"its README is written entirely in Norwegian, explicitly for 'toppledere i",
"norsk offentlig sektor' — the same Norway-only-audience test that set",
"`okr`, not a code trait.",
"",
"Detection is a stopword-frequency comparison over prose with code stripped.", "Detection is a stopword-frequency comparison over prose with code stripped.",
"It answers WHICH language dominates, never whether the prose is any good." "It answers WHICH language dominates, never whether the prose is any good."
], ],
"locales": { "locales": {
"okr": "nb", "ms-ai-architect": "nb",
"ki-produktivitetsmodell": "nb" "okr": "nb"
}, },
"$comment_heading_aliases": [
"The Non-goals contract is one fixed English string, matched literally — it",
"cannot be satisfied by a repo whose readers were declared `nb` in `locales`",
"above, and the only way to go green was to plant an English heading inside",
"an otherwise-Norwegian document. Same job `titles` does for a README H1, one",
"requirement over: the decision is taken in the repo, the bookkeeping happens",
"here — a registered alias satisfies the requirement and is its own OK",
"(`HEADING-ALIAS`), never a silent pass.",
"",
"Keyed PER REPO, not per locale: a locale-wide translation table would force",
"every nb-repo into the same Norwegian phrasing for the same section, which",
"is exactly the constraint `titles` already rejects for H1s (a human title is",
"a valid choice, not a slot to fill from a fixed list).",
"",
"ADDED 2026-08-18 (order from ki-produktivitetsmodell, census 09 on that",
"repo): its README is `nb` (see `locales` above) and carries",
"`## Virkeområde og forbehold`, which does the job the Non-goals contract",
"exists for — a stranger sees what the repo does not try to be. The operator",
"decided the README stays as written; this entry is the mechanism, not an",
"exception for one repo. Verified against the clone before writing it down."
],
"heading_aliases": {
"ki-produktivitetsmodell": {
"## Non-goals": "## Virkeområde og forbehold"
}
},
"$comment_tags_lightweight_accepted": [
"Where a decided YES about lightweight tag HISTORY gets written down — the",
"same job `titles` does for a README H1, one axis over. A lightweight tag is",
"movable without a trace and the catalog pins plugins by tag, so it is a",
"supply-chain property; but the only remedy for a PUBLISHED one is force-",
"moving it, which is the act the check warns about. Without this list the",
"WARN can never be cleared, and the gate says the same thing forever.",
"",
"Keyed on tag NAME, never on a count. A count stays satisfied the moment one",
"tag is re-cut and a different, unaccepted one takes its place.",
"",
"MEASURED 2026-08-12 from the LOCAL clones, not from the forge — which is",
"the very gap REMOTE-SYNC exists to name, so these are claims about three",
"OTHER repos and were sent to them by coord so a wrong name can be disputed.",
"",
"`ktg-plugin-marketplace v7.7.2` was DELIBERATELY ABSENT until 2026-08-14:",
"as the newest tag it had the one safe remedy (`git tag -a -f`), and",
"pre-accepting it would have let cutting v7.8.0 skip the cheap fix",
"unrecorded. ACCEPTED 2026-08-14 on the catalog's measured counter (their",
"coord, catalog HEAD d0d5d70): v7.7.2 is a monorepo-era llm-security tag",
"(2026-05-19, pre-split), not a catalog release; their README install block",
"pins no ref and all 12 marketplace.json refs are plugin-repo tags, so zero",
"install paths consume it — 'newest tag' here sorts last without being the",
"newest RELEASE, which is the premise the safe-remedy rule rests on.",
"Force-pushing a published ref for zero consumers costs more than the",
"finding. The catalog committed to annotating every future tag; the check",
"itself enforces that, since a new lightweight newest tag is not on this",
"list and fires ERROR.",
"",
"This entry was DEAD WEIGHT from 2026-08-14 to 2026-08-18: `accepted` was",
"only ever consulted for tags OLDER than newest, so v7.7.2 kept firing",
"TAG-ANNOTATED ERROR the whole time — the exact 'we decided this' vs.",
"'nobody looked' collapse this axis exists to prevent, one level down.",
"Caught and reported by the catalog itself (coord, 2026-08-17), fixed in",
"`checkTagIntegrity` (repo-standard, 2026-08-18): the accepted set is now",
"checked against the newest tag too, emitting a distinct",
"`TAG-ANNOTATED-ACCEPTED-NEWEST` OK rather than silently doing nothing.",
"",
"The two slash-named `config-audit/v*` tags on that forge are OUT OF SCOPE",
"by construction, not omitted by a fetch gap: the engine reads",
"`refs/tags/v*` — the repo's own version line — and a namespaced",
"monorepo-era tag is not part of it."
],
"tags_lightweight_accepted": {
"ktg-plugin-marketplace": ["v3.4.1", "v5.0.0", "v5.0.1", "v5.0.2", "v5.0.3", "v7.7.0", "v7.7.1", "v7.7.2"],
"llm-ingestion-okf": ["v0.2.0", "v0.3.0", "v0.3.1", "v0.3.2", "v0.4.0"],
"llm-ingestion-pipeline-security": ["v0.3.0"]
},
"$comment_tags_signed_from": [
"The date the org's tag-signing policy takes effect. Operator decision",
"2026-08-13, on the rule that decides these: would a public AAA+ repository",
"do this? Yes — a signed tag ties a released ref to a signer rather than to",
"whoever could write to the forge, and the catalog pins plugins by tag.",
"",
"It lives HERE and not in the engine for the reason the org-profile",
"exemption is a flag rather than `if (klass === 'org-profile')`: the engine",
"holds no org decision of its own. Remove the key and TAG-SIGNED goes back",
"to a notRun SKIP — the gate never assumes a policy nobody recorded.",
"",
"A DATE, not a boolean, and that is the whole reason the check is shippable.",
"MEASURED across 20 clones 2026-08-13: 158 `v*` tags, 144 annotated, 14",
"lightweight, 0 signed, and NOT ONE tag dated after 2026-08-12. A boolean",
"would have failed 20 correct repositories the day it landed — the mechanism",
"that gets gates switched off — and its only remedy would have been force-",
"moving 144 published refs. The date fires nothing today and acquires teeth",
"at each repo's next release, which is how signing is adopted in public.",
"",
"Do NOT backdate this to 'catch up' history. Every tag below it was correct",
"when it was cut, and the check says so out loud (TAG-SIGNED-PREPOLICY, OK)",
"rather than going silent about it."
],
"tags_signed_from": "2026-08-13",
"description_max_codepoints": 180, "description_max_codepoints": 180,
"$comment_length": [ "$comment_length": [
"180 codepoints, not bytes and not UTF-16 units. The same string measures 248", "180 codepoints, not bytes and not UTF-16 units. The same string measures 248",

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -25,39 +25,21 @@ the script cannot encode.
node "${CLAUDE_PLUGIN_ROOT}/scripts/repo-standard-check.mjs" --dir "$PWD" node "${CLAUDE_PLUGIN_ROOT}/scripts/repo-standard-check.mjs" --dir "$PWD"
Findings are `ERROR` (blocks), `WARN` (look, then decide), `SKIP` (not judged — Findings are `ERROR` (blocks), `WARN` (look, then decide), `SKIP` (the check
in one of two ways, see below), `OK`. Exit 1 on any ERROR. Add `--offline` to skip the three could not run), `OK`. Exit 1 on any ERROR. Add `--offline` to skip the two
API calls — the org listing, the catalog manifest, and the repo's releases — network calls, `--json` for machine output, `--refresh` to compare the bundled
`--json` for machine output, `--refresh` to compare the bundled register against register against the live org listing.
the live org listing. Refs are read over the git protocol instead and cost
nothing against that budget.
**Never report a `SKIP` as a pass.** A SKIP means the gate did not judge the **Never report a `SKIP` as a pass.** A SKIP means the gate could not see enough
thing. Say which, and say which KIND — since 0.8.0 there are two, and they are to judge — an unreachable forge, an untagged repo, a link leaving the
not interchangeable: repository. Say which.
- **`NOT CHECKED` (`skips.notRun`)** — the gate could not see enough. An
unreachable forge or catalog, an unregistered repo, a repo with no tags. A
re-run or an operator action turns it into a verdict, so it belongs in your
report even when the status is green.
- **`NOT JUDGED` (`skips.byDesign`)** — the gate saw it and declined on
purpose. A link leaving the repository, a dead link inside a fixture path.
Nobody has an action. Reporting these as outstanding work is how a clean repo
gets rewritten for nothing.
The inverse error is reporting an `OK` as an un-run check. A check that ran and
found **no subject** is a verdict, not a skip: `VERSION-NONE` ("no version
claimed anywhere"), `VERIFY-NONE` ("nothing runnable, so no verification command
is owed") and `LANGUAGE` ("no running prose to judge") are `OK` and
carry no action. `VERSION-NONE` was `VERSION-UNAVAILABLE`/`notRun` before 0.9.0
— if you see the old code, the engine is stale; see the version note below.
That applies hardest when the repo's own status is green. Since 0.7.0 a `SKIP` That applies hardest when the repo's own status is green. Since 0.7.0 a `SKIP`
no longer outranks an `OK` in the status line, so a repo reads `OK · 3 not run` no longer outranks an `OK` in the status line, so a repo reads
rather than `SKIP`. The summary line carries only the `notRun` count — a repo `OK · 3 not checked` rather than `SKIP`. The number is the qualifier: three
whose every skip was deliberate prints an unqualified verdict. **The line's findings nobody judged. Report the status and the number together, never the
silence is not proof there was nothing to say:** read the `NOT JUDGED` block, or status alone, and name what went unjudged — the detail output lists them under
`skips.byDesign` in `--json`, before you tell anyone a repo was fully judged. `NOT CHECKED`.
**Confirm the version before you trust a green run.** Measured 2026-08-04: **Confirm the version before you trust a green run.** Measured 2026-08-04:
`${CLAUDE_PLUGIN_ROOT}` resolved to a cached `0.1.1` in a live session while `${CLAUDE_PLUGIN_ROOT}` resolved to a cached `0.1.1` in a live session while
@ -159,53 +141,6 @@ command that runs the whole suite from a clean clone, stated in the README along
with the fact that nothing runs it automatically. Verifiability is the goal; CI with the fact that nothing runs it automatically. Verifiability is the goal; CI
is one mechanism for it, and it is not available here. is one mechanism for it, and it is not available here.
Since 0.10.0 that substitute is measured rather than assumed. `VERIFY-MISSING`
(`WARN`) means the repository has something runnable — a `scripts.test` entry, or
tracked executable test files — and its README shows no command in a code block.
Fixing it has exactly one honest move: **document the command that already
exists, after running it yourself.** Writing `npm test` into a README because
the finding mentioned it, without a `scripts.test` to back it, converts a `WARN`
about missing documentation into a false claim on the first screen — a worse
defect than the one you were sent to fix, and one this gate cannot catch,
because it never runs anything. A green `VERIFY-COMMAND` means *documented*,
never *passing*.
`VERIFY-NONE` needs no action at all. A repository of prose, output styles or
domain packs owes no command, and adding a hollow test script to clear a finding
that was never against it is the anti-pattern in its purest form.
## Tag signing — forward only, and never by rewriting history
Since 0.11.0 the gate judges whether a release tag is signed. The org's policy
date lives in the register (`tags_signed_from`); tags cut before it are reported
as `TAG-SIGNED-PREPOLICY` (`OK`) and are **not** findings. If the key is absent
the check is a `notRun` SKIP — the gate never assumes a policy nobody recorded.
`TAG-SIGNED` (`ERROR`) is the newest tag cut under the policy, unsigned. The
remedy is to re-cut that one tag signed. `TAG-SIGNED-HISTORY` (`WARN`) is a
count of older ones, and it is a `WARN` precisely because its only remedy is
force-moving an already published ref — the risk the check exists to name. Do
not clear a WARN by doing the dangerous thing.
Two moves look like fixes and are not:
- **Backdating `tags_signed_from` to "catch up".** Every tag below the date was
correct when it was cut. Backdating manufactures findings whose only remedy is
re-signing published history.
- **Re-signing the whole tag history.** Same act, one step further. The org
adopted signing forward for the same reason public projects do.
`TAG-SIGNED-LIGHTWEIGHT` is a `byDesign` skip, not work. A lightweight tag has
no tag object to hold a signature at all, so signing is not a remedy it has —
`TAG-ANNOTATED` owns that finding, and fixing it there is what puts the tag in
this check's scope.
The check reads the signature from the clone, so a green line means *this tag
carries a signature*. It does **not** mean the forge verified it — the forge's
"Verified" badge needs a key registered there, which is a different subject on a
different surface. Do not upgrade the claim when reporting.
## Diagrams — capability is not justification ## Diagrams — capability is not justification
Mermaid renders in a README on this forge. That was verified on the live Mermaid renders in a README on this forge. That was verified on the live