feat(gate): buckets, traits, and the checks the brief calls load-bearing

Measured this build against a documentation brief for public repos. The
five original checks covered roughly one of its ten sections, so this
adds what a single repo can answer on its own.

New: required README headings per class (Non-goals is the cheapest
trust-builder there is), in-repo version consistency across manifest /
badge / CHANGELOG / tag, badge honesty, boilerplate, licence-claim,
and relative links. Findings now carry a BUCKET beside the level -
broken / missing / weakening - and output is grouped by it, because
that is the order the work gets done in.

Traits are a second axis beside class: class is structural and readable
off the catalog, a trait says what the code does. `security` attaches
SECURITY.md and a Known limitations section. The two names carrying it
are proposed, not measured - that list is the operator's.

Solo-maintained settles a category: CONTRIBUTING, CODE_OF_CONDUCT and
MAINTAINERS are required by no class. Consumer-facing documents are
untouched by that; SECURITY.md exists for the stranger who finds a hole.

Three bugs found by running against llm-security, not by reading:
- ~30 link findings, all noise. Regexes inside code spans are
  `[...](...)` to a naive scanner. Strip code first.
- `file:` and other schemes were treated as repo-relative paths.
- Relative links were resolved against the repo root instead of the
  file they sit in, calling two files missing that sat next to the
  README linking them.
Same fix applied to the boilerplate check: a document ABOUT placeholder
detection was tripping the placeholder detector.

Also removed this repo's own static tests badge. There is no CI - the
forge has zero Actions runners registered - so it could never become
real, and it is the exact anti-pattern the gate now flags.

67 tests. Against llm-security every remaining finding is real and
matches the census's independent hand-measurement.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WYJ3FHLtVgzFXMZ6UF598h
This commit is contained in:
Kjell Tore Guttormsen 2026-07-27 16:06:33 +02:00
commit 720850a9ad
7 changed files with 915 additions and 39 deletions

View file

