@mutmutco/cli 4.1.9 → 4.1.11

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 +559 -96
  2. package/package.json +1 -1
package/dist/main.cjs CHANGED
@@ -5860,7 +5860,7 @@ var program = new Command();
5860
5860
 
5861
5861
  // src/index.ts
5862
5862
  var import_promises8 = require("node:fs/promises");
5863
- var import_node_fs46 = require("node:fs");
5863
+ var import_node_fs48 = require("node:fs");
5864
5864
  var import_node_child_process21 = require("node:child_process");
5865
5865
  init_cli_shared();
5866
5866
 
@@ -6902,7 +6902,7 @@ function commandLadderHint() {
6902
6902
  }
6903
6903
 
6904
6904
  // src/index.ts
6905
- var import_node_path42 = require("node:path");
6905
+ var import_node_path43 = require("node:path");
6906
6906
 
6907
6907
  // src/merge-ci-policy.ts
6908
6908
  function resolveMergeCiPolicy(input) {
@@ -13152,10 +13152,10 @@ var rollout_plan_default = {
13152
13152
  note: "The v4.0.0 stamp happens at cut time (D6e #4463); until then the candidate is the origin/development head artifacts (built cli/dist + npm pack), identity proven by dist content hash (D6a)."
13153
13153
  },
13154
13154
  baseline: {
13155
- version: "4.1.9",
13156
- tag: "v4.1.9",
13157
- commit: "4edf781e31f0",
13158
- npm: "@mutmutco/cli@4.1.9"
13155
+ version: "4.1.11",
13156
+ tag: "v4.1.11",
13157
+ commit: "97f4b2119c1d",
13158
+ npm: "@mutmutco/cli@4.1.11"
13159
13159
  },
13160
13160
  exitCriterion: "fleet-n-of-n",
13161
13161
  hubOnlyShortcut: "forbidden",
@@ -13172,14 +13172,14 @@ var rollout_plan_default = {
13172
13172
  repo: "mutmutco/mmi-hub",
13173
13173
  role: "canary",
13174
13174
  schedule: "train",
13175
- v3Target: "v4.1.9"
13175
+ v3Target: "v4.1.11"
13176
13176
  }
13177
13177
  ],
13178
13178
  rollbackTrigger: "Any red inside the post-contract soak window: `devops train gate` FAIL attributable to the v4 doors, Hub endpoint health probe failure, a pre-v4 client admitted instead of receiving actionable HTTP 426, or npm consumer install/doctor failure on the v4-only dist.",
13179
13179
  rollback: {
13180
13180
  independent: true,
13181
- mechanism: "npm dist-tag latest -> 4.1.9 and redeploy the Hub Lambda from tag v4.1.9 (4edf781e31f0); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
13182
- v3Target: "v4.1.9 (@mutmutco/cli@4.1.9, tag commit 4edf781e31f0 \u2014 last known-good release carrying the repo-index v4-only contract)"
13181
+ mechanism: "npm dist-tag latest -> 4.1.11 and redeploy the Hub Lambda from tag v4.1.11 (97f4b2119c1d); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
13182
+ v3Target: "v4.1.11 (@mutmutco/cli@4.1.11, tag commit 97f4b2119c1d \u2014 last known-good release carrying the repo-index v4-only contract)"
13183
13183
  }
13184
13184
  },
