@webpieces/repo-workflow-core 0.4.958 → 0.4.959

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.
@@ -1,27 +1,17 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.MainSyncStatusService = exports.MainSyncLock = exports.DEFAULT_HANG_TIMEOUT_MINUTES = exports.MAIN_SYNC_STATUS_VERSION = exports.PullRequestIndex = exports.MainSyncStatusFile = exports.MainSyncStatus = void 0;
3
+ exports.MainSyncStatusService = exports.DEFAULT_HANG_TIMEOUT_MINUTES = exports.RefreshOutcome = exports.MAIN_SYNC_STATUS_VERSION = exports.PullRequestIndex = exports.MainSyncStatusFile = exports.MainSyncStatus = void 0;
4
4
  exports.mainSyncStatusPath = mainSyncStatusPath;
5
- exports.mainSyncLockPath = mainSyncLockPath;
6
5
  exports.readMainSyncStatus = readMainSyncStatus;
7
6
  exports.readMainSyncStatusFile = readMainSyncStatusFile;
8
7
  exports.writeMainSyncStatus = writeMainSyncStatus;
9
8
  exports.writeMainSyncStatusFile = writeMainSyncStatusFile;
10
- exports.readMainSyncLock = readMainSyncLock;
11
- exports.writeMainSyncLock = writeMainSyncLock;
12
- exports.isLockStale = isLockStale;
13
- exports.isRefreshInProgress = isRefreshInProgress;
14
- exports.tryAcquireMainSyncLock = tryAcquireMainSyncLock;
15
- exports.inProcessLock = inProcessLock;
16
- exports.finishedLock = finishedLock;
17
9
  exports.computeMainSyncStatus = computeMainSyncStatus;
18
10
  exports.computeAllMainSyncStatuses = computeAllMainSyncStatuses;
19
11
  exports.squashRecoverySteps = squashRecoverySteps;
20
12
  exports.stampCleanMainSyncStatus = stampCleanMainSyncStatus;
21
13
  const tslib_1 = require("tslib");
22
14
  const child_process_1 = require("child_process");
23
- const fs = tslib_1.__importStar(require("fs"));
24
- const path = tslib_1.__importStar(require("path"));
25
15
  const inversify_1 = require("inversify");
26
16
  const tooling_common_1 = require("@webpieces/tooling-common");
27
17
  const main_sync_file_1 = require("./main-sync-file");
