@mutmutco/cli 3.78.0 → 3.79.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/README.md +10 -1
- package/dist/main.cjs +1370 -117
- package/package.json +2 -2
package/dist/main.cjs
CHANGED
|
@@ -3580,6 +3580,8 @@ var ERROR_CODES = {
|
|
|
3580
3580
|
ERR_CONFLICTING_FLAGS: "ERR_CONFLICTING_FLAGS",
|
|
3581
3581
|
/** A flag was supplied but its resolved value is empty (e.g. an empty `--title-file` or empty stdin). */
|
|
3582
3582
|
ERR_EMPTY_INPUT: "ERR_EMPTY_INPUT",
|
|
3583
|
+
/** A supplied value has the right source but an invalid shape (e.g. a title with line breaks). */
|
|
3584
|
+
ERR_INVALID_INPUT: "ERR_INVALID_INPUT",
|
|
3583
3585
|
/** A flag's value is outside its allowed set (e.g. `--priority nope`). */
|
|
3584
3586
|
ERR_BAD_ENUM: "ERR_BAD_ENUM",
|
|
3585
3587
|
/** An unknown flag or subcommand — usually a typo; carries a `did_you_mean`. */
|
|
@@ -3612,6 +3614,11 @@ var ERROR_CODE_REFERENCE = [
|
|
|
3612
3614
|
meaning: "A flag was supplied but resolved to an empty value (e.g. an empty file or empty stdin).",
|
|
3613
3615
|
typical_fix: "Provide non-empty content for the flag in `offending_flag` (a file with text, or a real pipe/heredoc for stdin)."
|
|
3614
3616
|
},
|
|
3617
|
+
{
|
|
3618
|
+
code: ERROR_CODES.ERR_INVALID_INPUT,
|
|
3619
|
+
meaning: "A supplied value has an invalid shape for the flag.",
|
|
3620
|
+
typical_fix: "Correct the value named by `offending_flag` and retry."
|
|
3621
|
+
},
|
|
3615
3622
|
{
|
|
3616
3623
|
code: ERROR_CODES.ERR_BAD_ENUM,
|
|
3617
3624
|
meaning: "A flag value is outside the allowed enum.",
|
|
@@ -9430,6 +9437,81 @@ async function runReleaseResume(deps, options = {}) {
|
|
|
9430
9437
|
note: `resumed and completed ${tag} \u2014 the original version was preserved, not re-cut`
|
|
9431
9438
|
};
|
|
9432
9439
|
}
|
|
9440
|
+
async function runReleaseAbort(deps, options = {}) {
|
|
9441
|
+
if (!options.approved) {
|
|
9442
|
+
throw new Error("release --abort requires --apply after explicit approval; nothing was written");
|
|
9443
|
+
}
|
|
9444
|
+
const ctx = await buildTrainApplyContext(deps);
|
|
9445
|
+
if (!isHubControlRepo(ctx.repo)) {
|
|
9446
|
+
throw new Error("release --abort is limited to mutmutco/MMI-Hub until other release tracks define equivalent publish-absence proof");
|
|
9447
|
+
}
|
|
9448
|
+
await requireCleanTree(deps);
|
|
9449
|
+
if (await currentBranch(deps) !== "main") {
|
|
9450
|
+
throw new Error("release --abort must run from the primary checkout left on main by the failed train; nothing was written");
|
|
9451
|
+
}
|
|
9452
|
+
await runGitRemoteRead(deps, ["fetch", "origin", "--tags"]);
|
|
9453
|
+
const tags = clean2(await deps.run("git", ["tag", "--list", "v[0-9]*.[0-9]*.[0-9]*", "--sort=-v:refname"])).split("\n").map((tag2) => tag2.trim()).filter((tag2) => /^v\d+\.\d+\.\d+$/.test(tag2));
|
|
9454
|
+
const tag = tags[0];
|
|
9455
|
+
if (!tag) throw new Error("release --abort found no final v* tag; nothing was written");
|
|
9456
|
+
const tagSha = await probeRemoteTag(deps, tag);
|
|
9457
|
+
if (!tagSha) throw new Error(`release --abort: ${tag} is not on origin; nothing was written`);
|
|
9458
|
+
const localTagSha = clean2(await deps.run("git", ["rev-parse", "--verify", `refs/tags/${tag}^{commit}`]));
|
|
9459
|
+
const localMainSha = clean2(await deps.run("git", ["rev-parse", "main"]));
|
|
9460
|
+
const originMainSha = clean2(await deps.run("git", ["rev-parse", "origin/main"]));
|
|
9461
|
+
if (localTagSha !== tagSha || localMainSha !== tagSha) {
|
|
9462
|
+
throw new Error(
|
|
9463
|
+
`release --abort: local tag/main do not both identify origin ${tag} (${tagSha.slice(0, 12)}); nothing was written`
|
|
9464
|
+
);
|
|
9465
|
+
}
|
|
9466
|
+
if (!await probeAncestor(deps, "origin/main", tagSha, "release --abort: origin/main ancestry")) {
|
|
9467
|
+
throw new Error(`release --abort: ${tag} does not descend from origin/main; nothing was written`);
|
|
9468
|
+
}
|
|
9469
|
+
const candidateParents = clean2(await deps.run("git", ["rev-list", "--parents", "-n", "1", tagSha])).split(/\s+/);
|
|
9470
|
+
if (candidateParents.length !== 2 || candidateParents[0] !== tagSha) {
|
|
9471
|
+
throw new Error(`release --abort: ${tag} is not a single-parent release bump; nothing was written`);
|
|
9472
|
+
}
|
|
9473
|
+
const candidateBaseSha = candidateParents[1];
|
|
9474
|
+
if (!await probeAncestor(deps, candidateBaseSha, "origin/development", "release --abort: development ancestry")) {
|
|
9475
|
+
throw new Error(`release --abort: current development does not descend from ${tag}'s candidate base; nothing was written`);
|
|
9476
|
+
}
|
|
9477
|
+
if (!await isStrayUnreleasedTag(deps, tag, tagSha, ctx.repo)) {
|
|
9478
|
+
throw new Error(`release --abort: ${tag} is released or its unpublished state could not be proven; nothing was written`);
|
|
9479
|
+
}
|
|
9480
|
+
const version = tag.slice(1);
|
|
9481
|
+
try {
|
|
9482
|
+
const published = clean2(await deps.run("npm", ["view", `@mutmutco/cli@${version}`, "version", "--json"]));
|
|
9483
|
+
if (published) {
|
|
9484
|
+
throw new Error(`release --abort: @mutmutco/cli@${version} is already published; ${tag} cannot be reused`);
|
|
9485
|
+
}
|
|
9486
|
+
throw new Error("npm registry returned an empty successful response");
|
|
9487
|
+
} catch (error) {
|
|
9488
|
+
const detail = `${error instanceof Error ? error.message : String(error)} ${String(error.stderr ?? "")}`;
|
|
9489
|
+
if (!/E404|not found|No match found/i.test(detail)) {
|
|
9490
|
+
if (/already published/.test(detail)) throw error;
|
|
9491
|
+
throw new Error(`release --abort: npm publish absence could not be proven (${detail.trim()}); nothing was written`);
|
|
9492
|
+
}
|
|
9493
|
+
}
|
|
9494
|
+
await runGitPush(deps, ["push", "origin", "--delete", tag]);
|
|
9495
|
+
if (await probeRemoteTag(deps, tag)) {
|
|
9496
|
+
throw new Error(`release --abort: origin still reports ${tag} after deletion; local state was preserved`);
|
|
9497
|
+
}
|
|
9498
|
+
await deps.run("git", ["tag", "-d", tag]);
|
|
9499
|
+
await deps.run("git", ["checkout", "development"]);
|
|
9500
|
+
await ffOnlyPull(deps, "development");
|
|
9501
|
+
await deps.run("git", ["branch", "-f", "main", "origin/main"]);
|
|
9502
|
+
const restoredMainSha = clean2(await deps.run("git", ["rev-parse", "main"]));
|
|
9503
|
+
if (restoredMainSha !== originMainSha) {
|
|
9504
|
+
throw new Error(`release --abort removed ${tag}, but local main did not restore to origin/main; inspect before retrying`);
|
|
9505
|
+
}
|
|
9506
|
+
return {
|
|
9507
|
+
command: "release-abort",
|
|
9508
|
+
repo: ctx.repo,
|
|
9509
|
+
tag,
|
|
9510
|
+
tagSha,
|
|
9511
|
+
restoredMainSha,
|
|
9512
|
+
note: `removed the unpublished ${tag} candidate and restored local main; repair development before recutting`
|
|
9513
|
+
};
|
|
9514
|
+
}
|
|
9433
9515
|
async function runTrainApplyPipeline(mode, input) {
|
|
9434
9516
|
const { deps, ctx, command, meta, branchHints, watch, options } = input;
|
|
9435
9517
|
const directTrack = input.directTrack ?? false;
|
|
@@ -12318,6 +12400,18 @@ function formatVaultPointer(p) {
|
|
|
12318
12400
|
return lines.join("\n");
|
|
12319
12401
|
}
|
|
12320
12402
|
var TIMEOUT_MS = 8e3;
|
|
12403
|
+
var VAULT_READ_ATTEMPTS = 2;
|
|
12404
|
+
async function fetchVaultRead(deps, url, init) {
|
|
12405
|
+
let lastError;
|
|
12406
|
+
for (let attempt = 0; attempt < VAULT_READ_ATTEMPTS; attempt += 1) {
|
|
12407
|
+
try {
|
|
12408
|
+
return await deps.fetch(url, { ...init, signal: AbortSignal.timeout(TIMEOUT_MS) });
|
|
12409
|
+
} catch (error) {
|
|
12410
|
+
lastError = error;
|
|
12411
|
+
}
|
|
12412
|
+
}
|
|
12413
|
+
throw lastError;
|
|
12414
|
+
}
|
|
12321
12415
|
var repoOf = (slug) => `${OWNER}/${slug}`;
|
|
12322
12416
|
var RECALL_REGIONS = ["us-east-1", "us-west-2", "eu-central-1", "ap-northeast-1"];
|
|
12323
12417
|
var PROVIDER_VERIFY_TIMEOUT_MS = 8e3;
|
|
@@ -12405,11 +12499,10 @@ async function fetchSecretValue(deps, key, opts) {
|
|
|
12405
12499
|
const repo = await targetRepo(deps, opts);
|
|
12406
12500
|
const slug = opts.slug?.toLowerCase();
|
|
12407
12501
|
try {
|
|
12408
|
-
const res = await deps
|
|
12502
|
+
const res = await fetchVaultRead(deps, `${deps.apiUrl}/secrets/get`, {
|
|
12409
12503
|
method: "POST",
|
|
12410
12504
|
headers: await deps.headers({ "content-type": "application/json" }),
|
|
12411
|
-
body: JSON.stringify({ repo, key, use: true, ...slug ? { slug } : {} })
|
|
12412
|
-
signal: AbortSignal.timeout(TIMEOUT_MS)
|
|
12505
|
+
body: JSON.stringify({ repo, key, use: true, ...slug ? { slug } : {} })
|
|
12413
12506
|
});
|
|
12414
12507
|
if (!res.ok) return null;
|
|
12415
12508
|
const { value } = await res.json();
|
|
@@ -12423,10 +12516,9 @@ async function secretsList(deps, opts) {
|
|
|
12423
12516
|
const qs = new URLSearchParams({ repo }).toString();
|
|
12424
12517
|
let res;
|
|
12425
12518
|
try {
|
|
12426
|
-
res = await deps
|
|
12519
|
+
res = await fetchVaultRead(deps, `${deps.apiUrl}/secrets/list?${qs}`, {
|
|
12427
12520
|
method: "GET",
|
|
12428
|
-
headers: await deps.headers()
|
|
12429
|
-
signal: AbortSignal.timeout(TIMEOUT_MS)
|
|
12521
|
+
headers: await deps.headers()
|
|
12430
12522
|
});
|
|
12431
12523
|
} catch (e) {
|
|
12432
12524
|
deps.err(`secrets list: ${e.message}`);
|
|
@@ -13478,12 +13570,17 @@ function secretsUseExitCode(result) {
|
|
|
13478
13570
|
return void 0;
|
|
13479
13571
|
}
|
|
13480
13572
|
async function fetchSecretForUse(deps, { repo, key, slug }) {
|
|
13481
|
-
|
|
13482
|
-
|
|
13483
|
-
|
|
13484
|
-
|
|
13485
|
-
|
|
13486
|
-
|
|
13573
|
+
let res;
|
|
13574
|
+
try {
|
|
13575
|
+
res = await fetchVaultRead(deps, `${deps.apiUrl}/secrets/get`, {
|
|
13576
|
+
method: "POST",
|
|
13577
|
+
headers: await deps.headers({ "content-type": "application/json" }),
|
|
13578
|
+
body: JSON.stringify({ repo, key, use: true, ...slug ? { slug } : {} })
|
|
13579
|
+
});
|
|
13580
|
+
} catch (error) {
|
|
13581
|
+
deps.err(`secrets use: ${error.message}`);
|
|
13582
|
+
return null;
|
|
13583
|
+
}
|
|
13487
13584
|
if (!res.ok) {
|
|
13488
13585
|
const body = await readJsonBody(res);
|
|
13489
13586
|
if (res.status === 404 && body.code === "secret_not_found") {
|
|
@@ -14175,7 +14272,7 @@ function buildPluginGuardLine(state, opts = {}) {
|
|
|
14175
14272
|
if (state === "healthy" || state === "not-org") return { exitCode: 0 };
|
|
14176
14273
|
const recovery = opts.recovery ?? "mmi-cli plugin heal";
|
|
14177
14274
|
const restartHint = opts.restartHint ?? "restart your agent host / reload plugins";
|
|
14178
|
-
const reason = state === "no-install" ? "MMI plugin is not installed for this user/session" : "MMI plugin is installed but its
|
|
14275
|
+
const reason = state === "no-install" ? "MMI plugin is not installed for this user/session" : "MMI plugin is installed but its delivery/cache is unresolved";
|
|
14179
14276
|
return {
|
|
14180
14277
|
line: `[mmi-guard] ${reason}; run ${recovery} and ${restartHint}.`,
|
|
14181
14278
|
exitCode: 1
|
|
@@ -14195,6 +14292,9 @@ function detectSurface(env) {
|
|
|
14195
14292
|
if (env.MMI_AGENT_SURFACE === "kimi" || has("KIMI_PLUGIN_ROOT") || has("KIMI_CODE_HOME")) {
|
|
14196
14293
|
return "kimi";
|
|
14197
14294
|
}
|
|
14295
|
+
if (env.MMI_AGENT_SURFACE === "kilo" || has("KILO") || Object.keys(env).some((k) => /^(?:KILO|KILOCODE)_/.test(k))) {
|
|
14296
|
+
return "kilo";
|
|
14297
|
+
}
|
|
14198
14298
|
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")) {
|
|
14199
14299
|
return "cursor";
|
|
14200
14300
|
}
|
|
@@ -14214,6 +14314,8 @@ function surfaceToken(surface) {
|
|
|
14214
14314
|
return "codex";
|
|
14215
14315
|
case "kimi":
|
|
14216
14316
|
return "kimi";
|
|
14317
|
+
case "kilo":
|
|
14318
|
+
return "kilo";
|
|
14217
14319
|
case "cursor":
|
|
14218
14320
|
return "cursor";
|
|
14219
14321
|
case "opencode":
|
|
@@ -14231,10 +14333,12 @@ function reloadAction(surface) {
|
|
|
14231
14333
|
return "restart Codex";
|
|
14232
14334
|
case "kimi":
|
|
14233
14335
|
return "run /reload (or start a new session) in Kimi Code";
|
|
14336
|
+
case "kilo":
|
|
14337
|
+
return "run /reload (or start a new session) in Kilo Code";
|
|
14234
14338
|
case "opencode":
|
|
14235
14339
|
return "restart OpenCode";
|
|
14236
14340
|
case "cursor":
|
|
14237
|
-
return "
|
|
14341
|
+
return "reload the Cursor window";
|
|
14238
14342
|
case "claude-cli":
|
|
14239
14343
|
case "shell":
|
|
14240
14344
|
default:
|
|
@@ -14243,6 +14347,7 @@ function reloadAction(surface) {
|
|
|
14243
14347
|
}
|
|
14244
14348
|
var CLAUDE_RECOVERY = `claude plugin marketplace remove ${LEGACY_MMI_MARKETPLACE} && claude plugin marketplace remove mutmutco && claude plugin marketplace add mutmutco/MMI-Hub --ref main && claude plugin install mmi@mutmutco`;
|
|
14245
14349
|
var CODEX_RECOVERY = "codex plugin remove mmi@mutmutco && codex plugin marketplace remove mutmutco && codex plugin marketplace add mutmutco/MMI-Hub --ref main && codex plugin add mmi@mutmutco";
|
|
14350
|
+
var CURSOR_RECOVERY = "mmi-cli plugin heal # installs ~/.cursor/plugins/local/mmi from mutmutco/MMI-Hub";
|
|
14246
14351
|
var PLUGIN_SURFACE_HEAL = {
|
|
14247
14352
|
claude: {
|
|
14248
14353
|
delivery: "plugin-cli",
|
|
@@ -14268,6 +14373,24 @@ var PLUGIN_SURFACE_HEAL = {
|
|
|
14268
14373
|
],
|
|
14269
14374
|
fix: (surface) => `${CODEX_RECOVERY} # then ${reloadAction(surface)} and review /hooks`,
|
|
14270
14375
|
updateRecipe: [CODEX_RECOVERY]
|
|
14376
|
+
},
|
|
14377
|
+
cursor: {
|
|
14378
|
+
delivery: "local-checkout",
|
|
14379
|
+
recovery: CURSOR_RECOVERY,
|
|
14380
|
+
healSteps: null,
|
|
14381
|
+
fix: (surface) => `${CURSOR_RECOVERY} # then ${reloadAction(surface)}`,
|
|
14382
|
+
updateRecipe: [CURSOR_RECOVERY]
|
|
14383
|
+
},
|
|
14384
|
+
kilo: {
|
|
14385
|
+
// kilo-p1: no marketplace step — `kilo plugin <npm>` installs + patches the config in one non-interactive
|
|
14386
|
+
// verb. --global writes ~/.config/kilo/opencode.json; the plugin's server() provisions the skills.
|
|
14387
|
+
delivery: "plugin-cli",
|
|
14388
|
+
recovery: "kilo plugin @mutmutco/kilo-plugin --global",
|
|
14389
|
+
healSteps: [
|
|
14390
|
+
{ args: ["plugin", "@mutmutco/kilo-plugin", "--global"], gated: true }
|
|
14391
|
+
],
|
|
14392
|
+
fix: (surface) => `kilo plugin @mutmutco/kilo-plugin --global # then ${reloadAction(surface)} to load the plugin and its provisioned skills`,
|
|
14393
|
+
updateRecipe: ["kilo plugin @mutmutco/kilo-plugin --global"]
|
|
14271
14394
|
}
|
|
14272
14395
|
};
|
|
14273
14396
|
function nonClaudeSurfaceHealMessage(surface) {
|
|
@@ -14277,7 +14400,13 @@ function nonClaudeSurfaceHealMessage(surface) {
|
|
|
14277
14400
|
if (surface === "kimi") {
|
|
14278
14401
|
return "Kimi Code CLI ships an MMI plugin (kimi-k3). Install or repair it from the Kimi TUI with:\n /plugins install https://github.com/mutmutco/MMI-Hub\n Then run /reload (plugin hooks start only after a reload), and TRUST the install when prompted.\n Update the CLI too: npm i -g @mutmutco/cli";
|
|
14279
14402
|
}
|
|
14280
|
-
|
|
14403
|
+
if (surface === "kilo") {
|
|
14404
|
+
return "Kilo Code ships an MMI plugin (kilo-p1). Install or repair it with:\n kilo plugin @mutmutco/kilo-plugin --global\n Then run /reload (or start a new session) \u2014 the plugin\u2019s first run provisions the skills\n into ~/.kilo, and the deny gates + redactor only load after a reload.\n Update the CLI too: npm i -g @mutmutco/cli";
|
|
14405
|
+
}
|
|
14406
|
+
if (surface === "cursor") {
|
|
14407
|
+
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";
|
|
14408
|
+
}
|
|
14409
|
+
return "No Hub-shipped MMI plugin on this host \u2014 the Hub ships plugins for Claude, Codex, Kimi, Cursor, and Kilo only.\n Update the CLI instead: npm i -g @mutmutco/cli (org rules ride an AGENTS.md authored outside the Hub)";
|
|
14281
14410
|
}
|
|
14282
14411
|
function healStepAborts(step, ok) {
|
|
14283
14412
|
return !ok && step.gated;
|
|
@@ -14319,6 +14448,8 @@ function runHostBin(bin, args, opts) {
|
|
|
14319
14448
|
function surfaceConfigRoot(surface, env = process.env, home = (0, import_node_os5.homedir)()) {
|
|
14320
14449
|
if (surface === "codex") return env.CODEX_HOME?.trim() || (0, import_node_path16.join)(home, ".codex");
|
|
14321
14450
|
if (surface === "kimi") return env.KIMI_CODE_HOME?.trim() || (0, import_node_path16.join)(home, ".kimi-code");
|
|
14451
|
+
if (surface === "kilo") return env.KILO_CONFIG_DIR?.trim() || (0, import_node_path16.join)(home, ".config", "kilo");
|
|
14452
|
+
if (surface === "cursor") return (0, import_node_path16.join)(home, ".cursor");
|
|
14322
14453
|
return (0, import_node_path16.join)(home, ".claude");
|
|
14323
14454
|
}
|
|
14324
14455
|
var installedPluginsPath = (surface = detectSurface(process.env)) => {
|
|
@@ -14340,6 +14471,8 @@ function marketplaceCloneCandidates(surface, home, env = process.env) {
|
|
|
14340
14471
|
];
|
|
14341
14472
|
}
|
|
14342
14473
|
if (surface === "kimi") return [];
|
|
14474
|
+
if (surface === "kilo") return [];
|
|
14475
|
+
if (surface === "cursor") return [];
|
|
14343
14476
|
return [(0, import_node_path16.join)(home, ".claude", "plugins", "marketplaces", "mutmutco")];
|
|
14344
14477
|
}
|
|
14345
14478
|
function marketplaceClonePresent(surface, home, exists = import_node_fs18.existsSync, env = process.env) {
|
|
@@ -14349,11 +14482,13 @@ function runHostBinSync(bin, args) {
|
|
|
14349
14482
|
return isWin ? (0, import_node_child_process7.execFileSync)("cmd.exe", ["/c", bin, ...args], {
|
|
14350
14483
|
encoding: "utf8",
|
|
14351
14484
|
stdio: ["ignore", "pipe", "ignore"],
|
|
14352
|
-
timeout: 15e3
|
|
14485
|
+
timeout: 15e3,
|
|
14486
|
+
windowsHide: true
|
|
14353
14487
|
}) : (0, import_node_child_process7.execFileSync)(bin, args, {
|
|
14354
14488
|
encoding: "utf8",
|
|
14355
14489
|
stdio: ["ignore", "pipe", "ignore"],
|
|
14356
|
-
timeout: 15e3
|
|
14490
|
+
timeout: 15e3,
|
|
14491
|
+
windowsHide: true
|
|
14357
14492
|
});
|
|
14358
14493
|
}
|
|
14359
14494
|
function codexPluginStatus() {
|
|
@@ -14426,16 +14561,61 @@ async function npmSelfUpdateCli(target) {
|
|
|
14426
14561
|
return { ok: false, detail: e.message.trim().slice(0, 200).replace(/\s+/g, " ") };
|
|
14427
14562
|
}
|
|
14428
14563
|
}
|
|
14564
|
+
function kiloConfigListsPlugin(configRoot, home = (0, import_node_os5.homedir)(), read = (p) => (0, import_node_fs18.readFileSync)(p, "utf8"), exists = import_node_fs18.existsSync) {
|
|
14565
|
+
const candidates = ["kilo.json", "kilo.jsonc", "opencode.json", "opencode.jsonc", "config.json"];
|
|
14566
|
+
for (const dir of [configRoot, (0, import_node_path16.join)(home, ".kilo")]) {
|
|
14567
|
+
for (const file of candidates) {
|
|
14568
|
+
const path2 = (0, import_node_path16.join)(dir, file);
|
|
14569
|
+
if (!exists(path2)) continue;
|
|
14570
|
+
try {
|
|
14571
|
+
const stripped = read(path2).replace(/\/\*[\s\S]*?\*\//g, "").replace(/\/\/.*$/gm, "");
|
|
14572
|
+
const parsed = JSON.parse(stripped);
|
|
14573
|
+
const plugins = Array.isArray(parsed?.plugin) ? parsed.plugin : [];
|
|
14574
|
+
if (plugins.some((p) => {
|
|
14575
|
+
const spec = String(Array.isArray(p) ? p[0] : p);
|
|
14576
|
+
return spec.includes("@mutmutco/kilo-plugin") || spec.includes(".kilo-plugin");
|
|
14577
|
+
})) return true;
|
|
14578
|
+
} catch {
|
|
14579
|
+
}
|
|
14580
|
+
}
|
|
14581
|
+
}
|
|
14582
|
+
return false;
|
|
14583
|
+
}
|
|
14584
|
+
function cursorLocalPluginRoot(env = process.env, home = (0, import_node_os5.homedir)()) {
|
|
14585
|
+
return (0, import_node_path16.join)(surfaceConfigRoot("cursor", env, home), "plugins", "local", "mmi");
|
|
14586
|
+
}
|
|
14587
|
+
function cursorPluginTreeHealthy(root, exists = import_node_fs18.existsSync) {
|
|
14588
|
+
return [
|
|
14589
|
+
".cursor-plugin/plugin.json",
|
|
14590
|
+
"skills/mmi/SKILL.md",
|
|
14591
|
+
"hooks/cursor-hooks.json",
|
|
14592
|
+
"scripts/hook-run.mjs",
|
|
14593
|
+
"scripts/hook-policy.mjs"
|
|
14594
|
+
].every((path2) => exists((0, import_node_path16.join)(root, ...path2.split("/"))));
|
|
14595
|
+
}
|
|
14596
|
+
function kimiPluginTreeHealthy(root, exists = import_node_fs18.existsSync) {
|
|
14597
|
+
return [
|
|
14598
|
+
".kimi-plugin/plugin.json",
|
|
14599
|
+
"skills/mmi/SKILL.md",
|
|
14600
|
+
"scripts/hook-run.mjs"
|
|
14601
|
+
].every((path2) => exists((0, import_node_path16.join)(root, ...path2.split("/"))));
|
|
14602
|
+
}
|
|
14429
14603
|
function snapshotPluginGuardInput(surface = detectSurface(process.env), isOrgRepo = false) {
|
|
14430
14604
|
const root = surfaceConfigRoot(surface);
|
|
14431
14605
|
const installed = readInstalledPlugins(surface);
|
|
14432
14606
|
const codexStatus = surface === "codex" ? codexPluginStatus() : void 0;
|
|
14433
14607
|
return {
|
|
14434
14608
|
isOrgRepo,
|
|
14435
|
-
installRecordPresent: hasUserInstallRecord(installed, MMI_PLUGIN_ID) || hasProjectInstallRecord(installed, MMI_PLUGIN_ID, process.cwd())
|
|
14436
|
-
|
|
14609
|
+
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.
|
|
14610
|
+
surface === "kimi" && (0, import_node_fs18.existsSync)((0, import_node_path16.join)(root, "plugins", "managed", "mmi")) || // kilo-p1: the install record is the config file itself.
|
|
14611
|
+
surface === "kilo" && kiloConfigListsPlugin(root) || surface === "cursor" && (0, import_node_fs18.existsSync)(cursorLocalPluginRoot()),
|
|
14612
|
+
// Kilo has no marketplace to clone — the config file IS the install record, so this dimension of
|
|
14613
|
+
// the shared guard table is vacuously satisfied.
|
|
14614
|
+
marketplaceClonePresent: surface === "kimi" || surface === "kilo" || surface === "cursor" ? true : marketplaceClonePresent(surface, (0, import_node_os5.homedir)()),
|
|
14437
14615
|
// Kimi keeps no plugin cache dir — installs are copied to plugins/managed/<id> and run from there.
|
|
14438
|
-
|
|
14616
|
+
// Kilo (kilo-p1) keeps no cache dir either: the plugin's server() provisions ~/.kilo behind the
|
|
14617
|
+
// version stamp, so the stamp's presence is the cache signal.
|
|
14618
|
+
pluginCachePresent: surface === "kilo" ? (0, import_node_fs18.existsSync)((0, import_node_path16.join)((0, import_node_os5.homedir)(), ".kilo", ".mmi-kilo-version")) : surface === "kimi" ? kimiPluginTreeHealthy((0, import_node_path16.join)(root, "plugins", "managed", "mmi")) : surface === "cursor" ? cursorPluginTreeHealthy(cursorLocalPluginRoot()) : surface === "codex" ? Boolean(
|
|
14439
14619
|
codexStatus?.installed && codexStatus.enabled && codexStatus.version && (0, import_node_fs18.existsSync)((0, import_node_path16.join)(root, "plugins", "cache", CODEX_MARKETPLACE, "mmi", codexStatus.version))
|
|
14440
14620
|
) : (0, import_node_fs18.existsSync)((0, import_node_path16.join)(root, "plugins", "cache", "mutmutco", "mmi"))
|
|
14441
14621
|
};
|
|
@@ -14459,6 +14639,98 @@ async function runCodexPlugin(args) {
|
|
|
14459
14639
|
return false;
|
|
14460
14640
|
}
|
|
14461
14641
|
}
|
|
14642
|
+
async function runKiloPlugin(args) {
|
|
14643
|
+
try {
|
|
14644
|
+
await runHostBin("kilo", args, { timeout: CLAUDE_PLUGIN_TIMEOUT_MS });
|
|
14645
|
+
return true;
|
|
14646
|
+
} catch {
|
|
14647
|
+
return false;
|
|
14648
|
+
}
|
|
14649
|
+
}
|
|
14650
|
+
function captureCodexHookLauncher() {
|
|
14651
|
+
const status = codexPluginStatus();
|
|
14652
|
+
if (!status.installed || !status.enabled || !status.version) return void 0;
|
|
14653
|
+
const root = (0, import_node_path16.join)(surfaceConfigRoot("codex"), "plugins", "cache", CODEX_MARKETPLACE, "mmi", status.version);
|
|
14654
|
+
const files = ["mmi-hook", "mmi-hook.exe"].flatMap((name) => {
|
|
14655
|
+
const path2 = (0, import_node_path16.join)(root, "bin", name);
|
|
14656
|
+
try {
|
|
14657
|
+
return [{ name, content: (0, import_node_fs18.readFileSync)(path2) }];
|
|
14658
|
+
} catch {
|
|
14659
|
+
return [];
|
|
14660
|
+
}
|
|
14661
|
+
});
|
|
14662
|
+
return files.length === 2 ? { root, files } : void 0;
|
|
14663
|
+
}
|
|
14664
|
+
function restoreCodexHookLauncher(snapshot) {
|
|
14665
|
+
if (!snapshot || (0, import_node_fs18.existsSync)((0, import_node_path16.join)(snapshot.root, "scripts", "hook-run.mjs"))) return false;
|
|
14666
|
+
const bin = (0, import_node_path16.join)(snapshot.root, "bin");
|
|
14667
|
+
(0, import_node_fs18.mkdirSync)(bin, { recursive: true });
|
|
14668
|
+
for (const file of snapshot.files) {
|
|
14669
|
+
const path2 = (0, import_node_path16.join)(bin, file.name);
|
|
14670
|
+
(0, import_node_fs18.writeFileSync)(path2, file.content);
|
|
14671
|
+
if (file.name === "mmi-hook") (0, import_node_fs18.chmodSync)(path2, 493);
|
|
14672
|
+
}
|
|
14673
|
+
return true;
|
|
14674
|
+
}
|
|
14675
|
+
function canonicalCursorRemote(remote) {
|
|
14676
|
+
return /^(?:https?:\/\/github\.com\/|ssh:\/\/git@github\.com\/|git@github\.com:|github\.com[:/])mutmutco\/MMI-Hub(?:\.git)?\/?$/i.test(remote.trim().replace(/\\/g, "/"));
|
|
14677
|
+
}
|
|
14678
|
+
async function installCursorPluginCheckout(env = process.env) {
|
|
14679
|
+
const configRoot = surfaceConfigRoot("cursor", env);
|
|
14680
|
+
const pluginsRoot = (0, import_node_path16.join)(configRoot, "plugins");
|
|
14681
|
+
const target = (0, import_node_path16.join)(pluginsRoot, "local", "mmi");
|
|
14682
|
+
const source = env.MMI_CURSOR_PLUGIN_SOURCE?.trim();
|
|
14683
|
+
if ((0, import_node_fs18.existsSync)(target) && !source) {
|
|
14684
|
+
try {
|
|
14685
|
+
const { stdout } = await runHostBin("git", ["-C", target, "remote", "get-url", "origin"], { timeout: 15e3 });
|
|
14686
|
+
if (!canonicalCursorRemote(stdout)) {
|
|
14687
|
+
return { ok: false, detail: `refused to replace unrelated Cursor plugin directory at ${target}` };
|
|
14688
|
+
}
|
|
14689
|
+
} catch {
|
|
14690
|
+
return { ok: false, detail: `refused to replace unmanaged Cursor plugin directory at ${target}` };
|
|
14691
|
+
}
|
|
14692
|
+
}
|
|
14693
|
+
(0, import_node_fs18.mkdirSync)((0, import_node_path16.join)(pluginsRoot, "local"), { recursive: true });
|
|
14694
|
+
(0, import_node_fs18.mkdirSync)((0, import_node_path16.join)(pluginsRoot, "staging"), { recursive: true });
|
|
14695
|
+
(0, import_node_fs18.mkdirSync)((0, import_node_path16.join)(pluginsRoot, "quarantine"), { recursive: true });
|
|
14696
|
+
const suffix = `${Date.now()}-${process.pid}`;
|
|
14697
|
+
const staged = (0, import_node_path16.join)(pluginsRoot, "staging", `mmi-${suffix}`);
|
|
14698
|
+
const quarantined = (0, import_node_path16.join)(pluginsRoot, "quarantine", `mmi-${suffix}`);
|
|
14699
|
+
try {
|
|
14700
|
+
if (source) {
|
|
14701
|
+
(0, import_node_fs18.cpSync)(source, staged, {
|
|
14702
|
+
recursive: true,
|
|
14703
|
+
filter: (path2) => !path2.split(/[\\/]/).some((part) => part === ".git" || part === "node_modules")
|
|
14704
|
+
});
|
|
14705
|
+
} else {
|
|
14706
|
+
await runHostBin("gh", ["repo", "clone", "mutmutco/MMI-Hub", staged, "--", "--branch", "main", "--depth", "1"], {
|
|
14707
|
+
timeout: CLAUDE_PLUGIN_TIMEOUT_MS
|
|
14708
|
+
});
|
|
14709
|
+
}
|
|
14710
|
+
if (!cursorPluginTreeHealthy(staged)) {
|
|
14711
|
+
(0, import_node_fs18.rmSync)(staged, { recursive: true, force: true });
|
|
14712
|
+
return { ok: false, detail: "downloaded Cursor plugin is incomplete; existing install was preserved" };
|
|
14713
|
+
}
|
|
14714
|
+
let movedOld = false;
|
|
14715
|
+
if ((0, import_node_fs18.existsSync)(target)) {
|
|
14716
|
+
(0, import_node_fs18.renameSync)(target, quarantined);
|
|
14717
|
+
movedOld = true;
|
|
14718
|
+
}
|
|
14719
|
+
try {
|
|
14720
|
+
(0, import_node_fs18.renameSync)(staged, target);
|
|
14721
|
+
} catch (error) {
|
|
14722
|
+
if (movedOld && !(0, import_node_fs18.existsSync)(target)) (0, import_node_fs18.renameSync)(quarantined, target);
|
|
14723
|
+
throw error;
|
|
14724
|
+
}
|
|
14725
|
+
return {
|
|
14726
|
+
ok: true,
|
|
14727
|
+
detail: movedOld ? `installed canonical Cursor plugin; previous checkout quarantined at ${quarantined}` : `installed canonical Cursor plugin at ${target}`
|
|
14728
|
+
};
|
|
14729
|
+
} catch (error) {
|
|
14730
|
+
if ((0, import_node_fs18.existsSync)(staged)) (0, import_node_fs18.rmSync)(staged, { recursive: true, force: true });
|
|
14731
|
+
return { ok: false, detail: error.message.trim().slice(0, 240).replace(/\s+/g, " ") };
|
|
14732
|
+
}
|
|
14733
|
+
}
|
|
14462
14734
|
async function marketplaceAddRefSupported(bin) {
|
|
14463
14735
|
try {
|
|
14464
14736
|
const { stdout, stderr } = await runHostBin(bin, ["plugin", "marketplace", "add", "--help"], {
|
|
@@ -14486,20 +14758,35 @@ function pluginReadGrantNote(login = "<your-github-login>") {
|
|
|
14486
14758
|
}
|
|
14487
14759
|
async function applyPluginHeal(surface, log, opts) {
|
|
14488
14760
|
const token = surfaceToken(surface);
|
|
14489
|
-
if (token !== "claude" && token !== "codex") return false;
|
|
14490
|
-
if (!opts?.force && !PLUGIN_SURFACE_HEAL[token]) return false;
|
|
14761
|
+
if (token !== "claude" && token !== "codex" && token !== "kilo") return false;
|
|
14491
14762
|
const descriptor = PLUGIN_SURFACE_HEAL[token];
|
|
14763
|
+
if (!descriptor || !descriptor.healSteps) return false;
|
|
14764
|
+
if (token === "kilo") {
|
|
14765
|
+
log(" \u21BB reinstalling the MMI plugin via `kilo plugin` (install \u2192 server() provisions the skills)\u2026");
|
|
14766
|
+
for (const step of descriptor.healSteps) {
|
|
14767
|
+
const ok = await runKiloPlugin([...step.args]);
|
|
14768
|
+
if (healStepAborts(step, ok)) return false;
|
|
14769
|
+
}
|
|
14770
|
+
return true;
|
|
14771
|
+
}
|
|
14492
14772
|
const tableSteps = descriptor.healSteps;
|
|
14493
14773
|
if (!tableSteps) return false;
|
|
14774
|
+
const loadedCodexLauncher = token === "codex" ? captureCodexHookLauncher() : void 0;
|
|
14494
14775
|
const bin = token;
|
|
14495
14776
|
const refSupported = await marketplaceAddRefSupported(bin);
|
|
14496
14777
|
const { steps } = adaptHealStepsForRefSupport(tableSteps, refSupported);
|
|
14497
14778
|
log(healBannerLine(bin, token, refSupported));
|
|
14498
14779
|
const pinsPath = (0, import_node_path16.join)((0, import_node_os5.homedir)(), ...KNOWN_MARKETPLACES_RELATIVE);
|
|
14499
14780
|
const pins = token === "claude" ? captureMarketplacePins(readKnownMarketplacesFile(pinsPath), [MMI_MARKETPLACE_NAME, JERV_MARKETPLACE_NAME]) : /* @__PURE__ */ new Map();
|
|
14500
|
-
|
|
14501
|
-
const
|
|
14502
|
-
|
|
14781
|
+
try {
|
|
14782
|
+
for (const step of steps) {
|
|
14783
|
+
const ok = token === "claude" ? await runClaudePlugin([...step.args]) : await runCodexPlugin([...step.args]);
|
|
14784
|
+
if (healStepAborts(step, ok)) return false;
|
|
14785
|
+
}
|
|
14786
|
+
} finally {
|
|
14787
|
+
if (restoreCodexHookLauncher(loadedCodexLauncher)) {
|
|
14788
|
+
log(" retained the windowless Codex hook bridge for the loaded session; restart removes its need");
|
|
14789
|
+
}
|
|
14503
14790
|
}
|
|
14504
14791
|
const restored = token === "claude" ? restoreMarketplacePinsOnDisk(pinsPath, pins) : void 0;
|
|
14505
14792
|
if (restored) log(` ${restored}`);
|
|
@@ -14519,16 +14806,23 @@ async function healClaudePluginForDoctor(surface = detectSurface(process.env)) {
|
|
|
14519
14806
|
}
|
|
14520
14807
|
async function healActivePluginForDoctor(surface = detectSurface(process.env)) {
|
|
14521
14808
|
const token = surfaceToken(surface);
|
|
14522
|
-
if (token !== "claude" && token !== "codex") {
|
|
14809
|
+
if (token !== "claude" && token !== "codex" && token !== "cursor" && token !== "kilo") {
|
|
14523
14810
|
return { ok: false, detail: `not a supported plugin surface (${surface})` };
|
|
14524
14811
|
}
|
|
14525
14812
|
if (token === "claude") return healClaudePluginForDoctor(surface);
|
|
14813
|
+
if (token === "cursor") return installCursorPluginCheckout();
|
|
14526
14814
|
const steps = [];
|
|
14527
14815
|
const applied = await applyPluginHeal(surface, (msg) => steps.push(msg.trim()));
|
|
14528
14816
|
const snapshot = applied ? snapshotPluginGuardInput(surface, true) : void 0;
|
|
14529
14817
|
const guardState = snapshot ? buildPluginGuardDecision(snapshot).state : "unresolved";
|
|
14530
14818
|
const ok = applied && guardState === "healthy";
|
|
14531
14819
|
const verification = snapshot ? `record=${snapshot.installRecordPresent ? "yes" : "no"}, marketplace=${snapshot.marketplaceClonePresent ? "yes" : "no"}, enabled-cache=${snapshot.pluginCachePresent ? "yes" : "no"}` : "reinstall steps did not complete";
|
|
14820
|
+
if (token === "kilo") {
|
|
14821
|
+
return {
|
|
14822
|
+
ok,
|
|
14823
|
+
detail: ok ? `kilo plugin install succeeded; full guard verified (${verification})` : `\`kilo plugin\` reinstall failed full-guard verification (${verification})${steps.length ? `; ${steps[steps.length - 1]}` : ""}`
|
|
14824
|
+
};
|
|
14825
|
+
}
|
|
14532
14826
|
return {
|
|
14533
14827
|
ok,
|
|
14534
14828
|
detail: ok ? `marketplace remove \u2192 add --ref main \u2192 plugin add succeeded; full guard verified (${verification})` : `\`codex plugin\` reinstall failed full-guard verification (${verification})${steps.length ? `; ${steps[steps.length - 1]}` : ""}`
|
|
@@ -14562,14 +14856,14 @@ async function runGuard(readOrigin) {
|
|
|
14562
14856
|
const surface = detectSurface(process.env);
|
|
14563
14857
|
try {
|
|
14564
14858
|
const token = surfaceToken(surface);
|
|
14565
|
-
if (token !== "claude" && token !== "codex") {
|
|
14859
|
+
if (token !== "claude" && token !== "codex" && token !== "cursor" && token !== "kilo") {
|
|
14566
14860
|
process.exitCode = 0;
|
|
14567
14861
|
return;
|
|
14568
14862
|
}
|
|
14569
14863
|
const isOrgRepo = readOrigin ? await isOrgRepoRoot(readOrigin) : await isOrgRepoRoot();
|
|
14570
14864
|
const input = snapshotPluginGuardInput(surface, isOrgRepo);
|
|
14571
14865
|
const { state } = buildPluginGuardDecision(input);
|
|
14572
|
-
const { line, exitCode } = buildPluginGuardLine(state);
|
|
14866
|
+
const { line, exitCode } = buildPluginGuardLine(state, { restartHint: reloadAction(surface) });
|
|
14573
14867
|
if (line) console.error(line);
|
|
14574
14868
|
if (token === "codex" && exitCode === 0) {
|
|
14575
14869
|
const trust = codexHookTrustState();
|
|
@@ -14582,6 +14876,10 @@ async function runGuard(readOrigin) {
|
|
|
14582
14876
|
if (surfaceToken(surface) === "codex") {
|
|
14583
14877
|
console.error("[mmi-guard] Could not inspect the active Codex plugin; run `mmi-cli plugin heal`.");
|
|
14584
14878
|
process.exitCode = 1;
|
|
14879
|
+
} else if (surfaceToken(surface) === "kilo" || surfaceToken(surface) === "cursor") {
|
|
14880
|
+
const host = surfaceToken(surface) === "cursor" ? "Cursor" : "Kilo";
|
|
14881
|
+
console.error(`[mmi-guard] Could not inspect the active ${host} plugin; run \`mmi-cli plugin heal\`.`);
|
|
14882
|
+
process.exitCode = 1;
|
|
14585
14883
|
} else {
|
|
14586
14884
|
process.exitCode = 0;
|
|
14587
14885
|
}
|
|
@@ -14589,21 +14887,24 @@ async function runGuard(readOrigin) {
|
|
|
14589
14887
|
}
|
|
14590
14888
|
async function runPluginHeal(surface = detectSurface(process.env)) {
|
|
14591
14889
|
const token = surfaceToken(surface);
|
|
14592
|
-
if (token !== "claude" && token !== "codex") {
|
|
14890
|
+
if (token !== "claude" && token !== "codex" && token !== "cursor" && token !== "kilo") {
|
|
14593
14891
|
console.log(nonClaudeSurfaceHealMessage(token ?? void 0));
|
|
14594
14892
|
return;
|
|
14595
14893
|
}
|
|
14596
14894
|
const descriptor = PLUGIN_SURFACE_HEAL[token];
|
|
14597
|
-
const
|
|
14598
|
-
|
|
14895
|
+
const cursorResult = token === "cursor" ? await installCursorPluginCheckout() : void 0;
|
|
14896
|
+
if (cursorResult) console.log(` \u21BB ${cursorResult.detail}`);
|
|
14897
|
+
const applied = cursorResult ? cursorResult.ok : await applyPluginHeal(surface, console.log, { force: true });
|
|
14898
|
+
const healed = token !== "claude" ? applied && buildPluginGuardDecision(snapshotPluginGuardInput(surface, true)).state === "healthy" : applied;
|
|
14599
14899
|
if (healed) {
|
|
14600
14900
|
const trust = token === "codex" ? " Then run /hooks and review + trust the MMI hooks." : "";
|
|
14601
|
-
|
|
14901
|
+
const reload = token === "kilo" ? " The plugin provisions the skills on first load." : "";
|
|
14902
|
+
console.log(` \u2713 MMI plugin reinstalled \u2014 ${reloadAction(surface)} to load MMI skills and commands.${trust}${reload}`);
|
|
14602
14903
|
} else {
|
|
14603
14904
|
process.exitCode = 1;
|
|
14604
|
-
const refSupported = await marketplaceAddRefSupported(token);
|
|
14605
|
-
const recovery = refSupported ? descriptor.recovery : recoveryWithoutRef(descriptor.recovery);
|
|
14606
|
-
const note = refAbsenceNote(token, refSupported);
|
|
14905
|
+
const refSupported = token === "kilo" || token === "cursor" ? false : await marketplaceAddRefSupported(token);
|
|
14906
|
+
const recovery = token === "kilo" || token === "cursor" ? descriptor.recovery : refSupported ? descriptor.recovery : recoveryWithoutRef(descriptor.recovery);
|
|
14907
|
+
const note = token === "kilo" || token === "cursor" ? "" : refAbsenceNote(token, refSupported);
|
|
14607
14908
|
console.log(` \u2717 Auto-heal failed or was skipped. Run manually:
|
|
14608
14909
|
${recovery}${note}${pluginReadGrantNote()}`);
|
|
14609
14910
|
}
|
|
@@ -16126,12 +16427,20 @@ function resolveIssueBody(input, deps) {
|
|
|
16126
16427
|
noun: "body"
|
|
16127
16428
|
});
|
|
16128
16429
|
}
|
|
16129
|
-
function resolveIssueTitle(input, deps) {
|
|
16130
|
-
|
|
16430
|
+
async function resolveIssueTitle(input, deps) {
|
|
16431
|
+
const title = await resolveTextArg({ value: input.title, file: input.titleFile }, deps, {
|
|
16131
16432
|
value: "--title",
|
|
16132
16433
|
file: "--title-file",
|
|
16133
16434
|
noun: "title"
|
|
16134
16435
|
});
|
|
16436
|
+
const normalized = title.replace(/(?:\r\n|\n|\r)$/, "");
|
|
16437
|
+
if (!normalized.trim()) {
|
|
16438
|
+
throw new TextArgError("--title produced an empty title", ERROR_CODES.ERR_EMPTY_INPUT, "--title");
|
|
16439
|
+
}
|
|
16440
|
+
if (/[\r\n]/.test(normalized)) {
|
|
16441
|
+
throw new TextArgError("--title must be one line; internal line breaks are not allowed", ERROR_CODES.ERR_INVALID_INPUT, "--title");
|
|
16442
|
+
}
|
|
16443
|
+
return normalized;
|
|
16135
16444
|
}
|
|
16136
16445
|
|
|
16137
16446
|
// src/issue-view-json.ts
|
|
@@ -16219,6 +16528,40 @@ var PATH_OVERRIDES = {
|
|
|
16219
16528
|
"secrets grant": { category: "admin", discovery: "all-only", help_group: "Operations" },
|
|
16220
16529
|
"secrets revoke": { category: "admin", discovery: "all-only", help_group: "Operations" }
|
|
16221
16530
|
};
|
|
16531
|
+
var COMMAND_OWNERSHIP = {
|
|
16532
|
+
onboard: { module_owner: "cli/src/discovery-commands.ts", consumer: "agent-session" },
|
|
16533
|
+
status: { module_owner: "cli/src/discovery-commands.ts", consumer: "agent-session" },
|
|
16534
|
+
next: { module_owner: "cli/src/discovery-commands.ts", consumer: "agent-session" },
|
|
16535
|
+
doctor: { module_owner: "cli/src/doctor-clean.ts", consumer: "agent-session" },
|
|
16536
|
+
whoami: { module_owner: "cli/src/whoami.ts", consumer: "agent-session" },
|
|
16537
|
+
commands: { module_owner: "cli/src/command-manifest.ts", consumer: "agent-session" },
|
|
16538
|
+
explain: { module_owner: "cli/src/explain-command.ts", consumer: "agent-session" },
|
|
16539
|
+
board: { module_owner: "cli/src/board-commands.ts", consumer: "agent-workflow" },
|
|
16540
|
+
issue: { module_owner: "cli/src/issue-commands.ts", consumer: "agent-workflow" },
|
|
16541
|
+
worktree: { module_owner: "cli/src/worktree-lifecycle-commands.ts", consumer: "agent-workflow" },
|
|
16542
|
+
stage: { module_owner: "cli/src/stage-commands.ts", consumer: "agent-workflow" },
|
|
16543
|
+
pr: { module_owner: "cli/src/pr-commands.ts", consumer: "agent-workflow" },
|
|
16544
|
+
ci: { module_owner: "cli/src/ci-audit.ts", consumer: "release-operator" },
|
|
16545
|
+
rcand: { module_owner: "cli/src/train-commands.ts", consumer: "release-operator" },
|
|
16546
|
+
release: { module_owner: "cli/src/train-commands.ts", consumer: "release-operator" },
|
|
16547
|
+
hotfix: { module_owner: "cli/src/hotfix-apply.ts", consumer: "release-operator" },
|
|
16548
|
+
train: { module_owner: "cli/src/train-commands.ts", consumer: "release-operator" },
|
|
16549
|
+
bootstrap: { module_owner: "cli/src/bootstrap-commands.ts", consumer: "repo-bootstrap" },
|
|
16550
|
+
secrets: { module_owner: "cli/src/secrets-commands.ts", consumer: "authenticated-operator" },
|
|
16551
|
+
docs: { module_owner: "cli/src/docs-index-command.ts", consumer: "repo-gates" },
|
|
16552
|
+
tests: { module_owner: "cli/src/test-policy-core.ts", consumer: "repo-gates" },
|
|
16553
|
+
wave: { module_owner: "cli/src/wave-land.ts", consumer: "campaign-orchestrator" },
|
|
16554
|
+
report: { module_owner: "cli/src/report.ts", consumer: "campaign-orchestrator" },
|
|
16555
|
+
"skill-lesson": { module_owner: "cli/src/skill-lesson.ts", consumer: "campaign-orchestrator" },
|
|
16556
|
+
org: { module_owner: "cli/src/command-consolidation.ts", consumer: "org-operator" },
|
|
16557
|
+
runtime: { module_owner: "cli/src/command-consolidation.ts", consumer: "runtime-operator" },
|
|
16558
|
+
plugin: { module_owner: "cli/src/plugin-guard-io.ts", consumer: "host-runtime" }
|
|
16559
|
+
};
|
|
16560
|
+
function commandOwnership(name) {
|
|
16561
|
+
const ownership = COMMAND_OWNERSHIP[name];
|
|
16562
|
+
if (!ownership) throw new Error(`command taxonomy: root "${name}" has no module owner or consumer`);
|
|
16563
|
+
return ownership;
|
|
16564
|
+
}
|
|
16222
16565
|
var DEFAULT_ADMIN_MARKER = "(master-only)";
|
|
16223
16566
|
var ADMIN_MARKERS = {
|
|
16224
16567
|
"secrets org-catalog": "(writes master-only)"
|
|
@@ -16233,7 +16576,8 @@ function primaryMetadata(name) {
|
|
|
16233
16576
|
return {
|
|
16234
16577
|
category: SUPPORT_PRIMARY.has(name) ? "support" : "core",
|
|
16235
16578
|
discovery: "primary",
|
|
16236
|
-
help_group: helpGroup
|
|
16579
|
+
help_group: helpGroup,
|
|
16580
|
+
...commandOwnership(name)
|
|
16237
16581
|
};
|
|
16238
16582
|
}
|
|
16239
16583
|
}
|
|
@@ -16248,7 +16592,8 @@ function topLevelMetadata(name) {
|
|
|
16248
16592
|
return {
|
|
16249
16593
|
category: name === "plugin" ? "internal" : "admin",
|
|
16250
16594
|
discovery: "all-only",
|
|
16251
|
-
help_group: "Operations"
|
|
16595
|
+
help_group: "Operations",
|
|
16596
|
+
...commandOwnership(name)
|
|
16252
16597
|
};
|
|
16253
16598
|
}
|
|
16254
16599
|
function setCommandMetadata(command, metadata) {
|
|
@@ -16265,7 +16610,7 @@ var REQUIRED_OPTION_HELP = {
|
|
|
16265
16610
|
};
|
|
16266
16611
|
function classifyTree(command, path2, inherited, hideFromParent) {
|
|
16267
16612
|
const override = PATH_OVERRIDES[path2];
|
|
16268
|
-
const metadata = override
|
|
16613
|
+
const metadata = override ? { ...inherited, ...override } : inherited;
|
|
16269
16614
|
setCommandMetadata(command, metadata);
|
|
16270
16615
|
const hideByOverride = override !== void 0 && metadata.discovery === "hidden";
|
|
16271
16616
|
if ((hideByOverride || hideFromParent) && metadata.discovery !== "primary") {
|
|
@@ -16401,7 +16746,9 @@ function buildCommand(cmd, path2) {
|
|
|
16401
16746
|
const metadata = commandMetadata(cmd) ?? {
|
|
16402
16747
|
category: "core",
|
|
16403
16748
|
discovery: "primary",
|
|
16404
|
-
help_group: "Plan and work"
|
|
16749
|
+
help_group: "Plan and work",
|
|
16750
|
+
module_owner: "unclassified",
|
|
16751
|
+
consumer: "unclassified"
|
|
16405
16752
|
};
|
|
16406
16753
|
const out = {
|
|
16407
16754
|
name: cmd.name(),
|
|
@@ -20960,7 +21307,7 @@ function reconcileGithubActions(liveGithub, registry2, readRepos, activeWorkflow
|
|
|
20960
21307
|
name: e.name,
|
|
20961
21308
|
executor: e.executor,
|
|
20962
21309
|
detail: "armed with no SCHEDULE# row",
|
|
20963
|
-
remedy: 'add the eight-field header (docs/schedules.md "The entry template") and re-run
|
|
21310
|
+
remedy: 'add the eight-field header (docs/schedules.md "The entry template") and re-run schedules register so it creates a SCHEDULE# row'
|
|
20964
21311
|
});
|
|
20965
21312
|
}
|
|
20966
21313
|
}
|
|
@@ -20973,7 +21320,7 @@ function reconcileGithubActions(liveGithub, registry2, readRepos, activeWorkflow
|
|
|
20973
21320
|
name: r.id,
|
|
20974
21321
|
executor: r.executor || live.executor,
|
|
20975
21322
|
detail: `live cadence \`${live.cadence}\` disagrees with registered \`${r.cadence}\``,
|
|
20976
|
-
remedy: "re-run
|
|
21323
|
+
remedy: "re-run schedules register to refresh the SCHEDULE# row from the current header/cron"
|
|
20977
21324
|
});
|
|
20978
21325
|
}
|
|
20979
21326
|
continue;
|
|
@@ -20985,7 +21332,7 @@ function reconcileGithubActions(liveGithub, registry2, readRepos, activeWorkflow
|
|
|
20985
21332
|
name: r.id,
|
|
20986
21333
|
executor: r.executor || "github-actions",
|
|
20987
21334
|
detail: "registered with no workflow file behind it",
|
|
20988
|
-
remedy: `restore the workflow in ${r.repo} (or, if it was retired on purpose, re-run
|
|
21335
|
+
remedy: `restore the workflow in ${r.repo} (or, if it was retired on purpose, re-run schedules register so the replace prunes the stale SCHEDULE# row)`
|
|
20989
21336
|
});
|
|
20990
21337
|
}
|
|
20991
21338
|
}
|
|
@@ -21433,27 +21780,27 @@ function scheduleRecordFromWorkflow(repo, workflowPath, yamlText) {
|
|
|
21433
21780
|
const expectedId = `${repo}/${basename4.replace(/\.ya?ml$/, "")}`;
|
|
21434
21781
|
const missing = SCHEDULE_HEADER_FIELDS.filter((f) => !header[f]);
|
|
21435
21782
|
if (missing.length) {
|
|
21436
|
-
throw new Error(`schedules
|
|
21783
|
+
throw new Error(`schedules register: ${expectedId} (${workflowPath}) is a scheduled workflow but is missing the eight-field entry-template header field(s): ${missing.join(", ")} \u2014 see docs/schedules.md "The entry template".`);
|
|
21437
21784
|
}
|
|
21438
21785
|
const h = header;
|
|
21439
21786
|
if (h.schedule !== expectedId) {
|
|
21440
|
-
throw new Error(`schedules
|
|
21787
|
+
throw new Error(`schedules register: ${workflowPath} header \`schedule: ${h.schedule}\` must equal the join key \`${expectedId}\` (<repo>/<workflow-basename>).`);
|
|
21441
21788
|
}
|
|
21442
21789
|
for (const cron of crons) {
|
|
21443
21790
|
if (!cadenceContainsCron(h.cadence, cron)) {
|
|
21444
|
-
throw new Error(`schedules
|
|
21791
|
+
throw new Error(`schedules register: ${expectedId} cadence \`${h.cadence}\` does not contain the workflow cron \`${cron}\` (whole-token comparison) \u2014 the declared cadence disagrees with the actual schedule.`);
|
|
21445
21792
|
}
|
|
21446
21793
|
}
|
|
21447
21794
|
if (llmFromHeader(yamlText) === "unknown") {
|
|
21448
|
-
throw new Error(`schedules
|
|
21795
|
+
throw new Error(`schedules register: ${expectedId} header \`llm: ${h.llm}\` does not classify (yes / no / embeddings) \u2014 an unreadable declaration would register a row whose LLM column lies.`);
|
|
21449
21796
|
}
|
|
21450
21797
|
const target = header.target;
|
|
21451
21798
|
if (target !== void 0 && !/^mmi-fleet-[A-Za-z0-9_-]{1,130}$/.test(target)) {
|
|
21452
|
-
throw new Error(`schedules
|
|
21799
|
+
throw new Error(`schedules register: ${expectedId} header \`target: ${target}\` must be a bare lambda function name in the reserved fleet namespace \`mmi-fleet-*\` (allowed after the prefix: A-Z a-z 0-9 _ -; total max 140). The dispatcher can only invoke mmi-fleet-* functions.`);
|
|
21453
21800
|
}
|
|
21454
21801
|
const model = header.model;
|
|
21455
21802
|
if (model !== void 0 && !/^[a-z][a-z0-9-]{0,30}$/.test(model)) {
|
|
21456
|
-
throw new Error(`schedules
|
|
21803
|
+
throw new Error(`schedules register: ${expectedId} header \`model: ${model}\` must be a model ROLE KEY (lowercase kebab, max 31 chars \u2014 e.g. janitor, arbiter), never a model id \u2014 ids resolve from the vault at dispatch.`);
|
|
21457
21804
|
}
|
|
21458
21805
|
return {
|
|
21459
21806
|
id: expectedId,
|
|
@@ -21478,7 +21825,7 @@ function scheduleRecordsFromWorkflows(repo, files) {
|
|
|
21478
21825
|
const rec = scheduleRecordFromWorkflow(repo, path2, text);
|
|
21479
21826
|
if (!rec) continue;
|
|
21480
21827
|
if (seen.has(rec.id)) {
|
|
21481
|
-
throw new Error(`schedules
|
|
21828
|
+
throw new Error(`schedules register: duplicate schedule id \`${rec.id}\` \u2014 two workflow files in ${repo} share one join key (${rec.sourcePath}).`);
|
|
21482
21829
|
}
|
|
21483
21830
|
seen.add(rec.id);
|
|
21484
21831
|
records.push(rec);
|
|
@@ -21530,16 +21877,16 @@ async function runSchedulesLift(opts, deps = {}) {
|
|
|
21530
21877
|
const url = await (deps.originUrl ?? (() => gitOut(["remote", "get-url", "origin"])))();
|
|
21531
21878
|
repo = repoNameFromRemoteUrl(url) ?? void 0;
|
|
21532
21879
|
if (!repo) {
|
|
21533
|
-
throw new Error("schedules
|
|
21880
|
+
throw new Error("schedules register: could not resolve the repo name from the origin remote \u2014 pass --repo <name>.");
|
|
21534
21881
|
}
|
|
21535
21882
|
}
|
|
21536
21883
|
if (!SCHEDULE_REPO_RE.test(repo)) {
|
|
21537
|
-
throw new SchedulesLiftUsageError(`schedules
|
|
21884
|
+
throw new SchedulesLiftUsageError(`schedules register: repo \`${repo}\` is not a bare repo segment (allowed: A-Z a-z 0-9 _ . -; no \`/\` or \`#\`) \u2014 the schedule id is <repo>/<name>, so a separator would form an invalid multi-segment id.`);
|
|
21538
21885
|
}
|
|
21539
21886
|
const dir = opts.dir ?? DEFAULT_WORKFLOWS_DIR;
|
|
21540
21887
|
const files = await (deps.readFiles ?? readWorkflowFiles)(dir);
|
|
21541
21888
|
if (!files.length) {
|
|
21542
|
-
throw new Error(`schedules
|
|
21889
|
+
throw new Error(`schedules register: no workflow files under ${dir} \u2014 refusing to post an empty registration (the route prunes; run from the repo checkout or pass --dir).`);
|
|
21543
21890
|
}
|
|
21544
21891
|
const records = scheduleRecordsFromWorkflows(repo, files);
|
|
21545
21892
|
const payload = { repo, schedules: records };
|
|
@@ -21548,11 +21895,11 @@ async function runSchedulesLift(opts, deps = {}) {
|
|
|
21548
21895
|
if (!response.ok) {
|
|
21549
21896
|
const unreachable = response.unreachable ?? (response.status === 404 ? "route-absent" : void 0);
|
|
21550
21897
|
if (unreachable) {
|
|
21551
|
-
throw new RegistryUnreachableError(`schedules
|
|
21898
|
+
throw new RegistryUnreachableError(`schedules register: registry unreachable (${unreachable}) \u2014 ${response.error ?? `HTTP ${response.status}`}`);
|
|
21552
21899
|
}
|
|
21553
|
-
if (response.error) throw new Error(`schedules
|
|
21900
|
+
if (response.error) throw new Error(`schedules register: ${response.error}`);
|
|
21554
21901
|
const detail = response.body?.error ?? "";
|
|
21555
|
-
throw new Error(`schedules
|
|
21902
|
+
throw new Error(`schedules register: HTTP ${response.status}${detail ? ` \u2014 ${detail}` : ""}`);
|
|
21556
21903
|
}
|
|
21557
21904
|
return { repo, records, payload, response };
|
|
21558
21905
|
}
|
|
@@ -21573,8 +21920,8 @@ function withArmingReport(body) {
|
|
|
21573
21920
|
}
|
|
21574
21921
|
function registerSchedulesLiftCommand(program3, deps = {}) {
|
|
21575
21922
|
const schedules = program3.commands.find((c) => c.name() === "schedules");
|
|
21576
|
-
if (!schedules) throw new Error("schedules
|
|
21577
|
-
jsonParity(schedules.command("register").
|
|
21923
|
+
if (!schedules) throw new Error("schedules register: registerSchedulesCommands must run first \u2014 register attaches to the `schedules` command");
|
|
21924
|
+
jsonParity(schedules.command("register").description(`register this repo's eight-field workflow headers as SCHEDULE# registry rows (C2, #3186; renamed from \`lift\`, #3219) \u2014 a per-repo replace; aborts the whole registration on any header violation; exits 75 when the registry is unreachable (#3187). Registering does NOT arm the clock: the fleet-clock reconciler arms the rows within ${FLEET_CLOCK_RECONCILE_WINDOW}, and the success JSON carries an \`arming\` block saying so (#3281)`).option("--repo <name>", "bare repo name for the join key (defaults to the origin remote basename, case-preserved)").option("--dir <path>", "workflows directory to register (defaults to .github/workflows)").option("--dry-run", "print the {repo, schedules} body that would be POSTed; never write")).action(async (o) => {
|
|
21578
21925
|
try {
|
|
21579
21926
|
const result = await runSchedulesLift({ repo: o.repo, dir: o.dir, dryRun: Boolean(o.dryRun) }, deps);
|
|
21580
21927
|
if (o.dryRun) {
|
|
@@ -23624,7 +23971,7 @@ function registerBoardCommands(program3) {
|
|
|
23624
23971
|
"Claim already assigns and moves Status to In Progress, so do not also board move it.",
|
|
23625
23972
|
"Multiple refs are handled as a batch and return per-item results."
|
|
23626
23973
|
]);
|
|
23627
|
-
board.command("show <issue>").
|
|
23974
|
+
board.command("show <issue>").description("print one board item (status, assignees, type, url) with its body and comments").option("--json", "machine-readable output").option("--repo <owner/repo>", "current repo for local issue numbers (defaults to git origin)").option("--allow-partial", "return the item even if its body/comments fetch fails").action(async (issueRef, o) => {
|
|
23628
23975
|
try {
|
|
23629
23976
|
const item = await showBoardItem({ config: await loadConfigForBoardSelector2(issueRef, o.repo), selector: issueRef, repo: o.repo, allowPartial: o.allowPartial });
|
|
23630
23977
|
console.log(o.json ? JSON.stringify(item) : renderBoardItem(item));
|
|
@@ -26971,6 +27318,784 @@ function doctorReportExitCode(checks) {
|
|
|
26971
27318
|
return checks.some((c) => !c.ok && !c.reportOnly) ? 1 : 0;
|
|
26972
27319
|
}
|
|
26973
27320
|
|
|
27321
|
+
// ../surfaces.json
|
|
27322
|
+
var surfaces_default = {
|
|
27323
|
+
_comment: "Hub-owned agent/developer contract. sharedAgentCore owns policy and capability sources once; agentSurfaces contains only host delivery differences; surfaces inventories and assembles the outward artifacts those adapters reference. See docs/surfaces.schema.json (v7) and docs/surface-delivery-template.md.",
|
|
27324
|
+
schemaVersion: 7,
|
|
27325
|
+
productScope: "agentic-coding",
|
|
27326
|
+
sharedAgentCore: {
|
|
27327
|
+
skills: {
|
|
27328
|
+
ownerPath: "skills",
|
|
27329
|
+
sourceSurfaceId: "mmi-skills"
|
|
27330
|
+
},
|
|
27331
|
+
cli: {
|
|
27332
|
+
ownerPath: "cli",
|
|
27333
|
+
surfaceId: "mmi-cli"
|
|
27334
|
+
},
|
|
27335
|
+
hookPolicy: {
|
|
27336
|
+
launcherPath: "bin/mmi-hook",
|
|
27337
|
+
ownerPath: "scripts/hook-policy.mjs",
|
|
27338
|
+
runnerPath: "scripts/hook-run.mjs",
|
|
27339
|
+
sourceSurfaceId: "mmi-hook-policy",
|
|
27340
|
+
version: 1,
|
|
27341
|
+
windowsLauncherPath: "bin/mmi-hook.exe"
|
|
27342
|
+
},
|
|
27343
|
+
releaseMetadata: {
|
|
27344
|
+
ownerPath: "surfaces.json",
|
|
27345
|
+
bomPath: "distribution-bom.json"
|
|
27346
|
+
}
|
|
27347
|
+
},
|
|
27348
|
+
agentSurfaces: [
|
|
27349
|
+
{
|
|
27350
|
+
token: "claude",
|
|
27351
|
+
displayName: "Claude Code",
|
|
27352
|
+
lifecycle: "active",
|
|
27353
|
+
artifactIds: ["mmi-claude-plugin", "mmi-claude-marketplace", "mmi-skills", "mmi-hook-policy", "mmi-hooks", "mmi-cli"],
|
|
27354
|
+
plannedArtifactIds: [],
|
|
27355
|
+
assembly: {
|
|
27356
|
+
mode: "source-tree",
|
|
27357
|
+
rootPath: ".",
|
|
27358
|
+
sync: []
|
|
27359
|
+
},
|
|
27360
|
+
install: {
|
|
27361
|
+
mechanism: "marketplace",
|
|
27362
|
+
locator: "mmi@mutmutco"
|
|
27363
|
+
},
|
|
27364
|
+
upgrade: {
|
|
27365
|
+
mechanism: "reinstall",
|
|
27366
|
+
reload: "session"
|
|
27367
|
+
},
|
|
27368
|
+
skills: {
|
|
27369
|
+
delivery: "plugin",
|
|
27370
|
+
sourceSurfaceId: "mmi-skills",
|
|
27371
|
+
artifactId: "mmi-skills",
|
|
27372
|
+
invocation: {
|
|
27373
|
+
entry: "/mmi:mmi",
|
|
27374
|
+
any: "/mmi:<skill>"
|
|
27375
|
+
}
|
|
27376
|
+
},
|
|
27377
|
+
hooks: {
|
|
27378
|
+
adapterPath: "hooks/hooks.json",
|
|
27379
|
+
sourceSurfaceId: "mmi-hook-policy",
|
|
27380
|
+
gates: [
|
|
27381
|
+
{ id: "command-ladder", failure: "closed" },
|
|
27382
|
+
{ id: "vault-edit", failure: "closed" },
|
|
27383
|
+
{ id: "secret-output", failure: "open" }
|
|
27384
|
+
],
|
|
27385
|
+
probe: "installed-contract",
|
|
27386
|
+
execution: "command",
|
|
27387
|
+
preToolUse: "enforced",
|
|
27388
|
+
postToolOutput: "rewrite",
|
|
27389
|
+
finalOutput: "unsupported"
|
|
27390
|
+
},
|
|
27391
|
+
cli: {
|
|
27392
|
+
delivery: "bundled",
|
|
27393
|
+
artifactId: "mmi-cli"
|
|
27394
|
+
},
|
|
27395
|
+
ownership: {
|
|
27396
|
+
trust: "host",
|
|
27397
|
+
cache: "host",
|
|
27398
|
+
repair: "mmi-cli"
|
|
27399
|
+
},
|
|
27400
|
+
certification: {
|
|
27401
|
+
hostCommand: "claude",
|
|
27402
|
+
versionArgs: ["--version"]
|
|
27403
|
+
},
|
|
27404
|
+
enforcementCeilings: [
|
|
27405
|
+
"Final assistant text cannot be rewritten by the hook surface.",
|
|
27406
|
+
"Disabled or untrusted hooks enforce nothing."
|
|
27407
|
+
]
|
|
27408
|
+
},
|
|
27409
|
+
{
|
|
27410
|
+
token: "codex",
|
|
27411
|
+
displayName: "Codex",
|
|
27412
|
+
lifecycle: "active",
|
|
27413
|
+
artifactIds: ["mmi-codex-plugin", "mmi-skills", "mmi-hook-policy", "mmi-cli"],
|
|
27414
|
+
plannedArtifactIds: [],
|
|
27415
|
+
assembly: {
|
|
27416
|
+
mode: "source-tree",
|
|
27417
|
+
rootPath: ".",
|
|
27418
|
+
sync: []
|
|
27419
|
+
},
|
|
27420
|
+
install: {
|
|
27421
|
+
mechanism: "marketplace",
|
|
27422
|
+
locator: "mmi@mutmutco"
|
|
27423
|
+
},
|
|
27424
|
+
upgrade: {
|
|
27425
|
+
mechanism: "marketplace-upgrade",
|
|
27426
|
+
reload: "session"
|
|
27427
|
+
},
|
|
27428
|
+
skills: {
|
|
27429
|
+
delivery: "plugin",
|
|
27430
|
+
sourceSurfaceId: "mmi-skills",
|
|
27431
|
+
artifactId: "mmi-skills",
|
|
27432
|
+
invocation: {
|
|
27433
|
+
entry: "$mmi:mmi",
|
|
27434
|
+
any: "$mmi:<skill>"
|
|
27435
|
+
}
|
|
27436
|
+
},
|
|
27437
|
+
hooks: {
|
|
27438
|
+
adapterPath: "hooks/codex-hooks.json",
|
|
27439
|
+
sourceSurfaceId: "mmi-hook-policy",
|
|
27440
|
+
gates: [
|
|
27441
|
+
{ id: "command-ladder", failure: "closed" },
|
|
27442
|
+
{ id: "vault-edit", failure: "closed" },
|
|
27443
|
+
{ id: "secret-output", failure: "open" }
|
|
27444
|
+
],
|
|
27445
|
+
probe: "installed-contract",
|
|
27446
|
+
execution: "node-bootstrap",
|
|
27447
|
+
preToolUse: "enforced",
|
|
27448
|
+
postToolOutput: "detect-only",
|
|
27449
|
+
finalOutput: "unsupported"
|
|
27450
|
+
},
|
|
27451
|
+
cli: {
|
|
27452
|
+
delivery: "standalone",
|
|
27453
|
+
artifactId: "mmi-cli"
|
|
27454
|
+
},
|
|
27455
|
+
ownership: {
|
|
27456
|
+
trust: "operator",
|
|
27457
|
+
cache: "host",
|
|
27458
|
+
repair: "mmi-cli"
|
|
27459
|
+
},
|
|
27460
|
+
certification: {
|
|
27461
|
+
hostCommand: "codex",
|
|
27462
|
+
versionArgs: ["--version"]
|
|
27463
|
+
},
|
|
27464
|
+
enforcementCeilings: [
|
|
27465
|
+
"Post-tool hooks can detect secret output but cannot rewrite it.",
|
|
27466
|
+
"Hosted tools and disabled or untrusted hooks bypass local enforcement."
|
|
27467
|
+
]
|
|
27468
|
+
},
|
|
27469
|
+
{
|
|
27470
|
+
token: "kimi",
|
|
27471
|
+
displayName: "Kimi Code CLI",
|
|
27472
|
+
lifecycle: "active",
|
|
27473
|
+
artifactIds: ["mmi-kimi-plugin", "mmi-skills", "mmi-hook-policy", "mmi-cli"],
|
|
27474
|
+
plannedArtifactIds: [],
|
|
27475
|
+
assembly: {
|
|
27476
|
+
mode: "source-tree",
|
|
27477
|
+
rootPath: ".",
|
|
27478
|
+
sync: []
|
|
27479
|
+
},
|
|
27480
|
+
install: {
|
|
27481
|
+
mechanism: "github-source",
|
|
27482
|
+
locator: "https://github.com/mutmutco/MMI-Hub"
|
|
27483
|
+
},
|
|
27484
|
+
upgrade: {
|
|
27485
|
+
mechanism: "reinstall",
|
|
27486
|
+
reload: "session"
|
|
27487
|
+
},
|
|
27488
|
+
skills: {
|
|
27489
|
+
delivery: "plugin",
|
|
27490
|
+
sourceSurfaceId: "mmi-skills",
|
|
27491
|
+
artifactId: "mmi-skills",
|
|
27492
|
+
invocation: {
|
|
27493
|
+
entry: "/skill:mmi",
|
|
27494
|
+
any: "/skill:<skill>"
|
|
27495
|
+
}
|
|
27496
|
+
},
|
|
27497
|
+
hooks: {
|
|
27498
|
+
adapterPath: ".kimi-plugin/plugin.json",
|
|
27499
|
+
sourceSurfaceId: "mmi-hook-policy",
|
|
27500
|
+
gates: [
|
|
27501
|
+
{ id: "command-ladder", failure: "closed" },
|
|
27502
|
+
{ id: "vault-edit", failure: "closed" },
|
|
27503
|
+
{ id: "secret-output", failure: "open" }
|
|
27504
|
+
],
|
|
27505
|
+
probe: "installed-contract",
|
|
27506
|
+
execution: "inline-manifest",
|
|
27507
|
+
preToolUse: "enforced",
|
|
27508
|
+
postToolOutput: "detect-only",
|
|
27509
|
+
finalOutput: "unsupported"
|
|
27510
|
+
},
|
|
27511
|
+
cli: {
|
|
27512
|
+
delivery: "standalone",
|
|
27513
|
+
artifactId: "mmi-cli"
|
|
27514
|
+
},
|
|
27515
|
+
ownership: {
|
|
27516
|
+
trust: "host",
|
|
27517
|
+
cache: "host",
|
|
27518
|
+
repair: "operator"
|
|
27519
|
+
},
|
|
27520
|
+
certification: {
|
|
27521
|
+
hostCommand: "kimi",
|
|
27522
|
+
versionArgs: ["--version"]
|
|
27523
|
+
},
|
|
27524
|
+
enforcementCeilings: [
|
|
27525
|
+
"Post-tool hooks are observation-only and cannot rewrite secret output.",
|
|
27526
|
+
"Hook errors, timeouts, disabled plugins, and untrusted plugins enforce nothing."
|
|
27527
|
+
]
|
|
27528
|
+
},
|
|
27529
|
+
{
|
|
27530
|
+
token: "cursor",
|
|
27531
|
+
displayName: "Cursor",
|
|
27532
|
+
lifecycle: "active",
|
|
27533
|
+
artifactIds: ["mmi-cursor-plugin", "mmi-skills", "mmi-hook-policy", "mmi-cli"],
|
|
27534
|
+
plannedArtifactIds: [],
|
|
27535
|
+
assembly: {
|
|
27536
|
+
mode: "source-tree",
|
|
27537
|
+
rootPath: ".",
|
|
27538
|
+
sync: []
|
|
27539
|
+
},
|
|
27540
|
+
install: {
|
|
27541
|
+
mechanism: "github-source",
|
|
27542
|
+
locator: "~/.cursor/plugins/local/mmi"
|
|
27543
|
+
},
|
|
27544
|
+
upgrade: {
|
|
27545
|
+
mechanism: "reinstall",
|
|
27546
|
+
reload: "workspace"
|
|
27547
|
+
},
|
|
27548
|
+
skills: {
|
|
27549
|
+
delivery: "plugin",
|
|
27550
|
+
sourceSurfaceId: "mmi-skills",
|
|
27551
|
+
artifactId: "mmi-skills",
|
|
27552
|
+
invocation: {
|
|
27553
|
+
entry: "/mmi",
|
|
27554
|
+
any: "/<skill>"
|
|
27555
|
+
}
|
|
27556
|
+
},
|
|
27557
|
+
hooks: {
|
|
27558
|
+
adapterPath: "hooks/cursor-hooks.json",
|
|
27559
|
+
sourceSurfaceId: "mmi-hook-policy",
|
|
27560
|
+
gates: [
|
|
27561
|
+
{ id: "command-ladder", failure: "closed" },
|
|
27562
|
+
{ id: "vault-edit", failure: "closed" },
|
|
27563
|
+
{ id: "secret-output", failure: "open" }
|
|
27564
|
+
],
|
|
27565
|
+
probe: "installed-contract",
|
|
27566
|
+
execution: "command",
|
|
27567
|
+
preToolUse: "enforced",
|
|
27568
|
+
postToolOutput: "detect-only",
|
|
27569
|
+
finalOutput: "unsupported"
|
|
27570
|
+
},
|
|
27571
|
+
cli: {
|
|
27572
|
+
delivery: "standalone",
|
|
27573
|
+
artifactId: "mmi-cli"
|
|
27574
|
+
},
|
|
27575
|
+
ownership: {
|
|
27576
|
+
trust: "host",
|
|
27577
|
+
cache: "mmi-cli",
|
|
27578
|
+
repair: "mmi-cli"
|
|
27579
|
+
},
|
|
27580
|
+
certification: {
|
|
27581
|
+
hostCommand: "cursor-agent",
|
|
27582
|
+
versionArgs: ["--version"]
|
|
27583
|
+
},
|
|
27584
|
+
enforcementCeilings: [
|
|
27585
|
+
"Cursor can replace MCP results only; it cannot rewrite ordinary tool output after execution, though MMI still detects secret-shaped output.",
|
|
27586
|
+
"Final assistant output is outside Cursor hook control.",
|
|
27587
|
+
"Cloud agents do not run sessionStart or sessionEnd hooks, and disabled plugins or untrusted workspaces enforce nothing."
|
|
27588
|
+
]
|
|
27589
|
+
},
|
|
27590
|
+
{
|
|
27591
|
+
token: "kilo",
|
|
27592
|
+
displayName: "Kilo Code",
|
|
27593
|
+
lifecycle: "active",
|
|
27594
|
+
artifactIds: ["mmi-kilo-plugin", "mmi-kilo-skills", "mmi-hook-policy", "mmi-cli"],
|
|
27595
|
+
plannedArtifactIds: [],
|
|
27596
|
+
assembly: {
|
|
27597
|
+
mode: "package-directory",
|
|
27598
|
+
rootPath: ".kilo-plugin",
|
|
27599
|
+
sync: [
|
|
27600
|
+
{
|
|
27601
|
+
mode: "directories",
|
|
27602
|
+
sourcePath: "skills",
|
|
27603
|
+
targetPath: ".kilo-plugin/skills",
|
|
27604
|
+
excludeNamePrefixes: ["_"]
|
|
27605
|
+
},
|
|
27606
|
+
{
|
|
27607
|
+
mode: "files",
|
|
27608
|
+
sourcePath: "scripts",
|
|
27609
|
+
targetPath: ".kilo-plugin/scripts",
|
|
27610
|
+
include: [
|
|
27611
|
+
"pretooluse-shell-gates.mjs",
|
|
27612
|
+
"vault-edit-gate.mjs",
|
|
27613
|
+
"secret-redact.mjs",
|
|
27614
|
+
"deny-gate-crash.mjs",
|
|
27615
|
+
"secret-echo-lint.mjs",
|
|
27616
|
+
"env-write-lint.mjs",
|
|
27617
|
+
"command-ladder-gate.mjs",
|
|
27618
|
+
"command-ladder-core.mjs",
|
|
27619
|
+
"validate-hook.mjs",
|
|
27620
|
+
"hook-io.mjs",
|
|
27621
|
+
"hook-trace.mjs",
|
|
27622
|
+
"edit-tool-paths.mjs",
|
|
27623
|
+
"throttle-core.mjs",
|
|
27624
|
+
"hook-policy.mjs",
|
|
27625
|
+
"hook-run.mjs"
|
|
27626
|
+
]
|
|
27627
|
+
}
|
|
27628
|
+
]
|
|
27629
|
+
},
|
|
27630
|
+
install: {
|
|
27631
|
+
mechanism: "npm",
|
|
27632
|
+
locator: "@mutmutco/kilo-plugin"
|
|
27633
|
+
},
|
|
27634
|
+
upgrade: {
|
|
27635
|
+
mechanism: "package-manager",
|
|
27636
|
+
reload: "session"
|
|
27637
|
+
},
|
|
27638
|
+
skills: {
|
|
27639
|
+
delivery: "provisioned",
|
|
27640
|
+
sourceSurfaceId: "mmi-skills",
|
|
27641
|
+
artifactId: "mmi-kilo-skills",
|
|
27642
|
+
invocation: {
|
|
27643
|
+
entry: "mmi skill via the skill tool",
|
|
27644
|
+
any: "skill tool"
|
|
27645
|
+
}
|
|
27646
|
+
},
|
|
27647
|
+
hooks: {
|
|
27648
|
+
adapterPath: ".kilo-plugin/server.mjs",
|
|
27649
|
+
sourceSurfaceId: "mmi-hook-policy",
|
|
27650
|
+
gates: [
|
|
27651
|
+
{ id: "command-ladder", failure: "closed" },
|
|
27652
|
+
{ id: "vault-edit", failure: "closed" },
|
|
27653
|
+
{ id: "secret-output", failure: "open" }
|
|
27654
|
+
],
|
|
27655
|
+
probe: "installed-contract",
|
|
27656
|
+
execution: "in-process",
|
|
27657
|
+
preToolUse: "enforced",
|
|
27658
|
+
postToolOutput: "rewrite",
|
|
27659
|
+
finalOutput: "rewrite"
|
|
27660
|
+
},
|
|
27661
|
+
cli: {
|
|
27662
|
+
delivery: "standalone",
|
|
27663
|
+
artifactId: "mmi-cli"
|
|
27664
|
+
},
|
|
27665
|
+
ownership: {
|
|
27666
|
+
trust: "host",
|
|
27667
|
+
cache: "host",
|
|
27668
|
+
repair: "mmi-cli"
|
|
27669
|
+
},
|
|
27670
|
+
certification: {
|
|
27671
|
+
hostCommand: "kilo",
|
|
27672
|
+
versionArgs: ["--version"]
|
|
27673
|
+
},
|
|
27674
|
+
enforcementCeilings: [
|
|
27675
|
+
"Some read, write, and web-fetch tool outputs are not rewriteable by the host.",
|
|
27676
|
+
"Provisioned skills and agents become discoverable only after a fresh session."
|
|
27677
|
+
]
|
|
27678
|
+
}
|
|
27679
|
+
],
|
|
27680
|
+
surfaces: [
|
|
27681
|
+
{
|
|
27682
|
+
id: "mmi-skills",
|
|
27683
|
+
classification: "capability",
|
|
27684
|
+
kind: "skills",
|
|
27685
|
+
ownerPath: "skills",
|
|
27686
|
+
deliveryPath: "skills",
|
|
27687
|
+
delivery: "plugin-install",
|
|
27688
|
+
applicability: "plugin-enabled agent surfaces",
|
|
27689
|
+
versionCoordinated: false,
|
|
27690
|
+
artifactIdentity: {
|
|
27691
|
+
kind: "sha256-tree",
|
|
27692
|
+
paths: ["skills"]
|
|
27693
|
+
},
|
|
27694
|
+
verify: [
|
|
27695
|
+
{
|
|
27696
|
+
command: "node",
|
|
27697
|
+
args: ["scripts/check-skill-payload.mjs", "--contract"],
|
|
27698
|
+
expected: "skill payload check: ok"
|
|
27699
|
+
}
|
|
27700
|
+
],
|
|
27701
|
+
publishVisibility: "public"
|
|
27702
|
+
},
|
|
27703
|
+
{
|
|
27704
|
+
id: "mmi-claude-plugin",
|
|
27705
|
+
classification: "packaging",
|
|
27706
|
+
kind: "plugin",
|
|
27707
|
+
ownerPath: ".claude-plugin/plugin.json",
|
|
27708
|
+
deliveryPath: ".claude-plugin/plugin.json",
|
|
27709
|
+
delivery: "release",
|
|
27710
|
+
applicability: "Claude Code plugin marketplace",
|
|
27711
|
+
versionCoordinated: true,
|
|
27712
|
+
surfaceToken: "claude",
|
|
27713
|
+
versionPaths: [
|
|
27714
|
+
{ path: ".claude-plugin/plugin.json", pointer: "version" }
|
|
27715
|
+
],
|
|
27716
|
+
artifactIdentity: {
|
|
27717
|
+
kind: "sha256-tree",
|
|
27718
|
+
paths: [".claude-plugin/plugin.json", "skills", "hooks", "scripts", "bin"]
|
|
27719
|
+
},
|
|
27720
|
+
publishVisibility: "public"
|
|
27721
|
+
},
|
|
27722
|
+
{
|
|
27723
|
+
id: "mmi-codex-plugin",
|
|
27724
|
+
classification: "packaging",
|
|
27725
|
+
kind: "plugin",
|
|
27726
|
+
ownerPath: ".codex-plugin/plugin.json",
|
|
27727
|
+
deliveryPath: ".codex-plugin/plugin.json",
|
|
27728
|
+
delivery: "release",
|
|
27729
|
+
applicability: "Codex plugin marketplace; carries the MMI lifecycle hooks via hooks/codex-hooks.json (#3563)",
|
|
27730
|
+
versionCoordinated: true,
|
|
27731
|
+
surfaceToken: "codex",
|
|
27732
|
+
versionPaths: [
|
|
27733
|
+
{ path: ".codex-plugin/plugin.json", pointer: "version" }
|
|
27734
|
+
],
|
|
27735
|
+
artifactIdentity: {
|
|
27736
|
+
kind: "sha256-tree",
|
|
27737
|
+
paths: [".codex-plugin/plugin.json", "skills", "hooks/codex-hooks.json", "scripts", "bin"]
|
|
27738
|
+
},
|
|
27739
|
+
publishVisibility: "public"
|
|
27740
|
+
},
|
|
27741
|
+
{
|
|
27742
|
+
id: "mmi-kimi-plugin",
|
|
27743
|
+
classification: "packaging",
|
|
27744
|
+
kind: "plugin",
|
|
27745
|
+
ownerPath: ".kimi-plugin/plugin.json",
|
|
27746
|
+
deliveryPath: ".kimi-plugin/plugin.json",
|
|
27747
|
+
delivery: "release",
|
|
27748
|
+
applicability: "Kimi Code CLI plugin install (GitHub source); inline event mapping invokes the shared hook runner",
|
|
27749
|
+
versionCoordinated: true,
|
|
27750
|
+
surfaceToken: "kimi",
|
|
27751
|
+
versionPaths: [
|
|
27752
|
+
{ path: ".kimi-plugin/plugin.json", pointer: "version" }
|
|
27753
|
+
],
|
|
27754
|
+
artifactIdentity: {
|
|
27755
|
+
kind: "sha256-tree",
|
|
27756
|
+
paths: [".kimi-plugin/plugin.json", "skills", "scripts", "bin"]
|
|
27757
|
+
},
|
|
27758
|
+
publishVisibility: "public"
|
|
27759
|
+
},
|
|
27760
|
+
{
|
|
27761
|
+
id: "mmi-cursor-plugin",
|
|
27762
|
+
classification: "packaging",
|
|
27763
|
+
kind: "plugin",
|
|
27764
|
+
ownerPath: ".cursor-plugin/plugin.json",
|
|
27765
|
+
deliveryPath: "~/.cursor/plugins/local/mmi",
|
|
27766
|
+
delivery: "local-copy",
|
|
27767
|
+
applicability: "Cursor IDE and Cursor Agent CLI; local plugin checkout managed by mmi-cli",
|
|
27768
|
+
versionCoordinated: true,
|
|
27769
|
+
surfaceToken: "cursor",
|
|
27770
|
+
versionPaths: [
|
|
27771
|
+
{ path: ".cursor-plugin/plugin.json", pointer: "version" }
|
|
27772
|
+
],
|
|
27773
|
+
artifactIdentity: {
|
|
27774
|
+
kind: "sha256-tree",
|
|
27775
|
+
paths: [".cursor-plugin/plugin.json", "skills", "hooks/cursor-hooks.json", "scripts", "bin"]
|
|
27776
|
+
},
|
|
27777
|
+
publishVisibility: "public"
|
|
27778
|
+
},
|
|
27779
|
+
{
|
|
27780
|
+
id: "mmi-kilo-plugin",
|
|
27781
|
+
classification: "packaging",
|
|
27782
|
+
kind: "plugin",
|
|
27783
|
+
ownerPath: ".kilo-plugin/package.json",
|
|
27784
|
+
deliveryPath: ".kilo-plugin/package.json",
|
|
27785
|
+
delivery: "npm",
|
|
27786
|
+
applicability: "Kilo Code extension + Kilo CLI; npm package @mutmutco/kilo-plugin (kilo-p1), installed via `kilo plugin @mutmutco/kilo-plugin`",
|
|
27787
|
+
versionCoordinated: true,
|
|
27788
|
+
surfaceToken: "kilo",
|
|
27789
|
+
versionPaths: [
|
|
27790
|
+
{ path: ".kilo-plugin/package.json", pointer: "version" }
|
|
27791
|
+
],
|
|
27792
|
+
additionalPaths: [
|
|
27793
|
+
".kilo-plugin/server.mjs",
|
|
27794
|
+
".kilo-plugin/skills",
|
|
27795
|
+
".kilo-plugin/scripts"
|
|
27796
|
+
],
|
|
27797
|
+
artifactIdentity: {
|
|
27798
|
+
kind: "npm-pack",
|
|
27799
|
+
packagePath: ".kilo-plugin"
|
|
27800
|
+
},
|
|
27801
|
+
publishVisibility: "public"
|
|
27802
|
+
},
|
|
27803
|
+
{
|
|
27804
|
+
id: "mmi-kilo-skills",
|
|
27805
|
+
classification: "capability",
|
|
27806
|
+
kind: "skills",
|
|
27807
|
+
ownerPath: "skills",
|
|
27808
|
+
deliveryPath: "skills",
|
|
27809
|
+
delivery: "plugin-install",
|
|
27810
|
+
applicability: "Kilo Code (via @mutmutco/kilo-plugin provisioning to ~/.kilo/skills)",
|
|
27811
|
+
versionCoordinated: false,
|
|
27812
|
+
artifactIdentity: {
|
|
27813
|
+
kind: "sha256-tree",
|
|
27814
|
+
paths: [".kilo-plugin/skills"]
|
|
27815
|
+
},
|
|
27816
|
+
surfaceToken: "kilo",
|
|
27817
|
+
publishVisibility: "public"
|
|
27818
|
+
},
|
|
27819
|
+
{
|
|
27820
|
+
id: "mmi-claude-marketplace",
|
|
27821
|
+
classification: "delivery",
|
|
27822
|
+
kind: "marketplace",
|
|
27823
|
+
ownerPath: ".claude-plugin/marketplace.json",
|
|
27824
|
+
deliveryPath: ".claude-plugin/marketplace.json",
|
|
27825
|
+
delivery: "release",
|
|
27826
|
+
applicability: "Claude Code plugin marketplace",
|
|
27827
|
+
versionCoordinated: true,
|
|
27828
|
+
surfaceToken: "claude",
|
|
27829
|
+
versionPaths: [
|
|
27830
|
+
{ path: ".claude-plugin/marketplace.json", pointer: "plugins.0.version" },
|
|
27831
|
+
{ path: ".claude-plugin/marketplace.json", pointer: "plugins.0.displayName", template: "MMI {version}" }
|
|
27832
|
+
],
|
|
27833
|
+
artifactIdentity: {
|
|
27834
|
+
kind: "sha256-tree",
|
|
27835
|
+
paths: [".claude-plugin/marketplace.json"]
|
|
27836
|
+
},
|
|
27837
|
+
publishVisibility: "public"
|
|
27838
|
+
},
|
|
27839
|
+
{
|
|
27840
|
+
id: "mmi-cli",
|
|
27841
|
+
classification: "capability",
|
|
27842
|
+
kind: "cli",
|
|
27843
|
+
ownerPath: "cli/package.json",
|
|
27844
|
+
deliveryPath: "cli/package.json",
|
|
27845
|
+
delivery: "npm",
|
|
27846
|
+
applicability: "editor-agnostic agent surfaces",
|
|
27847
|
+
versionCoordinated: true,
|
|
27848
|
+
versionPaths: [
|
|
27849
|
+
{ path: "cli/package.json", pointer: "version" }
|
|
27850
|
+
],
|
|
27851
|
+
additionalPaths: ["cli/dist"],
|
|
27852
|
+
artifactIdentity: {
|
|
27853
|
+
kind: "npm-pack",
|
|
27854
|
+
packagePath: "cli"
|
|
27855
|
+
},
|
|
27856
|
+
publishVisibility: "public"
|
|
27857
|
+
},
|
|
27858
|
+
{
|
|
27859
|
+
id: "mmi-cli-lock",
|
|
27860
|
+
classification: "packaging",
|
|
27861
|
+
kind: "package-lock",
|
|
27862
|
+
ownerPath: "cli/package-lock.json",
|
|
27863
|
+
deliveryPath: "cli/package-lock.json",
|
|
27864
|
+
delivery: "npm",
|
|
27865
|
+
applicability: "editor-agnostic agent surfaces",
|
|
27866
|
+
versionCoordinated: true,
|
|
27867
|
+
versionPaths: [
|
|
27868
|
+
{ path: "cli/package-lock.json", pointer: "version" },
|
|
27869
|
+
{ path: "cli/package-lock.json", pointer: "packages.$root.version" }
|
|
27870
|
+
],
|
|
27871
|
+
artifactIdentity: {
|
|
27872
|
+
kind: "sha256-tree",
|
|
27873
|
+
paths: ["cli/package-lock.json"]
|
|
27874
|
+
},
|
|
27875
|
+
publishVisibility: "public"
|
|
27876
|
+
},
|
|
27877
|
+
{
|
|
27878
|
+
id: "mmi-cli-dist",
|
|
27879
|
+
classification: "packaging",
|
|
27880
|
+
kind: "bundle",
|
|
27881
|
+
ownerPath: "cli/dist/index.cjs",
|
|
27882
|
+
deliveryPath: "cli/dist/index.cjs",
|
|
27883
|
+
delivery: "npm",
|
|
27884
|
+
applicability: "editor-agnostic agent surfaces",
|
|
27885
|
+
versionCoordinated: true,
|
|
27886
|
+
versionPaths: [
|
|
27887
|
+
{ path: "cli/package.json", pointer: "version" }
|
|
27888
|
+
],
|
|
27889
|
+
additionalPaths: ["cli/dist/main.cjs"],
|
|
27890
|
+
prepare: [
|
|
27891
|
+
{
|
|
27892
|
+
command: "npm",
|
|
27893
|
+
args: ["--prefix", "cli", "run", "build"],
|
|
27894
|
+
inputs: ["cli/src", "cli/build.mjs", "cli/package.json", "cli/README.md", "cli/tsconfig.json"],
|
|
27895
|
+
outputs: ["cli/dist/index.cjs", "cli/dist/main.cjs"]
|
|
27896
|
+
}
|
|
27897
|
+
],
|
|
27898
|
+
verify: [
|
|
27899
|
+
{
|
|
27900
|
+
command: "node",
|
|
27901
|
+
args: ["cli/dist/index.cjs", "--version"],
|
|
27902
|
+
expected: "{version}"
|
|
27903
|
+
}
|
|
27904
|
+
],
|
|
27905
|
+
artifactIdentity: {
|
|
27906
|
+
kind: "sha256-tree",
|
|
27907
|
+
paths: ["cli/dist"]
|
|
27908
|
+
},
|
|
27909
|
+
publishVisibility: "public"
|
|
27910
|
+
},
|
|
27911
|
+
{
|
|
27912
|
+
id: "mmi-hook-policy",
|
|
27913
|
+
classification: "guard",
|
|
27914
|
+
kind: "hooks",
|
|
27915
|
+
ownerPath: "scripts/hook-policy.mjs",
|
|
27916
|
+
deliveryPath: "scripts/hook-policy.mjs",
|
|
27917
|
+
delivery: "plugin-install",
|
|
27918
|
+
applicability: "all five active hook-capable agent surfaces",
|
|
27919
|
+
versionCoordinated: false,
|
|
27920
|
+
artifactIdentity: {
|
|
27921
|
+
kind: "sha256-tree",
|
|
27922
|
+
paths: [
|
|
27923
|
+
"scripts/hook-policy.mjs",
|
|
27924
|
+
"scripts/hook-run.mjs",
|
|
27925
|
+
"scripts/pretooluse-shell-gates.mjs",
|
|
27926
|
+
"scripts/vault-edit-gate.mjs",
|
|
27927
|
+
"scripts/secret-redact.mjs",
|
|
27928
|
+
"scripts/deny-gate-crash.mjs",
|
|
27929
|
+
"scripts/hook-io.mjs",
|
|
27930
|
+
"scripts/hook-trace.mjs",
|
|
27931
|
+
"bin/mmi-hook",
|
|
27932
|
+
"bin/mmi-hook.exe",
|
|
27933
|
+
"native/windows-hook-launcher.c",
|
|
27934
|
+
"scripts/build-windows-hook-launcher.ps1",
|
|
27935
|
+
"scripts/probe-windows-hook-window.ps1"
|
|
27936
|
+
]
|
|
27937
|
+
},
|
|
27938
|
+
verify: [
|
|
27939
|
+
{
|
|
27940
|
+
command: "node",
|
|
27941
|
+
args: ["scripts/check-hook-contract.mjs"],
|
|
27942
|
+
expected: "hook contract: 5 active adapters proven, 12 repositories covered"
|
|
27943
|
+
}
|
|
27944
|
+
],
|
|
27945
|
+
publishVisibility: "public"
|
|
27946
|
+
},
|
|
27947
|
+
{
|
|
27948
|
+
id: "mmi-hooks",
|
|
27949
|
+
classification: "guard",
|
|
27950
|
+
kind: "hooks",
|
|
27951
|
+
ownerPath: "hooks/hooks.json",
|
|
27952
|
+
deliveryPath: "hooks/hooks.json",
|
|
27953
|
+
delivery: "plugin",
|
|
27954
|
+
applicability: "Claude hook-enabled agents",
|
|
27955
|
+
versionCoordinated: false,
|
|
27956
|
+
artifactIdentity: {
|
|
27957
|
+
kind: "sha256-tree",
|
|
27958
|
+
paths: ["hooks", "scripts"]
|
|
27959
|
+
},
|
|
27960
|
+
surfaceToken: "claude",
|
|
27961
|
+
publishVisibility: "public"
|
|
27962
|
+
},
|
|
27963
|
+
{
|
|
27964
|
+
id: "mmi-github-app",
|
|
27965
|
+
classification: "integration",
|
|
27966
|
+
kind: "github-app",
|
|
27967
|
+
ownerPath: "infra/src/github-app.ts",
|
|
27968
|
+
deliveryPath: "infra/dist/handler.js",
|
|
27969
|
+
delivery: "lambda",
|
|
27970
|
+
applicability: "org automation and authorization",
|
|
27971
|
+
versionCoordinated: true,
|
|
27972
|
+
versionPaths: [
|
|
27973
|
+
{ path: "infra/package.json", pointer: "version" }
|
|
27974
|
+
],
|
|
27975
|
+
publishVisibility: "n/a",
|
|
27976
|
+
deployedVersionSource: "http-header:x-hub-version"
|
|
27977
|
+
},
|
|
27978
|
+
{
|
|
27979
|
+
id: "mmi-slack-notify",
|
|
27980
|
+
classification: "integration",
|
|
27981
|
+
kind: "notification",
|
|
27982
|
+
ownerPath: "scripts/slack-post.mjs",
|
|
27983
|
+
deliveryPath: "scripts/slack-post.mjs",
|
|
27984
|
+
delivery: "github-actions",
|
|
27985
|
+
applicability: "master DM notifications only; chatops out by default",
|
|
27986
|
+
versionCoordinated: false,
|
|
27987
|
+
publishVisibility: "n/a"
|
|
27988
|
+
}
|
|
27989
|
+
]
|
|
27990
|
+
};
|
|
27991
|
+
|
|
27992
|
+
// src/surface-doctor.ts
|
|
27993
|
+
var TOKENS = /* @__PURE__ */ new Set(["claude", "codex", "kimi", "cursor", "kilo"]);
|
|
27994
|
+
function isActiveToken(value) {
|
|
27995
|
+
return TOKENS.has(value);
|
|
27996
|
+
}
|
|
27997
|
+
var DOCTOR_SURFACES = Object.freeze(
|
|
27998
|
+
(surfaces_default.agentSurfaces ?? []).filter((surface) => surface.lifecycle === "active" && isActiveToken(surface.token)).map((surface) => Object.freeze({
|
|
27999
|
+
token: surface.token,
|
|
28000
|
+
displayName: surface.displayName,
|
|
28001
|
+
artifactIds: Object.freeze([...surface.artifactIds]),
|
|
28002
|
+
installMechanism: surface.install.mechanism,
|
|
28003
|
+
installLocator: surface.install.locator,
|
|
28004
|
+
upgradeMechanism: surface.upgrade.mechanism,
|
|
28005
|
+
reload: surface.upgrade.reload,
|
|
28006
|
+
repairOwner: surface.ownership.repair === "mmi-cli" ? "mmi-cli" : "operator"
|
|
28007
|
+
}))
|
|
28008
|
+
);
|
|
28009
|
+
function doctorSurface(token) {
|
|
28010
|
+
const descriptor = DOCTOR_SURFACES.find((surface) => surface.token === token);
|
|
28011
|
+
if (!descriptor) throw new Error(`surface registry has no active doctor descriptor for ${token}`);
|
|
28012
|
+
return descriptor;
|
|
28013
|
+
}
|
|
28014
|
+
function diagnoseSurface(evidence) {
|
|
28015
|
+
const base = {
|
|
28016
|
+
descriptor: evidence.descriptor,
|
|
28017
|
+
...evidence.installedVersion ? { installedVersion: evidence.installedVersion } : {},
|
|
28018
|
+
...evidence.releasedVersion ? { releasedVersion: evidence.releasedVersion } : {}
|
|
28019
|
+
};
|
|
28020
|
+
if (!evidence.applicable) return { ...base, state: "skipped" };
|
|
28021
|
+
if (evidence.repair?.attempted && !evidence.repair.ok) {
|
|
28022
|
+
return { ...base, state: "repair-failed", repairDetail: evidence.repair.detail };
|
|
28023
|
+
}
|
|
28024
|
+
if (evidence.repair?.attempted && evidence.repair.ok) {
|
|
28025
|
+
return { ...base, state: "clean", repairDetail: evidence.repair.detail };
|
|
28026
|
+
}
|
|
28027
|
+
if (!evidence.installRecordPresent) return { ...base, state: "missing" };
|
|
28028
|
+
if (!evidence.deliveryPresent || !evidence.payloadPresent || evidence.manifest === "missing") {
|
|
28029
|
+
return { ...base, state: "partial" };
|
|
28030
|
+
}
|
|
28031
|
+
if (evidence.manifest === "invalid") return { ...base, state: "corrupt" };
|
|
28032
|
+
if (!evidence.releasedVersion) return { ...base, state: "freshness-unknown" };
|
|
28033
|
+
if (evidence.installedVersion && evidence.releasedVersion && compareVersions(evidence.installedVersion, evidence.releasedVersion) < 0) {
|
|
28034
|
+
return { ...base, state: "stale" };
|
|
28035
|
+
}
|
|
28036
|
+
return { ...base, state: "clean" };
|
|
28037
|
+
}
|
|
28038
|
+
function reloadInstruction(descriptor) {
|
|
28039
|
+
if (descriptor.token === "claude") return "restart Claude";
|
|
28040
|
+
if (descriptor.token === "codex") return "restart Codex";
|
|
28041
|
+
if (descriptor.token === "kimi") return "restart Kimi Code CLI";
|
|
28042
|
+
if (descriptor.token === "kilo") return "restart Kilo Code";
|
|
28043
|
+
return descriptor.reload === "workspace" ? `reload ${descriptor.displayName}` : `restart ${descriptor.displayName}`;
|
|
28044
|
+
}
|
|
28045
|
+
function planSurfaceRepair(diagnosis) {
|
|
28046
|
+
if (diagnosis.state === "clean" || diagnosis.state === "skipped" || diagnosis.state === "freshness-unknown") return null;
|
|
28047
|
+
const descriptor = diagnosis.descriptor;
|
|
28048
|
+
if (descriptor.repairOwner !== "mmi-cli") {
|
|
28049
|
+
return {
|
|
28050
|
+
supported: false,
|
|
28051
|
+
owner: "operator",
|
|
28052
|
+
changes: [],
|
|
28053
|
+
instruction: `repair is unsupported by mmi-cli; reinstall via ${descriptor.installMechanism} (${descriptor.installLocator}), then ${reloadInstruction(descriptor)}`
|
|
28054
|
+
};
|
|
28055
|
+
}
|
|
28056
|
+
return {
|
|
28057
|
+
supported: true,
|
|
28058
|
+
owner: "mmi-cli",
|
|
28059
|
+
command: "mmi-cli plugin heal",
|
|
28060
|
+
changes: ["replace the active MMI plugin installation", `reload via ${descriptor.reload}`],
|
|
28061
|
+
instruction: `run \`mmi-cli doctor --apply\` (or \`mmi-cli plugin heal\`), then ${reloadInstruction(descriptor)}`
|
|
28062
|
+
};
|
|
28063
|
+
}
|
|
28064
|
+
function buildSurfaceDoctorCheck(diagnosis) {
|
|
28065
|
+
const { descriptor, state } = diagnosis;
|
|
28066
|
+
const plan = planSurfaceRepair(diagnosis);
|
|
28067
|
+
const versions = diagnosis.installedVersion && diagnosis.releasedVersion ? compareVersions(diagnosis.installedVersion, diagnosis.releasedVersion) === 0 ? diagnosis.installedVersion : `${diagnosis.installedVersion} \u2192 ${diagnosis.releasedVersion}` : diagnosis.installedVersion;
|
|
28068
|
+
const detailByState = {
|
|
28069
|
+
skipped: "skipped \u2014 host not active",
|
|
28070
|
+
clean: diagnosis.repairDetail ? `clean \u2014 repaired and verified (${diagnosis.repairDetail})` : `clean${versions ? ` \u2014 ${versions}` : ""}`,
|
|
28071
|
+
"freshness-unknown": `${diagnosis.installedVersion ?? "installed"} \u2014 freshness UNKNOWN, the published version could not be read`,
|
|
28072
|
+
stale: `stale${versions ? ` \u2014 ${versions}` : ""}`,
|
|
28073
|
+
missing: "missing \u2014 no install record",
|
|
28074
|
+
corrupt: "corrupt \u2014 installed manifest is unreadable",
|
|
28075
|
+
partial: "partial \u2014 one or more delivery artifacts are absent",
|
|
28076
|
+
"repair-failed": `repair failed${diagnosis.repairDetail ? ` \u2014 ${diagnosis.repairDetail}` : ""}`
|
|
28077
|
+
};
|
|
28078
|
+
return {
|
|
28079
|
+
id: `${descriptor.token}-plugin`,
|
|
28080
|
+
surface: descriptor.token,
|
|
28081
|
+
state,
|
|
28082
|
+
ok: state === "clean" || state === "skipped",
|
|
28083
|
+
...state === "freshness-unknown" ? { reportOnly: true } : {},
|
|
28084
|
+
label: `${descriptor.displayName} plugin`,
|
|
28085
|
+
detail: detailByState[state],
|
|
28086
|
+
...plan ? { fix: plan.instruction } : state === "freshness-unknown" ? { fix: "check `mmi-cli --version` against `npm view @mutmutco/cli version`, then rerun doctor" } : {},
|
|
28087
|
+
verbose: [
|
|
28088
|
+
`registry surface: ${descriptor.token}`,
|
|
28089
|
+
`install: ${descriptor.installMechanism} (${descriptor.installLocator})`,
|
|
28090
|
+
`repair owner: ${descriptor.repairOwner}`,
|
|
28091
|
+
`artifacts: ${descriptor.artifactIds.join(", ")}`
|
|
28092
|
+
]
|
|
28093
|
+
};
|
|
28094
|
+
}
|
|
28095
|
+
function surfaceRestartAction(descriptor) {
|
|
28096
|
+
return reloadInstruction(descriptor);
|
|
28097
|
+
}
|
|
28098
|
+
|
|
26974
28099
|
// src/doctor-clean.ts
|
|
26975
28100
|
function checkGithubAuth(probe) {
|
|
26976
28101
|
const login = probe.login?.trim();
|
|
@@ -27069,9 +28194,11 @@ function pluginHealTrigger(probe) {
|
|
|
27069
28194
|
function checkClaudePlugin(probe) {
|
|
27070
28195
|
const { installed, released, guardState } = probe;
|
|
27071
28196
|
const codex = probe.surface === "codex";
|
|
27072
|
-
const
|
|
27073
|
-
const
|
|
27074
|
-
const
|
|
28197
|
+
const kilo = probe.surface === "kilo";
|
|
28198
|
+
const cursor = probe.surface === "cursor";
|
|
28199
|
+
const id = codex ? "codex-plugin" : kilo ? "kilo-plugin" : cursor ? "cursor-plugin" : "claude-plugin";
|
|
28200
|
+
const label = codex ? "Codex plugin" : kilo ? "Kilo plugin" : cursor ? "Cursor plugin" : "Claude plugin";
|
|
28201
|
+
const restart = codex ? "restart Codex" : kilo ? "reload Kilo" : cursor ? "reload Cursor" : "restart Claude";
|
|
27075
28202
|
const evidence = [
|
|
27076
28203
|
`installed: ${installed ?? "(none)"}`,
|
|
27077
28204
|
// #3485 item 7: when the value was reused from the banner's once-a-day cache, say so and say how old.
|
|
@@ -27084,8 +28211,8 @@ function checkClaudePlugin(probe) {
|
|
|
27084
28211
|
id,
|
|
27085
28212
|
ok: false,
|
|
27086
28213
|
label,
|
|
27087
|
-
detail: guardState === "no-install" ? "not installed" : "unresolved (
|
|
27088
|
-
fix: `run \`mmi-cli doctor --apply\` (or \`mmi-cli plugin heal\`) to reinstall the MMI
|
|
28214
|
+
detail: guardState === "no-install" ? "not installed" : "unresolved (delivery/cache missing)",
|
|
28215
|
+
fix: `run \`mmi-cli doctor --apply\` (or \`mmi-cli plugin heal\`) to reinstall the MMI plugin, then ${restart}`,
|
|
27089
28216
|
verbose: evidence
|
|
27090
28217
|
};
|
|
27091
28218
|
}
|
|
@@ -27368,9 +28495,12 @@ async function runDoctorClean(opts, io, deps) {
|
|
|
27368
28495
|
opts.banner || opts.preflight || opts.fast && !opts.self ? Promise.resolve(void 0) : deps.githubRepoReach?.() ?? Promise.resolve(void 0)
|
|
27369
28496
|
]);
|
|
27370
28497
|
const ghInstalled2 = login ? true : await deps.ghInstalled();
|
|
27371
|
-
const
|
|
28498
|
+
const registryEvidence = deps.surfaceEvidence?.(isOrgRepo);
|
|
28499
|
+
const installed = registryEvidence?.installedVersion ?? deps.installedPluginVersion();
|
|
27372
28500
|
const pluginSurface = deps.pluginSurface?.() ?? "claude-cli";
|
|
27373
|
-
const codexSurface = pluginSurface === "codex";
|
|
28501
|
+
const codexSurface = registryEvidence?.descriptor.token === "codex" || pluginSurface === "codex";
|
|
28502
|
+
const cursorSurface = pluginSurface === "cursor";
|
|
28503
|
+
const restartAction = registryEvidence ? surfaceRestartAction(registryEvidence.descriptor) : codexSurface ? "restart Codex" : cursorSurface ? "reload Cursor" : pluginSurface === "kilo" ? "restart Kilo Code" : "restart Claude";
|
|
27374
28504
|
const checks = [];
|
|
27375
28505
|
let restartPending = false;
|
|
27376
28506
|
checks.push(checkGithubAuth({ login, ghInstalled: ghInstalled2, reach }));
|
|
@@ -27400,47 +28530,61 @@ async function runDoctorClean(opts, io, deps) {
|
|
|
27400
28530
|
const pluginProbe = {
|
|
27401
28531
|
installed,
|
|
27402
28532
|
released,
|
|
27403
|
-
guardState: deps.pluginGuardState(isOrgRepo),
|
|
28533
|
+
guardState: registryEvidence?.guardState ?? deps.pluginGuardState(isOrgRepo),
|
|
27404
28534
|
releasedNote,
|
|
27405
28535
|
surface: pluginSurface
|
|
27406
28536
|
};
|
|
27407
|
-
const
|
|
28537
|
+
const registryDiagnosis = registryEvidence ? diagnoseSurface({ ...registryEvidence, releasedVersion: released }) : void 0;
|
|
28538
|
+
const registryRepairPlan = registryDiagnosis ? planSurfaceRepair(registryDiagnosis) : null;
|
|
28539
|
+
const healTrigger = registryDiagnosis ? registryRepairPlan?.supported ? registryDiagnosis.state : null : pluginHealTrigger(pluginProbe);
|
|
27408
28540
|
let pluginHealed = false;
|
|
27409
|
-
if (applyEnv && deps.healPlugin && healTrigger) {
|
|
28541
|
+
if (applyEnv && deps.healPlugin && healTrigger && (!registryRepairPlan || registryRepairPlan.supported)) {
|
|
27410
28542
|
const heal = await deps.healPlugin();
|
|
27411
28543
|
pluginHealed = heal.ok;
|
|
27412
|
-
|
|
27413
|
-
|
|
27414
|
-
|
|
27415
|
-
|
|
27416
|
-
|
|
27417
|
-
|
|
27418
|
-
|
|
27419
|
-
|
|
27420
|
-
|
|
27421
|
-
|
|
27422
|
-
|
|
27423
|
-
|
|
27424
|
-
|
|
27425
|
-
|
|
27426
|
-
|
|
27427
|
-
|
|
27428
|
-
|
|
27429
|
-
|
|
27430
|
-
|
|
27431
|
-
|
|
27432
|
-
|
|
27433
|
-
|
|
27434
|
-
|
|
27435
|
-
|
|
27436
|
-
|
|
27437
|
-
|
|
27438
|
-
|
|
28544
|
+
if (registryEvidence) {
|
|
28545
|
+
const repairedDiagnosis = diagnoseSurface({
|
|
28546
|
+
...registryEvidence,
|
|
28547
|
+
releasedVersion: released,
|
|
28548
|
+
repair: { attempted: true, ok: heal.ok, detail: heal.detail }
|
|
28549
|
+
});
|
|
28550
|
+
const repairedCheck = buildSurfaceDoctorCheck(repairedDiagnosis);
|
|
28551
|
+
if (heal.skipped) repairedCheck.reportOnly = true;
|
|
28552
|
+
checks.push(repairedCheck);
|
|
28553
|
+
if (!heal.skipped) restartPending = true;
|
|
28554
|
+
} else {
|
|
28555
|
+
const measured = healTrigger === "behind" ? `${installed} \u2192 ${released}` : "unresolved install";
|
|
28556
|
+
const healEvidence = [
|
|
28557
|
+
`installed: ${installed ?? "(none)"}`,
|
|
28558
|
+
`released: ${released ?? "(not checked \u2014 offline or --fast)"}`,
|
|
28559
|
+
`resolvable: ${pluginProbe.guardState}`,
|
|
28560
|
+
`heal: ${heal.detail}`
|
|
28561
|
+
];
|
|
28562
|
+
checks.push(heal.ok ? {
|
|
28563
|
+
id: codexSurface ? "codex-plugin" : pluginSurface === "kilo" ? "kilo-plugin" : cursorSurface ? "cursor-plugin" : "claude-plugin",
|
|
28564
|
+
ok: true,
|
|
28565
|
+
label: codexSurface ? "Codex plugin" : pluginSurface === "kilo" ? "Kilo plugin" : cursorSurface ? "Cursor plugin" : "Claude plugin",
|
|
28566
|
+
detail: pluginSurface === "kilo" ? `${measured} \u2014 reinstalled via \`kilo plugin\` (the plugin provisions the skills on next load)` : cursorSurface ? `${measured} \u2014 replaced the managed local checkout (previous copy quarantined)` : `${measured} \u2014 reinstalled via the MMI marketplace (remove \u2192 add \u2192 ${codexSurface ? "add" : "install"})${codexSurface ? "; review trust in /hooks" : ""}`,
|
|
28567
|
+
verbose: healEvidence
|
|
28568
|
+
} : {
|
|
28569
|
+
id: codexSurface ? "codex-plugin" : pluginSurface === "kilo" ? "kilo-plugin" : cursorSurface ? "cursor-plugin" : "claude-plugin",
|
|
28570
|
+
ok: false,
|
|
28571
|
+
label: codexSurface ? "Codex plugin" : pluginSurface === "kilo" ? "Kilo plugin" : cursorSurface ? "Cursor plugin" : "Claude plugin",
|
|
28572
|
+
detail: measured,
|
|
28573
|
+
// #3489: a heal skipped because another doctor holds the env-heal lock is a real ✗ — this run did
|
|
28574
|
+
// not fix what it found — but it is not this invocation's to clear. The other process is doing it;
|
|
28575
|
+
// waiting is the correct response and a re-run finds it healed. A heal that RAN and failed is a
|
|
28576
|
+
// genuine gap and still gates.
|
|
28577
|
+
...heal.skipped ? { reportOnly: true } : {},
|
|
28578
|
+
fix: heal.skipped ? `${heal.detail} \u2014 another doctor on this machine is healing it now; re-run once it finishes` : `auto-heal failed (${heal.detail}) \u2014 run \`mmi-cli plugin heal\`, then ${codexSurface ? "restart Codex" : pluginSurface === "kilo" ? "reload Kilo" : cursorSurface ? "reload Cursor" : "restart Claude"}`,
|
|
28579
|
+
verbose: healEvidence
|
|
28580
|
+
});
|
|
28581
|
+
if (!heal.skipped) restartPending = true;
|
|
28582
|
+
}
|
|
27439
28583
|
} else {
|
|
27440
|
-
const plugin = checkClaudePlugin(pluginProbe);
|
|
28584
|
+
const plugin = registryDiagnosis ? buildSurfaceDoctorCheck(registryDiagnosis) : checkClaudePlugin(pluginProbe);
|
|
27441
28585
|
if (plugin) {
|
|
27442
|
-
checks.push(plugin);
|
|
27443
|
-
if (!plugin.ok) restartPending = true;
|
|
28586
|
+
if (!("state" in plugin) || plugin.state !== "skipped") checks.push(plugin);
|
|
28587
|
+
if (!plugin.ok && (registryDiagnosis ? Boolean(registryRepairPlan?.supported) : true)) restartPending = true;
|
|
27444
28588
|
}
|
|
27445
28589
|
}
|
|
27446
28590
|
if (codexSurface && (pluginProbe.guardState === "healthy" || pluginHealed)) {
|
|
@@ -27583,7 +28727,7 @@ async function runDoctorClean(opts, io, deps) {
|
|
|
27583
28727
|
io.log(JSON.stringify({
|
|
27584
28728
|
checks: payload,
|
|
27585
28729
|
restartPending,
|
|
27586
|
-
...restartPending ? { restartAction
|
|
28730
|
+
...restartPending ? { restartAction } : {},
|
|
27587
28731
|
exitCode
|
|
27588
28732
|
}, null, 2));
|
|
27589
28733
|
return exitCode;
|
|
@@ -27594,12 +28738,13 @@ async function runDoctorClean(opts, io, deps) {
|
|
|
27594
28738
|
io.log(renderReport([c], { restartPending: false }));
|
|
27595
28739
|
if (opts.verbose) for (const evidence of c.verbose ?? []) io.log(`${VERBOSE_INDENT}${evidence}`);
|
|
27596
28740
|
}
|
|
27597
|
-
if (restartPending) io.log(
|
|
28741
|
+
if (restartPending) io.log(`\u21BB ${restartAction.charAt(0).toUpperCase()}${restartAction.slice(1)} to finish.`);
|
|
27598
28742
|
return 0;
|
|
27599
28743
|
}
|
|
27600
|
-
const
|
|
27601
|
-
|
|
27602
|
-
|
|
28744
|
+
const legacyRestart = restartAction === "restart Claude";
|
|
28745
|
+
const rendered = renderDoctorText(checks, { verbose: Boolean(opts.verbose), restartPending: restartPending && legacyRestart });
|
|
28746
|
+
io.log(restartPending && !legacyRestart ? `${rendered}
|
|
28747
|
+
\u21BB ${restartAction.charAt(0).toUpperCase()}${restartAction.slice(1)} to finish.` : rendered);
|
|
27603
28748
|
return exitCode;
|
|
27604
28749
|
}
|
|
27605
28750
|
|
|
@@ -27701,17 +28846,43 @@ function installedClaudePluginVersion() {
|
|
|
27701
28846
|
return void 0;
|
|
27702
28847
|
}
|
|
27703
28848
|
}
|
|
27704
|
-
function
|
|
27705
|
-
|
|
28849
|
+
function manifestVersion(path2) {
|
|
28850
|
+
try {
|
|
28851
|
+
const manifest = JSON.parse((0, import_node_fs33.readFileSync)(path2, "utf8"));
|
|
28852
|
+
return typeof manifest.version === "string" && manifest.version.trim() ? manifest.version.trim() : void 0;
|
|
28853
|
+
} catch {
|
|
28854
|
+
return void 0;
|
|
28855
|
+
}
|
|
28856
|
+
}
|
|
28857
|
+
function installedSurfacePluginVersion(surface) {
|
|
28858
|
+
const token = surfaceToken(surface);
|
|
28859
|
+
if (token === "kilo") {
|
|
28860
|
+
try {
|
|
28861
|
+
const stamp = (0, import_node_fs33.readFileSync)((0, import_node_path32.join)((0, import_node_os12.homedir)(), ".kilo", ".mmi-kilo-version"), "utf8").trim();
|
|
28862
|
+
return stamp || void 0;
|
|
28863
|
+
} catch {
|
|
28864
|
+
return void 0;
|
|
28865
|
+
}
|
|
28866
|
+
}
|
|
28867
|
+
if (token === "cursor") {
|
|
28868
|
+
return manifestVersion((0, import_node_path32.join)(cursorLocalPluginRoot(), ".cursor-plugin", "plugin.json"));
|
|
28869
|
+
}
|
|
28870
|
+
if (token === "kimi") {
|
|
28871
|
+
return manifestVersion((0, import_node_path32.join)(surfaceConfigRoot(surface), "plugins", "managed", "mmi", ".kimi-plugin", "plugin.json"));
|
|
28872
|
+
}
|
|
28873
|
+
if (token === "claude") return installedClaudePluginVersion();
|
|
28874
|
+
if (token !== "codex") return void 0;
|
|
27706
28875
|
try {
|
|
27707
28876
|
const raw = process.platform === "win32" ? (0, import_node_child_process14.execFileSync)("cmd.exe", ["/c", "codex", "plugin", "list", "--json"], {
|
|
27708
28877
|
encoding: "utf8",
|
|
27709
28878
|
stdio: ["ignore", "pipe", "ignore"],
|
|
27710
|
-
timeout: 15e3
|
|
28879
|
+
timeout: 15e3,
|
|
28880
|
+
windowsHide: true
|
|
27711
28881
|
}) : (0, import_node_child_process14.execFileSync)("codex", ["plugin", "list", "--json"], {
|
|
27712
28882
|
encoding: "utf8",
|
|
27713
28883
|
stdio: ["ignore", "pipe", "ignore"],
|
|
27714
|
-
timeout: 15e3
|
|
28884
|
+
timeout: 15e3,
|
|
28885
|
+
windowsHide: true
|
|
27715
28886
|
});
|
|
27716
28887
|
const parsed = JSON.parse(raw);
|
|
27717
28888
|
const plugin = parsed.installed?.find((entry) => entry.pluginId === MMI_PLUGIN_ID2 && entry.installed === true && entry.enabled === true);
|
|
@@ -27720,6 +28891,9 @@ function installedActivePluginVersion(surface = detectSurface(process.env)) {
|
|
|
27720
28891
|
return void 0;
|
|
27721
28892
|
}
|
|
27722
28893
|
}
|
|
28894
|
+
function installedActivePluginVersion(surface = detectSurface(process.env)) {
|
|
28895
|
+
return installedSurfacePluginVersion(surface);
|
|
28896
|
+
}
|
|
27723
28897
|
function worktreeRootSync() {
|
|
27724
28898
|
try {
|
|
27725
28899
|
const out = (0, import_node_child_process14.execFileSync)("git", ["rev-parse", "--show-toplevel"], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
|
|
@@ -27857,6 +29031,29 @@ function mmiDoctorDeps(opts = {}) {
|
|
|
27857
29031
|
const throttled = opts.throttleReleasedRead ? throttledReleasedVersion() : void 0;
|
|
27858
29032
|
let notebook;
|
|
27859
29033
|
const notebookOnce = () => notebook ??= fetchNotebook();
|
|
29034
|
+
let surfaceEvidence;
|
|
29035
|
+
let surfaceEvidenceRead = false;
|
|
29036
|
+
const surfaceEvidenceOnce = (isOrgRepo) => {
|
|
29037
|
+
if (surfaceEvidenceRead) return surfaceEvidence;
|
|
29038
|
+
surfaceEvidenceRead = true;
|
|
29039
|
+
const runtimeSurface = detectSurface(process.env);
|
|
29040
|
+
const token = surfaceToken(runtimeSurface);
|
|
29041
|
+
if (!token || token === "opencode") return void 0;
|
|
29042
|
+
const descriptor = doctorSurface(token);
|
|
29043
|
+
const snapshot = snapshotPluginGuardInput(runtimeSurface, isOrgRepo);
|
|
29044
|
+
const installedVersion = installedSurfacePluginVersion(runtimeSurface);
|
|
29045
|
+
surfaceEvidence = {
|
|
29046
|
+
descriptor,
|
|
29047
|
+
applicable: isOrgRepo || snapshot.installRecordPresent || snapshot.pluginCachePresent,
|
|
29048
|
+
installRecordPresent: snapshot.installRecordPresent,
|
|
29049
|
+
deliveryPresent: snapshot.marketplaceClonePresent,
|
|
29050
|
+
payloadPresent: snapshot.pluginCachePresent,
|
|
29051
|
+
manifest: installedVersion ? "valid" : snapshot.installRecordPresent ? "invalid" : "missing",
|
|
29052
|
+
guardState: buildPluginGuardDecision(snapshot).state,
|
|
29053
|
+
...installedVersion ? { installedVersion } : {}
|
|
29054
|
+
};
|
|
29055
|
+
return surfaceEvidence;
|
|
29056
|
+
};
|
|
27860
29057
|
const docsJanitorArmedAt = async (repo) => {
|
|
27861
29058
|
const wanted = docsJanitorScheduleId(repo);
|
|
27862
29059
|
const { entries } = await notebookOnce();
|
|
@@ -27869,6 +29066,7 @@ function mmiDoctorDeps(opts = {}) {
|
|
|
27869
29066
|
awsCallerArn,
|
|
27870
29067
|
isOrgRepo: () => isOrgRepoRoot(),
|
|
27871
29068
|
installedPluginVersion: installedActivePluginVersion,
|
|
29069
|
+
surfaceEvidence: surfaceEvidenceOnce,
|
|
27872
29070
|
pluginGuardState: activePluginGuardState,
|
|
27873
29071
|
pluginSurface: () => detectSurface(process.env),
|
|
27874
29072
|
pluginTrustState: () => codexHookTrustState(),
|
|
@@ -27884,7 +29082,8 @@ function mmiDoctorDeps(opts = {}) {
|
|
|
27884
29082
|
// `mmi-cli plugin heal` drives. Takes effect on the next Claude reload, so the row asks for a restart.
|
|
27885
29083
|
healPlugin: () => {
|
|
27886
29084
|
const surface = detectSurface(process.env);
|
|
27887
|
-
|
|
29085
|
+
const host = surface === "codex" ? "Codex" : surface === "kilo" ? "Kilo" : surface === "cursor" ? "Cursor" : "Claude";
|
|
29086
|
+
return withEnvHealLock(`${host} plugin reinstall`, () => healActivePluginForDoctor(surface));
|
|
27888
29087
|
},
|
|
27889
29088
|
currentCliVersion: resolveClientVersion,
|
|
27890
29089
|
readGitignore,
|
|
@@ -29344,13 +30543,25 @@ function surfaceWaived() {
|
|
|
29344
30543
|
}
|
|
29345
30544
|
var issue = program2.command("issue").description("issues \u2014 reliable create with structured output");
|
|
29346
30545
|
withExamples(mutating(
|
|
29347
|
-
issue.command("create").description("create an issue (type \u2192 label) and print {number,url,label} JSON").addOption(new Option("--type <type>", "bug | feature | task (sets the matching label; required unless --batch)").choices([...ISSUE_TYPES])).option("--title <title>", "issue title").option("--title-file <path|->", "read the issue title from a UTF-8 file, or from stdin with -").option("--body <body>", "issue body (markdown)").option("--body-file <path|->", "read issue body from a UTF-8 file. `-` (stdin) needs a heredoc, which the agent inline-body guard denies (#1473/#2125) \u2014 prefer a real path; a title with backticks
|
|
29348
|
-
// --dry-run/--validate-only plan:
|
|
29349
|
-
//
|
|
29350
|
-
//
|
|
30546
|
+
issue.command("create").description("create an issue (type \u2192 label) and print {number,url,label} JSON").addOption(new Option("--type <type>", "bug | feature | task (sets the matching label; required unless --batch)").choices([...ISSUE_TYPES])).option("--title <title>", "issue title").option("--title-file <path|->", "read the issue title from a UTF-8 file, or from stdin with -").option("--body <body>", "issue body (markdown)").option("--body-file <path|->", "read issue body from a UTF-8 file. `-` (stdin) needs a heredoc, which the agent inline-body guard denies (#1473/#2125) \u2014 prefer a real path; a title with backticks needs --title-file for the same reason (#3381)").option("--priority <priority>", "urgent | high | medium | low (defaults to medium; sets the board Priority field only \u2014 never a priority:* label, #416)").option("--repo <owner/repo>", "target repo (defaults to the current repo)").option("--label <label...>", "extra label(s) to attach (repeatable; auto-created if missing)").option("--surface <surface>", "issue surface, with or without the surface: prefix (#3789). Required when the target repo runs the one-surface-label board rule; any value satisfies it, so this is not a closed enum").option("--no-surface", "file without a surface label on a repo that requires one \u2014 for a genuinely exempt filing (e.g. a coop proof issue that spans every surface)").option("--parent <ref>", "file as a native sub-issue of this parent (#123, owner/repo#123, or URL)").option("--no-related", "skip the auto related-issues comment"),
|
|
30547
|
+
// --dry-run/--validate-only plan: resolve the same title source and validate the same type, priority,
|
|
30548
|
+
// and surface contract as the real action. A plan that echoes the title-file PATH instead of its value
|
|
30549
|
+
// is not a plan of the mutation that will run (#3914).
|
|
29351
30550
|
async (opts) => {
|
|
29352
30551
|
const type = resolveCreateType(opts.type, "issue create", opts.label);
|
|
29353
30552
|
const priority = resolveCreatePriority(opts.priority, "issue create");
|
|
30553
|
+
let title;
|
|
30554
|
+
try {
|
|
30555
|
+
title = await resolveIssueTitle(
|
|
30556
|
+
{ title: opts.title, titleFile: opts.titleFile },
|
|
30557
|
+
{ readFile: import_promises10.readFile, readStdin }
|
|
30558
|
+
);
|
|
30559
|
+
} catch (e) {
|
|
30560
|
+
return fail(
|
|
30561
|
+
`issue create: ${e.message}`,
|
|
30562
|
+
e instanceof TextArgError ? { code: e.code, offending_flag: e.offendingFlag } : void 0
|
|
30563
|
+
);
|
|
30564
|
+
}
|
|
29354
30565
|
const planLabels = opts.label ?? [];
|
|
29355
30566
|
const clash = conflictingSurfaceInputs(opts.surface, planLabels);
|
|
29356
30567
|
if (clash) fail(clash.message, clash.payload);
|
|
@@ -29368,7 +30579,7 @@ withExamples(mutating(
|
|
|
29368
30579
|
return {
|
|
29369
30580
|
command: "issue create",
|
|
29370
30581
|
type,
|
|
29371
|
-
title
|
|
30582
|
+
title,
|
|
29372
30583
|
priority,
|
|
29373
30584
|
repo: opts.repo,
|
|
29374
30585
|
...surface ? { surface } : {}
|
|
@@ -30271,6 +31482,9 @@ function renderReleaseResume(r) {
|
|
|
30271
31482
|
if (r.rcAlignment) lines.push(` rc: ${r.rcAlignment.note}`);
|
|
30272
31483
|
return lines.join("\n");
|
|
30273
31484
|
}
|
|
31485
|
+
function renderReleaseAbort(r) {
|
|
31486
|
+
return `mmi-cli release --abort --apply: ${r.repo} ${r.tag} (${r.tagSha.slice(0, 12)}) \u2014 ${r.note}`;
|
|
31487
|
+
}
|
|
30274
31488
|
function renderRcandResume(r) {
|
|
30275
31489
|
return `mmi-cli rcand --resume: promoted ${r.repo} \u2192 rc at ${r.tag} (${r.tagSha.slice(0, 12)}) [${r.deployModel}]; ${renderDeployLine(r)}; ${r.note}`;
|
|
30276
31490
|
}
|
|
@@ -30315,9 +31529,10 @@ async function resolveRcandPlanTargets() {
|
|
|
30315
31529
|
for (const commandName of ["rcand", "release"]) {
|
|
30316
31530
|
const trainCmd = program2.command(commandName).description(`plan ${commandName} train operations; mutations require explicit master-admin approval`).option("--json", "machine-readable output").option("--watch", "block on the deploy/publish workflow runs and report their outcomes").option("--apply", "execute the guarded master-only train after explicit approval").option("--resume", commandName === "rcand" ? "finish a candidate whose immutable public rc tag passed policy but origin/rc was not pushed (#3881)" : "finish a partial release or its protected post-release alignment without re-cutting, republishing, or redeploying (#3851/#3885)");
|
|
30317
31531
|
const RELEASE_ONLY_FLAGS = [
|
|
30318
|
-
{ flags: "--announce-summary-file <path>", description: "agent-curated summary
|
|
31532
|
+
{ flags: "--announce-summary-file <path>", description: "agent-curated 3-6 line Hub Slack summary; required for a new MMI-Hub --apply (#883/#3901)" },
|
|
30319
31533
|
{ flags: "--ack <shas>", description: "comma-separated dev shas a human verified are in the candidate, overriding the hotfix-coverage guard for a conflicted port whose -x trailer was lost (#958)" },
|
|
30320
|
-
{ flags: "--dev", description: "full-track repos release development -> main directly, skipping rc (refuses if rc carries content not in development; no-op on direct-track repos) (#1062)" }
|
|
31534
|
+
{ flags: "--dev", description: "full-track repos release development -> main directly, skipping rc (refuses if rc carries content not in development; no-op on direct-track repos) (#1062)" },
|
|
31535
|
+
{ flags: "--abort", description: "with --apply, delete only a proven unpublished failed Hub tag and restore local main for a clean recut (#3944)" }
|
|
30321
31536
|
];
|
|
30322
31537
|
for (const f of RELEASE_ONLY_FLAGS) {
|
|
30323
31538
|
if (commandName === "release") {
|
|
@@ -30341,6 +31556,26 @@ for (const commandName of ["rcand", "release"]) {
|
|
|
30341
31556
|
if (o.announceSummaryFile && commandName !== "release") {
|
|
30342
31557
|
return fail(`${commandName}: --announce-summary-file applies only to release \u2014 rcand posts no Hub Slack announcement. Run: mmi-cli release --announce-summary-file <path>`);
|
|
30343
31558
|
}
|
|
31559
|
+
if (o.abort && commandName !== "release") {
|
|
31560
|
+
return fail(`${commandName}: --abort applies only to release \u2014 it rolls back a proven unpublished Hub release tag. Run: mmi-cli release --abort --apply`);
|
|
31561
|
+
}
|
|
31562
|
+
if (o.abort) {
|
|
31563
|
+
if (o.resume) return fail("release: --abort and --resume are mutually exclusive \u2014 abort removes an unpublished tag while resume preserves and promotes it");
|
|
31564
|
+
if (!o.apply) return fail("release: --abort requires --apply after explicit approval; nothing was written");
|
|
31565
|
+
if (o.watch || o.announceSummaryFile || o.ack || o.dev) {
|
|
31566
|
+
return fail("release: --abort accepts only --apply, --repo and --json; promotion flags cannot be combined with rollback");
|
|
31567
|
+
}
|
|
31568
|
+
if (o.repo) {
|
|
31569
|
+
const guard = planTrainApplyRepoGuard(o.repo, await resolveRepo(), "mmi-cli release --abort --apply");
|
|
31570
|
+
if (!guard.ok) return fail(`release: ${guard.message}`);
|
|
31571
|
+
}
|
|
31572
|
+
try {
|
|
31573
|
+
const result = await runReleaseAbort(trainApplyDeps(), { approved: true });
|
|
31574
|
+
return printLine(o.json ? JSON.stringify(result, null, 2) : renderReleaseAbort(result));
|
|
31575
|
+
} catch (e) {
|
|
31576
|
+
return failGraceful(`release --abort: ${e.message}`);
|
|
31577
|
+
}
|
|
31578
|
+
}
|
|
30344
31579
|
if (o.resume) {
|
|
30345
31580
|
if (o.apply) return fail(`${commandName}: --resume and --apply are mutually exclusive \u2014 --apply cuts the NEXT version, --resume finishes the immutable tag already on origin`);
|
|
30346
31581
|
try {
|
|
@@ -30362,6 +31597,24 @@ for (const commandName of ["rcand", "release"]) {
|
|
|
30362
31597
|
const guard = planTrainApplyRepoGuard(o.repo, await resolveRepo(), rerun);
|
|
30363
31598
|
if (!guard.ok) return fail(`${commandName}: ${guard.message}`);
|
|
30364
31599
|
}
|
|
31600
|
+
if (o.apply && commandName === "release" && (await resolveRepo())?.toLowerCase() === ANNOUNCE_REPO.toLowerCase()) {
|
|
31601
|
+
if (!o.announceSummaryFile) {
|
|
31602
|
+
return fail(
|
|
31603
|
+
"release: a new MMI-Hub release requires --announce-summary-file <path> with 3-6 short LLM-curated lines; create the summary file, then rerun the same release command"
|
|
31604
|
+
);
|
|
31605
|
+
}
|
|
31606
|
+
let summaryLines;
|
|
31607
|
+
try {
|
|
31608
|
+
summaryLines = summaryFileLines(await (0, import_promises10.readFile)(o.announceSummaryFile, "utf8"));
|
|
31609
|
+
} catch (e) {
|
|
31610
|
+
return fail(`release: could not read --announce-summary-file ${o.announceSummaryFile}: ${e.message}`);
|
|
31611
|
+
}
|
|
31612
|
+
if (summaryLines.length < 3 || summaryLines.length > 6) {
|
|
31613
|
+
return fail(
|
|
31614
|
+
`release: --announce-summary-file must contain 3-6 non-empty LLM-curated lines (found ${summaryLines.length})`
|
|
31615
|
+
);
|
|
31616
|
+
}
|
|
31617
|
+
}
|
|
30365
31618
|
if (o.apply) {
|
|
30366
31619
|
try {
|
|
30367
31620
|
const ack = (o.ack ?? "").split(",").map((s) => s.trim()).filter(Boolean);
|