fix(#2522): fold wrapped success-criteria lines into their criterion (#2637)

* test(#2522): wrapped + blank-line success-criteria parse

* fix(#2522): fold wrapped success-criteria lines into their criterion

* chore(#2522): backfill changeset pr to 2637
This commit is contained in:
Tom Boucher
2026-07-25 00:36:05 -04:00
committed by GitHub
parent 1a9ae7601b
commit 461c744c31
3 changed files with 82 additions and 3 deletions

View File

@@ -621,6 +621,73 @@ describe('roadmap get-phase success criteria', () => {
assert.ok(output.success_criteria[2].includes('Third criterion'), 'third criterion matches');
});
test('#2522: wrapped success-criteria lines fold into their criterion (not dropped/truncated)', () => {
// A criterion that wraps onto an indented continuation line must fold INTO its
// criterion (retaining its trailing [REQ-ID]), and every criterion below the wrap
// must still be parsed. The old regex broke the run at the wrap, truncating
// criterion 2 mid-sentence (losing [A-02]) and silently dropping criterion 3.
fs.writeFileSync(
path.join(tmpDir, '.planning', 'ROADMAP.md'),
[
'# Roadmap',
'',
'### Phase 9: T',
'**Goal**: g',
'**Success Criteria** (what must be TRUE):',
' 1. alpha. [A-01]',
' 2. beta which is long and',
' wraps onto a second line. [A-02]',
' 3. gamma. [A-03]',
'**Plans**: TBD',
'',
].join('\n')
);
const result = runGsdTools('roadmap get-phase 9', tmpDir);
assert.ok(result.success, `Command failed: ${result.error}`);
const output = JSON.parse(result.output);
assert.strictEqual(output.found, true, 'phase should be found');
assert.strictEqual(output.success_criteria.length, 3, 'all 3 criteria must parse — wrapped continuations must not drop/truncate (#2522)');
assert.ok(output.success_criteria[1].includes('wraps onto a second line'), 'the wrapped continuation must fold into criterion 2');
assert.ok(output.success_criteria[1].includes('[A-02]'), 'the wrapped criterion must retain its trailing [REQ-ID] tag (#2522)');
assert.ok(output.success_criteria[2].includes('[A-03]'), 'criterion 3 (below the wrap) must not be dropped (#2522)');
// Boundary: the parse must not bleed past the Success Criteria block.
assert.ok(!output.success_criteria.some(c => c.includes('TBD')), 'parse must not consume the **Plans** line');
});
test('#2522: blank-line-separated success criteria still parse (the continuation fix must not regress blank lines)', () => {
// Guard against the naive-form trap the issue flags: narrowing the leading
// whitespace to [ \t]* and splitting on \n would trade wrapped-line truncation
// for blank-line truncation. Blank lines between criteria must keep working.
fs.writeFileSync(
path.join(tmpDir, '.planning', 'ROADMAP.md'),
[
'# Roadmap',
'',
'### Phase 1: T',
'**Goal**: g',
'**Success Criteria** (what must be TRUE):',
' 1. alpha. [A-01]',
'',
' 2. beta. [A-02]',
'',
' 3. gamma. [A-03]',
'**Plans**: TBD',
'',
].join('\n')
);
const result = runGsdTools('roadmap get-phase 1', tmpDir);
assert.ok(result.success, `Command failed: ${result.error}`);
const output = JSON.parse(result.output);
assert.strictEqual(output.success_criteria.length, 3, 'blank-line-separated criteria must all parse (#2522 guard)');
assert.ok(output.success_criteria[0].includes('[A-01]'));
assert.ok(output.success_criteria[1].includes('[A-02]'));
assert.ok(output.success_criteria[2].includes('[A-03]'));
});
test('returns empty array when no success criteria present', () => {
fs.writeFileSync(
path.join(tmpDir, '.planning', 'ROADMAP.md'),