From 75bad7aedd7a3b857dd652bb3a24315392bb8fed Mon Sep 17 00:00:00 2001
From: Dennis Alexis Valin Dittrich
Date: Fri, 4 Sep 2026 16:22:10 +0200
Subject: [PATCH] test(#3936): tighten quick researcher regression coverage
(#4169)
* test(#3936): tighten quick researcher regression coverage
* test(#3936): restore adjacent quick dispatch coverage
Assert the default researcher model and bind the executor persona check to its Agent payload.
* test(#3936): make parse-list assertion wrap-safe
Bound the researcher-model check to the full parse paragraph so formatting-only line wraps do not fail the regression test.
* test(#3936): isolate Windows model defaults
---------
Co-authored-by: Tom Boucher
---
tests/init.test.cjs | 14 ++--
tests/quick-research.test.cjs | 126 +++++++++++++++++-----------------
2 files changed, 68 insertions(+), 72 deletions(-)
diff --git a/tests/init.test.cjs b/tests/init.test.cjs
index 0a248774d..967ed1d75 100644
--- a/tests/init.test.cjs
+++ b/tests/init.test.cjs
@@ -1951,16 +1951,15 @@ describe('cmdInitQuick', () => {
cleanup(tmpDir);
});
- test('init quick emits researcher_model', () => {
- // #3936: the quick research step (Step 4.75) dispatches gsd-phase-researcher,
- // so init quick must resolve the researcher's own model tier — parity with
- // init plan-phase (which emits researcher_model for the same agent).
- const result = runGsdTools('init quick "Fix login bug"', tmpDir);
+ test('init quick resolves the default researcher_model without overrides', () => {
+ // #3936: the quick research step dispatches gsd-phase-researcher, so init
+ // quick must resolve that agent's balanced-profile model without an override.
+ const result = runGsdTools('init quick "Fix login bug" --raw', tmpDir, { HOME: tmpDir, USERPROFILE: tmpDir });
assert.ok(result.success, `Command failed: ${result.error}`);
const output = JSON.parse(result.output);
- assert.ok('researcher_model' in output,
- 'init quick should emit researcher_model for the Step 4.75 research dispatch');
+ assert.strictEqual(output.researcher_model, 'sonnet',
+ 'default balanced profile should resolve the research agent model');
});
test('init quick resolves researcher_model from model_overrides', () => {
@@ -5045,4 +5044,3 @@ describe('init — GSD_PROJECT scoping (#3964)', () => {
assert.equal(out['codebase_dir_exists'], true, 'unscoped probe of the root codebase dir');
});
});
-
diff --git a/tests/quick-research.test.cjs b/tests/quick-research.test.cjs
index 818f4744b..5cab9c0f1 100644
--- a/tests/quick-research.test.cjs
+++ b/tests/quick-research.test.cjs
@@ -117,77 +117,75 @@ describe('quick workflow: research step', () => {
);
});
- test('research step spawns gsd-phase-researcher', () => {
+ test('research Agent uses researcher role bindings end to end', () => {
content = expandWorkflowSections(workflowPath);
- const researchSection = content.substring(
- content.indexOf('Step 4.75'),
- content.indexOf('Step 5:')
- );
- assert.ok(
- researchSection.includes('subagent_type="gsd-phase-researcher"'),
- 'research step should spawn gsd-phase-researcher agent'
+ const researchStart = content.indexOf('Step 4.75');
+ const plannerStart = content.indexOf('Step 5:', researchStart);
+ assert.ok(researchStart !== -1, 'Step 4.75 anchor should exist');
+ assert.ok(plannerStart > researchStart, 'Step 5 should follow Step 4.75');
+
+ const researchSection = content.slice(researchStart, plannerStart);
+ const parseStart = content.indexOf('Parse JSON for:');
+ const parseEnd = content.indexOf('\n\n', parseStart);
+ assert.ok(parseStart !== -1, 'init parse-list anchor should exist');
+ assert.ok(parseEnd > parseStart, 'init parse list should be non-empty');
+ const parseList = content.slice(parseStart, parseEnd);
+
+ const agentStart = researchSection.indexOf('Agent(');
+ const agentEnd = researchSection.indexOf('\n)', agentStart);
+ assert.ok(agentStart !== -1, 'research Agent call should exist');
+ assert.ok(agentEnd > agentStart, 'research Agent payload should be non-empty');
+ const researchAgent = researchSection.slice(agentStart, agentEnd);
+
+ assert.deepStrictEqual(
+ {
+ hostSkillBinding: content.includes(
+ 'AGENT_SKILLS_RESEARCHER=$(gsd_run query agent-skills gsd-phase-researcher)'
+ ),
+ modelParsed: parseList.includes('researcher_model'),
+ researcherPersona: researchAgent.includes('${AGENT_SKILLS_RESEARCHER}'),
+ researcherSubagent: researchAgent.includes('subagent_type="gsd-phase-researcher"'),
+ researcherModel: researchAgent.includes('model="{researcher_model}"'),
+ plannerPersona: researchAgent.includes('${AGENT_SKILLS_PLANNER}'),
+ plannerModel: researchAgent.includes('model="{planner_model}"'),
+ },
+ {
+ hostSkillBinding: true,
+ modelParsed: true,
+ researcherPersona: true,
+ researcherSubagent: true,
+ researcherModel: true,
+ plannerPersona: false,
+ plannerModel: false,
+ }
);
});
- test('research step injects the researcher persona, not the planner persona', () => {
- // #3936: the dispatch targets gsd-phase-researcher, so the persona riding the
- // prompt and the model tier must be the researcher's own — not the planner's.
+ test('executor dispatch keeps its own persona', () => {
content = expandWorkflowSections(workflowPath);
- const researchSection = content.substring(
- content.indexOf('Step 4.75'),
- content.indexOf('Step 5:')
- );
- assert.ok(
- researchSection.includes('${AGENT_SKILLS_RESEARCHER}'),
- 'research step should inject the gsd-phase-researcher persona'
- );
- assert.ok(
- !researchSection.includes('${AGENT_SKILLS_PLANNER}'),
- 'research step must not inject the planner persona into a researcher dispatch'
- );
- assert.ok(
- researchSection.includes('model="{researcher_model}"'),
- 'research step should pin the researcher model tier, not planner_model'
- );
- });
+ const executorStart = content.indexOf('Step 6: Spawn executor');
+ const reviewStart = content.indexOf('Step 6.25', executorStart);
+ assert.ok(executorStart !== -1, 'Step 6 executor anchor should exist');
+ assert.ok(reviewStart > executorStart, 'Step 6.25 should follow the executor');
- test('quick workflow resolves the researcher persona', () => {
- // #3936: AGENT_SKILLS_RESEARCHER must be resolved in quick.md's Step 2 block,
- // the way plan-phase.md does, or the Step 4.75 interpolation expands empty.
- const quickMd = fs.readFileSync(path.join(WORKFLOWS_DIR, 'quick.md'), 'utf8');
- assert.ok(
- quickMd.includes('AGENT_SKILLS_RESEARCHER=$(gsd_run query agent-skills gsd-phase-researcher)'),
- 'quick.md should resolve AGENT_SKILLS_RESEARCHER from agent-skills'
- );
- });
+ const executorSection = content.slice(executorStart, reviewStart);
+ const agentStart = executorSection.indexOf('Agent(');
+ const agentEnd = executorSection.indexOf('\n)', agentStart);
+ assert.ok(agentStart !== -1, 'executor Agent call should exist');
+ assert.ok(agentEnd > agentStart, 'executor Agent payload should be non-empty');
+ const executorAgent = executorSection.slice(agentStart, agentEnd);
- test('quick workflow parses researcher_model from init quick', () => {
- const quickMd = fs.readFileSync(path.join(WORKFLOWS_DIR, 'quick.md'), 'utf8');
- const parseList = quickMd.substring(
- quickMd.indexOf('Parse JSON for:'),
- quickMd.indexOf('```', quickMd.indexOf('Parse JSON for:'))
- );
- assert.ok(
- parseList.includes('researcher_model'),
- 'quick.md parse list should include researcher_model'
- );
- });
-
- test('planner and executor dispatches keep their own personas', () => {
- // Negative space: #3936 touches only the research dispatch — the planner and
- // executor spawns must keep their own personas and model tiers.
- content = expandWorkflowSections(workflowPath);
- const plannerSection = content.substring(
- content.indexOf('Step 5: Spawn planner'),
- content.indexOf('Step 5.5')
- );
- assert.ok(
- plannerSection.includes('${AGENT_SKILLS_PLANNER}'),
- 'planner dispatch should still inject the planner persona'
- );
- assert.ok(
- content.includes('${AGENT_SKILLS_EXECUTOR}'),
- 'executor dispatch should still inject the executor persona'
+ assert.deepStrictEqual(
+ {
+ executorPersona: executorAgent.includes('${AGENT_SKILLS_EXECUTOR}'),
+ plannerPersona: executorAgent.includes('${AGENT_SKILLS_PLANNER}'),
+ researcherPersona: executorAgent.includes('${AGENT_SKILLS_RESEARCHER}'),
+ },
+ {
+ executorPersona: true,
+ plannerPersona: false,
+ researcherPersona: false,
+ }
);
});