feat(register): a decided title, and an org-profile that stops lying
Three changes at the register/engine boundary, all agreed with org-ops after census 05 and all about a check missing a place to record a legitimate exception. `titles`: an optional per-repo README title. Set, the H1 matches it and is OK; unset, the WARN stands as before. A human title was already this engine's stated position and rds-v1's prescription, but a decided YES had nowhere to live, so the same 6 WARNs were reported three censuses running and would have been reported forever. Five registered, each H1 read from the repo rather than copied from the census; `ai-psychosis` deliberately left out so the one repo where a reader cannot connect title to name stands alone. Measured across 21 local clones: 6 WARN before, 1 after, nothing else moved. `readme_desc_match: false` on the org-profile class: for an ordinary repo the README opening and the forge description describe the same subject and equality is right; for this class they do not — the README is the org's landing page, the forge text describes the repo. The equality is what does not apply, not either text. Class data, not a hardcoded name, and the exemption is RECORDED as an OK naming its reason, not dropped. `.profile` went ERROR to 0 ERROR / 0 WARN; the same README under a plugin class is still an ERROR. `engineCommit`: the version names a file, only the sha names the code. A sweep stamped 18 files 0.4.0 while four carried a 0.5.0-only finding — feature and version bump are two commits, so the stamp lied without being broken. Present-and-null when underivable, never absent: an absent key means an older engine, null means this one ran without a HEAD to read. 135 to 147 tests. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NELsvPY5gnJjN3esdhWYWC
This commit is contained in:
parent
a7276e6f78
commit
5884a64e54
8 changed files with 358 additions and 27 deletions
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "repo-standard",
|
||||
"version": "0.5.0",
|
||||
"version": "0.6.0",
|
||||
"description": "Per-repo gate for the open/ presentation standard: README first screen, install block, files required by the repo's class, and dead repo references.",
|
||||
"author": {
|
||||
"name": "Kjell Tore Guttormsen"
|
||||
|
|
|
|||
42
CHANGELOG.md
42
CHANGELOG.md
|
|
@ -4,6 +4,48 @@ 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.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
|
||||
|
|
|
|||
27
CLAUDE.md
27
CLAUDE.md
|
|
@ -93,13 +93,38 @@ would recreate, in data, exactly the drift this plugin exists to remove.
|
|||
- **A repo's name is its remote, not its directory.** `catalog/` holds
|
||||
`ktg-plugin-marketplace`. The basename left it unregistered with zero checks
|
||||
run, against the one repo every catalog rule depends on.
|
||||
- **A decision needs somewhere to live, or the gate repeats itself forever.**
|
||||
The engine already held that a human README title is the operator's call —
|
||||
and still warned about it every round, because a YES could not be recorded.
|
||||
Six warnings, unchanged across censuses 05, 06 and 07. `titles` in the
|
||||
register is that record: set, the H1 matching it is `OK`; unset, the `WARN`
|
||||
stands. What the gate must never do is make "we decided this" and "nobody
|
||||
looked" the same output. The wanted side effect is exposure, not silence —
|
||||
`ai-psychosis` is deliberately unregistered so it stands alone.
|
||||
- **An exemption is a finding, not a deletion.** `readme_desc_match: false`
|
||||
turns off README-DESC equality for a class, and the check still emits an `OK`
|
||||
naming why. An exception nobody can see reads exactly like a check that
|
||||
silently stopped running.
|
||||
- **Class rules live in the register, never as a class name in the engine.**
|
||||
The `org-profile` exemption is a flag on the class, not `if (klass ===
|
||||
'org-profile')`. Per-repo copies of a rule are the drift this plugin exists
|
||||
to remove; a class name hardcoded in a classifier is the same defect one
|
||||
level up.
|
||||
- **The version names a file; only the sha names the code.** `engineVersion`
|
||||
was added because a stale cache served an old engine silently — but a feature
|
||||
and its version bump are two commits, so a worktree carries new behaviour
|
||||
under the old number for a window. Measured: a sweep stamped 18 raw files
|
||||
`0.4.0`, four of them holding findings from a check that only exists in
|
||||
`0.5.0`. `engineCommit` closes that, derived from the same checkout with no
|
||||
network call. It is present-and-`null` when underivable, never absent — an
|
||||
absent key means an older engine, `null` means this one ran without a HEAD.
|
||||
- **No hook until the rule is precise.** A blocking gate that fails a correct
|
||||
repository is the mechanism that gets gates switched off.
|
||||
|
||||
## Commands
|
||||
|
||||
```bash
|
||||
npm test # 135 tests
|
||||
npm test # 147 tests
|
||||
node scripts/repo-standard-check.mjs --dir "$PWD" # gate one repo
|
||||
node scripts/repo-standard-check.mjs --offline # no network call
|
||||
node scripts/repo-standard-check.mjs --json # machine output
|
||||
|
|
|
|||
59
README.md
59
README.md
|
|
@ -10,7 +10,7 @@ checks that surface in one repository and reports what it finds.
|
|||
|
||||
*AI-generated: all code produced by Claude Code through dialog-driven development.*
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||

|
||||
|
|
@ -108,6 +108,39 @@ 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.
|
||||
|
||||
### Registered titles — where a decision gets written down
|
||||
|
||||
A README H1 that is a human title rather than the repo name is a naming choice
|
||||
the maintainer owns, and the standard prescribes `# Title` for every class. But
|
||||
until a YES could be *recorded*, the gate reported the same six warnings every
|
||||
round, and there was no way to tell "this was decided" from "nobody has looked".
|
||||
|
||||
An optional `titles` entry in the register is that record. Set it, and an H1
|
||||
matching it is an `OK`. Leave it out, and the warning stands exactly as before.
|
||||
The point is the asymmetry: once the deliberate titles are registered, the repos
|
||||
nobody has ruled on are the only ones still warning.
|
||||
|
||||
An H1 that matches neither the repo name nor a registered title is still a
|
||||
warning, and the message names both — that is drift in one of the two, not a
|
||||
title.
|
||||
|
||||
### When equality is the wrong demand
|
||||
|
||||
`README-DESC` requires the README's opening line to be the forge description
|
||||
verbatim, because for an ordinary repo the two describe the same subject and any
|
||||
gap between them is drift on a published surface.
|
||||
|
||||
The `org-profile` class is the one place that premise fails. Its README is the
|
||||
organisation's landing page; the forge description describes the repository.
|
||||
Both are correct about their own subject, so it is the *equality* that does not
|
||||
apply, not either text — and a landing page's opening line carries a link, while
|
||||
the description field renders as plain text, so satisfying the old check would
|
||||
have meant putting raw markdown on a real surface.
|
||||
|
||||
A class turns the demand off with `readme_desc_match: false` in the register.
|
||||
The exemption is still reported as a finding that names the reason: an exception
|
||||
nobody can see is indistinguishable from a check that silently stopped running.
|
||||
|
||||
### What is deliberately not required
|
||||
|
||||
`CONTRIBUTING.md`, `CODE_OF_CONDUCT.md` and `MAINTAINERS.md` are required by no
|
||||
|
|
@ -147,6 +180,28 @@ 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.
|
||||
|
||||
### Every run says which engine produced it
|
||||
|
||||
The header and the `--json` output carry the engine's version *and* the commit
|
||||
it ran from:
|
||||
|
||||
```
|
||||
✓ voyage [plugin] — OK (repo-standard v0.6.0 @a7276e6)
|
||||
```
|
||||
|
||||
Both, because they answer different questions. A stale plugin cache once served
|
||||
an old engine against a current repo and the output looked like a clean pass,
|
||||
which is why the version is there. But the version names a *file*: a feature and
|
||||
its version bump are two commits, so a worktree holds new behaviour under the
|
||||
old number for a window, and a sweep once stamped eighteen files with a version
|
||||
that predated the check that produced their findings. Only the commit names the
|
||||
code.
|
||||
|
||||
The commit is read from the engine's own checkout, with no network call. When
|
||||
there is none to read — a vendored copy, an unpacked tarball — the JSON field is
|
||||
present and `null` rather than missing, so "this engine could not tell you" stays
|
||||
distinct from "an older engine that never tried".
|
||||
|
||||
## Non-goals
|
||||
|
||||
- **Anything requiring a view across every repository at once.** This gate sees
|
||||
|
|
@ -167,7 +222,7 @@ without that, a raw scan turns three dead names into about twenty.
|
|||
npm test
|
||||
```
|
||||
|
||||
135 tests over the pure classifiers. The reference fixtures are measured false
|
||||
147 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
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "repo-standard",
|
||||
"version": "0.5.0",
|
||||
"version": "0.6.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"engines": {
|
||||
|
|
|
|||
|
|
@ -71,7 +71,18 @@
|
|||
"",
|
||||
"Consumer-facing documentation is unaffected by working alone, and that is",
|
||||
"the whole distinction: SECURITY.md, LICENSE, CHANGELOG, non-goals and honest",
|
||||
"limitations exist for the reader, not for a contributor."
|
||||
"limitations exist for the reader, not for a contributor.",
|
||||
"",
|
||||
"`readme_desc_match: false` turns OFF the README-opening == forge-description",
|
||||
"equality for a class. Default is on, and it is right for an ordinary repo:",
|
||||
"both texts describe the SAME subject there. `org-profile` is the one class",
|
||||
"where they do not — its README is the ORGANISATION's landing page and the",
|
||||
"forge text describes the REPO. Both are correct about their own subject, so",
|
||||
"it is the equality that does not apply, not either text. A landing page's",
|
||||
"opening line also carries a markdown link, and the description field renders",
|
||||
"as PLAIN TEXT: passing the check would mean putting raw markdown on a real",
|
||||
"surface — trading a census ERROR for a visible defect. One member, so the",
|
||||
"exemption removes coverage nowhere else."
|
||||
],
|
||||
"classes": {
|
||||
"plugin": {
|
||||
|
|
@ -92,7 +103,8 @@
|
|||
"org-profile": {
|
||||
"required_files": ["README.md"],
|
||||
"required_headings": [],
|
||||
"install": "none"
|
||||
"install": "none",
|
||||
"readme_desc_match": false
|
||||
},
|
||||
"standalone": {
|
||||
"required_files": ["README.md", "LICENSE"],
|
||||
|
|
@ -101,6 +113,36 @@
|
|||
}
|
||||
},
|
||||
|
||||
"$comment_titles": [
|
||||
"Where a decided YES about a README H1 gets written down. A human title is a",
|
||||
"valid choice — `docs/rds-v1.md` prescribes `1. # Title` for every class, not",
|
||||
"`# repo-name` — but until this map existed the decision had nowhere to live,",
|
||||
"so census 05, 06 and 07 would have reported the same 6 WARNs forever.",
|
||||
"",
|
||||
"Set: the H1 is compared against it and a match is OK. Unset: WARN, exactly as",
|
||||
"before. That makes 'we decided this is correct' and 'nobody has looked at it'",
|
||||
"two different outcomes instead of one.",
|
||||
"",
|
||||
"PROPOSED BY THE OPERATOR, NOT MEASURED — like `traits` and `locales`, this is",
|
||||
"a judgement. The five below are not one phenomenon: `.profile` CANNOT be",
|
||||
"`# .profile` (the name is Forgejo's mechanism name for the org-profile repo),",
|
||||
"`llm-ingestion-guard` is not a title at all but the published package name",
|
||||
"(it is in `non_repos` above for the same reason), and the three plugins are",
|
||||
"one consistent product convention. Each H1 was read from the repo, not",
|
||||
"copied from a census.",
|
||||
"",
|
||||
"`ai-psychosis` (`# Interaction Awareness`) is DELIBERATELY ABSENT: it is the",
|
||||
"one where a reader cannot connect the title to the repo. Leaving it standing",
|
||||
"alone is the wanted effect of registering the others."
|
||||
],
|
||||
"titles": {
|
||||
".profile": "fromaitochitta / open",
|
||||
"llm-ingestion-pipeline-security": "llm-ingestion-guard",
|
||||
"linkedin-studio": "LinkedIn Studio Plugin for Claude Code",
|
||||
"llm-security": "LLM Security Plugin for Claude Code",
|
||||
"ms-ai-architect": "AI Architect Plugin for Claude Code"
|
||||
},
|
||||
|
||||
"$comment_traits": [
|
||||
"A SECOND axis, orthogonal to class. Class is structural (read off the",
|
||||
"catalog and the remotes); a trait is about what the code DOES, which no",
|
||||
|
|
|
|||
|
|
@ -37,6 +37,23 @@ export function readEngineVersion(path = PACKAGE_PATH) {
|
|||
return JSON.parse(readFileSync(path, 'utf8')).version;
|
||||
}
|
||||
|
||||
// The version names a FILE; only the sha names the CODE. A sweep once stamped
|
||||
// 18 raw files `0.4.0` while four of them 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. Derived from
|
||||
// this checkout, no network call; null outside a git checkout (a vendored copy
|
||||
// or a tarball has no HEAD, and that is not a crash).
|
||||
export function readEngineCommit(dir = join(HERE, '..')) {
|
||||
try {
|
||||
return execFileSync('git', ['-C', dir, 'rev-parse', 'HEAD'], {
|
||||
encoding: 'utf8',
|
||||
stdio: ['ignore', 'pipe', 'ignore'],
|
||||
}).trim() || null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
const LEVELS = ['OK', 'SKIP', 'WARN', 'ERROR'];
|
||||
|
||||
// Findings carry a level AND a bucket, and the two are independent axes.
|
||||
|
|
@ -189,7 +206,7 @@ export function checkDescription(description, register) {
|
|||
// The opening line makes description == catalog == README: the same thread on a
|
||||
// third surface, and the only one of the three a machine can check from inside
|
||||
// the repo.
|
||||
export function checkFirstScreen({ readme, name, description }) {
|
||||
export function checkFirstScreen({ readme, name, description, klass }, register) {
|
||||
const findings = [];
|
||||
const lines = String(readme ?? '').split('\n');
|
||||
const firstIdx = lines.findIndex((l) => l.trim() !== '');
|
||||
|
|
@ -211,15 +228,47 @@ export function checkFirstScreen({ readme, name, description }) {
|
|||
});
|
||||
return findings;
|
||||
}
|
||||
if (heading !== `# ${name}`) {
|
||||
|
||||
// A registered title is where that operator call gets WRITTEN DOWN. Without
|
||||
// one, the same WARN reappears every census and "we decided this is correct"
|
||||
// is indistinguishable from "nobody has looked". With one, the two separate —
|
||||
// and a repo whose title nobody has ruled on is left standing alone, which is
|
||||
// the wanted side effect, not a cost.
|
||||
const title = register?.titles?.[name];
|
||||
if (heading === `# ${name}`) {
|
||||
findings.push({ level: 'OK', code: 'README-H1', msg: `H1 is \`# ${name}\`` });
|
||||
} else if (title && heading === `# ${title}`) {
|
||||
findings.push({ level: 'OK', code: 'README-H1', msg: `H1 is \`# ${title}\` — the registered title for \`${name}\`` });
|
||||
} else if (title) {
|
||||
findings.push({
|
||||
level: 'WARN',
|
||||
code: 'README-H1',
|
||||
bucket: 'weakening',
|
||||
msg: `H1 is \`${heading}\`, not \`# ${name}\` — deliberate title, or drift? Operator's call.`,
|
||||
msg: `H1 is \`${heading}\`, which is neither \`# ${name}\` nor the registered title \`${title}\` — one of the two has drifted.`,
|
||||
});
|
||||
} else {
|
||||
findings.push({ level: 'OK', code: 'README-H1', msg: `H1 is \`# ${name}\`` });
|
||||
findings.push({
|
||||
level: 'WARN',
|
||||
code: 'README-H1',
|
||||
bucket: 'weakening',
|
||||
msg: `H1 is \`${heading}\`, not \`# ${name}\` — deliberate title, or drift? Operator's call. Record the decision as \`titles.${name}\` in the register.`,
|
||||
});
|
||||
}
|
||||
|
||||
// For an ordinary repo the README opening and the forge description describe
|
||||
// the SAME subject, and equality is the right demand. `org-profile` is the one
|
||||
// class where they do not: its README is the organisation's landing page and
|
||||
// the forge text describes the repo. Both are correct about their own subject,
|
||||
// so it is the EQUALITY that does not apply — and a landing page's opening
|
||||
// link could only ever match by putting raw markdown on a plain-text surface.
|
||||
// Class-level data, not a hardcoded name: class rules live in the register.
|
||||
if (register?.classes?.[klass]?.readme_desc_match === false) {
|
||||
findings.push({
|
||||
level: 'OK',
|
||||
code: 'README-DESC',
|
||||
msg: `class \`${klass}\` is exempt: the README describes the org, the forge description describes the repo — different subjects, so equality is not required`,
|
||||
});
|
||||
return findings;
|
||||
}
|
||||
|
||||
if (description === null || description === undefined) {
|
||||
|
|
@ -883,7 +932,7 @@ export function classifyRepo(
|
|||
const traits = register.traits?.[name] ?? [];
|
||||
const readme = (files ?? {})['README.md'] ?? '';
|
||||
const findings = [
|
||||
...checkFirstScreen({ readme, name, description }),
|
||||
...checkFirstScreen({ readme, name, description, klass }, register),
|
||||
...checkInstallBlock({ readme, name, klass }, register),
|
||||
...checkInstallTruth({ name, klass, catalogNames }),
|
||||
...checkHeadings({ readme, klass, traits }, register),
|
||||
|
|
@ -1106,19 +1155,23 @@ const BUCKET_TITLE = {
|
|||
// engine is visible, not just correctable in hindsight.
|
||||
const MARK = { OK: '✓', WARN: '!', ERROR: '✗', SKIP: '·' };
|
||||
|
||||
export function headerLine(result, engineVersion) {
|
||||
export function headerLine(result, engineVersion, engineCommit = null) {
|
||||
const klass = result.klass ? ` [${result.klass}]` : '';
|
||||
const traits = result.traits?.length ? ` {${result.traits.join(', ')}}` : '';
|
||||
return `${MARK[result.status]} ${result.name}${klass}${traits} — ${result.status} (repo-standard v${engineVersion})`;
|
||||
const sha = engineCommit ? ` @${String(engineCommit).slice(0, 7)}` : '';
|
||||
return `${MARK[result.status]} ${result.name}${klass}${traits} — ${result.status} (repo-standard v${engineVersion}${sha})`;
|
||||
}
|
||||
|
||||
export function withEngineVersion(result, engineVersion) {
|
||||
return { ...result, engineVersion };
|
||||
// `engineCommit` is always present, null when underivable: an ABSENT key means
|
||||
// an older engine, an explicit null means this engine ran and had no HEAD to
|
||||
// read. A consumer sorting raw files by stamp needs those to be different.
|
||||
export function withEngineVersion(result, engineVersion, engineCommit = null) {
|
||||
return { ...result, engineVersion, engineCommit };
|
||||
}
|
||||
|
||||
function render(result, engineVersion) {
|
||||
function render(result, engineVersion, engineCommit) {
|
||||
const mark = MARK;
|
||||
console.log(`\n${headerLine(result, engineVersion)}`);
|
||||
console.log(`\n${headerLine(result, engineVersion, engineCommit)}`);
|
||||
|
||||
for (const bucket of BUCKETS) {
|
||||
const inBucket = result.findings.filter((f) => f.bucket === bucket);
|
||||
|
|
@ -1182,11 +1235,12 @@ async function main(argv) {
|
|||
|
||||
const result = inspectRepo(dir, name, register, description, catalogNames);
|
||||
const engineVersion = readEngineVersion();
|
||||
const engineCommit = readEngineCommit();
|
||||
|
||||
if (argv.includes('--json')) {
|
||||
console.log(JSON.stringify(withEngineVersion(result, engineVersion), null, 2));
|
||||
console.log(JSON.stringify(withEngineVersion(result, engineVersion, engineCommit), null, 2));
|
||||
} else {
|
||||
render(result, engineVersion);
|
||||
render(result, engineVersion, engineCommit);
|
||||
}
|
||||
process.exit(result.status === 'ERROR' ? 1 : 0);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ import {
|
|||
fetchWithRetry,
|
||||
headerLine,
|
||||
withEngineVersion,
|
||||
readEngineCommit,
|
||||
loadRegister,
|
||||
} from './repo-standard-check.mjs';
|
||||
|
||||
|
|
@ -69,9 +70,12 @@ const REGISTER = {
|
|||
install: 'catalog',
|
||||
},
|
||||
'shared-asset': { required_files: ['README.md', 'LICENSE'], required_headings: ['## Non-goals'], install: 'vendor' },
|
||||
'org-profile': { required_files: ['README.md'], required_headings: [], install: 'none' },
|
||||
'org-profile': { required_files: ['README.md'], required_headings: [], install: 'none', readme_desc_match: false },
|
||||
standalone: { required_files: ['README.md', 'LICENSE'], required_headings: ['## Install', '## Non-goals'], install: 'package' },
|
||||
},
|
||||
titles: {
|
||||
'llm-security': 'LLM Security Plugin for Claude Code',
|
||||
},
|
||||
traits: {
|
||||
'llm-security': ['security'],
|
||||
'llm-ingestion-pipeline-security': ['security'],
|
||||
|
|
@ -306,7 +310,7 @@ test('an unavailable description is SKIP, never a pass', () => {
|
|||
test('README line 1 must be the H1, and the description line must match the forge', () => {
|
||||
const readme = '# repo-mailbox\nA local mailbox for coordination.\n';
|
||||
assert.equal(
|
||||
checkFirstScreen({ readme, name: 'repo-mailbox', description: 'A local mailbox for coordination.' })
|
||||
checkFirstScreen({ readme, name: 'repo-mailbox', description: 'A local mailbox for coordination.' }, REGISTER)
|
||||
.every((f) => f.level === 'OK'),
|
||||
true,
|
||||
);
|
||||
|
|
@ -314,17 +318,17 @@ test('README line 1 must be the H1, and the description line must match the forg
|
|||
|
||||
test('a README whose opening line diverges from the description is an ERROR', () => {
|
||||
const readme = '# repo-mailbox\nSomething else entirely.\n';
|
||||
const f = checkFirstScreen({ readme, name: 'repo-mailbox', description: 'A local mailbox for coordination.' });
|
||||
const f = checkFirstScreen({ readme, name: 'repo-mailbox', description: 'A local mailbox for coordination.' }, REGISTER);
|
||||
assert.equal(f.some((x) => x.level === 'ERROR' && x.code === 'README-DESC'), true);
|
||||
});
|
||||
|
||||
test('first-screen description match is SKIP when the forge text is unavailable', () => {
|
||||
const f = checkFirstScreen({ readme: '# x\nbody\n', name: 'x', description: null });
|
||||
const f = checkFirstScreen({ readme: '# x\nbody\n', name: 'x', description: null }, REGISTER);
|
||||
assert.equal(f.some((x) => x.code === 'README-DESC' && x.level === 'SKIP'), true);
|
||||
});
|
||||
|
||||
test('a missing H1 is an ERROR', () => {
|
||||
const f = checkFirstScreen({ readme: 'no heading here\n', name: 'x', description: null });
|
||||
const f = checkFirstScreen({ readme: 'no heading here\n', name: 'x', description: null }, REGISTER);
|
||||
assert.equal(f.some((x) => x.level === 'ERROR' && x.code === 'README-H1'), true);
|
||||
});
|
||||
|
||||
|
|
@ -332,16 +336,84 @@ test('an H1 that differs from the repo name is a WARN, not a failure', () => {
|
|||
// `# OKR for Public Sector` is a naming choice, not a defect: the thread that
|
||||
// must hold is description == catalog == opening line, and the H1 is none of
|
||||
// those three. Surface it; let the operator decide.
|
||||
const f = checkFirstScreen({ readme: '# OKR for Public Sector\nbody\n', name: 'okr', description: null });
|
||||
const f = checkFirstScreen({ readme: '# OKR for Public Sector\nbody\n', name: 'okr', description: null }, REGISTER);
|
||||
assert.equal(f.some((x) => x.level === 'WARN' && x.code === 'README-H1'), true);
|
||||
assert.equal(f.some((x) => x.level === 'ERROR'), false);
|
||||
});
|
||||
|
||||
test('a differing H1 does not stop the description check from running', () => {
|
||||
const f = checkFirstScreen({ readme: '# Nice Title\nthe description\n', name: 'x', description: 'the description' });
|
||||
const f = checkFirstScreen({ readme: '# Nice Title\nthe description\n', name: 'x', description: 'the description' }, REGISTER);
|
||||
assert.equal(f.some((x) => x.code === 'README-DESC' && x.level === 'OK'), true);
|
||||
});
|
||||
|
||||
// ------------------------------------------------- registered README titles
|
||||
// Census 05 measured the same 6 README-H1 WARNs three rounds running, because
|
||||
// a decided YES had nowhere to live. `titles` is that place: with an entry the
|
||||
// H1 is OK, without one it WARNs exactly as before — so "we decided this" and
|
||||
// "nobody has looked at it" stop sharing one outcome. The wanted side effect is
|
||||
// that a repo nobody has ruled on stands alone once the others are registered.
|
||||
|
||||
test('an H1 matching the registered title is OK, not a WARN', () => {
|
||||
const readme = '# LLM Security Plugin for Claude Code\nbody\n';
|
||||
const f = checkFirstScreen({ readme, name: 'llm-security', description: null }, REGISTER);
|
||||
assert.equal(f.some((x) => x.code === 'README-H1' && x.level === 'OK'), true);
|
||||
assert.equal(f.some((x) => x.code === 'README-H1' && x.level === 'WARN'), false);
|
||||
});
|
||||
|
||||
test('a repo with no registered title still WARNs — deciding and not looking stay different outcomes', () => {
|
||||
const f = checkFirstScreen({ readme: '# Interaction Awareness\nbody\n', name: 'repo-mailbox', description: null }, REGISTER);
|
||||
assert.equal(f.some((x) => x.code === 'README-H1' && x.level === 'WARN'), true);
|
||||
});
|
||||
|
||||
test('an H1 matching neither the name nor the registered title WARNs, and the message names both', () => {
|
||||
const f = checkFirstScreen({ readme: '# Something Drifted\nbody\n', name: 'llm-security', description: null }, REGISTER);
|
||||
const warn = f.find((x) => x.code === 'README-H1' && x.level === 'WARN');
|
||||
assert.ok(warn, 'expected a README-H1 WARN');
|
||||
assert.match(warn.msg, /Something Drifted/);
|
||||
assert.match(warn.msg, /LLM Security Plugin for Claude Code/);
|
||||
});
|
||||
|
||||
test('the repo name still passes when a title is registered — both spellings are accepted', () => {
|
||||
const f = checkFirstScreen({ readme: '# llm-security\nbody\n', name: 'llm-security', description: null }, REGISTER);
|
||||
assert.equal(f.some((x) => x.code === 'README-H1' && x.level === 'OK'), true);
|
||||
});
|
||||
|
||||
// --------------------------------------------- org-profile README-DESC exemption
|
||||
// For an ordinary repo the README opening and the forge description describe the
|
||||
// SAME subject, and equality is the right demand. For `org-profile` they do not:
|
||||
// the README is the organisation's landing page, the forge description describes
|
||||
// the repo. Both texts are correct about their own subject. The description field
|
||||
// also renders as PLAIN TEXT, so a landing page's opening link could only ever
|
||||
// match by putting raw markdown on a real surface.
|
||||
const PROFILE_README = '# .profile\nCompanion repos, plugins, and tools for [From AI to Chitta](https://fromaitochitta.com).\n';
|
||||
|
||||
test('org-profile is exempt from the README-DESC equality check', () => {
|
||||
const f = checkFirstScreen(
|
||||
{ readme: PROFILE_README, name: '.profile', klass: 'org-profile', description: 'Organization profile and navigation for the open org.' },
|
||||
REGISTER,
|
||||
);
|
||||
const desc = f.find((x) => x.code === 'README-DESC');
|
||||
assert.ok(desc, 'expected a README-DESC finding — an exemption must still be recorded');
|
||||
assert.equal(desc.level, 'OK');
|
||||
assert.equal(f.some((x) => x.level === 'ERROR'), false);
|
||||
});
|
||||
|
||||
test('the exemption is per class, not global — the same README under a plugin class is still an ERROR', () => {
|
||||
const f = checkFirstScreen(
|
||||
{ readme: PROFILE_README, name: 'llm-security', klass: 'plugin', description: 'Organization profile and navigation for the open org.' },
|
||||
REGISTER,
|
||||
);
|
||||
assert.equal(f.some((x) => x.code === 'README-DESC' && x.level === 'ERROR'), true);
|
||||
});
|
||||
|
||||
test('the exemption does not silence the H1 check for org-profile', () => {
|
||||
const f = checkFirstScreen(
|
||||
{ readme: 'no heading here\n', name: '.profile', klass: 'org-profile', description: 'anything' },
|
||||
REGISTER,
|
||||
);
|
||||
assert.equal(f.some((x) => x.code === 'README-H1' && x.level === 'ERROR'), true);
|
||||
});
|
||||
|
||||
// ------------------------------------------------------------ install block
|
||||
|
||||
const MKT = REGISTER.marketplace;
|
||||
|
|
@ -1390,3 +1462,44 @@ test('withEngineVersion adds the version without disturbing existing fields', ()
|
|||
assert.equal(stamped.status, 'OK');
|
||||
assert.deepEqual(result, { name: 'x', klass: 'plugin', status: 'OK', findings: [] });
|
||||
});
|
||||
|
||||
// ---------------------------------------------------- engine commit stamp
|
||||
// Measured 2026-08-09 by org-ops: a sweep stamped 18 raw files `0.4.0`, and
|
||||
// four of them carried findings from a check that only exists in 0.5.0. The
|
||||
// stamp lied without being broken — `9eb210b` (the feature) and `63c75d8` (the
|
||||
// version bump) are TWO commits, so the worktree held new code under an old
|
||||
// number for a window. The version names a FILE; only the sha names the CODE,
|
||||
// and the bump is by definition a different commit from the change it describes.
|
||||
|
||||
test('headerLine names the engine commit alongside the version', () => {
|
||||
const line = headerLine({ name: 'voyage', klass: 'plugin', traits: [], status: 'OK' }, '0.5.0', 'a7276e6f0d1e2b3c');
|
||||
assert.match(line, /0\.5\.0/);
|
||||
assert.match(line, /a7276e6/);
|
||||
});
|
||||
|
||||
test('headerLine falls back to the version alone when the commit is underivable', () => {
|
||||
const line = headerLine({ name: 'voyage', klass: 'plugin', traits: [], status: 'OK' }, '0.5.0', null);
|
||||
assert.match(line, /0\.5\.0/);
|
||||
assert.doesNotMatch(line, /null|undefined/);
|
||||
});
|
||||
|
||||
test('withEngineVersion stamps the commit next to the version', () => {
|
||||
const stamped = withEngineVersion({ name: 'x', status: 'OK', findings: [] }, '0.5.0', 'a7276e6f0d1e2b3c');
|
||||
assert.equal(stamped.engineVersion, '0.5.0');
|
||||
assert.equal(stamped.engineCommit, 'a7276e6f0d1e2b3c');
|
||||
});
|
||||
|
||||
test('engineCommit is present-and-null when underivable, never absent', () => {
|
||||
// An ABSENT key means an older engine; an explicit null means this engine ran
|
||||
// and could not derive the sha. A consumer sorting raw files by stamp needs
|
||||
// those two to be different, which is the whole point of the field.
|
||||
const stamped = withEngineVersion({ name: 'x', status: 'OK', findings: [] }, '0.5.0', null);
|
||||
assert.equal(Object.hasOwn(stamped, 'engineCommit'), true);
|
||||
assert.equal(stamped.engineCommit, null);
|
||||
});
|
||||
|
||||
test('readEngineCommit returns null outside a git checkout instead of throwing', () => {
|
||||
// Derived from the engine's own checkout with no network call. A consumer of
|
||||
// a tarball or a vendored copy has no HEAD, and that is not a crash.
|
||||
assert.equal(readEngineCommit('/nonexistent-path-for-repo-standard-test'), null);
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue