fix(#3255): updateTableCell scans for the table carrying the requested column (#3377)

* test(#3255): updateTableCell reaches a later table when the first lacks the column

A ## Traceability section holding a summary table (no Status) above the
requirement table (with Status) made updateTableCell bind to the first table
and return 'unknown column: Status', so requirements.mark-complete left the
row at Pending. Add a failing-first regression at the primitive.

RED — fails on next; fix follows.

* fix(#3255): updateTableCell scans for the table carrying the requested column

updateTableCell bound to the FIRST GFM table in the scoped text and returned
'unknown column' if that table lacked the column — so a ## Traceability section
holding a phase-summary table above the requirement table never reached the
requirement table, and requirements.mark-complete left the row at Pending while
reporting table_unmatched (the #2140 silent-divergence class one level deeper,
flagged but not closed by the #2245 cross-section scoping fix).

Scan candidate table headers and pick the first VALID table (delimiter +
matching column count) whose columns include the requested column. Single-table
behaviour is byte-identical (the one table carries the column). Error semantics
preserved: no table -> 'no table found'; valid table without the column ->
'unknown column'; lone malformed table keeps its specific reason. Also fixes the
derived hasRow/doneTable reasoning, which probes via the same call.

* chore(#3255): add changeset fragment

* chore(#3255): backfill changeset PR number (#3377)

---------

Co-authored-by: sim <sim@local>
This commit is contained in:
Tom Boucher
2026-08-11 23:52:46 -04:00
committed by GitHub
parent ad07f76a31
commit 864f76b46f
3 changed files with 64 additions and 22 deletions

View File

@@ -639,6 +639,29 @@ describe('updateTableCell', () => {
assert.match(result.reason, /no table found/);
});
test('#3255: reaches a later table when the first table lacks the requested column', () => {
// A ## Traceability section holding a phase-summary table (no Status) ABOVE
// the requirement table (with Status). The prior code bound to the FIRST
// table and returned 'unknown column: Status' without ever reaching the
// requirement table, so requirements.mark-complete left the row at Pending.
const multiTable = [
'| Phase | Name | Requirements |',
'|-------|------|--------------|',
'| 1 | Portal Spec | 4 (PORTAL-01..04) |',
'',
'| Requirement | Phase | Status |',
'|-------------|-------|--------|',
'| AUTHZ-02 | Phase 02.2 | Pending |',
].join('\n');
const byReq = (row) => (Object.values(row)[0] ?? '').trim().toLowerCase() === 'authz-02';
const result = updateTableCell(multiTable, byReq, 'Status', ' Complete ');
assert.equal(result.ok, true, `expected ok:true (should reach the requirement table); got: ${result && result.reason}`);
assert.ok(result.value.includes('| Phase | Name | Requirements |'), 'first (summary) table header untouched');
assert.ok(result.value.includes('| 1 | Portal Spec | 4 (PORTAL-01..04) |'), 'first (summary) data row untouched');
assert.ok(/AUTHZ-02 \| Phase 02\.2 \| Complete/.test(result.value), 'second table Status cell flipped Pending → Complete');
});
test('5-column milestone table: Milestone cell and other columns stay byte-identical', () => {
const byPhase = (row) => row['Phase'].trim() === '1. Alpha';
const result = updateTableCell(fiveCol, byPhase, 'Plans Complete', ' 2/2 ');