* test(#2199): cover bullet/em-dash ROADMAP phase resolution + milestone count Adds the bullet-only ROADMAP fixture the suite lacked: an all-bullet em-dash ROADMAP resolves each phase (no Phase null), colon/en-dash/hyphen bullet separators all resolve, mixed heading + bullet forms coexist, and the milestone phase-count counts bullet-form phases instead of collapsing to zero. * fix(#2199): accept bullet/em-dash phase entries in roadmap lookup + milestone filter Roadmap phase lookup (findRoadmapPhaseInContent) matched only ATX headings against a colon-required pattern, so a bullet/checkbox entry like `- [ ] **Phase N — name**` — which the bundled roadmapper emits in bullet-house- style ROADMAPs — resolved found:false and `Phase null` was written into STATE.md. The milestone phase-filter built its phase set from headings only, so a bullet- only ROADMAP collapsed to a zero-count pass-all filter and progress denominators broke. - Add a shared bullet-phase-line pattern (separator: em-dash/en-dash/hyphen/colon). - findRoadmapPhaseInContent: on a heading-match miss, fall back to the bullet line for the requested phase; return found:true with the captured name. - getMilestonePhaseFilter: also scan bullet lines into the milestone phase set. Closes #2199 * docs(#2199): add changeset fragment * fix(#2199): bullet phase lookup as last resort + consolidate test (review) Two corrections to the initial fix: 1. Regression — the bullet fallback inside findRoadmapPhaseInContent was too eager: it returned a bullet match from the scoped (current-milestone) content before the caller tried the full-content heading path, so a phase whose Requirements live in a Phase Details heading (after the active-milestone section) got a bullet-line section with no Requirements → phase_req_ids null (broke 3 init tests). Restructure: findRoadmapPhaseInContent is heading-only again; a separate findRoadmapBulletPhaseInContent runs in getRoadmapPhaseInternal ONLY after scoped + full heading lookup fails, so a heading with a Requirements section always wins. 2. lint-test-file-count — the standalone fix-2199-roadmap-bullet-phase.test.cjs collided with the over-cap 'roadmap' module (FAIL_NOVEL_FILES). Consolidate the regression into the existing tests/roadmap-parser.test.cjs (its natural home, under the 2-file cap). * test(#2199): assert heading-in-full beats bullet-in-scoped (review L3) The exact first-attempt regression: a phase has a bullet in the active-milestone scope but its heading (carrying Requirements) lives in a Phase Details section outside that scope. Pin that the heading section wins over the bullet line so req_ids resolve and the eager-bullet bug cannot return. * docs(#2199): backfill PR number in changeset fragment
Changeset Fragments
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.
See #2975 for the full rationale.
Adding a fragment
node scripts/changeset/new.cjs \
--type Fixed \
--pr 1234 \
--body "fix the thing — explain the user-visible change in one sentence"
This writes .changeset/<adjective>-<noun>-<noun>.md with frontmatter and a body. Three random words → concurrent PRs don't collide.
Format
---
type: Fixed
pr: 1234
---
**`/gsd-foo` no longer drops trailing slashes** — explain the user-visible change.
Allowed type: values follow Keep a Changelog: Added, Changed, Deprecated, Removed, Fixed, Security.
Opting out
PRs that legitimately have no user-facing impact can add the no-changelog label. CI honors it. When unsure, add the fragment.
At release time
Promotion is automatic. The release workflow's finalize job runs:
node scripts/changeset/cli.cjs render --version vX.Y.Z --date YYYY-MM-DD --allow-empty
This reads every fragment, groups bullets by type:, replaces ## [Unreleased] with a new ## [vX.Y.Z] - YYYY-MM-DD block, opens a fresh ## [Unreleased] above, and deletes consumed fragments. The --allow-empty flag ensures a no-change release still gets a dated heading (with a _No notable changes._ placeholder). A subsequent verify step confirms the promotion landed correctly. Maintainers do not run this by hand.
Archived fragments
.changeset/archived/ holds fragments for already-shipped releases (≤ 1.3.1), retained for provenance. Their content was hand-curated into the dated ## [1.x.y] sections of CHANGELOG.md during the #690 backfill — they were never consumed by render. All changeset tooling enumerates .changeset/ non-recursively, so archived fragments are never picked up or rendered. Do not move them back to the top level.