@mutmutco/cli 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/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) return null;
987
- if (new Date(session.expiresAt).getTime() <= now.getTime() + REFRESH_WINDOW_MS) return null;
988
- return { ...session, role: roleFromToken(session.token) };
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
- async function hubAuthSession(deps) {
1008
- if (!deps.baseUrl) return void 0;
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
- return { ...{ token: envToken }, expiresAt: new Date(Date.now() + 30 * 60 * 1e3).toISOString(), apiUrl, githubTokenFingerprint: "env-override" };
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) return void 0;
1018
- const githubTokenFingerprint = tokenFingerprint(ghToken);
1019
- const cached = readCache(cachePath, apiUrl, now, githubTokenFingerprint);
1020
- if (cached) return cached;
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
- writeCache(cachePath, session);
1043
- } catch {
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 detectSurface(env) {
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 "hermes":
9388
- return "hermes";
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 "hermes":
9407
- return "start a fresh Hermes Agent process";
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 Hermes only.\n Update the CLI instead: npm i -g @mutmutco/cli (org rules ride an AGENTS.md authored outside the Hub)";
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 hermesConfigRoot(env = process.env, home = (0, import_node_os4.homedir)(), platform2 = process.platform) {
9491
- if (env.HERMES_HOME?.trim()) return env.HERMES_HOME.trim();
9492
- if (platform2 === "win32") return (0, import_node_path4.join)(env.LOCALAPPDATA?.trim() || (0, import_node_path4.join)(home, "AppData", "Local"), "hermes");
9493
- return (0, import_node_path4.join)(home, ".hermes");
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 === "hermes") return hermesConfigRoot(env, home);
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 === "hermes") return [];
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 hermesPluginRoot(env = process.env, home = (0, import_node_os4.homedir)()) {
9632
- return (0, import_node_path4.join)(hermesConfigRoot(env, home), "plugins", "mmi");
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 hermesPluginTreeHealthy(root, exists = import_node_fs7.existsSync) {
9635
- if (!["plugin.yaml", "__init__.py"].every((file) => exists((0, import_node_path4.join)(root, file)))) return false;
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
- return (0, import_node_fs7.statSync)((0, import_node_path4.join)(root, "skills")).isDirectory();
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 || surface === "hermes" && (0, import_node_fs7.existsSync)(hermesPluginRoot()),
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 === "hermes" ? true : marketplaceClonePresent(surface, (0, import_node_os4.homedir)()),
9655
- pluginCachePresent: surface === "jervcode" ? mmiPiPackageHealthy() : surface === "hermes" ? hermesPluginTreeHealthy(hermesPluginRoot()) : surface === "codex" ? Boolean(
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 import_node_fs60 = require("node:fs");
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) ?? `secrets list failed: HTTP ${res.status}${await readErr(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) ?? `secrets request failed: HTTP ${res.status}${errorDetail(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)}` : `secrets use failed: HTTP ${res.status}${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.227",
27095
- tag: "v4.5.227",
27096
- commit: "9498ff4da322",
27097
- npm: "@mutmutco/cli@4.5.227"
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.227"
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.227 and redeploy the Hub Lambda from tag v4.5.227 (9498ff4da322); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
27121
- v3Target: "v4.5.227 (@mutmutco/cli@4.5.227, tag commit 9498ff4da322 \u2014 last known-good release carrying the repo-index v4-only contract)"
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 import_node_fs30 = require("node:fs");
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 import_node_fs28 = require("node:fs");
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 import_node_fs25 = require("node:fs");
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, import_node_fs25.openSync)(sink, "r");
32018
- const errFd = (0, import_node_fs25.openSync)(sink, "w");
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, import_node_fs25.closeSync)(inFd);
32027
- (0, import_node_fs25.closeSync)(errFd);
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, import_node_fs25.readFileSync)((0, import_node_path22.join)(stagingRoot, "launch-target.json"), "utf8"));
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, import_node_fs25.readFileSync)((0, import_node_path22.join)(record2.incoming, ".claude-plugin", "plugin.json"), "utf8"));
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, import_node_fs25.readFileSync)((0, import_node_path22.join)((0, import_node_os13.homedir)(), ".claude", "plugins", "installed_plugins.json"), "utf8")
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 readHermesPluginEvidence(env = process.env) {
32056
- const host = hermesConfigRoot(env);
32057
- const root = hermesPluginRoot(env);
32058
- const installRecordPresent = (0, import_node_fs25.existsSync)(root);
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
- try {
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: (0, import_node_fs25.existsSync)(host),
32096
- installRecordPresent,
32247
+ hostPresent: workspaces.length > 0,
32248
+ installRecordPresent: record2 !== null,
32097
32249
  manifest,
32098
- payloadPresent: treeOk && provisioned,
32099
- ...installedVersion ? { installedVersion } : {},
32100
- ...receipt ? { receipt } : {}
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 === "hermes") return readHermesPluginEvidence().installedVersion;
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, import_node_fs25.readFileSync)(path2, "utf8");
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, import_node_fs25.writeFileSync)(path2, content, "utf8");
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, import_node_fs25.existsSync)((0, import_node_path22.join)(root, ".gitattributes"));
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 import_node_fs26 = require("node:fs");
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, import_node_fs26.openSync)(path2, "r");
32466
+ fd = (0, import_node_fs27.openSync)(path2, "r");
32315
32467
  const buffer = new Uint8Array(MAGIC_HEAD_BYTES);
