@mutmutco/cli 4.5.4 → 4.5.5

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 +515 -196
  2. package/package.json +1 -1
package/dist/main.cjs CHANGED
@@ -4534,7 +4534,7 @@ function useColor() {
4534
4534
  var program = new Command();
4535
4535
 
4536
4536
  // src/command-composition.ts
4537
- var import_node_fs49 = require("node:fs");
4537
+ var import_node_fs50 = require("node:fs");
4538
4538
  init_clean_exit();
4539
4539
  init_cli_shared();
4540
4540
 
@@ -5437,7 +5437,7 @@ function resolveVerbAliasShim(argv) {
5437
5437
 
5438
5438
  // src/command-composition.ts
5439
5439
  var import_node_os22 = require("node:os");
5440
- var import_node_path47 = require("node:path");
5440
+ var import_node_path48 = require("node:path");
5441
5441
 
5442
5442
  // src/board-read.ts
5443
5443
  var import_node_fs13 = require("node:fs");
@@ -15998,10 +15998,10 @@ var rollout_plan_default = {
15998
15998
  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)."
15999
15999
  },
16000
16000
  baseline: {
16001
- version: "4.5.4",
16002
- tag: "v4.5.4",
16003
- commit: "0993d5d2fa7b",
16004
- npm: "@mutmutco/cli@4.5.4"
16001
+ version: "4.5.5",
16002
+ tag: "v4.5.5",
16003
+ commit: "696fa43d4d49",
16004
+ npm: "@mutmutco/cli@4.5.5"
16005
16005
  },
16006
16006
  exitCriterion: "fleet-n-of-n",
16007
16007
  hubOnlyShortcut: "forbidden",
@@ -16018,14 +16018,14 @@ var rollout_plan_default = {
16018
16018
  repo: "mutmutco/mmi-hub",
16019
16019
  role: "canary",
16020
16020
  schedule: "train",
16021
- v3Target: "v4.5.4"
16021
+ v3Target: "v4.5.5"
16022
16022
  }
16023
16023
  ],
16024
16024
  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.",
16025
16025
  rollback: {
16026
16026
  independent: true,
16027
- mechanism: "npm dist-tag latest -> 4.5.4 and redeploy the Hub Lambda from tag v4.5.4 (0993d5d2fa7b); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
16028
- v3Target: "v4.5.4 (@mutmutco/cli@4.5.4, tag commit 0993d5d2fa7b \u2014 last known-good release carrying the repo-index v4-only contract)"
16027
+ mechanism: "npm dist-tag latest -> 4.5.5 and redeploy the Hub Lambda from tag v4.5.5 (696fa43d4d49); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
16028
+ v3Target: "v4.5.5 (@mutmutco/cli@4.5.5, tag commit 696fa43d4d49 \u2014 last known-good release carrying the repo-index v4-only contract)"
16029
16029
  }
16030
16030
  },
