* fix(#4488): report state update as successful when the value is already correct `cmdStateUpdate` unconditionally overwrote `updateCore`'s own `updated:true` signal with `reconcileReportedFields`'s disk-diff result. That diff reports `[]` -- by design -- whenever `readModifyWriteStateMd`'s #948 no-op guard fires because the transform's output was byte-identical to the input, which happens precisely when the requested value already equals what's on disk. The field genuinely was found and matched; there was simply nothing left to change. Collapsing that into the same `false`/"not found" response as a genuine miss produced an actively wrong diagnostic message and a silent same-day no-op in gsd-ship + gsd-extract-learnings, which both write `Last Activity` to today's date. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * docs(#4488): backfill changeset pr number to 4581 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * fix(#4488): untrack tdd-red-evidence.cjs, completing its ADR-457 gitignore migration Bundled discovery from this PR's own CI run: tests/lint-compiled-artifact- sync.test.cjs's full tsc compile (which runs whenever ANY compiled artifact remains tracked) SIGTERM'd under shard contention. gsd-core/bin/lib/tdd-red- evidence.cjs (introduced by #3770/PR #4279) was the sole remaining tracked artifact -- a tenth, later, separate instance of the #2657/#2653 migration-gap defect class this test file's closed nine-item list doesn't cover. Untracked it and added the .gitignore entry, same fix shape as the original nine. This eliminates the slow tsc-compile path entirely (verified: 0.1s vs ~7s locally) rather than papering over a timeout. Added a generic regression test asserting the tracked set is fully empty. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> --------- Co-authored-by: sim <sim@local> Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -808,7 +808,31 @@ function cmdStateUpdate(cwd: string, field: string | undefined, value: string |
|
||||
// (#3345's direction) — reported separately from `updated` because this
|
||||
// command's contract is a single-field boolean, not a per-field array.
|
||||
const reconciled = reconcileReportedFields(statePath, preWriteState, updated ? [field as string] : [], divergedFields);
|
||||
updated = reconciled.includes(field as string);
|
||||
// #4488: `updateCore` itself already told us whether it matched the field
|
||||
// (`updated`, captured above `readModifyWriteStateMd` runs it) — that is a
|
||||
// real signal, not a guess. `reconcileReportedFields` answers a DIFFERENT
|
||||
// question ("what changed on disk") and, per its own docstring, reports
|
||||
// `[]` whenever `preWriteState.fm` is `undefined`. That happens in two
|
||||
// known cases, both of which mean "no snapshot was ever captured", not
|
||||
// "nothing happened": (a) `readModifyWriteStateMd`'s #948 no-op guard
|
||||
// fires because the transform's output was byte-identical to the input —
|
||||
// the requested value already equals what's on disk, so the field WAS
|
||||
// found and there was simply nothing left to change; (b)
|
||||
// `applyPostSyncPreservation`'s `isUnparseableFrontmatter` early return —
|
||||
// the ORIGINAL frontmatter block was malformed, so preservation never
|
||||
// runs, yet `readModifyWriteStateMd` still persists the transform's raw
|
||||
// output via `platformWriteSync`. In neither case did preservation
|
||||
// discard or rewrite what the transform wrote, so trusting the
|
||||
// transform's own `updated` signal here is never a false positive.
|
||||
// Collapsing either case into the same `false` as "field not found" is
|
||||
// the #4488 bug — `explainUpdateFailure` then reports a message that is
|
||||
// actively false (it tells the caller to add a line that is already
|
||||
// there). Every other `false` origin (case-D fallback did not apply, or
|
||||
// the transform genuinely found nothing) is unaffected: there
|
||||
// `preWriteState.fm` is defined (a normal sync ran) or `updated` was
|
||||
// already false before this line.
|
||||
const noopBecauseAlreadyCorrect = updated && preWriteState.fm === undefined;
|
||||
updated = reconciled.includes(field as string) || noopBecauseAlreadyCorrect;
|
||||
const preserved = reconciled.filter((f) => f !== field);
|
||||
|
||||
if (updated) {
|
||||
|
||||
Reference in New Issue
Block a user