fix(roadmap): recognize '## Slice Roadmap' header in extractSlicesSection

The regex in extractSlicesSection matched Slices, Slice Overview, Slice Table,
Slice Summary, and Slice Status but not Slice Roadmap. When a roadmap used the
'## Slice Roadmap' heading, the section extractor returned empty, causing the
parser to fall through to prose headers which lack checkbox state -- marking all
slices as incomplete and trapping auto-mode in a dispatch loop.

Add 'Roadmap' to the alternation and a regression test that verifies checkbox
slices under the '## Slice Roadmap' heading are parsed with correct done state.

Fixes #1940

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Tom Boucher 2026-03-21 23:30:15 -04:00
parent c1a35dd1b3
commit 2662c41bf6
2 changed files with 28 additions and 2 deletions

View file

@ -41,8 +41,8 @@ export function expandDependencies(deps: string[]): string[] {
}
function extractSlicesSection(content: string): string {
// Match "## Slices", "## Slice Overview", "## Slice Table", etc.
const headingMatch = /^## Slice(?:s| Overview| Table| Summary| Status)\b.*$/m.exec(content);
// Match "## Slices", "## Slice Overview", "## Slice Table", "## Slice Roadmap", etc.
const headingMatch = /^## Slice(?:s| Overview| Table| Summary| Status| Roadmap)\b.*$/m.exec(content);
if (!headingMatch || headingMatch.index == null) return "";
const start = headingMatch.index + headingMatch[0].length;

View file

@ -236,6 +236,32 @@ test("parseRoadmapSlices: ## Slices with valid checkboxes does NOT invoke prose
assert.equal(slices[0]?.done, true);
});
// ── Regression test for #1940 ───────────────────────────────────────────────
// '## Slice Roadmap' header is not recognized by extractSlicesSection, causing
// checkbox-format slices to be missed and all slices reported as incomplete.
test("parseRoadmapSlices: ## Slice Roadmap heading recognized (#1940)", () => {
const roadmapContent = [
"# M002: Current Milestone", "",
"**Vision:** Ship it.", "",
"## Slice Roadmap", "",
"- [x] **S01: Foundation** `risk:low` `depends:[]`",
" > After this: base layer works.",
"- [x] **S02: Core Logic** `risk:medium` `depends:[S01]`",
"- [ ] **S03: Polish** `risk:low` `depends:[S01,S02]`", "",
"## Boundary Map",
].join("\n");
const slices = parseRoadmapSlices(roadmapContent);
assert.equal(slices.length, 3, "should parse 3 slices under '## Slice Roadmap'");
assert.equal(slices[0]?.id, "S01");
assert.equal(slices[0]?.done, true, "S01 should be marked done");
assert.equal(slices[1]?.id, "S02");
assert.equal(slices[1]?.done, true, "S02 should be marked done");
assert.equal(slices[2]?.id, "S03");
assert.equal(slices[2]?.done, false, "S03 should be pending");
assert.deepEqual(slices[2]?.depends, ["S01", "S02"]);
});
test("parseRoadmapSlices: ## Slices with only non-matching lines returns prose fallback results", () => {
const weirdContent = `# M020: Odd