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, + } ); });