@mutmutco/cli 3.63.0 → 3.65.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 +527 -229
  2. package/package.json +1 -1
package/dist/main.cjs CHANGED
@@ -38,7 +38,8 @@ __export(index_exports, {
38
38
  isOrgRegisteredRepo: () => isOrgRegisteredRepo,
39
39
  registryClientDeps: () => registryClientDeps,
40
40
  repoSlug: () => repoSlug,
41
- shouldMarkWorktreeActivity: () => shouldMarkWorktreeActivity
41
+ shouldMarkWorktreeActivity: () => shouldMarkWorktreeActivity,
42
+ suggestCommandPath: () => suggestCommandPath
42
43
  });
43
44
  module.exports = __toCommonJS(index_exports);
44
45
 
@@ -3412,8 +3413,8 @@ var program = new Command();
3412
3413
 
3413
3414
  // src/index.ts
3414
3415
  var import_promises8 = require("node:fs/promises");
3415
- var import_node_fs32 = require("node:fs");
3416
- var import_node_child_process13 = require("node:child_process");
3416
+ var import_node_fs33 = require("node:fs");
3417
+ var import_node_child_process14 = require("node:child_process");
3417
3418
 
3418
3419
  // src/cli-shared.ts
3419
3420
  var import_node_child_process3 = require("node:child_process");
