fix(config): prevent workflow.research reset during milestone transitions

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 <noreply@anthropic.com>
This commit is contained in:
Maxim Brashenko
2026-03-15 21:08:59 +05:00
committed by GitHub
parent 63823c2e8a
commit eba9423ce0
4 changed files with 36 additions and 13 deletions

View File

@@ -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

View File

@@ -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' });

View File

@@ -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":**
```
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

View File

@@ -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', () => {