@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.
- package/dist/main.cjs +807 -146
- 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
|
|
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
|
|
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) ||
|
|
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(
|
|
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("/"))
|
|
19722
|
-
|
|
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:
|
|
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
|
-
|
|
19817
|
-
|
|
19818
|
-
|
|
19819
|
-
|
|
19820
|
-
|
|
19821
|
-
|
|
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 ${
|
|
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
|
|
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,
|
|
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
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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
|
|
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,
|
|
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,
|
|
20580
|
-
(0, import_node_fs27.existsSync)((0,
|
|
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
|
|
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,
|
|
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,
|
|
21568
|
-
const masterKeyPath = (0,
|
|
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,
|
|
21586
|
-
const scriptPath = (0,
|
|
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,
|
|
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,
|
|
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
|
|
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,
|
|
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
|
|
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,
|
|
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
|
|
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,
|
|
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
|
|
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
|
|
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,
|
|
24787
|
-
const ddbScriptPath = (0,
|
|
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,
|
|
24795
|
-
const sibling = portRangeInfraAt((0,
|
|
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,
|
|
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,
|
|
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,
|
|
24996
|
-
hasEnvExample: (0, import_node_fs32.existsSync)((0,
|
|
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
|
|
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
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
25874
|
-
const path2 = (0,
|
|
26110
|
+
const dir = (0, import_node_fs33.mkdtempSync)((0, import_node_path32.join)((0, import_node_os12.tmpdir)(), "mmi-squash-body-"));
|
|
26111
|
+
const path2 = (0, import_node_path32.join)(dir, "body.txt");
|
|
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
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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
|
|
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,
|
|
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
|
|
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 (!
|
|
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,
|
|
27962
|
-
readSettings: () => readFileSyncSafe((0,
|
|
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
|
|
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
|
-
|
|
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)
|
|
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
|
-
|
|
30102
|
-
const
|
|
30103
|
-
|
|
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.
|
|
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 ${
|
|
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(
|
|
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
|
-
|
|
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)
|
|
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
|
|
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
|
-
|
|
30301
|
-
|
|
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
|
-
|
|
30306
|
-
|
|
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
|
|
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
|
|
30936
|
+
const join33 = (...parts) => parts.join(sep3);
|
|
30364
30937
|
const explicit = env.GH_CONFIG_DIR?.trim();
|
|
30365
|
-
if (explicit) return
|
|
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 ?
|
|
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
|
|
30944
|
+
if (xdg) return join33(xdg, "gh", "hosts.yml");
|
|
30372
30945
|
const home = env.HOME?.trim();
|
|
30373
|
-
return home ?
|
|
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
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
30713
|
-
readFileSyncSafe((0,
|
|
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,
|
|
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,
|
|
31323
|
+
const result = applyOrgMarketplacePins((0, import_node_path37.join)(home, ...KNOWN_MARKETPLACES_RELATIVE), names);
|
|
30746
31324
|
if (result?.wrote) {
|
|
30747
|
-
writeMarketplacePinPending((0,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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
|
-
|
|
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" :
|
|
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:
|
|
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
|
|
31722
|
-
const
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
32971
|
-
if ((0, import_node_fs39.existsSync)((0,
|
|
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,
|
|
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,
|
|
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,
|
|
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);
|