32316
- const read = (0, import_node_fs26.readSync)(fd, buffer, 0, MAGIC_HEAD_BYTES, 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, import_node_fs26.closeSync)(fd);
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, import_node_fs26.statSync)(path2).size;
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, import_node_fs26.existsSync)((0, import_node_path24.join)(dir, "package.json")));
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, import_node_fs26.readFileSync)((0, import_node_path24.join)(packageRoot, "package.json"), "utf8"));
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, import_node_fs26.existsSync)(file)) })).find((c) => c.path);
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, import_node_fs26.existsSync)(binPath)) {
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, import_node_fs26.existsSync)(probe.binPath)) {
32588
+ if ((0, import_node_fs27.existsSync)(probe.binPath)) {
32437
32589
  try {
32438
- (0, import_node_fs26.renameSync)(probe.binPath, aside);
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, import_node_fs26.copyFileSync)(probe.source.path, probe.binPath);
32448
- if (platform2 !== "win32") (0, import_node_fs26.chmodSync)(probe.binPath, 493);
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, import_node_fs26.renameSync)(aside, probe.binPath);
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, import_node_fs26.rmSync)(aside);
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 import_node_fs27 = require("node:fs");
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, import_node_fs27.mkdirSync)((0, import_node_path26.dirname)(artifactPath), { recursive: true });
33001
- (0, import_node_fs27.writeFileSync)(artifactPath, `${fileLines.join("\n")}
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, import_node_fs27.readFileSync)((0, import_node_path26.join)(gitDir, "commondir"), "utf8").trim();
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, import_node_fs27.readFileSync)((0, import_node_path26.join)(commonDir, "HEAD"), "utf8");
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, import_node_fs27.mkdirSync)((0, import_node_path26.dirname)(path2), { recursive: true });
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, import_node_fs27.writeFileSync)(tmp, `${JSON.stringify(ledger, null, 2)}
33342
+ (0, import_node_fs28.writeFileSync)(tmp, `${JSON.stringify(ledger, null, 2)}
33191
33343
  `, "utf8");
33192
- (0, import_node_fs27.renameSync)(tmp, path2);
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, import_node_fs27.readFileSync)(trainWriterLockPath(cwd), "utf8");
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, import_node_fs27.mkdirSync)((0, import_node_path26.dirname)(lockPath), { recursive: true });
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, import_node_fs27.readFileSync)(lockPath, "utf8");
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, import_node_fs27.readFileSync)(path2, "utf8");
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, import_node_fs27.readFileSync)(path2, "utf8");
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, import_node_fs27.unlinkSync)(path2));
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, import_node_fs27.readFileSync)(path2, "utf8"));
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, import_node_fs27.readFileSync)(path2, "utf8"));
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, import_node_fs27.readFileSync)(path2, "utf8"));
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, import_node_fs27.existsSync)(archivePath)) {
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, import_node_fs27.renameSync)(path2, archivePath));
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, import_node_fs27.readFileSync)(path2, "utf8"));
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
- const dispatch = await appendJervGatewayReleaseDeploy(
34390
- deps,
34391
- prior.repo,
34392
- prior.tag,
34393
- prior.tagSha,
34394
- { note: `Jerv Gateway operator-host deploy leg for ${prior.tag} run during the publish supersession`, deployStatus: "success" }
34395
- );
34396
- phases.deploy = {
34397
- ...phaseEntry(deployPhaseInput("registry-publish", dispatch, { releaseSha: prior.tagSha })),
34398
- updatedAt: (/* @__PURE__ */ new Date()).toISOString()
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, import_node_fs27.readFileSync)(path2, "utf8"));
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, import_node_fs27.readFileSync)(path2, "utf8"));
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, import_node_fs27.readFileSync)(path2, "utf8");
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, import_node_fs27.unlinkSync)(archivePath));
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, import_node_fs27.renameSync)(path2, archivePath));
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, import_node_fs27.readFileSync)(path2, "utf8"));
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, import_node_fs28.mkdtempSync)((0, import_node_path27.join)((0, import_node_os16.tmpdir)(), "mmi-npmrc-"));
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, import_node_fs28.writeFileSync)(rc, "//registry.npmjs.org/:_authToken=${NPM_TOKEN}\n");
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, import_node_fs28.rmSync)(dir, { recursive: true, force: true });
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, import_node_fs28.readFileSync)(path2, "utf8");
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, import_node_fs28.readFileSync)((0, import_node_path27.join)(cwd, "surfaces.json"), "utf8");
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 import_node_fs29 = require("node:fs");
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, import_node_fs29.readFileSync)(file, "utf8");
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, import_node_fs30.readdirSync)(dir);
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, import_node_fs30.readFileSync)((0, import_node_path28.join)(dir, name), "utf8") });
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, import_node_fs30.readdirSync)(dir);
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, import_node_fs30.readFileSync)((0, import_node_path28.join)(dir, name), "utf8") });
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, import_node_fs30.readFileSync)(path2, "utf8"));
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
- if (await currentBranch(deps) !== "main") {
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 import_node_fs34 = require("node:fs");
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 import_node_fs31 = require("node:fs");
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, import_node_fs31.existsSync)(path2)) return {};
41238
- const raw = JSON.parse((0, import_node_fs31.readFileSync)(path2, "utf8"));
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, import_node_fs31.existsSync)(registryPath) || !(0, import_node_fs31.existsSync)(ddbScriptPath)) return null;
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 import_node_fs33 = require("node:fs");
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 import_node_fs32 = require("node:fs");
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, import_node_fs32.existsSync)(masterKeyPath)) {
42732
- env.RAILS_MASTER_KEY = (0, import_node_fs32.readFileSync)(masterKeyPath, "utf8").trim();
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, import_node_fs32.mkdtempSync)((0, import_node_path30.join)((0, import_node_os17.tmpdir)(), "mmi-rails-decrypt-"));
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, import_node_fs32.writeFileSync)(scriptPath, script, "utf8");
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, import_node_fs32.rmSync)(scriptDir, { recursive: true, force: true });
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, import_node_fs32.readFileSync)((0, import_node_path30.resolve)(o.file), "utf8");
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, import_node_fs32.unlinkSync)((0, import_node_path30.resolve)(o.appDir ?? process.cwd(), path2))
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, import_node_fs33.writeFileSync)(wroteScript, sshRecipeScript(found, remote), "utf8");
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, import_node_fs33.mkdtempSync)((0, import_node_path31.join)((0, import_node_os18.tmpdir)(), "mmi-box-control-"));
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, import_node_fs33.writeFileSync)(recipe, sshRecipeScript(found, "sh -s"), "utf8");
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, import_node_fs33.rmSync)(dir, { recursive: true, force: true });
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, import_node_fs34.mkdtempSync)((0, import_node_path32.join)((0, import_node_os19.tmpdir)(), "mmi-gate-diff-"));
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, import_node_fs34.writeFileSync)(before, current);
45020
- (0, import_node_fs34.writeFileSync)(after, rendered);
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, import_node_fs34.rmSync)(dir, { recursive: true, force: true });
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, import_node_fs34.existsSync)(path2) ? (0, import_node_fs34.readFileSync)(path2, "utf8") : null,
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, import_node_fs34.existsSync)("access-matrix.json") ? entriesValueByCanonicalRepo(loadSanctionedAdmins((0, import_node_fs34.readFileSync)("access-matrix.json", "utf8")), repo) : void 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, import_node_fs34.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`);
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, import_node_fs34.readFileSync)(manifestPath, "utf8"));
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, import_node_fs34.existsSync)(s.target) ? (0, import_node_fs34.readFileSync)(s.target, "utf8") : null);
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, import_node_fs34.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`);
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, import_node_fs34.readFileSync)(manifestPath, "utf8"));
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, import_node_fs34.existsSync)(p) ? (0, import_node_fs34.readFileSync)(p, "utf8") : null;
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, import_node_fs34.writeFileSync)(tmp, JSON.stringify(contentPutBody(target, content, ref, sha)), "utf8");
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, import_node_fs34.unlinkSync)(tmp);
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, import_node_fs34.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`);
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, import_node_fs34.readFileSync)(manifestPath, "utf8"));
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, import_node_fs34.existsSync)(seed.target)) return fail(`bootstrap propagate: the Hub's own copy of '${seed.target}' is missing \u2014 nothing to propagate`);
46054
- const hubContent = seed.managedBlock ? null : (0, import_node_fs34.readFileSync)(seed.target, "utf8");
46055
- const readSeedFile2 = (path2) => (0, import_node_fs34.existsSync)(path2) ? (0, import_node_fs34.readFileSync)(path2, "utf8") : null;
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, import_node_fs34.existsSync)("projects.json")) {
46270
+ if ((0, import_node_fs35.existsSync)("projects.json")) {
46065
46271
  try {
46066
- const local = JSON.parse((0, import_node_fs34.readFileSync)("projects.json", "utf8"));
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, import_node_fs34.existsSync)(RUNBOOK_UNAFFECTED_PATH) ? runbookUnaffectedReason(JSON.parse((0, import_node_fs34.readFileSync)(RUNBOOK_UNAFFECTED_PATH, "utf8")), seed.target, sourceBlob) : void 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, import_node_fs34.writeFileSync)(tmp, JSON.stringify(contentPutBody(seed.target, desiredContent, branch, existingSha, runbookUnaffected)), "utf8");
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, import_node_fs34.unlinkSync)(tmp);
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, import_node_fs34.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)`);
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, import_node_fs34.readFileSync)(manifestPath, "utf8"));
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, import_node_fs34.existsSync)(o.record)) return fail(`bootstrap rollback: --record '${o.record}' not found`);
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, import_node_fs34.readFileSync)(o.record, "utf8"));
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, import_node_fs34.writeFileSync)(tmp, JSON.stringify(contentPutBody(seed.target, preSeedContent, plan.branch, existingSha)), "utf8");
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, import_node_fs34.unlinkSync)(tmp);
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 import_node_fs35 = require("node:fs");
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, import_node_fs35.readFileSync)(path2, "utf8");
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, import_node_fs35.mkdirSync)((0, import_node_path33.dirname)(path2), { recursive: true });
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, import_node_fs35.writeFileSync)(temp, JSON.stringify(record2, null, 2), { flag: "wx" });
47006
- (0, import_node_fs35.renameSync)(temp, path2);
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, import_node_fs35.existsSync)(deps.ledgerPath)) return void 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, import_node_fs35.readFileSync)(deps.ledgerPath, "utf8"));
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, import_node_fs35.existsSync)(archive)) throw new Error(`ledger archive already exists at ${archive}; inspect before retiring again`);
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, import_node_fs35.renameSync)(deps.ledgerPath, archive));
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, import_node_fs35.existsSync)(archive)) throw new Error(`abandoned-cut archive already exists at ${archive}; inspect before abandoning again`);
47073
- (0, import_node_fs35.renameSync)(recordPath, archive);
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, import_node_fs35.existsSync)(archive)) throw new Error(`suite cut archive already exists at ${archive}; inspect before a new cut`);
47161
- (0, import_node_fs35.renameSync)(deps.recordPath, archive);
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, import_node_fs35.rmSync)(deps.recordPath, { force: true });
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, import_node_fs35.rmSync)(deps.recordPath, { force: true });
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, import_node_fs35.rmSync)(deps.recordPath, { force: true });
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, import_node_fs35.readFileSync)(path2, "utf8");
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, import_node_fs35.renameSync)(path2, `${path2}.${completed.leadTag}.json`);
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 import_node_fs48 = require("node:fs");
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 import_node_fs37 = require("node:fs");
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 import_node_fs36 = require("node:fs");
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, import_node_fs36.readFileSync)(path2, "utf8");
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, import_node_fs36.existsSync)(path2)) {
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, import_node_fs36.existsSync)(path2)) {
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, import_node_fs36.existsSync)(path2));
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, import_node_fs37.mkdtempSync)((0, import_node_path35.join)((0, import_node_os20.tmpdir)(), "mmi-squash-body-"));
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, import_node_fs37.writeFileSync)(path2, body.endsWith("\n") ? body : `${body}
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, import_node_fs37.rmSync)(dir, { recursive: true, force: true });
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 import_node_fs38 = require("node:fs");
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, import_node_fs38.existsSync)(readmePath)) throw new Error(`org project sync-info: ${targetRepo2} has no README.md`);
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, import_node_fs38.readFileSync)(readmePath, "utf8")) : `Shared work across ${new Intl.ListFormat("en", { type: "conjunction" }).format(entryNames)}.`;
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, import_node_fs38.existsSync)((0, import_node_path36.join)(repoRoot2, "architecture.md")) ? `- [Hub architecture](${targetBase}/blob/${targetBranch}/architecture.md)` : "",
51325
- (0, import_node_fs38.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)` : ""
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 import_node_fs39 = require("node:fs");
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, import_node_fs39.readFileSync)((0, import_node_path37.join)(process.cwd(), ".claude-plugin", "plugin.json"), "utf8")).version || void 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 import_node_fs47 = require("node:fs");
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 import_node_fs40 = require("node:fs");
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, import_node_fs40.readFileSync)(p, e)), readStdin: () => Promise.resolve("") };
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, import_node_fs40.readFileSync)(opts.batch, "utf8");
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 import_node_fs41 = require("node:fs");
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, import_node_fs41.mkdirSync)((0, import_node_path39.dirname)(path2), { recursive: true });
52982
- (0, import_node_fs41.writeFileSync)(path2, `${JSON.stringify(recovery, null, 2)}
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, import_node_fs41.existsSync)(path2)) return;
53258
+ if (!(0, import_node_fs42.existsSync)(path2)) return;
52989
53259
  try {
52990
- (0, import_node_fs41.unlinkSync)(path2);
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 import_node_fs42 = require("node:fs");
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, import_node_fs42.mkdtempSync)((0, import_node_path40.join)((0, import_node_os22.tmpdir)(), "mmi-review-verdict-"));
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, import_node_fs42.writeFileSync)(path2, body, "utf8");
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, import_node_fs42.rmSync)(dir, { recursive: true, force: true });
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 import_node_fs46 = require("node:fs");
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 import_node_fs45 = require("node:fs");
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 import_node_fs44 = require("node:fs");
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 import_node_fs43 = require("node:fs");
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, import_node_fs43.readdirSync)(dir, { withFileTypes: true });
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, import_node_fs43.lstatSync)((0, import_node_path41.join)(dir, entry.name)).isDirectory();
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, import_node_fs44.existsSync)(configured)) return configured;
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, import_node_fs44.existsSync)(path2));
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, import_node_fs44.existsSync)(envFile) && envFileKeys((0, import_node_fs44.readFileSync)(envFile, "utf8")).has("PORT");
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, import_node_fs44.existsSync)((0, import_node_path42.join)(cwd, conventional))) files.push(conventional);
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, import_node_fs44.existsSync)((0, import_node_path42.join)(cwd, "docker-compose.yml"))) return {};
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, import_node_fs44.mkdirSync)((0, import_node_path42.join)(cwd, "tmp", "stage"), { recursive: true });
54464
- (0, import_node_fs44.writeFileSync)(overridePath, body, "utf8");
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, import_node_fs44.existsSync)(target) && (0, import_node_fs44.existsSync)(example)) {
54472
- (0, import_node_fs44.copyFileSync)(example, target);
54473
- } else if ((0, import_node_fs44.existsSync)(target) && (0, import_node_fs44.existsSync)(example)) {
54474
- const stale = detectStaleEnvFile((0, import_node_fs44.readFileSync)(example, "utf8"), (0, import_node_fs44.readFileSync)(target, "utf8"), {
54475
- exampleMtimeMs: (0, import_node_fs44.statSync)(example).mtimeMs,
54476
- targetMtimeMs: (0, import_node_fs44.statSync)(target).mtimeMs
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, import_node_fs44.existsSync)(target)) {
54485
- (0, import_node_fs44.writeFileSync)(target, mergeEnvSecretsIntoFile((0, import_node_fs44.readFileSync)(target, "utf8"), opts.vaultEnvMerge), "utf8");
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, import_node_fs44.existsSync)(path2)) return null;
54785
+ if (!(0, import_node_fs45.existsSync)(path2)) return null;
54516
54786
  try {
54517
- return JSON.parse((0, import_node_fs44.readFileSync)(path2, "utf8"));
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, import_node_fs44.mkdirSync)(dir, { recursive: true });
54794
+ (0, import_node_fs45.mkdirSync)(dir, { recursive: true });
54525
54795
  }
