@mutmutco/codex-plugin 4.5.86 → 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 +358 -398
- 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"];
|
|
@@ -21372,6 +21251,10 @@ async function retireRcRuntime(deps, ctx, model, deployStatus, releasedRcSha) {
|
|
|
21372
21251
|
};
|
|
21373
21252
|
}
|
|
21374
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
|
+
}
|
|
21375
21258
|
await deps.run("git", ["fetch", "origin", "rc"]);
|
|
21376
21259
|
const rcNow = clean2(await deps.run("git", ["rev-parse", "origin/rc"]));
|
|
21377
21260
|
if (rcNow !== releasedRcSha) {
|
|
@@ -21904,14 +21787,6 @@ function installedClaudePluginVersion() {
|
|
|
21904
21787
|
return void 0;
|
|
21905
21788
|
}
|
|
21906
21789
|
}
|
|
21907
|
-
function manifestVersion(path2) {
|
|
21908
|
-
try {
|
|
21909
|
-
const manifest = JSON.parse((0, import_node_fs20.readFileSync)(path2, "utf8"));
|
|
21910
|
-
return typeof manifest.version === "string" && manifest.version.trim() ? manifest.version.trim() : void 0;
|
|
21911
|
-
} catch {
|
|
21912
|
-
return void 0;
|
|
21913
|
-
}
|
|
21914
|
-
}
|
|
21915
21790
|
function readHermesPluginEvidence(env = process.env) {
|
|
21916
21791
|
const host = hermesConfigRoot(env);
|
|
21917
21792
|
const root = hermesPluginRoot(env);
|
|
@@ -21963,9 +21838,6 @@ function readHermesPluginEvidence(env = process.env) {
|
|
|
21963
21838
|
function installedSurfacePluginVersion(surface) {
|
|
21964
21839
|
const token = surfaceToken(surface);
|
|
21965
21840
|
if (token === "hermes") return readHermesPluginEvidence().installedVersion;
|
|
21966
|
-
if (token === "cursor") {
|
|
21967
|
-
return manifestVersion((0, import_node_path18.join)(cursorLocalPluginRoot(), ".cursor-plugin", "plugin.json"));
|
|
21968
|
-
}
|
|
21969
21841
|
if (token === "jervcode") {
|
|
21970
21842
|
return installedJervCodePackageVersion();
|
|
21971
21843
|
}
|
|
@@ -23747,17 +23619,23 @@ async function reverifyPersistedRunPhases(deps, path2, anchors, input) {
|
|
|
23747
23619
|
async (current) => {
|
|
23748
23620
|
if (!current) return { value: { ledger: void 0, notes } };
|
|
23749
23621
|
const phases = { ...current.phases };
|
|
23750
|
-
for (const phase of ["deploy", "publish"]) {
|
|
23622
|
+
for (const phase of input.phases ?? ["deploy", "publish"]) {
|
|
23751
23623
|
const rec = phases[phase];
|
|
23752
23624
|
if (rec.updatedAt === "never") continue;
|
|
23753
23625
|
if (rec.runId == null) {
|
|
23754
|
-
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`);
|
|
23755
23627
|
continue;
|
|
23756
23628
|
}
|
|
23757
|
-
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") {
|
|
23758
23636
|
throw new ReleaseLedgerError(
|
|
23759
23637
|
"invalid-record",
|
|
23760
|
-
`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)`
|
|
23761
23639
|
);
|
|
23762
23640
|
}
|
|
23763
23641
|
const runRepo = rec.runRepo ?? input.repo;
|
|
@@ -23792,7 +23670,8 @@ async function reverifyPersistedRunPhases(deps, path2, anchors, input) {
|
|
|
23792
23670
|
}
|
|
23793
23671
|
if (meta.conclusion === "success") {
|
|
23794
23672
|
if (rec.state !== "complete") {
|
|
23795
|
-
|
|
23673
|
+
const { error: _error, ...withoutPriorError } = rec;
|
|
23674
|
+
phases[phase] = { ...withoutPriorError, state: "complete", note: `re-verified live: ${liveLabel} concluded success`, updatedAt: now().toISOString() };
|
|
23796
23675
|
notes.push(`${phase}: ${liveLabel} re-verified green live`);
|
|
23797
23676
|
}
|
|
23798
23677
|
continue;
|
|
@@ -25070,6 +24949,19 @@ async function runTrainDoctor(input) {
|
|
|
25070
24949
|
try {
|
|
25071
24950
|
let ledger = parseReleaseLedger(ledgerRaw);
|
|
25072
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
|
+
}
|
|
25073
24965
|
const deployModel = typeof meta?.deployModel === "string" && isDeployModel(meta.deployModel) ? meta.deployModel : void 0;
|
|
25074
24966
|
if (healLedger && deployModel && derived.legs.some((l) => (l.phase === "deploy" || l.phase === "publish") && l.state === "pending")) {
|
|
25075
24967
|
const run = await deps.healRunLegs(train, cwd, deployModel);
|
|
@@ -26174,7 +26066,9 @@ async function completeMainRelease(deps, ctx, meta, deployModel, watch, options,
|
|
|
26174
26066
|
const ledgerAnchors = { repo: ctx.repo, tag, tagSha: releaseSha };
|
|
26175
26067
|
try {
|
|
26176
26068
|
deps.warn?.("Release: Checking the package before publishing");
|
|
26069
|
+
const dryRunStart = (deps.now ?? Date.now)();
|
|
26177
26070
|
await verifyPublishDryRun(deps, ctx, meta, deployModel);
|
|
26071
|
+
deps.warn?.(`Release: publish dry run took ${(((deps.now ?? Date.now)() - dryRunStart) / 1e3).toFixed(1)}s`);
|
|
26178
26072
|
} catch (e) {
|
|
26179
26073
|
throw await recoverFailedFold(deps, e, startBranch, preFold.mainSha, resumeCommand);
|
|
26180
26074
|
}
|
|
@@ -26412,7 +26306,7 @@ async function runRcandResumeLocked(deps, options) {
|
|
|
26412
26306
|
await requireCleanTree(deps);
|
|
26413
26307
|
await runGitRemoteRead(deps, ["fetch", "origin", "--tags"]);
|
|
26414
26308
|
const meta = requireProjectMetaForTrain(await loadProjectMeta(deps, ctx), ctx.repo);
|
|
26415
|
-
const suiteRefusal = suiteMemberRefusal(ctx.repo, meta, "rcand",
|
|
26309
|
+
const suiteRefusal = suiteMemberRefusal(ctx.repo, meta, "rcand", options.suiteSolo === true, deps.suiteCut);
|
|
26416
26310
|
if (suiteRefusal) throw new Error(`rcand --resume: ${suiteRefusal}`);
|
|
26417
26311
|
const branchHints = await loadReleaseTrackBranchHints(deps);
|
|
26418
26312
|
if (isHubControlRepo(ctx.repo) || resolveReleaseTrack(meta, branchHints) === "direct") {
|
|
@@ -27725,7 +27619,6 @@ async function listWorkflowPaths(deps, repo, branch) {
|
|
|
27725
27619
|
return e?.status === 404 ? [] : void 0;
|
|
27726
27620
|
}
|
|
27727
27621
|
}
|
|
27728
|
-
var AGENT_PR_BOOKKEEPING_CONTEXTS = /* @__PURE__ */ new Set(["guard", "verdict"]);
|
|
27729
27622
|
function gateWorkflowFiles(prWorkflowPaths) {
|
|
27730
27623
|
const files = prWorkflowPaths.map((p) => p.slice(p.lastIndexOf("/") + 1)).filter((f) => f !== "agent-pr.yml");
|
|
27731
27624
|
return files.length ? files : DEFAULT_GATE_FILES;
|
|
@@ -28062,8 +27955,7 @@ async function auditRepoCi(repo, callerDeps) {
|
|
|
28062
27955
|
}
|
|
28063
27956
|
}
|
|
28064
27957
|
if (explicitNoCi) {
|
|
28065
|
-
const
|
|
28066
|
-
const emitted = resolved?.filter((c) => !AGENT_PR_BOOKKEEPING_CONTEXTS.has(c));
|
|
27958
|
+
const emitted = await getEmittedPrContexts();
|
|
28067
27959
|
if (emitted && emitted.length > 0) {
|
|
28068
27960
|
checks.push({
|
|
28069
27961
|
ok: false,
|
|
@@ -28194,7 +28086,7 @@ async function auditRepoCi(repo, callerDeps) {
|
|
|
28194
28086
|
if (hasRequiredChecks) {
|
|
28195
28087
|
const gateFiles = new Set(gateWorkflowFiles(prWorkflowPaths));
|
|
28196
28088
|
const gateWorkflows = (await getBranchWorkflows() ?? []).filter((wf) => gateFiles.has(wf.path.slice(wf.path.lastIndexOf("/") + 1)));
|
|
28197
|
-
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))));
|
|
28198
28090
|
checks.push({
|
|
28199
28091
|
ok: unblocking.length === 0,
|
|
28200
28092
|
label: UNBLOCKING_GATE_JOBS_LABEL,
|
|
@@ -28217,8 +28109,7 @@ async function auditRepoCi(repo, callerDeps) {
|
|
|
28217
28109
|
for (const ref of releaseBranches) {
|
|
28218
28110
|
emittedByBranch[ref] = await resolveEmittedPrContexts(deps, repo, ref.replace("refs/heads/", ""));
|
|
28219
28111
|
}
|
|
28220
|
-
const
|
|
28221
|
-
const unreachable = unreachableReleaseContexts({ requiredContexts: liveContexts, releaseBranches, emittedByBranch, ignore });
|
|
28112
|
+
const unreachable = unreachableReleaseContexts({ requiredContexts: liveContexts, releaseBranches, emittedByBranch, ignore: TRAIN_PR_ONLY_CONTEXTS });
|
|
28222
28113
|
checks.push({
|
|
28223
28114
|
ok: unreachable.length === 0,
|
|
28224
28115
|
label: RELEASE_BRANCH_REACHABLE_LABEL,
|
|
@@ -28685,8 +28576,7 @@ async function authoritativeSafetyHold(authority, deps, repo, baseBranch, meta)
|
|
|
28685
28576
|
if (authority.coversReleaseBranches && authority.source !== "registry META requiredChecks") {
|
|
28686
28577
|
const track = resolveReleaseTrack(meta, void 0, repo);
|
|
28687
28578
|
const releaseBranches = branchesForTrack(track).filter((b) => b !== baseBranch);
|
|
28688
|
-
const
|
|
28689
|
-
const requiredForReleaseBranches = authority.contexts.filter((context) => !ignore.has(context));
|
|
28579
|
+
const requiredForReleaseBranches = authority.contexts.filter((context) => !TRAIN_PR_ONLY_CONTEXTS.has(context));
|
|
28690
28580
|
for (const branch of releaseBranches) {
|
|
28691
28581
|
if (await branchPresence(deps, repo, branch) !== true) continue;
|
|
28692
28582
|
const emittedThere = await resolveEmittedPrContexts(deps, repo, branch);
|
|
@@ -30507,7 +30397,7 @@ function trainPlan(command, options = {}) {
|
|
|
30507
30397
|
{ label: "fold the version bump into the release commit \u2014 runs inside the apply step, no separate bump PR", gated: true },
|
|
30508
30398
|
{ label: "tag release and publish GitHub Release", gated: true },
|
|
30509
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 },
|
|
30510
|
-
{ 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 }] : [],
|
|
30511
30401
|
{ label: "roll development forward and align rc to the released main", gated: true },
|
|
30512
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 }
|
|
30513
30403
|
];
|
|
@@ -30515,6 +30405,8 @@ function trainPlan(command, options = {}) {
|
|
|
30515
30405
|
return [
|
|
30516
30406
|
authority,
|
|
30517
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 },
|
|
30518
30410
|
{ label: "verify registry META for this project", command: "mmi-cli oracle org project get <owner/repo>", gated: true },
|
|
30519
30411
|
{ label: "preflight required main secret names", command: "mmi-cli vault secrets preflight --stage main --repo <owner/repo>", gated: true },
|
|
30520
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 },
|
|
@@ -31409,7 +31301,7 @@ var SETTABLE_VAR_HINTS = {
|
|
|
31409
31301
|
requiredGcpApis: "comma-string",
|
|
31410
31302
|
requiredRuntimeSecrets: 'JSON stage map, e.g. {"dev":["KEY"],"rc":["KEY"],"main":["KEY"]}',
|
|
31411
31303
|
requiredBuildSecrets: 'JSON flat array, e.g. ["NODE_AUTH_TOKEN=@github-packages-token"]',
|
|
31412
|
-
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',
|
|
31413
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",
|
|
31414
31306
|
edgeDomains: "JSON {dev,rc,main} domain map",
|
|
31415
31307
|
statusOptions: "JSON name\u2192id map",
|
|
@@ -31440,6 +31332,18 @@ function duplicateVarKeyAcrossFlags(vars, sets) {
|
|
|
31440
31332
|
}
|
|
31441
31333
|
return null;
|
|
31442
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
|
+
}
|
|
31443
31347
|
function secretRemovalRefusal(existing, patch) {
|
|
31444
31348
|
const remove2 = Array.isArray(patch.secretsRemove) ? patch.secretsRemove : [];
|
|
31445
31349
|
if (remove2.length === 0) return null;
|
|
@@ -31614,12 +31518,26 @@ function buildProjectSetPatch(input) {
|
|
|
31614
31518
|
}
|
|
31615
31519
|
patch.secretsRemove = keys;
|
|
31616
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
|
+
}
|
|
31617
31535
|
if (input.clearWebProfile) {
|
|
31618
31536
|
patch.oauth = null;
|
|
31619
31537
|
patch.edgeDomains = null;
|
|
31620
31538
|
}
|
|
31621
31539
|
if (Object.keys(patch).length === 0) {
|
|
31622
|
-
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");
|
|
31623
31541
|
}
|
|
31624
31542
|
return patch;
|
|
31625
31543
|
}
|
|
@@ -35796,8 +35714,24 @@ async function readSuiteStatus(deps) {
|
|
|
35796
35714
|
})));
|
|
35797
35715
|
if (!deps.leadTags || !deps.registryAt) return { suite: "mm-work", members: rows };
|
|
35798
35716
|
const tags = await deps.leadTags().catch(() => []);
|
|
35799
|
-
const latest = (
|
|
35800
|
-
|
|
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) };
|
|
35801
35735
|
const lead = rows[0].stages;
|
|
35802
35736
|
for (const stage of ["rc", "main"]) {
|
|
35803
35737
|
const tag = leadTag[stage];
|
|
@@ -35813,7 +35747,7 @@ async function readSuiteStatus(deps) {
|
|
|
35813
35747
|
}
|
|
35814
35748
|
}
|
|
35815
35749
|
const recorded = leadTag.main && deps.leadReleaseBody ? parseSuiteMapping(await deps.leadReleaseBody(leadTag.main).catch(() => null)) : void 0;
|
|
35816
|
-
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 };
|
|
35817
35751
|
}
|
|
35818
35752
|
var short = (v) => /^[a-f0-9]{40}$/i.test(v) ? v.slice(0, 12) : v;
|
|
35819
35753
|
var proofText = (p) => p ? p.state === "proven" ? "proven" : p.state === "mismatch" ? `mismatch (${p.detail})` : `unprovable (${p.detail})` : "";
|
|
@@ -35855,7 +35789,8 @@ async function setSuiteHold(deps, member, hold) {
|
|
|
35855
35789
|
const after = await deps.read(slug);
|
|
35856
35790
|
if (!after.ok || !after.project) throw new Error(`${found.repo}: write sent but read-back failed; verify with org project get`);
|
|
35857
35791
|
const got = after.project.suiteHold ?? null;
|
|
35858
|
-
|
|
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`);
|
|
35859
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}`);
|
|
35860
35795
|
return hold ? `${found.repo}: HELD (${hold.reason})` : `${found.repo}: hold cleared`;
|
|
35861
35796
|
}
|
|
@@ -36037,7 +35972,7 @@ async function carriedPins(deps, member, bundles) {
|
|
|
36037
35972
|
}
|
|
36038
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`);
|
|
36039
35974
|
}
|
|
36040
|
-
function
|
|
35975
|
+
function abandonableCut(recordPath, ownerGo) {
|
|
36041
35976
|
const record = readSuiteCut(recordPath);
|
|
36042
35977
|
if (!record) throw new Error("no suite cut record to abandon");
|
|
36043
35978
|
if (record.leadReleased || record.members.some((m) => m.released)) {
|
|
@@ -36046,6 +35981,19 @@ function suiteAbandon(recordPath, ownerGo = false, now = /* @__PURE__ */ new Dat
|
|
|
36046
35981
|
if (record.pinPr && !ownerGo) {
|
|
36047
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`);
|
|
36048
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);
|
|
36049
35997
|
const archive = `${recordPath}.abandoned-${record.leadDevelopment.slice(0, 12)}-${now.toISOString().replace(/[:.]/g, "")}.json`;
|
|
36050
35998
|
if ((0, import_node_fs29.existsSync)(archive)) throw new Error(`abandoned-cut archive already exists at ${archive}; inspect before abandoning again`);
|
|
36051
35999
|
(0, import_node_fs29.renameSync)(recordPath, archive);
|
|
@@ -36932,12 +36880,20 @@ ${lines2.join("\n")}
|
|
|
36932
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`;
|
|
36933
36881
|
}
|
|
36934
36882
|
var CHERRY_PICKED_FROM = /\(cherry picked from commit ([0-9a-f]{7,40})\)/g;
|
|
36935
|
-
async function
|
|
36936
|
-
if (sources.length === 0) return [];
|
|
36883
|
+
async function scanHotfixPickGaps(deps, sources, tolerated, rcRef) {
|
|
36884
|
+
if (sources.length === 0) return { gaps: [], rcCarried: 0 };
|
|
36937
36885
|
const namedShas = new Set(sources.map((s) => s.sha.toLowerCase()));
|
|
36938
36886
|
const carried = /* @__PURE__ */ new Set();
|
|
36939
36887
|
const mainOnly = await deps.run("git", ["log", "--no-merges", "--format=%B", "origin/development..origin/main"]);
|
|
36940
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;
|
|
36941
36897
|
const gaps = [];
|
|
36942
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);
|
|
36943
36899
|
for (const line of devOnly) {
|
|
@@ -36945,11 +36901,37 @@ async function findHotfixPickGaps(deps, sources, tolerated) {
|
|
|
36945
36901
|
if (!sha) continue;
|
|
36946
36902
|
const lower = sha.toLowerCase();
|
|
36947
36903
|
if (namedShas.has(lower) || carried.has(lower)) continue;
|
|
36904
|
+
if (rcRef && (!notOnRc.has(lower) || rcPicked.has(lower))) {
|
|
36905
|
+
rcCarried++;
|
|
36906
|
+
continue;
|
|
36907
|
+
}
|
|
36948
36908
|
const paths = (await deps.run("git", ["show", "--no-color", "--name-only", "--pretty=format:", sha])).split("\n").map((s) => s.trim()).filter(Boolean);
|
|
36949
36909
|
if (paths.length === 0 || paths.every((p) => pathIsTolerated(p, tolerated))) continue;
|
|
36950
36910
|
gaps.push({ sha, subject: subjectParts.join(" ") });
|
|
36951
36911
|
}
|
|
36952
|
-
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
|
+
}
|
|
36953
36935
|
}
|
|
36954
36936
|
function formatHotfixPickGapRefusal(gaps, tag, fromSpecs) {
|
|
36955
36937
|
const lines2 = gaps.map((g) => ` - ${g.sha.slice(0, 7)} ${g.subject}`);
|
|
@@ -37169,7 +37151,16 @@ async function runHotfixStartLocked(deps, options, doctor) {
|
|
|
37169
37151
|
const label = sources.map((s) => s.label).join(", ");
|
|
37170
37152
|
const { foldPaths, pickTolerated } = await resolveHotfixPickTolerated(deps, deployModel);
|
|
37171
37153
|
const ackedShas = (options.ack ?? []).map((a) => a.trim().toLowerCase()).filter(Boolean);
|
|
37172
|
-
|
|
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
|
+
}
|
|
37173
37164
|
const unacked = pickGaps.filter((g) => {
|
|
37174
37165
|
const sha = g.sha.toLowerCase();
|
|
37175
37166
|
return !ackedShas.some((a) => sha.startsWith(a) || a.startsWith(sha));
|
|
@@ -37262,7 +37253,9 @@ async function runHotfixStartLocked(deps, options, doctor) {
|
|
|
37262
37253
|
|
|
37263
37254
|
<!-- mmi-hotfix-carries: ${activeSources.map((s) => s.sha).join(",")} -->${pickGaps.length > 0 ? `
|
|
37264
37255
|
|
|
37265
|
-
<!-- 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} -->` : ""}
|
|
37266
37259
|
|
|
37267
37260
|
Merge this PR (human-initiated), then run \`mmi-cli devops hotfix release ${tag}\`.`
|
|
37268
37261
|
]));
|
|
@@ -37502,7 +37495,7 @@ async function waitForHotfixRequiredChecks(deps, ctx, mergedSha, required, fresh
|
|
|
37502
37495
|
const recover = hooks.recover ?? transientRecoveryForFailedPhase;
|
|
37503
37496
|
let checks;
|
|
37504
37497
|
try {
|
|
37505
|
-
checks = await wait(deps, ctx, mergedSha, required, freshSince);
|
|
37498
|
+
checks = await wait(deps, ctx, mergedSha, required, freshSince, { exactCommitGreenSuffices: true });
|
|
37506
37499
|
} catch (e) {
|
|
37507
37500
|
const failureText = e instanceof Error ? e.message : String(e);
|
|
37508
37501
|
let recovery;
|
|
@@ -37531,7 +37524,7 @@ release ledger: ${recoveryFailure} - manual intervention required`);
|
|
|
37531
37524
|
|
|
37532
37525
|
${recovery.note}`);
|
|
37533
37526
|
try {
|
|
37534
|
-
checks = await wait(deps, ctx, mergedSha, required, void 0);
|
|
37527
|
+
checks = await wait(deps, ctx, mergedSha, required, void 0, { exactCommitGreenSuffices: true });
|
|
37535
37528
|
} catch (e2) {
|
|
37536
37529
|
const revalidationFailure = e2 instanceof Error ? e2.message : String(e2);
|
|
37537
37530
|
const prior2 = (await ledger.load(anchors).catch(() => void 0))?.phases.ordinaryCi;
|
|
@@ -40048,6 +40041,30 @@ function releaseProgressVerdict(status, subject = "Release") {
|
|
|
40048
40041
|
// src/suite-pin.ts
|
|
40049
40042
|
var import_promises6 = require("node:fs/promises");
|
|
40050
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
|
+
};
|
|
40051
40068
|
async function openSuiteCutPinPr(deps, input) {
|
|
40052
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");
|
|
40053
40070
|
const ids = input.pins.map((p) => p.module);
|
|
@@ -40190,8 +40207,8 @@ async function openSuiteRestorePinPr(deps, input) {
|
|
|
40190
40207
|
}
|
|
40191
40208
|
async function openSuitePinPr(deps, input) {
|
|
40192
40209
|
const { repo, tag, stage } = input;
|
|
40193
|
-
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`;
|
|
40194
40210
|
let scratch = "";
|
|
40211
|
+
let failure;
|
|
40195
40212
|
try {
|
|
40196
40213
|
const get = JSON.parse(await deps.run("gh", ["api", `repos/${SUITE_REPO}/contents/seed/modules.json?ref=development`]));
|
|
40197
40214
|
if (!get.content) throw new Error("MM-Work seed/modules.json was unreadable");
|
|
@@ -40256,9 +40273,19 @@ async function openSuitePinPr(deps, input) {
|
|
|
40256
40273
|
await run("gh", ["pr", "close", old2.url, "--repo", SUITE_REPO, "--comment", `Superseded by ${pr} (${member.id} ${stage} at ${tag}).`]);
|
|
40257
40274
|
return { status: "pending", pr, url: pin };
|
|
40258
40275
|
} catch (e) {
|
|
40259
|
-
|
|
40276
|
+
failure = new SuitePinError(repo, tag, e);
|
|
40277
|
+
throw failure;
|
|
40260
40278
|
} finally {
|
|
40261
|
-
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
|
+
}
|
|
40262
40289
|
}
|
|
40263
40290
|
}
|
|
40264
40291
|
|
|
@@ -45813,7 +45840,7 @@ async function requireFreshTrainCli(commandName) {
|
|
|
45813
45840
|
process.exit(converge.reexecCode);
|
|
45814
45841
|
}
|
|
45815
45842
|
var GH_TRAIN_TIMEOUT_MS = 3e4;
|
|
45816
|
-
var GH_RUN_WATCH_TIMEOUT_MS =
|
|
45843
|
+
var GH_RUN_WATCH_TIMEOUT_MS = 240 * 6e4;
|
|
45817
45844
|
var NODE_PREPARE_TIMEOUT_MS = 10 * 6e4;
|
|
45818
45845
|
var NODE_VERIFY_TIMEOUT_MS = 5 * 6e4;
|
|
45819
45846
|
var NPM_TRAIN_TIMEOUT_MS = 6e4;
|
|
@@ -46235,10 +46262,43 @@ function emitTrainResult(label, output, out) {
|
|
|
46235
46262
|
if (!out) printLine(output);
|
|
46236
46263
|
else printLine(`Wrote ${label} result to ${out} (UTF-8, ${writeUtf8Receipt(out, output)} bytes)`);
|
|
46237
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
|
+
};
|
|
46238
46275
|
function writeTrainFailureReceipt(commandName, json, out, error, suiteSolo = false) {
|
|
46239
|
-
|
|
46240
|
-
|
|
46241
|
-
|
|
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);
|
|
46242
46302
|
}
|
|
46243
46303
|
function renderTenantRedeploy(r) {
|
|
46244
46304
|
return `mmi-cli devops runtime tenant redeploy: ${r.repo} ${r.stage} (ref=${r.ref}) [${r.deployModel}]; ${renderDeployLine(r)}`;
|
|
@@ -46264,8 +46324,15 @@ function registerTrainOperationsCommands(program3, runProjectInfoSyncCallback) {
|
|
|
46264
46324
|
const root = (await execFileP("git", ["rev-parse", "--show-toplevel"])).stdout.trim();
|
|
46265
46325
|
return openSuitePinPr({ root, run: async (file, args, cwd) => (await execFileP(file, args, { cwd, timeout: GIT_TIMEOUT_MS })).stdout }, { repo, tag, stage: "main" });
|
|
46266
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
|
+
}
|
|
46267
46334
|
for (const commandName of ["rcand", "release"]) {
|
|
46268
|
-
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)");
|
|
46269
46336
|
const RELEASE_ONLY_FLAGS = [
|
|
46270
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" },
|
|
46271
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)" },
|
|
@@ -46451,7 +46518,7 @@ function registerTrainOperationsCommands(program3, runProjectInfoSyncCallback) {
|
|
|
46451
46518
|
const raw2 = await withReleaseProgress(
|
|
46452
46519
|
trainApplyDeps(),
|
|
46453
46520
|
"Checking the existing release candidate",
|
|
46454
|
-
(deps) => runRcandResume(deps, { watch: o.watch })
|
|
46521
|
+
(deps) => runRcandResume(deps, { watch: o.watch, suiteSolo: o.suiteSolo })
|
|
46455
46522
|
);
|
|
46456
46523
|
return emitRcandContinuation("rcand --resume", raw2, o);
|
|
46457
46524
|
}
|
|
@@ -46467,7 +46534,7 @@ function registerTrainOperationsCommands(program3, runProjectInfoSyncCallback) {
|
|
|
46467
46534
|
() => runProjectInfoSyncLeg(runProjectInfoSyncCallback, result.repo)
|
|
46468
46535
|
);
|
|
46469
46536
|
const { followUpStatus, releaseVerdict } = buildResumeReleaseVerdict(result, projectInfoSync);
|
|
46470
|
-
const suitePin = o.suiteSolo ? await
|
|
46537
|
+
const suitePin = o.suiteSolo ? await pinCompletedMember({ ...result, projectInfoSync, releaseVerdict }) : void 0;
|
|
46471
46538
|
if (suitePin?.status === "pending") {
|
|
46472
46539
|
releaseVerdict.legs.push({ leg: "suite-pin-pr", status: "pending", error: `merge ${suitePin.pr} on MM-Work development, then verify the pin` });
|
|
46473
46540
|
releaseVerdict.followUpStatus = "pending";
|
|
@@ -46480,6 +46547,7 @@ function registerTrainOperationsCommands(program3, runProjectInfoSyncCallback) {
|
|
|
46480
46547
|
} catch (e) {
|
|
46481
46548
|
applyTrainFollowUpExit("failed");
|
|
46482
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);
|
|
46483
46551
|
return failGraceful(`${commandName} --resume: ${e.message}`);
|
|
46484
46552
|
}
|
|
46485
46553
|
}
|
|
@@ -46533,8 +46601,8 @@ function registerTrainOperationsCommands(program3, runProjectInfoSyncCallback) {
|
|
|
46533
46601
|
() => runProjectInfoSyncLeg(runProjectInfoSyncCallback, result.repo)
|
|
46534
46602
|
);
|
|
46535
46603
|
}
|
|
46536
|
-
const suitePin = o.suiteSolo && commandName === "release" ? await pinSoloMember(result.repo, result.tag) : void 0;
|
|
46537
46604
|
const verdict = buildReleaseVerdict(commandName, result, projectInfoSync);
|
|
46605
|
+
const suitePin = o.suiteSolo && commandName === "release" ? await pinCompletedMember({ ...result, projectInfoSync, releaseVerdict: verdict.releaseVerdict }) : void 0;
|
|
46538
46606
|
if (suitePin?.status === "pending" && verdict.releaseVerdict) {
|
|
46539
46607
|
verdict.releaseVerdict.legs.push({ leg: "suite-pin-pr", status: "pending", error: `merge ${suitePin.pr} on MM-Work development, then verify the pin` });
|
|
46540
46608
|
verdict.releaseVerdict.followUpStatus = "pending";
|
|
@@ -46563,6 +46631,8 @@ function registerTrainOperationsCommands(program3, runProjectInfoSyncCallback) {
|
|
|
46563
46631
|
const repo = o.repo ?? await resolveRepo();
|
|
46564
46632
|
const targets = commandName === "rcand" && !o.repo ? await resolveRcandPlanTargets() : void 0;
|
|
46565
46633
|
let releaseTrack;
|
|
46634
|
+
let suite;
|
|
46635
|
+
let preOneZeroPlan = false;
|
|
46566
46636
|
let releaseChannel = null;
|
|
46567
46637
|
let releaseLanguage = "en";
|
|
46568
46638
|
if (repo) {
|
|
@@ -46579,14 +46649,24 @@ function registerTrainOperationsCommands(program3, runProjectInfoSyncCallback) {
|
|
|
46579
46649
|
}
|
|
46580
46650
|
const raw = read.project?.releaseTrack;
|
|
46581
46651
|
if (isReleaseTrack(raw)) releaseTrack = raw;
|
|
46652
|
+
suite = read.project?.suite;
|
|
46653
|
+
preOneZeroPlan = preOneZeroInForce(read.project ?? void 0);
|
|
46582
46654
|
releaseChannel = read.project?.releaseChannel?.trim() || null;
|
|
46583
46655
|
releaseLanguage = read.project?.releaseLanguage?.trim() || "en";
|
|
46584
46656
|
}
|
|
46585
|
-
|
|
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 });
|
|
46586
46666
|
let doctor;
|
|
46587
46667
|
if (repo && (!o.repo || (await resolveRepo())?.toLowerCase() === repo.toLowerCase())) {
|
|
46588
46668
|
try {
|
|
46589
|
-
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 });
|
|
46590
46670
|
} catch (e) {
|
|
46591
46671
|
consoleIo.err(`mmi-cli: train doctor did not complete: ${e.message}`);
|
|
46592
46672
|
}
|
|
@@ -46674,10 +46754,10 @@ function registerTrainOperationsCommands(program3, runProjectInfoSyncCallback) {
|
|
|
46674
46754
|
foldPort
|
|
46675
46755
|
}));
|
|
46676
46756
|
} catch (e) {
|
|
46677
|
-
if (sub === "release"
|
|
46678
|
-
|
|
46679
|
-
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);
|
|
46680
46759
|
}
|
|
46760
|
+
applyTrainFollowUpExit("failed");
|
|
46681
46761
|
return failGraceful(`hotfix ${sub}: ${e.message}`);
|
|
46682
46762
|
}
|
|
46683
46763
|
}
|
|
@@ -46705,7 +46785,7 @@ function registerTrainOperationsCommands(program3, runProjectInfoSyncCallback) {
|
|
|
46705
46785
|
}
|
|
46706
46786
|
return runHotfixSub("release", async () => {
|
|
46707
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) } : {} });
|
|
46708
|
-
const suitePin = o.suiteSolo ? await
|
|
46788
|
+
const suitePin = o.suiteSolo ? await pinCompletedMember(result) : void 0;
|
|
46709
46789
|
const projectInfoSync = await runProjectInfoSyncLeg(runProjectInfoSyncCallback, result.repo);
|
|
46710
46790
|
return { ...result, projectInfoSync, ...o.suiteSolo ? { suiteSolo: true, suitePin } : {} };
|
|
46711
46791
|
}, o, renderHotfixRelease);
|
|
@@ -47041,6 +47121,13 @@ function suiteTrainIo(root, summaryFile, memberSummaryFiles = {}) {
|
|
|
47041
47121
|
publishMain,
|
|
47042
47122
|
preflightMain,
|
|
47043
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
|
+
},
|
|
47044
47131
|
restoreMain: (input) => openSuiteRestorePinPr({ root, run: (file, args, cwd) => exec(file, args, cwd, GIT_TIMEOUT_MS) }, input),
|
|
47045
47132
|
verifyLeadCut: async (sha) => {
|
|
47046
47133
|
const current = await gh(["api", `repos/${SUITE_REPO}/git/ref/heads/development`, "--jq", ".object.sha"]);
|
|
@@ -47205,9 +47292,11 @@ function registerSuiteCommands(program3) {
|
|
|
47205
47292
|
const unlock = await acquireTrainWriterLock(root, "suite abandon");
|
|
47206
47293
|
let result;
|
|
47207
47294
|
let restored;
|
|
47295
|
+
let marked;
|
|
47208
47296
|
try {
|
|
47209
47297
|
const io = suiteTrainIo(root);
|
|
47210
47298
|
restored = o.ownerGo === true ? await suiteRestoreMainPins(io) : void 0;
|
|
47299
|
+
marked = await suiteMarkAbandoned(io, o.ownerGo === true);
|
|
47211
47300
|
result = suiteAbandon(io.recordPath, o.ownerGo === true);
|
|
47212
47301
|
} finally {
|
|
47213
47302
|
await unlock();
|
|
@@ -47216,6 +47305,7 @@ function registerSuiteCommands(program3) {
|
|
|
47216
47305
|
`suite abandon: archived the cut record to ${result.archive}`,
|
|
47217
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"],
|
|
47218
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(", ")}`] : [],
|
|
47219
47309
|
" the next suite rcand --owner-go re-snapshots every member"
|
|
47220
47310
|
].join("\n"));
|
|
47221
47311
|
} catch (e) {
|
|
@@ -47734,7 +47824,6 @@ Next command: ${report.nextCommand}`);
|
|
|
47734
47824
|
var RESTART_LINE = "\u21BB Restart Claude to finish.";
|
|
47735
47825
|
var HOST_RESTART_LINES = {
|
|
47736
47826
|
codex: "\u21BB Restart Codex to finish.",
|
|
47737
|
-
cursor: "\u21BB Restart Cursor to finish.",
|
|
47738
47827
|
jervcode: "\u21BB Restart JervCode to finish."
|
|
47739
47828
|
};
|
|
47740
47829
|
function restartLineForHost(host) {
|
|
@@ -47966,92 +48055,6 @@ var surfaces_default = {
|
|
|
47966
48055
|
"Hosted tools and disabled or untrusted hooks bypass local enforcement."
|
|
47967
48056
|
]
|
|
47968
48057
|
},
|
|
47969
|
-
{
|
|
47970
|
-
token: "cursor",
|
|
47971
|
-
displayName: "Cursor",
|
|
47972
|
-
lifecycle: "active",
|
|
47973
|
-
artifactIds: [
|
|
47974
|
-
"mmi-cursor-plugin",
|
|
47975
|
-
"mmi-skills",
|
|
47976
|
-
"mmi-cli"
|
|
47977
|
-
],
|
|
47978
|
-
plannedArtifactIds: [],
|
|
47979
|
-
assembly: {
|
|
47980
|
-
mode: "package-directory",
|
|
47981
|
-
rootPath: "packages/cursor-plugin",
|
|
47982
|
-
sync: [
|
|
47983
|
-
{
|
|
47984
|
-
mode: "directories",
|
|
47985
|
-
sourcePath: "skills",
|
|
47986
|
-
targetPath: "packages/cursor-plugin/skills"
|
|
47987
|
-
},
|
|
47988
|
-
{
|
|
47989
|
-
mode: "files",
|
|
47990
|
-
sourcePath: "bin",
|
|
47991
|
-
targetPath: "packages/cursor-plugin/bin",
|
|
47992
|
-
include: [
|
|
47993
|
-
"mmi-cli",
|
|
47994
|
-
"mmi-cli.cmd"
|
|
47995
|
-
]
|
|
47996
|
-
},
|
|
47997
|
-
{
|
|
47998
|
-
mode: "files",
|
|
47999
|
-
sourcePath: "cli",
|
|
48000
|
-
targetPath: "packages/cursor-plugin/cli",
|
|
48001
|
-
include: [
|
|
48002
|
-
"dist/index.cjs",
|
|
48003
|
-
"dist/main.cjs",
|
|
48004
|
-
"package.json"
|
|
48005
|
-
]
|
|
48006
|
-
},
|
|
48007
|
-
{
|
|
48008
|
-
mode: "files",
|
|
48009
|
-
sourcePath: ".cursor-plugin",
|
|
48010
|
-
targetPath: "packages/cursor-plugin/.cursor-plugin",
|
|
48011
|
-
include: [
|
|
48012
|
-
"plugin.json"
|
|
48013
|
-
]
|
|
48014
|
-
}
|
|
48015
|
-
]
|
|
48016
|
-
},
|
|
48017
|
-
install: {
|
|
48018
|
-
mechanism: "github-source",
|
|
48019
|
-
locator: "~/.cursor/plugins/local/mmi"
|
|
48020
|
-
},
|
|
48021
|
-
upgrade: {
|
|
48022
|
-
mechanism: "reinstall",
|
|
48023
|
-
reload: "workspace"
|
|
48024
|
-
},
|
|
48025
|
-
skills: {
|
|
48026
|
-
delivery: "plugin",
|
|
48027
|
-
sourceSurfaceId: "mmi-skills",
|
|
48028
|
-
artifactId: "mmi-skills",
|
|
48029
|
-
invocation: {
|
|
48030
|
-
entry: "/epic",
|
|
48031
|
-
any: "/<skill>"
|
|
48032
|
-
}
|
|
48033
|
-
},
|
|
48034
|
-
cli: {
|
|
48035
|
-
delivery: "standalone",
|
|
48036
|
-
artifactId: "mmi-cli"
|
|
48037
|
-
},
|
|
48038
|
-
ownership: {
|
|
48039
|
-
trust: "host",
|
|
48040
|
-
cache: "mmi-hub",
|
|
48041
|
-
repair: "mmi-hub"
|
|
48042
|
-
},
|
|
48043
|
-
certification: {
|
|
48044
|
-
hostCommand: "cursor-agent",
|
|
48045
|
-
versionArgs: [
|
|
48046
|
-
"--version"
|
|
48047
|
-
]
|
|
48048
|
-
},
|
|
48049
|
-
enforcementCeilings: [
|
|
48050
|
-
"Cursor can replace MCP results only; it cannot rewrite ordinary tool output after execution, though MMI still detects secret-shaped output.",
|
|
48051
|
-
"Final assistant output is outside Cursor hook control.",
|
|
48052
|
-
"Cloud agents do not run sessionStart or sessionEnd hooks, and disabled plugins or untrusted workspaces enforce nothing."
|
|
48053
|
-
]
|
|
48054
|
-
},
|
|
48055
48058
|
{
|
|
48056
48059
|
token: "jervcode",
|
|
48057
48060
|
displayName: "JervCode",
|
|
@@ -48281,38 +48284,6 @@ var surfaces_default = {
|
|
|
48281
48284
|
},
|
|
48282
48285
|
publishVisibility: "public"
|
|
48283
48286
|
},
|
|
48284
|
-
{
|
|
48285
|
-
id: "mmi-cursor-plugin",
|
|
48286
|
-
classification: "packaging",
|
|
48287
|
-
kind: "plugin",
|
|
48288
|
-
ownerPath: "packages/cursor-plugin/package.json",
|
|
48289
|
-
deliveryPath: "packages/cursor-plugin/package.json",
|
|
48290
|
-
delivery: "npm",
|
|
48291
|
-
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).",
|
|
48292
|
-
versionCoordinated: true,
|
|
48293
|
-
surfaceToken: "cursor",
|
|
48294
|
-
versionPaths: [
|
|
48295
|
-
{
|
|
48296
|
-
path: ".cursor-plugin/plugin.json",
|
|
48297
|
-
pointer: "version"
|
|
48298
|
-
},
|
|
48299
|
-
{
|
|
48300
|
-
path: "packages/cursor-plugin/package.json",
|
|
48301
|
-
pointer: "version"
|
|
48302
|
-
}
|
|
48303
|
-
],
|
|
48304
|
-
additionalPaths: [
|
|
48305
|
-
"packages/cursor-plugin/.cursor-plugin",
|
|
48306
|
-
"packages/cursor-plugin/skills",
|
|
48307
|
-
"packages/cursor-plugin/bin",
|
|
48308
|
-
"packages/cursor-plugin/cli"
|
|
48309
|
-
],
|
|
48310
|
-
artifactIdentity: {
|
|
48311
|
-
kind: "npm-pack",
|
|
48312
|
-
packagePath: "packages/cursor-plugin"
|
|
48313
|
-
},
|
|
48314
|
-
publishVisibility: "public"
|
|
48315
|
-
},
|
|
48316
48287
|
{
|
|
48317
48288
|
id: "mmi-claude-marketplace",
|
|
48318
48289
|
classification: "delivery",
|
|
@@ -48604,7 +48575,7 @@ var surfaces_default = {
|
|
|
48604
48575
|
};
|
|
48605
48576
|
|
|
48606
48577
|
// src/surface-doctor.ts
|
|
48607
|
-
var TOKENS = /* @__PURE__ */ new Set(["claude", "codex", "
|
|
48578
|
+
var TOKENS = /* @__PURE__ */ new Set(["claude", "codex", "jervcode", "hermes"]);
|
|
48608
48579
|
function isActiveToken(value) {
|
|
48609
48580
|
return TOKENS.has(value);
|
|
48610
48581
|
}
|
|
@@ -50511,20 +50482,12 @@ async function readOrFail(read) {
|
|
|
50511
50482
|
async function defaultRegistryRead() {
|
|
50512
50483
|
return fetchSchedulesList(registryClientDeps(await loadConfig()));
|
|
50513
50484
|
}
|
|
50514
|
-
async function
|
|
50515
|
-
|
|
50516
|
-
}
|
|
50517
|
-
function modelRoleKeyOf(row) {
|
|
50518
|
-
const model = row.model;
|
|
50519
|
-
return typeof model === "string" && model.trim() ? model : void 0;
|
|
50520
|
-
}
|
|
50521
|
-
async function fetchNotebook(client = defaultGitHubClient(), readRegistry = defaultRegistryRead, readProjects = defaultProjectsRead, readRunVerdicts = defaultRunVerdictRead) {
|
|
50522
|
-
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([
|
|
50523
50487
|
githubEntries(client),
|
|
50524
50488
|
awsEntries(),
|
|
50525
50489
|
readOrFail(readRegistry),
|
|
50526
|
-
readOrFail(readProjects)
|
|
50527
|
-
readOrFail(readRunVerdicts)
|
|
50490
|
+
readOrFail(readProjects)
|
|
50528
50491
|
]);
|
|
50529
50492
|
const readFailures = [];
|
|
50530
50493
|
if (registryRead.state === "failed") {
|
|
@@ -50534,16 +50497,10 @@ async function fetchNotebook(client = defaultGitHubClient(), readRegistry = defa
|
|
|
50534
50497
|
readFailures.push(`registry: could not read the projects list \u2014 schedulesMode opt-outs unresolved (nothing suppressed) \u2014 ${projectsRead.error}`);
|
|
50535
50498
|
}
|
|
50536
50499
|
const registry2 = registryRead.state === "ok" ? registryRead.value : null;
|
|
50537
|
-
const runVerdicts = runVerdictRead.state === "ok" ? runVerdictRead.value : null;
|
|
50538
50500
|
const projects = projectsRead.state === "ok" ? projectsRead.value : null;
|
|
50539
|
-
const decoratedAws = decorateAwsModel(
|
|
50540
|
-
aws.entries,
|
|
50541
|
-
registry2?.map((r) => ({ id: r.id, executor: r.executor, model: modelRoleKeyOf(r) })) ?? null,
|
|
50542
|
-
runVerdicts
|
|
50543
|
-
);
|
|
50544
50501
|
const recon = assembleReconciliation(gh2.entries, new Set(gh2.readRepos), registry2, new Set(gh2.workflowNames), {
|
|
50545
50502
|
// #3286: the harbour side joins registry rows against the live aws-scheduler clocks by scheduleId.
|
|
50546
|
-
awsEntries:
|
|
50503
|
+
awsEntries: aws.entries,
|
|
50547
50504
|
schedulerRead: Boolean(aws.schedulerRead)
|
|
50548
50505
|
}, new Set(gh2.disabledWorkflowNames));
|
|
50549
50506
|
const selfManaged = /* @__PURE__ */ new Set();
|
|
@@ -50557,7 +50514,7 @@ async function fetchNotebook(client = defaultGitHubClient(), readRegistry = defa
|
|
|
50557
50514
|
);
|
|
50558
50515
|
const driftLines = reconciliation.map(renderDrift);
|
|
50559
50516
|
return {
|
|
50560
|
-
entries: sortEntries([...gh2.entries, ...
|
|
50517
|
+
entries: sortEntries([...gh2.entries, ...aws.entries, ...DECLARED_ENTRIES]),
|
|
50561
50518
|
incomplete: [...gh2.incomplete, ...aws.incomplete, ...recon.incomplete, ...readFailures],
|
|
50562
50519
|
drift: driftLines,
|
|
50563
50520
|
reconciliation,
|
|
@@ -53282,7 +53239,7 @@ projectDeploy.command("retire [owner/repo]").description("retire (tombstone) one
|
|
|
53282
53239
|
}
|
|
53283
53240
|
if (!result.ok) return failGraceful(`org project deploy retire: ${result.error ?? "read failed"}`);
|
|
53284
53241
|
});
|
|
53285
|
-
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) => {
|
|
53286
53243
|
const cfg = await loadConfig();
|
|
53287
53244
|
let target;
|
|
53288
53245
|
try {
|
|
@@ -53313,6 +53270,7 @@ project.command("set [owner/repo]").description("upsert project META (idempotent
|
|
|
53313
53270
|
vars,
|
|
53314
53271
|
unsets: o.unset ?? [],
|
|
53315
53272
|
unsetSecrets: o.unsetSecret ?? [],
|
|
53273
|
+
unsetTasks: o.unsetTask ?? [],
|
|
53316
53274
|
clearWebProfile: Boolean(o.clearWebProfile)
|
|
53317
53275
|
});
|
|
53318
53276
|
} catch (e) {
|
|
@@ -53323,6 +53281,8 @@ project.command("set [owner/repo]").description("upsert project META (idempotent
|
|
|
53323
53281
|
if (clearRefusal) return fail(clearRefusal);
|
|
53324
53282
|
const removalRefusal = secretRemovalRefusal(existing, patch);
|
|
53325
53283
|
if (removalRefusal) return fail(removalRefusal);
|
|
53284
|
+
const taskRefusal = tenantTaskWriteRefusal(existing, patch);
|
|
53285
|
+
if (taskRefusal) return fail(taskRefusal);
|
|
53326
53286
|
const boardError = boardLinkWriteError(patch, existing);
|
|
53327
53287
|
if (boardError) return fail(`org project set: ${boardError}`);
|
|
53328
53288
|
const res = await upsertProject(slug, { ...patch, repo }, registryClientDeps(cfg));
|