Compare commits
34 commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f422b63bb3 | |||
| e6cd8983ca | |||
| 9f08ebc5ed | |||
| 009c911f9e | |||
| e00ed3340c | |||
| 0a4373c29b | |||
| aebd4d28a9 | |||
| a563035feb | |||
| 067ab0528d | |||
| 425e7e9bdf | |||
| fe0d039de2 | |||
| fa0cfe07d5 | |||
| ddfc628761 | |||
| 3955b10c4d | |||
| 9073d3535d | |||
| 36583646ff | |||
| 1dfd408ffc | |||
| 9a566240e8 | |||
| f4017bcfe1 | |||
| 1a647a591f | |||
| 10ad1254ab | |||
| 5884a64e54 | |||
| a7276e6f78 | |||
| 63c75d841c | |||
| 9eb210bb01 | |||
| 43a8d28f00 | |||
| c8cbeb97a5 | |||
| c5c11f4b13 | |||
| f28615f3b6 | |||
| 5cb28503fe | |||
| aefbadf9a8 | |||
| e6b0f04021 | |||
| 568b8e374a | |||
| ca81db549c |
10 changed files with 3625 additions and 95 deletions
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "repo-standard",
|
"name": "repo-standard",
|
||||||
"version": "0.2.2",
|
"version": "0.11.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"
|
||||||
|
|
|
||||||
523
CHANGELOG.md
523
CHANGELOG.md
|
|
@ -4,6 +4,529 @@ 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.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
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- The skill told a reader what a `SKIP` finding means and stopped there, which
|
||||||
|
was complete before 0.7.0 and is not now: a repo can headline `OK · 3 not
|
||||||
|
checked`, and a session reporting only the status word would report a green
|
||||||
|
pass over three unjudged findings. `SKILL.md` now requires the status and the
|
||||||
|
count to be reported together. Shipping 0.7.0 without this made the plugin
|
||||||
|
whose purpose is catching stale published documents ship one.
|
||||||
|
|
||||||
|
- README's example output carried commits older than the behaviour they
|
||||||
|
demonstrate — one from 0.6.0 beside a `notChecked` that did not exist in it,
|
||||||
|
one two releases back. This repo's own invariant is that the version names a
|
||||||
|
file and only the sha names the code; the examples now cite `10ad125`, the
|
||||||
|
commit the feature actually shipped in.
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- README states the unit of `notChecked`: it counts un-judged **findings**, not
|
||||||
|
checks. A repo with many links leaving it scores a high count without a single
|
||||||
|
check having failed to run, so the bare number reads worse than the repo is.
|
||||||
|
Named as a known limitation rather than corrected in the string — the fix is
|
||||||
|
splitting *deliberately not judged* from *could not run*, which is its own
|
||||||
|
release.
|
||||||
|
|
||||||
|
## [0.7.0] — 2026-08-09
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- **A repo's `status` is the worst *judged* finding, and `SKIP` only when
|
||||||
|
nothing was judged.** `SKIP` used to rank between `OK` and `WARN`, so one
|
||||||
|
un-runnable check spoke for every check that ran: a repo with 0 ERROR, 0 WARN
|
||||||
|
and a dozen `OK` headlined as "skipped". Reported by org-ops against five
|
||||||
|
repos in census 05 — `ktg-plugin-marketplace`, `okr`,
|
||||||
|
`playground-design-system`, `portfolio-optimiser`, `commons` — with `okr`
|
||||||
|
holding the most `OK` in the org and reading as unread. A skip is not a
|
||||||
|
severity; it is the absence of a verdict, and it cannot be the worst of a set
|
||||||
|
that contains real ones. "`SKIP` is never a pass" survives intact in the half
|
||||||
|
of the rule that keeps it: an unregistered repo, or an empty finding set,
|
||||||
|
still says `SKIP`, because there is nothing else to be worst of.
|
||||||
|
|
||||||
|
Measured across all 21 local clones from one saved sweep, so before and after
|
||||||
|
are computed from the *same* findings rather than from two sweeps of a moving
|
||||||
|
org: 343 findings before, 343 after, none added or removed. 8 repos moved,
|
||||||
|
every one `SKIP` → `OK`. The counts that decide whether a repo needs work did
|
||||||
|
not move at all — 1 `ERROR` before and after, 3 `WARN` before and after.
|
||||||
|
|
||||||
|
Consumers reading `.status` see a changed value domain: a repo that reported
|
||||||
|
`SKIP` in censuses 03–07 may report `OK` from 08 on with an identical finding
|
||||||
|
set. Raw census files already carry `engineVersion` and `engineCommit`, which
|
||||||
|
is what distinguishes the two readings.
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- `notChecked` beside `status`, in `--json` and as a `· N not checked` qualifier
|
||||||
|
on the summary line. Fixing the status alone would have traded "clean repos
|
||||||
|
look skipped" for "skipped checks look clean", which is the worse direction —
|
||||||
|
so coverage gets its own axis rather than being folded into the verdict, the
|
||||||
|
same split the engine already makes between a finding's level and its bucket.
|
||||||
|
The count is the number of `SKIP` findings, which is what the existing
|
||||||
|
`NOT CHECKED — these are not passes` section has always listed. An **absent**
|
||||||
|
`notChecked` means an older engine and prints the pre-0.7.0 line; it is not
|
||||||
|
zero.
|
||||||
|
|
||||||
|
## [0.6.0] — 2026-08-09
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- `titles` in the register: an optional per-repo README title. Set, the H1 is
|
||||||
|
compared against it and a match is `OK`; unset, the `WARN` stands exactly as
|
||||||
|
before. A human title was already a valid choice — `docs/rds-v1.md` prescribes
|
||||||
|
`1. # Title` for every class, not `# repo-name` — but a decided YES had
|
||||||
|
nowhere to live, so the same 6 `README-H1` WARNs were reported in census 05,
|
||||||
|
06 and 07 and would have been reported forever. "We decided this is correct"
|
||||||
|
and "nobody has looked at it" now produce different outcomes. Five titles
|
||||||
|
registered, each H1 read from the repo rather than copied from the census;
|
||||||
|
`ai-psychosis` is deliberately left unregistered, so the one repo where a
|
||||||
|
reader cannot connect title to name stands alone. Measured across all 21
|
||||||
|
local clones: 6 WARN before, 1 after, no other finding moved.
|
||||||
|
|
||||||
|
- `engineCommit` alongside `engineVersion`, in the header line (short) and the
|
||||||
|
`--json` output (full). The version names a FILE; only the sha names the CODE.
|
||||||
|
A sweep stamped 18 raw files `0.4.0` while four carried findings from a check
|
||||||
|
that only exists in 0.5.0 — the feature and the version bump are two commits,
|
||||||
|
so the worktree held new code under an old number for a window, and the stamp
|
||||||
|
lied without being broken. Derived from this checkout, no network call. The
|
||||||
|
field is present-and-`null` when underivable, never absent: an absent key
|
||||||
|
means an older engine, an explicit `null` means this engine ran and had no
|
||||||
|
HEAD to read, and a consumer sorting raw files by stamp needs those to differ.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- `README-DESC` no longer demands byte equality for the `org-profile` class,
|
||||||
|
via `readme_desc_match: false` on the class — data in the register, not a
|
||||||
|
hardcoded name. For an ordinary repo the README opening and the forge
|
||||||
|
description describe the same subject and equality is right; for this one
|
||||||
|
class they do not — the README is the organisation's landing page, the forge
|
||||||
|
text describes the repo. Both are correct about their own subject, so it is
|
||||||
|
the equality that does not apply. A landing page's opening line also carries a
|
||||||
|
markdown link while the description field renders as plain text, so passing
|
||||||
|
the old check would have meant putting raw markdown on a real surface. The
|
||||||
|
class has one member, so coverage is lost nowhere else, and the exemption is
|
||||||
|
still RECORDED as an `OK` finding naming the reason rather than dropped.
|
||||||
|
Measured: `.profile` went from `ERROR` to 0 ERROR / 0 WARN, and the same
|
||||||
|
README under a `plugin` class is still an `ERROR`.
|
||||||
|
|
||||||
|
## [0.5.0] — 2026-08-09
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- `LINK-INTERNAL-FIXTURE`: a dead link inside a `test/`, `tests/`,
|
||||||
|
`fixtures/`, or `*golden*` path now reports as `SKIP` instead of `WARN`.
|
||||||
|
These paths are presumed to break their own links on purpose —
|
||||||
|
`nav-golden-escape/bundle/index.md`'s deliberate `../../../../etc/passwd`
|
||||||
|
escape pops the whole base path instead of resolving outside the repo, so
|
||||||
|
it read as a genuine dead link against three repos in the org. The finding
|
||||||
|
still fires with file and line; only the judgement changes. Measured
|
||||||
|
before shipping: 16 `LINK-INTERNAL-*` findings before, 16 after, across
|
||||||
|
all 20 local clones — every one converted 1:1, none disappeared.
|
||||||
|
|
||||||
|
## [0.4.0] — 2026-08-09
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- `LINKS-OPEN-REFS`: the `open/` reference check now reports its own result
|
||||||
|
instead of only its failures. Emitting nothing on success made "no dead
|
||||||
|
references" and "the check never ran" identical in the output — a sweep
|
||||||
|
across the org could not tell 19 clean repos from 19 unread ones (measured:
|
||||||
|
org-ops census 03b). The OK line carries the count it resolved, because an
|
||||||
|
OK that cannot say how many it checked is the same silence in a different
|
||||||
|
colour. Zero enumerated files is now `LINKS-OPEN-REFS-UNAVAILABLE` (SKIP),
|
||||||
|
which is the honest name for the case that used to look like a pass.
|
||||||
|
- `portfolio-optimiser-commons` registered as `shared-asset`, the org's 20th
|
||||||
|
repo. Class verified structurally rather than accepted: absent from the
|
||||||
|
catalog's `marketplace.json`, no `.claude-plugin/`, `README.md` + `LICENSE`
|
||||||
|
present, consumed by both reference implementations as a `git subtree` —
|
||||||
|
so `vendor` is the right install form and `## Install` would be wrong to
|
||||||
|
require. Its absence was producing three false `LINK-DEAD` ERRORs in
|
||||||
|
`portfolio-optimiser` against a repo that exists, and leaving repo #20 with
|
||||||
|
zero coverage. Verified after the fact: `portfolio-optimiser` now has no
|
||||||
|
ERROR, and the commons repo measures as WARN with 9 checks passed.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- `LINK-DEAD`/`LINK-NON-REPO` false negative on a schemaless host. `URL_REF`
|
||||||
|
required `://` or `@host:`, so `git.fromaitochitta.com/open/<name>` — the
|
||||||
|
form a subtree instruction routinely uses — resolved to nothing at all
|
||||||
|
(measured: llm-security/V3-UPGRADE.md:343). What makes a name resolvable is
|
||||||
|
its position after a host, not the scheme in front of it. Two guards keep
|
||||||
|
the widening from becoming noise: the host must end in a TLD-shaped label,
|
||||||
|
and a candidate preceded by `/` is a path segment that merely contains a
|
||||||
|
dot, not a host — so `docs/v1.2/open/` and `test/nav.golden/open/` stay
|
||||||
|
silent, and the API-endpoint rule from 0.3.0 is untouched. Measured before
|
||||||
|
shipping across 1501 tracked Markdown files in 20 local clones: 14 lines
|
||||||
|
changed verdict, 13 of them references that were previously invisible.
|
||||||
|
- The 14th was a defect the widening introduced, caught by that same
|
||||||
|
measurement and fixed here: a markdown link whose display text repeats its
|
||||||
|
own URL matched on both halves, which prints one dead reference twice and
|
||||||
|
inflates the count the new OK line offers as evidence. References are now
|
||||||
|
deduplicated per name-and-line — two different names on one line, or the
|
||||||
|
same name on two lines, still count as two.
|
||||||
|
|
||||||
|
## [0.3.0] — 2026-08-04
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- `BADGE-DEAD-LINK`: a linked badge making a run claim ("tests", "build",
|
||||||
|
"CI", "passing") is now resolved against the repo's tracked files when the
|
||||||
|
link target is relative. Being wrapped in a link previously ended all
|
||||||
|
scrutiny — the link-target capture group was missing from the regex
|
||||||
|
outright, so a badge linked to a dead relative path passed silently.
|
||||||
|
External targets (the ordinary case — a CI provider) still need the
|
||||||
|
network and stay out of scope, same precedent as `checkInternalLinks`.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- `LINK-DEAD` false positive on API endpoint paths: `URL_REF` matched
|
||||||
|
`open/<name>` anywhere in a URL, so a Forgejo API call like
|
||||||
|
`.../api/v1/orgs/open/repos` read as a dead reference to a repo named
|
||||||
|
"repos". `open` must now be the first path segment after the host,
|
||||||
|
matching how every real repo URL is shaped. Measured twice against the
|
||||||
|
catalog's own `RUNBOOK.md`.
|
||||||
|
- 429 backoff was tuned for a hard IP ban that does not exist. Measured
|
||||||
|
directly against the live forge: nginx never sends `Retry-After` (the
|
||||||
|
branch handling it is dead code in practice), the limit is a leaky bucket
|
||||||
|
that can take up to ~15s to drain, and it is IP-based rather than
|
||||||
|
token-quota-based — a valid token made no difference. `fetchWithRetry` now
|
||||||
|
defaults to a 23s worst-case budget (`retries: 5`, `maxDelayMs: 8000`), up
|
||||||
|
from 7s. `CLAUDE.md`'s "13 calls in a loop" explanation corrected to match.
|
||||||
|
- Two tests asserted facts about their own local test fixture instead of the
|
||||||
|
real `register/repos.json` — a typo in the real register would have
|
||||||
|
drifted past them silently. Both now read the live register via
|
||||||
|
`loadRegister()`.
|
||||||
|
|
||||||
## [0.2.2] — 2026-08-04
|
## [0.2.2] — 2026-08-04
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
|
||||||
296
CLAUDE.md
296
CLAUDE.md
|
|
@ -28,7 +28,47 @@ would recreate, in data, exactly the drift this plugin exists to remove.
|
||||||
not belong here. It is measured where the org is enumerated.
|
not belong here. It is measured where the org is enumerated.
|
||||||
- **It records, it does not fix.** Findings first, remediation afterwards.
|
- **It records, it does not fix.** Findings first, remediation afterwards.
|
||||||
Patching while measuring is how the inconsistency it detects was produced.
|
Patching while measuring is how the inconsistency it detects was produced.
|
||||||
- **`SKIP` is never a pass.** A check that could not run says so and names why.
|
- **`SKIP` is never a pass — and never a severity either.** A check that could
|
||||||
|
not run says so and names why. But a skip is the ABSENCE of a verdict, so it
|
||||||
|
cannot be the worst of a set that contains real ones: a repo's `status` is the
|
||||||
|
worst *judged* finding, and `SKIP` only when nothing was judged (unregistered,
|
||||||
|
or an empty finding set). It used to outrank `OK`, which made five repos with
|
||||||
|
0 ERROR, 0 WARN and a dozen OK headline as "skipped" in org-ops census 05 —
|
||||||
|
`okr`, with the most OK in the org, read as unread. Fixing it in the status
|
||||||
|
alone would have traded that for the worse direction, "skipped checks look
|
||||||
|
clean", so coverage rides its own axis: `notChecked` in `--json`, a `· N not
|
||||||
|
checked` qualifier on the summary line. Absent `notChecked` means an older
|
||||||
|
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
|
||||||
|
|
@ -48,19 +88,115 @@ 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.
|
||||||
- **Two API calls per invocation, anonymous, with 429 retry.** The org listing
|
- **Three 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 other (added after this used to say "one call" — that
|
INSTALL-TRUTH is the second (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). Both go through `fetchWithRetry`,
|
tripped the rate limiter at 26 requests); the repo's releases listing for
|
||||||
which honors `Retry-After` on HTTP 429 rather than silently reporting SKIP.
|
`RELEASE-CURRENT` is the third, and is the only subject the cheaper git
|
||||||
Both are anonymous — no token — so the gate works for any reader, not only
|
channel cannot cover, because a release is not a ref. This count is the line
|
||||||
someone holding one. A sweep across every repo still does not belong here:
|
that has now gone stale twice — it is updated in the SAME commit as the check
|
||||||
it needs the listing fetched once, not once per invocation, which is a
|
that changes it, never afterwards. All go through `fetchWithRetry`,
|
||||||
different shape of caller (org-ops), not a flag on this engine.
|
which retries HTTP 429 rather than silently reporting SKIP. All are
|
||||||
|
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
|
||||||
|
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
|
||||||
|
this engine. **The arithmetic moved with the count**: a full online sweep of
|
||||||
|
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
|
||||||
|
forge's nginx never sends `Retry-After` on its 429s (measured directly), so
|
||||||
|
`fetchWithRetry` always falls back to exponential backoff — the
|
||||||
|
`Retry-After` branch is live code with no live path yet. The limit is also
|
||||||
|
smaller than "loop of 13" implied: 20 concurrent requests from one IP
|
||||||
|
reproduced it directly, no loop needed, and a single well-formed 2-call
|
||||||
|
invocation can still lose if something else on the same IP is calling the
|
||||||
|
forge at the same moment (other repos' hooks, another session). The block
|
||||||
|
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` /
|
||||||
|
`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
|
||||||
|
|
@ -68,16 +204,147 @@ would recreate, in data, exactly the drift this plugin exists to remove.
|
||||||
root it is a `WARN`: that is where session plans, agent working files and
|
root it is a `WARN`: that is where session plans, agent working files and
|
||||||
path-traversal fixtures with deliberately invalid targets live. Measured, 30
|
path-traversal fixtures with deliberately invalid targets live. Measured, 30
|
||||||
of 43 findings were down there and all were `ERROR`s.
|
of 43 findings were down there and all were `ERROR`s.
|
||||||
|
- **A fixture-path dead link is `SKIP`, not `WARN` — and never silently
|
||||||
|
dropped.** `test/`, `tests/`, `fixtures/` (exact segment) and `*golden*`
|
||||||
|
(substring) mark a path as presumed intentional; the finding still fires as
|
||||||
|
`LINK-INTERNAL-FIXTURE` with its file and line, it just isn't judged.
|
||||||
|
Grounded in `nav-golden-escape/bundle/index.md`'s deliberate
|
||||||
|
`../../../../etc/passwd` escape: the deep `..` pops the whole base path
|
||||||
|
rather than resolving to `null`, so it read as a genuine `WARN` — third tool
|
||||||
|
in the org to hit this exact pattern, which is the signal the check was at
|
||||||
|
fault. Measured before shipping: 16 findings before, 16 after, across all 20
|
||||||
|
local clones — every one converted 1:1, none disappeared.
|
||||||
- **A repo's name is its remote, not its directory.** `catalog/` holds
|
- **A repo's name is its remote, not its directory.** `catalog/` holds
|
||||||
`ktg-plugin-marketplace`. The basename left it unregistered with zero checks
|
`ktg-plugin-marketplace`. The basename left it unregistered with zero checks
|
||||||
run, against the one repo every catalog rule depends on.
|
run, against the one repo every catalog rule depends on.
|
||||||
|
- **A decision needs somewhere to live, or the gate repeats itself forever.**
|
||||||
|
The engine already held that a human README title is the operator's call —
|
||||||
|
and still warned about it every round, because a YES could not be recorded.
|
||||||
|
Six warnings, unchanged across censuses 05, 06 and 07. `titles` in the
|
||||||
|
register is that record: set, the H1 matching it is `OK`; unset, the `WARN`
|
||||||
|
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 —
|
||||||
|
`ai-psychosis` is deliberately unregistered so it stands alone.
|
||||||
|
- **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`
|
||||||
|
naming why. An exception nobody can see reads exactly like a check that
|
||||||
|
silently stopped running.
|
||||||
|
- **Class rules live in the register, never as a class name in the engine.**
|
||||||
|
The `org-profile` exemption is a flag on the class, not `if (klass ===
|
||||||
|
'org-profile')`. Per-repo copies of a rule are the drift this plugin exists
|
||||||
|
to remove; a class name hardcoded in a classifier is the same defect one
|
||||||
|
level up.
|
||||||
|
- **The version names a file; only the sha names the code.** `engineVersion`
|
||||||
|
was added because a stale cache served an old engine silently — but a feature
|
||||||
|
and its version bump are two commits, so a worktree carries new behaviour
|
||||||
|
under the old number for a window. Measured: a sweep stamped 18 raw files
|
||||||
|
`0.4.0`, four of them holding findings from a check that only exists in
|
||||||
|
`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
|
||||||
|
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 # 103 tests
|
npm test # 243 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
|
||||||
|
|
@ -86,6 +353,15 @@ node scripts/repo-standard-check.mjs --refresh # register vs. forge
|
||||||
|
|
||||||
## Release
|
## Release
|
||||||
|
|
||||||
|
**Run `--refresh` before every release.** Register freshness is owned HERE, not
|
||||||
|
by the sweeps that read the register. Twice running, a newly published repo was
|
||||||
|
missing when a census ran, and the cost is not a gap — it is false ERRORs in a
|
||||||
|
*different* repo: `portfolio-optimiser` earned three `LINK-DEAD`s against a repo
|
||||||
|
that existed, in the same round it fixed its three real ones, so its status line
|
||||||
|
did not move even though the work was done. A stale register makes the gate
|
||||||
|
lie about repos that are not even the stale one. One owner, no shared duty:
|
||||||
|
consumers of the register are told not to check freshness themselves.
|
||||||
|
|
||||||
Polyrepo rule: a version bump is not finished until the tag `vX.Y.Z` is pushed
|
Polyrepo rule: a version bump is not finished until the tag `vX.Y.Z` is pushed
|
||||||
**and** the catalog `ref` is bumped to it. Use `release-plugin.mjs`, never a
|
**and** the catalog `ref` is bumped to it. Use `release-plugin.mjs`, never a
|
||||||
hand-edited `ref`.
|
hand-edited `ref`.
|
||||||
|
|
|
||||||
131
GOVERNANCE.md
Normal file
131
GOVERNANCE.md
Normal file
|
|
@ -0,0 +1,131 @@
|
||||||
|
# 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.
|
||||||
152
README.md
152
README.md
|
|
@ -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 the [marketplace governance](https://git.fromaitochitta.com/open/ktg-plugin-marketplace/src/branch/main/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 [GOVERNANCE.md](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.*
|
||||||
|
|
||||||

|

|
||||||

|

|
||||||

|

|
||||||

|

|
||||||
|
|
@ -41,10 +41,17 @@ a replacement for the two commands above:
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
|
|
||||||
Node 18 or newer. No dependencies. Two network calls: the org listing (for a
|
Node 18 or newer. No dependencies. Three API calls: the org listing (for a
|
||||||
repo's published description) and the catalog manifest (to confirm the install
|
repo's published description), the catalog manifest (to confirm the install
|
||||||
command resolves). Both read anonymously, so no token is needed, and `--offline`
|
command resolves), and the repo's releases listing (to see the version page a
|
||||||
skips both — the checks that depended on them then report `SKIP`, not `OK`.
|
stranger reads). Refs come over the git protocol instead — `git ls-remote
|
||||||
|
--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
|
||||||
|
|
||||||
|
|
@ -61,6 +68,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 |
|
||||||
| 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 |
|
||||||
|
|
@ -71,6 +79,11 @@ 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`,
|
||||||
|
|
@ -80,9 +93,51 @@ 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` means the check could not run — an
|
The process exits 1 on any `ERROR`. A `SKIP` is never a pass and never a
|
||||||
unreachable forge, an untagged repo, a link that leaves the repository. It is
|
severity — it is the absence of a verdict, printed apart from the judged
|
||||||
not a pass, and the output prints those separately under a heading that says so.
|
findings.
|
||||||
|
|
||||||
|
A repository's own `status` is the worst of the findings that were *judged*, and
|
||||||
|
`SKIP` only when nothing was. A skip cannot outrank twelve checks that did run.
|
||||||
|
|
||||||
|
Coverage rides beside it on its own axis, and since 0.8.0 that axis has two
|
||||||
|
values, because a skip is one of two unrelated facts:
|
||||||
|
|
||||||
|
- **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>)
|
||||||
|
✗ some-plugin [plugin] — ERROR · 2 not run (repo-standard v0.10.0 @<sha>)
|
||||||
|
· stranger — SKIP · 1 not run (repo-standard v0.10.0 @<sha>)
|
||||||
|
```
|
||||||
|
|
||||||
|
The deliberate ones are not silenced: they keep their own `NOT JUDGED` heading
|
||||||
|
in the detail output, beside `NOT CHECKED`. A decision nobody can see reads
|
||||||
|
exactly like a check that silently stopped running.
|
||||||
|
|
||||||
|
`--json` carries `skips: { byDesign, notRun }` beside `status`. `notChecked` is
|
||||||
|
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
|
||||||
|
|
||||||
|
|
@ -104,10 +159,63 @@ 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` — the question is live and unanswered. Where there is no
|
finding is a `SKIP`, and a `notRun` one — the question is live and unanswered,
|
||||||
|
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.
|
||||||
|
|
||||||
|
### Registered titles — where a decision gets written down
|
||||||
|
|
||||||
|
A README H1 that is a human title rather than the repo name is a naming choice
|
||||||
|
the maintainer owns, and the standard prescribes `# Title` for every class. But
|
||||||
|
until a YES could be *recorded*, the gate reported the same six warnings every
|
||||||
|
round, and there was no way to tell "this was decided" from "nobody has looked".
|
||||||
|
|
||||||
|
An optional `titles` entry in the register is that record. Set it, and an H1
|
||||||
|
matching it is an `OK`. Leave it out, and the warning stands exactly as before.
|
||||||
|
The point is the asymmetry: once the deliberate titles are registered, the repos
|
||||||
|
nobody has ruled on are the only ones still warning.
|
||||||
|
|
||||||
|
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
|
||||||
|
title.
|
||||||
|
|
||||||
|
### When equality is the wrong demand
|
||||||
|
|
||||||
|
`README-DESC` requires the README's opening line to be the forge description
|
||||||
|
verbatim, because for an ordinary repo the two describe the same subject and any
|
||||||
|
gap between them is drift on a published surface.
|
||||||
|
|
||||||
|
The `org-profile` class is the one place that premise fails. Its README is the
|
||||||
|
organisation's landing page; the forge description describes the repository.
|
||||||
|
Both are correct about their own subject, so it is the *equality* that does not
|
||||||
|
apply, not either text — and a landing page's opening line carries a link, while
|
||||||
|
the description field renders as plain text, so satisfying the old check would
|
||||||
|
have meant putting raw markdown on a real surface.
|
||||||
|
|
||||||
|
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
|
||||||
|
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
|
||||||
|
|
@ -147,6 +255,28 @@ Only names in **URL position** are treated as references, which excludes prose,
|
||||||
paths and directory names in one move. The `.git` suffix is normalised first —
|
paths and directory names in one move. The `.git` suffix is normalised first —
|
||||||
without that, a raw scan turns three dead names into about twenty.
|
without that, a raw scan turns three dead names into about twenty.
|
||||||
|
|
||||||
|
### Every run says which engine produced it
|
||||||
|
|
||||||
|
The header and the `--json` output carry the engine's version *and* the commit
|
||||||
|
it ran from:
|
||||||
|
|
||||||
|
```
|
||||||
|
✓ voyage [plugin] — OK (repo-standard v0.7.0 @10ad125)
|
||||||
|
```
|
||||||
|
|
||||||
|
Both, because they answer different questions. A stale plugin cache once served
|
||||||
|
an old engine against a current repo and the output looked like a clean pass,
|
||||||
|
which is why the version is there. But the version names a *file*: a feature and
|
||||||
|
its version bump are two commits, so a worktree holds new behaviour under the
|
||||||
|
old number for a window, and a sweep once stamped eighteen files with a version
|
||||||
|
that predated the check that produced their findings. Only the commit names the
|
||||||
|
code.
|
||||||
|
|
||||||
|
The commit is read from the engine's own checkout, with no network call. When
|
||||||
|
there is none to read — a vendored copy, an unpacked tarball — the JSON field is
|
||||||
|
present and `null` rather than missing, so "this engine could not tell you" stays
|
||||||
|
distinct from "an older engine that never tried".
|
||||||
|
|
||||||
## Non-goals
|
## Non-goals
|
||||||
|
|
||||||
- **Anything requiring a view across every repository at once.** This gate sees
|
- **Anything requiring a view across every repository at once.** This gate sees
|
||||||
|
|
@ -167,7 +297,7 @@ without that, a raw scan turns three dead names into about twenty.
|
||||||
npm test
|
npm test
|
||||||
```
|
```
|
||||||
|
|
||||||
103 tests over the pure classifiers. The reference fixtures are measured false
|
170 tests over the pure classifiers. 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
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "repo-standard",
|
"name": "repo-standard",
|
||||||
"version": "0.2.2",
|
"version": "0.11.1",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"engines": {
|
"engines": {
|
||||||
|
|
|
||||||
|
|
@ -30,11 +30,14 @@
|
||||||
"repo-standard": "plugin",
|
"repo-standard": "plugin",
|
||||||
"ktg-plugin-marketplace": "catalog",
|
"ktg-plugin-marketplace": "catalog",
|
||||||
"playground-design-system": "shared-asset",
|
"playground-design-system": "shared-asset",
|
||||||
|
"portfolio-optimiser-commons": "shared-asset",
|
||||||
|
"llm-security-commons": "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": [
|
||||||
|
|
@ -69,7 +72,18 @@
|
||||||
"",
|
"",
|
||||||
"Consumer-facing documentation is unaffected by working alone, and that is",
|
"Consumer-facing documentation is unaffected by working alone, and that is",
|
||||||
"the whole distinction: SECURITY.md, LICENSE, CHANGELOG, non-goals and honest",
|
"the whole distinction: SECURITY.md, LICENSE, CHANGELOG, non-goals and honest",
|
||||||
"limitations exist for the reader, not for a contributor."
|
"limitations exist for the reader, not for a contributor.",
|
||||||
|
"",
|
||||||
|
"`readme_desc_match: false` turns OFF the README-opening == forge-description",
|
||||||
|
"equality for a class. Default is on, and it is right for an ordinary repo:",
|
||||||
|
"both texts describe the SAME subject there. `org-profile` is the one class",
|
||||||
|
"where they do not — its README is the ORGANISATION's landing page and the",
|
||||||
|
"forge text describes the REPO. Both are correct about their own subject, so",
|
||||||
|
"it is the equality that does not apply, not either text. A landing page's",
|
||||||
|
"opening line also carries a markdown link, and the description field renders",
|
||||||
|
"as PLAIN TEXT: passing the check would mean putting raw markdown on a real",
|
||||||
|
"surface — trading a census ERROR for a visible defect. One member, so the",
|
||||||
|
"exemption removes coverage nowhere else."
|
||||||
],
|
],
|
||||||
"classes": {
|
"classes": {
|
||||||
"plugin": {
|
"plugin": {
|
||||||
|
|
@ -90,7 +104,8 @@
|
||||||
"org-profile": {
|
"org-profile": {
|
||||||
"required_files": ["README.md"],
|
"required_files": ["README.md"],
|
||||||
"required_headings": [],
|
"required_headings": [],
|
||||||
"install": "none"
|
"install": "none",
|
||||||
|
"readme_desc_match": false
|
||||||
},
|
},
|
||||||
"standalone": {
|
"standalone": {
|
||||||
"required_files": ["README.md", "LICENSE"],
|
"required_files": ["README.md", "LICENSE"],
|
||||||
|
|
@ -99,6 +114,36 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
"$comment_titles": [
|
||||||
|
"Where a decided YES about a README H1 gets written down. A human title is a",
|
||||||
|
"valid choice — `docs/rds-v1.md` prescribes `1. # Title` for every class, not",
|
||||||
|
"`# repo-name` — but until this map existed the decision had nowhere to live,",
|
||||||
|
"so census 05, 06 and 07 would have reported the same 6 WARNs forever.",
|
||||||
|
"",
|
||||||
|
"Set: the H1 is compared against it and a match is OK. Unset: WARN, exactly as",
|
||||||
|
"before. That makes 'we decided this is correct' and 'nobody has looked at it'",
|
||||||
|
"two different outcomes instead of one.",
|
||||||
|
"",
|
||||||
|
"PROPOSED BY THE OPERATOR, NOT MEASURED — like `traits` and `locales`, this is",
|
||||||
|
"a judgement. The five below are not one phenomenon: `.profile` CANNOT be",
|
||||||
|
"`# .profile` (the name is Forgejo's mechanism name for the org-profile repo),",
|
||||||
|
"`llm-ingestion-guard` is not a title at all but the published package name",
|
||||||
|
"(it is in `non_repos` above for the same reason), and the three plugins are",
|
||||||
|
"one consistent product convention. Each H1 was read from the repo, not",
|
||||||
|
"copied from a census.",
|
||||||
|
"",
|
||||||
|
"`ai-psychosis` (`# Interaction Awareness`) is DELIBERATELY ABSENT: it is the",
|
||||||
|
"one where a reader cannot connect the title to the repo. Leaving it standing",
|
||||||
|
"alone is the wanted effect of registering the others."
|
||||||
|
],
|
||||||
|
"titles": {
|
||||||
|
".profile": "fromaitochitta / open",
|
||||||
|
"llm-ingestion-pipeline-security": "llm-ingestion-guard",
|
||||||
|
"linkedin-studio": "LinkedIn Studio Plugin for Claude Code",
|
||||||
|
"llm-security": "LLM Security Plugin for Claude Code",
|
||||||
|
"ms-ai-architect": "AI Architect Plugin for Claude Code"
|
||||||
|
},
|
||||||
|
|
||||||
"$comment_traits": [
|
"$comment_traits": [
|
||||||
"A SECOND axis, orthogonal to class. Class is structural (read off the",
|
"A SECOND axis, orthogonal to class. Class is structural (read off the",
|
||||||
"catalog and the remotes); a trait is about what the code DOES, which no",
|
"catalog and the remotes); a trait is about what the code DOES, which no",
|
||||||
|
|
@ -141,6 +186,59 @@
|
||||||
"okr": "nb"
|
"okr": "nb"
|
||||||
},
|
},
|
||||||
|
|
||||||
|
"$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.",
|
||||||
|
"",
|
||||||
|
"13 entries, not the 14 lightweight tags that exist. `ktg-plugin-marketplace",
|
||||||
|
"v7.7.2` is DELIBERATELY ABSENT: it is that repo's newest tag, the one",
|
||||||
|
"lightweight tag with a safe remedy (`git tag -a -f`), and it is an ERROR",
|
||||||
|
"today. Pre-accepting it would mean that cutting v7.8.0 instead of fixing it",
|
||||||
|
"makes the finding vanish silently — the cheap fix skipped and unrecorded.",
|
||||||
|
"If it ages into history un-remedied, a fresh WARN is the correct signal."
|
||||||
|
],
|
||||||
|
"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"],
|
||||||
|
"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
|
|
@ -25,14 +25,39 @@ 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` (the check
|
Findings are `ERROR` (blocks), `WARN` (look, then decide), `SKIP` (not judged —
|
||||||
could not run), `OK`. Exit 1 on any ERROR. Add `--offline` to skip the two
|
in one of two ways, see below), `OK`. Exit 1 on any ERROR. Add `--offline` to skip the three
|
||||||
network calls, `--json` for machine output, `--refresh` to compare the bundled
|
API calls — the org listing, the catalog manifest, and the repo's releases —
|
||||||
register against the live org listing.
|
`--json` for machine output, `--refresh` to compare the bundled register against
|
||||||
|
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 could not see enough
|
**Never report a `SKIP` as a pass.** A SKIP means the gate did not judge the
|
||||||
to judge — an unreachable forge, an untagged repo, a link leaving the
|
thing. Say which, and say which KIND — since 0.8.0 there are two, and they are
|
||||||
repository. Say which.
|
not interchangeable:
|
||||||
|
|
||||||
|
- **`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`
|
||||||
|
no longer outranks an `OK` in the status line, so a repo reads `OK · 3 not run`
|
||||||
|
rather than `SKIP`. The summary line carries only the `notRun` count — a repo
|
||||||
|
whose every skip was deliberate prints an unqualified verdict. **The line's
|
||||||
|
silence is not proof there was nothing to say:** read the `NOT JUDGED` block, or
|
||||||
|
`skips.byDesign` in `--json`, before you tell anyone a repo was fully judged.
|
||||||
|
|
||||||
**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
|
||||||
|
|
@ -134,6 +159,53 @@ 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
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue