@mutmutco/cli 3.97.0 → 3.98.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 +586 -257
  2. package/package.json +1 -1
package/dist/main.cjs CHANGED
@@ -3416,7 +3416,7 @@ var program = new Command();
3416
3416
 
3417
3417
  // src/index.ts
3418
3418
  var import_promises11 = require("node:fs/promises");
3419
- var import_node_fs39 = require("node:fs");
3419
+ var import_node_fs40 = require("node:fs");
3420
3420
  var import_node_child_process18 = require("node:child_process");
3421
3421
 
3422
3422
  // src/cli-shared.ts
@@ -6999,7 +6999,7 @@ function commandLadderHint() {
6999
6999
  }
7000
7000
 
7001
7001
  // src/index.ts
7002
- var import_node_path37 = require("node:path");
7002
+ var import_node_path38 = require("node:path");
7003
7003
 
7004
7004
  // src/merge-ci-policy.ts
7005
7005
  function resolveMergeCiPolicy(input) {
@@ -7643,6 +7643,9 @@ var CMD_RE = /`mmi-cli ((?:[a-z][a-z-]*)(?: [a-z][a-z-]*){0,3})/g;
7643
7643
  var RETIRED_RE = /\b(retired|historical|removed|deleted|gone|no longer|superseded|legacy)\b/i;
7644
7644
  var ROOT_DOCS = ["README.md", "architecture.md"];
7645
7645
  var SKIP_WALK = ["docs/Archive/", "docs/incidents/", "docs/research/"];
7646
+ function refFirstSegment(ref) {
7647
+ return ref.replace(/^(\.\/)+/, "").split("/")[0];
7648
+ }
7646
7649
  function stripFences(markdown) {
7647
7650
  let inFence = false;
7648
7651
  return markdown.split(/\r?\n/).map((line) => {
@@ -7745,6 +7748,12 @@ function extractCommands(markdown) {
7745
7748
  }
7746
7749
  function checkRefs(root, deps, docs2) {
7747
7750
  const { exists, isIgnored = () => /* @__PURE__ */ new Set() } = deps;
7751
+ const allFirstSegments = /* @__PURE__ */ new Set();
7752
+ for (const markdown of Object.values(docs2)) {
7753
+ for (const { ref } of extractRefs(markdown)) allFirstSegments.add(refFirstSegment(ref));
7754
+ }
7755
+ const tracked = allFirstSegments.size ? deps.trackedFirstSegments?.([...allFirstSegments]) ?? null : null;
7756
+ const firstVerifiable = (first) => tracked ? tracked.has(first) : exists((0, import_node_path12.join)(root, first));
7748
7757
  const candidates = [];
7749
7758
  const direct = [];
7750
7759
  for (const [doc, markdown] of Object.entries(docs2)) {
@@ -7781,8 +7790,7 @@ function checkRefs(root, deps, docs2) {
7781
7790
  }
7782
7791
  }
7783
7792
  for (const { ref, line } of extractRefs(markdown)) {
7784
- const first = ref.split("/")[0];
7785
- if (!exists((0, import_node_path12.join)(root, first))) continue;
7793
+ if (!firstVerifiable(refFirstSegment(ref))) continue;
7786
7794
  if (!exists((0, import_node_path12.join)(root, ref))) candidates.push({ kind: "missing-path", doc, line, detail: ref });
7787
7795
  }
7788
7796
  for (const { target, line, resolved, missing } of links) {
@@ -7872,18 +7880,43 @@ function defaultIsIgnored(root, relPaths, exec = import_node_child_process5.exec
7872
7880
  throw error;
7873
7881
  }
7874
7882
  }
7883
+ function defaultTrackedFirstSegments(root, firstSegments, exec = import_node_child_process5.execFileSync) {
7884
+ if (firstSegments.length === 0) return /* @__PURE__ */ new Set();
7885
+ try {
7886
+ const out = exec("git", ["ls-files", "-z", "--", ...firstSegments.map((s) => `:(literal)${s}`)], {
7887
+ cwd: root,
7888
+ encoding: "utf8",
7889
+ maxBuffer: CHECK_IGNORE_MAX_BUFFER
7890
+ });
7891
+ const tracked = /* @__PURE__ */ new Set();
7892
+ for (const path2 of out.split("\0")) {
7893
+ if (path2) tracked.add(path2.split("/")[0]);
7894
+ }
7895
+ return tracked;
7896
+ } catch (error) {
7897
+ if (error?.status === 128) return null;
7898
+ if (error?.code === "ENOENT") return null;
7899
+ if (error?.code === "ENOBUFS") {
7900
+ throw new Error(
7901
+ `git ls-files produced more than ${CHECK_IGNORE_MAX_BUFFER} bytes for ${firstSegments.length} segment(s) \u2014 the tracked set cannot be read, and treating it as empty would silently skip every ref (#4208)`
7902
+ );
7903
+ }
7904
+ throw error;
7905
+ }
7906
+ }
7875
7907
  function runDocRefs(root, deps = {}) {
7876
7908
  const readFile9 = deps.readFile ?? readFileOrNull;
7877
7909
  const exists = deps.exists ?? import_node_fs13.existsSync;
7878
7910
  const listDocs = deps.listDocs ?? defaultListDocs;
7879
7911
  const isIgnored = deps.isIgnored ?? ((paths) => defaultIsIgnored(root, paths));
7912
+ const trackedFirstSegments = deps.trackedFirstSegments ?? ((segs) => defaultTrackedFirstSegments(root, segs));
7880
7913
  const commandPaths = deps.commandPaths ?? null;
7881
7914
  const walked = listDocs(root);
7882
7915
  const ignoredDocs = walked.length ? isIgnored(walked) : /* @__PURE__ */ new Set();
7883
7916
  const docs2 = Object.fromEntries(
7884
7917
  walked.filter((rel) => !ignoredDocs.has(rel)).map((rel) => [rel, readFile9((0, import_node_path12.join)(root, rel))]).filter(([, body]) => body != null)
7885
7918
  );
7886
- const refResult = checkRefs(root, { exists, isIgnored }, docs2);
7919
+ const refResult = checkRefs(root, { exists, isIgnored, trackedFirstSegments }, docs2);
7887
7920
  const findings = [
7888
7921
  ...checkPins(root, readFile9, docs2).findings,
7889
7922
  ...refResult.findings
@@ -9218,15 +9251,15 @@ function healJervCodePackageRegistration(opts = {}) {
9218
9251
  return { available: true, ok: true, changed: false, version: null, detail: "skipped \u2014 no installed MMI clone carries the pi wrapper (install the Claude plugin first)" };
9219
9252
  }
9220
9253
  const packagePath = `${clone.path.replace(/\\/g, "/").replace(/\/+$/, "")}/${JERVCODE_WRAPPER_DIR}`;
9221
- const settingsPath = (0, import_node_path14.join)(agentDir, "settings.json");
9222
- const settings = readPiSettings(settingsPath);
9254
+ const settingsPath2 = (0, import_node_path14.join)(agentDir, "settings.json");
9255
+ const settings = readPiSettings(settingsPath2);
9223
9256
  if (settings === null) {
9224
9257
  return {
9225
9258
  available: true,
9226
9259
  ok: false,
9227
9260
  changed: false,
9228
9261
  version: clone.version,
9229
- detail: `package NOT registered \u2014 ${settingsPath} is not a strict JSON object, so mmi left it alone; add ${packagePath} to its packages array by hand`
9262
+ detail: `package NOT registered \u2014 ${settingsPath2} is not a strict JSON object, so mmi left it alone; add ${packagePath} to its packages array by hand`
9230
9263
  };
9231
9264
  }
9232
9265
  const current = settings ?? {};
@@ -9237,12 +9270,12 @@ function healJervCodePackageRegistration(opts = {}) {
9237
9270
  }
9238
9271
  current.packages = merged.next;
9239
9272
  try {
9240
- (0, import_node_fs15.mkdirSync)((0, import_node_path14.dirname)(settingsPath), { recursive: true });
9241
- const tmp = `${settingsPath}.tmp-${process.pid}`;
9273
+ (0, import_node_fs15.mkdirSync)((0, import_node_path14.dirname)(settingsPath2), { recursive: true });
9274
+ const tmp = `${settingsPath2}.tmp-${process.pid}`;
9242
9275
  (0, import_node_fs15.writeFileSync)(tmp, `${JSON.stringify(current, null, 2)}
9243
9276
  `, "utf8");
9244
9277
  try {
9245
- (0, import_node_fs15.renameSync)(tmp, settingsPath);
9278
+ (0, import_node_fs15.renameSync)(tmp, settingsPath2);
9246
9279
  } catch (renameError) {
9247
9280
  (0, import_node_fs15.rmSync)(tmp, { force: true });
9248
9281
  throw renameError;
@@ -12011,6 +12044,12 @@ async function auditRepoCi(repo, deps) {
12011
12044
  label: "delete_branch_on_merge enabled",
12012
12045
  detail: info.delete_branch_on_merge === true ? void 0 : "false or unavailable"
12013
12046
  });
12047
+ checks.push({
12048
+ ok: info.has_wiki === false,
12049
+ label: "has_wiki disabled",
12050
+ detail: info.has_wiki === false ? void 0 : "wikis are retired org-wide",
12051
+ remediation: `gh api -X PATCH repos/${repo} -F has_wiki=false`
12052
+ });
12014
12053
  const hasCanonicalGateWorkflow = repoClass === "hub" ? true : repoClass === "content" ? true : await contentExists(deps, repo, baseBranch, PRODUCT_GATE_PATH);
12015
12054
  let prWorkflowPaths = repoClass === "deployable" && hasCanonicalGateWorkflow ? [PRODUCT_GATE_PATH] : [];
12016
12055
  if (repoClass === "deployable" && !hasCanonicalGateWorkflow) {
@@ -12269,7 +12308,7 @@ async function applyCiReconcileMergeSettingsFromReport(repo, deps, report) {
12269
12308
  const applied = [];
12270
12309
  const skipped = [];
12271
12310
  const errors = [];
12272
- const mergeChecks = report.checks.filter((c) => c.label.startsWith("allow_") || c.label.startsWith("delete_branch"));
12311
+ const mergeChecks = report.checks.filter((c) => c.label.startsWith("allow_") || c.label.startsWith("delete_branch") || c.label.startsWith("has_wiki"));
12273
12312
  const needsPatch = mergeChecks.some((c) => !c.ok);
12274
12313
  if (!needsPatch) {
12275
12314
  skipped.push("merge settings already canonical");
@@ -12280,10 +12319,11 @@ async function applyCiReconcileMergeSettingsFromReport(repo, deps, report) {
12280
12319
  body: {
12281
12320
  allow_auto_merge: true,
12282
12321
  allow_squash_merge: true,
12283
- delete_branch_on_merge: true
12322
+ delete_branch_on_merge: true,
12323
+ has_wiki: false
12284
12324
  }
12285
12325
  });
12286
- applied.push("allow_auto_merge, allow_squash_merge, delete_branch_on_merge");
12326
+ applied.push("allow_auto_merge, allow_squash_merge, delete_branch_on_merge, has_wiki=false");
12287
12327
  } catch (e) {
12288
12328
  errors.push(e.message);
12289
12329
  }
@@ -12466,7 +12506,7 @@ async function parkProductRuleset(repo, deps) {
12466
12506
  return result;
12467
12507
  }
12468
12508
  function reconcileOwnedFailures(report) {
12469
- return report.checks.filter((check) => !check.ok).filter((check) => check.label.startsWith("allow_") || check.label.startsWith("delete_branch") || check.label.startsWith("gate workflow committed on ") || check.label.startsWith("product ruleset reference committed on ") || check.label === "product required status checks active" || check.label === RULESET_REFERENCE_MATCH_LABEL || check.label === REQUIRED_CONTEXTS_EMITTED_LABEL || check.label === TAG_ADDRESSABLE_CONTEXTS_LABEL).map((check) => check.label);
12509
+ return report.checks.filter((check) => !check.ok).filter((check) => check.label.startsWith("allow_") || check.label.startsWith("delete_branch") || check.label.startsWith("has_wiki") || check.label.startsWith("gate workflow committed on ") || check.label.startsWith("product ruleset reference committed on ") || check.label === "product required status checks active" || check.label === RULESET_REFERENCE_MATCH_LABEL || check.label === REQUIRED_CONTEXTS_EMITTED_LABEL || check.label === TAG_ADDRESSABLE_CONTEXTS_LABEL).map((check) => check.label);
12470
12510
  }
12471
12511
  async function finalizeCiReconcile(repo, deps, result, before, pendingReason) {
12472
12512
  if (result.errors.length > 0) {
@@ -17825,18 +17865,25 @@ function consolidateCommandNamespaces(program3) {
17825
17865
  move(program3, stage, "port-range");
17826
17866
  }
17827
17867
 
17868
+ // src/pi-plugin-registration.ts
17869
+ var import_node_fs22 = require("node:fs");
17870
+ var import_node_path20 = require("node:path");
17871
+
17828
17872
  // src/plugin-cache-prune.ts
17829
17873
  var PLUGIN_CACHE_KEEP = 2;
17830
17874
  var VERSION_DIR = /^\d+\.\d+\.\d+(?:[-+].*)?$/;
17831
17875
  function isVersionDirName(name) {
17832
17876
  return VERSION_DIR.test(name);
17833
17877
  }
17834
- function selectPrunablePluginVersions(names, currentVersion) {
17878
+ function selectPrunablePluginVersions(names, currentVersion, installedVersion) {
17835
17879
  const versions = [...new Set(names)].filter(isVersionDirName);
17836
17880
  if (versions.length <= PLUGIN_CACHE_KEEP) return [];
17881
+ if (!currentVersion && !installedVersion) return [];
17837
17882
  const newestFirst = [...versions].sort((a, b) => compareVersions(b, a));
17838
17883
  const keep = /* @__PURE__ */ new Set();
17884
+ keep.add(newestFirst[0]);
17839
17885
  if (currentVersion && versions.includes(currentVersion)) keep.add(currentVersion);
17886
+ if (installedVersion && versions.includes(installedVersion)) keep.add(installedVersion);
17840
17887
  for (const v of newestFirst) {
17841
17888
  if (keep.size >= PLUGIN_CACHE_KEEP) break;
17842
17889
  keep.add(v);
@@ -17964,7 +18011,7 @@ function buildPluginCachePlan(home, running, deps, opts = {}) {
17964
18011
  return absent;
17965
18012
  }
17966
18013
  const versions = names.filter(isVersionDirName);
17967
- const prune = selectPrunablePluginVersions(versions, running);
18014
+ const prune = selectPrunablePluginVersions(versions, running, opts.installedVersion);
17968
18015
  const pruneSet = new Set(prune);
17969
18016
  const keep = [...versions].sort((a, b) => compareVersions(b, a)).filter((v) => !pruneSet.has(v));
17970
18017
  const bytes = opts.withBytes ? prune.reduce((sum, v) => sum + deps.dirBytes(`${cacheRoot}/${v}`), 0) : 0;
@@ -18063,6 +18110,52 @@ function renderPluginCachePlan(plan, applied) {
18063
18110
  return lines.join("\n");
18064
18111
  }
18065
18112
 
18113
+ // src/pi-plugin-registration.ts
18114
+ function isMmiPiPackage(entry) {
18115
+ return /[\\/]mutmutco[\\/]mmi[\\/][^\\/]+[\\/]\.pi-plugin\/?$/.test(entry);
18116
+ }
18117
+ function expectedPiPluginPath(home, env, installedVersion) {
18118
+ const root = env.CLAUDE_PLUGIN_ROOT?.trim();
18119
+ if (root && /[\\/]mutmutco[\\/]mmi[\\/]/.test(root)) return (0, import_node_path20.join)(root, ".pi-plugin");
18120
+ const version = installedVersion ?? runningPluginVersion(env);
18121
+ if (!version) return void 0;
18122
+ return (0, import_node_path20.join)(home, ".claude", "plugins", "cache", "mutmutco", "mmi", version, ".pi-plugin");
18123
+ }
18124
+ function settingsPath(home) {
18125
+ return (0, import_node_path20.join)(home, ".pi", "agent", "settings.json");
18126
+ }
18127
+ function readPiPluginState(home, env, installedVersion) {
18128
+ if (!(0, import_node_fs22.existsSync)((0, import_node_path20.join)(home, ".pi", "agent"))) return void 0;
18129
+ const expectedPath = expectedPiPluginPath(home, env, installedVersion);
18130
+ if (!expectedPath || !(0, import_node_fs22.existsSync)(expectedPath)) return void 0;
18131
+ const file = settingsPath(home);
18132
+ if (!(0, import_node_fs22.existsSync)(file)) return { expectedPath, settingsReadable: true };
18133
+ try {
18134
+ const parsed = JSON.parse((0, import_node_fs22.readFileSync)(file, "utf8"));
18135
+ const packages = Array.isArray(parsed.packages) ? parsed.packages : [];
18136
+ return { expectedPath, registeredPath: packages.find((p) => typeof p === "string" && isMmiPiPackage(p)), settingsReadable: true };
18137
+ } catch {
18138
+ return { expectedPath, settingsReadable: false };
18139
+ }
18140
+ }
18141
+ function healPiPluginRegistration(home, env, installedVersion) {
18142
+ const state = readPiPluginState(home, env, installedVersion);
18143
+ if (!state) return { ok: false, detail: "no ~/.pi/agent or no installed .pi-plugin payload" };
18144
+ if (!state.settingsReadable) return { ok: false, detail: "settings.json unreadable \u2014 nothing written (fail closed)" };
18145
+ const file = settingsPath(home);
18146
+ try {
18147
+ const parsed = (0, import_node_fs22.existsSync)(file) ? JSON.parse((0, import_node_fs22.readFileSync)(file, "utf8")) : {};
18148
+ const packages = Array.isArray(parsed.packages) ? parsed.packages : [];
18149
+ const next = packages.filter((p) => !(typeof p === "string" && isMmiPiPackage(p)));
18150
+ next.push(state.expectedPath);
18151
+ (0, import_node_fs22.writeFileSync)(file, `${JSON.stringify({ ...parsed, packages: next }, null, 2)}
18152
+ `);
18153
+ return { ok: true, detail: state.registeredPath ? `replaced ${state.registeredPath}` : `registered ${state.expectedPath}` };
18154
+ } catch (e) {
18155
+ return { ok: false, detail: e.message };
18156
+ }
18157
+ }
18158
+
18066
18159
  // src/skill-lesson.ts
18067
18160
  var SKILL_LESSON_LABEL = "skill-lesson";
18068
18161
  var SKILL_NAMES = ["bootstrap", "browser-automation", "doctor", "epic", "hotfix", "mmi", "onboard", "rcand", "release", "resume", "secrets", "stage", "worktree"];
@@ -19490,8 +19583,8 @@ function renderAccessReport(report) {
19490
19583
  // src/repo-index.ts
19491
19584
  var import_node_crypto4 = require("node:crypto");
19492
19585
  var import_node_child_process11 = require("node:child_process");
19493
- var import_node_fs22 = require("node:fs");
19494
- var import_node_path20 = require("node:path");
19586
+ var import_node_fs23 = require("node:fs");
19587
+ var import_node_path21 = require("node:path");
19495
19588
  var REPO_INDEX_SCHEMA = 1;
19496
19589
  var HARD_DENY = [
19497
19590
  /(^|\/)\.env(\.|$)/i,
@@ -19616,11 +19709,11 @@ function loadReadmeHints(cwd, candidatePaths) {
19616
19709
  }
19617
19710
  for (const rel of readmes) {
19618
19711
  if (isHardDeniedPath(rel)) continue;
19619
- const abs = (0, import_node_path20.join)(cwd, ...rel.split("/"));
19620
- if (!(0, import_node_fs22.existsSync)(abs)) continue;
19712
+ const abs = (0, import_node_path21.join)(cwd, ...rel.split("/"));
19713
+ if (!(0, import_node_fs23.existsSync)(abs)) continue;
19621
19714
  let text;
19622
19715
  try {
19623
- text = (0, import_node_fs22.readFileSync)(abs, "utf8");
19716
+ text = (0, import_node_fs23.readFileSync)(abs, "utf8");
19624
19717
  } catch {
19625
19718
  continue;
19626
19719
  }
@@ -19633,7 +19726,7 @@ function loadReadmeHints(cwd, candidatePaths) {
19633
19726
  return hints;
19634
19727
  }
19635
19728
  function toPosix(p) {
19636
- return p.split(import_node_path20.sep).join("/");
19729
+ return p.split(import_node_path21.sep).join("/");
19637
19730
  }
19638
19731
  function listCandidatePaths(cwd, exec = import_node_child_process11.execFileSync) {
19639
19732
  try {
@@ -19655,11 +19748,11 @@ function rebuildRepoIndex(cwd, repoSlug2) {
19655
19748
  for (const rel of candidates) {
19656
19749
  if (ignored.has(rel)) continue;
19657
19750
  if (isHardDeniedPath(rel)) continue;
19658
- const abs = (0, import_node_path20.join)(cwd, ...rel.split("/"));
19659
- if (!(0, import_node_fs22.existsSync)(abs)) continue;
19751
+ const abs = (0, import_node_path21.join)(cwd, ...rel.split("/"));
19752
+ if (!(0, import_node_fs23.existsSync)(abs)) continue;
19660
19753
  let text;
19661
19754
  try {
19662
- text = (0, import_node_fs22.readFileSync)(abs, "utf8");
19755
+ text = (0, import_node_fs23.readFileSync)(abs, "utf8");
19663
19756
  } catch {
19664
19757
  continue;
19665
19758
  }
@@ -19684,16 +19777,16 @@ function rebuildRepoIndex(cwd, repoSlug2) {
19684
19777
  entries
19685
19778
  };
19686
19779
  const store = repoIndexStorePath(cwd);
19687
- (0, import_node_fs22.mkdirSync)((0, import_node_path20.dirname)(store), { recursive: true });
19688
- (0, import_node_fs22.writeFileSync)(store, `${JSON.stringify(projection, null, 2)}
19780
+ (0, import_node_fs23.mkdirSync)((0, import_node_path21.dirname)(store), { recursive: true });
19781
+ (0, import_node_fs23.writeFileSync)(store, `${JSON.stringify(projection, null, 2)}
19689
19782
  `, "utf8");
19690
19783
  return projection;
19691
19784
  }
19692
19785
  function loadRepoIndex(cwd) {
19693
19786
  const store = repoIndexStorePath(cwd);
19694
- if (!(0, import_node_fs22.existsSync)(store)) return null;
19787
+ if (!(0, import_node_fs23.existsSync)(store)) return null;
19695
19788
  try {
19696
- const raw = JSON.parse((0, import_node_fs22.readFileSync)(store, "utf8"));
19789
+ const raw = JSON.parse((0, import_node_fs23.readFileSync)(store, "utf8"));
19697
19790
  if (raw?.schema !== REPO_INDEX_SCHEMA || !Array.isArray(raw.entries)) return null;
19698
19791
  return raw;
19699
19792
  } catch {
@@ -19765,7 +19858,7 @@ function inferRepoSlug(cwd, exec = import_node_child_process11.execFileSync) {
19765
19858
  if (m?.[1]) return m[1].toLowerCase();
19766
19859
  } catch {
19767
19860
  }
19768
- return ((0, import_node_path20.basename)(cwd) || "local").toLowerCase();
19861
+ return ((0, import_node_path21.basename)(cwd) || "local").toLowerCase();
19769
19862
  }
19770
19863
 
19771
19864
  // src/repo-index-cloud-client.ts
@@ -19905,9 +19998,9 @@ async function gcRepoIndexCloud(deps) {
19905
19998
  }
19906
19999
 
19907
20000
  // src/repo-index-sync.ts
19908
- var import_node_fs23 = require("node:fs");
20001
+ var import_node_fs24 = require("node:fs");
19909
20002
  var import_node_os9 = require("node:os");
19910
- var import_node_path21 = require("node:path");
20003
+ var import_node_path22 = require("node:path");
19911
20004
  var import_node_child_process12 = require("node:child_process");
19912
20005
  var MAX_EMBED_BACKFILL_ROUNDS = 40;
19913
20006
  function normalizeRepo(raw) {
@@ -19951,7 +20044,7 @@ async function syncEstateRepoIndex(opts) {
19951
20044
  const failed = [];
19952
20045
  const skipped = [];
19953
20046
  for (const repo of repos) {
19954
- const dir = (0, import_node_fs23.mkdtempSync)((0, import_node_path21.join)((0, import_node_os9.tmpdir)(), "mmi-repo-index-"));
20047
+ const dir = (0, import_node_fs24.mkdtempSync)((0, import_node_path22.join)((0, import_node_os9.tmpdir)(), "mmi-repo-index-"));
19955
20048
  try {
19956
20049
  shallowClone(repo, dir, opts.githubToken);
19957
20050
  const built = rebuildRepoIndex(dir, repo);
@@ -20001,7 +20094,7 @@ async function syncEstateRepoIndex(opts) {
20001
20094
  failed.push({ repo, error: e.message });
20002
20095
  } finally {
20003
20096
  try {
20004
- (0, import_node_fs23.rmSync)(dir, { recursive: true, force: true });
20097
+ (0, import_node_fs24.rmSync)(dir, { recursive: true, force: true });
20005
20098
  } catch {
20006
20099
  }
20007
20100
  }
@@ -20010,7 +20103,7 @@ async function syncEstateRepoIndex(opts) {
20010
20103
  }
20011
20104
 
20012
20105
  // src/repo-index-health.ts
20013
- var import_node_fs24 = require("node:fs");
20106
+ var import_node_fs25 = require("node:fs");
20014
20107
 
20015
20108
  // testdata/repo-index-golden-queries.json
20016
20109
  var repo_index_golden_queries_default = {
@@ -20052,7 +20145,7 @@ function assertGoldenSuite(raw, source) {
20052
20145
  function loadGoldenSuite(path2) {
20053
20146
  let text;
20054
20147
  try {
20055
- text = (0, import_node_fs24.readFileSync)(path2, "utf8");
20148
+ text = (0, import_node_fs25.readFileSync)(path2, "utf8");
20056
20149
  } catch (e) {
20057
20150
  throw new Error(`golden suite unreadable at ${path2}: ${e.message}`);
20058
20151
  }
@@ -20196,8 +20289,8 @@ async function runRepoIndexHealth(opts) {
20196
20289
 
20197
20290
  // src/spawn-policy-core.ts
20198
20291
  var import_node_child_process13 = require("node:child_process");
20199
- var import_node_fs25 = require("node:fs");
20200
- var import_node_path22 = require("node:path");
20292
+ var import_node_fs26 = require("node:fs");
20293
+ var import_node_path23 = require("node:path");
20201
20294
  var SPAWNERS = ["spawn", "spawnSync", "exec", "execSync", "execFile", "execFileSync"];
20202
20295
  var CALL_SOURCE = String.raw`(^|[^.\w$])(${SPAWNERS.join("|")})\s*\(`;
20203
20296
  var SOURCE_EXT = /\.(ts|mts|cts|js|mjs|cjs)$/;
@@ -20283,7 +20376,7 @@ function runSpawnPolicy(root) {
20283
20376
  for (const file of files) {
20284
20377
  let raw;
20285
20378
  try {
20286
- raw = (0, import_node_fs25.readFileSync)((0, import_node_path22.join)(root, file), "utf8");
20379
+ raw = (0, import_node_fs26.readFileSync)((0, import_node_path23.join)(root, file), "utf8");
20287
20380
  } catch {
20288
20381
  continue;
20289
20382
  }
@@ -20301,8 +20394,8 @@ function runSpawnPolicy(root) {
20301
20394
 
20302
20395
  // src/test-policy-core.ts
20303
20396
  var import_node_child_process14 = require("node:child_process");
20304
- var import_node_fs26 = require("node:fs");
20305
- var import_node_path23 = require("node:path");
20397
+ var import_node_fs27 = require("node:fs");
20398
+ var import_node_path24 = require("node:path");
20306
20399
  var POLICY_FILE = "test-policy.json";
20307
20400
  var TEST_RE = /\.(?:test|spec)\.[cm]?[jt]sx?$/;
20308
20401
  var PY_TEST_RE = /(?:^|\/)test_[^/]*\.py$|_test\.py$/;
@@ -20355,7 +20448,7 @@ function isTestPath(path2) {
20355
20448
  return TEST_RE.test(path2) || PY_TEST_RE.test(path2);
20356
20449
  }
20357
20450
  function loadPolicy(root, readFile9 = readFileOrNull2) {
20358
- const raw = readFile9((0, import_node_path23.join)(root, POLICY_FILE));
20451
+ const raw = readFile9((0, import_node_path24.join)(root, POLICY_FILE));
20359
20452
  if (raw == null) return { mandatory: [], declared: false };
20360
20453
  try {
20361
20454
  return { ...JSON.parse(raw), declared: true };
@@ -20365,7 +20458,7 @@ function loadPolicy(root, readFile9 = readFileOrNull2) {
20365
20458
  }
20366
20459
  function readFileOrNull2(path2) {
20367
20460
  try {
20368
- return (0, import_node_fs26.readFileSync)(path2, "utf8");
20461
+ return (0, import_node_fs27.readFileSync)(path2, "utf8");
20369
20462
  } catch {
20370
20463
  return null;
20371
20464
  }
@@ -20392,12 +20485,12 @@ function classify(changed, policy, present = () => false) {
20392
20485
  const removedProtected = [...removed].filter((p) => protectedBy.has(p)).map((p) => ({ path: p, why: protectedBy.get(p) ?? "" }));
20393
20486
  return { mandatoryHits, untestedHits, testChanges, addedTests, removedProtected };
20394
20487
  }
20395
- function unresolvedProtectedEntries(policy, root, exists = (path2) => (0, import_node_fs26.existsSync)(path2)) {
20396
- return (policy.protected ?? []).map((p) => p.path).filter((p) => !exists((0, import_node_path23.join)(root, p)));
20488
+ function unresolvedProtectedEntries(policy, root, exists = (path2) => (0, import_node_fs27.existsSync)(path2)) {
20489
+ return (policy.protected ?? []).map((p) => p.path).filter((p) => !exists((0, import_node_path24.join)(root, p)));
20397
20490
  }
20398
- function unresolvedSatisfiers(policy, root, exists = (path2) => (0, import_node_fs26.existsSync)(path2)) {
20491
+ function unresolvedSatisfiers(policy, root, exists = (path2) => (0, import_node_fs27.existsSync)(path2)) {
20399
20492
  const declared = (policy.mandatory ?? []).flatMap((m) => m.satisfiedBy ?? []);
20400
- return [...new Set(declared)].filter((p) => !exists((0, import_node_path23.join)(root, p)));
20493
+ return [...new Set(declared)].filter((p) => !exists((0, import_node_path24.join)(root, p)));
20401
20494
  }
20402
20495
  function evaluate(changed, policy, present = () => false) {
20403
20496
  const { mandatoryHits, untestedHits, testChanges, addedTests, removedProtected } = classify(changed, policy, present);
@@ -20579,13 +20672,13 @@ function changedFilesSince(base, cwd) {
20579
20672
  }
20580
20673
  function runTestPolicy(root, deps = {}) {
20581
20674
  const policy = deps.policy ?? loadPolicy(root);
20582
- const exists = deps.exists ?? ((path2) => (0, import_node_fs26.existsSync)(path2));
20675
+ const exists = deps.exists ?? ((path2) => (0, import_node_fs27.existsSync)(path2));
20583
20676
  const counts = { mandatoryCount: (policy.mandatory ?? []).length, protectedCount: (policy.protected ?? []).length };
20584
20677
  const base = deps.changed ? "(injected)" : resolveBase(root, deps.base);
20585
20678
  const refusal = deps.changed ? null : untrustworthyRange(root, base);
20586
20679
  const changed = deps.changed ?? (refusal ? [] : changedFilesSince(base, root));
20587
20680
  const lookup = deps.override !== void 0 ? { override: deps.override, refusals: [] } : !deps.changed && !refusal ? readOverride(base, root) : { override: null, refusals: [] };
20588
- const present = (path2) => exists((0, import_node_path23.join)(root, path2));
20681
+ const present = (path2) => exists((0, import_node_path24.join)(root, path2));
20589
20682
  const removedByThisDiff = removedPaths(changed);
20590
20683
  const staleFindings = [];
20591
20684
  const unresolved = unresolvedProtectedEntries(policy, root, exists).filter((p) => !removedByThisDiff.has(p));
@@ -20741,8 +20834,8 @@ function docsAuditStatus(fetch2, opts) {
20741
20834
  }
20742
20835
 
20743
20836
  // src/project-info-sync.ts
20744
- var import_node_fs27 = require("node:fs");
20745
- var import_node_path24 = require("node:path");
20837
+ var import_node_fs28 = require("node:fs");
20838
+ var import_node_path25 = require("node:path");
20746
20839
  var UPDATE_PROJECT_INFO = `mutation($projectId: ID!, $shortDescription: String!, $readme: String!) {
20747
20840
  updateProjectV2(input: { projectId: $projectId, shortDescription: $shortDescription, readme: $readme }) {
20748
20841
  projectV2 { id }
@@ -20787,14 +20880,14 @@ function sharedName(entries, fallback) {
20787
20880
  }
20788
20881
  function buildProjectInfoSyncPlan(targetRepo2, project2, projects, repoRoot2) {
20789
20882
  if (!project2.projectId) throw new Error(`org project sync-info: ${targetRepo2} registry META has no projectId`);
20790
- const readmePath = (0, import_node_path24.join)(repoRoot2, "README.md");
20791
- if (!(0, import_node_fs27.existsSync)(readmePath)) throw new Error(`org project sync-info: ${targetRepo2} has no README.md`);
20883
+ const readmePath = (0, import_node_path25.join)(repoRoot2, "README.md");
20884
+ if (!(0, import_node_fs28.existsSync)(readmePath)) throw new Error(`org project sync-info: ${targetRepo2} has no README.md`);
20792
20885
  const entries = entriesFor(project2, projects);
20793
20886
  const memberRepos = [...new Set(entries.flatMap((entry) => entry.repos ?? []))].filter((repo) => /^[^/]+\/[^/]+$/.test(repo)).sort((a, b) => a.localeCompare(b));
20794
20887
  const projectName = sharedName(entries, project2.name?.trim() || targetRepo2.split("/").pop() || targetRepo2);
20795
20888
  if (!memberRepos.length) throw new Error(`org project sync-info: project ${projectName} has no registered member repos`);
20796
20889
  const entryNames = entries.map((entry) => entry.name?.trim()).filter((name) => Boolean(name));
20797
- const shortDescription = memberRepos.length === 1 ? shortDescriptionFromReadme((0, import_node_fs27.readFileSync)(readmePath, "utf8")) : `Shared work across ${new Intl.ListFormat("en", { type: "conjunction" }).format(entryNames)}.`;
20890
+ const shortDescription = memberRepos.length === 1 ? shortDescriptionFromReadme((0, import_node_fs28.readFileSync)(readmePath, "utf8")) : `Shared work across ${new Intl.ListFormat("en", { type: "conjunction" }).format(entryNames)}.`;
20798
20891
  const lines = [
20799
20892
  `# ${projectName}`,
20800
20893
  "",
@@ -20813,8 +20906,8 @@ function buildProjectInfoSyncPlan(targetRepo2, project2, projects, repoRoot2) {
20813
20906
  const targetBase = `https://github.com/${targetRepo2}`;
20814
20907
  const targetBranch = branchFor(targetRepo2, projects);
20815
20908
  const orgDocs = [
20816
- (0, import_node_fs27.existsSync)((0, import_node_path24.join)(repoRoot2, "docs", "org-readme.md")) ? `- [Org identity](${targetBase}/blob/${targetBranch}/docs/org-readme.md)` : "",
20817
- (0, import_node_fs27.existsSync)((0, import_node_path24.join)(repoRoot2, "docs", "org-architecture.md")) ? `- [Org architecture](${targetBase}/blob/${targetBranch}/docs/org-architecture.md)` : ""
20909
+ (0, import_node_fs28.existsSync)((0, import_node_path25.join)(repoRoot2, "docs", "org-readme.md")) ? `- [Org identity](${targetBase}/blob/${targetBranch}/docs/org-readme.md)` : "",
20910
+ (0, import_node_fs28.existsSync)((0, import_node_path25.join)(repoRoot2, "docs", "org-architecture.md")) ? `- [Org architecture](${targetBase}/blob/${targetBranch}/docs/org-architecture.md)` : ""
20818
20911
  ].filter(Boolean);
20819
20912
  if (orgDocs.length) lines.push("", "## Organisation docs", "", ...orgDocs);
20820
20913
  return { projectId: project2.projectId, projectName, targetRepo: targetRepo2, memberRepos, shortDescription, readme: `${lines.join("\n")}
@@ -21673,8 +21766,8 @@ function writeError(res) {
21673
21766
  }
21674
21767
 
21675
21768
  // src/secrets-commands.ts
21676
- var import_node_fs28 = require("node:fs");
21677
- var import_node_path25 = require("node:path");
21769
+ var import_node_fs29 = require("node:fs");
21770
+ var import_node_path26 = require("node:path");
21678
21771
  var import_node_os10 = require("node:os");
21679
21772
 
21680
21773
  // src/project-runtime.ts
@@ -21798,18 +21891,18 @@ function collectMap(value, previous = []) {
21798
21891
  return [...previous, value];
21799
21892
  }
21800
21893
  async function decryptRailsCredentials(input) {
21801
- const appDir = (0, import_node_path25.resolve)(input.appDir ?? process.cwd());
21894
+ const appDir = (0, import_node_path26.resolve)(input.appDir ?? process.cwd());
21802
21895
  const credentialsFile = input.credentialsFile ?? DEFAULT_RAILS_CREDENTIALS_FILE;
21803
21896
  const masterKeyFile = input.masterKeyFile ?? DEFAULT_RAILS_MASTER_KEY_FILE;
21804
- const credentialsPath = (0, import_node_path25.resolve)(appDir, credentialsFile);
21805
- const masterKeyPath = (0, import_node_path25.resolve)(appDir, masterKeyFile);
21897
+ const credentialsPath = (0, import_node_path26.resolve)(appDir, credentialsFile);
21898
+ const masterKeyPath = (0, import_node_path26.resolve)(appDir, masterKeyFile);
21806
21899
  const env = {
21807
21900
  ...process.env,
21808
21901
  MMI_RAILS_CREDENTIALS_FILE: credentialsPath,
21809
21902
  MMI_RAILS_MASTER_KEY_FILE: masterKeyPath
21810
21903
  };
21811
- if ((0, import_node_fs28.existsSync)(masterKeyPath)) {
21812
- env.RAILS_MASTER_KEY = (0, import_node_fs28.readFileSync)(masterKeyPath, "utf8").trim();
21904
+ if ((0, import_node_fs29.existsSync)(masterKeyPath)) {
21905
+ env.RAILS_MASTER_KEY = (0, import_node_fs29.readFileSync)(masterKeyPath, "utf8").trim();
21813
21906
  }
21814
21907
  const script = [
21815
21908
  'require "json"',
@@ -21819,9 +21912,9 @@ async function decryptRailsCredentials(input) {
21819
21912
  'config = ActiveSupport::EncryptedConfiguration.new(config_path: config_path, key_path: key_path, env_key: "RAILS_MASTER_KEY", raise_if_missing_key: true)',
21820
21913
  "puts JSON.generate(config.config)"
21821
21914
  ].join("\n");
21822
- const scriptDir = (0, import_node_fs28.mkdtempSync)((0, import_node_path25.join)((0, import_node_os10.tmpdir)(), "mmi-rails-decrypt-"));
21823
- const scriptPath = (0, import_node_path25.join)(scriptDir, "decrypt.rb");
21824
- (0, import_node_fs28.writeFileSync)(scriptPath, script, "utf8");
21915
+ const scriptDir = (0, import_node_fs29.mkdtempSync)((0, import_node_path26.join)((0, import_node_os10.tmpdir)(), "mmi-rails-decrypt-"));
21916
+ const scriptPath = (0, import_node_path26.join)(scriptDir, "decrypt.rb");
21917
+ (0, import_node_fs29.writeFileSync)(scriptPath, script, "utf8");
21825
21918
  try {
21826
21919
  const args = ["exec", "ruby", scriptPath];
21827
21920
  const cmd = process.platform === "win32" ? "cmd.exe" : "bundle";
@@ -21833,7 +21926,7 @@ async function decryptRailsCredentials(input) {
21833
21926
  });
21834
21927
  return JSON.parse(stdout);
21835
21928
  } finally {
21836
- (0, import_node_fs28.rmSync)(scriptDir, { recursive: true, force: true });
21929
+ (0, import_node_fs29.rmSync)(scriptDir, { recursive: true, force: true });
21837
21930
  }
21838
21931
  }
21839
21932
  async function readSecretStdin() {
@@ -21923,7 +22016,7 @@ function registerSecretsCommands(program3) {
21923
22016
  let body;
21924
22017
  if (o.file) {
21925
22018
  try {
21926
- body = (0, import_node_fs28.readFileSync)((0, import_node_path25.resolve)(o.file), "utf8");
22019
+ body = (0, import_node_fs29.readFileSync)((0, import_node_path26.resolve)(o.file), "utf8");
21927
22020
  } catch (e) {
21928
22021
  return fail(`secrets org-catalog: cannot read --file ${o.file}: ${e.message}`);
21929
22022
  }
@@ -22028,7 +22121,7 @@ function registerSecretsCommands(program3) {
22028
22121
  {
22029
22122
  ...d,
22030
22123
  decryptRailsCredentials,
22031
- removeFile: (path2) => (0, import_node_fs28.unlinkSync)((0, import_node_path25.resolve)(o.appDir ?? process.cwd(), path2))
22124
+ removeFile: (path2) => (0, import_node_fs29.unlinkSync)((0, import_node_path26.resolve)(o.appDir ?? process.cwd(), path2))
22032
22125
  },
22033
22126
  {
22034
22127
  repo: o.repo,
@@ -22192,7 +22285,7 @@ async function activateAppActor(commandPath3, env, mint) {
22192
22285
  }
22193
22286
 
22194
22287
  // src/box-commands.ts
22195
- var import_node_fs29 = require("node:fs");
22288
+ var import_node_fs30 = require("node:fs");
22196
22289
 
22197
22290
  // src/box.ts
22198
22291
  var BOX_KEYS = {
@@ -22395,7 +22488,7 @@ function registerBoxCommands(program3) {
22395
22488
  }
22396
22489
  if (o.json) console.log(JSON.stringify({ box: found, incomplete }, null, 2));
22397
22490
  else if (o.ssh && o.script) {
22398
- (0, import_node_fs29.writeFileSync)(o.script, sshRecipeScript(found), "utf8");
22491
+ (0, import_node_fs30.writeFileSync)(o.script, sshRecipeScript(found), "utf8");
22399
22492
  console.log(`wrote ${o.script} \u2014 run: bash "${o.script}"`);
22400
22493
  } else if (o.ssh) console.log(`${formatSshRecipe(found)}
22401
22494
  ${SSH_RECIPE_AGENT_NOTE}`);
@@ -23084,7 +23177,7 @@ function registerSchedulesCommands(program3) {
23084
23177
 
23085
23178
  // src/file-lock.ts
23086
23179
  var import_promises5 = require("node:fs/promises");
23087
- var import_node_path26 = require("node:path");
23180
+ var import_node_path27 = require("node:path");
23088
23181
  var sleep = (ms) => new Promise((resolve5) => setTimeout(resolve5, ms));
23089
23182
  var IMMEDIATE_RETRY_BUDGET = 3;
23090
23183
  var FileLockBusyError = class extends Error {
@@ -23169,7 +23262,7 @@ async function releaseFileLock(lockPath, guard) {
23169
23262
  }
23170
23263
  async function withFileLock(lockPath, opts, fn) {
23171
23264
  const resolved = resolveFileLockOpts(opts);
23172
- await (0, import_promises5.mkdir)((0, import_node_path26.dirname)(lockPath), { recursive: true }).catch(() => void 0);
23265
+ await (0, import_promises5.mkdir)((0, import_node_path27.dirname)(lockPath), { recursive: true }).catch(() => void 0);
23173
23266
  const guard = await acquireFileLock(lockPath, resolved, Date.now() + resolved.maxWaitMs);
23174
23267
  try {
23175
23268
  return await fn();
@@ -23180,7 +23273,7 @@ async function withFileLock(lockPath, opts, fn) {
23180
23273
 
23181
23274
  // src/schedules-lift-command.ts
23182
23275
  var import_promises6 = require("node:fs/promises");
23183
- var import_node_path27 = require("node:path");
23276
+ var import_node_path28 = require("node:path");
23184
23277
 
23185
23278
  // src/schedules-lift.ts
23186
23279
  var SCHEDULE_HEADER_FIELDS = ["schedule", "what", "owner", "cadence", "executor", "llm", "output", "breaks", "kill"];
@@ -23286,7 +23379,7 @@ async function readWorkflowFiles(dir) {
23286
23379
  const files = [];
23287
23380
  for (const name of names.sort()) {
23288
23381
  if (!/\.ya?ml$/.test(name)) continue;
23289
- files.push({ path: `.github/workflows/${name}`, text: await (0, import_promises6.readFile)((0, import_node_path27.join)(dir, name), "utf8") });
23382
+ files.push({ path: `.github/workflows/${name}`, text: await (0, import_promises6.readFile)((0, import_node_path28.join)(dir, name), "utf8") });
23290
23383
  }
23291
23384
  return files;
23292
23385
  }
@@ -23828,9 +23921,9 @@ function registerQueryCommands(program3) {
23828
23921
  }
23829
23922
 
23830
23923
  // src/bootstrap-commands.ts
23831
- var import_node_fs30 = require("node:fs");
23924
+ var import_node_fs31 = require("node:fs");
23832
23925
  var import_node_os11 = require("node:os");
23833
- var import_node_path28 = require("node:path");
23926
+ var import_node_path29 = require("node:path");
23834
23927
 
23835
23928
  // src/bootstrap-drift.ts
23836
23929
  function byteComparableSeeds(manifest, cls) {
@@ -24102,6 +24195,11 @@ async function verifyBootstrap(repo, repoClass, deps, releaseTrack) {
24102
24195
  const repoInfo = await restJson3(deps, `repos/${repo}`, {});
24103
24196
  checks.push({ ok: Boolean(repoInfo.default_branch), label: "repo exists" });
24104
24197
  checks.push({ ok: repoInfo.default_branch === baseBranch, label: `default branch is ${baseBranch}`, detail: repoInfo.default_branch || "missing" });
24198
+ checks.push({
24199
+ ok: repoInfo.has_wiki === false,
24200
+ label: "has_wiki disabled",
24201
+ detail: repoInfo.has_wiki === false ? void 0 : `wikis are retired org-wide \u2014 gh api -X PATCH repos/${repo} -F has_wiki=false`
24202
+ });
24105
24203
  const branchList = await restPagedJson2(deps, `repos/${repo}/branches`, []);
24106
24204
  const branchNames = new Set(branchList.map((b) => b.name));
24107
24205
  for (const branch of branchesWanted) {
@@ -24543,13 +24641,13 @@ function registerBootstrapCommands(program3) {
24543
24641
  client: defaultGitHubClient(),
24544
24642
  projectMeta: meta,
24545
24643
  deployModel: typeof meta?.deployModel === "string" ? meta.deployModel : void 0,
24546
- readLocalFile: (path2) => path2 === "projects.json" && apiProjects != null ? apiProjects : (0, import_node_fs30.existsSync)(path2) ? (0, import_node_fs30.readFileSync)(path2, "utf8") : null,
24644
+ readLocalFile: (path2) => path2 === "projects.json" && apiProjects != null ? apiProjects : (0, import_node_fs31.existsSync)(path2) ? (0, import_node_fs31.readFileSync)(path2, "utf8") : null,
24547
24645
  // requiredGcpApis is stored as an array by a JSON write, but `org project set --var KEY=VALUE` stores a raw
24548
24646
  // comma-string — accept either so the seeded value verifies regardless of how it was written.
24549
24647
  // #3689: the same committed map the org access audit reads (#3664), so a sanctioned admin is not a
24550
24648
  // permanent bootstrap failure on one surface and an intended state on the other. Absent file → no
24551
24649
  // sanction, which is the pre-#3664 behaviour.
24552
- sanctionedAdmins: (0, import_node_fs30.existsSync)("access-matrix.json") ? entriesValueByCanonicalRepo(loadSanctionedAdmins((0, import_node_fs30.readFileSync)("access-matrix.json", "utf8")), repo) : void 0,
24650
+ sanctionedAdmins: (0, import_node_fs31.existsSync)("access-matrix.json") ? entriesValueByCanonicalRepo(loadSanctionedAdmins((0, import_node_fs31.readFileSync)("access-matrix.json", "utf8")), repo) : void 0,
24553
24651
  requiredGcpApis: (() => {
24554
24652
  const v = meta?.requiredGcpApis;
24555
24653
  if (Array.isArray(v)) return v;
@@ -24602,12 +24700,12 @@ function registerBootstrapCommands(program3) {
24602
24700
  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 () => {
24603
24701
  const o = { repo: rawValue("--repo", ""), json: rawFlag("--json") };
24604
24702
  const manifestPath = "skills/bootstrap/seeds/manifest.json";
24605
- if (!(0, import_node_fs30.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`);
24606
- const manifest = loadBootstrapSeeds((0, import_node_fs30.readFileSync)(manifestPath, "utf8"));
24703
+ if (!(0, import_node_fs31.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`);
24704
+ const manifest = loadBootstrapSeeds((0, import_node_fs31.readFileSync)(manifestPath, "utf8"));
24607
24705
  const hubContents = /* @__PURE__ */ new Map();
24608
24706
  for (const s of manifest.seeds) {
24609
24707
  if (s.ownership !== "org" || s.source !== "self") continue;
24610
- hubContents.set(s.target, (0, import_node_fs30.existsSync)(s.target) ? (0, import_node_fs30.readFileSync)(s.target, "utf8") : null);
24708
+ hubContents.set(s.target, (0, import_node_fs31.existsSync)(s.target) ? (0, import_node_fs31.readFileSync)(s.target, "utf8") : null);
24611
24709
  }
24612
24710
  let targets;
24613
24711
  let classOf = (_repo) => "deployable";
@@ -24684,8 +24782,8 @@ function registerBootstrapCommands(program3) {
24684
24782
  return fail(`bootstrap apply: ${e.message}`);
24685
24783
  }
24686
24784
  const manifestPath = "skills/bootstrap/seeds/manifest.json";
24687
- if (!(0, import_node_fs30.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`);
24688
- const manifest = loadBootstrapSeeds((0, import_node_fs30.readFileSync)(manifestPath, "utf8"));
24785
+ if (!(0, import_node_fs31.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`);
24786
+ const manifest = loadBootstrapSeeds((0, import_node_fs31.readFileSync)(manifestPath, "utf8"));
24689
24787
  const baseBranch = o.class === "content" ? "main" : "development";
24690
24788
  const slug = parsedRepo.slug;
24691
24789
  const onlyTarget = o.only.trim();
@@ -24696,16 +24794,16 @@ function registerBootstrapCommands(program3) {
24696
24794
  ${known}`);
24697
24795
  }
24698
24796
  const gh = async (args) => execFileP2("gh", args, { timeout: 2e4 });
24699
- const readFile9 = (p) => (0, import_node_fs30.existsSync)(p) ? (0, import_node_fs30.readFileSync)(p, "utf8") : null;
24797
+ const readFile9 = (p) => (0, import_node_fs31.existsSync)(p) ? (0, import_node_fs31.readFileSync)(p, "utf8") : null;
24700
24798
  const enc = (p) => p.split("/").map(encodeURIComponent).join("/");
24701
24799
  const putSeed = async (target, content, ref, sha) => {
24702
- const tmp = (0, import_node_path28.join)((0, import_node_os11.tmpdir)(), `mmi-seed-${process.pid}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}.json`);
24703
- (0, import_node_fs30.writeFileSync)(tmp, JSON.stringify(contentPutBody(target, content, ref, sha)), "utf8");
24800
+ const tmp = (0, import_node_path29.join)((0, import_node_os11.tmpdir)(), `mmi-seed-${process.pid}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}.json`);
24801
+ (0, import_node_fs31.writeFileSync)(tmp, JSON.stringify(contentPutBody(target, content, ref, sha)), "utf8");
24704
24802
  try {
24705
24803
  await gh(contentPutInputArgs(repo, target, tmp));
24706
24804
  } finally {
24707
24805
  try {
24708
- (0, import_node_fs30.unlinkSync)(tmp);
24806
+ (0, import_node_fs31.unlinkSync)(tmp);
24709
24807
  } catch {
24710
24808
  }
24711
24809
  }
@@ -24879,6 +24977,14 @@ function registerBootstrapCommands(program3) {
24879
24977
  });
24880
24978
  applied.push(autoMergeEnabled ? `seed: PR ${seedPrUrl} (base ${baseBranch} protected; auto-merge enabled)` : `seed: PR ${seedPrUrl} (base ${baseBranch} protected; auto-merge refused \u2014 PR is already clean. Land it: mmi-cli pr land <n> --repo ${repo})`);
24881
24979
  }
24980
+ if (o.execute && !onlyTarget) {
24981
+ try {
24982
+ await gh(["api", "-X", "PATCH", `repos/${repo}`, "-F", "has_wiki=false"]);
24983
+ applied.push("has_wiki=false (wikis retired org-wide)");
24984
+ } catch (e) {
24985
+ applied.push(`has_wiki=false (failed: ${e.message})`);
24986
+ }
24987
+ }
24882
24988
  if (o.execute && !onlyTarget && o.class === "deployable") {
24883
24989
  try {
24884
24990
  await gh(["api", "-X", "PATCH", `repos/${repo}`, "-f", "allow_auto_merge=true", "-f", "allow_squash_merge=true", "-f", "delete_branch_on_merge=true"]);
@@ -24962,12 +25068,12 @@ LIVE apply to ${repo}:
24962
25068
  }
24963
25069
 
24964
25070
  // src/stage-commands.ts
24965
- var import_node_fs32 = require("node:fs");
24966
- var import_node_path30 = require("node:path");
25071
+ var import_node_fs33 = require("node:fs");
25072
+ var import_node_path31 = require("node:path");
24967
25073
 
24968
25074
  // src/port-registry.ts
24969
- var import_node_fs31 = require("node:fs");
24970
- var import_node_path29 = require("node:path");
25075
+ var import_node_fs32 = require("node:fs");
25076
+ var import_node_path30 = require("node:path");
24971
25077
 
24972
25078
  // ../infra/port-geometry.mjs
24973
25079
  var PORT_BLOCK = 100;
@@ -24981,8 +25087,8 @@ function nextPortBlock(registry2) {
24981
25087
  return [base, base + PORT_SPAN];
24982
25088
  }
24983
25089
  function loadPortRegistry(path2) {
24984
- if (!(0, import_node_fs31.existsSync)(path2)) return {};
24985
- const raw = JSON.parse((0, import_node_fs31.readFileSync)(path2, "utf8"));
25090
+ if (!(0, import_node_fs32.existsSync)(path2)) return {};
25091
+ const raw = JSON.parse((0, import_node_fs32.readFileSync)(path2, "utf8"));
24986
25092
  const out = {};
24987
25093
  for (const [key, value] of Object.entries(raw)) {
24988
25094
  if (Array.isArray(value) && value.length === 2 && value.every((n) => typeof n === "number")) {
@@ -24996,9 +25102,9 @@ function ensurePortRange(repo, path2) {
24996
25102
  const existing = registry2[repo];
24997
25103
  if (existing) return existing;
24998
25104
  const range = nextPortBlock(registry2);
24999
- const raw = (0, import_node_fs31.existsSync)(path2) ? JSON.parse((0, import_node_fs31.readFileSync)(path2, "utf8")) : {};
25105
+ const raw = (0, import_node_fs32.existsSync)(path2) ? JSON.parse((0, import_node_fs32.readFileSync)(path2, "utf8")) : {};
25000
25106
  raw[repo] = range;
25001
- (0, import_node_fs31.writeFileSync)(path2, JSON.stringify(raw, null, 2) + "\n", "utf8");
25107
+ (0, import_node_fs32.writeFileSync)(path2, JSON.stringify(raw, null, 2) + "\n", "utf8");
25002
25108
  return range;
25003
25109
  }
25004
25110
  function portCursorSeed(registry2) {
@@ -25020,22 +25126,22 @@ function existingPortRange(repo, registry2) {
25020
25126
  return registry2[repo] ?? null;
25021
25127
  }
25022
25128
  function portRangeInfraAt(root, source) {
25023
- const registryPath = (0, import_node_path29.join)(root, "infra", "port-ranges.json");
25024
- const ddbScriptPath = (0, import_node_path29.join)(root, "infra", "port-ddb.mjs");
25025
- if (!(0, import_node_fs31.existsSync)(registryPath) || !(0, import_node_fs31.existsSync)(ddbScriptPath)) return null;
25129
+ const registryPath = (0, import_node_path30.join)(root, "infra", "port-ranges.json");
25130
+ const ddbScriptPath = (0, import_node_path30.join)(root, "infra", "port-ddb.mjs");
25131
+ if (!(0, import_node_fs32.existsSync)(registryPath) || !(0, import_node_fs32.existsSync)(ddbScriptPath)) return null;
25026
25132
  return { root, source, registryPath, ddbScriptPath };
25027
25133
  }
25028
25134
  function resolvePortRangeInfra(cwd, packageDir) {
25029
25135
  const direct = portRangeInfraAt(cwd, "cwd");
25030
25136
  if (direct) return direct;
25031
- for (let dir = cwd; ; dir = (0, import_node_path29.dirname)(dir)) {
25032
- const sibling = portRangeInfraAt((0, import_node_path29.join)(dir, "MMI-Hub"), "sibling-hub");
25137
+ for (let dir = cwd; ; dir = (0, import_node_path30.dirname)(dir)) {
25138
+ const sibling = portRangeInfraAt((0, import_node_path30.join)(dir, "MMI-Hub"), "sibling-hub");
25033
25139
  if (sibling) return sibling;
25034
- const parent = (0, import_node_path29.dirname)(dir);
25140
+ const parent = (0, import_node_path30.dirname)(dir);
25035
25141
  if (parent === dir) break;
25036
25142
  }
25037
25143
  if (packageDir) {
25038
- const pkgRoot = (0, import_node_path29.join)(packageDir, "..", "..");
25144
+ const pkgRoot = (0, import_node_path30.join)(packageDir, "..", "..");
25039
25145
  const pkgFrom = portRangeInfraAt(pkgRoot, "pkg-root");
25040
25146
  if (pkgFrom) return pkgFrom;
25041
25147
  }
@@ -25229,8 +25335,8 @@ function registerStageCommands(program3) {
25229
25335
  const portRange = portRangeMeta && typeof portRangeMeta.start === "number" && typeof portRangeMeta.end === "number" ? [portRangeMeta.start, portRangeMeta.end] : void 0;
25230
25336
  return decideStage({
25231
25337
  registry: { deployModel: project2?.deployModel, portRange, error: read.ok ? void 0 : read.error },
25232
- hasCompose: (0, import_node_fs32.existsSync)((0, import_node_path30.join)(process.cwd(), "docker-compose.yml")),
25233
- hasEnvExample: (0, import_node_fs32.existsSync)((0, import_node_path30.join)(process.cwd(), ".env.example"))
25338
+ hasCompose: (0, import_node_fs33.existsSync)((0, import_node_path31.join)(process.cwd(), "docker-compose.yml")),
25339
+ hasEnvExample: (0, import_node_fs33.existsSync)((0, import_node_path31.join)(process.cwd(), ".env.example"))
25234
25340
  });
25235
25341
  }
25236
25342
  async function fetchStageVaultEnvMerge() {
@@ -25668,9 +25774,9 @@ function registerBoardCommands(program3) {
25668
25774
  }
25669
25775
 
25670
25776
  // src/merge-cleanup.ts
25671
- var import_node_fs33 = require("node:fs");
25777
+ var import_node_fs34 = require("node:fs");
25672
25778
  var import_promises8 = require("node:fs/promises");
25673
- var import_node_path32 = require("node:path");
25779
+ var import_node_path33 = require("node:path");
25674
25780
  var import_node_os12 = require("node:os");
25675
25781
  var import_node_child_process16 = require("node:child_process");
25676
25782
 
@@ -25758,7 +25864,7 @@ function boardAdvanceFailureMessage(result) {
25758
25864
 
25759
25865
  // src/deferred-registry-store.ts
25760
25866
  var import_promises7 = require("node:fs/promises");
25761
- var import_node_path31 = require("node:path");
25867
+ var import_node_path32 = require("node:path");
25762
25868
  var sleep2 = (ms) => new Promise((resolve5) => setTimeout(resolve5, ms));
25763
25869
  async function atomicWrite(target, contents) {
25764
25870
  const tmp = `${target}.tmp-${process.pid}-${Math.random().toString(36).slice(2)}`;
@@ -25809,12 +25915,12 @@ function makeDeferredWorktreeStore(registryPath, lockOpts = {}) {
25809
25915
  },
25810
25916
  // Standalone atomic write — THROWS on failure (no best-effort swallow, #2846).
25811
25917
  write: async (entries) => {
25812
- await (0, import_promises7.mkdir)((0, import_node_path31.dirname)(registryPath), { recursive: true });
25918
+ await (0, import_promises7.mkdir)((0, import_node_path32.dirname)(registryPath), { recursive: true });
25813
25919
  await atomicWrite(registryPath, serializeDeferredWorktrees(entries));
25814
25920
  },
25815
25921
  // Serialized read-modify-write under the repo-wide lock (#2846).
25816
25922
  update: async (mutate) => {
25817
- await (0, import_promises7.mkdir)((0, import_node_path31.dirname)(registryPath), { recursive: true });
25923
+ await (0, import_promises7.mkdir)((0, import_node_path32.dirname)(registryPath), { recursive: true });
25818
25924
  const deadline = Date.now() + opts.maxWaitMs;
25819
25925
  for (; ; ) {
25820
25926
  const guard = await acquireLock(lockPath, opts, deadline);
@@ -25962,7 +26068,7 @@ async function applyGcPlan(plan, remote, opts = {}) {
25962
26068
  );
25963
26069
  const repoRoot2 = (await execFileP2("git", ["rev-parse", "--show-toplevel"], { timeout: GIT_TIMEOUT_MS }).catch(() => ({ stdout: "" }))).stdout.trim() || process.cwd();
25964
26070
  const worktreeGitRoot = await currentRepoWorktreeGitRoot(repoRoot2);
25965
- const primaryRepoRoot = worktreeGitRoot ? (0, import_node_path32.dirname)((0, import_node_path32.dirname)(worktreeGitRoot)) : repoRoot2;
26071
+ const primaryRepoRoot = worktreeGitRoot ? (0, import_node_path33.dirname)((0, import_node_path33.dirname)(worktreeGitRoot)) : repoRoot2;
25966
26072
  const gcActor = describeActor({ env: process.env, surface: detectSurface(process.env), cwd: process.cwd() });
25967
26073
  const owners = readWorktreeOwners(primaryRepoRoot);
25968
26074
  const removalNow = Date.now();
@@ -25993,7 +26099,7 @@ async function applyGcPlan(plan, remote, opts = {}) {
25993
26099
  const cleanup = await cleanupPrMergeLocalBranch(branch.branch, {
25994
26100
  beforeWorktrees,
25995
26101
  startingPath: branch.worktreePath,
25996
- pathExists: (p) => (0, import_node_fs33.existsSync)(p),
26102
+ pathExists: (p) => (0, import_node_fs34.existsSync)(p),
25997
26103
  execGit: async (args) => (await execFileP2("git", args, { timeout: GIT_TIMEOUT_MS })).stdout,
25998
26104
  teardownWorktreeStage,
25999
26105
  deferredStore,
@@ -26021,7 +26127,7 @@ async function applyGcPlan(plan, remote, opts = {}) {
26021
26127
  let removalAttempted = false;
26022
26128
  try {
26023
26129
  const cleanupTarget = resolveSafeSiblingWorktreeCleanupTarget(wt.path, siblingRoot, {
26024
- realpath: (path2) => (0, import_node_fs33.realpathSync)(path2)
26130
+ realpath: (path2) => (0, import_node_fs34.realpathSync)(path2)
26025
26131
  });
26026
26132
  if (!cleanupTarget.ok) {
26027
26133
  result.failed.push(`${wt.path}: ${cleanupTarget.reason}`);
@@ -26107,13 +26213,13 @@ async function composeOverrideBodyFile(prNumber, repoArgs, gh) {
26107
26213
  const commits = JSON.parse(raw).commits ?? [];
26108
26214
  const body = squashBodyWithOverride(commits.map((c) => ({ headline: c.messageHeadline ?? "", body: c.messageBody ?? "" })), process.cwd());
26109
26215
  if (!body) return void 0;
26110
- const dir = (0, import_node_fs33.mkdtempSync)((0, import_node_path32.join)((0, import_node_os12.tmpdir)(), "mmi-squash-body-"));
26111
- const path2 = (0, import_node_path32.join)(dir, "body.txt");
26112
- (0, import_node_fs33.writeFileSync)(path2, `${body}
26216
+ const dir = (0, import_node_fs34.mkdtempSync)((0, import_node_path33.join)((0, import_node_os12.tmpdir)(), "mmi-squash-body-"));
26217
+ const path2 = (0, import_node_path33.join)(dir, "body.txt");
26218
+ (0, import_node_fs34.writeFileSync)(path2, `${body}
26113
26219
  `, "utf8");
26114
26220
  return { path: path2, cleanup: () => {
26115
26221
  try {
26116
- (0, import_node_fs33.rmSync)(dir, { recursive: true, force: true });
26222
+ (0, import_node_fs34.rmSync)(dir, { recursive: true, force: true });
26117
26223
  } catch {
26118
26224
  }
26119
26225
  } };
@@ -26235,13 +26341,13 @@ var realWorktreeDirRemover = {
26235
26341
  probe: (p) => {
26236
26342
  let st;
26237
26343
  try {
26238
- st = (0, import_node_fs33.lstatSync)(p);
26344
+ st = (0, import_node_fs34.lstatSync)(p);
26239
26345
  } catch {
26240
26346
  return null;
26241
26347
  }
26242
26348
  if (st.isSymbolicLink()) return "link";
26243
26349
  try {
26244
- (0, import_node_fs33.readlinkSync)(p);
26350
+ (0, import_node_fs34.readlinkSync)(p);
26245
26351
  return "link";
26246
26352
  } catch {
26247
26353
  }
@@ -26249,7 +26355,7 @@ var realWorktreeDirRemover = {
26249
26355
  },
26250
26356
  readdir: (p) => {
26251
26357
  try {
26252
- return (0, import_node_fs33.readdirSync)(p);
26358
+ return (0, import_node_fs34.readdirSync)(p);
26253
26359
  } catch {
26254
26360
  return [];
26255
26361
  }
@@ -26258,9 +26364,9 @@ var realWorktreeDirRemover = {
26258
26364
  // leaving the target); a file symlink with unlink. rmdir first, fall back to unlink.
26259
26365
  detachLink: (p) => {
26260
26366
  try {
26261
- (0, import_node_fs33.rmdirSync)(p);
26367
+ (0, import_node_fs34.rmdirSync)(p);
26262
26368
  } catch {
26263
- (0, import_node_fs33.unlinkSync)(p);
26369
+ (0, import_node_fs34.unlinkSync)(p);
26264
26370
  }
26265
26371
  },
26266
26372
  removeTree: (p) => (0, import_promises8.rm)(p, { recursive: true, force: true, maxRetries: 5, retryDelay: 200 })
@@ -26293,9 +26399,9 @@ async function worktreeHasStageState(worktreePath) {
26293
26399
  }
26294
26400
  }
26295
26401
  function stageStateFileBelongsToWorktree(statePath, worktreePath) {
26296
- if (!(0, import_node_fs33.existsSync)(statePath)) return false;
26402
+ if (!(0, import_node_fs34.existsSync)(statePath)) return false;
26297
26403
  try {
26298
- const state = JSON.parse((0, import_node_fs33.readFileSync)(statePath, "utf8"));
26404
+ const state = JSON.parse((0, import_node_fs34.readFileSync)(statePath, "utf8"));
26299
26405
  const recordedCwd = typeof state.identity?.cwd === "string" ? state.identity.cwd : typeof state.cwd === "string" ? state.cwd : "";
26300
26406
  return Boolean(recordedCwd && isPathUnderDirectory(recordedCwd, worktreePath));
26301
26407
  } catch {
@@ -26626,9 +26732,9 @@ async function checkDocsIndexAtHead(opts, deps) {
26626
26732
  }
26627
26733
 
26628
26734
  // src/worktree-lifecycle-commands.ts
26629
- var import_node_fs34 = require("node:fs");
26735
+ var import_node_fs35 = require("node:fs");
26630
26736
  var import_promises9 = require("node:fs/promises");
26631
- var import_node_path33 = require("node:path");
26737
+ var import_node_path34 = require("node:path");
26632
26738
  var GH_TIMEOUT_MS = 2e4;
26633
26739
  var DEFAULT_BASE = "origin/development";
26634
26740
  var DEFAULT_REMOTE = "origin";
@@ -26774,7 +26880,7 @@ function classifyStaleLeaks(input) {
26774
26880
  var defaultOrphanDirScanDeps = {
26775
26881
  listDirs: (root) => {
26776
26882
  try {
26777
- return (0, import_node_fs34.readdirSync)(root, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => (0, import_node_path33.join)(root, e.name));
26883
+ return (0, import_node_fs35.readdirSync)(root, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => (0, import_node_path34.join)(root, e.name));
26778
26884
  } catch {
26779
26885
  return [];
26780
26886
  }
@@ -26927,13 +27033,13 @@ function registerWorktreeCommands(program3) {
26927
27033
  const detached = headBorn && !symbolicBranch;
26928
27034
  const branch = symbolicBranch || (detached ? "HEAD" : "");
26929
27035
  if (!wtPath || !branch) return fail("worktree land: not inside a git worktree");
26930
- const gitFile = (0, import_node_path33.join)(wtPath, ".git");
26931
- const isLinked = (0, import_node_fs34.existsSync)(gitFile) && (0, import_node_fs34.statSync)(gitFile).isFile();
27036
+ const gitFile = (0, import_node_path34.join)(wtPath, ".git");
27037
+ const isLinked = (0, import_node_fs35.existsSync)(gitFile) && (0, import_node_fs35.statSync)(gitFile).isFile();
26932
27038
  if (apply && !isLinked) {
26933
27039
  return fail("worktree land: run from inside the linked worktree you want to land (this is the primary checkout)");
26934
27040
  }
26935
27041
  const commonDir = (await execFileP2("git", ["rev-parse", "--git-common-dir"], { timeout: GIT_TIMEOUT_MS }).catch(() => ({ stdout: "" }))).stdout.trim();
26936
- const primaryCheckout = commonDir ? (0, import_node_path33.dirname)(commonDir) : wtPath;
27042
+ const primaryCheckout = commonDir ? (0, import_node_path34.dirname)(commonDir) : wtPath;
26937
27043
  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);
26938
27044
  const orphan = classifyOrphanedWorktree({
26939
27045
  branch,
@@ -27144,10 +27250,10 @@ async function gatherWorktreeContext() {
27144
27250
  if (s) stages.push({ path: wt.path, port: s.port });
27145
27251
  }
27146
27252
  const worktreeGitRoot = await currentRepoWorktreeGitRoot(repoRoot2);
27147
- const primaryRepoRoot = worktreeGitRoot ? (0, import_node_path33.dirname)((0, import_node_path33.dirname)(worktreeGitRoot)) : repoRoot2;
27253
+ const primaryRepoRoot = worktreeGitRoot ? (0, import_node_path34.dirname)((0, import_node_path34.dirname)(worktreeGitRoot)) : repoRoot2;
27148
27254
  const wtRoot = siblingMmiWorktreesRoot(primaryRepoRoot);
27149
27255
  let orphanDirs = [];
27150
- if ((0, import_node_fs34.existsSync)(wtRoot)) {
27256
+ if ((0, import_node_fs35.existsSync)(wtRoot)) {
27151
27257
  orphanDirs = scanOrphanDirs(wtRoot, worktreeGitRoot, {
27152
27258
  ...defaultOrphanDirScanDeps,
27153
27259
  listDirs: (root) => worktreeScanDirs(root, primaryRepoRoot, defaultOrphanDirScanDeps.listDirs, isRepoCheckoutDir)
@@ -27173,7 +27279,7 @@ ${err.stderr ?? ""}`;
27173
27279
  }
27174
27280
 
27175
27281
  // src/issue-commands.ts
27176
- var import_node_fs35 = require("node:fs");
27282
+ var import_node_fs36 = require("node:fs");
27177
27283
  var import_node_crypto6 = require("node:crypto");
27178
27284
  var ghRunner = async (args, timeoutMs) => (await execFileP2("gh", args, { timeout: timeoutMs })).stdout;
27179
27285
  var ReparentConflictError = class extends Error {
@@ -27191,7 +27297,7 @@ async function editIssue(client, options, deps = {}) {
27191
27297
  const url = `https://github.com/${repo}/issues/${parsed.number}`;
27192
27298
  const patch = {};
27193
27299
  let bodyChanged = false;
27194
- const textDeps = () => deps.textDeps ?? { readFile: (p, e) => Promise.resolve((0, import_node_fs35.readFileSync)(p, e)), readStdin: () => Promise.resolve("") };
27300
+ const textDeps = () => deps.textDeps ?? { readFile: (p, e) => Promise.resolve((0, import_node_fs36.readFileSync)(p, e)), readStdin: () => Promise.resolve("") };
27195
27301
  if (options.titleFile !== void 0) {
27196
27302
  patch.title = await resolveIssueTitle({ title: options.title, titleFile: options.titleFile }, textDeps());
27197
27303
  } else if (options.title !== void 0) {
@@ -27754,7 +27860,7 @@ function extendCreateCommand(issue2, batchAttach) {
27754
27860
  if (opts.batch) {
27755
27861
  let specs;
27756
27862
  try {
27757
- const raw = (0, import_node_fs35.readFileSync)(opts.batch, "utf8");
27863
+ const raw = (0, import_node_fs36.readFileSync)(opts.batch, "utf8");
27758
27864
  specs = JSON.parse(raw);
27759
27865
  if (!Array.isArray(specs)) throw new Error("batch file must contain a JSON array");
27760
27866
  } catch (e) {
@@ -27828,8 +27934,8 @@ ${lines}`, {
27828
27934
  }
27829
27935
 
27830
27936
  // src/train-commands.ts
27831
- var import_node_fs36 = require("node:fs");
27832
- var import_node_path34 = require("node:path");
27937
+ var import_node_fs37 = require("node:fs");
27938
+ var import_node_path35 = require("node:path");
27833
27939
 
27834
27940
  // src/train-status.ts
27835
27941
  function buildTrainStatusReport(input) {
@@ -27869,7 +27975,7 @@ function formatTrainStatus(r) {
27869
27975
  // src/train-commands.ts
27870
27976
  function readRepoVersion() {
27871
27977
  try {
27872
- return JSON.parse((0, import_node_fs36.readFileSync)((0, import_node_path34.join)(process.cwd(), ".claude-plugin", "plugin.json"), "utf8")).version || void 0;
27978
+ return JSON.parse((0, import_node_fs37.readFileSync)((0, import_node_path35.join)(process.cwd(), ".claude-plugin", "plugin.json"), "utf8")).version || void 0;
27873
27979
  } catch {
27874
27980
  return void 0;
27875
27981
  }
@@ -28015,9 +28121,9 @@ function registerDeployCommands(program3) {
28015
28121
  }
28016
28122
 
28017
28123
  // src/discovery-commands.ts
28018
- var import_node_fs37 = require("node:fs");
28124
+ var import_node_fs38 = require("node:fs");
28019
28125
  var import_node_os13 = require("node:os");
28020
- var import_node_path35 = require("node:path");
28126
+ var import_node_path36 = require("node:path");
28021
28127
  var GC_GH_TIMEOUT_MS3 = 2e4;
28022
28128
  async function collectStatus() {
28023
28129
  const repo = await resolveRepo();
@@ -28207,8 +28313,8 @@ async function collectOnboardStatus(opts = {}) {
28207
28313
  }
28208
28314
  const home = (0, import_node_os13.homedir)();
28209
28315
  const plugin = onboardPluginGate({
28210
- readKnown: () => readFileSyncSafe((0, import_node_path35.join)(home, ...KNOWN_MARKETPLACES_RELATIVE), import_node_fs37.readFileSync),
28211
- readSettings: () => readFileSyncSafe((0, import_node_path35.join)(home, ".claude", "settings.json"), import_node_fs37.readFileSync)
28316
+ readKnown: () => readFileSyncSafe((0, import_node_path36.join)(home, ...KNOWN_MARKETPLACES_RELATIVE), import_node_fs38.readFileSync),
28317
+ readSettings: () => readFileSyncSafe((0, import_node_path36.join)(home, ".claude", "settings.json"), import_node_fs38.readFileSync)
28212
28318
  });
28213
28319
  return { track, board, registry: registry2, secrets, plugin, estateCli, nextCommand };
28214
28320
  }
@@ -29985,6 +30091,10 @@ function surfaceRestartAction(descriptor) {
29985
30091
  }
29986
30092
 
29987
30093
  // src/doctor-clean.ts
30094
+ function doctorSnapshotFingerprint(checks) {
30095
+ const rows = [...checks].sort((a, b) => `${a.id ?? a.label}`.localeCompare(`${b.id ?? b.label}`)).map((c) => [c.id ?? c.label, c.ok, c.verified ?? null, c.detail ?? null]);
30096
+ return JSON.stringify(rows);
30097
+ }
29988
30098
  function checkGithubAuth(probe) {
29989
30099
  const login = probe.login?.trim();
29990
30100
  const authed = Boolean(login);
@@ -30351,7 +30461,7 @@ function gcReapable(plan) {
30351
30461
  }
30352
30462
  async function runDoctorClean(opts, io, deps) {
30353
30463
  const full = !opts.fast && !opts.banner && !opts.preflight;
30354
- const applyEnv = full || Boolean(opts.preflight);
30464
+ const applyEnv = full;
30355
30465
  const applyRepo = full && opts.repoWrites !== false;
30356
30466
  const lane = {
30357
30467
  banner: Boolean(opts.banner),
@@ -30378,11 +30488,14 @@ async function runDoctorClean(opts, io, deps) {
30378
30488
  const checks = [];
30379
30489
  let restartPending = false;
30380
30490
  const streamed = /* @__PURE__ */ new Set();
30491
+ const streamedLines = /* @__PURE__ */ new Set();
30492
+ const alreadyShown = (c) => streamed.has(c) || streamedLines.has(renderCheckLine(c));
30381
30493
  const worthPrinting = (c) => Boolean(opts.verbose) || !c.ok || Boolean(c.warn);
30382
30494
  const emitNow = (check) => {
30383
30495
  checks.push(check);
30384
- if (opts.json || !worthPrinting(check)) return;
30496
+ if (opts.json || !streamingPass || !worthPrinting(check)) return;
30385
30497
  streamed.add(check);
30498
+ streamedLines.add(renderCheckLine(check));
30386
30499
  io.log(renderCheckLine(check));
30387
30500
  if (opts.verbose) for (const evidence of check.verbose ?? []) io.log(`${VERBOSE_INDENT}${evidence}`);
30388
30501
  };
@@ -30394,13 +30507,35 @@ async function runDoctorClean(opts, io, deps) {
30394
30507
  };
30395
30508
  const releasedNote = deps.releasedVersionNote?.();
30396
30509
  let pluginHealed = false;
30510
+ const spentHeals = /* @__PURE__ */ new Set();
30511
+ let healChangedThisPass = false;
30512
+ const markHealChanged = () => {
30513
+ healChangedThisPass = true;
30514
+ };
30515
+ const traceHeal = (kind) => opts.healTrace?.(kind);
30516
+ const spendOnce = (kind) => {
30517
+ if (spentHeals.has(kind)) return false;
30518
+ spentHeals.add(kind);
30519
+ return true;
30520
+ };
30521
+ const spentRows = /* @__PURE__ */ new Map();
30397
30522
  async function runPluginRow() {
30398
30523
  if (!registryEvidence) return;
30524
+ const spentRow = spentRows.get("plugin-chain");
30525
+ if (spentRow) {
30526
+ emitNow(spentRow);
30527
+ if (registryEvidence.descriptor.trustOwner === "operator" && (registryEvidence.guardState === "healthy" || pluginHealed)) {
30528
+ const trust = checkCodexHookTrust(deps.pluginTrustState?.(), registryEvidence.descriptor.displayName);
30529
+ if (trust) emitNow(trust);
30530
+ }
30531
+ return;
30532
+ }
30399
30533
  const diagnosis = diagnoseSurface({ ...registryEvidence, releasedVersion: released });
30400
30534
  const repair = planSurfaceRepair(diagnosis);
30401
- if (applyEnv && deps.healPlugin && repair?.supported) {
30535
+ if (applyEnv && deps.healPlugin && repair?.supported && spendOnce("plugin-chain")) {
30402
30536
  const { descriptor } = registryEvidence;
30403
30537
  healIntent(`${descriptor.displayName} plugin \u2014 healing via ${descriptor.installMechanism} (${descriptor.installLocator})`);
30538
+ traceHeal("plugin-chain");
30404
30539
  const heal = await deps.healPlugin(healStep);
30405
30540
  pluginHealed = heal.ok;
30406
30541
  const row = buildSurfaceDoctorCheck(diagnoseSurface({
@@ -30413,7 +30548,12 @@ async function runDoctorClean(opts, io, deps) {
30413
30548
  row.fix = `${heal.detail} \u2014 another doctor on this machine is healing it now; re-run once it finishes`;
30414
30549
  }
30415
30550
  emitNow(row);
30551
+ if (heal.skipped) spentHeals.delete("plugin-chain");
30416
30552
  if (!heal.skipped) restartPending = true;
30553
+ if (heal.ok && !heal.skipped) {
30554
+ markHealChanged();
30555
+ spentRows.set("plugin-chain", row);
30556
+ }
30417
30557
  } else if (diagnosis.state !== "skipped") {
30418
30558
  const row = buildSurfaceDoctorCheck(diagnosis);
30419
30559
  emitNow(row);
@@ -30425,13 +30565,18 @@ async function runDoctorClean(opts, io, deps) {
30425
30565
  }
30426
30566
  }
30427
30567
  async function runCliRow() {
30568
+ const spentRow = spentRows.get("cli-self-update");
30569
+ if (spentRow) {
30570
+ emitNow(spentRow);
30571
+ return;
30572
+ }
30428
30573
  const missing = deps.missingCliCommands?.() ?? [];
30429
30574
  const cliInput = { currentVersion: deps.currentCliVersion(), releasedVersion: released };
30430
30575
  const cliReport = buildVersionLagReport(cliInput);
30431
30576
  const capabilityGap = missing.length > 0 && Boolean(cliReport.releasedVersion);
30432
30577
  const shouldUpdate = Boolean(
30433
30578
  applyEnv && deps.updateCli && (versionAutoUpdateAction(cliReport) === "npm" || capabilityGap)
30434
- );
30579
+ ) && spendOnce("cli-self-update");
30435
30580
  if (!shouldUpdate) {
30436
30581
  const cli = checkCliVersion(cliInput, releasedNote);
30437
30582
  if (cli) {
@@ -30461,29 +30606,40 @@ async function runDoctorClean(opts, io, deps) {
30461
30606
  const target = cliReport.releasedVersion;
30462
30607
  const intent = capabilityGap && cliReport.ok ? `mmi-cli \u2014 self-updating to ${target} via npm install -g (missing commands: ${missing.join(", ")})` : `mmi-cli \u2014 self-updating ${cliReport.currentVersion} \u2192 ${target} via npm install -g`;
30463
30608
  healIntent(intent);
30609
+ traceHeal("cli-self-update");
30464
30610
  const heal = await deps.updateCli(target, healStep);
30611
+ if (heal.skipped) spentHeals.delete("cli-self-update");
30612
+ if (heal.ok && !heal.skipped) markHealChanged();
30465
30613
  const healEvidence = [
30466
30614
  `running: ${cliReport.currentVersion}`,
30467
30615
  `published: ${target ?? "(unknown)"}`,
30468
30616
  `heal: ${heal.detail}`,
30469
30617
  ...missing.length ? [`missing commands before heal: ${missing.join(", ")}`] : []
30470
30618
  ];
30471
- emitNow(heal.ok ? {
30472
- id: "cli-version",
30473
- ok: true,
30474
- label: "mmi-cli",
30475
- detail: capabilityGap && cliReport.ok ? `${cliReport.currentVersion} \u2192 ${target} \u2014 self-updated via npm install -g (was missing: ${missing.join(", ")}); the next mmi-cli invocation runs the new version` : `${cliReport.currentVersion} \u2192 ${target} \u2014 self-updated via npm install -g; the next mmi-cli invocation runs the new version`,
30476
- verbose: healEvidence
30477
- } : {
30478
- id: "cli-version",
30479
- ok: false,
30480
- label: "mmi-cli",
30481
- detail: capabilityGap && cliReport.ok ? `missing commands: ${missing.join(", ")}` : `${cliReport.currentVersion} \u2192 ${target}`,
30482
- // #3489: same split as the plugin heal above — a lock-contention skip is not this run's gap.
30483
- ...heal.skipped ? { reportOnly: true } : {},
30484
- 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(target)}\``,
30485
- verbose: healEvidence
30486
- });
30619
+ if (heal.ok) {
30620
+ const healedRow = {
30621
+ id: "cli-version",
30622
+ ok: true,
30623
+ label: "mmi-cli",
30624
+ detail: capabilityGap && cliReport.ok ? `${cliReport.currentVersion} \u2192 ${target} \u2014 self-updated via npm install -g (was missing: ${missing.join(", ")}); the next mmi-cli invocation runs the new version` : `${cliReport.currentVersion} \u2192 ${target} \u2014 self-updated via npm install -g; the next mmi-cli invocation runs the new version`,
30625
+ verbose: healEvidence
30626
+ };
30627
+ spentRows.set("cli-self-update", healedRow);
30628
+ emitNow(healedRow);
30629
+ return;
30630
+ }
30631
+ emitNow(
30632
+ {
30633
+ id: "cli-version",
30634
+ ok: false,
30635
+ label: "mmi-cli",
30636
+ detail: capabilityGap && cliReport.ok ? `missing commands: ${missing.join(", ")}` : `${cliReport.currentVersion} \u2192 ${target}`,
30637
+ // #3489: same split as the plugin heal above — a lock-contention skip is not this run's gap.
30638
+ ...heal.skipped ? { reportOnly: true } : {},
30639
+ 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(target)}\``,
30640
+ verbose: healEvidence
30641
+ }
30642
+ );
30487
30643
  }
30488
30644
  async function runGithubAuthRow() {
30489
30645
  emitNow(checkGithubAuth({ login, ghInstalled: ghInstalled2, reach }));
@@ -30505,6 +30661,8 @@ async function runDoctorClean(opts, io, deps) {
30505
30661
  if (gi.ok) {
30506
30662
  emitNow({ ok: true, id: "gitignore-block", label: "gitignore block", verbose: giEvidence });
30507
30663
  } else if (applyRepo && gi.content && deps.writeGitignore(gi.content)) {
30664
+ traceHeal("repo-cleans");
30665
+ markHealChanged();
30508
30666
  emitNow({ ok: true, id: "gitignore-block", label: "gitignore block", detail: "rewrote managed block", verbose: giEvidence });
30509
30667
  restartPending = true;
30510
30668
  } else {
@@ -30512,8 +30670,100 @@ async function runDoctorClean(opts, io, deps) {
30512
30670
  }
30513
30671
  }
30514
30672
  async function runPluginCacheRow() {
30673
+ if (applyEnv && deps.prunePluginCache && spendOnce("cache-prune")) {
30674
+ const cache = deps.pluginCache();
30675
+ if (cache.stale.length === 0 && cache.staging.length === 0) {
30676
+ emitNow(checkPluginCache(cache));
30677
+ return;
30678
+ }
30679
+ healIntent(`plugin cache \u2014 pruning ${cache.stale.length} superseded version(s) (guarded)`);
30680
+ traceHeal("cache-prune");
30681
+ const outcome = deps.prunePluginCache();
30682
+ if (outcome.removed.length) markHealChanged();
30683
+ const evidence = [
30684
+ ...outcome.removed.map((v) => `pruned: ${v}`),
30685
+ ...outcome.held.map((h) => `still held: ${h.version} (${h.error}) \u2014 never forced`),
30686
+ ...outcome.kept.map((k) => `kept: ${k.version} \u2014 ${k.reason}`)
30687
+ ];
30688
+ if (outcome.held.length === 0) {
30689
+ emitNow({
30690
+ id: "plugin-cache",
30691
+ ok: true,
30692
+ label: "plugin cache",
30693
+ detail: outcome.removed.length ? `pruned ${outcome.removed.length} superseded version(s)` : "nothing provably superseded (guards kept every candidate)",
30694
+ verbose: evidence
30695
+ });
30696
+ } else {
30697
+ emitNow({
30698
+ id: "plugin-cache",
30699
+ ok: false,
30700
+ reportOnly: true,
30701
+ label: "plugin cache",
30702
+ detail: `${outcome.removed.length} pruned; ${outcome.held.length} still held by a live session`,
30703
+ fix: "close the session holding it and re-run `mmi-cli doctor`",
30704
+ verbose: evidence
30705
+ });
30706
+ }
30707
+ return;
30708
+ }
30515
30709
  emitNow(checkPluginCache(deps.pluginCache()));
30516
30710
  }
30711
+ async function runPiPluginRow() {
30712
+ const state = deps.piPluginState?.();
30713
+ if (!state) return;
30714
+ if (!state.settingsReadable) {
30715
+ emitNow({
30716
+ id: "pi-plugin",
30717
+ ok: false,
30718
+ reportOnly: true,
30719
+ label: "pi plugin",
30720
+ detail: "settings.json unreadable",
30721
+ fix: "repair ~/.pi/agent/settings.json, then re-run doctor",
30722
+ verbose: ["~/.pi/agent exists but settings.json could not be parsed \u2014 fail closed, nothing written"]
30723
+ });
30724
+ return;
30725
+ }
30726
+ const current = state.registeredPath === state.expectedPath;
30727
+ if (current) {
30728
+ emitNow({
30729
+ id: "pi-plugin",
30730
+ ok: true,
30731
+ label: "pi plugin",
30732
+ detail: "package registered in settings.json",
30733
+ verbose: [`registered: ${state.registeredPath}`]
30734
+ });
30735
+ return;
30736
+ }
30737
+ if (applyEnv && deps.healPiPlugin) {
30738
+ healIntent(`pi plugin \u2014 registering ${state.expectedPath}`);
30739
+ traceHeal("env-heals");
30740
+ const heal = deps.healPiPlugin();
30741
+ if (heal.ok) markHealChanged();
30742
+ emitNow(heal.ok ? {
30743
+ id: "pi-plugin",
30744
+ ok: true,
30745
+ label: "pi plugin",
30746
+ detail: state.registeredPath ? "replaced stale package path" : "registered package in settings.json",
30747
+ verbose: [`was: ${state.registeredPath ?? "(absent)"}`, `now: ${state.expectedPath}`, `heal: ${heal.detail}`]
30748
+ } : {
30749
+ id: "pi-plugin",
30750
+ ok: false,
30751
+ label: "pi plugin",
30752
+ detail: state.registeredPath ? "stale package path" : "package not registered",
30753
+ fix: `heal failed (${heal.detail}) \u2014 add ${state.expectedPath} to packages[] in ~/.pi/agent/settings.json`,
30754
+ verbose: [`expected: ${state.expectedPath}`]
30755
+ });
30756
+ return;
30757
+ }
30758
+ emitNow({
30759
+ id: "pi-plugin",
30760
+ ok: false,
30761
+ label: "pi plugin",
30762
+ detail: state.registeredPath ? "stale package path" : "package not registered",
30763
+ fix: "run `mmi-cli doctor` to register the mmi .pi-plugin in ~/.pi/agent/settings.json",
30764
+ verbose: [`expected: ${state.expectedPath}`, `registered: ${state.registeredPath ?? "(absent)"}`]
30765
+ });
30766
+ }
30517
30767
  async function runSessionPayloadRow() {
30518
30768
  const payload = checkSessionPayload(deps.sessionPayload());
30519
30769
  if (payload) emitNow(payload);
@@ -30523,7 +30773,11 @@ async function runDoctorClean(opts, io, deps) {
30523
30773
  const healed = deps.healMarketplacePins();
30524
30774
  if (healed) {
30525
30775
  healIntent(`marketplace pins \u2014 ${healed.detail}`);
30526
- if (healed.wrote) restartPending = true;
30776
+ if (healed.wrote) {
30777
+ traceHeal("env-heals");
30778
+ markHealChanged();
30779
+ restartPending = true;
30780
+ }
30527
30781
  }
30528
30782
  }
30529
30783
  for (const row of deps.marketplaceRows()) emitNow(row);
@@ -30566,6 +30820,8 @@ async function runDoctorClean(opts, io, deps) {
30566
30820
  let healedWrite = false;
30567
30821
  if (applyRepo && probe?.drift && deps.healDocsIndex) {
30568
30822
  healIntent("docs index \u2014 regenerating docs/index.md");
30823
+ traceHeal("repo-cleans");
30824
+ markHealChanged();
30569
30825
  try {
30570
30826
  probe = deps.healDocsIndex(root);
30571
30827
  healedWrite = !probe.drift;
@@ -30786,7 +31042,11 @@ async function runDoctorClean(opts, io, deps) {
30786
31042
  `skipped: ${sweep.skipped}`
30787
31043
  ]
30788
31044
  });
30789
- if (sweep.removed.length) restartPending = true;
31045
+ if (sweep.removed.length) {
31046
+ traceHeal("repo-cleans");
31047
+ markHealChanged();
31048
+ restartPending = true;
31049
+ }
30790
31050
  } catch (e) {
30791
31051
  const message = e instanceof Error ? e.message : String(e);
30792
31052
  emitNow({
@@ -30824,7 +31084,11 @@ async function runDoctorClean(opts, io, deps) {
30824
31084
  ...r.failed.map((f) => `FAILED: ${f}`)
30825
31085
  ] : ["nothing reapable"]
30826
31086
  });
30827
- if (reaped) restartPending = true;
31087
+ if (reaped) {
31088
+ traceHeal("repo-cleans");
31089
+ markHealChanged();
31090
+ restartPending = true;
31091
+ }
30828
31092
  } else {
30829
31093
  const n = gcReapable(plan);
30830
31094
  const gcEvidence = [
@@ -30851,36 +31115,62 @@ async function runDoctorClean(opts, io, deps) {
30851
31115
  const applied = deps.executeScratchGc(repoRoot2, { apply: true });
30852
31116
  const pruned = applied.applied?.pruned.length ?? 0;
30853
31117
  emitNow({ id: "scratch", ok: true, label: "scratch", detail: pruned ? `removed ${pruned} aged item(s)` : "nothing stale", verbose: scratchEvidence });
30854
- if (pruned) restartPending = true;
31118
+ if (pruned) {
31119
+ traceHeal("repo-cleans");
31120
+ markHealChanged();
31121
+ restartPending = true;
31122
+ }
30855
31123
  } else {
30856
31124
  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` (without --no-repo-writes)", verbose: scratchEvidence });
30857
31125
  }
30858
31126
  }
30859
31127
  const prefix = [
30860
- { id: "plugin", when: true, run: runPluginRow },
30861
31128
  { id: "cli-version", when: true, run: runCliRow },
31129
+ { id: "plugin", when: true, run: runPluginRow },
31130
+ { id: "plugin-cache", when: true, run: runPluginCacheRow },
30862
31131
  { id: "github-auth", when: true, run: runGithubAuthRow },
30863
31132
  { id: "aws-identity", when: true, run: runAwsRow },
30864
31133
  { id: "repo-worktrees", when: true, run: runRepoWorktreesRow },
30865
- { id: "gitignore-block", when: isOrgRepo, run: runGitignoreRow },
30866
- { id: "plugin-cache", when: true, run: runPluginCacheRow },
30867
31134
  { id: "sessionstart-payload", when: true, run: runSessionPayloadRow },
30868
- { id: "marketplace", when: true, run: runMarketplaceRows }
30869
- ];
30870
- for (const entry of prefix) if (entry.when) await entry.run();
30871
- const parallel = [
30872
- { id: "docs-index", when: isOrgRepo && lane.full, run: runDocsIndexRow },
30873
- // Hub cloud probe (short timeout). Full lane + `--self`; `--preflight` may emit command-absent only (#4156).
30874
- { id: "repo-index", when: isOrgRepo && (lane.full || Boolean(opts.self) || lane.preflight), run: runRepoIndexRow },
30875
- { id: "board-doctor", when: isOrgRepo && lane.full, run: runBoardDoctorRow },
30876
- { id: "schedules-drift", when: isOrgRepo && lane.full, run: runSchedulesDriftRow }
30877
- ];
30878
- await Promise.all(parallel.filter((entry) => entry.when).map((entry) => entry.run()));
30879
- const suffix = [
30880
- { id: "train-branches", when: isOrgRepo && lane.full, run: runTrainSyncRow },
30881
- { id: "housekeeper", when: isOrgRepo && lane.full, run: runHousekeeperRows }
31135
+ { id: "marketplace", when: true, run: runMarketplaceRows },
31136
+ { id: "pi-plugin", when: true, run: runPiPluginRow },
31137
+ { id: "gitignore-block", when: isOrgRepo, run: runGitignoreRow }
30882
31138
  ];
30883
- for (const entry of suffix) if (entry.when) await entry.run();
31139
+ const maxPasses = 3;
31140
+ let passes = 0;
31141
+ let prevFingerprint;
31142
+ let streamingPass = true;
31143
+ for (; ; ) {
31144
+ passes += 1;
31145
+ opts.onPass?.(passes);
31146
+ checks.length = 0;
31147
+ healChangedThisPass = false;
31148
+ for (const entry of prefix) if (entry.when) await entry.run();
31149
+ const parallelRows = [
31150
+ { id: "docs-index", when: isOrgRepo && lane.full, run: runDocsIndexRow },
31151
+ // Hub cloud probe (short timeout). Full lane + `--self`; `--preflight` may emit command-absent only (#4156).
31152
+ { id: "repo-index", when: isOrgRepo && (lane.full || Boolean(opts.self) || lane.preflight), run: runRepoIndexRow },
31153
+ { id: "board-doctor", when: isOrgRepo && lane.full, run: runBoardDoctorRow },
31154
+ { id: "schedules-drift", when: isOrgRepo && lane.full, run: runSchedulesDriftRow }
31155
+ ];
31156
+ await Promise.all(parallelRows.filter((entry) => entry.when).map((entry) => entry.run()));
31157
+ const suffix = [
31158
+ { id: "train-branches", when: isOrgRepo && lane.full, run: runTrainSyncRow },
31159
+ { id: "housekeeper", when: isOrgRepo && lane.full, run: runHousekeeperRows }
31160
+ ];
31161
+ for (const entry of suffix) if (entry.when) await entry.run();
31162
+ if (!lane.full) break;
31163
+ const fingerprint = doctorSnapshotFingerprint(checks);
31164
+ const stillActionable = checks.some((c) => !c.ok && !c.reportOnly);
31165
+ if (!healChangedThisPass || !stillActionable || fingerprint === prevFingerprint) break;
31166
+ if (passes >= maxPasses) {
31167
+ healIntent(`convergence \u2014 pass bound (${maxPasses}) reached with rows still moving; re-run doctor to continue`);
31168
+ break;
31169
+ }
31170
+ prevFingerprint = fingerprint;
31171
+ streamingPass = false;
31172
+ healIntent(`convergence \u2014 pass ${passes} healed; re-measuring (pass ${passes + 1}/${maxPasses})`);
31173
+ }
30884
31174
  const exitCode = doctorReportExitCode(checks);
30885
31175
  if (opts.json) {
30886
31176
  const payload = opts.verbose ? checks : checks.map(({ verbose: _evidence, ...check }) => check);
@@ -30893,7 +31183,7 @@ async function runDoctorClean(opts, io, deps) {
30893
31183
  return exitCode;
30894
31184
  }
30895
31185
  if (opts.banner) {
30896
- const actionable = checks.filter((c) => (!c.ok || c.warn) && !streamed.has(c));
31186
+ const actionable = checks.filter((c) => (!c.ok || c.warn) && !alreadyShown(c));
30897
31187
  for (const c of actionable) {
30898
31188
  io.log(renderReport([c], { restartPending: false }));
30899
31189
  if (opts.verbose) for (const evidence of c.verbose ?? []) io.log(`${VERBOSE_INDENT}${evidence}`);
@@ -30901,7 +31191,7 @@ async function runDoctorClean(opts, io, deps) {
30901
31191
  if (restartPending) io.log(`\u21BB ${restartAction.charAt(0).toUpperCase()}${restartAction.slice(1)} to finish.`);
30902
31192
  return 0;
30903
31193
  }
30904
- const rest = checks.filter((c) => !streamed.has(c));
31194
+ const rest = checks.filter((c) => !alreadyShown(c));
30905
31195
  const shown = opts.verbose ? rest : rest.filter((c) => !c.ok || c.warn);
30906
31196
  const lines = [];
30907
31197
  for (const check of shown) {
@@ -30933,17 +31223,17 @@ function parseOriginRepo(remoteUrl) {
30933
31223
  }
30934
31224
  function ghHostsConfigPath(env, platform2) {
30935
31225
  const sep3 = platform2 === "win32" ? "\\" : "/";
30936
- const join33 = (...parts) => parts.join(sep3);
31226
+ const join34 = (...parts) => parts.join(sep3);
30937
31227
  const explicit = env.GH_CONFIG_DIR?.trim();
30938
- if (explicit) return join33(explicit, "hosts.yml");
31228
+ if (explicit) return join34(explicit, "hosts.yml");
30939
31229
  if (platform2 === "win32") {
30940
31230
  const appData = (env.AppData ?? env.APPDATA)?.trim();
30941
- return appData ? join33(appData, "GitHub CLI", "hosts.yml") : void 0;
31231
+ return appData ? join34(appData, "GitHub CLI", "hosts.yml") : void 0;
30942
31232
  }
30943
31233
  const xdg = env.XDG_CONFIG_HOME?.trim();
30944
- if (xdg) return join33(xdg, "gh", "hosts.yml");
31234
+ if (xdg) return join34(xdg, "gh", "hosts.yml");
30945
31235
  const home = env.HOME?.trim();
30946
- return home ? join33(home, ".config", "gh", "hosts.yml") : void 0;
31236
+ return home ? join34(home, ".config", "gh", "hosts.yml") : void 0;
30947
31237
  }
30948
31238
  function parseGhHostsAccounts(yaml, host = "github.com") {
30949
31239
  let hostIndent = null;
@@ -30993,9 +31283,9 @@ function ghAccountCaveat(announcedLogin, accounts) {
30993
31283
  }
30994
31284
 
30995
31285
  // src/doctor-io.ts
30996
- var import_node_fs38 = require("node:fs");
31286
+ var import_node_fs39 = require("node:fs");
30997
31287
  var import_node_os14 = require("node:os");
30998
- var import_node_path36 = require("node:path");
31288
+ var import_node_path37 = require("node:path");
30999
31289
  var import_node_child_process17 = require("node:child_process");
31000
31290
  var import_node_util8 = require("node:util");
31001
31291
  var execFileP6 = (0, import_node_util8.promisify)(import_node_child_process17.execFile);
@@ -31003,7 +31293,7 @@ var MMI_PLUGIN_ID2 = "mmi@mutmutco";
31003
31293
  function installedClaudePluginVersion() {
31004
31294
  try {
31005
31295
  const file = JSON.parse(
31006
- (0, import_node_fs38.readFileSync)((0, import_node_path36.join)((0, import_node_os14.homedir)(), ".claude", "plugins", "installed_plugins.json"), "utf8")
31296
+ (0, import_node_fs39.readFileSync)((0, import_node_path37.join)((0, import_node_os14.homedir)(), ".claude", "plugins", "installed_plugins.json"), "utf8")
31007
31297
  );
31008
31298
  const versions = (file.plugins?.[MMI_PLUGIN_ID2] ?? []).map((r) => r.version).filter((v) => Boolean(v));
31009
31299
  if (versions.length === 0) return void 0;
@@ -31014,7 +31304,7 @@ function installedClaudePluginVersion() {
31014
31304
  }
31015
31305
  function manifestVersion(path2) {
31016
31306
  try {
31017
- const manifest = JSON.parse((0, import_node_fs38.readFileSync)(path2, "utf8"));
31307
+ const manifest = JSON.parse((0, import_node_fs39.readFileSync)(path2, "utf8"));
31018
31308
  return typeof manifest.version === "string" && manifest.version.trim() ? manifest.version.trim() : void 0;
31019
31309
  } catch {
31020
31310
  return void 0;
@@ -31024,22 +31314,22 @@ function installedSurfacePluginVersion(surface) {
31024
31314
  const token = surfaceToken(surface);
31025
31315
  if (token === "kilo") {
31026
31316
  try {
31027
- const stamp = (0, import_node_fs38.readFileSync)((0, import_node_path36.join)((0, import_node_os14.homedir)(), ".kilo", ".mmi-kilo-version"), "utf8").trim();
31317
+ const stamp = (0, import_node_fs39.readFileSync)((0, import_node_path37.join)((0, import_node_os14.homedir)(), ".kilo", ".mmi-kilo-version"), "utf8").trim();
31028
31318
  return stamp || void 0;
31029
31319
  } catch {
31030
31320
  return void 0;
31031
31321
  }
31032
31322
  }
31033
31323
  if (token === "cursor") {
31034
- return manifestVersion((0, import_node_path36.join)(cursorLocalPluginRoot(), ".cursor-plugin", "plugin.json"));
31324
+ return manifestVersion((0, import_node_path37.join)(cursorLocalPluginRoot(), ".cursor-plugin", "plugin.json"));
31035
31325
  }
31036
31326
  if (token === "jervcode") {
31037
31327
  const entry = mmiPiWrapperEntry();
31038
31328
  if (!entry) return void 0;
31039
- return manifestVersion((0, import_node_path36.join)(decodeURIComponent(entry.replace(/^file:\/\/\/?/, "")), "package.json"));
31329
+ return manifestVersion((0, import_node_path37.join)(decodeURIComponent(entry.replace(/^file:\/\/\/?/, "")), "package.json"));
31040
31330
  }
31041
31331
  if (token === "kimi") {
31042
- return manifestVersion((0, import_node_path36.join)(surfaceConfigRoot(surface), "plugins", "managed", "mmi", ".kimi-plugin", "plugin.json"));
31332
+ return manifestVersion((0, import_node_path37.join)(surfaceConfigRoot(surface), "plugins", "managed", "mmi", ".kimi-plugin", "plugin.json"));
31043
31333
  }
31044
31334
  if (token === "claude") return installedClaudePluginVersion();
31045
31335
  if (token !== "codex") return void 0;
@@ -31077,13 +31367,13 @@ function worktreeRootSync() {
31077
31367
  }
31078
31368
  var gitignorePath = () => {
31079
31369
  const root = worktreeRootSync();
31080
- return root === null ? null : (0, import_node_path36.join)(root, ".gitignore");
31370
+ return root === null ? null : (0, import_node_path37.join)(root, ".gitignore");
31081
31371
  };
31082
31372
  function readGitignore() {
31083
31373
  const path2 = gitignorePath();
31084
31374
  if (path2 === null) return null;
31085
31375
  try {
31086
- return (0, import_node_fs38.readFileSync)(path2, "utf8");
31376
+ return (0, import_node_fs39.readFileSync)(path2, "utf8");
31087
31377
  } catch {
31088
31378
  return null;
31089
31379
  }
@@ -31092,7 +31382,7 @@ function writeGitignore(content) {
31092
31382
  const path2 = gitignorePath();
31093
31383
  if (path2 === null) return false;
31094
31384
  try {
31095
- (0, import_node_fs38.writeFileSync)(path2, content, "utf8");
31385
+ (0, import_node_fs39.writeFileSync)(path2, content, "utf8");
31096
31386
  return true;
31097
31387
  } catch {
31098
31388
  return false;
@@ -31116,7 +31406,7 @@ async function repoRoot() {
31116
31406
  }
31117
31407
  function hasRepoLocalWorktrees() {
31118
31408
  const root = worktreeRootSync();
31119
- return root !== null && (0, import_node_fs38.existsSync)((0, import_node_path36.join)(root, ".worktrees"));
31409
+ return root !== null && (0, import_node_fs39.existsSync)((0, import_node_path37.join)(root, ".worktrees"));
31120
31410
  }
31121
31411
 
31122
31412
  // src/index.ts
@@ -31152,8 +31442,8 @@ ${r.stderr ?? ""}`).catch(() => "");
31152
31442
  function ghMultiAccountCaveat(announcedLogin) {
31153
31443
  try {
31154
31444
  const hostsPath = ghHostsConfigPath(process.env, process.platform);
31155
- if (!hostsPath || !(0, import_node_fs39.existsSync)(hostsPath)) return void 0;
31156
- return ghAccountCaveat(announcedLogin, parseGhHostsAccounts((0, import_node_fs39.readFileSync)(hostsPath, "utf8")));
31445
+ if (!hostsPath || !(0, import_node_fs40.existsSync)(hostsPath)) return void 0;
31446
+ return ghAccountCaveat(announcedLogin, parseGhHostsAccounts((0, import_node_fs40.readFileSync)(hostsPath, "utf8")));
31157
31447
  } catch {
31158
31448
  return void 0;
31159
31449
  }
@@ -31161,7 +31451,7 @@ function ghMultiAccountCaveat(announcedLogin) {
31161
31451
  var ENV_HEAL_LOCK_STALE_MS = 10 * 6e4;
31162
31452
  var ENV_HEAL_LOCK_MAX_WAIT_MS = 2 * 6e4;
31163
31453
  function envHealLockPath(home) {
31164
- return (0, import_node_path37.join)(home, ".claude", "plugins", ".mmi-env-heal.lock");
31454
+ return (0, import_node_path38.join)(home, ".claude", "plugins", ".mmi-env-heal.lock");
31165
31455
  }
31166
31456
  async function withEnvHealLock(what, run) {
31167
31457
  try {
@@ -31276,6 +31566,45 @@ function mmiDoctorDeps(opts = {}) {
31276
31566
  stagingBytes: plan.stagingBytes
31277
31567
  };
31278
31568
  },
31569
+ // #4199: guarded auto-prune (docs/doctor-contract.md § Guarded cache prune). The plan is rebuilt
31570
+ // HERE, from live evidence, immediately before the delete — plan is the revalidation. Guards live in
31571
+ // `selectPrunablePluginVersions` (never running/newest/installed; unreadable evidence keeps the dir,
31572
+ // named). No `force`: a dir whose delete refuses (a session holds it) surfaces as `held`, never freed
31573
+ // out from under the holder.
31574
+ prunePluginCache: () => {
31575
+ const surface = detectSurface(process.env);
31576
+ const configRoot = surfaceConfigRoot(surface);
31577
+ const running = surface === "codex" ? installedActivePluginVersion(surface) : runningPluginVersion(process.env, resolveClientVersion());
31578
+ const installed = installedActivePluginVersion(surface);
31579
+ const plan = buildPluginCachePlan(
31580
+ (0, import_node_os15.homedir)(),
31581
+ running,
31582
+ pluginCacheFsDeps(configRoot, () => 0),
31583
+ { configRoot, includeStaging: surface !== "codex", installedVersion: installed }
31584
+ );
31585
+ const result = applyPluginCachePlan(
31586
+ plan,
31587
+ (p) => (0, import_node_fs40.rmSync)(p, { recursive: true }),
31588
+ stagingApplyFsGuard(configRoot)
31589
+ );
31590
+ return {
31591
+ removed: [...result.removed, ...result.removedStaging],
31592
+ held: [
31593
+ ...result.failed,
31594
+ ...result.failedStaging.map((f) => ({ version: f.name, error: f.error }))
31595
+ ],
31596
+ kept: [
31597
+ ...plan.keep.map((version) => ({
31598
+ version,
31599
+ reason: version === running ? "running" : version === installed ? "installed" : "newest / keep policy"
31600
+ })),
31601
+ ...result.skippedStaging.map((s) => ({ version: s.name, reason: s.reason }))
31602
+ ]
31603
+ };
31604
+ },
31605
+ // #4201: mmi `.pi-plugin` registration in ~/.pi/agent/settings.json — mirror of jerv-cli's own heal.
31606
+ piPluginState: () => readPiPluginState((0, import_node_os15.homedir)(), process.env),
31607
+ healPiPlugin: () => healPiPluginRegistration((0, import_node_os15.homedir)(), process.env),
31279
31608
  // #3470: what the SessionStart hook LAST emitted, measured at emission by the session-start verb below.
31280
31609
  // A local record read ? cheap enough for every lane, including the banner.
31281
31610
  sessionPayload: () => readSessionPayload(process.cwd()),
@@ -31287,14 +31616,14 @@ function mmiDoctorDeps(opts = {}) {
31287
31616
  const home = (0, import_node_os15.homedir)();
31288
31617
  const rows = marketplaceRows(
31289
31618
  MMI_MARKETPLACE_NAME,
31290
- readFileSyncSafe((0, import_node_path37.join)(home, ...KNOWN_MARKETPLACES_RELATIVE), import_node_fs39.readFileSync),
31291
- readFileSyncSafe((0, import_node_path37.join)(home, ".claude", "settings.json"), import_node_fs39.readFileSync),
31619
+ readFileSyncSafe((0, import_node_path38.join)(home, ...KNOWN_MARKETPLACES_RELATIVE), import_node_fs40.readFileSync),
31620
+ readFileSyncSafe((0, import_node_path38.join)(home, ".claude", "settings.json"), import_node_fs40.readFileSync),
31292
31621
  // #3974: this CLI heals these rows, so report mode names the doctor run rather than the hand
31293
31622
  // edit. Unconditional ? it is a fact about mmi-cli, not about the lane this run is on.
31294
31623
  true
31295
31624
  );
31296
31625
  const pending = readMarketplacePinPending(
31297
- (0, import_node_path37.join)(home, ".claude", "plugins", ".mmi-marketplace-pin-pending.json"),
31626
+ (0, import_node_path38.join)(home, ".claude", "plugins", ".mmi-marketplace-pin-pending.json"),
31298
31627
  MMI_MARKETPLACE_NAME
31299
31628
  );
31300
31629
  if (!pending) return rows;
@@ -31320,9 +31649,9 @@ function mmiDoctorDeps(opts = {}) {
31320
31649
  if (detectSurface(process.env) === "codex") return void 0;
31321
31650
  const home = (0, import_node_os15.homedir)();
31322
31651
  const names = [MMI_MARKETPLACE_NAME];
31323
- const result = applyOrgMarketplacePins((0, import_node_path37.join)(home, ...KNOWN_MARKETPLACES_RELATIVE), names);
31652
+ const result = applyOrgMarketplacePins((0, import_node_path38.join)(home, ...KNOWN_MARKETPLACES_RELATIVE), names);
31324
31653
  if (result?.wrote) {
31325
- writeMarketplacePinPending((0, import_node_path37.join)(home, ".claude", "plugins", ".mmi-marketplace-pin-pending.json"), names);
31654
+ writeMarketplacePinPending((0, import_node_path38.join)(home, ".claude", "plugins", ".mmi-marketplace-pin-pending.json"), names);
31326
31655
  }
31327
31656
  return result;
31328
31657
  } catch {
@@ -31338,7 +31667,7 @@ function mmiDoctorDeps(opts = {}) {
31338
31667
  // adopted the generated routing index (MMG-Unlive: `docs/Archive/**` only, no index, no gate.yml) would
31339
31668
  // get a permanent ? demanding an artifact it never asked for.
31340
31669
  docsIndexState: (root) => {
31341
- if (!(0, import_node_fs39.existsSync)((0, import_node_path37.join)(root, DOCS_INDEX_PATH))) return void 0;
31670
+ if (!(0, import_node_fs40.existsSync)((0, import_node_path38.join)(root, DOCS_INDEX_PATH))) return void 0;
31342
31671
  const real = createDocsIndexDeps(root);
31343
31672
  let docs2;
31344
31673
  const listDocs = () => docs2 ??= real.listDocs();
@@ -31347,7 +31676,7 @@ function mmiDoctorDeps(opts = {}) {
31347
31676
  },
31348
31677
  // #4168: working-tree heal — write if drifted, then re-check. Commit remains the operator's step.
31349
31678
  healDocsIndex: (root) => {
31350
- if (!(0, import_node_fs39.existsSync)((0, import_node_path37.join)(root, DOCS_INDEX_PATH))) return { drift: false, docCount: 0 };
31679
+ if (!(0, import_node_fs40.existsSync)((0, import_node_path38.join)(root, DOCS_INDEX_PATH))) return { drift: false, docCount: 0 };
31351
31680
  const real = createDocsIndexDeps(root);
31352
31681
  let docs2;
31353
31682
  const listDocs = () => docs2 ??= real.listDocs();
@@ -31681,19 +32010,19 @@ program2.hook("preAction", async (_thisCommand, actionCommand) => {
31681
32010
  });
31682
32011
  var rules = program2.command("rules").description("org-managed .gitignore delivery");
31683
32012
  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) => {
31684
- const path2 = (0, import_node_path37.join)(process.cwd(), ".gitignore");
31685
- const current = (0, import_node_fs39.existsSync)(path2) ? (0, import_node_fs39.readFileSync)(path2, "utf8") : null;
32013
+ const path2 = (0, import_node_path38.join)(process.cwd(), ".gitignore");
32014
+ const current = (0, import_node_fs40.existsSync)(path2) ? (0, import_node_fs40.readFileSync)(path2, "utf8") : null;
31686
32015
  const plan = planManagedGitignore(current);
31687
32016
  const drift = [...plan.added.map((l) => `+${l}`), ...plan.removed.map((l) => `-${l}`)].join(", ") || "block normalize";
31688
32017
  if (opts.json) {
31689
- if (opts.write && plan.changed) (0, import_node_fs39.writeFileSync)(path2, plan.content, "utf8");
32018
+ if (opts.write && plan.changed) (0, import_node_fs40.writeFileSync)(path2, plan.content, "utf8");
31690
32019
  console.log(JSON.stringify(plan, null, 2));
31691
32020
  if (!opts.write && plan.changed) process.exitCode = 1;
31692
32021
  return;
31693
32022
  }
31694
32023
  if (opts.write) {
31695
32024
  if (plan.changed) {
31696
- (0, import_node_fs39.writeFileSync)(path2, plan.content, "utf8");
32025
+ (0, import_node_fs40.writeFileSync)(path2, plan.content, "utf8");
31697
32026
  console.log(`mmi-cli org rules gitignore: updated .gitignore (${drift})`);
31698
32027
  } else {
31699
32028
  console.log("mmi-cli org rules gitignore: up to date");
@@ -31850,8 +32179,8 @@ gcCmd.option("--dry-run", "show what would be deleted (default)").option("--appl
31850
32179
  if (!Number.isFinite(limit) || limit < 1) return fail("worktree gc: --limit must be a positive integer");
31851
32180
  let root;
31852
32181
  if (o.root !== void 0) {
31853
- root = (0, import_node_path37.resolve)(o.root);
31854
- if (!(0, import_node_fs39.existsSync)(root) || !(0, import_node_fs39.statSync)(root).isDirectory()) return fail(`worktree gc: --root ${o.root} is not a directory`);
32182
+ root = (0, import_node_path38.resolve)(o.root);
32183
+ if (!(0, import_node_fs40.existsSync)(root) || !(0, import_node_fs40.statSync)(root).isDirectory()) return fail(`worktree gc: --root ${o.root} is not a directory`);
31855
32184
  const gcRepoRoot = (await execFileP2("git", ["rev-parse", "--show-toplevel"], { timeout: GIT_TIMEOUT_MS }).catch(() => ({ stdout: "" }))).stdout.trim() || process.cwd();
31856
32185
  if (isPathUnderDirectory(gcRepoRoot, root)) {
31857
32186
  return fail(`worktree gc: --root ${root} contains this checkout ? name a worktrees root, not the repo or an ancestor of it`);
@@ -31930,7 +32259,7 @@ async function currentWorktreeRemovalContext(command, force) {
31930
32259
  };
31931
32260
  }
31932
32261
  async function unprovenWorktreeReason(wtPath, repoRoot2) {
31933
- if (!(0, import_node_fs39.existsSync)(wtPath)) return `${wtPath} does not exist on disk`;
32262
+ if (!(0, import_node_fs40.existsSync)(wtPath)) return `${wtPath} does not exist on disk`;
31934
32263
  const porcelain = (await execFileP2("git", ["-C", repoRoot2, "worktree", "list", "--porcelain"], { timeout: GIT_TIMEOUT_MS }).catch(() => ({ stdout: "" }))).stdout;
31935
32264
  const registered = parseWorktreePorcelainEntries(porcelain);
31936
32265
  if (!registered.length) {
@@ -31952,26 +32281,26 @@ function makeProvisionDeps(worktreeRoot, quiet, log) {
31952
32281
  function acquireWorktreeSetupLock(worktreeRoot) {
31953
32282
  const lockPath = repoRuntimeStatePath(worktreeRoot, "worktree-setup.lock");
31954
32283
  const take = () => {
31955
- const fd = (0, import_node_fs39.openSync)(lockPath, "wx");
32284
+ const fd = (0, import_node_fs40.openSync)(lockPath, "wx");
31956
32285
  try {
31957
- (0, import_node_fs39.writeSync)(fd, String(Date.now()));
32286
+ (0, import_node_fs40.writeSync)(fd, String(Date.now()));
31958
32287
  } finally {
31959
- (0, import_node_fs39.closeSync)(fd);
32288
+ (0, import_node_fs40.closeSync)(fd);
31960
32289
  }
31961
32290
  return () => {
31962
32291
  try {
31963
- (0, import_node_fs39.rmSync)(lockPath, { force: true });
32292
+ (0, import_node_fs40.rmSync)(lockPath, { force: true });
31964
32293
  } catch {
31965
32294
  }
31966
32295
  };
31967
32296
  };
31968
32297
  try {
31969
- (0, import_node_fs39.mkdirSync)((0, import_node_path37.dirname)(lockPath), { recursive: true });
32298
+ (0, import_node_fs40.mkdirSync)((0, import_node_path38.dirname)(lockPath), { recursive: true });
31970
32299
  return take();
31971
32300
  } catch {
31972
32301
  try {
31973
- if (Date.now() - (0, import_node_fs39.statSync)(lockPath).mtimeMs > WORKTREE_SETUP_LOCK_TTL_MS) {
31974
- (0, import_node_fs39.rmSync)(lockPath, { force: true });
32302
+ if (Date.now() - (0, import_node_fs40.statSync)(lockPath).mtimeMs > WORKTREE_SETUP_LOCK_TTL_MS) {
32303
+ (0, import_node_fs40.rmSync)(lockPath, { force: true });
31975
32304
  return take();
31976
32305
  }
31977
32306
  } catch {
@@ -32832,7 +33161,7 @@ project.command("set [owner/repo]").description("upsert project META (idempotent
32832
33161
  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`);
32833
33162
  if (o.secretsFile) {
32834
33163
  try {
32835
- vars.push(`secrets=${(0, import_node_fs39.readFileSync)(o.secretsFile, "utf8")}`);
33164
+ vars.push(`secrets=${(0, import_node_fs40.readFileSync)(o.secretsFile, "utf8")}`);
32836
33165
  } catch (e) {
32837
33166
  return fail(`org project set: cannot read --secrets-file ${o.secretsFile}: ${e.message}`);
32838
33167
  }
@@ -33584,11 +33913,11 @@ pr.command("view <number>").description("read a PR as structured JSON (merged st
33584
33913
  }
33585
33914
  });
33586
33915
  async function listCiWorkflowPaths(cwd = process.cwd()) {
33587
- const wfDir = (0, import_node_path37.join)(cwd, ".github", "workflows");
33588
- if (!(0, import_node_fs39.existsSync)(wfDir)) return [];
33589
- return (0, import_node_fs39.readdirSync)(wfDir).filter((name) => /\.(ya?ml)$/i.test(name)).filter((name) => {
33916
+ const wfDir = (0, import_node_path38.join)(cwd, ".github", "workflows");
33917
+ if (!(0, import_node_fs40.existsSync)(wfDir)) return [];
33918
+ return (0, import_node_fs40.readdirSync)(wfDir).filter((name) => /\.(ya?ml)$/i.test(name)).filter((name) => {
33590
33919
  try {
33591
- return workflowReportsPrChecks((0, import_node_fs39.readFileSync)((0, import_node_path37.join)(wfDir, name), "utf8"));
33920
+ return workflowReportsPrChecks((0, import_node_fs40.readFileSync)((0, import_node_path38.join)(wfDir, name), "utf8"));
33592
33921
  } catch {
33593
33922
  return true;
33594
33923
  }
@@ -33620,16 +33949,16 @@ function ciAuditDeps() {
33620
33949
  // gate re-seed step is skipped gracefully rather than failing mid-run.
33621
33950
  readSeedFile: (path2) => {
33622
33951
  if (!root) return null;
33623
- const fullPath = (0, import_node_path37.join)(root, path2);
33624
- return (0, import_node_fs39.existsSync)(fullPath) ? (0, import_node_fs39.readFileSync)(fullPath, "utf8") : null;
33952
+ const fullPath = (0, import_node_path38.join)(root, path2);
33953
+ return (0, import_node_fs40.existsSync)(fullPath) ? (0, import_node_fs40.readFileSync)(fullPath, "utf8") : null;
33625
33954
  }
33626
33955
  };
33627
33956
  }
33628
33957
  function hubRoot() {
33629
- const fromPkg = (0, import_node_path37.join)(__dirname, "..", "..");
33958
+ const fromPkg = (0, import_node_path38.join)(__dirname, "..", "..");
33630
33959
  const marker = "skills/bootstrap/seeds/manifest.json";
33631
- if ((0, import_node_fs39.existsSync)((0, import_node_path37.join)(fromPkg, marker))) return fromPkg;
33632
- if ((0, import_node_fs39.existsSync)((0, import_node_path37.join)(process.cwd(), marker))) return process.cwd();
33960
+ if ((0, import_node_fs40.existsSync)((0, import_node_path38.join)(fromPkg, marker))) return fromPkg;
33961
+ if ((0, import_node_fs40.existsSync)((0, import_node_path38.join)(process.cwd(), marker))) return process.cwd();
33633
33962
  return null;
33634
33963
  }
33635
33964
  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) => {
@@ -33940,7 +34269,7 @@ jsonParity(pr.command("merge <number>").description("merge a PR (squash by defau
33940
34269
  localCleanup = await cleanupPrMergeLocalBranch(headRef, {
33941
34270
  beforeWorktrees,
33942
34271
  startingPath,
33943
- pathExists: (p) => (0, import_node_fs39.existsSync)(p),
34272
+ pathExists: (p) => (0, import_node_fs40.existsSync)(p),
33944
34273
  execGit: async (args) => (await execFileP2("git", args, { timeout: GIT_TIMEOUT_MS })).stdout,
33945
34274
  teardownWorktreeStage,
33946
34275
  deferredStore,
@@ -34434,19 +34763,19 @@ access.command("audit").description("audit collaborator roles + train-branch pus
34434
34763
  targets = resolution.targets;
34435
34764
  }
34436
34765
  const derivedMatrix = registryProjects ? accessMatrixFromProjects(registryProjects) : {};
34437
- const fileMatrix = (0, import_node_fs39.existsSync)("access-matrix.json") ? loadAccessMatrix((0, import_node_fs39.readFileSync)("access-matrix.json", "utf8")) : {};
34766
+ const fileMatrix = (0, import_node_fs40.existsSync)("access-matrix.json") ? loadAccessMatrix((0, import_node_fs40.readFileSync)("access-matrix.json", "utf8")) : {};
34438
34767
  const matrix = mergeAccessMatrix(fileMatrix, derivedMatrix);
34439
34768
  const derivedContracts = registryProjects ? dataAccessContractsFromProjects(registryProjects) : { consumers: {} };
34440
- const fileContracts = (0, import_node_fs39.existsSync)("data-access-contracts.json") ? loadDataAccessContracts((0, import_node_fs39.readFileSync)("data-access-contracts.json", "utf8")) : { consumers: {} };
34769
+ const fileContracts = (0, import_node_fs40.existsSync)("data-access-contracts.json") ? loadDataAccessContracts((0, import_node_fs40.readFileSync)("data-access-contracts.json", "utf8")) : { consumers: {} };
34441
34770
  const dataAccess = mergeDataAccessContracts(fileContracts, derivedContracts);
34442
- const sanctioned = (0, import_node_fs39.existsSync)("access-matrix.json") ? loadSanctionedAdmins((0, import_node_fs39.readFileSync)("access-matrix.json", "utf8")) : {};
34771
+ const sanctioned = (0, import_node_fs40.existsSync)("access-matrix.json") ? loadSanctionedAdmins((0, import_node_fs40.readFileSync)("access-matrix.json", "utf8")) : {};
34443
34772
  const report = await auditOrgAccess(targets, deps, matrix, dataAccess, sanctioned);
34444
34773
  console.log(o.json ? JSON.stringify(report, null, 2) : renderAccessReport(report));
34445
34774
  if (!report.ok) process.exitCode = 1;
34446
34775
  });
34447
34776
  access.command("capabilities").description("enumerate your effective vault reach ? every credential NAME + tier + scope you can read/use across project + org/master tiers (names only, no values) (#1615)").option("--repo <owner/repo>", "target repo (defaults to the current repo)").option("--json", "machine-readable output").action((o) => withSecrets((d) => secretsCapabilities(d, o)));
34448
34777
  var isWin2 = process.platform === "win32";
34449
- program2.command("doctor").description("heal CLI/plugin wiring and clean up repo cruft ? repairs run by default (#3975); use --verbose for the full audit checklist").option("--banner", "one-line resume summary; silent when all gates pass").option("--fast", "offline-safe fast lane; read-only, skips slow checks and network probes").option("--preflight", "eager version/plugin-heal (env repairs only ? never the repo working tree) with upfront notice when stale; silent when healthy (#1871)").option("--verbose", "print the evidence behind every check ? probes, resolved paths, versions compared, and the names behind each count (#2977)").option("--guide", "print the MMI Agentic Onboarding guide URL").option("--json", "machine-readable output (an output format ? repairs still run by lane)").option("--apply", "deprecated no-op: repairs run by default now (#3975); kept so older instructions still parse").option("--no-repo-writes", "env/plugin repairs only ? never mutate the repo working tree; report pending managed .gitignore repairs with the follow-up command (for train preflights)").option("--self", "verify CLI/plugin version parity and gh auth reach; suggests plugin-heal on a hard gap (reads the published version, so not offline-safe) (#2689)").addHelpText("after", "\nExit codes:\n 0 no hard gaps remain; advisory gaps may exist\n 1 one or more included hard checks failed\n\nA plain run heals env drift (CLI, plugin, marketplace pins) and cleans repo cruft (gitignore block,\nmerged branches, dead worktrees, aged scratch) automatically (#3975). --no-repo-writes keeps the\nworking tree untouched for train preflights; --banner/--fast/--self are read-only lanes.\n\n--banner keeps the legacy SessionStart contract and returns 0 unless the process crashes.\n").action(async (opts) => {
34778
+ program2.command("doctor").description("heal CLI/plugin wiring and clean up repo cruft ? repairs run by default (#3975); use --verbose for the full audit checklist").option("--banner", "one-line resume summary; silent when all gates pass").option("--fast", "offline-safe fast lane; read-only, skips slow checks and network probes").option("--preflight", "read-only fast gate for automation: measures and reports with the shared exit code, zero writes (#4199, canon); heal env drift with a plain run or --no-repo-writes").option("--verbose", "print the evidence behind every check ? probes, resolved paths, versions compared, and the names behind each count (#2977)").option("--guide", "print the MMI Agentic Onboarding guide URL").option("--json", "machine-readable output (an output format ? repairs still run by lane)").option("--apply", "deprecated no-op: repairs run by default now (#3975); kept so older instructions still parse").option("--no-repo-writes", "env/plugin repairs only ? never mutate the repo working tree; report pending managed .gitignore repairs with the follow-up command (for train preflights)").option("--self", "verify CLI/plugin version parity and gh auth reach; suggests plugin-heal on a hard gap (reads the published version, so not offline-safe) (#2689)").addHelpText("after", "\nExit codes:\n 0 no hard gaps remain; advisory gaps may exist\n 1 one or more included hard checks failed\n\nA plain run heals env drift (CLI, plugin, marketplace pins) and cleans repo cruft (gitignore block,\nmerged branches, dead worktrees, aged scratch) automatically (#3975). --no-repo-writes keeps the\nworking tree untouched for train preflights; --banner/--fast/--self are read-only lanes.\n\n--banner keeps the legacy SessionStart contract and returns 0 unless the process crashes.\n").action(async (opts) => {
34450
34779
  if (opts.guide) {
34451
34780
  consoleIo.log("MMI Agentic Onboarding: docs/Architecture/agentic-dev-environment.md");
34452
34781
  return;
@@ -34471,16 +34800,16 @@ function directoryBytes(path2) {
34471
34800
  let total = 0;
34472
34801
  let entries;
34473
34802
  try {
34474
- entries = (0, import_node_fs39.readdirSync)(path2, { withFileTypes: true });
34803
+ entries = (0, import_node_fs40.readdirSync)(path2, { withFileTypes: true });
34475
34804
  } catch {
34476
34805
  return 0;
34477
34806
  }
34478
34807
  for (const entry of entries) {
34479
- const child2 = (0, import_node_path37.join)(path2, entry.name);
34808
+ const child2 = (0, import_node_path38.join)(path2, entry.name);
34480
34809
  if (entry.isDirectory()) total += directoryBytes(child2);
34481
34810
  else {
34482
34811
  try {
34483
- total += (0, import_node_fs39.statSync)(child2).size;
34812
+ total += (0, import_node_fs40.statSync)(child2).size;
34484
34813
  } catch {
34485
34814
  }
34486
34815
  }
@@ -34488,25 +34817,25 @@ function directoryBytes(path2) {
34488
34817
  return total;
34489
34818
  }
34490
34819
  function listDirEntries(dir) {
34491
- return (0, import_node_fs39.readdirSync)(dir, { withFileTypes: true }).map((d) => ({ name: d.name, isDirectory: d.isDirectory() }));
34820
+ return (0, import_node_fs40.readdirSync)(dir, { withFileTypes: true }).map((d) => ({ name: d.name, isDirectory: d.isDirectory() }));
34492
34821
  }
34493
34822
  function readInstalledPluginRefs(configRoot) {
34494
34823
  const p = installedPluginsPathForConfig(configRoot);
34495
- if (!(0, import_node_fs39.existsSync)(p)) return [];
34824
+ if (!(0, import_node_fs40.existsSync)(p)) return [];
34496
34825
  try {
34497
- return installedPluginPaths((0, import_node_fs39.readFileSync)(p, "utf8"));
34826
+ return installedPluginPaths((0, import_node_fs40.readFileSync)(p, "utf8"));
34498
34827
  } catch {
34499
34828
  return null;
34500
34829
  }
34501
34830
  }
34502
34831
  function pluginCacheFsDeps(configRoot, dirBytes) {
34503
34832
  return {
34504
- exists: (p) => (0, import_node_fs39.existsSync)(p),
34505
- listVersionDirs: (root) => (0, import_node_fs39.readdirSync)(root, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name),
34833
+ exists: (p) => (0, import_node_fs40.existsSync)(p),
34834
+ listVersionDirs: (root) => (0, import_node_fs40.readdirSync)(root, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name),
34506
34835
  dirBytes,
34507
- listStagingDirs: (root) => (0, import_node_fs39.readdirSync)(root, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => {
34836
+ listStagingDirs: (root) => (0, import_node_fs40.readdirSync)(root, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => {
34508
34837
  try {
34509
- return { name: d.name, mtimeMs: newestMtimeMs((0, import_node_path37.join)(root, d.name), listDirEntries, (p) => (0, import_node_fs39.statSync)(p).mtimeMs) };
34838
+ return { name: d.name, mtimeMs: newestMtimeMs((0, import_node_path38.join)(root, d.name), listDirEntries, (p) => (0, import_node_fs40.statSync)(p).mtimeMs) };
34510
34839
  } catch {
34511
34840
  return { name: d.name, mtimeMs: Date.now() };
34512
34841
  }
@@ -34520,10 +34849,10 @@ function stagingApplyFsGuard(configRoot) {
34520
34849
  return {
34521
34850
  referencedPaths: () => readInstalledPluginRefs(configRoot),
34522
34851
  mtimeMs: (name) => {
34523
- const p = (0, import_node_path37.join)(stagingRoot, name);
34524
- if (!(0, import_node_fs39.existsSync)(p)) return null;
34852
+ const p = (0, import_node_path38.join)(stagingRoot, name);
34853
+ if (!(0, import_node_fs40.existsSync)(p)) return null;
34525
34854
  try {
34526
- return newestMtimeMs(p, listDirEntries, (q) => (0, import_node_fs39.statSync)(q).mtimeMs);
34855
+ return newestMtimeMs(p, listDirEntries, (q) => (0, import_node_fs40.statSync)(q).mtimeMs);
34527
34856
  } catch {
34528
34857
  return null;
34529
34858
  }
@@ -34549,7 +34878,7 @@ program2.command("plugin-prune").description(`prune stale cached MMI plugin vers
34549
34878
  { withBytes: true, configRoot, includeStaging: surface !== "codex" }
34550
34879
  );
34551
34880
  const anythingToDelete = plan.prune.length > 0 || plan.staging.length > 0;
34552
- const result = o.apply && anythingToDelete ? applyPluginCachePlan(plan, (p) => (0, import_node_fs39.rmSync)(p, { recursive: true, force: true }), stagingApplyFsGuard(configRoot)) : void 0;
34881
+ const result = o.apply && anythingToDelete ? applyPluginCachePlan(plan, (p) => (0, import_node_fs40.rmSync)(p, { recursive: true, force: true }), stagingApplyFsGuard(configRoot)) : void 0;
34553
34882
  const warnings = plan.prune.length > 0 ? [CONCURRENT_SESSION_WARNING] : [];
34554
34883
  if (o.json) console.log(JSON.stringify({ ...plan, warnings, applied: result ?? null }));
34555
34884
  else console.log(renderPluginCachePlan(plan, result));