16031
16031
  {
@@ -18939,37 +18939,65 @@ function isTerminalVercelState(state) {
18939
18939
  function str(v) {
18940
18940
  return typeof v === "string" && v.length > 0 ? v : void 0;
18941
18941
  }
18942
- async function readVercelDeployment(api, repo, target, environment) {
18943
- const query = target.sha ? `sha=${encodeURIComponent(target.sha)}` : `ref=${encodeURIComponent(target.ref ?? "")}`;
18944
- const listed = await api(`repos/${repo}/deployments?${query}&per_page=30`);
18945
- const rows = Array.isArray(listed) ? listed : [];
18946
- const prefix = environment.toLowerCase();
18947
- const hit = rows.find((d) => typeof d.id === "number" && (str(d.environment) ?? "").toLowerCase().startsWith(prefix));
18948
- if (!hit) return void 0;
18949
- const statuses = await api(`repos/${repo}/deployments/${hit.id}/statuses?per_page=1`);
18950
- const latest = Array.isArray(statuses) ? statuses[0] : void 0;
18951
- const state = str(latest?.state) ?? "none";
18942
+ function matchesTarget(environment, target) {
18943
+ const production = environment.toLowerCase().startsWith("production");
18944
+ return target === "production" ? production : !production;
18945
+ }
18946
+ async function readOne(api, repo, id) {
18947
+ const statuses = await api(`repos/${repo}/deployments/${id}/statuses?per_page=10`);
18948
+ const rows = Array.isArray(statuses) ? statuses : [];
18949
+ const latest = rows.find((s) => str(s.state) !== "inactive") ?? rows[0];
18950
+ const raw = str(latest?.state);
18951
+ const state = raw === void 0 ? "none" : raw === "inactive" ? "success" : raw;
18952
18952
  const url = str(latest?.environment_url) ?? str(latest?.target_url);
18953
+ const logUrl = str(latest?.log_url);
18954
+ return { state, ...url ? { url } : {}, ...logUrl ? { logUrl } : {} };
18955
+ }
18956
+ var FAILED = /* @__PURE__ */ new Set(["failure", "error"]);
18957
+ async function readVercelDeployment(api, repo, sha, target, since) {
18958
+ const listed = await api(`repos/${repo}/deployments?sha=${encodeURIComponent(sha)}&per_page=30`);
18959
+ const rows = Array.isArray(listed) ? listed : [];
18960
+ const newest = /* @__PURE__ */ new Map();
18961
+ for (const d of rows) {
18962
+ const env = str(d.environment) ?? "";
18963
+ if (typeof d.id !== "number" || !matchesTarget(env, target) || newest.has(env)) continue;
18964
+ const created = Date.parse(str(d.created_at) ?? "");
18965
+ if (since !== void 0 && Number.isFinite(created) && created < since) continue;
18966
+ newest.set(env, d.id);
18967
+ }
18968
+ if (newest.size === 0) return void 0;
18969
+ const reads = await Promise.all([...newest].map(async ([env, id]) => ({ env, id, ...await readOne(api, repo, id) })));
18970
+ const failed = reads.find((r) => FAILED.has(r.state));
18971
+ const unfinished = reads.find((r) => !isTerminalVercelState(r.state));
18972
+ const pick = failed ?? unfinished ?? reads[0];
18953
18973
  return {
18954
- id: hit.id,
18955
- environment: str(hit.environment) ?? environment,
18956
- sha: str(hit.sha) ?? target.sha ?? "",
18957
- ref: str(hit.ref) ?? target.ref ?? "",
18958
- state,
18959
- ...url ? { url } : {},
18960
- ...str(latest?.log_url) ? { logUrl: str(latest?.log_url) } : {}
18974
+ id: pick.id,
18975
+ environment: reads.map((r) => r.env).join(", "),
18976
+ sha,
18977
+ state: pick.state,
18978
+ ...reads[0].url ? { url: reads[0].url } : {},
18979
+ ...pick.logUrl ? { logUrl: pick.logUrl } : {}
18961
18980
  };
18962
18981
  }
18963
18982
  var VERCEL_WATCH_ATTEMPTS = 120;
18964
18983
  var VERCEL_WATCH_DELAY_MS = 1e4;
18965
- async function watchVercelProduction(api, repo, sha, sleep2, onTick) {
18984
+ async function watchVercelDeployment(api, repo, sha, target, sleep2, onTick, since) {
18966
18985
  let last;
18986
+ let readOnce = false;
18987
+ let lastError;
18967
18988
  for (let attempt = 0; attempt < VERCEL_WATCH_ATTEMPTS; attempt++) {
18968
18989
  if (attempt > 0) await sleep2(VERCEL_WATCH_DELAY_MS);
18969
- last = await readVercelDeployment(api, repo, { sha }, "Production");
18990
+ try {
18991
+ last = await readVercelDeployment(api, repo, sha, target, since);
18992
+ readOnce = true;
18993
+ } catch (e) {
18994
+ lastError = e;
18995
+ continue;
18996
+ }
18970
18997
  onTick?.(attempt, last);
18971
18998
  if (last && isTerminalVercelState(last.state)) return last;
18972
18999
  }
19000
+ if (!readOnce) throw lastError;
18973
19001
  return last;
18974
19002
  }
18975
19003
  async function probeVercelUrl(url, fetchImpl = fetch) {
@@ -19420,34 +19448,28 @@ function prodReleaseTargets(model) {
19420
19448
  function trainObservesShipment(model) {
19421
19449
  return isCentralDispatchModel(model) || prodReleaseTargets(model).length > 0 || model === "vercel";
19422
19450
  }
19423
- async function vercelDeployDispatch(deps, ctx, ref, watch, headSha) {
19424
- const note = "no dispatch: Vercel deploys main on push (Production) \u2014 observed via the Vercel GitHub app Deployment";
19425
- if (ref !== "main") return { note: "no dispatch on rc: a vercel repo is direct-track and has no rc stage", deployStatus: "pending" };
19451
+ async function vercelDeployDispatch(deps, ctx, ref, watch, headSha, since) {
19452
+ const target = ref === "main" ? "production" : "preview";
19453
+ const plane = target === "production" ? "Production" : `${ref} (non-Production)`;
19454
+ const note = `no dispatch: Vercel deploys ${ref} on push (${plane}) \u2014 observed via the Vercel GitHub app Deployment`;
19426
19455
  if (!headSha) return { note, deployStatus: "pending" };
19427
- const api = async (path2) => JSON.parse(await deps.run("gh", ["api", path2]));
19428
- const row = (d, conclusion) => ({
19429
- workflow: "vercel",
19430
- conclusion,
19431
- ...d?.logUrl ? { runUrl: d.logUrl } : { runUrlNote: "no Vercel Deployment posted for this SHA yet" },
19432
- event: "push"
19433
- });
19434
- const verdict = (d) => d?.state === "success" ? "success" : d && isTerminalVercelState(d.state) ? "failure" : "pending";
19456
+ const after = target === "preview" ? rcWindow(since) : void 0;
19435
19457
  let read;
19436
19458
  try {
19437
19459
  if (!watch) {
19438
19460
  deps.warn?.("Release: Checking Vercel deployment status");
19439
- read = await readVercelDeployment(api, ctx.repo, { sha: headSha }, "Production");
19461
+ read = await readVercelDeployment(vercelApi(deps), ctx.repo, headSha, target, after);
19440
19462
  } else {
19441
- deps.warn?.("Release: Waiting for Vercel to deploy production");
19442
- read = await watchVercelProduction(api, ctx.repo, headSha, resolveSleep(deps), (attempt, d) => {
19463
+ deps.warn?.(`Release: Waiting for Vercel to deploy ${ref}`);
19464
+ read = await watchVercelDeployment(vercelApi(deps), ctx.repo, headSha, target, resolveSleep(deps), (attempt, d) => {
19443
19465
  if (attempt > 0 && attempt % 6 === 0) deps.warn?.(`Vercel deployment ${d ? d.state : "not posted yet"} \u2014 ${attempt / 6}m elapsed`);
19444
- });
19466
+ }, after);
19445
19467
  }
19446
19468
  } catch (e) {
19447
- return { note: `${note}; the Deployment read FAILED (${describeReadError(e)}) \u2014 verify on Vercel; never re-tag`, workflowRuns: [row(void 0, "failure")], deployStatus: "failure" };
19469
+ return { note: `${note}; the Deployment read FAILED (${describeReadError(e)}) \u2014 verify on Vercel; never re-tag`, workflowRuns: [vercelRow(void 0, "failure")], deployStatus: "failure" };
19448
19470
  }
19449
- let deployStatus = verdict(read);
19450
- let detail = read ? `Vercel Production deployment ${read.state}${read.url ? ` at ${read.url}` : ""}` : "no Vercel Deployment posted for this SHA (is the Vercel GitHub app connected?)";
19471
+ let deployStatus = vercelVerdict(read);
19472
+ let detail = read ? `Vercel ${plane} deployment ${read.state}${read.url ? ` at ${read.url}` : ""}` : "no Vercel Deployment posted for this SHA (is the Vercel GitHub app connected?)";
19451
19473
  if (watch && deployStatus === "success" && read?.url) {
19452
19474
  const probe = await (deps.probeUrl ?? probeVercelUrl)(read.url);
19453
19475
  if (probe.ok) detail = `${detail}; URL answered HTTP ${probe.status}`;
@@ -19456,7 +19478,27 @@ async function vercelDeployDispatch(deps, ctx, ref, watch, headSha) {
19456
19478
  detail = `${detail}; URL probe FAILED (${probe.error ?? `HTTP ${probe.status}`})`;
19457
19479
  }
19458
19480
  }
19459
- return { note: `${note}; ${detail}`, ...read?.logUrl ? { runUrl: read.logUrl } : {}, workflowRuns: [row(read, deployStatus)], deployStatus };
19481
+ return { note: `${note}; ${detail}`, ...read?.logUrl ? { runUrl: read.logUrl } : {}, workflowRuns: [vercelRow(read, deployStatus)], deployStatus };
19482
+ }
19483
+ function vercelApi(deps) {
19484
+ return async (path2) => JSON.parse(await deps.run("gh", ["api", path2]));
19485
+ }
19486
+ function vercelVerdict(d) {
19487
+ return d?.state === "success" ? "success" : d && isTerminalVercelState(d.state) ? "failure" : "pending";
19488
+ }
19489
+ function vercelRow(d, conclusion) {
19490
+ return { workflow: "vercel", conclusion, ...d?.logUrl ? { runUrl: d.logUrl } : { runUrlNote: "no Vercel Deployment posted for this SHA yet" }, event: "push" };
19491
+ }
19492
+ async function listVercelDeployRows(deps, repo, sha, ref = "main", since) {
19493
+ const target = ref === "main" ? "production" : "preview";
19494
+ const read = await readVercelDeployment(vercelApi(deps), repo, sha, target, target === "preview" ? rcWindow(since) : void 0);
19495
+ if (!read) return [];
19496
+ const verdict = vercelVerdict(read);
19497
+ const probe = verdict === "success" && read.url ? await (deps.probeUrl ?? probeVercelUrl)(read.url) : void 0;
19498
+ return [vercelRow(read, probe && !probe.ok ? "failure" : verdict)];
19499
+ }
19500
+ function rcWindow(since) {
19501
+ return since === void 0 ? void 0 : since - 6e4;
19460
19502
  }
19461
19503
  function shipLegsGreen(deploy, publish) {
19462
19504
  const green = (leg) => leg?.superseded !== true && (leg?.state === "complete" || leg?.state === "skipped");
@@ -19638,7 +19680,7 @@ async function dispatchDeploy(deps, ctx, stage, ref, model, watch, autoRunSince,
19638
19680
  deployStatus: aggregateWorkflowRuns(workflowRuns)
19639
19681
  };
19640
19682
  }
19641
- if (model === "vercel") return vercelDeployDispatch(deps, ctx, ref, watch, autoRunHeadSha);
19683
+ if (model === "vercel") return vercelDeployDispatch(deps, ctx, ref, watch, autoRunHeadSha, autoRunSince);
19642
19684
  return { note: `no manual dispatch: ${model} repo deploys via its own push-triggered workflow`, deployStatus: "pending" };
19643
19685
  }
19644
19686
  async function readEdgeDomains(deps, slug) {
@@ -21907,6 +21949,16 @@ function deployPhaseInput(model, dispatch, opts = {}) {
21907
21949
  ...runMetadata({ runId: dispatch.runId, runUrl: dispatch.runUrl, workflow: "tenant-deploy.yml", event: "workflow_dispatch" }, runRepo, "dispatch"),
21908
21950
  note: dispatch.note
21909
21951
  };
21952
+ case "vercel": {
21953
+ const row = (dispatch.workflowRuns ?? []).find((r) => r.workflow === "vercel");
21954
+ return {
21955
+ state: runRowState(row, dispatch.deployStatus),
21956
+ ...sha ? { sha } : {},
21957
+ workflow: "vercel",
21958
+ ...row?.runUrl ? { runUrl: row.runUrl } : {},
21959
+ note: dispatch.note
21960
+ };
21961
+ }
21910
21962
  default:
21911
21963
  return {
21912
21964
  state: dispatch.deployStatus === "success" ? "complete" : dispatch.deployStatus === "failure" ? "failed" : "pending",
@@ -21988,6 +22040,13 @@ function phaseInputsFromRunRows(model, rows, tagSha, opts = {}) {
21988
22040
  note: `${publishRun.workflow} run on the release SHA`
21989
22041
  } : { state: "pending", sha: tagSha, note: "publish.yml release run not yet correlated" }
21990
22042
  };
22043
+ case "vercel": {
22044
+ const vercelRow2 = rows.find((r) => r.workflow === "vercel");
22045
+ return {
22046
+ deploy: vercelRow2 ? { state: runRowState(vercelRow2, "pending"), sha: tagSha, workflow: "vercel", ...vercelRow2.runUrl ? { runUrl: vercelRow2.runUrl } : {}, note: "Vercel GitHub app Deployment on the release SHA" } : { state: "pending", sha: tagSha, note: "no Vercel Deployment posted for the release SHA yet (is the Vercel GitHub app connected?)" },
22047
+ publish: { state: "skipped", sha: tagSha, note: "vercel has no publish leg" }
22048
+ };
22049
+ }
21991
22050
  default:
21992
22051
  return {
21993
22052
  deploy: { state: "pending", sha: tagSha, note: `${model} deploys outside the release SHA's run set \u2014 historical outcome uncorrelatable` },
@@ -22194,6 +22253,17 @@ function hotfixRetryDeployShape(ledger) {
22194
22253
  if (!open2.every((phase) => RETRY_DEPLOY_LEGS.has(phase))) return false;
22195
22254
  return open2.some((phase) => ledger.phases[phase].state === "failed");
22196
22255
  }
22256
+ function rcandRetryDeployShape(ledger) {
22257
+ if (ledger.lane !== "rcand") return false;
22258
+ if (ledger.phases.promotion.state !== "complete") return false;
22259
+ const deploy = ledger.phases.deploy;
22260
+ if (deploy.state !== "failed" || deploy.superseded === true || deploy.workflow !== "tenant-deploy.yml") return false;
22261
+ return RELEASE_LEDGER_PHASES.every((phase) => {
22262
+ if (phase === "deploy") return true;
22263
+ const state = ledger.phases[phase].state;
22264
+ return state === "complete" || state === "skipped";
22265
+ });
22266
+ }
22197
22267
  function ledgerFailedRemedy(ledger) {
22198
22268
  const derived = deriveReleasePhaseStatus(ledger);
22199
22269
  const openLegs = derived.legs.filter((l) => l.state === "pending" || l.state === "failed");
@@ -22202,6 +22272,9 @@ function ledgerFailedRemedy(ledger) {
22202
22272
  const failed = openLegs.filter((l) => l.state === "failed").map((l) => l.phase);
22203
22273
  return `the published ${ledger.tag} hotfix can retry the failed ${joinLegNames(failed)} of the same tag: \`mmi-cli devops hotfix release ${ledger.tag} --retry-deploy\` (the deploy helper is idempotent; alignment re-attempts the fold-port PR) (#6985)${tails}`;
22204
22274
  }
22275
+ if (rcandRetryDeployShape(ledger)) {
22276
+ return `fix the rc deploy failure, then retry the failed deploy of the promoted ${ledger.tag} candidate (same tag and SHA, never re-cut): \`mmi-cli devops rcand --retry-deploy --watch\` (#7053)${tails}`;
22277
+ }
22205
22278
  const deployOnlyDeadlock = openLegs.length > 0 && openLegs.every((l) => l.phase === "deploy" && supersedeableDeployLegShape(l));
22206
22279
  const supersession = deployOnlyDeadlock ? `; when the deploy failure is content-caused on the already-published ${ledger.tag} (the immutable tag can never deploy green), the sanctioned exit is \`mmi-cli devops release --supersede-deploy --apply\` after explicit master approval \u2014 the next patch train carries the cause fix (#6553)` : "";
22207
22280
  return `diagnose the failed leg, then ${ledgerLaneResumeCommand(ledger)} once its cause is fixed (an abort only with fresh approval)${supersession}${tails}`;
@@ -22251,6 +22324,10 @@ async function healStaleAlignmentLeg(deps, cwd, expected) {
22251
22324
  return prior.tag;
22252
22325
  });
22253
22326
  }
22327
+ function rcPromotedAt(ledger) {
22328
+ const at = Date.parse(ledger.phases.promotion.updatedAt);
22329
+ return Number.isFinite(at) ? at : void 0;
22330
+ }
22254
22331
  async function healUnfoldedRunLegs(deps, cwd, model) {
22255
22332
  const path2 = releaseLedgerPath(cwd);
22256
22333
  let prior;
@@ -22282,15 +22359,16 @@ async function healUnfoldedRunLegs(deps, cwd, model) {
22282
22359
  }
22283
22360
  const targets = prodReleaseTargets(model);
22284
22361
  const runless = legs.filter((phase) => openLeg(current.phases[phase]) && current.phases[phase].runId == null);
22285
- if (runless.length > 0 && targets.length > 0) {
22362
+ if (runless.length > 0 && (targets.length > 0 || model === "vercel")) {
22286
22363
  let rows;
22287
22364
  try {
22288
- rows = await listOwnWorkflowRuns(deps, releaseRunRepoFor(model, prior.repo), targets, prior.tagSha);
22365
+ rows = model === "vercel" ? await listVercelDeployRows(deps, prior.repo, prior.tagSha, prior.lane === "rcand" ? "rc" : "main", rcPromotedAt(prior)) : await listOwnWorkflowRuns(deps, releaseRunRepoFor(model, prior.repo), targets, prior.tagSha);
22289
22366
  } catch {
22290
22367
  rows = [];
22291
22368
  }
22292
22369
  const inputs = phaseInputsFromRunRows(model, rows, prior.tagSha, { repo: prior.repo });
22293
- const foldable = runless.filter((phase) => inputs[phase].runId != null && inputs[phase].state !== "pending");
22370
+ const correlated = (entry) => entry.runId != null || entry.workflow === "vercel";
22371
+ const foldable = runless.filter((phase) => correlated(inputs[phase]) && inputs[phase].state !== "pending");
22294
22372
  if (foldable.length > 0) {
22295
22373
  const written = await withReleaseLedger(path2, anchors, (ledger) => {
22296
22374
  if (!ledger) return { value: [] };
@@ -23188,6 +23266,13 @@ async function runTrainDoctor(input) {
23188
23266
  unverified(`required status checks on ${stage}`, e);
23189
23267
  }
23190
23268
  }
23269
+ if (meta?.deployModel === "vercel") {
23270
+ const tip = await git3(["rev-parse", "--verify", "--quiet", "refs/remotes/origin/main"]).catch(() => "");
23271
+ const rows = tip ? await listVercelDeployRows(train, repo, tip).catch(() => void 0) : void 0;
23272
+ if (rows && !rows.length) {
23273
+ add({ code: "vercel-unobserved", severity: "warning", source: "origin", title: `no Vercel Production Deployment on origin/main@${tip.slice(0, 12)} \u2014 the Vercel GitHub app or the git-connected Vercel project may be missing`, remedy: `connect ${repo} to its Vercel project (production branch main) and enable the Vercel GitHub app for the repo; the train reads the app's Deployments, never a Vercel token` });
23274
+ }
23275
+ }
23191
23276
  let ledgerTag;
23192
23277
  let ledgerPath;
23193
23278
  try {
@@ -24152,10 +24237,15 @@ async function recoverFailedDispatchPhase(deps, ledger, anchors, phase, dispatch
24152
24237
  note: notes.length > 0 ? `${dispatch.note}; ${notes.join("; ")}` : dispatch.note
24153
24238
  };
24154
24239
  }
24240
+ var RCAND_RETRY_DEPLOY_COMMAND = "mmi-cli devops rcand --retry-deploy --watch";
24241
+ function rcPushedAt(ledger) {
24242
+ const at = Date.parse(ledger?.phases.promotion.updatedAt ?? "");
24243
+ return Number.isFinite(at) ? at : void 0;
24244
+ }
24155
24245
  async function recordRcandDeployLegs(ledger, deps, anchors, deployModel, d, opts = {}) {
24156
24246
  const sha = anchors.tagSha;
24157
24247
  const landed = "the immutable rc tag, the green wall, origin/rc, and the rc deploy dispatch";
24158
- const dispatch = await recoverFailedDispatchPhase(
24248
+ const recovered = await recoverFailedDispatchPhase(
24159
24249
  deps,
24160
24250
  ledger,
24161
24251
  anchors,
@@ -24168,6 +24258,7 @@ async function recordRcandDeployLegs(ledger, deps, anchors, deployModel, d, opts
24168
24258
  workflow: isCentralDispatchModel(deployModel) ? "tenant-deploy.yml" : "rc deploy workflow"
24169
24259
  }
24170
24260
  );
24261
+ const dispatch = recovered.deployStatus === "failure" && !recovered.note.includes(RCAND_RETRY_DEPLOY_COMMAND) ? { ...recovered, note: `once the cause is fixed, retry this rc deploy with \`${RCAND_RETRY_DEPLOY_COMMAND}\` (same tag and SHA, never re-cut): ${recovered.note}` } : recovered;
24171
24262
  const unverifiable = !isCentralDispatchModel(deployModel) && dispatch.deployStatus === "pending" && dispatch.runId == null && !dispatch.workflowRuns?.length;
24172
24263
  const deployEntry = unverifiable ? { state: "skipped", sha, note: `${dispatch.note} \u2014 no run to verify on rc; recorded skipped, never pending (#6068)` } : deployPhaseInput(deployModel, dispatch, { releaseSha: sha });
24173
24264
  await recordPhase(ledger, deps, anchors, "deploy", phaseEntry(deployEntry), { ...opts, landed: "the immutable rc tag, the green wall, and origin/rc" });
@@ -24176,7 +24267,7 @@ async function recordRcandDeployLegs(ledger, deps, anchors, deployModel, d, opts
24176
24267
  return dispatch;
24177
24268
  }
24178
24269
  function releaseAnnounceDeferral(model, summaryFile) {
24179
- return prodReleaseTargets(model).length > 0 ? `\`mmi-cli devops release --resume --watch${summaryFile ? ` --announce-summary-file ${summaryFile}` : ""}\` announces it once they are green` : "a central deploy is proven only under --watch, so this release stays unannounced";
24270
+ return prodReleaseTargets(model).length > 0 || model === "vercel" ? `\`mmi-cli devops release --resume --watch${summaryFile ? ` --announce-summary-file ${summaryFile}` : ""}\` announces it once they are green` : "a central deploy is proven only under --watch, so this release stays unannounced";
24180
24271
  }
24181
24272
  async function completeMainRelease(deps, ctx, meta, deployModel, watch, options, tag, releaseSha, startBranch, preFold, tagProbe, branchHints, ledger, alignEarly) {
24182
24273
  const resumeCommand = options.dev ? "mmi-cli devops release --dev --apply" : "mmi-cli devops release --apply";
@@ -24463,7 +24554,8 @@ Nothing was written. Inspect ${ledger.path} before retrying.`);
24463
24554
  const terminal = deployLeg.state === "complete" || deployLeg.state === "skipped";
24464
24555
  const legStatus = deployLeg.state === "failed" ? "failure" : terminal ? "success" : "pending";
24465
24556
  const hasDispatchEvidence = deployLeg.runId != null || deployLeg.workflow != null || deployLeg.updatedAt !== "never";
24466
- const dispatch02 = terminal || hasDispatchEvidence ? {
24557
+ const vercelReread = deployModel === "vercel" && !terminal;
24558
+ const dispatch02 = (terminal || hasDispatchEvidence) && !vercelReread ? {
24467
24559
  note: deployLeg.note ?? deployLeg.error ?? `persisted rc deploy leg is ${deployLeg.state}`,
24468
24560
  deployStatus: legStatus,
24469
24561
  ...deployLeg.runId != null ? { runId: deployLeg.runId } : {},
@@ -24475,7 +24567,7 @@ Nothing was written. Inspect ${ledger.path} before retrying.`);
24475
24567
  ...deployLeg.event ? { event: deployLeg.event } : {},
24476
24568
  conclusion: legStatus
24477
24569
  }] } : {}
24478
- } : await dispatchDeploy(deps, ctx, "rc", "rc", deployModel, watch, (deps.now ?? Date.now)(), tagSha);
24570
+ } : await dispatchDeploy(deps, ctx, "rc", "rc", deployModel, watch, vercelReread ? rcPushedAt(persisted) : (deps.now ?? Date.now)(), tagSha, "report");
24479
24571
  const dispatch2 = terminal ? dispatch02 : await recordRcandDeployLegs(ledger, deps, anchors, deployModel, dispatch02, { strict: true });
24480
24572
  const ledgerReport2 = releaseLedgerReport(await ledger.load(anchors).catch(() => void 0), ledger.path);
24481
24573
  return {
@@ -24541,10 +24633,11 @@ ${spent.note}`);
24541
24633
  }
24542
24634
  await recordPhase(ledger, deps, anchors, "ordinaryCi", phaseEntry({ state: "complete", sha: tagSha, note: "re-verified the required-check wall live on this resume" }), { strict, landed: "the immutable rc tag on origin at the resumed SHA" });
24543
24635
  const autoRunSince = (deps.now ?? Date.now)();
24636
+ deps.warn?.("Release: Promoting the verified release candidate");
24544
24637
  await runGitPush(deps, ["push", "origin", `${tagSha}:refs/heads/rc`]);
24545
24638
  await recordPhase(ledger, deps, anchors, "promotion", phaseEntry({ state: "complete", sha: tagSha, note: `resumed: ${tagSha.slice(0, 12)} pushed to origin/rc (tag preserved, never re-cut)` }), { strict, landed: "the immutable rc tag and the re-proven required-check wall" });
24546
24639
  await recordPhase(ledger, deps, anchors, "githubRelease", phaseEntry({ state: "skipped", sha: tagSha, note: "rcand mints no GitHub Release \u2014 the rc tag is the candidate" }), { strict, landed: "the immutable rc tag, the green wall, and origin/rc" });
24547
- const dispatch0 = await dispatchDeploy(deps, ctx, "rc", "rc", deployModel, watch, autoRunSince, tagSha);
24640
+ const dispatch0 = await dispatchDeploy(deps, ctx, "rc", "rc", deployModel, watch, autoRunSince, tagSha, "report");
24548
24641
  const dispatch = await recordRcandDeployLegs(ledger, deps, anchors, deployModel, dispatch0, { strict });
24549
24642
  const ledgerReport = releaseLedgerReport(await ledger.load(anchors).catch(() => void 0), ledger.path);
24550
24643
  return {
@@ -24566,6 +24659,64 @@ ${spent.note}`);
24566
24659
  note: `reused immutable ${tag}; pushed its exact SHA to rc and dispatched the rc deploy`
24567
24660
  };
24568
24661
  }
24662
+ async function runRcandRetryDeploy(deps, options = {}) {
24663
+ return withTrainWriter(deps, "rcand --retry-deploy", () => runRcandRetryDeployLocked(deps, options));
24664
+ }
24665
+ async function runRcandRetryDeployLocked(deps, options) {
24666
+ const label = "rcand --retry-deploy";
24667
+ const ctx = await buildTrainApplyContext(deps);
24668
+ await requireCleanTree(deps);
24669
+ await runGitRemoteRead(deps, ["fetch", "origin", "--tags"]);
24670
+ const meta = requireProjectMetaForTrain(await loadProjectMeta(deps, ctx), ctx.repo);
24671
+ const deployModel = await preflight(deps, ctx, "rc", meta);
24672
+ if (!isCentralDispatchModel(deployModel)) {
24673
+ throw new Error(`${label}: the ${deployModel} rc deploy is the repo's own push-triggered workflow, not a dispatch \u2014 rerun that failed run (gh run rerun <run-id> --failed), then mmi-cli devops rcand --resume re-verifies it; nothing was written`);
24674
+ }
24675
+ const candidate = await activeRcandLedgerCandidate(deps, process.cwd());
24676
+ if (!candidate) {
24677
+ throw new Error(`${label}: no active rcand ledger whose rc tag is live on origin at its recorded SHA \u2014 there is no promoted candidate deploy to retry; nothing was written`);
24678
+ }
24679
+ const { tag, sha: tagSha } = candidate;
24680
+ const ledger = openReleaseLedgerWriter(process.cwd());
24681
+ const anchors = { repo: ctx.repo, tag, tagSha };
24682
+ let persisted;
24683
+ try {
24684
+ persisted = (await reverifyPersistedRunPhases(deps, ledger.path, anchors, { sha: tagSha, repo: ctx.repo })).ledger;
24685
+ } catch (e) {
24686
+ throw new Error(`${label}: ${e instanceof Error ? e.message : String(e)}
24687
+ Nothing was written. Inspect ${ledger.path} before retrying.`);
24688
+ }
24689
+ if (!persisted || !rcandRetryDeployShape(persisted)) {
24690
+ const open2 = persisted ? deriveReleasePhaseStatus(persisted).legs.filter((l) => l.state === "pending" || l.state === "failed") : [];
24691
+ throw new Error(
24692
+ `${label}: the ${tag} ledger has no failed rc deploy to retry on a promoted candidate (${open2.map((l) => `${l.phase}=${l.state}`).join(", ") || "phases-green"}) \u2014 finish an incomplete candidate with mmi-cli devops rcand --resume; nothing was written`
24693
+ );
24694
+ }
24695
+ if (!await probeAncestor(deps, tagSha, "origin/rc", `${label}: origin/rc ancestry`)) {
24696
+ throw new Error(`${label}: origin/rc does not contain ${tag} (${tagSha.slice(0, 12)}) \u2014 the candidate is not promoted; nothing was written`);
24697
+ }
24698
+ const d0 = await dispatchDeploy(deps, ctx, "rc", "rc", deployModel, options.watch ?? false, (deps.now ?? Date.now)(), tagSha, "report");
24699
+ const dispatch = await recordRcandDeployLegs(ledger, deps, anchors, deployModel, d0, { strict: true });
24700
+ const ledgerReport = releaseLedgerReport(await ledger.load(anchors).catch(() => void 0), ledger.path);
24701
+ return {
24702
+ ...ctx,
24703
+ command: "rcand-retry-deploy",
24704
+ stage: "rc",
24705
+ ref: "rc",
24706
+ tag,
24707
+ tagSha,
24708
+ deployModel,
24709
+ promoted: true,
24710
+ checks: persisted.phases.ordinaryCi.note ?? "required-check wall was completed before origin/rc promotion",
24711
+ dispatch: dispatch.note,
24712
+ runId: dispatch.runId,
24713
+ runUrl: dispatch.runUrl,
24714
+ workflowRuns: dispatch.workflowRuns,
24715
+ deployStatus: dispatch.deployStatus,
24716
+ ...ledgerReport ? { ledger: ledgerReport } : {},
24717
+ note: `re-dispatched the failed rc deploy of immutable ${tag} at ${tagSha.slice(0, 12)}; nothing re-cut, re-tagged or re-pushed`
24718
+ };
24719
+ }
24569
24720
  async function runReleaseResumePipeline(deps, options, prepared) {
24570
24721
  const watch = options.watch ?? false;
24571
24722
  const { ctx, meta, branchHints, directTrack, deployModel } = prepared;
@@ -24629,8 +24780,9 @@ Nothing was written. Inspect ${ledger.path} (or clear it only after proving the
24629
24780
  deps.warn?.("Release: Checking deployment and publishing progress");
24630
24781
  const historicalTargets = prodReleaseTargets(deployModel);
24631
24782
  const historicalRepo = releaseRunRepoFor(deployModel, ctx.repo);
24632
- let historicalRows = historicalTargets.length ? await listOwnWorkflowRuns(deps, historicalRepo, historicalTargets, tagSha) : [];
24633
- let historicalDeploy = historicalTargets.length ? aggregateWorkflowRuns(historicalRows) : "pending";
24783
+ const vercel = deployModel === "vercel";
24784
+ let historicalRows = vercel ? await listVercelDeployRows(deps, ctx.repo, tagSha).catch(() => []) : historicalTargets.length ? await listOwnWorkflowRuns(deps, historicalRepo, historicalTargets, tagSha) : [];
24785
+ let historicalDeploy = historicalTargets.length || vercel && historicalRows.length ? aggregateWorkflowRuns(historicalRows) : "pending";
24634
24786
  let historicalNote = `historical ${deployModel} deploy/publish status: ${historicalDeploy}`;
24635
24787
  if (historicalDeploy === "failure") {
24636
24788
  const recovered = await recoverFailedDispatchPhase(
@@ -24642,7 +24794,7 @@ Nothing was written. Inspect ${ledger.path} (or clear it only after proving the
24642
24794
  { repo: historicalRepo, sha: tagSha, watched: false, workflow: "historical release workflow" }
24643
24795
  );
24644
24796
  historicalRows = recovered.workflowRuns ?? historicalRows;
24645
- historicalDeploy = historicalTargets.length ? aggregateWorkflowRuns(historicalRows) : recovered.deployStatus;
24797
+ historicalDeploy = historicalTargets.length || vercel && historicalRows.length ? aggregateWorkflowRuns(historicalRows) : recovered.deployStatus;
24646
24798
  historicalNote = recovered.note;
24647
24799
  }
24648
24800
  const dispatch2 = await appendJervGatewayReleaseDeploy(deps, ctx.repo, tag, tagSha, {
@@ -24680,7 +24832,7 @@ Nothing was written. Inspect ${ledger.path} (or clear it only after proving the
24680
24832
  Object.assign(dispatch2, shippedDispatch);
24681
24833
  if (isJervHubRepo(ctx.repo)) steps2.push(dispatch2.note);
24682
24834
  const settledBySupersede = persisted?.phases.deploy.superseded === true || persisted?.phases.publish.superseded === true;
24683
- const announceNote2 = persisted && !shippedBefore && !settledBySupersede && (prodReleaseTargets(deployModel).length > 0 || companion !== null) ? await announceShippedRelease(
24835
+ const announceNote2 = persisted && !shippedBefore && !settledBySupersede && (prodReleaseTargets(deployModel).length > 0 || deployModel === "vercel" || companion !== null) ? await announceShippedRelease(
24684
24836
  deps,
24685
24837
  { repo: ctx.repo, tag, summaryFile: options.announceSummaryFile },
24686
24838
  deployModel,
@@ -25128,6 +25280,7 @@ async function runTrainApplyPipeline(mode, input) {
25128
25280
  if (!start.ok) throw new Error(`rcand refused before the rc tag was pushed: the durable phase ledger could not be started at ${ledger.path} (${start.error}) - nothing was tagged or pushed (#5987/#6068)`);
25129
25281
  ledgerAnchors2 = anchors2;
25130
25282
  }
25283
+ deps.warn?.("Release: Publishing the release candidate tag");
25131
25284
  tagPush = await ensureTagPushed(deps, tag2, rcSha);
25132
25285
  } catch (e) {
25133
25286
  if (ledger && ledgerAnchors2) {
@@ -25164,6 +25317,7 @@ ${spent.note}`), rcandRecovery(tag2));
25164
25317
  }
25165
25318
  await recordPhase(ledger, deps, anchors, "ordinaryCi", phaseEntry({ state: "complete", sha: rcSha, note: checks2 }), { landed: "the immutable rc tag pushed at " + rcSha.slice(0, 12) });
25166
25319
  const autoRunSince = (deps.now ?? Date.now)();
25320
+ deps.warn?.("Release: Promoting the verified release candidate");
25167
25321
  try {
25168
25322
  await runGitPush(deps, ["push", "origin", "rc"]);
25169
25323
  } catch (e) {
@@ -25172,7 +25326,7 @@ ${spent.note}`), rcandRecovery(tag2));
25172
25326
  }
25173
25327
  await recordPhase(ledger, deps, anchors, "promotion", phaseEntry({ state: "complete", sha: rcSha, note: `immutable ${tag2} pushed at ${rcSha.slice(0, 12)}; origin/rc fast-forwarded to it` }), { landed: "the immutable rc tag and the green required-check wall" });
25174
25328
  await recordPhase(ledger, deps, anchors, "githubRelease", phaseEntry({ state: "skipped", sha: rcSha, note: "rcand mints no GitHub Release \u2014 the rc tag is the candidate" }), { landed: "the immutable rc tag, the green wall, and origin/rc" });
25175
- const d0 = await dispatchDeploy(deps, ctx, "rc", "rc", deployModel2, watch, autoRunSince, rcSha);
25329
+ const d0 = await dispatchDeploy(deps, ctx, "rc", "rc", deployModel2, watch, autoRunSince, rcSha, "report");
25176
25330
  const d2 = await recordRcandDeployLegs(ledger, deps, anchors, deployModel2, d0);
25177
25331
  const rcLedgerReport = releaseLedgerReport(await ledger?.load(anchors).catch(() => void 0), ledger?.path ?? "(no ledger)");
25178
25332
  return { ...ctx, command, stage: "rc", ref: "rc", tag: tag2, bumpIntent, deployModel: deployModel2, promoted: true, checks: checks2, resumeNote, dispatch: d2.note, runId: d2.runId, runUrl: d2.runUrl, workflowRuns: d2.workflowRuns, deployStatus: d2.deployStatus, ...rcLedgerReport ? { ledger: rcLedgerReport } : {} };
@@ -25462,7 +25616,7 @@ async function runTrainApplyLocked(command, deps, options = {}) {
25462
25616
  if (directTrack) {
25463
25617
  throw new Error("direct-track repos release straight to main (no rc); run `mmi-cli devops release --apply` from development instead of rcand");
25464
25618
  }
25465
- return runTrainApplyPipeline("rcand", pipelineInput);
25619
+ return withCheckoutRestoredAfterRelease(deps, await runTrainApplyPipeline("rcand", pipelineInput), "development");
25466
25620
  }
25467
25621
  if (directTrack) {
25468
25622
  return withCheckoutRestoredAfterRelease(
@@ -38194,8 +38348,9 @@ function registerStageCommands(program3) {
38194
38348
  const branch = await gitOut(["rev-parse", "--abbrev-ref", "HEAD"]);
38195
38349
  const repo = Array.isArray(project2.repos) && typeof project2.repos[0] === "string" ? project2.repos[0] : await repoSlug();
38196
38350
  try {
38351
+ const sha = await gitOut(["rev-parse", `origin/${branch}`]).catch(() => gitOut(["rev-parse", "HEAD"]));
38197
38352
  const gh = defaultGitHubClient();
38198
- const deployment = await readVercelDeployment((path2) => gh.rest("GET", path2), repo, { ref: branch }, "Preview");
38353
+ const deployment = await readVercelDeployment((path2) => gh.rest("GET", path2), repo, sha, "preview");
38199
38354
  return { branch, ...deployment ? { deployment } : {} };
38200
38355
  } catch (e) {
38201
38356
  return { branch, error: e.message };
@@ -38636,7 +38791,7 @@ function renderVerifySecrets(body) {
38636
38791
 
38637
38792
  // src/command-register-collaboration.ts
38638
38793
  var import_node_child_process15 = require("node:child_process");
38639
- var import_node_fs42 = require("node:fs");
38794
+ var import_node_fs43 = require("node:fs");
38640
38795
  var import_promises7 = require("node:fs/promises");
38641
38796
  init_clean_exit();
38642
38797
  init_cli_shared();
@@ -38660,7 +38815,7 @@ function spawnDetachedSelf(args, deps, opts = {}) {
38660
38815
  }
38661
38816
 
38662
38817
  // src/command-register-collaboration.ts
38663
- var import_node_path40 = require("node:path");
38818
+ var import_node_path41 = require("node:path");
38664
38819
 
38665
38820
  // src/attach-to-project.ts
38666
38821
  async function attachViaHubApp(cfg, contentNodeId) {
@@ -39744,9 +39899,9 @@ function changedFilesSince(base, cwd) {
39744
39899
  const raw = git2(["diff", "--name-status", "-M", base, "--"], cwd).trim();
39745
39900
  const changed = raw ? raw.split("\n").map((line) => {
39746
39901
  const parts = line.split(" ");
39747
- const norm = (p) => p.replace(/\\/g, "/");
39748
- const row = { status: parts[0][0], path: norm(parts[parts.length - 1]) };
39749
- if (parts.length > 2) row.from = norm(parts[1]);
39902
+ const norm2 = (p) => p.replace(/\\/g, "/");
39903
+ const row = { status: parts[0][0], path: norm2(parts[parts.length - 1]) };
39904
+ if (parts.length > 2) row.from = norm2(parts[1]);
39750
39905
  return row;
39751
39906
  }) : [];
39752
39907
  const untracked = git2(["ls-files", "--others", "--exclude-standard", "-z"], cwd).split("\0").filter(Boolean).map((path2) => ({ status: "A", path: path2.replace(/\\/g, "/") }));
@@ -40809,6 +40964,10 @@ async function prCreateClaimRefusal(body, repoOption, deps = {}) {
40809
40964
  return void 0;
40810
40965
  }
40811
40966
 
40967
+ // src/worktree-residue-sweep.ts
40968
+ var import_node_fs42 = require("node:fs");
40969
+ var import_node_path40 = require("node:path");
40970
+
40812
40971
  // src/worktree-merge-cleanup.ts
40813
40972
  var import_node_fs41 = require("node:fs");
40814
40973
  var import_node_path39 = require("node:path");
@@ -41654,6 +41813,122 @@ function renderPrMergeCleanupLines(cleanup) {
41654
41813
  return lines2;
41655
41814
  }
41656
41815
 
41816
+ // src/worktree-residue-sweep.ts
41817
+ var RESIDUE_LEDGER_FILE = "mmi-worktree-residue.json";
41818
+ function norm(p) {
41819
+ const s = p.replace(/\\/g, "/").replace(/\/+$/, "");
41820
+ return process.platform === "win32" ? s.toLowerCase() : s;
41821
+ }
41822
+ function residueLedgerPath(gitCommonDir) {
41823
+ return (0, import_node_path40.join)(gitCommonDir, RESIDUE_LEDGER_FILE);
41824
+ }
41825
+ function readPaths(file, deps) {
41826
+ try {
41827
+ const parsed = JSON.parse((deps.readLedger ?? ((f) => (0, import_node_fs42.readFileSync)(f, "utf8")))(file));
41828
+ return Array.isArray(parsed) ? parsed.filter((p) => typeof p === "string") : [];
41829
+ } catch {
41830
+ return [];
41831
+ }
41832
+ }
41833
+ function writePaths(file, paths, deps) {
41834
+ try {
41835
+ const write = deps.writeLedger ?? ((f, t) => {
41836
+ (0, import_node_fs42.mkdirSync)((0, import_node_path40.dirname)(f), { recursive: true });
41837
+ (0, import_node_fs42.writeFileSync)(f, t, "utf8");
41838
+ });
41839
+ write(file, `${JSON.stringify([...new Set(paths)], null, 1)}
41840
+ `);
41841
+ } catch {
41842
+ }
41843
+ }
41844
+ function recordResidue(gitCommonDir, path2, deps = {}) {
41845
+ const file = residueLedgerPath(gitCommonDir);
41846
+ const paths = readPaths(file, deps);
41847
+ if (!paths.some((p) => norm(p) === norm(path2))) paths.push(path2);
41848
+ writePaths(file, paths, deps);
41849
+ }
41850
+ async function sweepWorktreeResidue(gitCommonDir, registered, deps = {}) {
41851
+ const exists = deps.exists ?? import_node_fs42.existsSync;
41852
+ const readDir = deps.readDir ?? ((p) => (0, import_node_fs42.readdirSync)(p));
41853
+ const isDir = deps.isDir ?? ((p) => {
41854
+ try {
41855
+ return (0, import_node_fs42.statSync)(p).isDirectory();
41856
+ } catch {
41857
+ return false;
41858
+ }
41859
+ });
41860
+ const removeRecorded = deps.removeRecorded ?? ((p) => removeResidueDirectory(p));
41861
+ const removeEmpty = deps.removeEmpty ?? ((p) => (0, import_node_fs42.rmdirSync)(p));
41862
+ const report = { swept: [], kept: [], dropped: [], emptyRemoved: [], unrecorded: [] };
41863
+ const live = new Set(registered.map(norm));
41864
+ const primary = registered[0] ? norm(registered[0]) : void 0;
41865
+ const file = residueLedgerPath(gitCommonDir);
41866
+ const recorded = readPaths(file, deps);
41867
+ const remaining = [];
41868
+ for (const path2 of recorded) {
41869
+ const key = norm(path2);
41870
+ if (live.has(key) || key === primary) {
41871
+ report.dropped.push(path2);
41872
+ continue;
41873
+ }
41874
+ if (!exists(path2)) {
41875
+ report.dropped.push(path2);
41876
+ continue;
41877
+ }
41878
+ const removed = await removeRecorded(path2);
41879
+ if (removed.ok && !exists(path2)) report.swept.push(path2);
41880
+ else {
41881
+ report.kept.push({ path: path2, error: removed.ok ? "directory remains" : removed.error });
41882
+ remaining.push(path2);
41883
+ }
41884
+ }
41885
+ if (recorded.length) writePaths(file, remaining, deps);
41886
+ const roots = /* @__PURE__ */ new Set();
41887
+ for (const p of [...registered.slice(1), ...recorded]) roots.add((0, import_node_path40.dirname)(p));
41888
+ const keptKeys = new Set(remaining.map(norm));
41889
+ for (const root of roots) {
41890
+ if (!isDir(root) || live.has(norm(root)) || norm(root) === primary) continue;
41891
+ let children;
41892
+ try {
41893
+ children = readDir(root);
41894
+ } catch {
41895
+ continue;
41896
+ }
41897
+ for (const name of children) {
41898
+ const child2 = (0, import_node_path40.join)(root, name);
41899
+ const key = norm(child2);
41900
+ if (live.has(key) || key === primary || keptKeys.has(key) || !isDir(child2)) continue;
41901
+ let entries;
41902
+ try {
41903
+ entries = readDir(child2);
41904
+ } catch {
41905
+ continue;
41906
+ }
41907
+ if (entries.length === 0) {
41908
+ try {
41909
+ removeEmpty(child2);
41910
+ report.emptyRemoved.push(child2);
41911
+ } catch {
41912
+ }
41913
+ } else {
41914
+ report.unrecorded.push(child2);
41915
+ }
41916
+ }
41917
+ }
41918
+ return report;
41919
+ }
41920
+ function renderResidueSweepLines(report) {
41921
+ if (!report) return [];
41922
+ const lines2 = [];
41923
+ const cleaned = report.swept.length + report.emptyRemoved.length;
41924
+ if (cleaned) lines2.push(`pr merge: removed ${cleaned} leftover worktree folder(s) from earlier merges`);
41925
+ for (const k of report.kept) lines2.push(`pr merge: leftover folder still locked, retried on the next merge \u2014 ${k.path}`);
41926
+ if (report.unrecorded.length) {
41927
+ lines2.push(`pr merge: ${report.unrecorded.length} unregistered folder(s) with content under the worktree root were left untouched (not recorded by a merge): ${report.unrecorded.join(", ")}`);
41928
+ }
41929
+ return lines2;
41930
+ }
41931
+
41657
41932
  // src/pr-deploy-dev.ts
41658
41933
  function slugOfRepo(repo) {
41659
41934
  return repo.split("/")[1]?.toLowerCase() ?? "";
@@ -41688,10 +41963,10 @@ function argvWantsJson2() {
41688
41963
  return process.argv.some((a) => a === "--json" || a.startsWith("--json="));
41689
41964
  }
41690
41965
  function hubRoot() {
41691
- const fromPkg = (0, import_node_path40.join)(__dirname, "..", "..");
41966
+ const fromPkg = (0, import_node_path41.join)(__dirname, "..", "..");
41692
41967
  const marker = "skills/bootstrap/seeds/manifest.json";
41693
- if ((0, import_node_fs42.existsSync)((0, import_node_path40.join)(fromPkg, marker))) return fromPkg;
41694
- if ((0, import_node_fs42.existsSync)((0, import_node_path40.join)(process.cwd(), marker))) return process.cwd();
41968
+ if ((0, import_node_fs43.existsSync)((0, import_node_path41.join)(fromPkg, marker))) return fromPkg;
41969
+ if ((0, import_node_fs43.existsSync)((0, import_node_path41.join)(process.cwd(), marker))) return process.cwd();
41695
41970
  return null;
41696
41971
  }
41697
41972
  function ciAuditDeps() {
@@ -41703,8 +41978,8 @@ function ciAuditDeps() {
41703
41978
  getProjectMeta: async (slug) => fetchProjectBySlug(slug, registryClientDeps(await cfgPromise)),
41704
41979
  readSeedFile: (path2) => {
41705
41980
  if (!root) return null;
41706
- const fullPath = (0, import_node_path40.join)(root, path2);
41707
- return (0, import_node_fs42.existsSync)(fullPath) ? (0, import_node_fs42.readFileSync)(fullPath, "utf8") : null;
41981
+ const fullPath = (0, import_node_path41.join)(root, path2);
41982
+ return (0, import_node_fs43.existsSync)(fullPath) ? (0, import_node_fs43.readFileSync)(fullPath, "utf8") : null;
41708
41983
  }
41709
41984
  };
41710
41985
  }
@@ -42429,11 +42704,11 @@ ${list}`);
42429
42704
  }
42430
42705
  });
42431
42706
  async function listCiWorkflowPaths(cwd = process.cwd()) {
42432
- const wfDir = (0, import_node_path40.join)(cwd, ".github", "workflows");
42433
- if (!(0, import_node_fs42.existsSync)(wfDir)) return [];
42434
- return (0, import_node_fs42.readdirSync)(wfDir).filter((name) => /\.(ya?ml)$/i.test(name)).filter((name) => {
42707
+ const wfDir = (0, import_node_path41.join)(cwd, ".github", "workflows");
42708
+ if (!(0, import_node_fs43.existsSync)(wfDir)) return [];
42709
+ return (0, import_node_fs43.readdirSync)(wfDir).filter((name) => /\.(ya?ml)$/i.test(name)).filter((name) => {
42435
42710
  try {
42436
- return workflowReportsPrChecks((0, import_node_fs42.readFileSync)((0, import_node_path40.join)(wfDir, name), "utf8"));
42711
+ return workflowReportsPrChecks((0, import_node_fs43.readFileSync)((0, import_node_path41.join)(wfDir, name), "utf8"));
42437
42712
  } catch {
42438
42713
  return true;
42439
42714
  }
@@ -42629,7 +42904,7 @@ ${list}`);
42629
42904
  jsonParity(pr.command("review-verdict <number>").description("post an explicit review verdict comment: computes the PR diff patch-id and head sha, renders the v1 contract body, and posts it as a PR comment").requiredOption("--repo <owner/repo>", "target repo").requiredOption("--verdict <verdict>", `one of ${REVIEW_VERDICTS.join("|")}`).requiredOption("--scope <text>", "one line: what was reviewed").requiredOption("--risk <text>", "one line: the residual risk").option("--unverified <text>", "a claim the reviewer could not verify (repeatable)", (v, acc) => [...acc, v], []).requiredOption("--reviewer <id>", "reviewer seat or model id").option("--findings-file <path>", "Markdown findings appended after the JSON block")).action(async (number, o) => {
42630
42905
  if (!isReviewVerdict(o.verdict)) return fail(`pr review-verdict: --verdict must be one of ${REVIEW_VERDICTS.join("|")} (got ${o.verdict})`);
42631
42906
  const verdict = o.verdict;
42632
- const findings = o.findingsFile ? (0, import_node_fs42.readFileSync)(o.findingsFile, "utf8") : void 0;
42907
+ const findings = o.findingsFile ? (0, import_node_fs43.readFileSync)(o.findingsFile, "utf8") : void 0;
42633
42908
  let patch;
42634
42909
  let head;
42635
42910
  try {
@@ -42903,7 +43178,7 @@ ${list}`);
42903
43178
  const mergeSquashBody = squashBodyTextForMerge(
42904
43179
  closingGuardInput,
42905
43180
  method === "--squash",
42906
- o.squashBodyFile ? (0, import_node_fs42.readFileSync)(o.squashBodyFile, "utf8") : void 0
43181
+ o.squashBodyFile ? (0, import_node_fs43.readFileSync)(o.squashBodyFile, "utf8") : void 0
42907
43182
  );
42908
43183
  if (!o.squashBodyFile) warnSquashBodyClosingStrip("pr merge", closingGuardInput, mergeSquashBody);
42909
43184
  const closingGuardVerdict = evaluateClosingGuard(closingGuardInput, {
@@ -42935,7 +43210,7 @@ ${list}`);
42935
43210
  const remote = foreignCwd ? `https://github.com/${targetRepo2}.git` : "origin";
42936
43211
  let foreignCheckout;
42937
43212
  if (foreignCwd) {
42938
- const sibling = (0, import_node_path40.join)((0, import_node_path40.dirname)(beforeWorktrees[0]?.path || startingPath || process.cwd()), targetRepo2.split("/")[1]);
43213
+ const sibling = (0, import_node_path41.join)((0, import_node_path41.dirname)(beforeWorktrees[0]?.path || startingPath || process.cwd()), targetRepo2.split("/")[1]);
42939
43214
  const siblingRepo = repoFromRemoteUrl(await gitOut(["-C", sibling, "remote", "get-url", "origin"]).catch(() => ""));
42940
43215
  if (siblingRepo?.toLowerCase() === targetRepo2.toLowerCase()) {
42941
43216
  const siblingWorktrees = await execFileP("git", ["-C", sibling, "worktree", "list", "--porcelain"], { timeout: GIT_TIMEOUT_MS }).then((r) => parseGitWorktreePorcelain(r.stdout)).catch(() => void 0);
@@ -43033,7 +43308,7 @@ ${list}`);
43033
43308
  }
43034
43309
  if (!repoForPostCleanup) throw e;
43035
43310
  console.warn(`pr merge: gh GraphQL rate-limited \u2014 merging PR #${number} via REST PUT instead (#4588).`);
43036
- const commitMessage = bodyFile ? (0, import_node_fs42.readFileSync)(bodyFile, "utf8") : void 0;
43311
+ const commitMessage = bodyFile ? (0, import_node_fs43.readFileSync)(bodyFile, "utf8") : void 0;
43037
43312
  await defaultGitHubClient().rest("PUT", `repos/${repoForPostCleanup}/pulls/${number}/merge`, {
43038
43313
  body: { merge_method: method.slice(2), ...commitMessage ? { commit_message: commitMessage } : {} },
43039
43314
  timeoutMs: GH_MUTATION_TIMEOUT_MS
@@ -43140,6 +43415,15 @@ ${list}`);
43140
43415
  return;
43141
43416
  }
43142
43417
  const primaryRoot = beforeWorktrees[0]?.path ?? (startingPath || process.cwd());
43418
+ let gitCommonDir;
43419
+ let residueSweep;
43420
+ if (!(foreignCwd && !foreignCheckout)) {
43421
+ try {
43422
+ gitCommonDir = (0, import_node_path41.resolve)(primaryRoot, (await execFileP("git", ["-C", primaryRoot, "rev-parse", "--git-common-dir"], { timeout: GIT_TIMEOUT_MS })).stdout.trim());
43423
+ residueSweep = await sweepWorktreeResidue(gitCommonDir, beforeWorktrees.map((w) => w.path));
43424
+ } catch {
43425
+ }
43426
+ }
43143
43427
  let localCleanup;
43144
43428
  if (foreignCwd && !foreignCheckout) {
43145
43429
  localCleanup = { branch: headRef, localBranch: { name: headRef, status: "not-attempted", reason: "skipped-foreign-cwd" } };
@@ -43153,7 +43437,7 @@ ${list}`);
43153
43437
  preserveWorktree: o.preserveWorktree,
43154
43438
  gcAcknowledged: o.gc,
43155
43439
  expectedHeadOid: headRefOid,
43156
- pathExists: (p) => (0, import_node_fs42.existsSync)(p),
43440
+ pathExists: (p) => (0, import_node_fs43.existsSync)(p),
43157
43441
  // #5899: pin cleanup git calls to the main checkout — the task worktree this process may be
43158
43442
  // standing in is removed mid-cleanup, so a cwd-relative invocation fails with
43159
43443
  // 'fatal: not a git repository' and leaves a spurious partial-cleanup exit.
@@ -43184,6 +43468,7 @@ ${list}`);
43184
43468
  remote
43185
43469
  });
43186
43470
  const worktree = localCleanup?.worktree;
43471
+ if (gitCommonDir && worktree?.path && worktree.residue === "left") recordResidue(gitCommonDir, worktree.path);
43187
43472
  const worktreePartial = isPrMergeWorktreePartial(localCleanup) && worktree?.path ? {
43188
43473
  kind: "worktree-directory",
43189
43474
  path: worktree.path,
@@ -43248,6 +43533,7 @@ ${list}`);
43248
43533
  ...partialCleanup.length ? { cleanupStatus: "partial", partialCleanup } : {},
43249
43534
  ...localCleanup?.worktree ? { worktree: localCleanup.worktree } : {},
43250
43535
  ...localCleanup?.localBranch ? { localBranch: localCleanup.localBranch } : {},
43536
+ ...residueSweep && (residueSweep.swept.length || residueSweep.emptyRemoved.length || residueSweep.kept.length || residueSweep.unrecorded.length) ? { residueSweep } : {},
43251
43537
  // `boardAdvance` keeps its published array shape; `boardAdvanceStatus` is the field a caller reads to
43252
43538
  // tell "merged clean" from "merged, but the board did not follow" without guessing from the exit code.
43253
43539
  ...boardAdvance.entries.length ? { boardAdvance: boardAdvance.entries } : {},
@@ -43257,6 +43543,7 @@ ${list}`);
43257
43543
  ...devDeploy ? { devDeploy: devDeploy.ok ? { dispatched: true } : { dispatched: false, manual: devDeployManual } } : {}
43258
43544
  }));
43259
43545
  for (const line of renderPrMergeCleanupLines(localCleanup)) console.warn(line);
43546
+ for (const line of renderResidueSweepLines(residueSweep)) console.warn(line);
43260
43547
  if (devDeploy && !devDeploy.ok) {
43261
43548
  console.error(`pr merge: ${devDeployManual} (${devDeploy.detail})`);
43262
43549
  }
@@ -43274,7 +43561,7 @@ ${list}`);
43274
43561
  }
43275
43562
 
43276
43563
  // src/command-register-developer.ts
43277
- var import_node_fs47 = require("node:fs");
43564
+ var import_node_fs48 = require("node:fs");
43278
43565
  init_clean_exit();
43279
43566
  init_cli_shared();
43280
43567
  init_error_codes();
@@ -43307,7 +43594,7 @@ async function resolveWhoami(deps) {
43307
43594
  }
43308
43595
 
43309
43596
  // src/command-register-developer.ts
43310
- var import_node_path45 = require("node:path");
43597
+ var import_node_path46 = require("node:path");
43311
43598
  init_house_map();
43312
43599
  init_hub_url();
43313
43600
 
@@ -43340,7 +43627,7 @@ async function executeWaveLand(plan, deps) {
43340
43627
  }
43341
43628
 
43342
43629
  // src/box-commands.ts
43343
- var import_node_fs43 = require("node:fs");
43630
+ var import_node_fs44 = require("node:fs");
43344
43631
  init_clean_exit();
43345
43632
 
43346
43633
  // src/box.ts
@@ -43552,7 +43839,7 @@ function registerBoxCommands(program3) {
43552
43839
  }
43553
43840
  const remote = command && command.length ? command.join(" ") : void 0;
43554
43841
  const wroteScript = o.ssh && o.script ? o.script : null;
43555
- if (wroteScript) (0, import_node_fs43.writeFileSync)(wroteScript, sshRecipeScript(found, remote), "utf8");
43842
+ if (wroteScript) (0, import_node_fs44.writeFileSync)(wroteScript, sshRecipeScript(found, remote), "utf8");
43556
43843
  if (o.json) {
43557
43844
  console.log(JSON.stringify({
43558
43845
  box: found,
@@ -43574,19 +43861,19 @@ ${SSH_RECIPE_AGENT_NOTE}`);
43574
43861
  // src/dist-drift.ts
43575
43862
  var import_node_child_process16 = require("node:child_process");
43576
43863
  var import_node_crypto13 = require("node:crypto");
43577
- var import_node_fs45 = require("node:fs");
43864
+ var import_node_fs46 = require("node:fs");
43578
43865
  var import_node_os21 = require("node:os");
43579
- var import_node_path42 = require("node:path");
43866
+ var import_node_path43 = require("node:path");
43580
43867
 
43581
43868
  // ../scripts/distribution-digest.mjs
43582
43869
  var import_node_crypto12 = require("node:crypto");
43583
- var import_node_fs44 = require("node:fs");
43584
- var import_node_path41 = require("node:path");
43870
+ var import_node_fs45 = require("node:fs");
43871
+ var import_node_path42 = require("node:path");
43585
43872
  var slash = (value) => value.replaceAll("\\", "/");
43586
43873
  function repoPath(root, declaredPath, label) {
43587
- const absoluteRoot = (0, import_node_path41.resolve)(root);
43588
- const target = (0, import_node_path41.resolve)(root, declaredPath);
43589
- if (target !== absoluteRoot && !target.startsWith(`${absoluteRoot}${import_node_path41.sep}`)) {
43874
+ const absoluteRoot = (0, import_node_path42.resolve)(root);
43875
+ const target = (0, import_node_path42.resolve)(root, declaredPath);
43876
+ if (target !== absoluteRoot && !target.startsWith(`${absoluteRoot}${import_node_path42.sep}`)) {
43590
43877
  throw new Error(`${label} ${declaredPath} escapes the repository root`);
43591
43878
  }
43592
43879
  return target;
@@ -43594,7 +43881,7 @@ function repoPath(root, declaredPath, label) {
43594
43881
  function digestFiles(files) {
43595
43882
  const hash = (0, import_node_crypto12.createHash)("sha256");
43596
43883
  for (const file of [...files].sort((a, b) => a.relative.localeCompare(b.relative))) {
43597
- const content = file.stat.isSymbolicLink() ? Buffer.from((0, import_node_fs44.readlinkSync)(file.absolute), "utf8") : (0, import_node_fs44.readFileSync)(file.absolute);
43884
+ const content = file.stat.isSymbolicLink() ? Buffer.from((0, import_node_fs45.readlinkSync)(file.absolute), "utf8") : (0, import_node_fs45.readFileSync)(file.absolute);
43598
43885
  hash.update(file.relative, "utf8");
43599
43886
  hash.update("\0");
43600
43887
  hash.update(file.stat.isSymbolicLink() ? "symlink" : "file", "utf8");
@@ -43609,8 +43896,8 @@ function digestFiles(files) {
43609
43896
  function digestPackedFiles(packageRoot, packedFiles) {
43610
43897
  return digestFiles(packedFiles.map((path2) => {
43611
43898
  const absolute = repoPath(packageRoot, path2, "packed artifact identity path");
43612
- if (!(0, import_node_fs44.existsSync)(absolute)) throw new Error(`packed artifact identity path ${path2} does not exist`);
43613
- return { absolute, relative: slash(path2), stat: (0, import_node_fs44.lstatSync)(absolute) };
43899
+ if (!(0, import_node_fs45.existsSync)(absolute)) throw new Error(`packed artifact identity path ${path2} does not exist`);
43900
+ return { absolute, relative: slash(path2), stat: (0, import_node_fs45.lstatSync)(absolute) };
43614
43901
  }));
43615
43902
  }
43616
43903
 
@@ -43694,13 +43981,13 @@ function renderDistDriftReceipt(receipt) {
43694
43981
  return lines2;
43695
43982
  }
43696
43983
  function readOrNull(path2) {
43697
- return (0, import_node_fs45.existsSync)(path2) ? (0, import_node_fs45.readFileSync)(path2) : null;
43984
+ return (0, import_node_fs46.existsSync)(path2) ? (0, import_node_fs46.readFileSync)(path2) : null;
43698
43985
  }
43699
43986
  function walkFiles(root) {
43700
43987
  const files = [];
43701
43988
  const walk2 = (directory) => {
43702
- for (const entry of (0, import_node_fs45.readdirSync)(directory, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
43703
- const child2 = (0, import_node_path42.join)(directory, entry.name);
43989
+ for (const entry of (0, import_node_fs46.readdirSync)(directory, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
43990
+ const child2 = (0, import_node_path43.join)(directory, entry.name);
43704
43991
  if (entry.isDirectory()) walk2(child2);
43705
43992
  else files.push(child2);
43706
43993
  }
@@ -43710,10 +43997,10 @@ function walkFiles(root) {
43710
43997
  }
43711
43998
  function bomPathFor(root) {
43712
43999
  try {
43713
- const registry2 = JSON.parse((0, import_node_fs45.readFileSync)((0, import_node_path42.join)(root, "surfaces.json"), "utf8"));
43714
- return (0, import_node_path42.join)(root, registry2?.sharedAgentCore?.releaseMetadata?.bomPath ?? "distribution-bom.json");
44000
+ const registry2 = JSON.parse((0, import_node_fs46.readFileSync)((0, import_node_path43.join)(root, "surfaces.json"), "utf8"));
44001
+ return (0, import_node_path43.join)(root, registry2?.sharedAgentCore?.releaseMetadata?.bomPath ?? "distribution-bom.json");
43715
44002
  } catch {
43716
- return (0, import_node_path42.join)(root, "distribution-bom.json");
44003
+ return (0, import_node_path43.join)(root, "distribution-bom.json");
43717
44004
  }
43718
44005
  }
43719
44006
  function rebuildTo(packageRoot, outDir) {
@@ -43726,35 +44013,35 @@ function rebuildTo(packageRoot, outDir) {
43726
44013
  });
43727
44014
  }
43728
44015
  function runDistStatus(root) {
43729
- const stage = (0, import_node_fs45.mkdtempSync)((0, import_node_path42.join)((0, import_node_os21.tmpdir)(), "mmi-dist-drift-"));
44016
+ const stage = (0, import_node_fs46.mkdtempSync)((0, import_node_path43.join)((0, import_node_os21.tmpdir)(), "mmi-dist-drift-"));
43730
44017
  let overlayCount = 0;
43731
44018
  try {
43732
- const cliOut = (0, import_node_path42.join)(stage, "cli-dist");
43733
- const hubOut = (0, import_node_path42.join)(stage, "hub-dist");
43734
- rebuildTo((0, import_node_path42.join)(root, "cli"), cliOut);
43735
- rebuildTo((0, import_node_path42.join)(root, "updater"), hubOut);
44019
+ const cliOut = (0, import_node_path43.join)(stage, "cli-dist");
44020
+ const hubOut = (0, import_node_path43.join)(stage, "hub-dist");
44021
+ rebuildTo((0, import_node_path43.join)(root, "cli"), cliOut);
44022
+ rebuildTo((0, import_node_path43.join)(root, "updater"), hubOut);
43736
44023
  const outDirFor = (packageDir) => packageDir === "cli" ? cliOut : hubOut;
43737
44024
  const rebuilt = (path2) => {
43738
44025
  const spec = DIST_ARTIFACTS.find((entry) => entry.path === path2);
43739
- return spec ? readOrNull((0, import_node_path42.join)(outDirFor(spec.packageDir), spec.output)) : null;
44026
+ return spec ? readOrNull((0, import_node_path43.join)(outDirFor(spec.packageDir), spec.output)) : null;
43740
44027
  };
43741
- const committed = (path2) => readOrNull((0, import_node_path42.join)(root, path2));
43742
- const tree = (path2) => readOrNull((0, import_node_path42.join)(root, path2));
43743
- const distRoot = (0, import_node_path42.join)(root, "cli", "dist");
43744
- const distTree = () => walkFiles(distRoot).map((absolute) => `cli/dist/${(0, import_node_path42.relative)(distRoot, absolute).replaceAll("\\", "/")}`);
43745
- const bom = JSON.parse((0, import_node_fs45.readFileSync)(bomPathFor(root), "utf8"));
44028
+ const committed = (path2) => readOrNull((0, import_node_path43.join)(root, path2));
44029
+ const tree = (path2) => readOrNull((0, import_node_path43.join)(root, path2));
44030
+ const distRoot = (0, import_node_path43.join)(root, "cli", "dist");
44031
+ const distTree = () => walkFiles(distRoot).map((absolute) => `cli/dist/${(0, import_node_path43.relative)(distRoot, absolute).replaceAll("\\", "/")}`);
44032
+ const bom = JSON.parse((0, import_node_fs46.readFileSync)(bomPathFor(root), "utf8"));
43746
44033
  const digest = (entries) => {
43747
- const overlay = (0, import_node_path42.join)(stage, `overlay-${overlayCount++}`);
44034
+ const overlay = (0, import_node_path43.join)(stage, `overlay-${overlayCount++}`);
43748
44035
  for (const entry of entries) {
43749
- const target = (0, import_node_path42.join)(overlay, entry.path);
43750
- (0, import_node_fs45.mkdirSync)((0, import_node_path42.dirname)(target), { recursive: true });
43751
- (0, import_node_fs45.writeFileSync)(target, entry.bytes);
44036
+ const target = (0, import_node_path43.join)(overlay, entry.path);
44037
+ (0, import_node_fs46.mkdirSync)((0, import_node_path43.dirname)(target), { recursive: true });
44038
+ (0, import_node_fs46.writeFileSync)(target, entry.bytes);
43752
44039
  }
43753
44040
  return digestPackedFiles(overlay, entries.map((entry) => entry.path));
43754
44041
  };
43755
44042
  return computeDistDriftReceipt({ committed, tree, rebuilt, distTree, bom, digest });
43756
44043
  } finally {
43757
- (0, import_node_fs45.rmSync)(stage, { recursive: true, force: true });
44044
+ (0, import_node_fs46.rmSync)(stage, { recursive: true, force: true });
43758
44045
  }
43759
44046
  }
43760
44047
 
@@ -43833,7 +44120,7 @@ init_hub_auth();
43833
44120
 
43834
44121
  // src/schedules-lift-command.ts
43835
44122
  var import_promises8 = require("node:fs/promises");
43836
- var import_node_path43 = require("node:path");
44123
+ var import_node_path44 = require("node:path");
43837
44124
  init_clean_exit();
43838
44125
  init_cli_shared();
43839
44126
  var DEFAULT_WORKFLOWS_DIR = ".github/workflows";
@@ -43862,7 +44149,7 @@ async function readWorkflowFiles(dir) {
43862
44149
  const files = [];
43863
44150
  for (const name of names.sort()) {
43864
44151
  if (!/\.ya?ml$/.test(name)) continue;
43865
- files.push({ path: `.github/workflows/${name}`, text: await (0, import_promises8.readFile)((0, import_node_path43.join)(dir, name), "utf8") });
44152
+ files.push({ path: `.github/workflows/${name}`, text: await (0, import_promises8.readFile)((0, import_node_path44.join)(dir, name), "utf8") });
43866
44153
  }
43867
44154
  return files;
43868
44155
  }
@@ -43945,8 +44232,8 @@ function registerSchedulesLiftCommand(program3, deps = {}) {
43945
44232
 
43946
44233
  // src/spawn-policy-core.ts
43947
44234
  var import_node_child_process17 = require("node:child_process");
43948
- var import_node_fs46 = require("node:fs");
43949
- var import_node_path44 = require("node:path");
44235
+ var import_node_fs47 = require("node:fs");
44236
+ var import_node_path45 = require("node:path");
43950
44237
  var SPAWNERS = ["spawn", "spawnSync", "exec", "execSync", "execFile", "execFileSync"];
43951
44238
  var CALL_SOURCE = String.raw`(^|[^.\w$])(${SPAWNERS.join("|")})\s*\(`;
43952
44239
  var SOURCE_EXT = /\.(ts|mts|cts|js|mjs|cjs)$/;
@@ -44032,7 +44319,7 @@ function runSpawnPolicy(root) {
44032
44319
  for (const file of files) {
44033
44320
  let raw;
44034
44321
  try {
44035
- raw = (0, import_node_fs46.readFileSync)((0, import_node_path44.join)(root, file), "utf8");
44322
+ raw = (0, import_node_fs47.readFileSync)((0, import_node_path45.join)(root, file), "utf8");
44036
44323
  } catch {
44037
44324
  continue;
44038
44325
  }
@@ -44052,19 +44339,19 @@ function runSpawnPolicy(root) {
44052
44339
  function registerDeveloperCommands(program3) {
44053
44340
  const rules = program3.command("rules").description("org-managed .gitignore delivery");
44054
44341
  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) => {
44055
- const path2 = (0, import_node_path45.join)(process.cwd(), ".gitignore");
44056
- const current = (0, import_node_fs47.existsSync)(path2) ? (0, import_node_fs47.readFileSync)(path2, "utf8") : null;
44342
+ const path2 = (0, import_node_path46.join)(process.cwd(), ".gitignore");
44343
+ const current = (0, import_node_fs48.existsSync)(path2) ? (0, import_node_fs48.readFileSync)(path2, "utf8") : null;
44057
44344
  const plan = planManagedGitignore(current);
44058
44345
  const drift = [...plan.added.map((l) => `+${l}`), ...plan.removed.map((l) => `-${l}`)].join(", ") || "block normalize";
44059
44346
  if (opts.json) {
44060
- if (opts.write && plan.changed) (0, import_node_fs47.writeFileSync)(path2, plan.content, "utf8");
44347
+ if (opts.write && plan.changed) (0, import_node_fs48.writeFileSync)(path2, plan.content, "utf8");
44061
44348
  console.log(JSON.stringify(plan, null, 2));
44062
44349
  if (!opts.write && plan.changed) process.exitCode = 1;
44063
44350
  return;
44064
44351
  }
44065
44352
  if (opts.write) {
44066
44353
  if (plan.changed) {
44067
- (0, import_node_fs47.writeFileSync)(path2, plan.content, "utf8");
44354
+ (0, import_node_fs48.writeFileSync)(path2, plan.content, "utf8");
44068
44355
  console.log(`mmi-cli devops org rules gitignore: updated .gitignore (${drift})`);
44069
44356
  } else {
44070
44357
  console.log("mmi-cli devops org rules gitignore: up to date");
@@ -45295,6 +45582,10 @@ ${decision.note}`);
45295
45582
  deployNote = dispatch.note;
45296
45583
  runs.push(hotfixWorkflowRunFromDispatch("tenant-deploy.yml", deploy));
45297
45584
  if (publish) runs.push(hotfixWorkflowRunFromDispatch("publish.yml", publish));
45585
+ } else if (deployModel === "vercel") {
45586
+ const vercel = await dispatchDeploy(deps, ctx, "main", "main", deployModel, true, (deps.now ?? Date.now)(), mergedSha, "report");
45587
+ runs.push(hotfixWorkflowRunFromDispatch("vercel", vercel));
45588
+ deployNote = vercel.note;
45298
45589
  } else {
45299
45590
  deployNote = `no hotfix deploy dispatch for deployModel=${deployModel} \u2014 prod deploy is repo-specific`;
45300
45591
  }
@@ -45804,8 +46095,8 @@ function checkHotfixCarries(options) {
45804
46095
  }
45805
46096
 
45806
46097
  // src/train-commands.ts
45807
- var import_node_fs48 = require("node:fs");
45808
- var import_node_path46 = require("node:path");
46098
+ var import_node_fs49 = require("node:fs");
46099
+ var import_node_path47 = require("node:path");
45809
46100
  init_cli_shared();
45810
46101
  init_clean_exit();
45811
46102
  init_client_version();
@@ -45820,7 +46111,7 @@ function resolveReleaseBumpIntent(raw) {
45820
46111
  }
45821
46112
  function readRepoVersion() {
45822
46113
  try {
45823
- return JSON.parse((0, import_node_fs48.readFileSync)((0, import_node_path46.join)(process.cwd(), ".claude-plugin", "plugin.json"), "utf8")).version || void 0;
46114
+ return JSON.parse((0, import_node_fs49.readFileSync)((0, import_node_path47.join)(process.cwd(), ".claude-plugin", "plugin.json"), "utf8")).version || void 0;
45824
46115
  } catch {
45825
46116
  return void 0;
45826
46117
  }
@@ -45922,8 +46213,8 @@ async function withReleaseProgress(deps, initial, action) {
45922
46213
  clearInterval(heartbeat);
45923
46214
  }
45924
46215
  }
45925
- function releaseProgressVerdict(status) {
45926
- return status === "complete" ? "Release complete" : status === "failed" ? "Release failed \u2014 a follow-up needs attention" : "Release pending \u2014 waiting for unfinished follow-ups";
46216
+ function releaseProgressVerdict(status, subject = "Release") {
46217
+ return status === "complete" ? `${subject} complete` : status === "failed" ? `${subject} failed \u2014 a follow-up needs attention` : `${subject} pending \u2014 waiting for unfinished follow-ups`;
45927
46218
  }
45928
46219
 
45929
46220
  // src/command-register-train-operations.ts
@@ -46160,8 +46451,31 @@ function renderReleasePublishRetry(r) {
46160
46451
  return `mmi-cli devops release --retry-publish: ${r.repo} ${r.tag} (${r.tagSha.slice(0, 12)}) \u2014 ${r.note}; ${r.runUrl}`;
46161
46452
  }
46162
46453
  function renderRcandResume(r) {
46163
- const base = `mmi-cli devops rcand --resume: promoted ${r.repo} \u2014 rc at ${r.tag} (${r.tagSha.slice(0, 12)}) [${r.deployModel}]; ${renderDeployLine(r)}; ${r.note}`;
46164
- return r.ledger ? [base, ...formatReleaseLedgerReport(r.ledger).map((line) => ` ${line}`)].join("\n") : base;
46454
+ const verb = r.command === "rcand-retry-deploy" ? "retry-deploy" : "resume";
46455
+ const base = `mmi-cli devops rcand --${verb}: promoted ${r.repo} \u2014 rc at ${r.tag} (${r.tagSha.slice(0, 12)}) [${r.deployModel}]; ${renderDeployLine(r)}; ${r.note}`;
46456
+ return appendVerdictAndLedger(base, r.releaseVerdict, r.ledger);
46457
+ }
46458
+ function progressSubject(commandName) {
46459
+ return commandName === "rcand" ? "Release candidate" : "Release";
46460
+ }
46461
+ function emitRcandContinuation(label, raw, o) {
46462
+ const result = raw.workflowRuns ? { ...raw, workflowRuns: raw.workflowRuns.map(workflowRunWithEvidence) } : raw;
46463
+ const { followUpStatus, releaseVerdict } = buildReleaseVerdict("rcand", result, void 0);
46464
+ const reported = { ...result, releaseVerdict };
46465
+ emitTrainResult(label, o.json ? JSON.stringify(reported, null, 2) : renderRcandResume(reported), o.out);
46466
+ consoleIo.err(`mmi-cli: Release: ${releaseProgressVerdict(followUpStatus, progressSubject("rcand"))}`);
46467
+ applyTrainFollowUpExit(followUpStatus);
46468
+ }
46469
+ function appendVerdictAndLedger(base, v, ledger) {
46470
+ const lines2 = [base];
46471
+ if (v) {
46472
+ const verdict = v.followUpStatus === "failed" ? "PROMOTED, FOLLOW-UP FAILED" : v.followUpStatus === "pending" ? "PROMOTED, FOLLOW-UP PENDING" : "SUCCEEDED";
46473
+ const followUpNote = trainFollowUpNote(v.followUpStatus);
46474
+ lines2[0] = `${base}; release verdict: ${verdict}; promoted=${v.promoted}; tag=${v.tag}; release=${v.releaseUrl ?? "unreported"}; follow-up: ${v.followUpStatus.toUpperCase()}${followUpNote ? ` (${followUpNote})` : ""}`;
46475
+ lines2.push(...v.legs.filter((leg) => leg.status === "failed").map((leg) => ` - follow-up leg ${leg.leg}: FAILED \u2014 ${leg.error ?? "no diagnostic recorded"}`));
46476
+ }
46477
+ if (ledger) lines2.push(...formatReleaseLedgerReport(ledger).map((line) => ` ${line}`));
46478
+ return lines2.join("\n");
46165
46479
  }
46166
46480
  function renderAlignment(label, alignment) {
46167
46481
  if (alignment.status !== "pr-pending") return `${label}: ${alignment.note}`;
@@ -46269,16 +46583,17 @@ function buildReleaseVerdict(commandName, result, projectInfoSync) {
46269
46583
  alignment: alignmentPending ? "unresolved" : "ok",
46270
46584
  foldPort: "ok"
46271
46585
  });
46586
+ const legs = releaseFollowUpLegs(result, projectInfoSync);
46272
46587
  return {
46273
46588
  followUpStatus,
46274
- releaseVerdict: commandName === "release" ? {
46589
+ releaseVerdict: {
46275
46590
  releaseStatus: "succeeded",
46276
46591
  followUpStatus,
46277
46592
  promoted: result.promoted,
46278
46593
  tag: result.tag,
46279
46594
  releaseUrl: result.release?.url ?? null,
46280
- legs: releaseFollowUpLegs(result, projectInfoSync)
46281
- } : void 0
46595
+ legs: commandName === "rcand" ? legs.filter((leg) => leg.leg !== "project-info") : legs
46596
+ }
46282
46597
  };
46283
46598
  }
46284
46599
  function buildResumeReleaseVerdict(result, projectInfoSync) {
@@ -46331,22 +46646,7 @@ function renderTrainApply(commandName, r) {
46331
46646
  base = `${base}; project info: ${r.projectInfoSync.note}`;
46332
46647
  }
46333
46648
  if (r.announceNote) base = `${base}; announce: ${r.announceNote}`;
46334
- if (r.releaseVerdict) {
46335
- const v = r.releaseVerdict;
46336
- const verdict = v.followUpStatus === "failed" ? "PROMOTED, FOLLOW-UP FAILED" : v.followUpStatus === "pending" ? "PROMOTED, FOLLOW-UP PENDING" : "SUCCEEDED";
46337
- const failedLegs = v.legs.filter((leg) => leg.status === "failed");
46338
- const followUpNote = trainFollowUpNote(v.followUpStatus);
46339
- return [
46340
- `${base}; release verdict: ${verdict}; promoted=${v.promoted}; tag=${v.tag}; release=${v.releaseUrl ?? "unreported"}; follow-up: ${v.followUpStatus.toUpperCase()}${followUpNote ? ` (${followUpNote})` : ""}`,
46341
- ...failedLegs.map((leg) => ` - follow-up leg ${leg.leg}: FAILED \u2014 ${leg.error ?? "no diagnostic recorded"}`),
46342
- // #5987: the durable ledger's phase legs + derived typed verdict ride the same receipt.
46343
- ...r.ledger ? formatReleaseLedgerReport(r.ledger).map((line) => ` ${line}`) : []
46344
- ].join("\n");
46345
- }
46346
- if (r.ledger) {
46347
- return [base, ...formatReleaseLedgerReport(r.ledger).map((line) => ` ${line}`)].join("\n");
46348
- }
46349
- return base;
46649
+ return appendVerdictAndLedger(base, r.releaseVerdict, r.ledger);
46350
46650
  }
46351
46651
  function applyTrainFollowUpExit(status) {
46352
46652
  const code = trainFollowUpExitCode(status);
@@ -46396,6 +46696,8 @@ function registerTrainOperationsCommands(program3, runProjectInfoSyncCallback) {
46396
46696
  trainCmd.addOption(new Option(f.flags, f.description).hideHelp());
46397
46697
  }
46398
46698
  }
46699
+ const retryDeploy = new Option("--retry-deploy", "re-dispatch the FAILED rc deploy of the promoted candidate in the active rcand ledger \u2014 same tag and SHA, never re-cut or re-pushed (#7053)");
46700
+ trainCmd.addOption(commandName === "rcand" ? retryDeploy : retryDeploy.hideHelp());
46399
46701
  trainCmd.option("--repo <owner/repo>", "dry-run plan for a target repo without relying on the current checkout; --apply still uses the current checkout").action(async (o) => {
46400
46702
  try {
46401
46703
  await requireFreshTrainCli(commandName);
@@ -46423,8 +46725,30 @@ function registerTrainOperationsCommands(program3, runProjectInfoSyncCallback) {
46423
46725
  if (o.retryPublish && commandName !== "release") {
46424
46726
  return fail(`${commandName}: --retry-publish applies only to release. Run: mmi-cli devops release --retry-publish <run-id> --apply --watch`);
46425
46727
  }
46426
- if (o.out && !o.apply && !o.resume) {
46427
- return fail(`${commandName}: --out writes a terminal result receipt (--apply, --resume, --abort, --retry-publish) \u2014 the dry-run plan keeps its stdout/JSON output. Drop --out or rerun with --apply`);
46728
+ if (o.retryDeploy && commandName !== "rcand") {
46729
+ return fail(`${commandName}: --retry-deploy applies only to rcand \u2014 it re-dispatches a failed rc deploy of the promoted candidate. Run: mmi-cli devops rcand --retry-deploy --watch (a failed hotfix deploy: mmi-cli devops hotfix release <tag> --retry-deploy)`);
46730
+ }
46731
+ if (o.out && !o.apply && !o.resume && !o.retryDeploy) {
46732
+ return fail(`${commandName}: --out writes a terminal result receipt (--apply, --resume, --retry-deploy, --abort, --retry-publish) \u2014 the dry-run plan keeps its stdout/JSON output. Drop --out or rerun with --apply`);
46733
+ }
46734
+ if (o.retryDeploy) {
46735
+ if (o.apply || o.resume) return fail("rcand: --retry-deploy cannot be combined with --apply or --resume \u2014 it re-dispatches only the failed rc deploy of the already-promoted candidate");
46736
+ if (o.repo) {
46737
+ const guard = planTrainApplyRepoGuard(o.repo, await resolveRepo(), `mmi-cli devops rcand --retry-deploy${o.watch ? " --watch" : ""}`);
46738
+ if (!guard.ok) return fail(`rcand: ${guard.message}`);
46739
+ }
46740
+ try {
46741
+ const raw = await withReleaseProgress(
46742
+ trainApplyDeps(),
46743
+ "Checking the failed release candidate deploy",
46744
+ (deps) => runRcandRetryDeploy(deps, { watch: o.watch })
46745
+ );
46746
+ return emitRcandContinuation("rcand --retry-deploy", raw, o);
46747
+ } catch (e) {
46748
+ applyTrainFollowUpExit("failed");
46749
+ consoleIo.err("mmi-cli: Release: Release candidate blocked \u2014 see the error below");
46750
+ return failGraceful(`rcand --retry-deploy: ${e.message}`);
46751
+ }
46428
46752
  }
46429
46753
  if (o.supersedeDeploy && o.supersedePublish) {
46430
46754
  if (o.resume) return fail("release: --supersede-deploy --supersede-publish and --resume are mutually exclusive \u2014 the closure settles the ledger so a NEW train can run, resume re-derives the very legs it closes");
@@ -46528,17 +46852,12 @@ function registerTrainOperationsCommands(program3, runProjectInfoSyncCallback) {
46528
46852
  if (o.apply) return fail(`${commandName}: --resume and --apply are mutually exclusive \u2014 --apply cuts the NEXT version, --resume finishes the immutable tag already on origin`);
46529
46853
  try {
46530
46854
  if (commandName === "rcand") {
46531
- const raw2 = await runRcandResume(trainApplyDeps(), { watch: o.watch });
46532
- const result2 = raw2.workflowRuns ? { ...raw2, workflowRuns: raw2.workflowRuns.map(workflowRunWithEvidence) } : raw2;
46533
- emitTrainResult("rcand --resume", o.json ? JSON.stringify(result2, null, 2) : renderRcandResume(result2), o.out);
46534
- applyTrainFollowUpExit(deriveTrainFollowUpStatus({
46535
- projectInfo: "ok",
46536
- deploy: deployFollowUpOutcome(result2.deployStatus),
46537
- rcRetirement: "ok",
46538
- alignment: "ok",
46539
- foldPort: "ok"
46540
- }));
46541
- return;
46855
+ const raw2 = await withReleaseProgress(
46856
+ trainApplyDeps(),
46857
+ "Checking the existing release candidate",
46858
+ (deps) => runRcandResume(deps, { watch: o.watch })
46859
+ );
46860
+ return emitRcandContinuation("rcand --resume", raw2, o);
46542
46861
  }
46543
46862
  const raw = await withReleaseProgress(
46544
46863
  trainApplyDeps(),
@@ -46559,7 +46878,7 @@ function registerTrainOperationsCommands(program3, runProjectInfoSyncCallback) {
46559
46878
  return;
46560
46879
  } catch (e) {
46561
46880
  applyTrainFollowUpExit("failed");
46562
- if (commandName === "release") consoleIo.err("mmi-cli: Release: Release blocked \u2014 see the error below");
46881
+ consoleIo.err(`mmi-cli: Release: ${progressSubject(commandName)} blocked \u2014 see the error below`);
46563
46882
  return failGraceful(`${commandName} --resume: ${e.message}`);
46564
46883
  }
46565
46884
  }
@@ -46603,7 +46922,7 @@ function registerTrainOperationsCommands(program3, runProjectInfoSyncCallback) {
46603
46922
  try {
46604
46923
  const ack = (o.ack ?? "").split(",").map((s) => s.trim()).filter(Boolean);
46605
46924
  const apply = (deps) => runTrainApply(commandName, deps, { watch: o.watch, announceSummaryFile: o.announceSummaryFile, ack, dev: o.dev });
46606
- const raw = commandName === "release" ? await withReleaseProgress(trainApplyDeps(), "Preparing release", apply) : await apply(trainApplyDeps());
46925
+ const raw = await withReleaseProgress(trainApplyDeps(), commandName === "release" ? "Preparing release" : "Preparing release candidate", apply);
46607
46926
  const result = raw.workflowRuns ? { ...raw, workflowRuns: raw.workflowRuns.map(workflowRunWithEvidence) } : raw;
46608
46927
  let projectInfoSync;
46609
46928
  if (commandName === "release") {
@@ -46621,14 +46940,14 @@ function registerTrainOperationsCommands(program3, runProjectInfoSyncCallback) {
46621
46940
  };
46622
46941
  const output = o.json ? JSON.stringify(reported, null, 2) : renderTrainApply(commandName, reported);
46623
46942
  emitTrainResult(`${commandName} apply`, output, o.out);
46624
- if (commandName === "release") consoleIo.err(`mmi-cli: Release: ${releaseProgressVerdict(followUpStatus)}`);
46943
+ consoleIo.err(`mmi-cli: Release: ${releaseProgressVerdict(followUpStatus, progressSubject(commandName))}`);
46625
46944
  applyTrainFollowUpExit(followUpStatus);
46626
46945
  return;
46627
46946
  } catch (e) {
46628
46947
  const error = e;
46629
46948
  writeTrainFailureReceipt(commandName, o.json === true, o.out, error);
46630
46949
  process.exitCode = 1;
46631
- if (commandName === "release") consoleIo.err("mmi-cli: Release: Release blocked \u2014 see the error below");
46950
+ consoleIo.err(`mmi-cli: Release: ${progressSubject(commandName)} blocked \u2014 see the error below`);
46632
46951
  return await failGraceful(`${commandName}: ${error.message}`);
46633
46952
  }
46634
46953
  }
@@ -46808,7 +47127,7 @@ ${r.stderr ?? ""}`).catch(() => "");
46808
47127
  var ENV_HEAL_LOCK_STALE_MS = 10 * 6e4;
46809
47128
  var ENV_HEAL_LOCK_MAX_WAIT_MS = 2 * 6e4;
46810
47129
  function envHealLockPath(home) {
46811
- return (0, import_node_path47.join)(home, ".claude", "plugins", ".mmi-env-heal.lock");
47130
+ return (0, import_node_path48.join)(home, ".claude", "plugins", ".mmi-env-heal.lock");
46812
47131
  }
46813
47132
  async function withEnvHealLock(what, run) {
46814
47133
  try {
@@ -46945,7 +47264,7 @@ function mmiDoctorDeps(opts = {}) {
46945
47264
  );
46946
47265
  const result = applyPluginCachePlan(
46947
47266
  plan,
46948
- (p) => (0, import_node_fs49.rmSync)(p, { recursive: true }),
47267
+ (p) => (0, import_node_fs50.rmSync)(p, { recursive: true }),
46949
47268
  stagingApplyFsGuard(configRoot)
46950
47269
  );
46951
47270
  return {
@@ -46980,7 +47299,7 @@ function mmiDoctorDeps(opts = {}) {
46980
47299
  // has no generated routing index would
46981
47300
  // get a permanent — demanding an artifact it never asked for.
46982
47301
  docsIndexState: (root) => {
46983
- if (!(0, import_node_fs49.existsSync)((0, import_node_path47.join)(root, DOCS_INDEX_PATH))) return void 0;
47302
+ if (!(0, import_node_fs50.existsSync)((0, import_node_path48.join)(root, DOCS_INDEX_PATH))) return void 0;
46984
47303
  const real = createDocsIndexDeps(root);
46985
47304
  let docs;
46986
47305
  const listDocs = () => docs ??= real.listDocs();
@@ -46989,7 +47308,7 @@ function mmiDoctorDeps(opts = {}) {
46989
47308
  },
46990
47309
  // #4168: working-tree heal — write if drifted, then re-check. Commit remains the operator's step.
46991
47310
  healDocsIndex: (root) => {
46992
- if (!(0, import_node_fs49.existsSync)((0, import_node_path47.join)(root, DOCS_INDEX_PATH))) return { drift: false, docCount: 0 };
47311
+ if (!(0, import_node_fs50.existsSync)((0, import_node_path48.join)(root, DOCS_INDEX_PATH))) return { drift: false, docCount: 0 };
46993
47312
  const real = createDocsIndexDeps(root);
46994
47313
  let docs;
46995
47314
  const listDocs = () => docs ??= real.listDocs();
@@ -47615,7 +47934,7 @@ project.command("set [owner/repo]").description("upsert project META (idempotent
47615
47934
  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`);
47616
47935
  if (o.secretsFile) {
47617
47936
  try {
47618
- vars.push(`secrets=${(0, import_node_fs49.readFileSync)(o.secretsFile, "utf8")}`);
47937
+ vars.push(`secrets=${(0, import_node_fs50.readFileSync)(o.secretsFile, "utf8")}`);
47619
47938
  } catch (e) {
47620
47939
  return fail(`org project set: cannot read --secrets-file ${o.secretsFile}: ${e.message}`);
47621
47940
  }
@@ -47948,16 +48267,16 @@ function ciAuditDeps2() {
47948
48267
  // gate re-seed step is skipped gracefully rather than failing mid-run.
47949
48268
  readSeedFile: (path2) => {
47950
48269
  if (!root) return null;
47951
- const fullPath = (0, import_node_path47.join)(root, path2);
47952
- return (0, import_node_fs49.existsSync)(fullPath) ? (0, import_node_fs49.readFileSync)(fullPath, "utf8") : null;
48270
+ const fullPath = (0, import_node_path48.join)(root, path2);
48271
+ return (0, import_node_fs50.existsSync)(fullPath) ? (0, import_node_fs50.readFileSync)(fullPath, "utf8") : null;
47953
48272
  }
47954
48273
  };
47955
48274
  }
47956
48275
  function hubRoot2() {
47957
- const fromPkg = (0, import_node_path47.join)(__dirname, "..", "..");
48276
+ const fromPkg = (0, import_node_path48.join)(__dirname, "..", "..");
47958
48277
  const marker = "skills/bootstrap/seeds/manifest.json";
47959
- if ((0, import_node_fs49.existsSync)((0, import_node_path47.join)(fromPkg, marker))) return fromPkg;
47960
- if ((0, import_node_fs49.existsSync)((0, import_node_path47.join)(process.cwd(), marker))) return process.cwd();
48278
+ if ((0, import_node_fs50.existsSync)((0, import_node_path48.join)(fromPkg, marker))) return fromPkg;
48279
+ if ((0, import_node_fs50.existsSync)((0, import_node_path48.join)(process.cwd(), marker))) return process.cwd();
47961
48280
  return null;
47962
48281
  }
47963
48282
  registerQueryCommands(program2);
@@ -48057,10 +48376,10 @@ access.command("audit").description("audit collaborator roles + train-branch pus
48057
48376
  targets = resolution.targets;
48058
48377
  }
48059
48378
  const derivedMatrix = registryProjects ? accessMatrixFromProjects(registryProjects) : {};
48060
- const fileMatrix = (0, import_node_fs49.existsSync)("access-matrix.json") ? loadAccessMatrix((0, import_node_fs49.readFileSync)("access-matrix.json", "utf8")) : {};
48379
+ const fileMatrix = (0, import_node_fs50.existsSync)("access-matrix.json") ? loadAccessMatrix((0, import_node_fs50.readFileSync)("access-matrix.json", "utf8")) : {};
48061
48380
  const matrix = mergeAccessMatrix(fileMatrix, derivedMatrix);
48062
48381
  const dataAccess = registryProjects ? dataAccessContractsFromProjects(registryProjects) : { consumers: {} };
48063
- const sanctioned = (0, import_node_fs49.existsSync)("access-matrix.json") ? loadSanctionedAdmins((0, import_node_fs49.readFileSync)("access-matrix.json", "utf8")) : {};
48382
+ const sanctioned = (0, import_node_fs50.existsSync)("access-matrix.json") ? loadSanctionedAdmins((0, import_node_fs50.readFileSync)("access-matrix.json", "utf8")) : {};
48064
48383
  const report = await auditOrgAccess(targets, deps, matrix, dataAccess, sanctioned);
48065
48384
  console.log(o.json ? JSON.stringify(report, null, 2) : renderAccessReport(report));
48066
48385
  if (!report.ok) process.exitCode = 1;
@@ -48091,16 +48410,16 @@ function directoryBytes(path2) {
48091
48410
  let total = 0;
48092
48411
  let entries;
48093
48412
  try {
48094
- entries = (0, import_node_fs49.readdirSync)(path2, { withFileTypes: true });
48413
+ entries = (0, import_node_fs50.readdirSync)(path2, { withFileTypes: true });
48095
48414
  } catch {
48096
48415
  return 0;
48097
48416
  }
48098
48417
  for (const entry of entries) {
48099
- const child2 = (0, import_node_path47.join)(path2, entry.name);
48418
+ const child2 = (0, import_node_path48.join)(path2, entry.name);
48100
48419
  if (entry.isDirectory()) total += directoryBytes(child2);
48101
48420
  else {
48102
48421
  try {
48103
- total += (0, import_node_fs49.statSync)(child2).size;
48422
+ total += (0, import_node_fs50.statSync)(child2).size;
48104
48423
  } catch {
48105
48424
  }
48106
48425
  }
@@ -48108,25 +48427,25 @@ function directoryBytes(path2) {
48108
48427
  return total;
48109
48428
  }
48110
48429
  function listDirEntries(dir) {
48111
- return (0, import_node_fs49.readdirSync)(dir, { withFileTypes: true }).map((d) => ({ name: d.name, isDirectory: d.isDirectory() }));
48430
+ return (0, import_node_fs50.readdirSync)(dir, { withFileTypes: true }).map((d) => ({ name: d.name, isDirectory: d.isDirectory() }));
48112
48431
  }
48113
48432
  function readInstalledPluginRefs(configRoot) {
48114
48433
  const p = installedPluginsPathForConfig(configRoot);
48115
- if (!(0, import_node_fs49.existsSync)(p)) return [];
48434
+ if (!(0, import_node_fs50.existsSync)(p)) return [];
48116
48435
  try {
48117
- return installedPluginPaths((0, import_node_fs49.readFileSync)(p, "utf8"));
48436
+ return installedPluginPaths((0, import_node_fs50.readFileSync)(p, "utf8"));
48118
48437
  } catch {
48119
48438
  return null;
48120
48439
  }
48121
48440
  }
48122
48441
  function pluginCacheFsDeps(configRoot, dirBytes) {
48123
48442
  return {
48124
- exists: (p) => (0, import_node_fs49.existsSync)(p),
48125
- listVersionDirs: (root) => (0, import_node_fs49.readdirSync)(root, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name),
48443
+ exists: (p) => (0, import_node_fs50.existsSync)(p),
48444
+ listVersionDirs: (root) => (0, import_node_fs50.readdirSync)(root, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name),
48126
48445
  dirBytes,
48127
- listStagingDirs: (root) => (0, import_node_fs49.readdirSync)(root, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => {
48446
+ listStagingDirs: (root) => (0, import_node_fs50.readdirSync)(root, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => {
48128
48447
  try {
48129
- return { name: d.name, mtimeMs: newestMtimeMs((0, import_node_path47.join)(root, d.name), listDirEntries, (p) => (0, import_node_fs49.statSync)(p).mtimeMs) };
48448
+ return { name: d.name, mtimeMs: newestMtimeMs((0, import_node_path48.join)(root, d.name), listDirEntries, (p) => (0, import_node_fs50.statSync)(p).mtimeMs) };
48130
48449
  } catch {
48131
48450
  return { name: d.name, mtimeMs: Date.now() };
48132
48451
  }
@@ -48140,10 +48459,10 @@ function stagingApplyFsGuard(configRoot) {
48140
48459
  return {
48141
48460
  referencedPaths: () => readInstalledPluginRefs(configRoot),
48142
48461
  mtimeMs: (name) => {
48143
- const p = (0, import_node_path47.join)(stagingRoot, name);
48144
- if (!(0, import_node_fs49.existsSync)(p)) return null;
48462
+ const p = (0, import_node_path48.join)(stagingRoot, name);
48463
+ if (!(0, import_node_fs50.existsSync)(p)) return null;
48145
48464
  try {
48146
- return newestMtimeMs(p, listDirEntries, (q) => (0, import_node_fs49.statSync)(q).mtimeMs);
48465
+ return newestMtimeMs(p, listDirEntries, (q) => (0, import_node_fs50.statSync)(q).mtimeMs);
48147
48466
  } catch {
48148
48467
  return null;
48149
48468
  }
@@ -48169,7 +48488,7 @@ program2.command("plugin-prune").description(`prune stale cached MMI plugin vers
48169
48488
  { withBytes: true, configRoot, includeStaging: surface !== "codex" }
48170
48489
  );
48171
48490
  const anythingToDelete = plan.prune.length > 0 || plan.staging.length > 0;
48172
- const result = o.apply && anythingToDelete ? applyPluginCachePlan(plan, (p) => (0, import_node_fs49.rmSync)(p, { recursive: true, force: true }), stagingApplyFsGuard(configRoot)) : void 0;
48491
+ const result = o.apply && anythingToDelete ? applyPluginCachePlan(plan, (p) => (0, import_node_fs50.rmSync)(p, { recursive: true, force: true }), stagingApplyFsGuard(configRoot)) : void 0;
48173
48492
  const warnings = plan.prune.length > 0 ? [CONCURRENT_SESSION_WARNING] : [];
48174
48493
  if (o.json) console.log(JSON.stringify({ ...plan, warnings, applied: result ?? null }));
48175
48494
  else console.log(renderPluginCachePlan(plan, result));