@webpieces/rules-config 0.3.288 → 0.3.290

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webpieces/rules-config",
3
- "version": "0.3.288",
3
+ "version": "0.3.290",
4
4
  "description": "Shared webpieces.config.json loader. Single source of truth for validation rule configuration consumed by @webpieces/ai-hook-rules, @webpieces/code-rules, and @webpieces/nx-webpieces-rules.",
5
5
  "type": "commonjs",
6
6
  "main": "./src/index.js",
@@ -10,6 +10,7 @@ export declare class MainSyncStatus {
10
10
  conflict: boolean;
11
11
  conflictFiles: string[];
12
12
  timestamp: string;
13
+ openPr: string;
13
14
  constructor(branch: string, branchAlreadyMerged: boolean, mergedPr: string, hasForkPoint: boolean, forkPoint: string | null, originMain: string, featureHead: string, conflict: boolean, conflictFiles: string[], timestamp: string);
14
15
  }
15
16
  export declare class MainSyncLock {
@@ -47,6 +47,12 @@ class MainSyncStatus {
47
47
  conflict;
48
48
  conflictFiles;
49
49
  timestamp;
50
+ // An OPEN (not merged) PR tracking this branch, if any — '' = none or not-yet-known. Set by the
51
+ // refresher (best-effort) and read by the feature-branch-guard so a mid-work conflict block can
52
+ // steer straight to the PR flow instead of the update-only flow (which would just fail-fast when a
53
+ // PR exists). Advisory only — the authoritative gate is wp-update-start's own fail-fast check.
54
+ // Kept OUT of the positional constructor (a defaulted field) so existing call sites don't churn.
55
+ openPr = '';
50
56
  constructor(branch, branchAlreadyMerged, mergedPr, hasForkPoint, forkPoint, originMain, featureHead, conflict, conflictFiles, timestamp) {
51
57
  this.branch = branch;
52
58
  this.branchAlreadyMerged = branchAlreadyMerged;
@@ -94,7 +100,9 @@ function readMainSyncStatus(repoRoot) {
94
100
  if (!fs.existsSync(statusPath))
95
101
  return null;
96
102
  const raw = JSON.parse(fs.readFileSync(statusPath, 'utf8'));
97
- return new MainSyncStatus(raw.branch ?? '', raw.branchAlreadyMerged ?? false, raw.mergedPr ?? '', raw.hasForkPoint ?? true, raw.forkPoint ?? null, raw.originMain ?? '', raw.featureHead ?? '', raw.conflict ?? false, raw.conflictFiles ?? [], raw.timestamp ?? '');
103
+ const status = new MainSyncStatus(raw.branch ?? '', raw.branchAlreadyMerged ?? false, raw.mergedPr ?? '', raw.hasForkPoint ?? true, raw.forkPoint ?? null, raw.originMain ?? '', raw.featureHead ?? '', raw.conflict ?? false, raw.conflictFiles ?? [], raw.timestamp ?? '');
104
+ status.openPr = raw.openPr ?? '';
105
+ return status;
98
106
  }
99
107
  catch (err) {
100
108
  const error = (0, to_error_1.toError)(err);
@@ -220,6 +228,15 @@ function detectMergedPr(repoRoot, branch) {
220
228
  const result = capture(repoRoot, 'gh', ['pr', 'list', '--head', branch, '--state', 'merged', '--json', 'number', '--jq', '.[0].number']);
221
229
  return result.ok ? result.out : '';
222
230
  }
231
+ // An OPEN PR tracking this branch, if any. Best-effort — this is ADVISORY (it only lets the guard's
232
+ // conflict block steer to the PR flow early), so an unreachable gh degrades to '' here. The HARD gate
233
+ // that must never guess is wp-update-start's own openPrForBranch, which fails fast instead.
234
+ function detectOpenPr(repoRoot, branch) {
235
+ if (!branch || branch === 'main')
236
+ return '';
237
+ const result = capture(repoRoot, 'gh', ['pr', 'list', '--head', branch, '--state', 'open', '--json', 'number', '--jq', '.[0].number']);
238
+ return result.ok ? result.out : '';
239
+ }
223
240
  // A benign status that never blocks — used when origin/main can't be resolved (no remote yet,
224
241
  // offline before first fetch). hasForkPoint=true + conflict=false so the guard allows the edit.
225
242
  function benignStatus(branch, featureHead) {
@@ -235,6 +252,8 @@ function benignStatus(branch, featureHead) {
235
252
  function computeMainSyncStatus(repoRoot) {
236
253
  const branch = gitBranch(repoRoot);
237
254
  const mergedPr = detectMergedPr(repoRoot, branch);
255
+ // Advisory: lets the guard's conflict block steer to the PR flow early when a PR is already open.
256
+ const openPr = detectOpenPr(repoRoot, branch);
238
257
  // Best-effort network refresh; offline just means we evaluate against the last-fetched ref.
239
258
  (0, child_process_1.spawnSync)('git', ['fetch', 'origin', 'main'], { cwd: repoRoot, stdio: 'ignore' });
240
259
  const head = capture(repoRoot, 'git', ['rev-parse', 'HEAD']);
@@ -244,17 +263,22 @@ function computeMainSyncStatus(repoRoot) {
244
263
  const status = benignStatus(branch, featureHead);
245
264
  status.branchAlreadyMerged = mergedPr !== '';
246
265
  status.mergedPr = mergedPr;
266
+ status.openPr = openPr;
247
267
  return status;
248
268
  }
249
269
  const forkPoint = capture(repoRoot, 'git', ['merge-base', 'origin/main', 'HEAD']);
250
270
  if (!forkPoint.ok || forkPoint.out === '') {
251
271
  // No common ancestor — main was merged into the branch. Force the human to squash.
252
- return new MainSyncStatus(branch, mergedPr !== '', mergedPr, false, null, originMain.out, featureHead, false, [], new Date().toISOString());
272
+ const noFork = new MainSyncStatus(branch, mergedPr !== '', mergedPr, false, null, originMain.out, featureHead, false, [], new Date().toISOString());
273
+ noFork.openPr = openPr;
274
+ return noFork;
253
275
  }
254
276
  const featureFiles = new Set(featureChangedFiles(repoRoot, forkPoint.out));
255
277
  const mainFiles = changedFiles(repoRoot, forkPoint.out, 'origin/main');
256
278
  const conflictFiles = mainFiles.filter((file) => featureFiles.has(file));
257
- return new MainSyncStatus(branch, mergedPr !== '', mergedPr, true, forkPoint.out, originMain.out, featureHead, conflictFiles.length > 0, conflictFiles, new Date().toISOString());
279
+ const status = new MainSyncStatus(branch, mergedPr !== '', mergedPr, true, forkPoint.out, originMain.out, featureHead, conflictFiles.length > 0, conflictFiles, new Date().toISOString());
280
+ status.openPr = openPr;
281
+ return status;
258
282
  }
259
283
  // The recovery steps when there is no fork point with origin/main (someone merged main into the
260
284
  // branch, so a clean squash-merge is impossible). Shared so the feature-branch-guard and pr-gate's
@@ -1 +1 @@
1
- {"version":3,"file":"main-sync-status.js","sourceRoot":"","sources":["../../../../../packages/tooling/rules-config/src/main-sync-status.ts"],"names":[],"mappings":";;;AA2GA,gDAEC;AAED,4CAEC;AAQD,gDAuBC;AAED,kDAIC;AAED,4CAYC;AAED,8CAIC;AAID,kCAEC;AAsBD,kDAMC;AAED,sCAEC;AAED,oCAEC;AAkED,sDAuCC;AAMD,kDASC;AAKD,4DAeC;;AAhWD,iDAA0C;AAC1C,+CAAyB;AACzB,mDAA6B;AAE7B,2CAAgD;AAChD,yCAAqC;AAErC,oGAAoG;AACpG,qGAAqG;AACrG,2FAA2F;AAC3F,oGAAoG;AACpG,mGAAmG;AACnG,yDAAyD;AAEzD,mGAAmG;AACnG,6FAA6F;AAChF,QAAA,4BAA4B,GAAG,CAAC,CAAC;AAE9C,MAAM,qBAAqB,GAAG,uBAAuB,CAAC;AACtD,MAAM,mBAAmB,GAAG,qBAAqB,CAAC;AAElD,MAAM,oBAAoB,GAAG,WAAW,CAAC;AACzC,MAAM,mBAAmB,GAAG,UAAU,CAAC;AAEvC,kGAAkG;AAClG,mGAAmG;AACnG,kGAAkG;AAClG,MAAa,cAAc;IACvB,MAAM,CAAS;IACf,mBAAmB,CAAU;IAC7B,QAAQ,CAAS;IACjB,YAAY,CAAU;IACtB,SAAS,CAAgB;IACzB,UAAU,CAAS;IACnB,WAAW,CAAS;IACpB,QAAQ,CAAU;IAClB,aAAa,CAAW;IACxB,SAAS,CAAS;IAElB,YACI,MAAc,EACd,mBAA4B,EAC5B,QAAgB,EAChB,YAAqB,EACrB,SAAwB,EACxB,UAAkB,EAClB,WAAmB,EACnB,QAAiB,EACjB,aAAuB,EACvB,SAAiB;QAEjB,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,IAAI,CAAC,mBAAmB,GAAG,mBAAmB,CAAC;QAC/C,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;QACzB,IAAI,CAAC,YAAY,GAAG,YAAY,CAAC;QACjC,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;QAC3B,IAAI,CAAC,UAAU,GAAG,UAAU,CAAC;QAC7B,IAAI,CAAC,WAAW,GAAG,WAAW,CAAC;QAC/B,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;QACzB,IAAI,CAAC,aAAa,GAAG,aAAa,CAAC;QACnC,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;IAC/B,CAAC;CACJ;AAnCD,wCAmCC;AAED,kGAAkG;AAClG,iGAAiG;AACjG,iGAAiG;AACjG,gGAAgG;AAChG,MAAa,YAAY;IACrB,KAAK,CAAS;IACd,OAAO,CAAS;IAChB,GAAG,CAAS;IAEZ,YAAY,KAAa,EAAE,OAAe,EAAE,MAAc,CAAC;QACvD,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;QACnB,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;QACvB,IAAI,CAAC,GAAG,GAAG,GAAG,CAAC;IACnB,CAAC;CACJ;AAVD,oCAUC;AA6BD,SAAgB,kBAAkB,CAAC,QAAgB;IAC/C,OAAO,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,6BAAiB,EAAE,qBAAqB,CAAC,CAAC;AACzE,CAAC;AAED,SAAgB,gBAAgB,CAAC,QAAgB;IAC7C,OAAO,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,6BAAiB,EAAE,mBAAmB,CAAC,CAAC;AACvE,CAAC;AAED,SAAS,SAAS,CAAC,QAAgB;IAC/B,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;AAC9D,CAAC;AAED,4FAA4F;AAC5F,yDAAyD;AACzD,SAAgB,kBAAkB,CAAC,QAAgB;IAC/C,8DAA8D;IAC9D,IAAI,CAAC;QACD,MAAM,UAAU,GAAG,kBAAkB,CAAC,QAAQ,CAAC,CAAC;QAChD,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,UAAU,CAAC;YAAE,OAAO,IAAI,CAAC;QAC5C,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,UAAU,EAAE,MAAM,CAAC,CAAc,CAAC;QACzE,OAAO,IAAI,cAAc,CACrB,GAAG,CAAC,MAAM,IAAI,EAAE,EAChB,GAAG,CAAC,mBAAmB,IAAI,KAAK,EAChC,GAAG,CAAC,QAAQ,IAAI,EAAE,EAClB,GAAG,CAAC,YAAY,IAAI,IAAI,EACxB,GAAG,CAAC,SAAS,IAAI,IAAI,EACrB,GAAG,CAAC,UAAU,IAAI,EAAE,EACpB,GAAG,CAAC,WAAW,IAAI,EAAE,EACrB,GAAG,CAAC,QAAQ,IAAI,KAAK,EACrB,GAAG,CAAC,aAAa,IAAI,EAAE,EACvB,GAAG,CAAC,SAAS,IAAI,EAAE,CACtB,CAAC;IACN,CAAC;IAAC,OAAO,GAAY,EAAE,CAAC;QACpB,MAAM,KAAK,GAAG,IAAA,kBAAO,EAAC,GAAG,CAAC,CAAC;QAC3B,KAAK,KAAK,CAAC;QACX,OAAO,IAAI,CAAC;IAChB,CAAC;AACL,CAAC;AAED,SAAgB,mBAAmB,CAAC,QAAgB,EAAE,MAAsB;IACxE,MAAM,UAAU,GAAG,kBAAkB,CAAC,QAAQ,CAAC,CAAC;IAChD,SAAS,CAAC,UAAU,CAAC,CAAC;IACtB,EAAE,CAAC,aAAa,CAAC,UAAU,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;AACzE,CAAC;AAED,SAAgB,gBAAgB,CAAC,QAAgB;IAC7C,8DAA8D;IAC9D,IAAI,CAAC;QACD,MAAM,QAAQ,GAAG,gBAAgB,CAAC,QAAQ,CAAC,CAAC;QAC5C,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC;YAAE,OAAO,IAAI,CAAC;QAC1C,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAY,CAAC;QACrE,OAAO,IAAI,YAAY,CAAC,GAAG,CAAC,KAAK,IAAI,mBAAmB,EAAE,GAAG,CAAC,OAAO,IAAI,CAAC,EAAE,GAAG,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC;IAC9F,CAAC;IAAC,OAAO,GAAY,EAAE,CAAC;QACpB,MAAM,KAAK,GAAG,IAAA,kBAAO,EAAC,GAAG,CAAC,CAAC;QAC3B,KAAK,KAAK,CAAC;QACX,OAAO,IAAI,CAAC;IAChB,CAAC;AACL,CAAC;AAED,SAAgB,iBAAiB,CAAC,QAAgB,EAAE,IAAkB;IAClE,MAAM,QAAQ,GAAG,gBAAgB,CAAC,QAAQ,CAAC,CAAC;IAC5C,SAAS,CAAC,QAAQ,CAAC,CAAC;IACpB,EAAE,CAAC,aAAa,CAAC,QAAQ,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;AACrE,CAAC;AAED,iGAAiG;AACjG,6EAA6E;AAC7E,SAAgB,WAAW,CAAC,IAAkB,EAAE,kBAA0B,EAAE,MAAc,IAAI,CAAC,GAAG,EAAE;IAChG,OAAO,GAAG,GAAG,IAAI,CAAC,OAAO,GAAG,kBAAkB,GAAG,EAAE,GAAG,IAAI,CAAC;AAC/D,CAAC;AAED,oGAAoG;AACpG,mGAAmG;AACnG,qGAAqG;AACrG,SAAS,cAAc,CAAC,GAAW;IAC/B,IAAI,GAAG,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC;IAC1B,8DAA8D;IAC9D,IAAI,CAAC;QACD,OAAO,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;QACrB,OAAO,IAAI,CAAC;IAChB,CAAC;IAAC,OAAO,GAAY,EAAE,CAAC;QACpB,MAAM,KAAK,GAAG,IAAA,kBAAO,EAAC,GAAG,CAAC,CAAC;QAC3B,yFAAyF;QACzF,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;IAC5C,CAAC;AACL,CAAC;AAED,mGAAmG;AACnG,mGAAmG;AACnG,+FAA+F;AAC/F,6DAA6D;AAC7D,SAAgB,mBAAmB,CAAC,QAAgB,EAAE,kBAA0B,EAAE,MAAc,IAAI,CAAC,GAAG,EAAE;IACtG,MAAM,IAAI,GAAG,gBAAgB,CAAC,QAAQ,CAAC,CAAC;IACxC,IAAI,CAAC,IAAI;QAAE,OAAO,KAAK,CAAC;IACxB,IAAI,IAAI,CAAC,KAAK,KAAK,oBAAoB;QAAE,OAAO,KAAK,CAAC;IACtD,IAAI,WAAW,CAAC,IAAI,EAAE,kBAAkB,EAAE,GAAG,CAAC;QAAE,OAAO,KAAK,CAAC;IAC7D,OAAO,cAAc,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AACpC,CAAC;AAED,SAAgB,aAAa,CAAC,MAAc,IAAI,CAAC,GAAG,EAAE,EAAE,MAAc,OAAO,CAAC,GAAG;IAC7E,OAAO,IAAI,YAAY,CAAC,oBAAoB,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC;AAC5D,CAAC;AAED,SAAgB,YAAY,CAAC,OAAe;IACxC,OAAO,IAAI,YAAY,CAAC,mBAAmB,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC;AAC7D,CAAC;AAED,sFAAsF;AACtF,SAAS,OAAO,CAAC,QAAgB,EAAE,GAAW,EAAE,IAAc;IAC1D,MAAM,MAAM,GAAG,IAAA,yBAAS,EAAC,GAAG,EAAE,IAAI,EAAE,EAAE,GAAG,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,CAAC;IACzE,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,IAAI,OAAO,MAAM,CAAC,MAAM,KAAK,QAAQ;QAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE,EAAE,CAAC;IAC5F,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,GAAG,EAAE,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,EAAE,CAAC;AACnD,CAAC;AAED,+FAA+F;AAC/F,qGAAqG;AACrG,SAAS,SAAS,CAAC,QAAgB;IAC/B,MAAM,MAAM,GAAG,OAAO,CAAC,QAAQ,EAAE,KAAK,EAAE,CAAC,WAAW,EAAE,cAAc,EAAE,MAAM,CAAC,CAAC,CAAC;IAC/E,OAAO,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;AACvC,CAAC;AAED,SAAS,YAAY,CAAC,QAAgB,EAAE,IAAY,EAAE,IAAY;IAC9D,MAAM,MAAM,GAAG,OAAO,CAAC,QAAQ,EAAE,KAAK,EAAE,CAAC,MAAM,EAAE,aAAa,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC;IAC7E,IAAI,CAAC,MAAM,CAAC,EAAE,IAAI,MAAM,CAAC,GAAG,KAAK,EAAE;QAAE,OAAO,EAAE,CAAC;IAC/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;AAChI,CAAC;AAED,+FAA+F;AAC/F,kGAAkG;AAClG,qGAAqG;AACrG,6FAA6F;AAC7F,sGAAsG;AACtG,uGAAuG;AACvG,SAAS,mBAAmB,CAAC,QAAgB,EAAE,SAAiB;IAC5D,MAAM,GAAG,GAAG,IAAI,GAAG,EAAU,CAAC;IAC9B,MAAM,GAAG,GAAG,CAAC,IAAc,EAAQ,EAAE;QACjC,MAAM,CAAC,GAAG,OAAO,CAAC,QAAQ,EAAE,KAAK,EAAE,IAAI,CAAC,CAAC;QACzC,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,GAAG,KAAK,EAAE;YAAE,OAAO;QAClC,KAAK,MAAM,IAAI,IAAI,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;YACnC,MAAM,CAAC,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;YACtB,IAAI,CAAC,CAAC,MAAM,GAAG,CAAC;gBAAE,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;QACjC,CAAC;IACL,CAAC,CAAC;IACF,GAAG,CAAC,CAAC,MAAM,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,CAAC,CAAC,CAAC,CAAM,iCAAiC;IACvF,GAAG,CAAC,CAAC,MAAM,EAAE,aAAa,EAAE,MAAM,CAAC,CAAC,CAAC,CAAiB,8BAA8B;IACpF,GAAG,CAAC,CAAC,MAAM,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,CAAC,CAAC,CAAC,CAAK,eAAe;IACrE,GAAG,CAAC,CAAC,UAAU,EAAE,UAAU,EAAE,oBAAoB,CAAC,CAAC,CAAC,CAAE,4CAA4C;IAClG,OAAO,CAAC,GAAG,GAAG,CAAC,CAAC;AACpB,CAAC;AAED,qGAAqG;AACrG,4FAA4F;AAC5F,SAAS,cAAc,CAAC,QAAgB,EAAE,MAAc;IACpD,IAAI,CAAC,MAAM,IAAI,MAAM,KAAK,MAAM;QAAE,OAAO,EAAE,CAAC;IAC5C,MAAM,MAAM,GAAG,OAAO,CAAC,QAAQ,EAAE,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,EAAE,aAAa,CAAC,CAAC,CAAC;IACzI,OAAO,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;AACvC,CAAC;AAED,8FAA8F;AAC9F,gGAAgG;AAChG,SAAS,YAAY,CAAC,MAAc,EAAE,WAAmB;IACrD,OAAO,IAAI,cAAc,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;AACnH,CAAC;AAED;;;;;;GAMG;AACH,SAAgB,qBAAqB,CAAC,QAAgB;IAClD,MAAM,MAAM,GAAG,SAAS,CAAC,QAAQ,CAAC,CAAC;IACnC,MAAM,QAAQ,GAAG,cAAc,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;IAElD,4FAA4F;IAC5F,IAAA,yBAAS,EAAC,KAAK,EAAE,CAAC,OAAO,EAAE,QAAQ,EAAE,MAAM,CAAC,EAAE,EAAE,GAAG,EAAE,QAAQ,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC,CAAC;IAElF,MAAM,IAAI,GAAG,OAAO,CAAC,QAAQ,EAAE,KAAK,EAAE,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC,CAAC;IAC7D,MAAM,UAAU,GAAG,OAAO,CAAC,QAAQ,EAAE,KAAK,EAAE,CAAC,WAAW,EAAE,aAAa,CAAC,CAAC,CAAC;IAC1E,MAAM,WAAW,GAAG,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;IAC5C,IAAI,CAAC,IAAI,CAAC,EAAE,IAAI,CAAC,UAAU,CAAC,EAAE,EAAE,CAAC;QAC7B,MAAM,MAAM,GAAG,YAAY,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;QACjD,MAAM,CAAC,mBAAmB,GAAG,QAAQ,KAAK,EAAE,CAAC;QAC7C,MAAM,CAAC,QAAQ,GAAG,QAAQ,CAAC;QAC3B,OAAO,MAAM,CAAC;IAClB,CAAC;IAED,MAAM,SAAS,GAAG,OAAO,CAAC,QAAQ,EAAE,KAAK,EAAE,CAAC,YAAY,EAAE,aAAa,EAAE,MAAM,CAAC,CAAC,CAAC;IAClF,IAAI,CAAC,SAAS,CAAC,EAAE,IAAI,SAAS,CAAC,GAAG,KAAK,EAAE,EAAE,CAAC;QACxC,mFAAmF;QACnF,OAAO,IAAI,cAAc,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;IAChJ,CAAC;IAED,MAAM,YAAY,GAAG,IAAI,GAAG,CAAC,mBAAmB,CAAC,QAAQ,EAAE,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC;IAC3E,MAAM,SAAS,GAAG,YAAY,CAAC,QAAQ,EAAE,SAAS,CAAC,GAAG,EAAE,aAAa,CAAC,CAAC;IACvE,MAAM,aAAa,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC,IAAY,EAAW,EAAE,CAAC,YAAY,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC;IAE1F,OAAO,IAAI,cAAc,CACrB,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;AACN,CAAC;AAED,gGAAgG;AAChG,mGAAmG;AACnG,sGAAsG;AACtG,mEAAmE;AACnE,SAAgB,mBAAmB,CAAC,aAAqB;IACrD,OAAO;QACH,iDAAiD;QACjD,wCAAwC;QACxC,iDAAiD,aAAa,KAAK;QACnE,oDAAoD,aAAa,EAAE;QACnE,4EAA4E,aAAa,GAAG;QAC5F,uFAAuF;KAC1F,CAAC;AACN,CAAC;AAED,kGAAkG;AAClG,oGAAoG;AACpG,iGAAiG;AACjG,SAAgB,wBAAwB,CAAC,QAAgB;IACrD,8DAA8D;IAC9D,IAAI,CAAC;QACD,MAAM,MAAM,GAAG,SAAS,CAAC,QAAQ,CAAC,CAAC;QACnC,MAAM,UAAU,GAAG,OAAO,CAAC,QAAQ,EAAE,KAAK,EAAE,CAAC,WAAW,EAAE,aAAa,CAAC,CAAC,CAAC;QAC1E,MAAM,WAAW,GAAG,OAAO,CAAC,QAAQ,EAAE,KAAK,EAAE,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC,CAAC;QACpE,IAAI,CAAC,UAAU,CAAC,EAAE,IAAI,CAAC,WAAW,CAAC,EAAE;YAAE,OAAO;QAC9C,MAAM,MAAM,GAAG,IAAI,cAAc,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;QACF,mBAAmB,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;IAC1C,CAAC;IAAC,OAAO,GAAY,EAAE,CAAC;QACpB,MAAM,KAAK,GAAG,IAAA,kBAAO,EAAC,GAAG,CAAC,CAAC;QAC3B,KAAK,KAAK,CAAC;IACf,CAAC;AACL,CAAC","sourcesContent":["import { spawnSync } from 'child_process';\nimport * as fs from 'fs';\nimport * as path from 'path';\n\nimport { WEBPIECES_TMP_DIR } from './constants';\nimport { toError } from './to-error';\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\n// in a DETACHED background process so the PreToolUse hook never blocks on the network. The\n// feature-branch-guard then only READS this cached file (instant). pr-gate's merge flow also writes\n// it synchronously after a merge so the next edit is unblocked immediately. Lives here (the shared\n// dep of both packages) so neither depends on the other.\n\n// How long an `inprocess` refresher lock may sit before a new refresher assumes the prior run hung\n// and proceeds anyway. Overridable per-rule via FeatureBranchGuardConfig.hangTimeoutMinutes.\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// Data-only (per CLAUDE.md, classes for data). `forkPoint` is null exactly when `hasForkPoint` is\n// false (no merge-base with origin/main). `branchAlreadyMerged` flags that this feature branch was\n// already merged into main (a merged PR exists) — the \"you're working on a finished branch\" case.\nexport class MainSyncStatus {\n branch: string;\n branchAlreadyMerged: boolean;\n mergedPr: string;\n hasForkPoint: boolean;\n forkPoint: string | null;\n originMain: string;\n featureHead: string;\n conflict: boolean;\n conflictFiles: string[];\n timestamp: string;\n\n constructor(\n branch: string,\n branchAlreadyMerged: boolean,\n mergedPr: string,\n hasForkPoint: boolean,\n forkPoint: string | null,\n originMain: string,\n featureHead: string,\n conflict: boolean,\n conflictFiles: string[],\n timestamp: string,\n ) {\n this.branch = branch;\n this.branchAlreadyMerged = branchAlreadyMerged;\n this.mergedPr = mergedPr;\n this.hasForkPoint = hasForkPoint;\n this.forkPoint = forkPoint;\n this.originMain = originMain;\n this.featureHead = featureHead;\n this.conflict = conflict;\n this.conflictFiles = conflictFiles;\n this.timestamp = timestamp;\n }\n}\n\n// Concurrency state machine for the detached refresher. `started` is epoch milliseconds. `pid` is\n// the refresher process's pid (0 = unknown, e.g. a lock written by an older version) — used so a\n// refresher that was KILLED before writing its finished lock (SIGKILL skips the finally) doesn't\n// wedge `inprocess` for the whole hangTimeout: if its pid is gone, the lock is reclaimable now.\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 shapes for the cast at the parse boundary —\n// keeps `any`/`unknown` out of the cast so no-any-unknown stays clean.\ninterface RawStatus {\n branch?: string;\n branchAlreadyMerged?: boolean;\n mergedPr?: string;\n hasForkPoint?: boolean;\n forkPoint?: string | null;\n originMain?: string;\n featureHead?: string;\n conflict?: boolean;\n conflictFiles?: string[];\n timestamp?: string;\n}\n\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\nexport function mainSyncStatusPath(repoRoot: string): string {\n return path.join(repoRoot, WEBPIECES_TMP_DIR, MAIN_SYNC_STATUS_FILE);\n}\n\nexport function mainSyncLockPath(repoRoot: string): string {\n return path.join(repoRoot, WEBPIECES_TMP_DIR, MAIN_SYNC_LOCK_FILE);\n}\n\nfunction ensureDir(filePath: string): void {\n fs.mkdirSync(path.dirname(filePath), { recursive: true });\n}\n\n// Pure read — any error (missing file, malformed JSON) returns null so the guard fails OPEN\n// (never block an edit because the cache is unreadable).\nexport function readMainSyncStatus(repoRoot: string): MainSyncStatus | null {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const statusPath = mainSyncStatusPath(repoRoot);\n if (!fs.existsSync(statusPath)) return null;\n const raw = JSON.parse(fs.readFileSync(statusPath, 'utf8')) as RawStatus;\n return new MainSyncStatus(\n raw.branch ?? '',\n raw.branchAlreadyMerged ?? false,\n raw.mergedPr ?? '',\n raw.hasForkPoint ?? true,\n raw.forkPoint ?? null,\n raw.originMain ?? '',\n raw.featureHead ?? '',\n raw.conflict ?? false,\n raw.conflictFiles ?? [],\n raw.timestamp ?? '',\n );\n } catch (err: unknown) {\n const error = toError(err);\n void error;\n return null;\n }\n}\n\nexport function writeMainSyncStatus(repoRoot: string, status: MainSyncStatus): void {\n const statusPath = mainSyncStatusPath(repoRoot);\n ensureDir(statusPath);\n fs.writeFileSync(statusPath, JSON.stringify(status, null, 2) + '\\n');\n}\n\nexport function readMainSyncLock(repoRoot: string): MainSyncLock | null {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const lockPath = 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\nexport function writeMainSyncLock(repoRoot: string, lock: MainSyncLock): void {\n const lockPath = mainSyncLockPath(repoRoot);\n ensureDir(lockPath);\n fs.writeFileSync(lockPath, JSON.stringify(lock, null, 2) + '\\n');\n}\n\n// A lock is stale (the prior refresher is assumed hung) once it has been `inprocess` longer than\n// hangTimeoutMinutes. `now` is injectable for tests; defaults to Date.now().\nexport function isLockStale(lock: MainSyncLock, hangTimeoutMinutes: number, now: number = Date.now()): boolean {\n return now - lock.started > hangTimeoutMinutes * 60 * 1000;\n}\n\n// Liveness probe: is `pid` still a running process? `process.kill(pid, 0)` sends no signal, it only\n// tests existence — ESRCH means the process is gone, EPERM means it exists but isn't ours (alive).\n// pid <= 0 means \"unknown\" (an old lock with no pid) → assume alive and fall back to staleness only.\nfunction 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 // ESRCH = no such process (dead); anything else (e.g. EPERM = exists, not ours) = alive.\n return !error.message.includes('ESRCH');\n }\n}\n\n// True when another refresher is actively running and we should NOT start a second one. A finished\n// lock, a missing lock, a stale (hung) inprocess lock, OR an inprocess lock whose refresher pid is\n// already dead (it was killed before writing its finished lock) all return false — so a killed\n// refresher never wedges refreshes for the full hangTimeout.\nexport function isRefreshInProgress(repoRoot: string, hangTimeoutMinutes: number, now: number = Date.now()): boolean {\n const lock = readMainSyncLock(repoRoot);\n if (!lock) return false;\n if (lock.state !== LOCK_STATE_INPROCESS) return false;\n if (isLockStale(lock, hangTimeoutMinutes, now)) return false;\n return isProcessAlive(lock.pid);\n}\n\nexport function inProcessLock(now: number = Date.now(), pid: number = process.pid): MainSyncLock {\n return new MainSyncLock(LOCK_STATE_INPROCESS, now, pid);\n}\n\nexport function finishedLock(started: number): MainSyncLock {\n return new MainSyncLock(LOCK_STATE_FINISHED, started, 0);\n}\n\n// Run a command capturing trimmed stdout; ok=false on spawn failure or non-zero exit.\nfunction 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 always\n// matches what the feature-branch-guard compares against (its own `git rev-parse` in the workspace).\nfunction gitBranch(repoRoot: string): string {\n const result = capture(repoRoot, 'git', ['rev-parse', '--abbrev-ref', 'HEAD']);\n return result.ok ? result.out : '';\n}\n\nfunction changedFiles(repoRoot: string, base: string, head: string): string[] {\n const result = 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). The committed-only `git diff forkPoint..HEAD` was\n// BLIND to uncommitted edits: for most of an editing session the files you are actively changing are\n// not yet in HEAD, so the overlap with main's changes was empty and conflict=false even when\n// origin/main had already moved onto those same files. Unioning in the working-tree changes makes the\n// conflict visible WHILE editing, so the guard can force an early merge instead of a painful late one.\nfunction featureChangedFiles(repoRoot: string, forkPoint: string): string[] {\n const out = new Set<string>();\n const add = (args: string[]): void => {\n const r = 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// Has this feature branch already been merged into main? Reliable signal: a MERGED PR exists for the\n// branch. Best-effort — if gh is missing/unauthenticated we just report not-merged (false).\nfunction detectMergedPr(repoRoot: string, branch: string): string {\n if (!branch || branch === 'main') return '';\n const result = capture(repoRoot, 'gh', ['pr', 'list', '--head', branch, '--state', 'merged', '--json', 'number', '--jq', '.[0].number']);\n return result.ok ? result.out : '';\n}\n\n// A benign status that never blocks — used when origin/main can't be resolved (no remote yet,\n// offline before first fetch). hasForkPoint=true + conflict=false so the guard allows the edit.\nfunction benignStatus(branch: string, featureHead: string): MainSyncStatus {\n return new MainSyncStatus(branch, false, '', true, null, '', featureHead, false, [], new Date().toISOString());\n}\n\n/**\n * The SLOW path, run only inside the detached refresher. Computes every cached signal the\n * feature-branch-guard needs: whether the branch is already merged (merged PR), whether a fork point\n * with origin/main still exists, and whether origin/main and this branch touched the SAME file since\n * the fork point (the deliberately-simple conflict heuristic — it over-blocks rather than miss a real\n * conflict). Never run on the hook's blocking path.\n */\nexport function computeMainSyncStatus(repoRoot: string): MainSyncStatus {\n const branch = gitBranch(repoRoot);\n const mergedPr = detectMergedPr(repoRoot, branch);\n\n // Best-effort network refresh; offline just means we evaluate against the last-fetched ref.\n spawnSync('git', ['fetch', 'origin', 'main'], { cwd: repoRoot, stdio: 'ignore' });\n\n const head = capture(repoRoot, 'git', ['rev-parse', 'HEAD']);\n const originMain = capture(repoRoot, 'git', ['rev-parse', 'origin/main']);\n const featureHead = head.ok ? head.out : '';\n if (!head.ok || !originMain.ok) {\n const status = benignStatus(branch, featureHead);\n status.branchAlreadyMerged = mergedPr !== '';\n status.mergedPr = mergedPr;\n return status;\n }\n\n const forkPoint = capture(repoRoot, 'git', ['merge-base', 'origin/main', 'HEAD']);\n if (!forkPoint.ok || forkPoint.out === '') {\n // No common ancestor — main was merged into the branch. Force the human to squash.\n return new MainSyncStatus(branch, mergedPr !== '', mergedPr, false, null, originMain.out, featureHead, false, [], new Date().toISOString());\n }\n\n const featureFiles = new Set(featureChangedFiles(repoRoot, forkPoint.out));\n const mainFiles = changedFiles(repoRoot, forkPoint.out, 'origin/main');\n const conflictFiles = mainFiles.filter((file: string): boolean => featureFiles.has(file));\n\n return 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}\n\n// The recovery steps when there is no fork point with origin/main (someone merged main into the\n// branch, so a clean squash-merge is impossible). Shared so the feature-branch-guard and pr-gate's\n// findForkPoint check present the SAME instructions. The human must redo the work on a fresh branch —\n// deliberately painful so the bad merge gets noticed and reported.\nexport function squashRecoverySteps(currentBranch: string): string[] {\n return [\n '1. Switch to main: git checkout main',\n '2. Pull latest: git pull',\n `3. New branch (new name): git checkout -b ${currentBranch}-v2`,\n `4. Squash-merge old branch: git merge --squash ${currentBranch}`,\n `5. Commit the squash: git add -A && git commit -m \"Squashed from ${currentBranch}\"`,\n '6. If a PR exists: open a NEW PR for the -v2 branch and close the old one.',\n ];\n}\n\n// Synchronously stamp a clean \"up to date with main\" status — call right after a successful merge\n// (the branch now contains origin/main). Unblocks the next edit immediately without waiting for the\n// async refresher. Best-effort: a git failure is swallowed (the refresher will recompute later).\nexport function stampCleanMainSyncStatus(repoRoot: string): void {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const branch = gitBranch(repoRoot);\n const originMain = capture(repoRoot, 'git', ['rev-parse', 'origin/main']);\n const featureHead = 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 writeMainSyncStatus(repoRoot, status);\n } catch (err: unknown) {\n const error = toError(err);\n void error;\n }\n}\n"]}
1
+ {"version":3,"file":"main-sync-status.js","sourceRoot":"","sources":["../../../../../packages/tooling/rules-config/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';\n\nimport { WEBPIECES_TMP_DIR } from './constants';\nimport { toError } from './to-error';\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\n// in a DETACHED background process so the PreToolUse hook never blocks on the network. The\n// feature-branch-guard then only READS this cached file (instant). pr-gate's merge flow also writes\n// it synchronously after a merge so the next edit is unblocked immediately. Lives here (the shared\n// dep of both packages) so neither depends on the other.\n\n// How long an `inprocess` refresher lock may sit before a new refresher assumes the prior run hung\n// and proceeds anyway. Overridable per-rule via FeatureBranchGuardConfig.hangTimeoutMinutes.\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// Data-only (per CLAUDE.md, classes for data). `forkPoint` is null exactly when `hasForkPoint` is\n// false (no merge-base with origin/main). `branchAlreadyMerged` flags that this feature branch was\n// already merged into main (a merged PR exists) — the \"you're working on a finished branch\" case.\nexport class MainSyncStatus {\n branch: string;\n branchAlreadyMerged: boolean;\n mergedPr: string;\n hasForkPoint: boolean;\n forkPoint: string | null;\n originMain: string;\n featureHead: string;\n conflict: boolean;\n conflictFiles: string[];\n timestamp: string;\n // An OPEN (not merged) PR tracking this branch, if any — '' = none or not-yet-known. Set by the\n // refresher (best-effort) and read by the feature-branch-guard so a mid-work conflict block can\n // steer straight to the PR flow instead of the update-only flow (which would just fail-fast when a\n // PR exists). Advisory only — the authoritative gate is wp-update-start's own fail-fast check.\n // Kept OUT of the positional constructor (a defaulted field) so existing call sites don't churn.\n openPr: string = '';\n\n constructor(\n branch: string,\n branchAlreadyMerged: boolean,\n mergedPr: string,\n hasForkPoint: boolean,\n forkPoint: string | null,\n originMain: string,\n featureHead: string,\n conflict: boolean,\n conflictFiles: string[],\n timestamp: string,\n ) {\n this.branch = branch;\n this.branchAlreadyMerged = branchAlreadyMerged;\n this.mergedPr = mergedPr;\n this.hasForkPoint = hasForkPoint;\n this.forkPoint = forkPoint;\n this.originMain = originMain;\n this.featureHead = featureHead;\n this.conflict = conflict;\n this.conflictFiles = conflictFiles;\n this.timestamp = timestamp;\n }\n}\n\n// Concurrency state machine for the detached refresher. `started` is epoch milliseconds. `pid` is\n// the refresher process's pid (0 = unknown, e.g. a lock written by an older version) — used so a\n// refresher that was KILLED before writing its finished lock (SIGKILL skips the finally) doesn't\n// wedge `inprocess` for the whole hangTimeout: if its pid is gone, the lock is reclaimable now.\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 shapes for the cast at the parse boundary —\n// keeps `any`/`unknown` out of the cast so no-any-unknown stays clean.\ninterface RawStatus {\n branch?: string;\n branchAlreadyMerged?: boolean;\n mergedPr?: string;\n hasForkPoint?: boolean;\n forkPoint?: string | null;\n originMain?: string;\n featureHead?: string;\n conflict?: boolean;\n conflictFiles?: string[];\n timestamp?: string;\n openPr?: string;\n}\n\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\nexport function mainSyncStatusPath(repoRoot: string): string {\n return path.join(repoRoot, WEBPIECES_TMP_DIR, MAIN_SYNC_STATUS_FILE);\n}\n\nexport function mainSyncLockPath(repoRoot: string): string {\n return path.join(repoRoot, WEBPIECES_TMP_DIR, MAIN_SYNC_LOCK_FILE);\n}\n\nfunction ensureDir(filePath: string): void {\n fs.mkdirSync(path.dirname(filePath), { recursive: true });\n}\n\n// Pure read — any error (missing file, malformed JSON) returns null so the guard fails OPEN\n// (never block an edit because the cache is unreadable).\nexport function readMainSyncStatus(repoRoot: string): MainSyncStatus | null {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const statusPath = mainSyncStatusPath(repoRoot);\n if (!fs.existsSync(statusPath)) return null;\n const raw = JSON.parse(fs.readFileSync(statusPath, 'utf8')) as RawStatus;\n const status = new MainSyncStatus(\n raw.branch ?? '',\n raw.branchAlreadyMerged ?? false,\n raw.mergedPr ?? '',\n raw.hasForkPoint ?? true,\n raw.forkPoint ?? null,\n raw.originMain ?? '',\n raw.featureHead ?? '',\n raw.conflict ?? false,\n raw.conflictFiles ?? [],\n raw.timestamp ?? '',\n );\n status.openPr = raw.openPr ?? '';\n return status;\n } catch (err: unknown) {\n const error = toError(err);\n void error;\n return null;\n }\n}\n\nexport function writeMainSyncStatus(repoRoot: string, status: MainSyncStatus): void {\n const statusPath = mainSyncStatusPath(repoRoot);\n ensureDir(statusPath);\n fs.writeFileSync(statusPath, JSON.stringify(status, null, 2) + '\\n');\n}\n\nexport function readMainSyncLock(repoRoot: string): MainSyncLock | null {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const lockPath = 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\nexport function writeMainSyncLock(repoRoot: string, lock: MainSyncLock): void {\n const lockPath = mainSyncLockPath(repoRoot);\n ensureDir(lockPath);\n fs.writeFileSync(lockPath, JSON.stringify(lock, null, 2) + '\\n');\n}\n\n// A lock is stale (the prior refresher is assumed hung) once it has been `inprocess` longer than\n// hangTimeoutMinutes. `now` is injectable for tests; defaults to Date.now().\nexport function isLockStale(lock: MainSyncLock, hangTimeoutMinutes: number, now: number = Date.now()): boolean {\n return now - lock.started > hangTimeoutMinutes * 60 * 1000;\n}\n\n// Liveness probe: is `pid` still a running process? `process.kill(pid, 0)` sends no signal, it only\n// tests existence — ESRCH means the process is gone, EPERM means it exists but isn't ours (alive).\n// pid <= 0 means \"unknown\" (an old lock with no pid) → assume alive and fall back to staleness only.\nfunction 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 // ESRCH = no such process (dead); anything else (e.g. EPERM = exists, not ours) = alive.\n return !error.message.includes('ESRCH');\n }\n}\n\n// True when another refresher is actively running and we should NOT start a second one. A finished\n// lock, a missing lock, a stale (hung) inprocess lock, OR an inprocess lock whose refresher pid is\n// already dead (it was killed before writing its finished lock) all return false — so a killed\n// refresher never wedges refreshes for the full hangTimeout.\nexport function isRefreshInProgress(repoRoot: string, hangTimeoutMinutes: number, now: number = Date.now()): boolean {\n const lock = readMainSyncLock(repoRoot);\n if (!lock) return false;\n if (lock.state !== LOCK_STATE_INPROCESS) return false;\n if (isLockStale(lock, hangTimeoutMinutes, now)) return false;\n return isProcessAlive(lock.pid);\n}\n\nexport function inProcessLock(now: number = Date.now(), pid: number = process.pid): MainSyncLock {\n return new MainSyncLock(LOCK_STATE_INPROCESS, now, pid);\n}\n\nexport function finishedLock(started: number): MainSyncLock {\n return new MainSyncLock(LOCK_STATE_FINISHED, started, 0);\n}\n\n// Run a command capturing trimmed stdout; ok=false on spawn failure or non-zero exit.\nfunction 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 always\n// matches what the feature-branch-guard compares against (its own `git rev-parse` in the workspace).\nfunction gitBranch(repoRoot: string): string {\n const result = capture(repoRoot, 'git', ['rev-parse', '--abbrev-ref', 'HEAD']);\n return result.ok ? result.out : '';\n}\n\nfunction changedFiles(repoRoot: string, base: string, head: string): string[] {\n const result = 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). The committed-only `git diff forkPoint..HEAD` was\n// BLIND to uncommitted edits: for most of an editing session the files you are actively changing are\n// not yet in HEAD, so the overlap with main's changes was empty and conflict=false even when\n// origin/main had already moved onto those same files. Unioning in the working-tree changes makes the\n// conflict visible WHILE editing, so the guard can force an early merge instead of a painful late one.\nfunction featureChangedFiles(repoRoot: string, forkPoint: string): string[] {\n const out = new Set<string>();\n const add = (args: string[]): void => {\n const r = 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// Has this feature branch already been merged into main? Reliable signal: a MERGED PR exists for the\n// branch. Best-effort — if gh is missing/unauthenticated we just report not-merged (false).\nfunction detectMergedPr(repoRoot: string, branch: string): string {\n if (!branch || branch === 'main') return '';\n const result = capture(repoRoot, 'gh', ['pr', 'list', '--head', branch, '--state', 'merged', '--json', 'number', '--jq', '.[0].number']);\n return result.ok ? result.out : '';\n}\n\n// An OPEN PR tracking this branch, if any. Best-effort — this is ADVISORY (it only lets the guard's\n// conflict block steer to the PR flow early), so an unreachable gh degrades to '' here. The HARD gate\n// that must never guess is wp-update-start's own openPrForBranch, which fails fast instead.\nfunction detectOpenPr(repoRoot: string, branch: string): string {\n if (!branch || branch === 'main') return '';\n const result = capture(repoRoot, 'gh', ['pr', 'list', '--head', branch, '--state', 'open', '--json', 'number', '--jq', '.[0].number']);\n return result.ok ? result.out : '';\n}\n\n// A benign status that never blocks — used when origin/main can't be resolved (no remote yet,\n// offline before first fetch). hasForkPoint=true + conflict=false so the guard allows the edit.\nfunction benignStatus(branch: string, featureHead: string): MainSyncStatus {\n return new MainSyncStatus(branch, false, '', true, null, '', featureHead, false, [], new Date().toISOString());\n}\n\n/**\n * The SLOW path, run only inside the detached refresher. Computes every cached signal the\n * feature-branch-guard needs: whether the branch is already merged (merged PR), whether a fork point\n * with origin/main still exists, and whether origin/main and this branch touched the SAME file since\n * the fork point (the deliberately-simple conflict heuristic — it over-blocks rather than miss a real\n * conflict). Never run on the hook's blocking path.\n */\nexport function computeMainSyncStatus(repoRoot: string): MainSyncStatus {\n const branch = gitBranch(repoRoot);\n const mergedPr = detectMergedPr(repoRoot, branch);\n // Advisory: lets the guard's conflict block steer to the PR flow early when a PR is already open.\n const openPr = detectOpenPr(repoRoot, branch);\n\n // Best-effort network refresh; offline just means we evaluate against the last-fetched ref.\n spawnSync('git', ['fetch', 'origin', 'main'], { cwd: repoRoot, stdio: 'ignore' });\n\n const head = capture(repoRoot, 'git', ['rev-parse', 'HEAD']);\n const originMain = capture(repoRoot, 'git', ['rev-parse', 'origin/main']);\n const featureHead = head.ok ? head.out : '';\n if (!head.ok || !originMain.ok) {\n const status = benignStatus(branch, featureHead);\n status.branchAlreadyMerged = mergedPr !== '';\n status.mergedPr = mergedPr;\n status.openPr = openPr;\n return status;\n }\n\n const forkPoint = capture(repoRoot, 'git', ['merge-base', 'origin/main', 'HEAD']);\n if (!forkPoint.ok || forkPoint.out === '') {\n // No common ancestor — main was merged into the branch. Force the human to squash.\n const noFork = new MainSyncStatus(branch, mergedPr !== '', mergedPr, false, null, originMain.out, featureHead, false, [], new Date().toISOString());\n noFork.openPr = openPr;\n return noFork;\n }\n\n const featureFiles = new Set(featureChangedFiles(repoRoot, forkPoint.out));\n const mainFiles = 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 return status;\n}\n\n// The recovery steps when there is no fork point with origin/main (someone merged main into the\n// branch, so a clean squash-merge is impossible). Shared so the feature-branch-guard and pr-gate's\n// findForkPoint check present the SAME instructions. The human must redo the work on a fresh branch —\n// deliberately painful so the bad merge gets noticed and reported.\nexport function squashRecoverySteps(currentBranch: string): string[] {\n return [\n '1. Switch to main: git checkout main',\n '2. Pull latest: git pull',\n `3. New branch (new name): git checkout -b ${currentBranch}-v2`,\n `4. Squash-merge old branch: git merge --squash ${currentBranch}`,\n `5. Commit the squash: git add -A && git commit -m \"Squashed from ${currentBranch}\"`,\n '6. If a PR exists: open a NEW PR for the -v2 branch and close the old one.',\n ];\n}\n\n// Synchronously stamp a clean \"up to date with main\" status — call right after a successful merge\n// (the branch now contains origin/main). Unblocks the next edit immediately without waiting for the\n// async refresher. Best-effort: a git failure is swallowed (the refresher will recompute later).\nexport function stampCleanMainSyncStatus(repoRoot: string): void {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const branch = gitBranch(repoRoot);\n const originMain = capture(repoRoot, 'git', ['rev-parse', 'origin/main']);\n const featureHead = 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 writeMainSyncStatus(repoRoot, status);\n } catch (err: unknown) {\n const error = toError(err);\n void error;\n }\n}\n"]}