repo-standard/skills/repo-standard/SKILL.md
Kjell Tore Guttormsen 1dfd408ffc feat(engine)!: "no version to compare against" is a verdict, not a skip
`VERSION-UNAVAILABLE` (SKIP/notRun) becomes `VERSION-NONE` (OK). A repo 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.

0.8.0 deferred this and recorded the reason in the engine: re-levelling "moves
a repo's status". Measured false across 19 clones — an added OK cannot worsen
the worst *judged* finding, and all three repos emitting the code already read
OK. Status moves only for a repo whose entire finding set was skips.

OK cannot bless a real gap here: no class requires a version file, and a
`plugin` missing its manifest is an independent FILE-MISSING ERROR.

Renamed, not just re-levelled: `-UNAVAILABLE` is this engine's naming for a
notRun skip, and one OK-level `-UNAVAILABLE` would mislead exactly the reader
this change serves. A source scan keeps the old name out.

A/B sweep, both engines offline, 19 clones: 223 judged findings, every status
and buckets identical; 3 conversions (notChecked 14→13, 3→2, 14→13), byDesign
untouched. 170 tests. `--refresh`: register 21 = forge 21.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CTNGvxzCLZp3UXfibYG4kb
2026-08-09 22:05:21 +02:00

16 KiB
Raw Blame History

name description version
repo-standard Check and fix one repository against the org's presentation standard — README first screen, install block, required files for its class, dead `open/<name>` references, and description length. Use when the user asks to "check this repo against the standard", "run repo-standard", "is this repo compliant", "fix the README first screen", "the install instructions are incomplete", "check for dead repo references", "why does this repo look abandoned", or before publishing/releasing a repo to the org. Also triggers on Norwegian phrasings: "sjekk repoet mot standarden", "er dette repoet i orden", "rydd opp i README", "fiks install-blokka", "finn døde repo-referanser", "gjør repoet presentabelt". Trigger when someone is about to release, publish, or hand over a repository and wants its public surface to hold up. 0.2.2

repo-standard — the per-repo gate

This skill checks one repository against the standard for its class, and helps fix what it finds. The script is the engine; this file is the judgement the script cannot encode.

Run the gate first, always

node "${CLAUDE_PLUGIN_ROOT}/scripts/repo-standard-check.mjs" --dir "$PWD"

Findings are ERROR (blocks), WARN (look, then decide), SKIP (not judged — in one of two ways, see below), OK. Exit 1 on any ERROR. Add --offline to skip the two network calls, --json for machine output, --refresh to compare the bundled register against the live org listing.

Never report a SKIP as a pass. A SKIP means the gate did not judge the thing. Say which, and say which KIND — since 0.8.0 there are two, and they are 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") 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: ${CLAUDE_PLUGIN_ROOT} resolved to a cached 0.1.1 in a live session while 0.2.0 was installed and the catalog pinned it — same instruction, same variable, wrong engine, and nothing in the output said so at the time. Two repos proved it by running both versions against the same checkout: 0.1.1 resurrected three findings 0.1.2/0.1.3 had already fixed. The header line and --json output now both carry repo-standard v<version> — read it, and if it does not match what the catalog pins (ktg-plugin-marketplace's marketplace.json, or ask the operator), say so instead of reporting the run as clean.

Report in buckets, not in a flat list. Every finding also carries broken (a stranger is blocked or misled), missing (an expected artefact is absent) or weakening (present, but reads as amateur). That is the order the work actually gets done in, and it is more useful to a reader than severity alone. The two axes are independent: a README opening line that contradicts the published description blocks nobody and is still an ERROR.

Before you trust a run, look at the noise

The first version of the link check produced about thirty findings against a real repository and every single one was noise: regexes inside code spans that are [...](...) to a naive scanner, file: URLs, and relative links compared against the repo root instead of the directory they sit in. The check now strips code and resolves properly.

The lesson generalises, so apply it to your own reading: when a check fires many times in one repo, suspect the check before the repo. A gate that is wrong this often teaches people to ignore it, which is worse than not having it at all. If you find a new noise source, fix the classifier and add the case as a fixture — do not tell the user to ignore some of the output.

What it measures, and what it cannot

