fix(plan): every planning and research spawn takes the resolved model, and no turn budget is promised
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
eafe023c74
commit
074feef475
5 changed files with 132 additions and 16 deletions
2
tests/fixtures/jsonl-schemas.md
vendored
2
tests/fixtures/jsonl-schemas.md
vendored
|
|
@ -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:1125-1149 | 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:1275-1303 | 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:1294-1322 | 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:2909-2931 | 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) |
|
||||
|
|
|
|||
|
|
@ -301,3 +301,102 @@ test('[#52] sibling: agents/planning-orchestrator.md says "execute" gives the /t
|
|||
assert.doesNotMatch(text, /Say "execute" to implement/, 'planning-orchestrator still says "execute" implements');
|
||||
assert.ok(text.split('\n').includes('- Say "execute" for the /trekexecute line'), 'missing line: - Say "execute" for the /trekexecute line');
|
||||
});
|
||||
|
||||
// --- #48 / #12 / #57 ---------------------------------------------------------------
|
||||
// The planning and research spawn sites pinned `model: "opus"`, past the
|
||||
// resolved model (brief signal > profile > agent frontmatter); every spawn now
|
||||
// takes `phase_signal_result.model`. No `maxTurns` budget is promised: the
|
||||
// Agent tool takes none, so "scale turns by size" was a promise nothing kept.
|
||||
|
||||
/**
|
||||
* From the line holding `marker` (it must be the only one) to the end of the
|
||||
* blank-line-separated paragraph after the marker's own paragraph, flattened.
|
||||
*/
|
||||
function paraAfter(rel, marker) {
|
||||
const lines = read(rel).split('\n');
|
||||
const hits = lines.filter((l) => l.includes(marker)).length;
|
||||
assert.equal(hits, 1, `${rel}: expected exactly one line holding "${marker}"; found ${hits}`);
|
||||
const start = lines.findIndex((l) => l.includes(marker));
|
||||
let i = start;
|
||||
while (i < lines.length && lines[i].trim() !== '') i++;
|
||||
while (i < lines.length && lines[i].trim() === '') i++;
|
||||
while (i < lines.length && lines[i].trim() !== '') i++;
|
||||
return flat(lines.slice(start, i).join('\n'));
|
||||
}
|
||||
|
||||
const PASS_MODEL = /Pass `model: \{phase_signal_result\.model\}` at this spawn; without it \(the `--brief` branch, where the resolver does not run, or a resolver that printed nothing\) the agent's own `model:` applies\./;
|
||||
|
||||
test('[#48] commands/trekplan.md pins no spawn to `model: "opus"`', () => {
|
||||
assert.doesNotMatch(read('commands/trekplan.md'), /model: "opus"/);
|
||||
});
|
||||
|
||||
for (const [where, marker] of [
|
||||
['session-decomposer (Phase 1.5)', 'Launch the **session-decomposer** agent:'],
|
||||
['brief-reviewer (Phase 4b)', 'Launch the **brief-reviewer** agent.'],
|
||||
['### Always launch (Phase 5)', '### Always launch (all codebase sizes):'],
|
||||
['research-scout (Phase 5)', 'launch **research-scout** in'],
|
||||
['plan-critic and scope-guardian (Phase 9)', 'it traces into `## Verification`.'],
|
||||
]) {
|
||||
test(`[#48] ${where}: the spawn takes phase_signal_result.model, the agent's own model: as the fallback`, () => {
|
||||
const part = paraAfter('commands/trekplan.md', marker);
|
||||
assert.match(part, PASS_MODEL, `${where}: missing the resolved-model paragraph after "${marker}"`);
|
||||
assert.doesNotMatch(part, /model: "opus"/);
|
||||
});
|
||||
}
|
||||
|
||||
test('[#48] convention-scanner (Phase 5): the spawn takes phase_signal_result.model, the frontmatter as the fallback', () => {
|
||||
const part = paraAfter('commands/trekplan.md', 'use the `convention-scanner` plugin agent');
|
||||
assert.match(part, /\(model: phase_signal_result\.model, the agent's frontmatter as the fallback\)/);
|
||||
assert.doesNotMatch(part, /model: "opus"/);
|
||||
});
|
||||
|
||||
test('[#48] deep-dives (Phase 6): the spawn takes phase_signal_result.model, else opus', () => {
|
||||
const part = paraAfter('commands/trekplan.md', 'spawn a targeted deep-dive agent');
|
||||
assert.match(part, /\(model: phase_signal_result\.model, else opus — Explore is Claude Code's built-in agent and has no Voyage frontmatter to fall back to, subagent_type: "Explore"\)/);
|
||||
assert.doesNotMatch(part, /model: "opus"/);
|
||||
});
|
||||
|
||||
test('[#48] sibling: agents/planning-orchestrator.md:137 takes phase_signal_result.model', () => {
|
||||
const text = read('agents/planning-orchestrator.md');
|
||||
assert.doesNotMatch(text, /model: "opus"/);
|
||||
const part = partAt('agents/planning-orchestrator.md', '**Convention Scanner** — use the `convention-scanner` plugin agent', toBlank);
|
||||
assert.match(part, /\(model: phase_signal_result\.model, the agent's frontmatter as the fallback\)/);
|
||||
});
|
||||
|
||||
test('[#12] trekresearch Phase 5 follow-ups take phase_signal_result.model, else opus', () => {
|
||||
const text = read('commands/trekresearch.md');
|
||||
const p5 = flat(section(text, '## Phase 5 — Targeted follow-ups'));
|
||||
assert.doesNotMatch(p5, /model: "opus"/);
|
||||
const part = partAt('commands/trekresearch.md', 'launch a targeted follow-up agent', toBlank);
|
||||
assert.match(part, /launch a targeted follow-up agent \(model: phase_signal_result\.model, else opus — a follow-up may be one of Claude Code's built-in agents, which have no Voyage frontmatter to fall back to; see Launch rules\)/);
|
||||
assert.match(flat(text), /captured as `phase_signal_result` and used at Agent-spawn sites in Phase 4 and Phase 5 to inject the resolved model/);
|
||||
});
|
||||
|
||||
test('[#57] commands/trekresearch.md promises no maxTurns budget', () => {
|
||||
const text = read('commands/trekresearch.md');
|
||||
assert.doesNotMatch(text, /maxTurns/);
|
||||
assert.match(text, /- convention-scanner: medium\+ codebases only \(50\+ files\)/);
|
||||
});
|
||||
|
||||
test('[#57] sibling: commands/trekplan.md promises no maxTurns budget, and Phase 4 calibrates no turns', () => {
|
||||
const text = read('commands/trekplan.md');
|
||||
assert.doesNotMatch(text, /maxTurns/);
|
||||
const p4 = flat(section(text, '## Phase 4 — Codebase sizing'));
|
||||
assert.doesNotMatch(p4, /calibrate agent turns/);
|
||||
assert.match(p4, /Determine codebase scale to decide whether convention-scanner runs\./);
|
||||
assert.match(text, /^\*\*All agents run for all codebase sizes\.\*\*$/m);
|
||||
});
|
||||
|
||||
test('[#57] sibling: agents/planning-orchestrator.md promises no maxTurns budget and scales no turns', () => {
|
||||
const text = read('agents/planning-orchestrator.md');
|
||||
assert.doesNotMatch(text, /maxTurns/);
|
||||
assert.doesNotMatch(text, /Scale turns/i);
|
||||
assert.match(text, /^\*\*All agents run for all codebase sizes\.\*\*$/m);
|
||||
assert.match(text, /^- \*\*Adaptive:\*\* All agents run for all sizes\.$/m);
|
||||
});
|
||||
|
||||
test('keep: /trekexecute\'s controller and its model note stay pinned to opus', () => {
|
||||
const text = read('commands/trekexecute.md');
|
||||
assert.match(text, /`subagent_type: "general-purpose"`, `model: "opus"`, no `name`/);
|
||||
assert.match(flat(text), /controller's `model: "opus"` — so `phase_signal_result\.model` still has no spawn site here/);
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue