@mutmutco/codex-plugin 4.6.47 → 4.6.49

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/cli/dist/main.cjs CHANGED
@@ -80,13 +80,13 @@ var init_compat = __esm({
80
80
  // src/client-version.ts
81
81
  function resolveClientVersionManifestCandidates(distDir = __dirname) {
82
82
  return [
83
- (0, import_node_path.join)(distDir, "..", "..", ".claude-plugin", "plugin.json"),
84
- (0, import_node_path.join)(distDir, "..", "package.json")
83
+ (0, import_node_path2.join)(distDir, "..", "..", ".claude-plugin", "plugin.json"),
84
+ (0, import_node_path2.join)(distDir, "..", "package.json")
85
85
  ];
86
86
  }
87
87
  function readVersionFromManifest(path2) {
88
88
  try {
89
- const version = JSON.parse((0, import_node_fs.readFileSync)(path2, "utf8")).version;
89
+ const version = JSON.parse((0, import_node_fs2.readFileSync)(path2, "utf8")).version;
90
90
  return typeof version === "string" && version.trim() ? version.trim() : null;
91
91
  } catch {
92
92
  return null;
@@ -106,12 +106,12 @@ function upgradeRequiredError(res, body) {
106
106
  const minVersion = body && typeof body === "object" && typeof body.minVersion === "string" ? body.minVersion : "a newer version";
107
107
  return `Hub requires mmi-cli >= ${minVersion} \u2014 run mmi-cli doctor (installed ${resolveClientVersion()})`;
108
108
  }
109
- var import_node_fs, import_node_path;
109
+ var import_node_fs2, import_node_path2;
110
110
  var init_client_version = __esm({
111
111
  "src/client-version.ts"() {
112
112
  "use strict";
113
- import_node_fs = require("node:fs");
114
- import_node_path = require("node:path");
113
+ import_node_fs2 = require("node:fs");
114
+ import_node_path2 = require("node:path");
115
115
  init_compat();
116
116
  }
117
117
  });
@@ -470,9 +470,9 @@ function recordGhUsage(via, pool, op) {
470
470
  if (process.env.MMI_GH_USAGE === "off" || process.env.VITEST && !process.env.MMI_GH_USAGE_DIR) return;
471
471
  try {
472
472
  const dir = usageDir();
473
- (0, import_node_fs2.mkdirSync)(dir, { recursive: true });
473
+ (0, import_node_fs3.mkdirSync)(dir, { recursive: true });
474
474
  const row = { t: Date.now(), cmd: commandPath(), via, pool, op: shapeOp(op) };
475
- (0, import_node_fs2.appendFileSync)((0, import_node_path2.join)(dir, `${new Date(row.t).toISOString().slice(0, 13)}.jsonl`), `${JSON.stringify(row)}
475
+ (0, import_node_fs3.appendFileSync)((0, import_node_path3.join)(dir, `${new Date(row.t).toISOString().slice(0, 13)}.jsonl`), `${JSON.stringify(row)}
476
476
  `);
477
477
  } catch {
478
478
  }
@@ -483,12 +483,12 @@ function summarizeGhUsage(hours = 1, now = Date.now(), env = process.env) {
483
483
  const rows = [];
484
484
  let files = [];
485
485
  try {
486
- files = (0, import_node_fs2.readdirSync)(dir).filter((f) => f.endsWith(".jsonl") && Date.parse(`${f.slice(0, 13)}:00:00Z`) >= since - 36e5);
486
+ files = (0, import_node_fs3.readdirSync)(dir).filter((f) => f.endsWith(".jsonl") && Date.parse(`${f.slice(0, 13)}:00:00Z`) >= since - 36e5);
487
487
  } catch {
488
488
  files = [];
489
489
  }
490
490
  for (const file of files) {
491
- for (const line of (0, import_node_fs2.readFileSync)((0, import_node_path2.join)(dir, file), "utf8").split("\n")) {
491
+ for (const line of (0, import_node_fs3.readFileSync)((0, import_node_path3.join)(dir, file), "utf8").split("\n")) {
492
492
  try {
493
493
  const row = JSON.parse(line);
494
494
  if (row.t >= since) rows.push(row);
@@ -507,14 +507,14 @@ function summarizeGhUsage(hours = 1, now = Date.now(), env = process.env) {
507
507
  }
508
508
  return { hours, total: rows.length, byPool, top: [...groups.values()].sort((a, b) => b.count - a.count).slice(0, 20) };
509
509
  }
510
- var import_node_fs2, import_node_os, import_node_path2, usageDir, cachedCommand;
510
+ var import_node_fs3, import_node_os, import_node_path3, usageDir, cachedCommand;
511
511
  var init_gh_usage = __esm({
512
512
  "src/gh-usage.ts"() {
513
513
  "use strict";
514
- import_node_fs2 = require("node:fs");
514
+ import_node_fs3 = require("node:fs");
515
515
  import_node_os = require("node:os");
516
- import_node_path2 = require("node:path");
517
- usageDir = (env = process.env) => env.MMI_GH_USAGE_DIR || (0, import_node_path2.join)((0, import_node_os.homedir)(), ".mmi", "gh-usage");
516
+ import_node_path3 = require("node:path");
517
+ usageDir = (env = process.env) => env.MMI_GH_USAGE_DIR || (0, import_node_path3.join)((0, import_node_os.homedir)(), ".mmi", "gh-usage");
518
518
  }
519
519
  });
520
520
 
@@ -691,7 +691,7 @@ function createGitHubClient(options = {}) {
691
691
  if (!res.ok) throw await errorFromResponse(res);
692
692
  return res;
693
693
  }
694
- async function parseJson(res) {
694
+ async function parseJson2(res) {
695
695
  if (res.status === 204) return void 0;
696
696
  const text = await res.text();
697
697
  if (!text) return void 0;
@@ -700,7 +700,7 @@ function createGitHubClient(options = {}) {
700
700
  let viewer;
701
701
  return {
702
702
  async rest(method, path2, init) {
703
- const read = async () => parseJson(await request(method, joinUrl(baseUrl, path2), init));
703
+ const read = async () => parseJson2(await request(method, joinUrl(baseUrl, path2), init));
704
704
  if (method === "GET" && path2.replace(/^\//, "") === "user" && !init) {
705
705
  viewer ??= read().catch((e) => {
706
706
  viewer = void 0;
@@ -712,14 +712,14 @@ function createGitHubClient(options = {}) {
712
712
  },
713
713
  async restWithStatus(method, path2, init) {
714
714
  const res = await request(method, joinUrl(baseUrl, path2), init);
715
- return { status: res.status, body: await parseJson(res) };
715
+ return { status: res.status, body: await parseJson2(res) };
716
716
  },
717
717
  async restPaginate(path2, init) {
718
718
  const items = [];
719
719
  let url = withPerPage(joinUrl(baseUrl, path2));
720
720
  while (url) {
721
721
  const res = await request("GET", url, init);
722
- const page = await parseJson(res);
722
+ const page = await parseJson2(res);
723
723
  if (!Array.isArray(page)) {
724
724
  throw new GitHubApiError(
725
725
  `pagination page was not a JSON array (got ${page === void 0 ? "an empty body" : typeof page}) \u2014 the list read is PARTIAL and must not be treated as complete: ${url}. Retry the call.`,
@@ -736,7 +736,7 @@ function createGitHubClient(options = {}) {
736
736
  ...init,
737
737
  body: { query, ...variables ? { variables } : {} }
738
738
  });
739
- const parsed = await parseJson(res);
739
+ const parsed = await parseJson2(res);
740
740
  if (parsed?.errors?.length) {
741
741
  const message2 = parsed.errors.map((e) => e.message ?? e.type ?? "unknown GraphQL error").join("; ");
742
742
  const rateLimited = parsed.errors.some((e) => e.type === "RATE_LIMITED" || /rate limit/i.test(e.message ?? ""));
@@ -988,11 +988,11 @@ function tokenFingerprint(token) {
988
988
  function defaultHubSessionCachePath(env = process.env) {
989
989
  if (env.MMI_HUB_SESSION_CACHE) return env.MMI_HUB_SESSION_CACHE;
990
990
  if (process.platform === "win32") {
991
- const base2 = env.LOCALAPPDATA || (0, import_node_path3.join)((0, import_node_os2.homedir)(), "AppData", "Local");
992
- return (0, import_node_path3.join)(base2, "MMI Future", "mmi-cli", "hub-session.json");
991
+ const base2 = env.LOCALAPPDATA || (0, import_node_path4.join)((0, import_node_os2.homedir)(), "AppData", "Local");
992
+ return (0, import_node_path4.join)(base2, "MMI Future", "mmi-cli", "hub-session.json");
993
993
  }
994
- const base = env.XDG_STATE_HOME || (0, import_node_path3.join)((0, import_node_os2.homedir)(), ".local", "state");
995
- return (0, import_node_path3.join)(base, "mmi-cli", "hub-session.json");
994
+ const base = env.XDG_STATE_HOME || (0, import_node_path4.join)((0, import_node_os2.homedir)(), ".local", "state");
995
+ return (0, import_node_path4.join)(base, "mmi-cli", "hub-session.json");
996
996
  }
997
997
  function roleFromToken(token) {
998
998
  const part = token?.split(".")[1];
@@ -1006,7 +1006,7 @@ function roleFromToken(token) {
1006
1006
  }
1007
1007
  function readCache(path2, apiUrl, now, githubTokenFingerprint) {
1008
1008
  try {
1009
- const session = JSON.parse((0, import_node_fs3.readFileSync)(path2, "utf8"));
1009
+ const session = JSON.parse((0, import_node_fs4.readFileSync)(path2, "utf8"));
1010
1010
  if (!session.token || !session.expiresAt || session.apiUrl !== apiUrl) return { session: null, stale: false };
1011
1011
  if (session.githubTokenFingerprint !== githubTokenFingerprint) {
1012
1012
  return { session: null, stale: true, staleLogin: typeof session.login === "string" ? session.login : void 0 };
@@ -1018,16 +1018,16 @@ function readCache(path2, apiUrl, now, githubTokenFingerprint) {
1018
1018
  }
1019
1019
  }
1020
1020
  function writeCache(path2, session) {
1021
- (0, import_node_fs3.mkdirSync)((0, import_node_path3.dirname)(path2), { recursive: true });
1021
+ (0, import_node_fs4.mkdirSync)((0, import_node_path4.dirname)(path2), { recursive: true });
1022
1022
  const tmp = `${path2}.${process.pid}.${Date.now()}.tmp`;
1023
- (0, import_node_fs3.writeFileSync)(tmp, JSON.stringify(session, null, 2) + "\n", { encoding: "utf8", mode: 384 });
1023
+ (0, import_node_fs4.writeFileSync)(tmp, JSON.stringify(session, null, 2) + "\n", { encoding: "utf8", mode: 384 });
1024
1024
  try {
1025
- (0, import_node_fs3.chmodSync)(tmp, 384);
1025
+ (0, import_node_fs4.chmodSync)(tmp, 384);
1026
1026
  } catch {
1027
1027
  }
1028
- (0, import_node_fs3.renameSync)(tmp, path2);
1028
+ (0, import_node_fs4.renameSync)(tmp, path2);
1029
1029
  try {
1030
- (0, import_node_fs3.chmodSync)(path2, 384);
1030
+ (0, import_node_fs4.chmodSync)(path2, 384);
1031
1031
  } catch {
1032
1032
  }
1033
1033
  }
@@ -1139,13 +1139,13 @@ async function hubAuthSession(deps) {
1139
1139
  async function hubAuthToken(deps) {
1140
1140
  return (await hubAuthSession(deps))?.token;
1141
1141
  }
1142
- 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;
1142
+ var import_node_crypto, import_node_fs4, import_node_path4, import_node_os2, REFRESH_WINDOW_MS, EXCHANGE_TIMEOUT_MS, EXCHANGE_ATTEMPTS, EXIT_HUB_SESSION_STALE, EXIT_HUB_SESSION_REFUSED, lastFailure, MEMBERSHIP_FIX;
1143
1143
  var init_hub_auth = __esm({
1144
1144
  "src/hub-auth.ts"() {
1145
1145
  "use strict";
1146
1146
  import_node_crypto = require("node:crypto");
1147
- import_node_fs3 = require("node:fs");
1148
- import_node_path3 = require("node:path");
1147
+ import_node_fs4 = require("node:fs");
1148
+ import_node_path4 = require("node:path");
1149
1149
  import_node_os2 = require("node:os");
1150
1150
  init_fetch_retry();
1151
1151
  init_client_version();
@@ -1317,7 +1317,7 @@ var init_house_map = __esm({
1317
1317
  function setInjectedStdin(payload) {
1318
1318
  injectedStdin = payload;
1319
1319
  }
1320
- function stdinHasPipedInput(statFd = () => (0, import_node_fs4.fstatSync)(0), getIsTTY = () => process.stdin.isTTY) {
1320
+ function stdinHasPipedInput(statFd = () => (0, import_node_fs5.fstatSync)(0), getIsTTY = () => process.stdin.isTTY) {
1321
1321
  try {
1322
1322
  const stat4 = statFd();
1323
1323
  if (stat4.isFIFO() || stat4.isFile()) return true;
@@ -1365,11 +1365,11 @@ async function readStdin(opts = {}) {
1365
1365
  }
1366
1366
  return Buffer.concat(chunks).toString("utf8");
1367
1367
  }
1368
- var import_node_fs4, injectedStdin, STDIN_MAX_BYTES, STDIN_DRAIN_TIMEOUT_MS;
1368
+ var import_node_fs5, injectedStdin, STDIN_MAX_BYTES, STDIN_DRAIN_TIMEOUT_MS;
1369
1369
  var init_stdin_inject = __esm({
1370
1370
  "src/stdin-inject.ts"() {
1371
1371
  "use strict";
1372
- import_node_fs4 = require("node:fs");
1372
+ import_node_fs5 = require("node:fs");
1373
1373
  STDIN_MAX_BYTES = 8 * 1024 * 1024;
1374
1374
  STDIN_DRAIN_TIMEOUT_MS = 5e3;
1375
1375
  }
@@ -1599,15 +1599,15 @@ function rawValue(flag, fallback) {
1599
1599
  return inline ? inline.slice(flag.length + 1) : fallback;
1600
1600
  }
1601
1601
  function printLine(value) {
1602
- (0, import_node_fs5.writeSync)(1, `${value}
1602
+ (0, import_node_fs6.writeSync)(1, `${value}
1603
1603
  `);
1604
1604
  }
1605
- var import_node_child_process, import_node_fs5, import_node_util, rawExecFileP, DEFAULT_EXEC_TIMEOUT_MS, execFileP, ghReadTokenProvider, ghWriteTokenProvider, GIT_TIMEOUT_MS, ExecDeadlineError, cachedGithubLogin, gitOut, consoleIo;
1605
+ var import_node_child_process, import_node_fs6, import_node_util, rawExecFileP, DEFAULT_EXEC_TIMEOUT_MS, execFileP, ghReadTokenProvider, ghWriteTokenProvider, GIT_TIMEOUT_MS, ExecDeadlineError, cachedGithubLogin, gitOut, consoleIo;
1606
1606
  var init_cli_shared = __esm({
1607
1607
  "src/cli-shared.ts"() {
1608
1608
  "use strict";
1609
1609
  import_node_child_process = require("node:child_process");
1610
- import_node_fs5 = require("node:fs");
1610
+ import_node_fs6 = require("node:fs");
1611
1611
  import_node_util = require("node:util");
1612
1612
  init_hub_url();
1613
1613
  init_client_version();
@@ -9186,6 +9186,32 @@ __export(index_exports, {
9186
9186
  });
9187
9187
  module.exports = __toCommonJS(index_exports);
9188
9188
 
9189
+ // src/utf8-receipt.ts
9190
+ var import_node_fs = require("node:fs");
9191
+ var import_node_path = require("node:path");
9192
+ function writeUtf8Receipt(path2, text) {
9193
+ (0, import_node_fs.mkdirSync)((0, import_node_path.dirname)(path2), { recursive: true });
9194
+ const body = text.endsWith("\n") ? text : `${text}
9195
+ `;
9196
+ (0, import_node_fs.writeFileSync)(path2, body, "utf8");
9197
+ return Buffer.byteLength(body, "utf8");
9198
+ }
9199
+
9200
+ // src/tenant-control-output.ts
9201
+ var TENANT_CONTROL_JSON_FAILURE_HINT = " (--json: exits 1 on stderr with nothing on stdout: check the exit code before parsing, never merge stderr into stdout (2>&1); use --out <file> so a failed read leaves no file)";
9202
+ function tenantControlFailure(message2, json) {
9203
+ return json ? `${message2}${TENANT_CONTROL_JSON_FAILURE_HINT}` : message2;
9204
+ }
9205
+ function emitTenantControlOutput(text, o, io = { print: console.log }) {
9206
+ if (o.out === void 0) {
9207
+ io.print(text);
9208
+ return;
9209
+ }
9210
+ if (o.failed) return;
9211
+ const bytes = (io.write ?? writeUtf8Receipt)(o.out, text);
9212
+ io.print(`Wrote tenant control ${o.action} to ${o.out} (UTF-8, ${bytes} bytes)`);
9213
+ }
9214
+
9189
9215
  // ../shared/quota-attribution.ts
9190
9216
  var ATTRIBUTION_MAX = 120;
9191
9217
  function sanitizeAttribution(value, max = ATTRIBUTION_MAX) {
@@ -9205,9 +9231,9 @@ var import_node_crypto2 = require("node:crypto");
9205
9231
  var import_node_os5 = require("node:os");
9206
9232
 
9207
9233
  // src/plugin-guard-io.ts
9208
- var import_node_fs7 = require("node:fs");
9234
+ var import_node_fs8 = require("node:fs");
9209
9235
  var import_node_child_process2 = require("node:child_process");
9210
- var import_node_path4 = require("node:path");
9236
+ var import_node_path5 = require("node:path");
9211
9237
  var import_node_os4 = require("node:os");
9212
9238
 
9213
9239
  // src/marketplace-autoupdate.ts
@@ -9439,7 +9465,7 @@ function buildPluginGuardLine(state, opts = {}) {
9439
9465
  }
9440
9466
 
9441
9467
  // src/host-doc-lock.ts
9442
- var import_node_fs6 = require("node:fs");
9468
+ var import_node_fs7 = require("node:fs");
9443
9469
  var import_node_os3 = require("node:os");
9444
9470
  var SHARED_DOC_LOCK_STALE_MS = 3e4;
9445
9471
  var SHARED_DOC_LOCK_ATTEMPTS = 50;
@@ -9453,7 +9479,7 @@ function lockContent(options) {
9453
9479
  }
9454
9480
  function readHolder(lockPath) {
9455
9481
  try {
9456
- const parsed = JSON.parse((0, import_node_fs6.readFileSync)(lockPath, "utf8"));
9482
+ const parsed = JSON.parse((0, import_node_fs7.readFileSync)(lockPath, "utf8"));
9457
9483
  if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return null;
9458
9484
  return parsed;
9459
9485
  } catch {
@@ -9465,7 +9491,7 @@ function lockAgeMs(lockPath, now) {
9465
9491
  const declared = typeof holder?.acquiredAt === "string" ? Date.parse(holder.acquiredAt) : Number.NaN;
9466
9492
  if (!Number.isNaN(declared)) return Math.max(0, now - declared);
9467
9493
  try {
9468
- return Math.max(0, now - (0, import_node_fs6.statSync)(lockPath).mtimeMs);
9494
+ return Math.max(0, now - (0, import_node_fs7.statSync)(lockPath).mtimeMs);
9469
9495
  } catch {
9470
9496
  return 0;
9471
9497
  }
@@ -9488,9 +9514,9 @@ function acquireSharedDocLock(documentPath, options) {
9488
9514
  for (let attempt = 0; attempt < attempts; attempt += 1) {
9489
9515
  let fd;
9490
9516
  try {
9491
- fd = (0, import_node_fs6.openSync)(lockPath, "wx");
9492
- (0, import_node_fs6.writeSync)(fd, content);
9493
- (0, import_node_fs6.closeSync)(fd);
9517
+ fd = (0, import_node_fs7.openSync)(lockPath, "wx");
9518
+ (0, import_node_fs7.writeSync)(fd, content);
9519
+ (0, import_node_fs7.closeSync)(fd);
9494
9520
  return {
9495
9521
  documentPath,
9496
9522
  lockPath,
@@ -9503,11 +9529,11 @@ function acquireSharedDocLock(documentPath, options) {
9503
9529
  } catch (error) {
9504
9530
  if (fd !== void 0) {
9505
9531
  try {
9506
- (0, import_node_fs6.closeSync)(fd);
9532
+ (0, import_node_fs7.closeSync)(fd);
9507
9533
  } catch {
9508
9534
  }
9509
9535
  try {
9510
- (0, import_node_fs6.unlinkSync)(lockPath);
9536
+ (0, import_node_fs7.unlinkSync)(lockPath);
9511
9537
  } catch {
9512
9538
  }
9513
9539
  }
@@ -9516,12 +9542,12 @@ function acquireSharedDocLock(documentPath, options) {
9516
9542
  if (lockAgeMs(lockPath, Date.now()) >= staleAfterMs) {
9517
9543
  const aside = `${lockPath}.stale-${Date.now()}`;
9518
9544
  try {
9519
- (0, import_node_fs6.renameSync)(lockPath, aside);
9545
+ (0, import_node_fs7.renameSync)(lockPath, aside);
9520
9546
  } catch {
9521
9547
  continue;
9522
9548
  }
9523
9549
  try {
9524
- (0, import_node_fs6.rmSync)(aside, { force: true });
9550
+ (0, import_node_fs7.rmSync)(aside, { force: true });
9525
9551
  } catch {
9526
9552
  }
9527
9553
  continue;
@@ -9534,7 +9560,7 @@ function releaseSharedDocLock(handle) {
9534
9560
  try {
9535
9561
  const holder = readHolder(handle.lockPath);
9536
9562
  if (holder && holder.owner === handle.owner && Number(holder.pid) === handle.pid) {
9537
- (0, import_node_fs6.unlinkSync)(handle.lockPath);
9563
+ (0, import_node_fs7.unlinkSync)(handle.lockPath);
9538
9564
  }
9539
9565
  } catch {
9540
9566
  }
@@ -9560,8 +9586,8 @@ function insideOpenBotAgentWorkspace(cwd, env = process.env, home = (0, import_n
9560
9586
  } catch {
9561
9587
  return false;
9562
9588
  }
9563
- const rel = (0, import_node_path4.relative)(openbotAgentsRoot(env, home), dir);
9564
- return !(0, import_node_path4.isAbsolute)(rel) && (rel.split(/[\\/]/)[0] ?? "").toLowerCase().startsWith("agent-");
9589
+ const rel = (0, import_node_path5.relative)(openbotAgentsRoot(env, home), dir);
9590
+ return !(0, import_node_path5.isAbsolute)(rel) && (rel.split(/[\\/]/)[0] ?? "").toLowerCase().startsWith("agent-");
9565
9591
  }
9566
9592
  function detectSurface(env, cwd) {
9567
9593
  const has = (k) => Boolean(env[k]?.trim());
@@ -9692,30 +9718,30 @@ function runHostBin(bin, args, opts) {
9692
9718
  return step ? execFileHard(file, argv, { ...shared, step }) : execFileP(file, argv, shared);
9693
9719
  }
9694
9720
  function openbotAgentsRoot(env = process.env, home = (0, import_node_os4.homedir)()) {
9695
- return env.MMI_UPDATER_OPENBOT_AGENTS_DIR?.trim() || (0, import_node_path4.join)(home, "OpenBot", "Agents");
9721
+ return env.MMI_UPDATER_OPENBOT_AGENTS_DIR?.trim() || (0, import_node_path5.join)(home, "OpenBot", "Agents");
9696
9722
  }
9697
9723
  function openbotAgentWorkspaces(env = process.env, home = (0, import_node_os4.homedir)()) {
9698
9724
  const root = openbotAgentsRoot(env, home);
9699
9725
  try {
9700
- 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();
9726
+ return (0, import_node_fs8.readdirSync)(root, { withFileTypes: true }).filter((entry) => entry.isDirectory() && entry.name.startsWith("agent-")).map((entry) => (0, import_node_path5.join)(root, entry.name)).sort();
9701
9727
  } catch {
9702
9728
  return [];
9703
9729
  }
9704
9730
  }
9705
9731
  function surfaceConfigRoot(surface, env = process.env, home = (0, import_node_os4.homedir)()) {
9706
- if (surface === "codex") return env.CODEX_HOME?.trim() || (0, import_node_path4.join)(home, ".codex");
9732
+ if (surface === "codex") return env.CODEX_HOME?.trim() || (0, import_node_path5.join)(home, ".codex");
9707
9733
  if (surface === "openbot") return openbotAgentsRoot(env, home);
9708
9734
  if (surface === "jervcode") {
9709
- return env.JERVCODE_CODING_AGENT_DIR?.trim() || env.PI_CODING_AGENT_DIR?.trim() || (0, import_node_path4.join)(home, ".jerv", "agent");
9735
+ return env.JERVCODE_CODING_AGENT_DIR?.trim() || env.PI_CODING_AGENT_DIR?.trim() || (0, import_node_path5.join)(home, ".jerv", "agent");
9710
9736
  }
9711
- return (0, import_node_path4.join)(home, ".claude");
9737
+ return (0, import_node_path5.join)(home, ".claude");
9712
9738
  }
9713
9739
  var installedPluginsPath = (surface = detectSurface(process.env)) => {
9714
- return (0, import_node_path4.join)(surfaceConfigRoot(surface), "plugins", "installed_plugins.json");
9740
+ return (0, import_node_path5.join)(surfaceConfigRoot(surface), "plugins", "installed_plugins.json");
9715
9741
  };
9716
9742
  function readInstalledPlugins(surface = detectSurface(process.env)) {
9717
9743
  try {
9718
- return JSON.parse((0, import_node_fs7.readFileSync)(installedPluginsPath(surface), "utf8"));
9744
+ return JSON.parse((0, import_node_fs8.readFileSync)(installedPluginsPath(surface), "utf8"));
9719
9745
  } catch {
9720
9746
  return null;
9721
9747
  }
@@ -9724,15 +9750,15 @@ function marketplaceCloneCandidates(surface, home, env = process.env) {
9724
9750
  if (surface === "codex") {
9725
9751
  const root = surfaceConfigRoot(surface, env, home);
9726
9752
  return [
9727
- (0, import_node_path4.join)(root, ".tmp", "marketplaces", CODEX_MARKETPLACE),
9728
- (0, import_node_path4.join)(root, "plugins", "marketplaces", CODEX_MARKETPLACE)
9753
+ (0, import_node_path5.join)(root, ".tmp", "marketplaces", CODEX_MARKETPLACE),
9754
+ (0, import_node_path5.join)(root, "plugins", "marketplaces", CODEX_MARKETPLACE)
9729
9755
  ];
9730
9756
  }
9731
9757
  if (surface === "openbot") return [];
9732
9758
  if (surface === "jervcode") return [];
9733
- return [(0, import_node_path4.join)(home, ".claude", "plugins", "marketplaces", "mutmutco")];
9759
+ return [(0, import_node_path5.join)(home, ".claude", "plugins", "marketplaces", "mutmutco")];
9734
9760
  }
9735
- function marketplaceClonePresent(surface, home, exists = import_node_fs7.existsSync, env = process.env) {
9761
+ function marketplaceClonePresent(surface, home, exists = import_node_fs8.existsSync, env = process.env) {
9736
9762
  return marketplaceCloneCandidates(surface, home, env).some(exists);
9737
9763
  }
9738
9764
  function runHostBinSync(bin, args) {
@@ -9776,15 +9802,15 @@ async function fetchNpmReleasedVersion() {
9776
9802
  var JERVCODE_NPM_PACKAGE = "@mutmutco/pi-plugin";
9777
9803
  function jervcodeAgentDirs(env = process.env, home = (0, import_node_os4.homedir)()) {
9778
9804
  const primary = surfaceConfigRoot("jervcode", env, home);
9779
- const legacy = (0, import_node_path4.join)(home, ".pi", "agent");
9805
+ const legacy = (0, import_node_path5.join)(home, ".pi", "agent");
9780
9806
  const dirs = [primary];
9781
9807
  if (legacy !== primary) dirs.push(legacy);
9782
- return dirs.filter((dir) => (0, import_node_fs7.existsSync)(dir));
9808
+ return dirs.filter((dir) => (0, import_node_fs8.existsSync)(dir));
9783
9809
  }
9784
9810
  function readPiSettings(path2) {
9785
- if (!(0, import_node_fs7.existsSync)(path2)) return void 0;
9811
+ if (!(0, import_node_fs8.existsSync)(path2)) return void 0;
9786
9812
  try {
9787
- const parsed = JSON.parse((0, import_node_fs7.readFileSync)(path2, "utf8"));
9813
+ const parsed = JSON.parse((0, import_node_fs8.readFileSync)(path2, "utf8"));
9788
9814
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return null;
9789
9815
  return parsed;
9790
9816
  } catch {
@@ -9793,7 +9819,7 @@ function readPiSettings(path2) {
9793
9819
  }
9794
9820
  function mmiPiPackageEntry(env = process.env, home = (0, import_node_os4.homedir)()) {
9795
9821
  for (const dir of jervcodeAgentDirs(env, home)) {
9796
- const settings = readPiSettings((0, import_node_path4.join)(dir, "settings.json"));
9822
+ const settings = readPiSettings((0, import_node_path5.join)(dir, "settings.json"));
9797
9823
  const entries = Array.isArray(settings?.packages) ? settings.packages : [];
9798
9824
  for (const entry of entries) {
9799
9825
  if (typeof entry !== "string") continue;
@@ -9806,7 +9832,7 @@ var LEGACY_MMI_PI_PATH = /[/\\]mutmutco[/\\]mmi[/\\]\d+\.\d+\.\d+[/\\]\.pi-plugi
9806
9832
  function legacyMmiPiPathEntries(env = process.env, home = (0, import_node_os4.homedir)()) {
9807
9833
  const found = [];
9808
9834
  for (const dir of jervcodeAgentDirs(env, home)) {
9809
- const settings = readPiSettings((0, import_node_path4.join)(dir, "settings.json"));
9835
+ const settings = readPiSettings((0, import_node_path5.join)(dir, "settings.json"));
9810
9836
  const entries = Array.isArray(settings?.packages) ? settings.packages : [];
9811
9837
  for (const entry of entries) {
9812
9838
  if (typeof entry === "string" && LEGACY_MMI_PI_PATH.test(entry) && !found.includes(entry)) found.push(entry);
@@ -9816,8 +9842,8 @@ function legacyMmiPiPathEntries(env = process.env, home = (0, import_node_os4.ho
9816
9842
  }
9817
9843
  function mmiPiPackageRoot(env = process.env, home = (0, import_node_os4.homedir)()) {
9818
9844
  for (const dir of jervcodeAgentDirs(env, home)) {
9819
- const root = (0, import_node_path4.join)(dir, "npm", "node_modules", ...JERVCODE_NPM_PACKAGE.split("/"));
9820
- if ((0, import_node_fs7.existsSync)((0, import_node_path4.join)(root, "package.json"))) return root;
9845
+ const root = (0, import_node_path5.join)(dir, "npm", "node_modules", ...JERVCODE_NPM_PACKAGE.split("/"));
9846
+ if ((0, import_node_fs8.existsSync)((0, import_node_path5.join)(root, "package.json"))) return root;
9821
9847
  }
9822
9848
  return null;
9823
9849
  }
@@ -9825,7 +9851,7 @@ function installedJervCodePackageVersion(env = process.env, home = (0, import_no
9825
9851
  const root = mmiPiPackageRoot(env, home);
9826
9852
  if (!root) return void 0;
9827
9853
  try {
9828
- const manifest = JSON.parse((0, import_node_fs7.readFileSync)((0, import_node_path4.join)(root, "package.json"), "utf8"));
9854
+ const manifest = JSON.parse((0, import_node_fs8.readFileSync)((0, import_node_path5.join)(root, "package.json"), "utf8"));
9829
9855
  return typeof manifest.version === "string" ? manifest.version : void 0;
9830
9856
  } catch {
9831
9857
  return void 0;
@@ -9833,17 +9859,17 @@ function installedJervCodePackageVersion(env = process.env, home = (0, import_no
9833
9859
  }
9834
9860
  function mmiPiPackageHealthy(env = process.env, home = (0, import_node_os4.homedir)()) {
9835
9861
  const root = mmiPiPackageRoot(env, home);
9836
- return Boolean(root && (0, import_node_fs7.existsSync)((0, import_node_path4.join)(root, "skills", "bootstrap", "SKILL.md")));
9862
+ return Boolean(root && (0, import_node_fs8.existsSync)((0, import_node_path5.join)(root, "skills", "bootstrap", "SKILL.md")));
9837
9863
  }
9838
9864
  function piDoctorEnv(agentDir) {
9839
9865
  return { ...process.env, PI_CODING_AGENT_DIR: agentDir };
9840
9866
  }
9841
9867
  function openbotManagedRecord(env = process.env, home = (0, import_node_os4.homedir)()) {
9842
9868
  for (const workspace of openbotAgentWorkspaces(env, home)) {
9843
- const path2 = (0, import_node_path4.join)(workspace, ".agents", "skills", ".mmi-managed.json");
9844
- if (!(0, import_node_fs7.existsSync)(path2)) continue;
9869
+ const path2 = (0, import_node_path5.join)(workspace, ".agents", "skills", ".mmi-managed.json");
9870
+ if (!(0, import_node_fs8.existsSync)(path2)) continue;
9845
9871
  try {
9846
- const record2 = JSON.parse((0, import_node_fs7.readFileSync)(path2, "utf8"));
9872
+ const record2 = JSON.parse((0, import_node_fs8.readFileSync)(path2, "utf8"));
9847
9873
  return { workspace, ...typeof record2.version === "string" ? { version: record2.version } : {} };
9848
9874
  } catch {
9849
9875
  return { workspace };
@@ -9855,9 +9881,9 @@ function openbotWorkspaceSkillsHealthy(env = process.env, home = (0, import_node
9855
9881
  const record2 = openbotManagedRecord(env, home);
9856
9882
  if (!record2) return false;
9857
9883
  try {
9858
- const recordFile = JSON.parse((0, import_node_fs7.readFileSync)((0, import_node_path4.join)(record2.workspace, ".agents", "skills", ".mmi-managed.json"), "utf8"));
9884
+ const recordFile = JSON.parse((0, import_node_fs8.readFileSync)((0, import_node_path5.join)(record2.workspace, ".agents", "skills", ".mmi-managed.json"), "utf8"));
9859
9885
  const owned = Array.isArray(recordFile.skills) ? recordFile.skills : [];
9860
- 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")));
9886
+ return owned.length > 0 && owned.every((name) => typeof name === "string" && (0, import_node_fs8.existsSync)((0, import_node_path5.join)(record2.workspace, ".agents", "skills", name, "SKILL.md")));
9861
9887
  } catch {
9862
9888
  return false;
9863
9889
  }
@@ -9877,8 +9903,8 @@ function snapshotPluginGuardInput(surface = detectSurface(process.env), isOrgRep
9877
9903
  // JervCode's settings entry and native local surfaces have no marketplace clone.
9878
9904
  marketplaceClonePresent: surface === "jervcode" || surface === "openbot" ? true : marketplaceClonePresent(surface, (0, import_node_os4.homedir)()),
9879
9905
  pluginCachePresent: surface === "jervcode" ? mmiPiPackageHealthy() : surface === "openbot" ? openbotWorkspaceSkillsHealthy() : surface === "codex" ? Boolean(
9880
- codexStatus?.installed && codexStatus.enabled && codexStatus.version && (0, import_node_fs7.existsSync)((0, import_node_path4.join)(root, "plugins", "cache", CODEX_MARKETPLACE, "mmi", codexStatus.version))
9881
- ) : (0, import_node_fs7.existsSync)((0, import_node_path4.join)(root, "plugins", "cache", "mutmutco", "mmi"))
9906
+ codexStatus?.installed && codexStatus.enabled && codexStatus.version && (0, import_node_fs8.existsSync)((0, import_node_path5.join)(root, "plugins", "cache", CODEX_MARKETPLACE, "mmi", codexStatus.version))
9907
+ ) : (0, import_node_fs8.existsSync)((0, import_node_path5.join)(root, "plugins", "cache", "mutmutco", "mmi"))
9882
9908
  };
9883
9909
  }
9884
9910
  async function runPluginCli(bin, args, log) {
@@ -9925,7 +9951,7 @@ async function runHealSteps(host, tableSteps, deps) {
9925
9951
  const refSupported = needsRefProbe ? await marketplaceAddRefSupported(host) : true;
9926
9952
  const { steps } = adaptHealStepsForRefSupport(tableSteps, refSupported);
9927
9953
  if (deps.banner) log(deps.banner(refSupported));
9928
- const pinsPath = (0, import_node_path4.join)((0, import_node_os4.homedir)(), ...KNOWN_MARKETPLACES_RELATIVE);
9954
+ const pinsPath = (0, import_node_path5.join)((0, import_node_os4.homedir)(), ...KNOWN_MARKETPLACES_RELATIVE);
9929
9955
  const pins = host === "claude" ? captureMarketplacePins(readKnownMarketplacesFile(pinsPath), [MMI_MARKETPLACE_NAME, JERV_MARKETPLACE_NAME]) : /* @__PURE__ */ new Map();
9930
9956
  let failure;
9931
9957
  try {
@@ -10036,7 +10062,7 @@ async function healActivePluginForDoctor(surface = detectSurface(process.env), o
10036
10062
  }
10037
10063
  function readKnownMarketplacesFile(path2) {
10038
10064
  try {
10039
- return (0, import_node_fs7.existsSync)(path2) ? (0, import_node_fs7.readFileSync)(path2, "utf8") : void 0;
10065
+ return (0, import_node_fs8.existsSync)(path2) ? (0, import_node_fs8.readFileSync)(path2, "utf8") : void 0;
10040
10066
  } catch {
10041
10067
  return void 0;
10042
10068
  }
@@ -10070,11 +10096,11 @@ function writeMarketplacePinsOnDisk(path2, pins, succeeded, failedVerb, declineW
10070
10096
  if (declined) return declined;
10071
10097
  const tmp = `${path2}.tmp-${process.pid}`;
10072
10098
  try {
10073
- (0, import_node_fs7.writeFileSync)(tmp, next, "utf8");
10074
- (0, import_node_fs7.renameSync)(tmp, path2);
10099
+ (0, import_node_fs8.writeFileSync)(tmp, next, "utf8");
10100
+ (0, import_node_fs8.renameSync)(tmp, path2);
10075
10101
  } catch {
10076
10102
  try {
10077
- (0, import_node_fs7.rmSync)(tmp, { force: true });
10103
+ (0, import_node_fs8.rmSync)(tmp, { force: true });
10078
10104
  } catch {
10079
10105
  }
10080
10106
  return `could NOT ${failedVerb} ${[...pins.keys()].join(", ")} \u2014 set it by hand`;
@@ -10383,8 +10409,8 @@ function humanReadableArgName(arg2) {
10383
10409
  // node_modules/commander/lib/command.js
10384
10410
  var import_node_events = require("node:events");
10385
10411
  var import_node_child_process3 = __toESM(require("node:child_process"), 1);
10386
- var import_node_path5 = __toESM(require("node:path"), 1);
10387
- var import_node_fs8 = __toESM(require("node:fs"), 1);
10412
+ var import_node_path6 = __toESM(require("node:path"), 1);
10413
+ var import_node_fs9 = __toESM(require("node:fs"), 1);
10388
10414
  var import_node_process = __toESM(require("node:process"), 1);
10389
10415
  var import_node_util3 = require("node:util");
10390
10416
 
@@ -12352,7 +12378,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
12352
12378
  * @param {string} subcommandName
12353
12379
  */
12354
12380
  _checkForMissingExecutable(executableFile, executableDir, subcommandName) {
12355
- if (import_node_fs8.default.existsSync(executableFile)) return;
12381
+ if (import_node_fs9.default.existsSync(executableFile)) return;
12356
12382
  const executableDirMessage = executableDir ? `searched for local subcommand relative to directory '${executableDir}'` : "no directory for search for local subcommand, use .executableDir() to supply a custom directory";
12357
12383
  const executableMissing = `'${executableFile}' does not exist
12358
12384
  - if '${subcommandName}' is not meant to be an executable command, remove description parameter from '.command()' and use '.description()' instead
@@ -12369,11 +12395,11 @@ Expecting one of '${allowedValues.join("', '")}'`);
12369
12395
  args = args.slice();
12370
12396
  const sourceExt = [".js", ".ts", ".tsx", ".mjs", ".cjs"];
12371
12397
  function findFile(baseDir, baseName) {
12372
- const localBin = import_node_path5.default.resolve(baseDir, baseName);
12373
- if (import_node_fs8.default.existsSync(localBin)) return localBin;
12374
- if (sourceExt.includes(import_node_path5.default.extname(baseName))) return void 0;
12398
+ const localBin = import_node_path6.default.resolve(baseDir, baseName);
12399
+ if (import_node_fs9.default.existsSync(localBin)) return localBin;
12400
+ if (sourceExt.includes(import_node_path6.default.extname(baseName))) return void 0;
12375
12401
  const foundExt = sourceExt.find(
12376
- (ext) => import_node_fs8.default.existsSync(`${localBin}${ext}`)
12402
+ (ext) => import_node_fs9.default.existsSync(`${localBin}${ext}`)
12377
12403
  );
12378
12404
  if (foundExt) return `${localBin}${foundExt}`;
12379
12405
  return void 0;
@@ -12385,21 +12411,21 @@ Expecting one of '${allowedValues.join("', '")}'`);
12385
12411
  if (this._scriptPath) {
12386
12412
  let resolvedScriptPath;
12387
12413
  try {
12388
- resolvedScriptPath = import_node_fs8.default.realpathSync(this._scriptPath);
12414
+ resolvedScriptPath = import_node_fs9.default.realpathSync(this._scriptPath);
12389
12415
  } catch {
12390
12416
  resolvedScriptPath = this._scriptPath;
12391
12417
  }
12392
- executableDir = import_node_path5.default.resolve(
12393
- import_node_path5.default.dirname(resolvedScriptPath),
12418
+ executableDir = import_node_path6.default.resolve(
12419
+ import_node_path6.default.dirname(resolvedScriptPath),
12394
12420
  executableDir
12395
12421
  );
12396
12422
  }
12397
12423
  if (executableDir) {
12398
12424
  let localFile = findFile(executableDir, executableFile);
12399
12425
  if (!localFile && !subcommand._executableFile && this._scriptPath) {
12400
- const legacyName = import_node_path5.default.basename(
12426
+ const legacyName = import_node_path6.default.basename(
12401
12427
  this._scriptPath,
12402
- import_node_path5.default.extname(this._scriptPath)
12428
+ import_node_path6.default.extname(this._scriptPath)
12403
12429
  );
12404
12430
  if (legacyName !== this._name) {
12405
12431
  localFile = findFile(
@@ -12410,7 +12436,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
12410
12436
  }
12411
12437
  executableFile = localFile || executableFile;
12412
12438
  }
12413
- const launchWithNode = sourceExt.includes(import_node_path5.default.extname(executableFile));
12439
+ const launchWithNode = sourceExt.includes(import_node_path6.default.extname(executableFile));
12414
12440
  let proc;
12415
12441
  if (import_node_process.default.platform !== "win32") {
12416
12442
  if (launchWithNode) {
@@ -13327,7 +13353,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
13327
13353
  * @return {Command}
13328
13354
  */
13329
13355
  nameFromFilename(filename) {
13330
- this._name = import_node_path5.default.basename(filename, import_node_path5.default.extname(filename));
13356
+ this._name = import_node_path6.default.basename(filename, import_node_path6.default.extname(filename));
13331
13357
  return this;
13332
13358
  }
13333
13359
  /**
@@ -13604,27 +13630,27 @@ init_clean_exit();
13604
13630
  init_cli_shared();
13605
13631
 
13606
13632
  // src/doctor-fleet-drift.ts
13607
- var import_node_fs9 = require("node:fs");
13633
+ var import_node_fs10 = require("node:fs");
13608
13634
  var import_node_os6 = require("node:os");
13609
- var import_node_path6 = require("node:path");
13635
+ var import_node_path7 = require("node:path");
13610
13636
  var ALWAYS_ARMED_SURFACES = ["cli", "updater"];
13611
13637
  function updaterJournalPath(env = process.env) {
13612
- return (0, import_node_path6.join)(env.MMI_UPDATER_HOME || (0, import_node_path6.join)((0, import_node_os6.homedir)(), ".mmi", "updater"), "journal.jsonl");
13638
+ return (0, import_node_path7.join)(env.MMI_UPDATER_HOME || (0, import_node_path7.join)((0, import_node_os6.homedir)(), ".mmi", "updater"), "journal.jsonl");
13613
13639
  }
13614
13640
  var JOURNAL_TAIL_BYTES = 256 * 1024;
13615
13641
  function readJournalTail(path2, maxBytes) {
13616
- const fd = (0, import_node_fs9.openSync)(path2, "r");
13642
+ const fd = (0, import_node_fs10.openSync)(path2, "r");
13617
13643
  try {
13618
- const size = (0, import_node_fs9.fstatSync)(fd).size;
13644
+ const size = (0, import_node_fs10.fstatSync)(fd).size;
13619
13645
  const length = Math.min(size, maxBytes);
13620
13646
  const buffer = Buffer.alloc(length);
13621
- if (length) (0, import_node_fs9.readSync)(fd, buffer, 0, length, size - length);
13647
+ if (length) (0, import_node_fs10.readSync)(fd, buffer, 0, length, size - length);
13622
13648
  const text = buffer.toString("utf8");
13623
13649
  if (length === size) return { text, truncated: false };
13624
13650
  const firstBreak = text.indexOf("\n");
13625
13651
  return { text: firstBreak === -1 ? "" : text.slice(firstBreak + 1), truncated: true };
13626
13652
  } finally {
13627
- (0, import_node_fs9.closeSync)(fd);
13653
+ (0, import_node_fs10.closeSync)(fd);
13628
13654
  }
13629
13655
  }
13630
13656
  function parseFleetDrift(journalPath, text, activeSurfaces, truncated = false) {
@@ -14333,14 +14359,14 @@ function classifyParseError(plain) {
14333
14359
  }
14334
14360
 
14335
14361
  // src/released-version-cache.ts
14336
- var import_node_fs10 = require("node:fs");
14337
- var import_node_path7 = require("node:path");
14362
+ var import_node_fs11 = require("node:fs");
14363
+ var import_node_path8 = require("node:path");
14338
14364
  init_client_version();
14339
14365
  var RELEASED_VERSION_CACHE_MS = 24 * 36e5;
14340
14366
  function releasedVersionCachePath(runtimeRoot) {
14341
- return (0, import_node_path7.join)(runtimeRoot, "head-ts", ".released-version");
14367
+ return (0, import_node_path8.join)(runtimeRoot, "head-ts", ".released-version");
14342
14368
  }
14343
- function readReleasedVersionCache(cachePath, now = Date.now(), read = import_node_fs10.readFileSync, currentVersion = resolveClientVersion()) {
14369
+ function readReleasedVersionCache(cachePath, now = Date.now(), read = import_node_fs11.readFileSync, currentVersion = resolveClientVersion()) {
14344
14370
  let parsed;
14345
14371
  try {
14346
14372
  parsed = JSON.parse(read(cachePath, "utf8"));
@@ -14358,8 +14384,8 @@ function readReleasedVersionCache(cachePath, now = Date.now(), read = import_nod
14358
14384
  }
14359
14385
  function writeReleasedVersionCache(cachePath, version, now = Date.now()) {
14360
14386
  try {
14361
- (0, import_node_fs10.mkdirSync)((0, import_node_path7.dirname)(cachePath), { recursive: true });
14362
- (0, import_node_fs10.writeFileSync)(cachePath, JSON.stringify({ version, at: now }), "utf8");
14387
+ (0, import_node_fs11.mkdirSync)((0, import_node_path8.dirname)(cachePath), { recursive: true });
14388
+ (0, import_node_fs11.writeFileSync)(cachePath, JSON.stringify({ version, at: now }), "utf8");
14363
14389
  } catch {
14364
14390
  }
14365
14391
  }
@@ -14372,47 +14398,47 @@ function cachedReadNote(cachedAt, now = Date.now()) {
14372
14398
  }
14373
14399
 
14374
14400
  // src/archive-prune.ts
14375
- var import_node_fs13 = require("node:fs");
14376
- var import_node_path10 = require("node:path");
14401
+ var import_node_fs14 = require("node:fs");
14402
+ var import_node_path11 = require("node:path");
14377
14403
 
14378
14404
  // src/worktree-evidence-archive.ts
14379
- var import_node_fs12 = require("node:fs");
14380
- var import_node_path9 = require("node:path");
14405
+ var import_node_fs13 = require("node:fs");
14406
+ var import_node_path10 = require("node:path");
14381
14407
 
14382
14408
  // src/repo-runtime-state.ts
14383
14409
  var import_node_crypto3 = require("node:crypto");
14384
- var import_node_fs11 = require("node:fs");
14410
+ var import_node_fs12 = require("node:fs");
14385
14411
  var import_node_os7 = require("node:os");
14386
- var import_node_path8 = require("node:path");
14412
+ var import_node_path9 = require("node:path");
14387
14413
  var RUNTIME_DIR = "mmi-runtime";
14388
14414
  function hashPath(path2) {
14389
- return (0, import_node_crypto3.createHash)("sha256").update((0, import_node_path8.resolve)(path2)).digest("hex").slice(0, 16);
14415
+ return (0, import_node_crypto3.createHash)("sha256").update((0, import_node_path9.resolve)(path2)).digest("hex").slice(0, 16);
14390
14416
  }
14391
14417
  function resolveGitDir(cwd) {
14392
- const dotGit = (0, import_node_path8.join)(cwd, ".git");
14418
+ const dotGit = (0, import_node_path9.join)(cwd, ".git");
14393
14419
  try {
14394
- const st = (0, import_node_fs11.statSync)(dotGit);
14420
+ const st = (0, import_node_fs12.statSync)(dotGit);
14395
14421
  if (st.isDirectory()) return dotGit;
14396
14422
  if (!st.isFile()) return void 0;
14397
14423
  } catch {
14398
14424
  return void 0;
14399
14425
  }
14400
14426
  try {
14401
- const raw = (0, import_node_fs11.readFileSync)(dotGit, "utf8").trim();
14427
+ const raw = (0, import_node_fs12.readFileSync)(dotGit, "utf8").trim();
14402
14428
  const match = /^gitdir:\s*(.+)$/i.exec(raw);
14403
14429
  if (!match) return void 0;
14404
14430
  const gitdir = match[1].trim();
14405
- return (0, import_node_path8.isAbsolute)(gitdir) ? gitdir : (0, import_node_path8.resolve)(cwd, gitdir);
14431
+ return (0, import_node_path9.isAbsolute)(gitdir) ? gitdir : (0, import_node_path9.resolve)(cwd, gitdir);
14406
14432
  } catch {
14407
14433
  return void 0;
14408
14434
  }
14409
14435
  }
14410
14436
  function resolveGitDirFrom(cwd) {
14411
- let dir = (0, import_node_path8.resolve)(cwd);
14437
+ let dir = (0, import_node_path9.resolve)(cwd);
14412
14438
  for (; ; ) {
14413
14439
  const found = resolveGitDir(dir);
14414
14440
  if (found) return found;
14415
- const parent = (0, import_node_path8.dirname)(dir);
14441
+ const parent = (0, import_node_path9.dirname)(dir);
14416
14442
  if (parent === dir) return void 0;
14417
14443
  dir = parent;
14418
14444
  }
@@ -14426,18 +14452,18 @@ function remoteOriginUrlFromConfig(configText) {
14426
14452
  function originUrlFromGitConfig(cwd = process.cwd()) {
14427
14453
  const gitDir = resolveGitDirFrom(cwd);
14428
14454
  if (!gitDir) return void 0;
14429
- const configs = [(0, import_node_path8.join)(gitDir, "config")];
14455
+ const configs = [(0, import_node_path9.join)(gitDir, "config")];
14430
14456
  try {
14431
- const common = (0, import_node_fs11.readFileSync)((0, import_node_path8.join)(gitDir, "commondir"), "utf8").trim();
14457
+ const common = (0, import_node_fs12.readFileSync)((0, import_node_path9.join)(gitDir, "commondir"), "utf8").trim();
14432
14458
  if (common) {
14433
- const commonDir = (0, import_node_path8.isAbsolute)(common) ? common : (0, import_node_path8.resolve)(gitDir, common);
14434
- configs.push((0, import_node_path8.join)(commonDir, "config"));
14459
+ const commonDir = (0, import_node_path9.isAbsolute)(common) ? common : (0, import_node_path9.resolve)(gitDir, common);
14460
+ configs.push((0, import_node_path9.join)(commonDir, "config"));
14435
14461
  }
14436
14462
  } catch {
14437
14463
  }
14438
14464
  for (const path2 of configs) {
14439
14465
  try {
14440
- const url = remoteOriginUrlFromConfig((0, import_node_fs11.readFileSync)(path2, "utf8"));
14466
+ const url = remoteOriginUrlFromConfig((0, import_node_fs12.readFileSync)(path2, "utf8"));
14441
14467
  if (url) return url;
14442
14468
  } catch {
14443
14469
  }
@@ -14446,8 +14472,8 @@ function originUrlFromGitConfig(cwd = process.cwd()) {
14446
14472
  }
14447
14473
  function repoRuntimeStatePath(cwd, ...parts) {
14448
14474
  const gitDir = resolveGitDir(cwd);
14449
- if (gitDir && (0, import_node_fs11.existsSync)(gitDir)) return (0, import_node_path8.join)(gitDir, RUNTIME_DIR, ...parts);
14450
- return (0, import_node_path8.join)((0, import_node_os7.tmpdir)(), "mmi-cli", hashPath(cwd), ...parts);
14475
+ if (gitDir && (0, import_node_fs12.existsSync)(gitDir)) return (0, import_node_path9.join)(gitDir, RUNTIME_DIR, ...parts);
14476
+ return (0, import_node_path9.join)((0, import_node_os7.tmpdir)(), "mmi-cli", hashPath(cwd), ...parts);
14451
14477
  }
14452
14478
 
14453
14479
  // src/worktree-evidence-archive.ts
@@ -14467,24 +14493,24 @@ function resolveArtifactRunScope(env = process.env) {
14467
14493
  }
14468
14494
  function defaultIsDirectory(path2) {
14469
14495
  try {
14470
- return (0, import_node_fs12.lstatSync)(path2).isDirectory();
14496
+ return (0, import_node_fs13.lstatSync)(path2).isDirectory();
14471
14497
  } catch {
14472
14498
  return false;
14473
14499
  }
14474
14500
  }
14475
14501
  function archiveWorktreeJervArtifacts(args, deps = {}) {
14476
- const exists = deps.exists ?? import_node_fs12.existsSync;
14502
+ const exists = deps.exists ?? import_node_fs13.existsSync;
14477
14503
  const isDirectory = deps.isDirectory ?? defaultIsDirectory;
14478
- const copyDir = deps.copyDir ?? ((from, to) => (0, import_node_fs12.cpSync)(from, to, { recursive: true, force: true }));
14504
+ const copyDir = deps.copyDir ?? ((from, to) => (0, import_node_fs13.cpSync)(from, to, { recursive: true, force: true }));
14479
14505
  const mkdirp = deps.mkdirp ?? ((path2) => {
14480
- (0, import_node_fs12.mkdirSync)(path2, { recursive: true });
14506
+ (0, import_node_fs13.mkdirSync)(path2, { recursive: true });
14481
14507
  });
14482
14508
  const env = deps.env ?? process.env;
14483
14509
  const now = deps.now ?? (() => /* @__PURE__ */ new Date());
14484
14510
  const resolveRoot = deps.resolveArchiveRoot ?? repoRuntimeStatePath;
14485
14511
  if (!args.primaryRoot?.trim()) return { status: "skipped", reason: "missing-primary-root" };
14486
14512
  if (!args.worktreePath?.trim()) return { status: "skipped", reason: "missing-worktree-path" };
14487
- const source = (0, import_node_path9.join)(args.worktreePath, ".jerv");
14513
+ const source = (0, import_node_path10.join)(args.worktreePath, ".jerv");
14488
14514
  if (!exists(source)) return { status: "absent" };
14489
14515
  if (!isDirectory(source)) return { status: "skipped", reason: "jerv-not-a-directory" };
14490
14516
  const runScope = resolveArtifactRunScope(env);
@@ -14492,7 +14518,7 @@ function archiveWorktreeJervArtifacts(args, deps = {}) {
14492
14518
  const stamp = now().toISOString().replace(/[:.]/g, "-");
14493
14519
  const dest = resolveRoot(args.primaryRoot, "jerv-artifacts", runScope, branchSlug, stamp, ".jerv");
14494
14520
  try {
14495
- mkdirp((0, import_node_path9.dirname)(dest));
14521
+ mkdirp((0, import_node_path10.dirname)(dest));
14496
14522
  copyDir(source, dest);
14497
14523
  if (!exists(dest)) return { status: "failed", error: `archive write left no directory at ${dest}` };
14498
14524
  return { status: "archived", path: dest, runScope };
@@ -14501,27 +14527,27 @@ function archiveWorktreeJervArtifacts(args, deps = {}) {
14501
14527
  }
14502
14528
  }
14503
14529
  function defaultStat(path2) {
14504
- const st = (0, import_node_fs12.lstatSync)(path2);
14530
+ const st = (0, import_node_fs13.lstatSync)(path2);
14505
14531
  return { mtimeMs: st.mtimeMs, size: st.size, isDirectory: () => st.isDirectory(), isSymbolicLink: () => st.isSymbolicLink() };
14506
14532
  }
14507
14533
  function scanWorktreeTmpEvidenceDetailed(worktreePath, newerThanMs, deps = {}) {
14508
- const exists = deps.exists ?? import_node_fs12.existsSync;
14534
+ const exists = deps.exists ?? import_node_fs13.existsSync;
14509
14535
  const stat4 = deps.stat ?? defaultStat;
14510
- const readdir2 = deps.readdir ?? import_node_fs12.readdirSync;
14536
+ const readdir2 = deps.readdir ?? import_node_fs13.readdirSync;
14511
14537
  const scan = { entries: [], excluded: [], links: [], unreadable: [] };
14512
- const tmpRoot = (0, import_node_path9.join)(worktreePath, "tmp");
14538
+ const tmpRoot = (0, import_node_path10.join)(worktreePath, "tmp");
14513
14539
  if (!exists(tmpRoot)) return scan;
14514
14540
  const walk2 = (dir, preserved) => {
14515
14541
  let names;
14516
14542
  try {
14517
14543
  names = readdir2(dir);
14518
14544
  } catch {
14519
- scan.unreadable.push((0, import_node_path9.relative)(worktreePath, dir).replace(/\\/g, "/"));
14545
+ scan.unreadable.push((0, import_node_path10.relative)(worktreePath, dir).replace(/\\/g, "/"));
14520
14546
  return;
14521
14547
  }
14522
14548
  for (const name of names) {
14523
- const full = (0, import_node_path9.join)(dir, name);
14524
- const relPath = (0, import_node_path9.relative)(worktreePath, full).replace(/\\/g, "/");
14549
+ const full = (0, import_node_path10.join)(dir, name);
14550
+ const relPath = (0, import_node_path10.relative)(worktreePath, full).replace(/\\/g, "/");
14525
14551
  let st;
14526
14552
  try {
14527
14553
  st = stat4(full);
@@ -14552,11 +14578,11 @@ function scanWorktreeTmpEvidence(worktreePath, newerThanMs, deps = {}) {
14552
14578
  return scanWorktreeTmpEvidenceDetailed(worktreePath, newerThanMs, deps).entries;
14553
14579
  }
14554
14580
  function archiveWorktreeTmpArtifacts(args, deps = {}) {
14555
- const copyFile = deps.copyFile ?? import_node_fs12.copyFileSync;
14556
- const fileSize = deps.fileSize ?? ((path2) => (0, import_node_fs12.statSync)(path2).size);
14557
- const writeFile5 = deps.writeFile ?? ((path2, text) => (0, import_node_fs12.writeFileSync)(path2, text));
14581
+ const copyFile = deps.copyFile ?? import_node_fs13.copyFileSync;
14582
+ const fileSize = deps.fileSize ?? ((path2) => (0, import_node_fs13.statSync)(path2).size);
14583
+ const writeFile5 = deps.writeFile ?? ((path2, text) => (0, import_node_fs13.writeFileSync)(path2, text));
14558
14584
  const mkdirp = deps.mkdirp ?? ((path2) => {
14559
- (0, import_node_fs12.mkdirSync)(path2, { recursive: true });
14585
+ (0, import_node_fs13.mkdirSync)(path2, { recursive: true });
14560
14586
  });
14561
14587
  const env = deps.env ?? process.env;
14562
14588
  const now = deps.now ?? (() => /* @__PURE__ */ new Date());
@@ -14573,14 +14599,14 @@ function archiveWorktreeTmpArtifacts(args, deps = {}) {
14573
14599
  try {
14574
14600
  let bytes = 0;
14575
14601
  for (const entry of scan.entries) {
14576
- const to = (0, import_node_path9.join)(dest, entry.relPath.slice("tmp/".length));
14577
- mkdirp((0, import_node_path9.dirname)(to));
14578
- copyFile((0, import_node_path9.join)(args.worktreePath, entry.relPath), to);
14602
+ const to = (0, import_node_path10.join)(dest, entry.relPath.slice("tmp/".length));
14603
+ mkdirp((0, import_node_path10.dirname)(to));
14604
+ copyFile((0, import_node_path10.join)(args.worktreePath, entry.relPath), to);
14579
14605
  const copied = fileSize(to);
14580
14606
  if (copied !== entry.bytes) return { status: "failed", error: `${entry.relPath} changed during archive (${entry.bytes} -> ${copied} bytes)` };
14581
14607
  bytes += copied;
14582
14608
  }
14583
- const manifest = (0, import_node_path9.join)((0, import_node_path9.dirname)(dest), "tmp-manifest.json");
14609
+ const manifest = (0, import_node_path10.join)((0, import_node_path10.dirname)(dest), "tmp-manifest.json");
14584
14610
  writeFile5(manifest, `${JSON.stringify({ files: scan.entries.map(({ relPath, bytes: b }) => ({ relPath, bytes: b })), bytes, excluded: scan.excluded, skippedLinks: scan.links }, null, 2)}
14585
14611
  `);
14586
14612
  return { status: "archived", path: dest, runScope, entries: scan.entries.length, bytes, manifest, excluded: scan.excluded, skippedLinks: scan.links };
@@ -14675,8 +14701,8 @@ function parseSnapshotStamp(stamp) {
14675
14701
  function listDirs(path2) {
14676
14702
  try {
14677
14703
  const names = [];
14678
- for (const name of (0, import_node_fs13.readdirSync)(path2)) {
14679
- const st = (0, import_node_fs13.lstatSync)((0, import_node_path10.join)(path2, name));
14704
+ for (const name of (0, import_node_fs14.readdirSync)(path2)) {
14705
+ const st = (0, import_node_fs14.lstatSync)((0, import_node_path11.join)(path2, name));
14680
14706
  if (st.isDirectory() && !st.isSymbolicLink()) names.push(name);
14681
14707
  }
14682
14708
  return { names, unreadable: false };
@@ -14689,17 +14715,17 @@ function measureSnapshot(root) {
14689
14715
  const walk2 = (dir, preserved, inReinstallable) => {
14690
14716
  let names;
14691
14717
  try {
14692
- names = (0, import_node_fs13.readdirSync)(dir);
14718
+ names = (0, import_node_fs14.readdirSync)(dir);
14693
14719
  } catch {
14694
14720
  out.uncertain = true;
14695
14721
  return 0;
14696
14722
  }
14697
14723
  let sum = 0;
14698
14724
  for (const name of names) {
14699
- const full = (0, import_node_path10.join)(dir, name);
14725
+ const full = (0, import_node_path11.join)(dir, name);
14700
14726
  let st;
14701
14727
  try {
14702
- st = (0, import_node_fs13.lstatSync)(full);
14728
+ st = (0, import_node_fs14.lstatSync)(full);
14703
14729
  } catch {
14704
14730
  out.uncertain = true;
14705
14731
  continue;
@@ -14726,10 +14752,10 @@ function inventoryArchives(archiveRootFor, nowMs = Date.now()) {
14726
14752
  for (const kind of ARCHIVE_KINDS) {
14727
14753
  const root = archiveRootFor(kind);
14728
14754
  for (const runScope of listDirs(root).names) {
14729
- for (const branch of listDirs((0, import_node_path10.join)(root, runScope)).names) {
14730
- const branchDir = (0, import_node_path10.join)(root, runScope, branch);
14755
+ for (const branch of listDirs((0, import_node_path11.join)(root, runScope)).names) {
14756
+ const branchDir = (0, import_node_path11.join)(root, runScope, branch);
14731
14757
  for (const stamp of listDirs(branchDir).names) {
14732
- const path2 = (0, import_node_path10.join)(branchDir, stamp);
14758
+ const path2 = (0, import_node_path11.join)(branchDir, stamp);
14733
14759
  const measured = measureSnapshot(path2);
14734
14760
  let mtimeMs = nowMs;
14735
14761
  let timeKnown = true;
@@ -14737,14 +14763,14 @@ function inventoryArchives(archiveRootFor, nowMs = Date.now()) {
14737
14763
  if (stampMs !== void 0) mtimeMs = stampMs;
14738
14764
  else {
14739
14765
  try {
14740
- mtimeMs = (0, import_node_fs13.lstatSync)(path2).mtimeMs;
14766
+ mtimeMs = (0, import_node_fs14.lstatSync)(path2).mtimeMs;
14741
14767
  } catch {
14742
14768
  timeKnown = false;
14743
14769
  }
14744
14770
  }
14745
14771
  let hasManifest = false;
14746
14772
  try {
14747
- hasManifest = (0, import_node_fs13.lstatSync)((0, import_node_path10.join)(path2, "tmp-manifest.json")).isFile();
14773
+ hasManifest = (0, import_node_fs14.lstatSync)((0, import_node_path11.join)(path2, "tmp-manifest.json")).isFile();
14748
14774
  } catch {
14749
14775
  hasManifest = false;
14750
14776
  }
@@ -14766,28 +14792,28 @@ function inventoryArchives(archiveRootFor, nowMs = Date.now()) {
14766
14792
  return rows;
14767
14793
  }
14768
14794
  function insideRoot(root, target) {
14769
- const rel = (0, import_node_path10.relative)(root, target);
14770
- return rel !== "" && !rel.startsWith("..") && !(0, import_node_path10.isAbsolute)(rel) && !rel.split(import_node_path10.sep).includes("..");
14795
+ const rel = (0, import_node_path11.relative)(root, target);
14796
+ return rel !== "" && !rel.startsWith("..") && !(0, import_node_path11.isAbsolute)(rel) && !rel.split(import_node_path11.sep).includes("..");
14771
14797
  }
14772
- function applyArchivePrune(plan, archiveRootFor, remove2 = (p) => (0, import_node_fs13.rmSync)(p, { recursive: true, force: true })) {
14798
+ function applyArchivePrune(plan, archiveRootFor, remove2 = (p) => (0, import_node_fs14.rmSync)(p, { recursive: true, force: true })) {
14773
14799
  const result = { removed: [], skipped: [], failed: [] };
14774
14800
  for (const snap of plan.snapshots) {
14775
14801
  if (snap.proposal !== "reclaim-dependency-trees") continue;
14776
14802
  let realRoot;
14777
14803
  try {
14778
- realRoot = (0, import_node_fs13.realpathSync)(archiveRootFor(snap.kind));
14804
+ realRoot = (0, import_node_fs14.realpathSync)(archiveRootFor(snap.kind));
14779
14805
  } catch (e) {
14780
14806
  for (const d of snap.reinstallableDirs) result.failed.push({ path: d, error: `archive root unresolvable: ${errMsg(e)}` });
14781
14807
  continue;
14782
14808
  }
14783
14809
  for (const dir of snap.reinstallableDirs) {
14784
14810
  try {
14785
- const st = (0, import_node_fs13.lstatSync)(dir);
14811
+ const st = (0, import_node_fs14.lstatSync)(dir);
14786
14812
  if (st.isSymbolicLink() || !st.isDirectory()) {
14787
14813
  result.skipped.push({ path: dir, reason: "no longer a real directory" });
14788
14814
  continue;
14789
14815
  }
14790
- const real = (0, import_node_fs13.realpathSync)(dir);
14816
+ const real = (0, import_node_fs14.realpathSync)(dir);
14791
14817
  if (!insideRoot(realRoot, real)) {
14792
14818
  result.skipped.push({ path: dir, reason: "resolves outside the archive root" });
14793
14819
  continue;
@@ -14830,13 +14856,13 @@ function renderArchivePrunePlan(plan, applied) {
14830
14856
  }
14831
14857
 
14832
14858
  // src/schedules-drift-cache.ts
14833
- var import_node_fs14 = require("node:fs");
14834
- var import_node_path11 = require("node:path");
14859
+ var import_node_fs15 = require("node:fs");
14860
+ var import_node_path12 = require("node:path");
14835
14861
  var SCHEDULES_DRIFT_CACHE_MS = 6 * 36e5;
14836
14862
  function schedulesDriftCachePath(runtimeRoot) {
14837
- return (0, import_node_path11.join)(runtimeRoot, "head-ts", ".schedules-drift");
14863
+ return (0, import_node_path12.join)(runtimeRoot, "head-ts", ".schedules-drift");
14838
14864
  }
14839
- function readSchedulesDriftCache(cachePath, now = Date.now(), read = import_node_fs14.readFileSync) {
14865
+ function readSchedulesDriftCache(cachePath, now = Date.now(), read = import_node_fs15.readFileSync) {
14840
14866
  let parsed;
14841
14867
  try {
14842
14868
  parsed = JSON.parse(read(cachePath, "utf8"));
@@ -14853,8 +14879,8 @@ function readSchedulesDriftCache(cachePath, now = Date.now(), read = import_node
14853
14879
  }
14854
14880
  function writeSchedulesDriftCache(cachePath, driftLines, now = Date.now()) {
14855
14881
  try {
14856
- (0, import_node_fs14.mkdirSync)((0, import_node_path11.dirname)(cachePath), { recursive: true });
14857
- (0, import_node_fs14.writeFileSync)(cachePath, JSON.stringify({ driftLines, at: now }), "utf8");
14882
+ (0, import_node_fs15.mkdirSync)((0, import_node_path12.dirname)(cachePath), { recursive: true });
14883
+ (0, import_node_fs15.writeFileSync)(cachePath, JSON.stringify({ driftLines, at: now }), "utf8");
14858
14884
  } catch {
14859
14885
  }
14860
14886
  }
@@ -14865,8 +14891,8 @@ function cachedSweepNote(cachedAt, now = Date.now()) {
14865
14891
 
14866
14892
  // src/scratch-gc.ts
14867
14893
  var import_node_child_process4 = require("node:child_process");
14868
- var import_node_fs15 = require("node:fs");
14869
- var import_node_path12 = require("node:path");
14894
+ var import_node_fs16 = require("node:fs");
14895
+ var import_node_path13 = require("node:path");
14870
14896
  var PLAN_ADVISORY_AGE_MS = 30 * 24 * 36e5;
14871
14897
  var ROOT_SCRATCH_STALE_MS = 24 * 36e5;
14872
14898
  function executeScratchGc(repoRoot2, opts, now = Date.now(), deps = {}) {
@@ -14963,7 +14989,7 @@ function planScratchGc(snap, now = Date.now()) {
14963
14989
  function isLinkLike(path2, symbolic = false) {
14964
14990
  if (symbolic) return true;
14965
14991
  try {
14966
- (0, import_node_fs15.readlinkSync)(path2);
14992
+ (0, import_node_fs16.readlinkSync)(path2);
14967
14993
  return true;
14968
14994
  } catch {
14969
14995
  return false;
@@ -14973,13 +14999,13 @@ function treeOlderThan(root, now, floor) {
14973
14999
  const stack = [root];
14974
15000
  while (stack.length) {
14975
15001
  const current = stack.pop();
14976
- const lst = (0, import_node_fs15.lstatSync)(current);
15002
+ const lst = (0, import_node_fs16.lstatSync)(current);
14977
15003
  if (isLinkLike(current, lst.isSymbolicLink())) return false;
14978
- const st = (0, import_node_fs15.statSync)(current);
15004
+ const st = (0, import_node_fs16.statSync)(current);
14979
15005
  if (now - st.mtimeMs <= floor) return false;
14980
15006
  if (!st.isDirectory()) continue;
14981
- for (const ent of (0, import_node_fs15.readdirSync)(current, { withFileTypes: true })) {
14982
- const child2 = (0, import_node_path12.join)(current, ent.name);
15007
+ for (const ent of (0, import_node_fs16.readdirSync)(current, { withFileTypes: true })) {
15008
+ const child2 = (0, import_node_path13.join)(current, ent.name);
14983
15009
  if (isLinkLike(child2, ent.isSymbolicLink())) return false;
14984
15010
  stack.push(child2);
14985
15011
  }
@@ -14991,13 +15017,13 @@ function applyScratchGc(plan, repoRoot2, now = Date.now(), deps = {}) {
14991
15017
  const result = { pruned: [], skipped: 0, bytes: 0 };
14992
15018
  let repoAnchor;
14993
15019
  try {
14994
- repoAnchor = (0, import_node_fs15.realpathSync)(repoRoot2).replace(/\\/g, "/").replace(/\/+$/, "");
15020
+ repoAnchor = (0, import_node_fs16.realpathSync)(repoRoot2).replace(/\\/g, "/").replace(/\/+$/, "");
14995
15021
  } catch {
14996
15022
  return result;
14997
15023
  }
14998
15024
  for (const c of plan.safeAuto) {
14999
15025
  try {
15000
- const real = (0, import_node_fs15.realpathSync)(c.path).replace(/\\/g, "/");
15026
+ const real = (0, import_node_fs16.realpathSync)(c.path).replace(/\\/g, "/");
15001
15027
  if (!pathContained(real, repoAnchor) || !rootCandidateStillAllowed(c, real, repoAnchor)) {
15002
15028
  result.skipped += 1;
15003
15029
  continue;
@@ -15006,12 +15032,12 @@ function applyScratchGc(plan, repoRoot2, now = Date.now(), deps = {}) {
15006
15032
  result.skipped += 1;
15007
15033
  continue;
15008
15034
  }
15009
- const lst = (0, import_node_fs15.lstatSync)(c.path);
15035
+ const lst = (0, import_node_fs16.lstatSync)(c.path);
15010
15036
  if (isLinkLike(c.path, lst.isSymbolicLink())) {
15011
15037
  result.skipped += 1;
15012
15038
  continue;
15013
15039
  }
15014
- const st = (0, import_node_fs15.statSync)(real);
15040
+ const st = (0, import_node_fs16.statSync)(real);
15015
15041
  if (c.kind === "dir" && !st.isDirectory() || c.kind !== "dir" && !st.isFile()) {
15016
15042
  result.skipped += 1;
15017
15043
  continue;
@@ -15025,8 +15051,8 @@ function applyScratchGc(plan, repoRoot2, now = Date.now(), deps = {}) {
15025
15051
  result.skipped += 1;
15026
15052
  continue;
15027
15053
  }
15028
- if (c.kind === "dir") (0, import_node_fs15.rmSync)(real, { recursive: true, force: true });
15029
- else (0, import_node_fs15.unlinkSync)(real);
15054
+ if (c.kind === "dir") (0, import_node_fs16.rmSync)(real, { recursive: true, force: true });
15055
+ else (0, import_node_fs16.unlinkSync)(real);
15030
15056
  result.pruned.push(c.path);
15031
15057
  result.bytes += c.bytes;
15032
15058
  } catch {
@@ -15040,19 +15066,19 @@ function pathContained(real, anchor) {
15040
15066
  }
15041
15067
  function rootCandidateStillAllowed(c, realPath, repoAnchor) {
15042
15068
  const path2 = realPath.replace(/\\/g, "/").replace(/\/+$/, "");
15043
- const parent = (0, import_node_path12.dirname)(path2).replace(/\\/g, "/").replace(/\/+$/, "");
15044
- const name = (0, import_node_path12.basename)(path2);
15069
+ const parent = (0, import_node_path13.dirname)(path2).replace(/\\/g, "/").replace(/\/+$/, "");
15070
+ const name = (0, import_node_path13.basename)(path2);
15045
15071
  if (c.family === "scratch-dir") return c.kind === "dir" && parent === repoAnchor && ROOT_SCRATCH_DIRS.has(name);
15046
15072
  if (c.family === "scratch-file") return (c.kind ?? "file") === "file" && parent === repoAnchor && ROOT_SCRATCH_FILE_PREFIXES.some((prefix) => name.startsWith(prefix));
15047
15073
  return false;
15048
15074
  }
15049
15075
  function trackedPathStatus(c, realPath, repoAnchor) {
15050
- const rel = (0, import_node_path12.relative)(repoAnchor, realPath).replace(/\\/g, "/");
15076
+ const rel = (0, import_node_path13.relative)(repoAnchor, realPath).replace(/\\/g, "/");
15051
15077
  if (!rel || rel.startsWith("../") || rel === "..") return null;
15052
15078
  const gitPath = c.kind === "dir" ? `${rel.replace(/\/+$/, "")}/` : rel;
15053
15079
  let hasDotGit = false;
15054
15080
  try {
15055
- (0, import_node_fs15.lstatSync)((0, import_node_path12.join)(repoAnchor, ".git"));
15081
+ (0, import_node_fs16.lstatSync)((0, import_node_path13.join)(repoAnchor, ".git"));
15056
15082
  hasDotGit = true;
15057
15083
  } catch {
15058
15084
  hasDotGit = false;
@@ -15093,10 +15119,10 @@ function rootScratchDirSnapshot(root, readdir2, stat4) {
15093
15119
  while (stack.length) {
15094
15120
  const current = stack.pop();
15095
15121
  for (const ent of readdir2(current, { withFileTypes: true })) {
15096
- const child2 = (0, import_node_path12.join)(current, ent.name);
15122
+ const child2 = (0, import_node_path13.join)(current, ent.name);
15097
15123
  if (ent.isSymbolicLink?.()) return null;
15098
15124
  try {
15099
- (0, import_node_fs15.readlinkSync)(child2);
15125
+ (0, import_node_fs16.readlinkSync)(child2);
15100
15126
  return null;
15101
15127
  } catch {
15102
15128
  }
@@ -15119,12 +15145,12 @@ function resolveRealPathOrRaw(p, realpath) {
15119
15145
  }
15120
15146
  }
15121
15147
  function collectScratchSnapshot(repoRoot2, deps = {}) {
15122
- const readdir2 = deps.readdir ?? import_node_fs15.readdirSync;
15123
- const stat4 = deps.stat ?? import_node_fs15.statSync;
15124
- const realpath = deps.realpath ?? import_node_fs15.realpathSync;
15148
+ const readdir2 = deps.readdir ?? import_node_fs16.readdirSync;
15149
+ const stat4 = deps.stat ?? import_node_fs16.statSync;
15150
+ const realpath = deps.realpath ?? import_node_fs16.realpathSync;
15125
15151
  const trackedStatus = deps.trackedStatus ?? trackedPathStatus;
15126
15152
  const repoAnchor = resolveRealPathOrRaw(repoRoot2, realpath).replace(/\\/g, "/").replace(/\/+$/, "");
15127
- const plansRoot = (0, import_node_path12.join)(repoRoot2, "plans");
15153
+ const plansRoot = (0, import_node_path13.join)(repoRoot2, "plans");
15128
15154
  const rootScratchFiles = [];
15129
15155
  try {
15130
15156
  for (const ent of readdir2(repoRoot2, { withFileTypes: true })) {
@@ -15133,7 +15159,7 @@ function collectScratchSnapshot(repoRoot2, deps = {}) {
15133
15159
  if (!(isDir && ROOT_SCRATCH_DIRS.has(ent.name)) && !(isFile && ROOT_SCRATCH_FILE_PREFIXES.some((prefix) => ent.name.startsWith(prefix)))) {
15134
15160
  continue;
15135
15161
  }
15136
- const full = (0, import_node_path12.join)(repoRoot2, ent.name);
15162
+ const full = (0, import_node_path13.join)(repoRoot2, ent.name);
15137
15163
  const kind = isDir ? "dir" : "file";
15138
15164
  try {
15139
15165
  const realFull = resolveRealPathOrRaw(full, realpath).replace(/\\/g, "/");
@@ -15156,7 +15182,7 @@ function collectScratchSnapshot(repoRoot2, deps = {}) {
15156
15182
  try {
15157
15183
  for (const ent of readdir2(plansRoot, { withFileTypes: true })) {
15158
15184
  if (!ent.isFile() || !ent.name.endsWith(".md")) continue;
15159
- const full = (0, import_node_path12.join)(plansRoot, ent.name);
15185
+ const full = (0, import_node_path13.join)(plansRoot, ent.name);
15160
15186
  try {
15161
15187
  const st = stat4(full);
15162
15188
  planMdFiles.push({ path: full, dir: plansRoot, name: ent.name, mtimeMs: st.mtimeMs, bytes: st.size, kind: "file" });
@@ -15270,9 +15296,9 @@ var import_node_os29 = require("node:os");
15270
15296
  var import_node_path65 = require("node:path");
15271
15297
 
15272
15298
  // src/board-read.ts
15273
- var import_node_fs18 = require("node:fs");
15299
+ var import_node_fs19 = require("node:fs");
15274
15300
  var import_node_os11 = require("node:os");
15275
- var import_node_path15 = require("node:path");
15301
+ var import_node_path16 = require("node:path");
15276
15302
  init_cli_shared();
15277
15303
  init_github_client();
15278
15304
 
@@ -15496,6 +15522,9 @@ var APP_WRITE_COMMANDS = /* @__PURE__ */ new Map([
15496
15522
  // #9587: one named run rerun (all or failed jobs) — same actions:write profile, nothing broader.
15497
15523
  ["run rerun", { metadata: "read", actions: "write" }],
15498
15524
  ["run dispatch", { metadata: "read", actions: "write" }],
15525
+ // #10142: one focused-test dispatch plus the run/job/log reads that give it a verdict on the owned
15526
+ // heavy lanes — the same actions:write profile, nothing broader (it reads no repo contents).
15527
+ ["run test", { metadata: "read", actions: "write" }],
15499
15528
  // #9780: repository Actions variables — get/list mint only Variables:read; create/update/delete
15500
15529
  // Variables:write. Repository scope only; no secret store, no rename, no workflow dispatch.
15501
15530
  // #9790: the token key is `actions_variables` (as ci-reconcile.yml mints); `variables` was refused 422.
@@ -16170,26 +16199,26 @@ init_github_quota();
16170
16199
 
16171
16200
  // src/rest-etag-cache.ts
16172
16201
  var import_node_crypto6 = require("node:crypto");
16173
- var import_node_fs16 = require("node:fs");
16202
+ var import_node_fs17 = require("node:fs");
16174
16203
  var import_node_os8 = require("node:os");
16175
- var import_node_path13 = require("node:path");
16204
+ var import_node_path14 = require("node:path");
16176
16205
  var MAX_AGE_MS = 24 * 60 * 6e4;
16177
16206
  var MAX_ENTRIES = 256;
16178
16207
  var MAX_BYTES = 16 * 1024 * 1024;
16179
16208
  var MAX_ENTRY_BYTES = 1024 * 1024;
16180
16209
  function restEtagCacheDir() {
16181
16210
  if (process.env.MMI_REST_ETAG_CACHE_DIR) return process.env.MMI_REST_ETAG_CACHE_DIR;
16182
- return process.platform === "win32" ? (0, import_node_path13.join)(process.env.LOCALAPPDATA || (0, import_node_path13.join)((0, import_node_os8.homedir)(), "AppData", "Local"), "MMI Future", "mmi-cli", "rest-etags") : (0, import_node_path13.join)(process.env.XDG_CACHE_HOME || (0, import_node_path13.join)((0, import_node_os8.homedir)(), ".cache"), "mmi-cli", "rest-etags");
16211
+ return process.platform === "win32" ? (0, import_node_path14.join)(process.env.LOCALAPPDATA || (0, import_node_path14.join)((0, import_node_os8.homedir)(), "AppData", "Local"), "MMI Future", "mmi-cli", "rest-etags") : (0, import_node_path14.join)(process.env.XDG_CACHE_HOME || (0, import_node_path14.join)((0, import_node_os8.homedir)(), ".cache"), "mmi-cli", "rest-etags");
16183
16212
  }
16184
16213
  function entryPath(dir, key) {
16185
- return (0, import_node_path13.join)(dir, `${(0, import_node_crypto6.createHash)("sha256").update(key).digest("hex")}.json`);
16214
+ return (0, import_node_path14.join)(dir, `${(0, import_node_crypto6.createHash)("sha256").update(key).digest("hex")}.json`);
16186
16215
  }
16187
16216
  function readRestEtag(key, dir = restEtagCacheDir()) {
16188
16217
  if (dir === "off") return void 0;
16189
16218
  try {
16190
16219
  const path2 = entryPath(dir, key);
16191
- if ((0, import_node_fs16.statSync)(path2).size > MAX_ENTRY_BYTES) return void 0;
16192
- const entry = JSON.parse((0, import_node_fs16.readFileSync)(path2, "utf8"));
16220
+ if ((0, import_node_fs17.statSync)(path2).size > MAX_ENTRY_BYTES) return void 0;
16221
+ const entry = JSON.parse((0, import_node_fs17.readFileSync)(path2, "utf8"));
16193
16222
  const age = Date.now() - entry.storedAt;
16194
16223
  if (typeof entry.etag === "string" && entry.etag && !/[\r\n]/.test(entry.etag) && typeof entry.body === "string" && Number.isFinite(entry.storedAt) && age >= 0 && age <= MAX_AGE_MS) return entry;
16195
16224
  } catch {
@@ -16199,18 +16228,18 @@ function readRestEtag(key, dir = restEtagCacheDir()) {
16199
16228
  function deleteRestEtag(key, dir = restEtagCacheDir()) {
16200
16229
  if (dir === "off") return;
16201
16230
  try {
16202
- (0, import_node_fs16.rmSync)(entryPath(dir, key), { force: true });
16231
+ (0, import_node_fs17.rmSync)(entryPath(dir, key), { force: true });
16203
16232
  } catch {
16204
16233
  }
16205
16234
  }
16206
16235
  function prune(dir) {
16207
16236
  const now = Date.now();
16208
- const files = (0, import_node_fs16.readdirSync)(dir).filter((name) => /^[a-f0-9]{64}\.json(?:\.tmp-.+)?$/.test(name)).flatMap((name) => {
16237
+ const files = (0, import_node_fs17.readdirSync)(dir).filter((name) => /^[a-f0-9]{64}\.json(?:\.tmp-.+)?$/.test(name)).flatMap((name) => {
16209
16238
  try {
16210
- const path2 = (0, import_node_path13.join)(dir, name);
16211
- const stat4 = (0, import_node_fs16.statSync)(path2);
16239
+ const path2 = (0, import_node_path14.join)(dir, name);
16240
+ const stat4 = (0, import_node_fs17.statSync)(path2);
16212
16241
  if (now - stat4.mtimeMs > MAX_AGE_MS) {
16213
- (0, import_node_fs16.rmSync)(path2, { force: true });
16242
+ (0, import_node_fs17.rmSync)(path2, { force: true });
16214
16243
  return [];
16215
16244
  }
16216
16245
  return name.endsWith(".json") ? [{ path: path2, size: stat4.size, at: stat4.mtimeMs }] : [];
@@ -16223,7 +16252,7 @@ function prune(dir) {
16223
16252
  for (const file of files) {
16224
16253
  if (count <= MAX_ENTRIES && bytes <= MAX_BYTES) break;
16225
16254
  try {
16226
- (0, import_node_fs16.rmSync)(file.path, { force: true });
16255
+ (0, import_node_fs17.rmSync)(file.path, { force: true });
16227
16256
  bytes -= file.size;
16228
16257
  count--;
16229
16258
  } catch {
@@ -16240,14 +16269,14 @@ function writeRestEtag(key, entry, dir = restEtagCacheDir()) {
16240
16269
  deleteRestEtag(key, dir);
16241
16270
  return;
16242
16271
  }
16243
- (0, import_node_fs16.mkdirSync)(dir, { recursive: true, mode: 448 });
16244
- (0, import_node_fs16.writeFileSync)(tmp, data, { mode: 384, flag: "wx" });
16245
- (0, import_node_fs16.renameSync)(tmp, path2);
16272
+ (0, import_node_fs17.mkdirSync)(dir, { recursive: true, mode: 448 });
16273
+ (0, import_node_fs17.writeFileSync)(tmp, data, { mode: 384, flag: "wx" });
16274
+ (0, import_node_fs17.renameSync)(tmp, path2);
16246
16275
  prune(dir);
16247
16276
  } catch {
16248
16277
  } finally {
16249
16278
  try {
16250
- (0, import_node_fs16.rmSync)(tmp, { force: true });
16279
+ (0, import_node_fs17.rmSync)(tmp, { force: true });
16251
16280
  } catch {
16252
16281
  }
16253
16282
  }
@@ -18712,7 +18741,7 @@ var SCHEDULE_EXECUTORS = /* @__PURE__ */ new Set([
18712
18741
  function parseScheduleHeader(yamlText) {
18713
18742
  const out = {};
18714
18743
  for (const rawLine of yamlText.split("\n")) {
18715
- const m = /^#\s*(schedule|what|owner|cadence|executor|llm|output|breaks|kill|target|model):\s*(.+?)\s*$/.exec(rawLine);
18744
+ const m = /^#\s*(schedule|what|owner|cadence|executor|llm|output|breaks|kill|target|model|fire):\s*(.+?)\s*$/.exec(rawLine);
18716
18745
  if (m && out[m[1]] === void 0) out[m[1]] = m[2].trim();
18717
18746
  }
18718
18747
  return out;
@@ -18793,6 +18822,10 @@ function scheduleRecordFromWorkflow(repo, workflowPath, yamlText) {
18793
18822
  if (header.model !== void 0) {
18794
18823
  throw new Error(`schedules register: ${expectedId} header \`model: ${header.model}\` is refused \u2014 a model role key is valid only on an LLM lane, and no scheduled-LLM executor is supported (Cursor retired #7779; Codex executor retired #7821). Remove the line.`);
18795
18824
  }
18825
+ const fire = header.fire;
18826
+ if (fire !== void 0 && (h.executor !== "github-actions" || fire !== "v1")) {
18827
+ throw new Error(`schedules register: ${expectedId} header \`fire: ${fire}\` is invalid \u2014 the fire contract is \`fire: v1\`, github-actions rows only (#9996). An unknown revision would register a row the dispatcher cannot protect; a non-github-actions row has no workflow to claim it.`);
18828
+ }
18796
18829
  return {
18797
18830
  id: expectedId,
18798
18831
  what: h.what,
@@ -18805,7 +18838,8 @@ function scheduleRecordFromWorkflow(repo, workflowPath, yamlText) {
18805
18838
  kill: h.kill,
18806
18839
  repo,
18807
18840
  sourcePath: workflowPath,
18808
- ...target !== void 0 ? { target } : {}
18841
+ ...target !== void 0 ? { target } : {},
18842
+ ...fire !== void 0 ? { fire } : {}
18809
18843
  };
18810
18844
  }
18811
18845
  function scheduleRecordsFromWorkflows(repo, files) {
@@ -20290,9 +20324,9 @@ init_clean_exit();
20290
20324
 
20291
20325
  // src/gh-create.ts
20292
20326
  var import_promises = require("node:fs/promises");
20293
- var import_node_fs17 = require("node:fs");
20327
+ var import_node_fs18 = require("node:fs");
20294
20328
  var import_node_os9 = require("node:os");
20295
- var import_node_path14 = require("node:path");
20329
+ var import_node_path15 = require("node:path");
20296
20330
  var import_node_crypto7 = require("node:crypto");
20297
20331
 
20298
20332
  // src/board-priority.ts
@@ -20380,8 +20414,8 @@ async function bodyArgsViaFile(args, deps = {}) {
20380
20414
  const remove2 = deps.remove ?? import_promises.unlink;
20381
20415
  const ensureDir = deps.ensureDir ?? import_promises.mkdir;
20382
20416
  const dir = deps.dir ?? (0, import_node_os9.tmpdir)();
20383
- const file = (0, import_node_path14.join)(dir, `mmi-gh-body-${process.pid}-${(0, import_node_crypto7.randomBytes)(4).toString("hex")}.md`);
20384
- await ensureDir((0, import_node_path14.dirname)(file), { recursive: true }).catch(() => {
20417
+ const file = (0, import_node_path15.join)(dir, `mmi-gh-body-${process.pid}-${(0, import_node_crypto7.randomBytes)(4).toString("hex")}.md`);
20418
+ await ensureDir((0, import_node_path15.dirname)(file), { recursive: true }).catch(() => {
20385
20419
  });
20386
20420
  await write(file, args[i + 1], "utf8");
20387
20421
  return {
@@ -20581,8 +20615,8 @@ function retryBodyFile(args) {
20581
20615
  if (file !== void 0) return file;
20582
20616
  const body = flagValue(args, "--body");
20583
20617
  if (body === void 0) return void 0;
20584
- const path2 = (0, import_node_path14.join)((0, import_node_fs17.mkdtempSync)((0, import_node_path14.join)((0, import_node_os9.tmpdir)(), "mmi-create-retry-")), "body.md");
20585
- (0, import_node_fs17.writeFileSync)(path2, body, "utf8");
20618
+ const path2 = (0, import_node_path15.join)((0, import_node_fs18.mkdtempSync)((0, import_node_path15.join)((0, import_node_os9.tmpdir)(), "mmi-create-retry-")), "body.md");
20619
+ (0, import_node_fs18.writeFileSync)(path2, body, "utf8");
20586
20620
  return path2;
20587
20621
  }
20588
20622
  function buildPrCreateRetryCommand(args) {
@@ -20731,7 +20765,7 @@ async function defaultRestCreatePr(exec3, input, deps = {}) {
20731
20765
  const remove2 = deps.remove ?? import_promises.unlink;
20732
20766
  const ensureDir = deps.ensureDir ?? import_promises.mkdir;
20733
20767
  const dir = deps.dir ?? (0, import_node_os9.tmpdir)();
20734
- const file = (0, import_node_path14.join)(dir, `mmi-rest-pr-${process.pid}-${(0, import_node_crypto7.randomBytes)(4).toString("hex")}.json`);
20768
+ const file = (0, import_node_path15.join)(dir, `mmi-rest-pr-${process.pid}-${(0, import_node_crypto7.randomBytes)(4).toString("hex")}.json`);
20735
20769
  const payload = {
20736
20770
  title: input.title,
20737
20771
  head: input.head,
@@ -20739,7 +20773,7 @@ async function defaultRestCreatePr(exec3, input, deps = {}) {
20739
20773
  };
20740
20774
  if (input.base) payload.base = input.base;
20741
20775
  if (input.draft) payload.draft = true;
20742
- await ensureDir((0, import_node_path14.dirname)(file), { recursive: true }).catch(() => {
20776
+ await ensureDir((0, import_node_path15.dirname)(file), { recursive: true }).catch(() => {
20743
20777
  });
20744
20778
  await write(file, JSON.stringify(payload), "utf8");
20745
20779
  const args = [
@@ -24041,11 +24075,11 @@ function scanSessionTranscripts(root, matches, now) {
24041
24075
  const pending = [root];
24042
24076
  while (pending.length) {
24043
24077
  const dir = pending.pop();
24044
- for (const entry of (0, import_node_fs18.readdirSync)(dir, { withFileTypes: true })) {
24045
- const path2 = (0, import_node_path15.join)(dir, entry.name);
24078
+ for (const entry of (0, import_node_fs19.readdirSync)(dir, { withFileTypes: true })) {
24079
+ const path2 = (0, import_node_path16.join)(dir, entry.name);
24046
24080
  if (entry.isDirectory()) pending.push(path2);
24047
24081
  else if (entry.isFile() && matches(entry.name.toLowerCase())) {
24048
- return now - (0, import_node_fs18.statSync)(path2).mtimeMs <= CLAIM_SESSION_ACTIVITY_MS ? "live" : "dead";
24082
+ return now - (0, import_node_fs19.statSync)(path2).mtimeMs <= CLAIM_SESSION_ACTIVITY_MS ? "live" : "dead";
24049
24083
  }
24050
24084
  }
24051
24085
  }
@@ -24065,7 +24099,7 @@ function probeLocalClaimSession(marker, now = Date.now(), roots = {}) {
24065
24099
  };
24066
24100
  try {
24067
24101
  const session = marker.session.toLowerCase();
24068
- const state = kind === "claude" ? scanSessionTranscripts(roots.claude ?? (0, import_node_path15.join)((0, import_node_os11.homedir)(), ".claude", "projects"), (name) => name === `${session}.jsonl`, now) : scanSessionTranscripts(roots.jerv ?? (0, import_node_path15.join)((0, import_node_os11.homedir)(), ".jerv", "agent", "sessions"), (name) => name.endsWith(`_${session}.jsonl`), now);
24102
+ const state = kind === "claude" ? scanSessionTranscripts(roots.claude ?? (0, import_node_path16.join)((0, import_node_os11.homedir)(), ".claude", "projects"), (name) => name === `${session}.jsonl`, now) : scanSessionTranscripts(roots.jerv ?? (0, import_node_path16.join)((0, import_node_os11.homedir)(), ".jerv", "agent", "sessions"), (name) => name.endsWith(`_${session}.jsonl`), now);
24069
24103
  return remember(state);
24070
24104
  } catch {
24071
24105
  return remember("unverifiable");
@@ -24558,8 +24592,8 @@ async function claimOneBoardItem(ctx, selector, options) {
24558
24592
  });
24559
24593
  if (contest.contested && !ctx.landing && contest.branch) {
24560
24594
  const candidateBranch = contest.branch.replace(/ \(head of open PR .*\)$/, "").trim();
24561
- const originRepo = repoFromRemoteUrl(await ctx.git(["remote", "get-url", "origin"]).catch(() => ""));
24562
- if (originRepo && originRepo.toLowerCase() === item.repository.toLowerCase()) {
24595
+ const originRepo2 = repoFromRemoteUrl(await ctx.git(["remote", "get-url", "origin"]).catch(() => ""));
24596
+ if (originRepo2 && originRepo2.toLowerCase() === item.repository.toLowerCase()) {
24563
24597
  const localSha = (await ctx.git(["rev-parse", "--verify", `refs/heads/${candidateBranch}^{commit}`]).catch(() => "")).trim();
24564
24598
  const currentBranch2 = (await ctx.git(["symbolic-ref", "--quiet", "--short", "HEAD"]).catch(() => "")).trim();
24565
24599
  if (localSha && (!contest.branchSha || localSha === contest.branchSha) || currentBranch2 === candidateBranch) {
@@ -25252,6 +25286,11 @@ function loadBootstrapSeeds(manifestJson) {
25252
25286
  if (s.ownership !== "org" && s.ownership !== "repo") {
25253
25287
  throw new Error(`invalid seed ownership '${s.ownership}' for ${s.target} (must be 'org' or 'repo')`);
25254
25288
  }
25289
+ if (s.repos !== void 0) {
25290
+ if (!Array.isArray(s.repos) || s.repos.length === 0 || s.repos.some((r) => typeof r !== "string" || !r.trim() || r.includes("/"))) {
25291
+ throw new Error(`invalid repos scope for ${s.target} (needs a non-empty list of repo slugs, no owner prefix): ${JSON.stringify(s.repos)}`);
25292
+ }
25293
+ }
25255
25294
  if (s.managedBlock) {
25256
25295
  const block = s.managedBlock;
25257
25296
  if (!block.source || !block.begin || !block.end || block.begin === block.end || /[\r\n]/.test(block.begin + block.end)) {
@@ -25277,6 +25316,11 @@ function seedMatchesReleaseTrack(seed, releaseTrack) {
25277
25316
  if (!seed.releaseTracks?.length) return true;
25278
25317
  return releaseTrack != null && seed.releaseTracks.includes(releaseTrack);
25279
25318
  }
25319
+ function seedMatchesRepo(seed, repo) {
25320
+ if (!seed.repos?.length) return true;
25321
+ const slug = (repo ?? "").split("/").pop().trim().toLowerCase();
25322
+ return slug !== "" && seed.repos.some((s) => s.trim().toLowerCase() === slug);
25323
+ }
25280
25324
  var TRAIN_ONLY_BEGIN = "mmi-train-only:start";
25281
25325
  var TRAIN_ONLY_END = "mmi-train-only:end";
25282
25326
  function renderSelfSeedForTrack(content, releaseTrack) {
@@ -25534,13 +25578,13 @@ function planManagedGitignore(current) {
25534
25578
  }
25535
25579
 
25536
25580
  // src/docs-index-command.ts
25537
- var import_node_fs20 = require("node:fs");
25538
- var import_node_path17 = require("node:path");
25581
+ var import_node_fs21 = require("node:fs");
25582
+ var import_node_path18 = require("node:path");
25539
25583
 
25540
25584
  // src/doc-refs-core.ts
25541
25585
  var import_node_child_process6 = require("node:child_process");
25542
- var import_node_fs19 = require("node:fs");
25543
- var import_node_path16 = require("node:path");
25586
+ var import_node_fs20 = require("node:fs");
25587
+ var import_node_path17 = require("node:path");
25544
25588
  var PIN_RE = /<!--\s*pinned by\s+([^:]+?)\s*:\s*"([^"]+)"[^"]*-->/;
25545
25589
  var PIN_MENTION_RE = /<!--\s*pinned by\b/;
25546
25590
  var FWD_RE = /<!--\s*forward-ref:\s*(\S+?)\s*-->/;
@@ -25602,7 +25646,7 @@ function checkPins(root, readFile11, docs) {
25602
25646
  findings.push({ kind: "malformed-pin", doc, line: pin.line, detail: pin.text });
25603
25647
  continue;
25604
25648
  }
25605
- const source = readFile11((0, import_node_path16.join)(root, pin.file));
25649
+ const source = readFile11((0, import_node_path17.join)(root, pin.file));
25606
25650
  if (source == null) {
25607
25651
  findings.push({ kind: "missing-test", doc, line: pin.line, detail: pin.file });
25608
25652
  continue;
@@ -25665,11 +25709,11 @@ function checkRefs(root, deps, docs) {
25665
25709
  for (const { ref } of extractRefs(markdown)) allFirstSegments.add(refFirstSegment(ref));
25666
25710
  }
25667
25711
  const tracked = allFirstSegments.size ? deps.trackedFirstSegments?.([...allFirstSegments]) ?? null : null;
25668
- const firstVerifiable = (first) => tracked ? tracked.has(first) : exists((0, import_node_path16.join)(root, first));
25712
+ const firstVerifiable = (first) => tracked ? tracked.has(first) : exists((0, import_node_path17.join)(root, first));
25669
25713
  const candidates = [];
25670
25714
  const direct = [];
25671
25715
  for (const [doc, markdown] of Object.entries(docs)) {
25672
- const docDir = import_node_path16.posix.dirname(doc);
25716
+ const docDir = import_node_path17.posix.dirname(doc);
25673
25717
  const base = docDir === "." ? "" : docDir;
25674
25718
  const covered = /* @__PURE__ */ new Set();
25675
25719
  const markers = [];
@@ -25678,21 +25722,21 @@ function checkRefs(root, deps, docs) {
25678
25722
  direct.push({ kind: "malformed-forward-ref", doc, line: fwd.line, detail: fwd.text });
25679
25723
  continue;
25680
25724
  }
25681
- const docRel = import_node_path16.posix.normalize(import_node_path16.posix.join(base, fwd.target));
25682
- const rootRel = import_node_path16.posix.normalize(fwd.target.replace(/^\/+/, ""));
25725
+ const docRel = import_node_path17.posix.normalize(import_node_path17.posix.join(base, fwd.target));
25726
+ const rootRel = import_node_path17.posix.normalize(fwd.target.replace(/^\/+/, ""));
25683
25727
  markers.push({ target: fwd.target, line: fwd.line, docRel, rootRel });
25684
25728
  covered.add(docRel);
25685
25729
  covered.add(rootRel);
25686
25730
  }
25687
25731
  const links = extractLinks(markdown).map(({ target, line }) => {
25688
- const resolved = import_node_path16.posix.normalize(import_node_path16.posix.join(base, target));
25689
- return { target, line, resolved, missing: !exists((0, import_node_path16.join)(root, resolved)) };
25732
+ const resolved = import_node_path17.posix.normalize(import_node_path17.posix.join(base, target));
25733
+ return { target, line, resolved, missing: !exists((0, import_node_path17.join)(root, resolved)) };
25690
25734
  });
25691
25735
  for (const marker of markers) {
25692
25736
  const coversMissing = links.some(
25693
25737
  (l) => l.missing && (l.resolved === marker.docRel || l.resolved === marker.rootRel)
25694
25738
  );
25695
- if (!coversMissing && (exists((0, import_node_path16.join)(root, marker.docRel)) || exists((0, import_node_path16.join)(root, marker.rootRel)))) {
25739
+ if (!coversMissing && (exists((0, import_node_path17.join)(root, marker.docRel)) || exists((0, import_node_path17.join)(root, marker.rootRel)))) {
25696
25740
  direct.push({
25697
25741
  kind: "stale-forward-ref",
25698
25742
  doc,
@@ -25703,7 +25747,7 @@ function checkRefs(root, deps, docs) {
25703
25747
  }
25704
25748
  for (const { ref, line } of extractRefs(markdown)) {
25705
25749
  if (!firstVerifiable(refFirstSegment(ref))) continue;
25706
- if (!exists((0, import_node_path16.join)(root, ref))) candidates.push({ kind: "missing-path", doc, line, detail: ref });
25750
+ if (!exists((0, import_node_path17.join)(root, ref))) candidates.push({ kind: "missing-path", doc, line, detail: ref });
25707
25751
  }
25708
25752
  for (const { target, line, resolved, missing } of links) {
25709
25753
  if (resolved.startsWith("..")) {
@@ -25826,7 +25870,7 @@ function checkRunbookClaims(root, docs, deps) {
25826
25870
  const dir = prefix >= 0 ? args[prefix + 1] : ".";
25827
25871
  const script = args.slice(run + 1).find((token, i, tail) => !token.startsWith("-") && tail[i - 1] !== "--prefix");
25828
25872
  if (!dir || !script || placeholder(dir) || placeholder(script)) continue;
25829
- const manifest = deps.readFile((0, import_node_path16.join)(root, dir, "package.json"));
25873
+ const manifest = deps.readFile((0, import_node_path17.join)(root, dir, "package.json"));
25830
25874
  const scripts = manifest ? JSON.parse(manifest).scripts : void 0;
25831
25875
  if (!scripts || !Object.hasOwn(scripts, script)) report("missing-script", `npm run ${script} (${dir}/package.json)`);
25832
25876
  } else if ((cmd === "python" || cmd === "python3") && args[0] === "-m") {
@@ -25836,10 +25880,10 @@ function checkRunbookClaims(root, docs, deps) {
25836
25880
  const first = rel.split("/")[0];
25837
25881
  const tracked = deps.trackedFirstSegments([first, `${first}.py`, `src/${first}`, `src/${first}.py`]);
25838
25882
  const roots = ["", "src/"].filter((prefix) => {
25839
- if (!prefix) return tracked ? tracked.has(first) || tracked.has(`${first}.py`) : deps.exists((0, import_node_path16.join)(root, first)) || deps.exists((0, import_node_path16.join)(root, `${first}.py`));
25840
- return tracked ? tracked.has(`src/${first}`) || tracked.has(`src/${first}.py`) : deps.exists((0, import_node_path16.join)(root, "src", first)) || deps.exists((0, import_node_path16.join)(root, "src", `${first}.py`));
25883
+ if (!prefix) return tracked ? tracked.has(first) || tracked.has(`${first}.py`) : deps.exists((0, import_node_path17.join)(root, first)) || deps.exists((0, import_node_path17.join)(root, `${first}.py`));
25884
+ return tracked ? tracked.has(`src/${first}`) || tracked.has(`src/${first}.py`) : deps.exists((0, import_node_path17.join)(root, "src", first)) || deps.exists((0, import_node_path17.join)(root, "src", `${first}.py`));
25841
25885
  });
25842
- if (roots.length && !roots.some((prefix) => [".py", "/__init__.py", "/__main__.py"].some((suffix) => deps.exists((0, import_node_path16.join)(root, `${prefix}${rel}${suffix}`))))) {
25886
+ if (roots.length && !roots.some((prefix) => [".py", "/__init__.py", "/__main__.py"].some((suffix) => deps.exists((0, import_node_path17.join)(root, `${prefix}${rel}${suffix}`))))) {
25843
25887
  report("missing-module", module2);
25844
25888
  }
25845
25889
  } else if (["node", "bash", "sh"].includes(cmd)) {
@@ -25847,7 +25891,7 @@ function checkRunbookClaims(root, docs, deps) {
25847
25891
  if (!rel || rel.startsWith("-") || placeholder(rel) || /^(\/|[A-Za-z]:|\.\.\/)/.test(rel)) continue;
25848
25892
  const first = refFirstSegment(rel);
25849
25893
  const tracked = deps.trackedFirstSegments([first]);
25850
- if ((tracked ? tracked.has(first) : deps.exists((0, import_node_path16.join)(root, first))) && !deps.exists((0, import_node_path16.join)(root, rel)) && !deps.isIgnored([rel]).has(rel)) report("missing-path", rel);
25894
+ if ((tracked ? tracked.has(first) : deps.exists((0, import_node_path17.join)(root, first))) && !deps.exists((0, import_node_path17.join)(root, rel)) && !deps.isIgnored([rel]).has(rel)) report("missing-path", rel);
25851
25895
  }
25852
25896
  }
25853
25897
  }
@@ -25899,14 +25943,14 @@ function checkRunbookCoverage(docs, base, git3) {
25899
25943
  return findings;
25900
25944
  }
25901
25945
  function checkSeedRunbookReview(root, base, readFile11, git3) {
25902
- const manifestJson = readFile11((0, import_node_path16.join)(root, "skills/bootstrap/seeds/manifest.json"));
25946
+ const manifestJson = readFile11((0, import_node_path17.join)(root, "skills/bootstrap/seeds/manifest.json"));
25903
25947
  if (!manifestJson) return [];
25904
25948
  const changed = new Set(git3(["diff", "--name-only", `${base}...HEAD`]).split(/\r?\n/).filter(Boolean));
25905
25949
  const touched = loadBootstrapSeeds(manifestJson).seeds.filter((seed) => isPropagatableSeed(seed) && changed.has(seedSourcePath(seed)));
25906
25950
  if (!touched.length) return [];
25907
25951
  let declarations = null;
25908
25952
  try {
25909
- declarations = JSON.parse(readFile11((0, import_node_path16.join)(root, RUNBOOK_UNAFFECTED_PATH)) ?? "null");
25953
+ declarations = JSON.parse(readFile11((0, import_node_path17.join)(root, RUNBOOK_UNAFFECTED_PATH)) ?? "null");
25910
25954
  } catch {
25911
25955
  declarations = null;
25912
25956
  }
@@ -25931,24 +25975,24 @@ function checkSeedRunbookReview(root, base, readFile11, git3) {
25931
25975
  return findings;
25932
25976
  }
25933
25977
  function readFileOrNull(path2) {
25934
- return (0, import_node_fs19.existsSync)(path2) ? (0, import_node_fs19.readFileSync)(path2, "utf8") : null;
25978
+ return (0, import_node_fs20.existsSync)(path2) ? (0, import_node_fs20.readFileSync)(path2, "utf8") : null;
25935
25979
  }
25936
25980
  function walk(dir, root, out) {
25937
- for (const entry of (0, import_node_fs19.readdirSync)(dir)) {
25938
- const full = (0, import_node_path16.join)(dir, entry);
25939
- if ((0, import_node_fs19.statSync)(full).isDirectory()) walk(full, root, out);
25981
+ for (const entry of (0, import_node_fs20.readdirSync)(dir)) {
25982
+ const full = (0, import_node_path17.join)(dir, entry);
25983
+ if ((0, import_node_fs20.statSync)(full).isDirectory()) walk(full, root, out);
25940
25984
  else if (entry.endsWith(".md")) out.push(full.slice(root.length + 1).replaceAll("\\", "/"));
25941
25985
  }
25942
25986
  return out;
25943
25987
  }
25944
25988
  function defaultListDocs(root) {
25945
25989
  const docs = ["docs", "skills/org-guide", "skills/mmi-help"].flatMap((path2) => {
25946
- const directory = (0, import_node_path16.join)(root, path2);
25947
- return (0, import_node_fs19.existsSync)(directory) ? walk(directory, root, []) : [];
25990
+ const directory = (0, import_node_path17.join)(root, path2);
25991
+ return (0, import_node_fs20.existsSync)(directory) ? walk(directory, root, []) : [];
25948
25992
  }).filter(
25949
25993
  (rel) => !SKIP_WALK.some((skip) => rel.startsWith(skip))
25950
25994
  );
25951
- return [...ROOT_DOCS.filter((rel) => (0, import_node_fs19.existsSync)((0, import_node_path16.join)(root, rel))), ...docs];
25995
+ return [...ROOT_DOCS.filter((rel) => (0, import_node_fs20.existsSync)((0, import_node_path17.join)(root, rel))), ...docs];
25952
25996
  }
25953
25997
  var CHECK_IGNORE_MAX_BUFFER = 32 * 1024 * 1024;
25954
25998
  function defaultIsIgnored(root, relPaths, exec3 = import_node_child_process6.execFileSync) {
@@ -26022,7 +26066,7 @@ function mergeGroupRunbookMessages(root, deps, readFile11, warnings) {
26022
26066
  }
26023
26067
  function runDocRefs(root, deps = {}) {
26024
26068
  const readFile11 = deps.readFile ?? readFileOrNull;
26025
- const exists = deps.exists ?? import_node_fs19.existsSync;
26069
+ const exists = deps.exists ?? import_node_fs20.existsSync;
26026
26070
  const listDocs = deps.listDocs ?? defaultListDocs;
26027
26071
  const isIgnored = deps.isIgnored ?? ((paths) => defaultIsIgnored(root, paths));
26028
26072
  let inventory;
@@ -26035,7 +26079,7 @@ function runDocRefs(root, deps = {}) {
26035
26079
  const walked = listDocs(root);
26036
26080
  const ignoredDocs = walked.length ? isIgnored(walked) : /* @__PURE__ */ new Set();
26037
26081
  const docs = Object.fromEntries(
26038
- walked.filter((rel) => !ignoredDocs.has(rel)).map((rel) => [rel, readFile11((0, import_node_path16.join)(root, rel))]).filter(([rel, body]) => body != null && (!deps.runbooksOnly || isRunbook(rel, body)))
26082
+ walked.filter((rel) => !ignoredDocs.has(rel)).map((rel) => [rel, readFile11((0, import_node_path17.join)(root, rel))]).filter(([rel, body]) => body != null && (!deps.runbooksOnly || isRunbook(rel, body)))
26039
26083
  );
26040
26084
  const refResult = checkRefs(root, { exists, isIgnored, trackedFirstSegments }, docs);
26041
26085
  const findings = [
@@ -26072,14 +26116,14 @@ ${sourceMessages}`;
26072
26116
  }
26073
26117
  for (const finding of findings) {
26074
26118
  if (finding.kind !== "missing-path") continue;
26075
- const directory = import_node_path16.posix.dirname(import_node_path16.posix.normalize(finding.detail)).split("/");
26119
+ const directory = import_node_path17.posix.dirname(import_node_path17.posix.normalize(finding.detail)).split("/");
26076
26120
  const distance = (path2) => {
26077
- const parts = import_node_path16.posix.dirname(path2).split("/");
26121
+ const parts = import_node_path17.posix.dirname(path2).split("/");
26078
26122
  let common = 0;
26079
26123
  while (common < directory.length && common < parts.length && directory[common] === parts[common]) common += 1;
26080
26124
  return directory.length + parts.length - 2 * common;
26081
26125
  };
26082
- const suggest = [...new Set(trackedFiles() ?? [])].filter((path2) => import_node_path16.posix.basename(path2) === import_node_path16.posix.basename(finding.detail) && exists((0, import_node_path16.join)(root, path2))).sort((a, b) => distance(a) - distance(b) || a.localeCompare(b)).slice(0, 3);
26126
+ const suggest = [...new Set(trackedFiles() ?? [])].filter((path2) => import_node_path17.posix.basename(path2) === import_node_path17.posix.basename(finding.detail) && exists((0, import_node_path17.join)(root, path2))).sort((a, b) => distance(a) - distance(b) || a.localeCompare(b)).slice(0, 3);
26083
26127
  if (suggest.length) finding.suggest = suggest;
26084
26128
  }
26085
26129
  return {
@@ -26172,31 +26216,31 @@ function walkMarkdown(dir) {
26172
26216
  const stack = [dir];
26173
26217
  while (stack.length) {
26174
26218
  const current = stack.pop();
26175
- for (const entry of (0, import_node_fs20.readdirSync)(current, { withFileTypes: true })) {
26176
- const full = (0, import_node_path17.join)(current, entry.name);
26219
+ for (const entry of (0, import_node_fs21.readdirSync)(current, { withFileTypes: true })) {
26220
+ const full = (0, import_node_path18.join)(current, entry.name);
26177
26221
  if (entry.isDirectory()) {
26178
26222
  stack.push(full);
26179
26223
  } else if (entry.isFile() && entry.name.endsWith(".md")) {
26180
- out.push((0, import_node_path17.relative)(dir, full).split(import_node_path17.sep).join("/"));
26224
+ out.push((0, import_node_path18.relative)(dir, full).split(import_node_path18.sep).join("/"));
26181
26225
  }
26182
26226
  }
26183
26227
  }
26184
26228
  return out;
26185
26229
  }
26186
26230
  function createDocsIndexDeps(repoRoot2) {
26187
- const docsDir = (0, import_node_path17.join)(repoRoot2, "docs");
26188
- const indexPath = (0, import_node_path17.join)(repoRoot2, DOCS_INDEX_PATH);
26231
+ const docsDir = (0, import_node_path18.join)(repoRoot2, "docs");
26232
+ const indexPath = (0, import_node_path18.join)(repoRoot2, DOCS_INDEX_PATH);
26189
26233
  return {
26190
26234
  listDocs: () => {
26191
- if (!(0, import_node_fs20.existsSync)(docsDir)) return [];
26235
+ if (!(0, import_node_fs21.existsSync)(docsDir)) return [];
26192
26236
  const walked = walkMarkdown(docsDir).filter(isRoutableDocsPath);
26193
26237
  if (!walked.length) return [];
26194
26238
  const ignored = defaultIsIgnored(repoRoot2, walked.map((rel) => `docs/${rel}`));
26195
26239
  return walked.filter((rel) => !ignored.has(`docs/${rel}`)).sort();
26196
26240
  },
26197
- readDoc: (relPath) => (0, import_node_fs20.readFileSync)((0, import_node_path17.join)(docsDir, relPath), "utf8"),
26198
- readIndex: () => (0, import_node_fs20.existsSync)(indexPath) ? (0, import_node_fs20.readFileSync)(indexPath, "utf8") : null,
26199
- writeIndex: (content) => (0, import_node_fs20.writeFileSync)(indexPath, content, "utf8")
26241
+ readDoc: (relPath) => (0, import_node_fs21.readFileSync)((0, import_node_path18.join)(docsDir, relPath), "utf8"),
26242
+ readIndex: () => (0, import_node_fs21.existsSync)(indexPath) ? (0, import_node_fs21.readFileSync)(indexPath, "utf8") : null,
26243
+ writeIndex: (content) => (0, import_node_fs21.writeFileSync)(indexPath, content, "utf8")
26200
26244
  };
26201
26245
  }
26202
26246
 
@@ -27319,8 +27363,8 @@ async function runBankRequest(fullName, options, deps) {
27319
27363
  }
27320
27364
 
27321
27365
  // src/train-apply-contracts.ts
27322
- var import_node_fs21 = require("node:fs");
27323
- var import_node_path18 = require("node:path");
27366
+ var import_node_fs22 = require("node:fs");
27367
+ var import_node_path19 = require("node:path");
27324
27368
  function reduceFollowUpOutcomes(outcomes) {
27325
27369
  if (outcomes.includes("failure")) return "failure";
27326
27370
  if (outcomes.includes("unresolved")) return "unresolved";
@@ -27400,7 +27444,7 @@ async function resolveFoldPaths(deps, model) {
27400
27444
  if (model === "vercel") return [];
27401
27445
  const helper = "scripts/release-distribution.mjs";
27402
27446
  if (model === "hub-serverless" || model === "registry-publish") {
27403
- if ((0, import_node_fs21.existsSync)(helper)) {
27447
+ if ((0, import_node_fs22.existsSync)(helper)) {
27404
27448
  let out;
27405
27449
  try {
27406
27450
  out = await deps.run("node", [helper, "changed-files"]);
@@ -27549,7 +27593,7 @@ async function restoreMainNpmPackIdentities(deps, version) {
27549
27593
  if (mainBom.version !== version) return void 0;
27550
27594
  let localBom;
27551
27595
  try {
27552
- localBom = JSON.parse((0, import_node_fs21.readFileSync)(bomPath, "utf8"));
27596
+ localBom = JSON.parse((0, import_node_fs22.readFileSync)(bomPath, "utf8"));
27553
27597
  } catch (e) {
27554
27598
  throw new Error(
27555
27599
  `version fold refused: ${bomPath} written by this fold's prepare is unreadable (${describeReadError(e)}) \u2014 cannot restore the published ${version} npm-pack identities from origin/main (#4503/#4713).`
@@ -27567,14 +27611,14 @@ async function restoreMainNpmPackIdentities(deps, version) {
27567
27611
  restored += 1;
27568
27612
  }
27569
27613
  if (restored === 0) return void 0;
27570
- (0, import_node_fs21.writeFileSync)(bomPath, `${JSON.stringify(localBom, null, 2)}
27614
+ (0, import_node_fs22.writeFileSync)(bomPath, `${JSON.stringify(localBom, null, 2)}
27571
27615
  `);
27572
27616
  return `restored ${restored} npm-pack BOM identit${restored === 1 ? "y" : "ies"} from origin/main (#4503)`;
27573
27617
  }
27574
27618
  function publishVisibilityFor(surfaceId) {
27575
27619
  let raw;
27576
27620
  try {
27577
- raw = (0, import_node_fs21.readFileSync)("surfaces.json", "utf8");
27621
+ raw = (0, import_node_fs22.readFileSync)("surfaces.json", "utf8");
27578
27622
  } catch (e) {
27579
27623
  if (e.code === "ENOENT") return "unknown";
27580
27624
  throw trainReadFailure(
@@ -27595,7 +27639,7 @@ function publishVisibilityFor(surfaceId) {
27595
27639
  }
27596
27640
  function npmPackArtifactName(packagePath) {
27597
27641
  try {
27598
- const pkg = JSON.parse((0, import_node_fs21.readFileSync)((0, import_node_path18.join)(packagePath, "package.json"), "utf8"));
27642
+ const pkg = JSON.parse((0, import_node_fs22.readFileSync)((0, import_node_path19.join)(packagePath, "package.json"), "utf8"));
27599
27643
  return pkg.name || void 0;
27600
27644
  } catch {
27601
27645
  return void 0;
@@ -27604,7 +27648,7 @@ function npmPackArtifactName(packagePath) {
27604
27648
  async function refuseDivergentPublishedNpmPack(deps, version) {
27605
27649
  let localBom;
27606
27650
  try {
27607
- localBom = JSON.parse((0, import_node_fs21.readFileSync)("distribution-bom.json", "utf8"));
27651
+ localBom = JSON.parse((0, import_node_fs22.readFileSync)("distribution-bom.json", "utf8"));
27608
27652
  } catch (e) {
27609
27653
  throw new Error(
27610
27654
  `version fold refused: distribution-bom.json written by this fold's prepare is unreadable (${e instanceof Error ? e.message.split("\n")[0] : String(e)}) \u2014 cannot compare the same-PATCH ${version} npm-pack identities against published npm (#4503/#4662).`
@@ -28556,10 +28600,10 @@ var rollout_plan_default = {
28556
28600
  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)."
28557
28601
  },
28558
28602
  baseline: {
28559
- version: "4.6.47",
28560
- tag: "v4.6.47",
28561
- commit: "4555234d7aa8",
28562
- npm: "@mutmutco/cli@4.6.47"
28603
+ version: "4.6.49",
28604
+ tag: "v4.6.49",
28605
+ commit: "2de561d64225",
28606
+ npm: "@mutmutco/cli@4.6.49"
28563
28607
  },
28564
28608
  exitCriterion: "fleet-n-of-n",
28565
28609
  hubOnlyShortcut: "forbidden",
@@ -28576,14 +28620,14 @@ var rollout_plan_default = {
28576
28620
  repo: "mutmutco/mmi-hub",
28577
28621
  role: "canary",
28578
28622
  schedule: "train",
28579
- v3Target: "v4.6.47"
28623
+ v3Target: "v4.6.49"
28580
28624
  }
28581
28625
  ],
28582
28626
  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.",
28583
28627
  rollback: {
28584
28628
  independent: true,
28585
- mechanism: "npm dist-tag latest -> 4.6.47 and redeploy the Hub Lambda from tag v4.6.47 (4555234d7aa8); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
28586
- v3Target: "v4.6.47 (@mutmutco/cli@4.6.47, tag commit 4555234d7aa8 \u2014 last known-good release carrying the repo-index v4-only contract)"
28629
+ mechanism: "npm dist-tag latest -> 4.6.49 and redeploy the Hub Lambda from tag v4.6.49 (2de561d64225); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
28630
+ v3Target: "v4.6.49 (@mutmutco/cli@4.6.49, tag commit 2de561d64225 \u2014 last known-good release carrying the repo-index v4-only contract)"
28587
28631
  }
28588
28632
  },
28589
28633
  {
@@ -28923,17 +28967,6 @@ function resolveIssueViewGhFields(normalized) {
28923
28967
  return { ghFields: requested.join(","), numberIsTransport: false };
28924
28968
  }
28925
28969
 
28926
- // src/utf8-receipt.ts
28927
- var import_node_fs22 = require("node:fs");
28928
- var import_node_path19 = require("node:path");
28929
- function writeUtf8Receipt(path2, text) {
28930
- (0, import_node_fs22.mkdirSync)((0, import_node_path19.dirname)(path2), { recursive: true });
28931
- const body = text.endsWith("\n") ? text : `${text}
28932
- `;
28933
- (0, import_node_fs22.writeFileSync)(path2, body, "utf8");
28934
- return Buffer.byteLength(body, "utf8");
28935
- }
28936
-
28937
28970
  // src/query-commands.ts
28938
28971
  var GH_LIST_TIMEOUT_MS = 2e4;
28939
28972
  var DEFAULT_LIMIT = 30;
@@ -35552,11 +35585,41 @@ function ledgerFailedRemedy(ledger) {
35552
35585
  function alignmentPrNumbers(note) {
35553
35586
  return [...new Set([...(note ?? "").matchAll(/\/pull\/(\d+)/g)].map((m) => Number.parseInt(m[1], 10)))];
35554
35587
  }
35588
+ function alignmentHeadBranches(tag) {
35589
+ return [
35590
+ { target: "development", head: `release-align/development/${tag}` },
35591
+ { target: "rc", head: `release-align/rc/${tag}` }
35592
+ ];
35593
+ }
35594
+ async function alignmentLegClosedByHead(deps, prior) {
35595
+ const found = [];
35596
+ for (const { target, head } of alignmentHeadBranches(prior.tag)) {
35597
+ let rows;
35598
+ try {
35599
+ rows = JSON.parse(await deps.run("gh", ["pr", "list", "--repo", prior.repo, "--head", head, "--state", "all", "--json", "number,state"]));
35600
+ } catch {
35601
+ return void 0;
35602
+ }
35603
+ if (!Array.isArray(rows)) return void 0;
35604
+ for (const row of rows) {
35605
+ if (typeof row?.number !== "number" || !Number.isInteger(row.number) || typeof row.state !== "string") return void 0;
35606
+ if (row.state !== "MERGED") return void 0;
35607
+ found.push({ target, head, number: row.number });
35608
+ }
35609
+ }
35610
+ if (!found.some((f) => f.target === "development")) return void 0;
35611
+ const named = found.map((f) => `#${f.number} (head \`${f.head}\`)`).join(", ");
35612
+ return {
35613
+ state: "complete",
35614
+ note: `alignment: already-merged \u2014 alignment PR ${named} merged on GitHub after the train returned; the leg named no PR, so the PR was found by its head branch; closed live at the next pre-flight (#10212)`,
35615
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
35616
+ };
35617
+ }
35555
35618
  async function alignmentLegClosedLive(deps, prior) {
35556
35619
  const leg = prior.phases.alignment;
35557
35620
  if (leg.state !== "pending") return void 0;
35558
35621
  const numbers = alignmentPrNumbers(leg.note);
35559
- if (numbers.length === 0) return void 0;
35622
+ if (numbers.length === 0) return alignmentLegClosedByHead(deps, prior);
35560
35623
  for (const n of numbers) {
35561
35624
  let state = "";
35562
35625
  try {
@@ -42773,6 +42836,9 @@ async function readGhPrStateWithRetry(fetchState, options) {
42773
42836
  function isNoFailedCheckQueueEjection(run) {
42774
42837
  return !run || run.conclusion === null || run.conclusion === "cancelled" || run.conclusion === "neutral" || run.conclusion === "skipped";
42775
42838
  }
42839
+ function isUnchangedHeadFailedRunEjection(run, submittedHead, currentHead) {
42840
+ return !!run && run.conclusion === "failure" && !!submittedHead && !!currentHead && currentHead.toLowerCase() === submittedHead.toLowerCase();
42841
+ }
42776
42842
  async function runPrLand(prNumber, options, deps) {
42777
42843
  const first = await runPrLandOnce(prNumber, options, deps);
42778
42844
  if (options.enqueueOnly || options.expectHead || first.status !== "failed") return first;
@@ -42964,6 +43030,7 @@ async function pollPrMergeQueue(pr, options, deps, base, snapshot, deadlineMs) {
42964
43030
  const deadline = deadlineMs ?? now() + timeoutMs;
42965
43031
  let attempt = 0;
42966
43032
  let credentialMismatched = false;
43033
+ let submittedHead;
42967
43034
  const observe = (read) => {
42968
43035
  const seen = deps.observeQueued?.(pr, base.repo, read);
42969
43036
  if (seen?.mismatched) {
@@ -42983,6 +43050,7 @@ async function pollPrMergeQueue(pr, options, deps, base, snapshot, deadlineMs) {
42983
43050
  state: "queued",
42984
43051
  ...read.mergeQueueEntry ? { position: read.mergeQueueEntry.position, entryState: read.mergeQueueEntry.state } : {}
42985
43052
  };
43053
+ submittedHead = read.headRefOid ?? submittedHead;
42986
43054
  const groupSha = read.mergeQueueEntry?.headCommit?.oid;
42987
43055
  if (!options.enqueueOnly && groupSha && deps.findQueueStartupFailure) {
42988
43056
  const startup = await deps.findQueueStartupFailure(base.repo, groupSha).catch(() => void 0);
@@ -43011,11 +43079,13 @@ async function pollPrMergeQueue(pr, options, deps, base, snapshot, deadlineMs) {
43011
43079
  continue;
43012
43080
  }
43013
43081
  const run = await deps.findMergeGroupRun(pr, base.repo, base.base);
43082
+ const withheld = isUnchangedHeadFailedRunEjection(run, submittedHead, again.headRefOid);
43014
43083
  const result2 = {
43015
43084
  ...base,
43016
43085
  mergeQueue: { ...base.mergeQueue, state: "ejected", ...run ? { ejectedRun: run } : {} },
43017
43086
  ...base.pinnedHead ? { pinnedHead: { ...base.pinnedHead, refused: "ejected" } } : {},
43018
- error: `merge queue ejected PR #${pr} \u2014 ${run ? `merge_group run ${run.id} ${run.conclusion} ${run.url}` : "merge_group run not found"}; ${prQueueReadHint(pr, base.repo)}${isNoFailedCheckQueueEjection(run) ? `; no check failed \u2014 re-run \`mmi-cli devops pr land ${pr} --repo ${base.repo}\` once to re-enqueue, and if a land is refused again report the exact refusal verbatim (never fall back to a direct/admin merge or an \`MMI_ACTOR=personal\` landing workaround)` : ""}`
43087
+ ...withheld && submittedHead ? { ejectedOnFailedRun: { runId: run.id, head: submittedHead, ...run.url ? { url: run.url } : {} } } : {},
43088
+ error: withheld && run ? `merge queue ejected PR #${pr} \u2014 merge_group run ${run.id} ${run.conclusion} ${run.url}; the PR head is unchanged since that run, so re-enqueueing it would resubmit the same failing commit and is withheld \u2014 fix the failed check, push the fix commit, then rerun \`mmi-cli devops pr land ${pr} --repo ${base.repo}\` (while a queued entry blocks a fix push, take the PR out of the queue first with \`mmi-cli devops pr land ${pr} --repo ${base.repo} --dequeue\`)` : `merge queue ejected PR #${pr} \u2014 ${run ? `merge_group run ${run.id} ${run.conclusion} ${run.url}` : "merge_group run not found"}; ${prQueueReadHint(pr, base.repo)}${isNoFailedCheckQueueEjection(run) ? `; no check failed \u2014 re-run \`mmi-cli devops pr land ${pr} --repo ${base.repo}\` once to re-enqueue, and if a land is refused again report the exact refusal verbatim (never fall back to a direct/admin merge or an \`MMI_ACTOR=personal\` landing workaround)` : ""}`
43019
43089
  };
43020
43090
  deps.queueProgress?.(result2);
43021
43091
  return result2;
@@ -43045,7 +43115,7 @@ async function landMany(prs, landOne, cleanup, hooks) {
43045
43115
  const safe = async (pr) => landOne(pr).catch((e) => ({ status: "failed", repo: "", pr, base: "", error: e.message }));
43046
43116
  return Promise.all(prs.map(async (pr) => {
43047
43117
  let result = await safe(pr);
43048
- if (prLandOutcome(result) === "ejected" && !result.pinnedHead && !result.retriedAfterEjection) {
43118
+ if (prLandOutcome(result) === "ejected" && !result.pinnedHead && !result.retriedAfterEjection && !result.ejectedOnFailedRun) {
43049
43119
  const baseRed = await hooks?.baseRedCheck?.(pr, result).catch(() => void 0);
43050
43120
  if (baseRed) {
43051
43121
  result = { ...result, baseRed: { base: result.base, baseRunId: baseRed.baseRunId }, error: `base-red: failures already on ${result.base} (${baseRed.baseRunId})` };
@@ -43325,10 +43395,17 @@ function readMessageOverride(message2, cwd) {
43325
43395
  }
43326
43396
  function readOverride(base, cwd, head = "HEAD") {
43327
43397
  const format = `%H${FLD}%(trailers:key=${TRAILER_KEY},valueonly,unfold)${FLD}%B${REC}`;
43398
+ const records = git(["log", `${base}..${head}`, `--format=${format}`], cwd).split(REC).map((record2) => record2.replace(/^\s+/, "").split(FLD));
43399
+ return readOverrideRecords(records, cwd);
43400
+ }
43401
+ function readMessageOverrides(messages, cwd) {
43402
+ const records = messages.map((message2, index) => [`source-${index + 1}`, readMessageOverride(message2, cwd), message2]).reverse();
43403
+ return readOverrideRecords(records, cwd);
43404
+ }
43405
+ function readOverrideRecords(records, cwd) {
43328
43406
  const refusals = [];
43329
43407
  let override = null;
43330
- for (const record2 of git(["log", `${base}..${head}`, `--format=${format}`], cwd).split(REC)) {
43331
- const [sha, trailer, body] = record2.replace(/^\s+/, "").split(FLD);
43408
+ for (const [sha, trailer, body] of records) {
43332
43409
  if (!sha) continue;
43333
43410
  if (override) break;
43334
43411
  const { value, messageTransform } = readOverrideTrailer(trailer, body, cwd);
@@ -43354,6 +43431,9 @@ var GO_TEST_RE = GO_TEST_FILE_RE;
43354
43431
  function readMessageOverride2(message2, cwd) {
43355
43432
  return readMessageOverride(message2, cwd);
43356
43433
  }
43434
+ function readMessageOverrides2(messages, cwd) {
43435
+ return readMessageOverrides(messages, cwd);
43436
+ }
43357
43437
  function translate(glob) {
43358
43438
  let out = "";
43359
43439
  for (let i = 0; i < glob.length; i++) {
@@ -44469,6 +44549,38 @@ ${message2.body}`)) || readMessageOverride2(githubQueueSquashMessage(title, pr,
44469
44549
  error: "queue preparation needed: the multi-commit COMMIT_MESSAGES squash would bury the Test-Policy-Override waiver mid-message, so the queue check would fail malformed-override-trailer"
44470
44550
  };
44471
44551
  }
44552
+ function queueBodyFile(pr) {
44553
+ return `.jerv/tmp/pr-${pr}-body.md`;
44554
+ }
44555
+ function writeQueueBodyFile(pr, body, targets, cwd = process.cwd()) {
44556
+ try {
44557
+ const alreadyCloses = (number) => new RegExp(`^[ \\t]*(?:close[sd]?|fix(?:e[sd])?|resolve[sd]?):?[ \\t]+\\[?#${number}(?![0-9])`, "im").test(body);
44558
+ const closes = targets.filter((number) => !alreadyCloses(number)).map((number) => `Closes #${number}`).join("\n");
44559
+ const base = body.trimEnd();
44560
+ const separator = [...base.matchAll(/\n[ \t]*\n/g)].at(-1);
44561
+ const head = separator ? base.slice(0, separator.index) : "";
44562
+ const last = separator ? base.slice(separator.index + separator[0].length) : base;
44563
+ const content = !closes ? `${base}
44564
+ ` : parseTrailers(base, cwd).length > 0 ? `${head ? `${head}
44565
+
44566
+ ` : ""}${closes}
44567
+
44568
+ ${last.trimEnd()}
44569
+ ` : `${base}${base ? "\n\n" : ""}${closes}
44570
+ `;
44571
+ const directory = (0, import_node_path31.join)(cwd, ".jerv", "tmp");
44572
+ (0, import_node_fs34.mkdirSync)(directory, { recursive: true });
44573
+ (0, import_node_fs34.writeFileSync)((0, import_node_path31.join)(directory, `pr-${pr}-body.md`), content);
44574
+ return queueBodyFile(pr);
44575
+ } catch {
44576
+ return void 0;
44577
+ }
44578
+ }
44579
+ function queueMessageRemedy(pr, repo, targets, bodyFile) {
44580
+ const named = (format, fallback) => targets.length ? targets.map(format).join(", ") : fallback;
44581
+ const issues = named((number) => `#${number}`, "#N");
44582
+ return `PR author: if PR #${pr} is meant to close ${issues}, add ${named((number) => `\`Closes #${number}\``, "`Closes #N`")} to the PR body (\`mmi-cli devops pr edit ${pr} --repo ${repo} --body-file ${queueBodyFile(pr)}\`) and rerun \`mmi-cli devops pr land ${pr} --repo ${repo}\` without pushing a new head.` + (bodyFile ? ` The body file that command reads was just written (\`${bodyFile}\`): the current PR body plus a closing line for any target it lacked, so it runs exactly as printed.` : "") + ` If ${issues} must stay open, remove the closing phrases from the source commit or PR text using ${named((number) => `\`Part of #${number}\``, "`Part of #N`")} / ${named((number) => `\`Refs #${number}\``, "`Refs #N`")}, then re-run checks and \`pr land\` with the prepared head. A queue cannot accept --squash-body-file.`;
44583
+ }
44472
44584
  async function inspectQueuePreparation(pr, repo, options) {
44473
44585
  const read = options.read ?? readApi;
44474
44586
  const queueRead = options.read ?? actorReadApi;
@@ -44521,7 +44633,7 @@ async function inspectQueuePreparation(pr, repo, options) {
44521
44633
  status: "preparation-needed",
44522
44634
  reason,
44523
44635
  issueTargets: targets.map((number) => `${repo}#${number}`),
44524
- nextAction: nextAction ?? (reason === "queue-message" ? `PR author: if PR #${pr} is meant to close ${targets.length ? targets.map((number) => `#${number}`).join(", ") : "#N"}, add ${targets.length ? targets.map((number) => `\`Closes #${number}\``).join(", ") : "`Closes #N`"} to the PR body (\`mmi-cli devops pr edit ${pr} --repo ${repo} --body-file .jerv/tmp/pr-${pr}-body.md\`) and rerun \`mmi-cli devops pr land ${pr} --repo ${repo}\` without pushing a new head; if ${targets.length ? targets.map((number) => `#${number}`).join(", ") : "#N"} must stay open, remove the closing phrases from the source commit or PR text using ${targets.length ? targets.map((number) => `\`Part of #${number}\``).join(", ") : "`Part of #N`"} / ${targets.length ? targets.map((number) => `\`Refs #${number}\``).join(", ") : "`Refs #N`"}, then re-run checks and \`pr land\` with the prepared head. A queue cannot accept --squash-body-file.` : "PR author: prepare the source commit messages and required trailers without a body override; re-run checks and pr land. GitHub may append coauthors even to one source commit, so source consolidation alone does not prove the generated message passes test policy. The combined queue candidate must pass with the updated shared trailer reader. Keep repository settings unchanged.")
44636
+ nextAction: nextAction ?? (reason === "queue-message" ? queueMessageRemedy(pr, repo, targets) : "PR author: prepare the source commit messages and required trailers without a body override; re-run checks and pr land. GitHub may append coauthors even to one source commit, so source consolidation alone does not prove the generated message passes test policy. The combined queue candidate must pass with the updated shared trailer reader. Keep repository settings unchanged.")
44525
44637
  },
44526
44638
  error: `queue preparation needed: ${detail}`
44527
44639
  });
@@ -44580,7 +44692,8 @@ ${pull.body}`;
44580
44692
  ...findNegatedClosings(input.text, closing),
44581
44693
  ...findCrossRepoAmbiguousClosings(input.text, closing, repo)
44582
44694
  ])];
44583
- return { kind: "refused", result: preparation("queue-message", targets, `GitHub's unchanged ${policy.body} message is unsafe for ${targets.map((number) => `${repo}#${number}`).join(", ")}. ${closingGuardDiagnostic(verdict.message)} See preparation.nextAction for the exact \`mmi-cli devops pr edit ${pr} --repo ${repo} --body-file .jerv/tmp/pr-${pr}-body.md\` remedy.`) };
44695
+ const bodyFile = targets.length ? writeQueueBodyFile(pr, pull.body, targets, process.cwd()) : void 0;
44696
+ return { kind: "refused", result: preparation("queue-message", targets, `GitHub's unchanged ${policy.body} message is unsafe for ${targets.map((number) => `${repo}#${number}`).join(", ")}. ${closingGuardDiagnostic(verdict.message)} See preparation.nextAction for the exact \`mmi-cli devops pr edit ${pr} --repo ${repo} --body-file ${queueBodyFile(pr)}\` remedy.`, queueMessageRemedy(pr, repo, targets, bodyFile)) };
44584
44697
  }
44585
44698
  if (method === "SQUASH" && policy.body === "COMMIT_MESSAGES" && options.bodyText == null) {
44586
44699
  const refusal = checkQueueSquashOverride(pull.title, pr, messages, process.cwd());
@@ -44588,7 +44701,18 @@ ${pull.body}`;
44588
44701
  }
44589
44702
  const requiredBody = options.bodyText ?? composedBody;
44590
44703
  const prBodyPreservesOverrides = requiredBody != null && method === "SQUASH" && policy.body === "PR_BODY" && options.bodyText == null && prBodyPreservesCommitOverrides(messages, headline, pull.body, process.cwd());
44591
- const queueKeepsWaiver = options.bodyText == null && method !== "SQUASH" && Boolean(readMessageOverride2(actualMessage, process.cwd()));
44704
+ const retained = options.bodyText == null && method !== "SQUASH" ? readMessageOverrides2(messages.map((message2) => `${message2.headline}
44705
+
44706
+ ${message2.body}`), process.cwd()) : null;
44707
+ if (retained?.refusals.length) {
44708
+ return { kind: "refused", result: preparation(
44709
+ "queue-message-override",
44710
+ [],
44711
+ `retained source commit messages fail the shared waiver reader: ${retained.refusals.map((finding) => finding.detail).join("\n")}`,
44712
+ "PR author: the queue retains each source commit message. Put an authorized Test-Policy-Override with a known scope in a later commit's Git-recognized final trailer block, push without rewriting history, and rerun pr land. The nearest valid waiver supersedes older ones; malformed or unknown-scope waivers newer than it still refuse. The synthetic merge-message preview does not decide this requirement. Keep repository settings unchanged."
44713
+ ) };
44714
+ }
44715
+ const queueKeepsWaiver = Boolean(retained?.override && !retained.refusals.length);
44592
44716
  if (requiredBody && !prBodyPreservesOverrides && !queueKeepsWaiver && (method !== "SQUASH" || requiredBody.trim() !== body.trim())) {
44593
44717
  if (method === "SQUASH" && policy.body === "PR_BODY" && options.bodyText == null) {
44594
44718
  return {
@@ -44675,7 +44799,7 @@ async function abandonQueueAdmission(pr, repo, options = {}) {
44675
44799
  if (!snapshot.headRefOid || snapshot.headRefOid.toLowerCase() !== receipt.head.toLowerCase()) {
44676
44800
  return refuse(`the PR head is ${snapshot.headRefOid ?? "unreadable"}, not the receipt's submitted head ${receipt.head}; the admission is unchanged`);
44677
44801
  }
44678
- if (snapshot.isInMergeQueue) return refuse("the PR is currently in the merge queue, so the admission is not stuck; the admission is unchanged");
44802
+ if (snapshot.isInMergeQueue) return refuse(`the PR is currently in the merge queue, so the admission is not stuck; the admission is unchanged \u2014 take the PR out of the queue first with \`mmi-cli devops pr land ${pr} --repo ${repo} --dequeue\` if a fix commit must be pushed`);
44679
44803
  try {
44680
44804
  journal.save("abandoned");
44681
44805
  } catch (error) {
@@ -44683,6 +44807,75 @@ async function abandonQueueAdmission(pr, repo, options = {}) {
44683
44807
  }
44684
44808
  return { status: "abandoned", repo, pr, head: receipt.head, ...receipt.uuid ? { operation: receipt.uuid } : {} };
44685
44809
  }
44810
+ async function dequeuePrMergeQueue(pr, repo, options = {}) {
44811
+ if (!/^\d+$/.test(pr)) return { status: "refused", repo, pr, reason: "a bare PR number is required" };
44812
+ const refuse = (reason) => ({ status: "refused", repo, pr, reason });
44813
+ const read = options.read ?? actorReadApi;
44814
+ let entry;
44815
+ try {
44816
+ const result = await queueGraphql(
44817
+ repo,
44818
+ `pullRequest(number:${pr}) { id state isInMergeQueue headRefOid }`,
44819
+ read
44820
+ );
44821
+ if (!result.pullRequest) throw new Error(`merge queue PR #${pr} not found`);
44822
+ entry = result.pullRequest;
44823
+ } catch (error) {
44824
+ return refuse(`the PR and queue state could not be read (${error.message}); nothing was dequeued`);
44825
+ }
44826
+ if (!entry.isInMergeQueue) return refuse(`the PR is '${entry.state ?? "unreadable"}' and not in the merge queue; nothing to dequeue`);
44827
+ if (!entry.id) return refuse("the PR node id is unreadable; nothing was dequeued");
44828
+ let output;
44829
+ try {
44830
+ const mutation = await (options.write ?? writeApi)(["graphql", "-f", "query=mutation($id:ID!){dequeuePullRequest(input:{pullRequestId:$id}){clientMutationId}}", "-f", `id=${entry.id}`]);
44831
+ output = typeof mutation === "string" ? mutation : mutation.stdout;
44832
+ } catch (error) {
44833
+ return refuse(`dequeuePullRequest failed (${error.message}); the queue entry is unchanged`);
44834
+ }
44835
+ let graphqlErrors = "";
44836
+ try {
44837
+ const response = JSON.parse(output);
44838
+ if (response.errors?.length) graphqlErrors = response.errors.map((e) => e.message).join("; ");
44839
+ } catch {
44840
+ graphqlErrors = "the dequeue response is unreadable";
44841
+ }
44842
+ if (graphqlErrors) return refuse(`dequeuePullRequest reported: ${graphqlErrors}; the queue entry is unchanged \u2014 verify with \`mmi-cli devops read pr queue ${pr} --repo ${repo} --json\` before pushing`);
44843
+ const sleep2 = options.sleep ?? ((ms) => new Promise((resolve8) => setTimeout(resolve8, ms)));
44844
+ let verified;
44845
+ let lastRead;
44846
+ for (let attempt = 0; attempt < 3; attempt++) {
44847
+ if (attempt) await sleep2(attempt * 1e3);
44848
+ try {
44849
+ const observed = await readPrMergeQueue(pr, repo, read, sleep2);
44850
+ if (!observed.isInMergeQueue) {
44851
+ verified = observed;
44852
+ break;
44853
+ }
44854
+ lastRead = `the PR still reads as in the merge queue (state '${observed.state}')`;
44855
+ } catch (error) {
44856
+ lastRead = error.message;
44857
+ }
44858
+ }
44859
+ if (!verified) {
44860
+ return refuse(`dequeuePullRequest was sent but the read-back could not verify the PR out of the queue (${lastRead ?? "unreadable"}); do not assume the entry is gone \u2014 verify with \`mmi-cli devops read pr queue ${pr} --repo ${repo} --json\`, and rerun --dequeue only if the PR still shows queued`);
44861
+ }
44862
+ let admission = "none";
44863
+ let operation;
44864
+ try {
44865
+ const journal = admissionJournal(repo, pr, options.receiptDirectory);
44866
+ const receipt = journal.receipt;
44867
+ if (receipt && (["pending", "enqueued"].includes(receipt.state) || receipt.state === "dispatching" && !receipt.uuid)) {
44868
+ journal.save("abandoned");
44869
+ admission = "terminal";
44870
+ operation = receipt.uuid;
44871
+ } else if (receipt) {
44872
+ admission = "left-open";
44873
+ }
44874
+ } catch {
44875
+ admission = "unreadable";
44876
+ }
44877
+ return { status: "dequeued", repo, pr, ...entry.headRefOid ? { head: entry.headRefOid } : {}, admission, ...operation ? { operation } : {} };
44878
+ }
44686
44879
  function closingGuardDiagnostic(message2) {
44687
44880
  if (!message2) return "";
44688
44881
  const remedy = message2.search(/GitHub's merge queue composes|If this PR is meant to close/);
@@ -45173,8 +45366,204 @@ function registerRunDispatch(run, writeToken, projects) {
45173
45366
  });
45174
45367
  }
45175
45368
 
45176
- // src/devops-vars.ts
45369
+ // src/remote-test.ts
45177
45370
  var import_node_crypto13 = require("node:crypto");
45371
+ init_cli_shared();
45372
+ var FOCUSED_TEST_REPO = "mutmutco/MMI-Hub";
45373
+ var FOCUSED_TEST_WORKFLOW = "focused-test.yml";
45374
+ var FOCUSED_TEST_REF = "development";
45375
+ var FOCUSED_TEST_OWNER = "mutmutco";
45376
+ var REMOTE_TEST_MAX_FILES = 20;
45377
+ var REMOTE_TEST_POLL_MS = 15e3;
45378
+ var REMOTE_TEST_CORRELATE_MS = 5 * 6e4;
45379
+ var REMOTE_TEST_WATCH_MS = 30 * 6e4;
45380
+ var REMOTE_TEST_LOG_TAIL = 60;
45381
+ var REMOTE_TEST_UNKNOWN = 3;
45382
+ var REMOTE_TEST_FILE_RE = /^[A-Za-z0-9._/@-]+$/;
45383
+ var REMOTE_TEST_NAME_RE = /^[A-Za-z0-9._-]+$/;
45384
+ var REMOTE_TEST_SHA_RE = /^[0-9a-f]{40}$/;
45385
+ function focusedTestRepoName(repo) {
45386
+ const parts = repo.split("/");
45387
+ if (parts.length !== 2 || !parts.every((part) => REMOTE_TEST_NAME_RE.test(part))) {
45388
+ throw new Error(`remote test: --repo must be <owner>/<repo> (letters, digits, dot, underscore, hyphen), received '${repo}'`);
45389
+ }
45390
+ if (parts[0].toLowerCase() !== FOCUSED_TEST_OWNER) {
45391
+ throw new Error(`remote test: --repo must name a ${FOCUSED_TEST_OWNER}/ repository (the central workflow checks out ${FOCUSED_TEST_OWNER}/<name> only), received '${repo}'`);
45392
+ }
45393
+ return parts[1];
45394
+ }
45395
+ function validateRemoteTest(request) {
45396
+ focusedTestRepoName(request.repo);
45397
+ if (!REMOTE_TEST_SHA_RE.test(request.sha)) {
45398
+ throw new Error(`remote test: --sha must be a 40-character lowercase commit sha, received '${request.sha}'`);
45399
+ }
45400
+ if (request.tool !== "node-test" && request.tool !== "vitest") {
45401
+ throw new Error(`remote test: --tool must be node-test or vitest, received '${String(request.tool)}'`);
45402
+ }
45403
+ if (!Array.isArray(request.files) || request.files.length === 0) {
45404
+ throw new Error("remote test: name at least one test file (space-separated literals, max 20)");
45405
+ }
45406
+ if (request.files.length > REMOTE_TEST_MAX_FILES) {
45407
+ throw new Error(`remote test: at most ${REMOTE_TEST_MAX_FILES} test files per run, received ${request.files.length}`);
45408
+ }
45409
+ for (const file of request.files) {
45410
+ if (!REMOTE_TEST_FILE_RE.test(file)) {
45411
+ throw new Error(`remote test: file path uses characters outside A-Za-z0-9._/@- : '${file}'`);
45412
+ }
45413
+ if (file.includes("..")) throw new Error(`remote test: file path must not contain '..': '${file}'`);
45414
+ if (file.startsWith("/")) throw new Error(`remote test: file path must be repo-relative: '${file}'`);
45415
+ if (file.startsWith("-")) throw new Error(`remote test: a file path must never read as a flag: '${file}'`);
45416
+ }
45417
+ if (typeof request.install !== "boolean") throw new Error("remote test: install must be a boolean");
45418
+ }
45419
+ function tailLines(text, count) {
45420
+ return text.replace(/\r\n/g, "\n").split("\n").filter((line, index, all) => !(index === all.length - 1 && line === "")).slice(-count);
45421
+ }
45422
+ function parseJson(path2, text) {
45423
+ try {
45424
+ return JSON.parse(text);
45425
+ } catch {
45426
+ throw new Error(`remote test: unreadable answer from ${path2}`);
45427
+ }
45428
+ }
45429
+ async function runRemoteTest(request, deps) {
45430
+ validateRemoteTest(request);
45431
+ const name = focusedTestRepoName(request.repo);
45432
+ const nonce = deps.nonce();
45433
+ const title = `focused-test ${nonce}`;
45434
+ const uncertain = (message2, runId = null, url = null) => ({ exitCode: REMOTE_TEST_UNKNOWN, title, runId, url, conclusion: null, tail: [], message: message2 });
45435
+ const inputs = [
45436
+ `repo=${name}`,
45437
+ `sha=${request.sha}`,
45438
+ `tool=${request.tool}`,
45439
+ `files=${request.files.join(" ")}`,
45440
+ `install=${request.install}`,
45441
+ `nonce=${nonce}`
45442
+ ];
45443
+ try {
45444
+ await deps.dispatch(inputs);
45445
+ } catch (error) {
45446
+ return uncertain(`remote test: dispatch outcome unknown \u2014 ${error.message}; inspect ${FOCUSED_TEST_REPO} Actions before any retry`);
45447
+ }
45448
+ const runsPath = `repos/${FOCUSED_TEST_REPO}/actions/workflows/${FOCUSED_TEST_WORKFLOW}/runs?event=workflow_dispatch&per_page=50`;
45449
+ let run;
45450
+ try {
45451
+ const correlateUntil = deps.now() + REMOTE_TEST_CORRELATE_MS;
45452
+ while (!run) {
45453
+ const rows = parseJson(runsPath, await deps.read(runsPath)).workflow_runs ?? [];
45454
+ run = rows.find((row) => row.display_title === title);
45455
+ if (run) break;
45456
+ if (deps.now() >= correlateUntil) {
45457
+ return uncertain(`remote test: no run named "${title}" appeared within ${REMOTE_TEST_CORRELATE_MS / 6e4}m \u2014 the dispatch was refused or is queued behind a rule; nothing was retried`);
45458
+ }
45459
+ await deps.sleep(REMOTE_TEST_POLL_MS);
45460
+ }
45461
+ const runPath = `repos/${FOCUSED_TEST_REPO}/actions/runs/${run.id}`;
45462
+ const watchUntil = deps.now() + REMOTE_TEST_WATCH_MS;
45463
+ let final;
45464
+ while (!final) {
45465
+ const current = parseJson(runPath, await deps.read(runPath));
45466
+ if (current.status === "completed") {
45467
+ final = current;
45468
+ break;
45469
+ }
45470
+ if (deps.now() >= watchUntil) {
45471
+ return uncertain(`remote test: run ${run.id} still ${current.status} after ${REMOTE_TEST_WATCH_MS / 6e4}m \u2014 no verdict exists`, run.id, current.html_url);
45472
+ }
45473
+ await deps.sleep(REMOTE_TEST_POLL_MS);
45474
+ }
45475
+ const jobsPath = `${runPath}/jobs?per_page=100`;
45476
+ const jobs = parseJson(jobsPath, await deps.read(jobsPath)).jobs ?? [];
45477
+ const testJob = jobs.find((job) => job.name === "test");
45478
+ const conclusion = testJob?.conclusion ?? final.conclusion;
45479
+ let tail = [];
45480
+ if (testJob) {
45481
+ try {
45482
+ tail = tailLines(await deps.read(`repos/${FOCUSED_TEST_REPO}/actions/jobs/${testJob.id}/logs`), REMOTE_TEST_LOG_TAIL);
45483
+ } catch {
45484
+ tail = [];
45485
+ }
45486
+ }
45487
+ const url = final.html_url;
45488
+ if (!testJob && !final.conclusion) return uncertain(`remote test: run ${run.id} completed with no verdict \u2014 no \`test\` job and no run conclusion`, run.id, url);
45489
+ return {
45490
+ exitCode: conclusion === "success" ? 0 : 1,
45491
+ title,
45492
+ runId: run.id,
45493
+ url,
45494
+ conclusion,
45495
+ tail,
45496
+ message: `remote test: ${title} ${conclusion ?? "completed"} on ${request.repo}@${request.sha.slice(0, 12)} \u2014 ${url}`
45497
+ };
45498
+ } catch (error) {
45499
+ return uncertain(`remote test: ${error.message} \u2014 the dispatch already happened; inspect ${FOCUSED_TEST_REPO} Actions for "${title}", never re-dispatch blind`, run?.id ?? null, run?.html_url ?? null);
45500
+ }
45501
+ }
45502
+ function appApiRead(writeToken, exec3 = execFileP) {
45503
+ return async (path2) => {
45504
+ const token = await writeToken();
45505
+ const { stdout } = await exec3("gh", ["api", path2], { timeout: 6e4, env: { ...process.env, GH_TOKEN: token, MMI_GH_POOL: "app" } });
45506
+ return stdout;
45507
+ };
45508
+ }
45509
+ var defaultGit2 = {
45510
+ git: async (args) => {
45511
+ try {
45512
+ return (await execFileP("git", args, { timeout: 15e3 })).stdout.trim();
45513
+ } catch {
45514
+ return "";
45515
+ }
45516
+ }
45517
+ };
45518
+ async function originRepo(git3 = defaultGit2) {
45519
+ const url = await git3.git(["config", "--get", "remote.origin.url"]);
45520
+ const match = /[:/]([A-Za-z0-9._-]+)\/([A-Za-z0-9._-]+?)(?:\.git)?$/.exec(url);
45521
+ if (!match) throw new Error(`remote test: no usable origin remote (git config --get remote.origin.url returned '${url}'); pass --repo <owner>/<repo>`);
45522
+ return `${match[1]}/${match[2]}`;
45523
+ }
45524
+ async function pushedSha(sha, git3 = defaultGit2) {
45525
+ const resolved = sha ?? await git3.git(["rev-parse", "HEAD"]);
45526
+ if (!REMOTE_TEST_SHA_RE.test(resolved)) {
45527
+ throw new Error(`remote test: --sha must be a 40-character lowercase commit sha (received '${resolved}'); in a checkout, omit --sha to use HEAD`);
45528
+ }
45529
+ if (sha === void 0 && !await git3.git(["branch", "-r", "--contains", resolved])) {
45530
+ throw new Error(`remote test: ${resolved.slice(0, 12)} is not on any origin branch \u2014 push first (the workflow checks the commit out of origin, so a local-only HEAD cannot be tested)`);
45531
+ }
45532
+ return resolved;
45533
+ }
45534
+ function registerRemoteTest(run, deps) {
45535
+ const git3 = deps.git ?? defaultGit2;
45536
+ const write = deps.write ?? printLine;
45537
+ run.command("test [files...]").description("run a focused test on the owned Hetzner heavy lanes (`.github/workflows/focused-test.yml`) instead of the local heavy slot; waits for the verdict and prints the test job log tail (exit 0 pass, 1 fail, 3 uncertain)").option("--repo <owner/repo>", "repository to test (default: this checkout `origin`)").option("--sha <sha>", "commit to test (default: HEAD); it must already be pushed to origin").addOption(new Option("--tool <tool>", "node-test | vitest").default("node-test").choices(["node-test", "vitest"])).option("--no-install", "skip `npm ci` in the checked-out repository").option("--json", "machine-readable receipt").action(async (files, options) => {
45538
+ try {
45539
+ const repo = options.repo ?? await originRepo(git3);
45540
+ const sha = await pushedSha(options.sha, git3);
45541
+ const tool = options.tool;
45542
+ const request = { repo, sha, tool, files, install: options.install };
45543
+ const receipt = await runRemoteTest(request, {
45544
+ dispatch: deps.dispatch,
45545
+ read: deps.read,
45546
+ now: deps.now ?? Date.now,
45547
+ sleep: deps.sleep ?? ((ms) => new Promise((resolve8) => setTimeout(resolve8, ms))),
45548
+ nonce: deps.nonce ?? (() => randomNonce())
45549
+ });
45550
+ if (options.json) write(JSON.stringify(receipt));
45551
+ else {
45552
+ write(receipt.message);
45553
+ for (const line of receipt.tail) write(line);
45554
+ }
45555
+ process.exitCode = receipt.exitCode;
45556
+ } catch (error) {
45557
+ return fail(error.message);
45558
+ }
45559
+ });
45560
+ }
45561
+ function randomNonce() {
45562
+ return (0, import_node_crypto13.randomBytes)(8).toString("hex");
45563
+ }
45564
+
45565
+ // src/devops-vars.ts
45566
+ var import_node_crypto14 = require("node:crypto");
45178
45567
  init_github_client();
45179
45568
  init_cli_shared();
45180
45569
  var VARS_RECEIPT_SCHEMA = "mmi-cli.devops-vars/1";
@@ -45265,7 +45654,7 @@ function receiptBase(deps, operation, repo, ctx, now) {
45265
45654
  repo,
45266
45655
  window: { startedAt: ctx.startedAt.toISOString(), endedAt: now().toISOString(), atomicSnapshot: false },
45267
45656
  actor: deps.actor ?? { credential: "app_installation" },
45268
- correlationId: (deps.correlationId ?? import_node_crypto13.randomUUID)(),
45657
+ correlationId: (deps.correlationId ?? import_node_crypto14.randomUUID)(),
45269
45658
  upstream: ctx.log
45270
45659
  };
45271
45660
  }
@@ -45676,7 +46065,7 @@ function printMutationText(receipt) {
45676
46065
  }
45677
46066
 
45678
46067
  // src/repo-archive-status.ts
45679
- var import_node_crypto14 = require("node:crypto");
46068
+ var import_node_crypto15 = require("node:crypto");
45680
46069
  init_github_client();
45681
46070
  init_cli_shared();
45682
46071
  var ARCHIVE_STATUS_RECEIPT_SCHEMA = "mmi-cli.repo-archive-status/1";
@@ -45717,7 +46106,7 @@ async function readArchiveStatusReceipt(deps, repo) {
45717
46106
  sourceTime: { observedAt: "", note: ARCHIVE_SOURCE_TIME_NOTE },
45718
46107
  window: { startedAt: startedAt.toISOString(), endedAt: "", atomicSnapshot: false },
45719
46108
  actor: deps.actor ?? { credential: "app_installation" },
45720
- correlationId: (deps.correlationId ?? import_node_crypto14.randomUUID)(),
46109
+ correlationId: (deps.correlationId ?? import_node_crypto15.randomUUID)(),
45721
46110
  upstream: log
45722
46111
  };
45723
46112
  try {
@@ -45770,7 +46159,7 @@ function registerRepoArchiveStatusCommand(program3, deps) {
45770
46159
  }
45771
46160
 
45772
46161
  // src/merge-queue-lifecycle.ts
45773
- var import_node_crypto15 = require("node:crypto");
46162
+ var import_node_crypto16 = require("node:crypto");
45774
46163
  var import_node_fs35 = require("node:fs");
45775
46164
  var import_node_path32 = require("node:path");
45776
46165
  init_github_client();
@@ -46270,7 +46659,7 @@ async function readMergeQueueLifecycle(deps, req) {
46270
46659
  observation: { started_at: started.toISOString(), ended_at: now().toISOString() },
46271
46660
  source: LIFECYCLE_SOURCE,
46272
46661
  actor: deps.actor ?? { credential: "app_installation" },
46273
- correlation_id: (deps.correlationId ?? import_node_crypto15.randomUUID)(),
46662
+ correlation_id: (deps.correlationId ?? import_node_crypto16.randomUUID)(),
46274
46663
  bounds: { max_pages: req.maxPages, max_events: req.maxEvents, deadline_seconds: req.deadlineSeconds },
46275
46664
  counts: { pages, events: eventCount, duplicates_dropped: states.reduce((a, s) => a + s.duplicates_dropped, 0) },
46276
46665
  complete,
@@ -46296,7 +46685,7 @@ async function readMergeQueueLifecycle(deps, req) {
46296
46685
  checkpoint: { prs: states }
46297
46686
  };
46298
46687
  }
46299
- var sha256 = (text) => (0, import_node_crypto15.createHash)("sha256").update(text).digest("hex");
46688
+ var sha256 = (text) => (0, import_node_crypto16.createHash)("sha256").update(text).digest("hex");
46300
46689
  function writeLifecycleReceipt(path2, receipt) {
46301
46690
  const text = `${JSON.stringify(receipt, null, 2)}
46302
46691
  `;
@@ -47375,7 +47764,7 @@ var import_node_os20 = require("node:os");
47375
47764
  var import_node_path36 = require("node:path");
47376
47765
 
47377
47766
  // src/box-control.ts
47378
- var import_node_crypto16 = require("node:crypto");
47767
+ var import_node_crypto17 = require("node:crypto");
47379
47768
  var MANIFEST_PATH = "infra/box-control/manifest.json";
47380
47769
  var ALLOWED_POST = /* @__PURE__ */ new Set(["systemctl daemon-reload"]);
47381
47770
  var sshdPost = (dest) => `if sshd -t; then systemctl reload ssh; else rm -f ${dest}; echo 'sshd -t rejected ${dest}: removed, not reloaded' >&2; exit 1; fi`;
@@ -47479,7 +47868,7 @@ function laneCoverageAction(files, box, op) {
47479
47868
  return { notice: `LANE_COVERAGE: ${mismatch}` };
47480
47869
  }
47481
47870
  function md5(content) {
47482
- return (0, import_node_crypto16.createHash)("md5").update(content).digest("hex");
47871
+ return (0, import_node_crypto17.createHash)("md5").update(content).digest("hex");
47483
47872
  }
47484
47873
  function planRemoteScript(dests) {
47485
47874
  const lines2 = ["set -u"];
@@ -49433,6 +49822,7 @@ ${formatSshRecipe(box, remoteCommand)}
49433
49822
  var import_node_fs38 = require("node:fs");
49434
49823
  var import_node_url = require("node:url");
49435
49824
  var import_node_path35 = require("node:path");
49825
+ var import_node_net = require("node:net");
49436
49826
  var import_meta = {};
49437
49827
  var HZ = "https://api.hetzner.cloud/v1";
49438
49828
  var APPLY = process.env.FIREWALL_APPLY === "1";
@@ -49468,7 +49858,23 @@ function buildRules(cloudflare, extras = []) {
49468
49858
  if (!["tcp", "udp"].includes(e.protocol) || !/^\d{1,5}(-\d{1,5})?$/.test(String(e.port)) || !e.description) {
49469
49859
  throw new Error(`invalid firewall extra ${JSON.stringify(e)}: needs protocol tcp|udp, port N or N-M, description`);
49470
49860
  }
49471
- rules.push({ direction: "in", protocol: e.protocol, port: String(e.port), source_ips: ANY, description: e.description.slice(0, 255) });
49861
+ if (e.source_ips !== void 0) {
49862
+ const cidrOk = (entry) => {
49863
+ if (typeof entry !== "string" || !entry.includes("/")) return false;
49864
+ const parts = entry.split("/");
49865
+ if (parts.length !== 2) return false;
49866
+ const [ip, prefixRaw] = parts;
49867
+ if (!/^(0|[1-9]\d{0,2})$/.test(prefixRaw)) return false;
49868
+ const prefix = Number(prefixRaw);
49869
+ if ((0, import_node_net.isIPv4)(ip)) return prefix >= 1 && prefix <= 32;
49870
+ if ((0, import_node_net.isIPv6)(ip)) return prefix >= 1 && prefix <= 128;
49871
+ return false;
49872
+ };
49873
+ if (!Array.isArray(e.source_ips) || !e.source_ips.length || !e.source_ips.every(cidrOk)) {
49874
+ throw new Error(`invalid firewall extra ${JSON.stringify(e)}: source_ips, when declared, is a non-empty list of specific CIDRs (IPv4 a.b.c.d/1-32 or IPv6 x::/1-128; prefix 0 is refused \u2014 omit source_ips if the extra is meant to be world-open)`);
49875
+ }
49876
+ }
49877
+ rules.push({ direction: "in", protocol: e.protocol, port: String(e.port), source_ips: e.source_ips ?? ANY, description: e.description.slice(0, 255) });
49472
49878
  }
49473
49879
  return rules;
49474
49880
  }
@@ -49478,7 +49884,17 @@ async function cloudflareRanges() {
49478
49884
  if (!res.ok || !body?.success) throw new Error(`Cloudflare IP list unreadable: ${res.status}`);
49479
49885
  return { ipv4: body.result.ipv4_cidrs ?? [], ipv6: body.result.ipv6_cidrs ?? [] };
49480
49886
  }
49481
- var norm = (rules) => JSON.stringify(rules.map((r) => ({ ...r, source_ips: [...r.source_ips ?? []].sort() })).map(({ direction, protocol, port, source_ips }) => ({ direction, protocol, port: port ?? null, source_ips })));
49887
+ function exactRules(rules) {
49888
+ const sortKeys = (v) => Array.isArray(v) ? v.map(sortKeys) : v && typeof v === "object" ? Object.fromEntries(Object.keys(v).sort().map((k) => [k, sortKeys(v[k])])) : v;
49889
+ const canon = (r) => {
49890
+ const { destination_ips, port, ...rest } = r;
49891
+ const out = { ...rest, source_ips: [...r.source_ips ?? []].sort() };
49892
+ if (port !== null && port !== void 0) out.port = port;
49893
+ if (Array.isArray(destination_ips) && destination_ips.length > 0) out.destination_ips = [...destination_ips].sort();
49894
+ return JSON.stringify(sortKeys(out));
49895
+ };
49896
+ return JSON.stringify((rules ?? []).map(canon).sort());
49897
+ }
49482
49898
  function declaredExtras(box) {
49483
49899
  const extrasFile = JSON.parse((0, import_node_fs38.readFileSync)((0, import_node_url.fileURLToPath)(new URL("./firewall-extras.json", import_meta.url)), "utf8"));
49484
49900
  return Object.prototype.hasOwnProperty.call(extrasFile, box) ? extrasFile[box] : void 0;
@@ -49514,7 +49930,7 @@ async function main() {
49514
49930
  if (extras === void 0) throw new Error(`${box} has no entry in firewall-extras.json; declare it (an empty list is fine) before applying`);
49515
49931
  const rules = buildRules(await cloudflareRanges(), extras);
49516
49932
  for (const r of rules) console.log(` in ${r.protocol}${r.port ? `/${r.port}` : ""} from ${r.source_ips.length > 2 ? `${r.source_ips.length} Cloudflare ranges` : r.source_ips.join(",")} \u2014 ${r.description}`);
49517
- const rulesChanged = !existing || norm(existing.rules) !== norm(rules);
49933
+ const rulesChanged = !existing || exactRules(existing.rules) !== exactRules(rules);
49518
49934
  console.log(`cloud-firewall: rules ${rulesChanged ? "would change" : "unchanged"}; apply-to-server ${applied ? "already done" : "needed"}`);
49519
49935
  if (!APPLY) {
49520
49936
  console.log("cloud-firewall: dry-run (FIREWALL_APPLY=1 to write)");
@@ -49542,7 +49958,7 @@ if (process.argv[1] && (0, import_node_path35.basename)(process.argv[1]).toLower
49542
49958
 
49543
49959
  // ../infra/hetzner/firewall-extras.json
49544
49960
  var firewall_extras_default = {
49545
- $comment: "#7356: per-box inbound exceptions to the Cloudflare-only Hetzner Cloud Firewall (cloud-firewall.mjs). Every entry names its owner and reason. Web traffic (80/443) is always Cloudflare-only and SSH (22) is always open; list only what must reach the box directly.",
49961
+ $comment: "#7356: per-box inbound exceptions to the Cloudflare-only Hetzner Cloud Firewall (cloud-firewall.mjs). Every entry names its owner and reason; an optional source_ips list (CIDRs) restricts the exception to those sources, absent = world-open. Web traffic (80/443) is always Cloudflare-only and SSH (22) is always open; list only what must reach the box directly.",
49546
49962
  "mm-work": [
49547
49963
  { protocol: "tcp", port: "7881", description: "MM-Chat dev huddles LiveKit RTC over TCP (MMI-Hub#7353, owner-approved 2026-09-26)" },
49548
49964
  { protocol: "udp", port: "7882", description: "MM-Chat dev huddles LiveKit RTC over UDP (MMI-Hub#7353, owner-approved 2026-09-26)" },
@@ -49552,7 +49968,9 @@ var firewall_extras_default = {
49552
49968
  "mm-central": [],
49553
49969
  "mm-recorder": [],
49554
49970
  "mmi-runner": [],
49555
- "mmi-runner2": []
49971
+ "mmi-runner2": [
49972
+ { protocol: "tcp", port: "8888", source_ips: ["92.44.216.204/32"], description: "Scout's SearXNG gate: the nginx gate on 8888 (Authorization bearer header required, 403 without) in front of the persistent /opt/searxng compose (owner GO for SearXNG-on-Hetzner #2008, 6 Nov 2026). Source is the Scout host's consumer IP - dynamic; if it changes, update the CIDR here or fall back to the ssh tunnel; never open the port to the world" }
49973
+ ]
49556
49974
  };
49557
49975
 
49558
49976
  // ../shared/runner-hosts.ts
@@ -49606,17 +50024,72 @@ async function runBoxFirewall(boxName, mode, live, deps) {
49606
50024
  observedAt
49607
50025
  };
49608
50026
  if (mode === "detach") {
49609
- if (!existing || !applied) {
49610
- return { ...target, ok: true, note: `firewall ${firewallName} is ${existing ? "not applied to" : "absent for"} this server \u2014 nothing to detach` };
50027
+ const saved = deps.loadSnapshot(server.id);
50028
+ if (!saved) {
50029
+ if (!existing || !applied) {
50030
+ return { ...target, ok: true, note: `firewall ${firewallName} is ${existing ? "not applied to" : "absent for"} this server \u2014 nothing to restore` };
50031
+ }
50032
+ throw new FirewallRefusal(`box firewall: refusing to detach ${firewallName} from ${runner}: no prior-policy snapshot is saved for this server, and detaching would leave the edge box with NO firewall. Run one --apply first (it saves the rollback snapshot), or if removing the firewall entirely is really the intent, do it deliberately in the Hetzner console \u2014 never through this rollback route.`);
50033
+ }
50034
+ const savedRules = saved.rules ?? [];
50035
+ target.ruleDiff = rulesDiff(existing?.rules ?? [], savedRules);
50036
+ if (!saved.attached) {
50037
+ if (!existing || !applied) {
50038
+ const fresh = await listAllFirewalls(deps, token);
50039
+ const stillAttached = fresh.some((f) => appliedToServer(f, server.id));
50040
+ target.readback = { observedAt: deps.now(), attached: stillAttached, rulesMatch: null };
50041
+ target.ok = !stillAttached;
50042
+ if (target.ok) target.note = `restored the saved prior policy (saved ${saved.savedAt}): no firewall attached`;
50043
+ if (target.ok) deps.saveSnapshot(server.id, { savedAt: deps.now(), firewall: existing ? { id: existing.id, name: existing.name } : null, rules: existing?.rules ?? [], attached: false });
50044
+ return target;
50045
+ }
50046
+ const action = await actionPost(deps, token, existing.id, "remove_from_resources", { remove_from: [{ type: "server", server: { id: server.id } }] });
50047
+ const waited = await waitForAction(deps, token, action);
50048
+ target.actions.push(actionReceipt("remove_from_resources", waited));
50049
+ target.ambiguous ||= waited.ambiguous;
50050
+ const readback3 = await readbackState(deps, token, existing.id, server.id, null, deps.now());
50051
+ target.readback = readback3;
50052
+ target.ok = !target.ambiguous && readback3.attached === false;
50053
+ if (target.ok) target.note = `restored the saved prior policy (saved ${saved.savedAt}): no firewall was attached`;
50054
+ if (target.ok) deps.saveSnapshot(server.id, { savedAt: deps.now(), firewall: { id: existing.id, name: existing.name }, rules: existing.rules ?? [], attached: false });
50055
+ return target;
49611
50056
  }
49612
- const action = await actionPost(deps, token, existing.id, "remove_from_resources", { remove_from: [{ type: "server", server: { id: server.id } }] });
49613
- const waited = await waitForAction(deps, token, action);
49614
- target.actions.push(actionReceipt("remove_from_resources", waited));
49615
- target.ambiguous = waited.ambiguous;
49616
- const readback2 = await readbackState(deps, token, existing.id, server.id, null, deps.now());
50057
+ let firewallId2 = existing?.id;
50058
+ if (!existing) {
50059
+ const created = await deps.hetzner(token, "/firewalls", {
50060
+ method: "POST",
50061
+ body: JSON.stringify({ name: firewallName, labels: { managed_by: "mmi-hub", box: runner }, rules: savedRules })
50062
+ });
50063
+ firewallId2 = created.firewall.id;
50064
+ target.actions.push({ id: firewallId2, kind: "create", status: "created" });
50065
+ } else if (exactRules(existing.rules ?? []) !== exactRules(savedRules)) {
50066
+ const action = await actionPost(deps, token, firewallId2, "set_rules", { rules: savedRules });
50067
+ const waited = await waitForAction(deps, token, action);
50068
+ target.actions.push(actionReceipt("set_rules", waited));
50069
+ target.ambiguous ||= waited.ambiguous;
50070
+ if (waited.ambiguous || waited.action.status === "error") {
50071
+ target.readback = await readbackState(deps, token, firewallId2, server.id, savedRules, deps.now());
50072
+ return target;
50073
+ }
50074
+ }
50075
+ if (!applied) {
50076
+ const action = await actionPost(deps, token, firewallId2, "apply_to_resources", { apply_to: [{ type: "server", server: { id: server.id } }] });
50077
+ const waited = await waitForAction(deps, token, action);
50078
+ target.actions.push(actionReceipt("apply_to_resources", waited));
50079
+ target.ambiguous ||= waited.ambiguous;
50080
+ if (waited.ambiguous || waited.action.status === "error") {
50081
+ target.readback = await readbackState(deps, token, firewallId2, server.id, savedRules, deps.now());
50082
+ return target;
50083
+ }
50084
+ }
50085
+ const readback2 = await readbackState(deps, token, firewallId2, server.id, savedRules, deps.now());
49617
50086
  target.readback = readback2;
49618
- target.ok = !waited.ambiguous && readback2.attached === false;
49619
- if (target.ok) target.note = `detached ${firewallName} from ${runner}`;
50087
+ target.firewall = { id: firewallId2, name: firewallName };
50088
+ target.ok = !target.ambiguous && readback2.attached && readback2.rulesMatch === true;
50089
+ if (target.ok) {
50090
+ target.note = `restored the saved prior policy (saved ${saved.savedAt}), verified by independent readback`;
50091
+ deps.saveSnapshot(server.id, { savedAt: deps.now(), firewall: { id: firewallId2, name: firewallName }, rules: savedRules, attached: true });
50092
+ }
49620
50093
  return target;
49621
50094
  }
49622
50095
  const extras = deps.declaredExtras(runner);
@@ -49625,13 +50098,18 @@ async function runBoxFirewall(boxName, mode, live, deps) {
49625
50098
  }
49626
50099
  const planned = buildRules(await deps.cloudflareRanges(), extras);
49627
50100
  target.plannedRules = planned;
49628
- const rulesChanged = !existing || norm(existing.rules ?? []) !== norm(planned);
50101
+ const rulesChanged = !existing || exactRules(existing.rules ?? []) !== exactRules(planned);
49629
50102
  target.rulesChanged = rulesChanged;
50103
+ target.ruleDiff = rulesDiff(existing?.rules ?? [], planned);
49630
50104
  if (mode === "plan") {
49631
50105
  target.ok = !rulesChanged && applied;
49632
50106
  if (target.ok) target.note = `firewall ${firewallName}#${existing.id} matches the reviewed policy and is applied`;
49633
50107
  return target;
49634
50108
  }
50109
+ if (!deps.loadSnapshot(server.id)) {
50110
+ deps.saveSnapshot(server.id, { savedAt: deps.now(), firewall: existing ? { id: existing.id, name: existing.name } : null, rules: existing?.rules ?? [], attached: applied });
50111
+ target.note = "saved the live policy as the rollback snapshot";
50112
+ }
49635
50113
  let firewallId = existing?.id;
49636
50114
  if (!existing) {
49637
50115
  const created = await deps.hetzner(token, "/firewalls", {
@@ -49666,9 +50144,21 @@ async function runBoxFirewall(boxName, mode, live, deps) {
49666
50144
  target.firewall = { id: firewallId, name: firewallName };
49667
50145
  target.readback = readback;
49668
50146
  target.ok = !target.ambiguous && readback.attached && readback.rulesMatch === true;
49669
- if (target.ok) target.note = `firewall ${firewallName}#${firewallId} applied to ${runner} and read back independently`;
50147
+ if (target.ok) target.note = `firewall ${firewallName}#${firewallId} applied to ${runner} and read back independently${target.note ? `; ${target.note}` : ""}`;
49670
50148
  return target;
49671
50149
  }
50150
+ async function listAllFirewalls(deps, token) {
50151
+ const out = [];
50152
+ for (let page = 1; page <= 50; page += 1) {
50153
+ const res = await deps.hetzner(token, `/firewalls?page=${page}`);
50154
+ out.push(...res.firewalls ?? []);
50155
+ if (!res.meta?.pagination?.next_page) return out;
50156
+ }
50157
+ throw new FirewallRefusal("box firewall: the project has more firewalls than the listing bound \u2014 cannot prove that no firewall is applied to this server");
50158
+ }
50159
+ function appliedToServer(f, serverId) {
50160
+ return Boolean(f.applied_to?.some((a) => a.server?.id === serverId || a.applied_to_resources?.some((r) => r.server?.id === serverId)));
50161
+ }
49672
50162
  async function actionPost(deps, token, firewallId, kind, body) {
49673
50163
  const res = await deps.hetzner(token, `/firewalls/${firewallId}/actions/${kind}`, { method: "POST", body: JSON.stringify(body) });
49674
50164
  return res.action;
@@ -49694,10 +50184,20 @@ function actionReceipt(kind, waited) {
49694
50184
  async function readbackState(deps, token, firewallId, serverId, planned, at) {
49695
50185
  const firewall = (await deps.hetzner(token, `/firewalls/${firewallId}`)).firewall;
49696
50186
  const server = (await deps.hetzner(token, `/servers/${serverId}`)).server;
49697
- const attached = server != null && server.id === serverId && Boolean(firewall?.applied_to?.some((a) => a.server?.id === serverId));
49698
- const rulesMatch = planned ? norm(firewall?.rules ?? []) === norm(planned) : null;
50187
+ const attached = server != null && server.id === serverId && appliedToServer(firewall, serverId);
50188
+ const rulesMatch = planned ? exactRules(firewall?.rules ?? []) === exactRules(planned) : null;
49699
50189
  return { observedAt: at, attached, rulesMatch };
49700
50190
  }
50191
+ function rulesDiff(current, target) {
50192
+ const key = (r) => exactRules([r]);
50193
+ const cur = Array.isArray(current) ? current : [];
50194
+ const targetKeys = new Set(target.map(key));
50195
+ const currentKeys = new Set(cur.map(key));
50196
+ return {
50197
+ added: target.filter((r) => !currentKeys.has(key(r))),
50198
+ removed: cur.filter((r) => !targetKeys.has(key(r)))
50199
+ };
50200
+ }
49701
50201
 
49702
50202
  // src/box-commands.ts
49703
50203
  var HETZNER_API = "https://api.hetzner.cloud/v1/servers";
@@ -49920,7 +50420,7 @@ ${SSH_RECIPE_AGENT_NOTE}`);
49920
50420
  process.exitCode = 2;
49921
50421
  }
49922
50422
  });
49923
- box.command("firewall <box>").description("the registered runner boxes' Hetzner Cloud Firewall (#9786): --plan (default, read-only) reports the exact current rules and attachment against the reviewed policy; --apply writes it; --detach --apply is the rollback").option("--plan", "read-only: current firewall, attachment and exact rules, and the diff against the reviewed policy (the default)").option("--apply", "create/update the approved firewall (policy #7359, SSH stays world-open) and attach it to exactly this server; waits for each provider action, then re-reads the firewall and attachment independently").option("--detach", "with --apply: detach this exact firewall from this exact server (rollback; a route independent of SSH)").option("--json", "machine-readable receipt").action(async (name, o) => {
50423
+ box.command("firewall <box>").description("the registered runner boxes' Hetzner Cloud Firewall (#9786): --plan (default, read-only) reports the exact current rules and attachment against the reviewed policy; --apply writes it; --detach --apply is the rollback").option("--plan", "read-only: current firewall, attachment and exact rules, and the diff against the reviewed policy (the default)").option("--apply", "create/update the approved firewall (policy #7359, SSH stays world-open) and attach it to exactly this server; waits for each provider action, then re-reads the firewall and attachment independently").option("--detach", "with --apply: restore this server's saved prior policy (the snapshot persisted before the first managed apply) \u2014 never a blind detach; refuses when no snapshot exists, because detaching would leave the edge box with no firewall").option("--json", "machine-readable receipt").action(async (name, o) => {
49924
50424
  try {
49925
50425
  if (o.apply && o.plan) throw new UsageError("box firewall: --plan and --apply are exclusive");
49926
50426
  if (o.detach && !o.apply) throw new UsageError("box firewall: --detach is a mutation \u2014 it needs --apply");
@@ -49969,6 +50469,12 @@ function renderFirewallReceipt(r, json) {
49969
50469
  lines2.push(` in ${rule.protocol}${rule.port ? `/${rule.port}` : ""} from ${from} \u2014 ${rule.description}`);
49970
50470
  }
49971
50471
  }
50472
+ if (r.ruleDiff && (r.ruleDiff.added.length > 0 || r.ruleDiff.removed.length > 0)) {
50473
+ lines2.push(` diff vs the provider's current set: ${r.ruleDiff.added.length} added, ${r.ruleDiff.removed.length} removed`);
50474
+ const render = (r2) => `in ${r2.protocol}${r2.port ? `/${r2.port}` : ""} from ${(r2.source_ips ?? []).join(", ")} \u2014 ${r2.description ?? ""}`;
50475
+ for (const rule of r.ruleDiff.added) lines2.push(` + ${render(rule)}`);
50476
+ for (const rule of r.ruleDiff.removed) lines2.push(` - ${render(rule)}`);
50477
+ }
49972
50478
  for (const a of r.actions) lines2.push(` action ${a.kind} #${a.id}: ${a.status}${a.error ? ` \u2014 ${a.error.code ?? ""} ${a.error.message ?? ""}` : ""}`);
49973
50479
  if (r.readback) {
49974
50480
  lines2.push(` readback (independent, ${r.readback.observedAt}): attached=${r.readback.attached}${r.readback.rulesMatch === null ? "" : ` rulesMatch=${r.readback.rulesMatch}`}`);
@@ -49978,10 +50484,25 @@ function renderFirewallReceipt(r, json) {
49978
50484
  console.log(lines2.join("\n"));
49979
50485
  }
49980
50486
  function firewallDeps() {
50487
+ const snapshotDir = (0, import_node_path36.join)((0, import_node_os20.homedir)(), ".mmi-cli", "firewall-rollback");
50488
+ const snapshotPath = (serverId) => (0, import_node_path36.join)(snapshotDir, `${serverId}.json`);
49981
50489
  return {
49982
50490
  hetzner: (token, path2, init) => hetzner(token, path2, init),
49983
50491
  cloudflareRanges: () => cloudflareRanges(),
49984
50492
  declaredExtras: (boxName) => declaredExtrasFromBundle(boxName),
50493
+ loadSnapshot: (serverId) => {
50494
+ const file = snapshotPath(serverId);
50495
+ if (!(0, import_node_fs39.existsSync)(file)) return null;
50496
+ const parsed = JSON.parse((0, import_node_fs39.readFileSync)(file, "utf8"));
50497
+ if (typeof parsed.savedAt !== "string" || !Array.isArray(parsed.rules) || typeof parsed.attached !== "boolean") {
50498
+ throw new Error(`box firewall: the rollback snapshot at ${file} is not a valid snapshot \u2014 inspect it before any rollback`);
50499
+ }
50500
+ return parsed;
50501
+ },
50502
+ saveSnapshot: (serverId, snapshot) => {
50503
+ (0, import_node_fs39.mkdirSync)(snapshotDir, { recursive: true });
50504
+ (0, import_node_fs39.writeFileSync)(snapshotPath(serverId), JSON.stringify(snapshot, null, 2));
50505
+ },
49985
50506
  fetchToken: async () => {
49986
50507
  let token = null;
49987
50508
  await withSecrets(async (secretsDeps) => {
@@ -50147,7 +50668,7 @@ function renderDeployPortDoctor(report) {
50147
50668
  }
50148
50669
 
50149
50670
  // src/bootstrap-drift.ts
50150
- var import_node_crypto17 = require("node:crypto");
50671
+ var import_node_crypto18 = require("node:crypto");
50151
50672
  function byteComparableSeeds(manifest, cls) {
50152
50673
  return manifest.seeds.filter((s) => s.ownership === "org" && s.source === "self" && s.classes.includes(cls));
50153
50674
  }
@@ -50167,7 +50688,7 @@ function compareSeedBytes(hubContent, repoContent) {
50167
50688
  return normalize(hubContent) === normalize(repoContent) ? "match" : "drift";
50168
50689
  }
50169
50690
  function seedContentHash(content) {
50170
- return (0, import_node_crypto17.createHash)("sha256").update(content.replace(/\r\n/g, "\n"), "utf8").digest("hex");
50691
+ return (0, import_node_crypto18.createHash)("sha256").update(content.replace(/\r\n/g, "\n"), "utf8").digest("hex");
50171
50692
  }
50172
50693
  function auditRepoSeedDrift(repo, seeds, hubContents, repoReads) {
50173
50694
  const byTarget = new Map(repoReads.map((r) => [r.target, r.content]));
@@ -51417,7 +51938,7 @@ function renderBootstrapVerifyReport(report) {
51417
51938
  }
51418
51939
 
51419
51940
  // src/bootstrap-seed-delivery.ts
51420
- var import_node_crypto18 = require("node:crypto");
51941
+ var import_node_crypto19 = require("node:crypto");
51421
51942
  init_github_client();
51422
51943
  var SHA = /^[a-f0-9]{40}$/;
51423
51944
  async function readSeedFile(repo, target, ref, client) {
@@ -51450,7 +51971,7 @@ async function targetedSeedPlan(repo, slug, target, baseBranch, sourceSha, clien
51450
51971
  }
51451
51972
  const baseSha = base?.object?.sha;
51452
51973
  if (!baseSha || !SHA.test(baseSha) || !SHA.test(sourceSha)) throw new Error("bootstrap apply: seed identity is unreadable");
51453
- const identity = (0, import_node_crypto18.createHash)("sha256").update(JSON.stringify([repo.toLowerCase(), target, baseSha, sourceSha])).digest("hex").slice(0, 24);
51974
+ const identity = (0, import_node_crypto19.createHash)("sha256").update(JSON.stringify([repo.toLowerCase(), target, baseSha, sourceSha])).digest("hex").slice(0, 24);
51454
51975
  const delivery = planSeedDelivery(rules, slug, baseBranch, `bootstrap-seed-${identity}`);
51455
51976
  const scoped = forcePr && delivery.mode === "direct" ? { mode: "pr", ref: `bootstrap-seed-${identity}-${slug}`, branch: `bootstrap-seed-${identity}-${slug}`, reason: "registry gate render requires review" } : delivery;
51456
51977
  return { ...scoped, baseSha, baseBranch };
@@ -51824,7 +52345,7 @@ function registerBootstrapCommands(program3) {
51824
52345
  const cls = classOf(repo);
51825
52346
  const meta = metaOf(repo);
51826
52347
  const track = resolveReleaseTrack(metaWithDeclaredClass(meta, cls), void 0, repo);
51827
- const seeds = byteComparableSeeds(manifest, cls).filter((s) => seedMatchesReleaseTrack(s, track));
52348
+ const seeds = byteComparableSeeds(manifest, cls).filter((s) => seedMatchesReleaseTrack(s, track) && seedMatchesRepo(s, repo));
51828
52349
  const trackHubContents = new Map([...hubContents].map(([t, c]) => [t, c == null ? c : renderSelfSeedForTrack(c, track)]));
51829
52350
  seedsPerRepo = Math.max(seedsPerRepo, seeds.length);
51830
52351
  const baseBranch = cls === "content" ? "main" : "development";
@@ -52267,6 +52788,7 @@ function registerBootstrapCommands(program3) {
52267
52788
  if (!seedMatchesDeployModel(seed, applyDeployModel)) continue;
52268
52789
  if (!seedMatchesProjectType(seed, applyProjectType)) continue;
52269
52790
  if (!seedMatchesReleaseTrack(seed, vars.RELEASE_TRACK)) continue;
52791
+ if (!seedMatchesRepo(seed, repo)) continue;
52270
52792
  const waiver = seed.waivers?.[slug];
52271
52793
  if (waiver) {
52272
52794
  actions.push({ action: "skip", target: seed.target, ownership: seed.ownership, reason: `waived: ${waiver}` });
@@ -52766,7 +53288,7 @@ LIVE apply to ${repo}:
52766
53288
  void 0,
52767
53289
  repo
52768
53290
  );
52769
- const repos = inClassRepos.filter((repo) => seedMatchesReleaseTrack(seed, trackOf(repo))).map((repo) => {
53291
+ const repos = inClassRepos.filter((repo) => seedMatchesReleaseTrack(seed, trackOf(repo)) && seedMatchesRepo(seed, repo)).map((repo) => {
52770
53292
  const slug = repo.split("/").pop().toLowerCase();
52771
53293
  const waiver = seed.waivers?.[slug];
52772
53294
  return { repo, slug, waiver };
@@ -53271,7 +53793,7 @@ function registerDeployCommands(program3) {
53271
53793
  }
53272
53794
 
53273
53795
  // src/intercom-guest.ts
53274
- var import_node_crypto19 = require("node:crypto");
53796
+ var import_node_crypto20 = require("node:crypto");
53275
53797
  var import_promises5 = require("node:fs/promises");
53276
53798
  var import_node_os22 = require("node:os");
53277
53799
  var import_node_path38 = require("node:path");
@@ -53522,7 +54044,7 @@ async function runIntercomGuest(request, deps = {}) {
53522
54044
  };
53523
54045
  }
53524
54046
  const fetchImpl = deps.fetchImpl ?? ((url, init) => fetch(url, init));
53525
- const newKey = deps.newIdempotencyKey ?? (() => (0, import_node_crypto19.randomUUID)());
54047
+ const newKey = deps.newIdempotencyKey ?? (() => (0, import_node_crypto20.randomUUID)());
53526
54048
  const secrets = [token, credential].filter((secret) => Boolean(secret));
53527
54049
  const callOne = async (op, body2) => {
53528
54050
  const headers = {
@@ -53648,7 +54170,7 @@ init_github_client();
53648
54170
  init_cli_shared();
53649
54171
 
53650
54172
  // src/suite-status.ts
53651
- var import_node_crypto20 = require("node:crypto");
54173
+ var import_node_crypto21 = require("node:crypto");
53652
54174
  var SUITE_REPO = "mutmutco/MM-Work";
53653
54175
  var REPOS = {
53654
54176
  "mmi-scout": "mutmutco/MMI-Scout",
@@ -53676,7 +54198,7 @@ function parseSuiteMapping(body) {
53676
54198
  return void 0;
53677
54199
  }
53678
54200
  }
53679
- var sha2562 = (b) => (0, import_node_crypto20.createHash)("sha256").update(b).digest("hex");
54201
+ var sha2562 = (b) => (0, import_node_crypto21.createHash)("sha256").update(b).digest("hex");
53680
54202
  async function proveBundle(url, tag, fetchUrl) {
53681
54203
  const get = async (u) => {
53682
54204
  try {
@@ -53935,7 +54457,7 @@ async function waitSuiteDev(deps, opts) {
53935
54457
  // src/suite-train.ts
53936
54458
  var import_node_fs41 = require("node:fs");
53937
54459
  var import_node_path39 = require("node:path");
53938
- var import_node_crypto21 = require("node:crypto");
54460
+ var import_node_crypto22 = require("node:crypto");
53939
54461
 
53940
54462
  // src/kit-version.ts
53941
54463
  var parse = (v) => /^(\d+)\.(\d+)\.(\d+)(-.+)?$/.exec(typeof v === "string" ? v.trim() : "");
@@ -54037,7 +54559,7 @@ function readSuiteCut(path2) {
54037
54559
  }
54038
54560
  function writeSuiteCut(path2, record2) {
54039
54561
  (0, import_node_fs41.mkdirSync)((0, import_node_path39.dirname)(path2), { recursive: true });
54040
- const temp = `${path2}.${(0, import_node_crypto21.randomBytes)(8).toString("hex")}.tmp`;
54562
+ const temp = `${path2}.${(0, import_node_crypto22.randomBytes)(8).toString("hex")}.tmp`;
54041
54563
  try {
54042
54564
  (0, import_node_fs41.writeFileSync)(temp, JSON.stringify(record2, null, 2), { flag: "wx" });
54043
54565
  (0, import_node_fs41.renameSync)(temp, path2);
@@ -57138,7 +57660,7 @@ async function deleteMergedRemoteBranch(options) {
57138
57660
  init_github_client();
57139
57661
 
57140
57662
  // src/release-custody.ts
57141
- var import_node_crypto22 = require("node:crypto");
57663
+ var import_node_crypto23 = require("node:crypto");
57142
57664
  var import_node_fs43 = require("node:fs");
57143
57665
  var import_node_path41 = require("node:path");
57144
57666
  init_github_client();
@@ -57362,7 +57884,7 @@ var ReleaseCustodyError = class extends Error {
57362
57884
  this.name = "ReleaseCustodyError";
57363
57885
  }
57364
57886
  };
57365
- var custodyDigest = (value) => (0, import_node_crypto22.createHash)("sha256").update(value).digest("hex");
57887
+ var custodyDigest = (value) => (0, import_node_crypto23.createHash)("sha256").update(value).digest("hex");
57366
57888
  function parseCustody(raw, repo) {
57367
57889
  const s = JSON.parse(raw);
57368
57890
  if (s.schema !== "mmi-release-custody/v1" || s.repo !== repo || !s.precutId || !Number.isSafeInteger(s.revision) || s.revision < 1 || !s.controller || !Number.isSafeInteger(s.controller.runId) || s.controller.runId <= 0 || !Number.isSafeInteger(s.controller.runAttempt) || s.controller.runAttempt <= 0 || !s.controller.token || !(s.ledger === null || typeof s.ledger === "string") || !s.archives || !s.effects) {
@@ -57463,7 +57985,7 @@ var ReleaseCustodySession = class _ReleaseCustodySession {
57463
57985
  }
57464
57986
  }
57465
57987
  let state = {
57466
- ...found.state ?? { schema: "mmi-release-custody/v1", repo, precutId: (0, import_node_crypto22.randomUUID)(), effects: {}, ...await initialize() },
57988
+ ...found.state ?? { schema: "mmi-release-custody/v1", repo, precutId: (0, import_node_crypto23.randomUUID)(), effects: {}, ...await initialize() },
57467
57989
  revision: (found.state?.revision ?? 0) + 1,
57468
57990
  controller
57469
57991
  };
@@ -57524,7 +58046,7 @@ var ReleaseCustodySession = class _ReleaseCustodySession {
57524
58046
  proof: { kind: "ledger-lifecycle", controller: state.controller }
57525
58047
  };
57526
58048
  }
57527
- return { ...state, ledger: raw, archives, precutId: state.ledger && !raw ? (0, import_node_crypto22.randomUUID)() : state.precutId };
58049
+ return { ...state, ledger: raw, archives, precutId: state.ledger && !raw ? (0, import_node_crypto23.randomUUID)() : state.precutId };
57528
58050
  });
57529
58051
  }
57530
58052
  async preserveLocal(raw) {
@@ -57758,7 +58280,7 @@ function withHubReleaseCustody(deps) {
57758
58280
  if (!authority.ok || !authority.train || authority.authenticatedDelegation !== true) {
57759
58281
  throw new ReleaseCustodyError("repository custody requires the server-verified owner-triggered train session");
57760
58282
  }
57761
- const controller = { ...runIdentity, token: (0, import_node_crypto22.randomUUID)(), ...process.env.GITHUB_JOB ? { job: process.env.GITHUB_JOB } : {} };
58283
+ const controller = { ...runIdentity, token: (0, import_node_crypto23.randomUUID)(), ...process.env.GITHUB_JOB ? { job: process.env.GITHUB_JOB } : {} };
57762
58284
  const raw = localRaw(path2);
57763
58285
  const requiredContexts = () => discoverRequiredCheckContexts(
57764
58286
  deps,
@@ -58470,7 +58992,7 @@ init_github_quota();
58470
58992
 
58471
58993
  // src/issue-commands.ts
58472
58994
  var import_node_fs46 = require("node:fs");
58473
- var import_node_crypto23 = require("node:crypto");
58995
+ var import_node_crypto24 = require("node:crypto");
58474
58996
  init_cli_shared();
58475
58997
  init_clean_exit();
58476
58998
  init_error_codes();
@@ -58947,7 +59469,7 @@ function rowIdempotencyKey(batchKey, spec) {
58947
59469
  const identity = `${spec.type}
58948
59470
  ${spec.title.trim()}
58949
59471
  ${spec.body ?? ""}`;
58950
- const hash = (0, import_node_crypto23.createHash)("sha256").update(identity).digest("hex").slice(0, 16);
59472
+ const hash = (0, import_node_crypto24.createHash)("sha256").update(identity).digest("hex").slice(0, 16);
58951
59473
  return `${batchKey}:${hash}`;
58952
59474
  }
58953
59475
  var BATCH_SPEC_KEYS = /* @__PURE__ */ new Set(["type", "title", "body", "priority", "labels", "label", "parent", "repo", "surface"]);
@@ -59731,7 +60253,7 @@ function typedReadHint(path2) {
59731
60253
  }
59732
60254
  if (!item) return say(`mmi-cli devops read pr list --repo ${slug} --json number,title,state,headRefName`);
59733
60255
  if (item.startsWith("comments/")) return say(`mmi-cli devops read pr view <pr> --repo ${slug} --json comments,reviews`);
59734
- return say(`mmi-cli devops read pr view ${item} --repo ${slug} --json title,state,comments,reviews,commits,statusCheckRollup,mergedAt,mergeCommit,headRefOid,mergeable,mergeStateStatus`, "; `mergedAt`, `mergeCommit` and `headRefOid` carry the merge and head evidence, `mergeable` and `mergeStateStatus` carry mergeability, and `--json` is a field list callers may narrow or extend");
60256
+ return say(`mmi-cli devops read pr view ${item} --repo ${slug} --json title,body,state,comments,reviews,commits,statusCheckRollup,mergedAt,mergeCommit,headRefOid,mergeable,mergeStateStatus`, "; `mergedAt`, `mergeCommit` and `headRefOid` carry the merge and head evidence, `mergeable` and `mergeStateStatus` carry mergeability, `--json` is a field list callers may narrow or extend, and `--jq <expr>` filters that JSON locally (#10154)");
59735
60257
  }
59736
60258
  function runsReadHint(path2) {
59737
60259
  const q = path2.indexOf("?");
@@ -59875,7 +60397,8 @@ function registerApiReadCommands(program3, overrides) {
59875
60397
  const shapes = "api get: --out <file> is required for, and only valid with, repos/{owner}/{repo}/actions/artifacts/{id}/zip and repos/{owner}/{repo}/actions/jobs/{id}/logs and repos/{owner}/{repo}/releases/assets/{id}";
59876
60398
  if (!zip && o.out !== void 0) {
59877
60399
  if (req.kind === "contents") return fail(`${shapes} \u2014 for this file's bytes, drop --out and use \`--accept raw\` with a redirect: \`mmi-cli devops api get "${req.path}" --accept raw > <file>\` (decoded bytes on stdout, no base64 or JSON.parse). This refusal exits 1 on stderr with nothing written: check the exit code and never discard stderr (2>&1 / >/dev/null) before parsing.`);
59878
- return fail(`${shapes} \u2014 drop --out and let the JSON body go to stdout (pipe it into node/jq, or use --jq <expr>), or use the typed read (e.g. \`mmi-cli devops read run list --repo <owner/repo> --json databaseId,workflowName,event,status,conclusion\`). This refusal exits 1 on stderr with nothing written: check the exit code and never discard stderr (2>&1 / >/dev/null) before parsing.`);
60400
+ const typed = typedReadHint(req.path);
60401
+ return fail(`${shapes} \u2014 drop --out and let the JSON body go to stdout (pipe it into node/jq, or use --jq <expr>)${typed ? `.${typed}` : ", or use the typed read (e.g. `mmi-cli devops read run list --repo <owner/repo> --json databaseId,workflowName,event,status,conclusion`)."} This refusal exits 1 on stderr with nothing written: check the exit code, never discard stderr (2>&1 / >/dev/null) before parsing, and never chain a command after it (a trailing \`; ls <file>\` replaces exit 1 with its own code and hides this reason).`);
59879
60402
  }
59880
60403
  return fail(`${shapes} \u2014 append \`--out <file>\` to this command (e.g. \`MMI_ACTOR=app mmi-cli devops api get ${req.path} --out .jerv/tmp/job.log\`), or for job logs use the typed read \`MMI_ACTOR=app mmi-cli devops read run log <runId> --repo <owner/repo> --job <name|id>\`. Shell redirection (\`>\` / \`2>&1\`) is not a substitute: this refusal exits 1 on stderr with nothing written to stdout, so \`>\` leaves a zero-byte file \u2014 delete it and check the exit code.`);
59881
60404
  }
@@ -59890,7 +60413,20 @@ function registerApiReadCommands(program3, overrides) {
59890
60413
  o.accept
59891
60414
  );
59892
60415
  if (req.workflowRuns) body = sliceWorkflowRunsPage(body, req.workflowRuns);
59893
- if (o.jq !== void 0) body = await jqRun(o.jq, body, localJqEnv(), ...bindings.length ? [bindings] : []);
60416
+ if (o.jq !== void 0) {
60417
+ const upstream = body;
60418
+ body = await jqRun(o.jq, body, localJqEnv(), ...bindings.length ? [bindings] : []);
60419
+ if (body.trim() === "null") {
60420
+ let keys;
60421
+ try {
60422
+ const parsed = JSON.parse(upstream);
60423
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) keys = Object.keys(parsed);
60424
+ } catch {
60425
+ }
60426
+ if (keys) process.stderr.write(`api get: --jq '${o.jq}' returned null. stdout is the raw upstream JSON with no body/--json wrapper; its top-level keys are: ${keys.join(", ") || "(none)"}.
60427
+ `);
60428
+ }
60429
+ }
59894
60430
  if (zip) {
59895
60431
  (0, import_node_fs48.writeFileSync)(o.out, body, "latin1");
59896
60432
  return;
@@ -60932,7 +61468,7 @@ function parseCheckJobId(details) {
60932
61468
  function failingCheckRows(rows) {
60933
61469
  return rows.filter((row) => row.status === "fail" || row.status === "failure");
60934
61470
  }
60935
- function tailLines(text, max = CHECK_LOG_TAIL_LINES) {
61471
+ function tailLines2(text, max = CHECK_LOG_TAIL_LINES) {
60936
61472
  const lines2 = text.split(/\r?\n/).filter((line) => line.trim());
60937
61473
  return { tail: lines2.slice(-max).join("\n"), truncated: lines2.length > max };
60938
61474
  }
@@ -60941,7 +61477,7 @@ async function attachFailureLogs(rows, fetchLog) {
60941
61477
  const jobId = parseCheckJobId(row.details);
60942
61478
  if (!jobId) continue;
60943
61479
  try {
60944
- const { tail, truncated } = tailLines(await fetchLog(jobId));
61480
+ const { tail, truncated } = tailLines2(await fetchLog(jobId));
60945
61481
  if (tail) row.log = { jobId, tail, truncated };
60946
61482
  } catch {
60947
61483
  }
@@ -62109,7 +62645,7 @@ init_cli_shared();
62109
62645
  var import_node_child_process18 = require("node:child_process");
62110
62646
  var import_node_fs51 = require("node:fs");
62111
62647
  var import_node_path48 = require("node:path");
62112
- var import_node_net = require("node:net");
62648
+ var import_node_net2 = require("node:net");
62113
62649
  var import_node_util5 = require("node:util");
62114
62650
 
62115
62651
  // src/rules-sync.ts
@@ -62512,7 +63048,7 @@ function pickStagePort(range, isFree) {
62512
63048
  }
62513
63049
  function isPortFree(port) {
62514
63050
  return new Promise((resolve8) => {
62515
- const srv = (0, import_node_net.createServer)();
63051
+ const srv = (0, import_node_net2.createServer)();
62516
63052
  srv.once("error", () => resolve8(false));
62517
63053
  srv.once("listening", () => srv.close(() => resolve8(true)));
62518
63054
  srv.listen(port, "127.0.0.1");
@@ -63100,6 +63636,14 @@ function isPathAtOrWithin(path2, worktreePath) {
63100
63636
  function isWindowsCwdLockResidueError(error) {
63101
63637
  return process.platform === "win32" && /\bEPERM\b|access(?: is)? denied/i.test(error);
63102
63638
  }
63639
+ function isNodeModulesLockError(error) {
63640
+ return /^(?:EPERM|EACCES|EBUSY)\b/.test(error);
63641
+ }
63642
+ function deferredNodeModulesLockRemediation(wtPath, branch, prNumber, repo) {
63643
+ const quote = (value) => value.replace(/'/g, "''");
63644
+ const rerun = `mmi-cli devops pr merge ${prNumber ?? "<pr>"}${repo ? ` --repo ${repo}` : ""}`;
63645
+ return `Close the handle on the locked file under '${quote((0, import_node_path49.join)(wtPath, "node_modules"))}', then from the primary checkout re-run cleanup for branch '${quote(branch)}': ${rerun}`;
63646
+ }
63103
63647
  function deferredCwdLockRemediation(wtPath) {
63104
63648
  const quote = (path2) => path2.replace(/'/g, "''");
63105
63649
  return `After leaving the directory and closing its handles, remove it only if empty: [System.IO.Directory]::Delete('${quote(wtPath)}', $false)`;
@@ -63416,7 +63960,7 @@ async function removeWorktreeWithReconcile(wtPath, git3, listWorktrees, pathExis
63416
63960
  }
63417
63961
  function isPrMergeWorktreePartial(cleanup) {
63418
63962
  const worktree = cleanup?.worktree;
63419
- return Boolean(worktree?.path && worktree.status !== "removed" && worktree.status !== "preserved" && worktree.status !== "retained-locked" && worktree.status !== "switched-primary");
63963
+ return Boolean(worktree?.path && worktree.status !== "removed" && worktree.status !== "preserved" && worktree.status !== "retained-locked" && worktree.status !== "switched-primary" && worktree.status !== "deferred");
63420
63964
  }
63421
63965
  function prMergeLocalCleanupExitCode(cleanup) {
63422
63966
  return cleanup?.worktree?.status === "failed" || cleanup?.localBranch?.status === "failed" ? 1 : void 0;
@@ -63575,18 +64119,19 @@ async function cleanupPrMergeLocalBranch(branch, options) {
63575
64119
  if (pathExists((0, import_node_path49.join)(wtPath, "node_modules"))) {
63576
64120
  const nmRemoved = await (options.removeRealNodeModules ?? removeWorktreeNodeModules)(wtPath);
63577
64121
  if (!nmRemoved.ok) {
64122
+ const deferred = isNodeModulesLockError(nmRemoved.error) && pathExists(wtPath);
63578
64123
  report.worktree = {
63579
64124
  path: wtPath,
63580
- status: "failed",
63581
- reason: "pre-clean-failed",
64125
+ status: deferred ? "deferred" : "failed",
64126
+ reason: deferred ? "node-modules-locked" : "pre-clean-failed",
63582
64127
  error: `real-node_modules cleanup failed before worktree removal: ${nmRemoved.error}`,
63583
64128
  artifactsArchive,
63584
64129
  stageTeardown,
63585
64130
  tmpEvidenceCount: tmpEvidence.length,
63586
64131
  ...unlinkedReparsePoints.length ? { unlinkedReparsePoints } : {},
63587
- remediation: `Close handles to '${wtPath.replace(/'/g, "''")}' and retry MMI PR landing from the primary checkout`
64132
+ remediation: deferred ? deferredNodeModulesLockRemediation(wtPath, branch, options.prNumber, options.repo) : `Close handles to '${wtPath.replace(/'/g, "''")}' and retry MMI PR landing from the primary checkout`
63588
64133
  };
63589
- report.localBranch = { name: branch, status: "not-attempted", reason: "worktree-pre-clean-failed" };
64134
+ report.localBranch = { name: branch, status: "not-attempted", reason: deferred ? "worktree-cleanup-deferred" : "worktree-pre-clean-failed" };
63590
64135
  return report;
63591
64136
  }
63592
64137
  }
@@ -63685,6 +64230,8 @@ function renderPrMergeCleanupLines(cleanup) {
63685
64230
  lines2.push(`pr merge: preserved worktree ${wt.path} (--preserve-worktree)`);
63686
64231
  } else if (wt.status === "retained-locked") {
63687
64232
  lines2.push(`pr merge: worktree ${wt.path} cleanup deferred \u2014 its Windows parent shell still holds the cwd; ${wt.remediation ?? "exit the shell and remove the residue from the primary checkout"}`);
64233
+ } else if (wt.status === "deferred") {
64234
+ lines2.push(`pr merge: worktree ${wt.path} cleanup deferred \u2014 a node_modules file is locked${wt.error ? ` (${wt.error})` : ""}; ${wt.remediation ?? "close the handle and re-run pr merge for this PR"}`);
63688
64235
  } else if (wt.status === "switched-primary") {
63689
64236
  const now = wt.switchedTo ? `to ${wt.switchedTo}` : "to the base branch";
63690
64237
  lines2.push(wt.error ? `pr merge: primary checkout ${wt.path} switched ${now} but deleting the merged branch failed \u2014 ${wt.error}; remediate: ${wt.remediation ?? "delete the merged branch manually"}` : `pr merge: primary checkout ${wt.path} held the merged branch \u2014 switched it back ${now} and deleted the branch`);
@@ -65044,7 +65591,7 @@ ${preparation.findings.map((finding) => `[${finding.check}/${finding.state}] ${f
65044
65591
  }
65045
65592
  class PrHeadBehindBaseError extends Error {
65046
65593
  }
65047
- pr.command("land <numbers...>").description("agent merge path (#1440): train probe \u2014 checks-wait \u2014 merge --auto \u2014 poll enqueued \u2014 development PRs only. Several PR numbers land together (#8322): all are enqueued, waited on together, a queue-ejected PR is updated and re-enqueued once, a queue merge is followed by worktree/branch cleanup and board Done, and one table (or --json) reports each outcome").option("--json", "machine-readable output").option("--repo <owner/repo>", "target repo (defaults to the PR repo)").option("--enqueue-only", "return once queue membership is confirmed; without a merge queue, land normally").option("--abandon-admission", "one stuck-receipt recovery, one PR per call: journal the saved non-terminal (pending/enqueued, or a dispatching send that saved no operation UUID after its sender died \u2014 #9995) queue-admission receipt terminal ONLY after positively observing the PR OPEN at the receipt head and absent from the queue; any unprovable observation refuses and changes nothing (#9951). The next pr land then submits a new intent with no new head").option("--no-require-train", "skip train-authority preflight (not recommended for autonomous agents)").option("--force", "silence the leftover-scratch warning (scratch never blocks, #8785) and acknowledge a negated-closing-keyword (#3718) or ambiguous-cross-repo-closing (#4279) refusal").option("--without-review <reason>", "compatibility option; shared merge helpers do not require a review verdict").option("--timeout <minutes>", `merge-queue wait per PR (default ${PR_LAND_QUEUE_TIMEOUT_MS / 6e4} minutes, one window per ${PR_LAND_QUEUE_BATCH_SIZE} PRs \u2014 per-repo ruleset check_response_timeout_minutes is 15 default with 30/45/60 overrides in .github/rulesets/mmi-development-merge-queue.json; agents never edit the ruleset to shorten a queue wait)`).option("--preserve-worktree", "after a queue merge keep the local worktree and branch (skips the post-merge cleanup)").option("--expect-head <sha>", "pin the PR head to this full 40-hex SHA (#8456): checked before landing, after checks and by GitHub at enqueue (--match-head-commit); never updates a BEHIND head and never retries an ejection \u2014 it stops with pinnedHead.refused instead. One PR only").action(async (numbers, opts) => {
65594
+ pr.command("land <numbers...>").description("agent merge path (#1440): train probe \u2014 checks-wait \u2014 merge --auto \u2014 poll enqueued \u2014 development PRs only. Several PR numbers land together (#8322): all are enqueued, waited on together, a queue-ejected PR is updated and re-enqueued once, a queue merge is followed by worktree/branch cleanup and board Done, and one table (or --json) reports each outcome").option("--json", "machine-readable output").option("--repo <owner/repo>", "target repo (defaults to the PR repo)").option("--enqueue-only", "return once queue membership is confirmed; without a merge queue, land normally").option("--abandon-admission", "one stuck-receipt recovery, one PR per call: journal the saved non-terminal (pending/enqueued, or a dispatching send that saved no operation UUID after its sender died \u2014 #9995) queue-admission receipt terminal ONLY after positively observing the PR OPEN at the receipt head and absent from the queue; any unprovable observation refuses and changes nothing (#9951). The next pr land then submits a new intent with no new head").option("--dequeue", "one queued-PR recovery, one PR per call: take the PR out of GitHub's merge queue (dequeuePullRequest, #10132) so a fix commit can reach a branch whose queue entry blocks pushes \u2014 refuses when the PR is not in the queue, read-back-verifies the entry is gone before reporting, and journals a saved non-terminal admission receipt terminal so the next pr land submits a new intent").option("--no-require-train", "skip train-authority preflight (not recommended for autonomous agents)").option("--force", "silence the leftover-scratch warning (scratch never blocks, #8785) and acknowledge a negated-closing-keyword (#3718) or ambiguous-cross-repo-closing (#4279) refusal").option("--without-review <reason>", "compatibility option; shared merge helpers do not require a review verdict").option("--timeout <minutes>", `merge-queue wait per PR (default ${PR_LAND_QUEUE_TIMEOUT_MS / 6e4} minutes, one window per ${PR_LAND_QUEUE_BATCH_SIZE} PRs \u2014 per-repo ruleset check_response_timeout_minutes is 15 default with 30/45/60 overrides in .github/rulesets/mmi-development-merge-queue.json; agents never edit the ruleset to shorten a queue wait)`).option("--preserve-worktree", "after a queue merge keep the local worktree and branch (skips the post-merge cleanup)").option("--expect-head <sha>", "pin the PR head to this full 40-hex SHA (#8456): checked before landing, after checks and by GitHub at enqueue (--match-head-commit); never updates a BEHIND head and never retries an ejection \u2014 it stops with pinnedHead.refused instead. One PR only").action(async (numbers, opts) => {
65048
65595
  const timeoutMinutes = opts.timeout === void 0 ? void 0 : Number(opts.timeout);
65049
65596
  if (timeoutMinutes !== void 0 && !(timeoutMinutes > 0)) return fail("pr land: --timeout must be a positive number of minutes");
65050
65597
  if (opts.abandonAdmission) {
@@ -65057,6 +65604,17 @@ ${preparation.findings.map((finding) => `[${finding.check}/${finding.state}] ${f
65057
65604
  if (outcome.status !== "abandoned") process.exitCode = 1;
65058
65605
  return;
65059
65606
  }
65607
+ if (opts.dequeue) {
65608
+ if (opts.abandonAdmission) return fail("pr land: --dequeue and --abandon-admission are separate one-PR recoveries; pass one per call");
65609
+ if (numbers.length !== 1) return fail("pr land: --dequeue dequeues one PR per call");
65610
+ const repo = await resolveRepo(opts.repo).catch(() => void 0) ?? opts.repo;
65611
+ if (!repo) return fail("pr land: --dequeue needs --repo <owner/repo>; the PR repo could not be resolved");
65612
+ const outcome = await dequeuePrMergeQueue(numbers[0], repo);
65613
+ if (opts.json) printLine(JSON.stringify(outcome));
65614
+ else printLine(outcome.status === "dequeued" ? `pr land: dequeued PR #${outcome.pr} from the merge queue on ${repo}${outcome.head ? ` at head ${outcome.head}` : ""} \u2014 push the fix commit, then rerun \`mmi-cli devops pr land ${outcome.pr} --repo ${repo}\`` + (outcome.admission === "terminal" ? `; the saved queue admission was journaled terminal${outcome.operation ? ` (operation ${outcome.operation})` : ""}, so the next pr land submits a new intent` : "") + (outcome.admission === "left-open" ? "; a saved admission receipt was left open \u2014 `--abandon-admission` settles it once the PR is verifiably out of the queue" : "") + (outcome.admission === "unreadable" ? "; the saved queue admission could not be read and was not changed" : "") : `pr land: refused to dequeue PR #${outcome.pr} on ${repo} \u2014 ${outcome.reason}`);
65615
+ if (outcome.status !== "dequeued") process.exitCode = 1;
65616
+ return;
65617
+ }
65060
65618
  if (opts.expectHead !== void 0) {
65061
65619
  if (!/^[0-9a-f]{40}$/i.test(opts.expectHead)) return fail("pr land: --expect-head needs the full 40-hex head SHA");
65062
65620
  if (numbers.length !== 1) return fail("pr land: --expect-head pins one PR head; land one PR per call");
@@ -65637,6 +66195,8 @@ ${preparation.findings.map((finding) => `[${finding.check}/${finding.state}] ${f
65637
66195
  preserveWorktree: o.preserveWorktree,
65638
66196
  gcAcknowledged: o.gc,
65639
66197
  expectedHeadOid: headRefOid,
66198
+ prNumber: number,
66199
+ repo: repoForPostCleanup,
65640
66200
  pathExists: (p) => (0, import_node_fs54.existsSync)(p),
65641
66201
  // #5899: pin cleanup git calls to the main checkout — the task worktree this process may be
65642
66202
  // standing in is removed mid-cleanup, so a cwd-relative invocation fails with
@@ -72386,14 +72946,14 @@ vercel preview (${preview.branch}): ${d.state}${d.url ? ` \u2014 ${d.url}` : ""}
72386
72946
  }
72387
72947
 
72388
72948
  // src/tenant-artifact.ts
72389
- var import_node_crypto24 = require("node:crypto");
72949
+ var import_node_crypto25 = require("node:crypto");
72390
72950
  var import_node_fs61 = require("node:fs");
72391
72951
  var import_promises12 = require("node:fs/promises");
72392
72952
  var import_node_path57 = require("node:path");
72393
72953
  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}$/;
72394
72954
  var MAX_BYTES2 = 5 * 1024 * 1024 * 1024;
72395
72955
  async function sha256File(path2) {
72396
- const hash = (0, import_node_crypto24.createHash)("sha256");
72956
+ const hash = (0, import_node_crypto25.createHash)("sha256");
72397
72957
  for await (const chunk of (0, import_node_fs61.createReadStream)(path2)) hash.update(chunk);
72398
72958
  return hash.digest("hex");
72399
72959
  }
@@ -73073,13 +73633,13 @@ async function executeWaveLand(plan, deps) {
73073
73633
 
73074
73634
  // src/dist-drift.ts
73075
73635
  var import_node_child_process23 = require("node:child_process");
73076
- var import_node_crypto26 = require("node:crypto");
73636
+ var import_node_crypto27 = require("node:crypto");
73077
73637
  var import_node_fs64 = require("node:fs");
73078
73638
  var import_node_os28 = require("node:os");
73079
73639
  var import_node_path60 = require("node:path");
73080
73640
 
73081
73641
  // ../scripts/distribution-digest.mjs
73082
- var import_node_crypto25 = require("node:crypto");
73642
+ var import_node_crypto26 = require("node:crypto");
73083
73643
  var import_node_fs63 = require("node:fs");
73084
73644
  var import_node_path59 = require("node:path");
73085
73645
  var slash = (value) => value.replaceAll("\\", "/");
@@ -73092,7 +73652,7 @@ function repoPath(root, declaredPath, label) {
73092
73652
  return target;
73093
73653
  }
73094
73654
  function digestFiles(files) {
73095
- const hash = (0, import_node_crypto25.createHash)("sha256");
73655
+ const hash = (0, import_node_crypto26.createHash)("sha256");
73096
73656
  for (const file of [...files].sort((a, b) => a.relative.localeCompare(b.relative))) {
73097
73657
  const content = file.stat.isSymbolicLink() ? Buffer.from((0, import_node_fs63.readlinkSync)(file.absolute), "utf8") : (0, import_node_fs63.readFileSync)(file.absolute);
73098
73658
  hash.update(file.relative, "utf8");
@@ -73123,7 +73683,7 @@ var DIST_ARTIFACTS = [
73123
73683
  ];
73124
73684
  var BOM_DIST_TREE_ID = "mmi-cli-dist";
73125
73685
  var ABSENT = "absent";
73126
- var sha2563 = (bytes) => `sha256:${(0, import_node_crypto26.createHash)("sha256").update(bytes).digest("hex")}`;
73686
+ var sha2563 = (bytes) => `sha256:${(0, import_node_crypto27.createHash)("sha256").update(bytes).digest("hex")}`;
73127
73687
  function artifactDrift(path2, committedBytes, rebuiltBytes) {
73128
73688
  const committed = committedBytes ? sha2563(committedBytes) : ABSENT;
73129
73689
  const rebuiltExpected = rebuiltBytes ? sha2563(rebuiltBytes) : ABSENT;
@@ -74693,6 +75253,7 @@ registerPreAction.call(program2, "preAction", async (_thisCommand, actionCommand
74693
75253
  "run cancel",
74694
75254
  "run rerun",
74695
75255
  "run dispatch",
75256
+ "run test",
74696
75257
  // #9779/#9780: the vars verbs and archive-status validate --repo/name/value before the first
74697
75258
  // dispatch, so their scoped App mint waits for the action handler like the run writes above.
74698
75259
  "vars get",
@@ -74723,7 +75284,7 @@ async function reportWrite(label, res) {
74723
75284
  return failGraceful(`${label}: HTTP ${res.status}${detail ? ` \u2014 ${detail}` : ""}`);
74724
75285
  }
74725
75286
  var tenant = program2.command("tenant").description("tenant runtime control through Hub authority");
74726
- tenant.command("control <owner/repo> <stage> <action>").description("bounded tenant control plus value-free vault/broker verification; dev/main retire needs master and exact data-loss confirmation").option("--watch", "block on the dispatched run and report its conclusion (status/retire/verify-secrets/verify-broker/logs watch by default)").option("--lines <n>", "logs only: trailing lines of the tenant service to return (1-2000, default 200)").option("--since <utc>", "logs only: read the journal from this UTC time, e.g. 2026-10-07T13:10Z (includes replaced containers)").option("--until <utc>", "logs only: read the journal up to this UTC time").option("--edge", "logs only: Caddy journal for this stage\u2019s hosts (default window: last 15 minutes)").option("--task <name>", "run-task only: registry-declared task name (never a command)").option("--artifact <id>", "run-task only: opaque id returned by tenant artifact put").option("--input <file>", "run-task only: upload this file as the task artifact (same as tenant artifact put), then run with its id").option("--resume <run-id>", "run-task only: re-attach to an already-dispatched run (read-only, never re-dispatches) and print its receipt").option("--confirm-data-loss <owner/repo/stage>", "dev/main retire only: exact target to decommission; without it show the dry-run plan").option("--json", "machine-readable output").action(async (repo, stage, action, o) => {
75287
+ tenant.command("control <owner/repo> <stage> <action>").description("bounded tenant control plus value-free vault/broker verification; dev/main retire needs master and exact data-loss confirmation").option("--watch", "block on the dispatched run and report its conclusion (status/retire/verify-secrets/verify-broker/logs watch by default)").option("--lines <n>", "logs only: trailing lines of the tenant service to return (1-2000, default 200)").option("--since <utc>", "logs only: read the journal from this UTC time, e.g. 2026-10-07T13:10Z (includes replaced containers)").option("--until <utc>", "logs only: read the journal up to this UTC time").option("--edge", "logs only: Caddy journal for this stage\u2019s hosts (default window: last 15 minutes)").option("--task <name>", "run-task only: registry-declared task name (never a command)").option("--artifact <id>", "run-task only: opaque id returned by tenant artifact put").option("--input <file>", "run-task only: upload this file as the task artifact (same as tenant artifact put), then run with its id").option("--resume <run-id>", "run-task only: re-attach to an already-dispatched run (read-only, never re-dispatches) and print its receipt").option("--confirm-data-loss <owner/repo/stage>", "dev/main retire only: exact target to decommission; without it show the dry-run plan").option("--out <path>", "write the output to this file as UTF-8 (no BOM) instead of stdout \u2014 the shell-free receipt path (#5802); written only after a successful result (#10127)").option("--json", "machine-readable output").action(async (repo, stage, action, o) => {
74727
75288
  try {
74728
75289
  if (o.resume !== void 0 && (action !== "run-task" || !/^\d+$/.test(o.resume) || o.task || o.artifact || o.input)) return fail("runtime tenant control: --resume <run-id> is run-task-only, takes a numeric run id, and cannot be combined with --task/--artifact/--input");
74729
75290
  if (o.input !== void 0 && o.artifact !== void 0) return fail("runtime tenant control: pass --input <file> or --artifact <id>, not both");
@@ -74732,9 +75293,9 @@ tenant.command("control <owner/repo> <stage> <action>").description("bounded ten
74732
75293
  if (o.confirmDataLoss !== void 0 && o.confirmDataLoss !== `${repo}/${stage}`) return fail(`runtime tenant control: --confirm-data-loss must match ${repo}/${stage} exactly`);
74733
75294
  if (o.confirmDataLoss === void 0) {
74734
75295
  const res = await tenantControl({ repo, stage, action, plan: true }, registryClientDeps(await loadConfig()));
74735
- if (!res.ok) return failGraceful(`runtime tenant control plan: ${res.body?.error ?? res.error ?? `HTTP ${res.status}`}`);
74736
- printLine(o.json ? JSON.stringify(res.body, null, 2) : `${JSON.stringify(res.body, null, 2)}
74737
- Dry run: pass --confirm-data-loss ${repo}/${stage} to decommission.`);
75296
+ if (!res.ok) return failGraceful(tenantControlFailure(`runtime tenant control plan: ${res.body?.error ?? res.error ?? `HTTP ${res.status}`}`, o.json));
75297
+ emitTenantControlOutput(o.json ? JSON.stringify(res.body, null, 2) : `${JSON.stringify(res.body, null, 2)}
75298
+ Dry run: pass --confirm-data-loss ${repo}/${stage} to decommission.`, { out: o.out, action }, { print: printLine });
74738
75299
  return;
74739
75300
  }
74740
75301
  }
@@ -74760,22 +75321,22 @@ Dry run: pass --confirm-data-loss ${repo}/${stage} to decommission.`);
74760
75321
  if (!o.json && action === "verify-secrets" && result.secrets) {
74761
75322
  const body = { ok: result.conclusion === "success", secrets: result.secrets, ssmStatus: result.conclusion === "success" ? "Success" : "Failed", raw: result.secretsRaw };
74762
75323
  const { lines: lines3, failure } = renderVerifySecrets(body);
74763
- for (const line of lines3) printLine(line);
74764
- if (failure) return failGraceful(`runtime tenant control ${stage} verify-secrets: ${failure}`);
75324
+ emitTenantControlOutput(lines3.join("\n"), { out: o.out, failed: !!failure || result.conclusion === "failure", action }, { print: printLine });
75325
+ if (failure) return failGraceful(tenantControlFailure(`runtime tenant control ${stage} verify-secrets: ${failure}`, o.json));
74765
75326
  } else if (!o.json && action === "logs" && result.logs) {
74766
- printLine(result.logs);
75327
+ emitTenantControlOutput(result.logs, { out: o.out, failed: result.conclusion === "failure", action }, { print: printLine });
74767
75328
  } else if (!o.json && action === "verify-broker" && result.broker) {
74768
75329
  const { lines: lines3, failure } = renderVerifyBroker({ broker: result.broker, raw: result.brokerRaw });
74769
- for (const line of lines3) printLine(line);
74770
- if (failure) return failGraceful(`runtime tenant control ${stage} verify-broker: ${failure}`);
75330
+ emitTenantControlOutput(lines3.join("\n"), { out: o.out, failed: !!failure || result.conclusion === "failure", action }, { print: printLine });
75331
+ if (failure) return failGraceful(tenantControlFailure(`runtime tenant control ${stage} verify-broker: ${failure}`, o.json));
74771
75332
  } else {
74772
- printLine(o.json ? JSON.stringify(result, null, 2) : renderTenantControl(result));
75333
+ emitTenantControlOutput(o.json ? JSON.stringify(result, null, 2) : renderTenantControl(result), { out: o.out, failed: result.conclusion === "failure", action }, { print: printLine });
74773
75334
  }
74774
75335
  if (result.conclusion === "failure") {
74775
- return failGraceful(`runtime tenant control ${stage} ${action}: ${result.category ?? "failed"} \u2014 ${result.note}`);
75336
+ return failGraceful(tenantControlFailure(`runtime tenant control ${stage} ${action}: ${result.category ?? "failed"} \u2014 ${result.note}`, o.json));
74776
75337
  }
74777
75338
  } catch (e) {
74778
- return failGraceful(`runtime tenant control: ${e.message}`);
75339
+ return failGraceful(tenantControlFailure(`runtime tenant control: ${e.message}`, o.json));
74779
75340
  }
74780
75341
  });
74781
75342
  tenant.command("undo <owner/repo> <stage>").description("switch a tenant stage back to its previous release (warm standby: seconds; else recreated from the retained release, no rebuild); watches by default").option("--json", "machine-readable output").action(async (repo, stage, o) => {
@@ -75588,6 +76149,17 @@ registerRunDispatch(
75588
76149
  async () => (await mintCommandWriteToken(appActorDeps(), "run dispatch")).token,
75589
76150
  async () => fetchProjectsList(registryClientDeps(await loadConfig()))
75590
76151
  );
76152
+ var remoteTestToken;
76153
+ var remoteTestWriteToken = () => remoteTestToken ??= mintCommandWriteToken(appActorDeps(), "run test").then((t) => t.token);
76154
+ registerRemoteTest(runRoot, {
76155
+ dispatch: async (inputs) => {
76156
+ await dispatchWorkflow(FOCUSED_TEST_REPO, FOCUSED_TEST_WORKFLOW, FOCUSED_TEST_REF, inputs, {
76157
+ projects: async () => fetchProjectsList(registryClientDeps(await loadConfig())),
76158
+ post: appDispatchPost(remoteTestWriteToken)
76159
+ });
76160
+ },
76161
+ read: appApiRead(remoteTestWriteToken)
76162
+ });
75591
76163
  var varsAppClient = (commandPath4) => async () => (
75592
76164
  // The concrete client always provides restWithStatus (optional on the interface only so hand-built
75593
76165
  // test stubs of GitHubClient keep compiling).