From 38c19466aecaae4a031a978f3fdd9b3ea02ea460 Mon Sep 17 00:00:00 2001 From: Hnturk Date: Fri, 27 Feb 2026 11:17:36 +0300 Subject: [PATCH] fix: getMilestoneInfo() returns wrong version after milestone completion (#768) Strip
blocks (shipped milestones) before matching, and extract both version and name from the same ## heading for consistency. --- get-shit-done/bin/lib/core.cjs | 16 ++++++-- tests/core.test.cjs | 74 ++++++++++++++++++++++++++++++++++ 2 files changed, 87 insertions(+), 3 deletions(-) diff --git a/get-shit-done/bin/lib/core.cjs b/get-shit-done/bin/lib/core.cjs index 20aab2803..bdd50bb7c 100644 --- a/get-shit-done/bin/lib/core.cjs +++ b/get-shit-done/bin/lib/core.cjs @@ -379,11 +379,21 @@ function generateSlugInternal(text) { function getMilestoneInfo(cwd) { try { const roadmap = fs.readFileSync(path.join(cwd, '.planning', 'ROADMAP.md'), 'utf-8'); - const versionMatch = roadmap.match(/v(\d+\.\d+)/); - const nameMatch = roadmap.match(/## .*v\d+\.\d+[:\s]+([^\n(]+)/); + // Strip
...
blocks so shipped milestones don't interfere + const cleaned = roadmap.replace(/
[\s\S]*?<\/details>/gi, ''); + // Extract version and name from the same ## heading for consistency + const headingMatch = cleaned.match(/## .*v(\d+\.\d+)[:\s]+([^\n(]+)/); + if (headingMatch) { + return { + version: 'v' + headingMatch[1], + name: headingMatch[2].trim(), + }; + } + // Fallback: try bare version match + const versionMatch = cleaned.match(/v(\d+\.\d+)/); return { version: versionMatch ? versionMatch[0] : 'v1.0', - name: nameMatch ? nameMatch[1].trim() : 'milestone', + name: 'milestone', }; } catch { return { version: 'v1.0', name: 'milestone' }; diff --git a/tests/core.test.cjs b/tests/core.test.cjs index 38a4cf783..d859269c2 100644 --- a/tests/core.test.cjs +++ b/tests/core.test.cjs @@ -398,6 +398,80 @@ describe('getMilestoneInfo', () => { assert.strictEqual(info.version, 'v1.0'); assert.strictEqual(info.name, 'milestone'); }); + + test('returns active milestone when shipped milestone is collapsed in details block', () => { + const roadmap = [ + '# Milestones', + '', + '| Version | Status |', + '|---------|--------|', + '| v0.1 | Shipped |', + '| v0.2 | Active |', + '', + '
', + 'v0.1 — Legacy Feature Parity (Shipped)', + '', + '## Roadmap v0.1: Legacy Feature Parity', + '', + '### Phase 1: Core Setup', + 'Some content about phase 1', + '', + '
', + '', + '## Roadmap v0.2: Dashboard Overhaul', + '', + '### Phase 8: New Dashboard Layout', + 'Some content about phase 8', + ].join('\n'); + fs.writeFileSync(path.join(tmpDir, '.planning', 'ROADMAP.md'), roadmap); + const info = getMilestoneInfo(tmpDir); + assert.strictEqual(info.version, 'v0.2'); + assert.strictEqual(info.name, 'Dashboard Overhaul'); + }); + + test('returns active milestone when multiple shipped milestones exist in details blocks', () => { + const roadmap = [ + '# Milestones', + '', + '| Version | Status |', + '|---------|--------|', + '| v0.1 | Shipped |', + '| v0.2 | Shipped |', + '| v0.3 | Active |', + '', + '
', + 'v0.1 — Initial Release (Shipped)', + '', + '## Roadmap v0.1: Initial Release', + '', + '
', + '', + '
', + 'v0.2 — Feature Expansion (Shipped)', + '', + '## Roadmap v0.2: Feature Expansion', + '', + '
', + '', + '## Roadmap v0.3: Performance Tuning', + '', + '### Phase 12: Optimize Queries', + ].join('\n'); + fs.writeFileSync(path.join(tmpDir, '.planning', 'ROADMAP.md'), roadmap); + const info = getMilestoneInfo(tmpDir); + assert.strictEqual(info.version, 'v0.3'); + assert.strictEqual(info.name, 'Performance Tuning'); + }); + + test('returns defaults when roadmap has no heading matches', () => { + fs.writeFileSync( + path.join(tmpDir, '.planning', 'ROADMAP.md'), + '# Roadmap\n\nSome content without version headings' + ); + const info = getMilestoneInfo(tmpDir); + assert.strictEqual(info.version, 'v1.0'); + assert.strictEqual(info.name, 'milestone'); + }); }); // ─── searchPhaseInDir ──────────────────────────────────────────────────────────