feat(engine): per-repo heading alias closes the Non-goals/nb-repo gap

The Non-goals contract is one literal English heading, matched
case-insensitively but never translated — a repo whose readers were
declared `nb` in `locales` could only go green by planting an English
heading inside an otherwise-Norwegian document. Measured on
ki-produktivitetsmodell (order, census 09): `## Virkeområde og
forbehold` already does the job Non-goals exists for, HEADING-MISSING
fired anyway. `heading_aliases` in the register is the same shape
`titles` already is for the README H1 — decision in the repo,
bookkeeping here — keyed per repo so two nb-repos need not share a
Norwegian phrasing. Satisfying a requirement through it is its own OK
(HEADING-ALIAS), never folded silently into the aggregate.

Also books two operator title decisions verified against the live
clones (not the coord messages that reported them): .profile's H1
became "From AI to Chitta — open" (org landing page rebuild, commit
9898a6e), and ki-produktivitetsmodell's H1 "Tre nivå av organisatorisk
produktivitet med KI" is deliberate, not drift.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Gh6T6iHHkMvgeBbGaLmLQD
This commit is contained in:
Kjell Tore Guttormsen 2026-08-20 23:14:05 +02:00
commit 45a2bf30de
5 changed files with 118 additions and 6 deletions

View file

@ -225,6 +225,21 @@ would recreate, in data, exactly the drift this plugin exists to remove.
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.
- **A fixed-language contract has a blind spot the `titles` pattern already
knows how to close.** `## Non-goals` is one literal English string, matched
case-insensitively but not translated, so it cannot be satisfied by a repo
whose readers were declared `nb` in `locales` — the only way to go green was
planting an English heading inside an otherwise-Norwegian document. Measured
on `ki-produktivitetsmodell` (order, 2026-08-18, census 09 on that repo):
`nb` locale, `## Virkeområde og forbehold` already doing the job Non-goals
exists for (a stranger sees what the repo does not try to be), `HEADING-
MISSING` `ERROR` regardless. `heading_aliases` in the register is the same
shape `titles` already is for the H1 — the decision is taken in the repo, the
bookkeeping happens here — and satisfying a requirement through it is its own
`OK` (`HEADING-ALIAS`), never folded silently into the aggregate. Keyed PER
REPO, not per locale: a locale-wide translation table would force every
nb-repo into the same Norwegian phrasing for the same section, exactly the
constraint `titles` already rejects for H1s one requirement over.
- **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

View file

