fix(#2640): report truthful state_updated + keep progress frontmatter in sync after phase remove (#2974)
* test(#2640): add regression for state_updated false positive + stale progress Three cases: (1) state_updated reflects actual content change, not just file existence; (2) progress.total_phases resync'd even when the body lacks 'Total Phases:' (the no-op guard was skipping syncStateFrontmatter); (3) state_updated is false when STATE.md doesn't exist. * fix(#2640): report truthful state_updated + force frontmatter resync Two defects in cmdPhaseRemove: 1. state_updated was fs.existsSync(statePath) — trivially true, since the file existed before and readModifyWriteStateMd never deletes it. Now captures the boolean return from readModifyWriteStateMd (changed from void to boolean: true when content was written, false on no-op). 2. progress.* frontmatter stayed stale when the body lacked 'Total Phases:' or 'of N' — readModifyWriteStateMd's no-op guard (#948) skipped syncStateFrontmatter when the body transform was unchanged. Now the transform forces a body diff when a phase was actually removed, so the guard passes and syncStateFrontmatter rebuilds progress.* from the post-deletion disk/ROADMAP state. * fix(#2640): address review — gate forced-diff on targetDir, strengthen assertions Two MAJOR findings from isolated adversarial review: 1. Forced-diff injected a spurious 'Total Phases:' line even when no directory was removed (targetDir === null). Now gated on targetDir !== null. 2. Test #2 asserted 'not 3' instead of '2' — would pass for any wrong count. Now asserts exact value. Test #1 strengthened to assert body Total Phases and frontmatter total_phases both equal 1. * chore(#2640): add changeset fragment * chore(#2640): backfill changeset PR number 2974 --------- Co-authored-by: sim <sim@local>
This commit is contained in:
@@ -2213,7 +2213,7 @@ function writeStateMd(statePath: string, content: string, cwd?: string, clock?:
|
||||
* @param clock
|
||||
* Optional clock seam; defaults to realClock. Passed through to acquireStateLock.
|
||||
*/
|
||||
function readModifyWriteStateMd(statePath: string, transformFn: (content: string) => string, cwd: string, options?: ReadModifyWriteOptions, clock?: StateLockClock): void {
|
||||
function readModifyWriteStateMd(statePath: string, transformFn: (content: string) => string, cwd: string, options?: ReadModifyWriteOptions, clock?: StateLockClock): boolean {
|
||||
const resync = !options || options.resync !== false;
|
||||
const lockPath = acquireStateLock(statePath, clock);
|
||||
try {
|
||||
@@ -2261,7 +2261,7 @@ function readModifyWriteStateMd(statePath: string, transformFn: (content: string
|
||||
// content already returns the mutated string, and callers that detect a
|
||||
// no-op explicitly return the original content unchanged.
|
||||
if (modified === content) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
let synced = syncStateFrontmatter(modified, cwd, options?.authoritativeFm);
|
||||
@@ -2317,6 +2317,7 @@ function readModifyWriteStateMd(statePath: string, transformFn: (content: string
|
||||
}
|
||||
|
||||
platformWriteSync(statePath, synced);
|
||||
return true;
|
||||
} finally {
|
||||
releaseStateLock(lockPath);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user