54526
54796
  function writeState(path2, state) {
54527
54797
  mkdirFor(path2);
54528
- (0, import_node_fs44.writeFileSync)(path2, JSON.stringify(state, null, 2), "utf8");
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, import_node_fs44.rmSync)(path2, { force: true });
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, import_node_fs44.rmSync)(statePath, { force: true });
54805
- if (globalStatePath && globalStatePath !== statePath) (0, import_node_fs44.rmSync)(globalStatePath, { force: true });
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, import_node_fs45.lstatSync)(nm).isSymbolicLink()) (0, import_node_fs45.rmdirSync)(nm);
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 = import_node_fs45.rmdirSync, exists = import_node_fs45.existsSync } = options;
54838
- const removeRecursive = options.removeRecursive ?? ((path2) => (0, import_node_fs45.rmSync)(path2, { recursive: true, force: true, maxRetries: 10, retryDelay: 100 }));
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, import_node_fs45.lstatSync)(path2);
55234
+ const stat4 = (0, import_node_fs46.lstatSync)(path2);
54965
55235
  if (stat4.isSymbolicLink()) {
54966
55236
  try {
54967
- (0, import_node_fs45.rmdirSync)(path2);
55237
+ (0, import_node_fs46.rmdirSync)(path2);
54968
55238
  } catch {
54969
- (0, import_node_fs45.unlinkSync)(path2);
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, import_node_fs45.readdirSync)(path2)) safeRemoveTree((0, import_node_path43.join)(path2, entry));
54975
- (0, import_node_fs45.rmdirSync)(path2);
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, import_node_fs45.unlinkSync)(path2);
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, import_node_fs45.realpathSync)(path2));
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, import_node_fs45.lstatSync)(root).isSymbolicLink()) {
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, import_node_fs45.realpathSync)(root));
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, import_node_fs45.readdirSync)(dir, { withFileTypes: true });
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, import_node_fs45.realpathSync)(child2));
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 ?? import_node_fs45.existsSync;
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, import_node_fs46.readFileSync)(f, "utf8")))(file));
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, import_node_fs46.mkdirSync)((0, import_node_path44.dirname)(f), { recursive: true });
55511
- (0, import_node_fs46.writeFileSync)(f, t, "utf8");
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 ?? import_node_fs46.existsSync;
55526
- const readDir = deps.readDir ?? ((p) => (0, import_node_fs46.readdirSync)(p));
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, import_node_fs46.statSync)(p).isDirectory();
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, import_node_fs46.rmdirSync)(p));
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, import_node_fs47.existsSync)((0, import_node_path45.join)(fromPkg, marker))) return fromPkg;
55616
- if ((0, import_node_fs47.existsSync)((0, import_node_path45.join)(process.cwd(), marker))) return process.cwd();
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, import_node_fs47.existsSync)(fullPath) ? (0, import_node_fs47.readFileSync)(fullPath, "utf8") : null;
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, import_node_fs47.existsSync)(wfDir)) return [];
56444
- return (0, import_node_fs47.readdirSync)(wfDir).filter((name) => /\.(ya?ml)$/i.test(name)).filter((name) => {
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, import_node_fs47.readFileSync)((0, import_node_path45.join)(wfDir, name), "utf8"));
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, import_node_fs47.readFileSync)(o.findingsFile, "utf8") : void 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 lastFailure;
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
- lastFailure = read.error;
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
- lastFailure = void 0;
57143
+ lastFailure2 = void 0;
56874
57144
  }