@ -78,7 +78,7 @@ The repository's **class** decides what each check means:
| 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 |
| Install pins | a `@v…` or `--branch v…` in an install command names a tag the forge does not have, so the command a stranger copies 404s |
| Required headings | `## Install`, `## Non-goals`, `## Changelog` — per class. Present at the wrong depth is its own finding |
| Required headings | `## Install`, `## Non-goals`, `## Changelog` — per class. Present at the wrong depth is its own finding. A registered per-repo heading alias (`register/repos.json`'s `heading_aliases`) satisfies it too, for a repo whose readers were declared to speak another language |
| 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 |
@ -189,6 +189,26 @@ 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.
### Registered heading aliases — the same asymmetry, one requirement over
`## Non-goals` (and every other required heading) is one fixed English string,
matched case-insensitively but never translated. A repository whose readers
were declared `nb` in the register's `locales` cannot satisfy it honestly —
the only way to go green was planting an English heading inside an otherwise
Norwegian document, which is worse than the warning it silences.
A `heading_aliases` entry in the register is the fix, shaped exactly like
`titles`: the decision — this section, in this repo's own language, does the
job the English heading names — is made in the repo, and the bookkeeping
happens here. Set one, and a README carrying the aliased heading instead of
the literal one is an `OK` (`HEADING-ALIAS`) that names the alias, never a
silent pass. Leave it out, and `HEADING-MISSING` stands exactly as before.
It is keyed **per repository**, not per locale. A locale-wide translation
table would force every `nb` repository into the same Norwegian phrasing for
the same section — precisely the constraint `titles` already rejects for H1s,
where a human title is a valid choice, not a slot filled from a fixed list.
### When equality is the wrong demand
`README-DESC` requires the README's opening line to be the forge description

View file

@ -135,14 +135,27 @@
"",
"`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."
"alone is the wanted effect of registering the others.",
"",
"`.profile` UPDATED 2026-08-18 (org-ops coord, census 09): the org landing",
"page was rebuilt the same day (commit 9898a6e, 'rebuild org landing page,",
"add LICENSE and SECURITY.md', per that repo's own STATE.md — order from",
"`.claude`, AAA+ round 5). The H1 became `# From AI to Chitta — open`; the",
"old value here was the H1 it replaced, not drift. Verified against the",
"clone, not the coord message — the em-dash is U+2014, not a hyphen.",
"",
"`ki-produktivitetsmodell` ADDED 2026-08-18 (order from that repo, on its own",
"operator decision recorded in its STATE.md the same day): `# Tre nivå av",
"organisatorisk produktivitet med KI` is the document's real title, the repo",
"name is a slug. Verified against the clone before writing it down."
],
"titles": {
".profile": "fromaitochitta / open",
".profile": "From AI to Chitta — 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"
"ms-ai-architect": "AI Architect Plugin for Claude Code",
"ki-produktivitetsmodell": "Tre nivå av organisatorisk produktivitet med KI"
},
"$comment_traits": [
@ -201,6 +214,33 @@
"ki-produktivitetsmodell": "nb"
},
"$comment_heading_aliases": [
"The Non-goals contract is one fixed English string, matched literally — it",
"cannot be satisfied by a repo whose readers were declared `nb` in `locales`",
"above, and the only way to go green was to plant an English heading inside",
"an otherwise-Norwegian document. Same job `titles` does for a README H1, one",
"requirement over: the decision is taken in the repo, the bookkeeping happens",
"here — a registered alias satisfies the requirement and is its own OK",
"(`HEADING-ALIAS`), never a silent pass.",
"",
"Keyed PER REPO, not per locale: a locale-wide translation table would force",
"every nb-repo into the same Norwegian phrasing for the same section, which",
"is exactly the constraint `titles` already rejects for H1s (a human title is",
"a valid choice, not a slot to fill from a fixed list).",
"",
"ADDED 2026-08-18 (order from ki-produktivitetsmodell, census 09 on that",
"repo): its README is `nb` (see `locales` above) and carries",
"`## Virkeområde og forbehold`, which does the job the Non-goals contract",
"exists for — a stranger sees what the repo does not try to be. The operator",
"decided the README stays as written; this entry is the mechanism, not an",
"exception for one repo. Verified against the clone before writing it down."
],
"heading_aliases": {
"ki-produktivitetsmodell": {
"## Non-goals": "## Virkeområde og forbehold"
}
},
"$comment_tags_lightweight_accepted": [
"Where a decided YES about lightweight tag HISTORY gets written down — the",
"same job `titles` does for a README H1, one axis over. A lightweight tag is",

View file

@ -575,16 +575,32 @@ export function checkRequiredFiles({ present, klass, traits }, register) {
// on a predictable heading is what agents pattern-match on, and `## Non-goals`
// is the cheapest trust-builder there is: it proves someone thought about the
// boundary, and it stops misuse before it starts.
export function checkHeadings({ readme, klass, traits }, register) {
export function checkHeadings({ readme, klass, traits, name }, register) {
const { headings: required } = requirementsFor(klass, traits, register);
const text = String(readme ?? '');
const present = new Set(
text.split('\n').map((l) => l.trim()).filter((l) => l.startsWith('#')),
);
const aliases = register.heading_aliases?.[name] ?? {};
const findings = [];
for (const h of required) {
if ([...present].some((p) => p.toLowerCase() === h.toLowerCase())) continue;
// Same job `titles` does for a README H1, one requirement over: the
// decision (this repo's readers were declared `nb`, so the contract's
// English wording is the wrong test) is taken in the repo, the bookkeeping
// happens here. Keyed per repo, not per locale — two nb-repos need not
// phrase the same section the same way.
const alias = aliases[h];
if (alias && [...present].some((p) => p.toLowerCase() === alias.toLowerCase())) {
findings.push({
level: 'OK',
code: 'HEADING-ALIAS',
msg: `\`${alias}\` satisfies \`${h}\` — the registered heading alias for \`${name}\``,
});
continue;
}
// Same title, wrong depth: say that, rather than "missing". The contract
// wants a predictable top-level heading because that is what an agent
// pattern-matches on — but the section does exist, and the fix is a
@ -1556,7 +1572,7 @@ export function classifyRepo(
...checkInstallBlock({ readme, name, klass }, register),
...checkInstallTruth({ name, klass, catalogNames }),
...checkInstallPins({ readme, forgeTagsByRepo }, register),
...checkHeadings({ readme, klass, traits }, register),
...checkHeadings({ readme, klass, traits, name }, register),
...checkRequiredFiles({ present, klass, traits }, register),
...checkLinks({ files }, register),
...checkInternalLinks({ files, present }),

View file

@ -770,6 +770,27 @@ test('org-profile requires no headings at all', () => {
assert.equal(f.filter((x) => x.level === 'ERROR').length, 0);
});
// The Non-goals contract is written in English and matched literally, which
// cannot be satisfied by a repo whose readers were declared `nb` — the fix is
// per-repo, not per-locale, because two nb-repos need not phrase the same
// section the same way. Same shape as `titles`: the decision is taken in the
// repo, the bookkeeping happens in the register.
test('a registered heading alias satisfies a required heading for a different-language reader', () => {
const aliasRegister = {
...REGISTER,
heading_aliases: { 'ki-produktivitetsmodell': { '## Non-goals': '## Virkeområde og forbehold' } },
};
const readme = '# x\n## Virkeområde og forbehold\n';
const f = checkHeadings({ readme, klass: 'shared-asset', name: 'ki-produktivitetsmodell' }, aliasRegister);
assert.equal(f.some((x) => x.level === 'ERROR'), false);
assert.equal(f.some((x) => x.code === 'HEADING-ALIAS'), true);
// The alias is keyed per-repo: a different repo with the same Norwegian
// heading still misses the literal `## Non-goals`.
const other = checkHeadings({ readme, klass: 'shared-asset', name: 'some-other-repo' }, aliasRegister);
assert.equal(other.some((x) => x.code === 'HEADING-MISSING'), true);
});
// ------------------------------------------------------------- file: URL links
// A `file:///Users/ktg/...` link is dead for every reader but its author, and