* test(#3204): failing-first suite for the clobbered phase count A project declaring six phases with four phase directories on disk had state.record-session write progress.total_phases: 4 — #2828 regressing at 1.9.1, reported in #3204 with a deterministic reproduction. Before the fix in the following commit, these rows FAILED (wrote 4, expected 6): a flat roadmap carrying `## Progress`; one carrying `## Overview` and `## Phase Details`; the CRLF variant of the first. Two more, found by adversarial review and added after the first fix attempt, failed against that attempt: structural headings interleaved among flat phase headings, and this repo's own bundled-template shape (a `## Phases` wrapper around a single nested milestone). The #1761 control — sibling milestone sections must keep falling back to the disk count — passes both before and after, so the fix has something it must not break. Assertions read progress.total_phases through the product's own frontmatter parser via `state json --raw`, never a regex over STATE.md. Rows 12 and 13 are hostile: a phase heading carrying a version token, and a version heading inside a fenced code block; neither may count as milestone sectioning. Refs #3185, #3204 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(#3204): milestone sectioning is vocabulary, not heading position buildStateFrontmatter chooses total_phases between the ROADMAP's declared phase count and the on-disk directory count, and refuses the roadmap count when hasMilestoneSectioning says the document is milestone-sectioned — because a whole-document count would then conflate sibling milestones (#1761). That predicate returned true for ANY non-Phase level-2/3 heading, so a flat roadmap carrying an ordinary `## Progress` was called sectioned and the disk count clobbered the declared one: six declared phases, four directories, total_phases written as 4, converging on the truth only once the last directory happened to exist. That is #2828 regressing at 1.9.1, and it came from this epic — #3184 replaced state.cts's hand-rolled #2828 guard with this predicate, and the replacement is strictly more permissive than the guard it retired. Three position-based models were tried and all failed, because position does not carry milestone-ness: - any non-Phase heading (shipped) — over-detects, giving #3204; - strict nesting/ownership — misses same-level siblings, regressing #1761, and false-positives on the bundled template, where `## Phases` wraps a single `### v1.1`; - adjacency — reproduced live: `## Overview` and `## Notes` interleaved among six phase headings are two owning candidates, so a 6-phase roadmap with 2 directories wrote 2. A heading is now a milestone heading iff it is a non-Phase heading carrying a milestone signal: a version token, a status marker, or the word Milestone. Sectioning means two or more, since one cannot conflate siblings. Known limit, recorded in the doc comment rather than hidden: two milestone sections carrying none of those three signals are not detected. Also drops buildStateFrontmatter's local dedup-key regex, flagged in-source as diverging from the canonical token rule, for phaseKeyFromDir — the remainder of #3185, since #3222 had already routed the enumeration itself through listMilestonePhaseDirs. #1514, #2445 and #3017 are preserved untouched. Closes #3185 Fixes #3204 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * docs(#3185): changeset, glossary entry and ADR status for the phase-count fix CONTEXT.md's Roadmap Parser Module entry never named hasMilestoneSectioning, so the predicate whose semantics this change reverses had no glossary presence at all — a PR gate for a module/seam change. Added, covering the vocabulary model, the three position-based models that failed, and the residual limit. ADR-3180 recorded the fifth enumeration copy as unowned in four places. It is owned now. Amendment 4's scope table row 1 also carried an error worth keeping visible rather than rewriting: it claimed Phase 3 merged without routing the state writers, when #3222 had in fact routed the enumeration — the audit read Amendment 3's silence about the symbol names as absence of the work. The real gap was the trust discriminator one layer above, which is what #3204 was. Changeset is Fixed and leads with the symptom a user sees — a phase count that shrinks to match how many phase directories happen to exist yet — and carries the known limit forward rather than leaving it in a source comment. Refs #3185, #3204 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(#3185): stop quoting the retired phase-token regex in a comment The remote runner failed tests/phase-id-drift-guard.test.cjs: the comment explaining that the local dedup regex had been replaced by phaseKeyFromDir quoted that regex verbatim, and scripts/lint-phase-id-drift.cjs scans for the literal token without caring whether it sits in code or in prose. That is the guard being right, not over-eager — a quoted pattern is one paste away from being live again, which is exactly how the copy it replaced spread. Described in prose instead. Worth recording: this guard is check:phase-id-drift, which lint:ci does not run — it is enforced by tests/phase-id-drift-guard.test.cjs. A green lint:ci is therefore not evidence the drift guards pass. Refs #3185 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * chore(#3185): backfill changeset PR number (#3230) Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: sim <sim@local> Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
5
.changeset/gentle-badgers-forage.md
Normal file
5
.changeset/gentle-badgers-forage.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
type: Fixed
|
||||
pr: 3230
|
||||
---
|
||||
**`state.record-session` no longer shrinks your phase count** — a project whose ROADMAP declares more phases than it has directories on disk (phases 5 and 6 planned but not started yet) had `progress.total_phases` silently overwritten with the directory count, converging on the right number only once the last phase directory happened to exist. A flat roadmap carrying an ordinary heading like `## Progress` was being misread as milestone-sectioned. Known limit: two milestone sections carrying no version token, no status marker and not the word "Milestone" are still not detected as sectioning. (#3204)
|
||||
Reference in New Issue
Block a user