@mutmutco/codex-plugin 4.5.85 → 4.5.87
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/.codex-plugin/plugin.json +1 -1
- package/cli/dist/main.cjs +364 -400
- package/cli/package.json +1 -1
- package/package.json +1 -1
- package/skills/bootstrap/SKILL.md +1 -1
- package/skills/hotfix/SKILL.md +1 -1
- package/skills/mmi-help/SKILL.md +1 -1
- package/skills/org-guide/references/getting-started.md +1 -1
- package/skills/rcand/SKILL.md +1 -1
- package/skills/release/SKILL.md +1 -1
- package/skills/secrets/SKILL.md +1 -1
- package/skills/stage/SKILL.md +1 -1
package/cli/dist/main.cjs
CHANGED
|
@@ -7447,25 +7447,6 @@ function workflowEntry(repo, workflowPath, yamlText) {
|
|
|
7447
7447
|
harbourUnmanaged: isHarbourUnmanaged(yamlText)
|
|
7448
7448
|
};
|
|
7449
7449
|
}
|
|
7450
|
-
function decorateAwsModel(entries, registry2, verdicts) {
|
|
7451
|
-
if (!registry2 || registry2.length === 0) return [...entries];
|
|
7452
|
-
const rowById = new Map(registry2.map((r) => [r.id, r]));
|
|
7453
|
-
const literalById = /* @__PURE__ */ new Map();
|
|
7454
|
-
for (const v of verdicts ?? []) {
|
|
7455
|
-
if (typeof v.modelId === "string" && v.modelId.trim() && !literalById.has(v.id)) {
|
|
7456
|
-
literalById.set(v.id, v.modelId);
|
|
7457
|
-
}
|
|
7458
|
-
}
|
|
7459
|
-
return entries.map((e) => {
|
|
7460
|
-
if (!e.scheduleId) return e;
|
|
7461
|
-
const row = rowById.get(e.scheduleId);
|
|
7462
|
-
if (!row || row.executor !== "cursor-agent") return e;
|
|
7463
|
-
const literal = literalById.get(e.scheduleId);
|
|
7464
|
-
if (literal) return { ...e, model: literal };
|
|
7465
|
-
if (row.model) return { ...e, model: `role: ${row.model}` };
|
|
7466
|
-
return { ...e, model: "org-default" };
|
|
7467
|
-
});
|
|
7468
|
-
}
|
|
7469
7450
|
var DECLARED_ENTRIES = [
|
|
7470
7451
|
{ name: "mmi-backup (mm-central, mm-work, mmi-oguz)", cadence: "17 2 * * *", executor: "box cron.d", llm: "no", resolved: "declared", source: "/etc/cron.d/mmi-backup \u2192 /opt/mmi-control/nightly-backup.sh; every Docker volume is tarred and every running Postgres container is pg_dumpall'd beside it (#7147; source scripts/nightly-backup.sh); zuber (46.225.58.31) is gone: deleted 2026-09-18 by the owner-approved ZuberShade decommission (MMC-ZuberShade#1838, snapshot kept); Hetzner has since reassigned that IP to another machine (#7351)" },
|
|
7471
7452
|
// #7148: a backup nobody restored is a hope. Monthly, restore the latest artifact of every
|
|
@@ -7511,12 +7492,10 @@ function formatSchedulesTable(entries, now = /* @__PURE__ */ new Date()) {
|
|
|
7511
7492
|
const cadW = w((e) => e.cadence, "CADENCE");
|
|
7512
7493
|
const exeW = w((e) => e.executor, "EXECUTOR");
|
|
7513
7494
|
const llmW = w((e) => e.llm, "LLM");
|
|
7514
|
-
const modelOf = (e) => e.model ?? "-";
|
|
7515
|
-
const modW = w((e) => modelOf(e), "MODEL");
|
|
7516
7495
|
const resW = w((e) => resolvedOf.get(e) ?? e.resolved, "RESOLVED");
|
|
7517
|
-
const row = (n, c, x, l,
|
|
7518
|
-
const lines2 = [row("JOB", "CADENCE", "EXECUTOR", "LLM", "
|
|
7519
|
-
for (const e of entries) lines2.push(row(e.name, e.cadence, e.executor, e.llm,
|
|
7496
|
+
const row = (n, c, x, l, r, s) => `${n.padEnd(nameW)} ${c.padEnd(cadW)} ${x.padEnd(exeW)} ${l.padEnd(llmW)} ${r.padEnd(resW)} ${s}`;
|
|
7497
|
+
const lines2 = [row("JOB", "CADENCE", "EXECUTOR", "LLM", "RESOLVED", "SOURCE")];
|
|
7498
|
+
for (const e of entries) lines2.push(row(e.name, e.cadence, e.executor, e.llm, resolvedOf.get(e) ?? e.resolved, e.source));
|
|
7520
7499
|
return lines2.join("\n");
|
|
7521
7500
|
}
|
|
7522
7501
|
function withTickState(entry, now = /* @__PURE__ */ new Date()) {
|
|
@@ -7524,11 +7503,11 @@ function withTickState(entry, now = /* @__PURE__ */ new Date()) {
|
|
|
7524
7503
|
}
|
|
7525
7504
|
function renderDocSection(entries, generatedAtIso) {
|
|
7526
7505
|
const lines2 = [
|
|
7527
|
-
"| Job | Cadence | Executor | LLM |
|
|
7528
|
-
"
|
|
7506
|
+
"| Job | Cadence | Executor | LLM | Resolved | Source |",
|
|
7507
|
+
"|---|---|---|---|---|---|"
|
|
7529
7508
|
];
|
|
7530
7509
|
for (const e of entries) {
|
|
7531
|
-
lines2.push(`| ${e.name} | \`${e.cadence}\` | ${e.executor} | ${e.llm} | ${e.
|
|
7510
|
+
lines2.push(`| ${e.name} | \`${e.cadence}\` | ${e.executor} | ${e.llm} | ${e.resolved} | ${e.source} |`);
|
|
7532
7511
|
}
|
|
7533
7512
|
return [
|
|
7534
7513
|
DOC_START_MARKER,
|
|
@@ -7546,10 +7525,6 @@ function spliceDoc(docText, generatedSection) {
|
|
|
7546
7525
|
}
|
|
7547
7526
|
return docText.slice(0, start) + generatedSection + docText.slice(end + DOC_END_MARKER.length);
|
|
7548
7527
|
}
|
|
7549
|
-
var HARBOUR_LLM_LAUNCHERS = /* @__PURE__ */ new Set(["cursor-agent"]);
|
|
7550
|
-
function isHarbourLlmLauncher(executor) {
|
|
7551
|
-
return HARBOUR_LLM_LAUNCHERS.has(executor);
|
|
7552
|
-
}
|
|
7553
7528
|
function splitCadence(cadence) {
|
|
7554
7529
|
return cadence.split("+").map((c) => c.replace(/\s+/g, " ").trim()).filter(Boolean);
|
|
7555
7530
|
}
|
|
@@ -7657,6 +7632,10 @@ function isHarbourUnmanaged(yamlText) {
|
|
|
7657
7632
|
function strayCronDrifts(workflows) {
|
|
7658
7633
|
return workflows.filter((w) => !isHarbourUnmanaged(w.yamlText)).filter((w) => workflowTriggersSchedule(w.yamlText)).map((w) => strayCronDrift(w.name)).sort((a, b) => a.name.localeCompare(b.name));
|
|
7659
7634
|
}
|
|
7635
|
+
var HARBOUR_LLM_LAUNCHERS = /* @__PURE__ */ new Set(["codex-agent"]);
|
|
7636
|
+
function isHarbourLlmLauncher(executor) {
|
|
7637
|
+
return HARBOUR_LLM_LAUNCHERS.has(executor);
|
|
7638
|
+
}
|
|
7660
7639
|
function unlauncheredLlmDrift(entry) {
|
|
7661
7640
|
if (entry.llm !== "yes") return null;
|
|
7662
7641
|
if (isHarbourLlmLauncher(entry.executor)) return null;
|
|
@@ -7667,7 +7646,7 @@ function unlauncheredLlmDrift(entry) {
|
|
|
7667
7646
|
name: entry.name,
|
|
7668
7647
|
executor: entry.executor,
|
|
7669
7648
|
detail: `llm: yes with executor outside the harbour launcher set (${allowed})`,
|
|
7670
|
-
remedy: `move the lane to executor: ${allowed} and run it through the Hub harbour
|
|
7649
|
+
remedy: `move the lane to executor: ${allowed} and run it through the Hub harbour (docs/schedules.md rule 3)`
|
|
7671
7650
|
};
|
|
7672
7651
|
}
|
|
7673
7652
|
function unlauncheredLlmDrifts(entries) {
|
|
@@ -7739,8 +7718,8 @@ var SCHEDULE_HEADER_FIELDS = ["schedule", "what", "owner", "cadence", "executor"
|
|
|
7739
7718
|
var SCHEDULE_EXECUTORS = /* @__PURE__ */ new Set([
|
|
7740
7719
|
"github-actions",
|
|
7741
7720
|
// deterministic workflow — clocked by the harbour, fired via workflow_dispatch
|
|
7742
|
-
"
|
|
7743
|
-
// LLM lane — the harbour's ONLY launcher (docs/schedules.md rule
|
|
7721
|
+
"codex-agent",
|
|
7722
|
+
// LLM lane — the harbour's ONLY scheduled-LLM launcher (docs/schedules.md rule 3, #7780)
|
|
7744
7723
|
"lambda",
|
|
7745
7724
|
// fleet function row — requires the `# target: mmi-fleet-*` header (#3205/#3246)
|
|
7746
7725
|
"box systemd",
|
|
@@ -7821,8 +7800,8 @@ function scheduleRecordFromWorkflow(repo, workflowPath, yamlText) {
|
|
|
7821
7800
|
if (llmFromHeader(yamlText) === "unknown") {
|
|
7822
7801
|
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.`);
|
|
7823
7802
|
}
|
|
7824
|
-
if (llmFromHeader(yamlText) === "yes" && h.executor !== "
|
|
7825
|
-
throw new Error(`schedules register: ${expectedId} header \`llm: yes\` requires the harbour launcher \`executor:
|
|
7803
|
+
if (llmFromHeader(yamlText) === "yes" && h.executor !== "codex-agent") {
|
|
7804
|
+
throw new Error(`schedules register: ${expectedId} header \`llm: yes\` requires the harbour launcher \`executor: codex-agent\` (declared: \`${h.executor}\`) \u2014 scheduled LLM work runs only through the Codex executor (docs/schedules.md rule 3, #7780); change the executor or the llm value.`);
|
|
7826
7805
|
}
|
|
7827
7806
|
const target = header.target;
|
|
7828
7807
|
if (target !== void 0 && h.executor !== "lambda") {
|
|
@@ -7831,9 +7810,8 @@ function scheduleRecordFromWorkflow(repo, workflowPath, yamlText) {
|
|
|
7831
7810
|
if (target !== void 0 && !/^mmi-fleet-[A-Za-z0-9_-]{1,130}$/.test(target)) {
|
|
7832
7811
|
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.`);
|
|
7833
7812
|
}
|
|
7834
|
-
|
|
7835
|
-
|
|
7836
|
-
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.`);
|
|
7813
|
+
if (header.model !== void 0) {
|
|
7814
|
+
throw new Error(`schedules register: ${expectedId} header \`model: ${header.model}\` is refused \u2014 the Codex executor pins its model (#7780), so no row declares one. Remove the line.`);
|
|
7837
7815
|
}
|
|
7838
7816
|
return {
|
|
7839
7817
|
id: expectedId,
|
|
@@ -7847,8 +7825,7 @@ function scheduleRecordFromWorkflow(repo, workflowPath, yamlText) {
|
|
|
7847
7825
|
kill: h.kill,
|
|
7848
7826
|
repo,
|
|
7849
7827
|
sourcePath: workflowPath,
|
|
7850
|
-
...target !== void 0 ? { target } : {}
|
|
7851
|
-
...model !== void 0 ? { model } : {}
|
|
7828
|
+
...target !== void 0 ? { target } : {}
|
|
7852
7829
|
};
|
|
7853
7830
|
}
|
|
7854
7831
|
function scheduleRecordsFromWorkflows(repo, files) {
|
|
@@ -7993,22 +7970,6 @@ async function fetchSchedulesList(deps) {
|
|
|
7993
7970
|
return null;
|
|
7994
7971
|
}
|
|
7995
7972
|
}
|
|
7996
|
-
async function fetchRunVerdicts(deps) {
|
|
7997
|
-
if (!deps.baseUrl) return null;
|
|
7998
|
-
const token = await deps.token();
|
|
7999
|
-
if (!token) return null;
|
|
8000
|
-
try {
|
|
8001
|
-
const res = await retriedFetch(deps, `${deps.baseUrl.replace(/\/$/, "")}/schedules/verdicts`, {
|
|
8002
|
-
method: "GET",
|
|
8003
|
-
headers: { Authorization: `Bearer ${token}` }
|
|
8004
|
-
});
|
|
8005
|
-
if (!res.ok) return null;
|
|
8006
|
-
const body = await res.json();
|
|
8007
|
-
return Array.isArray(body?.runVerdicts) ? body.runVerdicts : null;
|
|
8008
|
-
} catch {
|
|
8009
|
-
return null;
|
|
8010
|
-
}
|
|
8011
|
-
}
|
|
8012
7973
|
async function fetchOrgConfig(deps) {
|
|
8013
7974
|
if (!deps.baseUrl) return null;
|
|
8014
7975
|
const token = await deps.token();
|
|
@@ -11324,9 +11285,6 @@ function detectSurface(env) {
|
|
|
11324
11285
|
if (env.MMI_AGENT_SURFACE === "jervcode" || has("PI_SESSION_ID")) {
|
|
11325
11286
|
return "jervcode";
|
|
11326
11287
|
}
|
|
11327
|
-
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")) {
|
|
11328
|
-
return "cursor";
|
|
11329
|
-
}
|
|
11330
11288
|
if (env.MMI_AGENT_SURFACE === "opencode" || has("OPENCODE_CLIENT") || has("OPENCODE_SERVER_USERNAME")) return "opencode";
|
|
11331
11289
|
const isClaude = has("CLAUDECODE") || has("CLAUDE_CODE_ENTRYPOINT") || has("CLAUDE_PLUGIN_ROOT") || env.MMI_AGENT_SURFACE === "claude";
|
|
11332
11290
|
const isVscode = env.TERM_PROGRAM === "vscode" || has("VSCODE_PID") || has("VSCODE_GIT_ASKPASS_NODE");
|
|
@@ -11341,8 +11299,6 @@ function surfaceToken(surface) {
|
|
|
11341
11299
|
return "claude";
|
|
11342
11300
|
case "codex":
|
|
11343
11301
|
return "codex";
|
|
11344
|
-
case "cursor":
|
|
11345
|
-
return "cursor";
|
|
11346
11302
|
case "jervcode":
|
|
11347
11303
|
return "jervcode";
|
|
11348
11304
|
case "hermes":
|
|
@@ -11362,8 +11318,6 @@ function reloadAction(surface) {
|
|
|
11362
11318
|
return "restart Codex";
|
|
11363
11319
|
case "opencode":
|
|
11364
11320
|
return "restart OpenCode";
|
|
11365
|
-
case "cursor":
|
|
11366
|
-
return "reload the Cursor window";
|
|
11367
11321
|
case "jervcode":
|
|
11368
11322
|
return "restart JervCode (the package set is read at launch)";
|
|
11369
11323
|
case "hermes":
|
|
@@ -11376,7 +11330,6 @@ function reloadAction(surface) {
|
|
|
11376
11330
|
}
|
|
11377
11331
|
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`;
|
|
11378
11332
|
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";
|
|
11379
|
-
var CURSOR_RECOVERY = "mmi-cli plugin heal # installs ~/.cursor/plugins/local/mmi from mutmutco/MMI-Hub";
|
|
11380
11333
|
var PLUGIN_SURFACE_HEAL = {
|
|
11381
11334
|
claude: {
|
|
11382
11335
|
delivery: "plugin-cli",
|
|
@@ -11402,23 +11355,13 @@ var PLUGIN_SURFACE_HEAL = {
|
|
|
11402
11355
|
],
|
|
11403
11356
|
fix: (surface) => `${CODEX_RECOVERY} # then ${reloadAction(surface)} to load MMI commands`,
|
|
11404
11357
|
updateRecipe: [CODEX_RECOVERY]
|
|
11405
|
-
},
|
|
11406
|
-
cursor: {
|
|
11407
|
-
delivery: "local-checkout",
|
|
11408
|
-
recovery: CURSOR_RECOVERY,
|
|
11409
|
-
healSteps: null,
|
|
11410
|
-
fix: (surface) => `${CURSOR_RECOVERY} # then ${reloadAction(surface)}`,
|
|
11411
|
-
updateRecipe: [CURSOR_RECOVERY]
|
|
11412
11358
|
}
|
|
11413
11359
|
};
|
|
11414
11360
|
function nonClaudeSurfaceHealMessage(surface) {
|
|
11415
11361
|
if (surface === "codex") {
|
|
11416
11362
|
return "Codex ships an MMI plugin (#3563). Install or repair it with:\n codex plugin marketplace add mutmutco/MMI-Hub && codex plugin add mmi@mutmutco\n Update the CLI too: npm i -g @mutmutco/cli";
|
|
11417
11363
|
}
|
|
11418
|
-
|
|
11419
|
-
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";
|
|
11420
|
-
}
|
|
11421
|
-
return "No Hub-shipped MMI plugin on this host \u2014 the Hub ships plugins for Claude, Codex, Cursor, JervCode, and Hermes only.\n Update the CLI instead: npm i -g @mutmutco/cli (org rules ride an AGENTS.md authored outside the Hub)";
|
|
11364
|
+
return "No Hub-shipped MMI plugin on this host \u2014 the Hub ships plugins for Claude, Codex, JervCode, and Hermes only.\n Update the CLI instead: npm i -g @mutmutco/cli (org rules ride an AGENTS.md authored outside the Hub)";
|
|
11422
11365
|
}
|
|
11423
11366
|
function healStepAborts(step, ok) {
|
|
11424
11367
|
return !ok && step.gated;
|
|
@@ -11468,7 +11411,6 @@ function hermesConfigRoot(env = process.env, home = (0, import_node_os7.homedir)
|
|
|
11468
11411
|
}
|
|
11469
11412
|
function surfaceConfigRoot(surface, env = process.env, home = (0, import_node_os7.homedir)()) {
|
|
11470
11413
|
if (surface === "codex") return env.CODEX_HOME?.trim() || (0, import_node_path10.join)(home, ".codex");
|
|
11471
|
-
if (surface === "cursor") return (0, import_node_path10.join)(home, ".cursor");
|
|
11472
11414
|
if (surface === "hermes") return hermesConfigRoot(env, home);
|
|
11473
11415
|
if (surface === "jervcode") {
|
|
11474
11416
|
return env.JERVCODE_CODING_AGENT_DIR?.trim() || env.PI_CODING_AGENT_DIR?.trim() || (0, import_node_path10.join)(home, ".jerv", "agent");
|
|
@@ -11493,7 +11435,7 @@ function marketplaceCloneCandidates(surface, home, env = process.env) {
|
|
|
11493
11435
|
(0, import_node_path10.join)(root, "plugins", "marketplaces", CODEX_MARKETPLACE)
|
|
11494
11436
|
];
|
|
11495
11437
|
}
|
|
11496
|
-
if (surface === "
|
|
11438
|
+
if (surface === "hermes") return [];
|
|
11497
11439
|
if (surface === "jervcode") return [];
|
|
11498
11440
|
return [(0, import_node_path10.join)(home, ".claude", "plugins", "marketplaces", "mutmutco")];
|
|
11499
11441
|
}
|
|
@@ -11538,15 +11480,6 @@ async function fetchNpmReleasedVersion() {
|
|
|
11538
11480
|
return void 0;
|
|
11539
11481
|
}
|
|
11540
11482
|
}
|
|
11541
|
-
function cursorLocalPluginRoot(env = process.env, home = (0, import_node_os7.homedir)()) {
|
|
11542
|
-
return (0, import_node_path10.join)(surfaceConfigRoot("cursor", env, home), "plugins", "local", "mmi");
|
|
11543
|
-
}
|
|
11544
|
-
function cursorPluginTreeHealthy(root, exists = import_node_fs12.existsSync) {
|
|
11545
|
-
return [
|
|
11546
|
-
".cursor-plugin/plugin.json",
|
|
11547
|
-
"skills/bootstrap/SKILL.md"
|
|
11548
|
-
].every((path2) => exists((0, import_node_path10.join)(root, ...path2.split("/"))));
|
|
11549
|
-
}
|
|
11550
11483
|
var JERVCODE_NPM_PACKAGE = "@mutmutco/pi-plugin";
|
|
11551
11484
|
function jervcodeAgentDirs(env = process.env, home = (0, import_node_os7.homedir)()) {
|
|
11552
11485
|
const primary = surfaceConfigRoot("jervcode", env, home);
|
|
@@ -11633,10 +11566,10 @@ function snapshotPluginGuardInput(surface = detectSurface(process.env), isOrgRep
|
|
|
11633
11566
|
// Codex's native projection is authoritative; its legacy installed_plugins.json may be empty
|
|
11634
11567
|
// while `plugin list --json` proves the managed plugin installed (#6111).
|
|
11635
11568
|
installRecordPresent: surface === "codex" ? codexStatus?.installed === true : hasUserInstallRecord(installed, MMI_PLUGIN_ID) || hasProjectInstallRecord(installed, MMI_PLUGIN_ID, process.cwd()) || // #4188: jervcode's install record is the settings-file packages[] entry itself.
|
|
11636
|
-
surface === "jervcode" && piEntry !== null || surface === "
|
|
11569
|
+
surface === "jervcode" && piEntry !== null || surface === "hermes" && (0, import_node_fs12.existsSync)(hermesPluginRoot()),
|
|
11637
11570
|
// JervCode's settings entry and native local surfaces have no marketplace clone.
|
|
11638
|
-
marketplaceClonePresent: surface === "
|
|
11639
|
-
pluginCachePresent: surface === "jervcode" ? mmiPiPackageHealthy() : surface === "hermes" ? hermesPluginTreeHealthy(hermesPluginRoot()) : surface === "
|
|
11571
|
+
marketplaceClonePresent: surface === "jervcode" || surface === "hermes" ? true : marketplaceClonePresent(surface, (0, import_node_os7.homedir)()),
|
|
11572
|
+
pluginCachePresent: surface === "jervcode" ? mmiPiPackageHealthy() : surface === "hermes" ? hermesPluginTreeHealthy(hermesPluginRoot()) : surface === "codex" ? Boolean(
|
|
11640
11573
|
codexStatus?.installed && codexStatus.enabled && codexStatus.version && (0, import_node_fs12.existsSync)((0, import_node_path10.join)(root, "plugins", "cache", CODEX_MARKETPLACE, "mmi", codexStatus.version))
|
|
11641
11574
|
) : (0, import_node_fs12.existsSync)((0, import_node_path10.join)(root, "plugins", "cache", "mutmutco", "mmi"))
|
|
11642
11575
|
};
|
|
@@ -11652,84 +11585,6 @@ async function runPluginCli(bin, args, log) {
|
|
|
11652
11585
|
return false;
|
|
11653
11586
|
}
|
|
11654
11587
|
}
|
|
11655
|
-
function canonicalCursorRemote(remote) {
|
|
11656
|
-
return /^(?:https?:\/\/github\.com\/|ssh:\/\/git@github\.com\/|git@github\.com:|github\.com[:/])mutmutco\/MMI-Hub(?:\.git)?\/?$/i.test(remote.trim().replace(/\\/g, "/"));
|
|
11657
|
-
}
|
|
11658
|
-
function readJsonFile(path2) {
|
|
11659
|
-
try {
|
|
11660
|
-
return JSON.parse((0, import_node_fs12.readFileSync)(path2, "utf8"));
|
|
11661
|
-
} catch {
|
|
11662
|
-
return null;
|
|
11663
|
-
}
|
|
11664
|
-
}
|
|
11665
|
-
async function cursorTreeManaged(target) {
|
|
11666
|
-
if ((0, import_node_fs12.existsSync)((0, import_node_path10.join)(target, ".git"))) {
|
|
11667
|
-
try {
|
|
11668
|
-
const { stdout } = await runHostBin("git", ["-C", target, "remote", "get-url", "origin"], { timeout: 15e3 });
|
|
11669
|
-
if (canonicalCursorRemote(stdout)) return { ok: true };
|
|
11670
|
-
return { ok: false, detail: `refused to replace unrelated Cursor plugin directory at ${target}` };
|
|
11671
|
-
} catch {
|
|
11672
|
-
return { ok: false, detail: `refused to replace unmanaged Cursor plugin directory at ${target} (git checkout with no readable origin)` };
|
|
11673
|
-
}
|
|
11674
|
-
}
|
|
11675
|
-
if (readJsonFile((0, import_node_path10.join)(target, "package.json"))?.name === "@mutmutco/cursor-plugin") return { ok: true };
|
|
11676
|
-
if (readJsonFile((0, import_node_path10.join)(target, ".cursor-plugin", "plugin.json"))?.name === "mmi") return { ok: true };
|
|
11677
|
-
return {
|
|
11678
|
-
ok: false,
|
|
11679
|
-
detail: `refused to replace unmanaged Cursor plugin directory at ${target} \u2014 no canonical git remote, no @mutmutco/cursor-plugin package.json and no MMI plugin manifest`
|
|
11680
|
-
};
|
|
11681
|
-
}
|
|
11682
|
-
async function installCursorPluginCheckout(env = process.env) {
|
|
11683
|
-
const configRoot = surfaceConfigRoot("cursor", env);
|
|
11684
|
-
const pluginsRoot = (0, import_node_path10.join)(configRoot, "plugins");
|
|
11685
|
-
const target = (0, import_node_path10.join)(pluginsRoot, "local", "mmi");
|
|
11686
|
-
const source = env.MMI_CURSOR_PLUGIN_SOURCE?.trim();
|
|
11687
|
-
if ((0, import_node_fs12.existsSync)(target) && !source) {
|
|
11688
|
-
const managed = await cursorTreeManaged(target);
|
|
11689
|
-
if (!managed.ok) return { ok: false, detail: managed.detail };
|
|
11690
|
-
}
|
|
11691
|
-
(0, import_node_fs12.mkdirSync)((0, import_node_path10.join)(pluginsRoot, "local"), { recursive: true });
|
|
11692
|
-
(0, import_node_fs12.mkdirSync)((0, import_node_path10.join)(pluginsRoot, "staging"), { recursive: true });
|
|
11693
|
-
(0, import_node_fs12.mkdirSync)((0, import_node_path10.join)(pluginsRoot, "quarantine"), { recursive: true });
|
|
11694
|
-
const suffix = `${Date.now()}-${process.pid}`;
|
|
11695
|
-
const staged = (0, import_node_path10.join)(pluginsRoot, "staging", `mmi-${suffix}`);
|
|
11696
|
-
const quarantined = (0, import_node_path10.join)(pluginsRoot, "quarantine", `mmi-${suffix}`);
|
|
11697
|
-
try {
|
|
11698
|
-
if (source) {
|
|
11699
|
-
(0, import_node_fs12.cpSync)(source, staged, {
|
|
11700
|
-
recursive: true,
|
|
11701
|
-
filter: (path2) => !path2.split(/[\\/]/).some((part) => part === ".git" || part === "node_modules")
|
|
11702
|
-
});
|
|
11703
|
-
} else {
|
|
11704
|
-
await runHostBin("gh", ["repo", "clone", "mutmutco/MMI-Hub", staged, "--", "--branch", "main", "--depth", "1"], {
|
|
11705
|
-
timeout: CLAUDE_PLUGIN_TIMEOUT_MS,
|
|
11706
|
-
step: "gh repo clone mutmutco/MMI-Hub"
|
|
11707
|
-
});
|
|
11708
|
-
}
|
|
11709
|
-
if (!cursorPluginTreeHealthy(staged)) {
|
|
11710
|
-
(0, import_node_fs12.rmSync)(staged, { recursive: true, force: true });
|
|
11711
|
-
return { ok: false, detail: "downloaded Cursor plugin is incomplete; existing install was preserved" };
|
|
11712
|
-
}
|
|
11713
|
-
let movedOld = false;
|
|
11714
|
-
if ((0, import_node_fs12.existsSync)(target)) {
|
|
11715
|
-
(0, import_node_fs12.renameSync)(target, quarantined);
|
|
11716
|
-
movedOld = true;
|
|
11717
|
-
}
|
|
11718
|
-
try {
|
|
11719
|
-
(0, import_node_fs12.renameSync)(staged, target);
|
|
11720
|
-
} catch (error) {
|
|
11721
|
-
if (movedOld && !(0, import_node_fs12.existsSync)(target)) (0, import_node_fs12.renameSync)(quarantined, target);
|
|
11722
|
-
throw error;
|
|
11723
|
-
}
|
|
11724
|
-
return {
|
|
11725
|
-
ok: true,
|
|
11726
|
-
detail: movedOld ? `installed canonical Cursor plugin; previous checkout quarantined at ${quarantined}` : `installed canonical Cursor plugin at ${target}`
|
|
11727
|
-
};
|
|
11728
|
-
} catch (error) {
|
|
11729
|
-
if ((0, import_node_fs12.existsSync)(staged)) (0, import_node_fs12.rmSync)(staged, { recursive: true, force: true });
|
|
11730
|
-
return { ok: false, detail: error.message.trim().slice(0, 240).replace(/\s+/g, " ") };
|
|
11731
|
-
}
|
|
11732
|
-
}
|
|
11733
11588
|
async function marketplaceAddRefSupported(bin) {
|
|
11734
11589
|
try {
|
|
11735
11590
|
const { stdout, stderr } = await runHostBin(bin, ["plugin", "marketplace", "add", "--help"], {
|
|
@@ -11852,11 +11707,10 @@ async function healJervCodeForDoctor() {
|
|
|
11852
11707
|
}
|
|
11853
11708
|
async function healActivePluginForDoctor(surface = detectSurface(process.env), onStep) {
|
|
11854
11709
|
const token = surfaceToken(surface);
|
|
11855
|
-
if (token !== "claude" && token !== "codex" && token !== "
|
|
11710
|
+
if (token !== "claude" && token !== "codex" && token !== "jervcode") {
|
|
11856
11711
|
return { ok: false, detail: `not a supported plugin surface (${surface})` };
|
|
11857
11712
|
}
|
|
11858
11713
|
if (token === "claude") return healClaudePluginForDoctor(surface, onStep);
|
|
11859
|
-
if (token === "cursor") return installCursorPluginCheckout();
|
|
11860
11714
|
if (token === "jervcode") return healJervCodeForDoctor();
|
|
11861
11715
|
const steps = [];
|
|
11862
11716
|
const applied = await applyPluginHeal(surface, (msg) => {
|
|
@@ -11952,7 +11806,7 @@ async function runGuard(readOrigin) {
|
|
|
11952
11806
|
const surface = detectSurface(process.env);
|
|
11953
11807
|
try {
|
|
11954
11808
|
const token = surfaceToken(surface);
|
|
11955
|
-
if (token !== "claude" && token !== "codex" && token !== "
|
|
11809
|
+
if (token !== "claude" && token !== "codex" && token !== "jervcode") {
|
|
11956
11810
|
process.exitCode = 0;
|
|
11957
11811
|
return;
|
|
11958
11812
|
}
|
|
@@ -11966,9 +11820,8 @@ async function runGuard(readOrigin) {
|
|
|
11966
11820
|
if (surfaceToken(surface) === "codex") {
|
|
11967
11821
|
console.error("[mmi-guard] Could not inspect the active Codex plugin; run `mmi-cli plugin heal`.");
|
|
11968
11822
|
process.exitCode = 1;
|
|
11969
|
-
} else if (surfaceToken(surface) === "
|
|
11970
|
-
|
|
11971
|
-
console.error(`[mmi-guard] Could not inspect the active ${host} plugin; run \`mmi-cli plugin heal\`.`);
|
|
11823
|
+
} else if (surfaceToken(surface) === "jervcode") {
|
|
11824
|
+
console.error("[mmi-guard] Could not inspect the active JervCode plugin; run `mmi-cli plugin heal`.");
|
|
11972
11825
|
process.exitCode = 1;
|
|
11973
11826
|
} else {
|
|
11974
11827
|
process.exitCode = 0;
|
|
@@ -11977,7 +11830,7 @@ async function runGuard(readOrigin) {
|
|
|
11977
11830
|
}
|
|
11978
11831
|
async function runPluginHeal(surface = detectSurface(process.env)) {
|
|
11979
11832
|
const token = surfaceToken(surface);
|
|
11980
|
-
if (token !== "claude" && token !== "codex" && token !== "
|
|
11833
|
+
if (token !== "claude" && token !== "codex" && token !== "jervcode") {
|
|
11981
11834
|
console.log(nonClaudeSurfaceHealMessage(token ?? void 0));
|
|
11982
11835
|
return;
|
|
11983
11836
|
}
|
|
@@ -11994,17 +11847,15 @@ async function runPluginHeal(surface = detectSurface(process.env)) {
|
|
|
11994
11847
|
return;
|
|
11995
11848
|
}
|
|
11996
11849
|
const descriptor = PLUGIN_SURFACE_HEAL[token];
|
|
11997
|
-
const
|
|
11998
|
-
if (cursorResult) console.log(` \u21BB ${cursorResult.detail}`);
|
|
11999
|
-
const applied = cursorResult ? cursorResult.ok : await applyPluginHeal(surface, console.log, { force: true });
|
|
11850
|
+
const applied = await applyPluginHeal(surface, console.log, { force: true });
|
|
12000
11851
|
const healed = token !== "claude" ? applied && buildPluginGuardDecision(snapshotPluginGuardInput(surface, true)).state === "healthy" : applied;
|
|
12001
11852
|
if (healed) {
|
|
12002
11853
|
console.log(` \u2713 MMI plugin reinstalled \u2014 ${reloadAction(surface)} to load MMI skills and commands.`);
|
|
12003
11854
|
} else {
|
|
12004
11855
|
process.exitCode = 1;
|
|
12005
|
-
const refSupported =
|
|
12006
|
-
const recovery =
|
|
12007
|
-
const note =
|
|
11856
|
+
const refSupported = await marketplaceAddRefSupported(token);
|
|
11857
|
+
const recovery = refSupported ? descriptor.recovery : recoveryWithoutRef(descriptor.recovery);
|
|
11858
|
+
const note = refAbsenceNote(token, refSupported);
|
|
12008
11859
|
console.log(` \u2717 Auto-heal failed or was skipped. Run manually:
|
|
12009
11860
|
${recovery}${note}${pluginReadGrantNote()}`);
|
|
12010
11861
|
}
|
|
@@ -12017,7 +11868,6 @@ var SESSION_ID_ENV_VARS = [
|
|
|
12017
11868
|
"CLAUDE_CODE_SESSION_ID",
|
|
12018
11869
|
"CODEX_SESSION_ID",
|
|
12019
11870
|
"CODEX_THREAD_ID",
|
|
12020
|
-
"CURSOR_SESSION_ID",
|
|
12021
11871
|
"HERMES_SESSION_ID",
|
|
12022
11872
|
"PI_SESSION_ID",
|
|
12023
11873
|
// #6193: lane runners spawn the CLI through the host's extension shell operations, which strip
|
|
@@ -16436,8 +16286,18 @@ async function foldReleaseVersion(deps, model, tag, foldPaths, sourceCommit = "H
|
|
|
16436
16286
|
if (foldPaths.length === 0) return "no version manifest to fold \u2014 the tag is the version";
|
|
16437
16287
|
const version = tag.replace(/^v/, "");
|
|
16438
16288
|
let foldNoteExtra = "";
|
|
16289
|
+
const timings = [];
|
|
16290
|
+
const timed = async (label, fn) => {
|
|
16291
|
+
const t0 = (deps.now ?? Date.now)();
|
|
16292
|
+
try {
|
|
16293
|
+
return await fn();
|
|
16294
|
+
} finally {
|
|
16295
|
+
timings.push(`${label} ${(((deps.now ?? Date.now)() - t0) / 1e3).toFixed(1)}s`);
|
|
16296
|
+
}
|
|
16297
|
+
};
|
|
16298
|
+
const withTimings = (note) => `${note}; fold timings: ${timings.join(", ")}`;
|
|
16439
16299
|
if (model === "hub-serverless") {
|
|
16440
|
-
await deps.run("node", ["scripts/release-distribution.mjs", "prepare", version, "--source-commit", sourceCommit]);
|
|
16300
|
+
await timed("release-distribution prepare", () => deps.run("node", ["scripts/release-distribution.mjs", "prepare", version, "--source-commit", sourceCommit]));
|
|
16441
16301
|
if (verifyRelease) {
|
|
16442
16302
|
await refuseDivergentPublishedNpmPack(deps, version);
|
|
16443
16303
|
} else {
|
|
@@ -16445,10 +16305,10 @@ async function foldReleaseVersion(deps, model, tag, foldPaths, sourceCommit = "H
|
|
|
16445
16305
|
if (restored) foldNoteExtra = `; ${restored}`;
|
|
16446
16306
|
}
|
|
16447
16307
|
} else {
|
|
16448
|
-
const refresh = await refreshSharedInstallerDeps(deps);
|
|
16308
|
+
const refresh = await timed("installer refresh", () => refreshSharedInstallerDeps(deps));
|
|
16449
16309
|
if (refresh) foldNoteExtra = `; ${refresh}`;
|
|
16450
|
-
await installAppFoldDeps(deps);
|
|
16451
|
-
await deps.run("npm", ["version", version, "--no-git-tag-version", "--allow-same-version"]);
|
|
16310
|
+
await timed("npm ci", () => installAppFoldDeps(deps));
|
|
16311
|
+
await timed("npm version", () => deps.run("npm", ["version", version, "--no-git-tag-version", "--allow-same-version"]));
|
|
16452
16312
|
const patch = Number(version.split(".")[2] ?? "0");
|
|
16453
16313
|
if (model === "registry-publish" && Number.isFinite(patch) && patch > 0) {
|
|
16454
16314
|
await alignExistingHotfixReleaseMarker(deps, version);
|
|
@@ -16467,12 +16327,12 @@ async function foldReleaseVersion(deps, model, tag, foldPaths, sourceCommit = "H
|
|
|
16467
16327
|
);
|
|
16468
16328
|
}
|
|
16469
16329
|
const staged = (await deps.run("git", ["diff", "--cached", "--name-only"])).trim();
|
|
16470
|
-
if (!staged) return `version fold: manifests already at ${version} \u2014 nothing committed${foldNoteExtra}
|
|
16330
|
+
if (!staged) return withTimings(`version fold: manifests already at ${version} \u2014 nothing committed${foldNoteExtra}`);
|
|
16471
16331
|
await deps.run("git", ["commit", "-m", `chore(release): bump distribution to ${tag}`]);
|
|
16472
16332
|
if (model === "hub-serverless" && verifyRelease) {
|
|
16473
|
-
await deps.run("node", ["scripts/release-distribution.mjs", "verify", version, "--skip-npm-view"]);
|
|
16333
|
+
await timed("release-distribution verify", () => deps.run("node", ["scripts/release-distribution.mjs", "verify", version, "--skip-npm-view"]));
|
|
16474
16334
|
}
|
|
16475
|
-
return `version fold: distribution bumped to ${version} in the release commit${foldNoteExtra}
|
|
16335
|
+
return withTimings(`version fold: distribution bumped to ${version} in the release commit${foldNoteExtra}`);
|
|
16476
16336
|
}
|
|
16477
16337
|
function foldPortManualCommand(model, version) {
|
|
16478
16338
|
return model === "hub-serverless" ? `node scripts/release-distribution.mjs prepare ${version}` : `npm version ${version} --no-git-tag-version --allow-same-version`;
|
|
@@ -17268,10 +17128,10 @@ var rollout_plan_default = {
|
|
|
17268
17128
|
note: "The v4.0.0 stamp happens at cut time (D6e #4463); until then the candidate is the origin/development head artifacts (built cli/dist + npm pack), identity proven by dist content hash (D6a)."
|
|
17269
17129
|
},
|
|
17270
17130
|
baseline: {
|
|
17271
|
-
version: "4.5.
|
|
17272
|
-
tag: "v4.5.
|
|
17273
|
-
commit: "
|
|
17274
|
-
npm: "@mutmutco/cli@4.5.
|
|
17131
|
+
version: "4.5.87",
|
|
17132
|
+
tag: "v4.5.87",
|
|
17133
|
+
commit: "c8eaaec639a7",
|
|
17134
|
+
npm: "@mutmutco/cli@4.5.87"
|
|
17275
17135
|
},
|
|
17276
17136
|
exitCriterion: "fleet-n-of-n",
|
|
17277
17137
|
hubOnlyShortcut: "forbidden",
|
|
@@ -17288,14 +17148,14 @@ var rollout_plan_default = {
|
|
|
17288
17148
|
repo: "mutmutco/mmi-hub",
|
|
17289
17149
|
role: "canary",
|
|
17290
17150
|
schedule: "train",
|
|
17291
|
-
v3Target: "v4.5.
|
|
17151
|
+
v3Target: "v4.5.87"
|
|
17292
17152
|
}
|
|
17293
17153
|
],
|
|
17294
17154
|
rollbackTrigger: "Any red inside the post-contract soak window: `devops train gate` FAIL attributable to the v4 doors, Hub endpoint health probe failure, a pre-v4 client admitted instead of receiving actionable HTTP 426, or npm consumer install/doctor failure on the v4-only dist.",
|
|
17295
17155
|
rollback: {
|
|
17296
17156
|
independent: true,
|
|
17297
|
-
mechanism: "npm dist-tag latest -> 4.5.
|
|
17298
|
-
v3Target: "v4.5.
|
|
17157
|
+
mechanism: "npm dist-tag latest -> 4.5.87 and redeploy the Hub Lambda from tag v4.5.87 (c8eaaec639a7); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
|
|
17158
|
+
v3Target: "v4.5.87 (@mutmutco/cli@4.5.87, tag commit c8eaaec639a7 \u2014 last known-good release carrying the repo-index v4-only contract)"
|
|
17299
17159
|
}
|
|
17300
17160
|
},
|
|
17301
17161
|
{
|
|
@@ -19609,7 +19469,7 @@ var TRAIN_RULES_CONTEXTS_JQ = '[.[]|select(.type=="required_status_checks")|.par
|
|
|
19609
19469
|
var TRAIN_CHECK_ATTEMPTS = 40;
|
|
19610
19470
|
var TRAIN_QUEUED_CHECK_ATTEMPTS = 80;
|
|
19611
19471
|
var TRAIN_CHECK_DELAY_MS = 15e3;
|
|
19612
|
-
var TRAIN_PR_ONLY_CONTEXTS = /* @__PURE__ */ new Set(["add-to-project", "mark-merged-pr-done"
|
|
19472
|
+
var TRAIN_PR_ONLY_CONTEXTS = /* @__PURE__ */ new Set(["add-to-project", "mark-merged-pr-done"]);
|
|
19613
19473
|
var TRAIN_PR_AUTOMATION_GRACE_ATTEMPTS = 3;
|
|
19614
19474
|
var TRAIN_FRESH_RUN_GRACE_ATTEMPTS = 4;
|
|
19615
19475
|
async function correlateRun(deps, args) {
|
|
@@ -19963,7 +19823,7 @@ async function alignRcForward(deps, ctx, tag) {
|
|
|
19963
19823
|
return enqueueAlignmentAutoMerge(deps, ctx, number, url || void 0, `rc requires checks (${required.join(", ")}); opened alignment PR ${url || "(url unavailable)"}${supersededNote}`);
|
|
19964
19824
|
}
|
|
19965
19825
|
var FLEET_FOREIGN_VERDICT_GRACE_ATTEMPTS = 3;
|
|
19966
|
-
function resolveContextState(context, checkRuns, statuses) {
|
|
19826
|
+
function resolveContextState(context, checkRuns, statuses, opts = {}) {
|
|
19967
19827
|
let sawFailure = false;
|
|
19968
19828
|
let sawSuccess = false;
|
|
19969
19829
|
let sawPending = false;
|
|
@@ -19982,11 +19842,12 @@ function resolveContextState(context, checkRuns, statuses) {
|
|
|
19982
19842
|
else if (s.state === "failure" || s.state === "error") sawFailure = true;
|
|
19983
19843
|
else sawPending = true;
|
|
19984
19844
|
}
|
|
19845
|
+
if (sawSuccess && opts.successWins) return "success";
|
|
19985
19846
|
if (sawPending) return "pending";
|
|
19986
19847
|
if (sawSuccess) return "success";
|
|
19987
19848
|
return sawFailure ? "failed" : "pending";
|
|
19988
19849
|
}
|
|
19989
|
-
async function waitForRequiredTrainChecks(deps, ctx, sha, required, freshSince) {
|
|
19850
|
+
async function waitForRequiredTrainChecks(deps, ctx, sha, required, freshSince, opts = {}) {
|
|
19990
19851
|
if (required.length === 0) {
|
|
19991
19852
|
return "no required status checks configured on the target branch \u2014 check wait skipped (GitHub push gate is the backstop)";
|
|
19992
19853
|
}
|
|
@@ -20016,6 +19877,8 @@ async function waitForRequiredTrainChecks(deps, ctx, sha, required, freshSince)
|
|
|
20016
19877
|
let workflowRunNote = "";
|
|
20017
19878
|
let workflowRunEverSeen = false;
|
|
20018
19879
|
let hadQueuedWorkflowRun = false;
|
|
19880
|
+
let freshActiveRun = false;
|
|
19881
|
+
let anyActiveRun = false;
|
|
20019
19882
|
const everObserved = /* @__PURE__ */ new Set();
|
|
20020
19883
|
const autoSatisfied = /* @__PURE__ */ new Set();
|
|
20021
19884
|
let fleetForeignStreak = 0;
|
|
@@ -20048,7 +19911,7 @@ async function waitForRequiredTrainChecks(deps, ctx, sha, required, freshSince)
|
|
|
20048
19911
|
"--limit",
|
|
20049
19912
|
"100",
|
|
20050
19913
|
"--json",
|
|
20051
|
-
"databaseId,status,conclusion,headSha"
|
|
19914
|
+
"databaseId,status,conclusion,headSha,createdAt"
|
|
20052
19915
|
]);
|
|
20053
19916
|
const parsedWorkflowRuns = JSON.parse(workflowOut);
|
|
20054
19917
|
if (!Array.isArray(parsedWorkflowRuns)) throw new Error("workflow-runs response was not an array");
|
|
@@ -20057,6 +19920,13 @@ async function waitForRequiredTrainChecks(deps, ctx, sha, required, freshSince)
|
|
|
20057
19920
|
workflowRunRead = "found";
|
|
20058
19921
|
workflowRunEverSeen = true;
|
|
20059
19922
|
const activeRun = matchingRuns.find((run) => run.status === "queued" || run.status === "in_progress");
|
|
19923
|
+
freshActiveRun = freshThreshold !== void 0 && matchingRuns.some((run) => {
|
|
19924
|
+
if (!["queued", "in_progress", "waiting", "pending"].includes(String(run.status))) return false;
|
|
19925
|
+
const t = Date.parse(String(run.createdAt ?? ""));
|
|
19926
|
+
return Number.isFinite(t) && t >= freshThreshold;
|
|
19927
|
+
});
|
|
19928
|
+
if (freshActiveRun) hadQueuedWorkflowRun = true;
|
|
19929
|
+
anyActiveRun = matchingRuns.some((run) => ["queued", "in_progress", "waiting", "pending"].includes(String(run.status)));
|
|
20060
19930
|
if (activeRun) {
|
|
20061
19931
|
if (activeRun.status === "queued") hadQueuedWorkflowRun = true;
|
|
20062
19932
|
workflowRunNote = activeRun.status === "queued" ? "Actions run is queued behind busy runners" : "Actions run is in progress";
|
|
@@ -20065,10 +19935,14 @@ async function waitForRequiredTrainChecks(deps, ctx, sha, required, freshSince)
|
|
|
20065
19935
|
}
|
|
20066
19936
|
} else {
|
|
20067
19937
|
workflowRunRead = "empty";
|
|
19938
|
+
freshActiveRun = false;
|
|
19939
|
+
anyActiveRun = false;
|
|
20068
19940
|
workflowRunNote = "";
|
|
20069
19941
|
}
|
|
20070
19942
|
} catch (e) {
|
|
20071
19943
|
workflowRunRead = "failed";
|
|
19944
|
+
freshActiveRun = false;
|
|
19945
|
+
anyActiveRun = false;
|
|
20072
19946
|
workflowRunNote = `workflow-run lookup failed: ${e.message || String(e)}`;
|
|
20073
19947
|
}
|
|
20074
19948
|
for (const c of required) {
|
|
@@ -20080,7 +19954,7 @@ async function waitForRequiredTrainChecks(deps, ctx, sha, required, freshSince)
|
|
|
20080
19954
|
}
|
|
20081
19955
|
}
|
|
20082
19956
|
const pending = required.filter((c) => !autoSatisfied.has(c));
|
|
20083
|
-
const holdStale = freshThreshold !== void 0 && attempt < TRAIN_FRESH_RUN_GRACE_ATTEMPTS;
|
|
19957
|
+
const holdStale = freshThreshold !== void 0 && (attempt < TRAIN_FRESH_RUN_GRACE_ATTEMPTS || freshActiveRun);
|
|
20084
19958
|
const states = pending.map((c) => {
|
|
20085
19959
|
if (fleetPosted && c === FLEET_LOCKSTEP_CONTEXT) {
|
|
20086
19960
|
const resolution = resolveFleetVerdictState(fleetPosted, statuses);
|
|
@@ -20092,6 +19966,11 @@ async function waitForRequiredTrainChecks(deps, ctx, sha, required, freshSince)
|
|
|
20092
19966
|
fleetForeignStreak = 0;
|
|
20093
19967
|
return [c, resolution.state];
|
|
20094
19968
|
}
|
|
19969
|
+
if (opts.exactCommitGreenSuffices) {
|
|
19970
|
+
const state = resolveContextState(c, checkRuns, statuses, { successWins: true });
|
|
19971
|
+
const inGrace = freshThreshold !== void 0 && attempt < TRAIN_FRESH_RUN_GRACE_ATTEMPTS;
|
|
19972
|
+
return [c, state === "failed" && (anyActiveRun || inGrace) ? "pending" : state];
|
|
19973
|
+
}
|
|
20095
19974
|
if (holdStale) {
|
|
20096
19975
|
const runsFor = checkRuns.filter((r) => r.name === c);
|
|
20097
19976
|
if (runsFor.length > 0 && !runsFor.some(isFreshRun)) return [c, "pending"];
|
|
@@ -20339,6 +20218,10 @@ function validSuiteCutContext(repo, context) {
|
|
|
20339
20218
|
function authorisedSuiteCut(repo, meta, context) {
|
|
20340
20219
|
return validSuiteCutContext(repo, context) && !!meta?.suite && context.lead.toLowerCase() === suiteLeadRepo(repo, meta.suite).toLowerCase();
|
|
20341
20220
|
}
|
|
20221
|
+
function acceptedSuiteContext(repo, meta, context) {
|
|
20222
|
+
if (authorisedSuiteCut(repo, meta, context)) return true;
|
|
20223
|
+
return !!context && context.token === SUITE_LEAD_TOKEN && !!meta?.suite && !isBundleOnlySuiteMember(repo, meta.suite) && context.repo.toLowerCase() === repo.toLowerCase();
|
|
20224
|
+
}
|
|
20342
20225
|
var BUNDLE_ONLY_RC_EXCEPTION = "suite member: rc is bundle-only (no rc runtime)";
|
|
20343
20226
|
function isBundleOnlySuiteMember(repo, suite) {
|
|
20344
20227
|
if (!suite) return false;
|
|
@@ -21368,6 +21251,10 @@ async function retireRcRuntime(deps, ctx, model, deployStatus, releasedRcSha) {
|
|
|
21368
21251
|
};
|
|
21369
21252
|
}
|
|
21370
21253
|
try {
|
|
21254
|
+
const meta = requireProjectMetaForTrain(await loadProjectMeta(deps, ctx), ctx.repo);
|
|
21255
|
+
if (isBundleOnlySuiteMember(ctx.repo, meta.suite)) {
|
|
21256
|
+
return { status: "not-applicable", note: "bundle-only suite member has no rc runtime to retire" };
|
|
21257
|
+
}
|
|
21371
21258
|
await deps.run("git", ["fetch", "origin", "rc"]);
|
|
21372
21259
|
const rcNow = clean2(await deps.run("git", ["rev-parse", "origin/rc"]));
|
|
21373
21260
|
if (rcNow !== releasedRcSha) {
|
|
@@ -21900,14 +21787,6 @@ function installedClaudePluginVersion() {
|
|
|
21900
21787
|
return void 0;
|
|
21901
21788
|
}
|
|
21902
21789
|
}
|
|
21903
|
-
function manifestVersion(path2) {
|
|
21904
|
-
try {
|
|
21905
|
-
const manifest = JSON.parse((0, import_node_fs20.readFileSync)(path2, "utf8"));
|
|
21906
|
-
return typeof manifest.version === "string" && manifest.version.trim() ? manifest.version.trim() : void 0;
|
|
21907
|
-
} catch {
|
|
21908
|
-
return void 0;
|
|
21909
|
-
}
|
|
21910
|
-
}
|
|
21911
21790
|
function readHermesPluginEvidence(env = process.env) {
|
|
21912
21791
|
const host = hermesConfigRoot(env);
|
|
21913
21792
|
const root = hermesPluginRoot(env);
|
|
@@ -21959,9 +21838,6 @@ function readHermesPluginEvidence(env = process.env) {
|
|
|
21959
21838
|
function installedSurfacePluginVersion(surface) {
|
|
21960
21839
|
const token = surfaceToken(surface);
|
|
21961
21840
|
if (token === "hermes") return readHermesPluginEvidence().installedVersion;
|
|
21962
|
-
if (token === "cursor") {
|
|
21963
|
-
return manifestVersion((0, import_node_path18.join)(cursorLocalPluginRoot(), ".cursor-plugin", "plugin.json"));
|
|
21964
|
-
}
|
|
21965
21841
|
if (token === "jervcode") {
|
|
21966
21842
|
return installedJervCodePackageVersion();
|
|
21967
21843
|
}
|
|
@@ -23743,17 +23619,23 @@ async function reverifyPersistedRunPhases(deps, path2, anchors, input) {
|
|
|
23743
23619
|
async (current) => {
|
|
23744
23620
|
if (!current) return { value: { ledger: void 0, notes } };
|
|
23745
23621
|
const phases = { ...current.phases };
|
|
23746
|
-
for (const phase of ["deploy", "publish"]) {
|
|
23622
|
+
for (const phase of input.phases ?? ["deploy", "publish"]) {
|
|
23747
23623
|
const rec = phases[phase];
|
|
23748
23624
|
if (rec.updatedAt === "never") continue;
|
|
23749
23625
|
if (rec.runId == null) {
|
|
23750
|
-
if (rec.state === "complete") notes.push(`${phase}: persisted complete without a run id \u2014
|
|
23626
|
+
if (rec.state === "complete") notes.push(`${phase}: persisted complete without a run id \u2014 live phase evidence decides the leg`);
|
|
23751
23627
|
continue;
|
|
23752
23628
|
}
|
|
23753
|
-
if (
|
|
23629
|
+
if (phase === "ordinaryCi" && rec.runRepo != null && rec.runRepo !== input.repo) {
|
|
23630
|
+
throw new ReleaseLedgerError("invalid-record", `the persisted ordinaryCi run ${rec.runId} belongs to ${rec.runRepo}, not the release repo ${input.repo} \u2014 refusing unrelated run evidence (#7807)`);
|
|
23631
|
+
}
|
|
23632
|
+
if (phase === "ordinaryCi" && rec.sha != null && rec.sha.toLowerCase() !== input.sha.toLowerCase()) {
|
|
23633
|
+
throw new ReleaseLedgerError("tag-sha-mismatch", `the persisted ordinaryCi boundary is for ${rec.sha.slice(0, 12)} but the active release tag is ${input.sha.slice(0, 12)} \u2014 refusing unrelated SHA evidence (#7807)`);
|
|
23634
|
+
}
|
|
23635
|
+
if (phase === "ordinaryCi" && rec.runProvenance !== "sha" || phase !== "ordinaryCi" && rec.runProvenance !== "sha" && rec.runProvenance !== "dispatch") {
|
|
23754
23636
|
throw new ReleaseLedgerError(
|
|
23755
23637
|
"invalid-record",
|
|
23756
|
-
`the persisted ${phase} boundary names run ${rec.runId} without valid runProvenance ('sha'|'dispatch' required, got ${JSON.stringify(rec.runProvenance ?? null)}) \u2014 a run-backed boundary without provenance cannot be re-verified; failing closed (#5987)`
|
|
23638
|
+
`the persisted ${phase} boundary names run ${rec.runId} without valid runProvenance ('sha'${phase === "ordinaryCi" ? "" : "|'dispatch'"} required, got ${JSON.stringify(rec.runProvenance ?? null)}) \u2014 a run-backed boundary without provenance cannot be re-verified; failing closed (#5987)`
|
|
23757
23639
|
);
|
|
23758
23640
|
}
|
|
23759
23641
|
const runRepo = rec.runRepo ?? input.repo;
|
|
@@ -23788,7 +23670,8 @@ async function reverifyPersistedRunPhases(deps, path2, anchors, input) {
|
|
|
23788
23670
|
}
|
|
23789
23671
|
if (meta.conclusion === "success") {
|
|
23790
23672
|
if (rec.state !== "complete") {
|
|
23791
|
-
|
|
23673
|
+
const { error: _error, ...withoutPriorError } = rec;
|
|
23674
|
+
phases[phase] = { ...withoutPriorError, state: "complete", note: `re-verified live: ${liveLabel} concluded success`, updatedAt: now().toISOString() };
|
|
23792
23675
|
notes.push(`${phase}: ${liveLabel} re-verified green live`);
|
|
23793
23676
|
}
|
|
23794
23677
|
continue;
|
|
@@ -24809,7 +24692,7 @@ async function runTrainDoctor(input) {
|
|
|
24809
24692
|
const dev = input.dev === true && lane === "release";
|
|
24810
24693
|
const suiteRefusal = suiteMemberRefusal(repo, meta, lane, input.suiteSolo === true, input.suiteCut);
|
|
24811
24694
|
if (suiteRefusal) add({ code: "suite-member-solo", severity: "blocker", source: "origin", title: suiteRefusal, remedy: suiteRefusal });
|
|
24812
|
-
if (input.suiteCut && !
|
|
24695
|
+
if (input.suiteCut && !acceptedSuiteContext(repo, meta, input.suiteCut))
|
|
24813
24696
|
add({ code: "suite-cut-context-invalid", severity: "blocker", source: "origin", title: "suite cut context does not match this member and its registered lead", remedy: "run from the MM-Work suite command with the member registered in META" });
|
|
24814
24697
|
if (lane === "rcand" && track === "direct") {
|
|
24815
24698
|
add({ code: "lane-mismatch", severity: "blocker", source: "origin", title: `${repo} is direct-track (no rc) \u2014 rcand does not apply`, remedy: "run `mmi-cli devops release --apply` from development instead of rcand" });
|
|
@@ -25066,6 +24949,19 @@ async function runTrainDoctor(input) {
|
|
|
25066
24949
|
try {
|
|
25067
24950
|
let ledger = parseReleaseLedger(ledgerRaw);
|
|
25068
24951
|
let derived = deriveReleasePhaseStatus(ledger);
|
|
24952
|
+
const ordinaryCi = ledger.phases.ordinaryCi;
|
|
24953
|
+
if (healLedger && ordinaryCi.state === "failed" && ordinaryCi.runId != null) {
|
|
24954
|
+
const outcome = await reverifyPersistedRunPhases(train, ledgerPath, {
|
|
24955
|
+
repo: ledger.repo,
|
|
24956
|
+
tag: ledger.tag,
|
|
24957
|
+
tagSha: ledger.tagSha
|
|
24958
|
+
}, { sha: ledger.tagSha, repo: ledger.repo, phases: ["ordinaryCi"] });
|
|
24959
|
+
if (outcome.notes.some((note) => note.startsWith("ordinaryCi:") && note.includes("re-verified green live"))) {
|
|
24960
|
+
add({ code: "ledger-ordinary-ci-rerun", severity: "healed", source: "origin", title: `the exact ordinaryCi run ${ordinaryCi.runId} for ${ledger.tag} is green on its recorded release SHA (#7807)`, remedy: "nothing \u2014 the ordinaryCi ledger leg now records the live green run" });
|
|
24961
|
+
ledger = parseReleaseLedger(deps.readLedgerRaw(ledgerPath) ?? ledgerRaw);
|
|
24962
|
+
derived = deriveReleasePhaseStatus(ledger);
|
|
24963
|
+
}
|
|
24964
|
+
}
|
|
25069
24965
|
const deployModel = typeof meta?.deployModel === "string" && isDeployModel(meta.deployModel) ? meta.deployModel : void 0;
|
|
25070
24966
|
if (healLedger && deployModel && derived.legs.some((l) => (l.phase === "deploy" || l.phase === "publish") && l.state === "pending")) {
|
|
25071
24967
|
const run = await deps.healRunLegs(train, cwd, deployModel);
|
|
@@ -25527,7 +25423,7 @@ function enforceGateBudget(deps, repo) {
|
|
|
25527
25423
|
async function preflight(deps, ctx, stage, meta, lane) {
|
|
25528
25424
|
assertGitBashOnWindowsForJervHubRelease(ctx.repo);
|
|
25529
25425
|
deps.warn?.("Release: Checking release prerequisites");
|
|
25530
|
-
if (deps.suiteCut && !
|
|
25426
|
+
if (deps.suiteCut && !acceptedSuiteContext(ctx.repo, meta, deps.suiteCut))
|
|
25531
25427
|
throw new Error(`${ctx.repo}: suite cut context does not match registry META; no deployment or tag was changed`);
|
|
25532
25428
|
const model = requireDeployModel(meta, ctx.repo);
|
|
25533
25429
|
if (model === "content") {
|
|
@@ -26170,7 +26066,9 @@ async function completeMainRelease(deps, ctx, meta, deployModel, watch, options,
|
|
|
26170
26066
|
const ledgerAnchors = { repo: ctx.repo, tag, tagSha: releaseSha };
|
|
26171
26067
|
try {
|
|
26172
26068
|
deps.warn?.("Release: Checking the package before publishing");
|
|
26069
|
+
const dryRunStart = (deps.now ?? Date.now)();
|
|
26173
26070
|
await verifyPublishDryRun(deps, ctx, meta, deployModel);
|
|
26071
|
+
deps.warn?.(`Release: publish dry run took ${(((deps.now ?? Date.now)() - dryRunStart) / 1e3).toFixed(1)}s`);
|
|
26174
26072
|
} catch (e) {
|
|
26175
26073
|
throw await recoverFailedFold(deps, e, startBranch, preFold.mainSha, resumeCommand);
|
|
26176
26074
|
}
|
|
@@ -26408,7 +26306,7 @@ async function runRcandResumeLocked(deps, options) {
|
|
|
26408
26306
|
await requireCleanTree(deps);
|
|
26409
26307
|
await runGitRemoteRead(deps, ["fetch", "origin", "--tags"]);
|
|
26410
26308
|
const meta = requireProjectMetaForTrain(await loadProjectMeta(deps, ctx), ctx.repo);
|
|
26411
|
-
const suiteRefusal = suiteMemberRefusal(ctx.repo, meta, "rcand",
|
|
26309
|
+
const suiteRefusal = suiteMemberRefusal(ctx.repo, meta, "rcand", options.suiteSolo === true, deps.suiteCut);
|
|
26412
26310
|
if (suiteRefusal) throw new Error(`rcand --resume: ${suiteRefusal}`);
|
|
26413
26311
|
const branchHints = await loadReleaseTrackBranchHints(deps);
|
|
26414
26312
|
if (isHubControlRepo(ctx.repo) || resolveReleaseTrack(meta, branchHints) === "direct") {
|
|
@@ -27721,7 +27619,6 @@ async function listWorkflowPaths(deps, repo, branch) {
|
|
|
27721
27619
|
return e?.status === 404 ? [] : void 0;
|
|
27722
27620
|
}
|
|
27723
27621
|
}
|
|
27724
|
-
var AGENT_PR_BOOKKEEPING_CONTEXTS = /* @__PURE__ */ new Set(["guard", "verdict"]);
|
|
27725
27622
|
function gateWorkflowFiles(prWorkflowPaths) {
|
|
27726
27623
|
const files = prWorkflowPaths.map((p) => p.slice(p.lastIndexOf("/") + 1)).filter((f) => f !== "agent-pr.yml");
|
|
27727
27624
|
return files.length ? files : DEFAULT_GATE_FILES;
|
|
@@ -28058,8 +27955,7 @@ async function auditRepoCi(repo, callerDeps) {
|
|
|
28058
27955
|
}
|
|
28059
27956
|
}
|
|
28060
27957
|
if (explicitNoCi) {
|
|
28061
|
-
const
|
|
28062
|
-
const emitted = resolved?.filter((c) => !AGENT_PR_BOOKKEEPING_CONTEXTS.has(c));
|
|
27958
|
+
const emitted = await getEmittedPrContexts();
|
|
28063
27959
|
if (emitted && emitted.length > 0) {
|
|
28064
27960
|
checks.push({
|
|
28065
27961
|
ok: false,
|
|
@@ -28190,7 +28086,7 @@ async function auditRepoCi(repo, callerDeps) {
|
|
|
28190
28086
|
if (hasRequiredChecks) {
|
|
28191
28087
|
const gateFiles = new Set(gateWorkflowFiles(prWorkflowPaths));
|
|
28192
28088
|
const gateWorkflows = (await getBranchWorkflows() ?? []).filter((wf) => gateFiles.has(wf.path.slice(wf.path.lastIndexOf("/") + 1)));
|
|
28193
|
-
const unblocking = unblockingGateJobs(gateWorkflows, new Set(liveContexts)).filter((job) => !
|
|
28089
|
+
const unblocking = unblockingGateJobs(gateWorkflows, new Set(liveContexts)).filter((job) => !branchPlan?.pending.some((entry) => entry.branch === baseBranch && entry.contexts.some((context) => contextMatchesJobId(job, context))));
|
|
28194
28090
|
checks.push({
|
|
28195
28091
|
ok: unblocking.length === 0,
|
|
28196
28092
|
label: UNBLOCKING_GATE_JOBS_LABEL,
|
|
@@ -28213,8 +28109,7 @@ async function auditRepoCi(repo, callerDeps) {
|
|
|
28213
28109
|
for (const ref of releaseBranches) {
|
|
28214
28110
|
emittedByBranch[ref] = await resolveEmittedPrContexts(deps, repo, ref.replace("refs/heads/", ""));
|
|
28215
28111
|
}
|
|
28216
|
-
const
|
|
28217
|
-
const unreachable = unreachableReleaseContexts({ requiredContexts: liveContexts, releaseBranches, emittedByBranch, ignore });
|
|
28112
|
+
const unreachable = unreachableReleaseContexts({ requiredContexts: liveContexts, releaseBranches, emittedByBranch, ignore: TRAIN_PR_ONLY_CONTEXTS });
|
|
28218
28113
|
checks.push({
|
|
28219
28114
|
ok: unreachable.length === 0,
|
|
28220
28115
|
label: RELEASE_BRANCH_REACHABLE_LABEL,
|
|
@@ -28681,8 +28576,7 @@ async function authoritativeSafetyHold(authority, deps, repo, baseBranch, meta)
|
|
|
28681
28576
|
if (authority.coversReleaseBranches && authority.source !== "registry META requiredChecks") {
|
|
28682
28577
|
const track = resolveReleaseTrack(meta, void 0, repo);
|
|
28683
28578
|
const releaseBranches = branchesForTrack(track).filter((b) => b !== baseBranch);
|
|
28684
|
-
const
|
|
28685
|
-
const requiredForReleaseBranches = authority.contexts.filter((context) => !ignore.has(context));
|
|
28579
|
+
const requiredForReleaseBranches = authority.contexts.filter((context) => !TRAIN_PR_ONLY_CONTEXTS.has(context));
|
|
28686
28580
|
for (const branch of releaseBranches) {
|
|
28687
28581
|
if (await branchPresence(deps, repo, branch) !== true) continue;
|
|
28688
28582
|
const emittedThere = await resolveEmittedPrContexts(deps, repo, branch);
|
|
@@ -30503,7 +30397,7 @@ function trainPlan(command, options = {}) {
|
|
|
30503
30397
|
{ label: "fold the version bump into the release commit \u2014 runs inside the apply step, no separate bump PR", gated: true },
|
|
30504
30398
|
{ label: "tag release and publish GitHub Release", gated: true },
|
|
30505
30399
|
{ label: "trigger the deploy path for this repo model, returning Hub Actions run id/url data (and, with --watch, its outcome)", command: "tenant-container: gh workflow run tenant-deploy.yml ... then gh run list/watch", gated: true },
|
|
30506
|
-
{ label: "retire the rc runtime (rc is ephemeral \u2014 non-fatal, reported as rcRetirement)", command: "mmi-cli devops runtime tenant control <owner/repo> rc retire", gated: true },
|
|
30400
|
+
...!isBundleOnlySuiteMember(options.repo ?? "", options.suite) ? [{ label: "retire the rc runtime (rc is ephemeral \u2014 non-fatal, reported as rcRetirement)", command: "mmi-cli devops runtime tenant control <owner/repo> rc retire", gated: true }] : [],
|
|
30507
30401
|
{ label: "roll development forward and align rc to the released main", gated: true },
|
|
30508
30402
|
{ label: "synchronize the owning Project short description + thin README from the repo docs and current member repos", command: "mmi-cli oracle org project sync-info --apply", gated: true }
|
|
30509
30403
|
];
|
|
@@ -30511,6 +30405,8 @@ function trainPlan(command, options = {}) {
|
|
|
30511
30405
|
return [
|
|
30512
30406
|
authority,
|
|
30513
30407
|
{ label: "verify current branch is rc", gated: true },
|
|
30408
|
+
// #7783: the version the apply will mint, read from the rc tag at origin/rc's head (never a stale higher cycle).
|
|
30409
|
+
{ label: `resolve the release tag from the rc head (${options.releaseTagError ? `error: ${options.releaseTagError}` : `releaseTag=${options.releaseTag ?? "(unresolved)"}`})`, command: "node scripts/next-version.mjs release", gated: true },
|
|
30514
30410
|
{ label: "verify registry META for this project", command: "mmi-cli oracle org project get <owner/repo>", gated: true },
|
|
30515
30411
|
{ label: "preflight required main secret names", command: "mmi-cli vault secrets preflight --stage main --repo <owner/repo>", gated: true },
|
|
30516
30412
|
{ label: "preflight GitHub Actions hosted job start (billing/spending) before minting a tag", command: "train dispatches actions-job-start-canary.yml on MMI-Hub (#5604); exact-run --retry-publish skips this", gated: true },
|
|
@@ -31405,7 +31301,7 @@ var SETTABLE_VAR_HINTS = {
|
|
|
31405
31301
|
requiredGcpApis: "comma-string",
|
|
31406
31302
|
requiredRuntimeSecrets: 'JSON stage map, e.g. {"dev":["KEY"],"rc":["KEY"],"main":["KEY"]}',
|
|
31407
31303
|
requiredBuildSecrets: 'JSON flat array, e.g. ["NODE_AUTH_TOKEN=@github-packages-token"]',
|
|
31408
|
-
tenantTasks: 'JSON map {name:{service,command[],stages[],timeoutSeconds?,artifact?:"required",secretOutput?:{path,vault:{repo,key},overwrite?}}}; use {artifact} in argv when required; a secretOutput is master-only',
|
|
31304
|
+
tenantTasks: 'JSON map {name:{service,command[],stages[],timeoutSeconds?,artifact?:"required",secretOutput?:{path,vault:{repo,key},overwrite?}}} \u2014 merged per entry, at most 20 stored in total; retire ONE task with --unset-task NAME; use {artifact} in argv when required; a secretOutput is master-only',
|
|
31409
31305
|
secrets: "JSON catalog map keyed by KEY {key,purpose,group,owner,stages[],consumers[]} \u2014 merged per entry; drop ONE entry with --unset-secret KEY; clear ALL with --unset secrets --confirm-clear-secrets; prefer --secrets-file",
|
|
31410
31306
|
edgeDomains: "JSON {dev,rc,main} domain map",
|
|
31411
31307
|
statusOptions: "JSON name\u2192id map",
|
|
@@ -31436,6 +31332,18 @@ function duplicateVarKeyAcrossFlags(vars, sets) {
|
|
|
31436
31332
|
}
|
|
31437
31333
|
return null;
|
|
31438
31334
|
}
|
|
31335
|
+
function tenantTaskWriteRefusal(existing, patch) {
|
|
31336
|
+
const remove2 = Array.isArray(patch.tenantTasksRemove) ? patch.tenantTasksRemove : [];
|
|
31337
|
+
const stored = existing?.tenantTasks ?? {};
|
|
31338
|
+
const slug = existing?.slug ?? "this project";
|
|
31339
|
+
const missing = remove2.filter((name) => stored[name] === void 0);
|
|
31340
|
+
if (missing.length > 0) return `org project set: --unset-task ${missing.join(", ")} not declared in ${slug}'s tenantTasks (${Object.keys(stored).length} declared) \u2014 nothing to remove`;
|
|
31341
|
+
const sent = patch.tenantTasks;
|
|
31342
|
+
if (sent === null || sent === void 0) return null;
|
|
31343
|
+
const merged = /* @__PURE__ */ new Set([...Object.keys(stored), ...Object.keys(sent)]);
|
|
31344
|
+
if (merged.size > 20) return `org project set: ${slug} would hold ${merged.size} tenantTasks (${Object.keys(stored).length} declared today); the cap is 20 \u2014 retire some first with --unset-task <name>`;
|
|
31345
|
+
return null;
|
|
31346
|
+
}
|
|
31439
31347
|
function secretRemovalRefusal(existing, patch) {
|
|
31440
31348
|
const remove2 = Array.isArray(patch.secretsRemove) ? patch.secretsRemove : [];
|
|
31441
31349
|
if (remove2.length === 0) return null;
|
|
@@ -31610,12 +31518,26 @@ function buildProjectSetPatch(input) {
|
|
|
31610
31518
|
}
|
|
31611
31519
|
patch.secretsRemove = keys;
|
|
31612
31520
|
}
|
|
31521
|
+
if (input.unsetTasks?.length) {
|
|
31522
|
+
if (patch.tenantTasks !== void 0) {
|
|
31523
|
+
throw new Error("org project set: --unset-task cannot be combined with --var tenantTasks or --unset tenantTasks \u2014 pass one or the other");
|
|
31524
|
+
}
|
|
31525
|
+
const names = [];
|
|
31526
|
+
for (const raw of input.unsetTasks) {
|
|
31527
|
+
const name = raw.trim();
|
|
31528
|
+
if (!/^[a-z0-9][a-z0-9-]{0,62}$/.test(name)) {
|
|
31529
|
+
throw new Error(`org project set: --unset-task must be a task name (lowercase kebab-case) \u2014 got ${JSON.stringify(raw)}`);
|
|
31530
|
+
}
|
|
31531
|
+
if (!names.includes(name)) names.push(name);
|
|
31532
|
+
}
|
|
31533
|
+
patch.tenantTasksRemove = names;
|
|
31534
|
+
}
|
|
31613
31535
|
if (input.clearWebProfile) {
|
|
31614
31536
|
patch.oauth = null;
|
|
31615
31537
|
patch.edgeDomains = null;
|
|
31616
31538
|
}
|
|
31617
31539
|
if (Object.keys(patch).length === 0) {
|
|
31618
|
-
throw new Error("org project set: nothing to set - pass --class, --project-type, --deploy-model, --release-track, --var KEY=VALUE, --unset KEY, --unset-secret KEY, and/or --clear-web-profile");
|
|
31540
|
+
throw new Error("org project set: nothing to set - pass --class, --project-type, --deploy-model, --release-track, --var KEY=VALUE, --unset KEY, --unset-secret KEY, --unset-task NAME, and/or --clear-web-profile");
|
|
31619
31541
|
}
|
|
31620
31542
|
return patch;
|
|
31621
31543
|
}
|
|
@@ -35792,8 +35714,24 @@ async function readSuiteStatus(deps) {
|
|
|
35792
35714
|
})));
|
|
35793
35715
|
if (!deps.leadTags || !deps.registryAt) return { suite: "mm-work", members: rows };
|
|
35794
35716
|
const tags = await deps.leadTags().catch(() => []);
|
|
35795
|
-
const latest = (
|
|
35796
|
-
|
|
35717
|
+
const latest = (rc2) => tags.filter((t) => /^v\d+\.\d+\.\d+(?:-rc\.\d+)?$/.test(t) && t.includes("-rc.") === rc2).sort((a, b) => compareVersions(b, a))[0];
|
|
35718
|
+
let rc;
|
|
35719
|
+
let rcLabel = "n/a";
|
|
35720
|
+
const rcTags = tags.filter((t) => /^v\d+\.\d+\.\d+-rc\.\d+$/.test(t)).sort((a, b) => compareVersions(b, a));
|
|
35721
|
+
if (rcTags.length) {
|
|
35722
|
+
const rcHead = await deps.head(rows[0].repo, "rc").catch(() => void 0);
|
|
35723
|
+
if (rcHead === void 0) rcLabel = "unknown";
|
|
35724
|
+
else {
|
|
35725
|
+
for (const t of rcTags) {
|
|
35726
|
+
if ((await deps.tagCommit(rows[0].repo, t).catch(() => void 0))?.toLowerCase() === rcHead?.toLowerCase()) {
|
|
35727
|
+
rc = t;
|
|
35728
|
+
break;
|
|
35729
|
+
}
|
|
35730
|
+
}
|
|
35731
|
+
rcLabel = rc ?? "none at head";
|
|
35732
|
+
}
|
|
35733
|
+
}
|
|
35734
|
+
const leadTag = { rc, main: latest(false) };
|
|
35797
35735
|
const lead = rows[0].stages;
|
|
35798
35736
|
for (const stage of ["rc", "main"]) {
|
|
35799
35737
|
const tag = leadTag[stage];
|
|
@@ -35809,7 +35747,7 @@ async function readSuiteStatus(deps) {
|
|
|
35809
35747
|
}
|
|
35810
35748
|
}
|
|
35811
35749
|
const recorded = leadTag.main && deps.leadReleaseBody ? parseSuiteMapping(await deps.leadReleaseBody(leadTag.main).catch(() => null)) : void 0;
|
|
35812
|
-
return { suite: "mm-work", lead: { dev: lead[0].head, rc:
|
|
35750
|
+
return { suite: "mm-work", lead: { dev: lead[0].head, rc: rcLabel, main: leadTag.main ?? "unknown", ...recorded ? { members: recorded } : {} }, members: rows };
|
|
35813
35751
|
}
|
|
35814
35752
|
var short = (v) => /^[a-f0-9]{40}$/i.test(v) ? v.slice(0, 12) : v;
|
|
35815
35753
|
var proofText = (p) => p ? p.state === "proven" ? "proven" : p.state === "mismatch" ? `mismatch (${p.detail})` : `unprovable (${p.detail})` : "";
|
|
@@ -35851,7 +35789,8 @@ async function setSuiteHold(deps, member, hold) {
|
|
|
35851
35789
|
const after = await deps.read(slug);
|
|
35852
35790
|
if (!after.ok || !after.project) throw new Error(`${found.repo}: write sent but read-back failed; verify with org project get`);
|
|
35853
35791
|
const got = after.project.suiteHold ?? null;
|
|
35854
|
-
|
|
35792
|
+
const same2 = got === null || hold === null ? got === hold : got.reason === hold.reason && got.since === hold.since;
|
|
35793
|
+
if (!same2) throw new Error(`${found.repo}: read-back suiteHold ${JSON.stringify(got)} does not match what was written`);
|
|
35855
35794
|
if (track && after.project.releaseTrack !== track) throw new Error(`${found.repo}: releaseTrack changed from ${track} to ${after.project.releaseTrack ?? "unset"} by the write; restore it with org project set --release-track ${track}`);
|
|
35856
35795
|
return hold ? `${found.repo}: HELD (${hold.reason})` : `${found.repo}: hold cleared`;
|
|
35857
35796
|
}
|
|
@@ -36033,7 +35972,7 @@ async function carriedPins(deps, member, bundles) {
|
|
|
36033
35972
|
}
|
|
36034
35973
|
throw new Error(`${member.repo}: nothing new since rc, but no pinned MM-Work bundle carries development ${member.development.slice(0, 12)}; pin it before the suite cut`);
|
|
36035
35974
|
}
|
|
36036
|
-
function
|
|
35975
|
+
function abandonableCut(recordPath, ownerGo) {
|
|
36037
35976
|
const record = readSuiteCut(recordPath);
|
|
36038
35977
|
if (!record) throw new Error("no suite cut record to abandon");
|
|
36039
35978
|
if (record.leadReleased || record.members.some((m) => m.released)) {
|
|
@@ -36042,6 +35981,19 @@ function suiteAbandon(recordPath, ownerGo = false, now = /* @__PURE__ */ new Dat
|
|
|
36042
35981
|
if (record.pinPr && !ownerGo) {
|
|
36043
35982
|
throw new Error(`the cut already opened pin PR ${record.pinPr}; its pins may be visible, so resume it (suite rcand --owner-go), release it (suite release), or retire it with suite abandon --owner-go`);
|
|
36044
35983
|
}
|
|
35984
|
+
return record;
|
|
35985
|
+
}
|
|
35986
|
+
async function suiteMarkAbandoned(deps, ownerGo = false) {
|
|
35987
|
+
const record = abandonableCut(deps.recordPath, ownerGo);
|
|
35988
|
+
const tags = [
|
|
35989
|
+
...record.members.flatMap((m) => m.tag && m.sha ? [{ repo: m.repo, tag: m.tag, sha: m.sha }] : []),
|
|
35990
|
+
...record.leadRcTag && record.leadRcSha ? [{ repo: SUITE_REPO, tag: record.leadRcTag, sha: record.leadRcSha }] : []
|
|
35991
|
+
];
|
|
35992
|
+
for (const t of tags) await deps.markAbandoned(t.repo, t.tag, t.sha);
|
|
35993
|
+
return tags.map((t) => `${t.repo}@${t.tag}`);
|
|
35994
|
+
}
|
|
35995
|
+
function suiteAbandon(recordPath, ownerGo = false, now = /* @__PURE__ */ new Date()) {
|
|
35996
|
+
const record = abandonableCut(recordPath, ownerGo);
|
|
36045
35997
|
const archive = `${recordPath}.abandoned-${record.leadDevelopment.slice(0, 12)}-${now.toISOString().replace(/[:.]/g, "")}.json`;
|
|
36046
35998
|
if ((0, import_node_fs29.existsSync)(archive)) throw new Error(`abandoned-cut archive already exists at ${archive}; inspect before abandoning again`);
|
|
36047
35999
|
(0, import_node_fs29.renameSync)(recordPath, archive);
|
|
@@ -36928,12 +36880,20 @@ ${lines2.join("\n")}
|
|
|
36928
36880
|
Land ONE development-based cite-only synthesis PR for the whole batch (#4643 supersedes the old cut-from-origin/main wording, which produced main-lineage PRs into development \u2014 the shape portFoldToDevelopment forbids, #4365/#4371): cut the branch FROM origin/development, regenerate the listed conflict content so it cherry-picks cleanly onto current origin/main, and land that development-parented PR with the cite-only marker (the proven #5096 flow), then rerun \`mmi-cli devops hotfix start --from ${batchFrom}\` with the port merge SHA(s) \u2014 never hand-resolve onto main; never a main-parented branch into development; never N serial ports (#3167) \u2014 see https://github.com/mutmutco/MMI-Hub/blob/development/docs/Guides/train-troubleshooting.md#hotfix-pick-refused`;
|
|
36929
36881
|
}
|
|
36930
36882
|
var CHERRY_PICKED_FROM = /\(cherry picked from commit ([0-9a-f]{7,40})\)/g;
|
|
36931
|
-
async function
|
|
36932
|
-
if (sources.length === 0) return [];
|
|
36883
|
+
async function scanHotfixPickGaps(deps, sources, tolerated, rcRef) {
|
|
36884
|
+
if (sources.length === 0) return { gaps: [], rcCarried: 0 };
|
|
36933
36885
|
const namedShas = new Set(sources.map((s) => s.sha.toLowerCase()));
|
|
36934
36886
|
const carried = /* @__PURE__ */ new Set();
|
|
36935
36887
|
const mainOnly = await deps.run("git", ["log", "--no-merges", "--format=%B", "origin/development..origin/main"]);
|
|
36936
36888
|
for (const m of mainOnly.matchAll(CHERRY_PICKED_FROM)) carried.add(m[1].toLowerCase());
|
|
36889
|
+
const notOnRc = /* @__PURE__ */ new Set();
|
|
36890
|
+
const rcPicked = /* @__PURE__ */ new Set();
|
|
36891
|
+
if (rcRef) {
|
|
36892
|
+
(await deps.run("git", ["log", "--no-merges", "--format=%H", `${rcRef}..origin/development`])).split("\n").map((s) => s.trim().toLowerCase()).filter(Boolean).forEach((s) => notOnRc.add(s));
|
|
36893
|
+
const rcOnly = await deps.run("git", ["log", "--no-merges", "--format=%B", `origin/development..${rcRef}`]);
|
|
36894
|
+
for (const m of rcOnly.matchAll(CHERRY_PICKED_FROM)) rcPicked.add(m[1].toLowerCase());
|
|
36895
|
+
}
|
|
36896
|
+
let rcCarried = 0;
|
|
36937
36897
|
const gaps = [];
|
|
36938
36898
|
const devOnly = (await deps.run("git", ["log", "--no-merges", "--reverse", "--format=%H%x09%s", "origin/main..origin/development"])).split("\n").map((s) => s.trim()).filter(Boolean);
|
|
36939
36899
|
for (const line of devOnly) {
|
|
@@ -36941,11 +36901,37 @@ async function findHotfixPickGaps(deps, sources, tolerated) {
|
|
|
36941
36901
|
if (!sha) continue;
|
|
36942
36902
|
const lower = sha.toLowerCase();
|
|
36943
36903
|
if (namedShas.has(lower) || carried.has(lower)) continue;
|
|
36904
|
+
if (rcRef && (!notOnRc.has(lower) || rcPicked.has(lower))) {
|
|
36905
|
+
rcCarried++;
|
|
36906
|
+
continue;
|
|
36907
|
+
}
|
|
36944
36908
|
const paths = (await deps.run("git", ["show", "--no-color", "--name-only", "--pretty=format:", sha])).split("\n").map((s) => s.trim()).filter(Boolean);
|
|
36945
36909
|
if (paths.length === 0 || paths.every((p) => pathIsTolerated(p, tolerated))) continue;
|
|
36946
36910
|
gaps.push({ sha, subject: subjectParts.join(" ") });
|
|
36947
36911
|
}
|
|
36948
|
-
return gaps;
|
|
36912
|
+
return { gaps, rcCarried };
|
|
36913
|
+
}
|
|
36914
|
+
async function resolveActiveRcCandidate(deps) {
|
|
36915
|
+
try {
|
|
36916
|
+
await deps.run("git", ["rev-parse", "--verify", "--quiet", "origin/rc"]);
|
|
36917
|
+
} catch {
|
|
36918
|
+
return { reason: "origin/rc does not exist" };
|
|
36919
|
+
}
|
|
36920
|
+
try {
|
|
36921
|
+
const rcTags = (await deps.run("git", ["tag", "--points-at", "origin/rc", "--list", "v*"])).split("\n").map((s) => s.trim()).filter((t) => /^v\d+\.\d+\.\d+-rc\.\d+$/.test(t));
|
|
36922
|
+
const tag = rcTags.sort((a, b) => Number(a.split("-rc.")[1]) - Number(b.split("-rc.")[1])).pop();
|
|
36923
|
+
if (!tag) return { reason: "no rc tag points at the origin/rc head, so it is not an active candidate" };
|
|
36924
|
+
if ((await deps.run("git", ["ls-remote", "origin", `refs/abandoned-rc/${tag}`])).trim()) {
|
|
36925
|
+
return { reason: `${tag} was abandoned (marker refs/abandoned-rc/${tag} on origin), so it is not an active candidate` };
|
|
36926
|
+
}
|
|
36927
|
+
const released = tag.replace(/-rc\.\d+$/, "");
|
|
36928
|
+
if ((await deps.run("git", ["tag", "--list", released])).trim()) {
|
|
36929
|
+
return { reason: `${released} is already released, so ${tag} is no longer a pending candidate` };
|
|
36930
|
+
}
|
|
36931
|
+
return { ref: "origin/rc", tag };
|
|
36932
|
+
} catch (e) {
|
|
36933
|
+
return { reason: `the origin/rc candidate could not be read (${e.message ?? e})` };
|
|
36934
|
+
}
|
|
36949
36935
|
}
|
|
36950
36936
|
function formatHotfixPickGapRefusal(gaps, tag, fromSpecs) {
|
|
36951
36937
|
const lines2 = gaps.map((g) => ` - ${g.sha.slice(0, 7)} ${g.subject}`);
|
|
@@ -37165,7 +37151,16 @@ async function runHotfixStartLocked(deps, options, doctor) {
|
|
|
37165
37151
|
const label = sources.map((s) => s.label).join(", ");
|
|
37166
37152
|
const { foldPaths, pickTolerated } = await resolveHotfixPickTolerated(deps, deployModel);
|
|
37167
37153
|
const ackedShas = (options.ack ?? []).map((a) => a.trim().toLowerCase()).filter(Boolean);
|
|
37168
|
-
|
|
37154
|
+
let rcCandidate;
|
|
37155
|
+
if (resolveReleaseTrack(meta, void 0, ctx.repo) === "full") {
|
|
37156
|
+
const found = await resolveActiveRcCandidate(deps);
|
|
37157
|
+
if ("reason" in found) notes.push(`pick-completeness guard checked against origin/main only: ${found.reason} (#7799)`);
|
|
37158
|
+
else rcCandidate = found;
|
|
37159
|
+
}
|
|
37160
|
+
const { gaps: pickGaps, rcCarried } = await scanHotfixPickGaps(deps, sources, pickTolerated, rcCandidate?.ref);
|
|
37161
|
+
if (rcCandidate && rcCarried > 0) {
|
|
37162
|
+
notes.push(`pick-completeness: ${rcCarried} development commit(s) carried by rc candidate ${rcCandidate.tag}, not counted as gaps (#7799)`);
|
|
37163
|
+
}
|
|
37169
37164
|
const unacked = pickGaps.filter((g) => {
|
|
37170
37165
|
const sha = g.sha.toLowerCase();
|
|
37171
37166
|
return !ackedShas.some((a) => sha.startsWith(a) || a.startsWith(sha));
|
|
@@ -37258,7 +37253,9 @@ async function runHotfixStartLocked(deps, options, doctor) {
|
|
|
37258
37253
|
|
|
37259
37254
|
<!-- mmi-hotfix-carries: ${activeSources.map((s) => s.sha).join(",")} -->${pickGaps.length > 0 ? `
|
|
37260
37255
|
|
|
37261
|
-
<!-- mmi-hotfix-acked-excluded: ${pickGaps.map((g) => g.sha).join(",")} (#6994 pick-completeness) -->` : ""}
|
|
37256
|
+
<!-- mmi-hotfix-acked-excluded: ${pickGaps.map((g) => g.sha).join(",")} (#6994 pick-completeness) -->` : ""}${rcCandidate && rcCarried > 0 ? `
|
|
37257
|
+
|
|
37258
|
+
<!-- mmi-hotfix-rc-carried: ${rcCarried} commits carried by ${rcCandidate.tag} -->` : ""}
|
|
37262
37259
|
|
|
37263
37260
|
Merge this PR (human-initiated), then run \`mmi-cli devops hotfix release ${tag}\`.`
|
|
37264
37261
|
]));
|
|
@@ -37498,7 +37495,7 @@ async function waitForHotfixRequiredChecks(deps, ctx, mergedSha, required, fresh
|
|
|
37498
37495
|
const recover = hooks.recover ?? transientRecoveryForFailedPhase;
|
|
37499
37496
|
let checks;
|
|
37500
37497
|
try {
|
|
37501
|
-
checks = await wait(deps, ctx, mergedSha, required, freshSince);
|
|
37498
|
+
checks = await wait(deps, ctx, mergedSha, required, freshSince, { exactCommitGreenSuffices: true });
|
|
37502
37499
|
} catch (e) {
|
|
37503
37500
|
const failureText = e instanceof Error ? e.message : String(e);
|
|
37504
37501
|
let recovery;
|
|
@@ -37527,7 +37524,7 @@ release ledger: ${recoveryFailure} - manual intervention required`);
|
|
|
37527
37524
|
|
|
37528
37525
|
${recovery.note}`);
|
|
37529
37526
|
try {
|
|
37530
|
-
checks = await wait(deps, ctx, mergedSha, required, void 0);
|
|
37527
|
+
checks = await wait(deps, ctx, mergedSha, required, void 0, { exactCommitGreenSuffices: true });
|
|
37531
37528
|
} catch (e2) {
|
|
37532
37529
|
const revalidationFailure = e2 instanceof Error ? e2.message : String(e2);
|
|
37533
37530
|
const prior2 = (await ledger.load(anchors).catch(() => void 0))?.phases.ordinaryCi;
|
|
@@ -40044,6 +40041,30 @@ function releaseProgressVerdict(status, subject = "Release") {
|
|
|
40044
40041
|
// src/suite-pin.ts
|
|
40045
40042
|
var import_promises6 = require("node:fs/promises");
|
|
40046
40043
|
var import_node_path34 = require("node:path");
|
|
40044
|
+
var SuitePinError = class _SuitePinError extends Error {
|
|
40045
|
+
diagnostic;
|
|
40046
|
+
constructor(repo, tag, error) {
|
|
40047
|
+
const e = error instanceof _SuitePinError ? { ...error.diagnostic } : error && typeof error === "object" ? error : {};
|
|
40048
|
+
const code = typeof e.code === "number" && Number.isInteger(e.code) && e.code > 0 && e.code <= 255 ? e.code : typeof e.code === "string" && /^(ETIMEDOUT|EACCES|EPERM|EBUSY|ENOENT|ENOSPC|EIO|EEXIST|ECONNRESET|ECONNREFUSED|EPIPE)$/.test(e.code) ? e.code : void 0;
|
|
40049
|
+
const signal = typeof e.signal === "string" && /^(SIGTERM|SIGKILL|SIGINT|SIGHUP|SIGABRT|SIGPIPE)$/.test(e.signal) ? e.signal : void 0;
|
|
40050
|
+
const hint = [error instanceof _SuitePinError ? error.message : e.message, e.stderr].filter((s) => typeof s === "string").map((s) => s.slice(0, 4096)).join("\n");
|
|
40051
|
+
const carriedCategory = error instanceof _SuitePinError && typeof e.category === "string" && ["timeout", "permission", "index-lock", "terminated", "exit", "unknown"].includes(e.category) ? e.category : void 0;
|
|
40052
|
+
const category = carriedCategory ?? (code === "ETIMEDOUT" ? "timeout" : code === "EACCES" || code === "EPERM" || /permission denied/i.test(hint) ? "permission" : /index\.lock.*(?:exists|unable|denied)|unable.*index\.lock/i.test(hint) ? "index-lock" : signal || e.killed === true ? "terminated" : typeof code === "number" ? "exit" : "unknown");
|
|
40053
|
+
const diagnostic = {
|
|
40054
|
+
category,
|
|
40055
|
+
...code !== void 0 ? { code } : {},
|
|
40056
|
+
...signal ? { signal } : {},
|
|
40057
|
+
...typeof e.killed === "boolean" ? { killed: e.killed } : {},
|
|
40058
|
+
...typeof e.durationMs === "number" && Number.isFinite(e.durationMs) && e.durationMs >= 0 && e.durationMs <= 864e5 ? { durationMs: e.durationMs } : {},
|
|
40059
|
+
...e.cleanupFailed === true ? { cleanupFailed: true } : {}
|
|
40060
|
+
};
|
|
40061
|
+
const safeRepo = /^[\w.-]+\/[\w.-]+$/.test(repo) && repo.length <= 200 ? repo : "[invalid repo]";
|
|
40062
|
+
const safeTag = /^v\d+\.\d+\.\d+(?:-rc\.\d+)?$/.test(tag) && tag.length <= 80 ? tag : "[invalid tag]";
|
|
40063
|
+
const reason = /closed without landing/.test(hint) ? "pin PR closed without landing" : /merged but development/.test(hint) ? "merged PR does not match the development pin" : /unreadable|META unreadable/.test(hint) ? "registry unreadable" : /not a declared MM-Work module/.test(hint) ? "not a declared MM-Work module" : /no .* bundle host/.test(hint) ? "bundle host missing" : diagnostic.category === "permission" ? "permission denied" : diagnostic.category;
|
|
40064
|
+
super(`suite pin PR FAILED for ${safeRepo} ${safeTag}: ${reason}; remedy: open/merge the ${SUITE_REPO} development pin PR for ${safeRepo} ${safeTag}, then rerun the same train command; do not recut the tag \u2014 see https://github.com/mutmutco/MMI-Hub/blob/development/docs/Guides/train-troubleshooting.md#suite-pin-pr`);
|
|
40065
|
+
this.diagnostic = diagnostic;
|
|
40066
|
+
}
|
|
40067
|
+
};
|
|
40047
40068
|
async function openSuiteCutPinPr(deps, input) {
|
|
40048
40069
|
if (!/^[a-f0-9]{40}$/.test(input.cut) || !input.pins.length) throw new Error("suite pin PR needs an anchored cut and at least one module");
|
|
40049
40070
|
const ids = input.pins.map((p) => p.module);
|
|
@@ -40186,8 +40207,8 @@ async function openSuiteRestorePinPr(deps, input) {
|
|
|
40186
40207
|
}
|
|
40187
40208
|
async function openSuitePinPr(deps, input) {
|
|
40188
40209
|
const { repo, tag, stage } = input;
|
|
40189
|
-
const remedy = `open/merge the ${SUITE_REPO} development pin PR for ${repo} ${tag}, then rerun the same train command; do not recut the tag \u2014 see https://github.com/mutmutco/MMI-Hub/blob/development/docs/Guides/train-troubleshooting.md#suite-pin-pr`;
|
|
40190
40210
|
let scratch = "";
|
|
40211
|
+
let failure;
|
|
40191
40212
|
try {
|
|
40192
40213
|
const get = JSON.parse(await deps.run("gh", ["api", `repos/${SUITE_REPO}/contents/seed/modules.json?ref=development`]));
|
|
40193
40214
|
if (!get.content) throw new Error("MM-Work seed/modules.json was unreadable");
|
|
@@ -40252,9 +40273,19 @@ async function openSuitePinPr(deps, input) {
|
|
|
40252
40273
|
await run("gh", ["pr", "close", old2.url, "--repo", SUITE_REPO, "--comment", `Superseded by ${pr} (${member.id} ${stage} at ${tag}).`]);
|
|
40253
40274
|
return { status: "pending", pr, url: pin };
|
|
40254
40275
|
} catch (e) {
|
|
40255
|
-
|
|
40276
|
+
failure = new SuitePinError(repo, tag, e);
|
|
40277
|
+
throw failure;
|
|
40256
40278
|
} finally {
|
|
40257
|
-
if (scratch)
|
|
40279
|
+
if (scratch) {
|
|
40280
|
+
try {
|
|
40281
|
+
await (0, import_promises6.rm)(scratch, { recursive: true, force: true });
|
|
40282
|
+
} catch (e) {
|
|
40283
|
+
const cleanupFailure = failure ?? new SuitePinError(repo, tag, e);
|
|
40284
|
+
cleanupFailure.diagnostic.cleanupFailed = true;
|
|
40285
|
+
cleanupFailure.message += "; owned scratch cleanup also failed";
|
|
40286
|
+
if (!failure) throw cleanupFailure;
|
|
40287
|
+
}
|
|
40288
|
+
}
|
|
40258
40289
|
}
|
|
40259
40290
|
}
|
|
40260
40291
|
|
|
@@ -45809,7 +45840,7 @@ async function requireFreshTrainCli(commandName) {
|
|
|
45809
45840
|
process.exit(converge.reexecCode);
|
|
45810
45841
|
}
|
|
45811
45842
|
var GH_TRAIN_TIMEOUT_MS = 3e4;
|
|
45812
|
-
var GH_RUN_WATCH_TIMEOUT_MS =
|
|
45843
|
+
var GH_RUN_WATCH_TIMEOUT_MS = 240 * 6e4;
|
|
45813
45844
|
var NODE_PREPARE_TIMEOUT_MS = 10 * 6e4;
|
|
45814
45845
|
var NODE_VERIFY_TIMEOUT_MS = 5 * 6e4;
|
|
45815
45846
|
var NPM_TRAIN_TIMEOUT_MS = 6e4;
|
|
@@ -46231,10 +46262,43 @@ function emitTrainResult(label, output, out) {
|
|
|
46231
46262
|
if (!out) printLine(output);
|
|
46232
46263
|
else printLine(`Wrote ${label} result to ${out} (UTF-8, ${writeUtf8Receipt(out, output)} bytes)`);
|
|
46233
46264
|
}
|
|
46265
|
+
var TrainPinFollowUpError = class extends Error {
|
|
46266
|
+
constructor(completed, error) {
|
|
46267
|
+
const failure = new SuitePinError(completed.repo, completed.tag, error);
|
|
46268
|
+
super(failure.message);
|
|
46269
|
+
this.completed = completed;
|
|
46270
|
+
this.diagnostic = failure.diagnostic;
|
|
46271
|
+
}
|
|
46272
|
+
completed;
|
|
46273
|
+
diagnostic;
|
|
46274
|
+
};
|
|
46234
46275
|
function writeTrainFailureReceipt(commandName, json, out, error, suiteSolo = false) {
|
|
46235
|
-
|
|
46236
|
-
|
|
46237
|
-
|
|
46276
|
+
const completed = error instanceof TrainPinFollowUpError ? error.completed : void 0;
|
|
46277
|
+
if (!out && !completed) return;
|
|
46278
|
+
const command = commandName.includes(" ") ? commandName : `${commandName} --apply`;
|
|
46279
|
+
const legs = completed?.releaseVerdict?.legs ?? (completed?.command === "hotfix-release" ? hotfixFollowUpLegs(completed.runs, completed.foldStatus, ledgerAlignmentFollowUpOutcome(completed.ledger), completed.foldNote, completed.deployNote) : []);
|
|
46280
|
+
const failedLeg = { leg: "suite-pin-pr", status: "failed", error: error.message };
|
|
46281
|
+
const failure = completed ? {
|
|
46282
|
+
...completed,
|
|
46283
|
+
suiteSolo: true,
|
|
46284
|
+
suitePin: { status: "failed", error: error.message, diagnostic: error.diagnostic },
|
|
46285
|
+
failedLeg: "suite-pin-pr",
|
|
46286
|
+
...completed.command === "hotfix-release" ? { legs: [...legs, failedLeg] } : {},
|
|
46287
|
+
releaseVerdict: {
|
|
46288
|
+
releaseUrl: null,
|
|
46289
|
+
...completed.releaseVerdict,
|
|
46290
|
+
releaseStatus: "succeeded",
|
|
46291
|
+
followUpStatus: "failed",
|
|
46292
|
+
promoted: true,
|
|
46293
|
+
tag: completed.tag,
|
|
46294
|
+
legs: [...legs, failedLeg]
|
|
46295
|
+
}
|
|
46296
|
+
} : { command, ...suiteSolo ? { suiteSolo: true } : {} };
|
|
46297
|
+
const evidence = JSON.stringify({ ...failure, verdict: "failed", error: error.message }, null, 2);
|
|
46298
|
+
const output = json ? evidence : `mmi-cli ${command}: FAILED \u2014 ${completed ? `${completed.repo} ${completed.tag} published; suite-pin-pr follow-up FAILED \u2014 ` : ""}${error.message}${completed ? `
|
|
46299
|
+
Completed train evidence and failed follow-up:
|
|
46300
|
+
${evidence}` : ""}`;
|
|
46301
|
+
emitTrainResult(command, output, out);
|
|
46238
46302
|
}
|
|
46239
46303
|
function renderTenantRedeploy(r) {
|
|
46240
46304
|
return `mmi-cli devops runtime tenant redeploy: ${r.repo} ${r.stage} (ref=${r.ref}) [${r.deployModel}]; ${renderDeployLine(r)}`;
|
|
@@ -46260,8 +46324,15 @@ function registerTrainOperationsCommands(program3, runProjectInfoSyncCallback) {
|
|
|
46260
46324
|
const root = (await execFileP("git", ["rev-parse", "--show-toplevel"])).stdout.trim();
|
|
46261
46325
|
return openSuitePinPr({ root, run: async (file, args, cwd) => (await execFileP(file, args, { cwd, timeout: GIT_TIMEOUT_MS })).stdout }, { repo, tag, stage: "main" });
|
|
46262
46326
|
}
|
|
46327
|
+
async function pinCompletedMember(completed) {
|
|
46328
|
+
try {
|
|
46329
|
+
return await pinSoloMember(completed.repo, completed.tag);
|
|
46330
|
+
} catch (e) {
|
|
46331
|
+
throw new TrainPinFollowUpError(completed, e);
|
|
46332
|
+
}
|
|
46333
|
+
}
|
|
46263
46334
|
for (const commandName of ["rcand", "release"]) {
|
|
46264
|
-
const trainCmd = program3.command(commandName).description(`plan ${commandName} train operations; mutations require explicit project-admin or master approval`).option("--json", "machine-readable output").option("--watch", "block on the deploy/publish workflow runs and report their outcomes").option("--apply", "execute the guarded repo-authorized train after explicit approval (Hub: master only)").option("--suite-solo", "release one suite module alone (requires --owner-go;
|
|
46335
|
+
const trainCmd = program3.command(commandName).description(`plan ${commandName} train operations; mutations require explicit project-admin or master approval`).option("--json", "machine-readable output").option("--watch", "block on the deploy/publish workflow runs and report their outcomes").option("--apply", "execute the guarded repo-authorized train after explicit approval (Hub: master only)").option("--suite-solo", "release one suite module alone, or cut the suite lead alone (requires --owner-go; a member rcand is always refused)").option("--owner-go", "explicit owner approval for a suite-solo backend release").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)").option("--out <path>", "write the terminal result (--apply, --resume, --abort, --retry-publish) to this file as UTF-8 (no BOM) instead of stdout \u2014 the shell-free receipt path (#5983/#6068)");
|
|
46265
46336
|
const RELEASE_ONLY_FLAGS = [
|
|
46266
46337
|
{ flags: "--announce-summary-file <path>", description: "agent-curated 3-6 line release Slack summary; required for a new MMI-Hub --apply (#883/#3901) and for a product repo whose registry META sets `releaseChannel` (#6523), in its `releaseLanguage`; nothing is posted to a project channel without it" },
|
|
46267
46338
|
{ 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)" },
|
|
@@ -46447,7 +46518,7 @@ function registerTrainOperationsCommands(program3, runProjectInfoSyncCallback) {
|
|
|
46447
46518
|
const raw2 = await withReleaseProgress(
|
|
46448
46519
|
trainApplyDeps(),
|
|
46449
46520
|
"Checking the existing release candidate",
|
|
46450
|
-
(deps) => runRcandResume(deps, { watch: o.watch })
|
|
46521
|
+
(deps) => runRcandResume(deps, { watch: o.watch, suiteSolo: o.suiteSolo })
|
|
46451
46522
|
);
|
|
46452
46523
|
return emitRcandContinuation("rcand --resume", raw2, o);
|
|
46453
46524
|
}
|
|
@@ -46463,7 +46534,7 @@ function registerTrainOperationsCommands(program3, runProjectInfoSyncCallback) {
|
|
|
46463
46534
|
() => runProjectInfoSyncLeg(runProjectInfoSyncCallback, result.repo)
|
|
46464
46535
|
);
|
|
46465
46536
|
const { followUpStatus, releaseVerdict } = buildResumeReleaseVerdict(result, projectInfoSync);
|
|
46466
|
-
const suitePin = o.suiteSolo ? await
|
|
46537
|
+
const suitePin = o.suiteSolo ? await pinCompletedMember({ ...result, projectInfoSync, releaseVerdict }) : void 0;
|
|
46467
46538
|
if (suitePin?.status === "pending") {
|
|
46468
46539
|
releaseVerdict.legs.push({ leg: "suite-pin-pr", status: "pending", error: `merge ${suitePin.pr} on MM-Work development, then verify the pin` });
|
|
46469
46540
|
releaseVerdict.followUpStatus = "pending";
|
|
@@ -46476,6 +46547,7 @@ function registerTrainOperationsCommands(program3, runProjectInfoSyncCallback) {
|
|
|
46476
46547
|
} catch (e) {
|
|
46477
46548
|
applyTrainFollowUpExit("failed");
|
|
46478
46549
|
consoleIo.err(`mmi-cli: Release: ${progressSubject(commandName)} blocked \u2014 see the error below`);
|
|
46550
|
+
writeTrainFailureReceipt(`${commandName} --resume`, o.json === true, o.out, e, o.suiteSolo === true);
|
|
46479
46551
|
return failGraceful(`${commandName} --resume: ${e.message}`);
|
|
46480
46552
|
}
|
|
46481
46553
|
}
|
|
@@ -46529,8 +46601,8 @@ function registerTrainOperationsCommands(program3, runProjectInfoSyncCallback) {
|
|
|
46529
46601
|
() => runProjectInfoSyncLeg(runProjectInfoSyncCallback, result.repo)
|
|
46530
46602
|
);
|
|
46531
46603
|
}
|
|
46532
|
-
const suitePin = o.suiteSolo && commandName === "release" ? await pinSoloMember(result.repo, result.tag) : void 0;
|
|
46533
46604
|
const verdict = buildReleaseVerdict(commandName, result, projectInfoSync);
|
|
46605
|
+
const suitePin = o.suiteSolo && commandName === "release" ? await pinCompletedMember({ ...result, projectInfoSync, releaseVerdict: verdict.releaseVerdict }) : void 0;
|
|
46534
46606
|
if (suitePin?.status === "pending" && verdict.releaseVerdict) {
|
|
46535
46607
|
verdict.releaseVerdict.legs.push({ leg: "suite-pin-pr", status: "pending", error: `merge ${suitePin.pr} on MM-Work development, then verify the pin` });
|
|
46536
46608
|
verdict.releaseVerdict.followUpStatus = "pending";
|
|
@@ -46559,6 +46631,8 @@ function registerTrainOperationsCommands(program3, runProjectInfoSyncCallback) {
|
|
|
46559
46631
|
const repo = o.repo ?? await resolveRepo();
|
|
46560
46632
|
const targets = commandName === "rcand" && !o.repo ? await resolveRcandPlanTargets() : void 0;
|
|
46561
46633
|
let releaseTrack;
|
|
46634
|
+
let suite;
|
|
46635
|
+
let preOneZeroPlan = false;
|
|
46562
46636
|
let releaseChannel = null;
|
|
46563
46637
|
let releaseLanguage = "en";
|
|
46564
46638
|
if (repo) {
|
|
@@ -46575,14 +46649,24 @@ function registerTrainOperationsCommands(program3, runProjectInfoSyncCallback) {
|
|
|
46575
46649
|
}
|
|
46576
46650
|
const raw = read.project?.releaseTrack;
|
|
46577
46651
|
if (isReleaseTrack(raw)) releaseTrack = raw;
|
|
46652
|
+
suite = read.project?.suite;
|
|
46653
|
+
preOneZeroPlan = preOneZeroInForce(read.project ?? void 0);
|
|
46578
46654
|
releaseChannel = read.project?.releaseChannel?.trim() || null;
|
|
46579
46655
|
releaseLanguage = read.project?.releaseLanguage?.trim() || "en";
|
|
46580
46656
|
}
|
|
46581
|
-
|
|
46657
|
+
let releaseTarget;
|
|
46658
|
+
if (commandName === "release" && !o.repo && !o.dev && releaseTrack !== "direct" && releaseTrack !== "trunk" && repo?.toLowerCase() !== "mutmutco/mmi-hub") {
|
|
46659
|
+
try {
|
|
46660
|
+
releaseTarget = { releaseTag: (await execFileP("node", ["scripts/next-version.mjs", "release", ...preOneZeroPlan ? ["--pre-one-zero"] : []], { timeout: 15e3 })).stdout.trim() };
|
|
46661
|
+
} catch (e) {
|
|
46662
|
+
releaseTarget = { releaseTagError: (e.stderr || e.message).trim() };
|
|
46663
|
+
}
|
|
46664
|
+
}
|
|
46665
|
+
const steps = trainPlan(commandName, { ...targets ?? {}, ...releaseTarget ?? {}, repo, dev: o.dev, releaseTrack, suite });
|
|
46582
46666
|
let doctor;
|
|
46583
46667
|
if (repo && (!o.repo || (await resolveRepo())?.toLowerCase() === repo.toLowerCase())) {
|
|
46584
46668
|
try {
|
|
46585
|
-
doctor = await runTrainDoctor({ lane: commandName, dev: commandName === "release" && o.dev === true, heal: false, train: trainApplyDeps(), repo });
|
|
46669
|
+
doctor = await runTrainDoctor({ lane: commandName, dev: commandName === "release" && o.dev === true, heal: false, train: trainApplyDeps(), repo, suiteSolo: o.suiteSolo === true });
|
|
46586
46670
|
} catch (e) {
|
|
46587
46671
|
consoleIo.err(`mmi-cli: train doctor did not complete: ${e.message}`);
|
|
46588
46672
|
}
|
|
@@ -46670,10 +46754,10 @@ function registerTrainOperationsCommands(program3, runProjectInfoSyncCallback) {
|
|
|
46670
46754
|
foldPort
|
|
46671
46755
|
}));
|
|
46672
46756
|
} catch (e) {
|
|
46673
|
-
if (sub === "release"
|
|
46674
|
-
|
|
46675
|
-
emitTrainResult("hotfix release", o.json ? JSON.stringify({ command: "hotfix release", suiteSolo: true, suitePin: { status: "failed", error }, verdict: "failed" }, null, 2) : `hotfix release: suite pin PR FAILED \u2014 ${error}`, o.out);
|
|
46757
|
+
if (sub === "release") {
|
|
46758
|
+
writeTrainFailureReceipt("hotfix release", o.json || Boolean(hotfixCmd.opts().json), o.out, e, o.suiteSolo === true);
|
|
46676
46759
|
}
|
|
46760
|
+
applyTrainFollowUpExit("failed");
|
|
46677
46761
|
return failGraceful(`hotfix ${sub}: ${e.message}`);
|
|
46678
46762
|
}
|
|
46679
46763
|
}
|
|
@@ -46701,7 +46785,7 @@ function registerTrainOperationsCommands(program3, runProjectInfoSyncCallback) {
|
|
|
46701
46785
|
}
|
|
46702
46786
|
return runHotfixSub("release", async () => {
|
|
46703
46787
|
const result = await runHotfixRelease(trainApplyDeps(), version, { announceSummaryFile: o.announceSummaryFile, carries: o.carries ? [o.carries] : [], suiteSolo: o.suiteSolo, ...o.ack ? { ack: o.ack.split(",").map((s) => s.trim()).filter(Boolean) } : {} });
|
|
46704
|
-
const suitePin = o.suiteSolo ? await
|
|
46788
|
+
const suitePin = o.suiteSolo ? await pinCompletedMember(result) : void 0;
|
|
46705
46789
|
const projectInfoSync = await runProjectInfoSyncLeg(runProjectInfoSyncCallback, result.repo);
|
|
46706
46790
|
return { ...result, projectInfoSync, ...o.suiteSolo ? { suiteSolo: true, suitePin } : {} };
|
|
46707
46791
|
}, o, renderHotfixRelease);
|
|
@@ -47037,6 +47121,13 @@ function suiteTrainIo(root, summaryFile, memberSummaryFiles = {}) {
|
|
|
47037
47121
|
publishMain,
|
|
47038
47122
|
preflightMain,
|
|
47039
47123
|
pin: (input) => openSuiteCutPinPr({ root, run: (file, args, cwd) => exec(file, args, cwd, GIT_TIMEOUT_MS) }, input),
|
|
47124
|
+
markAbandoned: async (repo, tag, sha) => {
|
|
47125
|
+
try {
|
|
47126
|
+
await gh(["api", "-X", "POST", `repos/${repo}/git/refs`, "-f", `ref=refs/abandoned-rc/${tag}`, "-f", `sha=${sha}`]);
|
|
47127
|
+
} catch (e) {
|
|
47128
|
+
if (!/already exists/i.test(e.message)) throw e;
|
|
47129
|
+
}
|
|
47130
|
+
},
|
|
47040
47131
|
restoreMain: (input) => openSuiteRestorePinPr({ root, run: (file, args, cwd) => exec(file, args, cwd, GIT_TIMEOUT_MS) }, input),
|
|
47041
47132
|
verifyLeadCut: async (sha) => {
|
|
47042
47133
|
const current = await gh(["api", `repos/${SUITE_REPO}/git/ref/heads/development`, "--jq", ".object.sha"]);
|
|
@@ -47201,9 +47292,11 @@ function registerSuiteCommands(program3) {
|
|
|
47201
47292
|
const unlock = await acquireTrainWriterLock(root, "suite abandon");
|
|
47202
47293
|
let result;
|
|
47203
47294
|
let restored;
|
|
47295
|
+
let marked;
|
|
47204
47296
|
try {
|
|
47205
47297
|
const io = suiteTrainIo(root);
|
|
47206
47298
|
restored = o.ownerGo === true ? await suiteRestoreMainPins(io) : void 0;
|
|
47299
|
+
marked = await suiteMarkAbandoned(io, o.ownerGo === true);
|
|
47207
47300
|
result = suiteAbandon(io.recordPath, o.ownerGo === true);
|
|
47208
47301
|
} finally {
|
|
47209
47302
|
await unlock();
|
|
@@ -47212,6 +47305,7 @@ function registerSuiteCommands(program3) {
|
|
|
47212
47305
|
`suite abandon: archived the cut record to ${result.archive}`,
|
|
47213
47306
|
...result.orphanTags.length ? [" member rc tags left in place (immutable; the next cut reuses one still unreleased on its rc head, #7450):", ...result.orphanTags.map((t) => ` - ${t}`)] : [" no member rc tag was minted"],
|
|
47214
47307
|
...restored ? [` main pins the cut planned for unreleased tags are restored by ${restored.pr} (${restored.status}, #7758)`] : [],
|
|
47308
|
+
...marked.length ? [` marked abandoned on origin, so the hotfix guard stops counting them as active (#7801): ${marked.join(", ")}`] : [],
|
|
47215
47309
|
" the next suite rcand --owner-go re-snapshots every member"
|
|
47216
47310
|
].join("\n"));
|
|
47217
47311
|
} catch (e) {
|
|
@@ -47730,7 +47824,6 @@ Next command: ${report.nextCommand}`);
|
|
|
47730
47824
|
var RESTART_LINE = "\u21BB Restart Claude to finish.";
|
|
47731
47825
|
var HOST_RESTART_LINES = {
|
|
47732
47826
|
codex: "\u21BB Restart Codex to finish.",
|
|
47733
|
-
cursor: "\u21BB Restart Cursor to finish.",
|
|
47734
47827
|
jervcode: "\u21BB Restart JervCode to finish."
|
|
47735
47828
|
};
|
|
47736
47829
|
function restartLineForHost(host) {
|
|
@@ -47962,92 +48055,6 @@ var surfaces_default = {
|
|
|
47962
48055
|
"Hosted tools and disabled or untrusted hooks bypass local enforcement."
|
|
47963
48056
|
]
|
|
47964
48057
|
},
|
|
47965
|
-
{
|
|
47966
|
-
token: "cursor",
|
|
47967
|
-
displayName: "Cursor",
|
|
47968
|
-
lifecycle: "active",
|
|
47969
|
-
artifactIds: [
|
|
47970
|
-
"mmi-cursor-plugin",
|
|
47971
|
-
"mmi-skills",
|
|
47972
|
-
"mmi-cli"
|
|
47973
|
-
],
|
|
47974
|
-
plannedArtifactIds: [],
|
|
47975
|
-
assembly: {
|
|
47976
|
-
mode: "package-directory",
|
|
47977
|
-
rootPath: "packages/cursor-plugin",
|
|
47978
|
-
sync: [
|
|
47979
|
-
{
|
|
47980
|
-
mode: "directories",
|
|
47981
|
-
sourcePath: "skills",
|
|
47982
|
-
targetPath: "packages/cursor-plugin/skills"
|
|
47983
|
-
},
|
|
47984
|
-
{
|
|
47985
|
-
mode: "files",
|
|
47986
|
-
sourcePath: "bin",
|
|
47987
|
-
targetPath: "packages/cursor-plugin/bin",
|
|
47988
|
-
include: [
|
|
47989
|
-
"mmi-cli",
|
|
47990
|
-
"mmi-cli.cmd"
|
|
47991
|
-
]
|
|
47992
|
-
},
|
|
47993
|
-
{
|
|
47994
|
-
mode: "files",
|
|
47995
|
-
sourcePath: "cli",
|
|
47996
|
-
targetPath: "packages/cursor-plugin/cli",
|
|
47997
|
-
include: [
|
|
47998
|
-
"dist/index.cjs",
|
|
47999
|
-
"dist/main.cjs",
|
|
48000
|
-
"package.json"
|
|
48001
|
-
]
|
|
48002
|
-
},
|
|
48003
|
-
{
|
|
48004
|
-
mode: "files",
|
|
48005
|
-
sourcePath: ".cursor-plugin",
|
|
48006
|
-
targetPath: "packages/cursor-plugin/.cursor-plugin",
|
|
48007
|
-
include: [
|
|
48008
|
-
"plugin.json"
|
|
48009
|
-
]
|
|
48010
|
-
}
|
|
48011
|
-
]
|
|
48012
|
-
},
|
|
48013
|
-
install: {
|
|
48014
|
-
mechanism: "github-source",
|
|
48015
|
-
locator: "~/.cursor/plugins/local/mmi"
|
|
48016
|
-
},
|
|
48017
|
-
upgrade: {
|
|
48018
|
-
mechanism: "reinstall",
|
|
48019
|
-
reload: "workspace"
|
|
48020
|
-
},
|
|
48021
|
-
skills: {
|
|
48022
|
-
delivery: "plugin",
|
|
48023
|
-
sourceSurfaceId: "mmi-skills",
|
|
48024
|
-
artifactId: "mmi-skills",
|
|
48025
|
-
invocation: {
|
|
48026
|
-
entry: "/epic",
|
|
48027
|
-
any: "/<skill>"
|
|
48028
|
-
}
|
|
48029
|
-
},
|
|
48030
|
-
cli: {
|
|
48031
|
-
delivery: "standalone",
|
|
48032
|
-
artifactId: "mmi-cli"
|
|
48033
|
-
},
|
|
48034
|
-
ownership: {
|
|
48035
|
-
trust: "host",
|
|
48036
|
-
cache: "mmi-hub",
|
|
48037
|
-
repair: "mmi-hub"
|
|
48038
|
-
},
|
|
48039
|
-
certification: {
|
|
48040
|
-
hostCommand: "cursor-agent",
|
|
48041
|
-
versionArgs: [
|
|
48042
|
-
"--version"
|
|
48043
|
-
]
|
|
48044
|
-
},
|
|
48045
|
-
enforcementCeilings: [
|
|
48046
|
-
"Cursor can replace MCP results only; it cannot rewrite ordinary tool output after execution, though MMI still detects secret-shaped output.",
|
|
48047
|
-
"Final assistant output is outside Cursor hook control.",
|
|
48048
|
-
"Cloud agents do not run sessionStart or sessionEnd hooks, and disabled plugins or untrusted workspaces enforce nothing."
|
|
48049
|
-
]
|
|
48050
|
-
},
|
|
48051
48058
|
{
|
|
48052
48059
|
token: "jervcode",
|
|
48053
48060
|
displayName: "JervCode",
|
|
@@ -48277,38 +48284,6 @@ var surfaces_default = {
|
|
|
48277
48284
|
},
|
|
48278
48285
|
publishVisibility: "public"
|
|
48279
48286
|
},
|
|
48280
|
-
{
|
|
48281
|
-
id: "mmi-cursor-plugin",
|
|
48282
|
-
classification: "packaging",
|
|
48283
|
-
kind: "plugin",
|
|
48284
|
-
ownerPath: "packages/cursor-plugin/package.json",
|
|
48285
|
-
deliveryPath: "packages/cursor-plugin/package.json",
|
|
48286
|
-
delivery: "npm",
|
|
48287
|
-
applicability: "Cursor IDE and Cursor Agent CLI; npm package @mutmutco/cursor-plugin carrying the plugin manifest and skills. The managed checkout at ~/.cursor/plugins/local/mmi is switched to each release by the fleet updater (#4945).",
|
|
48288
|
-
versionCoordinated: true,
|
|
48289
|
-
surfaceToken: "cursor",
|
|
48290
|
-
versionPaths: [
|
|
48291
|
-
{
|
|
48292
|
-
path: ".cursor-plugin/plugin.json",
|
|
48293
|
-
pointer: "version"
|
|
48294
|
-
},
|
|
48295
|
-
{
|
|
48296
|
-
path: "packages/cursor-plugin/package.json",
|
|
48297
|
-
pointer: "version"
|
|
48298
|
-
}
|
|
48299
|
-
],
|
|
48300
|
-
additionalPaths: [
|
|
48301
|
-
"packages/cursor-plugin/.cursor-plugin",
|
|
48302
|
-
"packages/cursor-plugin/skills",
|
|
48303
|
-
"packages/cursor-plugin/bin",
|
|
48304
|
-
"packages/cursor-plugin/cli"
|
|
48305
|
-
],
|
|
48306
|
-
artifactIdentity: {
|
|
48307
|
-
kind: "npm-pack",
|
|
48308
|
-
packagePath: "packages/cursor-plugin"
|
|
48309
|
-
},
|
|
48310
|
-
publishVisibility: "public"
|
|
48311
|
-
},
|
|
48312
48287
|
{
|
|
48313
48288
|
id: "mmi-claude-marketplace",
|
|
48314
48289
|
classification: "delivery",
|
|
@@ -48600,7 +48575,7 @@ var surfaces_default = {
|
|
|
48600
48575
|
};
|
|
48601
48576
|
|
|
48602
48577
|
// src/surface-doctor.ts
|
|
48603
|
-
var TOKENS = /* @__PURE__ */ new Set(["claude", "codex", "
|
|
48578
|
+
var TOKENS = /* @__PURE__ */ new Set(["claude", "codex", "jervcode", "hermes"]);
|
|
48604
48579
|
function isActiveToken(value) {
|
|
48605
48580
|
return TOKENS.has(value);
|
|
48606
48581
|
}
|
|
@@ -50507,20 +50482,12 @@ async function readOrFail(read) {
|
|
|
50507
50482
|
async function defaultRegistryRead() {
|
|
50508
50483
|
return fetchSchedulesList(registryClientDeps(await loadConfig()));
|
|
50509
50484
|
}
|
|
50510
|
-
async function
|
|
50511
|
-
|
|
50512
|
-
}
|
|
50513
|
-
function modelRoleKeyOf(row) {
|
|
50514
|
-
const model = row.model;
|
|
50515
|
-
return typeof model === "string" && model.trim() ? model : void 0;
|
|
50516
|
-
}
|
|
50517
|
-
async function fetchNotebook(client = defaultGitHubClient(), readRegistry = defaultRegistryRead, readProjects = defaultProjectsRead, readRunVerdicts = defaultRunVerdictRead) {
|
|
50518
|
-
const [gh2, aws, registryRead, projectsRead, runVerdictRead] = await Promise.all([
|
|
50485
|
+
async function fetchNotebook(client = defaultGitHubClient(), readRegistry = defaultRegistryRead, readProjects = defaultProjectsRead) {
|
|
50486
|
+
const [gh2, aws, registryRead, projectsRead] = await Promise.all([
|
|
50519
50487
|
githubEntries(client),
|
|
50520
50488
|
awsEntries(),
|
|
50521
50489
|
readOrFail(readRegistry),
|
|
50522
|
-
readOrFail(readProjects)
|
|
50523
|
-
readOrFail(readRunVerdicts)
|
|
50490
|
+
readOrFail(readProjects)
|
|
50524
50491
|
]);
|
|
50525
50492
|
const readFailures = [];
|
|
50526
50493
|
if (registryRead.state === "failed") {
|
|
@@ -50530,16 +50497,10 @@ async function fetchNotebook(client = defaultGitHubClient(), readRegistry = defa
|
|
|
50530
50497
|
readFailures.push(`registry: could not read the projects list \u2014 schedulesMode opt-outs unresolved (nothing suppressed) \u2014 ${projectsRead.error}`);
|
|
50531
50498
|
}
|
|
50532
50499
|
const registry2 = registryRead.state === "ok" ? registryRead.value : null;
|
|
50533
|
-
const runVerdicts = runVerdictRead.state === "ok" ? runVerdictRead.value : null;
|
|
50534
50500
|
const projects = projectsRead.state === "ok" ? projectsRead.value : null;
|
|
50535
|
-
const decoratedAws = decorateAwsModel(
|
|
50536
|
-
aws.entries,
|
|
50537
|
-
registry2?.map((r) => ({ id: r.id, executor: r.executor, model: modelRoleKeyOf(r) })) ?? null,
|
|
50538
|
-
runVerdicts
|
|
50539
|
-
);
|
|
50540
50501
|
const recon = assembleReconciliation(gh2.entries, new Set(gh2.readRepos), registry2, new Set(gh2.workflowNames), {
|
|
50541
50502
|
// #3286: the harbour side joins registry rows against the live aws-scheduler clocks by scheduleId.
|
|
50542
|
-
awsEntries:
|
|
50503
|
+
awsEntries: aws.entries,
|
|
50543
50504
|
schedulerRead: Boolean(aws.schedulerRead)
|
|
50544
50505
|
}, new Set(gh2.disabledWorkflowNames));
|
|
50545
50506
|
const selfManaged = /* @__PURE__ */ new Set();
|
|
@@ -50553,7 +50514,7 @@ async function fetchNotebook(client = defaultGitHubClient(), readRegistry = defa
|
|
|
50553
50514
|
);
|
|
50554
50515
|
const driftLines = reconciliation.map(renderDrift);
|
|
50555
50516
|
return {
|
|
50556
|
-
entries: sortEntries([...gh2.entries, ...
|
|
50517
|
+
entries: sortEntries([...gh2.entries, ...aws.entries, ...DECLARED_ENTRIES]),
|
|
50557
50518
|
incomplete: [...gh2.incomplete, ...aws.incomplete, ...recon.incomplete, ...readFailures],
|
|
50558
50519
|
drift: driftLines,
|
|
50559
50520
|
reconciliation,
|
|
@@ -53278,7 +53239,7 @@ projectDeploy.command("retire [owner/repo]").description("retire (tombstone) one
|
|
|
53278
53239
|
}
|
|
53279
53240
|
if (!result.ok) return failGraceful(`org project deploy retire: ${result.error ?? "read failed"}`);
|
|
53280
53241
|
});
|
|
53281
|
-
project.command("set [owner/repo]").description("upsert project META (idempotent merge; master-gated)").addOption(new Option("--class <class>", "deployable | content").choices(["deployable", "content"])).addOption(new Option("--project-type <type>", `${PROJECT_TYPES.join(" | ")} (capability shape)`).choices([...PROJECT_TYPES])).addOption(new Option("--deploy-model <model>", `${DEPLOY_MODELS.join(" | ")} (release/deploy path; none means no Hub deploy registration)`).choices([...DEPLOY_MODELS])).addOption(new Option("--release-track <track>", `${RELEASE_TRACKS.join(" | ")} (branch topology; direct skips rc)`).choices([...RELEASE_TRACKS])).option("--var <KEY=VALUE...>", settableVarHelp()).option("--set <KEY=VALUE...>", "alias of --var (one KEY=VALUE per flag; repeat the flag to set several)").option("--secrets-file <path>", 'read the #2244 secrets catalog map (JSON) from a file and merge it per entry into the existing catalog (drop ONE entry with --unset-secret KEY; clear ALL with --unset secrets --confirm-clear-secrets). SHAPE: a JSON object keyed by env name, each entry {"key":"UPPER_SNAKE" (repeat the env name), "purpose":"<non-empty>", "group":"<e.g. auth|database>", "owner":"<github login>", "stages":[] (empty = the one stageless shared value; else any of dev/rc/main), "consumers":["runtime"|"box"|?], "provider":"<optional>"}').option("--unset-secret <KEY...>", "remove exactly these #2244 catalog entries (repeatable; KEY or KEY@dev|rc|main). Refuses while a stage still requires the key \u2014 drop it from requiredRuntimeSecrets/requiredBuildSecrets first").option("--confirm-clear-secrets", "required to let --unset secrets wipe the WHOLE catalog (the refusal names how many entries that removes)").option("--unset <KEY...>", "META field to remove (repeatable): oauth, requiredRuntimeSecrets, requiredBuildSecrets, secrets (the WHOLE catalog \u2014 needs --confirm-clear-secrets), edgeDomains, requiredGcpApis, publishRequired, releaseChannel, releaseLanguage").option("--clear-web-profile", "remove web-only registry fields (oauth, edgeDomains) for non-web/content projects").option("--json", "machine-readable output").action(async (repoOrSlug, o) => {
|
|
53242
|
+
project.command("set [owner/repo]").description("upsert project META (idempotent merge; master-gated)").addOption(new Option("--class <class>", "deployable | content").choices(["deployable", "content"])).addOption(new Option("--project-type <type>", `${PROJECT_TYPES.join(" | ")} (capability shape)`).choices([...PROJECT_TYPES])).addOption(new Option("--deploy-model <model>", `${DEPLOY_MODELS.join(" | ")} (release/deploy path; none means no Hub deploy registration)`).choices([...DEPLOY_MODELS])).addOption(new Option("--release-track <track>", `${RELEASE_TRACKS.join(" | ")} (branch topology; direct skips rc)`).choices([...RELEASE_TRACKS])).option("--var <KEY=VALUE...>", settableVarHelp()).option("--set <KEY=VALUE...>", "alias of --var (one KEY=VALUE per flag; repeat the flag to set several)").option("--secrets-file <path>", 'read the #2244 secrets catalog map (JSON) from a file and merge it per entry into the existing catalog (drop ONE entry with --unset-secret KEY; clear ALL with --unset secrets --confirm-clear-secrets). SHAPE: a JSON object keyed by env name, each entry {"key":"UPPER_SNAKE" (repeat the env name), "purpose":"<non-empty>", "group":"<e.g. auth|database>", "owner":"<github login>", "stages":[] (empty = the one stageless shared value; else any of dev/rc/main), "consumers":["runtime"|"box"|?], "provider":"<optional>"}').option("--unset-secret <KEY...>", "remove exactly these #2244 catalog entries (repeatable; KEY or KEY@dev|rc|main). Refuses while a stage still requires the key \u2014 drop it from requiredRuntimeSecrets/requiredBuildSecrets first").option("--unset-task <NAME...>", "retire exactly these tenant tasks (repeatable; lowercase kebab-case names). Refuses a name that is not declared. tenantTasks merges per entry and is capped at 20 stored in total").option("--confirm-clear-secrets", "required to let --unset secrets wipe the WHOLE catalog (the refusal names how many entries that removes)").option("--unset <KEY...>", "META field to remove (repeatable): oauth, requiredRuntimeSecrets, requiredBuildSecrets, secrets (the WHOLE catalog \u2014 needs --confirm-clear-secrets), edgeDomains, requiredGcpApis, publishRequired, releaseChannel, releaseLanguage").option("--clear-web-profile", "remove web-only registry fields (oauth, edgeDomains) for non-web/content projects").option("--json", "machine-readable output").action(async (repoOrSlug, o) => {
|
|
53282
53243
|
const cfg = await loadConfig();
|
|
53283
53244
|
let target;
|
|
53284
53245
|
try {
|
|
@@ -53309,6 +53270,7 @@ project.command("set [owner/repo]").description("upsert project META (idempotent
|
|
|
53309
53270
|
vars,
|
|
53310
53271
|
unsets: o.unset ?? [],
|
|
53311
53272
|
unsetSecrets: o.unsetSecret ?? [],
|
|
53273
|
+
unsetTasks: o.unsetTask ?? [],
|
|
53312
53274
|
clearWebProfile: Boolean(o.clearWebProfile)
|
|
53313
53275
|
});
|
|
53314
53276
|
} catch (e) {
|
|
@@ -53319,6 +53281,8 @@ project.command("set [owner/repo]").description("upsert project META (idempotent
|
|
|
53319
53281
|
if (clearRefusal) return fail(clearRefusal);
|
|
53320
53282
|
const removalRefusal = secretRemovalRefusal(existing, patch);
|
|
53321
53283
|
if (removalRefusal) return fail(removalRefusal);
|
|
53284
|
+
const taskRefusal = tenantTaskWriteRefusal(existing, patch);
|
|
53285
|
+
if (taskRefusal) return fail(taskRefusal);
|
|
53322
53286
|
const boardError = boardLinkWriteError(patch, existing);
|
|
53323
53287
|
if (boardError) return fail(`org project set: ${boardError}`);
|
|
53324
53288
|
const res = await upsertProject(slug, { ...patch, repo }, registryClientDeps(cfg));
|