@@ -29,34 +19,21 @@ Object.defineProperty(exports, "MAIN_SYNC_STATUS_VERSION", { enumerable: true, g
29
19
  Object.defineProperty(exports, "MainSyncStatus", { enumerable: true, get: function () { return main_sync_file_1.MainSyncStatus; } });
30
20
  Object.defineProperty(exports, "MainSyncStatusFile", { enumerable: true, get: function () { return main_sync_file_1.MainSyncStatusFile; } });
31
21
  Object.defineProperty(exports, "PullRequestIndex", { enumerable: true, get: function () { return main_sync_file_1.PullRequestIndex; } });
22
+ Object.defineProperty(exports, "RefreshOutcome", { enumerable: true, get: function () { return main_sync_file_1.RefreshOutcome; } });
32
23
  const tooling_common_2 = require("@webpieces/tooling-common");
33
24
  const to_error_1 = require("@webpieces/tooling-common/to-error");
34
25
  const worktrees_1 = require("./worktrees");
26
+ const command_timeouts_1 = require("./command-timeouts");
35
27
  // Shared "is my feature branch healthy relative to origin/main?" state. The SLOW signals (git fetch +
36
28
  // merge-base + same-file-overlap + a merged-PR lookup) are computed by the ai-hook-rules refresher in a
37
29
  // DETACHED background process; the feature-branch-guard only READS this cached file. pr-gate's merge
38
30
  // flow also writes it synchronously after a merge. Lives here (the shared dep of both).
39
- // How long an `inprocess` refresher lock may sit before a new refresher assumes the prior run hung.
31
+ // How long a refresher may hold main-sync.lock.json before the next spawner assumes it hung, kills it
32
+ // and reclaims the lock (see MainSyncLockService).
40
33
  exports.DEFAULT_HANG_TIMEOUT_MINUTES = 5;
41
34
  const MAIN_SYNC_STATUS_FILE = 'main-sync-status.json';
42
- const MAIN_SYNC_LOCK_FILE = 'main-sync.lock.json';
43
- const LOCK_STATE_INPROCESS = 'inprocess';
44
- const LOCK_STATE_FINISHED = 'finished';
45
- // Concurrency state machine for the detached refresher. `started` is epoch ms. `pid` is the refresher
46
- // process's pid (0 = unknown) — used so a KILLED refresher doesn't wedge `inprocess` for the timeout.
47
- class MainSyncLock {
48
- state;
49
- started;
50
- pid;
51
- constructor(state, started, pid = 0) {
52
- this.state = state;
53
- this.started = started;
54
- this.pid = pid;
55
- }
56
- }
57
- exports.MainSyncLock = MainSyncLock;
58
35
  /**
59
- * Reads/writes the main-sync cache + lock and computes the slow "is my branch healthy vs origin/main?"
36
+ * Reads/writes the main-sync cache and computes the slow "is my branch healthy vs origin/main?"
60
37
  * status. `@injectable(bindingScopeValues.Singleton)` so it's injectable and drawn in consumers’ DI designs.
61
38
  */
62
39
  let MainSyncStatusService = class MainSyncStatusService {
@@ -64,11 +41,13 @@ let MainSyncStatusService = class MainSyncStatusService {
64
41
  atomicFile;
65
42
  store;
66
43
  worktrees;
67
- constructor(dotDir = tooling_common_2.dotWebpieces, atomicFile = new tooling_common_1.AtomicFile(), store = new main_sync_file_1.MainSyncFileStore(atomicFile), worktrees = new worktrees_1.WorktreeService()) {
44
+ timeouts;
45
+ constructor(dotDir = tooling_common_2.dotWebpieces, atomicFile = new tooling_common_1.AtomicFile(), store = new main_sync_file_1.MainSyncFileStore(atomicFile), worktrees = new worktrees_1.WorktreeService(), timeouts = new command_timeouts_1.CommandTimeouts()) {
68
46
  this.dotDir = dotDir;
69
47
  this.atomicFile = atomicFile;
70
48
  this.store = store;
71
49
  this.worktrees = worktrees;
50
+ this.timeouts = timeouts;
72
51
  }
73
52
  /**
74
53
  * SHARED scope: `<primary>/.webpieces/main-sync-status.json`, one per repo.
@@ -88,23 +67,6 @@ let MainSyncStatusService = class MainSyncStatusService {
88
67
  mainSyncStatusPath(repoRoot) {
89
68
  return this.dotDir.sharedFile(repoRoot, MAIN_SYNC_STATUS_FILE);
90
69
  }
91
- /**
92
- * SHARED scope — the single-flight lock for the whole repo.
93
- *
94
- * This is the fix for the filed bug "the detached main-sync refresher's `git fetch` races the
95
- * agent's foreground `git fetch`/`git pull` and corrupts `.git/FETCH_HEAD`". The lock was already
96
- * atomic (O_CREAT|O_EXCL) and already serialised refreshers against each other — but only WITHIN
97
- * one worktree, so N worktrees meant N locks and up to N concurrent `git fetch`es against the ONE
98
- * shared `.git`, which is exactly how a duplicate `for-merge` line lands in FETCH_HEAD. There is
99
- * one `.git`, so there is now one lock: at most one refresher in flight across all worktrees.
100
- *
101
- * Stale-lock handling is unchanged and still required (a lock nobody can clear is worse than the
102
- * race): the holder records pid + start epoch, and `tryAcquireMainSyncLock` reclaims it once
103
- * `isRefreshInProgress` proves the holder is finished, hung past hangTimeoutMinutes, or dead.
104
- */
105
- mainSyncLockPath(repoRoot) {
106
- return this.dotDir.sharedFile(repoRoot, MAIN_SYNC_LOCK_FILE);
107
- }
108
70
  /**
109
71
  * The cached status FOR ONE BRANCH — the only read the four main-sync guards perform.
110
72
  *
@@ -141,99 +103,14 @@ let MainSyncStatusService = class MainSyncStatusService {
141
103
  writeMainSyncStatusFile(repoRoot, file) {
142
104
  this.store.writeFile(this.mainSyncStatusPath(repoRoot), file);
143
105
  }
144
- readMainSyncLock(repoRoot) {
145
- // eslint-disable-next-line @webpieces/no-unmanaged-exceptions
146
- try {
147
- const lockPath = this.mainSyncLockPath(repoRoot);
148
- if (!fs.existsSync(lockPath))
149
- return null;
150
- const raw = JSON.parse(fs.readFileSync(lockPath, 'utf8'));
151
- return new MainSyncLock(raw.state ?? LOCK_STATE_FINISHED, raw.started ?? 0, raw.pid ?? 0);
152
- }
153
- catch (err) {
154
- const error = (0, to_error_1.toError)(err);
155
- void error;
156
- return null;
157
- }
158
- }
159
- writeMainSyncLock(repoRoot, lock) {
160
- const lockPath = this.mainSyncLockPath(repoRoot);
161
- this.ensureDir(lockPath);
162
- fs.writeFileSync(lockPath, JSON.stringify(lock, null, 2) + '\n');
163
- }
164
- // A lock is stale (prior refresher assumed hung) once `inprocess` longer than hangTimeoutMinutes.
165
- isLockStale(lock, hangTimeoutMinutes, now = Date.now()) {
166
- return now - lock.started > hangTimeoutMinutes * 60 * 1000;
167
- }
168
- // True when another refresher is actively running and we should NOT start a second one.
169
- isRefreshInProgress(repoRoot, hangTimeoutMinutes, now = Date.now()) {
170
- const lock = this.readMainSyncLock(repoRoot);
171
- if (!lock)
172
- return false;
173
- if (lock.state !== LOCK_STATE_INPROCESS)
174
- return false;
175
- if (this.isLockStale(lock, hangTimeoutMinutes, now))
176
- return false;
177
- return this.isProcessAlive(lock.pid);
178
- }
179
106
  /**
180
- * ATOMICALLY take the refresher lock. Returns the held lock, or null when someone else holds it.
181
- *
182
- * Replaces the check-then-write pair (`isRefreshInProgress` then `writeMainSyncLock`), whose gap
183
- * let two detached refreshers both pass the check and then both run `git fetch` at once — the
184
- * concurrency this lock exists to prevent. The create uses the `wx` flag (O_CREAT|O_EXCL), so of
185
- * N racing refreshers exactly one creates the file.
186
- *
187
- * A lock file left behind by a FINISHED, hung, or killed refresher is reclaimed: we only unlink
188
- * and re-take it once `isRefreshInProgress` says the holder is provably not running, and we
189
- * re-read afterwards to confirm the entry we see is ours before claiming the lock.
107
+ * Record that a refresh STARTED at `startedMs` and ended with `outcome`, keeping every branch entry
108
+ * already on disk. The refresher's failure path: a run that produced no new branch data still
109
+ * counts as a run, so the throttle retries it after MIN_REFRESH_INTERVAL_MINUTES rather than on
110
+ * every tool call. A successful run stamps the whole document instead (writeMainSyncStatusFile).
190
111
  */
191
- tryAcquireMainSyncLock(repoRoot, hangTimeoutMinutes, now = Date.now(), pid = process.pid) {
192
- const lockPath = this.mainSyncLockPath(repoRoot);
193
- this.ensureDir(lockPath);
194
- const lock = this.inProcessLock(now, pid);
195
- const payload = JSON.stringify(lock, null, 2) + '\n';
196
- if (this.createExclusive(lockPath, payload))
197
- return lock;
198
- // The file already exists. Leave it alone while a live refresher owns it.
199
- if (this.isRefreshInProgress(repoRoot, hangTimeoutMinutes, now))
200
- return null;
201
- // Reclaim it: remove the dead entry, then re-take it exclusively so only one reclaimer wins.
202
- // eslint-disable-next-line @webpieces/no-unmanaged-exceptions
203
- try {
204
- fs.unlinkSync(lockPath);
205
- }
206
- catch (err) {
207
- const error = (0, to_error_1.toError)(err);
208
- void error; // someone else reclaimed it first — the exclusive create below decides
209
- }
210
- if (!this.createExclusive(lockPath, payload))
211
- return null;
212
- // Confirm the lock on disk is OURS (a simultaneous reclaimer could have unlinked ours and
213
- // written its own between the two calls above).
214
- const held = this.readMainSyncLock(repoRoot);
215
- if (!held || held.pid !== pid || held.started !== lock.started)
216
- return null;
217
- return lock;
218
- }
219
- // O_CREAT|O_EXCL write: true when THIS call created the file, false when it already existed.
220
- createExclusive(lockPath, payload) {
221
- // eslint-disable-next-line @webpieces/no-unmanaged-exceptions
222
- try {
223
- fs.writeFileSync(lockPath, payload, { flag: 'wx' });
224
- return true;
225
- }
226
- catch (err) {
227
- const error = (0, to_error_1.toError)(err);
228
- void error;
229
- return false;
230
- }
231
- }
232
- inProcessLock(now = Date.now(), pid = process.pid) {
233
- return new MainSyncLock(LOCK_STATE_INPROCESS, now, pid);
234
- }
235
- finishedLock(started) {
236
- return new MainSyncLock(LOCK_STATE_FINISHED, started, 0);
112
+ recordRefreshOutcome(repoRoot, startedMs, outcome) {
113
+ this.store.recordRefresh(this.mainSyncStatusPath(repoRoot), startedMs, outcome);
237
114
  }
238
115
  /**
239
116
  * The SLOW path for ONE branch: the branch checked out in `repoRoot`. Runs its own fetch and its
@@ -387,39 +264,22 @@ let MainSyncStatusService = class MainSyncStatusService {
387
264
  */
388
265
  fetchOriginMain(repoRoot) {
389
266
  const safe = (0, child_process_1.spawnSync)('git', ['fetch', '--no-write-fetch-head', 'origin', 'main'], {
390
- cwd: repoRoot, encoding: 'utf8', stdio: ['ignore', 'ignore', 'pipe'],
267
+ cwd: repoRoot, encoding: 'utf8', stdio: ['ignore', 'ignore', 'pipe'], timeout: command_timeouts_1.CommandTimeouts.NETWORK_MS,
391
268
  });
392
269
  if (safe.status === 0)
393
270
  return;
394
271
  if (!this.isUnknownGitOption(safe.stderr))
395
272
  return; // a real failure (offline, auth) — not our business
396
- (0, child_process_1.spawnSync)('git', ['fetch', 'origin', 'main'], { cwd: repoRoot, stdio: 'ignore' });
273
+ (0, child_process_1.spawnSync)('git', ['fetch', 'origin', 'main'], { cwd: repoRoot, stdio: 'ignore', timeout: command_timeouts_1.CommandTimeouts.NETWORK_MS });
397
274
  }
398
275
  // Did git reject the flag itself (too old), as opposed to failing the network refresh?
399
276
  isUnknownGitOption(stderr) {
400
277
  const text = (stderr ?? '').toLowerCase();
401
278
  return text.includes('unknown option') || text.includes('unknown switch') || text.includes('unrecognized option');
402
279
  }
403
- ensureDir(filePath) {
404
- fs.mkdirSync(path.dirname(filePath), { recursive: true });
405
- }
406
- // Liveness probe: is `pid` still a running process? pid <= 0 (unknown) → assume alive.
407
- isProcessAlive(pid) {
408
- if (pid <= 0)
409
- return true;
410
- // eslint-disable-next-line @webpieces/no-unmanaged-exceptions
411
- try {
412
- process.kill(pid, 0);
413
- return true;
414
- }
415
- catch (err) {
416
- const error = (0, to_error_1.toError)(err);
417
- return !error.message.includes('ESRCH');
418
- }
419
- }
420
280
  // Run a command capturing trimmed stdout; ok=false on spawn failure or non-zero exit.
421
281
  capture(repoRoot, cmd, args) {
422
- const result = (0, child_process_1.spawnSync)(cmd, args, { cwd: repoRoot, encoding: 'utf8' });
282
+ const result = (0, child_process_1.spawnSync)(cmd, args, { cwd: repoRoot, encoding: 'utf8', timeout: this.timeouts.forCommand(cmd) });
423
283
  if (result.status !== 0 || typeof result.stdout !== 'string')
424
284
  return { ok: false, out: '' };
425
285
  return { ok: true, out: result.stdout.trim() };
@@ -514,15 +374,14 @@ exports.MainSyncStatusService = MainSyncStatusService = tslib_1.__decorate([
514
374
  tslib_1.__metadata("design:paramtypes", [tooling_common_2.DotWebpieces,
515
375
  tooling_common_1.AtomicFile,
516
376
  main_sync_file_1.MainSyncFileStore,
517
- worktrees_1.WorktreeService])
377
+ worktrees_1.WorktreeService,
378
+ command_timeouts_1.CommandTimeouts])
518
379
  ], MainSyncStatusService);
519
380
  // Temporary migration delegators to MainSyncStatusService — removed once consumers inject it.
520
381
  const mainSyncSvc = new MainSyncStatusService();
521
382
  // webpieces-disable no-function-outside-class -- temporary back-compat delegator to MainSyncStatusService; removed once consumers inject it
522
383
  function mainSyncStatusPath(repoRoot) { return mainSyncSvc.mainSyncStatusPath(repoRoot); }
523
384
  // webpieces-disable no-function-outside-class -- temporary back-compat delegator to MainSyncStatusService; removed once consumers inject it
524
- function mainSyncLockPath(repoRoot) { return mainSyncSvc.mainSyncLockPath(repoRoot); }
525
- // webpieces-disable no-function-outside-class -- temporary back-compat delegator to MainSyncStatusService; removed once consumers inject it
526
385
  function readMainSyncStatus(repoRoot, branch) { return mainSyncSvc.readMainSyncStatus(repoRoot, branch); }
527
386
  // webpieces-disable no-function-outside-class -- temporary back-compat delegator to MainSyncStatusService; removed once consumers inject it
528
387
  function readMainSyncStatusFile(repoRoot) { return mainSyncSvc.readMainSyncStatusFile(repoRoot); }
@@ -531,20 +390,6 @@ function writeMainSyncStatus(repoRoot, status) { mainSyncSvc.writeMainSyncStatus
531
390
  // webpieces-disable no-function-outside-class -- temporary back-compat delegator to MainSyncStatusService; removed once consumers inject it
532
391
  function writeMainSyncStatusFile(repoRoot, file) { mainSyncSvc.writeMainSyncStatusFile(repoRoot, file); }
533
392
  // webpieces-disable no-function-outside-class -- temporary back-compat delegator to MainSyncStatusService; removed once consumers inject it
534
- function readMainSyncLock(repoRoot) { return mainSyncSvc.readMainSyncLock(repoRoot); }
535
- // webpieces-disable no-function-outside-class -- temporary back-compat delegator to MainSyncStatusService; removed once consumers inject it
536
- function writeMainSyncLock(repoRoot, lock) { mainSyncSvc.writeMainSyncLock(repoRoot, lock); }
537
- // webpieces-disable no-function-outside-class -- temporary back-compat delegator to MainSyncStatusService; removed once consumers inject it
538
- function isLockStale(lock, hangTimeoutMinutes, now = Date.now()) { return mainSyncSvc.isLockStale(lock, hangTimeoutMinutes, now); }
539
- // webpieces-disable no-function-outside-class -- temporary back-compat delegator to MainSyncStatusService; removed once consumers inject it
540
- function isRefreshInProgress(repoRoot, hangTimeoutMinutes, now = Date.now()) { return mainSyncSvc.isRefreshInProgress(repoRoot, hangTimeoutMinutes, now); }
541
- // webpieces-disable no-function-outside-class -- temporary back-compat delegator to MainSyncStatusService; removed once consumers inject it
542
- function tryAcquireMainSyncLock(repoRoot, hangTimeoutMinutes, now = Date.now(), pid = process.pid) { return mainSyncSvc.tryAcquireMainSyncLock(repoRoot, hangTimeoutMinutes, now, pid); }
543
- // webpieces-disable no-function-outside-class -- temporary back-compat delegator to MainSyncStatusService; removed once consumers inject it
544
- function inProcessLock(now = Date.now(), pid = process.pid) { return mainSyncSvc.inProcessLock(now, pid); }
545
- // webpieces-disable no-function-outside-class -- temporary back-compat delegator to MainSyncStatusService; removed once consumers inject it
546
- function finishedLock(started) { return mainSyncSvc.finishedLock(started); }
547
- // webpieces-disable no-function-outside-class -- temporary back-compat delegator to MainSyncStatusService; removed once consumers inject it
548
393
  function computeMainSyncStatus(repoRoot) { return mainSyncSvc.computeMainSyncStatus(repoRoot); }
549
394
  // webpieces-disable no-function-outside-class -- temporary back-compat delegator to MainSyncStatusService; removed once consumers inject it
550
395
  function computeAllMainSyncStatuses(repoRoot) { return mainSyncSvc.computeAllMainSyncStatuses(repoRoot); }
@@ -1 +1 @@
1
- {"version":3,"file":"main-sync-status.js","sourceRoot":"","sources":["../../../../../packages/tooling/repo-workflow-core/src/main-sync-status.ts"],"names":[],"mappings":"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","sourcesContent":["import { spawnSync } from 'child_process';\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport { injectable, bindingScopeValues } from 'inversify';\n\nimport { AtomicFile } from '@webpieces/tooling-common';\nimport {\n MAIN_SYNC_STATUS_VERSION,\n MainSyncFileStore,\n MainSyncStatus,\n MainSyncStatusFile,\n PullRequestIndex,\n} from './main-sync-file';\nimport { DotWebpieces, dotWebpieces } from '@webpieces/tooling-common';\nimport { toError } from '@webpieces/tooling-common/to-error';\nimport { WorktreeService } from './worktrees';\n\n// The on-disk shape lives in main-sync-file.ts; re-exported so every existing importer of\n// `MainSyncStatus` from this module keeps working.\nexport { MainSyncStatus, MainSyncStatusFile, PullRequestIndex, MAIN_SYNC_STATUS_VERSION };\n\n// Shared \"is my feature branch healthy relative to origin/main?\" state. The SLOW signals (git fetch +\n// merge-base + same-file-overlap + a merged-PR lookup) are computed by the ai-hook-rules refresher in a\n// DETACHED background process; the feature-branch-guard only READS this cached file. pr-gate's merge\n// flow also writes it synchronously after a merge. Lives here (the shared dep of both).\n\n// How long an `inprocess` refresher lock may sit before a new refresher assumes the prior run hung.\nexport const DEFAULT_HANG_TIMEOUT_MINUTES = 5;\n\nconst MAIN_SYNC_STATUS_FILE = 'main-sync-status.json';\nconst MAIN_SYNC_LOCK_FILE = 'main-sync.lock.json';\n\nconst LOCK_STATE_INPROCESS = 'inprocess';\nconst LOCK_STATE_FINISHED = 'finished';\n\n// Concurrency state machine for the detached refresher. `started` is epoch ms. `pid` is the refresher\n// process's pid (0 = unknown) — used so a KILLED refresher doesn't wedge `inprocess` for the timeout.\nexport class MainSyncLock {\n state: string;\n started: number;\n pid: number;\n\n constructor(state: string, started: number, pid: number = 0) {\n this.state = state;\n this.started = started;\n this.pid = pid;\n }\n}\n\n// Raw JSON shape for the cast at the parse boundary.\ninterface RawLock {\n state?: string;\n started?: number;\n pid?: number;\n}\n\n// Result of a captured git/gh invocation: ok=false on spawn failure or non-zero exit.\ninterface CmdCapture {\n ok: boolean;\n out: string;\n}\n\n/**\n * Reads/writes the main-sync cache + lock and computes the slow \"is my branch healthy vs origin/main?\"\n * status. `@injectable(bindingScopeValues.Singleton)` so it's injectable and drawn in consumers’ DI designs.\n */\n@injectable(bindingScopeValues.Singleton)\nexport class MainSyncStatusService {\n constructor(\n private readonly dotDir: DotWebpieces = dotWebpieces,\n private readonly atomicFile: AtomicFile = new AtomicFile(),\n private readonly store: MainSyncFileStore = new MainSyncFileStore(atomicFile),\n private readonly worktrees: WorktreeService = new WorktreeService(),\n ) {}\n\n /**\n * SHARED scope: `<primary>/.webpieces/main-sync-status.json`, one per repo.\n *\n * It is the OUTPUT of the single-flight refresher below, so it has to live where the lock lives —\n * one `.git`, one `origin/main`, one fetch, one answer. A per-worktree copy under single-flight\n * would only ever be written for whichever worktree won the lock anyway.\n *\n * FORMERLY a known defect, now fixed: the file's CONTENT used to be keyed to ONE branch, and every\n * reader fails OPEN when the cached branch is not the branch being judged (`stale-cross-branch-cache\n * (fail-open)`). With several worktrees on several branches, only the winner of the lock was ever\n * described, so every other worktree's guards abstained — and which one was armed thrashed as the\n * lock changed hands. The file is now a MAP of branch -> status (see MainSyncStatusFile): the single\n * winning refresher enumerates every worktree and records all of their branches in one atomic write,\n * so one fetch arms everyone. Single-flight is unchanged; only what the winner writes changed.\n */\n mainSyncStatusPath(repoRoot: string): string {\n return this.dotDir.sharedFile(repoRoot, MAIN_SYNC_STATUS_FILE);\n }\n\n /**\n * SHARED scope — the single-flight lock for the whole repo.\n *\n * This is the fix for the filed bug \"the detached main-sync refresher's `git fetch` races the\n * agent's foreground `git fetch`/`git pull` and corrupts `.git/FETCH_HEAD`\". The lock was already\n * atomic (O_CREAT|O_EXCL) and already serialised refreshers against each other — but only WITHIN\n * one worktree, so N worktrees meant N locks and up to N concurrent `git fetch`es against the ONE\n * shared `.git`, which is exactly how a duplicate `for-merge` line lands in FETCH_HEAD. There is\n * one `.git`, so there is now one lock: at most one refresher in flight across all worktrees.\n *\n * Stale-lock handling is unchanged and still required (a lock nobody can clear is worse than the\n * race): the holder records pid + start epoch, and `tryAcquireMainSyncLock` reclaims it once\n * `isRefreshInProgress` proves the holder is finished, hung past hangTimeoutMinutes, or dead.\n */\n mainSyncLockPath(repoRoot: string): string {\n return this.dotDir.sharedFile(repoRoot, MAIN_SYNC_LOCK_FILE);\n }\n\n /**\n * The cached status FOR ONE BRANCH — the only read the four main-sync guards perform.\n *\n * `branch` is mandatory and is the branch being judged. A branch the last refresh never saw\n * returns null, which every guard already treats as `no-sync-cache (fail-open)`, so an unknown\n * branch degrades exactly as a missing file did. Any error (missing, truncated, malformed JSON)\n * also returns null — this must never throw on the blocking path.\n */\n readMainSyncStatus(repoRoot: string, branch: string): MainSyncStatus | null {\n return this.store.branchStatus(this.readMainSyncStatusFile(repoRoot), branch);\n }\n\n // The whole branch-keyed document, or null. Callers that need more than one branch use this.\n readMainSyncStatusFile(repoRoot: string): MainSyncStatusFile | null {\n return this.store.readFile(this.mainSyncStatusPath(repoRoot));\n }\n\n /**\n * Merge ONE branch's entry into the existing map, atomically, leaving siblings untouched.\n *\n * Read-modify-write rather than replace: this is the writer used by everyone who is NOT the\n * single-flight refresher (notably pr-gate's post-merge stamp, which holds no lock and knows about\n * exactly one branch). Replacing the document from there would erase every other worktree's entry\n * and silently disarm their guards until the next refresh.\n */\n writeMainSyncStatus(repoRoot: string, status: MainSyncStatus): void {\n this.store.mergeBranch(this.mainSyncStatusPath(repoRoot), status);\n }\n\n /**\n * Replace the WHOLE document — only the winning refresher may do this.\n *\n * ATOMIC (temp file + `rename()` in the same directory). The refresher writes this file while\n * guards on the blocking path read it, possibly from another worktree; a plain `writeFileSync`\n * truncates first, so a reader landing in that window gets a torn document. See AtomicFile.\n */\n writeMainSyncStatusFile(repoRoot: string, file: MainSyncStatusFile): void {\n this.store.writeFile(this.mainSyncStatusPath(repoRoot), file);\n }\n\n readMainSyncLock(repoRoot: string): MainSyncLock | null {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const lockPath = this.mainSyncLockPath(repoRoot);\n if (!fs.existsSync(lockPath)) return null;\n const raw = JSON.parse(fs.readFileSync(lockPath, 'utf8')) as RawLock;\n return new MainSyncLock(raw.state ?? LOCK_STATE_FINISHED, raw.started ?? 0, raw.pid ?? 0);\n } catch (err: unknown) {\n const error = toError(err);\n void error;\n return null;\n }\n }\n\n writeMainSyncLock(repoRoot: string, lock: MainSyncLock): void {\n const lockPath = this.mainSyncLockPath(repoRoot);\n this.ensureDir(lockPath);\n fs.writeFileSync(lockPath, JSON.stringify(lock, null, 2) + '\\n');\n }\n\n // A lock is stale (prior refresher assumed hung) once `inprocess` longer than hangTimeoutMinutes.\n isLockStale(lock: MainSyncLock, hangTimeoutMinutes: number, now: number = Date.now()): boolean {\n return now - lock.started > hangTimeoutMinutes * 60 * 1000;\n }\n\n // True when another refresher is actively running and we should NOT start a second one.\n isRefreshInProgress(repoRoot: string, hangTimeoutMinutes: number, now: number = Date.now()): boolean {\n const lock = this.readMainSyncLock(repoRoot);\n if (!lock) return false;\n if (lock.state !== LOCK_STATE_INPROCESS) return false;\n if (this.isLockStale(lock, hangTimeoutMinutes, now)) return false;\n return this.isProcessAlive(lock.pid);\n }\n\n /**\n * ATOMICALLY take the refresher lock. Returns the held lock, or null when someone else holds it.\n *\n * Replaces the check-then-write pair (`isRefreshInProgress` then `writeMainSyncLock`), whose gap\n * let two detached refreshers both pass the check and then both run `git fetch` at once — the\n * concurrency this lock exists to prevent. The create uses the `wx` flag (O_CREAT|O_EXCL), so of\n * N racing refreshers exactly one creates the file.\n *\n * A lock file left behind by a FINISHED, hung, or killed refresher is reclaimed: we only unlink\n * and re-take it once `isRefreshInProgress` says the holder is provably not running, and we\n * re-read afterwards to confirm the entry we see is ours before claiming the lock.\n */\n tryAcquireMainSyncLock(\n repoRoot: string,\n hangTimeoutMinutes: number,\n now: number = Date.now(),\n pid: number = process.pid,\n ): MainSyncLock | null {\n const lockPath = this.mainSyncLockPath(repoRoot);\n this.ensureDir(lockPath);\n const lock = this.inProcessLock(now, pid);\n const payload = JSON.stringify(lock, null, 2) + '\\n';\n\n if (this.createExclusive(lockPath, payload)) return lock;\n\n // The file already exists. Leave it alone while a live refresher owns it.\n if (this.isRefreshInProgress(repoRoot, hangTimeoutMinutes, now)) return null;\n\n // Reclaim it: remove the dead entry, then re-take it exclusively so only one reclaimer wins.\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n fs.unlinkSync(lockPath);\n } catch (err: unknown) {\n const error = toError(err);\n void error; // someone else reclaimed it first — the exclusive create below decides\n }\n if (!this.createExclusive(lockPath, payload)) return null;\n\n // Confirm the lock on disk is OURS (a simultaneous reclaimer could have unlinked ours and\n // written its own between the two calls above).\n const held = this.readMainSyncLock(repoRoot);\n if (!held || held.pid !== pid || held.started !== lock.started) return null;\n return lock;\n }\n\n // O_CREAT|O_EXCL write: true when THIS call created the file, false when it already existed.\n private createExclusive(lockPath: string, payload: string): boolean {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n fs.writeFileSync(lockPath, payload, { flag: 'wx' });\n return true;\n } catch (err: unknown) {\n const error = toError(err);\n void error;\n return false;\n }\n }\n\n inProcessLock(now: number = Date.now(), pid: number = process.pid): MainSyncLock {\n return new MainSyncLock(LOCK_STATE_INPROCESS, now, pid);\n }\n\n finishedLock(started: number): MainSyncLock {\n return new MainSyncLock(LOCK_STATE_FINISHED, started, 0);\n }\n\n /**\n * The SLOW path for ONE branch: the branch checked out in `repoRoot`. Runs its own fetch and its\n * own `gh` query, so it is the right entry point for a single-worktree caller and for tests.\n * The refresher uses computeAllMainSyncStatuses instead — see there for why.\n */\n computeMainSyncStatus(repoRoot: string): MainSyncStatus {\n // Best-effort network refresh; offline just means we evaluate against the last-fetched ref.\n this.fetchOriginMain(repoRoot);\n return this.computeBranchStatus(repoRoot, this.gitBranch(repoRoot), this.loadPullRequestIndex(repoRoot));\n }\n\n /**\n * The SLOW path for EVERY branch this repo has checked out — what the detached refresher runs.\n *\n * The cache is shared by all worktrees but was written for one branch, so only one worktree's\n * guards were ever armed. Enumerating the worktrees here and writing all of their branches in one\n * atomic map arms every worktree off a single refresh.\n *\n * NETWORK COST DOES NOT SCALE WITH BRANCH COUNT, which is the whole reason this is a separate\n * method: ONE `git fetch` and ONE repo-wide `gh pr list` for all N branches (the old code asked\n * `gh` twice per branch, so looping it would have cost 2N round trips). Everything after that is\n * local git in each worktree — merge-base, rev-parse, diff, ls-files.\n *\n * The map is rebuilt from the LIVE worktree set every refresh, so a deleted branch or removed\n * worktree simply stops appearing. No pruning pass, no unbounded growth.\n */\n computeAllMainSyncStatuses(repoRoot: string): MainSyncStatusFile {\n this.fetchOriginMain(repoRoot);\n const index = this.loadPullRequestIndex(repoRoot);\n const branches: Record<string, MainSyncStatus> = {};\n for (const tree of this.worktrees.listWorktrees(repoRoot)) {\n // Detached HEAD (and a bare worktree) has no branch to key an entry on. Skipping it must\n // not stop the others being recorded, which is why this is a `continue`, not a bail-out.\n if (tree.branch === '' || this.hasBranch(branches, tree.branch)) continue;\n branches[tree.branch] = this.computeBranchStatus(tree.path, tree.branch, index);\n }\n // `main` always present, even with no worktree on it: read-stale-guard's state A looks itself\n // up under 'main', and an absent entry there would silently disarm the stale-main block.\n if (!this.hasBranch(branches, 'main')) branches['main'] = this.mainOnlyStatus(repoRoot);\n return new MainSyncStatusFile(MAIN_SYNC_STATUS_VERSION, new Date().toISOString(), branches);\n }\n\n private hasBranch(branches: Record<string, MainSyncStatus>, branch: string): boolean {\n return Object.prototype.hasOwnProperty.call(branches, branch);\n }\n\n /**\n * One branch's status, computed in `worktreePath` (the tree that has it checked out — the\n * working-tree overlap signals are only correct from there). Does NOT fetch and does NOT call\n * `gh`: both are done once per refresh by the caller.\n */\n // webpieces-disable max-lines-new-methods -- one cohesive slow-path computation\n private computeBranchStatus(repoRoot: string, branch: string, index: PullRequestIndex): MainSyncStatus {\n return this.stampForge(this.computeBranchStatusInner(repoRoot, branch, index), index);\n }\n\n // Every return path of the computation below has to carry the forge flag, and threading it through\n // four constructions is how one of them would end up missing it. One wrapper, one assignment.\n private stampForge(status: MainSyncStatus, index: PullRequestIndex): MainSyncStatus {\n status.forgeReachable = index.forgeReachable;\n return status;\n }\n\n // webpieces-disable max-lines-new-methods -- one cohesive slow-path computation\n private computeBranchStatusInner(repoRoot: string, branch: string, index: PullRequestIndex): MainSyncStatus {\n const mergedPr = index.mergedFor(branch);\n const openPr = index.openFor(branch);\n\n const head = this.capture(repoRoot, 'git', ['rev-parse', 'HEAD']);\n const originMain = this.capture(repoRoot, 'git', ['rev-parse', 'origin/main']);\n const localMain = this.localMainHash(repoRoot);\n const featureHead = head.ok ? head.out : '';\n if (!head.ok || !originMain.ok) {\n const status = this.benignStatus(branch, featureHead);\n status.branchAlreadyMerged = mergedPr !== '';\n status.mergedPr = mergedPr;\n status.openPr = openPr;\n status.localMain = localMain;\n status.commitsBehind = this.commitDistance(repoRoot, localMain, originMain.ok ? originMain.out : '');\n return status;\n }\n\n const forkPoint = this.capture(repoRoot, 'git', ['merge-base', 'origin/main', 'HEAD']);\n if (!forkPoint.ok || forkPoint.out === '') {\n const noFork = new MainSyncStatus(branch, mergedPr !== '', mergedPr, false, null, originMain.out, featureHead, false, [], new Date().toISOString());\n noFork.openPr = openPr;\n noFork.localMain = localMain;\n noFork.commitsBehind = this.commitDistance(repoRoot, localMain, originMain.out);\n return noFork;\n }\n\n const featureFiles = new Set(this.featureChangedFiles(repoRoot, forkPoint.out));\n const mainFiles = this.changedFiles(repoRoot, forkPoint.out, 'origin/main');\n const conflictFiles = mainFiles.filter((file: string): boolean => featureFiles.has(file));\n\n const status = new MainSyncStatus(\n branch,\n mergedPr !== '',\n mergedPr,\n true,\n forkPoint.out,\n originMain.out,\n featureHead,\n conflictFiles.length > 0,\n conflictFiles,\n new Date().toISOString(),\n );\n status.openPr = openPr;\n status.localMain = localMain;\n status.commitsBehind = this.commitDistance(repoRoot, localMain, originMain.out);\n return status;\n }\n\n // The recovery steps when there is no fork point with origin/main (a bad merge of main into branch).\n squashRecoverySteps(currentBranch: string): string[] {\n return [\n '1. Fetch latest main: git fetch origin main',\n `2. New branch off origin/main: git checkout -b ${currentBranch}-v2 origin/main`,\n `3. Squash-merge old branch: git merge --squash ${currentBranch}`,\n ' ^^ HUMAN-ONLY. `git merge` is blocked for AI (redirect-how-to-merge-main). AI: ask the',\n ' human to run step 3, and warn them it is a raw merge — only correct here because the',\n ' branch is already broken. For a normal update from main they should push back and',\n ' tell you to use the gated 3-point merge instead: `pnpm wp-start-update` (paired with',\n ' `pnpm wp-finish-update`) when no PR is open, or `pnpm wp-start-upsert-pr` (paired with',\n ' `pnpm wp-finish-upsert-pr`) when a PR IS open — a PR MUST use the upsert-pr pair.',\n `4. Commit the squash: git add -A && git commit -m \"Squashed from ${currentBranch}\"`,\n '5. If a PR exists: open a NEW PR for the -v2 branch and close the old one.',\n ];\n }\n\n /**\n * Synchronously stamp a clean \"up to date with main\" status — call right after a successful merge.\n *\n * This is the one writer that takes NO lock, and it knows about exactly one branch. It therefore\n * goes through writeMainSyncStatus, which MERGES its entry into the existing map; writing a whole\n * document from here would wipe every other worktree's entry.\n */\n stampCleanMainSyncStatus(repoRoot: string): void {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const branch = this.gitBranch(repoRoot);\n const originMain = this.capture(repoRoot, 'git', ['rev-parse', 'origin/main']);\n const featureHead = this.capture(repoRoot, 'git', ['rev-parse', 'HEAD']);\n if (!originMain.ok || !featureHead.ok) return;\n const status = new MainSyncStatus(\n branch, false, '', true, originMain.out, originMain.out, featureHead.out, false, [], new Date().toISOString(),\n );\n status.localMain = this.localMainHash(repoRoot);\n status.commitsBehind = 0;\n this.writeMainSyncStatus(repoRoot, status);\n } catch (err: unknown) {\n const error = toError(err);\n void error;\n }\n }\n\n /**\n * Refresh `origin/main` WITHOUT writing `.git/FETCH_HEAD`.\n *\n * WHY the flag is not optional: this runs in a DETACHED background process while the agent is\n * running its own foreground `git fetch` / `git pull` in the very same repo. `.git/FETCH_HEAD` is\n * a single file that git takes no lock on, so two overlapping fetches interleave their writes and\n * can leave the SAME `for-merge` line twice. `git pull` then reads two for-merge entries and dies\n * with `fatal: Cannot fast-forward to multiple branches` — wedging the exact command the\n * read-stale-guard tells the agent to run. `--no-write-fetch-head` (git >= 2.29) still updates the\n * remote-tracking ref, which is all anything downstream reads (`origin/main`, merge-base), so\n * nothing here needs FETCH_HEAD written at all.\n *\n * Older git rejects the flag; only then do we retry the plain form, accepting the old behaviour\n * rather than losing the refresh entirely on a pre-2020 git.\n */\n private fetchOriginMain(repoRoot: string): void {\n const safe = spawnSync('git', ['fetch', '--no-write-fetch-head', 'origin', 'main'], {\n cwd: repoRoot, encoding: 'utf8', stdio: ['ignore', 'ignore', 'pipe'],\n });\n if (safe.status === 0) return;\n if (!this.isUnknownGitOption(safe.stderr)) return; // a real failure (offline, auth) — not our business\n spawnSync('git', ['fetch', 'origin', 'main'], { cwd: repoRoot, stdio: 'ignore' });\n }\n\n // Did git reject the flag itself (too old), as opposed to failing the network refresh?\n private isUnknownGitOption(stderr: string | null): boolean {\n const text = (stderr ?? '').toLowerCase();\n return text.includes('unknown option') || text.includes('unknown switch') || text.includes('unrecognized option');\n }\n\n private ensureDir(filePath: string): void {\n fs.mkdirSync(path.dirname(filePath), { recursive: true });\n }\n\n // Liveness probe: is `pid` still a running process? pid <= 0 (unknown) → assume alive.\n private isProcessAlive(pid: number): boolean {\n if (pid <= 0) return true;\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n process.kill(pid, 0);\n return true;\n } catch (err: unknown) {\n const error = toError(err);\n return !error.message.includes('ESRCH');\n }\n }\n\n // Run a command capturing trimmed stdout; ok=false on spawn failure or non-zero exit.\n private capture(repoRoot: string, cmd: string, args: string[]): CmdCapture {\n const result = spawnSync(cmd, args, { cwd: repoRoot, encoding: 'utf8' });\n if (result.status !== 0 || typeof result.stdout !== 'string') return { ok: false, out: '' };\n return { ok: true, out: result.stdout.trim() };\n }\n\n // The actual checked-out branch in repoRoot — cwd-correct so the cache's `branch` label matches.\n private gitBranch(repoRoot: string): string {\n const result = this.capture(repoRoot, 'git', ['rev-parse', '--abbrev-ref', 'HEAD']);\n return result.ok ? result.out : '';\n }\n\n // The LOCAL main hash. `refs/heads/main` (not the bare name) so it can never resolve to a remote\n // ref or a tag. '' when main does not exist locally — which the guard treats as fail-open.\n private localMainHash(repoRoot: string): string {\n const result = this.capture(repoRoot, 'git', ['rev-parse', 'refs/heads/main']);\n return result.ok ? result.out : '';\n }\n\n // Slow-path only: the detached refresher calls this after fetching. Guards never count commits.\n private commitDistance(repoRoot: string, localMain: string, originMain: string): number | null {\n if (localMain === '' || originMain === '') return null;\n const result = this.capture(repoRoot, 'git', ['rev-list', '--count', `${localMain}..${originMain}`]);\n if (!result.ok || !/^\\d+$/.test(result.out)) return null;\n return Number(result.out);\n }\n\n private changedFiles(repoRoot: string, base: string, head: string): string[] {\n const result = this.capture(repoRoot, 'git', ['diff', '--name-only', base, head]);\n if (!result.ok || result.out === '') return [];\n return result.out.split('\\n').map((line: string): string => line.trim()).filter((line: string): boolean => line.length > 0);\n }\n\n // Every file this feature branch has touched since the fork point — committed AND still in the\n // working tree (staged / unstaged / untracked), so a conflict is visible WHILE editing.\n private featureChangedFiles(repoRoot: string, forkPoint: string): string[] {\n const out = new Set<string>();\n const add = (args: string[]): void => {\n const r = this.capture(repoRoot, 'git', args);\n if (!r.ok || r.out === '') return;\n for (const line of r.out.split('\\n')) {\n const f = line.trim();\n if (f.length > 0) out.add(f);\n }\n };\n add(['diff', '--name-only', forkPoint, 'HEAD']); // committed since the fork point\n add(['diff', '--name-only', 'HEAD']); // unstaged working-tree edits\n add(['diff', '--name-only', '--cached', 'HEAD']); // staged edits\n add(['ls-files', '--others', '--exclude-standard']); // untracked new files (respects .gitignore)\n return [...out];\n }\n\n /**\n * ONE repo-wide `gh pr list` giving every branch's merged/open PR — replaces the two per-branch\n * `--head <branch>` queries. `--limit 200` bounds it; a branch whose only PR is older than that\n * falls out of the index and reads as \"no PR\", which is the fail-OPEN direction (no merged PR =\n * no block), the same way a missing `gh` or an offline machine already degraded.\n */\n private loadPullRequestIndex(repoRoot: string): PullRequestIndex {\n const result = this.capture(repoRoot, 'gh', [\n 'pr', 'list', '--state', 'all', '--json', 'number,headRefName,state', '--limit', '200',\n ]);\n // `gh` could not be run, or exited non-zero (missing, unauthenticated, rate-limited, offline).\n // An EMPTY index is the same fail-open answer as before — but it is now labelled UNREACHABLE, so\n // the guards can report an abstention rather than an approval. Empty STDOUT on a zero exit is a\n // real answer (\"this repo has no PRs\"), so only the failure path clears the flag.\n if (!result.ok) return new PullRequestIndex({}, {}, false);\n if (result.out === '') return new PullRequestIndex({}, {}, true);\n return this.store.indexPullRequests(result.out);\n }\n\n /**\n * A `main` entry synthesised WITHOUT a worktree standing on main. Carries only the two hashes\n * read-stale-guard / stale-main-bash-guard actually compare (originMain vs the live tree's\n * ancestry) — never merged, never conflicting, so it can only ever produce the stale-main verdict.\n */\n private mainOnlyStatus(repoRoot: string): MainSyncStatus {\n const originMain = this.capture(repoRoot, 'git', ['rev-parse', 'origin/main']);\n const localMain = this.localMainHash(repoRoot);\n const status = new MainSyncStatus(\n 'main', false, '', true, localMain === '' ? null : localMain,\n originMain.ok ? originMain.out : '', localMain, false, [], new Date().toISOString(),\n );\n status.localMain = localMain;\n status.commitsBehind = this.commitDistance(repoRoot, localMain, originMain.ok ? originMain.out : '');\n return status;\n }\n\n // A benign status that never blocks — used when origin/main can't be resolved.\n private benignStatus(branch: string, featureHead: string): MainSyncStatus {\n return new MainSyncStatus(branch, false, '', true, null, '', featureHead, false, [], new Date().toISOString());\n }\n}\n\n// Temporary migration delegators to MainSyncStatusService — removed once consumers inject it.\nconst mainSyncSvc = new MainSyncStatusService();\n\n// webpieces-disable no-function-outside-class -- temporary back-compat delegator to MainSyncStatusService; removed once consumers inject it\nexport function mainSyncStatusPath(repoRoot: string): string { return mainSyncSvc.mainSyncStatusPath(repoRoot); }\n// webpieces-disable no-function-outside-class -- temporary back-compat delegator to MainSyncStatusService; removed once consumers inject it\nexport function mainSyncLockPath(repoRoot: string): string { return mainSyncSvc.mainSyncLockPath(repoRoot); }\n// webpieces-disable no-function-outside-class -- temporary back-compat delegator to MainSyncStatusService; removed once consumers inject it\nexport function readMainSyncStatus(repoRoot: string, branch: string): MainSyncStatus | null { return mainSyncSvc.readMainSyncStatus(repoRoot, branch); }\n// webpieces-disable no-function-outside-class -- temporary back-compat delegator to MainSyncStatusService; removed once consumers inject it\nexport function readMainSyncStatusFile(repoRoot: string): MainSyncStatusFile | null { return mainSyncSvc.readMainSyncStatusFile(repoRoot); }\n// webpieces-disable no-function-outside-class -- temporary back-compat delegator to MainSyncStatusService; removed once consumers inject it\nexport function writeMainSyncStatus(repoRoot: string, status: MainSyncStatus): void { mainSyncSvc.writeMainSyncStatus(repoRoot, status); }\n// webpieces-disable no-function-outside-class -- temporary back-compat delegator to MainSyncStatusService; removed once consumers inject it\nexport function writeMainSyncStatusFile(repoRoot: string, file: MainSyncStatusFile): void { mainSyncSvc.writeMainSyncStatusFile(repoRoot, file); }\n// webpieces-disable no-function-outside-class -- temporary back-compat delegator to MainSyncStatusService; removed once consumers inject it\nexport function readMainSyncLock(repoRoot: string): MainSyncLock | null { return mainSyncSvc.readMainSyncLock(repoRoot); }\n// webpieces-disable no-function-outside-class -- temporary back-compat delegator to MainSyncStatusService; removed once consumers inject it\nexport function writeMainSyncLock(repoRoot: string, lock: MainSyncLock): void { mainSyncSvc.writeMainSyncLock(repoRoot, lock); }\n// webpieces-disable no-function-outside-class -- temporary back-compat delegator to MainSyncStatusService; removed once consumers inject it\nexport function isLockStale(lock: MainSyncLock, hangTimeoutMinutes: number, now: number = Date.now()): boolean { return mainSyncSvc.isLockStale(lock, hangTimeoutMinutes, now); }\n// webpieces-disable no-function-outside-class -- temporary back-compat delegator to MainSyncStatusService; removed once consumers inject it\nexport function isRefreshInProgress(repoRoot: string, hangTimeoutMinutes: number, now: number = Date.now()): boolean { return mainSyncSvc.isRefreshInProgress(repoRoot, hangTimeoutMinutes, now); }\n// webpieces-disable no-function-outside-class -- temporary back-compat delegator to MainSyncStatusService; removed once consumers inject it\nexport function tryAcquireMainSyncLock(repoRoot: string, hangTimeoutMinutes: number, now: number = Date.now(), pid: number = process.pid): MainSyncLock | null { return mainSyncSvc.tryAcquireMainSyncLock(repoRoot, hangTimeoutMinutes, now, pid); }\n// webpieces-disable no-function-outside-class -- temporary back-compat delegator to MainSyncStatusService; removed once consumers inject it\nexport function inProcessLock(now: number = Date.now(), pid: number = process.pid): MainSyncLock { return mainSyncSvc.inProcessLock(now, pid); }\n// webpieces-disable no-function-outside-class -- temporary back-compat delegator to MainSyncStatusService; removed once consumers inject it\nexport function finishedLock(started: number): MainSyncLock { return mainSyncSvc.finishedLock(started); }\n// webpieces-disable no-function-outside-class -- temporary back-compat delegator to MainSyncStatusService; removed once consumers inject it\nexport function computeMainSyncStatus(repoRoot: string): MainSyncStatus { return mainSyncSvc.computeMainSyncStatus(repoRoot); }\n// webpieces-disable no-function-outside-class -- temporary back-compat delegator to MainSyncStatusService; removed once consumers inject it\nexport function computeAllMainSyncStatuses(repoRoot: string): MainSyncStatusFile { return mainSyncSvc.computeAllMainSyncStatuses(repoRoot); }\n// webpieces-disable no-function-outside-class -- temporary back-compat delegator to MainSyncStatusService; removed once consumers inject it\nexport function squashRecoverySteps(currentBranch: string): string[] { return mainSyncSvc.squashRecoverySteps(currentBranch); }\n// webpieces-disable no-function-outside-class -- temporary back-compat delegator to MainSyncStatusService; removed once consumers inject it\nexport function stampCleanMainSyncStatus(repoRoot: string): void { mainSyncSvc.stampCleanMainSyncStatus(repoRoot); }\n"]}
1
+ {"version":3,"file":"main-sync-status.js","sourceRoot":"","sources":["../../../../../packages/tooling/repo-workflow-core/src/main-sync-status.ts"],"names":[],"mappings":";;;AAsZA,gDAAiH;AAEjH,gDAAwJ;AAExJ,wDAA4I;AAE5I,kDAA0I;AAE1I,0DAAkJ;AAElJ,sDAA+H;AAE/H,gEAA6I;AAE7I,kDAA+H;AAE/H,4DAAoH;;AAtapH,iDAA0C;AAC1C,yCAA2D;AAE3D,8DAAuD;AACvD,qDAO0B;AAQqC,yGAd3D,yCAAwB,OAc2D;AAA9E,+FAZL,+BAAc,OAYK;AAAE,mGAXrB,mCAAkB,OAWqB;AAAE,iGAVzC,iCAAgB,OAUyC;AAA4B,+FATrF,+BAAc,OASqF;AAPvG,8DAAuE;AACvE,iEAA6D;AAC7D,2CAA8C;AAC9C,yDAAqD;AAMrD,sGAAsG;AACtG,wGAAwG;AACxG,qGAAqG;AACrG,wFAAwF;AAExF,sGAAsG;AACtG,mDAAmD;AACtC,QAAA,4BAA4B,GAAG,CAAC,CAAC;AAE9C,MAAM,qBAAqB,GAAG,uBAAuB,CAAC;AAQtD;;;GAGG;AAEI,IAAM,qBAAqB,GAA3B,MAAM,qBAAqB;IAET;IACA;IACA;IACA;IACA;IALrB,YACqB,SAAuB,6BAAY,EACnC,aAAyB,IAAI,2BAAU,EAAE,EACzC,QAA2B,IAAI,kCAAiB,CAAC,UAAU,CAAC,EAC5D,YAA6B,IAAI,2BAAe,EAAE,EAClD,WAA4B,IAAI,kCAAe,EAAE;QAJjD,WAAM,GAAN,MAAM,CAA6B;QACnC,eAAU,GAAV,UAAU,CAA+B;QACzC,UAAK,GAAL,KAAK,CAAuD;QAC5D,cAAS,GAAT,SAAS,CAAyC;QAClD,aAAQ,GAAR,QAAQ,CAAyC;IACnE,CAAC;IAEJ;;;;;;;;;;;;;;OAcG;IACH,kBAAkB,CAAC,QAAgB;QAC/B,OAAO,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,QAAQ,EAAE,qBAAqB,CAAC,CAAC;IACnE,CAAC;IAED;;;;;;;OAOG;IACH,kBAAkB,CAAC,QAAgB,EAAE,MAAc;QAC/C,OAAO,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,CAAC,sBAAsB,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC,CAAC;IAClF,CAAC;IAED,6FAA6F;IAC7F,sBAAsB,CAAC,QAAgB;QACnC,OAAO,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,kBAAkB,CAAC,QAAQ,CAAC,CAAC,CAAC;IAClE,CAAC;IAED;;;;;;;OAOG;IACH,mBAAmB,CAAC,QAAgB,EAAE,MAAsB;QACxD,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,IAAI,CAAC,kBAAkB,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC,CAAC;IACtE,CAAC;IAED;;;;;;OAMG;IACH,uBAAuB,CAAC,QAAgB,EAAE,IAAwB;QAC9D,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,IAAI,CAAC,kBAAkB,CAAC,QAAQ,CAAC,EAAE,IAAI,CAAC,CAAC;IAClE,CAAC;IAED;;;;;OAKG;IACH,oBAAoB,CAAC,QAAgB,EAAE,SAAiB,EAAE,OAAuB;QAC7E,IAAI,CAAC,KAAK,CAAC,aAAa,CAAC,IAAI,CAAC,kBAAkB,CAAC,QAAQ,CAAC,EAAE,SAAS,EAAE,OAAO,CAAC,CAAC;IACpF,CAAC;IAED;;;;OAIG;IACH,qBAAqB,CAAC,QAAgB;QAClC,4FAA4F;QAC5F,IAAI,CAAC,eAAe,CAAC,QAAQ,CAAC,CAAC;QAC/B,OAAO,IAAI,CAAC,mBAAmB,CAAC,QAAQ,EAAE,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,EAAE,IAAI,CAAC,oBAAoB,CAAC,QAAQ,CAAC,CAAC,CAAC;IAC7G,CAAC;IAED;;;;;;;;;;;;;;OAcG;IACH,0BAA0B,CAAC,QAAgB;QACvC,IAAI,CAAC,eAAe,CAAC,QAAQ,CAAC,CAAC;QAC/B,MAAM,KAAK,GAAG,IAAI,CAAC,oBAAoB,CAAC,QAAQ,CAAC,CAAC;QAClD,MAAM,QAAQ,GAAmC,EAAE,CAAC;QACpD,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,SAAS,CAAC,aAAa,CAAC,QAAQ,CAAC,EAAE,CAAC;YACxD,yFAAyF;YACzF,yFAAyF;YACzF,IAAI,IAAI,CAAC,MAAM,KAAK,EAAE,IAAI,IAAI,CAAC,SAAS,CAAC,QAAQ,EAAE,IAAI,CAAC,MAAM,CAAC;gBAAE,SAAS;YAC1E,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,IAAI,CAAC,mBAAmB,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;QACpF,CAAC;QACD,8FAA8F;QAC9F,yFAAyF;QACzF,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,QAAQ,EAAE,MAAM,CAAC;YAAE,QAAQ,CAAC,MAAM,CAAC,GAAG,IAAI,CAAC,cAAc,CAAC,QAAQ,CAAC,CAAC;QACxF,OAAO,IAAI,mCAAkB,CAAC,yCAAwB,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,EAAE,QAAQ,CAAC,CAAC;IAChG,CAAC;IAEO,SAAS,CAAC,QAAwC,EAAE,MAAc;QACtE,OAAO,MAAM,CAAC,SAAS,CAAC,cAAc,CAAC,IAAI,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;IAClE,CAAC;IAED;;;;OAIG;IACH,gFAAgF;IACxE,mBAAmB,CAAC,QAAgB,EAAE,MAAc,EAAE,KAAuB;QACjF,OAAO,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,wBAAwB,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,KAAK,CAAC,CAAC;IAC1F,CAAC;IAED,mGAAmG;IACnG,8FAA8F;IACtF,UAAU,CAAC,MAAsB,EAAE,KAAuB;QAC9D,MAAM,CAAC,cAAc,GAAG,KAAK,CAAC,cAAc,CAAC;QAC7C,OAAO,MAAM,CAAC;IAClB,CAAC;IAED,gFAAgF;IACxE,wBAAwB,CAAC,QAAgB,EAAE,MAAc,EAAE,KAAuB;QACtF,MAAM,QAAQ,GAAG,KAAK,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;QACzC,MAAM,MAAM,GAAG,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;QAErC,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,KAAK,EAAE,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC,CAAC;QAClE,MAAM,UAAU,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,KAAK,EAAE,CAAC,WAAW,EAAE,aAAa,CAAC,CAAC,CAAC;QAC/E,MAAM,SAAS,GAAG,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC;QAC/C,MAAM,WAAW,GAAG,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;QAC5C,IAAI,CAAC,IAAI,CAAC,EAAE,IAAI,CAAC,UAAU,CAAC,EAAE,EAAE,CAAC;YAC7B,MAAM,MAAM,GAAG,IAAI,CAAC,YAAY,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;YACtD,MAAM,CAAC,mBAAmB,GAAG,QAAQ,KAAK,EAAE,CAAC;YAC7C,MAAM,CAAC,QAAQ,GAAG,QAAQ,CAAC;YAC3B,MAAM,CAAC,MAAM,GAAG,MAAM,CAAC;YACvB,MAAM,CAAC,SAAS,GAAG,SAAS,CAAC;YAC7B,MAAM,CAAC,aAAa,GAAG,IAAI,CAAC,cAAc,CAAC,QAAQ,EAAE,SAAS,EAAE,UAAU,CAAC,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;YACrG,OAAO,MAAM,CAAC;QAClB,CAAC;QAED,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,KAAK,EAAE,CAAC,YAAY,EAAE,aAAa,EAAE,MAAM,CAAC,CAAC,CAAC;QACvF,IAAI,CAAC,SAAS,CAAC,EAAE,IAAI,SAAS,CAAC,GAAG,KAAK,EAAE,EAAE,CAAC;YACxC,MAAM,MAAM,GAAG,IAAI,+BAAc,CAAC,MAAM,EAAE,QAAQ,KAAK,EAAE,EAAE,QAAQ,EAAE,KAAK,EAAE,IAAI,EAAE,UAAU,CAAC,GAAG,EAAE,WAAW,EAAE,KAAK,EAAE,EAAE,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,CAAC;YACpJ,MAAM,CAAC,MAAM,GAAG,MAAM,CAAC;YACvB,MAAM,CAAC,SAAS,GAAG,SAAS,CAAC;YAC7B,MAAM,CAAC,aAAa,GAAG,IAAI,CAAC,cAAc,CAAC,QAAQ,EAAE,SAAS,EAAE,UAAU,CAAC,GAAG,CAAC,CAAC;YAChF,OAAO,MAAM,CAAC;QAClB,CAAC;QAED,MAAM,YAAY,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,mBAAmB,CAAC,QAAQ,EAAE,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC;QAChF,MAAM,SAAS,GAAG,IAAI,CAAC,YAAY,CAAC,QAAQ,EAAE,SAAS,CAAC,GAAG,EAAE,aAAa,CAAC,CAAC;QAC5E,MAAM,aAAa,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC,IAAY,EAAW,EAAE,CAAC,YAAY,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC;QAE1F,MAAM,MAAM,GAAG,IAAI,+BAAc,CAC7B,MAAM,EACN,QAAQ,KAAK,EAAE,EACf,QAAQ,EACR,IAAI,EACJ,SAAS,CAAC,GAAG,EACb,UAAU,CAAC,GAAG,EACd,WAAW,EACX,aAAa,CAAC,MAAM,GAAG,CAAC,EACxB,aAAa,EACb,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAC3B,CAAC;QACF,MAAM,CAAC,MAAM,GAAG,MAAM,CAAC;QACvB,MAAM,CAAC,SAAS,GAAG,SAAS,CAAC;QAC7B,MAAM,CAAC,aAAa,GAAG,IAAI,CAAC,cAAc,CAAC,QAAQ,EAAE,SAAS,EAAE,UAAU,CAAC,GAAG,CAAC,CAAC;QAChF,OAAO,MAAM,CAAC;IAClB,CAAC;IAED,qGAAqG;IACrG,mBAAmB,CAAC,aAAqB;QACrC,OAAO;YACH,wDAAwD;YACxD,oDAAoD,aAAa,iBAAiB;YAClF,uDAAuD,aAAa,EAAE;YACtE,2FAA2F;YAC3F,yFAAyF;YACzF,sFAAsF;YACtF,yFAAyF;YACzF,2FAA2F;YAC3F,sFAAsF;YACtF,+EAA+E,aAAa,GAAG;YAC/F,0FAA0F;SAC7F,CAAC;IACN,CAAC;IAED;;;;;;OAMG;IACH,wBAAwB,CAAC,QAAgB;QACrC,8DAA8D;QAC9D,IAAI,CAAC;YACD,MAAM,MAAM,GAAG,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC;YACxC,MAAM,UAAU,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,KAAK,EAAE,CAAC,WAAW,EAAE,aAAa,CAAC,CAAC,CAAC;YAC/E,MAAM,WAAW,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,KAAK,EAAE,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC,CAAC;YACzE,IAAI,CAAC,UAAU,CAAC,EAAE,IAAI,CAAC,WAAW,CAAC,EAAE;gBAAE,OAAO;YAC9C,MAAM,MAAM,GAAG,IAAI,+BAAc,CAC7B,MAAM,EAAE,KAAK,EAAE,EAAE,EAAE,IAAI,EAAE,UAAU,CAAC,GAAG,EAAE,UAAU,CAAC,GAAG,EAAE,WAAW,CAAC,GAAG,EAAE,KAAK,EAAE,EAAE,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAChH,CAAC;YACF,MAAM,CAAC,SAAS,GAAG,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC;YAChD,MAAM,CAAC,aAAa,GAAG,CAAC,CAAC;YACzB,IAAI,CAAC,mBAAmB,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;QAC/C,CAAC;QAAC,OAAO,GAAY,EAAE,CAAC;YACpB,MAAM,KAAK,GAAG,IAAA,kBAAO,EAAC,GAAG,CAAC,CAAC;YAC3B,KAAK,KAAK,CAAC;QACf,CAAC;IACL,CAAC;IAED;;;;;;;;;;;;;;OAcG;IACK,eAAe,CAAC,QAAgB;QACpC,MAAM,IAAI,GAAG,IAAA,yBAAS,EAAC,KAAK,EAAE,CAAC,OAAO,EAAE,uBAAuB,EAAE,QAAQ,EAAE,MAAM,CAAC,EAAE;YAChF,GAAG,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,QAAQ,EAAE,QAAQ,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,kCAAe,CAAC,UAAU;SAC5G,CAAC,CAAC;QACH,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO;QAC9B,IAAI,CAAC,IAAI,CAAC,kBAAkB,CAAC,IAAI,CAAC,MAAM,CAAC;YAAE,OAAO,CAAE,oDAAoD;QACxG,IAAA,yBAAS,EAAC,KAAK,EAAE,CAAC,OAAO,EAAE,QAAQ,EAAE,MAAM,CAAC,EAAE,EAAE,GAAG,EAAE,QAAQ,EAAE,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,kCAAe,CAAC,UAAU,EAAE,CAAC,CAAC;IAC3H,CAAC;IAED,uFAAuF;IAC/E,kBAAkB,CAAC,MAAqB;QAC5C,MAAM,IAAI,GAAG,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC;QAC1C,OAAO,IAAI,CAAC,QAAQ,CAAC,gBAAgB,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,gBAAgB,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,qBAAqB,CAAC,CAAC;IACtH,CAAC;IAED,sFAAsF;IAC9E,OAAO,CAAC,QAAgB,EAAE,GAAW,EAAE,IAAc;QACzD,MAAM,MAAM,GAAG,IAAA,yBAAS,EAAC,GAAG,EAAE,IAAI,EAAE,EAAE,GAAG,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACjH,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,IAAI,OAAO,MAAM,CAAC,MAAM,KAAK,QAAQ;YAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE,EAAE,CAAC;QAC5F,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,GAAG,EAAE,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,EAAE,CAAC;IACnD,CAAC;IAED,iGAAiG;IACzF,SAAS,CAAC,QAAgB;QAC9B,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,KAAK,EAAE,CAAC,WAAW,EAAE,cAAc,EAAE,MAAM,CAAC,CAAC,CAAC;QACpF,OAAO,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;IACvC,CAAC;IAED,iGAAiG;IACjG,2FAA2F;IACnF,aAAa,CAAC,QAAgB;QAClC,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,KAAK,EAAE,CAAC,WAAW,EAAE,iBAAiB,CAAC,CAAC,CAAC;QAC/E,OAAO,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;IACvC,CAAC;IAED,gGAAgG;IACxF,cAAc,CAAC,QAAgB,EAAE,SAAiB,EAAE,UAAkB;QAC1E,IAAI,SAAS,KAAK,EAAE,IAAI,UAAU,KAAK,EAAE;YAAE,OAAO,IAAI,CAAC;QACvD,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,KAAK,EAAE,CAAC,UAAU,EAAE,SAAS,EAAE,GAAG,SAAS,KAAK,UAAU,EAAE,CAAC,CAAC,CAAC;QACrG,IAAI,CAAC,MAAM,CAAC,EAAE,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC;YAAE,OAAO,IAAI,CAAC;QACzD,OAAO,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IAC9B,CAAC;IAEO,YAAY,CAAC,QAAgB,EAAE,IAAY,EAAE,IAAY;QAC7D,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,KAAK,EAAE,CAAC,MAAM,EAAE,aAAa,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC;QAClF,IAAI,CAAC,MAAM,CAAC,EAAE,IAAI,MAAM,CAAC,GAAG,KAAK,EAAE;YAAE,OAAO,EAAE,CAAC;QAC/C,OAAO,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,IAAY,EAAU,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,IAAY,EAAW,EAAE,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IAChI,CAAC;IAED,+FAA+F;IAC/F,wFAAwF;IAChF,mBAAmB,CAAC,QAAgB,EAAE,SAAiB;QAC3D,MAAM,GAAG,GAAG,IAAI,GAAG,EAAU,CAAC;QAC9B,MAAM,GAAG,GAAG,CAAC,IAAc,EAAQ,EAAE;YACjC,MAAM,CAAC,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,KAAK,EAAE,IAAI,CAAC,CAAC;YAC9C,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,GAAG,KAAK,EAAE;gBAAE,OAAO;YAClC,KAAK,MAAM,IAAI,IAAI,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;gBACnC,MAAM,CAAC,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;gBACtB,IAAI,CAAC,CAAC,MAAM,GAAG,CAAC;oBAAE,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;YACjC,CAAC;QACL,CAAC,CAAC;QACF,GAAG,CAAC,CAAC,MAAM,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,CAAC,CAAC,CAAC,CAAM,iCAAiC;QACvF,GAAG,CAAC,CAAC,MAAM,EAAE,aAAa,EAAE,MAAM,CAAC,CAAC,CAAC,CAAiB,8BAA8B;QACpF,GAAG,CAAC,CAAC,MAAM,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,CAAC,CAAC,CAAC,CAAK,eAAe;QACrE,GAAG,CAAC,CAAC,UAAU,EAAE,UAAU,EAAE,oBAAoB,CAAC,CAAC,CAAC,CAAE,4CAA4C;QAClG,OAAO,CAAC,GAAG,GAAG,CAAC,CAAC;IACpB,CAAC;IAED;;;;;OAKG;IACK,oBAAoB,CAAC,QAAgB;QACzC,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,IAAI,EAAE;YACxC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,KAAK,EAAE,QAAQ,EAAE,0BAA0B,EAAE,SAAS,EAAE,KAAK;SACzF,CAAC,CAAC;QACH,+FAA+F;QAC/F,iGAAiG;QACjG,gGAAgG;QAChG,kFAAkF;QAClF,IAAI,CAAC,MAAM,CAAC,EAAE;YAAE,OAAO,IAAI,iCAAgB,CAAC,EAAE,EAAE,EAAE,EAAE,KAAK,CAAC,CAAC;QAC3D,IAAI,MAAM,CAAC,GAAG,KAAK,EAAE;YAAE,OAAO,IAAI,iCAAgB,CAAC,EAAE,EAAE,EAAE,EAAE,IAAI,CAAC,CAAC;QACjE,OAAO,IAAI,CAAC,KAAK,CAAC,iBAAiB,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IACpD,CAAC;IAED;;;;OAIG;IACK,cAAc,CAAC,QAAgB;QACnC,MAAM,UAAU,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,KAAK,EAAE,CAAC,WAAW,EAAE,aAAa,CAAC,CAAC,CAAC;QAC/E,MAAM,SAAS,GAAG,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC;QAC/C,MAAM,MAAM,GAAG,IAAI,+BAAc,CAC7B,MAAM,EAAE,KAAK,EAAE,EAAE,EAAE,IAAI,EAAE,SAAS,KAAK,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,EAC5D,UAAU,CAAC,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,SAAS,EAAE,KAAK,EAAE,EAAE,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CACtF,CAAC;QACF,MAAM,CAAC,SAAS,GAAG,SAAS,CAAC;QAC7B,MAAM,CAAC,aAAa,GAAG,IAAI,CAAC,cAAc,CAAC,QAAQ,EAAE,SAAS,EAAE,UAAU,CAAC,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;QACrG,OAAO,MAAM,CAAC;IAClB,CAAC;IAED,+EAA+E;IACvE,YAAY,CAAC,MAAc,EAAE,WAAmB;QACpD,OAAO,IAAI,+BAAc,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,EAAE,WAAW,EAAE,KAAK,EAAE,EAAE,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,CAAC;IACnH,CAAC;CACJ,CAAA;AArWY,sDAAqB;gCAArB,qBAAqB;IADjC,IAAA,sBAAU,EAAC,8BAAkB,CAAC,SAAS,CAAC;6CAGR,6BAAY;QACR,2BAAU;QACf,kCAAiB;QACb,2BAAe;QAChB,kCAAe;GANrC,qBAAqB,CAqWjC;AAED,8FAA8F;AAC9F,MAAM,WAAW,GAAG,IAAI,qBAAqB,EAAE,CAAC;AAEhD,4IAA4I;AAC5I,SAAgB,kBAAkB,CAAC,QAAgB,IAAY,OAAO,WAAW,CAAC,kBAAkB,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;AACjH,4IAA4I;AAC5I,SAAgB,kBAAkB,CAAC,QAAgB,EAAE,MAAc,IAA2B,OAAO,WAAW,CAAC,kBAAkB,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC;AACxJ,4IAA4I;AAC5I,SAAgB,sBAAsB,CAAC,QAAgB,IAA+B,OAAO,WAAW,CAAC,sBAAsB,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;AAC5I,4IAA4I;AAC5I,SAAgB,mBAAmB,CAAC,QAAgB,EAAE,MAAsB,IAAU,WAAW,CAAC,mBAAmB,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC;AAC1I,4IAA4I;AAC5I,SAAgB,uBAAuB,CAAC,QAAgB,EAAE,IAAwB,IAAU,WAAW,CAAC,uBAAuB,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC;AAClJ,4IAA4I;AAC5I,SAAgB,qBAAqB,CAAC,QAAgB,IAAoB,OAAO,WAAW,CAAC,qBAAqB,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;AAC/H,4IAA4I;AAC5I,SAAgB,0BAA0B,CAAC,QAAgB,IAAwB,OAAO,WAAW,CAAC,0BAA0B,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;AAC7I,4IAA4I;AAC5I,SAAgB,mBAAmB,CAAC,aAAqB,IAAc,OAAO,WAAW,CAAC,mBAAmB,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC;AAC/H,4IAA4I;AAC5I,SAAgB,wBAAwB,CAAC,QAAgB,IAAU,WAAW,CAAC,wBAAwB,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC","sourcesContent":["import { spawnSync } from 'child_process';\nimport { injectable, bindingScopeValues } from 'inversify';\n\nimport { AtomicFile } from '@webpieces/tooling-common';\nimport {\n MAIN_SYNC_STATUS_VERSION,\n MainSyncFileStore,\n MainSyncStatus,\n MainSyncStatusFile,\n PullRequestIndex,\n RefreshOutcome,\n} from './main-sync-file';\nimport { DotWebpieces, dotWebpieces } from '@webpieces/tooling-common';\nimport { toError } from '@webpieces/tooling-common/to-error';\nimport { WorktreeService } from './worktrees';\nimport { CommandTimeouts } from './command-timeouts';\n\n// The on-disk shape lives in main-sync-file.ts; re-exported so every existing importer of\n// `MainSyncStatus` from this module keeps working.\nexport { MainSyncStatus, MainSyncStatusFile, PullRequestIndex, MAIN_SYNC_STATUS_VERSION, RefreshOutcome };\n\n// Shared \"is my feature branch healthy relative to origin/main?\" state. The SLOW signals (git fetch +\n// merge-base + same-file-overlap + a merged-PR lookup) are computed by the ai-hook-rules refresher in a\n// DETACHED background process; the feature-branch-guard only READS this cached file. pr-gate's merge\n// flow also writes it synchronously after a merge. Lives here (the shared dep of both).\n\n// How long a refresher may hold main-sync.lock.json before the next spawner assumes it hung, kills it\n// and reclaims the lock (see MainSyncLockService).\nexport const DEFAULT_HANG_TIMEOUT_MINUTES = 5;\n\nconst MAIN_SYNC_STATUS_FILE = 'main-sync-status.json';\n\n// Result of a captured git/gh invocation: ok=false on spawn failure or non-zero exit.\ninterface CmdCapture {\n ok: boolean;\n out: string;\n}\n\n/**\n * Reads/writes the main-sync cache and computes the slow \"is my branch healthy vs origin/main?\"\n * status. `@injectable(bindingScopeValues.Singleton)` so it's injectable and drawn in consumers’ DI designs.\n */\n@injectable(bindingScopeValues.Singleton)\nexport class MainSyncStatusService {\n constructor(\n private readonly dotDir: DotWebpieces = dotWebpieces,\n private readonly atomicFile: AtomicFile = new AtomicFile(),\n private readonly store: MainSyncFileStore = new MainSyncFileStore(atomicFile),\n private readonly worktrees: WorktreeService = new WorktreeService(),\n private readonly timeouts: CommandTimeouts = new CommandTimeouts(),\n ) {}\n\n /**\n * SHARED scope: `<primary>/.webpieces/main-sync-status.json`, one per repo.\n *\n * It is the OUTPUT of the single-flight refresher below, so it has to live where the lock lives —\n * one `.git`, one `origin/main`, one fetch, one answer. A per-worktree copy under single-flight\n * would only ever be written for whichever worktree won the lock anyway.\n *\n * FORMERLY a known defect, now fixed: the file's CONTENT used to be keyed to ONE branch, and every\n * reader fails OPEN when the cached branch is not the branch being judged (`stale-cross-branch-cache\n * (fail-open)`). With several worktrees on several branches, only the winner of the lock was ever\n * described, so every other worktree's guards abstained — and which one was armed thrashed as the\n * lock changed hands. The file is now a MAP of branch -> status (see MainSyncStatusFile): the single\n * winning refresher enumerates every worktree and records all of their branches in one atomic write,\n * so one fetch arms everyone. Single-flight is unchanged; only what the winner writes changed.\n */\n mainSyncStatusPath(repoRoot: string): string {\n return this.dotDir.sharedFile(repoRoot, MAIN_SYNC_STATUS_FILE);\n }\n\n /**\n * The cached status FOR ONE BRANCH — the only read the four main-sync guards perform.\n *\n * `branch` is mandatory and is the branch being judged. A branch the last refresh never saw\n * returns null, which every guard already treats as `no-sync-cache (fail-open)`, so an unknown\n * branch degrades exactly as a missing file did. Any error (missing, truncated, malformed JSON)\n * also returns null — this must never throw on the blocking path.\n */\n readMainSyncStatus(repoRoot: string, branch: string): MainSyncStatus | null {\n return this.store.branchStatus(this.readMainSyncStatusFile(repoRoot), branch);\n }\n\n // The whole branch-keyed document, or null. Callers that need more than one branch use this.\n readMainSyncStatusFile(repoRoot: string): MainSyncStatusFile | null {\n return this.store.readFile(this.mainSyncStatusPath(repoRoot));\n }\n\n /**\n * Merge ONE branch's entry into the existing map, atomically, leaving siblings untouched.\n *\n * Read-modify-write rather than replace: this is the writer used by everyone who is NOT the\n * single-flight refresher (notably pr-gate's post-merge stamp, which holds no lock and knows about\n * exactly one branch). Replacing the document from there would erase every other worktree's entry\n * and silently disarm their guards until the next refresh.\n */\n writeMainSyncStatus(repoRoot: string, status: MainSyncStatus): void {\n this.store.mergeBranch(this.mainSyncStatusPath(repoRoot), status);\n }\n\n /**\n * Replace the WHOLE document — only the winning refresher may do this.\n *\n * ATOMIC (temp file + `rename()` in the same directory). The refresher writes this file while\n * guards on the blocking path read it, possibly from another worktree; a plain `writeFileSync`\n * truncates first, so a reader landing in that window gets a torn document. See AtomicFile.\n */\n writeMainSyncStatusFile(repoRoot: string, file: MainSyncStatusFile): void {\n this.store.writeFile(this.mainSyncStatusPath(repoRoot), file);\n }\n\n /**\n * Record that a refresh STARTED at `startedMs` and ended with `outcome`, keeping every branch entry\n * already on disk. The refresher's failure path: a run that produced no new branch data still\n * counts as a run, so the throttle retries it after MIN_REFRESH_INTERVAL_MINUTES rather than on\n * every tool call. A successful run stamps the whole document instead (writeMainSyncStatusFile).\n */\n recordRefreshOutcome(repoRoot: string, startedMs: number, outcome: RefreshOutcome): void {\n this.store.recordRefresh(this.mainSyncStatusPath(repoRoot), startedMs, outcome);\n }\n\n /**\n * The SLOW path for ONE branch: the branch checked out in `repoRoot`. Runs its own fetch and its\n * own `gh` query, so it is the right entry point for a single-worktree caller and for tests.\n * The refresher uses computeAllMainSyncStatuses instead — see there for why.\n */\n computeMainSyncStatus(repoRoot: string): MainSyncStatus {\n // Best-effort network refresh; offline just means we evaluate against the last-fetched ref.\n this.fetchOriginMain(repoRoot);\n return this.computeBranchStatus(repoRoot, this.gitBranch(repoRoot), this.loadPullRequestIndex(repoRoot));\n }\n\n /**\n * The SLOW path for EVERY branch this repo has checked out — what the detached refresher runs.\n *\n * The cache is shared by all worktrees but was written for one branch, so only one worktree's\n * guards were ever armed. Enumerating the worktrees here and writing all of their branches in one\n * atomic map arms every worktree off a single refresh.\n *\n * NETWORK COST DOES NOT SCALE WITH BRANCH COUNT, which is the whole reason this is a separate\n * method: ONE `git fetch` and ONE repo-wide `gh pr list` for all N branches (the old code asked\n * `gh` twice per branch, so looping it would have cost 2N round trips). Everything after that is\n * local git in each worktree — merge-base, rev-parse, diff, ls-files.\n *\n * The map is rebuilt from the LIVE worktree set every refresh, so a deleted branch or removed\n * worktree simply stops appearing. No pruning pass, no unbounded growth.\n */\n computeAllMainSyncStatuses(repoRoot: string): MainSyncStatusFile {\n this.fetchOriginMain(repoRoot);\n const index = this.loadPullRequestIndex(repoRoot);\n const branches: Record<string, MainSyncStatus> = {};\n for (const tree of this.worktrees.listWorktrees(repoRoot)) {\n // Detached HEAD (and a bare worktree) has no branch to key an entry on. Skipping it must\n // not stop the others being recorded, which is why this is a `continue`, not a bail-out.\n if (tree.branch === '' || this.hasBranch(branches, tree.branch)) continue;\n branches[tree.branch] = this.computeBranchStatus(tree.path, tree.branch, index);\n }\n // `main` always present, even with no worktree on it: read-stale-guard's state A looks itself\n // up under 'main', and an absent entry there would silently disarm the stale-main block.\n if (!this.hasBranch(branches, 'main')) branches['main'] = this.mainOnlyStatus(repoRoot);\n return new MainSyncStatusFile(MAIN_SYNC_STATUS_VERSION, new Date().toISOString(), branches);\n }\n\n private hasBranch(branches: Record<string, MainSyncStatus>, branch: string): boolean {\n return Object.prototype.hasOwnProperty.call(branches, branch);\n }\n\n /**\n * One branch's status, computed in `worktreePath` (the tree that has it checked out — the\n * working-tree overlap signals are only correct from there). Does NOT fetch and does NOT call\n * `gh`: both are done once per refresh by the caller.\n */\n // webpieces-disable max-lines-new-methods -- one cohesive slow-path computation\n private computeBranchStatus(repoRoot: string, branch: string, index: PullRequestIndex): MainSyncStatus {\n return this.stampForge(this.computeBranchStatusInner(repoRoot, branch, index), index);\n }\n\n // Every return path of the computation below has to carry the forge flag, and threading it through\n // four constructions is how one of them would end up missing it. One wrapper, one assignment.\n private stampForge(status: MainSyncStatus, index: PullRequestIndex): MainSyncStatus {\n status.forgeReachable = index.forgeReachable;\n return status;\n }\n\n // webpieces-disable max-lines-new-methods -- one cohesive slow-path computation\n private computeBranchStatusInner(repoRoot: string, branch: string, index: PullRequestIndex): MainSyncStatus {\n const mergedPr = index.mergedFor(branch);\n const openPr = index.openFor(branch);\n\n const head = this.capture(repoRoot, 'git', ['rev-parse', 'HEAD']);\n const originMain = this.capture(repoRoot, 'git', ['rev-parse', 'origin/main']);\n const localMain = this.localMainHash(repoRoot);\n const featureHead = head.ok ? head.out : '';\n if (!head.ok || !originMain.ok) {\n const status = this.benignStatus(branch, featureHead);\n status.branchAlreadyMerged = mergedPr !== '';\n status.mergedPr = mergedPr;\n status.openPr = openPr;\n status.localMain = localMain;\n status.commitsBehind = this.commitDistance(repoRoot, localMain, originMain.ok ? originMain.out : '');\n return status;\n }\n\n const forkPoint = this.capture(repoRoot, 'git', ['merge-base', 'origin/main', 'HEAD']);\n if (!forkPoint.ok || forkPoint.out === '') {\n const noFork = new MainSyncStatus(branch, mergedPr !== '', mergedPr, false, null, originMain.out, featureHead, false, [], new Date().toISOString());\n noFork.openPr = openPr;\n noFork.localMain = localMain;\n noFork.commitsBehind = this.commitDistance(repoRoot, localMain, originMain.out);\n return noFork;\n }\n\n const featureFiles = new Set(this.featureChangedFiles(repoRoot, forkPoint.out));\n const mainFiles = this.changedFiles(repoRoot, forkPoint.out, 'origin/main');\n const conflictFiles = mainFiles.filter((file: string): boolean => featureFiles.has(file));\n\n const status = new MainSyncStatus(\n branch,\n mergedPr !== '',\n mergedPr,\n true,\n forkPoint.out,\n originMain.out,\n featureHead,\n conflictFiles.length > 0,\n conflictFiles,\n new Date().toISOString(),\n );\n status.openPr = openPr;\n status.localMain = localMain;\n status.commitsBehind = this.commitDistance(repoRoot, localMain, originMain.out);\n return status;\n }\n\n // The recovery steps when there is no fork point with origin/main (a bad merge of main into branch).\n squashRecoverySteps(currentBranch: string): string[] {\n return [\n '1. Fetch latest main: git fetch origin main',\n `2. New branch off origin/main: git checkout -b ${currentBranch}-v2 origin/main`,\n `3. Squash-merge old branch: git merge --squash ${currentBranch}`,\n ' ^^ HUMAN-ONLY. `git merge` is blocked for AI (redirect-how-to-merge-main). AI: ask the',\n ' human to run step 3, and warn them it is a raw merge — only correct here because the',\n ' branch is already broken. For a normal update from main they should push back and',\n ' tell you to use the gated 3-point merge instead: `pnpm wp-start-update` (paired with',\n ' `pnpm wp-finish-update`) when no PR is open, or `pnpm wp-start-upsert-pr` (paired with',\n ' `pnpm wp-finish-upsert-pr`) when a PR IS open — a PR MUST use the upsert-pr pair.',\n `4. Commit the squash: git add -A && git commit -m \"Squashed from ${currentBranch}\"`,\n '5. If a PR exists: open a NEW PR for the -v2 branch and close the old one.',\n ];\n }\n\n /**\n * Synchronously stamp a clean \"up to date with main\" status — call right after a successful merge.\n *\n * This is the one writer that takes NO lock, and it knows about exactly one branch. It therefore\n * goes through writeMainSyncStatus, which MERGES its entry into the existing map; writing a whole\n * document from here would wipe every other worktree's entry.\n */\n stampCleanMainSyncStatus(repoRoot: string): void {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const branch = this.gitBranch(repoRoot);\n const originMain = this.capture(repoRoot, 'git', ['rev-parse', 'origin/main']);\n const featureHead = this.capture(repoRoot, 'git', ['rev-parse', 'HEAD']);\n if (!originMain.ok || !featureHead.ok) return;\n const status = new MainSyncStatus(\n branch, false, '', true, originMain.out, originMain.out, featureHead.out, false, [], new Date().toISOString(),\n );\n status.localMain = this.localMainHash(repoRoot);\n status.commitsBehind = 0;\n this.writeMainSyncStatus(repoRoot, status);\n } catch (err: unknown) {\n const error = toError(err);\n void error;\n }\n }\n\n /**\n * Refresh `origin/main` WITHOUT writing `.git/FETCH_HEAD`.\n *\n * WHY the flag is not optional: this runs in a DETACHED background process while the agent is\n * running its own foreground `git fetch` / `git pull` in the very same repo. `.git/FETCH_HEAD` is\n * a single file that git takes no lock on, so two overlapping fetches interleave their writes and\n * can leave the SAME `for-merge` line twice. `git pull` then reads two for-merge entries and dies\n * with `fatal: Cannot fast-forward to multiple branches` — wedging the exact command the\n * read-stale-guard tells the agent to run. `--no-write-fetch-head` (git >= 2.29) still updates the\n * remote-tracking ref, which is all anything downstream reads (`origin/main`, merge-base), so\n * nothing here needs FETCH_HEAD written at all.\n *\n * Older git rejects the flag; only then do we retry the plain form, accepting the old behaviour\n * rather than losing the refresh entirely on a pre-2020 git.\n */\n private fetchOriginMain(repoRoot: string): void {\n const safe = spawnSync('git', ['fetch', '--no-write-fetch-head', 'origin', 'main'], {\n cwd: repoRoot, encoding: 'utf8', stdio: ['ignore', 'ignore', 'pipe'], timeout: CommandTimeouts.NETWORK_MS,\n });\n if (safe.status === 0) return;\n if (!this.isUnknownGitOption(safe.stderr)) return; // a real failure (offline, auth) — not our business\n spawnSync('git', ['fetch', 'origin', 'main'], { cwd: repoRoot, stdio: 'ignore', timeout: CommandTimeouts.NETWORK_MS });\n }\n\n // Did git reject the flag itself (too old), as opposed to failing the network refresh?\n private isUnknownGitOption(stderr: string | null): boolean {\n const text = (stderr ?? '').toLowerCase();\n return text.includes('unknown option') || text.includes('unknown switch') || text.includes('unrecognized option');\n }\n\n // Run a command capturing trimmed stdout; ok=false on spawn failure or non-zero exit.\n private capture(repoRoot: string, cmd: string, args: string[]): CmdCapture {\n const result = spawnSync(cmd, args, { cwd: repoRoot, encoding: 'utf8', timeout: this.timeouts.forCommand(cmd) });\n if (result.status !== 0 || typeof result.stdout !== 'string') return { ok: false, out: '' };\n return { ok: true, out: result.stdout.trim() };\n }\n\n // The actual checked-out branch in repoRoot — cwd-correct so the cache's `branch` label matches.\n private gitBranch(repoRoot: string): string {\n const result = this.capture(repoRoot, 'git', ['rev-parse', '--abbrev-ref', 'HEAD']);\n return result.ok ? result.out : '';\n }\n\n // The LOCAL main hash. `refs/heads/main` (not the bare name) so it can never resolve to a remote\n // ref or a tag. '' when main does not exist locally — which the guard treats as fail-open.\n private localMainHash(repoRoot: string): string {\n const result = this.capture(repoRoot, 'git', ['rev-parse', 'refs/heads/main']);\n return result.ok ? result.out : '';\n }\n\n // Slow-path only: the detached refresher calls this after fetching. Guards never count commits.\n private commitDistance(repoRoot: string, localMain: string, originMain: string): number | null {\n if (localMain === '' || originMain === '') return null;\n const result = this.capture(repoRoot, 'git', ['rev-list', '--count', `${localMain}..${originMain}`]);\n if (!result.ok || !/^\\d+$/.test(result.out)) return null;\n return Number(result.out);\n }\n\n private changedFiles(repoRoot: string, base: string, head: string): string[] {\n const result = this.capture(repoRoot, 'git', ['diff', '--name-only', base, head]);\n if (!result.ok || result.out === '') return [];\n return result.out.split('\\n').map((line: string): string => line.trim()).filter((line: string): boolean => line.length > 0);\n }\n\n // Every file this feature branch has touched since the fork point — committed AND still in the\n // working tree (staged / unstaged / untracked), so a conflict is visible WHILE editing.\n private featureChangedFiles(repoRoot: string, forkPoint: string): string[] {\n const out = new Set<string>();\n const add = (args: string[]): void => {\n const r = this.capture(repoRoot, 'git', args);\n if (!r.ok || r.out === '') return;\n for (const line of r.out.split('\\n')) {\n const f = line.trim();\n if (f.length > 0) out.add(f);\n }\n };\n add(['diff', '--name-only', forkPoint, 'HEAD']); // committed since the fork point\n add(['diff', '--name-only', 'HEAD']); // unstaged working-tree edits\n add(['diff', '--name-only', '--cached', 'HEAD']); // staged edits\n add(['ls-files', '--others', '--exclude-standard']); // untracked new files (respects .gitignore)\n return [...out];\n }\n\n /**\n * ONE repo-wide `gh pr list` giving every branch's merged/open PR — replaces the two per-branch\n * `--head <branch>` queries. `--limit 200` bounds it; a branch whose only PR is older than that\n * falls out of the index and reads as \"no PR\", which is the fail-OPEN direction (no merged PR =\n * no block), the same way a missing `gh` or an offline machine already degraded.\n */\n private loadPullRequestIndex(repoRoot: string): PullRequestIndex {\n const result = this.capture(repoRoot, 'gh', [\n 'pr', 'list', '--state', 'all', '--json', 'number,headRefName,state', '--limit', '200',\n ]);\n // `gh` could not be run, or exited non-zero (missing, unauthenticated, rate-limited, offline).\n // An EMPTY index is the same fail-open answer as before — but it is now labelled UNREACHABLE, so\n // the guards can report an abstention rather than an approval. Empty STDOUT on a zero exit is a\n // real answer (\"this repo has no PRs\"), so only the failure path clears the flag.\n if (!result.ok) return new PullRequestIndex({}, {}, false);\n if (result.out === '') return new PullRequestIndex({}, {}, true);\n return this.store.indexPullRequests(result.out);\n }\n\n /**\n * A `main` entry synthesised WITHOUT a worktree standing on main. Carries only the two hashes\n * read-stale-guard / stale-main-bash-guard actually compare (originMain vs the live tree's\n * ancestry) — never merged, never conflicting, so it can only ever produce the stale-main verdict.\n */\n private mainOnlyStatus(repoRoot: string): MainSyncStatus {\n const originMain = this.capture(repoRoot, 'git', ['rev-parse', 'origin/main']);\n const localMain = this.localMainHash(repoRoot);\n const status = new MainSyncStatus(\n 'main', false, '', true, localMain === '' ? null : localMain,\n originMain.ok ? originMain.out : '', localMain, false, [], new Date().toISOString(),\n );\n status.localMain = localMain;\n status.commitsBehind = this.commitDistance(repoRoot, localMain, originMain.ok ? originMain.out : '');\n return status;\n }\n\n // A benign status that never blocks — used when origin/main can't be resolved.\n private benignStatus(branch: string, featureHead: string): MainSyncStatus {\n return new MainSyncStatus(branch, false, '', true, null, '', featureHead, false, [], new Date().toISOString());\n }\n}\n\n// Temporary migration delegators to MainSyncStatusService — removed once consumers inject it.\nconst mainSyncSvc = new MainSyncStatusService();\n\n// webpieces-disable no-function-outside-class -- temporary back-compat delegator to MainSyncStatusService; removed once consumers inject it\nexport function mainSyncStatusPath(repoRoot: string): string { return mainSyncSvc.mainSyncStatusPath(repoRoot); }\n// webpieces-disable no-function-outside-class -- temporary back-compat delegator to MainSyncStatusService; removed once consumers inject it\nexport function readMainSyncStatus(repoRoot: string, branch: string): MainSyncStatus | null { return mainSyncSvc.readMainSyncStatus(repoRoot, branch); }\n// webpieces-disable no-function-outside-class -- temporary back-compat delegator to MainSyncStatusService; removed once consumers inject it\nexport function readMainSyncStatusFile(repoRoot: string): MainSyncStatusFile | null { return mainSyncSvc.readMainSyncStatusFile(repoRoot); }\n// webpieces-disable no-function-outside-class -- temporary back-compat delegator to MainSyncStatusService; removed once consumers inject it\nexport function writeMainSyncStatus(repoRoot: string, status: MainSyncStatus): void { mainSyncSvc.writeMainSyncStatus(repoRoot, status); }\n// webpieces-disable no-function-outside-class -- temporary back-compat delegator to MainSyncStatusService; removed once consumers inject it\nexport function writeMainSyncStatusFile(repoRoot: string, file: MainSyncStatusFile): void { mainSyncSvc.writeMainSyncStatusFile(repoRoot, file); }\n// webpieces-disable no-function-outside-class -- temporary back-compat delegator to MainSyncStatusService; removed once consumers inject it\nexport function computeMainSyncStatus(repoRoot: string): MainSyncStatus { return mainSyncSvc.computeMainSyncStatus(repoRoot); }\n// webpieces-disable no-function-outside-class -- temporary back-compat delegator to MainSyncStatusService; removed once consumers inject it\nexport function computeAllMainSyncStatuses(repoRoot: string): MainSyncStatusFile { return mainSyncSvc.computeAllMainSyncStatuses(repoRoot); }\n// webpieces-disable no-function-outside-class -- temporary back-compat delegator to MainSyncStatusService; removed once consumers inject it\nexport function squashRecoverySteps(currentBranch: string): string[] { return mainSyncSvc.squashRecoverySteps(currentBranch); }\n// webpieces-disable no-function-outside-class -- temporary back-compat delegator to MainSyncStatusService; removed once consumers inject it\nexport function stampCleanMainSyncStatus(repoRoot: string): void { mainSyncSvc.stampCleanMainSyncStatus(repoRoot); }\n"]}
@@ -1,6 +1,7 @@
1
1
  import { AtomicFile } from '@webpieces/tooling-common';
2
2
  import { DotWebpieces } from '@webpieces/tooling-common';
3
3
  import { WorktreeService } from './worktrees';
4
+ import { CommandTimeouts } from './command-timeouts';
4
5
  import { WorktreeLockVerdicts } from './worktree-lock-verdicts';
5
6
  import { CacheFreshness, MergedBranchesCache } from './merged-branch-verdicts';
6
7
  export { MergedBranch, DeletableBranch, DeletableWorktree, MergedBranchesCache, CacheFreshness, CACHE_STALE_AFTER_MS, CLASSIFICATION_MERGED_PR, CLASSIFICATION_BACKUP_OF_MERGED, CLASSIFICATION_BACKUP_OF_LIVE, CLASSIFICATION_NO_COMMITS, CLASSIFICATION_SUPERSEDED, CLASSIFICATION_CONTENT_IN_MAIN, CLASSIFICATION_NEVER_PROPOSED, CLASSIFICATION_IN_USE, CLASSIFICATION_PRUNABLE, CLASSIFICATION_LOCKED, CLASSIFICATION_CURRENT, CLASSIFICATION_DETACHED, ADJUDICATED_CLASSIFICATIONS, } from './merged-branch-verdicts';
@@ -9,7 +10,8 @@ export declare class MergedBranchesService {
9
10
  private readonly dotDir;
10
11
  private readonly atomicFile;
11
12
  private readonly locks;
12
- constructor(worktrees?: WorktreeService, dotDir?: DotWebpieces, atomicFile?: AtomicFile, locks?: WorktreeLockVerdicts);
13
+ private readonly timeouts;
14
+ constructor(worktrees?: WorktreeService, dotDir?: DotWebpieces, atomicFile?: AtomicFile, locks?: WorktreeLockVerdicts, timeouts?: CommandTimeouts);
13
15
  /**
14
16
  * SHARED scope — one per repo, in the primary clone, NOT one per worktree. This file's contents
15
17
  * describe every branch AND every worktree in the repo, so a per-worktree copy was a repo-wide fact
@@ -9,6 +9,7 @@ const tooling_common_1 = require("@webpieces/tooling-common");
9
9
  const tooling_common_2 = require("@webpieces/tooling-common");
10
10
  const to_error_1 = require("@webpieces/tooling-common/to-error");
11
11
  const worktrees_1 = require("./worktrees");
12
+ const command_timeouts_1 = require("./command-timeouts");
12
13
  const worktree_lock_verdicts_1 = require("./worktree-lock-verdicts");
13
14
  const merged_branch_verdicts_1 = require("./merged-branch-verdicts");
14
15
  var merged_branch_verdicts_2 = require("./merged-branch-verdicts");
@@ -99,13 +100,15 @@ let MergedBranchesService = class MergedBranchesService {
99
100
  dotDir;
100
101
  atomicFile;
101
102
  locks;
103
+ timeouts;
102
104
  // Defaulted so the non-DI call sites (`new MergedBranchesService()` in the guard and the detached
103
105
  // refresher) keep working, while inversify still injects the singleton when resolved from a container.
104
- constructor(worktrees = new worktrees_1.WorktreeService(), dotDir = tooling_common_2.dotWebpieces, atomicFile = new tooling_common_1.AtomicFile(), locks = new worktree_lock_verdicts_1.WorktreeLockVerdicts()) {
106
+ constructor(worktrees = new worktrees_1.WorktreeService(), dotDir = tooling_common_2.dotWebpieces, atomicFile = new tooling_common_1.AtomicFile(), locks = new worktree_lock_verdicts_1.WorktreeLockVerdicts(), timeouts = new command_timeouts_1.CommandTimeouts()) {
105
107
  this.worktrees = worktrees;
106
108
  this.dotDir = dotDir;
107
109
  this.atomicFile = atomicFile;
108
110
  this.locks = locks;
111
+ this.timeouts = timeouts;
109
112
  }
110
113
  /**
111
114
  * SHARED scope — one per repo, in the primary clone, NOT one per worktree. This file's contents
@@ -504,7 +507,7 @@ let MergedBranchesService = class MergedBranchesService {
504
507
  }
505
508
  // Run a command capturing trimmed stdout; ok=false on spawn failure or non-zero exit.
506
509
  capture(repoRoot, cmd, args) {
507
- const result = (0, child_process_1.spawnSync)(cmd, args, { cwd: repoRoot, encoding: 'utf8' });
510
+ const result = (0, child_process_1.spawnSync)(cmd, args, { cwd: repoRoot, encoding: 'utf8', timeout: this.timeouts.forCommand(cmd) });
508
511
  if (result.status !== 0 || typeof result.stdout !== 'string')
509
512
  return { ok: false, out: '' };
510
513
  return { ok: true, out: result.stdout.trim() };
@@ -516,6 +519,7 @@ exports.MergedBranchesService = MergedBranchesService = tslib_1.__decorate([
516
519
  tslib_1.__metadata("design:paramtypes", [worktrees_1.WorktreeService,
517
520
  tooling_common_2.DotWebpieces,
518
521
  tooling_common_1.AtomicFile,
519
- worktree_lock_verdicts_1.WorktreeLockVerdicts])
522
+ worktree_lock_verdicts_1.WorktreeLockVerdicts,
523
+ command_timeouts_1.CommandTimeouts])
520
524
  ], MergedBranchesService);
521
525
  //# sourceMappingURL=merged-branches.js.map