docs(plan): Phase 9, Handover 4 and CLAUDE.md name rule A, with door lines

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Kjell Tore Guttormsen 2026-10-16 01:34:51 +02:00
commit fc865ca07a
Signed by: ktg
SSH key fingerprint: SHA256:JakMjO6FTBBzN0Bhfj9saOoEjaFxlSdYuZQQpM/lF9Q
10 changed files with 181 additions and 13 deletions

View file

@ -21,7 +21,7 @@
|-----------|--------|-------------|----------|---------------|-----|
| trekbrief-stats | ts, task, slug, mode, interview_turns, review_iterations, brief_quality, research_topics, auto_research, auto_result, intent_approved, project_dir, order_id, order_claimed_at, order_reason | commands/trekbrief.md (orchestrator-emit Phase 7; order_* added by lib/stats/stats-append.mjs) | trekbrief.md:1137-1161 | profile, phase_models, profile_source | none |
| trekresearch-stats | ts, question, mode, scope, engine, slug, project_dir, brief_path, dimensions, dimensions_baseline, dimensions_baseline_preserved, effort, conv_turns, empty_turns, unique_sources, agents_local, agents_external, gemini_used, confidence, contradictions, open_questions | lib/stats/stats-append.mjs (trekresearch.md Stats tracking) | trekresearch.md:782-808 | profile, phase_models, parallel_agents, external_research_enabled, profile_source | none |
| trekplan-stats | ts, task, mode, slug, brief_path, project_dir, codebase_size, codebase_files, agents_deployed, deep_dives, research_briefs_used, research_scout_used, critic_verdict, guardian_verdict, outcome | lib/stats/stats-append.mjs (trekplan.md Phase 12) | trekplan.md:1234-1262 | profile, phase_models, parallel_agents, profile_source | none |
| trekplan-stats | ts, task, mode, slug, brief_path, project_dir, codebase_size, codebase_files, agents_deployed, deep_dives, research_briefs_used, research_scout_used, critic_verdict, guardian_verdict, outcome | lib/stats/stats-append.mjs (trekplan.md Phase 12) | trekplan.md:1271-1299 | profile, phase_models, parallel_agents, profile_source | none |
| trekexecute-stats (Phase 9 record) | ts, plan, plan_type, mode, result, steps_total, steps_passed, steps_failed, steps_skipped, failed_at_step | lib/stats/stats-append.mjs (trekexecute.md Phase 9) | trekexecute.md:2892-2914 | profile, phase_models, profile_source | none |
| trekexecute-stats (autonomy events) | ts, event, known_event, payload | lib/stats/event-emit.mjs `emit()` | event-emit.mjs:78-99 | payload.profile, payload.phase_models, payload.profile_source | none |
| trekexecute-stats (PostToolUse Bash) | ts, session_id, command_excerpt, duration_ms, success | hooks/scripts/post-bash-stats.mjs (Bash PostToolUse) | post-bash-stats.mjs:42-54 | none (hook is plugin-level, not profile-aware) | command_excerpt (CWE-212) |

View file

@ -289,7 +289,7 @@ test('the template\'s own example steps validate as 1.9 with none of the new pat
// lines the release check accepts. The release check is imported as a module
// namespace, so a missing parseDoors export fails an assertion, not the load.
const releaseCheck = await import('../../scripts/release-check.mjs');
const UNRELEASED = '## Unreleased — plan_version 1.9, the trial-4 findings, session-clean tests';
const UNRELEASED = '## Unreleased — plan_version 1.9, the trial-4 findings, session-clean tests, the converging critic loop';
function firstChangelogSection() {
const lines = read('CHANGELOG.md').split('\n');

View file

@ -0,0 +1,68 @@
// tests/lib/review-loop-docs.test.mjs
// Prose pins for the converging critic loop (decision 43): /trekplan Phase 9,
// Handover 4, CLAUDE.md and the agent files must state rule A and rule B in the
// exact words lib/review/critic-diff.mjs exports, name the optional verdict
// fields, and no longer say the loop ends on a round with no BLOCKER.
// Texts are compared with every run of whitespace read as one space, so a
// sentence wrapped over two lines still counts.
import { test } from 'node:test';
import { strict as assert } from 'node:assert';
import { readFileSync } from 'node:fs';
import { join, dirname } from 'node:path';
import { fileURLToPath } from 'node:url';
import { RULE_A, RULE_B, DIFF_FIELDS } from '../../lib/review/critic-diff.mjs';
const ROOT = join(dirname(fileURLToPath(import.meta.url)), '..', '..');
const read = rel => readFileSync(join(ROOT, rel), 'utf-8');
const flat = text => String(text).replace(/\s+/g, ' ');
function phase(text, n) {
const start = text.indexOf(`\n## Phase ${n} `);
assert.ok(start >= 0, `## Phase ${n} missing`);
const end = text.indexOf('\n## Phase ', start + 1);
return text.slice(start, end < 0 ? undefined : end);
}
function handover4() {
const contract = read('docs/HANDOVER-CONTRACTS.md');
const start = contract.indexOf('## Handover 4');
const end = contract.indexOf('## Handover 5');
assert.ok(start >= 0 && end > start, 'Handover 4 and Handover 5 headings found in order');
return contract.slice(start, end);
}
const P9 = flat(phase(read('commands/trekplan.md'), 9));
test('RULE_A stands verbatim in Phase 9, Handover 4, CLAUDE.md and the planning orchestrator', () => {
const rule = flat(RULE_A);
assert.ok(P9.includes(rule), 'Phase 9 of commands/trekplan.md states RULE_A');
assert.ok(flat(handover4()).includes(rule), 'Handover 4 of docs/HANDOVER-CONTRACTS.md states RULE_A');
assert.ok(flat(read('CLAUDE.md')).includes(rule), 'CLAUDE.md states RULE_A');
assert.ok(flat(read('agents/planning-orchestrator.md')).includes(rule), 'agents/planning-orchestrator.md states RULE_A');
});
test('RULE_B stands verbatim in Phase 9 and in both reviewer agent files', () => {
const rule = flat(RULE_B);
assert.ok(P9.includes(rule), 'Phase 9 of commands/trekplan.md states RULE_B');
for (const rel of ['agents/plan-critic.md', 'agents/scope-guardian.md']) {
assert.ok(flat(read(rel)).includes(rule), `${rel} states RULE_B`);
}
});
test('Handover 4 names every DIFF_FIELDS entry and says absent means round-1 behaviour', () => {
const h4 = handover4();
assert.ok(DIFF_FIELDS.length > 0, 'DIFF_FIELDS lists the optional fields');
const missing = DIFF_FIELDS.filter(f => !h4.includes(`\`${f}\``));
assert.deepEqual(missing, [], `Handover 4 does not name (backticked): ${missing.join(', ')}`);
assert.ok(flat(h4).includes('absent means round-1 behaviour'), 'Handover 4 says "absent means round-1 behaviour"');
});
test('Phase 9 names plan-gate --diff and says round 1 is unchanged', () => {
assert.ok(P9.includes('plan-gate.mjs --diff'), 'Phase 9 runs plan-gate.mjs --diff');
assert.match(P9, /round 1 is unchanged/i, 'Phase 9 says "round 1 is unchanged"');
});
test('Phase 9 no longer says the loop ends on a round with no BLOCKER', () => {
assert.ok(!P9.includes('ends on a round with no BLOCKER'), 'the stale "ends on a round with no BLOCKER" is gone');
});