fix(#4243): anchor stateReplaceField bold form; pin frontmatter round-trip (#4453)

* test(#4243): failing-first regressions for bold-field anchoring and frontmatter round-trip

* fix(#4243): anchor stateReplaceField bold form to line start

The bold branch of stateReplaceField carried no ^ and no /m flag, so a bold
label quoted mid-sentence inside prose — the issue's **Status:** inside an
Accumulated Context bullet — captured the rewrite and destroyed the rest of
its line, silently, whenever a whole-body caller fed the function every
section (beginPhaseCore's tryField, advancePlanCore's Status/Current Plan
writes). The plain branch was always line-anchored; only the bold branch
lagged.

Anchored to ^([ \t]*\*\*Field:\*\*[ \t]*) with /im, reusing #4010's
same-line confinement idiom for the leading class (deliberately not the
issue's suggested ^\s* — it can consume the newlines before the label into
the match) and #4186's recognition-by-anchoring discipline. Frontmatter
half of the issue (unknown-key drops, invented milestone defaults) is
already fixed on next by #2202/#3216/#4129; pinned here with the issue's
requested regression fixtures.

* test(#4243): pin survival contract, not derived percent, in frontmatter rows

Bench RED run caught two assertion defects in the pin rows: the unknown
progress subkey re-parses as a quoted scalar ('77' vs 77), and percent is a
declared derived subkey - omitted under the #3573 no-roadmap withhold,
recomputed when measured (#4129) - so pinning its value over-pins derived
semantics. The rows now pin what the issue demands: unknown/custom keys
survive, stored counters are kept under the withhold, milestone identity is
never reset to invented defaults.

* chore(#4243): changeset for the anchored bold-field fix

* chore(#4243): backfill PR number in changeset
This commit is contained in:
Tom Boucher
2026-09-07 00:03:15 -04:00
committed by GitHub
parent 8c8eda46b0
commit 33e393ba4c
4 changed files with 451 additions and 1 deletions

View File

@@ -553,7 +553,22 @@ export function stateReplaceField(content: string, fieldName: string, newValue:
// `(.*)` captured the following line and the rebuild discarded it — the #4010
// data-loss. ADR-3180 §7.7 makes stateExtractField the same-line-confined owner;
// this aligns the writer to it.
const boldPattern = new RegExp(`(\\*\\*${escaped}:\\*\\*[ \\t]*)(.*)`, 'i');
//
// #4243: the bold form is also ANCHORED to line start, with same-line leading
// whitespace only. The pre-fix pattern carried no `^` and no `m` flag, so a
// bold label quoted MID-SENTENCE inside prose — an Accumulated Context bullet
// mentioning `**Status:**` — captured the rewrite and destroyed the rest of
// its line, silently, whenever a whole-body caller fed this function every
// section (beginPhaseCore's tryField, advancePlanCore's Status/Current Plan
// writes). The plain branch below was always line-anchored; only the bold
// branch lagged. Anchoring reuses #4010's same-line confinement idiom (the
// leading class is `[ \t]*`, deliberately NOT the `\s*` the issue suggested —
// `^\s*\*\*` can consume the newlines before the label into the match and
// drop them on rebuild) and #4186's recognition-by-anchoring discipline: a
// write target must BE the whole declared line shape, never a substring
// guess inside prose. `$` is explicit-and-inert (`.` never crosses line
// terminators) and documents that the match ends at end-of-line.
const boldPattern = new RegExp(`^([ \\t]*\\*\\*${escaped}:\\*\\*[ \\t]*)(.*)$`, 'im');
if (boldPattern.test(content)) {
return content.replace(boldPattern, (_match, prefix: string) => joinFieldReplacement(prefix, newValue));
}