@mutmutco/cli 4.5.4 → 4.5.6

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