|
5 | 5 | // composite. Underpins /run-action, self-repair, and auto-emission — |
6 | 6 | // they all read/write through this single chokepoint so schema |
7 | 7 | // invariants stay enforced. |
8 | | -import { existsSync, readFileSync } from 'node:fs'; |
| 8 | +import { existsSync, readFileSync, statSync } from 'node:fs'; |
9 | 9 | import { join } from 'node:path'; |
10 | 10 | import { parseM7Header, serializeM7Header, } from './reusable-action.js'; |
11 | | -import { loadOrInitSidecar, sidecarPathFor, yamlEditedSinceLastSeen, } from './sidecar-io.js'; |
| 11 | +import { loadOrInitSidecar, markSeen, saveSidecar, sidecarPathFor, yamlEditedSinceLastSeen, } from './sidecar-io.js'; |
12 | 12 | import { atomicWriter } from './atomic-writer.js'; |
13 | 13 | import { assertValidActionId, assertWithinDir } from './path-safety.js'; |
14 | 14 | /** |
@@ -229,6 +229,37 @@ export function saveAction(action) { |
229 | 229 | export function actionWasEditedExternally(action) { |
230 | 230 | return yamlEditedSinceLastSeen(action.filePath, action.state); |
231 | 231 | } |
| 232 | +/** |
| 233 | + * GH #173 (sub-issue 3): treat the YAML's current on-disk mtime as the |
| 234 | + * new baseline. Stats the YAML, persists `markSeen(state, currentMtime)` |
| 235 | + * to the sidecar, and returns a new ReusableAction with the refreshed |
| 236 | + * lastSeenMtimeMs. Subsequent `actionWasEditedExternally()` checks |
| 237 | + * return false until something edits the YAML again. |
| 238 | + * |
| 239 | + * Use case: `cdp_run_action` is called while the human is actively |
| 240 | + * composing the YAML. The human's edit IS the intent; the Phase 129 |
| 241 | + * guardrail (which exists to protect offline human edits from |
| 242 | + * auto-repair clobber) is over-protective in this loop. The orchestrator |
| 243 | + * acknowledges the edit before running so any downstream repair |
| 244 | + * proceeds without `STALE_TARGET`. |
| 245 | + * |
| 246 | + * No-op when the YAML mtime equals the sidecar's lastSeenMtimeMs (the |
| 247 | + * common case where no external write happened). |
| 248 | + */ |
| 249 | +export function acknowledgeExternalEdit(action) { |
| 250 | + let currentMtimeMs; |
| 251 | + try { |
| 252 | + currentMtimeMs = statSync(action.filePath).mtimeMs; |
| 253 | + } |
| 254 | + catch { |
| 255 | + return action; |
| 256 | + } |
| 257 | + if (currentMtimeMs <= action.state.lastSeenMtimeMs) |
| 258 | + return action; |
| 259 | + const nextState = markSeen(action.state, currentMtimeMs); |
| 260 | + saveSidecar(action.filePath, nextState); |
| 261 | + return { ...action, state: nextState }; |
| 262 | +} |
232 | 263 | /** |
233 | 264 | * Issue #117: CAS variant of `saveAction`. Compares the on-disk |
234 | 265 | * sidecar's `lastSeenMtimeMs` to the in-memory `action.state. |
|
0 commit comments