docs(voyage): S16 — stale counts/strings truth-pass + 4 doc-consistency pins

Truth-pass over the README/CLAUDE/contract docs the S14 audit flagged as
stale (findings #2/#6/#7/#8/#9). Re-grepped every count against the actual
files first — line numbers in the audit are S14 snapshots that had already
rotted. docs + tests only; lib/ runtime and all behaviour untouched.

Corrections:
- README architecture block: 23 → 24 agents; 5 → 7 hooks (add the two it
  omitted: post-compact-flush, otel-export); dropped the rotting test-count
  ("109" — already wrong twice, 109→683→686) for "comprehensive node:test
  suite" so the number can never drift again (operator choice).
- brief-reviewer five → six dimensions (+ memory alignment); plan-critic
  9 → 10 dimensions (README ×2 + CLAUDE.md), matching the agent's 10 numbered
  dims and the v5.5 flagship 6th brief dimension.
- phantom "v5.4" contract-freeze references → v5.5.0 (CLAUDE.md + HANDOVER-
  CONTRACTS ×3). v5.2–5.4 never shipped; the formalization landed with 2.2 in
  v5.5.0 (CHANGELOG:9), so line 46 got a prose tweak (one release both
  established 2.1 and evolved it to 2.2). CHANGELOG history left intact — it
  correctly explains the phantom.
- trekplan Phase-8 inline-sealing rationale "Opus 4.7" → 4.8.
- bonus (operator-approved): end-session helper name trekplan-end-session →
  trekendsession (the actual command).

New doc-consistency pins (TDD red→green): README agent-count and hook-count
(file counts), plan-critic dim-count (computed from ### N. headers),
brief-reviewer dim-count (cross-file, excludes agent-list lines that
co-mention plan-critic's count). Updated the existing pin that guarded the
phantom "v5.4 froze 2.1" string to the corrected wording + a !/v5.4/ guard.
Version-string pins (Opus 4.8 / v5.5.0) deliberately omitted as S19 prose-bloat.

Tests 686 → 690 (688 pass / 2 skip / 0 fail). claude plugin validate passes
(1 accepted CLAUDE.md-at-root warning).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LqBYc8Ltrk7LipyJmGxXiB
This commit is contained in:
Kjell Tore Guttormsen 2026-06-18 21:42:05 +02:00
commit 6ba58fda9b
5 changed files with 78 additions and 15 deletions

View file

@ -18,6 +18,18 @@ const ROOT = join(HERE, '..', '..');
function read(rel) { return readFileSync(join(ROOT, rel), 'utf-8'); }
function listMd(rel) { return readdirSync(join(ROOT, rel)).filter(f => f.endsWith('.md')); }
function listMjs(rel) { return readdirSync(join(ROOT, rel)).filter(f => f.endsWith('.mjs')); }
// Normalize a "<N|word> dimensions" descriptor to an integer (four..ten + digits).
const NUMWORDS = { four: 4, five: 5, six: 6, seven: 7, eight: 8, nine: 9, ten: 10 };
function dimCounts(text, anchor) {
// Every dimension count stated on a line mentioning `anchor`.
return text.split('\n')
.filter(l => l.includes(anchor) && /dimensions/.test(l))
.map(l => l.match(/(\d+|four|five|six|seven|eight|nine|ten)\s+(?:numbered\s+)?dimensions/i))
.filter(Boolean)
.map(m => /^\d+$/.test(m[1]) ? Number(m[1]) : NUMWORDS[m[1].toLowerCase()]);
}
test('CLAUDE.md agents table row count == agents/*.md file count', () => {
const md = read('CLAUDE.md');
@ -35,6 +47,55 @@ test('CLAUDE.md agents table row count == agents/*.md file count', () => {
);
});
test('README architecture block agent-count == agents/*.md file count', () => {
const md = read('README.md');
const m = md.match(/agents\/\s+(\d+)\s+specialized agents/);
assert.ok(m, 'README architecture block must state "N specialized agents"');
assert.equal(
Number(m[1]),
listMd('agents').length,
`Drift: README says ${m[1]} specialized agents vs ${listMd('agents').length} agents/*.md files.`,
);
});
test('README architecture block hook-count == hooks/scripts/*.mjs file count', () => {
const md = read('README.md');
const m = md.match(/hooks\/\s+(\d+)\s+hooks\s*\(/);
assert.ok(m, 'README architecture block must state "N hooks (...)"');
assert.equal(
Number(m[1]),
listMjs('hooks/scripts').length,
`Drift: README says ${m[1]} hooks vs ${listMjs('hooks/scripts').length} hooks/scripts/*.mjs files.`,
);
});
test('plan-critic dimension count: README + CLAUDE.md match the agent definition', () => {
const dims = (read('agents/plan-critic.md').match(/^### \d+\./gm) || []).length;
assert.ok(dims > 0, 'agents/plan-critic.md must define numbered "### N." dimensions');
const stated = [...dimCounts(read('README.md'), 'plan-critic'), ...dimCounts(read('CLAUDE.md'), 'plan-critic')];
assert.ok(stated.length >= 2, 'README + CLAUDE.md must cite plan-critic (N dimensions)');
for (const n of stated) {
assert.equal(n, dims, `Drift: a doc cites plan-critic (${n} dimensions) vs ${dims} numbered dims in agents/plan-critic.md.`);
}
});
test('brief-reviewer dimension count: README matches the agent definition', () => {
const agentDims = dimCounts(read('agents/brief-reviewer.md'), 'dimensions');
assert.ok(agentDims.length >= 1, 'agents/brief-reviewer.md must state "N dimensions"');
const expected = agentDims[0];
// Only the line stating brief-reviewer's OWN count — exclude agent-list lines
// that co-mention `plan-critic (N dimensions)` (those carry plan-critic's count).
const readmeDims = read('README.md').split('\n')
.filter(l => l.includes('brief-reviewer') && /dimensions/.test(l) && !l.includes('plan-critic'))
.map(l => l.match(/(\d+|four|five|six|seven|eight|nine|ten)\s+dimensions/i))
.filter(Boolean)
.map(m => /^\d+$/.test(m[1]) ? Number(m[1]) : NUMWORDS[m[1].toLowerCase()]);
assert.ok(readmeDims.length >= 1, 'README must cite brief-reviewer (N dimensions)');
for (const n of readmeDims) {
assert.equal(n, expected, `Drift: README cites brief-reviewer (${n} dimensions) vs ${expected} in agents/brief-reviewer.md.`);
}
});
test('CLAUDE.md commands table mentions every commands/*.md file', () => {
const md = read('CLAUDE.md');
const commandFiles = listMd('commands');
@ -670,12 +731,14 @@ test('v5.5 — brief_version 2.2 is current in the public-contract schema table
'Handover 1 schema table must mark brief_version "2.2" as current');
});
test('v5.4 — phase_signals stays optional: not promoted to required', () => {
test('v5.5 — phase_signals stays optional: not promoted to required', () => {
const t = read('docs/HANDOVER-CONTRACTS.md');
assert.ok(!t.includes('v5.4 may promote'),
'the speculative "v5.4 may promote phase_signals to required" line must be resolved (S3 froze the shape)');
assert.ok(t.includes('froze `2.1` as the public-contract baseline'),
'the Versioning note must record that v5.4 froze 2.1 as the public-contract baseline');
assert.ok(!/v5\.4/.test(t),
'the contract doc must not cite the phantom v5.4 release (v5.25.4 never shipped; S16 → v5.5.0)');
assert.ok(t.includes('established `2.1` as the public-contract baseline'),
'the Versioning note must record the v5.5.0 contract formalization establishing 2.1 as the baseline');
});
// --- v5.5 — framing enforcement: brief_version 2.2 (Handover 1 contract evolution) ---