13185
13185
  {
@@ -16909,6 +16909,7 @@ async function preflight(deps, ctx, stage, meta) {
16909
16909
  throw new Error(`${ctx.repo} is not Hub-deployed (deployModel=none) \u2014 the release train does not apply; use the project's own release path`);
16910
16910
  }
16911
16911
  await deps.runSelf(["secrets", "preflight", "--stage", stage, "--repo", ctx.repo]);
16912
+ await assertNpmMajorPreflightFromWorkflows(deps, ctx.repo);
16912
16913
  await assertActionsJobsCanStart(deps, ctx.repo);
16913
16914
  enforceGateBudget(deps, ctx.repo);
16914
16915
  if (model === "hub-serverless") {
@@ -17161,10 +17162,49 @@ function resolveGreenGateNpmFromGateWorkflows(files) {
17161
17162
  const pin = preferGateNodePin(files.flatMap((file) => parseGateNodeVersionPins(file.body)));
17162
17163
  return pin ? bundledNpmForNodePin(pin) : void 0;
17163
17164
  }
17165
+ function parsePublishWorkflowNpmPin(workflowBody) {
17166
+ const match = /(?:^|[^\w])npm@(\d+\.\d+\.\d+)/m.exec(workflowBody);
17167
+ return match?.[1];
17168
+ }
17169
+ function resolveExpectedCiNpmFromWorkflows(gateFiles, allFiles) {
17170
+ const fromGate = resolveGreenGateNpmFromGateWorkflows(gateFiles);
17171
+ if (fromGate) return fromGate;
17172
+ const files = allFiles ?? gateFiles;
17173
+ if (!files?.length) return void 0;
17174
+ const publish = files.find((f) => /(?:^|\/)publish\.ya?ml$/i.test(f.path));
17175
+ return publish ? parsePublishWorkflowNpmPin(publish.body) : void 0;
17176
+ }
17164
17177
  function npmMajor(version) {
17165
17178
  const match = /^(\d+)/.exec(version.trim());
17166
17179
  return match ? Number(match[1]) : void 0;
17167
17180
  }
17181
+ function assertNpmMajorPreflight(opts) {
17182
+ const expectedNpm = opts.expectedNpm?.trim();
17183
+ if (!expectedNpm) return;
17184
+ const localNpm = opts.localNpm.trim();
17185
+ const localMajor = npmMajor(localNpm);
17186
+ const expectedMajor = npmMajor(expectedNpm);
17187
+ if (localMajor === void 0 || expectedMajor === void 0) return;
17188
+ if (localMajor === expectedMajor) return;
17189
+ throw new Error(
17190
+ `${opts.repo}: Step 0a npm-major preflight failed \u2014 local npm ${localNpm} (major ${localMajor}) \u2260 CI npm ${expectedNpm} (major ${expectedMajor}). Align local npm before --apply: \`npm install -g npm@${expectedNpm}\`. This is an npm-major mismatch (#5666/#5616/#4578), not a lockfile or publish-surface problem. When gate.yml has no bundled npm pin, read the publish workflow npm pin; otherwise compare \`npm -v\` with the green gate log's toolchain-preflight line (#3446).`
17191
+ );
17192
+ }
17193
+ async function assertNpmMajorPreflightFromWorkflows(deps, repo) {
17194
+ const gateFiles = (deps.readGateWorkflows ?? readLocalGateWorkflows)();
17195
+ const allFiles = (deps.readWorkflows ?? readLocalWorkflows)();
17196
+ let localNpm;
17197
+ try {
17198
+ localNpm = clean2(await deps.run("npm", ["-v"]));
17199
+ } catch {
17200
+ return;
17201
+ }
17202
+ assertNpmMajorPreflight({
17203
+ repo,
17204
+ localNpm,
17205
+ expectedNpm: resolveExpectedCiNpmFromWorkflows(gateFiles, allFiles)
17206
+ });
17207
+ }
17168
17208
  function subprocessFailureDetail(e) {
17169
17209
  if (!(e instanceof Error)) return String(e);
17170
17210
  const err = e;
@@ -17211,7 +17251,10 @@ async function verifyPublishDryRun(deps, ctx, meta, deployModel) {
17211
17251
  localNpm = clean2(await deps.run("npm", ["-v"]));
17212
17252
  } catch {
17213
17253
  }
17214
- const gateNpm = resolveGreenGateNpmFromGateWorkflows((deps.readGateWorkflows ?? readLocalGateWorkflows)());
17254
+ const gateNpm = resolveExpectedCiNpmFromWorkflows(
17255
+ (deps.readGateWorkflows ?? readLocalGateWorkflows)(),
17256
+ (deps.readWorkflows ?? readLocalWorkflows)()
17257
+ );
17215
17258
  throw classifyPublishDryRunFailure({
17216
17259
  repo: ctx.repo,
17217
17260
  publishDir,
@@ -33415,6 +33458,396 @@ async function remoteBranchExists2(branch, options = {}) {
33415
33458
  }
33416
33459
  }
33417
33460
 
33461
+ // src/worktree-merge-cleanup.ts
33462
+ var import_node_fs42 = require("node:fs");
33463
+ init_cli_shared();
33464
+
33465
+ // src/worktree-evidence-archive.ts
33466
+ var import_node_fs41 = require("node:fs");
33467
+ var import_node_path38 = require("node:path");
33468
+ var JERV_ARTIFACT_RUN_SCOPE_ENV_VARS = ["JERV_RUN_ID", ...SESSION_ID_ENV_VARS];
33469
+ function sanitizeArchiveSegment(value, max = 80) {
33470
+ const scrubbed = value.replace(/[^A-Za-z0-9._@+-]+/g, "-").replace(/^-+|-+$/g, "");
33471
+ return (scrubbed || "unknown").slice(0, max);
33472
+ }
33473
+ function resolveArtifactRunScope(env = process.env) {
33474
+ for (const key of JERV_ARTIFACT_RUN_SCOPE_ENV_VARS) {
33475
+ const value = env[key]?.trim();
33476
+ if (value) return sanitizeArchiveSegment(value);
33477
+ }
33478
+ return "no-run";
33479
+ }
33480
+ function defaultIsDirectory(path2) {
33481
+ try {
33482
+ return (0, import_node_fs41.lstatSync)(path2).isDirectory();
33483
+ } catch {
33484
+ return false;
33485
+ }
33486
+ }
33487
+ function archiveWorktreeJervArtifacts(args, deps = {}) {
33488
+ const exists = deps.exists ?? import_node_fs41.existsSync;
33489
+ const isDirectory = deps.isDirectory ?? defaultIsDirectory;
33490
+ const copyDir = deps.copyDir ?? ((from, to) => (0, import_node_fs41.cpSync)(from, to, { recursive: true, force: true }));
33491
+ const mkdirp = deps.mkdirp ?? ((path2) => {
33492
+ (0, import_node_fs41.mkdirSync)(path2, { recursive: true });
33493
+ });
33494
+ const env = deps.env ?? process.env;
33495
+ const now = deps.now ?? (() => /* @__PURE__ */ new Date());
33496
+ const resolveRoot = deps.resolveArchiveRoot ?? repoRuntimeStatePath;
33497
+ if (!args.primaryRoot?.trim()) return { status: "skipped", reason: "missing-primary-root" };
33498
+ if (!args.worktreePath?.trim()) return { status: "skipped", reason: "missing-worktree-path" };
33499
+ const source = (0, import_node_path38.join)(args.worktreePath, ".jerv");
33500
+ if (!exists(source)) return { status: "absent" };
33501
+ if (!isDirectory(source)) return { status: "skipped", reason: "jerv-not-a-directory" };
33502
+ const runScope = resolveArtifactRunScope(env);
33503
+ const branchSlug = sanitizeArchiveSegment(args.branch.replace(/[/\\]+/g, "-"), 40);
33504
+ const stamp = now().toISOString().replace(/[:.]/g, "-");
33505
+ const dest = resolveRoot(args.primaryRoot, "jerv-artifacts", runScope, branchSlug, stamp, ".jerv");
33506
+ try {
33507
+ mkdirp((0, import_node_path38.dirname)(dest));
33508
+ copyDir(source, dest);
33509
+ if (!exists(dest)) return { status: "failed", error: `archive write left no directory at ${dest}` };
33510
+ return { status: "archived", path: dest, runScope };
33511
+ } catch (e) {
33512
+ return { status: "failed", error: e instanceof Error ? e.message : String(e) };
33513
+ }
33514
+ }
33515
+ function defaultStat(path2) {
33516
+ const st = (0, import_node_fs41.statSync)(path2);
33517
+ return { mtimeMs: st.mtimeMs, size: st.size, isDirectory: () => st.isDirectory() };
33518
+ }
33519
+ function scanWorktreeTmpEvidence(worktreePath, newerThanMs, deps = {}) {
33520
+ const exists = deps.exists ?? import_node_fs41.existsSync;
33521
+ const stat4 = deps.stat ?? defaultStat;
33522
+ const readdir2 = deps.readdir ?? import_node_fs41.readdirSync;
33523
+ const tmpRoot = (0, import_node_path38.join)(worktreePath, "tmp");
33524
+ if (!exists(tmpRoot)) return [];
33525
+ const entries = [];
33526
+ const walk2 = (dir) => {
33527
+ let names;
33528
+ try {
33529
+ names = readdir2(dir);
33530
+ } catch {
33531
+ return;
33532
+ }
33533
+ for (const name of names) {
33534
+ const full = (0, import_node_path38.join)(dir, name);
33535
+ let st;
33536
+ try {
33537
+ st = stat4(full);
33538
+ } catch {
33539
+ continue;
33540
+ }
33541
+ const relPath = (0, import_node_path38.relative)(worktreePath, full).replace(/\\/g, "/");
33542
+ if (st.isDirectory()) {
33543
+ if (st.mtimeMs > newerThanMs) entries.push({ relPath, bytes: 0, mtimeMs: st.mtimeMs });
33544
+ walk2(full);
33545
+ continue;
33546
+ }
33547
+ if (st.mtimeMs > newerThanMs) entries.push({ relPath, bytes: st.size, mtimeMs: st.mtimeMs });
33548
+ }
33549
+ };
33550
+ walk2(tmpRoot);
33551
+ entries.sort((a, b) => a.relPath.localeCompare(b.relPath));
33552
+ return entries;
33553
+ }
33554
+ function archiveWorktreeTmpArtifacts(args, deps = {}) {
33555
+ const exists = deps.exists ?? import_node_fs41.existsSync;
33556
+ const copyDir = deps.copyDir ?? ((from, to) => (0, import_node_fs41.cpSync)(from, to, { recursive: true, force: true }));
33557
+ const mkdirp = deps.mkdirp ?? ((path2) => {
33558
+ (0, import_node_fs41.mkdirSync)(path2, { recursive: true });
33559
+ });
33560
+ const env = deps.env ?? process.env;
33561
+ const now = deps.now ?? (() => /* @__PURE__ */ new Date());
33562
+ const resolveRoot = deps.resolveArchiveRoot ?? repoRuntimeStatePath;
33563
+ if (!args.primaryRoot?.trim()) return { status: "skipped", reason: "missing-primary-root" };
33564
+ if (!args.worktreePath?.trim()) return { status: "skipped", reason: "missing-worktree-path" };
33565
+ const scanned = scanWorktreeTmpEvidence(args.worktreePath, args.newerThanMs, deps);
33566
+ const source = (0, import_node_path38.join)(args.worktreePath, "tmp");
33567
+ if (!scanned.length) return { status: "absent" };
33568
+ if (!exists(source)) return { status: "absent" };
33569
+ const runScope = resolveArtifactRunScope(env);
33570
+ const branchSlug = sanitizeArchiveSegment(args.branch.replace(/[/\\]+/g, "-"), 40);
33571
+ const stamp = now().toISOString().replace(/[:.]/g, "-");
33572
+ const dest = resolveRoot(args.primaryRoot, "worktree-artifacts", runScope, branchSlug, stamp, "tmp");
33573
+ try {
33574
+ mkdirp((0, import_node_path38.dirname)(dest));
33575
+ copyDir(source, dest);
33576
+ if (!exists(dest)) return { status: "failed", error: `archive write left no directory at ${dest}` };
33577
+ const bytes = scanned.reduce((sum, e) => sum + e.bytes, 0);
33578
+ return { status: "archived", path: dest, runScope, entries: scanned.length, bytes };
33579
+ } catch (e) {
33580
+ return { status: "failed", error: e instanceof Error ? e.message : String(e) };
33581
+ }
33582
+ }
33583
+ function archiveWorktreeArtifacts(args, deps = {}) {
33584
+ return {
33585
+ jerv: archiveWorktreeJervArtifacts(args, deps),
33586
+ tmp: archiveWorktreeTmpArtifacts(args, deps)
33587
+ };
33588
+ }
33589
+ function evaluateTmpEvidenceRemovalGuard(input) {
33590
+ if (!input.tmpEvidence.length) return { blocked: false };
33591
+ if (input.tmpArchive.status === "archived") return { blocked: false };
33592
+ if (input.tmpArchive.status === "failed") {
33593
+ return {
33594
+ blocked: true,
33595
+ reason: `tmp evidence archive failed \u2014 ${input.tmpArchive.error}`,
33596
+ wouldDelete: input.tmpEvidence.map((e) => e.relPath)
33597
+ };
33598
+ }
33599
+ if (input.gcAcknowledged) return { blocked: false, wouldDelete: input.tmpEvidence.map((e) => e.relPath) };
33600
+ const sample = input.tmpEvidence.slice(0, 8).map((e) => e.relPath);
33601
+ const more = input.tmpEvidence.length > sample.length ? ` (+${input.tmpEvidence.length - sample.length} more)` : "";
33602
+ return {
33603
+ blocked: true,
33604
+ reason: `worktree tmp evidence would be destroyed (${input.tmpEvidence.length} path(s) newer than the branch base) \u2014 re-run with --gc to acknowledge deletion, or copy tmp/ out first`,
33605
+ wouldDelete: sample.concat(more ? [`${input.tmpEvidence.length} total under tmp/`] : [])
33606
+ };
33607
+ }
33608
+ function parseCommitTimestampSec(stdout) {
33609
+ const trimmed = stdout.trim();
33610
+ if (!/^\d+$/.test(trimmed)) return void 0;
33611
+ const sec = Number(trimmed);
33612
+ return Number.isFinite(sec) && sec > 0 ? sec : void 0;
33613
+ }
33614
+ function formatArtifactsArchiveReceipt(archive) {
33615
+ const jervArchived = archive.jerv.status === "archived";
33616
+ const tmpArchived = archive.tmp.status === "archived";
33617
+ if (jervArchived || tmpArchived) {
33618
+ const path2 = archive.tmp.status === "archived" ? archive.tmp.path : archive.jerv.status === "archived" ? archive.jerv.path : void 0;
33619
+ return {
33620
+ status: "archived",
33621
+ jerv: archive.jerv,
33622
+ tmp: archive.tmp,
33623
+ path: path2
33624
+ };
33625
+ }
33626
+ if (archive.jerv.status === "absent" && archive.tmp.status === "absent") {
33627
+ return { status: "absent", jerv: archive.jerv, tmp: archive.tmp };
33628
+ }
33629
+ if (archive.tmp.status === "failed") {
33630
+ return { status: "blocked", jerv: archive.jerv, tmp: archive.tmp, error: archive.tmp.error };
33631
+ }
33632
+ if (archive.jerv.status === "failed") {
33633
+ return { status: "blocked", jerv: archive.jerv, tmp: archive.tmp, error: archive.jerv.error };
33634
+ }
33635
+ return { status: "partial", jerv: archive.jerv, tmp: archive.tmp };
33636
+ }
33637
+
33638
+ // src/worktree-merge-cleanup.ts
33639
+ function normPath2(p) {
33640
+ return p.replace(/\\/g, "/").replace(/\/+$/, "");
33641
+ }
33642
+ function samePath(a, b) {
33643
+ const na = normPath2(a);
33644
+ const nb = normPath2(b);
33645
+ return process.platform === "win32" ? na.toLowerCase() === nb.toLowerCase() : na === nb;
33646
+ }
33647
+ function selectPrMergeCleanupWorktree(branch, before, after, startingPath) {
33648
+ if (!branch) return void 0;
33649
+ const current = after.find((w) => w.branch === branch)?.path;
33650
+ if (current) return current;
33651
+ const previous = before.find((w) => w.branch === branch)?.path;
33652
+ if (previous) return previous;
33653
+ if (startingPath && before.some((w) => w.branch === branch && samePath(w.path, startingPath))) return startingPath;
33654
+ return void 0;
33655
+ }
33656
+ function selectSafeWorktreeCwd(worktrees, targetPath, options) {
33657
+ if (!targetPath) return void 0;
33658
+ const exists = options?.pathExists ?? (() => true);
33659
+ return worktrees.find((w) => !samePath(w.path, targetPath) && exists(w.path))?.path;
33660
+ }
33661
+ async function resolveMergeBaseTimestampMs(git3, baseRef, branch) {
33662
+ try {
33663
+ const mergeBase = (await git3(["merge-base", baseRef, branch])).trim();
33664
+ if (!mergeBase) return 0;
33665
+ const sec = parseCommitTimestampSec(await git3(["show", "-s", "--format=%ct", mergeBase]));
33666
+ return sec ? sec * 1e3 : 0;
33667
+ } catch {
33668
+ return 0;
33669
+ }
33670
+ }
33671
+ async function verifyBranchHead(git3, branch, expectedHeadOid) {
33672
+ if (!expectedHeadOid) return { ok: false, reason: "branch-head-unverified" };
33673
+ const currentHead = (await git3(["rev-parse", `refs/heads/${branch}`]).catch(() => "") || "").trim();
33674
+ if (!currentHead) return { ok: false, reason: "branch-head-unverified" };
33675
+ if (currentHead !== expectedHeadOid) {
33676
+ return { ok: false, reason: "unpushed-branch", error: `${currentHead} != ${expectedHeadOid}` };
33677
+ }
33678
+ return { ok: true };
33679
+ }
33680
+ async function teardownWorktreeStage(worktreePath) {
33681
+ try {
33682
+ const result = await stopStage({ cwd: worktreePath, requiredIdentityCwd: worktreePath, globalStatePath: false });
33683
+ return { status: result.ok ? "stopped" : "failed", ...result.ok ? {} : { error: result.message } };
33684
+ } catch (e) {
33685
+ return { status: "failed", error: e instanceof Error ? e.message : String(e) };
33686
+ }
33687
+ }
33688
+ async function removeWorktree(wtPath, git3) {
33689
+ try {
33690
+ await git3(["worktree", "remove", "--force", wtPath]);
33691
+ await git3(["worktree", "prune"]).catch(() => "");
33692
+ return { status: "removed" };
33693
+ } catch (e) {
33694
+ return { status: "failed", error: e instanceof Error ? e.message : String(e) };
33695
+ }
33696
+ }
33697
+ async function cleanupPrMergeLocalBranch(branch, options) {
33698
+ const report = { branch };
33699
+ if (!branch) {
33700
+ report.localBranch = { name: branch, status: "not-attempted", reason: "missing-branch" };
33701
+ return report;
33702
+ }
33703
+ if (isProtectedBranch(branch)) {
33704
+ report.localBranch = { name: branch, status: "not-attempted", reason: "protected-branch" };
33705
+ return report;
33706
+ }
33707
+ const execGit = options.execGit ?? (async (args) => (await execFileP2("git", args, { timeout: GIT_TIMEOUT_MS })).stdout);
33708
+ const pathExists = options.pathExists ?? import_node_fs42.existsSync;
33709
+ let afterWorktrees = [];
33710
+ try {
33711
+ afterWorktrees = parseGitWorktreePorcelain(await execGit(["worktree", "list", "--porcelain"]));
33712
+ } catch (e) {
33713
+ report.localBranch = {
33714
+ name: branch,
33715
+ status: "not-attempted",
33716
+ reason: "worktree-list-failed",
33717
+ error: e instanceof Error ? e.message : String(e)
33718
+ };
33719
+ return report;
33720
+ }
33721
+ const beforeWorktrees = options.beforeWorktrees ?? [];
33722
+ const wtPath = selectPrMergeCleanupWorktree(branch, beforeWorktrees, afterWorktrees, options.startingPath);
33723
+ if (options.preserveWorktree) {
33724
+ if (wtPath) report.worktree = { path: wtPath, status: "preserved", reason: "preserve-worktree" };
33725
+ report.localBranch = { name: branch, status: "not-attempted", reason: "preserve-worktree" };
33726
+ return report;
33727
+ }
33728
+ const mainWorktreePath = beforeWorktrees[0]?.path ?? afterWorktrees[0]?.path;
33729
+ const mainWorktreeTarget = Boolean(wtPath && mainWorktreePath && samePath(wtPath, mainWorktreePath));
33730
+ if (!wtPath || mainWorktreeTarget) {
33731
+ if (wtPath && mainWorktreeTarget) {
33732
+ report.worktree = { path: wtPath, status: "not-attempted", reason: "main-worktree" };
33733
+ }
33734
+ report.localBranch = { name: branch, status: "not-attempted", reason: wtPath ? "main-worktree" : "no-worktree" };
33735
+ return report;
33736
+ }
33737
+ const safeCwd = selectSafeWorktreeCwd([...afterWorktrees, ...beforeWorktrees], wtPath, { pathExists });
33738
+ const git3 = (args) => safeCwd ? execGit(["-C", safeCwd, ...args]) : execGit(args);
33739
+ const headCheck = await verifyBranchHead(git3, branch, options.expectedHeadOid);
33740
+ if (!headCheck.ok) {
33741
+ report.localBranch = {
33742
+ name: branch,
33743
+ status: "not-attempted",
33744
+ reason: headCheck.reason,
33745
+ ...headCheck.error ? { error: headCheck.error } : {}
33746
+ };
33747
+ return report;
33748
+ }
33749
+ const porcelain = await execGit(["-C", wtPath, "status", "--porcelain"]).catch(() => void 0);
33750
+ if (porcelain?.trim()) {
33751
+ report.worktree = { path: wtPath, status: "refused", reason: "dirty-worktree" };
33752
+ report.localBranch = { name: branch, status: "not-attempted", reason: "dirty-worktree" };
33753
+ return report;
33754
+ }
33755
+ const newerThanMs = await resolveMergeBaseTimestampMs(
33756
+ (args) => execGit(["-C", wtPath, ...args]),
33757
+ options.baseRef,
33758
+ branch
33759
+ );
33760
+ const tmpEvidence = scanWorktreeTmpEvidence(wtPath, newerThanMs, { exists: pathExists });
33761
+ const stageTeardown = await teardownWorktreeStage(wtPath);
33762
+ const artifactsArchiveRaw = archiveWorktreeArtifacts({
33763
+ worktreePath: wtPath,
33764
+ primaryRoot: options.primaryRoot,
33765
+ branch,
33766
+ newerThanMs
33767
+ });
33768
+ const artifactsArchive = formatArtifactsArchiveReceipt(artifactsArchiveRaw);
33769
+ const tmpGuard = evaluateTmpEvidenceRemovalGuard({
33770
+ tmpEvidence,
33771
+ tmpArchive: artifactsArchiveRaw.tmp,
33772
+ gcAcknowledged: Boolean(options.gcAcknowledged)
33773
+ });
33774
+ if (tmpGuard.blocked) {
33775
+ report.worktree = {
33776
+ path: wtPath,
33777
+ status: "refused",
33778
+ reason: "tmp-evidence",
33779
+ error: tmpGuard.reason,
33780
+ artifactsArchive,
33781
+ stageTeardown,
33782
+ tmpEvidenceCount: tmpEvidence.length
33783
+ };
33784
+ report.localBranch = { name: branch, status: "not-attempted", reason: "tmp-evidence" };
33785
+ return report;
33786
+ }
33787
+ if (artifactsArchive.status === "blocked") {
33788
+ report.worktree = {
33789
+ path: wtPath,
33790
+ status: "refused",
33791
+ reason: "archive-failed",
33792
+ error: artifactsArchive.error,
33793
+ artifactsArchive,
33794
+ stageTeardown,
33795
+ tmpEvidenceCount: tmpEvidence.length
33796
+ };
33797
+ report.localBranch = { name: branch, status: "not-attempted", reason: "archive-failed" };
33798
+ return report;
33799
+ }
33800
+ const removal = await removeWorktree(wtPath, git3);
33801
+ if (removal.status === "failed") {
33802
+ report.worktree = {
33803
+ path: wtPath,
33804
+ status: "failed",
33805
+ error: removal.error,
33806
+ artifactsArchive,
33807
+ stageTeardown,
33808
+ tmpEvidenceCount: tmpEvidence.length
33809
+ };
33810
+ report.localBranch = { name: branch, status: "not-attempted", reason: "worktree-removal-failed" };
33811
+ return report;
33812
+ }
33813
+ report.worktree = {
33814
+ path: wtPath,
33815
+ status: "removed",
33816
+ artifactsArchive,
33817
+ stageTeardown,
33818
+ tmpEvidenceCount: tmpEvidence.length
33819
+ };
33820
+ try {
33821
+ await git3(["branch", "-D", branch]);
33822
+ report.localBranch = { name: branch, status: "deleted" };
33823
+ } catch (e) {
33824
+ const remaining = await git3(["branch", "--list", branch]).catch(() => "");
33825
+ report.localBranch = remaining.trim() ? { name: branch, status: "failed", error: e instanceof Error ? e.message : String(e) } : { name: branch, status: "already-gone" };
33826
+ }
33827
+ return report;
33828
+ }
33829
+ function renderPrMergeCleanupLines(cleanup) {
33830
+ const wt = cleanup?.worktree;
33831
+ if (!wt?.path) return [];
33832
+ const lines = [];
33833
+ if (wt.status === "removed") {
33834
+ lines.push(`pr merge: removed worktree ${wt.path} \u2014 that directory is gone; cd elsewhere before your next command`);
33835
+ } else if (wt.status === "refused") {
33836
+ lines.push(`pr merge: worktree ${wt.path} kept \u2014 ${wt.reason ?? "refused"}${wt.error ? `: ${wt.error}` : ""}`);
33837
+ if (wt.tmpEvidenceCount) lines.push(`pr merge: ${wt.tmpEvidenceCount} tmp path(s) newer than branch base would have been destroyed`);
33838
+ } else if (wt.status === "failed") {
33839
+ lines.push(`pr merge: worktree ${wt.path} removal failed${wt.error ? ` \u2014 ${wt.error}` : ""}`);
33840
+ } else if (wt.status === "preserved") {
33841
+ lines.push(`pr merge: preserved worktree ${wt.path} (--preserve-worktree)`);
33842
+ }
33843
+ if (wt.artifactsArchive?.status === "archived" && wt.artifactsArchive.path) {
33844
+ lines.push(`pr merge: archived worktree evidence to ${wt.artifactsArchive.path}`);
33845
+ } else if (wt.artifactsArchive?.status === "blocked" && wt.artifactsArchive.error) {
33846
+ lines.push(`pr merge: evidence archive failed \u2014 ${wt.artifactsArchive.error}`);
33847
+ }
33848
+ return lines;
33849
+ }
33850
+
33418
33851
  // src/board-commands.ts
33419
33852
  init_cli_shared();
33420
33853
  init_clean_exit();
@@ -34119,7 +34552,7 @@ async function checkDocsIndexAtHead(opts, deps) {
34119
34552
  }
34120
34553
 
34121
34554
  // src/issue-commands.ts
34122
- var import_node_fs41 = require("node:fs");
34555
+ var import_node_fs43 = require("node:fs");
34123
34556
  var import_node_crypto16 = require("node:crypto");
34124
34557
  init_cli_shared();
34125
34558
  init_clean_exit();
@@ -34312,7 +34745,7 @@ async function editIssue(client, options, deps = {}) {
34312
34745
  const url = `https://github.com/${repo}/issues/${parsed.number}`;
34313
34746
  const patch = {};
34314
34747
  let bodyChanged = false;
34315
- const textDeps = () => deps.textDeps ?? { readFile: (p, e) => Promise.resolve((0, import_node_fs41.readFileSync)(p, e)), readStdin: () => Promise.resolve("") };
34748
+ const textDeps = () => deps.textDeps ?? { readFile: (p, e) => Promise.resolve((0, import_node_fs43.readFileSync)(p, e)), readStdin: () => Promise.resolve("") };
34316
34749
  if (options.titleFile !== void 0) {
34317
34750
  patch.title = await resolveIssueTitle({ title: options.title, titleFile: options.titleFile }, textDeps());
34318
34751
  } else if (options.title !== void 0) {
@@ -34929,7 +35362,7 @@ function extendCreateCommand(issue2, batchAttach) {
34929
35362
  if (opts.batch) {
34930
35363
  let specs;
34931
35364
  try {
34932
- const raw = (0, import_node_fs41.readFileSync)(opts.batch, "utf8");
35365
+ const raw = (0, import_node_fs43.readFileSync)(opts.batch, "utf8");
34933
35366
  specs = JSON.parse(raw);
34934
35367
  if (!Array.isArray(specs)) throw new Error("batch file must contain a JSON array");
34935
35368
  } catch (e) {
@@ -35004,8 +35437,8 @@ ${lines}`, {
35004
35437
  }
35005
35438
 
35006
35439
  // src/train-commands.ts
35007
- var import_node_fs42 = require("node:fs");
35008
- var import_node_path38 = require("node:path");
35440
+ var import_node_fs44 = require("node:fs");
35441
+ var import_node_path39 = require("node:path");
35009
35442
  init_cli_shared();
35010
35443
  init_clean_exit();
35011
35444
  init_client_version();
@@ -35020,7 +35453,7 @@ function resolveReleaseBumpIntent(raw) {
35020
35453
  }
35021
35454
  function readRepoVersion() {
35022
35455
  try {
35023
- return JSON.parse((0, import_node_fs42.readFileSync)((0, import_node_path38.join)(process.cwd(), ".claude-plugin", "plugin.json"), "utf8")).version || void 0;
35456
+ return JSON.parse((0, import_node_fs44.readFileSync)((0, import_node_path39.join)(process.cwd(), ".claude-plugin", "plugin.json"), "utf8")).version || void 0;
35024
35457
  } catch {
35025
35458
  return void 0;
35026
35459
  }
@@ -35184,9 +35617,9 @@ function registerDeployCommands(program3) {
35184
35617
  init_cli_shared();
35185
35618
  init_github_client();
35186
35619
  init_cli_shared();
35187
- var import_node_fs43 = require("node:fs");
35620
+ var import_node_fs45 = require("node:fs");
35188
35621
  var import_node_os19 = require("node:os");
35189
- var import_node_path39 = require("node:path");
35622
+ var import_node_path40 = require("node:path");
35190
35623
  init_marketplace_autoupdate();
35191
35624
  var GC_GH_TIMEOUT_MS2 = 2e4;
35192
35625
  async function collectStatus() {
@@ -35389,8 +35822,8 @@ async function collectOnboardStatus(opts = {}) {
35389
35822
  }
35390
35823
  const home = (0, import_node_os19.homedir)();
35391
35824
  const plugin = onboardPluginGate({
35392
- readKnown: () => readFileSyncSafe((0, import_node_path39.join)(home, ...KNOWN_MARKETPLACES_RELATIVE), import_node_fs43.readFileSync),
35393
- readSettings: () => readFileSyncSafe((0, import_node_path39.join)(home, ".claude", "settings.json"), import_node_fs43.readFileSync)
35825
+ readKnown: () => readFileSyncSafe((0, import_node_path40.join)(home, ...KNOWN_MARKETPLACES_RELATIVE), import_node_fs45.readFileSync),
35826
+ readSettings: () => readFileSyncSafe((0, import_node_path40.join)(home, ".claude", "settings.json"), import_node_fs45.readFileSync)
35394
35827
  });
35395
35828
  return { track, board, registry: registry2, secrets, plugin, estateCli, doors: opts.doors ?? [], nextCommand };
35396
35829
  }
@@ -36153,8 +36586,8 @@ function registerPrLifecycleCommands(program3) {
36153
36586
  }
36154
36587
 
36155
36588
  // src/post-merge-recon.ts
36156
- var import_node_fs44 = require("node:fs");
36157
- var import_node_path40 = require("node:path");
36589
+ var import_node_fs46 = require("node:fs");
36590
+ var import_node_path41 = require("node:path");
36158
36591
 
36159
36592
  // src/cross-repo-filing-issue.ts
36160
36593
  init_github_client();
@@ -36321,16 +36754,16 @@ function buildPostMergeReconRecovery(input) {
36321
36754
  }
36322
36755
  function writePostMergeReconRecovery(cwd, recovery) {
36323
36756
  const path2 = postMergeReconStatePath(cwd, recovery.repo, recovery.pr);
36324
- (0, import_node_fs44.mkdirSync)((0, import_node_path40.dirname)(path2), { recursive: true });
36325
- (0, import_node_fs44.writeFileSync)(path2, `${JSON.stringify(recovery, null, 2)}
36757
+ (0, import_node_fs46.mkdirSync)((0, import_node_path41.dirname)(path2), { recursive: true });
36758
+ (0, import_node_fs46.writeFileSync)(path2, `${JSON.stringify(recovery, null, 2)}
36326
36759
  `, "utf8");
36327
36760
  return path2;
36328
36761
  }
36329
36762
  function clearPostMergeReconRecovery(cwd, repo, pr2) {
36330
36763
  const path2 = postMergeReconStatePath(cwd, repo, pr2);
36331
- if (!(0, import_node_fs44.existsSync)(path2)) return;
36764
+ if (!(0, import_node_fs46.existsSync)(path2)) return;
36332
36765
  try {
36333
- (0, import_node_fs44.unlinkSync)(path2);
36766
+ (0, import_node_fs46.unlinkSync)(path2);
36334
36767
  } catch {
36335
36768
  }
36336
36769
  }
@@ -39457,17 +39890,17 @@ function parseOriginRepo(remoteUrl) {
39457
39890
  }
39458
39891
  function ghHostsConfigPath(env, platform2) {
39459
39892
  const sep4 = platform2 === "win32" ? "\\" : "/";
39460
- const join36 = (...parts) => parts.join(sep4);
39893
+ const join37 = (...parts) => parts.join(sep4);
39461
39894
  const explicit = env.GH_CONFIG_DIR?.trim();
39462
- if (explicit) return join36(explicit, "hosts.yml");
39895
+ if (explicit) return join37(explicit, "hosts.yml");
39463
39896
  if (platform2 === "win32") {
39464
39897
  const appData = (env.AppData ?? env.APPDATA)?.trim();
39465
- return appData ? join36(appData, "GitHub CLI", "hosts.yml") : void 0;
39898
+ return appData ? join37(appData, "GitHub CLI", "hosts.yml") : void 0;
39466
39899
  }
39467
39900
  const xdg = env.XDG_CONFIG_HOME?.trim();
39468
- if (xdg) return join36(xdg, "gh", "hosts.yml");
39901
+ if (xdg) return join37(xdg, "gh", "hosts.yml");
39469
39902
  const home = env.HOME?.trim();
39470
- return home ? join36(home, ".config", "gh", "hosts.yml") : void 0;
39903
+ return home ? join37(home, ".config", "gh", "hosts.yml") : void 0;
39471
39904
  }
39472
39905
  function parseGhHostsAccounts(yaml, host = "github.com") {
39473
39906
  let hostIndent = null;
@@ -39517,9 +39950,9 @@ function ghAccountCaveat(announcedLogin, accounts) {
39517
39950
  }
39518
39951
 
39519
39952
  // src/doctor-io.ts
39520
- var import_node_fs45 = require("node:fs");
39953
+ var import_node_fs47 = require("node:fs");
39521
39954
  var import_node_os20 = require("node:os");
39522
- var import_node_path41 = require("node:path");
39955
+ var import_node_path42 = require("node:path");
39523
39956
  var import_node_child_process20 = require("node:child_process");
39524
39957
  var import_node_util8 = require("node:util");
39525
39958
  init_version_lag();
@@ -39534,8 +39967,8 @@ function nodeDiscardSinkPath(platform2 = process.platform) {
39534
39967
  var execFileP6 = (0, import_node_util8.promisify)(import_node_child_process20.execFile);
39535
39968
  function execFileCapture(file, args, opts = {}) {
39536
39969
  const sink = nodeDiscardSinkPath();
39537
- const inFd = (0, import_node_fs45.openSync)(sink, "r");
39538
- const errFd = (0, import_node_fs45.openSync)(sink, "w");
39970
+ const inFd = (0, import_node_fs47.openSync)(sink, "r");
39971
+ const errFd = (0, import_node_fs47.openSync)(sink, "w");
39539
39972
  try {
39540
39973
  return (0, import_node_child_process20.execFileSync)(file, args, {
39541
39974
  ...opts,
@@ -39543,15 +39976,15 @@ function execFileCapture(file, args, opts = {}) {
39543
39976
  stdio: [inFd, "pipe", errFd]
39544
39977
  });
39545
39978
  } finally {
39546
- (0, import_node_fs45.closeSync)(inFd);
39547
- (0, import_node_fs45.closeSync)(errFd);
39979
+ (0, import_node_fs47.closeSync)(inFd);
39980
+ (0, import_node_fs47.closeSync)(errFd);
39548
39981
  }
39549
39982
  }
39550
39983
  var MMI_PLUGIN_ID2 = "mmi@mutmutco";
39551
39984
  function installedClaudePluginVersion() {
39552
39985
  try {
39553
39986
  const file = JSON.parse(
39554
- (0, import_node_fs45.readFileSync)((0, import_node_path41.join)((0, import_node_os20.homedir)(), ".claude", "plugins", "installed_plugins.json"), "utf8")
39987
+ (0, import_node_fs47.readFileSync)((0, import_node_path42.join)((0, import_node_os20.homedir)(), ".claude", "plugins", "installed_plugins.json"), "utf8")
39555
39988
  );
39556
39989
  const versions = (file.plugins?.[MMI_PLUGIN_ID2] ?? []).map((r) => r.version).filter((v) => Boolean(v));
39557
39990
  if (versions.length === 0) return void 0;
@@ -39562,7 +39995,7 @@ function installedClaudePluginVersion() {
39562
39995
  }
39563
39996
  function manifestVersion(path2) {
39564
39997
  try {
39565
- const manifest = JSON.parse((0, import_node_fs45.readFileSync)(path2, "utf8"));
39998
+ const manifest = JSON.parse((0, import_node_fs47.readFileSync)(path2, "utf8"));
39566
39999
  return typeof manifest.version === "string" && manifest.version.trim() ? manifest.version.trim() : void 0;
39567
40000
  } catch {
39568
40001
  return void 0;
@@ -39571,12 +40004,12 @@ function manifestVersion(path2) {
39571
40004
  function readHermesPluginEvidence(env = process.env) {
39572
40005
  const host = hermesConfigRoot(env);
39573
40006
  const root = hermesPluginRoot(env);
39574
- const installRecordPresent = (0, import_node_fs45.existsSync)(root);
39575
- const manifestPath = (0, import_node_path41.join)(root, "plugin.yaml");
40007
+ const installRecordPresent = (0, import_node_fs47.existsSync)(root);
40008
+ const manifestPath = (0, import_node_path42.join)(root, "plugin.yaml");
39576
40009
  let installedVersion;
39577
40010
  let manifest = "missing";
39578
40011
  try {
39579
- const text = (0, import_node_fs45.readFileSync)(manifestPath, "utf8");
40012
+ const text = (0, import_node_fs47.readFileSync)(manifestPath, "utf8");
39580
40013
  let version;
39581
40014
  try {
39582
40015
  const parsed = JSON.parse(text).version;
@@ -39590,20 +40023,20 @@ function readHermesPluginEvidence(env = process.env) {
39590
40023
  if (version) {
39591
40024
  installedVersion = version;
39592
40025
  manifest = "valid";
39593
- } else if ((0, import_node_fs45.existsSync)(manifestPath)) manifest = "invalid";
40026
+ } else if ((0, import_node_fs47.existsSync)(manifestPath)) manifest = "invalid";
39594
40027
  } catch {
39595
- if ((0, import_node_fs45.existsSync)(manifestPath)) manifest = "invalid";
40028
+ if ((0, import_node_fs47.existsSync)(manifestPath)) manifest = "invalid";
39596
40029
  }
39597
40030
  let skills = false;
39598
40031
  try {
39599
- skills = (0, import_node_fs45.existsSync)((0, import_node_path41.join)(root, "skills")) && (0, import_node_fs45.statSync)((0, import_node_path41.join)(root, "skills")).isDirectory();
40032
+ skills = (0, import_node_fs47.existsSync)((0, import_node_path42.join)(root, "skills")) && (0, import_node_fs47.statSync)((0, import_node_path42.join)(root, "skills")).isDirectory();
39600
40033
  } catch {
39601
40034
  }
39602
40035
  return {
39603
- hostPresent: (0, import_node_fs45.existsSync)(host),
40036
+ hostPresent: (0, import_node_fs47.existsSync)(host),
39604
40037
  installRecordPresent,
39605
40038
  manifest,
39606
- payloadPresent: (0, import_node_fs45.existsSync)((0, import_node_path41.join)(root, "__init__.py")) && skills && manifest === "valid",
40039
+ payloadPresent: (0, import_node_fs47.existsSync)((0, import_node_path42.join)(root, "__init__.py")) && skills && manifest === "valid",
39607
40040
  ...installedVersion ? { installedVersion } : {}
39608
40041
  };
39609
40042
  }
@@ -39611,7 +40044,7 @@ function installedSurfacePluginVersion(surface) {
39611
40044
  const token = surfaceToken(surface);
39612
40045
  if (token === "kilo") {
39613
40046
  try {
39614
- const stamp = (0, import_node_fs45.readFileSync)((0, import_node_path41.join)((0, import_node_os20.homedir)(), ".kilo", ".mmi-kilo-version"), "utf8").trim();
40047
+ const stamp = (0, import_node_fs47.readFileSync)((0, import_node_path42.join)((0, import_node_os20.homedir)(), ".kilo", ".mmi-kilo-version"), "utf8").trim();
39615
40048
  return stamp || void 0;
39616
40049
  } catch {
39617
40050
  return void 0;
@@ -39619,13 +40052,13 @@ function installedSurfacePluginVersion(surface) {
39619
40052
  }
39620
40053
  if (token === "hermes") return readHermesPluginEvidence().installedVersion;
39621
40054
  if (token === "cursor") {
39622
- return manifestVersion((0, import_node_path41.join)(cursorLocalPluginRoot(), ".cursor-plugin", "plugin.json"));
40055
+ return manifestVersion((0, import_node_path42.join)(cursorLocalPluginRoot(), ".cursor-plugin", "plugin.json"));
39623
40056
  }
39624
40057
  if (token === "jervcode") {
39625
40058
  return installedJervCodePackageVersion();
39626
40059
  }
39627
40060
  if (token === "kimi") {
39628
- return manifestVersion((0, import_node_path41.join)(surfaceConfigRoot(surface), "plugins", "managed", "mmi", ".kimi-plugin", "plugin.json"));
40061
+ return manifestVersion((0, import_node_path42.join)(surfaceConfigRoot(surface), "plugins", "managed", "mmi", ".kimi-plugin", "plugin.json"));
39629
40062
  }
39630
40063
  if (token === "claude") return installedClaudePluginVersion();
39631
40064
  if (token !== "codex") return void 0;
@@ -39659,13 +40092,13 @@ function worktreeRootSync() {
39659
40092
  }
39660
40093
  var gitignorePath = () => {
39661
40094
  const root = worktreeRootSync();
39662
- return root === null ? null : (0, import_node_path41.join)(root, ".gitignore");
40095
+ return root === null ? null : (0, import_node_path42.join)(root, ".gitignore");
39663
40096
  };
39664
40097
  function readGitignore() {
39665
40098
  const path2 = gitignorePath();
39666
40099
  if (path2 === null) return null;
39667
40100
  try {
39668
- return (0, import_node_fs45.readFileSync)(path2, "utf8");
40101
+ return (0, import_node_fs47.readFileSync)(path2, "utf8");
39669
40102
  } catch {
39670
40103
  return null;
39671
40104
  }
@@ -39674,14 +40107,14 @@ function writeGitignore(content) {
39674
40107
  const path2 = gitignorePath();
39675
40108
  if (path2 === null) return false;
39676
40109
  try {
39677
- (0, import_node_fs45.writeFileSync)(path2, content, "utf8");
40110
+ (0, import_node_fs47.writeFileSync)(path2, content, "utf8");
39678
40111
  return true;
39679
40112
  } catch {
39680
40113
  return false;
39681
40114
  }
39682
40115
  }
39683
40116
  function lineEndingState(root) {
39684
- const attributesPresent = (0, import_node_fs45.existsSync)((0, import_node_path41.join)(root, ".gitattributes"));
40117
+ const attributesPresent = (0, import_node_fs47.existsSync)((0, import_node_path42.join)(root, ".gitattributes"));
39685
40118
  try {
39686
40119
  const output = execFileCapture("git", ["-C", root, "ls-files", "--eol", "--", ":(glob)**/*.sh"], {
39687
40120
  windowsHide: true
@@ -39748,8 +40181,8 @@ ${r.stderr ?? ""}`).catch(() => "");
39748
40181
  function ghMultiAccountCaveat(announcedLogin) {
39749
40182
  try {
39750
40183
  const hostsPath = ghHostsConfigPath(process.env, process.platform);
39751
- if (!hostsPath || !(0, import_node_fs46.existsSync)(hostsPath)) return void 0;
39752
- return ghAccountCaveat(announcedLogin, parseGhHostsAccounts((0, import_node_fs46.readFileSync)(hostsPath, "utf8")));
40184
+ if (!hostsPath || !(0, import_node_fs48.existsSync)(hostsPath)) return void 0;
40185
+ return ghAccountCaveat(announcedLogin, parseGhHostsAccounts((0, import_node_fs48.readFileSync)(hostsPath, "utf8")));
39753
40186
  } catch {
39754
40187
  return void 0;
39755
40188
  }
@@ -39757,7 +40190,7 @@ function ghMultiAccountCaveat(announcedLogin) {
39757
40190
  var ENV_HEAL_LOCK_STALE_MS = 10 * 6e4;
39758
40191
  var ENV_HEAL_LOCK_MAX_WAIT_MS = 2 * 6e4;
39759
40192
  function envHealLockPath(home) {
39760
- return (0, import_node_path42.join)(home, ".claude", "plugins", ".mmi-env-heal.lock");
40193
+ return (0, import_node_path43.join)(home, ".claude", "plugins", ".mmi-env-heal.lock");
39761
40194
  }
39762
40195
  async function withEnvHealLock(what, run) {
39763
40196
  try {
@@ -39894,7 +40327,7 @@ function mmiDoctorDeps(opts = {}) {
39894
40327
  );
39895
40328
  const result = applyPluginCachePlan(
39896
40329
  plan,
39897
- (p) => (0, import_node_fs46.rmSync)(p, { recursive: true }),
40330
+ (p) => (0, import_node_fs48.rmSync)(p, { recursive: true }),
39898
40331
  stagingApplyFsGuard(configRoot)
39899
40332
  );
39900
40333
  return {
@@ -39932,7 +40365,7 @@ function mmiDoctorDeps(opts = {}) {
39932
40365
  // adopted the generated routing index (MMG-Unlive: `docs/Archive/**` only, no index, no gate.yml) would
39933
40366
  // get a permanent — demanding an artifact it never asked for.
39934
40367
  docsIndexState: (root) => {
39935
- if (!(0, import_node_fs46.existsSync)((0, import_node_path42.join)(root, DOCS_INDEX_PATH))) return void 0;
40368
+ if (!(0, import_node_fs48.existsSync)((0, import_node_path43.join)(root, DOCS_INDEX_PATH))) return void 0;
39936
40369
  const real = createDocsIndexDeps(root);
39937
40370
  let docs2;
39938
40371
  const listDocs = () => docs2 ??= real.listDocs();
@@ -39941,7 +40374,7 @@ function mmiDoctorDeps(opts = {}) {
39941
40374
  },
39942
40375
  // #4168: working-tree heal — write if drifted, then re-check. Commit remains the operator's step.
39943
40376
  healDocsIndex: (root) => {
39944
- if (!(0, import_node_fs46.existsSync)((0, import_node_path42.join)(root, DOCS_INDEX_PATH))) return { drift: false, docCount: 0 };
40377
+ if (!(0, import_node_fs48.existsSync)((0, import_node_path43.join)(root, DOCS_INDEX_PATH))) return { drift: false, docCount: 0 };
39945
40378
  const real = createDocsIndexDeps(root);
39946
40379
  let docs2;
39947
40380
  const listDocs = () => docs2 ??= real.listDocs();
@@ -40033,7 +40466,7 @@ function mmiDoctorDeps(opts = {}) {
40033
40466
  repoIndexCloudState: async (root) => {
40034
40467
  let localV4 = { state: "absent" };
40035
40468
  try {
40036
- const parsed = JSON.parse((0, import_node_fs46.readFileSync)(repoIndexV4StorePath(root), "utf8"));
40469
+ const parsed = JSON.parse((0, import_node_fs48.readFileSync)(repoIndexV4StorePath(root), "utf8"));
40037
40470
  const state = parsed.status?.state;
40038
40471
  if (parsed.schemaVersion === 4 && (state === "ready" || state === "degraded")) {
40039
40472
  const chunks = parsed.manifest?.chunks?.length ?? 0;
@@ -40326,19 +40759,19 @@ program2.hook("preAction", async (_thisCommand, actionCommand) => {
40326
40759
  });
40327
40760
  var rules = program2.command("rules").description("org-managed .gitignore delivery");
40328
40761
  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) => {
40329
- const path2 = (0, import_node_path42.join)(process.cwd(), ".gitignore");
40330
- const current = (0, import_node_fs46.existsSync)(path2) ? (0, import_node_fs46.readFileSync)(path2, "utf8") : null;
40762
+ const path2 = (0, import_node_path43.join)(process.cwd(), ".gitignore");
40763
+ const current = (0, import_node_fs48.existsSync)(path2) ? (0, import_node_fs48.readFileSync)(path2, "utf8") : null;
40331
40764
  const plan = planManagedGitignore(current);
40332
40765
  const drift = [...plan.added.map((l) => `+${l}`), ...plan.removed.map((l) => `-${l}`)].join(", ") || "block normalize";
40333
40766
  if (opts.json) {
40334
- if (opts.write && plan.changed) (0, import_node_fs46.writeFileSync)(path2, plan.content, "utf8");
40767
+ if (opts.write && plan.changed) (0, import_node_fs48.writeFileSync)(path2, plan.content, "utf8");
40335
40768
  console.log(JSON.stringify(plan, null, 2));
40336
40769
  if (!opts.write && plan.changed) process.exitCode = 1;
40337
40770
  return;
40338
40771
  }
40339
40772
  if (opts.write) {
40340
40773
  if (plan.changed) {
40341
- (0, import_node_fs46.writeFileSync)(path2, plan.content, "utf8");
40774
+ (0, import_node_fs48.writeFileSync)(path2, plan.content, "utf8");
40342
40775
  console.log(`mmi-cli devops org rules gitignore: updated .gitignore (${drift})`);
40343
40776
  } else {
40344
40777
  console.log("mmi-cli devops org rules gitignore: up to date");
@@ -41217,7 +41650,7 @@ project.command("set [owner/repo]").description("upsert project META (idempotent
41217
41650
  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`);
41218
41651
  if (o.secretsFile) {
41219
41652
  try {
41220
- vars.push(`secrets=${(0, import_node_fs46.readFileSync)(o.secretsFile, "utf8")}`);
41653
+ vars.push(`secrets=${(0, import_node_fs48.readFileSync)(o.secretsFile, "utf8")}`);
41221
41654
  } catch (e) {
41222
41655
  return fail(`org project set: cannot read --secrets-file ${o.secretsFile}: ${e.message}`);
41223
41656
  }
@@ -42042,11 +42475,11 @@ pr.command("view <number>").description("read a PR as structured JSON (merged st
42042
42475
  }
42043
42476
  });
42044
42477
  async function listCiWorkflowPaths(cwd = process.cwd()) {
42045
- const wfDir = (0, import_node_path42.join)(cwd, ".github", "workflows");
42046
- if (!(0, import_node_fs46.existsSync)(wfDir)) return [];
42047
- return (0, import_node_fs46.readdirSync)(wfDir).filter((name) => /\.(ya?ml)$/i.test(name)).filter((name) => {
42478
+ const wfDir = (0, import_node_path43.join)(cwd, ".github", "workflows");
42479
+ if (!(0, import_node_fs48.existsSync)(wfDir)) return [];
42480
+ return (0, import_node_fs48.readdirSync)(wfDir).filter((name) => /\.(ya?ml)$/i.test(name)).filter((name) => {
42048
42481
  try {
42049
- return workflowReportsPrChecks((0, import_node_fs46.readFileSync)((0, import_node_path42.join)(wfDir, name), "utf8"));
42482
+ return workflowReportsPrChecks((0, import_node_fs48.readFileSync)((0, import_node_path43.join)(wfDir, name), "utf8"));
42050
42483
  } catch {
42051
42484
  return true;
42052
42485
  }
@@ -42098,16 +42531,16 @@ function ciAuditDeps() {
42098
42531
  // gate re-seed step is skipped gracefully rather than failing mid-run.
42099
42532
  readSeedFile: (path2) => {
42100
42533
  if (!root) return null;
42101
- const fullPath = (0, import_node_path42.join)(root, path2);
42102
- return (0, import_node_fs46.existsSync)(fullPath) ? (0, import_node_fs46.readFileSync)(fullPath, "utf8") : null;
42534
+ const fullPath = (0, import_node_path43.join)(root, path2);
42535
+ return (0, import_node_fs48.existsSync)(fullPath) ? (0, import_node_fs48.readFileSync)(fullPath, "utf8") : null;
42103
42536
  }
42104
42537
  };
42105
42538
  }
42106
42539
  function hubRoot() {
42107
- const fromPkg = (0, import_node_path42.join)(__dirname, "..", "..");
42540
+ const fromPkg = (0, import_node_path43.join)(__dirname, "..", "..");
42108
42541
  const marker = "skills/bootstrap/seeds/manifest.json";
42109
- if ((0, import_node_fs46.existsSync)((0, import_node_path42.join)(fromPkg, marker))) return fromPkg;
42110
- if ((0, import_node_fs46.existsSync)((0, import_node_path42.join)(process.cwd(), marker))) return process.cwd();
42542
+ if ((0, import_node_fs48.existsSync)((0, import_node_path43.join)(fromPkg, marker))) return fromPkg;
42543
+ if ((0, import_node_fs48.existsSync)((0, import_node_path43.join)(process.cwd(), marker))) return process.cwd();
42111
42544
  return null;
42112
42545
  }
42113
42546
  async function waitLoopCorePool(label) {
@@ -42370,18 +42803,19 @@ pr.command("land <number>").description("agent merge path (#1440): train probe \
42370
42803
  else printLine(`pr land: ${result.status}${result.error ? ` \u2014 ${result.error}` : ""}`);
42371
42804
  if (result.status === "failed") process.exitCode = 1;
42372
42805
  });
42373
- jsonParity(pr.command("merge <number>").description("merge a PR (squash by default); the host owns local workspace lifecycle; on no-ci repos run pr ci-policy / checks-wait first (#1432)").option("--squash", "squash merge (default)").option("--merge", "create a merge commit").option("--rebase", "rebase merge").option("--repo <owner/repo>", "target repo (defaults to the current repo)").option("--auto", "enable auto-merge \u2014 merge once the base-branch policy is satisfied (use for policy-gated repos)").option("--wait", `wait for checks to reach a terminal passing verdict before merging (default budget ${PR_CHECKS_TIMEOUT_MS / 6e4}m)`).option("--force", "acknowledge and merge past a remaining prunable/severe scratch housekeeping block (advisory kept plans/ never blocks, #3012) or a negated-closing-keyword (#3718) or ambiguous-cross-repo-closing (#4279) refusal")).action(async (number, o) => {
42806
+ jsonParity(pr.command("merge <number>").description("merge a PR (squash by default); archives gitignored tmp/** before worktree teardown; on no-ci repos run pr ci-policy / checks-wait first (#1432, #5679)").option("--squash", "squash merge (default)").option("--merge", "create a merge commit").option("--rebase", "rebase merge").option("--repo <owner/repo>", "target repo (defaults to the current repo)").option("--auto", "enable auto-merge \u2014 merge once the base-branch policy is satisfied (use for policy-gated repos)").option("--wait", `wait for checks to reach a terminal passing verdict before merging (default budget ${PR_CHECKS_TIMEOUT_MS / 6e4}m)`).option("--preserve-worktree", "after merge, keep the local PR worktree/branch for an active batch (#1888)").option("--gc", "acknowledge deleting unarchived gitignored tmp/** evidence newer than the branch base (#5679)").option("--force", "acknowledge and merge past a remaining prunable/severe scratch housekeeping block (advisory kept plans/ never blocks, #3012) or a negated-closing-keyword (#3718) or ambiguous-cross-repo-closing (#4279) refusal")).action(async (number, o) => {
42374
42807
  const method = o.rebase ? "--rebase" : o.merge ? "--merge" : "--squash";
42375
42808
  const repoArgs = o.repo ? ["--repo", o.repo] : [];
42376
42809
  const repoForPostCleanup = await resolveRepo(o.repo) ?? o.repo;
42377
- const prMeta = await execFileP2("gh", ["pr", "view", number, ...repoArgs, "--json", "headRefName,baseRefName", "--jq", "{head: .headRefName, base: .baseRefName}"], { timeout: GC_GH_TIMEOUT_MS }).then((r) => JSON.parse(r.stdout)).catch(async (e) => {
42810
+ const prMeta = await execFileP2("gh", ["pr", "view", number, ...repoArgs, "--json", "headRefName,baseRefName,headRefOid", "--jq", "{head: .headRefName, base: .baseRefName, oid: .headRefOid}"], { timeout: GC_GH_TIMEOUT_MS }).then((r) => JSON.parse(r.stdout)).catch(async (e) => {
42378
42811
  if (!isGitHubRateLimitError(e) || !repoForPostCleanup) throw e;
42379
42812
  console.warn(`pr merge: gh GraphQL rate-limited \u2014 reading PR #${number} via REST instead (#4588).`);
42380
42813
  const snapshot = await fetchRestPrSnapshot(number, repoForPostCleanup);
42381
- return { head: snapshot.headRef, base: snapshot.baseRef };
42814
+ return { head: snapshot.headRef, base: snapshot.baseRef, oid: snapshot.headSha };
42382
42815
  });
42383
42816
  const headRef = prMeta.head;
42384
42817
  const baseRef = prMeta.base;
42818
+ const headRefOid = (prMeta.oid ?? "").trim() || void 0;
42385
42819
  const devDeployDeps = repoForPostCleanup ? registryClientDeps(await loadConfig()) : void 0;
42386
42820
  const devDeployPlan = repoForPostCleanup && devDeployDeps ? await planDevDeployOnDevelopmentMerge(repoForPostCleanup, baseRef, devDeployDeps).catch(() => ({ applicable: false, reason: "unreadable" })) : { applicable: false, reason: "unreadable" };
42387
42821
  const closingGuardInput = await withAlreadyClosedCommitTargets(
@@ -42401,6 +42835,8 @@ jsonParity(pr.command("merge <number>").description("merge a PR (squash by defau
42401
42835
  if (closingGuardVerdict.message) console.warn(closingGuardVerdict.message);
42402
42836
  const startingPath = (await execFileP2("git", ["rev-parse", "--show-toplevel"], { timeout: GIT_TIMEOUT_MS }).catch(() => ({ stdout: "" }))).stdout.trim();
42403
42837
  const housekeeping = assertPrMergeHousekeepingClean(startingPath || process.cwd(), "pr merge", { force: o.force });
42838
+ const beforeWorktreesRead = await execFileP2("git", ["worktree", "list", "--porcelain"], { timeout: GIT_TIMEOUT_MS }).then((r) => ({ state: "ok", stdout: r.stdout })).catch((e) => ({ state: "failed", error: e.message || "git worktree list failed" }));
42839
+ const beforeWorktrees = beforeWorktreesRead.state === "ok" ? parseGitWorktreePorcelain(beforeWorktreesRead.stdout) : [];
42404
42840
  const ciHeadRef = repoForPostCleanup ? await prHeadRefForCiProbe(number, repoForPostCleanup) : void 0;
42405
42841
  const ciPolicy = await resolveMergeCiPolicyForCheckout(o.repo, ciHeadRef);
42406
42842
  if (o.wait) {
@@ -42462,7 +42898,7 @@ jsonParity(pr.command("merge <number>").description("merge a PR (squash by defau
42462
42898
  }
42463
42899
  if (!repoForPostCleanup) throw e;
42464
42900
  console.warn(`pr merge: gh GraphQL rate-limited \u2014 merging PR #${number} via REST PUT instead (#4588).`);
42465
- const commitMessage = bodyFile ? (0, import_node_fs46.readFileSync)(bodyFile, "utf8") : void 0;
42901
+ const commitMessage = bodyFile ? (0, import_node_fs48.readFileSync)(bodyFile, "utf8") : void 0;
42466
42902
  await defaultGitHubClient().rest("PUT", `repos/${repoForPostCleanup}/pulls/${number}/merge`, {
42467
42903
  body: { merge_method: method.slice(2), ...commitMessage ? { commit_message: commitMessage } : {} },
42468
42904
  timeoutMs: GH_MUTATION_TIMEOUT_MS
@@ -42540,6 +42976,30 @@ jsonParity(pr.command("merge <number>").description("merge a PR (squash by defau
42540
42976
  return;
42541
42977
  }
42542
42978
  const remoteBranch = { branch: headRef, existedBefore: remoteBefore, attempted: false, reason: remoteNotAttemptedReason };
42979
+ const primaryRoot = beforeWorktrees[0]?.path ?? (startingPath || process.cwd());
42980
+ let localCleanup;
42981
+ try {
42982
+ localCleanup = await cleanupPrMergeLocalBranch(headRef, {
42983
+ beforeWorktrees,
42984
+ startingPath,
42985
+ baseRef,
42986
+ primaryRoot,
42987
+ preserveWorktree: o.preserveWorktree,
42988
+ gcAcknowledged: o.gc,
42989
+ expectedHeadOid: headRefOid,
42990
+ pathExists: (p) => (0, import_node_fs48.existsSync)(p),
42991
+ execGit: async (args) => (await execFileP2("git", args, { timeout: GIT_TIMEOUT_MS })).stdout
42992
+ });
42993
+ } catch (e) {
42994
+ localCleanup = {
42995
+ branch: headRef,
42996
+ localBranch: {
42997
+ name: headRef,
42998
+ status: "failed",
42999
+ error: e instanceof Error ? e.message : String(e)
43000
+ }
43001
+ };
43002
+ }
42543
43003
  const boardAdvance = await advanceClosedIssuesToDone2(number, repoForPostCleanup);
42544
43004
  const crossRepoFilingIssue = repoForPostCleanup ? await reconcileCrossRepoFilingIssue(defaultGitHubClient(), repoForPostCleanup, Number(number)) : { status: "failed", error: "could not resolve the PR repo for cross-repo filing-issue reconciliation" };
42545
43005
  const recovery = repoForPostCleanup ? buildPostMergeReconRecovery({
@@ -42583,6 +43043,8 @@ jsonParity(pr.command("merge <number>").description("merge a PR (squash by defau
42583
43043
  ...methodField ? { method: methodField } : {},
42584
43044
  remoteBranch,
42585
43045
  housekeeping,
43046
+ ...localCleanup?.worktree ? { worktree: localCleanup.worktree } : {},
43047
+ ...localCleanup?.localBranch ? { localBranch: localCleanup.localBranch } : {},
42586
43048
  // `boardAdvance` keeps its published array shape; `boardAdvanceStatus` is the field a caller reads to
42587
43049
  // tell "merged clean" from "merged, but the board did not follow" without guessing from the exit code.
42588
43050
  ...boardAdvance.entries.length ? { boardAdvance: boardAdvance.entries } : {},
@@ -42591,6 +43053,7 @@ jsonParity(pr.command("merge <number>").description("merge a PR (squash by defau
42591
43053
  ...recovery ? { postMergeReconRecovery: recovery } : {},
42592
43054
  ...devDeploy ? { devDeploy: devDeploy.ok ? { dispatched: true } : { dispatched: false, manual: devDeployManual } } : {}
42593
43055
  }));
43056
+ for (const line of renderPrMergeCleanupLines(localCleanup)) console.warn(line);
42594
43057
  if (devDeploy && !devDeploy.ok) {
42595
43058
  console.error(`pr merge: ${devDeployManual} (${devDeploy.detail})`);
42596
43059
  }
@@ -43158,12 +43621,12 @@ access.command("audit").description("audit collaborator roles + train-branch pus
43158
43621
  targets = resolution.targets;
43159
43622
  }
43160
43623
  const derivedMatrix = registryProjects ? accessMatrixFromProjects(registryProjects) : {};
43161
- const fileMatrix = (0, import_node_fs46.existsSync)("access-matrix.json") ? loadAccessMatrix((0, import_node_fs46.readFileSync)("access-matrix.json", "utf8")) : {};
43624
+ const fileMatrix = (0, import_node_fs48.existsSync)("access-matrix.json") ? loadAccessMatrix((0, import_node_fs48.readFileSync)("access-matrix.json", "utf8")) : {};
43162
43625
  const matrix = mergeAccessMatrix(fileMatrix, derivedMatrix);
43163
43626
  const derivedContracts = registryProjects ? dataAccessContractsFromProjects(registryProjects) : { consumers: {} };
43164
- const fileContracts = (0, import_node_fs46.existsSync)("data-access-contracts.json") ? loadDataAccessContracts((0, import_node_fs46.readFileSync)("data-access-contracts.json", "utf8")) : { consumers: {} };
43627
+ const fileContracts = (0, import_node_fs48.existsSync)("data-access-contracts.json") ? loadDataAccessContracts((0, import_node_fs48.readFileSync)("data-access-contracts.json", "utf8")) : { consumers: {} };
43165
43628
  const dataAccess = mergeDataAccessContracts(fileContracts, derivedContracts);
43166
- const sanctioned = (0, import_node_fs46.existsSync)("access-matrix.json") ? loadSanctionedAdmins((0, import_node_fs46.readFileSync)("access-matrix.json", "utf8")) : {};
43629
+ const sanctioned = (0, import_node_fs48.existsSync)("access-matrix.json") ? loadSanctionedAdmins((0, import_node_fs48.readFileSync)("access-matrix.json", "utf8")) : {};
43167
43630
  const report = await auditOrgAccess(targets, deps, matrix, dataAccess, sanctioned);
43168
43631
  console.log(o.json ? JSON.stringify(report, null, 2) : renderAccessReport(report));
43169
43632
  if (!report.ok) process.exitCode = 1;
@@ -43195,16 +43658,16 @@ function directoryBytes(path2) {
43195
43658
  let total = 0;
43196
43659
  let entries;
43197
43660
  try {
43198
- entries = (0, import_node_fs46.readdirSync)(path2, { withFileTypes: true });
43661
+ entries = (0, import_node_fs48.readdirSync)(path2, { withFileTypes: true });
43199
43662
  } catch {
43200
43663
  return 0;
43201
43664
  }
43202
43665
  for (const entry of entries) {
43203
- const child2 = (0, import_node_path42.join)(path2, entry.name);
43666
+ const child2 = (0, import_node_path43.join)(path2, entry.name);
43204
43667
  if (entry.isDirectory()) total += directoryBytes(child2);
43205
43668
  else {
43206
43669
  try {
43207
- total += (0, import_node_fs46.statSync)(child2).size;
43670
+ total += (0, import_node_fs48.statSync)(child2).size;
43208
43671
  } catch {
43209
43672
  }
43210
43673
  }
@@ -43212,25 +43675,25 @@ function directoryBytes(path2) {
43212
43675
  return total;
43213
43676
  }
43214
43677
  function listDirEntries(dir) {
43215
- return (0, import_node_fs46.readdirSync)(dir, { withFileTypes: true }).map((d) => ({ name: d.name, isDirectory: d.isDirectory() }));
43678
+ return (0, import_node_fs48.readdirSync)(dir, { withFileTypes: true }).map((d) => ({ name: d.name, isDirectory: d.isDirectory() }));
43216
43679
  }
43217
43680
  function readInstalledPluginRefs(configRoot) {
43218
43681
  const p = installedPluginsPathForConfig(configRoot);
43219
- if (!(0, import_node_fs46.existsSync)(p)) return [];
43682
+ if (!(0, import_node_fs48.existsSync)(p)) return [];
43220
43683
  try {
43221
- return installedPluginPaths((0, import_node_fs46.readFileSync)(p, "utf8"));
43684
+ return installedPluginPaths((0, import_node_fs48.readFileSync)(p, "utf8"));
43222
43685
  } catch {
43223
43686
  return null;
43224
43687
  }
43225
43688
  }
43226
43689
  function pluginCacheFsDeps(configRoot, dirBytes) {
43227
43690
  return {
43228
- exists: (p) => (0, import_node_fs46.existsSync)(p),
43229
- listVersionDirs: (root) => (0, import_node_fs46.readdirSync)(root, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name),
43691
+ exists: (p) => (0, import_node_fs48.existsSync)(p),
43692
+ listVersionDirs: (root) => (0, import_node_fs48.readdirSync)(root, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name),
43230
43693
  dirBytes,
43231
- listStagingDirs: (root) => (0, import_node_fs46.readdirSync)(root, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => {
43694
+ listStagingDirs: (root) => (0, import_node_fs48.readdirSync)(root, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => {
43232
43695
  try {
43233
- return { name: d.name, mtimeMs: newestMtimeMs((0, import_node_path42.join)(root, d.name), listDirEntries, (p) => (0, import_node_fs46.statSync)(p).mtimeMs) };
43696
+ return { name: d.name, mtimeMs: newestMtimeMs((0, import_node_path43.join)(root, d.name), listDirEntries, (p) => (0, import_node_fs48.statSync)(p).mtimeMs) };
43234
43697
  } catch {
43235
43698
  return { name: d.name, mtimeMs: Date.now() };
43236
43699
  }
@@ -43244,10 +43707,10 @@ function stagingApplyFsGuard(configRoot) {
43244
43707
  return {
43245
43708
  referencedPaths: () => readInstalledPluginRefs(configRoot),
43246
43709
  mtimeMs: (name) => {
43247
- const p = (0, import_node_path42.join)(stagingRoot, name);
43248
- if (!(0, import_node_fs46.existsSync)(p)) return null;
43710
+ const p = (0, import_node_path43.join)(stagingRoot, name);
43711
+ if (!(0, import_node_fs48.existsSync)(p)) return null;
43249
43712
  try {
43250
- return newestMtimeMs(p, listDirEntries, (q) => (0, import_node_fs46.statSync)(q).mtimeMs);
43713
+ return newestMtimeMs(p, listDirEntries, (q) => (0, import_node_fs48.statSync)(q).mtimeMs);
43251
43714
  } catch {
43252
43715
  return null;
43253
43716
  }
@@ -43273,7 +43736,7 @@ program2.command("plugin-prune").description(`prune stale cached MMI plugin vers
43273
43736
  { withBytes: true, configRoot, includeStaging: surface !== "codex" }
43274
43737
  );
43275
43738
  const anythingToDelete = plan.prune.length > 0 || plan.staging.length > 0;
43276
- const result = o.apply && anythingToDelete ? applyPluginCachePlan(plan, (p) => (0, import_node_fs46.rmSync)(p, { recursive: true, force: true }), stagingApplyFsGuard(configRoot)) : void 0;
43739
+ const result = o.apply && anythingToDelete ? applyPluginCachePlan(plan, (p) => (0, import_node_fs48.rmSync)(p, { recursive: true, force: true }), stagingApplyFsGuard(configRoot)) : void 0;
43277
43740
  const warnings = plan.prune.length > 0 ? [CONCURRENT_SESSION_WARNING] : [];
43278
43741
  if (o.json) console.log(JSON.stringify({ ...plan, warnings, applied: result ?? null }));
43279
43742
  else console.log(renderPluginCachePlan(plan, result));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mutmutco/cli",
3
- "version": "4.1.9",
3
+ "version": "4.1.11",
4
4
  "description": "MMI Future CLI — the org dev toolbox and shared cross-IDE engine for every registry-declared MMI coding surface.",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",