Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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