fix(milestone): correct version detection, stats scoping, and entry ordering#2
fix(milestone): correct version detection, stats scoping, and entry ordering#2
Conversation
…ion (gsd-build#768) Strip <details> blocks (shipped milestones) before matching, and extract both version and name from the same ## heading for consistency.
…milestone phases cmdMilestoneComplete previously iterated ALL phase directories in .planning/phases/, including phases from prior milestones that remain on disk. This produced inflated stats, stale accomplishments from old summaries, and --archive-phases moving directories that belong to earlier milestones. This fix parses ROADMAP.md to extract phase numbers for the current milestone, builds a normalized Set for O(1) lookup, and filters all phase directory operations through an isDirInMilestone() helper. The helper handles edge cases: leading zeros (01 -> 1), letter suffixes (3A), decimal phases (3.1), large numbers (456), and excludes non-numeric directories (notes/, misc/). Adds 5 tests covering scoped stats, scoped archive, prefix collision guard, non-numeric directory exclusion, and large phase numbers. Complements upstream PRs gsd-build#756 and gsd-build#783 which fix getMilestoneInfo() milestone detection — this fix addresses milestone completion scoping. Co-Authored-By: Claude Opus 4.6 <[email protected]>
… order cmdMilestoneComplete previously appended new milestone entries at the end of MILESTONES.md. Over successive milestones this produced chronological order (oldest first), which is counterintuitive — users expect the most recent milestone at the top. This fix detects the file header (h1-h3) and inserts the new entry immediately after it, pushing older entries down. Files without a recognizable header get the entry prepended. New files still get a default '# Milestones' header. Adds 2 tests: single insertion ordering assertion and three-sequential- completions verification (v1.2 < v1.1 < v1.0 in file order). Co-Authored-By: Claude Opus 4.6 <[email protected]>
Code Logic Verification of PR #21. The Before State (Bug Paths)Bug 1 (gsd-build#768) — Wrong version detection: Bug 2 — Inflated stats: Bug 3 — Wrong MILESTONES.md order: New entries appended to bottom (oldest-first). Should be reverse chronological (newest-first). 2. The After State (Fix Paths)Fix 1 (
Fix 2 (
Fix 3 (
3. Key Correctness Checks
Pre-existing edge cases identified (not introduced by this PR):
4. VerdictPASS — All three bugs correctly fixed. Phase scoping is robust with exact-match normalization. Version detection correctly handles Status: All 3 commits already present on
|
|
Closing — all 3 commits already merged to main. Upstream PRs gsd-build#784, gsd-build#785 merged; gsd-build#809 closed. Core Logic Verification posted above confirms PASS. |
Summary
This PR fixes three interrelated bugs in the
milestone completeworkflow that caused incorrect behavior when completing milestones in projects with a multi-milestone history. These bugs were discovered during production use of GSD on a project that had shipped 50+ milestones.Bug 1 — Wrong version detection (gsd-build#768): After completing a milestone,
getMilestoneInfo()would return the version of a previously shipped milestone instead of the current active one. This happened because shipped milestones were collapsed into<details>blocks in ROADMAP.md but their## Roadmap vX.Y:headings still matched the regex first.Bug 2 — Inflated stats and stale accomplishments: The
milestone completecommand counted all phase directories on disk — including phases from prior milestones that had not been archived. This inflated phase counts, plan counts, and polluted the accomplishments list with work from previous milestones.Bug 3 — Wrong chronological order in MILESTONES.md: New milestone entries were appended to the bottom of MILESTONES.md, producing oldest-first ordering. Users expect reverse chronological order (newest first), matching the convention used by changelogs and release history files.
Root Cause
Bug 1 (
core.cjs—getMilestoneInfo): The version and name were extracted with two independent regexes that both scanned the entire ROADMAP.md file, including<details>blocks containing shipped milestones:When a ROADMAP.md looked like this, the regex matched
v0.1from the collapsed<details>block instead of the activev0.2:Bug 2 (
milestone.cjs—cmdMilestoneComplete): The stats-gathering loop iterated over every directory in.planning/phases/with no filtering:The same unscoped iteration applied to
--archive-phases, which would move all phase directories (including ones belonging to other milestones) into the archive.Bug 3 (
milestone.cjs— MILESTONES.md write): New entries were simply appended:Fix
Commit 1:
getMilestoneInfo()version detection<details>...</details>blocks from the ROADMAP before regex matching, so shipped milestones collapsed in details blocks cannot interfere## Roadmap vX.Y: Nameheading match (single regex) instead of two independent matches, ensuring consistencyv\d+\.\d+match (on the cleaned content) if no heading is foundCommit 2: Scope stats, accomplishments, and archive to current milestone phases
### Phase N:headings to build a set of phase numbers belonging to the current milestoneisDirInMilestone(dirName)helper that maps directory names like01-setupto phase number1,456-dacpto456,3A-barto3aisDirInMilestone()so only current-milestone phases are counted/movednotes/) are excluded when scoping is active1does not prefix-match phase10(exact number match, not string prefix)Commit 3: Reverse chronological MILESTONES.md insertion
/^(#{1,3}\s+[^\n]*\n\n?)/Relationship to Other PRs
This is PR 1 of 6 from the
dev-bugfixbranch, which collects bug fixes and enhancements discovered during production use:core.cjs,milestone.cjsdev-bugfix, notmainThese PRs have no code overlap and can be merged independently in any order. This PR touches only
get-shit-done/bin/lib/core.cjsandget-shit-done/bin/lib/milestone.cjs.Testing
New tests added (10 test cases)
returns active milestone when shipped milestone is collapsed in details block<details>block with shipped milestone is stripped; active milestone is detectedreturns active milestone when multiple shipped milestones exist in details blocks<details>blocks are all stripped; only the active heading is matchedreturns defaults when roadmap has no heading matchesv1.0/milestonewhen no## Roadmap vX.Y:heading existsprepends to existing MILESTONES.md (reverse chronological)three sequential completions maintain reverse-chronological orderscopes stats to current milestone phases onlyarchive-phases only archives current milestone phases--archive-phasesmoves only current-milestone phase dirs; prior-milestone dirs remain in placephase 1 in roadmap does NOT match directory 10-something1does not prefix-collide with phase directory10-scalingnon-numeric directory is excluded when milestone scoping is activenotes/directory insidephases/is not counted as a phaselarge phase numbers (456, 457) scope correctly45is excludedFull suite results
Impact
getMilestoneInfo()now correctly returns the active milestone version even when ROADMAP.md contains shipped milestones in<details>blocks. This fixes downstream consumers that rely on version detection (e.g.,discuss-phase,plan-phase,execute-phase).--archive-phasesnow moves only the current milestone's phase directories, leaving prior-milestone phases untouched for projects that retain them on disk.<details>blocks,getMilestoneInfo()behaves identically. If ROADMAP.md contains no phase headings, the stats loop includes all directories (same as before). The only visible change for existing users is that MILESTONES.md entries are now prepended instead of appended.