fix(sdk): extractCurrentMilestone Backlog leak + state.begin-phase flag parsing (#2455)
* fix(sdk): extractCurrentMilestone Backlog leak + state.begin-phase flag parsing Closes #2422 Closes #2420 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: patch-version semver in milestone boundary regex + flag-parser validation Two follow-on correctness issues identified in code review: 1. roadmap.ts: currentVersionMatch and nextMilestoneRegex only captured major.minor (v(\d+\.\d+)), collapsing v2.0.1 to "2.0". A sub-heading "## v2.0.2 Phase Details" would match the same prefix and be incorrectly skipped. Both patterns updated to v(\d+(?:\.\d+)+) to capture full semver. 2. state-mutation.ts: pair-wise flag parsing loop advanced i by 2 unconditionally, so a missing flag value caused the next flag token to be assigned as the value (e.g. flags['phase'] = '--name'). Fix: iterate with i++ and validate that the candidate value exists and does not start with '--' before assigning; throw GSDError('missing value for --<key>') on invalid input. Added regression test. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -144,6 +144,57 @@ describe('extractCurrentMilestone', () => {
|
||||
const result = await extractCurrentMilestone(content, tmpDir);
|
||||
expect(result).toBe('current content');
|
||||
});
|
||||
|
||||
// ─── Bug #2422: preamble Backlog leak ─────────────────────────────────
|
||||
it('bug-2422: does not include ## Backlog section before the current milestone', async () => {
|
||||
const roadmapWithBacklog = `# ROADMAP
|
||||
|
||||
## Backlog
|
||||
### Phase 999.1: Parking lot item A
|
||||
### Phase 999.2: Parking lot item B
|
||||
|
||||
### 🚧 v2.0 My Milestone (In Progress)
|
||||
- [ ] **Phase 100: Real work**
|
||||
|
||||
## v2.0 Phase Details
|
||||
### Phase 100: Real work
|
||||
**Goal**: Do stuff.
|
||||
`;
|
||||
const state = `---\nmilestone: v2.0\n---\n# State\n`;
|
||||
await writeFile(join(tmpDir, '.planning', 'STATE.md'), state);
|
||||
await writeFile(join(tmpDir, '.planning', 'ROADMAP.md'), roadmapWithBacklog);
|
||||
|
||||
const result = await extractCurrentMilestone(roadmapWithBacklog, tmpDir);
|
||||
|
||||
// Must NOT include backlog phases
|
||||
expect(result).not.toContain('Phase 999.1');
|
||||
expect(result).not.toContain('Phase 999.2');
|
||||
expect(result).not.toContain('Parking lot');
|
||||
// Must include the actual v2.0 content
|
||||
expect(result).toContain('Phase 100');
|
||||
});
|
||||
|
||||
// ─── Bug #2422: same-version sub-heading truncation ───────────────────
|
||||
it('bug-2422: does not truncate at same-version sub-heading (## v2.0 Phase Details)', async () => {
|
||||
const roadmapWithDetails = `# ROADMAP
|
||||
|
||||
### 🚧 v2.0 My Milestone (In Progress)
|
||||
- [ ] **Phase 100: Real work**
|
||||
|
||||
## v2.0 Phase Details
|
||||
### Phase 100: Real work
|
||||
**Goal**: Do stuff.
|
||||
`;
|
||||
const state = `---\nmilestone: v2.0\n---\n# State\n`;
|
||||
await writeFile(join(tmpDir, '.planning', 'STATE.md'), state);
|
||||
await writeFile(join(tmpDir, '.planning', 'ROADMAP.md'), roadmapWithDetails);
|
||||
|
||||
const result = await extractCurrentMilestone(roadmapWithDetails, tmpDir);
|
||||
|
||||
// The detail section must survive — not be cut off
|
||||
expect(result).toContain('Phase 100');
|
||||
expect(result).toContain('Phase Details');
|
||||
});
|
||||
});
|
||||
|
||||
// ─── roadmapGetPhase ──────────────────────────────────────────────────────
|
||||
|
||||
Reference in New Issue
Block a user