From eba9423ce04209d3e6d734ab42cabc7f13d3fcf6 Mon Sep 17 00:00:00 2001 From: Maxim Brashenko <39818683+j2h4u@users.noreply.github.com> Date: Sun, 15 Mar 2026 21:08:59 +0500 Subject: [PATCH] fix(config): prevent workflow.research reset during milestone transitions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two bugs caused workflow.research to silently reset to false: 1. new-milestone.md unconditionally overwrote workflow.research via config-set after asking about research — coupling a per-milestone decision to a persistent user preference. Now the research question is per-invocation only; persistent config changes via /gsd:settings. 2. verify.cjs health repair (createConfig/resetConfig) used flat keys (research, plan_checker, verifier) instead of the canonical nested workflow object from config.cjs, also missing branch templates, nyquist_validation, and brave_search fields. Co-authored-by: Claude Opus 4.6 --- CHANGELOG.md | 4 ++++ get-shit-done/bin/lib/verify.cjs | 12 +++++++++--- get-shit-done/workflows/new-milestone.md | 20 +++++++++++--------- tests/verify-health.test.cjs | 13 ++++++++++++- 4 files changed, 36 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 082f3be21..0b19ccf5e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,10 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). ### Added - **Node repair operator** (`workflows/node-repair.md`) — autonomous recovery when task verification fails. Instead of immediately asking the user, the executor attempts structured repair: RETRY (different approach), DECOMPOSE (break into sub-tasks), or PRUNE (skip with justification). Only escalates to the user when the repair budget is exhausted or an architectural decision is needed. Repair budget defaults to 2 attempts per task; configurable via `workflow.node_repair_budget`. Disable entirely with `workflow.node_repair: false` to restore original behavior. +### Fixed +- `/gsd:new-milestone` no longer overwrites `workflow.research` config — milestone research decision is now per-invocation, persistent preference only changes via `/gsd:settings` +- `/gsd:health --repair` now creates config.json with correct nested `workflow` structure matching `config-ensure-section` canonical format + ## [1.22.4] - 2026-03-03 ### Added diff --git a/get-shit-done/bin/lib/verify.cjs b/get-shit-done/bin/lib/verify.cjs index 7eba9ece2..7d4dc8e02 100644 --- a/get-shit-done/bin/lib/verify.cjs +++ b/get-shit-done/bin/lib/verify.cjs @@ -728,10 +728,16 @@ function cmdValidateHealth(cwd, options, raw) { commit_docs: true, search_gitignored: false, branching_strategy: 'none', - research: true, - plan_checker: true, - verifier: true, + phase_branch_template: 'gsd/phase-{phase}-{slug}', + milestone_branch_template: 'gsd/{milestone}-{slug}', + workflow: { + research: true, + plan_check: true, + verifier: true, + nyquist_validation: true, + }, parallelization: true, + brave_search: false, }; fs.writeFileSync(configPath, JSON.stringify(defaults, null, 2), 'utf-8'); repairActions.push({ action: repair, success: true, path: 'config.json' }); diff --git a/get-shit-done/workflows/new-milestone.md b/get-shit-done/workflows/new-milestone.md index 4ffb8caa8..59b894bbb 100644 --- a/get-shit-done/workflows/new-milestone.md +++ b/get-shit-done/workflows/new-milestone.md @@ -86,21 +86,23 @@ Extract from init JSON: `researcher_model`, `synthesizer_model`, `roadmapper_mod ## 8. Research Decision +Check `research_enabled` from init JSON (loaded from config). + +**If `research_enabled` is `true`:** + AskUserQuestion: "Research the domain ecosystem for new features before defining requirements?" - "Research first (Recommended)" — Discover patterns, features, architecture for NEW capabilities -- "Skip research" — Go straight to requirements +- "Skip research for this milestone" — Go straight to requirements (does not change your default) -**Persist choice to config** (so future `/gsd:plan-phase` honors it): +**If `research_enabled` is `false`:** -```bash -# If "Research first": persist true -node "$HOME/.claude/get-shit-done/bin/gsd-tools.cjs" config-set workflow.research true +AskUserQuestion: "Research the domain ecosystem for new features before defining requirements?" +- "Skip research (current default)" — Go straight to requirements +- "Research first" — Discover patterns, features, architecture for NEW capabilities -# If "Skip research": persist false -node "$HOME/.claude/get-shit-done/bin/gsd-tools.cjs" config-set workflow.research false -``` +**IMPORTANT:** Do NOT persist this choice to config.json. The `workflow.research` setting is a persistent user preference that controls plan-phase behavior across the project. Changing it here would silently alter future `/gsd:plan-phase` behavior. To change the default, use `/gsd:settings`. -**If "Research first":** +**If user chose "Research first":** ``` ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ diff --git a/tests/verify-health.test.cjs b/tests/verify-health.test.cjs index 593094479..69a9b5cc7 100644 --- a/tests/verify-health.test.cjs +++ b/tests/verify-health.test.cjs @@ -527,6 +527,15 @@ describe('validate health --repair command', () => { assert.ok(fs.existsSync(configPath), 'config.json should now exist on disk'); const diskConfig = JSON.parse(fs.readFileSync(configPath, 'utf-8')); assert.strictEqual(diskConfig.model_profile, 'balanced', 'default model_profile should be balanced'); + // Verify nested workflow structure matches config.cjs canonical format + assert.ok(diskConfig.workflow, 'config should have nested workflow object'); + assert.strictEqual(diskConfig.workflow.research, true, 'workflow.research should default to true'); + assert.strictEqual(diskConfig.workflow.plan_check, true, 'workflow.plan_check should default to true'); + assert.strictEqual(diskConfig.workflow.verifier, true, 'workflow.verifier should default to true'); + assert.strictEqual(diskConfig.workflow.nyquist_validation, true, 'workflow.nyquist_validation should default to true'); + // Verify branch templates are present + assert.strictEqual(diskConfig.phase_branch_template, 'gsd/phase-{phase}-{slug}'); + assert.strictEqual(diskConfig.milestone_branch_template, 'gsd/{milestone}-{slug}'); }); test('resets config.json when JSON is invalid', () => { @@ -545,9 +554,11 @@ describe('validate health --repair command', () => { const resetAction = output.repairs_performed.find(r => r.action === 'resetConfig'); assert.ok(resetAction, `Expected resetConfig action: ${JSON.stringify(output.repairs_performed)}`); - // Verify config.json is now valid JSON + // Verify config.json is now valid JSON with correct nested structure const diskConfig = JSON.parse(fs.readFileSync(configPath, 'utf-8')); assert.ok(typeof diskConfig === 'object', 'config.json should be valid JSON after repair'); + assert.ok(diskConfig.workflow, 'reset config should have nested workflow object'); + assert.strictEqual(diskConfig.workflow.research, true, 'workflow.research should be true after reset'); }); test('regenerates STATE.md when missing', () => {