@mutmutco/cli 2.49.0 → 2.50.1
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 +376 -86
- package/package.json +1 -1
package/dist/main.cjs
CHANGED
|
@@ -4582,18 +4582,32 @@ async function ffOnlyPull(deps, branch) {
|
|
|
4582
4582
|
await deps.run("git", ["pull", "--ff-only", "origin", branch]);
|
|
4583
4583
|
}
|
|
4584
4584
|
var RELEASE_TOLERATED_PATHS = [".gitignore"];
|
|
4585
|
-
|
|
4586
|
-
|
|
4587
|
-
|
|
4588
|
-
|
|
4589
|
-
|
|
4590
|
-
|
|
4591
|
-
|
|
4592
|
-
|
|
4593
|
-
|
|
4585
|
+
var MERGE_TREE_PREFLIGHT_ATTEMPTS = 3;
|
|
4586
|
+
var MERGE_TREE_PREFLIGHT_DELAY_MS = 250;
|
|
4587
|
+
function mergeTreeConflictFiles(stdout) {
|
|
4588
|
+
return stdout.split("\n").map((s) => s.trim()).filter(Boolean).slice(1);
|
|
4589
|
+
}
|
|
4590
|
+
async function runMergeTreePreflight(deps, ours, theirs) {
|
|
4591
|
+
const sleep = resolveSleep(deps);
|
|
4592
|
+
let lastError;
|
|
4593
|
+
for (let attempt = 0; attempt < MERGE_TREE_PREFLIGHT_ATTEMPTS; attempt++) {
|
|
4594
|
+
try {
|
|
4595
|
+
await deps.run("git", ["merge-tree", "--write-tree", "--name-only", "--no-messages", ours, theirs]);
|
|
4596
|
+
return [];
|
|
4597
|
+
} catch (e) {
|
|
4598
|
+
lastError = e;
|
|
4599
|
+
const files = mergeTreeConflictFiles(String(e.stdout ?? ""));
|
|
4600
|
+
if (files.length > 0) return files;
|
|
4601
|
+
if (attempt + 1 < MERGE_TREE_PREFLIGHT_ATTEMPTS) await sleep(MERGE_TREE_PREFLIGHT_DELAY_MS);
|
|
4594
4602
|
}
|
|
4595
|
-
return files;
|
|
4596
4603
|
}
|
|
4604
|
+
const msg = lastError?.message ?? String(lastError);
|
|
4605
|
+
throw new Error(
|
|
4606
|
+
`could not preflight the ${theirs} -> ${ours} merge (git merge-tree failed after ${MERGE_TREE_PREFLIGHT_ATTEMPTS} attempts: ${msg})`
|
|
4607
|
+
);
|
|
4608
|
+
}
|
|
4609
|
+
async function predictMergeConflicts(deps, ours, theirs) {
|
|
4610
|
+
return runMergeTreePreflight(deps, ours, theirs);
|
|
4597
4611
|
}
|
|
4598
4612
|
async function mergeWithSpineResolution(deps, sourceRef, label, resolve5, extraTolerated = []) {
|
|
4599
4613
|
try {
|
|
@@ -17660,6 +17674,7 @@ async function applyCursorPluginCacheSeed(input) {
|
|
|
17660
17674
|
|
|
17661
17675
|
// src/doctor.ts
|
|
17662
17676
|
var GH_PROJECT_LOGIN_FIX = 'run: gh auth login --hostname github.com --git-protocol https --web --scopes "project"';
|
|
17677
|
+
var AWS_CROSS_ACCOUNT_LABEL = "AWS cross-account identity (master-agent audits)";
|
|
17663
17678
|
var AWS_CROSS_ACCOUNT_FIX = "use a non-root IAM user/session profile for master-agent AWS checks; set AWS_PROFILE or AWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEY (plus AWS_SESSION_TOKEN for temporary credentials), then verify `aws sts get-caller-identity` does not end in :root";
|
|
17664
17679
|
var MMI_AGENTIC_ONBOARDING_GUIDE = {
|
|
17665
17680
|
label: "MMI Agentic Onboarding",
|
|
@@ -17680,7 +17695,7 @@ function buildAwsCrossAccountCheck(input) {
|
|
|
17680
17695
|
const callerArn = input.callerArn?.trim();
|
|
17681
17696
|
return {
|
|
17682
17697
|
ok: !callerArn || !callerArn.endsWith(":root"),
|
|
17683
|
-
label:
|
|
17698
|
+
label: AWS_CROSS_ACCOUNT_LABEL,
|
|
17684
17699
|
fix: AWS_CROSS_ACCOUNT_FIX
|
|
17685
17700
|
};
|
|
17686
17701
|
}
|
|
@@ -18002,7 +18017,7 @@ function detectSurface(env) {
|
|
|
18002
18017
|
if (env.MMI_AGENT_SURFACE === "cursor" || has("CURSOR_TRACE_ID") || has("CURSOR_USER") || has("CURSOR_SESSION_ID") || env.CURSOR_AGENT === "1" || has("CURSOR_EXTENSION_HOST_ROLE")) {
|
|
18003
18018
|
return "cursor";
|
|
18004
18019
|
}
|
|
18005
|
-
if (env.MMI_AGENT_SURFACE === "opencode") return "opencode";
|
|
18020
|
+
if (env.MMI_AGENT_SURFACE === "opencode" || has("OPENCODE_CLIENT") || has("OPENCODE_SERVER_USERNAME")) return "opencode";
|
|
18006
18021
|
const isClaude = has("CLAUDECODE") || has("CLAUDE_CODE_ENTRYPOINT") || has("CLAUDE_PLUGIN_ROOT") || env.MMI_AGENT_SURFACE === "claude";
|
|
18007
18022
|
const isVscode = env.TERM_PROGRAM === "vscode" || has("VSCODE_PID") || has("VSCODE_GIT_ASKPASS_NODE");
|
|
18008
18023
|
if (isClaude && isVscode) return "claude-vscode";
|
|
@@ -18027,7 +18042,10 @@ function reloadAction(surface) {
|
|
|
18027
18042
|
}
|
|
18028
18043
|
var CLAUDE_RECOVERY = `claude plugin marketplace remove ${LEGACY_MMI_MARKETPLACE} && claude plugin marketplace remove mutmutco && claude plugin marketplace add mutmutco/MMI-Hub && claude plugin install mmi@mutmutco`;
|
|
18029
18044
|
var CODEX_RECOVERY = `codex plugin marketplace remove ${LEGACY_MMI_MARKETPLACE} && codex plugin marketplace remove mutmutco && codex plugin marketplace add mutmutco/MMI-Hub --ref main && codex plugin add mmi@mutmutco`;
|
|
18030
|
-
var
|
|
18045
|
+
var OPENCODE_PLUGIN_PACKAGE = "@mutmutco/opencode-mmi";
|
|
18046
|
+
var OPENCODE_PLUGIN_SPEC = `${OPENCODE_PLUGIN_PACKAGE}@latest`;
|
|
18047
|
+
var OPENCODE_PLUGIN_INSTALL_COMMAND = `opencode plugin ${OPENCODE_PLUGIN_SPEC} --global --force`;
|
|
18048
|
+
var OPENCODE_RECOVERY = `${OPENCODE_PLUGIN_INSTALL_COMMAND} # then restart OpenCode to load MMI commands`;
|
|
18031
18049
|
var PLUGIN_SURFACE_HEAL = {
|
|
18032
18050
|
claude: {
|
|
18033
18051
|
delivery: "plugin-cli",
|
|
@@ -18067,8 +18085,8 @@ var PLUGIN_SURFACE_HEAL = {
|
|
|
18067
18085
|
delivery: "npm",
|
|
18068
18086
|
recovery: OPENCODE_RECOVERY,
|
|
18069
18087
|
healSteps: null,
|
|
18070
|
-
fix: (
|
|
18071
|
-
updateRecipe: [OPENCODE_RECOVERY, "
|
|
18088
|
+
fix: () => OPENCODE_RECOVERY,
|
|
18089
|
+
updateRecipe: [OPENCODE_RECOVERY, "restart OpenCode, then run mmi-cli doctor to verify OpenCode plugin reports the released version"]
|
|
18072
18090
|
}
|
|
18073
18091
|
};
|
|
18074
18092
|
var CLAUDE_PLUGIN_RECOVERY = PLUGIN_SURFACE_HEAL.claude.recovery;
|
|
@@ -18220,6 +18238,94 @@ function buildOpencodeVersionCheck(input) {
|
|
|
18220
18238
|
}
|
|
18221
18239
|
return { ...base, ok: false, installedVersion: input.installedVersion, releasedVersion: input.releasedVersion };
|
|
18222
18240
|
}
|
|
18241
|
+
var OPENCODE_CONFIG_PLUGIN_LABEL = "OpenCode MMI adapter config wiring";
|
|
18242
|
+
function opencodePluginEntryMatches(entry) {
|
|
18243
|
+
return entry === OPENCODE_PLUGIN_PACKAGE || entry === OPENCODE_PLUGIN_SPEC || Array.isArray(entry) && (entry[0] === OPENCODE_PLUGIN_PACKAGE || entry[0] === OPENCODE_PLUGIN_SPEC);
|
|
18244
|
+
}
|
|
18245
|
+
function stripJsonc(text) {
|
|
18246
|
+
let out = "";
|
|
18247
|
+
let inString = false;
|
|
18248
|
+
let quote = "";
|
|
18249
|
+
let escaped = false;
|
|
18250
|
+
for (let i = 0; i < text.length; i += 1) {
|
|
18251
|
+
const ch = text[i];
|
|
18252
|
+
const next = text[i + 1];
|
|
18253
|
+
if (inString) {
|
|
18254
|
+
out += ch;
|
|
18255
|
+
if (escaped) escaped = false;
|
|
18256
|
+
else if (ch === "\\") escaped = true;
|
|
18257
|
+
else if (ch === quote) inString = false;
|
|
18258
|
+
continue;
|
|
18259
|
+
}
|
|
18260
|
+
if (ch === '"' || ch === "'") {
|
|
18261
|
+
inString = true;
|
|
18262
|
+
quote = ch;
|
|
18263
|
+
out += ch;
|
|
18264
|
+
continue;
|
|
18265
|
+
}
|
|
18266
|
+
if (ch === "/" && next === "/") {
|
|
18267
|
+
while (i < text.length && text[i] !== "\n") i += 1;
|
|
18268
|
+
out += "\n";
|
|
18269
|
+
continue;
|
|
18270
|
+
}
|
|
18271
|
+
if (ch === "/" && next === "*") {
|
|
18272
|
+
i += 2;
|
|
18273
|
+
while (i < text.length && !(text[i] === "*" && text[i + 1] === "/")) i += 1;
|
|
18274
|
+
i += 1;
|
|
18275
|
+
out += " ";
|
|
18276
|
+
continue;
|
|
18277
|
+
}
|
|
18278
|
+
out += ch;
|
|
18279
|
+
}
|
|
18280
|
+
return out.replace(/,\s*([}\]])/g, "$1");
|
|
18281
|
+
}
|
|
18282
|
+
function planOpencodeConfigWrite(raw) {
|
|
18283
|
+
if (raw === void 0) {
|
|
18284
|
+
return {
|
|
18285
|
+
action: "create",
|
|
18286
|
+
text: `${JSON.stringify({ $schema: "https://opencode.ai/config.json", plugin: [OPENCODE_PLUGIN_SPEC] }, null, 2)}
|
|
18287
|
+
`
|
|
18288
|
+
};
|
|
18289
|
+
}
|
|
18290
|
+
let parsed;
|
|
18291
|
+
try {
|
|
18292
|
+
parsed = JSON.parse(stripJsonc(raw));
|
|
18293
|
+
} catch {
|
|
18294
|
+
return { action: "unsafe" };
|
|
18295
|
+
}
|
|
18296
|
+
const hasPluginField = Object.prototype.hasOwnProperty.call(parsed, "plugin");
|
|
18297
|
+
if (hasPluginField) {
|
|
18298
|
+
if (!Array.isArray(parsed.plugin)) return { action: "unsafe" };
|
|
18299
|
+
if (parsed.plugin.some(opencodePluginEntryMatches)) return { action: "already" };
|
|
18300
|
+
const next2 = raw.replace(/("plugin"\s*:\s*\[)([\s\S]*?)(\])/m, (_m, start, body, end) => {
|
|
18301
|
+
const prefix = body.trim() ? `${body.replace(/\s*$/, "")}, ` : "";
|
|
18302
|
+
return `${start}${prefix}"${OPENCODE_PLUGIN_SPEC}"${end}`;
|
|
18303
|
+
});
|
|
18304
|
+
if (next2 === raw) return { action: "unsafe" };
|
|
18305
|
+
return { action: "append", text: next2.endsWith("\n") ? next2 : `${next2}
|
|
18306
|
+
` };
|
|
18307
|
+
}
|
|
18308
|
+
const brace = raw.indexOf("{");
|
|
18309
|
+
if (brace < 0) return { action: "unsafe" };
|
|
18310
|
+
const insertAt = brace + 1;
|
|
18311
|
+
const next = `${raw.slice(0, insertAt)}
|
|
18312
|
+
"plugin": ["${OPENCODE_PLUGIN_SPEC}"],${raw.slice(insertAt)}`;
|
|
18313
|
+
return { action: "insert", text: next.endsWith("\n") ? next : `${next}
|
|
18314
|
+
` };
|
|
18315
|
+
}
|
|
18316
|
+
function buildOpencodeConfigPluginCheck(input) {
|
|
18317
|
+
const fix = OPENCODE_RECOVERY;
|
|
18318
|
+
const base = { ok: true, label: OPENCODE_CONFIG_PLUGIN_LABEL, fix };
|
|
18319
|
+
if (!input.isOrgRepo) return base;
|
|
18320
|
+
if (!input.hasConfig) return { ...base, ok: false, configPath: input.configPath, reason: "missing-config" };
|
|
18321
|
+
if (!input.parseOk) return { ...base, ok: false, configPath: input.configPath, reason: "unreadable-config" };
|
|
18322
|
+
if (!input.hasPluginField) return { ...base, ok: false, configPath: input.configPath, reason: "missing-plugin-entry" };
|
|
18323
|
+
if (!Array.isArray(input.pluginEntries)) return { ...base, ok: false, configPath: input.configPath, reason: "invalid-plugin-shape" };
|
|
18324
|
+
if (!input.pluginEntries.some(opencodePluginEntryMatches)) {
|
|
18325
|
+
return { ...base, ok: false, configPath: input.configPath, reason: "missing-plugin-entry" };
|
|
18326
|
+
}
|
|
18327
|
+
return { ...base, configPath: input.configPath };
|
|
18328
|
+
}
|
|
18223
18329
|
var OPENCODE_DESKTOP_BOOTSTRAP_LABEL = "OpenCode Desktop stale project bootstrap";
|
|
18224
18330
|
var OPENCODE_DESKTOP_BOOTSTRAP_FIX = "OpenCode Desktop is bootstrapping a deleted MMI worktree; open an existing checkout in OpenCode, remove/select away from the stale project entry, then restart OpenCode";
|
|
18225
18331
|
function decodeLogUrlDirectory(value) {
|
|
@@ -18243,6 +18349,38 @@ function buildOpencodeDesktopBootstrapCheck(input) {
|
|
|
18243
18349
|
const dirs = input.issues.map((i) => i.directory).join(", ");
|
|
18244
18350
|
return { ...base, ok: false, fix: `${OPENCODE_DESKTOP_BOOTSTRAP_FIX} (stale: ${dirs})`, issues: [...input.issues] };
|
|
18245
18351
|
}
|
|
18352
|
+
var OPENCODE_LEGACY_CONFIG_LABEL = "OpenCode legacy ~/.opencode config (stale plugin entries)";
|
|
18353
|
+
var OPENCODE_LEGACY_CONFIG_FIX = 'remove or rename ~/.opencode/opencode.json (legacy path); configure OpenCode at ~/.config/opencode/opencode.jsonc with "plugin": ["@mutmutco/opencode-mmi"] \u2014 see docs/Guides/opencode-saga.md';
|
|
18354
|
+
function parseOpencodeLegacyConfigPlugins(content) {
|
|
18355
|
+
try {
|
|
18356
|
+
const parsed = JSON.parse(content);
|
|
18357
|
+
if (!Array.isArray(parsed.plugin)) return null;
|
|
18358
|
+
return parsed.plugin.filter((entry) => typeof entry === "string");
|
|
18359
|
+
} catch {
|
|
18360
|
+
return null;
|
|
18361
|
+
}
|
|
18362
|
+
}
|
|
18363
|
+
function isStaleOpencodePluginEntry(entry) {
|
|
18364
|
+
const trimmed = entry.trim();
|
|
18365
|
+
if (!trimmed) return false;
|
|
18366
|
+
if (trimmed.startsWith("@") || trimmed.includes("/")) return false;
|
|
18367
|
+
return /^[a-z][a-z0-9-]*$/i.test(trimmed);
|
|
18368
|
+
}
|
|
18369
|
+
function buildOpencodeLegacyConfigCheck(input) {
|
|
18370
|
+
const base = {
|
|
18371
|
+
ok: true,
|
|
18372
|
+
label: OPENCODE_LEGACY_CONFIG_LABEL,
|
|
18373
|
+
fix: OPENCODE_LEGACY_CONFIG_FIX,
|
|
18374
|
+
legacyPath: input.legacyPath
|
|
18375
|
+
};
|
|
18376
|
+
if (!input.isOrgRepo || !input.stalePlugins?.length) return base;
|
|
18377
|
+
return {
|
|
18378
|
+
...base,
|
|
18379
|
+
ok: false,
|
|
18380
|
+
stalePlugins: [...input.stalePlugins],
|
|
18381
|
+
fix: `${OPENCODE_LEGACY_CONFIG_FIX} \u2014 stale entries: ${input.stalePlugins.join(", ")}`
|
|
18382
|
+
};
|
|
18383
|
+
}
|
|
18246
18384
|
var CURSOR_PLUGIN_INSTALL_LABEL = "Cursor Team Marketplace plugin install";
|
|
18247
18385
|
var CURSOR_MARKETPLACE_INSTALL_GUIDE = "https://github.com/mutmutco/MMI-Hub/blob/development/docs/Guides/cursor-marketplace-install.md";
|
|
18248
18386
|
var CURSOR_PLUGIN_JSON_REL = ".cursor-plugin/plugin.json";
|
|
@@ -18491,6 +18629,35 @@ function doctorPreflightDoneLine(surface) {
|
|
|
18491
18629
|
function pluginAutonomousHaltLine(reloadHint) {
|
|
18492
18630
|
return `\u26A0 PLUGIN RELOAD REQUIRED \u2014 mmi:* skills and agent types are unavailable until you ${reloadHint}. Halt autonomous /grind and /build until then.`;
|
|
18493
18631
|
}
|
|
18632
|
+
function renderPluginUpdateReportStaleOnly(report) {
|
|
18633
|
+
const v = report.versions;
|
|
18634
|
+
const released = v.released;
|
|
18635
|
+
if (!released) return [];
|
|
18636
|
+
const isStale = (current) => Boolean(current && isSemverVersion2(current) && compareVersions(current, released) < 0);
|
|
18637
|
+
const recipeLines = [];
|
|
18638
|
+
if (isStale(v.cli)) recipeLines.push(` npm CLI: ${report.recipes.cli.join(" ; ")}`);
|
|
18639
|
+
if (isStale(v.claudePlugin)) recipeLines.push(` Claude: ${report.recipes.claude.join(" ; ")}`);
|
|
18640
|
+
if (isStale(v.codexMarketplace) || isStale(v.codexActiveCache)) {
|
|
18641
|
+
recipeLines.push(` Codex: ${report.recipes.codex.join(" ; ")}`);
|
|
18642
|
+
}
|
|
18643
|
+
if (isStale(v.opencodePlugin)) recipeLines.push(` OpenCode: ${report.recipes.opencode.join(" ; ")}`);
|
|
18644
|
+
if (!recipeLines.length) return [];
|
|
18645
|
+
return ["Update recipes (stale surfaces):", ...recipeLines];
|
|
18646
|
+
}
|
|
18647
|
+
function renderTerseDoctorReport(input) {
|
|
18648
|
+
if (!input.gaps.length) return [];
|
|
18649
|
+
const lines = [];
|
|
18650
|
+
for (const c of input.gaps) {
|
|
18651
|
+
lines.push(`\u2717 ${c.label}`);
|
|
18652
|
+
lines.push(` \u2192 ${c.fix}`);
|
|
18653
|
+
}
|
|
18654
|
+
const stale = renderPluginUpdateReportStaleOnly(input.updateReport);
|
|
18655
|
+
if (stale.length) {
|
|
18656
|
+
lines.push("");
|
|
18657
|
+
lines.push(...stale);
|
|
18658
|
+
}
|
|
18659
|
+
return lines;
|
|
18660
|
+
}
|
|
18494
18661
|
|
|
18495
18662
|
// src/kb-drift-report.ts
|
|
18496
18663
|
var import_node_fs23 = require("node:fs");
|
|
@@ -18917,6 +19084,42 @@ function backupAndWriteInstalledPlugins(records, pluginId) {
|
|
|
18917
19084
|
return false;
|
|
18918
19085
|
}
|
|
18919
19086
|
}
|
|
19087
|
+
function opencodeConfigPath() {
|
|
19088
|
+
return (0, import_node_path22.join)((0, import_node_os5.homedir)(), ".config", "opencode", "opencode.jsonc");
|
|
19089
|
+
}
|
|
19090
|
+
function opencodeConfigSnapshot() {
|
|
19091
|
+
const path2 = opencodeConfigPath();
|
|
19092
|
+
if (!(0, import_node_fs26.existsSync)(path2)) return { path: path2, hasConfig: false, hasPluginField: false, parseOk: true };
|
|
19093
|
+
try {
|
|
19094
|
+
const raw = (0, import_node_fs26.readFileSync)(path2, "utf8");
|
|
19095
|
+
const parsed = JSON.parse(stripJsonc(raw));
|
|
19096
|
+
const hasPluginField = Object.prototype.hasOwnProperty.call(parsed, "plugin");
|
|
19097
|
+
return {
|
|
19098
|
+
path: path2,
|
|
19099
|
+
hasConfig: true,
|
|
19100
|
+
hasPluginField,
|
|
19101
|
+
parseOk: true,
|
|
19102
|
+
raw,
|
|
19103
|
+
...Array.isArray(parsed.plugin) ? { pluginEntries: parsed.plugin } : parsed.plugin === void 0 ? {} : { pluginEntries: void 0 }
|
|
19104
|
+
};
|
|
19105
|
+
} catch {
|
|
19106
|
+
return { path: path2, hasConfig: true, hasPluginField: false, parseOk: false };
|
|
19107
|
+
}
|
|
19108
|
+
}
|
|
19109
|
+
function writeOpencodeConfigPlugin(snapshot) {
|
|
19110
|
+
try {
|
|
19111
|
+
const path2 = snapshot.path;
|
|
19112
|
+
const plan2 = planOpencodeConfigWrite(snapshot.hasConfig ? snapshot.raw : void 0);
|
|
19113
|
+
if (plan2.action === "already") return true;
|
|
19114
|
+
if (!plan2.text || plan2.action === "unsafe") return false;
|
|
19115
|
+
(0, import_node_fs26.mkdirSync)((0, import_node_path22.dirname)(path2), { recursive: true });
|
|
19116
|
+
if (snapshot.hasConfig) (0, import_node_fs26.copyFileSync)(path2, `${path2}.bak`);
|
|
19117
|
+
(0, import_node_fs26.writeFileSync)(path2, plan2.text, "utf8");
|
|
19118
|
+
return true;
|
|
19119
|
+
} catch {
|
|
19120
|
+
return false;
|
|
19121
|
+
}
|
|
19122
|
+
}
|
|
18920
19123
|
function opencodeDesktopLogsRoot() {
|
|
18921
19124
|
if (process.platform === "win32") {
|
|
18922
19125
|
const base = process.env.APPDATA || (0, import_node_path22.join)((0, import_node_os5.homedir)(), "AppData", "Roaming");
|
|
@@ -18940,6 +19143,27 @@ function opencodeDesktopBootstrapSnapshot() {
|
|
|
18940
19143
|
return [];
|
|
18941
19144
|
}
|
|
18942
19145
|
}
|
|
19146
|
+
function opencodeLegacyConfigSnapshot() {
|
|
19147
|
+
const legacyPath = (0, import_node_path22.join)((0, import_node_os5.homedir)(), ".opencode", "opencode.json");
|
|
19148
|
+
if (!(0, import_node_fs26.existsSync)(legacyPath)) return {};
|
|
19149
|
+
const content = readTextFile(legacyPath);
|
|
19150
|
+
if (content == null) return {};
|
|
19151
|
+
const plugins = parseOpencodeLegacyConfigPlugins(content);
|
|
19152
|
+
if (!plugins) return {};
|
|
19153
|
+
const stalePlugins = plugins.filter(isStaleOpencodePluginEntry);
|
|
19154
|
+
if (!stalePlugins.length) return {};
|
|
19155
|
+
return { legacyPath, stalePlugins };
|
|
19156
|
+
}
|
|
19157
|
+
function quarantineOpencodeLegacyConfig(legacyPath) {
|
|
19158
|
+
try {
|
|
19159
|
+
const backupPath = `${legacyPath}.bak`;
|
|
19160
|
+
if ((0, import_node_fs26.existsSync)(backupPath)) return false;
|
|
19161
|
+
(0, import_node_fs26.renameSync)(legacyPath, backupPath);
|
|
19162
|
+
return true;
|
|
19163
|
+
} catch {
|
|
19164
|
+
return false;
|
|
19165
|
+
}
|
|
19166
|
+
}
|
|
18943
19167
|
function cursorPluginCacheRoot() {
|
|
18944
19168
|
return (0, import_node_path22.join)((0, import_node_os5.homedir)(), ".cursor", "plugins", "cache", "mutmutco", "mmi");
|
|
18945
19169
|
}
|
|
@@ -19160,6 +19384,8 @@ async function runDoctor(opts, io = consoleIo) {
|
|
|
19160
19384
|
}
|
|
19161
19385
|
const repairLocal = !opts.json || Boolean(opts.apply) || Boolean(opts.preflight);
|
|
19162
19386
|
const repoWritesAllowed = !opts.noRepoWrites;
|
|
19387
|
+
const runExtended = Boolean(opts.verbose) || Boolean(opts.json);
|
|
19388
|
+
const terseOutput = !opts.verbose && !opts.json && !opts.banner && !opts.preflight;
|
|
19163
19389
|
const checks = [];
|
|
19164
19390
|
const REWRITE_KEY = "url.https://github.com/.insteadOf";
|
|
19165
19391
|
const CLONE_FIX = 'run: git config --global url."https://github.com/".insteadOf "git@github.com:"';
|
|
@@ -19238,15 +19464,19 @@ async function runDoctor(opts, io = consoleIo) {
|
|
|
19238
19464
|
installedVersion
|
|
19239
19465
|
})
|
|
19240
19466
|
);
|
|
19241
|
-
|
|
19242
|
-
|
|
19243
|
-
|
|
19244
|
-
|
|
19245
|
-
|
|
19246
|
-
|
|
19247
|
-
|
|
19248
|
-
|
|
19249
|
-
|
|
19467
|
+
if (runExtended) {
|
|
19468
|
+
checks.push(
|
|
19469
|
+
buildHubDeployFreshnessCheck({
|
|
19470
|
+
isOrgRepo: Boolean(cfg.sagaApiUrl),
|
|
19471
|
+
deployedHubVersion: hubVersionInfo?.hubVersion,
|
|
19472
|
+
installedVersion,
|
|
19473
|
+
releasedVersion
|
|
19474
|
+
})
|
|
19475
|
+
);
|
|
19476
|
+
}
|
|
19477
|
+
if (runExtended) {
|
|
19478
|
+
checks.push(buildAwsCrossAccountCheck({ callerArn }));
|
|
19479
|
+
}
|
|
19250
19480
|
let cloneOk = cloneProbe;
|
|
19251
19481
|
if (!cloneOk && repairFull) {
|
|
19252
19482
|
try {
|
|
@@ -19368,7 +19598,35 @@ async function runDoctor(opts, io = consoleIo) {
|
|
|
19368
19598
|
}
|
|
19369
19599
|
}
|
|
19370
19600
|
checks.push(installedVersionCheck);
|
|
19371
|
-
|
|
19601
|
+
const openCodeConfigSnapshot = opencodeConfigSnapshot();
|
|
19602
|
+
const inspectOpenCode = surface === "opencode" || openCodeConfigSnapshot.hasConfig || runExtended;
|
|
19603
|
+
if (inspectOpenCode) {
|
|
19604
|
+
let opencodeConfigCheck = buildOpencodeConfigPluginCheck({
|
|
19605
|
+
isOrgRepo: Boolean(cfg.sagaApiUrl),
|
|
19606
|
+
configPath: openCodeConfigSnapshot.path,
|
|
19607
|
+
hasConfig: openCodeConfigSnapshot.hasConfig,
|
|
19608
|
+
hasPluginField: openCodeConfigSnapshot.hasPluginField,
|
|
19609
|
+
pluginEntries: openCodeConfigSnapshot.pluginEntries,
|
|
19610
|
+
parseOk: openCodeConfigSnapshot.parseOk
|
|
19611
|
+
});
|
|
19612
|
+
if (!opencodeConfigCheck.ok && opencodeConfigCheck.reason !== "unreadable-config" && opencodeConfigCheck.reason !== "invalid-plugin-shape" && repairLocal) {
|
|
19613
|
+
if (writeOpencodeConfigPlugin(openCodeConfigSnapshot)) {
|
|
19614
|
+
const refreshed = opencodeConfigSnapshot();
|
|
19615
|
+
opencodeConfigCheck = buildOpencodeConfigPluginCheck({
|
|
19616
|
+
isOrgRepo: Boolean(cfg.sagaApiUrl),
|
|
19617
|
+
configPath: refreshed.path,
|
|
19618
|
+
hasConfig: refreshed.hasConfig,
|
|
19619
|
+
hasPluginField: refreshed.hasPluginField,
|
|
19620
|
+
pluginEntries: refreshed.pluginEntries,
|
|
19621
|
+
parseOk: refreshed.parseOk
|
|
19622
|
+
});
|
|
19623
|
+
if (opencodeConfigCheck.ok) {
|
|
19624
|
+
markPluginReloadRequired();
|
|
19625
|
+
io.err(` \u21BB repaired: wired ${OPENCODE_PLUGIN_PACKAGE} in OpenCode config \u2014 ${reloadAction("opencode")} to load MMI commands`);
|
|
19626
|
+
}
|
|
19627
|
+
}
|
|
19628
|
+
}
|
|
19629
|
+
checks.push(opencodeConfigCheck);
|
|
19372
19630
|
checks.push(buildOpencodeVersionCheck({
|
|
19373
19631
|
isOrgRepo: Boolean(cfg.sagaApiUrl),
|
|
19374
19632
|
installedVersion: process.env.MMI_OPENCODE_PLUGIN_VERSION,
|
|
@@ -19380,6 +19638,21 @@ async function runDoctor(opts, io = consoleIo) {
|
|
|
19380
19638
|
issues: opencodeDesktopBootstrapSnapshot()
|
|
19381
19639
|
}));
|
|
19382
19640
|
}
|
|
19641
|
+
const legacyOpenCodeConfig = opencodeLegacyConfigSnapshot();
|
|
19642
|
+
if (Boolean(cfg.sagaApiUrl) && legacyOpenCodeConfig.stalePlugins?.length) {
|
|
19643
|
+
let legacyOpenCodeCheck = buildOpencodeLegacyConfigCheck({
|
|
19644
|
+
isOrgRepo: true,
|
|
19645
|
+
legacyPath: legacyOpenCodeConfig.legacyPath,
|
|
19646
|
+
stalePlugins: legacyOpenCodeConfig.stalePlugins
|
|
19647
|
+
});
|
|
19648
|
+
if (!legacyOpenCodeCheck.ok && repairLocal && legacyOpenCodeConfig.legacyPath) {
|
|
19649
|
+
if (quarantineOpencodeLegacyConfig(legacyOpenCodeConfig.legacyPath)) {
|
|
19650
|
+
legacyOpenCodeCheck = buildOpencodeLegacyConfigCheck({ isOrgRepo: true });
|
|
19651
|
+
io.err(` \u21BB quarantined legacy OpenCode config \u2192 ${legacyOpenCodeConfig.legacyPath}.bak \u2014 restart OpenCode`);
|
|
19652
|
+
}
|
|
19653
|
+
}
|
|
19654
|
+
checks.push(legacyOpenCodeCheck);
|
|
19655
|
+
}
|
|
19383
19656
|
let cacheCleanupCheck = buildMmiPluginCacheCleanupCheck({
|
|
19384
19657
|
isOrgRepo: Boolean(cfg.sagaApiUrl),
|
|
19385
19658
|
roots: mmiPluginCacheRootSnapshots(),
|
|
@@ -19492,53 +19765,62 @@ async function runDoctor(opts, io = consoleIo) {
|
|
|
19492
19765
|
mmiCliOnPath: onPath
|
|
19493
19766
|
})
|
|
19494
19767
|
);
|
|
19495
|
-
|
|
19496
|
-
|
|
19497
|
-
|
|
19498
|
-
|
|
19499
|
-
|
|
19500
|
-
|
|
19501
|
-
|
|
19502
|
-
|
|
19503
|
-
|
|
19504
|
-
|
|
19505
|
-
|
|
19506
|
-
|
|
19507
|
-
|
|
19508
|
-
|
|
19509
|
-
|
|
19510
|
-
|
|
19511
|
-
|
|
19512
|
-
|
|
19513
|
-
|
|
19514
|
-
|
|
19768
|
+
if (runExtended) {
|
|
19769
|
+
const playwrightMcpConfigs = playwrightMcpConfigSnapshots();
|
|
19770
|
+
checks.push(
|
|
19771
|
+
buildPlaywrightMcpVisionCapCheck({
|
|
19772
|
+
isOrgRepo: Boolean(cfg.sagaApiUrl),
|
|
19773
|
+
configs: playwrightMcpConfigs
|
|
19774
|
+
})
|
|
19775
|
+
);
|
|
19776
|
+
checks.push(
|
|
19777
|
+
buildPlaywrightMcpOutputDirCheck({
|
|
19778
|
+
isOrgRepo: Boolean(cfg.sagaApiUrl),
|
|
19779
|
+
configs: playwrightMcpConfigs
|
|
19780
|
+
})
|
|
19781
|
+
);
|
|
19782
|
+
checks.push(
|
|
19783
|
+
buildBrowserArtifactsCheck({
|
|
19784
|
+
isOrgRepo: Boolean(cfg.sagaApiUrl),
|
|
19785
|
+
strayPaths: strayBrowserArtifactPaths()
|
|
19786
|
+
})
|
|
19787
|
+
);
|
|
19788
|
+
}
|
|
19789
|
+
if (runExtended && !opts.banner && Boolean(cfg.sagaApiUrl)) {
|
|
19515
19790
|
const repoRoot = (await execFileP2("git", ["rev-parse", "--show-toplevel"], { timeout: GIT_TIMEOUT_MS }).catch(() => ({ stdout: "" }))).stdout.trim() || process.cwd();
|
|
19516
19791
|
const driftReport = await fetchLatestKbDriftReport(execFileP2, repoRoot);
|
|
19517
19792
|
checks.push(buildKbDriftAdvisoryCheck({ isOrgRepo: true, report: driftReport }));
|
|
19518
19793
|
}
|
|
19519
19794
|
if (!opts.banner) {
|
|
19520
19795
|
const repoRoot = (await execFileP2("git", ["rev-parse", "--show-toplevel"], { timeout: GIT_TIMEOUT_MS }).catch(() => ({ stdout: "" }))).stdout.trim() || process.cwd();
|
|
19521
|
-
|
|
19522
|
-
|
|
19523
|
-
|
|
19796
|
+
if (runExtended) {
|
|
19797
|
+
let scratchRun = executeScratchGc(repoRoot, { apply: false });
|
|
19798
|
+
if (opts.apply && repoWritesAllowed && scratchRun.plan.safeAuto.length > 0) {
|
|
19799
|
+
scratchRun = executeScratchGc(repoRoot, { apply: true });
|
|
19800
|
+
if (scratchRun.applied?.pruned.length) {
|
|
19801
|
+
io.err(` \u21BB pruned ${scratchRun.applied.pruned.length} scratch item(s) (${scratchRun.applied.bytes} bytes)`);
|
|
19802
|
+
}
|
|
19803
|
+
scratchRun = executeScratchGc(repoRoot, { apply: false });
|
|
19804
|
+
}
|
|
19805
|
+
checks.push(buildScratchGcCheck(scratchRun.plan));
|
|
19806
|
+
try {
|
|
19807
|
+
checks.push(buildGitGcCheck(await gcPlan("origin", 200)));
|
|
19808
|
+
} catch {
|
|
19809
|
+
}
|
|
19810
|
+
} else if (opts.apply && repoWritesAllowed) {
|
|
19811
|
+
const scratchRun = executeScratchGc(repoRoot, { apply: true });
|
|
19524
19812
|
if (scratchRun.applied?.pruned.length) {
|
|
19525
19813
|
io.err(` \u21BB pruned ${scratchRun.applied.pruned.length} scratch item(s) (${scratchRun.applied.bytes} bytes)`);
|
|
19526
19814
|
}
|
|
19527
|
-
scratchRun = executeScratchGc(repoRoot, { apply: false });
|
|
19528
|
-
}
|
|
19529
|
-
checks.push(buildScratchGcCheck(scratchRun.plan));
|
|
19530
|
-
try {
|
|
19531
|
-
checks.push(buildGitGcCheck(await gcPlan("origin", 200)));
|
|
19532
|
-
} catch {
|
|
19533
19815
|
}
|
|
19534
19816
|
}
|
|
19535
|
-
if (opts.banner) {
|
|
19817
|
+
if (opts.banner && runExtended) {
|
|
19536
19818
|
try {
|
|
19537
19819
|
checks.push(buildGitGcCheck(await gcPlan("origin", 50)));
|
|
19538
19820
|
} catch {
|
|
19539
19821
|
}
|
|
19540
19822
|
}
|
|
19541
|
-
if (!opts.banner) {
|
|
19823
|
+
if (runExtended && !opts.banner) {
|
|
19542
19824
|
const continuity = readContinuityStamp();
|
|
19543
19825
|
checks.push(
|
|
19544
19826
|
buildContinuityFreshnessCheck({
|
|
@@ -19549,38 +19831,40 @@ async function runDoctor(opts, io = consoleIo) {
|
|
|
19549
19831
|
})
|
|
19550
19832
|
);
|
|
19551
19833
|
}
|
|
19552
|
-
|
|
19553
|
-
|
|
19554
|
-
|
|
19555
|
-
|
|
19556
|
-
|
|
19557
|
-
|
|
19558
|
-
|
|
19559
|
-
|
|
19560
|
-
|
|
19561
|
-
|
|
19562
|
-
designSystemCheck
|
|
19563
|
-
|
|
19564
|
-
|
|
19834
|
+
if (runExtended) {
|
|
19835
|
+
const dashboardConsumer = await resolveDashboardConsumer(cfg);
|
|
19836
|
+
const isDashboardConsumer = dashboardConsumer.isConsumer;
|
|
19837
|
+
const uiSnapshot = designSystemSnapshot(process.cwd());
|
|
19838
|
+
const uiLatestVersion = isDashboardConsumer && uiSnapshot.packageName ? await fetchUiPackageLatestVersion(uiSnapshot.packageName) : void 0;
|
|
19839
|
+
let designSystemCheck = dashboardConsumer.registryReadFailed ? buildDesignSystemRegistryReadCheck(dashboardConsumer.registryReadFailed) : buildDesignSystemVersionCheck({
|
|
19840
|
+
...uiSnapshot,
|
|
19841
|
+
isConsumerRepo: isDashboardConsumer,
|
|
19842
|
+
latestVersion: uiLatestVersion
|
|
19843
|
+
});
|
|
19844
|
+
if (!designSystemCheck.ok && (repairFull || repairLocal) && designSystemCheck.packageName) {
|
|
19845
|
+
designSystemCheck = await applyDesignSystemUpdate(designSystemCheck, (m) => io.err(m));
|
|
19846
|
+
if (designSystemCheck.ok) {
|
|
19847
|
+
io.err(` \u21BB updated ${designSystemCheck.packageName} \u2192 ${designSystemCheck.installedVersion ?? designSystemCheck.latestVersion ?? "latest"}`);
|
|
19848
|
+
}
|
|
19565
19849
|
}
|
|
19566
|
-
|
|
19567
|
-
|
|
19568
|
-
|
|
19569
|
-
|
|
19570
|
-
|
|
19571
|
-
|
|
19572
|
-
|
|
19573
|
-
|
|
19574
|
-
|
|
19575
|
-
|
|
19576
|
-
|
|
19577
|
-
|
|
19578
|
-
|
|
19579
|
-
|
|
19580
|
-
|
|
19850
|
+
checks.push(designSystemCheck);
|
|
19851
|
+
const registryTargetVersion = designSystemCheck.latestVersion ?? designSystemCheck.installedVersion ?? uiLatestVersion;
|
|
19852
|
+
let registryComponentsCheck = dashboardConsumer.registryReadFailed ? buildRegistryComponentsRegistryReadCheck(dashboardConsumer.registryReadFailed) : buildRegistryComponentsCheck({
|
|
19853
|
+
...await gatherRegistryComponentsState(process.cwd(), registryTargetVersion, { fetch }),
|
|
19854
|
+
isConsumerRepo: isDashboardConsumer
|
|
19855
|
+
});
|
|
19856
|
+
if (!registryComponentsCheck.ok && (repairFull || repairLocal) && repoWritesAllowed && registryComponentsCheck.components?.length) {
|
|
19857
|
+
registryComponentsCheck = await applyRegistryComponentsSyncCheck(
|
|
19858
|
+
registryComponentsCheck,
|
|
19859
|
+
registryTargetVersion,
|
|
19860
|
+
(m) => io.err(m)
|
|
19861
|
+
);
|
|
19862
|
+
if (registryComponentsCheck.ok) {
|
|
19863
|
+
io.err(` \u21BB synced ${registryComponentsCheck.components?.length ?? 0} registry component(s) \u2192 .mmi/design-system/components`);
|
|
19864
|
+
}
|
|
19581
19865
|
}
|
|
19866
|
+
checks.push(registryComponentsCheck);
|
|
19582
19867
|
}
|
|
19583
|
-
checks.push(registryComponentsCheck);
|
|
19584
19868
|
const gaps = checks.filter((c) => !c.ok);
|
|
19585
19869
|
if (opts.preflight) {
|
|
19586
19870
|
if (healPlan.needsEagerHeal) io.err(doctorPreflightDoneLine(surface));
|
|
@@ -19609,6 +19893,12 @@ async function runDoctor(opts, io = consoleIo) {
|
|
|
19609
19893
|
io.log(JSON.stringify(buildDoctorJsonPayload({ checks, updateReport, resources }), null, 2));
|
|
19610
19894
|
return;
|
|
19611
19895
|
}
|
|
19896
|
+
if (terseOutput) {
|
|
19897
|
+
if (pluginReloadRequired) io.err(pluginAutonomousHaltLine(reloadHint));
|
|
19898
|
+
for (const line of renderTerseDoctorReport({ gaps, updateReport })) io.log(line);
|
|
19899
|
+
for (const r of resources) io.log(`Resource: ${r.label} \u2014 ${r.url}`);
|
|
19900
|
+
return;
|
|
19901
|
+
}
|
|
19612
19902
|
for (const c of checks) io.log(c.ok ? `\u2713 ${c.label}` : `\u2717 ${c.label}
|
|
19613
19903
|
\u2192 ${c.fix}`);
|
|
19614
19904
|
for (const r of resources) io.log(`Resource: ${r.label} \u2014 ${r.url}`);
|
|
@@ -22450,7 +22740,7 @@ designSystem.command("registry").description("compare .mmi/design-system/compone
|
|
|
22450
22740
|
if (!check.ok) console.log(` fix: ${check.fix}`);
|
|
22451
22741
|
process.exitCode = check.ok ? 0 : 1;
|
|
22452
22742
|
});
|
|
22453
|
-
program2.command("doctor").description("check onboarding gates
|
|
22743
|
+
program2.command("doctor").description("check onboarding gates and auto-heal CLI/plugin wiring; use --verbose for the full audit checklist").option("--banner", "one-line resume summary; silent when all gates pass").option("--preflight", "eager version/plugin heal with upfront notice when stale; silent when healthy (#1871)").option("--verbose", "run extended audit checks and print the full checklist + version report (#1989)").option("--guide", "print the MMI Agentic Onboarding guide URL").option("--json", "machine-readable output (read-only inspection \u2014 performs no repairs)").option("--apply", "perform the same auto-repairs as the interactive run (combine with --json for a machine-readable repair run)").option("--no-repo-writes", "env/plugin repairs only \u2014 never mutate the repo working tree; report pending managed .gitignore repairs with the follow-up command (for train preflights)").action((opts) => (
|
|
22454
22744
|
// Commander maps `--no-repo-writes` to `repoWrites: false`; translate to the explicit `noRepoWrites` flag.
|
|
22455
22745
|
runDoctor({ ...opts, noRepoWrites: opts.repoWrites === false })
|
|
22456
22746
|
));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mutmutco/cli",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.50.1",
|
|
4
4
|
"description": "MMI Future CLI — delivers the org rules (whole-file), plus saga and KB access. The cross-IDE engine the plugin's SessionStart hook drives.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "UNLICENSED",
|