@mutmutco/cli 3.95.0 → 3.97.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 +807 -146
  2. package/package.json +1 -1
package/dist/main.cjs CHANGED
@@ -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
  }
@@ -6999,7 +6999,7 @@ function commandLadderHint() {
6999
6999
  }
7000
7000
 
7001
7001
  // src/index.ts
7002
- var import_node_path38 = require("node:path");
7002
+ var import_node_path37 = require("node:path");
7003
7003
 
7004
7004
  // src/merge-ci-policy.ts
7005
7005
  function resolveMergeCiPolicy(input) {
@@ -8788,6 +8788,9 @@ function detectSurface(env) {
8788
8788
  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
8789
  return "codex";
8790
8790
  }
8791
+ if (env.MMI_AGENT_SURFACE === "jervcode" || has("PI_SESSION_ID")) {
8792
+ return "jervcode";
8793
+ }
8791
8794
  if (env.MMI_AGENT_SURFACE === "kimi" || has("KIMI_PLUGIN_ROOT") || has("KIMI_CODE_HOME")) {
8792
8795
  return "kimi";
8793
8796
  }
@@ -8817,6 +8820,8 @@ function surfaceToken(surface) {
8817
8820
  return "kilo";
8818
8821
  case "cursor":
8819
8822
  return "cursor";
8823
+ case "jervcode":
8824
+ return "jervcode";
8820
8825
  case "opencode":
8821
8826
  return "opencode";
8822
8827
  case "shell":
@@ -8838,6 +8843,8 @@ function reloadAction(surface) {
8838
8843
  return "restart OpenCode";
8839
8844
  case "cursor":
8840
8845
  return "reload the Cursor window";
8846
+ case "jervcode":
8847
+ return "restart JervCode (the package set is read at launch)";
8841
8848
  case "claude-cli":
8842
8849
  case "shell":
8843
8850
  default:
@@ -8905,7 +8912,7 @@ function nonClaudeSurfaceHealMessage(surface) {
8905
8912
  if (surface === "cursor") {
8906
8913
  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
8914
  }
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)";
8915
+ 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
8916
  }
8910
8917
  function healStepAborts(step, ok) {
8911
8918
  return !ok && step.gated;
@@ -8953,6 +8960,7 @@ function surfaceConfigRoot(surface, env = process.env, home = (0, import_node_os
8953
8960
  if (surface === "kimi") return env.KIMI_CODE_HOME?.trim() || (0, import_node_path14.join)(home, ".kimi-code");
8954
8961
  if (surface === "kilo") return env.KILO_CONFIG_DIR?.trim() || (0, import_node_path14.join)(home, ".config", "kilo");
8955
8962
  if (surface === "cursor") return (0, import_node_path14.join)(home, ".cursor");
8963
+ if (surface === "jervcode") return env.PI_CODING_AGENT_DIR?.trim() || (0, import_node_path14.join)(home, ".pi", "agent");
8956
8964
  return (0, import_node_path14.join)(home, ".claude");
8957
8965
  }
8958
8966
  var installedPluginsPath = (surface = detectSurface(process.env)) => {
@@ -8976,6 +8984,7 @@ function marketplaceCloneCandidates(surface, home, env = process.env) {
8976
8984
  if (surface === "kimi") return [];
8977
8985
  if (surface === "kilo") return [];
8978
8986
  if (surface === "cursor") return [];
8987
+ if (surface === "jervcode") return [];
8979
8988
  return [(0, import_node_path14.join)(home, ".claude", "plugins", "marketplaces", "mutmutco")];
8980
8989
  }
8981
8990
  function marketplaceClonePresent(surface, home, exists = import_node_fs15.existsSync, env = process.env) {
@@ -9104,22 +9113,170 @@ function kimiPluginTreeHealthy(root, exists = import_node_fs15.existsSync) {
9104
9113
  "scripts/hook-run.mjs"
9105
9114
  ].every((path2) => exists((0, import_node_path14.join)(root, ...path2.split("/"))));
9106
9115
  }
9116
+ var JERVCODE_WRAPPER_DIR = ".pi-plugin";
9117
+ function normalizePiEntry(value) {
9118
+ return decodeURIComponent(value.replace(/^file:\/\/\/?/, "")).replace(/\\/g, "/").replace(/\/+$/, "").toLowerCase();
9119
+ }
9120
+ function piEntryFsPath(entry) {
9121
+ return decodeURIComponent(entry.replace(/^file:\/\/\/?/, ""));
9122
+ }
9123
+ function samePiEntry(entry, packagePath) {
9124
+ return typeof entry === "string" && normalizePiEntry(entry) === normalizePiEntry(packagePath);
9125
+ }
9126
+ function jervcodePackageFamily(entry) {
9127
+ if (typeof entry !== "string") return null;
9128
+ const parts = normalizePiEntry(entry).split("/");
9129
+ if (parts.length < 3 || parts[parts.length - 1] !== JERVCODE_WRAPPER_DIR) return null;
9130
+ if (!/^\d+\.\d+\.\d+$/.test(parts[parts.length - 2])) return null;
9131
+ parts.splice(parts.length - 2, 1);
9132
+ return parts.join("/");
9133
+ }
9134
+ function isMmiOwnedPiEntry(entry) {
9135
+ if (typeof entry !== "string") return false;
9136
+ try {
9137
+ const pkg = JSON.parse((0, import_node_fs15.readFileSync)((0, import_node_path14.join)(piEntryFsPath(entry), "package.json"), "utf8"));
9138
+ return pkg.name === "mmi";
9139
+ } catch {
9140
+ return false;
9141
+ }
9142
+ }
9143
+ function mergeMmiPiPackageEntries(entries, packagePath) {
9144
+ const family = jervcodePackageFamily(packagePath);
9145
+ const others = entries.filter((entry) => {
9146
+ if (samePiEntry(entry, packagePath)) return false;
9147
+ if (family === null || jervcodePackageFamily(entry) !== family) return true;
9148
+ return !isMmiOwnedPiEntry(entry);
9149
+ });
9150
+ const already = entries.some((entry) => samePiEntry(entry, packagePath));
9151
+ const superseded = entries.length - others.length - (already ? 1 : 0);
9152
+ return {
9153
+ next: [...others, packagePath],
9154
+ superseded,
9155
+ changed: !(already && others.length === entries.length - 1)
9156
+ };
9157
+ }
9158
+ function readPiSettings(path2) {
9159
+ if (!(0, import_node_fs15.existsSync)(path2)) return void 0;
9160
+ try {
9161
+ const parsed = JSON.parse((0, import_node_fs15.readFileSync)(path2, "utf8"));
9162
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return null;
9163
+ return parsed;
9164
+ } catch {
9165
+ return null;
9166
+ }
9167
+ }
9168
+ function mmiPiWrapperEntry(env = process.env, home = (0, import_node_os4.homedir)()) {
9169
+ const settings = readPiSettings((0, import_node_path14.join)(surfaceConfigRoot("jervcode", env, home), "settings.json"));
9170
+ const entries = Array.isArray(settings?.packages) ? settings.packages : [];
9171
+ for (const entry of entries) {
9172
+ if (typeof entry !== "string") continue;
9173
+ if (jervcodePackageFamily(entry)?.endsWith("/mutmutco/mmi/" + JERVCODE_WRAPPER_DIR) || isMmiOwnedPiEntry(entry)) {
9174
+ return entry;
9175
+ }
9176
+ }
9177
+ return null;
9178
+ }
9179
+ function mmiPiWrapperHealthy(entry) {
9180
+ if (!entry) return false;
9181
+ const wrapper = piEntryFsPath(entry);
9182
+ return isMmiOwnedPiEntry(entry) && (0, import_node_fs15.existsSync)((0, import_node_path14.join)((0, import_node_path14.dirname)(wrapper), "skills", "mmi", "SKILL.md"));
9183
+ }
9184
+ function findMmiPiSourceClone(home = (0, import_node_os4.homedir)()) {
9185
+ const cacheRoot = (0, import_node_path14.join)(home, ".claude", "plugins", "cache", "mutmutco", "mmi");
9186
+ let best = null;
9187
+ try {
9188
+ for (const entry of (0, import_node_fs15.readdirSync)(cacheRoot, { withFileTypes: true })) {
9189
+ if (!entry.isDirectory() || !/^\d+\.\d+\.\d+$/.test(entry.name)) continue;
9190
+ if (!(0, import_node_fs15.existsSync)((0, import_node_path14.join)(cacheRoot, entry.name, JERVCODE_WRAPPER_DIR, "package.json"))) continue;
9191
+ if (!best || compareVersions(entry.name, best.version) > 0) {
9192
+ best = { path: (0, import_node_path14.join)(cacheRoot, entry.name), version: entry.name };
9193
+ }
9194
+ }
9195
+ } catch {
9196
+ return null;
9197
+ }
9198
+ return best;
9199
+ }
9200
+ function healJervCodePackageRegistration(opts = {}) {
9201
+ const env = opts.env ?? process.env;
9202
+ if (env.PI_SESSION_ID?.trim()) {
9203
+ return {
9204
+ available: true,
9205
+ ok: true,
9206
+ changed: false,
9207
+ version: null,
9208
+ 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"
9209
+ };
9210
+ }
9211
+ const home = opts.home ?? (0, import_node_os4.homedir)();
9212
+ const agentDir = surfaceConfigRoot("jervcode", env, home);
9213
+ if (!(0, import_node_fs15.existsSync)(agentDir)) {
9214
+ return { available: false, ok: true, changed: false, version: null, detail: "skipped \u2014 no Pi/JervCode install (no agent config dir)" };
9215
+ }
9216
+ const clone = opts.clone === void 0 ? findMmiPiSourceClone(home) : opts.clone;
9217
+ if (!clone) {
9218
+ return { available: true, ok: true, changed: false, version: null, detail: "skipped \u2014 no installed MMI clone carries the pi wrapper (install the Claude plugin first)" };
9219
+ }
9220
+ const packagePath = `${clone.path.replace(/\\/g, "/").replace(/\/+$/, "")}/${JERVCODE_WRAPPER_DIR}`;
9221
+ const settingsPath = (0, import_node_path14.join)(agentDir, "settings.json");
9222
+ const settings = readPiSettings(settingsPath);
9223
+ if (settings === null) {
9224
+ return {
9225
+ available: true,
9226
+ ok: false,
9227
+ changed: false,
9228
+ version: clone.version,
9229
+ detail: `package NOT registered \u2014 ${settingsPath} is not a strict JSON object, so mmi left it alone; add ${packagePath} to its packages array by hand`
9230
+ };
9231
+ }
9232
+ const current = settings ?? {};
9233
+ const entries = Array.isArray(current.packages) ? current.packages : [];
9234
+ const merged = mergeMmiPiPackageEntries(entries, packagePath);
9235
+ if (!merged.changed) {
9236
+ return { available: true, ok: true, changed: false, version: clone.version, detail: `holds ${clone.version}` };
9237
+ }
9238
+ current.packages = merged.next;
9239
+ try {
9240
+ (0, import_node_fs15.mkdirSync)((0, import_node_path14.dirname)(settingsPath), { recursive: true });
9241
+ const tmp = `${settingsPath}.tmp-${process.pid}`;
9242
+ (0, import_node_fs15.writeFileSync)(tmp, `${JSON.stringify(current, null, 2)}
9243
+ `, "utf8");
9244
+ try {
9245
+ (0, import_node_fs15.renameSync)(tmp, settingsPath);
9246
+ } catch (renameError) {
9247
+ (0, import_node_fs15.rmSync)(tmp, { force: true });
9248
+ throw renameError;
9249
+ }
9250
+ } catch (error) {
9251
+ return { available: true, ok: false, changed: false, version: clone.version, detail: `package NOT registered \u2014 ${error.message}` };
9252
+ }
9253
+ const beside = `package registered in settings.json (${merged.next.length - 1} other package(s) untouched)`;
9254
+ return {
9255
+ available: true,
9256
+ ok: true,
9257
+ changed: true,
9258
+ version: clone.version,
9259
+ detail: `${clone.version}: ${merged.superseded > 0 ? `${beside}, superseding ${merged.superseded} earlier mmi version(s)` : beside}`
9260
+ };
9261
+ }
9107
9262
  function snapshotPluginGuardInput(surface = detectSurface(process.env), isOrgRepo = false) {
9108
9263
  const root = surfaceConfigRoot(surface);
9109
9264
  const installed = readInstalledPlugins(surface);
9110
9265
  const codexStatus = surface === "codex" ? codexPluginStatus() : void 0;
9266
+ const piEntry = surface === "jervcode" ? mmiPiWrapperEntry() : null;
9111
9267
  return {
9112
9268
  isOrgRepo,
9113
9269
  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
9270
  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()),
9271
+ surface === "kilo" && kiloConfigListsPlugin(root) || // #4188: jervcode's install record is the settings-file packages[] entry itself.
9272
+ surface === "jervcode" && piEntry !== null || surface === "cursor" && (0, import_node_fs15.existsSync)(cursorLocalPluginRoot()),
9116
9273
  // 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)()),
9274
+ // the shared guard table is vacuously satisfied. Same for jervcode's settings entry.
9275
+ marketplaceClonePresent: surface === "kimi" || surface === "kilo" || surface === "cursor" || surface === "jervcode" ? true : marketplaceClonePresent(surface, (0, import_node_os4.homedir)()),
9119
9276
  // Kimi keeps no plugin cache dir — installs are copied to plugins/managed/<id> and run from there.
9120
9277
  // Kilo (kilo-p1) keeps no cache dir either: the plugin's server() provisions ~/.kilo behind the
9121
9278
  // 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(
9279
+ 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
9280
  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
9281
  ) : (0, import_node_fs15.existsSync)((0, import_node_path14.join)(root, "plugins", "cache", "mutmutco", "mmi"))
9125
9282
  };
@@ -9298,13 +9455,26 @@ async function healClaudePluginForDoctor(surface = detectSurface(process.env), o
9298
9455
  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
9456
  };
9300
9457
  }
9458
+ async function healJervCodeForDoctor() {
9459
+ const heal = healJervCodePackageRegistration();
9460
+ if (!heal.ok) return { ok: false, detail: heal.detail };
9461
+ const snapshot = snapshotPluginGuardInput("jervcode", true);
9462
+ const state = buildPluginGuardDecision(snapshot).state;
9463
+ const verification = `record=${snapshot.installRecordPresent ? "yes" : "no"}, payload=${snapshot.pluginCachePresent ? "yes" : "no"}`;
9464
+ if (!heal.changed && heal.detail.startsWith("skipped")) return { ok: true, detail: heal.detail };
9465
+ return {
9466
+ ok: state === "healthy",
9467
+ detail: state === "healthy" ? `${heal.detail}; full guard verified (${verification})` : `pi package registration did not verify (${verification}); ${heal.detail}`
9468
+ };
9469
+ }
9301
9470
  async function healActivePluginForDoctor(surface = detectSurface(process.env), onStep) {
9302
9471
  const token = surfaceToken(surface);
9303
- if (token !== "claude" && token !== "codex" && token !== "cursor" && token !== "kilo") {
9472
+ if (token !== "claude" && token !== "codex" && token !== "cursor" && token !== "kilo" && token !== "jervcode") {
9304
9473
  return { ok: false, detail: `not a supported plugin surface (${surface})` };
9305
9474
  }
9306
9475
  if (token === "claude") return healClaudePluginForDoctor(surface, onStep);
9307
9476
  if (token === "cursor") return installCursorPluginCheckout();
9477
+ if (token === "jervcode") return healJervCodeForDoctor();
9308
9478
  const steps = [];
9309
9479
  const applied = await applyPluginHeal(surface, (msg) => {
9310
9480
  const line = msg.trim();
@@ -9421,7 +9591,7 @@ async function runGuard(readOrigin) {
9421
9591
  const surface = detectSurface(process.env);
9422
9592
  try {
9423
9593
  const token = surfaceToken(surface);
9424
- if (token !== "claude" && token !== "codex" && token !== "cursor" && token !== "kilo") {
9594
+ if (token !== "claude" && token !== "codex" && token !== "cursor" && token !== "kilo" && token !== "jervcode") {
9425
9595
  process.exitCode = 0;
9426
9596
  return;
9427
9597
  }
@@ -9441,8 +9611,8 @@ async function runGuard(readOrigin) {
9441
9611
  if (surfaceToken(surface) === "codex") {
9442
9612
  console.error("[mmi-guard] Could not inspect the active Codex plugin; run `mmi-cli plugin heal`.");
9443
9613
  process.exitCode = 1;
9444
- } else if (surfaceToken(surface) === "kilo" || surfaceToken(surface) === "cursor") {
9445
- const host = surfaceToken(surface) === "cursor" ? "Cursor" : "Kilo";
9614
+ } else if (surfaceToken(surface) === "kilo" || surfaceToken(surface) === "cursor" || surfaceToken(surface) === "jervcode") {
9615
+ const host = surfaceToken(surface) === "cursor" ? "Cursor" : surfaceToken(surface) === "jervcode" ? "JervCode" : "Kilo";
9446
9616
  console.error(`[mmi-guard] Could not inspect the active ${host} plugin; run \`mmi-cli plugin heal\`.`);
9447
9617
  process.exitCode = 1;
9448
9618
  } else {
@@ -9452,10 +9622,21 @@ async function runGuard(readOrigin) {
9452
9622
  }
9453
9623
  async function runPluginHeal(surface = detectSurface(process.env)) {
9454
9624
  const token = surfaceToken(surface);
9455
- if (token !== "claude" && token !== "codex" && token !== "cursor" && token !== "kilo") {
9625
+ if (token !== "claude" && token !== "codex" && token !== "cursor" && token !== "kilo" && token !== "jervcode") {
9456
9626
  console.log(nonClaudeSurfaceHealMessage(token ?? void 0));
9457
9627
  return;
9458
9628
  }
9629
+ if (token === "jervcode") {
9630
+ const result = await healJervCodeForDoctor();
9631
+ console.log(` \u21BB ${result.detail}`);
9632
+ if (result.ok) {
9633
+ console.log(` \u2713 MMI pi package registered \u2014 ${reloadAction(surface)} to load MMI skills.`);
9634
+ } else {
9635
+ process.exitCode = 1;
9636
+ 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`.");
9637
+ }
9638
+ return;
9639
+ }
9459
9640
  const descriptor = PLUGIN_SURFACE_HEAL[token];
9460
9641
  const cursorResult = token === "cursor" ? await installCursorPluginCheckout() : void 0;
9461
9642
  if (cursorResult) console.log(` \u21BB ${cursorResult.detail}`);
@@ -16990,7 +17171,7 @@ async function boardDoctor(options, deps = {}) {
16990
17171
  };
16991
17172
  await Promise.all(Array.from({ length: Math.min(concurrency, suspicious.length) }, () => ghostWorker()));
16992
17173
  }
16993
- const claimedItems = collected.items.filter(
17174
+ const claimedItems = options.skipClaimLiveness ? [] : collected.items.filter(
16994
17175
  (item) => item.contentType === "Issue" && item.assignees.length > 0 && item.status === "In Progress"
16995
17176
  );
16996
17177
  const liveClaims = [];
@@ -17227,7 +17408,7 @@ function applyChecklistCheck(body, query, checked) {
17227
17408
  // src/command-taxonomy.ts
17228
17409
  var COMMAND_METADATA = /* @__PURE__ */ Symbol.for("mmi.commandTaxonomy.metadata");
17229
17410
  var PRIMARY_GROUPS = [
17230
- ["Orient", ["onboard", "status", "next", "doctor", "whoami", "commands", "explain"]],
17411
+ ["Orient", ["onboard", "status", "next", "find", "doctor", "whoami", "commands", "explain"]],
17231
17412
  ["Plan and work", ["board", "issue", "worktree", "stage"]],
17232
17413
  ["Review and ship", ["pr", "ci", "rcand", "release", "hotfix", "train"]],
17233
17414
  // `tests` sits beside `docs` deliberately: both are deterministic, repo-local gates a workflow
@@ -17237,7 +17418,7 @@ var PRIMARY_GROUPS = [
17237
17418
  ["Coordinate and improve", ["wave", "report", "skill-lesson"]]
17238
17419
  ];
17239
17420
  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"]);
17421
+ var SUPPORT_PRIMARY = /* @__PURE__ */ new Set(["doctor", "whoami", "commands", "explain", "docs", "repo-index", "find", "tests", "spawn", "wave", "report", "skill-lesson"]);
17241
17422
  var TOP_LEVEL_ORDER = /* @__PURE__ */ new Map();
17242
17423
  var HELP_GROUP_ORDER = /* @__PURE__ */ new Map();
17243
17424
  var topLevelPosition = 0;
@@ -17248,6 +17429,10 @@ for (const [helpGroup, names] of PRIMARY_GROUPS) {
17248
17429
  HELP_GROUP_ORDER.set("Operations", HELP_GROUP_ORDER.size);
17249
17430
  for (const name of OPERATIONAL_TOP_LEVEL) TOP_LEVEL_ORDER.set(name, topLevelPosition++);
17250
17431
  var PATH_OVERRIDES = {
17432
+ // #4184: the estate-search door reads as Orient, while rebuild/publish/gc/sync-estate stay
17433
+ // Setup and support — that split is the point.
17434
+ "repo-index search": { category: "support", discovery: "primary", help_group: "Orient" },
17435
+ "repo-index status": { category: "support", discovery: "primary", help_group: "Orient" },
17251
17436
  "secrets find": { category: "admin", discovery: "all-only", help_group: "Operations" },
17252
17437
  "secrets catalog": { category: "admin", discovery: "all-only", help_group: "Operations" },
17253
17438
  "secrets doctor": { category: "admin", discovery: "all-only", help_group: "Operations" },
@@ -17277,6 +17462,7 @@ var COMMAND_OWNERSHIP = {
17277
17462
  secrets: { module_owner: "cli/src/secrets-commands.ts", consumer: "authenticated-operator" },
17278
17463
  docs: { module_owner: "cli/src/docs-index-command.ts", consumer: "repo-gates" },
17279
17464
  "repo-index": { module_owner: "cli/src/repo-index.ts", consumer: "agent-session" },
17465
+ find: { module_owner: "cli/src/repo-index.ts", consumer: "agent-session" },
17280
17466
  tests: { module_owner: "cli/src/test-policy-core.ts", consumer: "repo-gates" },
17281
17467
  spawn: { module_owner: "cli/src/spawn-policy-core.ts", consumer: "repo-gates" },
17282
17468
  wave: { module_owner: "cli/src/wave-land.ts", consumer: "campaign-orchestrator" },
@@ -19342,6 +19528,12 @@ var INDEXABLE_EXT = /* @__PURE__ */ new Set([
19342
19528
  ".html"
19343
19529
  ]);
19344
19530
  var SYMBOL_RE = /^(?:export\s+)?(?:async\s+)?(?:function|class|const|let|var|type|interface|enum)\s+([A-Za-z_$][\w$]*)/gm;
19531
+ function repoIndexPathTieBreak(path2) {
19532
+ const p = path2.replace(/\\/g, "/");
19533
+ if (/(^|\/)__tests__\//.test(p) || /(^|\/)tests?\//.test(p)) return 1;
19534
+ if (/\.(test|spec)\.[^.]+$/i.test(p)) return 1;
19535
+ return 0;
19536
+ }
19345
19537
  function repoIndexStorePath(cwd) {
19346
19538
  return repoRuntimeStatePath(cwd, "repo-index", "index.json");
19347
19539
  }
@@ -19552,7 +19744,9 @@ function searchRepoIndex(idx, query, limit = 20) {
19552
19744
  }
19553
19745
  }
19554
19746
  }
19555
- hits.sort((a, b) => b.score - a.score || a.path.localeCompare(b.path));
19747
+ hits.sort(
19748
+ (a, b) => b.score - a.score || repoIndexPathTieBreak(a.path) - repoIndexPathTieBreak(b.path) || a.path.localeCompare(b.path)
19749
+ );
19556
19750
  const seen = /* @__PURE__ */ new Set();
19557
19751
  const out = [];
19558
19752
  for (const h of hits) {
@@ -19568,10 +19762,10 @@ function inferRepoSlug(cwd, exec = import_node_child_process11.execFileSync) {
19568
19762
  try {
19569
19763
  const url = String(exec("git", ["remote", "get-url", "origin"], { cwd, encoding: "utf8" })).trim();
19570
19764
  const m = /[:/]([^/]+\/[^/]+?)(?:\.git)?$/.exec(url);
19571
- if (m?.[1]) return m[1];
19765
+ if (m?.[1]) return m[1].toLowerCase();
19572
19766
  } catch {
19573
19767
  }
19574
- return (0, import_node_path20.basename)(cwd) || "local";
19768
+ return ((0, import_node_path20.basename)(cwd) || "local").toLowerCase();
19575
19769
  }
19576
19770
 
19577
19771
  // src/repo-index-cloud-client.ts
@@ -19718,8 +19912,11 @@ var import_node_child_process12 = require("node:child_process");
19718
19912
  var MAX_EMBED_BACKFILL_ROUNDS = 40;
19719
19913
  function normalizeRepo(raw) {
19720
19914
  const t = raw.trim().replace(/\.git$/, "");
19721
- if (t.includes("/")) return t;
19722
- return `mutmutco/${t}`;
19915
+ if (t.includes("/")) {
19916
+ const [owner, name] = t.split("/");
19917
+ return `${(owner || "").toLowerCase()}/${(name || "").toLowerCase()}`;
19918
+ }
19919
+ return `mutmutco/${t.toLowerCase()}`;
19723
19920
  }
19724
19921
  function rosterRepos(projects) {
19725
19922
  const set = /* @__PURE__ */ new Set();
@@ -19730,10 +19927,11 @@ function rosterRepos(projects) {
19730
19927
  return [...set].sort((a, b) => a.localeCompare(b));
19731
19928
  }
19732
19929
  function shallowClone(repo, dest, token) {
19930
+ const basic = Buffer.from(`x-access-token:${token}`, "utf8").toString("base64");
19733
19931
  (0, import_node_child_process12.execFileSync)(
19734
19932
  "git",
19735
- ["-c", `http.extraHeader=Authorization: Bearer ${token}`, "clone", "--depth", "1", "--single-branch", `https://github.com/${repo}.git`, dest],
19736
- { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] }
19933
+ ["-c", `http.extraHeader=Authorization: Basic ${basic}`, "clone", "--depth", "1", "--single-branch", `https://github.com/${repo}.git`, dest],
19934
+ { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"], windowsHide: true }
19737
19935
  );
19738
19936
  }
19739
19937
  async function syncEstateRepoIndex(opts) {
@@ -19813,17 +20011,56 @@ async function syncEstateRepoIndex(opts) {
19813
20011
 
19814
20012
  // src/repo-index-health.ts
19815
20013
  var import_node_fs24 = require("node:fs");
19816
- var import_node_path22 = require("node:path");
19817
- function defaultGoldenPath(repoRoot2) {
19818
- return (0, import_node_path22.join)(repoRoot2, "cli", "testdata", "repo-index-golden-queries.json");
19819
- }
19820
- function loadGoldenSuite(path2) {
19821
- const raw = JSON.parse((0, import_node_fs24.readFileSync)(path2, "utf8"));
20014
+
20015
+ // testdata/repo-index-golden-queries.json
20016
+ var repo_index_golden_queries_default = {
20017
+ schema: 1,
20018
+ staleThresholdHours: 12,
20019
+ hubRepo: "mutmutco/MMI-Hub",
20020
+ hubEmbMinCoverage: 0.5,
20021
+ maxSyncEmbeds: 100,
20022
+ queries: [
20023
+ {
20024
+ id: "rebuild-symbol-lexical",
20025
+ q: "rebuildRepoIndex",
20026
+ mode: "lexical",
20027
+ expect: {
20028
+ topRepos: ["mutmutco/MMI-Hub"],
20029
+ topPaths: ["cli/src/repo-index.ts"],
20030
+ minScore: 0.1
20031
+ }
20032
+ },
20033
+ {
20034
+ id: "schedules-register-semantic",
20035
+ q: "schedules register",
20036
+ mode: "semantic",
20037
+ expect: {
20038
+ topRepos: ["mutmutco/MMI-Hub"],
20039
+ minScore: 0.05
20040
+ }
20041
+ }
20042
+ ]
20043
+ };
20044
+
20045
+ // src/repo-index-health.ts
20046
+ function assertGoldenSuite(raw, source) {
19822
20047
  if (!raw || raw.schema !== 1 || !Array.isArray(raw.queries)) {
19823
- throw new Error(`invalid golden suite at ${path2}`);
20048
+ throw new Error(`invalid golden suite at ${source}`);
19824
20049
  }
19825
20050
  return raw;
19826
20051
  }
20052
+ function loadGoldenSuite(path2) {
20053
+ let text;
20054
+ try {
20055
+ text = (0, import_node_fs24.readFileSync)(path2, "utf8");
20056
+ } catch (e) {
20057
+ throw new Error(`golden suite unreadable at ${path2}: ${e.message}`);
20058
+ }
20059
+ return assertGoldenSuite(JSON.parse(text), path2);
20060
+ }
20061
+ function defaultGoldenSuite() {
20062
+ return assertGoldenSuite(repo_index_golden_queries_default, "bundled repo-index-golden-queries.json");
20063
+ }
19827
20064
  function evaluateQueryHits(query, hits) {
19828
20065
  if (!hits.length) {
19829
20066
  return { ok: false, code: "empty-hits", detail: `${query.id}: no hits for ${JSON.stringify(query.q)}` };
@@ -19960,7 +20197,7 @@ async function runRepoIndexHealth(opts) {
19960
20197
  // src/spawn-policy-core.ts
19961
20198
  var import_node_child_process13 = require("node:child_process");
19962
20199
  var import_node_fs25 = require("node:fs");
19963
- var import_node_path23 = require("node:path");
20200
+ var import_node_path22 = require("node:path");
19964
20201
  var SPAWNERS = ["spawn", "spawnSync", "exec", "execSync", "execFile", "execFileSync"];
19965
20202
  var CALL_SOURCE = String.raw`(^|[^.\w$])(${SPAWNERS.join("|")})\s*\(`;
19966
20203
  var SOURCE_EXT = /\.(ts|mts|cts|js|mjs|cjs)$/;
@@ -20046,7 +20283,7 @@ function runSpawnPolicy(root) {
20046
20283
  for (const file of files) {
20047
20284
  let raw;
20048
20285
  try {
20049
- raw = (0, import_node_fs25.readFileSync)((0, import_node_path23.join)(root, file), "utf8");
20286
+ raw = (0, import_node_fs25.readFileSync)((0, import_node_path22.join)(root, file), "utf8");
20050
20287
  } catch {
20051
20288
  continue;
20052
20289
  }
@@ -20065,7 +20302,7 @@ function runSpawnPolicy(root) {
20065
20302
  // src/test-policy-core.ts
20066
20303
  var import_node_child_process14 = require("node:child_process");
20067
20304
  var import_node_fs26 = require("node:fs");
20068
- var import_node_path24 = require("node:path");
20305
+ var import_node_path23 = require("node:path");
20069
20306
  var POLICY_FILE = "test-policy.json";
20070
20307
  var TEST_RE = /\.(?:test|spec)\.[cm]?[jt]sx?$/;
20071
20308
  var PY_TEST_RE = /(?:^|\/)test_[^/]*\.py$|_test\.py$/;
@@ -20118,7 +20355,7 @@ function isTestPath(path2) {
20118
20355
  return TEST_RE.test(path2) || PY_TEST_RE.test(path2);
20119
20356
  }
20120
20357
  function loadPolicy(root, readFile9 = readFileOrNull2) {
20121
- const raw = readFile9((0, import_node_path24.join)(root, POLICY_FILE));
20358
+ const raw = readFile9((0, import_node_path23.join)(root, POLICY_FILE));
20122
20359
  if (raw == null) return { mandatory: [], declared: false };
20123
20360
  try {
20124
20361
  return { ...JSON.parse(raw), declared: true };
@@ -20156,11 +20393,11 @@ function classify(changed, policy, present = () => false) {
20156
20393
  return { mandatoryHits, untestedHits, testChanges, addedTests, removedProtected };
20157
20394
  }
20158
20395
  function unresolvedProtectedEntries(policy, root, exists = (path2) => (0, import_node_fs26.existsSync)(path2)) {
20159
- return (policy.protected ?? []).map((p) => p.path).filter((p) => !exists((0, import_node_path24.join)(root, p)));
20396
+ return (policy.protected ?? []).map((p) => p.path).filter((p) => !exists((0, import_node_path23.join)(root, p)));
20160
20397
  }
20161
20398
  function unresolvedSatisfiers(policy, root, exists = (path2) => (0, import_node_fs26.existsSync)(path2)) {
20162
20399
  const declared = (policy.mandatory ?? []).flatMap((m) => m.satisfiedBy ?? []);
20163
- return [...new Set(declared)].filter((p) => !exists((0, import_node_path24.join)(root, p)));
20400
+ return [...new Set(declared)].filter((p) => !exists((0, import_node_path23.join)(root, p)));
20164
20401
  }
20165
20402
  function evaluate(changed, policy, present = () => false) {
20166
20403
  const { mandatoryHits, untestedHits, testChanges, addedTests, removedProtected } = classify(changed, policy, present);
@@ -20348,7 +20585,7 @@ function runTestPolicy(root, deps = {}) {
20348
20585
  const refusal = deps.changed ? null : untrustworthyRange(root, base);
20349
20586
  const changed = deps.changed ?? (refusal ? [] : changedFilesSince(base, root));
20350
20587
  const lookup = deps.override !== void 0 ? { override: deps.override, refusals: [] } : !deps.changed && !refusal ? readOverride(base, root) : { override: null, refusals: [] };
20351
- const present = (path2) => exists((0, import_node_path24.join)(root, path2));
20588
+ const present = (path2) => exists((0, import_node_path23.join)(root, path2));
20352
20589
  const removedByThisDiff = removedPaths(changed);
20353
20590
  const staleFindings = [];
20354
20591
  const unresolved = unresolvedProtectedEntries(policy, root, exists).filter((p) => !removedByThisDiff.has(p));
@@ -20505,7 +20742,7 @@ function docsAuditStatus(fetch2, opts) {
20505
20742
 
20506
20743
  // src/project-info-sync.ts
20507
20744
  var import_node_fs27 = require("node:fs");
20508
- var import_node_path25 = require("node:path");
20745
+ var import_node_path24 = require("node:path");
20509
20746
  var UPDATE_PROJECT_INFO = `mutation($projectId: ID!, $shortDescription: String!, $readme: String!) {
20510
20747
  updateProjectV2(input: { projectId: $projectId, shortDescription: $shortDescription, readme: $readme }) {
20511
20748
  projectV2 { id }
@@ -20550,7 +20787,7 @@ function sharedName(entries, fallback) {
20550
20787
  }
20551
20788
  function buildProjectInfoSyncPlan(targetRepo2, project2, projects, repoRoot2) {
20552
20789
  if (!project2.projectId) throw new Error(`org project sync-info: ${targetRepo2} registry META has no projectId`);
20553
- const readmePath = (0, import_node_path25.join)(repoRoot2, "README.md");
20790
+ const readmePath = (0, import_node_path24.join)(repoRoot2, "README.md");
20554
20791
  if (!(0, import_node_fs27.existsSync)(readmePath)) throw new Error(`org project sync-info: ${targetRepo2} has no README.md`);
20555
20792
  const entries = entriesFor(project2, projects);
20556
20793
  const memberRepos = [...new Set(entries.flatMap((entry) => entry.repos ?? []))].filter((repo) => /^[^/]+\/[^/]+$/.test(repo)).sort((a, b) => a.localeCompare(b));
@@ -20576,8 +20813,8 @@ function buildProjectInfoSyncPlan(targetRepo2, project2, projects, repoRoot2) {
20576
20813
  const targetBase = `https://github.com/${targetRepo2}`;
20577
20814
  const targetBranch = branchFor(targetRepo2, projects);
20578
20815
  const orgDocs = [
20579
- (0, import_node_fs27.existsSync)((0, import_node_path25.join)(repoRoot2, "docs", "org-readme.md")) ? `- [Org identity](${targetBase}/blob/${targetBranch}/docs/org-readme.md)` : "",
20580
- (0, import_node_fs27.existsSync)((0, import_node_path25.join)(repoRoot2, "docs", "org-architecture.md")) ? `- [Org architecture](${targetBase}/blob/${targetBranch}/docs/org-architecture.md)` : ""
20816
+ (0, import_node_fs27.existsSync)((0, import_node_path24.join)(repoRoot2, "docs", "org-readme.md")) ? `- [Org identity](${targetBase}/blob/${targetBranch}/docs/org-readme.md)` : "",
20817
+ (0, import_node_fs27.existsSync)((0, import_node_path24.join)(repoRoot2, "docs", "org-architecture.md")) ? `- [Org architecture](${targetBase}/blob/${targetBranch}/docs/org-architecture.md)` : ""
20581
20818
  ].filter(Boolean);
20582
20819
  if (orgDocs.length) lines.push("", "## Organisation docs", "", ...orgDocs);
20583
20820
  return { projectId: project2.projectId, projectName, targetRepo: targetRepo2, memberRepos, shortDescription, readme: `${lines.join("\n")}
@@ -21437,7 +21674,7 @@ function writeError(res) {
21437
21674
 
21438
21675
  // src/secrets-commands.ts
21439
21676
  var import_node_fs28 = require("node:fs");
21440
- var import_node_path26 = require("node:path");
21677
+ var import_node_path25 = require("node:path");
21441
21678
  var import_node_os10 = require("node:os");
21442
21679
 
21443
21680
  // src/project-runtime.ts
@@ -21561,11 +21798,11 @@ function collectMap(value, previous = []) {
21561
21798
  return [...previous, value];
21562
21799
  }
21563
21800
  async function decryptRailsCredentials(input) {
21564
- const appDir = (0, import_node_path26.resolve)(input.appDir ?? process.cwd());
21801
+ const appDir = (0, import_node_path25.resolve)(input.appDir ?? process.cwd());
21565
21802
  const credentialsFile = input.credentialsFile ?? DEFAULT_RAILS_CREDENTIALS_FILE;
21566
21803
  const masterKeyFile = input.masterKeyFile ?? DEFAULT_RAILS_MASTER_KEY_FILE;
21567
- const credentialsPath = (0, import_node_path26.resolve)(appDir, credentialsFile);
21568
- const masterKeyPath = (0, import_node_path26.resolve)(appDir, masterKeyFile);
21804
+ const credentialsPath = (0, import_node_path25.resolve)(appDir, credentialsFile);
21805
+ const masterKeyPath = (0, import_node_path25.resolve)(appDir, masterKeyFile);
21569
21806
  const env = {
21570
21807
  ...process.env,
21571
21808
  MMI_RAILS_CREDENTIALS_FILE: credentialsPath,
@@ -21582,8 +21819,8 @@ async function decryptRailsCredentials(input) {
21582
21819
  'config = ActiveSupport::EncryptedConfiguration.new(config_path: config_path, key_path: key_path, env_key: "RAILS_MASTER_KEY", raise_if_missing_key: true)',
21583
21820
  "puts JSON.generate(config.config)"
21584
21821
  ].join("\n");
21585
- const scriptDir = (0, import_node_fs28.mkdtempSync)((0, import_node_path26.join)((0, import_node_os10.tmpdir)(), "mmi-rails-decrypt-"));
21586
- const scriptPath = (0, import_node_path26.join)(scriptDir, "decrypt.rb");
21822
+ const scriptDir = (0, import_node_fs28.mkdtempSync)((0, import_node_path25.join)((0, import_node_os10.tmpdir)(), "mmi-rails-decrypt-"));
21823
+ const scriptPath = (0, import_node_path25.join)(scriptDir, "decrypt.rb");
21587
21824
  (0, import_node_fs28.writeFileSync)(scriptPath, script, "utf8");
21588
21825
  try {
21589
21826
  const args = ["exec", "ruby", scriptPath];
@@ -21686,7 +21923,7 @@ function registerSecretsCommands(program3) {
21686
21923
  let body;
21687
21924
  if (o.file) {
21688
21925
  try {
21689
- body = (0, import_node_fs28.readFileSync)((0, import_node_path26.resolve)(o.file), "utf8");
21926
+ body = (0, import_node_fs28.readFileSync)((0, import_node_path25.resolve)(o.file), "utf8");
21690
21927
  } catch (e) {
21691
21928
  return fail(`secrets org-catalog: cannot read --file ${o.file}: ${e.message}`);
21692
21929
  }
@@ -21791,7 +22028,7 @@ function registerSecretsCommands(program3) {
21791
22028
  {
21792
22029
  ...d,
21793
22030
  decryptRailsCredentials,
21794
- removeFile: (path2) => (0, import_node_fs28.unlinkSync)((0, import_node_path26.resolve)(o.appDir ?? process.cwd(), path2))
22031
+ removeFile: (path2) => (0, import_node_fs28.unlinkSync)((0, import_node_path25.resolve)(o.appDir ?? process.cwd(), path2))
21795
22032
  },
21796
22033
  {
21797
22034
  repo: o.repo,
@@ -22847,7 +23084,7 @@ function registerSchedulesCommands(program3) {
22847
23084
 
22848
23085
  // src/file-lock.ts
22849
23086
  var import_promises5 = require("node:fs/promises");
22850
- var import_node_path27 = require("node:path");
23087
+ var import_node_path26 = require("node:path");
22851
23088
  var sleep = (ms) => new Promise((resolve5) => setTimeout(resolve5, ms));
22852
23089
  var IMMEDIATE_RETRY_BUDGET = 3;
22853
23090
  var FileLockBusyError = class extends Error {
@@ -22932,7 +23169,7 @@ async function releaseFileLock(lockPath, guard) {
22932
23169
  }
22933
23170
  async function withFileLock(lockPath, opts, fn) {
22934
23171
  const resolved = resolveFileLockOpts(opts);
22935
- await (0, import_promises5.mkdir)((0, import_node_path27.dirname)(lockPath), { recursive: true }).catch(() => void 0);
23172
+ await (0, import_promises5.mkdir)((0, import_node_path26.dirname)(lockPath), { recursive: true }).catch(() => void 0);
22936
23173
  const guard = await acquireFileLock(lockPath, resolved, Date.now() + resolved.maxWaitMs);
22937
23174
  try {
22938
23175
  return await fn();
@@ -22943,7 +23180,7 @@ async function withFileLock(lockPath, opts, fn) {
22943
23180
 
22944
23181
  // src/schedules-lift-command.ts
22945
23182
  var import_promises6 = require("node:fs/promises");
22946
- var import_node_path28 = require("node:path");
23183
+ var import_node_path27 = require("node:path");
22947
23184
 
22948
23185
  // src/schedules-lift.ts
22949
23186
  var SCHEDULE_HEADER_FIELDS = ["schedule", "what", "owner", "cadence", "executor", "llm", "output", "breaks", "kill"];
@@ -23049,7 +23286,7 @@ async function readWorkflowFiles(dir) {
23049
23286
  const files = [];
23050
23287
  for (const name of names.sort()) {
23051
23288
  if (!/\.ya?ml$/.test(name)) continue;
23052
- files.push({ path: `.github/workflows/${name}`, text: await (0, import_promises6.readFile)((0, import_node_path28.join)(dir, name), "utf8") });
23289
+ files.push({ path: `.github/workflows/${name}`, text: await (0, import_promises6.readFile)((0, import_node_path27.join)(dir, name), "utf8") });
23053
23290
  }
23054
23291
  return files;
23055
23292
  }
@@ -23593,7 +23830,7 @@ function registerQueryCommands(program3) {
23593
23830
  // src/bootstrap-commands.ts
23594
23831
  var import_node_fs30 = require("node:fs");
23595
23832
  var import_node_os11 = require("node:os");
23596
- var import_node_path29 = require("node:path");
23833
+ var import_node_path28 = require("node:path");
23597
23834
 
23598
23835
  // src/bootstrap-drift.ts
23599
23836
  function byteComparableSeeds(manifest, cls) {
@@ -24462,7 +24699,7 @@ function registerBootstrapCommands(program3) {
24462
24699
  const readFile9 = (p) => (0, import_node_fs30.existsSync)(p) ? (0, import_node_fs30.readFileSync)(p, "utf8") : null;
24463
24700
  const enc = (p) => p.split("/").map(encodeURIComponent).join("/");
24464
24701
  const putSeed = async (target, content, ref, sha) => {
24465
- 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`);
24702
+ const tmp = (0, import_node_path28.join)((0, import_node_os11.tmpdir)(), `mmi-seed-${process.pid}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}.json`);
24466
24703
  (0, import_node_fs30.writeFileSync)(tmp, JSON.stringify(contentPutBody(target, content, ref, sha)), "utf8");
24467
24704
  try {
24468
24705
  await gh(contentPutInputArgs(repo, target, tmp));
@@ -24726,11 +24963,11 @@ LIVE apply to ${repo}:
24726
24963
 
24727
24964
  // src/stage-commands.ts
24728
24965
  var import_node_fs32 = require("node:fs");
24729
- var import_node_path31 = require("node:path");
24966
+ var import_node_path30 = require("node:path");
24730
24967
 
24731
24968
  // src/port-registry.ts
24732
24969
  var import_node_fs31 = require("node:fs");
24733
- var import_node_path30 = require("node:path");
24970
+ var import_node_path29 = require("node:path");
24734
24971
 
24735
24972
  // ../infra/port-geometry.mjs
24736
24973
  var PORT_BLOCK = 100;
@@ -24783,22 +25020,22 @@ function existingPortRange(repo, registry2) {
24783
25020
  return registry2[repo] ?? null;
24784
25021
  }
24785
25022
  function portRangeInfraAt(root, source) {
24786
- const registryPath = (0, import_node_path30.join)(root, "infra", "port-ranges.json");
24787
- const ddbScriptPath = (0, import_node_path30.join)(root, "infra", "port-ddb.mjs");
25023
+ const registryPath = (0, import_node_path29.join)(root, "infra", "port-ranges.json");
25024
+ const ddbScriptPath = (0, import_node_path29.join)(root, "infra", "port-ddb.mjs");
24788
25025
  if (!(0, import_node_fs31.existsSync)(registryPath) || !(0, import_node_fs31.existsSync)(ddbScriptPath)) return null;
24789
25026
  return { root, source, registryPath, ddbScriptPath };
24790
25027
  }
24791
25028
  function resolvePortRangeInfra(cwd, packageDir) {
24792
25029
  const direct = portRangeInfraAt(cwd, "cwd");
24793
25030
  if (direct) return direct;
24794
- for (let dir = cwd; ; dir = (0, import_node_path30.dirname)(dir)) {
24795
- const sibling = portRangeInfraAt((0, import_node_path30.join)(dir, "MMI-Hub"), "sibling-hub");
25031
+ for (let dir = cwd; ; dir = (0, import_node_path29.dirname)(dir)) {
25032
+ const sibling = portRangeInfraAt((0, import_node_path29.join)(dir, "MMI-Hub"), "sibling-hub");
24796
25033
  if (sibling) return sibling;
24797
- const parent = (0, import_node_path30.dirname)(dir);
25034
+ const parent = (0, import_node_path29.dirname)(dir);
24798
25035
  if (parent === dir) break;
24799
25036
  }
24800
25037
  if (packageDir) {
24801
- const pkgRoot = (0, import_node_path30.join)(packageDir, "..", "..");
25038
+ const pkgRoot = (0, import_node_path29.join)(packageDir, "..", "..");
24802
25039
  const pkgFrom = portRangeInfraAt(pkgRoot, "pkg-root");
24803
25040
  if (pkgFrom) return pkgFrom;
24804
25041
  }
@@ -24992,8 +25229,8 @@ function registerStageCommands(program3) {
24992
25229
  const portRange = portRangeMeta && typeof portRangeMeta.start === "number" && typeof portRangeMeta.end === "number" ? [portRangeMeta.start, portRangeMeta.end] : void 0;
24993
25230
  return decideStage({
24994
25231
  registry: { deployModel: project2?.deployModel, portRange, error: read.ok ? void 0 : read.error },
24995
- hasCompose: (0, import_node_fs32.existsSync)((0, import_node_path31.join)(process.cwd(), "docker-compose.yml")),
24996
- hasEnvExample: (0, import_node_fs32.existsSync)((0, import_node_path31.join)(process.cwd(), ".env.example"))
25232
+ hasCompose: (0, import_node_fs32.existsSync)((0, import_node_path30.join)(process.cwd(), "docker-compose.yml")),
25233
+ hasEnvExample: (0, import_node_fs32.existsSync)((0, import_node_path30.join)(process.cwd(), ".env.example"))
24997
25234
  });
24998
25235
  }
24999
25236
  async function fetchStageVaultEnvMerge() {
@@ -25433,7 +25670,7 @@ function registerBoardCommands(program3) {
25433
25670
  // src/merge-cleanup.ts
25434
25671
  var import_node_fs33 = require("node:fs");
25435
25672
  var import_promises8 = require("node:fs/promises");
25436
- var import_node_path33 = require("node:path");
25673
+ var import_node_path32 = require("node:path");
25437
25674
  var import_node_os12 = require("node:os");
25438
25675
  var import_node_child_process16 = require("node:child_process");
25439
25676
 
@@ -25521,7 +25758,7 @@ function boardAdvanceFailureMessage(result) {
25521
25758
 
25522
25759
  // src/deferred-registry-store.ts
25523
25760
  var import_promises7 = require("node:fs/promises");
25524
- var import_node_path32 = require("node:path");
25761
+ var import_node_path31 = require("node:path");
25525
25762
  var sleep2 = (ms) => new Promise((resolve5) => setTimeout(resolve5, ms));
25526
25763
  async function atomicWrite(target, contents) {
25527
25764
  const tmp = `${target}.tmp-${process.pid}-${Math.random().toString(36).slice(2)}`;
@@ -25572,12 +25809,12 @@ function makeDeferredWorktreeStore(registryPath, lockOpts = {}) {
25572
25809
  },
25573
25810
  // Standalone atomic write — THROWS on failure (no best-effort swallow, #2846).
25574
25811
  write: async (entries) => {
25575
- await (0, import_promises7.mkdir)((0, import_node_path32.dirname)(registryPath), { recursive: true });
25812
+ await (0, import_promises7.mkdir)((0, import_node_path31.dirname)(registryPath), { recursive: true });
25576
25813
  await atomicWrite(registryPath, serializeDeferredWorktrees(entries));
25577
25814
  },
25578
25815
  // Serialized read-modify-write under the repo-wide lock (#2846).
25579
25816
  update: async (mutate) => {
25580
- await (0, import_promises7.mkdir)((0, import_node_path32.dirname)(registryPath), { recursive: true });
25817
+ await (0, import_promises7.mkdir)((0, import_node_path31.dirname)(registryPath), { recursive: true });
25581
25818
  const deadline = Date.now() + opts.maxWaitMs;
25582
25819
  for (; ; ) {
25583
25820
  const guard = await acquireLock(lockPath, opts, deadline);
@@ -25725,7 +25962,7 @@ async function applyGcPlan(plan, remote, opts = {}) {
25725
25962
  );
25726
25963
  const repoRoot2 = (await execFileP2("git", ["rev-parse", "--show-toplevel"], { timeout: GIT_TIMEOUT_MS }).catch(() => ({ stdout: "" }))).stdout.trim() || process.cwd();
25727
25964
  const worktreeGitRoot = await currentRepoWorktreeGitRoot(repoRoot2);
25728
- const primaryRepoRoot = worktreeGitRoot ? (0, import_node_path33.dirname)((0, import_node_path33.dirname)(worktreeGitRoot)) : repoRoot2;
25965
+ const primaryRepoRoot = worktreeGitRoot ? (0, import_node_path32.dirname)((0, import_node_path32.dirname)(worktreeGitRoot)) : repoRoot2;
25729
25966
  const gcActor = describeActor({ env: process.env, surface: detectSurface(process.env), cwd: process.cwd() });
25730
25967
  const owners = readWorktreeOwners(primaryRepoRoot);
25731
25968
  const removalNow = Date.now();
@@ -25870,8 +26107,8 @@ async function composeOverrideBodyFile(prNumber, repoArgs, gh) {
25870
26107
  const commits = JSON.parse(raw).commits ?? [];
25871
26108
  const body = squashBodyWithOverride(commits.map((c) => ({ headline: c.messageHeadline ?? "", body: c.messageBody ?? "" })), process.cwd());
25872
26109
  if (!body) return void 0;
25873
- const dir = (0, import_node_fs33.mkdtempSync)((0, import_node_path33.join)((0, import_node_os12.tmpdir)(), "mmi-squash-body-"));
25874
- const path2 = (0, import_node_path33.join)(dir, "body.txt");
26110
+ const dir = (0, import_node_fs33.mkdtempSync)((0, import_node_path32.join)((0, import_node_os12.tmpdir)(), "mmi-squash-body-"));
26111
+ const path2 = (0, import_node_path32.join)(dir, "body.txt");
25875
26112
  (0, import_node_fs33.writeFileSync)(path2, `${body}
25876
26113
  `, "utf8");
25877
26114
  return { path: path2, cleanup: () => {
@@ -26391,7 +26628,7 @@ async function checkDocsIndexAtHead(opts, deps) {
26391
26628
  // src/worktree-lifecycle-commands.ts
26392
26629
  var import_node_fs34 = require("node:fs");
26393
26630
  var import_promises9 = require("node:fs/promises");
26394
- var import_node_path34 = require("node:path");
26631
+ var import_node_path33 = require("node:path");
26395
26632
  var GH_TIMEOUT_MS = 2e4;
26396
26633
  var DEFAULT_BASE = "origin/development";
26397
26634
  var DEFAULT_REMOTE = "origin";
@@ -26537,7 +26774,7 @@ function classifyStaleLeaks(input) {
26537
26774
  var defaultOrphanDirScanDeps = {
26538
26775
  listDirs: (root) => {
26539
26776
  try {
26540
- return (0, import_node_fs34.readdirSync)(root, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => (0, import_node_path34.join)(root, e.name));
26777
+ return (0, import_node_fs34.readdirSync)(root, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => (0, import_node_path33.join)(root, e.name));
26541
26778
  } catch {
26542
26779
  return [];
26543
26780
  }
@@ -26690,13 +26927,13 @@ function registerWorktreeCommands(program3) {
26690
26927
  const detached = headBorn && !symbolicBranch;
26691
26928
  const branch = symbolicBranch || (detached ? "HEAD" : "");
26692
26929
  if (!wtPath || !branch) return fail("worktree land: not inside a git worktree");
26693
- const gitFile = (0, import_node_path34.join)(wtPath, ".git");
26930
+ const gitFile = (0, import_node_path33.join)(wtPath, ".git");
26694
26931
  const isLinked = (0, import_node_fs34.existsSync)(gitFile) && (0, import_node_fs34.statSync)(gitFile).isFile();
26695
26932
  if (apply && !isLinked) {
26696
26933
  return fail("worktree land: run from inside the linked worktree you want to land (this is the primary checkout)");
26697
26934
  }
26698
26935
  const commonDir = (await execFileP2("git", ["rev-parse", "--git-common-dir"], { timeout: GIT_TIMEOUT_MS }).catch(() => ({ stdout: "" }))).stdout.trim();
26699
- const primaryCheckout = commonDir ? (0, import_node_path34.dirname)(commonDir) : wtPath;
26936
+ const primaryCheckout = commonDir ? (0, import_node_path33.dirname)(commonDir) : wtPath;
26700
26937
  const localBranchNames = await execFileP2("git", ["-C", primaryCheckout, "for-each-ref", "--format=%(refname:short)", "refs/heads"], { timeout: GIT_TIMEOUT_MS }).then(({ stdout }) => new Set((stdout || "").split("\n").map((l) => l.trim()).filter(Boolean))).catch(() => void 0);
26701
26938
  const orphan = classifyOrphanedWorktree({
26702
26939
  branch,
@@ -26907,7 +27144,7 @@ async function gatherWorktreeContext() {
26907
27144
  if (s) stages.push({ path: wt.path, port: s.port });
26908
27145
  }
26909
27146
  const worktreeGitRoot = await currentRepoWorktreeGitRoot(repoRoot2);
26910
- const primaryRepoRoot = worktreeGitRoot ? (0, import_node_path34.dirname)((0, import_node_path34.dirname)(worktreeGitRoot)) : repoRoot2;
27147
+ const primaryRepoRoot = worktreeGitRoot ? (0, import_node_path33.dirname)((0, import_node_path33.dirname)(worktreeGitRoot)) : repoRoot2;
26911
27148
  const wtRoot = siblingMmiWorktreesRoot(primaryRepoRoot);
26912
27149
  let orphanDirs = [];
26913
27150
  if ((0, import_node_fs34.existsSync)(wtRoot)) {
@@ -27592,7 +27829,7 @@ ${lines}`, {
27592
27829
 
27593
27830
  // src/train-commands.ts
27594
27831
  var import_node_fs36 = require("node:fs");
27595
- var import_node_path35 = require("node:path");
27832
+ var import_node_path34 = require("node:path");
27596
27833
 
27597
27834
  // src/train-status.ts
27598
27835
  function buildTrainStatusReport(input) {
@@ -27632,7 +27869,7 @@ function formatTrainStatus(r) {
27632
27869
  // src/train-commands.ts
27633
27870
  function readRepoVersion() {
27634
27871
  try {
27635
- return JSON.parse((0, import_node_fs36.readFileSync)((0, import_node_path35.join)(process.cwd(), ".claude-plugin", "plugin.json"), "utf8")).version || void 0;
27872
+ return JSON.parse((0, import_node_fs36.readFileSync)((0, import_node_path34.join)(process.cwd(), ".claude-plugin", "plugin.json"), "utf8")).version || void 0;
27636
27873
  } catch {
27637
27874
  return void 0;
27638
27875
  }
@@ -27780,7 +28017,7 @@ function registerDeployCommands(program3) {
27780
28017
  // src/discovery-commands.ts
27781
28018
  var import_node_fs37 = require("node:fs");
27782
28019
  var import_node_os13 = require("node:os");
27783
- var import_node_path36 = require("node:path");
28020
+ var import_node_path35 = require("node:path");
27784
28021
  var GC_GH_TIMEOUT_MS3 = 2e4;
27785
28022
  async function collectStatus() {
27786
28023
  const repo = await resolveRepo();
@@ -27866,6 +28103,11 @@ async function recommendNext(repo, deps) {
27866
28103
  const top = sorted[0];
27867
28104
  return { number: top.number, title: top.title, url: top.url, priority: top.priority, repo: top.repository };
27868
28105
  }
28106
+ function requiredEstateCommandsPresent(commandNames) {
28107
+ const required = ["repo-index"];
28108
+ const present = new Set(commandNames);
28109
+ return required.filter((name) => !present.has(name));
28110
+ }
27869
28111
  function onboardPluginGate(deps) {
27870
28112
  const { registered, declared, ref } = readKnownMarketplace(deps.readKnown(), MMI_MARKETPLACE_NAME);
27871
28113
  if (!registered) {
@@ -27883,8 +28125,13 @@ function onboardPluginGate(deps) {
27883
28125
  if (catalog && !catalog.agrees) gaps.push(`the catalog is ${catalog.unpinned ? "unpinned" : `pinned to ${catalog.ref}`} \u2014 ${CATALOG_REF_PIN_STEPS}`);
27884
28126
  return gaps.length === 0 ? { ok: true, detail: `auto-update on, catalog pinned to ${catalog?.ref}` } : { ok: false, detail: gaps.join("; ") };
27885
28127
  }
27886
- async function collectOnboardStatus() {
28128
+ async function collectOnboardStatus(opts = {}) {
27887
28129
  const cfg = await loadConfig();
28130
+ const missingEstate = requiredEstateCommandsPresent(opts.commandNames ?? []);
28131
+ const estateCli = missingEstate.length === 0 ? { ok: true, detail: "estate commands present (repo-index)" } : {
28132
+ ok: false,
28133
+ detail: `global CLI missing ${missingEstate.join(", ")} \u2014 run mmi-cli doctor to heal`
28134
+ };
27888
28135
  let track = "unknown";
27889
28136
  try {
27890
28137
  const revParse2 = (await execFileP2("git", ["rev-parse", "--abbrev-ref", "HEAD"], { timeout: GIT_TIMEOUT_MS }).catch(() => ({ stdout: "" }))).stdout.trim();
@@ -27938,7 +28185,9 @@ async function collectOnboardStatus() {
27938
28185
  }
27939
28186
  }
27940
28187
  let nextCommand = "mmi-cli doctor";
27941
- if (!cfg.sagaApiUrl) {
28188
+ if (!estateCli.ok) {
28189
+ nextCommand = "mmi-cli doctor \u2014 heal global CLI (missing repo-index)";
28190
+ } else if (!cfg.sagaApiUrl) {
27942
28191
  nextCommand = "mmi-cli doctor \u2014 fix Hub API URL configuration first";
27943
28192
  } else if (!registry2.ok) {
27944
28193
  nextCommand = "mmi-cli org project set <owner/repo>";
@@ -27958,10 +28207,10 @@ async function collectOnboardStatus() {
27958
28207
  }
27959
28208
  const home = (0, import_node_os13.homedir)();
27960
28209
  const plugin = onboardPluginGate({
27961
- readKnown: () => readFileSyncSafe((0, import_node_path36.join)(home, ...KNOWN_MARKETPLACES_RELATIVE), import_node_fs37.readFileSync),
27962
- readSettings: () => readFileSyncSafe((0, import_node_path36.join)(home, ".claude", "settings.json"), import_node_fs37.readFileSync)
28210
+ readKnown: () => readFileSyncSafe((0, import_node_path35.join)(home, ...KNOWN_MARKETPLACES_RELATIVE), import_node_fs37.readFileSync),
28211
+ readSettings: () => readFileSyncSafe((0, import_node_path35.join)(home, ".claude", "settings.json"), import_node_fs37.readFileSync)
27963
28212
  });
27964
- return { track, board, registry: registry2, secrets, plugin, nextCommand };
28213
+ return { track, board, registry: registry2, secrets, plugin, estateCli, nextCommand };
27965
28214
  }
27966
28215
  function registerDiscoveryCommands(program3) {
27967
28216
  program3.command("status").description("unified repo snapshot: branch, worktrees, open PRs (yours), claimed board items, stage state").option("--json", "machine-readable output").action(async (o) => {
@@ -27998,7 +28247,8 @@ function registerDiscoveryCommands(program3) {
27998
28247
  });
27999
28248
  program3.command("onboard").description("report repo track/board/secrets/registry readiness and the concrete next command").option("--json", "machine-readable output").action(async (o) => {
28000
28249
  try {
28001
- const report = await collectOnboardStatus();
28250
+ const commandNames = program3.commands.map((c) => c.name());
28251
+ const report = await collectOnboardStatus({ commandNames });
28002
28252
  if (o.json) {
28003
28253
  console.log(JSON.stringify(report, null, 2));
28004
28254
  } else {
@@ -28007,6 +28257,7 @@ function registerDiscoveryCommands(program3) {
28007
28257
  console.log(`Registry: ${report.registry.ok ? "\u2713" : "\u2717"} ${report.registry.detail}`);
28008
28258
  console.log(`Secrets: ${report.secrets.ok ? "\u2713" : "\u2717"} ${report.secrets.detail}`);
28009
28259
  console.log(`Plugin: ${report.plugin.ok ? "\u2713" : "\u2717"} ${report.plugin.detail}`);
28260
+ console.log(`Estate CLI: ${report.estateCli.ok ? "\u2713" : "\u2717"} ${report.estateCli.detail}`);
28010
28261
  console.log(`
28011
28262
  Next command: ${report.nextCommand}`);
28012
28263
  }
@@ -29220,6 +29471,63 @@ var surfaces_default = {
29220
29471
  "Some read, write, and web-fetch tool outputs are not rewriteable by the host.",
29221
29472
  "Provisioned skills and agents become discoverable only after a fresh session."
29222
29473
  ]
29474
+ },
29475
+ {
29476
+ token: "jervcode",
29477
+ displayName: "JervCode",
29478
+ lifecycle: "active",
29479
+ artifactIds: ["mmi-pi-plugin", "mmi-skills", "mmi-cli"],
29480
+ plannedArtifactIds: [],
29481
+ assembly: {
29482
+ mode: "source-tree",
29483
+ rootPath: ".",
29484
+ sync: []
29485
+ },
29486
+ install: {
29487
+ mechanism: "managed-config",
29488
+ locator: "~/.pi/agent/settings.json"
29489
+ },
29490
+ upgrade: {
29491
+ mechanism: "reinstall",
29492
+ reload: "restart"
29493
+ },
29494
+ skills: {
29495
+ delivery: "manifest",
29496
+ sourceSurfaceId: "mmi-skills",
29497
+ artifactId: "mmi-skills",
29498
+ invocation: {
29499
+ entry: "/mmi",
29500
+ any: "/<skill>"
29501
+ }
29502
+ },
29503
+ hooks: {
29504
+ adapterPath: ".pi-plugin/package.json",
29505
+ sourceSurfaceId: "mmi-hook-policy",
29506
+ gates: [],
29507
+ probe: "unsupported",
29508
+ execution: "unsupported",
29509
+ preToolUse: "unsupported",
29510
+ postToolOutput: "unsupported",
29511
+ finalOutput: "unsupported"
29512
+ },
29513
+ cli: {
29514
+ delivery: "standalone",
29515
+ artifactId: "mmi-cli"
29516
+ },
29517
+ ownership: {
29518
+ trust: "host",
29519
+ cache: "host",
29520
+ repair: "mmi-cli"
29521
+ },
29522
+ certification: {
29523
+ hostCommand: "jervcode",
29524
+ versionArgs: ["--version"]
29525
+ },
29526
+ enforcementCeilings: [
29527
+ "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.",
29528
+ "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.",
29529
+ "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."
29530
+ ]
29223
29531
  }
29224
29532
  ],
29225
29533
  surfaces: [
@@ -29530,12 +29838,38 @@ var surfaces_default = {
29530
29838
  applicability: "master DM notifications only; chatops out by default",
29531
29839
  versionCoordinated: false,
29532
29840
  publishVisibility: "n/a"
29841
+ },
29842
+ {
29843
+ id: "mmi-pi-plugin",
29844
+ classification: "packaging",
29845
+ kind: "plugin",
29846
+ ownerPath: ".pi-plugin/package.json",
29847
+ deliveryPath: ".pi-plugin/package.json",
29848
+ delivery: "release",
29849
+ 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.",
29850
+ versionCoordinated: true,
29851
+ surfaceToken: "jervcode",
29852
+ versionPaths: [
29853
+ { path: ".pi-plugin/package.json", pointer: "version" }
29854
+ ],
29855
+ artifactIdentity: {
29856
+ kind: "sha256-tree",
29857
+ paths: [".pi-plugin/package.json", "skills"]
29858
+ },
29859
+ verify: [
29860
+ {
29861
+ command: "node",
29862
+ args: ["scripts/check-jervcode-consumer.mjs"],
29863
+ expected: "JervCode consumer fitness: clean ({version})"
29864
+ }
29865
+ ],
29866
+ publishVisibility: "public"
29533
29867
  }
29534
29868
  ]
29535
29869
  };
29536
29870
 
29537
29871
  // src/surface-doctor.ts
29538
- var TOKENS = /* @__PURE__ */ new Set(["claude", "codex", "kimi", "cursor", "kilo"]);
29872
+ var TOKENS = /* @__PURE__ */ new Set(["claude", "codex", "kimi", "cursor", "kilo", "jervcode"]);
29539
29873
  function isActiveToken(value) {
29540
29874
  return TOKENS.has(value);
29541
29875
  }
@@ -30056,7 +30390,7 @@ async function runDoctorClean(opts, io, deps) {
30056
30390
  if (!opts.json) io.log(`\u21BB ${line}`);
30057
30391
  };
30058
30392
  const healStep = (message) => {
30059
- if (!opts.json) io.log(`${VERBOSE_INDENT}${message.trim()}`);
30393
+ if (!opts.json && opts.verbose) io.log(`${VERBOSE_INDENT}${message.trim()}`);
30060
30394
  };
30061
30395
  const releasedNote = deps.releasedVersionNote?.();
30062
30396
  let pluginHealed = false;
@@ -30091,30 +30425,63 @@ async function runDoctorClean(opts, io, deps) {
30091
30425
  }
30092
30426
  }
30093
30427
  async function runCliRow() {
30428
+ const missing = deps.missingCliCommands?.() ?? [];
30094
30429
  const cliInput = { currentVersion: deps.currentCliVersion(), releasedVersion: released };
30095
30430
  const cliReport = buildVersionLagReport(cliInput);
30096
- if (!(applyEnv && deps.updateCli && versionAutoUpdateAction(cliReport) === "npm")) {
30431
+ const capabilityGap = missing.length > 0 && Boolean(cliReport.releasedVersion);
30432
+ const shouldUpdate = Boolean(
30433
+ applyEnv && deps.updateCli && (versionAutoUpdateAction(cliReport) === "npm" || capabilityGap)
30434
+ );
30435
+ if (!shouldUpdate) {
30097
30436
  const cli = checkCliVersion(cliInput, releasedNote);
30098
- if (cli) emitNow(cli);
30437
+ if (cli) {
30438
+ if (missing.length) {
30439
+ emitNow({
30440
+ ...cli,
30441
+ ok: false,
30442
+ detail: cli.ok ? `missing commands: ${missing.join(", ")}` : `${cli.detail ?? ""}`.trim() || `missing commands: ${missing.join(", ")}`,
30443
+ fix: `run \`mmi-cli doctor\` to heal the global CLI (missing: ${missing.join(", ")}), or \`${cliUpdateCommand(cliReport.releasedVersion)}\``,
30444
+ verbose: [...cli.verbose ?? [], `missing commands: ${missing.join(", ")}`]
30445
+ });
30446
+ } else {
30447
+ emitNow(cli);
30448
+ }
30449
+ } else if (missing.length) {
30450
+ emitNow({
30451
+ id: "cli-version",
30452
+ ok: false,
30453
+ label: "mmi-cli",
30454
+ detail: `missing commands: ${missing.join(", ")}`,
30455
+ fix: `run \`mmi-cli doctor\` to heal the global CLI, or \`${cliUpdateCommand(cliReport.releasedVersion)}\``,
30456
+ verbose: [`missing commands: ${missing.join(", ")}`]
30457
+ });
30458
+ }
30099
30459
  return;
30100
30460
  }
30101
- healIntent(`mmi-cli \u2014 self-updating ${cliReport.currentVersion} \u2192 ${cliReport.releasedVersion} via npm install -g`);
30102
- const heal = await deps.updateCli(cliReport.releasedVersion, healStep);
30103
- const healEvidence = [`running: ${cliReport.currentVersion}`, `published: ${cliReport.releasedVersion}`, `heal: ${heal.detail}`];
30461
+ const target = cliReport.releasedVersion;
30462
+ const intent = capabilityGap && cliReport.ok ? `mmi-cli \u2014 self-updating to ${target} via npm install -g (missing commands: ${missing.join(", ")})` : `mmi-cli \u2014 self-updating ${cliReport.currentVersion} \u2192 ${target} via npm install -g`;
30463
+ healIntent(intent);
30464
+ const heal = await deps.updateCli(target, healStep);
30465
+ const healEvidence = [
30466
+ `running: ${cliReport.currentVersion}`,
30467
+ `published: ${target ?? "(unknown)"}`,
30468
+ `heal: ${heal.detail}`,
30469
+ ...missing.length ? [`missing commands before heal: ${missing.join(", ")}`] : []
30470
+ ];
30104
30471
  emitNow(heal.ok ? {
30105
30472
  id: "cli-version",
30106
30473
  ok: true,
30107
30474
  label: "mmi-cli",
30108
- detail: `${cliReport.currentVersion} \u2192 ${cliReport.releasedVersion} \u2014 self-updated via npm install -g; the next mmi-cli invocation runs the new version`,
30475
+ detail: capabilityGap && cliReport.ok ? `${cliReport.currentVersion} \u2192 ${target} \u2014 self-updated via npm install -g (was missing: ${missing.join(", ")}); the next mmi-cli invocation runs the new version` : `${cliReport.currentVersion} \u2192 ${target} \u2014 self-updated via npm install -g; the next mmi-cli invocation runs the new version`,
30109
30476
  verbose: healEvidence
30110
30477
  } : {
30111
30478
  id: "cli-version",
30112
30479
  ok: false,
30113
30480
  label: "mmi-cli",
30114
- detail: `${cliReport.currentVersion} \u2192 ${cliReport.releasedVersion}`,
30481
+ detail: capabilityGap && cliReport.ok ? `missing commands: ${missing.join(", ")}` : `${cliReport.currentVersion} \u2192 ${target}`,
30115
30482
  // #3489: same split as the plugin heal above — a lock-contention skip is not this run's gap.
30116
30483
  ...heal.skipped ? { reportOnly: true } : {},
30117
- fix: heal.skipped ? `${heal.detail} \u2014 another doctor on this machine is updating it now; re-run once it finishes` : `self-update failed (${heal.detail}) \u2014 run \`${cliUpdateCommand(cliReport.releasedVersion)}\``,
30484
+ fix: heal.skipped ? `${heal.detail} \u2014 another doctor on this machine is updating it now; re-run once it finishes` : `self-update failed (${heal.detail}) \u2014 run \`${cliUpdateCommand(target)}\``,
30118
30485
  verbose: healEvidence
30119
30486
  });
30120
30487
  }
@@ -30177,8 +30544,10 @@ async function runDoctorClean(opts, io, deps) {
30177
30544
  }
30178
30545
  async function runDocsIndexRow() {
30179
30546
  let probe;
30547
+ let root;
30180
30548
  try {
30181
- probe = deps.docsIndexState(await deps.repoRoot());
30549
+ root = await deps.repoRoot();
30550
+ probe = deps.docsIndexState(root);
30182
30551
  } catch (e) {
30183
30552
  const message = e instanceof Error ? e.message : String(e);
30184
30553
  emitNow({
@@ -30194,10 +30563,44 @@ async function runDoctorClean(opts, io, deps) {
30194
30563
  });
30195
30564
  return;
30196
30565
  }
30566
+ let healedWrite = false;
30567
+ if (applyRepo && probe?.drift && deps.healDocsIndex) {
30568
+ healIntent("docs index \u2014 regenerating docs/index.md");
30569
+ try {
30570
+ probe = deps.healDocsIndex(root);
30571
+ healedWrite = !probe.drift;
30572
+ } catch (e) {
30573
+ const message = e instanceof Error ? e.message : String(e);
30574
+ emitNow({
30575
+ ok: false,
30576
+ id: "docs-index",
30577
+ label: "docs index",
30578
+ detail: `heal failed \u2014 ${message}`,
30579
+ fix: "run `mmi-cli docs index --write` and commit docs/index.md",
30580
+ verbose: [`healDocsIndex threw: ${message}`]
30581
+ });
30582
+ return;
30583
+ }
30584
+ }
30197
30585
  const docs2 = checkDocsIndex(probe);
30198
- if (docs2) emitNow(docs2);
30586
+ if (!docs2) return;
30587
+ if (healedWrite) {
30588
+ emitNow({
30589
+ ...docs2,
30590
+ detail: "rewrote docs/index.md (working-tree heal \u2014 commit still needed)"
30591
+ });
30592
+ restartPending = true;
30593
+ return;
30594
+ }
30595
+ emitNow(docs2);
30199
30596
  }
30200
30597
  async function runRepoIndexRow() {
30598
+ const missing = deps.missingCliCommands?.() ?? [];
30599
+ if (missing.includes("repo-index")) {
30600
+ const row2 = checkRepoIndexCloud({ kind: "command-absent" });
30601
+ if (row2) emitNow(row2);
30602
+ return;
30603
+ }
30201
30604
  if (lane.preflight && !opts.self && !lane.full) {
30202
30605
  emitNow({
30203
30606
  ok: true,
@@ -30227,8 +30630,176 @@ async function runDoctorClean(opts, io, deps) {
30227
30630
  const row = checkRepoIndexCloud(probe);
30228
30631
  if (row) emitNow(row);
30229
30632
  }
30633
+ async function runBoardDoctorRow() {
30634
+ if (!deps.boardDoctorFix) return;
30635
+ try {
30636
+ const result = await deps.boardDoctorFix({ fix: applyRepo });
30637
+ if (!result) {
30638
+ emitNow({
30639
+ ok: true,
30640
+ warn: true,
30641
+ reportOnly: true,
30642
+ id: "board-doctor",
30643
+ label: "board doctor",
30644
+ detail: "skipped",
30645
+ verbose: ["boardDoctorFix returned undefined"]
30646
+ });
30647
+ return;
30648
+ }
30649
+ if (result.timedOut) {
30650
+ emitNow({
30651
+ ok: false,
30652
+ warn: true,
30653
+ reportOnly: true,
30654
+ id: "board-doctor",
30655
+ label: "board doctor",
30656
+ detail: "timed out",
30657
+ fix: "run `mmi-cli board doctor --fix`",
30658
+ verbose: [`scanned before timeout: ${result.scanned}`, `findings: ${result.findings}`]
30659
+ });
30660
+ return;
30661
+ }
30662
+ const allFixed = applyRepo && result.findings > 0 && result.failed === 0 && result.fixed >= result.findings;
30663
+ const ok = result.findings === 0 || Boolean(allFixed);
30664
+ emitNow(ok ? {
30665
+ ok: true,
30666
+ id: "board-doctor",
30667
+ label: "board doctor",
30668
+ detail: `scanned ${result.scanned}; fixed ${result.fixed}`,
30669
+ verbose: [`findings: ${result.findings}`, `fixed: ${result.fixed}`, `failed: ${result.failed}`]
30670
+ } : {
30671
+ ok: false,
30672
+ // Gate when fix was attempted and mechanical findings remain; detect-only stays visible but
30673
+ // report-only would hide a red that `board doctor --fix` clears — prefer gate after a failed fix.
30674
+ reportOnly: !applyRepo,
30675
+ id: "board-doctor",
30676
+ label: "board doctor",
30677
+ detail: `scanned ${result.scanned}; ${result.findings} findings; fixed ${result.fixed}; failed ${result.failed}`,
30678
+ fix: "run `mmi-cli board doctor --fix`",
30679
+ verbose: [`findings: ${result.findings}`, `fixed: ${result.fixed}`, `failed: ${result.failed}`]
30680
+ });
30681
+ } catch (e) {
30682
+ const message = e instanceof Error ? e.message : String(e);
30683
+ emitNow({
30684
+ ok: true,
30685
+ warn: true,
30686
+ reportOnly: true,
30687
+ id: "board-doctor",
30688
+ label: "board doctor",
30689
+ detail: `skipped \u2014 ${message}`,
30690
+ verbose: [`boardDoctorFix threw: ${message}`]
30691
+ });
30692
+ }
30693
+ }
30694
+ async function runSchedulesDriftRow() {
30695
+ if (!deps.schedulesDriftState) return;
30696
+ try {
30697
+ const state = await deps.schedulesDriftState();
30698
+ if (!state) {
30699
+ emitNow({
30700
+ ok: true,
30701
+ warn: true,
30702
+ reportOnly: true,
30703
+ id: "schedules-drift",
30704
+ label: "schedules drift",
30705
+ detail: "skipped",
30706
+ verbose: ["schedulesDriftState returned undefined"]
30707
+ });
30708
+ return;
30709
+ }
30710
+ if (state.timedOut) {
30711
+ emitNow({
30712
+ ok: false,
30713
+ warn: true,
30714
+ reportOnly: true,
30715
+ id: "schedules-drift",
30716
+ label: "schedules drift",
30717
+ detail: "timed out",
30718
+ fix: "run `mmi-cli org schedules` / `org schedules register`",
30719
+ verbose: ["schedulesDriftState timed out"]
30720
+ });
30721
+ return;
30722
+ }
30723
+ if (state.incomplete.length) {
30724
+ emitNow({
30725
+ ok: true,
30726
+ warn: true,
30727
+ reportOnly: true,
30728
+ id: "schedules-drift",
30729
+ label: "schedules drift",
30730
+ detail: `incomplete \u2014 ${state.incomplete.join("; ")}`,
30731
+ fix: "run `mmi-cli org schedules` / `org schedules register`",
30732
+ verbose: state.incomplete
30733
+ });
30734
+ return;
30735
+ }
30736
+ if (state.driftLines.length === 0) {
30737
+ emitNow({
30738
+ ok: true,
30739
+ reportOnly: true,
30740
+ id: "schedules-drift",
30741
+ label: "schedules drift",
30742
+ detail: "no drift",
30743
+ verbose: ["no drift lines"]
30744
+ });
30745
+ return;
30746
+ }
30747
+ emitNow({
30748
+ ok: false,
30749
+ reportOnly: true,
30750
+ id: "schedules-drift",
30751
+ label: "schedules drift",
30752
+ detail: `${state.driftLines.length} drift line(s)`,
30753
+ fix: "run `mmi-cli org schedules` / `org schedules register`",
30754
+ verbose: state.driftLines
30755
+ });
30756
+ } catch (e) {
30757
+ const message = e instanceof Error ? e.message : String(e);
30758
+ emitNow({
30759
+ ok: true,
30760
+ warn: true,
30761
+ reportOnly: true,
30762
+ id: "schedules-drift",
30763
+ label: "schedules drift",
30764
+ detail: `skipped \u2014 ${message}`,
30765
+ verbose: [`schedulesDriftState threw: ${message}`]
30766
+ });
30767
+ }
30768
+ }
30230
30769
  async function runHousekeeperRows() {
30231
30770
  const repoRoot2 = await deps.repoRoot();
30771
+ if (applyRepo && deps.sweepDeferred) {
30772
+ try {
30773
+ const sweep = await deps.sweepDeferred();
30774
+ const detailParts = [];
30775
+ if (sweep.removed.length) detailParts.push(`removed ${sweep.removed.length}`);
30776
+ if (sweep.stillQueued) detailParts.push(`${sweep.stillQueued} still queued`);
30777
+ if (sweep.skipped) detailParts.push(`${sweep.skipped} skipped`);
30778
+ emitNow({
30779
+ id: "deferred-worktrees",
30780
+ ok: true,
30781
+ label: "deferred worktrees",
30782
+ detail: detailParts.length ? detailParts.join("; ") : "nothing queued",
30783
+ verbose: [
30784
+ ...sweep.removed.map((p) => `removed: ${p}`),
30785
+ `stillQueued: ${sweep.stillQueued}`,
30786
+ `skipped: ${sweep.skipped}`
30787
+ ]
30788
+ });
30789
+ if (sweep.removed.length) restartPending = true;
30790
+ } catch (e) {
30791
+ const message = e instanceof Error ? e.message : String(e);
30792
+ emitNow({
30793
+ id: "deferred-worktrees",
30794
+ ok: true,
30795
+ warn: true,
30796
+ reportOnly: true,
30797
+ label: "deferred worktrees",
30798
+ detail: `sweep failed (fail-soft) \u2014 ${message}`,
30799
+ verbose: [`sweepDeferred threw: ${message}`]
30800
+ });
30801
+ }
30802
+ }
30232
30803
  try {
30233
30804
  const plan = await deps.gcPlan("origin", 200);
30234
30805
  if (applyRepo) {
@@ -30285,9 +30856,7 @@ async function runDoctorClean(opts, io, deps) {
30285
30856
  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 });
30286
30857
  }
30287
30858
  }
30288
- const table = [
30289
- // Heals first: the slowest work starts at second zero, and a stale mmi-cli is the binary every row
30290
- // below is being measured with (#3954).
30859
+ const prefix = [
30291
30860
  { id: "plugin", when: true, run: runPluginRow },
30292
30861
  { id: "cli-version", when: true, run: runCliRow },
30293
30862
  { id: "github-auth", when: true, run: runGithubAuthRow },
@@ -30296,18 +30865,22 @@ async function runDoctorClean(opts, io, deps) {
30296
30865
  { id: "gitignore-block", when: isOrgRepo, run: runGitignoreRow },
30297
30866
  { id: "plugin-cache", when: true, run: runPluginCacheRow },
30298
30867
  { id: "sessionstart-payload", when: true, run: runSessionPayloadRow },
30299
- { id: "marketplace", when: true, run: runMarketplaceRows },
30300
- // A `docs/` tree walk plus one batched `git check-ignore` — cheap next to the two rows below it, but a
30301
- // disk walk and a subprocess all the same, so full lane only (#4091).
30868
+ { id: "marketplace", when: true, run: runMarketplaceRows }
30869
+ ];
30870
+ for (const entry of prefix) if (entry.when) await entry.run();
30871
+ const parallel = [
30302
30872
  { id: "docs-index", when: isOrgRepo && lane.full, run: runDocsIndexRow },
30303
30873
  // Hub cloud probe (short timeout). Full lane + `--self`; `--preflight` may emit command-absent only (#4156).
30304
30874
  { id: "repo-index", when: isOrgRepo && (lane.full || Boolean(opts.self) || lane.preflight), run: runRepoIndexRow },
30305
- // The two most expensive things in this file — a real `git fetch` plus train-branch fast-forward,
30306
- // and a `gh`-backed gc sweep with a 20s timeout — so org repos on the full lane only (#3485).
30875
+ { id: "board-doctor", when: isOrgRepo && lane.full, run: runBoardDoctorRow },
30876
+ { id: "schedules-drift", when: isOrgRepo && lane.full, run: runSchedulesDriftRow }
30877
+ ];
30878
+ await Promise.all(parallel.filter((entry) => entry.when).map((entry) => entry.run()));
30879
+ const suffix = [
30307
30880
  { id: "train-branches", when: isOrgRepo && lane.full, run: runTrainSyncRow },
30308
30881
  { id: "housekeeper", when: isOrgRepo && lane.full, run: runHousekeeperRows }
30309
30882
  ];
30310
- for (const entry of table) if (entry.when) await entry.run();
30883
+ for (const entry of suffix) if (entry.when) await entry.run();
30311
30884
  const exitCode = doctorReportExitCode(checks);
30312
30885
  if (opts.json) {
30313
30886
  const payload = opts.verbose ? checks : checks.map(({ verbose: _evidence, ...check }) => check);
@@ -30360,17 +30933,17 @@ function parseOriginRepo(remoteUrl) {
30360
30933
  }
30361
30934
  function ghHostsConfigPath(env, platform2) {
30362
30935
  const sep3 = platform2 === "win32" ? "\\" : "/";
30363
- const join34 = (...parts) => parts.join(sep3);
30936
+ const join33 = (...parts) => parts.join(sep3);
30364
30937
  const explicit = env.GH_CONFIG_DIR?.trim();
30365
- if (explicit) return join34(explicit, "hosts.yml");
30938
+ if (explicit) return join33(explicit, "hosts.yml");
30366
30939
  if (platform2 === "win32") {
30367
30940
  const appData = (env.AppData ?? env.APPDATA)?.trim();
30368
- return appData ? join34(appData, "GitHub CLI", "hosts.yml") : void 0;
30941
+ return appData ? join33(appData, "GitHub CLI", "hosts.yml") : void 0;
30369
30942
  }
30370
30943
  const xdg = env.XDG_CONFIG_HOME?.trim();
30371
- if (xdg) return join34(xdg, "gh", "hosts.yml");
30944
+ if (xdg) return join33(xdg, "gh", "hosts.yml");
30372
30945
  const home = env.HOME?.trim();
30373
- return home ? join34(home, ".config", "gh", "hosts.yml") : void 0;
30946
+ return home ? join33(home, ".config", "gh", "hosts.yml") : void 0;
30374
30947
  }
30375
30948
  function parseGhHostsAccounts(yaml, host = "github.com") {
30376
30949
  let hostIndent = null;
@@ -30422,7 +30995,7 @@ function ghAccountCaveat(announcedLogin, accounts) {
30422
30995
  // src/doctor-io.ts
30423
30996
  var import_node_fs38 = require("node:fs");
30424
30997
  var import_node_os14 = require("node:os");
30425
- var import_node_path37 = require("node:path");
30998
+ var import_node_path36 = require("node:path");
30426
30999
  var import_node_child_process17 = require("node:child_process");
30427
31000
  var import_node_util8 = require("node:util");
30428
31001
  var execFileP6 = (0, import_node_util8.promisify)(import_node_child_process17.execFile);
@@ -30430,7 +31003,7 @@ var MMI_PLUGIN_ID2 = "mmi@mutmutco";
30430
31003
  function installedClaudePluginVersion() {
30431
31004
  try {
30432
31005
  const file = JSON.parse(
30433
- (0, import_node_fs38.readFileSync)((0, import_node_path37.join)((0, import_node_os14.homedir)(), ".claude", "plugins", "installed_plugins.json"), "utf8")
31006
+ (0, import_node_fs38.readFileSync)((0, import_node_path36.join)((0, import_node_os14.homedir)(), ".claude", "plugins", "installed_plugins.json"), "utf8")
30434
31007
  );
30435
31008
  const versions = (file.plugins?.[MMI_PLUGIN_ID2] ?? []).map((r) => r.version).filter((v) => Boolean(v));
30436
31009
  if (versions.length === 0) return void 0;
@@ -30451,17 +31024,22 @@ function installedSurfacePluginVersion(surface) {
30451
31024
  const token = surfaceToken(surface);
30452
31025
  if (token === "kilo") {
30453
31026
  try {
30454
- const stamp = (0, import_node_fs38.readFileSync)((0, import_node_path37.join)((0, import_node_os14.homedir)(), ".kilo", ".mmi-kilo-version"), "utf8").trim();
31027
+ const stamp = (0, import_node_fs38.readFileSync)((0, import_node_path36.join)((0, import_node_os14.homedir)(), ".kilo", ".mmi-kilo-version"), "utf8").trim();
30455
31028
  return stamp || void 0;
30456
31029
  } catch {
30457
31030
  return void 0;
30458
31031
  }
30459
31032
  }
30460
31033
  if (token === "cursor") {
30461
- return manifestVersion((0, import_node_path37.join)(cursorLocalPluginRoot(), ".cursor-plugin", "plugin.json"));
31034
+ return manifestVersion((0, import_node_path36.join)(cursorLocalPluginRoot(), ".cursor-plugin", "plugin.json"));
31035
+ }
31036
+ if (token === "jervcode") {
31037
+ const entry = mmiPiWrapperEntry();
31038
+ if (!entry) return void 0;
31039
+ return manifestVersion((0, import_node_path36.join)(decodeURIComponent(entry.replace(/^file:\/\/\/?/, "")), "package.json"));
30462
31040
  }
30463
31041
  if (token === "kimi") {
30464
- return manifestVersion((0, import_node_path37.join)(surfaceConfigRoot(surface), "plugins", "managed", "mmi", ".kimi-plugin", "plugin.json"));
31042
+ return manifestVersion((0, import_node_path36.join)(surfaceConfigRoot(surface), "plugins", "managed", "mmi", ".kimi-plugin", "plugin.json"));
30465
31043
  }
30466
31044
  if (token === "claude") return installedClaudePluginVersion();
30467
31045
  if (token !== "codex") return void 0;
@@ -30499,7 +31077,7 @@ function worktreeRootSync() {
30499
31077
  }
30500
31078
  var gitignorePath = () => {
30501
31079
  const root = worktreeRootSync();
30502
- return root === null ? null : (0, import_node_path37.join)(root, ".gitignore");
31080
+ return root === null ? null : (0, import_node_path36.join)(root, ".gitignore");
30503
31081
  };
30504
31082
  function readGitignore() {
30505
31083
  const path2 = gitignorePath();
@@ -30538,7 +31116,7 @@ async function repoRoot() {
30538
31116
  }
30539
31117
  function hasRepoLocalWorktrees() {
30540
31118
  const root = worktreeRootSync();
30541
- return root !== null && (0, import_node_fs38.existsSync)((0, import_node_path37.join)(root, ".worktrees"));
31119
+ return root !== null && (0, import_node_fs38.existsSync)((0, import_node_path36.join)(root, ".worktrees"));
30542
31120
  }
30543
31121
 
30544
31122
  // src/index.ts
@@ -30583,7 +31161,7 @@ function ghMultiAccountCaveat(announcedLogin) {
30583
31161
  var ENV_HEAL_LOCK_STALE_MS = 10 * 6e4;
30584
31162
  var ENV_HEAL_LOCK_MAX_WAIT_MS = 2 * 6e4;
30585
31163
  function envHealLockPath(home) {
30586
- return (0, import_node_path38.join)(home, ".claude", "plugins", ".mmi-env-heal.lock");
31164
+ return (0, import_node_path37.join)(home, ".claude", "plugins", ".mmi-env-heal.lock");
30587
31165
  }
30588
31166
  async function withEnvHealLock(what, run) {
30589
31167
  try {
@@ -30709,14 +31287,14 @@ function mmiDoctorDeps(opts = {}) {
30709
31287
  const home = (0, import_node_os15.homedir)();
30710
31288
  const rows = marketplaceRows(
30711
31289
  MMI_MARKETPLACE_NAME,
30712
- readFileSyncSafe((0, import_node_path38.join)(home, ...KNOWN_MARKETPLACES_RELATIVE), import_node_fs39.readFileSync),
30713
- readFileSyncSafe((0, import_node_path38.join)(home, ".claude", "settings.json"), import_node_fs39.readFileSync),
31290
+ readFileSyncSafe((0, import_node_path37.join)(home, ...KNOWN_MARKETPLACES_RELATIVE), import_node_fs39.readFileSync),
31291
+ readFileSyncSafe((0, import_node_path37.join)(home, ".claude", "settings.json"), import_node_fs39.readFileSync),
30714
31292
  // #3974: this CLI heals these rows, so report mode names the doctor run rather than the hand
30715
31293
  // edit. Unconditional ? it is a fact about mmi-cli, not about the lane this run is on.
30716
31294
  true
30717
31295
  );
30718
31296
  const pending = readMarketplacePinPending(
30719
- (0, import_node_path38.join)(home, ".claude", "plugins", ".mmi-marketplace-pin-pending.json"),
31297
+ (0, import_node_path37.join)(home, ".claude", "plugins", ".mmi-marketplace-pin-pending.json"),
30720
31298
  MMI_MARKETPLACE_NAME
30721
31299
  );
30722
31300
  if (!pending) return rows;
@@ -30742,9 +31320,9 @@ function mmiDoctorDeps(opts = {}) {
30742
31320
  if (detectSurface(process.env) === "codex") return void 0;
30743
31321
  const home = (0, import_node_os15.homedir)();
30744
31322
  const names = [MMI_MARKETPLACE_NAME];
30745
- const result = applyOrgMarketplacePins((0, import_node_path38.join)(home, ...KNOWN_MARKETPLACES_RELATIVE), names);
31323
+ const result = applyOrgMarketplacePins((0, import_node_path37.join)(home, ...KNOWN_MARKETPLACES_RELATIVE), names);
30746
31324
  if (result?.wrote) {
30747
- writeMarketplacePinPending((0, import_node_path38.join)(home, ".claude", "plugins", ".mmi-marketplace-pin-pending.json"), names);
31325
+ writeMarketplacePinPending((0, import_node_path37.join)(home, ".claude", "plugins", ".mmi-marketplace-pin-pending.json"), names);
30748
31326
  }
30749
31327
  return result;
30750
31328
  } catch {
@@ -30760,13 +31338,91 @@ function mmiDoctorDeps(opts = {}) {
30760
31338
  // adopted the generated routing index (MMG-Unlive: `docs/Archive/**` only, no index, no gate.yml) would
30761
31339
  // get a permanent ? demanding an artifact it never asked for.
30762
31340
  docsIndexState: (root) => {
30763
- if (!(0, import_node_fs39.existsSync)((0, import_node_path38.join)(root, DOCS_INDEX_PATH))) return void 0;
31341
+ if (!(0, import_node_fs39.existsSync)((0, import_node_path37.join)(root, DOCS_INDEX_PATH))) return void 0;
31342
+ const real = createDocsIndexDeps(root);
31343
+ let docs2;
31344
+ const listDocs = () => docs2 ??= real.listDocs();
31345
+ const drift = docsIndex({ ...real, listDocs }, { check: true }).drift;
31346
+ return { drift, docCount: listDocs().length };
31347
+ },
31348
+ // #4168: working-tree heal — write if drifted, then re-check. Commit remains the operator's step.
31349
+ healDocsIndex: (root) => {
31350
+ if (!(0, import_node_fs39.existsSync)((0, import_node_path37.join)(root, DOCS_INDEX_PATH))) return { drift: false, docCount: 0 };
30764
31351
  const real = createDocsIndexDeps(root);
30765
31352
  let docs2;
30766
31353
  const listDocs = () => docs2 ??= real.listDocs();
31354
+ docsIndex({ ...real, listDocs }, { check: false });
30767
31355
  const drift = docsIndex({ ...real, listDocs }, { check: true }).drift;
30768
31356
  return { drift, docCount: listDocs().length };
30769
31357
  },
31358
+ // #4169: mirror `worktree gc --apply` — sweep deferred removals before applyGcPlan.
31359
+ sweepDeferred: async () => {
31360
+ const deferredStore = await createDeferredWorktreeStore();
31361
+ const removalContext = await currentWorktreeRemovalContext("doctor deferred sweep");
31362
+ const result = await sweepDeferredWorktrees(
31363
+ deferredStore,
31364
+ // #2841: same `-c core.fsmonitor=false` guard as `worktree gc --apply` / sweep-deferred.
31365
+ worktreeRemoveDeps(async (args) => (await execFileP2("git", ["-c", "core.fsmonitor=false", ...args], { timeout: GIT_TIMEOUT_MS })).stdout),
31366
+ removalContext
31367
+ );
31368
+ return {
31369
+ removed: result.removed,
31370
+ stillQueued: result.stillDeferred.length,
31371
+ skipped: result.skipped.length
31372
+ };
31373
+ },
31374
+ // #4170: light board doctor with 15s ceiling. skipClaimLiveness keeps Phase 3 off.
31375
+ boardDoctorFix: async ({ fix } = {}) => {
31376
+ const BOARD_DOCTOR_TIMEOUT_MS = 15e3;
31377
+ try {
31378
+ const cfg = await loadConfigForRepo();
31379
+ const work = boardDoctor({
31380
+ config: cfg,
31381
+ fix: Boolean(fix),
31382
+ skipClaimLiveness: true
31383
+ });
31384
+ const raced = await Promise.race([
31385
+ work.then((r) => ({ ...r, timedOut: false })),
31386
+ new Promise((resolve5) => {
31387
+ setTimeout(() => resolve5({ timedOut: true, scanned: 0, findings: 0, fixed: 0, failed: 0 }), BOARD_DOCTOR_TIMEOUT_MS);
31388
+ })
31389
+ ]);
31390
+ if (raced.timedOut) return { scanned: 0, findings: 0, fixed: 0, failed: 0, timedOut: true };
31391
+ return {
31392
+ scanned: raced.scanned,
31393
+ findings: raced.findings,
31394
+ fixed: raced.fixed,
31395
+ failed: raced.failed
31396
+ };
31397
+ } catch {
31398
+ return void 0;
31399
+ }
31400
+ },
31401
+ // #4171: schedules drift notebook — report-only, ~20s ceiling.
31402
+ schedulesDriftState: async () => {
31403
+ const SCHEDULES_DRIFT_TIMEOUT_MS = 2e4;
31404
+ try {
31405
+ const raced = await Promise.race([
31406
+ fetchNotebook().then((nb) => ({
31407
+ driftLines: nb.drift,
31408
+ incomplete: nb.incomplete,
31409
+ timedOut: false
31410
+ })),
31411
+ new Promise((resolve5) => {
31412
+ setTimeout(() => resolve5({ driftLines: [], incomplete: [], timedOut: true }), SCHEDULES_DRIFT_TIMEOUT_MS);
31413
+ })
31414
+ ]);
31415
+ return raced;
31416
+ } catch {
31417
+ return void 0;
31418
+ }
31419
+ },
31420
+ // #4173: estate commands absent from the running Commander program (stale global CLI).
31421
+ missingCliCommands: () => {
31422
+ const required = ["repo-index"];
31423
+ const names = new Set((opts.program?.commands ?? []).map((c) => c.name()));
31424
+ return required.filter((n) => !names.has(n));
31425
+ },
30770
31426
  // #4156: short-timeout Hub status for the current repo. Fail-soft ? never throws to doctor.
30771
31427
  repoIndexCloudState: async (root) => {
30772
31428
  const local = repoIndexStatus(root);
@@ -31025,7 +31681,7 @@ program2.hook("preAction", async (_thisCommand, actionCommand) => {
31025
31681
  });
31026
31682
  var rules = program2.command("rules").description("org-managed .gitignore delivery");
31027
31683
  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) => {
31028
- const path2 = (0, import_node_path38.join)(process.cwd(), ".gitignore");
31684
+ const path2 = (0, import_node_path37.join)(process.cwd(), ".gitignore");
31029
31685
  const current = (0, import_node_fs39.existsSync)(path2) ? (0, import_node_fs39.readFileSync)(path2, "utf8") : null;
31030
31686
  const plan = planManagedGitignore(current);
31031
31687
  const drift = [...plan.added.map((l) => `+${l}`), ...plan.removed.map((l) => `-${l}`)].join(", ") || "block normalize";
@@ -31194,7 +31850,7 @@ gcCmd.option("--dry-run", "show what would be deleted (default)").option("--appl
31194
31850
  if (!Number.isFinite(limit) || limit < 1) return fail("worktree gc: --limit must be a positive integer");
31195
31851
  let root;
31196
31852
  if (o.root !== void 0) {
31197
- root = (0, import_node_path38.resolve)(o.root);
31853
+ root = (0, import_node_path37.resolve)(o.root);
31198
31854
  if (!(0, import_node_fs39.existsSync)(root) || !(0, import_node_fs39.statSync)(root).isDirectory()) return fail(`worktree gc: --root ${o.root} is not a directory`);
31199
31855
  const gcRepoRoot = (await execFileP2("git", ["rev-parse", "--show-toplevel"], { timeout: GIT_TIMEOUT_MS }).catch(() => ({ stdout: "" }))).stdout.trim() || process.cwd();
31200
31856
  if (isPathUnderDirectory(gcRepoRoot, root)) {
@@ -31310,7 +31966,7 @@ function acquireWorktreeSetupLock(worktreeRoot) {
31310
31966
  };
31311
31967
  };
31312
31968
  try {
31313
- (0, import_node_fs39.mkdirSync)((0, import_node_path38.dirname)(lockPath), { recursive: true });
31969
+ (0, import_node_fs39.mkdirSync)((0, import_node_path37.dirname)(lockPath), { recursive: true });
31314
31970
  return take();
31315
31971
  } catch {
31316
31972
  try {
@@ -31622,7 +32278,7 @@ repoIndex.command("publish").description("publish this checkout's projection to
31622
32278
  return await failGraceful(e.message);
31623
32279
  }
31624
32280
  });
31625
- 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) => {
32281
+ async function runRepoIndexSearchCommand(query, o, defaultMode) {
31626
32282
  try {
31627
32283
  const limit = Math.max(1, Math.min(100, Number(o.limit ?? 20) || 20));
31628
32284
  const useLocal = o.local === true && o.cloud !== true;
@@ -31645,7 +32301,7 @@ repoIndex.command("search").description("search Hub cloud by default (lexical/hy
31645
32301
  }
31646
32302
  return;
31647
32303
  }
31648
- const mode = o.semantic ? "semantic" : o.lexical ? "lexical" : "hybrid";
32304
+ const mode = o.semantic ? "semantic" : o.lexical ? "lexical" : defaultMode;
31649
32305
  const cfg = await loadConfig();
31650
32306
  const res = await searchRepoIndexCloud(query, { mode, limit, repo: o.repo }, registryClientDeps(cfg));
31651
32307
  if (!res.ok) return await failGraceful(res.error);
@@ -31664,7 +32320,13 @@ repoIndex.command("search").description("search Hub cloud by default (lexical/hy
31664
32320
  } catch (e) {
31665
32321
  return await failGraceful(e.message);
31666
32322
  }
31667
- });
32323
+ }
32324
+ var REPO_INDEX_SEARCH_WHEN = 'beats grep for cross-repo questions, "where does X happen", and unknown filenames; grep wins inside a known checkout';
32325
+ function repoIndexSearchOptions(cmd) {
32326
+ 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");
32327
+ }
32328
+ 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"));
32329
+ 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"));
31668
32330
  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) => {
31669
32331
  try {
31670
32332
  if (o.cloud || o.repo) {
@@ -31716,11 +32378,10 @@ repoIndex.command("status").description("show local and/or cloud repo-index stat
31716
32378
  return await failGraceful(e.message);
31717
32379
  }
31718
32380
  });
31719
- 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) => {
32381
+ 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) => {
31720
32382
  try {
31721
- const root = await repoRoot();
31722
- const goldenPath = o.golden || defaultGoldenPath(root);
31723
- const golden = loadGoldenSuite(goldenPath);
32383
+ const goldenPath = o.golden || "bundled repo-index-golden-queries.json";
32384
+ const golden = o.golden ? loadGoldenSuite(o.golden) : defaultGoldenSuite();
31724
32385
  const result = o.live ? await runRepoIndexHealth({
31725
32386
  golden,
31726
32387
  live: {
@@ -32923,11 +33584,11 @@ pr.command("view <number>").description("read a PR as structured JSON (merged st
32923
33584
  }
32924
33585
  });
32925
33586
  async function listCiWorkflowPaths(cwd = process.cwd()) {
32926
- const wfDir = (0, import_node_path38.join)(cwd, ".github", "workflows");
33587
+ const wfDir = (0, import_node_path37.join)(cwd, ".github", "workflows");
32927
33588
  if (!(0, import_node_fs39.existsSync)(wfDir)) return [];
32928
33589
  return (0, import_node_fs39.readdirSync)(wfDir).filter((name) => /\.(ya?ml)$/i.test(name)).filter((name) => {
32929
33590
  try {
32930
- return workflowReportsPrChecks((0, import_node_fs39.readFileSync)((0, import_node_path38.join)(wfDir, name), "utf8"));
33591
+ return workflowReportsPrChecks((0, import_node_fs39.readFileSync)((0, import_node_path37.join)(wfDir, name), "utf8"));
32931
33592
  } catch {
32932
33593
  return true;
32933
33594
  }
@@ -32959,16 +33620,16 @@ function ciAuditDeps() {
32959
33620
  // gate re-seed step is skipped gracefully rather than failing mid-run.
32960
33621
  readSeedFile: (path2) => {
32961
33622
  if (!root) return null;
32962
- const fullPath = (0, import_node_path38.join)(root, path2);
33623
+ const fullPath = (0, import_node_path37.join)(root, path2);
32963
33624
  return (0, import_node_fs39.existsSync)(fullPath) ? (0, import_node_fs39.readFileSync)(fullPath, "utf8") : null;
32964
33625
  }
32965
33626
  };
32966
33627
  }
32967
33628
  function hubRoot() {
32968
- const fromPkg = (0, import_node_path38.join)(__dirname, "..", "..");
33629
+ const fromPkg = (0, import_node_path37.join)(__dirname, "..", "..");
32969
33630
  const marker = "skills/bootstrap/seeds/manifest.json";
32970
- if ((0, import_node_fs39.existsSync)((0, import_node_path38.join)(fromPkg, marker))) return fromPkg;
32971
- if ((0, import_node_fs39.existsSync)((0, import_node_path38.join)(process.cwd(), marker))) return process.cwd();
33631
+ if ((0, import_node_fs39.existsSync)((0, import_node_path37.join)(fromPkg, marker))) return fromPkg;
33632
+ if ((0, import_node_fs39.existsSync)((0, import_node_path37.join)(process.cwd(), marker))) return process.cwd();
32972
33633
  return null;
32973
33634
  }
32974
33635
  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) => {
@@ -33801,7 +34462,7 @@ program2.command("doctor").description("heal CLI/plugin wiring and clean up repo
33801
34462
  verbose: opts.verbose
33802
34463
  },
33803
34464
  consoleIo,
33804
- mmiDoctorDeps()
34465
+ mmiDoctorDeps({ program: program2 })
33805
34466
  );
33806
34467
  });
33807
34468
  program2.command("guard").description("detect a pruned/unresolved MMI plugin on disk").action(() => runGuard());
@@ -33815,7 +34476,7 @@ function directoryBytes(path2) {
33815
34476
  return 0;
33816
34477
  }
33817
34478
  for (const entry of entries) {
33818
- const child2 = (0, import_node_path38.join)(path2, entry.name);
34479
+ const child2 = (0, import_node_path37.join)(path2, entry.name);
33819
34480
  if (entry.isDirectory()) total += directoryBytes(child2);
33820
34481
  else {
33821
34482
  try {
@@ -33845,7 +34506,7 @@ function pluginCacheFsDeps(configRoot, dirBytes) {
33845
34506
  dirBytes,
33846
34507
  listStagingDirs: (root) => (0, import_node_fs39.readdirSync)(root, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => {
33847
34508
  try {
33848
- return { name: d.name, mtimeMs: newestMtimeMs((0, import_node_path38.join)(root, d.name), listDirEntries, (p) => (0, import_node_fs39.statSync)(p).mtimeMs) };
34509
+ return { name: d.name, mtimeMs: newestMtimeMs((0, import_node_path37.join)(root, d.name), listDirEntries, (p) => (0, import_node_fs39.statSync)(p).mtimeMs) };
33849
34510
  } catch {
33850
34511
  return { name: d.name, mtimeMs: Date.now() };
33851
34512
  }
@@ -33859,7 +34520,7 @@ function stagingApplyFsGuard(configRoot) {
33859
34520
  return {
33860
34521
  referencedPaths: () => readInstalledPluginRefs(configRoot),
33861
34522
  mtimeMs: (name) => {
33862
- const p = (0, import_node_path38.join)(stagingRoot, name);
34523
+ const p = (0, import_node_path37.join)(stagingRoot, name);
33863
34524
  if (!(0, import_node_fs39.existsSync)(p)) return null;
33864
34525
  try {
33865
34526
  return newestMtimeMs(p, listDirEntries, (q) => (0, import_node_fs39.statSync)(q).mtimeMs);
@@ -33944,7 +34605,7 @@ program2.command("session-start").description("run the SessionStart verbs (whoam
33944
34605
  // #3485 item 7: the ONLY lane that throttles the npm read ? it runs on every session start, on a
33945
34606
  // blocking hook. Every interactive lane still reads live.
33946
34607
  doctor: async (io) => {
33947
- await runDoctorClean({ banner: true }, io, mmiDoctorDeps({ throttleReleasedRead: true }));
34608
+ await runDoctorClean({ banner: true }, io, mmiDoctorDeps({ throttleReleasedRead: true, program: program2 }));
33948
34609
  }
33949
34610
  });
33950
34611
  await runSessionStart(parallel, sequential, bannerIo);