From 8009b67e3e053423cb66d3378beb823f58dd84a6 Mon Sep 17 00:00:00 2001 From: Tom Boucher Date: Sat, 11 Apr 2026 15:39:50 -0400 Subject: [PATCH] feat: expose tdd_mode in init JSON and add --tdd flag override (#2124) * test(2123): add failing tests for TDD init JSON exposure and --tdd flag Co-Authored-By: Claude Opus 4.6 * feat(2123): expose tdd_mode in init JSON and add --tdd flag override Co-Authored-By: Claude Opus 4.6 --------- Co-authored-by: Claude Opus 4.6 --- commands/gsd/execute-phase.md | 2 +- commands/gsd/plan-phase.md | 2 +- get-shit-done/bin/gsd-tools.cjs | 8 +-- get-shit-done/bin/lib/core.cjs | 1 + get-shit-done/bin/lib/init.cjs | 2 + tests/tdd-mode.test.cjs | 108 ++++++++++++++++++++++++++++++++ 6 files changed, 117 insertions(+), 6 deletions(-) diff --git a/commands/gsd/execute-phase.md b/commands/gsd/execute-phase.md index ef2a54935..62cde382f 100644 --- a/commands/gsd/execute-phase.md +++ b/commands/gsd/execute-phase.md @@ -1,7 +1,7 @@ --- name: gsd:execute-phase description: Execute all plans in a phase with wave-based parallelization -argument-hint: " [--wave N] [--gaps-only] [--interactive]" +argument-hint: " [--wave N] [--gaps-only] [--interactive] [--tdd]" allowed-tools: - Read - Write diff --git a/commands/gsd/plan-phase.md b/commands/gsd/plan-phase.md index efa24fac9..11ab0feb6 100644 --- a/commands/gsd/plan-phase.md +++ b/commands/gsd/plan-phase.md @@ -1,7 +1,7 @@ --- name: gsd:plan-phase description: Create detailed phase plan (PLAN.md) with verification loop -argument-hint: "[phase] [--auto] [--research] [--skip-research] [--gaps] [--skip-verify] [--prd ] [--reviews] [--text]" +argument-hint: "[phase] [--auto] [--research] [--skip-research] [--gaps] [--skip-verify] [--prd ] [--reviews] [--text] [--tdd]" agent: gsd-planner allowed-tools: - Read diff --git a/get-shit-done/bin/gsd-tools.cjs b/get-shit-done/bin/gsd-tools.cjs index d57272a8c..5317c3c68 100755 --- a/get-shit-done/bin/gsd-tools.cjs +++ b/get-shit-done/bin/gsd-tools.cjs @@ -807,13 +807,13 @@ async function runCommand(command, args, cwd, raw, defaultValue) { const workflow = args[1]; switch (workflow) { case 'execute-phase': { - const { validate: epValidate } = parseNamedArgs(args, [], ['validate']); - init.cmdInitExecutePhase(cwd, args[2], raw, { validate: epValidate }); + const { validate: epValidate, tdd: epTdd } = parseNamedArgs(args, [], ['validate', 'tdd']); + init.cmdInitExecutePhase(cwd, args[2], raw, { validate: epValidate, tdd: epTdd }); break; } case 'plan-phase': { - const { validate: ppValidate } = parseNamedArgs(args, [], ['validate']); - init.cmdInitPlanPhase(cwd, args[2], raw, { validate: ppValidate }); + const { validate: ppValidate, tdd: ppTdd } = parseNamedArgs(args, [], ['validate', 'tdd']); + init.cmdInitPlanPhase(cwd, args[2], raw, { validate: ppValidate, tdd: ppTdd }); break; } case 'new-project': diff --git a/get-shit-done/bin/lib/core.cjs b/get-shit-done/bin/lib/core.cjs index 6e7e1f584..ebc969ca8 100644 --- a/get-shit-done/bin/lib/core.cjs +++ b/get-shit-done/bin/lib/core.cjs @@ -375,6 +375,7 @@ function loadConfig(cwd) { brave_search: get('brave_search') ?? defaults.brave_search, firecrawl: get('firecrawl') ?? defaults.firecrawl, exa_search: get('exa_search') ?? defaults.exa_search, + tdd_mode: get('tdd_mode', { section: 'workflow', field: 'tdd_mode' }) ?? false, text_mode: get('text_mode', { section: 'workflow', field: 'text_mode' }) ?? defaults.text_mode, sub_repos: get('sub_repos', { section: 'planning', field: 'sub_repos' }) ?? defaults.sub_repos, resolve_model_ids: get('resolve_model_ids') ?? defaults.resolve_model_ids, diff --git a/get-shit-done/bin/lib/init.cjs b/get-shit-done/bin/lib/init.cjs index 9ba58ca08..a092ffa27 100644 --- a/get-shit-done/bin/lib/init.cjs +++ b/get-shit-done/bin/lib/init.cjs @@ -88,6 +88,7 @@ function cmdInitExecutePhase(cwd, phase, raw, options = {}) { verifier_model: resolveModelInternal(cwd, 'gsd-verifier'), // Config flags + tdd_mode: options.tdd || config.tdd_mode || false, commit_docs: config.commit_docs, sub_repos: config.sub_repos, parallelization: config.parallelization, @@ -211,6 +212,7 @@ function cmdInitPlanPhase(cwd, phase, raw, options = {}) { checker_model: resolveModelInternal(cwd, 'gsd-plan-checker'), // Workflow flags + tdd_mode: options.tdd || config.tdd_mode || false, research_enabled: config.research, plan_checker_enabled: config.plan_checker, nyquist_validation_enabled: config.nyquist_validation, diff --git a/tests/tdd-mode.test.cjs b/tests/tdd-mode.test.cjs index 20718bc2e..de04a89a1 100644 --- a/tests/tdd-mode.test.cjs +++ b/tests/tdd-mode.test.cjs @@ -103,3 +103,111 @@ describe('workflow.tdd_mode config round-trip', () => { assert.strictEqual(typeof config.workflow.tdd_mode, 'boolean'); }); }); + +// ─── init JSON exposure ──────────────────────────────────────────────────── + +describe('tdd_mode in init plan-phase JSON output', () => { + let tmpDir; + + beforeEach(() => { + tmpDir = createTempProject(); + // Create ROADMAP.md with a phase so init plan-phase can find it + const roadmap = [ + '# Roadmap', + '', + '## Phase 1 — Foundation', + '**Status:** Planned', + '**Requirements:** [R1]', + ].join('\n'); + fs.writeFileSync(path.join(tmpDir, '.planning', 'ROADMAP.md'), roadmap); + // Ensure config exists + runGsdTools('config-ensure-section', tmpDir, { HOME: tmpDir }); + }); + + afterEach(() => { + cleanup(tmpDir); + }); + + test('init plan-phase includes tdd_mode: false by default', () => { + const result = runGsdTools('init plan-phase 1', tmpDir); + assert.ok(result.success, `init plan-phase failed: ${result.error}`); + const json = JSON.parse(result.output); + assert.strictEqual(json.tdd_mode, false); + }); + + test('init plan-phase --tdd overrides to tdd_mode: true', () => { + const result = runGsdTools('init plan-phase 1 --tdd', tmpDir); + assert.ok(result.success, `init plan-phase --tdd failed: ${result.error}`); + const json = JSON.parse(result.output); + assert.strictEqual(json.tdd_mode, true); + }); + + test('config workflow.tdd_mode: true surfaces in init plan-phase without flag', () => { + runGsdTools('config-set workflow.tdd_mode true', tmpDir); + const result = runGsdTools('init plan-phase 1', tmpDir); + assert.ok(result.success, `init plan-phase failed: ${result.error}`); + const json = JSON.parse(result.output); + assert.strictEqual(json.tdd_mode, true); + }); + + test('--tdd flag overrides config value of false', () => { + runGsdTools('config-set workflow.tdd_mode false', tmpDir); + const result = runGsdTools('init plan-phase 1 --tdd', tmpDir); + assert.ok(result.success, `init plan-phase --tdd failed: ${result.error}`); + const json = JSON.parse(result.output); + assert.strictEqual(json.tdd_mode, true); + }); +}); + +describe('tdd_mode in init execute-phase JSON output', () => { + let tmpDir; + + beforeEach(() => { + tmpDir = createTempProject(); + // Create ROADMAP.md with a phase so init execute-phase can find it + const roadmap = [ + '# Roadmap', + '', + '## Phase 1 — Foundation', + '**Status:** Planned', + '**Requirements:** [R1]', + ].join('\n'); + fs.writeFileSync(path.join(tmpDir, '.planning', 'ROADMAP.md'), roadmap); + // Ensure config exists + runGsdTools('config-ensure-section', tmpDir, { HOME: tmpDir }); + }); + + afterEach(() => { + cleanup(tmpDir); + }); + + test('init execute-phase includes tdd_mode: false by default', () => { + const result = runGsdTools('init execute-phase 1', tmpDir); + assert.ok(result.success, `init execute-phase failed: ${result.error}`); + const json = JSON.parse(result.output); + assert.strictEqual(json.tdd_mode, false); + }); + + test('init execute-phase --tdd overrides to tdd_mode: true', () => { + const result = runGsdTools('init execute-phase 1 --tdd', tmpDir); + assert.ok(result.success, `init execute-phase --tdd failed: ${result.error}`); + const json = JSON.parse(result.output); + assert.strictEqual(json.tdd_mode, true); + }); + + test('config workflow.tdd_mode: true surfaces in init execute-phase without flag', () => { + runGsdTools('config-set workflow.tdd_mode true', tmpDir); + const result = runGsdTools('init execute-phase 1', tmpDir); + assert.ok(result.success, `init execute-phase failed: ${result.error}`); + const json = JSON.parse(result.output); + assert.strictEqual(json.tdd_mode, true); + }); + + test('--tdd flag overrides config value of false', () => { + runGsdTools('config-set workflow.tdd_mode false', tmpDir); + const result = runGsdTools('init execute-phase 1 --tdd', tmpDir); + assert.ok(result.success, `init execute-phase --tdd failed: ${result.error}`); + const json = JSON.parse(result.output); + assert.strictEqual(json.tdd_mode, true); + }); +});