56875
57145
  await new Promise((resolve7) => setTimeout(resolve7, PR_LAND_POLL_MS));
56876
57146
  }
56877
- if (lastFailure) {
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 (${lastFailure}). The auto-merge enqueue stands; read the PR directly (gh pr view ${prNumber} --repo ${repo} --json merged,state) and rerun cleanup if it merged.`
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, import_node_fs47.existsSync)(cwd0) ? cwd0 : primaryRoot0,
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, import_node_fs47.readFileSync)(o.squashBodyFile, "utf8") : void 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, import_node_fs47.readFileSync)(bodyFile, "utf8") : void 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, import_node_fs47.existsSync)(p),
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
- return r.gatewayDeploy ? `${base}; the deferred Jerv Gateway operator-host deploy leg was run and verified during the supersession (jerv-gateway=complete): ${r.gatewayDeploy.note}` : base;
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
- return r.gatewayDeploy ? `${base}; the deferred Jerv Gateway operator-host deploy leg was run and verified during the closure (jerv-gateway=complete): ${r.gatewayDeploy.note}` : base;
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, import_node_fs48.existsSync)(target)) {
58591
- (0, import_node_fs48.mkdirSync)(base, { recursive: true });
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, import_node_fs48.existsSync)((0, import_node_path47.join)(target, ".git"))) throw new Error(`${repo}: suite checkout has no .git at ${target}; stop`);
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, import_node_fs48.mkdirSync)(dir, { recursive: true });
59176
+ (0, import_node_fs49.mkdirSync)(dir, { recursive: true });
58902
59177
  const file = (0, import_node_path47.join)(dir, `notes-${leadTag}.md`);
58903
- (0, import_node_fs48.writeFileSync)(file, `${body}
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 import_node_fs49 = require("node:fs");
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, import_node_fs49.existsSync)(io.recordPath)) {
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 import_node_fs50 = require("node:fs");
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, import_node_fs50.readFileSync)(packagePath, "utf8");
59857
- const lockText = (0, import_node_fs50.readFileSync)(lockPath, "utf8");
59858
- const work = (0, import_node_fs50.mkdtempSync)((0, import_node_path48.join)((0, import_node_os23.tmpdir)(), "mmi-adopt-export-"));
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, import_node_fs50.writeFileSync)((0, import_node_path48.join)(work, "expected.json"), JSON.stringify(expectedFromRun(run, opts)));
59862
- (0, import_node_fs50.writeFileSync)((0, import_node_path48.join)(work, "provider.json"), await deps.ghApi(`repos/${EXPORT_REPO}/actions/artifacts/${opts.artifact}`));
59863
- (0, import_node_fs50.writeFileSync)((0, import_node_path48.join)(work, "artifact.zip"), await deps.ghApi(`repos/${EXPORT_REPO}/actions/artifacts/${opts.artifact}/zip`));
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, import_node_fs50.writeFileSync)(script, package_export_default);
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, import_node_fs50.readFileSync)((0, import_node_path48.join)(verified, `${short3}.metadata.json`), "utf8"))]));
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, import_node_fs50.writeFileSync)(packagePath, plan.packageText);
59875
- (0, import_node_fs50.writeFileSync)(lockPath, plan.lockText);
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, import_node_fs50.writeFileSync)(packagePath, packageText);
59880
- (0, import_node_fs50.writeFileSync)(lockPath, lockText);
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, import_node_fs50.rmSync)(work, { recursive: true, force: true });
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 import_node_fs51 = require("node:fs");
60225
+ var import_node_fs52 = require("node:fs");
59951
60226
  function readStageSummary(worktreePath) {
59952
60227
  const statePath = stageStatePath(worktreePath);
59953
- if (!(0, import_node_fs51.existsSync)(statePath)) return void 0;
60228
+ if (!(0, import_node_fs52.existsSync)(statePath)) return void 0;
59954
60229
  try {
59955
- const state = JSON.parse((0, import_node_fs51.readFileSync)(statePath, "utf8"));
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 import_node_fs52 = require("node:fs");
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), import_node_fs52.readFileSync),
60206
- readSettings: () => readFileSyncSafe((0, import_node_path49.join)(home, ".claude", "settings.json"), import_node_fs52.readFileSync)
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: "hermes",
60586
- displayName: "Hermes Agent",
60860
+ token: "openbot",
60861
+ displayName: "OpenBot",
60587
60862
  lifecycle: "active",
60588
60863
  artifactIds: [
60589
- "mmi-hermes-plugin",
60864
+ "mmi-openbot-plugin",
60590
60865
  "mmi-skills",
60591
60866
  "mmi-cli"
60592
60867
  ],
60593
60868
  plannedArtifactIds: [],
60594
60869
  assembly: {
60595
- mode: "package-directory",
60596
- rootPath: "packages/hermes-plugin",
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/hermes-plugin (npm transport; MMI-Hub extracts the package into $HERMES_HOME/plugins/mmi)"
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-hermes-plugin",
60885
+ artifactId: "mmi-skills",
60617
60886
  invocation: {
60618
- entry: "/epic",
60619
- any: "skill_view(name='<skill>')"
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
- hostCommand: "hermes",
60633
- versionArgs: [
60634
- "--version"
60635
- ]
60901
+ hostProbe: "none",
60902
+ hostCommand: null,
60903
+ versionArgs: []
60636
60904
  },
60637
60905
  compatibility: {
60638
- hostVersion: "0.20.1",
60639
- certification: "Hermes Agent v0.20.1 release v2026.8.13, upstream f80f453ae0679347e38abc917c7f94f717bf96c5 (#5053)"
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
- "MMI ships no Hermes hooks (#5908): the plugin contributes skills and the /mmi command only; enforcement is host-side.",
60643
- "Hermes user plugins are opt-in through plugins.enabled (#6656): after a verified converge MMI-Hub enables its OWN mmi key through Hermes' own `hermes plugins enable mmi --no-allow-tool-override` command, and never reads, reorders, or writes another operator entry. Built-in tool override is explicitly declined, since MMI ships no Hermes hooks. Host process isolation remains Hermes-owned.",
60644
- "npm is MMI transport only: MMI-Hub extracts @mutmutco/hermes-plugin into $HERMES_HOME/plugins/mmi and provisions the canonical skills into $HERMES_HOME/skills/mmi/; a fresh Hermes Agent process is required for discovery.",
60645
- "Every Hermes home is covered per home (#6672): a profile under $HERMES_HOME/profiles/<name> is a full sibling home and Hermes scans only the active home's plugins/, so MMI-Hub repeats the same single-home install, skills provisioning, and host-command enable in the resolved home and in each profile home with HERMES_HOME bound to it. No profile is enumerated for any other purpose, and nothing outside those per-home MMI paths is read or written."
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-hermes-plugin",
61277
+ id: "mmi-openbot-plugin",
61010
61278
  classification: "packaging",
61011
61279
  kind: "plugin",
61012
- ownerPath: "packages/hermes-plugin/package.json",
61013
- deliveryPath: "packages/hermes-plugin/package.json",
61280
+ ownerPath: ".pi-plugin/package.json",
61281
+ deliveryPath: ".pi-plugin/package.json",
61014
61282
  delivery: "npm",
61015
- applicability: "Hermes Agent native directory plugin transported as npm package @mutmutco/hermes-plugin; MMI-Hub extracts its package bytes into $HERMES_HOME/plugins/mmi, where Hermes v0.20.1 discovers it as an enabled user plugin.",
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: "hermes",
61285
+ surfaceToken: "openbot",
61018
61286
  versionPaths: [
61019
61287
  {
61020
- path: "packages/hermes-plugin/package.json",
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
- "packages/hermes-plugin/plugin.yaml",
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: "packages/hermes-plugin"
61297
+ packagePath: ".pi-plugin"
61036
61298
  },
61037
- publishVisibility: "public"
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", "hermes"]);
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 === "hermes") return "start a fresh Hermes Agent process";
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 — the literal "Hermes plugin" sent every other
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 === "hermes" ? "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})` : ""}`,
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 === "hermes" ? "start a fresh process" : descriptor.reload === "workspace" ? "reload" : "restart"} ${descriptor.displayName} to converge`,
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 import_node_fs53 = require("node:fs");
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 args = withJson(withRepo(["pr", "view", parseBrokerId(ref, "PR number")], parseBrokerRepo(o.repo, "--repo")), parseBrokerJsonFields(o.json), o.json);
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
- process.stderr.write(`read: gh ${spec.args.slice(0, 2).join(" ")} failed on the App pool (exit ${code}) \u2014 inspect stderr; retry the same typed read and only parse stdout after success
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, import_node_fs53.writeFileSync)(o.out, body, "latin1");
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 import_node_fs54 = require("node:fs");
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, import_node_fs54.existsSync)((0, import_node_path50.join)(process.cwd(), "docker-compose.yml")),
64285
- hasEnvExample: (0, import_node_fs54.existsSync)((0, import_node_path50.join)(process.cwd(), ".env.example")),
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 import_node_fs55 = require("node:fs");
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, import_node_fs55.createReadStream)(path2)) hash.update(chunk);
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, import_node_fs55.createReadStream)(path2);
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 import_node_fs59 = require("node:fs");
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 import_node_fs57 = require("node:fs");
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 import_node_fs56 = require("node:fs");
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, import_node_fs56.readlinkSync)(file.absolute), "utf8") : (0, import_node_fs56.readFileSync)(file.absolute);
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, import_node_fs56.existsSync)(absolute)) throw new Error(`packed artifact identity path ${path2} does not exist`);
64853
- return { absolute, relative: slash(path2), stat: (0, import_node_fs56.lstatSync)(absolute) };
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, import_node_fs57.existsSync)(path2) ? (0, import_node_fs57.readFileSync)(path2) : null;
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, import_node_fs57.readdirSync)(directory, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
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, import_node_fs57.readFileSync)((0, import_node_path53.join)(root, "surfaces.json"), "utf8"));
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, import_node_fs57.mkdtempSync)((0, import_node_path53.join)((0, import_node_os25.tmpdir)(), "mmi-dist-drift-"));
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, import_node_fs57.readFileSync)(bomPathFor(root), "utf8"));
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, import_node_fs57.mkdirSync)((0, import_node_path53.dirname)(target), { recursive: true });
64992
- (0, import_node_fs57.writeFileSync)(target, entry.bytes);
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, import_node_fs57.rmSync)(stage, { recursive: true, force: true });
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 import_node_fs58 = require("node:fs");
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, import_node_fs58.readFileSync)((0, import_node_path56.join)(root, file), "utf8");
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, import_node_fs59.existsSync)(path2) ? (0, import_node_fs59.readFileSync)(path2, "utf8") : null;
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, import_node_fs59.writeFileSync)(path2, plan.content, "utf8");
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, import_node_fs59.writeFileSync)(path2, plan.content, "utf8");
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 hermes = token === "hermes" ? readHermesPluginEvidence(process.env) : void 0;
65777
- const installedVersion = hermes?.installedVersion ?? installedSurfacePluginVersion(runtimeSurface);
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
- // Hermes' root is the host evidence: a configured but uninstalled MMI tree is missing, while an
65782
- // absent root is skipped. Other mature surfaces retain their established org/install applicability.
65783
- applicable: hermes ? hermes.hostPresent : isOrgRepo || snapshot.installRecordPresent || snapshot.pluginCachePresent,
65784
- installRecordPresent: hermes?.installRecordPresent ?? snapshot.installRecordPresent,
65785
- deliveryPresent: hermes ? hermes.installRecordPresent : snapshot.marketplaceClonePresent,
65786
- payloadPresent: hermes?.payloadPresent ?? snapshot.pluginCachePresent,
65787
- manifest: hermes?.manifest ?? (installedVersion ? "valid" : snapshot.installRecordPresent ? "invalid" : "missing"),
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
- ...hermes?.receipt ? { receipt: hermes.receipt } : {}
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, import_node_fs60.rmSync)(p, { recursive: true }),
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, import_node_fs60.existsSync)((0, import_node_path58.join)(root, DOCS_INDEX_PATH))) return void 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, import_node_fs60.existsSync)((0, import_node_path58.join)(root, DOCS_INDEX_PATH))) return { drift: false, docCount: 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, import_node_fs60.readFileSync)(o.secretsFile, "utf8")}`);
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, import_node_fs60.existsSync)(fullPath) ? (0, import_node_fs60.readFileSync)(fullPath, "utf8") : null;
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, import_node_fs60.existsSync)((0, import_node_path58.join)(fromPkg, marker))) return fromPkg;
67067
- if ((0, import_node_fs60.existsSync)((0, import_node_path58.join)(process.cwd(), marker))) return process.cwd();
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, import_node_fs60.existsSync)("access-matrix.json") ? loadAccessMatrix((0, import_node_fs60.readFileSync)("access-matrix.json", "utf8")) : {};
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, import_node_fs60.existsSync)("access-matrix.json") ? loadSanctionedAdmins((0, import_node_fs60.readFileSync)("access-matrix.json", "utf8")) : {};
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, import_node_fs60.readdirSync)(path2, { withFileTypes: true });
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, import_node_fs60.statSync)(child2).size;
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, import_node_fs60.readdirSync)(dir, { withFileTypes: true }).map((d) => ({ name: d.name, isDirectory: d.isDirectory() }));
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, import_node_fs60.existsSync)(p)) return [];
67564
+ if (!(0, import_node_fs61.existsSync)(p)) return [];
67254
67565
  try {
67255
- return installedPluginPaths((0, import_node_fs60.readFileSync)(p, "utf8"));
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, import_node_fs60.existsSync)(p),
67263
- listVersionDirs: (root) => (0, import_node_fs60.readdirSync)(root, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name),
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, import_node_fs60.readdirSync)(root, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => {
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, import_node_fs60.statSync)(p).mtimeMs) };
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, import_node_fs60.existsSync)(p)) return null;
67593
+ if (!(0, import_node_fs61.existsSync)(p)) return null;
67283
67594
  try {
67284
- return newestMtimeMs(p, listDirEntries, (q) => (0, import_node_fs60.statSync)(q).mtimeMs);
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, import_node_fs60.rmSync)(p, { recursive: true, force: true }), stagingApplyFsGuard(configRoot)) : void 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));