* chore(#2143): fail-loud Result + per-surface write-set contract — Phase 3 Phase 3 of epic #2143 (ADR-2143 §5/§6). The three target bugs (#2140, #2112, #2118) were already fixed tactically on next; this introduces the reusable structural contracts and rewires the primary #2140 site onto them. - src/write-set.cts (new): the parse `Result<T> = {ok,value|reason}` (§5) and the per-surface write-set (`WriteOutcome {surface, applied, requirement?}`, `WriteSet`, `writeSetComplete`) (§6). markdown-table.cts now imports + re-exports `Result` from here (single source; distinct from command-routing-hub's Result). - requirements mark-complete (src/milestone.cts): returns a PER-REQUIREMENT, per-surface write-set; `write_set_complete` is true only if every surface of every requirement applied — structurally forbidding the #2140 OR-into-one-flag masking, including across a multi-ID batch (adversarial-review regression). Pre-existing output fields unchanged (behaviour-preserving; #2140 already fixed). - deriveProgressFromRoadmap (src/phase-lifecycle.cts): removed the vestigial null-swallowing try/catch (findTableWithColumns never throws) — ADR §5 no-swallow; RoadmapProgress return contract unchanged. - commit --files (#2112) and milestone complete --dry-run (#2118) left as-is (single-surface commit / pre-mutation preview — not genuine multi-surface writes). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * chore(#2244): backfill changeset PR number (#2251) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -18,6 +18,8 @@ import { platformWriteSync, platformEnsureDir, execGit, retryRenameSync } from '
|
||||
import { formatGsdSlash, resolveRuntime } from './runtime-slash.cjs';
|
||||
import { realClock } from './clock.cjs';
|
||||
import { transitionCore } from './state-transition.cjs';
|
||||
import { writeSetComplete } from './write-set.cjs';
|
||||
import type { WriteSet } from './write-set.cjs';
|
||||
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
||||
import ioMod = require('./io.cjs');
|
||||
const { output, error } = ioMod;
|
||||
@@ -79,6 +81,18 @@ function cmdRequirementsMarkComplete(cwd: string, reqIdsRaw: string[], raw: bool
|
||||
// a missing row only counts as drift when a table actually exists.
|
||||
const hasTable = /^\|\s*Requirement\s*\|/im.test(reqContent);
|
||||
|
||||
// ADR-2143 §6 per-surface write-set, tracked PER requirement ID: a
|
||||
// multi-ID batch must not OR one ID's surface outcome into another's —
|
||||
// that is the exact #2140 class one level up (an ID whose traceability
|
||||
// row is absent/unmatched must not have its partial write masked by a
|
||||
// different ID in the same invocation that fully reconciled). Reported
|
||||
// additively as `write_set` below — it does not change the existing
|
||||
// marked_complete/already_complete/not_found/table_unmatched/updated
|
||||
// computation, which stays byte-for-behaviour identical (#2140's tactical
|
||||
// fix already surfaces the checkbox-only-partial-write case via
|
||||
// table_unmatched; this only adds the structured ADR-2143 shape on top).
|
||||
const writeSet: WriteSet = [];
|
||||
|
||||
for (const reqId of reqIds) {
|
||||
const reqEscaped = escapeRegex(reqId);
|
||||
|
||||
@@ -96,6 +110,16 @@ function cmdRequirementsMarkComplete(cwd: string, reqIdsRaw: string[], raw: bool
|
||||
const tableHit = afterTable !== reqContent;
|
||||
if (tableHit) reqContent = afterTable;
|
||||
|
||||
// ADR-2143 §6 per-ID write-set entries: this ID's checkbox surface is
|
||||
// always tracked; the traceability surface is tracked only when the file
|
||||
// has a traceability table at all (same `hasTable` gate the existing
|
||||
// required-surface logic below uses) — omitted entirely, not a false
|
||||
// `applied:false`, when no table is required of this file.
|
||||
writeSet.push({ requirement: reqId, surface: 'checkbox', applied: checkboxHit });
|
||||
if (hasTable) {
|
||||
writeSet.push({ requirement: reqId, surface: 'traceability', applied: tableHit });
|
||||
}
|
||||
|
||||
// Coverage of the traceability surface for this ID (computed after any flip).
|
||||
// hasRow keys on the ID + a second cell (`| ID | <phase> |`) so a bare mention
|
||||
// of the ID in a non-traceability table does not masquerade as a real row.
|
||||
@@ -129,6 +153,17 @@ function cmdRequirementsMarkComplete(cwd: string, reqIdsRaw: string[], raw: bool
|
||||
platformWriteSync(reqPath, reqContent);
|
||||
}
|
||||
|
||||
// ADR-2143 §6: `writeSet` above already carries one WriteOutcome per
|
||||
// (requirement, surface) this invocation could have written to — per ID,
|
||||
// not ORed across the batch. `write_set` and `write_set_complete` are
|
||||
// additive: they do not replace or gate `updated` / `marked_complete` /
|
||||
// `already_complete` / `not_found` / `table_unmatched`, which remain
|
||||
// computed exactly as before (see #2140 note above — that fix already
|
||||
// surfaces a checkbox-only partial write via `table_unmatched`;
|
||||
// `write_set_complete` is a structured, ADR-2143-shaped read of the SAME
|
||||
// per-surface, per-ID facts, `false` if ANY id's ANY required surface did
|
||||
// not apply, since `writeSetComplete` requires EVERY entry to have
|
||||
// applied, never an OR across surfaces OR across IDs).
|
||||
output(
|
||||
{
|
||||
updated: updated.length > 0,
|
||||
@@ -137,6 +172,8 @@ function cmdRequirementsMarkComplete(cwd: string, reqIdsRaw: string[], raw: bool
|
||||
not_found: notFound,
|
||||
table_unmatched: tableUnmatched,
|
||||
total: reqIds.length,
|
||||
write_set: writeSet,
|
||||
write_set_complete: writeSetComplete(writeSet),
|
||||
},
|
||||
raw,
|
||||
`${updated.length}/${reqIds.length} requirements marked complete`,
|
||||
|
||||
Reference in New Issue
Block a user