diff --git a/get-shit-done/bin/lib/commands.cjs b/get-shit-done/bin/lib/commands.cjs index 630b4703..a7743664 100644 --- a/get-shit-done/bin/lib/commands.cjs +++ b/get-shit-done/bin/lib/commands.cjs @@ -831,8 +831,9 @@ function cmdStats(cwd, format, raw) { const headingPattern = /#{2,4}\s*Phase\s+(\d+[A-Z]?(?:\.\d+)*)\s*:\s*([^\n]+)/gi; let match; while ((match = headingPattern.exec(roadmapContent)) !== null) { - phasesByNumber.set(match[1], { - number: match[1], + const key = normalizePhaseName(match[1]); + phasesByNumber.set(key, { + number: key, name: match[2].replace(/\(INSERTED\)/i, '').trim(), plans: 0, summaries: 0, @@ -862,9 +863,10 @@ function cmdStats(cwd, format, raw) { const status = determinePhaseStatus(plans, summaries, path.join(phasesDir, dir), 'Not Started'); - const existing = phasesByNumber.get(phaseNum); - phasesByNumber.set(phaseNum, { - number: phaseNum, + const normalizedNum = normalizePhaseName(phaseNum); + const existing = phasesByNumber.get(normalizedNum); + phasesByNumber.set(normalizedNum, { + number: normalizedNum, name: existing?.name || phaseName, plans: (existing?.plans || 0) + plans, summaries: (existing?.summaries || 0) + summaries, diff --git a/tests/commands.test.cjs b/tests/commands.test.cjs index a899dcfa..0c3f88dc 100644 --- a/tests/commands.test.cjs +++ b/tests/commands.test.cjs @@ -1693,6 +1693,36 @@ describe('stats command', () => { const output = JSON.parse(result.output); assert.strictEqual(output.phases[0].status, 'Executed', 'progress should show Executed without verification'); }); + + test('does not duplicate phases when ROADMAP uses unpadded numbers and dirs use padded numbers', () => { + // ROADMAP.md uses "Phase 1:" (unpadded) but directory is "01-auth" (padded). + // Without normalization, the Map holds two entries: "1" and "01", doubling phases_total. + const p1 = path.join(tmpDir, '.planning', 'phases', '01-auth'); + 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, 'VERIFICATION.md'), '---\nstatus: passed\n---\n# Verified'); + + fs.writeFileSync( + path.join(tmpDir, '.planning', 'ROADMAP.md'), + [ + '# Roadmap', + '', + '## Milestone v1', + '', + '### Phase 1: Auth', + '**Goal:** Authentication', + ].join('\n') + ); + + const result = runGsdTools('stats', tmpDir); + assert.ok(result.success, `Command failed: ${result.error}`); + + const stats = JSON.parse(result.output); + assert.strictEqual(stats.phases_total, 1, 'unpadded ROADMAP heading and padded dir should merge into one phase'); + assert.strictEqual(stats.phases_completed, 1); + assert.strictEqual(stats.phases.length, 1); + }); }); // ─────────────────────────────────────────────────────────────────────────────