@mutmutco/claude-plugin 4.5.161 → 4.5.162

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
@@ -76,19 +76,19 @@ function hardExit(code) {
76
76
  process.exit(code);
77
77
  }
78
78
  function flushStream(stream) {
79
- return new Promise((resolve8) => {
79
+ return new Promise((resolve7) => {
80
80
  try {
81
- stream.write("", () => resolve8());
81
+ stream.write("", () => resolve7());
82
82
  } catch {
83
- resolve8();
83
+ resolve7();
84
84
  }
85
85
  });
86
86
  }
87
87
  async function flushStdio(timeoutMs = STDIO_FLUSH_TIMEOUT_MS) {
88
88
  await Promise.race([
89
89
  Promise.all([flushStream(process.stdout), flushStream(process.stderr)]),
90
- new Promise((resolve8) => {
91
- setTimeout(resolve8, timeoutMs).unref?.();
90
+ new Promise((resolve7) => {
91
+ setTimeout(resolve7, timeoutMs).unref?.();
92
92
  })
93
93
  ]);
94
94
  }
@@ -96,7 +96,7 @@ async function cleanExit(code) {
96
96
  process.exitCode = code;
97
97
  await closeHttpPool();
98
98
  await flushStdio();
99
- await new Promise((resolve8) => setImmediate(resolve8));
99
+ await new Promise((resolve7) => setImmediate(resolve7));
100
100
  return void 0;
101
101
  }
102
102
  async function finishCliRun(watchdogMs = CLI_EXIT_WATCHDOG_MS) {
@@ -590,6 +590,11 @@ var init_github_client = __esm({
590
590
  });
591
591
 
592
592
  // src/gh-read-route.ts
593
+ function isAppFilingWrite(args) {
594
+ if (args.some((a) => VIEWER.test(a))) return false;
595
+ const [group, verb] = args;
596
+ return (group === "issue" || group === "label") && verb === "create";
597
+ }
593
598
  function isViewerFreeGhRead(args) {
594
599
  if (args.some((a) => VIEWER.test(a))) return false;
595
600
  const [group, verb] = args;
@@ -692,7 +697,7 @@ function rateLimitedReceipt(opts) {
692
697
  };
693
698
  }
694
699
  async function runWithRateLimitBackoff(operation, opts) {
695
- const sleep2 = opts.sleep ?? ((ms) => new Promise((resolve8) => setTimeout(resolve8, ms)));
700
+ const sleep2 = opts.sleep ?? ((ms) => new Promise((resolve7) => setTimeout(resolve7, ms)));
696
701
  const now = opts.now ?? Date.now;
697
702
  const log = opts.log ?? ((message2) => console.warn(message2));
698
703
  const capMs = opts.capMs ?? RATE_LIMIT_WAIT_CAP_MS;
@@ -782,7 +787,7 @@ async function fetchWithRetry(fetchImpl, url, init, opts = {}) {
782
787
  const attempts = opts.attempts ?? 3;
783
788
  const baseDelayMs = opts.baseDelayMs ?? 250;
784
789
  const retryOn = opts.retryOn ?? ((res) => res.status >= 500);
785
- const sleep2 = opts.sleep ?? ((ms) => new Promise((resolve8) => setTimeout(resolve8, ms)));
790
+ const sleep2 = opts.sleep ?? ((ms) => new Promise((resolve7) => setTimeout(resolve7, ms)));
786
791
  let lastErr;
787
792
  for (let i = 0; i < attempts; i++) {
788
793
  const isLast = i === attempts - 1;
@@ -1109,8 +1114,8 @@ async function readStdin(opts = {}) {
1109
1114
  })().catch(() => {
1110
1115
  });
1111
1116
  let timer;
1112
- const timeout = new Promise((resolve8) => {
1113
- timer = setTimeout(resolve8, timeoutMs);
1117
+ const timeout = new Promise((resolve7) => {
1118
+ timer = setTimeout(resolve7, timeoutMs);
1114
1119
  });
1115
1120
  try {
1116
1121
  await Promise.race([drain, timeout]);
@@ -1161,11 +1166,21 @@ __export(cli_shared_exports, {
1161
1166
  readStdin: () => readStdin,
1162
1167
  repoProject: () => repoProject,
1163
1168
  setGhReadTokenProvider: () => setGhReadTokenProvider,
1169
+ setGhWriteTokenProvider: () => setGhWriteTokenProvider,
1164
1170
  setInjectedStdin: () => setInjectedStdin
1165
1171
  });
1166
1172
  function setGhReadTokenProvider(provider) {
1167
1173
  ghReadTokenProvider = provider;
1168
1174
  }
1175
+ function setGhWriteTokenProvider(provider) {
1176
+ ghWriteTokenProvider = provider;
1177
+ }
1178
+ async function ghFilingAppEnv(file, args) {
1179
+ if (!ghWriteTokenProvider || !/(^|[\\/])gh(\.exe)?$/i.test(file) || process.env.MMI_ACTOR?.trim() === "personal") return void 0;
1180
+ if (!isAppFilingWrite(args)) return void 0;
1181
+ const token = await ghWriteTokenProvider().catch(() => void 0);
1182
+ return token ? { ...process.env, GH_TOKEN: token } : void 0;
1183
+ }
1169
1184
  async function ghReadAppEnv(file, args) {
1170
1185
  if (!ghReadTokenProvider || !/(^|[\\/])gh(\.exe)?$/i.test(file) || process.env.MMI_ACTOR?.trim() === "personal") return void 0;
1171
1186
  if (!isViewerFreeGhRead(args)) return void 0;
@@ -1187,12 +1202,12 @@ function killProcessTree(pid) {
1187
1202
  function execFileHard(file, args, options) {
1188
1203
  const { timeout, step, ...rest } = options;
1189
1204
  const started = Date.now();
1190
- return new Promise((resolve8, reject) => {
1205
+ return new Promise((resolve7, reject) => {
1191
1206
  const child2 = (0, import_node_child_process2.execFile)(file, args, { encoding: "utf8", windowsHide: true, ...rest, timeout: 0 }, (error, stdout, stderr) => {
1192
1207
  clearTimeout(timer);
1193
1208
  if (expired) return;
1194
1209
  if (error) reject(error);
1195
- else resolve8({ stdout: String(stdout), stderr: String(stderr) });
1210
+ else resolve7({ stdout: String(stdout), stderr: String(stderr) });
1196
1211
  });
1197
1212
  let expired = false;
1198
1213
  const timer = setTimeout(() => {
@@ -1340,7 +1355,7 @@ function printLine(value) {
1340
1355
  (0, import_node_fs5.writeSync)(1, `${value}
1341
1356
  `);
1342
1357
  }
1343
- var import_node_child_process2, import_node_fs5, import_node_util3, rawExecFileP, DEFAULT_EXEC_TIMEOUT_MS, execFileP, ghReadTokenProvider, GIT_TIMEOUT_MS, ExecDeadlineError, cachedGithubLogin, gitOut, consoleIo;
1358
+ var import_node_child_process2, import_node_fs5, import_node_util3, rawExecFileP, DEFAULT_EXEC_TIMEOUT_MS, execFileP, ghReadTokenProvider, ghWriteTokenProvider, GIT_TIMEOUT_MS, ExecDeadlineError, cachedGithubLogin, gitOut, consoleIo;
1344
1359
  var init_cli_shared = __esm({
1345
1360
  "src/cli-shared.ts"() {
1346
1361
  "use strict";
@@ -1364,7 +1379,7 @@ var init_cli_shared = __esm({
1364
1379
  // promisify(execFile)'s overloads widen to string|Buffer when options is spread in.
1365
1380
  rawExecFileP(file, args, { encoding: "utf8", windowsHide: true, timeout: DEFAULT_EXEC_TIMEOUT_MS, killSignal: "SIGTERM", ...options, ...env ? { env } : {} })
1366
1381
  );
1367
- const appEnv = options.env ? void 0 : await ghReadAppEnv(file, args);
1382
+ const appEnv = options.env ? void 0 : await ghReadAppEnv(file, args) ?? await ghFilingAppEnv(file, args);
1368
1383
  if (!appEnv) return run();
1369
1384
  try {
1370
1385
  return await run(appEnv);
@@ -6664,7 +6679,6 @@ var import_node_fs14 = require("node:fs");
6664
6679
  var import_node_path11 = require("node:path");
6665
6680
  var PLAN_ADVISORY_AGE_MS = 30 * 24 * 36e5;
6666
6681
  var ROOT_SCRATCH_STALE_MS = 24 * 36e5;
6667
- var SCRATCH_GC_THROTTLE_MS = 24 * 36e5;
6668
6682
  function executeScratchGc(repoRoot2, opts, now = Date.now()) {
6669
6683
  const snap = collectScratchSnapshot(repoRoot2);
6670
6684
  const plan = planScratchGc(snap, now);
@@ -7239,7 +7253,9 @@ async function mintInstallationToken(deps) {
7239
7253
  return mintScopedInstallationToken(deps, APP_POLL_PERMISSIONS);
7240
7254
  }
7241
7255
  var APP_WRITE_COMMANDS = /* @__PURE__ */ new Map([
7242
- ["issue comment", { metadata: "read", issues: "write", pull_requests: "write" }]
7256
+ ["issue comment", { metadata: "read", issues: "write", pull_requests: "write" }],
7257
+ // #8401: the detached related-issue lookup (issue list/view reads + one marker comment) — no identity contract.
7258
+ ["issue discover-related", { metadata: "read", issues: "write" }]
7243
7259
  ]);
7244
7260
  async function mintCommandWriteToken(deps, commandPath3) {
7245
7261
  const permissions = APP_WRITE_COMMANDS.get(commandPath3);
@@ -7251,6 +7267,14 @@ async function mintCommandWriteToken(deps, commandPath3) {
7251
7267
  throw new AppActorError(`app actor: write token mint failed (${e?.constructor?.name ?? "Error"})`);
7252
7268
  }
7253
7269
  }
7270
+ async function mintFilingWriteToken(deps) {
7271
+ try {
7272
+ return await mintFromMember(deps, { metadata: "read", issues: "write" }, APP_VAULT_KEYS);
7273
+ } catch (e) {
7274
+ if (e instanceof AppActorError) throw e;
7275
+ throw new AppActorError(`app actor: filing token mint failed (${e?.constructor?.name ?? "Error"})`);
7276
+ }
7277
+ }
7254
7278
  async function mintStatusWriteToken(deps) {
7255
7279
  return mintScopedInstallationToken(deps, { metadata: "read", statuses: "write" });
7256
7280
  }
@@ -8201,7 +8225,7 @@ async function readRestPrSnapshotWithRetry(prNumber, repo, gh2 = defaultGhApi, o
8201
8225
  const retries = options?.retries ?? PR_SNAPSHOT_READ_RETRIES;
8202
8226
  const untilMs = options?.retryTransientUntilMs;
8203
8227
  const delayMs = options?.delayMs ?? (untilMs !== void 0 ? PR_CHECKS_POLL_MS : PR_SNAPSHOT_READ_DELAY_MS);
8204
- const sleep2 = options?.sleep ?? ((ms) => new Promise((resolve8) => setTimeout(resolve8, ms)));
8228
+ const sleep2 = options?.sleep ?? ((ms) => new Promise((resolve7) => setTimeout(resolve7, ms)));
8205
8229
  const now = options?.now ?? (() => Date.now());
8206
8230
  let lastError = "no attempt completed";
8207
8231
  let attempt = 0;
@@ -10181,15 +10205,15 @@ async function postJson(pathSuffix, payload, deps, method = "POST", opts = {}) {
10181
10205
  return { ok: false, status: 0, body: null, error: e.message, unreachable: "transport" };
10182
10206
  }
10183
10207
  }
10184
- async function edgeDnsForStage(slug, stage, deps, ensure = false) {
10208
+ async function edgeDnsForStage(slug, stage, deps, ensure = false, deletion) {
10185
10209
  if (!deps.baseUrl) throw new Error("Hub API URL unavailable");
10186
10210
  const token = await deps.token();
10187
10211
  if (!token) throw new Error("Hub session token unavailable");
10188
10212
  const path2 = `/projects/${encodeURIComponent(slug)}/edge-dns/${stage}`;
10189
10213
  let body;
10190
- if (ensure) {
10191
- const write = await postJson(path2, {}, deps, "POST", { noRetry: true });
10192
- if (!write.ok) throw new Error(`Hub edge dns ensure failed: ${write.error ?? `HTTP ${write.status}`}`);
10214
+ if (ensure || deletion) {
10215
+ const write = await postJson(path2, deletion ?? {}, deps, deletion ? "DELETE" : "POST", { noRetry: true });
10216
+ if (!write.ok) throw new Error(`Hub edge dns ${deletion ? "delete" : "ensure"} failed: ${write.error ?? `HTTP ${write.status}`}`);
10193
10217
  body = write.body;
10194
10218
  } else {
10195
10219
  const res = await retriedFetch(
@@ -10203,6 +10227,20 @@ async function edgeDnsForStage(slug, stage, deps, ensure = false) {
10203
10227
  if (typeof body?.required !== "boolean") throw new Error("Hub edge dns response invalid");
10204
10228
  return body;
10205
10229
  }
10230
+ async function boxPreviewsForHost(box, opts, deps) {
10231
+ const payload = {
10232
+ box,
10233
+ ...opts.portStart !== void 0 ? { portStart: opts.portStart } : {},
10234
+ ...opts.cap !== void 0 ? { cap: opts.cap } : {},
10235
+ apply: opts.apply === true
10236
+ };
10237
+ const write = await postJson("/projects/box-previews", payload, deps, "POST", { noRetry: opts.apply === true });
10238
+ if (!write.ok) {
10239
+ const detail = write.body?.error;
10240
+ throw new Error(`Hub box previews failed: ${detail ?? write.error ?? `HTTP ${write.status}`}`);
10241
+ }
10242
+ return write.body;
10243
+ }
10206
10244
  async function edgeCertificateForStage(slug, stage, deps, ensure = false) {
10207
10245
  if (!deps.baseUrl) throw new Error("Hub API URL unavailable");
10208
10246
  const token = await deps.token();
@@ -12063,7 +12101,7 @@ async function createPrViaRestFallback(args, swappedArgs, deps, knownPools) {
12063
12101
  }
12064
12102
  async function ghCreate(args, deps = {}) {
12065
12103
  const exec3 = deps.exec ?? execFileP;
12066
- const sleep2 = deps.sleep ?? ((ms) => new Promise((resolve8) => setTimeout(resolve8, ms)));
12104
+ const sleep2 = deps.sleep ?? ((ms) => new Promise((resolve7) => setTimeout(resolve7, ms)));
12067
12105
  const now = deps.now ?? Date.now;
12068
12106
  const read = deps.readFile ?? import_promises.readFile;
12069
12107
  const restCreatePr = deps.restCreatePr ?? ((input) => defaultRestCreatePr(exec3, input));
@@ -12891,8 +12929,8 @@ var PRE_SPAWN_DRAIN_TIMEOUT_MS = 2e3;
12891
12929
  async function drainHttpPoolBeforeSpawn() {
12892
12930
  const drained = await Promise.race([
12893
12931
  closeHttpPool().then(() => true),
12894
- new Promise((resolve8) => {
12895
- setTimeout(() => resolve8(false), PRE_SPAWN_DRAIN_TIMEOUT_MS).unref?.();
12932
+ new Promise((resolve7) => {
12933
+ setTimeout(() => resolve7(false), PRE_SPAWN_DRAIN_TIMEOUT_MS).unref?.();
12896
12934
  })
12897
12935
  ]);
12898
12936
  if (!drained) destroyHttpPool();
@@ -15580,7 +15618,7 @@ async function setBoardItemPriority(client, cfg, itemId, priority) {
15580
15618
  await updateItemSingleSelect(client, cfg.projectId, itemId, cfg.priorityFieldId, optionId);
15581
15619
  return cliPriorityToFieldName(priority);
15582
15620
  }
15583
- var defaultRetrySleep = (ms) => new Promise((resolve8) => setTimeout(resolve8, ms));
15621
+ var defaultRetrySleep = (ms) => new Promise((resolve7) => setTimeout(resolve7, ms));
15584
15622
  async function resolveProjectItemIdWithRetry(client, cfg, selector, opts = {}) {
15585
15623
  const attempts = Math.max(1, opts.attempts ?? 5);
15586
15624
  const delayMs = opts.delayMs ?? 300;
@@ -16500,7 +16538,7 @@ function checkRunbookCoverage(docs, base, git3) {
16500
16538
  const globs = lines2.slice(heading.start, markerEnd - 1).flatMap((text) => /<!--\s*runbook-covers:\s*(.*?)\s*-->/.exec(text)?.[1].split(",").map((s) => s.trim()) ?? []);
16501
16539
  const affected = changed.filter((path2) => globs.some((glob) => matches(glob, path2)));
16502
16540
  if (affected.length && !ranges.some((r) => r.count > 0 && r.start < end && r.start + r.count > heading.start)) {
16503
- findings.push({ kind: "runbook-section-stale", doc, line: heading.start, detail: `${heading.title}: changed ${affected.join(", ")}; update this section or declare Runbook-Unaffected: <reason>` });
16541
+ findings.push({ kind: "runbook-section-stale", doc, line: heading.start, detail: `${heading.title}: changed ${affected.join(", ")}; update this section or declare Runbook-Unaffected: <reason>`, section: { heading: heading.title, level: heading.level, startLine: heading.start, endLine: end - 1 }, sources: affected });
16504
16542
  }
16505
16543
  }
16506
16544
  }
@@ -18919,10 +18957,10 @@ var rollout_plan_default = {
18919
18957
  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)."
18920
18958
  },
18921
18959
  baseline: {
18922
- version: "4.5.161",
18923
- tag: "v4.5.161",
18924
- commit: "236dbfae4af2",
18925
- npm: "@mutmutco/cli@4.5.161"
18960
+ version: "4.5.162",
18961
+ tag: "v4.5.162",
18962
+ commit: "5d12949c1714",
18963
+ npm: "@mutmutco/cli@4.5.162"
18926
18964
  },
18927
18965
  exitCriterion: "fleet-n-of-n",
18928
18966
  hubOnlyShortcut: "forbidden",
@@ -18939,14 +18977,14 @@ var rollout_plan_default = {
18939
18977
  repo: "mutmutco/mmi-hub",
18940
18978
  role: "canary",
18941
18979
  schedule: "train",
18942
- v3Target: "v4.5.161"
18980
+ v3Target: "v4.5.162"
18943
18981
  }
18944
18982
  ],
18945
18983
  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.",
18946
18984
  rollback: {
18947
18985
  independent: true,
18948
- mechanism: "npm dist-tag latest -> 4.5.161 and redeploy the Hub Lambda from tag v4.5.161 (236dbfae4af2); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
18949
- v3Target: "v4.5.161 (@mutmutco/cli@4.5.161, tag commit 236dbfae4af2 \u2014 last known-good release carrying the repo-index v4-only contract)"
18986
+ mechanism: "npm dist-tag latest -> 4.5.162 and redeploy the Hub Lambda from tag v4.5.162 (5d12949c1714); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
18987
+ v3Target: "v4.5.162 (@mutmutco/cli@4.5.162, tag commit 5d12949c1714 \u2014 last known-good release carrying the repo-index v4-only contract)"
18950
18988
  }
18951
18989
  },
18952
18990
  {
@@ -21033,7 +21071,7 @@ async function runMergeTreePreflight(deps, ours, theirs) {
21033
21071
  async function predictMergeConflicts(deps, ours, theirs) {
21034
21072
  return runMergeTreePreflight(deps, ours, theirs);
21035
21073
  }
21036
- async function mergeWithToleratedResolution(deps, sourceRef, label, resolve8, extraTolerated = []) {
21074
+ async function mergeWithToleratedResolution(deps, sourceRef, label, resolve7, extraTolerated = []) {
21037
21075
  try {
21038
21076
  await deps.run("git", ["merge", sourceRef, "--no-edit"]);
21039
21077
  return;
@@ -21047,7 +21085,7 @@ async function mergeWithToleratedResolution(deps, sourceRef, label, resolve8, ex
21047
21085
  unmerged.length === 0 ? `${label} merge failed without conflicted paths \u2014 merge aborted; inspect the repo state and rerun` : `${label} merge conflicts on untolerated path(s): ${blocking.join(", ")} \u2014 merge aborted (the train is misaligned; reconcile the branches via an approved alignment PR, then rerun)`
21048
21086
  );
21049
21087
  }
21050
- await deps.run("git", ["checkout", `--${resolve8}`, "--", ...unmerged]);
21088
+ await deps.run("git", ["checkout", `--${resolve7}`, "--", ...unmerged]);
21051
21089
  await deps.run("git", ["add", "--", ...unmerged]);
21052
21090
  await deps.run("git", ["commit", "--no-edit"]);
21053
21091
  }
@@ -21241,7 +21279,7 @@ var CORRELATE_SKEW_SLACK_MS = 1e4;
21241
21279
  var CORRELATE_PAGE_LIMIT = 50;
21242
21280
  var RUN_CONFIRM_ATTEMPTS = 3;
21243
21281
  var RUN_CONFIRM_DELAY_MS = 1e3;
21244
- var defaultSleep = (ms) => new Promise((resolve8) => setTimeout(resolve8, ms));
21282
+ var defaultSleep = (ms) => new Promise((resolve7) => setTimeout(resolve7, ms));
21245
21283
  function resolveSleep(deps) {
21246
21284
  return deps.sleep ?? defaultSleep;
21247
21285
  }
@@ -21545,7 +21583,7 @@ async function enqueueAlignmentAutoMerge(deps, ctx, prNumber, prUrl, openedNote)
21545
21583
  }
21546
21584
  return { ...base, note: manual };
21547
21585
  }
21548
- var ALIGNMENT_LAND_POLL_MS = 15e3;
21586
+ var ALIGNMENT_LAND_POLL_MS = 6e4;
21549
21587
  var ALIGNMENT_LAND_TIMEOUT_MS = 20 * 6e4;
21550
21588
  async function awaitAlignmentLanded(deps, ctx, leg, base, opts = {}) {
21551
21589
  if (leg.status !== "pr-pending" || !leg.autoMergeEnqueued || leg.prNumber === void 0) return leg;
@@ -23162,7 +23200,7 @@ async function runTenantReconcile(deps, options) {
23162
23200
  };
23163
23201
  }
23164
23202
  function tenantControlWatches(action) {
23165
- return action === "status" || action === "retire" || action === "verify-secrets" || action === "verify-broker" || action === "logs" || action === "run-task";
23203
+ return action === "status" || action === "retire" || action === "undo" || action === "verify-secrets" || action === "verify-broker" || action === "logs" || action === "run-task";
23166
23204
  }
23167
23205
  async function runTenantControl(deps, options) {
23168
23206
  const { repo, stage, action } = options;
@@ -23809,8 +23847,6 @@ var import_node_path23 = require("node:path");
23809
23847
  // src/jerv-cli-spawn.ts
23810
23848
  var import_node_os13 = require("node:os");
23811
23849
  var import_node_path22 = require("node:path");
23812
- init_cli_shared();
23813
- var JERV_CLI_ENTRY = (0, import_node_path22.join)("node_modules", "@jervaise", "jerv-cli", "dist", "index.cjs");
23814
23850
  function pathEnvEntries(pathEnv, platform2 = process.platform) {
23815
23851
  if (platform2 !== "win32") {
23816
23852
  return pathEnv.split(import_node_path22.delimiter).map((e) => e.trim()).filter(Boolean);
@@ -24444,7 +24480,7 @@ function lockTokenPid(token) {
24444
24480
  const m = /^(\d+)-/.exec(token);
24445
24481
  return m ? Number(m[1]) : void 0;
24446
24482
  }
24447
- var sleep = (ms) => new Promise((resolve8) => setTimeout(resolve8, ms));
24483
+ var sleep = (ms) => new Promise((resolve7) => setTimeout(resolve7, ms));
24448
24484
  var IMMEDIATE_RETRY_BUDGET = 3;
24449
24485
  var FileLockBusyError = class extends Error {
24450
24486
  lockPath;
@@ -25320,7 +25356,7 @@ async function defaultWatchRun(deps, runId, repo) {
25320
25356
  }
25321
25357
  let confirmError;
25322
25358
  for (let attempt = 0; attempt < RUN_CONFIRM_ATTEMPTS2; attempt++) {
25323
- if (attempt > 0) await new Promise((resolve8) => setTimeout(resolve8, RUN_CONFIRM_DELAY_MS2));
25359
+ if (attempt > 0) await new Promise((resolve7) => setTimeout(resolve7, RUN_CONFIRM_DELAY_MS2));
25324
25360
  try {
25325
25361
  const parsed = JSON.parse(await deps.run("gh", ["run", "view", String(runId), "--repo", repo, "--json", "status,conclusion"]));
25326
25362
  if (parsed.status === "completed") return parsed.conclusion === "success" ? "success" : "failure";
@@ -26362,11 +26398,11 @@ var defaultDeps = {
26362
26398
  // #5813: the caller supplies that command line. `process.argv` is NOT it — the house shim splices
26363
26399
  // the house root out in place, so re-reading it here re-ran the removed flat alias (`release …`
26364
26400
  // instead of `devops release …`) and the converged child refused with exit 2.
26365
- reexec: (argv) => new Promise((resolve8) => {
26401
+ reexec: (argv) => new Promise((resolve7) => {
26366
26402
  const args = [...argv];
26367
26403
  const child2 = isWin2 ? (0, import_node_child_process8.spawn)("cmd.exe", ["/c", "mmi-cli", ...args], { stdio: "inherit", windowsHide: true }) : (0, import_node_child_process8.spawn)("mmi-cli", args, { stdio: "inherit", windowsHide: true });
26368
- child2.on("exit", (code) => resolve8(code ?? 1));
26369
- child2.on("error", () => resolve8(1));
26404
+ child2.on("exit", (code) => resolve7(code ?? 1));
26405
+ child2.on("error", () => resolve7(1));
26370
26406
  }),
26371
26407
  // stderr only — stdout may be carrying --json output.
26372
26408
  warn: (line) => console.error(line)
@@ -26474,7 +26510,7 @@ var defaultDeps2 = {
26474
26510
  }
26475
26511
  },
26476
26512
  registryWhoami: probeRegistryWhoami,
26477
- sleep: (ms) => new Promise((resolve8) => setTimeout(resolve8, ms)),
26513
+ sleep: (ms) => new Promise((resolve7) => setTimeout(resolve7, ms)),
26478
26514
  env: process.env
26479
26515
  };
26480
26516
  function message(e) {
@@ -32728,6 +32764,7 @@ function applyRemoteScript(items, date) {
32728
32764
 
32729
32765
  // src/box-commands.ts
32730
32766
  init_clean_exit();
32767
+ init_cli_shared();
32731
32768
 
32732
32769
  // src/secrets-commands.ts
32733
32770
  var import_node_fs31 = require("node:fs");
@@ -33603,6 +33640,14 @@ function buildSetDeployPatch(_slug, input) {
33603
33640
  if (!DEPLOY_DOMAIN_RE.test(a)) throw new Error(`org project set-deploy: --alias must be a DNS hostname, got ${JSON.stringify(a)}`);
33604
33641
  }
33605
33642
  const uniqueAliases = [...new Set(aliases)];
33643
+ const redirectFrom = [...new Set((input.redirectFrom ?? []).map((a) => clean4(a)).filter((a) => a !== void 0))];
33644
+ for (const entry of redirectFrom) {
33645
+ const [host, target, extra] = entry.split("=");
33646
+ if (!host || !DEPLOY_DOMAIN_RE.test(host) || extra !== void 0 || target !== void 0 && !DEPLOY_DOMAIN_RE.test(target)) throw new Error(`org project set-deploy: invalid --redirect-from ${JSON.stringify(entry)}`);
33647
+ const serving = [domain, ...uniqueAliases].map((a) => a?.toLowerCase());
33648
+ if (serving.includes(host.toLowerCase())) throw new Error("org project set-deploy: --redirect-from must not equal the primary domain or an alias");
33649
+ if (target !== void 0 && domain !== void 0 && (input.aliases !== void 0 || input.clearAliases) && !serving.includes(target.toLowerCase())) throw new Error("org project set-deploy: --redirect-from target must be the primary domain or an alias");
33650
+ }
33606
33651
  if (input.clearAliases && uniqueAliases.length) {
33607
33652
  throw new Error("org project set-deploy: --clear-aliases cannot be combined with --alias (they contradict)");
33608
33653
  }
@@ -33642,6 +33687,7 @@ function buildSetDeployPatch(_slug, input) {
33642
33687
  // #2986: an explicit `[]` is what the route reads as "clear them"; omitting the key is what it reads as
33643
33688
  // "keep them". Those are different instructions and they now have different flags.
33644
33689
  ...input.clearAliases ? { aliases: [] } : uniqueAliases.length ? { aliases: uniqueAliases } : {},
33690
+ ...input.redirectFrom !== void 0 ? { redirectFrom } : {},
33645
33691
  ...input.noEnvFile !== void 0 ? { noEnvFile: input.noEnvFile } : {},
33646
33692
  // #7353: an explicit `[]` is what the route reads as "clear them"; omitting the key is what it reads as
33647
33693
  // "keep them" — same patch contract as aliases (#2986/#2984).
@@ -34404,6 +34450,48 @@ ${SSH_RECIPE_AGENT_NOTE}`);
34404
34450
  await failGraceful(e.message);
34405
34451
  }
34406
34452
  });
34453
+ box.command("previews <name>").description("inspect or configure a box preview port range in BOX#<sshHost>/META (#8444; master-only, dry-run unless --apply)").option("--port-start <port>", "first port in the dedicated preview interval (>= 1024)", (v) => Number(v)).option("--cap <n>", "maximum concurrent PR previews on this box (1..20, default 3)", (v) => Number(v)).option("--apply", "write the preview port range to BOX#<sshHost>/META (default: dry-run)").option("--json", "machine-readable output").action(async (name, o) => {
34454
+ try {
34455
+ const { boxes, incomplete } = await fetchInventory();
34456
+ const result = lookupBox(name, boxes, incomplete);
34457
+ if (result.kind === "unknown") {
34458
+ warnIncomplete(result.reasons);
34459
+ await failGraceful(
34460
+ `runtime box: cannot confirm whether '${name}' exists \u2014 part of the inventory could not be read (see above). Refusing to answer "no such box" from a list I know is incomplete.`
34461
+ );
34462
+ return;
34463
+ }
34464
+ if (result.kind === "absent") {
34465
+ await failGraceful(`runtime box: no box named '${name}'. Known boxes: ${result.known.join(", ") || "(none)"}`);
34466
+ return;
34467
+ }
34468
+ const found = result.box;
34469
+ if (!found.address) {
34470
+ await failGraceful(`runtime box previews: box '${found.name}' has no public IPv4 address`);
34471
+ return;
34472
+ }
34473
+ const status = await boxPreviewsForHost(
34474
+ found.address,
34475
+ { portStart: o.portStart, cap: o.cap, apply: o.apply === true },
34476
+ registryClientDeps(await loadConfig())
34477
+ );
34478
+ const fmt = (r) => r ? `${r.ports.start}..${r.ports.end} (portStart=${r.previewPortStart}, cap=${r.previewCap})` : "(unconfigured)";
34479
+ if (o.json) {
34480
+ console.log(JSON.stringify({ ...status, box: found.name, address: found.address, incomplete }, null, 2));
34481
+ } else {
34482
+ console.log(
34483
+ `${found.name} (${found.address}) \u2014 ${status.apply ? "applied" : "dry-run"}
34484
+ current: ${fmt(status.current)}
34485
+ planned: ${fmt(status.planned)}
34486
+ live reservations: ${status.activeReservations}`
34487
+ );
34488
+ warnIncomplete(incomplete);
34489
+ }
34490
+ if (incomplete.length) process.exitCode = 1;
34491
+ } catch (e) {
34492
+ await failGraceful(e.message);
34493
+ }
34494
+ });
34407
34495
  box.command("control <name>").description("box-side control files (#7402): --plan (default, read-only) reports drift from the repo; --apply --only <src> installs with backups").option("--plan", "read-only drift report (the default)").option("--apply", "install the files named by --only on this box, backing up each previous copy").option("--only <src>", "with --apply: a manifest src to install (repeatable; approval is file by file)", (v, acc) => [...acc, v], []).option("--dest <path>", "with --apply: a destination of a multi-target --only source to install (repeatable, #7409)", (v, acc) => [...acc, v], []).option("--all-targets", "with --apply: install a multi-target --only source to every one of its destinations on this box").option("--ref <ref>", "git ref the repo content is read from", "origin/development").option("--json", "machine-readable output").action(async (name, o) => {
34408
34496
  try {
34409
34497
  if (o.apply && o.plan) throw new UsageError("box control: --plan and --apply are exclusive");
@@ -38327,7 +38415,7 @@ Each module must ship a live release that bind-mounts <deployPath>/data read-onl
38327
38415
  let running = 0;
38328
38416
  const waiting = [];
38329
38417
  const results = await Promise.allSettled(record.members.map(async (member) => {
38330
- if (running >= 4) await new Promise((resolve8) => waiting.push(resolve8));
38418
+ if (running >= 4) await new Promise((resolve7) => waiting.push(resolve7));
38331
38419
  else running++;
38332
38420
  try {
38333
38421
  if ((await deps.head(member.repo, "development"))?.toLowerCase() !== member.development.toLowerCase())
@@ -38998,7 +39086,7 @@ function clean3(out) {
38998
39086
  return out.trim();
38999
39087
  }
39000
39088
  function sleeper(deps) {
39001
- return deps.sleep ?? ((ms) => new Promise((resolve8) => setTimeout(resolve8, ms)));
39089
+ return deps.sleep ?? ((ms) => new Promise((resolve7) => setTimeout(resolve7, ms)));
39002
39090
  }
39003
39091
  function normalizeHotfixVersion(input) {
39004
39092
  const m = /^v?(\d+\.\d+\.\d+)$/.exec(input.trim());
@@ -40672,7 +40760,7 @@ async function updatePrHeadFromBase(input) {
40672
40760
  } catch (e) {
40673
40761
  throw new Error(`GitHub could not update ${input.head} from ${input.base} (${e.message.trim()}) \u2014 ${remedy}`);
40674
40762
  }
40675
- const sleep2 = input.sleep ?? ((ms) => new Promise((resolve8) => setTimeout(resolve8, ms)));
40763
+ const sleep2 = input.sleep ?? ((ms) => new Promise((resolve7) => setTimeout(resolve7, ms)));
40676
40764
  for (let attempt = 0; attempt < PR_HEAD_UPDATE_API_READ_RETRIES; attempt++) {
40677
40765
  const to = await readHead2();
40678
40766
  if (to && to !== from) return { from, to, base: input.base, via: "update-branch-api" };
@@ -40697,7 +40785,7 @@ async function mergeAutoWithTransientRetry(prNumber, repo, deps) {
40697
40785
  if (first.mergeStatus !== "failed") return first;
40698
40786
  const ready = await deps.probeMergeReady(prNumber, repo).catch(() => ({ open: false, mergeable: false, checksPassing: false }));
40699
40787
  if (!ready.open || !ready.mergeable || !ready.checksPassing) return first;
40700
- const sleep2 = deps.sleep ?? ((ms) => new Promise((resolve8) => setTimeout(resolve8, ms)));
40788
+ const sleep2 = deps.sleep ?? ((ms) => new Promise((resolve7) => setTimeout(resolve7, ms)));
40701
40789
  await sleep2(PR_LAND_MERGE_RETRY_DELAY_MS);
40702
40790
  const retried = await deps.mergeAuto(prNumber, repo);
40703
40791
  if (retried.mergeStatus !== "failed") return retried;
@@ -40708,7 +40796,7 @@ var AUTO_MERGE_CONFIRM_DELAY_MS = 15e3;
40708
40796
  async function confirmAutoMergeEnqueued(deps, options) {
40709
40797
  const retries = options?.retries ?? AUTO_MERGE_CONFIRM_RETRIES;
40710
40798
  const delayMs = options?.delayMs ?? AUTO_MERGE_CONFIRM_DELAY_MS;
40711
- const sleep2 = deps.sleep ?? ((ms) => new Promise((resolve8) => setTimeout(resolve8, ms)));
40799
+ const sleep2 = deps.sleep ?? ((ms) => new Promise((resolve7) => setTimeout(resolve7, ms)));
40712
40800
  for (let attempt = 0; attempt < retries; attempt++) {
40713
40801
  if (await deps.readMerged().catch(() => false)) return "merged";
40714
40802
  const stuck = await deps.readAutoMergeRequest().then((s) => s.trim()).catch(() => "");
@@ -40725,7 +40813,7 @@ async function confirmAutoMergeEnqueued(deps, options) {
40725
40813
  async function readGhPrStateWithRetry(fetchState, options) {
40726
40814
  const retries = options?.retries ?? PR_LAND_STATE_READ_RETRIES;
40727
40815
  const delayMs = options?.delayMs ?? PR_LAND_STATE_READ_DELAY_MS;
40728
- const sleep2 = options?.sleep ?? ((ms) => new Promise((resolve8) => setTimeout(resolve8, ms)));
40816
+ const sleep2 = options?.sleep ?? ((ms) => new Promise((resolve7) => setTimeout(resolve7, ms)));
40729
40817
  let lastError = "empty state";
40730
40818
  for (let attempt = 0; attempt < retries; attempt++) {
40731
40819
  try {
@@ -40809,7 +40897,7 @@ async function runPrLand(prNumber, options, deps) {
40809
40897
  return queueMerged(base, deps);
40810
40898
  }
40811
40899
  const deadline = (deps.now ?? (() => Date.now()))() + (options.queueTimeoutMs ?? PR_LAND_QUEUE_TIMEOUT_MS);
40812
- await (deps.sleep ?? ((ms) => new Promise((resolve8) => setTimeout(resolve8, ms))))(deps.pollDelayMs?.(0) ?? PR_LAND_POLL_MS);
40900
+ await (deps.sleep ?? ((ms) => new Promise((resolve7) => setTimeout(resolve7, ms))))(deps.pollDelayMs?.(0) ?? PR_LAND_POLL_MS);
40813
40901
  return pollPrMergeQueue(prNumber, options, deps, base, void 0, deadline);
40814
40902
  }
40815
40903
  if (merge.mergeStatus === "merged") {
@@ -40847,7 +40935,7 @@ function queueMerged(base, deps) {
40847
40935
  }
40848
40936
  async function pollPrMergeQueue(pr, options, deps, base, snapshot, deadlineMs) {
40849
40937
  const now = deps.now ?? (() => Date.now());
40850
- const sleep2 = deps.sleep ?? ((ms) => new Promise((resolve8) => setTimeout(resolve8, ms)));
40938
+ const sleep2 = deps.sleep ?? ((ms) => new Promise((resolve7) => setTimeout(resolve7, ms)));
40851
40939
  const timeoutMs = options.queueTimeoutMs ?? PR_LAND_QUEUE_TIMEOUT_MS;
40852
40940
  const deadline = deadlineMs ?? now() + timeoutMs;
40853
40941
  let attempt = 0;
@@ -41990,7 +42078,7 @@ async function composeOverrideBodyFile(prNumber, repoArgs, gh2, opts) {
41990
42078
  async function ghAdminTrueMergeWhenClean(prNumber, repo, opts = {}) {
41991
42079
  const io = opts.io ?? defaultGhMergeAutoIo;
41992
42080
  const sleep2 = opts.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
41993
- const pollMs = opts.pollMs ?? 15e3;
42081
+ const pollMs = opts.pollMs ?? 6e4;
41994
42082
  const deadline = Date.now() + (opts.timeoutMs ?? 20 * 6e4);
41995
42083
  for (; ; ) {
41996
42084
  const raw = await io.gh(["pr", "view", prNumber, "--repo", repo, "--json", "state,mergeStateStatus"], GC_GH_TIMEOUT_MS).catch(() => "");
@@ -44017,7 +44105,7 @@ var import_promises7 = require("node:fs/promises");
44017
44105
  init_cli_shared();
44018
44106
  init_error_codes();
44019
44107
  var GC_GH_TIMEOUT_MS2 = 2e4;
44020
- var CHECKS_WATCH_POLL_MS = 15e3;
44108
+ var CHECKS_WATCH_POLL_MS = 6e4;
44021
44109
  var CHECKS_WATCH_TIMEOUT_MS = 10 * 6e4;
44022
44110
  function prCommentMarker(markerId) {
44023
44111
  return `<!-- mmi-pr-marker:${markerId} -->`;
@@ -44556,7 +44644,7 @@ async function checkPrReview(number, repo, head, deps = {
44556
44644
  }
44557
44645
  }
44558
44646
  function computePrPatchId(number, repo) {
44559
- return new Promise((resolve8, reject) => {
44647
+ return new Promise((resolve7, reject) => {
44560
44648
  const gh2 = (0, import_node_child_process14.spawn)("gh", ["pr", "diff", number, "--repo", repo], { windowsHide: true, stdio: ["ignore", "pipe", "pipe"] });
44561
44649
  const git3 = (0, import_node_child_process14.spawn)("git", ["patch-id", "--stable"], { windowsHide: true, stdio: ["pipe", "pipe", "pipe"] });
44562
44650
  let out = "";
@@ -44594,7 +44682,7 @@ function computePrPatchId(number, repo) {
44594
44682
  if (gitCode !== 0) return reject(new Error(`git patch-id --stable exited ${gitCode}: ${gitErr.trim()}`));
44595
44683
  const id = out.trim().split(/\s+/)[0];
44596
44684
  if (!/^[0-9a-f]{40}$/.test(id ?? "")) return reject(new Error(`patch-id: PR #${number} diff is empty or unreadable (gh: ${ghErr.trim() || "no stderr"})`));
44597
- resolve8(id);
44685
+ resolve7(id);
44598
44686
  };
44599
44687
  gh2.on("close", (code) => {
44600
44688
  ghCode = code;
@@ -44645,7 +44733,7 @@ init_cli_shared();
44645
44733
  var GIT_TIMEOUT_MS2 = 15e3;
44646
44734
  function catFileBatch(root, ref, paths) {
44647
44735
  if (paths.length === 0) return Promise.resolve([]);
44648
- return new Promise((resolve8) => {
44736
+ return new Promise((resolve7) => {
44649
44737
  const chunks = [];
44650
44738
  let settled = false;
44651
44739
  const child2 = (0, import_node_child_process15.spawn)("git", ["-C", root, "cat-file", "--batch", "--buffer"], { windowsHide: true });
@@ -44653,7 +44741,7 @@ function catFileBatch(root, ref, paths) {
44653
44741
  if (settled) return;
44654
44742
  settled = true;
44655
44743
  clearTimeout(timer);
44656
- resolve8(parseCatFileBatch(Buffer.concat(chunks), paths.length));
44744
+ resolve7(parseCatFileBatch(Buffer.concat(chunks), paths.length));
44657
44745
  };
44658
44746
  const timer = setTimeout(() => {
44659
44747
  child2.kill("SIGKILL");
@@ -45078,7 +45166,7 @@ function healthPollIntervalMs() {
45078
45166
  return HEALTH_POLL_INTERVAL_MS;
45079
45167
  }
45080
45168
  function waitForProcessStability(child2, graceMs = earlyExitGraceMs()) {
45081
- return new Promise((resolve8, reject) => {
45169
+ return new Promise((resolve7, reject) => {
45082
45170
  let settled = false;
45083
45171
  const finish = (fn) => {
45084
45172
  if (settled) return;
@@ -45088,7 +45176,7 @@ function waitForProcessStability(child2, graceMs = earlyExitGraceMs()) {
45088
45176
  child2.removeAllListeners("exit");
45089
45177
  fn();
45090
45178
  };
45091
- const timer = setTimeout(() => finish(resolve8), graceMs);
45179
+ const timer = setTimeout(() => finish(resolve7), graceMs);
45092
45180
  child2.on("error", (err) => finish(() => reject(new Error(`stage process failed to start: ${err.message}`))));
45093
45181
  child2.on("exit", (code, signal) => {
45094
45182
  const detail = code != null ? `code ${code}` : signal ? `signal ${signal}` : "unknown reason";
@@ -45281,10 +45369,10 @@ function pickStagePort(range, isFree) {
45281
45369
  throw new Error(`no free stage port in range ${start}-${end} \u2014 every port is in use`);
45282
45370
  }
45283
45371
  function isPortFree(port) {
45284
- return new Promise((resolve8) => {
45372
+ return new Promise((resolve7) => {
45285
45373
  const srv = (0, import_node_net.createServer)();
45286
- srv.once("error", () => resolve8(false));
45287
- srv.once("listening", () => srv.close(() => resolve8(true)));
45374
+ srv.once("error", () => resolve7(false));
45375
+ srv.once("listening", () => srv.close(() => resolve7(true)));
45288
45376
  srv.listen(port, "127.0.0.1");
45289
45377
  });
45290
45378
  }
@@ -45562,7 +45650,7 @@ async function killTree(pid) {
45562
45650
  } catch {
45563
45651
  }
45564
45652
  }
45565
- await new Promise((resolve8) => setTimeout(resolve8, 500));
45653
+ await new Promise((resolve7) => setTimeout(resolve7, 500));
45566
45654
  try {
45567
45655
  process.kill(-pid, "SIGKILL");
45568
45656
  } catch {
@@ -45635,7 +45723,7 @@ async function waitForHealth(url, timeoutMs, anyStatus = false) {
45635
45723
  } catch (e) {
45636
45724
  last = e.message;
45637
45725
  }
45638
- await new Promise((resolve8) => setTimeout(resolve8, healthPollIntervalMs()));
45726
+ await new Promise((resolve7) => setTimeout(resolve7, healthPollIntervalMs()));
45639
45727
  }
45640
45728
  throw new Error(`stage health check timed out for ${url}${last ? ` (${last})` : ""}`);
45641
45729
  }
@@ -45822,7 +45910,7 @@ function unlinkNodeModulesJunction(wtPath) {
45822
45910
  }
45823
45911
  }
45824
45912
  function defaultSleep2(ms) {
45825
- return new Promise((resolve8) => setTimeout(resolve8, ms));
45913
+ return new Promise((resolve7) => setTimeout(resolve7, ms));
45826
45914
  }
45827
45915
  async function removeResidueDirectory(wtPath, options = {}) {
45828
45916
  const { sleep: sleep2 = defaultSleep2, removeEmptyDir = import_node_fs44.rmdirSync, exists = import_node_fs44.existsSync } = options;
@@ -47572,7 +47660,7 @@ ${list}`);
47572
47660
  // #5400: after grace, name "GitHub delivered zero runs" instead of burning the full budget as pending.
47573
47661
  pollHeadWorkflowRunCount: () => waitLoopHeadWorkflowRunCount("pr checks-wait", number, repo),
47574
47662
  baseBranch,
47575
- sleep: (ms) => new Promise((resolve8) => setTimeout(resolve8, ms)),
47663
+ sleep: (ms) => new Promise((resolve7) => setTimeout(resolve7, ms)),
47576
47664
  log: (message2) => console.warn(message2),
47577
47665
  timeoutMs: Math.max(1, budgetMs - (Date.now() - waitStarted)),
47578
47666
  // Liveness on stderr, one line per poll. A silent bounded wait is indistinguishable from a hang, and
@@ -47742,7 +47830,7 @@ ${list}`);
47742
47830
  // #5400: same zero-runs delivery probe as checks-wait — do not burn the land budget on silence.
47743
47831
  pollHeadWorkflowRunCount: () => waitLoopHeadWorkflowRunCount("pr land", prNumber, repo),
47744
47832
  baseBranch: "development",
47745
- sleep: (ms) => new Promise((resolve8) => setTimeout(resolve8, ms)),
47833
+ sleep: (ms) => new Promise((resolve7) => setTimeout(resolve7, ms)),
47746
47834
  log: (message2) => console.warn(message2),
47747
47835
  // `pr land` inherits the same (raised) checks budget, so it needs the same liveness — otherwise the
47748
47836
  // 30m wait is SILENT and reads exactly like the hang #2940 was filed about, only three times longer.
@@ -47794,7 +47882,7 @@ ${list}`);
47794
47882
  } else {
47795
47883
  lastFailure = void 0;
47796
47884
  }
47797
- await new Promise((resolve8) => setTimeout(resolve8, PR_LAND_POLL_MS));
47885
+ await new Promise((resolve7) => setTimeout(resolve7, PR_LAND_POLL_MS));
47798
47886
  }
47799
47887
  if (lastFailure) {
47800
47888
  throw new Error(
@@ -47985,7 +48073,7 @@ ${list}`);
47985
48073
  checksDiagnosticCommand: `gh pr checks ${number} --repo ${repo}`,
47986
48074
  pollHeadWorkflowRunCount: () => waitLoopHeadWorkflowRunCount("pr merge --wait", number, repo),
47987
48075
  baseBranch,
47988
- sleep: (ms) => new Promise((resolve8) => setTimeout(resolve8, ms)),
48076
+ sleep: (ms) => new Promise((resolve7) => setTimeout(resolve7, ms)),
47989
48077
  log: (message2) => console.warn(message2),
47990
48078
  timeoutMs: Math.max(1, budgetMs - (Date.now() - waitStarted)),
47991
48079
  progress: ({ state, elapsedMs, remainingMs }) => console.warn(`pr merge: --wait checks \u2014 ${state}, ${Math.round(elapsedMs / 1e3)}s elapsed, ${Math.round(remainingMs / 6e4)}m left`)
@@ -48075,7 +48163,7 @@ ${list}`);
48075
48163
  }
48076
48164
  if (!o.auto && mergeRejectedBaseBranchModified(message2)) {
48077
48165
  console.warn(`pr merge: the base branch was modified by a concurrent merge \u2014 waiting ${PR_MERGE_BASE_RACE_RETRY_DELAY_MS / 1e3}s and retrying PR #${number}'s merge once (#6052).`);
48078
- await new Promise((resolve8) => setTimeout(resolve8, PR_MERGE_BASE_RACE_RETRY_DELAY_MS));
48166
+ await new Promise((resolve7) => setTimeout(resolve7, PR_MERGE_BASE_RACE_RETRY_DELAY_MS));
48079
48167
  await execFileP("gh", buildPrMergeArgs({ number, repoArgs, method, auto: false, deleteBranch: false, bodyFile }), { timeout: GH_MUTATION_TIMEOUT_MS }).catch((e2) => {
48080
48168
  const m2 = String(e2.message || "");
48081
48169
  if (/already been merged/i.test(m2)) {
@@ -48337,7 +48425,7 @@ ${list}`);
48337
48425
  console.warn(`ci checks-wait: zero-runs probe FAILED (${read.error}) \u2014 not treating as delivery failure this cycle`);
48338
48426
  return null;
48339
48427
  },
48340
- sleep: (ms) => new Promise((resolve8) => setTimeout(resolve8, ms)),
48428
+ sleep: (ms) => new Promise((resolve7) => setTimeout(resolve7, ms)),
48341
48429
  log: (message2) => console.warn(message2),
48342
48430
  timeoutMs: timeoutMs ?? PR_CHECKS_TIMEOUT_MS,
48343
48431
  progress: ({ state, elapsedMs, remainingMs }) => console.warn(`ci checks-wait: ${state} \u2014 ${Math.round(elapsedMs / 1e3)}s elapsed, ${Math.round(remainingMs / 6e4)}m left`)
@@ -48714,7 +48802,7 @@ function hotfixFollowUpLegs(runs, foldPort, alignment, foldNote, deployNote) {
48714
48802
  }
48715
48803
  return legs;
48716
48804
  }
48717
- async function runProjectInfoSyncLeg(cb, repo, sleep2 = (ms) => new Promise((resolve8) => setTimeout(resolve8, ms))) {
48805
+ async function runProjectInfoSyncLeg(cb, repo, sleep2 = (ms) => new Promise((resolve7) => setTimeout(resolve7, ms))) {
48718
48806
  for (let attempt = 0; ; attempt++) {
48719
48807
  try {
48720
48808
  return await cb(repo, true);
@@ -49639,7 +49727,7 @@ function suiteTrainIo(root, summaryFile, memberSummaryFiles = {}, summaryFileTr,
49639
49727
  if (!reuse) await gh(["workflow", "run", "module-rc-publish.yml", "--repo", "mutmutco/MMI-Hub", ...fields.flatMap((f) => ["--field", f])]);
49640
49728
  let candidate = reuse;
49641
49729
  for (let i = 0; !candidate && i < 25; i++) {
49642
- await new Promise((resolve8) => setTimeout(resolve8, 1e3));
49730
+ await new Promise((resolve7) => setTimeout(resolve7, 1e3));
49643
49731
  candidate = (await rcRuns()).find((r) => r.displayTitle === title && Date.parse(r.createdAt) >= since);
49644
49732
  }
49645
49733
  if (!candidate) throw new Error(`workflow dispatch outcome ambiguous for "${title}"; inspect Actions before retrying`);
@@ -49795,7 +49883,7 @@ function suiteTrainIo(root, summaryFile, memberSummaryFiles = {}, summaryFileTr,
49795
49883
  for (let i = 0; ; i++) {
49796
49884
  const pin = await openSuitePinPr({ root, run: (file, args, cwd) => exec(file, args, cwd, GIT_TIMEOUT_MS) }, { repo, tag, stage: "main" });
49797
49885
  if (pin.status !== "pending" || i >= 40) return pin;
49798
- await new Promise((resolve8) => setTimeout(resolve8, 3e4));
49886
+ await new Promise((resolve7) => setTimeout(resolve7, 3e4));
49799
49887
  }
49800
49888
  },
49801
49889
  suiteMapping: async (leadTag) => {
@@ -49877,7 +49965,7 @@ function suiteRcDataProdDeps() {
49877
49965
  const rc = facts.stages.rc;
49878
49966
  return rc?.present ? rc.substrate ?? "an unnamed substrate" : null;
49879
49967
  },
49880
- sleep: (ms) => new Promise((resolve8) => setTimeout(resolve8, ms))
49968
+ sleep: (ms) => new Promise((resolve7) => setTimeout(resolve7, ms))
49881
49969
  };
49882
49970
  }
49883
49971
  function registerSuiteCommands(program3) {
@@ -52665,6 +52753,12 @@ async function runDoctorClean(opts, io, deps) {
52665
52753
  init_cli_shared();
52666
52754
  init_error_codes();
52667
52755
  init_house_map();
52756
+ var PR_PREFLIGHT_STEPS = [
52757
+ { label: "Review the docs and runbook sections affected by the code" },
52758
+ { label: "Update affected instructions, or record a precise Runbook-Unaffected explanation when they genuinely remain unchanged" },
52759
+ { label: "Commit code and documentation first (coverage compares committed base...HEAD)", command: "git commit" },
52760
+ { label: "Run the repository's existing fast local preflight (check:local where declared, otherwise its documented equivalent)", command: "npm run check:local" }
52761
+ ];
52668
52762
  var LOOP_PLAYBOOKS = {
52669
52763
  agent: {
52670
52764
  title: "Agent",
@@ -52676,7 +52770,8 @@ var LOOP_PLAYBOOKS = {
52676
52770
  { label: "Claim board work (board mutation only \u2014 never `oracle issue claim` or the flat `board claim`; ground unknown write routes via `commands` / `explain` first)", command: "mmi-cli oracle board claim <ref>" },
52677
52771
  { label: "Prepare the local workspace through the host surface" },
52678
52772
  { label: "Before broad docs rewrites, follow docs/Guides/doc-ceremony-cut.md" },
52679
- { label: "Apply the repository test policy, then build the touched package", command: "mmi-cli tests policy --base origin/development && npm run build" },
52773
+ { label: "Apply the repository test policy and run only its permitted focused checks, without a broad local build or full suite", command: "mmi-cli tests policy --base origin/development" },
52774
+ ...PR_PREFLIGHT_STEPS,
52680
52775
  { label: "Publish the branch", command: "git push origin <branch>:<branch>" },
52681
52776
  { label: "Open the development-base PR", command: 'mmi-cli devops pr create --title "<title>" --body-file .jerv/PR_BODY.md --base development' },
52682
52777
  { label: "Land to development (waits for checks itself)", command: "mmi-cli devops pr land <PR-number>" },
@@ -52698,6 +52793,7 @@ var LOOP_PLAYBOOKS = {
52698
52793
  "ship-pr": {
52699
52794
  title: "Ship PR",
52700
52795
  steps: [
52796
+ ...PR_PREFLIGHT_STEPS,
52701
52797
  { label: "Publish the branch", command: "git push origin <branch>:<branch>" },
52702
52798
  { label: "Open the development-base PR", command: 'mmi-cli devops pr create --title "<title>" --body-file .jerv/PR_BODY.md --base development' },
52703
52799
  { label: "Land the PR (merge to development \u2014 waits for checks itself)", command: "mmi-cli devops pr land <PR-number>" }
@@ -55856,7 +55952,7 @@ function createRunbookLiveDeps(cliBin) {
55856
55952
  };
55857
55953
  const first = await probe().catch(() => 599);
55858
55954
  if (first < 500) return first;
55859
- await new Promise((resolve8) => setTimeout(resolve8, 2e4));
55955
+ await new Promise((resolve7) => setTimeout(resolve7, 2e4));
55860
55956
  return probe();
55861
55957
  }
55862
55958
  };
@@ -55929,11 +56025,13 @@ function registerEdgeCommands(program3) {
55929
56025
  process.exitCode = 1;
55930
56026
  }
55931
56027
  });
55932
- edge.command("dns <action> <owner/repo> <stage>").description("read (`status`) or create-when-missing (`ensure`, master-only) the proxied Cloudflare A records for a registered DEPLOY# row; existing records are never modified").action(async (action, repo, stage) => {
55933
- if (action !== "status" && action !== "ensure") return fail("runtime edge dns: <action> must be status or ensure");
56028
+ edge.command("dns <action> <owner/repo> <stage>").description("read (`status`), create missing (`ensure`) or guarded delete (`delete`, dry-run unless --apply) stage DNS; writes are master-only").option("--host <host>", "declared stage host to delete").option("--apply", "apply the guarded DNS deletion").action(async (action, repo, stage, options) => {
56029
+ if (!["status", "ensure", "delete"].includes(action)) return fail("runtime edge dns: <action> must be status, ensure or delete");
56030
+ if (action === "delete" && !options.host) return fail("runtime edge dns delete: --host is required");
56031
+ if (action !== "delete" && (options.host || options.apply)) return fail("runtime edge dns: --host and --apply are only for delete");
55934
56032
  if (stage !== "dev" && stage !== "rc" && stage !== "main") return fail("runtime edge dns: <stage> must be dev, rc, or main");
55935
56033
  try {
55936
- const result = await edgeDnsForStage(slugOf(repo), stage, registryClientDeps(await loadConfig()), action === "ensure");
56034
+ const result = await edgeDnsForStage(slugOf(repo), stage, registryClientDeps(await loadConfig()), action === "ensure", action === "delete" ? { host: options.host, apply: options.apply === true } : void 0);
55937
56035
  console.log(JSON.stringify(result, null, 2));
55938
56036
  if (result.required && result.complete === false) process.exitCode = 1;
55939
56037
  } catch (e) {
@@ -56673,8 +56771,8 @@ function mmiDoctorDeps(opts = {}) {
56673
56771
  });
56674
56772
  const raced = await Promise.race([
56675
56773
  work.then((r) => ({ ...r, timedOut: false })),
56676
- new Promise((resolve8) => {
56677
- ceiling = setTimeout(() => resolve8({ timedOut: true, scanned: 0, findings: 0, fixed: 0, failed: 0 }), BOARD_DOCTOR_TIMEOUT_MS);
56774
+ new Promise((resolve7) => {
56775
+ ceiling = setTimeout(() => resolve7({ timedOut: true, scanned: 0, findings: 0, fixed: 0, failed: 0 }), BOARD_DOCTOR_TIMEOUT_MS);
56678
56776
  })
56679
56777
  ]);
56680
56778
  if (raced.timedOut) return { scanned: 0, findings: 0, fixed: 0, failed: 0, timedOut: true };
@@ -56707,8 +56805,8 @@ function mmiDoctorDeps(opts = {}) {
56707
56805
  incomplete: nb.incomplete,
56708
56806
  timedOut: false
56709
56807
  })),
56710
- new Promise((resolve8) => {
56711
- ceiling = setTimeout(() => resolve8({ driftLines: [], incomplete: [], timedOut: true }), SCHEDULES_DRIFT_TIMEOUT_MS);
56808
+ new Promise((resolve7) => {
56809
+ ceiling = setTimeout(() => resolve7({ driftLines: [], incomplete: [], timedOut: true }), SCHEDULES_DRIFT_TIMEOUT_MS);
56712
56810
  })
56713
56811
  ]);
56714
56812
  if (!raced.timedOut && raced.incomplete.length === 0) writeSchedulesDriftCache(cachePath, raced.driftLines);
@@ -56988,7 +57086,6 @@ function envelopeAwareWriteErr(str2) {
56988
57086
  }
56989
57087
  writeHumanParseError(str2);
56990
57088
  }
56991
- var INVOKED_ARGV3 = process.argv.slice(2);
56992
57089
  var program2 = new Command();
56993
57090
  program2.name("mmi-cli").description("MMI Future Hub CLI \u2014 the org control plane for agentic coding.").version(resolveClientVersion()).configureOutput({ writeErr: envelopeAwareWriteErr }).showHelpAfterError(PARSE_HINT_SENTINEL);
56994
57091
  program2.addHelpText(
@@ -57013,6 +57110,8 @@ function appActorDeps() {
57013
57110
  }
57014
57111
  setPollTokenProvider(async () => activeAppToken() ?? pollTokenOrUndefined(appActorDeps()));
57015
57112
  setGhReadTokenProvider(pollToken);
57113
+ var filingToken;
57114
+ setGhWriteTokenProvider(() => filingToken ??= mintFilingWriteToken(appActorDeps()).then((t) => t.token, () => void 0));
57016
57115
  setStatusWriteTokenProvider(() => mintStatusWriteToken(appActorDeps()));
57017
57116
  var registerPreAction = Reflect.get(program2, ["h", "ook"].join(""));
57018
57117
  registerPreAction.call(program2, "preAction", async (_thisCommand, actionCommand) => {
@@ -57090,6 +57189,15 @@ Dry run: pass --confirm-data-loss ${repo}/${stage} to decommission.`);
57090
57189
  return failGraceful(`runtime tenant control: ${e.message}`);
57091
57190
  }
57092
57191
  });
57192
+ 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) => {
57193
+ try {
57194
+ const result = await runTenantControl(trainApplyDeps(), { repo, stage, action: "undo", watch: true });
57195
+ printLine(o.json ? JSON.stringify(result, null, 2) : renderTenantControl(result));
57196
+ if (result.conclusion === "failure") return failGraceful(`runtime tenant undo ${stage}: ${result.category ?? "failed"} \u2014 ${result.note}`);
57197
+ } catch (e) {
57198
+ return failGraceful(`runtime tenant undo: ${e.message}`);
57199
+ }
57200
+ });
57093
57201
  var tenantArtifact = tenant.command("artifact").description("private stage-bound inputs for declared tenant tasks");
57094
57202
  tenantArtifact.command("put <owner/repo> <stage> <path>").description("upload a private file through a short-lived Hub grant; prints only an opaque artifact id").option("--json", "machine-readable output").action(async (repo, stage, path2, o) => {
57095
57203
  try {
@@ -57532,7 +57640,7 @@ fullTrack.command("readiness <owner/repo>").description("aggregate branch topolo
57532
57640
  console.log(JSON.stringify(report, null, 2));
57533
57641
  if (!report.rcand.canApply) process.exitCode = 1;
57534
57642
  });
57535
- project.command("set-deploy [owner/repo]").description("patch a tenant DEPLOY row \u2014 project-admin may set only --no-env-file/--compose-profiles on their own existing dev/rc row; master may seed/change all coords and main; defaults to the current repo").addOption(new Option("--stage <stage>", "dev | rc | main").makeOptionMandatory().choices(["dev", "rc", "main"])).option("--ssh-host <host>", "the box address the deploy ssh-es into; omit to keep the stored value (required only for a NEW hetzner-ssh row \u2014 `mmi-cli devops runtime box list` finds it)").option("--ssh-user <user>", "ssh user; omit to leave the row unchanged (default root on a new row)").option("--port <port>", "loopback port the container binds / Caddy upstream (1..65535); omit to leave the row unchanged").addOption(new Option("--substrate <substrate>", "hetzner-ssh | s3-cloudfront; omit to leave the row unchanged").choices([...DEPLOY_SUBSTRATES])).option("--deploy-role-arn <arn>", "s3-cloudfront: the scoped OIDC deployer role tenant-deploy.yml assumes for this stage").option("--app-bucket <bucket>", "s3-cloudfront: the S3 bucket the built dist/ is synced into").option("--distribution-id <id>", "s3-cloudfront: the CloudFront distribution invalidated after the sync").option("--distribution-domain <domain>", "s3-cloudfront: the host the post-deploy security-header proof is measured against").option("--deploy-path <path>", "on-box per-stage release root; omit to leave the row unchanged (default /opt/mmi/<slug>/<stage> on a new row)").option("--service <name>", "systemd/compose service name; omit to leave the row unchanged (default the slug on a new row)").option("--domain <domain>", "canonical serving host; omit to leave the row unchanged").option("--alias <domain...>", "extra serving hostname the box Caddy answers (repeatable); omit to leave the stored aliases unchanged").option("--clear-aliases", "remove EVERY serving alias from the row (#2986) \u2014 omitting --alias preserves them, so clearing needs saying out loud").option("--no-env-file <bool>", "set the DEPLOY# fileless flag (true|false) \u2014 own-repo project-admin dev/rc; master main; true = env passthrough with no .env symlink").option("--compose-profiles <profiles>", "comma-separated compose profiles to enable for this stage (e.g. huddles,slack-bridge); omit to leave unchanged").option("--clear-compose-profiles", "remove EVERY compose profile from the row \u2014 omitting --compose-profiles preserves them, so clearing needs saying out loud").option("--force", "explicit recovery override: skip fileless-compose verification only after independent proof").option("--json", "machine-readable output").action(async (repoOrSlug, o) => {
57643
+ project.command("set-deploy [owner/repo]").description("patch a tenant DEPLOY row \u2014 project-admin may set only --no-env-file/--compose-profiles on their own existing dev/rc row; master may seed/change all coords and main; defaults to the current repo").addOption(new Option("--stage <stage>", "dev | rc | main").makeOptionMandatory().choices(["dev", "rc", "main"])).option("--ssh-host <host>", "the box address the deploy ssh-es into; omit to keep the stored value (required only for a NEW hetzner-ssh row \u2014 `mmi-cli devops runtime box list` finds it)").option("--ssh-user <user>", "ssh user; omit to leave the row unchanged (default root on a new row)").option("--port <port>", "loopback port the container binds / Caddy upstream (1..65535); omit to leave the row unchanged").addOption(new Option("--substrate <substrate>", "hetzner-ssh | s3-cloudfront; omit to leave the row unchanged").choices([...DEPLOY_SUBSTRATES])).option("--deploy-role-arn <arn>", "s3-cloudfront: the scoped OIDC deployer role tenant-deploy.yml assumes for this stage").option("--app-bucket <bucket>", "s3-cloudfront: the S3 bucket the built dist/ is synced into").option("--distribution-id <id>", "s3-cloudfront: the CloudFront distribution invalidated after the sync").option("--distribution-domain <domain>", "s3-cloudfront: the host the post-deploy security-header proof is measured against").option("--deploy-path <path>", "on-box per-stage release root; omit to leave the row unchanged (default /opt/mmi/<slug>/<stage> on a new row)").option("--service <name>", "systemd/compose service name; omit to leave the row unchanged (default the slug on a new row)").option("--domain <domain>", "canonical serving host; omit to leave the row unchanged").option("--alias <domain...>", "extra serving hostname the box Caddy answers (repeatable); omit to leave the stored aliases unchanged").option("--redirect-from <host>", "permanent redirect host or host=target (primary or alias); repeatable", (host, hosts) => [...hosts, host], []).option("--clear-aliases", "remove EVERY serving alias from the row (#2986) \u2014 omitting --alias preserves them, so clearing needs saying out loud").option("--no-env-file <bool>", "set the DEPLOY# fileless flag (true|false) \u2014 own-repo project-admin dev/rc; master main; true = env passthrough with no .env symlink").option("--compose-profiles <profiles>", "comma-separated compose profiles to enable for this stage (e.g. huddles,slack-bridge); omit to leave unchanged").option("--clear-compose-profiles", "remove EVERY compose profile from the row \u2014 omitting --compose-profiles preserves them, so clearing needs saying out loud").option("--force", "explicit recovery override: skip fileless-compose verification only after independent proof").option("--json", "machine-readable output").action(async (repoOrSlug, o) => {
57536
57644
  const cfg = await loadConfig();
57537
57645
  let target;
57538
57646
  try {
@@ -57587,6 +57695,7 @@ ${filelessTransitionGuide(target, o.stage)}`
57587
57695
  distributionId: o.distributionId,
57588
57696
  distributionDomain: o.distributionDomain,
57589
57697
  aliases: o.alias,
57698
+ redirectFrom: o.redirectFrom?.length ? o.redirectFrom : void 0,
57590
57699
  clearAliases: o.clearAliases,
57591
57700
  noEnvFile,
57592
57701
  composeProfiles,