@mutmutco/cli 4.3.1 → 4.3.2

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.
Files changed (2) hide show
  1. package/dist/main.cjs +1891 -543
  2. package/package.json +1 -1
package/dist/main.cjs CHANGED
@@ -3416,7 +3416,7 @@ function useColor() {
3416
3416
  var program = new Command();
3417
3417
 
3418
3418
  // src/command-composition.ts
3419
- var import_node_fs46 = require("node:fs");
3419
+ var import_node_fs48 = require("node:fs");
3420
3420
 
3421
3421
  // src/clean-exit.ts
3422
3422
  var UNDICI_GLOBAL_DISPATCHER_SYMBOL = Object.getOwnPropertySymbols(globalThis).find(
@@ -3465,19 +3465,19 @@ function hardExit(code) {
3465
3465
  }
3466
3466
  var STDIO_FLUSH_TIMEOUT_MS = 2e3;
3467
3467
  function flushStream(stream) {
3468
- return new Promise((resolve6) => {
3468
+ return new Promise((resolve7) => {
3469
3469
  try {
3470
- stream.write("", () => resolve6());
3470
+ stream.write("", () => resolve7());
3471
3471
  } catch {
3472
- resolve6();
3472
+ resolve7();
3473
3473
  }
3474
3474
  });
3475
3475
  }
3476
3476
  async function flushStdio(timeoutMs = STDIO_FLUSH_TIMEOUT_MS) {
3477
3477
  await Promise.race([
3478
3478
  Promise.all([flushStream(process.stdout), flushStream(process.stderr)]),
3479
- new Promise((resolve6) => {
3480
- setTimeout(resolve6, timeoutMs).unref?.();
3479
+ new Promise((resolve7) => {
3480
+ setTimeout(resolve7, timeoutMs).unref?.();
3481
3481
  })
3482
3482
  ]);
3483
3483
  }
@@ -3485,7 +3485,7 @@ async function cleanExit(code) {
3485
3485
  process.exitCode = code;
3486
3486
  await closeHttpPool();
3487
3487
  await flushStdio();
3488
- await new Promise((resolve6) => setImmediate(resolve6));
3488
+ await new Promise((resolve7) => setImmediate(resolve7));
3489
3489
  return void 0;
3490
3490
  }
3491
3491
  var CLI_EXIT_WATCHDOG_MS = (() => {
@@ -3930,7 +3930,7 @@ async function fetchWithRetry(fetchImpl, url, init, opts = {}) {
3930
3930
  const attempts = opts.attempts ?? 3;
3931
3931
  const baseDelayMs = opts.baseDelayMs ?? 250;
3932
3932
  const retryOn = opts.retryOn ?? ((res) => res.status >= 500);
3933
- const sleep2 = opts.sleep ?? ((ms) => new Promise((resolve6) => setTimeout(resolve6, ms)));
3933
+ const sleep2 = opts.sleep ?? ((ms) => new Promise((resolve7) => setTimeout(resolve7, ms)));
3934
3934
  let lastErr;
3935
3935
  for (let i = 0; i < attempts; i++) {
3936
3936
  const isLast = i === attempts - 1;
@@ -4198,8 +4198,8 @@ async function readStdin(opts = {}) {
4198
4198
  })().catch(() => {
4199
4199
  });
4200
4200
  let timer;
4201
- const timeout = new Promise((resolve6) => {
4202
- timer = setTimeout(resolve6, timeoutMs);
4201
+ const timeout = new Promise((resolve7) => {
4202
+ timer = setTimeout(resolve7, timeoutMs);
4203
4203
  });
4204
4204
  try {
4205
4205
  await Promise.race([drain, timeout]);
@@ -4253,12 +4253,12 @@ function killProcessTree(pid) {
4253
4253
  function execFileHard(file, args, options) {
4254
4254
  const { timeout, step, ...rest } = options;
4255
4255
  const started = Date.now();
4256
- return new Promise((resolve6, reject) => {
4256
+ return new Promise((resolve7, reject) => {
4257
4257
  const child2 = (0, import_node_child_process2.execFile)(file, args, { encoding: "utf8", windowsHide: true, ...rest, timeout: 0 }, (error, stdout, stderr) => {
4258
4258
  clearTimeout(timer);
4259
4259
  if (expired) return;
4260
4260
  if (error) reject(error);
4261
- else resolve6({ stdout: String(stdout), stderr: String(stderr) });
4261
+ else resolve7({ stdout: String(stdout), stderr: String(stderr) });
4262
4262
  });
4263
4263
  let expired = false;
4264
4264
  const timer = setTimeout(() => {
@@ -5219,8 +5219,8 @@ function unknownCommandDomainGuide(parentPath, token) {
5219
5219
  }
5220
5220
 
5221
5221
  // src/command-composition.ts
5222
- var import_node_os19 = require("node:os");
5223
- var import_node_path43 = require("node:path");
5222
+ var import_node_os20 = require("node:os");
5223
+ var import_node_path45 = require("node:path");
5224
5224
 
5225
5225
  // src/board-read.ts
5226
5226
  var import_node_fs13 = require("node:fs");
@@ -5330,7 +5330,7 @@ function rateLimitedReceipt(opts) {
5330
5330
  };
5331
5331
  }
5332
5332
  async function runWithRateLimitBackoff(operation, opts) {
5333
- const sleep2 = opts.sleep ?? ((ms) => new Promise((resolve6) => setTimeout(resolve6, ms)));
5333
+ const sleep2 = opts.sleep ?? ((ms) => new Promise((resolve7) => setTimeout(resolve7, ms)));
5334
5334
  const now = opts.now ?? Date.now;
5335
5335
  const log = opts.log ?? ((message) => console.warn(message));
5336
5336
  const capMs = opts.capMs ?? RATE_LIMIT_WAIT_CAP_MS;
@@ -8016,13 +8016,47 @@ async function defaultCompareRefs(exec, repo, base, head) {
8016
8016
  return "unknown";
8017
8017
  }
8018
8018
  }
8019
+ async function publishAbsentPrHeadIfOwned(repo, head, exec) {
8020
+ if (isForkStyleHead(head)) return false;
8021
+ let originRepo;
8022
+ try {
8023
+ const { stdout } = await exec("git", ["remote", "get-url", "--push", "origin"], { timeout: GIT_TIMEOUT_MS });
8024
+ originRepo = repoFromRemoteUrl(stdout.trim());
8025
+ } catch {
8026
+ return false;
8027
+ }
8028
+ if (!originRepo || originRepo.toLowerCase() !== repo.toLowerCase()) return false;
8029
+ let currentBranch2;
8030
+ try {
8031
+ currentBranch2 = (await exec("git", ["symbolic-ref", "--quiet", "--short", "HEAD"], { timeout: GIT_TIMEOUT_MS })).stdout.trim();
8032
+ } catch {
8033
+ return false;
8034
+ }
8035
+ if (!currentBranch2 || currentBranch2 !== head) return false;
8036
+ let commit;
8037
+ try {
8038
+ commit = (await exec("git", ["rev-parse", "--verify", "HEAD^{commit}"], { timeout: GIT_TIMEOUT_MS })).stdout.trim();
8039
+ } catch {
8040
+ return false;
8041
+ }
8042
+ if (!commit) return false;
8043
+ try {
8044
+ await exec("git", ["push", "origin", `${commit}:refs/heads/${head}`], { timeout: GH_MUTATION_TIMEOUT_MS });
8045
+ return true;
8046
+ } catch {
8047
+ return false;
8048
+ }
8049
+ }
8019
8050
  async function preflightPrCreateRemoteHead(args, deps) {
8020
8051
  const repo = flagValue(args, "--repo");
8021
8052
  const head = flagValue(args, "--head");
8022
8053
  if (!repo || !head || isForkStyleHead(head)) return void 0;
8023
8054
  const readRemoteHead = deps.readRemoteHead ?? ((input) => defaultReadRemoteHead(deps.exec, input.repo, input.head));
8024
- const presence = await readRemoteHead({ repo, head });
8025
- if (presence === "absent") return unpushedHeadMessage(head);
8055
+ let presence = await readRemoteHead({ repo, head });
8056
+ if (presence === "absent") {
8057
+ if (!await publishAbsentPrHeadIfOwned(repo, head, deps.exec)) return unpushedHeadMessage(head);
8058
+ presence = "present";
8059
+ }
8026
8060
  if (presence !== "present") return void 0;
8027
8061
  const base = flagValue(args, "--base");
8028
8062
  if (!base) return void 0;
@@ -8438,7 +8472,7 @@ async function createPrViaRestFallback(args, swappedArgs, deps, knownPools) {
8438
8472
  }
8439
8473
  async function ghCreate(args, deps = {}) {
8440
8474
  const exec = deps.exec ?? execFileP;
8441
- const sleep2 = deps.sleep ?? ((ms) => new Promise((resolve6) => setTimeout(resolve6, ms)));
8475
+ const sleep2 = deps.sleep ?? ((ms) => new Promise((resolve7) => setTimeout(resolve7, ms)));
8442
8476
  const now = deps.now ?? Date.now;
8443
8477
  const read = deps.readFile ?? import_promises.readFile;
8444
8478
  const restCreatePr = deps.restCreatePr ?? ((input) => defaultRestCreatePr(exec, input));
@@ -9151,8 +9185,8 @@ var PRE_SPAWN_DRAIN_TIMEOUT_MS = 2e3;
9151
9185
  async function drainHttpPoolBeforeSpawn() {
9152
9186
  const drained = await Promise.race([
9153
9187
  closeHttpPool().then(() => true),
9154
- new Promise((resolve6) => {
9155
- setTimeout(() => resolve6(false), PRE_SPAWN_DRAIN_TIMEOUT_MS).unref?.();
9188
+ new Promise((resolve7) => {
9189
+ setTimeout(() => resolve7(false), PRE_SPAWN_DRAIN_TIMEOUT_MS).unref?.();
9156
9190
  })
9157
9191
  ]);
9158
9192
  if (!drained) destroyHttpPool();
@@ -9400,8 +9434,22 @@ function repoFromSelector(selector) {
9400
9434
  return void 0;
9401
9435
  }
9402
9436
  }
9437
+ var REPO_OPTION_SHAPES = "owner/repo or host/owner/repo";
9438
+ function invalidRepoOption(repo, hint) {
9439
+ const base = `invalid repo "${repo}" (expected ${REPO_OPTION_SHAPES})`;
9440
+ return new Error(hint ? `${base} \u2014 ${hint}` : base);
9441
+ }
9442
+ async function validateRepoOption(repo) {
9443
+ const value = repo.trim();
9444
+ const parts = value.split("/");
9445
+ if ((parts.length === 2 || parts.length === 3) && parts.every((p) => p !== "")) return value;
9446
+ const origin = repoFromRemoteUrl(await gitOut(["remote", "get-url", "origin"])) ?? repoFromRemoteUrl(originUrlFromGitConfig() ?? "");
9447
+ const originName = origin?.split("/")[1];
9448
+ const hint = originName !== void 0 && value.toLowerCase() === originName.toLowerCase() ? `did you mean ${origin}?` : void 0;
9449
+ throw invalidRepoOption(repo, hint);
9450
+ }
9403
9451
  async function resolveRepo(repo) {
9404
- if (repo) return repo;
9452
+ if (repo) return validateRepoOption(repo);
9405
9453
  const fromOrigin = repoFromRemoteUrl(await gitOut(["remote", "get-url", "origin"])) ?? repoFromRemoteUrl(originUrlFromGitConfig() ?? "");
9406
9454
  if (fromOrigin) return fromOrigin;
9407
9455
  try {
@@ -12271,7 +12319,7 @@ async function setBoardItemPriority(client, cfg, itemId, priority) {
12271
12319
  await updateItemSingleSelect(client, cfg.projectId, itemId, cfg.priorityFieldId, optionId);
12272
12320
  return cliPriorityToFieldName(priority);
12273
12321
  }
12274
- var defaultRetrySleep = (ms) => new Promise((resolve6) => setTimeout(resolve6, ms));
12322
+ var defaultRetrySleep = (ms) => new Promise((resolve7) => setTimeout(resolve7, ms));
12275
12323
  async function resolveProjectItemIdWithRetry(client, cfg, selector, opts = {}) {
12276
12324
  const attempts = Math.max(1, opts.attempts ?? 5);
12277
12325
  const delayMs = opts.delayMs ?? 300;
@@ -15274,10 +15322,10 @@ var rollout_plan_default = {
15274
15322
  note: "The v4.0.0 stamp happens at cut time (D6e #4463); until then the candidate is the origin/development head artifacts (built cli/dist + npm pack), identity proven by dist content hash (D6a)."
15275
15323
  },
15276
15324
  baseline: {
15277
- version: "4.3.1",
15278
- tag: "v4.3.1",
15279
- commit: "c3366772907f",
15280
- npm: "@mutmutco/cli@4.3.1"
15325
+ version: "4.3.2",
15326
+ tag: "v4.3.2",
15327
+ commit: "c4aa995c1dfc",
15328
+ npm: "@mutmutco/cli@4.3.2"
15281
15329
  },
15282
15330
  exitCriterion: "fleet-n-of-n",
15283
15331
  hubOnlyShortcut: "forbidden",
@@ -15294,14 +15342,14 @@ var rollout_plan_default = {
15294
15342
  repo: "mutmutco/mmi-hub",
15295
15343
  role: "canary",
15296
15344
  schedule: "train",
15297
- v3Target: "v4.3.1"
15345
+ v3Target: "v4.3.2"
15298
15346
  }
15299
15347
  ],
15300
15348
  rollbackTrigger: "Any red inside the post-contract soak window: `devops train gate` FAIL attributable to the v4 doors, Hub endpoint health probe failure, a pre-v4 client admitted instead of receiving actionable HTTP 426, or npm consumer install/doctor failure on the v4-only dist.",
15301
15349
  rollback: {
15302
15350
  independent: true,
15303
- mechanism: "npm dist-tag latest -> 4.3.1 and redeploy the Hub Lambda from tag v4.3.1 (c3366772907f); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
15304
- v3Target: "v4.3.1 (@mutmutco/cli@4.3.1, tag commit c3366772907f \u2014 last known-good release carrying the repo-index v4-only contract)"
15351
+ mechanism: "npm dist-tag latest -> 4.3.2 and redeploy the Hub Lambda from tag v4.3.2 (c4aa995c1dfc); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
15352
+ v3Target: "v4.3.2 (@mutmutco/cli@4.3.2, tag commit c4aa995c1dfc \u2014 last known-good release carrying the repo-index v4-only contract)"
15305
15353
  }
15306
15354
  },
15307
15355
  {
@@ -16169,6 +16217,7 @@ function validateEnum(flag, allowed, value, command) {
16169
16217
  var ISSUE_STATES = ["open", "closed", "all"];
16170
16218
  var PR_STATES = ["open", "all"];
16171
16219
  async function resolveBoardProjectId(repoOption) {
16220
+ if (repoOption !== void 0) await validateRepoOption(repoOption);
16172
16221
  try {
16173
16222
  const floor = await loadConfig();
16174
16223
  if (!floor.sagaApiUrl) return void 0;
@@ -17270,7 +17319,7 @@ async function runMergeTreePreflight(deps, ours, theirs) {
17270
17319
  async function predictMergeConflicts(deps, ours, theirs) {
17271
17320
  return runMergeTreePreflight(deps, ours, theirs);
17272
17321
  }
17273
- async function mergeWithToleratedResolution(deps, sourceRef, label, resolve6, extraTolerated = []) {
17322
+ async function mergeWithToleratedResolution(deps, sourceRef, label, resolve7, extraTolerated = []) {
17274
17323
  try {
17275
17324
  await deps.run("git", ["merge", sourceRef, "--no-edit"]);
17276
17325
  return;
@@ -17284,7 +17333,7 @@ async function mergeWithToleratedResolution(deps, sourceRef, label, resolve6, ex
17284
17333
  unmerged.length === 0 ? `${label} merge failed without conflicted paths \u2014 merge aborted; inspect the repo state and rerun` : `${label} merge conflicts on untolerated path(s): ${blocking.join(", ")} \u2014 merge aborted (the train is misaligned; reconcile the branches via an approved alignment PR, then rerun)`
17285
17334
  );
17286
17335
  }
17287
- await deps.run("git", ["checkout", `--${resolve6}`, "--", ...unmerged]);
17336
+ await deps.run("git", ["checkout", `--${resolve7}`, "--", ...unmerged]);
17288
17337
  await deps.run("git", ["add", "--", ...unmerged]);
17289
17338
  await deps.run("git", ["commit", "--no-edit"]);
17290
17339
  }
@@ -17463,7 +17512,7 @@ var CORRELATE_SKEW_SLACK_MS = 1e4;
17463
17512
  var CORRELATE_PAGE_LIMIT = 50;
17464
17513
  var RUN_CONFIRM_ATTEMPTS = 3;
17465
17514
  var RUN_CONFIRM_DELAY_MS = 1e3;
17466
- var defaultSleep = (ms) => new Promise((resolve6) => setTimeout(resolve6, ms));
17515
+ var defaultSleep = (ms) => new Promise((resolve7) => setTimeout(resolve7, ms));
17467
17516
  function resolveSleep(deps) {
17468
17517
  return deps.sleep ?? defaultSleep;
17469
17518
  }
@@ -18292,12 +18341,6 @@ async function discoverShaWorkflowRuns(deps, repo, headSha, seenRunIds) {
18292
18341
  }
18293
18342
  return extra;
18294
18343
  }
18295
- async function deriveHistoricalDeployStatus(deps, ctx, model, releaseSha) {
18296
- const targets = prodReleaseTargets(model);
18297
- if (!targets.length) return "pending";
18298
- const repo = model === "hub-serverless" ? HUB_REPO2 : ctx.repo;
18299
- return aggregateWorkflowRuns(await listOwnWorkflowRuns(deps, repo, targets, releaseSha));
18300
- }
18301
18344
  async function dispatchDeploy(deps, ctx, stage, ref, model, watch, autoRunSince, autoRunHeadSha, dispatchFailure = "throw", publishDir) {
18302
18345
  if (isCentralDispatchModel(model)) {
18303
18346
  const since = (deps.now ?? Date.now)();
@@ -18592,9 +18635,9 @@ function renderTenantControl(r) {
18592
18635
  }
18593
18636
 
18594
18637
  // src/train-apply-phases.ts
18595
- var import_node_fs19 = require("node:fs");
18596
- var import_promises2 = require("node:fs/promises");
18597
- var import_node_path17 = require("node:path");
18638
+ var import_node_fs20 = require("node:fs");
18639
+ var import_promises3 = require("node:fs/promises");
18640
+ var import_node_path19 = require("node:path");
18598
18641
 
18599
18642
  // src/actions-billing-preflight.ts
18600
18643
  var CANARY_WORKFLOW = "actions-job-start-canary.yml";
@@ -18818,37 +18861,1077 @@ Resume it: ${resumeCommand}
18818
18861
  );
18819
18862
  }
18820
18863
 
18864
+ // src/release-ledger.ts
18865
+ var import_node_fs19 = require("node:fs");
18866
+ var import_node_path18 = require("node:path");
18867
+ var import_node_crypto5 = require("node:crypto");
18868
+
18869
+ // src/file-lock.ts
18870
+ var import_promises2 = require("node:fs/promises");
18871
+ var import_node_path17 = require("node:path");
18872
+ var sleep = (ms) => new Promise((resolve7) => setTimeout(resolve7, ms));
18873
+ var IMMEDIATE_RETRY_BUDGET = 3;
18874
+ var FileLockBusyError = class extends Error {
18875
+ lockPath;
18876
+ constructor(label, lockPath, maxWaitMs) {
18877
+ super(`${label} busy: ${lockPath} held longer than ${maxWaitMs}ms`);
18878
+ this.name = "FileLockBusyError";
18879
+ this.lockPath = lockPath;
18880
+ }
18881
+ };
18882
+ function resolveFileLockOpts(opts = {}) {
18883
+ return {
18884
+ staleMs: opts.staleMs ?? 3e4,
18885
+ retryMs: opts.retryMs ?? 50,
18886
+ maxWaitMs: opts.maxWaitMs ?? 5e3,
18887
+ label: opts.label ?? "file lock"
18888
+ };
18889
+ }
18890
+ async function acquireFileLock(lockPath, opts, deadline) {
18891
+ let immediateRetries = 0;
18892
+ for (; ; ) {
18893
+ let handle;
18894
+ try {
18895
+ handle = await (0, import_promises2.open)(lockPath, "wx");
18896
+ } catch (e) {
18897
+ const code = e.code;
18898
+ if (code !== "EEXIST" && code !== "EPERM" && code !== "EBUSY" && code !== "EACCES") throw e;
18899
+ const retryNow = async () => {
18900
+ if (Date.now() >= deadline) throw new FileLockBusyError(opts.label, lockPath, opts.maxWaitMs);
18901
+ if (++immediateRetries > IMMEDIATE_RETRY_BUDGET) await sleep(opts.retryMs);
18902
+ };
18903
+ try {
18904
+ const age = Date.now() - (await (0, import_promises2.stat)(lockPath)).mtimeMs;
18905
+ if (age > opts.staleMs) {
18906
+ try {
18907
+ await (0, import_promises2.unlink)(lockPath);
18908
+ await retryNow();
18909
+ continue;
18910
+ } catch (unlinkError) {
18911
+ if (unlinkError instanceof FileLockBusyError) throw unlinkError;
18912
+ const unlinkCode = unlinkError.code;
18913
+ if (unlinkCode === "ENOENT") {
18914
+ await retryNow();
18915
+ continue;
18916
+ }
18917
+ }
18918
+ }
18919
+ } catch (statError) {
18920
+ if (statError instanceof FileLockBusyError) throw statError;
18921
+ if (statError.code !== "ENOENT") throw statError;
18922
+ await retryNow();
18923
+ continue;
18924
+ }
18925
+ if (Date.now() >= deadline) {
18926
+ throw new FileLockBusyError(opts.label, lockPath, opts.maxWaitMs);
18927
+ }
18928
+ await sleep(opts.retryMs);
18929
+ continue;
18930
+ }
18931
+ const token = `${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2)}`;
18932
+ try {
18933
+ await handle.writeFile(token);
18934
+ } catch (e) {
18935
+ await handle.close().catch(() => void 0);
18936
+ throw e;
18937
+ }
18938
+ return { token, handle };
18939
+ }
18940
+ }
18941
+ async function fileLockHeldBy(lockPath, token) {
18942
+ try {
18943
+ return await (0, import_promises2.readFile)(lockPath, "utf8") === token;
18944
+ } catch {
18945
+ return false;
18946
+ }
18947
+ }
18948
+ async function releaseFileLock(lockPath, guard) {
18949
+ await guard.handle.close().catch(() => void 0);
18950
+ if (await fileLockHeldBy(lockPath, guard.token)) {
18951
+ await (0, import_promises2.unlink)(lockPath).catch(() => void 0);
18952
+ }
18953
+ }
18954
+ async function withFileLock(lockPath, opts, fn) {
18955
+ const resolved = resolveFileLockOpts(opts);
18956
+ await (0, import_promises2.mkdir)((0, import_node_path17.dirname)(lockPath), { recursive: true }).catch(() => void 0);
18957
+ const guard = await acquireFileLock(lockPath, resolved, Date.now() + resolved.maxWaitMs);
18958
+ try {
18959
+ return await fn();
18960
+ } finally {
18961
+ await releaseFileLock(lockPath, guard);
18962
+ }
18963
+ }
18964
+
18965
+ // src/release-ledger.ts
18966
+ var RELEASE_LEDGER_SCHEMA = "mmi-release-ledger/v1";
18967
+ var RELEASE_LEDGER_PHASES = [
18968
+ "promotion",
18969
+ "ordinaryCi",
18970
+ "githubRelease",
18971
+ "publish",
18972
+ "deploy",
18973
+ "alignment"
18974
+ ];
18975
+ var PHASE_SET = new Set(RELEASE_LEDGER_PHASES);
18976
+ var STATE_SET = /* @__PURE__ */ new Set(["pending", "complete", "failed", "skipped"]);
18977
+ var ReleaseLedgerError = class extends Error {
18978
+ kind;
18979
+ constructor(kind, message) {
18980
+ super(message);
18981
+ this.name = "ReleaseLedgerError";
18982
+ this.kind = kind;
18983
+ }
18984
+ };
18985
+ var SHA_RE = /^[0-9a-f]{40}$/;
18986
+ var MAX_TEXT = 500;
18987
+ function truncate(text, max) {
18988
+ return text.length <= max ? text : `${text.slice(0, max - 1)}\u2026`;
18989
+ }
18990
+ function normalizeLedgerAnchors(anchors) {
18991
+ const repo = anchors.repo?.trim();
18992
+ const tag = anchors.tag?.trim();
18993
+ const tagSha = anchors.tagSha?.trim().toLowerCase();
18994
+ if (!repo || !/^[^/]+\/[^/]+$/.test(repo)) {
18995
+ throw new ReleaseLedgerError("invalid-record", `release ledger: invalid repo anchor ${JSON.stringify(anchors.repo ?? "")}`);
18996
+ }
18997
+ if (!tag || !/^v\d+\.\d+\.\d+(-rc\.\d+)?$/.test(tag)) {
18998
+ throw new ReleaseLedgerError("invalid-record", `release ledger: invalid tag anchor ${JSON.stringify(anchors.tag ?? "")}`);
18999
+ }
19000
+ if (!tagSha || !SHA_RE.test(tagSha)) {
19001
+ throw new ReleaseLedgerError("invalid-record", `release ledger: invalid tagSha anchor ${JSON.stringify(anchors.tagSha ?? "")}`);
19002
+ }
19003
+ return { repo, tag, tagSha };
19004
+ }
19005
+ function emptyPhases() {
19006
+ return Object.fromEntries(RELEASE_LEDGER_PHASES.map((p) => [p, { state: "pending", updatedAt: "never" }]));
19007
+ }
19008
+ function validatePhaseRecord(phase, raw) {
19009
+ if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
19010
+ throw new ReleaseLedgerError("invalid-record", `release ledger: phase ${phase} is not an object`);
19011
+ }
19012
+ const rec = raw;
19013
+ const state = rec.state;
19014
+ if (typeof state !== "string" || !STATE_SET.has(state)) {
19015
+ throw new ReleaseLedgerError("invalid-record", `release ledger: phase ${phase} has invalid state ${JSON.stringify(state)}`);
19016
+ }
19017
+ if (rec.sha !== void 0 && (typeof rec.sha !== "string" || !SHA_RE.test(rec.sha))) {
19018
+ throw new ReleaseLedgerError("invalid-record", `release ledger: phase ${phase} has a malformed sha`);
19019
+ }
19020
+ if (rec.runId !== void 0 && (typeof rec.runId !== "number" || !Number.isSafeInteger(rec.runId) || rec.runId <= 0)) {
19021
+ throw new ReleaseLedgerError("invalid-record", `release ledger: phase ${phase} has a malformed runId`);
19022
+ }
19023
+ if (rec.runRepo !== void 0 && (typeof rec.runRepo !== "string" || !/^[\w.-]+\/[\w.-]+$/.test(rec.runRepo))) {
19024
+ throw new ReleaseLedgerError("invalid-record", `release ledger: phase ${phase} has a malformed runRepo`);
19025
+ }
19026
+ if (rec.runProvenance !== void 0 && rec.runProvenance !== "sha" && rec.runProvenance !== "dispatch") {
19027
+ throw new ReleaseLedgerError("invalid-record", `release ledger: phase ${phase} has a malformed runProvenance ${JSON.stringify(rec.runProvenance)}`);
19028
+ }
19029
+ for (const key of ["runUrl", "note", "error", "workflow", "event"]) {
19030
+ if (rec[key] !== void 0 && (typeof rec[key] !== "string" || rec[key].length > MAX_TEXT * 4)) {
19031
+ throw new ReleaseLedgerError("invalid-record", `release ledger: phase ${phase} has a malformed ${key}`);
19032
+ }
19033
+ }
19034
+ if (rec.retries !== void 0 && (typeof rec.retries !== "number" || !Number.isSafeInteger(rec.retries) || rec.retries < 0 || rec.retries > 9)) {
19035
+ throw new ReleaseLedgerError("invalid-record", `release ledger: phase ${phase} has a malformed retries value`);
19036
+ }
19037
+ if (typeof rec.updatedAt !== "string" || !rec.updatedAt) {
19038
+ throw new ReleaseLedgerError("invalid-record", `release ledger: phase ${phase} is missing updatedAt`);
19039
+ }
19040
+ return rec;
19041
+ }
19042
+ function parseReleaseLedger(raw) {
19043
+ let parsed;
19044
+ try {
19045
+ parsed = JSON.parse(raw);
19046
+ } catch (e) {
19047
+ throw new ReleaseLedgerError("unreadable", `release ledger is not valid JSON (${e instanceof Error ? e.message : String(e)}) \u2014 refusing to resume against unreadable state (#5987)`);
19048
+ }
19049
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
19050
+ throw new ReleaseLedgerError("unreadable", "release ledger is not a JSON object");
19051
+ }
19052
+ const obj = parsed;
19053
+ if (obj.schema !== RELEASE_LEDGER_SCHEMA) {
19054
+ throw new ReleaseLedgerError(
19055
+ "unknown-schema",
19056
+ `release ledger schema ${JSON.stringify(obj.schema ?? "(missing)")} is not ${JSON.stringify(RELEASE_LEDGER_SCHEMA)} \u2014 a NEWER ledger this CLI cannot interpret must fail closed; do not delete or hand-edit the file, use a CLI that understands it`
19057
+ );
19058
+ }
19059
+ const repo = obj.repo;
19060
+ const tag = obj.tag;
19061
+ const tagSha = obj.tagSha;
19062
+ if (typeof repo !== "string" || !repo || typeof tag !== "string" || !tag || typeof tagSha !== "string" || !SHA_RE.test(tagSha)) {
19063
+ throw new ReleaseLedgerError("unreadable", `release ledger has malformed identity anchors (repo/tag/tagSha)`);
19064
+ }
19065
+ if (typeof obj.updatedAt !== "string" || !obj.updatedAt) {
19066
+ throw new ReleaseLedgerError("unreadable", "release ledger is missing updatedAt");
19067
+ }
19068
+ const phasesRaw = obj.phases;
19069
+ if (typeof phasesRaw !== "object" || phasesRaw === null || Array.isArray(phasesRaw)) {
19070
+ throw new ReleaseLedgerError("unreadable", "release ledger phases block is not an object");
19071
+ }
19072
+ const phases = { ...emptyPhases() };
19073
+ for (const [phase, rec] of Object.entries(phasesRaw)) {
19074
+ if (!PHASE_SET.has(phase)) continue;
19075
+ phases[phase] = validatePhaseRecord(phase, rec);
19076
+ }
19077
+ return { schema: RELEASE_LEDGER_SCHEMA, repo, tag, tagSha, phases, updatedAt: obj.updatedAt };
19078
+ }
19079
+ function validateLedgerAnchors(ledger, expected) {
19080
+ const anchors = normalizeAnchorsForCompare(expected);
19081
+ if (ledger.repo.toLowerCase() !== anchors.repo.toLowerCase()) {
19082
+ throw new ReleaseLedgerError(
19083
+ "repo-mismatch",
19084
+ `release ledger belongs to ${ledger.repo}, not ${anchors.repo} \u2014 refusing to mix releases across repos (#5987)`
19085
+ );
19086
+ }
19087
+ if (ledger.tag !== anchors.tag) {
19088
+ throw new ReleaseLedgerError(
19089
+ "tag-mismatch",
19090
+ `release ledger belongs to ${ledger.tag}, not ${anchors.tag} \u2014 refusing to resume a different tag against it (#5987)`
19091
+ );
19092
+ }
19093
+ if (ledger.tagSha.toLowerCase() !== anchors.tagSha.toLowerCase()) {
19094
+ throw new ReleaseLedgerError(
19095
+ "tag-sha-mismatch",
19096
+ `release ledger anchors ${ledger.tag} at ${ledger.tagSha.slice(0, 12)} but live origin reports ${anchors.tagSha.slice(0, 12)} \u2014 the immutable tag moved or the ledger is stale; failing closed instead of trusting either (#5987)`
19097
+ );
19098
+ }
19099
+ }
19100
+ function normalizeAnchorsForCompare(expected) {
19101
+ const repo = expected.repo?.trim() ?? "";
19102
+ const tag = expected.tag?.trim() ?? "";
19103
+ const tagSha = expected.tagSha?.trim().toLowerCase() ?? "";
19104
+ if (!repo || !tag || !SHA_RE.test(tagSha)) {
19105
+ throw new ReleaseLedgerError("invalid-record", `release ledger: invalid live anchors (repo=${JSON.stringify(repo)}, tag=${JSON.stringify(tag)})`);
19106
+ }
19107
+ return { repo, tag, tagSha };
19108
+ }
19109
+ function resolvePrimaryGitDir(cwd) {
19110
+ const gitDir = resolveGitDir(cwd);
19111
+ if (!gitDir) {
19112
+ throw new ReleaseLedgerError("no-checkout", `release ledger: no git checkout found above ${cwd}`);
19113
+ }
19114
+ let common;
19115
+ try {
19116
+ common = (0, import_node_fs19.readFileSync)((0, import_node_path18.join)(gitDir, "commondir"), "utf8").trim();
19117
+ } catch (e) {
19118
+ if (e.code !== "ENOENT") {
19119
+ throw new ReleaseLedgerError("unreadable", `release ledger: the worktree gitdir's commondir pointer could not be READ (${e instanceof Error ? e.message : String(e)}) \u2014 a failed read is unverified, not primary (#5987/#4713)`);
19120
+ }
19121
+ }
19122
+ if (!common) return gitDir;
19123
+ const commonDir = (0, import_node_path18.isAbsolute)(common) ? common : (0, import_node_path18.resolve)(gitDir, common);
19124
+ try {
19125
+ (0, import_node_fs19.readFileSync)((0, import_node_path18.join)(commonDir, "HEAD"), "utf8");
19126
+ } catch (e) {
19127
+ throw new ReleaseLedgerError("no-checkout", `release ledger: the worktree's commondir pointer names ${commonDir}, which has no readable HEAD (${e instanceof Error ? e.message : String(e)}) \u2014 the primary checkout may have been removed; refusing to write the ledger into a doomed location`);
19128
+ }
19129
+ return commonDir;
19130
+ }
19131
+ function releaseLedgerPath(cwd) {
19132
+ return (0, import_node_path18.join)(resolvePrimaryGitDir(cwd), "mmi-runtime", "release", "phases.json");
19133
+ }
19134
+ function atomicWriteJson(path2, ledger) {
19135
+ (0, import_node_fs19.mkdirSync)((0, import_node_path18.dirname)(path2), { recursive: true });
19136
+ const tmp = `${path2}.tmp-${process.pid}-${(0, import_node_crypto5.randomBytes)(4).toString("hex")}`;
19137
+ (0, import_node_fs19.writeFileSync)(tmp, `${JSON.stringify(ledger, null, 2)}
19138
+ `, "utf8");
19139
+ (0, import_node_fs19.renameSync)(tmp, path2);
19140
+ }
19141
+ async function withReleaseLedger(path2, expected, fn, opts = {}) {
19142
+ return withFileLock(`${path2}.lock`, { label: "release ledger", maxWaitMs: opts.maxWaitMs ?? 5e3 }, async () => {
19143
+ let current;
19144
+ let raw;
19145
+ try {
19146
+ raw = (0, import_node_fs19.readFileSync)(path2, "utf8");
19147
+ } catch (e) {
19148
+ if (e.code !== "ENOENT") {
19149
+ throw new ReleaseLedgerError("unreadable", `release ledger at ${path2} could not be READ (${e instanceof Error ? e.message : String(e)}) \u2014 a failed read is unverified, not absent (#5987)`);
19150
+ }
19151
+ }
19152
+ if (raw !== void 0) {
19153
+ current = parseReleaseLedger(raw);
19154
+ if (expected) validateLedgerAnchors(current, expected);
19155
+ }
19156
+ const { ledger, value } = await fn(current);
19157
+ if (ledger) atomicWriteJson(path2, ledger);
19158
+ return value;
19159
+ });
19160
+ }
19161
+ function openReleaseLedgerWriter(cwd, opts = {}) {
19162
+ const now = opts.now ?? (() => /* @__PURE__ */ new Date());
19163
+ let path2;
19164
+ try {
19165
+ path2 = releaseLedgerPath(cwd);
19166
+ } catch (e) {
19167
+ const failure = e;
19168
+ return {
19169
+ path: "(unresolved)",
19170
+ record: async () => ({ ok: false, error: failure instanceof Error ? failure.message : String(failure) }),
19171
+ load: async () => {
19172
+ throw failure;
19173
+ }
19174
+ };
19175
+ }
19176
+ return {
19177
+ path: path2,
19178
+ record: async (anchors, phase, entry) => {
19179
+ try {
19180
+ return await withReleaseLedger(
19181
+ path2,
19182
+ anchors,
19183
+ (current) => {
19184
+ const normalized = normalizeLedgerAnchors(anchors);
19185
+ const base = current ?? {
19186
+ schema: RELEASE_LEDGER_SCHEMA,
19187
+ repo: normalized.repo,
19188
+ tag: normalized.tag,
19189
+ tagSha: normalized.tagSha,
19190
+ phases: emptyPhases(),
19191
+ updatedAt: now().toISOString()
19192
+ };
19193
+ const previousRetries = base.phases[phase].retries ?? 0;
19194
+ const record = {
19195
+ ...entry,
19196
+ note: entry.note !== void 0 ? truncate(entry.note, MAX_TEXT) : void 0,
19197
+ error: entry.error !== void 0 ? truncate(entry.error, MAX_TEXT * 2) : void 0,
19198
+ retries: entry.retries ?? previousRetries,
19199
+ updatedAt: now().toISOString()
19200
+ };
19201
+ const updated = {
19202
+ ...base,
19203
+ phases: { ...base.phases, [phase]: record },
19204
+ updatedAt: now().toISOString()
19205
+ };
19206
+ return { ledger: updated, value: { ok: true } };
19207
+ },
19208
+ { maxWaitMs: opts.maxWaitMs }
19209
+ );
19210
+ } catch (e) {
19211
+ return { ok: false, error: e instanceof Error ? e.message : String(e) };
19212
+ }
19213
+ },
19214
+ load: async (anchors) => {
19215
+ try {
19216
+ return await withReleaseLedger(
19217
+ path2,
19218
+ anchors,
19219
+ (current) => ({ value: current }),
19220
+ { maxWaitMs: opts.maxWaitMs }
19221
+ );
19222
+ } catch (e) {
19223
+ if (e instanceof ReleaseLedgerError) throw e;
19224
+ throw new ReleaseLedgerError("unreadable", e instanceof Error ? e.message : String(e));
19225
+ }
19226
+ }
19227
+ };
19228
+ }
19229
+ function compactEntry(entry) {
19230
+ return Object.fromEntries(Object.entries(entry).filter(([, v]) => v !== void 0));
19231
+ }
19232
+ function phaseEntry(entry) {
19233
+ return compactEntry(entry);
19234
+ }
19235
+ var PHASE_RANK = { failed: 0, pending: 1, complete: 2, skipped: 2 };
19236
+ function deriveReleasePhaseStatus(ledger) {
19237
+ const legs = RELEASE_LEDGER_PHASES.map((phase) => {
19238
+ const rec = ledger.phases[phase];
19239
+ return {
19240
+ phase,
19241
+ state: rec.state,
19242
+ ...rec.runId !== void 0 ? { runId: rec.runId } : {},
19243
+ ...rec.runRepo !== void 0 ? { runRepo: rec.runRepo } : {},
19244
+ ...rec.workflow !== void 0 ? { workflow: rec.workflow } : {},
19245
+ ...rec.event !== void 0 ? { event: rec.event } : {},
19246
+ ...rec.note !== void 0 ? { note: rec.note } : {},
19247
+ ...rec.error !== void 0 ? { error: rec.error } : {}
19248
+ };
19249
+ });
19250
+ const worst = legs.reduce((acc, leg) => Math.min(acc, PHASE_RANK[leg.state]), 2);
19251
+ return { phaseStatus: worst === 0 ? "phases-failed" : worst === 1 ? "phases-pending" : "phases-green", legs };
19252
+ }
19253
+ function safeRunAction(runId, runRepo, state) {
19254
+ return state === "failed" ? `next: diagnose with gh run view ${runId} --repo ${runRepo} --log-failed; rerun by hand only when the cause is fixed: gh run rerun ${runId} --repo ${runRepo} --failed (never re-tag)` : `next: gh run watch ${runId} --repo ${runRepo} --exit-status`;
19255
+ }
19256
+ function formatReleaseLedgerReport(report) {
19257
+ const lines = [`release phase ledger: ${report.phaseStatus.toUpperCase()} (${report.path}) \u2014 phase status of the recorded boundaries, NOT the release verdict`];
19258
+ for (const leg of report.legs) {
19259
+ const detail = leg.error ?? leg.note;
19260
+ const run = [leg.workflow, leg.event ? `(${leg.event})` : void 0, leg.runId != null ? `run ${leg.runId}` : void 0, leg.runRepo ? `in ${leg.runRepo}` : void 0].filter(Boolean).join(" ");
19261
+ lines.push(` - ${leg.phase}: ${leg.state.toUpperCase()}${run ? ` \u2014 ${run}` : ""}${detail ? ` \u2014 ${detail}` : ""}`);
19262
+ if ((leg.state === "failed" || leg.state === "pending") && leg.runId != null && leg.runRepo) {
19263
+ lines.push(` ${safeRunAction(leg.runId, leg.runRepo, leg.state === "failed" ? "failed" : "pending")}`);
19264
+ }
19265
+ }
19266
+ return lines;
19267
+ }
19268
+ function releaseLedgerReport(ledger, path2) {
19269
+ if (!ledger) return void 0;
19270
+ const derived = deriveReleasePhaseStatus(ledger);
19271
+ return { path: path2, phases: ledger.phases, phaseStatus: derived.phaseStatus, legs: derived.legs };
19272
+ }
19273
+ var TRANSIENT_TRANSPORT_MARKERS = [
19274
+ /connection (?:reset|refused|aborted|timed? ?out)/i,
19275
+ /\bECONN(?:RESET|REFUSED|ABORTED|TIMEDOUT)\b/,
19276
+ /\bE(?:HOST|NET)UNREACH\b/,
19277
+ /\bEAI_AGAIN\b/,
19278
+ /\bEPIPE\b/,
19279
+ /lost communication with (?:the )?github actions runner service/i,
19280
+ /runner lost communication/i,
19281
+ /\b(?:502|503|504)\b[^0-9]{0,40}(?:bad gateway|service unavailable|gateway time-?out|server error)/i,
19282
+ /(?:bad gateway|service unavailable|gateway time-?out)/i,
19283
+ /fetch failed/i,
19284
+ /network (?:error|failure|unreachable|timeout|timed? ?out)/i,
19285
+ /socket hang ?up/i,
19286
+ /remote end hung ?up unexpectedly/i,
19287
+ /early EOF/i,
19288
+ /tls handshake timeout/i
19289
+ ];
19290
+ function classifyTransientTransportFailure(evidence) {
19291
+ if (!evidence) return false;
19292
+ return TRANSIENT_TRANSPORT_MARKERS.some((marker) => marker.test(evidence));
19293
+ }
19294
+ var TRANSIENT_EVIDENCE_MAX_CHARS = 16e3;
19295
+ var AUTO_RERUN_MAX_RETRIES = 1;
19296
+ function failedRunIdentity(run, repo) {
19297
+ return `${run.workflow}${run.event ? ` (${run.event})` : ""} run ${run.runId} in ${repo}`;
19298
+ }
19299
+ function manualRunAction(run, repo) {
19300
+ return `safe next action: diagnose with gh run view ${run.runId} --repo ${repo} --log-failed; rerun by hand only when the cause is fixed: gh run rerun ${run.runId} --repo ${repo} --failed (never re-tag)`;
19301
+ }
19302
+ async function readFailedRunsOnSha(deps, repo, sha) {
19303
+ try {
19304
+ const rows = JSON.parse(await deps.run("gh", [
19305
+ "run",
19306
+ "list",
19307
+ "--repo",
19308
+ repo,
19309
+ "--commit",
19310
+ sha,
19311
+ "--limit",
19312
+ "20",
19313
+ "--json",
19314
+ "databaseId,url,workflowName,event,status,conclusion"
19315
+ ]));
19316
+ if (!Array.isArray(rows)) return [];
19317
+ return rows.filter((r) => typeof r.databaseId === "number" && r.status === "completed" && (r.conclusion === "failure" || r.conclusion === "cancelled")).map((r) => ({
19318
+ workflow: r.workflowName ?? `run:${r.databaseId}`,
19319
+ runId: r.databaseId,
19320
+ runUrl: r.url,
19321
+ repo,
19322
+ ...r.event ? { event: r.event } : {},
19323
+ conclusion: r.conclusion ?? "failure"
19324
+ }));
19325
+ } catch {
19326
+ return [];
19327
+ }
19328
+ }
19329
+ async function transientEvidenceForRun(deps, repo, runId) {
19330
+ const parts = [];
19331
+ try {
19332
+ parts.push(await deps.run("gh", ["run", "view", String(runId), "--repo", repo, "--json", "jobs", "--jq", '[.jobs[] | select(.conclusion=="failure") | {name:.name, steps:[.steps[] | select(.conclusion=="failure") | .name]}] | tostring']));
19333
+ } catch {
19334
+ }
19335
+ try {
19336
+ parts.push(await deps.run("gh", ["run", "view", String(runId), "--repo", repo, "--log-failed"]));
19337
+ } catch {
19338
+ }
19339
+ const evidence = parts.filter(Boolean).join("\n");
19340
+ return evidence.length > TRANSIENT_EVIDENCE_MAX_CHARS ? evidence.slice(-TRANSIENT_EVIDENCE_MAX_CHARS) : evidence;
19341
+ }
19342
+ function extractFailedRequiredContexts(failure) {
19343
+ const match = /required train check failed:\s*([^\n(]+)/.exec(failure);
19344
+ if (!match) return [];
19345
+ return match[1].split(",").map((s) => s.trim()).filter(Boolean);
19346
+ }
19347
+ async function readFailedCheckRunsOnSha(deps, repo, sha) {
19348
+ try {
19349
+ const rows = JSON.parse(await deps.run("gh", [
19350
+ "api",
19351
+ `repos/${repo}/commits/${sha}/check-runs`,
19352
+ "--jq",
19353
+ '[.check_runs[] | select(.status == "completed" and .conclusion != "success" and .conclusion != null) | {name: .name, url: .details_url}]'
19354
+ ]));
19355
+ if (!Array.isArray(rows)) return [];
19356
+ return rows.flatMap((r) => {
19357
+ if (typeof r.name !== "string" || typeof r.url !== "string") return [];
19358
+ const id = /\/actions\/runs\/(\d+)/.exec(r.url);
19359
+ return [{ name: r.name, ...id ? { runId: Number(id[1]) } : {} }];
19360
+ });
19361
+ } catch {
19362
+ return [];
19363
+ }
19364
+ }
19365
+ async function correlateCandidateRunsToRequiredContexts(deps, repo, sha, candidates, contexts) {
19366
+ if (contexts.length === 0) {
19367
+ return {
19368
+ unique: false,
19369
+ note: `the failure names no failed required context \u2014 rerunning the first failed run could rerun an unrelated workflow; manual intervention required; safe next action: gh run list --repo ${repo} --commit ${sha.slice(0, 12)} to identify the run`
19370
+ };
19371
+ }
19372
+ const failedChecks = await readFailedCheckRunsOnSha(deps, repo, sha);
19373
+ const contextRunIds = /* @__PURE__ */ new Map();
19374
+ for (const check of failedChecks) {
19375
+ if (check.runId != null && contexts.includes(check.name)) contextRunIds.set(check.runId, check.name);
19376
+ }
19377
+ const proven = candidates.filter((c) => c.runId != null && contextRunIds.has(c.runId));
19378
+ if (proven.length === 1) return { unique: true, candidate: proven[0] };
19379
+ if (proven.length > 1) {
19380
+ return {
19381
+ unique: false,
19382
+ note: `${proven.length} failed runs correspond to the failed required contexts (${contexts.join(", ")}): ${proven.map((c) => failedRunIdentity(c, c.repo ?? repo)).join("; ")} \u2014 ambiguous which to rerun; manual intervention required; safe next action: gh run view <run-id> --repo ${repo} --log-failed`
19383
+ };
19384
+ }
19385
+ return {
19386
+ unique: false,
19387
+ note: `no enumerated failed run corresponds to the failed required contexts (${contexts.join(", ")}) \u2014 the failed run(s) are unrelated to the required gate; manual intervention required; safe next action: gh run view <run-id> --repo ${repo} --log-failed`
19388
+ };
19389
+ }
19390
+ async function transientRecoveryForFailedPhase(deps, input) {
19391
+ const { ledger, anchors, phase, sha, failure } = input;
19392
+ const enumerated = !input.runs;
19393
+ const provenance = input.runsProvenance ?? "sha";
19394
+ const recordProvenance = provenance === "watched" ? "dispatch" : "sha";
19395
+ let candidates = (input.runs ?? await transientRecoveryEnumerate(deps, input.repo, sha)).filter((r) => typeof r.runId === "number" && (r.conclusion === "failure" || r.conclusion === "cancelled"));
19396
+ const first = candidates[0];
19397
+ await ledger.record(anchors, phase, phaseEntry({
19398
+ state: "failed",
19399
+ sha,
19400
+ error: failure,
19401
+ ...first ? {
19402
+ runId: first.runId,
19403
+ ...first.runUrl ? { runUrl: first.runUrl } : {},
19404
+ runRepo: first.repo ?? input.repo,
19405
+ runProvenance: recordProvenance,
19406
+ workflow: first.workflow,
19407
+ ...first.event ? { event: first.event } : {}
19408
+ } : {}
19409
+ }));
19410
+ const recorded = await ledger.load(anchors).catch(() => void 0);
19411
+ const retriesSoFar = recorded?.phases[phase].retries ?? 0;
19412
+ if (retriesSoFar >= AUTO_RERUN_MAX_RETRIES) {
19413
+ const spent = first ? ` for ${failedRunIdentity(first, first.repo ?? input.repo)}` : "";
19414
+ return {
19415
+ reran: false,
19416
+ recovered: false,
19417
+ ...first?.runId != null ? { runId: first.runId, runRepo: first.repo ?? input.repo } : {},
19418
+ note: `automatic rerun already spent for ${phase}${spent} \u2014 manual intervention required; ${first?.runId != null ? manualRunAction(first, first.repo ?? input.repo) : "safe next action: identify the failed run on the Actions tab"}`
19419
+ };
19420
+ }
19421
+ if (candidates.length === 0) {
19422
+ return { reran: false, recovered: false, note: `no failed run with an id could be classified on ${sha.slice(0, 12)} \u2014 manual intervention required (no marker proof, no automatic rerun); safe next action: gh run list --repo ${input.repo} --commit ${sha.slice(0, 12)} to identify the run` };
19423
+ }
19424
+ if (enumerated && phase === "ordinaryCi") {
19425
+ const correlation = await correlateCandidateRunsToRequiredContexts(deps, input.repo, sha, candidates, extractFailedRequiredContexts(failure));
19426
+ if (!correlation.unique) return { reran: false, recovered: false, note: correlation.note };
19427
+ candidates = [correlation.candidate];
19428
+ }
19429
+ const candidate = candidates[0];
19430
+ const runRepo = candidate.repo ?? input.repo;
19431
+ let meta;
19432
+ try {
19433
+ meta = JSON.parse(await deps.run("gh", [
19434
+ "run",
19435
+ "view",
19436
+ String(candidate.runId),
19437
+ "--repo",
19438
+ runRepo,
19439
+ "--json",
19440
+ "headSha,status,conclusion,url"
19441
+ ]));
19442
+ } catch (e) {
19443
+ return { reran: false, recovered: false, note: `could not READ failed run ${failedRunIdentity(candidate, runRepo)} to prove its identity (${e instanceof Error ? e.message : String(e)}) \u2014 no automatic rerun without proof; ${manualRunAction(candidate, runRepo)}` };
19444
+ }
19445
+ const shaProven = provenance === "watched" || (meta.headSha ?? "").toLowerCase() === sha.toLowerCase();
19446
+ if (!shaProven || meta.conclusion !== "failure") {
19447
+ return { reran: false, recovered: false, note: `run ${failedRunIdentity(candidate, runRepo)} does not prove a failed run on ${sha.slice(0, 12)} \u2014 no automatic rerun; ${manualRunAction(candidate, runRepo)}` };
19448
+ }
19449
+ const evidence = await transientEvidenceForRun(deps, runRepo, candidate.runId);
19450
+ if (!classifyTransientTransportFailure(evidence)) {
19451
+ return { reran: false, recovered: false, note: `run ${failedRunIdentity(candidate, runRepo)} failed on evidence that is NOT a proven transient transport/network failure \u2014 manual intervention required; ${manualRunAction(candidate, runRepo)}` };
19452
+ }
19453
+ const reservation = await ledger.record(anchors, phase, phaseEntry({
19454
+ state: "pending",
19455
+ sha,
19456
+ runId: candidate.runId,
19457
+ ...meta.url ?? candidate.runUrl ? { runUrl: meta.url ?? candidate.runUrl } : {},
19458
+ runRepo,
19459
+ runProvenance: recordProvenance,
19460
+ workflow: candidate.workflow,
19461
+ ...candidate.event ? { event: candidate.event } : {},
19462
+ note: "ONE automatic transient rerun RESERVED (cap spent durably) \u2014 dispatch pending",
19463
+ retries: retriesSoFar + 1
19464
+ }));
19465
+ if (!reservation.ok) {
19466
+ return { reran: false, recovered: false, note: `the rerun reservation could not be durably recorded (${reservation.error}) \u2014 no automatic rerun without a durable cap; manual intervention required; ${manualRunAction(candidate, runRepo)}` };
19467
+ }
19468
+ try {
19469
+ await deps.run("gh", ["run", "rerun", String(candidate.runId), "--repo", runRepo, "--failed"]);
19470
+ } catch (e) {
19471
+ await ledger.record(anchors, phase, phaseEntry({
19472
+ state: "failed",
19473
+ sha,
19474
+ runId: candidate.runId,
19475
+ ...meta.url ?? candidate.runUrl ? { runUrl: meta.url ?? candidate.runUrl } : {},
19476
+ runRepo,
19477
+ runProvenance: recordProvenance,
19478
+ workflow: candidate.workflow,
19479
+ ...candidate.event ? { event: candidate.event } : {},
19480
+ error: `automatic rerun dispatch failed: ${e instanceof Error ? e.message : String(e)}`,
19481
+ retries: retriesSoFar + 1
19482
+ }));
19483
+ return {
19484
+ reran: true,
19485
+ recovered: false,
19486
+ runId: candidate.runId,
19487
+ runRepo,
19488
+ note: `automatic rerun of ${failedRunIdentity(candidate, runRepo)} FAILED to dispatch (${e instanceof Error ? e.message : String(e)}) \u2014 the one automatic rerun remains spent; manual intervention required; ${manualRunAction(candidate, runRepo)}`
19489
+ };
19490
+ }
19491
+ const watched = await (input.watchRun ?? defaultWatchRun)(deps, candidate.runId, runRepo);
19492
+ const runFields = {
19493
+ sha,
19494
+ runId: candidate.runId,
19495
+ ...meta.url ?? candidate.runUrl ? { runUrl: meta.url ?? candidate.runUrl } : {},
19496
+ runRepo,
19497
+ runProvenance: recordProvenance,
19498
+ workflow: candidate.workflow,
19499
+ ...candidate.event ? { event: candidate.event } : {}
19500
+ };
19501
+ if (watched === "success") {
19502
+ await ledger.record(anchors, phase, phaseEntry({
19503
+ state: "complete",
19504
+ ...runFields,
19505
+ note: `recovered: the ONE automatic rerun was watched green in this invocation`,
19506
+ retries: retriesSoFar + 1
19507
+ }));
19508
+ return {
19509
+ reran: true,
19510
+ recovered: true,
19511
+ runId: candidate.runId,
19512
+ runRepo,
19513
+ note: `${failedRunIdentity(candidate, runRepo)} was a proven transient transport failure \u2014 the ONE automatic rerun was dispatched and watched GREEN; continuing in this invocation`
19514
+ };
19515
+ }
19516
+ if (watched === "failure") {
19517
+ await ledger.record(anchors, phase, phaseEntry({
19518
+ state: "failed",
19519
+ ...runFields,
19520
+ error: "the automatic rerun was watched FAILURE \u2014 not transient after all",
19521
+ retries: retriesSoFar + 1
19522
+ }));
19523
+ return {
19524
+ reran: true,
19525
+ recovered: false,
19526
+ runId: candidate.runId,
19527
+ runRepo,
19528
+ note: `the automatic rerun of ${failedRunIdentity(candidate, runRepo)} FAILED \u2014 the one automatic rerun is spent; manual intervention required; ${manualRunAction(candidate, runRepo)}`
19529
+ };
19530
+ }
19531
+ return {
19532
+ reran: true,
19533
+ recovered: false,
19534
+ runId: candidate.runId,
19535
+ runRepo,
19536
+ note: `the automatic rerun of ${failedRunIdentity(candidate, runRepo)} did not resolve within the bounded watch \u2014 typed PENDING (the one automatic rerun is spent); safe next action: gh run watch ${candidate.runId} --repo ${runRepo} --exit-status`
19537
+ };
19538
+ }
19539
+ async function transientRecoveryEnumerate(deps, repo, sha) {
19540
+ return readFailedRunsOnSha(deps, repo, sha);
19541
+ }
19542
+ var RUN_CONFIRM_ATTEMPTS2 = 3;
19543
+ var RUN_CONFIRM_DELAY_MS2 = 1e3;
19544
+ async function defaultWatchRun(deps, runId, repo) {
19545
+ let watchSaidSuccess = true;
19546
+ try {
19547
+ await deps.run("gh", ["run", "watch", String(runId), "--repo", repo, "--exit-status"]);
19548
+ } catch {
19549
+ watchSaidSuccess = false;
19550
+ }
19551
+ let confirmError;
19552
+ for (let attempt = 0; attempt < RUN_CONFIRM_ATTEMPTS2; attempt++) {
19553
+ if (attempt > 0) await new Promise((resolve7) => setTimeout(resolve7, RUN_CONFIRM_DELAY_MS2));
19554
+ try {
19555
+ const parsed = JSON.parse(await deps.run("gh", ["run", "view", String(runId), "--repo", repo, "--json", "status,conclusion"]));
19556
+ if (parsed.status === "completed") return parsed.conclusion === "success" ? "success" : "failure";
19557
+ confirmError = void 0;
19558
+ } catch (e) {
19559
+ confirmError = e;
19560
+ }
19561
+ }
19562
+ if (!watchSaidSuccess) return "failure";
19563
+ if (confirmError) return "pending";
19564
+ return "success";
19565
+ }
19566
+ function alignmentPhaseEntry(dev, rc) {
19567
+ if (!dev && !rc) return { state: "skipped", note: "no protected-branch alignment leg on this release" };
19568
+ const pending = dev?.status === "pr-pending" || rc?.status === "pr-pending";
19569
+ const note = [dev ? `development: ${dev.note}` : void 0, rc ? `rc: ${rc.note}` : void 0].filter(Boolean).join("; ");
19570
+ return { state: pending ? "pending" : "complete", note };
19571
+ }
19572
+ function runRowState(run, fallback) {
19573
+ if (!run) return fallback === "success" ? "complete" : fallback === "failure" ? "failed" : "pending";
19574
+ return run.conclusion === "success" ? "complete" : run.conclusion === "failure" ? "failed" : "pending";
19575
+ }
19576
+ function runMetadata(run, runRepo, provenance) {
19577
+ return {
19578
+ ...run.runId != null ? { runId: run.runId } : {},
19579
+ ...run.runUrl != null ? { runUrl: run.runUrl } : {},
19580
+ ...run.runId != null ? { runRepo, runProvenance: provenance } : {},
19581
+ ...run.workflow ? { workflow: run.workflow } : {},
19582
+ ...run.event ? { event: run.event } : {}
19583
+ };
19584
+ }
19585
+ function releaseRunRepoFor(model, productRepo) {
19586
+ return model === "hub-serverless" || isCentralDispatchModel(model) ? HUB_REPO2 : productRepo;
19587
+ }
19588
+ function deployPhaseInput(model, dispatch, opts = {}) {
19589
+ const sha = opts.releaseSha;
19590
+ const runRepo = releaseRunRepoFor(model, "");
19591
+ switch (model) {
19592
+ case "hub-serverless": {
19593
+ const deployRun = (dispatch.workflowRuns ?? []).find((r) => r.workflow === "deploy.yml");
19594
+ return {
19595
+ state: runRowState(deployRun, dispatch.deployStatus),
19596
+ ...sha ? { sha } : {},
19597
+ ...runMetadata(deployRun ?? {}, runRepo, "sha"),
19598
+ note: deployRun ? `${deployRun.workflow} release run` : dispatch.note
19599
+ };
19600
+ }
19601
+ case "registry-publish":
19602
+ return { state: "skipped", ...sha ? { sha } : {}, note: "registry-publish deploys by publishing \u2014 the release-event publish.yml run is the deploy plane" };
19603
+ case "tenant-container":
19604
+ case "solo-container":
19605
+ case "static-cdn":
19606
+ return {
19607
+ state: dispatch.deployStatus === "success" ? "complete" : dispatch.deployStatus === "failure" ? "failed" : "pending",
19608
+ ...sha ? { sha } : {},
19609
+ ...runMetadata({ runId: dispatch.runId, runUrl: dispatch.runUrl, workflow: "tenant-deploy.yml", event: "workflow_dispatch" }, runRepo, "dispatch"),
19610
+ note: dispatch.note
19611
+ };
19612
+ default:
19613
+ return {
19614
+ state: dispatch.deployStatus === "success" ? "complete" : dispatch.deployStatus === "failure" ? "failed" : "pending",
19615
+ ...sha ? { sha } : {},
19616
+ note: dispatch.note
19617
+ };
19618
+ }
19619
+ }
19620
+ function publishPhaseInput(model, dispatch, publishDispatch, publishRequired, opts = {}) {
19621
+ const sha = opts.releaseSha;
19622
+ const publishRow = publishDispatch ? {
19623
+ state: publishDispatch.deployStatus === "success" ? "complete" : publishDispatch.deployStatus === "failure" ? "failed" : "pending",
19624
+ ...runMetadata({ runId: publishDispatch.runId, runUrl: publishDispatch.runUrl, workflow: "tenant-publish.yml", event: "workflow_dispatch" }, HUB_REPO2, "dispatch"),
19625
+ note: publishDispatch.note
19626
+ } : void 0;
19627
+ switch (model) {
19628
+ case "hub-serverless": {
19629
+ const publishRun = (dispatch.workflowRuns ?? []).find((r) => r.workflow === "publish.yml");
19630
+ return publishRow ?? {
19631
+ state: runRowState(publishRun, dispatch.deployStatus),
19632
+ ...sha ? { sha } : {},
19633
+ ...runMetadata(publishRun ?? {}, HUB_REPO2, "sha"),
19634
+ note: publishRun ? `${publishRun.workflow} release run` : dispatch.note
19635
+ };
19636
+ }
19637
+ case "registry-publish": {
19638
+ const publishRun = (dispatch.workflowRuns ?? []).find((r) => r.workflow === "publish.yml");
19639
+ return publishRow ?? {
19640
+ state: runRowState(publishRun, dispatch.deployStatus),
19641
+ ...sha ? { sha } : {},
19642
+ ...runMetadata(publishRun ?? {}, opts.repo ?? "", "sha"),
19643
+ note: publishRun ? `${publishRun.workflow} release run` : dispatch.note
19644
+ };
19645
+ }
19646
+ case "tenant-container":
19647
+ case "solo-container":
19648
+ case "static-cdn":
19649
+ return publishRow ?? (publishRequired ? { state: "skipped", ...sha ? { sha } : {}, note: opts.skipPublishNote ?? "publish skipped \u2014 deploy not confirmed" } : { state: "skipped", ...sha ? { sha } : {}, note: "publishRequired=false \u2014 no publish leg" });
19650
+ default:
19651
+ return { state: "skipped", ...sha ? { sha } : {}, note: `${model} has no publish leg` };
19652
+ }
19653
+ }
19654
+ function phaseInputsFromRunRows(model, rows, tagSha, opts = {}) {
19655
+ const deployRun = rows.find((r) => r.workflow === "deploy.yml");
19656
+ const publishRun = rows.find((r) => r.workflow === "publish.yml");
19657
+ switch (model) {
19658
+ case "hub-serverless":
19659
+ return {
19660
+ deploy: {
19661
+ state: runRowState(deployRun, "pending"),
19662
+ sha: tagSha,
19663
+ ...runMetadata(deployRun ?? {}, HUB_REPO2, "sha"),
19664
+ note: deployRun ? `${deployRun.workflow} run on the release SHA` : "deploy.yml release run not yet correlated"
19665
+ },
19666
+ publish: publishRun ? {
19667
+ state: runRowState(publishRun, "pending"),
19668
+ sha: tagSha,
19669
+ ...runMetadata(publishRun, HUB_REPO2, "sha"),
19670
+ note: `${publishRun.workflow} run on the release SHA`
19671
+ } : { state: "pending", sha: tagSha, note: "publish.yml release run not yet correlated" }
19672
+ };
19673
+ case "registry-publish":
19674
+ return {
19675
+ deploy: { state: "skipped", sha: tagSha, note: "registry-publish deploys by publishing \u2014 the release-event publish.yml run is the deploy plane" },
19676
+ publish: publishRun ? {
19677
+ state: runRowState(publishRun, "pending"),
19678
+ sha: tagSha,
19679
+ ...runMetadata(publishRun, opts.repo ?? "", "sha"),
19680
+ note: `${publishRun.workflow} run on the release SHA`
19681
+ } : { state: "pending", sha: tagSha, note: "publish.yml release run not yet correlated" }
19682
+ };
19683
+ default:
19684
+ return {
19685
+ deploy: { state: "pending", sha: tagSha, note: `${model} deploys outside the release SHA's run set \u2014 historical outcome uncorrelatable` },
19686
+ publish: { state: "skipped", sha: tagSha, note: "no historical publish leg correlated for this model" }
19687
+ };
19688
+ }
19689
+ }
19690
+ async function reverifyPersistedRunPhases(deps, path2, anchors, input) {
19691
+ const now = input.now ?? (() => /* @__PURE__ */ new Date());
19692
+ const notes = [];
19693
+ const result = await withReleaseLedger(
19694
+ path2,
19695
+ anchors,
19696
+ async (current) => {
19697
+ if (!current) return { value: { ledger: void 0, notes } };
19698
+ const phases = { ...current.phases };
19699
+ for (const phase of ["deploy", "publish"]) {
19700
+ const rec = phases[phase];
19701
+ if (rec.updatedAt === "never") continue;
19702
+ if (rec.runId == null) {
19703
+ if (rec.state === "complete") notes.push(`${phase}: persisted complete without a run id \u2014 the live deploy/publish re-derivation below decides the leg`);
19704
+ continue;
19705
+ }
19706
+ if (rec.runProvenance !== "sha" && rec.runProvenance !== "dispatch") {
19707
+ throw new ReleaseLedgerError(
19708
+ "invalid-record",
19709
+ `the persisted ${phase} boundary names run ${rec.runId} without valid runProvenance ('sha'|'dispatch' required, got ${JSON.stringify(rec.runProvenance ?? null)}) \u2014 a run-backed boundary without provenance cannot be re-verified; failing closed (#5987)`
19710
+ );
19711
+ }
19712
+ const runRepo = rec.runRepo ?? input.repo;
19713
+ let meta;
19714
+ try {
19715
+ meta = JSON.parse(await deps.run("gh", [
19716
+ "run",
19717
+ "view",
19718
+ String(rec.runId),
19719
+ "--repo",
19720
+ runRepo,
19721
+ "--json",
19722
+ "status,conclusion,headSha,url"
19723
+ ]));
19724
+ } catch (e) {
19725
+ throw new ReleaseLedgerError(
19726
+ "unreadable",
19727
+ `resume could not re-verify the persisted ${phase} boundary (run ${rec.runId} in ${runRepo}: ${e instanceof Error ? e.message : String(e)}) \u2014 a failed live read is unverified, not passable; retry when GitHub answers (#5987/#4713)`
19728
+ );
19729
+ }
19730
+ if (rec.runProvenance === "sha" && (meta.headSha ?? "").toLowerCase() !== input.sha.toLowerCase()) {
19731
+ throw new ReleaseLedgerError(
19732
+ "tag-sha-mismatch",
19733
+ `the persisted ${phase} boundary names run ${rec.runId} at ${(meta.headSha ?? "").slice(0, 12)} but the release tag is ${input.sha.slice(0, 12)} \u2014 ledger contradicted by live proof; failing closed (#5987)`
19734
+ );
19735
+ }
19736
+ const liveLabel = `${rec.workflow ? `${rec.workflow}${rec.event ? ` (${rec.event})` : ""} ` : ""}run ${rec.runId} in ${runRepo}`;
19737
+ if (meta.status !== "completed") {
19738
+ phases[phase] = { ...rec, state: "pending", note: `re-verified live: ${liveLabel} still in flight`, updatedAt: now().toISOString() };
19739
+ notes.push(`${phase}: ${liveLabel} still in flight \u2014 leg re-verified as pending`);
19740
+ continue;
19741
+ }
19742
+ if (meta.conclusion === "success") {
19743
+ if (rec.state !== "complete") {
19744
+ phases[phase] = { ...rec, state: "complete", note: `re-verified live: ${liveLabel} concluded success`, updatedAt: now().toISOString() };
19745
+ notes.push(`${phase}: ${liveLabel} re-verified green live`);
19746
+ }
19747
+ continue;
19748
+ }
19749
+ if (rec.state === "complete") {
19750
+ throw new ReleaseLedgerError(
19751
+ "contradicted",
19752
+ `the persisted ${phase} boundary says COMPLETE but live ${liveLabel} concluded ${meta.conclusion ?? "not-success"} \u2014 ledger contradicted by live GitHub proof; failing closed instead of trusting either (#5987)`
19753
+ );
19754
+ }
19755
+ phases[phase] = { ...rec, state: "failed", error: `live re-verification: ${liveLabel} concluded ${meta.conclusion}`, updatedAt: now().toISOString() };
19756
+ notes.push(`${phase}: ${liveLabel} failed live \u2014 leg re-verified as failed`);
19757
+ }
19758
+ const ledger = { ...current, phases, updatedAt: now().toISOString() };
19759
+ return { ledger, value: { ledger, notes } };
19760
+ }
19761
+ );
19762
+ return result;
19763
+ }
19764
+ async function clearLedgerFile(path2, anchors, maxWaitMs) {
19765
+ return withFileLock(`${path2}.lock`, { label: "release ledger", maxWaitMs }, async () => {
19766
+ let raw;
19767
+ try {
19768
+ raw = (0, import_node_fs19.readFileSync)(path2, "utf8");
19769
+ } catch (e) {
19770
+ if (e.code === "ENOENT") return { cleared: false };
19771
+ return { cleared: false, note: `release ledger at ${path2} could not be read for clearing (${e instanceof Error ? e.message : String(e)}) \u2014 left in place` };
19772
+ }
19773
+ let ledger;
19774
+ try {
19775
+ ledger = parseReleaseLedger(raw);
19776
+ } catch (e) {
19777
+ return { cleared: false, note: `release ledger at ${path2} is unreadable (${e instanceof Error ? e.message : String(e)}) \u2014 left in place; inspect it by hand` };
19778
+ }
19779
+ try {
19780
+ validateLedgerAnchors(ledger, anchors);
19781
+ } catch (e) {
19782
+ return { cleared: false, note: `release ledger at ${path2} belongs to ${ledger.tag} @ ${ledger.tagSha.slice(0, 12)} \u2014 not this release; left in place (${e instanceof Error ? e.message : String(e)})` };
19783
+ }
19784
+ (0, import_node_fs19.unlinkSync)(path2);
19785
+ return { cleared: true, note: `cleared the release ledger at ${path2} for the live-proven aborted ${anchors.tag}` };
19786
+ });
19787
+ }
19788
+ async function clearReleaseLedger(cwd, anchors, opts = {}) {
19789
+ let path2;
19790
+ try {
19791
+ path2 = releaseLedgerPath(cwd);
19792
+ } catch {
19793
+ return void 0;
19794
+ }
19795
+ return (await clearLedgerFile(path2, anchors, opts.maxWaitMs)).note;
19796
+ }
19797
+ async function reconcileLedgerAfterFailedTagPush(deps, ledger, anchors) {
19798
+ let remoteOut;
19799
+ try {
19800
+ remoteOut = await deps.run("git", ["ls-remote", "origin", `refs/tags/${anchors.tag}`]);
19801
+ } catch (e) {
19802
+ return {
19803
+ action: "kept-unproven",
19804
+ note: `the tag-existence read for ${anchors.tag} FAILED (${e instanceof Error ? e.message : String(e)}) \u2014 the just-started all-pending ledger is LEFT in place (never cleared on a failed read); it will block the next release until this release is resolved by hand (#5987)`
19805
+ };
19806
+ }
19807
+ const remoteSha = remoteOut.trim().split(/\s+/)[0] ?? "";
19808
+ if (remoteSha.toLowerCase() === anchors.tagSha.toLowerCase()) {
19809
+ return {
19810
+ action: "kept-present",
19811
+ note: `origin shows ${anchors.tag} at this release SHA ${remoteSha.slice(0, 12)} \u2014 the partial release state stands and the ledger is kept; resume with mmi-cli devops release --resume (do not re-tag) (#5987)`
19812
+ };
19813
+ }
19814
+ let clear;
19815
+ try {
19816
+ clear = await clearLedgerFile(ledger.path, anchors);
19817
+ } catch (e) {
19818
+ clear = { cleared: false, note: `clear failed: ${e instanceof Error ? e.message : String(e)}` };
19819
+ }
19820
+ if (!clear.cleared && clear.note) {
19821
+ return { action: "kept-unproven", note: `the just-started ledger could not be cleared after origin proved this run has no resumable tag (${clear.note}) \u2014 resolve it by hand (#5987)` };
19822
+ }
19823
+ return {
19824
+ action: "cleared",
19825
+ note: remoteSha ? `origin shows ${anchors.tag} at foreign SHA ${remoteSha.slice(0, 12)}, not this run's ${anchors.tagSha.slice(0, 12)} \u2014 this run's just-started ledger was cleared; let the owning release finish before retrying (#5987)` : `origin proves ${anchors.tag} was never pushed (no tag on origin) \u2014 the just-started all-pending ledger was cleared, so the next release is not blocked; rerun the release once the failure above is fixed (#5987)`
19826
+ };
19827
+ }
19828
+ async function startReleaseLedger(ledger, anchors, opts = {}) {
19829
+ const now = opts.now ?? (() => /* @__PURE__ */ new Date());
19830
+ try {
19831
+ return await withReleaseLedger(
19832
+ ledger.path,
19833
+ anchors,
19834
+ (current) => {
19835
+ if (current) return { value: { ok: true } };
19836
+ const normalized = normalizeLedgerAnchors(anchors);
19837
+ const fresh = {
19838
+ schema: RELEASE_LEDGER_SCHEMA,
19839
+ repo: normalized.repo,
19840
+ tag: normalized.tag,
19841
+ tagSha: normalized.tagSha,
19842
+ phases: emptyPhases(),
19843
+ updatedAt: now().toISOString()
19844
+ };
19845
+ return { ledger: fresh, value: { ok: true } };
19846
+ }
19847
+ );
19848
+ } catch (e) {
19849
+ return { ok: false, error: e instanceof Error ? e.message : String(e) };
19850
+ }
19851
+ }
19852
+ async function beginReleaseLedger(deps, cwd, targetTag) {
19853
+ let path2;
19854
+ try {
19855
+ path2 = releaseLedgerPath(cwd);
19856
+ } catch {
19857
+ return { ok: true };
19858
+ }
19859
+ return withFileLock(`${path2}.lock`, { label: "release ledger" }, async () => {
19860
+ let raw;
19861
+ try {
19862
+ raw = (0, import_node_fs19.readFileSync)(path2, "utf8");
19863
+ } catch (e) {
19864
+ if (e.code === "ENOENT") return { ok: true };
19865
+ return { ok: false, error: `the release ledger at ${path2} could not be READ (${e instanceof Error ? e.message : String(e)}) \u2014 a failed read is unverified, not absent (#5987/#4713)` };
19866
+ }
19867
+ let prior;
19868
+ try {
19869
+ prior = parseReleaseLedger(raw);
19870
+ } catch (e) {
19871
+ return { ok: false, error: `the active release ledger at ${path2} is unreadable (${e instanceof Error ? e.message : String(e)}) \u2014 resolve it by hand before starting a release (#5987)` };
19872
+ }
19873
+ if (targetTag && prior.tag === targetTag) return { ok: true };
19874
+ const derived = deriveReleasePhaseStatus(prior);
19875
+ if (derived.phaseStatus !== "phases-green") {
19876
+ const open2 = derived.legs.filter((l) => l.state === "pending" || l.state === "failed");
19877
+ return {
19878
+ ok: false,
19879
+ error: `a prior release ledger for ${prior.tag} is ${derived.phaseStatus} (${open2.map((l) => `${l.phase}=${l.state}`).join(", ")}) \u2014 a new release must not stack on an unresolved one. Finish it first: mmi-cli devops release --resume (or, only when proven unpublished, mmi-cli devops release --abort --apply) (#5987)`
19880
+ };
19881
+ }
19882
+ let liveSha = "";
19883
+ try {
19884
+ liveSha = (await deps.run("git", ["ls-remote", "origin", `refs/tags/${prior.tag}`])).trim().split(/\s+/)[0] ?? "";
19885
+ } catch (e) {
19886
+ return { ok: false, error: `could not prove the completed ${prior.tag} live (${e instanceof Error ? e.message : String(e)}) \u2014 refusing to archive the ledger; retry when origin answers (#5987/#4713)` };
19887
+ }
19888
+ if (liveSha.toLowerCase() !== prior.tagSha.toLowerCase()) {
19889
+ return { ok: false, error: `the completed ${prior.tag} ledger says phases-green but origin reports ${liveSha.slice(0, 12) || "no tag"} \u2260 ${prior.tagSha.slice(0, 12)} \u2014 refusing to archive on unproven state; resolve by hand (#5987)` };
19890
+ }
19891
+ const archivePath = (0, import_node_path18.join)((0, import_node_path18.dirname)(path2), `phases.archive-${prior.tag}.json`);
19892
+ try {
19893
+ (0, import_node_fs19.unlinkSync)(archivePath);
19894
+ } catch (e) {
19895
+ if (e.code !== "ENOENT") {
19896
+ return { ok: false, error: `could not clear the stale archive at ${archivePath} (${e instanceof Error ? e.message : String(e)}) \u2014 the ledger for ${prior.tag} stays active (#5987)` };
19897
+ }
19898
+ }
19899
+ (0, import_node_fs19.renameSync)(path2, archivePath);
19900
+ return { ok: true, note: `archived the completed release ledger ${prior.tag} to ${archivePath} (live-proven at ${prior.tagSha.slice(0, 12)})` };
19901
+ });
19902
+ }
19903
+
18821
19904
  // src/train-apply-phases.ts
18822
19905
  var TRAIN_BUMP_INTENTS = ["major", "minor", "patch"];
18823
19906
  function readLocalGateWorkflows() {
18824
- const dir = (0, import_node_path17.join)(".github", "workflows");
19907
+ const dir = (0, import_node_path19.join)(".github", "workflows");
18825
19908
  let names;
18826
19909
  try {
18827
- names = (0, import_node_fs19.readdirSync)(dir);
19910
+ names = (0, import_node_fs20.readdirSync)(dir);
18828
19911
  } catch {
18829
19912
  return null;
18830
19913
  }
18831
19914
  const files = [];
18832
19915
  for (const name of names.filter(isGateWorkflowPath)) {
18833
19916
  try {
18834
- files.push({ path: `${dir}/${name}`.replace(/\\/g, "/"), body: (0, import_node_fs19.readFileSync)((0, import_node_path17.join)(dir, name), "utf8") });
19917
+ files.push({ path: `${dir}/${name}`.replace(/\\/g, "/"), body: (0, import_node_fs20.readFileSync)((0, import_node_path19.join)(dir, name), "utf8") });
18835
19918
  } catch {
18836
19919
  }
18837
19920
  }
18838
19921
  return files;
18839
19922
  }
18840
19923
  function readLocalWorkflows() {
18841
- const dir = (0, import_node_path17.join)(".github", "workflows");
19924
+ const dir = (0, import_node_path19.join)(".github", "workflows");
18842
19925
  let names;
18843
19926
  try {
18844
- names = (0, import_node_fs19.readdirSync)(dir);
19927
+ names = (0, import_node_fs20.readdirSync)(dir);
18845
19928
  } catch {
18846
19929
  return null;
18847
19930
  }
18848
19931
  const files = [];
18849
19932
  for (const name of names.filter((n) => /\.ya?ml$/i.test(n))) {
18850
19933
  try {
18851
- files.push({ path: `${dir}/${name}`.replace(/\\/g, "/"), body: (0, import_node_fs19.readFileSync)((0, import_node_path17.join)(dir, name), "utf8") });
19934
+ files.push({ path: `${dir}/${name}`.replace(/\\/g, "/"), body: (0, import_node_fs20.readFileSync)((0, import_node_path19.join)(dir, name), "utf8") });
18852
19935
  } catch {
18853
19936
  }
18854
19937
  }
@@ -18986,7 +20069,7 @@ async function waitForFoldIndexLock(deps, startBranch, resumeCommand) {
18986
20069
  const deadline = now() + GIT_INDEX_LOCK_WAIT_MS;
18987
20070
  for (; ; ) {
18988
20071
  try {
18989
- await (0, import_promises2.stat)(lockPath);
20072
+ await (0, import_promises3.stat)(lockPath);
18990
20073
  } catch (e) {
18991
20074
  if (e.code === "ENOENT") return;
18992
20075
  throw foldFailureGuidance(
@@ -19316,8 +20399,35 @@ async function gateTrueMergeCommit(deps, ctx, tag, releaseSha, requiredChecks) {
19316
20399
  }
19317
20400
  }
19318
20401
  }
19319
- async function completeMainRelease(deps, ctx, meta, deployModel, watch, options, tag, releaseSha, startBranch, preFold, tagProbe, branchHints) {
20402
+ async function recordPhase(ledger, deps, anchors, phase, entry, opts = {}) {
20403
+ if (!ledger) return;
20404
+ const res = await ledger.record(anchors, phase, entry);
20405
+ if (!res.ok) {
20406
+ const message = `release ledger write FAILED for phase '${phase}' (${res.error}) - already landed and immutable: ${opts.landed ?? "no boundary recorded yet"}. Nothing is rolled back, but this run stops here rather than mutating untracked: resolve the ledger at the recorded path and rerun mmi-cli devops release --resume (#5987)`;
20407
+ if (opts.strict !== false) throw new Error(message);
20408
+ deps.warn?.(message);
20409
+ }
20410
+ }
20411
+ async function recoverPhaseWithMaybeTransientRerun(deps, ledger, anchors, phase, input) {
20412
+ if (!ledger) return { reran: false, recovered: false, note: "" };
20413
+ try {
20414
+ return await transientRecoveryForFailedPhase(deps, {
20415
+ ledger,
20416
+ anchors,
20417
+ phase,
20418
+ repo: input.repo,
20419
+ sha: input.sha,
20420
+ failure: input.failure,
20421
+ ...input.runs ? { runs: input.runs } : {},
20422
+ runsProvenance: input.watched ? "watched" : "sha"
20423
+ });
20424
+ } catch (e) {
20425
+ return { reran: false, recovered: false, note: `release ledger: the transient recovery read failed (${e instanceof Error ? e.message : String(e)}) - manual intervention required` };
20426
+ }
20427
+ }
20428
+ async function completeMainRelease(deps, ctx, meta, deployModel, watch, options, tag, releaseSha, startBranch, preFold, tagProbe, branchHints, ledger) {
19320
20429
  const resumeCommand = options.dev ? "mmi-cli devops release --dev --apply" : "mmi-cli devops release --apply";
20430
+ const ledgerAnchors = { repo: ctx.repo, tag, tagSha: releaseSha };
19321
20431
  try {
19322
20432
  await verifyPublishDryRun(deps, ctx, meta, deployModel);
19323
20433
  } catch (e) {
@@ -19344,45 +20454,128 @@ async function completeMainRelease(deps, ctx, meta, deployModel, watch, options,
19344
20454
  }
19345
20455
  }
19346
20456
  let tagPush;
20457
+ let tagLedgerStarted = false;
20458
+ const recoveryInput = { repo: ctx.repo, tag, stage: "main", deployModel, meta, branchHints };
19347
20459
  try {
19348
20460
  await assertComputedReleaseTagStillNext(deps, tag);
19349
20461
  if (deployModel === "hub-serverless") {
19350
20462
  await deps.run("node", ["scripts/release-distribution.mjs", "assert-release-lineage", tag.replace(/^v/, ""), "--release-commit", releaseSha]);
19351
20463
  }
20464
+ if (ledger) {
20465
+ const start = await startReleaseLedger(ledger, ledgerAnchors);
20466
+ if (!start.ok) {
20467
+ throw new Error(
20468
+ `release refused before the tag was pushed: the durable phase ledger could not be started at ${ledger.path} (${start.error}) - nothing was tagged or pushed; repair the ledger location and rerun the release (#5987)`
20469
+ );
20470
+ }
20471
+ tagLedgerStarted = true;
20472
+ }
19352
20473
  tagPush = await ensureTagPushed(deps, tag, releaseSha, tagProbe, ctx.repo);
19353
20474
  } catch (e) {
20475
+ if (ledger && tagLedgerStarted) {
20476
+ const decision = await reconcileLedgerAfterFailedTagPush(deps, ledger, ledgerAnchors);
20477
+ const annotated = new Error(`${e instanceof Error ? e.message : String(e)}
20478
+
20479
+ ${decision.note}`);
20480
+ if (decision.action === "kept-present") throw partialTrainRecoveryError(annotated, recoveryInput);
20481
+ if (decision.action === "kept-unproven") throw annotated;
20482
+ throw await recoverFailedFold(deps, annotated, startBranch, preFold.mainSha, resumeCommand);
20483
+ }
19354
20484
  throw await recoverFailedFold(deps, e, startBranch, preFold.mainSha, resumeCommand);
19355
20485
  }
19356
20486
  const tagPushSince = (deps.now ?? Date.now)();
19357
20487
  let checks;
19358
- const recoveryInput = { repo: ctx.repo, tag, stage: "main", deployModel, meta, branchHints };
19359
20488
  try {
19360
20489
  checks = await waitForRequiredTrainChecks(deps, ctx, releaseSha, requiredChecks, tagPush.pushed ? tagPushSince : void 0);
19361
20490
  } catch (e) {
19362
- throw partialTrainRecoveryError(e, recoveryInput);
20491
+ const failureText = e instanceof Error ? e.message : String(e);
20492
+ const recovery = await recoverPhaseWithMaybeTransientRerun(deps, ledger, ledgerAnchors, "ordinaryCi", {
20493
+ repo: ctx.repo,
20494
+ sha: releaseSha,
20495
+ failure: failureText,
20496
+ watched: false
20497
+ });
20498
+ if (recovery.reran && recovery.recovered) {
20499
+ try {
20500
+ checks = await waitForRequiredTrainChecks(deps, ctx, releaseSha, requiredChecks, void 0);
20501
+ } catch (e2) {
20502
+ const spent = await recoverPhaseWithMaybeTransientRerun(deps, ledger, ledgerAnchors, "ordinaryCi", {
20503
+ repo: ctx.repo,
20504
+ sha: releaseSha,
20505
+ failure: e2 instanceof Error ? e2.message : String(e2),
20506
+ watched: false
20507
+ });
20508
+ throw partialTrainRecoveryError(new Error(`${e2 instanceof Error ? e2.message : String(e2)}
20509
+
20510
+ ${spent.note}`), recoveryInput);
20511
+ }
20512
+ } else {
20513
+ throw partialTrainRecoveryError(new Error(`${failureText}
20514
+
20515
+ ${recovery.note}`), recoveryInput);
20516
+ }
19363
20517
  }
20518
+ await recordPhase(ledger, deps, ledgerAnchors, "ordinaryCi", phaseEntry({ state: "complete", sha: releaseSha, note: checks }), {
20519
+ landed: "the immutable tag pushed at " + releaseSha.slice(0, 12)
20520
+ });
19364
20521
  if (trueMergeGateNote) checks = `${trueMergeGateNote}; ${checks}`;
19365
20522
  try {
19366
20523
  await runGitPush(deps, ["push", "origin", "main"]);
19367
20524
  } catch (e) {
20525
+ await recordPhase(ledger, deps, ledgerAnchors, "promotion", phaseEntry({ state: "failed", sha: releaseSha, error: `origin/main push failed after ${tag} was pushed: ${e instanceof Error ? e.message : String(e)}` }), { landed: "the immutable tag pushed at " + releaseSha.slice(0, 12) });
19368
20526
  throw partialTrainRecoveryError(e, recoveryInput);
19369
20527
  }
20528
+ await recordPhase(ledger, deps, ledgerAnchors, "promotion", phaseEntry({ state: "complete", sha: releaseSha, note: `immutable ${tag} pushed at ${releaseSha.slice(0, 12)}; origin/main fast-forwarded to it` }), { landed: "the immutable tag and the green required-check wall" });
19370
20529
  const releaseUrl = clean2(await deps.run("gh", ["release", "create", tag, "--target", "main", "--generate-notes", "--latest", "--repo", ctx.repo])) || void 0;
19371
20530
  await verifyPublishedRelease(deps, ctx.repo, tag, "main", releaseSha);
20531
+ await recordPhase(ledger, deps, ledgerAnchors, "githubRelease", phaseEntry({ state: "complete", sha: releaseSha, runUrl: releaseUrl, note: `verified GitHub Release ${tag} against ${releaseSha.slice(0, 12)}` }), { landed: "the immutable tag, the green required-check wall, and the origin/main fast-forward" });
19372
20532
  const announceNote = deps.announce ? (await deps.announce({ repo: ctx.repo, tag, summaryFile: options.announceSummaryFile })).note : void 0;
19373
20533
  const autoRunSince = (deps.now ?? Date.now)();
19374
- const deployDispatch = await dispatchDeploy(deps, ctx, "main", "main", deployModel, watch, autoRunSince, releaseSha, "report", meta.publishDir);
20534
+ const deployDispatch0 = await dispatchDeploy(deps, ctx, "main", "main", deployModel, watch, autoRunSince, releaseSha, "report", meta.publishDir);
20535
+ let deployDispatch = deployDispatch0;
20536
+ let deployRecoveryNote;
20537
+ if (deployDispatch0.deployStatus === "failure" && ledger) {
20538
+ const deployRunRepo = releaseRunRepoFor(deployModel, ctx.repo);
20539
+ const failedRow = (deployDispatch0.workflowRuns ?? []).find((r) => r.runId != null && r.conclusion === "failure");
20540
+ const dispatchRun = deployDispatch0.runId != null ? [{ workflow: isCentralDispatchModel(deployModel) ? "tenant-deploy.yml" : "deploy workflow", runId: deployDispatch0.runId, runUrl: deployDispatch0.runUrl, conclusion: "failure", repo: deployRunRepo, ...isCentralDispatchModel(deployModel) ? { event: "workflow_dispatch" } : {} }] : [];
20541
+ const recovery = await recoverPhaseWithMaybeTransientRerun(deps, ledger, ledgerAnchors, failedRow?.workflow === "publish.yml" ? "publish" : "deploy", {
20542
+ repo: deployRunRepo,
20543
+ sha: releaseSha,
20544
+ failure: `watched deploy run(s) failed on the release SHA: ${deployDispatch0.note}`,
20545
+ runs: deployDispatch0.workflowRuns?.filter((r) => r.runId != null).map((r) => ({ ...r, repo: deployRunRepo })) ?? (deployDispatch0.runId != null ? [{ workflow: isCentralDispatchModel(deployModel) ? "tenant-deploy.yml" : "deploy workflow", runId: deployDispatch0.runId, runUrl: deployDispatch0.runUrl, conclusion: "failure", repo: deployRunRepo, ...isCentralDispatchModel(deployModel) ? { event: "workflow_dispatch" } : {} }] : []),
20546
+ watched: isCentralDispatchModel(deployModel)
20547
+ });
20548
+ if (recovery.reran && recovery.recovered) {
20549
+ deployDispatch = {
20550
+ ...deployDispatch0,
20551
+ deployStatus: "success",
20552
+ workflowRuns: deployDispatch0.workflowRuns?.map((r) => recovery.runId != null && r.runId === recovery.runId ? { ...r, conclusion: "success" } : r),
20553
+ note: `${deployDispatch0.note}; ${recovery.note}`
20554
+ };
20555
+ } else if (recovery.note) {
20556
+ deployRecoveryNote = recovery.note;
20557
+ }
20558
+ }
20559
+ await recordPhase(ledger, deps, ledgerAnchors, "deploy", phaseEntry(deployPhaseInput(deployModel, deployDispatch, { releaseSha })), {
20560
+ landed: "the immutable tag, the green required-check wall, the origin/main fast-forward, and the verified GitHub Release"
20561
+ });
19375
20562
  const publishDispatch = deployDispatch.deployStatus === "success" ? await dispatchPublishIfRequired(deps, ctx, meta, deployModel, "main", tag, watch, "report") : null;
19376
- let dispatch = appendPublishDispatch(deployDispatch, publishDispatch);
19377
- dispatch = await appendJervGatewayReleaseDeploy(deps, ctx.repo, tag, releaseSha, dispatch);
20563
+ let publishSkipNote;
19378
20564
  if (!publishDispatch && deployDispatch.deployStatus !== "success" && meta.publishRequired && (deployModel === "tenant-container" || deployModel === "solo-container")) {
19379
- const reason = deployDispatch.deployStatus === "failure" ? "box deploy failed \u2014 redeploy the box before publishing" : "box deploy not confirmed (run with --watch) \u2014 publish after the box deploy lands";
19380
- dispatch = {
19381
- ...dispatch,
19382
- note: `${dispatch.note}; tenant-publish.yml skipped (${reason})`
19383
- };
20565
+ publishSkipNote = deployDispatch.deployStatus === "failure" ? "box deploy failed - redeploy the box before publishing" : "box deploy not confirmed (run with --watch) - publish after the box deploy lands";
19384
20566
  }
19385
- return { checks, releaseUrl, announceNote, dispatch };
20567
+ await recordPhase(ledger, deps, ledgerAnchors, "publish", phaseEntry(publishPhaseInput(deployModel, deployDispatch, publishDispatch, meta.publishRequired === true, {
20568
+ releaseSha,
20569
+ repo: ctx.repo,
20570
+ ...publishSkipNote !== void 0 ? { skipPublishNote: publishSkipNote } : {}
20571
+ })), {
20572
+ landed: "the immutable tag, the green required-check wall, origin/main, the verified GitHub Release, and the dispatched deploy path"
20573
+ });
20574
+ let dispatch = appendPublishDispatch(deployDispatch, publishDispatch);
20575
+ dispatch = await appendJervGatewayReleaseDeploy(deps, ctx.repo, tag, releaseSha, dispatch);
20576
+ if (publishSkipNote) dispatch = { ...dispatch, note: `${dispatch.note}; tenant-publish.yml skipped (${publishSkipNote})` };
20577
+ if (deployRecoveryNote) dispatch = { ...dispatch, note: `${dispatch.note}; ${deployRecoveryNote}` };
20578
+ return { checks, releaseUrl, announceNote, dispatch, ledgerAnchors };
19386
20579
  }
19387
20580
  function trainResumeExitCode(state) {
19388
20581
  if (state === "failed") return 1;
@@ -19492,6 +20685,35 @@ async function runReleaseResumePipeline(deps, options, prepared) {
19492
20685
  );
19493
20686
  }
19494
20687
  const base = { command: "release-resume", repo: ctx.repo, tag, tagSha, deployModel };
20688
+ const ledger = openReleaseLedgerWriter(process.cwd());
20689
+ const anchors = { repo: ctx.repo, tag, tagSha };
20690
+ const begin = await beginReleaseLedger(deps, process.cwd(), tag);
20691
+ if (!begin.ok) throw new Error(`release --resume refused: ${begin.error}`);
20692
+ else if (begin.note) deps.warn?.(`release ledger: ${begin.note}`);
20693
+ let persisted;
20694
+ try {
20695
+ persisted = await ledger.load(anchors);
20696
+ } catch (e) {
20697
+ throw new Error(
20698
+ `release --resume: ${e instanceof Error ? e.message : String(e)}
20699
+ Live origin/tag/Release proof stays authoritative; nothing was written. Inspect ${ledger.path} before retrying.`
20700
+ );
20701
+ }
20702
+ if (persisted) {
20703
+ let reverifyNotes = [];
20704
+ try {
20705
+ const reverified = await reverifyPersistedRunPhases(deps, ledger.path, anchors, { sha: tagSha, repo: ctx.repo });
20706
+ persisted = reverified.ledger;
20707
+ reverifyNotes = reverified.notes;
20708
+ } catch (e) {
20709
+ throw new Error(
20710
+ `release --resume: ${e instanceof Error ? e.message : String(e)}
20711
+ Nothing was written. Inspect ${ledger.path} (or clear it only after proving the release state by hand).`
20712
+ );
20713
+ }
20714
+ for (const note of reverifyNotes) deps.warn?.(`release ledger: ${note}`);
20715
+ }
20716
+ const ledgerMode = persisted ? "strict" : "migration";
19495
20717
  const partialRelease = await isStrayUnreleasedTag(deps, tag, tagSha, ctx.repo);
19496
20718
  if (!partialRelease) {
19497
20719
  try {
@@ -19506,12 +20728,44 @@ async function runReleaseResumePipeline(deps, options, prepared) {
19506
20728
  steps2.push(`development alignment: ${devRollForward2.status}`);
19507
20729
  const rcAlignment2 = !directTrack && branchHints.hasRcBranch ? await alignRcForward(deps, ctx, tag) : void 0;
19508
20730
  if (rcAlignment2) steps2.push(`rc alignment: ${rcAlignment2.status}`);
19509
- const historicalDeploy = await deriveHistoricalDeployStatus(deps, ctx, deployModel, tagSha);
20731
+ const historicalTargets = prodReleaseTargets(deployModel);
20732
+ const historicalRepo = releaseRunRepoFor(deployModel, ctx.repo);
20733
+ let historicalRows = historicalTargets.length ? await listOwnWorkflowRuns(deps, historicalRepo, historicalTargets, tagSha) : [];
20734
+ let historicalDeploy = historicalTargets.length ? aggregateWorkflowRuns(historicalRows) : "pending";
20735
+ let historicalNote = `historical ${deployModel} deploy/publish status: ${historicalDeploy}`;
20736
+ if (historicalDeploy === "failure" && ledger) {
20737
+ const failedRow = historicalRows.find((r) => r.runId != null && r.conclusion === "failure");
20738
+ const recovery = await recoverPhaseWithMaybeTransientRerun(deps, ledger, anchors, failedRow?.workflow === "publish.yml" ? "publish" : "deploy", {
20739
+ repo: historicalRepo,
20740
+ sha: tagSha,
20741
+ failure: `historical deploy/publish run(s) failed on the release SHA: ${historicalRows.map((r) => `${r.workflow}=${r.conclusion}`).join(", ")}`,
20742
+ runs: historicalRows.filter((r) => r.runId != null).map((r) => ({ ...r, repo: historicalRepo })),
20743
+ watched: false
20744
+ });
20745
+ if (recovery.reran && recovery.recovered) {
20746
+ historicalRows = historicalRows.map((r) => recovery.runId != null && r.runId === recovery.runId ? { ...r, conclusion: "success" } : r);
20747
+ historicalDeploy = aggregateWorkflowRuns(historicalRows);
20748
+ historicalNote = `historical ${deployModel} deploy/publish status: ${historicalDeploy}; ${recovery.note}`;
20749
+ } else if (recovery.note) {
20750
+ historicalNote = `${historicalNote}; ${recovery.note}`;
20751
+ }
20752
+ }
20753
+ if (persisted) {
20754
+ const phaseInputs = phaseInputsFromRunRows(deployModel, historicalRows, tagSha, { repo: ctx.repo });
20755
+ await recordPhase(ledger, deps, anchors, "deploy", phaseEntry(phaseInputs.deploy), { strict: true, landed: "the verified GitHub Release and origin/main at the immutable tag SHA" });
20756
+ await recordPhase(ledger, deps, anchors, "publish", phaseEntry(phaseInputs.publish), { strict: true, landed: "the verified GitHub Release and origin/main at the immutable tag SHA" });
20757
+ await recordPhase(ledger, deps, anchors, "githubRelease", phaseEntry({ state: "complete", sha: tagSha, note: "re-verified live: the GitHub Release exists at the immutable tag SHA" }), { strict: true, landed: "the promotion (verified live)" });
20758
+ await recordPhase(ledger, deps, anchors, "promotion", phaseEntry({ state: "complete", sha: tagSha, note: "re-verified live: origin/main contains the immutable tag SHA" }), { strict: true, landed: "nothing beyond the already-public release" });
20759
+ }
19510
20760
  const dispatch2 = await appendJervGatewayReleaseDeploy(deps, ctx.repo, tag, tagSha, {
19511
- note: `historical ${deployModel} deploy/publish status: ${historicalDeploy}`,
20761
+ note: historicalNote,
19512
20762
  deployStatus: historicalDeploy
19513
20763
  });
19514
20764
  if (isJervHubRepo(ctx.repo)) steps2.push(dispatch2.note);
20765
+ if (persisted) {
20766
+ await recordPhase(ledger, deps, anchors, "alignment", phaseEntry(alignmentPhaseEntry(devRollForward2, rcAlignment2)), { strict: true, landed: "the released, verified, deploy-resolved release (alignment PRs reused/landed)" });
20767
+ }
20768
+ const ledgerReport2 = releaseLedgerReport(await ledger.load(anchors).catch(() => void 0), ledger.path);
19515
20769
  const state2 = resumeStateOf(deriveTrainFollowUpStatus({
19516
20770
  projectInfo: "ok",
19517
20771
  deploy: deployFollowUpOutcome(dispatch2.deployStatus),
@@ -19528,6 +20782,7 @@ async function runReleaseResumePipeline(deps, options, prepared) {
19528
20782
  devRollForward: devRollForward2,
19529
20783
  rcAlignment: rcAlignment2,
19530
20784
  dispatch: dispatch2,
20785
+ ...ledgerReport2 ? { ledger: ledgerReport2 } : {},
19531
20786
  note: state2 === "failed" ? `release ${tag} was already published, but its deploy/publish on the release SHA FAILED \u2014 re-run the failed job (do NOT re-tag); the protected alignment PRs were reused` : state2 === "pending" ? `release ${tag} was already published; reused its protected alignment PRs, but a deploy/publish or alignment follow-up is unresolved \u2014 watch it to completion` : `release ${tag} was already published; alignment is complete and the deploy on the release SHA is proven green`
19532
20787
  };
19533
20788
  }
@@ -19548,21 +20803,81 @@ async function runReleaseResumePipeline(deps, options, prepared) {
19548
20803
  try {
19549
20804
  steps.push(await waitForRequiredTrainChecks(deps, ctx, tagSha, requiredChecks, void 0));
19550
20805
  } catch (e) {
19551
- throw partialTrainRecoveryError(e, recoveryInput);
20806
+ const failureText = e instanceof Error ? e.message : String(e);
20807
+ const recovery = await recoverPhaseWithMaybeTransientRerun(deps, ledger, anchors, "ordinaryCi", {
20808
+ repo: ctx.repo,
20809
+ sha: tagSha,
20810
+ failure: failureText,
20811
+ watched: false
20812
+ });
20813
+ if (recovery.reran && recovery.recovered) {
20814
+ try {
20815
+ steps.push(await waitForRequiredTrainChecks(deps, ctx, tagSha, requiredChecks, void 0));
20816
+ } catch (e2) {
20817
+ const spent = await recoverPhaseWithMaybeTransientRerun(deps, ledger, anchors, "ordinaryCi", {
20818
+ repo: ctx.repo,
20819
+ sha: tagSha,
20820
+ failure: e2 instanceof Error ? e2.message : String(e2),
20821
+ watched: false
20822
+ });
20823
+ throw partialTrainRecoveryError(new Error(`${e2 instanceof Error ? e2.message : String(e2)}
20824
+
20825
+ ${spent.note}`), recoveryInput);
20826
+ }
20827
+ } else {
20828
+ throw partialTrainRecoveryError(new Error(`${failureText}
20829
+
20830
+ ${recovery.note}`), recoveryInput);
20831
+ }
19552
20832
  }
20833
+ await recordPhase(ledger, deps, anchors, "ordinaryCi", phaseEntry({ state: "complete", sha: tagSha, note: "re-verified the required-check wall live on this resume" }), { strict: ledgerMode === "strict", landed: "the immutable tag on origin at the resumed SHA" });
19553
20834
  await runGitPush(deps, ["push", "origin", `${tagSha}:refs/heads/main`]);
19554
20835
  steps.push(`pushed ${tagSha.slice(0, 12)} to origin/main`);
20836
+ await recordPhase(ledger, deps, anchors, "promotion", phaseEntry({ state: "complete", sha: tagSha, note: `resumed: ${tagSha.slice(0, 12)} pushed to origin/main (tag preserved, never re-cut)` }), { strict: ledgerMode === "strict", landed: "the immutable tag and the re-proven required-check wall" });
19555
20837
  const releaseUrl = clean2(await deps.run("gh", ["release", "create", tag, "--target", "main", "--generate-notes", "--latest", "--repo", ctx.repo])) || void 0;
19556
20838
  steps.push(`created the GitHub Release for ${tag}`);
19557
20839
  await verifyPublishedRelease(deps, ctx.repo, tag, "main", tagSha);
19558
20840
  steps.push("verified the Release published against the tagged commit");
20841
+ await recordPhase(ledger, deps, anchors, "githubRelease", phaseEntry({ state: "complete", sha: tagSha, runUrl: releaseUrl, note: "verified the Release published against the tagged commit" }), { strict: ledgerMode === "strict", landed: "the immutable tag, the re-proven wall, and the origin/main push" });
19559
20842
  const announceNote = deps.announce ? (await deps.announce({ repo: ctx.repo, tag, summaryFile: options.announceSummaryFile })).note : void 0;
19560
20843
  const autoRunSince = (deps.now ?? Date.now)();
19561
- const deployDispatch = await dispatchDeploy(deps, ctx, "main", "main", deployModel, watch, autoRunSince, tagSha, "report", meta.publishDir);
20844
+ const deployDispatch0 = await dispatchDeploy(deps, ctx, "main", "main", deployModel, watch, autoRunSince, tagSha, "report", meta.publishDir);
20845
+ let deployDispatch = deployDispatch0;
20846
+ let deployRecoveryNote;
20847
+ if (deployDispatch0.deployStatus === "failure" && ledger) {
20848
+ const deployRunRepo = releaseRunRepoFor(deployModel, ctx.repo);
20849
+ const failedRow = (deployDispatch0.workflowRuns ?? []).find((r) => r.runId != null && r.conclusion === "failure");
20850
+ const recovery = await recoverPhaseWithMaybeTransientRerun(deps, ledger, anchors, failedRow?.workflow === "publish.yml" ? "publish" : "deploy", {
20851
+ repo: deployRunRepo,
20852
+ sha: tagSha,
20853
+ failure: `watched deploy run(s) failed on the release SHA: ${deployDispatch0.note}`,
20854
+ runs: deployDispatch0.workflowRuns?.filter((r) => r.runId != null).map((r) => ({ ...r, repo: deployRunRepo })) ?? (deployDispatch0.runId != null ? [{ workflow: isCentralDispatchModel(deployModel) ? "tenant-deploy.yml" : "deploy workflow", runId: deployDispatch0.runId, runUrl: deployDispatch0.runUrl, conclusion: "failure", repo: deployRunRepo, ...isCentralDispatchModel(deployModel) ? { event: "workflow_dispatch" } : {} }] : []),
20855
+ watched: isCentralDispatchModel(deployModel)
20856
+ });
20857
+ if (recovery.reran && recovery.recovered) {
20858
+ deployDispatch = {
20859
+ ...deployDispatch0,
20860
+ deployStatus: "success",
20861
+ workflowRuns: deployDispatch0.workflowRuns?.map((r) => recovery.runId != null && r.runId === recovery.runId ? { ...r, conclusion: "success" } : r),
20862
+ note: `${deployDispatch0.note}; ${recovery.note}`
20863
+ };
20864
+ } else if (recovery.note) {
20865
+ deployRecoveryNote = recovery.note;
20866
+ }
20867
+ }
20868
+ steps.push(`dispatched the ${deployModel} deploy path`);
20869
+ await recordPhase(ledger, deps, anchors, "deploy", phaseEntry(deployPhaseInput(deployModel, deployDispatch, { releaseSha: tagSha })), {
20870
+ strict: ledgerMode === "strict",
20871
+ landed: "the immutable tag, the re-proven wall, the origin/main push, and the verified GitHub Release"
20872
+ });
19562
20873
  const publishDispatch = deployDispatch.deployStatus === "success" ? await dispatchPublishIfRequired(deps, ctx, meta, deployModel, "main", tag, watch, "report") : null;
20874
+ await recordPhase(ledger, deps, anchors, "publish", phaseEntry(publishPhaseInput(deployModel, deployDispatch, publishDispatch, meta.publishRequired === true, { releaseSha: tagSha, repo: ctx.repo })), {
20875
+ strict: ledgerMode === "strict",
20876
+ landed: "the immutable tag, the re-proven wall, origin/main, the verified GitHub Release, and the dispatched deploy path"
20877
+ });
19563
20878
  let dispatch = appendPublishDispatch(deployDispatch, publishDispatch);
19564
20879
  dispatch = await appendJervGatewayReleaseDeploy(deps, ctx.repo, tag, tagSha, dispatch);
19565
- steps.push(`dispatched the ${deployModel} deploy path`);
20880
+ if (deployRecoveryNote) dispatch = { ...dispatch, note: `${dispatch.note}; ${deployRecoveryNote}` };
19566
20881
  const devRollForward = await rollDevelopmentForward(deps, ctx, tag);
19567
20882
  steps.push(`development roll-forward: ${devRollForward.status}`);
19568
20883
  const rcAlignment = !directTrack && branchHints.hasRcBranch ? await alignRcForward(deps, ctx, tag) : void 0;
@@ -19575,6 +20890,8 @@ async function runReleaseResumePipeline(deps, options, prepared) {
19575
20890
  foldPort: "ok"
19576
20891
  });
19577
20892
  const state = resumeStateOf(followUp);
20893
+ await recordPhase(ledger, deps, anchors, "alignment", phaseEntry(alignmentPhaseEntry(devRollForward, rcAlignment)), { strict: ledgerMode === "strict", landed: "the promotion, verified Release, and deploy path of the resumed release" });
20894
+ const ledgerReport = releaseLedgerReport(await ledger.load(anchors).catch(() => void 0), ledger?.path ?? "(no ledger)");
19578
20895
  return {
19579
20896
  ...base,
19580
20897
  mode: "partial-release",
@@ -19586,6 +20903,7 @@ async function runReleaseResumePipeline(deps, options, prepared) {
19586
20903
  dispatch,
19587
20904
  devRollForward,
19588
20905
  rcAlignment,
20906
+ ...ledgerReport ? { ledger: ledgerReport } : {},
19589
20907
  note: followUp === "failed" ? `resumed ${tag} (version preserved, not re-cut) \u2014 but a watched deploy/publish run FAILED; the Release stands, re-run the failed job (do NOT re-tag)` : followUp === "pending" ? `resumed ${tag} (version preserved, not re-cut) \u2014 a deploy/publish or alignment follow-up is unresolved; watch it to completion` : `resumed and completed ${tag} \u2014 the original version was preserved, not re-cut`
19590
20908
  };
19591
20909
  }
@@ -19679,13 +20997,14 @@ async function runReleaseAbort(deps, options = {}) {
19679
20997
  if (restoredMainSha !== originMainSha) {
19680
20998
  throw new Error(`release --abort removed ${tag}, but local main did not restore to origin/main; inspect before retrying`);
19681
20999
  }
21000
+ const clearedLedgerNote = await clearReleaseLedger(process.cwd(), { repo: ctx.repo, tag, tagSha });
19682
21001
  return {
19683
21002
  command: "release-abort",
19684
21003
  repo: ctx.repo,
19685
21004
  tag,
19686
21005
  tagSha,
19687
21006
  restoredMainSha,
19688
- note: `removed the unpublished ${tag} candidate and restored local main; repair development before recutting`
21007
+ note: `removed the unpublished ${tag} candidate and restored local main; repair development before recutting` + (clearedLedgerNote ? `; ${clearedLedgerNote}` : "")
19689
21008
  };
19690
21009
  }
19691
21010
  async function runReleasePublishRetry(deps, runId, options = {}) {
@@ -19732,6 +21051,25 @@ async function runReleasePublishRetry(deps, runId, options = {}) {
19732
21051
  if (options.watch && await watchTenantRun(deps, runId, ctx.repo) !== "success") {
19733
21052
  throw new Error(`publish run ${runId} did not finish successfully after retry`);
19734
21053
  }
21054
+ const ledger = openReleaseLedgerWriter(process.cwd());
21055
+ const existing = await ledger.load({ repo: ctx.repo, tag, tagSha }).catch(() => void 0);
21056
+ let ledgerNote;
21057
+ if (existing) {
21058
+ const record = await ledger.record({ repo: ctx.repo, tag, tagSha }, "publish", phaseEntry({
21059
+ state: options.watch ? "complete" : "pending",
21060
+ sha: tagSha,
21061
+ runId,
21062
+ runUrl: run.url ?? `https://github.com/${ctx.repo}/actions/runs/${runId}`,
21063
+ runRepo: ctx.repo,
21064
+ runProvenance: "sha",
21065
+ workflow: "publish.yml",
21066
+ event: "release",
21067
+ note: options.watch ? `publish run ${runId} retried via release --retry-publish and passed (live-proven)` : `publish run ${runId} queued for retry via release --retry-publish`
21068
+ }));
21069
+ ledgerNote = record.ok ? options.watch ? "publish boundary updated to complete in the release ledger" : "publish boundary updated to pending in the release ledger" : `release ledger not updated: ${record.error}`;
21070
+ } else {
21071
+ ledgerNote = "no release ledger on disk for this release \u2014 nothing to update";
21072
+ }
19735
21073
  return {
19736
21074
  command: "release-retry-publish",
19737
21075
  repo: ctx.repo,
@@ -19740,12 +21078,18 @@ async function runReleasePublishRetry(deps, runId, options = {}) {
19740
21078
  runId,
19741
21079
  runUrl: run.url ?? `https://github.com/${ctx.repo}/actions/runs/${runId}`,
19742
21080
  status: options.watch ? "success" : "pending",
19743
- note: options.watch ? `publish run ${runId} retried and passed` : `publish run ${runId} queued for retry`
21081
+ note: options.watch ? `publish run ${runId} retried and passed; ${ledgerNote}` : `publish run ${runId} queued for retry; ${ledgerNote}`
19744
21082
  };
19745
21083
  }
19746
21084
  async function runTrainApplyPipeline(mode, input) {
19747
21085
  const { deps, ctx, command, meta, branchHints, watch, options, bumpIntent } = input;
19748
21086
  const directTrack = input.directTrack ?? false;
21087
+ const ledger = mode === "rcand" ? void 0 : openReleaseLedgerWriter(process.cwd());
21088
+ if (ledger) {
21089
+ const begin = await beginReleaseLedger(deps, process.cwd());
21090
+ if (!begin.ok) throw new Error(`release refused before any mutation: ${begin.error}`);
21091
+ else if (begin.note) deps.warn?.(`release ledger: ${begin.note}`);
21092
+ }
19749
21093
  if (mode === "rcand") {
19750
21094
  await requireBranch(deps, "development");
19751
21095
  if (directTrack) {
@@ -19832,8 +21176,10 @@ async function runTrainApplyPipeline(mode, input) {
19832
21176
  const releaseSha3 = requireValue(clean2(await deps.run("git", ["rev-parse", "main"])), "release sha");
19833
21177
  return { versionFold: versionFold3, releaseSha: releaseSha3 };
19834
21178
  });
19835
- const { checks: checks2, releaseUrl: releaseUrl2, announceNote: announceNote2, dispatch: d2 } = await completeMainRelease(deps, ctx, meta, deployModel2, watch, options, tag2, releaseSha2, "development", preFold2, tagProbe2, branchHints);
21179
+ const { checks: checks2, releaseUrl: releaseUrl2, announceNote: announceNote2, dispatch: d2, ledgerAnchors: ledgerAnchors2 } = await completeMainRelease(deps, ctx, meta, deployModel2, watch, options, tag2, releaseSha2, "development", preFold2, tagProbe2, branchHints, ledger);
19836
21180
  const devRollForward2 = await rollDevelopmentForward(deps, ctx, tag2);
21181
+ await recordPhase(ledger, deps, ledgerAnchors2, "alignment", phaseEntry(alignmentPhaseEntry(devRollForward2)), { landed: "the immutable tag, the green wall, origin/main, the verified Release, and the deploy/publish path" });
21182
+ const ledgerReport2 = releaseLedgerReport(await ledger?.load(ledgerAnchors2).catch(() => void 0), ledger?.path ?? "(no ledger)");
19837
21183
  if (directTrack) {
19838
21184
  return {
19839
21185
  ...ctx,
@@ -19856,11 +21202,14 @@ async function runTrainApplyPipeline(mode, input) {
19856
21202
  devRollForward: devRollForward2,
19857
21203
  announceNote: announceNote2,
19858
21204
  devNote: options.dev ? "--dev is a no-op on a direct-track repo \u2014 it already releases development -> main" : void 0,
19859
- release: { tag: tag2, url: releaseUrl2, targetSha: releaseSha2 }
21205
+ release: { tag: tag2, url: releaseUrl2, targetSha: releaseSha2 },
21206
+ ...ledgerReport2 ? { ledger: ledgerReport2 } : {}
19860
21207
  };
19861
21208
  }
19862
21209
  const retirement2 = hasRcBranch ? await retireRcRuntime(deps, ctx, deployModel2, d2.deployStatus, rcShaAtRelease) : { status: "not-applicable", note: "no origin/rc branch \u2014 rc runtime retirement skipped" };
19863
21210
  const rcAlignment2 = hasRcBranch ? await alignRcForward(deps, ctx, tag2) : void 0;
21211
+ await recordPhase(ledger, deps, ledgerAnchors2, "alignment", phaseEntry(alignmentPhaseEntry(devRollForward2, rcAlignment2)), { landed: "the immutable tag, the green wall, origin/main, the verified Release, and the deploy/publish path" });
21212
+ const fullLedgerReport = releaseLedgerReport(await ledger?.load(ledgerAnchors2).catch(() => void 0), ledger?.path ?? "(no ledger)");
19864
21213
  const environments2 = await buildEnvironments(deps, ctx, deployModel2, d2.deployStatus, retirement2);
19865
21214
  return {
19866
21215
  ...ctx,
@@ -19886,7 +21235,8 @@ async function runTrainApplyPipeline(mode, input) {
19886
21235
  announceNote: announceNote2,
19887
21236
  devNote: "released development -> main (--dev), skipping the rc candidate",
19888
21237
  release: { tag: tag2, url: releaseUrl2, targetSha: releaseSha2 },
19889
- environments: environments2
21238
+ environments: environments2,
21239
+ ...fullLedgerReport ? { ledger: fullLedgerReport } : {}
19890
21240
  };
19891
21241
  }
19892
21242
  await requireBranch(deps, "rc");
@@ -19919,10 +21269,12 @@ async function runTrainApplyPipeline(mode, input) {
19919
21269
  const releaseSha2 = requireValue(clean2(await deps.run("git", ["rev-parse", "main"])), "release sha");
19920
21270
  return { versionFold: versionFold2, releaseSha: releaseSha2 };
19921
21271
  });
19922
- const { checks, releaseUrl, announceNote, dispatch: d } = await completeMainRelease(deps, ctx, meta, deployModel, watch, options, tag, releaseSha, "rc", preFold, tagProbe, branchHints);
21272
+ const { checks, releaseUrl, announceNote, dispatch: d, ledgerAnchors } = await completeMainRelease(deps, ctx, meta, deployModel, watch, options, tag, releaseSha, "rc", preFold, tagProbe, branchHints, ledger);
19923
21273
  const retirement = await retireRcRuntime(deps, ctx, deployModel, d.deployStatus, releasedRcSha);
19924
21274
  const devRollForward = await rollDevelopmentForward(deps, ctx, tag);
19925
21275
  const rcAlignment = await alignRcForward(deps, ctx, tag);
21276
+ await recordPhase(ledger, deps, ledgerAnchors, "alignment", phaseEntry(alignmentPhaseEntry(devRollForward, rcAlignment)), { landed: "the immutable tag, the green wall, origin/main, the verified Release, and the deploy/publish path" });
21277
+ const ledgerReport = releaseLedgerReport(await ledger?.load(ledgerAnchors).catch(() => void 0), ledger?.path ?? "(no ledger)");
19926
21278
  const environments = await buildEnvironments(deps, ctx, deployModel, d.deployStatus, retirement);
19927
21279
  return {
19928
21280
  ...ctx,
@@ -19947,7 +21299,8 @@ async function runTrainApplyPipeline(mode, input) {
19947
21299
  devRollForward,
19948
21300
  announceNote,
19949
21301
  release: { tag, url: releaseUrl, targetSha: releaseSha },
19950
- environments
21302
+ environments,
21303
+ ...ledgerReport ? { ledger: ledgerReport } : {}
19951
21304
  };
19952
21305
  }
19953
21306
  async function latestLocalReleaseTag(deps) {
@@ -21015,20 +22368,20 @@ async function applyCiReconcileRepo(repo, deps) {
21015
22368
  }
21016
22369
 
21017
22370
  // src/claude-binary-doctor.ts
21018
- var import_node_fs21 = require("node:fs");
22371
+ var import_node_fs22 = require("node:fs");
21019
22372
  var import_node_os11 = require("node:os");
21020
- var import_node_path19 = require("node:path");
22373
+ var import_node_path21 = require("node:path");
21021
22374
 
21022
22375
  // src/jerv-cli-spawn.ts
21023
- var import_node_fs20 = require("node:fs");
22376
+ var import_node_fs21 = require("node:fs");
21024
22377
  var import_node_os10 = require("node:os");
21025
- var import_node_path18 = require("node:path");
22378
+ var import_node_path20 = require("node:path");
21026
22379
  var WIN_NAMES = ["jerv-cli.cmd", "jerv-cli.exe", "jerv-cli"];
21027
22380
  var POSIX_NAMES = ["jerv-cli"];
21028
- var JERV_CLI_ENTRY = (0, import_node_path18.join)("node_modules", "@jervaise", "jerv-cli", "dist", "index.cjs");
22381
+ var JERV_CLI_ENTRY = (0, import_node_path20.join)("node_modules", "@jervaise", "jerv-cli", "dist", "index.cjs");
21029
22382
  function pathEnvEntries(pathEnv, platform2 = process.platform) {
21030
22383
  if (platform2 !== "win32") {
21031
- return pathEnv.split(import_node_path18.delimiter).map((e) => e.trim()).filter(Boolean);
22384
+ return pathEnv.split(import_node_path20.delimiter).map((e) => e.trim()).filter(Boolean);
21032
22385
  }
21033
22386
  if (pathEnv.includes(";")) {
21034
22387
  return pathEnv.split(";").map((e) => e.trim()).filter(Boolean);
@@ -21061,10 +22414,10 @@ function jervCliCandidateDirs(env = process.env, home = (0, import_node_os10.hom
21061
22414
  push(normalizeSpawnPathEntry(entry, platform2));
21062
22415
  }
21063
22416
  if (platform2 === "win32") {
21064
- if (env.APPDATA) push((0, import_node_path18.join)(env.APPDATA, "npm"));
21065
- if (env.LOCALAPPDATA) push((0, import_node_path18.join)(env.LOCALAPPDATA, "npm"));
22417
+ if (env.APPDATA) push((0, import_node_path20.join)(env.APPDATA, "npm"));
22418
+ if (env.LOCALAPPDATA) push((0, import_node_path20.join)(env.LOCALAPPDATA, "npm"));
21066
22419
  } else {
21067
- push((0, import_node_path18.join)(home, ".local", "bin"));
22420
+ push((0, import_node_path20.join)(home, ".local", "bin"));
21068
22421
  }
21069
22422
  return out;
21070
22423
  }
@@ -21072,23 +22425,23 @@ function jervCliCandidatePaths(env = process.env, home = (0, import_node_os10.ho
21072
22425
  const names = platform2 === "win32" ? WIN_NAMES : POSIX_NAMES;
21073
22426
  const out = [];
21074
22427
  for (const dir of jervCliCandidateDirs(env, home, platform2)) {
21075
- for (const name of names) out.push((0, import_node_path18.join)(dir, name));
22428
+ for (const name of names) out.push((0, import_node_path20.join)(dir, name));
21076
22429
  }
21077
22430
  return out;
21078
22431
  }
21079
- function resolveJervCliPath(env = process.env, home = (0, import_node_os10.homedir)(), platform2 = process.platform, exists = import_node_fs20.existsSync) {
22432
+ function resolveJervCliPath(env = process.env, home = (0, import_node_os10.homedir)(), platform2 = process.platform, exists = import_node_fs21.existsSync) {
21080
22433
  for (const candidate of jervCliCandidatePaths(env, home, platform2)) {
21081
22434
  if (exists(candidate)) return candidate;
21082
22435
  }
21083
22436
  return void 0;
21084
22437
  }
21085
- function resolveJervCliNodeEntry(shimPath, exists = import_node_fs20.existsSync) {
21086
- const entry = (0, import_node_path18.join)((0, import_node_path18.dirname)(shimPath), JERV_CLI_ENTRY);
22438
+ function resolveJervCliNodeEntry(shimPath, exists = import_node_fs21.existsSync) {
22439
+ const entry = (0, import_node_path20.join)((0, import_node_path20.dirname)(shimPath), JERV_CLI_ENTRY);
21087
22440
  return exists(entry) ? entry : void 0;
21088
22441
  }
21089
22442
  function jervCliExecFileArgs(args, opts = {}) {
21090
22443
  const platform2 = opts.platform ?? process.platform;
21091
- const exists = opts.exists ?? import_node_fs20.existsSync;
22444
+ const exists = opts.exists ?? import_node_fs21.existsSync;
21092
22445
  const resolved = resolveJervCliPath(opts.env ?? process.env, opts.home ?? (0, import_node_os10.homedir)(), platform2, exists);
21093
22446
  if (resolved) {
21094
22447
  const entry = resolveJervCliNodeEntry(resolved, exists);
@@ -21178,26 +22531,26 @@ function globalNodeModulesRoots(host) {
21178
22531
  out.push(dir);
21179
22532
  };
21180
22533
  const prefix = env.npm_config_prefix?.trim();
21181
- if (prefix) push(platform2 === "win32" ? (0, import_node_path19.join)(prefix, "node_modules") : (0, import_node_path19.join)(prefix, "lib", "node_modules"));
22534
+ if (prefix) push(platform2 === "win32" ? (0, import_node_path21.join)(prefix, "node_modules") : (0, import_node_path21.join)(prefix, "lib", "node_modules"));
21182
22535
  for (const dir of jervCliCandidateDirs(env, host.home ?? (0, import_node_os11.homedir)(), platform2)) {
21183
- push((0, import_node_path19.join)(dir, "node_modules"));
21184
- push((0, import_node_path19.join)((0, import_node_path19.dirname)(dir), "lib", "node_modules"));
22536
+ push((0, import_node_path21.join)(dir, "node_modules"));
22537
+ push((0, import_node_path21.join)((0, import_node_path21.dirname)(dir), "lib", "node_modules"));
21185
22538
  }
21186
22539
  return out;
21187
22540
  }
21188
22541
  function readHead(path2) {
21189
22542
  let fd;
21190
22543
  try {
21191
- fd = (0, import_node_fs21.openSync)(path2, "r");
22544
+ fd = (0, import_node_fs22.openSync)(path2, "r");
21192
22545
  const buffer = new Uint8Array(MAGIC_HEAD_BYTES);
21193
- const read = (0, import_node_fs21.readSync)(fd, buffer, 0, MAGIC_HEAD_BYTES, 0);
22546
+ const read = (0, import_node_fs22.readSync)(fd, buffer, 0, MAGIC_HEAD_BYTES, 0);
21194
22547
  return buffer.subarray(0, read);
21195
22548
  } catch {
21196
22549
  return void 0;
21197
22550
  } finally {
21198
22551
  if (fd !== void 0) {
21199
22552
  try {
21200
- (0, import_node_fs21.closeSync)(fd);
22553
+ (0, import_node_fs22.closeSync)(fd);
21201
22554
  } catch {
21202
22555
  }
21203
22556
  }
@@ -21205,7 +22558,7 @@ function readHead(path2) {
21205
22558
  }
21206
22559
  function fileBytes(path2) {
21207
22560
  try {
21208
- return (0, import_node_fs21.statSync)(path2).size;
22561
+ return (0, import_node_fs22.statSync)(path2).size;
21209
22562
  } catch {
21210
22563
  return void 0;
21211
22564
  }
@@ -21219,17 +22572,17 @@ function readClaudeBinaryState(host = {}) {
21219
22572
  const arch = host.arch ?? process.arch;
21220
22573
  const magic = EXECUTABLE_MAGIC[platform2];
21221
22574
  if (!magic) return void 0;
21222
- const packageRoot = globalNodeModulesRoots(host).map((root) => (0, import_node_path19.join)(root, ...PACKAGE.split("/"))).find((dir) => (0, import_node_fs21.existsSync)((0, import_node_path19.join)(dir, "package.json")));
22575
+ const packageRoot = globalNodeModulesRoots(host).map((root) => (0, import_node_path21.join)(root, ...PACKAGE.split("/"))).find((dir) => (0, import_node_fs22.existsSync)((0, import_node_path21.join)(dir, "package.json")));
21223
22576
  if (!packageRoot) return void 0;
21224
22577
  const keys = platformPackageKeys(platform2, arch);
21225
22578
  const fallbackPackage = `${PACKAGE}-${keys[0]}`;
21226
22579
  let manifest;
21227
22580
  try {
21228
- manifest = JSON.parse((0, import_node_fs21.readFileSync)((0, import_node_path19.join)(packageRoot, "package.json"), "utf8"));
22581
+ manifest = JSON.parse((0, import_node_fs22.readFileSync)((0, import_node_path21.join)(packageRoot, "package.json"), "utf8"));
21229
22582
  } catch (e) {
21230
22583
  return {
21231
22584
  state: "unreadable",
21232
- binPath: (0, import_node_path19.join)(packageRoot, "package.json"),
22585
+ binPath: (0, import_node_path21.join)(packageRoot, "package.json"),
21233
22586
  expectedMagic: magic.name,
21234
22587
  platformPackage: fallbackPackage,
21235
22588
  error: `package.json could not be read \u2014 ${e.message}`
@@ -21246,17 +22599,17 @@ function readClaudeBinaryState(host = {}) {
21246
22599
  error: "package.json declares no `bin` entry \u2014 the executed file cannot be resolved"
21247
22600
  };
21248
22601
  }
21249
- const binPath = (0, import_node_path19.join)(packageRoot, binRelative);
21250
- const binName = (0, import_node_path19.basename)(binRelative);
22602
+ const binPath = (0, import_node_path21.join)(packageRoot, binRelative);
22603
+ const binName = (0, import_node_path21.basename)(binRelative);
21251
22604
  const optional = manifest.optionalDependencies;
21252
22605
  const publishes = (name) => typeof optional === "object" && optional !== null && Object.prototype.hasOwnProperty.call(optional, name);
21253
22606
  const published = keys.map((key) => `${PACKAGE}-${key}`).filter(publishes);
21254
22607
  if (published.length === 0) return void 0;
21255
22608
  const binIn = (name) => [
21256
- (0, import_node_path19.join)(packageRoot, "node_modules", ...name.split("/"), binName),
21257
- (0, import_node_path19.join)((0, import_node_path19.dirname)((0, import_node_path19.dirname)(packageRoot)), ...name.split("/"), binName)
22609
+ (0, import_node_path21.join)(packageRoot, "node_modules", ...name.split("/"), binName),
22610
+ (0, import_node_path21.join)((0, import_node_path21.dirname)((0, import_node_path21.dirname)(packageRoot)), ...name.split("/"), binName)
21258
22611
  ];
21259
- const found = published.map((name) => ({ name, path: binIn(name).find((file) => (0, import_node_fs21.existsSync)(file)) })).find((c) => c.path);
22612
+ const found = published.map((name) => ({ name, path: binIn(name).find((file) => (0, import_node_fs22.existsSync)(file)) })).find((c) => c.path);
21260
22613
  const platformPackage = found?.name ?? published[0];
21261
22614
  let source;
21262
22615
  let sourceProblem;
@@ -21267,7 +22620,7 @@ function readClaudeBinaryState(host = {}) {
21267
22620
  } else {
21268
22621
  source = { path: found.path, bytes: fileBytes(found.path) ?? 0 };
21269
22622
  }
21270
- if (!(0, import_node_fs21.existsSync)(binPath)) {
22623
+ if (!(0, import_node_fs22.existsSync)(binPath)) {
21271
22624
  return { state: "missing", binPath, expectedMagic: magic.name, platformPackage, ...source ? { source } : {}, ...sourceProblem ? { sourceProblem } : {} };
21272
22625
  }
21273
22626
  const head = readHead(binPath);
@@ -21310,9 +22663,9 @@ function healClaudeBinary(host = {}, onStep) {
21310
22663
  const platform2 = host.platform ?? process.platform;
21311
22664
  const aside = `${probe.binPath}.stub-${Date.now()}`;
21312
22665
  let renamed = false;
21313
- if ((0, import_node_fs21.existsSync)(probe.binPath)) {
22666
+ if ((0, import_node_fs22.existsSync)(probe.binPath)) {
21314
22667
  try {
21315
- (0, import_node_fs21.renameSync)(probe.binPath, aside);
22668
+ (0, import_node_fs22.renameSync)(probe.binPath, aside);
21316
22669
  renamed = true;
21317
22670
  onStep?.(`renamed the stub aside: ${aside}`);
21318
22671
  } catch (e) {
@@ -21321,12 +22674,12 @@ function healClaudeBinary(host = {}, onStep) {
21321
22674
  }
21322
22675
  try {
21323
22676
  onStep?.(`copying ${probe.source.path} \u2192 ${probe.binPath} (${(probe.source.bytes / 1e6).toFixed(0)} MB)`);
21324
- (0, import_node_fs21.copyFileSync)(probe.source.path, probe.binPath);
21325
- if (platform2 !== "win32") (0, import_node_fs21.chmodSync)(probe.binPath, 493);
22677
+ (0, import_node_fs22.copyFileSync)(probe.source.path, probe.binPath);
22678
+ if (platform2 !== "win32") (0, import_node_fs22.chmodSync)(probe.binPath, 493);
21326
22679
  } catch (e) {
21327
22680
  if (renamed) {
21328
22681
  try {
21329
- (0, import_node_fs21.renameSync)(aside, probe.binPath);
22682
+ (0, import_node_fs22.renameSync)(aside, probe.binPath);
21330
22683
  } catch {
21331
22684
  return { ok: false, detail: `copy failed (${e.message}) and the stub could not be restored \u2014 the original is at ${aside}` };
21332
22685
  }
@@ -21340,7 +22693,7 @@ function healClaudeBinary(host = {}, onStep) {
21340
22693
  let kept = false;
21341
22694
  if (renamed) {
21342
22695
  try {
21343
- (0, import_node_fs21.rmSync)(aside);
22696
+ (0, import_node_fs22.rmSync)(aside);
21344
22697
  } catch {
21345
22698
  kept = true;
21346
22699
  }
@@ -22192,7 +23545,7 @@ function renderPluginCachePlan(plan, applied) {
22192
23545
  }
22193
23546
 
22194
23547
  // src/app-actor.ts
22195
- var import_node_crypto5 = require("node:crypto");
23548
+ var import_node_crypto6 = require("node:crypto");
22196
23549
  var APP_ACTOR_ENV = "MMI_ACTOR";
22197
23550
  var APP_VAULT_REPO = "mutmutco/MMI-Hub";
22198
23551
  var APP_VAULT_KEYS = ["GITHUB_APP_ID", "GITHUB_APP_INSTALLATION_ID", "GITHUB_APP_PRIVATE_KEY"];
@@ -22236,7 +23589,7 @@ function mintAppJwt(appId, privateKeyPem, nowSec) {
22236
23589
  exp: now + APP_JWT_TTL_S,
22237
23590
  iss: appId
22238
23591
  }));
22239
- const signer = (0, import_node_crypto5.createSign)("RSA-SHA256");
23592
+ const signer = (0, import_node_crypto6.createSign)("RSA-SHA256");
22240
23593
  signer.update(`${header}.${payload}`);
22241
23594
  return `${header}.${payload}.${signer.sign(privateKeyPem, "base64url")}`;
22242
23595
  }
@@ -22637,9 +23990,9 @@ function registerBoardCommands(program3) {
22637
23990
  }
22638
23991
 
22639
23992
  // src/bootstrap-commands.ts
22640
- var import_node_fs23 = require("node:fs");
23993
+ var import_node_fs24 = require("node:fs");
22641
23994
  var import_node_os12 = require("node:os");
22642
- var import_node_path21 = require("node:path");
23995
+ var import_node_path23 = require("node:path");
22643
23996
 
22644
23997
  // src/command-plans.ts
22645
23998
  function parseTrainTag(tag) {
@@ -22799,8 +24152,8 @@ function renderSteps(title, steps) {
22799
24152
  }
22800
24153
 
22801
24154
  // src/port-registry.ts
22802
- var import_node_fs22 = require("node:fs");
22803
- var import_node_path20 = require("node:path");
24155
+ var import_node_fs23 = require("node:fs");
24156
+ var import_node_path22 = require("node:path");
22804
24157
 
22805
24158
  // ../infra/port-geometry.mjs
22806
24159
  var PORT_BLOCK = 100;
@@ -22814,8 +24167,8 @@ function nextPortBlock(registry2) {
22814
24167
  return [base, base + PORT_SPAN];
22815
24168
  }
22816
24169
  function loadPortRegistry(path2) {
22817
- if (!(0, import_node_fs22.existsSync)(path2)) return {};
22818
- const raw = JSON.parse((0, import_node_fs22.readFileSync)(path2, "utf8"));
24170
+ if (!(0, import_node_fs23.existsSync)(path2)) return {};
24171
+ const raw = JSON.parse((0, import_node_fs23.readFileSync)(path2, "utf8"));
22819
24172
  const out = {};
22820
24173
  for (const [key, value] of Object.entries(raw)) {
22821
24174
  if (Array.isArray(value) && value.length === 2 && value.every((n) => typeof n === "number")) {
@@ -22829,9 +24182,9 @@ function ensurePortRange(repo, path2) {
22829
24182
  const existing = registry2[repo];
22830
24183
  if (existing) return existing;
22831
24184
  const range = nextPortBlock(registry2);
22832
- const raw = (0, import_node_fs22.existsSync)(path2) ? JSON.parse((0, import_node_fs22.readFileSync)(path2, "utf8")) : {};
24185
+ const raw = (0, import_node_fs23.existsSync)(path2) ? JSON.parse((0, import_node_fs23.readFileSync)(path2, "utf8")) : {};
22833
24186
  raw[repo] = range;
22834
- (0, import_node_fs22.writeFileSync)(path2, JSON.stringify(raw, null, 2) + "\n", "utf8");
24187
+ (0, import_node_fs23.writeFileSync)(path2, JSON.stringify(raw, null, 2) + "\n", "utf8");
22835
24188
  return range;
22836
24189
  }
22837
24190
  function portCursorSeed(registry2) {
@@ -22853,22 +24206,22 @@ function existingPortRange(repo, registry2) {
22853
24206
  return registry2[repo] ?? null;
22854
24207
  }
22855
24208
  function portRangeInfraAt(root, source) {
22856
- const registryPath = (0, import_node_path20.join)(root, "infra", "port-ranges.json");
22857
- const ddbScriptPath = (0, import_node_path20.join)(root, "infra", "port-ddb.mjs");
22858
- if (!(0, import_node_fs22.existsSync)(registryPath) || !(0, import_node_fs22.existsSync)(ddbScriptPath)) return null;
24209
+ const registryPath = (0, import_node_path22.join)(root, "infra", "port-ranges.json");
24210
+ const ddbScriptPath = (0, import_node_path22.join)(root, "infra", "port-ddb.mjs");
24211
+ if (!(0, import_node_fs23.existsSync)(registryPath) || !(0, import_node_fs23.existsSync)(ddbScriptPath)) return null;
22859
24212
  return { root, source, registryPath, ddbScriptPath };
22860
24213
  }
22861
24214
  function resolvePortRangeInfra(cwd, packageDir) {
22862
24215
  const direct = portRangeInfraAt(cwd, "cwd");
22863
24216
  if (direct) return direct;
22864
- for (let dir = cwd; ; dir = (0, import_node_path20.dirname)(dir)) {
22865
- const sibling = portRangeInfraAt((0, import_node_path20.join)(dir, "MMI-Hub"), "sibling-hub");
24217
+ for (let dir = cwd; ; dir = (0, import_node_path22.dirname)(dir)) {
24218
+ const sibling = portRangeInfraAt((0, import_node_path22.join)(dir, "MMI-Hub"), "sibling-hub");
22866
24219
  if (sibling) return sibling;
22867
- const parent = (0, import_node_path20.dirname)(dir);
24220
+ const parent = (0, import_node_path22.dirname)(dir);
22868
24221
  if (parent === dir) break;
22869
24222
  }
22870
24223
  if (packageDir) {
22871
- const pkgRoot = (0, import_node_path20.join)(packageDir, "..", "..");
24224
+ const pkgRoot = (0, import_node_path22.join)(packageDir, "..", "..");
22872
24225
  const pkgFrom = portRangeInfraAt(pkgRoot, "pkg-root");
22873
24226
  if (pkgFrom) return pkgFrom;
22874
24227
  }
@@ -22930,7 +24283,7 @@ async function assignPersistedPortRange(repo, slug, reg, opts) {
22930
24283
  }
22931
24284
 
22932
24285
  // src/bootstrap-drift.ts
22933
- var import_node_crypto6 = require("node:crypto");
24286
+ var import_node_crypto7 = require("node:crypto");
22934
24287
  function byteComparableSeeds(manifest, cls) {
22935
24288
  return manifest.seeds.filter((s) => s.ownership === "org" && s.source === "self" && s.classes.includes(cls));
22936
24289
  }
@@ -22950,7 +24303,7 @@ function compareSeedBytes(hubContent, repoContent) {
22950
24303
  return normalize(hubContent) === normalize(repoContent) ? "match" : "drift";
22951
24304
  }
22952
24305
  function seedContentHash(content) {
22953
- return (0, import_node_crypto6.createHash)("sha256").update(content.replace(/\r\n/g, "\n"), "utf8").digest("hex");
24306
+ return (0, import_node_crypto7.createHash)("sha256").update(content.replace(/\r\n/g, "\n"), "utf8").digest("hex");
22954
24307
  }
22955
24308
  function auditRepoSeedDrift(repo, seeds, hubContents, repoReads) {
22956
24309
  const byTarget = new Map(repoReads.map((r) => [r.target, r.content]));
@@ -23112,7 +24465,7 @@ function renderPropagationReport(plan) {
23112
24465
  }
23113
24466
 
23114
24467
  // src/bootstrap-propagation-identity.ts
23115
- var import_node_crypto7 = require("node:crypto");
24468
+ var import_node_crypto8 = require("node:crypto");
23116
24469
  var PROPAGATION_BRANCH_PREFIX = "seed-propagate-";
23117
24470
  var TARGET_MARKER_NAME = "mmi-bootstrap-propagation-target";
23118
24471
  function safeBranchPart(value, maxLength, fallback) {
@@ -23125,7 +24478,7 @@ function repoSlug2(repo) {
23125
24478
  function propagationBranch(repo, target) {
23126
24479
  const repoPart = safeBranchPart(repoSlug2(repo), 32, "repo");
23127
24480
  const targetPart = safeBranchPart(target, 48, "target");
23128
- const hash = (0, import_node_crypto7.createHash)("sha256").update(repo.trim().toLowerCase()).update("\0").update(target).digest("hex").slice(0, 12);
24481
+ const hash = (0, import_node_crypto8.createHash)("sha256").update(repo.trim().toLowerCase()).update("\0").update(target).digest("hex").slice(0, 12);
23129
24482
  return `${PROPAGATION_BRANCH_PREFIX}${repoPart}-${targetPart}-${hash}`;
23130
24483
  }
23131
24484
  function legacyPropagationBranch(repo) {
@@ -23932,13 +25285,13 @@ function registerBootstrapCommands(program3) {
23932
25285
  client: defaultGitHubClient(),
23933
25286
  projectMeta: meta,
23934
25287
  deployModel: typeof meta?.deployModel === "string" ? meta.deployModel : void 0,
23935
- readLocalFile: (path2) => path2 === "projects.json" && apiProjects != null ? apiProjects : (0, import_node_fs23.existsSync)(path2) ? (0, import_node_fs23.readFileSync)(path2, "utf8") : null,
25288
+ readLocalFile: (path2) => path2 === "projects.json" && apiProjects != null ? apiProjects : (0, import_node_fs24.existsSync)(path2) ? (0, import_node_fs24.readFileSync)(path2, "utf8") : null,
23936
25289
  // requiredGcpApis is stored as an array by a JSON write, but `org project set --var KEY=VALUE` stores a raw
23937
25290
  // comma-string — accept either so the seeded value verifies regardless of how it was written.
23938
25291
  // #3689: the same committed map the org access audit reads (#3664), so a sanctioned admin is not a
23939
25292
  // permanent bootstrap failure on one surface and an intended state on the other. Absent file → no
23940
25293
  // sanction, which is the pre-#3664 behaviour.
23941
- sanctionedAdmins: (0, import_node_fs23.existsSync)("access-matrix.json") ? entriesValueByCanonicalRepo(loadSanctionedAdmins((0, import_node_fs23.readFileSync)("access-matrix.json", "utf8")), repo) : void 0,
25294
+ sanctionedAdmins: (0, import_node_fs24.existsSync)("access-matrix.json") ? entriesValueByCanonicalRepo(loadSanctionedAdmins((0, import_node_fs24.readFileSync)("access-matrix.json", "utf8")), repo) : void 0,
23942
25295
  requiredGcpApis: (() => {
23943
25296
  const v = meta?.requiredGcpApis;
23944
25297
  if (Array.isArray(v)) return v;
@@ -23991,14 +25344,14 @@ function registerBootstrapCommands(program3) {
23991
25344
  bootstrap.command("drift").description("#3818: compare every org-owned whole-file seed against MMI-Hub's copy across the registry roster; read-only").option("--repo <owner/repo>", "audit one repo instead of the roster (never a fleet verdict)").option("--json", "machine-readable output").action(async () => {
23992
25345
  const o = { repo: rawValue("--repo", ""), json: rawFlag("--json") };
23993
25346
  const manifestPath = "skills/bootstrap/seeds/manifest.json";
23994
- if (!(0, import_node_fs23.existsSync)(manifestPath)) return fail(`bootstrap drift: ${manifestPath} not found; run from the MMI-Hub repo root \u2014 the Hub's copies ARE the reference this compares against`);
25347
+ if (!(0, import_node_fs24.existsSync)(manifestPath)) return fail(`bootstrap drift: ${manifestPath} not found; run from the MMI-Hub repo root \u2014 the Hub's copies ARE the reference this compares against`);
23995
25348
  const seedSource = await resolveHubSeedSource(execGitForSeedSource);
23996
25349
  if (!seedSource.ok) return fail(`bootstrap drift: ${seedSource.reason}`);
23997
- const manifest = loadBootstrapSeeds((0, import_node_fs23.readFileSync)(manifestPath, "utf8"));
25350
+ const manifest = loadBootstrapSeeds((0, import_node_fs24.readFileSync)(manifestPath, "utf8"));
23998
25351
  const hubContents = /* @__PURE__ */ new Map();
23999
25352
  for (const s of manifest.seeds) {
24000
25353
  if (s.ownership !== "org" || s.source !== "self") continue;
24001
- hubContents.set(s.target, (0, import_node_fs23.existsSync)(s.target) ? (0, import_node_fs23.readFileSync)(s.target, "utf8") : null);
25354
+ hubContents.set(s.target, (0, import_node_fs24.existsSync)(s.target) ? (0, import_node_fs24.readFileSync)(s.target, "utf8") : null);
24002
25355
  }
24003
25356
  let targets;
24004
25357
  let classOf = (_repo) => "deployable";
@@ -24116,10 +25469,10 @@ function registerBootstrapCommands(program3) {
24116
25469
  return;
24117
25470
  }
24118
25471
  const manifestPath = "skills/bootstrap/seeds/manifest.json";
24119
- if (!(0, import_node_fs23.existsSync)(manifestPath)) return fail(`bootstrap apply: ${manifestPath} not found; bootstrap runs from the MMI-Hub repo root by design \u2014 it stamps org-level resources (Project, Ruleset, secrets, access) through the GitHub App, which is only authorized from the Hub checkout`);
25472
+ if (!(0, import_node_fs24.existsSync)(manifestPath)) return fail(`bootstrap apply: ${manifestPath} not found; bootstrap runs from the MMI-Hub repo root by design \u2014 it stamps org-level resources (Project, Ruleset, secrets, access) through the GitHub App, which is only authorized from the Hub checkout`);
24120
25473
  const seedSource = await resolveHubSeedSource(execGitForSeedSource);
24121
25474
  if (!seedSource.ok) return fail(`bootstrap apply: ${seedSource.reason}`);
24122
- const manifest = loadBootstrapSeeds((0, import_node_fs23.readFileSync)(manifestPath, "utf8"));
25475
+ const manifest = loadBootstrapSeeds((0, import_node_fs24.readFileSync)(manifestPath, "utf8"));
24123
25476
  const baseBranch = o.class === "content" ? "main" : "development";
24124
25477
  const slug = parsedRepo.slug;
24125
25478
  const onlyTarget = o.only.trim();
@@ -24131,16 +25484,16 @@ function registerBootstrapCommands(program3) {
24131
25484
  }
24132
25485
  const onlyManagedBlock = onlyTarget ? seedsToApply[0]?.managedBlock != null : false;
24133
25486
  const gh = async (args) => execFileP("gh", args, { timeout: 2e4 });
24134
- const readFile8 = (p) => (0, import_node_fs23.existsSync)(p) ? (0, import_node_fs23.readFileSync)(p, "utf8") : null;
25487
+ const readFile8 = (p) => (0, import_node_fs24.existsSync)(p) ? (0, import_node_fs24.readFileSync)(p, "utf8") : null;
24135
25488
  const enc = (p) => p.split("/").map(encodeURIComponent).join("/");
24136
25489
  const putSeed = async (target, content, ref, sha) => {
24137
- const tmp = (0, import_node_path21.join)((0, import_node_os12.tmpdir)(), `mmi-seed-${process.pid}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}.json`);
24138
- (0, import_node_fs23.writeFileSync)(tmp, JSON.stringify(contentPutBody(target, content, ref, sha)), "utf8");
25490
+ const tmp = (0, import_node_path23.join)((0, import_node_os12.tmpdir)(), `mmi-seed-${process.pid}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}.json`);
25491
+ (0, import_node_fs24.writeFileSync)(tmp, JSON.stringify(contentPutBody(target, content, ref, sha)), "utf8");
24139
25492
  try {
24140
25493
  await gh(contentPutInputArgs(repo, target, tmp));
24141
25494
  } finally {
24142
25495
  try {
24143
- (0, import_node_fs23.unlinkSync)(tmp);
25496
+ (0, import_node_fs24.unlinkSync)(tmp);
24144
25497
  } catch {
24145
25498
  }
24146
25499
  }
@@ -24455,10 +25808,10 @@ LIVE apply to ${repo}:
24455
25808
  bootstrap.command("propagate").description("#4238: re-entrant canary\u2192wave tick \u2014 plan (or, with --execute, open) per-repo PRs fanning an org-owned seed out to the fleet").option("--target <path>", "the manifest target to propagate (an org-owned whole file or declared Hub-managed block)").option("--execute", "LIVE tick via gh (master-gated) \u2014 opens/reuses per-repo seed-propagate PRs; dry-run prints the plan only").option("--json", "machine-readable output").action(async () => {
24456
25809
  const o = { target: rawValue("--target", ""), execute: rawFlag("--execute"), json: rawFlag("--json") };
24457
25810
  const manifestPath = "skills/bootstrap/seeds/manifest.json";
24458
- if (!(0, import_node_fs23.existsSync)(manifestPath)) return fail(`bootstrap propagate: ${manifestPath} not found; run from the MMI-Hub repo root \u2014 the Hub's copies ARE the desired state this tick propagates`);
25811
+ if (!(0, import_node_fs24.existsSync)(manifestPath)) return fail(`bootstrap propagate: ${manifestPath} not found; run from the MMI-Hub repo root \u2014 the Hub's copies ARE the desired state this tick propagates`);
24459
25812
  const seedSource = await resolveHubSeedSource(execGitForSeedSource);
24460
25813
  if (!seedSource.ok) return fail(`bootstrap propagate: ${seedSource.reason}`);
24461
- const manifest = loadBootstrapSeeds((0, import_node_fs23.readFileSync)(manifestPath, "utf8"));
25814
+ const manifest = loadBootstrapSeeds((0, import_node_fs24.readFileSync)(manifestPath, "utf8"));
24462
25815
  const propagatable = manifest.seeds.filter(isPropagatableSeed);
24463
25816
  if (!o.target) {
24464
25817
  return fail(`bootstrap propagate: --target <path> is required \u2014 one of:
@@ -24467,9 +25820,9 @@ LIVE apply to ${repo}:
24467
25820
  const seed = propagatable.find((s) => s.target === o.target);
24468
25821
  if (!seed) return fail(`bootstrap propagate: --target '${o.target}' names no centrally propagatable seed in ${manifestPath}. Propagatable targets:
24469
25822
  ${propagatable.map((s) => s.target).join("\n ")}`);
24470
- if (!seed.managedBlock && !(0, import_node_fs23.existsSync)(seed.target)) return fail(`bootstrap propagate: the Hub's own copy of '${seed.target}' is missing \u2014 nothing to propagate`);
24471
- const hubContent = seed.managedBlock ? null : (0, import_node_fs23.readFileSync)(seed.target, "utf8");
24472
- const readSeedFile = (path2) => (0, import_node_fs23.existsSync)(path2) ? (0, import_node_fs23.readFileSync)(path2, "utf8") : null;
25823
+ if (!seed.managedBlock && !(0, import_node_fs24.existsSync)(seed.target)) return fail(`bootstrap propagate: the Hub's own copy of '${seed.target}' is missing \u2014 nothing to propagate`);
25824
+ const hubContent = seed.managedBlock ? null : (0, import_node_fs24.readFileSync)(seed.target, "utf8");
25825
+ const readSeedFile = (path2) => (0, import_node_fs24.existsSync)(path2) ? (0, import_node_fs24.readFileSync)(path2, "utf8") : null;
24473
25826
  const isWorkflowSeed = seed.target.startsWith(".github/workflows/");
24474
25827
  const cfg = await loadConfig();
24475
25828
  const projects = await fetchProjectsList(registryClientDeps(cfg));
@@ -24478,9 +25831,9 @@ LIVE apply to ${repo}:
24478
25831
  }
24479
25832
  const rosterRepos = collectRegistryRepos(projects).filter((r) => r.toLowerCase() !== "mutmutco/mmi-hub");
24480
25833
  let independentCount = rosterRepos.length;
24481
- if ((0, import_node_fs23.existsSync)("projects.json")) {
25834
+ if ((0, import_node_fs24.existsSync)("projects.json")) {
24482
25835
  try {
24483
- const local = JSON.parse((0, import_node_fs23.readFileSync)("projects.json", "utf8"));
25836
+ const local = JSON.parse((0, import_node_fs24.readFileSync)("projects.json", "utf8"));
24484
25837
  const localRepos = /* @__PURE__ */ new Set();
24485
25838
  for (const p of local.projects ?? []) for (const r of p.repos ?? []) {
24486
25839
  const full = (r.includes("/") ? r : `mutmutco/${r}`).toLowerCase();
@@ -24640,15 +25993,15 @@ LIVE apply to ${repo}:
24640
25993
  } catch {
24641
25994
  existingSha = void 0;
24642
25995
  }
24643
- const tmp = (0, import_node_path21.join)((0, import_node_os12.tmpdir)(), `mmi-propagate-${process.pid}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}.json`);
25996
+ const tmp = (0, import_node_path23.join)((0, import_node_os12.tmpdir)(), `mmi-propagate-${process.pid}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}.json`);
24644
25997
  const desiredContent = desiredByRepo.get(rec.repo);
24645
25998
  if (desiredContent == null) return fail(`bootstrap propagate: no resolved content for ${rec.repo} ${seed.target} \u2014 refusing to write`);
24646
- (0, import_node_fs23.writeFileSync)(tmp, JSON.stringify(contentPutBody(seed.target, desiredContent, branch, existingSha)), "utf8");
25999
+ (0, import_node_fs24.writeFileSync)(tmp, JSON.stringify(contentPutBody(seed.target, desiredContent, branch, existingSha)), "utf8");
24647
26000
  try {
24648
26001
  await gh(contentPutInputArgs(rec.repo, seed.target, tmp));
24649
26002
  } finally {
24650
26003
  try {
24651
- (0, import_node_fs23.unlinkSync)(tmp);
26004
+ (0, import_node_fs24.unlinkSync)(tmp);
24652
26005
  } catch {
24653
26006
  }
24654
26007
  }
@@ -24715,10 +26068,10 @@ Rollback: \`mmi-cli devops bootstrap rollback ${rec.repo} --target ${seed.target
24715
26068
  return fail(`bootstrap rollback: ${e.message}`);
24716
26069
  }
24717
26070
  const manifestPath = "skills/bootstrap/seeds/manifest.json";
24718
- if (!(0, import_node_fs23.existsSync)(manifestPath)) return fail(`bootstrap rollback: ${manifestPath} not found; run from the MMI-Hub repo root \u2014 the manifest names which targets are org-owned and therefore propagated (and rollback-able)`);
26071
+ if (!(0, import_node_fs24.existsSync)(manifestPath)) return fail(`bootstrap rollback: ${manifestPath} not found; run from the MMI-Hub repo root \u2014 the manifest names which targets are org-owned and therefore propagated (and rollback-able)`);
24719
26072
  const seedSource = await resolveHubSeedSource(execGitForSeedSource);
24720
26073
  if (!seedSource.ok) return fail(`bootstrap rollback: ${seedSource.reason}`);
24721
- const manifest = loadBootstrapSeeds((0, import_node_fs23.readFileSync)(manifestPath, "utf8"));
26074
+ const manifest = loadBootstrapSeeds((0, import_node_fs24.readFileSync)(manifestPath, "utf8"));
24722
26075
  const propagatable = manifest.seeds.filter(isPropagatableSeed);
24723
26076
  if (!o.target) {
24724
26077
  return fail(`bootstrap rollback: --target <path> is required \u2014 one of:
@@ -24735,10 +26088,10 @@ Rollback: \`mmi-cli devops bootstrap rollback ${rec.repo} --target ${seed.target
24735
26088
  const enc = (p) => p.split("/").map(encodeURIComponent).join("/");
24736
26089
  let candidates;
24737
26090
  if (o.record) {
24738
- if (!(0, import_node_fs23.existsSync)(o.record)) return fail(`bootstrap rollback: --record '${o.record}' not found`);
26091
+ if (!(0, import_node_fs24.existsSync)(o.record)) return fail(`bootstrap rollback: --record '${o.record}' not found`);
24739
26092
  let parsed;
24740
26093
  try {
24741
- parsed = JSON.parse((0, import_node_fs23.readFileSync)(o.record, "utf8"));
26094
+ parsed = JSON.parse((0, import_node_fs24.readFileSync)(o.record, "utf8"));
24742
26095
  } catch (e) {
24743
26096
  return fail(`bootstrap rollback: --record '${o.record}' is not valid JSON: ${e.message}`);
24744
26097
  }
@@ -24815,13 +26168,13 @@ Rollback: \`mmi-cli devops bootstrap rollback ${rec.repo} --target ${seed.target
24815
26168
  } catch {
24816
26169
  existingSha = void 0;
24817
26170
  }
24818
- const tmp = (0, import_node_path21.join)((0, import_node_os12.tmpdir)(), `mmi-rollback-${process.pid}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}.json`);
24819
- (0, import_node_fs23.writeFileSync)(tmp, JSON.stringify(contentPutBody(seed.target, preSeedContent, plan.branch, existingSha)), "utf8");
26171
+ const tmp = (0, import_node_path23.join)((0, import_node_os12.tmpdir)(), `mmi-rollback-${process.pid}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}.json`);
26172
+ (0, import_node_fs24.writeFileSync)(tmp, JSON.stringify(contentPutBody(seed.target, preSeedContent, plan.branch, existingSha)), "utf8");
24820
26173
  try {
24821
26174
  await gh(contentPutInputArgs(repo, seed.target, tmp));
24822
26175
  } finally {
24823
26176
  try {
24824
- (0, import_node_fs23.unlinkSync)(tmp);
26177
+ (0, import_node_fs24.unlinkSync)(tmp);
24825
26178
  } catch {
24826
26179
  }
24827
26180
  }
@@ -25814,7 +27167,7 @@ function renderDeployPortDoctor(report) {
25814
27167
  }
25815
27168
 
25816
27169
  // src/wave-status.ts
25817
- var import_node_fs26 = require("node:fs");
27170
+ var import_node_fs27 = require("node:fs");
25818
27171
 
25819
27172
  // src/git-worktree-observation.ts
25820
27173
  function parseGitWorktreePorcelain(text) {
@@ -25845,8 +27198,8 @@ function deriveComposeProjectName(worktreePath) {
25845
27198
 
25846
27199
  // src/stage-runner.ts
25847
27200
  var import_node_child_process8 = require("node:child_process");
25848
- var import_node_fs25 = require("node:fs");
25849
- var import_node_path23 = require("node:path");
27201
+ var import_node_fs26 = require("node:fs");
27202
+ var import_node_path25 = require("node:path");
25850
27203
  var import_node_net = require("node:net");
25851
27204
  var import_node_util4 = require("node:util");
25852
27205
 
@@ -25856,8 +27209,8 @@ function normalizeEol2(s) {
25856
27209
  }
25857
27210
 
25858
27211
  // src/env-detection.ts
25859
- var import_node_fs24 = require("node:fs");
25860
- var import_node_path22 = require("node:path");
27212
+ var import_node_fs25 = require("node:fs");
27213
+ var import_node_path24 = require("node:path");
25861
27214
  var SKIPPED_DIRS = /* @__PURE__ */ new Set([".git", "node_modules", "dist", ".jerv", ".pi", ".venv", "venv"]);
25862
27215
  function isEnvBaseName(name) {
25863
27216
  return name.toLowerCase() === ".env";
@@ -25867,7 +27220,7 @@ function findEnvFiles(root) {
25867
27220
  const walk2 = (dir, rel) => {
25868
27221
  let entries;
25869
27222
  try {
25870
- entries = (0, import_node_fs24.readdirSync)(dir, { withFileTypes: true });
27223
+ entries = (0, import_node_fs25.readdirSync)(dir, { withFileTypes: true });
25871
27224
  } catch {
25872
27225
  return;
25873
27226
  }
@@ -25875,13 +27228,13 @@ function findEnvFiles(root) {
25875
27228
  const relPath = rel ? `${rel}/${entry.name}` : entry.name;
25876
27229
  if (entry.isDirectory()) {
25877
27230
  if (SKIPPED_DIRS.has(entry.name)) continue;
25878
- walk2((0, import_node_path22.join)(dir, entry.name), relPath);
27231
+ walk2((0, import_node_path24.join)(dir, entry.name), relPath);
25879
27232
  continue;
25880
27233
  }
25881
27234
  if (isEnvBaseName(entry.name)) {
25882
27235
  let isDir = false;
25883
27236
  try {
25884
- isDir = (0, import_node_fs24.lstatSync)((0, import_node_path22.join)(dir, entry.name)).isDirectory();
27237
+ isDir = (0, import_node_fs25.lstatSync)((0, import_node_path24.join)(dir, entry.name)).isDirectory();
25885
27238
  } catch {
25886
27239
  }
25887
27240
  if (!isDir) found.push(relPath);
@@ -25912,7 +27265,7 @@ function healthPollIntervalMs() {
25912
27265
  return HEALTH_POLL_INTERVAL_MS;
25913
27266
  }
25914
27267
  function waitForProcessStability(child2, graceMs = earlyExitGraceMs()) {
25915
- return new Promise((resolve6, reject) => {
27268
+ return new Promise((resolve7, reject) => {
25916
27269
  let settled = false;
25917
27270
  const finish = (fn) => {
25918
27271
  if (settled) return;
@@ -25922,7 +27275,7 @@ function waitForProcessStability(child2, graceMs = earlyExitGraceMs()) {
25922
27275
  child2.removeAllListeners("exit");
25923
27276
  fn();
25924
27277
  };
25925
- const timer = setTimeout(() => finish(resolve6), graceMs);
27278
+ const timer = setTimeout(() => finish(resolve7), graceMs);
25926
27279
  child2.on("error", (err) => finish(() => reject(new Error(`stage process failed to start: ${err.message}`))));
25927
27280
  child2.on("exit", (code, signal) => {
25928
27281
  const detail = code != null ? `code ${code}` : signal ? `signal ${signal}` : "unknown reason";
@@ -26022,7 +27375,7 @@ function appendForceRecreate(up) {
26022
27375
  return `${up.trimEnd()} --force-recreate`;
26023
27376
  }
26024
27377
  function stageStatePath(cwd = process.cwd()) {
26025
- return (0, import_node_path23.join)(cwd, "tmp", "stage", "state.json");
27378
+ return (0, import_node_path25.join)(cwd, "tmp", "stage", "state.json");
26026
27379
  }
26027
27380
  function normPath(path2) {
26028
27381
  return path2.replace(/\\/g, "/").replace(/\/+$/, "").toLowerCase();
@@ -26128,10 +27481,10 @@ function pickStagePort(range, isFree) {
26128
27481
  throw new Error(`no free stage port in range ${start}-${end} \u2014 every port is in use`);
26129
27482
  }
26130
27483
  function isPortFree(port) {
26131
- return new Promise((resolve6) => {
27484
+ return new Promise((resolve7) => {
26132
27485
  const srv = (0, import_node_net.createServer)();
26133
- srv.once("error", () => resolve6(false));
26134
- srv.once("listening", () => srv.close(() => resolve6(true)));
27486
+ srv.once("error", () => resolve7(false));
27487
+ srv.once("listening", () => srv.close(() => resolve7(true)));
26135
27488
  srv.listen(port, "127.0.0.1");
26136
27489
  });
26137
27490
  }
@@ -26246,14 +27599,14 @@ function stageProcessEnv(stagePort, extraEnv) {
26246
27599
  }
26247
27600
  async function ensureStageRuntimeEnv(config, opts, cwd) {
26248
27601
  if (!config.ensureEnv) return;
26249
- const target = (0, import_node_path23.join)(cwd, config.ensureEnv.target);
26250
- const example = (0, import_node_path23.join)(cwd, config.ensureEnv.example);
26251
- if (!(0, import_node_fs25.existsSync)(target) && (0, import_node_fs25.existsSync)(example)) {
26252
- (0, import_node_fs25.copyFileSync)(example, target);
26253
- } else if ((0, import_node_fs25.existsSync)(target) && (0, import_node_fs25.existsSync)(example)) {
26254
- const stale = detectStaleEnvFile((0, import_node_fs25.readFileSync)(example, "utf8"), (0, import_node_fs25.readFileSync)(target, "utf8"), {
26255
- exampleMtimeMs: (0, import_node_fs25.statSync)(example).mtimeMs,
26256
- targetMtimeMs: (0, import_node_fs25.statSync)(target).mtimeMs
27602
+ const target = (0, import_node_path25.join)(cwd, config.ensureEnv.target);
27603
+ const example = (0, import_node_path25.join)(cwd, config.ensureEnv.example);
27604
+ if (!(0, import_node_fs26.existsSync)(target) && (0, import_node_fs26.existsSync)(example)) {
27605
+ (0, import_node_fs26.copyFileSync)(example, target);
27606
+ } else if ((0, import_node_fs26.existsSync)(target) && (0, import_node_fs26.existsSync)(example)) {
27607
+ const stale = detectStaleEnvFile((0, import_node_fs26.readFileSync)(example, "utf8"), (0, import_node_fs26.readFileSync)(target, "utf8"), {
27608
+ exampleMtimeMs: (0, import_node_fs26.statSync)(example).mtimeMs,
27609
+ targetMtimeMs: (0, import_node_fs26.statSync)(target).mtimeMs
26257
27610
  });
26258
27611
  if (stale) {
26259
27612
  const msg = `stale ${config.ensureEnv.target} (${stale}) \u2014 delete it or refresh from ${config.ensureEnv.example} before re-running /stage`;
@@ -26261,8 +27614,8 @@ async function ensureStageRuntimeEnv(config, opts, cwd) {
26261
27614
  console.error(`mmi-cli stage: ${msg} (allowed via --allow-stale-env)`);
26262
27615
  }
26263
27616
  }
26264
- if (opts.vaultEnvMerge && Object.keys(opts.vaultEnvMerge).length && (0, import_node_fs25.existsSync)(target)) {
26265
- (0, import_node_fs25.writeFileSync)(target, mergeEnvSecretsIntoFile((0, import_node_fs25.readFileSync)(target, "utf8"), opts.vaultEnvMerge), "utf8");
27617
+ if (opts.vaultEnvMerge && Object.keys(opts.vaultEnvMerge).length && (0, import_node_fs26.existsSync)(target)) {
27618
+ (0, import_node_fs26.writeFileSync)(target, mergeEnvSecretsIntoFile((0, import_node_fs26.readFileSync)(target, "utf8"), opts.vaultEnvMerge), "utf8");
26266
27619
  }
26267
27620
  }
26268
27621
  async function gitText(cwd, args) {
@@ -26292,20 +27645,20 @@ async function resolveGlobalStatePath(cwd, explicit) {
26292
27645
  return void 0;
26293
27646
  }
26294
27647
  function readState(path2) {
26295
- if (!(0, import_node_fs25.existsSync)(path2)) return null;
27648
+ if (!(0, import_node_fs26.existsSync)(path2)) return null;
26296
27649
  try {
26297
- return JSON.parse((0, import_node_fs25.readFileSync)(path2, "utf8"));
27650
+ return JSON.parse((0, import_node_fs26.readFileSync)(path2, "utf8"));
26298
27651
  } catch {
26299
27652
  return null;
26300
27653
  }
26301
27654
  }
26302
27655
  function mkdirFor(path2) {
26303
27656
  const dir = path2.slice(0, Math.max(path2.lastIndexOf("/"), path2.lastIndexOf("\\")));
26304
- (0, import_node_fs25.mkdirSync)(dir, { recursive: true });
27657
+ (0, import_node_fs26.mkdirSync)(dir, { recursive: true });
26305
27658
  }
26306
27659
  function writeState(path2, state) {
26307
27660
  mkdirFor(path2);
26308
- (0, import_node_fs25.writeFileSync)(path2, JSON.stringify(state, null, 2), "utf8");
27661
+ (0, import_node_fs26.writeFileSync)(path2, JSON.stringify(state, null, 2), "utf8");
26309
27662
  }
26310
27663
  function writeStagePortReservation(port, cwd, statePath, globalStatePath, now) {
26311
27664
  const reservation = {
@@ -26325,7 +27678,7 @@ async function cleanupStageState(state, paths, timeoutMs, fallbackCwd) {
26325
27678
  await shell(state.teardown.command.trim(), state.teardown.cwd || state.cwd || fallbackCwd, Math.max(timeoutMs, 1e4));
26326
27679
  }
26327
27680
  for (const path2 of [...new Set(paths.filter((p) => Boolean(p)))]) {
26328
- (0, import_node_fs25.rmSync)(path2, { force: true });
27681
+ (0, import_node_fs26.rmSync)(path2, { force: true });
26329
27682
  }
26330
27683
  }
26331
27684
  async function killTree(pid) {
@@ -26342,7 +27695,7 @@ async function killTree(pid) {
26342
27695
  } catch {
26343
27696
  }
26344
27697
  }
26345
- await new Promise((resolve6) => setTimeout(resolve6, 500));
27698
+ await new Promise((resolve7) => setTimeout(resolve7, 500));
26346
27699
  try {
26347
27700
  process.kill(-pid, "SIGKILL");
26348
27701
  } catch {
@@ -26363,7 +27716,7 @@ async function waitForHealth(url, timeoutMs, anyStatus = false) {
26363
27716
  } catch (e) {
26364
27717
  last = e.message;
26365
27718
  }
26366
- await new Promise((resolve6) => setTimeout(resolve6, healthPollIntervalMs()));
27719
+ await new Promise((resolve7) => setTimeout(resolve7, healthPollIntervalMs()));
26367
27720
  }
26368
27721
  throw new Error(`stage health check timed out for ${url}${last ? ` (${last})` : ""}`);
26369
27722
  }
@@ -26495,8 +27848,8 @@ async function runStage(config = {}, opts = {}) {
26495
27848
  });
26496
27849
  }
26497
27850
  } catch (e) {
26498
- (0, import_node_fs25.rmSync)(statePath, { force: true });
26499
- if (globalStatePath && globalStatePath !== statePath) (0, import_node_fs25.rmSync)(globalStatePath, { force: true });
27851
+ (0, import_node_fs26.rmSync)(statePath, { force: true });
27852
+ if (globalStatePath && globalStatePath !== statePath) (0, import_node_fs26.rmSync)(globalStatePath, { force: true });
26500
27853
  throw e;
26501
27854
  }
26502
27855
  const started = await startStage(config, {
@@ -26513,9 +27866,9 @@ async function runStage(config = {}, opts = {}) {
26513
27866
  // src/wave-status.ts
26514
27867
  function readStageSummary(worktreePath) {
26515
27868
  const statePath = stageStatePath(worktreePath);
26516
- if (!(0, import_node_fs26.existsSync)(statePath)) return void 0;
27869
+ if (!(0, import_node_fs27.existsSync)(statePath)) return void 0;
26517
27870
  try {
26518
- const state = JSON.parse((0, import_node_fs26.readFileSync)(statePath, "utf8"));
27871
+ const state = JSON.parse((0, import_node_fs27.readFileSync)(statePath, "utf8"));
26519
27872
  const port = typeof state.port === "number" ? state.port : void 0;
26520
27873
  if (port == null || !Number.isInteger(port) || port <= 0) return void 0;
26521
27874
  return { port, url: typeof state.url === "string" ? state.url : void 0 };
@@ -26570,9 +27923,9 @@ async function collectWaveStatus(deps) {
26570
27923
  }
26571
27924
 
26572
27925
  // src/discovery-commands.ts
26573
- var import_node_fs27 = require("node:fs");
27926
+ var import_node_fs28 = require("node:fs");
26574
27927
  var import_node_os13 = require("node:os");
26575
- var import_node_path24 = require("node:path");
27928
+ var import_node_path26 = require("node:path");
26576
27929
  var GC_GH_TIMEOUT_MS = 2e4;
26577
27930
  async function collectStatus() {
26578
27931
  const repo = await resolveRepo();
@@ -26762,8 +28115,8 @@ async function collectOnboardStatus(opts = {}) {
26762
28115
  }
26763
28116
  const home = (0, import_node_os13.homedir)();
26764
28117
  const plugin = onboardPluginGate({
26765
- readKnown: () => readFileSyncSafe((0, import_node_path24.join)(home, ...KNOWN_MARKETPLACES_RELATIVE), import_node_fs27.readFileSync),
26766
- readSettings: () => readFileSyncSafe((0, import_node_path24.join)(home, ".claude", "settings.json"), import_node_fs27.readFileSync)
28118
+ readKnown: () => readFileSyncSafe((0, import_node_path26.join)(home, ...KNOWN_MARKETPLACES_RELATIVE), import_node_fs28.readFileSync),
28119
+ readSettings: () => readFileSyncSafe((0, import_node_path26.join)(home, ".claude", "settings.json"), import_node_fs28.readFileSync)
26767
28120
  });
26768
28121
  return { track, board, registry: registry2, secrets, plugin, doors: opts.doors ?? [], nextCommand };
26769
28122
  }
@@ -28794,9 +30147,9 @@ async function runDoctorClean(opts, io, deps) {
28794
30147
  }
28795
30148
 
28796
30149
  // src/doctor-io.ts
28797
- var import_node_fs28 = require("node:fs");
30150
+ var import_node_fs29 = require("node:fs");
28798
30151
  var import_node_os14 = require("node:os");
28799
- var import_node_path25 = require("node:path");
30152
+ var import_node_path27 = require("node:path");
28800
30153
  var import_node_child_process9 = require("node:child_process");
28801
30154
  var import_node_util5 = require("node:util");
28802
30155
 
@@ -28809,8 +30162,8 @@ function nodeDiscardSinkPath(platform2 = process.platform) {
28809
30162
  var execFileP3 = (0, import_node_util5.promisify)(import_node_child_process9.execFile);
28810
30163
  function execFileCapture(file, args, opts = {}) {
28811
30164
  const sink = nodeDiscardSinkPath();
28812
- const inFd = (0, import_node_fs28.openSync)(sink, "r");
28813
- const errFd = (0, import_node_fs28.openSync)(sink, "w");
30165
+ const inFd = (0, import_node_fs29.openSync)(sink, "r");
30166
+ const errFd = (0, import_node_fs29.openSync)(sink, "w");
28814
30167
  try {
28815
30168
  return (0, import_node_child_process9.execFileSync)(file, args, {
28816
30169
  ...opts,
@@ -28818,17 +30171,17 @@ function execFileCapture(file, args, opts = {}) {
28818
30171
  stdio: [inFd, "pipe", errFd]
28819
30172
  });
28820
30173
  } finally {
28821
- (0, import_node_fs28.closeSync)(inFd);
28822
- (0, import_node_fs28.closeSync)(errFd);
30174
+ (0, import_node_fs29.closeSync)(inFd);
30175
+ (0, import_node_fs29.closeSync)(errFd);
28823
30176
  }
28824
30177
  }
28825
30178
  var MMI_PLUGIN_ID2 = "mmi@mutmutco";
28826
30179
  function stagedClaudePluginVersion() {
28827
30180
  try {
28828
- const stagingRoot = (0, import_node_path25.join)((0, import_node_os14.homedir)(), ".claude", "plugins", "staging");
28829
- const record = JSON.parse((0, import_node_fs28.readFileSync)((0, import_node_path25.join)(stagingRoot, "launch-target.json"), "utf8"));
30181
+ const stagingRoot = (0, import_node_path27.join)((0, import_node_os14.homedir)(), ".claude", "plugins", "staging");
30182
+ const record = JSON.parse((0, import_node_fs29.readFileSync)((0, import_node_path27.join)(stagingRoot, "launch-target.json"), "utf8"));
28830
30183
  if (typeof record.target !== "string" || typeof record.incoming !== "string") return void 0;
28831
- const manifest = JSON.parse((0, import_node_fs28.readFileSync)((0, import_node_path25.join)(record.incoming, ".claude-plugin", "plugin.json"), "utf8"));
30184
+ const manifest = JSON.parse((0, import_node_fs29.readFileSync)((0, import_node_path27.join)(record.incoming, ".claude-plugin", "plugin.json"), "utf8"));
28832
30185
  if (typeof manifest.version !== "string") return void 0;
28833
30186
  return manifest.version === record.target ? record.target : void 0;
28834
30187
  } catch {
@@ -28838,7 +30191,7 @@ function stagedClaudePluginVersion() {
28838
30191
  function installedClaudePluginVersion() {
28839
30192
  try {
28840
30193
  const file = JSON.parse(
28841
- (0, import_node_fs28.readFileSync)((0, import_node_path25.join)((0, import_node_os14.homedir)(), ".claude", "plugins", "installed_plugins.json"), "utf8")
30194
+ (0, import_node_fs29.readFileSync)((0, import_node_path27.join)((0, import_node_os14.homedir)(), ".claude", "plugins", "installed_plugins.json"), "utf8")
28842
30195
  );
28843
30196
  const versions = (file.plugins?.[MMI_PLUGIN_ID2] ?? []).map((r) => r.version).filter((v) => Boolean(v));
28844
30197
  if (versions.length === 0) return void 0;
@@ -28849,7 +30202,7 @@ function installedClaudePluginVersion() {
28849
30202
  }
28850
30203
  function manifestVersion(path2) {
28851
30204
  try {
28852
- const manifest = JSON.parse((0, import_node_fs28.readFileSync)(path2, "utf8"));
30205
+ const manifest = JSON.parse((0, import_node_fs29.readFileSync)(path2, "utf8"));
28853
30206
  return typeof manifest.version === "string" && manifest.version.trim() ? manifest.version.trim() : void 0;
28854
30207
  } catch {
28855
30208
  return void 0;
@@ -28858,12 +30211,12 @@ function manifestVersion(path2) {
28858
30211
  function readHermesPluginEvidence(env = process.env) {
28859
30212
  const host = hermesConfigRoot(env);
28860
30213
  const root = hermesPluginRoot(env);
28861
- const installRecordPresent = (0, import_node_fs28.existsSync)(root);
28862
- const manifestPath = (0, import_node_path25.join)(root, "plugin.yaml");
30214
+ const installRecordPresent = (0, import_node_fs29.existsSync)(root);
30215
+ const manifestPath = (0, import_node_path27.join)(root, "plugin.yaml");
28863
30216
  let installedVersion;
28864
30217
  let manifest = "missing";
28865
30218
  try {
28866
- const text = (0, import_node_fs28.readFileSync)(manifestPath, "utf8");
30219
+ const text = (0, import_node_fs29.readFileSync)(manifestPath, "utf8");
28867
30220
  let version;
28868
30221
  try {
28869
30222
  const parsed = JSON.parse(text).version;
@@ -28877,20 +30230,20 @@ function readHermesPluginEvidence(env = process.env) {
28877
30230
  if (version) {
28878
30231
  installedVersion = version;
28879
30232
  manifest = "valid";
28880
- } else if ((0, import_node_fs28.existsSync)(manifestPath)) manifest = "invalid";
30233
+ } else if ((0, import_node_fs29.existsSync)(manifestPath)) manifest = "invalid";
28881
30234
  } catch {
28882
- if ((0, import_node_fs28.existsSync)(manifestPath)) manifest = "invalid";
30235
+ if ((0, import_node_fs29.existsSync)(manifestPath)) manifest = "invalid";
28883
30236
  }
28884
30237
  let skills = false;
28885
30238
  try {
28886
- skills = (0, import_node_fs28.existsSync)((0, import_node_path25.join)(root, "skills")) && (0, import_node_fs28.statSync)((0, import_node_path25.join)(root, "skills")).isDirectory();
30239
+ skills = (0, import_node_fs29.existsSync)((0, import_node_path27.join)(root, "skills")) && (0, import_node_fs29.statSync)((0, import_node_path27.join)(root, "skills")).isDirectory();
28887
30240
  } catch {
28888
30241
  }
28889
30242
  return {
28890
- hostPresent: (0, import_node_fs28.existsSync)(host),
30243
+ hostPresent: (0, import_node_fs29.existsSync)(host),
28891
30244
  installRecordPresent,
28892
30245
  manifest,
28893
- payloadPresent: (0, import_node_fs28.existsSync)((0, import_node_path25.join)(root, "__init__.py")) && skills && manifest === "valid",
30246
+ payloadPresent: (0, import_node_fs29.existsSync)((0, import_node_path27.join)(root, "__init__.py")) && skills && manifest === "valid",
28894
30247
  ...installedVersion ? { installedVersion } : {}
28895
30248
  };
28896
30249
  }
@@ -28898,7 +30251,7 @@ function installedSurfacePluginVersion(surface) {
28898
30251
  const token = surfaceToken(surface);
28899
30252
  if (token === "hermes") return readHermesPluginEvidence().installedVersion;
28900
30253
  if (token === "cursor") {
28901
- return manifestVersion((0, import_node_path25.join)(cursorLocalPluginRoot(), ".cursor-plugin", "plugin.json"));
30254
+ return manifestVersion((0, import_node_path27.join)(cursorLocalPluginRoot(), ".cursor-plugin", "plugin.json"));
28902
30255
  }
28903
30256
  if (token === "jervcode") {
28904
30257
  return installedJervCodePackageVersion();
@@ -28935,13 +30288,13 @@ function worktreeRootSync() {
28935
30288
  }
28936
30289
  var gitignorePath = () => {
28937
30290
  const root = worktreeRootSync();
28938
- return root === null ? null : (0, import_node_path25.join)(root, ".gitignore");
30291
+ return root === null ? null : (0, import_node_path27.join)(root, ".gitignore");
28939
30292
  };
28940
30293
  function readGitignore() {
28941
30294
  const path2 = gitignorePath();
28942
30295
  if (path2 === null) return null;
28943
30296
  try {
28944
- return (0, import_node_fs28.readFileSync)(path2, "utf8");
30297
+ return (0, import_node_fs29.readFileSync)(path2, "utf8");
28945
30298
  } catch {
28946
30299
  return null;
28947
30300
  }
@@ -28950,14 +30303,14 @@ function writeGitignore(content) {
28950
30303
  const path2 = gitignorePath();
28951
30304
  if (path2 === null) return false;
28952
30305
  try {
28953
- (0, import_node_fs28.writeFileSync)(path2, content, "utf8");
30306
+ (0, import_node_fs29.writeFileSync)(path2, content, "utf8");
28954
30307
  return true;
28955
30308
  } catch {
28956
30309
  return false;
28957
30310
  }
28958
30311
  }
28959
30312
  function lineEndingState(root) {
28960
- const attributesPresent = (0, import_node_fs28.existsSync)((0, import_node_path25.join)(root, ".gitattributes"));
30313
+ const attributesPresent = (0, import_node_fs29.existsSync)((0, import_node_path27.join)(root, ".gitattributes"));
28961
30314
  try {
28962
30315
  const output = execFileCapture("git", ["-C", root, "ls-files", "--eol", "--", ":(glob)**/*.sh"], {
28963
30316
  windowsHide: true
@@ -29277,102 +30630,6 @@ function registerExplainCommand(program3) {
29277
30630
  });
29278
30631
  }
29279
30632
 
29280
- // src/file-lock.ts
29281
- var import_promises3 = require("node:fs/promises");
29282
- var import_node_path26 = require("node:path");
29283
- var sleep = (ms) => new Promise((resolve6) => setTimeout(resolve6, ms));
29284
- var IMMEDIATE_RETRY_BUDGET = 3;
29285
- var FileLockBusyError = class extends Error {
29286
- lockPath;
29287
- constructor(label, lockPath, maxWaitMs) {
29288
- super(`${label} busy: ${lockPath} held longer than ${maxWaitMs}ms`);
29289
- this.name = "FileLockBusyError";
29290
- this.lockPath = lockPath;
29291
- }
29292
- };
29293
- function resolveFileLockOpts(opts = {}) {
29294
- return {
29295
- staleMs: opts.staleMs ?? 3e4,
29296
- retryMs: opts.retryMs ?? 50,
29297
- maxWaitMs: opts.maxWaitMs ?? 5e3,
29298
- label: opts.label ?? "file lock"
29299
- };
29300
- }
29301
- async function acquireFileLock(lockPath, opts, deadline) {
29302
- let immediateRetries = 0;
29303
- for (; ; ) {
29304
- let handle;
29305
- try {
29306
- handle = await (0, import_promises3.open)(lockPath, "wx");
29307
- } catch (e) {
29308
- const code = e.code;
29309
- if (code !== "EEXIST" && code !== "EPERM" && code !== "EBUSY" && code !== "EACCES") throw e;
29310
- const retryNow = async () => {
29311
- if (Date.now() >= deadline) throw new FileLockBusyError(opts.label, lockPath, opts.maxWaitMs);
29312
- if (++immediateRetries > IMMEDIATE_RETRY_BUDGET) await sleep(opts.retryMs);
29313
- };
29314
- try {
29315
- const age = Date.now() - (await (0, import_promises3.stat)(lockPath)).mtimeMs;
29316
- if (age > opts.staleMs) {
29317
- try {
29318
- await (0, import_promises3.unlink)(lockPath);
29319
- await retryNow();
29320
- continue;
29321
- } catch (unlinkError) {
29322
- if (unlinkError instanceof FileLockBusyError) throw unlinkError;
29323
- const unlinkCode = unlinkError.code;
29324
- if (unlinkCode === "ENOENT") {
29325
- await retryNow();
29326
- continue;
29327
- }
29328
- }
29329
- }
29330
- } catch (statError) {
29331
- if (statError instanceof FileLockBusyError) throw statError;
29332
- if (statError.code !== "ENOENT") throw statError;
29333
- await retryNow();
29334
- continue;
29335
- }
29336
- if (Date.now() >= deadline) {
29337
- throw new FileLockBusyError(opts.label, lockPath, opts.maxWaitMs);
29338
- }
29339
- await sleep(opts.retryMs);
29340
- continue;
29341
- }
29342
- const token = `${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2)}`;
29343
- try {
29344
- await handle.writeFile(token);
29345
- } catch (e) {
29346
- await handle.close().catch(() => void 0);
29347
- throw e;
29348
- }
29349
- return { token, handle };
29350
- }
29351
- }
29352
- async function fileLockHeldBy(lockPath, token) {
29353
- try {
29354
- return await (0, import_promises3.readFile)(lockPath, "utf8") === token;
29355
- } catch {
29356
- return false;
29357
- }
29358
- }
29359
- async function releaseFileLock(lockPath, guard) {
29360
- await guard.handle.close().catch(() => void 0);
29361
- if (await fileLockHeldBy(lockPath, guard.token)) {
29362
- await (0, import_promises3.unlink)(lockPath).catch(() => void 0);
29363
- }
29364
- }
29365
- async function withFileLock(lockPath, opts, fn) {
29366
- const resolved = resolveFileLockOpts(opts);
29367
- await (0, import_promises3.mkdir)((0, import_node_path26.dirname)(lockPath), { recursive: true }).catch(() => void 0);
29368
- const guard = await acquireFileLock(lockPath, resolved, Date.now() + resolved.maxWaitMs);
29369
- try {
29370
- return await fn();
29371
- } finally {
29372
- await releaseFileLock(lockPath, guard);
29373
- }
29374
- }
29375
-
29376
30633
  // src/gh-account-visibility.ts
29377
30634
  function parseGhAuthAccounts(stdout) {
29378
30635
  const accounts = [];
@@ -29392,8 +30649,8 @@ function parseOriginRepo(remoteUrl) {
29392
30649
  }
29393
30650
 
29394
30651
  // src/issue-commands.ts
29395
- var import_node_fs29 = require("node:fs");
29396
- var import_node_crypto8 = require("node:crypto");
30652
+ var import_node_fs30 = require("node:fs");
30653
+ var import_node_crypto9 = require("node:crypto");
29397
30654
 
29398
30655
  // src/issue-body.ts
29399
30656
  var import_node_os15 = require("node:os");
@@ -29642,7 +30899,7 @@ async function editIssue(client, options, deps = {}) {
29642
30899
  const url = `https://github.com/${repo}/issues/${parsed.number}`;
29643
30900
  const patch = {};
29644
30901
  let bodyChanged = false;
29645
- const textDeps = () => deps.textDeps ?? { readFile: (p, e) => Promise.resolve((0, import_node_fs29.readFileSync)(p, e)), readStdin: () => Promise.resolve("") };
30902
+ const textDeps = () => deps.textDeps ?? { readFile: (p, e) => Promise.resolve((0, import_node_fs30.readFileSync)(p, e)), readStdin: () => Promise.resolve("") };
29646
30903
  if (options.titleFile !== void 0) {
29647
30904
  patch.title = await resolveIssueTitle({ title: options.title, titleFile: options.titleFile }, textDeps());
29648
30905
  } else if (options.title !== void 0) {
@@ -29923,7 +31180,7 @@ function rowIdempotencyKey(batchKey, spec) {
29923
31180
  const identity = `${spec.type}
29924
31181
  ${spec.title.trim()}
29925
31182
  ${spec.body ?? ""}`;
29926
- const hash = (0, import_node_crypto8.createHash)("sha256").update(identity).digest("hex").slice(0, 16);
31183
+ const hash = (0, import_node_crypto9.createHash)("sha256").update(identity).digest("hex").slice(0, 16);
29927
31184
  return `${batchKey}:${hash}`;
29928
31185
  }
29929
31186
  var BATCH_SPEC_KEYS = /* @__PURE__ */ new Set(["type", "title", "body", "priority", "labels", "label", "parent", "repo", "surface"]);
@@ -30243,8 +31500,8 @@ function registerIssueLifecycleCommands(program3, deps = {}) {
30243
31500
  (_opts, args) => ({ command: "issue unlink-child", parent: args[0], child: args[1] })
30244
31501
  ).action(async (parentRef, childRef, o) => {
30245
31502
  try {
30246
- parseIssueRef(parentRef, o.repo);
30247
- parseIssueRef(childRef, o.repo);
31503
+ parseIssueRef(parentRef);
31504
+ parseIssueRef(childRef);
30248
31505
  const defaultRepo = await resolveRepo(o.repo);
30249
31506
  const result = await unlinkSubIssue(ghRunner, parentRef, childRef, defaultRepo);
30250
31507
  console.log(JSON.stringify(result));
@@ -30267,7 +31524,7 @@ function extendCreateCommand(issue, batchAttach) {
30267
31524
  if (opts.batch) {
30268
31525
  let specs;
30269
31526
  try {
30270
- const raw = (0, import_node_fs29.readFileSync)(opts.batch, "utf8");
31527
+ const raw = (0, import_node_fs30.readFileSync)(opts.batch, "utf8");
30271
31528
  specs = JSON.parse(raw);
30272
31529
  if (!Array.isArray(specs)) throw new Error("batch file must contain a JSON array");
30273
31530
  } catch (e) {
@@ -30739,7 +31996,7 @@ async function readRestPrSnapshotWithRetry(prNumber, repo, gh = defaultGhApi, op
30739
31996
  const retries = options?.retries ?? PR_SNAPSHOT_READ_RETRIES;
30740
31997
  const untilMs = options?.retryTransientUntilMs;
30741
31998
  const delayMs = options?.delayMs ?? (untilMs !== void 0 ? PR_CHECKS_POLL_MS : PR_SNAPSHOT_READ_DELAY_MS);
30742
- const sleep2 = options?.sleep ?? ((ms) => new Promise((resolve6) => setTimeout(resolve6, ms)));
31999
+ const sleep2 = options?.sleep ?? ((ms) => new Promise((resolve7) => setTimeout(resolve7, ms)));
30743
32000
  const now = options?.now ?? (() => Date.now());
30744
32001
  let lastError = "no attempt completed";
30745
32002
  let attempt = 0;
@@ -31394,8 +32651,8 @@ function registerPrLifecycleCommands(program3) {
31394
32651
  }
31395
32652
 
31396
32653
  // src/project-info-sync.ts
31397
- var import_node_fs30 = require("node:fs");
31398
- var import_node_path27 = require("node:path");
32654
+ var import_node_fs31 = require("node:fs");
32655
+ var import_node_path28 = require("node:path");
31399
32656
  var UPDATE_PROJECT_INFO = `mutation($projectId: ID!, $shortDescription: String!, $readme: String!) {
31400
32657
  updateProjectV2(input: { projectId: $projectId, shortDescription: $shortDescription, readme: $readme }) {
31401
32658
  projectV2 { id }
@@ -31440,14 +32697,14 @@ function sharedName(entries, fallback) {
31440
32697
  }
31441
32698
  function buildProjectInfoSyncPlan(targetRepo2, project2, projects, repoRoot2) {
31442
32699
  if (!project2.projectId) throw new Error(`org project sync-info: ${targetRepo2} registry META has no projectId`);
31443
- const readmePath = (0, import_node_path27.join)(repoRoot2, "README.md");
31444
- if (!(0, import_node_fs30.existsSync)(readmePath)) throw new Error(`org project sync-info: ${targetRepo2} has no README.md`);
32700
+ const readmePath = (0, import_node_path28.join)(repoRoot2, "README.md");
32701
+ if (!(0, import_node_fs31.existsSync)(readmePath)) throw new Error(`org project sync-info: ${targetRepo2} has no README.md`);
31445
32702
  const entries = entriesFor(project2, projects);
31446
32703
  const memberRepos = [...new Set(entries.flatMap((entry) => entry.repos ?? []))].filter((repo) => /^[^/]+\/[^/]+$/.test(repo)).sort((a, b) => a.localeCompare(b));
31447
32704
  const projectName = sharedName(entries, project2.name?.trim() || targetRepo2.split("/").pop() || targetRepo2);
31448
32705
  if (!memberRepos.length) throw new Error(`org project sync-info: project ${projectName} has no registered member repos`);
31449
32706
  const entryNames = entries.map((entry) => entry.name?.trim()).filter((name) => Boolean(name));
31450
- const shortDescription = memberRepos.length === 1 ? shortDescriptionFromReadme((0, import_node_fs30.readFileSync)(readmePath, "utf8")) : `Shared work across ${new Intl.ListFormat("en", { type: "conjunction" }).format(entryNames)}.`;
32707
+ const shortDescription = memberRepos.length === 1 ? shortDescriptionFromReadme((0, import_node_fs31.readFileSync)(readmePath, "utf8")) : `Shared work across ${new Intl.ListFormat("en", { type: "conjunction" }).format(entryNames)}.`;
31451
32708
  const lines = [
31452
32709
  `# ${projectName}`,
31453
32710
  "",
@@ -31466,8 +32723,8 @@ function buildProjectInfoSyncPlan(targetRepo2, project2, projects, repoRoot2) {
31466
32723
  const targetBase = `https://github.com/${targetRepo2}`;
31467
32724
  const targetBranch = branchFor(targetRepo2, projects);
31468
32725
  const orgDocs = [
31469
- (0, import_node_fs30.existsSync)((0, import_node_path27.join)(repoRoot2, "architecture.md")) ? `- [Hub architecture](${targetBase}/blob/${targetBranch}/architecture.md)` : "",
31470
- (0, import_node_fs30.existsSync)((0, import_node_path27.join)(repoRoot2, "docs", "Architecture", "agentic-dev-environment.md")) ? `- [Agentic development environment](${targetBase}/blob/${targetBranch}/docs/Architecture/agentic-dev-environment.md)` : ""
32726
+ (0, import_node_fs31.existsSync)((0, import_node_path28.join)(repoRoot2, "architecture.md")) ? `- [Hub architecture](${targetBase}/blob/${targetBranch}/architecture.md)` : "",
32727
+ (0, import_node_fs31.existsSync)((0, import_node_path28.join)(repoRoot2, "docs", "Architecture", "agentic-dev-environment.md")) ? `- [Agentic development environment](${targetBase}/blob/${targetBranch}/docs/Architecture/agentic-dev-environment.md)` : ""
31471
32728
  ].filter(Boolean);
31472
32729
  if (orgDocs.length) lines.push("", "## Organisation docs", "", ...orgDocs);
31473
32730
  return { projectId: project2.projectId, projectName, targetRepo: targetRepo2, memberRepos, shortDescription, readme: `${lines.join("\n")}
@@ -31682,7 +32939,7 @@ async function awsJson(args) {
31682
32939
  try {
31683
32940
  return await run();
31684
32941
  } catch {
31685
- await new Promise((resolve6) => setTimeout(resolve6, AWS_RETRY_DELAY_MS));
32942
+ await new Promise((resolve7) => setTimeout(resolve7, AWS_RETRY_DELAY_MS));
31686
32943
  return run();
31687
32944
  }
31688
32945
  }
@@ -31885,8 +33142,8 @@ function registerSchedulesCommands(program3) {
31885
33142
  }
31886
33143
 
31887
33144
  // src/secrets-commands.ts
31888
- var import_node_fs31 = require("node:fs");
31889
- var import_node_path28 = require("node:path");
33145
+ var import_node_fs32 = require("node:fs");
33146
+ var import_node_path29 = require("node:path");
31890
33147
  var import_node_os16 = require("node:os");
31891
33148
 
31892
33149
  // src/secrets-diff.ts
@@ -31989,18 +33246,18 @@ function collectMap(value, previous = []) {
31989
33246
  return [...previous, value];
31990
33247
  }
31991
33248
  async function decryptRailsCredentials(input) {
31992
- const appDir = (0, import_node_path28.resolve)(input.appDir ?? process.cwd());
33249
+ const appDir = (0, import_node_path29.resolve)(input.appDir ?? process.cwd());
31993
33250
  const credentialsFile = input.credentialsFile ?? DEFAULT_RAILS_CREDENTIALS_FILE;
31994
33251
  const masterKeyFile = input.masterKeyFile ?? DEFAULT_RAILS_MASTER_KEY_FILE;
31995
- const credentialsPath = (0, import_node_path28.resolve)(appDir, credentialsFile);
31996
- const masterKeyPath = (0, import_node_path28.resolve)(appDir, masterKeyFile);
33252
+ const credentialsPath = (0, import_node_path29.resolve)(appDir, credentialsFile);
33253
+ const masterKeyPath = (0, import_node_path29.resolve)(appDir, masterKeyFile);
31997
33254
  const env = {
31998
33255
  ...process.env,
31999
33256
  MMI_RAILS_CREDENTIALS_FILE: credentialsPath,
32000
33257
  MMI_RAILS_MASTER_KEY_FILE: masterKeyPath
32001
33258
  };
32002
- if ((0, import_node_fs31.existsSync)(masterKeyPath)) {
32003
- env.RAILS_MASTER_KEY = (0, import_node_fs31.readFileSync)(masterKeyPath, "utf8").trim();
33259
+ if ((0, import_node_fs32.existsSync)(masterKeyPath)) {
33260
+ env.RAILS_MASTER_KEY = (0, import_node_fs32.readFileSync)(masterKeyPath, "utf8").trim();
32004
33261
  }
32005
33262
  const script = [
32006
33263
  'require "json"',
@@ -32010,9 +33267,9 @@ async function decryptRailsCredentials(input) {
32010
33267
  'config = ActiveSupport::EncryptedConfiguration.new(config_path: config_path, key_path: key_path, env_key: "RAILS_MASTER_KEY", raise_if_missing_key: true)',
32011
33268
  "puts JSON.generate(config.config)"
32012
33269
  ].join("\n");
32013
- const scriptDir = (0, import_node_fs31.mkdtempSync)((0, import_node_path28.join)((0, import_node_os16.tmpdir)(), "mmi-rails-decrypt-"));
32014
- const scriptPath = (0, import_node_path28.join)(scriptDir, "decrypt.rb");
32015
- (0, import_node_fs31.writeFileSync)(scriptPath, script, "utf8");
33270
+ const scriptDir = (0, import_node_fs32.mkdtempSync)((0, import_node_path29.join)((0, import_node_os16.tmpdir)(), "mmi-rails-decrypt-"));
33271
+ const scriptPath = (0, import_node_path29.join)(scriptDir, "decrypt.rb");
33272
+ (0, import_node_fs32.writeFileSync)(scriptPath, script, "utf8");
32016
33273
  try {
32017
33274
  const args = ["exec", "ruby", scriptPath];
32018
33275
  const cmd = process.platform === "win32" ? "cmd.exe" : "bundle";
@@ -32024,7 +33281,7 @@ async function decryptRailsCredentials(input) {
32024
33281
  });
32025
33282
  return JSON.parse(stdout);
32026
33283
  } finally {
32027
- (0, import_node_fs31.rmSync)(scriptDir, { recursive: true, force: true });
33284
+ (0, import_node_fs32.rmSync)(scriptDir, { recursive: true, force: true });
32028
33285
  }
32029
33286
  }
32030
33287
  async function readSecretStdin() {
@@ -32114,7 +33371,7 @@ function registerSecretsCommands(program3) {
32114
33371
  let body;
32115
33372
  if (o.file) {
32116
33373
  try {
32117
- body = (0, import_node_fs31.readFileSync)((0, import_node_path28.resolve)(o.file), "utf8");
33374
+ body = (0, import_node_fs32.readFileSync)((0, import_node_path29.resolve)(o.file), "utf8");
32118
33375
  } catch (e) {
32119
33376
  return fail(`secrets org-catalog: cannot read --file ${o.file}: ${e.message}`);
32120
33377
  }
@@ -32219,7 +33476,7 @@ function registerSecretsCommands(program3) {
32219
33476
  {
32220
33477
  ...d,
32221
33478
  decryptRailsCredentials,
32222
- removeFile: (path2) => (0, import_node_fs31.unlinkSync)((0, import_node_path28.resolve)(o.appDir ?? process.cwd(), path2))
33479
+ removeFile: (path2) => (0, import_node_fs32.unlinkSync)((0, import_node_path29.resolve)(o.appDir ?? process.cwd(), path2))
32223
33480
  },
32224
33481
  {
32225
33482
  repo: o.repo,
@@ -32380,8 +33637,8 @@ function registerSessionReport(program3) {
32380
33637
  }
32381
33638
 
32382
33639
  // src/stage-commands.ts
32383
- var import_node_fs32 = require("node:fs");
32384
- var import_node_path29 = require("node:path");
33640
+ var import_node_fs33 = require("node:fs");
33641
+ var import_node_path30 = require("node:path");
32385
33642
 
32386
33643
  // src/stage-default.ts
32387
33644
  function shellFor(platform2 = process.platform) {
@@ -32665,8 +33922,8 @@ function registerStageCommands(program3) {
32665
33922
  return {
32666
33923
  resolution: decideStage({
32667
33924
  registry: { deployModel: project2?.deployModel, portRange, error: read.ok ? void 0 : read.error },
32668
- hasCompose: (0, import_node_fs32.existsSync)((0, import_node_path29.join)(process.cwd(), "docker-compose.yml")),
32669
- hasEnvExample: (0, import_node_fs32.existsSync)((0, import_node_path29.join)(process.cwd(), ".env.example")),
33925
+ hasCompose: (0, import_node_fs33.existsSync)((0, import_node_path30.join)(process.cwd(), "docker-compose.yml")),
33926
+ hasEnvExample: (0, import_node_fs33.existsSync)((0, import_node_path30.join)(process.cwd(), ".env.example")),
32670
33927
  repo
32671
33928
  }),
32672
33929
  project: project2,
@@ -32916,20 +34173,20 @@ function registerStageCommands(program3) {
32916
34173
  }
32917
34174
 
32918
34175
  // src/tenant-artifact.ts
32919
- var import_node_crypto9 = require("node:crypto");
32920
- var import_node_fs33 = require("node:fs");
34176
+ var import_node_crypto10 = require("node:crypto");
34177
+ var import_node_fs34 = require("node:fs");
32921
34178
  var import_promises6 = require("node:fs/promises");
32922
- var import_node_path30 = require("node:path");
34179
+ var import_node_path31 = require("node:path");
32923
34180
  var ARTIFACT_ID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
32924
34181
  var MAX_BYTES = 5 * 1024 * 1024 * 1024;
32925
34182
  async function sha256File(path2) {
32926
- const hash = (0, import_node_crypto9.createHash)("sha256");
32927
- for await (const chunk of (0, import_node_fs33.createReadStream)(path2)) hash.update(chunk);
34183
+ const hash = (0, import_node_crypto10.createHash)("sha256");
34184
+ for await (const chunk of (0, import_node_fs34.createReadStream)(path2)) hash.update(chunk);
32928
34185
  return hash.digest("hex");
32929
34186
  }
32930
34187
  async function putTenantArtifact(repo, stage, inputPath, deps) {
32931
34188
  if (!["dev", "rc", "main"].includes(stage)) throw new Error("tenant artifact put: <stage> must be dev, rc, or main");
32932
- const path2 = (0, import_node_path30.resolve)(inputPath);
34189
+ const path2 = (0, import_node_path31.resolve)(inputPath);
32933
34190
  const info = await (0, import_promises6.stat)(path2);
32934
34191
  if (!info.isFile()) throw new Error("tenant artifact put: input path must be a file");
32935
34192
  if (!Number.isSafeInteger(info.size) || info.size < 1 || info.size > MAX_BYTES) throw new Error(`tenant artifact put: file must be 1..${MAX_BYTES} bytes`);
@@ -32947,7 +34204,7 @@ async function putTenantArtifact(repo, stage, inputPath, deps) {
32947
34204
  return [key, value];
32948
34205
  }));
32949
34206
  headers["content-length"] = String(info.size);
32950
- const stream = (0, import_node_fs33.createReadStream)(path2);
34207
+ const stream = (0, import_node_fs34.createReadStream)(path2);
32951
34208
  let uploaded;
32952
34209
  try {
32953
34210
  uploaded = await fetch(body.uploadUrl, {
@@ -33087,7 +34344,7 @@ function renderVerifySecrets(body) {
33087
34344
 
33088
34345
  // src/command-register-collaboration.ts
33089
34346
  var import_node_child_process14 = require("node:child_process");
33090
- var import_node_fs39 = require("node:fs");
34347
+ var import_node_fs41 = require("node:fs");
33091
34348
  var import_promises7 = require("node:fs/promises");
33092
34349
 
33093
34350
  // src/session-runtime.ts
@@ -33107,7 +34364,7 @@ function spawnDetachedSelf(args, deps, opts = {}) {
33107
34364
  }
33108
34365
 
33109
34366
  // src/command-register-collaboration.ts
33110
- var import_node_path36 = require("node:path");
34367
+ var import_node_path38 = require("node:path");
33111
34368
 
33112
34369
  // src/attach-to-project.ts
33113
34370
  function boardAttachRateLimitedReceipt(resetEpochSeconds) {
@@ -33194,7 +34451,7 @@ async function mergeAutoWithTransientRetry(prNumber, repo, deps) {
33194
34451
  if (first.mergeStatus !== "failed") return first;
33195
34452
  const ready = await deps.probeMergeReady(prNumber, repo).catch(() => ({ open: false, mergeable: false, checksPassing: false }));
33196
34453
  if (!ready.open || !ready.mergeable || !ready.checksPassing) return first;
33197
- const sleep2 = deps.sleep ?? ((ms) => new Promise((resolve6) => setTimeout(resolve6, ms)));
34454
+ const sleep2 = deps.sleep ?? ((ms) => new Promise((resolve7) => setTimeout(resolve7, ms)));
33198
34455
  await sleep2(PR_LAND_MERGE_RETRY_DELAY_MS);
33199
34456
  const retried = await deps.mergeAuto(prNumber, repo);
33200
34457
  if (retried.mergeStatus !== "failed") return retried;
@@ -33205,7 +34462,7 @@ var AUTO_MERGE_CONFIRM_DELAY_MS = 3e3;
33205
34462
  async function confirmAutoMergeEnqueued(deps, options) {
33206
34463
  const retries = options?.retries ?? AUTO_MERGE_CONFIRM_RETRIES;
33207
34464
  const delayMs = options?.delayMs ?? AUTO_MERGE_CONFIRM_DELAY_MS;
33208
- const sleep2 = deps.sleep ?? ((ms) => new Promise((resolve6) => setTimeout(resolve6, ms)));
34465
+ const sleep2 = deps.sleep ?? ((ms) => new Promise((resolve7) => setTimeout(resolve7, ms)));
33209
34466
  for (let attempt = 0; attempt < retries; attempt++) {
33210
34467
  if (await deps.readMerged().catch(() => false)) return "merged";
33211
34468
  const stuck = await deps.readAutoMergeRequest().then((s) => s.trim()).catch(() => "");
@@ -33222,7 +34479,7 @@ async function confirmAutoMergeEnqueued(deps, options) {
33222
34479
  async function readGhPrStateWithRetry(fetchState, options) {
33223
34480
  const retries = options?.retries ?? PR_LAND_STATE_READ_RETRIES;
33224
34481
  const delayMs = options?.delayMs ?? PR_LAND_STATE_READ_DELAY_MS;
33225
- const sleep2 = options?.sleep ?? ((ms) => new Promise((resolve6) => setTimeout(resolve6, ms)));
34482
+ const sleep2 = options?.sleep ?? ((ms) => new Promise((resolve7) => setTimeout(resolve7, ms)));
33226
34483
  let lastError = "empty state";
33227
34484
  for (let attempt = 0; attempt < retries; attempt++) {
33228
34485
  try {
@@ -33295,8 +34552,8 @@ async function runPrLand(prNumber, options, deps) {
33295
34552
  }
33296
34553
 
33297
34554
  // src/merge-cleanup.ts
33298
- var import_node_fs35 = require("node:fs");
33299
- var import_node_path32 = require("node:path");
34555
+ var import_node_fs36 = require("node:fs");
34556
+ var import_node_path33 = require("node:path");
33300
34557
  var import_node_os17 = require("node:os");
33301
34558
 
33302
34559
  // src/board-advance.ts
@@ -33399,8 +34656,8 @@ function boardAdvanceFailureMessage(result) {
33399
34656
 
33400
34657
  // src/test-policy-core.ts
33401
34658
  var import_node_child_process12 = require("node:child_process");
33402
- var import_node_fs34 = require("node:fs");
33403
- var import_node_path31 = require("node:path");
34659
+ var import_node_fs35 = require("node:fs");
34660
+ var import_node_path32 = require("node:path");
33404
34661
 
33405
34662
  // src/test-command-policy-shared.mjs
33406
34663
  var import_node_child_process11 = require("node:child_process");
@@ -33855,7 +35112,7 @@ function isMeaningfulRow(file) {
33855
35112
  return file.meaningful !== false;
33856
35113
  }
33857
35114
  function loadPolicy(root, readFile8 = readFileOrNull2) {
33858
- const raw = readFile8((0, import_node_path31.join)(root, POLICY_FILE));
35115
+ const raw = readFile8((0, import_node_path32.join)(root, POLICY_FILE));
33859
35116
  if (raw == null) return { mandatory: [], declared: false };
33860
35117
  try {
33861
35118
  return { ...JSON.parse(raw), declared: true };
@@ -33865,7 +35122,7 @@ function loadPolicy(root, readFile8 = readFileOrNull2) {
33865
35122
  }
33866
35123
  function readFileOrNull2(path2) {
33867
35124
  try {
33868
- return (0, import_node_fs34.readFileSync)(path2, "utf8");
35125
+ return (0, import_node_fs35.readFileSync)(path2, "utf8");
33869
35126
  } catch {
33870
35127
  return null;
33871
35128
  }
@@ -33893,12 +35150,12 @@ function classify(changed, policy, present = () => false) {
33893
35150
  const removedProtected = [...removed].filter((p) => protectedBy.has(p)).map((p) => ({ path: p, why: protectedBy.get(p) ?? "" }));
33894
35151
  return { mandatoryHits, untestedHits, testChanges, meaningfulTestChanges, addedTests, removedProtected };
33895
35152
  }
33896
- function unresolvedProtectedEntries(policy, root, exists = (path2) => (0, import_node_fs34.existsSync)(path2)) {
33897
- return (policy.protected ?? []).map((p) => p.path).filter((p) => !exists((0, import_node_path31.join)(root, p)));
35153
+ function unresolvedProtectedEntries(policy, root, exists = (path2) => (0, import_node_fs35.existsSync)(path2)) {
35154
+ return (policy.protected ?? []).map((p) => p.path).filter((p) => !exists((0, import_node_path32.join)(root, p)));
33898
35155
  }
33899
- function unresolvedSatisfiers(policy, root, exists = (path2) => (0, import_node_fs34.existsSync)(path2)) {
35156
+ function unresolvedSatisfiers(policy, root, exists = (path2) => (0, import_node_fs35.existsSync)(path2)) {
33900
35157
  const declared = (policy.mandatory ?? []).flatMap((m) => m.satisfiedBy ?? []);
33901
- return [...new Set(declared)].filter((p) => !exists((0, import_node_path31.join)(root, p)));
35158
+ return [...new Set(declared)].filter((p) => !exists((0, import_node_path32.join)(root, p)));
33902
35159
  }
33903
35160
  function evaluate(changed, policy, present = () => false) {
33904
35161
  const { mandatoryHits, untestedHits, meaningfulTestChanges, addedTests, removedProtected } = classify(changed, policy, present);
@@ -34026,17 +35283,17 @@ function blobAt(base, path2, cwd) {
34026
35283
  }
34027
35284
  function runTestPolicy(root, deps = {}) {
34028
35285
  const policy = deps.policy ?? loadPolicy(root);
34029
- const exists = deps.exists ?? ((path2) => (0, import_node_fs34.existsSync)(path2));
35286
+ const exists = deps.exists ?? ((path2) => (0, import_node_fs35.existsSync)(path2));
34030
35287
  const counts = { mandatoryCount: (policy.mandatory ?? []).length, protectedCount: (policy.protected ?? []).length };
34031
35288
  const base = deps.changed ? "(injected)" : resolveBase(root, deps.base);
34032
35289
  const refusal = deps.changed ? null : untrustworthyRange(root, base);
34033
35290
  const raw = deps.changed ?? (refusal ? [] : changedFilesSince(base, root));
34034
35291
  const changed = deps.changed ? raw : annotateChangeMeaning(raw, policy, (path2) => ({
34035
35292
  before: blobAt(base, path2, root),
34036
- after: readFileOrNull2((0, import_node_path31.join)(root, path2))
35293
+ after: readFileOrNull2((0, import_node_path32.join)(root, path2))
34037
35294
  }));
34038
35295
  const lookup = deps.override !== void 0 ? { override: deps.override, refusals: [] } : !deps.changed && !refusal ? readOverride2(base, root) : { override: null, refusals: [] };
34039
- const present = (path2) => exists((0, import_node_path31.join)(root, path2));
35296
+ const present = (path2) => exists((0, import_node_path32.join)(root, path2));
34040
35297
  const removedByThisDiff = removedPaths(changed);
34041
35298
  const staleFindings = [];
34042
35299
  const unresolved = unresolvedProtectedEntries(policy, root, exists).filter((p) => !removedByThisDiff.has(p));
@@ -34236,13 +35493,13 @@ function resolveSquashMergeBodyText(commits, allowedClosing, cwd) {
34236
35493
  return rewritten === base ? null : rewritten;
34237
35494
  }
34238
35495
  function writeSquashBodyFile(body) {
34239
- const dir = (0, import_node_fs35.mkdtempSync)((0, import_node_path32.join)((0, import_node_os17.tmpdir)(), "mmi-squash-body-"));
34240
- const path2 = (0, import_node_path32.join)(dir, "body.txt");
34241
- (0, import_node_fs35.writeFileSync)(path2, body.endsWith("\n") ? body : `${body}
35496
+ const dir = (0, import_node_fs36.mkdtempSync)((0, import_node_path33.join)((0, import_node_os17.tmpdir)(), "mmi-squash-body-"));
35497
+ const path2 = (0, import_node_path33.join)(dir, "body.txt");
35498
+ (0, import_node_fs36.writeFileSync)(path2, body.endsWith("\n") ? body : `${body}
34242
35499
  `, "utf8");
34243
35500
  return { path: path2, cleanup: () => {
34244
35501
  try {
34245
- (0, import_node_fs35.rmSync)(dir, { recursive: true, force: true });
35502
+ (0, import_node_fs36.rmSync)(dir, { recursive: true, force: true });
34246
35503
  } catch {
34247
35504
  }
34248
35505
  } };
@@ -34444,8 +35701,8 @@ async function deleteMergedRemoteBranch(options) {
34444
35701
  }
34445
35702
 
34446
35703
  // src/post-merge-recon.ts
34447
- var import_node_fs36 = require("node:fs");
34448
- var import_node_path33 = require("node:path");
35704
+ var import_node_fs37 = require("node:fs");
35705
+ var import_node_path34 = require("node:path");
34449
35706
 
34450
35707
  // src/cross-repo-filing-issue.ts
34451
35708
  function crossRepoFilingRetryCommand(prRepo, prNumber) {
@@ -34611,16 +35868,16 @@ function buildPostMergeReconRecovery(input) {
34611
35868
  }
34612
35869
  function writePostMergeReconRecovery(cwd, recovery) {
34613
35870
  const path2 = postMergeReconStatePath(cwd, recovery.repo, recovery.pr);
34614
- (0, import_node_fs36.mkdirSync)((0, import_node_path33.dirname)(path2), { recursive: true });
34615
- (0, import_node_fs36.writeFileSync)(path2, `${JSON.stringify(recovery, null, 2)}
35871
+ (0, import_node_fs37.mkdirSync)((0, import_node_path34.dirname)(path2), { recursive: true });
35872
+ (0, import_node_fs37.writeFileSync)(path2, `${JSON.stringify(recovery, null, 2)}
34616
35873
  `, "utf8");
34617
35874
  return path2;
34618
35875
  }
34619
35876
  function clearPostMergeReconRecovery(cwd, repo, pr) {
34620
35877
  const path2 = postMergeReconStatePath(cwd, repo, pr);
34621
- if (!(0, import_node_fs36.existsSync)(path2)) return;
35878
+ if (!(0, import_node_fs37.existsSync)(path2)) return;
34622
35879
  try {
34623
- (0, import_node_fs36.unlinkSync)(path2);
35880
+ (0, import_node_fs37.unlinkSync)(path2);
34624
35881
  } catch {
34625
35882
  }
34626
35883
  }
@@ -34655,7 +35912,7 @@ var import_node_child_process13 = require("node:child_process");
34655
35912
  var GIT_TIMEOUT_MS2 = 15e3;
34656
35913
  function catFileBatch(root, ref, paths) {
34657
35914
  if (paths.length === 0) return Promise.resolve([]);
34658
- return new Promise((resolve6) => {
35915
+ return new Promise((resolve7) => {
34659
35916
  const chunks = [];
34660
35917
  let settled = false;
34661
35918
  const child2 = (0, import_node_child_process13.spawn)("git", ["-C", root, "cat-file", "--batch", "--buffer"], { windowsHide: true });
@@ -34663,7 +35920,7 @@ function catFileBatch(root, ref, paths) {
34663
35920
  if (settled) return;
34664
35921
  settled = true;
34665
35922
  clearTimeout(timer);
34666
- resolve6(parseCatFileBatch(Buffer.concat(chunks), paths.length));
35923
+ resolve7(parseCatFileBatch(Buffer.concat(chunks), paths.length));
34667
35924
  };
34668
35925
  const timer = setTimeout(() => {
34669
35926
  child2.kill("SIGKILL");
@@ -34772,12 +36029,69 @@ async function checkDocsIndexAtHead(opts, deps) {
34772
36029
  }
34773
36030
 
34774
36031
  // src/worktree-merge-cleanup.ts
36032
+ var import_node_fs40 = require("node:fs");
36033
+ var import_node_path37 = require("node:path");
36034
+
36035
+ // src/jervcode-node-modules-cleanup.ts
34775
36036
  var import_node_fs38 = require("node:fs");
36037
+ var import_node_os18 = require("node:os");
34776
36038
  var import_node_path35 = require("node:path");
36039
+ var JERVCODE_PACKAGE_ENTRY = (0, import_node_path35.join)("node_modules", "@jervaise", "jervcode", "dist", "launcher-entry.js");
36040
+ var WIN_NAMES2 = ["jervcode.cmd", "jervcode"];
36041
+ var POSIX_NAMES2 = ["jervcode"];
36042
+ var NODE_MODULES_CLEANUP_TIMEOUT_MS = 3e5;
36043
+ function jervcodeCandidatePaths(env = process.env, home = (0, import_node_os18.homedir)(), platform2 = process.platform) {
36044
+ const names = platform2 === "win32" ? WIN_NAMES2 : POSIX_NAMES2;
36045
+ const out = [];
36046
+ for (const dir of jervCliCandidateDirs(env, home, platform2)) {
36047
+ for (const name of names) out.push((0, import_node_path35.join)(dir, name));
36048
+ }
36049
+ return out;
36050
+ }
36051
+ function resolveJervcodePath(env = process.env, home = (0, import_node_os18.homedir)(), platform2 = process.platform, exists = import_node_fs38.existsSync) {
36052
+ for (const candidate of jervcodeCandidatePaths(env, home, platform2)) {
36053
+ if (exists(candidate)) return candidate;
36054
+ }
36055
+ return void 0;
36056
+ }
36057
+ function jervcodeExecFileArgs(args, opts = {}) {
36058
+ const platform2 = opts.platform ?? process.platform;
36059
+ const exists = opts.exists ?? import_node_fs38.existsSync;
36060
+ const resolved = resolveJervcodePath(opts.env ?? process.env, opts.home ?? (0, import_node_os18.homedir)(), platform2, exists);
36061
+ if (resolved) {
36062
+ const entry = (0, import_node_path35.join)((0, import_node_path35.join)(resolved, ".."), JERVCODE_PACKAGE_ENTRY);
36063
+ if (exists(entry)) {
36064
+ return { file: opts.execPath ?? process.execPath, args: [entry, ...args], via: "node-entry" };
36065
+ }
36066
+ }
36067
+ const bin = resolved ?? "jervcode";
36068
+ if (platform2 === "win32") {
36069
+ return { file: "cmd.exe", args: ["/c", bin, ...args], via: resolved ? "cmd-shim" : "bare" };
36070
+ }
36071
+ return { file: bin, args: [...args], via: resolved ? "posix" : "bare" };
36072
+ }
36073
+ async function removeWorktreeNodeModulesViaHelper(wtPath, opts = {}) {
36074
+ const { cwd, timeoutMs = NODE_MODULES_CLEANUP_TIMEOUT_MS } = opts;
36075
+ const env = opts.env ?? process.env;
36076
+ const exec = opts.exec ?? execFileP;
36077
+ const candidates = jervcodeCandidatePaths(env, opts.home, opts.platform ?? process.platform);
36078
+ const plan = jervcodeExecFileArgs(["worktree-node-modules-cleanup", "--worktree", wtPath], opts);
36079
+ const execOptions = { timeout: timeoutMs, ...cwd ? { cwd } : {} };
36080
+ try {
36081
+ await exec(plan.file, plan.args, execOptions);
36082
+ return { ok: true };
36083
+ } catch (e) {
36084
+ const err = e;
36085
+ const base = formatJervCliSpawnFailure(err, plan, candidates);
36086
+ const stderr = typeof err.stderr === "string" ? err.stderr.trim() : "";
36087
+ const detail = stderr.split("\n")[0] || base;
36088
+ return { ok: false, error: `jervcode worktree-node-modules-cleanup failed: ${detail}` };
36089
+ }
36090
+ }
34777
36091
 
34778
36092
  // src/worktree-evidence-archive.ts
34779
- var import_node_fs37 = require("node:fs");
34780
- var import_node_path34 = require("node:path");
36093
+ var import_node_fs39 = require("node:fs");
36094
+ var import_node_path36 = require("node:path");
34781
36095
  var JERV_ARTIFACT_RUN_SCOPE_ENV_VARS = ["JERV_RUN_ID", ...SESSION_ID_ENV_VARS];
34782
36096
  function sanitizeArchiveSegment(value, max = 80) {
34783
36097
  const scrubbed = value.replace(/[^A-Za-z0-9._@+-]+/g, "-").replace(/^-+|-+$/g, "");
@@ -34792,24 +36106,24 @@ function resolveArtifactRunScope(env = process.env) {
34792
36106
  }
34793
36107
  function defaultIsDirectory(path2) {
34794
36108
  try {
34795
- return (0, import_node_fs37.lstatSync)(path2).isDirectory();
36109
+ return (0, import_node_fs39.lstatSync)(path2).isDirectory();
34796
36110
  } catch {
34797
36111
  return false;
34798
36112
  }
34799
36113
  }
34800
36114
  function archiveWorktreeJervArtifacts(args, deps = {}) {
34801
- const exists = deps.exists ?? import_node_fs37.existsSync;
36115
+ const exists = deps.exists ?? import_node_fs39.existsSync;
34802
36116
  const isDirectory = deps.isDirectory ?? defaultIsDirectory;
34803
- const copyDir = deps.copyDir ?? ((from, to) => (0, import_node_fs37.cpSync)(from, to, { recursive: true, force: true }));
36117
+ const copyDir = deps.copyDir ?? ((from, to) => (0, import_node_fs39.cpSync)(from, to, { recursive: true, force: true }));
34804
36118
  const mkdirp = deps.mkdirp ?? ((path2) => {
34805
- (0, import_node_fs37.mkdirSync)(path2, { recursive: true });
36119
+ (0, import_node_fs39.mkdirSync)(path2, { recursive: true });
34806
36120
  });
34807
36121
  const env = deps.env ?? process.env;
34808
36122
  const now = deps.now ?? (() => /* @__PURE__ */ new Date());
34809
36123
  const resolveRoot = deps.resolveArchiveRoot ?? repoRuntimeStatePath;
34810
36124
  if (!args.primaryRoot?.trim()) return { status: "skipped", reason: "missing-primary-root" };
34811
36125
  if (!args.worktreePath?.trim()) return { status: "skipped", reason: "missing-worktree-path" };
34812
- const source = (0, import_node_path34.join)(args.worktreePath, ".jerv");
36126
+ const source = (0, import_node_path36.join)(args.worktreePath, ".jerv");
34813
36127
  if (!exists(source)) return { status: "absent" };
34814
36128
  if (!isDirectory(source)) return { status: "skipped", reason: "jerv-not-a-directory" };
34815
36129
  const runScope = resolveArtifactRunScope(env);
@@ -34817,7 +36131,7 @@ function archiveWorktreeJervArtifacts(args, deps = {}) {
34817
36131
  const stamp = now().toISOString().replace(/[:.]/g, "-");
34818
36132
  const dest = resolveRoot(args.primaryRoot, "jerv-artifacts", runScope, branchSlug, stamp, ".jerv");
34819
36133
  try {
34820
- mkdirp((0, import_node_path34.dirname)(dest));
36134
+ mkdirp((0, import_node_path36.dirname)(dest));
34821
36135
  copyDir(source, dest);
34822
36136
  if (!exists(dest)) return { status: "failed", error: `archive write left no directory at ${dest}` };
34823
36137
  return { status: "archived", path: dest, runScope };
@@ -34826,14 +36140,14 @@ function archiveWorktreeJervArtifacts(args, deps = {}) {
34826
36140
  }
34827
36141
  }
34828
36142
  function defaultStat(path2) {
34829
- const st = (0, import_node_fs37.statSync)(path2);
36143
+ const st = (0, import_node_fs39.statSync)(path2);
34830
36144
  return { mtimeMs: st.mtimeMs, size: st.size, isDirectory: () => st.isDirectory() };
34831
36145
  }
34832
36146
  function scanWorktreeTmpEvidence(worktreePath, newerThanMs, deps = {}) {
34833
- const exists = deps.exists ?? import_node_fs37.existsSync;
36147
+ const exists = deps.exists ?? import_node_fs39.existsSync;
34834
36148
  const stat4 = deps.stat ?? defaultStat;
34835
- const readdir2 = deps.readdir ?? import_node_fs37.readdirSync;
34836
- const tmpRoot = (0, import_node_path34.join)(worktreePath, "tmp");
36149
+ const readdir2 = deps.readdir ?? import_node_fs39.readdirSync;
36150
+ const tmpRoot = (0, import_node_path36.join)(worktreePath, "tmp");
34837
36151
  if (!exists(tmpRoot)) return [];
34838
36152
  const entries = [];
34839
36153
  const walk2 = (dir) => {
@@ -34844,14 +36158,14 @@ function scanWorktreeTmpEvidence(worktreePath, newerThanMs, deps = {}) {
34844
36158
  return;
34845
36159
  }
34846
36160
  for (const name of names) {
34847
- const full = (0, import_node_path34.join)(dir, name);
36161
+ const full = (0, import_node_path36.join)(dir, name);
34848
36162
  let st;
34849
36163
  try {
34850
36164
  st = stat4(full);
34851
36165
  } catch {
34852
36166
  continue;
34853
36167
  }
34854
- const relPath = (0, import_node_path34.relative)(worktreePath, full).replace(/\\/g, "/");
36168
+ const relPath = (0, import_node_path36.relative)(worktreePath, full).replace(/\\/g, "/");
34855
36169
  if (st.isDirectory()) {
34856
36170
  if (st.mtimeMs > newerThanMs) entries.push({ relPath, bytes: 0, mtimeMs: st.mtimeMs });
34857
36171
  walk2(full);
@@ -34865,10 +36179,10 @@ function scanWorktreeTmpEvidence(worktreePath, newerThanMs, deps = {}) {
34865
36179
  return entries;
34866
36180
  }
34867
36181
  function archiveWorktreeTmpArtifacts(args, deps = {}) {
34868
- const exists = deps.exists ?? import_node_fs37.existsSync;
34869
- const copyDir = deps.copyDir ?? ((from, to) => (0, import_node_fs37.cpSync)(from, to, { recursive: true, force: true }));
36182
+ const exists = deps.exists ?? import_node_fs39.existsSync;
36183
+ const copyDir = deps.copyDir ?? ((from, to) => (0, import_node_fs39.cpSync)(from, to, { recursive: true, force: true }));
34870
36184
  const mkdirp = deps.mkdirp ?? ((path2) => {
34871
- (0, import_node_fs37.mkdirSync)(path2, { recursive: true });
36185
+ (0, import_node_fs39.mkdirSync)(path2, { recursive: true });
34872
36186
  });
34873
36187
  const env = deps.env ?? process.env;
34874
36188
  const now = deps.now ?? (() => /* @__PURE__ */ new Date());
@@ -34876,7 +36190,7 @@ function archiveWorktreeTmpArtifacts(args, deps = {}) {
34876
36190
  if (!args.primaryRoot?.trim()) return { status: "skipped", reason: "missing-primary-root" };
34877
36191
  if (!args.worktreePath?.trim()) return { status: "skipped", reason: "missing-worktree-path" };
34878
36192
  const scanned = scanWorktreeTmpEvidence(args.worktreePath, args.newerThanMs, deps);
34879
- const source = (0, import_node_path34.join)(args.worktreePath, "tmp");
36193
+ const source = (0, import_node_path36.join)(args.worktreePath, "tmp");
34880
36194
  if (!scanned.length) return { status: "absent" };
34881
36195
  if (!exists(source)) return { status: "absent" };
34882
36196
  const runScope = resolveArtifactRunScope(env);
@@ -34884,7 +36198,7 @@ function archiveWorktreeTmpArtifacts(args, deps = {}) {
34884
36198
  const stamp = now().toISOString().replace(/[:.]/g, "-");
34885
36199
  const dest = resolveRoot(args.primaryRoot, "worktree-artifacts", runScope, branchSlug, stamp, "tmp");
34886
36200
  try {
34887
- mkdirp((0, import_node_path34.dirname)(dest));
36201
+ mkdirp((0, import_node_path36.dirname)(dest));
34888
36202
  copyDir(source, dest);
34889
36203
  if (!exists(dest)) return { status: "failed", error: `archive write left no directory at ${dest}` };
34890
36204
  const bytes = scanned.reduce((sum, e) => sum + e.bytes, 0);
@@ -34953,9 +36267,9 @@ function normPath2(p) {
34953
36267
  return p.replace(/\\/g, "/").replace(/\/+$/, "");
34954
36268
  }
34955
36269
  function unlinkNodeModulesJunction(wtPath) {
34956
- const nm = (0, import_node_path35.join)(wtPath, "node_modules");
36270
+ const nm = (0, import_node_path37.join)(wtPath, "node_modules");
34957
36271
  try {
34958
- if ((0, import_node_fs38.lstatSync)(nm).isSymbolicLink()) (0, import_node_fs38.rmdirSync)(nm);
36272
+ if ((0, import_node_fs40.lstatSync)(nm).isSymbolicLink()) (0, import_node_fs40.rmdirSync)(nm);
34959
36273
  return { ok: true };
34960
36274
  } catch (e) {
34961
36275
  if (e.code === "ENOENT") return { ok: true };
@@ -34963,11 +36277,11 @@ function unlinkNodeModulesJunction(wtPath) {
34963
36277
  }
34964
36278
  }
34965
36279
  function defaultSleep2(ms) {
34966
- return new Promise((resolve6) => setTimeout(resolve6, ms));
36280
+ return new Promise((resolve7) => setTimeout(resolve7, ms));
34967
36281
  }
34968
36282
  async function removeResidueDirectory(wtPath, options = {}) {
34969
- const { sleep: sleep2 = defaultSleep2, removeEmptyDir = import_node_fs38.rmdirSync, exists = import_node_fs38.existsSync } = options;
34970
- const removeRecursive = options.removeRecursive ?? ((path2) => (0, import_node_fs38.rmSync)(path2, { recursive: true, force: true, maxRetries: 10, retryDelay: 100 }));
36283
+ const { sleep: sleep2 = defaultSleep2, removeEmptyDir = import_node_fs40.rmdirSync, exists = import_node_fs40.existsSync } = options;
36284
+ const removeRecursive = options.removeRecursive ?? ((path2) => (0, import_node_fs40.rmSync)(path2, { recursive: true, force: true, maxRetries: 10, retryDelay: 100 }));
34971
36285
  const junction = unlinkNodeModulesJunction(wtPath);
34972
36286
  if (!junction.ok) return junction;
34973
36287
  try {
@@ -35181,7 +36495,7 @@ async function cleanupPrMergeLocalBranch(branch, options) {
35181
36495
  return report;
35182
36496
  }
35183
36497
  const execGit = options.execGit ?? (async (args) => (await execFileP("git", args, { timeout: GIT_TIMEOUT_MS })).stdout);
35184
- const pathExists = options.pathExists ?? import_node_fs38.existsSync;
36498
+ const pathExists = options.pathExists ?? import_node_fs40.existsSync;
35185
36499
  let afterWorktrees = [];
35186
36500
  try {
35187
36501
  afterWorktrees = parseGitWorktreePorcelain(await execGit(["worktree", "list", "--porcelain"]));
@@ -35273,6 +36587,23 @@ async function cleanupPrMergeLocalBranch(branch, options) {
35273
36587
  report.localBranch = { name: branch, status: "not-attempted", reason: "archive-failed" };
35274
36588
  return report;
35275
36589
  }
36590
+ if (pathExists((0, import_node_path37.join)(wtPath, "node_modules"))) {
36591
+ const nmRemoved = await (options.removeRealNodeModules ?? ((p) => removeWorktreeNodeModulesViaHelper(p, { cwd: mainWorktreePath })))(wtPath);
36592
+ if (!nmRemoved.ok) {
36593
+ report.worktree = {
36594
+ path: wtPath,
36595
+ status: "failed",
36596
+ reason: "pre-clean-failed",
36597
+ error: `real-node_modules cleanup failed before worktree removal: ${nmRemoved.error}`,
36598
+ artifactsArchive,
36599
+ stageTeardown,
36600
+ tmpEvidenceCount: tmpEvidence.length,
36601
+ remediation: `jervcode worktree-node-modules-cleanup --worktree '${wtPath.replace(/'/g, "''")}'`
36602
+ };
36603
+ report.localBranch = { name: branch, status: "not-attempted", reason: "worktree-pre-clean-failed" };
36604
+ return report;
36605
+ }
36606
+ }
35276
36607
  const preClean = await preCleanWorktreeForRemoval(wtPath, execGit);
35277
36608
  if (!preClean.ok) {
35278
36609
  report.worktree = {
@@ -35399,10 +36730,10 @@ function argvWantsJson2() {
35399
36730
  return process.argv.some((a) => a === "--json" || a.startsWith("--json="));
35400
36731
  }
35401
36732
  function hubRoot() {
35402
- const fromPkg = (0, import_node_path36.join)(__dirname, "..", "..");
36733
+ const fromPkg = (0, import_node_path38.join)(__dirname, "..", "..");
35403
36734
  const marker = "skills/bootstrap/seeds/manifest.json";
35404
- if ((0, import_node_fs39.existsSync)((0, import_node_path36.join)(fromPkg, marker))) return fromPkg;
35405
- if ((0, import_node_fs39.existsSync)((0, import_node_path36.join)(process.cwd(), marker))) return process.cwd();
36735
+ if ((0, import_node_fs41.existsSync)((0, import_node_path38.join)(fromPkg, marker))) return fromPkg;
36736
+ if ((0, import_node_fs41.existsSync)((0, import_node_path38.join)(process.cwd(), marker))) return process.cwd();
35406
36737
  return null;
35407
36738
  }
35408
36739
  function ciAuditDeps() {
@@ -35414,8 +36745,8 @@ function ciAuditDeps() {
35414
36745
  getProjectMeta: async (slug) => fetchProjectBySlug(slug, registryClientDeps(await cfgPromise)),
35415
36746
  readSeedFile: (path2) => {
35416
36747
  if (!root) return null;
35417
- const fullPath = (0, import_node_path36.join)(root, path2);
35418
- return (0, import_node_fs39.existsSync)(fullPath) ? (0, import_node_fs39.readFileSync)(fullPath, "utf8") : null;
36748
+ const fullPath = (0, import_node_path38.join)(root, path2);
36749
+ return (0, import_node_fs41.existsSync)(fullPath) ? (0, import_node_fs41.readFileSync)(fullPath, "utf8") : null;
35419
36750
  }
35420
36751
  };
35421
36752
  }
@@ -35631,7 +36962,7 @@ function registerCollaborationCommands(program3) {
35631
36962
  repo: targetRepo2,
35632
36963
  labels: extraLabels.length ? extraLabels : void 0
35633
36964
  });
35634
- if (o.parent !== void 0) parseIssueRef(o.parent, o.repo);
36965
+ if (o.parent !== void 0) parseIssueRef(o.parent);
35635
36966
  } catch (e) {
35636
36967
  return fail(`issue create: ${e.message}`, e instanceof TextArgError ? { code: e.code, offending_flag: e.offendingFlag } : void 0);
35637
36968
  }
@@ -35788,14 +37119,14 @@ function registerCollaborationCommands(program3) {
35788
37119
  jsonParity(issue.command("link-child <parent> <child>").description("link an existing issue as a native sub-issue of a parent and print {parentNumber,subIssueNumber,totalCount} JSON").option("--repo <owner/repo>", "repo for bare refs on either side (defaults to the current repo)")).action(async (parentRef, childRef, o) => {
35789
37120
  const defaultRepo = await resolveRepo(o.repo);
35790
37121
  try {
35791
- parseIssueRef(parentRef, o.repo);
35792
- parseIssueRef(childRef, o.repo);
37122
+ parseIssueRef(parentRef);
37123
+ parseIssueRef(childRef);
35793
37124
  const result = await linkSubIssue(ghRunner2, parentRef, childRef, defaultRepo);
35794
37125
  console.log(JSON.stringify(result));
35795
37126
  } catch (e) {
35796
37127
  let conflict;
35797
37128
  try {
35798
- const child2 = parseIssueRef(childRef, o.repo);
37129
+ const child2 = parseIssueRef(childRef);
35799
37130
  const childRepo = child2.repo ?? defaultRepo;
35800
37131
  if (childRepo) conflict = await classifyReparentFailure(e, ghRunner2, childRepo, child2.number, parentRef);
35801
37132
  } catch {
@@ -36046,11 +37377,11 @@ ${list}`);
36046
37377
  }
36047
37378
  });
36048
37379
  async function listCiWorkflowPaths(cwd = process.cwd()) {
36049
- const wfDir = (0, import_node_path36.join)(cwd, ".github", "workflows");
36050
- if (!(0, import_node_fs39.existsSync)(wfDir)) return [];
36051
- return (0, import_node_fs39.readdirSync)(wfDir).filter((name) => /\.(ya?ml)$/i.test(name)).filter((name) => {
37380
+ const wfDir = (0, import_node_path38.join)(cwd, ".github", "workflows");
37381
+ if (!(0, import_node_fs41.existsSync)(wfDir)) return [];
37382
+ return (0, import_node_fs41.readdirSync)(wfDir).filter((name) => /\.(ya?ml)$/i.test(name)).filter((name) => {
36052
37383
  try {
36053
- return workflowReportsPrChecks((0, import_node_fs39.readFileSync)((0, import_node_path36.join)(wfDir, name), "utf8"));
37384
+ return workflowReportsPrChecks((0, import_node_fs41.readFileSync)((0, import_node_path38.join)(wfDir, name), "utf8"));
36054
37385
  } catch {
36055
37386
  return true;
36056
37387
  }
@@ -36208,7 +37539,7 @@ ${list}`);
36208
37539
  // #5400: after grace, name "GitHub delivered zero runs" instead of burning the full budget as pending.
36209
37540
  pollHeadWorkflowRunCount: () => waitLoopHeadWorkflowRunCount("pr checks-wait", number, repo),
36210
37541
  baseBranch,
36211
- sleep: (ms) => new Promise((resolve6) => setTimeout(resolve6, ms)),
37542
+ sleep: (ms) => new Promise((resolve7) => setTimeout(resolve7, ms)),
36212
37543
  log: (message) => console.warn(message),
36213
37544
  timeoutMs: Math.max(1, budgetMs - (Date.now() - waitStarted)),
36214
37545
  // Liveness on stderr, one line per poll. A silent bounded wait is indistinguishable from a hang, and
@@ -36312,7 +37643,7 @@ ${list}`);
36312
37643
  // #5400: same zero-runs delivery probe as checks-wait — do not burn the land budget on silence.
36313
37644
  pollHeadWorkflowRunCount: () => waitLoopHeadWorkflowRunCount("pr land", prNumber, repo),
36314
37645
  baseBranch: "development",
36315
- sleep: (ms) => new Promise((resolve6) => setTimeout(resolve6, ms)),
37646
+ sleep: (ms) => new Promise((resolve7) => setTimeout(resolve7, ms)),
36316
37647
  log: (message) => console.warn(message),
36317
37648
  // `pr land` inherits the same (raised) checks budget, so it needs the same liveness — otherwise the
36318
37649
  // 30m wait is SILENT and reads exactly like the hang #2940 was filed about, only three times longer.
@@ -36358,7 +37689,7 @@ ${list}`);
36358
37689
  } else {
36359
37690
  lastFailure = void 0;
36360
37691
  }
36361
- await new Promise((resolve6) => setTimeout(resolve6, PR_LAND_POLL_MS));
37692
+ await new Promise((resolve7) => setTimeout(resolve7, PR_LAND_POLL_MS));
36362
37693
  }
36363
37694
  if (lastFailure) {
36364
37695
  throw new Error(
@@ -36445,7 +37776,7 @@ ${list}`);
36445
37776
  const mergeSquashBody = squashBodyTextForMerge(
36446
37777
  closingGuardInput,
36447
37778
  method === "--squash",
36448
- o.squashBodyFile ? (0, import_node_fs39.readFileSync)(o.squashBodyFile, "utf8") : void 0
37779
+ o.squashBodyFile ? (0, import_node_fs41.readFileSync)(o.squashBodyFile, "utf8") : void 0
36449
37780
  );
36450
37781
  if (!o.squashBodyFile) warnSquashBodyClosingStrip("pr merge", closingGuardInput, mergeSquashBody);
36451
37782
  const closingGuardVerdict = evaluateClosingGuard(closingGuardInput, {
@@ -36495,7 +37826,7 @@ ${list}`);
36495
37826
  checksDiagnosticCommand: `gh pr checks ${number} --repo ${repo}`,
36496
37827
  pollHeadWorkflowRunCount: () => waitLoopHeadWorkflowRunCount("pr merge --wait", number, repo),
36497
37828
  baseBranch,
36498
- sleep: (ms) => new Promise((resolve6) => setTimeout(resolve6, ms)),
37829
+ sleep: (ms) => new Promise((resolve7) => setTimeout(resolve7, ms)),
36499
37830
  log: (message) => console.warn(message),
36500
37831
  timeoutMs: Math.max(1, budgetMs - (Date.now() - waitStarted)),
36501
37832
  progress: ({ state, elapsedMs, remainingMs }) => console.warn(`pr merge: --wait checks \u2014 ${state}, ${Math.round(elapsedMs / 1e3)}s elapsed, ${Math.round(remainingMs / 6e4)}m left`)
@@ -36549,7 +37880,7 @@ ${list}`);
36549
37880
  }
36550
37881
  if (!repoForPostCleanup) throw e;
36551
37882
  console.warn(`pr merge: gh GraphQL rate-limited \u2014 merging PR #${number} via REST PUT instead (#4588).`);
36552
- const commitMessage = bodyFile ? (0, import_node_fs39.readFileSync)(bodyFile, "utf8") : void 0;
37883
+ const commitMessage = bodyFile ? (0, import_node_fs41.readFileSync)(bodyFile, "utf8") : void 0;
36553
37884
  await defaultGitHubClient().rest("PUT", `repos/${repoForPostCleanup}/pulls/${number}/merge`, {
36554
37885
  body: { merge_method: method.slice(2), ...commitMessage ? { commit_message: commitMessage } : {} },
36555
37886
  timeoutMs: GH_MUTATION_TIMEOUT_MS
@@ -36637,7 +37968,7 @@ ${list}`);
36637
37968
  preserveWorktree: o.preserveWorktree,
36638
37969
  gcAcknowledged: o.gc,
36639
37970
  expectedHeadOid: headRefOid,
36640
- pathExists: (p) => (0, import_node_fs39.existsSync)(p),
37971
+ pathExists: (p) => (0, import_node_fs41.existsSync)(p),
36641
37972
  // #5899: pin cleanup git calls to the main checkout — the task worktree this process may be
36642
37973
  // standing in is removed mid-cleanup, so a cwd-relative invocation fails with
36643
37974
  // 'fatal: not a git repository' and leaves a spurious partial-cleanup exit.
@@ -36752,7 +38083,7 @@ ${list}`);
36752
38083
  }
36753
38084
 
36754
38085
  // src/command-register-developer.ts
36755
- var import_node_fs44 = require("node:fs");
38086
+ var import_node_fs46 = require("node:fs");
36756
38087
 
36757
38088
  // src/whoami.ts
36758
38089
  async function resolveWhoami(deps) {
@@ -36782,7 +38113,7 @@ async function resolveWhoami(deps) {
36782
38113
  }
36783
38114
 
36784
38115
  // src/command-register-developer.ts
36785
- var import_node_path41 = require("node:path");
38116
+ var import_node_path43 = require("node:path");
36786
38117
 
36787
38118
  // src/wave-land.ts
36788
38119
  function planWaveLand(prs) {
@@ -36813,7 +38144,7 @@ async function executeWaveLand(plan, deps) {
36813
38144
  }
36814
38145
 
36815
38146
  // src/box-commands.ts
36816
- var import_node_fs40 = require("node:fs");
38147
+ var import_node_fs42 = require("node:fs");
36817
38148
 
36818
38149
  // src/box.ts
36819
38150
  var BOX_KEYS = {
@@ -37016,7 +38347,7 @@ function registerBoxCommands(program3) {
37016
38347
  }
37017
38348
  if (o.json) console.log(JSON.stringify({ box: found, incomplete }, null, 2));
37018
38349
  else if (o.ssh && o.script) {
37019
- (0, import_node_fs40.writeFileSync)(o.script, sshRecipeScript(found), "utf8");
38350
+ (0, import_node_fs42.writeFileSync)(o.script, sshRecipeScript(found), "utf8");
37020
38351
  console.log(`wrote ${o.script} \u2014 run: bash "${o.script}"`);
37021
38352
  } else if (o.ssh) console.log(`${formatSshRecipe(found)}
37022
38353
  ${SSH_RECIPE_AGENT_NOTE}`);
@@ -37031,28 +38362,28 @@ ${SSH_RECIPE_AGENT_NOTE}`);
37031
38362
 
37032
38363
  // src/dist-drift.ts
37033
38364
  var import_node_child_process15 = require("node:child_process");
37034
- var import_node_crypto11 = require("node:crypto");
37035
- var import_node_fs42 = require("node:fs");
37036
- var import_node_os18 = require("node:os");
37037
- var import_node_path38 = require("node:path");
38365
+ var import_node_crypto12 = require("node:crypto");
38366
+ var import_node_fs44 = require("node:fs");
38367
+ var import_node_os19 = require("node:os");
38368
+ var import_node_path40 = require("node:path");
37038
38369
 
37039
38370
  // ../scripts/distribution-digest.mjs
37040
- var import_node_crypto10 = require("node:crypto");
37041
- var import_node_fs41 = require("node:fs");
37042
- var import_node_path37 = require("node:path");
38371
+ var import_node_crypto11 = require("node:crypto");
38372
+ var import_node_fs43 = require("node:fs");
38373
+ var import_node_path39 = require("node:path");
37043
38374
  var slash = (value) => value.replaceAll("\\", "/");
37044
38375
  function repoPath(root, declaredPath, label) {
37045
- const absoluteRoot = (0, import_node_path37.resolve)(root);
37046
- const target = (0, import_node_path37.resolve)(root, declaredPath);
37047
- if (target !== absoluteRoot && !target.startsWith(`${absoluteRoot}${import_node_path37.sep}`)) {
38376
+ const absoluteRoot = (0, import_node_path39.resolve)(root);
38377
+ const target = (0, import_node_path39.resolve)(root, declaredPath);
38378
+ if (target !== absoluteRoot && !target.startsWith(`${absoluteRoot}${import_node_path39.sep}`)) {
37048
38379
  throw new Error(`${label} ${declaredPath} escapes the repository root`);
37049
38380
  }
37050
38381
  return target;
37051
38382
  }
37052
38383
  function digestFiles(files) {
37053
- const hash = (0, import_node_crypto10.createHash)("sha256");
38384
+ const hash = (0, import_node_crypto11.createHash)("sha256");
37054
38385
  for (const file of [...files].sort((a, b) => a.relative.localeCompare(b.relative))) {
37055
- const content = file.stat.isSymbolicLink() ? Buffer.from((0, import_node_fs41.readlinkSync)(file.absolute), "utf8") : (0, import_node_fs41.readFileSync)(file.absolute);
38386
+ const content = file.stat.isSymbolicLink() ? Buffer.from((0, import_node_fs43.readlinkSync)(file.absolute), "utf8") : (0, import_node_fs43.readFileSync)(file.absolute);
37056
38387
  hash.update(file.relative, "utf8");
37057
38388
  hash.update("\0");
37058
38389
  hash.update(file.stat.isSymbolicLink() ? "symlink" : "file", "utf8");
@@ -37067,8 +38398,8 @@ function digestFiles(files) {
37067
38398
  function digestPackedFiles(packageRoot, packedFiles) {
37068
38399
  return digestFiles(packedFiles.map((path2) => {
37069
38400
  const absolute = repoPath(packageRoot, path2, "packed artifact identity path");
37070
- if (!(0, import_node_fs41.existsSync)(absolute)) throw new Error(`packed artifact identity path ${path2} does not exist`);
37071
- return { absolute, relative: slash(path2), stat: (0, import_node_fs41.lstatSync)(absolute) };
38401
+ if (!(0, import_node_fs43.existsSync)(absolute)) throw new Error(`packed artifact identity path ${path2} does not exist`);
38402
+ return { absolute, relative: slash(path2), stat: (0, import_node_fs43.lstatSync)(absolute) };
37072
38403
  }));
37073
38404
  }
37074
38405
 
@@ -37080,7 +38411,7 @@ var DIST_ARTIFACTS = [
37080
38411
  ];
37081
38412
  var BOM_DIST_TREE_ID = "mmi-cli-dist";
37082
38413
  var ABSENT = "absent";
37083
- var sha256 = (bytes) => `sha256:${(0, import_node_crypto11.createHash)("sha256").update(bytes).digest("hex")}`;
38414
+ var sha256 = (bytes) => `sha256:${(0, import_node_crypto12.createHash)("sha256").update(bytes).digest("hex")}`;
37084
38415
  function artifactDrift(path2, committedBytes, rebuiltBytes) {
37085
38416
  const committed = committedBytes ? sha256(committedBytes) : ABSENT;
37086
38417
  const rebuiltExpected = rebuiltBytes ? sha256(rebuiltBytes) : ABSENT;
@@ -37152,13 +38483,13 @@ function renderDistDriftReceipt(receipt) {
37152
38483
  return lines;
37153
38484
  }
37154
38485
  function readOrNull(path2) {
37155
- return (0, import_node_fs42.existsSync)(path2) ? (0, import_node_fs42.readFileSync)(path2) : null;
38486
+ return (0, import_node_fs44.existsSync)(path2) ? (0, import_node_fs44.readFileSync)(path2) : null;
37156
38487
  }
37157
38488
  function walkFiles(root) {
37158
38489
  const files = [];
37159
38490
  const walk2 = (directory) => {
37160
- for (const entry of (0, import_node_fs42.readdirSync)(directory, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
37161
- const child2 = (0, import_node_path38.join)(directory, entry.name);
38491
+ for (const entry of (0, import_node_fs44.readdirSync)(directory, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
38492
+ const child2 = (0, import_node_path40.join)(directory, entry.name);
37162
38493
  if (entry.isDirectory()) walk2(child2);
37163
38494
  else files.push(child2);
37164
38495
  }
@@ -37168,10 +38499,10 @@ function walkFiles(root) {
37168
38499
  }
37169
38500
  function bomPathFor(root) {
37170
38501
  try {
37171
- const registry2 = JSON.parse((0, import_node_fs42.readFileSync)((0, import_node_path38.join)(root, "surfaces.json"), "utf8"));
37172
- return (0, import_node_path38.join)(root, registry2?.sharedAgentCore?.releaseMetadata?.bomPath ?? "distribution-bom.json");
38502
+ const registry2 = JSON.parse((0, import_node_fs44.readFileSync)((0, import_node_path40.join)(root, "surfaces.json"), "utf8"));
38503
+ return (0, import_node_path40.join)(root, registry2?.sharedAgentCore?.releaseMetadata?.bomPath ?? "distribution-bom.json");
37173
38504
  } catch {
37174
- return (0, import_node_path38.join)(root, "distribution-bom.json");
38505
+ return (0, import_node_path40.join)(root, "distribution-bom.json");
37175
38506
  }
37176
38507
  }
37177
38508
  function rebuildTo(packageRoot, outDir) {
@@ -37184,35 +38515,35 @@ function rebuildTo(packageRoot, outDir) {
37184
38515
  });
37185
38516
  }
37186
38517
  function runDistStatus(root) {
37187
- const stage = (0, import_node_fs42.mkdtempSync)((0, import_node_path38.join)((0, import_node_os18.tmpdir)(), "mmi-dist-drift-"));
38518
+ const stage = (0, import_node_fs44.mkdtempSync)((0, import_node_path40.join)((0, import_node_os19.tmpdir)(), "mmi-dist-drift-"));
37188
38519
  let overlayCount = 0;
37189
38520
  try {
37190
- const cliOut = (0, import_node_path38.join)(stage, "cli-dist");
37191
- const hubOut = (0, import_node_path38.join)(stage, "hub-dist");
37192
- rebuildTo((0, import_node_path38.join)(root, "cli"), cliOut);
37193
- rebuildTo((0, import_node_path38.join)(root, "updater"), hubOut);
38521
+ const cliOut = (0, import_node_path40.join)(stage, "cli-dist");
38522
+ const hubOut = (0, import_node_path40.join)(stage, "hub-dist");
38523
+ rebuildTo((0, import_node_path40.join)(root, "cli"), cliOut);
38524
+ rebuildTo((0, import_node_path40.join)(root, "updater"), hubOut);
37194
38525
  const outDirFor = (packageDir) => packageDir === "cli" ? cliOut : hubOut;
37195
38526
  const rebuilt = (path2) => {
37196
38527
  const spec = DIST_ARTIFACTS.find((entry) => entry.path === path2);
37197
- return spec ? readOrNull((0, import_node_path38.join)(outDirFor(spec.packageDir), spec.output)) : null;
38528
+ return spec ? readOrNull((0, import_node_path40.join)(outDirFor(spec.packageDir), spec.output)) : null;
37198
38529
  };
37199
- const committed = (path2) => readOrNull((0, import_node_path38.join)(root, path2));
37200
- const tree = (path2) => readOrNull((0, import_node_path38.join)(root, path2));
37201
- const distRoot = (0, import_node_path38.join)(root, "cli", "dist");
37202
- const distTree = () => walkFiles(distRoot).map((absolute) => `cli/dist/${(0, import_node_path38.relative)(distRoot, absolute).replaceAll("\\", "/")}`);
37203
- const bom = JSON.parse((0, import_node_fs42.readFileSync)(bomPathFor(root), "utf8"));
38530
+ const committed = (path2) => readOrNull((0, import_node_path40.join)(root, path2));
38531
+ const tree = (path2) => readOrNull((0, import_node_path40.join)(root, path2));
38532
+ const distRoot = (0, import_node_path40.join)(root, "cli", "dist");
38533
+ const distTree = () => walkFiles(distRoot).map((absolute) => `cli/dist/${(0, import_node_path40.relative)(distRoot, absolute).replaceAll("\\", "/")}`);
38534
+ const bom = JSON.parse((0, import_node_fs44.readFileSync)(bomPathFor(root), "utf8"));
37204
38535
  const digest = (entries) => {
37205
- const overlay = (0, import_node_path38.join)(stage, `overlay-${overlayCount++}`);
38536
+ const overlay = (0, import_node_path40.join)(stage, `overlay-${overlayCount++}`);
37206
38537
  for (const entry of entries) {
37207
- const target = (0, import_node_path38.join)(overlay, entry.path);
37208
- (0, import_node_fs42.mkdirSync)((0, import_node_path38.dirname)(target), { recursive: true });
37209
- (0, import_node_fs42.writeFileSync)(target, entry.bytes);
38538
+ const target = (0, import_node_path40.join)(overlay, entry.path);
38539
+ (0, import_node_fs44.mkdirSync)((0, import_node_path40.dirname)(target), { recursive: true });
38540
+ (0, import_node_fs44.writeFileSync)(target, entry.bytes);
37210
38541
  }
37211
38542
  return digestPackedFiles(overlay, entries.map((entry) => entry.path));
37212
38543
  };
37213
38544
  return computeDistDriftReceipt({ committed, tree, rebuilt, distTree, bom, digest });
37214
38545
  } finally {
37215
- (0, import_node_fs42.rmSync)(stage, { recursive: true, force: true });
38546
+ (0, import_node_fs44.rmSync)(stage, { recursive: true, force: true });
37216
38547
  }
37217
38548
  }
37218
38549
 
@@ -37284,7 +38615,7 @@ function registerEdgeCommands(program3) {
37284
38615
 
37285
38616
  // src/schedules-lift-command.ts
37286
38617
  var import_promises8 = require("node:fs/promises");
37287
- var import_node_path39 = require("node:path");
38618
+ var import_node_path41 = require("node:path");
37288
38619
  var DEFAULT_WORKFLOWS_DIR = ".github/workflows";
37289
38620
  var SCHEDULE_REPO_RE = /^[A-Za-z0-9_.-]+$/;
37290
38621
  var SchedulesLiftUsageError = class extends Error {
@@ -37311,7 +38642,7 @@ async function readWorkflowFiles(dir) {
37311
38642
  const files = [];
37312
38643
  for (const name of names.sort()) {
37313
38644
  if (!/\.ya?ml$/.test(name)) continue;
37314
- files.push({ path: `.github/workflows/${name}`, text: await (0, import_promises8.readFile)((0, import_node_path39.join)(dir, name), "utf8") });
38645
+ files.push({ path: `.github/workflows/${name}`, text: await (0, import_promises8.readFile)((0, import_node_path41.join)(dir, name), "utf8") });
37315
38646
  }
37316
38647
  return files;
37317
38648
  }
@@ -37394,8 +38725,8 @@ function registerSchedulesLiftCommand(program3, deps = {}) {
37394
38725
 
37395
38726
  // src/spawn-policy-core.ts
37396
38727
  var import_node_child_process16 = require("node:child_process");
37397
- var import_node_fs43 = require("node:fs");
37398
- var import_node_path40 = require("node:path");
38728
+ var import_node_fs45 = require("node:fs");
38729
+ var import_node_path42 = require("node:path");
37399
38730
  var SPAWNERS = ["spawn", "spawnSync", "exec", "execSync", "execFile", "execFileSync"];
37400
38731
  var CALL_SOURCE = String.raw`(^|[^.\w$])(${SPAWNERS.join("|")})\s*\(`;
37401
38732
  var SOURCE_EXT = /\.(ts|mts|cts|js|mjs|cjs)$/;
@@ -37481,7 +38812,7 @@ function runSpawnPolicy(root) {
37481
38812
  for (const file of files) {
37482
38813
  let raw;
37483
38814
  try {
37484
- raw = (0, import_node_fs43.readFileSync)((0, import_node_path40.join)(root, file), "utf8");
38815
+ raw = (0, import_node_fs45.readFileSync)((0, import_node_path42.join)(root, file), "utf8");
37485
38816
  } catch {
37486
38817
  continue;
37487
38818
  }
@@ -37501,19 +38832,19 @@ function runSpawnPolicy(root) {
37501
38832
  function registerDeveloperCommands(program3) {
37502
38833
  const rules = program3.command("rules").description("org-managed .gitignore delivery");
37503
38834
  rules.command("gitignore").option("--write", "upsert the managed block into .gitignore (default: check only, non-zero exit on drift)").option("--json", "machine-readable output").description("verify (or --write) this repo's org-managed .gitignore block matches the SSOT").action((opts) => {
37504
- const path2 = (0, import_node_path41.join)(process.cwd(), ".gitignore");
37505
- const current = (0, import_node_fs44.existsSync)(path2) ? (0, import_node_fs44.readFileSync)(path2, "utf8") : null;
38835
+ const path2 = (0, import_node_path43.join)(process.cwd(), ".gitignore");
38836
+ const current = (0, import_node_fs46.existsSync)(path2) ? (0, import_node_fs46.readFileSync)(path2, "utf8") : null;
37506
38837
  const plan = planManagedGitignore(current);
37507
38838
  const drift = [...plan.added.map((l) => `+${l}`), ...plan.removed.map((l) => `-${l}`)].join(", ") || "block normalize";
37508
38839
  if (opts.json) {
37509
- if (opts.write && plan.changed) (0, import_node_fs44.writeFileSync)(path2, plan.content, "utf8");
38840
+ if (opts.write && plan.changed) (0, import_node_fs46.writeFileSync)(path2, plan.content, "utf8");
37510
38841
  console.log(JSON.stringify(plan, null, 2));
37511
38842
  if (!opts.write && plan.changed) process.exitCode = 1;
37512
38843
  return;
37513
38844
  }
37514
38845
  if (opts.write) {
37515
38846
  if (plan.changed) {
37516
- (0, import_node_fs44.writeFileSync)(path2, plan.content, "utf8");
38847
+ (0, import_node_fs46.writeFileSync)(path2, plan.content, "utf8");
37517
38848
  console.log(`mmi-cli devops org rules gitignore: updated .gitignore (${drift})`);
37518
38849
  } else {
37519
38850
  console.log("mmi-cli devops org rules gitignore: up to date");
@@ -37771,11 +39102,11 @@ var defaultDeps = {
37771
39102
  // #5813: the caller supplies that command line. `process.argv` is NOT it — the house shim splices
37772
39103
  // the house root out in place, so re-reading it here re-ran the removed flat alias (`release …`
37773
39104
  // instead of `devops release …`) and the converged child refused with exit 2.
37774
- reexec: (argv) => new Promise((resolve6) => {
39105
+ reexec: (argv) => new Promise((resolve7) => {
37775
39106
  const args = [...argv];
37776
39107
  const child2 = isWin2 ? (0, import_node_child_process17.spawn)("cmd.exe", ["/c", "mmi-cli", ...args], { stdio: "inherit", windowsHide: true }) : (0, import_node_child_process17.spawn)("mmi-cli", args, { stdio: "inherit", windowsHide: true });
37777
- child2.on("exit", (code) => resolve6(code ?? 1));
37778
- child2.on("error", () => resolve6(1));
39108
+ child2.on("exit", (code) => resolve7(code ?? 1));
39109
+ child2.on("error", () => resolve7(1));
37779
39110
  }),
37780
39111
  // stderr only — stdout may be carrying --json output.
37781
39112
  warn: (line) => console.error(line)
@@ -37817,7 +39148,7 @@ function clean3(out) {
37817
39148
  return out.trim();
37818
39149
  }
37819
39150
  function sleeper(deps) {
37820
- return deps.sleep ?? ((ms) => new Promise((resolve6) => setTimeout(resolve6, ms)));
39151
+ return deps.sleep ?? ((ms) => new Promise((resolve7) => setTimeout(resolve7, ms)));
37821
39152
  }
37822
39153
  function normalizeHotfixVersion(input) {
37823
39154
  const m = /^v?(\d+\.\d+\.\d+)$/.exec(input.trim());
@@ -38846,8 +40177,8 @@ async function announceRelease(deps, args) {
38846
40177
  }
38847
40178
 
38848
40179
  // src/train-commands.ts
38849
- var import_node_fs45 = require("node:fs");
38850
- var import_node_path42 = require("node:path");
40180
+ var import_node_fs47 = require("node:fs");
40181
+ var import_node_path44 = require("node:path");
38851
40182
  var RELEASE_BUMP_INTENTS = ["major", "minor", "patch"];
38852
40183
  function resolveReleaseBumpIntent(raw) {
38853
40184
  const intent = typeof raw === "string" ? raw.trim() : "";
@@ -38858,7 +40189,7 @@ function resolveReleaseBumpIntent(raw) {
38858
40189
  }
38859
40190
  function readRepoVersion() {
38860
40191
  try {
38861
- return JSON.parse((0, import_node_fs45.readFileSync)((0, import_node_path42.join)(process.cwd(), ".claude-plugin", "plugin.json"), "utf8")).version || void 0;
40192
+ return JSON.parse((0, import_node_fs47.readFileSync)((0, import_node_path44.join)(process.cwd(), ".claude-plugin", "plugin.json"), "utf8")).version || void 0;
38862
40193
  } catch {
38863
40194
  return void 0;
38864
40195
  }
@@ -39041,6 +40372,7 @@ function renderReleaseResume(r) {
39041
40372
  if (r.devRollForward) lines.push(` development: ${r.devRollForward.note}`);
39042
40373
  if (r.rcAlignment) lines.push(` rc: ${r.rcAlignment.note}`);
39043
40374
  if (r.checkout) lines.push(` checkout: ${r.checkout.note}`);
40375
+ if (r.ledger) lines.push(...formatReleaseLedgerReport(r.ledger).map((l, i) => i === 0 ? ` ${l}` : ` ${l}`));
39044
40376
  return lines.join("\n");
39045
40377
  }
39046
40378
  function renderReleaseAbort(r) {
@@ -39120,6 +40452,27 @@ async function runPostReleaseJervDoctor(repo) {
39120
40452
  };
39121
40453
  }
39122
40454
  }
40455
+ function buildReleaseVerdict(commandName, result, projectInfoSync) {
40456
+ const alignmentPending = result.devRollForward?.status === "pr-pending" || result.rcAlignment?.status === "pr-pending";
40457
+ const followUpStatus = deriveTrainFollowUpStatus({
40458
+ projectInfo: projectInfoSync && "error" in projectInfoSync ? "failure" : "ok",
40459
+ deploy: deployFollowUpOutcome(result.deployStatus),
40460
+ rcRetirement: result.rcRetirement === "failed" ? result.rcRetirementCategory === "wait-timeout" ? "unresolved" : "failure" : "ok",
40461
+ alignment: alignmentPending ? "unresolved" : "ok",
40462
+ foldPort: "ok"
40463
+ });
40464
+ return {
40465
+ followUpStatus,
40466
+ releaseVerdict: commandName === "release" ? {
40467
+ releaseStatus: "succeeded",
40468
+ followUpStatus,
40469
+ promoted: result.promoted,
40470
+ tag: result.tag,
40471
+ releaseUrl: result.release?.url ?? null,
40472
+ legs: releaseFollowUpLegs(result, projectInfoSync)
40473
+ } : void 0
40474
+ };
40475
+ }
39123
40476
  function renderTrainApply(commandName, r) {
39124
40477
  let base = `mmi-cli ${commandName}: promoted ${r.repo} \u2014 ${r.stage} at ${r.tag} [${r.deployModel}]; ${renderDeployLine(r)}`;
39125
40478
  if (r.versionFold) base = `${base}; ${r.versionFold}`;
@@ -39148,9 +40501,14 @@ function renderTrainApply(commandName, r) {
39148
40501
  const failedLegs = v.legs.filter((leg) => leg.status === "failed");
39149
40502
  return [
39150
40503
  `${base}; release verdict: ${verdict}; promoted=${v.promoted}; tag=${v.tag}; release=${v.releaseUrl ?? "unreported"}; follow-up: ${v.followUpStatus.toUpperCase()}`,
39151
- ...failedLegs.map((leg) => ` - follow-up leg ${leg.leg}: FAILED \u2014 ${leg.error ?? "no diagnostic recorded"}`)
40504
+ ...failedLegs.map((leg) => ` - follow-up leg ${leg.leg}: FAILED \u2014 ${leg.error ?? "no diagnostic recorded"}`),
40505
+ // #5987: the durable ledger's phase legs + derived typed verdict ride the same receipt.
40506
+ ...r.ledger ? formatReleaseLedgerReport(r.ledger).map((line) => ` ${line}`) : []
39152
40507
  ].join("\n");
39153
40508
  }
40509
+ if (r.ledger) {
40510
+ return [base, ...formatReleaseLedgerReport(r.ledger).map((line) => ` ${line}`)].join("\n");
40511
+ }
39154
40512
  return base;
39155
40513
  }
39156
40514
  function applyTrainFollowUpExit(status) {
@@ -39175,7 +40533,7 @@ function registerTrainCommandsAndInventory(program3) {
39175
40533
  }
39176
40534
  function registerTrainOperationsCommands(program3, runProjectInfoSyncCallback) {
39177
40535
  for (const commandName of ["rcand", "release"]) {
39178
- const trainCmd = program3.command(commandName).description(`plan ${commandName} train operations; mutations require explicit master-admin approval`).option("--json", "machine-readable output").option("--watch", "block on the deploy/publish workflow runs and report their outcomes").option("--apply", "execute the guarded master-only train after explicit approval").option("--resume", commandName === "rcand" ? "finish a candidate whose immutable public rc tag passed policy but origin/rc was not pushed (#3881)" : "finish a partial release or its protected post-release alignment without re-cutting, republishing, or redeploying (#3851/#3885)");
40536
+ const trainCmd = program3.command(commandName).description(`plan ${commandName} train operations; mutations require explicit master-admin approval`).option("--json", "machine-readable output").option("--watch", "block on the deploy/publish workflow runs and report their outcomes").option("--apply", "execute the guarded master-only train after explicit approval").option("--resume", commandName === "rcand" ? "finish a candidate whose immutable public rc tag passed policy but origin/rc was not pushed (#3881)" : "finish a partial release or its protected post-release alignment without re-cutting, republishing, or redeploying (#3851/#3885)").option("--out <path>", "with --apply, write the result to this file as UTF-8 (no BOM) instead of stdout \u2014 the shell-free receipt path (#5983)");
39179
40537
  const RELEASE_ONLY_FLAGS = [
39180
40538
  { flags: "--announce-summary-file <path>", description: "agent-curated 3-6 line Hub Slack summary; required for a new MMI-Hub --apply (#883/#3901)" },
39181
40539
  { flags: "--ack <shas>", description: "comma-separated dev shas a human verified are in the candidate, overriding the hotfix-coverage guard for a conflicted port whose -x trailer was lost (#958)" },
@@ -39211,10 +40569,13 @@ function registerTrainOperationsCommands(program3, runProjectInfoSyncCallback) {
39211
40569
  if (o.retryPublish && commandName !== "release") {
39212
40570
  return fail(`${commandName}: --retry-publish applies only to release. Run: mmi-cli devops release --retry-publish <run-id> --apply --watch`);
39213
40571
  }
40572
+ if (o.out && !o.apply) {
40573
+ return fail(`${commandName}: --out writes the --apply result receipt only \u2014 the dry-run plan and --resume keep their stdout/JSON output. Drop --out${o.resume ? "" : " or rerun with --apply"}`);
40574
+ }
39214
40575
  if (o.retryPublish) {
39215
40576
  if (o.resume || o.abort) return fail("release: --retry-publish cannot be combined with --resume or --abort");
39216
40577
  if (!o.apply) return fail("release: --retry-publish requires --apply after explicit approval; nothing was written");
39217
- if (o.announceSummaryFile || o.ack || o.dev) return fail("release: --retry-publish accepts only --apply, --watch, --repo and --json");
40578
+ if (o.announceSummaryFile || o.ack || o.dev || o.out) return fail("release: --retry-publish accepts only --apply, --watch, --repo and --json");
39218
40579
  if (o.repo) {
39219
40580
  const guard = planTrainApplyRepoGuard(o.repo, await resolveRepo(), `mmi-cli devops release --retry-publish ${o.retryPublish} --apply${o.watch ? " --watch" : ""}`);
39220
40581
  if (!guard.ok) return fail(`release: ${guard.message}`);
@@ -39238,7 +40599,7 @@ function registerTrainOperationsCommands(program3, runProjectInfoSyncCallback) {
39238
40599
  if (o.abort) {
39239
40600
  if (o.resume) return fail("release: --abort and --resume are mutually exclusive \u2014 abort removes an unpublished tag while resume preserves and promotes it");
39240
40601
  if (!o.apply) return fail("release: --abort requires --apply after explicit approval; nothing was written");
39241
- if (o.watch || o.announceSummaryFile || o.ack || o.dev) {
40602
+ if (o.watch || o.announceSummaryFile || o.ack || o.dev || o.out) {
39242
40603
  return fail("release: --abort accepts only --apply, --repo and --json; promotion flags cannot be combined with rollback");
39243
40604
  }
39244
40605
  if (o.repo) {
@@ -39313,29 +40674,16 @@ function registerTrainOperationsCommands(program3, runProjectInfoSyncCallback) {
39313
40674
  projectInfoSync = { applied: false, note: `FAILED \u2014 ${error}`, error };
39314
40675
  }
39315
40676
  }
39316
- const alignmentPending = result.devRollForward?.status === "pr-pending" || result.rcAlignment?.status === "pr-pending";
39317
- const followUpStatus = deriveTrainFollowUpStatus({
39318
- projectInfo: projectInfoSync && "error" in projectInfoSync ? "failure" : "ok",
39319
- deploy: deployFollowUpOutcome(result.deployStatus),
39320
- rcRetirement: result.rcRetirement === "failed" ? result.rcRetirementCategory === "wait-timeout" ? "unresolved" : "failure" : "ok",
39321
- alignment: alignmentPending ? "unresolved" : "ok",
39322
- foldPort: "ok"
39323
- });
39324
- const releaseVerdict = commandName === "release" ? {
39325
- releaseStatus: "succeeded",
39326
- followUpStatus,
39327
- promoted: result.promoted,
39328
- tag: result.tag,
39329
- releaseUrl: result.release?.url ?? null,
39330
- legs: releaseFollowUpLegs(result, projectInfoSync)
39331
- } : void 0;
40677
+ const { followUpStatus, releaseVerdict } = buildReleaseVerdict(commandName, result, projectInfoSync);
39332
40678
  const reported = {
39333
40679
  ...result,
39334
40680
  ...projectInfoSync ? { projectInfoSync } : {},
39335
40681
  ...postReleaseJervDoctor ? { postReleaseJervDoctor } : {},
39336
40682
  ...releaseVerdict ? { releaseVerdict } : {}
39337
40683
  };
39338
- printLine(o.json ? JSON.stringify(reported, null, 2) : renderTrainApply(commandName, reported));
40684
+ const output = o.json ? JSON.stringify(reported, null, 2) : renderTrainApply(commandName, reported);
40685
+ if (!o.out) printLine(output);
40686
+ else printLine(`Wrote ${commandName} apply result to ${o.out} (UTF-8, ${writeUtf8Receipt(o.out, output)} bytes)`);
39339
40687
  applyTrainFollowUpExit(followUpStatus);
39340
40688
  return;
39341
40689
  } catch (e) {
@@ -39477,12 +40825,12 @@ ${r.stderr ?? ""}`).catch(() => "");
39477
40825
  var ENV_HEAL_LOCK_STALE_MS = 10 * 6e4;
39478
40826
  var ENV_HEAL_LOCK_MAX_WAIT_MS = 2 * 6e4;
39479
40827
  function envHealLockPath(home) {
39480
- return (0, import_node_path43.join)(home, ".claude", "plugins", ".mmi-env-heal.lock");
40828
+ return (0, import_node_path45.join)(home, ".claude", "plugins", ".mmi-env-heal.lock");
39481
40829
  }
39482
40830
  async function withEnvHealLock(what, run) {
39483
40831
  try {
39484
40832
  return await withFileLock(
39485
- envHealLockPath((0, import_node_os19.homedir)()),
40833
+ envHealLockPath((0, import_node_os20.homedir)()),
39486
40834
  { staleMs: ENV_HEAL_LOCK_STALE_MS, maxWaitMs: ENV_HEAL_LOCK_MAX_WAIT_MS, label: "mmi env-heal lock" },
39487
40835
  run
39488
40836
  );
@@ -39578,7 +40926,7 @@ function mmiDoctorDeps(opts = {}) {
39578
40926
  const configRoot = surfaceConfigRoot(surface);
39579
40927
  const running = surface === "codex" ? installedActivePluginVersion(surface) : runningPluginVersion(process.env, resolveClientVersion());
39580
40928
  const plan = buildPluginCachePlan(
39581
- (0, import_node_os19.homedir)(),
40929
+ (0, import_node_os20.homedir)(),
39582
40930
  running,
39583
40931
  pluginCacheFsDeps(configRoot, () => 0),
39584
40932
  { configRoot, includeStaging: surface !== "codex" }
@@ -39606,14 +40954,14 @@ function mmiDoctorDeps(opts = {}) {
39606
40954
  const running = surface === "codex" ? installedActivePluginVersion(surface) : runningPluginVersion(process.env, resolveClientVersion());
39607
40955
  const installed = installedActivePluginVersion(surface);
39608
40956
  const plan = buildPluginCachePlan(
39609
- (0, import_node_os19.homedir)(),
40957
+ (0, import_node_os20.homedir)(),
39610
40958
  running,
39611
40959
  pluginCacheFsDeps(configRoot, () => 0),
39612
40960
  { configRoot, includeStaging: surface !== "codex", installedVersion: installed }
39613
40961
  );
39614
40962
  const result = applyPluginCachePlan(
39615
40963
  plan,
39616
- (p) => (0, import_node_fs46.rmSync)(p, { recursive: true }),
40964
+ (p) => (0, import_node_fs48.rmSync)(p, { recursive: true }),
39617
40965
  stagingApplyFsGuard(configRoot)
39618
40966
  );
39619
40967
  return {
@@ -39648,7 +40996,7 @@ function mmiDoctorDeps(opts = {}) {
39648
40996
  // has no generated routing index would
39649
40997
  // get a permanent — demanding an artifact it never asked for.
39650
40998
  docsIndexState: (root) => {
39651
- if (!(0, import_node_fs46.existsSync)((0, import_node_path43.join)(root, DOCS_INDEX_PATH))) return void 0;
40999
+ if (!(0, import_node_fs48.existsSync)((0, import_node_path45.join)(root, DOCS_INDEX_PATH))) return void 0;
39652
41000
  const real = createDocsIndexDeps(root);
39653
41001
  let docs;
39654
41002
  const listDocs = () => docs ??= real.listDocs();
@@ -39657,7 +41005,7 @@ function mmiDoctorDeps(opts = {}) {
39657
41005
  },
39658
41006
  // #4168: working-tree heal — write if drifted, then re-check. Commit remains the operator's step.
39659
41007
  healDocsIndex: (root) => {
39660
- if (!(0, import_node_fs46.existsSync)((0, import_node_path43.join)(root, DOCS_INDEX_PATH))) return { drift: false, docCount: 0 };
41008
+ if (!(0, import_node_fs48.existsSync)((0, import_node_path45.join)(root, DOCS_INDEX_PATH))) return { drift: false, docCount: 0 };
39661
41009
  const real = createDocsIndexDeps(root);
39662
41010
  let docs;
39663
41011
  const listDocs = () => docs ??= real.listDocs();
@@ -39678,8 +41026,8 @@ function mmiDoctorDeps(opts = {}) {
39678
41026
  });
39679
41027
  const raced = await Promise.race([
39680
41028
  work.then((r) => ({ ...r, timedOut: false })),
39681
- new Promise((resolve6) => {
39682
- ceiling = setTimeout(() => resolve6({ timedOut: true, scanned: 0, findings: 0, fixed: 0, failed: 0 }), BOARD_DOCTOR_TIMEOUT_MS);
41029
+ new Promise((resolve7) => {
41030
+ ceiling = setTimeout(() => resolve7({ timedOut: true, scanned: 0, findings: 0, fixed: 0, failed: 0 }), BOARD_DOCTOR_TIMEOUT_MS);
39683
41031
  })
39684
41032
  ]);
39685
41033
  if (raced.timedOut) return { scanned: 0, findings: 0, fixed: 0, failed: 0, timedOut: true };
@@ -39712,8 +41060,8 @@ function mmiDoctorDeps(opts = {}) {
39712
41060
  incomplete: nb.incomplete,
39713
41061
  timedOut: false
39714
41062
  })),
39715
- new Promise((resolve6) => {
39716
- ceiling = setTimeout(() => resolve6({ driftLines: [], incomplete: [], timedOut: true }), SCHEDULES_DRIFT_TIMEOUT_MS);
41063
+ new Promise((resolve7) => {
41064
+ ceiling = setTimeout(() => resolve7({ driftLines: [], incomplete: [], timedOut: true }), SCHEDULES_DRIFT_TIMEOUT_MS);
39717
41065
  })
39718
41066
  ]);
39719
41067
  if (!raced.timedOut && raced.incomplete.length === 0) writeSchedulesDriftCache(cachePath, raced.driftLines);
@@ -40228,7 +41576,7 @@ project.command("set [owner/repo]").description("upsert project META (idempotent
40228
41576
  if (dupe) return fail(`org project set: KEY "${dupe}" was passed to both --var and --set; --set is an alias of --var, so pass each KEY once`);
40229
41577
  if (o.secretsFile) {
40230
41578
  try {
40231
- vars.push(`secrets=${(0, import_node_fs46.readFileSync)(o.secretsFile, "utf8")}`);
41579
+ vars.push(`secrets=${(0, import_node_fs48.readFileSync)(o.secretsFile, "utf8")}`);
40232
41580
  } catch (e) {
40233
41581
  return fail(`org project set: cannot read --secrets-file ${o.secretsFile}: ${e.message}`);
40234
41582
  }
@@ -40541,16 +41889,16 @@ function ciAuditDeps2() {
40541
41889
  // gate re-seed step is skipped gracefully rather than failing mid-run.
40542
41890
  readSeedFile: (path2) => {
40543
41891
  if (!root) return null;
40544
- const fullPath = (0, import_node_path43.join)(root, path2);
40545
- return (0, import_node_fs46.existsSync)(fullPath) ? (0, import_node_fs46.readFileSync)(fullPath, "utf8") : null;
41892
+ const fullPath = (0, import_node_path45.join)(root, path2);
41893
+ return (0, import_node_fs48.existsSync)(fullPath) ? (0, import_node_fs48.readFileSync)(fullPath, "utf8") : null;
40546
41894
  }
40547
41895
  };
40548
41896
  }
40549
41897
  function hubRoot2() {
40550
- const fromPkg = (0, import_node_path43.join)(__dirname, "..", "..");
41898
+ const fromPkg = (0, import_node_path45.join)(__dirname, "..", "..");
40551
41899
  const marker = "skills/bootstrap/seeds/manifest.json";
40552
- if ((0, import_node_fs46.existsSync)((0, import_node_path43.join)(fromPkg, marker))) return fromPkg;
40553
- if ((0, import_node_fs46.existsSync)((0, import_node_path43.join)(process.cwd(), marker))) return process.cwd();
41900
+ if ((0, import_node_fs48.existsSync)((0, import_node_path45.join)(fromPkg, marker))) return fromPkg;
41901
+ if ((0, import_node_fs48.existsSync)((0, import_node_path45.join)(process.cwd(), marker))) return process.cwd();
40554
41902
  return null;
40555
41903
  }
40556
41904
  registerQueryCommands(program2);
@@ -40650,10 +41998,10 @@ access.command("audit").description("audit collaborator roles + train-branch pus
40650
41998
  targets = resolution.targets;
40651
41999
  }
40652
42000
  const derivedMatrix = registryProjects ? accessMatrixFromProjects(registryProjects) : {};
40653
- const fileMatrix = (0, import_node_fs46.existsSync)("access-matrix.json") ? loadAccessMatrix((0, import_node_fs46.readFileSync)("access-matrix.json", "utf8")) : {};
42001
+ const fileMatrix = (0, import_node_fs48.existsSync)("access-matrix.json") ? loadAccessMatrix((0, import_node_fs48.readFileSync)("access-matrix.json", "utf8")) : {};
40654
42002
  const matrix = mergeAccessMatrix(fileMatrix, derivedMatrix);
40655
42003
  const dataAccess = registryProjects ? dataAccessContractsFromProjects(registryProjects) : { consumers: {} };
40656
- const sanctioned = (0, import_node_fs46.existsSync)("access-matrix.json") ? loadSanctionedAdmins((0, import_node_fs46.readFileSync)("access-matrix.json", "utf8")) : {};
42004
+ const sanctioned = (0, import_node_fs48.existsSync)("access-matrix.json") ? loadSanctionedAdmins((0, import_node_fs48.readFileSync)("access-matrix.json", "utf8")) : {};
40657
42005
  const report = await auditOrgAccess(targets, deps, matrix, dataAccess, sanctioned);
40658
42006
  console.log(o.json ? JSON.stringify(report, null, 2) : renderAccessReport(report));
40659
42007
  if (!report.ok) process.exitCode = 1;
@@ -40684,16 +42032,16 @@ function directoryBytes(path2) {
40684
42032
  let total = 0;
40685
42033
  let entries;
40686
42034
  try {
40687
- entries = (0, import_node_fs46.readdirSync)(path2, { withFileTypes: true });
42035
+ entries = (0, import_node_fs48.readdirSync)(path2, { withFileTypes: true });
40688
42036
  } catch {
40689
42037
  return 0;
40690
42038
  }
40691
42039
  for (const entry of entries) {
40692
- const child2 = (0, import_node_path43.join)(path2, entry.name);
42040
+ const child2 = (0, import_node_path45.join)(path2, entry.name);
40693
42041
  if (entry.isDirectory()) total += directoryBytes(child2);
40694
42042
  else {
40695
42043
  try {
40696
- total += (0, import_node_fs46.statSync)(child2).size;
42044
+ total += (0, import_node_fs48.statSync)(child2).size;
40697
42045
  } catch {
40698
42046
  }
40699
42047
  }
@@ -40701,25 +42049,25 @@ function directoryBytes(path2) {
40701
42049
  return total;
40702
42050
  }
40703
42051
  function listDirEntries(dir) {
40704
- return (0, import_node_fs46.readdirSync)(dir, { withFileTypes: true }).map((d) => ({ name: d.name, isDirectory: d.isDirectory() }));
42052
+ return (0, import_node_fs48.readdirSync)(dir, { withFileTypes: true }).map((d) => ({ name: d.name, isDirectory: d.isDirectory() }));
40705
42053
  }
40706
42054
  function readInstalledPluginRefs(configRoot) {
40707
42055
  const p = installedPluginsPathForConfig(configRoot);
40708
- if (!(0, import_node_fs46.existsSync)(p)) return [];
42056
+ if (!(0, import_node_fs48.existsSync)(p)) return [];
40709
42057
  try {
40710
- return installedPluginPaths((0, import_node_fs46.readFileSync)(p, "utf8"));
42058
+ return installedPluginPaths((0, import_node_fs48.readFileSync)(p, "utf8"));
40711
42059
  } catch {
40712
42060
  return null;
40713
42061
  }
40714
42062
  }
40715
42063
  function pluginCacheFsDeps(configRoot, dirBytes) {
40716
42064
  return {
40717
- exists: (p) => (0, import_node_fs46.existsSync)(p),
40718
- listVersionDirs: (root) => (0, import_node_fs46.readdirSync)(root, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name),
42065
+ exists: (p) => (0, import_node_fs48.existsSync)(p),
42066
+ listVersionDirs: (root) => (0, import_node_fs48.readdirSync)(root, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name),
40719
42067
  dirBytes,
40720
- listStagingDirs: (root) => (0, import_node_fs46.readdirSync)(root, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => {
42068
+ listStagingDirs: (root) => (0, import_node_fs48.readdirSync)(root, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => {
40721
42069
  try {
40722
- return { name: d.name, mtimeMs: newestMtimeMs((0, import_node_path43.join)(root, d.name), listDirEntries, (p) => (0, import_node_fs46.statSync)(p).mtimeMs) };
42070
+ return { name: d.name, mtimeMs: newestMtimeMs((0, import_node_path45.join)(root, d.name), listDirEntries, (p) => (0, import_node_fs48.statSync)(p).mtimeMs) };
40723
42071
  } catch {
40724
42072
  return { name: d.name, mtimeMs: Date.now() };
40725
42073
  }
@@ -40733,10 +42081,10 @@ function stagingApplyFsGuard(configRoot) {
40733
42081
  return {
40734
42082
  referencedPaths: () => readInstalledPluginRefs(configRoot),
40735
42083
  mtimeMs: (name) => {
40736
- const p = (0, import_node_path43.join)(stagingRoot, name);
40737
- if (!(0, import_node_fs46.existsSync)(p)) return null;
42084
+ const p = (0, import_node_path45.join)(stagingRoot, name);
42085
+ if (!(0, import_node_fs48.existsSync)(p)) return null;
40738
42086
  try {
40739
- return newestMtimeMs(p, listDirEntries, (q) => (0, import_node_fs46.statSync)(q).mtimeMs);
42087
+ return newestMtimeMs(p, listDirEntries, (q) => (0, import_node_fs48.statSync)(q).mtimeMs);
40740
42088
  } catch {
40741
42089
  return null;
40742
42090
  }
@@ -40756,13 +42104,13 @@ program2.command("plugin-prune").description(`prune stale cached MMI plugin vers
40756
42104
  return;
40757
42105
  }
40758
42106
  const plan = buildPluginCachePlan(
40759
- (0, import_node_os19.homedir)(),
42107
+ (0, import_node_os20.homedir)(),
40760
42108
  running,
40761
42109
  pluginCacheFsDeps(configRoot, directoryBytes),
40762
42110
  { withBytes: true, configRoot, includeStaging: surface !== "codex" }
40763
42111
  );
40764
42112
  const anythingToDelete = plan.prune.length > 0 || plan.staging.length > 0;
40765
- const result = o.apply && anythingToDelete ? applyPluginCachePlan(plan, (p) => (0, import_node_fs46.rmSync)(p, { recursive: true, force: true }), stagingApplyFsGuard(configRoot)) : void 0;
42113
+ const result = o.apply && anythingToDelete ? applyPluginCachePlan(plan, (p) => (0, import_node_fs48.rmSync)(p, { recursive: true, force: true }), stagingApplyFsGuard(configRoot)) : void 0;
40766
42114
  const warnings = plan.prune.length > 0 ? [CONCURRENT_SESSION_WARNING] : [];
40767
42115
  if (o.json) console.log(JSON.stringify({ ...plan, warnings, applied: result ?? null }));
40768
42116
  else console.log(renderPluginCachePlan(plan, result));