/** * GSD Tools Tests - roadmap get-phase fallback to full ROADMAP.md * * Covers issue #1634: phases outside the current milestone slice should still * resolve by falling back to the full ROADMAP.md content. */ const { test, describe, beforeEach, afterEach } = require('node:test'); const assert = require('node:assert/strict'); const fs = require('fs'); const path = require('path'); const { runGsdTools, createTempProject, cleanup } = require('./helpers.cjs'); /** * Helper: write STATE.md with a milestone version so extractCurrentMilestone * will slice the roadmap to only that milestone's section. */ function writeState(tmpDir, version) { fs.writeFileSync( path.join(tmpDir, '.planning', 'STATE.md'), `---\nmilestone: ${version}\n---\n` ); } describe('roadmap get-phase fallback to full ROADMAP.md (#1634)', () => { let tmpDir; beforeEach(() => { tmpDir = createTempProject(); }); afterEach(() => { cleanup(tmpDir); }); test('active milestone phase still resolves correctly', () => { writeState(tmpDir, 'v1.0'); fs.writeFileSync( path.join(tmpDir, '.planning', 'ROADMAP.md'), `# Roadmap ## v1.0 Current Release ### Phase 1: Foundation **Goal:** Set up project infrastructure ### Phase 2: API **Goal:** Build REST API ## v2.0 Next Release ### Phase 3: Frontend **Goal:** Build UI layer ` ); const result = runGsdTools('roadmap get-phase 1', tmpDir); assert.ok(result.success, `Command failed: ${result.error}`); const output = JSON.parse(result.output); assert.equal(output.found, true, 'active milestone phase should be found'); assert.equal(output.phase_number, '1'); assert.equal(output.phase_name, 'Foundation'); assert.equal(output.goal, 'Set up project infrastructure'); }); test('backlog phase outside current milestone resolves via fallback', () => { writeState(tmpDir, 'v1.0'); fs.writeFileSync( path.join(tmpDir, '.planning', 'ROADMAP.md'), `# Roadmap ## v1.0 Current Release ### Phase 1: Foundation **Goal:** Set up project infrastructure ## v2.0 Future Release ### Phase 999.60: Backlog Cleanup **Goal:** Clean up technical debt from backlog ` ); const result = runGsdTools('roadmap get-phase 999.60', tmpDir); assert.ok(result.success, `Command failed: ${result.error}`); const output = JSON.parse(result.output); assert.equal(output.found, true, 'backlog phase should be found via fallback'); assert.equal(output.phase_number, '999.60'); assert.equal(output.phase_name, 'Backlog Cleanup'); assert.equal(output.goal, 'Clean up technical debt from backlog'); }); test('future planned milestone phase resolves via fallback', () => { writeState(tmpDir, 'v1.0'); fs.writeFileSync( path.join(tmpDir, '.planning', 'ROADMAP.md'), `# Roadmap ## v1.0 Current Release ### Phase 1: Foundation **Goal:** Set up project infrastructure ## v3.0 Planned Milestone ### Phase 1025: Advanced Analytics **Goal:** Build analytics dashboard for enterprise customers **Success Criteria** (what must be TRUE): 1. Dashboard renders in under 2s 2. Supports 10k concurrent users ` ); const result = runGsdTools('roadmap get-phase 1025', tmpDir); assert.ok(result.success, `Command failed: ${result.error}`); const output = JSON.parse(result.output); assert.equal(output.found, true, 'future milestone phase should be found via fallback'); assert.equal(output.phase_number, '1025'); assert.equal(output.phase_name, 'Advanced Analytics'); assert.equal(output.goal, 'Build analytics dashboard for enterprise customers'); assert.ok(Array.isArray(output.success_criteria), 'success_criteria should be extracted'); assert.equal(output.success_criteria.length, 2, 'should have 2 criteria'); }); test('truly missing phase still returns found: false', () => { writeState(tmpDir, 'v1.0'); fs.writeFileSync( path.join(tmpDir, '.planning', 'ROADMAP.md'), `# Roadmap ## v1.0 Current Release ### Phase 1: Foundation **Goal:** Set up project infrastructure ## v2.0 Future Release ### Phase 5: Mobile **Goal:** Build mobile app ` ); const result = runGsdTools('roadmap get-phase 9999', tmpDir); assert.ok(result.success, `Command failed: ${result.error}`); const output = JSON.parse(result.output); assert.equal(output.found, false, 'truly missing phase should return found: false'); assert.equal(output.phase_number, '9999'); }); test('backlog checklist-only phase triggers malformed_roadmap via fallback', () => { writeState(tmpDir, 'v1.0'); fs.writeFileSync( path.join(tmpDir, '.planning', 'ROADMAP.md'), `# Roadmap ## v1.0 Current Release ### Phase 1: Foundation **Goal:** Set up project infrastructure ## v2.0 Backlog - [ ] **Phase 50: Cleanup** - Remove old code ` ); const result = runGsdTools('roadmap get-phase 50', tmpDir); assert.ok(result.success, `Command failed: ${result.error}`); const output = JSON.parse(result.output); assert.equal(output.found, false, 'checklist-only phase should not be "found"'); assert.equal(output.error, 'malformed_roadmap', 'should identify malformed roadmap via fallback'); assert.ok(output.message.includes('missing'), 'should explain the issue'); }); test('checklist in milestone does not block full header match in wider roadmap', () => { writeState(tmpDir, 'v1.0'); fs.writeFileSync( path.join(tmpDir, '.planning', 'ROADMAP.md'), `# Roadmap ## v1.0 Current Release ### Phase 1: Foundation **Goal:** Set up project infrastructure - [ ] **Phase 50: Cleanup** - referenced in checklist ## v2.0 Future Release ### Phase 50: Cleanup **Goal:** Remove deprecated modules ` ); const result = runGsdTools('roadmap get-phase 50', tmpDir); assert.ok(result.success, `Command failed: ${result.error}`); const output = JSON.parse(result.output); assert.equal(output.found, true, 'full header in v2.0 should win over checklist in v1.0'); assert.equal(output.phase_name, 'Cleanup'); assert.equal(output.goal, 'Remove deprecated modules'); }); test('section extraction from fallback includes correct content boundaries', () => { writeState(tmpDir, 'v1.0'); fs.writeFileSync( path.join(tmpDir, '.planning', 'ROADMAP.md'), `# Roadmap ## v1.0 Current Release ### Phase 1: Foundation **Goal:** Set up project infrastructure ## v2.0 Future Release ### Phase 10: Database **Goal:** Schema design and migrations This phase covers: - Schema modeling - Migration tooling - Seed data ### Phase 11: Caching **Goal:** Add Redis caching layer ` ); const result = runGsdTools('roadmap get-phase 10', tmpDir); assert.ok(result.success, `Command failed: ${result.error}`); const output = JSON.parse(result.output); assert.equal(output.found, true, 'phase 10 should be found via fallback'); assert.ok(output.section.includes('Schema modeling'), 'section includes description'); assert.ok(output.section.includes('Seed data'), 'section includes all bullets'); assert.ok(!output.section.includes('Phase 11'), 'section does not include next phase'); }); });