diff --git a/get-shit-done/bin/lib/phase.cjs b/get-shit-done/bin/lib/phase.cjs index cfb9d3669..1d7c26a11 100644 --- a/get-shit-done/bin/lib/phase.cjs +++ b/get-shit-done/bin/lib/phase.cjs @@ -689,10 +689,12 @@ function cmdPhaseComplete(cwd, phaseNum, raw) { const cells = fullRow.split('|').slice(1, -1); if (cells.length === 5) { // 5-col: Phase | Milestone | Plans | Status | Completed + cells[2] = ` ${summaryCount}/${planCount} `; cells[3] = ' Complete '; cells[4] = ` ${today} `; } else if (cells.length === 4) { // 4-col: Phase | Plans | Status | Completed + cells[1] = ` ${summaryCount}/${planCount} `; cells[2] = ' Complete '; cells[3] = ` ${today} `; } @@ -709,6 +711,18 @@ function cmdPhaseComplete(cwd, phaseNum, raw) { `$1${summaryCount}/${planCount} plans complete` ); + // Mark completed plan checkboxes (safety net for missed per-plan updates) + for (const summaryFile of phaseInfo.summaries) { + const planId = summaryFile.replace('-SUMMARY.md', '').replace('SUMMARY.md', ''); + if (!planId) continue; + const planEscaped = escapeRegex(planId); + const planCheckboxPattern = new RegExp( + `(-\\s*\\[) (\\]\\s*${planEscaped})`, + 'i' + ); + roadmapContent = roadmapContent.replace(planCheckboxPattern, '$1x$2'); + } + fs.writeFileSync(roadmapPath, roadmapContent, 'utf-8'); // Update REQUIREMENTS.md traceability for this phase's requirements diff --git a/tests/phase.test.cjs b/tests/phase.test.cjs index bbc0c004e..1828db931 100644 --- a/tests/phase.test.cjs +++ b/tests/phase.test.cjs @@ -1533,6 +1533,130 @@ describe('phase complete command', () => { // Verify compound format preserved assert.ok(state.match(/Phase:.*of\s+1/), 'should preserve "of N" in compound Phase format'); }); + + test('updates Plans Complete column in 4-column progress table', () => { + 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 + +## Progress + +| Phase | Plans Complete | Status | Completed | +|-------|----------------|--------|-----------| +| 1. Foundation | 0/1 | Not started | - | +| 2. API | 0/1 | Not started | - | +` + ); + 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-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 roadmap = fs.readFileSync(path.join(tmpDir, '.planning', 'ROADMAP.md'), 'utf-8'); + const rowMatch = roadmap.match(/^\|[^\n]*1\. Foundation[^\n]*$/m); + assert.ok(rowMatch, 'table row should exist'); + const cells = rowMatch[0].split('|').slice(1, -1).map(c => c.trim()); + assert.strictEqual(cells.length, 4, 'should have 4 columns'); + assert.strictEqual(cells[1], '1/1', 'Plans Complete column should be updated to 1/1'); + assert.ok(cells[2].includes('Complete'), 'Status column should be Complete'); + }); + + test('updates Plans Complete column in 5-column progress table', () => { + fs.writeFileSync( + path.join(tmpDir, '.planning', 'ROADMAP.md'), + `# Roadmap + +- [ ] Phase 1: Foundation + +### Phase 1: Foundation +**Goal:** Setup +**Plans:** 1 plans + +## Progress + +| Phase | Milestone | Plans Complete | Status | Completed | +|-------|-----------|----------------|--------|-----------| +| 1. Foundation | v1.0 | 0/1 | Planned | | +` + ); + 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-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 result = runGsdTools('phase complete 1', tmpDir); + assert.ok(result.success, `Command failed: ${result.error}`); + + const roadmap = fs.readFileSync(path.join(tmpDir, '.planning', 'ROADMAP.md'), 'utf-8'); + const rowMatch = roadmap.match(/^\|[^\n]*1\. Foundation[^\n]*$/m); + assert.ok(rowMatch, 'table row should exist'); + const cells = rowMatch[0].split('|').slice(1, -1).map(c => c.trim()); + assert.strictEqual(cells.length, 5, 'should have 5 columns'); + assert.strictEqual(cells[2], '1/1', 'Plans Complete column should be updated to 1/1'); + assert.ok(cells[3].includes('Complete'), 'Status column should be Complete'); + }); + + test('marks plan-level checkboxes on phase complete', () => { + fs.writeFileSync( + path.join(tmpDir, '.planning', 'ROADMAP.md'), + `# Roadmap + +- [ ] Phase 1: Foundation + +### Phase 1: Foundation +**Goal:** Setup +**Plans:** 2 plans + +Plans: +- [ ] 01-01-PLAN.md \u2014 Schema migration +- [ ] 01-02-PLAN.md \u2014 Auth setup +` + ); + fs.writeFileSync( + path.join(tmpDir, '.planning', 'STATE.md'), + `# State\n\n**Current Phase:** 01\n**Status:** In progress\n**Current Plan:** 01-02\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-PLAN.md'), '# Plan'); + fs.writeFileSync(path.join(p1, '01-01-SUMMARY.md'), '# Summary'); + fs.writeFileSync(path.join(p1, '01-02-PLAN.md'), '# Plan'); + fs.writeFileSync(path.join(p1, '01-02-SUMMARY.md'), '# Summary'); + + const result = runGsdTools('phase complete 1', tmpDir); + assert.ok(result.success, `Command failed: ${result.error}`); + + const roadmap = fs.readFileSync(path.join(tmpDir, '.planning', 'ROADMAP.md'), 'utf-8'); + assert.ok(roadmap.includes('[x] 01-01-PLAN.md'), 'plan 01-01 checkbox should be checked'); + assert.ok(roadmap.includes('[x] 01-02-PLAN.md'), 'plan 01-02 checkbox should be checked'); + assert.ok(!roadmap.includes('[ ] 01-01-PLAN.md'), 'plan 01-01 should not remain unchecked'); + assert.ok(!roadmap.includes('[ ] 01-02-PLAN.md'), 'plan 01-02 should not remain unchecked'); + }); }); // ─────────────────────────────────────────────────────────────────────────────