@@ -3655,6 +3656,7 @@ function didYouMean(input, candidates) {
3655
3656
  const cand = strip(candidate);
3656
3657
  if (!cand) continue;
3657
3658
  if (cand === target) continue;
3659
+ if (cand === `${target}-file` || target === `${cand}-file`) continue;
3658
3660
  const distance = levenshtein(target, cand);
3659
3661
  const prefix = cand.startsWith(target) || target.startsWith(cand);
3660
3662
  const threshold = Math.max(2, Math.ceil(cand.length * 0.4));
@@ -5163,16 +5165,20 @@ function validateDeadWorktreeDirContent(planned, inspected) {
5163
5165
  return { ok: false, reason: "unknown dead worktree cleanup kind" };
5164
5166
  }
5165
5167
  function validateBranchCleanupHead(planned, currentHeads) {
5168
+ const currentHeadOid = currentHeads.find((h) => h.branch === planned.branch)?.oid;
5169
+ if (planned.containedInBase && planned.plannedHeadOid) {
5170
+ if (!currentHeadOid) return { ok: false, reason: "local branch head could not be read" };
5171
+ return currentHeadOid === planned.plannedHeadOid ? { ok: true } : { ok: false, reason: `branch moved since planning (${currentHeadOid} != ${planned.plannedHeadOid})` };
5172
+ }
5166
5173
  const reviewedHeadOids = planned.reviewedHeadOids?.filter(Boolean) ?? [];
5167
5174
  if (!reviewedHeadOids.length) {
5168
5175
  return { ok: false, reason: "planned branch head was not verified against a PR head" };
5169
5176
  }
5170
- const currentHead = currentHeads.find((h) => h.branch === planned.branch)?.oid;
5171
- if (!currentHead) {
5177
+ if (!currentHeadOid) {
5172
5178
  return { ok: false, reason: "local branch head could not be verified against the PR head" };
5173
5179
  }
5174
- if (!reviewedHeadOids.includes(currentHead)) {
5175
- return { ok: false, reason: `${currentHead} != ${reviewedHeadOids.join("|")}` };
5180
+ if (!reviewedHeadOids.includes(currentHeadOid)) {
5181
+ return { ok: false, reason: `${currentHeadOid} != ${reviewedHeadOids.join("|")}` };
5176
5182
  }
5177
5183
  return { ok: true };
5178
5184
  }
@@ -5272,6 +5278,7 @@ function buildGcPlan(inputs) {
5272
5278
  const unpushedWorktrees = new Set((inputs.worktrees ?? []).filter((w) => w.unpushed).map((w) => w.branch));
5273
5279
  const preservedBranches2 = new Set(inputs.preservedBranches ?? []);
5274
5280
  const preservedWorktrees = new Set((inputs.worktrees ?? []).filter((w) => w.preserved).map((w) => w.branch));
5281
+ const mergedIntoBase = new Set((inputs.mergedIntoBase ?? []).map((b) => b.trim()).filter(Boolean));
5275
5282
  const skipped = [];
5276
5283
  const branches = [];
5277
5284
  const skipTrackingBranches = /* @__PURE__ */ new Set();
@@ -5311,7 +5318,8 @@ function buildGcPlan(inputs) {
5311
5318
  continue;
5312
5319
  }
5313
5320
  const localHead = branchHeads?.get(branch);
5314
- if (worktree2?.unpushed || branchHeads && (!localHead || !state.headOids.length || !state.headOids.includes(localHead))) {
5321
+ const containedInBase = mergedIntoBase.has(branch);
5322
+ if (!containedInBase && (worktree2?.unpushed || branchHeads && (!localHead || !state.headOids.length || !state.headOids.includes(localHead)))) {
5315
5323
  const detail = worktree2?.unpushed ? worktree2.path : localHead && state.headOids.length ? `${localHead} != ${state.headOids.join("|")}` : "local branch head could not be verified against the PR head";
5316
5324
  skipped.push({ branch, reason: "unpushed-branch", detail });
5317
5325
  skipTrackingBranches.add(branch);
@@ -5322,7 +5330,8 @@ function buildGcPlan(inputs) {
5322
5330
  prState: state.state,
5323
5331
  prNumbers: state.numbers,
5324
5332
  worktreePath: worktree2?.path,
5325
- ...state.headOids.length ? { reviewedHeadOids: state.headOids } : {}
5333
+ ...state.headOids.length ? { reviewedHeadOids: state.headOids } : {},
5334
+ ...containedInBase ? { containedInBase: true, ...localHead ? { plannedHeadOid: localHead } : {} } : {}
5326
5335
  });
5327
5336
  }
5328
5337
  const trackingRefs = [...new Set(inputs.staleTrackingRefs ?? [])].map((ref) => {
@@ -5525,38 +5534,38 @@ function safeWorktreeRemoveCommand(safeCwd, targetPath) {
5525
5534
  function errorMessage(error) {
5526
5535
  return error instanceof Error ? error.message : String(error);
5527
5536
  }
5528
- async function fastForwardCurrentBranch(git) {
5537
+ async function fastForwardCurrentBranch(git2) {
5529
5538
  try {
5530
- const before = (await git(["rev-parse", "HEAD"])).trim();
5531
- await git(["pull", "--ff-only"]);
5532
- const after = (await git(["rev-parse", "HEAD"])).trim();
5539
+ const before = (await git2(["rev-parse", "HEAD"])).trim();
5540
+ await git2(["pull", "--ff-only"]);
5541
+ const after = (await git2(["rev-parse", "HEAD"])).trim();
5533
5542
  return { status: before === after ? "up-to-date" : "fast-forwarded" };
5534
5543
  } catch (e) {
5535
5544
  return { status: "failed", error: errorMessage(e) };
5536
5545
  }
5537
5546
  }
5538
- async function returnCheckoutToBase(git, base, mergedBranch, currentBranch2) {
5547
+ async function returnCheckoutToBase(git2, base, mergedBranch, currentBranch2) {
5539
5548
  if (currentBranch2 === mergedBranch) {
5540
- const porcelain = await git(["status", "--porcelain"]).catch(() => void 0);
5549
+ const porcelain = await git2(["status", "--porcelain"]).catch(() => void 0);
5541
5550
  const dirt = porcelain === void 0 ? "tracked-changes" : classifyWorktreeDirt(porcelain);
5542
5551
  if (!isRemovableDirt(dirt)) {
5543
5552
  return { report: { branch: base, switched: false, sync: "skipped", reason: dirt === "untracked-files" ? "untracked-files" : "dirty-worktree" }, canDeleteBranch: false };
5544
5553
  }
5545
5554
  try {
5546
- await git(["checkout", base]);
5555
+ await git2(["checkout", base]);
5547
5556
  } catch (e) {
5548
5557
  return {
5549
5558
  report: { branch: base, switched: false, sync: "skipped", reason: "checkout-failed", error: errorMessage(e) },
5550
5559
  canDeleteBranch: false
5551
5560
  };
5552
5561
  }
5553
- const ff2 = await fastForwardCurrentBranch(git);
5562
+ const ff2 = await fastForwardCurrentBranch(git2);
5554
5563
  return { report: { branch: base, switched: true, sync: ff2.status, ...ff2.error ? { error: ff2.error } : {} }, canDeleteBranch: true };
5555
5564
  }
5556
5565
  if (currentBranch2 !== base) {
5557
5566
  return { report: { branch: base, switched: false, sync: "skipped", reason: "not-on-base" }, canDeleteBranch: true };
5558
5567
  }
5559
- const ff = await fastForwardCurrentBranch(git);
5568
+ const ff = await fastForwardCurrentBranch(git2);
5560
5569
  return { report: { branch: base, switched: false, sync: ff.status, ...ff.error ? { error: ff.error } : {} }, canDeleteBranch: true };
5561
5570
  }
5562
5571
  async function cleanupPrMergeLocalBranch(branch, options) {
@@ -5597,17 +5606,17 @@ async function cleanupPrMergeLocalBranch(branch, options) {
5597
5606
  const safeCwd = selectSafeWorktreeCwd([...afterWorktrees, ...beforeWorktrees], wtPath, {
5598
5607
  pathExists: options.pathExists
5599
5608
  });
5600
- const git = (args) => safeCwd ? options.execGit(["-C", safeCwd, ...args]) : options.execGit(args);
5609
+ const git2 = (args) => safeCwd ? options.execGit(["-C", safeCwd, ...args]) : options.execGit(args);
5601
5610
  const expectedHeadOid = options.expectedHeadOid;
5602
5611
  const verifyReviewedBranchHead = async () => {
5603
5612
  if (!expectedHeadOid) {
5604
- const remaining = await git(["branch", "--list", branch]).catch(() => "");
5613
+ const remaining = await git2(["branch", "--list", branch]).catch(() => "");
5605
5614
  report.localBranch = branchMissingFromList(branch, remaining) ? { name: branch, status: "already-gone" } : { name: branch, status: "not-attempted", reason: "branch-head-unverified" };
5606
5615
  return false;
5607
5616
  }
5608
- const currentHead = (await git(["rev-parse", `refs/heads/${branch}`]).catch(() => "") || "").trim();
5617
+ const currentHead = (await git2(["rev-parse", `refs/heads/${branch}`]).catch(() => "") || "").trim();
5609
5618
  if (!currentHead) {
5610
- const remaining = await git(["branch", "--list", branch]).catch(() => "");
5619
+ const remaining = await git2(["branch", "--list", branch]).catch(() => "");
5611
5620
  report.localBranch = branchMissingFromList(branch, remaining) ? { name: branch, status: "already-gone" } : { name: branch, status: "not-attempted", reason: "branch-head-unverified" };
5612
5621
  return false;
5613
5622
  }
@@ -5642,7 +5651,7 @@ async function cleanupPrMergeLocalBranch(branch, options) {
5642
5651
  return report;
5643
5652
  }
5644
5653
  const removeDeps = {
5645
- git,
5654
+ git: git2,
5646
5655
  sleep: options.sleep ?? defaultSleep,
5647
5656
  detachReparsePoints: options.detachReparsePoints,
5648
5657
  // #3064: junction-safe deferred sweep too
@@ -5662,7 +5671,7 @@ async function cleanupPrMergeLocalBranch(branch, options) {
5662
5671
  chdir: options.chdir
5663
5672
  });
5664
5673
  const outcome = await removeWorktreeWithRecovery(wtPath, {
5665
- git,
5674
+ git: git2,
5666
5675
  sleep: options.sleep ?? defaultSleep,
5667
5676
  detachReparsePoints: options.detachReparsePoints,
5668
5677
  // #3064: reach the actual pr-merge teardown path
@@ -5713,10 +5722,10 @@ async function cleanupPrMergeLocalBranch(branch, options) {
5713
5722
  return report;
5714
5723
  }
5715
5724
  }
5716
- const current = (await git(["rev-parse", "--abbrev-ref", "HEAD"]).catch(() => "") || "").trim();
5725
+ const current = (await git2(["rev-parse", "--abbrev-ref", "HEAD"]).catch(() => "") || "").trim();
5717
5726
  if (options.returnToBranch && options.returnToBranch !== branch) {
5718
5727
  const { report: checkout, canDeleteBranch } = await returnCheckoutToBase(
5719
- git,
5728
+ git2,
5720
5729
  options.returnToBranch,
5721
5730
  branch,
5722
5731
  current
@@ -5735,16 +5744,16 @@ async function cleanupPrMergeLocalBranch(branch, options) {
5735
5744
  }
5736
5745
  const reviewedHeadOid = expectedHeadOid;
5737
5746
  try {
5738
- await git(["update-ref", "-d", `refs/heads/${branch}`, reviewedHeadOid]);
5747
+ await git2(["update-ref", "-d", `refs/heads/${branch}`, reviewedHeadOid]);
5739
5748
  report.localBranch = { name: branch, status: "deleted" };
5740
5749
  } catch (e) {
5741
- const remaining = await git(["branch", "--list", branch]).catch(() => "");
5750
+ const remaining = await git2(["branch", "--list", branch]).catch(() => "");
5742
5751
  report.localBranch = branchMissingFromList(branch, remaining) ? { name: branch, status: "already-gone" } : { name: branch, status: "failed", error: errorMessage(e) };
5743
5752
  }
5744
5753
  if (report.localBranch.status === "deleted" || report.localBranch.status === "already-gone") {
5745
- await git(["config", "--local", "--unset-all", PRESERVED_WORKTREE_CONFIG, `^${escapeGitConfigValueRegex(branch)}$`]).catch(() => void 0);
5754
+ await git2(["config", "--local", "--unset-all", PRESERVED_WORKTREE_CONFIG, `^${escapeGitConfigValueRegex(branch)}$`]).catch(() => void 0);
5746
5755
  }
5747
- if (wtPath) await git(["worktree", "prune"]).catch(() => "");
5756
+ if (wtPath) await git2(["worktree", "prune"]).catch(() => "");
5748
5757
  return report;
5749
5758
  }
5750
5759
  function formatGcPlan(plan, apply) {
@@ -6242,9 +6251,9 @@ function defaultWorktreePath(repoRoot2, branch) {
6242
6251
  const safe = capWorktreeDirName(branch.replace(/[/\\]+/g, "-"));
6243
6252
  return (0, import_node_path10.join)((0, import_node_path10.dirname)(repoRoot2), "mmi-worktrees", (0, import_node_path10.basename)(repoRoot2), safe);
6244
6253
  }
6245
- async function primaryCheckoutRootOf(git) {
6254
+ async function primaryCheckoutRootOf(git2) {
6246
6255
  try {
6247
- const out = (await git(["rev-parse", "--path-format=absolute", "--git-common-dir"])).trim();
6256
+ const out = (await git2(["rev-parse", "--path-format=absolute", "--git-common-dir"])).trim();
6248
6257
  return out ? (0, import_node_path10.dirname)(out) : void 0;
6249
6258
  } catch {
6250
6259
  return void 0;
@@ -6321,7 +6330,7 @@ async function resolveWhoami(deps) {
6321
6330
  }
6322
6331
  function whoamiLine(report, caveat) {
6323
6332
  if (!report.login) return null;
6324
- const line = `current human: ${report.login} (source: ${report.source}) \u2014 act for this login (e.g. claim --for ${report.login}); do not ask who the user is.`;
6333
+ const line = `current human: ${report.login} (source: ${report.source}) \u2014 act for this login (e.g. mmi-cli board claim <n> --for ${report.login}); do not ask who the user is.`;
6325
6334
  return caveat?.trim() ? `${line} ${caveat.trim()}` : line;
6326
6335
  }
6327
6336
  function authorityLine(report) {
@@ -6363,7 +6372,7 @@ function commandLadderHint() {
6363
6372
  }
6364
6373
 
6365
6374
  // src/index.ts
6366
- var import_node_path30 = require("node:path");
6375
+ var import_node_path31 = require("node:path");
6367
6376
 
6368
6377
  // src/merge-ci-policy.ts
6369
6378
  function resolveMergeCiPolicy(input) {
@@ -10809,7 +10818,7 @@ function resolveExplicitScanRoot(explicitRoot, repoRoot2) {
10809
10818
  return explicitRepoWorktreesRoot(explicitRoot, repoRoot2, rootDirs);
10810
10819
  }
10811
10820
  async function gcPlan(remote, limit, opts = {}) {
10812
- const [branches, heads, current, stale, prs, worktrees, siblingDirs, preserved] = await Promise.all([
10821
+ const [branches, heads, current, stale, prs, worktrees, siblingDirs, preserved, mergedIntoBase] = await Promise.all([
10813
10822
  gitOut(["branch", "--format=%(refname:short)"]),
10814
10823
  localBranchHeads(),
10815
10824
  gitOut(["rev-parse", "--abbrev-ref", "HEAD"]),
@@ -10819,7 +10828,8 @@ async function gcPlan(remote, limit, opts = {}) {
10819
10828
  ghPrs(limit),
10820
10829
  worktreeBranches(),
10821
10830
  siblingWorktreeDirs(opts.root),
10822
- preservedBranches()
10831
+ preservedBranches(),
10832
+ branchesMergedIntoBase(remote)
10823
10833
  ]);
10824
10834
  return buildGcPlan({
10825
10835
  localBranches: branches.split(/\r?\n/).map((b) => b.trim()).filter(Boolean),
@@ -10830,9 +10840,23 @@ async function gcPlan(remote, limit, opts = {}) {
10830
10840
  pullRequests: prs,
10831
10841
  worktrees,
10832
10842
  remote,
10833
- preservedBranches: preserved
10843
+ preservedBranches: preserved,
10844
+ mergedIntoBase
10834
10845
  });
10835
10846
  }
10847
+ async function branchesMergedIntoBase(remote) {
10848
+ const out = /* @__PURE__ */ new Set();
10849
+ for (const base of ["development", "main", "master"]) {
10850
+ let listed;
10851
+ try {
10852
+ listed = await gitOut(["branch", "--merged", `${remote}/${base}`, "--format=%(refname:short)"]);
10853
+ } catch {
10854
+ continue;
10855
+ }
10856
+ for (const b of listed.split(/\r?\n/).map((x) => x.trim()).filter(Boolean)) out.add(b);
10857
+ }
10858
+ return [...out];
10859
+ }
10836
10860
  var STRAY_ROOT_WALK_BUDGET_MS = 4e3;
10837
10861
  function measureWorktreeRoot(root, deadline) {
10838
10862
  let dirs = 0;
@@ -11212,8 +11236,8 @@ function shouldSkipInactiveBoardNode(node, cfg) {
11212
11236
  }
11213
11237
  async function collectBoardItems(cfg, options, deps) {
11214
11238
  const client = deps.client ?? defaultGitHubClient();
11215
- const git = deps.git ?? defaultGit;
11216
- const currentRepo = options.repo ?? repoFromRemoteUrl(await git(["remote", "get-url", "origin"])) ?? "";
11239
+ const git2 = deps.git ?? defaultGit;
11240
+ const currentRepo = options.repo ?? repoFromRemoteUrl(await git2(["remote", "get-url", "origin"])) ?? "";
11217
11241
  if (!currentRepo) throw new Error("could not resolve current GitHub repo; pass --repo owner/repo");
11218
11242
  const warnings = [];
11219
11243
  const nodes = [];
@@ -11324,8 +11348,8 @@ function findBoardItem(items, selector, board) {
11324
11348
  return found;
11325
11349
  }
11326
11350
  async function resolveCurrentRepo(options, deps) {
11327
- const git = deps.git ?? defaultGit;
11328
- const currentRepo = options.repo ?? repoFromRemoteUrl(await git(["remote", "get-url", "origin"])) ?? "";
11351
+ const git2 = deps.git ?? defaultGit;
11352
+ const currentRepo = options.repo ?? repoFromRemoteUrl(await git2(["remote", "get-url", "origin"])) ?? "";
11329
11353
  if (!currentRepo) throw new Error("could not resolve current GitHub repo; pass --repo owner/repo");
11330
11354
  return currentRepo;
11331
11355
  }
@@ -11532,8 +11556,8 @@ async function claimBoardIssues(options, deps = {}) {
11532
11556
  async function moveBoardIssues(options, deps = {}) {
11533
11557
  const cfg = resolveBoardConfig(options.config);
11534
11558
  const client = deps.client ?? defaultGitHubClient();
11535
- const git = deps.git ?? defaultGit;
11536
- const currentRepo = options.repo ?? repoFromRemoteUrl(await git(["remote", "get-url", "origin"])) ?? "";
11559
+ const git2 = deps.git ?? defaultGit;
11560
+ const currentRepo = options.repo ?? repoFromRemoteUrl(await git2(["remote", "get-url", "origin"])) ?? "";
11537
11561
  if (!currentRepo) throw new Error("could not resolve current GitHub repo; pass --repo owner/repo");
11538
11562
  const selectors = [];
11539
11563
  const seen = /* @__PURE__ */ new Set();
@@ -11617,8 +11641,8 @@ async function moveBoardIssues(options, deps = {}) {
11617
11641
  async function unclaimBoardIssue(options, deps = {}) {
11618
11642
  const cfg = resolveBoardConfig(options.config);
11619
11643
  const client = deps.client ?? defaultGitHubClient();
11620
- const git = deps.git ?? defaultGit;
11621
- const currentRepo = options.repo ?? repoFromRemoteUrl(await git(["remote", "get-url", "origin"])) ?? "";
11644
+ const git2 = deps.git ?? defaultGit;
11645
+ const currentRepo = options.repo ?? repoFromRemoteUrl(await git2(["remote", "get-url", "origin"])) ?? "";
11622
11646
  if (!currentRepo) throw new Error("could not resolve current GitHub repo; pass --repo owner/repo");
11623
11647
  const selector = parseIssueSelector(options.selector, currentRepo);
11624
11648
  const { viewer, item } = await fetchIssueProjectItem(client, cfg, selector);
@@ -12450,11 +12474,13 @@ var PRIMARY_GROUPS = [
12450
12474
  ["Orient", ["onboard", "status", "next", "doctor", "whoami", "commands", "explain"]],
12451
12475
  ["Plan and work", ["board", "issue", "worktree", "stage"]],
12452
12476
  ["Review and ship", ["pr", "ci", "rcand", "release", "hotfix", "train"]],
12453
- ["Setup and support", ["bootstrap", "secrets", "docs"]],
12477
+ // `tests` sits beside `docs` deliberately: both are deterministic, repo-local gates a workflow
12478
+ // step invokes (`docs refs`, `tests policy`), not org-plane operations (#3605).
12479
+ ["Setup and support", ["bootstrap", "secrets", "docs", "tests"]],
12454
12480
  ["Coordinate and improve", ["wave", "report", "skill-lesson"]]
12455
12481
  ];
12456
12482
  var OPERATIONAL_TOP_LEVEL = /* @__PURE__ */ new Set(["org", "runtime", "plugin"]);
12457
- var SUPPORT_PRIMARY = /* @__PURE__ */ new Set(["doctor", "whoami", "commands", "explain", "docs", "wave", "report", "skill-lesson"]);
12483
+ var SUPPORT_PRIMARY = /* @__PURE__ */ new Set(["doctor", "whoami", "commands", "explain", "docs", "tests", "wave", "report", "skill-lesson"]);
12458
12484
  var TOP_LEVEL_ORDER = /* @__PURE__ */ new Map();
12459
12485
  var HELP_GROUP_ORDER = /* @__PURE__ */ new Map();
12460
12486
  var topLevelPosition = 0;
@@ -15031,31 +15057,31 @@ var CHERRY_TRAILER = /\(cherry picked from commit ([0-9a-f]{7,40})\)/g;
15031
15057
  function checkHotfixCoverage(options = {}) {
15032
15058
  const { cwd = process.cwd(), mainRef = "origin/main", rcRef = "origin/rc", manifestPaths = [] } = options;
15033
15059
  const ack = (options.ack ?? []).filter(Boolean);
15034
- const git = options.git ?? ((args, opts) => (0, import_node_child_process8.execFileSync)("git", args, { cwd, encoding: "utf8", input: opts?.input, stdio: ["pipe", "pipe", "pipe"] }));
15060
+ const git2 = options.git ?? ((args, opts) => (0, import_node_child_process8.execFileSync)("git", args, { cwd, encoding: "utf8", input: opts?.input, stdio: ["pipe", "pipe", "pipe"] }));
15035
15061
  const revList = (range) => {
15036
- const out = git(["rev-list", "--no-merges", range]).trim();
15062
+ const out = git2(["rev-list", "--no-merges", range]).trim();
15037
15063
  return out ? out.split("\n") : [];
15038
15064
  };
15039
15065
  const isAncestor = (sha, ref) => {
15040
15066
  try {
15041
- git(["merge-base", "--is-ancestor", sha, ref]);
15067
+ git2(["merge-base", "--is-ancestor", sha, ref]);
15042
15068
  return true;
15043
15069
  } catch {
15044
15070
  return false;
15045
15071
  }
15046
15072
  };
15047
15073
  const patchId = (sha) => {
15048
- const diff = git(["show", "--no-color", "--pretty=format:", sha]);
15074
+ const diff = git2(["show", "--no-color", "--pretty=format:", sha]);
15049
15075
  if (!diff.trim()) return null;
15050
- const out = git(["patch-id", "--stable"], { input: diff }).trim();
15076
+ const out = git2(["patch-id", "--stable"], { input: diff }).trim();
15051
15077
  return out ? out.split(" ")[0] : null;
15052
15078
  };
15053
15079
  const changedPaths = (sha) => {
15054
- const out = git(["show", "--no-color", "--name-only", "--pretty=format:", sha]).trim();
15080
+ const out = git2(["show", "--no-color", "--name-only", "--pretty=format:", sha]).trim();
15055
15081
  return out ? out.split("\n") : [];
15056
15082
  };
15057
15083
  const cherrySources = (sha) => {
15058
- const message = git(["log", "-1", "--format=%B", sha]);
15084
+ const message = git2(["log", "-1", "--format=%B", sha]);
15059
15085
  return [...message.matchAll(CHERRY_TRAILER)].map((m) => m[1]);
15060
15086
  };
15061
15087
  const mainOnly = revList(`${rcRef}..${mainRef}`);
@@ -15070,7 +15096,7 @@ function checkHotfixCoverage(options = {}) {
15070
15096
  }
15071
15097
  };
15072
15098
  const commits = mainOnly.map((sha) => {
15073
- const subject = git(["log", "-1", "--format=%s", sha]).trim();
15099
+ const subject = git2(["log", "-1", "--format=%s", sha]).trim();
15074
15100
  const paths = changedPaths(sha);
15075
15101
  if (paths.length > 0 && paths.every((p) => manifestPaths.includes(p))) {
15076
15102
  return { sha, subject, coverage: "exempt-distribution" };
@@ -15099,21 +15125,21 @@ function checkHotfixCoverage(options = {}) {
15099
15125
  }
15100
15126
  function checkHotfixCarries(options) {
15101
15127
  const { cwd = process.cwd(), branch, baseRef, targets } = options;
15102
- const git = options.git ?? ((args, opts) => (0, import_node_child_process8.execFileSync)("git", args, { cwd, encoding: "utf8", input: opts?.input, stdio: ["pipe", "pipe", "pipe"] }));
15128
+ const git2 = options.git ?? ((args, opts) => (0, import_node_child_process8.execFileSync)("git", args, { cwd, encoding: "utf8", input: opts?.input, stdio: ["pipe", "pipe", "pipe"] }));
15103
15129
  const isAncestor = (sha, ref) => {
15104
15130
  try {
15105
- git(["merge-base", "--is-ancestor", sha, ref]);
15131
+ git2(["merge-base", "--is-ancestor", sha, ref]);
15106
15132
  return true;
15107
15133
  } catch {
15108
15134
  return false;
15109
15135
  }
15110
15136
  };
15111
15137
  const revList = (range) => {
15112
- const out = git(["rev-list", "--no-merges", range]).trim();
15138
+ const out = git2(["rev-list", "--no-merges", range]).trim();
15113
15139
  return out ? out.split("\n") : [];
15114
15140
  };
15115
15141
  const cherrySources = (sha) => {
15116
- const message = git(["log", "-1", "--format=%B", sha]);
15142
+ const message = git2(["log", "-1", "--format=%B", sha]);
15117
15143
  return [...message.matchAll(CHERRY_TRAILER)].map((m) => m[1]);
15118
15144
  };
15119
15145
  const carried = /* @__PURE__ */ new Set();
@@ -16364,6 +16390,188 @@ function runDocRefs(root, deps = {}) {
16364
16390
  return { ok: findings.length === 0, findings, docCount: Object.keys(docs2).length };
16365
16391
  }
16366
16392
 
16393
+ // src/test-policy-core.ts
16394
+ var import_node_child_process10 = require("node:child_process");
16395
+ var import_node_fs19 = require("node:fs");
16396
+ var import_node_path17 = require("node:path");
16397
+ var POLICY_FILE = "test-policy.json";
16398
+ var TEST_RE = /\.(?:test|spec)\.[cm]?[jt]sx?$/;
16399
+ var PY_TEST_RE = /(?:^|\/)test_[^/]*\.py$|_test\.py$/;
16400
+ var OVERRIDE_RE = /^Test-Policy-Override:\s*(.+)$/im;
16401
+ function translate(glob) {
16402
+ let out = "";
16403
+ for (let i = 0; i < glob.length; i++) {
16404
+ const c = glob[i];
16405
+ if (c === "*") {
16406
+ if (glob[i + 1] === "*") {
16407
+ if (glob[i + 2] === "/") {
16408
+ out += "(?:.*/)?";
16409
+ i += 2;
16410
+ } else {
16411
+ out += ".*";
16412
+ i += 1;
16413
+ }
16414
+ } else {
16415
+ out += "[^/]*";
16416
+ }
16417
+ continue;
16418
+ }
16419
+ if (c === "{") {
16420
+ const close = glob.indexOf("}", i);
16421
+ if (close !== -1) {
16422
+ const alts = glob.slice(i + 1, close).split(",").map((a) => translate(a));
16423
+ out += `(?:${alts.join("|")})`;
16424
+ i = close;
16425
+ continue;
16426
+ }
16427
+ }
16428
+ out += /[.+?^${}()|[\]\\]/.test(c) ? `\\${c}` : c;
16429
+ }
16430
+ return out;
16431
+ }
16432
+ function globToRegExp(glob) {
16433
+ return new RegExp(`^${translate(glob)}$`);
16434
+ }
16435
+ function isTestPath(path2) {
16436
+ return TEST_RE.test(path2) || PY_TEST_RE.test(path2);
16437
+ }
16438
+ function loadPolicy(root, readFile7 = readFileOrNull2) {
16439
+ const raw = readFile7((0, import_node_path17.join)(root, POLICY_FILE));
16440
+ if (raw == null) return { mandatory: [], declared: false };
16441
+ try {
16442
+ return { ...JSON.parse(raw), declared: true };
16443
+ } catch (error) {
16444
+ throw new Error(`${POLICY_FILE} is not valid JSON: ${error.message}`);
16445
+ }
16446
+ }
16447
+ function readFileOrNull2(path2) {
16448
+ try {
16449
+ return (0, import_node_fs19.readFileSync)(path2, "utf8");
16450
+ } catch {
16451
+ return null;
16452
+ }
16453
+ }
16454
+ function removedPaths(changed) {
16455
+ return new Set(
16456
+ changed.map((f) => f.status === "D" ? f.path : f.status === "R" || f.status === "C" ? f.from : void 0).filter((p) => typeof p === "string")
16457
+ );
16458
+ }
16459
+ function classify(changed, policy, present = () => false) {
16460
+ const matchers = (policy.mandatory ?? []).map((m) => ({ ...m, re: globToRegExp(m.glob) }));
16461
+ const mandatoryHits = changed.filter((f) => matchers.some((m) => m.re.test(f.path)));
16462
+ const testChanges = changed.filter((f) => isTestPath(f.path));
16463
+ const addedTests = testChanges.filter((f) => f.status === "A");
16464
+ const removed = removedPaths(changed);
16465
+ const discharged = (m) => (m.satisfiedBy?.length ?? 0) > 0 && m.satisfiedBy.every((p) => present(p) && !removed.has(p));
16466
+ const untestedHits = changed.filter((f) => matchers.some((m) => m.re.test(f.path) && !discharged(m)));
16467
+ const protectedBy = new Map((policy.protected ?? []).map((p) => [p.path, p.why ?? ""]));
16468
+ for (const m of policy.mandatory ?? []) {
16469
+ for (const p of m.satisfiedBy ?? []) {
16470
+ if (!protectedBy.has(p)) protectedBy.set(p, `the standing coverage \`${m.glob}\` is discharged by. Removing it silently empties that glob.`);
16471
+ }
16472
+ }
16473
+ const removedProtected = [...removed].filter((p) => protectedBy.has(p)).map((p) => ({ path: p, why: protectedBy.get(p) ?? "" }));
16474
+ return { mandatoryHits, untestedHits, testChanges, addedTests, removedProtected };
16475
+ }
16476
+ function unresolvedProtectedEntries(policy, root, exists = (path2) => (0, import_node_fs19.existsSync)(path2)) {
16477
+ return (policy.protected ?? []).map((p) => p.path).filter((p) => !exists((0, import_node_path17.join)(root, p)));
16478
+ }
16479
+ function unresolvedSatisfiers(policy, root, exists = (path2) => (0, import_node_fs19.existsSync)(path2)) {
16480
+ const declared = (policy.mandatory ?? []).flatMap((m) => m.satisfiedBy ?? []);
16481
+ return [...new Set(declared)].filter((p) => !exists((0, import_node_path17.join)(root, p)));
16482
+ }
16483
+ function evaluate(changed, policy, present = () => false) {
16484
+ const { mandatoryHits, untestedHits, testChanges, addedTests, removedProtected } = classify(changed, policy, present);
16485
+ const findings = [];
16486
+ if (removedProtected.length > 0) {
16487
+ findings.push({
16488
+ kind: "protected-removed",
16489
+ paths: removedProtected.map((f) => f.path),
16490
+ detail: `PROTECTED TEST FILE(S) REMOVED \u2014 this change deletes or renames away ${removedProtected.length} file(s) test-policy.json protects:
16491
+ ` + removedProtected.map((f) => ` ${f.path}
16492
+ ${f.why}`).join("\n") + "\n If you are removing one because it looked unrequired, read the reason above first \u2014 it is\n the answer to exactly that question. If this removal was asked for, add a commit trailer:\n Test-Policy-Override: <reason>"
16493
+ });
16494
+ }
16495
+ if (untestedHits.length > 0 && testChanges.length === 0) {
16496
+ findings.push({
16497
+ kind: "mandatory-zone-untested",
16498
+ paths: untestedHits.map((f) => f.path),
16499
+ detail: `MANDATORY ZONE UNTESTED \u2014 this change touches ${untestedHits.length} path(s) in the mandatory zone but changes no test:
16500
+ ` + untestedHits.map((f) => ` ${f.path}`).join("\n") + "\n These are the paths where a silent failure costs real money, real data, a security breach,\n or production. Add or update a test that would fail if the guarded property broke."
16501
+ });
16502
+ }
16503
+ if (policy.declared !== false && addedTests.length > 0 && mandatoryHits.length === 0) {
16504
+ findings.push({
16505
+ kind: "unrequested-test-file",
16506
+ paths: addedTests.map((f) => f.path),
16507
+ detail: `UNREQUESTED TEST FILE(S) \u2014 this change adds ${addedTests.length} new test file(s) but touches nothing in the mandatory zone:
16508
+ ` + addedTests.map((f) => ` ${f.path}`).join("\n") + "\n Tests are OPT-IN in this estate. Write one only if its absence means catastrophe \u2014 money\n lost, data destroyed, a security breach, or production down. If a silent failure costs\n nothing but a re-run, delete the file and move on. If this test was asked for, add a commit\n trailer: Test-Policy-Override: <reason>"
16509
+ });
16510
+ }
16511
+ return findings;
16512
+ }
16513
+ function git(args, cwd) {
16514
+ return (0, import_node_child_process10.execFileSync)("git", args, { cwd, encoding: "utf8", maxBuffer: 32 * 1024 * 1024 });
16515
+ }
16516
+ function resolveBase(cwd, explicit) {
16517
+ const candidates = [explicit, process.env.TEST_POLICY_BASE, "origin/development", "origin/main"].filter(Boolean);
16518
+ for (const ref of candidates) {
16519
+ try {
16520
+ return git(["merge-base", "HEAD", ref], cwd).trim();
16521
+ } catch {
16522
+ }
16523
+ }
16524
+ return null;
16525
+ }
16526
+ function changedFilesSince(base, cwd) {
16527
+ const raw = git(["diff", "--name-status", "-M", `${base}...HEAD`], cwd).trim();
16528
+ if (!raw) return [];
16529
+ return raw.split("\n").map((line) => {
16530
+ const parts = line.split(" ");
16531
+ const norm = (p) => p.replace(/\\/g, "/");
16532
+ const row = { status: parts[0][0], path: norm(parts[parts.length - 1]) };
16533
+ if (parts.length > 2) row.from = norm(parts[1]);
16534
+ return row;
16535
+ });
16536
+ }
16537
+ function runTestPolicy(root, deps = {}) {
16538
+ const policy = deps.policy ?? loadPolicy(root);
16539
+ const exists = deps.exists ?? ((path2) => (0, import_node_fs19.existsSync)(path2));
16540
+ const counts = { mandatoryCount: (policy.mandatory ?? []).length, protectedCount: (policy.protected ?? []).length };
16541
+ const base = deps.changed ? "(injected)" : resolveBase(root, deps.base);
16542
+ const changed = deps.changed ?? (base ? changedFilesSince(base, root) : []);
16543
+ const override = deps.overrideReason !== void 0 ? deps.overrideReason : !deps.changed && base ? OVERRIDE_RE.exec(git(["log", `${base}..HEAD`, "--format=%B"], root))?.[1]?.trim() ?? null : null;
16544
+ if (override) return { ok: true, findings: [], changedCount: changed.length, base, overriddenBy: override, ...counts };
16545
+ const present = (path2) => exists((0, import_node_path17.join)(root, path2));
16546
+ const removedByThisDiff = removedPaths(changed);
16547
+ const staleFindings = [];
16548
+ const unresolved = unresolvedProtectedEntries(policy, root, exists).filter((p) => !removedByThisDiff.has(p));
16549
+ if (unresolved.length > 0) {
16550
+ staleFindings.push({
16551
+ kind: "stale-protected-entry",
16552
+ paths: unresolved,
16553
+ detail: `STALE PROTECTED ENTRY \u2014 test-policy.json protects ${unresolved.length} path(s) that do not exist:
16554
+ ` + unresolved.map((p) => ` ${p}`).join("\n") + "\n An entry naming a missing file reads as protection while protecting nothing.\n Restore the file, or remove its entry."
16555
+ });
16556
+ }
16557
+ const staleSatisfiers = unresolvedSatisfiers(policy, root, exists).filter((p) => !removedByThisDiff.has(p));
16558
+ if (staleSatisfiers.length > 0) {
16559
+ staleFindings.push({
16560
+ kind: "stale-satisfied-by",
16561
+ paths: staleSatisfiers,
16562
+ detail: `STALE STANDING COVERAGE \u2014 a mandatory glob claims ${staleSatisfiers.length} path(s) as satisfiedBy that do not exist:
16563
+ ` + staleSatisfiers.map((p) => ` ${p}`).join("\n") + "\n A glob discharged by coverage that is not there is a glob enforcing nothing, quietly.\n Restore the file, or drop it from satisfiedBy so the glob asks for a test again."
16564
+ });
16565
+ }
16566
+ if (staleFindings.length > 0) {
16567
+ return { ok: false, base, changedCount: changed.length, ...counts, findings: staleFindings };
16568
+ }
16569
+ if (!deps.changed && !base) return { ok: true, findings: [], changedCount: 0, base: null, ...counts };
16570
+ if (changed.length === 0) return { ok: true, findings: [], changedCount: 0, base, ...counts };
16571
+ const findings = evaluate(changed, policy, present);
16572
+ return { ok: findings.length === 0, findings, changedCount: changed.length, base, ...counts };
16573
+ }
16574
+
16367
16575
  // src/docs-audit-command.ts
16368
16576
  function serializeOutcome(outcome) {
16369
16577
  switch (outcome.kind) {
@@ -17305,8 +17513,8 @@ function writeError(res) {
17305
17513
  }
17306
17514
 
17307
17515
  // src/secrets-commands.ts
17308
- var import_node_fs19 = require("node:fs");
17309
- var import_node_path17 = require("node:path");
17516
+ var import_node_fs20 = require("node:fs");
17517
+ var import_node_path18 = require("node:path");
17310
17518
  var import_node_os5 = require("node:os");
17311
17519
 
17312
17520
  // src/project-runtime.ts
@@ -17430,18 +17638,18 @@ function collectMap(value, previous = []) {
17430
17638
  return [...previous, value];
17431
17639
  }
17432
17640
  async function decryptRailsCredentials(input) {
17433
- const appDir = (0, import_node_path17.resolve)(input.appDir ?? process.cwd());
17641
+ const appDir = (0, import_node_path18.resolve)(input.appDir ?? process.cwd());
17434
17642
  const credentialsFile = input.credentialsFile ?? DEFAULT_RAILS_CREDENTIALS_FILE;
17435
17643
  const masterKeyFile = input.masterKeyFile ?? DEFAULT_RAILS_MASTER_KEY_FILE;
17436
- const credentialsPath = (0, import_node_path17.resolve)(appDir, credentialsFile);
17437
- const masterKeyPath = (0, import_node_path17.resolve)(appDir, masterKeyFile);
17644
+ const credentialsPath = (0, import_node_path18.resolve)(appDir, credentialsFile);
17645
+ const masterKeyPath = (0, import_node_path18.resolve)(appDir, masterKeyFile);
17438
17646
  const env = {
17439
17647
  ...process.env,
17440
17648
  MMI_RAILS_CREDENTIALS_FILE: credentialsPath,
17441
17649
  MMI_RAILS_MASTER_KEY_FILE: masterKeyPath
17442
17650
  };
17443
- if ((0, import_node_fs19.existsSync)(masterKeyPath)) {
17444
- env.RAILS_MASTER_KEY = (0, import_node_fs19.readFileSync)(masterKeyPath, "utf8").trim();
17651
+ if ((0, import_node_fs20.existsSync)(masterKeyPath)) {
17652
+ env.RAILS_MASTER_KEY = (0, import_node_fs20.readFileSync)(masterKeyPath, "utf8").trim();
17445
17653
  }
17446
17654
  const script = [
17447
17655
  'require "json"',
@@ -17451,9 +17659,9 @@ async function decryptRailsCredentials(input) {
17451
17659
  'config = ActiveSupport::EncryptedConfiguration.new(config_path: config_path, key_path: key_path, env_key: "RAILS_MASTER_KEY", raise_if_missing_key: true)',
17452
17660
  "puts JSON.generate(config.config)"
17453
17661
  ].join("\n");
17454
- const scriptDir = (0, import_node_fs19.mkdtempSync)((0, import_node_path17.join)((0, import_node_os5.tmpdir)(), "mmi-rails-decrypt-"));
17455
- const scriptPath = (0, import_node_path17.join)(scriptDir, "decrypt.rb");
17456
- (0, import_node_fs19.writeFileSync)(scriptPath, script, "utf8");
17662
+ const scriptDir = (0, import_node_fs20.mkdtempSync)((0, import_node_path18.join)((0, import_node_os5.tmpdir)(), "mmi-rails-decrypt-"));
17663
+ const scriptPath = (0, import_node_path18.join)(scriptDir, "decrypt.rb");
17664
+ (0, import_node_fs20.writeFileSync)(scriptPath, script, "utf8");
17457
17665
  try {
17458
17666
  const args = ["exec", "ruby", scriptPath];
17459
17667
  const cmd = process.platform === "win32" ? "cmd.exe" : "bundle";
@@ -17465,7 +17673,7 @@ async function decryptRailsCredentials(input) {
17465
17673
  });
17466
17674
  return JSON.parse(stdout);
17467
17675
  } finally {
17468
- (0, import_node_fs19.rmSync)(scriptDir, { recursive: true, force: true });
17676
+ (0, import_node_fs20.rmSync)(scriptDir, { recursive: true, force: true });
17469
17677
  }
17470
17678
  }
17471
17679
  async function readSecretStdin() {
@@ -17555,7 +17763,7 @@ function registerSecretsCommands(program3) {
17555
17763
  let body;
17556
17764
  if (o.file) {
17557
17765
  try {
17558
- body = (0, import_node_fs19.readFileSync)((0, import_node_path17.resolve)(o.file), "utf8");
17766
+ body = (0, import_node_fs20.readFileSync)((0, import_node_path18.resolve)(o.file), "utf8");
17559
17767
  } catch (e) {
17560
17768
  return fail(`secrets org-catalog: cannot read --file ${o.file}: ${e.message}`);
17561
17769
  }
@@ -17660,7 +17868,7 @@ function registerSecretsCommands(program3) {
17660
17868
  {
17661
17869
  ...d,
17662
17870
  decryptRailsCredentials,
17663
- removeFile: (path2) => (0, import_node_fs19.unlinkSync)((0, import_node_path17.resolve)(o.appDir ?? process.cwd(), path2))
17871
+ removeFile: (path2) => (0, import_node_fs20.unlinkSync)((0, import_node_path18.resolve)(o.appDir ?? process.cwd(), path2))
17664
17872
  },
17665
17873
  {
17666
17874
  repo: o.repo,
@@ -17854,7 +18062,7 @@ function checkGithubPools(probe) {
17854
18062
  }
17855
18063
 
17856
18064
  // src/box-commands.ts
17857
- var import_node_fs20 = require("node:fs");
18065
+ var import_node_fs21 = require("node:fs");
17858
18066
 
17859
18067
  // src/box.ts
17860
18068
  var BOX_KEYS = {
@@ -18057,7 +18265,7 @@ function registerBoxCommands(program3) {
18057
18265
  }
18058
18266
  if (o.json) console.log(JSON.stringify({ box: found, incomplete }, null, 2));
18059
18267
  else if (o.ssh && o.script) {
18060
- (0, import_node_fs20.writeFileSync)(o.script, sshRecipeScript(found), "utf8");
18268
+ (0, import_node_fs21.writeFileSync)(o.script, sshRecipeScript(found), "utf8");
18061
18269
  console.log(`wrote ${o.script} \u2014 run: bash "${o.script}"`);
18062
18270
  } else if (o.ssh) console.log(`${formatSshRecipe(found)}
18063
18271
  ${SSH_RECIPE_AGENT_NOTE}`);
@@ -18072,7 +18280,7 @@ ${SSH_RECIPE_AGENT_NOTE}`);
18072
18280
 
18073
18281
  // src/schedules-commands.ts
18074
18282
  var import_promises2 = require("node:fs/promises");
18075
- var import_node_child_process10 = require("node:child_process");
18283
+ var import_node_child_process11 = require("node:child_process");
18076
18284
  var import_node_util7 = require("node:util");
18077
18285
 
18078
18286
  // src/schedules.ts
@@ -18512,7 +18720,7 @@ function assembleReconciliation(githubEntries2, readRepos, registry2, activeWork
18512
18720
  }
18513
18721
 
18514
18722
  // src/schedules-commands.ts
18515
- var execFileP5 = (0, import_node_util7.promisify)(import_node_child_process10.execFile);
18723
+ var execFileP5 = (0, import_node_util7.promisify)(import_node_child_process11.execFile);
18516
18724
  var AWS_REGION = "eu-central-1";
18517
18725
  var AWS_TIMEOUT_MS = 3e4;
18518
18726
  var AWS_RETRY_DELAY_MS = 1500;
@@ -18749,7 +18957,7 @@ function registerSchedulesCommands(program3) {
18749
18957
 
18750
18958
  // src/file-lock.ts
18751
18959
  var import_promises3 = require("node:fs/promises");
18752
- var import_node_path18 = require("node:path");
18960
+ var import_node_path19 = require("node:path");
18753
18961
  var sleep = (ms) => new Promise((resolve5) => setTimeout(resolve5, ms));
18754
18962
  var IMMEDIATE_RETRY_BUDGET = 3;
18755
18963
  var FileLockBusyError = class extends Error {
@@ -18834,7 +19042,7 @@ async function releaseFileLock(lockPath, guard) {
18834
19042
  }
18835
19043
  async function withFileLock(lockPath, opts, fn) {
18836
19044
  const resolved = resolveFileLockOpts(opts);
18837
- await (0, import_promises3.mkdir)((0, import_node_path18.dirname)(lockPath), { recursive: true }).catch(() => void 0);
19045
+ await (0, import_promises3.mkdir)((0, import_node_path19.dirname)(lockPath), { recursive: true }).catch(() => void 0);
18838
19046
  const guard = await acquireFileLock(lockPath, resolved, Date.now() + resolved.maxWaitMs);
18839
19047
  try {
18840
19048
  return await fn();
@@ -18845,7 +19053,7 @@ async function withFileLock(lockPath, opts, fn) {
18845
19053
 
18846
19054
  // src/schedules-lift-command.ts
18847
19055
  var import_promises4 = require("node:fs/promises");
18848
- var import_node_path19 = require("node:path");
19056
+ var import_node_path20 = require("node:path");
18849
19057
 
18850
19058
  // src/schedules-lift.ts
18851
19059
  var SCHEDULE_HEADER_FIELDS = ["schedule", "what", "owner", "cadence", "executor", "llm", "output", "breaks", "kill"];
@@ -18951,7 +19159,7 @@ async function readWorkflowFiles(dir) {
18951
19159
  const files = [];
18952
19160
  for (const name of names.sort()) {
18953
19161
  if (!/\.ya?ml$/.test(name)) continue;
18954
- files.push({ path: `.github/workflows/${name}`, text: await (0, import_promises4.readFile)((0, import_node_path19.join)(dir, name), "utf8") });
19162
+ files.push({ path: `.github/workflows/${name}`, text: await (0, import_promises4.readFile)((0, import_node_path20.join)(dir, name), "utf8") });
18955
19163
  }
18956
19164
  return files;
18957
19165
  }
@@ -19493,7 +19701,7 @@ function registerQueryCommands(program3) {
19493
19701
  }
19494
19702
 
19495
19703
  // src/bootstrap-commands.ts
19496
- var import_node_fs21 = require("node:fs");
19704
+ var import_node_fs22 = require("node:fs");
19497
19705
 
19498
19706
  // src/bootstrap-verify.ts
19499
19707
  var TRAIN_BRANCHES2 = ["development", "rc", "main"];
@@ -20036,7 +20244,7 @@ function registerBootstrapCommands(program3) {
20036
20244
  client: defaultGitHubClient(),
20037
20245
  projectMeta: meta,
20038
20246
  deployModel: typeof meta?.deployModel === "string" ? meta.deployModel : void 0,
20039
- readLocalFile: (path2) => path2 === "projects.json" && apiProjects != null ? apiProjects : (0, import_node_fs21.existsSync)(path2) ? (0, import_node_fs21.readFileSync)(path2, "utf8") : null,
20247
+ readLocalFile: (path2) => path2 === "projects.json" && apiProjects != null ? apiProjects : (0, import_node_fs22.existsSync)(path2) ? (0, import_node_fs22.readFileSync)(path2, "utf8") : null,
20040
20248
  // requiredGcpApis is stored as an array by a JSON write, but `org project set --var KEY=VALUE` stores a raw
20041
20249
  // comma-string — accept either so the seeded value verifies regardless of how it was written.
20042
20250
  requiredGcpApis: (() => {
@@ -20091,12 +20299,12 @@ function registerBootstrapCommands(program3) {
20091
20299
  return fail(`bootstrap apply: ${e.message}`);
20092
20300
  }
20093
20301
  const manifestPath = "skills/bootstrap/seeds/manifest.json";
20094
- if (!(0, import_node_fs21.existsSync)(manifestPath)) return fail(`bootstrap apply: ${manifestPath} not found; bootstrap runs from the MMI-Hub repo root by design \u2014 it stamps org-level resources (Project, Ruleset, secrets, access) through the GitHub App, which is only authorized from the Hub checkout`);
20095
- const manifest = loadBootstrapSeeds((0, import_node_fs21.readFileSync)(manifestPath, "utf8"));
20302
+ if (!(0, import_node_fs22.existsSync)(manifestPath)) return fail(`bootstrap apply: ${manifestPath} not found; bootstrap runs from the MMI-Hub repo root by design \u2014 it stamps org-level resources (Project, Ruleset, secrets, access) through the GitHub App, which is only authorized from the Hub checkout`);
20303
+ const manifest = loadBootstrapSeeds((0, import_node_fs22.readFileSync)(manifestPath, "utf8"));
20096
20304
  const baseBranch = o.class === "content" ? "main" : "development";
20097
20305
  const slug = parsedRepo.slug;
20098
20306
  const gh = async (args) => execFileP2("gh", args, { timeout: 2e4 });
20099
- const readFile7 = (p) => (0, import_node_fs21.existsSync)(p) ? (0, import_node_fs21.readFileSync)(p, "utf8") : null;
20307
+ const readFile7 = (p) => (0, import_node_fs22.existsSync)(p) ? (0, import_node_fs22.readFileSync)(p, "utf8") : null;
20100
20308
  const enc = (p) => p.split("/").map(encodeURIComponent).join("/");
20101
20309
  const rawVars = {};
20102
20310
  for (const value of cmdOpts.var ?? []) {
@@ -20330,12 +20538,12 @@ LIVE apply to ${repo}:
20330
20538
  }
20331
20539
 
20332
20540
  // src/stage-commands.ts
20333
- var import_node_fs23 = require("node:fs");
20334
- var import_node_path21 = require("node:path");
20541
+ var import_node_fs24 = require("node:fs");
20542
+ var import_node_path22 = require("node:path");
20335
20543
 
20336
20544
  // src/port-registry.ts
20337
- var import_node_fs22 = require("node:fs");
20338
- var import_node_path20 = require("node:path");
20545
+ var import_node_fs23 = require("node:fs");
20546
+ var import_node_path21 = require("node:path");
20339
20547
 
20340
20548
  // ../infra/port-geometry.mjs
20341
20549
  var PORT_BLOCK = 100;
@@ -20349,8 +20557,8 @@ function nextPortBlock(registry2) {
20349
20557
  return [base, base + PORT_SPAN];
20350
20558
  }
20351
20559
  function loadPortRegistry(path2) {
20352
- if (!(0, import_node_fs22.existsSync)(path2)) return {};
20353
- const raw = JSON.parse((0, import_node_fs22.readFileSync)(path2, "utf8"));
20560
+ if (!(0, import_node_fs23.existsSync)(path2)) return {};
20561
+ const raw = JSON.parse((0, import_node_fs23.readFileSync)(path2, "utf8"));
20354
20562
  const out = {};
20355
20563
  for (const [key, value] of Object.entries(raw)) {
20356
20564
  if (Array.isArray(value) && value.length === 2 && value.every((n) => typeof n === "number")) {
@@ -20364,9 +20572,9 @@ function ensurePortRange(repo, path2) {
20364
20572
  const existing = registry2[repo];
20365
20573
  if (existing) return existing;
20366
20574
  const range = nextPortBlock(registry2);
20367
- const raw = (0, import_node_fs22.existsSync)(path2) ? JSON.parse((0, import_node_fs22.readFileSync)(path2, "utf8")) : {};
20575
+ const raw = (0, import_node_fs23.existsSync)(path2) ? JSON.parse((0, import_node_fs23.readFileSync)(path2, "utf8")) : {};
20368
20576
  raw[repo] = range;
20369
- (0, import_node_fs22.writeFileSync)(path2, JSON.stringify(raw, null, 2) + "\n", "utf8");
20577
+ (0, import_node_fs23.writeFileSync)(path2, JSON.stringify(raw, null, 2) + "\n", "utf8");
20370
20578
  return range;
20371
20579
  }
20372
20580
  function portCursorSeed(registry2) {
@@ -20388,22 +20596,22 @@ function existingPortRange(repo, registry2) {
20388
20596
  return registry2[repo] ?? null;
20389
20597
  }
20390
20598
  function portRangeInfraAt(root, source) {
20391
- const registryPath = (0, import_node_path20.join)(root, "infra", "port-ranges.json");
20392
- const ddbScriptPath = (0, import_node_path20.join)(root, "infra", "port-ddb.mjs");
20393
- if (!(0, import_node_fs22.existsSync)(registryPath) || !(0, import_node_fs22.existsSync)(ddbScriptPath)) return null;
20599
+ const registryPath = (0, import_node_path21.join)(root, "infra", "port-ranges.json");
20600
+ const ddbScriptPath = (0, import_node_path21.join)(root, "infra", "port-ddb.mjs");
20601
+ if (!(0, import_node_fs23.existsSync)(registryPath) || !(0, import_node_fs23.existsSync)(ddbScriptPath)) return null;
20394
20602
  return { root, source, registryPath, ddbScriptPath };
20395
20603
  }
20396
20604
  function resolvePortRangeInfra(cwd, packageDir) {
20397
20605
  const direct = portRangeInfraAt(cwd, "cwd");
20398
20606
  if (direct) return direct;
20399
- for (let dir = cwd; ; dir = (0, import_node_path20.dirname)(dir)) {
20400
- const sibling = portRangeInfraAt((0, import_node_path20.join)(dir, "MMI-Hub"), "sibling-hub");
20607
+ for (let dir = cwd; ; dir = (0, import_node_path21.dirname)(dir)) {
20608
+ const sibling = portRangeInfraAt((0, import_node_path21.join)(dir, "MMI-Hub"), "sibling-hub");
20401
20609
  if (sibling) return sibling;
20402
- const parent = (0, import_node_path20.dirname)(dir);
20610
+ const parent = (0, import_node_path21.dirname)(dir);
20403
20611
  if (parent === dir) break;
20404
20612
  }
20405
20613
  if (packageDir) {
20406
- const pkgRoot = (0, import_node_path20.join)(packageDir, "..", "..");
20614
+ const pkgRoot = (0, import_node_path21.join)(packageDir, "..", "..");
20407
20615
  const pkgFrom = portRangeInfraAt(pkgRoot, "pkg-root");
20408
20616
  if (pkgFrom) return pkgFrom;
20409
20617
  }
@@ -20579,8 +20787,8 @@ function registerStageCommands(program3) {
20579
20787
  const portRange = portRangeMeta && typeof portRangeMeta.start === "number" && typeof portRangeMeta.end === "number" ? [portRangeMeta.start, portRangeMeta.end] : void 0;
20580
20788
  return decideStage({
20581
20789
  registry: { deployModel: project2?.deployModel, portRange, error: read.ok ? void 0 : read.error },
20582
- hasCompose: (0, import_node_fs23.existsSync)((0, import_node_path21.join)(process.cwd(), "docker-compose.yml")),
20583
- hasEnvExample: (0, import_node_fs23.existsSync)((0, import_node_path21.join)(process.cwd(), ".env.example"))
20790
+ hasCompose: (0, import_node_fs24.existsSync)((0, import_node_path22.join)(process.cwd(), "docker-compose.yml")),
20791
+ hasEnvExample: (0, import_node_fs24.existsSync)((0, import_node_path22.join)(process.cwd(), ".env.example"))
20584
20792
  });
20585
20793
  }
20586
20794
  async function fetchStageVaultEnvMerge() {
@@ -21012,10 +21220,10 @@ function registerBoardCommands(program3) {
21012
21220
  }
21013
21221
 
21014
21222
  // src/merge-cleanup.ts
21015
- var import_node_fs26 = require("node:fs");
21223
+ var import_node_fs27 = require("node:fs");
21016
21224
  var import_promises6 = require("node:fs/promises");
21017
- var import_node_path25 = require("node:path");
21018
- var import_node_child_process11 = require("node:child_process");
21225
+ var import_node_path26 = require("node:path");
21226
+ var import_node_child_process12 = require("node:child_process");
21019
21227
 
21020
21228
  // src/board-advance.ts
21021
21229
  function repoOf2(ref) {
@@ -21101,7 +21309,7 @@ function boardAdvanceFailureMessage(result) {
21101
21309
 
21102
21310
  // src/deferred-registry-store.ts
21103
21311
  var import_promises5 = require("node:fs/promises");
21104
- var import_node_path22 = require("node:path");
21312
+ var import_node_path23 = require("node:path");
21105
21313
  var sleep2 = (ms) => new Promise((resolve5) => setTimeout(resolve5, ms));
21106
21314
  async function atomicWrite(target, contents) {
21107
21315
  const tmp = `${target}.tmp-${process.pid}-${Math.random().toString(36).slice(2)}`;
@@ -21152,12 +21360,12 @@ function makeDeferredWorktreeStore(registryPath, lockOpts = {}) {
21152
21360
  },
21153
21361
  // Standalone atomic write — THROWS on failure (no best-effort swallow, #2846).
21154
21362
  write: async (entries) => {
21155
- await (0, import_promises5.mkdir)((0, import_node_path22.dirname)(registryPath), { recursive: true });
21363
+ await (0, import_promises5.mkdir)((0, import_node_path23.dirname)(registryPath), { recursive: true });
21156
21364
  await atomicWrite(registryPath, serializeDeferredWorktrees(entries));
21157
21365
  },
21158
21366
  // Serialized read-modify-write under the repo-wide lock (#2846).
21159
21367
  update: async (mutate) => {
21160
- await (0, import_promises5.mkdir)((0, import_node_path22.dirname)(registryPath), { recursive: true });
21368
+ await (0, import_promises5.mkdir)((0, import_node_path23.dirname)(registryPath), { recursive: true });
21161
21369
  const deadline = Date.now() + opts.maxWaitMs;
21162
21370
  for (; ; ) {
21163
21371
  const guard = await acquireLock(lockPath, opts, deadline);
@@ -21181,8 +21389,8 @@ function makeDeferredWorktreeStore(registryPath, lockOpts = {}) {
21181
21389
  }
21182
21390
 
21183
21391
  // src/plugin-guard-io.ts
21184
- var import_node_fs24 = require("node:fs");
21185
- var import_node_path23 = require("node:path");
21392
+ var import_node_fs25 = require("node:fs");
21393
+ var import_node_path24 = require("node:path");
21186
21394
  var import_node_os6 = require("node:os");
21187
21395
 
21188
21396
  // src/plugin-guard.ts
@@ -21315,11 +21523,11 @@ function runHostBin(bin, args, opts) {
21315
21523
  }
21316
21524
  var installedPluginsPath2 = (surface = detectSurface(process.env)) => {
21317
21525
  const homeDir = surface === "codex" ? ".codex" : ".claude";
21318
- return (0, import_node_path23.join)((0, import_node_os6.homedir)(), homeDir, "plugins", "installed_plugins.json");
21526
+ return (0, import_node_path24.join)((0, import_node_os6.homedir)(), homeDir, "plugins", "installed_plugins.json");
21319
21527
  };
21320
21528
  function readInstalledPlugins(surface = detectSurface(process.env)) {
21321
21529
  try {
21322
- return JSON.parse((0, import_node_fs24.readFileSync)(installedPluginsPath2(surface), "utf8"));
21530
+ return JSON.parse((0, import_node_fs25.readFileSync)(installedPluginsPath2(surface), "utf8"));
21323
21531
  } catch {
21324
21532
  return null;
21325
21533
  }
@@ -21327,13 +21535,13 @@ function readInstalledPlugins(surface = detectSurface(process.env)) {
21327
21535
  function marketplaceCloneCandidates(surface, home) {
21328
21536
  if (surface === "codex") {
21329
21537
  return [
21330
- (0, import_node_path23.join)(home, ".codex", ".tmp", "marketplaces", "mutmutco"),
21331
- (0, import_node_path23.join)(home, ".codex", "plugins", "marketplaces", "mutmutco")
21538
+ (0, import_node_path24.join)(home, ".codex", ".tmp", "marketplaces", "mutmutco"),
21539
+ (0, import_node_path24.join)(home, ".codex", "plugins", "marketplaces", "mutmutco")
21332
21540
  ];
21333
21541
  }
21334
- return [(0, import_node_path23.join)(home, ".claude", "plugins", "marketplaces", "mutmutco")];
21542
+ return [(0, import_node_path24.join)(home, ".claude", "plugins", "marketplaces", "mutmutco")];
21335
21543
  }
21336
- function marketplaceClonePresent(surface, home, exists = import_node_fs24.existsSync) {
21544
+ function marketplaceClonePresent(surface, home, exists = import_node_fs25.existsSync) {
21337
21545
  return marketplaceCloneCandidates(surface, home).some(exists);
21338
21546
  }
21339
21547
  async function fetchNpmReleasedVersion() {
@@ -21361,7 +21569,7 @@ function snapshotPluginGuardInput(surface = detectSurface(process.env), isOrgRep
21361
21569
  isOrgRepo,
21362
21570
  installRecordPresent: hasUserInstallRecord(installed, MMI_PLUGIN_ID) || hasProjectInstallRecord(installed, MMI_PLUGIN_ID, process.cwd()),
21363
21571
  marketplaceClonePresent: marketplaceClonePresent(surface, (0, import_node_os6.homedir)()),
21364
- pluginCachePresent: (0, import_node_fs24.existsSync)((0, import_node_path23.join)((0, import_node_os6.homedir)(), homeDir, "plugins", "cache", "mutmutco", "mmi"))
21572
+ pluginCachePresent: (0, import_node_fs25.existsSync)((0, import_node_path24.join)((0, import_node_os6.homedir)(), homeDir, "plugins", "cache", "mutmutco", "mmi"))
21365
21573
  };
21366
21574
  }
21367
21575
  function claudePluginGuardState(isOrgRepo) {
@@ -21407,7 +21615,7 @@ async function applyPluginHeal(surface, log, opts) {
21407
21615
  const refSupported = await marketplaceAddRefSupported("claude");
21408
21616
  const { steps } = adaptHealStepsForRefSupport(tableSteps, refSupported);
21409
21617
  log(healBannerLine("claude", "claude", refSupported));
21410
- const pinsPath = (0, import_node_path23.join)((0, import_node_os6.homedir)(), ...KNOWN_MARKETPLACES_RELATIVE);
21618
+ const pinsPath = (0, import_node_path24.join)((0, import_node_os6.homedir)(), ...KNOWN_MARKETPLACES_RELATIVE);
21411
21619
  const pins = captureMarketplacePins(readKnownMarketplacesFile(pinsPath), [MMI_MARKETPLACE_NAME, JERV_MARKETPLACE_NAME]);
21412
21620
  for (const step of steps) {
21413
21621
  if (healStepAborts(step, await runClaudePlugin([...step.args]))) return false;
@@ -21430,7 +21638,7 @@ async function healClaudePluginForDoctor(surface = detectSurface(process.env)) {
21430
21638
  }
21431
21639
  function readKnownMarketplacesFile(path2) {
21432
21640
  try {
21433
- return (0, import_node_fs24.existsSync)(path2) ? (0, import_node_fs24.readFileSync)(path2, "utf8") : void 0;
21641
+ return (0, import_node_fs25.existsSync)(path2) ? (0, import_node_fs25.readFileSync)(path2, "utf8") : void 0;
21434
21642
  } catch {
21435
21643
  return void 0;
21436
21644
  }
@@ -21441,7 +21649,7 @@ function restoreMarketplacePinsOnDisk(path2, pins) {
21441
21649
  const next = restoreMarketplacePins(after, pins);
21442
21650
  if (next === null) return void 0;
21443
21651
  try {
21444
- (0, import_node_fs24.writeFileSync)(path2, next, "utf8");
21652
+ (0, import_node_fs25.writeFileSync)(path2, next, "utf8");
21445
21653
  } catch {
21446
21654
  return `could NOT restore ${[...pins.keys()].join(", ")} \u2014 re-pin by hand`;
21447
21655
  }
@@ -21488,9 +21696,9 @@ async function runPluginHeal(surface = detectSurface(process.env)) {
21488
21696
  }
21489
21697
 
21490
21698
  // src/worktree-ownership.ts
21491
- var import_node_fs25 = require("node:fs");
21699
+ var import_node_fs26 = require("node:fs");
21492
21700
  var import_node_os7 = require("node:os");
21493
- var import_node_path24 = require("node:path");
21701
+ var import_node_path25 = require("node:path");
21494
21702
  var OWNERS_FILE = "worktree-owners.json";
21495
21703
  var EVENTS_FILE = "worktree-events.jsonl";
21496
21704
  var WORKTREE_ACTIVITY_WINDOW_MS = 30 * 6e4;
@@ -21587,7 +21795,7 @@ function describeActorShort(actor) {
21587
21795
  }
21588
21796
  function readOwners(primaryRoot) {
21589
21797
  try {
21590
- return parseWorktreeOwners((0, import_node_fs25.readFileSync)(worktreeOwnersPath(primaryRoot), "utf8"));
21798
+ return parseWorktreeOwners((0, import_node_fs26.readFileSync)(worktreeOwnersPath(primaryRoot), "utf8"));
21591
21799
  } catch {
21592
21800
  return [];
21593
21801
  }
@@ -21595,8 +21803,8 @@ function readOwners(primaryRoot) {
21595
21803
  function writeOwners(primaryRoot, entries) {
21596
21804
  try {
21597
21805
  const path2 = worktreeOwnersPath(primaryRoot);
21598
- (0, import_node_fs25.mkdirSync)((0, import_node_path24.dirname)(path2), { recursive: true });
21599
- (0, import_node_fs25.writeFileSync)(path2, serializeWorktreeOwners(entries), "utf8");
21806
+ (0, import_node_fs26.mkdirSync)((0, import_node_path25.dirname)(path2), { recursive: true });
21807
+ (0, import_node_fs26.writeFileSync)(path2, serializeWorktreeOwners(entries), "utf8");
21600
21808
  } catch {
21601
21809
  }
21602
21810
  }
@@ -21629,8 +21837,8 @@ function dropWorktreeOwner(primaryRoot, path2) {
21629
21837
  function appendWorktreeEvent(primaryRoot, event) {
21630
21838
  try {
21631
21839
  const path2 = worktreeEventsPath(primaryRoot);
21632
- (0, import_node_fs25.mkdirSync)((0, import_node_path24.dirname)(path2), { recursive: true });
21633
- (0, import_node_fs25.appendFileSync)(path2, `${JSON.stringify({ at: event.at ?? (/* @__PURE__ */ new Date()).toISOString(), ...event })}
21840
+ (0, import_node_fs26.mkdirSync)((0, import_node_path25.dirname)(path2), { recursive: true });
21841
+ (0, import_node_fs26.appendFileSync)(path2, `${JSON.stringify({ at: event.at ?? (/* @__PURE__ */ new Date()).toISOString(), ...event })}
21634
21842
  `, "utf8");
21635
21843
  } catch {
21636
21844
  }
@@ -21638,7 +21846,7 @@ function appendWorktreeEvent(primaryRoot, event) {
21638
21846
  function readWorktreeEvents(primaryRoot, limit = 50) {
21639
21847
  let text;
21640
21848
  try {
21641
- text = (0, import_node_fs25.readFileSync)(worktreeEventsPath(primaryRoot), "utf8");
21849
+ text = (0, import_node_fs26.readFileSync)(worktreeEventsPath(primaryRoot), "utf8");
21642
21850
  } catch {
21643
21851
  return [];
21644
21852
  }
@@ -21715,7 +21923,18 @@ function recordGcWorktreeRemoval(primaryRoot, actor, owners, path2, branch) {
21715
21923
  });
21716
21924
  dropWorktreeOwner(primaryRoot, path2);
21717
21925
  }
21718
- function renderGcApplyResult(result) {
21926
+ function renderPrLandCleanupLines(cleanup) {
21927
+ const report = cleanup;
21928
+ const wt = report?.worktree;
21929
+ if (!wt?.path) return [];
21930
+ const lines = [];
21931
+ if (wt.status === "removed") lines.push(`pr land: removed worktree ${wt.path} \u2014 that directory is gone; cd elsewhere before your next command`);
21932
+ else if (wt.status === "deferred") lines.push(`pr land: worktree ${wt.path} could not be removed yet and is registered for a later sweep`);
21933
+ else lines.push(`pr land: worktree ${wt.path} \u2014 ${wt.status ?? "unknown"}${wt.reason ? ` (${wt.reason})` : ""}`);
21934
+ if (wt.deferredNote) lines.push(`pr land: ${wt.deferredNote}`);
21935
+ return lines;
21936
+ }
21937
+ function renderGcApplyResult(result, skipped = []) {
21719
21938
  const lines = ["worktree gc apply result:"];
21720
21939
  lines.push(` branches removed: ${result.removedBranches.length ? result.removedBranches.join(", ") : "none"}`);
21721
21940
  lines.push(` remote branches removed: ${result.removedRemoteBranches.length ? result.removedRemoteBranches.join(", ") : "none"}`);
@@ -21732,9 +21951,15 @@ function renderGcApplyResult(result) {
21732
21951
  }
21733
21952
  const removedNothing = result.removedBranches.length === 0 && result.removedRemoteBranches.length === 0 && result.removedTrackingRefs.length === 0 && result.removedWorktreeDirs.length === 0;
21734
21953
  if (removedNothing && !result.refused.length) {
21735
- lines.push(" NOTHING WAS REMOVED. A stale worktree whose directory still exists is not reachable by");
21736
- lines.push(" this verb \u2014 remove it from inside it with `mmi-cli worktree land --apply`, or delete the");
21737
- lines.push(" directory and re-run to clear its registration.");
21954
+ const actionable = skipped.filter((s) => s.reason !== "protected" && s.reason !== "current-branch");
21955
+ if (actionable.length) {
21956
+ lines.push(` NOTHING WAS REMOVED \u2014 every candidate was skipped for a stated reason (${actionable.length}):`);
21957
+ for (const s of actionable) lines.push(` - ${s.branch}: ${s.reason}${s.detail ? ` (${s.detail})` : ""}`);
21958
+ } else {
21959
+ lines.push(" NOTHING WAS REMOVED. A stale worktree whose directory still exists is not reachable by");
21960
+ lines.push(" this verb \u2014 remove it from inside it with `mmi-cli worktree land --apply`, or delete the");
21961
+ lines.push(" directory and re-run to clear its registration.");
21962
+ }
21738
21963
  }
21739
21964
  return lines.join("\n");
21740
21965
  }
@@ -21788,7 +22013,7 @@ async function applyGcPlan(plan, remote, opts = {}) {
21788
22013
  );
21789
22014
  const repoRoot2 = (await execFileP2("git", ["rev-parse", "--show-toplevel"], { timeout: GIT_TIMEOUT_MS }).catch(() => ({ stdout: "" }))).stdout.trim() || process.cwd();
21790
22015
  const worktreeGitRoot = await currentRepoWorktreeGitRoot(repoRoot2);
21791
- const primaryRepoRoot = worktreeGitRoot ? (0, import_node_path25.dirname)((0, import_node_path25.dirname)(worktreeGitRoot)) : repoRoot2;
22016
+ const primaryRepoRoot = worktreeGitRoot ? (0, import_node_path26.dirname)((0, import_node_path26.dirname)(worktreeGitRoot)) : repoRoot2;
21792
22017
  const gcActor = describeActor({ env: process.env, surface: detectSurface(process.env), cwd: process.cwd() });
21793
22018
  const owners = readWorktreeOwners(primaryRepoRoot);
21794
22019
  const removalNow = Date.now();
@@ -21819,7 +22044,7 @@ async function applyGcPlan(plan, remote, opts = {}) {
21819
22044
  const cleanup = await cleanupPrMergeLocalBranch(branch.branch, {
21820
22045
  beforeWorktrees,
21821
22046
  startingPath: branch.worktreePath,
21822
- pathExists: (p) => (0, import_node_fs26.existsSync)(p),
22047
+ pathExists: (p) => (0, import_node_fs27.existsSync)(p),
21823
22048
  execGit: async (args) => (await execFileP2("git", args, { timeout: GIT_TIMEOUT_MS })).stdout,
21824
22049
  teardownWorktreeStage,
21825
22050
  deferredStore,
@@ -21847,7 +22072,7 @@ async function applyGcPlan(plan, remote, opts = {}) {
21847
22072
  for (const wt of worktreeDirsToRemove) {
21848
22073
  try {
21849
22074
  const cleanupTarget = resolveSafeSiblingWorktreeCleanupTarget(wt.path, siblingRoot, {
21850
- realpath: (path2) => (0, import_node_fs26.realpathSync)(path2)
22075
+ realpath: (path2) => (0, import_node_fs27.realpathSync)(path2)
21851
22076
  });
21852
22077
  if (!cleanupTarget.ok) {
21853
22078
  result.failed.push(`${wt.path}: ${cleanupTarget.reason}`);
@@ -21998,7 +22223,7 @@ async function remoteBranchExists2(branch, options = {}) {
21998
22223
  }
21999
22224
  var COMPOSE_TIMEOUT_MS = 12e4;
22000
22225
  function spawnDeferredGcSweep() {
22001
- spawnDetachedSelf(["worktree", "gc", "sweep-deferred", "--quiet"], { spawn: import_node_child_process11.spawn, execPath: process.execPath, scriptPath: process.argv[1] });
22226
+ spawnDetachedSelf(["worktree", "gc", "sweep-deferred", "--quiet"], { spawn: import_node_child_process12.spawn, execPath: process.execPath, scriptPath: process.argv[1] });
22002
22227
  }
22003
22228
  async function createDeferredWorktreeStore() {
22004
22229
  try {
@@ -22012,13 +22237,13 @@ var realWorktreeDirRemover = {
22012
22237
  probe: (p) => {
22013
22238
  let st;
22014
22239
  try {
22015
- st = (0, import_node_fs26.lstatSync)(p);
22240
+ st = (0, import_node_fs27.lstatSync)(p);
22016
22241
  } catch {
22017
22242
  return null;
22018
22243
  }
22019
22244
  if (st.isSymbolicLink()) return "link";
22020
22245
  try {
22021
- (0, import_node_fs26.readlinkSync)(p);
22246
+ (0, import_node_fs27.readlinkSync)(p);
22022
22247
  return "link";
22023
22248
  } catch {
22024
22249
  }
@@ -22026,7 +22251,7 @@ var realWorktreeDirRemover = {
22026
22251
  },
22027
22252
  readdir: (p) => {
22028
22253
  try {
22029
- return (0, import_node_fs26.readdirSync)(p);
22254
+ return (0, import_node_fs27.readdirSync)(p);
22030
22255
  } catch {
22031
22256
  return [];
22032
22257
  }
@@ -22035,9 +22260,9 @@ var realWorktreeDirRemover = {
22035
22260
  // leaving the target); a file symlink with unlink. rmdir first, fall back to unlink.
22036
22261
  detachLink: (p) => {
22037
22262
  try {
22038
- (0, import_node_fs26.rmdirSync)(p);
22263
+ (0, import_node_fs27.rmdirSync)(p);
22039
22264
  } catch {
22040
- (0, import_node_fs26.unlinkSync)(p);
22265
+ (0, import_node_fs27.unlinkSync)(p);
22041
22266
  }
22042
22267
  },
22043
22268
  removeTree: (p) => (0, import_promises6.rm)(p, { recursive: true, force: true, maxRetries: 5, retryDelay: 200 })
@@ -22070,9 +22295,9 @@ async function worktreeHasStageState(worktreePath) {
22070
22295
  }
22071
22296
  }
22072
22297
  function stageStateFileBelongsToWorktree(statePath, worktreePath) {
22073
- if (!(0, import_node_fs26.existsSync)(statePath)) return false;
22298
+ if (!(0, import_node_fs27.existsSync)(statePath)) return false;
22074
22299
  try {
22075
- const state = JSON.parse((0, import_node_fs26.readFileSync)(statePath, "utf8"));
22300
+ const state = JSON.parse((0, import_node_fs27.readFileSync)(statePath, "utf8"));
22076
22301
  const recordedCwd = typeof state.identity?.cwd === "string" ? state.identity.cwd : typeof state.cwd === "string" ? state.cwd : "";
22077
22302
  return Boolean(recordedCwd && isPathUnderDirectory(recordedCwd, worktreePath));
22078
22303
  } catch {
@@ -22279,8 +22504,8 @@ async function fetchRestCorePool(gh = defaultGhApi) {
22279
22504
  }
22280
22505
 
22281
22506
  // src/worktree-lifecycle-commands.ts
22282
- var import_node_fs27 = require("node:fs");
22283
- var import_node_path26 = require("node:path");
22507
+ var import_node_fs28 = require("node:fs");
22508
+ var import_node_path27 = require("node:path");
22284
22509
  var GH_TIMEOUT_MS = 2e4;
22285
22510
  var DEFAULT_BASE = "origin/development";
22286
22511
  var DEFAULT_REMOTE = "origin";
@@ -22383,7 +22608,7 @@ function classifyStaleLeaks(input) {
22383
22608
  var defaultOrphanDirScanDeps = {
22384
22609
  listDirs: (root) => {
22385
22610
  try {
22386
- return (0, import_node_fs27.readdirSync)(root, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => (0, import_node_path26.join)(root, e.name));
22611
+ return (0, import_node_fs28.readdirSync)(root, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => (0, import_node_path27.join)(root, e.name));
22387
22612
  } catch {
22388
22613
  return [];
22389
22614
  }
@@ -22530,13 +22755,13 @@ function registerWorktreeCommands(program3) {
22530
22755
  if (PROTECTED_BRANCHES2.has(branch)) {
22531
22756
  return fail(`worktree land: refusing to land protected branch '${branch}'`, { code: ERROR_CODES.ERR_BAD_ENUM });
22532
22757
  }
22533
- const gitFile = (0, import_node_path26.join)(wtPath, ".git");
22534
- const isLinked = (0, import_node_fs27.existsSync)(gitFile) && (0, import_node_fs27.statSync)(gitFile).isFile();
22758
+ const gitFile = (0, import_node_path27.join)(wtPath, ".git");
22759
+ const isLinked = (0, import_node_fs28.existsSync)(gitFile) && (0, import_node_fs28.statSync)(gitFile).isFile();
22535
22760
  if (apply && !isLinked) {
22536
22761
  return fail("worktree land: run from inside the linked worktree you want to land (this is the primary checkout)");
22537
22762
  }
22538
22763
  const commonDir = (await execFileP2("git", ["rev-parse", "--git-common-dir"], { timeout: GIT_TIMEOUT_MS }).catch(() => ({ stdout: "" }))).stdout.trim();
22539
- const primaryCheckout = commonDir ? (0, import_node_path26.dirname)(commonDir) : wtPath;
22764
+ const primaryCheckout = commonDir ? (0, import_node_path27.dirname)(commonDir) : wtPath;
22540
22765
  const stageSummary = readStageSummary(wtPath);
22541
22766
  const hasStage = stageSummary != null;
22542
22767
  const steps = landSteps(branch, true, hasStage);
@@ -22688,10 +22913,10 @@ async function gatherWorktreeContext() {
22688
22913
  if (s) stages.push({ path: wt.path, port: s.port });
22689
22914
  }
22690
22915
  const worktreeGitRoot = await currentRepoWorktreeGitRoot(repoRoot2);
22691
- const primaryRepoRoot = worktreeGitRoot ? (0, import_node_path26.dirname)((0, import_node_path26.dirname)(worktreeGitRoot)) : repoRoot2;
22916
+ const primaryRepoRoot = worktreeGitRoot ? (0, import_node_path27.dirname)((0, import_node_path27.dirname)(worktreeGitRoot)) : repoRoot2;
22692
22917
  const wtRoot = siblingMmiWorktreesRoot(primaryRepoRoot);
22693
22918
  let orphanDirs = [];
22694
- if ((0, import_node_fs27.existsSync)(wtRoot)) {
22919
+ if ((0, import_node_fs28.existsSync)(wtRoot)) {
22695
22920
  orphanDirs = scanOrphanDirs(wtRoot, worktreeGitRoot, {
22696
22921
  ...defaultOrphanDirScanDeps,
22697
22922
  listDirs: (root) => worktreeScanDirs(root, primaryRepoRoot, defaultOrphanDirScanDeps.listDirs, isRepoCheckoutDir)
@@ -22714,7 +22939,7 @@ async function bestEffortGit(args, cwd, step, timeoutMs = GIT_TIMEOUT_MS) {
22714
22939
  }
22715
22940
 
22716
22941
  // src/issue-commands.ts
22717
- var import_node_fs28 = require("node:fs");
22942
+ var import_node_fs29 = require("node:fs");
22718
22943
  var import_node_crypto5 = require("node:crypto");
22719
22944
  var ghRunner = async (args, timeoutMs) => (await execFileP2("gh", args, { timeout: timeoutMs })).stdout;
22720
22945
  async function editIssue(client, options, deps = {}) {
@@ -22724,11 +22949,16 @@ async function editIssue(client, options, deps = {}) {
22724
22949
  const url = `https://github.com/${repo}/issues/${parsed.number}`;
22725
22950
  const patch = {};
22726
22951
  let bodyChanged = false;
22727
- if (options.title !== void 0) patch.title = options.title;
22952
+ const textDeps = () => deps.textDeps ?? { readFile: (p, e) => Promise.resolve((0, import_node_fs29.readFileSync)(p, e)), readStdin: () => Promise.resolve("") };
22953
+ if (options.titleFile !== void 0) {
22954
+ patch.title = await resolveIssueTitle({ title: options.title, titleFile: options.titleFile }, textDeps());
22955
+ } else if (options.title !== void 0) {
22956
+ patch.title = options.title;
22957
+ }
22728
22958
  if (options.body !== void 0 || options.bodyFile !== void 0) {
22729
22959
  let body;
22730
22960
  if (options.bodyFile) {
22731
- const td = deps.textDeps ?? { readFile: (p, e) => Promise.resolve((0, import_node_fs28.readFileSync)(p, e)), readStdin: () => Promise.resolve("") };
22961
+ const td = textDeps();
22732
22962
  body = await resolveIssueBody({ body: options.body, bodyFile: options.bodyFile }, td);
22733
22963
  } else {
22734
22964
  body = options.body ?? "";
@@ -23093,7 +23323,7 @@ function registerIssueLifecycleCommands(program3, deps = {}) {
23093
23323
  const issue2 = program3.commands.find((c) => c.name() === "issue");
23094
23324
  if (!issue2) return;
23095
23325
  mutating(
23096
- issue2.command("edit <ref>").description("edit an issue title/body/labels or reparent it (--parent) \u2014 reparenting is impossible via raw gh").option("--title <title>", "new title").option("--body <body>", "new body (markdown)").option("--body-file <path|->", "read new body from a UTF-8 file, or stdin with -").option("--add-label <label...>", "label(s) to add (repeatable)").option("--remove-label <label...>", "label(s) to remove (repeatable)").option("--parent <ref>", "reparent: link under this parent (#123, owner/repo#123, or URL)").option("--repo <owner/repo>", "repo for a bare ref (defaults to the current repo)"),
23326
+ issue2.command("edit <ref>").description("edit an issue title/body/labels or reparent it (--parent) \u2014 reparenting is impossible via raw gh").option("--title <title>", "new title").option("--title-file <path|->", "read the new title from a UTF-8 file, or stdin with - (a title with backticks or newlines needs this, #3381)").option("--body <body>", "new body (markdown)").option("--body-file <path|->", "read new body from a UTF-8 file, or stdin with -").option("--add-label <label...>", "label(s) to add (repeatable)").option("--remove-label <label...>", "label(s) to remove (repeatable)").option("--parent <ref>", "reparent: link under this parent (#123, owner/repo#123, or URL)").option("--repo <owner/repo>", "repo for a bare ref (defaults to the current repo)"),
23097
23327
  (opts, args) => ({ command: "issue edit", ref: args[0], fields: Object.keys(opts).filter((k) => k !== "repo" && opts[k] !== void 0) })
23098
23328
  ).action(async (ref, o) => {
23099
23329
  try {
@@ -23102,6 +23332,7 @@ function registerIssueLifecycleCommands(program3, deps = {}) {
23102
23332
  ref,
23103
23333
  defaultRepo,
23104
23334
  title: o.title,
23335
+ titleFile: o.titleFile,
23105
23336
  body: o.body,
23106
23337
  bodyFile: o.bodyFile,
23107
23338
  addLabel: o.addLabel,
@@ -23225,7 +23456,7 @@ function extendCreateCommand(issue2, batchAttach) {
23225
23456
  if (opts.batch) {
23226
23457
  let specs;
23227
23458
  try {
23228
- const raw = (0, import_node_fs28.readFileSync)(opts.batch, "utf8");
23459
+ const raw = (0, import_node_fs29.readFileSync)(opts.batch, "utf8");
23229
23460
  specs = JSON.parse(raw);
23230
23461
  if (!Array.isArray(specs)) throw new Error("batch file must contain a JSON array");
23231
23462
  } catch (e) {
@@ -23283,8 +23514,8 @@ ${lines}`);
23283
23514
  }
23284
23515
 
23285
23516
  // src/train-commands.ts
23286
- var import_node_fs29 = require("node:fs");
23287
- var import_node_path27 = require("node:path");
23517
+ var import_node_fs30 = require("node:fs");
23518
+ var import_node_path28 = require("node:path");
23288
23519
 
23289
23520
  // src/train-status.ts
23290
23521
  function buildTrainStatusReport(input) {
@@ -23324,7 +23555,7 @@ function formatTrainStatus(r) {
23324
23555
  // src/train-commands.ts
23325
23556
  function readRepoVersion() {
23326
23557
  try {
23327
- return JSON.parse((0, import_node_fs29.readFileSync)((0, import_node_path27.join)(process.cwd(), ".claude-plugin", "plugin.json"), "utf8")).version || void 0;
23558
+ return JSON.parse((0, import_node_fs30.readFileSync)((0, import_node_path28.join)(process.cwd(), ".claude-plugin", "plugin.json"), "utf8")).version || void 0;
23328
23559
  } catch {
23329
23560
  return void 0;
23330
23561
  }
@@ -23470,9 +23701,9 @@ function registerDeployCommands(program3) {
23470
23701
  }
23471
23702
 
23472
23703
  // src/discovery-commands.ts
23473
- var import_node_fs30 = require("node:fs");
23704
+ var import_node_fs31 = require("node:fs");
23474
23705
  var import_node_os8 = require("node:os");
23475
- var import_node_path28 = require("node:path");
23706
+ var import_node_path29 = require("node:path");
23476
23707
  var GC_GH_TIMEOUT_MS3 = 2e4;
23477
23708
  async function collectStatus() {
23478
23709
  let branch = "";
@@ -23649,8 +23880,8 @@ async function collectOnboardStatus() {
23649
23880
  }
23650
23881
  const home = (0, import_node_os8.homedir)();
23651
23882
  const plugin = onboardPluginGate({
23652
- readKnown: () => readFileSyncSafe((0, import_node_path28.join)(home, ...KNOWN_MARKETPLACES_RELATIVE), import_node_fs30.readFileSync),
23653
- readSettings: () => readFileSyncSafe((0, import_node_path28.join)(home, ".claude", "settings.json"), import_node_fs30.readFileSync)
23883
+ readKnown: () => readFileSyncSafe((0, import_node_path29.join)(home, ...KNOWN_MARKETPLACES_RELATIVE), import_node_fs31.readFileSync),
23884
+ readSettings: () => readFileSyncSafe((0, import_node_path29.join)(home, ".claude", "settings.json"), import_node_fs31.readFileSync)
23654
23885
  });
23655
23886
  return { track, board, registry: registry2, secrets, plugin, nextCommand };
23656
23887
  }
@@ -25010,17 +25241,17 @@ function parseOriginRepo(remoteUrl) {
25010
25241
  }
25011
25242
  function ghHostsConfigPath(env, platform2) {
25012
25243
  const sep2 = platform2 === "win32" ? "\\" : "/";
25013
- const join25 = (...parts) => parts.join(sep2);
25244
+ const join26 = (...parts) => parts.join(sep2);
25014
25245
  const explicit = env.GH_CONFIG_DIR?.trim();
25015
- if (explicit) return join25(explicit, "hosts.yml");
25246
+ if (explicit) return join26(explicit, "hosts.yml");
25016
25247
  if (platform2 === "win32") {
25017
25248
  const appData = (env.AppData ?? env.APPDATA)?.trim();
25018
- return appData ? join25(appData, "GitHub CLI", "hosts.yml") : void 0;
25249
+ return appData ? join26(appData, "GitHub CLI", "hosts.yml") : void 0;
25019
25250
  }
25020
25251
  const xdg = env.XDG_CONFIG_HOME?.trim();
25021
- if (xdg) return join25(xdg, "gh", "hosts.yml");
25252
+ if (xdg) return join26(xdg, "gh", "hosts.yml");
25022
25253
  const home = env.HOME?.trim();
25023
- return home ? join25(home, ".config", "gh", "hosts.yml") : void 0;
25254
+ return home ? join26(home, ".config", "gh", "hosts.yml") : void 0;
25024
25255
  }
25025
25256
  function parseGhHostsAccounts(yaml, host = "github.com") {
25026
25257
  let hostIndent = null;
@@ -25070,17 +25301,17 @@ function ghAccountCaveat(announcedLogin, accounts) {
25070
25301
  }
25071
25302
 
25072
25303
  // src/doctor-io.ts
25073
- var import_node_fs31 = require("node:fs");
25304
+ var import_node_fs32 = require("node:fs");
25074
25305
  var import_node_os9 = require("node:os");
25075
- var import_node_path29 = require("node:path");
25076
- var import_node_child_process12 = require("node:child_process");
25306
+ var import_node_path30 = require("node:path");
25307
+ var import_node_child_process13 = require("node:child_process");
25077
25308
  var import_node_util8 = require("node:util");
25078
- var execFileP6 = (0, import_node_util8.promisify)(import_node_child_process12.execFile);
25309
+ var execFileP6 = (0, import_node_util8.promisify)(import_node_child_process13.execFile);
25079
25310
  var MMI_PLUGIN_ID2 = "mmi@mutmutco";
25080
25311
  function installedClaudePluginVersion() {
25081
25312
  try {
25082
25313
  const file = JSON.parse(
25083
- (0, import_node_fs31.readFileSync)((0, import_node_path29.join)((0, import_node_os9.homedir)(), ".claude", "plugins", "installed_plugins.json"), "utf8")
25314
+ (0, import_node_fs32.readFileSync)((0, import_node_path30.join)((0, import_node_os9.homedir)(), ".claude", "plugins", "installed_plugins.json"), "utf8")
25084
25315
  );
25085
25316
  const versions = (file.plugins?.[MMI_PLUGIN_ID2] ?? []).map((r) => r.version).filter((v) => Boolean(v));
25086
25317
  if (versions.length === 0) return void 0;
@@ -25091,7 +25322,7 @@ function installedClaudePluginVersion() {
25091
25322
  }
25092
25323
  function worktreeRootSync() {
25093
25324
  try {
25094
- const out = (0, import_node_child_process12.execFileSync)("git", ["rev-parse", "--show-toplevel"], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
25325
+ const out = (0, import_node_child_process13.execFileSync)("git", ["rev-parse", "--show-toplevel"], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
25095
25326
  let root = out.endsWith("\n") ? out.slice(0, -1) : out;
25096
25327
  if (process.platform === "win32" && root.endsWith("\r")) root = root.slice(0, -1);
25097
25328
  return root || null;
@@ -25101,13 +25332,13 @@ function worktreeRootSync() {
25101
25332
  }
25102
25333
  var gitignorePath = () => {
25103
25334
  const root = worktreeRootSync();
25104
- return root === null ? null : (0, import_node_path29.join)(root, ".gitignore");
25335
+ return root === null ? null : (0, import_node_path30.join)(root, ".gitignore");
25105
25336
  };
25106
25337
  function readGitignore() {
25107
25338
  const path2 = gitignorePath();
25108
25339
  if (path2 === null) return null;
25109
25340
  try {
25110
- return (0, import_node_fs31.readFileSync)(path2, "utf8");
25341
+ return (0, import_node_fs32.readFileSync)(path2, "utf8");
25111
25342
  } catch {
25112
25343
  return null;
25113
25344
  }
@@ -25116,7 +25347,7 @@ function writeGitignore(content) {
25116
25347
  const path2 = gitignorePath();
25117
25348
  if (path2 === null) return false;
25118
25349
  try {
25119
- (0, import_node_fs31.writeFileSync)(path2, content, "utf8");
25350
+ (0, import_node_fs32.writeFileSync)(path2, content, "utf8");
25120
25351
  return true;
25121
25352
  } catch {
25122
25353
  return false;
@@ -25140,7 +25371,7 @@ async function repoRoot() {
25140
25371
  }
25141
25372
  function hasRepoLocalWorktrees() {
25142
25373
  const root = worktreeRootSync();
25143
- return root !== null && (0, import_node_fs31.existsSync)((0, import_node_path29.join)(root, ".worktrees"));
25374
+ return root !== null && (0, import_node_fs32.existsSync)((0, import_node_path30.join)(root, ".worktrees"));
25144
25375
  }
25145
25376
 
25146
25377
  // src/index.ts
@@ -25175,8 +25406,8 @@ ${r.stderr ?? ""}`).catch(() => "");
25175
25406
  function ghMultiAccountCaveat(announcedLogin) {
25176
25407
  try {
25177
25408
  const hostsPath = ghHostsConfigPath(process.env, process.platform);
25178
- if (!hostsPath || !(0, import_node_fs32.existsSync)(hostsPath)) return void 0;
25179
- return ghAccountCaveat(announcedLogin, parseGhHostsAccounts((0, import_node_fs32.readFileSync)(hostsPath, "utf8")));
25409
+ if (!hostsPath || !(0, import_node_fs33.existsSync)(hostsPath)) return void 0;
25410
+ return ghAccountCaveat(announcedLogin, parseGhHostsAccounts((0, import_node_fs33.readFileSync)(hostsPath, "utf8")));
25180
25411
  } catch {
25181
25412
  return void 0;
25182
25413
  }
@@ -25184,7 +25415,7 @@ function ghMultiAccountCaveat(announcedLogin) {
25184
25415
  var ENV_HEAL_LOCK_STALE_MS = 10 * 6e4;
25185
25416
  var ENV_HEAL_LOCK_MAX_WAIT_MS = 2 * 6e4;
25186
25417
  function envHealLockPath(home) {
25187
- return (0, import_node_path30.join)(home, ".claude", "plugins", ".mmi-env-heal.lock");
25418
+ return (0, import_node_path31.join)(home, ".claude", "plugins", ".mmi-env-heal.lock");
25188
25419
  }
25189
25420
  async function withEnvHealLock(what, run) {
25190
25421
  try {
@@ -25335,8 +25566,8 @@ function mmiDoctorDeps(opts = {}) {
25335
25566
  const home = (0, import_node_os10.homedir)();
25336
25567
  return marketplaceRows(
25337
25568
  MMI_MARKETPLACE_NAME,
25338
- readFileSyncSafe((0, import_node_path30.join)(home, ...KNOWN_MARKETPLACES_RELATIVE), import_node_fs32.readFileSync),
25339
- readFileSyncSafe((0, import_node_path30.join)(home, ".claude", "settings.json"), import_node_fs32.readFileSync)
25569
+ readFileSyncSafe((0, import_node_path31.join)(home, ...KNOWN_MARKETPLACES_RELATIVE), import_node_fs33.readFileSync),
25570
+ readFileSyncSafe((0, import_node_path31.join)(home, ".claude", "settings.json"), import_node_fs33.readFileSync)
25340
25571
  );
25341
25572
  } catch {
25342
25573
  return [];
@@ -25370,6 +25601,35 @@ function allLongFlags() {
25370
25601
  cachedLongFlags = [...acc];
25371
25602
  return cachedLongFlags;
25372
25603
  }
25604
+ var cachedCommandPaths;
25605
+ function allCommandPaths() {
25606
+ if (cachedCommandPaths) return cachedCommandPaths;
25607
+ const acc = [];
25608
+ const walk2 = (node, trail) => {
25609
+ if (!isCanonicalSuggestion(node)) return;
25610
+ const here = [...trail, node.name];
25611
+ if (here.length) acc.push(here.join(" "));
25612
+ for (const child2 of node.subcommands) walk2(child2, here);
25613
+ };
25614
+ for (const child2 of buildCommandManifest(program2).tree.subcommands) walk2(child2, []);
25615
+ cachedCommandPaths = acc;
25616
+ return cachedCommandPaths;
25617
+ }
25618
+ function suggestCommandPath(token, paths) {
25619
+ const all = [...paths];
25620
+ const leafOf = (p) => p.slice(p.lastIndexOf(" ") + 1);
25621
+ const exact = all.filter((p) => leafOf(p) === token);
25622
+ if (exact.length === 1) return exact[0];
25623
+ if (exact.length > 1) return void 0;
25624
+ const leaves = /* @__PURE__ */ new Map();
25625
+ for (const p of all) {
25626
+ const leaf = leafOf(p);
25627
+ leaves.set(leaf, [...leaves.get(leaf) ?? [], p]);
25628
+ }
25629
+ const near = didYouMean(token, leaves.keys());
25630
+ const hits = near ? leaves.get(near) ?? [] : [];
25631
+ return hits.length === 1 ? hits[0] : void 0;
25632
+ }
25373
25633
  function argvWantsJson2() {
25374
25634
  return process.argv.some((a) => a === "--json" || a.startsWith("--json="));
25375
25635
  }
@@ -25378,6 +25638,7 @@ var PARSE_HINT_SENTINEL = "@@mmi-parse-hint@@";
25378
25638
  var DISCOVERY_HINT = "run `mmi-cli commands` for the agentic-coding map, `mmi-cli explain <command>` for detail, or `mmi-cli commands --json --primary` for machine discovery";
25379
25639
  var STALE_HINT = `(${DISCOVERY_HINT}). If you expected this command to exist, your installed mmi-cli may be stale \u2014 update it (see: mmi-cli doctor)`;
25380
25640
  var lastParseErrorKind = "other";
25641
+ var lastUnknownCommand;
25381
25642
  function classifyParseError(plain) {
25382
25643
  if (/unknown command/i.test(plain)) return "unknown-command";
25383
25644
  if (/unknown option|missing required argument|too many arguments|argument missing/i.test(plain)) {
@@ -25407,6 +25668,10 @@ function commandPath2(cmd) {
25407
25668
  return parts.join(" ");
25408
25669
  }
25409
25670
  function resolveParseHint() {
25671
+ if (lastParseErrorKind === "unknown-command") {
25672
+ const path3 = lastUnknownCommand ? suggestCommandPath(lastUnknownCommand, allCommandPaths()) : void 0;
25673
+ return path3 ? `(did you mean \`mmi-cli ${path3}\`? ${DISCOVERY_HINT})` : STALE_HINT;
25674
+ }
25410
25675
  if (lastParseErrorKind !== "bad-arguments") return STALE_HINT;
25411
25676
  const cmd = resolveCommandFromArgv(program2, process.argv.slice(2));
25412
25677
  if (!cmd) return `(${DISCOVERY_HINT})`;
@@ -25425,6 +25690,7 @@ function envelopeAwareWriteErr(str) {
25425
25690
  return;
25426
25691
  }
25427
25692
  lastParseErrorKind = classifyParseError(plain);
25693
+ lastUnknownCommand = /unknown command '?([\w:-]+)'?/.exec(plain)?.[1];
25428
25694
  const match = /unknown option '([^']+)'/.exec(plain);
25429
25695
  if (match) {
25430
25696
  const flag = match[1];
@@ -25480,19 +25746,19 @@ program2.hook("preAction", async (_thisCommand, actionCommand) => {
25480
25746
  });
25481
25747
  var rules = program2.command("rules").description("org-managed .gitignore delivery");
25482
25748
  rules.command("gitignore").option("--write", "upsert the managed block into .gitignore (default: check only, non-zero exit on drift)").option("--json", "machine-readable output").description("verify (or --write) this repo's org-managed .gitignore block matches the SSOT").action((opts) => {
25483
- const path2 = (0, import_node_path30.join)(process.cwd(), ".gitignore");
25484
- const current = (0, import_node_fs32.existsSync)(path2) ? (0, import_node_fs32.readFileSync)(path2, "utf8") : null;
25749
+ const path2 = (0, import_node_path31.join)(process.cwd(), ".gitignore");
25750
+ const current = (0, import_node_fs33.existsSync)(path2) ? (0, import_node_fs33.readFileSync)(path2, "utf8") : null;
25485
25751
  const plan = planManagedGitignore(current);
25486
25752
  const drift = [...plan.added.map((l) => `+${l}`), ...plan.removed.map((l) => `-${l}`)].join(", ") || "block normalize";
25487
25753
  if (opts.json) {
25488
- if (opts.write && plan.changed) (0, import_node_fs32.writeFileSync)(path2, plan.content, "utf8");
25754
+ if (opts.write && plan.changed) (0, import_node_fs33.writeFileSync)(path2, plan.content, "utf8");
25489
25755
  console.log(JSON.stringify(plan, null, 2));
25490
25756
  if (!opts.write && plan.changed) process.exitCode = 1;
25491
25757
  return;
25492
25758
  }
25493
25759
  if (opts.write) {
25494
25760
  if (plan.changed) {
25495
- (0, import_node_fs32.writeFileSync)(path2, plan.content, "utf8");
25761
+ (0, import_node_fs33.writeFileSync)(path2, plan.content, "utf8");
25496
25762
  console.log(`mmi-cli org rules gitignore: updated .gitignore (${drift})`);
25497
25763
  } else {
25498
25764
  console.log("mmi-cli org rules gitignore: up to date");
@@ -25618,8 +25884,8 @@ gcCmd.option("--dry-run", "show what would be deleted (default)").option("--appl
25618
25884
  if (!Number.isFinite(limit) || limit < 1) return fail("worktree gc: --limit must be a positive integer");
25619
25885
  let root;
25620
25886
  if (o.root !== void 0) {
25621
- root = (0, import_node_path30.resolve)(o.root);
25622
- if (!(0, import_node_fs32.existsSync)(root) || !(0, import_node_fs32.statSync)(root).isDirectory()) return fail(`worktree gc: --root ${o.root} is not a directory`);
25887
+ root = (0, import_node_path31.resolve)(o.root);
25888
+ if (!(0, import_node_fs33.existsSync)(root) || !(0, import_node_fs33.statSync)(root).isDirectory()) return fail(`worktree gc: --root ${o.root} is not a directory`);
25623
25889
  const gcRepoRoot = (await execFileP2("git", ["rev-parse", "--show-toplevel"], { timeout: GIT_TIMEOUT_MS }).catch(() => ({ stdout: "" }))).stdout.trim() || process.cwd();
25624
25890
  if (isPathUnderDirectory(gcRepoRoot, root)) {
25625
25891
  return fail(`worktree gc: --root ${root} contains this checkout \u2014 name a worktrees root, not the repo or an ancestor of it`);
@@ -25647,7 +25913,7 @@ gcCmd.option("--dry-run", "show what would be deleted (default)").option("--appl
25647
25913
  console.log(formatGcPlan(plan, false));
25648
25914
  } else {
25649
25915
  if (applyResult) console.log(`
25650
- ${renderGcApplyResult(applyResult)}`);
25916
+ ${renderGcApplyResult(applyResult, plan.skipped)}`);
25651
25917
  }
25652
25918
  if (applyResult?.failed.length) process.exitCode = 1;
25653
25919
  } catch (e) {
@@ -25661,7 +25927,7 @@ function runWorktreeInstall(command, cwd, quiet) {
25661
25927
  const file = isWin2 ? "cmd.exe" : bin;
25662
25928
  const spawnArgs = isWin2 ? ["/c", bin, ...args] : args;
25663
25929
  return new Promise((resolve5, reject) => {
25664
- const child2 = (0, import_node_child_process13.spawn)(file, spawnArgs, { cwd, stdio: quiet ? "ignore" : "inherit", windowsHide: true });
25930
+ const child2 = (0, import_node_child_process14.spawn)(file, spawnArgs, { cwd, stdio: quiet ? "ignore" : "inherit", windowsHide: true });
25665
25931
  const timer = setTimeout(() => {
25666
25932
  try {
25667
25933
  child2.kill();
@@ -25684,7 +25950,7 @@ async function primaryCheckoutRoot(from) {
25684
25950
  return primaryCheckoutRootOf(async (args) => (await execFileP2("git", ["-C", from, ...args], { timeout: GIT_TIMEOUT_MS })).stdout);
25685
25951
  }
25686
25952
  async function unprovenWorktreeReason(wtPath, repoRoot2) {
25687
- if (!(0, import_node_fs32.existsSync)(wtPath)) return `${wtPath} does not exist on disk`;
25953
+ if (!(0, import_node_fs33.existsSync)(wtPath)) return `${wtPath} does not exist on disk`;
25688
25954
  const porcelain = (await execFileP2("git", ["-C", repoRoot2, "worktree", "list", "--porcelain"], { timeout: GIT_TIMEOUT_MS }).catch(() => ({ stdout: "" }))).stdout;
25689
25955
  const registered = parseWorktreePorcelainEntries(porcelain);
25690
25956
  if (!registered.length) {
@@ -25705,26 +25971,26 @@ function makeProvisionDeps(worktreeRoot, quiet, log) {
25705
25971
  function acquireWorktreeSetupLock(worktreeRoot) {
25706
25972
  const lockPath = repoRuntimeStatePath(worktreeRoot, "worktree-setup.lock");
25707
25973
  const take = () => {
25708
- const fd = (0, import_node_fs32.openSync)(lockPath, "wx");
25974
+ const fd = (0, import_node_fs33.openSync)(lockPath, "wx");
25709
25975
  try {
25710
- (0, import_node_fs32.writeSync)(fd, String(Date.now()));
25976
+ (0, import_node_fs33.writeSync)(fd, String(Date.now()));
25711
25977
  } finally {
25712
- (0, import_node_fs32.closeSync)(fd);
25978
+ (0, import_node_fs33.closeSync)(fd);
25713
25979
  }
25714
25980
  return () => {
25715
25981
  try {
25716
- (0, import_node_fs32.rmSync)(lockPath, { force: true });
25982
+ (0, import_node_fs33.rmSync)(lockPath, { force: true });
25717
25983
  } catch {
25718
25984
  }
25719
25985
  };
25720
25986
  };
25721
25987
  try {
25722
- (0, import_node_fs32.mkdirSync)((0, import_node_path30.dirname)(lockPath), { recursive: true });
25988
+ (0, import_node_fs33.mkdirSync)((0, import_node_path31.dirname)(lockPath), { recursive: true });
25723
25989
  return take();
25724
25990
  } catch {
25725
25991
  try {
25726
- if (Date.now() - (0, import_node_fs32.statSync)(lockPath).mtimeMs > WORKTREE_SETUP_LOCK_TTL_MS) {
25727
- (0, import_node_fs32.rmSync)(lockPath, { force: true });
25992
+ if (Date.now() - (0, import_node_fs33.statSync)(lockPath).mtimeMs > WORKTREE_SETUP_LOCK_TTL_MS) {
25993
+ (0, import_node_fs33.rmSync)(lockPath, { force: true });
25728
25994
  return take();
25729
25995
  }
25730
25996
  } catch {
@@ -25932,7 +26198,7 @@ function scheduleRelatedDiscovery(o) {
25932
26198
  try {
25933
26199
  const args = ["issue", "discover-related", "--number", String(o.number), "--title", o.title, "--body", o.body, "--fail-soft"];
25934
26200
  if (o.repo) args.push("--repo", o.repo);
25935
- spawnDetachedSelf(args, { spawn: import_node_child_process13.spawn, execPath: process.execPath, scriptPath: process.argv[1] }, { cwd: process.cwd() });
26201
+ spawnDetachedSelf(args, { spawn: import_node_child_process14.spawn, execPath: process.execPath, scriptPath: process.argv[1] }, { cwd: process.cwd() });
25936
26202
  } catch {
25937
26203
  }
25938
26204
  }
@@ -25979,6 +26245,36 @@ docs.command("refs").description("deterministic doc reference gate: every backti
25979
26245
  await failGraceful(e.message);
25980
26246
  }
25981
26247
  });
26248
+ var tests = program2.command("tests").description("a repo's test-policy.json \u2014 the opt-in test contract and its enforcement");
26249
+ tests.command("policy").description("enforce this repo's test-policy.json against the diff: a mandatory-zone change must carry a test, an unrequested new test file is refused, a `protected` test file may not be deleted or renamed away, and a `protected` entry naming a missing file is refused. Override any of them with a `Test-Policy-Override: <reason>` commit trailer (#3605)").option("--json", "machine-readable result: { ok, base, changedCount, findings[] }").option("--base <ref>", "comparison base (default: TEST_POLICY_BASE, then origin/development, then origin/main)").action(async (o) => {
26250
+ try {
26251
+ const root = await repoRoot();
26252
+ const result = runTestPolicy(root, { base: o.base });
26253
+ if (o.json) {
26254
+ consoleIo.log(JSON.stringify(result, null, 2));
26255
+ if (!result.ok) process.exitCode = 1;
26256
+ return;
26257
+ }
26258
+ if (result.base === null && result.ok) {
26259
+ console.log("tests policy: no comparison base (origin/development or origin/main) \u2014 skipping.");
26260
+ return;
26261
+ }
26262
+ if (result.overriddenBy) {
26263
+ console.log(`tests policy: overridden by commit trailer \u2014 ${result.overriddenBy}`);
26264
+ return;
26265
+ }
26266
+ if (result.ok) {
26267
+ console.log(
26268
+ `tests policy: OK (${result.changedCount} changed file(s); ${result.mandatoryCount} mandatory glob(s), ${result.protectedCount} protected file(s)).`
26269
+ );
26270
+ return;
26271
+ }
26272
+ for (const f of result.findings) console.error(`tests policy: ${f.detail}`);
26273
+ process.exitCode = 1;
26274
+ } catch (e) {
26275
+ await failGraceful(e.message);
26276
+ }
26277
+ });
25982
26278
  var docsAudit = program2.command("docs-audit").description("the docs janitor verdict ledger \u2014 record a dated run verdict, or read the dead-man status back");
25983
26279
  docsAudit.command("record").description("write a dated janitor verdict for one repo to the registry ledger (master-only server-side)").option("--repo <owner/name>", "the repo the verdict is for (default: the current repo)").option("--date <YYYY-MM-DD>", "the ISO day the run examined (default: today)").requiredOption("--sha-range <a..b>", "the git range the janitor read (e.g. <lastVerdictSha>..HEAD)").requiredOption("--outcome <kind>", "clean | refreshed | failed").option("--count <n>", "docs refreshed (required when --outcome refreshed)").option("--reason <text>", "why the run failed (required when --outcome failed)").requiredOption("--checker-vendor <vendor>", "which vendor's model actually ran the check").action(async (o) => {
25984
26280
  try {
@@ -26194,7 +26490,7 @@ project.command("set [owner/repo]").description("upsert project META (idempotent
26194
26490
  if (dupe) return fail(`org project set: KEY "${dupe}" was passed to both --var and --set; --set is an alias of --var, so pass each KEY once`);
26195
26491
  if (o.secretsFile) {
26196
26492
  try {
26197
- vars.push(`secrets=${(0, import_node_fs32.readFileSync)(o.secretsFile, "utf8")}`);
26493
+ vars.push(`secrets=${(0, import_node_fs33.readFileSync)(o.secretsFile, "utf8")}`);
26198
26494
  } catch (e) {
26199
26495
  return fail(`org project set: cannot read --secrets-file ${o.secretsFile}: ${e.message}`);
26200
26496
  }
@@ -26850,11 +27146,11 @@ pr.command("view <number>").description("read a PR as structured JSON (merged st
26850
27146
  }
26851
27147
  });
26852
27148
  async function listCiWorkflowPaths(cwd = process.cwd()) {
26853
- const wfDir = (0, import_node_path30.join)(cwd, ".github", "workflows");
26854
- if (!(0, import_node_fs32.existsSync)(wfDir)) return [];
26855
- return (0, import_node_fs32.readdirSync)(wfDir).filter((name) => /\.(ya?ml)$/i.test(name)).filter((name) => {
27149
+ const wfDir = (0, import_node_path31.join)(cwd, ".github", "workflows");
27150
+ if (!(0, import_node_fs33.existsSync)(wfDir)) return [];
27151
+ return (0, import_node_fs33.readdirSync)(wfDir).filter((name) => /\.(ya?ml)$/i.test(name)).filter((name) => {
26856
27152
  try {
26857
- return workflowReportsPrChecks((0, import_node_fs32.readFileSync)((0, import_node_path30.join)(wfDir, name), "utf8"));
27153
+ return workflowReportsPrChecks((0, import_node_fs33.readFileSync)((0, import_node_path31.join)(wfDir, name), "utf8"));
26858
27154
  } catch {
26859
27155
  return true;
26860
27156
  }
@@ -26881,16 +27177,16 @@ function ciAuditDeps() {
26881
27177
  // gate re-seed step is skipped gracefully rather than failing mid-run.
26882
27178
  readSeedFile: (path2) => {
26883
27179
  if (!root) return null;
26884
- const fullPath = (0, import_node_path30.join)(root, path2);
26885
- return (0, import_node_fs32.existsSync)(fullPath) ? (0, import_node_fs32.readFileSync)(fullPath, "utf8") : null;
27180
+ const fullPath = (0, import_node_path31.join)(root, path2);
27181
+ return (0, import_node_fs33.existsSync)(fullPath) ? (0, import_node_fs33.readFileSync)(fullPath, "utf8") : null;
26886
27182
  }
26887
27183
  };
26888
27184
  }
26889
27185
  function hubRoot() {
26890
- const fromPkg = (0, import_node_path30.join)(__dirname, "..", "..");
27186
+ const fromPkg = (0, import_node_path31.join)(__dirname, "..", "..");
26891
27187
  const marker = "skills/bootstrap/seeds/manifest.json";
26892
- if ((0, import_node_fs32.existsSync)((0, import_node_path30.join)(fromPkg, marker))) return fromPkg;
26893
- if ((0, import_node_fs32.existsSync)((0, import_node_path30.join)(process.cwd(), marker))) return process.cwd();
27188
+ if ((0, import_node_fs33.existsSync)((0, import_node_path31.join)(fromPkg, marker))) return fromPkg;
27189
+ if ((0, import_node_fs33.existsSync)((0, import_node_path31.join)(process.cwd(), marker))) return process.cwd();
26894
27190
  return null;
26895
27191
  }
26896
27192
  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) => {
@@ -27037,6 +27333,7 @@ pr.command("land <number>").description("agent merge path (#1440): train probe \
27037
27333
  if (o.json) printLine(JSON.stringify(result));
27038
27334
  else {
27039
27335
  printLine(`pr land: ${result.status}${result.error ? ` \u2014 ${result.error}` : ""}`);
27336
+ for (const line of renderPrLandCleanupLines(result.cleanup)) printLine(line);
27040
27337
  if (result.cleanupError) printLine(`pr land cleanup: ${result.cleanupError}`);
27041
27338
  }
27042
27339
  if (result.status === "failed" || result.cleanupError) process.exitCode = 1;
@@ -27167,7 +27464,7 @@ jsonParity(pr.command("merge <number>").description("merge a PR (squash by defau
27167
27464
  localCleanup = await cleanupPrMergeLocalBranch(headRef, {
27168
27465
  beforeWorktrees,
27169
27466
  startingPath,
27170
- pathExists: (p) => (0, import_node_fs32.existsSync)(p),
27467
+ pathExists: (p) => (0, import_node_fs33.existsSync)(p),
27171
27468
  execGit: async (args) => (await execFileP2("git", args, { timeout: GIT_TIMEOUT_MS })).stdout,
27172
27469
  teardownWorktreeStage,
27173
27470
  deferredStore,
@@ -27508,10 +27805,10 @@ access.command("audit").description("audit collaborator roles + train-branch pus
27508
27805
  targets = resolution.targets;
27509
27806
  }
27510
27807
  const derivedMatrix = registryProjects ? accessMatrixFromProjects(registryProjects) : {};
27511
- const fileMatrix = (0, import_node_fs32.existsSync)("access-matrix.json") ? loadAccessMatrix((0, import_node_fs32.readFileSync)("access-matrix.json", "utf8")) : {};
27808
+ const fileMatrix = (0, import_node_fs33.existsSync)("access-matrix.json") ? loadAccessMatrix((0, import_node_fs33.readFileSync)("access-matrix.json", "utf8")) : {};
27512
27809
  const matrix = mergeAccessMatrix(fileMatrix, derivedMatrix);
27513
27810
  const derivedContracts = registryProjects ? dataAccessContractsFromProjects(registryProjects) : { consumers: {} };
27514
- const fileContracts = (0, import_node_fs32.existsSync)("data-access-contracts.json") ? loadDataAccessContracts((0, import_node_fs32.readFileSync)("data-access-contracts.json", "utf8")) : { consumers: {} };
27811
+ const fileContracts = (0, import_node_fs33.existsSync)("data-access-contracts.json") ? loadDataAccessContracts((0, import_node_fs33.readFileSync)("data-access-contracts.json", "utf8")) : { consumers: {} };
27515
27812
  const dataAccess = mergeDataAccessContracts(fileContracts, derivedContracts);
27516
27813
  const report = await auditOrgAccess(targets, deps, matrix, dataAccess);
27517
27814
  console.log(o.json ? JSON.stringify(report, null, 2) : renderAccessReport(report));
@@ -27545,16 +27842,16 @@ function directoryBytes(path2) {
27545
27842
  let total = 0;
27546
27843
  let entries;
27547
27844
  try {
27548
- entries = (0, import_node_fs32.readdirSync)(path2, { withFileTypes: true });
27845
+ entries = (0, import_node_fs33.readdirSync)(path2, { withFileTypes: true });
27549
27846
  } catch {
27550
27847
  return 0;
27551
27848
  }
27552
27849
  for (const entry of entries) {
27553
- const child2 = (0, import_node_path30.join)(path2, entry.name);
27850
+ const child2 = (0, import_node_path31.join)(path2, entry.name);
27554
27851
  if (entry.isDirectory()) total += directoryBytes(child2);
27555
27852
  else {
27556
27853
  try {
27557
- total += (0, import_node_fs32.statSync)(child2).size;
27854
+ total += (0, import_node_fs33.statSync)(child2).size;
27558
27855
  } catch {
27559
27856
  }
27560
27857
  }
@@ -27562,25 +27859,25 @@ function directoryBytes(path2) {
27562
27859
  return total;
27563
27860
  }
27564
27861
  function listDirEntries(dir) {
27565
- return (0, import_node_fs32.readdirSync)(dir, { withFileTypes: true }).map((d) => ({ name: d.name, isDirectory: d.isDirectory() }));
27862
+ return (0, import_node_fs33.readdirSync)(dir, { withFileTypes: true }).map((d) => ({ name: d.name, isDirectory: d.isDirectory() }));
27566
27863
  }
27567
27864
  function readInstalledPluginRefs(home) {
27568
27865
  const p = installedPluginsPath(home);
27569
- if (!(0, import_node_fs32.existsSync)(p)) return [];
27866
+ if (!(0, import_node_fs33.existsSync)(p)) return [];
27570
27867
  try {
27571
- return installedPluginPaths((0, import_node_fs32.readFileSync)(p, "utf8"));
27868
+ return installedPluginPaths((0, import_node_fs33.readFileSync)(p, "utf8"));
27572
27869
  } catch {
27573
27870
  return null;
27574
27871
  }
27575
27872
  }
27576
27873
  function pluginCacheFsDeps(home, dirBytes) {
27577
27874
  return {
27578
- exists: (p) => (0, import_node_fs32.existsSync)(p),
27579
- listVersionDirs: (root) => (0, import_node_fs32.readdirSync)(root, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name),
27875
+ exists: (p) => (0, import_node_fs33.existsSync)(p),
27876
+ listVersionDirs: (root) => (0, import_node_fs33.readdirSync)(root, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name),
27580
27877
  dirBytes,
27581
- listStagingDirs: (root) => (0, import_node_fs32.readdirSync)(root, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => {
27878
+ listStagingDirs: (root) => (0, import_node_fs33.readdirSync)(root, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => {
27582
27879
  try {
27583
- return { name: d.name, mtimeMs: newestMtimeMs((0, import_node_path30.join)(root, d.name), listDirEntries, (p) => (0, import_node_fs32.statSync)(p).mtimeMs) };
27880
+ return { name: d.name, mtimeMs: newestMtimeMs((0, import_node_path31.join)(root, d.name), listDirEntries, (p) => (0, import_node_fs33.statSync)(p).mtimeMs) };
27584
27881
  } catch {
27585
27882
  return { name: d.name, mtimeMs: Date.now() };
27586
27883
  }
@@ -27594,10 +27891,10 @@ function stagingApplyFsGuard(home) {
27594
27891
  return {
27595
27892
  referencedPaths: () => readInstalledPluginRefs(home),
27596
27893
  mtimeMs: (name) => {
27597
- const p = (0, import_node_path30.join)(stagingRoot, name);
27598
- if (!(0, import_node_fs32.existsSync)(p)) return null;
27894
+ const p = (0, import_node_path31.join)(stagingRoot, name);
27895
+ if (!(0, import_node_fs33.existsSync)(p)) return null;
27599
27896
  try {
27600
- return newestMtimeMs(p, listDirEntries, (q) => (0, import_node_fs32.statSync)(q).mtimeMs);
27897
+ return newestMtimeMs(p, listDirEntries, (q) => (0, import_node_fs33.statSync)(q).mtimeMs);
27601
27898
  } catch {
27602
27899
  return null;
27603
27900
  }
@@ -27613,7 +27910,7 @@ program2.command("plugin-prune").description(`prune stale cached MMI plugin vers
27613
27910
  { withBytes: true }
27614
27911
  );
27615
27912
  const anythingToDelete = plan.prune.length > 0 || plan.staging.length > 0;
27616
- const result = o.apply && anythingToDelete ? applyPluginCachePlan(plan, (p) => (0, import_node_fs32.rmSync)(p, { recursive: true, force: true }), stagingApplyFsGuard((0, import_node_os10.homedir)())) : void 0;
27913
+ const result = o.apply && anythingToDelete ? applyPluginCachePlan(plan, (p) => (0, import_node_fs33.rmSync)(p, { recursive: true, force: true }), stagingApplyFsGuard((0, import_node_os10.homedir)())) : void 0;
27617
27914
  const warnings = plan.prune.length > 0 ? [CONCURRENT_SESSION_WARNING] : [];
27618
27915
  if (o.json) console.log(JSON.stringify({ ...plan, warnings, applied: result ?? null }));
27619
27916
  else console.log(renderPluginCachePlan(plan, result));
@@ -27676,7 +27973,7 @@ program2.command("session-start").description("run the SessionStart verbs (whoam
27676
27973
  for (const line of scratchGcLines(process.cwd())) bannerIo.log(line);
27677
27974
  const worktreeBanner = worktreeAutoProvisionBanner(process.cwd());
27678
27975
  if (worktreeBanner) {
27679
- spawnDetachedSelf(["worktree", "setup", "--quiet"], { spawn: import_node_child_process13.spawn, execPath: process.execPath, scriptPath: process.argv[1] });
27976
+ spawnDetachedSelf(["worktree", "setup", "--quiet"], { spawn: import_node_child_process14.spawn, execPath: process.execPath, scriptPath: process.argv[1] });
27680
27977
  bannerIo.log(worktreeBanner);
27681
27978
  }
27682
27979
  if (isLinkedWorktree(process.cwd())) {
@@ -27701,5 +27998,6 @@ program2.parseAsync(process.argv).then(() => finishCliRun()).catch((e) => failGr
27701
27998
  isOrgRegisteredRepo,
27702
27999
  registryClientDeps,
27703
28000
  repoSlug,
27704
- shouldMarkWorktreeActivity
28001
+ shouldMarkWorktreeActivity,
28002
+ suggestCommandPath
27705
28003
  });