Per-repo gate for the open/ presentation standard: README first screen, install block, files required by the repo's class, and dead repo references.
  • JavaScript 100%
Find a file
Kjell Tore Guttormsen 9eb210bb01 feat(engine): fixture-path dead links are SKIP, not WARN
A file living under test/, tests/, fixtures/, or a *golden* path is
presumed to break its own links on purpose. nav-golden-escape/bundle/
index.md's deliberate `../../../../etc/passwd` escape pops the whole
base path instead of resolving to null, so it read as a genuine WARN
against three repos in the org — the check was at fault, not them.

The finding still fires, as LINK-INTERNAL-FIXTURE at SKIP with file
and line, so it is never silently dropped. Measured before shipping:
16 LINK-INTERNAL-* findings before, 16 after, across all 20 local
clones — every one converted 1:1, none disappeared.

135 tests (was 129).
2026-08-09 14:31:18 +02:00
.claude-plugin chore(release): v0.4.0, since LINKS-OPEN-REFS is a new check 2026-08-09 14:18:32 +02:00
docs feat(gate): two presentation checks the evidence actually supports 2026-08-04 09:41:53 +02:00
register feat(register): register portfolio-optimiser-commons as shared-asset 2026-08-09 14:18:09 +02:00
scripts feat(engine): fixture-path dead links are SKIP, not WARN 2026-08-09 14:31:18 +02:00
skills/repo-standard fix(engine): print engine version, so a stale plugin cache is visible 2026-08-04 13:12:29 +02:00
.gitignore feat(repo-standard): v0.1.0 - per-repo gate for the open/ standard 2026-07-27 09:10:46 +02:00
CHANGELOG.md chore(release): v0.4.0, since LINKS-OPEN-REFS is a new check 2026-08-09 14:18:32 +02:00
CLAUDE.md feat(engine): fixture-path dead links are SKIP, not WARN 2026-08-09 14:31:18 +02:00
LICENSE feat(repo-standard): v0.1.0 - per-repo gate for the open/ standard 2026-07-27 09:10:46 +02:00
package.json chore(release): v0.4.0, since LINKS-OPEN-REFS is a new check 2026-08-09 14:18:32 +02:00
README.md feat(engine): fixture-path dead links are SKIP, not WARN 2026-08-09 14:31:18 +02:00

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 for the full model.

AI-generated: all code produced by Claude Code through dialog-driven development.

Version Platform Skills License

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.

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:

{ "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:

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

npm test

135 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.