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:
@@ -304,6 +304,50 @@ describe('stateBeginPhase', () => {
|
||||
expect(content).toContain('Executing Phase 11');
|
||||
expect(content).toContain('State Mutations');
|
||||
});
|
||||
|
||||
// ─── Bug #2420: flag-form args not parsed ────────────────────────────
|
||||
it('bug-2420: parses --phase/--name/--plans flag-form args correctly', async () => {
|
||||
const { stateBeginPhase } = await import('./state-mutation.js');
|
||||
|
||||
// This is how execute-phase.md calls it: flag form
|
||||
const result = await stateBeginPhase(
|
||||
['--phase', '99', '--name', 'probe-test', '--plans', '1'],
|
||||
tmpDir
|
||||
);
|
||||
const data = result.data as Record<string, unknown>;
|
||||
|
||||
// Must return the actual values, not the flag names
|
||||
expect(data.phase).toBe('99');
|
||||
expect(data.name).toBe('probe-test');
|
||||
expect(data.plan_count).toBe('1');
|
||||
|
||||
// STATE.md must contain clean output, not literal "--phase"
|
||||
const content = await readFile(join(tmpDir, '.planning', 'STATE.md'), 'utf-8');
|
||||
expect(content).not.toContain('--phase');
|
||||
expect(content).not.toContain('--name');
|
||||
expect(content).not.toContain('--plans');
|
||||
expect(content).toContain('Executing Phase 99');
|
||||
expect(content).toContain('probe-test');
|
||||
});
|
||||
|
||||
it('bug-2420: positional args still work after flag-parsing fix', async () => {
|
||||
const { stateBeginPhase } = await import('./state-mutation.js');
|
||||
|
||||
const result = await stateBeginPhase(['42', 'Positional Test', '5'], tmpDir);
|
||||
const data = result.data as Record<string, unknown>;
|
||||
expect(data.phase).toBe('42');
|
||||
expect(data.name).toBe('Positional Test');
|
||||
expect(data.plan_count).toBe('5');
|
||||
});
|
||||
|
||||
it('bug-2420: flag parser throws when a flag value is missing (next token is a flag)', async () => {
|
||||
const { stateBeginPhase } = await import('./state-mutation.js');
|
||||
|
||||
// --phase has no value — next token is --name, which is itself a flag.
|
||||
await expect(
|
||||
stateBeginPhase(['--phase', '--name', 'Title', '--plans', '1'], tmpDir)
|
||||
).rejects.toThrow('missing value for --phase');
|
||||
});
|
||||
});
|
||||
|
||||
// ─── stateAdvancePlan ───────────────────────────────────────────────────────
|
||||
|
||||
Reference in New Issue
Block a user