Skip to content
Open
Prev Previous commit
Next Next commit
docs+test: update USER-GUIDE, CHANGELOG, planner template, and add sw…
…itch-warning tests (#291)

- USER-GUIDE: add /gsd:switch-milestone, concurrent milestones section,
  multi-milestone directory layout
- CHANGELOG: add [Unreleased] entry for concurrent milestone execution
- planner-subagent-prompt.md: replace hardcoded .planning/ paths with
  milestone-aware template variables
- 4 new tests: switch in-progress warning, idle switch, same-milestone
  switch, and auto-migration on first milestone create (461 total)
  • Loading branch information
Ethan Hurst committed Mar 3, 2026
commit c376903f72bb7dcfff088ce76e4472d5905cb1ff
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,14 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
- Analysis paralysis guard in agents to prevent over-deliberation during planning
- Exhaustive cross-check and task-level TDD patterns in agent workflows
- Code-aware discuss phase with codebase scouting — `/gsd:discuss-phase` now analyzes relevant source files before asking questions
- Concurrent milestone execution: work on multiple milestones in parallel with isolated state (#291)
- Milestone-scoped directories under `.planning/milestones/<name>/`
- `ACTIVE_MILESTONE` pointer file for switching context
- `/gsd:switch-milestone` command with in-progress work warnings
- `--milestone` CLI flag for explicit milestone targeting
- Statusline shows active milestone in multi-milestone mode
- All 28 workflow files updated for milestone-aware paths
- Zero behavioral change for single-milestone projects (legacy mode)

### Fixed
- Update checker clears both cache paths to prevent stale version notifications
Expand Down
59 changes: 54 additions & 5 deletions docs/USER-GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,7 @@ rapid prototyping phases where test infrastructure isn't the focus.
| `/gsd:audit-milestone` | Verify milestone met its definition of done | Before completing milestone |
| `/gsd:complete-milestone` | Archive milestone, tag release | All phases verified |
| `/gsd:new-milestone [name]` | Start next version cycle | After completing a milestone |
| `/gsd:switch-milestone <name>` | Switch active milestone for concurrent work | When working on multiple milestones |

### Navigation

Expand Down Expand Up @@ -381,6 +382,38 @@ claude --dangerously-skip-permissions
/gsd:remove-phase 7 # Descope phase 7 and renumber
```

### Concurrent Milestones

Work on multiple milestones simultaneously (e.g., v2.0 features + v1.5.1 hotfix):

```
/gsd:new-milestone "v1.5.1 Hotfix" # Creates milestone-scoped directory
/gsd:switch-milestone v2.0-features # Switch back to feature work
/gsd:progress # See status of active milestone
```

Each milestone gets isolated state under `.planning/milestones/<name>/`:

```
.planning/
├── PROJECT.md # Global (shared)
├── MILESTONES.md # Global (shared)
├── ACTIVE_MILESTONE # Pointer: "v2.0"
├── milestones/
│ ├── v2.0/
│ │ ├── STATE.md
│ │ ├── ROADMAP.md
│ │ ├── REQUIREMENTS.md
│ │ ├── config.json
│ │ └── phases/
│ └── v1.5.1-hotfix/
│ ├── STATE.md
│ ├── ROADMAP.md
│ └── phases/
```

When no second milestone exists, everything stays in `.planning/` as usual.

---

## Troubleshooting
Expand Down Expand Up @@ -450,23 +483,39 @@ For reference, here is what GSD creates in your project:
```
.planning/
PROJECT.md # Project vision and context (always loaded)
REQUIREMENTS.md # Scoped v1/v2 requirements with IDs
ROADMAP.md # Phase breakdown with status tracking
STATE.md # Decisions, blockers, session memory
config.json # Workflow configuration
MILESTONES.md # Completed milestone archive
MILESTONES.md # Completed milestone archive (global, shared)
ACTIVE_MILESTONE # Active milestone pointer (multi-milestone mode only)
research/ # Domain research from /gsd:new-project
todos/
pending/ # Captured ideas awaiting work
done/ # Completed todos
debug/ # Active debug sessions
resolved/ # Archived debug sessions
codebase/ # Brownfield codebase mapping (from /gsd:map-codebase)

# Single-milestone layout (default):
REQUIREMENTS.md # Scoped v1/v2 requirements with IDs
ROADMAP.md # Phase breakdown with status tracking
STATE.md # Decisions, blockers, session memory
config.json # Workflow configuration
phases/
XX-phase-name/
XX-YY-PLAN.md # Atomic execution plans
XX-YY-SUMMARY.md # Execution outcomes and decisions
CONTEXT.md # Your implementation preferences
RESEARCH.md # Ecosystem research findings
VERIFICATION.md # Post-execution verification results

# Multi-milestone layout (when concurrent milestones exist):
milestones/
v2.0/
STATE.md
ROADMAP.md
REQUIREMENTS.md
config.json
phases/
v1.5.1-hotfix/
STATE.md
ROADMAP.md
phases/
```
18 changes: 9 additions & 9 deletions get-shit-done/templates/planner-subagent-prompt.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,23 +13,23 @@ Template for spawning gsd-planner agent. The agent contains all planning experti
**Mode:** {standard | gap_closure}

**Project State:**
@.planning/STATE.md
@{state_path}

**Roadmap:**
@.planning/ROADMAP.md
@{roadmap_path}

**Requirements (if exists):**
@.planning/REQUIREMENTS.md
@{requirements_path}

**Phase Context (if exists):**
@.planning/phases/{phase_dir}/{phase_num}-CONTEXT.md
@{phase_dir}/{phase_num}-CONTEXT.md

**Research (if exists):**
@.planning/phases/{phase_dir}/{phase_num}-RESEARCH.md
@{phase_dir}/{phase_num}-RESEARCH.md

**Gap Closure (if --gaps mode):**
@.planning/phases/{phase_dir}/{phase_num}-VERIFICATION.md
@.planning/phases/{phase_dir}/{phase_num}-UAT.md
@{phase_dir}/{phase_num}-VERIFICATION.md
@{phase_dir}/{phase_num}-UAT.md

</planning_context>

Expand Down Expand Up @@ -98,8 +98,8 @@ Continue planning for Phase {phase_number}: {phase_name}
</objective>

<prior_state>
Phase directory: @.planning/phases/{phase_dir}/
Existing plans: @.planning/phases/{phase_dir}/*-PLAN.md
Phase directory: @{phase_dir}/
Existing plans: @{phase_dir}/*-PLAN.md
</prior_state>

<checkpoint_response>
Expand Down
33 changes: 33 additions & 0 deletions tests/milestone.test.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -605,6 +605,39 @@ describe('requirements mark-complete command', () => {
});
});

// ─────────────────────────────────────────────────────────────────────────────
// milestone create auto-migration
// ─────────────────────────────────────────────────────────────────────────────

describe('milestone create auto-migration', () => {
let tmpDir;

beforeEach(() => {
tmpDir = createTempProject();
});

afterEach(() => {
cleanup(tmpDir);
});

test('migrates legacy STATE.md to milestone directory on first create', () => {
// Write legacy STATE.md with content
const legacyStatePath = path.join(tmpDir, '.planning', 'STATE.md');
fs.writeFileSync(legacyStatePath, '---\nphase: 3\n---\n# State\n\n**Status:** Executing Phase 3\n');

// Create first milestone
const result = runGsdTools('milestone create v1.0', tmpDir);
assert.ok(result.success, `Command failed: ${result.error}`);

const output = JSON.parse(result.output);
assert.strictEqual(output.created, true);

// Check milestone STATE.md exists
const msStatePath = path.join(tmpDir, '.planning', 'milestones', 'v1.0', 'STATE.md');
assert.ok(fs.existsSync(msStatePath));
});
});

// ─────────────────────────────────────────────────────────────────────────────
// validate consistency command
// ─────────────────────────────────────────────────────────────────────────────
Expand Down
54 changes: 54 additions & 0 deletions tests/paths.test.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,60 @@ describe('milestone switch command', () => {
).trim();
assert.strictEqual(active, 'v2.0');
});

test('switch warns when current milestone has in-progress work', () => {
// beforeEach already created v1.0 and v2.0; recreate to get fresh STATE.md
runGsdTools('milestone create v1.0', tmpDir);

// Set v1.0 STATE.md to have in-progress status
const v1StatePath = path.join(tmpDir, '.planning', 'milestones', 'v1.0', 'STATE.md');
let stateContent = fs.readFileSync(v1StatePath, 'utf-8');
stateContent = stateContent.replace('**Status:** Ready to plan', '**Status:** Executing Phase 2');
fs.writeFileSync(v1StatePath, stateContent);

// Recreate second milestone (makes v2.0 active)
runGsdTools('milestone create v2.0', tmpDir);

// Switch back to v1.0 first (so v1.0 is active)
runGsdTools('milestone switch v1.0', tmpDir);

// Now set v1.0 to executing again and switch to v2.0
stateContent = fs.readFileSync(v1StatePath, 'utf-8');
stateContent = stateContent.replace(/\*\*Status:\*\*.*/, '**Status:** Executing Phase 3');
fs.writeFileSync(v1StatePath, stateContent);

const result = runGsdTools('milestone switch v2.0', tmpDir);
assert.ok(result.success, `Command failed: ${result.error}`);

const output = JSON.parse(result.output);
assert.strictEqual(output.switched, true);
assert.strictEqual(output.has_in_progress, true);
assert.strictEqual(output.previous_milestone, 'v1.0');
assert.ok(output.previous_status.includes('Executing'));
});

test('switch has no warning when current milestone is idle', () => {
// beforeEach created v1.0 then v2.0; v2.0 is active with "Ready to plan" status
// Switch to v1.0: previous=v2.0 with "Ready to plan" — not in-progress
const result = runGsdTools('milestone switch v1.0', tmpDir);
assert.ok(result.success, `Command failed: ${result.error}`);

const output = JSON.parse(result.output);
assert.strictEqual(output.switched, true);
assert.strictEqual(output.has_in_progress, false);
assert.strictEqual(output.previous_milestone, 'v2.0');
});

test('switch to same milestone has no in-progress warning', () => {
// beforeEach created v1.0 then v2.0; v2.0 is active
// Switch to v2.0 (same as current): previousMilestone === name, so no in-progress check
const result = runGsdTools('milestone switch v2.0', tmpDir);
assert.ok(result.success, `Command failed: ${result.error}`);

const output = JSON.parse(result.output);
assert.strictEqual(output.switched, true);
assert.strictEqual(output.has_in_progress, false);
});
});

// ─── milestone list command ─────────────────────────────────────────────────
Expand Down