From afa0f9e463546c28902c12759bad6c431608bdbc Mon Sep 17 00:00:00 2001 From: mastertyko <11311479+mastertyko@users.noreply.github.com> Date: Wed, 8 Apr 2026 19:19:32 +0200 Subject: [PATCH] fix(gsd): ignore filename headings in parsePlan --- .../extensions/gsd/parsers-legacy.ts | 4 ++- .../extensions/gsd/tests/parsers.test.ts | 25 +++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/src/resources/extensions/gsd/parsers-legacy.ts b/src/resources/extensions/gsd/parsers-legacy.ts index ee0632fd2..00ecb00c5 100644 --- a/src/resources/extensions/gsd/parsers-legacy.ts +++ b/src/resources/extensions/gsd/parsers-legacy.ts @@ -210,7 +210,9 @@ function _parsePlanImpl(content: string): SlicePlan { for (const line of lines) { const cbMatch = line.match(/^-\s+\[([ xX])\]\s+\*\*([\w.]+):\s+(.+?)\*\*\s*(.*)/); // Heading-style: ### T01 -- Title, ### T01: Title, ### T01 — Title - const hdMatch = !cbMatch ? line.match(/^#{2,4}\s+([\w.]+)\s*(?:--|—|:)\s*(.+)/) : null; + const hdMatch = !cbMatch + ? line.match(/^#{2,4}\s+([A-Z]+\d+(?:\.[A-Z]+\d+)*)\s*(?:--|—|:)\s*(.+)/) + : null; if (cbMatch || hdMatch) { const taskId = cbMatch ? cbMatch[2] : hdMatch![1]; // Skip tasks already found in the Tasks section diff --git a/src/resources/extensions/gsd/tests/parsers.test.ts b/src/resources/extensions/gsd/tests/parsers.test.ts index 3292d71ad..0c727b7ec 100644 --- a/src/resources/extensions/gsd/tests/parsers.test.ts +++ b/src/resources/extensions/gsd/tests/parsers.test.ts @@ -703,6 +703,31 @@ Widget description. assert.deepStrictEqual(p.tasks[0].title, 'Build the widget', 'em-dash heading T01 title'); }); +test('parsePlan: filename subheadings do not become task ids', () => { + const content = `# S15: Filename Headings + +**Goal:** Ignore file-reference subheadings inside task descriptions. +**Demo:** Only real task ids are parsed. + +## Tasks + +- [ ] **T01: First task** \`est:10m\` + Implement the feature. + +### constraints.py — \`add_off_request_tiered()\` +- preserve behavior + +### annotations.py — \`annotate()\` +- keep metadata +`; + + const p = parsePlan(content); + assert.deepStrictEqual(p.tasks.map((task) => task.id), ['T01'], 'filename subheadings should not create extra tasks'); + assert.deepStrictEqual(p.tasks[0].title, 'First task', 'real task should still parse normally'); + assert.ok(p.tasks[0].description.includes('preserve behavior'), 'detail lines under filename subheadings should remain attached to the task'); + assert.ok(p.tasks[0].description.includes('keep metadata'), 'later detail lines should also remain attached to the task'); +}); + test('parsePlan: mixed checkbox and heading-style tasks', () => { const content = `# S14: Mixed Format