@mutmutco/cli 3.79.0 → 3.81.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 +634 -414
  2. package/package.json +1 -1
package/dist/main.cjs CHANGED
@@ -3416,8 +3416,8 @@ var program = new Command();
3416
3416
 
3417
3417
  // src/index.ts
3418
3418
  var import_promises10 = require("node:fs/promises");
3419
- var import_node_fs34 = require("node:fs");
3420
- var import_node_child_process15 = require("node:child_process");
3419
+ var import_node_fs35 = require("node:fs");
3420
+ var import_node_child_process16 = require("node:child_process");
3421
3421
 
3422
3422
  // src/cli-shared.ts
3423
3423
  var import_node_child_process3 = require("node:child_process");
@@ -4483,6 +4483,7 @@ function trackedPathStatus(c, realPath, repoAnchor) {
4483
4483
  const gitPath = c.kind === "dir" ? `${rel.replace(/\/+$/, "")}/` : rel;
4484
4484
  try {
4485
4485
  (0, import_node_child_process4.execFileSync)("git", ["-C", repoAnchor, "rev-parse", "--is-inside-work-tree"], {
4486
+ windowsHide: true,
4486
4487
  stdio: ["ignore", "ignore", "ignore"],
4487
4488
  timeout: 1e4
4488
4489
  });
@@ -4491,6 +4492,7 @@ function trackedPathStatus(c, realPath, repoAnchor) {
4491
4492
  }
4492
4493
  try {
4493
4494
  const out = (0, import_node_child_process4.execFileSync)("git", ["-C", repoAnchor, "ls-files", "-z", "--", gitPath], {
4495
+ windowsHide: true,
4494
4496
  encoding: "utf8",
4495
4497
  stdio: ["ignore", "pipe", "ignore"],
4496
4498
  timeout: 1e4
@@ -6259,6 +6261,8 @@ function readSettingsAutoUpdate(raw, name) {
6259
6261
  var AUTO_UPDATE_TOGGLE_STEPS = "`/plugin` \u2192 Marketplaces tab \u2192 select the marketplace \u2192 Enable auto-update";
6260
6262
  var CATALOG_CONTENT_REF = "main";
6261
6263
  var CATALOG_REF_PIN_STEPS = `add \`"ref": "${CATALOG_CONTENT_REF}"\` to this marketplace's \`source\` in ~/.claude/plugins/known_marketplaces.json, then restart Claude`;
6264
+ var PIN_HEAL_COMMAND = "run `mmi-cli doctor --apply`";
6265
+ var ORG_MARKETPLACE_PINS = { autoUpdate: true, ref: CATALOG_CONTENT_REF };
6262
6266
  function resolveCatalogRef(probe) {
6263
6267
  const { name, registered, ref, autoUpdate } = probe;
6264
6268
  if (!registered) return void 0;
@@ -6281,7 +6285,7 @@ function resolveCatalogRef(probe) {
6281
6285
  reason: `no ref pinned \u2014 the catalog is served from the repo's default branch while plugin content is pinned to ${CATALOG_CONTENT_REF}`
6282
6286
  };
6283
6287
  }
6284
- function checkMarketplaceAutoUpdate(probe) {
6288
+ function checkMarketplaceAutoUpdate(probe, healable = false) {
6285
6289
  if (!probe) return null;
6286
6290
  const evidence = [`${probe.name}: auto-update ${probe.effective ? "on" : "off"} \u2014 ${probe.reason}`];
6287
6291
  if (probe.effective) {
@@ -6292,11 +6296,11 @@ function checkMarketplaceAutoUpdate(probe) {
6292
6296
  ok: true,
6293
6297
  warn: true,
6294
6298
  label: `marketplace auto-update (${probe.name})`,
6295
- detail: `off \u2014 this machine will not pick up a new plugin release on its own; turn it on with ${AUTO_UPDATE_TOGGLE_STEPS}`,
6299
+ detail: `off \u2014 this machine will not pick up a new plugin release on its own; ${healable ? `${PIN_HEAL_COMMAND} to turn it on` : `turn it on with ${AUTO_UPDATE_TOGGLE_STEPS}`}`,
6296
6300
  verbose: evidence
6297
6301
  };
6298
6302
  }
6299
- function checkMarketplaceCatalogRef(probe) {
6303
+ function checkMarketplaceCatalogRef(probe, healable = false) {
6300
6304
  if (!probe) return null;
6301
6305
  const label = `marketplace catalog ref (${probe.name})`;
6302
6306
  const evidence = [`${probe.name}: ${probe.reason}`, `auto-update: ${probe.autoUpdate ? "on" : "off"}`];
@@ -6313,7 +6317,7 @@ function checkMarketplaceCatalogRef(probe) {
6313
6317
  // ✗ branch carried it in `fix` only because `renderCheckLine` discarded `detail` on failure — one
6314
6318
  // row, two conventions, decided by which field would actually print.
6315
6319
  detail: `${drift} while auto-update is ON`,
6316
- fix: `this machine installs plugin releases advertised by a branch nobody released; ${CATALOG_REF_PIN_STEPS}`,
6320
+ fix: `this machine installs plugin releases advertised by a branch nobody released; ${healable ? `${PIN_HEAL_COMMAND} to pin it` : CATALOG_REF_PIN_STEPS}`,
6317
6321
  verbose: evidence
6318
6322
  };
6319
6323
  }
@@ -6322,11 +6326,11 @@ function checkMarketplaceCatalogRef(probe) {
6322
6326
  ok: true,
6323
6327
  warn: true,
6324
6328
  label,
6325
- detail: `${drift} \u2014 harmless while auto-update is off, and the thing to fix before turning it on; ${CATALOG_REF_PIN_STEPS}`,
6329
+ detail: `${drift} \u2014 harmless while auto-update is off, and the thing to fix before turning it on; ${healable ? `${PIN_HEAL_COMMAND} to pin it` : CATALOG_REF_PIN_STEPS}`,
6326
6330
  verbose: evidence
6327
6331
  };
6328
6332
  }
6329
- function marketplaceRows(name, known, settings) {
6333
+ function marketplaceRows(name, known, settings, healable = false) {
6330
6334
  const { registered, declared, ref } = readKnownMarketplace(known, name);
6331
6335
  const autoUpdate = resolveAutoUpdate({
6332
6336
  name,
@@ -6334,8 +6338,9 @@ function marketplaceRows(name, known, settings) {
6334
6338
  declared,
6335
6339
  settingsDeclared: readSettingsAutoUpdate(settings, name)
6336
6340
  });
6337
- const rows = [checkMarketplaceAutoUpdate(autoUpdate), checkMarketplaceCatalogRef(
6338
- resolveCatalogRef({ name, registered, ref, autoUpdate: autoUpdate.effective })
6341
+ const rows = [checkMarketplaceAutoUpdate(autoUpdate, healable), checkMarketplaceCatalogRef(
6342
+ resolveCatalogRef({ name, registered, ref, autoUpdate: autoUpdate.effective }),
6343
+ healable
6339
6344
  )];
6340
6345
  return rows.filter((r) => r !== null);
6341
6346
  }
@@ -6432,7 +6437,7 @@ function scanInstallDirs(root, fs2 = realFsProbe) {
6432
6437
  return [factsFor(""), ...children.map(factsFor).filter((f) => f.hasPackageJson)];
6433
6438
  }
6434
6439
  function npmInstallTargets(dirs) {
6435
- return dirs.filter((d) => d.hasPackageJson && !d.hasNodeModules).map((d) => ({ dir: d.dir, command: d.hasLockfile ? "npm ci" : "npm install" }));
6440
+ return dirs.filter((d) => d.hasPackageJson && !d.hasNodeModules).map((d) => ({ dir: d.dir, command: d.hasLockfile ? "npm ci" : "npm install --no-package-lock" }));
6436
6441
  }
6437
6442
  function isLinkedWorktree(root, fs2 = realFsProbe) {
6438
6443
  return fs2.isFile((0, import_node_path10.join)(root, ".git"));
@@ -6619,7 +6624,7 @@ function commandLadderHint() {
6619
6624
  }
6620
6625
 
6621
6626
  // src/index.ts
6622
- var import_node_path33 = require("node:path");
6627
+ var import_node_path34 = require("node:path");
6623
6628
 
6624
6629
  // src/merge-ci-policy.ts
6625
6630
  function resolveMergeCiPolicy(input) {
@@ -9512,6 +9517,57 @@ async function runReleaseAbort(deps, options = {}) {
9512
9517
  note: `removed the unpublished ${tag} candidate and restored local main; repair development before recutting`
9513
9518
  };
9514
9519
  }
9520
+ async function runReleasePublishRetry(deps, runId, options = {}) {
9521
+ if (!options.approved) {
9522
+ throw new Error("release --retry-publish requires --apply after explicit approval; nothing was written");
9523
+ }
9524
+ if (!Number.isSafeInteger(runId) || runId <= 0) throw new Error(`invalid publish run id ${runId}`);
9525
+ const ctx = await buildTrainApplyContext(deps);
9526
+ if (!isHubControlRepo(ctx.repo)) {
9527
+ throw new Error("release --retry-publish is limited to mutmutco/MMI-Hub");
9528
+ }
9529
+ await requireCleanTree(deps);
9530
+ await runGitRemoteRead(deps, ["fetch", "origin", "--tags"]);
9531
+ const readRun = async () => {
9532
+ const raw = await deps.run("gh", ["run", "view", String(runId), "--repo", ctx.repo, "--json", "databaseId,workflowName,event,headBranch,headSha,status,conclusion,url"]);
9533
+ try {
9534
+ return JSON.parse(raw);
9535
+ } catch {
9536
+ throw new Error(`publish run ${runId} metadata was not valid JSON`);
9537
+ }
9538
+ };
9539
+ const run = await readRun();
9540
+ if (run.databaseId !== runId || run.workflowName !== "publish" || run.event !== "release" || run.status !== "completed" || run.conclusion !== "failure") {
9541
+ throw new Error(`run ${runId} is not a completed failed Hub publish release run; nothing was written`);
9542
+ }
9543
+ const tag = run.headBranch ?? "";
9544
+ const tagSha = run.headSha ?? "";
9545
+ if (!/^v\d+\.\d+\.\d+$/.test(tag) || !/^[0-9a-f]{40}$/.test(tagSha)) {
9546
+ throw new Error(`run ${runId} has invalid release identity (${tag || "(no tag)"} / ${tagSha || "(no sha)"}); nothing was written`);
9547
+ }
9548
+ if (await probeRemoteTag(deps, tag) !== tagSha) {
9549
+ throw new Error(`run ${runId} does not match origin ${tag}; nothing was written`);
9550
+ }
9551
+ await verifyPublishedRelease(deps, ctx.repo, tag, "main", tagSha);
9552
+ await deps.run("gh", ["run", "rerun", String(runId), "--repo", ctx.repo, "--failed"]);
9553
+ if (options.watch) {
9554
+ await deps.run("gh", ["run", "watch", String(runId), "--repo", ctx.repo, "--exit-status"]);
9555
+ const completed = await readRun();
9556
+ if (completed.status !== "completed" || completed.conclusion !== "success") {
9557
+ throw new Error(`publish run ${runId} did not finish successfully after retry`);
9558
+ }
9559
+ }
9560
+ return {
9561
+ command: "release-retry-publish",
9562
+ repo: ctx.repo,
9563
+ tag,
9564
+ tagSha,
9565
+ runId,
9566
+ runUrl: run.url ?? `https://github.com/${ctx.repo}/actions/runs/${runId}`,
9567
+ status: options.watch ? "success" : "pending",
9568
+ note: options.watch ? `publish run ${runId} retried and passed` : `publish run ${runId} failed jobs queued for retry`
9569
+ };
9570
+ }
9515
9571
  async function runTrainApplyPipeline(mode, input) {
9516
9572
  const { deps, ctx, command, meta, branchHints, watch, options } = input;
9517
9573
  const directTrack = input.directTrack ?? false;
@@ -13389,6 +13445,7 @@ function spawnExitCode(status, signal) {
13389
13445
  function defaultSpawn(command, args, env) {
13390
13446
  const target = resolveSpawnTarget(command, args);
13391
13447
  const r = (0, import_node_child_process6.spawnSync)(target.cmd, target.args, {
13448
+ windowsHide: true,
13392
13449
  stdio: "inherit",
13393
13450
  env,
13394
13451
  windowsVerbatimArguments: target.windowsVerbatimArguments
@@ -14552,9 +14609,10 @@ async function fetchNpmReleasedVersion() {
14552
14609
  }
14553
14610
  }
14554
14611
  var NPM_INSTALL_TIMEOUT_MS = 12e4;
14555
- async function npmSelfUpdateCli(target) {
14612
+ async function npmSelfUpdateCli(target, onStep) {
14556
14613
  const command = cliUpdateCommand(target);
14557
14614
  try {
14615
+ onStep?.(command);
14558
14616
  await runHostBin("npm", ["install", "-g", `@mutmutco/cli@${target ?? "latest"}`], { timeout: NPM_INSTALL_TIMEOUT_MS });
14559
14617
  return { ok: true, detail: `${command} exited 0` };
14560
14618
  } catch (e) {
@@ -14620,9 +14678,6 @@ function snapshotPluginGuardInput(surface = detectSurface(process.env), isOrgRep
14620
14678
  ) : (0, import_node_fs18.existsSync)((0, import_node_path16.join)(root, "plugins", "cache", "mutmutco", "mmi"))
14621
14679
  };
14622
14680
  }
14623
- function activePluginGuardState(isOrgRepo) {
14624
- return buildPluginGuardDecision(snapshotPluginGuardInput(detectSurface(process.env), isOrgRepo)).state;
14625
- }
14626
14681
  async function runClaudePlugin(args) {
14627
14682
  try {
14628
14683
  await runHostBin("claude", args, { timeout: CLAUDE_PLUGIN_TIMEOUT_MS });
@@ -14792,27 +14847,35 @@ async function applyPluginHeal(surface, log, opts) {
14792
14847
  if (restored) log(` ${restored}`);
14793
14848
  return true;
14794
14849
  }
14795
- async function healClaudePluginForDoctor(surface = detectSurface(process.env)) {
14850
+ async function healClaudePluginForDoctor(surface = detectSurface(process.env), onStep) {
14796
14851
  if (surfaceToken(surface) !== "claude") {
14797
14852
  return { ok: false, detail: `not a Claude surface (${surface}) \u2014 no Hub-shipped plugin to reinstall` };
14798
14853
  }
14799
14854
  const steps = [];
14800
- const ok = await applyPluginHeal(surface, (msg) => steps.push(msg.trim()));
14855
+ const ok = await applyPluginHeal(surface, (msg) => {
14856
+ const line = msg.trim();
14857
+ steps.push(line);
14858
+ if (line) onStep?.(line);
14859
+ });
14801
14860
  const pinNote = steps.find((s) => s.startsWith("re-pinned ") || s.includes("re-pin by hand"));
14802
14861
  return {
14803
14862
  ok,
14804
14863
  detail: ok ? `marketplace remove \u2192 add \u2192 install succeeded${pinNote ? `; ${pinNote}` : ""}` : `\`claude plugin\` reinstall failed or was skipped${steps.length ? ` (${steps[steps.length - 1]})` : ""}`
14805
14864
  };
14806
14865
  }
14807
- async function healActivePluginForDoctor(surface = detectSurface(process.env)) {
14866
+ async function healActivePluginForDoctor(surface = detectSurface(process.env), onStep) {
14808
14867
  const token = surfaceToken(surface);
14809
14868
  if (token !== "claude" && token !== "codex" && token !== "cursor" && token !== "kilo") {
14810
14869
  return { ok: false, detail: `not a supported plugin surface (${surface})` };
14811
14870
  }
14812
- if (token === "claude") return healClaudePluginForDoctor(surface);
14871
+ if (token === "claude") return healClaudePluginForDoctor(surface, onStep);
14813
14872
  if (token === "cursor") return installCursorPluginCheckout();
14814
14873
  const steps = [];
14815
- const applied = await applyPluginHeal(surface, (msg) => steps.push(msg.trim()));
14874
+ const applied = await applyPluginHeal(surface, (msg) => {
14875
+ const line = msg.trim();
14876
+ steps.push(line);
14877
+ if (line) onStep?.(line);
14878
+ });
14816
14879
  const snapshot = applied ? snapshotPluginGuardInput(surface, true) : void 0;
14817
14880
  const guardState = snapshot ? buildPluginGuardDecision(snapshot).state : "unresolved";
14818
14881
  const ok = applied && guardState === "healthy";
@@ -14835,7 +14898,7 @@ function readKnownMarketplacesFile(path2) {
14835
14898
  return void 0;
14836
14899
  }
14837
14900
  }
14838
- function restoreMarketplacePinsOnDisk(path2, pins) {
14901
+ function writeMarketplacePinsOnDisk(path2, pins, succeeded, failedVerb) {
14839
14902
  if (pins.size === 0) return void 0;
14840
14903
  const after = readKnownMarketplacesFile(path2);
14841
14904
  const next = restoreMarketplacePins(after, pins);
@@ -14843,14 +14906,35 @@ function restoreMarketplacePinsOnDisk(path2, pins) {
14843
14906
  try {
14844
14907
  (0, import_node_fs18.writeFileSync)(path2, next, "utf8");
14845
14908
  } catch {
14846
- return `could NOT restore ${[...pins.keys()].join(", ")} \u2014 re-pin by hand`;
14909
+ return `could NOT ${failedVerb} ${[...pins.keys()].join(", ")} \u2014 set it by hand`;
14847
14910
  }
14848
14911
  const verify = readKnownMarketplacesFile(path2);
14849
14912
  const failed = [...pins].filter(([name, want]) => {
14850
14913
  const got = readKnownMarketplace(verify, name);
14851
14914
  return typeof want.autoUpdate === "boolean" && got.declared !== want.autoUpdate || want.ref !== void 0 && got.ref !== want.ref;
14852
14915
  });
14853
- return failed.length ? `restore did NOT take for ${failed.map(([n]) => n).join(", ")} \u2014 re-pin by hand` : `re-pinned ${[...pins.keys()].join(", ")} (auto-update + catalog ref survive the reinstall)`;
14916
+ return failed.length ? `${failedVerb} did NOT take for ${failed.map(([n]) => n).join(", ")} \u2014 set it by hand` : succeeded(pins);
14917
+ }
14918
+ function restoreMarketplacePinsOnDisk(path2, pins) {
14919
+ return writeMarketplacePinsOnDisk(path2, pins, restoredPinsLine, "restore");
14920
+ }
14921
+ function applyOrgMarketplacePins(path2, names) {
14922
+ return writeMarketplacePinsOnDisk(
14923
+ path2,
14924
+ new Map(names.map((name) => [name, ORG_MARKETPLACE_PINS])),
14925
+ (pins) => `pinned ${[...pins.keys()].join(", ")} to ${ORG_MARKETPLACE_PINS.ref} with auto-update on`,
14926
+ "pin"
14927
+ );
14928
+ }
14929
+ function restoredPinsLine(pins) {
14930
+ const named = [...pins].map(([name, want]) => {
14931
+ const fields = [
14932
+ ...typeof want.autoUpdate === "boolean" ? ["auto-update"] : [],
14933
+ ...want.ref ? ["catalog ref"] : []
14934
+ ];
14935
+ return `${name} (${fields.join(" + ")})`;
14936
+ });
14937
+ return `re-pinned ${named.join(", ")} \u2014 restored across the reinstall`;
14854
14938
  }
14855
14939
  async function runGuard(readOrigin) {
14856
14940
  const surface = detectSurface(process.env);
@@ -16505,12 +16589,13 @@ var PRIMARY_GROUPS = [
16505
16589
  ["Plan and work", ["board", "issue", "worktree", "stage"]],
16506
16590
  ["Review and ship", ["pr", "ci", "rcand", "release", "hotfix", "train"]],
16507
16591
  // `tests` sits beside `docs` deliberately: both are deterministic, repo-local gates a workflow
16508
- // step invokes (`docs refs`, `tests policy`), not org-plane operations (#3605).
16509
- ["Setup and support", ["bootstrap", "secrets", "docs", "tests"]],
16592
+ // step invokes (`docs refs`, `tests policy`), not org-plane operations (#3605). `spawn policy`
16593
+ // joins them on the same footing (#3979).
16594
+ ["Setup and support", ["bootstrap", "secrets", "docs", "tests", "spawn"]],
16510
16595
  ["Coordinate and improve", ["wave", "report", "skill-lesson"]]
16511
16596
  ];
16512
16597
  var OPERATIONAL_TOP_LEVEL = /* @__PURE__ */ new Set(["org", "runtime", "plugin"]);
16513
- var SUPPORT_PRIMARY = /* @__PURE__ */ new Set(["doctor", "whoami", "commands", "explain", "docs", "tests", "wave", "report", "skill-lesson"]);
16598
+ var SUPPORT_PRIMARY = /* @__PURE__ */ new Set(["doctor", "whoami", "commands", "explain", "docs", "tests", "spawn", "wave", "report", "skill-lesson"]);
16514
16599
  var TOP_LEVEL_ORDER = /* @__PURE__ */ new Map();
16515
16600
  var HELP_GROUP_ORDER = /* @__PURE__ */ new Map();
16516
16601
  var topLevelPosition = 0;
@@ -16550,6 +16635,7 @@ var COMMAND_OWNERSHIP = {
16550
16635
  secrets: { module_owner: "cli/src/secrets-commands.ts", consumer: "authenticated-operator" },
16551
16636
  docs: { module_owner: "cli/src/docs-index-command.ts", consumer: "repo-gates" },
16552
16637
  tests: { module_owner: "cli/src/test-policy-core.ts", consumer: "repo-gates" },
16638
+ spawn: { module_owner: "cli/src/spawn-policy-core.ts", consumer: "repo-gates" },
16553
16639
  wave: { module_owner: "cli/src/wave-land.ts", consumer: "campaign-orchestrator" },
16554
16640
  report: { module_owner: "cli/src/report.ts", consumer: "campaign-orchestrator" },
16555
16641
  "skill-lesson": { module_owner: "cli/src/skill-lesson.ts", consumer: "campaign-orchestrator" },
@@ -18841,10 +18927,115 @@ function runDocRefs(root, deps = {}) {
18841
18927
  };
18842
18928
  }
18843
18929
 
18844
- // src/test-policy-core.ts
18930
+ // src/spawn-policy-core.ts
18845
18931
  var import_node_child_process11 = require("node:child_process");
18846
18932
  var import_node_fs21 = require("node:fs");
18847
18933
  var import_node_path19 = require("node:path");
18934
+ var SPAWNERS = ["spawn", "spawnSync", "exec", "execSync", "execFile", "execFileSync"];
18935
+ var CALL_SOURCE = String.raw`(^|[^.\w$])(${SPAWNERS.join("|")})\s*\(`;
18936
+ var SOURCE_EXT = /\.(ts|mts|cts|js|mjs|cjs)$/;
18937
+ var EXCLUDED = [
18938
+ /(^|\/)node_modules\//,
18939
+ /(^|\/)(dist|build|coverage)\//,
18940
+ /(^|\/)(test|tests|__tests__|fixtures|__fixtures__)\//,
18941
+ /\.(test|spec)\.[cm]?[jt]s$/,
18942
+ /\.d\.[cm]?ts$/
18943
+ ];
18944
+ function stripComments(src) {
18945
+ return src.replace(/\/\*[\s\S]*?\*\//g, (m) => m.replace(/[^\n]/g, " ")).replace(/(^|[^:])\/\/[^\n]*/g, (m, p) => p + " ".repeat(m.length - p.length));
18946
+ }
18947
+ function callArgs(src, open2) {
18948
+ let depth = 0;
18949
+ for (let i = open2; i < src.length; i++) {
18950
+ const c = src[i];
18951
+ if (c === "(") depth++;
18952
+ else if (c === ")") {
18953
+ depth--;
18954
+ if (depth === 0) return src.slice(open2, i + 1);
18955
+ }
18956
+ }
18957
+ return "";
18958
+ }
18959
+ function findViolationsInSource(raw) {
18960
+ if (!/from\s+["']node:child_process["']/.test(raw)) return [];
18961
+ const importBlock = /import\s*(?:type\s*)?\{([^}]*)\}\s*from\s+["']node:child_process["']/.exec(raw);
18962
+ const imported = new Set(
18963
+ (importBlock?.[1] ?? "").split(",").map((s) => s.trim().split(/\s+as\s+/)[0].trim()).filter(Boolean)
18964
+ );
18965
+ if (imported.size === 0) return [];
18966
+ const rawLines = raw.split("\n");
18967
+ const src = stripComments(raw);
18968
+ const call = new RegExp(CALL_SOURCE, "g");
18969
+ const found = [];
18970
+ let m;
18971
+ while (m = call.exec(src)) {
18972
+ const callee = m[2];
18973
+ if (!imported.has(callee)) continue;
18974
+ const open2 = m.index + m[0].length - 1;
18975
+ const args = callArgs(src, open2);
18976
+ if (!args) continue;
18977
+ if (args.includes("windowsHide")) continue;
18978
+ const line = src.slice(0, m.index + m[1].length).split("\n").length;
18979
+ if (/windows-hide-exempt:/.test(rawLines[line - 2] ?? "")) continue;
18980
+ if (/\.{3}\s*\w+/.test(args)) continue;
18981
+ if (/\b(opts|options|spawnOptions)\b/.test(args)) continue;
18982
+ const identifiers = new Set(args.match(/\b[A-Za-z_$][\w$]*\b/g) ?? []);
18983
+ let satisfied = false;
18984
+ for (const id of identifiers) {
18985
+ if (SPAWNERS.includes(id)) continue;
18986
+ const decl = new RegExp(String.raw`\b(?:const|let|var)\s+${id}\b[^;]*`, "s").exec(src);
18987
+ if (decl && decl[0].includes("windowsHide")) {
18988
+ satisfied = true;
18989
+ break;
18990
+ }
18991
+ if (new RegExp(String.raw`\b${id}\s*:\s*\{[^}]*windowsHide`, "s").test(src)) {
18992
+ satisfied = true;
18993
+ break;
18994
+ }
18995
+ }
18996
+ if (satisfied) continue;
18997
+ found.push({ line, callee });
18998
+ }
18999
+ return found;
19000
+ }
19001
+ function policedFiles(root) {
19002
+ const r = (0, import_node_child_process11.spawnSync)("git", ["ls-files", "-z", "--cached", "--others", "--exclude-standard"], {
19003
+ cwd: root,
19004
+ encoding: "utf8",
19005
+ windowsHide: true,
19006
+ maxBuffer: 32 * 1024 * 1024
19007
+ });
19008
+ if (r.status !== 0) {
19009
+ throw new Error(`spawn policy: git ls-files failed: ${(r.stderr || "").trim() || `exit ${r.status}`}`);
19010
+ }
19011
+ return (r.stdout ?? "").split("\0").filter(Boolean).filter((f) => SOURCE_EXT.test(f)).filter((f) => !EXCLUDED.some((re) => re.test(f)));
19012
+ }
19013
+ function runSpawnPolicy(root) {
19014
+ const files = policedFiles(root);
19015
+ const findings = [];
19016
+ for (const file of files) {
19017
+ let raw;
19018
+ try {
19019
+ raw = (0, import_node_fs21.readFileSync)((0, import_node_path19.join)(root, file), "utf8");
19020
+ } catch {
19021
+ continue;
19022
+ }
19023
+ for (const v of findViolationsInSource(raw)) {
19024
+ findings.push({
19025
+ file,
19026
+ line: v.line,
19027
+ callee: v.callee,
19028
+ detail: `${file}:${v.line} ${v.callee}(...) has no windowsHide`
19029
+ });
19030
+ }
19031
+ }
19032
+ return { ok: findings.length === 0, scannedCount: files.length, findings };
19033
+ }
19034
+
19035
+ // src/test-policy-core.ts
19036
+ var import_node_child_process12 = require("node:child_process");
19037
+ var import_node_fs22 = require("node:fs");
19038
+ var import_node_path20 = require("node:path");
18848
19039
  var POLICY_FILE = "test-policy.json";
18849
19040
  var TEST_RE = /\.(?:test|spec)\.[cm]?[jt]sx?$/;
18850
19041
  var PY_TEST_RE = /(?:^|\/)test_[^/]*\.py$|_test\.py$/;
@@ -18897,7 +19088,7 @@ function isTestPath(path2) {
18897
19088
  return TEST_RE.test(path2) || PY_TEST_RE.test(path2);
18898
19089
  }
18899
19090
  function loadPolicy(root, readFile9 = readFileOrNull2) {
18900
- const raw = readFile9((0, import_node_path19.join)(root, POLICY_FILE));
19091
+ const raw = readFile9((0, import_node_path20.join)(root, POLICY_FILE));
18901
19092
  if (raw == null) return { mandatory: [], declared: false };
18902
19093
  try {
18903
19094
  return { ...JSON.parse(raw), declared: true };
@@ -18907,7 +19098,7 @@ function loadPolicy(root, readFile9 = readFileOrNull2) {
18907
19098
  }
18908
19099
  function readFileOrNull2(path2) {
18909
19100
  try {
18910
- return (0, import_node_fs21.readFileSync)(path2, "utf8");
19101
+ return (0, import_node_fs22.readFileSync)(path2, "utf8");
18911
19102
  } catch {
18912
19103
  return null;
18913
19104
  }
@@ -18934,12 +19125,12 @@ function classify(changed, policy, present = () => false) {
18934
19125
  const removedProtected = [...removed].filter((p) => protectedBy.has(p)).map((p) => ({ path: p, why: protectedBy.get(p) ?? "" }));
18935
19126
  return { mandatoryHits, untestedHits, testChanges, addedTests, removedProtected };
18936
19127
  }
18937
- function unresolvedProtectedEntries(policy, root, exists = (path2) => (0, import_node_fs21.existsSync)(path2)) {
18938
- return (policy.protected ?? []).map((p) => p.path).filter((p) => !exists((0, import_node_path19.join)(root, p)));
19128
+ function unresolvedProtectedEntries(policy, root, exists = (path2) => (0, import_node_fs22.existsSync)(path2)) {
19129
+ return (policy.protected ?? []).map((p) => p.path).filter((p) => !exists((0, import_node_path20.join)(root, p)));
18939
19130
  }
18940
- function unresolvedSatisfiers(policy, root, exists = (path2) => (0, import_node_fs21.existsSync)(path2)) {
19131
+ function unresolvedSatisfiers(policy, root, exists = (path2) => (0, import_node_fs22.existsSync)(path2)) {
18941
19132
  const declared = (policy.mandatory ?? []).flatMap((m) => m.satisfiedBy ?? []);
18942
- return [...new Set(declared)].filter((p) => !exists((0, import_node_path19.join)(root, p)));
19133
+ return [...new Set(declared)].filter((p) => !exists((0, import_node_path20.join)(root, p)));
18943
19134
  }
18944
19135
  function evaluate(changed, policy, present = () => false) {
18945
19136
  const { mandatoryHits, untestedHits, testChanges, addedTests, removedProtected } = classify(changed, policy, present);
@@ -18972,14 +19163,15 @@ function evaluate(changed, policy, present = () => false) {
18972
19163
  return findings;
18973
19164
  }
18974
19165
  function git(args, cwd) {
18975
- return (0, import_node_child_process11.execFileSync)("git", args, { cwd, encoding: "utf8", maxBuffer: 32 * 1024 * 1024 });
19166
+ return (0, import_node_child_process12.execFileSync)("git", args, { windowsHide: true, cwd, encoding: "utf8", maxBuffer: 32 * 1024 * 1024 });
18976
19167
  }
18977
19168
  var COAUTHOR_KEY = "Co-authored-by";
18978
19169
  var GH_MESSAGE_SEPARATOR = /^-{5,}$/;
18979
19170
  var LIFTED_KEYS = new RegExp(`^(?:${TRAILER_KEY}|${COAUTHOR_KEY}):`, "i");
18980
19171
  function parseTrailers(message, cwd) {
18981
19172
  try {
18982
- return (0, import_node_child_process11.execFileSync)("git", ["interpret-trailers", "--parse", "--unfold"], {
19173
+ return (0, import_node_child_process12.execFileSync)("git", ["interpret-trailers", "--parse", "--unfold"], {
19174
+ windowsHide: true,
18983
19175
  cwd,
18984
19176
  input: message,
18985
19177
  encoding: "utf8",
@@ -19120,13 +19312,13 @@ function changedFilesSince(base, cwd) {
19120
19312
  }
19121
19313
  function runTestPolicy(root, deps = {}) {
19122
19314
  const policy = deps.policy ?? loadPolicy(root);
19123
- const exists = deps.exists ?? ((path2) => (0, import_node_fs21.existsSync)(path2));
19315
+ const exists = deps.exists ?? ((path2) => (0, import_node_fs22.existsSync)(path2));
19124
19316
  const counts = { mandatoryCount: (policy.mandatory ?? []).length, protectedCount: (policy.protected ?? []).length };
19125
19317
  const base = deps.changed ? "(injected)" : resolveBase(root, deps.base);
19126
19318
  const refusal = deps.changed ? null : untrustworthyRange(root, base);
19127
19319
  const changed = deps.changed ?? (refusal ? [] : changedFilesSince(base, root));
19128
19320
  const lookup = deps.override !== void 0 ? { override: deps.override, refusals: [] } : !deps.changed && !refusal ? readOverride(base, root) : { override: null, refusals: [] };
19129
- const present = (path2) => exists((0, import_node_path19.join)(root, path2));
19321
+ const present = (path2) => exists((0, import_node_path20.join)(root, path2));
19130
19322
  const removedByThisDiff = removedPaths(changed);
19131
19323
  const staleFindings = [];
19132
19324
  const unresolved = unresolvedProtectedEntries(policy, root, exists).filter((p) => !removedByThisDiff.has(p));
@@ -19282,8 +19474,8 @@ function docsAuditStatus(fetch2, opts) {
19282
19474
  }
19283
19475
 
19284
19476
  // src/project-info-sync.ts
19285
- var import_node_fs22 = require("node:fs");
19286
- var import_node_path20 = require("node:path");
19477
+ var import_node_fs23 = require("node:fs");
19478
+ var import_node_path21 = require("node:path");
19287
19479
  var UPDATE_PROJECT_INFO = `mutation($projectId: ID!, $shortDescription: String!, $readme: String!) {
19288
19480
  updateProjectV2(input: { projectId: $projectId, shortDescription: $shortDescription, readme: $readme }) {
19289
19481
  projectV2 { id }
@@ -19328,14 +19520,14 @@ function sharedName(entries, fallback) {
19328
19520
  }
19329
19521
  function buildProjectInfoSyncPlan(targetRepo2, project2, projects, repoRoot2) {
19330
19522
  if (!project2.projectId) throw new Error(`org project sync-info: ${targetRepo2} registry META has no projectId`);
19331
- const readmePath = (0, import_node_path20.join)(repoRoot2, "README.md");
19332
- if (!(0, import_node_fs22.existsSync)(readmePath)) throw new Error(`org project sync-info: ${targetRepo2} has no README.md`);
19523
+ const readmePath = (0, import_node_path21.join)(repoRoot2, "README.md");
19524
+ if (!(0, import_node_fs23.existsSync)(readmePath)) throw new Error(`org project sync-info: ${targetRepo2} has no README.md`);
19333
19525
  const entries = entriesFor(project2, projects);
19334
19526
  const memberRepos = [...new Set(entries.flatMap((entry) => entry.repos ?? []))].filter((repo) => /^[^/]+\/[^/]+$/.test(repo)).sort((a, b) => a.localeCompare(b));
19335
19527
  const projectName = sharedName(entries, project2.name?.trim() || targetRepo2.split("/").pop() || targetRepo2);
19336
19528
  if (!memberRepos.length) throw new Error(`org project sync-info: project ${projectName} has no registered member repos`);
19337
19529
  const entryNames = entries.map((entry) => entry.name?.trim()).filter((name) => Boolean(name));
19338
- const shortDescription = memberRepos.length === 1 ? shortDescriptionFromReadme((0, import_node_fs22.readFileSync)(readmePath, "utf8")) : `Shared work across ${new Intl.ListFormat("en", { type: "conjunction" }).format(entryNames)}.`;
19530
+ const shortDescription = memberRepos.length === 1 ? shortDescriptionFromReadme((0, import_node_fs23.readFileSync)(readmePath, "utf8")) : `Shared work across ${new Intl.ListFormat("en", { type: "conjunction" }).format(entryNames)}.`;
19339
19531
  const lines = [
19340
19532
  `# ${projectName}`,
19341
19533
  "",
@@ -19354,8 +19546,8 @@ function buildProjectInfoSyncPlan(targetRepo2, project2, projects, repoRoot2) {
19354
19546
  const targetBase = `https://github.com/${targetRepo2}`;
19355
19547
  const targetBranch = branchFor(targetRepo2, projects);
19356
19548
  const orgDocs = [
19357
- (0, import_node_fs22.existsSync)((0, import_node_path20.join)(repoRoot2, "docs", "org-readme.md")) ? `- [Org identity](${targetBase}/blob/${targetBranch}/docs/org-readme.md)` : "",
19358
- (0, import_node_fs22.existsSync)((0, import_node_path20.join)(repoRoot2, "docs", "org-architecture.md")) ? `- [Org architecture](${targetBase}/blob/${targetBranch}/docs/org-architecture.md)` : ""
19549
+ (0, import_node_fs23.existsSync)((0, import_node_path21.join)(repoRoot2, "docs", "org-readme.md")) ? `- [Org identity](${targetBase}/blob/${targetBranch}/docs/org-readme.md)` : "",
19550
+ (0, import_node_fs23.existsSync)((0, import_node_path21.join)(repoRoot2, "docs", "org-architecture.md")) ? `- [Org architecture](${targetBase}/blob/${targetBranch}/docs/org-architecture.md)` : ""
19359
19551
  ].filter(Boolean);
19360
19552
  if (orgDocs.length) lines.push("", "## Organisation docs", "", ...orgDocs);
19361
19553
  return { projectId: project2.projectId, projectName, targetRepo: targetRepo2, memberRepos, shortDescription, readme: `${lines.join("\n")}
@@ -20214,8 +20406,8 @@ function writeError(res) {
20214
20406
  }
20215
20407
 
20216
20408
  // src/secrets-commands.ts
20217
- var import_node_fs23 = require("node:fs");
20218
- var import_node_path21 = require("node:path");
20409
+ var import_node_fs24 = require("node:fs");
20410
+ var import_node_path22 = require("node:path");
20219
20411
  var import_node_os8 = require("node:os");
20220
20412
 
20221
20413
  // src/project-runtime.ts
@@ -20339,18 +20531,18 @@ function collectMap(value, previous = []) {
20339
20531
  return [...previous, value];
20340
20532
  }
20341
20533
  async function decryptRailsCredentials(input) {
20342
- const appDir = (0, import_node_path21.resolve)(input.appDir ?? process.cwd());
20534
+ const appDir = (0, import_node_path22.resolve)(input.appDir ?? process.cwd());
20343
20535
  const credentialsFile = input.credentialsFile ?? DEFAULT_RAILS_CREDENTIALS_FILE;
20344
20536
  const masterKeyFile = input.masterKeyFile ?? DEFAULT_RAILS_MASTER_KEY_FILE;
20345
- const credentialsPath = (0, import_node_path21.resolve)(appDir, credentialsFile);
20346
- const masterKeyPath = (0, import_node_path21.resolve)(appDir, masterKeyFile);
20537
+ const credentialsPath = (0, import_node_path22.resolve)(appDir, credentialsFile);
20538
+ const masterKeyPath = (0, import_node_path22.resolve)(appDir, masterKeyFile);
20347
20539
  const env = {
20348
20540
  ...process.env,
20349
20541
  MMI_RAILS_CREDENTIALS_FILE: credentialsPath,
20350
20542
  MMI_RAILS_MASTER_KEY_FILE: masterKeyPath
20351
20543
  };
20352
- if ((0, import_node_fs23.existsSync)(masterKeyPath)) {
20353
- env.RAILS_MASTER_KEY = (0, import_node_fs23.readFileSync)(masterKeyPath, "utf8").trim();
20544
+ if ((0, import_node_fs24.existsSync)(masterKeyPath)) {
20545
+ env.RAILS_MASTER_KEY = (0, import_node_fs24.readFileSync)(masterKeyPath, "utf8").trim();
20354
20546
  }
20355
20547
  const script = [
20356
20548
  'require "json"',
@@ -20360,9 +20552,9 @@ async function decryptRailsCredentials(input) {
20360
20552
  'config = ActiveSupport::EncryptedConfiguration.new(config_path: config_path, key_path: key_path, env_key: "RAILS_MASTER_KEY", raise_if_missing_key: true)',
20361
20553
  "puts JSON.generate(config.config)"
20362
20554
  ].join("\n");
20363
- const scriptDir = (0, import_node_fs23.mkdtempSync)((0, import_node_path21.join)((0, import_node_os8.tmpdir)(), "mmi-rails-decrypt-"));
20364
- const scriptPath = (0, import_node_path21.join)(scriptDir, "decrypt.rb");
20365
- (0, import_node_fs23.writeFileSync)(scriptPath, script, "utf8");
20555
+ const scriptDir = (0, import_node_fs24.mkdtempSync)((0, import_node_path22.join)((0, import_node_os8.tmpdir)(), "mmi-rails-decrypt-"));
20556
+ const scriptPath = (0, import_node_path22.join)(scriptDir, "decrypt.rb");
20557
+ (0, import_node_fs24.writeFileSync)(scriptPath, script, "utf8");
20366
20558
  try {
20367
20559
  const args = ["exec", "ruby", scriptPath];
20368
20560
  const cmd = process.platform === "win32" ? "cmd.exe" : "bundle";
@@ -20374,7 +20566,7 @@ async function decryptRailsCredentials(input) {
20374
20566
  });
20375
20567
  return JSON.parse(stdout);
20376
20568
  } finally {
20377
- (0, import_node_fs23.rmSync)(scriptDir, { recursive: true, force: true });
20569
+ (0, import_node_fs24.rmSync)(scriptDir, { recursive: true, force: true });
20378
20570
  }
20379
20571
  }
20380
20572
  async function readSecretStdin() {
@@ -20464,7 +20656,7 @@ function registerSecretsCommands(program3) {
20464
20656
  let body;
20465
20657
  if (o.file) {
20466
20658
  try {
20467
- body = (0, import_node_fs23.readFileSync)((0, import_node_path21.resolve)(o.file), "utf8");
20659
+ body = (0, import_node_fs24.readFileSync)((0, import_node_path22.resolve)(o.file), "utf8");
20468
20660
  } catch (e) {
20469
20661
  return fail(`secrets org-catalog: cannot read --file ${o.file}: ${e.message}`);
20470
20662
  }
@@ -20569,7 +20761,7 @@ function registerSecretsCommands(program3) {
20569
20761
  {
20570
20762
  ...d,
20571
20763
  decryptRailsCredentials,
20572
- removeFile: (path2) => (0, import_node_fs23.unlinkSync)((0, import_node_path21.resolve)(o.appDir ?? process.cwd(), path2))
20764
+ removeFile: (path2) => (0, import_node_fs24.unlinkSync)((0, import_node_path22.resolve)(o.appDir ?? process.cwd(), path2))
20573
20765
  },
20574
20766
  {
20575
20767
  repo: o.repo,
@@ -20763,7 +20955,7 @@ function checkGithubPools(probe) {
20763
20955
  }
20764
20956
 
20765
20957
  // src/box-commands.ts
20766
- var import_node_fs24 = require("node:fs");
20958
+ var import_node_fs25 = require("node:fs");
20767
20959
 
20768
20960
  // src/box.ts
20769
20961
  var BOX_KEYS = {
@@ -20966,7 +21158,7 @@ function registerBoxCommands(program3) {
20966
21158
  }
20967
21159
  if (o.json) console.log(JSON.stringify({ box: found, incomplete }, null, 2));
20968
21160
  else if (o.ssh && o.script) {
20969
- (0, import_node_fs24.writeFileSync)(o.script, sshRecipeScript(found), "utf8");
21161
+ (0, import_node_fs25.writeFileSync)(o.script, sshRecipeScript(found), "utf8");
20970
21162
  console.log(`wrote ${o.script} \u2014 run: bash "${o.script}"`);
20971
21163
  } else if (o.ssh) console.log(`${formatSshRecipe(found)}
20972
21164
  ${SSH_RECIPE_AGENT_NOTE}`);
@@ -20981,7 +21173,7 @@ ${SSH_RECIPE_AGENT_NOTE}`);
20981
21173
 
20982
21174
  // src/schedules-commands.ts
20983
21175
  var import_promises3 = require("node:fs/promises");
20984
- var import_node_child_process12 = require("node:child_process");
21176
+ var import_node_child_process13 = require("node:child_process");
20985
21177
  var import_node_util7 = require("node:util");
20986
21178
 
20987
21179
  // src/schedules.ts
@@ -21421,7 +21613,7 @@ function assembleReconciliation(githubEntries2, readRepos, registry2, activeWork
21421
21613
  }
21422
21614
 
21423
21615
  // src/schedules-commands.ts
21424
- var execFileP5 = (0, import_node_util7.promisify)(import_node_child_process12.execFile);
21616
+ var execFileP5 = (0, import_node_util7.promisify)(import_node_child_process13.execFile);
21425
21617
  var AWS_REGION = "eu-central-1";
21426
21618
  var AWS_TIMEOUT_MS = 3e4;
21427
21619
  var AWS_RETRY_DELAY_MS = 1500;
@@ -21658,7 +21850,7 @@ function registerSchedulesCommands(program3) {
21658
21850
 
21659
21851
  // src/file-lock.ts
21660
21852
  var import_promises4 = require("node:fs/promises");
21661
- var import_node_path22 = require("node:path");
21853
+ var import_node_path23 = require("node:path");
21662
21854
  var sleep = (ms) => new Promise((resolve5) => setTimeout(resolve5, ms));
21663
21855
  var IMMEDIATE_RETRY_BUDGET = 3;
21664
21856
  var FileLockBusyError = class extends Error {
@@ -21743,7 +21935,7 @@ async function releaseFileLock(lockPath, guard) {
21743
21935
  }
21744
21936
  async function withFileLock(lockPath, opts, fn) {
21745
21937
  const resolved = resolveFileLockOpts(opts);
21746
- await (0, import_promises4.mkdir)((0, import_node_path22.dirname)(lockPath), { recursive: true }).catch(() => void 0);
21938
+ await (0, import_promises4.mkdir)((0, import_node_path23.dirname)(lockPath), { recursive: true }).catch(() => void 0);
21747
21939
  const guard = await acquireFileLock(lockPath, resolved, Date.now() + resolved.maxWaitMs);
21748
21940
  try {
21749
21941
  return await fn();
@@ -21754,7 +21946,7 @@ async function withFileLock(lockPath, opts, fn) {
21754
21946
 
21755
21947
  // src/schedules-lift-command.ts
21756
21948
  var import_promises5 = require("node:fs/promises");
21757
- var import_node_path23 = require("node:path");
21949
+ var import_node_path24 = require("node:path");
21758
21950
 
21759
21951
  // src/schedules-lift.ts
21760
21952
  var SCHEDULE_HEADER_FIELDS = ["schedule", "what", "owner", "cadence", "executor", "llm", "output", "breaks", "kill"];
@@ -21860,7 +22052,7 @@ async function readWorkflowFiles(dir) {
21860
22052
  const files = [];
21861
22053
  for (const name of names.sort()) {
21862
22054
  if (!/\.ya?ml$/.test(name)) continue;
21863
- files.push({ path: `.github/workflows/${name}`, text: await (0, import_promises5.readFile)((0, import_node_path23.join)(dir, name), "utf8") });
22055
+ files.push({ path: `.github/workflows/${name}`, text: await (0, import_promises5.readFile)((0, import_node_path24.join)(dir, name), "utf8") });
21864
22056
  }
21865
22057
  return files;
21866
22058
  }
@@ -22402,9 +22594,9 @@ function registerQueryCommands(program3) {
22402
22594
  }
22403
22595
 
22404
22596
  // src/bootstrap-commands.ts
22405
- var import_node_fs25 = require("node:fs");
22597
+ var import_node_fs26 = require("node:fs");
22406
22598
  var import_node_os9 = require("node:os");
22407
- var import_node_path24 = require("node:path");
22599
+ var import_node_path25 = require("node:path");
22408
22600
 
22409
22601
  // src/bootstrap-drift.ts
22410
22602
  function byteComparableSeeds(manifest, cls) {
@@ -23018,13 +23210,13 @@ function registerBootstrapCommands(program3) {
23018
23210
  client: defaultGitHubClient(),
23019
23211
  projectMeta: meta,
23020
23212
  deployModel: typeof meta?.deployModel === "string" ? meta.deployModel : void 0,
23021
- readLocalFile: (path2) => path2 === "projects.json" && apiProjects != null ? apiProjects : (0, import_node_fs25.existsSync)(path2) ? (0, import_node_fs25.readFileSync)(path2, "utf8") : null,
23213
+ readLocalFile: (path2) => path2 === "projects.json" && apiProjects != null ? apiProjects : (0, import_node_fs26.existsSync)(path2) ? (0, import_node_fs26.readFileSync)(path2, "utf8") : null,
23022
23214
  // requiredGcpApis is stored as an array by a JSON write, but `org project set --var KEY=VALUE` stores a raw
23023
23215
  // comma-string — accept either so the seeded value verifies regardless of how it was written.
23024
23216
  // #3689: the same committed map the org access audit reads (#3664), so a sanctioned admin is not a
23025
23217
  // permanent bootstrap failure on one surface and an intended state on the other. Absent file → no
23026
23218
  // sanction, which is the pre-#3664 behaviour.
23027
- sanctionedAdmins: (0, import_node_fs25.existsSync)("access-matrix.json") ? entriesValueByCanonicalRepo(loadSanctionedAdmins((0, import_node_fs25.readFileSync)("access-matrix.json", "utf8")), repo) : void 0,
23219
+ sanctionedAdmins: (0, import_node_fs26.existsSync)("access-matrix.json") ? entriesValueByCanonicalRepo(loadSanctionedAdmins((0, import_node_fs26.readFileSync)("access-matrix.json", "utf8")), repo) : void 0,
23028
23220
  requiredGcpApis: (() => {
23029
23221
  const v = meta?.requiredGcpApis;
23030
23222
  if (Array.isArray(v)) return v;
@@ -23062,12 +23254,12 @@ function registerBootstrapCommands(program3) {
23062
23254
  bootstrap.command("drift").description("#3818: compare every org-owned whole-file seed against MMI-Hub's copy across the registry roster; read-only").option("--repo <owner/repo>", "audit one repo instead of the roster (never a fleet verdict)").option("--json", "machine-readable output").action(async () => {
23063
23255
  const o = { repo: rawValue("--repo", ""), json: rawFlag("--json") };
23064
23256
  const manifestPath = "skills/bootstrap/seeds/manifest.json";
23065
- if (!(0, import_node_fs25.existsSync)(manifestPath)) return fail(`bootstrap drift: ${manifestPath} not found; run from the MMI-Hub repo root \u2014 the Hub's copies ARE the reference this compares against`);
23066
- const manifest = loadBootstrapSeeds((0, import_node_fs25.readFileSync)(manifestPath, "utf8"));
23257
+ if (!(0, import_node_fs26.existsSync)(manifestPath)) return fail(`bootstrap drift: ${manifestPath} not found; run from the MMI-Hub repo root \u2014 the Hub's copies ARE the reference this compares against`);
23258
+ const manifest = loadBootstrapSeeds((0, import_node_fs26.readFileSync)(manifestPath, "utf8"));
23067
23259
  const hubContents = /* @__PURE__ */ new Map();
23068
23260
  for (const s of manifest.seeds) {
23069
23261
  if (s.ownership !== "org" || s.source !== "self") continue;
23070
- hubContents.set(s.target, (0, import_node_fs25.existsSync)(s.target) ? (0, import_node_fs25.readFileSync)(s.target, "utf8") : null);
23262
+ hubContents.set(s.target, (0, import_node_fs26.existsSync)(s.target) ? (0, import_node_fs26.readFileSync)(s.target, "utf8") : null);
23071
23263
  }
23072
23264
  let targets;
23073
23265
  let classOf = (_repo) => "deployable";
@@ -23144,8 +23336,8 @@ function registerBootstrapCommands(program3) {
23144
23336
  return fail(`bootstrap apply: ${e.message}`);
23145
23337
  }
23146
23338
  const manifestPath = "skills/bootstrap/seeds/manifest.json";
23147
- if (!(0, import_node_fs25.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`);
23148
- const manifest = loadBootstrapSeeds((0, import_node_fs25.readFileSync)(manifestPath, "utf8"));
23339
+ if (!(0, import_node_fs26.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`);
23340
+ const manifest = loadBootstrapSeeds((0, import_node_fs26.readFileSync)(manifestPath, "utf8"));
23149
23341
  const baseBranch = o.class === "content" ? "main" : "development";
23150
23342
  const slug = parsedRepo.slug;
23151
23343
  const onlyTarget = o.only.trim();
@@ -23156,16 +23348,16 @@ function registerBootstrapCommands(program3) {
23156
23348
  ${known}`);
23157
23349
  }
23158
23350
  const gh = async (args) => execFileP2("gh", args, { timeout: 2e4 });
23159
- const readFile9 = (p) => (0, import_node_fs25.existsSync)(p) ? (0, import_node_fs25.readFileSync)(p, "utf8") : null;
23351
+ const readFile9 = (p) => (0, import_node_fs26.existsSync)(p) ? (0, import_node_fs26.readFileSync)(p, "utf8") : null;
23160
23352
  const enc = (p) => p.split("/").map(encodeURIComponent).join("/");
23161
23353
  const putSeed = async (target, content, ref, sha) => {
23162
- const tmp = (0, import_node_path24.join)((0, import_node_os9.tmpdir)(), `mmi-seed-${process.pid}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}.json`);
23163
- (0, import_node_fs25.writeFileSync)(tmp, JSON.stringify(contentPutBody(target, content, ref, sha)), "utf8");
23354
+ const tmp = (0, import_node_path25.join)((0, import_node_os9.tmpdir)(), `mmi-seed-${process.pid}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}.json`);
23355
+ (0, import_node_fs26.writeFileSync)(tmp, JSON.stringify(contentPutBody(target, content, ref, sha)), "utf8");
23164
23356
  try {
23165
23357
  await gh(contentPutInputArgs(repo, target, tmp));
23166
23358
  } finally {
23167
23359
  try {
23168
- (0, import_node_fs25.unlinkSync)(tmp);
23360
+ (0, import_node_fs26.unlinkSync)(tmp);
23169
23361
  } catch {
23170
23362
  }
23171
23363
  }
@@ -23417,12 +23609,12 @@ LIVE apply to ${repo}:
23417
23609
  }
23418
23610
 
23419
23611
  // src/stage-commands.ts
23420
- var import_node_fs27 = require("node:fs");
23421
- var import_node_path26 = require("node:path");
23612
+ var import_node_fs28 = require("node:fs");
23613
+ var import_node_path27 = require("node:path");
23422
23614
 
23423
23615
  // src/port-registry.ts
23424
- var import_node_fs26 = require("node:fs");
23425
- var import_node_path25 = require("node:path");
23616
+ var import_node_fs27 = require("node:fs");
23617
+ var import_node_path26 = require("node:path");
23426
23618
 
23427
23619
  // ../infra/port-geometry.mjs
23428
23620
  var PORT_BLOCK = 100;
@@ -23436,8 +23628,8 @@ function nextPortBlock(registry2) {
23436
23628
  return [base, base + PORT_SPAN];
23437
23629
  }
23438
23630
  function loadPortRegistry(path2) {
23439
- if (!(0, import_node_fs26.existsSync)(path2)) return {};
23440
- const raw = JSON.parse((0, import_node_fs26.readFileSync)(path2, "utf8"));
23631
+ if (!(0, import_node_fs27.existsSync)(path2)) return {};
23632
+ const raw = JSON.parse((0, import_node_fs27.readFileSync)(path2, "utf8"));
23441
23633
  const out = {};
23442
23634
  for (const [key, value] of Object.entries(raw)) {
23443
23635
  if (Array.isArray(value) && value.length === 2 && value.every((n) => typeof n === "number")) {
@@ -23451,9 +23643,9 @@ function ensurePortRange(repo, path2) {
23451
23643
  const existing = registry2[repo];
23452
23644
  if (existing) return existing;
23453
23645
  const range = nextPortBlock(registry2);
23454
- const raw = (0, import_node_fs26.existsSync)(path2) ? JSON.parse((0, import_node_fs26.readFileSync)(path2, "utf8")) : {};
23646
+ const raw = (0, import_node_fs27.existsSync)(path2) ? JSON.parse((0, import_node_fs27.readFileSync)(path2, "utf8")) : {};
23455
23647
  raw[repo] = range;
23456
- (0, import_node_fs26.writeFileSync)(path2, JSON.stringify(raw, null, 2) + "\n", "utf8");
23648
+ (0, import_node_fs27.writeFileSync)(path2, JSON.stringify(raw, null, 2) + "\n", "utf8");
23457
23649
  return range;
23458
23650
  }
23459
23651
  function portCursorSeed(registry2) {
@@ -23475,22 +23667,22 @@ function existingPortRange(repo, registry2) {
23475
23667
  return registry2[repo] ?? null;
23476
23668
  }
23477
23669
  function portRangeInfraAt(root, source) {
23478
- const registryPath = (0, import_node_path25.join)(root, "infra", "port-ranges.json");
23479
- const ddbScriptPath = (0, import_node_path25.join)(root, "infra", "port-ddb.mjs");
23480
- if (!(0, import_node_fs26.existsSync)(registryPath) || !(0, import_node_fs26.existsSync)(ddbScriptPath)) return null;
23670
+ const registryPath = (0, import_node_path26.join)(root, "infra", "port-ranges.json");
23671
+ const ddbScriptPath = (0, import_node_path26.join)(root, "infra", "port-ddb.mjs");
23672
+ if (!(0, import_node_fs27.existsSync)(registryPath) || !(0, import_node_fs27.existsSync)(ddbScriptPath)) return null;
23481
23673
  return { root, source, registryPath, ddbScriptPath };
23482
23674
  }
23483
23675
  function resolvePortRangeInfra(cwd, packageDir) {
23484
23676
  const direct = portRangeInfraAt(cwd, "cwd");
23485
23677
  if (direct) return direct;
23486
- for (let dir = cwd; ; dir = (0, import_node_path25.dirname)(dir)) {
23487
- const sibling = portRangeInfraAt((0, import_node_path25.join)(dir, "MMI-Hub"), "sibling-hub");
23678
+ for (let dir = cwd; ; dir = (0, import_node_path26.dirname)(dir)) {
23679
+ const sibling = portRangeInfraAt((0, import_node_path26.join)(dir, "MMI-Hub"), "sibling-hub");
23488
23680
  if (sibling) return sibling;
23489
- const parent = (0, import_node_path25.dirname)(dir);
23681
+ const parent = (0, import_node_path26.dirname)(dir);
23490
23682
  if (parent === dir) break;
23491
23683
  }
23492
23684
  if (packageDir) {
23493
- const pkgRoot = (0, import_node_path25.join)(packageDir, "..", "..");
23685
+ const pkgRoot = (0, import_node_path26.join)(packageDir, "..", "..");
23494
23686
  const pkgFrom = portRangeInfraAt(pkgRoot, "pkg-root");
23495
23687
  if (pkgFrom) return pkgFrom;
23496
23688
  }
@@ -23666,8 +23858,8 @@ function registerStageCommands(program3) {
23666
23858
  const portRange = portRangeMeta && typeof portRangeMeta.start === "number" && typeof portRangeMeta.end === "number" ? [portRangeMeta.start, portRangeMeta.end] : void 0;
23667
23859
  return decideStage({
23668
23860
  registry: { deployModel: project2?.deployModel, portRange, error: read.ok ? void 0 : read.error },
23669
- hasCompose: (0, import_node_fs27.existsSync)((0, import_node_path26.join)(process.cwd(), "docker-compose.yml")),
23670
- hasEnvExample: (0, import_node_fs27.existsSync)((0, import_node_path26.join)(process.cwd(), ".env.example"))
23861
+ hasCompose: (0, import_node_fs28.existsSync)((0, import_node_path27.join)(process.cwd(), "docker-compose.yml")),
23862
+ hasEnvExample: (0, import_node_fs28.existsSync)((0, import_node_path27.join)(process.cwd(), ".env.example"))
23671
23863
  });
23672
23864
  }
23673
23865
  async function fetchStageVaultEnvMerge() {
@@ -24105,11 +24297,11 @@ function registerBoardCommands(program3) {
24105
24297
  }
24106
24298
 
24107
24299
  // src/merge-cleanup.ts
24108
- var import_node_fs28 = require("node:fs");
24300
+ var import_node_fs29 = require("node:fs");
24109
24301
  var import_promises7 = require("node:fs/promises");
24110
- var import_node_path28 = require("node:path");
24302
+ var import_node_path29 = require("node:path");
24111
24303
  var import_node_os10 = require("node:os");
24112
- var import_node_child_process13 = require("node:child_process");
24304
+ var import_node_child_process14 = require("node:child_process");
24113
24305
 
24114
24306
  // src/board-advance.ts
24115
24307
  function repoOf2(ref) {
@@ -24195,7 +24387,7 @@ function boardAdvanceFailureMessage(result) {
24195
24387
 
24196
24388
  // src/deferred-registry-store.ts
24197
24389
  var import_promises6 = require("node:fs/promises");
24198
- var import_node_path27 = require("node:path");
24390
+ var import_node_path28 = require("node:path");
24199
24391
  var sleep2 = (ms) => new Promise((resolve5) => setTimeout(resolve5, ms));
24200
24392
  async function atomicWrite(target, contents) {
24201
24393
  const tmp = `${target}.tmp-${process.pid}-${Math.random().toString(36).slice(2)}`;
@@ -24246,12 +24438,12 @@ function makeDeferredWorktreeStore(registryPath, lockOpts = {}) {
24246
24438
  },
24247
24439
  // Standalone atomic write — THROWS on failure (no best-effort swallow, #2846).
24248
24440
  write: async (entries) => {
24249
- await (0, import_promises6.mkdir)((0, import_node_path27.dirname)(registryPath), { recursive: true });
24441
+ await (0, import_promises6.mkdir)((0, import_node_path28.dirname)(registryPath), { recursive: true });
24250
24442
  await atomicWrite(registryPath, serializeDeferredWorktrees(entries));
24251
24443
  },
24252
24444
  // Serialized read-modify-write under the repo-wide lock (#2846).
24253
24445
  update: async (mutate) => {
24254
- await (0, import_promises6.mkdir)((0, import_node_path27.dirname)(registryPath), { recursive: true });
24446
+ await (0, import_promises6.mkdir)((0, import_node_path28.dirname)(registryPath), { recursive: true });
24255
24447
  const deadline = Date.now() + opts.maxWaitMs;
24256
24448
  for (; ; ) {
24257
24449
  const guard = await acquireLock(lockPath, opts, deadline);
@@ -24412,7 +24604,7 @@ async function applyGcPlan(plan, remote, opts = {}) {
24412
24604
  );
24413
24605
  const repoRoot2 = (await execFileP2("git", ["rev-parse", "--show-toplevel"], { timeout: GIT_TIMEOUT_MS }).catch(() => ({ stdout: "" }))).stdout.trim() || process.cwd();
24414
24606
  const worktreeGitRoot = await currentRepoWorktreeGitRoot(repoRoot2);
24415
- const primaryRepoRoot = worktreeGitRoot ? (0, import_node_path28.dirname)((0, import_node_path28.dirname)(worktreeGitRoot)) : repoRoot2;
24607
+ const primaryRepoRoot = worktreeGitRoot ? (0, import_node_path29.dirname)((0, import_node_path29.dirname)(worktreeGitRoot)) : repoRoot2;
24416
24608
  const gcActor = describeActor({ env: process.env, surface: detectSurface(process.env), cwd: process.cwd() });
24417
24609
  const owners = readWorktreeOwners(primaryRepoRoot);
24418
24610
  const removalNow = Date.now();
@@ -24443,7 +24635,7 @@ async function applyGcPlan(plan, remote, opts = {}) {
24443
24635
  const cleanup = await cleanupPrMergeLocalBranch(branch.branch, {
24444
24636
  beforeWorktrees,
24445
24637
  startingPath: branch.worktreePath,
24446
- pathExists: (p) => (0, import_node_fs28.existsSync)(p),
24638
+ pathExists: (p) => (0, import_node_fs29.existsSync)(p),
24447
24639
  execGit: async (args) => (await execFileP2("git", args, { timeout: GIT_TIMEOUT_MS })).stdout,
24448
24640
  teardownWorktreeStage,
24449
24641
  deferredStore,
@@ -24471,7 +24663,7 @@ async function applyGcPlan(plan, remote, opts = {}) {
24471
24663
  for (const wt of worktreeDirsToRemove) {
24472
24664
  try {
24473
24665
  const cleanupTarget = resolveSafeSiblingWorktreeCleanupTarget(wt.path, siblingRoot, {
24474
- realpath: (path2) => (0, import_node_fs28.realpathSync)(path2)
24666
+ realpath: (path2) => (0, import_node_fs29.realpathSync)(path2)
24475
24667
  });
24476
24668
  if (!cleanupTarget.ok) {
24477
24669
  result.failed.push(`${wt.path}: ${cleanupTarget.reason}`);
@@ -24536,13 +24728,13 @@ async function composeOverrideBodyFile(prNumber, repoArgs, gh) {
24536
24728
  const commits = JSON.parse(raw).commits ?? [];
24537
24729
  const body = squashBodyWithOverride(commits.map((c) => ({ headline: c.messageHeadline ?? "", body: c.messageBody ?? "" })), process.cwd());
24538
24730
  if (!body) return void 0;
24539
- const dir = (0, import_node_fs28.mkdtempSync)((0, import_node_path28.join)((0, import_node_os10.tmpdir)(), "mmi-squash-body-"));
24540
- const path2 = (0, import_node_path28.join)(dir, "body.txt");
24541
- (0, import_node_fs28.writeFileSync)(path2, `${body}
24731
+ const dir = (0, import_node_fs29.mkdtempSync)((0, import_node_path29.join)((0, import_node_os10.tmpdir)(), "mmi-squash-body-"));
24732
+ const path2 = (0, import_node_path29.join)(dir, "body.txt");
24733
+ (0, import_node_fs29.writeFileSync)(path2, `${body}
24542
24734
  `, "utf8");
24543
24735
  return { path: path2, cleanup: () => {
24544
24736
  try {
24545
- (0, import_node_fs28.rmSync)(dir, { recursive: true, force: true });
24737
+ (0, import_node_fs29.rmSync)(dir, { recursive: true, force: true });
24546
24738
  } catch {
24547
24739
  }
24548
24740
  } };
@@ -24650,7 +24842,7 @@ async function remoteBranchExists2(branch, options = {}) {
24650
24842
  }
24651
24843
  var COMPOSE_TIMEOUT_MS = 12e4;
24652
24844
  function spawnDeferredGcSweep() {
24653
- spawnDetachedSelf(["worktree", "gc", "sweep-deferred", "--quiet"], { spawn: import_node_child_process13.spawn, execPath: process.execPath, scriptPath: process.argv[1] });
24845
+ spawnDetachedSelf(["worktree", "gc", "sweep-deferred", "--quiet"], { spawn: import_node_child_process14.spawn, execPath: process.execPath, scriptPath: process.argv[1] });
24654
24846
  }
24655
24847
  async function createDeferredWorktreeStore() {
24656
24848
  try {
@@ -24664,13 +24856,13 @@ var realWorktreeDirRemover = {
24664
24856
  probe: (p) => {
24665
24857
  let st;
24666
24858
  try {
24667
- st = (0, import_node_fs28.lstatSync)(p);
24859
+ st = (0, import_node_fs29.lstatSync)(p);
24668
24860
  } catch {
24669
24861
  return null;
24670
24862
  }
24671
24863
  if (st.isSymbolicLink()) return "link";
24672
24864
  try {
24673
- (0, import_node_fs28.readlinkSync)(p);
24865
+ (0, import_node_fs29.readlinkSync)(p);
24674
24866
  return "link";
24675
24867
  } catch {
24676
24868
  }
@@ -24678,7 +24870,7 @@ var realWorktreeDirRemover = {
24678
24870
  },
24679
24871
  readdir: (p) => {
24680
24872
  try {
24681
- return (0, import_node_fs28.readdirSync)(p);
24873
+ return (0, import_node_fs29.readdirSync)(p);
24682
24874
  } catch {
24683
24875
  return [];
24684
24876
  }
@@ -24687,9 +24879,9 @@ var realWorktreeDirRemover = {
24687
24879
  // leaving the target); a file symlink with unlink. rmdir first, fall back to unlink.
24688
24880
  detachLink: (p) => {
24689
24881
  try {
24690
- (0, import_node_fs28.rmdirSync)(p);
24882
+ (0, import_node_fs29.rmdirSync)(p);
24691
24883
  } catch {
24692
- (0, import_node_fs28.unlinkSync)(p);
24884
+ (0, import_node_fs29.unlinkSync)(p);
24693
24885
  }
24694
24886
  },
24695
24887
  removeTree: (p) => (0, import_promises7.rm)(p, { recursive: true, force: true, maxRetries: 5, retryDelay: 200 })
@@ -24722,9 +24914,9 @@ async function worktreeHasStageState(worktreePath) {
24722
24914
  }
24723
24915
  }
24724
24916
  function stageStateFileBelongsToWorktree(statePath, worktreePath) {
24725
- if (!(0, import_node_fs28.existsSync)(statePath)) return false;
24917
+ if (!(0, import_node_fs29.existsSync)(statePath)) return false;
24726
24918
  try {
24727
- const state = JSON.parse((0, import_node_fs28.readFileSync)(statePath, "utf8"));
24919
+ const state = JSON.parse((0, import_node_fs29.readFileSync)(statePath, "utf8"));
24728
24920
  const recordedCwd = typeof state.identity?.cwd === "string" ? state.identity.cwd : typeof state.cwd === "string" ? state.cwd : "";
24729
24921
  return Boolean(recordedCwd && isPathUnderDirectory(recordedCwd, worktreePath));
24730
24922
  } catch {
@@ -24931,9 +25123,9 @@ async function fetchRestCorePool(gh = defaultGhApi) {
24931
25123
  }
24932
25124
 
24933
25125
  // src/worktree-lifecycle-commands.ts
24934
- var import_node_fs29 = require("node:fs");
25126
+ var import_node_fs30 = require("node:fs");
24935
25127
  var import_promises8 = require("node:fs/promises");
24936
- var import_node_path29 = require("node:path");
25128
+ var import_node_path30 = require("node:path");
24937
25129
  var GH_TIMEOUT_MS = 2e4;
24938
25130
  var DEFAULT_BASE = "origin/development";
24939
25131
  var DEFAULT_REMOTE = "origin";
@@ -25069,7 +25261,7 @@ function classifyStaleLeaks(input) {
25069
25261
  var defaultOrphanDirScanDeps = {
25070
25262
  listDirs: (root) => {
25071
25263
  try {
25072
- return (0, import_node_fs29.readdirSync)(root, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => (0, import_node_path29.join)(root, e.name));
25264
+ return (0, import_node_fs30.readdirSync)(root, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => (0, import_node_path30.join)(root, e.name));
25073
25265
  } catch {
25074
25266
  return [];
25075
25267
  }
@@ -25216,13 +25408,13 @@ function registerWorktreeCommands(program3) {
25216
25408
  const headBorn = await execFileP2("git", ["-C", wtPath || ".", "rev-parse", "--verify", "--quiet", "HEAD"], { timeout: GIT_TIMEOUT_MS }).then(() => true).catch(() => false);
25217
25409
  const branch = headBorn ? (await execFileP2("git", ["rev-parse", "--abbrev-ref", "HEAD"], { timeout: GIT_TIMEOUT_MS })).stdout.trim() : (await execFileP2("git", ["symbolic-ref", "--quiet", "--short", "HEAD"], { timeout: GIT_TIMEOUT_MS }).catch(() => ({ stdout: "" }))).stdout.trim();
25218
25410
  if (!wtPath || !branch) return fail("worktree land: not inside a git worktree");
25219
- const gitFile = (0, import_node_path29.join)(wtPath, ".git");
25220
- const isLinked = (0, import_node_fs29.existsSync)(gitFile) && (0, import_node_fs29.statSync)(gitFile).isFile();
25411
+ const gitFile = (0, import_node_path30.join)(wtPath, ".git");
25412
+ const isLinked = (0, import_node_fs30.existsSync)(gitFile) && (0, import_node_fs30.statSync)(gitFile).isFile();
25221
25413
  if (apply && !isLinked) {
25222
25414
  return fail("worktree land: run from inside the linked worktree you want to land (this is the primary checkout)");
25223
25415
  }
25224
25416
  const commonDir = (await execFileP2("git", ["rev-parse", "--git-common-dir"], { timeout: GIT_TIMEOUT_MS }).catch(() => ({ stdout: "" }))).stdout.trim();
25225
- const primaryCheckout = commonDir ? (0, import_node_path29.dirname)(commonDir) : wtPath;
25417
+ const primaryCheckout = commonDir ? (0, import_node_path30.dirname)(commonDir) : wtPath;
25226
25418
  const localBranchNames = await execFileP2("git", ["-C", primaryCheckout, "for-each-ref", "--format=%(refname:short)", "refs/heads"], { timeout: GIT_TIMEOUT_MS }).then(({ stdout }) => new Set((stdout || "").split("\n").map((l) => l.trim()).filter(Boolean))).catch(() => void 0);
25227
25419
  const orphan = classifyOrphanedWorktree({
25228
25420
  branch,
@@ -25409,10 +25601,10 @@ async function gatherWorktreeContext() {
25409
25601
  if (s) stages.push({ path: wt.path, port: s.port });
25410
25602
  }
25411
25603
  const worktreeGitRoot = await currentRepoWorktreeGitRoot(repoRoot2);
25412
- const primaryRepoRoot = worktreeGitRoot ? (0, import_node_path29.dirname)((0, import_node_path29.dirname)(worktreeGitRoot)) : repoRoot2;
25604
+ const primaryRepoRoot = worktreeGitRoot ? (0, import_node_path30.dirname)((0, import_node_path30.dirname)(worktreeGitRoot)) : repoRoot2;
25413
25605
  const wtRoot = siblingMmiWorktreesRoot(primaryRepoRoot);
25414
25606
  let orphanDirs = [];
25415
- if ((0, import_node_fs29.existsSync)(wtRoot)) {
25607
+ if ((0, import_node_fs30.existsSync)(wtRoot)) {
25416
25608
  orphanDirs = scanOrphanDirs(wtRoot, worktreeGitRoot, {
25417
25609
  ...defaultOrphanDirScanDeps,
25418
25610
  listDirs: (root) => worktreeScanDirs(root, primaryRepoRoot, defaultOrphanDirScanDeps.listDirs, isRepoCheckoutDir)
@@ -25435,7 +25627,7 @@ async function bestEffortGit(args, cwd, step, timeoutMs = GIT_TIMEOUT_MS) {
25435
25627
  }
25436
25628
 
25437
25629
  // src/issue-commands.ts
25438
- var import_node_fs30 = require("node:fs");
25630
+ var import_node_fs31 = require("node:fs");
25439
25631
  var import_node_crypto5 = require("node:crypto");
25440
25632
  var ghRunner = async (args, timeoutMs) => (await execFileP2("gh", args, { timeout: timeoutMs })).stdout;
25441
25633
  var ReparentConflictError = class extends Error {
@@ -25453,7 +25645,7 @@ async function editIssue(client, options, deps = {}) {
25453
25645
  const url = `https://github.com/${repo}/issues/${parsed.number}`;
25454
25646
  const patch = {};
25455
25647
  let bodyChanged = false;
25456
- const textDeps = () => deps.textDeps ?? { readFile: (p, e) => Promise.resolve((0, import_node_fs30.readFileSync)(p, e)), readStdin: () => Promise.resolve("") };
25648
+ const textDeps = () => deps.textDeps ?? { readFile: (p, e) => Promise.resolve((0, import_node_fs31.readFileSync)(p, e)), readStdin: () => Promise.resolve("") };
25457
25649
  if (options.titleFile !== void 0) {
25458
25650
  patch.title = await resolveIssueTitle({ title: options.title, titleFile: options.titleFile }, textDeps());
25459
25651
  } else if (options.title !== void 0) {
@@ -26016,7 +26208,7 @@ function extendCreateCommand(issue2, batchAttach) {
26016
26208
  if (opts.batch) {
26017
26209
  let specs;
26018
26210
  try {
26019
- const raw = (0, import_node_fs30.readFileSync)(opts.batch, "utf8");
26211
+ const raw = (0, import_node_fs31.readFileSync)(opts.batch, "utf8");
26020
26212
  specs = JSON.parse(raw);
26021
26213
  if (!Array.isArray(specs)) throw new Error("batch file must contain a JSON array");
26022
26214
  } catch (e) {
@@ -26090,8 +26282,8 @@ ${lines}`, {
26090
26282
  }
26091
26283
 
26092
26284
  // src/train-commands.ts
26093
- var import_node_fs31 = require("node:fs");
26094
- var import_node_path30 = require("node:path");
26285
+ var import_node_fs32 = require("node:fs");
26286
+ var import_node_path31 = require("node:path");
26095
26287
 
26096
26288
  // src/train-status.ts
26097
26289
  function buildTrainStatusReport(input) {
@@ -26131,7 +26323,7 @@ function formatTrainStatus(r) {
26131
26323
  // src/train-commands.ts
26132
26324
  function readRepoVersion() {
26133
26325
  try {
26134
- return JSON.parse((0, import_node_fs31.readFileSync)((0, import_node_path30.join)(process.cwd(), ".claude-plugin", "plugin.json"), "utf8")).version || void 0;
26326
+ return JSON.parse((0, import_node_fs32.readFileSync)((0, import_node_path31.join)(process.cwd(), ".claude-plugin", "plugin.json"), "utf8")).version || void 0;
26135
26327
  } catch {
26136
26328
  return void 0;
26137
26329
  }
@@ -26277,9 +26469,9 @@ function registerDeployCommands(program3) {
26277
26469
  }
26278
26470
 
26279
26471
  // src/discovery-commands.ts
26280
- var import_node_fs32 = require("node:fs");
26472
+ var import_node_fs33 = require("node:fs");
26281
26473
  var import_node_os11 = require("node:os");
26282
- var import_node_path31 = require("node:path");
26474
+ var import_node_path32 = require("node:path");
26283
26475
  var GC_GH_TIMEOUT_MS3 = 2e4;
26284
26476
  async function collectStatus() {
26285
26477
  let branch = "";
@@ -26456,8 +26648,8 @@ async function collectOnboardStatus() {
26456
26648
  }
26457
26649
  const home = (0, import_node_os11.homedir)();
26458
26650
  const plugin = onboardPluginGate({
26459
- readKnown: () => readFileSyncSafe((0, import_node_path31.join)(home, ...KNOWN_MARKETPLACES_RELATIVE), import_node_fs32.readFileSync),
26460
- readSettings: () => readFileSyncSafe((0, import_node_path31.join)(home, ".claude", "settings.json"), import_node_fs32.readFileSync)
26651
+ readKnown: () => readFileSyncSafe((0, import_node_path32.join)(home, ...KNOWN_MARKETPLACES_RELATIVE), import_node_fs33.readFileSync),
26652
+ readSettings: () => readFileSyncSafe((0, import_node_path32.join)(home, ".claude", "settings.json"), import_node_fs33.readFileSync)
26461
26653
  });
26462
26654
  return { track, board, registry: registry2, secrets, plugin, nextCommand };
26463
26655
  }
@@ -27301,19 +27493,6 @@ function renderTally(checks) {
27301
27493
  const healthy = total - checks.filter((c) => !c.ok).length;
27302
27494
  return healthy === total ? `\u2713 all ${total} checks healthy` : `\u2713 ${healthy} of ${total} checks healthy`;
27303
27495
  }
27304
- function renderDoctorText(checks, opts) {
27305
- const lines = [];
27306
- const shown = opts.verbose ? checks : checks.filter((c) => !c.ok || c.warn);
27307
- for (const check of shown) {
27308
- lines.push(renderCheckLine(check));
27309
- if (opts.verbose) {
27310
- for (const evidence of check.verbose ?? []) lines.push(`${VERBOSE_INDENT}${evidence}`);
27311
- }
27312
- }
27313
- lines.push(renderTally(checks));
27314
- if (opts.restartPending) lines.push(RESTART_LINE);
27315
- return lines.join("\n");
27316
- }
27317
27496
  function doctorReportExitCode(checks) {
27318
27497
  return checks.some((c) => !c.ok && !c.reportOnly) ? 1 : 0;
27319
27498
  }
@@ -28003,7 +28182,8 @@ var DOCTOR_SURFACES = Object.freeze(
28003
28182
  installLocator: surface.install.locator,
28004
28183
  upgradeMechanism: surface.upgrade.mechanism,
28005
28184
  reload: surface.upgrade.reload,
28006
- repairOwner: surface.ownership.repair === "mmi-cli" ? "mmi-cli" : "operator"
28185
+ repairOwner: surface.ownership.repair === "mmi-cli" ? "mmi-cli" : "operator",
28186
+ trustOwner: surface.ownership.trust === "operator" ? "operator" : "host"
28007
28187
  }))
28008
28188
  );
28009
28189
  function doctorSurface(token) {
@@ -28036,11 +28216,8 @@ function diagnoseSurface(evidence) {
28036
28216
  return { ...base, state: "clean" };
28037
28217
  }
28038
28218
  function reloadInstruction(descriptor) {
28039
- if (descriptor.token === "claude") return "restart Claude";
28040
- if (descriptor.token === "codex") return "restart Codex";
28041
- if (descriptor.token === "kimi") return "restart Kimi Code CLI";
28042
- if (descriptor.token === "kilo") return "restart Kilo Code";
28043
- return descriptor.reload === "workspace" ? `reload ${descriptor.displayName}` : `restart ${descriptor.displayName}`;
28219
+ const verb = descriptor.reload === "workspace" ? "reload" : "restart";
28220
+ return `${verb} ${descriptor.displayName}`;
28044
28221
  }
28045
28222
  function planSurfaceRepair(diagnosis) {
28046
28223
  if (diagnosis.state === "clean" || diagnosis.state === "skipped" || diagnosis.state === "freshness-unknown") return null;
@@ -28085,10 +28262,16 @@ function buildSurfaceDoctorCheck(diagnosis) {
28085
28262
  detail: detailByState[state],
28086
28263
  ...plan ? { fix: plan.instruction } : state === "freshness-unknown" ? { fix: "check `mmi-cli --version` against `npm view @mutmutco/cli version`, then rerun doctor" } : {},
28087
28264
  verbose: [
28265
+ // The three numbers the legacy builder used to print. They belong here now that this is the only
28266
+ // plugin row, and a report that names a state without naming the versions behind it is not evidence.
28267
+ `installed: ${diagnosis.installedVersion ?? "(none)"}`,
28268
+ `released: ${diagnosis.releasedVersion ?? "(not checked \u2014 offline or --fast)"}`,
28269
+ `state: ${state}`,
28088
28270
  `registry surface: ${descriptor.token}`,
28089
28271
  `install: ${descriptor.installMechanism} (${descriptor.installLocator})`,
28090
28272
  `repair owner: ${descriptor.repairOwner}`,
28091
- `artifacts: ${descriptor.artifactIds.join(", ")}`
28273
+ `artifacts: ${descriptor.artifactIds.join(", ")}`,
28274
+ ...diagnosis.repairDetail ? [`heal: ${diagnosis.repairDetail}`] : []
28092
28275
  ]
28093
28276
  };
28094
28277
  }
@@ -28186,77 +28369,23 @@ function checkWorktreeRoots(probe) {
28186
28369
  verbose: evidence
28187
28370
  };
28188
28371
  }
28189
- function pluginHealTrigger(probe) {
28190
- if (probe.guardState === "no-install" || probe.guardState === "unresolved") return "unresolved";
28191
- const behind = Boolean(probe.installed && probe.released) && compareVersions(probe.installed, probe.released) < 0;
28192
- return behind ? "behind" : null;
28193
- }
28194
- function checkClaudePlugin(probe) {
28195
- const { installed, released, guardState } = probe;
28196
- const codex = probe.surface === "codex";
28197
- const kilo = probe.surface === "kilo";
28198
- const cursor = probe.surface === "cursor";
28199
- const id = codex ? "codex-plugin" : kilo ? "kilo-plugin" : cursor ? "cursor-plugin" : "claude-plugin";
28200
- const label = codex ? "Codex plugin" : kilo ? "Kilo plugin" : cursor ? "Cursor plugin" : "Claude plugin";
28201
- const restart = codex ? "restart Codex" : kilo ? "reload Kilo" : cursor ? "reload Cursor" : "restart Claude";
28202
- const evidence = [
28203
- `installed: ${installed ?? "(none)"}`,
28204
- // #3485 item 7: when the value was reused from the banner's once-a-day cache, say so and say how old.
28205
- `released: ${released ?? "(not checked \u2014 offline or --fast)"}${released && probe.releasedNote ? ` ${probe.releasedNote}` : ""}`,
28206
- `resolvable: ${guardState}`
28207
- ];
28208
- const trigger = pluginHealTrigger(probe);
28209
- if (trigger === "unresolved") {
28210
- return {
28211
- id,
28212
- ok: false,
28213
- label,
28214
- detail: guardState === "no-install" ? "not installed" : "unresolved (delivery/cache missing)",
28215
- fix: `run \`mmi-cli doctor --apply\` (or \`mmi-cli plugin heal\`) to reinstall the MMI plugin, then ${restart}`,
28216
- verbose: evidence
28217
- };
28218
- }
28219
- if (trigger === "behind") {
28220
- return {
28221
- id,
28222
- ok: false,
28223
- label,
28224
- detail: `${installed} \u2192 ${released}`,
28225
- // Name the CLI verb that actually fixes it, like every other red row — `/plugin` is the manual
28226
- // fallback, not the first resort (#3282).
28227
- fix: `run \`mmi-cli doctor --apply\` (or \`mmi-cli plugin heal\`) to reinstall it, then ${restart}`,
28228
- verbose: evidence
28229
- };
28230
- }
28231
- if (!released) {
28232
- return {
28233
- id,
28234
- ok: false,
28235
- reportOnly: true,
28236
- label,
28237
- detail: `${installed ?? "installed"} \u2014 freshness UNKNOWN, the published version could not be read`,
28238
- fix: "check it directly: `npm view @mutmutco/cli version`, then `mmi-cli plugin heal` if behind",
28239
- verbose: evidence
28240
- };
28241
- }
28242
- return { id, ok: true, label, ...installed ? { detail: installed } : {}, verbose: evidence };
28243
- }
28244
- function checkCodexHookTrust(probe) {
28372
+ function checkCodexHookTrust(probe, displayName = "Codex") {
28245
28373
  if (!probe?.applicable) return null;
28246
28374
  const evidence = [
28247
28375
  `stored approval rows: ${probe.trustedCount}/${probe.requiredCount}`,
28248
28376
  "current command hashes: verify interactively in /hooks"
28249
28377
  ];
28378
+ const label = `${displayName} hook trust`;
28250
28379
  if (probe.trusted) {
28251
- return { id: "codex-hook-trust", ok: true, label: "Codex hook trust", detail: "trusted", verbose: evidence };
28380
+ return { id: "codex-hook-trust", ok: true, label, detail: "trusted", verbose: evidence };
28252
28381
  }
28253
28382
  return {
28254
28383
  id: "codex-hook-trust",
28255
28384
  ok: true,
28256
28385
  warn: true,
28257
- label: "Codex hook trust",
28386
+ label,
28258
28387
  detail: probe.requiredCount > 0 ? `${probe.trustedCount}/${probe.requiredCount} approval rows present \u2014 current hashes unverified` : "hook bundle could not be verified",
28259
- fix: probe.requiredCount > 0 ? "Codex does not allow silent hook approval; run `/hooks`, review the MMI commands, and trust them" : "run `mmi-cli plugin heal`, restart Codex, then review and trust MMI under `/hooks`",
28388
+ fix: probe.requiredCount > 0 ? `${displayName} does not allow silent hook approval; run \`/hooks\`, review the MMI commands, and trust them` : `run \`mmi-cli plugin heal\`, restart ${displayName}, then review and trust MMI under \`/hooks\``,
28260
28389
  verbose: evidence
28261
28390
  };
28262
28391
  }
@@ -28481,6 +28610,12 @@ function gcReapable(plan) {
28481
28610
  async function runDoctorClean(opts, io, deps) {
28482
28611
  const applyEnv = Boolean(opts.apply) || Boolean(opts.preflight);
28483
28612
  const applyRepo = Boolean(opts.apply) && opts.repoWrites !== false;
28613
+ const lane = {
28614
+ banner: Boolean(opts.banner),
28615
+ fast: Boolean(opts.fast),
28616
+ preflight: Boolean(opts.preflight),
28617
+ full: !opts.fast && !opts.banner && !opts.preflight
28618
+ };
28484
28619
  const probeReleased = !opts.fast || Boolean(opts.self);
28485
28620
  const probeAws = !opts.fast && !opts.banner;
28486
28621
  const [login, isOrgRepo, released, callerArn, reach] = await Promise.all([
@@ -28495,108 +28630,69 @@ async function runDoctorClean(opts, io, deps) {
28495
28630
  opts.banner || opts.preflight || opts.fast && !opts.self ? Promise.resolve(void 0) : deps.githubRepoReach?.() ?? Promise.resolve(void 0)
28496
28631
  ]);
28497
28632
  const ghInstalled2 = login ? true : await deps.ghInstalled();
28498
- const registryEvidence = deps.surfaceEvidence?.(isOrgRepo);
28499
- const installed = registryEvidence?.installedVersion ?? deps.installedPluginVersion();
28500
- const pluginSurface = deps.pluginSurface?.() ?? "claude-cli";
28501
- const codexSurface = registryEvidence?.descriptor.token === "codex" || pluginSurface === "codex";
28502
- const cursorSurface = pluginSurface === "cursor";
28503
- const restartAction = registryEvidence ? surfaceRestartAction(registryEvidence.descriptor) : codexSurface ? "restart Codex" : cursorSurface ? "reload Cursor" : pluginSurface === "kilo" ? "restart Kilo Code" : "restart Claude";
28633
+ const registryEvidence = deps.surfaceEvidence(isOrgRepo);
28634
+ const restartAction = registryEvidence ? surfaceRestartAction(registryEvidence.descriptor) : "restart the agent host";
28504
28635
  const checks = [];
28505
28636
  let restartPending = false;
28506
- checks.push(checkGithubAuth({ login, ghInstalled: ghInstalled2, reach }));
28507
- if (!opts.fast && !opts.banner && !opts.preflight && deps.githubPools) {
28508
- checks.push(...checkGithubPools(await deps.githubPools()));
28509
- }
28510
- const aws = checkAwsIdentity({ isOrgRepo, probed: probeAws, callerArn });
28511
- if (aws) checks.push(aws);
28512
- checks.push(checkRepoWorktrees({ isOrgRepo, hasRepoLocalWorktrees: deps.hasRepoLocalWorktrees() }));
28513
- if (isOrgRepo) {
28514
- const current = deps.readGitignore();
28515
- const gi = planGitignore(current);
28516
- const giEvidence = [
28517
- `.gitignore: ${current === null ? "absent" : `${current.split("\n").length} lines`}`,
28518
- `managed block: ${gi.ok ? "present and current" : "missing or out of date"}`
28519
- ];
28520
- if (gi.ok) {
28521
- checks.push({ ok: true, id: "gitignore-block", label: "gitignore block", verbose: giEvidence });
28522
- } else if (applyRepo && gi.content && deps.writeGitignore(gi.content)) {
28523
- checks.push({ ok: true, id: "gitignore-block", label: "gitignore block", detail: "rewrote managed block", verbose: giEvidence });
28524
- restartPending = true;
28525
- } else {
28526
- checks.push({ ok: false, id: "gitignore-block", label: "gitignore block", fix: "run `mmi-cli doctor --apply` to write the org-managed .gitignore block", verbose: giEvidence });
28527
- }
28528
- }
28529
- const releasedNote = deps.releasedVersionNote?.();
28530
- const pluginProbe = {
28531
- installed,
28532
- released,
28533
- guardState: registryEvidence?.guardState ?? deps.pluginGuardState(isOrgRepo),
28534
- releasedNote,
28535
- surface: pluginSurface
28637
+ const streamed = /* @__PURE__ */ new Set();
28638
+ const worthPrinting = (c) => Boolean(opts.verbose) || !c.ok || Boolean(c.warn);
28639
+ const emitNow = (check) => {
28640
+ checks.push(check);
28641
+ if (opts.json || !worthPrinting(check)) return;
28642
+ streamed.add(check);
28643
+ io.log(renderCheckLine(check));
28644
+ if (opts.verbose) for (const evidence of check.verbose ?? []) io.log(`${VERBOSE_INDENT}${evidence}`);
28536
28645
  };
28537
- const registryDiagnosis = registryEvidence ? diagnoseSurface({ ...registryEvidence, releasedVersion: released }) : void 0;
28538
- const registryRepairPlan = registryDiagnosis ? planSurfaceRepair(registryDiagnosis) : null;
28539
- const healTrigger = registryDiagnosis ? registryRepairPlan?.supported ? registryDiagnosis.state : null : pluginHealTrigger(pluginProbe);
28646
+ const healIntent = (line) => {
28647
+ if (!opts.json) io.log(`\u21BB ${line}`);
28648
+ };
28649
+ const healStep = (message) => {
28650
+ if (!opts.json) io.log(`${VERBOSE_INDENT}${message.trim()}`);
28651
+ };
28652
+ const releasedNote = deps.releasedVersionNote?.();
28540
28653
  let pluginHealed = false;
28541
- if (applyEnv && deps.healPlugin && healTrigger && (!registryRepairPlan || registryRepairPlan.supported)) {
28542
- const heal = await deps.healPlugin();
28543
- pluginHealed = heal.ok;
28544
- if (registryEvidence) {
28545
- const repairedDiagnosis = diagnoseSurface({
28654
+ async function runPluginRow() {
28655
+ if (!registryEvidence) return;
28656
+ const diagnosis = diagnoseSurface({ ...registryEvidence, releasedVersion: released });
28657
+ const repair = planSurfaceRepair(diagnosis);
28658
+ if (applyEnv && deps.healPlugin && repair?.supported) {
28659
+ const { descriptor } = registryEvidence;
28660
+ healIntent(`${descriptor.displayName} plugin \u2014 healing via ${descriptor.installMechanism} (${descriptor.installLocator})`);
28661
+ const heal = await deps.healPlugin(healStep);
28662
+ pluginHealed = heal.ok;
28663
+ const row = buildSurfaceDoctorCheck(diagnoseSurface({
28546
28664
  ...registryEvidence,
28547
28665
  releasedVersion: released,
28548
28666
  repair: { attempted: true, ok: heal.ok, detail: heal.detail }
28549
- });
28550
- const repairedCheck = buildSurfaceDoctorCheck(repairedDiagnosis);
28551
- if (heal.skipped) repairedCheck.reportOnly = true;
28552
- checks.push(repairedCheck);
28553
- if (!heal.skipped) restartPending = true;
28554
- } else {
28555
- const measured = healTrigger === "behind" ? `${installed} \u2192 ${released}` : "unresolved install";
28556
- const healEvidence = [
28557
- `installed: ${installed ?? "(none)"}`,
28558
- `released: ${released ?? "(not checked \u2014 offline or --fast)"}`,
28559
- `resolvable: ${pluginProbe.guardState}`,
28560
- `heal: ${heal.detail}`
28561
- ];
28562
- checks.push(heal.ok ? {
28563
- id: codexSurface ? "codex-plugin" : pluginSurface === "kilo" ? "kilo-plugin" : cursorSurface ? "cursor-plugin" : "claude-plugin",
28564
- ok: true,
28565
- label: codexSurface ? "Codex plugin" : pluginSurface === "kilo" ? "Kilo plugin" : cursorSurface ? "Cursor plugin" : "Claude plugin",
28566
- detail: pluginSurface === "kilo" ? `${measured} \u2014 reinstalled via \`kilo plugin\` (the plugin provisions the skills on next load)` : cursorSurface ? `${measured} \u2014 replaced the managed local checkout (previous copy quarantined)` : `${measured} \u2014 reinstalled via the MMI marketplace (remove \u2192 add \u2192 ${codexSurface ? "add" : "install"})${codexSurface ? "; review trust in /hooks" : ""}`,
28567
- verbose: healEvidence
28568
- } : {
28569
- id: codexSurface ? "codex-plugin" : pluginSurface === "kilo" ? "kilo-plugin" : cursorSurface ? "cursor-plugin" : "claude-plugin",
28570
- ok: false,
28571
- label: codexSurface ? "Codex plugin" : pluginSurface === "kilo" ? "Kilo plugin" : cursorSurface ? "Cursor plugin" : "Claude plugin",
28572
- detail: measured,
28573
- // #3489: a heal skipped because another doctor holds the env-heal lock is a real ✗ — this run did
28574
- // not fix what it found — but it is not this invocation's to clear. The other process is doing it;
28575
- // waiting is the correct response and a re-run finds it healed. A heal that RAN and failed is a
28576
- // genuine gap and still gates.
28577
- ...heal.skipped ? { reportOnly: true } : {},
28578
- fix: heal.skipped ? `${heal.detail} \u2014 another doctor on this machine is healing it now; re-run once it finishes` : `auto-heal failed (${heal.detail}) \u2014 run \`mmi-cli plugin heal\`, then ${codexSurface ? "restart Codex" : pluginSurface === "kilo" ? "reload Kilo" : cursorSurface ? "reload Cursor" : "restart Claude"}`,
28579
- verbose: healEvidence
28580
- });
28667
+ }));
28668
+ if (heal.skipped) {
28669
+ row.reportOnly = true;
28670
+ row.fix = `${heal.detail} \u2014 another doctor on this machine is healing it now; re-run once it finishes`;
28671
+ }
28672
+ emitNow(row);
28581
28673
  if (!heal.skipped) restartPending = true;
28674
+ } else if (diagnosis.state !== "skipped") {
28675
+ const row = buildSurfaceDoctorCheck(diagnosis);
28676
+ emitNow(row);
28677
+ if (!row.ok && repair?.supported) restartPending = true;
28678
+ }
28679
+ if (registryEvidence.descriptor.trustOwner === "operator" && (registryEvidence.guardState === "healthy" || pluginHealed)) {
28680
+ const trust = checkCodexHookTrust(deps.pluginTrustState?.(), registryEvidence.descriptor.displayName);
28681
+ if (trust) emitNow(trust);
28682
+ }
28683
+ }
28684
+ async function runCliRow() {
28685
+ const cliInput = { currentVersion: deps.currentCliVersion(), releasedVersion: released };
28686
+ const cliReport = buildVersionLagReport(cliInput);
28687
+ if (!(applyEnv && deps.updateCli && versionAutoUpdateAction(cliReport) === "npm")) {
28688
+ const cli = checkCliVersion(cliInput, releasedNote);
28689
+ if (cli) emitNow(cli);
28690
+ return;
28582
28691
  }
28583
- } else {
28584
- const plugin = registryDiagnosis ? buildSurfaceDoctorCheck(registryDiagnosis) : checkClaudePlugin(pluginProbe);
28585
- if (plugin) {
28586
- if (!("state" in plugin) || plugin.state !== "skipped") checks.push(plugin);
28587
- if (!plugin.ok && (registryDiagnosis ? Boolean(registryRepairPlan?.supported) : true)) restartPending = true;
28588
- }
28589
- }
28590
- if (codexSurface && (pluginProbe.guardState === "healthy" || pluginHealed)) {
28591
- const trust = checkCodexHookTrust(deps.pluginTrustState?.());
28592
- if (trust) checks.push(trust);
28593
- }
28594
- const cliInput = { currentVersion: deps.currentCliVersion(), releasedVersion: released };
28595
- const cliReport = buildVersionLagReport(cliInput);
28596
- if (applyEnv && deps.updateCli && versionAutoUpdateAction(cliReport) === "npm") {
28597
- const heal = await deps.updateCli(cliReport.releasedVersion);
28692
+ healIntent(`mmi-cli \u2014 self-updating ${cliReport.currentVersion} \u2192 ${cliReport.releasedVersion} via npm install -g`);
28693
+ const heal = await deps.updateCli(cliReport.releasedVersion, healStep);
28598
28694
  const healEvidence = [`running: ${cliReport.currentVersion}`, `published: ${cliReport.releasedVersion}`, `heal: ${heal.detail}`];
28599
- checks.push(heal.ok ? {
28695
+ emitNow(heal.ok ? {
28600
28696
  id: "cli-version",
28601
28697
  ok: true,
28602
28698
  label: "mmi-cli",
@@ -28612,17 +28708,54 @@ async function runDoctorClean(opts, io, deps) {
28612
28708
  fix: heal.skipped ? `${heal.detail} \u2014 another doctor on this machine is updating it now; re-run once it finishes` : `self-update failed (${heal.detail}) \u2014 run \`${cliUpdateCommand(cliReport.releasedVersion)}\``,
28613
28709
  verbose: healEvidence
28614
28710
  });
28615
- } else {
28616
- const cli = checkCliVersion(cliInput, releasedNote);
28617
- if (cli) checks.push(cli);
28618
28711
  }
28619
- checks.push(checkPluginCache(deps.pluginCache()));
28620
- if (deps.sessionPayload) {
28712
+ async function runGithubAuthRow() {
28713
+ emitNow(checkGithubAuth({ login, ghInstalled: ghInstalled2, reach }));
28714
+ }
28715
+ async function runGithubPoolsRows() {
28716
+ for (const pool of checkGithubPools(await deps.githubPools())) emitNow(pool);
28717
+ }
28718
+ async function runAwsRow() {
28719
+ const aws = checkAwsIdentity({ isOrgRepo, probed: probeAws, callerArn });
28720
+ if (aws) emitNow(aws);
28721
+ }
28722
+ async function runRepoWorktreesRow() {
28723
+ emitNow(checkRepoWorktrees({ isOrgRepo, hasRepoLocalWorktrees: deps.hasRepoLocalWorktrees() }));
28724
+ }
28725
+ async function runGitignoreRow() {
28726
+ const current = deps.readGitignore();
28727
+ const gi = planGitignore(current);
28728
+ const giEvidence = [
28729
+ `.gitignore: ${current === null ? "absent" : `${current.split("\n").length} lines`}`,
28730
+ `managed block: ${gi.ok ? "present and current" : "missing or out of date"}`
28731
+ ];
28732
+ if (gi.ok) {
28733
+ emitNow({ ok: true, id: "gitignore-block", label: "gitignore block", verbose: giEvidence });
28734
+ } else if (applyRepo && gi.content && deps.writeGitignore(gi.content)) {
28735
+ emitNow({ ok: true, id: "gitignore-block", label: "gitignore block", detail: "rewrote managed block", verbose: giEvidence });
28736
+ restartPending = true;
28737
+ } else {
28738
+ emitNow({ ok: false, id: "gitignore-block", label: "gitignore block", fix: "run `mmi-cli doctor --apply` to write the org-managed .gitignore block", verbose: giEvidence });
28739
+ }
28740
+ }
28741
+ async function runPluginCacheRow() {
28742
+ emitNow(checkPluginCache(deps.pluginCache()));
28743
+ }
28744
+ async function runSessionPayloadRow() {
28621
28745
  const payload = checkSessionPayload(deps.sessionPayload());
28622
- if (payload) checks.push(payload);
28746
+ if (payload) emitNow(payload);
28747
+ }
28748
+ async function runMarketplaceRows() {
28749
+ if (applyEnv) {
28750
+ const healed = deps.healMarketplacePins();
28751
+ if (healed) {
28752
+ healIntent(`marketplace pins \u2014 ${healed}`);
28753
+ restartPending = true;
28754
+ }
28755
+ }
28756
+ for (const row of deps.marketplaceRows()) emitNow(row);
28623
28757
  }
28624
- if (deps.marketplaceRows) checks.push(...deps.marketplaceRows());
28625
- if (!opts.fast && !opts.banner && !opts.preflight) {
28758
+ async function runSchedulesRow() {
28626
28759
  const probe = await deps.schedulesNotebook().catch((e) => ({
28627
28760
  armed: 0,
28628
28761
  live: 0,
@@ -28631,32 +28764,32 @@ async function runDoctorClean(opts, io, deps) {
28631
28764
  drift: []
28632
28765
  }));
28633
28766
  const sched = checkSchedules(probe);
28634
- if (sched) checks.push(sched);
28767
+ if (sched) emitNow(sched);
28635
28768
  }
28636
- if (!opts.fast && !opts.banner && !opts.preflight && deps.redactorLiveness) {
28769
+ async function runRedactorRow() {
28637
28770
  const redactor = checkRedactorLiveness(deps.redactorLiveness());
28638
- if (redactor) checks.push(redactor);
28771
+ if (redactor) emitNow(redactor);
28639
28772
  }
28640
- if (!opts.fast && !opts.banner && !opts.preflight && deps.docsAudit) {
28773
+ async function runDocsAuditRow() {
28641
28774
  const probe = await deps.docsAudit().catch((e) => ({
28642
28775
  armed: true,
28643
28776
  ok: false,
28644
28777
  detail: `docs-audit probe failed \u2014 ${e.message}`
28645
28778
  }));
28646
28779
  const docsAudit2 = checkDocsAudit(probe);
28647
- if (docsAudit2) checks.push(docsAudit2);
28780
+ if (docsAudit2) emitNow(docsAudit2);
28648
28781
  }
28649
- if (!opts.fast && !opts.banner && !opts.preflight && deps.worktreeRoots) {
28782
+ async function runWorktreeRootsRow() {
28650
28783
  const probe = await deps.worktreeRoots().catch(() => void 0);
28651
28784
  const roots = checkWorktreeRoots(probe);
28652
- if (roots) checks.push(roots);
28785
+ if (roots) emitNow(roots);
28653
28786
  }
28654
- if (isOrgRepo && !opts.fast && !opts.banner && !opts.preflight) {
28787
+ async function runTrainSyncRow() {
28655
28788
  try {
28656
- checks.push(checkTrainSync(await deps.syncTrain()));
28789
+ emitNow(checkTrainSync(await deps.syncTrain()));
28657
28790
  } catch (e) {
28658
28791
  const message = e instanceof Error ? e.message : String(e);
28659
- checks.push({
28792
+ emitNow({
28660
28793
  ok: false,
28661
28794
  id: "train-branches",
28662
28795
  label: "train branches",
@@ -28664,6 +28797,8 @@ async function runDoctorClean(opts, io, deps) {
28664
28797
  verbose: [`sync threw: ${message}`, "no train branch was fast-forwarded this run"]
28665
28798
  });
28666
28799
  }
28800
+ }
28801
+ async function runHousekeeperRows() {
28667
28802
  const repoRoot2 = await deps.repoRoot();
28668
28803
  try {
28669
28804
  const plan = await deps.gcPlan("origin", 200);
@@ -28674,7 +28809,7 @@ async function runDoctorClean(opts, io, deps) {
28674
28809
  if (r.removedBranches.length || r.removedRemoteBranches.length) detailParts.push(`${r.removedBranches.length + r.removedRemoteBranches.length} merged branches`);
28675
28810
  if (r.removedWorktreeDirs.length) detailParts.push(`${r.removedWorktreeDirs.length} dead worktrees`);
28676
28811
  if (r.removedTrackingRefs.length) detailParts.push(`${r.removedTrackingRefs.length} stale refs`);
28677
- checks.push({
28812
+ emitNow({
28678
28813
  id: "branches-worktrees",
28679
28814
  ok: true,
28680
28815
  label: "branches / worktrees",
@@ -28697,7 +28832,7 @@ async function runDoctorClean(opts, io, deps) {
28697
28832
  ...plan.trackingRefs.map((r) => `stale tracking ref: ${r.ref}`),
28698
28833
  ...plan.worktreeDirs.map((w) => `dead worktree: ${w.path} (${w.reason})`)
28699
28834
  ];
28700
- checks.push(n === 0 ? { id: "branches-worktrees", ok: true, label: "branches / worktrees", verbose: ["nothing reapable"] } : {
28835
+ emitNow(n === 0 ? { id: "branches-worktrees", ok: true, label: "branches / worktrees", verbose: ["nothing reapable"] } : {
28701
28836
  id: "branches-worktrees",
28702
28837
  ok: false,
28703
28838
  label: "branches / worktrees",
@@ -28715,12 +28850,35 @@ async function runDoctorClean(opts, io, deps) {
28715
28850
  if (applyRepo && scratch.plan.safeAuto.length > 0) {
28716
28851
  const applied = deps.executeScratchGc(repoRoot2, { apply: true });
28717
28852
  const pruned = applied.applied?.pruned.length ?? 0;
28718
- checks.push({ id: "scratch", ok: true, label: "scratch", detail: pruned ? `removed ${pruned} aged item(s)` : "nothing stale", verbose: scratchEvidence });
28853
+ emitNow({ id: "scratch", ok: true, label: "scratch", detail: pruned ? `removed ${pruned} aged item(s)` : "nothing stale", verbose: scratchEvidence });
28719
28854
  if (pruned) restartPending = true;
28720
28855
  } else {
28721
- checks.push(scratch.plan.safeAuto.length === 0 ? { id: "scratch", ok: true, label: "scratch", verbose: scratchEvidence } : { id: "scratch", ok: false, label: "scratch", detail: `${scratch.plan.safeAuto.length} aged item(s)`, fix: "run `mmi-cli doctor --apply`", verbose: scratchEvidence });
28722
- }
28723
- }
28856
+ emitNow(scratch.plan.safeAuto.length === 0 ? { id: "scratch", ok: true, label: "scratch", verbose: scratchEvidence } : { id: "scratch", ok: false, label: "scratch", detail: `${scratch.plan.safeAuto.length} aged item(s)`, fix: "run `mmi-cli doctor --apply`", verbose: scratchEvidence });
28857
+ }
28858
+ }
28859
+ const table = [
28860
+ // Heals first: the slowest work starts at second zero, and a stale mmi-cli is the binary every row
28861
+ // below is being measured with (#3954).
28862
+ { id: "plugin", when: true, run: runPluginRow },
28863
+ { id: "cli-version", when: true, run: runCliRow },
28864
+ { id: "github-auth", when: true, run: runGithubAuthRow },
28865
+ { id: "github-pools", when: lane.full, run: runGithubPoolsRows },
28866
+ { id: "aws-identity", when: true, run: runAwsRow },
28867
+ { id: "repo-worktrees", when: true, run: runRepoWorktreesRow },
28868
+ { id: "gitignore-block", when: isOrgRepo, run: runGitignoreRow },
28869
+ { id: "plugin-cache", when: true, run: runPluginCacheRow },
28870
+ { id: "sessionstart-payload", when: true, run: runSessionPayloadRow },
28871
+ { id: "marketplace", when: true, run: runMarketplaceRows },
28872
+ { id: "schedules", when: lane.full, run: runSchedulesRow },
28873
+ { id: "redactor-liveness", when: lane.full, run: runRedactorRow },
28874
+ { id: "docs-audit", when: lane.full, run: runDocsAuditRow },
28875
+ { id: "worktree-roots", when: lane.full, run: runWorktreeRootsRow },
28876
+ // The two most expensive things in this file — a real `git fetch` plus train-branch fast-forward,
28877
+ // and a `gh`-backed gc sweep with a 20s timeout — so org repos on the full lane only (#3485).
28878
+ { id: "train-branches", when: isOrgRepo && lane.full, run: runTrainSyncRow },
28879
+ { id: "housekeeper", when: isOrgRepo && lane.full, run: runHousekeeperRows }
28880
+ ];
28881
+ for (const entry of table) if (entry.when) await entry.run();
28724
28882
  const exitCode = doctorReportExitCode(checks);
28725
28883
  if (opts.json) {
28726
28884
  const payload = opts.verbose ? checks : checks.map(({ verbose: _evidence, ...check }) => check);
@@ -28733,7 +28891,7 @@ async function runDoctorClean(opts, io, deps) {
28733
28891
  return exitCode;
28734
28892
  }
28735
28893
  if (opts.banner) {
28736
- const actionable = checks.filter((c) => !c.ok || c.warn);
28894
+ const actionable = checks.filter((c) => (!c.ok || c.warn) && !streamed.has(c));
28737
28895
  for (const c of actionable) {
28738
28896
  io.log(renderReport([c], { restartPending: false }));
28739
28897
  if (opts.verbose) for (const evidence of c.verbose ?? []) io.log(`${VERBOSE_INDENT}${evidence}`);
@@ -28741,10 +28899,16 @@ async function runDoctorClean(opts, io, deps) {
28741
28899
  if (restartPending) io.log(`\u21BB ${restartAction.charAt(0).toUpperCase()}${restartAction.slice(1)} to finish.`);
28742
28900
  return 0;
28743
28901
  }
28744
- const legacyRestart = restartAction === "restart Claude";
28745
- const rendered = renderDoctorText(checks, { verbose: Boolean(opts.verbose), restartPending: restartPending && legacyRestart });
28746
- io.log(restartPending && !legacyRestart ? `${rendered}
28747
- \u21BB ${restartAction.charAt(0).toUpperCase()}${restartAction.slice(1)} to finish.` : rendered);
28902
+ const rest = checks.filter((c) => !streamed.has(c));
28903
+ const shown = opts.verbose ? rest : rest.filter((c) => !c.ok || c.warn);
28904
+ const lines = [];
28905
+ for (const check of shown) {
28906
+ lines.push(renderCheckLine(check));
28907
+ if (opts.verbose) for (const evidence of check.verbose ?? []) lines.push(`${VERBOSE_INDENT}${evidence}`);
28908
+ }
28909
+ lines.push(renderTally(checks));
28910
+ if (restartPending) lines.push(`\u21BB ${restartAction.charAt(0).toUpperCase()}${restartAction.slice(1)} to finish.`);
28911
+ io.log(lines.join("\n"));
28748
28912
  return exitCode;
28749
28913
  }
28750
28914
 
@@ -28767,17 +28931,17 @@ function parseOriginRepo(remoteUrl) {
28767
28931
  }
28768
28932
  function ghHostsConfigPath(env, platform2) {
28769
28933
  const sep2 = platform2 === "win32" ? "\\" : "/";
28770
- const join28 = (...parts) => parts.join(sep2);
28934
+ const join29 = (...parts) => parts.join(sep2);
28771
28935
  const explicit = env.GH_CONFIG_DIR?.trim();
28772
- if (explicit) return join28(explicit, "hosts.yml");
28936
+ if (explicit) return join29(explicit, "hosts.yml");
28773
28937
  if (platform2 === "win32") {
28774
28938
  const appData = (env.AppData ?? env.APPDATA)?.trim();
28775
- return appData ? join28(appData, "GitHub CLI", "hosts.yml") : void 0;
28939
+ return appData ? join29(appData, "GitHub CLI", "hosts.yml") : void 0;
28776
28940
  }
28777
28941
  const xdg = env.XDG_CONFIG_HOME?.trim();
28778
- if (xdg) return join28(xdg, "gh", "hosts.yml");
28942
+ if (xdg) return join29(xdg, "gh", "hosts.yml");
28779
28943
  const home = env.HOME?.trim();
28780
- return home ? join28(home, ".config", "gh", "hosts.yml") : void 0;
28944
+ return home ? join29(home, ".config", "gh", "hosts.yml") : void 0;
28781
28945
  }
28782
28946
  function parseGhHostsAccounts(yaml, host = "github.com") {
28783
28947
  let hostIndent = null;
@@ -28827,17 +28991,17 @@ function ghAccountCaveat(announcedLogin, accounts) {
28827
28991
  }
28828
28992
 
28829
28993
  // src/doctor-io.ts
28830
- var import_node_fs33 = require("node:fs");
28994
+ var import_node_fs34 = require("node:fs");
28831
28995
  var import_node_os12 = require("node:os");
28832
- var import_node_path32 = require("node:path");
28833
- var import_node_child_process14 = require("node:child_process");
28996
+ var import_node_path33 = require("node:path");
28997
+ var import_node_child_process15 = require("node:child_process");
28834
28998
  var import_node_util8 = require("node:util");
28835
- var execFileP6 = (0, import_node_util8.promisify)(import_node_child_process14.execFile);
28999
+ var execFileP6 = (0, import_node_util8.promisify)(import_node_child_process15.execFile);
28836
29000
  var MMI_PLUGIN_ID2 = "mmi@mutmutco";
28837
29001
  function installedClaudePluginVersion() {
28838
29002
  try {
28839
29003
  const file = JSON.parse(
28840
- (0, import_node_fs33.readFileSync)((0, import_node_path32.join)((0, import_node_os12.homedir)(), ".claude", "plugins", "installed_plugins.json"), "utf8")
29004
+ (0, import_node_fs34.readFileSync)((0, import_node_path33.join)((0, import_node_os12.homedir)(), ".claude", "plugins", "installed_plugins.json"), "utf8")
28841
29005
  );
28842
29006
  const versions = (file.plugins?.[MMI_PLUGIN_ID2] ?? []).map((r) => r.version).filter((v) => Boolean(v));
28843
29007
  if (versions.length === 0) return void 0;
@@ -28848,7 +29012,7 @@ function installedClaudePluginVersion() {
28848
29012
  }
28849
29013
  function manifestVersion(path2) {
28850
29014
  try {
28851
- const manifest = JSON.parse((0, import_node_fs33.readFileSync)(path2, "utf8"));
29015
+ const manifest = JSON.parse((0, import_node_fs34.readFileSync)(path2, "utf8"));
28852
29016
  return typeof manifest.version === "string" && manifest.version.trim() ? manifest.version.trim() : void 0;
28853
29017
  } catch {
28854
29018
  return void 0;
@@ -28858,27 +29022,27 @@ function installedSurfacePluginVersion(surface) {
28858
29022
  const token = surfaceToken(surface);
28859
29023
  if (token === "kilo") {
28860
29024
  try {
28861
- const stamp = (0, import_node_fs33.readFileSync)((0, import_node_path32.join)((0, import_node_os12.homedir)(), ".kilo", ".mmi-kilo-version"), "utf8").trim();
29025
+ const stamp = (0, import_node_fs34.readFileSync)((0, import_node_path33.join)((0, import_node_os12.homedir)(), ".kilo", ".mmi-kilo-version"), "utf8").trim();
28862
29026
  return stamp || void 0;
28863
29027
  } catch {
28864
29028
  return void 0;
28865
29029
  }
28866
29030
  }
28867
29031
  if (token === "cursor") {
28868
- return manifestVersion((0, import_node_path32.join)(cursorLocalPluginRoot(), ".cursor-plugin", "plugin.json"));
29032
+ return manifestVersion((0, import_node_path33.join)(cursorLocalPluginRoot(), ".cursor-plugin", "plugin.json"));
28869
29033
  }
28870
29034
  if (token === "kimi") {
28871
- return manifestVersion((0, import_node_path32.join)(surfaceConfigRoot(surface), "plugins", "managed", "mmi", ".kimi-plugin", "plugin.json"));
29035
+ return manifestVersion((0, import_node_path33.join)(surfaceConfigRoot(surface), "plugins", "managed", "mmi", ".kimi-plugin", "plugin.json"));
28872
29036
  }
28873
29037
  if (token === "claude") return installedClaudePluginVersion();
28874
29038
  if (token !== "codex") return void 0;
28875
29039
  try {
28876
- const raw = process.platform === "win32" ? (0, import_node_child_process14.execFileSync)("cmd.exe", ["/c", "codex", "plugin", "list", "--json"], {
29040
+ const raw = process.platform === "win32" ? (0, import_node_child_process15.execFileSync)("cmd.exe", ["/c", "codex", "plugin", "list", "--json"], {
28877
29041
  encoding: "utf8",
28878
29042
  stdio: ["ignore", "pipe", "ignore"],
28879
29043
  timeout: 15e3,
28880
29044
  windowsHide: true
28881
- }) : (0, import_node_child_process14.execFileSync)("codex", ["plugin", "list", "--json"], {
29045
+ }) : (0, import_node_child_process15.execFileSync)("codex", ["plugin", "list", "--json"], {
28882
29046
  encoding: "utf8",
28883
29047
  stdio: ["ignore", "pipe", "ignore"],
28884
29048
  timeout: 15e3,
@@ -28896,7 +29060,7 @@ function installedActivePluginVersion(surface = detectSurface(process.env)) {
28896
29060
  }
28897
29061
  function worktreeRootSync() {
28898
29062
  try {
28899
- const out = (0, import_node_child_process14.execFileSync)("git", ["rev-parse", "--show-toplevel"], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
29063
+ const out = (0, import_node_child_process15.execFileSync)("git", ["rev-parse", "--show-toplevel"], { windowsHide: true, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
28900
29064
  let root = out.endsWith("\n") ? out.slice(0, -1) : out;
28901
29065
  if (process.platform === "win32" && root.endsWith("\r")) root = root.slice(0, -1);
28902
29066
  return root || null;
@@ -28906,13 +29070,13 @@ function worktreeRootSync() {
28906
29070
  }
28907
29071
  var gitignorePath = () => {
28908
29072
  const root = worktreeRootSync();
28909
- return root === null ? null : (0, import_node_path32.join)(root, ".gitignore");
29073
+ return root === null ? null : (0, import_node_path33.join)(root, ".gitignore");
28910
29074
  };
28911
29075
  function readGitignore() {
28912
29076
  const path2 = gitignorePath();
28913
29077
  if (path2 === null) return null;
28914
29078
  try {
28915
- return (0, import_node_fs33.readFileSync)(path2, "utf8");
29079
+ return (0, import_node_fs34.readFileSync)(path2, "utf8");
28916
29080
  } catch {
28917
29081
  return null;
28918
29082
  }
@@ -28921,7 +29085,7 @@ function writeGitignore(content) {
28921
29085
  const path2 = gitignorePath();
28922
29086
  if (path2 === null) return false;
28923
29087
  try {
28924
- (0, import_node_fs33.writeFileSync)(path2, content, "utf8");
29088
+ (0, import_node_fs34.writeFileSync)(path2, content, "utf8");
28925
29089
  return true;
28926
29090
  } catch {
28927
29091
  return false;
@@ -28945,7 +29109,7 @@ async function repoRoot() {
28945
29109
  }
28946
29110
  function hasRepoLocalWorktrees() {
28947
29111
  const root = worktreeRootSync();
28948
- return root !== null && (0, import_node_fs33.existsSync)((0, import_node_path32.join)(root, ".worktrees"));
29112
+ return root !== null && (0, import_node_fs34.existsSync)((0, import_node_path33.join)(root, ".worktrees"));
28949
29113
  }
28950
29114
 
28951
29115
  // src/index.ts
@@ -28981,8 +29145,8 @@ ${r.stderr ?? ""}`).catch(() => "");
28981
29145
  function ghMultiAccountCaveat(announcedLogin) {
28982
29146
  try {
28983
29147
  const hostsPath = ghHostsConfigPath(process.env, process.platform);
28984
- if (!hostsPath || !(0, import_node_fs34.existsSync)(hostsPath)) return void 0;
28985
- return ghAccountCaveat(announcedLogin, parseGhHostsAccounts((0, import_node_fs34.readFileSync)(hostsPath, "utf8")));
29148
+ if (!hostsPath || !(0, import_node_fs35.existsSync)(hostsPath)) return void 0;
29149
+ return ghAccountCaveat(announcedLogin, parseGhHostsAccounts((0, import_node_fs35.readFileSync)(hostsPath, "utf8")));
28986
29150
  } catch {
28987
29151
  return void 0;
28988
29152
  }
@@ -28990,7 +29154,7 @@ function ghMultiAccountCaveat(announcedLogin) {
28990
29154
  var ENV_HEAL_LOCK_STALE_MS = 10 * 6e4;
28991
29155
  var ENV_HEAL_LOCK_MAX_WAIT_MS = 2 * 6e4;
28992
29156
  function envHealLockPath(home) {
28993
- return (0, import_node_path33.join)(home, ".claude", "plugins", ".mmi-env-heal.lock");
29157
+ return (0, import_node_path34.join)(home, ".claude", "plugins", ".mmi-env-heal.lock");
28994
29158
  }
28995
29159
  async function withEnvHealLock(what, run) {
28996
29160
  try {
@@ -29065,10 +29229,7 @@ function mmiDoctorDeps(opts = {}) {
29065
29229
  githubRepoReach: githubRepoReachProbe,
29066
29230
  awsCallerArn,
29067
29231
  isOrgRepo: () => isOrgRepoRoot(),
29068
- installedPluginVersion: installedActivePluginVersion,
29069
29232
  surfaceEvidence: surfaceEvidenceOnce,
29070
- pluginGuardState: activePluginGuardState,
29071
- pluginSurface: () => detectSurface(process.env),
29072
29233
  pluginTrustState: () => codexHookTrustState(),
29073
29234
  releasedVersion: throttled ? throttled.read : fetchNpmReleasedVersion,
29074
29235
  releasedVersionNote: throttled ? throttled.note : void 0,
@@ -29077,13 +29238,13 @@ function mmiDoctorDeps(opts = {}) {
29077
29238
  // marketplace clone + plugin cache) and neither is idempotent under concurrency. They share ONE lock
29078
29239
  // rather than one each, because they are not independent: the plugin heal reinstalls a bundle that
29079
29240
  // carries a CLI shim, so a concurrent npm global install races the same PATH surface.
29080
- updateCli: (target) => withEnvHealLock("npm global CLI self-update", () => npmSelfUpdateCli(target)),
29241
+ updateCli: (target, onStep) => withEnvHealLock("npm global CLI self-update", () => npmSelfUpdateCli(target, onStep)),
29081
29242
  // #3282: the --apply self-heal for a stale/unresolved Claude plugin — the same marketplace reinstall
29082
29243
  // `mmi-cli plugin heal` drives. Takes effect on the next Claude reload, so the row asks for a restart.
29083
- healPlugin: () => {
29244
+ healPlugin: (onStep) => {
29084
29245
  const surface = detectSurface(process.env);
29085
- const host = surface === "codex" ? "Codex" : surface === "kilo" ? "Kilo" : surface === "cursor" ? "Cursor" : "Claude";
29086
- return withEnvHealLock(`${host} plugin reinstall`, () => healActivePluginForDoctor(surface));
29246
+ const host = surfaceEvidenceOnce(true)?.descriptor.displayName ?? surface;
29247
+ return withEnvHealLock(`${host} plugin reinstall`, () => healActivePluginForDoctor(surface, onStep));
29087
29248
  },
29088
29249
  currentCliVersion: resolveClientVersion,
29089
29250
  readGitignore,
@@ -29179,12 +29340,25 @@ function mmiDoctorDeps(opts = {}) {
29179
29340
  const home = (0, import_node_os13.homedir)();
29180
29341
  return marketplaceRows(
29181
29342
  MMI_MARKETPLACE_NAME,
29182
- readFileSyncSafe((0, import_node_path33.join)(home, ...KNOWN_MARKETPLACES_RELATIVE), import_node_fs34.readFileSync),
29183
- readFileSyncSafe((0, import_node_path33.join)(home, ".claude", "settings.json"), import_node_fs34.readFileSync)
29343
+ readFileSyncSafe((0, import_node_path34.join)(home, ...KNOWN_MARKETPLACES_RELATIVE), import_node_fs35.readFileSync),
29344
+ readFileSyncSafe((0, import_node_path34.join)(home, ".claude", "settings.json"), import_node_fs35.readFileSync),
29345
+ // #3974: this CLI heals these rows, so report mode names `doctor --apply` rather than the hand
29346
+ // edit. Unconditional — it is a fact about mmi-cli, not about whether --apply was passed.
29347
+ true
29184
29348
  );
29185
29349
  } catch {
29186
29350
  return [];
29187
29351
  }
29352
+ },
29353
+ // #3974: the heal behind those rows, under --apply only. Same Codex carve-out as the rows above —
29354
+ // Codex keeps its registration in config.toml, so there is no ~/.claude registration to pin.
29355
+ healMarketplacePins: () => {
29356
+ try {
29357
+ if (detectSurface(process.env) === "codex") return void 0;
29358
+ return applyOrgMarketplacePins((0, import_node_path34.join)((0, import_node_os13.homedir)(), ...KNOWN_MARKETPLACES_RELATIVE), [MMI_MARKETPLACE_NAME]);
29359
+ } catch {
29360
+ return void 0;
29361
+ }
29188
29362
  }
29189
29363
  };
29190
29364
  }
@@ -29386,19 +29560,19 @@ program2.hook("preAction", async (_thisCommand, actionCommand) => {
29386
29560
  });
29387
29561
  var rules = program2.command("rules").description("org-managed .gitignore delivery");
29388
29562
  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) => {
29389
- const path2 = (0, import_node_path33.join)(process.cwd(), ".gitignore");
29390
- const current = (0, import_node_fs34.existsSync)(path2) ? (0, import_node_fs34.readFileSync)(path2, "utf8") : null;
29563
+ const path2 = (0, import_node_path34.join)(process.cwd(), ".gitignore");
29564
+ const current = (0, import_node_fs35.existsSync)(path2) ? (0, import_node_fs35.readFileSync)(path2, "utf8") : null;
29391
29565
  const plan = planManagedGitignore(current);
29392
29566
  const drift = [...plan.added.map((l) => `+${l}`), ...plan.removed.map((l) => `-${l}`)].join(", ") || "block normalize";
29393
29567
  if (opts.json) {
29394
- if (opts.write && plan.changed) (0, import_node_fs34.writeFileSync)(path2, plan.content, "utf8");
29568
+ if (opts.write && plan.changed) (0, import_node_fs35.writeFileSync)(path2, plan.content, "utf8");
29395
29569
  console.log(JSON.stringify(plan, null, 2));
29396
29570
  if (!opts.write && plan.changed) process.exitCode = 1;
29397
29571
  return;
29398
29572
  }
29399
29573
  if (opts.write) {
29400
29574
  if (plan.changed) {
29401
- (0, import_node_fs34.writeFileSync)(path2, plan.content, "utf8");
29575
+ (0, import_node_fs35.writeFileSync)(path2, plan.content, "utf8");
29402
29576
  console.log(`mmi-cli org rules gitignore: updated .gitignore (${drift})`);
29403
29577
  } else {
29404
29578
  console.log("mmi-cli org rules gitignore: up to date");
@@ -29553,8 +29727,8 @@ gcCmd.option("--dry-run", "show what would be deleted (default)").option("--appl
29553
29727
  if (!Number.isFinite(limit) || limit < 1) return fail("worktree gc: --limit must be a positive integer");
29554
29728
  let root;
29555
29729
  if (o.root !== void 0) {
29556
- root = (0, import_node_path33.resolve)(o.root);
29557
- if (!(0, import_node_fs34.existsSync)(root) || !(0, import_node_fs34.statSync)(root).isDirectory()) return fail(`worktree gc: --root ${o.root} is not a directory`);
29730
+ root = (0, import_node_path34.resolve)(o.root);
29731
+ if (!(0, import_node_fs35.existsSync)(root) || !(0, import_node_fs35.statSync)(root).isDirectory()) return fail(`worktree gc: --root ${o.root} is not a directory`);
29558
29732
  const gcRepoRoot = (await execFileP2("git", ["rev-parse", "--show-toplevel"], { timeout: GIT_TIMEOUT_MS }).catch(() => ({ stdout: "" }))).stdout.trim() || process.cwd();
29559
29733
  if (isPathUnderDirectory(gcRepoRoot, root)) {
29560
29734
  return fail(`worktree gc: --root ${root} contains this checkout \u2014 name a worktrees root, not the repo or an ancestor of it`);
@@ -29596,7 +29770,7 @@ function runWorktreeInstall(command, cwd, quiet) {
29596
29770
  const file = isWin2 ? "cmd.exe" : bin;
29597
29771
  const spawnArgs = isWin2 ? ["/c", bin, ...args] : args;
29598
29772
  return new Promise((resolve5, reject) => {
29599
- const child2 = (0, import_node_child_process15.spawn)(file, spawnArgs, { cwd, stdio: quiet ? "ignore" : "inherit", windowsHide: true });
29773
+ const child2 = (0, import_node_child_process16.spawn)(file, spawnArgs, { cwd, stdio: quiet ? "ignore" : "inherit", windowsHide: true });
29600
29774
  const timer = setTimeout(() => {
29601
29775
  try {
29602
29776
  child2.kill();
@@ -29619,7 +29793,7 @@ async function primaryCheckoutRoot(from) {
29619
29793
  return primaryCheckoutRootOf(async (args) => (await execFileP2("git", ["-C", from, ...args], { timeout: GIT_TIMEOUT_MS })).stdout);
29620
29794
  }
29621
29795
  async function unprovenWorktreeReason(wtPath, repoRoot2) {
29622
- if (!(0, import_node_fs34.existsSync)(wtPath)) return `${wtPath} does not exist on disk`;
29796
+ if (!(0, import_node_fs35.existsSync)(wtPath)) return `${wtPath} does not exist on disk`;
29623
29797
  const porcelain = (await execFileP2("git", ["-C", repoRoot2, "worktree", "list", "--porcelain"], { timeout: GIT_TIMEOUT_MS }).catch(() => ({ stdout: "" }))).stdout;
29624
29798
  const registered = parseWorktreePorcelainEntries(porcelain);
29625
29799
  if (!registered.length) {
@@ -29640,26 +29814,26 @@ function makeProvisionDeps(worktreeRoot, quiet, log) {
29640
29814
  function acquireWorktreeSetupLock(worktreeRoot) {
29641
29815
  const lockPath = repoRuntimeStatePath(worktreeRoot, "worktree-setup.lock");
29642
29816
  const take = () => {
29643
- const fd = (0, import_node_fs34.openSync)(lockPath, "wx");
29817
+ const fd = (0, import_node_fs35.openSync)(lockPath, "wx");
29644
29818
  try {
29645
- (0, import_node_fs34.writeSync)(fd, String(Date.now()));
29819
+ (0, import_node_fs35.writeSync)(fd, String(Date.now()));
29646
29820
  } finally {
29647
- (0, import_node_fs34.closeSync)(fd);
29821
+ (0, import_node_fs35.closeSync)(fd);
29648
29822
  }
29649
29823
  return () => {
29650
29824
  try {
29651
- (0, import_node_fs34.rmSync)(lockPath, { force: true });
29825
+ (0, import_node_fs35.rmSync)(lockPath, { force: true });
29652
29826
  } catch {
29653
29827
  }
29654
29828
  };
29655
29829
  };
29656
29830
  try {
29657
- (0, import_node_fs34.mkdirSync)((0, import_node_path33.dirname)(lockPath), { recursive: true });
29831
+ (0, import_node_fs35.mkdirSync)((0, import_node_path34.dirname)(lockPath), { recursive: true });
29658
29832
  return take();
29659
29833
  } catch {
29660
29834
  try {
29661
- if (Date.now() - (0, import_node_fs34.statSync)(lockPath).mtimeMs > WORKTREE_SETUP_LOCK_TTL_MS) {
29662
- (0, import_node_fs34.rmSync)(lockPath, { force: true });
29835
+ if (Date.now() - (0, import_node_fs35.statSync)(lockPath).mtimeMs > WORKTREE_SETUP_LOCK_TTL_MS) {
29836
+ (0, import_node_fs35.rmSync)(lockPath, { force: true });
29663
29837
  return take();
29664
29838
  }
29665
29839
  } catch {
@@ -29882,7 +30056,7 @@ function scheduleRelatedDiscovery(o) {
29882
30056
  try {
29883
30057
  const args = ["issue", "discover-related", "--number", String(o.number), "--title", o.title, "--body", o.body, "--fail-soft"];
29884
30058
  if (o.repo) args.push("--repo", o.repo);
29885
- spawnDetachedSelf(args, { spawn: import_node_child_process15.spawn, execPath: process.execPath, scriptPath: process.argv[1] }, { cwd: process.cwd() });
30059
+ spawnDetachedSelf(args, { spawn: import_node_child_process16.spawn, execPath: process.execPath, scriptPath: process.argv[1] }, { cwd: process.cwd() });
29886
30060
  } catch {
29887
30061
  }
29888
30062
  }
@@ -29932,6 +30106,29 @@ docs.command("refs").description("deterministic doc reference gate: every backti
29932
30106
  await failGraceful(e.message);
29933
30107
  }
29934
30108
  });
30109
+ var spawnCmd = program2.command("spawn").description("this repo's process-spawn contract \u2014 every child process must be unable to pop a console window");
30110
+ spawnCmd.command("policy").description("enforce the windowsHide contract across this repo's tracked source: a child_process call must set windowsHide, or take its options from a named constant, a type annotation, or a forwarded caller bag that does. Waive a call the scan cannot classify with a `// windows-hide-exempt: <reason>` comment on the line above it (#3979)").option("--json", "machine-readable result: { ok, scannedCount, findings[] }").action(async (o) => {
30111
+ try {
30112
+ const root = await repoRoot();
30113
+ const result = runSpawnPolicy(root);
30114
+ if (o.json) {
30115
+ consoleIo.log(JSON.stringify(result, null, 2));
30116
+ if (!result.ok) process.exitCode = 1;
30117
+ return;
30118
+ }
30119
+ if (result.ok) {
30120
+ console.log(`spawn policy: OK (${result.scannedCount} source file(s) scanned).`);
30121
+ return;
30122
+ }
30123
+ for (const f of result.findings) console.error(`spawn policy: ${f.detail}`);
30124
+ console.error(
30125
+ "\nA process spawned without windowsHide allocates a console when it has none to inherit.\nWhere Windows Terminal is the default terminal, that console becomes a desktop window the\nhost never reaps \u2014 it outlives the process as an empty frame. Set windowsHide: true, or\nroute the call through a helper that does."
30126
+ );
30127
+ process.exitCode = 1;
30128
+ } catch (e) {
30129
+ await failGraceful(e.message);
30130
+ }
30131
+ });
29935
30132
  var tests = program2.command("tests").description("a repo's test-policy.json \u2014 the opt-in test contract and its enforcement");
29936
30133
  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) => {
29937
30134
  try {
@@ -30238,7 +30435,7 @@ project.command("set [owner/repo]").description("upsert project META (idempotent
30238
30435
  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`);
30239
30436
  if (o.secretsFile) {
30240
30437
  try {
30241
- vars.push(`secrets=${(0, import_node_fs34.readFileSync)(o.secretsFile, "utf8")}`);
30438
+ vars.push(`secrets=${(0, import_node_fs35.readFileSync)(o.secretsFile, "utf8")}`);
30242
30439
  } catch (e) {
30243
30440
  return fail(`org project set: cannot read --secrets-file ${o.secretsFile}: ${e.message}`);
30244
30441
  }
@@ -30986,11 +31183,11 @@ pr.command("view <number>").description("read a PR as structured JSON (merged st
30986
31183
  }
30987
31184
  });
30988
31185
  async function listCiWorkflowPaths(cwd = process.cwd()) {
30989
- const wfDir = (0, import_node_path33.join)(cwd, ".github", "workflows");
30990
- if (!(0, import_node_fs34.existsSync)(wfDir)) return [];
30991
- return (0, import_node_fs34.readdirSync)(wfDir).filter((name) => /\.(ya?ml)$/i.test(name)).filter((name) => {
31186
+ const wfDir = (0, import_node_path34.join)(cwd, ".github", "workflows");
31187
+ if (!(0, import_node_fs35.existsSync)(wfDir)) return [];
31188
+ return (0, import_node_fs35.readdirSync)(wfDir).filter((name) => /\.(ya?ml)$/i.test(name)).filter((name) => {
30992
31189
  try {
30993
- return workflowReportsPrChecks((0, import_node_fs34.readFileSync)((0, import_node_path33.join)(wfDir, name), "utf8"));
31190
+ return workflowReportsPrChecks((0, import_node_fs35.readFileSync)((0, import_node_path34.join)(wfDir, name), "utf8"));
30994
31191
  } catch {
30995
31192
  return true;
30996
31193
  }
@@ -31022,16 +31219,16 @@ function ciAuditDeps() {
31022
31219
  // gate re-seed step is skipped gracefully rather than failing mid-run.
31023
31220
  readSeedFile: (path2) => {
31024
31221
  if (!root) return null;
31025
- const fullPath = (0, import_node_path33.join)(root, path2);
31026
- return (0, import_node_fs34.existsSync)(fullPath) ? (0, import_node_fs34.readFileSync)(fullPath, "utf8") : null;
31222
+ const fullPath = (0, import_node_path34.join)(root, path2);
31223
+ return (0, import_node_fs35.existsSync)(fullPath) ? (0, import_node_fs35.readFileSync)(fullPath, "utf8") : null;
31027
31224
  }
31028
31225
  };
31029
31226
  }
31030
31227
  function hubRoot() {
31031
- const fromPkg = (0, import_node_path33.join)(__dirname, "..", "..");
31228
+ const fromPkg = (0, import_node_path34.join)(__dirname, "..", "..");
31032
31229
  const marker = "skills/bootstrap/seeds/manifest.json";
31033
- if ((0, import_node_fs34.existsSync)((0, import_node_path33.join)(fromPkg, marker))) return fromPkg;
31034
- if ((0, import_node_fs34.existsSync)((0, import_node_path33.join)(process.cwd(), marker))) return process.cwd();
31230
+ if ((0, import_node_fs35.existsSync)((0, import_node_path34.join)(fromPkg, marker))) return fromPkg;
31231
+ if ((0, import_node_fs35.existsSync)((0, import_node_path34.join)(process.cwd(), marker))) return process.cwd();
31035
31232
  return null;
31036
31233
  }
31037
31234
  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) => {
@@ -31334,7 +31531,7 @@ jsonParity(pr.command("merge <number>").description("merge a PR (squash by defau
31334
31531
  localCleanup = await cleanupPrMergeLocalBranch(headRef, {
31335
31532
  beforeWorktrees,
31336
31533
  startingPath,
31337
- pathExists: (p) => (0, import_node_fs34.existsSync)(p),
31534
+ pathExists: (p) => (0, import_node_fs35.existsSync)(p),
31338
31535
  execGit: async (args) => (await execFileP2("git", args, { timeout: GIT_TIMEOUT_MS })).stdout,
31339
31536
  teardownWorktreeStage,
31340
31537
  deferredStore,
@@ -31485,6 +31682,9 @@ function renderReleaseResume(r) {
31485
31682
  function renderReleaseAbort(r) {
31486
31683
  return `mmi-cli release --abort --apply: ${r.repo} ${r.tag} (${r.tagSha.slice(0, 12)}) \u2014 ${r.note}`;
31487
31684
  }
31685
+ function renderReleasePublishRetry(r) {
31686
+ return `mmi-cli release --retry-publish: ${r.repo} ${r.tag} (${r.tagSha.slice(0, 12)}) \u2014 ${r.note}; ${r.runUrl}`;
31687
+ }
31488
31688
  function renderRcandResume(r) {
31489
31689
  return `mmi-cli rcand --resume: promoted ${r.repo} \u2192 rc at ${r.tag} (${r.tagSha.slice(0, 12)}) [${r.deployModel}]; ${renderDeployLine(r)}; ${r.note}`;
31490
31690
  }
@@ -31532,7 +31732,8 @@ for (const commandName of ["rcand", "release"]) {
31532
31732
  { flags: "--announce-summary-file <path>", description: "agent-curated 3-6 line Hub Slack summary; required for a new MMI-Hub --apply (#883/#3901)" },
31533
31733
  { flags: "--ack <shas>", description: "comma-separated dev shas a human verified are in the candidate, overriding the hotfix-coverage guard for a conflicted port whose -x trailer was lost (#958)" },
31534
31734
  { flags: "--dev", description: "full-track repos release development -> main directly, skipping rc (refuses if rc carries content not in development; no-op on direct-track repos) (#1062)" },
31535
- { flags: "--abort", description: "with --apply, delete only a proven unpublished failed Hub tag and restore local main for a clean recut (#3944)" }
31735
+ { flags: "--abort", description: "with --apply, delete only a proven unpublished failed Hub tag and restore local main for a clean recut (#3944)" },
31736
+ { flags: "--retry-publish <run-id>", description: "with --apply, retry failed jobs of one proven Hub publish release run (#3949)" }
31536
31737
  ];
31537
31738
  for (const f of RELEASE_ONLY_FLAGS) {
31538
31739
  if (commandName === "release") {
@@ -31559,6 +31760,25 @@ for (const commandName of ["rcand", "release"]) {
31559
31760
  if (o.abort && commandName !== "release") {
31560
31761
  return fail(`${commandName}: --abort applies only to release \u2014 it rolls back a proven unpublished Hub release tag. Run: mmi-cli release --abort --apply`);
31561
31762
  }
31763
+ if (o.retryPublish && commandName !== "release") {
31764
+ return fail(`${commandName}: --retry-publish applies only to release. Run: mmi-cli release --retry-publish <run-id> --apply --watch`);
31765
+ }
31766
+ if (o.retryPublish) {
31767
+ if (o.resume || o.abort) return fail("release: --retry-publish cannot be combined with --resume or --abort");
31768
+ if (!o.apply) return fail("release: --retry-publish requires --apply after explicit approval; nothing was written");
31769
+ if (o.announceSummaryFile || o.ack || o.dev) return fail("release: --retry-publish accepts only --apply, --watch, --repo and --json");
31770
+ if (o.repo) {
31771
+ const guard = planTrainApplyRepoGuard(o.repo, await resolveRepo(), `mmi-cli release --retry-publish ${o.retryPublish} --apply${o.watch ? " --watch" : ""}`);
31772
+ if (!guard.ok) return fail(`release: ${guard.message}`);
31773
+ }
31774
+ const runId = Number.parseInt(o.retryPublish, 10);
31775
+ try {
31776
+ const result = await runReleasePublishRetry(trainApplyDeps(), runId, { approved: true, watch: o.watch });
31777
+ return printLine(o.json ? JSON.stringify(result, null, 2) : renderReleasePublishRetry(result));
31778
+ } catch (e) {
31779
+ return failGraceful(`release --retry-publish: ${e.message}`);
31780
+ }
31781
+ }
31562
31782
  if (o.abort) {
31563
31783
  if (o.resume) return fail("release: --abort and --resume are mutually exclusive \u2014 abort removes an unpublished tag while resume preserves and promotes it");
31564
31784
  if (!o.apply) return fail("release: --abort requires --apply after explicit approval; nothing was written");
@@ -31786,12 +32006,12 @@ access.command("audit").description("audit collaborator roles + train-branch pus
31786
32006
  targets = resolution.targets;
31787
32007
  }
31788
32008
  const derivedMatrix = registryProjects ? accessMatrixFromProjects(registryProjects) : {};
31789
- const fileMatrix = (0, import_node_fs34.existsSync)("access-matrix.json") ? loadAccessMatrix((0, import_node_fs34.readFileSync)("access-matrix.json", "utf8")) : {};
32009
+ const fileMatrix = (0, import_node_fs35.existsSync)("access-matrix.json") ? loadAccessMatrix((0, import_node_fs35.readFileSync)("access-matrix.json", "utf8")) : {};
31790
32010
  const matrix = mergeAccessMatrix(fileMatrix, derivedMatrix);
31791
32011
  const derivedContracts = registryProjects ? dataAccessContractsFromProjects(registryProjects) : { consumers: {} };
31792
- const fileContracts = (0, import_node_fs34.existsSync)("data-access-contracts.json") ? loadDataAccessContracts((0, import_node_fs34.readFileSync)("data-access-contracts.json", "utf8")) : { consumers: {} };
32012
+ const fileContracts = (0, import_node_fs35.existsSync)("data-access-contracts.json") ? loadDataAccessContracts((0, import_node_fs35.readFileSync)("data-access-contracts.json", "utf8")) : { consumers: {} };
31793
32013
  const dataAccess = mergeDataAccessContracts(fileContracts, derivedContracts);
31794
- const sanctioned = (0, import_node_fs34.existsSync)("access-matrix.json") ? loadSanctionedAdmins((0, import_node_fs34.readFileSync)("access-matrix.json", "utf8")) : {};
32014
+ const sanctioned = (0, import_node_fs35.existsSync)("access-matrix.json") ? loadSanctionedAdmins((0, import_node_fs35.readFileSync)("access-matrix.json", "utf8")) : {};
31795
32015
  const report = await auditOrgAccess(targets, deps, matrix, dataAccess, sanctioned);
31796
32016
  console.log(o.json ? JSON.stringify(report, null, 2) : renderAccessReport(report));
31797
32017
  if (!report.ok) process.exitCode = 1;
@@ -31824,16 +32044,16 @@ function directoryBytes(path2) {
31824
32044
  let total = 0;
31825
32045
  let entries;
31826
32046
  try {
31827
- entries = (0, import_node_fs34.readdirSync)(path2, { withFileTypes: true });
32047
+ entries = (0, import_node_fs35.readdirSync)(path2, { withFileTypes: true });
31828
32048
  } catch {
31829
32049
  return 0;
31830
32050
  }
31831
32051
  for (const entry of entries) {
31832
- const child2 = (0, import_node_path33.join)(path2, entry.name);
32052
+ const child2 = (0, import_node_path34.join)(path2, entry.name);
31833
32053
  if (entry.isDirectory()) total += directoryBytes(child2);
31834
32054
  else {
31835
32055
  try {
31836
- total += (0, import_node_fs34.statSync)(child2).size;
32056
+ total += (0, import_node_fs35.statSync)(child2).size;
31837
32057
  } catch {
31838
32058
  }
31839
32059
  }
@@ -31841,25 +32061,25 @@ function directoryBytes(path2) {
31841
32061
  return total;
31842
32062
  }
31843
32063
  function listDirEntries(dir) {
31844
- return (0, import_node_fs34.readdirSync)(dir, { withFileTypes: true }).map((d) => ({ name: d.name, isDirectory: d.isDirectory() }));
32064
+ return (0, import_node_fs35.readdirSync)(dir, { withFileTypes: true }).map((d) => ({ name: d.name, isDirectory: d.isDirectory() }));
31845
32065
  }
31846
32066
  function readInstalledPluginRefs(configRoot) {
31847
32067
  const p = installedPluginsPathForConfig(configRoot);
31848
- if (!(0, import_node_fs34.existsSync)(p)) return [];
32068
+ if (!(0, import_node_fs35.existsSync)(p)) return [];
31849
32069
  try {
31850
- return installedPluginPaths((0, import_node_fs34.readFileSync)(p, "utf8"));
32070
+ return installedPluginPaths((0, import_node_fs35.readFileSync)(p, "utf8"));
31851
32071
  } catch {
31852
32072
  return null;
31853
32073
  }
31854
32074
  }
31855
32075
  function pluginCacheFsDeps(configRoot, dirBytes) {
31856
32076
  return {
31857
- exists: (p) => (0, import_node_fs34.existsSync)(p),
31858
- listVersionDirs: (root) => (0, import_node_fs34.readdirSync)(root, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name),
32077
+ exists: (p) => (0, import_node_fs35.existsSync)(p),
32078
+ listVersionDirs: (root) => (0, import_node_fs35.readdirSync)(root, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name),
31859
32079
  dirBytes,
31860
- listStagingDirs: (root) => (0, import_node_fs34.readdirSync)(root, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => {
32080
+ listStagingDirs: (root) => (0, import_node_fs35.readdirSync)(root, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => {
31861
32081
  try {
31862
- return { name: d.name, mtimeMs: newestMtimeMs((0, import_node_path33.join)(root, d.name), listDirEntries, (p) => (0, import_node_fs34.statSync)(p).mtimeMs) };
32082
+ return { name: d.name, mtimeMs: newestMtimeMs((0, import_node_path34.join)(root, d.name), listDirEntries, (p) => (0, import_node_fs35.statSync)(p).mtimeMs) };
31863
32083
  } catch {
31864
32084
  return { name: d.name, mtimeMs: Date.now() };
31865
32085
  }
@@ -31873,10 +32093,10 @@ function stagingApplyFsGuard(configRoot) {
31873
32093
  return {
31874
32094
  referencedPaths: () => readInstalledPluginRefs(configRoot),
31875
32095
  mtimeMs: (name) => {
31876
- const p = (0, import_node_path33.join)(stagingRoot, name);
31877
- if (!(0, import_node_fs34.existsSync)(p)) return null;
32096
+ const p = (0, import_node_path34.join)(stagingRoot, name);
32097
+ if (!(0, import_node_fs35.existsSync)(p)) return null;
31878
32098
  try {
31879
- return newestMtimeMs(p, listDirEntries, (q) => (0, import_node_fs34.statSync)(q).mtimeMs);
32099
+ return newestMtimeMs(p, listDirEntries, (q) => (0, import_node_fs35.statSync)(q).mtimeMs);
31880
32100
  } catch {
31881
32101
  return null;
31882
32102
  }
@@ -31902,7 +32122,7 @@ program2.command("plugin-prune").description(`prune stale cached MMI plugin vers
31902
32122
  { withBytes: true, configRoot, includeStaging: surface !== "codex" }
31903
32123
  );
31904
32124
  const anythingToDelete = plan.prune.length > 0 || plan.staging.length > 0;
31905
- const result = o.apply && anythingToDelete ? applyPluginCachePlan(plan, (p) => (0, import_node_fs34.rmSync)(p, { recursive: true, force: true }), stagingApplyFsGuard(configRoot)) : void 0;
32125
+ const result = o.apply && anythingToDelete ? applyPluginCachePlan(plan, (p) => (0, import_node_fs35.rmSync)(p, { recursive: true, force: true }), stagingApplyFsGuard(configRoot)) : void 0;
31906
32126
  const warnings = plan.prune.length > 0 ? [CONCURRENT_SESSION_WARNING] : [];
31907
32127
  if (o.json) console.log(JSON.stringify({ ...plan, warnings, applied: result ?? null }));
31908
32128
  else console.log(renderPluginCachePlan(plan, result));
@@ -31965,7 +32185,7 @@ program2.command("session-start").description("run the SessionStart verbs (whoam
31965
32185
  for (const line of scratchGcLines(process.cwd())) bannerIo.log(line);
31966
32186
  const worktreeBanner = worktreeAutoProvisionBanner(process.cwd());
31967
32187
  if (worktreeBanner) {
31968
- spawnDetachedSelf(["worktree", "setup", "--quiet"], { spawn: import_node_child_process15.spawn, execPath: process.execPath, scriptPath: process.argv[1] });
32188
+ spawnDetachedSelf(["worktree", "setup", "--quiet"], { spawn: import_node_child_process16.spawn, execPath: process.execPath, scriptPath: process.argv[1] });
31969
32189
  bannerIo.log(worktreeBanner);
31970
32190
  }
31971
32191
  if (isLinkedWorktree(process.cwd())) {