repo-standard/README.md
Kjell Tore Guttormsen 75f2149761 chore(release): v0.2.0, since new checks are a minor and the ref is a pointer
BADGE-COUNT and README-LANGUAGE are additions, so this is a minor rather than a
patch. The catalog ref still sat on v0.1.1 against a released v0.1.3; bumping it
to v0.2.0 clears that lag in the same move, because a ref points, it does not
queue.

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

179 lines
8.8 KiB
Markdown

# repo-standard
Per-repo gate for the open/ presentation standard: README first screen, install block, files required by the repo's class, and dead repo references.
A repository can be substantially good and still read as abandoned. The gap is
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
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.
*AI-generated: all code produced by Claude Code through dialog-driven development.*
![Version](https://img.shields.io/badge/version-0.2.0-blue)
![Platform](https://img.shields.io/badge/platform-Claude_Code_Plugin-purple)
![Skills](https://img.shields.io/badge/skills-1-orange)
![License](https://img.shields.io/badge/license-MIT-lightgrey)
**Status:** maintained, solo. The public API is the gate's finding codes and the
register schema; both can still change before 1.0. There is no CI — the forge
has no Actions runner — so the test claim is one you run yourself, in one
command, from a clean clone: `npm test`. A badge asserting it would be a claim
dressed as evidence.
## Install
Use the `https://` form. The forge UI's clone button hands out an `ssh://` URL,
and `marketplace add` answers it with `Invalid git URL` — a message that never
mentions the protocol.
```bash
claude plugin marketplace add https://git.fromaitochitta.com/open/ktg-plugin-marketplace.git
claude plugin install repo-standard@ktg-plugin-marketplace
```
Or enable it directly in `~/.claude/settings.json` — a working second path, not
a replacement for the two commands above:
```json
{ "enabledPlugins": { "repo-standard@ktg-plugin-marketplace": true } }
```
## Requirements
Node 18 or newer. No dependencies. Two network calls: the org listing (for a
repo's published description) and the catalog manifest (to confirm the install
command resolves). Both read anonymously, so no token is needed, and `--offline`
skips both — the checks that depended on them then report `SKIP`, not `OK`.
## What it does
Run it inside a repository:
```bash
node scripts/repo-standard-check.mjs --dir "$PWD"
```
The repository's **class** decides what each check means:
| Check | What fails it |
| --- | --- |
| 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 truth | the plugin is not pinned in the catalog, so the documented command cannot succeed for anyone |
| 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 |
| Repo references | an `open/<name>` in URL position resolves to nothing |
| Relative links | a link points at a file that is not tracked |
| Licence claim | the README cites a licence the repo has no file for |
| Badges | a static image badge — any host — asserts a test, build or coverage run that nothing verifies |
| Badge count | more than five badges, the inflection measured by Trockman et al. (ICSE 2018) past which a badge row reads as clutter rather than as evidence. `WARN` only — the source cannot carry a hard limit |
| README language | the prose is not in the language this repo's readers were declared to speak |
| Boilerplate | template text nobody filled in |
| Version consistency | manifest, README badge, newest CHANGELOG entry and the git tag disagree |
| Description | empty, or past the length bound |
Findings carry two independent things: a **level** (`ERROR`, `WARN`, `SKIP`,
`OK`) and a **bucket**, which is what you actually triage on:
- **broken now** — a stranger is blocked or misled
- **missing** — an expected artefact is absent
- **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
unreachable forge, an untagged repo, a link that leaves the repository. It is
not a pass, and the output prints those separately under a heading that says so.
### Traits — a second axis
Class is structural. A **trait** is about what the code does, which no remote can
tell you. `security` attaches the obligations a tool takes on by handling
untrusted input: a `SECURITY.md` with a real disclosure channel, and a
`## Known limitations` section. Traits live in the register and are the
maintainer's judgement, not a reading.
### Locales — a third axis
Class is structural, a trait is what the code *does*, and a locale is who the
code is *for*. English is the default and is not listed. A repo aimed only at a
Norwegian readership is declared `nb` in the register, and is then wrong in
English rather than right — the standard's own principle is that who the reader
is decides what is required, and language is the first thing that decides.
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
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
finding is a `SKIP` — the question is live and unanswered. Where there is no
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.
### What is deliberately not required
`CONTRIBUTING.md`, `CODE_OF_CONDUCT.md` and `MAINTAINERS.md` are required by no
class. This project is solo-maintained and says so publicly; contributor-facing
documentation for something that accepts no contributors is theatre, and a code
of conduct with an unattended placeholder address is worse than none — it is a
visible unfinished template. Consumer-facing documents are untouched by that.
Being solo is not a reason to skip a `SECURITY.md`; someone finding a hole still
needs somewhere to send it.
### The class decides what is required
The class is read off the catalog and the remotes; it is structural, not a
judgement. It lives in `register/repos.json`.
| Class | Install form | Requires |
| --- | --- | --- |
| plugin | `marketplace add` **and** a CLI install command | README, LICENSE, CHANGELOG, plugin manifest |
| catalog | `marketplace add` only — it *is* the marketplace | + GOVERNANCE, CONVENTIONS |
| shared-asset | how to vendor it; never a plugin install line | README, LICENSE |
| standalone | pip/uv | README, LICENSE |
| org-profile | none | README |
A flat standard across every class would demand a roadmap from a five-line
profile. That is how a gate teaches people to switch it off.
### Three outcomes on references, not two
"Matches no repository" and "matches something that is deliberately not a
repository" are different findings. The second class is real and it is common:
a retired name kept alive on purpose in prose, a reserved namespace occupying a
repo-shaped path, a published package name that was never a repo. If those share
an outcome with genuine dead links, the genuine ones hide inside a pile of
correct text.
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 —
without that, a raw scan turns three dead names into about twenty.
## Non-goals
- **Anything requiring a view across every repository at once.** This gate sees
one repo. A README can look correct from the inside and only turn out wrong
when a dozen are compared. Org-wide divergence is measured where the org is
enumerated, not here.
- **Fixing while measuring.** It records findings; you fix them afterwards.
Patching under measurement is how inconsistency accumulates unnoticed.
- **Deciding whether to rename a repo, or whether a roadmap is real.** Those are
operator calls. The gate brings evidence to them.
- **Blocking commits.** There is no hook. A gate that fails a correct repository
is the mechanism that gets gates switched off, so this one earns that role
before it takes it.
## Tests
```bash
npm test
```
103 tests over the pure classifiers. The reference fixtures are measured false
positives, each with its expected verdict — the six that produced the
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
a naive scanner, `file:` URLs, and relative links resolved against the wrong
directory.
## Changelog
See [CHANGELOG.md](CHANGELOG.md).