/** * GSD Tools Tests - Init */ 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('init commands', () => { let tmpDir; beforeEach(() => { tmpDir = createTempProject(); }); afterEach(() => { cleanup(tmpDir); }); test('init execute-phase returns file paths', () => { const phaseDir = path.join(tmpDir, '.planning', 'phases', '03-api'); fs.mkdirSync(phaseDir, { recursive: true }); fs.writeFileSync(path.join(phaseDir, '03-01-PLAN.md'), '# Plan'); const result = runGsdTools('init execute-phase 03', tmpDir); assert.ok(result.success, `Command failed: ${result.error}`); const output = JSON.parse(result.output); assert.strictEqual(output.state_path, '.planning/STATE.md'); assert.strictEqual(output.roadmap_path, '.planning/ROADMAP.md'); assert.strictEqual(output.config_path, '.planning/config.json'); }); test('init plan-phase returns file paths', () => { const phaseDir = path.join(tmpDir, '.planning', 'phases', '03-api'); fs.mkdirSync(phaseDir, { recursive: true }); fs.writeFileSync(path.join(phaseDir, '03-CONTEXT.md'), '# Phase Context'); fs.writeFileSync(path.join(phaseDir, '03-RESEARCH.md'), '# Research Findings'); fs.writeFileSync(path.join(phaseDir, '03-VERIFICATION.md'), '# Verification'); fs.writeFileSync(path.join(phaseDir, '03-UAT.md'), '# UAT'); const result = runGsdTools('init plan-phase 03', tmpDir); assert.ok(result.success, `Command failed: ${result.error}`); const output = JSON.parse(result.output); assert.strictEqual(output.state_path, '.planning/STATE.md'); assert.strictEqual(output.roadmap_path, '.planning/ROADMAP.md'); assert.strictEqual(output.requirements_path, '.planning/REQUIREMENTS.md'); assert.strictEqual(output.context_path, '.planning/phases/03-api/03-CONTEXT.md'); assert.strictEqual(output.research_path, '.planning/phases/03-api/03-RESEARCH.md'); assert.strictEqual(output.verification_path, '.planning/phases/03-api/03-VERIFICATION.md'); assert.strictEqual(output.uat_path, '.planning/phases/03-api/03-UAT.md'); }); test('init progress returns file paths', () => { const result = runGsdTools('init progress', tmpDir); assert.ok(result.success, `Command failed: ${result.error}`); const output = JSON.parse(result.output); assert.strictEqual(output.state_path, '.planning/STATE.md'); assert.strictEqual(output.roadmap_path, '.planning/ROADMAP.md'); assert.strictEqual(output.project_path, '.planning/PROJECT.md'); assert.strictEqual(output.config_path, '.planning/config.json'); }); test('init phase-op returns core and optional phase file paths', () => { const phaseDir = path.join(tmpDir, '.planning', 'phases', '03-api'); fs.mkdirSync(phaseDir, { recursive: true }); fs.writeFileSync(path.join(phaseDir, '03-CONTEXT.md'), '# Phase Context'); fs.writeFileSync(path.join(phaseDir, '03-RESEARCH.md'), '# Research'); fs.writeFileSync(path.join(phaseDir, '03-VERIFICATION.md'), '# Verification'); fs.writeFileSync(path.join(phaseDir, '03-UAT.md'), '# UAT'); const result = runGsdTools('init phase-op 03', tmpDir); assert.ok(result.success, `Command failed: ${result.error}`); const output = JSON.parse(result.output); assert.strictEqual(output.state_path, '.planning/STATE.md'); assert.strictEqual(output.roadmap_path, '.planning/ROADMAP.md'); assert.strictEqual(output.requirements_path, '.planning/REQUIREMENTS.md'); assert.strictEqual(output.context_path, '.planning/phases/03-api/03-CONTEXT.md'); assert.strictEqual(output.research_path, '.planning/phases/03-api/03-RESEARCH.md'); assert.strictEqual(output.verification_path, '.planning/phases/03-api/03-VERIFICATION.md'); assert.strictEqual(output.uat_path, '.planning/phases/03-api/03-UAT.md'); }); test('init plan-phase omits optional paths if files missing', () => { const phaseDir = path.join(tmpDir, '.planning', 'phases', '03-api'); fs.mkdirSync(phaseDir, { recursive: true }); const result = runGsdTools('init plan-phase 03', tmpDir); assert.ok(result.success, `Command failed: ${result.error}`); const output = JSON.parse(result.output); assert.strictEqual(output.context_path, undefined); assert.strictEqual(output.research_path, undefined); }); // ── phase_req_ids extraction (fix for #684) ────────────────────────────── test('init plan-phase extracts phase_req_ids from ROADMAP', () => { fs.mkdirSync(path.join(tmpDir, '.planning', 'phases', '03-api'), { recursive: true }); fs.writeFileSync( path.join(tmpDir, '.planning', 'ROADMAP.md'), `# Roadmap\n\n### Phase 3: API\n**Goal:** Build API\n**Requirements**: CP-01, CP-02, CP-03\n**Plans:** 0 plans\n` ); const result = runGsdTools('init plan-phase 3', tmpDir); assert.ok(result.success, `Command failed: ${result.error}`); const output = JSON.parse(result.output); assert.strictEqual(output.phase_req_ids, 'CP-01, CP-02, CP-03'); }); test('init plan-phase strips brackets from phase_req_ids', () => { fs.mkdirSync(path.join(tmpDir, '.planning', 'phases', '03-api'), { recursive: true }); fs.writeFileSync( path.join(tmpDir, '.planning', 'ROADMAP.md'), `# Roadmap\n\n### Phase 3: API\n**Goal:** Build API\n**Requirements**: [CP-01, CP-02]\n**Plans:** 0 plans\n` ); const result = runGsdTools('init plan-phase 3', tmpDir); assert.ok(result.success, `Command failed: ${result.error}`); const output = JSON.parse(result.output); assert.strictEqual(output.phase_req_ids, 'CP-01, CP-02'); }); test('init plan-phase returns null phase_req_ids when Requirements line is absent', () => { fs.mkdirSync(path.join(tmpDir, '.planning', 'phases', '03-api'), { recursive: true }); fs.writeFileSync( path.join(tmpDir, '.planning', 'ROADMAP.md'), `# Roadmap\n\n### Phase 3: API\n**Goal:** Build API\n**Plans:** 0 plans\n` ); const result = runGsdTools('init plan-phase 3', tmpDir); assert.ok(result.success, `Command failed: ${result.error}`); const output = JSON.parse(result.output); assert.strictEqual(output.phase_req_ids, null); }); test('init plan-phase returns null phase_req_ids when ROADMAP is absent', () => { fs.mkdirSync(path.join(tmpDir, '.planning', 'phases', '03-api'), { recursive: true }); const result = runGsdTools('init plan-phase 3', tmpDir); assert.ok(result.success, `Command failed: ${result.error}`); const output = JSON.parse(result.output); assert.strictEqual(output.phase_req_ids, null); }); test('init execute-phase extracts phase_req_ids from ROADMAP', () => { const phaseDir = path.join(tmpDir, '.planning', 'phases', '03-api'); fs.mkdirSync(phaseDir, { recursive: true }); fs.writeFileSync(path.join(phaseDir, '03-01-PLAN.md'), '# Plan'); fs.writeFileSync( path.join(tmpDir, '.planning', 'ROADMAP.md'), `# Roadmap\n\n### Phase 3: API\n**Goal:** Build API\n**Requirements**: EX-01, EX-02\n**Plans:** 1 plans\n` ); const result = runGsdTools('init execute-phase 3', tmpDir); assert.ok(result.success, `Command failed: ${result.error}`); const output = JSON.parse(result.output); assert.strictEqual(output.phase_req_ids, 'EX-01, EX-02'); }); test('init plan-phase returns null phase_req_ids when value is TBD', () => { fs.mkdirSync(path.join(tmpDir, '.planning', 'phases', '03-api'), { recursive: true }); fs.writeFileSync( path.join(tmpDir, '.planning', 'ROADMAP.md'), `# Roadmap\n\n### Phase 3: API\n**Goal:** Build API\n**Requirements**: TBD\n**Plans:** 0 plans\n` ); const result = runGsdTools('init plan-phase 3', tmpDir); assert.ok(result.success, `Command failed: ${result.error}`); const output = JSON.parse(result.output); assert.strictEqual(output.phase_req_ids, null, 'TBD placeholder should return null'); }); test('init execute-phase returns null phase_req_ids when Requirements line is absent', () => { const phaseDir = path.join(tmpDir, '.planning', 'phases', '03-api'); fs.mkdirSync(phaseDir, { recursive: true }); fs.writeFileSync(path.join(phaseDir, '03-01-PLAN.md'), '# Plan'); fs.writeFileSync( path.join(tmpDir, '.planning', 'ROADMAP.md'), `# Roadmap\n\n### Phase 3: API\n**Goal:** Build API\n**Plans:** 1 plans\n` ); const result = runGsdTools('init execute-phase 3', tmpDir); assert.ok(result.success, `Command failed: ${result.error}`); const output = JSON.parse(result.output); assert.strictEqual(output.phase_req_ids, null); }); }); // ───────────────────────────────────────────────────────────────────────────── // roadmap analyze command // ─────────────────────────────────────────────────────────────────────────────