fix(gate): a differing README H1 is a WARN, not an ERROR

Measured against the live repos: okr opens `# OKR for Public Sector`
and claude-design `# Claude Design Facilitator`. Neither breaks the
thread the contract exists to protect - description == catalog ==
opening line - because the H1 is none of those three. Failing them
would be the gate that stops a correct repo, which is what teaches
people to switch gates off.

A missing H1 stays an ERROR, and a differing one no longer short-
circuits the description check.

Validation against three cases the census measured by hand, all
reproduced independently: okr (neither install line), claude-design
(slash form, no marketplace add), repo-mailbox (install correct,
first screen wrong). 34 tests.

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 09:14:33 +02:00
commit 2357771587
2 changed files with 34 additions and 4 deletions

View file

@ -120,15 +120,31 @@ export function checkFirstScreen({ readme, name, description }) {
const lines = String(readme ?? '').split('\n');
const firstIdx = lines.findIndex((l) => l.trim() !== '');
if (firstIdx === -1 || lines[firstIdx].trim() !== `# ${name}`) {
const heading = firstIdx === -1 ? null : lines[firstIdx].trim();
// No heading at all is broken. A heading that merely differs from the repo
// name is not: the thread that has to hold is description == catalog ==
// opening line, and the H1 is none of those three. A human title like
// `# OKR for Public Sector` is a naming choice the operator owns, so it is
// surfaced and left to them — a gate that fails a correct repo is the
// mechanism that gets gates switched off.
if (heading === null || !heading.startsWith('# ')) {
findings.push({
level: 'ERROR',
code: 'README-H1',
msg: `README line 1 must be \`# ${name}\` (found: ${firstIdx === -1 ? '<empty file>' : `\`${lines[firstIdx].trim()}\``})`,
msg: `README must open with an H1 (expected \`# ${name}\`, found: ${heading === null ? '<empty file>' : `\`${heading}\``})`,
});
return findings;
}
findings.push({ level: 'OK', code: 'README-H1', msg: `H1 is \`# ${name}\`` });
if (heading !== `# ${name}`) {
findings.push({
level: 'WARN',
code: 'README-H1',
msg: `H1 is \`${heading}\`, not \`# ${name}\` — deliberate title, or drift? Operator's call.`,
});
} else {
findings.push({ level: 'OK', code: 'README-H1', msg: `H1 is \`# ${name}\`` });
}
if (description === null || description === undefined) {
findings.push({ level: 'SKIP', code: 'README-DESC', msg: 'forge description not available — opening-line match not checked' });

View file

@ -192,11 +192,25 @@ test('first-screen description match is SKIP when the forge text is unavailable'
assert.equal(f.some((x) => x.code === 'README-DESC' && x.level === 'SKIP'), true);
});
test('a wrong or missing H1 is an ERROR', () => {
test('a missing H1 is an ERROR', () => {
const f = checkFirstScreen({ readme: 'no heading here\n', name: 'x', description: null });
assert.equal(f.some((x) => x.level === 'ERROR' && x.code === 'README-H1'), true);
});
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 });
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' });
assert.equal(f.some((x) => x.code === 'README-DESC' && x.level === 'OK'), true);
});
// ------------------------------------------------------------ install block
const MKT = REGISTER.marketplace;