@mutmutco/cli 3.8.0 → 3.10.0

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 +687 -305
  2. package/package.json +1 -1
package/dist/main.cjs CHANGED
@@ -3408,7 +3408,7 @@ var program = new Command();
3408
3408
 
3409
3409
  // src/index.ts
3410
3410
  var import_promises3 = require("node:fs/promises");
3411
- var import_node_fs19 = require("node:fs");
3411
+ var import_node_fs20 = require("node:fs");
3412
3412
 
3413
3413
  // src/bootstrap-org-ruleset.ts
3414
3414
  var ORG_NO_AGENT_FILES_RULESET_NAME = "mmi-no-agent-files-org";
@@ -4524,7 +4524,12 @@ function buildSessionStartPlan(verbs) {
4524
4524
  { name: "board slice", run: verbs.boardSlice }
4525
4525
  ],
4526
4526
  sequential: [
4527
- // Doctor runs last so it sees the final tree.
4527
+ // local train sync (#2582): fast-forward local development/main/rc to origin BEFORE doctor + before
4528
+ // the agent works, so a checkout never lags behind a release someone pushed to origin (and the
4529
+ // deferred main -> development alignment merge lands locally). Sequential + fail-soft: it runs one
4530
+ // bounded `git fetch` + ff, silent unless a branch actually moved, and never blocks the banner.
4531
+ { name: "train sync", run: verbs.syncTrain },
4532
+ // Doctor runs last so it sees the final (synced) tree.
4528
4533
  { name: "doctor", run: verbs.doctor }
4529
4534
  ]
4530
4535
  };
@@ -4566,6 +4571,103 @@ function scratchGcLines(cwd, env = process.env, now = Date.now()) {
4566
4571
  }
4567
4572
  }
4568
4573
 
4574
+ // src/git-clean-tree.ts
4575
+ function isAgentScratchPath(path2) {
4576
+ const normalized = path2.replace(/\\/g, "/").trim();
4577
+ return /^tmp_[^/]+$/.test(normalized);
4578
+ }
4579
+ function porcelainHasBlockingChanges(porcelain) {
4580
+ return porcelain.split("\n").some((line) => {
4581
+ const trimmed = line.trim();
4582
+ if (!trimmed) return false;
4583
+ const path2 = trimmed.slice(3).split(" -> ")[0]?.trim() ?? "";
4584
+ return path2 !== "" && !isAgentScratchPath(path2);
4585
+ });
4586
+ }
4587
+
4588
+ // src/local-train-sync.ts
4589
+ var TRAIN_BRANCHES = ["development", "main", "rc"];
4590
+ function clean(out) {
4591
+ return out.trim();
4592
+ }
4593
+ async function revParse(run, ref) {
4594
+ try {
4595
+ return clean(await run("git", ["rev-parse", "--verify", "--quiet", ref]));
4596
+ } catch {
4597
+ return "";
4598
+ }
4599
+ }
4600
+ async function isFastForward(run, ancestor, descendant) {
4601
+ try {
4602
+ await run("git", ["merge-base", "--is-ancestor", ancestor, descendant]);
4603
+ return true;
4604
+ } catch {
4605
+ return false;
4606
+ }
4607
+ }
4608
+ async function syncOneBranch(run, branch, currentBranch2, warn) {
4609
+ const localSha = await revParse(run, `refs/heads/${branch}`);
4610
+ if (!localSha) {
4611
+ return { branch, status: "skipped", reason: "no-local-branch", note: `${branch}: no local branch \u2014 skipped` };
4612
+ }
4613
+ const remoteSha = await revParse(run, `refs/remotes/origin/${branch}`);
4614
+ if (!remoteSha) {
4615
+ return { branch, status: "skipped", reason: "no-remote-branch", note: `${branch}: no origin/${branch} \u2014 skipped` };
4616
+ }
4617
+ if (localSha === remoteSha) {
4618
+ return { branch, status: "already-current", note: `${branch}: already at origin/${branch}` };
4619
+ }
4620
+ if (!await isFastForward(run, localSha, remoteSha)) {
4621
+ const note = `${branch}: local diverged from origin/${branch} \u2014 left untouched (never force)`;
4622
+ warn?.(note);
4623
+ return { branch, status: "skipped", reason: "diverged", note };
4624
+ }
4625
+ try {
4626
+ if (branch === currentBranch2) {
4627
+ const porcelain = await run("git", ["status", "--porcelain"]);
4628
+ if (porcelainHasBlockingChanges(porcelain)) {
4629
+ return { branch, status: "skipped", reason: "current-dirty", note: `${branch}: checked out with local changes \u2014 skipped (commit/stash, then rerun)` };
4630
+ }
4631
+ await run("git", ["merge", "--ff-only", `origin/${branch}`]);
4632
+ } else {
4633
+ await run("git", ["fetch", "origin", `${branch}:${branch}`]);
4634
+ }
4635
+ return { branch, status: "synced", note: `${branch}: fast-forwarded to origin/${branch}` };
4636
+ } catch (e) {
4637
+ const note = `${branch}: fast-forward failed \u2014 ${e instanceof Error ? e.message : String(e)}`;
4638
+ warn?.(note);
4639
+ return { branch, status: "skipped", reason: "ff-failed", note };
4640
+ }
4641
+ }
4642
+ async function syncLocalTrainBranches(run, options = {}) {
4643
+ const branches = options.branches ?? TRAIN_BRANCHES;
4644
+ const doFetch = options.fetch ?? true;
4645
+ const fetchRun = options.fetchRun ?? ((args) => run("git", args));
4646
+ if (doFetch) {
4647
+ try {
4648
+ await fetchRun(["fetch", "origin"]);
4649
+ } catch (e) {
4650
+ options.warn?.(`local-train-sync: git fetch origin failed \u2014 syncing against cached refs (${e instanceof Error ? e.message : String(e)})`);
4651
+ }
4652
+ }
4653
+ let currentBranch2 = "";
4654
+ try {
4655
+ currentBranch2 = clean(await run("git", ["rev-parse", "--abbrev-ref", "HEAD"]));
4656
+ } catch {
4657
+ currentBranch2 = "";
4658
+ }
4659
+ const results = [];
4660
+ for (const branch of branches) {
4661
+ results.push(await syncOneBranch(run, branch, currentBranch2, options.warn));
4662
+ }
4663
+ return { branches: results, changed: results.some((r) => r.status === "synced") };
4664
+ }
4665
+ function localTrainSyncBannerLine(result) {
4666
+ const moved = result.branches.filter((b) => b.status === "synced").map((b) => b.branch);
4667
+ if (moved.length === 0) return "";
4668
+ return `synced local ${moved.join(", ")} \u2192 origin`;
4669
+ }
4670
+
4569
4671
  // src/board.ts
4570
4672
  var import_node_child_process5 = require("node:child_process");
4571
4673
  var import_node_util5 = require("node:util");
@@ -5868,7 +5970,7 @@ function whoamiLine(report) {
5868
5970
  }
5869
5971
 
5870
5972
  // src/index.ts
5871
- var import_node_path19 = require("node:path");
5973
+ var import_node_path20 = require("node:path");
5872
5974
 
5873
5975
  // src/merge-ci-policy.ts
