* fix(#2769): tolerate Requirements header with colon inside bold delimiters extractReqIds in sdk/src/query/init.ts and the legacy init.cjs port only matched `**Requirements**:` (colon outside bold), so phases declared with the equally-valid markdown form `**Requirements:**` (colon inside bold, which is what the project's own templates emit) returned phase_req_ids: null for both `init plan-phase` and `init execute-phase`. The mirror-image bug in `phase complete`'s REQUIREMENTS.md traceability sweep at get-shit-done/bin/lib/phase.cjs:871 only matched the inside-bold form, silently skipping the REQ-ID checkbox flips for any roadmap that used the outside-bold form. Both parsers now share the same canonical regex that accepts all three rendered-identical variants: **Requirements:** (colon inside bold) **Requirements**: (colon outside bold) **Requirements** : (space before outside colon) Tests: - tests/init.test.cjs — parameterized over the three header variants for both init plan-phase and init execute-phase (6 new behavioral cases). - sdk/src/query/init.test.ts — describe.each over the same variants exercising initPlanPhase through the SDK. - tests/bug-2769-requirements-header-variants.test.cjs — phase complete flips REQ-001 in REQUIREMENTS.md across all three header variants. Closes #2769 * refactor(#2769): centralize REQUIREMENTS_HEADER_RE constant per CodeRabbit
This commit is contained in:
@@ -351,6 +351,40 @@ describe('initPlanPhase', () => {
|
||||
const data = result.data as Record<string, unknown>;
|
||||
expect(data.error).toBeDefined();
|
||||
});
|
||||
|
||||
// #2769: extractReqIds must accept all bold/colon variants of the
|
||||
// Requirements header. The forms render identically in markdown but differ
|
||||
// textually; the previous regex only matched **Requirements**: (colon
|
||||
// outside bold) and silently returned null for **Requirements:** (colon
|
||||
// inside bold) and **Requirements** : (spaced).
|
||||
describe.each([
|
||||
{ name: 'colon inside bold', header: '**Requirements:** RV-01, RV-02' },
|
||||
{ name: 'colon outside bold', header: '**Requirements**: RV-01, RV-02' },
|
||||
{ name: 'space before colon', header: '**Requirements** : RV-01, RV-02' },
|
||||
])('phase_req_ids extraction (#2769)', ({ name, header }) => {
|
||||
it(`parses Requirements header with ${name}`, async () => {
|
||||
// Overwrite ROADMAP.md so phase 9 carries the variant header.
|
||||
await writeFile(join(tmpDir, '.planning', 'ROADMAP.md'), [
|
||||
'# Roadmap',
|
||||
'',
|
||||
'## v3.0: SDK-First Migration',
|
||||
'',
|
||||
'### Phase 9: Foundation',
|
||||
'',
|
||||
'**Goal:** Build foundation',
|
||||
header,
|
||||
'',
|
||||
'### Phase 10: Read-Only Queries',
|
||||
'',
|
||||
'**Goal:** Implement queries',
|
||||
'',
|
||||
].join('\n'));
|
||||
|
||||
const result = await initPlanPhase(['9'], tmpDir);
|
||||
const data = result.data as Record<string, unknown>;
|
||||
expect(data.phase_req_ids).toBe('RV-01, RV-02');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('initNewMilestone', () => {
|
||||
|
||||
@@ -203,7 +203,12 @@ async function getPhaseInfoForVerifyWork(
|
||||
*/
|
||||
function extractReqIds(roadmapPhase: Record<string, unknown> | null): string | null {
|
||||
const section = roadmapPhase?.section as string | undefined;
|
||||
const reqMatch = section?.match(/^\*\*Requirements\*\*:[^\S\n]*([^\n]*)$/m);
|
||||
// Accept all bold/colon variants of the Requirements header. The forms
|
||||
// **Requirements:** (colon inside bold)
|
||||
// **Requirements**: (colon outside bold)
|
||||
// **Requirements** : (space before outside colon)
|
||||
// render identically in markdown but differ textually. Issue #2769.
|
||||
const reqMatch = section?.match(/^\*\*Requirements:?\*\*[^\S\n]*:?[^\S\n]*([^\n]*)$/m);
|
||||
const reqExtracted = reqMatch
|
||||
? reqMatch[1].replace(/[\[\]]/g, '').split(',').map((s: string) => s.trim()).filter(Boolean).join(', ')
|
||||
: null;
|
||||
|
||||
Reference in New Issue
Block a user