diff --git a/.changeset/patient-mice-greet.md b/.changeset/patient-mice-greet.md new file mode 100644 index 000000000..efd8cba58 --- /dev/null +++ b/.changeset/patient-mice-greet.md @@ -0,0 +1,5 @@ +--- +type: Fixed +pr: 1843 +--- +**`init milestone-op` now ignores backlog `999.x` headings when counting milestone phases** — parked backlog items no longer inflate `phase_count` or pin `all_phases_complete` false for an otherwise finished milestone. (#1843) diff --git a/src/init.cts b/src/init.cts index 52f2a576f..1a2294bab 100644 --- a/src/init.cts +++ b/src/init.cts @@ -1189,6 +1189,7 @@ function cmdInitMilestoneOp(cwd: string, raw: boolean): void { const phasePattern = /#{2,4}\s*Phase\s+(\d+[A-Z]?(?:\.\d+)*)\s*:/gi; let m: RegExpExecArray | null; while ((m = phasePattern.exec(currentSection)) !== null) { + if (/^999(?:\.|$)/.test(m[1])) continue; roadmapPhaseNumbers.push(m[1]); } } catch { diff --git a/tests/init.test.cjs b/tests/init.test.cjs index e665e0281..871786122 100644 --- a/tests/init.test.cjs +++ b/tests/init.test.cjs @@ -976,6 +976,7 @@ describe('cmdInitMilestoneOp', () => { path.join(tmpDir, '.planning', 'config.json'), JSON.stringify({ project_code: 'PROJ' }, null, 2) ); + fs.writeFileSync( path.join(tmpDir, '.planning', 'STATE.md'), [ @@ -1008,6 +1009,47 @@ describe('cmdInitMilestoneOp', () => { assert.strictEqual(output.all_phases_complete, true); }); + test('backlog 999.x headings do not inflate milestone phase counts (#1838)', () => { + const phase1 = path.join(tmpDir, '.planning', 'phases', '01-setup'); + fs.mkdirSync(phase1, { recursive: true }); + fs.writeFileSync(path.join(phase1, '01-01-PLAN.md'), '# Plan'); + fs.writeFileSync(path.join(phase1, '01-01-SUMMARY.md'), '# Summary'); + fs.writeFileSync( + path.join(tmpDir, '.planning', 'STATE.md'), + [ + '---', + 'gsd_state_version: 1.0', + 'milestone: v1.0.0', + 'milestone_name: Test Milestone', + 'status: completed', + '---', + '', + ].join('\n') + ); + fs.writeFileSync( + path.join(tmpDir, '.planning', 'ROADMAP.md'), + [ + '# Roadmap', + '', + '## 🚧 v1.0.0 Test Milestone', + '### Phase 1: Setup', + '', + '## Backlog', + '### Phase 999.1: Deferred Idea', + '### Phase 999.2: Another Deferred Idea', + '', + ].join('\n') + ); + + const result = runGsdTools('init milestone-op', tmpDir); + assert.ok(result.success, `Command failed: ${result.error}`); + + const output = JSON.parse(result.output); + assert.strictEqual(output.phase_count, 1); + assert.strictEqual(output.completed_phases, 1); + assert.strictEqual(output.all_phases_complete, true); + }); + test('archive directory scanning', () => { fs.mkdirSync(path.join(tmpDir, '.planning', 'archive', 'v1.0'), { recursive: true }); fs.mkdirSync(path.join(tmpDir, '.planning', 'archive', 'v0.9'), { recursive: true });