#1365's fail-loud guard reused the parser's own D- grammar as its evidence test, so a populated <decisions> block using any other ID prefix (e.g. D5-01) was invisible to both parser and guard, collapsing could-not-parse into a clean none-present pass. Add an ID-shaped bold-lead-in probe as format-agnostic evidence on both parse paths; empty/prose scaffolds stay none-present. Graduates the #2371 d5-prefix representative fixture to its expected* assertion. Closes #2347. Admin-merged (self-review bypass) with full green CI.
This commit is contained in:
@@ -84,6 +84,27 @@ const bulletEmDashRe = /^\s*-\s+\*\*D-([A-Za-z0-9][A-Za-z0-9_-]*)(?:\s*\[([^\]]+
|
||||
*/
|
||||
const bulletTitledColonRe = /^\s*-\s+\*\*D-([A-Za-z0-9][A-Za-z0-9_-]*)(?:\s*\[([^\]]+)\])?[^:*]*:[^:*]*\*\*\s*(.*)$/;
|
||||
|
||||
/**
|
||||
* #2347: format-agnostic evidence that a block/section holds real decision
|
||||
* ENTRIES the parser could not read — a bullet whose bold lead-in is an
|
||||
* ID-SHAPED token (uppercase prefix, optional digits, hyphen, alnum), whatever
|
||||
* the exact ID grammar. The three parser grammars above all require a `D-`
|
||||
* prefix; #1365's fail-loud guard reused that same `\bD-` test as its "is this
|
||||
* decision-shaped?" evidence, so any other prefix (e.g. `D5-01`) was invisible
|
||||
* to BOTH parser and guard, collapsing `could-not-parse` into a clean
|
||||
* `none-present` pass.
|
||||
*
|
||||
* The ID-shape requirement (not "any bold bullet") is deliberate: a decisions
|
||||
* block or `### Claude's Discretion` sub-section legitimately contains prose
|
||||
* bullets with bold labels (`- **Scope:** …`, `- **Why:** …`, `- **Note:** …`).
|
||||
* Those are NOT decision entries and must stay `none-present` — a false
|
||||
* `could-not-parse` hard-blocks the plan gate. `[A-Z]+[0-9]*-[A-Za-z0-9]` matches
|
||||
* `D-01` / `D5-01` / `DEC-01` but not `Scope:` / `Why:` / `Follow-up:` (mixed
|
||||
* case) / `TODO:` (no `-<alnum>` id) — mirroring the parser's own `D-<alnum>`
|
||||
* shape without hardcoding the `D`.
|
||||
*/
|
||||
const boldLeadInBulletRe = /^\s*-\s+\*\*[A-Z]+[0-9]*-[A-Za-z0-9]/m;
|
||||
|
||||
interface ParseDecisionLinesResult {
|
||||
decisions: Decision[];
|
||||
parseMisses: number;
|
||||
@@ -241,11 +262,13 @@ export function extractDecisions(content: unknown): DecisionExtraction {
|
||||
}
|
||||
// FIX A: Block present but 0 extracted and no parse-misses.
|
||||
// Only report could-not-parse when there is genuine evidence of real decisions
|
||||
// that failed to parse: a \bD- token in the block text, or an unterminated fence.
|
||||
// An empty scaffold (<decisions></decisions>) or an all-prose block has no such
|
||||
// evidence — treat as none-present so the gate passes cleanly.
|
||||
// that failed to parse: a bold-lead-in bullet (`- **…**`, any ID grammar — #2347),
|
||||
// a \bD- token in the block text, or an unterminated fence. An empty scaffold
|
||||
// (<decisions></decisions>) or an all-prose block has no such evidence — treat
|
||||
// as none-present so the gate passes cleanly.
|
||||
const hasDecisionTokenInBlock = /\bD-[A-Za-z0-9]/m.test(combined);
|
||||
if (hasDecisionTokenInBlock || unterminatedFence) {
|
||||
const hasBoldLeadInBullet = boldLeadInBulletRe.test(combined);
|
||||
if (hasDecisionTokenInBlock || hasBoldLeadInBullet || unterminatedFence) {
|
||||
return { decisions: [], outcome: 'could-not-parse' };
|
||||
}
|
||||
return { decisions: [], outcome: 'none-present' };
|
||||
@@ -271,11 +294,13 @@ export function extractDecisions(content: unknown): DecisionExtraction {
|
||||
return { decisions, outcome: 'could-not-parse' };
|
||||
}
|
||||
// FIX A: Heading found but 0 extracted and no parse-misses.
|
||||
// Only report could-not-parse when the section body contains a D- token.
|
||||
// A heading with only prose, sub-headings, or all-discretion content
|
||||
// (no trackable D- tokens) is a legitimate empty/discretion section → none-present.
|
||||
// Report could-not-parse when the section body holds a decision-entry-shaped
|
||||
// bold-lead-in bullet (`- **…**`, any ID grammar — #2347) or a D- token. A
|
||||
// heading with only prose, sub-headings, or all-discretion content (no such
|
||||
// evidence) is a legitimate empty/discretion section → none-present.
|
||||
const hasDecisionTokenInSection = /\bD-[A-Za-z0-9]/m.test(section.body);
|
||||
if (hasDecisionTokenInSection) {
|
||||
const hasBoldLeadInBulletInSection = boldLeadInBulletRe.test(section.body);
|
||||
if (hasDecisionTokenInSection || hasBoldLeadInBulletInSection) {
|
||||
return { decisions: [], outcome: 'could-not-parse' };
|
||||
}
|
||||
return { decisions: [], outcome: 'none-present' };
|
||||
|
||||
Reference in New Issue
Block a user