@mutmutco/cli 3.96.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 +864 -217
  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
@@ -5235,7 +5235,7 @@ function deferredWorktreesRegistryPath(gitDir) {
5235
5235
  return `${base}/mmi-deferred-worktrees.json`;
5236
5236
  }
5237
5237
  function parseDeferredWorktreesFile(text) {
5238
- const parsed = JSON.parse(text);
5238
+ const parsed = JSON.parse(text.replace(/^\uFEFF/, ""));
5239
5239
  if (!parsed || !Array.isArray(parsed.entries)) return [];
5240
5240
  return parsed.entries.filter((e) => Boolean(e) && typeof e === "object" && typeof e.path === "string" && typeof e.branch === "string" && e.reason === "lock-held").map((e) => ({ ...e, registeredAt: e.registeredAt || (/* @__PURE__ */ new Date(0)).toISOString() }));
5241
5241
  }
@@ -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
@@ -8788,6 +8821,9 @@ function detectSurface(env) {
8788
8821
  if (env.MMI_AGENT_SURFACE === "codex" || has("CODEX_THREAD_ID") || has("CODEX_MANAGED_BY_NPM") || has("CODEX_MANAGED_PACKAGE_ROOT") || (env.CLAUDE_PLUGIN_ROOT ?? "").includes(".codex")) {
8789
8822
  return "codex";
8790
8823
  }
8824
+ if (env.MMI_AGENT_SURFACE === "jervcode" || has("PI_SESSION_ID")) {
8825
+ return "jervcode";
8826
+ }
8791
8827
  if (env.MMI_AGENT_SURFACE === "kimi" || has("KIMI_PLUGIN_ROOT") || has("KIMI_CODE_HOME")) {
8792
8828
  return "kimi";
8793
8829
  }
@@ -8817,6 +8853,8 @@ function surfaceToken(surface) {
8817
8853
  return "kilo";
8818
8854
  case "cursor":
8819
8855
  return "cursor";
8856
+ case "jervcode":
8857
+ return "jervcode";
8820
8858
  case "opencode":
8821
8859
  return "opencode";
8822
8860
  case "shell":
@@ -8838,6 +8876,8 @@ function reloadAction(surface) {
8838
8876
  return "restart OpenCode";
8839
8877
  case "cursor":
8840
8878
  return "reload the Cursor window";
8879
+ case "jervcode":
8880
+ return "restart JervCode (the package set is read at launch)";
8841
8881
  case "claude-cli":
8842
8882
  case "shell":
8843
8883
  default:
@@ -8905,7 +8945,7 @@ function nonClaudeSurfaceHealMessage(surface) {
8905
8945
  if (surface === "cursor") {
8906
8946
  return "Cursor ships an MMI plugin (#3920). Install or repair its managed local checkout with:\n mmi-cli plugin heal\n Then reload the Cursor window. For one-off CLI use, pass --plugin-dir <MMI-Hub checkout>.\n Update the CLI too: npm i -g @mutmutco/cli";
8907
8947
  }
8908
- return "No Hub-shipped MMI plugin on this host \u2014 the Hub ships plugins for Claude, Codex, Kimi, Cursor, and Kilo only.\n Update the CLI instead: npm i -g @mutmutco/cli (org rules ride an AGENTS.md authored outside the Hub)";
8948
+ return "No Hub-shipped MMI plugin on this host \u2014 the Hub ships plugins for Claude, Codex, Kimi, Cursor, Kilo, and JervCode only.\n Update the CLI instead: npm i -g @mutmutco/cli (org rules ride an AGENTS.md authored outside the Hub)";
8909
8949
  }
8910
8950
  function healStepAborts(step, ok) {
8911
8951
  return !ok && step.gated;
@@ -8953,6 +8993,7 @@ function surfaceConfigRoot(surface, env = process.env, home = (0, import_node_os
8953
8993
  if (surface === "kimi") return env.KIMI_CODE_HOME?.trim() || (0, import_node_path14.join)(home, ".kimi-code");
8954
8994
  if (surface === "kilo") return env.KILO_CONFIG_DIR?.trim() || (0, import_node_path14.join)(home, ".config", "kilo");
8955
8995
  if (surface === "cursor") return (0, import_node_path14.join)(home, ".cursor");
8996
+ if (surface === "jervcode") return env.PI_CODING_AGENT_DIR?.trim() || (0, import_node_path14.join)(home, ".pi", "agent");
8956
8997
  return (0, import_node_path14.join)(home, ".claude");
8957
8998
  }
8958
8999
  var installedPluginsPath = (surface = detectSurface(process.env)) => {
@@ -8976,6 +9017,7 @@ function marketplaceCloneCandidates(surface, home, env = process.env) {
8976
9017
  if (surface === "kimi") return [];
8977
9018
  if (surface === "kilo") return [];
8978
9019
  if (surface === "cursor") return [];
9020
+ if (surface === "jervcode") return [];
8979
9021
  return [(0, import_node_path14.join)(home, ".claude", "plugins", "marketplaces", "mutmutco")];
8980
9022
  }
8981
9023
  function marketplaceClonePresent(surface, home, exists = import_node_fs15.existsSync, env = process.env) {
@@ -9104,22 +9146,170 @@ function kimiPluginTreeHealthy(root, exists = import_node_fs15.existsSync) {
9104
9146
  "scripts/hook-run.mjs"
9105
9147
  ].every((path2) => exists((0, import_node_path14.join)(root, ...path2.split("/"))));
9106
9148
  }
9149
+ var JERVCODE_WRAPPER_DIR = ".pi-plugin";
9150
+ function normalizePiEntry(value) {
9151
+ return decodeURIComponent(value.replace(/^file:\/\/\/?/, "")).replace(/\\/g, "/").replace(/\/+$/, "").toLowerCase();
9152
+ }
9153
+ function piEntryFsPath(entry) {
9154
+ return decodeURIComponent(entry.replace(/^file:\/\/\/?/, ""));
9155
+ }
9156
+ function samePiEntry(entry, packagePath) {
9157
+ return typeof entry === "string" && normalizePiEntry(entry) === normalizePiEntry(packagePath);
9158
+ }
9159
+ function jervcodePackageFamily(entry) {
9160
+ if (typeof entry !== "string") return null;
9161
+ const parts = normalizePiEntry(entry).split("/");
9162
+ if (parts.length < 3 || parts[parts.length - 1] !== JERVCODE_WRAPPER_DIR) return null;
9163
+ if (!/^\d+\.\d+\.\d+$/.test(parts[parts.length - 2])) return null;
9164
+ parts.splice(parts.length - 2, 1);
9165
+ return parts.join("/");
9166
+ }
9167
+ function isMmiOwnedPiEntry(entry) {
9168
+ if (typeof entry !== "string") return false;
9169
+ try {
9170
+ const pkg = JSON.parse((0, import_node_fs15.readFileSync)((0, import_node_path14.join)(piEntryFsPath(entry), "package.json"), "utf8"));
9171
+ return pkg.name === "mmi";
9172
+ } catch {
9173
+ return false;
9174
+ }
9175
+ }
9176
+ function mergeMmiPiPackageEntries(entries, packagePath) {
9177
+ const family = jervcodePackageFamily(packagePath);
9178
+ const others = entries.filter((entry) => {
9179
+ if (samePiEntry(entry, packagePath)) return false;
9180
+ if (family === null || jervcodePackageFamily(entry) !== family) return true;
9181
+ return !isMmiOwnedPiEntry(entry);
9182
+ });
9183
+ const already = entries.some((entry) => samePiEntry(entry, packagePath));
9184
+ const superseded = entries.length - others.length - (already ? 1 : 0);
9185
+ return {
9186
+ next: [...others, packagePath],
9187
+ superseded,
9188
+ changed: !(already && others.length === entries.length - 1)
9189
+ };
9190
+ }
9191
+ function readPiSettings(path2) {
9192
+ if (!(0, import_node_fs15.existsSync)(path2)) return void 0;
9193
+ try {
9194
+ const parsed = JSON.parse((0, import_node_fs15.readFileSync)(path2, "utf8"));
9195
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return null;
9196
+ return parsed;
9197
+ } catch {
9198
+ return null;
9199
+ }
9200
+ }
9201
+ function mmiPiWrapperEntry(env = process.env, home = (0, import_node_os4.homedir)()) {
9202
+ const settings = readPiSettings((0, import_node_path14.join)(surfaceConfigRoot("jervcode", env, home), "settings.json"));
9203
+ const entries = Array.isArray(settings?.packages) ? settings.packages : [];
9204
+ for (const entry of entries) {
9205
+ if (typeof entry !== "string") continue;
9206
+ if (jervcodePackageFamily(entry)?.endsWith("/mutmutco/mmi/" + JERVCODE_WRAPPER_DIR) || isMmiOwnedPiEntry(entry)) {
9207
+ return entry;
9208
+ }
9209
+ }
9210
+ return null;
9211
+ }
9212
+ function mmiPiWrapperHealthy(entry) {
9213
+ if (!entry) return false;
9214
+ const wrapper = piEntryFsPath(entry);
9215
+ return isMmiOwnedPiEntry(entry) && (0, import_node_fs15.existsSync)((0, import_node_path14.join)((0, import_node_path14.dirname)(wrapper), "skills", "mmi", "SKILL.md"));
9216
+ }
9217
+ function findMmiPiSourceClone(home = (0, import_node_os4.homedir)()) {
9218
+ const cacheRoot = (0, import_node_path14.join)(home, ".claude", "plugins", "cache", "mutmutco", "mmi");
9219
+ let best = null;
9220
+ try {
9221
+ for (const entry of (0, import_node_fs15.readdirSync)(cacheRoot, { withFileTypes: true })) {
9222
+ if (!entry.isDirectory() || !/^\d+\.\d+\.\d+$/.test(entry.name)) continue;
9223
+ if (!(0, import_node_fs15.existsSync)((0, import_node_path14.join)(cacheRoot, entry.name, JERVCODE_WRAPPER_DIR, "package.json"))) continue;
9224
+ if (!best || compareVersions(entry.name, best.version) > 0) {
9225
+ best = { path: (0, import_node_path14.join)(cacheRoot, entry.name), version: entry.name };
9226
+ }
9227
+ }
9228
+ } catch {
9229
+ return null;
9230
+ }
9231
+ return best;
9232
+ }
9233
+ function healJervCodePackageRegistration(opts = {}) {
9234
+ const env = opts.env ?? process.env;
9235
+ if (env.PI_SESSION_ID?.trim()) {
9236
+ return {
9237
+ available: true,
9238
+ ok: true,
9239
+ changed: false,
9240
+ version: null,
9241
+ detail: "skipped \u2014 live JervCode/Pi seat (PI_SESSION_ID set); the package set is read at launch, so this registration is deferred to an out-of-session doctor run"
9242
+ };
9243
+ }
9244
+ const home = opts.home ?? (0, import_node_os4.homedir)();
9245
+ const agentDir = surfaceConfigRoot("jervcode", env, home);
9246
+ if (!(0, import_node_fs15.existsSync)(agentDir)) {
9247
+ return { available: false, ok: true, changed: false, version: null, detail: "skipped \u2014 no Pi/JervCode install (no agent config dir)" };
9248
+ }
9249
+ const clone = opts.clone === void 0 ? findMmiPiSourceClone(home) : opts.clone;
9250
+ if (!clone) {
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)" };
9252
+ }
9253
+ const packagePath = `${clone.path.replace(/\\/g, "/").replace(/\/+$/, "")}/${JERVCODE_WRAPPER_DIR}`;
9254
+ const settingsPath2 = (0, import_node_path14.join)(agentDir, "settings.json");
9255
+ const settings = readPiSettings(settingsPath2);
9256
+ if (settings === null) {
9257
+ return {
9258
+ available: true,
9259
+ ok: false,
9260
+ changed: false,
9261
+ version: clone.version,
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`
9263
+ };
9264
+ }
9265
+ const current = settings ?? {};
9266
+ const entries = Array.isArray(current.packages) ? current.packages : [];
9267
+ const merged = mergeMmiPiPackageEntries(entries, packagePath);
9268
+ if (!merged.changed) {
9269
+ return { available: true, ok: true, changed: false, version: clone.version, detail: `holds ${clone.version}` };
9270
+ }
9271
+ current.packages = merged.next;
9272
+ try {
9273
+ (0, import_node_fs15.mkdirSync)((0, import_node_path14.dirname)(settingsPath2), { recursive: true });
9274
+ const tmp = `${settingsPath2}.tmp-${process.pid}`;
9275
+ (0, import_node_fs15.writeFileSync)(tmp, `${JSON.stringify(current, null, 2)}
9276
+ `, "utf8");
9277
+ try {
9278
+ (0, import_node_fs15.renameSync)(tmp, settingsPath2);
9279
+ } catch (renameError) {
9280
+ (0, import_node_fs15.rmSync)(tmp, { force: true });
9281
+ throw renameError;
9282
+ }
9283
+ } catch (error) {
9284
+ return { available: true, ok: false, changed: false, version: clone.version, detail: `package NOT registered \u2014 ${error.message}` };
9285
+ }
9286
+ const beside = `package registered in settings.json (${merged.next.length - 1} other package(s) untouched)`;
9287
+ return {
9288
+ available: true,
9289
+ ok: true,
9290
+ changed: true,
9291
+ version: clone.version,
9292
+ detail: `${clone.version}: ${merged.superseded > 0 ? `${beside}, superseding ${merged.superseded} earlier mmi version(s)` : beside}`
9293
+ };
9294
+ }
9107
9295
  function snapshotPluginGuardInput(surface = detectSurface(process.env), isOrgRepo = false) {
9108
9296
  const root = surfaceConfigRoot(surface);
9109
9297
  const installed = readInstalledPlugins(surface);
9110
9298
  const codexStatus = surface === "codex" ? codexPluginStatus() : void 0;
9299
+ const piEntry = surface === "jervcode" ? mmiPiWrapperEntry() : null;
9111
9300
  return {
9112
9301
  isOrgRepo,
9113
9302
  installRecordPresent: hasUserInstallRecord(installed, MMI_PLUGIN_ID) || hasProjectInstallRecord(installed, MMI_PLUGIN_ID, process.cwd()) || // Kimi's managed plugin directory is its native install record; it has no Claude-style ledger.
9114
9303
  surface === "kimi" && (0, import_node_fs15.existsSync)((0, import_node_path14.join)(root, "plugins", "managed", "mmi")) || // kilo-p1: the install record is the config file itself.
9115
- surface === "kilo" && kiloConfigListsPlugin(root) || surface === "cursor" && (0, import_node_fs15.existsSync)(cursorLocalPluginRoot()),
9304
+ surface === "kilo" && kiloConfigListsPlugin(root) || // #4188: jervcode's install record is the settings-file packages[] entry itself.
9305
+ surface === "jervcode" && piEntry !== null || surface === "cursor" && (0, import_node_fs15.existsSync)(cursorLocalPluginRoot()),
9116
9306
  // Kilo has no marketplace to clone — the config file IS the install record, so this dimension of
9117
- // the shared guard table is vacuously satisfied.
9118
- marketplaceClonePresent: surface === "kimi" || surface === "kilo" || surface === "cursor" ? true : marketplaceClonePresent(surface, (0, import_node_os4.homedir)()),
9307
+ // the shared guard table is vacuously satisfied. Same for jervcode's settings entry.
9308
+ marketplaceClonePresent: surface === "kimi" || surface === "kilo" || surface === "cursor" || surface === "jervcode" ? true : marketplaceClonePresent(surface, (0, import_node_os4.homedir)()),
9119
9309
  // Kimi keeps no plugin cache dir — installs are copied to plugins/managed/<id> and run from there.
9120
9310
  // Kilo (kilo-p1) keeps no cache dir either: the plugin's server() provisions ~/.kilo behind the
9121
9311
  // version stamp, so the stamp's presence is the cache signal.
9122
- pluginCachePresent: surface === "kilo" ? (0, import_node_fs15.existsSync)((0, import_node_path14.join)((0, import_node_os4.homedir)(), ".kilo", ".mmi-kilo-version")) : surface === "kimi" ? kimiPluginTreeHealthy((0, import_node_path14.join)(root, "plugins", "managed", "mmi")) : surface === "cursor" ? cursorPluginTreeHealthy(cursorLocalPluginRoot()) : surface === "codex" ? Boolean(
9312
+ pluginCachePresent: surface === "jervcode" ? mmiPiWrapperHealthy(piEntry) : surface === "kilo" ? (0, import_node_fs15.existsSync)((0, import_node_path14.join)((0, import_node_os4.homedir)(), ".kilo", ".mmi-kilo-version")) : surface === "kimi" ? kimiPluginTreeHealthy((0, import_node_path14.join)(root, "plugins", "managed", "mmi")) : surface === "cursor" ? cursorPluginTreeHealthy(cursorLocalPluginRoot()) : surface === "codex" ? Boolean(
9123
9313
  codexStatus?.installed && codexStatus.enabled && codexStatus.version && (0, import_node_fs15.existsSync)((0, import_node_path14.join)(root, "plugins", "cache", CODEX_MARKETPLACE, "mmi", codexStatus.version))
9124
9314
  ) : (0, import_node_fs15.existsSync)((0, import_node_path14.join)(root, "plugins", "cache", "mutmutco", "mmi"))
9125
9315
  };
@@ -9298,13 +9488,26 @@ async function healClaudePluginForDoctor(surface = detectSurface(process.env), o
9298
9488
  detail: ok ? `marketplace remove \u2192 add \u2192 install succeeded${pinNote ? `; ${pinNote}` : ""}` : `\`claude plugin\` reinstall failed or was skipped${steps.length ? ` (${steps[steps.length - 1]})` : ""}`
9299
9489
  };
9300
9490
  }
9491
+ async function healJervCodeForDoctor() {
9492
+ const heal = healJervCodePackageRegistration();
9493
+ if (!heal.ok) return { ok: false, detail: heal.detail };
9494
+ const snapshot = snapshotPluginGuardInput("jervcode", true);
9495
+ const state = buildPluginGuardDecision(snapshot).state;
9496
+ const verification = `record=${snapshot.installRecordPresent ? "yes" : "no"}, payload=${snapshot.pluginCachePresent ? "yes" : "no"}`;
9497
+ if (!heal.changed && heal.detail.startsWith("skipped")) return { ok: true, detail: heal.detail };
9498
+ return {
9499
+ ok: state === "healthy",
9500
+ detail: state === "healthy" ? `${heal.detail}; full guard verified (${verification})` : `pi package registration did not verify (${verification}); ${heal.detail}`
9501
+ };
9502
+ }
9301
9503
  async function healActivePluginForDoctor(surface = detectSurface(process.env), onStep) {
9302
9504
  const token = surfaceToken(surface);
9303
- if (token !== "claude" && token !== "codex" && token !== "cursor" && token !== "kilo") {
9505
+ if (token !== "claude" && token !== "codex" && token !== "cursor" && token !== "kilo" && token !== "jervcode") {
9304
9506
  return { ok: false, detail: `not a supported plugin surface (${surface})` };
9305
9507
  }
9306
9508
  if (token === "claude") return healClaudePluginForDoctor(surface, onStep);
9307
9509
  if (token === "cursor") return installCursorPluginCheckout();
9510
+ if (token === "jervcode") return healJervCodeForDoctor();
9308
9511
  const steps = [];
9309
9512
  const applied = await applyPluginHeal(surface, (msg) => {
9310
9513
  const line = msg.trim();
@@ -9421,7 +9624,7 @@ async function runGuard(readOrigin) {
9421
9624
  const surface = detectSurface(process.env);
9422
9625
  try {
9423
9626
  const token = surfaceToken(surface);
9424
- if (token !== "claude" && token !== "codex" && token !== "cursor" && token !== "kilo") {
9627
+ if (token !== "claude" && token !== "codex" && token !== "cursor" && token !== "kilo" && token !== "jervcode") {
9425
9628
  process.exitCode = 0;
9426
9629
  return;
9427
9630
  }
@@ -9441,8 +9644,8 @@ async function runGuard(readOrigin) {
9441
9644
  if (surfaceToken(surface) === "codex") {
9442
9645
  console.error("[mmi-guard] Could not inspect the active Codex plugin; run `mmi-cli plugin heal`.");
9443
9646
  process.exitCode = 1;
9444
- } else if (surfaceToken(surface) === "kilo" || surfaceToken(surface) === "cursor") {
9445
- const host = surfaceToken(surface) === "cursor" ? "Cursor" : "Kilo";
9647
+ } else if (surfaceToken(surface) === "kilo" || surfaceToken(surface) === "cursor" || surfaceToken(surface) === "jervcode") {
9648
+ const host = surfaceToken(surface) === "cursor" ? "Cursor" : surfaceToken(surface) === "jervcode" ? "JervCode" : "Kilo";
9446
9649
  console.error(`[mmi-guard] Could not inspect the active ${host} plugin; run \`mmi-cli plugin heal\`.`);
9447
9650
  process.exitCode = 1;
9448
9651
  } else {
@@ -9452,10 +9655,21 @@ async function runGuard(readOrigin) {
9452
9655
  }
9453
9656
  async function runPluginHeal(surface = detectSurface(process.env)) {
9454
9657
  const token = surfaceToken(surface);
9455
- if (token !== "claude" && token !== "codex" && token !== "cursor" && token !== "kilo") {
9658
+ if (token !== "claude" && token !== "codex" && token !== "cursor" && token !== "kilo" && token !== "jervcode") {
9456
9659
  console.log(nonClaudeSurfaceHealMessage(token ?? void 0));
9457
9660
  return;
9458
9661
  }
9662
+ if (token === "jervcode") {
9663
+ const result = await healJervCodeForDoctor();
9664
+ console.log(` \u21BB ${result.detail}`);
9665
+ if (result.ok) {
9666
+ console.log(` \u2713 MMI pi package registered \u2014 ${reloadAction(surface)} to load MMI skills.`);
9667
+ } else {
9668
+ process.exitCode = 1;
9669
+ console.log(" \u2717 Auto-heal failed or was skipped. Install the Claude MMI plugin first (it owns the referenced clone), then rerun `mmi-cli plugin heal`.");
9670
+ }
9671
+ return;
9672
+ }
9459
9673
  const descriptor = PLUGIN_SURFACE_HEAL[token];
9460
9674
  const cursorResult = token === "cursor" ? await installCursorPluginCheckout() : void 0;
9461
9675
  if (cursorResult) console.log(` \u21BB ${cursorResult.detail}`);
@@ -11830,6 +12044,12 @@ async function auditRepoCi(repo, deps) {
11830
12044
  label: "delete_branch_on_merge enabled",
11831
12045
  detail: info.delete_branch_on_merge === true ? void 0 : "false or unavailable"
11832
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
+ });
11833
12053
  const hasCanonicalGateWorkflow = repoClass === "hub" ? true : repoClass === "content" ? true : await contentExists(deps, repo, baseBranch, PRODUCT_GATE_PATH);
11834
12054
  let prWorkflowPaths = repoClass === "deployable" && hasCanonicalGateWorkflow ? [PRODUCT_GATE_PATH] : [];
11835
12055
  if (repoClass === "deployable" && !hasCanonicalGateWorkflow) {
@@ -12088,7 +12308,7 @@ async function applyCiReconcileMergeSettingsFromReport(repo, deps, report) {
12088
12308
  const applied = [];
12089
12309
  const skipped = [];
12090
12310
  const errors = [];
12091
- 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"));
12092
12312
  const needsPatch = mergeChecks.some((c) => !c.ok);
12093
12313
  if (!needsPatch) {
12094
12314
  skipped.push("merge settings already canonical");
@@ -12099,10 +12319,11 @@ async function applyCiReconcileMergeSettingsFromReport(repo, deps, report) {
12099
12319
  body: {
12100
12320
  allow_auto_merge: true,
12101
12321
  allow_squash_merge: true,
12102
- delete_branch_on_merge: true
12322
+ delete_branch_on_merge: true,
12323
+ has_wiki: false
12103
12324
  }
12104
12325
  });
12105
- 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");
12106
12327
  } catch (e) {
12107
12328
  errors.push(e.message);
12108
12329
  }
@@ -12285,7 +12506,7 @@ async function parkProductRuleset(repo, deps) {
12285
12506
  return result;
12286
12507
  }
12287
12508
  function reconcileOwnedFailures(report) {
12288
- 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);
12289
12510
  }
12290
12511
  async function finalizeCiReconcile(repo, deps, result, before, pendingReason) {
12291
12512
  if (result.errors.length > 0) {
@@ -17227,7 +17448,7 @@ function applyChecklistCheck(body, query, checked) {
17227
17448
  // src/command-taxonomy.ts
17228
17449
  var COMMAND_METADATA = /* @__PURE__ */ Symbol.for("mmi.commandTaxonomy.metadata");
17229
17450
  var PRIMARY_GROUPS = [
17230
- ["Orient", ["onboard", "status", "next", "doctor", "whoami", "commands", "explain"]],
17451
+ ["Orient", ["onboard", "status", "next", "find", "doctor", "whoami", "commands", "explain"]],
17231
17452
  ["Plan and work", ["board", "issue", "worktree", "stage"]],
17232
17453
  ["Review and ship", ["pr", "ci", "rcand", "release", "hotfix", "train"]],
17233
17454
  // `tests` sits beside `docs` deliberately: both are deterministic, repo-local gates a workflow
@@ -17237,7 +17458,7 @@ var PRIMARY_GROUPS = [
17237
17458
  ["Coordinate and improve", ["wave", "report", "skill-lesson"]]
17238
17459
  ];
17239
17460
  var OPERATIONAL_TOP_LEVEL = /* @__PURE__ */ new Set(["org", "runtime", "plugin"]);
17240
- var SUPPORT_PRIMARY = /* @__PURE__ */ new Set(["doctor", "whoami", "commands", "explain", "docs", "repo-index", "tests", "spawn", "wave", "report", "skill-lesson"]);
17461
+ var SUPPORT_PRIMARY = /* @__PURE__ */ new Set(["doctor", "whoami", "commands", "explain", "docs", "repo-index", "find", "tests", "spawn", "wave", "report", "skill-lesson"]);
17241
17462
  var TOP_LEVEL_ORDER = /* @__PURE__ */ new Map();
17242
17463
  var HELP_GROUP_ORDER = /* @__PURE__ */ new Map();
17243
17464
  var topLevelPosition = 0;
@@ -17248,6 +17469,10 @@ for (const [helpGroup, names] of PRIMARY_GROUPS) {
17248
17469
  HELP_GROUP_ORDER.set("Operations", HELP_GROUP_ORDER.size);
17249
17470
  for (const name of OPERATIONAL_TOP_LEVEL) TOP_LEVEL_ORDER.set(name, topLevelPosition++);
17250
17471
  var PATH_OVERRIDES = {
17472
+ // #4184: the estate-search door reads as Orient, while rebuild/publish/gc/sync-estate stay
17473
+ // Setup and support — that split is the point.
17474
+ "repo-index search": { category: "support", discovery: "primary", help_group: "Orient" },
17475
+ "repo-index status": { category: "support", discovery: "primary", help_group: "Orient" },
17251
17476
  "secrets find": { category: "admin", discovery: "all-only", help_group: "Operations" },
17252
17477
  "secrets catalog": { category: "admin", discovery: "all-only", help_group: "Operations" },
17253
17478
  "secrets doctor": { category: "admin", discovery: "all-only", help_group: "Operations" },
@@ -17277,6 +17502,7 @@ var COMMAND_OWNERSHIP = {
17277
17502
  secrets: { module_owner: "cli/src/secrets-commands.ts", consumer: "authenticated-operator" },
17278
17503
  docs: { module_owner: "cli/src/docs-index-command.ts", consumer: "repo-gates" },
17279
17504
  "repo-index": { module_owner: "cli/src/repo-index.ts", consumer: "agent-session" },
17505
+ find: { module_owner: "cli/src/repo-index.ts", consumer: "agent-session" },
17280
17506
  tests: { module_owner: "cli/src/test-policy-core.ts", consumer: "repo-gates" },
17281
17507
  spawn: { module_owner: "cli/src/spawn-policy-core.ts", consumer: "repo-gates" },
17282
17508
  wave: { module_owner: "cli/src/wave-land.ts", consumer: "campaign-orchestrator" },
@@ -17639,18 +17865,25 @@ function consolidateCommandNamespaces(program3) {
17639
17865
  move(program3, stage, "port-range");
17640
17866
  }
17641
17867
 
17868
+ // src/pi-plugin-registration.ts
17869
+ var import_node_fs22 = require("node:fs");
17870
+ var import_node_path20 = require("node:path");
17871
+
17642
17872
  // src/plugin-cache-prune.ts
17643
17873
  var PLUGIN_CACHE_KEEP = 2;
17644
17874
  var VERSION_DIR = /^\d+\.\d+\.\d+(?:[-+].*)?$/;
17645
17875
  function isVersionDirName(name) {
17646
17876
  return VERSION_DIR.test(name);
17647
17877
  }
17648
- function selectPrunablePluginVersions(names, currentVersion) {
17878
+ function selectPrunablePluginVersions(names, currentVersion, installedVersion) {
17649
17879
  const versions = [...new Set(names)].filter(isVersionDirName);
17650
17880
  if (versions.length <= PLUGIN_CACHE_KEEP) return [];
17881
+ if (!currentVersion && !installedVersion) return [];
17651
17882
  const newestFirst = [...versions].sort((a, b) => compareVersions(b, a));
17652
17883
  const keep = /* @__PURE__ */ new Set();
17884
+ keep.add(newestFirst[0]);
17653
17885
  if (currentVersion && versions.includes(currentVersion)) keep.add(currentVersion);
17886
+ if (installedVersion && versions.includes(installedVersion)) keep.add(installedVersion);
17654
17887
  for (const v of newestFirst) {
17655
17888
  if (keep.size >= PLUGIN_CACHE_KEEP) break;
17656
17889
  keep.add(v);
@@ -17778,7 +18011,7 @@ function buildPluginCachePlan(home, running, deps, opts = {}) {
17778
18011
  return absent;
17779
18012
  }
17780
18013
  const versions = names.filter(isVersionDirName);
17781
- const prune = selectPrunablePluginVersions(versions, running);
18014
+ const prune = selectPrunablePluginVersions(versions, running, opts.installedVersion);
17782
18015
  const pruneSet = new Set(prune);
17783
18016
  const keep = [...versions].sort((a, b) => compareVersions(b, a)).filter((v) => !pruneSet.has(v));
17784
18017
  const bytes = opts.withBytes ? prune.reduce((sum, v) => sum + deps.dirBytes(`${cacheRoot}/${v}`), 0) : 0;
@@ -17877,6 +18110,52 @@ function renderPluginCachePlan(plan, applied) {
17877
18110
  return lines.join("\n");
17878
18111
  }
17879
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
+
17880
18159
  // src/skill-lesson.ts
17881
18160
  var SKILL_LESSON_LABEL = "skill-lesson";
17882
18161
  var SKILL_NAMES = ["bootstrap", "browser-automation", "doctor", "epic", "hotfix", "mmi", "onboard", "rcand", "release", "resume", "secrets", "stage", "worktree"];
@@ -19304,8 +19583,8 @@ function renderAccessReport(report) {
19304
19583
  // src/repo-index.ts
19305
19584
  var import_node_crypto4 = require("node:crypto");
19306
19585
  var import_node_child_process11 = require("node:child_process");
19307
- var import_node_fs22 = require("node:fs");
19308
- var import_node_path20 = require("node:path");
19586
+ var import_node_fs23 = require("node:fs");
19587
+ var import_node_path21 = require("node:path");
19309
19588
  var REPO_INDEX_SCHEMA = 1;
19310
19589
  var HARD_DENY = [
19311
19590
  /(^|\/)\.env(\.|$)/i,
@@ -19430,11 +19709,11 @@ function loadReadmeHints(cwd, candidatePaths) {
19430
19709
  }
19431
19710
  for (const rel of readmes) {
19432
19711
  if (isHardDeniedPath(rel)) continue;
19433
- const abs = (0, import_node_path20.join)(cwd, ...rel.split("/"));
19434
- 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;
19435
19714
  let text;
19436
19715
  try {
19437
- text = (0, import_node_fs22.readFileSync)(abs, "utf8");
19716
+ text = (0, import_node_fs23.readFileSync)(abs, "utf8");
19438
19717
  } catch {
19439
19718
  continue;
19440
19719
  }
@@ -19447,7 +19726,7 @@ function loadReadmeHints(cwd, candidatePaths) {
19447
19726
  return hints;
19448
19727
  }
19449
19728
  function toPosix(p) {
19450
- return p.split(import_node_path20.sep).join("/");
19729
+ return p.split(import_node_path21.sep).join("/");
19451
19730
  }
19452
19731
  function listCandidatePaths(cwd, exec = import_node_child_process11.execFileSync) {
19453
19732
  try {
@@ -19469,11 +19748,11 @@ function rebuildRepoIndex(cwd, repoSlug2) {
19469
19748
  for (const rel of candidates) {
19470
19749
  if (ignored.has(rel)) continue;
19471
19750
  if (isHardDeniedPath(rel)) continue;
19472
- const abs = (0, import_node_path20.join)(cwd, ...rel.split("/"));
19473
- 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;
19474
19753
  let text;
19475
19754
  try {
19476
- text = (0, import_node_fs22.readFileSync)(abs, "utf8");
19755
+ text = (0, import_node_fs23.readFileSync)(abs, "utf8");
19477
19756
  } catch {
19478
19757
  continue;
19479
19758
  }
@@ -19498,16 +19777,16 @@ function rebuildRepoIndex(cwd, repoSlug2) {
19498
19777
  entries
19499
19778
  };
19500
19779
  const store = repoIndexStorePath(cwd);
19501
- (0, import_node_fs22.mkdirSync)((0, import_node_path20.dirname)(store), { recursive: true });
19502
- (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)}
19503
19782
  `, "utf8");
19504
19783
  return projection;
19505
19784
  }
19506
19785
  function loadRepoIndex(cwd) {
19507
19786
  const store = repoIndexStorePath(cwd);
19508
- if (!(0, import_node_fs22.existsSync)(store)) return null;
19787
+ if (!(0, import_node_fs23.existsSync)(store)) return null;
19509
19788
  try {
19510
- const raw = JSON.parse((0, import_node_fs22.readFileSync)(store, "utf8"));
19789
+ const raw = JSON.parse((0, import_node_fs23.readFileSync)(store, "utf8"));
19511
19790
  if (raw?.schema !== REPO_INDEX_SCHEMA || !Array.isArray(raw.entries)) return null;
19512
19791
  return raw;
19513
19792
  } catch {
@@ -19579,7 +19858,7 @@ function inferRepoSlug(cwd, exec = import_node_child_process11.execFileSync) {
19579
19858
  if (m?.[1]) return m[1].toLowerCase();
19580
19859
  } catch {
19581
19860
  }
19582
- return ((0, import_node_path20.basename)(cwd) || "local").toLowerCase();
19861
+ return ((0, import_node_path21.basename)(cwd) || "local").toLowerCase();
19583
19862
  }
19584
19863
 
19585
19864
  // src/repo-index-cloud-client.ts
@@ -19719,9 +19998,9 @@ async function gcRepoIndexCloud(deps) {
19719
19998
  }
19720
19999
 
19721
20000
  // src/repo-index-sync.ts
19722
- var import_node_fs23 = require("node:fs");
20001
+ var import_node_fs24 = require("node:fs");
19723
20002
  var import_node_os9 = require("node:os");
19724
- var import_node_path21 = require("node:path");
20003
+ var import_node_path22 = require("node:path");
19725
20004
  var import_node_child_process12 = require("node:child_process");
19726
20005
  var MAX_EMBED_BACKFILL_ROUNDS = 40;
19727
20006
  function normalizeRepo(raw) {
@@ -19765,7 +20044,7 @@ async function syncEstateRepoIndex(opts) {
19765
20044
  const failed = [];
19766
20045
  const skipped = [];
19767
20046
  for (const repo of repos) {
19768
- 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-"));
19769
20048
  try {
19770
20049
  shallowClone(repo, dir, opts.githubToken);
19771
20050
  const built = rebuildRepoIndex(dir, repo);
@@ -19815,7 +20094,7 @@ async function syncEstateRepoIndex(opts) {
19815
20094
  failed.push({ repo, error: e.message });
19816
20095
  } finally {
19817
20096
  try {
19818
- (0, import_node_fs23.rmSync)(dir, { recursive: true, force: true });
20097
+ (0, import_node_fs24.rmSync)(dir, { recursive: true, force: true });
19819
20098
  } catch {
19820
20099
  }
19821
20100
  }
@@ -19824,18 +20103,57 @@ async function syncEstateRepoIndex(opts) {
19824
20103
  }
19825
20104
 
19826
20105
  // src/repo-index-health.ts
19827
- var import_node_fs24 = require("node:fs");
19828
- var import_node_path22 = require("node:path");
19829
- function defaultGoldenPath(repoRoot2) {
19830
- return (0, import_node_path22.join)(repoRoot2, "cli", "testdata", "repo-index-golden-queries.json");
19831
- }
19832
- function loadGoldenSuite(path2) {
19833
- const raw = JSON.parse((0, import_node_fs24.readFileSync)(path2, "utf8"));
20106
+ var import_node_fs25 = require("node:fs");
20107
+
20108
+ // testdata/repo-index-golden-queries.json
20109
+ var repo_index_golden_queries_default = {
20110
+ schema: 1,
20111
+ staleThresholdHours: 12,
20112
+ hubRepo: "mutmutco/MMI-Hub",
20113
+ hubEmbMinCoverage: 0.5,
20114
+ maxSyncEmbeds: 100,
20115
+ queries: [
20116
+ {
20117
+ id: "rebuild-symbol-lexical",
20118
+ q: "rebuildRepoIndex",
20119
+ mode: "lexical",
20120
+ expect: {
20121
+ topRepos: ["mutmutco/MMI-Hub"],
20122
+ topPaths: ["cli/src/repo-index.ts"],
20123
+ minScore: 0.1
20124
+ }
20125
+ },
20126
+ {
20127
+ id: "schedules-register-semantic",
20128
+ q: "schedules register",
20129
+ mode: "semantic",
20130
+ expect: {
20131
+ topRepos: ["mutmutco/MMI-Hub"],
20132
+ minScore: 0.05
20133
+ }
20134
+ }
20135
+ ]
20136
+ };
20137
+
20138
+ // src/repo-index-health.ts
20139
+ function assertGoldenSuite(raw, source) {
19834
20140
  if (!raw || raw.schema !== 1 || !Array.isArray(raw.queries)) {
19835
- throw new Error(`invalid golden suite at ${path2}`);
20141
+ throw new Error(`invalid golden suite at ${source}`);
19836
20142
  }
19837
20143
  return raw;
19838
20144
  }
20145
+ function loadGoldenSuite(path2) {
20146
+ let text;
20147
+ try {
20148
+ text = (0, import_node_fs25.readFileSync)(path2, "utf8");
20149
+ } catch (e) {
20150
+ throw new Error(`golden suite unreadable at ${path2}: ${e.message}`);
20151
+ }
20152
+ return assertGoldenSuite(JSON.parse(text), path2);
20153
+ }
20154
+ function defaultGoldenSuite() {
20155
+ return assertGoldenSuite(repo_index_golden_queries_default, "bundled repo-index-golden-queries.json");
20156
+ }
19839
20157
  function evaluateQueryHits(query, hits) {
19840
20158
  if (!hits.length) {
19841
20159
  return { ok: false, code: "empty-hits", detail: `${query.id}: no hits for ${JSON.stringify(query.q)}` };
@@ -19971,7 +20289,7 @@ async function runRepoIndexHealth(opts) {
19971
20289
 
19972
20290
  // src/spawn-policy-core.ts
19973
20291
  var import_node_child_process13 = require("node:child_process");
19974
- var import_node_fs25 = require("node:fs");
20292
+ var import_node_fs26 = require("node:fs");
19975
20293
  var import_node_path23 = require("node:path");
19976
20294
  var SPAWNERS = ["spawn", "spawnSync", "exec", "execSync", "execFile", "execFileSync"];
19977
20295
  var CALL_SOURCE = String.raw`(^|[^.\w$])(${SPAWNERS.join("|")})\s*\(`;
@@ -20058,7 +20376,7 @@ function runSpawnPolicy(root) {
20058
20376
  for (const file of files) {
20059
20377
  let raw;
20060
20378
  try {
20061
- raw = (0, import_node_fs25.readFileSync)((0, import_node_path23.join)(root, file), "utf8");
20379
+ raw = (0, import_node_fs26.readFileSync)((0, import_node_path23.join)(root, file), "utf8");
20062
20380
  } catch {
20063
20381
  continue;
20064
20382
  }
@@ -20076,7 +20394,7 @@ function runSpawnPolicy(root) {
20076
20394
 
20077
20395
  // src/test-policy-core.ts
20078
20396
  var import_node_child_process14 = require("node:child_process");
20079
- var import_node_fs26 = require("node:fs");
20397
+ var import_node_fs27 = require("node:fs");
20080
20398
  var import_node_path24 = require("node:path");
20081
20399
  var POLICY_FILE = "test-policy.json";
20082
20400
  var TEST_RE = /\.(?:test|spec)\.[cm]?[jt]sx?$/;
@@ -20140,7 +20458,7 @@ function loadPolicy(root, readFile9 = readFileOrNull2) {
20140
20458
  }
20141
20459
  function readFileOrNull2(path2) {
20142
20460
  try {
20143
- return (0, import_node_fs26.readFileSync)(path2, "utf8");
20461
+ return (0, import_node_fs27.readFileSync)(path2, "utf8");
20144
20462
  } catch {
20145
20463
  return null;
20146
20464
  }
@@ -20167,10 +20485,10 @@ function classify(changed, policy, present = () => false) {
20167
20485
  const removedProtected = [...removed].filter((p) => protectedBy.has(p)).map((p) => ({ path: p, why: protectedBy.get(p) ?? "" }));
20168
20486
  return { mandatoryHits, untestedHits, testChanges, addedTests, removedProtected };
20169
20487
  }
20170
- function unresolvedProtectedEntries(policy, root, exists = (path2) => (0, import_node_fs26.existsSync)(path2)) {
20488
+ function unresolvedProtectedEntries(policy, root, exists = (path2) => (0, import_node_fs27.existsSync)(path2)) {
20171
20489
  return (policy.protected ?? []).map((p) => p.path).filter((p) => !exists((0, import_node_path24.join)(root, p)));
20172
20490
  }
20173
- 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)) {
20174
20492
  const declared = (policy.mandatory ?? []).flatMap((m) => m.satisfiedBy ?? []);
20175
20493
  return [...new Set(declared)].filter((p) => !exists((0, import_node_path24.join)(root, p)));
20176
20494
  }
@@ -20354,7 +20672,7 @@ function changedFilesSince(base, cwd) {
20354
20672
  }
20355
20673
  function runTestPolicy(root, deps = {}) {
20356
20674
  const policy = deps.policy ?? loadPolicy(root);
20357
- const exists = deps.exists ?? ((path2) => (0, import_node_fs26.existsSync)(path2));
20675
+ const exists = deps.exists ?? ((path2) => (0, import_node_fs27.existsSync)(path2));
20358
20676
  const counts = { mandatoryCount: (policy.mandatory ?? []).length, protectedCount: (policy.protected ?? []).length };
20359
20677
  const base = deps.changed ? "(injected)" : resolveBase(root, deps.base);
20360
20678
  const refusal = deps.changed ? null : untrustworthyRange(root, base);
@@ -20516,7 +20834,7 @@ function docsAuditStatus(fetch2, opts) {
20516
20834
  }
20517
20835
 
20518
20836
  // src/project-info-sync.ts
20519
- var import_node_fs27 = require("node:fs");
20837
+ var import_node_fs28 = require("node:fs");
20520
20838
  var import_node_path25 = require("node:path");
20521
20839
  var UPDATE_PROJECT_INFO = `mutation($projectId: ID!, $shortDescription: String!, $readme: String!) {
20522
20840
  updateProjectV2(input: { projectId: $projectId, shortDescription: $shortDescription, readme: $readme }) {
@@ -20563,13 +20881,13 @@ function sharedName(entries, fallback) {
20563
20881
  function buildProjectInfoSyncPlan(targetRepo2, project2, projects, repoRoot2) {
20564
20882
  if (!project2.projectId) throw new Error(`org project sync-info: ${targetRepo2} registry META has no projectId`);
20565
20883
  const readmePath = (0, import_node_path25.join)(repoRoot2, "README.md");
20566
- if (!(0, import_node_fs27.existsSync)(readmePath)) throw new Error(`org project sync-info: ${targetRepo2} has no README.md`);
20884
+ if (!(0, import_node_fs28.existsSync)(readmePath)) throw new Error(`org project sync-info: ${targetRepo2} has no README.md`);
20567
20885
  const entries = entriesFor(project2, projects);
20568
20886
  const memberRepos = [...new Set(entries.flatMap((entry) => entry.repos ?? []))].filter((repo) => /^[^/]+\/[^/]+$/.test(repo)).sort((a, b) => a.localeCompare(b));
20569
20887
  const projectName = sharedName(entries, project2.name?.trim() || targetRepo2.split("/").pop() || targetRepo2);
20570
20888
  if (!memberRepos.length) throw new Error(`org project sync-info: project ${projectName} has no registered member repos`);
20571
20889
  const entryNames = entries.map((entry) => entry.name?.trim()).filter((name) => Boolean(name));
20572
- 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)}.`;
20573
20891
  const lines = [
20574
20892
  `# ${projectName}`,
20575
20893
  "",
@@ -20588,8 +20906,8 @@ function buildProjectInfoSyncPlan(targetRepo2, project2, projects, repoRoot2) {
20588
20906
  const targetBase = `https://github.com/${targetRepo2}`;
20589
20907
  const targetBranch = branchFor(targetRepo2, projects);
20590
20908
  const orgDocs = [
20591
- (0, import_node_fs27.existsSync)((0, import_node_path25.join)(repoRoot2, "docs", "org-readme.md")) ? `- [Org identity](${targetBase}/blob/${targetBranch}/docs/org-readme.md)` : "",
20592
- (0, import_node_fs27.existsSync)((0, import_node_path25.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)` : ""
20593
20911
  ].filter(Boolean);
20594
20912
  if (orgDocs.length) lines.push("", "## Organisation docs", "", ...orgDocs);
20595
20913
  return { projectId: project2.projectId, projectName, targetRepo: targetRepo2, memberRepos, shortDescription, readme: `${lines.join("\n")}
@@ -21448,7 +21766,7 @@ function writeError(res) {
21448
21766
  }
21449
21767
 
21450
21768
  // src/secrets-commands.ts
21451
- var import_node_fs28 = require("node:fs");
21769
+ var import_node_fs29 = require("node:fs");
21452
21770
  var import_node_path26 = require("node:path");
21453
21771
  var import_node_os10 = require("node:os");
21454
21772
 
@@ -21583,8 +21901,8 @@ async function decryptRailsCredentials(input) {
21583
21901
  MMI_RAILS_CREDENTIALS_FILE: credentialsPath,
21584
21902
  MMI_RAILS_MASTER_KEY_FILE: masterKeyPath
21585
21903
  };
21586
- if ((0, import_node_fs28.existsSync)(masterKeyPath)) {
21587
- 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();
21588
21906
  }
21589
21907
  const script = [
21590
21908
  'require "json"',
@@ -21594,9 +21912,9 @@ async function decryptRailsCredentials(input) {
21594
21912
  'config = ActiveSupport::EncryptedConfiguration.new(config_path: config_path, key_path: key_path, env_key: "RAILS_MASTER_KEY", raise_if_missing_key: true)',
21595
21913
  "puts JSON.generate(config.config)"
21596
21914
  ].join("\n");
21597
- const scriptDir = (0, import_node_fs28.mkdtempSync)((0, import_node_path26.join)((0, import_node_os10.tmpdir)(), "mmi-rails-decrypt-"));
21915
+ const scriptDir = (0, import_node_fs29.mkdtempSync)((0, import_node_path26.join)((0, import_node_os10.tmpdir)(), "mmi-rails-decrypt-"));
21598
21916
  const scriptPath = (0, import_node_path26.join)(scriptDir, "decrypt.rb");
21599
- (0, import_node_fs28.writeFileSync)(scriptPath, script, "utf8");
21917
+ (0, import_node_fs29.writeFileSync)(scriptPath, script, "utf8");
21600
21918
  try {
21601
21919
  const args = ["exec", "ruby", scriptPath];
21602
21920
  const cmd = process.platform === "win32" ? "cmd.exe" : "bundle";
@@ -21608,7 +21926,7 @@ async function decryptRailsCredentials(input) {
21608
21926
  });
21609
21927
  return JSON.parse(stdout);
21610
21928
  } finally {
21611
- (0, import_node_fs28.rmSync)(scriptDir, { recursive: true, force: true });
21929
+ (0, import_node_fs29.rmSync)(scriptDir, { recursive: true, force: true });
21612
21930
  }
21613
21931
  }
21614
21932
  async function readSecretStdin() {
@@ -21698,7 +22016,7 @@ function registerSecretsCommands(program3) {
21698
22016
  let body;
21699
22017
  if (o.file) {
21700
22018
  try {
21701
- body = (0, import_node_fs28.readFileSync)((0, import_node_path26.resolve)(o.file), "utf8");
22019
+ body = (0, import_node_fs29.readFileSync)((0, import_node_path26.resolve)(o.file), "utf8");
21702
22020
  } catch (e) {
21703
22021
  return fail(`secrets org-catalog: cannot read --file ${o.file}: ${e.message}`);
21704
22022
  }
@@ -21803,7 +22121,7 @@ function registerSecretsCommands(program3) {
21803
22121
  {
21804
22122
  ...d,
21805
22123
  decryptRailsCredentials,
21806
- removeFile: (path2) => (0, import_node_fs28.unlinkSync)((0, import_node_path26.resolve)(o.appDir ?? process.cwd(), path2))
22124
+ removeFile: (path2) => (0, import_node_fs29.unlinkSync)((0, import_node_path26.resolve)(o.appDir ?? process.cwd(), path2))
21807
22125
  },
21808
22126
  {
21809
22127
  repo: o.repo,
@@ -21967,7 +22285,7 @@ async function activateAppActor(commandPath3, env, mint) {
21967
22285
  }
21968
22286
 
21969
22287
  // src/box-commands.ts
21970
- var import_node_fs29 = require("node:fs");
22288
+ var import_node_fs30 = require("node:fs");
21971
22289
 
21972
22290
  // src/box.ts
21973
22291
  var BOX_KEYS = {
@@ -22170,7 +22488,7 @@ function registerBoxCommands(program3) {
22170
22488
  }
22171
22489
  if (o.json) console.log(JSON.stringify({ box: found, incomplete }, null, 2));
22172
22490
  else if (o.ssh && o.script) {
22173
- (0, import_node_fs29.writeFileSync)(o.script, sshRecipeScript(found), "utf8");
22491
+ (0, import_node_fs30.writeFileSync)(o.script, sshRecipeScript(found), "utf8");
22174
22492
  console.log(`wrote ${o.script} \u2014 run: bash "${o.script}"`);
22175
22493
  } else if (o.ssh) console.log(`${formatSshRecipe(found)}
22176
22494
  ${SSH_RECIPE_AGENT_NOTE}`);
@@ -23603,7 +23921,7 @@ function registerQueryCommands(program3) {
23603
23921
  }
23604
23922
 
23605
23923
  // src/bootstrap-commands.ts
23606
- var import_node_fs30 = require("node:fs");
23924
+ var import_node_fs31 = require("node:fs");
23607
23925
  var import_node_os11 = require("node:os");
23608
23926
  var import_node_path29 = require("node:path");
23609
23927
 
@@ -23877,6 +24195,11 @@ async function verifyBootstrap(repo, repoClass, deps, releaseTrack) {
23877
24195
  const repoInfo = await restJson3(deps, `repos/${repo}`, {});
23878
24196
  checks.push({ ok: Boolean(repoInfo.default_branch), label: "repo exists" });
23879
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
+ });
23880
24203
  const branchList = await restPagedJson2(deps, `repos/${repo}/branches`, []);
23881
24204
  const branchNames = new Set(branchList.map((b) => b.name));
23882
24205
  for (const branch of branchesWanted) {
@@ -24318,13 +24641,13 @@ function registerBootstrapCommands(program3) {
24318
24641
  client: defaultGitHubClient(),
24319
24642
  projectMeta: meta,
24320
24643
  deployModel: typeof meta?.deployModel === "string" ? meta.deployModel : void 0,
24321
- 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,
24322
24645
  // requiredGcpApis is stored as an array by a JSON write, but `org project set --var KEY=VALUE` stores a raw
24323
24646
  // comma-string — accept either so the seeded value verifies regardless of how it was written.
24324
24647
  // #3689: the same committed map the org access audit reads (#3664), so a sanctioned admin is not a
24325
24648
  // permanent bootstrap failure on one surface and an intended state on the other. Absent file → no
24326
24649
  // sanction, which is the pre-#3664 behaviour.
24327
- 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,
24328
24651
  requiredGcpApis: (() => {
24329
24652
  const v = meta?.requiredGcpApis;
24330
24653
  if (Array.isArray(v)) return v;
@@ -24377,12 +24700,12 @@ function registerBootstrapCommands(program3) {
24377
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 () => {
24378
24701
  const o = { repo: rawValue("--repo", ""), json: rawFlag("--json") };
24379
24702
  const manifestPath = "skills/bootstrap/seeds/manifest.json";
24380
- 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`);
24381
- 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"));
24382
24705
  const hubContents = /* @__PURE__ */ new Map();
24383
24706
  for (const s of manifest.seeds) {
24384
24707
  if (s.ownership !== "org" || s.source !== "self") continue;
24385
- 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);
24386
24709
  }
24387
24710
  let targets;
24388
24711
  let classOf = (_repo) => "deployable";
@@ -24459,8 +24782,8 @@ function registerBootstrapCommands(program3) {
24459
24782
  return fail(`bootstrap apply: ${e.message}`);
24460
24783
  }
24461
24784
  const manifestPath = "skills/bootstrap/seeds/manifest.json";
24462
- 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`);
24463
- 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"));
24464
24787
  const baseBranch = o.class === "content" ? "main" : "development";
24465
24788
  const slug = parsedRepo.slug;
24466
24789
  const onlyTarget = o.only.trim();
@@ -24471,16 +24794,16 @@ function registerBootstrapCommands(program3) {
24471
24794
  ${known}`);
24472
24795
  }
24473
24796
  const gh = async (args) => execFileP2("gh", args, { timeout: 2e4 });
24474
- 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;
24475
24798
  const enc = (p) => p.split("/").map(encodeURIComponent).join("/");
24476
24799
  const putSeed = async (target, content, ref, sha) => {
24477
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`);
24478
- (0, import_node_fs30.writeFileSync)(tmp, JSON.stringify(contentPutBody(target, content, ref, sha)), "utf8");
24801
+ (0, import_node_fs31.writeFileSync)(tmp, JSON.stringify(contentPutBody(target, content, ref, sha)), "utf8");
24479
24802
  try {
24480
24803
  await gh(contentPutInputArgs(repo, target, tmp));
24481
24804
  } finally {
24482
24805
  try {
24483
- (0, import_node_fs30.unlinkSync)(tmp);
24806
+ (0, import_node_fs31.unlinkSync)(tmp);
24484
24807
  } catch {
24485
24808
  }
24486
24809
  }
@@ -24654,6 +24977,14 @@ function registerBootstrapCommands(program3) {
24654
24977
  });
24655
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})`);
24656
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
+ }
24657
24988
  if (o.execute && !onlyTarget && o.class === "deployable") {
24658
24989
  try {
24659
24990
  await gh(["api", "-X", "PATCH", `repos/${repo}`, "-f", "allow_auto_merge=true", "-f", "allow_squash_merge=true", "-f", "delete_branch_on_merge=true"]);
@@ -24737,11 +25068,11 @@ LIVE apply to ${repo}:
24737
25068
  }
24738
25069
 
24739
25070
  // src/stage-commands.ts
24740
- var import_node_fs32 = require("node:fs");
25071
+ var import_node_fs33 = require("node:fs");
24741
25072
  var import_node_path31 = require("node:path");
24742
25073
 
24743
25074
  // src/port-registry.ts
24744
- var import_node_fs31 = require("node:fs");
25075
+ var import_node_fs32 = require("node:fs");
24745
25076
  var import_node_path30 = require("node:path");
24746
25077
 
24747
25078
  // ../infra/port-geometry.mjs
@@ -24756,8 +25087,8 @@ function nextPortBlock(registry2) {
24756
25087
  return [base, base + PORT_SPAN];
24757
25088
  }
24758
25089
  function loadPortRegistry(path2) {
24759
- if (!(0, import_node_fs31.existsSync)(path2)) return {};
24760
- 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"));
24761
25092
  const out = {};
24762
25093
  for (const [key, value] of Object.entries(raw)) {
24763
25094
  if (Array.isArray(value) && value.length === 2 && value.every((n) => typeof n === "number")) {
@@ -24771,9 +25102,9 @@ function ensurePortRange(repo, path2) {
24771
25102
  const existing = registry2[repo];
24772
25103
  if (existing) return existing;
24773
25104
  const range = nextPortBlock(registry2);
24774
- 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")) : {};
24775
25106
  raw[repo] = range;
24776
- (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");
24777
25108
  return range;
24778
25109
  }
24779
25110
  function portCursorSeed(registry2) {
@@ -24797,7 +25128,7 @@ function existingPortRange(repo, registry2) {
24797
25128
  function portRangeInfraAt(root, source) {
24798
25129
  const registryPath = (0, import_node_path30.join)(root, "infra", "port-ranges.json");
24799
25130
  const ddbScriptPath = (0, import_node_path30.join)(root, "infra", "port-ddb.mjs");
24800
- if (!(0, import_node_fs31.existsSync)(registryPath) || !(0, import_node_fs31.existsSync)(ddbScriptPath)) return null;
25131
+ if (!(0, import_node_fs32.existsSync)(registryPath) || !(0, import_node_fs32.existsSync)(ddbScriptPath)) return null;
24801
25132
  return { root, source, registryPath, ddbScriptPath };
24802
25133
  }
24803
25134
  function resolvePortRangeInfra(cwd, packageDir) {
@@ -25004,8 +25335,8 @@ function registerStageCommands(program3) {
25004
25335
  const portRange = portRangeMeta && typeof portRangeMeta.start === "number" && typeof portRangeMeta.end === "number" ? [portRangeMeta.start, portRangeMeta.end] : void 0;
25005
25336
  return decideStage({
25006
25337
  registry: { deployModel: project2?.deployModel, portRange, error: read.ok ? void 0 : read.error },
25007
- hasCompose: (0, import_node_fs32.existsSync)((0, import_node_path31.join)(process.cwd(), "docker-compose.yml")),
25008
- hasEnvExample: (0, import_node_fs32.existsSync)((0, import_node_path31.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"))
25009
25340
  });
25010
25341
  }
25011
25342
  async function fetchStageVaultEnvMerge() {
@@ -25443,7 +25774,7 @@ function registerBoardCommands(program3) {
25443
25774
  }
25444
25775
 
25445
25776
  // src/merge-cleanup.ts
25446
- var import_node_fs33 = require("node:fs");
25777
+ var import_node_fs34 = require("node:fs");
25447
25778
  var import_promises8 = require("node:fs/promises");
25448
25779
  var import_node_path33 = require("node:path");
25449
25780
  var import_node_os12 = require("node:os");
@@ -25768,7 +26099,7 @@ async function applyGcPlan(plan, remote, opts = {}) {
25768
26099
  const cleanup = await cleanupPrMergeLocalBranch(branch.branch, {
25769
26100
  beforeWorktrees,
25770
26101
  startingPath: branch.worktreePath,
25771
- pathExists: (p) => (0, import_node_fs33.existsSync)(p),
26102
+ pathExists: (p) => (0, import_node_fs34.existsSync)(p),
25772
26103
  execGit: async (args) => (await execFileP2("git", args, { timeout: GIT_TIMEOUT_MS })).stdout,
25773
26104
  teardownWorktreeStage,
25774
26105
  deferredStore,
@@ -25796,7 +26127,7 @@ async function applyGcPlan(plan, remote, opts = {}) {
25796
26127
  let removalAttempted = false;
25797
26128
  try {
25798
26129
  const cleanupTarget = resolveSafeSiblingWorktreeCleanupTarget(wt.path, siblingRoot, {
25799
- realpath: (path2) => (0, import_node_fs33.realpathSync)(path2)
26130
+ realpath: (path2) => (0, import_node_fs34.realpathSync)(path2)
25800
26131
  });
25801
26132
  if (!cleanupTarget.ok) {
25802
26133
  result.failed.push(`${wt.path}: ${cleanupTarget.reason}`);
@@ -25882,13 +26213,13 @@ async function composeOverrideBodyFile(prNumber, repoArgs, gh) {
25882
26213
  const commits = JSON.parse(raw).commits ?? [];
25883
26214
  const body = squashBodyWithOverride(commits.map((c) => ({ headline: c.messageHeadline ?? "", body: c.messageBody ?? "" })), process.cwd());
25884
26215
  if (!body) return void 0;
25885
- const dir = (0, import_node_fs33.mkdtempSync)((0, import_node_path33.join)((0, import_node_os12.tmpdir)(), "mmi-squash-body-"));
26216
+ const dir = (0, import_node_fs34.mkdtempSync)((0, import_node_path33.join)((0, import_node_os12.tmpdir)(), "mmi-squash-body-"));
25886
26217
  const path2 = (0, import_node_path33.join)(dir, "body.txt");
25887
- (0, import_node_fs33.writeFileSync)(path2, `${body}
26218
+ (0, import_node_fs34.writeFileSync)(path2, `${body}
25888
26219
  `, "utf8");
25889
26220
  return { path: path2, cleanup: () => {
25890
26221
  try {
25891
- (0, import_node_fs33.rmSync)(dir, { recursive: true, force: true });
26222
+ (0, import_node_fs34.rmSync)(dir, { recursive: true, force: true });
25892
26223
  } catch {
25893
26224
  }
25894
26225
  } };
@@ -26010,13 +26341,13 @@ var realWorktreeDirRemover = {
26010
26341
  probe: (p) => {
26011
26342
  let st;
26012
26343
  try {
26013
- st = (0, import_node_fs33.lstatSync)(p);
26344
+ st = (0, import_node_fs34.lstatSync)(p);
26014
26345
  } catch {
26015
26346
  return null;
26016
26347
  }
26017
26348
  if (st.isSymbolicLink()) return "link";
26018
26349
  try {
26019
- (0, import_node_fs33.readlinkSync)(p);
26350
+ (0, import_node_fs34.readlinkSync)(p);
26020
26351
  return "link";
26021
26352
  } catch {
26022
26353
  }
@@ -26024,7 +26355,7 @@ var realWorktreeDirRemover = {
26024
26355
  },
26025
26356
  readdir: (p) => {
26026
26357
  try {
26027
- return (0, import_node_fs33.readdirSync)(p);
26358
+ return (0, import_node_fs34.readdirSync)(p);
26028
26359
  } catch {
26029
26360
  return [];
26030
26361
  }
@@ -26033,9 +26364,9 @@ var realWorktreeDirRemover = {
26033
26364
  // leaving the target); a file symlink with unlink. rmdir first, fall back to unlink.
26034
26365
  detachLink: (p) => {
26035
26366
  try {
26036
- (0, import_node_fs33.rmdirSync)(p);
26367
+ (0, import_node_fs34.rmdirSync)(p);
26037
26368
  } catch {
26038
- (0, import_node_fs33.unlinkSync)(p);
26369
+ (0, import_node_fs34.unlinkSync)(p);
26039
26370
  }
26040
26371
  },
26041
26372
  removeTree: (p) => (0, import_promises8.rm)(p, { recursive: true, force: true, maxRetries: 5, retryDelay: 200 })
@@ -26068,9 +26399,9 @@ async function worktreeHasStageState(worktreePath) {
26068
26399
  }
26069
26400
  }
26070
26401
  function stageStateFileBelongsToWorktree(statePath, worktreePath) {
26071
- if (!(0, import_node_fs33.existsSync)(statePath)) return false;
26402
+ if (!(0, import_node_fs34.existsSync)(statePath)) return false;
26072
26403
  try {
26073
- const state = JSON.parse((0, import_node_fs33.readFileSync)(statePath, "utf8"));
26404
+ const state = JSON.parse((0, import_node_fs34.readFileSync)(statePath, "utf8"));
26074
26405
  const recordedCwd = typeof state.identity?.cwd === "string" ? state.identity.cwd : typeof state.cwd === "string" ? state.cwd : "";
26075
26406
  return Boolean(recordedCwd && isPathUnderDirectory(recordedCwd, worktreePath));
26076
26407
  } catch {
@@ -26401,7 +26732,7 @@ async function checkDocsIndexAtHead(opts, deps) {
26401
26732
  }
26402
26733
 
26403
26734
  // src/worktree-lifecycle-commands.ts
26404
- var import_node_fs34 = require("node:fs");
26735
+ var import_node_fs35 = require("node:fs");
26405
26736
  var import_promises9 = require("node:fs/promises");
26406
26737
  var import_node_path34 = require("node:path");
26407
26738
  var GH_TIMEOUT_MS = 2e4;
@@ -26549,7 +26880,7 @@ function classifyStaleLeaks(input) {
26549
26880
  var defaultOrphanDirScanDeps = {
26550
26881
  listDirs: (root) => {
26551
26882
  try {
26552
- return (0, import_node_fs34.readdirSync)(root, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => (0, import_node_path34.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));
26553
26884
  } catch {
26554
26885
  return [];
26555
26886
  }
@@ -26703,7 +27034,7 @@ function registerWorktreeCommands(program3) {
26703
27034
  const branch = symbolicBranch || (detached ? "HEAD" : "");
26704
27035
  if (!wtPath || !branch) return fail("worktree land: not inside a git worktree");
26705
27036
  const gitFile = (0, import_node_path34.join)(wtPath, ".git");
26706
- const isLinked = (0, import_node_fs34.existsSync)(gitFile) && (0, import_node_fs34.statSync)(gitFile).isFile();
27037
+ const isLinked = (0, import_node_fs35.existsSync)(gitFile) && (0, import_node_fs35.statSync)(gitFile).isFile();
26707
27038
  if (apply && !isLinked) {
26708
27039
  return fail("worktree land: run from inside the linked worktree you want to land (this is the primary checkout)");
26709
27040
  }
@@ -26922,7 +27253,7 @@ async function gatherWorktreeContext() {
26922
27253
  const primaryRepoRoot = worktreeGitRoot ? (0, import_node_path34.dirname)((0, import_node_path34.dirname)(worktreeGitRoot)) : repoRoot2;
26923
27254
  const wtRoot = siblingMmiWorktreesRoot(primaryRepoRoot);
26924
27255
  let orphanDirs = [];
26925
- if ((0, import_node_fs34.existsSync)(wtRoot)) {
27256
+ if ((0, import_node_fs35.existsSync)(wtRoot)) {
26926
27257
  orphanDirs = scanOrphanDirs(wtRoot, worktreeGitRoot, {
26927
27258
  ...defaultOrphanDirScanDeps,
26928
27259
  listDirs: (root) => worktreeScanDirs(root, primaryRepoRoot, defaultOrphanDirScanDeps.listDirs, isRepoCheckoutDir)
@@ -26948,7 +27279,7 @@ ${err.stderr ?? ""}`;
26948
27279
  }
26949
27280
 
26950
27281
  // src/issue-commands.ts
26951
- var import_node_fs35 = require("node:fs");
27282
+ var import_node_fs36 = require("node:fs");
26952
27283
  var import_node_crypto6 = require("node:crypto");
26953
27284
  var ghRunner = async (args, timeoutMs) => (await execFileP2("gh", args, { timeout: timeoutMs })).stdout;
26954
27285
  var ReparentConflictError = class extends Error {
@@ -26966,7 +27297,7 @@ async function editIssue(client, options, deps = {}) {
26966
27297
  const url = `https://github.com/${repo}/issues/${parsed.number}`;
26967
27298
  const patch = {};
26968
27299
  let bodyChanged = false;
26969
- 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("") };
26970
27301
  if (options.titleFile !== void 0) {
26971
27302
  patch.title = await resolveIssueTitle({ title: options.title, titleFile: options.titleFile }, textDeps());
26972
27303
  } else if (options.title !== void 0) {
@@ -27529,7 +27860,7 @@ function extendCreateCommand(issue2, batchAttach) {
27529
27860
  if (opts.batch) {
27530
27861
  let specs;
27531
27862
  try {
27532
- const raw = (0, import_node_fs35.readFileSync)(opts.batch, "utf8");
27863
+ const raw = (0, import_node_fs36.readFileSync)(opts.batch, "utf8");
27533
27864
  specs = JSON.parse(raw);
27534
27865
  if (!Array.isArray(specs)) throw new Error("batch file must contain a JSON array");
27535
27866
  } catch (e) {
@@ -27603,7 +27934,7 @@ ${lines}`, {
27603
27934
  }
27604
27935
 
27605
27936
  // src/train-commands.ts
27606
- var import_node_fs36 = require("node:fs");
27937
+ var import_node_fs37 = require("node:fs");
27607
27938
  var import_node_path35 = require("node:path");
27608
27939
 
27609
27940
  // src/train-status.ts
@@ -27644,7 +27975,7 @@ function formatTrainStatus(r) {
27644
27975
  // src/train-commands.ts
27645
27976
  function readRepoVersion() {
27646
27977
  try {
27647
- return JSON.parse((0, import_node_fs36.readFileSync)((0, import_node_path35.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;
27648
27979
  } catch {
27649
27980
  return void 0;
27650
27981
  }
@@ -27790,7 +28121,7 @@ function registerDeployCommands(program3) {
27790
28121
  }
27791
28122
 
27792
28123
  // src/discovery-commands.ts
27793
- var import_node_fs37 = require("node:fs");
28124
+ var import_node_fs38 = require("node:fs");
27794
28125
  var import_node_os13 = require("node:os");
27795
28126
  var import_node_path36 = require("node:path");
27796
28127
  var GC_GH_TIMEOUT_MS3 = 2e4;
@@ -27982,8 +28313,8 @@ async function collectOnboardStatus(opts = {}) {
27982
28313
  }
27983
28314
  const home = (0, import_node_os13.homedir)();
27984
28315
  const plugin = onboardPluginGate({
27985
- readKnown: () => readFileSyncSafe((0, import_node_path36.join)(home, ...KNOWN_MARKETPLACES_RELATIVE), import_node_fs37.readFileSync),
27986
- readSettings: () => readFileSyncSafe((0, import_node_path36.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)
27987
28318
  });
27988
28319
  return { track, board, registry: registry2, secrets, plugin, estateCli, nextCommand };
27989
28320
  }
@@ -29246,6 +29577,63 @@ var surfaces_default = {
29246
29577
  "Some read, write, and web-fetch tool outputs are not rewriteable by the host.",
29247
29578
  "Provisioned skills and agents become discoverable only after a fresh session."
29248
29579
  ]
29580
+ },
29581
+ {
29582
+ token: "jervcode",
29583
+ displayName: "JervCode",
29584
+ lifecycle: "active",
29585
+ artifactIds: ["mmi-pi-plugin", "mmi-skills", "mmi-cli"],
29586
+ plannedArtifactIds: [],
29587
+ assembly: {
29588
+ mode: "source-tree",
29589
+ rootPath: ".",
29590
+ sync: []
29591
+ },
29592
+ install: {
29593
+ mechanism: "managed-config",
29594
+ locator: "~/.pi/agent/settings.json"
29595
+ },
29596
+ upgrade: {
29597
+ mechanism: "reinstall",
29598
+ reload: "restart"
29599
+ },
29600
+ skills: {
29601
+ delivery: "manifest",
29602
+ sourceSurfaceId: "mmi-skills",
29603
+ artifactId: "mmi-skills",
29604
+ invocation: {
29605
+ entry: "/mmi",
29606
+ any: "/<skill>"
29607
+ }
29608
+ },
29609
+ hooks: {
29610
+ adapterPath: ".pi-plugin/package.json",
29611
+ sourceSurfaceId: "mmi-hook-policy",
29612
+ gates: [],
29613
+ probe: "unsupported",
29614
+ execution: "unsupported",
29615
+ preToolUse: "unsupported",
29616
+ postToolOutput: "unsupported",
29617
+ finalOutput: "unsupported"
29618
+ },
29619
+ cli: {
29620
+ delivery: "standalone",
29621
+ artifactId: "mmi-cli"
29622
+ },
29623
+ ownership: {
29624
+ trust: "host",
29625
+ cache: "host",
29626
+ repair: "mmi-cli"
29627
+ },
29628
+ certification: {
29629
+ hostCommand: "jervcode",
29630
+ versionArgs: ["--version"]
29631
+ },
29632
+ enforcementCeilings: [
29633
+ "Pi reads the user package set from ~/.pi/agent/settings.json at launch: a live seat keeps the packages it started with, the mmi-cli heal refuses to edit the file while PI_SESSION_ID is set, and a new registration reaches JervCode only on the next launch.",
29634
+ "No hook event is wired on this host \u2014 there is no PreToolUse deny gate, no vault-edit gate, and no secret-output redaction or detection; the hook policy artifact does not ship here.",
29635
+ "The settings entry points into a version-pinned plugin cache clone; a release that mints a new clone leaves the entry stale until `mmi-cli doctor` re-points it, and Pi keeps loading the dead path silently until then."
29636
+ ]
29249
29637
  }
29250
29638
  ],
29251
29639
  surfaces: [
@@ -29556,12 +29944,38 @@ var surfaces_default = {
29556
29944
  applicability: "master DM notifications only; chatops out by default",
29557
29945
  versionCoordinated: false,
29558
29946
  publishVisibility: "n/a"
29947
+ },
29948
+ {
29949
+ id: "mmi-pi-plugin",
29950
+ classification: "packaging",
29951
+ kind: "plugin",
29952
+ ownerPath: ".pi-plugin/package.json",
29953
+ deliveryPath: ".pi-plugin/package.json",
29954
+ delivery: "release",
29955
+ applicability: "JervCode (branded launcher of the pi coding agent). A generated pi package wrapper whose `pi` block references the canonical skills tree by relative path inside the shipped clone \u2014 content is never copied. mmi-cli registers the wrapper directory as a local-path entry in the `packages` array of ~/.pi/agent/settings.json.",
29956
+ versionCoordinated: true,
29957
+ surfaceToken: "jervcode",
29958
+ versionPaths: [
29959
+ { path: ".pi-plugin/package.json", pointer: "version" }
29960
+ ],
29961
+ artifactIdentity: {
29962
+ kind: "sha256-tree",
29963
+ paths: [".pi-plugin/package.json", "skills"]
29964
+ },
29965
+ verify: [
29966
+ {
29967
+ command: "node",
29968
+ args: ["scripts/check-jervcode-consumer.mjs"],
29969
+ expected: "JervCode consumer fitness: clean ({version})"
29970
+ }
29971
+ ],
29972
+ publishVisibility: "public"
29559
29973
  }
29560
29974
  ]
29561
29975
  };
29562
29976
 
29563
29977
  // src/surface-doctor.ts
29564
- var TOKENS = /* @__PURE__ */ new Set(["claude", "codex", "kimi", "cursor", "kilo"]);
29978
+ var TOKENS = /* @__PURE__ */ new Set(["claude", "codex", "kimi", "cursor", "kilo", "jervcode"]);
29565
29979
  function isActiveToken(value) {
29566
29980
  return TOKENS.has(value);
29567
29981
  }
@@ -29677,6 +30091,10 @@ function surfaceRestartAction(descriptor) {
29677
30091
  }
29678
30092
 
29679
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
+ }
29680
30098
  function checkGithubAuth(probe) {
29681
30099
  const login = probe.login?.trim();
29682
30100
  const authed = Boolean(login);
@@ -30043,7 +30461,7 @@ function gcReapable(plan) {
30043
30461
  }
30044
30462
  async function runDoctorClean(opts, io, deps) {
30045
30463
  const full = !opts.fast && !opts.banner && !opts.preflight;
30046
- const applyEnv = full || Boolean(opts.preflight);
30464
+ const applyEnv = full;
30047
30465
  const applyRepo = full && opts.repoWrites !== false;
30048
30466
  const lane = {
30049
30467
  banner: Boolean(opts.banner),
@@ -30070,11 +30488,14 @@ async function runDoctorClean(opts, io, deps) {
30070
30488
  const checks = [];
30071
30489
  let restartPending = false;
30072
30490
  const streamed = /* @__PURE__ */ new Set();
30491
+ const streamedLines = /* @__PURE__ */ new Set();
30492
+ const alreadyShown = (c) => streamed.has(c) || streamedLines.has(renderCheckLine(c));
30073
30493
  const worthPrinting = (c) => Boolean(opts.verbose) || !c.ok || Boolean(c.warn);
30074
30494
  const emitNow = (check) => {
30075
30495
  checks.push(check);
30076
- if (opts.json || !worthPrinting(check)) return;
30496
+ if (opts.json || !streamingPass || !worthPrinting(check)) return;
30077
30497
  streamed.add(check);
30498
+ streamedLines.add(renderCheckLine(check));
30078
30499
  io.log(renderCheckLine(check));
30079
30500
  if (opts.verbose) for (const evidence of check.verbose ?? []) io.log(`${VERBOSE_INDENT}${evidence}`);
30080
30501
  };
@@ -30086,13 +30507,35 @@ async function runDoctorClean(opts, io, deps) {
30086
30507
  };
30087
30508
  const releasedNote = deps.releasedVersionNote?.();
30088
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();
30089
30522
  async function runPluginRow() {
30090
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
+ }
30091
30533
  const diagnosis = diagnoseSurface({ ...registryEvidence, releasedVersion: released });
30092
30534
  const repair = planSurfaceRepair(diagnosis);
30093
- if (applyEnv && deps.healPlugin && repair?.supported) {
30535
+ if (applyEnv && deps.healPlugin && repair?.supported && spendOnce("plugin-chain")) {
30094
30536
  const { descriptor } = registryEvidence;
30095
30537
  healIntent(`${descriptor.displayName} plugin \u2014 healing via ${descriptor.installMechanism} (${descriptor.installLocator})`);
30538
+ traceHeal("plugin-chain");
30096
30539
  const heal = await deps.healPlugin(healStep);
30097
30540
  pluginHealed = heal.ok;
30098
30541
  const row = buildSurfaceDoctorCheck(diagnoseSurface({
@@ -30105,7 +30548,12 @@ async function runDoctorClean(opts, io, deps) {
30105
30548
  row.fix = `${heal.detail} \u2014 another doctor on this machine is healing it now; re-run once it finishes`;
30106
30549
  }
30107
30550
  emitNow(row);
30551
+ if (heal.skipped) spentHeals.delete("plugin-chain");
30108
30552
  if (!heal.skipped) restartPending = true;
30553
+ if (heal.ok && !heal.skipped) {
30554
+ markHealChanged();
30555
+ spentRows.set("plugin-chain", row);
30556
+ }
30109
30557
  } else if (diagnosis.state !== "skipped") {
30110
30558
  const row = buildSurfaceDoctorCheck(diagnosis);
30111
30559
  emitNow(row);
@@ -30117,13 +30565,18 @@ async function runDoctorClean(opts, io, deps) {
30117
30565
  }
30118
30566
  }
30119
30567
  async function runCliRow() {
30568
+ const spentRow = spentRows.get("cli-self-update");
30569
+ if (spentRow) {
30570
+ emitNow(spentRow);
30571
+ return;
30572
+ }
30120
30573
  const missing = deps.missingCliCommands?.() ?? [];
30121
30574
  const cliInput = { currentVersion: deps.currentCliVersion(), releasedVersion: released };
30122
30575
  const cliReport = buildVersionLagReport(cliInput);
30123
30576
  const capabilityGap = missing.length > 0 && Boolean(cliReport.releasedVersion);
30124
30577
  const shouldUpdate = Boolean(
30125
30578
  applyEnv && deps.updateCli && (versionAutoUpdateAction(cliReport) === "npm" || capabilityGap)
30126
- );
30579
+ ) && spendOnce("cli-self-update");
30127
30580
  if (!shouldUpdate) {
30128
30581
  const cli = checkCliVersion(cliInput, releasedNote);
30129
30582
  if (cli) {
@@ -30153,29 +30606,40 @@ async function runDoctorClean(opts, io, deps) {
30153
30606
  const target = cliReport.releasedVersion;
30154
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`;
30155
30608
  healIntent(intent);
30609
+ traceHeal("cli-self-update");
30156
30610
  const heal = await deps.updateCli(target, healStep);
30611
+ if (heal.skipped) spentHeals.delete("cli-self-update");
30612
+ if (heal.ok && !heal.skipped) markHealChanged();
30157
30613
  const healEvidence = [
30158
30614
  `running: ${cliReport.currentVersion}`,
30159
30615
  `published: ${target ?? "(unknown)"}`,
30160
30616
  `heal: ${heal.detail}`,
30161
30617
  ...missing.length ? [`missing commands before heal: ${missing.join(", ")}`] : []
30162
30618
  ];
30163
- emitNow(heal.ok ? {
30164
- id: "cli-version",
30165
- ok: true,
30166
- label: "mmi-cli",
30167
- 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`,
30168
- verbose: healEvidence
30169
- } : {
30170
- id: "cli-version",
30171
- ok: false,
30172
- label: "mmi-cli",
30173
- detail: capabilityGap && cliReport.ok ? `missing commands: ${missing.join(", ")}` : `${cliReport.currentVersion} \u2192 ${target}`,
30174
- // #3489: same split as the plugin heal above — a lock-contention skip is not this run's gap.
30175
- ...heal.skipped ? { reportOnly: true } : {},
30176
- 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)}\``,
30177
- verbose: healEvidence
30178
- });
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
+ );
30179
30643
  }
30180
30644
  async function runGithubAuthRow() {
30181
30645
  emitNow(checkGithubAuth({ login, ghInstalled: ghInstalled2, reach }));
@@ -30197,6 +30661,8 @@ async function runDoctorClean(opts, io, deps) {
30197
30661
  if (gi.ok) {
30198
30662
  emitNow({ ok: true, id: "gitignore-block", label: "gitignore block", verbose: giEvidence });
30199
30663
  } else if (applyRepo && gi.content && deps.writeGitignore(gi.content)) {
30664
+ traceHeal("repo-cleans");
30665
+ markHealChanged();
30200
30666
  emitNow({ ok: true, id: "gitignore-block", label: "gitignore block", detail: "rewrote managed block", verbose: giEvidence });
30201
30667
  restartPending = true;
30202
30668
  } else {
@@ -30204,8 +30670,100 @@ async function runDoctorClean(opts, io, deps) {
30204
30670
  }
30205
30671
  }
30206
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
+ }
30207
30709
  emitNow(checkPluginCache(deps.pluginCache()));
30208
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
+ }
30209
30767
  async function runSessionPayloadRow() {
30210
30768
  const payload = checkSessionPayload(deps.sessionPayload());
30211
30769
  if (payload) emitNow(payload);
@@ -30215,7 +30773,11 @@ async function runDoctorClean(opts, io, deps) {
30215
30773
  const healed = deps.healMarketplacePins();
30216
30774
  if (healed) {
30217
30775
  healIntent(`marketplace pins \u2014 ${healed.detail}`);
30218
- if (healed.wrote) restartPending = true;
30776
+ if (healed.wrote) {
30777
+ traceHeal("env-heals");
30778
+ markHealChanged();
30779
+ restartPending = true;
30780
+ }
30219
30781
  }
30220
30782
  }
30221
30783
  for (const row of deps.marketplaceRows()) emitNow(row);
@@ -30258,6 +30820,8 @@ async function runDoctorClean(opts, io, deps) {
30258
30820
  let healedWrite = false;
30259
30821
  if (applyRepo && probe?.drift && deps.healDocsIndex) {
30260
30822
  healIntent("docs index \u2014 regenerating docs/index.md");
30823
+ traceHeal("repo-cleans");
30824
+ markHealChanged();
30261
30825
  try {
30262
30826
  probe = deps.healDocsIndex(root);
30263
30827
  healedWrite = !probe.drift;
@@ -30478,7 +31042,11 @@ async function runDoctorClean(opts, io, deps) {
30478
31042
  `skipped: ${sweep.skipped}`
30479
31043
  ]
30480
31044
  });
30481
- if (sweep.removed.length) restartPending = true;
31045
+ if (sweep.removed.length) {
31046
+ traceHeal("repo-cleans");
31047
+ markHealChanged();
31048
+ restartPending = true;
31049
+ }
30482
31050
  } catch (e) {
30483
31051
  const message = e instanceof Error ? e.message : String(e);
30484
31052
  emitNow({
@@ -30516,7 +31084,11 @@ async function runDoctorClean(opts, io, deps) {
30516
31084
  ...r.failed.map((f) => `FAILED: ${f}`)
30517
31085
  ] : ["nothing reapable"]
30518
31086
  });
30519
- if (reaped) restartPending = true;
31087
+ if (reaped) {
31088
+ traceHeal("repo-cleans");
31089
+ markHealChanged();
31090
+ restartPending = true;
31091
+ }
30520
31092
  } else {
30521
31093
  const n = gcReapable(plan);
30522
31094
  const gcEvidence = [
@@ -30543,36 +31115,62 @@ async function runDoctorClean(opts, io, deps) {
30543
31115
  const applied = deps.executeScratchGc(repoRoot2, { apply: true });
30544
31116
  const pruned = applied.applied?.pruned.length ?? 0;
30545
31117
  emitNow({ id: "scratch", ok: true, label: "scratch", detail: pruned ? `removed ${pruned} aged item(s)` : "nothing stale", verbose: scratchEvidence });
30546
- if (pruned) restartPending = true;
31118
+ if (pruned) {
31119
+ traceHeal("repo-cleans");
31120
+ markHealChanged();
31121
+ restartPending = true;
31122
+ }
30547
31123
  } else {
30548
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 });
30549
31125
  }
30550
31126
  }
30551
31127
  const prefix = [
30552
- { id: "plugin", when: true, run: runPluginRow },
30553
31128
  { id: "cli-version", when: true, run: runCliRow },
31129
+ { id: "plugin", when: true, run: runPluginRow },
31130
+ { id: "plugin-cache", when: true, run: runPluginCacheRow },
30554
31131
  { id: "github-auth", when: true, run: runGithubAuthRow },
30555
31132
  { id: "aws-identity", when: true, run: runAwsRow },
30556
31133
  { id: "repo-worktrees", when: true, run: runRepoWorktreesRow },
30557
- { id: "gitignore-block", when: isOrgRepo, run: runGitignoreRow },
30558
- { id: "plugin-cache", when: true, run: runPluginCacheRow },
30559
31134
  { id: "sessionstart-payload", when: true, run: runSessionPayloadRow },
30560
- { id: "marketplace", when: true, run: runMarketplaceRows }
30561
- ];
30562
- for (const entry of prefix) if (entry.when) await entry.run();
30563
- const parallel = [
30564
- { id: "docs-index", when: isOrgRepo && lane.full, run: runDocsIndexRow },
30565
- // Hub cloud probe (short timeout). Full lane + `--self`; `--preflight` may emit command-absent only (#4156).
30566
- { id: "repo-index", when: isOrgRepo && (lane.full || Boolean(opts.self) || lane.preflight), run: runRepoIndexRow },
30567
- { id: "board-doctor", when: isOrgRepo && lane.full, run: runBoardDoctorRow },
30568
- { id: "schedules-drift", when: isOrgRepo && lane.full, run: runSchedulesDriftRow }
31135
+ { id: "marketplace", when: true, run: runMarketplaceRows },
31136
+ { id: "pi-plugin", when: true, run: runPiPluginRow },
31137
+ { id: "gitignore-block", when: isOrgRepo, run: runGitignoreRow }
30569
31138
  ];
30570
- await Promise.all(parallel.filter((entry) => entry.when).map((entry) => entry.run()));
30571
- const suffix = [
30572
- { id: "train-branches", when: isOrgRepo && lane.full, run: runTrainSyncRow },
30573
- { id: "housekeeper", when: isOrgRepo && lane.full, run: runHousekeeperRows }
30574
- ];
30575
- 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
+ }
30576
31174
  const exitCode = doctorReportExitCode(checks);
30577
31175
  if (opts.json) {
30578
31176
  const payload = opts.verbose ? checks : checks.map(({ verbose: _evidence, ...check }) => check);
@@ -30585,7 +31183,7 @@ async function runDoctorClean(opts, io, deps) {
30585
31183
  return exitCode;
30586
31184
  }
30587
31185
  if (opts.banner) {
30588
- const actionable = checks.filter((c) => (!c.ok || c.warn) && !streamed.has(c));
31186
+ const actionable = checks.filter((c) => (!c.ok || c.warn) && !alreadyShown(c));
30589
31187
  for (const c of actionable) {
30590
31188
  io.log(renderReport([c], { restartPending: false }));
30591
31189
  if (opts.verbose) for (const evidence of c.verbose ?? []) io.log(`${VERBOSE_INDENT}${evidence}`);
@@ -30593,7 +31191,7 @@ async function runDoctorClean(opts, io, deps) {
30593
31191
  if (restartPending) io.log(`\u21BB ${restartAction.charAt(0).toUpperCase()}${restartAction.slice(1)} to finish.`);
30594
31192
  return 0;
30595
31193
  }
30596
- const rest = checks.filter((c) => !streamed.has(c));
31194
+ const rest = checks.filter((c) => !alreadyShown(c));
30597
31195
  const shown = opts.verbose ? rest : rest.filter((c) => !c.ok || c.warn);
30598
31196
  const lines = [];
30599
31197
  for (const check of shown) {
@@ -30685,7 +31283,7 @@ function ghAccountCaveat(announcedLogin, accounts) {
30685
31283
  }
30686
31284
 
30687
31285
  // src/doctor-io.ts
30688
- var import_node_fs38 = require("node:fs");
31286
+ var import_node_fs39 = require("node:fs");
30689
31287
  var import_node_os14 = require("node:os");
30690
31288
  var import_node_path37 = require("node:path");
30691
31289
  var import_node_child_process17 = require("node:child_process");
@@ -30695,7 +31293,7 @@ var MMI_PLUGIN_ID2 = "mmi@mutmutco";
30695
31293
  function installedClaudePluginVersion() {
30696
31294
  try {
30697
31295
  const file = JSON.parse(
30698
- (0, import_node_fs38.readFileSync)((0, import_node_path37.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")
30699
31297
  );
30700
31298
  const versions = (file.plugins?.[MMI_PLUGIN_ID2] ?? []).map((r) => r.version).filter((v) => Boolean(v));
30701
31299
  if (versions.length === 0) return void 0;
@@ -30706,7 +31304,7 @@ function installedClaudePluginVersion() {
30706
31304
  }
30707
31305
  function manifestVersion(path2) {
30708
31306
  try {
30709
- const manifest = JSON.parse((0, import_node_fs38.readFileSync)(path2, "utf8"));
31307
+ const manifest = JSON.parse((0, import_node_fs39.readFileSync)(path2, "utf8"));
30710
31308
  return typeof manifest.version === "string" && manifest.version.trim() ? manifest.version.trim() : void 0;
30711
31309
  } catch {
30712
31310
  return void 0;
@@ -30716,7 +31314,7 @@ function installedSurfacePluginVersion(surface) {
30716
31314
  const token = surfaceToken(surface);
30717
31315
  if (token === "kilo") {
30718
31316
  try {
30719
- const stamp = (0, import_node_fs38.readFileSync)((0, import_node_path37.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();
30720
31318
  return stamp || void 0;
30721
31319
  } catch {
30722
31320
  return void 0;
@@ -30725,6 +31323,11 @@ function installedSurfacePluginVersion(surface) {
30725
31323
  if (token === "cursor") {
30726
31324
  return manifestVersion((0, import_node_path37.join)(cursorLocalPluginRoot(), ".cursor-plugin", "plugin.json"));
30727
31325
  }
31326
+ if (token === "jervcode") {
31327
+ const entry = mmiPiWrapperEntry();
31328
+ if (!entry) return void 0;
31329
+ return manifestVersion((0, import_node_path37.join)(decodeURIComponent(entry.replace(/^file:\/\/\/?/, "")), "package.json"));
31330
+ }
30728
31331
  if (token === "kimi") {
30729
31332
  return manifestVersion((0, import_node_path37.join)(surfaceConfigRoot(surface), "plugins", "managed", "mmi", ".kimi-plugin", "plugin.json"));
30730
31333
  }
@@ -30770,7 +31373,7 @@ function readGitignore() {
30770
31373
  const path2 = gitignorePath();
30771
31374
  if (path2 === null) return null;
30772
31375
  try {
30773
- return (0, import_node_fs38.readFileSync)(path2, "utf8");
31376
+ return (0, import_node_fs39.readFileSync)(path2, "utf8");
30774
31377
  } catch {
30775
31378
  return null;
30776
31379
  }
@@ -30779,7 +31382,7 @@ function writeGitignore(content) {
30779
31382
  const path2 = gitignorePath();
30780
31383
  if (path2 === null) return false;
30781
31384
  try {
30782
- (0, import_node_fs38.writeFileSync)(path2, content, "utf8");
31385
+ (0, import_node_fs39.writeFileSync)(path2, content, "utf8");
30783
31386
  return true;
30784
31387
  } catch {
30785
31388
  return false;
@@ -30803,7 +31406,7 @@ async function repoRoot() {
30803
31406
  }
30804
31407
  function hasRepoLocalWorktrees() {
30805
31408
  const root = worktreeRootSync();
30806
- return root !== null && (0, import_node_fs38.existsSync)((0, import_node_path37.join)(root, ".worktrees"));
31409
+ return root !== null && (0, import_node_fs39.existsSync)((0, import_node_path37.join)(root, ".worktrees"));
30807
31410
  }
30808
31411
 
30809
31412
  // src/index.ts
@@ -30839,8 +31442,8 @@ ${r.stderr ?? ""}`).catch(() => "");
30839
31442
  function ghMultiAccountCaveat(announcedLogin) {
30840
31443
  try {
30841
31444
  const hostsPath = ghHostsConfigPath(process.env, process.platform);
30842
- if (!hostsPath || !(0, import_node_fs39.existsSync)(hostsPath)) return void 0;
30843
- 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")));
30844
31447
  } catch {
30845
31448
  return void 0;
30846
31449
  }
@@ -30963,6 +31566,45 @@ function mmiDoctorDeps(opts = {}) {
30963
31566
  stagingBytes: plan.stagingBytes
30964
31567
  };
30965
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),
30966
31608
  // #3470: what the SessionStart hook LAST emitted, measured at emission by the session-start verb below.
30967
31609
  // A local record read ? cheap enough for every lane, including the banner.
30968
31610
  sessionPayload: () => readSessionPayload(process.cwd()),
@@ -30974,8 +31616,8 @@ function mmiDoctorDeps(opts = {}) {
30974
31616
  const home = (0, import_node_os15.homedir)();
30975
31617
  const rows = marketplaceRows(
30976
31618
  MMI_MARKETPLACE_NAME,
30977
- readFileSyncSafe((0, import_node_path38.join)(home, ...KNOWN_MARKETPLACES_RELATIVE), import_node_fs39.readFileSync),
30978
- readFileSyncSafe((0, import_node_path38.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),
30979
31621
  // #3974: this CLI heals these rows, so report mode names the doctor run rather than the hand
30980
31622
  // edit. Unconditional ? it is a fact about mmi-cli, not about the lane this run is on.
30981
31623
  true
@@ -31025,7 +31667,7 @@ function mmiDoctorDeps(opts = {}) {
31025
31667
  // adopted the generated routing index (MMG-Unlive: `docs/Archive/**` only, no index, no gate.yml) would
31026
31668
  // get a permanent ? demanding an artifact it never asked for.
31027
31669
  docsIndexState: (root) => {
31028
- if (!(0, import_node_fs39.existsSync)((0, import_node_path38.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;
31029
31671
  const real = createDocsIndexDeps(root);
31030
31672
  let docs2;
31031
31673
  const listDocs = () => docs2 ??= real.listDocs();
@@ -31034,7 +31676,7 @@ function mmiDoctorDeps(opts = {}) {
31034
31676
  },
31035
31677
  // #4168: working-tree heal — write if drifted, then re-check. Commit remains the operator's step.
31036
31678
  healDocsIndex: (root) => {
31037
- if (!(0, import_node_fs39.existsSync)((0, import_node_path38.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 };
31038
31680
  const real = createDocsIndexDeps(root);
31039
31681
  let docs2;
31040
31682
  const listDocs = () => docs2 ??= real.listDocs();
@@ -31369,18 +32011,18 @@ program2.hook("preAction", async (_thisCommand, actionCommand) => {
31369
32011
  var rules = program2.command("rules").description("org-managed .gitignore delivery");
31370
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) => {
31371
32013
  const path2 = (0, import_node_path38.join)(process.cwd(), ".gitignore");
31372
- const current = (0, import_node_fs39.existsSync)(path2) ? (0, import_node_fs39.readFileSync)(path2, "utf8") : null;
32014
+ const current = (0, import_node_fs40.existsSync)(path2) ? (0, import_node_fs40.readFileSync)(path2, "utf8") : null;
31373
32015
  const plan = planManagedGitignore(current);
31374
32016
  const drift = [...plan.added.map((l) => `+${l}`), ...plan.removed.map((l) => `-${l}`)].join(", ") || "block normalize";
31375
32017
  if (opts.json) {
31376
- 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");
31377
32019
  console.log(JSON.stringify(plan, null, 2));
31378
32020
  if (!opts.write && plan.changed) process.exitCode = 1;
31379
32021
  return;
31380
32022
  }
31381
32023
  if (opts.write) {
31382
32024
  if (plan.changed) {
31383
- (0, import_node_fs39.writeFileSync)(path2, plan.content, "utf8");
32025
+ (0, import_node_fs40.writeFileSync)(path2, plan.content, "utf8");
31384
32026
  console.log(`mmi-cli org rules gitignore: updated .gitignore (${drift})`);
31385
32027
  } else {
31386
32028
  console.log("mmi-cli org rules gitignore: up to date");
@@ -31538,7 +32180,7 @@ gcCmd.option("--dry-run", "show what would be deleted (default)").option("--appl
31538
32180
  let root;
31539
32181
  if (o.root !== void 0) {
31540
32182
  root = (0, import_node_path38.resolve)(o.root);
31541
- 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`);
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`);
31542
32184
  const gcRepoRoot = (await execFileP2("git", ["rev-parse", "--show-toplevel"], { timeout: GIT_TIMEOUT_MS }).catch(() => ({ stdout: "" }))).stdout.trim() || process.cwd();
31543
32185
  if (isPathUnderDirectory(gcRepoRoot, root)) {
31544
32186
  return fail(`worktree gc: --root ${root} contains this checkout ? name a worktrees root, not the repo or an ancestor of it`);
@@ -31617,7 +32259,7 @@ async function currentWorktreeRemovalContext(command, force) {
31617
32259
  };
31618
32260
  }
31619
32261
  async function unprovenWorktreeReason(wtPath, repoRoot2) {
31620
- 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`;
31621
32263
  const porcelain = (await execFileP2("git", ["-C", repoRoot2, "worktree", "list", "--porcelain"], { timeout: GIT_TIMEOUT_MS }).catch(() => ({ stdout: "" }))).stdout;
31622
32264
  const registered = parseWorktreePorcelainEntries(porcelain);
31623
32265
  if (!registered.length) {
@@ -31639,26 +32281,26 @@ function makeProvisionDeps(worktreeRoot, quiet, log) {
31639
32281
  function acquireWorktreeSetupLock(worktreeRoot) {
31640
32282
  const lockPath = repoRuntimeStatePath(worktreeRoot, "worktree-setup.lock");
31641
32283
  const take = () => {
31642
- const fd = (0, import_node_fs39.openSync)(lockPath, "wx");
32284
+ const fd = (0, import_node_fs40.openSync)(lockPath, "wx");
31643
32285
  try {
31644
- (0, import_node_fs39.writeSync)(fd, String(Date.now()));
32286
+ (0, import_node_fs40.writeSync)(fd, String(Date.now()));
31645
32287
  } finally {
31646
- (0, import_node_fs39.closeSync)(fd);
32288
+ (0, import_node_fs40.closeSync)(fd);
31647
32289
  }
31648
32290
  return () => {
31649
32291
  try {
31650
- (0, import_node_fs39.rmSync)(lockPath, { force: true });
32292
+ (0, import_node_fs40.rmSync)(lockPath, { force: true });
31651
32293
  } catch {
31652
32294
  }
31653
32295
  };
31654
32296
  };
31655
32297
  try {
31656
- (0, import_node_fs39.mkdirSync)((0, import_node_path38.dirname)(lockPath), { recursive: true });
32298
+ (0, import_node_fs40.mkdirSync)((0, import_node_path38.dirname)(lockPath), { recursive: true });
31657
32299
  return take();
31658
32300
  } catch {
31659
32301
  try {
31660
- if (Date.now() - (0, import_node_fs39.statSync)(lockPath).mtimeMs > WORKTREE_SETUP_LOCK_TTL_MS) {
31661
- (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 });
31662
32304
  return take();
31663
32305
  }
31664
32306
  } catch {
@@ -31965,7 +32607,7 @@ repoIndex.command("publish").description("publish this checkout's projection to
31965
32607
  return await failGraceful(e.message);
31966
32608
  }
31967
32609
  });
31968
- repoIndex.command("search").description("search Hub cloud by default (lexical/hybrid/semantic); use --local for checkout-only").argument("<query>", "path fragment, symbol, or meaning phrase").option("--limit <n>", "max hits", "20").option("--json", "machine-readable hits").option("--local", "search only the local projection (rebuild if missing)").option("--cloud", "force Hub cloud search (default when not --local)").option("--semantic", "semantic mode (Titan cosine over path+symbols embeddings)").option("--lexical", "lexical-only mode (paths/symbols)").option("--repo <owner/name>", "limit cloud search to one repo").action(async (query, o) => {
32610
+ async function runRepoIndexSearchCommand(query, o, defaultMode) {
31969
32611
  try {
31970
32612
  const limit = Math.max(1, Math.min(100, Number(o.limit ?? 20) || 20));
31971
32613
  const useLocal = o.local === true && o.cloud !== true;
@@ -31988,7 +32630,7 @@ repoIndex.command("search").description("search Hub cloud by default (lexical/hy
31988
32630
  }
31989
32631
  return;
31990
32632
  }
31991
- const mode = o.semantic ? "semantic" : o.lexical ? "lexical" : "hybrid";
32633
+ const mode = o.semantic ? "semantic" : o.lexical ? "lexical" : defaultMode;
31992
32634
  const cfg = await loadConfig();
31993
32635
  const res = await searchRepoIndexCloud(query, { mode, limit, repo: o.repo }, registryClientDeps(cfg));
31994
32636
  if (!res.ok) return await failGraceful(res.error);
@@ -32007,7 +32649,13 @@ repoIndex.command("search").description("search Hub cloud by default (lexical/hy
32007
32649
  } catch (e) {
32008
32650
  return await failGraceful(e.message);
32009
32651
  }
32010
- });
32652
+ }
32653
+ var REPO_INDEX_SEARCH_WHEN = 'beats grep for cross-repo questions, "where does X happen", and unknown filenames; grep wins inside a known checkout';
32654
+ function repoIndexSearchOptions(cmd) {
32655
+ return cmd.option("--limit <n>", "max hits", "20").option("--json", "machine-readable hits").option("--local", "search only the local projection (rebuild if missing)").option("--cloud", "force Hub cloud search (default when not --local)").option("--semantic", "semantic mode (Titan cosine over path+symbols embeddings)").option("--lexical", "lexical-only mode (paths/symbols)").option("--repo <owner/name>", "limit cloud search to one repo");
32656
+ }
32657
+ repoIndexSearchOptions(repoIndex.command("search").description(`search Hub cloud by default (lexical/hybrid/semantic); use --local for checkout-only \u2014 ${REPO_INDEX_SEARCH_WHEN}`).argument("<query>", "path fragment, symbol, or meaning phrase")).action(async (query, o) => runRepoIndexSearchCommand(query, o, "hybrid"));
32658
+ repoIndexSearchOptions(program2.command("find").description(`estate code search across every registered repo (repo-index search, semantic by default) \u2014 ${REPO_INDEX_SEARCH_WHEN}`).argument("<query>", "path fragment, symbol, or meaning phrase")).action(async (query, o) => runRepoIndexSearchCommand(query, o, "semantic"));
32011
32659
  repoIndex.command("status").description("show local and/or cloud repo-index status").option("--json", "machine-readable status").option("--cloud", "query Hub cloud status (estate or --repo)").option("--repo <owner/name>", "cloud status for one repo").action(async (o) => {
32012
32660
  try {
32013
32661
  if (o.cloud || o.repo) {
@@ -32059,11 +32707,10 @@ repoIndex.command("status").description("show local and/or cloud repo-index stat
32059
32707
  return await failGraceful(e.message);
32060
32708
  }
32061
32709
  });
32062
- repoIndex.command("health").description("post-deploy health gate: status + golden lexical/semantic queries (Hub#4149)").option("--live", "hit Hub cloud (status + searches); omit for offline golden-shape check").option("--golden <path>", "golden queries JSON (default: cli/testdata/repo-index-golden-queries.json)").option("--json", "machine-readable findings").action(async (o) => {
32710
+ repoIndex.command("health").description("post-deploy health gate: status + golden lexical/semantic queries (Hub#4149)").option("--live", "hit Hub cloud (status + searches); omit for offline golden-shape check").option("--golden <path>", "golden queries JSON (default: the suite bundled with the CLI, so the gate runs from any repo)").option("--json", "machine-readable findings").action(async (o) => {
32063
32711
  try {
32064
- const root = await repoRoot();
32065
- const goldenPath = o.golden || defaultGoldenPath(root);
32066
- const golden = loadGoldenSuite(goldenPath);
32712
+ const goldenPath = o.golden || "bundled repo-index-golden-queries.json";
32713
+ const golden = o.golden ? loadGoldenSuite(o.golden) : defaultGoldenSuite();
32067
32714
  const result = o.live ? await runRepoIndexHealth({
32068
32715
  golden,
32069
32716
  live: {
@@ -32514,7 +33161,7 @@ project.command("set [owner/repo]").description("upsert project META (idempotent
32514
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`);
32515
33162
  if (o.secretsFile) {
32516
33163
  try {
32517
- vars.push(`secrets=${(0, import_node_fs39.readFileSync)(o.secretsFile, "utf8")}`);
33164
+ vars.push(`secrets=${(0, import_node_fs40.readFileSync)(o.secretsFile, "utf8")}`);
32518
33165
  } catch (e) {
32519
33166
  return fail(`org project set: cannot read --secrets-file ${o.secretsFile}: ${e.message}`);
32520
33167
  }
@@ -33267,10 +33914,10 @@ pr.command("view <number>").description("read a PR as structured JSON (merged st
33267
33914
  });
33268
33915
  async function listCiWorkflowPaths(cwd = process.cwd()) {
33269
33916
  const wfDir = (0, import_node_path38.join)(cwd, ".github", "workflows");
33270
- if (!(0, import_node_fs39.existsSync)(wfDir)) return [];
33271
- return (0, import_node_fs39.readdirSync)(wfDir).filter((name) => /\.(ya?ml)$/i.test(name)).filter((name) => {
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) => {
33272
33919
  try {
33273
- return workflowReportsPrChecks((0, import_node_fs39.readFileSync)((0, import_node_path38.join)(wfDir, name), "utf8"));
33920
+ return workflowReportsPrChecks((0, import_node_fs40.readFileSync)((0, import_node_path38.join)(wfDir, name), "utf8"));
33274
33921
  } catch {
33275
33922
  return true;
33276
33923
  }
@@ -33303,15 +33950,15 @@ function ciAuditDeps() {
33303
33950
  readSeedFile: (path2) => {
33304
33951
  if (!root) return null;
33305
33952
  const fullPath = (0, import_node_path38.join)(root, path2);
33306
- return (0, import_node_fs39.existsSync)(fullPath) ? (0, import_node_fs39.readFileSync)(fullPath, "utf8") : null;
33953
+ return (0, import_node_fs40.existsSync)(fullPath) ? (0, import_node_fs40.readFileSync)(fullPath, "utf8") : null;
33307
33954
  }
33308
33955
  };
33309
33956
  }
33310
33957
  function hubRoot() {
33311
33958
  const fromPkg = (0, import_node_path38.join)(__dirname, "..", "..");
33312
33959
  const marker = "skills/bootstrap/seeds/manifest.json";
33313
- if ((0, import_node_fs39.existsSync)((0, import_node_path38.join)(fromPkg, marker))) return fromPkg;
33314
- if ((0, import_node_fs39.existsSync)((0, import_node_path38.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();
33315
33962
  return null;
33316
33963
  }
33317
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) => {
@@ -33622,7 +34269,7 @@ jsonParity(pr.command("merge <number>").description("merge a PR (squash by defau
33622
34269
  localCleanup = await cleanupPrMergeLocalBranch(headRef, {
33623
34270
  beforeWorktrees,
33624
34271
  startingPath,
33625
- pathExists: (p) => (0, import_node_fs39.existsSync)(p),
34272
+ pathExists: (p) => (0, import_node_fs40.existsSync)(p),
33626
34273
  execGit: async (args) => (await execFileP2("git", args, { timeout: GIT_TIMEOUT_MS })).stdout,
33627
34274
  teardownWorktreeStage,
33628
34275
  deferredStore,
@@ -34116,19 +34763,19 @@ access.command("audit").description("audit collaborator roles + train-branch pus
34116
34763
  targets = resolution.targets;
34117
34764
  }
34118
34765
  const derivedMatrix = registryProjects ? accessMatrixFromProjects(registryProjects) : {};
34119
- 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")) : {};
34120
34767
  const matrix = mergeAccessMatrix(fileMatrix, derivedMatrix);
34121
34768
  const derivedContracts = registryProjects ? dataAccessContractsFromProjects(registryProjects) : { consumers: {} };
34122
- 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: {} };
34123
34770
  const dataAccess = mergeDataAccessContracts(fileContracts, derivedContracts);
34124
- 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")) : {};
34125
34772
  const report = await auditOrgAccess(targets, deps, matrix, dataAccess, sanctioned);
34126
34773
  console.log(o.json ? JSON.stringify(report, null, 2) : renderAccessReport(report));
34127
34774
  if (!report.ok) process.exitCode = 1;
34128
34775
  });
34129
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)));
34130
34777
  var isWin2 = process.platform === "win32";
34131
- 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) => {
34132
34779
  if (opts.guide) {
34133
34780
  consoleIo.log("MMI Agentic Onboarding: docs/Architecture/agentic-dev-environment.md");
34134
34781
  return;
@@ -34153,7 +34800,7 @@ function directoryBytes(path2) {
34153
34800
  let total = 0;
34154
34801
  let entries;
34155
34802
  try {
34156
- entries = (0, import_node_fs39.readdirSync)(path2, { withFileTypes: true });
34803
+ entries = (0, import_node_fs40.readdirSync)(path2, { withFileTypes: true });
34157
34804
  } catch {
34158
34805
  return 0;
34159
34806
  }
@@ -34162,7 +34809,7 @@ function directoryBytes(path2) {
34162
34809
  if (entry.isDirectory()) total += directoryBytes(child2);
34163
34810
  else {
34164
34811
  try {
34165
- total += (0, import_node_fs39.statSync)(child2).size;
34812
+ total += (0, import_node_fs40.statSync)(child2).size;
34166
34813
  } catch {
34167
34814
  }
34168
34815
  }
@@ -34170,25 +34817,25 @@ function directoryBytes(path2) {
34170
34817
  return total;
34171
34818
  }
34172
34819
  function listDirEntries(dir) {
34173
- 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() }));
34174
34821
  }
34175
34822
  function readInstalledPluginRefs(configRoot) {
34176
34823
  const p = installedPluginsPathForConfig(configRoot);
34177
- if (!(0, import_node_fs39.existsSync)(p)) return [];
34824
+ if (!(0, import_node_fs40.existsSync)(p)) return [];
34178
34825
  try {
34179
- return installedPluginPaths((0, import_node_fs39.readFileSync)(p, "utf8"));
34826
+ return installedPluginPaths((0, import_node_fs40.readFileSync)(p, "utf8"));
34180
34827
  } catch {
34181
34828
  return null;
34182
34829
  }
34183
34830
  }
34184
34831
  function pluginCacheFsDeps(configRoot, dirBytes) {
34185
34832
  return {
34186
- exists: (p) => (0, import_node_fs39.existsSync)(p),
34187
- 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),
34188
34835
  dirBytes,
34189
- 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) => {
34190
34837
  try {
34191
- return { name: d.name, mtimeMs: newestMtimeMs((0, import_node_path38.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) };
34192
34839
  } catch {
34193
34840
  return { name: d.name, mtimeMs: Date.now() };
34194
34841
  }
@@ -34203,9 +34850,9 @@ function stagingApplyFsGuard(configRoot) {
34203
34850
  referencedPaths: () => readInstalledPluginRefs(configRoot),
34204
34851
  mtimeMs: (name) => {
34205
34852
  const p = (0, import_node_path38.join)(stagingRoot, name);
34206
- if (!(0, import_node_fs39.existsSync)(p)) return null;
34853
+ if (!(0, import_node_fs40.existsSync)(p)) return null;
34207
34854
  try {
34208
- 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);
34209
34856
  } catch {
34210
34857
  return null;
34211
34858
  }
@@ -34231,7 +34878,7 @@ program2.command("plugin-prune").description(`prune stale cached MMI plugin vers
34231
34878
  { withBytes: true, configRoot, includeStaging: surface !== "codex" }
34232
34879
  );
34233
34880
  const anythingToDelete = plan.prune.length > 0 || plan.staging.length > 0;
34234
- 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;
34235
34882
  const warnings = plan.prune.length > 0 ? [CONCURRENT_SESSION_WARNING] : [];
34236
34883
  if (o.json) console.log(JSON.stringify({ ...plan, warnings, applied: result ?? null }));
34237
34884
  else console.log(renderPluginCachePlan(plan, result));