@ -17,6 +17,13 @@ import {
checkFirstScreen,
checkInstallBlock,
checkRequiredFiles,
checkVersionConsistency,
checkHeadings,
checkBadges,
checkBoilerplate,
checkLicenseClaim,
checkInternalLinks,
resolveRelative,
classifyRepo,
levelOf,
} from './repo-standard-check.mjs';
@ -45,15 +52,27 @@ const REGISTER = {
classes: {
plugin: {
required_files: ['README.md', 'LICENSE', 'CHANGELOG.md', '.claude-plugin/plugin.json'],
required_headings: ['## Install', '## Non-goals', '## Changelog'],
install: 'plugin',
},
catalog: {
required_files: ['README.md', 'LICENSE', 'GOVERNANCE.md', 'CONVENTIONS.md', '.claude-plugin/marketplace.json'],
required_headings: ['## Install', '## Non-goals'],
install: 'catalog',
},
'shared-asset': { required_files: ['README.md', 'LICENSE'], install: 'vendor' },
'org-profile': { required_files: ['README.md'], install: 'none' },
standalone: { required_files: ['README.md', 'LICENSE'], install: 'package' },
'shared-asset': { required_files: ['README.md', 'LICENSE'], required_headings: ['## Non-goals'], install: 'vendor' },
'org-profile': { required_files: ['README.md'], required_headings: [], install: 'none' },
standalone: { required_files: ['README.md', 'LICENSE'], required_headings: ['## Install', '## Non-goals'], install: 'package' },
},
traits: {
'llm-security': ['security'],
'llm-ingestion-pipeline-security': ['security'],
},
trait_requirements: {
security: {
required_files: ['SECURITY.md'],
required_headings: ['## Known limitations'],
},
},
description_max_codepoints: 180,
};
@ -367,9 +386,17 @@ test('a fully compliant plugin repo classifies OK', () => {
'',
'Body text.',
'',
'![Version](https://img.shields.io/badge/version-0.7.0-blue)',
'',
'## Install',
`claude plugin marketplace add ${MKT.url}`,
`claude plugin install repo-mailbox@${MKT.name}`,
'',
'## Non-goals',
'It is not a state store.',
'',
'## Changelog',
'See [CHANGELOG.md](CHANGELOG.md).',
].join('\n');
const r = classifyRepo(
{
@ -377,8 +404,293 @@ test('a fully compliant plugin repo classifies OK', () => {
files: { 'README.md': readme },
present: ['README.md', 'LICENSE', 'CHANGELOG.md', '.claude-plugin/plugin.json'],
description: 'A local mailbox for coordination.',
pluginVersion: '0.7.0',
readmeBadge: '0.7.0',
changelogTop: '0.7.0',
tags: ['v0.7.0'],
},
REGISTER,
);
assert.equal(r.status, 'OK');
assert.deepEqual(r.buckets, { broken: 0, missing: 0, weakening: 0 });
});
test('the security trait travels from the register into the verdict', () => {
const r = classifyRepo(
{
name: 'llm-security',
files: { 'README.md': '# llm-security\ndesc\n## Install\n## Non-goals\n## Changelog\n' },
present: ['README.md', 'LICENSE', 'CHANGELOG.md', '.claude-plugin/plugin.json'],
description: 'desc',
},
REGISTER,
);
assert.deepEqual(r.traits, ['security']);
assert.equal(r.findings.some((f) => f.code === 'FILE-MISSING' && f.msg.includes('SECURITY.md')), true);
assert.equal(r.findings.some((f) => f.code === 'HEADING-MISSING' && f.msg.includes('Known limitations')), true);
});
// =========================================================================
// The brief's reporting contract: findings carry a BUCKET alongside a level.
// "broken now" (strangers are blocked or misled), "missing" (an expected
// artefact is absent), "weakening" (present, but reads as amateur). The two
// axes are independent: a weakening finding can still be an ERROR.
// =========================================================================
test('every ERROR and WARN finding carries a bucket', () => {
const readme = ['## Install', `"enabledPlugins": { "okr@${MKT.name}": true }`].join('\n');
const all = [
...checkInstallBlock({ readme, name: 'okr', klass: 'plugin' }, REGISTER),
...checkLinks({ files: { 'README.md': 'https://git.fromaitochitta.com/open/nonesuch' } }, REGISTER),
...checkRequiredFiles({ present: ['README.md'], klass: 'plugin' }, REGISTER),
...checkDescription('', REGISTER),
];
const graded = all.filter((f) => f.level === 'ERROR' || f.level === 'WARN');
assert.ok(graded.length > 0);
for (const f of graded) {
assert.ok(['broken', 'missing', 'weakening'].includes(f.bucket), `${f.code} has bucket ${f.bucket}`);
}
});
test('an unusable install path is `broken`, an absent file is `missing`', () => {
const readme = ['## Install', 'nothing useful here'].join('\n');
const inst = checkInstallBlock({ readme, name: 'okr', klass: 'plugin' }, REGISTER);
assert.equal(inst.find((f) => f.code === 'INSTALL-NO-CLI').bucket, 'broken');
const files = checkRequiredFiles({ present: ['README.md'], klass: 'plugin' }, REGISTER);
assert.equal(files.find((f) => f.code === 'FILE-MISSING').bucket, 'missing');
});
// ------------------------------------------------------- version consistency
test('version consistency: manifest, README badge and CHANGELOG must agree', () => {
const ok = checkVersionConsistency({ pluginVersion: '0.1.0', readmeBadge: '0.1.0', changelogTop: '0.1.0', tags: ['v0.1.0'] });
assert.equal(ok.filter((f) => f.level === 'ERROR').length, 0);
const drift = checkVersionConsistency({ pluginVersion: '0.1.0', readmeBadge: '0.0.9', changelogTop: '0.1.0', tags: ['v0.1.0'] });
assert.equal(drift.some((f) => f.level === 'ERROR' && f.code === 'VERSION-BADGE'), true);
const stale = checkVersionConsistency({ pluginVersion: '0.2.0', readmeBadge: '0.2.0', changelogTop: '0.1.0', tags: ['v0.1.0', 'v0.2.0'] });
assert.equal(stale.some((f) => f.level === 'ERROR' && f.code === 'VERSION-CHANGELOG'), true);
});
test('an untagged repo SKIPs the tag comparison rather than failing it', () => {
// Nothing has been released yet. That is a state, not a defect — and "SKIP is
// never a pass" means it must say so rather than quietly succeed.
const f = checkVersionConsistency({ pluginVersion: '0.1.0', readmeBadge: '0.1.0', changelogTop: '0.1.0', tags: [] });
assert.equal(f.some((x) => x.code === 'VERSION-TAG' && x.level === 'SKIP'), true);
assert.equal(f.some((x) => x.level === 'ERROR'), false);
});
test('a released version with no matching tag is an ERROR', () => {
const f = checkVersionConsistency({ pluginVersion: '0.3.1', readmeBadge: '0.3.1', changelogTop: '0.3.1', tags: ['v0.1.0'] });
assert.equal(f.some((x) => x.level === 'ERROR' && x.code === 'VERSION-TAG'), true);
});
// -------------------------------------------------------- required headings
test('required headings are per class — Non-goals is required, not optional', () => {
const full = '# x\n## Install\n## Non-goals\n## Changelog\n';
assert.equal(checkHeadings({ readme: full, klass: 'plugin' }, REGISTER).filter((f) => f.level === 'ERROR').length, 0);
const noNonGoals = '# x\n## Install\n## Changelog\n';
const f = checkHeadings({ readme: noNonGoals, klass: 'plugin' }, REGISTER);
assert.equal(f.some((x) => x.code === 'HEADING-MISSING' && x.msg.includes('Non-goals')), true);
assert.equal(f.find((x) => x.code === 'HEADING-MISSING').bucket, 'missing');
});
test('org-profile requires no headings at all', () => {
const f = checkHeadings({ readme: '# .profile\nprofile\n', klass: 'org-profile' }, REGISTER);
assert.equal(f.filter((x) => x.level === 'ERROR').length, 0);
});
// ------------------------------------------------------------ badge honesty
test('a static badge asserting test or build status is a finding', () => {
// "tests: 642 passing" as a static image is a claim dressed as evidence.
const f = checkBadges({ readme: '![Tests](https://img.shields.io/badge/tests-34-green)' });
assert.equal(f.some((x) => x.code === 'BADGE-STATIC-CLAIM'), true);
assert.equal(f.find((x) => x.code === 'BADGE-STATIC-CLAIM').bucket, 'weakening');
});
test('static version, licence and platform badges are fine — they assert no run', () => {
const readme = [
'![Version](https://img.shields.io/badge/version-0.1.0-blue)',
'![License](https://img.shields.io/badge/license-MIT-lightgrey)',
'![Platform](https://img.shields.io/badge/platform-Claude_Code_Plugin-purple)',
].join('\n');
assert.equal(checkBadges({ readme }).filter((f) => f.level !== 'OK').length, 0);
});
test('a build badge that links to a real run is fine', () => {
const readme = '[![CI](https://forge.example/repo/badges/workflows/ci.yml/badge.svg)](https://forge.example/repo/actions)';
assert.equal(checkBadges({ readme }).filter((f) => f.level !== 'OK').length, 0);
});
// -------------------------------------------------------------- boilerplate
test('unfinished template text is a finding', () => {
const f = checkBoilerplate({ files: { 'README.md': 'Install your-project-name today' } });
assert.equal(f.some((x) => x.code === 'BOILERPLATE'), true);
const g = checkBoilerplate({ files: { 'CODE_OF_CONDUCT.md': 'Report to [INSERT EMAIL ADDRESS]' } });
assert.equal(g.some((x) => x.code === 'BOILERPLATE'), true);
});
test('ordinary prose is not boilerplate', () => {
const f = checkBoilerplate({ files: { 'README.md': 'This project solves a real problem.' } });
assert.equal(f.filter((x) => x.level !== 'OK').length, 0);
});
// --------------------------------------------------- licence claim vs. file
test('a README that cites a LICENSE the repo does not have is broken', () => {
const f = checkLicenseClaim({ readme: 'Released under the [MIT licence](LICENSE).', present: ['README.md'] });
assert.equal(f.some((x) => x.code === 'LICENSE-CLAIMED-ABSENT' && x.bucket === 'broken'), true);
});
test('a cited LICENSE that exists passes', () => {
const f = checkLicenseClaim({ readme: 'See [LICENSE](LICENSE).', present: ['README.md', 'LICENSE'] });
assert.equal(f.filter((x) => x.level === 'ERROR').length, 0);
});
// ------------------------------------------------------------ internal links
test('a relative link to a file that does not exist is a finding', () => {
const f = checkInternalLinks(
{ files: { 'README.md': 'See [the design](docs/design.md).' }, present: ['README.md'] },
);
assert.equal(f.some((x) => x.code === 'LINK-INTERNAL-MISSING'), true);
});
test('relative links that resolve, anchors and external URLs are left alone', () => {
const f = checkInternalLinks({
files: { 'README.md': '[a](docs/design.md) [b](#section) [c](https://example.com) [d](mailto:x@y.z)' },
present: ['README.md', 'docs/design.md'],
});
assert.equal(f.filter((x) => x.level !== 'OK').length, 0);
});
// ----------------------------------------------------------------- traits
test('the security trait adds a SECURITY.md requirement on top of the class', () => {
const f = checkRequiredFiles({ present: ['README.md', 'LICENSE'], klass: 'standalone', traits: ['security'] }, REGISTER);
assert.equal(f.some((x) => x.code === 'FILE-MISSING' && x.msg.includes('SECURITY.md')), true);
});
test('a repo without the security trait owes no SECURITY.md', () => {
const f = checkRequiredFiles({ present: ['README.md', 'LICENSE'], klass: 'standalone' }, REGISTER);
assert.equal(f.some((x) => x.msg.includes('SECURITY.md')), false);
});
test('the security trait requires limitations to be stated', () => {
const f = checkHeadings({ readme: '# x\n## Install\n## Non-goals\n', klass: 'standalone', traits: ['security'] }, REGISTER);
assert.equal(f.some((x) => x.code === 'HEADING-MISSING' && x.msg.includes('Known limitations')), true);
});
test('CONTRIBUTING and CODE_OF_CONDUCT are required by no class — the maintainer works alone', () => {
for (const klass of Object.keys(REGISTER.classes)) {
const req = REGISTER.classes[klass].required_files;
assert.equal(req.includes('CONTRIBUTING.md'), false);
assert.equal(req.includes('CODE_OF_CONDUCT.md'), false);
assert.equal(req.includes('MAINTAINERS.md'), false);
}
});
// ---------------------------------------------- link check: the noise sources
// All three found by running against llm-security, which produced ~30 false
// positives on the first pass. A check that is wrong this often teaches people
// to ignore it, which is worse than not having it.
test('a regex inside an inline code span is not a markdown link', () => {
// `["']([A-Za-z0-9\-._]{16,64})["']` is `[...](...)` to a naive scanner.
const line = '- **Regex:** `(?i)\\bapi[_\\-]?key\\s*[:=]\\s*["\']([A-Za-z0-9\\-._]{16,64})["\']`';
const f = checkInternalLinks({ files: { 'k.md': line }, present: ['k.md'] });
assert.equal(f.filter((x) => x.level !== 'OK').length, 0);
});
test('fenced code blocks are not scanned for links', () => {
const text = ['```bash', 'curl [x](not-a-real-file.md)', '```'].join('\n');
const f = checkInternalLinks({ files: { 'r.md': text }, present: ['r.md'] });
assert.equal(f.filter((x) => x.level !== 'OK').length, 0);
});
test('any URI scheme is left alone, not just http', () => {
const text = '[a](file:///abs/path.html) [b](ftp://x/y) [c](vscode://z)';
const f = checkInternalLinks({ files: { 'r.md': text }, present: ['r.md'] });
assert.equal(f.filter((x) => x.level !== 'OK').length, 0);
});
test('a path that escapes the repo is unresolvable, not broken', () => {
// Legitimately common: a plugin README pointing up at its marketplace.
// The gate sees one repo, so it cannot judge — and must not pretend to.
const f = checkInternalLinks({ files: { 'README.md': '[d](../../README.md)' }, present: ['README.md'] });
assert.equal(f.some((x) => x.code === 'LINK-OUTSIDE-REPO' && x.level === 'SKIP'), true);
assert.equal(f.some((x) => x.level === 'ERROR'), false);
});
test('a genuinely missing sibling file is still an ERROR', () => {
const f = checkInternalLinks({ files: { 'README.md': '[e](docs/gone.md)' }, present: ['README.md'] });
assert.equal(f.some((x) => x.code === 'LINK-INTERNAL-MISSING' && x.level === 'ERROR'), true);
});
test('a required heading present at the wrong level says so', () => {
// llm-security has `### Install` nested under `## Quick Start`. The contract
// wants it at level 2 — the finding should name that, not just "missing".
const f = checkHeadings({ readme: '# x\n## Quick Start\n### Install\n## Non-goals\n## Changelog\n', klass: 'plugin' }, REGISTER);
const hit = f.find((x) => x.code === 'HEADING-LEVEL');
assert.ok(hit, 'expected a HEADING-LEVEL finding');
assert.match(hit.msg, /### Install/);
});
test('a document ABOUT placeholders does not trip the placeholder detector', () => {
// llm-security documents the very strings it scans for. Inside code spans and
// fenced blocks they are subject matter, not unfinished template text.
const text = [
'Skip if the matched value contains: `your-project-name`, `FIXME`, `changeme`.',
'',
'```bash',
'cd <your-fork>',
'```',
].join('\n');
const f = checkBoilerplate({ files: { 'k.md': text } });
assert.equal(f.filter((x) => x.level !== 'OK').length, 0);
});
test('placeholder text in ordinary prose is still caught', () => {
const f = checkBoilerplate({ files: { 'README.md': 'Install your-project-name to begin.' } });
assert.equal(f.some((x) => x.code === 'BOILERPLATE'), true);
});
// --------------------------------- relative links resolve against their file
test('resolveRelative joins against the containing file, not the repo root', () => {
assert.equal(resolveRelative('examples/demo/README.md', 'expected.md'), 'examples/demo/expected.md');
assert.equal(resolveRelative('README.md', 'docs/design.md'), 'docs/design.md');
assert.equal(resolveRelative('docs/a/b.md', '../c.md'), 'docs/c.md');
assert.equal(resolveRelative('docs/a.md', './b.md'), 'docs/b.md');
});
test('resolveRelative returns null when the path escapes the repo', () => {
assert.equal(resolveRelative('README.md', '../../README.md'), null);
assert.equal(resolveRelative('docs/a.md', '../../../x.md'), null);
assert.equal(resolveRelative('README.md', '/etc/passwd'), null);
});
test('a link from a nested README to its sibling resolves', () => {
// Found against llm-security: both files existed, and the gate called them
// missing because it compared a file-relative target to repo-root paths.
const f = checkInternalLinks({
files: { 'examples/demo/README.md': 'See [findings](expected-findings.md).' },
present: ['examples/demo/README.md', 'examples/demo/expected-findings.md'],
});
assert.equal(f.filter((x) => x.level === 'ERROR').length, 0);
});
test('a nested link to a genuinely absent sibling is still an ERROR', () => {
const f = checkInternalLinks({
files: { 'examples/demo/README.md': 'See [gone](gone.md).' },
present: ['examples/demo/README.md'],
});
assert.equal(f.some((x) => x.code === 'LINK-INTERNAL-MISSING'), true);
});