fix(#2072): thread resolved model into routed-agent spawns (assumptions-analyzer, code-reviewer, code-fixer)

model_overrides / models.<phaseType> were silently inert for gsd-assumptions-analyzer,
gsd-code-reviewer, and gsd-code-fixer on Claude Code: resolveModelInternal honors them,
but the workflows spawned these agents with no model= param, so the resolved value
never reached the Agent tool and the agents inherited the session model — no warning.

Fix — thread each agent's resolved model at every spawn site (the established
plan-phase pattern; the architecture-consistent Claude mechanism, since 13 other
agents already thread their model):
- discuss-phase-assumptions.md: `resolve-model gsd-assumptions-analyzer --raw`
  → ANALYZER_MODEL, threaded.
- code-review.md + code-review-fix.md (re-review): `resolve-model gsd-code-reviewer --raw`
  → REVIEWER_MODEL, threaded.
- code-review-fix.md (both fixer spawns): `resolve-model gsd-code-fixer --raw`
  → FIXER_MODEL, threaded (same silently-inert bug, same file — folded in per review).
- quick.md review step: was reusing `{executor_model}` for gsd-code-reviewer (so the
  reviewer's own override was ignored); init.quick now resolves `reviewer_model`
  (gsd-code-reviewer) and the spawn threads it.

resolve-model --raw returns the bare model string (resolve-execution --raw would
return effort — wrong). The resolver maps these agents to phaseType discuss /
verification / execution, so models.<phaseType> apply too.

Scope: the three agents reachable from the two issue-named workflows + quick.md. The
wider systemic class (other agents in UNTOUCHED workflows with the same pattern) stays
documented on the issue for a maintainer-scoped structural decision (thread-at-source
vs embed-at-install like #2256), not widened here.

Docs: the stale "discuss — reserved, no subagent today" model-profile tables now list
gsd-assumptions-analyzer and the verification row includes gsd-code-reviewer, across
the English docs, the shipped gsd-core/references/model-profiles.md reference, and the
ja-JP / zh-CN / ko-KR / pt-BR locale mirrors.

Tests:
- tests/model-resolver.test.cjs: #2072 acceptance — model_overrides and
  models.discuss/verification/execution resolve for all three agents.
- tests/model-routing-spawn-threading.test.cjs: every spawn of the three agents threads
  a resolved model (fails pre-fix); a header-precise parity guard fails the suite if a
  new un-threaded spawn of any of them regresses.
All 16 golden-install-parity fixtures + the workflow size baseline regenerated for the
changed shipped files (4 workflows + the reference doc); bin/lib is excluded from parity.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Tom Boucher
2026-07-07 20:28:15 -04:00
parent cce405c9b7
commit 4483300253
37 changed files with 318 additions and 115 deletions

View File

@@ -120,6 +120,36 @@ describe('resolveModelInternal', () => {
assert.ok(typeof model === 'string' && model.length > 0);
});
// #2072 acceptance: these two catalog agents' config MUST resolve — the bug was
// that the workflows never threaded the resolved value, not that the resolver
// ignored it. These assert the value the (now-threaded) spawns receive.
test('#2072: model_overrides applies to gsd-code-reviewer', () => {
writeConfig(tmpDir, { model_overrides: { 'gsd-code-reviewer': 'my-custom-model' } });
assert.strictEqual(resolveModelInternal(tmpDir, 'gsd-code-reviewer'), 'my-custom-model');
});
test('#2072: model_overrides applies to gsd-assumptions-analyzer', () => {
writeConfig(tmpDir, { model_overrides: { 'gsd-assumptions-analyzer': 'my-custom-model' } });
assert.strictEqual(resolveModelInternal(tmpDir, 'gsd-assumptions-analyzer'), 'my-custom-model');
});
test('#2072: models.verification tier applies to gsd-code-reviewer', () => {
writeConfig(tmpDir, { models: { verification: 'opus' } });
assert.strictEqual(resolveModelInternal(tmpDir, 'gsd-code-reviewer'), 'opus');
});
test('#2072: models.discuss tier applies to gsd-assumptions-analyzer', () => {
writeConfig(tmpDir, { models: { discuss: 'opus' } });
assert.strictEqual(resolveModelInternal(tmpDir, 'gsd-assumptions-analyzer'), 'opus');
});
test('#2072: model_overrides + models.execution apply to gsd-code-fixer', () => {
writeConfig(tmpDir, { model_overrides: { 'gsd-code-fixer': 'my-custom-model' } });
assert.strictEqual(resolveModelInternal(tmpDir, 'gsd-code-fixer'), 'my-custom-model');
writeConfig(tmpDir, { models: { execution: 'opus' } });
assert.strictEqual(resolveModelInternal(tmpDir, 'gsd-code-fixer'), 'opus');
});
test('runtime non-claude + model_profile_overrides for runtime tier', () => {
writeConfig(tmpDir, {
runtime: 'codex',