phase.cjs:updateRoadmapAfterPhaseRemoval renumbers plan references in
ROADMAP.md via a regex that captured `NN-NN` followed by an optional
suffix:
/(?<![0-9-])(\d{2})-(\d{2})(?=(?:-(?:PLAN|SUMMARY)\.md)?(?![0-9-]))/g
The suffix branch was strict: it only accepted `-(PLAN|SUMMARY).md`
directly after the plan number. A slug like
`07-01-cherry-pick-foundation-PLAN.md` placed `-cherry-…` between the
number and the canonical suffix, so both the suffix branch AND the
"bare token" branch (`(?![0-9-])` — fails because the next char is `-`)
failed. Result: the on-disk file got renamed to
`06-01-cherry-pick-foundation-PLAN.md` by the directory-rename pass,
but the ROADMAP entry kept pointing at the stale `07-01-…` prefix —
disk/ROADMAP inconsistency.
Fix: extend the suffix branch to allow an optional kebab-case slug
between the plan number and the PLAN/SUMMARY suffix:
(?:(?:-[A-Za-z][A-Za-z0-9-]*)*-(?:PLAN|SUMMARY)\.md)|(?![0-9-])
Each slug token must start with a letter so `07-01-02-PLAN.md` is not
silently consumed as one slugged token (the `-02` is correctly
unreachable from the slug branch because it starts with a digit).
Regression test exercises three cases via the typed `roadmap get-phase
--json` query (no raw text matching on ROADMAP.md content):
1. Slugged PLAN + SUMMARY filenames get renumbered (#3602 fix).
2. Compact `NN-NN-PLAN.md` filenames still renumber correctly
(#3601 / earlier contracts preserved).
3. Counter-test: ISO dates (`2026-01-01`) and version tags (`v1-2-3`)
in ROADMAP prose are NOT modified — the existing `(?<![0-9-])` /
`(?![0-9-])` boundaries hold against false positives.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This directory holds per-PR CHANGELOG fragments. Every PR with user-facing changes drops one (or more) <random-name>.md files here describing its CHANGELOG entry. Fragments are consolidated into the top-level CHANGELOG.md at release time.
Why
Two PRs that both edit the ### Fixed block of CHANGELOG.md always conflict on merge — git can't pick a serialization order without human input. Two PRs that each add a fresh .changeset/<unique-name>.md never conflict because they don't share lines.
Reads every fragment, groups bullets by type:, replaces ## [Unreleased] with a new ## [vX.Y.Z] - YYYY-MM-DD block, opens a fresh ## [Unreleased] above, deletes consumed fragments. Idempotent.