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

@@ -221,6 +221,187 @@ describe('stateReplaceField — empty field preserves the following line (#4010)
});
});
// #4243: the bold branch of stateReplaceField was UNANCHORED
// (`(\*\*Field:\*\*[ \t]*)(.*)` with no ^ and no /m), 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 the line,
// silently, while the real field went stale or was updated elsewhere. The fix
// anchors the bold form to line start with same-line leading whitespace
// (`^([ \t]*\*\*Field:\*\*[ \t]*)`, 'im'), reusing #4010's same-line
// confinement idiom and #4186's recognition-by-anchoring discipline. These rows
// pin the corruption shapes; rows further down pin the negative space
// (legitimate line-start bold updates are byte-identical, branch order and
// first-occurrence-wins unchanged).
describe('stateReplaceField — anchored bold form leaves prose lookalikes untouched (#4243)', () => {
// The issue's verbatim prose line: a bold label quoted for documentation
// purposes inside a bullet, with the real field in the plain template form.
const ISSUE_PROSE_LINE =
'- [Phase 170]: archived files gained a `**Status:**Ready to execute` marker. Must not change.';
// ROW 1 — the failing-first regression from the issue. The lookalike must
// survive byte-identically and the REAL plain field must take the update.
test('issue repro: mid-sentence **Status:** lookalike survives, real plain field updates', () => {
const input = [
'## Current Position',
'',
'Phase: 5 of 9',
'Plan: 2 of 6',
'Status: Ready to execute',
'Last activity: 2026-08-01 — did a thing',
'',
'## Accumulated Context',
'',
'### Decisions',
'',
ISSUE_PROSE_LINE,
'',
].join('\n');
const result = stateReplaceField(input, 'Status', 'Executing Phase 901');
assert.notEqual(result, null, 'the real plain field must still match');
assert.ok(
result.includes(ISSUE_PROSE_LINE),
`prose lookalike must survive byte-identically, got:\n${result}`,
);
assert.ok(
/^Status: Executing Phase 901$/m.test(result),
'the real plain Status line must take the update',
);
assert.ok(
!result.includes('Executing Phase 901` marker'),
'the rewrite must not bleed into the prose occurrence',
);
});
test('lookalike ordered BEFORE the real bold field: prose survives, bold field updates', () => {
const input = [
'## Accumulated Context',
'',
ISSUE_PROSE_LINE,
'',
'## Current Position',
'',
'**Status:** Ready to execute',
'',
].join('\n');
const result = stateReplaceField(input, 'Status', 'Executing Phase 901');
assert.notEqual(result, null);
assert.ok(result.includes(ISSUE_PROSE_LINE), `prose lookalike must survive, got:\n${result}`);
assert.ok(
/^\*\*Status:\*\* Executing Phase 901$/m.test(result),
'the real line-start bold field must take the update',
);
});
test('mid-word lookalike with no real field: returns null (honest absence), never a rewrite', () => {
const input = 'Prose mentions text**Status:**tail mid-word and nothing else.';
assert.equal(stateReplaceField(input, 'Status', 'Executing Phase 901'), null);
});
// A list-item bold label (`- **Status:** value`) is prose-shaped for the
// writer: no STATE.md writer emits body fields as list items, and treating
// a bullet as a field write target is exactly the #4243 corruption class.
// The read side's own vocabulary (bold anywhere) is untouched; the writer
// reports honest absence instead.
test('list-item bold label is not a write target: returns null, bullet untouched', () => {
const input = '- **Status:** resolved in the archived review';
assert.equal(stateReplaceField(input, 'Status', 'new'), null);
});
// Every field the regex serves: a document whose ONLY occurrence of the
// label is a mid-sentence lookalike must yield null — no served field may
// be rewritten from prose.
test('mid-sentence lookalike yields null for every served field', () => {
const servedFields = [
'Status', 'Phase', 'Plan', 'Current Plan', 'Current Phase', 'Current Phase Name',
'Last Activity', 'Last Activity Description', 'Total Phases', 'Total Plans in Phase',
'Progress', 'Completed Phases', 'Stopped At',
];
for (const field of servedFields) {
const input = `Some prose sentence quoting a **${field}:** label mid-sentence, plus trailing words.`;
assert.equal(
stateReplaceField(input, field, 'NEW'),
null,
`mid-sentence **${field}:** lookalike must not match (got a rewrite)`,
);
}
});
test('lookalike plus real plain field: only the real plain line changes (representative fields)', () => {
const cases = [
{ field: 'Status', plain: 'Status: Ready to execute' },
{ field: 'Phase', plain: 'Phase: 5 of 9' },
{ field: 'Last Activity', plain: 'Last Activity: 2026-08-01 — did a thing' },
];
for (const { field, plain } of cases) {
const lookalike = `- notes: the **${field}:** label was archived here. Keep it.`;
const input = [plain, '', '## Accumulated Context', '', lookalike, ''].join('\n');
const result = stateReplaceField(input, field, 'NEW VALUE');
assert.notEqual(result, null, `${field}: real plain field must match`);
assert.ok(
result.includes(lookalike),
`${field}: lookalike line must survive byte-identically, got:\n${result}`,
);
}
});
// Negative space: an INDENTED line-start bold field is still a field (the
// doc's form ranking reads bold anywhere in the section; the writer keeps
// same-line indentation writable), and the indent is preserved.
test('indented line-start bold field still updates, indent preserved', () => {
const input = ' **Status:** old';
const result = stateReplaceField(input, 'Status', 'new');
assert.equal(result, ' **Status:** new');
});
// Negative space + fix-shape pin: leading blank lines before the label are
// NOT swallowed. The anchor's leading class is same-line whitespace only
// (`[ \t]*`, #4010's idiom); the issue's suggested `^\s*` variant would
// consume the newlines into the match and drop them on rebuild.
test('leading blank lines before a bold label survive byte-identically', () => {
const input = '\n\n**Status:** Ready';
const result = stateReplaceField(input, 'Status', 'Executing Phase 5');
assert.equal(result, '\n\n**Status:** Executing Phase 5');
});
test('CRLF document: lookalike survives with CRLF intact, real plain field updates', () => {
const input = [
'Status: Ready to execute',
'',
'## Accumulated Context',
'',
ISSUE_PROSE_LINE,
'',
].join('\r\n');
const result = stateReplaceField(input, 'Status', 'Executing Phase 901');
assert.notEqual(result, null);
assert.ok(result.includes(ISSUE_PROSE_LINE), `prose lookalike must survive, got:\n${result}`);
assert.ok(result.includes('\r\n'), 'CRLF endings must be preserved');
assert.ok(/^Status: Executing Phase 901\r?$/m.test(result), 'real plain field must update');
});
// Negative space: branch ORDER is unchanged — a line-start bold field still
// beats the plain form, and only the first bold occurrence is replaced.
test('line-start bold still beats the plain form (branch order unchanged)', () => {
const input = '**Status:** old bold\nStatus: old plain';
const result = stateReplaceField(input, 'Status', 'new');
assert.equal(result, '**Status:** new\nStatus: old plain');
});
test('two line-start bold occurrences: only the first is replaced', () => {
const input = '**Status:** first\n**Status:** second';
const result = stateReplaceField(input, 'Status', 'new');
assert.equal(result, '**Status:** new\n**Status:** second');
});
// #4010 same-line adjacency under the anchor: an empty bold field's value
// lands on its own line and the following line survives.
test('anchored bold branch keeps the #4010 empty-field boundary', () => {
const input = ' **Status:**\n **Current Plan:** 2 of 5';
const result = stateReplaceField(input, 'Status', 'Executing Phase 5');
assert.equal(result, ' **Status:** Executing Phase 5\n **Current Plan:** 2 of 5');
});
});
describe('stateExtractField (#2880)', () => {
test('extracts from a two-cell row', () => {
const input = '| Current Phase | 3 |';