@codedology/code-forge 0.15.3 → 0.15.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -12,15 +12,18 @@ import { assertRowPath, DELETED_HASH, gitChildEnv, hashBytes } from './ticket.mj
12
12
 
13
13
  const SHA = /^[0-9a-f]{40,64}$/;
14
14
  const BLOCK = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
15
+ const QUIET_MS = 15000;
16
+ const MAX_BURST_MS = 120000;
15
17
 
16
18
  /**
17
19
  * One scheduler tick, also exposed directly for deterministic race tests.
18
20
  * The boot config gates ALL watch work; absent/wait/overlap never reads the run or spawns git.
19
21
  * @param {{repoRoot: string, runId: string, cfg: Record<string, any>, readRun?: typeof readRun,
20
22
  * writeRow: (row: Record<string, any>) => Promise<unknown>, exec?: typeof exec,
21
- * running?: ReadonlySet<string>, hooks?: {afterHead?: (head: string) => Promise<unknown>, beforeQueue?: () => Promise<unknown>}}} opts
23
+ * running?: ReadonlySet<string>, now?: () => number,
24
+ * hooks?: {afterHead?: (head: string) => Promise<unknown>, beforeQueue?: () => Promise<unknown>}}} opts
22
25
  */
23
- export async function watchTick({ repoRoot, runId, cfg, readRun: loadRun = readRun, writeRow, exec: execute = exec, running = new Set(), hooks = {} }) {
26
+ export async function watchTick({ repoRoot, runId, cfg, readRun: loadRun = readRun, writeRow, exec: execute = exec, running = new Set(), now = Date.now, hooks = {} }) {
24
27
  if (reviewFileLoop(cfg) !== 'watch') return;
25
28
  const record = await loadRun(runId);
26
29
  if (record.status !== 'active' || reviewFileLoop(snapshotFor(record, '')?.config ?? cfg) !== 'watch') return;
@@ -68,15 +71,22 @@ export async function watchTick({ repoRoot, runId, cfg, readRun: loadRun = readR
68
71
  await skip('not_descendant'); continue;
69
72
  }
70
73
  const last = typeof state.sha === 'string' && SHA.test(state.sha) ? state.sha : base;
71
- if (last !== base && (await git(['merge-base', '--is-ancestor', last, head])).result !== 'ok') {
74
+ const previous = typeof state.pending?.sha === 'string' && SHA.test(state.pending.sha) ? state.pending.sha : last;
75
+ if (previous !== base && (await git(['merge-base', '--is-ancestor', previous, head])).result !== 'ok') {
72
76
  await skip('rewound');
73
- const { sha: _sha, ...restart } = state;
77
+ const { sha: _sha, pending: _pending, burst_started_at: _burst, ...restart } = state;
74
78
  save(restart);
75
79
  continue;
76
80
  }
77
81
  // A healthy tick ends the skip streak: no new commit here, or a queued commit below. An
78
82
  // unreadable commit keeps the streak, so it writes one watch.skipped row, not one per tick.
79
83
  if (last === head) { if (state.skipped?.length) save({ ...state, skipped: [] }); continue; }
84
+ const time = now();
85
+ if (state.pending?.sha !== head) {
86
+ // A new HEAD restarts the quiet wait, but keeps the burst's original deadline.
87
+ save({ ...state, pending: { sha: head, seen_at: time }, burst_started_at: state.burst_started_at ?? time });
88
+ }
89
+ if (time - state.pending.seen_at < QUIET_MS && time - state.burst_started_at < MAX_BURST_MS) continue;
80
90
  const diff = await git(['diff', '--name-only', '-z', '--no-renames', last, head, '--']);
81
91
  if (diff.result !== 'ok') { await skip('unreadable_head'); continue; }
82
92
  const owns = ownsAt(tree ? '' : path.relative(repoRoot, record.workspace).split(path.sep).join('/'));
@@ -106,7 +116,8 @@ export async function watchTick({ repoRoot, runId, cfg, readRun: loadRun = readR
106
116
  }
107
117
  if (held) await writeRow({ event: 'watch.held', block, file, content_hash });
108
118
  }
109
- save({ ...state, run: runId, base, tree, sha: head, skipped: [] });
119
+ const { pending: _pending, burst_started_at: _burst, ...queued } = state;
120
+ save({ ...queued, run: runId, base, tree, sha: head, skipped: [] });
110
121
  } catch {
111
122
  // A bad path, missing tree or failed git read is a skip, never a block failure.
112
123
  await skip('unreadable_head');