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