`notChecked` merged "deliberately not judged" with "could not run". 0.7.0 took
SKIP out of the severity lattice, which fixed "clean repos look skipped"; this
fixes the same defect one level down, where it was saying so more quietly.
Measured across 19 clones, 35 of 39 skips are deliberate — so
`portfolio-optimiser — OK · 11 not checked` announced eleven unread checks when
all eleven were fixture and out-of-repo links.
Every SKIP finding now carries `skip: 'byDesign' | 'notRun'`, set at the
emission site rather than 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 has to
re-derive a reason the emitter already had. Untagged falls to `notRun`, the
loud side. Results carry `skips: { byDesign, notRun }`; `notChecked` stays a
number and stays the total, because a consumer doing `notChecked > 0` against
an object gets a silent false.
The summary line names only `notRun` — the deliberate ones keep their own
`NOT JUDGED` heading in the detail output. Landing the split in `--json` alone
would have left the symptom exactly where it was.
VERSION-UNAVAILABLE stays `notRun` by decision, not by default: it is arguably
"no subject to judge", the shape checkReadmeLanguage answers with OK, but
re-levelling it moves a repo's status and is a second behaviour change.
Verified: 169 tests. 19 clones swept with both engines offline — 220 judged
findings, every status, notChecked and buckets identical, no finding differing
beyond the new tag. The exhaustiveness test scans the engine SOURCE, not a
finding set: a corpus test only sees the sites it triggers, and the next
emission site added would take the reader's default invisibly.
BREAKING CHANGE: the summary line's coverage qualifier reads `· N not run` and
counts only un-runnable skips, where it read `· N not checked` over all of
them. `--json` gains `skips`; `notChecked` is unchanged.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DHaQTcaD3w2C6PqVsAmrcv
490 lines
27 KiB
Markdown
490 lines
27 KiB
Markdown
# Changelog
|
||
|
||
All notable changes to this project are documented here.
|
||
Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/);
|
||
versioning is [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||
|
||
## [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
|
||
|
||
### Fixed
|
||
|
||
- The engine never printed its own version, which is how a stale plugin cache
|
||
went unnoticed: two repos independently proved the `/repo-standard` skill
|
||
had resolved `${CLAUDE_PLUGIN_ROOT}` to a cached `0.1.1` while `0.2.0` was
|
||
installed and the catalog pinned it — same instruction, wrong engine, and
|
||
`0.1.1` has neither `BADGE-COUNT` nor `README-LANGUAGE`, so the run looked
|
||
clean instead of incomplete. The header line and `--json` output now both
|
||
carry `repo-standard v<version>`; `SKILL.md` tells the reader to confirm it
|
||
against the catalog pin before trusting a green result. The cache
|
||
resolution itself is the harness's, not this repo's, and is not fixed here.
|
||
|
||
## [0.2.1] — 2026-08-04
|
||
|
||
### Fixed
|
||
|
||
- The gate makes two anonymous forge calls per invocation (org listing +
|
||
catalog `marketplace.json`), not one — this repo's own `CLAUDE.md` said
|
||
"one" from before `INSTALL-TRUTH` added the second, and stayed wrong long
|
||
enough that a 13-repo shell loop trusted the count and tripped an HTTP 429
|
||
at 26 requests. Both calls now go through `fetchWithRetry`, which honors
|
||
`Retry-After` and retries instead of silently reporting `SKIP` on a
|
||
transient rate limit. `CLAUDE.md` corrected to match.
|
||
|
||
## [0.2.0] — 2026-08-04
|
||
|
||
### Added
|
||
|
||
Two presentation checks, both from adversarially-verified research rather than
|
||
taste — `docs/presentation-research-2026-08-03.md` records what the evidence
|
||
supports and, more usefully, what it refuses to support.
|
||
|
||
- `BADGE-COUNT` (`WARN`) — more than five badges. Trockman et al., ICSE 2018
|
||
(n=294,941 npm packages) measured a non-linear relationship with popularity
|
||
inflecting at five, motivated by surveyed maintainers calling over-badged
|
||
READMEs cluttered and "trying too hard". `WARN` and never `ERROR`: the
|
||
coefficient sits in an appendix without CI or p-value, so it carries "more is
|
||
not better" and cannot carry a hard limit. Counting uses a narrower rule than
|
||
the existing claim check — a screenshot or architecture diagram must not be
|
||
counted as clutter. Measured: 8 of 18 `open/` repos are past it.
|
||
- `README-LANGUAGE` (`WARN`) — the prose is not in the language this repo's
|
||
readers were declared to speak, via a new `locales` axis in the register.
|
||
English is the default; `ms-ai-architect` and `okr` are declared `nb` as
|
||
Norway-only in audience. Detection is a stopword-frequency comparison over
|
||
prose with code stripped, so a Norwegian flag name in a shell example cannot
|
||
decide the document. Evenly bilingual prose is a `SKIP` — the question is
|
||
live and unanswered. No running prose is an `OK`: nothing claims a language,
|
||
and a thin README is `checkFirstScreen`'s business. Measured: fires on
|
||
exactly the two declared repos, silent on all sixteen English ones.
|
||
|
||
Deliberately **not** built, because the evidence does not reach: any rule about
|
||
images, diagrams, screenshots or terminal recordings (every such claim was
|
||
refuted 0-3); a README length bound (no evidence-based target exists); a section
|
||
count (would fire on 9 of 18 — textbook "suspect the CHECK"); Mermaid source
|
||
length and `#gh-dark-mode-only` (zero occurrences, and the instance's actual
|
||
limit is not readable via the API, so any threshold would be a guess).
|
||
|
||
### Changed
|
||
|
||
- The skill now carries diagram guidance, deliberately as judgement and not as a
|
||
check. Mermaid is verified to render on this forge; that is capability, not
|
||
justification, and every claim that visuals improve a README was refuted. What
|
||
the guidance covers is a diagram that already exists — height, node labels,
|
||
the silent degradation past `MERMAID_MAX_SOURCE_CHARACTERS`, and the fact that
|
||
`#gh-dark-mode-only` is unavailable below Gitea 1.26.0. The org's own measured
|
||
example renders at 1,459 px and wraps node text mid-word while its neighbour
|
||
sits at 191 px: rendering and rendering well are separate questions.
|
||
|
||
## [0.1.3] — 2026-08-03
|
||
|
||
### Fixed
|
||
|
||
Three more measured false positives, all reported via coord the same day
|
||
0.1.2 shipped, from repos running the gate for the first time.
|
||
|
||
- `extractChangelogTop` truncated a PEP 440 pre-release (`0.5.0a2`) to `0.5.0`,
|
||
so `VERSION-CHANGELOG` disagreed with `VERSION-TAG` — which compares the
|
||
untruncated tag and does not have this problem — over a version the manifest
|
||
and the tag already agreed on. Reported by `llm-ingestion-okf` with a
|
||
repro. An affected repo could reach 0 ERROR only by lying about its version;
|
||
now the pre-release suffix is kept.
|
||
- `BADGE-STATIC-CLAIM` treated a bare `status` badge (`status-alpha`,
|
||
`status-experimental`) as a run claim, the same as `tests-34-passing`.
|
||
A self-declared maturity label asserts no run — the same class as version,
|
||
licence and platform, already exempt. Reported by
|
||
`llm-ingestion-pipeline-security`. `build`/`ci`/`passing` still catch the
|
||
run-asserting compounds ("build status", "CI status"); only the bare word
|
||
is dropped.
|
||
- `BOILERPLATE` flagged a bare `FIXME` even when it named the *convention*
|
||
rather than an instance of it — a scanner's own docs describing what it
|
||
detects ("Flag TODO/FIXME markers…", a table row listing "TODO/FIXME
|
||
comments" as a finding type) read the same as a forgotten marker. Reported
|
||
by `config-audit`, whose product literally is a marker scanner. "TODO/FIXME"
|
||
named together is now read as the convention; a lone `FIXME` is still
|
||
caught.
|
||
|
||
## [0.1.2] — 2026-08-03
|
||
|
||
### Fixed
|
||
|
||
- `checkInternalLinks` compared a link's resolved target only against
|
||
`present` — the set of tracked *files* — so a link to a directory
|
||
(`[x](dir/)`) was reported `LINK-INTERNAL-MISSING` even when every file
|
||
under that directory was tracked. A directory was never a member of that
|
||
set to begin with. Reported by `portfolio-optimiser-claude` (coord
|
||
20260803T194933Z) with a minimal repro; confirmed and traced to the same
|
||
defect in `voyage`, `linkedin-studio` and `portfolio-optimiser` — 12 of the
|
||
71 ERROR findings measured across the org on 2026-08-03 were this one check,
|
||
not twelve separate repo problems. Fixed by deriving the set of directories
|
||
that actually contain a tracked file from `present` itself, and letting a
|
||
link that resolves to one of them pass.
|
||
|
||
## [0.1.1] — 2026-08-03
|
||
|
||
Documentation only. No behaviour change: `scripts/` and `register/` are
|
||
unchanged from 0.1.0 — this release carries version strings and the correction
|
||
below.
|
||
|
||
### Fixed
|
||
|
||
- The 0.1.0 entry said the gate has **eleven** checks. It has twelve, which is
|
||
what both the code and the README table already said. The entry is corrected
|
||
in place, and this release exists so the catalog's pinned tag serves the
|
||
corrected text instead of the wrong count. The count is drift of exactly the
|
||
kind this plugin was built to catch, and it was sitting in the plugin's own
|
||
repository.
|
||
|
||
## [0.1.0] — 2026-07-27
|
||
|
||
First release. Covers the checks that a single repository can answer on its own.
|
||
|
||
### Added
|
||
|
||
- **The gate** (`scripts/repo-standard-check.mjs`) — pure classifiers with all
|
||
I/O resolved into their input, findings tagged `ERROR`/`WARN`/`SKIP`/`OK`,
|
||
exit 1 on `ERROR`. Twelve checks: README first screen, install block, install
|
||
truth, required headings, files required by the repo's class and traits,
|
||
`open/<name>` references, relative links, licence claim, badges, boilerplate,
|
||
version consistency and description length.
|
||
- **Taxonomy register** (`register/repos.json`) — one central file mapping each
|
||
repository to its class, plus the per-class file and install requirements.
|
||
`--refresh` compares it against the live org listing.
|
||
- **Three-outcome reference check** — "matches no repository" (`ERROR`) is a
|
||
separate finding from "matches something that is deliberately not a
|
||
repository" (`WARN`). Sharing an outcome would let real dead links hide among
|
||
correct text. Only names in URL position are treated as references; the `.git`
|
||
suffix is normalised first.
|
||
- **Two-axis findings** — every `ERROR`/`WARN` carries a **bucket** alongside its
|
||
level: `broken` (a stranger is blocked or misled), `missing` (an expected
|
||
artefact is absent), `weakening` (present, but it reads as amateur). Output is
|
||
grouped by bucket, because that is the order findings get acted on.
|
||
- **Traits, a second axis beside class** — class is structural and readable off
|
||
the catalog; a trait is about what the code does. `security` attaches a
|
||
`SECURITY.md` requirement and a `## Known limitations` section.
|
||
- **Required README headings per class** — `## Install`, `## Non-goals`,
|
||
`## Changelog`. A heading present at the wrong depth is its own finding, not
|
||
"missing".
|
||
- **In-repo version consistency** — manifest, README version badge, newest
|
||
CHANGELOG entry and the git tag must agree. An untagged repo `SKIP`s the tag
|
||
comparison rather than failing it.
|
||
- **Badge honesty** — a static badge asserting a test, build or coverage run is
|
||
a claim dressed as evidence. Version, licence and platform badges assert no
|
||
run and are fine.
|
||
- **Install truth, not just install syntax** — the marketplace URL in the install
|
||
block must be the real one (offline, against the register), and the plugin must
|
||
actually be pinned in the catalog (one call; `SKIP` if unreachable). A perfectly
|
||
formed `claude plugin install x@mkt` fails silently when `x` was never pinned,
|
||
and that is the single most disqualifying thing a repo can publish.
|
||
- **Boilerplate, licence-claim and relative-link checks.**
|
||
- **The skill** (`skills/repo-standard/`) — the judgement the script cannot
|
||
encode: what a description should say, why the summary card must never be used
|
||
to verify one, what not to retrofit, and where the per-repo boundary is.
|
||
- **Link level follows the reader.** A dead relative link in a root document —
|
||
README, CHANGELOG, SECURITY — is an `ERROR`: it is in the shop window. The same
|
||
link below the root is a `WARN`. Measured across seventeen repositories: 30 of
|
||
43 findings sat in `shared/`, `docs/plan/` and `.claude/` — session plans,
|
||
agent working files, and path-traversal test fixtures whose targets are invalid
|
||
on purpose. All 30 were `ERROR`s, which is how a gate gets switched off. Only
|
||
the level moves; every finding is still reported with its file and line.
|
||
- **The repo name comes from the remote, not the directory.** `catalog/` is the
|
||
working directory of the repo named `ktg-plugin-marketplace`. Reading the
|
||
basename left it `REPO-UNREGISTERED` with zero checks run — against the one
|
||
repo every catalog rule depends on. The scp form the forge's clone button hands
|
||
out is handled, and a bare host is not a repo name.
|
||
- 84 tests over the pure classifiers, using measured false positives as
|
||
reference fixtures.
|
||
|
||
### Notes
|
||
|
||
- Descriptions are measured in **codepoints** — not bytes, and not UTF-16 units.
|
||
The same string measures 248 / 249 / 253 across those three yardsticks when it
|
||
contains an astral character.
|
||
- `enabledPlugins` in `settings.json` is treated as a legitimate second install
|
||
form. The gate requires a CLI command *as well*, and never reports the JSON
|
||
form as the defect.
|
||
- Two network calls, both anonymous: the org listing and the catalog manifest.
|
||
Per-repo fetching trips the forge's rate limiter, so neither is ever done per
|
||
repository. `--offline` skips both, and the checks that needed them report
|
||
`SKIP` rather than passing.
|
||
- `CONTRIBUTING.md`, `CODE_OF_CONDUCT.md` and `MAINTAINERS.md` are required by
|
||
no class: the project is solo-maintained and publishes that stance. This is
|
||
not a rule against having them — files already present are a cleanup decision,
|
||
not a gate finding. `SECURITY.md` is unaffected; it exists for the outsider who
|
||
finds a hole.
|
||
- There is no CI badge because there is no CI: the forge has no Actions runner
|
||
registered. The substitute is one command from a clean clone, stated honestly.
|
||
A badge asserting the test count would be the very anti-pattern the gate flags,
|
||
and an earlier draft of this README carried exactly that.
|
||
- The link and boilerplate checks ignore fenced blocks and inline code spans.
|
||
Documentation about regexes and placeholders is full of text that is a markdown
|
||
link to a naive scanner — the first version produced about thirty findings
|
||
against a real repository and every one was noise.
|
||
- Relative links resolve against the file they appear in, not the repo root.
|
||
- Indented text counts as a code block only when a blank line opens it, and then
|
||
continues while the indent holds. The first rule alone made links inside nested
|
||
list items invisible; the second alone let everything after a block's first line
|
||
leak back in. Both were silent false passes, which is worse than noise — the
|
||
second was caught by the gate on this plugin's own SKILL.md.
|
||
- A README H1 that differs from the repo name is a `WARN`, not an `ERROR`. The
|
||
thread that has to hold is description == catalog == opening line, and the H1
|
||
is none of those three; a human title is a naming choice the operator owns.
|
||
A missing H1 is still an `ERROR`.
|
||
- No hook ships in this release. A blocking gate has to be precise enough not to
|
||
fail a correct repository first.
|
||
|
||
[0.2.0]: https://git.fromaitochitta.com/open/repo-standard/src/tag/v0.2.0
|
||
[0.1.3]: https://git.fromaitochitta.com/open/repo-standard/src/tag/v0.1.3
|
||
[0.1.2]: https://git.fromaitochitta.com/open/repo-standard/src/tag/v0.1.2
|
||
[0.1.1]: https://git.fromaitochitta.com/open/repo-standard/src/tag/v0.1.1
|
||
[0.1.0]: https://git.fromaitochitta.com/open/repo-standard/src/tag/v0.1.0
|