fix(voyage): S23 — make /trekplan Phase 9 dedup executable (defect #1)
Phase 9's dedup hand-off was broken on two layers, both surfaced by the S22
dogfood: (a) plan-critic + scope-guardian (Read/Glob/Grep, no Write) were told
to write /tmp/*-out.json the dedup helper reads — they cannot; (b) even with
Write, their Output format emits markdown, not the helper's
{agent,findings:[{file,line,rule_key,text}]} schema. readJsonOrNull then
swallowed the absent files -> a silent empty merge that discarded every finding.
Fix (operator-chosen A'): keep the reviewers read-only; make the hand-off run.
- plan-review-dedup.mjs gains a --stdin mode reading {plan_critic,scope_guardian};
malformed stdin exits non-zero so a broken hand-off surfaces loudly instead of
collapsing into a silent empty merge. File mode + its tests are untouched.
- plan-critic.md + scope-guardian.md now emit a trailing machine-readable `json`
findings block (the inline hand-off; no Write tool needed).
- trekplan.md + planning-orchestrator.md Phase 9 rewritten in lockstep: extract
both blocks, pipe via heredoc into --stdin. No temp files, portable, no
pathguard dependency.
TDD: malformed-stdin test failed first (CLI ignored stdin -> exit 0 = the bug),
green after impl. New S23 doc-pin asserts both docs use --stdin (not the dead
/tmp paths) and both agents declare the json block. Suite 724 (722/2/0); live
HEAD baseline was 720, not the stale 705 STATE carried forward.
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:
parent
a366e332b7
commit
b4edc12bec
7 changed files with 196 additions and 30 deletions
|
|
@ -304,6 +304,37 @@ test('Phase 9 prose mandates parallel single-message dispatch + inline dedup', (
|
|||
);
|
||||
});
|
||||
|
||||
// --- S23 — Phase-9 dedup hand-off is executable (defect #1 fix) ---
|
||||
//
|
||||
// The read-only reviewers (plan-critic, scope-guardian: Read/Glob/Grep, no Write)
|
||||
// cannot write the temp files the old prose told them to. Pin the A′ fix so the
|
||||
// broken /tmp-write instruction cannot creep back:
|
||||
// 1. Phase-9 prose in BOTH docs must use --stdin, not the dead /tmp/*-out.json
|
||||
// file path the reviewers can't produce.
|
||||
// 2. BOTH reviewer agents must declare a machine-readable json findings block
|
||||
// with the keys the dedup helper consumes (agent + findings + rule_key).
|
||||
test('S23: Phase-9 dedup hand-off uses --stdin and the reviewers emit a json findings block', () => {
|
||||
const cmd = read('commands/trekplan.md');
|
||||
const orch = read('agents/planning-orchestrator.md');
|
||||
for (const [name, doc] of [['commands/trekplan.md', cmd], ['agents/planning-orchestrator.md', orch]]) {
|
||||
assert.ok(
|
||||
doc.includes('--stdin'),
|
||||
`${name} Phase 9 must pipe reviewer findings into plan-review-dedup.mjs via --stdin (reviewers are read-only)`,
|
||||
);
|
||||
assert.ok(
|
||||
!doc.includes('/tmp/plan-critic-out.json') && !doc.includes('/tmp/scope-guardian-out.json'),
|
||||
`${name} must NOT instruct the read-only reviewers to Write /tmp/*-out.json — they have no Write tool (defect #1)`,
|
||||
);
|
||||
}
|
||||
for (const agentFile of ['agents/plan-critic.md', 'agents/scope-guardian.md']) {
|
||||
const a = read(agentFile);
|
||||
assert.ok(
|
||||
a.includes('"findings"') && a.includes('"rule_key"') && a.includes('"agent"'),
|
||||
`${agentFile} must declare a machine-readable json findings block (agent + findings + rule_key) for the Phase-9 dedup helper`,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
// --- S20 — anti-false-claim: orchestrator docs must not claim ALL sub-agents run on Sonnet ---
|
||||
//
|
||||
// All named swarm agents are pinned `model: opus` (operator decision 40d8742;
|
||||
|
|
|
|||
|
|
@ -132,3 +132,42 @@ test('CLI shim tolerates missing input files (returns empty deduped JSON)', () =
|
|||
assert.equal(parsed.findings.length, 0);
|
||||
assert.equal(parsed.dedup_stats.total_in, 0);
|
||||
});
|
||||
|
||||
// --- S23 — --stdin mode (Phase-9 dedup fix: read-only reviewers can't write
|
||||
// temp files, so the orchestrator pipes both inline JSON blocks via stdin) ---
|
||||
|
||||
test('CLI --stdin merges {plan_critic, scope_guardian} payloads, provenance preserved', () => {
|
||||
const stdin = JSON.stringify({
|
||||
plan_critic: {
|
||||
agent: 'plan-critic',
|
||||
findings: [{ file: 'plan.md', line: 5, rule_key: 'PC1', text: 'duplicate finding shared by both' }],
|
||||
},
|
||||
scope_guardian: {
|
||||
agent: 'scope-guardian',
|
||||
findings: [{ file: 'plan.md', line: 5, rule_key: 'PC1', text: 'duplicate finding shared by both' }],
|
||||
},
|
||||
});
|
||||
const out = execFileSync(process.execPath, [SHIM, '--stdin'], { input: stdin, encoding: 'utf-8' });
|
||||
const parsed = JSON.parse(out);
|
||||
assert.equal(parsed.findings.length, 1);
|
||||
assert.deepEqual(parsed.findings[0].raised_by.sort(), ['plan-critic', 'scope-guardian']);
|
||||
assert.equal(parsed.dedup_stats.total_out, 1);
|
||||
});
|
||||
|
||||
test('CLI --stdin tolerates a single present payload (other key absent)', () => {
|
||||
const stdin = JSON.stringify({
|
||||
plan_critic: { agent: 'plan-critic', findings: [{ file: 'a.md', line: 1, rule_key: 'PC1', text: 'lonely finding' }] },
|
||||
});
|
||||
const out = execFileSync(process.execPath, [SHIM, '--stdin'], { input: stdin, encoding: 'utf-8' });
|
||||
const parsed = JSON.parse(out);
|
||||
assert.equal(parsed.findings.length, 1);
|
||||
assert.deepEqual(parsed.findings[0].raised_by, ['plan-critic']);
|
||||
});
|
||||
|
||||
test('CLI --stdin exits non-zero on malformed input (loud, not a silent empty merge)', () => {
|
||||
assert.throws(
|
||||
() => execFileSync(process.execPath, [SHIM, '--stdin'], { input: 'not json{', encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }),
|
||||
/Command failed|status 1/,
|
||||
'malformed --stdin must fail loudly so a broken hand-off cannot hide behind an empty merge',
|
||||
);
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue