@mutmutco/claude-plugin 4.5.227 → 4.5.229
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/.claude-plugin/plugin.json +1 -1
- package/cli/dist/main.cjs +814 -503
- package/cli/package.json +1 -1
- package/package.json +1 -1
- package/skills/board/SKILL.md +2 -2
- package/skills/bootstrap/SKILL.md +1 -1
- package/skills/bootstrap/seeds/suite-gate.template.yml +1 -1
- package/skills/hotfix/SKILL.md +1 -1
- package/skills/mmi-help/SKILL.md +3 -2
- package/skills/org-guide/references/getting-started.md +4 -5
- package/skills/org-guide/references/work-and-releases.md +7 -1
- package/skills/rcand/SKILL.md +1 -1
- package/skills/release/SKILL.md +3 -1
- package/skills/secrets/SKILL.md +1 -1
package/cli/dist/main.cjs
CHANGED
|
@@ -982,12 +982,14 @@ function roleFromToken(token) {
|
|
|
982
982
|
function readCache(path2, apiUrl, now, githubTokenFingerprint) {
|
|
983
983
|
try {
|
|
984
984
|
const session = JSON.parse((0, import_node_fs3.readFileSync)(path2, "utf8"));
|
|
985
|
-
if (!session.token || !session.expiresAt || session.apiUrl !== apiUrl) return null;
|
|
986
|
-
if (session.githubTokenFingerprint !== githubTokenFingerprint)
|
|
987
|
-
|
|
988
|
-
|
|
985
|
+
if (!session.token || !session.expiresAt || session.apiUrl !== apiUrl) return { session: null, stale: false };
|
|
986
|
+
if (session.githubTokenFingerprint !== githubTokenFingerprint) {
|
|
987
|
+
return { session: null, stale: true, staleLogin: typeof session.login === "string" ? session.login : void 0 };
|
|
988
|
+
}
|
|
989
|
+
if (new Date(session.expiresAt).getTime() <= now.getTime() + REFRESH_WINDOW_MS) return { session: null, stale: false };
|
|
990
|
+
return { session: { ...session, role: roleFromToken(session.token) }, stale: false };
|
|
989
991
|
} catch {
|
|
990
|
-
return null;
|
|
992
|
+
return { session: null, stale: false };
|
|
991
993
|
}
|
|
992
994
|
}
|
|
993
995
|
function writeCache(path2, session) {
|
|
@@ -1004,53 +1006,115 @@ function writeCache(path2, session) {
|
|
|
1004
1006
|
} catch {
|
|
1005
1007
|
}
|
|
1006
1008
|
}
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
+
function lastHubAuthFailure() {
|
|
1010
|
+
return lastFailure;
|
|
1011
|
+
}
|
|
1012
|
+
function hubAuthFailureMessage(input) {
|
|
1013
|
+
const r = input.refusal;
|
|
1014
|
+
const said = r ? [
|
|
1015
|
+
r.error ? `Hub error: ${r.error}` : `Hub error: HTTP ${r.status ?? "unknown"}`,
|
|
1016
|
+
r.reason ? `reason: ${r.reason}` : "",
|
|
1017
|
+
r.hint ? `hint: ${r.hint}` : ""
|
|
1018
|
+
].filter(Boolean).join(" ") : "";
|
|
1019
|
+
if (input.kind === "stale-session") {
|
|
1020
|
+
return `cached Hub session is stale: it was minted from a different GitHub token${input.staleLogin ? ` (cached login ${input.staleLogin})` : ""}, and the automatic re-mint was refused${said ? ` \u2014 ${said}` : ""}. Re-authenticate so ONE token source feeds both the cache and the mint (\`gh auth login\` / \`gh auth switch\`, or unset \`MMI_GH_PAT\` so the shell falls back to the gh keyring), then retry; \`mmi-cli whoami\` confirms the identity.`;
|
|
1021
|
+
}
|
|
1022
|
+
if (input.kind === "refused") {
|
|
1023
|
+
return `Hub refused this GitHub token for a Hub session${said ? ` \u2014 ${said}` : ""} (${MEMBERSHIP_FIX}).`;
|
|
1024
|
+
}
|
|
1025
|
+
return `Hub session exchange failed: ${(input.transient ?? said) || "no answer from the Hub"} \u2014 the Hub is unreachable or cold-started; retry, and \`mmi-cli doctor\` reports Hub reachability.`;
|
|
1026
|
+
}
|
|
1027
|
+
async function hubAuthSessionOutcome(deps) {
|
|
1028
|
+
if (!deps.baseUrl) return {};
|
|
1009
1029
|
const apiUrl = normalizeBaseUrl(deps.baseUrl);
|
|
1010
1030
|
const envToken = process.env.MMI_HUB_TOKEN;
|
|
1011
1031
|
if (envToken) {
|
|
1012
|
-
|
|
1032
|
+
lastFailure = void 0;
|
|
1033
|
+
return { session: { ...{ token: envToken }, expiresAt: new Date(Date.now() + 30 * 60 * 1e3).toISOString(), apiUrl, githubTokenFingerprint: "env-override" } };
|
|
1013
1034
|
}
|
|
1014
1035
|
const now = deps.now?.() ?? /* @__PURE__ */ new Date();
|
|
1015
1036
|
const cachePath = deps.cachePath ?? defaultHubSessionCachePath();
|
|
1016
1037
|
const ghToken = await deps.githubToken();
|
|
1017
|
-
if (!ghToken)
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
try {
|
|
1022
|
-
const res = await fetchWithRetry(
|
|
1023
|
-
deps.fetch ?? fetch,
|
|
1024
|
-
`${apiUrl}/auth/session`,
|
|
1025
|
-
{ method: "POST", headers: { ...clientVersionHeaders(), Authorization: `Bearer ${ghToken}` } },
|
|
1026
|
-
{ attempts: EXCHANGE_ATTEMPTS, timeoutMs: EXCHANGE_TIMEOUT_MS }
|
|
1027
|
-
);
|
|
1028
|
-
if (!res.ok) return void 0;
|
|
1029
|
-
const body = await res.json();
|
|
1030
|
-
if (!body.token || !body.expiresAt) return void 0;
|
|
1031
|
-
const session = {
|
|
1032
|
-
token: body.token,
|
|
1033
|
-
expiresAt: body.expiresAt,
|
|
1034
|
-
login: typeof body.login === "string" ? body.login : void 0,
|
|
1035
|
-
// #3513: from the SIGNED token, not the response body — one rule at every hop, so the cache
|
|
1036
|
-
// read and the network read cannot disagree about what counts as an assertion.
|
|
1037
|
-
role: roleFromToken(body.token),
|
|
1038
|
-
apiUrl,
|
|
1039
|
-
githubTokenFingerprint
|
|
1038
|
+
if (!ghToken) {
|
|
1039
|
+
lastFailure = {
|
|
1040
|
+
kind: "unreachable",
|
|
1041
|
+
message: "no GitHub token available for a Hub session \u2014 `gh auth login` (or set MMI_GH_PAT/GH_TOKEN), then retry."
|
|
1040
1042
|
};
|
|
1043
|
+
return { failure: lastFailure };
|
|
1044
|
+
}
|
|
1045
|
+
const githubTokenFingerprint = tokenFingerprint(ghToken);
|
|
1046
|
+
const cache = readCache(cachePath, apiUrl, now, githubTokenFingerprint);
|
|
1047
|
+
if (cache.session) {
|
|
1048
|
+
lastFailure = void 0;
|
|
1049
|
+
return { session: cache.session };
|
|
1050
|
+
}
|
|
1051
|
+
return mintSession(deps, cachePath, apiUrl, ghToken, githubTokenFingerprint, cache);
|
|
1052
|
+
}
|
|
1053
|
+
async function mintSession(deps, cachePath, apiUrl, ghToken, githubTokenFingerprint, cache) {
|
|
1054
|
+
const attempts = cache.stale ? 2 : 1;
|
|
1055
|
+
let refusal;
|
|
1056
|
+
let transient;
|
|
1057
|
+
for (let attempt = 0; attempt < attempts; attempt += 1) {
|
|
1041
1058
|
try {
|
|
1042
|
-
|
|
1043
|
-
|
|
1059
|
+
const res = await fetchWithRetry(
|
|
1060
|
+
deps.fetch ?? fetch,
|
|
1061
|
+
`${apiUrl}/auth/session`,
|
|
1062
|
+
{ method: "POST", headers: { ...clientVersionHeaders(), Authorization: `Bearer ${ghToken}` } },
|
|
1063
|
+
{ attempts: EXCHANGE_ATTEMPTS, timeoutMs: EXCHANGE_TIMEOUT_MS }
|
|
1064
|
+
);
|
|
1065
|
+
if (!res.ok) {
|
|
1066
|
+
const body2 = await res.json().catch(() => ({}));
|
|
1067
|
+
refusal = {
|
|
1068
|
+
status: res.status,
|
|
1069
|
+
error: typeof body2.error === "string" ? body2.error : void 0,
|
|
1070
|
+
reason: typeof body2.reason === "string" ? body2.reason : void 0,
|
|
1071
|
+
hint: typeof body2.hint === "string" ? body2.hint : void 0
|
|
1072
|
+
};
|
|
1073
|
+
continue;
|
|
1074
|
+
}
|
|
1075
|
+
const body = await res.json();
|
|
1076
|
+
if (!body.token || !body.expiresAt) {
|
|
1077
|
+
refusal = { status: res.status, error: "the Hub returned no session token" };
|
|
1078
|
+
continue;
|
|
1079
|
+
}
|
|
1080
|
+
const session = {
|
|
1081
|
+
token: body.token,
|
|
1082
|
+
expiresAt: body.expiresAt,
|
|
1083
|
+
login: typeof body.login === "string" ? body.login : void 0,
|
|
1084
|
+
// #3513: from the SIGNED token, not the response body — one rule at every hop, so the cache
|
|
1085
|
+
// read and the network read cannot disagree about what counts as an assertion.
|
|
1086
|
+
role: roleFromToken(body.token),
|
|
1087
|
+
apiUrl,
|
|
1088
|
+
githubTokenFingerprint
|
|
1089
|
+
};
|
|
1090
|
+
try {
|
|
1091
|
+
writeCache(cachePath, session);
|
|
1092
|
+
} catch {
|
|
1093
|
+
}
|
|
1094
|
+
lastFailure = void 0;
|
|
1095
|
+
return { session };
|
|
1096
|
+
} catch (e) {
|
|
1097
|
+
transient = e?.message || "network error";
|
|
1044
1098
|
}
|
|
1045
|
-
return session;
|
|
1046
|
-
} catch {
|
|
1047
|
-
return void 0;
|
|
1048
1099
|
}
|
|
1100
|
+
const kind = cache.stale ? "stale-session" : refusal ? "refused" : "unreachable";
|
|
1101
|
+
const failure = {
|
|
1102
|
+
kind,
|
|
1103
|
+
message: hubAuthFailureMessage({ kind, staleLogin: cache.staleLogin, refusal, transient }),
|
|
1104
|
+
...refusal?.reason ? { reason: refusal.reason } : {},
|
|
1105
|
+
...refusal?.hint ? { hint: refusal.hint } : {},
|
|
1106
|
+
...kind === "stale-session" ? { exitCode: EXIT_HUB_SESSION_STALE } : kind === "refused" ? { exitCode: EXIT_HUB_SESSION_REFUSED } : {}
|
|
1107
|
+
};
|
|
1108
|
+
lastFailure = failure;
|
|
1109
|
+
return { failure };
|
|
1110
|
+
}
|
|
1111
|
+
async function hubAuthSession(deps) {
|
|
1112
|
+
return (await hubAuthSessionOutcome(deps)).session;
|
|
1049
1113
|
}
|
|
1050
1114
|
async function hubAuthToken(deps) {
|
|
1051
1115
|
return (await hubAuthSession(deps))?.token;
|
|
1052
1116
|
}
|
|
1053
|
-
var import_node_crypto, import_node_fs3, import_node_path3, import_node_os2, REFRESH_WINDOW_MS, EXCHANGE_TIMEOUT_MS, EXCHANGE_ATTEMPTS;
|
|
1117
|
+
var import_node_crypto, import_node_fs3, import_node_path3, import_node_os2, REFRESH_WINDOW_MS, EXCHANGE_TIMEOUT_MS, EXCHANGE_ATTEMPTS, EXIT_HUB_SESSION_STALE, EXIT_HUB_SESSION_REFUSED, lastFailure, MEMBERSHIP_FIX;
|
|
1054
1118
|
var init_hub_auth = __esm({
|
|
1055
1119
|
"src/hub-auth.ts"() {
|
|
1056
1120
|
"use strict";
|
|
@@ -1063,6 +1127,9 @@ var init_hub_auth = __esm({
|
|
|
1063
1127
|
REFRESH_WINDOW_MS = 10 * 60 * 1e3;
|
|
1064
1128
|
EXCHANGE_TIMEOUT_MS = 8e3;
|
|
1065
1129
|
EXCHANGE_ATTEMPTS = 2;
|
|
1130
|
+
EXIT_HUB_SESSION_STALE = 76;
|
|
1131
|
+
EXIT_HUB_SESSION_REFUSED = 77;
|
|
1132
|
+
MEMBERSHIP_FIX = "the token needs Organization \u2192 Members: read (`read:org`) \u2014 use a classic PAT or an App carrying that permission, or `gh auth switch` to a login that has it; then `mmi-cli whoami` confirms the identity";
|
|
1066
1133
|
}
|
|
1067
1134
|
});
|
|
1068
1135
|
|
|
@@ -9359,12 +9426,22 @@ var isWin = process.platform === "win32";
|
|
|
9359
9426
|
var MMI_PLUGIN_ID = "mmi@mutmutco";
|
|
9360
9427
|
var LEGACY_MMI_MARKETPLACE = "mmi";
|
|
9361
9428
|
var CODEX_MARKETPLACE = "mutmutco";
|
|
9362
|
-
function
|
|
9429
|
+
function insideOpenBotAgentWorkspace(cwd, env = process.env, home = (0, import_node_os4.homedir)()) {
|
|
9430
|
+
let dir = cwd;
|
|
9431
|
+
try {
|
|
9432
|
+
dir ??= process.cwd();
|
|
9433
|
+
} catch {
|
|
9434
|
+
return false;
|
|
9435
|
+
}
|
|
9436
|
+
const rel = (0, import_node_path4.relative)(openbotAgentsRoot(env, home), dir);
|
|
9437
|
+
return !(0, import_node_path4.isAbsolute)(rel) && (rel.split(/[\\/]/)[0] ?? "").toLowerCase().startsWith("agent-");
|
|
9438
|
+
}
|
|
9439
|
+
function detectSurface(env, cwd) {
|
|
9363
9440
|
const has = (k) => Boolean(env[k]?.trim());
|
|
9441
|
+
if (env.MMI_AGENT_SURFACE === "openbot" || insideOpenBotAgentWorkspace(cwd, env)) return "openbot";
|
|
9364
9442
|
if (env.MMI_AGENT_SURFACE === "codex" || has("CODEX_THREAD_ID") || has("CODEX_MANAGED_BY_NPM") || has("CODEX_MANAGED_PACKAGE_ROOT") || (env.CLAUDE_PLUGIN_ROOT ?? "").includes(".codex")) {
|
|
9365
9443
|
return "codex";
|
|
9366
9444
|
}
|
|
9367
|
-
if (env.MMI_AGENT_SURFACE === "hermes" || has("HERMES_SESSION_ID") || has("HERMES_PLUGIN_ROOT")) return "hermes";
|
|
9368
9445
|
if (env.MMI_AGENT_SURFACE === "jervcode" || has("PI_SESSION_ID")) {
|
|
9369
9446
|
return "jervcode";
|
|
9370
9447
|
}
|
|
@@ -9384,8 +9461,8 @@ function surfaceToken(surface) {
|
|
|
9384
9461
|
return "codex";
|
|
9385
9462
|
case "jervcode":
|
|
9386
9463
|
return "jervcode";
|
|
9387
|
-
case "
|
|
9388
|
-
return "
|
|
9464
|
+
case "openbot":
|
|
9465
|
+
return "openbot";
|
|
9389
9466
|
case "opencode":
|
|
9390
9467
|
return "opencode";
|
|
9391
9468
|
case "shell":
|
|
@@ -9403,8 +9480,8 @@ function reloadAction(surface) {
|
|
|
9403
9480
|
return "restart OpenCode";
|
|
9404
9481
|
case "jervcode":
|
|
9405
9482
|
return "restart JervCode (the package set is read at launch)";
|
|
9406
|
-
case "
|
|
9407
|
-
return "start a fresh
|
|
9483
|
+
case "openbot":
|
|
9484
|
+
return "start a fresh OpenBot agent run";
|
|
9408
9485
|
case "claude-cli":
|
|
9409
9486
|
case "shell":
|
|
9410
9487
|
default:
|
|
@@ -9444,7 +9521,7 @@ function nonClaudeSurfaceHealMessage(surface) {
|
|
|
9444
9521
|
if (surface === "codex") {
|
|
9445
9522
|
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";
|
|
9446
9523
|
}
|
|
9447
|
-
return "No Hub-shipped MMI plugin on this host \u2014 the Hub ships plugins for Claude, Codex, JervCode, and
|
|
9524
|
+
return "No Hub-shipped MMI plugin on this host \u2014 the Hub ships plugins for Claude, Codex, JervCode, and OpenBot only.\n Update the CLI instead: npm i -g @mutmutco/cli (org rules ride an AGENTS.md authored outside the Hub)";
|
|
9448
9525
|
}
|
|
9449
9526
|
function healStepAborts(step, ok) {
|
|
9450
9527
|
return !ok && step.gated;
|
|
@@ -9487,14 +9564,20 @@ function runHostBin(bin, args, opts) {
|
|
|
9487
9564
|
const argv = isWin ? ["/c", bin, ...args] : args;
|
|
9488
9565
|
return step ? execFileHard(file, argv, { ...shared, step }) : execFileP(file, argv, shared);
|
|
9489
9566
|
}
|
|
9490
|
-
function
|
|
9491
|
-
|
|
9492
|
-
|
|
9493
|
-
|
|
9567
|
+
function openbotAgentsRoot(env = process.env, home = (0, import_node_os4.homedir)()) {
|
|
9568
|
+
return env.MMI_UPDATER_OPENBOT_AGENTS_DIR?.trim() || (0, import_node_path4.join)(home, "OpenBot", "Agents");
|
|
9569
|
+
}
|
|
9570
|
+
function openbotAgentWorkspaces(env = process.env, home = (0, import_node_os4.homedir)()) {
|
|
9571
|
+
const root = openbotAgentsRoot(env, home);
|
|
9572
|
+
try {
|
|
9573
|
+
return (0, import_node_fs7.readdirSync)(root, { withFileTypes: true }).filter((entry) => entry.isDirectory() && entry.name.startsWith("agent-")).map((entry) => (0, import_node_path4.join)(root, entry.name)).sort();
|
|
9574
|
+
} catch {
|
|
9575
|
+
return [];
|
|
9576
|
+
}
|
|
9494
9577
|
}
|
|
9495
9578
|
function surfaceConfigRoot(surface, env = process.env, home = (0, import_node_os4.homedir)()) {
|
|
9496
9579
|
if (surface === "codex") return env.CODEX_HOME?.trim() || (0, import_node_path4.join)(home, ".codex");
|
|
9497
|
-
if (surface === "
|
|
9580
|
+
if (surface === "openbot") return openbotAgentsRoot(env, home);
|
|
9498
9581
|
if (surface === "jervcode") {
|
|
9499
9582
|
return env.JERVCODE_CODING_AGENT_DIR?.trim() || env.PI_CODING_AGENT_DIR?.trim() || (0, import_node_path4.join)(home, ".jerv", "agent");
|
|
9500
9583
|
}
|
|
@@ -9518,7 +9601,7 @@ function marketplaceCloneCandidates(surface, home, env = process.env) {
|
|
|
9518
9601
|
(0, import_node_path4.join)(root, "plugins", "marketplaces", CODEX_MARKETPLACE)
|
|
9519
9602
|
];
|
|
9520
9603
|
}
|
|
9521
|
-
if (surface === "
|
|
9604
|
+
if (surface === "openbot") return [];
|
|
9522
9605
|
if (surface === "jervcode") return [];
|
|
9523
9606
|
return [(0, import_node_path4.join)(home, ".claude", "plugins", "marketplaces", "mutmutco")];
|
|
9524
9607
|
}
|
|
@@ -9628,13 +9711,26 @@ function mmiPiPackageHealthy(env = process.env, home = (0, import_node_os4.homed
|
|
|
9628
9711
|
function piDoctorEnv(agentDir) {
|
|
9629
9712
|
return { ...process.env, PI_CODING_AGENT_DIR: agentDir };
|
|
9630
9713
|
}
|
|
9631
|
-
function
|
|
9632
|
-
|
|
9714
|
+
function openbotManagedRecord(env = process.env, home = (0, import_node_os4.homedir)()) {
|
|
9715
|
+
for (const workspace of openbotAgentWorkspaces(env, home)) {
|
|
9716
|
+
const path2 = (0, import_node_path4.join)(workspace, ".agents", "skills", ".mmi-managed.json");
|
|
9717
|
+
if (!(0, import_node_fs7.existsSync)(path2)) continue;
|
|
9718
|
+
try {
|
|
9719
|
+
const record2 = JSON.parse((0, import_node_fs7.readFileSync)(path2, "utf8"));
|
|
9720
|
+
return { workspace, ...typeof record2.version === "string" ? { version: record2.version } : {} };
|
|
9721
|
+
} catch {
|
|
9722
|
+
return { workspace };
|
|
9723
|
+
}
|
|
9724
|
+
}
|
|
9725
|
+
return null;
|
|
9633
9726
|
}
|
|
9634
|
-
function
|
|
9635
|
-
|
|
9727
|
+
function openbotWorkspaceSkillsHealthy(env = process.env, home = (0, import_node_os4.homedir)()) {
|
|
9728
|
+
const record2 = openbotManagedRecord(env, home);
|
|
9729
|
+
if (!record2) return false;
|
|
9636
9730
|
try {
|
|
9637
|
-
|
|
9731
|
+
const recordFile = JSON.parse((0, import_node_fs7.readFileSync)((0, import_node_path4.join)(record2.workspace, ".agents", "skills", ".mmi-managed.json"), "utf8"));
|
|
9732
|
+
const owned = Array.isArray(recordFile.skills) ? recordFile.skills : [];
|
|
9733
|
+
return owned.length > 0 && owned.every((name) => typeof name === "string" && (0, import_node_fs7.existsSync)((0, import_node_path4.join)(record2.workspace, ".agents", "skills", name, "SKILL.md")));
|
|
9638
9734
|
} catch {
|
|
9639
9735
|
return false;
|
|
9640
9736
|
}
|
|
@@ -9649,10 +9745,11 @@ function snapshotPluginGuardInput(surface = detectSurface(process.env), isOrgRep
|
|
|
9649
9745
|
// Codex's native projection is authoritative; its legacy installed_plugins.json may be empty
|
|
9650
9746
|
// while `plugin list --json` proves the managed plugin installed (#6111).
|
|
9651
9747
|
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.
|
|
9652
|
-
surface === "jervcode" && piEntry !== null ||
|
|
9748
|
+
surface === "jervcode" && piEntry !== null || // #9130: OpenBot's install record is the workspace ownership file MMI itself writes.
|
|
9749
|
+
surface === "openbot" && openbotManagedRecord() !== null,
|
|
9653
9750
|
// JervCode's settings entry and native local surfaces have no marketplace clone.
|
|
9654
|
-
marketplaceClonePresent: surface === "jervcode" || surface === "
|
|
9655
|
-
pluginCachePresent: surface === "jervcode" ? mmiPiPackageHealthy() : surface === "
|
|
9751
|
+
marketplaceClonePresent: surface === "jervcode" || surface === "openbot" ? true : marketplaceClonePresent(surface, (0, import_node_os4.homedir)()),
|
|
9752
|
+
pluginCachePresent: surface === "jervcode" ? mmiPiPackageHealthy() : surface === "openbot" ? openbotWorkspaceSkillsHealthy() : surface === "codex" ? Boolean(
|
|
9656
9753
|
codexStatus?.installed && codexStatus.enabled && codexStatus.version && (0, import_node_fs7.existsSync)((0, import_node_path4.join)(root, "plugins", "cache", CODEX_MARKETPLACE, "mmi", codexStatus.version))
|
|
9657
9754
|
) : (0, import_node_fs7.existsSync)((0, import_node_path4.join)(root, "plugins", "cache", "mutmutco", "mmi"))
|
|
9658
9755
|
};
|
|
@@ -9951,7 +10048,6 @@ var SESSION_ID_ENV_VARS = [
|
|
|
9951
10048
|
"CLAUDE_CODE_SESSION_ID",
|
|
9952
10049
|
"CODEX_SESSION_ID",
|
|
9953
10050
|
"CODEX_THREAD_ID",
|
|
9954
|
-
"HERMES_SESSION_ID",
|
|
9955
10051
|
"PI_SESSION_ID",
|
|
9956
10052
|
// #6193: lane runners spawn the CLI through the host's extension shell operations, which strip
|
|
9957
10053
|
// the PI_* session variables; the JervCode launcher's durable lane id (JERV_SESSION_ID) is what
|
|
@@ -9960,6 +10056,9 @@ var SESSION_ID_ENV_VARS = [
|
|
|
9960
10056
|
// synth/shell identity and the claim guard contests the caller's OWN lane (the #3414/#3438
|
|
9961
10057
|
// refusals of jerv_worktree_land's `devops pr create` from the claimed task worktree).
|
|
9962
10058
|
"JERV_SESSION_ID"
|
|
10059
|
+
// #9130: OpenBot exports no session variable of its own (its surface comes from the agent-workspace
|
|
10060
|
+
// cwd). Merv's codex runtime carries CODEX_THREAD_ID, honoured above; otherwise an OpenBot claim's
|
|
10061
|
+
// session is the per-process `synth-openbot-…` fallback — it proves the surface, never one lane.
|
|
9963
10062
|
];
|
|
9964
10063
|
var SYNTH_SESSION_PREFIX = "synth-";
|
|
9965
10064
|
function isHostSessionId(session) {
|
|
@@ -13373,7 +13472,7 @@ function useColor() {
|
|
|
13373
13472
|
var program = new Command();
|
|
13374
13473
|
|
|
13375
13474
|
// src/command-composition.ts
|
|
13376
|
-
var
|
|
13475
|
+
var import_node_fs61 = require("node:fs");
|
|
13377
13476
|
init_clean_exit();
|
|
13378
13477
|
init_cli_shared();
|
|
13379
13478
|
|
|
@@ -18738,6 +18837,7 @@ function shouldWithdrawSurfaceFlag(result) {
|
|
|
18738
18837
|
|
|
18739
18838
|
// src/secrets-transport.ts
|
|
18740
18839
|
init_client_version();
|
|
18840
|
+
init_hub_auth();
|
|
18741
18841
|
var OWNER = "mutmutco";
|
|
18742
18842
|
var SSM_ROOT = "/mmi-future";
|
|
18743
18843
|
var ORG_INFRA_SLUG = "_org";
|
|
@@ -18899,6 +18999,17 @@ function errorDetail(body) {
|
|
|
18899
18999
|
const error = typeof body.error === "string" ? body.error : "";
|
|
18900
19000
|
return error ? `: ${error}` : "";
|
|
18901
19001
|
}
|
|
19002
|
+
function hubAuthErrorDetail(body) {
|
|
19003
|
+
const parts = [errorDetail(body)];
|
|
19004
|
+
if (safeServerReason(body.reason)) parts.push(` (reason: ${String(body.reason)})`);
|
|
19005
|
+
if (safeServerReason(body.hint)) parts.push(` \u2014 ${String(body.hint)}`);
|
|
19006
|
+
return parts.join("");
|
|
19007
|
+
}
|
|
19008
|
+
function refusedLine(prefix, res, body) {
|
|
19009
|
+
const local = lastHubAuthFailure();
|
|
19010
|
+
if (res.status === 401 && local) return local.message;
|
|
19011
|
+
return `${prefix}: HTTP ${res.status}${hubAuthErrorDetail(body)}`;
|
|
19012
|
+
}
|
|
18902
19013
|
async function upgradeMessage(res, body) {
|
|
18903
19014
|
if (res.status !== 426) return null;
|
|
18904
19015
|
return upgradeRequiredError(res, body ?? await readJsonBody(res));
|
|
@@ -18947,7 +19058,7 @@ async function secretsList(deps, opts) {
|
|
|
18947
19058
|
return;
|
|
18948
19059
|
}
|
|
18949
19060
|
if (!res.ok) {
|
|
18950
|
-
deps.err(await upgradeMessage(res) ??
|
|
19061
|
+
deps.err(await upgradeMessage(res) ?? refusedLine("secrets list failed", res, await readJsonBody(res)));
|
|
18951
19062
|
return;
|
|
18952
19063
|
}
|
|
18953
19064
|
const { secrets } = await res.json();
|
|
@@ -19332,6 +19443,10 @@ function missingRequiredKeys(stage, required, present, declaredSecrets) {
|
|
|
19332
19443
|
}
|
|
19333
19444
|
async function secretsPreflight(deps, opts) {
|
|
19334
19445
|
const repo = await targetRepo(deps, opts);
|
|
19446
|
+
if (opts.required.length === 0 && !opts.json) {
|
|
19447
|
+
deps.log(`0 required ${opts.stage} secret names`);
|
|
19448
|
+
return true;
|
|
19449
|
+
}
|
|
19335
19450
|
const qs = new URLSearchParams({ repo }).toString();
|
|
19336
19451
|
let res;
|
|
19337
19452
|
try {
|
|
@@ -20268,7 +20383,7 @@ async function secretsRequest(deps, key, opts) {
|
|
|
20268
20383
|
});
|
|
20269
20384
|
const body = await readJsonBody(res);
|
|
20270
20385
|
if (!res.ok) {
|
|
20271
|
-
deps.err(await upgradeMessage(res, body) ??
|
|
20386
|
+
deps.err(await upgradeMessage(res, body) ?? refusedLine("secrets request failed", res, body));
|
|
20272
20387
|
return false;
|
|
20273
20388
|
}
|
|
20274
20389
|
if (opts.json) {
|
|
@@ -20965,7 +21080,7 @@ async function fetchSecretForUse(deps, { repo, key, slug }) {
|
|
|
20965
21080
|
return null;
|
|
20966
21081
|
}
|
|
20967
21082
|
deps.err(
|
|
20968
|
-
await upgradeMessage(res, body) ?? (res.status === 403 ? `secrets use: not authorized for ${key} (HTTP 403) \u2014 ask the master for a \`secrets grant\`${errorDetail(body)}` :
|
|
21083
|
+
await upgradeMessage(res, body) ?? (res.status === 403 ? `secrets use: not authorized for ${key} (HTTP 403) \u2014 ask the master for a \`secrets grant\`${errorDetail(body)}` : refusedLine("secrets use failed", res, body))
|
|
20969
21084
|
);
|
|
20970
21085
|
return null;
|
|
20971
21086
|
}
|
|
@@ -24973,6 +25088,7 @@ function createDocsIndexDeps(repoRoot2) {
|
|
|
24973
25088
|
// src/gate-budget.ts
|
|
24974
25089
|
var BLESSED_RUN_WITH_BUDGET_SHA = "5ce541add569e54e7ca96de67a768577d3d604e4";
|
|
24975
25090
|
var BLESSED_RUNNER_GATE_SHA = "a19e9392e5ad9b0675f02e4304d68dd933fad60f";
|
|
25091
|
+
var BLESSED_KIT_AGENT_FILES_SHA = "a8b273ed418e703c0aa501a53ea87228ba28b1f0";
|
|
24976
25092
|
var BLESSED_RUNBOOK_GUARD_SHA = "e661e0aedc0e58b73c738bf6cdfeac65e5914584";
|
|
24977
25093
|
var REMOTE_USE = /^mutmutco\/MMI-Hub\/\.github\/actions\/run-with-budget@(\S+)$/;
|
|
24978
25094
|
var LOCAL_USE = "./.github/actions/run-with-budget";
|
|
@@ -25348,6 +25464,9 @@ function gateSeedVars(cls, releaseTrack, runtime = "node", requiredCheckBranches
|
|
|
25348
25464
|
// The pin is the CLI's blessed SHA, not an operator knob — one central place to bump (#3178).
|
|
25349
25465
|
GATE_BUDGET_SHA: BLESSED_RUN_WITH_BUDGET_SHA,
|
|
25350
25466
|
GATE_RUNNER_SHA: BLESSED_RUNNER_GATE_SHA,
|
|
25467
|
+
// #9161: the suite-only kit-file drift action arrived after the shared runner pin, so it carries its own
|
|
25468
|
+
// blessed revision — bumping GATE_RUNNER_SHA for it would move every generic repo's toolchain too.
|
|
25469
|
+
KIT_AGENT_FILES_SHA: BLESSED_KIT_AGENT_FILES_SHA,
|
|
25351
25470
|
// #8129: the guard and its bundled CLI must come from the same immutable Hub revision.
|
|
25352
25471
|
GATE_RUNBOOK_SHA: BLESSED_RUNBOOK_GUARD_SHA,
|
|
25353
25472
|
// #7407: repo-declared service containers and extra check steps; empty renders today's gate byte-for-byte.
|
|
@@ -27091,10 +27210,10 @@ var rollout_plan_default = {
|
|
|
27091
27210
|
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)."
|
|
27092
27211
|
},
|
|
27093
27212
|
baseline: {
|
|
27094
|
-
version: "4.5.
|
|
27095
|
-
tag: "v4.5.
|
|
27096
|
-
commit: "
|
|
27097
|
-
npm: "@mutmutco/cli@4.5.
|
|
27213
|
+
version: "4.5.229",
|
|
27214
|
+
tag: "v4.5.229",
|
|
27215
|
+
commit: "15792605c6d9",
|
|
27216
|
+
npm: "@mutmutco/cli@4.5.229"
|
|
27098
27217
|
},
|
|
27099
27218
|
exitCriterion: "fleet-n-of-n",
|
|
27100
27219
|
hubOnlyShortcut: "forbidden",
|
|
@@ -27111,14 +27230,14 @@ var rollout_plan_default = {
|
|
|
27111
27230
|
repo: "mutmutco/mmi-hub",
|
|
27112
27231
|
role: "canary",
|
|
27113
27232
|
schedule: "train",
|
|
27114
|
-
v3Target: "v4.5.
|
|
27233
|
+
v3Target: "v4.5.229"
|
|
27115
27234
|
}
|
|
27116
27235
|
],
|
|
27117
27236
|
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.",
|
|
27118
27237
|
rollback: {
|
|
27119
27238
|
independent: true,
|
|
27120
|
-
mechanism: "npm dist-tag latest -> 4.5.
|
|
27121
|
-
v3Target: "v4.5.
|
|
27239
|
+
mechanism: "npm dist-tag latest -> 4.5.229 and redeploy the Hub Lambda from tag v4.5.229 (15792605c6d9); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
|
|
27240
|
+
v3Target: "v4.5.229 (@mutmutco/cli@4.5.229, tag commit 15792605c6d9 \u2014 last known-good release carrying the repo-index v4-only contract)"
|
|
27122
27241
|
}
|
|
27123
27242
|
},
|
|
27124
27243
|
{
|
|
@@ -29275,7 +29394,8 @@ async function buildTrainApplyContext(deps) {
|
|
|
29275
29394
|
repo,
|
|
29276
29395
|
owner,
|
|
29277
29396
|
slug: name.toLowerCase(),
|
|
29278
|
-
login
|
|
29397
|
+
login,
|
|
29398
|
+
...verdict.authenticatedDelegation === true ? { delegatedActor: login } : {}
|
|
29279
29399
|
};
|
|
29280
29400
|
}
|
|
29281
29401
|
function commandAuthorityLabel(owner) {
|
|
@@ -31664,7 +31784,7 @@ function renderTenantControl(r) {
|
|
|
31664
31784
|
}
|
|
31665
31785
|
|
|
31666
31786
|
// src/train-apply-phases.ts
|
|
31667
|
-
var
|
|
31787
|
+
var import_node_fs31 = require("node:fs");
|
|
31668
31788
|
var import_promises4 = require("node:fs/promises");
|
|
31669
31789
|
var import_node_path28 = require("node:path");
|
|
31670
31790
|
|
|
@@ -31972,6 +32092,71 @@ Resume it: ${resumeCommand}
|
|
|
31972
32092
|
);
|
|
31973
32093
|
}
|
|
31974
32094
|
|
|
32095
|
+
// src/train-apply-pretag-hook.ts
|
|
32096
|
+
var import_node_fs25 = require("node:fs");
|
|
32097
|
+
var PRETAG_SCRIPT = "release:pretag";
|
|
32098
|
+
var PRETAG_TAIL_LINES = 40;
|
|
32099
|
+
function declaredScripts() {
|
|
32100
|
+
try {
|
|
32101
|
+
const parsed = JSON.parse((0, import_node_fs25.readFileSync)("package.json", "utf8"));
|
|
32102
|
+
return parsed.scripts ?? {};
|
|
32103
|
+
} catch {
|
|
32104
|
+
return null;
|
|
32105
|
+
}
|
|
32106
|
+
}
|
|
32107
|
+
function outputTail(error) {
|
|
32108
|
+
const e = error;
|
|
32109
|
+
const streams = [e?.stdout, e?.stderr].filter((s) => typeof s === "string" && s.trim() !== "");
|
|
32110
|
+
const text = streams.length > 0 ? streams.join("\n") : error instanceof Error ? error.message : String(error);
|
|
32111
|
+
const lines2 = text.replace(/\r\n/g, "\n").trimEnd().split("\n");
|
|
32112
|
+
return lines2.slice(-PRETAG_TAIL_LINES).join("\n");
|
|
32113
|
+
}
|
|
32114
|
+
function porcelainPaths(output) {
|
|
32115
|
+
return output.split("\n").map((line) => line.trimEnd()).filter(Boolean).map((line) => line.slice(3).trim().replace(/^"|"$/g, "").replaceAll("\\", "/"));
|
|
32116
|
+
}
|
|
32117
|
+
async function runPreTagHook(deps, input) {
|
|
32118
|
+
const command = `npm run ${PRETAG_SCRIPT}`;
|
|
32119
|
+
const shortSha = input.releaseSha.slice(0, 12);
|
|
32120
|
+
const scripts = declaredScripts();
|
|
32121
|
+
if (!scripts || !String(scripts[PRETAG_SCRIPT] ?? "").trim()) {
|
|
32122
|
+
const note = `no \`${PRETAG_SCRIPT}\` script in the repo's package.json: this release's refusable checks run only in CI, AFTER the tag is pushed \u2014 a late failure then leaves a public tag (and Release) on a version that never shipped (#9189). Declare "${PRETAG_SCRIPT}": "<the checks your publish/Release has refused before>" so the train can refuse before anything reaches origin.`;
|
|
32123
|
+
deps.warn?.(`Release: ${note}`);
|
|
32124
|
+
return { status: "missing", command, note };
|
|
32125
|
+
}
|
|
32126
|
+
const headBefore = clean2(await deps.run("git", ["rev-parse", "HEAD"]));
|
|
32127
|
+
if (headBefore !== input.releaseSha) {
|
|
32128
|
+
throw new Error(
|
|
32129
|
+
`pre-tag checks refused: the checkout is on ${headBefore.slice(0, 12)}, not the folded release commit ${shortSha} \u2014 \`${command}\` must prove the exact commit the tag would make immutable (#9189). Nothing was tagged, main was not pushed and no GitHub Release exists.`
|
|
32130
|
+
);
|
|
32131
|
+
}
|
|
32132
|
+
deps.warn?.(`Release: Running the repo's pre-tag checks (${command})`);
|
|
32133
|
+
try {
|
|
32134
|
+
await deps.run("npm", ["run", PRETAG_SCRIPT]);
|
|
32135
|
+
} catch (e) {
|
|
32136
|
+
throw new Error(
|
|
32137
|
+
`pre-tag checks refused: \`${command}\` failed on the folded release commit ${shortSha} \u2014 nothing was tagged, main was not pushed and no GitHub Release exists (#9189).
|
|
32138
|
+
|
|
32139
|
+
--- ${command} output (tail) ---
|
|
32140
|
+
${outputTail(e)}
|
|
32141
|
+
--- end of ${command} output ---
|
|
32142
|
+
Remedy: fix the failing check in the release fold (or in the repo) and rerun the release. Never tag around it \u2014 the same check runs again on the v* tag, after the tag is public and un-removable without deleting a release.`
|
|
32143
|
+
);
|
|
32144
|
+
}
|
|
32145
|
+
const headAfter = clean2(await deps.run("git", ["rev-parse", "HEAD"]));
|
|
32146
|
+
if (headAfter !== input.releaseSha) {
|
|
32147
|
+
throw new Error(
|
|
32148
|
+
`pre-tag checks refused: \`${command}\` moved the checkout to ${headAfter.slice(0, 12)} (the release commit is ${shortSha}) \u2014 the hook must VERIFY the committed release bytes, never commit or check out. Anything it changed would ship unverified (#9189). Nothing was tagged, main was not pushed and no GitHub Release exists.`
|
|
32149
|
+
);
|
|
32150
|
+
}
|
|
32151
|
+
const dirty = porcelainPaths(await deps.run("git", ["status", "--porcelain=v1", "--untracked-files=no"]));
|
|
32152
|
+
if (dirty.length > 0) {
|
|
32153
|
+
throw new Error(
|
|
32154
|
+
`pre-tag checks refused: \`${command}\` modified tracked file(s) in the release checkout \u2014 ${dirty.join(", ")}. The train tags the COMMITTED fold commit, so an uncommitted regeneration would never ship and the check that produced it proves nothing (#9189). Commit the regenerated file into the fold (or make the hook read-only) and rerun: nothing was tagged, main was not pushed and no GitHub Release exists.`
|
|
32155
|
+
);
|
|
32156
|
+
}
|
|
32157
|
+
return { status: "passed", command, note: `\`${command}\` passed on ${shortSha} before the tag` };
|
|
32158
|
+
}
|
|
32159
|
+
|
|
31975
32160
|
// src/pre-one-zero.ts
|
|
31976
32161
|
function preOneZeroInForce(meta) {
|
|
31977
32162
|
return !!meta?.suite && meta.preOneZero !== false;
|
|
@@ -31994,12 +32179,12 @@ function assertPreOneZero(repo, meta, tag) {
|
|
|
31994
32179
|
|
|
31995
32180
|
// src/train-doctor.ts
|
|
31996
32181
|
var import_node_child_process9 = require("node:child_process");
|
|
31997
|
-
var
|
|
32182
|
+
var import_node_fs29 = require("node:fs");
|
|
31998
32183
|
var import_node_os16 = require("node:os");
|
|
31999
32184
|
var import_node_path27 = require("node:path");
|
|
32000
32185
|
|
|
32001
32186
|
// src/doctor-io.ts
|
|
32002
|
-
var
|
|
32187
|
+
var import_node_fs26 = require("node:fs");
|
|
32003
32188
|
var import_node_os13 = require("node:os");
|
|
32004
32189
|
var import_node_path22 = require("node:path");
|
|
32005
32190
|
var import_node_child_process7 = require("node:child_process");
|
|
@@ -32014,8 +32199,8 @@ function nodeDiscardSinkPath(platform2 = process.platform) {
|
|
|
32014
32199
|
var execFileP2 = (0, import_node_util4.promisify)(import_node_child_process7.execFile);
|
|
32015
32200
|
function execFileCapture(file, args, opts = {}) {
|
|
32016
32201
|
const sink = nodeDiscardSinkPath();
|
|
32017
|
-
const inFd = (0,
|
|
32018
|
-
const errFd = (0,
|
|
32202
|
+
const inFd = (0, import_node_fs26.openSync)(sink, "r");
|
|
32203
|
+
const errFd = (0, import_node_fs26.openSync)(sink, "w");
|
|
32019
32204
|
try {
|
|
32020
32205
|
return (0, import_node_child_process7.execFileSync)(file, args, {
|
|
32021
32206
|
...opts,
|
|
@@ -32023,17 +32208,17 @@ function execFileCapture(file, args, opts = {}) {
|
|
|
32023
32208
|
stdio: [inFd, "pipe", errFd]
|
|
32024
32209
|
});
|
|
32025
32210
|
} finally {
|
|
32026
|
-
(0,
|
|
32027
|
-
(0,
|
|
32211
|
+
(0, import_node_fs26.closeSync)(inFd);
|
|
32212
|
+
(0, import_node_fs26.closeSync)(errFd);
|
|
32028
32213
|
}
|
|
32029
32214
|
}
|
|
32030
32215
|
var MMI_PLUGIN_ID2 = "mmi@mutmutco";
|
|
32031
32216
|
function stagedClaudePluginVersion() {
|
|
32032
32217
|
try {
|
|
32033
32218
|
const stagingRoot = (0, import_node_path22.join)((0, import_node_os13.homedir)(), ".claude", "plugins", "staging");
|
|
32034
|
-
const record2 = JSON.parse((0,
|
|
32219
|
+
const record2 = JSON.parse((0, import_node_fs26.readFileSync)((0, import_node_path22.join)(stagingRoot, "launch-target.json"), "utf8"));
|
|
32035
32220
|
if (typeof record2.target !== "string" || typeof record2.incoming !== "string") return void 0;
|
|
32036
|
-
const manifest = JSON.parse((0,
|
|
32221
|
+
const manifest = JSON.parse((0, import_node_fs26.readFileSync)((0, import_node_path22.join)(record2.incoming, ".claude-plugin", "plugin.json"), "utf8"));
|
|
32037
32222
|
if (typeof manifest.version !== "string") return void 0;
|
|
32038
32223
|
return manifest.version === record2.target ? record2.target : void 0;
|
|
32039
32224
|
} catch {
|
|
@@ -32043,7 +32228,7 @@ function stagedClaudePluginVersion() {
|
|
|
32043
32228
|
function installedClaudePluginVersion() {
|
|
32044
32229
|
try {
|
|
32045
32230
|
const file = JSON.parse(
|
|
32046
|
-
(0,
|
|
32231
|
+
(0, import_node_fs26.readFileSync)((0, import_node_path22.join)((0, import_node_os13.homedir)(), ".claude", "plugins", "installed_plugins.json"), "utf8")
|
|
32047
32232
|
);
|
|
32048
32233
|
const versions = (file.plugins?.[MMI_PLUGIN_ID2] ?? []).map((r) => r.version).filter((v) => Boolean(v));
|
|
32049
32234
|
if (versions.length === 0) return void 0;
|
|
@@ -32052,57 +32237,24 @@ function installedClaudePluginVersion() {
|
|
|
32052
32237
|
return void 0;
|
|
32053
32238
|
}
|
|
32054
32239
|
}
|
|
32055
|
-
function
|
|
32056
|
-
const
|
|
32057
|
-
const
|
|
32058
|
-
const
|
|
32059
|
-
const manifestPath = (0, import_node_path22.join)(root, "plugin.yaml");
|
|
32060
|
-
let installedVersion;
|
|
32240
|
+
function readOpenBotPluginEvidence(env = process.env) {
|
|
32241
|
+
const workspaces = openbotAgentWorkspaces(env);
|
|
32242
|
+
const record2 = openbotManagedRecord(env);
|
|
32243
|
+
const recordPath = record2 ? (0, import_node_path22.join)(record2.workspace, ".agents", "skills", ".mmi-managed.json") : void 0;
|
|
32061
32244
|
let manifest = "missing";
|
|
32062
|
-
|
|
32063
|
-
const text = (0, import_node_fs25.readFileSync)(manifestPath, "utf8");
|
|
32064
|
-
let version;
|
|
32065
|
-
try {
|
|
32066
|
-
const parsed = JSON.parse(text).version;
|
|
32067
|
-
if (typeof parsed === "string" && parsed.trim()) version = parsed.trim();
|
|
32068
|
-
} catch {
|
|
32069
|
-
}
|
|
32070
|
-
if (!version) {
|
|
32071
|
-
const match = /^version:\s*["']?([^\s"'#]+)["']?\s*(?:#.*)?$/m.exec(text);
|
|
32072
|
-
version = match?.[1];
|
|
32073
|
-
}
|
|
32074
|
-
if (version) {
|
|
32075
|
-
installedVersion = version;
|
|
32076
|
-
manifest = "valid";
|
|
32077
|
-
} else if ((0, import_node_fs25.existsSync)(manifestPath)) manifest = "invalid";
|
|
32078
|
-
} catch {
|
|
32079
|
-
if ((0, import_node_fs25.existsSync)(manifestPath)) manifest = "invalid";
|
|
32080
|
-
}
|
|
32081
|
-
let skills = false;
|
|
32082
|
-
try {
|
|
32083
|
-
skills = (0, import_node_fs25.existsSync)((0, import_node_path22.join)(root, "skills")) && (0, import_node_fs25.statSync)((0, import_node_path22.join)(root, "skills")).isDirectory();
|
|
32084
|
-
} catch {
|
|
32085
|
-
}
|
|
32086
|
-
let provisioned = false;
|
|
32087
|
-
const provisionedRoot = (0, import_node_path22.join)(host, "skills", "mmi");
|
|
32088
|
-
try {
|
|
32089
|
-
provisioned = (0, import_node_fs25.existsSync)(provisionedRoot) && (0, import_node_fs25.statSync)(provisionedRoot).isDirectory();
|
|
32090
|
-
} catch {
|
|
32091
|
-
}
|
|
32092
|
-
const treeOk = (0, import_node_fs25.existsSync)((0, import_node_path22.join)(root, "__init__.py")) && skills && manifest === "valid";
|
|
32093
|
-
const receipt = treeOk && !provisioned ? `hermes-skills-unprovisioned:${provisionedRoot}` : void 0;
|
|
32245
|
+
if (recordPath) manifest = record2?.version ? "valid" : "invalid";
|
|
32094
32246
|
return {
|
|
32095
|
-
hostPresent:
|
|
32096
|
-
installRecordPresent,
|
|
32247
|
+
hostPresent: workspaces.length > 0,
|
|
32248
|
+
installRecordPresent: record2 !== null,
|
|
32097
32249
|
manifest,
|
|
32098
|
-
payloadPresent:
|
|
32099
|
-
...
|
|
32100
|
-
...
|
|
32250
|
+
payloadPresent: record2 !== null && openbotWorkspaceSkillsHealthy(env),
|
|
32251
|
+
...record2?.version ? { installedVersion: record2.version } : {},
|
|
32252
|
+
...recordPath && !record2?.version ? { receipt: `openbot-ownership-unreadable:${recordPath}` } : {}
|
|
32101
32253
|
};
|
|
32102
32254
|
}
|
|
32103
32255
|
function installedSurfacePluginVersion(surface) {
|
|
32104
32256
|
const token = surfaceToken(surface);
|
|
32105
|
-
if (token === "
|
|
32257
|
+
if (token === "openbot") return readOpenBotPluginEvidence().installedVersion;
|
|
32106
32258
|
if (token === "jervcode") {
|
|
32107
32259
|
return installedJervCodePackageVersion();
|
|
32108
32260
|
}
|
|
@@ -32144,7 +32296,7 @@ function readGitignore() {
|
|
|
32144
32296
|
const path2 = gitignorePath();
|
|
32145
32297
|
if (path2 === null) return null;
|
|
32146
32298
|
try {
|
|
32147
|
-
return (0,
|
|
32299
|
+
return (0, import_node_fs26.readFileSync)(path2, "utf8");
|
|
32148
32300
|
} catch {
|
|
32149
32301
|
return null;
|
|
32150
32302
|
}
|
|
@@ -32153,14 +32305,14 @@ function writeGitignore(content) {
|
|
|
32153
32305
|
const path2 = gitignorePath();
|
|
32154
32306
|
if (path2 === null) return false;
|
|
32155
32307
|
try {
|
|
32156
|
-
(0,
|
|
32308
|
+
(0, import_node_fs26.writeFileSync)(path2, content, "utf8");
|
|
32157
32309
|
return true;
|
|
32158
32310
|
} catch {
|
|
32159
32311
|
return false;
|
|
32160
32312
|
}
|
|
32161
32313
|
}
|
|
32162
32314
|
function lineEndingState(root) {
|
|
32163
|
-
const attributesPresent = (0,
|
|
32315
|
+
const attributesPresent = (0, import_node_fs26.existsSync)((0, import_node_path22.join)(root, ".gitattributes"));
|
|
32164
32316
|
try {
|
|
32165
32317
|
const output = execFileCapture("git", ["-C", root, "ls-files", "--eol", "--", ":(glob)**/*.sh"], {
|
|
32166
32318
|
windowsHide: true
|
|
@@ -32203,7 +32355,7 @@ function compatHolds(range, version) {
|
|
|
32203
32355
|
}
|
|
32204
32356
|
|
|
32205
32357
|
// src/claude-binary-doctor.ts
|
|
32206
|
-
var
|
|
32358
|
+
var import_node_fs27 = require("node:fs");
|
|
32207
32359
|
var import_node_os15 = require("node:os");
|
|
32208
32360
|
var import_node_path24 = require("node:path");
|
|
32209
32361
|
|
|
@@ -32311,16 +32463,16 @@ function globalNodeModulesRoots(host) {
|
|
|
32311
32463
|
function readHead(path2) {
|
|
32312
32464
|
let fd;
|
|
32313
32465
|
try {
|
|
32314
|
-
fd = (0,
|
|
32466
|
+
fd = (0, import_node_fs27.openSync)(path2, "r");
|
|
32315
32467
|
const buffer = new Uint8Array(MAGIC_HEAD_BYTES);
|
|
32316
|
-
const read = (0,
|
|
32468
|
+
const read = (0, import_node_fs27.readSync)(fd, buffer, 0, MAGIC_HEAD_BYTES, 0);
|
|
32317
32469
|
return buffer.subarray(0, read);
|
|
32318
32470
|
} catch {
|
|
32319
32471
|
return void 0;
|
|
32320
32472
|
} finally {
|
|
32321
32473
|
if (fd !== void 0) {
|
|
32322
32474
|
try {
|
|
32323
|
-
(0,
|
|
32475
|
+
(0, import_node_fs27.closeSync)(fd);
|
|
32324
32476
|
} catch {
|
|
32325
32477
|
}
|
|
32326
32478
|
}
|
|
@@ -32328,7 +32480,7 @@ function readHead(path2) {
|
|
|
32328
32480
|
}
|
|
32329
32481
|
function fileBytes(path2) {
|
|
32330
32482
|
try {
|
|
32331
|
-
return (0,
|
|
32483
|
+
return (0, import_node_fs27.statSync)(path2).size;
|
|
32332
32484
|
} catch {
|
|
32333
32485
|
return void 0;
|
|
32334
32486
|
}
|
|
@@ -32342,13 +32494,13 @@ function readClaudeBinaryState(host = {}) {
|
|
|
32342
32494
|
const arch = host.arch ?? process.arch;
|
|
32343
32495
|
const magic = EXECUTABLE_MAGIC[platform2];
|
|
32344
32496
|
if (!magic) return void 0;
|
|
32345
|
-
const packageRoot = globalNodeModulesRoots(host).map((root) => (0, import_node_path24.join)(root, ...PACKAGE.split("/"))).find((dir) => (0,
|
|
32497
|
+
const packageRoot = globalNodeModulesRoots(host).map((root) => (0, import_node_path24.join)(root, ...PACKAGE.split("/"))).find((dir) => (0, import_node_fs27.existsSync)((0, import_node_path24.join)(dir, "package.json")));
|
|
32346
32498
|
if (!packageRoot) return void 0;
|
|
32347
32499
|
const keys = platformPackageKeys(platform2, arch);
|
|
32348
32500
|
const fallbackPackage = `${PACKAGE}-${keys[0]}`;
|
|
32349
32501
|
let manifest;
|
|
32350
32502
|
try {
|
|
32351
|
-
manifest = JSON.parse((0,
|
|
32503
|
+
manifest = JSON.parse((0, import_node_fs27.readFileSync)((0, import_node_path24.join)(packageRoot, "package.json"), "utf8"));
|
|
32352
32504
|
} catch (e) {
|
|
32353
32505
|
return {
|
|
32354
32506
|
state: "unreadable",
|
|
@@ -32379,7 +32531,7 @@ function readClaudeBinaryState(host = {}) {
|
|
|
32379
32531
|
(0, import_node_path24.join)(packageRoot, "node_modules", ...name.split("/"), binName),
|
|
32380
32532
|
(0, import_node_path24.join)((0, import_node_path24.dirname)((0, import_node_path24.dirname)(packageRoot)), ...name.split("/"), binName)
|
|
32381
32533
|
];
|
|
32382
|
-
const found = published.map((name) => ({ name, path: binIn(name).find((file) => (0,
|
|
32534
|
+
const found = published.map((name) => ({ name, path: binIn(name).find((file) => (0, import_node_fs27.existsSync)(file)) })).find((c) => c.path);
|
|
32383
32535
|
const platformPackage = found?.name ?? published[0];
|
|
32384
32536
|
let source;
|
|
32385
32537
|
let sourceProblem;
|
|
@@ -32390,7 +32542,7 @@ function readClaudeBinaryState(host = {}) {
|
|
|
32390
32542
|
} else {
|
|
32391
32543
|
source = { path: found.path, bytes: fileBytes(found.path) ?? 0 };
|
|
32392
32544
|
}
|
|
32393
|
-
if (!(0,
|
|
32545
|
+
if (!(0, import_node_fs27.existsSync)(binPath)) {
|
|
32394
32546
|
return { state: "missing", binPath, expectedMagic: magic.name, platformPackage, ...source ? { source } : {}, ...sourceProblem ? { sourceProblem } : {} };
|
|
32395
32547
|
}
|
|
32396
32548
|
const head = readHead(binPath);
|
|
@@ -32433,9 +32585,9 @@ function healClaudeBinary(host = {}, onStep) {
|
|
|
32433
32585
|
const platform2 = host.platform ?? process.platform;
|
|
32434
32586
|
const aside = `${probe.binPath}.stub-${Date.now()}`;
|
|
32435
32587
|
let renamed = false;
|
|
32436
|
-
if ((0,
|
|
32588
|
+
if ((0, import_node_fs27.existsSync)(probe.binPath)) {
|
|
32437
32589
|
try {
|
|
32438
|
-
(0,
|
|
32590
|
+
(0, import_node_fs27.renameSync)(probe.binPath, aside);
|
|
32439
32591
|
renamed = true;
|
|
32440
32592
|
onStep?.(`renamed the stub aside: ${aside}`);
|
|
32441
32593
|
} catch (e) {
|
|
@@ -32444,12 +32596,12 @@ function healClaudeBinary(host = {}, onStep) {
|
|
|
32444
32596
|
}
|
|
32445
32597
|
try {
|
|
32446
32598
|
onStep?.(`copying ${probe.source.path} \u2192 ${probe.binPath} (${(probe.source.bytes / 1e6).toFixed(0)} MB)`);
|
|
32447
|
-
(0,
|
|
32448
|
-
if (platform2 !== "win32") (0,
|
|
32599
|
+
(0, import_node_fs27.copyFileSync)(probe.source.path, probe.binPath);
|
|
32600
|
+
if (platform2 !== "win32") (0, import_node_fs27.chmodSync)(probe.binPath, 493);
|
|
32449
32601
|
} catch (e) {
|
|
32450
32602
|
if (renamed) {
|
|
32451
32603
|
try {
|
|
32452
|
-
(0,
|
|
32604
|
+
(0, import_node_fs27.renameSync)(aside, probe.binPath);
|
|
32453
32605
|
} catch {
|
|
32454
32606
|
return { ok: false, detail: `copy failed (${e.message}) and the stub could not be restored \u2014 the original is at ${aside}` };
|
|
32455
32607
|
}
|
|
@@ -32463,7 +32615,7 @@ function healClaudeBinary(host = {}, onStep) {
|
|
|
32463
32615
|
let kept = false;
|
|
32464
32616
|
if (renamed) {
|
|
32465
32617
|
try {
|
|
32466
|
-
(0,
|
|
32618
|
+
(0, import_node_fs27.rmSync)(aside);
|
|
32467
32619
|
} catch {
|
|
32468
32620
|
kept = true;
|
|
32469
32621
|
}
|
|
@@ -32822,7 +32974,7 @@ function checkLineEndings(probe) {
|
|
|
32822
32974
|
}
|
|
32823
32975
|
|
|
32824
32976
|
// src/release-ledger.ts
|
|
32825
|
-
var
|
|
32977
|
+
var import_node_fs28 = require("node:fs");
|
|
32826
32978
|
var import_node_path26 = require("node:path");
|
|
32827
32979
|
var import_node_crypto9 = require("node:crypto");
|
|
32828
32980
|
|
|
@@ -32997,8 +33149,8 @@ function attachOutputNote(note, suffix) {
|
|
|
32997
33149
|
}
|
|
32998
33150
|
function writeFailingLegOutput(artifactPath, output) {
|
|
32999
33151
|
const fileLines = lastNonEmptyLines(output, LEDGER_OUTPUT_FILE_LINES);
|
|
33000
|
-
(0,
|
|
33001
|
-
(0,
|
|
33152
|
+
(0, import_node_fs28.mkdirSync)((0, import_node_path26.dirname)(artifactPath), { recursive: true });
|
|
33153
|
+
(0, import_node_fs28.writeFileSync)(artifactPath, `${fileLines.join("\n")}
|
|
33002
33154
|
`, "utf8");
|
|
33003
33155
|
const tail = fileLines.slice(-LEDGER_OUTPUT_NOTE_LINES).join("\n");
|
|
33004
33156
|
const noteSuffix = truncate(`failing-leg output: ${artifactPath}
|
|
@@ -33166,7 +33318,7 @@ function resolvePrimaryGitDir(cwd) {
|
|
|
33166
33318
|
}
|
|
33167
33319
|
let common;
|
|
33168
33320
|
try {
|
|
33169
|
-
common = (0,
|
|
33321
|
+
common = (0, import_node_fs28.readFileSync)((0, import_node_path26.join)(gitDir, "commondir"), "utf8").trim();
|
|
33170
33322
|
} catch (e) {
|
|
33171
33323
|
if (e.code !== "ENOENT") {
|
|
33172
33324
|
throw new ReleaseLedgerError("unreadable", `release ledger: the worktree gitdir's commondir pointer could not be READ (${e instanceof Error ? e.message : String(e)}) \u2014 a failed read is unverified, not primary (#5987/#4713)`);
|
|
@@ -33175,7 +33327,7 @@ function resolvePrimaryGitDir(cwd) {
|
|
|
33175
33327
|
if (!common) return gitDir;
|
|
33176
33328
|
const commonDir = (0, import_node_path26.isAbsolute)(common) ? common : (0, import_node_path26.resolve)(gitDir, common);
|
|
33177
33329
|
try {
|
|
33178
|
-
(0,
|
|
33330
|
+
(0, import_node_fs28.readFileSync)((0, import_node_path26.join)(commonDir, "HEAD"), "utf8");
|
|
33179
33331
|
} catch (e) {
|
|
33180
33332
|
throw new ReleaseLedgerError("no-checkout", `release ledger: the worktree's commondir pointer names ${commonDir}, which has no readable HEAD (${e instanceof Error ? e.message : String(e)}) \u2014 the primary checkout may have been removed; refusing to write the ledger into a doomed location`);
|
|
33181
33333
|
}
|
|
@@ -33185,11 +33337,11 @@ function releaseLedgerPath(cwd) {
|
|
|
33185
33337
|
return (0, import_node_path26.join)(resolvePrimaryGitDir(cwd), "mmi-runtime", "release", "phases.json");
|
|
33186
33338
|
}
|
|
33187
33339
|
function atomicWriteJson(path2, ledger) {
|
|
33188
|
-
(0,
|
|
33340
|
+
(0, import_node_fs28.mkdirSync)((0, import_node_path26.dirname)(path2), { recursive: true });
|
|
33189
33341
|
const tmp = `${path2}.tmp-${process.pid}-${(0, import_node_crypto9.randomBytes)(4).toString("hex")}`;
|
|
33190
|
-
(0,
|
|
33342
|
+
(0, import_node_fs28.writeFileSync)(tmp, `${JSON.stringify(ledger, null, 2)}
|
|
33191
33343
|
`, "utf8");
|
|
33192
|
-
(0,
|
|
33344
|
+
(0, import_node_fs28.renameSync)(tmp, path2);
|
|
33193
33345
|
}
|
|
33194
33346
|
var LEDGER_LOCK_OPTS = { label: "release ledger", staleMs: 18e4, deadHolderSteal: true };
|
|
33195
33347
|
async function withLedgerLock(path2, fn, opts = {}) {
|
|
@@ -33236,7 +33388,7 @@ function describeTrainWriterToken(token) {
|
|
|
33236
33388
|
function liveTrainWriterHolder(cwd) {
|
|
33237
33389
|
let token;
|
|
33238
33390
|
try {
|
|
33239
|
-
token = (0,
|
|
33391
|
+
token = (0, import_node_fs28.readFileSync)(trainWriterLockPath(cwd), "utf8");
|
|
33240
33392
|
} catch {
|
|
33241
33393
|
return void 0;
|
|
33242
33394
|
}
|
|
@@ -33258,7 +33410,7 @@ async function acquireTrainWriterLock(cwd, verb) {
|
|
|
33258
33410
|
if (heldTrainWriter) heldTrainWriter.depth -= 1;
|
|
33259
33411
|
};
|
|
33260
33412
|
}
|
|
33261
|
-
(0,
|
|
33413
|
+
(0, import_node_fs28.mkdirSync)((0, import_node_path26.dirname)(lockPath), { recursive: true });
|
|
33262
33414
|
const opts = resolveFileLockOpts({ label: "train writer", staleMs: TRAIN_WRITER_STALE_MS, deadHolderSteal: true, maxWaitMs: 0, holder: verb });
|
|
33263
33415
|
let guard;
|
|
33264
33416
|
try {
|
|
@@ -33267,7 +33419,7 @@ async function acquireTrainWriterLock(cwd, verb) {
|
|
|
33267
33419
|
if (!(e instanceof FileLockBusyError)) throw e;
|
|
33268
33420
|
let token = "";
|
|
33269
33421
|
try {
|
|
33270
|
-
token = (0,
|
|
33422
|
+
token = (0, import_node_fs28.readFileSync)(lockPath, "utf8");
|
|
33271
33423
|
} catch {
|
|
33272
33424
|
token = "";
|
|
33273
33425
|
}
|
|
@@ -33302,7 +33454,7 @@ async function withReleaseLedger(path2, expected, fn, opts = {}) {
|
|
|
33302
33454
|
let current;
|
|
33303
33455
|
let raw;
|
|
33304
33456
|
try {
|
|
33305
|
-
raw = (0,
|
|
33457
|
+
raw = (0, import_node_fs28.readFileSync)(path2, "utf8");
|
|
33306
33458
|
} catch (e) {
|
|
33307
33459
|
if (e.code !== "ENOENT") {
|
|
33308
33460
|
throw new ReleaseLedgerError("unreadable", `release ledger at ${path2} could not be READ (${e instanceof Error ? e.message : String(e)}) \u2014 a failed read is unverified, not absent (#5987)`);
|
|
@@ -33969,7 +34121,7 @@ async function clearLedgerFile(path2, anchors, maxWaitMs) {
|
|
|
33969
34121
|
return withLedgerLock(path2, async (fence) => {
|
|
33970
34122
|
let raw;
|
|
33971
34123
|
try {
|
|
33972
|
-
raw = (0,
|
|
34124
|
+
raw = (0, import_node_fs28.readFileSync)(path2, "utf8");
|
|
33973
34125
|
} catch (e) {
|
|
33974
34126
|
if (e.code === "ENOENT") return { cleared: false };
|
|
33975
34127
|
return { cleared: false, note: `release ledger at ${path2} could not be read for clearing (${e instanceof Error ? e.message : String(e)}) \u2014 left in place` };
|
|
@@ -33985,7 +34137,7 @@ async function clearLedgerFile(path2, anchors, maxWaitMs) {
|
|
|
33985
34137
|
} catch (e) {
|
|
33986
34138
|
return { cleared: false, note: `release ledger at ${path2} belongs to ${ledger.tag} @ ${ledger.tagSha.slice(0, 12)} \u2014 not this release; left in place (${e instanceof Error ? e.message : String(e)})` };
|
|
33987
34139
|
}
|
|
33988
|
-
await fence(() => (0,
|
|
34140
|
+
await fence(() => (0, import_node_fs28.unlinkSync)(path2));
|
|
33989
34141
|
return { cleared: true, note: `cleared the release ledger at ${path2} for the live-proven aborted ${anchors.tag}` };
|
|
33990
34142
|
}, { maxWaitMs });
|
|
33991
34143
|
}
|
|
@@ -34145,7 +34297,7 @@ async function healStaleAlignmentLeg(deps, cwd, expected) {
|
|
|
34145
34297
|
return withLedgerLock(path2, async (fence) => {
|
|
34146
34298
|
let prior;
|
|
34147
34299
|
try {
|
|
34148
|
-
prior = parseReleaseLedger((0,
|
|
34300
|
+
prior = parseReleaseLedger((0, import_node_fs28.readFileSync)(path2, "utf8"));
|
|
34149
34301
|
} catch {
|
|
34150
34302
|
return void 0;
|
|
34151
34303
|
}
|
|
@@ -34170,7 +34322,7 @@ async function healUnfoldedRunLegs(deps, cwd, model) {
|
|
|
34170
34322
|
const path2 = releaseLedgerPath(cwd);
|
|
34171
34323
|
let prior;
|
|
34172
34324
|
try {
|
|
34173
|
-
prior = parseReleaseLedger((0,
|
|
34325
|
+
prior = parseReleaseLedger((0, import_node_fs28.readFileSync)(path2, "utf8"));
|
|
34174
34326
|
} catch {
|
|
34175
34327
|
return void 0;
|
|
34176
34328
|
}
|
|
@@ -34239,7 +34391,7 @@ async function supersedeContentCausedDeployLeg(deps, cwd) {
|
|
|
34239
34391
|
return withLedgerLock(path2, async (fence) => {
|
|
34240
34392
|
let prior;
|
|
34241
34393
|
try {
|
|
34242
|
-
prior = parseReleaseLedger((0,
|
|
34394
|
+
prior = parseReleaseLedger((0, import_node_fs28.readFileSync)(path2, "utf8"));
|
|
34243
34395
|
} catch (e) {
|
|
34244
34396
|
throw new ReleaseLedgerError(
|
|
34245
34397
|
"unreadable",
|
|
@@ -34301,6 +34453,22 @@ function supersedeablePublishLegShape(rec) {
|
|
|
34301
34453
|
function deferredGatewayDeployLegShape(rec) {
|
|
34302
34454
|
return rec.state === "pending" && rec.runId == null && rec.workflow === "jerv-gateway";
|
|
34303
34455
|
}
|
|
34456
|
+
function releaseAssetsCarryInstallerPayload(assets, tag) {
|
|
34457
|
+
const version = tag.replace(/^v/, "");
|
|
34458
|
+
return assets.some((asset) => typeof asset.name === "string" && asset.name.startsWith("installer-payload-") && asset.name.endsWith(`-${version}.tar.gz`));
|
|
34459
|
+
}
|
|
34460
|
+
async function proveInstallerPayloadPresence(deps, prior, label, ref) {
|
|
34461
|
+
let assets;
|
|
34462
|
+
try {
|
|
34463
|
+
const parsed = JSON.parse(await deps.run("gh", ["release", "view", prior.tag, "--repo", prior.repo, "--json", "assets"]));
|
|
34464
|
+
assets = Array.isArray(parsed.assets) ? parsed.assets : [];
|
|
34465
|
+
} catch (e) {
|
|
34466
|
+
throw new Error(
|
|
34467
|
+
`${label}: could not read the ${prior.tag} GitHub Release asset list (${e instanceof Error ? e.message.split("\n")[0] : String(e)}) \u2014 whether the installer payload exists is unproven, so the deploy leg cannot be closed; retry when GitHub answers; nothing was written (${ref})`
|
|
34468
|
+
);
|
|
34469
|
+
}
|
|
34470
|
+
return releaseAssetsCarryInstallerPayload(assets, prior.tag) ? "present" : "absent";
|
|
34471
|
+
}
|
|
34304
34472
|
async function proveOriginPromotionOrArchived(deps, prior, path2, label, ref, fence) {
|
|
34305
34473
|
let liveSha = "";
|
|
34306
34474
|
try {
|
|
@@ -34337,10 +34505,10 @@ async function proveOriginPromotionOrArchived(deps, prior, path2, label, ref, fe
|
|
|
34337
34505
|
throw new Error(`${label}: origin no longer has ${prior.tag} but its GitHub Release still exists \u2014 resolve the mixed external state before closing the ledger; nothing was written (${ref})`);
|
|
34338
34506
|
}
|
|
34339
34507
|
const archivePath = (0, import_node_path26.join)((0, import_node_path26.dirname)(path2), `phases.archive-${prior.tag}.json`);
|
|
34340
|
-
if ((0,
|
|
34508
|
+
if ((0, import_node_fs28.existsSync)(archivePath)) {
|
|
34341
34509
|
throw new Error(`${label}: archive already exists at ${archivePath} \u2014 preserving both ledger records; resolve the collision by hand; nothing was written (${ref})`);
|
|
34342
34510
|
}
|
|
34343
|
-
await fence(() => (0,
|
|
34511
|
+
await fence(() => (0, import_node_fs28.renameSync)(path2, archivePath));
|
|
34344
34512
|
return "deleted";
|
|
34345
34513
|
}
|
|
34346
34514
|
async function supersedeContentCausedPublishLeg(deps, cwd) {
|
|
@@ -34348,7 +34516,7 @@ async function supersedeContentCausedPublishLeg(deps, cwd) {
|
|
|
34348
34516
|
return withLedgerLock(path2, async (fence) => {
|
|
34349
34517
|
let prior;
|
|
34350
34518
|
try {
|
|
34351
|
-
prior = parseReleaseLedger((0,
|
|
34519
|
+
prior = parseReleaseLedger((0, import_node_fs28.readFileSync)(path2, "utf8"));
|
|
34352
34520
|
} catch (e) {
|
|
34353
34521
|
throw new ReleaseLedgerError(
|
|
34354
34522
|
"unreadable",
|
|
@@ -34386,23 +34554,29 @@ async function supersedeContentCausedPublishLeg(deps, cwd) {
|
|
|
34386
34554
|
};
|
|
34387
34555
|
}
|
|
34388
34556
|
if (deferredGateway) {
|
|
34389
|
-
|
|
34390
|
-
|
|
34391
|
-
prior.
|
|
34392
|
-
|
|
34393
|
-
|
|
34394
|
-
|
|
34395
|
-
|
|
34396
|
-
|
|
34397
|
-
|
|
34398
|
-
|
|
34399
|
-
|
|
34400
|
-
if (dispatch.deployStatus !== "success") {
|
|
34401
|
-
throw new Error(
|
|
34402
|
-
`release --supersede-publish: the deferred ${prior.tag} Jerv Gateway operator-host deploy leg did not verify (${dispatch.note}) \u2014 run \`scripts/jerv-gateway-release-deploy.sh --tag ${prior.tag}\` to a healthy receipt naming the released tag and commit, then retry; nothing was written (#6742)`
|
|
34557
|
+
if (prior.phases.publish.state === "failed" && await proveInstallerPayloadPresence(deps, prior, "release --supersede-publish", "#9182") === "absent") {
|
|
34558
|
+
const payloadNote = `superseded: the ${prior.tag} publish never produced the installer payload (the live release carries no installer-payload asset for this version) \u2014 the deferred Jerv Gateway operator-host deploy leg was skipped, nothing was deployable from this tag; the cause fix rides the next patch train (#9182)`;
|
|
34559
|
+
phases.deploy = { state: "skipped", sha: prior.tagSha, superseded: true, note: payloadNote, updatedAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
34560
|
+
gatewayDeploy = { workflow: "jerv-gateway", state: "skipped", note: payloadNote };
|
|
34561
|
+
} else {
|
|
34562
|
+
const dispatch = await appendJervGatewayReleaseDeploy(
|
|
34563
|
+
deps,
|
|
34564
|
+
prior.repo,
|
|
34565
|
+
prior.tag,
|
|
34566
|
+
prior.tagSha,
|
|
34567
|
+
{ note: `Jerv Gateway operator-host deploy leg for ${prior.tag} run during the publish supersession`, deployStatus: "success" }
|
|
34403
34568
|
);
|
|
34569
|
+
phases.deploy = {
|
|
34570
|
+
...phaseEntry(deployPhaseInput("registry-publish", dispatch, { releaseSha: prior.tagSha })),
|
|
34571
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
34572
|
+
};
|
|
34573
|
+
if (dispatch.deployStatus !== "success") {
|
|
34574
|
+
throw new Error(
|
|
34575
|
+
`release --supersede-publish: the deferred ${prior.tag} Jerv Gateway operator-host deploy leg did not verify (${dispatch.note}) \u2014 run \`scripts/jerv-gateway-release-deploy.sh --tag ${prior.tag}\` to a healthy receipt naming the released tag and commit, then retry; nothing was written (#6742)`
|
|
34576
|
+
);
|
|
34577
|
+
}
|
|
34578
|
+
gatewayDeploy = { workflow: "jerv-gateway", state: "complete", note: dispatch.note };
|
|
34404
34579
|
}
|
|
34405
|
-
gatewayDeploy = { workflow: "jerv-gateway", state: "complete", note: dispatch.note };
|
|
34406
34580
|
}
|
|
34407
34581
|
const note = `superseded: content-caused publish failure on the immutable promoted ${prior.tag} \u2014 the cause fix rides the next patch train (master-approved closure, #6638)`;
|
|
34408
34582
|
await fence(() => atomicWriteJson(path2, {
|
|
@@ -34430,7 +34604,7 @@ async function supersedeContentCausedDeployAndPublishLegs(deps, cwd) {
|
|
|
34430
34604
|
return withLedgerLock(path2, async (fence) => {
|
|
34431
34605
|
let prior;
|
|
34432
34606
|
try {
|
|
34433
|
-
prior = parseReleaseLedger((0,
|
|
34607
|
+
prior = parseReleaseLedger((0, import_node_fs28.readFileSync)(path2, "utf8"));
|
|
34434
34608
|
} catch (e) {
|
|
34435
34609
|
throw new ReleaseLedgerError(
|
|
34436
34610
|
"unreadable",
|
|
@@ -34486,7 +34660,11 @@ async function supersedeContentCausedDeployAndPublishLegs(deps, cwd) {
|
|
|
34486
34660
|
}
|
|
34487
34661
|
let deployEntry;
|
|
34488
34662
|
let gatewayDeploy;
|
|
34489
|
-
if (deferredGateway) {
|
|
34663
|
+
if (deferredGateway && publish.state === "failed" && await proveInstallerPayloadPresence(deps, prior, label, "#9182") === "absent") {
|
|
34664
|
+
const payloadNote = `superseded: the ${prior.tag} publish never produced the installer payload (the live release carries no installer-payload asset for this version) \u2014 the deferred Jerv Gateway operator-host deploy leg was skipped, nothing was deployable from this tag; the cause fix rides the next patch train (#9182)`;
|
|
34665
|
+
deployEntry = { state: "skipped", sha: prior.tagSha, superseded: true, note: payloadNote, updatedAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
34666
|
+
gatewayDeploy = { workflow: "jerv-gateway", state: "skipped", note: payloadNote };
|
|
34667
|
+
} else if (deferredGateway) {
|
|
34490
34668
|
const dispatch = await appendJervGatewayReleaseDeploy(
|
|
34491
34669
|
deps,
|
|
34492
34670
|
prior.repo,
|
|
@@ -34535,7 +34713,7 @@ async function supersedeContentCausedDeployAndPublishLegs(deps, cwd) {
|
|
|
34535
34713
|
async function reverifyPriorRunLegs(deps, path2, targetTag) {
|
|
34536
34714
|
let prior;
|
|
34537
34715
|
try {
|
|
34538
|
-
prior = parseReleaseLedger((0,
|
|
34716
|
+
prior = parseReleaseLedger((0, import_node_fs28.readFileSync)(path2, "utf8"));
|
|
34539
34717
|
} catch {
|
|
34540
34718
|
return;
|
|
34541
34719
|
}
|
|
@@ -34555,7 +34733,7 @@ async function beginReleaseLedger(deps, cwd, targetTag) {
|
|
|
34555
34733
|
return withLedgerLock(path2, async (fence) => {
|
|
34556
34734
|
let raw;
|
|
34557
34735
|
try {
|
|
34558
|
-
raw = (0,
|
|
34736
|
+
raw = (0, import_node_fs28.readFileSync)(path2, "utf8");
|
|
34559
34737
|
} catch (e) {
|
|
34560
34738
|
if (e.code === "ENOENT") return { ok: true };
|
|
34561
34739
|
return { ok: false, error: `the release ledger at ${path2} could not be READ (${e instanceof Error ? e.message : String(e)}) \u2014 a failed read is unverified, not absent (#5987/#4713)` };
|
|
@@ -34595,13 +34773,13 @@ async function beginReleaseLedger(deps, cwd, targetTag) {
|
|
|
34595
34773
|
}
|
|
34596
34774
|
const archivePath = (0, import_node_path26.join)((0, import_node_path26.dirname)(path2), `phases.archive-${prior.tag}.json`);
|
|
34597
34775
|
try {
|
|
34598
|
-
await fence(() => (0,
|
|
34776
|
+
await fence(() => (0, import_node_fs28.unlinkSync)(archivePath));
|
|
34599
34777
|
} catch (e) {
|
|
34600
34778
|
if (e.code !== "ENOENT") {
|
|
34601
34779
|
return { ok: false, error: `could not clear the stale archive at ${archivePath} (${e instanceof Error ? e.message : String(e)}) \u2014 the ledger for ${prior.tag} stays active (#5987)` };
|
|
34602
34780
|
}
|
|
34603
34781
|
}
|
|
34604
|
-
await fence(() => (0,
|
|
34782
|
+
await fence(() => (0, import_node_fs28.renameSync)(path2, archivePath));
|
|
34605
34783
|
return { ok: true, note: `archived the completed release ledger ${prior.tag} to ${archivePath} (live-proven at ${prior.tagSha.slice(0, 12)})` };
|
|
34606
34784
|
});
|
|
34607
34785
|
}
|
|
@@ -34629,7 +34807,7 @@ async function retryHotfixDeployLegs(deps, cwd, opts = {}) {
|
|
|
34629
34807
|
return withLedgerLock(path2, async (fence) => {
|
|
34630
34808
|
let prior;
|
|
34631
34809
|
try {
|
|
34632
|
-
prior = parseReleaseLedger((0,
|
|
34810
|
+
prior = parseReleaseLedger((0, import_node_fs28.readFileSync)(path2, "utf8"));
|
|
34633
34811
|
} catch (e) {
|
|
34634
34812
|
throw new ReleaseLedgerError(
|
|
34635
34813
|
"unreadable",
|
|
@@ -34871,10 +35049,10 @@ function readOriginWorkflowsDefault(root, ref) {
|
|
|
34871
35049
|
}
|
|
34872
35050
|
var NPM_REGISTRY = "https://registry.npmjs.org";
|
|
34873
35051
|
async function probeRegistryWhoami(train) {
|
|
34874
|
-
const dir = (0,
|
|
35052
|
+
const dir = (0, import_node_fs29.mkdtempSync)((0, import_node_path27.join)((0, import_node_os16.tmpdir)(), "mmi-npmrc-"));
|
|
34875
35053
|
const rc = (0, import_node_path27.join)(dir, "npmrc");
|
|
34876
35054
|
try {
|
|
34877
|
-
(0,
|
|
35055
|
+
(0, import_node_fs29.writeFileSync)(rc, "//registry.npmjs.org/:_authToken=${NPM_TOKEN}\n");
|
|
34878
35056
|
await train.runSelf(["secrets", "use", "npm/NPM_TOKEN", "--slug", "_org", "--", "npm", "whoami", "--registry", NPM_REGISTRY, "--userconfig", rc]);
|
|
34879
35057
|
return { kind: "ok", detail: "the registry answered a login" };
|
|
34880
35058
|
} catch (e) {
|
|
@@ -34885,7 +35063,7 @@ async function probeRegistryWhoami(train) {
|
|
|
34885
35063
|
if (/\bE401\b|401 Unauthorized/.test(stderr)) return { kind: "rejected", detail: "npm whoami answered 401 Unauthorized" };
|
|
34886
35064
|
return { kind: "unverified", detail: first };
|
|
34887
35065
|
} finally {
|
|
34888
|
-
(0,
|
|
35066
|
+
(0, import_node_fs29.rmSync)(dir, { recursive: true, force: true });
|
|
34889
35067
|
}
|
|
34890
35068
|
}
|
|
34891
35069
|
var REGISTRY_TOKEN_ENV = /\b(NODE_AUTH_TOKEN|NPM_TOKEN)\b/;
|
|
@@ -34903,7 +35081,7 @@ var defaultDeps2 = {
|
|
|
34903
35081
|
ledgerPath: releaseLedgerPath,
|
|
34904
35082
|
readLedgerRaw: (path2) => {
|
|
34905
35083
|
try {
|
|
34906
|
-
return (0,
|
|
35084
|
+
return (0, import_node_fs29.readFileSync)(path2, "utf8");
|
|
34907
35085
|
} catch (e) {
|
|
34908
35086
|
if (e.code === "ENOENT") return void 0;
|
|
34909
35087
|
throw e;
|
|
@@ -34914,7 +35092,7 @@ var defaultDeps2 = {
|
|
|
34914
35092
|
trainWriterHolder: liveTrainWriterHolder,
|
|
34915
35093
|
readSurfacesRaw: (cwd) => {
|
|
34916
35094
|
try {
|
|
34917
|
-
return (0,
|
|
35095
|
+
return (0, import_node_fs29.readFileSync)((0, import_node_path27.join)(cwd, "surfaces.json"), "utf8");
|
|
34918
35096
|
} catch {
|
|
34919
35097
|
return void 0;
|
|
34920
35098
|
}
|
|
@@ -35564,7 +35742,7 @@ function formatTrainDoctor(v) {
|
|
|
35564
35742
|
|
|
35565
35743
|
// src/release-plain-words.ts
|
|
35566
35744
|
var import_promises3 = require("node:fs/promises");
|
|
35567
|
-
var
|
|
35745
|
+
var import_node_fs30 = require("node:fs");
|
|
35568
35746
|
function releaseCutByNotes(cutBy) {
|
|
35569
35747
|
return `## Internal
|
|
35570
35748
|
Cut by ${formatSessionIdentity(cutBy)}
|
|
@@ -35670,7 +35848,7 @@ function validateSuiteReleaseSummary(path2, pathTr) {
|
|
|
35670
35848
|
for (const [lang, file] of files) {
|
|
35671
35849
|
let content;
|
|
35672
35850
|
try {
|
|
35673
|
-
content = (0,
|
|
35851
|
+
content = (0, import_node_fs30.readFileSync)(file, "utf8");
|
|
35674
35852
|
} catch (e) {
|
|
35675
35853
|
throw new Error(`could not read summary file ${file}: ${e.message} \u2014 see ${SUMMARY_GUIDE}`);
|
|
35676
35854
|
}
|
|
@@ -35885,14 +36063,14 @@ function readLocalGateWorkflows() {
|
|
|
35885
36063
|
const dir = (0, import_node_path28.join)(".github", "workflows");
|
|
35886
36064
|
let names;
|
|
35887
36065
|
try {
|
|
35888
|
-
names = (0,
|
|
36066
|
+
names = (0, import_node_fs31.readdirSync)(dir);
|
|
35889
36067
|
} catch {
|
|
35890
36068
|
return null;
|
|
35891
36069
|
}
|
|
35892
36070
|
const files = [];
|
|
35893
36071
|
for (const name of names.filter(isGateWorkflowPath)) {
|
|
35894
36072
|
try {
|
|
35895
|
-
files.push({ path: `${dir}/${name}`.replace(/\\/g, "/"), body: (0,
|
|
36073
|
+
files.push({ path: `${dir}/${name}`.replace(/\\/g, "/"), body: (0, import_node_fs31.readFileSync)((0, import_node_path28.join)(dir, name), "utf8") });
|
|
35896
36074
|
} catch {
|
|
35897
36075
|
}
|
|
35898
36076
|
}
|
|
@@ -35902,14 +36080,14 @@ function readLocalWorkflows() {
|
|
|
35902
36080
|
const dir = (0, import_node_path28.join)(".github", "workflows");
|
|
35903
36081
|
let names;
|
|
35904
36082
|
try {
|
|
35905
|
-
names = (0,
|
|
36083
|
+
names = (0, import_node_fs31.readdirSync)(dir);
|
|
35906
36084
|
} catch {
|
|
35907
36085
|
return null;
|
|
35908
36086
|
}
|
|
35909
36087
|
const files = [];
|
|
35910
36088
|
for (const name of names.filter((n) => /\.ya?ml$/i.test(n))) {
|
|
35911
36089
|
try {
|
|
35912
|
-
files.push({ path: `${dir}/${name}`.replace(/\\/g, "/"), body: (0,
|
|
36090
|
+
files.push({ path: `${dir}/${name}`.replace(/\\/g, "/"), body: (0, import_node_fs31.readFileSync)((0, import_node_path28.join)(dir, name), "utf8") });
|
|
35913
36091
|
} catch {
|
|
35914
36092
|
}
|
|
35915
36093
|
}
|
|
@@ -36581,6 +36759,12 @@ async function pushAfterVercelVersionEnv(deps, meta, stage, tag, pushArgs) {
|
|
|
36581
36759
|
}
|
|
36582
36760
|
async function completeMainRelease(deps, ctx, meta, deployModel, watch, options, tag, releaseSha, startBranch, preFold, tagProbe, branchHints, ledger, alignEarly) {
|
|
36583
36761
|
const resumeCommand = options.dev ? "mmi-cli devops release --dev --apply" : "mmi-cli devops release --apply";
|
|
36762
|
+
let pretag;
|
|
36763
|
+
try {
|
|
36764
|
+
pretag = await runPreTagHook(deps, { releaseSha });
|
|
36765
|
+
} catch (e) {
|
|
36766
|
+
throw await recoverFailedFold(deps, e, startBranch, preFold.mainSha, resumeCommand);
|
|
36767
|
+
}
|
|
36584
36768
|
const ledgerAnchors = { repo: ctx.repo, tag, tagSha: releaseSha };
|
|
36585
36769
|
try {
|
|
36586
36770
|
deps.warn?.("Release: Checking the package before publishing");
|
|
@@ -36756,7 +36940,7 @@ ${recovery.note}`), recoveryInput);
|
|
|
36756
36940
|
});
|
|
36757
36941
|
alignment = await aligning;
|
|
36758
36942
|
const announceNote = annotateVersionEnvAnnounce(await announcing, versionEnv);
|
|
36759
|
-
return { checks, releaseUrl, announceNote, localInstall, dispatch, ledgerAnchors, ...alignment ? { alignment } : {} };
|
|
36943
|
+
return { checks, releaseUrl, announceNote, localInstall, dispatch, ledgerAnchors, pretag, ...alignment ? { alignment } : {} };
|
|
36760
36944
|
}
|
|
36761
36945
|
function resumeStateOf(status) {
|
|
36762
36946
|
return status === "complete" ? "aligned" : status;
|
|
@@ -36798,7 +36982,7 @@ async function activeRcandLedgerCandidate(deps, cwd) {
|
|
|
36798
36982
|
}
|
|
36799
36983
|
let prior;
|
|
36800
36984
|
try {
|
|
36801
|
-
prior = parseReleaseLedger((0,
|
|
36985
|
+
prior = parseReleaseLedger((0, import_node_fs31.readFileSync)(path2, "utf8"));
|
|
36802
36986
|
} catch {
|
|
36803
36987
|
return void 0;
|
|
36804
36988
|
}
|
|
@@ -37404,7 +37588,15 @@ async function runReleaseAbortLocked(deps, options) {
|
|
|
37404
37588
|
);
|
|
37405
37589
|
}
|
|
37406
37590
|
await requireCleanTree(deps);
|
|
37407
|
-
|
|
37591
|
+
const ciCheckout = options.ciCheckout === true;
|
|
37592
|
+
if (ciCheckout) {
|
|
37593
|
+
if (!isHubControlRepo(ctx.repo)) {
|
|
37594
|
+
throw new Error(`release --abort: the CI checkout path aborts ${ctx.repo} never; it is for the Hub release workflow only; nothing was written`);
|
|
37595
|
+
}
|
|
37596
|
+
if (!ctx.delegatedActor) {
|
|
37597
|
+
throw new Error("release --abort: the CI checkout path needs the run's proven org-owner trigger (hub release workflow authority); nothing was written");
|
|
37598
|
+
}
|
|
37599
|
+
} else if (await currentBranch(deps) !== "main") {
|
|
37408
37600
|
throw new Error("release --abort must run from the primary checkout left on main by the failed train; nothing was written");
|
|
37409
37601
|
}
|
|
37410
37602
|
await runGitRemoteRead(deps, ["fetch", "origin", "--tags"]);
|
|
@@ -37414,7 +37606,7 @@ async function runReleaseAbortLocked(deps, options) {
|
|
|
37414
37606
|
const tagSha = await probeRemoteTag(deps, tag);
|
|
37415
37607
|
if (!tagSha) throw new Error(`release --abort: ${tag} is not on origin; nothing was written`);
|
|
37416
37608
|
const localTagSha = clean2(await deps.run("git", ["rev-parse", "--verify", `refs/tags/${tag}^{commit}`]));
|
|
37417
|
-
const localMainSha = clean2(await deps.run("git", ["rev-parse", "main"]));
|
|
37609
|
+
const localMainSha = ciCheckout ? tagSha : clean2(await deps.run("git", ["rev-parse", "main"]));
|
|
37418
37610
|
const originMainSha = clean2(await deps.run("git", ["rev-parse", "origin/main"]));
|
|
37419
37611
|
if (localTagSha !== tagSha || localMainSha !== tagSha) {
|
|
37420
37612
|
throw new Error(
|
|
@@ -37445,6 +37637,17 @@ async function runReleaseAbortLocked(deps, options) {
|
|
|
37445
37637
|
throw new Error(`release --abort: origin still reports ${tag} after deletion; local state was preserved`);
|
|
37446
37638
|
}
|
|
37447
37639
|
await deps.run("git", ["tag", "-d", tag]);
|
|
37640
|
+
if (ciCheckout) {
|
|
37641
|
+
const ciLedgerNote = await clearReleaseLedger(process.cwd(), { repo: ctx.repo, tag, tagSha });
|
|
37642
|
+
return {
|
|
37643
|
+
command: "release-abort",
|
|
37644
|
+
repo: ctx.repo,
|
|
37645
|
+
tag,
|
|
37646
|
+
tagSha,
|
|
37647
|
+
restoredMainSha: originMainSha,
|
|
37648
|
+
note: `removed the unpublished ${tag} candidate from the Hub release workflow; destructive approval: @${ctx.delegatedActor} triggered this run and is a proven active org owner; origin/main stays ${originMainSha.slice(0, 12)}; repair development before recutting` + (ciLedgerNote ? `; ${ciLedgerNote}` : "")
|
|
37649
|
+
};
|
|
37650
|
+
}
|
|
37448
37651
|
await deps.run("git", ["checkout", "development"]);
|
|
37449
37652
|
await ffOnlyPull(deps, "development");
|
|
37450
37653
|
await deps.run("git", ["branch", "-f", "main", "origin/main"]);
|
|
@@ -37716,7 +37919,7 @@ ${spent.note}`), rcandRecovery(tag2));
|
|
|
37716
37919
|
const releaseSha3 = requireValue(clean2(await deps.run("git", ["rev-parse", "main"])), "release sha");
|
|
37717
37920
|
return { versionFold: versionFold3, releaseSha: releaseSha3 };
|
|
37718
37921
|
});
|
|
37719
|
-
const { checks: checks2, releaseUrl: releaseUrl2, announceNote: announceNote2, localInstall: localInstall2, dispatch: d2, ledgerAnchors: ledgerAnchors2, alignment: alignment2 } = await completeMainRelease(
|
|
37922
|
+
const { checks: checks2, releaseUrl: releaseUrl2, announceNote: announceNote2, localInstall: localInstall2, dispatch: d2, ledgerAnchors: ledgerAnchors2, pretag: pretag2, alignment: alignment2 } = await completeMainRelease(
|
|
37720
37923
|
deps,
|
|
37721
37924
|
ctx,
|
|
37722
37925
|
meta,
|
|
@@ -37748,6 +37951,7 @@ ${spent.note}`), rcandRecovery(tag2));
|
|
|
37748
37951
|
promoted: true,
|
|
37749
37952
|
checks: checks2,
|
|
37750
37953
|
versionFold: versionFold2,
|
|
37954
|
+
pretag: pretag2,
|
|
37751
37955
|
dispatch: d2.note,
|
|
37752
37956
|
runId: d2.runId,
|
|
37753
37957
|
runUrl: d2.runUrl,
|
|
@@ -37778,6 +37982,7 @@ ${spent.note}`), rcandRecovery(tag2));
|
|
|
37778
37982
|
promoted: true,
|
|
37779
37983
|
checks: checks2,
|
|
37780
37984
|
versionFold: versionFold2,
|
|
37985
|
+
pretag: pretag2,
|
|
37781
37986
|
dispatch: d2.note,
|
|
37782
37987
|
runId: d2.runId,
|
|
37783
37988
|
runUrl: d2.runUrl,
|
|
@@ -37827,7 +38032,7 @@ ${spent.note}`), rcandRecovery(tag2));
|
|
|
37827
38032
|
const releaseSha2 = requireValue(clean2(await deps.run("git", ["rev-parse", "main"])), "release sha");
|
|
37828
38033
|
return { versionFold: versionFold2, releaseSha: releaseSha2 };
|
|
37829
38034
|
});
|
|
37830
|
-
const { checks, releaseUrl, announceNote, localInstall, dispatch: d, ledgerAnchors, alignment } = await completeMainRelease(
|
|
38035
|
+
const { checks, releaseUrl, announceNote, localInstall, dispatch: d, ledgerAnchors, pretag, alignment } = await completeMainRelease(
|
|
37831
38036
|
deps,
|
|
37832
38037
|
ctx,
|
|
37833
38038
|
meta,
|
|
@@ -37861,6 +38066,7 @@ ${spent.note}`), rcandRecovery(tag2));
|
|
|
37861
38066
|
promoted: true,
|
|
37862
38067
|
checks,
|
|
37863
38068
|
versionFold,
|
|
38069
|
+
pretag,
|
|
37864
38070
|
dispatch: d.note,
|
|
37865
38071
|
runId: d.runId,
|
|
37866
38072
|
runUrl: d.runUrl,
|
|
@@ -40786,7 +40992,7 @@ function registerBoardCommands(program3) {
|
|
|
40786
40992
|
return `Claimed ${ref} for ${holder} - In Progress${reclaimed}`;
|
|
40787
40993
|
}
|
|
40788
40994
|
const board = program3.command("board").description("read, claim, show, and move Project v2 work items for the current repo");
|
|
40789
|
-
board.command("read", { isDefault: true }).alias("list").description("read the board and print user-owned, claimable, and taken items").option("--json", "machine-readable output").option("--repo <owner/repo>", "current repo (defaults to git origin)").option("--direct", "bypass the Hub snapshot and read the live board through direct GitHub GraphQL").option("--snapshot-only", "never fall back to your personal GraphQL pool; failure exits: 20 hub-unreachable, 21 app-quota-exhausted, 22 refused. With --json, stdout is {ok:false,outcome,reason[,resetAt,pool]} (pool is rest or graphql when known); success is unchanged").option("--min-cursor <n>", "board-event cursor (from `oracle board events`) the Hub snapshot must postdate; a cached snapshot older than that event is rebuilt (#7240)").option("--bundle-details", "fetch body/comments only for user-owned and claimable issues").option("--bodies", "fetch body/comments for EVERY scoped row, including taken and unowned in-flight ones \u2014 for consumers that scope by Status rather than ownership (#4861); implies --bundle-details and costs one extra read per row").option("--allow-partial", "return partial board results when later page/detail reads fail").option("--out <path>", "write the output to this file as UTF-8 (no BOM) instead of stdout \u2014 the shell-free receipt path (#5802)").addHelpText("after", "\nread is always the authoritative live GitHub Project v2 board (#4926).\n--direct skips the Hub snapshot and uses the existing direct GitHub GraphQL read immediately.\n--allow-partial applies to the paginated path and detail reads.\n\nWhen using Windows PowerShell 5.1, avoid shell redirects: PowerShell 5.1's `> file.json` is Out-File,\nwhich writes UTF-16LE with a BOM, and Node reading it as 'utf8' then fails JSON.parse at position 1\n(#5802). Use --out instead \u2014 the CLI writes the file itself as UTF-8:\n mmi-cli oracle board read --json --out .jerv/tmp/board.json\n").action((o) => runBoardRead(o));
|
|
40995
|
+
board.command("read", { isDefault: true }).alias("list").description("read the board and print user-owned, claimable, and taken items").option("--json", "machine-readable output").option("--repo <owner/repo>", "current repo (defaults to git origin)").option("--direct", "bypass the Hub snapshot and read the live board through direct GitHub GraphQL").option("--snapshot-only", "never fall back to your personal GraphQL pool; failure exits: 20 hub-unreachable, 21 app-quota-exhausted, 22 refused. With --json, stdout is {ok:false,outcome,reason[,resetAt,pool]} (pool is rest or graphql when known) and the human-readable reason goes to stderr; success is unchanged").option("--min-cursor <n>", "board-event cursor (from `oracle board events`) the Hub snapshot must postdate; a cached snapshot older than that event is rebuilt (#7240)").option("--bundle-details", "fetch body/comments only for user-owned and claimable issues").option("--bodies", "fetch body/comments for EVERY scoped row, including taken and unowned in-flight ones \u2014 for consumers that scope by Status rather than ownership (#4861); implies --bundle-details and costs one extra read per row").option("--allow-partial", "return partial board results when later page/detail reads fail").option("--out <path>", "write the output to this file as UTF-8 (no BOM) instead of stdout \u2014 the shell-free receipt path (#5802)").addHelpText("after", "\nread is always the authoritative live GitHub Project v2 board (#4926).\n--direct skips the Hub snapshot and uses the existing direct GitHub GraphQL read immediately.\n--allow-partial applies to the paginated path and detail reads.\n\nWhen using Windows PowerShell 5.1, avoid shell redirects: PowerShell 5.1's `> file.json` is Out-File,\nwhich writes UTF-16LE with a BOM, and Node reading it as 'utf8' then fails JSON.parse at position 1\n(#5802). Use --out instead \u2014 the CLI writes the file itself as UTF-8:\n mmi-cli oracle board read --json --out .jerv/tmp/board.json\n\nUnder --json, parse stdout ONLY and check the exit code first. A failure still prints a\nhuman-readable reason on stderr, so never pipe 2>&1 into a JSON parser (#9151) \u2014 the merged\ntext is not JSON and hides the real board failure.\n").action((o) => runBoardRead(o));
|
|
40790
40996
|
board.command("events").description("content-free board/CI change records after a cursor, from the Hub event feed (no GitHub call) \u2014 for real-time sidebars (#7235)").option("--since <cursor>", "cursor from the previous answer; omit on first read (answers the head cursor with resync:true)").option("--wait <seconds>", "hold up to this long (max 120), polling every 2s, and return on the first event or resync", "0").option("--repo <owner/repo>", "repo whose Hub config to use (defaults to git origin)").option("--json", "machine-readable output (already the default)").addHelpText("after", "\nPrints {cursor, events, resync, more[, hidden]}. Pass the returned cursor as the next --since.\nresync:true means records were missed (retention ~1h, or a gap): re-read the board with\n`mmi-cli oracle board read --snapshot-only --json`.\n").action(async (o) => {
|
|
40791
40997
|
try {
|
|
40792
40998
|
if (o.since !== void 0 && !/^\d{1,15}$/.test(o.since)) throw new Error("--since must be a non-negative integer cursor");
|
|
@@ -41032,7 +41238,7 @@ function registerBoardCommands(program3) {
|
|
|
41032
41238
|
}
|
|
41033
41239
|
|
|
41034
41240
|
// src/bootstrap-commands.ts
|
|
41035
|
-
var
|
|
41241
|
+
var import_node_fs35 = require("node:fs");
|
|
41036
41242
|
var import_node_os19 = require("node:os");
|
|
41037
41243
|
var import_node_path32 = require("node:path");
|
|
41038
41244
|
init_cli_shared();
|
|
@@ -41219,7 +41425,7 @@ init_github_client();
|
|
|
41219
41425
|
init_cli_shared();
|
|
41220
41426
|
|
|
41221
41427
|
// src/port-registry.ts
|
|
41222
|
-
var
|
|
41428
|
+
var import_node_fs32 = require("node:fs");
|
|
41223
41429
|
var import_node_path29 = require("node:path");
|
|
41224
41430
|
|
|
41225
41431
|
// ../infra/port-geometry.mjs
|
|
@@ -41234,8 +41440,8 @@ function nextPortBlock(registry2) {
|
|
|
41234
41440
|
return [base, base + PORT_SPAN];
|
|
41235
41441
|
}
|
|
41236
41442
|
function loadPortRegistry(path2) {
|
|
41237
|
-
if (!(0,
|
|
41238
|
-
const raw = JSON.parse((0,
|
|
41443
|
+
if (!(0, import_node_fs32.existsSync)(path2)) return {};
|
|
41444
|
+
const raw = JSON.parse((0, import_node_fs32.readFileSync)(path2, "utf8"));
|
|
41239
41445
|
const out = {};
|
|
41240
41446
|
for (const [key, value] of Object.entries(raw)) {
|
|
41241
41447
|
if (Array.isArray(value) && value.length === 2 && value.every((n) => typeof n === "number")) {
|
|
@@ -41265,7 +41471,7 @@ function existingPortRange(repo, registry2) {
|
|
|
41265
41471
|
function portRangeInfraAt(root, source) {
|
|
41266
41472
|
const registryPath = (0, import_node_path29.join)(root, "infra", "port-ranges.json");
|
|
41267
41473
|
const ddbScriptPath = (0, import_node_path29.join)(root, "infra", "port-ddb.mjs");
|
|
41268
|
-
if (!(0,
|
|
41474
|
+
if (!(0, import_node_fs32.existsSync)(registryPath) || !(0, import_node_fs32.existsSync)(ddbScriptPath)) return null;
|
|
41269
41475
|
return { root, source, registryPath, ddbScriptPath };
|
|
41270
41476
|
}
|
|
41271
41477
|
function resolvePortRangeInfra(cwd, packageDir) {
|
|
@@ -41347,7 +41553,7 @@ async function assignPersistedPortRange(repo, slug, reg, opts) {
|
|
|
41347
41553
|
}
|
|
41348
41554
|
|
|
41349
41555
|
// src/box-commands.ts
|
|
41350
|
-
var
|
|
41556
|
+
var import_node_fs34 = require("node:fs");
|
|
41351
41557
|
var import_node_child_process10 = require("node:child_process");
|
|
41352
41558
|
var import_node_os18 = require("node:os");
|
|
41353
41559
|
var import_node_path31 = require("node:path");
|
|
@@ -41520,7 +41726,7 @@ init_clean_exit();
|
|
|
41520
41726
|
init_cli_shared();
|
|
41521
41727
|
|
|
41522
41728
|
// src/secrets-commands.ts
|
|
41523
|
-
var
|
|
41729
|
+
var import_node_fs33 = require("node:fs");
|
|
41524
41730
|
var import_node_path30 = require("node:path");
|
|
41525
41731
|
var import_node_os17 = require("node:os");
|
|
41526
41732
|
init_cli_shared();
|
|
@@ -42728,8 +42934,8 @@ async function decryptRailsCredentials(input) {
|
|
|
42728
42934
|
MMI_RAILS_CREDENTIALS_FILE: credentialsPath,
|
|
42729
42935
|
MMI_RAILS_MASTER_KEY_FILE: masterKeyPath
|
|
42730
42936
|
};
|
|
42731
|
-
if ((0,
|
|
42732
|
-
env.RAILS_MASTER_KEY = (0,
|
|
42937
|
+
if ((0, import_node_fs33.existsSync)(masterKeyPath)) {
|
|
42938
|
+
env.RAILS_MASTER_KEY = (0, import_node_fs33.readFileSync)(masterKeyPath, "utf8").trim();
|
|
42733
42939
|
}
|
|
42734
42940
|
const script = [
|
|
42735
42941
|
'require "json"',
|
|
@@ -42739,9 +42945,9 @@ async function decryptRailsCredentials(input) {
|
|
|
42739
42945
|
'config = ActiveSupport::EncryptedConfiguration.new(config_path: config_path, key_path: key_path, env_key: "RAILS_MASTER_KEY", raise_if_missing_key: true)',
|
|
42740
42946
|
"puts JSON.generate(config.config)"
|
|
42741
42947
|
].join("\n");
|
|
42742
|
-
const scriptDir = (0,
|
|
42948
|
+
const scriptDir = (0, import_node_fs33.mkdtempSync)((0, import_node_path30.join)((0, import_node_os17.tmpdir)(), "mmi-rails-decrypt-"));
|
|
42743
42949
|
const scriptPath = (0, import_node_path30.join)(scriptDir, "decrypt.rb");
|
|
42744
|
-
(0,
|
|
42950
|
+
(0, import_node_fs33.writeFileSync)(scriptPath, script, "utf8");
|
|
42745
42951
|
try {
|
|
42746
42952
|
const args = ["exec", "ruby", scriptPath];
|
|
42747
42953
|
const cmd = process.platform === "win32" ? "cmd.exe" : "bundle";
|
|
@@ -42753,7 +42959,7 @@ async function decryptRailsCredentials(input) {
|
|
|
42753
42959
|
});
|
|
42754
42960
|
return JSON.parse(stdout);
|
|
42755
42961
|
} finally {
|
|
42756
|
-
(0,
|
|
42962
|
+
(0, import_node_fs33.rmSync)(scriptDir, { recursive: true, force: true });
|
|
42757
42963
|
}
|
|
42758
42964
|
}
|
|
42759
42965
|
async function readSecretStdin() {
|
|
@@ -42843,7 +43049,7 @@ function registerSecretsCommands(program3) {
|
|
|
42843
43049
|
let body;
|
|
42844
43050
|
if (o.file) {
|
|
42845
43051
|
try {
|
|
42846
|
-
body = (0,
|
|
43052
|
+
body = (0, import_node_fs33.readFileSync)((0, import_node_path30.resolve)(o.file), "utf8");
|
|
42847
43053
|
} catch (e) {
|
|
42848
43054
|
return fail(`secrets org-catalog: cannot read --file ${o.file}: ${e.message}`);
|
|
42849
43055
|
}
|
|
@@ -42979,7 +43185,7 @@ function registerSecretsCommands(program3) {
|
|
|
42979
43185
|
{
|
|
42980
43186
|
...d,
|
|
42981
43187
|
decryptRailsCredentials,
|
|
42982
|
-
removeFile: (path2) => (0,
|
|
43188
|
+
removeFile: (path2) => (0, import_node_fs33.unlinkSync)((0, import_node_path30.resolve)(o.appDir ?? process.cwd(), path2))
|
|
42983
43189
|
},
|
|
42984
43190
|
{
|
|
42985
43191
|
repo: o.repo,
|
|
@@ -43006,7 +43212,7 @@ function registerSecretsCommands(program3) {
|
|
|
43006
43212
|
}
|
|
43007
43213
|
const { keys, command, ...o } = parsed.value;
|
|
43008
43214
|
const code = secretsUseExitCode(await secretsUse(d, keys, { ...o, command }));
|
|
43009
|
-
if (code !== void 0) process.exitCode = code;
|
|
43215
|
+
if (code !== void 0) process.exitCode = lastHubAuthFailure()?.exitCode ?? code;
|
|
43010
43216
|
});
|
|
43011
43217
|
});
|
|
43012
43218
|
withDeclaredOptions(
|
|
@@ -43235,7 +43441,7 @@ function registerBoxCommands(program3) {
|
|
|
43235
43441
|
}
|
|
43236
43442
|
const remote = command && command.length ? command.join(" ") : void 0;
|
|
43237
43443
|
const wroteScript = o.ssh && o.script ? o.script : null;
|
|
43238
|
-
if (wroteScript) (0,
|
|
43444
|
+
if (wroteScript) (0, import_node_fs34.writeFileSync)(wroteScript, sshRecipeScript(found, remote), "utf8");
|
|
43239
43445
|
if (o.json) {
|
|
43240
43446
|
console.log(JSON.stringify({
|
|
43241
43447
|
box: found,
|
|
@@ -43359,17 +43565,17 @@ function hubGitShow(ref) {
|
|
|
43359
43565
|
return (path2) => git3(["show", `${ref}:${path2}`]);
|
|
43360
43566
|
}
|
|
43361
43567
|
function runOnBox(found, script) {
|
|
43362
|
-
const dir = (0,
|
|
43568
|
+
const dir = (0, import_node_fs34.mkdtempSync)((0, import_node_path31.join)((0, import_node_os18.tmpdir)(), "mmi-box-control-"));
|
|
43363
43569
|
try {
|
|
43364
43570
|
const recipe = (0, import_node_path31.join)(dir, "recipe.sh");
|
|
43365
|
-
(0,
|
|
43571
|
+
(0, import_node_fs34.writeFileSync)(recipe, sshRecipeScript(found, "sh -s"), "utf8");
|
|
43366
43572
|
const r = (0, import_node_child_process10.spawnSync)("bash", [recipe], { input: script, encoding: "utf8", maxBuffer: 16 * 1024 * 1024, timeout: 18e4, windowsHide: true });
|
|
43367
43573
|
if (r.status !== 0) {
|
|
43368
43574
|
throw new UnreadableError(`box control: the ssh session to ${found.name} failed (exit ${r.status ?? r.signal}): ${(r.stderr || "").trim().split("\n").slice(-3).join(" | ")}`);
|
|
43369
43575
|
}
|
|
43370
43576
|
return r.stdout;
|
|
43371
43577
|
} finally {
|
|
43372
|
-
(0,
|
|
43578
|
+
(0, import_node_fs34.rmSync)(dir, { recursive: true, force: true });
|
|
43373
43579
|
}
|
|
43374
43580
|
}
|
|
43375
43581
|
function formatControl(mode, box, rows) {
|
|
@@ -45012,12 +45218,12 @@ async function verifyTargetedSeedPr(repo, number, plan, target, expected, client
|
|
|
45012
45218
|
// src/bootstrap-commands.ts
|
|
45013
45219
|
var execGitForSeedSource = async (args) => (await execFileP("git", args, { timeout: GIT_TIMEOUT_MS })).stdout;
|
|
45014
45220
|
async function gateRenderDiff(current, rendered) {
|
|
45015
|
-
const dir = (0,
|
|
45221
|
+
const dir = (0, import_node_fs35.mkdtempSync)((0, import_node_path32.join)((0, import_node_os19.tmpdir)(), "mmi-gate-diff-"));
|
|
45016
45222
|
const before = (0, import_node_path32.join)(dir, "committed-gate.yml");
|
|
45017
45223
|
const after = (0, import_node_path32.join)(dir, "registry-render.yml");
|
|
45018
45224
|
try {
|
|
45019
|
-
(0,
|
|
45020
|
-
(0,
|
|
45225
|
+
(0, import_node_fs35.writeFileSync)(before, current);
|
|
45226
|
+
(0, import_node_fs35.writeFileSync)(after, rendered);
|
|
45021
45227
|
try {
|
|
45022
45228
|
return (await execFileP("git", ["diff", "--no-index", "--", before, after], { timeout: GIT_TIMEOUT_MS })).stdout;
|
|
45023
45229
|
} catch (e) {
|
|
@@ -45026,7 +45232,7 @@ async function gateRenderDiff(current, rendered) {
|
|
|
45026
45232
|
throw e;
|
|
45027
45233
|
}
|
|
45028
45234
|
} finally {
|
|
45029
|
-
(0,
|
|
45235
|
+
(0, import_node_fs35.rmSync)(dir, { recursive: true, force: true });
|
|
45030
45236
|
}
|
|
45031
45237
|
}
|
|
45032
45238
|
function bootstrapTrainDoctorDeps() {
|
|
@@ -45086,13 +45292,13 @@ function registerBootstrapCommands(program3) {
|
|
|
45086
45292
|
})),
|
|
45087
45293
|
pushAllowlistOwners: verifyOwners,
|
|
45088
45294
|
pushAllowlistOwnersRead: verifyOwnersRead,
|
|
45089
|
-
readLocalFile: (path2) => path2 === "projects.json" && apiProjects != null ? apiProjects : (0,
|
|
45295
|
+
readLocalFile: (path2) => path2 === "projects.json" && apiProjects != null ? apiProjects : (0, import_node_fs35.existsSync)(path2) ? (0, import_node_fs35.readFileSync)(path2, "utf8") : null,
|
|
45090
45296
|
// requiredGcpApis is stored as an array by a JSON write, but `org project set --var KEY=VALUE` stores a raw
|
|
45091
45297
|
// comma-string — accept either so the seeded value verifies regardless of how it was written.
|
|
45092
45298
|
// #3689: the same committed map the org access audit reads (#3664), so a sanctioned admin is not a
|
|
45093
45299
|
// permanent bootstrap failure on one surface and an intended state on the other. Absent file → no
|
|
45094
45300
|
// sanction, which is the pre-#3664 behaviour.
|
|
45095
|
-
sanctionedAdmins: (0,
|
|
45301
|
+
sanctionedAdmins: (0, import_node_fs35.existsSync)("access-matrix.json") ? entriesValueByCanonicalRepo(loadSanctionedAdmins((0, import_node_fs35.readFileSync)("access-matrix.json", "utf8")), repo) : void 0,
|
|
45096
45302
|
requiredGcpApis: (() => {
|
|
45097
45303
|
const v = meta?.requiredGcpApis;
|
|
45098
45304
|
if (Array.isArray(v)) return v;
|
|
@@ -45170,14 +45376,14 @@ function registerBootstrapCommands(program3) {
|
|
|
45170
45376
|
bootstrap.command("drift").description("#3818: compare every org-owned whole-file seed against MMI-Hub's copy across the registry roster; read-only").option("--repo <owner/repo>", "audit one repo instead of the roster (never a fleet verdict)").option("--json", "machine-readable output").action(async () => {
|
|
45171
45377
|
const o = { repo: rawValue("--repo", ""), json: rawFlag("--json") };
|
|
45172
45378
|
const manifestPath = "skills/bootstrap/seeds/manifest.json";
|
|
45173
|
-
if (!(0,
|
|
45379
|
+
if (!(0, import_node_fs35.existsSync)(manifestPath)) return fail(`bootstrap drift: ${manifestPath} not found; run from the MMI-Hub repo root \u2014 the Hub's copies ARE the reference this compares against`);
|
|
45174
45380
|
const seedSource = await resolveHubSeedSource(execGitForSeedSource);
|
|
45175
45381
|
if (!seedSource.ok) return fail(`bootstrap drift: ${seedSource.reason}`);
|
|
45176
|
-
const manifest = loadBootstrapSeeds((0,
|
|
45382
|
+
const manifest = loadBootstrapSeeds((0, import_node_fs35.readFileSync)(manifestPath, "utf8"));
|
|
45177
45383
|
const hubContents = /* @__PURE__ */ new Map();
|
|
45178
45384
|
for (const s of manifest.seeds) {
|
|
45179
45385
|
if (s.ownership !== "org" || s.source !== "self") continue;
|
|
45180
|
-
hubContents.set(s.target, (0,
|
|
45386
|
+
hubContents.set(s.target, (0, import_node_fs35.existsSync)(s.target) ? (0, import_node_fs35.readFileSync)(s.target, "utf8") : null);
|
|
45181
45387
|
}
|
|
45182
45388
|
let targets;
|
|
45183
45389
|
let classOf = (_repo) => "deployable";
|
|
@@ -45385,24 +45591,24 @@ function registerBootstrapCommands(program3) {
|
|
|
45385
45591
|
return;
|
|
45386
45592
|
}
|
|
45387
45593
|
const manifestPath = "skills/bootstrap/seeds/manifest.json";
|
|
45388
|
-
if (!(0,
|
|
45594
|
+
if (!(0, import_node_fs35.existsSync)(manifestPath)) return fail(`bootstrap apply: ${manifestPath} not found; bootstrap runs from the MMI-Hub repo root by design \u2014 it stamps org-level resources (Project, Ruleset, secrets, access) through the GitHub App, which is only authorized from the Hub checkout`);
|
|
45389
45595
|
const seedSource = await resolveHubSeedSource(execGitForSeedSource);
|
|
45390
45596
|
if (!seedSource.ok) return fail(`bootstrap apply: ${seedSource.reason}`);
|
|
45391
|
-
const manifest = loadBootstrapSeeds((0,
|
|
45597
|
+
const manifest = loadBootstrapSeeds((0, import_node_fs35.readFileSync)(manifestPath, "utf8"));
|
|
45392
45598
|
const baseBranch = o.class === "content" ? "main" : "development";
|
|
45393
45599
|
const slug = parsedRepo.slug;
|
|
45394
45600
|
const onlyTarget = o.fromConfig && o.only.trim() === "gate.yml" ? ".github/workflows/gate.yml" : o.only.trim();
|
|
45395
45601
|
const gh2 = async (args) => execFileP("gh", args, { timeout: 2e4 });
|
|
45396
|
-
const readFile10 = (p) => (0,
|
|
45602
|
+
const readFile10 = (p) => (0, import_node_fs35.existsSync)(p) ? (0, import_node_fs35.readFileSync)(p, "utf8") : null;
|
|
45397
45603
|
const enc = (p) => p.split("/").map(encodeURIComponent).join("/");
|
|
45398
45604
|
const putSeed = async (target, content, ref, sha) => {
|
|
45399
45605
|
const tmp = (0, import_node_path32.join)((0, import_node_os19.tmpdir)(), `mmi-seed-${process.pid}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}.json`);
|
|
45400
|
-
(0,
|
|
45606
|
+
(0, import_node_fs35.writeFileSync)(tmp, JSON.stringify(contentPutBody(target, content, ref, sha)), "utf8");
|
|
45401
45607
|
try {
|
|
45402
45608
|
await gh2(contentPutInputArgs(repo, target, tmp));
|
|
45403
45609
|
} finally {
|
|
45404
45610
|
try {
|
|
45405
|
-
(0,
|
|
45611
|
+
(0, import_node_fs35.unlinkSync)(tmp);
|
|
45406
45612
|
} catch {
|
|
45407
45613
|
}
|
|
45408
45614
|
}
|
|
@@ -46038,10 +46244,10 @@ LIVE apply to ${repo}:
|
|
|
46038
46244
|
bootstrap.command("propagate").description("#4238: re-entrant canary\u2192wave tick \u2014 plan (or, with --execute, open) per-repo PRs fanning an org-owned seed out to the fleet").option("--target <path>", "the manifest target to propagate (an org-owned whole file or declared Hub-managed block)").option("--execute", "LIVE tick via gh (master-gated) \u2014 opens/reuses per-repo seed-propagate PRs; dry-run prints the plan only").option("--json", "machine-readable output").action(async () => {
|
|
46039
46245
|
const o = { target: rawValue("--target", ""), execute: rawFlag("--execute"), json: rawFlag("--json") };
|
|
46040
46246
|
const manifestPath = "skills/bootstrap/seeds/manifest.json";
|
|
46041
|
-
if (!(0,
|
|
46247
|
+
if (!(0, import_node_fs35.existsSync)(manifestPath)) return fail(`bootstrap propagate: ${manifestPath} not found; run from the MMI-Hub repo root \u2014 the Hub's copies ARE the desired state this tick propagates`);
|
|
46042
46248
|
const seedSource = await resolveHubSeedSource(execGitForSeedSource);
|
|
46043
46249
|
if (!seedSource.ok) return fail(`bootstrap propagate: ${seedSource.reason}`);
|
|
46044
|
-
const manifest = loadBootstrapSeeds((0,
|
|
46250
|
+
const manifest = loadBootstrapSeeds((0, import_node_fs35.readFileSync)(manifestPath, "utf8"));
|
|
46045
46251
|
const propagatable = manifest.seeds.filter(isPropagatableSeed);
|
|
46046
46252
|
if (!o.target) {
|
|
46047
46253
|
return fail(`bootstrap propagate: --target <path> is required \u2014 one of:
|
|
@@ -46050,9 +46256,9 @@ LIVE apply to ${repo}:
|
|
|
46050
46256
|
const seed = propagatable.find((s) => s.target === o.target);
|
|
46051
46257
|
if (!seed) return fail(`bootstrap propagate: --target '${o.target}' names no centrally propagatable seed in ${manifestPath}. Propagatable targets:
|
|
46052
46258
|
${propagatable.map((s) => s.target).join("\n ")}`);
|
|
46053
|
-
if (!seed.managedBlock && !(0,
|
|
46054
|
-
const hubContent = seed.managedBlock ? null : (0,
|
|
46055
|
-
const readSeedFile2 = (path2) => (0,
|
|
46259
|
+
if (!seed.managedBlock && !(0, import_node_fs35.existsSync)(seed.target)) return fail(`bootstrap propagate: the Hub's own copy of '${seed.target}' is missing \u2014 nothing to propagate`);
|
|
46260
|
+
const hubContent = seed.managedBlock ? null : (0, import_node_fs35.readFileSync)(seed.target, "utf8");
|
|
46261
|
+
const readSeedFile2 = (path2) => (0, import_node_fs35.existsSync)(path2) ? (0, import_node_fs35.readFileSync)(path2, "utf8") : null;
|
|
46056
46262
|
const isWorkflowSeed = seed.target.startsWith(".github/workflows/");
|
|
46057
46263
|
const cfg = await loadConfig();
|
|
46058
46264
|
const projects = await fetchProjectsList(registryClientDeps(cfg));
|
|
@@ -46061,9 +46267,9 @@ LIVE apply to ${repo}:
|
|
|
46061
46267
|
}
|
|
46062
46268
|
const rosterRepos = collectRegistryRepos(projects).filter((r) => r.toLowerCase() !== "mutmutco/mmi-hub");
|
|
46063
46269
|
let independentCount = rosterRepos.length;
|
|
46064
|
-
if ((0,
|
|
46270
|
+
if ((0, import_node_fs35.existsSync)("projects.json")) {
|
|
46065
46271
|
try {
|
|
46066
|
-
const local = JSON.parse((0,
|
|
46272
|
+
const local = JSON.parse((0, import_node_fs35.readFileSync)("projects.json", "utf8"));
|
|
46067
46273
|
const localRepos = /* @__PURE__ */ new Set();
|
|
46068
46274
|
for (const p of local.projects ?? []) for (const r of p.repos ?? []) {
|
|
46069
46275
|
const full = (r.includes("/") ? r : `mutmutco/${r}`).toLowerCase();
|
|
@@ -46219,7 +46425,7 @@ LIVE apply to ${repo}:
|
|
|
46219
46425
|
if (plan.refusedNoCanary) return fail("bootstrap propagate --execute: no canary declared for this target \u2014 refusing to write (set seedCanary:true on exactly one registry repo)");
|
|
46220
46426
|
const headSha = seedSource.sha;
|
|
46221
46427
|
const sourceBlob = (await execGitForSeedSource(["rev-parse", `${headSha}:${seedSourcePath(seed)}`])).trim();
|
|
46222
|
-
const runbookUnaffected = (0,
|
|
46428
|
+
const runbookUnaffected = (0, import_node_fs35.existsSync)(RUNBOOK_UNAFFECTED_PATH) ? runbookUnaffectedReason(JSON.parse((0, import_node_fs35.readFileSync)(RUNBOOK_UNAFFECTED_PATH, "utf8")), seed.target, sourceBlob) : void 0;
|
|
46223
46429
|
for (const rec of plan.records) {
|
|
46224
46430
|
if (rec.action !== "open-pr") continue;
|
|
46225
46431
|
const baseBranch = classOf(rec.repo) === "content" ? "main" : "development";
|
|
@@ -46244,12 +46450,12 @@ LIVE apply to ${repo}:
|
|
|
46244
46450
|
const tmp = (0, import_node_path32.join)((0, import_node_os19.tmpdir)(), `mmi-propagate-${process.pid}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}.json`);
|
|
46245
46451
|
const desiredContent = desiredByRepo.get(rec.repo);
|
|
46246
46452
|
if (desiredContent == null) return fail(`bootstrap propagate: no resolved content for ${rec.repo} ${seed.target} \u2014 refusing to write`);
|
|
46247
|
-
(0,
|
|
46453
|
+
(0, import_node_fs35.writeFileSync)(tmp, JSON.stringify(contentPutBody(seed.target, desiredContent, branch, existingSha, runbookUnaffected)), "utf8");
|
|
46248
46454
|
try {
|
|
46249
46455
|
await gh2(contentPutInputArgs(rec.repo, seed.target, tmp));
|
|
46250
46456
|
} finally {
|
|
46251
46457
|
try {
|
|
46252
|
-
(0,
|
|
46458
|
+
(0, import_node_fs35.unlinkSync)(tmp);
|
|
46253
46459
|
} catch {
|
|
46254
46460
|
}
|
|
46255
46461
|
}
|
|
@@ -46318,10 +46524,10 @@ Rollback: \`mmi-cli devops bootstrap rollback ${rec.repo} --target ${seed.target
|
|
|
46318
46524
|
return fail(`bootstrap rollback: ${e.message}`);
|
|
46319
46525
|
}
|
|
46320
46526
|
const manifestPath = "skills/bootstrap/seeds/manifest.json";
|
|
46321
|
-
if (!(0,
|
|
46527
|
+
if (!(0, import_node_fs35.existsSync)(manifestPath)) return fail(`bootstrap rollback: ${manifestPath} not found; run from the MMI-Hub repo root \u2014 the manifest names which targets are org-owned and therefore propagated (and rollback-able)`);
|
|
46322
46528
|
const seedSource = await resolveHubSeedSource(execGitForSeedSource);
|
|
46323
46529
|
if (!seedSource.ok) return fail(`bootstrap rollback: ${seedSource.reason}`);
|
|
46324
|
-
const manifest = loadBootstrapSeeds((0,
|
|
46530
|
+
const manifest = loadBootstrapSeeds((0, import_node_fs35.readFileSync)(manifestPath, "utf8"));
|
|
46325
46531
|
const propagatable = manifest.seeds.filter(isPropagatableSeed);
|
|
46326
46532
|
if (!o.target) {
|
|
46327
46533
|
return fail(`bootstrap rollback: --target <path> is required \u2014 one of:
|
|
@@ -46338,10 +46544,10 @@ Rollback: \`mmi-cli devops bootstrap rollback ${rec.repo} --target ${seed.target
|
|
|
46338
46544
|
const enc = (p) => p.split("/").map(encodeURIComponent).join("/");
|
|
46339
46545
|
let candidates;
|
|
46340
46546
|
if (o.record) {
|
|
46341
|
-
if (!(0,
|
|
46547
|
+
if (!(0, import_node_fs35.existsSync)(o.record)) return fail(`bootstrap rollback: --record '${o.record}' not found`);
|
|
46342
46548
|
let parsed;
|
|
46343
46549
|
try {
|
|
46344
|
-
parsed = JSON.parse((0,
|
|
46550
|
+
parsed = JSON.parse((0, import_node_fs35.readFileSync)(o.record, "utf8"));
|
|
46345
46551
|
} catch (e) {
|
|
46346
46552
|
return fail(`bootstrap rollback: --record '${o.record}' is not valid JSON: ${e.message}`);
|
|
46347
46553
|
}
|
|
@@ -46419,12 +46625,12 @@ Rollback: \`mmi-cli devops bootstrap rollback ${rec.repo} --target ${seed.target
|
|
|
46419
46625
|
existingSha = void 0;
|
|
46420
46626
|
}
|
|
46421
46627
|
const tmp = (0, import_node_path32.join)((0, import_node_os19.tmpdir)(), `mmi-rollback-${process.pid}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}.json`);
|
|
46422
|
-
(0,
|
|
46628
|
+
(0, import_node_fs35.writeFileSync)(tmp, JSON.stringify(contentPutBody(seed.target, preSeedContent, plan.branch, existingSha)), "utf8");
|
|
46423
46629
|
try {
|
|
46424
46630
|
await gh2(contentPutInputArgs(repo, seed.target, tmp));
|
|
46425
46631
|
} finally {
|
|
46426
46632
|
try {
|
|
46427
|
-
(0,
|
|
46633
|
+
(0, import_node_fs35.unlinkSync)(tmp);
|
|
46428
46634
|
} catch {
|
|
46429
46635
|
}
|
|
46430
46636
|
}
|
|
@@ -46896,7 +47102,7 @@ async function waitSuiteDev(deps, opts) {
|
|
|
46896
47102
|
}
|
|
46897
47103
|
|
|
46898
47104
|
// src/suite-train.ts
|
|
46899
|
-
var
|
|
47105
|
+
var import_node_fs36 = require("node:fs");
|
|
46900
47106
|
var import_node_path33 = require("node:path");
|
|
46901
47107
|
var import_node_crypto15 = require("node:crypto");
|
|
46902
47108
|
|
|
@@ -46983,7 +47189,7 @@ function suiteReleaseTag(rc) {
|
|
|
46983
47189
|
function readSuiteCut(path2) {
|
|
46984
47190
|
let raw;
|
|
46985
47191
|
try {
|
|
46986
|
-
raw = (0,
|
|
47192
|
+
raw = (0, import_node_fs36.readFileSync)(path2, "utf8");
|
|
46987
47193
|
} catch (e) {
|
|
46988
47194
|
if (e.code === "ENOENT") return void 0;
|
|
46989
47195
|
throw e;
|
|
@@ -46999,11 +47205,11 @@ function readSuiteCut(path2) {
|
|
|
46999
47205
|
return record2;
|
|
47000
47206
|
}
|
|
47001
47207
|
function writeSuiteCut(path2, record2) {
|
|
47002
|
-
(0,
|
|
47208
|
+
(0, import_node_fs36.mkdirSync)((0, import_node_path33.dirname)(path2), { recursive: true });
|
|
47003
47209
|
const temp = `${path2}.${(0, import_node_crypto15.randomBytes)(8).toString("hex")}.tmp`;
|
|
47004
47210
|
try {
|
|
47005
|
-
(0,
|
|
47006
|
-
(0,
|
|
47211
|
+
(0, import_node_fs36.writeFileSync)(temp, JSON.stringify(record2, null, 2), { flag: "wx" });
|
|
47212
|
+
(0, import_node_fs36.renameSync)(temp, path2);
|
|
47007
47213
|
} catch (e) {
|
|
47008
47214
|
throw new Error(`suite cut record write failed at ${path2}: ${e.message}; stop, verify the file before retrying`);
|
|
47009
47215
|
}
|
|
@@ -47052,25 +47258,25 @@ async function suiteMarkAbandoned(deps, ownerGo = false) {
|
|
|
47052
47258
|
return tags.map((t) => `${t.repo}@${t.tag}`);
|
|
47053
47259
|
}
|
|
47054
47260
|
async function retireFailedLeadRcLedger(deps, ownerGo) {
|
|
47055
|
-
if (!(0,
|
|
47261
|
+
if (!(0, import_node_fs36.existsSync)(deps.ledgerPath)) return void 0;
|
|
47056
47262
|
return withLedgerLock(deps.ledgerPath, async (fence) => {
|
|
47057
|
-
const ledger = parseReleaseLedger((0,
|
|
47263
|
+
const ledger = parseReleaseLedger((0, import_node_fs36.readFileSync)(deps.ledgerPath, "utf8"));
|
|
47058
47264
|
const states = Object.values(ledger.phases).map((p) => p.state);
|
|
47059
47265
|
if (ledger.repo !== SUITE_REPO || ledger.lane !== "rcand" || ledger.phases.promotion.state === "complete" || !states.includes("failed")) return void 0;
|
|
47060
47266
|
if (!ownerGo) throw new Error(`the lead's failed rcand ledger for ${ledger.tag} blocks every train; retire it with suite abandon --owner-go`);
|
|
47061
47267
|
if ((await deps.rcHead())?.toLowerCase() === ledger.tagSha.toLowerCase()) throw new Error(`origin rc already points at ${ledger.tag}; it was promoted, so its ledger is not retired`);
|
|
47062
47268
|
const archive = (0, import_node_path33.join)((0, import_node_path33.dirname)(deps.ledgerPath), `phases.archive-${ledger.tag}.json`);
|
|
47063
|
-
if ((0,
|
|
47269
|
+
if ((0, import_node_fs36.existsSync)(archive)) throw new Error(`ledger archive already exists at ${archive}; inspect before retiring again`);
|
|
47064
47270
|
await deps.markAbandoned(SUITE_REPO, ledger.tag, ledger.tagSha);
|
|
47065
|
-
await fence(() => (0,
|
|
47271
|
+
await fence(() => (0, import_node_fs36.renameSync)(deps.ledgerPath, archive));
|
|
47066
47272
|
return `${ledger.tag} (ledger archived to ${archive})`;
|
|
47067
47273
|
});
|
|
47068
47274
|
}
|
|
47069
47275
|
function suiteAbandon(recordPath, ownerGo = false, now = /* @__PURE__ */ new Date()) {
|
|
47070
47276
|
const record2 = abandonableCut(recordPath, ownerGo);
|
|
47071
47277
|
const archive = `${recordPath}.abandoned-${record2.leadDevelopment.slice(0, 12)}-${now.toISOString().replace(/[:.]/g, "")}.json`;
|
|
47072
|
-
if ((0,
|
|
47073
|
-
(0,
|
|
47278
|
+
if ((0, import_node_fs36.existsSync)(archive)) throw new Error(`abandoned-cut archive already exists at ${archive}; inspect before abandoning again`);
|
|
47279
|
+
(0, import_node_fs36.renameSync)(recordPath, archive);
|
|
47074
47280
|
return { archive, orphanTags: record2.members.filter((m) => m.tag).map((m) => `${m.repo}@${m.tag}`) };
|
|
47075
47281
|
}
|
|
47076
47282
|
async function suiteRestoreMainPins(deps) {
|
|
@@ -47157,12 +47363,12 @@ async function suiteRcandRun(deps, opts) {
|
|
|
47157
47363
|
}
|
|
47158
47364
|
if ((record2?.announcement === "sent" || record2?.announcement === "skipped") && record2.leadReleased) {
|
|
47159
47365
|
const archive = `${deps.recordPath}.${record2.leadRcTag}.json`;
|
|
47160
|
-
if ((0,
|
|
47161
|
-
(0,
|
|
47366
|
+
if ((0, import_node_fs36.existsSync)(archive)) throw new Error(`suite cut archive already exists at ${archive}; inspect before a new cut`);
|
|
47367
|
+
(0, import_node_fs36.renameSync)(deps.recordPath, archive);
|
|
47162
47368
|
record2 = void 0;
|
|
47163
47369
|
}
|
|
47164
47370
|
if (record2 && !record2.pinPr && !record2.leadRcTag && record2.members.every((m) => !m.tag)) {
|
|
47165
|
-
(0,
|
|
47371
|
+
(0, import_node_fs36.rmSync)(deps.recordPath, { force: true });
|
|
47166
47372
|
record2 = void 0;
|
|
47167
47373
|
}
|
|
47168
47374
|
if (!record2) {
|
|
@@ -47216,7 +47422,7 @@ async function suiteRcandRun(deps, opts) {
|
|
|
47216
47422
|
quiet.push(member);
|
|
47217
47423
|
}
|
|
47218
47424
|
if (record2.members.every((m) => m.carried || quiet.includes(m)) && await deps.contains(SUITE_REPO, record2.leadDevelopment, record2.leadPreviousRc)) {
|
|
47219
|
-
(0,
|
|
47425
|
+
(0, import_node_fs36.rmSync)(deps.recordPath, { force: true });
|
|
47220
47426
|
throw new Error("nothing to promote: neither MM-Work nor any module has new development commits since its rc; no suite cut started");
|
|
47221
47427
|
}
|
|
47222
47428
|
for (const member of quiet) {
|
|
@@ -47234,7 +47440,7 @@ async function suiteRcandRun(deps, opts) {
|
|
|
47234
47440
|
}
|
|
47235
47441
|
if (refusals.length) {
|
|
47236
47442
|
const untouched = record2.members.every((m) => !m.tag) && !record2.pinPr;
|
|
47237
|
-
if (untouched) (0,
|
|
47443
|
+
if (untouched) (0, import_node_fs36.rmSync)(deps.recordPath, { force: true });
|
|
47238
47444
|
throw new Error(`module rc serving is not ready; ${untouched ? "no suite cut started" : "no new tag was cut"}:
|
|
47239
47445
|
${refusals.join("\n")}
|
|
47240
47446
|
Each module must ship a live release that bind-mounts <deployPath>/data read-only and serves <data>/rc/module.js at /rc/module.js (docs/Guides/suite-trains.md).`);
|
|
@@ -47430,7 +47636,7 @@ var hotfixRecordPath = (deps) => (0, import_node_path33.join)((0, import_node_pa
|
|
|
47430
47636
|
function readSuiteHotfix(path2) {
|
|
47431
47637
|
let raw;
|
|
47432
47638
|
try {
|
|
47433
|
-
raw = (0,
|
|
47639
|
+
raw = (0, import_node_fs36.readFileSync)(path2, "utf8");
|
|
47434
47640
|
} catch (e) {
|
|
47435
47641
|
if (e.code === "ENOENT") return void 0;
|
|
47436
47642
|
throw e;
|
|
@@ -47473,7 +47679,7 @@ async function suiteHotfix(deps, opts) {
|
|
|
47473
47679
|
if (completed) record2 = void 0;
|
|
47474
47680
|
if (record2 && record2.repo !== found.repo) throw new Error(`an unfinished suite hotfix for ${record2.repo} is recorded; finish it (rerun with that member) first`);
|
|
47475
47681
|
if (!record2?.tag) await deps.validateHotfixSummary();
|
|
47476
|
-
if (completed) (0,
|
|
47682
|
+
if (completed) (0, import_node_fs36.renameSync)(path2, `${path2}.${completed.leadTag}.json`);
|
|
47477
47683
|
const rec = record2 ?? { schema: "mmi-suite-hotfix/v1", member: found.id, repo: found.repo, from: opts.from, ...opts.ack?.length ? { ack: opts.ack } : {} };
|
|
47478
47684
|
const save = () => writeSuiteHotfix(path2, rec);
|
|
47479
47685
|
save();
|
|
@@ -47742,7 +47948,7 @@ async function runRcDataSwitch(deps, opts) {
|
|
|
47742
47948
|
// src/suite-train-io.ts
|
|
47743
47949
|
var import_node_child_process18 = require("node:child_process");
|
|
47744
47950
|
var import_node_path46 = require("node:path");
|
|
47745
|
-
var
|
|
47951
|
+
var import_node_fs49 = require("node:fs");
|
|
47746
47952
|
var import_node_path47 = require("node:path");
|
|
47747
47953
|
init_cli_shared();
|
|
47748
47954
|
|
|
@@ -48828,6 +49034,15 @@ async function runHotfixReleaseLocked(deps, versionInput, options, doctor) {
|
|
|
48828
49034
|
const invented = announceSummaryFidelity(lines2, rangeLog);
|
|
48829
49035
|
if (invented) throw new Error(invented);
|
|
48830
49036
|
}
|
|
49037
|
+
try {
|
|
49038
|
+
await runPreTagHook(deps, { releaseSha: mergedSha });
|
|
49039
|
+
} catch (e) {
|
|
49040
|
+
throw new Error(
|
|
49041
|
+
`${e instanceof Error ? e.message : String(e)}
|
|
49042
|
+
|
|
49043
|
+
hotfix: origin/main already carries ${mergedSha.slice(0, 12)} (the hotfix PR merged), but nothing was tagged, no Release was created and nothing was published \u2014 fix the check above and rerun \`mmi-cli devops hotfix release ${tag}\`.`
|
|
49044
|
+
);
|
|
49045
|
+
}
|
|
48831
49046
|
const anchors = { repo: ctx.repo, tag, tagSha: mergedSha };
|
|
48832
49047
|
const start = await startReleaseLedger(ledger, anchors, { lane: "hotfix" });
|
|
48833
49048
|
if (!start.ok) {
|
|
@@ -49426,7 +49641,7 @@ function checkHotfixCarries(options) {
|
|
|
49426
49641
|
}
|
|
49427
49642
|
|
|
49428
49643
|
// src/merge-cleanup.ts
|
|
49429
|
-
var
|
|
49644
|
+
var import_node_fs38 = require("node:fs");
|
|
49430
49645
|
var import_node_path35 = require("node:path");
|
|
49431
49646
|
var import_node_os20 = require("node:os");
|
|
49432
49647
|
init_cli_shared();
|
|
@@ -49915,7 +50130,7 @@ function renderPrLandTable(entries) {
|
|
|
49915
50130
|
|
|
49916
50131
|
// src/test-policy-core.ts
|
|
49917
50132
|
var import_node_child_process13 = require("node:child_process");
|
|
49918
|
-
var
|
|
50133
|
+
var import_node_fs37 = require("node:fs");
|
|
49919
50134
|
var import_node_path34 = require("node:path");
|
|
49920
50135
|
|
|
49921
50136
|
// src/test-command-policy-shared.mjs
|
|
@@ -50485,7 +50700,7 @@ function loadPolicySource(root, explicitRef, diffBase) {
|
|
|
50485
50700
|
}
|
|
50486
50701
|
function readFileOrNull2(path2) {
|
|
50487
50702
|
try {
|
|
50488
|
-
return (0,
|
|
50703
|
+
return (0, import_node_fs37.readFileSync)(path2, "utf8");
|
|
50489
50704
|
} catch {
|
|
50490
50705
|
return null;
|
|
50491
50706
|
}
|
|
@@ -50513,10 +50728,10 @@ function classify(changed, policy, present = () => false) {
|
|
|
50513
50728
|
const removedProtected = [...removed].filter((p) => protectedBy.has(p)).map((p) => ({ path: p, why: protectedBy.get(p) ?? "" }));
|
|
50514
50729
|
return { mandatoryHits, untestedHits, testChanges, meaningfulTestChanges, addedTests, removedProtected };
|
|
50515
50730
|
}
|
|
50516
|
-
function unresolvedProtectedEntries(policy, root, exists = (path2) => (0,
|
|
50731
|
+
function unresolvedProtectedEntries(policy, root, exists = (path2) => (0, import_node_fs37.existsSync)(path2)) {
|
|
50517
50732
|
return (policy.protected ?? []).map((p) => p.path).filter((p) => !exists((0, import_node_path34.join)(root, p)));
|
|
50518
50733
|
}
|
|
50519
|
-
function unresolvedSatisfiers(policy, root, exists = (path2) => (0,
|
|
50734
|
+
function unresolvedSatisfiers(policy, root, exists = (path2) => (0, import_node_fs37.existsSync)(path2)) {
|
|
50520
50735
|
const declared = (policy.mandatory ?? []).flatMap((m) => m.satisfiedBy ?? []);
|
|
50521
50736
|
return [...new Set(declared)].filter((p) => !exists((0, import_node_path34.join)(root, p)));
|
|
50522
50737
|
}
|
|
@@ -50657,7 +50872,7 @@ function runTestPolicy(root, deps = {}) {
|
|
|
50657
50872
|
}
|
|
50658
50873
|
const loaded = deps.policy ? { policy: deps.policy, source: { ref: null, sha: null, path: POLICY_FILE } } : loadPolicySource(root, deps.policyRef, deps.base);
|
|
50659
50874
|
const { policy, source: policySource } = loaded;
|
|
50660
|
-
const exists = deps.exists ?? ((path2) => (0,
|
|
50875
|
+
const exists = deps.exists ?? ((path2) => (0, import_node_fs37.existsSync)(path2));
|
|
50661
50876
|
const counts = { mandatoryCount: (policy.mandatory ?? []).length, protectedCount: (policy.protected ?? []).length };
|
|
50662
50877
|
const base = deps.changed ? "(injected)" : deps.policyRef ? resolveHotfixBase(root) : resolveBase(root, deps.base);
|
|
50663
50878
|
const refusal = deps.changed ? null : untrustworthyRange(root, base);
|
|
@@ -50954,13 +51169,13 @@ function resolveSquashMergeBodyText(commits, allowedClosing, cwd) {
|
|
|
50954
51169
|
return rewritten === base ? null : rewritten;
|
|
50955
51170
|
}
|
|
50956
51171
|
function writeSquashBodyFile(body) {
|
|
50957
|
-
const dir = (0,
|
|
51172
|
+
const dir = (0, import_node_fs38.mkdtempSync)((0, import_node_path35.join)((0, import_node_os20.tmpdir)(), "mmi-squash-body-"));
|
|
50958
51173
|
const path2 = (0, import_node_path35.join)(dir, "body.txt");
|
|
50959
|
-
(0,
|
|
51174
|
+
(0, import_node_fs38.writeFileSync)(path2, body.endsWith("\n") ? body : `${body}
|
|
50960
51175
|
`, "utf8");
|
|
50961
51176
|
return { path: path2, cleanup: () => {
|
|
50962
51177
|
try {
|
|
50963
|
-
(0,
|
|
51178
|
+
(0, import_node_fs38.rmSync)(dir, { recursive: true, force: true });
|
|
50964
51179
|
} catch {
|
|
50965
51180
|
}
|
|
50966
51181
|
} };
|
|
@@ -51246,8 +51461,63 @@ function prMergeQueueLine(result) {
|
|
|
51246
51461
|
// src/command-register-train-operations.ts
|
|
51247
51462
|
init_github_client();
|
|
51248
51463
|
|
|
51464
|
+
// src/hub-release-ci-authority.ts
|
|
51465
|
+
var HUB_RELEASE_REPO = "mutmutco/MMI-Hub";
|
|
51466
|
+
var HUB_RELEASE_WORKFLOW = ".github/workflows/hub-release.yml";
|
|
51467
|
+
var HUB_RELEASE_REF = "refs/heads/development";
|
|
51468
|
+
var WORKFLOW_REF = `${HUB_RELEASE_REPO}/${HUB_RELEASE_WORKFLOW}@${HUB_RELEASE_REF}`;
|
|
51469
|
+
var LOGIN = /^[A-Za-z0-9-]{1,39}$/;
|
|
51470
|
+
var ID = /^[1-9][0-9]*$/;
|
|
51471
|
+
function hubReleaseCiRun(env) {
|
|
51472
|
+
if (env.MMI_HUB_RELEASE_CI !== "1" || env.GITHUB_ACTIONS !== "true") return null;
|
|
51473
|
+
if (env.GITHUB_REPOSITORY !== HUB_RELEASE_REPO || env.GITHUB_EVENT_NAME !== "workflow_dispatch") return null;
|
|
51474
|
+
if (env.GITHUB_WORKFLOW_REF !== WORKFLOW_REF || env.GITHUB_REF !== HUB_RELEASE_REF) return null;
|
|
51475
|
+
const runId = env.GITHUB_RUN_ID ?? "";
|
|
51476
|
+
const runAttempt = env.GITHUB_RUN_ATTEMPT ?? "";
|
|
51477
|
+
if (!ID.test(runId) || !ID.test(runAttempt)) return null;
|
|
51478
|
+
return { runId: Number(runId), runAttempt: Number(runAttempt) };
|
|
51479
|
+
}
|
|
51480
|
+
async function hubReleaseCiAuthority(repo, deps) {
|
|
51481
|
+
const run = hubReleaseCiRun(deps.env);
|
|
51482
|
+
if (!run) return null;
|
|
51483
|
+
const refuse = (check) => ({ ok: false, error: `hub release workflow authority refused: ${check}` });
|
|
51484
|
+
if (repo.toLowerCase() !== HUB_RELEASE_REPO.toLowerCase()) return refuse(`this workflow releases ${HUB_RELEASE_REPO} only`);
|
|
51485
|
+
let live;
|
|
51486
|
+
try {
|
|
51487
|
+
live = await deps.getJson(`repos/${HUB_RELEASE_REPO}/actions/runs/${run.runId}`);
|
|
51488
|
+
} catch (e) {
|
|
51489
|
+
return refuse(`live run read failed (${e instanceof Error ? e.message : String(e)})`);
|
|
51490
|
+
}
|
|
51491
|
+
const login = live?.triggering_actor?.login ?? "";
|
|
51492
|
+
const bound = live?.id === run.runId && live.run_attempt === run.runAttempt && live.status === "in_progress" && live.event === "workflow_dispatch" && live.path === HUB_RELEASE_WORKFLOW && live.head_branch === "development" && live.repository?.full_name === HUB_RELEASE_REPO;
|
|
51493
|
+
if (!bound) return refuse("live run binding");
|
|
51494
|
+
if (live.triggering_actor?.type !== "User" || !LOGIN.test(login)) return refuse("the run was not triggered by a person");
|
|
51495
|
+
let membership;
|
|
51496
|
+
try {
|
|
51497
|
+
membership = await deps.getJson(`orgs/mutmutco/memberships/${login}`);
|
|
51498
|
+
} catch (e) {
|
|
51499
|
+
return refuse(`org membership read for ${login} failed (${e instanceof Error ? e.message : String(e)})`);
|
|
51500
|
+
}
|
|
51501
|
+
if (membership?.role !== "admin" || membership?.state !== "active") return refuse(`${login} is not an active org owner`);
|
|
51502
|
+
return { ok: true, role: "master", train: true, authenticatedDelegation: true, delegatedActor: login };
|
|
51503
|
+
}
|
|
51504
|
+
var GITHUB_API = "https://api.github.com";
|
|
51505
|
+
function pinnedGitHubGetJson(token, fetchImpl = fetch) {
|
|
51506
|
+
return async (path2) => {
|
|
51507
|
+
if (!token) throw new Error("GH_TOKEN is not set in this run");
|
|
51508
|
+
if (!/^[A-Za-z0-9_.\/-]+$/.test(path2) || path2.includes("..")) throw new Error("refusing an unexpected API path");
|
|
51509
|
+
const res = await fetchImpl(`${GITHUB_API}/${path2}`, {
|
|
51510
|
+
headers: { Authorization: `Bearer ${token}`, Accept: "application/vnd.github+json", "X-GitHub-Api-Version": "2022-11-28" },
|
|
51511
|
+
signal: AbortSignal.timeout(15e3)
|
|
51512
|
+
});
|
|
51513
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
51514
|
+
return res.json();
|
|
51515
|
+
};
|
|
51516
|
+
}
|
|
51517
|
+
var HUB_RELEASE_CI_ANNOUNCE_DEFERRED = "announcement deferred: the Hub cannot authorize a release-workflow session to post yet (#9138); the curated summary is in this run's step summary";
|
|
51518
|
+
|
|
51249
51519
|
// src/project-info-sync.ts
|
|
51250
|
-
var
|
|
51520
|
+
var import_node_fs39 = require("node:fs");
|
|
51251
51521
|
var import_node_path36 = require("node:path");
|
|
51252
51522
|
var UPDATE_PROJECT_INFO = `mutation($projectId: ID!, $shortDescription: String!, $readme: String!) {
|
|
51253
51523
|
updateProjectV2(input: { projectId: $projectId, shortDescription: $shortDescription, readme: $readme }) {
|
|
@@ -51296,13 +51566,13 @@ function sharedName(entries, fallback) {
|
|
|
51296
51566
|
function buildProjectInfoSyncPlan(targetRepo2, project2, projects, repoRoot2) {
|
|
51297
51567
|
if (!project2.projectId) throw new Error(`org project sync-info: ${targetRepo2} registry META has no projectId`);
|
|
51298
51568
|
const readmePath = (0, import_node_path36.join)(repoRoot2, "README.md");
|
|
51299
|
-
if (!(0,
|
|
51569
|
+
if (!(0, import_node_fs39.existsSync)(readmePath)) throw new Error(`org project sync-info: ${targetRepo2} has no README.md`);
|
|
51300
51570
|
const entries = entriesFor(project2, projects);
|
|
51301
51571
|
const memberRepos = [...new Set(entries.flatMap((entry) => entry.repos ?? []))].filter((repo) => /^[^/]+\/[^/]+$/.test(repo)).sort((a, b) => a.localeCompare(b));
|
|
51302
51572
|
const projectName = sharedName(entries, project2.name?.trim() || targetRepo2.split("/").pop() || targetRepo2);
|
|
51303
51573
|
if (!memberRepos.length) throw new Error(`org project sync-info: project ${projectName} has no registered member repos`);
|
|
51304
51574
|
const entryNames = entries.map((entry) => entry.name?.trim()).filter((name) => Boolean(name));
|
|
51305
|
-
const shortDescription = memberRepos.length === 1 ? shortDescriptionFromReadme((0,
|
|
51575
|
+
const shortDescription = memberRepos.length === 1 ? shortDescriptionFromReadme((0, import_node_fs39.readFileSync)(readmePath, "utf8")) : `Shared work across ${new Intl.ListFormat("en", { type: "conjunction" }).format(entryNames)}.`;
|
|
51306
51576
|
const lines2 = [
|
|
51307
51577
|
`# ${projectName}`,
|
|
51308
51578
|
"",
|
|
@@ -51321,8 +51591,8 @@ function buildProjectInfoSyncPlan(targetRepo2, project2, projects, repoRoot2) {
|
|
|
51321
51591
|
const targetBase = `https://github.com/${targetRepo2}`;
|
|
51322
51592
|
const targetBranch = branchFor(targetRepo2, projects);
|
|
51323
51593
|
const orgDocs = [
|
|
51324
|
-
(0,
|
|
51325
|
-
(0,
|
|
51594
|
+
(0, import_node_fs39.existsSync)((0, import_node_path36.join)(repoRoot2, "architecture.md")) ? `- [Hub architecture](${targetBase}/blob/${targetBranch}/architecture.md)` : "",
|
|
51595
|
+
(0, import_node_fs39.existsSync)((0, import_node_path36.join)(repoRoot2, "docs", "Architecture", "agentic-dev-environment.md")) ? `- [Agentic development environment](${targetBase}/blob/${targetBranch}/docs/Architecture/agentic-dev-environment.md)` : ""
|
|
51326
51596
|
].filter(Boolean);
|
|
51327
51597
|
if (orgDocs.length) lines2.push("", "## Organisation docs", "", ...orgDocs);
|
|
51328
51598
|
return { projectId: project2.projectId, projectName, targetRepo: targetRepo2, memberRepos, shortDescription, readme: `${lines2.join("\n")}
|
|
@@ -51344,7 +51614,7 @@ async function syncProjectInfo(plan, client, apply) {
|
|
|
51344
51614
|
}
|
|
51345
51615
|
|
|
51346
51616
|
// src/train-commands.ts
|
|
51347
|
-
var
|
|
51617
|
+
var import_node_fs40 = require("node:fs");
|
|
51348
51618
|
var import_node_path37 = require("node:path");
|
|
51349
51619
|
init_cli_shared();
|
|
51350
51620
|
init_clean_exit();
|
|
@@ -51360,7 +51630,7 @@ function resolveReleaseBumpIntent(raw) {
|
|
|
51360
51630
|
}
|
|
51361
51631
|
function readRepoVersion() {
|
|
51362
51632
|
try {
|
|
51363
|
-
return JSON.parse((0,
|
|
51633
|
+
return JSON.parse((0, import_node_fs40.readFileSync)((0, import_node_path37.join)(process.cwd(), ".claude-plugin", "plugin.json"), "utf8")).version || void 0;
|
|
51364
51634
|
} catch {
|
|
51365
51635
|
return void 0;
|
|
51366
51636
|
}
|
|
@@ -51734,7 +52004,7 @@ function prTitleRefusal(title) {
|
|
|
51734
52004
|
|
|
51735
52005
|
// src/command-register-collaboration.ts
|
|
51736
52006
|
var import_node_child_process17 = require("node:child_process");
|
|
51737
|
-
var
|
|
52007
|
+
var import_node_fs48 = require("node:fs");
|
|
51738
52008
|
var import_promises8 = require("node:fs/promises");
|
|
51739
52009
|
init_clean_exit();
|
|
51740
52010
|
init_cli_shared();
|
|
@@ -51875,7 +52145,7 @@ init_github_client();
|
|
|
51875
52145
|
init_github_quota();
|
|
51876
52146
|
|
|
51877
52147
|
// src/issue-commands.ts
|
|
51878
|
-
var
|
|
52148
|
+
var import_node_fs41 = require("node:fs");
|
|
51879
52149
|
var import_node_crypto16 = require("node:crypto");
|
|
51880
52150
|
init_cli_shared();
|
|
51881
52151
|
init_clean_exit();
|
|
@@ -52068,7 +52338,7 @@ async function editIssue(client, options, deps = {}) {
|
|
|
52068
52338
|
const url = `https://github.com/${repo}/issues/${parsed.number}`;
|
|
52069
52339
|
const patch = {};
|
|
52070
52340
|
let bodyChanged = false;
|
|
52071
|
-
const textDeps = () => deps.textDeps ?? { readFile: (p, e) => Promise.resolve((0,
|
|
52341
|
+
const textDeps = () => deps.textDeps ?? { readFile: (p, e) => Promise.resolve((0, import_node_fs41.readFileSync)(p, e)), readStdin: () => Promise.resolve("") };
|
|
52072
52342
|
if (options.titleFile !== void 0) {
|
|
52073
52343
|
patch.title = await resolveIssueTitle({ title: options.title, titleFile: options.titleFile }, textDeps());
|
|
52074
52344
|
} else if (options.title !== void 0) {
|
|
@@ -52731,7 +53001,7 @@ function extendCreateCommand(issue, batchAttach) {
|
|
|
52731
53001
|
if (opts.batch) {
|
|
52732
53002
|
let specs;
|
|
52733
53003
|
try {
|
|
52734
|
-
const raw = (0,
|
|
53004
|
+
const raw = (0, import_node_fs41.readFileSync)(opts.batch, "utf8");
|
|
52735
53005
|
specs = JSON.parse(raw);
|
|
52736
53006
|
if (!Array.isArray(specs)) {
|
|
52737
53007
|
const top = specs === null ? "null" : typeof specs === "object" ? `an object with keys: ${Object.keys(specs).join(", ") || "(none)"}` : `a ${typeof specs}`;
|
|
@@ -52808,7 +53078,7 @@ ${lines2}`, {
|
|
|
52808
53078
|
}
|
|
52809
53079
|
|
|
52810
53080
|
// src/post-merge-recon.ts
|
|
52811
|
-
var
|
|
53081
|
+
var import_node_fs42 = require("node:fs");
|
|
52812
53082
|
var import_node_path39 = require("node:path");
|
|
52813
53083
|
|
|
52814
53084
|
// src/cross-repo-filing-issue.ts
|
|
@@ -52978,16 +53248,16 @@ function buildPostMergeReconRecovery(input) {
|
|
|
52978
53248
|
}
|
|
52979
53249
|
function writePostMergeReconRecovery(cwd, recovery) {
|
|
52980
53250
|
const path2 = postMergeReconStatePath(cwd, recovery.repo, recovery.pr);
|
|
52981
|
-
(0,
|
|
52982
|
-
(0,
|
|
53251
|
+
(0, import_node_fs42.mkdirSync)((0, import_node_path39.dirname)(path2), { recursive: true });
|
|
53252
|
+
(0, import_node_fs42.writeFileSync)(path2, `${JSON.stringify(recovery, null, 2)}
|
|
52983
53253
|
`, "utf8");
|
|
52984
53254
|
return path2;
|
|
52985
53255
|
}
|
|
52986
53256
|
function clearPostMergeReconRecovery(cwd, repo, pr) {
|
|
52987
53257
|
const path2 = postMergeReconStatePath(cwd, repo, pr);
|
|
52988
|
-
if (!(0,
|
|
53258
|
+
if (!(0, import_node_fs42.existsSync)(path2)) return;
|
|
52989
53259
|
try {
|
|
52990
|
-
(0,
|
|
53260
|
+
(0, import_node_fs42.unlinkSync)(path2);
|
|
52991
53261
|
} catch {
|
|
52992
53262
|
}
|
|
52993
53263
|
}
|
|
@@ -53463,7 +53733,7 @@ function registerPrLifecycleCommands(program3) {
|
|
|
53463
53733
|
|
|
53464
53734
|
// src/review-verdict.ts
|
|
53465
53735
|
var import_node_child_process14 = require("node:child_process");
|
|
53466
|
-
var
|
|
53736
|
+
var import_node_fs43 = require("node:fs");
|
|
53467
53737
|
var import_node_os22 = require("node:os");
|
|
53468
53738
|
var import_node_path40 = require("node:path");
|
|
53469
53739
|
init_cli_shared();
|
|
@@ -53630,15 +53900,15 @@ async function readPrIssueComments(number, repo) {
|
|
|
53630
53900
|
return comments;
|
|
53631
53901
|
}
|
|
53632
53902
|
async function postPrCommentFromFile(number, repo, body) {
|
|
53633
|
-
const dir = (0,
|
|
53903
|
+
const dir = (0, import_node_fs43.mkdtempSync)((0, import_node_path40.join)((0, import_node_os22.tmpdir)(), "mmi-review-verdict-"));
|
|
53634
53904
|
const path2 = (0, import_node_path40.join)(dir, "body.md");
|
|
53635
53905
|
try {
|
|
53636
|
-
(0,
|
|
53906
|
+
(0, import_node_fs43.writeFileSync)(path2, body, "utf8");
|
|
53637
53907
|
const { stdout } = await execFileP("gh", ["pr", "comment", number, "--repo", repo, "--body-file", path2], { timeout: GH_MUTATION_TIMEOUT_MS });
|
|
53638
53908
|
return stdout.trim();
|
|
53639
53909
|
} finally {
|
|
53640
53910
|
try {
|
|
53641
|
-
(0,
|
|
53911
|
+
(0, import_node_fs43.rmSync)(dir, { recursive: true, force: true });
|
|
53642
53912
|
} catch {
|
|
53643
53913
|
}
|
|
53644
53914
|
}
|
|
@@ -53876,17 +54146,17 @@ async function prCreateClaimRefusal(body, repoOption, deps = {}) {
|
|
|
53876
54146
|
}
|
|
53877
54147
|
|
|
53878
54148
|
// src/worktree-residue-sweep.ts
|
|
53879
|
-
var
|
|
54149
|
+
var import_node_fs47 = require("node:fs");
|
|
53880
54150
|
var import_node_path44 = require("node:path");
|
|
53881
54151
|
|
|
53882
54152
|
// src/worktree-merge-cleanup.ts
|
|
53883
|
-
var
|
|
54153
|
+
var import_node_fs46 = require("node:fs");
|
|
53884
54154
|
var import_node_path43 = require("node:path");
|
|
53885
54155
|
init_cli_shared();
|
|
53886
54156
|
|
|
53887
54157
|
// src/stage-runner.ts
|
|
53888
54158
|
var import_node_child_process16 = require("node:child_process");
|
|
53889
|
-
var
|
|
54159
|
+
var import_node_fs45 = require("node:fs");
|
|
53890
54160
|
var import_node_path42 = require("node:path");
|
|
53891
54161
|
var import_node_net = require("node:net");
|
|
53892
54162
|
var import_node_util5 = require("node:util");
|
|
@@ -53897,7 +54167,7 @@ function normalizeEol2(s) {
|
|
|
53897
54167
|
}
|
|
53898
54168
|
|
|
53899
54169
|
// src/env-detection.ts
|
|
53900
|
-
var
|
|
54170
|
+
var import_node_fs44 = require("node:fs");
|
|
53901
54171
|
var import_node_path41 = require("node:path");
|
|
53902
54172
|
var SKIPPED_DIRS = /* @__PURE__ */ new Set([".git", "node_modules", "dist", ".jerv", ".pi", ".venv", "venv"]);
|
|
53903
54173
|
function isEnvBaseName(name) {
|
|
@@ -53908,7 +54178,7 @@ function findEnvFiles(root) {
|
|
|
53908
54178
|
const walk2 = (dir, rel) => {
|
|
53909
54179
|
let entries;
|
|
53910
54180
|
try {
|
|
53911
|
-
entries = (0,
|
|
54181
|
+
entries = (0, import_node_fs44.readdirSync)(dir, { withFileTypes: true });
|
|
53912
54182
|
} catch {
|
|
53913
54183
|
return;
|
|
53914
54184
|
}
|
|
@@ -53922,7 +54192,7 @@ function findEnvFiles(root) {
|
|
|
53922
54192
|
if (isEnvBaseName(entry.name)) {
|
|
53923
54193
|
let isDir = false;
|
|
53924
54194
|
try {
|
|
53925
|
-
isDir = (0,
|
|
54195
|
+
isDir = (0, import_node_fs44.lstatSync)((0, import_node_path41.join)(dir, entry.name)).isDirectory();
|
|
53926
54196
|
} catch {
|
|
53927
54197
|
}
|
|
53928
54198
|
if (!isDir) found.push(relPath);
|
|
@@ -54061,12 +54331,12 @@ var DOCKER_TIMEOUT_MS = 15e3;
|
|
|
54061
54331
|
function stageBash() {
|
|
54062
54332
|
if (process.platform !== "win32") return "bash";
|
|
54063
54333
|
const configured = process.env.SHELL;
|
|
54064
|
-
if (configured && (0, import_node_path42.isAbsolute)(configured) && /[/\\]bash(?:\.exe)?$/i.test(configured) && !/[/\\](?:System32|Sysnative)[/\\]bash(?:\.exe)?$/i.test(configured) && (0,
|
|
54334
|
+
if (configured && (0, import_node_path42.isAbsolute)(configured) && /[/\\]bash(?:\.exe)?$/i.test(configured) && !/[/\\](?:System32|Sysnative)[/\\]bash(?:\.exe)?$/i.test(configured) && (0, import_node_fs45.existsSync)(configured)) return configured;
|
|
54065
54335
|
const candidates = [
|
|
54066
54336
|
(0, import_node_path42.join)(process.env.ProgramFiles || "C:\\Program Files", "Git", "bin", "bash.exe"),
|
|
54067
54337
|
...process.env.LOCALAPPDATA ? [(0, import_node_path42.join)(process.env.LOCALAPPDATA, "Programs", "Git", "bin", "bash.exe")] : []
|
|
54068
54338
|
];
|
|
54069
|
-
const bash = candidates.find((path2) => (0,
|
|
54339
|
+
const bash = candidates.find((path2) => (0, import_node_fs45.existsSync)(path2));
|
|
54070
54340
|
if (!bash) throw new Error("Local stages require Git Bash on Windows. Install Git for Windows or set SHELL to its absolute bash.exe path.");
|
|
54071
54341
|
return bash;
|
|
54072
54342
|
}
|
|
@@ -54422,7 +54692,7 @@ function stageProcessEnv(stagePort, extraEnv) {
|
|
|
54422
54692
|
function composeResolvesPort(cwd) {
|
|
54423
54693
|
if (process.env.PORT) return true;
|
|
54424
54694
|
const envFile = (0, import_node_path42.join)(cwd, ".env");
|
|
54425
|
-
return (0,
|
|
54695
|
+
return (0, import_node_fs45.existsSync)(envFile) && envFileKeys((0, import_node_fs45.readFileSync)(envFile, "utf8")).has("PORT");
|
|
54426
54696
|
}
|
|
54427
54697
|
function stageComposeEnv(config, stagePort, vaultEnvMerge, cwd) {
|
|
54428
54698
|
const composeProject = config.env?.COMPOSE_PROJECT_NAME ?? vaultEnvMerge?.COMPOSE_PROJECT_NAME ?? stageComposeProjectName(cwd);
|
|
@@ -54437,13 +54707,13 @@ function stageComposeEnv(config, stagePort, vaultEnvMerge, cwd) {
|
|
|
54437
54707
|
function stageComposeFiles(cwd) {
|
|
54438
54708
|
const files = ["docker-compose.yml"];
|
|
54439
54709
|
for (const conventional of ["docker-compose.override.yml", "docker-compose.override.yaml"]) {
|
|
54440
|
-
if ((0,
|
|
54710
|
+
if ((0, import_node_fs45.existsSync)((0, import_node_path42.join)(cwd, conventional))) files.push(conventional);
|
|
54441
54711
|
}
|
|
54442
54712
|
return [...files, RUNTIME_ENV_OVERRIDE_RELPATH];
|
|
54443
54713
|
}
|
|
54444
54714
|
async function prepareRuntimeEnvPassthrough(cwd, composeEnv, vaultEnvMerge) {
|
|
54445
54715
|
const keys = Object.keys(vaultEnvMerge ?? {});
|
|
54446
|
-
if (!keys.length || !(0,
|
|
54716
|
+
if (!keys.length || !(0, import_node_fs45.existsSync)((0, import_node_path42.join)(cwd, "docker-compose.yml"))) return {};
|
|
54447
54717
|
let configJson;
|
|
54448
54718
|
try {
|
|
54449
54719
|
const { stdout } = await execFileP3("docker", ["compose", "config", "--format", "json"], {
|
|
@@ -54460,20 +54730,20 @@ async function prepareRuntimeEnvPassthrough(cwd, composeEnv, vaultEnvMerge) {
|
|
|
54460
54730
|
const body = renderRuntimeEnvOverride(runtimeEnvMarkedServices(configJson), keys, runtimeEnvExcludes(configJson), runtimeEnvSelects(configJson));
|
|
54461
54731
|
if (!body) return {};
|
|
54462
54732
|
const overridePath = (0, import_node_path42.join)(cwd, RUNTIME_ENV_OVERRIDE_RELPATH);
|
|
54463
|
-
(0,
|
|
54464
|
-
(0,
|
|
54733
|
+
(0, import_node_fs45.mkdirSync)((0, import_node_path42.join)(cwd, "tmp", "stage"), { recursive: true });
|
|
54734
|
+
(0, import_node_fs45.writeFileSync)(overridePath, body, "utf8");
|
|
54465
54735
|
return stageComposeFileEnv(stageComposeFiles(cwd));
|
|
54466
54736
|
}
|
|
54467
54737
|
async function ensureStageRuntimeEnv(config, opts, cwd) {
|
|
54468
54738
|
if (!config.ensureEnv) return;
|
|
54469
54739
|
const target = (0, import_node_path42.join)(cwd, config.ensureEnv.target);
|
|
54470
54740
|
const example = (0, import_node_path42.join)(cwd, config.ensureEnv.example);
|
|
54471
|
-
if (!(0,
|
|
54472
|
-
(0,
|
|
54473
|
-
} else if ((0,
|
|
54474
|
-
const stale = detectStaleEnvFile((0,
|
|
54475
|
-
exampleMtimeMs: (0,
|
|
54476
|
-
targetMtimeMs: (0,
|
|
54741
|
+
if (!(0, import_node_fs45.existsSync)(target) && (0, import_node_fs45.existsSync)(example)) {
|
|
54742
|
+
(0, import_node_fs45.copyFileSync)(example, target);
|
|
54743
|
+
} else if ((0, import_node_fs45.existsSync)(target) && (0, import_node_fs45.existsSync)(example)) {
|
|
54744
|
+
const stale = detectStaleEnvFile((0, import_node_fs45.readFileSync)(example, "utf8"), (0, import_node_fs45.readFileSync)(target, "utf8"), {
|
|
54745
|
+
exampleMtimeMs: (0, import_node_fs45.statSync)(example).mtimeMs,
|
|
54746
|
+
targetMtimeMs: (0, import_node_fs45.statSync)(target).mtimeMs
|
|
54477
54747
|
});
|
|
54478
54748
|
if (stale) {
|
|
54479
54749
|
const msg = `stale ${config.ensureEnv.target} (${stale}) \u2014 delete it or refresh from ${config.ensureEnv.example} before re-running mmi-cli stage`;
|
|
@@ -54481,8 +54751,8 @@ async function ensureStageRuntimeEnv(config, opts, cwd) {
|
|
|
54481
54751
|
console.error(`mmi-cli stage: ${msg} (allowed via --allow-stale-env)`);
|
|
54482
54752
|
}
|
|
54483
54753
|
}
|
|
54484
|
-
if (opts.vaultEnvMerge && Object.keys(opts.vaultEnvMerge).length && (0,
|
|
54485
|
-
(0,
|
|
54754
|
+
if (opts.vaultEnvMerge && Object.keys(opts.vaultEnvMerge).length && (0, import_node_fs45.existsSync)(target)) {
|
|
54755
|
+
(0, import_node_fs45.writeFileSync)(target, mergeEnvSecretsIntoFile((0, import_node_fs45.readFileSync)(target, "utf8"), opts.vaultEnvMerge), "utf8");
|
|
54486
54756
|
}
|
|
54487
54757
|
}
|
|
54488
54758
|
async function gitText(cwd, args) {
|
|
@@ -54512,20 +54782,20 @@ async function resolveGlobalStatePath(cwd, explicit) {
|
|
|
54512
54782
|
return void 0;
|
|
54513
54783
|
}
|
|
54514
54784
|
function readState(path2) {
|
|
54515
|
-
if (!(0,
|
|
54785
|
+
if (!(0, import_node_fs45.existsSync)(path2)) return null;
|
|
54516
54786
|
try {
|
|
54517
|
-
return JSON.parse((0,
|
|
54787
|
+
return JSON.parse((0, import_node_fs45.readFileSync)(path2, "utf8"));
|
|
54518
54788
|
} catch {
|
|
54519
54789
|
return null;
|
|
54520
54790
|
}
|
|
54521
54791
|
}
|
|
54522
54792
|
function mkdirFor(path2) {
|
|
54523
54793
|
const dir = path2.slice(0, Math.max(path2.lastIndexOf("/"), path2.lastIndexOf("\\")));
|
|
54524
|
-
(0,
|
|
54794
|
+
(0, import_node_fs45.mkdirSync)(dir, { recursive: true });
|
|
54525
54795
|
}
|
|
54526
54796
|
function writeState(path2, state) {
|
|
54527
54797
|
mkdirFor(path2);
|
|
54528
|
-
(0,
|
|
54798
|
+
(0, import_node_fs45.writeFileSync)(path2, JSON.stringify(state, null, 2), "utf8");
|
|
54529
54799
|
}
|
|
54530
54800
|
function writeStagePortReservation(port, cwd, statePath, globalStatePath, now) {
|
|
54531
54801
|
const reservation = {
|
|
@@ -54554,7 +54824,7 @@ async function cleanupStageState(state, paths, timeoutMs, fallbackCwd, currentEn
|
|
|
54554
54824
|
);
|
|
54555
54825
|
}
|
|
54556
54826
|
for (const path2 of [...new Set(paths.filter((p) => Boolean(p)))]) {
|
|
54557
|
-
(0,
|
|
54827
|
+
(0, import_node_fs45.rmSync)(path2, { force: true });
|
|
54558
54828
|
}
|
|
54559
54829
|
}
|
|
54560
54830
|
async function killTree(pid) {
|
|
@@ -54801,8 +55071,8 @@ async function runStage(config = {}, opts = {}) {
|
|
|
54801
55071
|
});
|
|
54802
55072
|
}
|
|
54803
55073
|
} catch (e) {
|
|
54804
|
-
(0,
|
|
54805
|
-
if (globalStatePath && globalStatePath !== statePath) (0,
|
|
55074
|
+
(0, import_node_fs45.rmSync)(statePath, { force: true });
|
|
55075
|
+
if (globalStatePath && globalStatePath !== statePath) (0, import_node_fs45.rmSync)(globalStatePath, { force: true });
|
|
54806
55076
|
throw e;
|
|
54807
55077
|
}
|
|
54808
55078
|
const started = await startStage(config, {
|
|
@@ -54823,7 +55093,7 @@ function normPath2(p) {
|
|
|
54823
55093
|
function unlinkNodeModulesJunction(wtPath) {
|
|
54824
55094
|
const nm = (0, import_node_path43.join)(wtPath, "node_modules");
|
|
54825
55095
|
try {
|
|
54826
|
-
if ((0,
|
|
55096
|
+
if ((0, import_node_fs46.lstatSync)(nm).isSymbolicLink()) (0, import_node_fs46.rmdirSync)(nm);
|
|
54827
55097
|
return { ok: true };
|
|
54828
55098
|
} catch (e) {
|
|
54829
55099
|
if (e.code === "ENOENT") return { ok: true };
|
|
@@ -54834,8 +55104,8 @@ function defaultSleep2(ms) {
|
|
|
54834
55104
|
return new Promise((resolve7) => setTimeout(resolve7, ms));
|
|
54835
55105
|
}
|
|
54836
55106
|
async function removeResidueDirectory(wtPath, options = {}) {
|
|
54837
|
-
const { sleep: sleep2 = defaultSleep2, removeEmptyDir =
|
|
54838
|
-
const removeRecursive = options.removeRecursive ?? ((path2) => (0,
|
|
55107
|
+
const { sleep: sleep2 = defaultSleep2, removeEmptyDir = import_node_fs46.rmdirSync, exists = import_node_fs46.existsSync } = options;
|
|
55108
|
+
const removeRecursive = options.removeRecursive ?? ((path2) => (0, import_node_fs46.rmSync)(path2, { recursive: true, force: true, maxRetries: 10, retryDelay: 100 }));
|
|
54839
55109
|
const junction = unlinkNodeModulesJunction(wtPath);
|
|
54840
55110
|
if (!junction.ok) return junction;
|
|
54841
55111
|
try {
|
|
@@ -54961,21 +55231,21 @@ async function listNestedIgnoredNodeModules(wtPath, execGit) {
|
|
|
54961
55231
|
return out.split(/\r?\n/).map((line) => line.trim()).filter((line) => line.endsWith("node_modules/") && line !== "node_modules/").map((line) => (0, import_node_path43.join)(wtPath, line.slice(0, -1)));
|
|
54962
55232
|
}
|
|
54963
55233
|
function safeRemoveTree(path2) {
|
|
54964
|
-
const stat4 = (0,
|
|
55234
|
+
const stat4 = (0, import_node_fs46.lstatSync)(path2);
|
|
54965
55235
|
if (stat4.isSymbolicLink()) {
|
|
54966
55236
|
try {
|
|
54967
|
-
(0,
|
|
55237
|
+
(0, import_node_fs46.rmdirSync)(path2);
|
|
54968
55238
|
} catch {
|
|
54969
|
-
(0,
|
|
55239
|
+
(0, import_node_fs46.unlinkSync)(path2);
|
|
54970
55240
|
}
|
|
54971
55241
|
return;
|
|
54972
55242
|
}
|
|
54973
55243
|
if (stat4.isDirectory()) {
|
|
54974
|
-
for (const entry of (0,
|
|
54975
|
-
(0,
|
|
55244
|
+
for (const entry of (0, import_node_fs46.readdirSync)(path2)) safeRemoveTree((0, import_node_path43.join)(path2, entry));
|
|
55245
|
+
(0, import_node_fs46.rmdirSync)(path2);
|
|
54976
55246
|
return;
|
|
54977
55247
|
}
|
|
54978
|
-
(0,
|
|
55248
|
+
(0, import_node_fs46.unlinkSync)(path2);
|
|
54979
55249
|
}
|
|
54980
55250
|
async function removeWorktreeNodeModules(wtPath) {
|
|
54981
55251
|
try {
|
|
@@ -54991,7 +55261,7 @@ function errorMessage(e) {
|
|
|
54991
55261
|
}
|
|
54992
55262
|
function resolvedOrRaw(path2) {
|
|
54993
55263
|
try {
|
|
54994
|
-
return normPath2((0,
|
|
55264
|
+
return normPath2((0, import_node_fs46.realpathSync)(path2));
|
|
54995
55265
|
} catch {
|
|
54996
55266
|
return normPath2(path2);
|
|
54997
55267
|
}
|
|
@@ -54999,10 +55269,10 @@ function resolvedOrRaw(path2) {
|
|
|
54999
55269
|
function unlinkEscapingReparsePoints(root, primaryRoot) {
|
|
55000
55270
|
let realRoot;
|
|
55001
55271
|
try {
|
|
55002
|
-
if ((0,
|
|
55272
|
+
if ((0, import_node_fs46.lstatSync)(root).isSymbolicLink()) {
|
|
55003
55273
|
return { ok: false, error: `delete root ${normPath2(root)} is a reparse point resolving to ${resolvedOrRaw(root)}` };
|
|
55004
55274
|
}
|
|
55005
|
-
realRoot = normPath2((0,
|
|
55275
|
+
realRoot = normPath2((0, import_node_fs46.realpathSync)(root));
|
|
55006
55276
|
} catch (e) {
|
|
55007
55277
|
if (e.code === "ENOENT") return { ok: true, unlinked: [] };
|
|
55008
55278
|
return { ok: false, error: `cannot resolve delete root ${normPath2(root)}: ${errorMessage(e)}` };
|
|
@@ -55018,7 +55288,7 @@ function unlinkEscapingReparsePoints(root, primaryRoot) {
|
|
|
55018
55288
|
const dir = stack.pop();
|
|
55019
55289
|
let entries;
|
|
55020
55290
|
try {
|
|
55021
|
-
entries = (0,
|
|
55291
|
+
entries = (0, import_node_fs46.readdirSync)(dir, { withFileTypes: true });
|
|
55022
55292
|
} catch (e) {
|
|
55023
55293
|
if (e.code === "ENOENT") continue;
|
|
55024
55294
|
return { ok: false, error: `cannot scan ${normPath2(dir)} for reparse points: ${errorMessage(e)}` };
|
|
@@ -55028,7 +55298,7 @@ function unlinkEscapingReparsePoints(root, primaryRoot) {
|
|
|
55028
55298
|
if (entry.isSymbolicLink()) {
|
|
55029
55299
|
let target = "";
|
|
55030
55300
|
try {
|
|
55031
|
-
target = normPath2((0,
|
|
55301
|
+
target = normPath2((0, import_node_fs46.realpathSync)(child2));
|
|
55032
55302
|
} catch {
|
|
55033
55303
|
target = "";
|
|
55034
55304
|
}
|
|
@@ -55211,7 +55481,7 @@ async function cleanupPrMergeLocalBranch(branch, options) {
|
|
|
55211
55481
|
return report;
|
|
55212
55482
|
}
|
|
55213
55483
|
const execGit = options.execGit ?? (async (args) => (await execFileP("git", args, { timeout: GIT_TIMEOUT_MS })).stdout);
|
|
55214
|
-
const pathExists = options.pathExists ??
|
|
55484
|
+
const pathExists = options.pathExists ?? import_node_fs46.existsSync;
|
|
55215
55485
|
let afterWorktrees = [];
|
|
55216
55486
|
try {
|
|
55217
55487
|
afterWorktrees = parseGitWorktreePorcelain(await execGit(["worktree", "list", "--porcelain"]));
|
|
@@ -55498,7 +55768,7 @@ function residueLedgerPath(gitCommonDir) {
|
|
|
55498
55768
|
}
|
|
55499
55769
|
function readPaths(file, deps) {
|
|
55500
55770
|
try {
|
|
55501
|
-
const parsed = JSON.parse((deps.readLedger ?? ((f) => (0,
|
|
55771
|
+
const parsed = JSON.parse((deps.readLedger ?? ((f) => (0, import_node_fs47.readFileSync)(f, "utf8")))(file));
|
|
55502
55772
|
return Array.isArray(parsed) ? parsed.filter((p) => typeof p === "string") : [];
|
|
55503
55773
|
} catch {
|
|
55504
55774
|
return [];
|
|
@@ -55507,8 +55777,8 @@ function readPaths(file, deps) {
|
|
|
55507
55777
|
function writePaths(file, paths, deps) {
|
|
55508
55778
|
try {
|
|
55509
55779
|
const write = deps.writeLedger ?? ((f, t) => {
|
|
55510
|
-
(0,
|
|
55511
|
-
(0,
|
|
55780
|
+
(0, import_node_fs47.mkdirSync)((0, import_node_path44.dirname)(f), { recursive: true });
|
|
55781
|
+
(0, import_node_fs47.writeFileSync)(f, t, "utf8");
|
|
55512
55782
|
});
|
|
55513
55783
|
write(file, `${JSON.stringify([...new Set(paths)], null, 1)}
|
|
55514
55784
|
`);
|
|
@@ -55522,17 +55792,17 @@ function recordResidue(gitCommonDir, path2, deps = {}) {
|
|
|
55522
55792
|
writePaths(file, paths, deps);
|
|
55523
55793
|
}
|
|
55524
55794
|
async function sweepWorktreeResidue(gitCommonDir, registered, deps = {}) {
|
|
55525
|
-
const exists = deps.exists ??
|
|
55526
|
-
const readDir = deps.readDir ?? ((p) => (0,
|
|
55795
|
+
const exists = deps.exists ?? import_node_fs47.existsSync;
|
|
55796
|
+
const readDir = deps.readDir ?? ((p) => (0, import_node_fs47.readdirSync)(p));
|
|
55527
55797
|
const isDir = deps.isDir ?? ((p) => {
|
|
55528
55798
|
try {
|
|
55529
|
-
return (0,
|
|
55799
|
+
return (0, import_node_fs47.statSync)(p).isDirectory();
|
|
55530
55800
|
} catch {
|
|
55531
55801
|
return false;
|
|
55532
55802
|
}
|
|
55533
55803
|
});
|
|
55534
55804
|
const removeRecorded = deps.removeRecorded ?? ((p) => removeResidueDirectory(p));
|
|
55535
|
-
const removeEmpty = deps.removeEmpty ?? ((p) => (0,
|
|
55805
|
+
const removeEmpty = deps.removeEmpty ?? ((p) => (0, import_node_fs47.rmdirSync)(p));
|
|
55536
55806
|
const report = { swept: [], kept: [], dropped: [], emptyRemoved: [], unrecorded: [] };
|
|
55537
55807
|
const live = new Set(registered.map(norm));
|
|
55538
55808
|
const primary = registered[0] ? norm(registered[0]) : void 0;
|
|
@@ -55612,8 +55882,8 @@ function argvWantsJson2() {
|
|
|
55612
55882
|
function hubRoot() {
|
|
55613
55883
|
const fromPkg = (0, import_node_path45.join)(__dirname, "..", "..");
|
|
55614
55884
|
const marker = "skills/bootstrap/seeds/manifest.json";
|
|
55615
|
-
if ((0,
|
|
55616
|
-
if ((0,
|
|
55885
|
+
if ((0, import_node_fs48.existsSync)((0, import_node_path45.join)(fromPkg, marker))) return fromPkg;
|
|
55886
|
+
if ((0, import_node_fs48.existsSync)((0, import_node_path45.join)(process.cwd(), marker))) return process.cwd();
|
|
55617
55887
|
return null;
|
|
55618
55888
|
}
|
|
55619
55889
|
function ciAuditDeps() {
|
|
@@ -55626,7 +55896,7 @@ function ciAuditDeps() {
|
|
|
55626
55896
|
readSeedFile: (path2) => {
|
|
55627
55897
|
if (!root) return null;
|
|
55628
55898
|
const fullPath = (0, import_node_path45.join)(root, path2);
|
|
55629
|
-
return (0,
|
|
55899
|
+
return (0, import_node_fs48.existsSync)(fullPath) ? (0, import_node_fs48.readFileSync)(fullPath, "utf8") : null;
|
|
55630
55900
|
}
|
|
55631
55901
|
};
|
|
55632
55902
|
}
|
|
@@ -56440,10 +56710,10 @@ ${list}`);
|
|
|
56440
56710
|
});
|
|
56441
56711
|
async function listCiWorkflowPaths(cwd = process.cwd()) {
|
|
56442
56712
|
const wfDir = (0, import_node_path45.join)(cwd, ".github", "workflows");
|
|
56443
|
-
if (!(0,
|
|
56444
|
-
return (0,
|
|
56713
|
+
if (!(0, import_node_fs48.existsSync)(wfDir)) return [];
|
|
56714
|
+
return (0, import_node_fs48.readdirSync)(wfDir).filter((name) => /\.(ya?ml)$/i.test(name)).filter((name) => {
|
|
56445
56715
|
try {
|
|
56446
|
-
return workflowReportsPrChecks((0,
|
|
56716
|
+
return workflowReportsPrChecks((0, import_node_fs48.readFileSync)((0, import_node_path45.join)(wfDir, name), "utf8"));
|
|
56447
56717
|
} catch {
|
|
56448
56718
|
return true;
|
|
56449
56719
|
}
|
|
@@ -56648,7 +56918,7 @@ ${list}`);
|
|
|
56648
56918
|
jsonParity(pr.command("review-verdict <number>").description("post an explicit review verdict comment: computes the PR diff patch-id and head sha, renders the v1 contract body, and posts it as a PR comment").requiredOption("--repo <owner/repo>", "target repo").requiredOption("--verdict <verdict>", `one of ${REVIEW_VERDICTS.join("|")}`).requiredOption("--scope <text>", "one line: what was reviewed").requiredOption("--risk <text>", "one line: the residual risk").option("--unverified <text>", "a claim the reviewer could not verify (repeatable)", (v, acc) => [...acc, v], []).requiredOption("--reviewer <id>", "reviewer seat or model id").option("--findings-file <path>", "Markdown findings appended after the JSON block")).action(async (number, o) => {
|
|
56649
56919
|
if (!isReviewVerdict(o.verdict)) return fail(`pr review-verdict: --verdict must be one of ${REVIEW_VERDICTS.join("|")} (got ${o.verdict})`);
|
|
56650
56920
|
const verdict = o.verdict;
|
|
56651
|
-
const findings = o.findingsFile ? (0,
|
|
56921
|
+
const findings = o.findingsFile ? (0, import_node_fs48.readFileSync)(o.findingsFile, "utf8") : void 0;
|
|
56652
56922
|
let patch;
|
|
56653
56923
|
let head;
|
|
56654
56924
|
try {
|
|
@@ -56862,21 +57132,21 @@ ${list}`);
|
|
|
56862
57132
|
// #8456: a pinned land has no update lane at all.
|
|
56863
57133
|
...o.expectHead ? {} : { updateBranch: (prNumber, repo) => prLandUpdateBranch(prNumber, repo, Boolean(o.repo)) },
|
|
56864
57134
|
pollMerged: async (prNumber, repo, deadlineMs) => {
|
|
56865
|
-
let
|
|
57135
|
+
let lastFailure2;
|
|
56866
57136
|
while (Date.now() < deadlineMs) {
|
|
56867
57137
|
const read = await pollRestPrMerged(prNumber, repo);
|
|
56868
57138
|
if (read.state === "merged") return true;
|
|
56869
57139
|
if (read.state === "failed") {
|
|
56870
|
-
|
|
57140
|
+
lastFailure2 = read.error;
|
|
56871
57141
|
console.warn(`pr land: merged-state read FAILED (${read.error}) \u2014 retrying; this is not evidence the PR is unmerged`);
|
|
56872
57142
|
} else {
|
|
56873
|
-
|
|
57143
|
+
lastFailure2 = void 0;
|
|
56874
57144
|
}
|
|
56875
57145
|
await new Promise((resolve7) => setTimeout(resolve7, PR_LAND_POLL_MS));
|
|
56876
57146
|
}
|
|
56877
|
-
if (
|
|
57147
|
+
if (lastFailure2) {
|
|
56878
57148
|
throw new Error(
|
|
56879
|
-
`pr land: could not verify whether PR #${prNumber} merged \u2014 the last merged-state read FAILED (${
|
|
57149
|
+
`pr land: could not verify whether PR #${prNumber} merged \u2014 the last merged-state read FAILED (${lastFailure2}). The auto-merge enqueue stands; read the PR directly (gh pr view ${prNumber} --repo ${repo} --json merged,state) and rerun cleanup if it merged.`
|
|
56880
57150
|
);
|
|
56881
57151
|
}
|
|
56882
57152
|
return false;
|
|
@@ -56923,7 +57193,7 @@ ${list}`);
|
|
|
56923
57193
|
const cleanupMerged = async (r) => {
|
|
56924
57194
|
if (opts.preserveWorktree) return "cleanup skipped (--preserve-worktree)";
|
|
56925
57195
|
const ran = await execFileP(process.execPath, [process.argv[1], ...canonicalArgvFor(["pr", "merge", r.pr, "--repo", r.repo])], {
|
|
56926
|
-
cwd: (0,
|
|
57196
|
+
cwd: (0, import_node_fs48.existsSync)(cwd0) ? cwd0 : primaryRoot0,
|
|
56927
57197
|
timeout: GH_MUTATION_TIMEOUT_MS * 3
|
|
56928
57198
|
}).then((x) => ({ stdout: x.stdout, stderr: x.stderr }), (e) => ({ stdout: e.stdout ?? "", stderr: e.stderr ?? "" }));
|
|
56929
57199
|
const json = ran.stdout.split("\n").reverse().find((l) => l.startsWith("{"));
|
|
@@ -56993,7 +57263,7 @@ ${list}`);
|
|
|
56993
57263
|
const mergeSquashBody = squashBodyTextForMerge(
|
|
56994
57264
|
closingGuardInput,
|
|
56995
57265
|
method === "--squash",
|
|
56996
|
-
o.squashBodyFile ? (0,
|
|
57266
|
+
o.squashBodyFile ? (0, import_node_fs48.readFileSync)(o.squashBodyFile, "utf8") : void 0
|
|
56997
57267
|
);
|
|
56998
57268
|
if (!o.squashBodyFile) warnSquashBodyClosingStrip("pr merge", closingGuardInput, mergeSquashBody);
|
|
56999
57269
|
const closingGuardVerdict = evaluateClosingGuard(closingGuardInput, {
|
|
@@ -57144,7 +57414,7 @@ ${list}`);
|
|
|
57144
57414
|
}
|
|
57145
57415
|
if (!repoForPostCleanup) throw e;
|
|
57146
57416
|
console.warn(`pr merge: gh GraphQL rate-limited \u2014 merging PR #${number} via REST PUT instead (#4588).`);
|
|
57147
|
-
const commitMessage = bodyFile ? (0,
|
|
57417
|
+
const commitMessage = bodyFile ? (0, import_node_fs48.readFileSync)(bodyFile, "utf8") : void 0;
|
|
57148
57418
|
await defaultGitHubClient().rest("PUT", `repos/${repoForPostCleanup}/pulls/${number}/merge`, {
|
|
57149
57419
|
body: { merge_method: method.slice(2), ...commitMessage ? { commit_message: commitMessage } : {} },
|
|
57150
57420
|
timeoutMs: GH_MUTATION_TIMEOUT_MS
|
|
@@ -57277,7 +57547,7 @@ ${list}`);
|
|
|
57277
57547
|
preserveWorktree: o.preserveWorktree,
|
|
57278
57548
|
gcAcknowledged: o.gc,
|
|
57279
57549
|
expectedHeadOid: headRefOid,
|
|
57280
|
-
pathExists: (p) => (0,
|
|
57550
|
+
pathExists: (p) => (0, import_node_fs48.existsSync)(p),
|
|
57281
57551
|
// #5899: pin cleanup git calls to the main checkout — the task worktree this process may be
|
|
57282
57552
|
// standing in is removed mid-cleanup, so a cwd-relative invocation fails with
|
|
57283
57553
|
// 'fatal: not a git repository' and leaves a spurious partial-cleanup exit.
|
|
@@ -57539,6 +57809,8 @@ function trainApplyDeps() {
|
|
|
57539
57809
|
// parses, so the ONE self-spawn seam derives the prefix through the same rule the refusal names.
|
|
57540
57810
|
runSelf: async (args) => (await execFileP(process.execPath, [process.argv[1], ...canonicalArgvFor(args)], { timeout: 3e4 })).stdout,
|
|
57541
57811
|
trainAuthority: async (repo) => {
|
|
57812
|
+
const ci2 = await hubReleaseCiAuthority(repo, { env: process.env, getJson: pinnedGitHubGetJson(process.env.GH_TOKEN) });
|
|
57813
|
+
if (ci2) return ci2;
|
|
57542
57814
|
const verdict = await fetchTrainAuthority(repo, registryClientDeps(await loadConfig()));
|
|
57543
57815
|
return verdict.ok ? {
|
|
57544
57816
|
ok: true,
|
|
@@ -57609,6 +57881,7 @@ function trainApplyDeps() {
|
|
|
57609
57881
|
return (await execFileP(file, cmdArgs, { timeout: GH_TRAIN_TIMEOUT_MS, ...env ? { env } : {} })).stdout;
|
|
57610
57882
|
},
|
|
57611
57883
|
postAnnouncement: async (payload) => {
|
|
57884
|
+
if (hubReleaseCiRun(process.env)) throw new Error(HUB_RELEASE_CI_ANNOUNCE_DEFERRED);
|
|
57612
57885
|
const res = await releaseAnnouncement(payload, registryClientDeps(await loadConfig()));
|
|
57613
57886
|
const body = res.body;
|
|
57614
57887
|
if (!res.ok) throw new Error(body?.error ?? `Hub announcement failed (HTTP ${res.status}); verify delivery before retrying`);
|
|
@@ -57692,11 +57965,13 @@ function renderReleaseSupersedeDeploy(r) {
|
|
|
57692
57965
|
}
|
|
57693
57966
|
function renderReleaseSupersedePublish(r) {
|
|
57694
57967
|
const base = `mmi-cli devops release --supersede-publish --apply: ${r.repo} ${r.tag} (${r.tagSha.slice(0, 12)}) \u2014 ${r.note}`;
|
|
57695
|
-
|
|
57968
|
+
if (!r.gatewayDeploy) return base;
|
|
57969
|
+
return r.gatewayDeploy.state === "skipped" ? `${base}; the deferred Jerv Gateway operator-host deploy leg was SKIPPED because the failed publish never produced an installer payload (jerv-gateway=skipped, superseded): ${r.gatewayDeploy.note}` : `${base}; the deferred Jerv Gateway operator-host deploy leg was run and verified during the supersession (jerv-gateway=complete): ${r.gatewayDeploy.note}`;
|
|
57696
57970
|
}
|
|
57697
57971
|
function renderReleaseSupersedeDeployAndPublish(r) {
|
|
57698
57972
|
const base = `mmi-cli devops release --supersede-deploy --supersede-publish --apply: ${r.repo} ${r.tag} (${r.tagSha.slice(0, 12)}) \u2014 ${r.note}`;
|
|
57699
|
-
|
|
57973
|
+
if (!r.gatewayDeploy) return base;
|
|
57974
|
+
return r.gatewayDeploy.state === "skipped" ? `${base}; the deferred Jerv Gateway operator-host deploy leg was SKIPPED because the failed publish never produced an installer payload (jerv-gateway=skipped, superseded): ${r.gatewayDeploy.note}` : `${base}; the deferred Jerv Gateway operator-host deploy leg was run and verified during the closure (jerv-gateway=complete): ${r.gatewayDeploy.note}`;
|
|
57700
57975
|
}
|
|
57701
57976
|
function renderReleasePublishRetry(r) {
|
|
57702
57977
|
return `mmi-cli devops release --retry-publish: ${r.repo} ${r.tag} (${r.tagSha.slice(0, 12)}) \u2014 ${r.note}; ${r.runUrl}`;
|
|
@@ -58118,7 +58393,7 @@ function registerTrainOperationsCommands(program3, runProjectInfoSyncCallback) {
|
|
|
58118
58393
|
if (!guard.ok) return fail(`release: ${guard.message}`);
|
|
58119
58394
|
}
|
|
58120
58395
|
try {
|
|
58121
|
-
const result = await runReleaseAbort(trainApplyDeps(), { approved: true });
|
|
58396
|
+
const result = await runReleaseAbort(trainApplyDeps(), { approved: true, ciCheckout: hubReleaseCiRun(process.env) !== null });
|
|
58122
58397
|
return emitTrainResult("release --abort", o.json ? JSON.stringify(result, null, 2) : renderReleaseAbort(result), o.out);
|
|
58123
58398
|
} catch (e) {
|
|
58124
58399
|
return failGraceful(`release --abort: ${e.message}`);
|
|
@@ -58587,11 +58862,11 @@ function suiteTrainIo(root, summaryFile, memberSummaryFiles = {}, summaryFileTr,
|
|
|
58587
58862
|
const checkout = async (repo) => {
|
|
58588
58863
|
if (repo === SUITE_REPO) return root;
|
|
58589
58864
|
const target = (0, import_node_path47.join)(base, repo.split("/")[1]);
|
|
58590
|
-
if (!(0,
|
|
58591
|
-
(0,
|
|
58865
|
+
if (!(0, import_node_fs49.existsSync)(target)) {
|
|
58866
|
+
(0, import_node_fs49.mkdirSync)(base, { recursive: true });
|
|
58592
58867
|
await exec("git", ["clone", "--quiet", "--branch", "development", `https://github.com/${repo}.git`, target], root, WATCH_TIMEOUT);
|
|
58593
58868
|
}
|
|
58594
|
-
if (!(0,
|
|
58869
|
+
if (!(0, import_node_fs49.existsSync)((0, import_node_path47.join)(target, ".git"))) throw new Error(`${repo}: suite checkout has no .git at ${target}; stop`);
|
|
58595
58870
|
const origin = await exec("git", ["remote", "get-url", "origin"], target);
|
|
58596
58871
|
if (!origin.toLowerCase().endsWith(`/${repo.toLowerCase()}.git`) && !origin.toLowerCase().endsWith(`/${repo.toLowerCase()}`))
|
|
58597
58872
|
throw new Error(`${repo}: suite checkout origin mismatch at ${target}; stop`);
|
|
@@ -58898,9 +59173,9 @@ function suiteTrainIo(root, summaryFile, memberSummaryFiles = {}, summaryFileTr,
|
|
|
58898
59173
|
const body = await gh(["release", "view", leadTag, "--repo", SUITE_REPO, "--json", "body", "--jq", ".body"]);
|
|
58899
59174
|
if (parseSuiteMapping(body)) return;
|
|
58900
59175
|
const dir = suiteStateDir(root);
|
|
58901
|
-
(0,
|
|
59176
|
+
(0, import_node_fs49.mkdirSync)(dir, { recursive: true });
|
|
58902
59177
|
const file = (0, import_node_path47.join)(dir, `notes-${leadTag}.md`);
|
|
58903
|
-
(0,
|
|
59178
|
+
(0, import_node_fs49.writeFileSync)(file, `${body}
|
|
58904
59179
|
|
|
58905
59180
|
${suiteMappingBlock(leadTag, mapping)}`);
|
|
58906
59181
|
await gh(["release", "edit", leadTag, "--repo", SUITE_REPO, "--notes-file", file]);
|
|
@@ -58941,7 +59216,7 @@ ${suiteMappingBlock(leadTag, mapping)}`);
|
|
|
58941
59216
|
|
|
58942
59217
|
// src/suite-commands.ts
|
|
58943
59218
|
init_cli_shared();
|
|
58944
|
-
var
|
|
59219
|
+
var import_node_fs50 = require("node:fs");
|
|
58945
59220
|
function parseMemberSummaryFiles(entries) {
|
|
58946
59221
|
const memberSummaryFiles = {};
|
|
58947
59222
|
for (const entry of entries) {
|
|
@@ -59035,7 +59310,7 @@ function registerSuiteCommands(program3) {
|
|
|
59035
59310
|
try {
|
|
59036
59311
|
const io = suiteTrainIo(root);
|
|
59037
59312
|
retired = await retireFailedLeadRcLedger({ ledgerPath: releaseLedgerPath(root), rcHead: () => io.head(SUITE_REPO, "rc").then((h) => h ?? void 0), markAbandoned: io.markAbandoned }, o.ownerGo === true);
|
|
59038
|
-
if (retired && !(0,
|
|
59313
|
+
if (retired && !(0, import_node_fs50.existsSync)(io.recordPath)) {
|
|
59039
59314
|
console.log(o.json ? JSON.stringify({ retiredLeadRc: retired }, null, 2) : `suite abandon: retired the lead's failed, never-promoted rc candidate ${retired}; marked abandoned on origin (#7854)`);
|
|
59040
59315
|
return;
|
|
59041
59316
|
}
|
|
@@ -59229,7 +59504,7 @@ function suiteStatusDeps(github, registry2) {
|
|
|
59229
59504
|
|
|
59230
59505
|
// src/packages-adopt-export.ts
|
|
59231
59506
|
var import_node_child_process19 = require("node:child_process");
|
|
59232
|
-
var
|
|
59507
|
+
var import_node_fs51 = require("node:fs");
|
|
59233
59508
|
var import_node_os23 = require("node:os");
|
|
59234
59509
|
var import_node_path48 = require("node:path");
|
|
59235
59510
|
var import_node_util6 = require("node:util");
|
|
@@ -59853,36 +60128,36 @@ async function adoptExport(opts, deps) {
|
|
|
59853
60128
|
if (!/^sha256:[0-9a-f]{64}$/.test(opts.digest)) throw new Error("--digest must be sha256:<64 hex>, from the export run summary");
|
|
59854
60129
|
const packagePath = (0, import_node_path48.join)(opts.dir, "package.json");
|
|
59855
60130
|
const lockPath = (0, import_node_path48.join)(opts.dir, "package-lock.json");
|
|
59856
|
-
const packageText = (0,
|
|
59857
|
-
const lockText = (0,
|
|
59858
|
-
const work = (0,
|
|
60131
|
+
const packageText = (0, import_node_fs51.readFileSync)(packagePath, "utf8");
|
|
60132
|
+
const lockText = (0, import_node_fs51.readFileSync)(lockPath, "utf8");
|
|
60133
|
+
const work = (0, import_node_fs51.mkdtempSync)((0, import_node_path48.join)((0, import_node_os23.tmpdir)(), "mmi-adopt-export-"));
|
|
59859
60134
|
try {
|
|
59860
60135
|
const run = JSON.parse((await deps.ghApi(`repos/${EXPORT_REPO}/actions/runs/${opts.run}`)).toString("utf8"));
|
|
59861
|
-
(0,
|
|
59862
|
-
(0,
|
|
59863
|
-
(0,
|
|
60136
|
+
(0, import_node_fs51.writeFileSync)((0, import_node_path48.join)(work, "expected.json"), JSON.stringify(expectedFromRun(run, opts)));
|
|
60137
|
+
(0, import_node_fs51.writeFileSync)((0, import_node_path48.join)(work, "provider.json"), await deps.ghApi(`repos/${EXPORT_REPO}/actions/artifacts/${opts.artifact}`));
|
|
60138
|
+
(0, import_node_fs51.writeFileSync)((0, import_node_path48.join)(work, "artifact.zip"), await deps.ghApi(`repos/${EXPORT_REPO}/actions/artifacts/${opts.artifact}/zip`));
|
|
59864
60139
|
const script = (0, import_node_path48.join)(work, "package_export.py");
|
|
59865
|
-
(0,
|
|
60140
|
+
(0, import_node_fs51.writeFileSync)(script, package_export_default);
|
|
59866
60141
|
await deps.python(script, ["verify", work, opts.ds, opts.screens]);
|
|
59867
60142
|
const verified = (0, import_node_path48.join)(work, "verified");
|
|
59868
|
-
const metadata = Object.fromEntries(PACKAGES.map(({ name, short: short3 }) => [name, JSON.parse((0,
|
|
60143
|
+
const metadata = Object.fromEntries(PACKAGES.map(({ name, short: short3 }) => [name, JSON.parse((0, import_node_fs51.readFileSync)((0, import_node_path48.join)(verified, `${short3}.metadata.json`), "utf8"))]));
|
|
59869
60144
|
const plan = planAdoption(packageText, lockText, metadata);
|
|
59870
60145
|
for (const { name } of plan.result.adopted) {
|
|
59871
60146
|
const short3 = PACKAGES.find((p) => p.name === name).short;
|
|
59872
60147
|
await deps.npm(["cache", "add", (0, import_node_path48.join)(verified, `${short3}.tgz`)], opts.dir);
|
|
59873
60148
|
}
|
|
59874
|
-
(0,
|
|
59875
|
-
(0,
|
|
60149
|
+
(0, import_node_fs51.writeFileSync)(packagePath, plan.packageText);
|
|
60150
|
+
(0, import_node_fs51.writeFileSync)(lockPath, plan.lockText);
|
|
59876
60151
|
try {
|
|
59877
60152
|
await deps.npm(["ci", "--offline", "--ignore-scripts", "--no-audit", "--no-fund"], opts.dir);
|
|
59878
60153
|
} catch (error) {
|
|
59879
|
-
(0,
|
|
59880
|
-
(0,
|
|
60154
|
+
(0, import_node_fs51.writeFileSync)(packagePath, packageText);
|
|
60155
|
+
(0, import_node_fs51.writeFileSync)(lockPath, lockText);
|
|
59881
60156
|
throw new Error(`npm ci --offline failed, package.json and package-lock.json restored: ${error.message}`);
|
|
59882
60157
|
}
|
|
59883
60158
|
return plan.result;
|
|
59884
60159
|
} finally {
|
|
59885
|
-
(0,
|
|
60160
|
+
(0, import_node_fs51.rmSync)(work, { recursive: true, force: true });
|
|
59886
60161
|
}
|
|
59887
60162
|
}
|
|
59888
60163
|
var execFileBuffer = (0, import_node_util6.promisify)(import_node_child_process19.execFile);
|
|
@@ -59947,12 +60222,12 @@ function registerPackagesCommands(program3) {
|
|
|
59947
60222
|
init_cli_shared();
|
|
59948
60223
|
|
|
59949
60224
|
// src/wave-status.ts
|
|
59950
|
-
var
|
|
60225
|
+
var import_node_fs52 = require("node:fs");
|
|
59951
60226
|
function readStageSummary(worktreePath) {
|
|
59952
60227
|
const statePath = stageStatePath(worktreePath);
|
|
59953
|
-
if (!(0,
|
|
60228
|
+
if (!(0, import_node_fs52.existsSync)(statePath)) return void 0;
|
|
59954
60229
|
try {
|
|
59955
|
-
const state = JSON.parse((0,
|
|
60230
|
+
const state = JSON.parse((0, import_node_fs52.readFileSync)(statePath, "utf8"));
|
|
59956
60231
|
const port = typeof state.port === "number" ? state.port : void 0;
|
|
59957
60232
|
if (port == null || !Number.isInteger(port) || port <= 0) return void 0;
|
|
59958
60233
|
return { port, url: typeof state.url === "string" ? state.url : void 0 };
|
|
@@ -60009,7 +60284,7 @@ async function collectWaveStatus(deps) {
|
|
|
60009
60284
|
// src/discovery-commands.ts
|
|
60010
60285
|
init_github_client();
|
|
60011
60286
|
init_cli_shared();
|
|
60012
|
-
var
|
|
60287
|
+
var import_node_fs53 = require("node:fs");
|
|
60013
60288
|
var import_node_os24 = require("node:os");
|
|
60014
60289
|
var import_node_path49 = require("node:path");
|
|
60015
60290
|
var GC_GH_TIMEOUT_MS3 = 2e4;
|
|
@@ -60202,8 +60477,8 @@ async function collectOnboardStatus(opts = {}) {
|
|
|
60202
60477
|
}
|
|
60203
60478
|
const home = (0, import_node_os24.homedir)();
|
|
60204
60479
|
const plugin = onboardPluginGate({
|
|
60205
|
-
readKnown: () => readFileSyncSafe((0, import_node_path49.join)(home, ...KNOWN_MARKETPLACES_RELATIVE),
|
|
60206
|
-
readSettings: () => readFileSyncSafe((0, import_node_path49.join)(home, ".claude", "settings.json"),
|
|
60480
|
+
readKnown: () => readFileSyncSafe((0, import_node_path49.join)(home, ...KNOWN_MARKETPLACES_RELATIVE), import_node_fs53.readFileSync),
|
|
60481
|
+
readSettings: () => readFileSyncSafe((0, import_node_path49.join)(home, ".claude", "settings.json"), import_node_fs53.readFileSync)
|
|
60207
60482
|
});
|
|
60208
60483
|
return {
|
|
60209
60484
|
guide: "Read the MMI org-guide skill before project work; load only the relevant topic. Use mmi-help for a short introduction.",
|
|
@@ -60582,29 +60857,23 @@ var surfaces_default = {
|
|
|
60582
60857
|
]
|
|
60583
60858
|
},
|
|
60584
60859
|
{
|
|
60585
|
-
token: "
|
|
60586
|
-
displayName: "
|
|
60860
|
+
token: "openbot",
|
|
60861
|
+
displayName: "OpenBot",
|
|
60587
60862
|
lifecycle: "active",
|
|
60588
60863
|
artifactIds: [
|
|
60589
|
-
"mmi-
|
|
60864
|
+
"mmi-openbot-plugin",
|
|
60590
60865
|
"mmi-skills",
|
|
60591
60866
|
"mmi-cli"
|
|
60592
60867
|
],
|
|
60593
60868
|
plannedArtifactIds: [],
|
|
60594
60869
|
assembly: {
|
|
60595
|
-
mode: "
|
|
60596
|
-
rootPath: "
|
|
60597
|
-
sync: [
|
|
60598
|
-
{
|
|
60599
|
-
mode: "directories",
|
|
60600
|
-
sourcePath: "skills",
|
|
60601
|
-
targetPath: "packages/hermes-plugin/skills"
|
|
60602
|
-
}
|
|
60603
|
-
]
|
|
60870
|
+
mode: "source-tree",
|
|
60871
|
+
rootPath: ".pi-plugin",
|
|
60872
|
+
sync: []
|
|
60604
60873
|
},
|
|
60605
60874
|
install: {
|
|
60606
60875
|
mechanism: "npm",
|
|
60607
|
-
locator: "@mutmutco/
|
|
60876
|
+
locator: "@mutmutco/pi-plugin (npm transport; MMI-Hub extracts its skills into each OpenBot agent workspace at <home>/OpenBot/Agents/agent-<uuid>/.agents/skills)"
|
|
60608
60877
|
},
|
|
60609
60878
|
upgrade: {
|
|
60610
60879
|
mechanism: "package-manager",
|
|
@@ -60613,10 +60882,10 @@ var surfaces_default = {
|
|
|
60613
60882
|
skills: {
|
|
60614
60883
|
delivery: "provisioned",
|
|
60615
60884
|
sourceSurfaceId: "mmi-skills",
|
|
60616
|
-
artifactId: "mmi-
|
|
60885
|
+
artifactId: "mmi-skills",
|
|
60617
60886
|
invocation: {
|
|
60618
|
-
entry: "
|
|
60619
|
-
any: "
|
|
60887
|
+
entry: "use the epic skill",
|
|
60888
|
+
any: "use the <skill> skill"
|
|
60620
60889
|
}
|
|
60621
60890
|
},
|
|
60622
60891
|
cli: {
|
|
@@ -60629,20 +60898,19 @@ var surfaces_default = {
|
|
|
60629
60898
|
repair: "mmi-hub"
|
|
60630
60899
|
},
|
|
60631
60900
|
certification: {
|
|
60632
|
-
|
|
60633
|
-
|
|
60634
|
-
|
|
60635
|
-
]
|
|
60901
|
+
hostProbe: "none",
|
|
60902
|
+
hostCommand: null,
|
|
60903
|
+
versionArgs: []
|
|
60636
60904
|
},
|
|
60637
60905
|
compatibility: {
|
|
60638
|
-
hostVersion: "0.
|
|
60639
|
-
certification: "
|
|
60906
|
+
hostVersion: "0.53.2",
|
|
60907
|
+
certification: "OpenBot desktop 0.53.2 ships no host CLI: surface presence is the agent workspace root (<home>/OpenBot/Agents/agent-*), never a --version probe"
|
|
60640
60908
|
},
|
|
60641
60909
|
enforcementCeilings: [
|
|
60642
|
-
"
|
|
60643
|
-
"
|
|
60644
|
-
"
|
|
60645
|
-
"Every
|
|
60910
|
+
"OpenBot ships no MMI hooks: this surface contributes the provisioned skills and a standalone mmi-cli only; enforcement is host-side.",
|
|
60911
|
+
"OpenBot's agent registry (%APPDATA%/OpenBot/openbot.db) is event-sourced and host-owned: MMI-Hub never reads or writes it, and enumerates agent workspaces by directory instead.",
|
|
60912
|
+
"OpenBot agent shells inherit the user PATH, so the standalone mmi-cli resolves; MMI provisions the skill payload, never the shell environment. OpenBot exports no identifying variable: mmi-cli recognizes an OpenBot session by a working directory inside <home>/OpenBot/Agents/agent-<uuid> (or an explicit MMI_AGENT_SURFACE=openbot), and a session working elsewhere is classified by its runtime.",
|
|
60913
|
+
"Every Agent workspace holds its own skills tree: MMI-Hub writes only <workspace>/.agents/skills/<skill> folders it recorded in .agents/skills/.mmi-managed.json, and never .claude/skills, an unrecorded folder, or a skill it does not own."
|
|
60646
60914
|
]
|
|
60647
60915
|
}
|
|
60648
60916
|
],
|
|
@@ -61006,41 +61274,36 @@ var surfaces_default = {
|
|
|
61006
61274
|
publishVisibility: "public"
|
|
61007
61275
|
},
|
|
61008
61276
|
{
|
|
61009
|
-
id: "mmi-
|
|
61277
|
+
id: "mmi-openbot-plugin",
|
|
61010
61278
|
classification: "packaging",
|
|
61011
61279
|
kind: "plugin",
|
|
61012
|
-
ownerPath: "
|
|
61013
|
-
deliveryPath: "
|
|
61280
|
+
ownerPath: ".pi-plugin/package.json",
|
|
61281
|
+
deliveryPath: ".pi-plugin/package.json",
|
|
61014
61282
|
delivery: "npm",
|
|
61015
|
-
applicability: "
|
|
61283
|
+
applicability: "OpenBot desktop agent workspaces; the SAME npm package as JervCode (@mutmutco/pi-plugin, surfaceToken jervcode) re-declared as OpenBot's transport so the registry can name a token-owned plugin artifact for a host that has no CLI of its own. No OpenBot-specific package exists or is planned.",
|
|
61016
61284
|
versionCoordinated: true,
|
|
61017
|
-
surfaceToken: "
|
|
61285
|
+
surfaceToken: "openbot",
|
|
61018
61286
|
versionPaths: [
|
|
61019
61287
|
{
|
|
61020
|
-
path: "
|
|
61021
|
-
pointer: "version"
|
|
61022
|
-
},
|
|
61023
|
-
{
|
|
61024
|
-
path: "packages/hermes-plugin/plugin.yaml",
|
|
61288
|
+
path: ".pi-plugin/package.json",
|
|
61025
61289
|
pointer: "version"
|
|
61026
61290
|
}
|
|
61027
61291
|
],
|
|
61028
61292
|
additionalPaths: [
|
|
61029
|
-
"
|
|
61030
|
-
"packages/hermes-plugin/__init__.py",
|
|
61031
|
-
"packages/hermes-plugin/skills"
|
|
61293
|
+
".pi-plugin/skills"
|
|
61032
61294
|
],
|
|
61033
61295
|
artifactIdentity: {
|
|
61034
61296
|
kind: "npm-pack",
|
|
61035
|
-
packagePath: "
|
|
61297
|
+
packagePath: ".pi-plugin"
|
|
61036
61298
|
},
|
|
61037
|
-
publishVisibility: "
|
|
61299
|
+
publishVisibility: "n/a"
|
|
61038
61300
|
}
|
|
61039
61301
|
]
|
|
61040
61302
|
};
|
|
61041
61303
|
|
|
61042
61304
|
// src/surface-doctor.ts
|
|
61043
|
-
var TOKENS = /* @__PURE__ */ new Set(["claude", "codex", "jervcode", "
|
|
61305
|
+
var TOKENS = /* @__PURE__ */ new Set(["claude", "codex", "jervcode", "openbot"]);
|
|
61306
|
+
var FRESH_PROCESS_SURFACES = /* @__PURE__ */ new Set(["openbot"]);
|
|
61044
61307
|
function isActiveToken(value) {
|
|
61045
61308
|
return TOKENS.has(value);
|
|
61046
61309
|
}
|
|
@@ -61091,7 +61354,7 @@ function diagnoseSurface(evidence) {
|
|
|
61091
61354
|
return { ...base, state: "clean" };
|
|
61092
61355
|
}
|
|
61093
61356
|
function reloadInstruction(descriptor) {
|
|
61094
|
-
if (descriptor.token === "
|
|
61357
|
+
if (descriptor.token === "openbot") return "start a fresh OpenBot agent run";
|
|
61095
61358
|
const verb = descriptor.reload === "workspace" ? "reload" : "restart";
|
|
61096
61359
|
return `${verb} ${descriptor.displayName}`;
|
|
61097
61360
|
}
|
|
@@ -61103,7 +61366,7 @@ function planSurfaceRepair(diagnosis) {
|
|
|
61103
61366
|
supported: false,
|
|
61104
61367
|
owner: "mmi-hub",
|
|
61105
61368
|
changes: [],
|
|
61106
|
-
// #5833: name the SURFACE this row is about —
|
|
61369
|
+
// #5833: name the SURFACE this row is about — a hard-coded host name sent every other
|
|
61107
61370
|
// mmi-hub-owned surface (Claude Code included) chasing a different host that was already current.
|
|
61108
61371
|
instruction: `run \`mmi-hub update\` to converge the MMI-managed ${descriptor.displayName} plugin, then ${reloadInstruction(descriptor)}`
|
|
61109
61372
|
};
|
|
@@ -61131,9 +61394,9 @@ function buildSurfaceDoctorCheck(diagnosis) {
|
|
|
61131
61394
|
const detailByState = {
|
|
61132
61395
|
skipped: "skipped \u2014 host not active",
|
|
61133
61396
|
clean: diagnosis.repairDetail ? `clean \u2014 repaired and verified (${diagnosis.repairDetail})` : `clean${versions ? ` \u2014 ${versions}` : ""}`,
|
|
61134
|
-
"pending-reload": `pending ${descriptor.token
|
|
61397
|
+
"pending-reload": `pending ${FRESH_PROCESS_SURFACES.has(descriptor.token) ? "fresh process" : descriptor.reload === "workspace" ? "reload" : "restart"} \u2014 repaired to ${diagnosis.releasedVersion}, but ${diagnosis.installedVersion} is still the version this host has loaded${diagnosis.repairDetail ? ` (${diagnosis.repairDetail})` : ""}`,
|
|
61135
61398
|
// #5833: the update already staged the released tree; the wording mirrors `mmi-hub status`.
|
|
61136
|
-
staged: `loaded ${diagnosis.installedVersion}; staged ${diagnosis.releasedVersion} for next launch \u2014 ${descriptor.token
|
|
61399
|
+
staged: `loaded ${diagnosis.installedVersion}; staged ${diagnosis.releasedVersion} for next launch \u2014 ${FRESH_PROCESS_SURFACES.has(descriptor.token) ? "start a fresh process" : descriptor.reload === "workspace" ? "reload" : "restart"} ${descriptor.displayName} to converge`,
|
|
61137
61400
|
"freshness-unknown": `${diagnosis.installedVersion ?? "installed"} \u2014 freshness UNKNOWN, the published version could not be read`,
|
|
61138
61401
|
stale: `stale${versions ? ` \u2014 ${versions}` : ""}`,
|
|
61139
61402
|
missing: "missing \u2014 no install record",
|
|
@@ -62405,7 +62668,7 @@ function registerOrgHealthQuery(program3, deps = defaultOrgHealthQueryDeps()) {
|
|
|
62405
62668
|
}
|
|
62406
62669
|
|
|
62407
62670
|
// src/api-read.ts
|
|
62408
|
-
var
|
|
62671
|
+
var import_node_fs54 = require("node:fs");
|
|
62409
62672
|
|
|
62410
62673
|
// src/run-log-read.ts
|
|
62411
62674
|
init_cli_shared();
|
|
@@ -62679,8 +62942,17 @@ function buildRunWatchSpec(id, o) {
|
|
|
62679
62942
|
const timeoutMin = o.timeoutMin !== void 0 ? parseBrokerInt(o.timeoutMin, "--timeout", 1, 120) : 30;
|
|
62680
62943
|
return { program: "gh", args, timeoutMs: timeoutMin * 6e4 };
|
|
62681
62944
|
}
|
|
62945
|
+
function rejectPrViewQueueFields(fields, number) {
|
|
62946
|
+
if (!fields || !fields.split(",").includes("mergeQueueEntry")) return;
|
|
62947
|
+
throw new Error(
|
|
62948
|
+
`read pr view: 'mergeQueueEntry' is not a gh pr view --json field \u2014 read the merge queue with \`mmi-cli devops read pr queue ${number} --repo <owner/repo> --json\` and drop it from --json`
|
|
62949
|
+
);
|
|
62950
|
+
}
|
|
62682
62951
|
function buildPrViewSpec(ref, o) {
|
|
62683
|
-
const
|
|
62952
|
+
const number = parseBrokerId(ref, "PR number");
|
|
62953
|
+
const jsonFields = parseBrokerJsonFields(o.json);
|
|
62954
|
+
rejectPrViewQueueFields(jsonFields, number);
|
|
62955
|
+
const args = withJson(withRepo(["pr", "view", number], parseBrokerRepo(o.repo, "--repo")), jsonFields, o.json);
|
|
62684
62956
|
if (o.comments) args.push("--comments");
|
|
62685
62957
|
return { program: "gh", args, timeoutMs: SINGLE_SHOT_TIMEOUT_MS };
|
|
62686
62958
|
}
|
|
@@ -62753,6 +63025,20 @@ function ghExitCodeOf(error) {
|
|
|
62753
63025
|
const code = error.code;
|
|
62754
63026
|
return typeof code === "number" ? code : null;
|
|
62755
63027
|
}
|
|
63028
|
+
function brokerGhFailureHint(failureText, args) {
|
|
63029
|
+
const target = args[0] === "pr" && args[1] === "view" ? args[2] : void 0;
|
|
63030
|
+
const unknownField = /Unknown JSON field: "([^"]+)"/.exec(failureText)?.[1] ?? /Unknown JSON field: ([A-Za-z][A-Za-z0-9]*)/.exec(failureText)?.[1];
|
|
63031
|
+
if (unknownField === "mergeQueueEntry") {
|
|
63032
|
+
return `'mergeQueueEntry' is not a gh pr view --json field \u2014 read the merge queue with \`mmi-cli devops read pr queue ${target ?? "<number>"} --repo <owner/repo> --json\` and drop it from --json; repeating this read cannot succeed`;
|
|
63033
|
+
}
|
|
63034
|
+
if (unknownField) {
|
|
63035
|
+
return `gh rejected --json field '${unknownField}' \u2014 drop it or use the typed read whose fields cover it (the "Available fields" list gh printed is its own vocabulary)`;
|
|
63036
|
+
}
|
|
63037
|
+
if (/Could not resolve to a PullRequest/i.test(failureText)) {
|
|
63038
|
+
return `GitHub could not resolve the PR${target ? ` #${target}` : ""} \u2014 verify the number and that --repo names the repo that owns it before retrying; an identical re-run fails the same way`;
|
|
63039
|
+
}
|
|
63040
|
+
return void 0;
|
|
63041
|
+
}
|
|
62756
63042
|
async function runBrokerGh(run, spec, write = (chunk) => process.stdout.write(chunk)) {
|
|
62757
63043
|
const env = brokerChildEnv();
|
|
62758
63044
|
let output;
|
|
@@ -62764,7 +63050,9 @@ async function runBrokerGh(run, spec, write = (chunk) => process.stdout.write(ch
|
|
|
62764
63050
|
const code = ghExitCodeOf(e);
|
|
62765
63051
|
if (code !== null) {
|
|
62766
63052
|
if (typeof err.stderr === "string" && err.stderr) process.stderr.write(redactPrivateCredential(err.stderr, env));
|
|
62767
|
-
|
|
63053
|
+
const failure = `${typeof err.stdout === "string" ? err.stdout : ""}${typeof err.stderr === "string" ? err.stderr : ""}`;
|
|
63054
|
+
const recovery = brokerGhFailureHint(failure, spec.args) ?? "inspect stderr; retry the same typed read and only parse stdout after success";
|
|
63055
|
+
process.stderr.write(`read: gh ${spec.args.slice(0, 2).join(" ")} failed on the App pool (exit ${code}) \u2014 ${recovery}
|
|
62768
63056
|
`);
|
|
62769
63057
|
return code;
|
|
62770
63058
|
}
|
|
@@ -63068,11 +63356,33 @@ var RELEASE_TAG_RE = new RegExp(`^repos/${NAME}/${NAME}/releases/tags/(${NAME})$
|
|
|
63068
63356
|
var RELEASE_ASSET_RE = new RegExp(`^repos/${NAME}/${NAME}/releases/assets/\\d+$`);
|
|
63069
63357
|
var API_GET_ALLOWED_SHAPES = "repos/{owner}/{repo}/pulls/{n}/files, repos/{owner}/{repo}/compare/{a}...{b}, repos/{owner}/{repo}/contents/{path}[?ref=<branch>], repos/{owner}/{repo}/actions/runs[?per_page=1-100&page=<n>&status=&branch=&event=&head_sha=<40-hex>&created=<date-range>&actor=, each key at most once], repos/{owner}/{repo}/actions/runs/{id}, repos/{owner}/{repo}/actions/runs/{id}/jobs, repos/{owner}/{repo}/actions/runs/{id}/attempts/{n}/jobs, repos/{owner}/{repo}/actions/runs/{id}/artifacts, repos/{owner}/{repo}/actions/artifacts/{id}/zip (needs --out <file>), repos/{owner}/{repo}/actions/jobs/{id}/logs (needs --out <file>), repos/{owner}/{repo}/releases[?per_page=1-100&page=<n>, each key at most once], repos/{owner}/{repo}/releases/latest, repos/{owner}/{repo}/releases/tags/{tag}, repos/{owner}/{repo}/releases/assets/{id} (needs --out <file>)";
|
|
63070
63358
|
var API_GET_RECOVERY = " No other GET read exists \u2014 do not retry path variants; use a dedicated mmi-cli subcommand or ask a human token holder. This refusal exits 1 on stderr with nothing on stdout: check the exit code and never merge stderr into stdout (2>&1) before JSON-parsing.";
|
|
63359
|
+
var TYPED_READ_ROUTE_RE = new RegExp(`^repos/(${NAME})/(${NAME})/(issues|pulls)(?:/(comments/\\d+|\\d+)(?:/([a-z_]+))?)?$`);
|
|
63360
|
+
function typedReadHint(path2) {
|
|
63361
|
+
const match = TYPED_READ_ROUTE_RE.exec(path2.split("?")[0]);
|
|
63362
|
+
if (!match) return "";
|
|
63363
|
+
const [, owner, repo, kind, item, sub] = match;
|
|
63364
|
+
const slug = `${owner}/${repo}`;
|
|
63365
|
+
const say = (command, note = "") => ` Typed read for this route: \`${command}\`${note}.`;
|
|
63366
|
+
if (kind === "issues") {
|
|
63367
|
+
if (!item) return say(`mmi-cli devops read issue list --repo ${slug} --json number,title,state,labels`);
|
|
63368
|
+
if (item.startsWith("comments/")) {
|
|
63369
|
+
const id = item.slice("comments/".length);
|
|
63370
|
+
return say(`mmi-cli devops read issue view <issue> --repo ${slug} --json comments`, `; a comment is read through its issue, so match the comment whose url ends in issuecomment-${id}`);
|
|
63371
|
+
}
|
|
63372
|
+
if (sub === "events" || sub === "timeline") {
|
|
63373
|
+
return say(`mmi-cli devops read issue view ${item} --repo ${slug} --json state,stateReason,closedAt,labels,assignees,comments`, "; the raw event/timeline history itself has no read");
|
|
63374
|
+
}
|
|
63375
|
+
return say(`mmi-cli devops read issue view ${item} --repo ${slug} --json title,body,state,labels,comments`);
|
|
63376
|
+
}
|
|
63377
|
+
if (!item) return say(`mmi-cli devops read pr list --repo ${slug} --json number,title,state,headRefName`);
|
|
63378
|
+
if (item.startsWith("comments/")) return say(`mmi-cli devops read pr view <pr> --repo ${slug} --json comments,reviews`);
|
|
63379
|
+
return say(`mmi-cli devops read pr view ${item} --repo ${slug} --json title,state,comments,reviews,commits,statusCheckRollup`);
|
|
63380
|
+
}
|
|
63071
63381
|
function parseApiGetPath(rawPath) {
|
|
63072
63382
|
const path2 = rawPath.trim();
|
|
63073
63383
|
const refuse = () => {
|
|
63074
63384
|
const hint = /^[A-Za-z]:[/\\]/.test(path2) ? " Git Bash/MSYS may have rewritten a leading-slash argument; retry with MSYS_NO_PATHCONV=1 and the slash-free repos/... path. The endpoint must still be allowlisted." : "";
|
|
63075
|
-
return new Error(`api get: refusing '${rawPath}' \u2014 only these GET reads are allowed (#7219): ${API_GET_ALLOWED_SHAPES}.${API_GET_RECOVERY}${hint}`);
|
|
63385
|
+
return new Error(`api get: refusing '${rawPath}' \u2014 only these GET reads are allowed (#7219): ${API_GET_ALLOWED_SHAPES}.${API_GET_RECOVERY}${typedReadHint(path2)}${hint}`);
|
|
63076
63386
|
};
|
|
63077
63387
|
if (/[%#\\]/.test(path2) || path2.split("?")[0].split("/").some((seg) => seg === "." || seg === ".." || seg === "")) throw refuse();
|
|
63078
63388
|
const q = path2.indexOf("?");
|
|
@@ -63138,7 +63448,7 @@ function registerApiReadCommands(program3, overrides) {
|
|
|
63138
63448
|
o.accept
|
|
63139
63449
|
);
|
|
63140
63450
|
if (zip) {
|
|
63141
|
-
(0,
|
|
63451
|
+
(0, import_node_fs54.writeFileSync)(o.out, body, "latin1");
|
|
63142
63452
|
return;
|
|
63143
63453
|
}
|
|
63144
63454
|
} catch (e) {
|
|
@@ -64032,7 +64342,7 @@ function registerSessionReport(program3) {
|
|
|
64032
64342
|
}
|
|
64033
64343
|
|
|
64034
64344
|
// src/stage-commands.ts
|
|
64035
|
-
var
|
|
64345
|
+
var import_node_fs55 = require("node:fs");
|
|
64036
64346
|
var import_node_path50 = require("node:path");
|
|
64037
64347
|
init_cli_shared();
|
|
64038
64348
|
init_clean_exit();
|
|
@@ -64281,8 +64591,8 @@ function registerStageCommands(program3) {
|
|
|
64281
64591
|
const repo = Array.isArray(project2?.repos) && typeof project2.repos[0] === "string" ? project2.repos[0] : slug;
|
|
64282
64592
|
const resolution = decideStage({
|
|
64283
64593
|
registry: { deployModel: project2?.deployModel, portRange, error: read.ok ? void 0 : read.error },
|
|
64284
|
-
hasCompose: (0,
|
|
64285
|
-
hasEnvExample: (0,
|
|
64594
|
+
hasCompose: (0, import_node_fs55.existsSync)((0, import_node_path50.join)(process.cwd(), "docker-compose.yml")),
|
|
64595
|
+
hasEnvExample: (0, import_node_fs55.existsSync)((0, import_node_path50.join)(process.cwd(), ".env.example")),
|
|
64286
64596
|
repo
|
|
64287
64597
|
});
|
|
64288
64598
|
if (resolution.source === "derived" && resolution.derived) {
|
|
@@ -64575,14 +64885,14 @@ vercel preview (${preview.branch}): ${d.state}${d.url ? ` \u2014 ${d.url}` : ""}
|
|
|
64575
64885
|
|
|
64576
64886
|
// src/tenant-artifact.ts
|
|
64577
64887
|
var import_node_crypto17 = require("node:crypto");
|
|
64578
|
-
var
|
|
64888
|
+
var import_node_fs56 = require("node:fs");
|
|
64579
64889
|
var import_promises11 = require("node:fs/promises");
|
|
64580
64890
|
var import_node_path51 = require("node:path");
|
|
64581
64891
|
var ARTIFACT_ID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
|
|
64582
64892
|
var MAX_BYTES2 = 5 * 1024 * 1024 * 1024;
|
|
64583
64893
|
async function sha256File(path2) {
|
|
64584
64894
|
const hash = (0, import_node_crypto17.createHash)("sha256");
|
|
64585
|
-
for await (const chunk of (0,
|
|
64895
|
+
for await (const chunk of (0, import_node_fs56.createReadStream)(path2)) hash.update(chunk);
|
|
64586
64896
|
return hash.digest("hex");
|
|
64587
64897
|
}
|
|
64588
64898
|
async function putTenantArtifact(repo, stage, inputPath, deps) {
|
|
@@ -64605,7 +64915,7 @@ async function putTenantArtifact(repo, stage, inputPath, deps) {
|
|
|
64605
64915
|
return [key, value];
|
|
64606
64916
|
}));
|
|
64607
64917
|
headers["content-length"] = String(info.size);
|
|
64608
|
-
const stream = (0,
|
|
64918
|
+
const stream = (0, import_node_fs56.createReadStream)(path2);
|
|
64609
64919
|
let uploaded;
|
|
64610
64920
|
try {
|
|
64611
64921
|
uploaded = await fetch(body.uploadUrl, {
|
|
@@ -64746,7 +65056,7 @@ function renderVerifySecrets(body) {
|
|
|
64746
65056
|
}
|
|
64747
65057
|
|
|
64748
65058
|
// src/command-register-developer.ts
|
|
64749
|
-
var
|
|
65059
|
+
var import_node_fs60 = require("node:fs");
|
|
64750
65060
|
init_clean_exit();
|
|
64751
65061
|
init_cli_shared();
|
|
64752
65062
|
init_error_codes();
|
|
@@ -64814,13 +65124,13 @@ async function executeWaveLand(plan, deps) {
|
|
|
64814
65124
|
// src/dist-drift.ts
|
|
64815
65125
|
var import_node_child_process21 = require("node:child_process");
|
|
64816
65126
|
var import_node_crypto19 = require("node:crypto");
|
|
64817
|
-
var
|
|
65127
|
+
var import_node_fs58 = require("node:fs");
|
|
64818
65128
|
var import_node_os25 = require("node:os");
|
|
64819
65129
|
var import_node_path53 = require("node:path");
|
|
64820
65130
|
|
|
64821
65131
|
// ../scripts/distribution-digest.mjs
|
|
64822
65132
|
var import_node_crypto18 = require("node:crypto");
|
|
64823
|
-
var
|
|
65133
|
+
var import_node_fs57 = require("node:fs");
|
|
64824
65134
|
var import_node_path52 = require("node:path");
|
|
64825
65135
|
var slash = (value) => value.replaceAll("\\", "/");
|
|
64826
65136
|
function repoPath(root, declaredPath, label) {
|
|
@@ -64834,7 +65144,7 @@ function repoPath(root, declaredPath, label) {
|
|
|
64834
65144
|
function digestFiles(files) {
|
|
64835
65145
|
const hash = (0, import_node_crypto18.createHash)("sha256");
|
|
64836
65146
|
for (const file of [...files].sort((a, b) => a.relative.localeCompare(b.relative))) {
|
|
64837
|
-
const content = file.stat.isSymbolicLink() ? Buffer.from((0,
|
|
65147
|
+
const content = file.stat.isSymbolicLink() ? Buffer.from((0, import_node_fs57.readlinkSync)(file.absolute), "utf8") : (0, import_node_fs57.readFileSync)(file.absolute);
|
|
64838
65148
|
hash.update(file.relative, "utf8");
|
|
64839
65149
|
hash.update("\0");
|
|
64840
65150
|
hash.update(file.stat.isSymbolicLink() ? "symlink" : "file", "utf8");
|
|
@@ -64849,8 +65159,8 @@ function digestFiles(files) {
|
|
|
64849
65159
|
function digestPackedFiles(packageRoot, packedFiles) {
|
|
64850
65160
|
return digestFiles(packedFiles.map((path2) => {
|
|
64851
65161
|
const absolute = repoPath(packageRoot, path2, "packed artifact identity path");
|
|
64852
|
-
if (!(0,
|
|
64853
|
-
return { absolute, relative: slash(path2), stat: (0,
|
|
65162
|
+
if (!(0, import_node_fs57.existsSync)(absolute)) throw new Error(`packed artifact identity path ${path2} does not exist`);
|
|
65163
|
+
return { absolute, relative: slash(path2), stat: (0, import_node_fs57.lstatSync)(absolute) };
|
|
64854
65164
|
}));
|
|
64855
65165
|
}
|
|
64856
65166
|
|
|
@@ -64935,12 +65245,12 @@ function renderDistDriftReceipt(receipt) {
|
|
|
64935
65245
|
return lines2;
|
|
64936
65246
|
}
|
|
64937
65247
|
function readOrNull(path2) {
|
|
64938
|
-
return (0,
|
|
65248
|
+
return (0, import_node_fs58.existsSync)(path2) ? (0, import_node_fs58.readFileSync)(path2) : null;
|
|
64939
65249
|
}
|
|
64940
65250
|
function walkFiles(root) {
|
|
64941
65251
|
const files = [];
|
|
64942
65252
|
const walk2 = (directory) => {
|
|
64943
|
-
for (const entry of (0,
|
|
65253
|
+
for (const entry of (0, import_node_fs58.readdirSync)(directory, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
|
|
64944
65254
|
const child2 = (0, import_node_path53.join)(directory, entry.name);
|
|
64945
65255
|
if (entry.isDirectory()) walk2(child2);
|
|
64946
65256
|
else files.push(child2);
|
|
@@ -64951,7 +65261,7 @@ function walkFiles(root) {
|
|
|
64951
65261
|
}
|
|
64952
65262
|
function bomPathFor(root) {
|
|
64953
65263
|
try {
|
|
64954
|
-
const registry2 = JSON.parse((0,
|
|
65264
|
+
const registry2 = JSON.parse((0, import_node_fs58.readFileSync)((0, import_node_path53.join)(root, "surfaces.json"), "utf8"));
|
|
64955
65265
|
return (0, import_node_path53.join)(root, registry2?.sharedAgentCore?.releaseMetadata?.bomPath ?? "distribution-bom.json");
|
|
64956
65266
|
} catch {
|
|
64957
65267
|
return (0, import_node_path53.join)(root, "distribution-bom.json");
|
|
@@ -64967,7 +65277,7 @@ function rebuildTo(packageRoot, outDir) {
|
|
|
64967
65277
|
});
|
|
64968
65278
|
}
|
|
64969
65279
|
function runDistStatus(root) {
|
|
64970
|
-
const stage = (0,
|
|
65280
|
+
const stage = (0, import_node_fs58.mkdtempSync)((0, import_node_path53.join)((0, import_node_os25.tmpdir)(), "mmi-dist-drift-"));
|
|
64971
65281
|
let overlayCount = 0;
|
|
64972
65282
|
try {
|
|
64973
65283
|
const cliOut = (0, import_node_path53.join)(stage, "cli-dist");
|
|
@@ -64983,19 +65293,19 @@ function runDistStatus(root) {
|
|
|
64983
65293
|
const tree = (path2) => readOrNull((0, import_node_path53.join)(root, path2));
|
|
64984
65294
|
const distRoot = (0, import_node_path53.join)(root, "cli", "dist");
|
|
64985
65295
|
const distTree = () => walkFiles(distRoot).map((absolute) => `cli/dist/${(0, import_node_path53.relative)(distRoot, absolute).replaceAll("\\", "/")}`);
|
|
64986
|
-
const bom = JSON.parse((0,
|
|
65296
|
+
const bom = JSON.parse((0, import_node_fs58.readFileSync)(bomPathFor(root), "utf8"));
|
|
64987
65297
|
const digest = (entries) => {
|
|
64988
65298
|
const overlay = (0, import_node_path53.join)(stage, `overlay-${overlayCount++}`);
|
|
64989
65299
|
for (const entry of entries) {
|
|
64990
65300
|
const target = (0, import_node_path53.join)(overlay, entry.path);
|
|
64991
|
-
(0,
|
|
64992
|
-
(0,
|
|
65301
|
+
(0, import_node_fs58.mkdirSync)((0, import_node_path53.dirname)(target), { recursive: true });
|
|
65302
|
+
(0, import_node_fs58.writeFileSync)(target, entry.bytes);
|
|
64993
65303
|
}
|
|
64994
65304
|
return digestPackedFiles(overlay, entries.map((entry) => entry.path));
|
|
64995
65305
|
};
|
|
64996
65306
|
return computeDistDriftReceipt({ committed, tree, rebuilt, distTree, bom, digest });
|
|
64997
65307
|
} finally {
|
|
64998
|
-
(0,
|
|
65308
|
+
(0, import_node_fs58.rmSync)(stage, { recursive: true, force: true });
|
|
64999
65309
|
}
|
|
65000
65310
|
}
|
|
65001
65311
|
|
|
@@ -65327,7 +65637,7 @@ function registerSchedulesLiftCommand(program3, deps = {}) {
|
|
|
65327
65637
|
|
|
65328
65638
|
// src/spawn-policy-core.ts
|
|
65329
65639
|
var import_node_child_process23 = require("node:child_process");
|
|
65330
|
-
var
|
|
65640
|
+
var import_node_fs59 = require("node:fs");
|
|
65331
65641
|
var import_node_path56 = require("node:path");
|
|
65332
65642
|
var SPAWNERS = ["spawn", "spawnSync", "exec", "execSync", "execFile", "execFileSync"];
|
|
65333
65643
|
var CALL_SOURCE = String.raw`(^|[^.\w$])(${SPAWNERS.join("|")})\s*\(`;
|
|
@@ -65414,7 +65724,7 @@ function runSpawnPolicy(root) {
|
|
|
65414
65724
|
for (const file of files) {
|
|
65415
65725
|
let raw;
|
|
65416
65726
|
try {
|
|
65417
|
-
raw = (0,
|
|
65727
|
+
raw = (0, import_node_fs59.readFileSync)((0, import_node_path56.join)(root, file), "utf8");
|
|
65418
65728
|
} catch {
|
|
65419
65729
|
continue;
|
|
65420
65730
|
}
|
|
@@ -65435,18 +65745,18 @@ function registerDeveloperCommands(program3) {
|
|
|
65435
65745
|
const rules = program3.command("rules").description("org-managed .gitignore delivery");
|
|
65436
65746
|
rules.command("gitignore").option("--write", "upsert the managed block into .gitignore (default: check only, non-zero exit on drift)").option("--json", "machine-readable output").description("verify (or --write) this repo's org-managed .gitignore block matches the SSOT").action((opts) => {
|
|
65437
65747
|
const path2 = (0, import_node_path57.join)(process.cwd(), ".gitignore");
|
|
65438
|
-
const current = (0,
|
|
65748
|
+
const current = (0, import_node_fs60.existsSync)(path2) ? (0, import_node_fs60.readFileSync)(path2, "utf8") : null;
|
|
65439
65749
|
const plan = planManagedGitignore(current);
|
|
65440
65750
|
const drift = [...plan.added.map((l) => `+${l}`), ...plan.removed.map((l) => `-${l}`)].join(", ") || "block normalize";
|
|
65441
65751
|
if (opts.json) {
|
|
65442
|
-
if (opts.write && plan.changed) (0,
|
|
65752
|
+
if (opts.write && plan.changed) (0, import_node_fs60.writeFileSync)(path2, plan.content, "utf8");
|
|
65443
65753
|
console.log(JSON.stringify(plan, null, 2));
|
|
65444
65754
|
if (!opts.write && plan.changed) process.exitCode = 1;
|
|
65445
65755
|
return;
|
|
65446
65756
|
}
|
|
65447
65757
|
if (opts.write) {
|
|
65448
65758
|
if (plan.changed) {
|
|
65449
|
-
(0,
|
|
65759
|
+
(0, import_node_fs60.writeFileSync)(path2, plan.content, "utf8");
|
|
65450
65760
|
console.log(`mmi-cli devops org rules gitignore: updated .gitignore (${drift})`);
|
|
65451
65761
|
} else {
|
|
65452
65762
|
console.log("mmi-cli devops org rules gitignore: up to date");
|
|
@@ -65773,22 +66083,23 @@ function mmiDoctorDeps(opts = {}) {
|
|
|
65773
66083
|
if (!token || token === "opencode") return void 0;
|
|
65774
66084
|
const descriptor = doctorSurface(token);
|
|
65775
66085
|
const snapshot = snapshotPluginGuardInput(runtimeSurface, isOrgRepo);
|
|
65776
|
-
const
|
|
65777
|
-
const installedVersion =
|
|
66086
|
+
const native = token === "openbot" ? readOpenBotPluginEvidence(process.env) : void 0;
|
|
66087
|
+
const installedVersion = native?.installedVersion ?? installedSurfacePluginVersion(runtimeSurface);
|
|
65778
66088
|
const stagedVersion = token === "claude" ? stagedClaudePluginVersion() : void 0;
|
|
65779
66089
|
surfaceEvidence = {
|
|
65780
66090
|
descriptor,
|
|
65781
|
-
//
|
|
65782
|
-
// absent root is skipped. Other mature surfaces retain their established org/install
|
|
65783
|
-
|
|
65784
|
-
|
|
65785
|
-
|
|
65786
|
-
|
|
65787
|
-
|
|
66091
|
+
// A native evidence reader IS the host evidence: a configured but uninstalled MMI tree is missing,
|
|
66092
|
+
// while an absent root is skipped. Other mature surfaces retain their established org/install
|
|
66093
|
+
// applicability.
|
|
66094
|
+
applicable: native ? native.hostPresent : isOrgRepo || snapshot.installRecordPresent || snapshot.pluginCachePresent,
|
|
66095
|
+
installRecordPresent: native?.installRecordPresent ?? snapshot.installRecordPresent,
|
|
66096
|
+
deliveryPresent: native ? native.installRecordPresent : snapshot.marketplaceClonePresent,
|
|
66097
|
+
payloadPresent: native?.payloadPresent ?? snapshot.pluginCachePresent,
|
|
66098
|
+
manifest: native?.manifest ?? (installedVersion ? "valid" : snapshot.installRecordPresent ? "invalid" : "missing"),
|
|
65788
66099
|
guardState: buildPluginGuardDecision(snapshot).state,
|
|
65789
66100
|
...installedVersion ? { installedVersion } : {},
|
|
65790
66101
|
...stagedVersion ? { stagedVersion } : {},
|
|
65791
|
-
...
|
|
66102
|
+
...native?.receipt ? { receipt: native.receipt } : {}
|
|
65792
66103
|
};
|
|
65793
66104
|
return surfaceEvidence;
|
|
65794
66105
|
};
|
|
@@ -65864,7 +66175,7 @@ function mmiDoctorDeps(opts = {}) {
|
|
|
65864
66175
|
);
|
|
65865
66176
|
const result = applyPluginCachePlan(
|
|
65866
66177
|
plan,
|
|
65867
|
-
(p) => (0,
|
|
66178
|
+
(p) => (0, import_node_fs61.rmSync)(p, { recursive: true }),
|
|
65868
66179
|
stagingApplyFsGuard(configRoot)
|
|
65869
66180
|
);
|
|
65870
66181
|
return {
|
|
@@ -65899,7 +66210,7 @@ function mmiDoctorDeps(opts = {}) {
|
|
|
65899
66210
|
// has no generated routing index would
|
|
65900
66211
|
// get a permanent — demanding an artifact it never asked for.
|
|
65901
66212
|
docsIndexState: (root) => {
|
|
65902
|
-
if (!(0,
|
|
66213
|
+
if (!(0, import_node_fs61.existsSync)((0, import_node_path58.join)(root, DOCS_INDEX_PATH))) return void 0;
|
|
65903
66214
|
const real = createDocsIndexDeps(root);
|
|
65904
66215
|
let docs;
|
|
65905
66216
|
const listDocs = () => docs ??= real.listDocs();
|
|
@@ -65908,7 +66219,7 @@ function mmiDoctorDeps(opts = {}) {
|
|
|
65908
66219
|
},
|
|
65909
66220
|
// #4168: working-tree heal — write if drifted, then re-check. Commit remains the operator's step.
|
|
65910
66221
|
healDocsIndex: (root) => {
|
|
65911
|
-
if (!(0,
|
|
66222
|
+
if (!(0, import_node_fs61.existsSync)((0, import_node_path58.join)(root, DOCS_INDEX_PATH))) return { drift: false, docCount: 0 };
|
|
65912
66223
|
const real = createDocsIndexDeps(root);
|
|
65913
66224
|
let docs;
|
|
65914
66225
|
const listDocs = () => docs ??= real.listDocs();
|
|
@@ -66663,7 +66974,7 @@ project.command("set [owner/repo]").description("upsert project META (idempotent
|
|
|
66663
66974
|
if (dupe) return fail(`org project set: KEY "${dupe}" was passed to both --var and --set; --set is an alias of --var, so pass each KEY once`);
|
|
66664
66975
|
if (o.secretsFile) {
|
|
66665
66976
|
try {
|
|
66666
|
-
vars.push(`secrets=${(0,
|
|
66977
|
+
vars.push(`secrets=${(0, import_node_fs61.readFileSync)(o.secretsFile, "utf8")}`);
|
|
66667
66978
|
} catch (e) {
|
|
66668
66979
|
return fail(`org project set: cannot read --secrets-file ${o.secretsFile}: ${e.message}`);
|
|
66669
66980
|
}
|
|
@@ -67056,15 +67367,15 @@ function ciAuditDeps2() {
|
|
|
67056
67367
|
readSeedFile: (path2) => {
|
|
67057
67368
|
if (!root) return null;
|
|
67058
67369
|
const fullPath = (0, import_node_path58.join)(root, path2);
|
|
67059
|
-
return (0,
|
|
67370
|
+
return (0, import_node_fs61.existsSync)(fullPath) ? (0, import_node_fs61.readFileSync)(fullPath, "utf8") : null;
|
|
67060
67371
|
}
|
|
67061
67372
|
};
|
|
67062
67373
|
}
|
|
67063
67374
|
function hubRoot2() {
|
|
67064
67375
|
const fromPkg = (0, import_node_path58.join)(__dirname, "..", "..");
|
|
67065
67376
|
const marker = "skills/bootstrap/seeds/manifest.json";
|
|
67066
|
-
if ((0,
|
|
67067
|
-
if ((0,
|
|
67377
|
+
if ((0, import_node_fs61.existsSync)((0, import_node_path58.join)(fromPkg, marker))) return fromPkg;
|
|
67378
|
+
if ((0, import_node_fs61.existsSync)((0, import_node_path58.join)(process.cwd(), marker))) return process.cwd();
|
|
67068
67379
|
return null;
|
|
67069
67380
|
}
|
|
67070
67381
|
registerQueryCommands(program2);
|
|
@@ -67195,10 +67506,10 @@ access.command("audit").description("audit collaborator roles + train-branch pus
|
|
|
67195
67506
|
targets = resolution.targets;
|
|
67196
67507
|
}
|
|
67197
67508
|
const derivedMatrix = registryProjects ? accessMatrixFromProjects(registryProjects) : {};
|
|
67198
|
-
const fileMatrix = (0,
|
|
67509
|
+
const fileMatrix = (0, import_node_fs61.existsSync)("access-matrix.json") ? loadAccessMatrix((0, import_node_fs61.readFileSync)("access-matrix.json", "utf8")) : {};
|
|
67199
67510
|
const matrix = mergeAccessMatrix(fileMatrix, derivedMatrix);
|
|
67200
67511
|
const dataAccess = registryProjects ? dataAccessContractsFromProjects(registryProjects) : { consumers: {} };
|
|
67201
|
-
const sanctioned = (0,
|
|
67512
|
+
const sanctioned = (0, import_node_fs61.existsSync)("access-matrix.json") ? loadSanctionedAdmins((0, import_node_fs61.readFileSync)("access-matrix.json", "utf8")) : {};
|
|
67202
67513
|
const report = await auditOrgAccess(targets, deps, matrix, dataAccess, sanctioned);
|
|
67203
67514
|
console.log(o.json ? JSON.stringify(report, null, 2) : renderAccessReport(report));
|
|
67204
67515
|
if (!report.ok) process.exitCode = 1;
|
|
@@ -67229,7 +67540,7 @@ function directoryBytes(path2) {
|
|
|
67229
67540
|
let total = 0;
|
|
67230
67541
|
let entries;
|
|
67231
67542
|
try {
|
|
67232
|
-
entries = (0,
|
|
67543
|
+
entries = (0, import_node_fs61.readdirSync)(path2, { withFileTypes: true });
|
|
67233
67544
|
} catch {
|
|
67234
67545
|
return 0;
|
|
67235
67546
|
}
|
|
@@ -67238,7 +67549,7 @@ function directoryBytes(path2) {
|
|
|
67238
67549
|
if (entry.isDirectory()) total += directoryBytes(child2);
|
|
67239
67550
|
else {
|
|
67240
67551
|
try {
|
|
67241
|
-
total += (0,
|
|
67552
|
+
total += (0, import_node_fs61.statSync)(child2).size;
|
|
67242
67553
|
} catch {
|
|
67243
67554
|
}
|
|
67244
67555
|
}
|
|
@@ -67246,25 +67557,25 @@ function directoryBytes(path2) {
|
|
|
67246
67557
|
return total;
|
|
67247
67558
|
}
|
|
67248
67559
|
function listDirEntries(dir) {
|
|
67249
|
-
return (0,
|
|
67560
|
+
return (0, import_node_fs61.readdirSync)(dir, { withFileTypes: true }).map((d) => ({ name: d.name, isDirectory: d.isDirectory() }));
|
|
67250
67561
|
}
|
|
67251
67562
|
function readInstalledPluginRefs(configRoot) {
|
|
67252
67563
|
const p = installedPluginsPathForConfig(configRoot);
|
|
67253
|
-
if (!(0,
|
|
67564
|
+
if (!(0, import_node_fs61.existsSync)(p)) return [];
|
|
67254
67565
|
try {
|
|
67255
|
-
return installedPluginPaths((0,
|
|
67566
|
+
return installedPluginPaths((0, import_node_fs61.readFileSync)(p, "utf8"));
|
|
67256
67567
|
} catch {
|
|
67257
67568
|
return null;
|
|
67258
67569
|
}
|
|
67259
67570
|
}
|
|
67260
67571
|
function pluginCacheFsDeps(configRoot, dirBytes) {
|
|
67261
67572
|
return {
|
|
67262
|
-
exists: (p) => (0,
|
|
67263
|
-
listVersionDirs: (root) => (0,
|
|
67573
|
+
exists: (p) => (0, import_node_fs61.existsSync)(p),
|
|
67574
|
+
listVersionDirs: (root) => (0, import_node_fs61.readdirSync)(root, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name),
|
|
67264
67575
|
dirBytes,
|
|
67265
|
-
listStagingDirs: (root) => (0,
|
|
67576
|
+
listStagingDirs: (root) => (0, import_node_fs61.readdirSync)(root, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => {
|
|
67266
67577
|
try {
|
|
67267
|
-
return { name: d.name, mtimeMs: newestMtimeMs((0, import_node_path58.join)(root, d.name), listDirEntries, (p) => (0,
|
|
67578
|
+
return { name: d.name, mtimeMs: newestMtimeMs((0, import_node_path58.join)(root, d.name), listDirEntries, (p) => (0, import_node_fs61.statSync)(p).mtimeMs) };
|
|
67268
67579
|
} catch {
|
|
67269
67580
|
return { name: d.name, mtimeMs: Date.now() };
|
|
67270
67581
|
}
|
|
@@ -67279,9 +67590,9 @@ function stagingApplyFsGuard(configRoot) {
|
|
|
67279
67590
|
referencedPaths: () => readInstalledPluginRefs(configRoot),
|
|
67280
67591
|
mtimeMs: (name) => {
|
|
67281
67592
|
const p = (0, import_node_path58.join)(stagingRoot, name);
|
|
67282
|
-
if (!(0,
|
|
67593
|
+
if (!(0, import_node_fs61.existsSync)(p)) return null;
|
|
67283
67594
|
try {
|
|
67284
|
-
return newestMtimeMs(p, listDirEntries, (q) => (0,
|
|
67595
|
+
return newestMtimeMs(p, listDirEntries, (q) => (0, import_node_fs61.statSync)(q).mtimeMs);
|
|
67285
67596
|
} catch {
|
|
67286
67597
|
return null;
|
|
67287
67598
|
}
|
|
@@ -67307,7 +67618,7 @@ program2.command("plugin-prune").description(`prune stale cached MMI plugin vers
|
|
|
67307
67618
|
{ withBytes: true, configRoot, includeStaging: surface !== "codex" }
|
|
67308
67619
|
);
|
|
67309
67620
|
const anythingToDelete = plan.prune.length > 0 || plan.staging.length > 0;
|
|
67310
|
-
const result = o.apply && anythingToDelete ? applyPluginCachePlan(plan, (p) => (0,
|
|
67621
|
+
const result = o.apply && anythingToDelete ? applyPluginCachePlan(plan, (p) => (0, import_node_fs61.rmSync)(p, { recursive: true, force: true }), stagingApplyFsGuard(configRoot)) : void 0;
|
|
67311
67622
|
const warnings = plan.prune.length > 0 ? [CONCURRENT_SESSION_WARNING] : [];
|
|
67312
67623
|
if (o.json) console.log(JSON.stringify({ ...plan, warnings, applied: result ?? null }));
|
|
67313
67624
|
else console.log(renderPluginCachePlan(plan, result));
|