@mutmutco/cli 3.9.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 +551 -287
  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,7 +9834,7 @@ 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) {
@@ -9826,11 +9917,11 @@ async function verifyPublishedRelease(deps, repo, tag, expectedTarget, expectedS
9826
9917
  if (release.targetCommitish !== expectedTarget) {
9827
9918
  throw new Error(`Release ${tag} targetCommitish is ${String(release.targetCommitish || "(missing)")}, expected ${expectedTarget}`);
9828
9919
  }
9829
- 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");
9830
9921
  if (tagSha !== expectedSha) {
9831
9922
  throw new Error(`Release ${tag} tag points at ${tagSha}, expected ${expectedSha}`);
9832
9923
  }
9833
- const branchOut = clean(await runGitRemoteRead(deps, ["ls-remote", "origin", `refs/heads/${expectedTarget}`]));
9924
+ const branchOut = clean2(await runGitRemoteRead(deps, ["ls-remote", "origin", `refs/heads/${expectedTarget}`]));
9834
9925
  const branchSha = requireValue(branchOut.split(/\s+/)[0] ?? "", `origin/${expectedTarget} sha`);
9835
9926
  if (branchSha !== expectedSha) {
9836
9927
  throw new Error(`origin/${expectedTarget} points at ${branchSha}, expected ${expectedSha}`);
@@ -9891,7 +9982,7 @@ function ensurePositiveCount(out, emptyMessage) {
9891
9982
  if (count <= 0) throw new Error(emptyMessage);
9892
9983
  }
9893
9984
  async function remoteBranchExists(deps, branch) {
9894
- const out = clean(await runGitRemoteRead(deps, ["ls-remote", "origin", `refs/heads/${branch}`]));
9985
+ const out = clean2(await runGitRemoteRead(deps, ["ls-remote", "origin", `refs/heads/${branch}`]));
9895
9986
  return out.length > 0;
9896
9987
  }
9897
9988
  async function loadReleaseTrackBranchHints(deps) {
@@ -9903,10 +9994,10 @@ async function loadReleaseTrackBranchHints(deps) {
9903
9994
  return { hasDevelopmentBranch, hasMainBranch, hasRcBranch };
9904
9995
  }
9905
9996
  async function buildTrainApplyContext(deps) {
9906
- 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");
9907
9998
  const [owner, name] = repo.split("/");
9908
9999
  if (!owner || !name) throw new Error(`repo must be owner/name, got ${repo}`);
9909
- 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");
9910
10001
  const verdict = await deps.trainAuthority(repo);
9911
10002
  if (!verdict.ok) throw new Error(`${commandAuthorityLabel(owner)}: train authority could not be verified (${verdict.error})`);
9912
10003
  if (!verdict.train) {
@@ -9927,11 +10018,11 @@ async function requireCleanTree(deps) {
9927
10018
  if (porcelainHasBlockingChanges(status)) throw new Error("working tree must be clean before train apply");
9928
10019
  }
9929
10020
  async function requireBranch(deps, branch) {
9930
- 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"]));
9931
10022
  if (current !== branch) throw new Error(`must run from ${branch}, currently on ${current || "(unknown)"}`);
9932
10023
  }
9933
10024
  async function currentBranch(deps) {
9934
- 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)";
9935
10026
  }
9936
10027
  async function restoreCheckoutAfterRelease(deps, startBranch) {
9937
10028
  const status = await deps.run("git", ["status", "--porcelain"]);
@@ -9974,17 +10065,17 @@ async function restoreCheckoutAfterRelease(deps, startBranch) {
9974
10065
  }
9975
10066
  }
9976
10067
  async function withCheckoutRestoredAfterRelease(deps, result, startBranch) {
9977
- return {
9978
- ...result,
9979
- checkout: await restoreCheckoutAfterRelease(deps, startBranch)
9980
- };
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 } : {} };
9981
10072
  }
9982
10073
  function normGitPath(p) {
9983
10074
  return p.replace(/\\/g, "/").replace(/\/+$/, "").toLowerCase();
9984
10075
  }
9985
10076
  async function releaseRcBranchLock(deps) {
9986
10077
  const porcelain = await deps.run("git", ["worktree", "list", "--porcelain"]);
9987
- 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"])));
9988
10079
  for (const wt of parseWorktreePorcelain(porcelain)) {
9989
10080
  if (wt.branch !== "rc") continue;
9990
10081
  if (normGitPath(wt.path) === cwd) continue;
@@ -10200,7 +10291,7 @@ async function discoverRequiredCheckContexts(deps, ctx, branch) {
10200
10291
  return [...contexts];
10201
10292
  }
10202
10293
  async function findOpenAlignmentPr(deps, ctx) {
10203
- 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"]));
10204
10295
  if (!out) return void 0;
10205
10296
  const rows = JSON.parse(out);
10206
10297
  const row = rows.find((r) => typeof r.number === "number" && typeof r.url === "string");
@@ -10228,14 +10319,14 @@ async function rollDevelopmentForward(deps, ctx, tag) {
10228
10319
  note: `alignment PR already open: ${existing.url} \u2014 land it with \`mmi-cli pr merge ${existing.number} --auto --merge\``
10229
10320
  };
10230
10321
  }
10231
- 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"]));
10232
10323
  if (ahead === "0") {
10233
10324
  return { status: "aligned", note: "development already contains the released main; nothing to roll forward" };
10234
10325
  }
10235
10326
  const body = `Carries the ${tag} release (including the version fold) from \`main\` back to \`development\`.
10236
10327
 
10237
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.`;
10238
- 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]));
10239
10330
  const number = parsePrNumber(url);
10240
10331
  return {
10241
10332
  status: "pr-pending",
@@ -10353,13 +10444,13 @@ Do not delete or force-move the pushed tag; rerun the train only after confirmin
10353
10444
  }
10354
10445
  async function probeRemoteTag(deps, tag) {
10355
10446
  const remoteOut = await runGitRemoteRead(deps, ["ls-remote", "origin", `refs/tags/${tag}`]);
10356
- return clean(remoteOut).split(/\s+/)[0] || "";
10447
+ return clean2(remoteOut).split(/\s+/)[0] || "";
10357
10448
  }
10358
10449
  async function ensureTagPushed(deps, tag, sha, probed) {
10359
10450
  const remoteSha = probed ? probed.remoteSha : await probeRemoteTag(deps, tag);
10360
10451
  let localSha = "";
10361
10452
  try {
10362
- 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}`]));
10363
10454
  } catch {
10364
10455
  }
10365
10456
  if (remoteSha) {
@@ -10385,7 +10476,7 @@ function probeRcResumeTags(deps, base) {
10385
10476
  }
10386
10477
  async function resolveRcResumeTag(deps, base, sha, probed) {
10387
10478
  const out = probed ?? await runGitRemoteRead(deps, ["ls-remote", "--tags", "origin", `refs/tags/${base}-rc.*`]);
10388
- 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));
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));
10389
10480
  const unique = [...new Set(onSha)];
10390
10481
  if (unique.length === 0) return { tag: null };
10391
10482
  const rcNum = (t) => Number.parseInt(t.replace(/^.*-rc\./, ""), 10);
@@ -10581,7 +10672,7 @@ async function preflightMergeToMain(deps, deployModel, remoteRef, blockingPrefix
10581
10672
  async function executeMergeToMain(deps, sourceRef, mergeLabel, tolerated, predicted, preFold) {
10582
10673
  await deps.run("git", ["checkout", "main"]);
10583
10674
  await ffOnlyPull(deps, "main");
10584
- preFold.mainSha = clean(await deps.run("git", ["rev-parse", "main"]));
10675
+ preFold.mainSha = clean2(await deps.run("git", ["rev-parse", "main"]));
10585
10676
  if (predicted.length === 0) {
10586
10677
  await deps.run("git", ["merge", sourceRef, "--no-edit"]);
10587
10678
  } else {
@@ -10592,8 +10683,8 @@ async function probeFoldFailureState(deps) {
10592
10683
  const branch = await currentBranch(deps);
10593
10684
  const mergeInProgress = await deps.run("git", ["rev-parse", "-q", "--verify", "MERGE_HEAD"]).then(() => true).catch(() => false);
10594
10685
  const dirty = porcelainHasBlockingChanges(await deps.run("git", ["status", "--porcelain"]).catch(() => ""));
10595
- const mainSha = await deps.run("git", ["rev-parse", "main"]).then(clean).catch(() => "");
10596
- 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(() => "");
10597
10688
  const originIsAncestorOfMain = Boolean(mainSha) && Boolean(originMainSha) ? await deps.run("git", ["merge-base", "--is-ancestor", "origin/main", "main"]).then(() => true).catch(() => false) : false;
10598
10689
  return { branch, mergeInProgress, dirty, mainSha, originMainSha, originIsAncestorOfMain };
10599
10690
  }
@@ -10654,7 +10745,7 @@ Recovery sequence:
10654
10745
  );
10655
10746
  }
10656
10747
  }
10657
- const rcSha = await deps.run("git", ["rev-parse", "rc"]).then(clean).catch(() => "");
10748
+ const rcSha = await deps.run("git", ["rev-parse", "rc"]).then(clean2).catch(() => "");
10658
10749
  const rcDescends = Boolean(preRcSha) && Boolean(rcSha) ? await deps.run("git", ["merge-base", "--is-ancestor", preRcSha, "rc"]).then(() => true).catch(() => false) : false;
10659
10750
  if (branch === "rc" && rcDescends) {
10660
10751
  try {
@@ -10745,7 +10836,7 @@ async function completeMainRelease(deps, ctx, meta, deployModel, watch, options,
10745
10836
  throw partialTrainRecoveryError(e, { repo: ctx.repo, tag, stage: "main" });
10746
10837
  }
10747
10838
  await runGitPush(deps, ["push", "origin", "main"]);
10748
- const releaseUrl = clean(await deps.run("gh", ["release", "create", tag, "--target", "main", "--generate-notes", "--latest", "--repo", ctx.repo])) || void 0;
10839
+ const releaseUrl = clean2(await deps.run("gh", ["release", "create", tag, "--target", "main", "--generate-notes", "--latest", "--repo", ctx.repo])) || void 0;
10749
10840
  await verifyPublishedRelease(deps, ctx.repo, tag, "main", releaseSha);
10750
10841
  const announceNote = deps.announce ? (await deps.announce({ repo: ctx.repo, tag, summaryFile: options.announceSummaryFile })).note : void 0;
10751
10842
  const autoRunSince = (deps.now ?? Date.now)();
@@ -10783,21 +10874,21 @@ async function runTrainApplyPipeline(mode, input) {
10783
10874
  "nothing to promote: origin/development is not ahead of origin/rc"
10784
10875
  );
10785
10876
  const deployModel2 = await preflight(deps, ctx, "rc", meta);
10786
- 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");
10787
10878
  await releaseRcBranchLock(deps);
10788
10879
  const rcTagProbe = await probeRcResumeTags(deps, releaseBase);
10789
10880
  await deps.run("git", ["checkout", "rc"]);
10790
10881
  await ffOnlyPull(deps, "rc");
10791
- const preRcSha = requireValue(clean(await deps.run("git", ["rev-parse", "rc"])), "pre-merge rc sha");
10882
+ const preRcSha = requireValue(clean2(await deps.run("git", ["rev-parse", "rc"])), "pre-merge rc sha");
10792
10883
  let rcSha;
10793
10884
  let resume;
10794
10885
  let tag2;
10795
10886
  let tagPush;
10796
10887
  try {
10797
10888
  await deps.run("git", ["merge", "development", "--no-edit"]);
10798
- rcSha = requireValue(clean(await deps.run("git", ["rev-parse", "rc"])), "rc sha");
10889
+ rcSha = requireValue(clean2(await deps.run("git", ["rev-parse", "rc"])), "rc sha");
10799
10890
  resume = await resolveRcResumeTag(deps, releaseBase, rcSha, rcTagProbe);
10800
- tag2 = resume.tag ?? requireValue(clean(await deps.run("node", ["scripts/next-version.mjs", "rc"])), "rc tag");
10891
+ tag2 = resume.tag ?? requireValue(clean2(await deps.run("node", ["scripts/next-version.mjs", "rc"])), "rc tag");
10801
10892
  tagPush = await ensureTagPushed(deps, tag2, rcSha);
10802
10893
  } catch (e) {
10803
10894
  throw await recoverFailedRcand(deps, e, preRcSha);
@@ -10835,8 +10926,8 @@ async function runTrainApplyPipeline(mode, input) {
10835
10926
  "nothing to release: origin/development is not ahead of origin/main"
10836
10927
  );
10837
10928
  const deployModel2 = await preflight(deps, ctx, "main", meta);
10838
- const tag2 = requireValue(clean(await deps.run("node", ["scripts/next-version.mjs", "cycle"])), "release tag");
10839
- 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"])) : "";
10840
10931
  const { foldPaths: foldPaths2, tolerated: tolerated2, predicted: predicted2 } = await preflightMergeToMain(
10841
10932
  deps,
10842
10933
  deployModel2,
@@ -10849,7 +10940,7 @@ async function runTrainApplyPipeline(mode, input) {
10849
10940
  const { versionFold: versionFold2, releaseSha: releaseSha2 } = await runFoldStage(deps, "development", preFold2, async () => {
10850
10941
  await executeMergeToMain(deps, "development", "development -> main", tolerated2, predicted2, preFold2);
10851
10942
  const versionFold3 = await foldReleaseVersion(deps, deployModel2, tag2, foldPaths2);
10852
- 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");
10853
10944
  return { versionFold: versionFold3, releaseSha: releaseSha3 };
10854
10945
  });
10855
10946
  const { checks: checks2, releaseUrl: releaseUrl2, announceNote: announceNote2, dispatch: d2 } = await completeMainRelease(deps, ctx, meta, deployModel2, watch, options, tag2, releaseSha2, "development", preFold2, tagProbe2);
@@ -10927,14 +11018,14 @@ async function runTrainApplyPipeline(mode, input) {
10927
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].`
10928
11019
  );
10929
11020
  }
10930
- const releasedRcSha = clean(await deps.run("git", ["rev-parse", "origin/rc"]));
10931
- const tag = requireValue(clean(await deps.run("node", ["scripts/next-version.mjs", "release"])), "release tag");
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");
10932
11023
  const tagProbe = { remoteSha: await probeRemoteTag(deps, tag) };
10933
11024
  const preFold = {};
10934
11025
  const { versionFold, releaseSha } = await runFoldStage(deps, "rc", preFold, async () => {
10935
11026
  await executeMergeToMain(deps, "rc", "rc -> main", tolerated, predicted, preFold);
10936
11027
  const versionFold2 = await foldReleaseVersion(deps, deployModel, tag, foldPaths);
10937
- const releaseSha2 = requireValue(clean(await deps.run("git", ["rev-parse", "main"])), "release sha");
11028
+ const releaseSha2 = requireValue(clean2(await deps.run("git", ["rev-parse", "main"])), "release sha");
10938
11029
  return { versionFold: versionFold2, releaseSha: releaseSha2 };
10939
11030
  });
10940
11031
  const { checks, releaseUrl, announceNote, dispatch: d } = await completeMainRelease(deps, ctx, meta, deployModel, watch, options, tag, releaseSha, "rc", preFold, tagProbe);
@@ -11053,7 +11144,7 @@ async function retireRcRuntime(deps, ctx, model, deployStatus, releasedRcSha) {
11053
11144
  }
11054
11145
  try {
11055
11146
  await deps.run("git", ["fetch", "origin", "rc"]);
11056
- const rcNow = clean(await deps.run("git", ["rev-parse", "origin/rc"]));
11147
+ const rcNow = clean2(await deps.run("git", ["rev-parse", "origin/rc"]));
11057
11148
  if (rcNow !== releasedRcSha) {
11058
11149
  return {
11059
11150
  status: "skipped",
@@ -11084,7 +11175,7 @@ async function runTenantRedeploy(deps, options) {
11084
11175
  const repo = options.repo;
11085
11176
  const [owner, name] = repo.split("/");
11086
11177
  if (!owner || !name) throw new Error(`repo must be owner/name, got ${repo}`);
11087
- 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");
11088
11179
  const verdict = await deps.trainAuthority(repo);
11089
11180
  if (!verdict.ok) throw new Error(`${commandAuthorityLabel(owner)}: train authority could not be verified (${verdict.error})`);
11090
11181
  if (!verdict.train) throw new Error(`${commandAuthorityLabel(owner)}: @${login} is ${verdict.role} \u2014 no train authority on ${repo}`);
@@ -11324,7 +11415,7 @@ var HOTFIX_RUN_FIND_ATTEMPTS = 10;
11324
11415
  var HOTFIX_RUN_FIND_DELAY_MS = 15e3;
11325
11416
  var HOTFIX_VERIFY_ATTEMPTS = 5;
11326
11417
  var HOTFIX_VERIFY_RETRY_MS = 6e3;
11327
- function clean2(out) {
11418
+ function clean3(out) {
11328
11419
  return out.trim();
11329
11420
  }
11330
11421
  function sleeper(deps) {
@@ -11382,7 +11473,7 @@ async function resolveHotfixSource(deps, ctx, from) {
11382
11473
  if (!sha2) throw new Error(`PR #${num} has no merge commit recorded \u2014 name the commit SHA explicitly`);
11383
11474
  return { sha: sha2, label: `PR #${num} (${sha2.slice(0, 7)})` };
11384
11475
  }
11385
- 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}`]));
11386
11477
  if (!sha) throw new Error(`could not resolve commit ${from}`);
11387
11478
  return { sha, label: sha.slice(0, 7) };
11388
11479
  }
@@ -11410,7 +11501,7 @@ async function runHotfixStart(deps, options) {
11410
11501
  };
11411
11502
  }
11412
11503
  const { sha, label } = await resolveHotfixSource(deps, ctx, options.from);
11413
- 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}`]));
11414
11505
  if (remoteBranch) {
11415
11506
  await deps.run("git", ["checkout", branch]);
11416
11507
  await deps.run("git", ["pull", "--ff-only", "origin", branch]);
@@ -11441,7 +11532,7 @@ async function runHotfixStart(deps, options) {
11441
11532
  await deps.run("git", ["push", "-u", "origin", branch]);
11442
11533
  }
11443
11534
  const bumpNote = deployModel === "hub-serverless" ? " with the locked distribution bump" : "";
11444
- const prUrl = clean2(await deps.run("gh", [
11535
+ const prUrl = clean3(await deps.run("gh", [
11445
11536
  "pr",
11446
11537
  "create",
11447
11538
  "--repo",
@@ -11585,7 +11676,7 @@ async function runHotfixRelease(deps, versionInput, options = {}) {
11585
11676
  }
11586
11677
  let verifyNote;
11587
11678
  if (deployModel === "hub-serverless") {
11588
- 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"]));
11589
11680
  const publishSucceeded = runs.find((r) => r.workflow === "publish.yml")?.conclusion === "success";
11590
11681
  try {
11591
11682
  await deps.run("git", ["-c", "advice.detachedHead=false", "checkout", tag]);
@@ -11672,9 +11763,9 @@ async function runHotfixStatus(deps, versionInput) {
11672
11763
  return { ...ctx, command: "hotfix-status", ...facts, ...deriveHotfixState(facts), warnings };
11673
11764
  }
11674
11765
  async function gatherHotfixFacts(deps, ctx, tag, version) {
11675
- 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)}`])));
11676
11767
  const pr2 = await findHotfixPr(deps, ctx, tag);
11677
- 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}`]));
11678
11769
  const tagPushed = Boolean(remoteTag);
11679
11770
  const tagSha = remoteTag.split(/\s+/)[0] || "";
11680
11771
  let releaseExists = false;
@@ -11708,7 +11799,7 @@ async function gatherHotfixFacts(deps, ctx, tag, version) {
11708
11799
  }
11709
11800
  }
11710
11801
  }
11711
- 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");
11712
11803
  return { tag, version, branchExists, pr: pr2 ? { number: pr2.number, state: pr2.state, url: pr2.url } : null, tagPushed, releaseExists, runs, npmVersion };
11713
11804
  }
11714
11805
  function compareHotfixVersions(a, b) {
@@ -11743,7 +11834,7 @@ async function findInFlightHotfixVersion(deps, ctx, latestMainTag) {
11743
11834
  const m = typeof row.headRefName === "string" && /^hotfix\/(v\d+\.\d+\.\d+)/.exec(row.headRefName);
11744
11835
  if (m) tags.add(m[1]);
11745
11836
  }
11746
- 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*"]));
11747
11838
  for (const line of branchOut.split("\n").filter(Boolean)) {
11748
11839
  const ref = line.split(/\s+/)[1] ?? "";
11749
11840
  const m = /^refs\/heads\/hotfix\/(v\d+\.\d+\.\d+)/.exec(ref);
@@ -12235,7 +12326,7 @@ function renderAccessReport(report) {
12235
12326
  }
12236
12327
 
12237
12328
  // src/bootstrap-verify.ts
12238
- var TRAIN_BRANCHES = ["development", "rc", "main"];
12329
+ var TRAIN_BRANCHES2 = ["development", "rc", "main"];
12239
12330
  var requiredDocs = ["README.md", "architecture.md"];
12240
12331
  var requiredIssueTemplates = [
12241
12332
  ".github/ISSUE_TEMPLATE/bug.yml",
@@ -12413,7 +12504,7 @@ async function verifyBootstrap(repo, repoClass, deps, releaseTrack) {
12413
12504
  detail: hasPushAllowlist(protection) ? void 0 : "restrictions.users is empty or unset"
12414
12505
  });
12415
12506
  }
12416
- 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));
12417
12508
  checks.push({
12418
12509
  ok: strayTrainBranches.length === 0,
12419
12510
  label: "no stray train branch for this track",
@@ -12888,13 +12979,13 @@ var WIKI_STAMP_FILE = ".wiki-state.json";
12888
12979
  function redactWikiSecrets(msg) {
12889
12980
  return msg.replace(/x-access-token:[^@\s]+@/gi, "x-access-token:***@").replace(/(AUTHORIZATION:\s*basic\s+)[A-Za-z0-9+/=]+/gi, "$1***");
12890
12981
  }
12982
+ function wikiAuthArgs(token) {
12983
+ return ["-c", `http.extraheader=AUTHORIZATION: basic ${Buffer.from(`x-access-token:${token}`).toString("base64")}`];
12984
+ }
12891
12985
  function publishWikiViaGit(deps, opts) {
12892
12986
  const dir = deps.mkdtemp();
12893
12987
  const url = `https://github.com/${opts.repo}.wiki.git`;
12894
- const authArgs = [
12895
- "-c",
12896
- `http.extraheader=AUTHORIZATION: basic ${Buffer.from(`x-access-token:${opts.token}`).toString("base64")}`
12897
- ];
12988
+ const authArgs = wikiAuthArgs(opts.token);
12898
12989
  try {
12899
12990
  deps.gitRun(["init", "-q"], dir);
12900
12991
  deps.gitRun(["remote", "add", "origin", url], dir);
@@ -12949,12 +13040,37 @@ function publishWikiViaGit(deps, opts) {
12949
13040
  deps.rm(dir);
12950
13041
  }
12951
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
+ }
12952
13067
  function realWikiGitPublishDeps() {
12953
13068
  return {
12954
13069
  mkdtemp: () => (0, import_node_fs13.mkdtempSync)((0, import_node_path14.join)((0, import_node_os5.tmpdir)(), "wiki-")),
12955
13070
  readdir: (dir) => (0, import_node_fs13.readdirSync)(dir),
12956
13071
  copyFile: (src, dest) => (0, import_node_fs13.copyFileSync)(src, dest),
12957
13072
  fileExists: (p) => (0, import_node_fs13.existsSync)(p),
13073
+ readFile: (p) => (0, import_node_fs13.readFileSync)(p, "utf8"),
12958
13074
  rm: (p) => (0, import_node_fs13.rmSync)(p, { recursive: true, force: true }),
12959
13075
  gitProbe: (args, cwd) => {
12960
13076
  try {
@@ -12972,6 +13088,124 @@ function realWikiGitPublishDeps() {
12972
13088
  };
12973
13089
  }
12974
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
+
12975
13209
  // src/project-readiness.ts
12976
13210
  function stagesForTrack(meta) {
12977
13211
  return branchesForTrack(resolveReleaseTrack(meta)).map((b) => b === "development" ? "dev" : b);
@@ -13990,22 +14224,22 @@ var DEPLOY_STAGES = ["dev", "rc", "main"];
13990
14224
  var DEPLOY_DOMAIN_RE = /^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)+$/;
13991
14225
  var DEPLOY_SHELL_SAFE_RE = /^[A-Za-z0-9./:_?&=%-]*$/;
13992
14226
  function buildSetDeployPatch(slug, input) {
13993
- const clean3 = (v) => typeof v === "string" && v.trim() !== "" ? v.trim() : void 0;
13994
- const stage2 = clean3(input.stage);
14227
+ const clean4 = (v) => typeof v === "string" && v.trim() !== "" ? v.trim() : void 0;
14228
+ const stage2 = clean4(input.stage);
13995
14229
  if (!stage2 || !DEPLOY_STAGES.includes(stage2)) {
13996
14230
  throw new Error(`project set-deploy: --stage must be one of: ${DEPLOY_STAGES.join(", ")}`);
13997
14231
  }
13998
- const substrate = clean3(input.substrate) ?? "hetzner-ssh";
14232
+ const substrate = clean4(input.substrate) ?? "hetzner-ssh";
13999
14233
  if (!DEPLOY_SUBSTRATES.includes(substrate)) {
14000
14234
  throw new Error(`project set-deploy: --substrate must be one of: ${DEPLOY_SUBSTRATES.join(", ")}`);
14001
14235
  }
14002
- const sshHost = clean3(input.sshHost);
14236
+ const sshHost = clean4(input.sshHost);
14003
14237
  if (substrate === "hetzner-ssh" && !sshHost) {
14004
14238
  throw new Error("project set-deploy: hetzner-ssh requires --ssh-host");
14005
14239
  }
14006
- const sshUser = clean3(input.sshUser) ?? "root";
14007
- const deployPath = clean3(input.deployPath) ?? `/opt/mmi/${slug}/${stage2}`;
14008
- 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;
14009
14243
  for (const [label, v] of [["--ssh-host", sshHost], ["--ssh-user", sshUser], ["--deploy-path", deployPath], ["--service", serviceName]]) {
14010
14244
  if (v !== void 0 && !DEPLOY_SHELL_SAFE_RE.test(v)) throw new Error(`project set-deploy: ${label} contains unsafe characters`);
14011
14245
  }
@@ -14014,9 +14248,9 @@ function buildSetDeployPatch(slug, input) {
14014
14248
  port = Number(input.port);
14015
14249
  if (!Number.isInteger(port) || port < 1 || port > 65535) throw new Error("project set-deploy: --port must be an integer 1..65535");
14016
14250
  }
14017
- const domain = clean3(input.domain);
14251
+ const domain = clean4(input.domain);
14018
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)}`);
14019
- 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);
14020
14254
  for (const a of aliases) {
14021
14255
  if (!DEPLOY_DOMAIN_RE.test(a)) throw new Error(`project set-deploy: --alias must be a DNS hostname, got ${JSON.stringify(a)}`);
14022
14256
  }
@@ -14923,8 +15157,8 @@ async function secretsUse(deps, key, opts) {
14923
15157
  }
14924
15158
 
14925
15159
  // src/secrets-commands.ts
14926
- var import_node_fs14 = require("node:fs");
14927
- var import_node_path15 = require("node:path");
15160
+ var import_node_fs15 = require("node:fs");
15161
+ var import_node_path16 = require("node:path");
14928
15162
  var RAILS_CREDENTIALS_DECRYPT_TIMEOUT_MS = 3e4;
14929
15163
  var DEFAULT_RAILS_CREDENTIALS_FILE = "config/credentials.yml.enc";
14930
15164
  var DEFAULT_RAILS_MASTER_KEY_FILE = "config/master.key";
@@ -14932,18 +15166,18 @@ function collectMap(value, previous = []) {
14932
15166
  return [...previous, value];
14933
15167
  }
14934
15168
  async function decryptRailsCredentials(input) {
14935
- const appDir = (0, import_node_path15.resolve)(input.appDir ?? process.cwd());
15169
+ const appDir = (0, import_node_path16.resolve)(input.appDir ?? process.cwd());
14936
15170
  const credentialsFile = input.credentialsFile ?? DEFAULT_RAILS_CREDENTIALS_FILE;
14937
15171
  const masterKeyFile = input.masterKeyFile ?? DEFAULT_RAILS_MASTER_KEY_FILE;
14938
- const credentialsPath = (0, import_node_path15.resolve)(appDir, credentialsFile);
14939
- 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);
14940
15174
  const env = {
14941
15175
  ...process.env,
14942
15176
  MMI_RAILS_CREDENTIALS_FILE: credentialsPath,
14943
15177
  MMI_RAILS_MASTER_KEY_FILE: masterKeyPath
14944
15178
  };
14945
- if ((0, import_node_fs14.existsSync)(masterKeyPath)) {
14946
- 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();
14947
15181
  }
14948
15182
  const script = [
14949
15183
  'require "json"',
@@ -15009,7 +15243,7 @@ function registerSecretsCommands(program3) {
15009
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) => {
15010
15244
  let body;
15011
15245
  try {
15012
- 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");
15013
15247
  } catch (e) {
15014
15248
  return fail(`secrets org-catalog: cannot read --file ${o.file}: ${e.message}`);
15015
15249
  }
@@ -15084,7 +15318,7 @@ function registerSecretsCommands(program3) {
15084
15318
  {
15085
15319
  ...d,
15086
15320
  decryptRailsCredentials,
15087
- 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))
15088
15322
  },
15089
15323
  {
15090
15324
  repo: o.repo,
@@ -15173,10 +15407,10 @@ function registerEdgeCommands(program3) {
15173
15407
  }
15174
15408
 
15175
15409
  // src/doctor-run.ts
15176
- var import_node_fs18 = require("node:fs");
15410
+ var import_node_fs19 = require("node:fs");
15177
15411
  var import_node_child_process11 = require("node:child_process");
15178
15412
  var import_promises2 = require("node:fs/promises");
15179
- var import_node_path18 = require("node:path");
15413
+ var import_node_path19 = require("node:path");
15180
15414
  var import_node_os7 = require("node:os");
15181
15415
 
15182
15416
  // src/plugin-guard.ts
@@ -15199,9 +15433,9 @@ function buildGuardSessionStartLine(state, opts = {}) {
15199
15433
 
15200
15434
  // src/cursor-plugin-seed.ts
15201
15435
  var import_node_child_process10 = require("node:child_process");
15202
- var import_node_fs15 = require("node:fs");
15436
+ var import_node_fs16 = require("node:fs");
15203
15437
  var import_node_os6 = require("node:os");
15204
- var import_node_path16 = require("node:path");
15438
+ var import_node_path17 = require("node:path");
15205
15439
  var import_node_util7 = require("node:util");
15206
15440
 
15207
15441
  // src/doctor.ts
@@ -16521,17 +16755,17 @@ function ghReleaseTarballApiArgs(tag) {
16521
16755
  }
16522
16756
  function cursorUserGlobalStatePath() {
16523
16757
  if (process.platform === "win32") {
16524
- const base = process.env.APPDATA || (0, import_node_path16.join)((0, import_node_os6.homedir)(), "AppData", "Roaming");
16525
- 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");
16526
16760
  }
16527
16761
  if (process.platform === "darwin") {
16528
- 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");
16529
16763
  }
16530
- 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");
16531
16765
  }
16532
16766
  async function readCursorThirdPartyExtensibilityEnabled(execFileP5) {
16533
16767
  const dbPath = cursorUserGlobalStatePath();
16534
- if (!(0, import_node_fs15.existsSync)(dbPath)) return void 0;
16768
+ if (!(0, import_node_fs16.existsSync)(dbPath)) return void 0;
16535
16769
  try {
16536
16770
  const { stdout } = await execFileP5("sqlite3", [dbPath, `SELECT value FROM ItemTable WHERE key = '${CURSOR_THIRD_PARTY_STATE_KEY}';`], {
16537
16771
  timeout: 5e3
@@ -16545,57 +16779,57 @@ async function readCursorThirdPartyExtensibilityEnabled(execFileP5) {
16545
16779
  }
16546
16780
  }
16547
16781
  function syncDirContents(src, dest) {
16548
- (0, import_node_fs15.mkdirSync)(dest, { recursive: true });
16549
- for (const name of (0, import_node_fs15.readdirSync)(dest)) {
16550
- (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 });
16551
16785
  }
16552
- (0, import_node_fs15.cpSync)(src, dest, { recursive: true });
16786
+ (0, import_node_fs16.cpSync)(src, dest, { recursive: true });
16553
16787
  }
16554
16788
  function releaseTag(releasedVersion) {
16555
16789
  return releasedVersion.startsWith("v") ? releasedVersion : `v${releasedVersion}`;
16556
16790
  }
16557
16791
  async function extractPluginMmiFromHubCheckout(hubCheckout, tag, tmpRoot, execFileP5) {
16558
- const tarFile = (0, import_node_path16.join)(tmpRoot, "archive.tar");
16792
+ const tarFile = (0, import_node_path17.join)(tmpRoot, "archive.tar");
16559
16793
  try {
16560
16794
  await execFileP5("git", gitFetchReleaseTagArgs(hubCheckout, tag), { timeout: 6e4 });
16561
16795
  await execFileP5("git", ["-C", hubCheckout, "archive", "--format=tar", `--output=${tarFile}`, tag, "plugins/mmi"], {
16562
16796
  timeout: 6e4
16563
16797
  });
16564
16798
  await execFileP5("tar", ["-xf", tarFile, "-C", tmpRoot], { timeout: 6e4 });
16565
- const pluginMmi = (0, import_node_path16.join)(tmpRoot, "plugins", "mmi");
16566
- 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;
16567
16801
  } catch {
16568
16802
  return void 0;
16569
16803
  }
16570
16804
  }
16571
16805
  async function downloadPluginMmiViaGh(tag, tmpRoot) {
16572
- const tarPath = (0, import_node_path16.join)(tmpRoot, "repo.tgz");
16806
+ const tarPath = (0, import_node_path17.join)(tmpRoot, "repo.tgz");
16573
16807
  try {
16574
- (0, import_node_fs15.mkdirSync)(tmpRoot, { recursive: true });
16808
+ (0, import_node_fs16.mkdirSync)(tmpRoot, { recursive: true });
16575
16809
  const { stdout } = await execFileBuffer("gh", ghReleaseTarballApiArgs(tag), {
16576
16810
  timeout: 12e4,
16577
16811
  maxBuffer: 100 * 1024 * 1024,
16578
16812
  encoding: "buffer",
16579
16813
  windowsHide: true
16580
16814
  });
16581
- (0, import_node_fs15.writeFileSync)(tarPath, stdout);
16815
+ (0, import_node_fs16.writeFileSync)(tarPath, stdout);
16582
16816
  await execFileBuffer("tar", ["-xzf", tarPath, "-C", tmpRoot], { timeout: 12e4, windowsHide: true });
16583
- 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");
16584
16818
  if (!top) return void 0;
16585
- const pluginMmi = (0, import_node_path16.join)(tmpRoot, top, "plugins", "mmi");
16586
- 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;
16587
16821
  } catch {
16588
16822
  return void 0;
16589
16823
  }
16590
16824
  }
16591
16825
  async function resolvePluginMmiSource(releasedVersion, hubCheckout, tmpRoot, execFileP5) {
16592
- (0, import_node_fs15.mkdirSync)(tmpRoot, { recursive: true });
16826
+ (0, import_node_fs16.mkdirSync)(tmpRoot, { recursive: true });
16593
16827
  const tag = releaseTag(releasedVersion);
16594
16828
  if (hubCheckout) {
16595
16829
  const fromHub = await extractPluginMmiFromHubCheckout(hubCheckout, tag, tmpRoot, execFileP5);
16596
16830
  if (fromHub) return fromHub;
16597
16831
  }
16598
- return downloadPluginMmiViaGh(tag, (0, import_node_path16.join)(tmpRoot, "gh"));
16832
+ return downloadPluginMmiViaGh(tag, (0, import_node_path17.join)(tmpRoot, "gh"));
16599
16833
  }
16600
16834
  function cursorPluginPinsNeedingSeed(pins, releasedVersion) {
16601
16835
  if (!isSemverVersion2(releasedVersion)) return pins.filter((pin) => !pin.hasPluginJson || !pin.hasHooksJson || pin.isEmpty);
@@ -16609,7 +16843,7 @@ function cursorPluginCacheSeedTargets(pins, releasedVersion, cacheRoot, cacheRoo
16609
16843
  const needing = cursorPluginPinsNeedingSeed(pins, releasedVersion);
16610
16844
  if (needing.length > 0) return needing.map((pin) => pin.path);
16611
16845
  if (pins.length === 0 && cacheRoot && cacheRootExists && isSemverVersion2(releasedVersion)) {
16612
- return [(0, import_node_path16.join)(cacheRoot, `v${releasedVersion.replace(/^v/, "")}`)];
16846
+ return [(0, import_node_path17.join)(cacheRoot, `v${releasedVersion.replace(/^v/, "")}`)];
16613
16847
  }
16614
16848
  return [];
16615
16849
  }
@@ -16624,7 +16858,7 @@ async function applyCursorPluginCacheSeed(input) {
16624
16858
  for (const dest of targets) {
16625
16859
  syncDirContents(source, dest);
16626
16860
  }
16627
- (0, import_node_fs15.rmSync)(tmpRoot, { recursive: true, force: true });
16861
+ (0, import_node_fs16.rmSync)(tmpRoot, { recursive: true, force: true });
16628
16862
  return true;
16629
16863
  }
16630
16864
  var CURSOR_ENABLED_PIN_LABEL = "Cursor Team Marketplace enabled plugin pin";
@@ -16983,9 +17217,9 @@ ${block}
16983
17217
  }
16984
17218
 
16985
17219
  // src/cli-doctor-shared.ts
16986
- var import_node_fs16 = require("node:fs");
16987
- var import_node_path17 = require("node:path");
16988
17220
  var import_node_fs17 = require("node:fs");
17221
+ var import_node_path18 = require("node:path");
17222
+ var import_node_fs18 = require("node:fs");
16989
17223
  var GC_GH_TIMEOUT_MS = 2e4;
16990
17224
  async function awsCallerArn() {
16991
17225
  try {
@@ -17031,7 +17265,7 @@ async function localBranchHeads() {
17031
17265
  }
17032
17266
  async function currentRepoWorktreeGitRoot(repoRoot) {
17033
17267
  const gitCommonDir = (await execFileP2("git", ["rev-parse", "--git-common-dir"], { timeout: GIT_TIMEOUT_MS }).catch(() => ({ stdout: "" }))).stdout.trim();
17034
- return gitCommonDir ? (0, import_node_path17.resolve)(repoRoot, gitCommonDir, "worktrees") : "";
17268
+ return gitCommonDir ? (0, import_node_path18.resolve)(repoRoot, gitCommonDir, "worktrees") : "";
17035
17269
  }
17036
17270
  async function worktreeBranches() {
17037
17271
  const { stdout } = await execFileP2("git", ["worktree", "list", "--porcelain"], { timeout: GIT_TIMEOUT_MS });
@@ -17051,18 +17285,18 @@ function resolveGitdirForWorktreeFile(worktreePath, content) {
17051
17285
  const match = /^gitdir:\s*(.+)\s*$/im.exec(content);
17052
17286
  if (!match?.[1]) return void 0;
17053
17287
  const raw = match[1].trim();
17054
- 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);
17055
17289
  }
17056
17290
  function metadataOwnsMissingWorktreeDir(worktreePath, worktreeGitRoot) {
17057
17291
  if (!worktreeGitRoot) return false;
17058
17292
  try {
17059
- const entries = (0, import_node_fs17.readdirSync)(worktreeGitRoot, { withFileTypes: true });
17293
+ const entries = (0, import_node_fs18.readdirSync)(worktreeGitRoot, { withFileTypes: true });
17060
17294
  for (const ent of entries) {
17061
17295
  if (!ent.isDirectory()) continue;
17062
17296
  try {
17063
- const gitdirPath = (0, import_node_fs16.readFileSync)((0, import_node_path17.join)(worktreeGitRoot, ent.name, "gitdir"), "utf8").trim();
17064
- const resolvedGitdir = (0, import_node_path17.isAbsolute)(gitdirPath) ? gitdirPath : (0, import_node_path17.resolve)(worktreeGitRoot, ent.name, gitdirPath);
17065
- 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;
17066
17300
  } catch {
17067
17301
  }
17068
17302
  }
@@ -17072,7 +17306,7 @@ function metadataOwnsMissingWorktreeDir(worktreePath, worktreeGitRoot) {
17072
17306
  }
17073
17307
  function pathExistsKnown(path2) {
17074
17308
  try {
17075
- (0, import_node_fs17.statSync)(path2);
17309
+ (0, import_node_fs18.statSync)(path2);
17076
17310
  return true;
17077
17311
  } catch (e) {
17078
17312
  const code = typeof e === "object" && e && "code" in e ? String(e.code ?? "") : "";
@@ -17081,10 +17315,10 @@ function pathExistsKnown(path2) {
17081
17315
  }
17082
17316
  }
17083
17317
  function inspectSiblingWorktreeDir(path2, worktreeGitRoot) {
17084
- const gitPath = (0, import_node_path17.join)(path2, ".git");
17318
+ const gitPath = (0, import_node_path18.join)(path2, ".git");
17085
17319
  let st;
17086
17320
  try {
17087
- st = (0, import_node_fs17.lstatSync)(gitPath);
17321
+ st = (0, import_node_fs18.lstatSync)(gitPath);
17088
17322
  } catch (e) {
17089
17323
  const code = typeof e === "object" && e && "code" in e ? String(e.code ?? "") : "";
17090
17324
  if (code === "ENOENT" || code === "ENOTDIR") {
@@ -17101,7 +17335,7 @@ function inspectSiblingWorktreeDir(path2, worktreeGitRoot) {
17101
17335
  if (st.isDirectory()) return { path: path2, gitType: "dir" };
17102
17336
  if (!st.isFile()) return { path: path2, gitType: "other" };
17103
17337
  try {
17104
- const gitFileContent = (0, import_node_fs16.readFileSync)(gitPath, "utf8");
17338
+ const gitFileContent = (0, import_node_fs17.readFileSync)(gitPath, "utf8");
17105
17339
  const gitdir = resolveGitdirForWorktreeFile(path2, gitFileContent);
17106
17340
  const gitDirExists = gitdir ? pathExistsKnown(gitdir) : false;
17107
17341
  return {
@@ -17118,7 +17352,7 @@ function inspectSiblingWorktreeDir(path2, worktreeGitRoot) {
17118
17352
  }
17119
17353
  function inspectDeadWorktreeDirContent(path2) {
17120
17354
  try {
17121
- return { entries: (0, import_node_fs17.readdirSync)(path2) };
17355
+ return { entries: (0, import_node_fs18.readdirSync)(path2) };
17122
17356
  } catch (e) {
17123
17357
  const code = typeof e === "object" && e && "code" in e ? String(e.code ?? "") : "";
17124
17358
  return { error: code ? `unable to inspect directory contents (${code})` : "unable to inspect directory contents" };
@@ -17137,8 +17371,8 @@ async function siblingWorktreeDirs() {
17137
17371
  const worktreeGitRoot = await currentRepoWorktreeGitRoot(repoRoot);
17138
17372
  const siblingRoot = siblingMmiWorktreesRoot(repoRoot);
17139
17373
  try {
17140
- const entries = (0, import_node_fs17.readdirSync)(siblingRoot, { withFileTypes: true });
17141
- 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));
17142
17376
  } catch {
17143
17377
  return [];
17144
17378
  }
@@ -17188,7 +17422,7 @@ async function fetchHubVersionInfo(baseUrl) {
17188
17422
  }
17189
17423
  function readRepoVersion() {
17190
17424
  try {
17191
- 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;
17192
17426
  } catch {
17193
17427
  return void 0;
17194
17428
  }
@@ -17340,11 +17574,11 @@ async function applyPluginHeal(token, surface, log, opts) {
17340
17574
  }
17341
17575
  var installedPluginsPath = (surface = detectSurface(process.env)) => {
17342
17576
  const homeDir = surface === "codex" ? ".codex" : ".claude";
17343
- 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");
17344
17578
  };
17345
17579
  function readInstalledPlugins(surface = detectSurface(process.env)) {
17346
17580
  try {
17347
- return JSON.parse((0, import_node_fs18.readFileSync)(installedPluginsPath(surface), "utf8"));
17581
+ return JSON.parse((0, import_node_fs19.readFileSync)(installedPluginsPath(surface), "utf8"));
17348
17582
  } catch {
17349
17583
  return null;
17350
17584
  }
@@ -17352,13 +17586,13 @@ function readInstalledPlugins(surface = detectSurface(process.env)) {
17352
17586
  function marketplaceCloneCandidates(surface, home) {
17353
17587
  if (surface === "codex") {
17354
17588
  return [
17355
- (0, import_node_path18.join)(home, ".codex", ".tmp", "marketplaces", "mutmutco"),
17356
- (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")
17357
17591
  ];
17358
17592
  }
17359
- return [(0, import_node_path18.join)(home, ".claude", "plugins", "marketplaces", "mutmutco")];
17593
+ return [(0, import_node_path19.join)(home, ".claude", "plugins", "marketplaces", "mutmutco")];
17360
17594
  }
17361
- function marketplaceClonePresent(surface, home, exists = import_node_fs18.existsSync) {
17595
+ function marketplaceClonePresent(surface, home, exists = import_node_fs19.existsSync) {
17362
17596
  return marketplaceCloneCandidates(surface, home).some(exists);
17363
17597
  }
17364
17598
  function snapshotPluginGuardInput(surface = detectSurface(process.env), isOrgRepo = false) {
@@ -17368,14 +17602,14 @@ function snapshotPluginGuardInput(surface = detectSurface(process.env), isOrgRep
17368
17602
  isOrgRepo,
17369
17603
  installRecordPresent: hasUserInstallRecord(installed, MMI_PLUGIN_ID) || hasProjectInstallRecord(installed, MMI_PLUGIN_ID, process.cwd()),
17370
17604
  marketplaceClonePresent: marketplaceClonePresent(surface, (0, import_node_os7.homedir)()),
17371
- 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"))
17372
17606
  };
17373
17607
  }
17374
17608
  function installedPluginSources() {
17375
17609
  return ["claude", "codex"].map((surface) => {
17376
- 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");
17377
17611
  try {
17378
- 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 };
17379
17613
  } catch {
17380
17614
  return { surface, installed: null, recordPath };
17381
17615
  }
@@ -17383,7 +17617,7 @@ function installedPluginSources() {
17383
17617
  }
17384
17618
  function readClaudeSettings() {
17385
17619
  try {
17386
- 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"));
17387
17621
  } catch {
17388
17622
  return null;
17389
17623
  }
@@ -17405,7 +17639,7 @@ function writeProjectInstallRecord(record) {
17405
17639
  const list = file.plugins[MMI_PLUGIN_ID] ?? [];
17406
17640
  list.push(record);
17407
17641
  file.plugins[MMI_PLUGIN_ID] = list;
17408
- (0, import_node_fs18.writeFileSync)(installedPluginsPath(), `${JSON.stringify(file, null, 2)}
17642
+ (0, import_node_fs19.writeFileSync)(installedPluginsPath(), `${JSON.stringify(file, null, 2)}
17409
17643
  `, "utf8");
17410
17644
  return true;
17411
17645
  } catch {
@@ -17418,9 +17652,9 @@ function backupAndWriteInstalledPlugins(records, pluginId) {
17418
17652
  if (!file) return false;
17419
17653
  if (!file.plugins) file.plugins = {};
17420
17654
  const path2 = installedPluginsPath();
17421
- (0, import_node_fs18.copyFileSync)(path2, `${path2}.bak`);
17655
+ (0, import_node_fs19.copyFileSync)(path2, `${path2}.bak`);
17422
17656
  file.plugins[pluginId] = records;
17423
- (0, import_node_fs18.writeFileSync)(path2, `${JSON.stringify(file, null, 2)}
17657
+ (0, import_node_fs19.writeFileSync)(path2, `${JSON.stringify(file, null, 2)}
17424
17658
  `, "utf8");
17425
17659
  return true;
17426
17660
  } catch {
@@ -17428,22 +17662,22 @@ function backupAndWriteInstalledPlugins(records, pluginId) {
17428
17662
  }
17429
17663
  }
17430
17664
  function opencodeConfigDir() {
17431
- 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");
17432
17666
  }
17433
17667
  function opencodeConfigPath() {
17434
- return (0, import_node_path18.join)(opencodeConfigDir(), "opencode.jsonc");
17668
+ return (0, import_node_path19.join)(opencodeConfigDir(), "opencode.jsonc");
17435
17669
  }
17436
17670
  function opencodeCommandsDir() {
17437
- return (0, import_node_path18.join)(opencodeConfigDir(), "commands");
17671
+ return (0, import_node_path19.join)(opencodeConfigDir(), "commands");
17438
17672
  }
17439
17673
  function opencodeSkillsPath() {
17440
- 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");
17441
17675
  }
17442
17676
  function opencodeConfigSnapshot() {
17443
17677
  const path2 = opencodeConfigPath();
17444
- 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 };
17445
17679
  try {
17446
- const raw = (0, import_node_fs18.readFileSync)(path2, "utf8");
17680
+ const raw = (0, import_node_fs19.readFileSync)(path2, "utf8");
17447
17681
  const parsed = JSON.parse(stripJsonc(raw));
17448
17682
  const hasPluginField = Object.prototype.hasOwnProperty.call(parsed, "plugin");
17449
17683
  const skillsPaths = Array.isArray(parsed.skills?.paths) ? parsed.skills.paths.filter((p) => typeof p === "string") : void 0;
@@ -17466,9 +17700,9 @@ function writeOpencodeConfigPlugin(snapshot) {
17466
17700
  const plan = planOpencodeConfigWrite(snapshot.hasConfig ? snapshot.raw : void 0);
17467
17701
  if (plan.action === "already") return true;
17468
17702
  if (!plan.text || plan.action === "unsafe") return false;
17469
- (0, import_node_fs18.mkdirSync)((0, import_node_path18.dirname)(path2), { recursive: true });
17470
- if (snapshot.hasConfig) (0, import_node_fs18.copyFileSync)(path2, `${path2}.bak`);
17471
- (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");
17472
17706
  return true;
17473
17707
  } catch {
17474
17708
  return false;
@@ -17483,9 +17717,9 @@ function writeOpencodeSkillsPath(snapshot, skillsPath) {
17483
17717
  const normalized = skillsPath.replace(/\\/g, "/");
17484
17718
  if (!paths.some((p) => p.replace(/\\/g, "/") === normalized)) paths.push(skillsPath.replace(/\\/g, "/"));
17485
17719
  parsed.skills = { ...skills, paths };
17486
- (0, import_node_fs18.mkdirSync)((0, import_node_path18.dirname)(snapshot.path), { recursive: true });
17487
- if (snapshot.hasConfig && (0, import_node_fs18.existsSync)(snapshot.path)) (0, import_node_fs18.copyFileSync)(snapshot.path, `${snapshot.path}.bak`);
17488
- (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)}
17489
17723
  `, "utf8");
17490
17724
  return true;
17491
17725
  } catch {
@@ -17494,7 +17728,7 @@ function writeOpencodeSkillsPath(snapshot, skillsPath) {
17494
17728
  }
17495
17729
  function opencodeExistingCommands() {
17496
17730
  try {
17497
- 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());
17498
17732
  } catch {
17499
17733
  return [];
17500
17734
  }
@@ -17502,9 +17736,9 @@ function opencodeExistingCommands() {
17502
17736
  function writeOpencodeCommandFiles() {
17503
17737
  try {
17504
17738
  const dir = opencodeCommandsDir();
17505
- (0, import_node_fs18.mkdirSync)(dir, { recursive: true });
17739
+ (0, import_node_fs19.mkdirSync)(dir, { recursive: true });
17506
17740
  for (const command of OPENCODE_WORKFLOW_COMMANDS) {
17507
- (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");
17508
17742
  }
17509
17743
  return true;
17510
17744
  } catch {
@@ -17513,12 +17747,12 @@ function writeOpencodeCommandFiles() {
17513
17747
  }
17514
17748
  function readOpencodeAdapterDiskVersion() {
17515
17749
  const candidates = [
17516
- (0, import_node_path18.join)(opencodeConfigDir(), "node_modules", "@mutmutco", "opencode-mmi", "package.json"),
17517
- (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")
17518
17752
  ];
17519
17753
  for (const path2 of candidates) {
17520
17754
  try {
17521
- const parsed = JSON.parse((0, import_node_fs18.readFileSync)(path2, "utf8"));
17755
+ const parsed = JSON.parse((0, import_node_fs19.readFileSync)(path2, "utf8"));
17522
17756
  if (typeof parsed.version === "string" && parsed.version.trim()) return parsed.version.trim();
17523
17757
  } catch {
17524
17758
  continue;
@@ -17534,7 +17768,7 @@ async function forceInstallOpencodeMmiPlugins(snapshot, log) {
17534
17768
  try {
17535
17769
  const specs = opencodeMmiPluginSpecs(snapshot);
17536
17770
  log(` \u21BB force-refreshing OpenCode MMI npm plugin(s): ${specs.join(", ")}\u2026`);
17537
- (0, import_node_fs18.mkdirSync)(opencodeConfigDir(), { recursive: true });
17771
+ (0, import_node_fs19.mkdirSync)(opencodeConfigDir(), { recursive: true });
17538
17772
  await runHostBin("npm", ["install", "--prefix", opencodeConfigDir(), "--force", ...specs], { timeout: NPM_UPDATE_TIMEOUT_MS });
17539
17773
  return true;
17540
17774
  } catch {
@@ -17542,12 +17776,12 @@ async function forceInstallOpencodeMmiPlugins(snapshot, log) {
17542
17776
  }
17543
17777
  }
17544
17778
  function opencodePackagesRoot() {
17545
- 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");
17546
17780
  }
17547
17781
  function opencodePluginCacheDirs() {
17548
17782
  const root = opencodePackagesRoot();
17549
17783
  try {
17550
- 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));
17551
17785
  } catch {
17552
17786
  return [];
17553
17787
  }
@@ -17555,7 +17789,7 @@ function opencodePluginCacheDirs() {
17555
17789
  function readOpencodeCacheDirVersion(cacheDir) {
17556
17790
  try {
17557
17791
  const parsed = JSON.parse(
17558
- (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")
17559
17793
  );
17560
17794
  return typeof parsed.version === "string" && parsed.version.trim() ? parsed.version.trim() : void 0;
17561
17795
  } catch {
@@ -17578,9 +17812,9 @@ function quarantineStaleOpencodePluginCaches(releasedVersion, log) {
17578
17812
  });
17579
17813
  if (!plan.quarantine) continue;
17580
17814
  try {
17581
- const quarantineRoot = (0, import_node_path18.join)(opencodePackagesRoot(), ".mmi-quarantine", stamp);
17582
- (0, import_node_fs18.mkdirSync)(quarantineRoot, { recursive: true });
17583
- (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)));
17584
17818
  log(` \u21BB quarantined stale OpenCode plugin cache ${plan.cacheVersion} (< ${plan.releasedVersion}) \u2014 OpenCode reinstalls the current adapter on next start`);
17585
17819
  moved = true;
17586
17820
  } catch {
@@ -17606,30 +17840,30 @@ function opencodePluginVersionsForReport() {
17606
17840
  }
17607
17841
  function opencodeDesktopLogsRoot() {
17608
17842
  if (process.platform === "win32") {
17609
- const base = process.env.APPDATA || (0, import_node_path18.join)((0, import_node_os7.homedir)(), "AppData", "Roaming");
17610
- 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");
17611
17845
  }
17612
17846
  if (process.platform === "darwin") {
17613
- 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");
17614
17848
  }
17615
- 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");
17616
17850
  }
17617
17851
  function opencodeDesktopBootstrapSnapshot() {
17618
17852
  const root = opencodeDesktopLogsRoot();
17619
17853
  try {
17620
- 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);
17621
17855
  const newest = sessionDirs[0];
17622
17856
  if (!newest) return [];
17623
- const logPath = (0, import_node_path18.join)(newest, "renderer.log");
17624
- const text = (0, import_node_fs18.readFileSync)(logPath, "utf8");
17625
- 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 }));
17626
17860
  } catch {
17627
17861
  return [];
17628
17862
  }
17629
17863
  }
17630
17864
  function opencodeLegacyConfigSnapshot() {
17631
- const legacyPath = (0, import_node_path18.join)((0, import_node_os7.homedir)(), ".opencode", "opencode.json");
17632
- 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 {};
17633
17867
  const content = readTextFile(legacyPath);
17634
17868
  if (content == null) return {};
17635
17869
  const plugins = parseOpencodeLegacyConfigPlugins(content);
@@ -17641,24 +17875,24 @@ function opencodeLegacyConfigSnapshot() {
17641
17875
  function quarantineOpencodeLegacyConfig(legacyPath) {
17642
17876
  try {
17643
17877
  const backupPath = `${legacyPath}.bak`;
17644
- if ((0, import_node_fs18.existsSync)(backupPath)) return false;
17645
- (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);
17646
17880
  return true;
17647
17881
  } catch {
17648
17882
  return false;
17649
17883
  }
17650
17884
  }
17651
17885
  function cursorPluginCacheRoot() {
17652
- 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");
17653
17887
  }
17654
17888
  function cursorLogsRoot() {
17655
- if (process.platform === "win32") return (0, import_node_path18.join)((0, import_node_os7.homedir)(), "AppData", "Roaming", "Cursor", "logs");
17656
- if (process.platform === "darwin") return (0, import_node_path18.join)((0, import_node_os7.homedir)(), "Library", "Application Support", "Cursor", "logs");
17657
- 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");
17658
17892
  }
17659
17893
  function safeReaddirNames(dir) {
17660
17894
  try {
17661
- 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);
17662
17896
  } catch {
17663
17897
  return [];
17664
17898
  }
@@ -17667,15 +17901,15 @@ function readCursorPluginLogText() {
17667
17901
  const root = cursorLogsRoot();
17668
17902
  const sessions = safeReaddirNames(root).sort().reverse();
17669
17903
  for (const session of sessions.slice(0, 5)) {
17670
- const sessionDir = (0, import_node_path18.join)(root, session);
17904
+ const sessionDir = (0, import_node_path19.join)(root, session);
17671
17905
  const texts = [];
17672
17906
  for (const win of safeReaddirNames(sessionDir).filter((n) => n.startsWith("window"))) {
17673
- 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");
17674
17908
  for (const file of safeReaddirNames(dir)) {
17675
17909
  if (!/^Cursor Plugins.*\.log$/i.test(file)) continue;
17676
- const path2 = (0, import_node_path18.join)(dir, file);
17910
+ const path2 = (0, import_node_path19.join)(dir, file);
17677
17911
  try {
17678
- 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 });
17679
17913
  } catch {
17680
17914
  }
17681
17915
  }
@@ -17689,32 +17923,32 @@ function readCursorPluginLogText() {
17689
17923
  function cursorPluginCachePinSnapshots() {
17690
17924
  const root = cursorPluginCacheRoot();
17691
17925
  try {
17692
- return (0, import_node_fs18.readdirSync)(root, { withFileTypes: true }).filter((entry) => entry.isDirectory() && !entry.name.startsWith(".")).map((entry) => {
17693
- const path2 = (0, import_node_path18.join)(root, entry.name);
17694
- const pluginJson = (0, import_node_path18.join)(path2, ".cursor-plugin", "plugin.json");
17695
- const hooksJson = (0, import_node_path18.join)(path2, "hooks", "hooks.json");
17696
- const cliBundle = (0, import_node_path18.join)(path2, "cli", "dist", "index.cjs");
17697
- 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");
17698
17932
  let version;
17699
17933
  try {
17700
- const raw = JSON.parse((0, import_node_fs18.readFileSync)(pluginJson, "utf8"));
17934
+ const raw = JSON.parse((0, import_node_fs19.readFileSync)(pluginJson, "utf8"));
17701
17935
  version = typeof raw.version === "string" ? raw.version : void 0;
17702
17936
  } catch {
17703
17937
  version = void 0;
17704
17938
  }
17705
17939
  let isEmpty = true;
17706
17940
  try {
17707
- isEmpty = (0, import_node_fs18.readdirSync)(path2).length === 0;
17941
+ isEmpty = (0, import_node_fs19.readdirSync)(path2).length === 0;
17708
17942
  } catch {
17709
17943
  isEmpty = true;
17710
17944
  }
17711
17945
  return {
17712
17946
  name: entry.name,
17713
17947
  path: path2,
17714
- hasPluginJson: (0, import_node_fs18.existsSync)(pluginJson),
17715
- hasHooksJson: (0, import_node_fs18.existsSync)(hooksJson),
17716
- hasCliBundle: (0, import_node_fs18.existsSync)(cliBundle),
17717
- 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),
17718
17952
  isEmpty,
17719
17953
  version
17720
17954
  };
@@ -17724,19 +17958,19 @@ function cursorPluginCachePinSnapshots() {
17724
17958
  }
17725
17959
  }
17726
17960
  function hubCheckoutForCursorSeed() {
17727
- const manifest = (0, import_node_path18.join)(process.cwd(), "plugins", "mmi", ".cursor-plugin", "plugin.json");
17728
- 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;
17729
17963
  }
17730
17964
  function mmiPluginCacheRootSnapshots() {
17731
17965
  const roots = [
17732
- { surface: "claude", root: (0, import_node_path18.join)((0, import_node_os7.homedir)(), ".claude", "plugins", "cache", "mutmutco", "mmi") },
17733
- { 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") }
17734
17968
  ];
17735
17969
  return roots.flatMap(({ surface, root }) => {
17736
17970
  try {
17737
- 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) => ({
17738
17972
  name: entry.name,
17739
- path: (0, import_node_path18.join)(root, entry.name),
17973
+ path: (0, import_node_path19.join)(root, entry.name),
17740
17974
  isDirectory: entry.isDirectory()
17741
17975
  }));
17742
17976
  return [{ surface, root, entries }];
@@ -17747,7 +17981,7 @@ function mmiPluginCacheRootSnapshots() {
17747
17981
  }
17748
17982
  function hasNestedMmiChild(versionDir) {
17749
17983
  try {
17750
- 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();
17751
17985
  } catch {
17752
17986
  return false;
17753
17987
  }
@@ -17758,10 +17992,10 @@ function nestedPluginTreeSnapshot() {
17758
17992
  );
17759
17993
  }
17760
17994
  function uniqueQuarantineTarget(path2) {
17761
- if (!(0, import_node_fs18.existsSync)(path2)) return path2;
17995
+ if (!(0, import_node_fs19.existsSync)(path2)) return path2;
17762
17996
  for (let i = 1; i < 100; i += 1) {
17763
17997
  const candidate = `${path2}-${i}`;
17764
- if (!(0, import_node_fs18.existsSync)(candidate)) return candidate;
17998
+ if (!(0, import_node_fs19.existsSync)(candidate)) return candidate;
17765
17999
  }
17766
18000
  return `${path2}-${Date.now()}`;
17767
18001
  }
@@ -17770,10 +18004,10 @@ function quarantinePluginCacheDirs(plan) {
17770
18004
  const failed = [];
17771
18005
  for (const move of plan) {
17772
18006
  try {
17773
- if (!(0, import_node_fs18.existsSync)(move.from)) continue;
18007
+ if (!(0, import_node_fs19.existsSync)(move.from)) continue;
17774
18008
  const target = uniqueQuarantineTarget(move.to);
17775
- (0, import_node_fs18.mkdirSync)((0, import_node_path18.dirname)(target), { recursive: true });
17776
- (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);
17777
18011
  moved += 1;
17778
18012
  } catch {
17779
18013
  failed.push(move);
@@ -17792,23 +18026,23 @@ async function robocopyMirrorEmpty(emptyDir, target) {
17792
18026
  }
17793
18027
  async function clearNestedPluginTreeDir(targetPath) {
17794
18028
  try {
17795
- if (!(0, import_node_fs18.existsSync)(targetPath)) return true;
18029
+ if (!(0, import_node_fs19.existsSync)(targetPath)) return true;
17796
18030
  if (isWin) {
17797
- const emptyDir = (0, import_node_path18.join)((0, import_node_os7.tmpdir)(), `mmi-empty-${Date.now()}`);
17798
- (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 });
17799
18033
  try {
17800
18034
  await robocopyMirrorEmpty(emptyDir, targetPath);
17801
- (0, import_node_fs18.rmSync)(targetPath, { recursive: true, force: true });
18035
+ (0, import_node_fs19.rmSync)(targetPath, { recursive: true, force: true });
17802
18036
  } finally {
17803
18037
  try {
17804
- (0, import_node_fs18.rmSync)(emptyDir, { recursive: true, force: true });
18038
+ (0, import_node_fs19.rmSync)(emptyDir, { recursive: true, force: true });
17805
18039
  } catch {
17806
18040
  }
17807
18041
  }
17808
- return !(0, import_node_fs18.existsSync)(targetPath);
18042
+ return !(0, import_node_fs19.existsSync)(targetPath);
17809
18043
  }
17810
- (0, import_node_fs18.rmSync)(targetPath, { recursive: true, force: true });
17811
- 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);
17812
18046
  } catch {
17813
18047
  return false;
17814
18048
  }
@@ -17821,18 +18055,18 @@ async function applyNestedPluginTreeCleanup(paths, log) {
17821
18055
  }
17822
18056
  return true;
17823
18057
  }
17824
- var gitignorePath = () => (0, import_node_path18.join)(process.cwd(), ".gitignore");
18058
+ var gitignorePath = () => (0, import_node_path19.join)(process.cwd(), ".gitignore");
17825
18059
  function readTextFile(path2) {
17826
18060
  try {
17827
- if (!(0, import_node_fs18.existsSync)(path2)) return null;
17828
- 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");
17829
18063
  } catch {
17830
18064
  return null;
17831
18065
  }
17832
18066
  }
17833
18067
  function mcpDirExists(path2) {
17834
18068
  try {
17835
- return (0, import_node_fs18.existsSync)(path2);
18069
+ return (0, import_node_fs19.existsSync)(path2);
17836
18070
  } catch {
17837
18071
  return false;
17838
18072
  }
@@ -17840,60 +18074,60 @@ function mcpDirExists(path2) {
17840
18074
  function mcpConfigTargets() {
17841
18075
  const cwd = process.cwd();
17842
18076
  const home = (0, import_node_os7.homedir)();
17843
- const cursorProjectDir = (0, import_node_path18.join)(cwd, ".cursor");
17844
- const cursorUserDir = (0, import_node_path18.join)(home, ".cursor");
17845
- 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");
17846
18080
  return [
17847
18081
  // Claude Code project MCP — reconciled if present, never conjured (org seeds .cursor/mcp.json, not this).
17848
18082
  {
17849
18083
  host: "claude-code",
17850
18084
  label: "Claude Code project MCP",
17851
- path: (0, import_node_path18.join)(cwd, ".mcp.json"),
18085
+ path: (0, import_node_path19.join)(cwd, ".mcp.json"),
17852
18086
  format: "json",
17853
18087
  present: true,
17854
18088
  // parent is the repo root (cwd); the caller gates the whole reconcile on isOrgRepo
17855
18089
  isRepoFile: true,
17856
18090
  createWhenFileAbsent: false,
17857
- content: readTextFile((0, import_node_path18.join)(cwd, ".mcp.json"))
18091
+ content: readTextFile((0, import_node_path19.join)(cwd, ".mcp.json"))
17858
18092
  },
17859
18093
  // Cursor project MCP — the bootstrap-seeded surface; restored if its .cursor dir exists but the file is gone.
17860
18094
  {
17861
18095
  host: "cursor-project",
17862
18096
  label: "Cursor project MCP",
17863
- path: (0, import_node_path18.join)(cursorProjectDir, "mcp.json"),
18097
+ path: (0, import_node_path19.join)(cursorProjectDir, "mcp.json"),
17864
18098
  format: "json",
17865
18099
  present: mcpDirExists(cursorProjectDir),
17866
18100
  isRepoFile: true,
17867
18101
  createWhenFileAbsent: true,
17868
- content: readTextFile((0, import_node_path18.join)(cursorProjectDir, "mcp.json"))
18102
+ content: readTextFile((0, import_node_path19.join)(cursorProjectDir, "mcp.json"))
17869
18103
  },
17870
18104
  // Cursor user MCP — global; written only when Cursor is installed (~/.cursor exists).
17871
18105
  {
17872
18106
  host: "cursor-user",
17873
18107
  label: "Cursor user MCP",
17874
- path: (0, import_node_path18.join)(cursorUserDir, "mcp.json"),
18108
+ path: (0, import_node_path19.join)(cursorUserDir, "mcp.json"),
17875
18109
  format: "json",
17876
18110
  present: mcpDirExists(cursorUserDir),
17877
18111
  isRepoFile: false,
17878
18112
  createWhenFileAbsent: true,
17879
- content: readTextFile((0, import_node_path18.join)(cursorUserDir, "mcp.json"))
18113
+ content: readTextFile((0, import_node_path19.join)(cursorUserDir, "mcp.json"))
17880
18114
  },
17881
18115
  // Codex user config (TOML) — global; written only when Codex is installed (~/.codex exists).
17882
18116
  {
17883
18117
  host: "codex",
17884
18118
  label: "Codex user config",
17885
- path: (0, import_node_path18.join)(codexDir, "config.toml"),
18119
+ path: (0, import_node_path19.join)(codexDir, "config.toml"),
17886
18120
  format: "toml",
17887
18121
  present: mcpDirExists(codexDir),
17888
18122
  isRepoFile: false,
17889
18123
  createWhenFileAbsent: true,
17890
- content: readTextFile((0, import_node_path18.join)(codexDir, "config.toml"))
18124
+ content: readTextFile((0, import_node_path19.join)(codexDir, "config.toml"))
17891
18125
  }
17892
18126
  ];
17893
18127
  }
17894
18128
  function writeMcpConfigFile(path2, content) {
17895
18129
  try {
17896
- (0, import_node_fs18.writeFileSync)(path2, content, "utf8");
18130
+ (0, import_node_fs19.writeFileSync)(path2, content, "utf8");
17897
18131
  return true;
17898
18132
  } catch {
17899
18133
  return false;
@@ -17973,7 +18207,7 @@ function strayBrowserArtifactPaths() {
17973
18207
  const cwd = process.cwd();
17974
18208
  return STRAY_BROWSER_ARTIFACT_DIRS.filter((rel) => {
17975
18209
  try {
17976
- 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));
17977
18211
  } catch {
17978
18212
  return false;
17979
18213
  }
@@ -17981,14 +18215,14 @@ function strayBrowserArtifactPaths() {
17981
18215
  }
17982
18216
  function readGitignore() {
17983
18217
  try {
17984
- return (0, import_node_fs18.readFileSync)(gitignorePath(), "utf8");
18218
+ return (0, import_node_fs19.readFileSync)(gitignorePath(), "utf8");
17985
18219
  } catch {
17986
18220
  return null;
17987
18221
  }
17988
18222
  }
17989
18223
  function writeGitignore(content) {
17990
18224
  try {
17991
- (0, import_node_fs18.writeFileSync)(gitignorePath(), content, "utf8");
18225
+ (0, import_node_fs19.writeFileSync)(gitignorePath(), content, "utf8");
17992
18226
  return true;
17993
18227
  } catch {
17994
18228
  return false;
@@ -18030,7 +18264,7 @@ async function runDoctor(opts, io = consoleIo, readOrigin) {
18030
18264
  const semverPrefix = /^\d+\.\d+\.\d+/;
18031
18265
  const isBehind = (installed2, released) => Boolean(installed2 && released && semverPrefix.test(installed2) && semverPrefix.test(released) && compareVersions(installed2, released) < 0);
18032
18266
  const opencodeAdapterStale = isBehind(opencodeInstalledVersionForDoctor(), releasedVersion);
18033
- 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));
18034
18268
  const healPlan = doctorHealPlan({
18035
18269
  isOrgRepo,
18036
18270
  surface,
@@ -18065,7 +18299,7 @@ async function runDoctor(opts, io = consoleIo, readOrigin) {
18065
18299
  let onPath = pathProbe;
18066
18300
  if (!onPath) {
18067
18301
  const root = process.env.CLAUDE_PLUGIN_ROOT;
18068
- 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;
18069
18303
  }
18070
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" });
18071
18305
  const reloadHint = reloadAction(surface);
@@ -18212,7 +18446,7 @@ async function runDoctor(opts, io = consoleIo, readOrigin) {
18212
18446
  checks.push(gitignoreCheck);
18213
18447
  checks.push(buildRepoLocalWorktreeCheck({
18214
18448
  isOrgRepo,
18215
- 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"))
18216
18450
  }));
18217
18451
  let driftCheck = buildPluginConfigDriftCheck({ isOrgRepo, installed, surface });
18218
18452
  if (!driftCheck.ok && driftCheck.recordsToWrite && repairLocal) {
@@ -18501,7 +18735,7 @@ async function runDoctor(opts, io = consoleIo, readOrigin) {
18501
18735
  }
18502
18736
  checks.push(nestedPluginTreeCheck);
18503
18737
  const cursorCacheRoot = cursorPluginCacheRoot();
18504
- const cursorCacheRootExists = (0, import_node_fs18.existsSync)(cursorCacheRoot);
18738
+ const cursorCacheRootExists = (0, import_node_fs19.existsSync)(cursorCacheRoot);
18505
18739
  let cursorPins = cursorPluginCachePinSnapshots() ?? [];
18506
18740
  checks.push(
18507
18741
  buildCursorPluginCacheCleanupCheck({
@@ -18529,7 +18763,7 @@ async function runDoctor(opts, io = consoleIo, readOrigin) {
18529
18763
  cacheRootExists: cursorCacheRootExists,
18530
18764
  hubCheckout: hubCheckoutForCursorSeed(),
18531
18765
  execFileP: execFileP2,
18532
- 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)),
18533
18767
  log: (m) => io.err(m)
18534
18768
  });
18535
18769
  if (seeded) {
@@ -18724,23 +18958,23 @@ function mergeGuardHook(settings) {
18724
18958
  next.hooks = hooks;
18725
18959
  return next;
18726
18960
  }
18727
- 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");
18728
18962
  function ensureUserScopeGuardHook(opts = {}) {
18729
18963
  const path2 = opts.settingsPath ?? userScopeSettingsPath();
18730
18964
  try {
18731
18965
  let current = null;
18732
- if ((0, import_node_fs18.existsSync)(path2)) {
18966
+ if ((0, import_node_fs19.existsSync)(path2)) {
18733
18967
  try {
18734
- current = JSON.parse((0, import_node_fs18.readFileSync)(path2, "utf8"));
18968
+ current = JSON.parse((0, import_node_fs19.readFileSync)(path2, "utf8"));
18735
18969
  } catch {
18736
18970
  return "failed";
18737
18971
  }
18738
18972
  }
18739
18973
  if (settingsHasGuardHook(current)) return "already";
18740
18974
  const merged = mergeGuardHook(current);
18741
- (0, import_node_fs18.mkdirSync)((0, import_node_path18.dirname)(path2), { recursive: true });
18742
- if ((0, import_node_fs18.existsSync)(path2)) (0, import_node_fs18.copyFileSync)(path2, `${path2}.bak`);
18743
- (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)}
18744
18978
  `, "utf8");
18745
18979
  return "written";
18746
18980
  } catch {
@@ -18877,7 +19111,7 @@ async function applyGcPlan(plan, remote) {
18877
19111
  cleanupBranch: (branch, expectedHeadOid) => cleanupPrMergeLocalBranch(branch.branch, {
18878
19112
  beforeWorktrees,
18879
19113
  startingPath: branch.worktreePath,
18880
- pathExists: (p) => (0, import_node_fs19.existsSync)(p),
19114
+ pathExists: (p) => (0, import_node_fs20.existsSync)(p),
18881
19115
  execGit: async (args) => (await execFileP2("git", args, { timeout: GIT_TIMEOUT_MS })).stdout,
18882
19116
  teardownWorktreeStage,
18883
19117
  deferredStore,
@@ -18900,7 +19134,7 @@ async function applyGcPlan(plan, remote) {
18900
19134
  for (const wt of plan.worktreeDirs) {
18901
19135
  try {
18902
19136
  const cleanupTarget = resolveSafeSiblingWorktreeCleanupTarget(wt.path, siblingRoot, {
18903
- realpath: (path2) => (0, import_node_fs19.realpathSync)(path2)
19137
+ realpath: (path2) => (0, import_node_fs20.realpathSync)(path2)
18904
19138
  });
18905
19139
  if (!cleanupTarget.ok) {
18906
19140
  result.failed.push(`${wt.path}: ${cleanupTarget.reason}`);
@@ -18943,13 +19177,13 @@ var program2 = new Command();
18943
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)");
18944
19178
  var rules = program2.command("rules").description("org-managed .gitignore delivery");
18945
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) => {
18946
- const path2 = (0, import_node_path19.join)(process.cwd(), ".gitignore");
18947
- 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;
18948
19182
  const plan = planManagedGitignore(current);
18949
19183
  const drift = [...plan.added.map((l) => `+${l}`), ...plan.removed.map((l) => `-${l}`)].join(", ") || "block normalize";
18950
19184
  if (opts.write) {
18951
19185
  if (plan.changed) {
18952
- (0, import_node_fs19.writeFileSync)(path2, plan.content, "utf8");
19186
+ (0, import_node_fs20.writeFileSync)(path2, plan.content, "utf8");
18953
19187
  console.log(`mmi-cli rules gitignore: updated .gitignore (${drift})`);
18954
19188
  } else {
18955
19189
  console.log("mmi-cli rules gitignore: up to date");
@@ -19114,7 +19348,7 @@ function runWorktreeInstall(command, cwd, quiet) {
19114
19348
  async function primaryCheckoutRoot(worktreeRoot) {
19115
19349
  try {
19116
19350
  const out = (await execFileP2("git", ["-C", worktreeRoot, "rev-parse", "--path-format=absolute", "--git-common-dir"], { timeout: GIT_TIMEOUT_MS })).stdout.trim();
19117
- return out ? (0, import_node_path19.dirname)(out) : void 0;
19351
+ return out ? (0, import_node_path20.dirname)(out) : void 0;
19118
19352
  } catch {
19119
19353
  return void 0;
19120
19354
  }
@@ -19129,26 +19363,26 @@ function makeProvisionDeps(worktreeRoot, quiet, log) {
19129
19363
  function acquireWorktreeSetupLock(worktreeRoot) {
19130
19364
  const lockPath = repoRuntimeStatePath(worktreeRoot, "worktree-setup.lock");
19131
19365
  const take = () => {
19132
- const fd = (0, import_node_fs19.openSync)(lockPath, "wx");
19366
+ const fd = (0, import_node_fs20.openSync)(lockPath, "wx");
19133
19367
  try {
19134
- (0, import_node_fs19.writeSync)(fd, String(Date.now()));
19368
+ (0, import_node_fs20.writeSync)(fd, String(Date.now()));
19135
19369
  } finally {
19136
- (0, import_node_fs19.closeSync)(fd);
19370
+ (0, import_node_fs20.closeSync)(fd);
19137
19371
  }
19138
19372
  return () => {
19139
19373
  try {
19140
- (0, import_node_fs19.rmSync)(lockPath, { force: true });
19374
+ (0, import_node_fs20.rmSync)(lockPath, { force: true });
19141
19375
  } catch {
19142
19376
  }
19143
19377
  };
19144
19378
  };
19145
19379
  try {
19146
- (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 });
19147
19381
  return take();
19148
19382
  } catch {
19149
19383
  try {
19150
- if (Date.now() - (0, import_node_fs19.statSync)(lockPath).mtimeMs > WORKTREE_SETUP_LOCK_TTL_MS) {
19151
- (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 });
19152
19386
  return take();
19153
19387
  }
19154
19388
  } catch {
@@ -19638,7 +19872,7 @@ project.command("set [owner/repo]").description("upsert project META (idempotent
19638
19872
  const vars = rawValues("--var");
19639
19873
  if (o.secretsFile) {
19640
19874
  try {
19641
- vars.push(`secrets=${(0, import_node_fs19.readFileSync)(o.secretsFile, "utf8")}`);
19875
+ vars.push(`secrets=${(0, import_node_fs20.readFileSync)(o.secretsFile, "utf8")}`);
19642
19876
  } catch (e) {
19643
19877
  return fail(`project set: cannot read --secrets-file ${o.secretsFile}: ${e.message}`);
19644
19878
  }
@@ -20208,9 +20442,9 @@ pr.command("view <number>").description("read a PR as structured JSON (merged st
20208
20442
  }
20209
20443
  });
20210
20444
  async function listCiWorkflowPaths(cwd = process.cwd()) {
20211
- const wfDir = (0, import_node_path19.join)(cwd, ".github", "workflows");
20212
- if (!(0, import_node_fs19.existsSync)(wfDir)) return [];
20213
- 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}`);
20214
20448
  }
20215
20449
  async function resolveMergeCiPolicyForCheckout(repoOpt) {
20216
20450
  const repo = repoOpt ?? await resolveRepo();
@@ -20229,7 +20463,7 @@ function ciAuditDeps() {
20229
20463
  // Continuous CI delivery (#1550): the gate re-seed renders from the Hub's on-disk seed templates. The
20230
20464
  // reconcile runs IN the Hub checkout, so this is local-file I/O (no network fetch). Path is relative to
20231
20465
  // the repo root (e.g. skills/bootstrap/seeds/gate.template.yml).
20232
- 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
20233
20467
  };
20234
20468
  }
20235
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) => {
@@ -20399,7 +20633,7 @@ async function createDeferredWorktreeStore() {
20399
20633
  },
20400
20634
  write: async (entries) => {
20401
20635
  try {
20402
- 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 });
20403
20637
  await (0, import_promises3.writeFile)(registryPath, serializeDeferredWorktrees(entries), "utf8");
20404
20638
  } catch {
20405
20639
  }
@@ -20413,13 +20647,13 @@ var realWorktreeDirRemover = {
20413
20647
  probe: (p) => {
20414
20648
  let st;
20415
20649
  try {
20416
- st = (0, import_node_fs19.lstatSync)(p);
20650
+ st = (0, import_node_fs20.lstatSync)(p);
20417
20651
  } catch {
20418
20652
  return null;
20419
20653
  }
20420
20654
  if (st.isSymbolicLink()) return "link";
20421
20655
  try {
20422
- (0, import_node_fs19.readlinkSync)(p);
20656
+ (0, import_node_fs20.readlinkSync)(p);
20423
20657
  return "link";
20424
20658
  } catch {
20425
20659
  }
@@ -20427,7 +20661,7 @@ var realWorktreeDirRemover = {
20427
20661
  },
20428
20662
  readdir: (p) => {
20429
20663
  try {
20430
- return (0, import_node_fs19.readdirSync)(p);
20664
+ return (0, import_node_fs20.readdirSync)(p);
20431
20665
  } catch {
20432
20666
  return [];
20433
20667
  }
@@ -20436,9 +20670,9 @@ var realWorktreeDirRemover = {
20436
20670
  // leaving the target); a file symlink with unlink. rmdir first, fall back to unlink.
20437
20671
  detachLink: (p) => {
20438
20672
  try {
20439
- (0, import_node_fs19.rmdirSync)(p);
20673
+ (0, import_node_fs20.rmdirSync)(p);
20440
20674
  } catch {
20441
- (0, import_node_fs19.unlinkSync)(p);
20675
+ (0, import_node_fs20.unlinkSync)(p);
20442
20676
  }
20443
20677
  },
20444
20678
  removeTree: (p) => (0, import_promises3.rm)(p, { recursive: true, force: true, maxRetries: 5, retryDelay: 200 })
@@ -20468,9 +20702,9 @@ async function worktreeHasStageState(worktreePath) {
20468
20702
  }
20469
20703
  }
20470
20704
  function stageStateFileBelongsToWorktree(statePath, worktreePath) {
20471
- if (!(0, import_node_fs19.existsSync)(statePath)) return false;
20705
+ if (!(0, import_node_fs20.existsSync)(statePath)) return false;
20472
20706
  try {
20473
- const state = JSON.parse((0, import_node_fs19.readFileSync)(statePath, "utf8"));
20707
+ const state = JSON.parse((0, import_node_fs20.readFileSync)(statePath, "utf8"));
20474
20708
  const recordedCwd = typeof state.identity?.cwd === "string" ? state.identity.cwd : typeof state.cwd === "string" ? state.cwd : "";
20475
20709
  return Boolean(recordedCwd && isPathUnderDirectory(recordedCwd, worktreePath));
20476
20710
  } catch {
@@ -20542,7 +20776,7 @@ pr.command("merge <number>").description("merge a PR (squash by default) and cle
20542
20776
  localCleanup = await cleanupPrMergeLocalBranch(headRef, {
20543
20777
  beforeWorktrees,
20544
20778
  startingPath,
20545
- pathExists: (p) => (0, import_node_fs19.existsSync)(p),
20779
+ pathExists: (p) => (0, import_node_fs20.existsSync)(p),
20546
20780
  execGit: async (args) => (await execFileP2("git", args, { timeout: GIT_TIMEOUT_MS })).stdout,
20547
20781
  teardownWorktreeStage,
20548
20782
  deferredStore,
@@ -20763,7 +20997,7 @@ function stageScopedRunOpts(o) {
20763
20997
  };
20764
20998
  }
20765
20999
  function printLine(value) {
20766
- (0, import_node_fs19.writeSync)(1, `${value}
21000
+ (0, import_node_fs20.writeSync)(1, `${value}
20767
21001
  `);
20768
21002
  }
20769
21003
  function stageKeepAlive() {
@@ -20777,8 +21011,8 @@ async function resolveStage() {
20777
21011
  const portRange = portRangeMeta && typeof portRangeMeta.start === "number" && typeof portRangeMeta.end === "number" ? [portRangeMeta.start, portRangeMeta.end] : void 0;
20778
21012
  return decideStage({
20779
21013
  registry: { deployModel: project2?.deployModel, portRange, error: read.ok ? void 0 : read.error },
20780
- hasCompose: (0, import_node_fs19.existsSync)((0, import_node_path19.join)(process.cwd(), "docker-compose.yml")),
20781
- 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"))
20782
21016
  });
20783
21017
  }
20784
21018
  async function fetchStageVaultEnvMerge() {
@@ -21231,6 +21465,27 @@ wiki.command("publish <pages-dir>").description("mint a 1h repo-scoped contents:
21231
21465
  );
21232
21466
  if (!ok) process.exitCode = 1;
21233
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
+ });
21234
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) => {
21235
21490
  if (!o.repo) return fail("bootstrap: required option --repo <owner/repo> not specified");
21236
21491
  if (o.apply) return fail("bootstrap: execution is not implemented yet; use the dry-run plan and the existing /bootstrap skill");
@@ -21250,7 +21505,7 @@ bootstrap.command("verify <repo>").description("audit whether an existing repo i
21250
21505
  client: defaultGitHubClient(),
21251
21506
  projectMeta: meta,
21252
21507
  deployModel: typeof meta?.deployModel === "string" ? meta.deployModel : void 0,
21253
- 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,
21254
21509
  // requiredGcpApis is stored as an array by a JSON write, but `project set --var KEY=VALUE` stores a raw
21255
21510
  // comma-string — accept either so the seeded value verifies regardless of how it was written.
21256
21511
  requiredGcpApis: (() => {
@@ -21301,12 +21556,12 @@ bootstrap.command("apply <repo>").description("idempotent seed apply from skills
21301
21556
  return fail(`bootstrap apply: ${e.message}`);
21302
21557
  }
21303
21558
  const manifestPath = "skills/bootstrap/seeds/manifest.json";
21304
- if (!(0, import_node_fs19.existsSync)(manifestPath)) return fail(`bootstrap apply: ${manifestPath} not found; run from the MMI-Hub repo root`);
21305
- 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"));
21306
21561
  const baseBranch = o.class === "content" ? "main" : "development";
21307
21562
  const slug = parsedRepo.slug;
21308
21563
  const gh = async (args) => execFileP2("gh", args, { timeout: 2e4 });
21309
- 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;
21310
21565
  const enc = (p) => p.split("/").map(encodeURIComponent).join("/");
21311
21566
  const rawVars = {};
21312
21567
  for (const value of rawValues("--var")) {
@@ -21603,16 +21858,16 @@ access.command("audit").description("audit collaborator roles + train-branch pus
21603
21858
  const repoClass = o.class;
21604
21859
  targets = [{ repo: o.repo, class: repoClass, releaseTrack: repoClass === "content" ? "trunk" : resolveReleaseTrack(meta, void 0, o.repo) }];
21605
21860
  } else {
21606
- 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;
21607
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>");
21608
- 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;
21609
21864
  targets = loadAccessTargets(projectsJson, fanoutJson);
21610
21865
  }
21611
21866
  const derivedMatrix = registryProjects ? accessMatrixFromProjects(registryProjects) : {};
21612
- 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")) : {};
21613
21868
  const matrix = mergeAccessMatrix(fileMatrix, derivedMatrix);
21614
21869
  const derivedContracts = registryProjects ? dataAccessContractsFromProjects(registryProjects) : { consumers: {} };
21615
- 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: {} };
21616
21871
  const dataAccess = mergeDataAccessContracts(fileContracts, derivedContracts);
21617
21872
  const report = await auditOrgAccess(targets, deps, matrix, dataAccess);
21618
21873
  console.log(o.json ? JSON.stringify(report, null, 2) : renderAccessReport(report));
@@ -21651,6 +21906,15 @@ program2.command("session-start").description("run the SessionStart verbs (whoam
21651
21906
  // never costs banner time and next session's glance renders instantly within budget.
21652
21907
  scheduleRefresh: () => spawnDetachedSelf(["board", "slice-refresh", "--quiet"], { spawn: import_node_child_process12.spawn, execPath: process.execPath, scriptPath: process.argv[1] })
21653
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
+ },
21654
21918
  doctor: (io) => runDoctor({ banner: true }, io)
21655
21919
  });
21656
21920
  await runSessionStart(parallel, sequential, consoleIo);