config-audit/tests/commands/analyze-report-persistence.test.mjs
Kjell Tore Guttormsen 4b7b2d9c48 fix(acr): analyze persists agent-returned report (M-BUG-18)
The Claude Code subagent harness instructs spawned agents NOT to write
report/summary/findings/analysis .md files — the parent reads the final
text message. Verified live: analyzer-agent skipped Write entirely and
returned the report inline, so analysis-report.md never landed on disk
and the plan/interview/status phases would find nothing to read.

New contract (orchestrator-writes pattern): analyzer-agent returns the
complete report as its final message; the analyze command saves it
verbatim to the session directory before presenting the summary.

Same class exists in plan/feature-gap/optimize/scanner agent pairs —
deliberately left for their own dogfood chunks (plan is judged as-is
first per the pipeline sequence).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CTontYwY5JGS4nL2AuiASy
2026-07-16 20:24:12 +02:00

62 lines
2.6 KiB
JavaScript

/**
* M-BUG-18 — analysis-report.md persistence contract.
*
* The Claude Code subagent harness instructs spawned agents NOT to write
* report/summary/findings/analysis .md files — the parent reads the agent's
* final text message, not files it creates. The analyzer-agent therefore
* cannot be the one that persists analysis-report.md (verified live: the
* agent skipped Write and returned the report inline).
*
* New contract (orchestrator-writes pattern):
* - analyzer-agent returns the complete report as its final message
* - the analyze command saves that returned report verbatim to
* ~/.claude/config-audit/sessions/{session-id}/analysis-report.md,
* which downstream phases (plan, interview, status) read.
*/
import { test } from 'node:test';
import { strict as assert } from 'node:assert';
import { readFile } from 'node:fs/promises';
import { resolve, dirname } from 'node:path';
import { fileURLToPath } from 'node:url';
const __dirname = dirname(fileURLToPath(import.meta.url));
const COMMANDS_DIR = resolve(__dirname, '..', '..', 'commands');
const AGENTS_DIR = resolve(__dirname, '..', '..', 'agents');
test('analyze.md: agent prompt does not tell the agent to write the report file', async () => {
const content = await readFile(resolve(COMMANDS_DIR, 'analyze.md'), 'utf-8');
assert.doesNotMatch(
content,
/Output to:.*analysis-report\.md/,
'the spawn prompt must not instruct the subagent to write analysis-report.md — the harness blocks agent-written report files'
);
});
test('analyze.md: command saves the returned report to analysis-report.md', async () => {
const content = await readFile(resolve(COMMANDS_DIR, 'analyze.md'), 'utf-8');
assert.match(
content,
/return[s]? the complete report as (its|your) final message/i,
'analyze.md must state that the agent returns the report inline'
);
assert.match(
content,
/Write tool[\s\S]{0,200}analysis-report\.md|analysis-report\.md[\s\S]{0,200}Write tool/,
'analyze.md must instruct the command to persist the returned report to analysis-report.md with the Write tool'
);
});
test('analyzer-agent.md: output contract is return-inline, not self-write', async () => {
const content = await readFile(resolve(AGENTS_DIR, 'analyzer-agent.md'), 'utf-8');
assert.match(
content,
/return the complete report as your final message/i,
'analyzer-agent must be told its final message IS the report'
);
assert.doesNotMatch(
content,
/^Write to: .*analysis-report\.md/m,
'analyzer-agent must not carry the old self-write output contract'
);
});