The gate sees one repo. Every finding in the census that mattered came from measuring across eighteen. llm-security's README looks correct from the inside: it has a Quick Start, an Install heading, a code block. It is only wrong once eleven repos are compared and one of them turns out to do install completely.

So: divergence across the org is not this skill's job. Zero topics across all repos, three competing install forms, README release notes duplicating a CHANGELOG that most repos have — none of that is visible from in here. Those checks live where the org is enumerated, not in a per-repo gate. If you suspect a cross-repo problem, say so and stop; do not approximate it from one repo.

The class decides what is required

The class is read off the catalog and the remotes — it is structural, not a judgement call. register/repos.json holds it.

Class Install form
plugin marketplace add plus a CLI install command
catalog marketplace add only — it is the marketplace
shared-asset how to vendor it; never a plugin install line
standalone pip/uv
org-profile no install section

A flat standard across all classes would demand a ROADMAP from a five-line profile. That is how a gate teaches people to switch it off.

Traits are a second axis. Class is structural — readable off the catalog and the remotes. A trait is about what the code does, which no remote knows. security attaches what a tool owes once it handles untrusted input: a SECURITY.md with a channel someone actually reads, and limitations stated with their mechanism. Which repos carry a trait is the maintainer's judgement; do not add or remove one on your own initiative, propose it.

Who the reader is decides what is required

This project is solo-maintained, and that settles a whole category. Documents written for contributors — CONTRIBUTING.md, CODE_OF_CONDUCT.md, MAINTAINERS.md, PR templates, CLA/DCO — are required by no class here, because there are no contributors and the published stance says so plainly. A code of conduct with an unattended placeholder address is worse than none: it is a visible unfinished template.

Documents written for consumers and outsiders are completely unaffected by that, and working alone is never a reason to skip them: SECURITY.md exists for the stranger who finds a hole, LICENSE for the one deciding whether they may use it, non-goals and limitations for the one deciding whether it fits.

Do not turn this into a rule against having those files. A CODE_OF_CONDUCT.md already sitting in a repo is a cleanup decision for the operator, not a gate finding.

Badges, and the one that is a lie

Only badges that measure something. A static image reading tests: 642 passing is a claim dressed as evidence, and the gate flags it — an early draft of this plugin's own README carried exactly that, which is how it got caught. Version, licence and platform badges assert no run and are fine as static images.

There is no CI in this organisation: the forge has no Actions runner registered. So do not add a CI badge, and do not pretend. The honest substitute is a single 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 is one mechanism for it, and it is not available here.

Diagrams — capability is not justification

Mermaid renders in a README on this forge. That was verified on the live instance rather than inferred: the published voyage README yields two div.mermaid-block containers holding real SVG. It needs no committed image and no hand-written iframe, because the forge injects its own renderer.

That is the whole of what the evidence supports — availability. Every claim about visual content failed verification: that images are rare and therefore differentiating, that static images outperform GIFs, that screenshots correlate with anything. There is no evidential basis for a rule about diagrams, screenshots or terminal recordings, so there is no engine check and there will not be one until that changes. Adding a diagram may still be right. It is taste, it has to be argued as taste in the specific repo, and the argument has to say what this reader gains.

Measured, for context and not as a target: across all eighteen READMEs, every single ![...] resolves to a badge. The org has no real images at all. That is a description of where things stand, not a deficit to close — inverting a measurement into a requirement is the trap this whole research line kept falling into.

What can be judged is a diagram that already exists. Rendering and rendering well are separate questions, and the org has the gap on display: the second voyage diagram occupies 1,459 px of vertical space and wraps node text mid-word. The other one on the same page is 191 px. At the larger size it is not read, it is scrolled past.

So when a repo has a diagram, or you are adding one:

  • Height before beauty. Past roughly one screen it has stopped explaining and started interrupting. Prefer flowchart LR to TD once the chain runs beyond about five nodes — vertical is the axis that gets away from you.
  • Short node labels. Mid-word wrapping is the visible symptom of labels written as sentences. The sentence belongs in the prose underneath.
  • A diagram earns its place by replacing prose, not by accompanying it. Two descriptions of one thing is the same duplication the changelog rule catches, drawn instead of written.
  • Watch the source length. MERMAID_MAX_SOURCE_CHARACTERS is a per-instance limit, and exceeding it degrades silently to a plain code block — the reader gets raw Mermaid and nothing anywhere reports an error.
  • No dark-mode image trick. GitHub's #gh-dark-mode-only fragment landed in Gitea 1.26.0; this instance is built on gitea-1.22.0, so both variants stack and the reader sees two images. Mermaid, drawn by the theme, is the one visual that sidesteps the problem entirely.
  • Judge it published. All of the above is about the rendered result, and a local preview is a different renderer.

