From 767bef64ef11765e9d012308f81122add9d37610 Mon Sep 17 00:00:00 2001 From: Lex Christopherson Date: Sun, 8 Feb 2026 09:47:15 -0600 Subject: [PATCH] fix(#484): persist research decision from new-milestone to config When user selects "Skip research" during /gsd:new-milestone, the choice was not saved to .planning/config.json. Later, /gsd:plan-phase would read the default (research: true) and spawn researchers anyway. - Add `config-set` command to gsd-tools.js for setting nested config values - Update new-milestone workflow to persist research choice after user decides Closes #484 Co-Authored-By: Claude Opus 4.5 --- get-shit-done/bin/gsd-tools.js | 50 ++++++++++++++++++++++++ get-shit-done/workflows/new-milestone.md | 10 +++++ 2 files changed, 60 insertions(+) diff --git a/get-shit-done/bin/gsd-tools.js b/get-shit-done/bin/gsd-tools.js index 4a5cc50fa..7ae9b8ff9 100755 --- a/get-shit-done/bin/gsd-tools.js +++ b/get-shit-done/bin/gsd-tools.js @@ -609,6 +609,51 @@ function cmdConfigEnsureSection(cwd, raw) { } } +function cmdConfigSet(cwd, keyPath, value, raw) { + const configPath = path.join(cwd, '.planning', 'config.json'); + + if (!keyPath) { + error('Usage: config-set '); + } + + // Parse value (handle booleans and numbers) + let parsedValue = value; + if (value === 'true') parsedValue = true; + else if (value === 'false') parsedValue = false; + else if (!isNaN(value) && value !== '') parsedValue = Number(value); + + // Load existing config or start with empty object + let config = {}; + try { + if (fs.existsSync(configPath)) { + config = JSON.parse(fs.readFileSync(configPath, 'utf-8')); + } + } catch (err) { + error('Failed to read config.json: ' + err.message); + } + + // Set nested value using dot notation (e.g., "workflow.research") + const keys = keyPath.split('.'); + let current = config; + for (let i = 0; i < keys.length - 1; i++) { + const key = keys[i]; + if (current[key] === undefined || typeof current[key] !== 'object') { + current[key] = {}; + } + current = current[key]; + } + current[keys[keys.length - 1]] = parsedValue; + + // Write back + try { + fs.writeFileSync(configPath, JSON.stringify(config, null, 2), 'utf-8'); + const result = { updated: true, key: keyPath, value: parsedValue }; + output(result, raw, `${keyPath}=${parsedValue}`); + } catch (err) { + error('Failed to write config.json: ' + err.message); + } +} + function cmdHistoryDigest(cwd, raw) { const phasesDir = path.join(cwd, '.planning', 'phases'); const digest = { phases: {}, decisions: [], tech_stack: new Set() }; @@ -4279,6 +4324,11 @@ function main() { break; } + case 'config-set': { + cmdConfigSet(cwd, args[1], args[2], raw); + break; + } + case 'history-digest': { cmdHistoryDigest(cwd, raw); break; diff --git a/get-shit-done/workflows/new-milestone.md b/get-shit-done/workflows/new-milestone.md index 432fe412b..96face4c7 100644 --- a/get-shit-done/workflows/new-milestone.md +++ b/get-shit-done/workflows/new-milestone.md @@ -88,6 +88,16 @@ AskUserQuestion: "Research the domain ecosystem for new features before defining - "Research first (Recommended)" — Discover patterns, features, architecture for NEW capabilities - "Skip research" — Go straight to requirements +**Persist choice to config** (so future `/gsd:plan-phase` honors it): + +```bash +# If "Research first": persist true +node ~/.claude/get-shit-done/bin/gsd-tools.js config-set workflow.research true + +# If "Skip research": persist false +node ~/.claude/get-shit-done/bin/gsd-tools.js config-set workflow.research false +``` + **If "Research first":** ```