/** * GSD Tools Tests - Roadmap */ const { test, describe, beforeEach, afterEach } = require('node:test'); const assert = require('node:assert'); const fs = require('fs'); const path = require('path'); const { runGsdTools, createTempProject, cleanup } = require('./helpers.cjs'); describe('roadmap get-phase command', () => { let tmpDir; beforeEach(() => { tmpDir = createTempProject(); }); afterEach(() => { cleanup(tmpDir); }); test('extracts phase section from ROADMAP.md', () => { fs.writeFileSync( path.join(tmpDir, '.planning', 'ROADMAP.md'), `# Roadmap v1.0 ## Phases ### Phase 1: Foundation **Goal:** Set up project infrastructure **Plans:** 2 plans Some description here. ### Phase 2: API **Goal:** Build REST API **Plans:** 3 plans ` ); const result = runGsdTools('roadmap get-phase 1', tmpDir); assert.ok(result.success, `Command failed: ${result.error}`); const output = JSON.parse(result.output); assert.strictEqual(output.found, true, 'phase should be found'); assert.strictEqual(output.phase_number, '1', 'phase number correct'); assert.strictEqual(output.phase_name, 'Foundation', 'phase name extracted'); assert.strictEqual(output.goal, 'Set up project infrastructure', 'goal extracted'); }); test('returns not found for missing phase', () => { fs.writeFileSync( path.join(tmpDir, '.planning', 'ROADMAP.md'), `# Roadmap v1.0 ### Phase 1: Foundation **Goal:** Set up project ` ); const result = runGsdTools('roadmap get-phase 5', tmpDir); assert.ok(result.success, `Command failed: ${result.error}`); const output = JSON.parse(result.output); assert.strictEqual(output.found, false, 'phase should not be found'); }); test('handles decimal phase numbers', () => { fs.writeFileSync( path.join(tmpDir, '.planning', 'ROADMAP.md'), `# Roadmap ### Phase 2: Main **Goal:** Main work ### Phase 2.1: Hotfix **Goal:** Emergency fix ` ); const result = runGsdTools('roadmap get-phase 2.1', tmpDir); assert.ok(result.success, `Command failed: ${result.error}`); const output = JSON.parse(result.output); assert.strictEqual(output.found, true, 'decimal phase should be found'); assert.strictEqual(output.phase_name, 'Hotfix', 'phase name correct'); assert.strictEqual(output.goal, 'Emergency fix', 'goal extracted'); }); test('extracts full section content', () => { fs.writeFileSync( path.join(tmpDir, '.planning', 'ROADMAP.md'), `# Roadmap ### Phase 1: Setup **Goal:** Initialize everything This phase covers: - Database setup - Auth configuration - CI/CD pipeline ### Phase 2: Build **Goal:** Build features ` ); const result = runGsdTools('roadmap get-phase 1', tmpDir); assert.ok(result.success, `Command failed: ${result.error}`); const output = JSON.parse(result.output); assert.ok(output.section.includes('Database setup'), 'section includes description'); assert.ok(output.section.includes('CI/CD pipeline'), 'section includes all bullets'); assert.ok(!output.section.includes('Phase 2'), 'section does not include next phase'); }); test('handles missing ROADMAP.md gracefully', () => { const result = runGsdTools('roadmap get-phase 1', tmpDir); assert.ok(result.success, `Command failed: ${result.error}`); const output = JSON.parse(result.output); assert.strictEqual(output.found, false, 'should return not found'); assert.strictEqual(output.error, 'ROADMAP.md not found', 'should explain why'); }); test('accepts ## phase headers (two hashes)', () => { fs.writeFileSync( path.join(tmpDir, '.planning', 'ROADMAP.md'), `# Roadmap v1.0 ## Phase 1: Foundation **Goal:** Set up project infrastructure **Plans:** 2 plans ## Phase 2: API **Goal:** Build REST API ` ); const result = runGsdTools('roadmap get-phase 1', tmpDir); assert.ok(result.success, `Command failed: ${result.error}`); const output = JSON.parse(result.output); assert.strictEqual(output.found, true, 'phase with ## header should be found'); assert.strictEqual(output.phase_name, 'Foundation', 'phase name extracted'); assert.strictEqual(output.goal, 'Set up project infrastructure', 'goal extracted'); }); test('detects malformed ROADMAP with summary list but no detail sections', () => { fs.writeFileSync( path.join(tmpDir, '.planning', 'ROADMAP.md'), `# Roadmap v1.0 ## Phases - [ ] **Phase 1: Foundation** - Set up project - [ ] **Phase 2: API** - Build REST API ` ); const result = runGsdTools('roadmap get-phase 1', tmpDir); assert.ok(result.success, `Command failed: ${result.error}`); const output = JSON.parse(result.output); assert.strictEqual(output.found, false, 'phase should not be found'); assert.strictEqual(output.error, 'malformed_roadmap', 'should identify malformed roadmap'); assert.ok(output.message.includes('missing'), 'should explain the issue'); }); }); // ───────────────────────────────────────────────────────────────────────────── // phase next-decimal 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 // ─────────────────────────────────────────────────────────────────────────────