Keep the boundary: the gate reports nothing about visuals. Everything in this section is advice you give a human, never a finding you file.

Two things about the install block worth knowing before you edit one

enabledPlugins is a legitimate second form, not a defect. Most plugin READMEs offer it ("Or enable directly in ~/.claude/settings.json"), and a reader who scrolls to it has a complete, working path. What the gate requires is a CLI command as well, because an agent handed "install this" reaches for the CLI and otherwise finds marketplace add and nothing after it. Require the command; welcome the JSON beside it. Never treat the JSON as the failure.

marketplace add rejects ssh://. It answers Invalid git URL, and the message never mentions the protocol. The forge UI's clone button hands out exactly that URL, so this is a trap people walk into rather than a mistake they make. Always write the https:// form.

Keep the block parametric. The marketplace name and URL come from the register, not from a hardcoded string. A repo distributed through a different marketplace — including a private one — needs its own values, and a public skill cannot carry private marketplace names in the first place.

Fixing the first screen

Only lines 1–25. Nobody rewrites a 930-line body, and the gate does not ask you to.

# <name>
<one line — identical to the forge description>

<2–4 lines: which problem it solves, who it is for>

## Install
<the block for this class>

## Requirements
## What it does
## Non-goals
…rest unchanged…
## Changelog
See [CHANGELOG.md](CHANGELOG.md).

The opening line is not decoration: it makes description == catalog == README, which is the only place a machine can check that the three agree. ## Install on a fixed heading is what agents pattern-match on — position and predictability beat brevity for that reader. And Non-goals answers "is this for me?" better than any feature table; almost no README answers it at all.

Release notes do not belong on the first screen. A blockquote packing five versions before the reader knows what the thing is both duplicates the changelog and points at it. Move it under ## Changelog as a link.

What not to retrofit: inline version annotations inside feature tables — (v4.1.0), "since v2.4.0" — stay. They are real work and they help a reader who has already installed. The rule applies to new ones: do not add more.

Dead references — three outcomes, never two

The link check separates "matches no repo" (ERROR) from "matches something that is deliberately not a repo" (WARN). Keep them separate when you report, too. If they collapse into one number, a real loss hides inside a pile of correct text.

Names in URL position only are references. A repo-shaped string in a path, in running prose, or as a directory name is not a broken link — and in the measured cases that text was correct and is supposed to stay correct. Do not "fix" it.

Before calling a reference dead, normalise the .git suffix. A raw grep once turned three real dead names into about twenty.

Descriptions

Written for someone who has never seen the repo, saying what it does and who it is for. No sales language: "fully", "without exception", "everything from the terminal" are red flags in a technical text.

Length is bounded and measured in codepoints — not bytes, not UTF-16 units. The same string can measure 248 / 249 / 253 across those three yardsticks when it contains an astral character like 👉. An em-dash costs three bytes but one codepoint, so it only ever exposed the outer layer.

Never verify a description against the repo summary card. It is server-side cached with an undocumented TTL, measured unchanged for more than six hours after a write. Check the API, or og:description in the HTML. A card that still shows the old text is not a failed write, and re-writing it is how you turn a cache into an outage of your own making.

ROADMAP — not required, and not automatic

No class requires one. They are absent everywhere today, and a roadmap is a real judgement about where a repo is going, not a template.

When one is written, it is the published, sanitised derivation of the repo's own next-step block. STATE.md must never reach a public surface. Draft from it, have a human approve, then publish. Never automatically.

The boundary this skill will not cross

Whether to rename a repo, and whether a given roadmap is real or ceremonial, are operator decisions. Bring the evidence; do not decide.

And record findings before fixing them. Patching while measuring is how the inconsistency being cleaned up got there in the first place. Run the gate, report what it says, then fix — in that order.