diff --git a/sdk/src/query/roadmap.test.ts b/sdk/src/query/roadmap.test.ts index 05b844ebc..04e80e8bf 100644 --- a/sdk/src/query/roadmap.test.ts +++ b/sdk/src/query/roadmap.test.ts @@ -509,6 +509,31 @@ describe('extractCurrentMilestone', () => { expect(result).toMatch(/^##\s+v0\.9 Local-First Bus/m); }); + it('bug-2641: v0.1 must not substring-match v0.10 in markdown heading anchor path', async () => { + const roadmap = `# Roadmap + +## v0.10 Future Milestone + +### Phase 7: Wrong Phase +**Goal:** This is from v0.10, not v0.1. + +## v0.1 Active Milestone + +### Phase 1: Right Phase +**Goal:** This is the active milestone. +`; + const state = `---\nmilestone: v0.1\n---\n# State\n`; + await writeFile(join(tmpDir, '.planning', 'STATE.md'), state); + await writeFile(join(tmpDir, '.planning', 'ROADMAP.md'), roadmap); + + const result = await extractCurrentMilestone(roadmap, tmpDir); + + expect(result).toContain('### Phase 1: Right Phase'); + expect(result).toContain('This is the active milestone'); + expect(result).not.toContain('Phase 7: Wrong Phase'); + expect(result).not.toContain('This is from v0.10'); + }); + // ─── Bug #2641 (review hardening): substring-version trap ─── it('bug-2641: v0.1 must not substring-match v0.10 …', async () => { // The fallback regex anchors on `escapedVersion` inside `` text. diff --git a/sdk/src/query/roadmap.ts b/sdk/src/query/roadmap.ts index 013af8846..687d7b2f0 100644 --- a/sdk/src/query/roadmap.ts +++ b/sdk/src/query/roadmap.ts @@ -201,7 +201,7 @@ export async function extractCurrentMilestone(content: string, projectDir: strin // Find section matching this version const escapedVersion = escapeRegex(version); const sectionPattern = new RegExp( - `(^#{1,3}\\s+.*${escapedVersion}[^\\n]*)`, + `(^#{1,3}\\s+.*${escapedVersion}(?![\\d.])[^\\n]*)`, 'mi' ); const sectionMatch = content.match(sectionPattern);