5874
5976
  function resolveMergeCiPolicy(input) {
@@ -6148,6 +6250,9 @@ var MANAGED_GITIGNORE_LINES = [
6148
6250
  // doctor` flags one explicitly instead (buildRepoLocalWorktreeCheck) rather than baking the fallback path
6149
6251
  // into every repo's .gitignore.
6150
6252
  ".aws-sam/",
6253
+ // jerv-cli lease markers — the ephemeral per-worktree lease ledger `jerv-cli lease open` writes into a
6254
+ // worktree root; never tracked (it lives only for the life of the worktree). Hub#2586.
6255
+ ".jerv-lease.json",
6151
6256
  "/*.png",
6152
6257
  // Runtime secrets/config are vault-only (AGENTS.md "Privileged ops") — never commit a local .env. The
6153
6258
  // .env.example reference file stays tracked.
@@ -9689,20 +9794,6 @@ async function runStageLiveDown(deps, t) {
9689
9794
  };
9690
9795
  }
9691
9796
 
9692
- // src/git-clean-tree.ts
9693
- function isAgentScratchPath(path2) {
9694
- const normalized = path2.replace(/\\/g, "/").trim();
9695
- return /^tmp_[^/]+$/.test(normalized);
9696
- }
9697
- function porcelainHasBlockingChanges(porcelain) {
9698
- return porcelain.split("\n").some((line) => {
9699
- const trimmed = line.trim();
9700
- if (!trimmed) return false;
9701
- const path2 = trimmed.slice(3).split(" -> ")[0]?.trim() ?? "";
9702
- return path2 !== "" && !isAgentScratchPath(path2);
9703
- });
9704
- }
9705
-
9706
9797
  // src/tenant-control-parse.ts
9707
9798
  var OUTPUT_BEGIN = "mmi-control-output-begin";
9708
9799
  var OUTPUT_END = "mmi-control-output-end";
@@ -9743,13 +9834,26 @@ function resolveDeployModel2(meta, repo) {
9743
9834
  if (isDeployModel(m)) return m;
9744
9835
  return resolveDeployModel(meta, repo);
9745
9836
  }
9746
- function clean(out) {
9837
+ function clean2(out) {
9747
9838
  return out.trim();
9748
9839
  }
9749
9840
  function requireValue(value, label) {
9750
9841
  if (!value) throw new Error(`${label} could not be resolved`);
9751
9842
  return value;
9752
9843
  }
9844
+ function surfaceTrainSubprocessFailure(e, file, args) {
9845
+ if (!(e instanceof Error)) return e;
9846
+ const err = e;
9847
+ const stderr = typeof err.stderr === "string" ? err.stderr.trim() : "";
9848
+ const stdout = typeof err.stdout === "string" ? err.stdout.trim() : "";
9849
+ const cmd = `${file} ${args.join(" ")}`.trim();
9850
+ if (!stderr && !stdout) {
9851
+ err.message = `${cmd} failed with no stderr output (exit ${err.code ?? "?"}) \u2014 likely a transient subprocess/network error. [${err.message}]`;
9852
+ } else if (/^Command failed/i.test(err.message) && stderr) {
9853
+ err.message = `${cmd} failed: ${stderr}`;
9854
+ }
9855
+ return err;
9856
+ }
9753
9857
  function planTrainApplyRepoGuard(applyRepo, cwdRepo, rerun) {
9754
9858
  if (cwdRepo && cwdRepo.toLowerCase() === applyRepo.toLowerCase()) return { ok: true };
9755
9859
  const where = cwdRepo ? `this checkout is ${cwdRepo}` : "this checkout could not be identified";
@@ -9813,18 +9917,18 @@ async function verifyPublishedRelease(deps, repo, tag, expectedTarget, expectedS
9813
9917
  if (release.targetCommitish !== expectedTarget) {
9814
9918
  throw new Error(`Release ${tag} targetCommitish is ${String(release.targetCommitish || "(missing)")}, expected ${expectedTarget}`);
9815
9919
  }
9816
- const tagSha = requireValue(clean(await deps.run("git", ["rev-parse", `${tag}^{commit}`])), "release tag sha");
9920
+ const tagSha = requireValue(clean2(await deps.run("git", ["rev-parse", `${tag}^{commit}`])), "release tag sha");
9817
9921
  if (tagSha !== expectedSha) {
9818
9922
  throw new Error(`Release ${tag} tag points at ${tagSha}, expected ${expectedSha}`);
9819
9923
  }
9820
- const branchOut = clean(await deps.run("git", ["ls-remote", "origin", `refs/heads/${expectedTarget}`]));
9924
+ const branchOut = clean2(await runGitRemoteRead(deps, ["ls-remote", "origin", `refs/heads/${expectedTarget}`]));
9821
9925
  const branchSha = requireValue(branchOut.split(/\s+/)[0] ?? "", `origin/${expectedTarget} sha`);
9822
9926
  if (branchSha !== expectedSha) {
9823
9927
  throw new Error(`origin/${expectedTarget} points at ${branchSha}, expected ${expectedSha}`);
9824
9928
  }
9825
9929
  }
9826
9930
  async function ffOnlyPull(deps, branch) {
9827
- await deps.run("git", ["pull", "--ff-only", "origin", branch]);
9931
+ await runGitRemoteRead(deps, ["pull", "--ff-only", "origin", branch]);
9828
9932
  }
9829
9933
  var RELEASE_TOLERATED_PATHS = [".gitignore"];
9830
9934
  var MERGE_TREE_PREFLIGHT_ATTEMPTS = 3;
@@ -9878,7 +9982,7 @@ function ensurePositiveCount(out, emptyMessage) {
9878
9982
  if (count <= 0) throw new Error(emptyMessage);
9879
9983
  }
9880
9984
  async function remoteBranchExists(deps, branch) {
9881
- const out = clean(await deps.run("git", ["ls-remote", "origin", `refs/heads/${branch}`]));
9985
+ const out = clean2(await runGitRemoteRead(deps, ["ls-remote", "origin", `refs/heads/${branch}`]));
9882
9986
  return out.length > 0;
9883
9987
  }
9884
9988
  async function loadReleaseTrackBranchHints(deps) {
@@ -9890,10 +9994,10 @@ async function loadReleaseTrackBranchHints(deps) {
9890
9994
  return { hasDevelopmentBranch, hasMainBranch, hasRcBranch };
9891
9995
  }
9892
9996
  async function buildTrainApplyContext(deps) {
9893
- const repo = requireValue(clean(await deps.run("gh", ["repo", "view", "--json", "nameWithOwner", "--jq", ".nameWithOwner"])), "repo");
9997
+ const repo = requireValue(clean2(await deps.run("gh", ["repo", "view", "--json", "nameWithOwner", "--jq", ".nameWithOwner"])), "repo");
9894
9998
  const [owner, name] = repo.split("/");
9895
9999
  if (!owner || !name) throw new Error(`repo must be owner/name, got ${repo}`);
9896
- const login = requireValue(clean(await deps.run("gh", ["api", "user", "--jq", ".login"])), "GitHub login");
10000
+ const login = requireValue(clean2(await deps.run("gh", ["api", "user", "--jq", ".login"])), "GitHub login");
9897
10001
  const verdict = await deps.trainAuthority(repo);
9898
10002
  if (!verdict.ok) throw new Error(`${commandAuthorityLabel(owner)}: train authority could not be verified (${verdict.error})`);
9899
10003
  if (!verdict.train) {
@@ -9914,11 +10018,11 @@ async function requireCleanTree(deps) {
9914
10018
  if (porcelainHasBlockingChanges(status)) throw new Error("working tree must be clean before train apply");
9915
10019
  }
9916
10020
  async function requireBranch(deps, branch) {
9917
- const current = clean(await deps.run("git", ["rev-parse", "--abbrev-ref", "HEAD"]));
10021
+ const current = clean2(await deps.run("git", ["rev-parse", "--abbrev-ref", "HEAD"]));
9918
10022
  if (current !== branch) throw new Error(`must run from ${branch}, currently on ${current || "(unknown)"}`);
9919
10023
  }
9920
10024
  async function currentBranch(deps) {
9921
- return clean(await deps.run("git", ["rev-parse", "--abbrev-ref", "HEAD"])) || "(unknown)";
10025
+ return clean2(await deps.run("git", ["rev-parse", "--abbrev-ref", "HEAD"])) || "(unknown)";
9922
10026
  }
9923
10027
  async function restoreCheckoutAfterRelease(deps, startBranch) {
9924
10028
  const status = await deps.run("git", ["status", "--porcelain"]);
@@ -9961,17 +10065,17 @@ async function restoreCheckoutAfterRelease(deps, startBranch) {
9961
10065
  }
9962
10066
  }
9963
10067
  async function withCheckoutRestoredAfterRelease(deps, result, startBranch) {
9964
- return {
9965
- ...result,
9966
- checkout: await restoreCheckoutAfterRelease(deps, startBranch)
9967
- };
10068
+ const checkout = await restoreCheckoutAfterRelease(deps, startBranch);
10069
+ const branches = TRAIN_BRANCHES.filter((b) => b !== startBranch);
10070
+ const localSync = await syncLocalTrainBranches(deps.run, { branches, fetch: false, warn: deps.warn }).catch(() => void 0);
10071
+ return { ...result, checkout, ...localSync ? { localSync } : {} };
9968
10072
  }
9969
10073
  function normGitPath(p) {
9970
10074
  return p.replace(/\\/g, "/").replace(/\/+$/, "").toLowerCase();
9971
10075
  }
9972
10076
  async function releaseRcBranchLock(deps) {
9973
10077
  const porcelain = await deps.run("git", ["worktree", "list", "--porcelain"]);
9974
- const cwd = normGitPath(clean(await deps.run("git", ["rev-parse", "--show-toplevel"])));
10078
+ const cwd = normGitPath(clean2(await deps.run("git", ["rev-parse", "--show-toplevel"])));
9975
10079
  for (const wt of parseWorktreePorcelain(porcelain)) {
9976
10080
  if (wt.branch !== "rc") continue;
9977
10081
  if (normGitPath(wt.path) === cwd) continue;
@@ -10038,6 +10142,32 @@ var defaultSleep2 = (ms) => new Promise((resolve5) => setTimeout(resolve5, ms));
10038
10142
  function resolveSleep(deps) {
10039
10143
  return deps.sleep ?? defaultSleep2;
10040
10144
  }
10145
+ var GIT_NETWORK_ATTEMPTS = 3;
10146
+ var GIT_NETWORK_RETRY_DELAY_MS = 1e3;
10147
+ function isPersistentGitPushFailure(e) {
10148
+ const msg = `${e instanceof Error ? e.message : String(e)} ${String(e.stderr ?? "")}`;
10149
+ return /non-fast-forward|\[rejected\]|failed to push|protected branch|hook declined|permission denied|could not read Username|authentication failed|Authentication failed|403 Forbidden|GH006|GH013/i.test(msg);
10150
+ }
10151
+ async function runGitWithRetry(deps, args, shouldRetry) {
10152
+ const sleep = resolveSleep(deps);
10153
+ let lastError;
10154
+ for (let attempt = 1; attempt <= GIT_NETWORK_ATTEMPTS; attempt++) {
10155
+ try {
10156
+ return await deps.run("git", args);
10157
+ } catch (e) {
10158
+ lastError = e;
10159
+ if (attempt >= GIT_NETWORK_ATTEMPTS || !shouldRetry(e)) throw e;
10160
+ await sleep(GIT_NETWORK_RETRY_DELAY_MS * attempt);
10161
+ }
10162
+ }
10163
+ throw lastError;
10164
+ }
10165
+ function runGitRemoteRead(deps, args) {
10166
+ return runGitWithRetry(deps, args, () => true);
10167
+ }
10168
+ function runGitPush(deps, args) {
10169
+ return runGitWithRetry(deps, args, (e) => !isPersistentGitPushFailure(e));
10170
+ }
10041
10171
  var TRAIN_CHECK_RUNS_JQ = "[.check_runs[]|{name:.name,status:.status,conclusion:.conclusion,startedAt:.started_at}]";
10042
10172
  var TRAIN_COMMIT_STATUS_JQ = "[.statuses[]|{context:.context,state:.state}]";
10043
10173
  var TRAIN_PROTECTION_CONTEXTS_JQ = "[.contexts[]]";
@@ -10161,7 +10291,7 @@ async function discoverRequiredCheckContexts(deps, ctx, branch) {
10161
10291
  return [...contexts];
10162
10292
  }
10163
10293
  async function findOpenAlignmentPr(deps, ctx) {
10164
- const out = clean(await deps.run("gh", ["pr", "list", "--repo", ctx.repo, "--base", "development", "--head", "main", "--state", "open", "--json", "number,url"]));
10294
+ const out = clean2(await deps.run("gh", ["pr", "list", "--repo", ctx.repo, "--base", "development", "--head", "main", "--state", "open", "--json", "number,url"]));
10165
10295
  if (!out) return void 0;
10166
10296
  const rows = JSON.parse(out);
10167
10297
  const row = rows.find((r) => typeof r.number === "number" && typeof r.url === "string");
@@ -10189,14 +10319,14 @@ async function rollDevelopmentForward(deps, ctx, tag) {
10189
10319
  note: `alignment PR already open: ${existing.url} \u2014 land it with \`mmi-cli pr merge ${existing.number} --auto --merge\``
10190
10320
  };
10191
10321
  }
10192
- const ahead = clean(await deps.run("git", ["rev-list", "--count", "origin/development..main"]));
10322
+ const ahead = clean2(await deps.run("git", ["rev-list", "--count", "origin/development..main"]));
10193
10323
  if (ahead === "0") {
10194
10324
  return { status: "aligned", note: "development already contains the released main; nothing to roll forward" };
10195
10325
  }
10196
10326
  const body = `Carries the ${tag} release (including the version fold) from \`main\` back to \`development\`.
10197
10327
 
10198
10328
  \`development\` requires status checks, so the release train opens this alignment PR instead of a direct push of the un-checked merge commit (#1143). Land it with a **true merge** \u2014 \`mmi-cli pr merge <n> --auto --merge\` (not squash) so the merge parentage survives and the misalignment guard stays satisfied. \`--auto\` waits out the checks this PR triggers, which otherwise block an immediate merge right after the release.`;
10199
- const url = clean(await deps.run("gh", ["pr", "create", "--repo", ctx.repo, "--base", "development", "--head", "main", "--title", `chore(release): align development to ${tag}`, "--body", body]));
10329
+ const url = clean2(await deps.run("gh", ["pr", "create", "--repo", ctx.repo, "--base", "development", "--head", "main", "--title", `chore(release): align development to ${tag}`, "--body", body]));
10200
10330
  const number = parsePrNumber(url);
10201
10331
  return {
10202
10332
  status: "pr-pending",
@@ -10312,12 +10442,15 @@ Recovery sequence:
10312
10442
  Do not delete or force-move the pushed tag; rerun the train only after confirming the branch, release, and deploy states above.`
10313
10443
  );
10314
10444
  }
10315
- async function ensureTagPushed(deps, tag, sha) {
10316
- const remoteOut = await deps.run("git", ["ls-remote", "origin", `refs/tags/${tag}`]);
10317
- const remoteSha = clean(remoteOut).split(/\s+/)[0] || "";
10445
+ async function probeRemoteTag(deps, tag) {
10446
+ const remoteOut = await runGitRemoteRead(deps, ["ls-remote", "origin", `refs/tags/${tag}`]);
10447
+ return clean2(remoteOut).split(/\s+/)[0] || "";
10448
+ }
10449
+ async function ensureTagPushed(deps, tag, sha, probed) {
10450
+ const remoteSha = probed ? probed.remoteSha : await probeRemoteTag(deps, tag);
10318
10451
  let localSha = "";
10319
10452
  try {
10320
- localSha = clean(await deps.run("git", ["rev-parse", "--verify", `refs/tags/${tag}^{commit}`]));
10453
+ localSha = clean2(await deps.run("git", ["rev-parse", "--verify", `refs/tags/${tag}^{commit}`]));
10321
10454
  } catch {
10322
10455
  }
10323
10456
  if (remoteSha) {
@@ -10335,12 +10468,15 @@ async function ensureTagPushed(deps, tag, sha) {
10335
10468
  throw new Error(`local tag ${tag} already points at ${localSha}, not the intended ${sha} \u2014 delete the stale local tag (git tag -d ${tag}) and rerun`);
10336
10469
  }
10337
10470
  if (!localSha) await deps.run("git", ["tag", tag, sha]);
10338
- await deps.run("git", ["push", "origin", tag]);
10471
+ await runGitPush(deps, ["push", "origin", tag]);
10339
10472
  return { note: `tag ${tag} pushed at ${sha.slice(0, 7)}`, pushed: true };
10340
10473
  }
10341
- async function resolveRcResumeTag(deps, base, sha) {
10342
- const out = await deps.run("git", ["ls-remote", "--tags", "origin", `refs/tags/${base}-rc.*`]);
10343
- const onSha = clean(out).split("\n").map((line) => line.trim()).filter(Boolean).map((line) => line.split(/\s+/)).filter(([refSha]) => refSha === sha).map(([, ref]) => ref.replace(/^refs\/tags\//, "").replace(/\^\{\}$/, "")).filter((ref) => new RegExp(`^${base.replace(/\./g, "\\.")}-rc\\.\\d+$`).test(ref));
10474
+ function probeRcResumeTags(deps, base) {
10475
+ return runGitRemoteRead(deps, ["ls-remote", "--tags", "origin", `refs/tags/${base}-rc.*`]);
10476
+ }
10477
+ async function resolveRcResumeTag(deps, base, sha, probed) {
10478
+ const out = probed ?? await runGitRemoteRead(deps, ["ls-remote", "--tags", "origin", `refs/tags/${base}-rc.*`]);
10479
+ const onSha = clean2(out).split("\n").map((line) => line.trim()).filter(Boolean).map((line) => line.split(/\s+/)).filter(([refSha]) => refSha === sha).map(([, ref]) => ref.replace(/^refs\/tags\//, "").replace(/\^\{\}$/, "")).filter((ref) => new RegExp(`^${base.replace(/\./g, "\\.")}-rc\\.\\d+$`).test(ref));
10344
10480
  const unique = [...new Set(onSha)];
10345
10481
  if (unique.length === 0) return { tag: null };
10346
10482
  const rcNum = (t) => Number.parseInt(t.replace(/^.*-rc\./, ""), 10);
@@ -10536,7 +10672,7 @@ async function preflightMergeToMain(deps, deployModel, remoteRef, blockingPrefix
10536
10672
  async function executeMergeToMain(deps, sourceRef, mergeLabel, tolerated, predicted, preFold) {
10537
10673
  await deps.run("git", ["checkout", "main"]);
10538
10674
  await ffOnlyPull(deps, "main");
10539
- preFold.mainSha = clean(await deps.run("git", ["rev-parse", "main"]));
10675
+ preFold.mainSha = clean2(await deps.run("git", ["rev-parse", "main"]));
10540
10676
  if (predicted.length === 0) {
10541
10677
  await deps.run("git", ["merge", sourceRef, "--no-edit"]);
10542
10678
  } else {
@@ -10547,8 +10683,8 @@ async function probeFoldFailureState(deps) {
10547
10683
  const branch = await currentBranch(deps);
10548
10684
  const mergeInProgress = await deps.run("git", ["rev-parse", "-q", "--verify", "MERGE_HEAD"]).then(() => true).catch(() => false);
10549
10685
  const dirty = porcelainHasBlockingChanges(await deps.run("git", ["status", "--porcelain"]).catch(() => ""));
10550
- const mainSha = await deps.run("git", ["rev-parse", "main"]).then(clean).catch(() => "");
10551
- const originMainSha = await deps.run("git", ["rev-parse", "origin/main"]).then(clean).catch(() => "");
10686
+ const mainSha = await deps.run("git", ["rev-parse", "main"]).then(clean2).catch(() => "");
10687
+ const originMainSha = await deps.run("git", ["rev-parse", "origin/main"]).then(clean2).catch(() => "");
10552
10688
  const originIsAncestorOfMain = Boolean(mainSha) && Boolean(originMainSha) ? await deps.run("git", ["merge-base", "--is-ancestor", "origin/main", "main"]).then(() => true).catch(() => false) : false;
10553
10689
  return { branch, mergeInProgress, dirty, mainSha, originMainSha, originIsAncestorOfMain };
10554
10690
  }
@@ -10590,6 +10726,49 @@ Recovery sequence:
10590
10726
  ${steps.join("\n")}`
10591
10727
  );
10592
10728
  }
10729
+ async function recoverFailedRcand(deps, cause, preRcSha) {
10730
+ const causeMessage = cause instanceof Error ? cause.message : String(cause);
10731
+ const branch = await currentBranch(deps).catch(() => "");
10732
+ const mergeInProgress = await deps.run("git", ["rev-parse", "-q", "--verify", "MERGE_HEAD"]).then(() => true).catch(() => false);
10733
+ if (mergeInProgress) {
10734
+ try {
10735
+ await deps.run("git", ["merge", "--abort"]);
10736
+ } catch (e) {
10737
+ return new Error(
10738
+ `${causeMessage}
10739
+
10740
+ rcand failed with an in-progress merge on ${branch || "(unknown branch)"}; automatic \`git merge --abort\` failed: ${e instanceof Error ? e.message : String(e)}. Nothing was pushed to origin.
10741
+ Recovery sequence:
10742
+ 1. git merge --abort
10743
+ 2. git checkout development
10744
+ 3. git branch -f rc origin/rc`
10745
+ );
10746
+ }
10747
+ }
10748
+ const rcSha = await deps.run("git", ["rev-parse", "rc"]).then(clean2).catch(() => "");
10749
+ const rcDescends = Boolean(preRcSha) && Boolean(rcSha) ? await deps.run("git", ["merge-base", "--is-ancestor", preRcSha, "rc"]).then(() => true).catch(() => false) : false;
10750
+ if (branch === "rc" && rcDescends) {
10751
+ try {
10752
+ await deps.run("git", ["reset", "--hard", preRcSha]);
10753
+ await deps.run("git", ["checkout", "development"]);
10754
+ return new Error(
10755
+ `${causeMessage}
10756
+
10757
+ rcand failed after \`git checkout rc\`; local rc was reset to its pre-merge state (${shaLabel(preRcSha)}) and the checkout returned to development. Nothing was pushed to origin. Rerun \`mmi-cli rcand --apply\`.`
10758
+ );
10759
+ } catch {
10760
+ }
10761
+ }
10762
+ return new Error(
10763
+ `${causeMessage}
10764
+
10765
+ rcand failed leaving the checkout on ${branch || "(unknown)"} (pre-merge rc=${preRcSha ? shaLabel(preRcSha) : "not captured"}). Nothing was pushed to origin.
10766
+ Recovery sequence:
10767
+ 1. git checkout development
10768
+ 2. git branch -f rc origin/rc # discard the unpushed local rc merge
10769
+ 3. mmi-cli rcand --apply`
10770
+ );
10771
+ }
10593
10772
  async function recoverFailedFold(deps, cause, startBranch, preFoldMainSha) {
10594
10773
  const causeMessage = cause instanceof Error ? cause.message : String(cause);
10595
10774
  const probe = await probeFoldFailureState(deps);
@@ -10641,8 +10820,13 @@ async function runFoldStage(deps, startBranch, preFold, fn) {
10641
10820
  throw await recoverFailedFold(deps, e, startBranch, preFold.mainSha);
10642
10821
  }
10643
10822
  }
10644
- async function completeMainRelease(deps, ctx, meta, deployModel, watch, options, tag, releaseSha) {
10645
- const tagPush = await ensureTagPushed(deps, tag, releaseSha);
10823
+ async function completeMainRelease(deps, ctx, meta, deployModel, watch, options, tag, releaseSha, startBranch, preFold, tagProbe) {
10824
+ let tagPush;
10825
+ try {
10826
+ tagPush = await ensureTagPushed(deps, tag, releaseSha, tagProbe);
10827
+ } catch (e) {
10828
+ throw await recoverFailedFold(deps, e, startBranch, preFold.mainSha);
10829
+ }
10646
10830
  const tagPushSince = (deps.now ?? Date.now)();
10647
10831
  const requiredChecks = await discoverRequiredCheckContexts(deps, ctx, "main");
10648
10832
  let checks;
@@ -10651,8 +10835,8 @@ async function completeMainRelease(deps, ctx, meta, deployModel, watch, options,
10651
10835
  } catch (e) {
10652
10836
  throw partialTrainRecoveryError(e, { repo: ctx.repo, tag, stage: "main" });
10653
10837
  }
10654
- await deps.run("git", ["push", "origin", "main"]);
10655
- const releaseUrl = clean(await deps.run("gh", ["release", "create", tag, "--target", "main", "--generate-notes", "--latest", "--repo", ctx.repo])) || void 0;
10838
+ await runGitPush(deps, ["push", "origin", "main"]);
10839
+ const releaseUrl = clean2(await deps.run("gh", ["release", "create", tag, "--target", "main", "--generate-notes", "--latest", "--repo", ctx.repo])) || void 0;
10656
10840
  await verifyPublishedRelease(deps, ctx.repo, tag, "main", releaseSha);
10657
10841
  const announceNote = deps.announce ? (await deps.announce({ repo: ctx.repo, tag, summaryFile: options.announceSummaryFile })).note : void 0;
10658
10842
  const autoRunSince = (deps.now ?? Date.now)();
@@ -10690,16 +10874,26 @@ async function runTrainApplyPipeline(mode, input) {
10690
10874
  "nothing to promote: origin/development is not ahead of origin/rc"
10691
10875
  );
10692
10876
  const deployModel2 = await preflight(deps, ctx, "rc", meta);
10693
- const releaseBase = requireValue(clean(await deps.run("node", ["scripts/next-version.mjs", "cycle"])), "release base");
10877
+ const releaseBase = requireValue(clean2(await deps.run("node", ["scripts/next-version.mjs", "cycle"])), "release base");
10694
10878
  await releaseRcBranchLock(deps);
10879
+ const rcTagProbe = await probeRcResumeTags(deps, releaseBase);
10695
10880
  await deps.run("git", ["checkout", "rc"]);
10696
10881
  await ffOnlyPull(deps, "rc");
10697
- await deps.run("git", ["merge", "development", "--no-edit"]);
10698
- const rcSha = requireValue(clean(await deps.run("git", ["rev-parse", "rc"])), "rc sha");
10699
- const resume = await resolveRcResumeTag(deps, releaseBase, rcSha);
10700
- const tag2 = resume.tag ?? requireValue(clean(await deps.run("node", ["scripts/next-version.mjs", "rc"])), "rc tag");
10882
+ const preRcSha = requireValue(clean2(await deps.run("git", ["rev-parse", "rc"])), "pre-merge rc sha");
10883
+ let rcSha;
10884
+ let resume;
10885
+ let tag2;
10886
+ let tagPush;
10887
+ try {
10888
+ await deps.run("git", ["merge", "development", "--no-edit"]);
10889
+ rcSha = requireValue(clean2(await deps.run("git", ["rev-parse", "rc"])), "rc sha");
10890
+ resume = await resolveRcResumeTag(deps, releaseBase, rcSha, rcTagProbe);
10891
+ tag2 = resume.tag ?? requireValue(clean2(await deps.run("node", ["scripts/next-version.mjs", "rc"])), "rc tag");
10892
+ tagPush = await ensureTagPushed(deps, tag2, rcSha);
10893
+ } catch (e) {
10894
+ throw await recoverFailedRcand(deps, e, preRcSha);
10895
+ }
10701
10896
  const resumeNote = resume.tag ? resume.note : void 0;
10702
- const tagPush = await ensureTagPushed(deps, tag2, rcSha);
10703
10897
  const tagPushSince = (deps.now ?? Date.now)();
10704
10898
  const requiredChecks = await discoverRequiredCheckContexts(deps, ctx, "rc");
10705
10899
  let checks2;
@@ -10709,7 +10903,7 @@ async function runTrainApplyPipeline(mode, input) {
10709
10903
  throw partialTrainRecoveryError(e, { repo: ctx.repo, tag: tag2, stage: "rc" });
10710
10904
  }
10711
10905
  const autoRunSince = (deps.now ?? Date.now)();
10712
- await deps.run("git", ["push", "origin", "rc"]);
10906
+ await runGitPush(deps, ["push", "origin", "rc"]);
10713
10907
  const d2 = await dispatchDeploy(deps, ctx, "rc", "rc", deployModel2, watch, autoRunSince, rcSha);
10714
10908
  return { ...ctx, command, stage: "rc", ref: "rc", tag: tag2, deployModel: deployModel2, promoted: true, checks: checks2, resumeNote, dispatch: d2.note, runId: d2.runId, runUrl: d2.runUrl, workflowRuns: d2.workflowRuns, deployStatus: d2.deployStatus };
10715
10909
  }
@@ -10732,8 +10926,8 @@ async function runTrainApplyPipeline(mode, input) {
10732
10926
  "nothing to release: origin/development is not ahead of origin/main"
10733
10927
  );
10734
10928
  const deployModel2 = await preflight(deps, ctx, "main", meta);
10735
- const tag2 = requireValue(clean(await deps.run("node", ["scripts/next-version.mjs", "cycle"])), "release tag");
10736
- const rcShaAtRelease = !directTrack && hasRcBranch ? clean(await deps.run("git", ["rev-parse", "origin/rc"])) : "";
10929
+ const tag2 = requireValue(clean2(await deps.run("node", ["scripts/next-version.mjs", "cycle"])), "release tag");
10930
+ const rcShaAtRelease = !directTrack && hasRcBranch ? clean2(await deps.run("git", ["rev-parse", "origin/rc"])) : "";
10737
10931
  const { foldPaths: foldPaths2, tolerated: tolerated2, predicted: predicted2 } = await preflightMergeToMain(
10738
10932
  deps,
10739
10933
  deployModel2,
@@ -10741,14 +10935,15 @@ async function runTrainApplyPipeline(mode, input) {
10741
10935
  "development -> main merge would conflict on untolerated path(s)",
10742
10936
  "The train is misaligned: reconcile main and development via an approved alignment PR, then rerun release."
10743
10937
  );
10938
+ const tagProbe2 = { remoteSha: await probeRemoteTag(deps, tag2) };
10744
10939
  const preFold2 = {};
10745
10940
  const { versionFold: versionFold2, releaseSha: releaseSha2 } = await runFoldStage(deps, "development", preFold2, async () => {
10746
10941
  await executeMergeToMain(deps, "development", "development -> main", tolerated2, predicted2, preFold2);
10747
10942
  const versionFold3 = await foldReleaseVersion(deps, deployModel2, tag2, foldPaths2);
10748
- const releaseSha3 = requireValue(clean(await deps.run("git", ["rev-parse", "main"])), "release sha");
10943
+ const releaseSha3 = requireValue(clean2(await deps.run("git", ["rev-parse", "main"])), "release sha");
10749
10944
  return { versionFold: versionFold3, releaseSha: releaseSha3 };
10750
10945
  });
10751
- const { checks: checks2, releaseUrl: releaseUrl2, announceNote: announceNote2, dispatch: d2 } = await completeMainRelease(deps, ctx, meta, deployModel2, watch, options, tag2, releaseSha2);
10946
+ const { checks: checks2, releaseUrl: releaseUrl2, announceNote: announceNote2, dispatch: d2 } = await completeMainRelease(deps, ctx, meta, deployModel2, watch, options, tag2, releaseSha2, "development", preFold2, tagProbe2);
10752
10947
  const devRollForward2 = await rollDevelopmentForward(deps, ctx, tag2);
10753
10948
  if (directTrack) {
10754
10949
  return {
@@ -10823,16 +11018,17 @@ async function runTrainApplyPipeline(mode, input) {
10823
11018
  `hotfix-coverage: ${coverage.uncovered.length} main-only commit(s) not proven in origin/rc \u2014 the candidate would revert a prod hotfix: ${list}. Re-cut /rcand from development, or have the authorized human verify the content is in the candidate and rerun release with --ack <sha>[,<sha>\u2026].`
10824
11019
  );
10825
11020
  }
10826
- const releasedRcSha = clean(await deps.run("git", ["rev-parse", "origin/rc"]));
11021
+ const releasedRcSha = clean2(await deps.run("git", ["rev-parse", "origin/rc"]));
11022
+ const tag = requireValue(clean2(await deps.run("node", ["scripts/next-version.mjs", "release"])), "release tag");
11023
+ const tagProbe = { remoteSha: await probeRemoteTag(deps, tag) };
10827
11024
  const preFold = {};
10828
- const { tag, versionFold, releaseSha } = await runFoldStage(deps, "rc", preFold, async () => {
11025
+ const { versionFold, releaseSha } = await runFoldStage(deps, "rc", preFold, async () => {
10829
11026
  await executeMergeToMain(deps, "rc", "rc -> main", tolerated, predicted, preFold);
10830
- const tag2 = requireValue(clean(await deps.run("node", ["scripts/next-version.mjs", "release"])), "release tag");
10831
- const versionFold2 = await foldReleaseVersion(deps, deployModel, tag2, foldPaths);
10832
- const releaseSha2 = requireValue(clean(await deps.run("git", ["rev-parse", "main"])), "release sha");
10833
- return { tag: tag2, versionFold: versionFold2, releaseSha: releaseSha2 };
11027
+ const versionFold2 = await foldReleaseVersion(deps, deployModel, tag, foldPaths);
11028
+ const releaseSha2 = requireValue(clean2(await deps.run("git", ["rev-parse", "main"])), "release sha");
11029
+ return { versionFold: versionFold2, releaseSha: releaseSha2 };
10834
11030
  });
10835
- const { checks, releaseUrl, announceNote, dispatch: d } = await completeMainRelease(deps, ctx, meta, deployModel, watch, options, tag, releaseSha);
11031
+ const { checks, releaseUrl, announceNote, dispatch: d } = await completeMainRelease(deps, ctx, meta, deployModel, watch, options, tag, releaseSha, "rc", preFold, tagProbe);
10836
11032
  const retirement = await retireRcRuntime(deps, ctx, deployModel, d.deployStatus, releasedRcSha);
10837
11033
  const devRollForward = await rollDevelopmentForward(deps, ctx, tag);
10838
11034
  const rcAlignment = await pushRcAlignment(deps);
@@ -10866,7 +11062,7 @@ async function runTrainApply(command, deps, options = {}) {
10866
11062
  const watch = options.watch ?? false;
10867
11063
  const ctx = await buildTrainApplyContext(deps);
10868
11064
  await requireCleanTree(deps);
10869
- await deps.run("git", ["fetch", "origin"]);
11065
+ await runGitRemoteRead(deps, ["fetch", "origin"]);
10870
11066
  const meta = requireProjectMetaForTrain(await loadProjectMeta(deps, ctx), ctx.repo);
10871
11067
  const branchHints = await loadReleaseTrackBranchHints(deps);
10872
11068
  const directTrack = isHubControlRepo(ctx.repo) || resolveReleaseTrack(meta, branchHints) === "direct";
@@ -10948,7 +11144,7 @@ async function retireRcRuntime(deps, ctx, model, deployStatus, releasedRcSha) {
10948
11144
  }
10949
11145
  try {
10950
11146
  await deps.run("git", ["fetch", "origin", "rc"]);
10951
- const rcNow = clean(await deps.run("git", ["rev-parse", "origin/rc"]));
11147
+ const rcNow = clean2(await deps.run("git", ["rev-parse", "origin/rc"]));
10952
11148
  if (rcNow !== releasedRcSha) {
10953
11149
  return {
10954
11150
  status: "skipped",
@@ -10979,7 +11175,7 @@ async function runTenantRedeploy(deps, options) {
10979
11175
  const repo = options.repo;
10980
11176
  const [owner, name] = repo.split("/");
10981
11177
  if (!owner || !name) throw new Error(`repo must be owner/name, got ${repo}`);
10982
- const login = requireValue(clean(await deps.run("gh", ["api", "user", "--jq", ".login"])), "GitHub login");
11178
+ const login = requireValue(clean2(await deps.run("gh", ["api", "user", "--jq", ".login"])), "GitHub login");
10983
11179
  const verdict = await deps.trainAuthority(repo);
10984
11180
  if (!verdict.ok) throw new Error(`${commandAuthorityLabel(owner)}: train authority could not be verified (${verdict.error})`);
10985
11181
  if (!verdict.train) throw new Error(`${commandAuthorityLabel(owner)}: @${login} is ${verdict.role} \u2014 no train authority on ${repo}`);
@@ -11219,7 +11415,7 @@ var HOTFIX_RUN_FIND_ATTEMPTS = 10;
11219
11415
  var HOTFIX_RUN_FIND_DELAY_MS = 15e3;
11220
11416
  var HOTFIX_VERIFY_ATTEMPTS = 5;
11221
11417
  var HOTFIX_VERIFY_RETRY_MS = 6e3;
11222
- function clean2(out) {
11418
+ function clean3(out) {
11223
11419
  return out.trim();
11224
11420
  }
11225
11421
  function sleeper(deps) {
@@ -11277,7 +11473,7 @@ async function resolveHotfixSource(deps, ctx, from) {
11277
11473
  if (!sha2) throw new Error(`PR #${num} has no merge commit recorded \u2014 name the commit SHA explicitly`);
11278
11474
  return { sha: sha2, label: `PR #${num} (${sha2.slice(0, 7)})` };
11279
11475
  }
11280
- const sha = clean2(await deps.run("git", ["rev-parse", "--verify", `${from}^{commit}`]));
11476
+ const sha = clean3(await deps.run("git", ["rev-parse", "--verify", `${from}^{commit}`]));
11281
11477
  if (!sha) throw new Error(`could not resolve commit ${from}`);
11282
11478
  return { sha, label: sha.slice(0, 7) };
11283
11479
  }
@@ -11305,7 +11501,7 @@ async function runHotfixStart(deps, options) {
11305
11501
  };
11306
11502
  }
11307
11503
  const { sha, label } = await resolveHotfixSource(deps, ctx, options.from);
11308
- const remoteBranch = clean2(await deps.run("git", ["ls-remote", "origin", `refs/heads/${branch}`]));
11504
+ const remoteBranch = clean3(await deps.run("git", ["ls-remote", "origin", `refs/heads/${branch}`]));
11309
11505
  if (remoteBranch) {
11310
11506
  await deps.run("git", ["checkout", branch]);
11311
11507
  await deps.run("git", ["pull", "--ff-only", "origin", branch]);
@@ -11336,7 +11532,7 @@ async function runHotfixStart(deps, options) {
11336
11532
  await deps.run("git", ["push", "-u", "origin", branch]);
11337
11533
  }
11338
11534
  const bumpNote = deployModel === "hub-serverless" ? " with the locked distribution bump" : "";
11339
- const prUrl = clean2(await deps.run("gh", [
11535
+ const prUrl = clean3(await deps.run("gh", [
11340
11536
  "pr",
11341
11537
  "create",
11342
11538
  "--repo",
@@ -11480,7 +11676,7 @@ async function runHotfixRelease(deps, versionInput, options = {}) {
11480
11676
  }
11481
11677
  let verifyNote;
11482
11678
  if (deployModel === "hub-serverless") {
11483
- const previousRef = clean2(await deps.run("git", ["rev-parse", "--abbrev-ref", "HEAD"]));
11679
+ const previousRef = clean3(await deps.run("git", ["rev-parse", "--abbrev-ref", "HEAD"]));
11484
11680
  const publishSucceeded = runs.find((r) => r.workflow === "publish.yml")?.conclusion === "success";
11485
11681
  try {
11486
11682
  await deps.run("git", ["-c", "advice.detachedHead=false", "checkout", tag]);
@@ -11567,9 +11763,9 @@ async function runHotfixStatus(deps, versionInput) {
11567
11763
  return { ...ctx, command: "hotfix-status", ...facts, ...deriveHotfixState(facts), warnings };
11568
11764
  }
11569
11765
  async function gatherHotfixFacts(deps, ctx, tag, version) {
11570
- const branchExists = Boolean(clean2(await deps.run("git", ["ls-remote", "origin", `refs/heads/${hotfixBranch(tag)}`])));
11766
+ const branchExists = Boolean(clean3(await deps.run("git", ["ls-remote", "origin", `refs/heads/${hotfixBranch(tag)}`])));
11571
11767
  const pr2 = await findHotfixPr(deps, ctx, tag);
11572
- const remoteTag = clean2(await deps.run("git", ["ls-remote", "origin", `refs/tags/${tag}`]));
11768
+ const remoteTag = clean3(await deps.run("git", ["ls-remote", "origin", `refs/tags/${tag}`]));
11573
11769
  const tagPushed = Boolean(remoteTag);
11574
11770
  const tagSha = remoteTag.split(/\s+/)[0] || "";
11575
11771
  let releaseExists = false;
@@ -11603,7 +11799,7 @@ async function gatherHotfixFacts(deps, ctx, tag, version) {
11603
11799
  }
11604
11800
  }
11605
11801
  }
11606
- const npmVersion = await deps.run("npm", ["view", "@mutmutco/cli", "version", "--silent"]).then(clean2, () => "unknown");
11802
+ const npmVersion = await deps.run("npm", ["view", "@mutmutco/cli", "version", "--silent"]).then(clean3, () => "unknown");
11607
11803
  return { tag, version, branchExists, pr: pr2 ? { number: pr2.number, state: pr2.state, url: pr2.url } : null, tagPushed, releaseExists, runs, npmVersion };
11608
11804
  }
11609
11805
  function compareHotfixVersions(a, b) {
@@ -11638,7 +11834,7 @@ async function findInFlightHotfixVersion(deps, ctx, latestMainTag) {
11638
11834
  const m = typeof row.headRefName === "string" && /^hotfix\/(v\d+\.\d+\.\d+)/.exec(row.headRefName);
11639
11835
  if (m) tags.add(m[1]);
11640
11836
  }
11641
- const branchOut = clean2(await deps.run("git", ["ls-remote", "origin", "refs/heads/hotfix/v*"]));
11837
+ const branchOut = clean3(await deps.run("git", ["ls-remote", "origin", "refs/heads/hotfix/v*"]));
11642
11838
  for (const line of branchOut.split("\n").filter(Boolean)) {
11643
11839
  const ref = line.split(/\s+/)[1] ?? "";
11644
11840
  const m = /^refs\/heads\/hotfix\/(v\d+\.\d+\.\d+)/.exec(ref);
@@ -12130,7 +12326,7 @@ function renderAccessReport(report) {
12130
12326
  }
12131
12327
 
12132
12328
  // src/bootstrap-verify.ts
12133
- var TRAIN_BRANCHES = ["development", "rc", "main"];
12329
+ var TRAIN_BRANCHES2 = ["development", "rc", "main"];
12134
12330
  var requiredDocs = ["README.md", "architecture.md"];
12135
12331
  var requiredIssueTemplates = [
12136
12332
  ".github/ISSUE_TEMPLATE/bug.yml",
@@ -12308,7 +12504,7 @@ async function verifyBootstrap(repo, repoClass, deps, releaseTrack) {
12308
12504
  detail: hasPushAllowlist(protection) ? void 0 : "restrictions.users is empty or unset"
12309
12505
  });
12310
12506
  }
12311
- const strayTrainBranches = TRAIN_BRANCHES.filter((b) => !branchesWanted.includes(b) && branchNames.has(b));
12507
+ const strayTrainBranches = TRAIN_BRANCHES2.filter((b) => !branchesWanted.includes(b) && branchNames.has(b));
12312
12508
  checks.push({
12313
12509
  ok: strayTrainBranches.length === 0,
12314
12510
  label: "no stray train branch for this track",
@@ -12783,13 +12979,13 @@ var WIKI_STAMP_FILE = ".wiki-state.json";
12783
12979
  function redactWikiSecrets(msg) {
12784
12980
  return msg.replace(/x-access-token:[^@\s]+@/gi, "x-access-token:***@").replace(/(AUTHORIZATION:\s*basic\s+)[A-Za-z0-9+/=]+/gi, "$1***");
12785
12981
  }
12982
+ function wikiAuthArgs(token) {
12983
+ return ["-c", `http.extraheader=AUTHORIZATION: basic ${Buffer.from(`x-access-token:${token}`).toString("base64")}`];
12984
+ }
12786
12985
  function publishWikiViaGit(deps, opts) {
12787
12986
  const dir = deps.mkdtemp();
12788
12987
  const url = `https://github.com/${opts.repo}.wiki.git`;
12789
- const authArgs = [
12790
- "-c",
12791
- `http.extraheader=AUTHORIZATION: basic ${Buffer.from(`x-access-token:${opts.token}`).toString("base64")}`
12792
- ];
12988
+ const authArgs = wikiAuthArgs(opts.token);
12793
12989
  try {
12794
12990
  deps.gitRun(["init", "-q"], dir);
12795
12991
  deps.gitRun(["remote", "add", "origin", url], dir);
@@ -12844,12 +13040,37 @@ function publishWikiViaGit(deps, opts) {
12844
13040
  deps.rm(dir);
12845
13041
  }
12846
13042
  }
13043
+ function readWikiStampViaGit(deps, opts) {
13044
+ const dir = deps.mkdtemp();
13045
+ const url = `https://github.com/${opts.repo}.wiki.git`;
13046
+ const authArgs = wikiAuthArgs(opts.token);
13047
+ try {
13048
+ deps.gitRun(["init", "-q"], dir);
13049
+ deps.gitRun(["remote", "add", "origin", url], dir);
13050
+ if (!deps.gitProbe([...authArgs, "ls-remote", "origin"], dir)) {
13051
+ return { ok: false, error: `could not reach ${opts.repo}.wiki.git to read the wiki stamp \u2014 auth or network failure` };
13052
+ }
13053
+ if (!deps.gitProbe([...authArgs, "fetch", "--depth", "1", "origin", "master"], dir)) {
13054
+ return { ok: true, stamp: {} };
13055
+ }
13056
+ deps.gitRun(["reset", "--hard", "FETCH_HEAD"], dir);
13057
+ const stampPath = (0, import_node_path14.join)(dir, WIKI_STAMP_FILE);
13058
+ if (!deps.fileExists(stampPath)) return { ok: true, stamp: {} };
13059
+ const parsed = JSON.parse(deps.readFile(stampPath));
13060
+ return { ok: true, stamp: parsed && typeof parsed === "object" ? parsed : {} };
13061
+ } catch (e) {
13062
+ return { ok: false, error: redactWikiSecrets(e.message) };
13063
+ } finally {
13064
+ deps.rm(dir);
13065
+ }
13066
+ }
12847
13067
  function realWikiGitPublishDeps() {
12848
13068
  return {
12849
13069
  mkdtemp: () => (0, import_node_fs13.mkdtempSync)((0, import_node_path14.join)((0, import_node_os5.tmpdir)(), "wiki-")),
12850
13070
  readdir: (dir) => (0, import_node_fs13.readdirSync)(dir),
12851
13071
  copyFile: (src, dest) => (0, import_node_fs13.copyFileSync)(src, dest),
12852
13072
  fileExists: (p) => (0, import_node_fs13.existsSync)(p),
13073
+ readFile: (p) => (0, import_node_fs13.readFileSync)(p, "utf8"),
12853
13074
  rm: (p) => (0, import_node_fs13.rmSync)(p, { recursive: true, force: true }),
12854
13075
  gitProbe: (args, cwd) => {
12855
13076
  try {
@@ -12867,6 +13088,124 @@ function realWikiGitPublishDeps() {
12867
13088
  };
12868
13089
  }
12869
13090
 
13091
+ // src/wiki-pages.ts
13092
+ var import_node_crypto4 = require("node:crypto");
13093
+ var import_node_fs14 = require("node:fs");
13094
+ var import_node_path15 = require("node:path");
13095
+ var MARKER_RE = /<!--\s*wiki:page\s+([^>]*?)-->/g;
13096
+ var attr = (raw, key) => {
13097
+ const m = new RegExp(`${key}\\s*=\\s*"([^"]*)"`).exec(raw);
13098
+ return m ? m[1].trim() : "";
13099
+ };
13100
+ var sanitizeSlug = (s) => String(s ?? "").trim().replace(/[^\w.-]+/g, "-").replace(/^-+|-+$/g, "");
13101
+ function extractWikiMarkers(text, sourcePath = "") {
13102
+ const out = [];
13103
+ for (const m of String(text ?? "").matchAll(MARKER_RE)) {
13104
+ const raw = m[1];
13105
+ const slug = sanitizeSlug(attr(raw, "slug"));
13106
+ const title = attr(raw, "title");
13107
+ if (!slug || !title) continue;
13108
+ out.push({ slug, title, artifact: attr(raw, "artifact"), sourcePath });
13109
+ }
13110
+ return out;
13111
+ }
13112
+ var DURABLE_GLOBS = [
13113
+ "architecture.md",
13114
+ "docs/Architecture",
13115
+ "kb/architecture",
13116
+ "docs/org-architecture.md",
13117
+ "docs/org-readme.md"
13118
+ ];
13119
+ var walkMarkdown = (root, rel) => {
13120
+ const abs = (0, import_node_path15.join)(root, rel);
13121
+ let st;
13122
+ try {
13123
+ st = (0, import_node_fs14.statSync)(abs);
13124
+ } catch {
13125
+ return [];
13126
+ }
13127
+ if (st.isFile()) return rel.endsWith(".md") ? [rel] : [];
13128
+ return (0, import_node_fs14.readdirSync)(abs).flatMap((name) => walkMarkdown(root, (0, import_node_path15.join)(rel, name)));
13129
+ };
13130
+ function collectFeaturePages({ listDocs, readDoc }) {
13131
+ const seen = /* @__PURE__ */ new Set();
13132
+ const pages = [];
13133
+ for (const path2 of listDocs()) {
13134
+ for (const mk of extractWikiMarkers(readDoc(path2), path2)) {
13135
+ if (seen.has(mk.slug)) continue;
13136
+ seen.add(mk.slug);
13137
+ pages.push(mk);
13138
+ }
13139
+ }
13140
+ return pages;
13141
+ }
13142
+ function fsReaders(rootDir) {
13143
+ return {
13144
+ listDocs: () => DURABLE_GLOBS.flatMap((g) => walkMarkdown(rootDir, g)),
13145
+ readDoc: (p) => {
13146
+ try {
13147
+ return (0, import_node_fs14.readFileSync)((0, import_node_path15.join)(rootDir, p), "utf8");
13148
+ } catch {
13149
+ return "";
13150
+ }
13151
+ }
13152
+ };
13153
+ }
13154
+ function hashPageInputs({ slug, title, source }) {
13155
+ return (0, import_node_crypto4.createHash)("sha256").update(JSON.stringify([slug ?? "", title ?? "", source ?? ""])).digest("hex");
13156
+ }
13157
+ function computePageStamp(pages, readSource) {
13158
+ const stamp = {};
13159
+ for (const p of pages ?? []) {
13160
+ stamp[p.slug] = {
13161
+ hash: hashPageInputs({ slug: p.slug, title: p.title, source: readSource(p.sourcePath) }),
13162
+ sourcePath: p.sourcePath
13163
+ };
13164
+ }
13165
+ return stamp;
13166
+ }
13167
+ function selectChangedPages(pages, prevStamp, readSource) {
13168
+ const prev = prevStamp && typeof prevStamp === "object" ? prevStamp : {};
13169
+ const backfill = Object.keys(prev).length === 0;
13170
+ return (pages ?? []).filter((p) => {
13171
+ if (backfill) return true;
13172
+ const prior = prev[p.slug];
13173
+ if (!prior) return true;
13174
+ const cur = hashPageInputs({ slug: p.slug, title: p.title, source: readSource(p.sourcePath) });
13175
+ return prior.hash !== cur;
13176
+ });
13177
+ }
13178
+
13179
+ // src/wiki-plan-command.ts
13180
+ async function wikiPlan(deps, opts) {
13181
+ const wikiRepo = `${opts.repo}.wiki.git`;
13182
+ const pages = deps.collectPages();
13183
+ if (pages.length === 0) {
13184
+ emit(deps, { repo: opts.repo, wikiRepo, backfill: false, pages: [], stamp: {} });
13185
+ return true;
13186
+ }
13187
+ const read = await deps.mintAndReadStamp(opts.repo);
13188
+ if (!read.ok) {
13189
+ deps.err(`wiki plan failed: could not read the wiki stamp for ${opts.repo}: ${read.error}`);
13190
+ return false;
13191
+ }
13192
+ const prevStamp = read.stamp;
13193
+ const backfill = Object.keys(prevStamp).length === 0;
13194
+ const changed = new Set(selectChangedPages(pages, prevStamp, deps.readSource).map((p) => p.slug));
13195
+ const stamp = computePageStamp(pages, deps.readSource);
13196
+ emit(deps, {
13197
+ repo: opts.repo,
13198
+ wikiRepo,
13199
+ backfill,
13200
+ pages: pages.map((p) => ({ ...p, changed: changed.has(p.slug) })),
13201
+ stamp
13202
+ });
13203
+ return true;
13204
+ }
13205
+ function emit(deps, result) {
13206
+ deps.out(JSON.stringify(result, null, 2));
13207
+ }
13208
+
12870
13209
  // src/project-readiness.ts
12871
13210
  function stagesForTrack(meta) {
12872
13211
  return branchesForTrack(resolveReleaseTrack(meta)).map((b) => b === "development" ? "dev" : b);
@@ -13659,6 +13998,11 @@ function parseFofuEnabledVar(raw) {
13659
13998
  if (raw === "false") return false;
13660
13999
  throw new Error("project set: fofuEnabled must be true or false");
13661
14000
  }
14001
+ function parseRuntimeVaultOnlyVar(raw) {
14002
+ if (raw === "true") return true;
14003
+ if (raw === "false") return false;
14004
+ throw new Error("project set: runtimeVaultOnly must be true or false");
14005
+ }
13662
14006
  function parseConsumesDesignSystemVar(raw) {
13663
14007
  if (raw === "fofu") return raw;
13664
14008
  throw new Error('project set: consumesDesignSystem must be "fofu"');
@@ -13736,6 +14080,7 @@ var SETTABLE_VAR_KEYS = [
13736
14080
  "dsManifestPath",
13737
14081
  "fofuEnabled",
13738
14082
  "consumesDesignSystem",
14083
+ "runtimeVaultOnly",
13739
14084
  "requiredGcpApis",
13740
14085
  "requiredRuntimeSecrets",
13741
14086
  "requiredBuildSecrets",
@@ -13758,6 +14103,7 @@ var SETTABLE_VAR_HINTS = {
13758
14103
  dsManifestPath: "relative path to a package.json, e.g. web/package.json",
13759
14104
  fofuEnabled: "true|false",
13760
14105
  consumesDesignSystem: '"fofu"',
14106
+ runtimeVaultOnly: "true|false",
13761
14107
  repos: 'JSON array, e.g. ["mutmutco/mm-foo"]',
13762
14108
  oauth: "JSON {subdomains,domains,callbackPath,fofuSubdomain}",
13763
14109
  requiredGcpApis: "comma-string",
@@ -13839,6 +14185,8 @@ function buildProjectSetPatch(input) {
13839
14185
  patch[key] = parsePublishRequiredVar(raw);
13840
14186
  } else if (key === "fofuEnabled") {
13841
14187
  patch[key] = parseFofuEnabledVar(raw);
14188
+ } else if (key === "runtimeVaultOnly") {
14189
+ patch[key] = parseRuntimeVaultOnlyVar(raw);
13842
14190
  } else if (key === "consumesDesignSystem") {
13843
14191
  patch[key] = parseConsumesDesignSystemVar(raw);
13844
14192
  } else if (key === "publishDir") {
@@ -13876,22 +14224,22 @@ var DEPLOY_STAGES = ["dev", "rc", "main"];
13876
14224
  var DEPLOY_DOMAIN_RE = /^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)+$/;
13877
14225
  var DEPLOY_SHELL_SAFE_RE = /^[A-Za-z0-9./:_?&=%-]*$/;
13878
14226
  function buildSetDeployPatch(slug, input) {
13879
- const clean3 = (v) => typeof v === "string" && v.trim() !== "" ? v.trim() : void 0;
13880
- const stage2 = clean3(input.stage);
14227
+ const clean4 = (v) => typeof v === "string" && v.trim() !== "" ? v.trim() : void 0;
14228
+ const stage2 = clean4(input.stage);
13881
14229
  if (!stage2 || !DEPLOY_STAGES.includes(stage2)) {
13882
14230
  throw new Error(`project set-deploy: --stage must be one of: ${DEPLOY_STAGES.join(", ")}`);
13883
14231
  }
13884
- const substrate = clean3(input.substrate) ?? "hetzner-ssh";
14232
+ const substrate = clean4(input.substrate) ?? "hetzner-ssh";
13885
14233
  if (!DEPLOY_SUBSTRATES.includes(substrate)) {
13886
14234
  throw new Error(`project set-deploy: --substrate must be one of: ${DEPLOY_SUBSTRATES.join(", ")}`);
13887
14235
  }
13888
- const sshHost = clean3(input.sshHost);
14236
+ const sshHost = clean4(input.sshHost);
13889
14237
  if (substrate === "hetzner-ssh" && !sshHost) {
13890
14238
  throw new Error("project set-deploy: hetzner-ssh requires --ssh-host");
13891
14239
  }
13892
- const sshUser = clean3(input.sshUser) ?? "root";
13893
- const deployPath = clean3(input.deployPath) ?? `/opt/mmi/${slug}/${stage2}`;
13894
- const serviceName = clean3(input.service) ?? slug;
14240
+ const sshUser = clean4(input.sshUser) ?? "root";
14241
+ const deployPath = clean4(input.deployPath) ?? `/opt/mmi/${slug}/${stage2}`;
14242
+ const serviceName = clean4(input.service) ?? slug;
13895
14243
  for (const [label, v] of [["--ssh-host", sshHost], ["--ssh-user", sshUser], ["--deploy-path", deployPath], ["--service", serviceName]]) {
13896
14244
  if (v !== void 0 && !DEPLOY_SHELL_SAFE_RE.test(v)) throw new Error(`project set-deploy: ${label} contains unsafe characters`);
13897
14245
  }
@@ -13900,9 +14248,9 @@ function buildSetDeployPatch(slug, input) {
13900
14248
  port = Number(input.port);
13901
14249
  if (!Number.isInteger(port) || port < 1 || port > 65535) throw new Error("project set-deploy: --port must be an integer 1..65535");
13902
14250
  }
13903
- const domain = clean3(input.domain);
14251
+ const domain = clean4(input.domain);
13904
14252
  if (domain !== void 0 && !DEPLOY_DOMAIN_RE.test(domain)) throw new Error(`project set-deploy: --domain must be a DNS hostname, got ${JSON.stringify(input.domain)}`);
13905
- const aliases = (Array.isArray(input.aliases) ? input.aliases : []).map((a) => clean3(a)).filter((a) => a !== void 0);
14253
+ const aliases = (Array.isArray(input.aliases) ? input.aliases : []).map((a) => clean4(a)).filter((a) => a !== void 0);
13906
14254
  for (const a of aliases) {
13907
14255
  if (!DEPLOY_DOMAIN_RE.test(a)) throw new Error(`project set-deploy: --alias must be a DNS hostname, got ${JSON.stringify(a)}`);
13908
14256
  }
@@ -14809,8 +15157,8 @@ async function secretsUse(deps, key, opts) {
14809
15157
  }
14810
15158
 
14811
15159
  // src/secrets-commands.ts
14812
- var import_node_fs14 = require("node:fs");
14813
- var import_node_path15 = require("node:path");
15160
+ var import_node_fs15 = require("node:fs");
15161
+ var import_node_path16 = require("node:path");
14814
15162
  var RAILS_CREDENTIALS_DECRYPT_TIMEOUT_MS = 3e4;
14815
15163
  var DEFAULT_RAILS_CREDENTIALS_FILE = "config/credentials.yml.enc";
14816
15164
  var DEFAULT_RAILS_MASTER_KEY_FILE = "config/master.key";
@@ -14818,18 +15166,18 @@ function collectMap(value, previous = []) {
14818
15166
  return [...previous, value];
14819
15167
  }
14820
15168
  async function decryptRailsCredentials(input) {
14821
- const appDir = (0, import_node_path15.resolve)(input.appDir ?? process.cwd());
15169
+ const appDir = (0, import_node_path16.resolve)(input.appDir ?? process.cwd());
14822
15170
  const credentialsFile = input.credentialsFile ?? DEFAULT_RAILS_CREDENTIALS_FILE;
14823
15171
  const masterKeyFile = input.masterKeyFile ?? DEFAULT_RAILS_MASTER_KEY_FILE;
14824
- const credentialsPath = (0, import_node_path15.resolve)(appDir, credentialsFile);
14825
- const masterKeyPath = (0, import_node_path15.resolve)(appDir, masterKeyFile);
15172
+ const credentialsPath = (0, import_node_path16.resolve)(appDir, credentialsFile);
15173
+ const masterKeyPath = (0, import_node_path16.resolve)(appDir, masterKeyFile);
14826
15174
  const env = {
14827
15175
  ...process.env,
14828
15176
  MMI_RAILS_CREDENTIALS_FILE: credentialsPath,
14829
15177
  MMI_RAILS_MASTER_KEY_FILE: masterKeyPath
14830
15178
  };
14831
- if ((0, import_node_fs14.existsSync)(masterKeyPath)) {
14832
- env.RAILS_MASTER_KEY = (0, import_node_fs14.readFileSync)(masterKeyPath, "utf8").trim();
15179
+ if ((0, import_node_fs15.existsSync)(masterKeyPath)) {
15180
+ env.RAILS_MASTER_KEY = (0, import_node_fs15.readFileSync)(masterKeyPath, "utf8").trim();
14833
15181
  }
14834
15182
  const script = [
14835
15183
  'require "json"',
@@ -14895,7 +15243,7 @@ function registerSecretsCommands(program3) {
14895
15243
  secrets.command("org-catalog").description("MASTER-ONLY: set the _org/<provider>/* catalog declarations from a JSON file (#2244)").requiredOption("--file <path>", 'JSON file: { "entries": { "<provider>/<KEY>": {key,purpose,group,owner,provider,stages[],consumers[]} } }').action((o) => withSecrets(async (d) => {
14896
15244
  let body;
14897
15245
  try {
14898
- body = (0, import_node_fs14.readFileSync)((0, import_node_path15.resolve)(o.file), "utf8");
15246
+ body = (0, import_node_fs15.readFileSync)((0, import_node_path16.resolve)(o.file), "utf8");
14899
15247
  } catch (e) {
14900
15248
  return fail(`secrets org-catalog: cannot read --file ${o.file}: ${e.message}`);
14901
15249
  }
@@ -14970,7 +15318,7 @@ function registerSecretsCommands(program3) {
14970
15318
  {
14971
15319
  ...d,
14972
15320
  decryptRailsCredentials,
14973
- removeFile: (path2) => (0, import_node_fs14.unlinkSync)((0, import_node_path15.resolve)(o.appDir ?? process.cwd(), path2))
15321
+ removeFile: (path2) => (0, import_node_fs15.unlinkSync)((0, import_node_path16.resolve)(o.appDir ?? process.cwd(), path2))
14974
15322
  },
14975
15323
  {
14976
15324
  repo: o.repo,
@@ -15059,10 +15407,10 @@ function registerEdgeCommands(program3) {
15059
15407
  }
15060
15408
 
15061
15409
  // src/doctor-run.ts
15062
- var import_node_fs18 = require("node:fs");
15410
+ var import_node_fs19 = require("node:fs");
15063
15411
  var import_node_child_process11 = require("node:child_process");
15064
15412
  var import_promises2 = require("node:fs/promises");
15065
- var import_node_path18 = require("node:path");
15413
+ var import_node_path19 = require("node:path");
15066
15414
  var import_node_os7 = require("node:os");
15067
15415
 
15068
15416
  // src/plugin-guard.ts
@@ -15085,9 +15433,9 @@ function buildGuardSessionStartLine(state, opts = {}) {
15085
15433
 
15086
15434
  // src/cursor-plugin-seed.ts
15087
15435
  var import_node_child_process10 = require("node:child_process");
15088
- var import_node_fs15 = require("node:fs");
15436
+ var import_node_fs16 = require("node:fs");
15089
15437
  var import_node_os6 = require("node:os");
15090
- var import_node_path16 = require("node:path");
15438
+ var import_node_path17 = require("node:path");
15091
15439
  var import_node_util7 = require("node:util");
15092
15440
 
15093
15441
  // src/doctor.ts
@@ -16407,17 +16755,17 @@ function ghReleaseTarballApiArgs(tag) {
16407
16755
  }
16408
16756
  function cursorUserGlobalStatePath() {
16409
16757
  if (process.platform === "win32") {
16410
- const base = process.env.APPDATA || (0, import_node_path16.join)((0, import_node_os6.homedir)(), "AppData", "Roaming");
16411
- return (0, import_node_path16.join)(base, "Cursor", "User", "globalStorage", "state.vscdb");
16758
+ const base = process.env.APPDATA || (0, import_node_path17.join)((0, import_node_os6.homedir)(), "AppData", "Roaming");
16759
+ return (0, import_node_path17.join)(base, "Cursor", "User", "globalStorage", "state.vscdb");
16412
16760
  }
16413
16761
  if (process.platform === "darwin") {
16414
- return (0, import_node_path16.join)((0, import_node_os6.homedir)(), "Library", "Application Support", "Cursor", "User", "globalStorage", "state.vscdb");
16762
+ return (0, import_node_path17.join)((0, import_node_os6.homedir)(), "Library", "Application Support", "Cursor", "User", "globalStorage", "state.vscdb");
16415
16763
  }
16416
- return (0, import_node_path16.join)((0, import_node_os6.homedir)(), ".config", "Cursor", "User", "globalStorage", "state.vscdb");
16764
+ return (0, import_node_path17.join)((0, import_node_os6.homedir)(), ".config", "Cursor", "User", "globalStorage", "state.vscdb");
16417
16765
  }
16418
16766
  async function readCursorThirdPartyExtensibilityEnabled(execFileP5) {
16419
16767
  const dbPath = cursorUserGlobalStatePath();
16420
- if (!(0, import_node_fs15.existsSync)(dbPath)) return void 0;
16768
+ if (!(0, import_node_fs16.existsSync)(dbPath)) return void 0;
16421
16769
  try {
16422
16770
  const { stdout } = await execFileP5("sqlite3", [dbPath, `SELECT value FROM ItemTable WHERE key = '${CURSOR_THIRD_PARTY_STATE_KEY}';`], {
16423
16771
  timeout: 5e3
@@ -16431,57 +16779,57 @@ async function readCursorThirdPartyExtensibilityEnabled(execFileP5) {
16431
16779
  }
16432
16780
  }
16433
16781
  function syncDirContents(src, dest) {
16434
- (0, import_node_fs15.mkdirSync)(dest, { recursive: true });
16435
- for (const name of (0, import_node_fs15.readdirSync)(dest)) {
16436
- (0, import_node_fs15.rmSync)((0, import_node_path16.join)(dest, name), { recursive: true, force: true });
16782
+ (0, import_node_fs16.mkdirSync)(dest, { recursive: true });
16783
+ for (const name of (0, import_node_fs16.readdirSync)(dest)) {
16784
+ (0, import_node_fs16.rmSync)((0, import_node_path17.join)(dest, name), { recursive: true, force: true });
16437
16785
  }
16438
- (0, import_node_fs15.cpSync)(src, dest, { recursive: true });
16786
+ (0, import_node_fs16.cpSync)(src, dest, { recursive: true });
16439
16787
  }
16440
16788
  function releaseTag(releasedVersion) {
16441
16789
  return releasedVersion.startsWith("v") ? releasedVersion : `v${releasedVersion}`;
16442
16790
  }
16443
16791
  async function extractPluginMmiFromHubCheckout(hubCheckout, tag, tmpRoot, execFileP5) {
16444
- const tarFile = (0, import_node_path16.join)(tmpRoot, "archive.tar");
16792
+ const tarFile = (0, import_node_path17.join)(tmpRoot, "archive.tar");
16445
16793
  try {
16446
16794
  await execFileP5("git", gitFetchReleaseTagArgs(hubCheckout, tag), { timeout: 6e4 });
16447
16795
  await execFileP5("git", ["-C", hubCheckout, "archive", "--format=tar", `--output=${tarFile}`, tag, "plugins/mmi"], {
16448
16796
  timeout: 6e4
16449
16797
  });
16450
16798
  await execFileP5("tar", ["-xf", tarFile, "-C", tmpRoot], { timeout: 6e4 });
16451
- const pluginMmi = (0, import_node_path16.join)(tmpRoot, "plugins", "mmi");
16452
- return (0, import_node_fs15.existsSync)((0, import_node_path16.join)(pluginMmi, PLUGIN_JSON_REL)) ? pluginMmi : void 0;
16799
+ const pluginMmi = (0, import_node_path17.join)(tmpRoot, "plugins", "mmi");
16800
+ return (0, import_node_fs16.existsSync)((0, import_node_path17.join)(pluginMmi, PLUGIN_JSON_REL)) ? pluginMmi : void 0;
16453
16801
  } catch {
16454
16802
  return void 0;
16455
16803
  }
16456
16804
  }
16457
16805
  async function downloadPluginMmiViaGh(tag, tmpRoot) {
16458
- const tarPath = (0, import_node_path16.join)(tmpRoot, "repo.tgz");
16806
+ const tarPath = (0, import_node_path17.join)(tmpRoot, "repo.tgz");
16459
16807
  try {
16460
- (0, import_node_fs15.mkdirSync)(tmpRoot, { recursive: true });
16808
+ (0, import_node_fs16.mkdirSync)(tmpRoot, { recursive: true });
16461
16809
  const { stdout } = await execFileBuffer("gh", ghReleaseTarballApiArgs(tag), {
16462
16810
  timeout: 12e4,
16463
16811
  maxBuffer: 100 * 1024 * 1024,
16464
16812
  encoding: "buffer",
16465
16813
  windowsHide: true
16466
16814
  });
16467
- (0, import_node_fs15.writeFileSync)(tarPath, stdout);
16815
+ (0, import_node_fs16.writeFileSync)(tarPath, stdout);
16468
16816
  await execFileBuffer("tar", ["-xzf", tarPath, "-C", tmpRoot], { timeout: 12e4, windowsHide: true });
16469
- const top = (0, import_node_fs15.readdirSync)(tmpRoot).find((entry) => entry !== "repo.tgz");
16817
+ const top = (0, import_node_fs16.readdirSync)(tmpRoot).find((entry) => entry !== "repo.tgz");
16470
16818
  if (!top) return void 0;
16471
- const pluginMmi = (0, import_node_path16.join)(tmpRoot, top, "plugins", "mmi");
16472
- return (0, import_node_fs15.existsSync)((0, import_node_path16.join)(pluginMmi, PLUGIN_JSON_REL)) ? pluginMmi : void 0;
16819
+ const pluginMmi = (0, import_node_path17.join)(tmpRoot, top, "plugins", "mmi");
16820
+ return (0, import_node_fs16.existsSync)((0, import_node_path17.join)(pluginMmi, PLUGIN_JSON_REL)) ? pluginMmi : void 0;
16473
16821
  } catch {
16474
16822
  return void 0;
16475
16823
  }
16476
16824
  }
16477
16825
  async function resolvePluginMmiSource(releasedVersion, hubCheckout, tmpRoot, execFileP5) {
16478
- (0, import_node_fs15.mkdirSync)(tmpRoot, { recursive: true });
16826
+ (0, import_node_fs16.mkdirSync)(tmpRoot, { recursive: true });
16479
16827
  const tag = releaseTag(releasedVersion);
16480
16828
  if (hubCheckout) {
16481
16829
  const fromHub = await extractPluginMmiFromHubCheckout(hubCheckout, tag, tmpRoot, execFileP5);
16482
16830
  if (fromHub) return fromHub;
16483
16831
  }
16484
- return downloadPluginMmiViaGh(tag, (0, import_node_path16.join)(tmpRoot, "gh"));
16832
+ return downloadPluginMmiViaGh(tag, (0, import_node_path17.join)(tmpRoot, "gh"));
16485
16833
  }
16486
16834
  function cursorPluginPinsNeedingSeed(pins, releasedVersion) {
16487
16835
  if (!isSemverVersion2(releasedVersion)) return pins.filter((pin) => !pin.hasPluginJson || !pin.hasHooksJson || pin.isEmpty);
@@ -16495,7 +16843,7 @@ function cursorPluginCacheSeedTargets(pins, releasedVersion, cacheRoot, cacheRoo
16495
16843
  const needing = cursorPluginPinsNeedingSeed(pins, releasedVersion);
16496
16844
  if (needing.length > 0) return needing.map((pin) => pin.path);
16497
16845
  if (pins.length === 0 && cacheRoot && cacheRootExists && isSemverVersion2(releasedVersion)) {
16498
- return [(0, import_node_path16.join)(cacheRoot, `v${releasedVersion.replace(/^v/, "")}`)];
16846
+ return [(0, import_node_path17.join)(cacheRoot, `v${releasedVersion.replace(/^v/, "")}`)];
16499
16847
  }
16500
16848
  return [];
16501
16849
  }
@@ -16510,7 +16858,7 @@ async function applyCursorPluginCacheSeed(input) {
16510
16858
  for (const dest of targets) {
16511
16859
  syncDirContents(source, dest);
16512
16860
  }
16513
- (0, import_node_fs15.rmSync)(tmpRoot, { recursive: true, force: true });
16861
+ (0, import_node_fs16.rmSync)(tmpRoot, { recursive: true, force: true });
16514
16862
  return true;
16515
16863
  }
16516
16864
  var CURSOR_ENABLED_PIN_LABEL = "Cursor Team Marketplace enabled plugin pin";
@@ -16869,9 +17217,9 @@ ${block}
16869
17217
  }
16870
17218
 
16871
17219
  // src/cli-doctor-shared.ts
16872
- var import_node_fs16 = require("node:fs");
16873
- var import_node_path17 = require("node:path");
16874
17220
  var import_node_fs17 = require("node:fs");
17221
+ var import_node_path18 = require("node:path");
17222
+ var import_node_fs18 = require("node:fs");
16875
17223
  var GC_GH_TIMEOUT_MS = 2e4;
16876
17224
  async function awsCallerArn() {
16877
17225
  try {
@@ -16917,7 +17265,7 @@ async function localBranchHeads() {
16917
17265
  }
16918
17266
  async function currentRepoWorktreeGitRoot(repoRoot) {
16919
17267
  const gitCommonDir = (await execFileP2("git", ["rev-parse", "--git-common-dir"], { timeout: GIT_TIMEOUT_MS }).catch(() => ({ stdout: "" }))).stdout.trim();
16920
- return gitCommonDir ? (0, import_node_path17.resolve)(repoRoot, gitCommonDir, "worktrees") : "";
17268
+ return gitCommonDir ? (0, import_node_path18.resolve)(repoRoot, gitCommonDir, "worktrees") : "";
16921
17269
  }
16922
17270
  async function worktreeBranches() {
16923
17271
  const { stdout } = await execFileP2("git", ["worktree", "list", "--porcelain"], { timeout: GIT_TIMEOUT_MS });
@@ -16937,18 +17285,18 @@ function resolveGitdirForWorktreeFile(worktreePath, content) {
16937
17285
  const match = /^gitdir:\s*(.+)\s*$/im.exec(content);
16938
17286
  if (!match?.[1]) return void 0;
16939
17287
  const raw = match[1].trim();
16940
- return (0, import_node_path17.isAbsolute)(raw) ? raw : (0, import_node_path17.resolve)(worktreePath, raw);
17288
+ return (0, import_node_path18.isAbsolute)(raw) ? raw : (0, import_node_path18.resolve)(worktreePath, raw);
16941
17289
  }
16942
17290
  function metadataOwnsMissingWorktreeDir(worktreePath, worktreeGitRoot) {
16943
17291
  if (!worktreeGitRoot) return false;
16944
17292
  try {
16945
- const entries = (0, import_node_fs17.readdirSync)(worktreeGitRoot, { withFileTypes: true });
17293
+ const entries = (0, import_node_fs18.readdirSync)(worktreeGitRoot, { withFileTypes: true });
16946
17294
  for (const ent of entries) {
16947
17295
  if (!ent.isDirectory()) continue;
16948
17296
  try {
16949
- const gitdirPath = (0, import_node_fs16.readFileSync)((0, import_node_path17.join)(worktreeGitRoot, ent.name, "gitdir"), "utf8").trim();
16950
- const resolvedGitdir = (0, import_node_path17.isAbsolute)(gitdirPath) ? gitdirPath : (0, import_node_path17.resolve)(worktreeGitRoot, ent.name, gitdirPath);
16951
- if (sameWorktreeMetadataPath((0, import_node_path17.dirname)(resolvedGitdir), worktreePath)) return true;
17297
+ const gitdirPath = (0, import_node_fs17.readFileSync)((0, import_node_path18.join)(worktreeGitRoot, ent.name, "gitdir"), "utf8").trim();
17298
+ const resolvedGitdir = (0, import_node_path18.isAbsolute)(gitdirPath) ? gitdirPath : (0, import_node_path18.resolve)(worktreeGitRoot, ent.name, gitdirPath);
17299
+ if (sameWorktreeMetadataPath((0, import_node_path18.dirname)(resolvedGitdir), worktreePath)) return true;
16952
17300
  } catch {
16953
17301
  }
16954
17302
  }
@@ -16958,7 +17306,7 @@ function metadataOwnsMissingWorktreeDir(worktreePath, worktreeGitRoot) {
16958
17306
  }
16959
17307
  function pathExistsKnown(path2) {
16960
17308
  try {
16961
- (0, import_node_fs17.statSync)(path2);
17309
+ (0, import_node_fs18.statSync)(path2);
16962
17310
  return true;
16963
17311
  } catch (e) {
16964
17312
  const code = typeof e === "object" && e && "code" in e ? String(e.code ?? "") : "";
@@ -16967,10 +17315,10 @@ function pathExistsKnown(path2) {
16967
17315
  }
16968
17316
  }
16969
17317
  function inspectSiblingWorktreeDir(path2, worktreeGitRoot) {
16970
- const gitPath = (0, import_node_path17.join)(path2, ".git");
17318
+ const gitPath = (0, import_node_path18.join)(path2, ".git");
16971
17319
  let st;
16972
17320
  try {
16973
- st = (0, import_node_fs17.lstatSync)(gitPath);
17321
+ st = (0, import_node_fs18.lstatSync)(gitPath);
16974
17322
  } catch (e) {
16975
17323
  const code = typeof e === "object" && e && "code" in e ? String(e.code ?? "") : "";
16976
17324
  if (code === "ENOENT" || code === "ENOTDIR") {
@@ -16987,7 +17335,7 @@ function inspectSiblingWorktreeDir(path2, worktreeGitRoot) {
16987
17335
  if (st.isDirectory()) return { path: path2, gitType: "dir" };
16988
17336
  if (!st.isFile()) return { path: path2, gitType: "other" };
16989
17337
  try {
16990
- const gitFileContent = (0, import_node_fs16.readFileSync)(gitPath, "utf8");
17338
+ const gitFileContent = (0, import_node_fs17.readFileSync)(gitPath, "utf8");
16991
17339
  const gitdir = resolveGitdirForWorktreeFile(path2, gitFileContent);
16992
17340
  const gitDirExists = gitdir ? pathExistsKnown(gitdir) : false;
16993
17341
  return {
@@ -17004,7 +17352,7 @@ function inspectSiblingWorktreeDir(path2, worktreeGitRoot) {
17004
17352
  }
17005
17353
  function inspectDeadWorktreeDirContent(path2) {
17006
17354
  try {
17007
- return { entries: (0, import_node_fs17.readdirSync)(path2) };
17355
+ return { entries: (0, import_node_fs18.readdirSync)(path2) };
17008
17356
  } catch (e) {
17009
17357
  const code = typeof e === "object" && e && "code" in e ? String(e.code ?? "") : "";
17010
17358
  return { error: code ? `unable to inspect directory contents (${code})` : "unable to inspect directory contents" };
@@ -17023,8 +17371,8 @@ async function siblingWorktreeDirs() {
17023
17371
  const worktreeGitRoot = await currentRepoWorktreeGitRoot(repoRoot);
17024
17372
  const siblingRoot = siblingMmiWorktreesRoot(repoRoot);
17025
17373
  try {
17026
- const entries = (0, import_node_fs17.readdirSync)(siblingRoot, { withFileTypes: true });
17027
- return entries.filter((ent) => ent.isDirectory()).map((ent) => inspectSiblingWorktreeDir((0, import_node_path17.join)(siblingRoot, ent.name), worktreeGitRoot)).filter((entry) => Boolean(entry));
17374
+ const entries = (0, import_node_fs18.readdirSync)(siblingRoot, { withFileTypes: true });
17375
+ return entries.filter((ent) => ent.isDirectory()).map((ent) => inspectSiblingWorktreeDir((0, import_node_path18.join)(siblingRoot, ent.name), worktreeGitRoot)).filter((entry) => Boolean(entry));
17028
17376
  } catch {
17029
17377
  return [];
17030
17378
  }
@@ -17074,7 +17422,7 @@ async function fetchHubVersionInfo(baseUrl) {
17074
17422
  }
17075
17423
  function readRepoVersion() {
17076
17424
  try {
17077
- return JSON.parse((0, import_node_fs18.readFileSync)((0, import_node_path18.join)(process.cwd(), ".claude-plugin", "plugin.json"), "utf8")).version || void 0;
17425
+ return JSON.parse((0, import_node_fs19.readFileSync)((0, import_node_path19.join)(process.cwd(), ".claude-plugin", "plugin.json"), "utf8")).version || void 0;
17078
17426
  } catch {
17079
17427
  return void 0;
17080
17428
  }
@@ -17226,11 +17574,11 @@ async function applyPluginHeal(token, surface, log, opts) {
17226
17574
  }
17227
17575
  var installedPluginsPath = (surface = detectSurface(process.env)) => {
17228
17576
  const homeDir = surface === "codex" ? ".codex" : ".claude";
17229
- return (0, import_node_path18.join)((0, import_node_os7.homedir)(), homeDir, "plugins", "installed_plugins.json");
17577
+ return (0, import_node_path19.join)((0, import_node_os7.homedir)(), homeDir, "plugins", "installed_plugins.json");
17230
17578
  };
17231
17579
  function readInstalledPlugins(surface = detectSurface(process.env)) {
17232
17580
  try {
17233
- return JSON.parse((0, import_node_fs18.readFileSync)(installedPluginsPath(surface), "utf8"));
17581
+ return JSON.parse((0, import_node_fs19.readFileSync)(installedPluginsPath(surface), "utf8"));
17234
17582
  } catch {
17235
17583
  return null;
17236
17584
  }
@@ -17238,13 +17586,13 @@ function readInstalledPlugins(surface = detectSurface(process.env)) {
17238
17586
  function marketplaceCloneCandidates(surface, home) {
17239
17587
  if (surface === "codex") {
17240
17588
  return [
17241
- (0, import_node_path18.join)(home, ".codex", ".tmp", "marketplaces", "mutmutco"),
17242
- (0, import_node_path18.join)(home, ".codex", "plugins", "marketplaces", "mutmutco")
17589
+ (0, import_node_path19.join)(home, ".codex", ".tmp", "marketplaces", "mutmutco"),
17590
+ (0, import_node_path19.join)(home, ".codex", "plugins", "marketplaces", "mutmutco")
17243
17591
  ];
17244
17592
  }
17245
- return [(0, import_node_path18.join)(home, ".claude", "plugins", "marketplaces", "mutmutco")];
17593
+ return [(0, import_node_path19.join)(home, ".claude", "plugins", "marketplaces", "mutmutco")];
17246
17594
  }
17247
- function marketplaceClonePresent(surface, home, exists = import_node_fs18.existsSync) {
17595
+ function marketplaceClonePresent(surface, home, exists = import_node_fs19.existsSync) {
17248
17596
  return marketplaceCloneCandidates(surface, home).some(exists);
17249
17597
  }
17250
17598
  function snapshotPluginGuardInput(surface = detectSurface(process.env), isOrgRepo = false) {
@@ -17254,14 +17602,14 @@ function snapshotPluginGuardInput(surface = detectSurface(process.env), isOrgRep
17254
17602
  isOrgRepo,
17255
17603
  installRecordPresent: hasUserInstallRecord(installed, MMI_PLUGIN_ID) || hasProjectInstallRecord(installed, MMI_PLUGIN_ID, process.cwd()),
17256
17604
  marketplaceClonePresent: marketplaceClonePresent(surface, (0, import_node_os7.homedir)()),
17257
- pluginCachePresent: (0, import_node_fs18.existsSync)((0, import_node_path18.join)((0, import_node_os7.homedir)(), homeDir, "plugins", "cache", "mutmutco", "mmi"))
17605
+ pluginCachePresent: (0, import_node_fs19.existsSync)((0, import_node_path19.join)((0, import_node_os7.homedir)(), homeDir, "plugins", "cache", "mutmutco", "mmi"))
17258
17606
  };
17259
17607
  }
17260
17608
  function installedPluginSources() {
17261
17609
  return ["claude", "codex"].map((surface) => {
17262
- const recordPath = (0, import_node_path18.join)((0, import_node_os7.homedir)(), `.${surface}`, "plugins", "installed_plugins.json");
17610
+ const recordPath = (0, import_node_path19.join)((0, import_node_os7.homedir)(), `.${surface}`, "plugins", "installed_plugins.json");
17263
17611
  try {
17264
- return { surface, installed: JSON.parse((0, import_node_fs18.readFileSync)(recordPath, "utf8")), recordPath };
17612
+ return { surface, installed: JSON.parse((0, import_node_fs19.readFileSync)(recordPath, "utf8")), recordPath };
17265
17613
  } catch {
17266
17614
  return { surface, installed: null, recordPath };
17267
17615
  }
@@ -17269,7 +17617,7 @@ function installedPluginSources() {
17269
17617
  }
17270
17618
  function readClaudeSettings() {
17271
17619
  try {
17272
- return JSON.parse((0, import_node_fs18.readFileSync)((0, import_node_path18.join)(process.cwd(), ".claude", "settings.json"), "utf8"));
17620
+ return JSON.parse((0, import_node_fs19.readFileSync)((0, import_node_path19.join)(process.cwd(), ".claude", "settings.json"), "utf8"));
17273
17621
  } catch {
17274
17622
  return null;
17275
17623
  }
@@ -17291,7 +17639,7 @@ function writeProjectInstallRecord(record) {
17291
17639
  const list = file.plugins[MMI_PLUGIN_ID] ?? [];
17292
17640
  list.push(record);
17293
17641
  file.plugins[MMI_PLUGIN_ID] = list;
17294
- (0, import_node_fs18.writeFileSync)(installedPluginsPath(), `${JSON.stringify(file, null, 2)}
17642
+ (0, import_node_fs19.writeFileSync)(installedPluginsPath(), `${JSON.stringify(file, null, 2)}
17295
17643
  `, "utf8");
17296
17644
  return true;
17297
17645
  } catch {
@@ -17304,9 +17652,9 @@ function backupAndWriteInstalledPlugins(records, pluginId) {
17304
17652
  if (!file) return false;
17305
17653
  if (!file.plugins) file.plugins = {};
17306
17654
  const path2 = installedPluginsPath();
17307
- (0, import_node_fs18.copyFileSync)(path2, `${path2}.bak`);
17655
+ (0, import_node_fs19.copyFileSync)(path2, `${path2}.bak`);
17308
17656
  file.plugins[pluginId] = records;
17309
- (0, import_node_fs18.writeFileSync)(path2, `${JSON.stringify(file, null, 2)}
17657
+ (0, import_node_fs19.writeFileSync)(path2, `${JSON.stringify(file, null, 2)}
17310
17658
  `, "utf8");
17311
17659
  return true;
17312
17660
  } catch {
@@ -17314,22 +17662,22 @@ function backupAndWriteInstalledPlugins(records, pluginId) {
17314
17662
  }
17315
17663
  }
17316
17664
  function opencodeConfigDir() {
17317
- return (0, import_node_path18.join)((0, import_node_os7.homedir)(), ".config", "opencode");
17665
+ return (0, import_node_path19.join)((0, import_node_os7.homedir)(), ".config", "opencode");
17318
17666
  }
17319
17667
  function opencodeConfigPath() {
17320
- return (0, import_node_path18.join)(opencodeConfigDir(), "opencode.jsonc");
17668
+ return (0, import_node_path19.join)(opencodeConfigDir(), "opencode.jsonc");
17321
17669
  }
17322
17670
  function opencodeCommandsDir() {
17323
- return (0, import_node_path18.join)(opencodeConfigDir(), "commands");
17671
+ return (0, import_node_path19.join)(opencodeConfigDir(), "commands");
17324
17672
  }
17325
17673
  function opencodeSkillsPath() {
17326
- return (0, import_node_path18.join)(opencodeConfigDir(), "node_modules", "@mutmutco", "opencode-mmi", "skills");
17674
+ return (0, import_node_path19.join)(opencodeConfigDir(), "node_modules", "@mutmutco", "opencode-mmi", "skills");
17327
17675
  }
17328
17676
  function opencodeConfigSnapshot() {
17329
17677
  const path2 = opencodeConfigPath();
17330
- if (!(0, import_node_fs18.existsSync)(path2)) return { path: path2, hasConfig: false, hasPluginField: false, parseOk: true };
17678
+ if (!(0, import_node_fs19.existsSync)(path2)) return { path: path2, hasConfig: false, hasPluginField: false, parseOk: true };
17331
17679
  try {
17332
- const raw = (0, import_node_fs18.readFileSync)(path2, "utf8");
17680
+ const raw = (0, import_node_fs19.readFileSync)(path2, "utf8");
17333
17681
  const parsed = JSON.parse(stripJsonc(raw));
17334
17682
  const hasPluginField = Object.prototype.hasOwnProperty.call(parsed, "plugin");
17335
17683
  const skillsPaths = Array.isArray(parsed.skills?.paths) ? parsed.skills.paths.filter((p) => typeof p === "string") : void 0;
@@ -17352,9 +17700,9 @@ function writeOpencodeConfigPlugin(snapshot) {
17352
17700
  const plan = planOpencodeConfigWrite(snapshot.hasConfig ? snapshot.raw : void 0);
17353
17701
  if (plan.action === "already") return true;
17354
17702
  if (!plan.text || plan.action === "unsafe") return false;
17355
- (0, import_node_fs18.mkdirSync)((0, import_node_path18.dirname)(path2), { recursive: true });
17356
- if (snapshot.hasConfig) (0, import_node_fs18.copyFileSync)(path2, `${path2}.bak`);
17357
- (0, import_node_fs18.writeFileSync)(path2, plan.text, "utf8");
17703
+ (0, import_node_fs19.mkdirSync)((0, import_node_path19.dirname)(path2), { recursive: true });
17704
+ if (snapshot.hasConfig) (0, import_node_fs19.copyFileSync)(path2, `${path2}.bak`);
17705
+ (0, import_node_fs19.writeFileSync)(path2, plan.text, "utf8");
17358
17706
  return true;
17359
17707
  } catch {
17360
17708
  return false;
@@ -17369,9 +17717,9 @@ function writeOpencodeSkillsPath(snapshot, skillsPath) {
17369
17717
  const normalized = skillsPath.replace(/\\/g, "/");
17370
17718
  if (!paths.some((p) => p.replace(/\\/g, "/") === normalized)) paths.push(skillsPath.replace(/\\/g, "/"));
17371
17719
  parsed.skills = { ...skills, paths };
17372
- (0, import_node_fs18.mkdirSync)((0, import_node_path18.dirname)(snapshot.path), { recursive: true });
17373
- if (snapshot.hasConfig && (0, import_node_fs18.existsSync)(snapshot.path)) (0, import_node_fs18.copyFileSync)(snapshot.path, `${snapshot.path}.bak`);
17374
- (0, import_node_fs18.writeFileSync)(snapshot.path, `${JSON.stringify(parsed, null, 2)}
17720
+ (0, import_node_fs19.mkdirSync)((0, import_node_path19.dirname)(snapshot.path), { recursive: true });
17721
+ if (snapshot.hasConfig && (0, import_node_fs19.existsSync)(snapshot.path)) (0, import_node_fs19.copyFileSync)(snapshot.path, `${snapshot.path}.bak`);
17722
+ (0, import_node_fs19.writeFileSync)(snapshot.path, `${JSON.stringify(parsed, null, 2)}
17375
17723
  `, "utf8");
17376
17724
  return true;
17377
17725
  } catch {
@@ -17380,7 +17728,7 @@ function writeOpencodeSkillsPath(snapshot, skillsPath) {
17380
17728
  }
17381
17729
  function opencodeExistingCommands() {
17382
17730
  try {
17383
- return (0, import_node_fs18.readdirSync)(opencodeCommandsDir(), { withFileTypes: true }).filter((entry) => entry.isFile() && entry.name.endsWith(".md")).map((entry) => entry.name.slice(0, -3).toLowerCase());
17731
+ return (0, import_node_fs19.readdirSync)(opencodeCommandsDir(), { withFileTypes: true }).filter((entry) => entry.isFile() && entry.name.endsWith(".md")).map((entry) => entry.name.slice(0, -3).toLowerCase());
17384
17732
  } catch {
17385
17733
  return [];
17386
17734
  }
@@ -17388,9 +17736,9 @@ function opencodeExistingCommands() {
17388
17736
  function writeOpencodeCommandFiles() {
17389
17737
  try {
17390
17738
  const dir = opencodeCommandsDir();
17391
- (0, import_node_fs18.mkdirSync)(dir, { recursive: true });
17739
+ (0, import_node_fs19.mkdirSync)(dir, { recursive: true });
17392
17740
  for (const command of OPENCODE_WORKFLOW_COMMANDS) {
17393
- (0, import_node_fs18.writeFileSync)((0, import_node_path18.join)(dir, `${command}.md`), opencodeCommandMarkdown(command), "utf8");
17741
+ (0, import_node_fs19.writeFileSync)((0, import_node_path19.join)(dir, `${command}.md`), opencodeCommandMarkdown(command), "utf8");
17394
17742
  }
17395
17743
  return true;
17396
17744
  } catch {
@@ -17399,12 +17747,12 @@ function writeOpencodeCommandFiles() {
17399
17747
  }
17400
17748
  function readOpencodeAdapterDiskVersion() {
17401
17749
  const candidates = [
17402
- (0, import_node_path18.join)(opencodeConfigDir(), "node_modules", "@mutmutco", "opencode-mmi", "package.json"),
17403
- (0, import_node_path18.join)((0, import_node_os7.homedir)(), ".cache", "opencode", "node_modules", "@mutmutco", "opencode-mmi", "package.json")
17750
+ (0, import_node_path19.join)(opencodeConfigDir(), "node_modules", "@mutmutco", "opencode-mmi", "package.json"),
17751
+ (0, import_node_path19.join)((0, import_node_os7.homedir)(), ".cache", "opencode", "node_modules", "@mutmutco", "opencode-mmi", "package.json")
17404
17752
  ];
17405
17753
  for (const path2 of candidates) {
17406
17754
  try {
17407
- const parsed = JSON.parse((0, import_node_fs18.readFileSync)(path2, "utf8"));
17755
+ const parsed = JSON.parse((0, import_node_fs19.readFileSync)(path2, "utf8"));
17408
17756
  if (typeof parsed.version === "string" && parsed.version.trim()) return parsed.version.trim();
17409
17757
  } catch {
17410
17758
  continue;
@@ -17420,7 +17768,7 @@ async function forceInstallOpencodeMmiPlugins(snapshot, log) {
17420
17768
  try {
17421
17769
  const specs = opencodeMmiPluginSpecs(snapshot);
17422
17770
  log(` \u21BB force-refreshing OpenCode MMI npm plugin(s): ${specs.join(", ")}\u2026`);
17423
- (0, import_node_fs18.mkdirSync)(opencodeConfigDir(), { recursive: true });
17771
+ (0, import_node_fs19.mkdirSync)(opencodeConfigDir(), { recursive: true });
17424
17772
  await runHostBin("npm", ["install", "--prefix", opencodeConfigDir(), "--force", ...specs], { timeout: NPM_UPDATE_TIMEOUT_MS });
17425
17773
  return true;
17426
17774
  } catch {
@@ -17428,12 +17776,12 @@ async function forceInstallOpencodeMmiPlugins(snapshot, log) {
17428
17776
  }
17429
17777
  }
17430
17778
  function opencodePackagesRoot() {
17431
- return (0, import_node_path18.join)((0, import_node_os7.homedir)(), ".cache", "opencode", "packages", "@mutmutco");
17779
+ return (0, import_node_path19.join)((0, import_node_os7.homedir)(), ".cache", "opencode", "packages", "@mutmutco");
17432
17780
  }
17433
17781
  function opencodePluginCacheDirs() {
17434
17782
  const root = opencodePackagesRoot();
17435
17783
  try {
17436
- return (0, import_node_fs18.readdirSync)(root, { withFileTypes: true }).filter((e) => e.isDirectory() && e.name.startsWith("opencode-mmi@") && !e.name.startsWith(".")).map((e) => (0, import_node_path18.join)(root, e.name));
17784
+ return (0, import_node_fs19.readdirSync)(root, { withFileTypes: true }).filter((e) => e.isDirectory() && e.name.startsWith("opencode-mmi@") && !e.name.startsWith(".")).map((e) => (0, import_node_path19.join)(root, e.name));
17437
17785
  } catch {
17438
17786
  return [];
17439
17787
  }
@@ -17441,7 +17789,7 @@ function opencodePluginCacheDirs() {
17441
17789
  function readOpencodeCacheDirVersion(cacheDir) {
17442
17790
  try {
17443
17791
  const parsed = JSON.parse(
17444
- (0, import_node_fs18.readFileSync)((0, import_node_path18.join)(cacheDir, "node_modules", "@mutmutco", "opencode-mmi", "package.json"), "utf8")
17792
+ (0, import_node_fs19.readFileSync)((0, import_node_path19.join)(cacheDir, "node_modules", "@mutmutco", "opencode-mmi", "package.json"), "utf8")
17445
17793
  );
17446
17794
  return typeof parsed.version === "string" && parsed.version.trim() ? parsed.version.trim() : void 0;
17447
17795
  } catch {
@@ -17464,9 +17812,9 @@ function quarantineStaleOpencodePluginCaches(releasedVersion, log) {
17464
17812
  });
17465
17813
  if (!plan.quarantine) continue;
17466
17814
  try {
17467
- const quarantineRoot = (0, import_node_path18.join)(opencodePackagesRoot(), ".mmi-quarantine", stamp);
17468
- (0, import_node_fs18.mkdirSync)(quarantineRoot, { recursive: true });
17469
- (0, import_node_fs18.renameSync)(cacheDir, (0, import_node_path18.join)(quarantineRoot, (0, import_node_path18.basename)(cacheDir)));
17815
+ const quarantineRoot = (0, import_node_path19.join)(opencodePackagesRoot(), ".mmi-quarantine", stamp);
17816
+ (0, import_node_fs19.mkdirSync)(quarantineRoot, { recursive: true });
17817
+ (0, import_node_fs19.renameSync)(cacheDir, (0, import_node_path19.join)(quarantineRoot, (0, import_node_path19.basename)(cacheDir)));
17470
17818
  log(` \u21BB quarantined stale OpenCode plugin cache ${plan.cacheVersion} (< ${plan.releasedVersion}) \u2014 OpenCode reinstalls the current adapter on next start`);
17471
17819
  moved = true;
17472
17820
  } catch {
@@ -17492,30 +17840,30 @@ function opencodePluginVersionsForReport() {
17492
17840
  }
17493
17841
  function opencodeDesktopLogsRoot() {
17494
17842
  if (process.platform === "win32") {
17495
- const base = process.env.APPDATA || (0, import_node_path18.join)((0, import_node_os7.homedir)(), "AppData", "Roaming");
17496
- return (0, import_node_path18.join)(base, "ai.opencode.desktop", "logs");
17843
+ const base = process.env.APPDATA || (0, import_node_path19.join)((0, import_node_os7.homedir)(), "AppData", "Roaming");
17844
+ return (0, import_node_path19.join)(base, "ai.opencode.desktop", "logs");
17497
17845
  }
17498
17846
  if (process.platform === "darwin") {
17499
- return (0, import_node_path18.join)((0, import_node_os7.homedir)(), "Library", "Application Support", "ai.opencode.desktop", "logs");
17847
+ return (0, import_node_path19.join)((0, import_node_os7.homedir)(), "Library", "Application Support", "ai.opencode.desktop", "logs");
17500
17848
  }
17501
- return (0, import_node_path18.join)((0, import_node_os7.homedir)(), ".config", "ai.opencode.desktop", "logs");
17849
+ return (0, import_node_path19.join)((0, import_node_os7.homedir)(), ".config", "ai.opencode.desktop", "logs");
17502
17850
  }
17503
17851
  function opencodeDesktopBootstrapSnapshot() {
17504
17852
  const root = opencodeDesktopLogsRoot();
17505
17853
  try {
17506
- const sessionDirs = (0, import_node_fs18.readdirSync)(root, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => (0, import_node_path18.join)(root, entry.name)).sort((a, b) => (0, import_node_fs18.statSync)(b).mtimeMs - (0, import_node_fs18.statSync)(a).mtimeMs);
17854
+ const sessionDirs = (0, import_node_fs19.readdirSync)(root, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => (0, import_node_path19.join)(root, entry.name)).sort((a, b) => (0, import_node_fs19.statSync)(b).mtimeMs - (0, import_node_fs19.statSync)(a).mtimeMs);
17507
17855
  const newest = sessionDirs[0];
17508
17856
  if (!newest) return [];
17509
- const logPath = (0, import_node_path18.join)(newest, "renderer.log");
17510
- const text = (0, import_node_fs18.readFileSync)(logPath, "utf8");
17511
- return opencodeAgentDirectoriesFromLog(text).filter((directory) => !(0, import_node_fs18.existsSync)(directory)).map((directory) => ({ directory, logPath }));
17857
+ const logPath = (0, import_node_path19.join)(newest, "renderer.log");
17858
+ const text = (0, import_node_fs19.readFileSync)(logPath, "utf8");
17859
+ return opencodeAgentDirectoriesFromLog(text).filter((directory) => !(0, import_node_fs19.existsSync)(directory)).map((directory) => ({ directory, logPath }));
17512
17860
  } catch {
17513
17861
  return [];
17514
17862
  }
17515
17863
  }
17516
17864
  function opencodeLegacyConfigSnapshot() {
17517
- const legacyPath = (0, import_node_path18.join)((0, import_node_os7.homedir)(), ".opencode", "opencode.json");
17518
- if (!(0, import_node_fs18.existsSync)(legacyPath)) return {};
17865
+ const legacyPath = (0, import_node_path19.join)((0, import_node_os7.homedir)(), ".opencode", "opencode.json");
17866
+ if (!(0, import_node_fs19.existsSync)(legacyPath)) return {};
17519
17867
  const content = readTextFile(legacyPath);
17520
17868
  if (content == null) return {};
17521
17869
  const plugins = parseOpencodeLegacyConfigPlugins(content);
@@ -17527,24 +17875,24 @@ function opencodeLegacyConfigSnapshot() {
17527
17875
  function quarantineOpencodeLegacyConfig(legacyPath) {
17528
17876
  try {
17529
17877
  const backupPath = `${legacyPath}.bak`;
17530
- if ((0, import_node_fs18.existsSync)(backupPath)) return false;
17531
- (0, import_node_fs18.renameSync)(legacyPath, backupPath);
17878
+ if ((0, import_node_fs19.existsSync)(backupPath)) return false;
17879
+ (0, import_node_fs19.renameSync)(legacyPath, backupPath);
17532
17880
  return true;
17533
17881
  } catch {
17534
17882
  return false;
17535
17883
  }
17536
17884
  }
17537
17885
  function cursorPluginCacheRoot() {
17538
- return (0, import_node_path18.join)((0, import_node_os7.homedir)(), ".cursor", "plugins", "cache", "mutmutco", "mmi");
17886
+ return (0, import_node_path19.join)((0, import_node_os7.homedir)(), ".cursor", "plugins", "cache", "mutmutco", "mmi");
17539
17887
  }
17540
17888
  function cursorLogsRoot() {
17541
- if (process.platform === "win32") return (0, import_node_path18.join)((0, import_node_os7.homedir)(), "AppData", "Roaming", "Cursor", "logs");
17542
- if (process.platform === "darwin") return (0, import_node_path18.join)((0, import_node_os7.homedir)(), "Library", "Application Support", "Cursor", "logs");
17543
- return (0, import_node_path18.join)((0, import_node_os7.homedir)(), ".config", "Cursor", "logs");
17889
+ if (process.platform === "win32") return (0, import_node_path19.join)((0, import_node_os7.homedir)(), "AppData", "Roaming", "Cursor", "logs");
17890
+ if (process.platform === "darwin") return (0, import_node_path19.join)((0, import_node_os7.homedir)(), "Library", "Application Support", "Cursor", "logs");
17891
+ return (0, import_node_path19.join)((0, import_node_os7.homedir)(), ".config", "Cursor", "logs");
17544
17892
  }
17545
17893
  function safeReaddirNames(dir) {
17546
17894
  try {
17547
- return (0, import_node_fs18.readdirSync)(dir, { withFileTypes: true }).map((e) => e.name);
17895
+ return (0, import_node_fs19.readdirSync)(dir, { withFileTypes: true }).map((e) => e.name);
17548
17896
  } catch {
17549
17897
  return [];
17550
17898
  }
@@ -17553,15 +17901,15 @@ function readCursorPluginLogText() {
17553
17901
  const root = cursorLogsRoot();
17554
17902
  const sessions = safeReaddirNames(root).sort().reverse();
17555
17903
  for (const session of sessions.slice(0, 5)) {
17556
- const sessionDir = (0, import_node_path18.join)(root, session);
17904
+ const sessionDir = (0, import_node_path19.join)(root, session);
17557
17905
  const texts = [];
17558
17906
  for (const win of safeReaddirNames(sessionDir).filter((n) => n.startsWith("window"))) {
17559
- const dir = (0, import_node_path18.join)(sessionDir, win, "exthost", "anysphere.cursor-agent-exec");
17907
+ const dir = (0, import_node_path19.join)(sessionDir, win, "exthost", "anysphere.cursor-agent-exec");
17560
17908
  for (const file of safeReaddirNames(dir)) {
17561
17909
  if (!/^Cursor Plugins.*\.log$/i.test(file)) continue;
17562
- const path2 = (0, import_node_path18.join)(dir, file);
17910
+ const path2 = (0, import_node_path19.join)(dir, file);
17563
17911
  try {
17564
- texts.push({ text: (0, import_node_fs18.readFileSync)(path2, "utf8"), mtime: (0, import_node_fs18.statSync)(path2).mtimeMs });
17912
+ texts.push({ text: (0, import_node_fs19.readFileSync)(path2, "utf8"), mtime: (0, import_node_fs19.statSync)(path2).mtimeMs });
17565
17913
  } catch {
17566
17914
  }
17567
17915
  }
@@ -17575,32 +17923,32 @@ function readCursorPluginLogText() {
17575
17923
  function cursorPluginCachePinSnapshots() {
17576
17924
  const root = cursorPluginCacheRoot();
17577
17925
  try {
17578
- return (0, import_node_fs18.readdirSync)(root, { withFileTypes: true }).filter((entry) => entry.isDirectory() && !entry.name.startsWith(".")).map((entry) => {
17579
- const path2 = (0, import_node_path18.join)(root, entry.name);
17580
- const pluginJson = (0, import_node_path18.join)(path2, ".cursor-plugin", "plugin.json");
17581
- const hooksJson = (0, import_node_path18.join)(path2, "hooks", "hooks.json");
17582
- const cliBundle = (0, import_node_path18.join)(path2, "cli", "dist", "index.cjs");
17583
- const cursorHook = (0, import_node_path18.join)(path2, "scripts", "cursor-hook.mjs");
17926
+ return (0, import_node_fs19.readdirSync)(root, { withFileTypes: true }).filter((entry) => entry.isDirectory() && !entry.name.startsWith(".")).map((entry) => {
17927
+ const path2 = (0, import_node_path19.join)(root, entry.name);
17928
+ const pluginJson = (0, import_node_path19.join)(path2, ".cursor-plugin", "plugin.json");
17929
+ const hooksJson = (0, import_node_path19.join)(path2, "hooks", "hooks.json");
17930
+ const cliBundle = (0, import_node_path19.join)(path2, "cli", "dist", "index.cjs");
17931
+ const cursorHook = (0, import_node_path19.join)(path2, "scripts", "cursor-hook.mjs");
17584
17932
  let version;
17585
17933
  try {
17586
- const raw = JSON.parse((0, import_node_fs18.readFileSync)(pluginJson, "utf8"));
17934
+ const raw = JSON.parse((0, import_node_fs19.readFileSync)(pluginJson, "utf8"));
17587
17935
  version = typeof raw.version === "string" ? raw.version : void 0;
17588
17936
  } catch {
17589
17937
  version = void 0;
17590
17938
  }
17591
17939
  let isEmpty = true;
17592
17940
  try {
17593
- isEmpty = (0, import_node_fs18.readdirSync)(path2).length === 0;
17941
+ isEmpty = (0, import_node_fs19.readdirSync)(path2).length === 0;
17594
17942
  } catch {
17595
17943
  isEmpty = true;
17596
17944
  }
17597
17945
  return {
17598
17946
  name: entry.name,
17599
17947
  path: path2,
17600
- hasPluginJson: (0, import_node_fs18.existsSync)(pluginJson),
17601
- hasHooksJson: (0, import_node_fs18.existsSync)(hooksJson),
17602
- hasCliBundle: (0, import_node_fs18.existsSync)(cliBundle),
17603
- hasCursorHookScript: (0, import_node_fs18.existsSync)(cursorHook),
17948
+ hasPluginJson: (0, import_node_fs19.existsSync)(pluginJson),
17949
+ hasHooksJson: (0, import_node_fs19.existsSync)(hooksJson),
17950
+ hasCliBundle: (0, import_node_fs19.existsSync)(cliBundle),
17951
+ hasCursorHookScript: (0, import_node_fs19.existsSync)(cursorHook),
17604
17952
  isEmpty,
17605
17953
  version
17606
17954
  };
@@ -17610,19 +17958,19 @@ function cursorPluginCachePinSnapshots() {
17610
17958
  }
17611
17959
  }
17612
17960
  function hubCheckoutForCursorSeed() {
17613
- const manifest = (0, import_node_path18.join)(process.cwd(), "plugins", "mmi", ".cursor-plugin", "plugin.json");
17614
- return (0, import_node_fs18.existsSync)(manifest) ? process.cwd() : void 0;
17961
+ const manifest = (0, import_node_path19.join)(process.cwd(), "plugins", "mmi", ".cursor-plugin", "plugin.json");
17962
+ return (0, import_node_fs19.existsSync)(manifest) ? process.cwd() : void 0;
17615
17963
  }
17616
17964
  function mmiPluginCacheRootSnapshots() {
17617
17965
  const roots = [
17618
- { surface: "claude", root: (0, import_node_path18.join)((0, import_node_os7.homedir)(), ".claude", "plugins", "cache", "mutmutco", "mmi") },
17619
- { surface: "codex", root: (0, import_node_path18.join)((0, import_node_os7.homedir)(), ".codex", "plugins", "cache", "mutmutco", "mmi") }
17966
+ { surface: "claude", root: (0, import_node_path19.join)((0, import_node_os7.homedir)(), ".claude", "plugins", "cache", "mutmutco", "mmi") },
17967
+ { surface: "codex", root: (0, import_node_path19.join)((0, import_node_os7.homedir)(), ".codex", "plugins", "cache", "mutmutco", "mmi") }
17620
17968
  ];
17621
17969
  return roots.flatMap(({ surface, root }) => {
17622
17970
  try {
17623
- const entries = (0, import_node_fs18.readdirSync)(root, { withFileTypes: true }).map((entry) => ({
17971
+ const entries = (0, import_node_fs19.readdirSync)(root, { withFileTypes: true }).map((entry) => ({
17624
17972
  name: entry.name,
17625
- path: (0, import_node_path18.join)(root, entry.name),
17973
+ path: (0, import_node_path19.join)(root, entry.name),
17626
17974
  isDirectory: entry.isDirectory()
17627
17975
  }));
17628
17976
  return [{ surface, root, entries }];
@@ -17633,7 +17981,7 @@ function mmiPluginCacheRootSnapshots() {
17633
17981
  }
17634
17982
  function hasNestedMmiChild(versionDir) {
17635
17983
  try {
17636
- return (0, import_node_fs18.statSync)((0, import_node_path18.join)(versionDir, "mmi")).isDirectory();
17984
+ return (0, import_node_fs19.statSync)((0, import_node_path19.join)(versionDir, "mmi")).isDirectory();
17637
17985
  } catch {
17638
17986
  return false;
17639
17987
  }
@@ -17644,10 +17992,10 @@ function nestedPluginTreeSnapshot() {
17644
17992
  );
17645
17993
  }
17646
17994
  function uniqueQuarantineTarget(path2) {
17647
- if (!(0, import_node_fs18.existsSync)(path2)) return path2;
17995
+ if (!(0, import_node_fs19.existsSync)(path2)) return path2;
17648
17996
  for (let i = 1; i < 100; i += 1) {
17649
17997
  const candidate = `${path2}-${i}`;
17650
- if (!(0, import_node_fs18.existsSync)(candidate)) return candidate;
17998
+ if (!(0, import_node_fs19.existsSync)(candidate)) return candidate;
17651
17999
  }
17652
18000
  return `${path2}-${Date.now()}`;
17653
18001
  }
@@ -17656,10 +18004,10 @@ function quarantinePluginCacheDirs(plan) {
17656
18004
  const failed = [];
17657
18005
  for (const move of plan) {
17658
18006
  try {
17659
- if (!(0, import_node_fs18.existsSync)(move.from)) continue;
18007
+ if (!(0, import_node_fs19.existsSync)(move.from)) continue;
17660
18008
  const target = uniqueQuarantineTarget(move.to);
17661
- (0, import_node_fs18.mkdirSync)((0, import_node_path18.dirname)(target), { recursive: true });
17662
- (0, import_node_fs18.renameSync)(move.from, target);
18009
+ (0, import_node_fs19.mkdirSync)((0, import_node_path19.dirname)(target), { recursive: true });
18010
+ (0, import_node_fs19.renameSync)(move.from, target);
17663
18011
  moved += 1;
17664
18012
  } catch {
17665
18013
  failed.push(move);
@@ -17678,23 +18026,23 @@ async function robocopyMirrorEmpty(emptyDir, target) {
17678
18026
  }
17679
18027
  async function clearNestedPluginTreeDir(targetPath) {
17680
18028
  try {
17681
- if (!(0, import_node_fs18.existsSync)(targetPath)) return true;
18029
+ if (!(0, import_node_fs19.existsSync)(targetPath)) return true;
17682
18030
  if (isWin) {
17683
- const emptyDir = (0, import_node_path18.join)((0, import_node_os7.tmpdir)(), `mmi-empty-${Date.now()}`);
17684
- (0, import_node_fs18.mkdirSync)(emptyDir, { recursive: true });
18031
+ const emptyDir = (0, import_node_path19.join)((0, import_node_os7.tmpdir)(), `mmi-empty-${Date.now()}`);
18032
+ (0, import_node_fs19.mkdirSync)(emptyDir, { recursive: true });
17685
18033
  try {
17686
18034
  await robocopyMirrorEmpty(emptyDir, targetPath);
17687
- (0, import_node_fs18.rmSync)(targetPath, { recursive: true, force: true });
18035
+ (0, import_node_fs19.rmSync)(targetPath, { recursive: true, force: true });
17688
18036
  } finally {
17689
18037
  try {
17690
- (0, import_node_fs18.rmSync)(emptyDir, { recursive: true, force: true });
18038
+ (0, import_node_fs19.rmSync)(emptyDir, { recursive: true, force: true });
17691
18039
  } catch {
17692
18040
  }
17693
18041
  }
17694
- return !(0, import_node_fs18.existsSync)(targetPath);
18042
+ return !(0, import_node_fs19.existsSync)(targetPath);
17695
18043
  }
17696
- (0, import_node_fs18.rmSync)(targetPath, { recursive: true, force: true });
17697
- return !(0, import_node_fs18.existsSync)(targetPath);
18044
+ (0, import_node_fs19.rmSync)(targetPath, { recursive: true, force: true });
18045
+ return !(0, import_node_fs19.existsSync)(targetPath);
17698
18046
  } catch {
17699
18047
  return false;
17700
18048
  }
@@ -17707,18 +18055,18 @@ async function applyNestedPluginTreeCleanup(paths, log) {
17707
18055
  }
17708
18056
  return true;
17709
18057
  }
17710
- var gitignorePath = () => (0, import_node_path18.join)(process.cwd(), ".gitignore");
18058
+ var gitignorePath = () => (0, import_node_path19.join)(process.cwd(), ".gitignore");
17711
18059
  function readTextFile(path2) {
17712
18060
  try {
17713
- if (!(0, import_node_fs18.existsSync)(path2)) return null;
17714
- return (0, import_node_fs18.readFileSync)(path2, "utf8");
18061
+ if (!(0, import_node_fs19.existsSync)(path2)) return null;
18062
+ return (0, import_node_fs19.readFileSync)(path2, "utf8");
17715
18063
  } catch {
17716
18064
  return null;
17717
18065
  }
17718
18066
  }
17719
18067
  function mcpDirExists(path2) {
17720
18068
  try {
17721
- return (0, import_node_fs18.existsSync)(path2);
18069
+ return (0, import_node_fs19.existsSync)(path2);
17722
18070
  } catch {
17723
18071
  return false;
17724
18072
  }
@@ -17726,60 +18074,60 @@ function mcpDirExists(path2) {
17726
18074
  function mcpConfigTargets() {
17727
18075
  const cwd = process.cwd();
17728
18076
  const home = (0, import_node_os7.homedir)();
17729
- const cursorProjectDir = (0, import_node_path18.join)(cwd, ".cursor");
17730
- const cursorUserDir = (0, import_node_path18.join)(home, ".cursor");
17731
- const codexDir = (0, import_node_path18.join)(home, ".codex");
18077
+ const cursorProjectDir = (0, import_node_path19.join)(cwd, ".cursor");
18078
+ const cursorUserDir = (0, import_node_path19.join)(home, ".cursor");
18079
+ const codexDir = (0, import_node_path19.join)(home, ".codex");
17732
18080
  return [
17733
18081
  // Claude Code project MCP — reconciled if present, never conjured (org seeds .cursor/mcp.json, not this).
17734
18082
  {
17735
18083
  host: "claude-code",
17736
18084
  label: "Claude Code project MCP",
17737
- path: (0, import_node_path18.join)(cwd, ".mcp.json"),
18085
+ path: (0, import_node_path19.join)(cwd, ".mcp.json"),
17738
18086
  format: "json",
17739
18087
  present: true,
17740
18088
  // parent is the repo root (cwd); the caller gates the whole reconcile on isOrgRepo
17741
18089
  isRepoFile: true,
17742
18090
  createWhenFileAbsent: false,
17743
- content: readTextFile((0, import_node_path18.join)(cwd, ".mcp.json"))
18091
+ content: readTextFile((0, import_node_path19.join)(cwd, ".mcp.json"))
17744
18092
  },
17745
18093
  // Cursor project MCP — the bootstrap-seeded surface; restored if its .cursor dir exists but the file is gone.
17746
18094
  {
17747
18095
  host: "cursor-project",
17748
18096
  label: "Cursor project MCP",
17749
- path: (0, import_node_path18.join)(cursorProjectDir, "mcp.json"),
18097
+ path: (0, import_node_path19.join)(cursorProjectDir, "mcp.json"),
17750
18098
  format: "json",
17751
18099
  present: mcpDirExists(cursorProjectDir),
17752
18100
  isRepoFile: true,
17753
18101
  createWhenFileAbsent: true,
17754
- content: readTextFile((0, import_node_path18.join)(cursorProjectDir, "mcp.json"))
18102
+ content: readTextFile((0, import_node_path19.join)(cursorProjectDir, "mcp.json"))
17755
18103
  },
17756
18104
  // Cursor user MCP — global; written only when Cursor is installed (~/.cursor exists).
17757
18105
  {
17758
18106
  host: "cursor-user",
17759
18107
  label: "Cursor user MCP",
17760
- path: (0, import_node_path18.join)(cursorUserDir, "mcp.json"),
18108
+ path: (0, import_node_path19.join)(cursorUserDir, "mcp.json"),
17761
18109
  format: "json",
17762
18110
  present: mcpDirExists(cursorUserDir),
17763
18111
  isRepoFile: false,
17764
18112
  createWhenFileAbsent: true,
17765
- content: readTextFile((0, import_node_path18.join)(cursorUserDir, "mcp.json"))
18113
+ content: readTextFile((0, import_node_path19.join)(cursorUserDir, "mcp.json"))
17766
18114
  },
17767
18115
  // Codex user config (TOML) — global; written only when Codex is installed (~/.codex exists).
17768
18116
  {
17769
18117
  host: "codex",
17770
18118
  label: "Codex user config",
17771
- path: (0, import_node_path18.join)(codexDir, "config.toml"),
18119
+ path: (0, import_node_path19.join)(codexDir, "config.toml"),
17772
18120
  format: "toml",
17773
18121
  present: mcpDirExists(codexDir),
17774
18122
  isRepoFile: false,
17775
18123
  createWhenFileAbsent: true,
17776
- content: readTextFile((0, import_node_path18.join)(codexDir, "config.toml"))
18124
+ content: readTextFile((0, import_node_path19.join)(codexDir, "config.toml"))
17777
18125
  }
17778
18126
  ];
17779
18127
  }
17780
18128
  function writeMcpConfigFile(path2, content) {
17781
18129
  try {
17782
- (0, import_node_fs18.writeFileSync)(path2, content, "utf8");
18130
+ (0, import_node_fs19.writeFileSync)(path2, content, "utf8");
17783
18131
  return true;
17784
18132
  } catch {
17785
18133
  return false;
@@ -17859,7 +18207,7 @@ function strayBrowserArtifactPaths() {
17859
18207
  const cwd = process.cwd();
17860
18208
  return STRAY_BROWSER_ARTIFACT_DIRS.filter((rel) => {
17861
18209
  try {
17862
- return (0, import_node_fs18.existsSync)((0, import_node_path18.join)(cwd, rel));
18210
+ return (0, import_node_fs19.existsSync)((0, import_node_path19.join)(cwd, rel));
17863
18211
  } catch {
17864
18212
  return false;
17865
18213
  }
@@ -17867,14 +18215,14 @@ function strayBrowserArtifactPaths() {
17867
18215
  }
17868
18216
  function readGitignore() {
17869
18217
  try {
17870
- return (0, import_node_fs18.readFileSync)(gitignorePath(), "utf8");
18218
+ return (0, import_node_fs19.readFileSync)(gitignorePath(), "utf8");
17871
18219
  } catch {
17872
18220
  return null;
17873
18221
  }
17874
18222
  }
17875
18223
  function writeGitignore(content) {
17876
18224
  try {
17877
- (0, import_node_fs18.writeFileSync)(gitignorePath(), content, "utf8");
18225
+ (0, import_node_fs19.writeFileSync)(gitignorePath(), content, "utf8");
17878
18226
  return true;
17879
18227
  } catch {
17880
18228
  return false;
@@ -17916,7 +18264,7 @@ async function runDoctor(opts, io = consoleIo, readOrigin) {
17916
18264
  const semverPrefix = /^\d+\.\d+\.\d+/;
17917
18265
  const isBehind = (installed2, released) => Boolean(installed2 && released && semverPrefix.test(installed2) && semverPrefix.test(released) && compareVersions(installed2, released) < 0);
17918
18266
  const opencodeAdapterStale = isBehind(opencodeInstalledVersionForDoctor(), releasedVersion);
17919
- const cursorCacheStale = (0, import_node_fs18.existsSync)(cursorPluginCacheRoot()) && (cursorPluginCachePinSnapshots() ?? []).some((p) => isBehind(p.version, releasedVersion));
18267
+ const cursorCacheStale = (0, import_node_fs19.existsSync)(cursorPluginCacheRoot()) && (cursorPluginCachePinSnapshots() ?? []).some((p) => isBehind(p.version, releasedVersion));
17920
18268
  const healPlan = doctorHealPlan({
17921
18269
  isOrgRepo,
17922
18270
  surface,
@@ -17951,7 +18299,7 @@ async function runDoctor(opts, io = consoleIo, readOrigin) {
17951
18299
  let onPath = pathProbe;
17952
18300
  if (!onPath) {
17953
18301
  const root = process.env.CLAUDE_PLUGIN_ROOT;
17954
- if (root && (0, import_node_fs18.existsSync)(`${root}/bin/mmi-cli${isWin ? ".cmd" : ""}`)) onPath = true;
18302
+ if (root && (0, import_node_fs19.existsSync)(`${root}/bin/mmi-cli${isWin ? ".cmd" : ""}`)) onPath = true;
17955
18303
  }
17956
18304
  checks.push({ ok: onPath, label: "mmi-cli on PATH", fix: "auto-provisioned at session start \u2014 reopen the session, or install the MMI plugin" });
17957
18305
  const reloadHint = reloadAction(surface);
@@ -18098,7 +18446,7 @@ async function runDoctor(opts, io = consoleIo, readOrigin) {
18098
18446
  checks.push(gitignoreCheck);
18099
18447
  checks.push(buildRepoLocalWorktreeCheck({
18100
18448
  isOrgRepo,
18101
- hasRepoLocalWorktrees: (0, import_node_fs18.existsSync)((0, import_node_path18.join)(process.cwd(), ".worktrees"))
18449
+ hasRepoLocalWorktrees: (0, import_node_fs19.existsSync)((0, import_node_path19.join)(process.cwd(), ".worktrees"))
18102
18450
  }));
18103
18451
  let driftCheck = buildPluginConfigDriftCheck({ isOrgRepo, installed, surface });
18104
18452
  if (!driftCheck.ok && driftCheck.recordsToWrite && repairLocal) {
@@ -18387,7 +18735,7 @@ async function runDoctor(opts, io = consoleIo, readOrigin) {
18387
18735
  }
18388
18736
  checks.push(nestedPluginTreeCheck);
18389
18737
  const cursorCacheRoot = cursorPluginCacheRoot();
18390
- const cursorCacheRootExists = (0, import_node_fs18.existsSync)(cursorCacheRoot);
18738
+ const cursorCacheRootExists = (0, import_node_fs19.existsSync)(cursorCacheRoot);
18391
18739
  let cursorPins = cursorPluginCachePinSnapshots() ?? [];
18392
18740
  checks.push(
18393
18741
  buildCursorPluginCacheCleanupCheck({
@@ -18415,7 +18763,7 @@ async function runDoctor(opts, io = consoleIo, readOrigin) {
18415
18763
  cacheRootExists: cursorCacheRootExists,
18416
18764
  hubCheckout: hubCheckoutForCursorSeed(),
18417
18765
  execFileP: execFileP2,
18418
- mkdtemp: (prefix) => (0, import_promises2.mkdtemp)((0, import_node_path18.join)((0, import_node_os7.tmpdir)(), prefix)),
18766
+ mkdtemp: (prefix) => (0, import_promises2.mkdtemp)((0, import_node_path19.join)((0, import_node_os7.tmpdir)(), prefix)),
18419
18767
  log: (m) => io.err(m)
18420
18768
  });
18421
18769
  if (seeded) {
@@ -18610,23 +18958,23 @@ function mergeGuardHook(settings) {
18610
18958
  next.hooks = hooks;
18611
18959
  return next;
18612
18960
  }
18613
- var userScopeSettingsPath = (surface = detectSurface(process.env)) => (0, import_node_path18.join)((0, import_node_os7.homedir)(), surface === "codex" ? ".codex" : ".claude", "settings.json");
18961
+ var userScopeSettingsPath = (surface = detectSurface(process.env)) => (0, import_node_path19.join)((0, import_node_os7.homedir)(), surface === "codex" ? ".codex" : ".claude", "settings.json");
18614
18962
  function ensureUserScopeGuardHook(opts = {}) {
18615
18963
  const path2 = opts.settingsPath ?? userScopeSettingsPath();
18616
18964
  try {
18617
18965
  let current = null;
18618
- if ((0, import_node_fs18.existsSync)(path2)) {
18966
+ if ((0, import_node_fs19.existsSync)(path2)) {
18619
18967
  try {
18620
- current = JSON.parse((0, import_node_fs18.readFileSync)(path2, "utf8"));
18968
+ current = JSON.parse((0, import_node_fs19.readFileSync)(path2, "utf8"));
18621
18969
  } catch {
18622
18970
  return "failed";
18623
18971
  }
18624
18972
  }
18625
18973
  if (settingsHasGuardHook(current)) return "already";
18626
18974
  const merged = mergeGuardHook(current);
18627
- (0, import_node_fs18.mkdirSync)((0, import_node_path18.dirname)(path2), { recursive: true });
18628
- if ((0, import_node_fs18.existsSync)(path2)) (0, import_node_fs18.copyFileSync)(path2, `${path2}.bak`);
18629
- (0, import_node_fs18.writeFileSync)(path2, `${JSON.stringify(merged, null, 2)}
18975
+ (0, import_node_fs19.mkdirSync)((0, import_node_path19.dirname)(path2), { recursive: true });
18976
+ if ((0, import_node_fs19.existsSync)(path2)) (0, import_node_fs19.copyFileSync)(path2, `${path2}.bak`);
18977
+ (0, import_node_fs19.writeFileSync)(path2, `${JSON.stringify(merged, null, 2)}
18630
18978
  `, "utf8");
18631
18979
  return "written";
18632
18980
  } catch {
@@ -18763,7 +19111,7 @@ async function applyGcPlan(plan, remote) {
18763
19111
  cleanupBranch: (branch, expectedHeadOid) => cleanupPrMergeLocalBranch(branch.branch, {
18764
19112
  beforeWorktrees,
18765
19113
  startingPath: branch.worktreePath,
18766
- pathExists: (p) => (0, import_node_fs19.existsSync)(p),
19114
+ pathExists: (p) => (0, import_node_fs20.existsSync)(p),
18767
19115
  execGit: async (args) => (await execFileP2("git", args, { timeout: GIT_TIMEOUT_MS })).stdout,
18768
19116
  teardownWorktreeStage,
18769
19117
  deferredStore,
@@ -18786,7 +19134,7 @@ async function applyGcPlan(plan, remote) {
18786
19134
  for (const wt of plan.worktreeDirs) {
18787
19135
  try {
18788
19136
  const cleanupTarget = resolveSafeSiblingWorktreeCleanupTarget(wt.path, siblingRoot, {
18789
- realpath: (path2) => (0, import_node_fs19.realpathSync)(path2)
19137
+ realpath: (path2) => (0, import_node_fs20.realpathSync)(path2)
18790
19138
  });
18791
19139
  if (!cleanupTarget.ok) {
18792
19140
  result.failed.push(`${wt.path}: ${cleanupTarget.reason}`);
@@ -18829,13 +19177,13 @@ var program2 = new Command();
18829
19177
  program2.name("mmi-cli").description("MMI Future CLI \u2014 org rules delivery, Jervaise-only continuity. The engine the plugin SessionStart hook drives.").version(resolveClientVersion()).showHelpAfterError("(run `mmi-cli commands` to list every subcommand + its flags, or `mmi-cli commands --json` to ground against it)");
18830
19178
  var rules = program2.command("rules").description("org-managed .gitignore delivery");
18831
19179
  rules.command("gitignore").option("--write", "upsert the managed block into .gitignore (default: check only, non-zero exit on drift)").description("verify (or --write) this repo's org-managed .gitignore block matches the SSOT").action((opts) => {
18832
- const path2 = (0, import_node_path19.join)(process.cwd(), ".gitignore");
18833
- const current = (0, import_node_fs19.existsSync)(path2) ? (0, import_node_fs19.readFileSync)(path2, "utf8") : null;
19180
+ const path2 = (0, import_node_path20.join)(process.cwd(), ".gitignore");
19181
+ const current = (0, import_node_fs20.existsSync)(path2) ? (0, import_node_fs20.readFileSync)(path2, "utf8") : null;
18834
19182
  const plan = planManagedGitignore(current);
18835
19183
  const drift = [...plan.added.map((l) => `+${l}`), ...plan.removed.map((l) => `-${l}`)].join(", ") || "block normalize";
18836
19184
  if (opts.write) {
18837
19185
  if (plan.changed) {
18838
- (0, import_node_fs19.writeFileSync)(path2, plan.content, "utf8");
19186
+ (0, import_node_fs20.writeFileSync)(path2, plan.content, "utf8");
18839
19187
  console.log(`mmi-cli rules gitignore: updated .gitignore (${drift})`);
18840
19188
  } else {
18841
19189
  console.log("mmi-cli rules gitignore: up to date");
@@ -19000,7 +19348,7 @@ function runWorktreeInstall(command, cwd, quiet) {
19000
19348
  async function primaryCheckoutRoot(worktreeRoot) {
19001
19349
  try {
19002
19350
  const out = (await execFileP2("git", ["-C", worktreeRoot, "rev-parse", "--path-format=absolute", "--git-common-dir"], { timeout: GIT_TIMEOUT_MS })).stdout.trim();
19003
- return out ? (0, import_node_path19.dirname)(out) : void 0;
19351
+ return out ? (0, import_node_path20.dirname)(out) : void 0;
19004
19352
  } catch {
19005
19353
  return void 0;
19006
19354
  }
@@ -19015,26 +19363,26 @@ function makeProvisionDeps(worktreeRoot, quiet, log) {
19015
19363
  function acquireWorktreeSetupLock(worktreeRoot) {
19016
19364
  const lockPath = repoRuntimeStatePath(worktreeRoot, "worktree-setup.lock");
19017
19365
  const take = () => {
19018
- const fd = (0, import_node_fs19.openSync)(lockPath, "wx");
19366
+ const fd = (0, import_node_fs20.openSync)(lockPath, "wx");
19019
19367
  try {
19020
- (0, import_node_fs19.writeSync)(fd, String(Date.now()));
19368
+ (0, import_node_fs20.writeSync)(fd, String(Date.now()));
19021
19369
  } finally {
19022
- (0, import_node_fs19.closeSync)(fd);
19370
+ (0, import_node_fs20.closeSync)(fd);
19023
19371
  }
19024
19372
  return () => {
19025
19373
  try {
19026
- (0, import_node_fs19.rmSync)(lockPath, { force: true });
19374
+ (0, import_node_fs20.rmSync)(lockPath, { force: true });
19027
19375
  } catch {
19028
19376
  }
19029
19377
  };
19030
19378
  };
19031
19379
  try {
19032
- (0, import_node_fs19.mkdirSync)((0, import_node_path19.dirname)(lockPath), { recursive: true });
19380
+ (0, import_node_fs20.mkdirSync)((0, import_node_path20.dirname)(lockPath), { recursive: true });
19033
19381
  return take();
19034
19382
  } catch {
19035
19383
  try {
19036
- if (Date.now() - (0, import_node_fs19.statSync)(lockPath).mtimeMs > WORKTREE_SETUP_LOCK_TTL_MS) {
19037
- (0, import_node_fs19.rmSync)(lockPath, { force: true });
19384
+ if (Date.now() - (0, import_node_fs20.statSync)(lockPath).mtimeMs > WORKTREE_SETUP_LOCK_TTL_MS) {
19385
+ (0, import_node_fs20.rmSync)(lockPath, { force: true });
19038
19386
  return take();
19039
19387
  }
19040
19388
  } catch {
@@ -19524,7 +19872,7 @@ project.command("set [owner/repo]").description("upsert project META (idempotent
19524
19872
  const vars = rawValues("--var");
19525
19873
  if (o.secretsFile) {
19526
19874
  try {
19527
- vars.push(`secrets=${(0, import_node_fs19.readFileSync)(o.secretsFile, "utf8")}`);
19875
+ vars.push(`secrets=${(0, import_node_fs20.readFileSync)(o.secretsFile, "utf8")}`);
19528
19876
  } catch (e) {
19529
19877
  return fail(`project set: cannot read --secrets-file ${o.secretsFile}: ${e.message}`);
19530
19878
  }
@@ -20094,9 +20442,9 @@ pr.command("view <number>").description("read a PR as structured JSON (merged st
20094
20442
  }
20095
20443
  });
20096
20444
  async function listCiWorkflowPaths(cwd = process.cwd()) {
20097
- const wfDir = (0, import_node_path19.join)(cwd, ".github", "workflows");
20098
- if (!(0, import_node_fs19.existsSync)(wfDir)) return [];
20099
- return (0, import_node_fs19.readdirSync)(wfDir).filter((name) => /\.(ya?ml)$/i.test(name)).map((name) => `.github/workflows/${name}`);
20445
+ const wfDir = (0, import_node_path20.join)(cwd, ".github", "workflows");
20446
+ if (!(0, import_node_fs20.existsSync)(wfDir)) return [];
20447
+ return (0, import_node_fs20.readdirSync)(wfDir).filter((name) => /\.(ya?ml)$/i.test(name)).map((name) => `.github/workflows/${name}`);
20100
20448
  }
20101
20449
  async function resolveMergeCiPolicyForCheckout(repoOpt) {
20102
20450
  const repo = repoOpt ?? await resolveRepo();
@@ -20115,7 +20463,7 @@ function ciAuditDeps() {
20115
20463
  // Continuous CI delivery (#1550): the gate re-seed renders from the Hub's on-disk seed templates. The
20116
20464
  // reconcile runs IN the Hub checkout, so this is local-file I/O (no network fetch). Path is relative to
20117
20465
  // the repo root (e.g. skills/bootstrap/seeds/gate.template.yml).
20118
- readSeedFile: (path2) => (0, import_node_fs19.existsSync)(path2) ? (0, import_node_fs19.readFileSync)(path2, "utf8") : null
20466
+ readSeedFile: (path2) => (0, import_node_fs20.existsSync)(path2) ? (0, import_node_fs20.readFileSync)(path2, "utf8") : null
20119
20467
  };
20120
20468
  }
20121
20469
  pr.command("ci-policy").description("report merge CI policy: wait-for-checks vs no-ci (for grind/build agents)").option("--json", "machine-readable output").option("--repo <owner/repo>", "target repo (defaults to the current checkout)").action(async (o) => {
@@ -20285,7 +20633,7 @@ async function createDeferredWorktreeStore() {
20285
20633
  },
20286
20634
  write: async (entries) => {
20287
20635
  try {
20288
- await (0, import_promises3.mkdir)((0, import_node_path19.dirname)(registryPath), { recursive: true });
20636
+ await (0, import_promises3.mkdir)((0, import_node_path20.dirname)(registryPath), { recursive: true });
20289
20637
  await (0, import_promises3.writeFile)(registryPath, serializeDeferredWorktrees(entries), "utf8");
20290
20638
  } catch {
20291
20639
  }
@@ -20299,13 +20647,13 @@ var realWorktreeDirRemover = {
20299
20647
  probe: (p) => {
20300
20648
  let st;
20301
20649
  try {
20302
- st = (0, import_node_fs19.lstatSync)(p);
20650
+ st = (0, import_node_fs20.lstatSync)(p);
20303
20651
  } catch {
20304
20652
  return null;
20305
20653
  }
20306
20654
  if (st.isSymbolicLink()) return "link";
20307
20655
  try {
20308
- (0, import_node_fs19.readlinkSync)(p);
20656
+ (0, import_node_fs20.readlinkSync)(p);
20309
20657
  return "link";
20310
20658
  } catch {
20311
20659
  }
@@ -20313,7 +20661,7 @@ var realWorktreeDirRemover = {
20313
20661
  },
20314
20662
  readdir: (p) => {
20315
20663
  try {
20316
- return (0, import_node_fs19.readdirSync)(p);
20664
+ return (0, import_node_fs20.readdirSync)(p);
20317
20665
  } catch {
20318
20666
  return [];
20319
20667
  }
@@ -20322,9 +20670,9 @@ var realWorktreeDirRemover = {
20322
20670
  // leaving the target); a file symlink with unlink. rmdir first, fall back to unlink.
20323
20671
  detachLink: (p) => {
20324
20672
  try {
20325
- (0, import_node_fs19.rmdirSync)(p);
20673
+ (0, import_node_fs20.rmdirSync)(p);
20326
20674
  } catch {
20327
- (0, import_node_fs19.unlinkSync)(p);
20675
+ (0, import_node_fs20.unlinkSync)(p);
20328
20676
  }
20329
20677
  },
20330
20678
  removeTree: (p) => (0, import_promises3.rm)(p, { recursive: true, force: true, maxRetries: 5, retryDelay: 200 })
@@ -20354,9 +20702,9 @@ async function worktreeHasStageState(worktreePath) {
20354
20702
  }
20355
20703
  }
20356
20704
  function stageStateFileBelongsToWorktree(statePath, worktreePath) {
20357
- if (!(0, import_node_fs19.existsSync)(statePath)) return false;
20705
+ if (!(0, import_node_fs20.existsSync)(statePath)) return false;
20358
20706
  try {
20359
- const state = JSON.parse((0, import_node_fs19.readFileSync)(statePath, "utf8"));
20707
+ const state = JSON.parse((0, import_node_fs20.readFileSync)(statePath, "utf8"));
20360
20708
  const recordedCwd = typeof state.identity?.cwd === "string" ? state.identity.cwd : typeof state.cwd === "string" ? state.cwd : "";
20361
20709
  return Boolean(recordedCwd && isPathUnderDirectory(recordedCwd, worktreePath));
20362
20710
  } catch {
@@ -20428,7 +20776,7 @@ pr.command("merge <number>").description("merge a PR (squash by default) and cle
20428
20776
  localCleanup = await cleanupPrMergeLocalBranch(headRef, {
20429
20777
  beforeWorktrees,
20430
20778
  startingPath,
20431
- pathExists: (p) => (0, import_node_fs19.existsSync)(p),
20779
+ pathExists: (p) => (0, import_node_fs20.existsSync)(p),
20432
20780
  execGit: async (args) => (await execFileP2("git", args, { timeout: GIT_TIMEOUT_MS })).stdout,
20433
20781
  teardownWorktreeStage,
20434
20782
  deferredStore,
@@ -20649,7 +20997,7 @@ function stageScopedRunOpts(o) {
20649
20997
  };
20650
20998
  }
20651
20999
  function printLine(value) {
20652
- (0, import_node_fs19.writeSync)(1, `${value}
21000
+ (0, import_node_fs20.writeSync)(1, `${value}
20653
21001
  `);
20654
21002
  }
20655
21003
  function stageKeepAlive() {
@@ -20663,8 +21011,8 @@ async function resolveStage() {
20663
21011
  const portRange = portRangeMeta && typeof portRangeMeta.start === "number" && typeof portRangeMeta.end === "number" ? [portRangeMeta.start, portRangeMeta.end] : void 0;
20664
21012
  return decideStage({
20665
21013
  registry: { deployModel: project2?.deployModel, portRange, error: read.ok ? void 0 : read.error },
20666
- hasCompose: (0, import_node_fs19.existsSync)((0, import_node_path19.join)(process.cwd(), "docker-compose.yml")),
20667
- hasEnvExample: (0, import_node_fs19.existsSync)((0, import_node_path19.join)(process.cwd(), ".env.example"))
21014
+ hasCompose: (0, import_node_fs20.existsSync)((0, import_node_path20.join)(process.cwd(), "docker-compose.yml")),
21015
+ hasEnvExample: (0, import_node_fs20.existsSync)((0, import_node_path20.join)(process.cwd(), ".env.example"))
20668
21016
  });
20669
21017
  }
20670
21018
  async function fetchStageVaultEnvMerge() {
@@ -20887,7 +21235,11 @@ function trainApplyDeps() {
20887
21235
  return {
20888
21236
  run: async (file, args) => {
20889
21237
  const timeout = file === "node" && args[1] === "prepare" ? NODE_PREPARE_TIMEOUT_MS : file === "npm" ? NPM_TRAIN_TIMEOUT_MS : file !== "gh" ? GIT_TIMEOUT_MS : args[0] === "run" && args[1] === "watch" ? GH_RUN_WATCH_TIMEOUT_MS : GH_TRAIN_TIMEOUT_MS;
20890
- return isWin2 && file === "npm" ? (await execFileP2("cmd.exe", ["/c", "npm", ...args], { timeout })).stdout : (await execFileP2(file, args, { timeout })).stdout;
21238
+ try {
21239
+ return isWin2 && file === "npm" ? (await execFileP2("cmd.exe", ["/c", "npm", ...args], { timeout })).stdout : (await execFileP2(file, args, { timeout })).stdout;
21240
+ } catch (e) {
21241
+ throw surfaceTrainSubprocessFailure(e, file, args);
21242
+ }
20891
21243
  },
20892
21244
  runSelf: async (args) => (await execFileP2(process.execPath, [process.argv[1], ...args], { timeout: 3e4 })).stdout,
20893
21245
  trainAuthority: async (repo) => {
@@ -21113,6 +21465,27 @@ wiki.command("publish <pages-dir>").description("mint a 1h repo-scoped contents:
21113
21465
  );
21114
21466
  if (!ok) process.exitCode = 1;
21115
21467
  });
21468
+ wiki.command("plan").description("compute the release wiki plan for the cwd repo \u2014 collect its opt-in `wiki:page` pages, read the previous incremental stamp from <repo>.wiki.git (scoped minted token), and print a JSON plan naming which pages changed plus the fresh full stamp. Runs from ANY org repo checkout (no repo-local script). Fails loud on the credential/read gap \u2014 never a silent skip").option("--repo <owner/repo>", "target repo (defaults to the cwd repo)").action(async (o) => {
21469
+ const repo = await resolveRepo(o.repo);
21470
+ if (!repo) return fail("wiki plan: could not determine the target repo (pass --repo owner/name)");
21471
+ const cfg = await loadConfig();
21472
+ const readers = fsReaders(process.cwd());
21473
+ const ok = await wikiPlan(
21474
+ {
21475
+ collectPages: () => collectFeaturePages(readers),
21476
+ readSource: (p) => readers.readDoc(p),
21477
+ mintAndReadStamp: async (r) => {
21478
+ const minted = await mintWikiToken(r, registryClientDeps(cfg));
21479
+ if (!minted.ok) return { ok: false, error: minted.error };
21480
+ return readWikiStampViaGit(realWikiGitPublishDeps(), { repo: r, token: minted.mint.token });
21481
+ },
21482
+ out: (json) => console.log(json),
21483
+ err: (msg) => console.error(msg)
21484
+ },
21485
+ { repo }
21486
+ );
21487
+ if (!ok) process.exitCode = 1;
21488
+ });
21116
21489
  var bootstrap = program2.command("bootstrap").description("plan repo bootstrap operations; mutations require master-admin approval").option("--repo <owner/repo>", "target repo").option("--class <class>", "deployable | content", "deployable").option("--json", "machine-readable output").option("--apply", "reserved for future bootstrap execution after explicit master-admin approval").action((o) => {
21117
21490
  if (!o.repo) return fail("bootstrap: required option --repo <owner/repo> not specified");
21118
21491
  if (o.apply) return fail("bootstrap: execution is not implemented yet; use the dry-run plan and the existing /bootstrap skill");
@@ -21132,7 +21505,7 @@ bootstrap.command("verify <repo>").description("audit whether an existing repo i
21132
21505
  client: defaultGitHubClient(),
21133
21506
  projectMeta: meta,
21134
21507
  deployModel: typeof meta?.deployModel === "string" ? meta.deployModel : void 0,
21135
- readLocalFile: (path2) => path2 === "projects.json" && apiProjects != null ? apiProjects : (0, import_node_fs19.existsSync)(path2) ? (0, import_node_fs19.readFileSync)(path2, "utf8") : null,
21508
+ readLocalFile: (path2) => path2 === "projects.json" && apiProjects != null ? apiProjects : (0, import_node_fs20.existsSync)(path2) ? (0, import_node_fs20.readFileSync)(path2, "utf8") : null,
21136
21509
  // requiredGcpApis is stored as an array by a JSON write, but `project set --var KEY=VALUE` stores a raw
21137
21510
  // comma-string — accept either so the seeded value verifies regardless of how it was written.
21138
21511
  requiredGcpApis: (() => {
@@ -21183,12 +21556,12 @@ bootstrap.command("apply <repo>").description("idempotent seed apply from skills
21183
21556
  return fail(`bootstrap apply: ${e.message}`);
21184
21557
  }
21185
21558
  const manifestPath = "skills/bootstrap/seeds/manifest.json";
21186
- if (!(0, import_node_fs19.existsSync)(manifestPath)) return fail(`bootstrap apply: ${manifestPath} not found; run from the MMI-Hub repo root`);
21187
- const manifest = loadBootstrapSeeds((0, import_node_fs19.readFileSync)(manifestPath, "utf8"));
21559
+ if (!(0, import_node_fs20.existsSync)(manifestPath)) return fail(`bootstrap apply: ${manifestPath} not found; run from the MMI-Hub repo root`);
21560
+ const manifest = loadBootstrapSeeds((0, import_node_fs20.readFileSync)(manifestPath, "utf8"));
21188
21561
  const baseBranch = o.class === "content" ? "main" : "development";
21189
21562
  const slug = parsedRepo.slug;
21190
21563
  const gh = async (args) => execFileP2("gh", args, { timeout: 2e4 });
21191
- const readFile2 = (p) => (0, import_node_fs19.existsSync)(p) ? (0, import_node_fs19.readFileSync)(p, "utf8") : null;
21564
+ const readFile2 = (p) => (0, import_node_fs20.existsSync)(p) ? (0, import_node_fs20.readFileSync)(p, "utf8") : null;
21192
21565
  const enc = (p) => p.split("/").map(encodeURIComponent).join("/");
21193
21566
  const rawVars = {};
21194
21567
  for (const value of rawValues("--var")) {
@@ -21485,16 +21858,16 @@ access.command("audit").description("audit collaborator roles + train-branch pus
21485
21858
  const repoClass = o.class;
21486
21859
  targets = [{ repo: o.repo, class: repoClass, releaseTrack: repoClass === "content" ? "trunk" : resolveReleaseTrack(meta, void 0, o.repo) }];
21487
21860
  } else {
21488
- const projectsJson = registryProjects ? JSON.stringify({ projects: registryProjects }) : (0, import_node_fs19.existsSync)("projects.json") ? (0, import_node_fs19.readFileSync)("projects.json", "utf8") : null;
21861
+ const projectsJson = registryProjects ? JSON.stringify({ projects: registryProjects }) : (0, import_node_fs20.existsSync)("projects.json") ? (0, import_node_fs20.readFileSync)("projects.json", "utf8") : null;
21489
21862
  if (!projectsJson) return failGraceful("access audit: no project registry \u2014 Hub API unreachable and projects.json not found; run from the MMI-Hub repo root or pass --repo <owner/repo>");
21490
- const fanoutJson = (0, import_node_fs19.existsSync)(".github/fanout-targets.json") ? (0, import_node_fs19.readFileSync)(".github/fanout-targets.json", "utf8") : null;
21863
+ const fanoutJson = (0, import_node_fs20.existsSync)(".github/fanout-targets.json") ? (0, import_node_fs20.readFileSync)(".github/fanout-targets.json", "utf8") : null;
21491
21864
  targets = loadAccessTargets(projectsJson, fanoutJson);
21492
21865
  }
21493
21866
  const derivedMatrix = registryProjects ? accessMatrixFromProjects(registryProjects) : {};
21494
- const fileMatrix = (0, import_node_fs19.existsSync)("access-matrix.json") ? loadAccessMatrix((0, import_node_fs19.readFileSync)("access-matrix.json", "utf8")) : {};
21867
+ const fileMatrix = (0, import_node_fs20.existsSync)("access-matrix.json") ? loadAccessMatrix((0, import_node_fs20.readFileSync)("access-matrix.json", "utf8")) : {};
21495
21868
  const matrix = mergeAccessMatrix(fileMatrix, derivedMatrix);
21496
21869
  const derivedContracts = registryProjects ? dataAccessContractsFromProjects(registryProjects) : { consumers: {} };
21497
- const fileContracts = (0, import_node_fs19.existsSync)("data-access-contracts.json") ? loadDataAccessContracts((0, import_node_fs19.readFileSync)("data-access-contracts.json", "utf8")) : { consumers: {} };
21870
+ const fileContracts = (0, import_node_fs20.existsSync)("data-access-contracts.json") ? loadDataAccessContracts((0, import_node_fs20.readFileSync)("data-access-contracts.json", "utf8")) : { consumers: {} };
21498
21871
  const dataAccess = mergeDataAccessContracts(fileContracts, derivedContracts);
21499
21872
  const report = await auditOrgAccess(targets, deps, matrix, dataAccess);
21500
21873
  console.log(o.json ? JSON.stringify(report, null, 2) : renderAccessReport(report));
@@ -21533,6 +21906,15 @@ program2.command("session-start").description("run the SessionStart verbs (whoam
21533
21906
  // never costs banner time and next session's glance renders instantly within budget.
21534
21907
  scheduleRefresh: () => spawnDetachedSelf(["board", "slice-refresh", "--quiet"], { spawn: import_node_child_process12.spawn, execPath: process.execPath, scriptPath: process.argv[1] })
21535
21908
  }),
21909
+ // local train sync (#2582): ff local development/main/rc to origin so this checkout never lags a
21910
+ // release pushed to origin (incl. the deferred main -> development alignment merge). Bounded git,
21911
+ // fail-soft, silent unless a branch moved.
21912
+ syncTrain: async (io) => {
21913
+ const gitRun = async (file, args) => (await execFileP2(file, args, { timeout: GIT_TIMEOUT_MS })).stdout;
21914
+ const result = await syncLocalTrainBranches(gitRun, { warn: (m) => io.err(`[mmi-hook] train sync: ${m}`) });
21915
+ const line = localTrainSyncBannerLine(result);
21916
+ if (line) io.log(line);
21917
+ },
21536
21918
  doctor: (io) => runDoctor({ banner: true }, io)
21537
21919
  });
21538
21920
  await runSessionStart(parallel, sequential, consoleIo);