Merge pull request #483 from glittercowboy/claude/expand-gsd-automation-XVhXH
Add phase lifecycle and roadmap management commands
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* GSD Tools Tests — Schema validation for history-digest command
|
||||
* GSD Tools Tests
|
||||
*/
|
||||
|
||||
const { test, describe, beforeEach, afterEach } = require('node:test');
|
||||
@@ -1191,3 +1191,843 @@ describe('init commands with --include flag', () => {
|
||||
assert.strictEqual(output.roadmap_content, undefined, 'roadmap_content not requested, should be undefined');
|
||||
});
|
||||
});
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// roadmap analyze command
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
describe('roadmap analyze command', () => {
|
||||
let tmpDir;
|
||||
|
||||
beforeEach(() => {
|
||||
tmpDir = createTempProject();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
cleanup(tmpDir);
|
||||
});
|
||||
|
||||
test('missing ROADMAP.md returns error', () => {
|
||||
const result = runGsdTools('roadmap analyze', tmpDir);
|
||||
assert.ok(result.success, `Command should succeed: ${result.error}`);
|
||||
|
||||
const output = JSON.parse(result.output);
|
||||
assert.strictEqual(output.error, 'ROADMAP.md not found');
|
||||
});
|
||||
|
||||
test('parses phases with goals and disk status', () => {
|
||||
fs.writeFileSync(
|
||||
path.join(tmpDir, '.planning', 'ROADMAP.md'),
|
||||
`# Roadmap v1.0
|
||||
|
||||
### Phase 1: Foundation
|
||||
**Goal:** Set up infrastructure
|
||||
|
||||
### Phase 2: Authentication
|
||||
**Goal:** Add user auth
|
||||
|
||||
### Phase 3: Features
|
||||
**Goal:** Build core features
|
||||
`
|
||||
);
|
||||
|
||||
// Create phase dirs with varying completion
|
||||
const p1 = path.join(tmpDir, '.planning', 'phases', '01-foundation');
|
||||
fs.mkdirSync(p1, { recursive: true });
|
||||
fs.writeFileSync(path.join(p1, '01-01-PLAN.md'), '# Plan');
|
||||
fs.writeFileSync(path.join(p1, '01-01-SUMMARY.md'), '# Summary');
|
||||
|
||||
const p2 = path.join(tmpDir, '.planning', 'phases', '02-authentication');
|
||||
fs.mkdirSync(p2, { recursive: true });
|
||||
fs.writeFileSync(path.join(p2, '02-01-PLAN.md'), '# Plan');
|
||||
|
||||
const result = runGsdTools('roadmap analyze', tmpDir);
|
||||
assert.ok(result.success, `Command failed: ${result.error}`);
|
||||
|
||||
const output = JSON.parse(result.output);
|
||||
assert.strictEqual(output.phase_count, 3, 'should find 3 phases');
|
||||
assert.strictEqual(output.phases[0].disk_status, 'complete', 'phase 1 complete');
|
||||
assert.strictEqual(output.phases[1].disk_status, 'planned', 'phase 2 planned');
|
||||
assert.strictEqual(output.phases[2].disk_status, 'no_directory', 'phase 3 no directory');
|
||||
assert.strictEqual(output.completed_phases, 1, '1 phase complete');
|
||||
assert.strictEqual(output.total_plans, 2, '2 total plans');
|
||||
assert.strictEqual(output.total_summaries, 1, '1 total summary');
|
||||
assert.strictEqual(output.progress_percent, 50, '50% complete');
|
||||
assert.strictEqual(output.current_phase, '2', 'current phase is 2');
|
||||
});
|
||||
|
||||
test('extracts goals and dependencies', () => {
|
||||
fs.writeFileSync(
|
||||
path.join(tmpDir, '.planning', 'ROADMAP.md'),
|
||||
`# Roadmap
|
||||
|
||||
### Phase 1: Setup
|
||||
**Goal:** Initialize project
|
||||
**Depends on:** Nothing
|
||||
|
||||
### Phase 2: Build
|
||||
**Goal:** Build features
|
||||
**Depends on:** Phase 1
|
||||
`
|
||||
);
|
||||
|
||||
const result = runGsdTools('roadmap analyze', tmpDir);
|
||||
assert.ok(result.success, `Command failed: ${result.error}`);
|
||||
|
||||
const output = JSON.parse(result.output);
|
||||
assert.strictEqual(output.phases[0].goal, 'Initialize project');
|
||||
assert.strictEqual(output.phases[0].depends_on, 'Nothing');
|
||||
assert.strictEqual(output.phases[1].goal, 'Build features');
|
||||
assert.strictEqual(output.phases[1].depends_on, 'Phase 1');
|
||||
});
|
||||
});
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// phase add command
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
describe('phase add command', () => {
|
||||
let tmpDir;
|
||||
|
||||
beforeEach(() => {
|
||||
tmpDir = createTempProject();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
cleanup(tmpDir);
|
||||
});
|
||||
|
||||
test('adds phase after highest existing', () => {
|
||||
fs.writeFileSync(
|
||||
path.join(tmpDir, '.planning', 'ROADMAP.md'),
|
||||
`# Roadmap v1.0
|
||||
|
||||
### Phase 1: Foundation
|
||||
**Goal:** Setup
|
||||
|
||||
### Phase 2: API
|
||||
**Goal:** Build API
|
||||
|
||||
---
|
||||
`
|
||||
);
|
||||
|
||||
const result = runGsdTools('phase add User Dashboard', tmpDir);
|
||||
assert.ok(result.success, `Command failed: ${result.error}`);
|
||||
|
||||
const output = JSON.parse(result.output);
|
||||
assert.strictEqual(output.phase_number, 3, 'should be phase 3');
|
||||
assert.strictEqual(output.slug, 'user-dashboard');
|
||||
|
||||
// Verify directory created
|
||||
assert.ok(
|
||||
fs.existsSync(path.join(tmpDir, '.planning', 'phases', '03-user-dashboard')),
|
||||
'directory should be created'
|
||||
);
|
||||
|
||||
// Verify ROADMAP updated
|
||||
const roadmap = fs.readFileSync(path.join(tmpDir, '.planning', 'ROADMAP.md'), 'utf-8');
|
||||
assert.ok(roadmap.includes('### Phase 3: User Dashboard'), 'roadmap should include new phase');
|
||||
assert.ok(roadmap.includes('**Depends on:** Phase 2'), 'should depend on previous');
|
||||
});
|
||||
|
||||
test('handles empty roadmap', () => {
|
||||
fs.writeFileSync(
|
||||
path.join(tmpDir, '.planning', 'ROADMAP.md'),
|
||||
`# Roadmap v1.0\n`
|
||||
);
|
||||
|
||||
const result = runGsdTools('phase add Initial Setup', tmpDir);
|
||||
assert.ok(result.success, `Command failed: ${result.error}`);
|
||||
|
||||
const output = JSON.parse(result.output);
|
||||
assert.strictEqual(output.phase_number, 1, 'should be phase 1');
|
||||
});
|
||||
});
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// phase insert command
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
describe('phase insert command', () => {
|
||||
let tmpDir;
|
||||
|
||||
beforeEach(() => {
|
||||
tmpDir = createTempProject();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
cleanup(tmpDir);
|
||||
});
|
||||
|
||||
test('inserts decimal phase after target', () => {
|
||||
fs.writeFileSync(
|
||||
path.join(tmpDir, '.planning', 'ROADMAP.md'),
|
||||
`# Roadmap
|
||||
|
||||
### Phase 1: Foundation
|
||||
**Goal:** Setup
|
||||
|
||||
### Phase 2: API
|
||||
**Goal:** Build API
|
||||
`
|
||||
);
|
||||
fs.mkdirSync(path.join(tmpDir, '.planning', 'phases', '01-foundation'), { recursive: true });
|
||||
|
||||
const result = runGsdTools('phase insert 1 Fix Critical Bug', tmpDir);
|
||||
assert.ok(result.success, `Command failed: ${result.error}`);
|
||||
|
||||
const output = JSON.parse(result.output);
|
||||
assert.strictEqual(output.phase_number, '01.1', 'should be 01.1');
|
||||
assert.strictEqual(output.after_phase, '1');
|
||||
|
||||
// Verify directory
|
||||
assert.ok(
|
||||
fs.existsSync(path.join(tmpDir, '.planning', 'phases', '01.1-fix-critical-bug')),
|
||||
'decimal phase directory should be created'
|
||||
);
|
||||
|
||||
// Verify ROADMAP
|
||||
const roadmap = fs.readFileSync(path.join(tmpDir, '.planning', 'ROADMAP.md'), 'utf-8');
|
||||
assert.ok(roadmap.includes('Phase 01.1: Fix Critical Bug (INSERTED)'), 'roadmap should include inserted phase');
|
||||
});
|
||||
|
||||
test('increments decimal when siblings exist', () => {
|
||||
fs.writeFileSync(
|
||||
path.join(tmpDir, '.planning', 'ROADMAP.md'),
|
||||
`# Roadmap
|
||||
|
||||
### Phase 1: Foundation
|
||||
**Goal:** Setup
|
||||
|
||||
### Phase 2: API
|
||||
**Goal:** Build API
|
||||
`
|
||||
);
|
||||
fs.mkdirSync(path.join(tmpDir, '.planning', 'phases', '01-foundation'), { recursive: true });
|
||||
fs.mkdirSync(path.join(tmpDir, '.planning', 'phases', '01.1-hotfix'), { recursive: true });
|
||||
|
||||
const result = runGsdTools('phase insert 1 Another Fix', tmpDir);
|
||||
assert.ok(result.success, `Command failed: ${result.error}`);
|
||||
|
||||
const output = JSON.parse(result.output);
|
||||
assert.strictEqual(output.phase_number, '01.2', 'should be 01.2');
|
||||
});
|
||||
|
||||
test('rejects missing phase', () => {
|
||||
fs.writeFileSync(
|
||||
path.join(tmpDir, '.planning', 'ROADMAP.md'),
|
||||
`# Roadmap\n### Phase 1: Test\n**Goal:** Test\n`
|
||||
);
|
||||
|
||||
const result = runGsdTools('phase insert 99 Fix Something', tmpDir);
|
||||
assert.ok(!result.success, 'should fail for missing phase');
|
||||
assert.ok(result.error.includes('not found'), 'error mentions not found');
|
||||
});
|
||||
});
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// phase remove command
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
describe('phase remove command', () => {
|
||||
let tmpDir;
|
||||
|
||||
beforeEach(() => {
|
||||
tmpDir = createTempProject();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
cleanup(tmpDir);
|
||||
});
|
||||
|
||||
test('removes phase directory and renumbers subsequent', () => {
|
||||
// Setup 3 phases
|
||||
fs.writeFileSync(
|
||||
path.join(tmpDir, '.planning', 'ROADMAP.md'),
|
||||
`# Roadmap
|
||||
|
||||
### Phase 1: Foundation
|
||||
**Goal:** Setup
|
||||
**Depends on:** Nothing
|
||||
|
||||
### Phase 2: Auth
|
||||
**Goal:** Authentication
|
||||
**Depends on:** Phase 1
|
||||
|
||||
### Phase 3: Features
|
||||
**Goal:** Core features
|
||||
**Depends on:** Phase 2
|
||||
`
|
||||
);
|
||||
|
||||
fs.mkdirSync(path.join(tmpDir, '.planning', 'phases', '01-foundation'), { recursive: true });
|
||||
const p2 = path.join(tmpDir, '.planning', 'phases', '02-auth');
|
||||
fs.mkdirSync(p2, { recursive: true });
|
||||
fs.writeFileSync(path.join(p2, '02-01-PLAN.md'), '# Plan');
|
||||
const p3 = path.join(tmpDir, '.planning', 'phases', '03-features');
|
||||
fs.mkdirSync(p3, { recursive: true });
|
||||
fs.writeFileSync(path.join(p3, '03-01-PLAN.md'), '# Plan');
|
||||
fs.writeFileSync(path.join(p3, '03-02-PLAN.md'), '# Plan 2');
|
||||
|
||||
// Remove phase 2
|
||||
const result = runGsdTools('phase remove 2', tmpDir);
|
||||
assert.ok(result.success, `Command failed: ${result.error}`);
|
||||
|
||||
const output = JSON.parse(result.output);
|
||||
assert.strictEqual(output.removed, '2');
|
||||
assert.strictEqual(output.directory_deleted, '02-auth');
|
||||
|
||||
// Phase 3 should be renumbered to 02
|
||||
assert.ok(
|
||||
fs.existsSync(path.join(tmpDir, '.planning', 'phases', '02-features')),
|
||||
'phase 3 should be renumbered to 02-features'
|
||||
);
|
||||
assert.ok(
|
||||
!fs.existsSync(path.join(tmpDir, '.planning', 'phases', '03-features')),
|
||||
'old 03-features should not exist'
|
||||
);
|
||||
|
||||
// Files inside should be renamed
|
||||
assert.ok(
|
||||
fs.existsSync(path.join(tmpDir, '.planning', 'phases', '02-features', '02-01-PLAN.md')),
|
||||
'plan file should be renumbered to 02-01'
|
||||
);
|
||||
assert.ok(
|
||||
fs.existsSync(path.join(tmpDir, '.planning', 'phases', '02-features', '02-02-PLAN.md')),
|
||||
'plan 2 should be renumbered to 02-02'
|
||||
);
|
||||
|
||||
// ROADMAP should be updated
|
||||
const roadmap = fs.readFileSync(path.join(tmpDir, '.planning', 'ROADMAP.md'), 'utf-8');
|
||||
assert.ok(!roadmap.includes('Phase 2: Auth'), 'removed phase should not be in roadmap');
|
||||
assert.ok(roadmap.includes('Phase 2: Features'), 'phase 3 should be renumbered to 2');
|
||||
});
|
||||
|
||||
test('rejects removal of phase with summaries unless --force', () => {
|
||||
const p1 = path.join(tmpDir, '.planning', 'phases', '01-test');
|
||||
fs.mkdirSync(p1, { recursive: true });
|
||||
fs.writeFileSync(path.join(p1, '01-01-PLAN.md'), '# Plan');
|
||||
fs.writeFileSync(path.join(p1, '01-01-SUMMARY.md'), '# Summary');
|
||||
fs.writeFileSync(
|
||||
path.join(tmpDir, '.planning', 'ROADMAP.md'),
|
||||
`# Roadmap\n### Phase 1: Test\n**Goal:** Test\n`
|
||||
);
|
||||
|
||||
// Should fail without --force
|
||||
const result = runGsdTools('phase remove 1', tmpDir);
|
||||
assert.ok(!result.success, 'should fail without --force');
|
||||
assert.ok(result.error.includes('executed plan'), 'error mentions executed plans');
|
||||
|
||||
// Should succeed with --force
|
||||
const forceResult = runGsdTools('phase remove 1 --force', tmpDir);
|
||||
assert.ok(forceResult.success, `Force remove failed: ${forceResult.error}`);
|
||||
});
|
||||
|
||||
test('removes decimal phase and renumbers siblings', () => {
|
||||
fs.writeFileSync(
|
||||
path.join(tmpDir, '.planning', 'ROADMAP.md'),
|
||||
`# Roadmap\n### Phase 6: Main\n**Goal:** Main\n### Phase 6.1: Fix A\n**Goal:** Fix A\n### Phase 6.2: Fix B\n**Goal:** Fix B\n### Phase 6.3: Fix C\n**Goal:** Fix C\n`
|
||||
);
|
||||
|
||||
fs.mkdirSync(path.join(tmpDir, '.planning', 'phases', '06-main'), { recursive: true });
|
||||
fs.mkdirSync(path.join(tmpDir, '.planning', 'phases', '06.1-fix-a'), { recursive: true });
|
||||
fs.mkdirSync(path.join(tmpDir, '.planning', 'phases', '06.2-fix-b'), { recursive: true });
|
||||
fs.mkdirSync(path.join(tmpDir, '.planning', 'phases', '06.3-fix-c'), { recursive: true });
|
||||
|
||||
const result = runGsdTools('phase remove 6.2', tmpDir);
|
||||
assert.ok(result.success, `Command failed: ${result.error}`);
|
||||
|
||||
// 06.3 should become 06.2
|
||||
assert.ok(
|
||||
fs.existsSync(path.join(tmpDir, '.planning', 'phases', '06.2-fix-c')),
|
||||
'06.3 should be renumbered to 06.2'
|
||||
);
|
||||
assert.ok(
|
||||
!fs.existsSync(path.join(tmpDir, '.planning', 'phases', '06.3-fix-c')),
|
||||
'old 06.3 should not exist'
|
||||
);
|
||||
});
|
||||
|
||||
test('updates STATE.md phase count', () => {
|
||||
fs.writeFileSync(
|
||||
path.join(tmpDir, '.planning', 'ROADMAP.md'),
|
||||
`# Roadmap\n### Phase 1: A\n**Goal:** A\n### Phase 2: B\n**Goal:** B\n`
|
||||
);
|
||||
fs.writeFileSync(
|
||||
path.join(tmpDir, '.planning', 'STATE.md'),
|
||||
`# State\n\n**Current Phase:** 1\n**Total Phases:** 2\n`
|
||||
);
|
||||
fs.mkdirSync(path.join(tmpDir, '.planning', 'phases', '01-a'), { recursive: true });
|
||||
fs.mkdirSync(path.join(tmpDir, '.planning', 'phases', '02-b'), { recursive: true });
|
||||
|
||||
runGsdTools('phase remove 2', tmpDir);
|
||||
|
||||
const state = fs.readFileSync(path.join(tmpDir, '.planning', 'STATE.md'), 'utf-8');
|
||||
assert.ok(state.includes('**Total Phases:** 1'), 'total phases should be decremented');
|
||||
});
|
||||
});
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// phase complete command
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
describe('phase complete command', () => {
|
||||
let tmpDir;
|
||||
|
||||
beforeEach(() => {
|
||||
tmpDir = createTempProject();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
cleanup(tmpDir);
|
||||
});
|
||||
|
||||
test('marks phase complete and transitions to next', () => {
|
||||
fs.writeFileSync(
|
||||
path.join(tmpDir, '.planning', 'ROADMAP.md'),
|
||||
`# Roadmap
|
||||
|
||||
- [ ] Phase 1: Foundation
|
||||
- [ ] Phase 2: API
|
||||
|
||||
### Phase 1: Foundation
|
||||
**Goal:** Setup
|
||||
**Plans:** 1 plans
|
||||
|
||||
### Phase 2: API
|
||||
**Goal:** Build API
|
||||
`
|
||||
);
|
||||
fs.writeFileSync(
|
||||
path.join(tmpDir, '.planning', 'STATE.md'),
|
||||
`# State\n\n**Current Phase:** 01\n**Current Phase Name:** Foundation\n**Status:** In progress\n**Current Plan:** 01-01\n**Last Activity:** 2025-01-01\n**Last Activity Description:** Working on phase 1\n`
|
||||
);
|
||||
|
||||
const p1 = path.join(tmpDir, '.planning', 'phases', '01-foundation');
|
||||
fs.mkdirSync(p1, { recursive: true });
|
||||
fs.writeFileSync(path.join(p1, '01-01-PLAN.md'), '# Plan');
|
||||
fs.writeFileSync(path.join(p1, '01-01-SUMMARY.md'), '# Summary');
|
||||
fs.mkdirSync(path.join(tmpDir, '.planning', 'phases', '02-api'), { recursive: true });
|
||||
|
||||
const result = runGsdTools('phase complete 1', tmpDir);
|
||||
assert.ok(result.success, `Command failed: ${result.error}`);
|
||||
|
||||
const output = JSON.parse(result.output);
|
||||
assert.strictEqual(output.completed_phase, '1');
|
||||
assert.strictEqual(output.plans_executed, '1/1');
|
||||
assert.strictEqual(output.next_phase, '02');
|
||||
assert.strictEqual(output.is_last_phase, false);
|
||||
|
||||
// Verify STATE.md updated
|
||||
const state = fs.readFileSync(path.join(tmpDir, '.planning', 'STATE.md'), 'utf-8');
|
||||
assert.ok(state.includes('**Current Phase:** 02'), 'should advance to phase 02');
|
||||
assert.ok(state.includes('**Status:** Ready to plan'), 'status should be ready to plan');
|
||||
assert.ok(state.includes('**Current Plan:** Not started'), 'plan should be reset');
|
||||
|
||||
// Verify ROADMAP checkbox
|
||||
const roadmap = fs.readFileSync(path.join(tmpDir, '.planning', 'ROADMAP.md'), 'utf-8');
|
||||
assert.ok(roadmap.includes('[x]'), 'phase should be checked off');
|
||||
assert.ok(roadmap.includes('completed'), 'completion date should be added');
|
||||
});
|
||||
|
||||
test('detects last phase in milestone', () => {
|
||||
fs.writeFileSync(
|
||||
path.join(tmpDir, '.planning', 'ROADMAP.md'),
|
||||
`# Roadmap\n### Phase 1: Only Phase\n**Goal:** Everything\n`
|
||||
);
|
||||
fs.writeFileSync(
|
||||
path.join(tmpDir, '.planning', 'STATE.md'),
|
||||
`# State\n\n**Current Phase:** 01\n**Status:** In progress\n**Current Plan:** 01-01\n**Last Activity:** 2025-01-01\n**Last Activity Description:** Working\n`
|
||||
);
|
||||
|
||||
const p1 = path.join(tmpDir, '.planning', 'phases', '01-only-phase');
|
||||
fs.mkdirSync(p1, { recursive: true });
|
||||
fs.writeFileSync(path.join(p1, '01-01-PLAN.md'), '# Plan');
|
||||
fs.writeFileSync(path.join(p1, '01-01-SUMMARY.md'), '# Summary');
|
||||
|
||||
const result = runGsdTools('phase complete 1', tmpDir);
|
||||
assert.ok(result.success, `Command failed: ${result.error}`);
|
||||
|
||||
const output = JSON.parse(result.output);
|
||||
assert.strictEqual(output.is_last_phase, true, 'should detect last phase');
|
||||
assert.strictEqual(output.next_phase, null, 'no next phase');
|
||||
|
||||
const state = fs.readFileSync(path.join(tmpDir, '.planning', 'STATE.md'), 'utf-8');
|
||||
assert.ok(state.includes('Milestone complete'), 'status should be milestone complete');
|
||||
});
|
||||
});
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// milestone complete command
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
describe('milestone complete command', () => {
|
||||
let tmpDir;
|
||||
|
||||
beforeEach(() => {
|
||||
tmpDir = createTempProject();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
cleanup(tmpDir);
|
||||
});
|
||||
|
||||
test('archives roadmap, requirements, creates MILESTONES.md', () => {
|
||||
fs.writeFileSync(
|
||||
path.join(tmpDir, '.planning', 'ROADMAP.md'),
|
||||
`# Roadmap v1.0 MVP\n\n### Phase 1: Foundation\n**Goal:** Setup\n`
|
||||
);
|
||||
fs.writeFileSync(
|
||||
path.join(tmpDir, '.planning', 'REQUIREMENTS.md'),
|
||||
`# Requirements\n\n- [ ] User auth\n- [ ] Dashboard\n`
|
||||
);
|
||||
fs.writeFileSync(
|
||||
path.join(tmpDir, '.planning', 'STATE.md'),
|
||||
`# State\n\n**Status:** In progress\n**Last Activity:** 2025-01-01\n**Last Activity Description:** Working\n`
|
||||
);
|
||||
|
||||
const p1 = path.join(tmpDir, '.planning', 'phases', '01-foundation');
|
||||
fs.mkdirSync(p1, { recursive: true });
|
||||
fs.writeFileSync(
|
||||
path.join(p1, '01-01-SUMMARY.md'),
|
||||
`---\none-liner: Set up project infrastructure\n---\n# Summary\n`
|
||||
);
|
||||
|
||||
const result = runGsdTools('milestone complete v1.0 --name MVP Foundation', tmpDir);
|
||||
assert.ok(result.success, `Command failed: ${result.error}`);
|
||||
|
||||
const output = JSON.parse(result.output);
|
||||
assert.strictEqual(output.version, 'v1.0');
|
||||
assert.strictEqual(output.phases, 1);
|
||||
assert.ok(output.archived.roadmap, 'roadmap should be archived');
|
||||
assert.ok(output.archived.requirements, 'requirements should be archived');
|
||||
|
||||
// Verify archive files exist
|
||||
assert.ok(
|
||||
fs.existsSync(path.join(tmpDir, '.planning', 'milestones', 'v1.0-ROADMAP.md')),
|
||||
'archived roadmap should exist'
|
||||
);
|
||||
assert.ok(
|
||||
fs.existsSync(path.join(tmpDir, '.planning', 'milestones', 'v1.0-REQUIREMENTS.md')),
|
||||
'archived requirements should exist'
|
||||
);
|
||||
|
||||
// Verify MILESTONES.md created
|
||||
assert.ok(
|
||||
fs.existsSync(path.join(tmpDir, '.planning', 'MILESTONES.md')),
|
||||
'MILESTONES.md should be created'
|
||||
);
|
||||
const milestones = fs.readFileSync(path.join(tmpDir, '.planning', 'MILESTONES.md'), 'utf-8');
|
||||
assert.ok(milestones.includes('v1.0 MVP Foundation'), 'milestone entry should contain name');
|
||||
assert.ok(milestones.includes('Set up project infrastructure'), 'accomplishments should be listed');
|
||||
});
|
||||
|
||||
test('appends to existing MILESTONES.md', () => {
|
||||
fs.writeFileSync(
|
||||
path.join(tmpDir, '.planning', 'MILESTONES.md'),
|
||||
`# Milestones\n\n## v0.9 Alpha (Shipped: 2025-01-01)\n\n---\n\n`
|
||||
);
|
||||
fs.writeFileSync(
|
||||
path.join(tmpDir, '.planning', 'ROADMAP.md'),
|
||||
`# Roadmap v1.0\n`
|
||||
);
|
||||
fs.writeFileSync(
|
||||
path.join(tmpDir, '.planning', 'STATE.md'),
|
||||
`# State\n\n**Status:** In progress\n**Last Activity:** 2025-01-01\n**Last Activity Description:** Working\n`
|
||||
);
|
||||
|
||||
const result = runGsdTools('milestone complete v1.0 --name Beta', tmpDir);
|
||||
assert.ok(result.success, `Command failed: ${result.error}`);
|
||||
|
||||
const milestones = fs.readFileSync(path.join(tmpDir, '.planning', 'MILESTONES.md'), 'utf-8');
|
||||
assert.ok(milestones.includes('v0.9 Alpha'), 'existing entry should be preserved');
|
||||
assert.ok(milestones.includes('v1.0 Beta'), 'new entry should be appended');
|
||||
});
|
||||
});
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// validate consistency command
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
describe('validate consistency command', () => {
|
||||
let tmpDir;
|
||||
|
||||
beforeEach(() => {
|
||||
tmpDir = createTempProject();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
cleanup(tmpDir);
|
||||
});
|
||||
|
||||
test('passes for consistent project', () => {
|
||||
fs.writeFileSync(
|
||||
path.join(tmpDir, '.planning', 'ROADMAP.md'),
|
||||
`# Roadmap\n### Phase 1: A\n### Phase 2: B\n### Phase 3: C\n`
|
||||
);
|
||||
fs.mkdirSync(path.join(tmpDir, '.planning', 'phases', '01-a'), { recursive: true });
|
||||
fs.mkdirSync(path.join(tmpDir, '.planning', 'phases', '02-b'), { recursive: true });
|
||||
fs.mkdirSync(path.join(tmpDir, '.planning', 'phases', '03-c'), { recursive: true });
|
||||
|
||||
const result = runGsdTools('validate consistency', tmpDir);
|
||||
assert.ok(result.success, `Command failed: ${result.error}`);
|
||||
|
||||
const output = JSON.parse(result.output);
|
||||
assert.strictEqual(output.passed, true, 'should pass');
|
||||
assert.strictEqual(output.warning_count, 0, 'no warnings');
|
||||
});
|
||||
|
||||
test('warns about phase on disk but not in roadmap', () => {
|
||||
fs.writeFileSync(
|
||||
path.join(tmpDir, '.planning', 'ROADMAP.md'),
|
||||
`# Roadmap\n### Phase 1: A\n`
|
||||
);
|
||||
fs.mkdirSync(path.join(tmpDir, '.planning', 'phases', '01-a'), { recursive: true });
|
||||
fs.mkdirSync(path.join(tmpDir, '.planning', 'phases', '02-orphan'), { recursive: true });
|
||||
|
||||
const result = runGsdTools('validate consistency', tmpDir);
|
||||
assert.ok(result.success, `Command failed: ${result.error}`);
|
||||
|
||||
const output = JSON.parse(result.output);
|
||||
assert.ok(output.warning_count > 0, 'should have warnings');
|
||||
assert.ok(
|
||||
output.warnings.some(w => w.includes('disk but not in ROADMAP')),
|
||||
'should warn about orphan directory'
|
||||
);
|
||||
});
|
||||
|
||||
test('warns about gaps in phase numbering', () => {
|
||||
fs.writeFileSync(
|
||||
path.join(tmpDir, '.planning', 'ROADMAP.md'),
|
||||
`# Roadmap\n### Phase 1: A\n### Phase 3: C\n`
|
||||
);
|
||||
fs.mkdirSync(path.join(tmpDir, '.planning', 'phases', '01-a'), { recursive: true });
|
||||
fs.mkdirSync(path.join(tmpDir, '.planning', 'phases', '03-c'), { recursive: true });
|
||||
|
||||
const result = runGsdTools('validate consistency', tmpDir);
|
||||
assert.ok(result.success, `Command failed: ${result.error}`);
|
||||
|
||||
const output = JSON.parse(result.output);
|
||||
assert.ok(
|
||||
output.warnings.some(w => w.includes('Gap in phase numbering')),
|
||||
'should warn about gap'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// progress command
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
describe('progress command', () => {
|
||||
let tmpDir;
|
||||
|
||||
beforeEach(() => {
|
||||
tmpDir = createTempProject();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
cleanup(tmpDir);
|
||||
});
|
||||
|
||||
test('renders JSON progress', () => {
|
||||
fs.writeFileSync(
|
||||
path.join(tmpDir, '.planning', 'ROADMAP.md'),
|
||||
`# Roadmap v1.0 MVP\n`
|
||||
);
|
||||
const p1 = path.join(tmpDir, '.planning', 'phases', '01-foundation');
|
||||
fs.mkdirSync(p1, { recursive: true });
|
||||
fs.writeFileSync(path.join(p1, '01-01-PLAN.md'), '# Plan');
|
||||
fs.writeFileSync(path.join(p1, '01-01-SUMMARY.md'), '# Done');
|
||||
fs.writeFileSync(path.join(p1, '01-02-PLAN.md'), '# Plan 2');
|
||||
|
||||
const result = runGsdTools('progress json', tmpDir);
|
||||
assert.ok(result.success, `Command failed: ${result.error}`);
|
||||
|
||||
const output = JSON.parse(result.output);
|
||||
assert.strictEqual(output.total_plans, 2, '2 total plans');
|
||||
assert.strictEqual(output.total_summaries, 1, '1 summary');
|
||||
assert.strictEqual(output.percent, 50, '50%');
|
||||
assert.strictEqual(output.phases.length, 1, '1 phase');
|
||||
assert.strictEqual(output.phases[0].status, 'In Progress', 'phase in progress');
|
||||
});
|
||||
|
||||
test('renders bar format', () => {
|
||||
fs.writeFileSync(
|
||||
path.join(tmpDir, '.planning', 'ROADMAP.md'),
|
||||
`# Roadmap v1.0\n`
|
||||
);
|
||||
const p1 = path.join(tmpDir, '.planning', 'phases', '01-test');
|
||||
fs.mkdirSync(p1, { recursive: true });
|
||||
fs.writeFileSync(path.join(p1, '01-01-PLAN.md'), '# Plan');
|
||||
fs.writeFileSync(path.join(p1, '01-01-SUMMARY.md'), '# Done');
|
||||
|
||||
const result = runGsdTools('progress bar --raw', tmpDir);
|
||||
assert.ok(result.success, `Command failed: ${result.error}`);
|
||||
assert.ok(result.output.includes('1/1'), 'should include count');
|
||||
assert.ok(result.output.includes('100%'), 'should include 100%');
|
||||
});
|
||||
|
||||
test('renders table format', () => {
|
||||
fs.writeFileSync(
|
||||
path.join(tmpDir, '.planning', 'ROADMAP.md'),
|
||||
`# Roadmap v1.0 MVP\n`
|
||||
);
|
||||
const p1 = path.join(tmpDir, '.planning', 'phases', '01-foundation');
|
||||
fs.mkdirSync(p1, { recursive: true });
|
||||
fs.writeFileSync(path.join(p1, '01-01-PLAN.md'), '# Plan');
|
||||
|
||||
const result = runGsdTools('progress table --raw', tmpDir);
|
||||
assert.ok(result.success, `Command failed: ${result.error}`);
|
||||
assert.ok(result.output.includes('Phase'), 'should have table header');
|
||||
assert.ok(result.output.includes('foundation'), 'should include phase name');
|
||||
});
|
||||
});
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// todo complete command
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
describe('todo complete command', () => {
|
||||
let tmpDir;
|
||||
|
||||
beforeEach(() => {
|
||||
tmpDir = createTempProject();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
cleanup(tmpDir);
|
||||
});
|
||||
|
||||
test('moves todo from pending to completed', () => {
|
||||
const pendingDir = path.join(tmpDir, '.planning', 'todos', 'pending');
|
||||
fs.mkdirSync(pendingDir, { recursive: true });
|
||||
fs.writeFileSync(
|
||||
path.join(pendingDir, 'add-dark-mode.md'),
|
||||
`title: Add dark mode\narea: ui\ncreated: 2025-01-01\n`
|
||||
);
|
||||
|
||||
const result = runGsdTools('todo complete add-dark-mode.md', tmpDir);
|
||||
assert.ok(result.success, `Command failed: ${result.error}`);
|
||||
|
||||
const output = JSON.parse(result.output);
|
||||
assert.strictEqual(output.completed, true);
|
||||
|
||||
// Verify moved
|
||||
assert.ok(
|
||||
!fs.existsSync(path.join(tmpDir, '.planning', 'todos', 'pending', 'add-dark-mode.md')),
|
||||
'should be removed from pending'
|
||||
);
|
||||
assert.ok(
|
||||
fs.existsSync(path.join(tmpDir, '.planning', 'todos', 'completed', 'add-dark-mode.md')),
|
||||
'should be in completed'
|
||||
);
|
||||
|
||||
// Verify completion timestamp added
|
||||
const content = fs.readFileSync(
|
||||
path.join(tmpDir, '.planning', 'todos', 'completed', 'add-dark-mode.md'),
|
||||
'utf-8'
|
||||
);
|
||||
assert.ok(content.startsWith('completed:'), 'should have completed timestamp');
|
||||
});
|
||||
|
||||
test('fails for nonexistent todo', () => {
|
||||
const result = runGsdTools('todo complete nonexistent.md', tmpDir);
|
||||
assert.ok(!result.success, 'should fail');
|
||||
assert.ok(result.error.includes('not found'), 'error mentions not found');
|
||||
});
|
||||
});
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// scaffold command
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
describe('scaffold command', () => {
|
||||
let tmpDir;
|
||||
|
||||
beforeEach(() => {
|
||||
tmpDir = createTempProject();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
cleanup(tmpDir);
|
||||
});
|
||||
|
||||
test('scaffolds context file', () => {
|
||||
fs.mkdirSync(path.join(tmpDir, '.planning', 'phases', '03-api'), { recursive: true });
|
||||
|
||||
const result = runGsdTools('scaffold context --phase 3', tmpDir);
|
||||
assert.ok(result.success, `Command failed: ${result.error}`);
|
||||
|
||||
const output = JSON.parse(result.output);
|
||||
assert.strictEqual(output.created, true);
|
||||
|
||||
// Verify file content
|
||||
const content = fs.readFileSync(
|
||||
path.join(tmpDir, '.planning', 'phases', '03-api', '03-CONTEXT.md'),
|
||||
'utf-8'
|
||||
);
|
||||
assert.ok(content.includes('Phase 3'), 'should reference phase number');
|
||||
assert.ok(content.includes('Decisions'), 'should have decisions section');
|
||||
assert.ok(content.includes('Discretion Areas'), 'should have discretion section');
|
||||
});
|
||||
|
||||
test('scaffolds UAT file', () => {
|
||||
fs.mkdirSync(path.join(tmpDir, '.planning', 'phases', '03-api'), { recursive: true });
|
||||
|
||||
const result = runGsdTools('scaffold uat --phase 3', tmpDir);
|
||||
assert.ok(result.success, `Command failed: ${result.error}`);
|
||||
|
||||
const output = JSON.parse(result.output);
|
||||
assert.strictEqual(output.created, true);
|
||||
|
||||
const content = fs.readFileSync(
|
||||
path.join(tmpDir, '.planning', 'phases', '03-api', '03-UAT.md'),
|
||||
'utf-8'
|
||||
);
|
||||
assert.ok(content.includes('User Acceptance Testing'), 'should have UAT heading');
|
||||
assert.ok(content.includes('Test Results'), 'should have test results section');
|
||||
});
|
||||
|
||||
test('scaffolds verification file', () => {
|
||||
fs.mkdirSync(path.join(tmpDir, '.planning', 'phases', '03-api'), { recursive: true });
|
||||
|
||||
const result = runGsdTools('scaffold verification --phase 3', tmpDir);
|
||||
assert.ok(result.success, `Command failed: ${result.error}`);
|
||||
|
||||
const output = JSON.parse(result.output);
|
||||
assert.strictEqual(output.created, true);
|
||||
|
||||
const content = fs.readFileSync(
|
||||
path.join(tmpDir, '.planning', 'phases', '03-api', '03-VERIFICATION.md'),
|
||||
'utf-8'
|
||||
);
|
||||
assert.ok(content.includes('Goal-Backward Verification'), 'should have verification heading');
|
||||
});
|
||||
|
||||
test('scaffolds phase directory', () => {
|
||||
const result = runGsdTools('scaffold phase-dir --phase 5 --name User Dashboard', tmpDir);
|
||||
assert.ok(result.success, `Command failed: ${result.error}`);
|
||||
|
||||
const output = JSON.parse(result.output);
|
||||
assert.strictEqual(output.created, true);
|
||||
assert.ok(
|
||||
fs.existsSync(path.join(tmpDir, '.planning', 'phases', '05-user-dashboard')),
|
||||
'directory should be created'
|
||||
);
|
||||
});
|
||||
|
||||
test('does not overwrite existing files', () => {
|
||||
const phaseDir = path.join(tmpDir, '.planning', 'phases', '03-api');
|
||||
fs.mkdirSync(phaseDir, { recursive: true });
|
||||
fs.writeFileSync(path.join(phaseDir, '03-CONTEXT.md'), '# Existing content');
|
||||
|
||||
const result = runGsdTools('scaffold context --phase 3', tmpDir);
|
||||
assert.ok(result.success, `Command failed: ${result.error}`);
|
||||
|
||||
const output = JSON.parse(result.output);
|
||||
assert.strictEqual(output.created, false, 'should not overwrite');
|
||||
assert.strictEqual(output.reason, 'already_exists');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -38,96 +38,30 @@ ERROR: No roadmap found (.planning/ROADMAP.md)
|
||||
Run /gsd:new-project to initialize.
|
||||
```
|
||||
Exit.
|
||||
|
||||
Read roadmap content for parsing.
|
||||
</step>
|
||||
|
||||
<step name="find_current_milestone">
|
||||
Parse the roadmap to find the current milestone section:
|
||||
|
||||
1. Locate the "## Current Milestone:" heading
|
||||
2. Extract milestone name and version
|
||||
3. Identify all phases under this milestone (before next "---" separator or next milestone heading)
|
||||
4. Parse existing phase numbers (including decimals if present)
|
||||
|
||||
Example structure:
|
||||
|
||||
```
|
||||
## Current Milestone: v1.0 Foundation
|
||||
|
||||
### Phase 4: Focused Command System
|
||||
### Phase 5: Path Routing & Validation
|
||||
### Phase 6: Documentation & Distribution
|
||||
```
|
||||
</step>
|
||||
|
||||
<step name="calculate_next_phase">
|
||||
Find the highest integer phase number in the current milestone:
|
||||
|
||||
1. Extract all phase numbers from phase headings (### Phase N:)
|
||||
2. Filter to integer phases only (ignore decimals like 4.1, 4.2)
|
||||
3. Find the maximum integer value
|
||||
4. Add 1 to get the next phase number
|
||||
|
||||
Example: If phases are 4, 5, 5.1, 6 → next is 7
|
||||
|
||||
Format as two-digit: `printf "%02d" $next_phase`
|
||||
</step>
|
||||
|
||||
<step name="generate_slug">
|
||||
Convert the phase description to a kebab-case slug.
|
||||
|
||||
Use `init phase-op` which provides `phase_slug` computed from phase name, or call:
|
||||
```bash
|
||||
slug=$(node ~/.claude/get-shit-done/bin/gsd-tools.js generate-slug "$description" --raw)
|
||||
```
|
||||
|
||||
Phase directory name: `{two-digit-phase}-{slug}`
|
||||
Example: `07-add-authentication`
|
||||
</step>
|
||||
|
||||
<step name="create_phase_directory">
|
||||
Create the phase directory structure:
|
||||
<step name="add_phase">
|
||||
**Delegate the phase addition to gsd-tools:**
|
||||
|
||||
```bash
|
||||
phase_dir=".planning/phases/${phase_num}-${slug}"
|
||||
mkdir -p "$phase_dir"
|
||||
RESULT=$(node ~/.claude/get-shit-done/bin/gsd-tools.js phase add "${description}")
|
||||
```
|
||||
|
||||
Confirm: "Created directory: $phase_dir"
|
||||
</step>
|
||||
The CLI handles:
|
||||
- Finding the highest existing integer phase number
|
||||
- Calculating next phase number (max + 1)
|
||||
- Generating slug from description
|
||||
- Creating the phase directory (`.planning/phases/{NN}-{slug}/`)
|
||||
- Inserting the phase entry into ROADMAP.md with Goal, Depends on, and Plans sections
|
||||
|
||||
<step name="update_roadmap">
|
||||
Add the new phase entry to the roadmap:
|
||||
|
||||
1. Find the insertion point (after last phase in current milestone, before "---" separator)
|
||||
2. Insert new phase heading:
|
||||
|
||||
```
|
||||
### Phase {N}: {Description}
|
||||
|
||||
**Goal:** [To be planned]
|
||||
**Depends on:** Phase {N-1}
|
||||
**Plans:** 0 plans
|
||||
|
||||
Plans:
|
||||
- [ ] TBD (run /gsd:plan-phase {N} to break down)
|
||||
|
||||
**Details:**
|
||||
[To be added during planning]
|
||||
```
|
||||
|
||||
3. Write updated roadmap back to file
|
||||
|
||||
Preserve all other content exactly (formatting, spacing, other phases).
|
||||
Extract from result: `phase_number`, `padded`, `name`, `slug`, `directory`.
|
||||
</step>
|
||||
|
||||
<step name="update_project_state">
|
||||
Update STATE.md to reflect the new phase:
|
||||
|
||||
1. Read `.planning/STATE.md`
|
||||
2. Under "## Current Position" → "**Next Phase:**" add reference to new phase
|
||||
3. Under "## Accumulated Context" → "### Roadmap Evolution" add entry:
|
||||
2. Under "## Accumulated Context" → "### Roadmap Evolution" add entry:
|
||||
```
|
||||
- Phase {N} added: {description}
|
||||
```
|
||||
@@ -145,7 +79,6 @@ Phase {N} added to current milestone:
|
||||
- Status: Not planned yet
|
||||
|
||||
Roadmap updated: .planning/ROADMAP.md
|
||||
Project state updated: .planning/STATE.md
|
||||
|
||||
---
|
||||
|
||||
@@ -170,10 +103,9 @@ Project state updated: .planning/STATE.md
|
||||
</process>
|
||||
|
||||
<success_criteria>
|
||||
- [ ] Phase directory created: `.planning/phases/{NN}-{slug}/`
|
||||
- [ ] `gsd-tools phase add` executed successfully
|
||||
- [ ] Phase directory created
|
||||
- [ ] Roadmap updated with new phase entry
|
||||
- [ ] STATE.md updated with roadmap evolution note
|
||||
- [ ] New phase appears at end of current milestone
|
||||
- [ ] Next phase number calculated correctly (ignoring decimals)
|
||||
- [ ] User informed of next steps
|
||||
</success_criteria>
|
||||
|
||||
@@ -37,18 +37,16 @@ When a milestone completes:
|
||||
|
||||
<step name="verify_readiness">
|
||||
|
||||
Check milestone completion:
|
||||
**Use `roadmap analyze` for comprehensive readiness check:**
|
||||
|
||||
```bash
|
||||
cat .planning/ROADMAP.md
|
||||
ls .planning/phases/*/SUMMARY.md 2>/dev/null | wc -l
|
||||
ROADMAP=$(node ~/.claude/get-shit-done/bin/gsd-tools.js roadmap analyze)
|
||||
```
|
||||
|
||||
Verify:
|
||||
This returns all phases with plan/summary counts and disk status. Use this to verify:
|
||||
- Which phases belong to this milestone?
|
||||
- All phases complete (all plans have summaries)?
|
||||
- Work tested/validated?
|
||||
- Ready to ship/tag?
|
||||
- All phases complete (all plans have summaries)? Check `disk_status === 'complete'` for each.
|
||||
- `progress_percent` should be 100%.
|
||||
|
||||
Present:
|
||||
|
||||
@@ -61,7 +59,7 @@ Includes:
|
||||
- Phase 3: Core Features (3/3 plans complete)
|
||||
- Phase 4: Polish (1/1 plan complete)
|
||||
|
||||
Total: 4 phases, 8 plans, all complete
|
||||
Total: {phase_count} phases, {total_plans} plans, all complete
|
||||
```
|
||||
|
||||
<config-check>
|
||||
@@ -152,36 +150,9 @@ Key accomplishments for this milestone:
|
||||
|
||||
<step name="create_milestone_entry">
|
||||
|
||||
Create or update `.planning/MILESTONES.md`.
|
||||
**Note:** MILESTONES.md entry is now created automatically by `gsd-tools milestone complete` in the archive_milestone step. The entry includes version, date, phase/plan/task counts, and accomplishments extracted from SUMMARY.md files.
|
||||
|
||||
If file doesn't exist, create with project name header. If exists, prepend new entry (reverse chronological).
|
||||
|
||||
Use template from `templates/milestone.md`:
|
||||
|
||||
```markdown
|
||||
## v[Version] [Name] (Shipped: YYYY-MM-DD)
|
||||
|
||||
**Delivered:** [One sentence from user]
|
||||
|
||||
**Phases completed:** [X-Y] ([Z] plans total)
|
||||
|
||||
**Key accomplishments:**
|
||||
|
||||
- [List from previous step]
|
||||
|
||||
**Stats:**
|
||||
|
||||
- [Files] files created/modified
|
||||
- [LOC] lines of [language]
|
||||
- [Phases] phases, [Plans] plans, [Tasks] tasks
|
||||
- [Days] days from [start] to ship
|
||||
|
||||
**Git range:** `feat(XX-XX)` → `feat(YY-YY)`
|
||||
|
||||
**What's next:** [Ask user: what's the next goal?]
|
||||
|
||||
---
|
||||
```
|
||||
If additional details are needed (e.g., user-provided "Delivered" summary, git range, LOC stats), add them manually after the CLI creates the base entry.
|
||||
|
||||
</step>
|
||||
|
||||
@@ -370,103 +341,71 @@ Update `.planning/ROADMAP.md` — group completed milestone phases:
|
||||
|
||||
<step name="archive_milestone">
|
||||
|
||||
Extract completed milestone details to archive.
|
||||
**Delegate archival to gsd-tools:**
|
||||
|
||||
1. Create `.planning/milestones/v[X.Y]-ROADMAP.md`
|
||||
2. Read `~/.claude/get-shit-done/templates/milestone-archive.md` template
|
||||
3. Extract from ROADMAP.md: all phases in milestone (by number range), full phase details, plan lists with checkmarks
|
||||
4. Extract from PROJECT.md: key decisions, validated requirements
|
||||
5. Fill template placeholders: {{VERSION}}, {{MILESTONE_NAME}}, {{DATE}}, {{PHASE_START}}, {{PHASE_END}}, {{TOTAL_PLANS}}, {{MILESTONE_DESCRIPTION}}, {{PHASES_SECTION}}, {{DECISIONS_FROM_PROJECT}}, {{ISSUES_RESOLVED_DURING_MILESTONE}}
|
||||
6. Write to `.planning/milestones/v[X.Y]-ROADMAP.md`
|
||||
7. Delete ROADMAP.md:
|
||||
```bash
|
||||
rm .planning/ROADMAP.md
|
||||
```
|
||||
8. Verify:
|
||||
```bash
|
||||
ls .planning/milestones/v[X.Y]-ROADMAP.md
|
||||
```
|
||||
9. Confirm:
|
||||
```
|
||||
✅ v[X.Y] roadmap archived to milestones/v[X.Y]-ROADMAP.md
|
||||
✅ ROADMAP.md deleted (fresh one for next milestone)
|
||||
```
|
||||
```bash
|
||||
ARCHIVE=$(node ~/.claude/get-shit-done/bin/gsd-tools.js milestone complete "v[X.Y]" --name "[Milestone Name]")
|
||||
```
|
||||
|
||||
The CLI handles:
|
||||
- Creating `.planning/milestones/` directory
|
||||
- Archiving ROADMAP.md to `milestones/v[X.Y]-ROADMAP.md`
|
||||
- Archiving REQUIREMENTS.md to `milestones/v[X.Y]-REQUIREMENTS.md` with archive header
|
||||
- Moving audit file to milestones if it exists
|
||||
- Creating/appending MILESTONES.md entry with accomplishments from SUMMARY.md files
|
||||
- Updating STATE.md (status, last activity)
|
||||
|
||||
Extract from result: `version`, `date`, `phases`, `plans`, `tasks`, `accomplishments`, `archived`.
|
||||
|
||||
Verify: `✅ Milestone archived to .planning/milestones/`
|
||||
|
||||
**Note:** Phase directories (`.planning/phases/`) are NOT deleted — they accumulate across milestones as raw execution history. Phase numbering continues (v1.0 phases 1-4, v1.1 phases 5-8, etc.).
|
||||
|
||||
</step>
|
||||
|
||||
<step name="archive_requirements">
|
||||
|
||||
Archive requirements for next milestone.
|
||||
|
||||
1. Read REQUIREMENTS.md:
|
||||
```bash
|
||||
cat .planning/REQUIREMENTS.md
|
||||
```
|
||||
|
||||
2. Create `.planning/milestones/v[X.Y]-REQUIREMENTS.md`
|
||||
|
||||
3. Transform: mark all requirements `[x]` complete, add outcome notes, update traceability table to "Complete", add milestone summary (total shipped, scope changes, drops)
|
||||
|
||||
4. Write archive with header:
|
||||
```markdown
|
||||
# Requirements Archive: v[X.Y] [Milestone Name]
|
||||
|
||||
**Archived:** [DATE]
|
||||
**Status:** ✅ SHIPPED
|
||||
|
||||
For current requirements, see `.planning/REQUIREMENTS.md`.
|
||||
|
||||
---
|
||||
|
||||
[Full REQUIREMENTS.md content with checkboxes marked complete]
|
||||
|
||||
---
|
||||
|
||||
## Milestone Summary
|
||||
|
||||
**Shipped:** [X] of [Y] requirements
|
||||
**Adjusted:** [list any that changed]
|
||||
**Dropped:** [list any removed and why]
|
||||
|
||||
---
|
||||
*Archived: [DATE] as part of v[X.Y] milestone completion*
|
||||
```
|
||||
|
||||
5. Delete original:
|
||||
```bash
|
||||
rm .planning/REQUIREMENTS.md
|
||||
```
|
||||
|
||||
6. Confirm:
|
||||
```
|
||||
✅ Requirements archived to milestones/v[X.Y]-REQUIREMENTS.md
|
||||
✅ REQUIREMENTS.md deleted (fresh one for next milestone)
|
||||
```
|
||||
|
||||
**Note:** Next milestone starts with `/gsd:new-milestone` which includes requirements definition. PROJECT.md's Validated section carries cumulative record across milestones.
|
||||
After archival, the AI still handles:
|
||||
- Reorganizing ROADMAP.md with milestone grouping (requires judgment)
|
||||
- Full PROJECT.md evolution review (requires understanding)
|
||||
- Deleting original ROADMAP.md and REQUIREMENTS.md
|
||||
- These are NOT fully delegated because they require AI interpretation of content
|
||||
|
||||
</step>
|
||||
|
||||
<step name="archive_audit">
|
||||
<step name="reorganize_roadmap_and_delete_originals">
|
||||
|
||||
Move audit file to archive (if exists). Use init context or check directly:
|
||||
After `milestone complete` has archived, reorganize ROADMAP.md with milestone groupings, then delete originals:
|
||||
|
||||
```bash
|
||||
# Check if audit file exists
|
||||
[ -f ".planning/v[X.Y]-MILESTONE-AUDIT.md" ] && mv .planning/v[X.Y]-MILESTONE-AUDIT.md .planning/milestones/
|
||||
**Reorganize ROADMAP.md** — group completed milestone phases:
|
||||
|
||||
```markdown
|
||||
# Roadmap: [Project Name]
|
||||
|
||||
## Milestones
|
||||
|
||||
- ✅ **v1.0 MVP** — Phases 1-4 (shipped YYYY-MM-DD)
|
||||
- 🚧 **v1.1 Security** — Phases 5-6 (in progress)
|
||||
|
||||
## Phases
|
||||
|
||||
<details>
|
||||
<summary>✅ v1.0 MVP (Phases 1-4) — SHIPPED YYYY-MM-DD</summary>
|
||||
|
||||
- [x] Phase 1: Foundation (2/2 plans) — completed YYYY-MM-DD
|
||||
- [x] Phase 2: Authentication (2/2 plans) — completed YYYY-MM-DD
|
||||
|
||||
</details>
|
||||
```
|
||||
|
||||
Confirm: `✅ Audit archived to milestones/v[X.Y]-MILESTONE-AUDIT.md`
|
||||
**Then delete originals:**
|
||||
|
||||
Skip silently if no audit file — audit is optional.
|
||||
```bash
|
||||
rm .planning/ROADMAP.md
|
||||
rm .planning/REQUIREMENTS.md
|
||||
```
|
||||
|
||||
</step>
|
||||
|
||||
<step name="update_state">
|
||||
|
||||
Update STATE.md for milestone completion.
|
||||
Most STATE.md updates were handled by `milestone complete`, but verify and update remaining fields:
|
||||
|
||||
**Project Reference:**
|
||||
|
||||
@@ -479,17 +418,6 @@ See: .planning/PROJECT.md (updated [today])
|
||||
**Current focus:** [Next milestone or "Planning next milestone"]
|
||||
```
|
||||
|
||||
**Current Position:**
|
||||
|
||||
```markdown
|
||||
Phase: [Next phase] of [Total] ([Phase name])
|
||||
Plan: Not started
|
||||
Status: Ready to plan
|
||||
Last activity: [today] — v[X.Y] milestone complete
|
||||
|
||||
Progress: [updated progress bar]
|
||||
```
|
||||
|
||||
**Accumulated Context:**
|
||||
- Clear decisions summary (full log in PROJECT.md)
|
||||
- Clear resolved blockers
|
||||
|
||||
@@ -17,31 +17,17 @@ Example: `/gsd:insert-phase 72 Fix critical auth bug`
|
||||
-> after = 72
|
||||
-> description = "Fix critical auth bug"
|
||||
|
||||
Validation:
|
||||
If arguments missing:
|
||||
|
||||
```bash
|
||||
if [ $# -lt 2 ]; then
|
||||
echo "ERROR: Both phase number and description required"
|
||||
echo "Usage: /gsd:insert-phase <after> <description>"
|
||||
echo "Example: /gsd:insert-phase 72 Fix critical auth bug"
|
||||
exit 1
|
||||
fi
|
||||
```
|
||||
ERROR: Both phase number and description required
|
||||
Usage: /gsd:insert-phase <after> <description>
|
||||
Example: /gsd:insert-phase 72 Fix critical auth bug
|
||||
```
|
||||
|
||||
Parse first argument as integer:
|
||||
|
||||
```bash
|
||||
after_phase=$1
|
||||
shift
|
||||
description="$*"
|
||||
|
||||
# Validate after_phase is an integer
|
||||
if ! [[ "$after_phase" =~ ^[0-9]+$ ]]; then
|
||||
echo "ERROR: Phase number must be an integer"
|
||||
exit 1
|
||||
fi
|
||||
```
|
||||
Exit.
|
||||
|
||||
Validate first argument is an integer.
|
||||
</step>
|
||||
|
||||
<step name="init_context">
|
||||
@@ -56,111 +42,35 @@ Check `roadmap_exists` from init JSON. If false:
|
||||
ERROR: No roadmap found (.planning/ROADMAP.md)
|
||||
```
|
||||
Exit.
|
||||
|
||||
Read roadmap content for parsing.
|
||||
</step>
|
||||
|
||||
<step name="verify_target_phase">
|
||||
Verify that the target phase exists in the roadmap:
|
||||
|
||||
1. Search for "### Phase {after_phase}:" heading
|
||||
2. If not found:
|
||||
|
||||
```
|
||||
ERROR: Phase {after_phase} not found in roadmap
|
||||
Available phases: [list phase numbers]
|
||||
```
|
||||
|
||||
Exit.
|
||||
|
||||
3. Verify phase is in current milestone (not completed/archived)
|
||||
</step>
|
||||
|
||||
<step name="find_existing_decimals">
|
||||
Calculate next decimal phase number:
|
||||
<step name="insert_phase">
|
||||
**Delegate the phase insertion to gsd-tools:**
|
||||
|
||||
```bash
|
||||
DECIMAL_INFO=$(node ~/.claude/get-shit-done/bin/gsd-tools.js phase next-decimal "${after_phase}")
|
||||
RESULT=$(node ~/.claude/get-shit-done/bin/gsd-tools.js phase insert "${after_phase}" "${description}")
|
||||
```
|
||||
|
||||
Extract from JSON:
|
||||
- `next`: The next available decimal (e.g., "06.1", "06.3")
|
||||
- `existing`: Array of existing decimals (e.g., ["06.1", "06.2"])
|
||||
- `base_phase`: Normalized base phase (e.g., "06")
|
||||
The CLI handles:
|
||||
- Verifying target phase exists in ROADMAP.md
|
||||
- Calculating next decimal phase number (checking existing decimals on disk)
|
||||
- Generating slug from description
|
||||
- Creating the phase directory (`.planning/phases/{N.M}-{slug}/`)
|
||||
- Inserting the phase entry into ROADMAP.md after the target phase with (INSERTED) marker
|
||||
|
||||
Store the result:
|
||||
```bash
|
||||
decimal_phase=$(echo "$DECIMAL_INFO" | jq -r '.next')
|
||||
```
|
||||
|
||||
Examples:
|
||||
- Phase 72 with no decimals -> next is 72.1
|
||||
- Phase 72 with 72.1 -> next is 72.2
|
||||
- Phase 72 with 72.1, 72.2 -> next is 72.3
|
||||
</step>
|
||||
|
||||
<step name="generate_slug">
|
||||
Convert the phase description to a kebab-case slug.
|
||||
|
||||
Use `generate-slug` command (init phase-op provides `phase_slug` for existing phase, but this is a new phase):
|
||||
```bash
|
||||
slug=$(node ~/.claude/get-shit-done/bin/gsd-tools.js generate-slug "$description" --raw)
|
||||
```
|
||||
|
||||
Phase directory name: `{decimal-phase}-{slug}`
|
||||
Example: `06.1-fix-critical-auth-bug` (phase 6 insertion)
|
||||
</step>
|
||||
|
||||
<step name="create_phase_directory">
|
||||
Create the phase directory structure:
|
||||
|
||||
```bash
|
||||
phase_dir=".planning/phases/${decimal_phase}-${slug}"
|
||||
mkdir -p "$phase_dir"
|
||||
```
|
||||
|
||||
Confirm: "Created directory: $phase_dir"
|
||||
</step>
|
||||
|
||||
<step name="update_roadmap">
|
||||
Insert the new phase entry into the roadmap:
|
||||
|
||||
1. Find insertion point: immediately after Phase {after_phase}'s content (before next phase heading or "---")
|
||||
2. Insert new phase heading with (INSERTED) marker:
|
||||
|
||||
```
|
||||
### Phase {decimal_phase}: {Description} (INSERTED)
|
||||
|
||||
**Goal:** [Urgent work - to be planned]
|
||||
**Depends on:** Phase {after_phase}
|
||||
**Plans:** 0 plans
|
||||
|
||||
Plans:
|
||||
- [ ] TBD (run /gsd:plan-phase {decimal_phase} to break down)
|
||||
|
||||
**Details:**
|
||||
[To be added during planning]
|
||||
```
|
||||
|
||||
3. Write updated roadmap back to file
|
||||
|
||||
The "(INSERTED)" marker helps identify decimal phases as urgent insertions.
|
||||
|
||||
Preserve all other content exactly (formatting, spacing, other phases).
|
||||
Extract from result: `phase_number`, `after_phase`, `name`, `slug`, `directory`.
|
||||
</step>
|
||||
|
||||
<step name="update_project_state">
|
||||
Update STATE.md to reflect the inserted phase:
|
||||
|
||||
1. Read `.planning/STATE.md`
|
||||
2. Under "## Accumulated Context" -> "### Roadmap Evolution" add entry:
|
||||
2. Under "## Accumulated Context" → "### Roadmap Evolution" add entry:
|
||||
```
|
||||
- Phase {decimal_phase} inserted after Phase {after_phase}: {description} (URGENT)
|
||||
```
|
||||
|
||||
If "Roadmap Evolution" section doesn't exist, create it.
|
||||
|
||||
Add note about insertion reason if appropriate.
|
||||
</step>
|
||||
|
||||
<step name="completion">
|
||||
@@ -173,7 +83,7 @@ Phase {decimal_phase} inserted after Phase {after_phase}:
|
||||
- Status: Not planned yet
|
||||
- Marker: (INSERTED) - indicates urgent work
|
||||
|
||||
Roadmap updated: {roadmap-path}
|
||||
Roadmap updated: .planning/ROADMAP.md
|
||||
Project state updated: .planning/STATE.md
|
||||
|
||||
---
|
||||
@@ -211,10 +121,9 @@ Project state updated: .planning/STATE.md
|
||||
<success_criteria>
|
||||
Phase insertion is complete when:
|
||||
|
||||
- [ ] Phase directory created: `.planning/phases/{N.M}-{slug}/`
|
||||
- [ ] `gsd-tools phase insert` executed successfully
|
||||
- [ ] Phase directory created
|
||||
- [ ] Roadmap updated with new phase entry (includes "(INSERTED)" marker)
|
||||
- [ ] Phase inserted in correct position (after target phase, before next integer phase)
|
||||
- [ ] STATE.md updated with roadmap evolution note
|
||||
- [ ] Decimal number calculated correctly (based on existing decimals)
|
||||
- [ ] User informed of next steps and dependency implications
|
||||
</success_criteria>
|
||||
|
||||
@@ -50,42 +50,68 @@ All file contents are already loaded via `--include` in init_context step:
|
||||
No additional file reads needed.
|
||||
</step>
|
||||
|
||||
<step name="analyze_roadmap">
|
||||
**Get comprehensive roadmap analysis (replaces manual parsing):**
|
||||
|
||||
```bash
|
||||
ROADMAP=$(node ~/.claude/get-shit-done/bin/gsd-tools.js roadmap analyze)
|
||||
```
|
||||
|
||||
This returns structured JSON with:
|
||||
- All phases with disk status (complete/partial/planned/empty/no_directory)
|
||||
- Goal and dependencies per phase
|
||||
- Plan and summary counts per phase
|
||||
- Aggregated stats: total plans, summaries, progress percent
|
||||
- Current and next phase identification
|
||||
|
||||
Use this instead of manually reading/parsing ROADMAP.md.
|
||||
</step>
|
||||
|
||||
<step name="recent">
|
||||
**Gather recent work context:**
|
||||
|
||||
- Find the 2-3 most recent SUMMARY.md files
|
||||
- Extract from each: what was accomplished, key decisions, any issues logged
|
||||
- Use `summary-extract` for efficient parsing:
|
||||
```bash
|
||||
node ~/.claude/get-shit-done/bin/gsd-tools.js summary-extract <path> --fields one_liner
|
||||
```
|
||||
- This shows "what we've been working on"
|
||||
</step>
|
||||
|
||||
<step name="position">
|
||||
**Parse current position from init context:**
|
||||
**Parse current position from init context and roadmap analysis:**
|
||||
|
||||
- Use `current_phase` and `next_phase` from init for position
|
||||
- Use `phases` array for plan counts per phase
|
||||
- Note `paused_at` if work was paused
|
||||
- Check for CONTEXT.md: For phases without PLAN.md files, check if `{phase}-CONTEXT.md` exists in phase directory
|
||||
- Use `current_phase` and `next_phase` from roadmap analyze
|
||||
- Use phase-level `has_context` and `has_research` flags from analyze
|
||||
- Note `paused_at` if work was paused (from init context)
|
||||
- Count pending todos: use `init todos` or `list-todos`
|
||||
- Check for active debug sessions: `ls .planning/debug/*.md 2>/dev/null | grep -v resolved | wc -l`
|
||||
</step>
|
||||
|
||||
<step name="report">
|
||||
**Present rich status report:**
|
||||
**Generate progress bar from gsd-tools, then present rich status report:**
|
||||
|
||||
```bash
|
||||
# Get formatted progress bar
|
||||
PROGRESS_BAR=$(node ~/.claude/get-shit-done/bin/gsd-tools.js progress bar --raw)
|
||||
```
|
||||
|
||||
Present:
|
||||
|
||||
```
|
||||
# [Project Name]
|
||||
|
||||
**Progress:** [████████░░] 8/10 plans complete
|
||||
**Progress:** {PROGRESS_BAR}
|
||||
**Profile:** [quality/balanced/budget]
|
||||
|
||||
## Recent Work
|
||||
- [Phase X, Plan Y]: [what was accomplished - 1 line]
|
||||
- [Phase X, Plan Z]: [what was accomplished - 1 line]
|
||||
- [Phase X, Plan Y]: [what was accomplished - 1 line from summary-extract]
|
||||
- [Phase X, Plan Z]: [what was accomplished - 1 line from summary-extract]
|
||||
|
||||
## Current Position
|
||||
Phase [N] of [total]: [phase-name]
|
||||
Plan [M] of [phase-total]: [status]
|
||||
CONTEXT: [✓ if CONTEXT.md exists | - if not]
|
||||
CONTEXT: [✓ if has_context | - if not]
|
||||
|
||||
## Key Decisions Made
|
||||
- [decision 1 from STATE.md]
|
||||
@@ -102,7 +128,7 @@ CONTEXT: [✓ if CONTEXT.md exists | - if not]
|
||||
(Only show this section if count > 0)
|
||||
|
||||
## What's Next
|
||||
[Next phase/plan objective from ROADMAP]
|
||||
[Next phase/plan objective from roadmap analyze]
|
||||
```
|
||||
|
||||
</step>
|
||||
|
||||
@@ -37,20 +37,6 @@ Extract: `phase_found`, `phase_dir`, `phase_number`, `commit_docs`, `roadmap_exi
|
||||
Also read STATE.md and ROADMAP.md content for parsing current position.
|
||||
</step>
|
||||
|
||||
<step name="validate_phase_exists">
|
||||
Verify the target phase exists in ROADMAP.md:
|
||||
|
||||
1. Search for `### Phase {target}:` heading
|
||||
2. If not found:
|
||||
|
||||
```
|
||||
ERROR: Phase {target} not found in roadmap
|
||||
Available phases: [list phase numbers]
|
||||
```
|
||||
|
||||
Exit.
|
||||
</step>
|
||||
|
||||
<step name="validate_future_phase">
|
||||
Verify the phase is a future phase (not started):
|
||||
|
||||
@@ -70,46 +56,6 @@ To abandon current work, use /gsd:pause-work instead.
|
||||
```
|
||||
|
||||
Exit.
|
||||
|
||||
3. Check for SUMMARY.md files in phase directory:
|
||||
|
||||
```bash
|
||||
ls .planning/phases/{target}-*/*-SUMMARY.md 2>/dev/null
|
||||
```
|
||||
|
||||
If any SUMMARY.md files exist:
|
||||
|
||||
```
|
||||
ERROR: Phase {target} has completed work
|
||||
|
||||
Found executed plans:
|
||||
- {list of SUMMARY.md files}
|
||||
|
||||
Cannot remove phases with completed work.
|
||||
```
|
||||
|
||||
Exit.
|
||||
</step>
|
||||
|
||||
<step name="gather_phase_info">
|
||||
Collect information about the phase being removed:
|
||||
|
||||
1. Extract phase name from ROADMAP.md heading: `### Phase {target}: {Name}`
|
||||
2. Find phase directory: `.planning/phases/{target}-{slug}/`
|
||||
3. Find all subsequent phases (integer and decimal) that need renumbering
|
||||
|
||||
**Subsequent phase detection:**
|
||||
|
||||
For integer phase removal (e.g., 17):
|
||||
- Find all phases > 17 (integers: 18, 19, 20...)
|
||||
- Find all decimal phases >= 17.0 and < 18.0 (17.1, 17.2...) → these become 16.x
|
||||
- Find all decimal phases for subsequent integers (18.1, 19.1...) → renumber with their parent
|
||||
|
||||
For decimal phase removal (e.g., 17.1):
|
||||
- Find all decimal phases > 17.1 and < 18 (17.2, 17.3...) → renumber down
|
||||
- Integer phases unchanged
|
||||
|
||||
List all phases that will be renumbered.
|
||||
</step>
|
||||
|
||||
<step name="confirm_removal">
|
||||
@@ -120,11 +66,8 @@ Removing Phase {target}: {Name}
|
||||
|
||||
This will:
|
||||
- Delete: .planning/phases/{target}-{slug}/
|
||||
- Renumber {N} subsequent phases:
|
||||
- Phase 18 → Phase 17
|
||||
- Phase 18.1 → Phase 17.1
|
||||
- Phase 19 → Phase 18
|
||||
[etc.]
|
||||
- Renumber all subsequent phases
|
||||
- Update: ROADMAP.md, STATE.md
|
||||
|
||||
Proceed? (y/n)
|
||||
```
|
||||
@@ -132,110 +75,27 @@ Proceed? (y/n)
|
||||
Wait for confirmation.
|
||||
</step>
|
||||
|
||||
<step name="delete_phase_directory">
|
||||
Use the init context from earlier. The `phase_dir` from `init phase-op` provides the path.
|
||||
|
||||
Delete the target phase directory if it exists:
|
||||
<step name="execute_removal">
|
||||
**Delegate the entire removal operation to gsd-tools:**
|
||||
|
||||
```bash
|
||||
# phase_dir from init phase-op contains the path if found
|
||||
if [ -n "$phase_dir" ]; then
|
||||
rm -rf "$phase_dir"
|
||||
echo "Deleted: $phase_dir/"
|
||||
fi
|
||||
RESULT=$(node ~/.claude/get-shit-done/bin/gsd-tools.js phase remove "${target}")
|
||||
```
|
||||
|
||||
If directory doesn't exist (`phase_found=false` from init), note: "No directory to delete (phase not yet created)"
|
||||
</step>
|
||||
|
||||
<step name="renumber_directories">
|
||||
Rename all subsequent phase directories:
|
||||
|
||||
For each phase directory that needs renumbering (in reverse order to avoid conflicts):
|
||||
If the phase has executed plans (SUMMARY.md files), gsd-tools will error. Use `--force` only if the user confirms:
|
||||
|
||||
```bash
|
||||
# Example: renaming 18-dashboard to 17-dashboard
|
||||
mv ".planning/phases/18-dashboard" ".planning/phases/17-dashboard"
|
||||
RESULT=$(node ~/.claude/get-shit-done/bin/gsd-tools.js phase remove "${target}" --force)
|
||||
```
|
||||
|
||||
Process in descending order (20→19, then 19→18, then 18→17) to avoid overwriting.
|
||||
The CLI handles:
|
||||
- Deleting the phase directory
|
||||
- Renumbering all subsequent directories (in reverse order to avoid conflicts)
|
||||
- Renaming all files inside renumbered directories (PLAN.md, SUMMARY.md, etc.)
|
||||
- Updating ROADMAP.md (removing section, renumbering all phase references, updating dependencies)
|
||||
- Updating STATE.md (decrementing phase count)
|
||||
|
||||
Also rename decimal phase directories:
|
||||
- `17.1-fix-bug` → `16.1-fix-bug` (if removing integer 17)
|
||||
- `17.2-hotfix` → `17.1-hotfix` (if removing decimal 17.1)
|
||||
</step>
|
||||
|
||||
<step name="rename_files_in_directories">
|
||||
Rename plan files inside renumbered directories:
|
||||
|
||||
For each renumbered directory, rename files that contain the phase number:
|
||||
|
||||
```bash
|
||||
# Inside 17-dashboard (was 18-dashboard):
|
||||
mv "18-01-PLAN.md" "17-01-PLAN.md"
|
||||
mv "18-02-PLAN.md" "17-02-PLAN.md"
|
||||
mv "18-01-SUMMARY.md" "17-01-SUMMARY.md" # if exists
|
||||
# etc.
|
||||
```
|
||||
|
||||
Also handle CONTEXT.md and DISCOVERY.md (these don't have phase prefixes, so no rename needed).
|
||||
</step>
|
||||
|
||||
<step name="update_roadmap">
|
||||
Update ROADMAP.md:
|
||||
|
||||
1. **Remove the phase section entirely:**
|
||||
- Delete from `### Phase {target}:` to the next phase heading (or section end)
|
||||
|
||||
2. **Remove from phase list:**
|
||||
- Delete line `- [ ] **Phase {target}: {Name}**` or similar
|
||||
|
||||
3. **Remove from Progress table:**
|
||||
- Delete the row for Phase {target}
|
||||
|
||||
4. **Renumber all subsequent phases:**
|
||||
- `### Phase 18:` → `### Phase 17:`
|
||||
- `- [ ] **Phase 18:` → `- [ ] **Phase 17:`
|
||||
- Table rows: `| 18. Dashboard |` → `| 17. Dashboard |`
|
||||
- Plan references: `18-01:` → `17-01:`
|
||||
|
||||
5. **Update dependency references:**
|
||||
- `**Depends on:** Phase 18` → `**Depends on:** Phase 17`
|
||||
- For the phase that depended on the removed phase:
|
||||
- `**Depends on:** Phase 17` (removed) → `**Depends on:** Phase 16`
|
||||
|
||||
6. **Renumber decimal phases:**
|
||||
- `### Phase 17.1:` → `### Phase 16.1:` (if integer 17 removed)
|
||||
- Update all references consistently
|
||||
|
||||
Write updated ROADMAP.md.
|
||||
</step>
|
||||
|
||||
<step name="update_state">
|
||||
Update STATE.md:
|
||||
|
||||
1. **Update total phase count:**
|
||||
- `Phase: 16 of 20` → `Phase: 16 of 19`
|
||||
|
||||
2. **Recalculate progress percentage:**
|
||||
- New percentage based on completed plans / new total plans
|
||||
|
||||
Do NOT add a "Roadmap Evolution" note - the git commit is the record.
|
||||
|
||||
Write updated STATE.md.
|
||||
</step>
|
||||
|
||||
<step name="update_file_contents">
|
||||
Search for and update phase references inside plan files:
|
||||
|
||||
```bash
|
||||
# Find files that reference the old phase numbers
|
||||
grep -r "Phase 18" .planning/phases/17-*/ 2>/dev/null
|
||||
grep -r "Phase 19" .planning/phases/18-*/ 2>/dev/null
|
||||
# etc.
|
||||
```
|
||||
|
||||
Update any internal references to reflect new numbering.
|
||||
Extract from result: `removed`, `directory_deleted`, `renamed_directories`, `renamed_files`, `roadmap_updated`, `state_updated`.
|
||||
</step>
|
||||
|
||||
<step name="commit">
|
||||
@@ -256,13 +116,10 @@ Phase {target} ({original-name}) removed.
|
||||
|
||||
Changes:
|
||||
- Deleted: .planning/phases/{target}-{slug}/
|
||||
- Renumbered: Phases {first-renumbered}-{last-old} → {first-renumbered-1}-{last-new}
|
||||
- Renumbered: {N} directories and {M} files
|
||||
- Updated: ROADMAP.md, STATE.md
|
||||
- Committed: chore: remove phase {target} ({original-name})
|
||||
|
||||
Current roadmap: {total-remaining} phases
|
||||
Current position: Phase {current} of {new-total}
|
||||
|
||||
---
|
||||
|
||||
## What's Next
|
||||
@@ -280,47 +137,18 @@ Would you like to:
|
||||
|
||||
<anti_patterns>
|
||||
|
||||
- Don't remove completed phases (have SUMMARY.md files)
|
||||
- Don't remove completed phases (have SUMMARY.md files) without --force
|
||||
- Don't remove current or past phases
|
||||
- Don't leave gaps in numbering - always renumber
|
||||
- Don't add "removed phase" notes to STATE.md - git commit is the record
|
||||
- Don't ask about each decimal phase - just renumber them
|
||||
- Don't manually renumber — use `gsd-tools phase remove` which handles all renumbering
|
||||
- Don't add "removed phase" notes to STATE.md — git commit is the record
|
||||
- Don't modify completed phase directories
|
||||
</anti_patterns>
|
||||
|
||||
<edge_cases>
|
||||
|
||||
**Removing a decimal phase (e.g., 17.1):**
|
||||
- Only affects other decimals in same series (17.2 → 17.1, 17.3 → 17.2)
|
||||
- Integer phases unchanged
|
||||
- Simpler operation
|
||||
|
||||
**No subsequent phases to renumber:**
|
||||
- Removing the last phase (e.g., Phase 20 when that's the end)
|
||||
- Just delete and update ROADMAP.md, no renumbering needed
|
||||
|
||||
**Phase directory doesn't exist:**
|
||||
- Phase may be in ROADMAP.md but directory not created yet
|
||||
- Skip directory deletion, proceed with ROADMAP.md updates
|
||||
|
||||
**Decimal phases under removed integer:**
|
||||
- Removing Phase 17 when 17.1, 17.2 exist
|
||||
- 17.1 → 16.1, 17.2 → 16.2
|
||||
- They maintain their position in execution order (after current last integer)
|
||||
|
||||
</edge_cases>
|
||||
|
||||
<success_criteria>
|
||||
Phase removal is complete when:
|
||||
|
||||
- [ ] Target phase validated as future/unstarted
|
||||
- [ ] Phase directory deleted (if existed)
|
||||
- [ ] All subsequent phase directories renumbered
|
||||
- [ ] Files inside directories renamed ({old}-01-PLAN.md → {new}-01-PLAN.md)
|
||||
- [ ] ROADMAP.md updated (section removed, all references renumbered)
|
||||
- [ ] STATE.md updated (phase count, progress percentage)
|
||||
- [ ] Dependency references updated in subsequent phases
|
||||
- [ ] `gsd-tools phase remove` executed successfully
|
||||
- [ ] Changes committed with descriptive message
|
||||
- [ ] No gaps in phase numbering
|
||||
- [ ] User informed of changes
|
||||
</success_criteria>
|
||||
|
||||
@@ -118,39 +118,22 @@ If found, delete them — phase is complete, handoffs are stale.
|
||||
|
||||
</step>
|
||||
|
||||
<step name="update_roadmap">
|
||||
<step name="update_roadmap_and_state">
|
||||
|
||||
Update the roadmap file:
|
||||
**Delegate ROADMAP.md and STATE.md updates to gsd-tools:**
|
||||
|
||||
```bash
|
||||
ROADMAP_FILE=".planning/ROADMAP.md"
|
||||
TRANSITION=$(node ~/.claude/get-shit-done/bin/gsd-tools.js phase complete "${current_phase}")
|
||||
```
|
||||
|
||||
Update the file:
|
||||
The CLI handles:
|
||||
- Marking the phase checkbox as `[x]` complete with today's date
|
||||
- Updating plan count to final (e.g., "3/3 plans complete")
|
||||
- Updating the Progress table (Status → Complete, adding date)
|
||||
- Advancing STATE.md to next phase (Current Phase, Status → Ready to plan, Current Plan → Not started)
|
||||
- Detecting if this is the last phase in the milestone
|
||||
|
||||
- Mark current phase: `[x] Complete`
|
||||
- Add completion date
|
||||
- Update plan count to final (e.g., "3/3 plans complete")
|
||||
- Update Progress table
|
||||
- Keep next phase as `[ ] Not started`
|
||||
|
||||
**Example:**
|
||||
|
||||
```markdown
|
||||
## Phases
|
||||
|
||||
- [x] Phase 1: Foundation (completed 2025-01-15)
|
||||
- [ ] Phase 2: Authentication ← Next
|
||||
- [ ] Phase 3: Core Features
|
||||
|
||||
## Progress
|
||||
|
||||
| Phase | Plans Complete | Status | Completed |
|
||||
| ----------------- | -------------- | ----------- | ---------- |
|
||||
| 1. Foundation | 3/3 | Complete | 2025-01-15 |
|
||||
| 2. Authentication | 0/2 | Not started | - |
|
||||
| 3. Core Features | 0/1 | Not started | - |
|
||||
```
|
||||
Extract from result: `completed_phase`, `plans_executed`, `next_phase`, `next_phase_name`, `is_last_phase`.
|
||||
|
||||
</step>
|
||||
|
||||
@@ -250,61 +233,21 @@ After (Phase 2 shipped JWT auth, discovered rate limiting needed):
|
||||
|
||||
<step name="update_current_position_after_transition">
|
||||
|
||||
Update Current Position section in STATE.md to reflect phase completion and transition.
|
||||
**Note:** Basic position updates (Current Phase, Status, Current Plan, Last Activity) were already handled by `gsd-tools phase complete` in the update_roadmap_and_state step.
|
||||
|
||||
**Format:**
|
||||
Verify the updates are correct by reading STATE.md. If the progress bar needs updating, use:
|
||||
|
||||
```markdown
|
||||
Phase: [next] of [total] ([Next phase name])
|
||||
Plan: Not started
|
||||
Status: Ready to plan
|
||||
Last activity: [today] — Phase [X] complete, transitioned to Phase [X+1]
|
||||
|
||||
Progress: [updated progress bar]
|
||||
```bash
|
||||
PROGRESS=$(node ~/.claude/get-shit-done/bin/gsd-tools.js progress bar --raw)
|
||||
```
|
||||
|
||||
**Instructions:**
|
||||
|
||||
- Increment phase number to next phase
|
||||
- Reset plan to "Not started"
|
||||
- Set status to "Ready to plan"
|
||||
- Update last activity to describe transition
|
||||
- Recalculate progress bar based on completed plans
|
||||
|
||||
**Example — transitioning from Phase 2 to Phase 3:**
|
||||
|
||||
Before:
|
||||
|
||||
```markdown
|
||||
## Current Position
|
||||
|
||||
Phase: 2 of 4 (Authentication)
|
||||
Plan: 2 of 2 in current phase
|
||||
Status: Phase complete
|
||||
Last activity: 2025-01-20 — Completed 02-02-PLAN.md
|
||||
|
||||
Progress: ███████░░░ 60%
|
||||
```
|
||||
|
||||
After:
|
||||
|
||||
```markdown
|
||||
## Current Position
|
||||
|
||||
Phase: 3 of 4 (Core Features)
|
||||
Plan: Not started
|
||||
Status: Ready to plan
|
||||
Last activity: 2025-01-20 — Phase 2 complete, transitioned to Phase 3
|
||||
|
||||
Progress: ███████░░░ 60%
|
||||
```
|
||||
Update the progress bar line in STATE.md with the result.
|
||||
|
||||
**Step complete when:**
|
||||
|
||||
- [ ] Phase number incremented to next phase
|
||||
- [ ] Plan status reset to "Not started"
|
||||
- [ ] Status shows "Ready to plan"
|
||||
- [ ] Last activity describes the transition
|
||||
- [ ] Phase number incremented to next phase (done by phase complete)
|
||||
- [ ] Plan status reset to "Not started" (done by phase complete)
|
||||
- [ ] Status shows "Ready to plan" (done by phase complete)
|
||||
- [ ] Progress bar reflects total completed plans
|
||||
|
||||
</step>
|
||||
@@ -394,26 +337,20 @@ Resume file: None
|
||||
|
||||
**MANDATORY: Verify milestone status before presenting next steps.**
|
||||
|
||||
**Step 1: Read ROADMAP.md and identify phases in current milestone**
|
||||
**Use the transition result from `gsd-tools phase complete`:**
|
||||
|
||||
Read the ROADMAP.md file and extract:
|
||||
1. Current phase number (the phase just transitioned from)
|
||||
2. All phase numbers in the current milestone section
|
||||
The `is_last_phase` field from the phase complete result tells you directly:
|
||||
- `is_last_phase: false` → More phases remain → Go to **Route A**
|
||||
- `is_last_phase: true` → Milestone complete → Go to **Route B**
|
||||
|
||||
To find phases, look for:
|
||||
- Phase headers: lines starting with `### Phase` or `#### Phase`
|
||||
- Phase list items: lines like `- [ ] **Phase X:` or `- [x] **Phase X:`
|
||||
The `next_phase` and `next_phase_name` fields give you the next phase details.
|
||||
|
||||
Count total phases and identify the highest phase number in the milestone.
|
||||
If you need additional context, use:
|
||||
```bash
|
||||
ROADMAP=$(node ~/.claude/get-shit-done/bin/gsd-tools.js roadmap analyze)
|
||||
```
|
||||
|
||||
State: "Current phase is {X}. Milestone has {N} phases (highest: {Y})."
|
||||
|
||||
**Step 2: Route based on milestone status**
|
||||
|
||||
| Condition | Meaning | Action |
|
||||
|-----------|---------|--------|
|
||||
| current phase < highest phase | More phases remain | Go to **Route A** |
|
||||
| current phase = highest phase | Milestone complete | Go to **Route B** |
|
||||
This returns all phases with goals, disk status, and completion info.
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user