fix(#4130): parse phase-prefixed decision IDs (D4-01) (#4357)

* test(#4130): failing-first regression for phase-prefixed decision IDs

Add the #4130 matrix: D4-01/D12-01 across all three bullet forms, tags,
discretion, wrapped lead-ins, gate-level plan/verify end-to-end rows, and
parity properties (well-formed digit-prefixed ids parse to their exact id;
a non-digit injected into the prefix fails loud). Update the #2347
non-D-prefix fixture from D5-NN (now a legal grammar) to DEC-NN, and
graduate the representative d5-prefix corpus fixture from could-not-parse
to parsed-but-uncovered.

All new rows are RED against origin/next; they go green with the parser
fix in the next commit.

* fix(#4130): parse phase-prefixed decision IDs (D4-01)

The three declaration grammars, the parse-miss guard, the #3939 join
regexes, and the token evidence all anchored on the literal 'D-' (or
'**D-'), so an ID carrying a digit-run phase prefix between the leading
letter and the hyphen matched nothing — while the #2347 shape detector
correctly called those bullets decision-shaped, collapsing the whole
CONTEXT.md to could-not-parse with 0 extracted instead of a coverage
verdict.

Derive the extractor ID grammar from one shared DECISION_ID_SOURCE
('D[0-9]*-' + the existing alnum tail, full id captured), widen the
guard/join anchors to ID_ATTEMPT_SOURCE (bare 'D-' or a digit-initial
prefix run, so a typo'd 'D4x-01' fails loud while letter-initial prose
like 'Deferred-until' stays none-present), and align the bare-token
evidence. Both gates and the gap-checker share the parser, so all three
surfaces read phase-prefixed decisions now; the gate messages name the
accepted forms including the phase-prefixed one.

* docs(#4130): document the phase-prefixed decision identifier form

The canonical CONTEXT.md reference said decisions carry 'a sequential
D-NN identifier' with no mention of the optional phase-number prefix the
parser now accepts (D4-01) or the alphanumeric tail it always accepted
(D-INFRA-01). Name both in the Decision identifier format section, EN
and ja-JP.

* chore(#4130): changeset

* chore(#4130): backfill PR number in changeset

---------

Co-authored-by: sim <sim@local>
This commit is contained in:
Tom Boucher
2026-09-05 21:05:19 -04:00
committed by GitHub
parent 48789fe9a9
commit 06eba5fdb0
9 changed files with 480 additions and 67 deletions

View File

@@ -4,9 +4,10 @@
"fixtures": [
{
"file": "d5-prefix-context.md",
"expectedReason": "could-not-parse",
"expectedPassed": false,
"note": "A <decisions> block using the D5-01 ID-prefix shape from #2347's own reproduction ('- **D5-01:** some decision'), repeated twice so 'populated but 0 extracted' is unambiguous. The original report used 23 real decisions under a project-specific D5- prefix convention; this fixture preserves the exact grammar mismatch, not the count. FIXED by #2347: the guard's evidence test no longer reuses the parser's D- grammar — a bold ID-shaped lead-in bullet (any prefix) is now evidence, so this populated block correctly fails loud (could-not-parse). currentBuggyOutput removed and the assertion graduated to expected* per this corpus's contract."
"expectedTotal": 2,
"expectedCovered": 0,
"note": "A <decisions> block using the D5-01 ID-prefix shape from #2347's own reproduction ('- **D5-01:** some decision'), repeated twice so 'populated' is unambiguous. The original report used 23 real decisions under a project-specific D5- prefix convention; this fixture preserves the exact grammar, not the count. History: #2347 made the guard's evidence test format-agnostic so this populated block failed loud (could-not-parse) instead of silently passing. #4130 then made the digit-run phase prefix LEGAL — the parser now reads D5-01/D5-02 — so the gate reports a real coverage verdict for this shape: both decisions parsed (total 2) and uncovered (covered 0, passed false, no reason field) in this bare project. The corpus's graduation contract: the assertion was updated as part of the #4130 fix that changed the observable verdict."
}
]
}

View File

@@ -1,4 +1,4 @@
# Decision-coverage guard fixture (#2347)
# Decision-coverage guard fixture (#2347, graduated by #4130)
`d5-prefix-context.md` is the verbatim reproduction shape from #2347 — the
`- **D5-01:** some decision` bullet given in the issue's own "Steps to
@@ -7,18 +7,23 @@ reproduce" — used as a CONTEXT.md `<decisions>` block and driven through
CLI gate; see `tests/decisions.test.cjs` for the established pattern this
follows), not `extractDecisions()` called in isolation.
The #1365 fail-loud guard's "is this decision-shaped?" evidence test
(`/\bD-[A-Za-z0-9]/`) reuses the same `D-` grammar as the parser it guards.
For any ID prefix the parser cannot read — `D5-01` here — the guard sees
no evidence either, so the two failure modes the guard exists to
distinguish (`none-present` vs `could-not-parse`) collapse into
`none-present`, and a populated, genuinely decision-shaped CONTEXT.md
passes silently.
History of the expected verdict for this exact shape:
Expected once fixed: `reason: 'could-not-parse'`, `passed: false` (see
`MANIFEST.json`'s `expectedReason`/`expectedPassed`). Today's gate instead
reports `passed: true, skipped: true, reason: 'no trackable decisions'` —
pinned in `MANIFEST.json`'s `currentBuggyOutput` and asserted directly in
`tests/representative-corpus.test.cjs` (a characterization of today's
known-broken behavior, not a `todo` — see
`tests/fixtures/representative/README.md` for why) until #2347 lands.
1. **#2347 (original):** the #1365 fail-loud guard's "is this
decision-shaped?" evidence test (`/\bD-[A-Za-z0-9]/`) reused the same
`D-` grammar as the parser it guards, so `D5-01` was invisible to both
and a populated CONTEXT.md passed silently. #2347 made the evidence test
format-agnostic, and the fixture's expectation graduated from the
characterized buggy green-skip to `could-not-parse`.
2. **#4130 (current):** the digit-run phase prefix itself became a LEGAL
ID grammar — the parser now reads `D5-01`/`D5-02` — so this shape no
longer fails loud at all. The gate reports a real coverage verdict:
`total: 2`, `covered: 0`, `passed: false`, and NO `reason` field (the
coverage path emits none). Pinned in `MANIFEST.json`'s
`expectedTotal`/`expectedCovered`/`expectedPassed` and asserted in
`tests/representative-corpus.test.cjs`.
The fail-loud contract itself is unchanged and still covered by the
`DEC-`-prefix (non-`D` universe) tests in `tests/decisions.test.cjs`:
an ID grammar the parser genuinely cannot read still yields
`could-not-parse`.