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
62 lines
2.6 KiB
JavaScript
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'
|
|
);
|
|
});
|