* test(#3569): pin stats phase-id shape — inline-code mentions produce no phantom row Failing-first regression for #3569: cmdStats' heading scan accepted any word as a phase id, so prose mentioning ### Phase N: inside inline code inflated phases_total and disagreed with roadmap analyze. New adversarial fixture phase-heading-inside-inline-code.md (blockquote + bare mention), parity assertion against roadmap analyze, and over-narrowing guards for decimal / milestone-prefixed / letter-prefixed ids. * fix(#3569): require a digit-bearing phase id in the stats heading scan cmdStats' hand-rolled heading pattern captured any word as a phase id, so a ### Phase N: token inside an inline code span (the issue's blockquote) produced a phantom Not-Started row that could never complete, inflating phases_total and deflating percent forever. The id capture is now the canonical #3036 shape roadmap.cts uses (digit required; letter-prefixed, decimal, and milestone-prefixed ids keep counting), so stats and roadmap analyze agree. * fix(#3569): sanction the stats id-shape literal; correct zero-padded expectation Review findings: the phase-id drift guard requires the // phase-id-owner: comment directly above the regex (same form as roadmap.cts); the milestone-prefixed over-narrowing guard must expect normalizePhaseName's zero-padded 02-01 form, not the raw 2-01 token. * chore(#3569): add changeset fragment * chore(#3569): backfill changeset pr number --------- Co-authored-by: sim <sim@local>
Adversarial Roadmap Fixtures (#3594)
Hostile / messy ROADMAP.md inputs for
gsd-core/bin/lib/roadmap.cjs (searchPhaseInContent,
cmdRoadmapGetPhase, cmdRoadmapAnalyze) and the SDK roadmap parser
in sdk/src/query/roadmap.ts.
Tests in tests/feat-3594-parser-adversarial-roadmap.test.cjs and
tests/feat-3594-parser-property-style.test.cjs consume these.
Categories:
phase-heading-inside-fenced-code.md— amdblock contains a## Phase 999: fakeheading. The parser MUST ignore headings inside fenced code blocks. Historical regression #2787.nested-fenced-code.md— outerblock with inner ``` ``` ``` ```block. Headings inside either layer must be ignored.unicode-phase-titles.md— phase titles with non-ASCII characters. Parser must preserve them in the returnedphase_name.repeated-phase-ids.md— same integer phase number listed twice. Parser behavior must be deterministic (first-wins or last-wins is fine; the test pins whichever).decimal-phase-mixed.md— integer phase 2 and decimal phase 2.1 share a prefix. Parser must not return phase 2 when asked for 2.1 (or vice versa). Historical regression #3537.markdown-headings-inside-html-comment.md—<!-- ## Phase 999 -->patterns. Parser must not be fooled by comments.