diff --git a/CHANGELOG.md b/CHANGELOG.md index 082f3be215..0b19ccf5eb 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 7eba9ece20..7d4dc8e024 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 4ffb8caa80..59b894bbbe 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 5930944797..69a9b5cc7e 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', () => {