@mutmutco/cli 4.1.3 → 4.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/main.cjs CHANGED
@@ -151,19 +151,19 @@ function hardExit(code) {
151
151
  process.exit(code);
152
152
  }
153
153
  function flushStream(stream) {
154
- return new Promise((resolve5) => {
154
+ return new Promise((resolve6) => {
155
155
  try {
156
- stream.write("", () => resolve5());
156
+ stream.write("", () => resolve6());
157
157
  } catch {
158
- resolve5();
158
+ resolve6();
159
159
  }
160
160
  });
161
161
  }
162
162
  async function flushStdio(timeoutMs = STDIO_FLUSH_TIMEOUT_MS) {
163
163
  await Promise.race([
164
164
  Promise.all([flushStream(process.stdout), flushStream(process.stderr)]),
165
- new Promise((resolve5) => {
166
- setTimeout(resolve5, timeoutMs).unref?.();
165
+ new Promise((resolve6) => {
166
+ setTimeout(resolve6, timeoutMs).unref?.();
167
167
  })
168
168
  ]);
169
169
  }
@@ -171,7 +171,7 @@ async function cleanExit(code) {
171
171
  process.exitCode = code;
172
172
  await closeHttpPool();
173
173
  await flushStdio();
174
- await new Promise((resolve5) => setImmediate(resolve5));
174
+ await new Promise((resolve6) => setImmediate(resolve6));
175
175
  return void 0;
176
176
  }
177
177
  async function finishCliRun(watchdogMs = CLI_EXIT_WATCHDOG_MS) {
@@ -476,7 +476,7 @@ function createGitHubClient(options = {}) {
476
476
  if (!res.ok) throw await errorFromResponse(res);
477
477
  return res;
478
478
  }
479
- async function parseJson(res) {
479
+ async function parseJson2(res) {
480
480
  if (res.status === 204) return void 0;
481
481
  const text = await res.text();
482
482
  if (!text) return void 0;
@@ -485,14 +485,14 @@ function createGitHubClient(options = {}) {
485
485
  return {
486
486
  async rest(method, path2, init) {
487
487
  const res = await request(method, joinUrl(baseUrl, path2), init);
488
- return parseJson(res);
488
+ return parseJson2(res);
489
489
  },
490
490
  async restPaginate(path2, init) {
491
491
  const items = [];
492
492
  let url = withPerPage(joinUrl(baseUrl, path2));
493
493
  while (url) {
494
494
  const res = await request("GET", url, init);
495
- const page = await parseJson(res);
495
+ const page = await parseJson2(res);
496
496
  if (!Array.isArray(page)) {
497
497
  throw new GitHubApiError(
498
498
  `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.`,
@@ -509,7 +509,7 @@ function createGitHubClient(options = {}) {
509
509
  ...init,
510
510
  body: { query, ...variables ? { variables } : {} }
511
511
  });
512
- const parsed = await parseJson(res);
512
+ const parsed = await parseJson2(res);
513
513
  if (parsed?.errors?.length) {
514
514
  const message = parsed.errors.map((e) => e.message ?? e.type ?? "unknown GraphQL error").join("; ");
515
515
  const rateLimited = parsed.errors.some((e) => e.type === "RATE_LIMITED" || /rate limit/i.test(e.message ?? ""));
@@ -572,7 +572,7 @@ async function fetchWithRetry(fetchImpl, url, init, opts = {}) {
572
572
  const attempts = opts.attempts ?? 3;
573
573
  const baseDelayMs = opts.baseDelayMs ?? 250;
574
574
  const retryOn = opts.retryOn ?? ((res) => res.status >= 500);
575
- const sleep2 = opts.sleep ?? ((ms) => new Promise((resolve5) => setTimeout(resolve5, ms)));
575
+ const sleep2 = opts.sleep ?? ((ms) => new Promise((resolve6) => setTimeout(resolve6, ms)));
576
576
  let lastErr;
577
577
  for (let i = 0; i < attempts; i++) {
578
578
  const isLast = i === attempts - 1;
@@ -810,6 +810,8 @@ var init_house_map = __esm({
810
810
  // this repo's process-spawn contract
811
811
  tests: "core",
812
812
  // this repo's test-policy contract
813
+ dist: "core",
814
+ // this repo's committed dist/BOM drift receipt (#5576)
813
815
  doctor: "core",
814
816
  stage: "core",
815
817
  plugin: "core",
@@ -859,8 +861,8 @@ async function readStdin(opts = {}) {
859
861
  })().catch(() => {
860
862
  });
861
863
  let timer;
862
- const timeout = new Promise((resolve5) => {
863
- timer = setTimeout(resolve5, timeoutMs);
864
+ const timeout = new Promise((resolve6) => {
865
+ timer = setTimeout(resolve6, timeoutMs);
864
866
  });
865
867
  try {
866
868
  await Promise.race([drain, timeout]);
@@ -899,12 +901,12 @@ function killProcessTree(pid) {
899
901
  function execFileHard(file, args, options) {
900
902
  const { timeout, step, ...rest } = options;
901
903
  const started = Date.now();
902
- return new Promise((resolve5, reject) => {
904
+ return new Promise((resolve6, reject) => {
903
905
  const child2 = (0, import_node_child_process3.execFile)(file, args, { encoding: "utf8", windowsHide: true, ...rest, timeout: 0 }, (error, stdout, stderr) => {
904
906
  clearTimeout(timer);
905
907
  if (expired) return;
906
908
  if (error) reject(error);
907
- else resolve5({ stdout: String(stdout), stderr: String(stderr) });
909
+ else resolve6({ stdout: String(stdout), stderr: String(stderr) });
908
910
  });
909
911
  let expired = false;
910
912
  const timer = setTimeout(() => {
@@ -1331,6 +1333,142 @@ var init_plugin_guard = __esm({
1331
1333
  }
1332
1334
  });
1333
1335
 
1336
+ // src/kimi-registration.ts
1337
+ function validateKimiInstalledPlugin(document, pluginRoot, id) {
1338
+ if (!document || typeof document !== "object" || Array.isArray(document)) return "malformed-document";
1339
+ const plugins = document.plugins;
1340
+ if (!Array.isArray(plugins)) return "malformed-document";
1341
+ const entry = plugins.find(
1342
+ (candidate2) => candidate2 && typeof candidate2 === "object" && !Array.isArray(candidate2) && candidate2.id === id
1343
+ );
1344
+ if (!entry) return "absent-record";
1345
+ const record = entry;
1346
+ if (typeof record.root !== "string") return "malformed-record";
1347
+ if (record.root !== pluginRoot) return "foreign-root";
1348
+ return record.enabled ? "healthy" : "disabled";
1349
+ }
1350
+ var init_kimi_registration = __esm({
1351
+ "src/kimi-registration.ts"() {
1352
+ "use strict";
1353
+ }
1354
+ });
1355
+
1356
+ // src/host-doc-lock.ts
1357
+ function sharedDocLockPath(documentPath) {
1358
+ return `${documentPath}.jerv-lock`;
1359
+ }
1360
+ function lockContent(options) {
1361
+ return `${JSON.stringify(options, null, 2)}
1362
+ `;
1363
+ }
1364
+ function readHolder(lockPath) {
1365
+ try {
1366
+ const parsed = JSON.parse((0, import_node_fs18.readFileSync)(lockPath, "utf8"));
1367
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return null;
1368
+ return parsed;
1369
+ } catch {
1370
+ return null;
1371
+ }
1372
+ }
1373
+ function lockAgeMs(lockPath, now) {
1374
+ const holder = readHolder(lockPath);
1375
+ const declared = typeof holder?.acquiredAt === "string" ? Date.parse(holder.acquiredAt) : Number.NaN;
1376
+ if (!Number.isNaN(declared)) return Math.max(0, now - declared);
1377
+ try {
1378
+ return Math.max(0, now - (0, import_node_fs18.statSync)(lockPath).mtimeMs);
1379
+ } catch {
1380
+ return 0;
1381
+ }
1382
+ }
1383
+ function sharedDocLockTimeoutReceipt(documentPath, lockPath, holder, waitedMs) {
1384
+ const named = holder && typeof holder === "object";
1385
+ const who = named ? `held by ${String(holder.owner ?? "unknown")} (pid ${String(holder.pid ?? "?")}, host ${String(holder.host ?? "?")})` : `held by an unreadable lock at ${lockPath}`;
1386
+ return `shared-document lock timeout: ${documentPath} is ${who} \u2014 waited ${waitedMs}ms, nothing was written \u2014 retry next tick or remove ${lockPath} if stale`;
1387
+ }
1388
+ function acquireSharedDocLock(documentPath, options) {
1389
+ const lockPath = sharedDocLockPath(documentPath);
1390
+ const staleAfterMs = options.staleAfterMs ?? SHARED_DOC_LOCK_STALE_MS;
1391
+ const attempts = options.attempts ?? SHARED_DOC_LOCK_ATTEMPTS;
1392
+ const intervalMs = options.intervalMs ?? SHARED_DOC_LOCK_INTERVAL_MS;
1393
+ const identity = { owner: options.owner, pid: process.pid, host: options.host ?? (0, import_node_os6.hostname)(), acquiredAt: (/* @__PURE__ */ new Date()).toISOString() };
1394
+ const content = lockContent(identity);
1395
+ const sleep2 = (ms) => {
1396
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
1397
+ };
1398
+ for (let attempt = 0; attempt < attempts; attempt += 1) {
1399
+ let fd;
1400
+ try {
1401
+ fd = (0, import_node_fs18.openSync)(lockPath, "wx");
1402
+ (0, import_node_fs18.writeSync)(fd, content);
1403
+ (0, import_node_fs18.closeSync)(fd);
1404
+ return {
1405
+ documentPath,
1406
+ lockPath,
1407
+ owner: identity.owner,
1408
+ pid: identity.pid,
1409
+ host: identity.host,
1410
+ acquiredAt: identity.acquiredAt,
1411
+ release: () => releaseSharedDocLock({ lockPath, owner: identity.owner, pid: identity.pid })
1412
+ };
1413
+ } catch (error) {
1414
+ if (fd !== void 0) {
1415
+ try {
1416
+ (0, import_node_fs18.closeSync)(fd);
1417
+ } catch {
1418
+ }
1419
+ try {
1420
+ (0, import_node_fs18.unlinkSync)(lockPath);
1421
+ } catch {
1422
+ }
1423
+ }
1424
+ if (error?.code !== "EEXIST") throw error;
1425
+ }
1426
+ if (lockAgeMs(lockPath, Date.now()) >= staleAfterMs) {
1427
+ const aside = `${lockPath}.stale-${Date.now()}`;
1428
+ try {
1429
+ (0, import_node_fs18.renameSync)(lockPath, aside);
1430
+ } catch {
1431
+ continue;
1432
+ }
1433
+ try {
1434
+ (0, import_node_fs18.rmSync)(aside, { force: true });
1435
+ } catch {
1436
+ }
1437
+ continue;
1438
+ }
1439
+ if (attempt < attempts - 1) sleep2(intervalMs);
1440
+ }
1441
+ throw new Error(sharedDocLockTimeoutReceipt(documentPath, lockPath, readHolder(lockPath), Math.max(0, attempts * intervalMs)));
1442
+ }
1443
+ function releaseSharedDocLock(handle) {
1444
+ try {
1445
+ const holder = readHolder(handle.lockPath);
1446
+ if (holder && holder.owner === handle.owner && Number(holder.pid) === handle.pid) {
1447
+ (0, import_node_fs18.unlinkSync)(handle.lockPath);
1448
+ }
1449
+ } catch {
1450
+ }
1451
+ }
1452
+ function withSharedDocLock(documentPath, options, fn) {
1453
+ const lock = acquireSharedDocLock(documentPath, options);
1454
+ try {
1455
+ return fn(lock.documentPath);
1456
+ } finally {
1457
+ lock.release();
1458
+ }
1459
+ }
1460
+ var import_node_fs18, import_node_os6, SHARED_DOC_LOCK_STALE_MS, SHARED_DOC_LOCK_ATTEMPTS, SHARED_DOC_LOCK_INTERVAL_MS;
1461
+ var init_host_doc_lock = __esm({
1462
+ "src/host-doc-lock.ts"() {
1463
+ "use strict";
1464
+ import_node_fs18 = require("node:fs");
1465
+ import_node_os6 = require("node:os");
1466
+ SHARED_DOC_LOCK_STALE_MS = 3e4;
1467
+ SHARED_DOC_LOCK_ATTEMPTS = 50;
1468
+ SHARED_DOC_LOCK_INTERVAL_MS = 100;
1469
+ }
1470
+ });
1471
+
1334
1472
  // src/plugin-guard-io.ts
1335
1473
  var plugin_guard_io_exports = {};
1336
1474
  __export(plugin_guard_io_exports, {
@@ -1364,6 +1502,8 @@ __export(plugin_guard_io_exports, {
1364
1502
  hermesPluginTreeHealthy: () => hermesPluginTreeHealthy,
1365
1503
  installedJervCodePackageVersion: () => installedJervCodePackageVersion,
1366
1504
  kiloConfigListsPlugin: () => kiloConfigListsPlugin,
1505
+ kimiPluginHostEvidence: () => kimiPluginHostEvidence,
1506
+ kimiPluginRegistrationHealth: () => kimiPluginRegistrationHealth,
1367
1507
  kimiPluginTreeHealthy: () => kimiPluginTreeHealthy,
1368
1508
  legacyMmiPiPathEntries: () => legacyMmiPiPathEntries,
1369
1509
  marketplaceAddSupportsRef: () => marketplaceAddSupportsRef,
@@ -1525,12 +1665,12 @@ function surfaceHomeDir(surface) {
1525
1665
  if (surface === "hermes") return ".hermes";
1526
1666
  return ".claude";
1527
1667
  }
1528
- function hermesConfigRoot(env = process.env, home = (0, import_node_os6.homedir)(), platform2 = process.platform) {
1668
+ function hermesConfigRoot(env = process.env, home = (0, import_node_os7.homedir)(), platform2 = process.platform) {
1529
1669
  if (env.HERMES_HOME?.trim()) return env.HERMES_HOME.trim();
1530
1670
  if (platform2 === "win32") return (0, import_node_path16.join)(env.LOCALAPPDATA?.trim() || (0, import_node_path16.join)(home, "AppData", "Local"), "hermes");
1531
1671
  return (0, import_node_path16.join)(home, ".hermes");
1532
1672
  }
1533
- function surfaceConfigRoot(surface, env = process.env, home = (0, import_node_os6.homedir)()) {
1673
+ function surfaceConfigRoot(surface, env = process.env, home = (0, import_node_os7.homedir)()) {
1534
1674
  if (surface === "codex") return env.CODEX_HOME?.trim() || (0, import_node_path16.join)(home, ".codex");
1535
1675
  if (surface === "kimi") return env.KIMI_CODE_HOME?.trim() || (0, import_node_path16.join)(home, ".kimi-code");
1536
1676
  if (surface === "kilo") return env.KILO_CONFIG_DIR?.trim() || (0, import_node_path16.join)(home, ".config", "kilo");
@@ -1543,7 +1683,7 @@ function surfaceConfigRoot(surface, env = process.env, home = (0, import_node_os
1543
1683
  }
1544
1684
  function readInstalledPlugins(surface = detectSurface(process.env)) {
1545
1685
  try {
1546
- return JSON.parse((0, import_node_fs18.readFileSync)(installedPluginsPath(surface), "utf8"));
1686
+ return JSON.parse((0, import_node_fs19.readFileSync)(installedPluginsPath(surface), "utf8"));
1547
1687
  } catch {
1548
1688
  return null;
1549
1689
  }
@@ -1562,7 +1702,7 @@ function marketplaceCloneCandidates(surface, home, env = process.env) {
1562
1702
  if (surface === "jervcode") return [];
1563
1703
  return [(0, import_node_path16.join)(home, ".claude", "plugins", "marketplaces", "mutmutco")];
1564
1704
  }
1565
- function marketplaceClonePresent(surface, home, exists = import_node_fs18.existsSync, env = process.env) {
1705
+ function marketplaceClonePresent(surface, home, exists = import_node_fs19.existsSync, env = process.env) {
1566
1706
  return marketplaceCloneCandidates(surface, home, env).some(exists);
1567
1707
  }
1568
1708
  function runHostBinSync(bin, args) {
@@ -1597,7 +1737,7 @@ function codexPluginStatus() {
1597
1737
  }
1598
1738
  function countCodexHookCommands(path2) {
1599
1739
  try {
1600
- const parsed = JSON.parse((0, import_node_fs18.readFileSync)(path2, "utf8"));
1740
+ const parsed = JSON.parse((0, import_node_fs19.readFileSync)(path2, "utf8"));
1601
1741
  let count = 0;
1602
1742
  for (const groups of Object.values(parsed.hooks ?? {})) {
1603
1743
  for (const group of groups) {
@@ -1618,7 +1758,7 @@ function codexHookTrustState(status = codexPluginStatus()) {
1618
1758
  const requiredCount = countCodexHookCommands(hooksPath);
1619
1759
  let config = "";
1620
1760
  try {
1621
- config = (0, import_node_fs18.readFileSync)((0, import_node_path16.join)(root, "config.toml"), "utf8");
1761
+ config = (0, import_node_fs19.readFileSync)((0, import_node_path16.join)(root, "config.toml"), "utf8");
1622
1762
  } catch {
1623
1763
  return { applicable: true, trusted: false, trustedCount: 0, requiredCount };
1624
1764
  }
@@ -1641,7 +1781,7 @@ async function fetchNpmReleasedVersion() {
1641
1781
  return void 0;
1642
1782
  }
1643
1783
  }
1644
- function kiloConfigListsPlugin(configRoot, home = (0, import_node_os6.homedir)(), read = (p) => (0, import_node_fs18.readFileSync)(p, "utf8"), exists = import_node_fs18.existsSync) {
1784
+ function kiloConfigListsPlugin(configRoot, home = (0, import_node_os7.homedir)(), read = (p) => (0, import_node_fs19.readFileSync)(p, "utf8"), exists = import_node_fs19.existsSync) {
1645
1785
  const candidates = ["kilo.json", "kilo.jsonc", "opencode.json", "opencode.jsonc", "config.json"];
1646
1786
  for (const dir of [configRoot, (0, import_node_path16.join)(home, ".kilo")]) {
1647
1787
  for (const file of candidates) {
@@ -1661,10 +1801,10 @@ function kiloConfigListsPlugin(configRoot, home = (0, import_node_os6.homedir)()
1661
1801
  }
1662
1802
  return false;
1663
1803
  }
1664
- function cursorLocalPluginRoot(env = process.env, home = (0, import_node_os6.homedir)()) {
1804
+ function cursorLocalPluginRoot(env = process.env, home = (0, import_node_os7.homedir)()) {
1665
1805
  return (0, import_node_path16.join)(surfaceConfigRoot("cursor", env, home), "plugins", "local", "mmi");
1666
1806
  }
1667
- function cursorPluginTreeHealthy(root, exists = import_node_fs18.existsSync) {
1807
+ function cursorPluginTreeHealthy(root, exists = import_node_fs19.existsSync) {
1668
1808
  return [
1669
1809
  ".cursor-plugin/plugin.json",
1670
1810
  "skills/mmi/SKILL.md",
@@ -1673,31 +1813,52 @@ function cursorPluginTreeHealthy(root, exists = import_node_fs18.existsSync) {
1673
1813
  "scripts/hook-policy.mjs"
1674
1814
  ].every((path2) => exists((0, import_node_path16.join)(root, ...path2.split("/"))));
1675
1815
  }
1676
- function kimiPluginTreeHealthy(root, exists = import_node_fs18.existsSync) {
1816
+ function kimiPluginTreeHealthy(root, exists = import_node_fs19.existsSync) {
1677
1817
  return [
1678
1818
  ".kimi-plugin/plugin.json",
1679
1819
  "skills/mmi/SKILL.md",
1680
1820
  "scripts/hook-run.mjs"
1681
1821
  ].every((path2) => exists((0, import_node_path16.join)(root, ...path2.split("/"))));
1682
1822
  }
1683
- function jervcodeAgentDirs(env = process.env, home = (0, import_node_os6.homedir)()) {
1823
+ function kimiPluginRegistrationHealth(kimiHome, pluginRoot, exists = import_node_fs19.existsSync, read = (path2) => (0, import_node_fs19.readFileSync)(path2, "utf8")) {
1824
+ const path2 = (0, import_node_path16.join)(kimiHome, "plugins", "installed.json");
1825
+ if (!exists(path2)) return "absent-record";
1826
+ try {
1827
+ return validateKimiInstalledPlugin(JSON.parse(read(path2)), pluginRoot, "mmi");
1828
+ } catch {
1829
+ return "malformed-document";
1830
+ }
1831
+ }
1832
+ function kimiPluginHostEvidence(kimiHome) {
1833
+ const root = (0, import_node_path16.join)(kimiHome, "plugins", "managed", "mmi");
1834
+ const treeHealthy = kimiPluginTreeHealthy(root);
1835
+ const registration = kimiPluginRegistrationHealth(kimiHome, root);
1836
+ const healthy = treeHealthy && registration === "healthy";
1837
+ return {
1838
+ treeHealthy,
1839
+ registration,
1840
+ healthy,
1841
+ ...healthy ? {} : treeHealthy ? { receipt: `kimi:registration:${registration}` } : { receipt: "kimi:tree:missing-markers" }
1842
+ };
1843
+ }
1844
+ function jervcodeAgentDirs(env = process.env, home = (0, import_node_os7.homedir)()) {
1684
1845
  const primary = surfaceConfigRoot("jervcode", env, home);
1685
1846
  const legacy = (0, import_node_path16.join)(home, ".pi", "agent");
1686
1847
  const dirs = [primary];
1687
1848
  if (legacy !== primary) dirs.push(legacy);
1688
- return dirs.filter((dir) => (0, import_node_fs18.existsSync)(dir));
1849
+ return dirs.filter((dir) => (0, import_node_fs19.existsSync)(dir));
1689
1850
  }
1690
1851
  function readPiSettings(path2) {
1691
- if (!(0, import_node_fs18.existsSync)(path2)) return void 0;
1852
+ if (!(0, import_node_fs19.existsSync)(path2)) return void 0;
1692
1853
  try {
1693
- const parsed = JSON.parse((0, import_node_fs18.readFileSync)(path2, "utf8"));
1854
+ const parsed = JSON.parse((0, import_node_fs19.readFileSync)(path2, "utf8"));
1694
1855
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return null;
1695
1856
  return parsed;
1696
1857
  } catch {
1697
1858
  return null;
1698
1859
  }
1699
1860
  }
1700
- function mmiPiPackageEntry(env = process.env, home = (0, import_node_os6.homedir)()) {
1861
+ function mmiPiPackageEntry(env = process.env, home = (0, import_node_os7.homedir)()) {
1701
1862
  for (const dir of jervcodeAgentDirs(env, home)) {
1702
1863
  const settings = readPiSettings((0, import_node_path16.join)(dir, "settings.json"));
1703
1864
  const entries = Array.isArray(settings?.packages) ? settings.packages : [];
@@ -1708,7 +1869,7 @@ function mmiPiPackageEntry(env = process.env, home = (0, import_node_os6.homedir
1708
1869
  }
1709
1870
  return null;
1710
1871
  }
1711
- function legacyMmiPiPathEntries(env = process.env, home = (0, import_node_os6.homedir)()) {
1872
+ function legacyMmiPiPathEntries(env = process.env, home = (0, import_node_os7.homedir)()) {
1712
1873
  const found = [];
1713
1874
  for (const dir of jervcodeAgentDirs(env, home)) {
1714
1875
  const settings = readPiSettings((0, import_node_path16.join)(dir, "settings.json"));
@@ -1719,37 +1880,37 @@ function legacyMmiPiPathEntries(env = process.env, home = (0, import_node_os6.ho
1719
1880
  }
1720
1881
  return found;
1721
1882
  }
1722
- function mmiPiPackageRoot(env = process.env, home = (0, import_node_os6.homedir)()) {
1883
+ function mmiPiPackageRoot(env = process.env, home = (0, import_node_os7.homedir)()) {
1723
1884
  for (const dir of jervcodeAgentDirs(env, home)) {
1724
1885
  const root = (0, import_node_path16.join)(dir, "npm", "node_modules", ...JERVCODE_NPM_PACKAGE.split("/"));
1725
- if ((0, import_node_fs18.existsSync)((0, import_node_path16.join)(root, "package.json"))) return root;
1886
+ if ((0, import_node_fs19.existsSync)((0, import_node_path16.join)(root, "package.json"))) return root;
1726
1887
  }
1727
1888
  return null;
1728
1889
  }
1729
- function installedJervCodePackageVersion(env = process.env, home = (0, import_node_os6.homedir)()) {
1890
+ function installedJervCodePackageVersion(env = process.env, home = (0, import_node_os7.homedir)()) {
1730
1891
  const root = mmiPiPackageRoot(env, home);
1731
1892
  if (!root) return void 0;
1732
1893
  try {
1733
- const manifest = JSON.parse((0, import_node_fs18.readFileSync)((0, import_node_path16.join)(root, "package.json"), "utf8"));
1894
+ const manifest = JSON.parse((0, import_node_fs19.readFileSync)((0, import_node_path16.join)(root, "package.json"), "utf8"));
1734
1895
  return typeof manifest.version === "string" ? manifest.version : void 0;
1735
1896
  } catch {
1736
1897
  return void 0;
1737
1898
  }
1738
1899
  }
1739
- function mmiPiPackageHealthy(env = process.env, home = (0, import_node_os6.homedir)()) {
1900
+ function mmiPiPackageHealthy(env = process.env, home = (0, import_node_os7.homedir)()) {
1740
1901
  const root = mmiPiPackageRoot(env, home);
1741
- return Boolean(root && (0, import_node_fs18.existsSync)((0, import_node_path16.join)(root, "skills", "mmi", "SKILL.md")));
1902
+ return Boolean(root && (0, import_node_fs19.existsSync)((0, import_node_path16.join)(root, "skills", "mmi", "SKILL.md")));
1742
1903
  }
1743
1904
  function piDoctorEnv(agentDir) {
1744
1905
  return { ...process.env, PI_CODING_AGENT_DIR: agentDir };
1745
1906
  }
1746
- function hermesPluginRoot(env = process.env, home = (0, import_node_os6.homedir)()) {
1907
+ function hermesPluginRoot(env = process.env, home = (0, import_node_os7.homedir)()) {
1747
1908
  return (0, import_node_path16.join)(hermesConfigRoot(env, home), "plugins", "mmi");
1748
1909
  }
1749
- function hermesPluginTreeHealthy(root, exists = import_node_fs18.existsSync) {
1910
+ function hermesPluginTreeHealthy(root, exists = import_node_fs19.existsSync) {
1750
1911
  if (!["plugin.yaml", "__init__.py"].every((file) => exists((0, import_node_path16.join)(root, file)))) return false;
1751
1912
  try {
1752
- return (0, import_node_fs18.statSync)((0, import_node_path16.join)(root, "skills")).isDirectory();
1913
+ return (0, import_node_fs19.statSync)((0, import_node_path16.join)(root, "skills")).isDirectory();
1753
1914
  } catch {
1754
1915
  return false;
1755
1916
  }
@@ -1758,22 +1919,23 @@ function snapshotPluginGuardInput(surface = detectSurface(process.env), isOrgRep
1758
1919
  const root = surfaceConfigRoot(surface);
1759
1920
  const installed = readInstalledPlugins(surface);
1760
1921
  const codexStatus = surface === "codex" ? codexPluginStatus() : void 0;
1922
+ const kimi = surface === "kimi" ? kimiPluginHostEvidence(root) : void 0;
1761
1923
  const piEntry = surface === "jervcode" ? mmiPiPackageEntry() : null;
1762
1924
  return {
1763
1925
  isOrgRepo,
1764
- installRecordPresent: hasUserInstallRecord(installed, MMI_PLUGIN_ID) || hasProjectInstallRecord(installed, MMI_PLUGIN_ID, process.cwd()) || // Kimi's managed plugin directory is its native install record; it has no Claude-style ledger.
1765
- surface === "kimi" && (0, import_node_fs18.existsSync)((0, import_node_path16.join)(root, "plugins", "managed", "mmi")) || // kilo-p1: the install record is the config file itself.
1926
+ installRecordPresent: hasUserInstallRecord(installed, MMI_PLUGIN_ID) || hasProjectInstallRecord(installed, MMI_PLUGIN_ID, process.cwd()) || // Kimi loads only records in plugins/installed.json; a managed tree alone is inert.
1927
+ surface === "kimi" && kimi?.registration === "healthy" || // kilo-p1: the install record is the config file itself.
1766
1928
  surface === "kilo" && kiloConfigListsPlugin(root) || // #4188: jervcode's install record is the settings-file packages[] entry itself.
1767
- surface === "jervcode" && piEntry !== null || surface === "cursor" && (0, import_node_fs18.existsSync)(cursorLocalPluginRoot()) || surface === "hermes" && (0, import_node_fs18.existsSync)(hermesPluginRoot()),
1929
+ surface === "jervcode" && piEntry !== null || surface === "cursor" && (0, import_node_fs19.existsSync)(cursorLocalPluginRoot()) || surface === "hermes" && (0, import_node_fs19.existsSync)(hermesPluginRoot()),
1768
1930
  // Kilo has no marketplace to clone — the config file IS the install record, so this dimension of
1769
1931
  // the shared guard table is vacuously satisfied. Same for jervcode's settings entry.
1770
- marketplaceClonePresent: surface === "kimi" || surface === "kilo" || surface === "cursor" || surface === "jervcode" || surface === "hermes" ? true : marketplaceClonePresent(surface, (0, import_node_os6.homedir)()),
1932
+ marketplaceClonePresent: surface === "kimi" || surface === "kilo" || surface === "cursor" || surface === "jervcode" || surface === "hermes" ? true : marketplaceClonePresent(surface, (0, import_node_os7.homedir)()),
1771
1933
  // Kimi keeps no plugin cache dir — installs are copied to plugins/managed/<id> and run from there.
1772
1934
  // Kilo (kilo-p1) keeps no cache dir either: the plugin's server() provisions ~/.kilo behind the
1773
1935
  // version stamp, so the stamp's presence is the cache signal.
1774
- pluginCachePresent: surface === "jervcode" ? mmiPiPackageHealthy() : surface === "hermes" ? hermesPluginTreeHealthy(hermesPluginRoot()) : surface === "kilo" ? (0, import_node_fs18.existsSync)((0, import_node_path16.join)((0, import_node_os6.homedir)(), ".kilo", ".mmi-kilo-version")) : surface === "kimi" ? kimiPluginTreeHealthy((0, import_node_path16.join)(root, "plugins", "managed", "mmi")) : surface === "cursor" ? cursorPluginTreeHealthy(cursorLocalPluginRoot()) : surface === "codex" ? Boolean(
1775
- codexStatus?.installed && codexStatus.enabled && codexStatus.version && (0, import_node_fs18.existsSync)((0, import_node_path16.join)(root, "plugins", "cache", CODEX_MARKETPLACE, "mmi", codexStatus.version))
1776
- ) : (0, import_node_fs18.existsSync)((0, import_node_path16.join)(root, "plugins", "cache", "mutmutco", "mmi"))
1936
+ pluginCachePresent: surface === "jervcode" ? mmiPiPackageHealthy() : surface === "hermes" ? hermesPluginTreeHealthy(hermesPluginRoot()) : surface === "kilo" ? (0, import_node_fs19.existsSync)((0, import_node_path16.join)((0, import_node_os7.homedir)(), ".kilo", ".mmi-kilo-version")) : surface === "kimi" ? Boolean(kimi?.healthy) : surface === "cursor" ? cursorPluginTreeHealthy(cursorLocalPluginRoot()) : surface === "codex" ? Boolean(
1937
+ codexStatus?.installed && codexStatus.enabled && codexStatus.version && (0, import_node_fs19.existsSync)((0, import_node_path16.join)(root, "plugins", "cache", CODEX_MARKETPLACE, "mmi", codexStatus.version))
1938
+ ) : (0, import_node_fs19.existsSync)((0, import_node_path16.join)(root, "plugins", "cache", "mutmutco", "mmi"))
1777
1939
  };
1778
1940
  }
1779
1941
  function claudePluginGuardState(isOrgRepo) {
@@ -1800,7 +1962,7 @@ function captureCodexHookLauncher() {
1800
1962
  const files = ["mmi-hook", "mmi-hook.exe"].flatMap((name) => {
1801
1963
  const path2 = (0, import_node_path16.join)(root, "bin", name);
1802
1964
  try {
1803
- return [{ name, content: (0, import_node_fs18.readFileSync)(path2) }];
1965
+ return [{ name, content: (0, import_node_fs19.readFileSync)(path2) }];
1804
1966
  } catch {
1805
1967
  return [];
1806
1968
  }
@@ -1808,13 +1970,13 @@ function captureCodexHookLauncher() {
1808
1970
  return files.length === 2 ? { root, files } : void 0;
1809
1971
  }
1810
1972
  function restoreCodexHookLauncher(snapshot) {
1811
- if (!snapshot || (0, import_node_fs18.existsSync)((0, import_node_path16.join)(snapshot.root, "scripts", "hook-run.mjs"))) return false;
1973
+ if (!snapshot || (0, import_node_fs19.existsSync)((0, import_node_path16.join)(snapshot.root, "scripts", "hook-run.mjs"))) return false;
1812
1974
  const bin = (0, import_node_path16.join)(snapshot.root, "bin");
1813
- (0, import_node_fs18.mkdirSync)(bin, { recursive: true });
1975
+ (0, import_node_fs19.mkdirSync)(bin, { recursive: true });
1814
1976
  for (const file of snapshot.files) {
1815
1977
  const path2 = (0, import_node_path16.join)(bin, file.name);
1816
- (0, import_node_fs18.writeFileSync)(path2, file.content);
1817
- if (file.name === "mmi-hook") (0, import_node_fs18.chmodSync)(path2, 493);
1978
+ (0, import_node_fs19.writeFileSync)(path2, file.content);
1979
+ if (file.name === "mmi-hook") (0, import_node_fs19.chmodSync)(path2, 493);
1818
1980
  }
1819
1981
  return true;
1820
1982
  }
@@ -1823,13 +1985,13 @@ function canonicalCursorRemote(remote) {
1823
1985
  }
1824
1986
  function readJsonFile(path2) {
1825
1987
  try {
1826
- return JSON.parse((0, import_node_fs18.readFileSync)(path2, "utf8"));
1988
+ return JSON.parse((0, import_node_fs19.readFileSync)(path2, "utf8"));
1827
1989
  } catch {
1828
1990
  return null;
1829
1991
  }
1830
1992
  }
1831
1993
  async function cursorTreeManaged(target) {
1832
- if ((0, import_node_fs18.existsSync)((0, import_node_path16.join)(target, ".git"))) {
1994
+ if ((0, import_node_fs19.existsSync)((0, import_node_path16.join)(target, ".git"))) {
1833
1995
  try {
1834
1996
  const { stdout } = await runHostBin("git", ["-C", target, "remote", "get-url", "origin"], { timeout: 15e3 });
1835
1997
  if (canonicalCursorRemote(stdout)) return { ok: true };
@@ -1850,19 +2012,19 @@ async function installCursorPluginCheckout(env = process.env) {
1850
2012
  const pluginsRoot = (0, import_node_path16.join)(configRoot, "plugins");
1851
2013
  const target = (0, import_node_path16.join)(pluginsRoot, "local", "mmi");
1852
2014
  const source = env.MMI_CURSOR_PLUGIN_SOURCE?.trim();
1853
- if ((0, import_node_fs18.existsSync)(target) && !source) {
2015
+ if ((0, import_node_fs19.existsSync)(target) && !source) {
1854
2016
  const managed = await cursorTreeManaged(target);
1855
2017
  if (!managed.ok) return { ok: false, detail: managed.detail };
1856
2018
  }
1857
- (0, import_node_fs18.mkdirSync)((0, import_node_path16.join)(pluginsRoot, "local"), { recursive: true });
1858
- (0, import_node_fs18.mkdirSync)((0, import_node_path16.join)(pluginsRoot, "staging"), { recursive: true });
1859
- (0, import_node_fs18.mkdirSync)((0, import_node_path16.join)(pluginsRoot, "quarantine"), { recursive: true });
2019
+ (0, import_node_fs19.mkdirSync)((0, import_node_path16.join)(pluginsRoot, "local"), { recursive: true });
2020
+ (0, import_node_fs19.mkdirSync)((0, import_node_path16.join)(pluginsRoot, "staging"), { recursive: true });
2021
+ (0, import_node_fs19.mkdirSync)((0, import_node_path16.join)(pluginsRoot, "quarantine"), { recursive: true });
1860
2022
  const suffix = `${Date.now()}-${process.pid}`;
1861
2023
  const staged = (0, import_node_path16.join)(pluginsRoot, "staging", `mmi-${suffix}`);
1862
2024
  const quarantined = (0, import_node_path16.join)(pluginsRoot, "quarantine", `mmi-${suffix}`);
1863
2025
  try {
1864
2026
  if (source) {
1865
- (0, import_node_fs18.cpSync)(source, staged, {
2027
+ (0, import_node_fs19.cpSync)(source, staged, {
1866
2028
  recursive: true,
1867
2029
  filter: (path2) => !path2.split(/[\\/]/).some((part) => part === ".git" || part === "node_modules")
1868
2030
  });
@@ -1873,18 +2035,18 @@ async function installCursorPluginCheckout(env = process.env) {
1873
2035
  });
1874
2036
  }
1875
2037
  if (!cursorPluginTreeHealthy(staged)) {
1876
- (0, import_node_fs18.rmSync)(staged, { recursive: true, force: true });
2038
+ (0, import_node_fs19.rmSync)(staged, { recursive: true, force: true });
1877
2039
  return { ok: false, detail: "downloaded Cursor plugin is incomplete; existing install was preserved" };
1878
2040
  }
1879
2041
  let movedOld = false;
1880
- if ((0, import_node_fs18.existsSync)(target)) {
1881
- (0, import_node_fs18.renameSync)(target, quarantined);
2042
+ if ((0, import_node_fs19.existsSync)(target)) {
2043
+ (0, import_node_fs19.renameSync)(target, quarantined);
1882
2044
  movedOld = true;
1883
2045
  }
1884
2046
  try {
1885
- (0, import_node_fs18.renameSync)(staged, target);
2047
+ (0, import_node_fs19.renameSync)(staged, target);
1886
2048
  } catch (error) {
1887
- if (movedOld && !(0, import_node_fs18.existsSync)(target)) (0, import_node_fs18.renameSync)(quarantined, target);
2049
+ if (movedOld && !(0, import_node_fs19.existsSync)(target)) (0, import_node_fs19.renameSync)(quarantined, target);
1888
2050
  throw error;
1889
2051
  }
1890
2052
  return {
@@ -1892,7 +2054,7 @@ async function installCursorPluginCheckout(env = process.env) {
1892
2054
  detail: movedOld ? `installed canonical Cursor plugin; previous checkout quarantined at ${quarantined}` : `installed canonical Cursor plugin at ${target}`
1893
2055
  };
1894
2056
  } catch (error) {
1895
- if ((0, import_node_fs18.existsSync)(staged)) (0, import_node_fs18.rmSync)(staged, { recursive: true, force: true });
2057
+ if ((0, import_node_fs19.existsSync)(staged)) (0, import_node_fs19.rmSync)(staged, { recursive: true, force: true });
1896
2058
  return { ok: false, detail: error.message.trim().slice(0, 240).replace(/\s+/g, " ") };
1897
2059
  }
1898
2060
  }
@@ -1929,7 +2091,7 @@ async function runHealSteps(host, tableSteps, deps) {
1929
2091
  const refSupported = needsRefProbe ? await marketplaceAddRefSupported(host) : true;
1930
2092
  const { steps } = adaptHealStepsForRefSupport(tableSteps, refSupported);
1931
2093
  if (deps.banner) log(deps.banner(refSupported));
1932
- const pinsPath = (0, import_node_path16.join)((0, import_node_os6.homedir)(), ...KNOWN_MARKETPLACES_RELATIVE);
2094
+ const pinsPath = (0, import_node_path16.join)((0, import_node_os7.homedir)(), ...KNOWN_MARKETPLACES_RELATIVE);
1933
2095
  const pins = host === "claude" ? captureMarketplacePins(readKnownMarketplacesFile(pinsPath), [MMI_MARKETPLACE_NAME, JERV_MARKETPLACE_NAME]) : /* @__PURE__ */ new Map();
1934
2096
  let failure;
1935
2097
  try {
@@ -2052,7 +2214,7 @@ async function healActivePluginForDoctor(surface = detectSurface(process.env), o
2052
2214
  }
2053
2215
  function readKnownMarketplacesFile(path2) {
2054
2216
  try {
2055
- return (0, import_node_fs18.existsSync)(path2) ? (0, import_node_fs18.readFileSync)(path2, "utf8") : void 0;
2217
+ return (0, import_node_fs19.existsSync)(path2) ? (0, import_node_fs19.readFileSync)(path2, "utf8") : void 0;
2056
2218
  } catch {
2057
2219
  return void 0;
2058
2220
  }
@@ -2074,24 +2236,37 @@ function claudeCodeIsRunning(env = process.env, listProcesses = defaultProcessLi
2074
2236
  function defaultProcessList() {
2075
2237
  return isWin ? (0, import_node_child_process8.execFileSync)("powershell.exe", ["-NoProfile", "-Command", "Get-CimInstance Win32_Process | ForEach-Object { $_.CommandLine }"], { encoding: "utf8", windowsHide: true, maxBuffer: 32 * 1024 * 1024, timeout: 15e3 }) : (0, import_node_child_process8.execFileSync)("ps", ["-eo", "args="], { encoding: "utf8", windowsHide: true, timeout: 15e3 });
2076
2238
  }
2077
- function writeMarketplacePinsOnDisk(path2, pins, succeeded, failedVerb, declineWhileHostLive) {
2239
+ function writeMarketplacePinsOnDisk(path2, pins, succeeded, failedVerb, declineWhileHostLive, lock = {}) {
2078
2240
  if (pins.size === 0) return void 0;
2079
- const after = readKnownMarketplacesFile(path2);
2080
- const next = restoreMarketplacePins(after, pins);
2081
- if (next === null) return void 0;
2082
- const declined = declineWhileHostLive?.();
2083
- if (declined) return declined;
2084
2241
  try {
2085
- (0, import_node_fs18.writeFileSync)(path2, next, "utf8");
2086
- } catch {
2087
- return `could NOT ${failedVerb} ${[...pins.keys()].join(", ")} \u2014 set it by hand`;
2242
+ return withSharedDocLock(path2, { owner: MARKETPLACE_PINS_LOCK_OWNER, ...lock }, () => {
2243
+ const after = readKnownMarketplacesFile(path2);
2244
+ const next = restoreMarketplacePins(after, pins);
2245
+ if (next === null) return void 0;
2246
+ const declined = declineWhileHostLive?.();
2247
+ if (declined) return declined;
2248
+ const tmp = `${path2}.tmp-${process.pid}`;
2249
+ try {
2250
+ (0, import_node_fs19.writeFileSync)(tmp, next, "utf8");
2251
+ (0, import_node_fs19.renameSync)(tmp, path2);
2252
+ } catch {
2253
+ try {
2254
+ (0, import_node_fs19.rmSync)(tmp, { force: true });
2255
+ } catch {
2256
+ }
2257
+ return `could NOT ${failedVerb} ${[...pins.keys()].join(", ")} \u2014 set it by hand`;
2258
+ }
2259
+ const verify = readKnownMarketplacesFile(path2);
2260
+ const failed = [...pins].filter(([name, want]) => {
2261
+ const got = readKnownMarketplace(verify, name);
2262
+ return typeof want.autoUpdate === "boolean" && got.declared !== want.autoUpdate || want.ref !== void 0 && got.ref !== want.ref;
2263
+ });
2264
+ return failed.length ? `${failedVerb} did NOT take for ${failed.map(([n]) => n).join(", ")} \u2014 set it by hand` : succeeded(pins);
2265
+ });
2266
+ } catch (error) {
2267
+ if (error instanceof Error && error.message.startsWith("shared-document lock timeout:")) return error.message;
2268
+ throw error;
2088
2269
  }
2089
- const verify = readKnownMarketplacesFile(path2);
2090
- const failed = [...pins].filter(([name, want]) => {
2091
- const got = readKnownMarketplace(verify, name);
2092
- return typeof want.autoUpdate === "boolean" && got.declared !== want.autoUpdate || want.ref !== void 0 && got.ref !== want.ref;
2093
- });
2094
- return failed.length ? `${failedVerb} did NOT take for ${failed.map(([n]) => n).join(", ")} \u2014 set it by hand` : succeeded(pins);
2095
2270
  }
2096
2271
  function restoreMarketplacePinsOnDisk(path2, pins, hostIsRunning = claudeCodeIsRunning) {
2097
2272
  return writeMarketplacePinsOnDisk(
@@ -2101,7 +2276,7 @@ function restoreMarketplacePinsOnDisk(path2, pins, hostIsRunning = claudeCodeIsR
2101
2276
  "restore"
2102
2277
  );
2103
2278
  }
2104
- function applyOrgMarketplacePins(path2, names, hostIsRunning = claudeCodeIsRunning) {
2279
+ function applyOrgMarketplacePins(path2, names, hostIsRunning = claudeCodeIsRunning, lock = {}) {
2105
2280
  let landed = false;
2106
2281
  let blockedByHost = false;
2107
2282
  const detail = writeMarketplacePinsOnDisk(
@@ -2116,21 +2291,22 @@ function applyOrgMarketplacePins(path2, names, hostIsRunning = claudeCodeIsRunni
2116
2291
  if (!hostIsRunning()) return void 0;
2117
2292
  blockedByHost = true;
2118
2293
  return "not pinned \u2014 Claude Code is running and rewrites this registration from its own copy; quit it, then run `mmi-cli doctor`";
2119
- }
2294
+ },
2295
+ lock
2120
2296
  );
2121
2297
  return detail === void 0 ? void 0 : { detail, wrote: landed, blockedByHost };
2122
2298
  }
2123
2299
  function writeMarketplacePinPending(path2, names, now = Date.now()) {
2124
2300
  try {
2125
- (0, import_node_fs18.mkdirSync)((0, import_node_path16.dirname)(path2), { recursive: true });
2126
- (0, import_node_fs18.writeFileSync)(path2, `${JSON.stringify({ v: 1, names: [...names], at: new Date(now).toISOString() })}
2301
+ (0, import_node_fs19.mkdirSync)((0, import_node_path16.dirname)(path2), { recursive: true });
2302
+ (0, import_node_fs19.writeFileSync)(path2, `${JSON.stringify({ v: 1, names: [...names], at: new Date(now).toISOString() })}
2127
2303
  `, "utf8");
2128
2304
  } catch {
2129
2305
  }
2130
2306
  }
2131
2307
  function readMarketplacePinPending(path2, name, now = Date.now()) {
2132
2308
  try {
2133
- const parsed = JSON.parse((0, import_node_fs18.readFileSync)(path2, "utf8"));
2309
+ const parsed = JSON.parse((0, import_node_fs19.readFileSync)(path2, "utf8"));
2134
2310
  const at = typeof parsed.at === "string" ? Date.parse(parsed.at) : Number.NaN;
2135
2311
  if (parsed.v !== 1 || !Array.isArray(parsed.names) || !parsed.names.includes(name) || !Number.isFinite(at)) return void 0;
2136
2312
  if (now - at < 0 || now - at > 12 * 60 * 6e4) return void 0;
@@ -2218,18 +2394,20 @@ async function runPluginHeal(surface = detectSurface(process.env)) {
2218
2394
  ${recovery}${note}${pluginReadGrantNote()}`);
2219
2395
  }
2220
2396
  }
2221
- var import_node_fs18, import_node_child_process8, import_node_path16, import_node_os6, isWin, MMI_PLUGIN_ID, LEGACY_MMI_MARKETPLACE, CODEX_MARKETPLACE, CLAUDE_RECOVERY, CODEX_RECOVERY, CURSOR_RECOVERY, PLUGIN_SURFACE_HEAL, CLAUDE_PLUGIN_TIMEOUT_MS, NPM_VIEW_TIMEOUT_MS, installedPluginsPath, JERVCODE_NPM_PACKAGE, LEGACY_MMI_PI_PATH, PLUGIN_READ_REPO2;
2397
+ var import_node_fs19, import_node_child_process8, import_node_path16, import_node_os7, isWin, MMI_PLUGIN_ID, LEGACY_MMI_MARKETPLACE, CODEX_MARKETPLACE, CLAUDE_RECOVERY, CODEX_RECOVERY, CURSOR_RECOVERY, PLUGIN_SURFACE_HEAL, CLAUDE_PLUGIN_TIMEOUT_MS, NPM_VIEW_TIMEOUT_MS, installedPluginsPath, JERVCODE_NPM_PACKAGE, LEGACY_MMI_PI_PATH, PLUGIN_READ_REPO2, MARKETPLACE_PINS_LOCK_OWNER;
2222
2398
  var init_plugin_guard_io = __esm({
2223
2399
  "src/plugin-guard-io.ts"() {
2224
2400
  "use strict";
2225
- import_node_fs18 = require("node:fs");
2401
+ import_node_fs19 = require("node:fs");
2226
2402
  import_node_child_process8 = require("node:child_process");
2227
2403
  import_node_path16 = require("node:path");
2228
- import_node_os6 = require("node:os");
2404
+ import_node_os7 = require("node:os");
2229
2405
  init_marketplace_autoupdate();
2230
2406
  init_cli_shared();
2231
2407
  init_version_lag();
2232
2408
  init_plugin_guard();
2409
+ init_kimi_registration();
2410
+ init_host_doc_lock();
2233
2411
  isWin = process.platform === "win32";
2234
2412
  MMI_PLUGIN_ID = "mmi@mutmutco";
2235
2413
  LEGACY_MMI_MARKETPLACE = "mmi";
@@ -2290,6 +2468,7 @@ var init_plugin_guard_io = __esm({
2290
2468
  JERVCODE_NPM_PACKAGE = "@mutmutco/pi-plugin";
2291
2469
  LEGACY_MMI_PI_PATH = /[/\\]mutmutco[/\\]mmi[/\\]\d+\.\d+\.\d+[/\\]\.pi-plugin[/\\]?$/i;
2292
2470
  PLUGIN_READ_REPO2 = "mutmutco/MMI-Hub";
2471
+ MARKETPLACE_PINS_LOCK_OWNER = "mmi-hub:marketplace-pins";
2293
2472
  }
2294
2473
  });
2295
2474
 
@@ -5681,8 +5860,8 @@ var program = new Command();
5681
5860
 
5682
5861
  // src/index.ts
5683
5862
  var import_promises8 = require("node:fs/promises");
5684
- var import_node_fs43 = require("node:fs");
5685
- var import_node_child_process20 = require("node:child_process");
5863
+ var import_node_fs46 = require("node:fs");
5864
+ var import_node_child_process21 = require("node:child_process");
5686
5865
  init_cli_shared();
5687
5866
 
5688
5867
  // src/issue-surface.ts
@@ -6723,7 +6902,7 @@ function commandLadderHint() {
6723
6902
  }
6724
6903
 
6725
6904
  // src/index.ts
6726
- var import_node_path40 = require("node:path");
6905
+ var import_node_path42 = require("node:path");
6727
6906
 
6728
6907
  // src/merge-ci-policy.ts
6729
6908
  function resolveMergeCiPolicy(input) {
@@ -6843,6 +7022,32 @@ function conflictingResult(policy, baseBranch, waitedMs) {
6843
7022
  return { policy, status: "conflicting", reason: conflictingPrMessage(baseBranch), detail: "conflicting", waitedMs };
6844
7023
  }
6845
7024
  var PR_CHECKS_TIMEOUT_EXIT_CODE = 2;
7025
+ function isRetryableGitHubWaitReadError(e) {
7026
+ const status = typeof e?.status === "number" ? e.status : void 0;
7027
+ const code = typeof e?.code === "string" ? e.code : "";
7028
+ const text = [
7029
+ typeof e?.stderr === "string" ? e.stderr : "",
7030
+ typeof e?.stdout === "string" ? e.stdout : "",
7031
+ e instanceof Error ? e.message : String(e ?? "")
7032
+ ].join("\n");
7033
+ const codes = /* @__PURE__ */ new Set();
7034
+ if (typeof status === "number" && status > 0) codes.add(status);
7035
+ for (const m of text.matchAll(/\bHTTP\/?\d*(?:\.\d)?\s+(\d{3})\b/g)) codes.add(Number(m[1]));
7036
+ const rateLimited = e?.rateLimited === true || /API rate limit already exceeded|rate limit already exceeded|RATE_LIMITED|secondary rate|abuse detection/i.test(text);
7037
+ if ([401, 404, 409, 422].some((c) => codes.has(c))) return false;
7038
+ if (codes.has(403) && !rateLimited) return false;
7039
+ if (codes.has(429) || rateLimited) return true;
7040
+ if ([...codes].some((c) => c >= 500 && c < 600)) return true;
7041
+ if (/^(ETIMEDOUT|ECONNRESET|ECONNREFUSED|ENOTFOUND|EAI_AGAIN|ENETUNREACH)$/i.test(code) || /ETIMEDOUT|ECONNRESET|ECONNREFUSED|ENOTFOUND|EAI_AGAIN|socket hang up|network timeout|Client network socket disconnected/i.test(text)) {
7042
+ return true;
7043
+ }
7044
+ return /Something went wrong while executing your query/.test(text) || /^\s*unexpected end of JSON input\s*$/m.test(text);
7045
+ }
7046
+ function waitReadFailureMessage(e) {
7047
+ const err = e;
7048
+ const stderr = typeof err?.stderr === "string" ? err.stderr.trim() : "";
7049
+ return (stderr || err?.message || String(e)).replace(/\s+/g, " ").slice(0, 300);
7050
+ }
6846
7051
  var PR_CHECKS_POLL_MS = 3e4;
6847
7052
  var PR_CHECKS_TIMEOUT_MS = 30 * 6e4;
6848
7053
  var PR_CHECKS_SUCCESS_CONFIRMATIONS = 2;
@@ -6877,14 +7082,22 @@ async function waitForPrChecks(deps) {
6877
7082
  const baseBranch = deps.baseBranch ?? "development";
6878
7083
  const queuedStates = [];
6879
7084
  if (policy === "no-ci") {
6880
- const firstState = await deps.pollChecks();
6881
- if (firstState === "no-checks-reported") {
6882
- return { policy, status: "skipped", reason };
7085
+ try {
7086
+ const firstState = await deps.pollChecks();
7087
+ if (firstState === "no-checks-reported") {
7088
+ return { policy, status: "skipped", reason };
7089
+ }
7090
+ reason = NO_CI_LIVE_CHECKS_CONTRADICTION_REASON;
7091
+ deps.log?.(`merge CI policy contradiction: ${reason}; waiting for live PR checks`);
7092
+ policy = "wait-for-checks";
7093
+ queuedStates.push(firstState);
7094
+ } catch (e) {
7095
+ if (!isRetryableGitHubWaitReadError(e)) {
7096
+ return { policy, status: "failure", reason: waitReadFailureMessage(e), detail: "github-read-failed" };
7097
+ }
7098
+ deps.log?.(`merge CI policy no-ci probe hit a transient GitHub read failure \u2014 waiting: ${waitReadFailureMessage(e)}`);
7099
+ policy = "wait-for-checks";
6883
7100
  }
6884
- reason = NO_CI_LIVE_CHECKS_CONTRADICTION_REASON;
6885
- deps.log?.(`merge CI policy contradiction: ${reason}; waiting for live PR checks`);
6886
- policy = "wait-for-checks";
6887
- queuedStates.push(firstState);
6888
7101
  }
6889
7102
  if (deps.pollMergeable) {
6890
7103
  const mergeable = await resolveSettledMergeableState(deps.pollMergeable, deps.sleep);
@@ -6934,7 +7147,19 @@ async function waitForPrChecks(deps) {
6934
7147
  const mergeable = await deps.pollMergeable();
6935
7148
  if (mergeable === "CONFLICTING") return conflictingResult(policy, baseBranch, now() - started);
6936
7149
  }
6937
- const state = queuedStates.shift() ?? await deps.pollChecks();
7150
+ let state;
7151
+ try {
7152
+ state = queuedStates.shift() ?? await deps.pollChecks();
7153
+ } catch (e) {
7154
+ if (!isRetryableGitHubWaitReadError(e)) {
7155
+ return { policy, status: "failure", reason: waitReadFailureMessage(e), detail: "github-read-failed", waitedMs: now() - started };
7156
+ }
7157
+ lastDetail = `github-read-transient (${waitReadFailureMessage(e)})`;
7158
+ deps.log?.(`transient GitHub read failure \u2014 retrying within the wait budget: ${lastDetail}`);
7159
+ report("pending");
7160
+ await deps.sleep(PR_CHECKS_POLL_MS);
7161
+ continue;
7162
+ }
6938
7163
  report(state);
6939
7164
  if (state !== "success") successStreak = 0;
6940
7165
  if (state !== "failure") failureStreak = 0;
@@ -8449,15 +8674,9 @@ function gateSeedVars(cls, releaseTrack, runtime = "node", requiredCheckBranches
8449
8674
  const trackBranches = track === "trunk" ? ["main"] : track === "direct" ? ["development", "main"] : ["development", "rc", "main"];
8450
8675
  const rulesetBranches = requiredCheckBranches?.length ? [...requiredCheckBranches] : trackBranches;
8451
8676
  const rulesetRefs = JSON.stringify(rulesetBranches.map((branch) => `refs/heads/${branch}`));
8452
- const windowsCompat = {
8453
- // #5113: opt-in informational windows-latest proof. Default OFF — the CLI fills the rendered job
8454
- // YAML (or '') at the final layering step; never hand-passed.
8455
- GATE_WINDOWS_COMPAT_JOB_YAML: ""
8456
- };
8457
8677
  if (track === "trunk") {
8458
8678
  return {
8459
8679
  ...runtimeVars,
8460
- ...windowsCompat,
8461
8680
  GATE_PUSH_BRANCHES_YAML: "[main]",
8462
8681
  GATE_FULL_RUN_BRANCH: "main",
8463
8682
  GATE_RULESET_BRANCH_REFS_JSON: rulesetRefs
@@ -8466,7 +8685,6 @@ function gateSeedVars(cls, releaseTrack, runtime = "node", requiredCheckBranches
8466
8685
  if (track === "direct") {
8467
8686
  return {
8468
8687
  ...runtimeVars,
8469
- ...windowsCompat,
8470
8688
  GATE_PUSH_BRANCHES_YAML: "[development, main]",
8471
8689
  GATE_FULL_RUN_BRANCH: "development",
8472
8690
  GATE_RULESET_BRANCH_REFS_JSON: rulesetRefs
@@ -8474,7 +8692,6 @@ function gateSeedVars(cls, releaseTrack, runtime = "node", requiredCheckBranches
8474
8692
  }
8475
8693
  return {
8476
8694
  ...runtimeVars,
8477
- ...windowsCompat,
8478
8695
  GATE_PUSH_BRANCHES_YAML: "[development, rc, main]",
8479
8696
  GATE_FULL_RUN_BRANCH: "development",
8480
8697
  GATE_RULESET_BRANCH_REFS_JSON: rulesetRefs
@@ -8491,38 +8708,8 @@ function withDerivedRepoVars(vars, parsed, cls, releaseTrack, requiredCheckBranc
8491
8708
  for (const [key, value] of Object.entries(gateSeedVars(cls, track, runtime, requiredCheckBranches))) {
8492
8709
  out[key] ??= value;
8493
8710
  }
8494
- if (out.GATE_WINDOWS_COMPAT === "true" && !out.GATE_WINDOWS_COMPAT_JOB_YAML) {
8495
- out.GATE_WINDOWS_COMPAT_JOB_YAML = windowsCompatJobYaml(out);
8496
- }
8497
8711
  return out;
8498
8712
  }
8499
- function windowsCompatJobYaml(vars) {
8500
- const workdir = vars.GATE_WORKDIR ?? ".";
8501
- const cmd = vars.GATE_CMD ?? DEFAULT_GATE_CMD;
8502
- const install = vars.GATE_INSTALL_CMD ?? "npm ci";
8503
- const fullRunBranch = vars.GATE_FULL_RUN_BRANCH ?? "development";
8504
- const runtime = vars.GATE_RUNTIME === "python" ? "python" : "node";
8505
- const setup = runtime === "python" ? ` - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
8506
- with: { python-version: '${vars.GATE_PY_VERSION ?? DEFAULT_GATE_PY_VERSION}' }` : ` - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
8507
- with: { node-version: 24, cache: npm, cache-dependency-path: ${vars.GATE_CACHE_DEP_PATH ?? "package-lock.json"} }`;
8508
- return ` # MMI-Hub#5113: opt-in Windows compatibility proof \u2014 informational only. Do NOT add this job to a
8509
- # required-contexts ruleset without a deliberate repo decision: the required gate stays the Linux lane
8510
- # (faster, cheaper, where autonomous agents run). GitHub-hosted Windows minutes cost more than Linux,
8511
- # hence opt-in. defaults.run.shell=bash is Git for Windows bash on windows-latest, so the check syntax
8512
- # the Linux gate runs keeps working here.
8513
- windows-compat:
8514
- if: \${{ github.event_name == 'pull_request' || (github.event_name == 'push' && (github.ref_name == '${fullRunBranch}' || github.ref_name == 'main')) }}
8515
- runs-on: windows-latest
8516
- defaults:
8517
- run: { working-directory: ${workdir}, shell: bash }
8518
- steps:
8519
- - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
8520
- ${setup}
8521
- - run: ${install}
8522
- # Fast proof only \u2014 the full suite stays on the Linux gate.
8523
- - run: ${cmd}
8524
- `;
8525
- }
8526
8713
  function gateConfigToVars(gate) {
8527
8714
  const out = {};
8528
8715
  if (!gate || typeof gate !== "object") return out;
@@ -8533,7 +8720,6 @@ function gateConfigToVars(gate) {
8533
8720
  if (typeof gate.pyVersion === "string" && gate.pyVersion.trim()) out.GATE_PY_VERSION = gate.pyVersion;
8534
8721
  const seconds = typeof gate.maxSeconds === "number" ? String(gate.maxSeconds) : gate.maxSeconds;
8535
8722
  if (typeof seconds === "string" && /^\d+$/.test(seconds.trim()) && Number(seconds) > 0) out.GATE_MAX_SECONDS = seconds.trim();
8536
- if (gate.windowsCompat === true) out.GATE_WINDOWS_COMPAT = "true";
8537
8723
  return out;
8538
8724
  }
8539
8725
  function seedMatchesDeployModel(seed, deployModel) {
@@ -10396,7 +10582,7 @@ function rateLimitedReceipt(opts) {
10396
10582
  };
10397
10583
  }
10398
10584
  async function runWithRateLimitBackoff(operation, opts) {
10399
- const sleep2 = opts.sleep ?? ((ms) => new Promise((resolve5) => setTimeout(resolve5, ms)));
10585
+ const sleep2 = opts.sleep ?? ((ms) => new Promise((resolve6) => setTimeout(resolve6, ms)));
10400
10586
  const now = opts.now ?? Date.now;
10401
10587
  const log = opts.log ?? ((message) => console.warn(message));
10402
10588
  const capMs = opts.capMs ?? RATE_LIMIT_WAIT_CAP_MS;
@@ -10650,6 +10836,69 @@ function flagValue(args, flag) {
10650
10836
  if (i === -1 || i + 1 >= args.length) return void 0;
10651
10837
  return args[i + 1];
10652
10838
  }
10839
+ function isPrCreateRemoteHeadGraphqlNoise(text) {
10840
+ return /Head sha can't be blank/i.test(text) || /Head ref must be a branch/i.test(text) || /No commits between /i.test(text);
10841
+ }
10842
+ function unpushedHeadMessage(head) {
10843
+ return `pr create: head branch '${head}' is not on the remote \u2014 push the branch first`;
10844
+ }
10845
+ function emptyCompareMessage(base, head) {
10846
+ return `pr create: no commits between ${base} and ${head} \u2014 push the branch first if those commits are only local`;
10847
+ }
10848
+ function humanPrCreateRemoteHeadError(text, args) {
10849
+ if (!isPrCreateRemoteHeadGraphqlNoise(text)) return void 0;
10850
+ const head = flagValue(args, "--head") ?? "the head branch";
10851
+ const base = flagValue(args, "--base") ?? "the base";
10852
+ if (/Head sha can't be blank/i.test(text) || /Head ref must be a branch/i.test(text)) {
10853
+ return unpushedHeadMessage(head);
10854
+ }
10855
+ return emptyCompareMessage(base, head);
10856
+ }
10857
+ function isGhHttpNotFound(err) {
10858
+ const text = execErrorText(err);
10859
+ if (httpStatusCodes(text).includes(404)) return true;
10860
+ return /HTTP\s*404|Not Found \(HTTP 404\)|\(404\)/i.test(text);
10861
+ }
10862
+ function isForkStyleHead(head) {
10863
+ return head.includes(":");
10864
+ }
10865
+ async function defaultReadRemoteHead(exec, repo, head) {
10866
+ try {
10867
+ await exec("gh", ["api", `repos/${repo}/git/ref/heads/${encodeURIComponent(head)}`], { timeout: 15e3 });
10868
+ return "present";
10869
+ } catch (e) {
10870
+ if (isGhHttpNotFound(e)) return "absent";
10871
+ return "unknown";
10872
+ }
10873
+ }
10874
+ async function defaultCompareRefs(exec, repo, base, head) {
10875
+ try {
10876
+ const { stdout } = await exec(
10877
+ "gh",
10878
+ ["api", `repos/${repo}/compare/${encodeURIComponent(base)}...${encodeURIComponent(head)}`],
10879
+ { timeout: 15e3 }
10880
+ );
10881
+ const aheadBy = JSON.parse(stdout).ahead_by;
10882
+ return typeof aheadBy === "number" ? { aheadBy } : "unknown";
10883
+ } catch {
10884
+ return "unknown";
10885
+ }
10886
+ }
10887
+ async function preflightPrCreateRemoteHead(args, deps) {
10888
+ const repo = flagValue(args, "--repo");
10889
+ const head = flagValue(args, "--head");
10890
+ if (!repo || !head || isForkStyleHead(head)) return void 0;
10891
+ const readRemoteHead = deps.readRemoteHead ?? ((input) => defaultReadRemoteHead(deps.exec, input.repo, input.head));
10892
+ const presence = await readRemoteHead({ repo, head });
10893
+ if (presence === "absent") return unpushedHeadMessage(head);
10894
+ if (presence !== "present") return void 0;
10895
+ const base = flagValue(args, "--base");
10896
+ if (!base) return void 0;
10897
+ const compareRefs = deps.compareRefs ?? ((input) => defaultCompareRefs(deps.exec, input.repo, input.base, input.head));
10898
+ const compared = await compareRefs({ repo, base, head });
10899
+ if (compared !== "unknown" && compared.aheadBy <= 0) return emptyCompareMessage(base, head);
10900
+ return void 0;
10901
+ }
10653
10902
  function buildPrCreateRetryCommand(args) {
10654
10903
  const parts = ["mmi-cli", "devops", "pr", "create"];
10655
10904
  for (const flag of ["--repo", "--base", "--head", "--title"]) {
@@ -11057,13 +11306,21 @@ async function createPrViaRestFallback(args, swappedArgs, deps, knownPools) {
11057
11306
  }
11058
11307
  async function ghCreate(args, deps = {}) {
11059
11308
  const exec = deps.exec ?? execFileP2;
11060
- const sleep2 = deps.sleep ?? ((ms) => new Promise((resolve5) => setTimeout(resolve5, ms)));
11309
+ const sleep2 = deps.sleep ?? ((ms) => new Promise((resolve6) => setTimeout(resolve6, ms)));
11061
11310
  const now = deps.now ?? Date.now;
11062
11311
  const read = deps.readFile ?? import_promises.readFile;
11063
11312
  const restCreatePr = deps.restCreatePr ?? ((input) => defaultRestCreatePr(exec, input));
11064
11313
  const restCreateIssue = deps.restCreateIssue ?? ((input) => defaultRestCreateIssue(exec, input));
11065
11314
  const findOpenPr = deps.findOpenPr ?? ((input) => defaultFindOpenPr(exec, input));
11066
11315
  const readRateLimit = deps.readRateLimit ?? (() => defaultReadRateLimit(exec));
11316
+ if (args[0] === "pr") {
11317
+ const refusal = await preflightPrCreateRemoteHead(args, {
11318
+ exec,
11319
+ readRemoteHead: deps.readRemoteHead,
11320
+ compareRefs: deps.compareRefs
11321
+ });
11322
+ if (refusal) return fail(refusal);
11323
+ }
11067
11324
  const swapped = await bodyArgsViaFile(args);
11068
11325
  const restDeps = {
11069
11326
  exec,
@@ -11082,6 +11339,8 @@ async function ghCreate(args, deps = {}) {
11082
11339
  } catch (restErr) {
11083
11340
  await swapped.cleanup();
11084
11341
  const restText = execErrorText(restErr);
11342
+ const humanRest = humanPrCreateRemoteHeadError(restText, args);
11343
+ if (humanRest) return fail(humanRest);
11085
11344
  if (isGhRateLimitError(restText)) {
11086
11345
  const pools2 = await readRateLimit();
11087
11346
  return rateLimitedResult({
@@ -11172,6 +11431,10 @@ async function ghCreate(args, deps = {}) {
11172
11431
  message: `${context}: GraphQL rate-limited \u2014 ${rateLimitResetNote(pools.graphql?.reset ?? pools.core?.reset, now())}`
11173
11432
  });
11174
11433
  }
11434
+ if (args[0] === "pr") {
11435
+ const human = humanPrCreateRemoteHeadError(errText, args);
11436
+ if (human) return fail(human);
11437
+ }
11175
11438
  if (isUpstreamGitHubFault(faultText)) return fail(upstreamFaultMessage(args[0], faultText));
11176
11439
  return fail(`gh ${args[0]} create failed: ${(err.stderr || err.stdout || err.message || String(e)).trim()}${note ? ` (${note})` : ""}`);
11177
11440
  }
@@ -11204,9 +11467,9 @@ function isValidSecretKey(key) {
11204
11467
  return KEY_RE.test(key);
11205
11468
  }
11206
11469
  function classifyTier(_slug, key) {
11207
- const slash = key.indexOf("/");
11208
- if (slash === -1) return "project";
11209
- return key.slice(0, slash) === PROJECT_TIER_SEGMENT ? "project" : "org";
11470
+ const slash2 = key.indexOf("/");
11471
+ if (slash2 === -1) return "project";
11472
+ return key.slice(0, slash2) === PROJECT_TIER_SEGMENT ? "project" : "org";
11210
11473
  }
11211
11474
  function secretParamName(slug, key) {
11212
11475
  return `${SSM_ROOT}/${slug}/${key}`;
@@ -11476,8 +11739,8 @@ async function probeCapabilities(deps, repo) {
11476
11739
  }
11477
11740
  }
11478
11741
  function secretKeyLeaf(key) {
11479
- const slash = key.lastIndexOf("/");
11480
- return slash === -1 ? key : key.slice(slash + 1);
11742
+ const slash2 = key.lastIndexOf("/");
11743
+ return slash2 === -1 ? key : key.slice(slash2 + 1);
11481
11744
  }
11482
11745
  function resolveNotFoundGuidance(input) {
11483
11746
  const { key, repo, slug, report } = input;
@@ -12147,8 +12410,8 @@ async function secretsRevoke(deps, repo, login, key, _opts) {
12147
12410
  }
12148
12411
  var SECRET_COPY_BLOCKED_RE = /(?:ENC_KEY|ENCRYPTION_KEY|SECRET_KEY_BASE)/i;
12149
12412
  function isSecretCopyBlocked(key) {
12150
- const slash = key.indexOf("/");
12151
- const leaf = slash === -1 ? key : key.slice(slash + 1);
12413
+ const slash2 = key.indexOf("/");
12414
+ const leaf = slash2 === -1 ? key : key.slice(slash2 + 1);
12152
12415
  return SECRET_COPY_BLOCKED_RE.test(leaf);
12153
12416
  }
12154
12417
  function copyTierKey(stage, leaf) {
@@ -12303,9 +12566,9 @@ function parseSecretsUseArgv(tail) {
12303
12566
  const flags = {};
12304
12567
  const keys = [];
12305
12568
  const firstSep = tail.indexOf("--");
12306
- const sep3 = firstSep !== -1 && separatorIsOurs(tail.slice(0, firstSep)) ? firstSep : -1;
12307
- const head = sep3 === -1 ? tail : tail.slice(0, sep3);
12308
- let command = sep3 === -1 ? [] : tail.slice(sep3 + 1).slice();
12569
+ const sep4 = firstSep !== -1 && separatorIsOurs(tail.slice(0, firstSep)) ? firstSep : -1;
12570
+ const head = sep4 === -1 ? tail : tail.slice(0, sep4);
12571
+ let command = sep4 === -1 ? [] : tail.slice(sep4 + 1).slice();
12309
12572
  for (let i = 0; i < head.length; ) {
12310
12573
  const tok = head[i];
12311
12574
  const eq = tok.indexOf("=");
@@ -12367,8 +12630,8 @@ var PRE_SPAWN_DRAIN_TIMEOUT_MS = 2e3;
12367
12630
  async function drainHttpPoolBeforeSpawn() {
12368
12631
  const drained = await Promise.race([
12369
12632
  closeHttpPool().then(() => true),
12370
- new Promise((resolve5) => {
12371
- setTimeout(() => resolve5(false), PRE_SPAWN_DRAIN_TIMEOUT_MS).unref?.();
12633
+ new Promise((resolve6) => {
12634
+ setTimeout(() => resolve6(false), PRE_SPAWN_DRAIN_TIMEOUT_MS).unref?.();
12372
12635
  })
12373
12636
  ]);
12374
12637
  if (!drained) destroyHttpPool();
@@ -12868,10 +13131,10 @@ var rollout_plan_default = {
12868
13131
  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)."
12869
13132
  },
12870
13133
  baseline: {
12871
- version: "4.1.3",
12872
- tag: "v4.1.3",
12873
- commit: "3164484f3f37",
12874
- npm: "@mutmutco/cli@4.1.3"
13134
+ version: "4.1.4",
13135
+ tag: "v4.1.4",
13136
+ commit: "5b2b9ef75abf",
13137
+ npm: "@mutmutco/cli@4.1.4"
12875
13138
  },
12876
13139
  exitCriterion: "fleet-n-of-n",
12877
13140
  hubOnlyShortcut: "forbidden",
@@ -12888,14 +13151,14 @@ var rollout_plan_default = {
12888
13151
  repo: "mutmutco/mmi-hub",
12889
13152
  role: "canary",
12890
13153
  schedule: "train",
12891
- v3Target: "v4.1.3"
13154
+ v3Target: "v4.1.4"
12892
13155
  }
12893
13156
  ],
12894
13157
  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.",
12895
13158
  rollback: {
12896
13159
  independent: true,
12897
- mechanism: "npm dist-tag latest -> 4.1.3 and redeploy the Hub Lambda from tag v4.1.3 (3164484f3f37); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
12898
- v3Target: "v4.1.3 (@mutmutco/cli@4.1.3, tag commit 3164484f3f37 \u2014 last known-good release carrying the repo-index v4-only contract)"
13160
+ mechanism: "npm dist-tag latest -> 4.1.4 and redeploy the Hub Lambda from tag v4.1.4 (5b2b9ef75abf); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
13161
+ v3Target: "v4.1.4 (@mutmutco/cli@4.1.4, tag commit 5b2b9ef75abf \u2014 last known-good release carrying the repo-index v4-only contract)"
12899
13162
  }
12900
13163
  },
12901
13164
  {
@@ -13402,6 +13665,10 @@ var DEFAULT_LIMIT = 30;
13402
13665
  var MAX_LIMIT = 100;
13403
13666
  var CHILDREN_MAX_TOTAL = 100;
13404
13667
  var CHILDREN_MAX_DEPTH = 6;
13668
+ var BOARD_STATUS_ALIAS_BATCH = 25;
13669
+ var BOARD_STATUS_LOOKUP_CONCURRENCY = 3;
13670
+ var CHILD_PROJECT_ITEMS_PAGE = 50;
13671
+ var CHILD_PROJECT_ITEMS_FIELDS = `projectItems(first:${CHILD_PROJECT_ITEMS_PAGE}){nodes{project{id} fieldValues(first:20){nodes{... on ProjectV2ItemFieldSingleSelectValue{name field{...on ProjectV2SingleSelectField{name}}}}}}}`;
13405
13672
  var ISSUE_LIST_FIELDS = "number,title,state,url,assignees,labels";
13406
13673
  var PR_LIST_FIELDS = "number,title,state,url,headRefName,baseRefName";
13407
13674
  var QueryReadError = class extends Error {
@@ -13485,23 +13752,43 @@ async function runIssueList(deps, opts) {
13485
13752
  return shapeIssueList(rows);
13486
13753
  }
13487
13754
  function childrenGraphqlArgs(owner, name, number) {
13488
- const query = "query($owner:String!,$name:String!){repository(owner:$owner,name:$name){issue(number:" + number + "){number subIssues(first:100){nodes{number title state url assignees(first:10){nodes{login}} repository{nameWithOwner} projectItems(first:5){nodes{project{id} fieldValues(first:20){nodes{... on ProjectV2ItemFieldSingleSelectValue{name field{...on ProjectV2SingleSelectField{name}}}}}}} timelineItems(first:30,itemTypes:[CROSS_REFERENCED_EVENT]){nodes{...on CrossReferencedEvent{source{...on PullRequest{number title state url}}}}}}}}}}";
13755
+ const query = "query($owner:String!,$name:String!){repository(owner:$owner,name:$name){issue(number:" + number + "){number subIssues(first:100){nodes{number title state url assignees(first:10){nodes{login}} repository{nameWithOwner} " + CHILD_PROJECT_ITEMS_FIELDS + " timelineItems(first:30,itemTypes:[CROSS_REFERENCED_EVENT]){nodes{...on CrossReferencedEvent{source{...on PullRequest{number title state url}}}}}}}}}}";
13489
13756
  return ["api", "graphql", "-f", `query=${query}`, "-f", `owner=${owner}`, "-f", `name=${name}`];
13490
13757
  }
13491
- function shapeChildNode(node, depth, boardProjectId) {
13492
- const n = node ?? {};
13493
- const assigneeNodes = n.assignees?.nodes ?? [];
13494
- let boardStatus = null;
13495
- for (const pi of n.projectItems?.nodes ?? []) {
13496
- if (!boardProjectId || pi?.project?.id !== boardProjectId) continue;
13758
+ function childrenBoardStatusGraphqlArgs(owner, name, numbers) {
13759
+ const aliases = numbers.map((n, i) => `i${i}:issue(number:${n}){number ${CHILD_PROJECT_ITEMS_FIELDS}}`).join(" ");
13760
+ const query = `query($owner:String!,$name:String!){repository(owner:$owner,name:$name){${aliases}}}`;
13761
+ return ["api", "graphql", "-f", `query=${query}`, "-f", `owner=${owner}`, "-f", `name=${name}`];
13762
+ }
13763
+ function boardStatusFromProjectItems(nodes, boardProjectId) {
13764
+ if (!boardProjectId) return null;
13765
+ for (const pi of Array.isArray(nodes) ? nodes : []) {
13766
+ if (pi?.project?.id !== boardProjectId) continue;
13497
13767
  const status = (pi?.fieldValues?.nodes ?? []).find(
13498
13768
  (fv) => fv?.field?.name === "Status" && fv?.name
13499
13769
  );
13500
- if (status) {
13501
- boardStatus = String(status.name);
13502
- break;
13503
- }
13770
+ if (status) return String(status.name);
13771
+ }
13772
+ return null;
13773
+ }
13774
+ function extractIssueBoardStatusMap(resp, boardProjectId) {
13775
+ const map = /* @__PURE__ */ new Map();
13776
+ const repo = resp?.data?.repository;
13777
+ if (!repo || typeof repo !== "object") return map;
13778
+ for (const node of Object.values(repo)) {
13779
+ if (!node || typeof node !== "object") continue;
13780
+ const n = node;
13781
+ const number = Number(n.number);
13782
+ if (!Number.isFinite(number)) continue;
13783
+ const status = boardStatusFromProjectItems(n.projectItems?.nodes, boardProjectId);
13784
+ if (status) map.set(number, status);
13504
13785
  }
13786
+ return map;
13787
+ }
13788
+ function shapeChildNode(node, depth, boardProjectId) {
13789
+ const n = node ?? {};
13790
+ const assigneeNodes = n.assignees?.nodes ?? [];
13791
+ const boardStatus = boardStatusFromProjectItems(n.projectItems?.nodes, boardProjectId);
13505
13792
  const linkedPrs = [];
13506
13793
  const seenPr = /* @__PURE__ */ new Set();
13507
13794
  for (const ev of n.timelineItems?.nodes ?? []) {
@@ -13550,6 +13837,42 @@ async function queryChildren(deps, owner, name, number) {
13550
13837
  }
13551
13838
  return extractChildrenResponse(resp);
13552
13839
  }
13840
+ async function fillMissingChildBoardStatus(deps, children, boardProjectId) {
13841
+ if (!boardProjectId) return;
13842
+ const missing = children.filter((c) => !c.boardStatus && trySplitRepo(c.repo));
13843
+ if (!missing.length) return;
13844
+ const byRepo = /* @__PURE__ */ new Map();
13845
+ for (const c of missing) {
13846
+ const nums = byRepo.get(c.repo) ?? [];
13847
+ if (!nums.includes(c.number)) nums.push(c.number);
13848
+ byRepo.set(c.repo, nums);
13849
+ }
13850
+ const jobs = [];
13851
+ for (const [repo, numbers] of byRepo) {
13852
+ for (let i = 0; i < numbers.length; i += BOARD_STATUS_ALIAS_BATCH) {
13853
+ jobs.push({ repo, numbers: numbers.slice(i, i + BOARD_STATUS_ALIAS_BATCH) });
13854
+ }
13855
+ }
13856
+ const maps = await mapBounded(jobs, BOARD_STATUS_LOOKUP_CONCURRENCY, async (job) => {
13857
+ try {
13858
+ const { owner, name } = splitRepo(job.repo);
13859
+ const resp = await deps.ghJson(childrenBoardStatusGraphqlArgs(owner, name, job.numbers), GH_LIST_TIMEOUT_MS);
13860
+ return extractIssueBoardStatusMap(resp, boardProjectId);
13861
+ } catch {
13862
+ return /* @__PURE__ */ new Map();
13863
+ }
13864
+ });
13865
+ const resolved = /* @__PURE__ */ new Map();
13866
+ for (let i = 0; i < jobs.length; i++) {
13867
+ const job = jobs[i];
13868
+ for (const [num, status] of maps[i] ?? []) resolved.set(childKey(job.repo, num), status);
13869
+ }
13870
+ for (const c of children) {
13871
+ if (c.boardStatus) continue;
13872
+ const status = resolved.get(childKey(c.repo, c.number));
13873
+ if (status) c.boardStatus = status;
13874
+ }
13875
+ }
13553
13876
  async function runIssueChildren(deps, epic, opts) {
13554
13877
  const ref = parseIssueRef(epic);
13555
13878
  const repo = ref.repo ?? await deps.resolveRepo(void 0);
@@ -13566,7 +13889,10 @@ async function runIssueChildren(deps, epic, opts) {
13566
13889
  out.push(child2);
13567
13890
  seen.add(childKey(child2.repo, child2.number));
13568
13891
  }
13569
- if (!opts.recursive || out.length >= CHILDREN_MAX_TOTAL) return out;
13892
+ if (!opts.recursive || out.length >= CHILDREN_MAX_TOTAL) {
13893
+ await fillMissingChildBoardStatus(deps, out, boardProjectId);
13894
+ return out;
13895
+ }
13570
13896
  const queue = [];
13571
13897
  for (const child2 of out) {
13572
13898
  const sp = trySplitRepo(child2.repo);
@@ -13592,6 +13918,7 @@ async function runIssueChildren(deps, epic, opts) {
13592
13918
  if (sp) queue.push({ owner: sp.owner, name: sp.name, number: child2.number, depth: frame.depth + 1 });
13593
13919
  }
13594
13920
  }
13921
+ await fillMissingChildBoardStatus(deps, out, boardProjectId);
13595
13922
  return out;
13596
13923
  }
13597
13924
  var FRONTIER_BUCKET_ORDER = {
@@ -13990,7 +14317,7 @@ function registerQueryCommands(program3) {
13990
14317
  queryFail("issue list", e);
13991
14318
  }
13992
14319
  });
13993
- issue2.command("children <epic>").description("walk an epic's native sub-issue tree \u2014 each child: number/title/state/repo/assignee/boardStatus/linkedPrs; --recursive walks deeper").option("--recursive", "walk the full sub-issue tree (bounded depth + total cap)").option("--repo <owner/repo>", "repo for a bare epic ref (defaults to the current repo)").option("--json", "machine-readable output (already the default \u2014 accepted for contract uniformity)").action(async (epic, o) => {
14320
+ issue2.command("children <epic>").description("walk an epic's native sub-issue tree \u2014 each child: number/title/state/repo/assignee/boardStatus/linkedPrs (boardStatus null is inconclusive, not off-board); --recursive walks deeper").option("--recursive", "walk the full sub-issue tree (bounded depth + total cap)").option("--repo <owner/repo>", "repo for a bare epic ref (defaults to the current repo)").option("--json", "machine-readable output (already the default \u2014 accepted for contract uniformity)").action(async (epic, o) => {
13994
14321
  try {
13995
14322
  const childrenDeps = { ...deps, resolveRepo: async (r) => deps.resolveRepo(r ?? o.repo) };
13996
14323
  const boardProjectId = await resolveBoardProjectId(o.repo);
@@ -14913,6 +15240,183 @@ rollback stays available: mmi-cli devops train enforce --apply --disarm`
14913
15240
  });
14914
15241
  }
14915
15242
 
15243
+ // src/actions-billing-preflight.ts
15244
+ var CANARY_WORKFLOW = "actions-job-start-canary.yml";
15245
+ var CANARY_REPO = "mutmutco/MMI-Hub";
15246
+ var ACTIONS_BILLING_BLOCK_RE = /job was not started because recent account payments have failed|account payments have failed|spending limit needs to be increased|Billing & plans/i;
15247
+ var CANARY_POLLS = 20;
15248
+ var CANARY_POLL_MS = 1e3;
15249
+ function isActionsBillingBlockText(text) {
15250
+ return ACTIONS_BILLING_BLOCK_RE.test(text);
15251
+ }
15252
+ function interpretActionsJobStart(input) {
15253
+ const text = input.text ?? "";
15254
+ if (isActionsBillingBlockText(text)) return "billing-blocked";
15255
+ const jobs = input.jobs ?? [];
15256
+ if (jobs.some((job) => isActionsBillingBlockText(JSON.stringify(job)))) return "billing-blocked";
15257
+ if (jobs.some((job) => Array.isArray(job.steps) && job.steps.length > 0 || Boolean(job.startedAt))) {
15258
+ return "started";
15259
+ }
15260
+ if (jobs.length > 0 && jobs.every((job) => {
15261
+ const finished = job.conclusion === "failure" || job.status === "completed";
15262
+ const empty = !Array.isArray(job.steps) || job.steps.length === 0;
15263
+ return finished && empty && !job.startedAt;
15264
+ })) {
15265
+ return "never-started";
15266
+ }
15267
+ return "pending";
15268
+ }
15269
+ function actionsBillingRefusal(detail) {
15270
+ return new Error(
15271
+ `release refused: GitHub Actions cannot start a hosted job (billing/spending). ${detail} Fix Billing & plans / the org spending limit, then rerun. Do not mint a new tag. If a tag is already on origin, use \`mmi-cli devops release --retry-publish <run-id> --apply\` on that exact run \u2014 never recut.`
15272
+ );
15273
+ }
15274
+ function parseJson(raw, label) {
15275
+ try {
15276
+ return JSON.parse(raw);
15277
+ } catch {
15278
+ throw new Error(`${label} was not valid JSON`);
15279
+ }
15280
+ }
15281
+ async function scanRepoForBillingBlock(deps, repo) {
15282
+ let raw;
15283
+ try {
15284
+ raw = await deps.run("gh", [
15285
+ "run",
15286
+ "list",
15287
+ "--repo",
15288
+ repo,
15289
+ "--limit",
15290
+ "8",
15291
+ "--json",
15292
+ "databaseId,conclusion,status,displayTitle,url"
15293
+ ]);
15294
+ } catch {
15295
+ return void 0;
15296
+ }
15297
+ const rows = parseJson(raw, `gh run list --repo ${repo}`);
15298
+ if (!Array.isArray(rows)) return void 0;
15299
+ for (const row of rows) {
15300
+ const blob = `${row.displayTitle ?? ""} ${row.conclusion ?? ""} ${row.url ?? ""}`;
15301
+ if (isActionsBillingBlockText(blob)) {
15302
+ return `${repo} run ${row.databaseId ?? row.url ?? "(unknown)"} already names a billing/spending block`;
15303
+ }
15304
+ if (row.conclusion !== "failure" && row.status !== "completed") continue;
15305
+ if (typeof row.databaseId !== "number") continue;
15306
+ try {
15307
+ const view = await deps.run("gh", [
15308
+ "run",
15309
+ "view",
15310
+ String(row.databaseId),
15311
+ "--repo",
15312
+ repo,
15313
+ "--json",
15314
+ "jobs,conclusion,displayTitle,url"
15315
+ ]);
15316
+ const parsed = parseJson(view, `gh run view ${row.databaseId}`);
15317
+ const verdict = interpretActionsJobStart({
15318
+ jobs: parsed.jobs,
15319
+ text: `${parsed.displayTitle ?? ""} ${parsed.conclusion ?? ""} ${view}`
15320
+ });
15321
+ if (verdict === "billing-blocked" || verdict === "never-started") {
15322
+ return `${repo} run ${row.databaseId} (${parsed.url ?? row.url ?? "no url"}): hosted job never started` + (verdict === "billing-blocked" ? " (billing/spending refusal)" : " (empty steps)");
15323
+ }
15324
+ } catch {
15325
+ }
15326
+ }
15327
+ return void 0;
15328
+ }
15329
+ async function correlateCanaryRun(deps, nonce) {
15330
+ const sleep2 = deps.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
15331
+ let lastError = "no matching canary run";
15332
+ for (let attempt = 0; attempt < 8; attempt++) {
15333
+ if (attempt > 0) await sleep2(CANARY_POLL_MS);
15334
+ let raw;
15335
+ try {
15336
+ raw = await deps.run("gh", [
15337
+ "run",
15338
+ "list",
15339
+ "--repo",
15340
+ CANARY_REPO,
15341
+ "--workflow",
15342
+ CANARY_WORKFLOW,
15343
+ "--limit",
15344
+ "10",
15345
+ "--json",
15346
+ "databaseId,displayTitle,status,createdAt"
15347
+ ]);
15348
+ } catch (e) {
15349
+ lastError = e instanceof Error ? e.message : String(e);
15350
+ continue;
15351
+ }
15352
+ const rows = parseJson(
15353
+ raw,
15354
+ "canary gh run list"
15355
+ );
15356
+ const match = rows.find((row) => (row.displayTitle ?? "").includes(nonce) && typeof row.databaseId === "number");
15357
+ if (match?.databaseId) return match.databaseId;
15358
+ }
15359
+ throw new Error(
15360
+ `could not correlate ${CANARY_WORKFLOW} on ${CANARY_REPO} (nonce ${nonce}): ${lastError}`
15361
+ );
15362
+ }
15363
+ async function assertActionsJobsCanStart(deps, targetRepo3) {
15364
+ const scanned = await scanRepoForBillingBlock(deps, targetRepo3);
15365
+ if (scanned) throw actionsBillingRefusal(scanned);
15366
+ if (targetRepo3.toLowerCase() !== CANARY_REPO.toLowerCase()) {
15367
+ const hubScan = await scanRepoForBillingBlock(deps, CANARY_REPO);
15368
+ if (hubScan) throw actionsBillingRefusal(hubScan);
15369
+ }
15370
+ const nonce = `5604-${(deps.now ?? Date.now)().toString(36)}`;
15371
+ try {
15372
+ await deps.run("gh", [
15373
+ "workflow",
15374
+ "run",
15375
+ CANARY_WORKFLOW,
15376
+ "--repo",
15377
+ CANARY_REPO,
15378
+ "-f",
15379
+ `nonce=${nonce}`
15380
+ ]);
15381
+ } catch (e) {
15382
+ throw actionsBillingRefusal(
15383
+ `could not dispatch ${CANARY_WORKFLOW} on ${CANARY_REPO}: ${e instanceof Error ? e.message : String(e)}. A hosted publish job may not start (the v1.54.2 class).`
15384
+ );
15385
+ }
15386
+ const runId = await correlateCanaryRun(deps, nonce);
15387
+ const sleep2 = deps.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
15388
+ let last = "pending";
15389
+ let url = `https://github.com/${CANARY_REPO}/actions/runs/${runId}`;
15390
+ for (let attempt = 0; attempt < CANARY_POLLS; attempt++) {
15391
+ if (attempt > 0) await sleep2(CANARY_POLL_MS);
15392
+ const view = await deps.run("gh", [
15393
+ "run",
15394
+ "view",
15395
+ String(runId),
15396
+ "--repo",
15397
+ CANARY_REPO,
15398
+ "--json",
15399
+ "jobs,status,conclusion,url,displayTitle"
15400
+ ]);
15401
+ const parsed = parseJson(view, `canary gh run view ${runId}`);
15402
+ if (parsed.url) url = parsed.url;
15403
+ last = interpretActionsJobStart({
15404
+ jobs: parsed.jobs,
15405
+ text: `${parsed.displayTitle ?? ""} ${parsed.conclusion ?? ""} ${view}`
15406
+ });
15407
+ if (last === "started") {
15408
+ await deps.run("gh", ["run", "cancel", String(runId), "--repo", CANARY_REPO]).catch(() => "");
15409
+ return;
15410
+ }
15411
+ if (last === "billing-blocked" || last === "never-started") {
15412
+ throw actionsBillingRefusal(`${url}: hosted canary ${last}`);
15413
+ }
15414
+ }
15415
+ throw actionsBillingRefusal(
15416
+ `${url}: hosted canary stayed ${last} after ${CANARY_POLLS} polls \u2014 cannot prove a hosted job can start`
15417
+ );
15418
+ }
15419
+
14916
15420
  // src/train-apply.ts
14917
15421
  var TRAIN_BUMP_INTENTS = ["major", "minor", "patch"];
14918
15422
  function reduceFollowUpOutcomes(outcomes) {
@@ -15298,7 +15802,7 @@ async function runMergeTreePreflight(deps, ours, theirs) {
15298
15802
  async function predictMergeConflicts(deps, ours, theirs) {
15299
15803
  return runMergeTreePreflight(deps, ours, theirs);
15300
15804
  }
15301
- async function mergeWithToleratedResolution(deps, sourceRef, label, resolve5, extraTolerated = []) {
15805
+ async function mergeWithToleratedResolution(deps, sourceRef, label, resolve6, extraTolerated = []) {
15302
15806
  try {
15303
15807
  await deps.run("git", ["merge", sourceRef, "--no-edit"]);
15304
15808
  return;
@@ -15312,7 +15816,7 @@ async function mergeWithToleratedResolution(deps, sourceRef, label, resolve5, ex
15312
15816
  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)`
15313
15817
  );
15314
15818
  }
15315
- await deps.run("git", ["checkout", `--${resolve5}`, "--", ...unmerged]);
15819
+ await deps.run("git", ["checkout", `--${resolve6}`, "--", ...unmerged]);
15316
15820
  await deps.run("git", ["add", "--", ...unmerged]);
15317
15821
  await deps.run("git", ["commit", "--no-edit"]);
15318
15822
  }
@@ -15460,7 +15964,7 @@ var CORRELATE_SKEW_SLACK_MS = 1e4;
15460
15964
  var CORRELATE_PAGE_LIMIT = 50;
15461
15965
  var RUN_CONFIRM_ATTEMPTS = 3;
15462
15966
  var RUN_CONFIRM_DELAY_MS = 1e3;
15463
- var defaultSleep = (ms) => new Promise((resolve5) => setTimeout(resolve5, ms));
15967
+ var defaultSleep = (ms) => new Promise((resolve6) => setTimeout(resolve6, ms));
15464
15968
  function resolveSleep(deps) {
15465
15969
  return deps.sleep ?? defaultSleep;
15466
15970
  }
@@ -16348,6 +16852,7 @@ async function preflight(deps, ctx, stage, meta) {
16348
16852
  throw new Error(`${ctx.repo} is not Hub-deployed (deployModel=none) \u2014 the release train does not apply; use the project's own release path`);
16349
16853
  }
16350
16854
  await deps.runSelf(["secrets", "preflight", "--stage", stage, "--repo", ctx.repo]);
16855
+ await assertActionsJobsCanStart(deps, ctx.repo);
16351
16856
  enforceGateBudget(deps, ctx.repo);
16352
16857
  if (model === "hub-serverless") {
16353
16858
  await deps.run("node", ["scripts/release-distribution.mjs", "verify-deps"]);
@@ -18525,7 +19030,7 @@ async function mergeAutoWithTransientRetry(prNumber, repo, deps) {
18525
19030
  if (first.mergeStatus !== "failed") return first;
18526
19031
  const ready = await deps.probeMergeReady(prNumber, repo).catch(() => ({ open: false, mergeable: false, checksPassing: false }));
18527
19032
  if (!ready.open || !ready.mergeable || !ready.checksPassing) return first;
18528
- const sleep2 = deps.sleep ?? ((ms) => new Promise((resolve5) => setTimeout(resolve5, ms)));
19033
+ const sleep2 = deps.sleep ?? ((ms) => new Promise((resolve6) => setTimeout(resolve6, ms)));
18529
19034
  await sleep2(PR_LAND_MERGE_RETRY_DELAY_MS);
18530
19035
  const retried = await deps.mergeAuto(prNumber, repo);
18531
19036
  if (retried.mergeStatus !== "failed") return retried;
@@ -18536,7 +19041,7 @@ var AUTO_MERGE_CONFIRM_DELAY_MS = 3e3;
18536
19041
  async function confirmAutoMergeEnqueued(deps, options) {
18537
19042
  const retries = options?.retries ?? AUTO_MERGE_CONFIRM_RETRIES;
18538
19043
  const delayMs = options?.delayMs ?? AUTO_MERGE_CONFIRM_DELAY_MS;
18539
- const sleep2 = deps.sleep ?? ((ms) => new Promise((resolve5) => setTimeout(resolve5, ms)));
19044
+ const sleep2 = deps.sleep ?? ((ms) => new Promise((resolve6) => setTimeout(resolve6, ms)));
18540
19045
  for (let attempt = 0; attempt < retries; attempt++) {
18541
19046
  if (await deps.readMerged().catch(() => false)) return "merged";
18542
19047
  const stuck = await deps.readAutoMergeRequest().then((s) => s.trim()).catch(() => "");
@@ -18553,7 +19058,7 @@ async function confirmAutoMergeEnqueued(deps, options) {
18553
19058
  async function readGhPrStateWithRetry(fetchState, options) {
18554
19059
  const retries = options?.retries ?? PR_LAND_STATE_READ_RETRIES;
18555
19060
  const delayMs = options?.delayMs ?? PR_LAND_STATE_READ_DELAY_MS;
18556
- const sleep2 = options?.sleep ?? ((ms) => new Promise((resolve5) => setTimeout(resolve5, ms)));
19061
+ const sleep2 = options?.sleep ?? ((ms) => new Promise((resolve6) => setTimeout(resolve6, ms)));
18557
19062
  let lastError = "empty state";
18558
19063
  for (let attempt = 0; attempt < retries; attempt++) {
18559
19064
  try {
@@ -18687,7 +19192,7 @@ function healthPollIntervalMs() {
18687
19192
  return HEALTH_POLL_INTERVAL_MS;
18688
19193
  }
18689
19194
  function waitForProcessStability(child2, graceMs = earlyExitGraceMs()) {
18690
- return new Promise((resolve5, reject) => {
19195
+ return new Promise((resolve6, reject) => {
18691
19196
  let settled = false;
18692
19197
  const finish = (fn) => {
18693
19198
  if (settled) return;
@@ -18697,7 +19202,7 @@ function waitForProcessStability(child2, graceMs = earlyExitGraceMs()) {
18697
19202
  child2.removeAllListeners("exit");
18698
19203
  fn();
18699
19204
  };
18700
- const timer = setTimeout(() => finish(resolve5), graceMs);
19205
+ const timer = setTimeout(() => finish(resolve6), graceMs);
18701
19206
  child2.on("error", (err) => finish(() => reject(new Error(`stage process failed to start: ${err.message}`))));
18702
19207
  child2.on("exit", (code, signal) => {
18703
19208
  const detail = code != null ? `code ${code}` : signal ? `signal ${signal}` : "unknown reason";
@@ -18903,10 +19408,10 @@ function pickStagePort(range, isFree) {
18903
19408
  throw new Error(`no free stage port in range ${start}-${end} \u2014 every port is in use`);
18904
19409
  }
18905
19410
  function isPortFree(port) {
18906
- return new Promise((resolve5) => {
19411
+ return new Promise((resolve6) => {
18907
19412
  const srv = (0, import_node_net.createServer)();
18908
- srv.once("error", () => resolve5(false));
18909
- srv.once("listening", () => srv.close(() => resolve5(true)));
19413
+ srv.once("error", () => resolve6(false));
19414
+ srv.once("listening", () => srv.close(() => resolve6(true)));
18910
19415
  srv.listen(port, "127.0.0.1");
18911
19416
  });
18912
19417
  }
@@ -19117,7 +19622,7 @@ async function killTree(pid) {
19117
19622
  } catch {
19118
19623
  }
19119
19624
  }
19120
- await new Promise((resolve5) => setTimeout(resolve5, 500));
19625
+ await new Promise((resolve6) => setTimeout(resolve6, 500));
19121
19626
  try {
19122
19627
  process.kill(-pid, "SIGKILL");
19123
19628
  } catch {
@@ -19138,7 +19643,7 @@ async function waitForHealth(url, timeoutMs, anyStatus = false) {
19138
19643
  } catch (e) {
19139
19644
  last = e.message;
19140
19645
  }
19141
- await new Promise((resolve5) => setTimeout(resolve5, healthPollIntervalMs()));
19646
+ await new Promise((resolve6) => setTimeout(resolve6, healthPollIntervalMs()));
19142
19647
  }
19143
19648
  throw new Error(`stage health check timed out for ${url}${last ? ` (${last})` : ""}`);
19144
19649
  }
@@ -19373,12 +19878,12 @@ async function executeWaveLand(plan, deps) {
19373
19878
  }
19374
19879
 
19375
19880
  // src/index.ts
19376
- var import_node_os19 = require("node:os");
19881
+ var import_node_os21 = require("node:os");
19377
19882
 
19378
19883
  // src/board.ts
19379
19884
  var import_node_child_process9 = require("node:child_process");
19380
- var import_node_fs19 = require("node:fs");
19381
- var import_node_os8 = require("node:os");
19885
+ var import_node_fs20 = require("node:fs");
19886
+ var import_node_os9 = require("node:os");
19382
19887
  var import_node_path17 = require("node:path");
19383
19888
  var import_node_util6 = require("node:util");
19384
19889
  init_github_client();
@@ -19826,7 +20331,7 @@ async function postIssueComment(client, input) {
19826
20331
  var SKILL_LESSON_LABEL = "skill-lesson";
19827
20332
  var SKILL_LESSON_FILE_LABELS = [SKILL_LESSON_LABEL, LEARNING_LABEL];
19828
20333
  var SKILL_LESSON_LOOP_KIND = "lesson";
19829
- var SKILL_NAMES = ["bootstrap", "browser-automation", "doctor", "epic", "hotfix", "mmi", "onboard", "rcand", "release", "resume", "secrets", "stage"];
20334
+ var SKILL_NAMES = ["bootstrap", "browser-automation", "doctor", "epic", "hotfix", "mmi", "onboard", "rcand", "release", "repo-index-audit", "resume", "secrets", "stage"];
19830
20335
  function assertSkillName(name) {
19831
20336
  const match = SKILL_NAMES.find((skill) => skill === name);
19832
20337
  if (!match) throw new Error(`unknown skill "${name}" \u2014 expected one of: ${SKILL_NAMES.join(", ")}`);
@@ -19858,7 +20363,7 @@ function findDuplicateLesson(source, openLessons) {
19858
20363
 
19859
20364
  // src/session-identity.ts
19860
20365
  var import_node_crypto4 = require("node:crypto");
19861
- var import_node_os7 = require("node:os");
20366
+ var import_node_os8 = require("node:os");
19862
20367
  init_plugin_guard_io();
19863
20368
  var SESSION_ID_ENV_VARS = [
19864
20369
  "MMI_SESSION_ID",
@@ -19894,7 +20399,7 @@ function describeSessionIdentity(env = process.env) {
19894
20399
  return {
19895
20400
  session: readSessionId(env) ?? fallbackSessionId(surface),
19896
20401
  surface,
19897
- host: (0, import_node_os7.hostname)()
20402
+ host: (0, import_node_os8.hostname)()
19898
20403
  };
19899
20404
  }
19900
20405
 
@@ -20530,7 +21035,14 @@ async function prepareClaimContext(options, selectors, deps, collected) {
20530
21035
  report[scope].claimable = filtered.claimable;
20531
21036
  report.warnings.push(...filtered.warnings);
20532
21037
  }
20533
- return { cfg, client, items: collected.items, writable: writableOrUnknown(writable), report };
21038
+ return {
21039
+ cfg,
21040
+ client,
21041
+ items: collected.items,
21042
+ writable: writableOrUnknown(writable),
21043
+ report,
21044
+ session: deps.session ?? describeSessionIdentity()
21045
+ };
20534
21046
  }
20535
21047
  async function claimOneBoardItem(ctx, selector, options) {
20536
21048
  const { cfg, client, report } = ctx;
@@ -20575,26 +21087,29 @@ async function claimOneBoardItem(ctx, selector, options) {
20575
21087
  const verdict = evaluateClaim(fresh, assignedLogin);
20576
21088
  if (!verdict.ok) throw new Error(verdict.reason);
20577
21089
  item = fresh;
21090
+ const refuseIfContested = async () => {
21091
+ if (options.force) return;
21092
+ const contest = await checkLaneContest(client, item, ctx.session);
21093
+ if (contest.contested) throw new Error(laneContestMessage(item.ref, contest, "claim"));
21094
+ };
21095
+ await refuseIfContested();
20578
21096
  if (verdict.alreadyClaimed) {
20579
- if (!options.force) {
20580
- const contest = await checkLaneContest(client, item);
20581
- if (contest.contested) throw new Error(laneContestMessage(item.ref, contest, "claim"));
20582
- }
20583
21097
  if (options.check) {
20584
21098
  return { item, viewer: report.viewer, repo: report.repo, status: "In Progress", partial: false, alreadyClaimed: true, checked: true };
20585
21099
  }
20586
- await postClaimMarkerComment(client, item);
21100
+ await postClaimMarkerComment(client, item, ctx.session);
20587
21101
  return { item, viewer: report.viewer, repo: report.repo, status: "In Progress", partial: false, alreadyClaimed: true };
20588
21102
  }
20589
21103
  if (options.check) {
20590
21104
  return { item, viewer: report.viewer, repo: report.repo, status: item.status, partial: false, checked: true };
20591
21105
  }
21106
+ await refuseIfContested();
20592
21107
  try {
20593
21108
  await client.rest("POST", `repos/${item.repository}/issues/${item.number}/assignees`, { body: { assignees: [assignedLogin] } });
20594
21109
  } catch (e) {
20595
21110
  throw new Error(`claim failed before board status changed: ${ghError(e)}`);
20596
21111
  }
20597
- await postClaimMarkerComment(client, item);
21112
+ await postClaimMarkerComment(client, item, ctx.session);
20598
21113
  try {
20599
21114
  await updateItemSingleSelect(client, cfg.projectId, item.itemId, cfg.statusFieldId, cfg.statusOptions["In Progress"]);
20600
21115
  } catch (e) {
@@ -20787,7 +21302,7 @@ async function setBoardItemPriority(client, cfg, itemId, priority) {
20787
21302
  await updateItemSingleSelect(client, cfg.projectId, itemId, cfg.priorityFieldId, optionId);
20788
21303
  return cliPriorityToFieldName(priority);
20789
21304
  }
20790
- var defaultRetrySleep = (ms) => new Promise((resolve5) => setTimeout(resolve5, ms));
21305
+ var defaultRetrySleep = (ms) => new Promise((resolve6) => setTimeout(resolve6, ms));
20791
21306
  async function resolveProjectItemIdWithRetry(client, cfg, selector, opts = {}) {
20792
21307
  const attempts = Math.max(1, opts.attempts ?? 5);
20793
21308
  const delayMs = opts.delayMs ?? 300;
@@ -21220,9 +21735,8 @@ function boardItemClaim(item) {
21220
21735
  currentlyClaimed: item.assignees.length > 0 && item.status === "In Progress"
21221
21736
  };
21222
21737
  }
21223
- async function postClaimMarkerComment(client, item) {
21738
+ async function postClaimMarkerComment(client, item, actor = describeSessionIdentity()) {
21224
21739
  try {
21225
- const actor = describeSessionIdentity();
21226
21740
  const marker = {
21227
21741
  v: 1,
21228
21742
  session: actor.session,
@@ -21245,7 +21759,7 @@ var CLAIM_SESSION_ACTIVITY_MS = 30 * 6e4;
21245
21759
  var CLAIM_SESSION_PROBE_CACHE_MS = 6e4;
21246
21760
  var claimSessionProbeCache = /* @__PURE__ */ new Map();
21247
21761
  function probeLocalClaimSession(marker, now = Date.now()) {
21248
- if (!marker.session || !marker.host || marker.host.toLowerCase() !== (0, import_node_os8.hostname)().toLowerCase()) return void 0;
21762
+ if (!marker.session || !marker.host || marker.host.toLowerCase() !== (0, import_node_os9.hostname)().toLowerCase()) return void 0;
21249
21763
  if (!marker.surface?.toLowerCase().startsWith("claude")) return void 0;
21250
21764
  const cacheKey = `${marker.host.toLowerCase()}/${marker.session}`;
21251
21765
  const cached = claimSessionProbeCache.get(cacheKey);
@@ -21254,17 +21768,17 @@ function probeLocalClaimSession(marker, now = Date.now()) {
21254
21768
  claimSessionProbeCache.set(cacheKey, { checkedAt: now, state });
21255
21769
  return state;
21256
21770
  };
21257
- const root = (0, import_node_path17.join)((0, import_node_os8.homedir)(), ".claude", "projects");
21771
+ const root = (0, import_node_path17.join)((0, import_node_os9.homedir)(), ".claude", "projects");
21258
21772
  try {
21259
21773
  const wanted = `${marker.session}.jsonl`.toLowerCase();
21260
21774
  const pending = [root];
21261
21775
  while (pending.length) {
21262
21776
  const dir = pending.pop();
21263
- for (const entry of (0, import_node_fs19.readdirSync)(dir, { withFileTypes: true })) {
21777
+ for (const entry of (0, import_node_fs20.readdirSync)(dir, { withFileTypes: true })) {
21264
21778
  const path2 = (0, import_node_path17.join)(dir, entry.name);
21265
21779
  if (entry.isDirectory()) pending.push(path2);
21266
21780
  else if (entry.isFile() && entry.name.toLowerCase() === wanted) {
21267
- return remember(now - (0, import_node_fs19.statSync)(path2).mtimeMs <= CLAIM_SESSION_ACTIVITY_MS ? "live" : "dead");
21781
+ return remember(now - (0, import_node_fs20.statSync)(path2).mtimeMs <= CLAIM_SESSION_ACTIVITY_MS ? "live" : "dead");
21268
21782
  }
21269
21783
  }
21270
21784
  }
@@ -21376,9 +21890,8 @@ function laneOwnership(marker, current) {
21376
21890
  }
21377
21891
  return "unknown";
21378
21892
  }
21379
- async function checkLaneContest(client, item) {
21893
+ async function checkLaneContest(client, item, actor = describeSessionIdentity()) {
21380
21894
  const evidence = await gatherClaimLiveness(client, item.repository, item.number, openPullsFetcher(client));
21381
- const actor = describeSessionIdentity();
21382
21895
  const ownership = laneOwnership(evidence.marker, actor);
21383
21896
  const live = ownership === "mine" ? [] : liveEvidenceLines(evidence, item.repository);
21384
21897
  const unverifiable = ownership === "mine" ? [] : evidence.failed;
@@ -21576,7 +22089,7 @@ async function resolveAutoAddBoardAttach(client, cfg, selector, priority, warn =
21576
22089
  }
21577
22090
 
21578
22091
  // src/issue-body.ts
21579
- var import_node_os9 = require("node:os");
22092
+ var import_node_os10 = require("node:os");
21580
22093
  init_error_codes();
21581
22094
  var TextArgError = class extends Error {
21582
22095
  constructor(message, code, offendingFlag) {
@@ -21589,7 +22102,7 @@ var TextArgError = class extends Error {
21589
22102
  offendingFlag;
21590
22103
  };
21591
22104
  function emptyStdinMessage(fileFlag) {
21592
- if ((0, import_node_os9.platform)() === "win32") {
22105
+ if ((0, import_node_os10.platform)() === "win32") {
21593
22106
  return `${fileFlag} - read empty stdin (on Windows, ${fileFlag} - is unreliable through the npm .cmd shim \u2014 use ${fileFlag} <path>, or pipe to \`node cli/dist/index.cjs\` directly)`;
21594
22107
  }
21595
22108
  return `${fileFlag} - read empty stdin (nothing piped \u2014 pass a heredoc/pipe, or ${fileFlag} <path>)`;
@@ -21690,12 +22203,13 @@ var PRIMARY_GROUPS = [
21690
22203
  ["Review and ship", ["pr", "ci", "rcand", "release", "hotfix", "train"]],
21691
22204
  // `tests` sits beside `docs` deliberately: both are deterministic, repo-local gates a workflow
21692
22205
  // step invokes (`docs refs`, `tests policy`), not org-plane operations (#3605). `spawn policy`
21693
- // joins them on the same footing (#3979).
21694
- ["Setup and support", ["bootstrap", "secrets", "docs", "repo-index", "tests", "spawn"]],
22206
+ // joins them on the same footing (#3979); `dist status` does too (#5576) — the checkout's own
22207
+ // dist/BOM freshness read.
22208
+ ["Setup and support", ["bootstrap", "secrets", "docs", "repo-index", "tests", "spawn", "dist"]],
21695
22209
  ["Coordinate and improve", ["wave", "report", "skill-lesson", "closure-rate"]]
21696
22210
  ];
21697
22211
  var OPERATIONAL_TOP_LEVEL = /* @__PURE__ */ new Set(["org", "runtime", "plugin"]);
21698
- var SUPPORT_PRIMARY = /* @__PURE__ */ new Set(["doctor", "whoami", "commands", "explain", "docs", "repo-index", "find", "tests", "spawn", "wave", "report", "skill-lesson", "closure-rate"]);
22212
+ var SUPPORT_PRIMARY = /* @__PURE__ */ new Set(["doctor", "whoami", "commands", "explain", "docs", "repo-index", "find", "tests", "spawn", "dist", "wave", "report", "skill-lesson", "closure-rate"]);
21699
22213
  var TOP_LEVEL_ORDER = /* @__PURE__ */ new Map();
21700
22214
  var HELP_GROUP_ORDER = /* @__PURE__ */ new Map();
21701
22215
  var topLevelPosition = 0;
@@ -21748,6 +22262,7 @@ var COMMAND_OWNERSHIP = {
21748
22262
  find: { module_owner: "cli/src/repo-index.ts", consumer: "agent-session" },
21749
22263
  tests: { module_owner: "cli/src/test-policy-core.ts", consumer: "repo-gates" },
21750
22264
  spawn: { module_owner: "cli/src/spawn-policy-core.ts", consumer: "repo-gates" },
22265
+ dist: { module_owner: "cli/src/dist-drift.ts", consumer: "repo-gates" },
21751
22266
  wave: { module_owner: "cli/src/wave-land.ts", consumer: "campaign-orchestrator" },
21752
22267
  report: { module_owner: "cli/src/report.ts", consumer: "campaign-orchestrator" },
21753
22268
  "skill-lesson": { module_owner: "cli/src/skill-lesson.ts", consumer: "campaign-orchestrator" },
@@ -22269,13 +22784,13 @@ init_hub_url();
22269
22784
  init_client_version();
22270
22785
 
22271
22786
  // src/claude-binary-doctor.ts
22272
- var import_node_fs21 = require("node:fs");
22273
- var import_node_os11 = require("node:os");
22787
+ var import_node_fs22 = require("node:fs");
22788
+ var import_node_os12 = require("node:os");
22274
22789
  var import_node_path19 = require("node:path");
22275
22790
 
22276
22791
  // src/jerv-cli-spawn.ts
22277
- var import_node_fs20 = require("node:fs");
22278
- var import_node_os10 = require("node:os");
22792
+ var import_node_fs21 = require("node:fs");
22793
+ var import_node_os11 = require("node:os");
22279
22794
  var import_node_path18 = require("node:path");
22280
22795
  init_cli_shared();
22281
22796
  var WIN_NAMES = ["jerv-cli.cmd", "jerv-cli.exe", "jerv-cli"];
@@ -22302,7 +22817,7 @@ function normalizeSpawnPathEntry(entry, platform2 = process.platform) {
22302
22817
  if (msys) return `${msys[1].toUpperCase()}:\\${msys[2].replace(/\//g, "\\")}`;
22303
22818
  return trimmed;
22304
22819
  }
22305
- function jervCliCandidateDirs(env = process.env, home = (0, import_node_os10.homedir)(), platform2 = process.platform) {
22820
+ function jervCliCandidateDirs(env = process.env, home = (0, import_node_os11.homedir)(), platform2 = process.platform) {
22306
22821
  const seen = /* @__PURE__ */ new Set();
22307
22822
  const out = [];
22308
22823
  const push = (dir) => {
@@ -22323,7 +22838,7 @@ function jervCliCandidateDirs(env = process.env, home = (0, import_node_os10.hom
22323
22838
  }
22324
22839
  return out;
22325
22840
  }
22326
- function jervCliCandidatePaths(env = process.env, home = (0, import_node_os10.homedir)(), platform2 = process.platform) {
22841
+ function jervCliCandidatePaths(env = process.env, home = (0, import_node_os11.homedir)(), platform2 = process.platform) {
22327
22842
  const names = platform2 === "win32" ? WIN_NAMES : POSIX_NAMES;
22328
22843
  const out = [];
22329
22844
  for (const dir of jervCliCandidateDirs(env, home, platform2)) {
@@ -22331,20 +22846,20 @@ function jervCliCandidatePaths(env = process.env, home = (0, import_node_os10.ho
22331
22846
  }
22332
22847
  return out;
22333
22848
  }
22334
- function resolveJervCliPath(env = process.env, home = (0, import_node_os10.homedir)(), platform2 = process.platform, exists = import_node_fs20.existsSync) {
22849
+ function resolveJervCliPath(env = process.env, home = (0, import_node_os11.homedir)(), platform2 = process.platform, exists = import_node_fs21.existsSync) {
22335
22850
  for (const candidate2 of jervCliCandidatePaths(env, home, platform2)) {
22336
22851
  if (exists(candidate2)) return candidate2;
22337
22852
  }
22338
22853
  return void 0;
22339
22854
  }
22340
- function resolveJervCliNodeEntry(shimPath, exists = import_node_fs20.existsSync) {
22855
+ function resolveJervCliNodeEntry(shimPath, exists = import_node_fs21.existsSync) {
22341
22856
  const entry = (0, import_node_path18.join)((0, import_node_path18.dirname)(shimPath), JERV_CLI_ENTRY);
22342
22857
  return exists(entry) ? entry : void 0;
22343
22858
  }
22344
22859
  function jervCliExecFileArgs(args, opts = {}) {
22345
22860
  const platform2 = opts.platform ?? process.platform;
22346
- const exists = opts.exists ?? import_node_fs20.existsSync;
22347
- const resolved = resolveJervCliPath(opts.env ?? process.env, opts.home ?? (0, import_node_os10.homedir)(), platform2, exists);
22861
+ const exists = opts.exists ?? import_node_fs21.existsSync;
22862
+ const resolved = resolveJervCliPath(opts.env ?? process.env, opts.home ?? (0, import_node_os11.homedir)(), platform2, exists);
22348
22863
  if (resolved) {
22349
22864
  const entry = resolveJervCliNodeEntry(resolved, exists);
22350
22865
  if (entry) {
@@ -22434,7 +22949,7 @@ function globalNodeModulesRoots(host) {
22434
22949
  };
22435
22950
  const prefix = env.npm_config_prefix?.trim();
22436
22951
  if (prefix) push(platform2 === "win32" ? (0, import_node_path19.join)(prefix, "node_modules") : (0, import_node_path19.join)(prefix, "lib", "node_modules"));
22437
- for (const dir of jervCliCandidateDirs(env, host.home ?? (0, import_node_os11.homedir)(), platform2)) {
22952
+ for (const dir of jervCliCandidateDirs(env, host.home ?? (0, import_node_os12.homedir)(), platform2)) {
22438
22953
  push((0, import_node_path19.join)(dir, "node_modules"));
22439
22954
  push((0, import_node_path19.join)((0, import_node_path19.dirname)(dir), "lib", "node_modules"));
22440
22955
  }
@@ -22443,16 +22958,16 @@ function globalNodeModulesRoots(host) {
22443
22958
  function readHead(path2) {
22444
22959
  let fd;
22445
22960
  try {
22446
- fd = (0, import_node_fs21.openSync)(path2, "r");
22961
+ fd = (0, import_node_fs22.openSync)(path2, "r");
22447
22962
  const buffer = new Uint8Array(MAGIC_HEAD_BYTES);
22448
- const read = (0, import_node_fs21.readSync)(fd, buffer, 0, MAGIC_HEAD_BYTES, 0);
22963
+ const read = (0, import_node_fs22.readSync)(fd, buffer, 0, MAGIC_HEAD_BYTES, 0);
22449
22964
  return buffer.subarray(0, read);
22450
22965
  } catch {
22451
22966
  return void 0;
22452
22967
  } finally {
22453
22968
  if (fd !== void 0) {
22454
22969
  try {
22455
- (0, import_node_fs21.closeSync)(fd);
22970
+ (0, import_node_fs22.closeSync)(fd);
22456
22971
  } catch {
22457
22972
  }
22458
22973
  }
@@ -22460,7 +22975,7 @@ function readHead(path2) {
22460
22975
  }
22461
22976
  function fileBytes(path2) {
22462
22977
  try {
22463
- return (0, import_node_fs21.statSync)(path2).size;
22978
+ return (0, import_node_fs22.statSync)(path2).size;
22464
22979
  } catch {
22465
22980
  return void 0;
22466
22981
  }
@@ -22474,13 +22989,13 @@ function readClaudeBinaryState(host = {}) {
22474
22989
  const arch = host.arch ?? process.arch;
22475
22990
  const magic = EXECUTABLE_MAGIC[platform2];
22476
22991
  if (!magic) return void 0;
22477
- const packageRoot = globalNodeModulesRoots(host).map((root) => (0, import_node_path19.join)(root, ...PACKAGE.split("/"))).find((dir) => (0, import_node_fs21.existsSync)((0, import_node_path19.join)(dir, "package.json")));
22992
+ const packageRoot = globalNodeModulesRoots(host).map((root) => (0, import_node_path19.join)(root, ...PACKAGE.split("/"))).find((dir) => (0, import_node_fs22.existsSync)((0, import_node_path19.join)(dir, "package.json")));
22478
22993
  if (!packageRoot) return void 0;
22479
22994
  const keys = platformPackageKeys(platform2, arch);
22480
22995
  const fallbackPackage = `${PACKAGE}-${keys[0]}`;
22481
22996
  let manifest;
22482
22997
  try {
22483
- manifest = JSON.parse((0, import_node_fs21.readFileSync)((0, import_node_path19.join)(packageRoot, "package.json"), "utf8"));
22998
+ manifest = JSON.parse((0, import_node_fs22.readFileSync)((0, import_node_path19.join)(packageRoot, "package.json"), "utf8"));
22484
22999
  } catch (e) {
22485
23000
  return {
22486
23001
  state: "unreadable",
@@ -22511,7 +23026,7 @@ function readClaudeBinaryState(host = {}) {
22511
23026
  (0, import_node_path19.join)(packageRoot, "node_modules", ...name.split("/"), binName),
22512
23027
  (0, import_node_path19.join)((0, import_node_path19.dirname)((0, import_node_path19.dirname)(packageRoot)), ...name.split("/"), binName)
22513
23028
  ];
22514
- const found = published.map((name) => ({ name, path: binIn(name).find((file) => (0, import_node_fs21.existsSync)(file)) })).find((c) => c.path);
23029
+ const found = published.map((name) => ({ name, path: binIn(name).find((file) => (0, import_node_fs22.existsSync)(file)) })).find((c) => c.path);
22515
23030
  const platformPackage = found?.name ?? published[0];
22516
23031
  let source;
22517
23032
  let sourceProblem;
@@ -22522,7 +23037,7 @@ function readClaudeBinaryState(host = {}) {
22522
23037
  } else {
22523
23038
  source = { path: found.path, bytes: fileBytes(found.path) ?? 0 };
22524
23039
  }
22525
- if (!(0, import_node_fs21.existsSync)(binPath)) {
23040
+ if (!(0, import_node_fs22.existsSync)(binPath)) {
22526
23041
  return { state: "missing", binPath, expectedMagic: magic.name, platformPackage, ...source ? { source } : {}, ...sourceProblem ? { sourceProblem } : {} };
22527
23042
  }
22528
23043
  const head = readHead(binPath);
@@ -22565,9 +23080,9 @@ function healClaudeBinary(host = {}, onStep) {
22565
23080
  const platform2 = host.platform ?? process.platform;
22566
23081
  const aside = `${probe.binPath}.stub-${Date.now()}`;
22567
23082
  let renamed = false;
22568
- if ((0, import_node_fs21.existsSync)(probe.binPath)) {
23083
+ if ((0, import_node_fs22.existsSync)(probe.binPath)) {
22569
23084
  try {
22570
- (0, import_node_fs21.renameSync)(probe.binPath, aside);
23085
+ (0, import_node_fs22.renameSync)(probe.binPath, aside);
22571
23086
  renamed = true;
22572
23087
  onStep?.(`renamed the stub aside: ${aside}`);
22573
23088
  } catch (e) {
@@ -22576,12 +23091,12 @@ function healClaudeBinary(host = {}, onStep) {
22576
23091
  }
22577
23092
  try {
22578
23093
  onStep?.(`copying ${probe.source.path} \u2192 ${probe.binPath} (${(probe.source.bytes / 1e6).toFixed(0)} MB)`);
22579
- (0, import_node_fs21.copyFileSync)(probe.source.path, probe.binPath);
22580
- if (platform2 !== "win32") (0, import_node_fs21.chmodSync)(probe.binPath, 493);
23094
+ (0, import_node_fs22.copyFileSync)(probe.source.path, probe.binPath);
23095
+ if (platform2 !== "win32") (0, import_node_fs22.chmodSync)(probe.binPath, 493);
22581
23096
  } catch (e) {
22582
23097
  if (renamed) {
22583
23098
  try {
22584
- (0, import_node_fs21.renameSync)(aside, probe.binPath);
23099
+ (0, import_node_fs22.renameSync)(aside, probe.binPath);
22585
23100
  } catch {
22586
23101
  return { ok: false, detail: `copy failed (${e.message}) and the stub could not be restored \u2014 the original is at ${aside}` };
22587
23102
  }
@@ -22595,7 +23110,7 @@ function healClaudeBinary(host = {}, onStep) {
22595
23110
  let kept = false;
22596
23111
  if (renamed) {
22597
23112
  try {
22598
- (0, import_node_fs21.rmSync)(aside);
23113
+ (0, import_node_fs22.rmSync)(aside);
22599
23114
  } catch {
22600
23115
  kept = true;
22601
23116
  }
@@ -22960,6 +23475,7 @@ function trainPlan(command, options = {}) {
22960
23475
  { label: "verify current branch is development", gated: true },
22961
23476
  { label: "verify registry META for this project", command: "mmi-cli oracle org project get <owner/repo>", gated: true },
22962
23477
  { label: "preflight required main secret names", command: "mmi-cli vault secrets preflight --stage main --repo <owner/repo>", gated: true },
23478
+ { label: "preflight GitHub Actions hosted job start (billing/spending) before minting a tag", command: "train dispatches actions-job-start-canary.yml on MMI-Hub (#5604); exact-run --retry-publish skips this", gated: true },
22963
23479
  { label: "merge development to main", gated: true },
22964
23480
  { label: "fold the version bump into the release commit (Hub: full distribution set; app repos: root package manifest) \u2014 runs inside the apply step, no separate bump PR", gated: true },
22965
23481
  { label: "tag release and publish GitHub Release", gated: true },
@@ -22975,6 +23491,7 @@ function trainPlan(command, options = {}) {
22975
23491
  { label: "guard: refuse if origin/rc carries content not in development (a dev -> main release would drop it)", command: "git rev-list --count --right-only --cherry-pick --no-merges origin/development...origin/rc", gated: true },
22976
23492
  { label: "verify registry META for this project", command: "mmi-cli oracle org project get <owner/repo>", gated: true },
22977
23493
  { label: "preflight required main secret names", command: "mmi-cli vault secrets preflight --stage main --repo <owner/repo>", gated: true },
23494
+ { label: "preflight GitHub Actions hosted job start (billing/spending) before minting a tag", command: "train dispatches actions-job-start-canary.yml on MMI-Hub (#5604); exact-run --retry-publish skips this", gated: true },
22978
23495
  { label: "merge development to main (rc skipped)", gated: true },
22979
23496
  { label: "fold the version bump into the release commit \u2014 runs inside the apply step, no separate bump PR", gated: true },
22980
23497
  { label: "tag release and publish GitHub Release", gated: true },
@@ -22989,6 +23506,7 @@ function trainPlan(command, options = {}) {
22989
23506
  { label: "verify current branch is rc", gated: true },
22990
23507
  { label: "verify registry META for this project", command: "mmi-cli oracle org project get <owner/repo>", gated: true },
22991
23508
  { label: "preflight required main secret names", command: "mmi-cli vault secrets preflight --stage main --repo <owner/repo>", gated: true },
23509
+ { label: "preflight GitHub Actions hosted job start (billing/spending) before minting a tag", command: "train dispatches actions-job-start-canary.yml on MMI-Hub (#5604); exact-run --retry-publish skips this", gated: true },
22992
23510
  { label: "verify every main-only hotfix commit is covered by the rc candidate (the guard runs automatically inside the apply step below; --ack <sha> overrides a verified, trailer-less port)", command: "mmi-cli devops release --apply [--ack <sha>]", gated: true },
22993
23511
  { label: "merge rc to main", gated: true },
22994
23512
  { label: "fold the version bump into the release commit (app repos: root package manifest) \u2014 runs inside the apply step, no separate bump PR", gated: true },
@@ -23077,14 +23595,14 @@ function renderVerifyBroker(input) {
23077
23595
 
23078
23596
  // src/tenant-artifact.ts
23079
23597
  var import_node_crypto5 = require("node:crypto");
23080
- var import_node_fs22 = require("node:fs");
23598
+ var import_node_fs23 = require("node:fs");
23081
23599
  var import_promises3 = require("node:fs/promises");
23082
23600
  var import_node_path20 = require("node:path");
23083
23601
  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}$/;
23084
23602
  var MAX_BYTES = 5 * 1024 * 1024 * 1024;
23085
23603
  async function sha256File(path2) {
23086
23604
  const hash = (0, import_node_crypto5.createHash)("sha256");
23087
- for await (const chunk of (0, import_node_fs22.createReadStream)(path2)) hash.update(chunk);
23605
+ for await (const chunk of (0, import_node_fs23.createReadStream)(path2)) hash.update(chunk);
23088
23606
  return hash.digest("hex");
23089
23607
  }
23090
23608
  async function putTenantArtifact(repo, stage, inputPath, deps) {
@@ -23093,8 +23611,8 @@ async function putTenantArtifact(repo, stage, inputPath, deps) {
23093
23611
  const info = await (0, import_promises3.stat)(path2);
23094
23612
  if (!info.isFile()) throw new Error("tenant artifact put: input path must be a file");
23095
23613
  if (!Number.isSafeInteger(info.size) || info.size < 1 || info.size > MAX_BYTES) throw new Error(`tenant artifact put: file must be 1..${MAX_BYTES} bytes`);
23096
- const sha2563 = await sha256File(path2);
23097
- const prepared = await tenantArtifactUpload({ repo, stage, size: info.size, sha256: sha2563 }, deps);
23614
+ const sha2564 = await sha256File(path2);
23615
+ const prepared = await tenantArtifactUpload({ repo, stage, size: info.size, sha256: sha2564 }, deps);
23098
23616
  if (!prepared.ok) {
23099
23617
  const detail = prepared.body?.error ?? prepared.error ?? `HTTP ${prepared.status}`;
23100
23618
  throw new Error(`tenant artifact put: ${detail}`);
@@ -23107,7 +23625,7 @@ async function putTenantArtifact(repo, stage, inputPath, deps) {
23107
23625
  return [key, value];
23108
23626
  }));
23109
23627
  headers["content-length"] = String(info.size);
23110
- const stream = (0, import_node_fs22.createReadStream)(path2);
23628
+ const stream = (0, import_node_fs23.createReadStream)(path2);
23111
23629
  let uploaded;
23112
23630
  try {
23113
23631
  uploaded = await fetch(body.uploadUrl, {
@@ -23122,8 +23640,8 @@ async function putTenantArtifact(repo, stage, inputPath, deps) {
23122
23640
  throw error;
23123
23641
  }
23124
23642
  if (!uploaded.ok) throw new Error(`tenant artifact put: object upload failed (HTTP ${uploaded.status})`);
23125
- if (body.size !== info.size || body.sha256 !== sha2563 || typeof body.expiresAt !== "string" || !Number.isFinite(Date.parse(body.expiresAt))) throw new Error("tenant artifact put: Hub returned inconsistent artifact metadata");
23126
- return { artifactId: body.artifactId, repo, stage, size: info.size, sha256: sha2563, expiresAt: body.expiresAt };
23643
+ if (body.size !== info.size || body.sha256 !== sha2564 || typeof body.expiresAt !== "string" || !Number.isFinite(Date.parse(body.expiresAt))) throw new Error("tenant artifact put: Hub returned inconsistent artifact metadata");
23644
+ return { artifactId: body.artifactId, repo, stage, size: info.size, sha256: sha2564, expiresAt: body.expiresAt };
23127
23645
  }
23128
23646
 
23129
23647
  // src/hotfix-coverage.ts
@@ -23309,7 +23827,7 @@ function clean3(out) {
23309
23827
  return out.trim();
23310
23828
  }
23311
23829
  function sleeper(deps) {
23312
- return deps.sleep ?? ((ms) => new Promise((resolve5) => setTimeout(resolve5, ms)));
23830
+ return deps.sleep ?? ((ms) => new Promise((resolve6) => setTimeout(resolve6, ms)));
23313
23831
  }
23314
23832
  function normalizeHotfixVersion(input) {
23315
23833
  const m = /^v?(\d+\.\d+\.\d+)$/.exec(input.trim());
@@ -24304,7 +24822,7 @@ function renderDeployPortDoctor(report) {
24304
24822
  // src/repo-index.ts
24305
24823
  var import_node_crypto6 = require("node:crypto");
24306
24824
  var import_node_child_process12 = require("node:child_process");
24307
- var import_node_fs23 = require("node:fs");
24825
+ var import_node_fs24 = require("node:fs");
24308
24826
  var import_node_path21 = require("node:path");
24309
24827
 
24310
24828
  // ../infra/repo-index-path-policy.mjs
@@ -24505,10 +25023,10 @@ function loadReadmeHints(cwd, candidatePaths) {
24505
25023
  for (const rel of readmes) {
24506
25024
  if (isHardDeniedPath(rel)) continue;
24507
25025
  const abs = (0, import_node_path21.join)(cwd, ...rel.split("/"));
24508
- if (!(0, import_node_fs23.existsSync)(abs)) continue;
25026
+ if (!(0, import_node_fs24.existsSync)(abs)) continue;
24509
25027
  let text;
24510
25028
  try {
24511
- text = (0, import_node_fs23.readFileSync)(abs, "utf8");
25029
+ text = (0, import_node_fs24.readFileSync)(abs, "utf8");
24512
25030
  } catch {
24513
25031
  continue;
24514
25032
  }
@@ -24544,10 +25062,10 @@ function rebuildRepoIndex(cwd, repoSlug3) {
24544
25062
  if (ignored.has(rel)) continue;
24545
25063
  if (isHardDeniedPath(rel)) continue;
24546
25064
  const abs = (0, import_node_path21.join)(cwd, ...rel.split("/"));
24547
- if (!(0, import_node_fs23.existsSync)(abs)) continue;
25065
+ if (!(0, import_node_fs24.existsSync)(abs)) continue;
24548
25066
  let text;
24549
25067
  try {
24550
- text = (0, import_node_fs23.readFileSync)(abs, "utf8");
25068
+ text = (0, import_node_fs24.readFileSync)(abs, "utf8");
24551
25069
  } catch {
24552
25070
  continue;
24553
25071
  }
@@ -24572,16 +25090,16 @@ function rebuildRepoIndex(cwd, repoSlug3) {
24572
25090
  entries
24573
25091
  };
24574
25092
  const store = repoIndexStorePath(cwd);
24575
- (0, import_node_fs23.mkdirSync)((0, import_node_path21.dirname)(store), { recursive: true });
24576
- (0, import_node_fs23.writeFileSync)(store, `${JSON.stringify(projection, null, 2)}
25093
+ (0, import_node_fs24.mkdirSync)((0, import_node_path21.dirname)(store), { recursive: true });
25094
+ (0, import_node_fs24.writeFileSync)(store, `${JSON.stringify(projection, null, 2)}
24577
25095
  `, "utf8");
24578
25096
  return projection;
24579
25097
  }
24580
25098
  function loadRepoIndex(cwd) {
24581
25099
  const store = repoIndexStorePath(cwd);
24582
- if (!(0, import_node_fs23.existsSync)(store)) return null;
25100
+ if (!(0, import_node_fs24.existsSync)(store)) return null;
24583
25101
  try {
24584
- const raw = JSON.parse((0, import_node_fs23.readFileSync)(store, "utf8"));
25102
+ const raw = JSON.parse((0, import_node_fs24.readFileSync)(store, "utf8"));
24585
25103
  if (raw?.schema !== REPO_INDEX_SCHEMA || !Array.isArray(raw.entries)) return null;
24586
25104
  return raw;
24587
25105
  } catch {
@@ -24665,8 +25183,8 @@ init_compat();
24665
25183
  // src/repo-index-v4/builder.ts
24666
25184
  var import_node_crypto9 = require("node:crypto");
24667
25185
  var import_node_child_process14 = require("node:child_process");
24668
- var import_node_fs25 = require("node:fs");
24669
- var import_node_os12 = require("node:os");
25186
+ var import_node_fs26 = require("node:fs");
25187
+ var import_node_os13 = require("node:os");
24670
25188
  var import_node_path23 = require("node:path");
24671
25189
 
24672
25190
  // ../infra/repo-index-material-buckets.mjs
@@ -24766,7 +25284,7 @@ function buildRepoIndexMaterialLayout(repo, chunks, embeddings) {
24766
25284
 
24767
25285
  // src/repo-index-v4/chunks.ts
24768
25286
  var import_node_crypto8 = require("node:crypto");
24769
- var import_node_fs24 = require("node:fs");
25287
+ var import_node_fs25 = require("node:fs");
24770
25288
  var import_node_path22 = require("node:path");
24771
25289
 
24772
25290
  // src/repo-index-v4/language.ts
@@ -24932,10 +25450,10 @@ async function buildStructuralChunksForPaths(cwd, repo, commit, paths) {
24932
25450
  const chunks = [];
24933
25451
  for (const path2 of paths) {
24934
25452
  const absolute = (0, import_node_path22.join)(cwd, ...path2.split("/"));
24935
- if (!(0, import_node_fs24.existsSync)(absolute)) continue;
25453
+ if (!(0, import_node_fs25.existsSync)(absolute)) continue;
24936
25454
  let source;
24937
25455
  try {
24938
- source = (0, import_node_fs24.readFileSync)(absolute, "utf8");
25456
+ source = (0, import_node_fs25.readFileSync)(absolute, "utf8");
24939
25457
  } catch {
24940
25458
  continue;
24941
25459
  }
@@ -24997,7 +25515,8 @@ function planRepoIndexV4Delta(opts) {
24997
25515
  const baseCommit = opts.baseCommit ? opts.baseCommit.toLowerCase() : null;
24998
25516
  const full = (fallbackReason) => ({ mode: "full", headCommit, fallbackReason, ...baseCommit ? { baseCommit } : {} });
24999
25517
  if (opts.forceFull) return full("explicit-full-rebuild");
25000
- if (!baseCommit || !COMMIT.test(baseCommit) || baseCommit === headCommit) return full("no-active-authority");
25518
+ if (!baseCommit || !COMMIT.test(baseCommit)) return full("no-active-authority");
25519
+ if (baseCommit === headCommit) return { mode: "unchanged", baseCommit, headCommit };
25001
25520
  if (opts.basePipelineCompatible === false) return full("incompatible-base-provenance");
25002
25521
  if (opts.hasPriorMaterial === false) return full("no-prior-material");
25003
25522
  const { git: git3 } = opts;
@@ -25063,7 +25582,7 @@ function gitInfo(cwd) {
25063
25582
  }
25064
25583
  function prior(cwd) {
25065
25584
  try {
25066
- const p = JSON.parse((0, import_node_fs25.readFileSync)(statePath(cwd), "utf8"));
25585
+ const p = JSON.parse((0, import_node_fs26.readFileSync)(statePath(cwd), "utf8"));
25067
25586
  return p?.schemaVersion === 4 && p?.manifest?.immutable === true ? p : null;
25068
25587
  } catch {
25069
25588
  return null;
@@ -25082,7 +25601,7 @@ function embeddingInput(cwd, chunk) {
25082
25601
  ${chunk.symbol ?? ""}
25083
25602
  ${chunk.blurb ?? ""}`;
25084
25603
  try {
25085
- const lines = (0, import_node_fs25.readFileSync)((0, import_node_path23.join)(cwd, ...chunk.path.split("/")), "utf8").split(/\r?\n/);
25604
+ const lines = (0, import_node_fs26.readFileSync)((0, import_node_path23.join)(cwd, ...chunk.path.split("/")), "utf8").split(/\r?\n/);
25086
25605
  const body = lines.slice(Math.max(0, (c.startLine ?? 1) - 1), Math.min(lines.length, c.endLine ?? lines.length)).join("\n");
25087
25606
  return body.slice(0, 1e5);
25088
25607
  } catch {
@@ -25097,18 +25616,18 @@ function runEmbedderOnce(cwd, chunks, modelDirectory, createdAt) {
25097
25616
  if (!chunks.length) return { ok: true, embeddings: [] };
25098
25617
  const orchestratorRunner = (0, import_node_path23.join)(process.cwd(), "repo-indexer", "src", "batch.mjs");
25099
25618
  const targetRunner = (0, import_node_path23.join)(cwd, "repo-indexer", "src", "batch.mjs");
25100
- const file = (0, import_node_fs25.existsSync)(orchestratorRunner) ? orchestratorRunner : targetRunner;
25101
- if (!(0, import_node_fs25.existsSync)(file)) return { ok: false, reason: "embeddings-unavailable" };
25619
+ const file = (0, import_node_fs26.existsSync)(orchestratorRunner) ? orchestratorRunner : targetRunner;
25620
+ if (!(0, import_node_fs26.existsSync)(file)) return { ok: false, reason: "embeddings-unavailable" };
25102
25621
  const request = { texts: chunks.map((chunk) => ({ id: chunk.id, text: embeddingInput(cwd, chunk) })), maxBatch: V4_EMBED_BATCH };
25103
25622
  const env = { ...process.env, ...modelDirectory ? { MMI_REPO_INDEXER_MODEL_DIR: modelDirectory } : {} };
25104
- const requestDir = (0, import_node_fs25.mkdtempSync)((0, import_node_path23.join)((0, import_node_os12.tmpdir)(), "mmi-repo-index-req-"));
25623
+ const requestDir = (0, import_node_fs26.mkdtempSync)((0, import_node_path23.join)((0, import_node_os13.tmpdir)(), "mmi-repo-index-req-"));
25105
25624
  const requestFile = (0, import_node_path23.join)(requestDir, "request.json");
25106
- (0, import_node_fs25.writeFileSync)(requestFile, JSON.stringify(request));
25625
+ (0, import_node_fs26.writeFileSync)(requestFile, JSON.stringify(request));
25107
25626
  let result;
25108
25627
  try {
25109
25628
  result = (0, import_node_child_process14.spawnSync)(process.execPath, [file, requestFile], { encoding: "utf8", windowsHide: true, timeout: V4_EMBED_TIMEOUT_MS, maxBuffer: V4_MAX_ARTIFACT_BYTES, env });
25110
25629
  } finally {
25111
- (0, import_node_fs25.rmSync)(requestDir, { recursive: true, force: true });
25630
+ (0, import_node_fs26.rmSync)(requestDir, { recursive: true, force: true });
25112
25631
  }
25113
25632
  if (result.error || result.status !== 0) {
25114
25633
  const cleanExit2 = result.error === void 0 && result.signal === void 0 && typeof result.status === "number" && result.status !== 0;
@@ -25241,8 +25760,8 @@ async function buildRepoIndexV4Detailed(cwd, repo, opts = {}) {
25241
25760
  const encoded = canonicalJson(envelope);
25242
25761
  if (Buffer.byteLength(encoded) > V4_MAX_ARTIFACT_BYTES) throw new Error(`repo-index v4 artifact exceeds ${V4_MAX_ARTIFACT_BYTES} byte ceiling`);
25243
25762
  const path2 = statePath(cwd);
25244
- (0, import_node_fs25.mkdirSync)((0, import_node_path23.dirname)(path2), { recursive: true });
25245
- (0, import_node_fs25.writeFileSync)(path2, `${JSON.stringify(envelope, null, 2)}
25763
+ (0, import_node_fs26.mkdirSync)((0, import_node_path23.dirname)(path2), { recursive: true });
25764
+ (0, import_node_fs26.writeFileSync)(path2, `${JSON.stringify(envelope, null, 2)}
25246
25765
  `, "utf8");
25247
25766
  const metrics = {
25248
25767
  mode: delta ? "delta" : "full",
@@ -25793,8 +26312,8 @@ async function gcRepoIndexCloud(deps) {
25793
26312
  }
25794
26313
 
25795
26314
  // src/repo-index-sync.ts
25796
- var import_node_fs27 = require("node:fs");
25797
- var import_node_os13 = require("node:os");
26315
+ var import_node_fs28 = require("node:fs");
26316
+ var import_node_os14 = require("node:os");
25798
26317
  var import_node_path25 = require("node:path");
25799
26318
  var import_node_child_process15 = require("node:child_process");
25800
26319
 
@@ -25834,7 +26353,7 @@ function repoIndexRoster(projects) {
25834
26353
  }
25835
26354
 
25836
26355
  // src/repo-index-v4/edges.ts
25837
- var import_node_fs26 = require("node:fs");
26356
+ var import_node_fs27 = require("node:fs");
25838
26357
  var import_node_path24 = require("node:path");
25839
26358
  var V4_GRAPH_MAX_EDGES = 5e3;
25840
26359
  var V4_GRAPH_MAX_EDGES_PER_FILE = 64;
@@ -25924,9 +26443,9 @@ function buildGraphEdges(cwd, repo, commit, rosterRepos2) {
25924
26443
  for (const path2 of paths) {
25925
26444
  if (ignored.has(path2) || isHardDeniedPath(path2) || !SUPPORTED.has(extension(path2))) continue;
25926
26445
  const absolute = (0, import_node_path24.join)(cwd, ...path2.split("/"));
25927
- if (!(0, import_node_fs26.existsSync)(absolute)) continue;
26446
+ if (!(0, import_node_fs27.existsSync)(absolute)) continue;
25928
26447
  try {
25929
- edges.push(...graphEdgesForSource(repo, commit, path2, (0, import_node_fs26.readFileSync)(absolute, "utf8"), rosterRepos2));
26448
+ edges.push(...graphEdgesForSource(repo, commit, path2, (0, import_node_fs27.readFileSync)(absolute, "utf8"), rosterRepos2));
25930
26449
  } catch {
25931
26450
  }
25932
26451
  if (edges.length >= V4_GRAPH_MAX_EDGES) break;
@@ -25936,10 +26455,10 @@ function buildGraphEdges(cwd, repo, commit, rosterRepos2) {
25936
26455
 
25937
26456
  // src/repo-index-sync.ts
25938
26457
  function execFileUtf8(file, args) {
25939
- return new Promise((resolve5, reject) => {
26458
+ return new Promise((resolve6, reject) => {
25940
26459
  (0, import_node_child_process15.execFile)(file, args, { encoding: "utf8", windowsHide: true }, (error, stdout) => {
25941
26460
  if (error) reject(error);
25942
- else resolve5(String(stdout ?? ""));
26461
+ else resolve6(String(stdout ?? ""));
25943
26462
  });
25944
26463
  });
25945
26464
  }
@@ -26135,11 +26654,12 @@ async function syncEstateRepoIndex(opts) {
26135
26654
  }
26136
26655
  let targetCommit = requestedCommit || void 0;
26137
26656
  if (base && !forceFull) {
26657
+ let headUnreadable = false;
26138
26658
  if (!targetCommit) {
26139
26659
  try {
26140
26660
  targetCommit = await remoteHead(repo, opts.githubToken);
26141
26661
  } catch {
26142
- targetCommit = void 0;
26662
+ headUnreadable = true;
26143
26663
  }
26144
26664
  }
26145
26665
  const provenance = await fetchRepoIndexV4ProvenanceCloud(repo, opts.deps).catch(
@@ -26163,6 +26683,12 @@ async function syncEstateRepoIndex(opts) {
26163
26683
  emit({ row: row2, warning });
26164
26684
  return { repo, row: row2, warning, base, expectedActiveDigest };
26165
26685
  }
26686
+ if (headUnreadable) {
26687
+ const row2 = { repo, reason: "head-unreadable", action: "skip", activeCommit: base.commit };
26688
+ const warning = `${repo}: remote HEAD unreadable; leaving verified-ready authority at ${base.commit}`;
26689
+ emit({ row: row2, warning });
26690
+ return { repo, row: row2, warning, base, expectedActiveDigest };
26691
+ }
26166
26692
  }
26167
26693
  const row = {
26168
26694
  repo,
@@ -26183,13 +26709,17 @@ async function syncEstateRepoIndex(opts) {
26183
26709
  for (const entry of classified) {
26184
26710
  if (entry.row.action !== "build") continue;
26185
26711
  const { repo, base, expectedActiveDigest } = entry;
26186
- const dir = (0, import_node_fs27.mkdtempSync)((0, import_node_path25.join)((0, import_node_os13.tmpdir)(), "mmi-repo-index-"));
26712
+ const dir = (0, import_node_fs28.mkdtempSync)((0, import_node_path25.join)((0, import_node_os14.tmpdir)(), "mmi-repo-index-"));
26187
26713
  try {
26188
26714
  shallowClone(repo, dir, opts.githubToken);
26189
26715
  if (requestedCommit) checkoutExactCommit(repo, dir, opts.githubToken, requestedCommit);
26190
26716
  let v4;
26191
26717
  try {
26192
26718
  const plan = await planDeltaBuild(repo, dir, base, opts.deps, opts.githubToken, forceFull);
26719
+ if (plan.plan.mode === "unchanged") {
26720
+ skipped.push(`${repo}: unchanged verified-ready authority at ${plan.plan.headCommit}`);
26721
+ continue;
26722
+ }
26193
26723
  if (base && plan.plan.mode === "full") skipped.push(`${repo}: full rebuild (${plan.plan.fallbackReason})`);
26194
26724
  v4 = await buildRepoIndexV4Detailed(dir, repo, {
26195
26725
  modelDirectory: process.env.MMI_REPO_INDEXER_MODEL_DIR,
@@ -26233,7 +26763,7 @@ async function syncEstateRepoIndex(opts) {
26233
26763
  failed.push({ repo, error: e.message });
26234
26764
  } finally {
26235
26765
  try {
26236
- (0, import_node_fs27.rmSync)(dir, { recursive: true, force: true });
26766
+ (0, import_node_fs28.rmSync)(dir, { recursive: true, force: true });
26237
26767
  } catch {
26238
26768
  }
26239
26769
  }
@@ -26242,7 +26772,7 @@ async function syncEstateRepoIndex(opts) {
26242
26772
  }
26243
26773
 
26244
26774
  // src/repo-index-health.ts
26245
- var import_node_fs28 = require("node:fs");
26775
+ var import_node_fs29 = require("node:fs");
26246
26776
 
26247
26777
  // testdata/repo-index-golden-queries.json
26248
26778
  var repo_index_golden_queries_default = {
@@ -26346,7 +26876,7 @@ function assertGoldenSuite(raw, source) {
26346
26876
  function loadGoldenSuite(path2) {
26347
26877
  let text;
26348
26878
  try {
26349
- text = (0, import_node_fs28.readFileSync)(path2, "utf8");
26879
+ text = (0, import_node_fs29.readFileSync)(path2, "utf8");
26350
26880
  } catch (e) {
26351
26881
  throw new Error(`golden suite unreadable at ${path2}: ${e.message}`);
26352
26882
  }
@@ -26516,7 +27046,7 @@ async function runRepoIndexHealth(opts) {
26516
27046
 
26517
27047
  // src/spawn-policy-core.ts
26518
27048
  var import_node_child_process16 = require("node:child_process");
26519
- var import_node_fs29 = require("node:fs");
27049
+ var import_node_fs30 = require("node:fs");
26520
27050
  var import_node_path26 = require("node:path");
26521
27051
  var SPAWNERS = ["spawn", "spawnSync", "exec", "execSync", "execFile", "execFileSync"];
26522
27052
  var CALL_SOURCE = String.raw`(^|[^.\w$])(${SPAWNERS.join("|")})\s*\(`;
@@ -26603,7 +27133,7 @@ function runSpawnPolicy(root) {
26603
27133
  for (const file of files) {
26604
27134
  let raw;
26605
27135
  try {
26606
- raw = (0, import_node_fs29.readFileSync)((0, import_node_path26.join)(root, file), "utf8");
27136
+ raw = (0, import_node_fs30.readFileSync)((0, import_node_path26.join)(root, file), "utf8");
26607
27137
  } catch {
26608
27138
  continue;
26609
27139
  }
@@ -26621,7 +27151,7 @@ function runSpawnPolicy(root) {
26621
27151
 
26622
27152
  // src/test-policy-core.ts
26623
27153
  var import_node_child_process17 = require("node:child_process");
26624
- var import_node_fs30 = require("node:fs");
27154
+ var import_node_fs31 = require("node:fs");
26625
27155
  var import_node_path27 = require("node:path");
26626
27156
 
26627
27157
  // ../scripts/test-command-policy-core.mjs
@@ -27002,7 +27532,7 @@ function loadPolicy(root, readFile7 = readFileOrNull2) {
27002
27532
  }
27003
27533
  function readFileOrNull2(path2) {
27004
27534
  try {
27005
- return (0, import_node_fs30.readFileSync)(path2, "utf8");
27535
+ return (0, import_node_fs31.readFileSync)(path2, "utf8");
27006
27536
  } catch {
27007
27537
  return null;
27008
27538
  }
@@ -27030,10 +27560,10 @@ function classify(changed, policy, present = () => false) {
27030
27560
  const removedProtected = [...removed].filter((p) => protectedBy.has(p)).map((p) => ({ path: p, why: protectedBy.get(p) ?? "" }));
27031
27561
  return { mandatoryHits, untestedHits, testChanges, meaningfulTestChanges, addedTests, removedProtected };
27032
27562
  }
27033
- function unresolvedProtectedEntries(policy, root, exists = (path2) => (0, import_node_fs30.existsSync)(path2)) {
27563
+ function unresolvedProtectedEntries(policy, root, exists = (path2) => (0, import_node_fs31.existsSync)(path2)) {
27034
27564
  return (policy.protected ?? []).map((p) => p.path).filter((p) => !exists((0, import_node_path27.join)(root, p)));
27035
27565
  }
27036
- function unresolvedSatisfiers(policy, root, exists = (path2) => (0, import_node_fs30.existsSync)(path2)) {
27566
+ function unresolvedSatisfiers(policy, root, exists = (path2) => (0, import_node_fs31.existsSync)(path2)) {
27037
27567
  const declared = (policy.mandatory ?? []).flatMap((m) => m.satisfiedBy ?? []);
27038
27568
  return [...new Set(declared)].filter((p) => !exists((0, import_node_path27.join)(root, p)));
27039
27569
  }
@@ -27224,7 +27754,7 @@ function blobAt(base, path2, cwd) {
27224
27754
  }
27225
27755
  function runTestPolicy(root, deps = {}) {
27226
27756
  const policy = deps.policy ?? loadPolicy(root);
27227
- const exists = deps.exists ?? ((path2) => (0, import_node_fs30.existsSync)(path2));
27757
+ const exists = deps.exists ?? ((path2) => (0, import_node_fs31.existsSync)(path2));
27228
27758
  const counts = { mandatoryCount: (policy.mandatory ?? []).length, protectedCount: (policy.protected ?? []).length };
27229
27759
  const base = deps.changed ? "(injected)" : resolveBase(root, deps.base);
27230
27760
  const refusal = deps.changed ? null : untrustworthyRange(root, base);
@@ -27287,9 +27817,197 @@ function runTestPolicy(root, deps = {}) {
27287
27817
  return result;
27288
27818
  }
27289
27819
 
27290
- // src/project-info-sync.ts
27291
- var import_node_fs31 = require("node:fs");
27820
+ // src/dist-drift.ts
27821
+ var import_node_child_process18 = require("node:child_process");
27822
+ var import_node_crypto12 = require("node:crypto");
27823
+ var import_node_fs33 = require("node:fs");
27824
+ var import_node_os15 = require("node:os");
27825
+ var import_node_path29 = require("node:path");
27826
+
27827
+ // ../scripts/distribution-digest.mjs
27828
+ var import_node_crypto11 = require("node:crypto");
27829
+ var import_node_fs32 = require("node:fs");
27292
27830
  var import_node_path28 = require("node:path");
27831
+ var slash = (value) => value.replaceAll("\\", "/");
27832
+ function repoPath(root, declaredPath, label) {
27833
+ const absoluteRoot = (0, import_node_path28.resolve)(root);
27834
+ const target = (0, import_node_path28.resolve)(root, declaredPath);
27835
+ if (target !== absoluteRoot && !target.startsWith(`${absoluteRoot}${import_node_path28.sep}`)) {
27836
+ throw new Error(`${label} ${declaredPath} escapes the repository root`);
27837
+ }
27838
+ return target;
27839
+ }
27840
+ function digestFiles(files) {
27841
+ const hash = (0, import_node_crypto11.createHash)("sha256");
27842
+ for (const file of [...files].sort((a, b) => a.relative.localeCompare(b.relative))) {
27843
+ const content = file.stat.isSymbolicLink() ? Buffer.from((0, import_node_fs32.readlinkSync)(file.absolute), "utf8") : (0, import_node_fs32.readFileSync)(file.absolute);
27844
+ hash.update(file.relative, "utf8");
27845
+ hash.update("\0");
27846
+ hash.update(file.stat.isSymbolicLink() ? "symlink" : "file", "utf8");
27847
+ hash.update("\0");
27848
+ hash.update(String(content.length), "utf8");
27849
+ hash.update("\0");
27850
+ hash.update(content);
27851
+ hash.update("\0");
27852
+ }
27853
+ return `sha256:${hash.digest("hex")}`;
27854
+ }
27855
+ function digestPackedFiles(packageRoot, packedFiles) {
27856
+ return digestFiles(packedFiles.map((path2) => {
27857
+ const absolute = repoPath(packageRoot, path2, "packed artifact identity path");
27858
+ if (!(0, import_node_fs32.existsSync)(absolute)) throw new Error(`packed artifact identity path ${path2} does not exist`);
27859
+ return { absolute, relative: slash(path2), stat: (0, import_node_fs32.lstatSync)(absolute) };
27860
+ }));
27861
+ }
27862
+
27863
+ // src/dist-drift.ts
27864
+ var DIST_ARTIFACTS = [
27865
+ { path: "cli/dist/index.cjs", packageDir: "cli", output: "index.cjs" },
27866
+ { path: "cli/dist/main.cjs", packageDir: "cli", output: "main.cjs" },
27867
+ { path: "cli/dist/repo-index-v4.cjs", packageDir: "cli", output: "repo-index-v4.cjs" },
27868
+ { path: "updater/dist/index.cjs", packageDir: "updater", output: "index.cjs" }
27869
+ ];
27870
+ var BOM_DIST_TREE_ID = "mmi-cli-dist";
27871
+ var ABSENT = "absent";
27872
+ var sha2563 = (bytes) => `sha256:${(0, import_node_crypto12.createHash)("sha256").update(bytes).digest("hex")}`;
27873
+ function artifactDrift(path2, committedBytes, rebuiltBytes) {
27874
+ const committed = committedBytes ? sha2563(committedBytes) : ABSENT;
27875
+ const rebuiltExpected = rebuiltBytes ? sha2563(rebuiltBytes) : ABSENT;
27876
+ return { path: path2, status: committed === rebuiltExpected ? "current" : "stale", committed, rebuiltExpected };
27877
+ }
27878
+ function distTreeIdentity(labels, rebuilt, committed, digest) {
27879
+ const entries = [];
27880
+ for (const label of labels) {
27881
+ const bytes = rebuilt(label) ?? committed(label);
27882
+ if (!bytes) {
27883
+ return { rebuiltExpected: ABSENT, note: `recorded tree file ${label} is absent from the checkout` };
27884
+ }
27885
+ entries.push({ path: label, bytes });
27886
+ }
27887
+ return { rebuiltExpected: digest(entries) };
27888
+ }
27889
+ function npmPackIdentity(id, packageDir, identity, rebuilt, tree, digest) {
27890
+ const recorded = identity?.value ?? ABSENT;
27891
+ const kind = identity?.kind ?? "npm-pack";
27892
+ if (!identity || !Array.isArray(identity.files) || identity.files.length === 0) {
27893
+ return { id, kind, status: "stale", recorded, rebuiltExpected: ABSENT, note: "identity records no packed file list to recompute against" };
27894
+ }
27895
+ const entries = [];
27896
+ for (const label of identity.files) {
27897
+ const repoPath2 = `${packageDir}/${label}`;
27898
+ const bytes = label.startsWith("dist/") ? rebuilt(repoPath2) : tree(repoPath2);
27899
+ if (!bytes) {
27900
+ return { id, kind, status: "stale", recorded, rebuiltExpected: ABSENT, note: `packed file ${repoPath2} is absent from the checkout` };
27901
+ }
27902
+ entries.push({ path: label, bytes });
27903
+ }
27904
+ const rebuiltExpected = digest(entries);
27905
+ return { id, kind, status: recorded === rebuiltExpected ? "current" : "stale", recorded, rebuiltExpected };
27906
+ }
27907
+ function computeDistDriftReceipt(sources) {
27908
+ const artifacts = DIST_ARTIFACTS.map((spec) => artifactDrift(spec.path, sources.committed(spec.path), sources.rebuilt(spec.path)));
27909
+ const bomArtifact = (id) => sources.bom.artifacts?.find((entry) => entry.id === id);
27910
+ const cliDistDeclared = bomArtifact(BOM_DIST_TREE_ID)?.identity;
27911
+ const distTree = distTreeIdentity(sources.distTree(), sources.rebuilt, sources.committed, sources.digest);
27912
+ const distTreeRecorded = cliDistDeclared?.value ?? ABSENT;
27913
+ const identities = [
27914
+ { id: BOM_DIST_TREE_ID, kind: cliDistDeclared?.kind ?? "sha256-tree", status: distTreeRecorded === distTree.rebuiltExpected ? "current" : "stale", recorded: distTreeRecorded, rebuiltExpected: distTree.rebuiltExpected, ...distTree.note ? { note: distTree.note } : {} },
27915
+ npmPackIdentity("mmi-cli", "cli", bomArtifact("mmi-cli")?.identity, sources.rebuilt, sources.tree, sources.digest),
27916
+ npmPackIdentity("mmi-hub", "updater", bomArtifact("mmi-hub")?.identity, sources.rebuilt, sources.tree, sources.digest)
27917
+ ];
27918
+ const representative = identities[0];
27919
+ const bomStatus = identities.some((identity) => identity.status === "stale") ? "stale" : "current";
27920
+ const staleCount = artifacts.filter((a) => a.status === "stale").length + (bomStatus === "stale" ? 1 : 0);
27921
+ const summary = staleCount > 0 ? `dist/BOM: ${staleCount} artifact(s) stale \u2014 refresh with \`node scripts/release-distribution.mjs prepare\`` : "dist/BOM: current \u2014 committed dist and distribution-bom.json match a fresh rebuild of source";
27922
+ return {
27923
+ artifacts,
27924
+ bom: { status: bomStatus, recorded: representative.recorded, rebuiltExpected: representative.rebuiltExpected, identities },
27925
+ staleCount,
27926
+ summary
27927
+ };
27928
+ }
27929
+ function shortHash(value) {
27930
+ return value.startsWith("sha256:") ? `sha256:${value.slice(7, 23)}` : value;
27931
+ }
27932
+ function renderDistDriftReceipt(receipt) {
27933
+ const lines = [];
27934
+ for (const artifact of receipt.artifacts) {
27935
+ lines.push(`${artifact.path} ${artifact.status} committed=${shortHash(artifact.committed)} rebuilt-expected=${shortHash(artifact.rebuiltExpected)}`);
27936
+ }
27937
+ const line = `distribution-bom.json ${receipt.bom.status} committed=${shortHash(receipt.bom.recorded)} rebuilt-expected=${shortHash(receipt.bom.rebuiltExpected)}`;
27938
+ const otherStale = receipt.bom.identities.filter((identity) => identity.id !== BOM_DIST_TREE_ID && identity.status === "stale");
27939
+ lines.push(otherStale.length > 0 ? `${line} (${otherStale.map((identity) => `${identity.id} identity stale`).join("; ")})` : line);
27940
+ lines.push(receipt.summary);
27941
+ return lines;
27942
+ }
27943
+ function readOrNull(path2) {
27944
+ return (0, import_node_fs33.existsSync)(path2) ? (0, import_node_fs33.readFileSync)(path2) : null;
27945
+ }
27946
+ function walkFiles(root) {
27947
+ const files = [];
27948
+ const walk2 = (directory) => {
27949
+ for (const entry of (0, import_node_fs33.readdirSync)(directory, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
27950
+ const child2 = (0, import_node_path29.join)(directory, entry.name);
27951
+ if (entry.isDirectory()) walk2(child2);
27952
+ else files.push(child2);
27953
+ }
27954
+ };
27955
+ walk2(root);
27956
+ return files;
27957
+ }
27958
+ function bomPathFor(root) {
27959
+ try {
27960
+ const registry2 = JSON.parse((0, import_node_fs33.readFileSync)((0, import_node_path29.join)(root, "surfaces.json"), "utf8"));
27961
+ return (0, import_node_path29.join)(root, registry2?.sharedAgentCore?.releaseMetadata?.bomPath ?? "distribution-bom.json");
27962
+ } catch {
27963
+ return (0, import_node_path29.join)(root, "distribution-bom.json");
27964
+ }
27965
+ }
27966
+ function rebuildTo(packageRoot, outDir) {
27967
+ (0, import_node_child_process18.execFileSync)(process.execPath, ["build.mjs"], {
27968
+ cwd: packageRoot,
27969
+ env: { ...process.env, MMI_DIST_OUTDIR: outDir },
27970
+ windowsHide: true,
27971
+ stdio: "pipe",
27972
+ encoding: "utf8"
27973
+ });
27974
+ }
27975
+ function runDistStatus(root) {
27976
+ const stage = (0, import_node_fs33.mkdtempSync)((0, import_node_path29.join)((0, import_node_os15.tmpdir)(), "mmi-dist-drift-"));
27977
+ let overlayCount = 0;
27978
+ try {
27979
+ const cliOut = (0, import_node_path29.join)(stage, "cli-dist");
27980
+ const hubOut = (0, import_node_path29.join)(stage, "hub-dist");
27981
+ rebuildTo((0, import_node_path29.join)(root, "cli"), cliOut);
27982
+ rebuildTo((0, import_node_path29.join)(root, "updater"), hubOut);
27983
+ const outDirFor = (packageDir) => packageDir === "cli" ? cliOut : hubOut;
27984
+ const rebuilt = (path2) => {
27985
+ const spec = DIST_ARTIFACTS.find((entry) => entry.path === path2);
27986
+ return spec ? readOrNull((0, import_node_path29.join)(outDirFor(spec.packageDir), spec.output)) : null;
27987
+ };
27988
+ const committed = (path2) => readOrNull((0, import_node_path29.join)(root, path2));
27989
+ const tree = (path2) => readOrNull((0, import_node_path29.join)(root, path2));
27990
+ const distRoot = (0, import_node_path29.join)(root, "cli", "dist");
27991
+ const distTree = () => walkFiles(distRoot).map((absolute) => `cli/dist/${(0, import_node_path29.relative)(distRoot, absolute).replaceAll("\\", "/")}`);
27992
+ const bom = JSON.parse((0, import_node_fs33.readFileSync)(bomPathFor(root), "utf8"));
27993
+ const digest = (entries) => {
27994
+ const overlay = (0, import_node_path29.join)(stage, `overlay-${overlayCount++}`);
27995
+ for (const entry of entries) {
27996
+ const target = (0, import_node_path29.join)(overlay, entry.path);
27997
+ (0, import_node_fs33.mkdirSync)((0, import_node_path29.dirname)(target), { recursive: true });
27998
+ (0, import_node_fs33.writeFileSync)(target, entry.bytes);
27999
+ }
28000
+ return digestPackedFiles(overlay, entries.map((entry) => entry.path));
28001
+ };
28002
+ return computeDistDriftReceipt({ committed, tree, rebuilt, distTree, bom, digest });
28003
+ } finally {
28004
+ (0, import_node_fs33.rmSync)(stage, { recursive: true, force: true });
28005
+ }
28006
+ }
28007
+
28008
+ // src/project-info-sync.ts
28009
+ var import_node_fs34 = require("node:fs");
28010
+ var import_node_path30 = require("node:path");
27293
28011
  var UPDATE_PROJECT_INFO = `mutation($projectId: ID!, $shortDescription: String!, $readme: String!) {
27294
28012
  updateProjectV2(input: { projectId: $projectId, shortDescription: $shortDescription, readme: $readme }) {
27295
28013
  projectV2 { id }
@@ -27334,14 +28052,14 @@ function sharedName(entries, fallback) {
27334
28052
  }
27335
28053
  function buildProjectInfoSyncPlan(targetRepo3, project2, projects, repoRoot2) {
27336
28054
  if (!project2.projectId) throw new Error(`org project sync-info: ${targetRepo3} registry META has no projectId`);
27337
- const readmePath = (0, import_node_path28.join)(repoRoot2, "README.md");
27338
- if (!(0, import_node_fs31.existsSync)(readmePath)) throw new Error(`org project sync-info: ${targetRepo3} has no README.md`);
28055
+ const readmePath = (0, import_node_path30.join)(repoRoot2, "README.md");
28056
+ if (!(0, import_node_fs34.existsSync)(readmePath)) throw new Error(`org project sync-info: ${targetRepo3} has no README.md`);
27339
28057
  const entries = entriesFor(project2, projects);
27340
28058
  const memberRepos = [...new Set(entries.flatMap((entry) => entry.repos ?? []))].filter((repo) => /^[^/]+\/[^/]+$/.test(repo)).sort((a, b) => a.localeCompare(b));
27341
28059
  const projectName = sharedName(entries, project2.name?.trim() || targetRepo3.split("/").pop() || targetRepo3);
27342
28060
  if (!memberRepos.length) throw new Error(`org project sync-info: project ${projectName} has no registered member repos`);
27343
28061
  const entryNames = entries.map((entry) => entry.name?.trim()).filter((name) => Boolean(name));
27344
- const shortDescription = memberRepos.length === 1 ? shortDescriptionFromReadme((0, import_node_fs31.readFileSync)(readmePath, "utf8")) : `Shared work across ${new Intl.ListFormat("en", { type: "conjunction" }).format(entryNames)}.`;
28062
+ const shortDescription = memberRepos.length === 1 ? shortDescriptionFromReadme((0, import_node_fs34.readFileSync)(readmePath, "utf8")) : `Shared work across ${new Intl.ListFormat("en", { type: "conjunction" }).format(entryNames)}.`;
27345
28063
  const lines = [
27346
28064
  `# ${projectName}`,
27347
28065
  "",
@@ -27360,8 +28078,8 @@ function buildProjectInfoSyncPlan(targetRepo3, project2, projects, repoRoot2) {
27360
28078
  const targetBase = `https://github.com/${targetRepo3}`;
27361
28079
  const targetBranch = branchFor(targetRepo3, projects);
27362
28080
  const orgDocs = [
27363
- (0, import_node_fs31.existsSync)((0, import_node_path28.join)(repoRoot2, "docs", "org-readme.md")) ? `- [Org identity](${targetBase}/blob/${targetBranch}/docs/org-readme.md)` : "",
27364
- (0, import_node_fs31.existsSync)((0, import_node_path28.join)(repoRoot2, "docs", "org-architecture.md")) ? `- [Org architecture](${targetBase}/blob/${targetBranch}/docs/org-architecture.md)` : ""
28081
+ (0, import_node_fs34.existsSync)((0, import_node_path30.join)(repoRoot2, "docs", "org-readme.md")) ? `- [Org identity](${targetBase}/blob/${targetBranch}/docs/org-readme.md)` : "",
28082
+ (0, import_node_fs34.existsSync)((0, import_node_path30.join)(repoRoot2, "docs", "org-architecture.md")) ? `- [Org architecture](${targetBase}/blob/${targetBranch}/docs/org-architecture.md)` : ""
27365
28083
  ].filter(Boolean);
27366
28084
  if (orgDocs.length) lines.push("", "## Organisation docs", "", ...orgDocs);
27367
28085
  return { projectId: project2.projectId, projectName, targetRepo: targetRepo3, memberRepos, shortDescription, readme: `${lines.join("\n")}
@@ -28209,9 +28927,9 @@ function writeError(res) {
28209
28927
  }
28210
28928
 
28211
28929
  // src/secrets-commands.ts
28212
- var import_node_fs32 = require("node:fs");
28213
- var import_node_path29 = require("node:path");
28214
- var import_node_os14 = require("node:os");
28930
+ var import_node_fs35 = require("node:fs");
28931
+ var import_node_path31 = require("node:path");
28932
+ var import_node_os16 = require("node:os");
28215
28933
  init_cli_shared();
28216
28934
  init_hub_auth();
28217
28935
  init_github_client();
@@ -28316,18 +29034,18 @@ function collectMap(value, previous = []) {
28316
29034
  return [...previous, value];
28317
29035
  }
28318
29036
  async function decryptRailsCredentials(input) {
28319
- const appDir = (0, import_node_path29.resolve)(input.appDir ?? process.cwd());
29037
+ const appDir = (0, import_node_path31.resolve)(input.appDir ?? process.cwd());
28320
29038
  const credentialsFile = input.credentialsFile ?? DEFAULT_RAILS_CREDENTIALS_FILE;
28321
29039
  const masterKeyFile = input.masterKeyFile ?? DEFAULT_RAILS_MASTER_KEY_FILE;
28322
- const credentialsPath = (0, import_node_path29.resolve)(appDir, credentialsFile);
28323
- const masterKeyPath = (0, import_node_path29.resolve)(appDir, masterKeyFile);
29040
+ const credentialsPath = (0, import_node_path31.resolve)(appDir, credentialsFile);
29041
+ const masterKeyPath = (0, import_node_path31.resolve)(appDir, masterKeyFile);
28324
29042
  const env = {
28325
29043
  ...process.env,
28326
29044
  MMI_RAILS_CREDENTIALS_FILE: credentialsPath,
28327
29045
  MMI_RAILS_MASTER_KEY_FILE: masterKeyPath
28328
29046
  };
28329
- if ((0, import_node_fs32.existsSync)(masterKeyPath)) {
28330
- env.RAILS_MASTER_KEY = (0, import_node_fs32.readFileSync)(masterKeyPath, "utf8").trim();
29047
+ if ((0, import_node_fs35.existsSync)(masterKeyPath)) {
29048
+ env.RAILS_MASTER_KEY = (0, import_node_fs35.readFileSync)(masterKeyPath, "utf8").trim();
28331
29049
  }
28332
29050
  const script = [
28333
29051
  'require "json"',
@@ -28337,9 +29055,9 @@ async function decryptRailsCredentials(input) {
28337
29055
  'config = ActiveSupport::EncryptedConfiguration.new(config_path: config_path, key_path: key_path, env_key: "RAILS_MASTER_KEY", raise_if_missing_key: true)',
28338
29056
  "puts JSON.generate(config.config)"
28339
29057
  ].join("\n");
28340
- const scriptDir = (0, import_node_fs32.mkdtempSync)((0, import_node_path29.join)((0, import_node_os14.tmpdir)(), "mmi-rails-decrypt-"));
28341
- const scriptPath = (0, import_node_path29.join)(scriptDir, "decrypt.rb");
28342
- (0, import_node_fs32.writeFileSync)(scriptPath, script, "utf8");
29058
+ const scriptDir = (0, import_node_fs35.mkdtempSync)((0, import_node_path31.join)((0, import_node_os16.tmpdir)(), "mmi-rails-decrypt-"));
29059
+ const scriptPath = (0, import_node_path31.join)(scriptDir, "decrypt.rb");
29060
+ (0, import_node_fs35.writeFileSync)(scriptPath, script, "utf8");
28343
29061
  try {
28344
29062
  const args = ["exec", "ruby", scriptPath];
28345
29063
  const cmd = process.platform === "win32" ? "cmd.exe" : "bundle";
@@ -28351,7 +29069,7 @@ async function decryptRailsCredentials(input) {
28351
29069
  });
28352
29070
  return JSON.parse(stdout);
28353
29071
  } finally {
28354
- (0, import_node_fs32.rmSync)(scriptDir, { recursive: true, force: true });
29072
+ (0, import_node_fs35.rmSync)(scriptDir, { recursive: true, force: true });
28355
29073
  }
28356
29074
  }
28357
29075
  async function readSecretStdin() {
@@ -28441,7 +29159,7 @@ function registerSecretsCommands(program3) {
28441
29159
  let body;
28442
29160
  if (o.file) {
28443
29161
  try {
28444
- body = (0, import_node_fs32.readFileSync)((0, import_node_path29.resolve)(o.file), "utf8");
29162
+ body = (0, import_node_fs35.readFileSync)((0, import_node_path31.resolve)(o.file), "utf8");
28445
29163
  } catch (e) {
28446
29164
  return fail(`secrets org-catalog: cannot read --file ${o.file}: ${e.message}`);
28447
29165
  }
@@ -28546,7 +29264,7 @@ function registerSecretsCommands(program3) {
28546
29264
  {
28547
29265
  ...d,
28548
29266
  decryptRailsCredentials,
28549
- removeFile: (path2) => (0, import_node_fs32.unlinkSync)((0, import_node_path29.resolve)(o.appDir ?? process.cwd(), path2))
29267
+ removeFile: (path2) => (0, import_node_fs35.unlinkSync)((0, import_node_path31.resolve)(o.appDir ?? process.cwd(), path2))
28550
29268
  },
28551
29269
  {
28552
29270
  repo: o.repo,
@@ -28591,7 +29309,7 @@ function registerSecretsCommands(program3) {
28591
29309
  }
28592
29310
 
28593
29311
  // src/app-actor.ts
28594
- var import_node_crypto11 = require("node:crypto");
29312
+ var import_node_crypto13 = require("node:crypto");
28595
29313
  var APP_ACTOR_ENV = "MMI_ACTOR";
28596
29314
  var APP_VAULT_REPO = "mutmutco/MMI-Hub";
28597
29315
  var APP_VAULT_KEYS = ["GITHUB_APP_ID", "GITHUB_APP_INSTALLATION_ID", "GITHUB_APP_PRIVATE_KEY"];
@@ -28635,7 +29353,7 @@ function mintAppJwt(appId, privateKeyPem, nowSec) {
28635
29353
  exp: now + APP_JWT_TTL_S,
28636
29354
  iss: appId
28637
29355
  }));
28638
- const signer = (0, import_node_crypto11.createSign)("RSA-SHA256");
29356
+ const signer = (0, import_node_crypto13.createSign)("RSA-SHA256");
28639
29357
  signer.update(`${header}.${payload}`);
28640
29358
  return `${header}.${payload}.${signer.sign(privateKeyPem, "base64url")}`;
28641
29359
  }
@@ -28753,7 +29471,7 @@ function emitCliCallTelemetry(command) {
28753
29471
  }
28754
29472
 
28755
29473
  // src/box-commands.ts
28756
- var import_node_fs33 = require("node:fs");
29474
+ var import_node_fs36 = require("node:fs");
28757
29475
  init_clean_exit();
28758
29476
 
28759
29477
  // src/box.ts
@@ -28957,7 +29675,7 @@ function registerBoxCommands(program3) {
28957
29675
  }
28958
29676
  if (o.json) console.log(JSON.stringify({ box: found, incomplete }, null, 2));
28959
29677
  else if (o.ssh && o.script) {
28960
- (0, import_node_fs33.writeFileSync)(o.script, sshRecipeScript(found), "utf8");
29678
+ (0, import_node_fs36.writeFileSync)(o.script, sshRecipeScript(found), "utf8");
28961
29679
  console.log(`wrote ${o.script} \u2014 run: bash "${o.script}"`);
28962
29680
  } else if (o.ssh) console.log(`${formatSshRecipe(found)}
28963
29681
  ${SSH_RECIPE_AGENT_NOTE}`);
@@ -28972,12 +29690,12 @@ ${SSH_RECIPE_AGENT_NOTE}`);
28972
29690
 
28973
29691
  // src/schedules-commands.ts
28974
29692
  var import_promises4 = require("node:fs/promises");
28975
- var import_node_child_process18 = require("node:child_process");
29693
+ var import_node_child_process19 = require("node:child_process");
28976
29694
  var import_node_util7 = require("node:util");
28977
29695
  init_clean_exit();
28978
29696
  init_github_client();
28979
29697
  init_cli_shared();
28980
- var execFileP5 = (0, import_node_util7.promisify)(import_node_child_process18.execFile);
29698
+ var execFileP5 = (0, import_node_util7.promisify)(import_node_child_process19.execFile);
28981
29699
  var AWS_REGION = "eu-central-1";
28982
29700
  var AWS_TIMEOUT_MS = 3e4;
28983
29701
  var AWS_RETRY_DELAY_MS = 1500;
@@ -29089,7 +29807,7 @@ async function awsJson(args) {
29089
29807
  try {
29090
29808
  return await run();
29091
29809
  } catch {
29092
- await new Promise((resolve5) => setTimeout(resolve5, AWS_RETRY_DELAY_MS));
29810
+ await new Promise((resolve6) => setTimeout(resolve6, AWS_RETRY_DELAY_MS));
29093
29811
  return run();
29094
29812
  }
29095
29813
  }
@@ -29293,8 +30011,8 @@ function registerSchedulesCommands(program3) {
29293
30011
 
29294
30012
  // src/file-lock.ts
29295
30013
  var import_promises5 = require("node:fs/promises");
29296
- var import_node_path30 = require("node:path");
29297
- var sleep = (ms) => new Promise((resolve5) => setTimeout(resolve5, ms));
30014
+ var import_node_path32 = require("node:path");
30015
+ var sleep = (ms) => new Promise((resolve6) => setTimeout(resolve6, ms));
29298
30016
  var IMMEDIATE_RETRY_BUDGET = 3;
29299
30017
  var FileLockBusyError = class extends Error {
29300
30018
  lockPath;
@@ -29378,7 +30096,7 @@ async function releaseFileLock(lockPath, guard) {
29378
30096
  }
29379
30097
  async function withFileLock(lockPath, opts, fn) {
29380
30098
  const resolved = resolveFileLockOpts(opts);
29381
- await (0, import_promises5.mkdir)((0, import_node_path30.dirname)(lockPath), { recursive: true }).catch(() => void 0);
30099
+ await (0, import_promises5.mkdir)((0, import_node_path32.dirname)(lockPath), { recursive: true }).catch(() => void 0);
29382
30100
  const guard = await acquireFileLock(lockPath, resolved, Date.now() + resolved.maxWaitMs);
29383
30101
  try {
29384
30102
  return await fn();
@@ -29389,7 +30107,7 @@ async function withFileLock(lockPath, opts, fn) {
29389
30107
 
29390
30108
  // src/schedules-lift-command.ts
29391
30109
  var import_promises6 = require("node:fs/promises");
29392
- var import_node_path31 = require("node:path");
30110
+ var import_node_path33 = require("node:path");
29393
30111
  init_clean_exit();
29394
30112
  init_cli_shared();
29395
30113
  var DEFAULT_WORKFLOWS_DIR = ".github/workflows";
@@ -29418,7 +30136,7 @@ async function readWorkflowFiles(dir) {
29418
30136
  const files = [];
29419
30137
  for (const name of names.sort()) {
29420
30138
  if (!/\.ya?ml$/.test(name)) continue;
29421
- files.push({ path: `.github/workflows/${name}`, text: await (0, import_promises6.readFile)((0, import_node_path31.join)(dir, name), "utf8") });
30139
+ files.push({ path: `.github/workflows/${name}`, text: await (0, import_promises6.readFile)((0, import_node_path33.join)(dir, name), "utf8") });
29422
30140
  }
29423
30141
  return files;
29424
30142
  }
@@ -29505,13 +30223,13 @@ init_cli_shared();
29505
30223
  // src/edge-tunnel.ts
29506
30224
  var HOSTNAME_RE = /^(?=.{1,253}$)([a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)(\.[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)+$/;
29507
30225
  var UPSTREAM_RE = /^https?:\/\/[^/\s]+(?::\d+)?(?:\/.*)?$/;
29508
- function tunnelNameFromHostname(hostname3) {
29509
- return hostname3.replace(/\./g, "-").slice(0, 63);
30226
+ function tunnelNameFromHostname(hostname4) {
30227
+ return hostname4.replace(/\./g, "-").slice(0, 63);
29510
30228
  }
29511
- function planInfraTunnel(hostname3, upstream) {
29512
- const host = hostname3.trim().toLowerCase();
30229
+ function planInfraTunnel(hostname4, upstream) {
30230
+ const host = hostname4.trim().toLowerCase();
29513
30231
  const origin = upstream.trim();
29514
- if (!HOSTNAME_RE.test(host)) throw new Error(`invalid hostname ${JSON.stringify(hostname3)}`);
30232
+ if (!HOSTNAME_RE.test(host)) throw new Error(`invalid hostname ${JSON.stringify(hostname4)}`);
29515
30233
  if (!UPSTREAM_RE.test(origin)) throw new Error(`invalid upstream ${JSON.stringify(upstream)} \u2014 expected http(s)://host:port`);
29516
30234
  const tunnelName = tunnelNameFromHostname(host);
29517
30235
  const configYaml = [
@@ -29569,15 +30287,149 @@ function registerEdgeCommands(program3) {
29569
30287
  }
29570
30288
 
29571
30289
  // src/bootstrap-commands.ts
29572
- var import_node_fs34 = require("node:fs");
29573
- var import_node_os15 = require("node:os");
29574
- var import_node_path32 = require("node:path");
30290
+ var import_node_fs38 = require("node:fs");
30291
+ var import_node_os17 = require("node:os");
30292
+ var import_node_path35 = require("node:path");
29575
30293
  init_cli_shared();
29576
30294
  init_clean_exit();
29577
30295
  init_github_client();
29578
30296
 
30297
+ // src/port-range-assign.ts
30298
+ init_cli_shared();
30299
+
30300
+ // src/port-registry.ts
30301
+ var import_node_fs37 = require("node:fs");
30302
+ var import_node_path34 = require("node:path");
30303
+
30304
+ // ../infra/port-geometry.mjs
30305
+ var PORT_BLOCK = 100;
30306
+ var PORT_SPAN = 10;
30307
+ var PORT_FIRST = 3e3;
30308
+
30309
+ // src/port-registry.ts
30310
+ function nextPortBlock(registry2) {
30311
+ const bases = Object.values(registry2).map(([start]) => start);
30312
+ const base = bases.length ? Math.max(...bases) + PORT_BLOCK : PORT_FIRST;
30313
+ return [base, base + PORT_SPAN];
30314
+ }
30315
+ function loadPortRegistry(path2) {
30316
+ if (!(0, import_node_fs37.existsSync)(path2)) return {};
30317
+ const raw = JSON.parse((0, import_node_fs37.readFileSync)(path2, "utf8"));
30318
+ const out = {};
30319
+ for (const [key, value] of Object.entries(raw)) {
30320
+ if (Array.isArray(value) && value.length === 2 && value.every((n) => typeof n === "number")) {
30321
+ out[key] = [value[0], value[1]];
30322
+ }
30323
+ }
30324
+ return out;
30325
+ }
30326
+ function ensurePortRange(repo, path2) {
30327
+ const registry2 = loadPortRegistry(path2);
30328
+ const existing = registry2[repo];
30329
+ if (existing) return existing;
30330
+ const range = nextPortBlock(registry2);
30331
+ const raw = (0, import_node_fs37.existsSync)(path2) ? JSON.parse((0, import_node_fs37.readFileSync)(path2, "utf8")) : {};
30332
+ raw[repo] = range;
30333
+ (0, import_node_fs37.writeFileSync)(path2, JSON.stringify(raw, null, 2) + "\n", "utf8");
30334
+ return range;
30335
+ }
30336
+ function portCursorSeed(registry2) {
30337
+ return nextPortBlock(registry2)[0];
30338
+ }
30339
+ function metaPortRange(meta) {
30340
+ const r = meta?.portRange;
30341
+ if (r && typeof r.start === "number" && typeof r.end === "number") return [r.start, r.end];
30342
+ return null;
30343
+ }
30344
+ function decidePortRange(input) {
30345
+ if (!input.metaReadOk) {
30346
+ return { action: "fail", reason: "could not verify the existing port block (Hub registry read failed) \u2014 retry; NOT allocating (a re-allocation on an unverified read would advance the cursor and hand out a duplicate block)" };
30347
+ }
30348
+ if (input.metaPortRange) return { action: "return", range: input.metaPortRange };
30349
+ return { action: "allocate" };
30350
+ }
30351
+ function existingPortRange(repo, registry2) {
30352
+ return registry2[repo] ?? null;
30353
+ }
30354
+ function portRangeInfraAt(root, source) {
30355
+ const registryPath = (0, import_node_path34.join)(root, "infra", "port-ranges.json");
30356
+ const ddbScriptPath = (0, import_node_path34.join)(root, "infra", "port-ddb.mjs");
30357
+ if (!(0, import_node_fs37.existsSync)(registryPath) || !(0, import_node_fs37.existsSync)(ddbScriptPath)) return null;
30358
+ return { root, source, registryPath, ddbScriptPath };
30359
+ }
30360
+ function resolvePortRangeInfra(cwd, packageDir) {
30361
+ const direct = portRangeInfraAt(cwd, "cwd");
30362
+ if (direct) return direct;
30363
+ for (let dir = cwd; ; dir = (0, import_node_path34.dirname)(dir)) {
30364
+ const sibling = portRangeInfraAt((0, import_node_path34.join)(dir, "MMI-Hub"), "sibling-hub");
30365
+ if (sibling) return sibling;
30366
+ const parent = (0, import_node_path34.dirname)(dir);
30367
+ if (parent === dir) break;
30368
+ }
30369
+ if (packageDir) {
30370
+ const pkgRoot = (0, import_node_path34.join)(packageDir, "..", "..");
30371
+ const pkgFrom = portRangeInfraAt(pkgRoot, "pkg-root");
30372
+ if (pkgFrom) return pkgFrom;
30373
+ }
30374
+ return null;
30375
+ }
30376
+ async function ensurePortRangeAtomic(repo, path2, allocate, opts = {}) {
30377
+ const registry2 = loadPortRegistry(path2);
30378
+ const existing = existingPortRange(repo, registry2);
30379
+ if (existing) return { range: existing, source: "existing" };
30380
+ const seed = portCursorSeed(registry2);
30381
+ try {
30382
+ const range = await allocate(seed);
30383
+ return { range, source: "ddb" };
30384
+ } catch (e) {
30385
+ if (!opts.quiet) console.warn(`port-registry: DDB allocator unreachable, falling back to committed file (${e.message})`);
30386
+ return { range: ensurePortRange(repo, path2), source: "file" };
30387
+ }
30388
+ }
30389
+
30390
+ // src/port-range-assign.ts
30391
+ async function assignPersistedPortRange(repo, slug, reg, opts) {
30392
+ const read = await fetchProjectBySlugChecked(slug, reg);
30393
+ const decision = decidePortRange({ metaReadOk: read.ok, metaPortRange: read.ok ? metaPortRange(read.project) : null });
30394
+ if (decision.action === "fail") {
30395
+ return { ok: false, error: `${decision.reason}${read.ok ? "" : ` (${read.error})`}` };
30396
+ }
30397
+ if (decision.action === "return") {
30398
+ return { ok: true, range: decision.range, source: "meta", persisted: true };
30399
+ }
30400
+ const infra = resolvePortRangeInfra(opts.cwd, opts.moduleDir);
30401
+ if (!infra) {
30402
+ return {
30403
+ ok: false,
30404
+ error: `no MMI-Hub allocator files found (checked cwd ${opts.cwd}, sibling MMI-Hub dirs, and the installed package location); ensure the Hub's infra/port-ranges.json and infra/port-ddb.mjs are reachable`
30405
+ };
30406
+ }
30407
+ const path2 = infra.registryPath;
30408
+ const allocate = async (seed) => {
30409
+ const { stdout } = await execFileP2("node", [infra.ddbScriptPath, String(seed)], { timeout: 15e3 });
30410
+ const parsed = JSON.parse(stdout);
30411
+ if (!Array.isArray(parsed.range) || parsed.range.length !== 2) throw new Error("port-ddb: no range in output");
30412
+ return parsed.range;
30413
+ };
30414
+ const { range: [start, end], source } = await ensurePortRangeAtomic(repo, path2, allocate);
30415
+ const write = await upsertProject(slug, { portRange: { start, end } }, reg);
30416
+ if (!write.ok && source === "ddb") {
30417
+ return {
30418
+ ok: false,
30419
+ error: `block [${start}, ${end}] was allocated (cursor advanced) but NOT recorded in the registry META (${write.error ?? `HTTP ${write.status}`}) \u2014 fix auth/connectivity and retry so the block is persisted; do not re-run blind`
30420
+ };
30421
+ }
30422
+ return {
30423
+ ok: true,
30424
+ range: [start, end],
30425
+ source: "allocated",
30426
+ persisted: write.ok,
30427
+ ...write.ok ? {} : { persistError: write.error ?? `HTTP ${write.status}` }
30428
+ };
30429
+ }
30430
+
29579
30431
  // src/bootstrap-drift.ts
29580
- var import_node_crypto12 = require("node:crypto");
30432
+ var import_node_crypto14 = require("node:crypto");
29581
30433
  function byteComparableSeeds(manifest, cls) {
29582
30434
  return manifest.seeds.filter((s) => s.ownership === "org" && s.source === "self" && s.classes.includes(cls));
29583
30435
  }
@@ -29597,7 +30449,7 @@ function compareSeedBytes(hubContent, repoContent) {
29597
30449
  return normalize(hubContent) === normalize(repoContent) ? "match" : "drift";
29598
30450
  }
29599
30451
  function seedContentHash(content) {
29600
- return (0, import_node_crypto12.createHash)("sha256").update(content.replace(/\r\n/g, "\n"), "utf8").digest("hex");
30452
+ return (0, import_node_crypto14.createHash)("sha256").update(content.replace(/\r\n/g, "\n"), "utf8").digest("hex");
29601
30453
  }
29602
30454
  function auditRepoSeedDrift(repo, seeds, hubContents, repoReads) {
29603
30455
  const byTarget = new Map(repoReads.map((r) => [r.target, r.content]));
@@ -29758,7 +30610,7 @@ function renderPropagationReport(plan) {
29758
30610
  }
29759
30611
 
29760
30612
  // src/bootstrap-propagation-identity.ts
29761
- var import_node_crypto13 = require("node:crypto");
30613
+ var import_node_crypto15 = require("node:crypto");
29762
30614
  var PROPAGATION_BRANCH_PREFIX = "seed-propagate-";
29763
30615
  var TARGET_MARKER_NAME = "mmi-bootstrap-propagation-target";
29764
30616
  function safeBranchPart(value, maxLength, fallback) {
@@ -29771,7 +30623,7 @@ function repoSlug2(repo) {
29771
30623
  function propagationBranch(repo, target) {
29772
30624
  const repoPart = safeBranchPart(repoSlug2(repo), 32, "repo");
29773
30625
  const targetPart = safeBranchPart(target, 48, "target");
29774
- const hash = (0, import_node_crypto13.createHash)("sha256").update(repo.trim().toLowerCase()).update("\0").update(target).digest("hex").slice(0, 12);
30626
+ const hash = (0, import_node_crypto15.createHash)("sha256").update(repo.trim().toLowerCase()).update("\0").update(target).digest("hex").slice(0, 12);
29775
30627
  return `${PROPAGATION_BRANCH_PREFIX}${repoPart}-${targetPart}-${hash}`;
29776
30628
  }
29777
30629
  function legacyPropagationBranch(repo) {
@@ -30211,6 +31063,20 @@ function filledDocCheck(label, text, path2) {
30211
31063
  const unfilled = unfilledDocPlaceholders(text);
30212
31064
  return { ok: unfilled.length === 0, label, detail: unfilled.length ? `unfilled: ${unfilled.join(", ")}` : void 0 };
30213
31065
  }
31066
+ function isCentralContainerDeployModel(model) {
31067
+ return model === "tenant-container" || model === "solo-container";
31068
+ }
31069
+ function centralContainerPortRangeCheck(deployModel, portRange, repo) {
31070
+ if (!isCentralContainerDeployModel(deployModel)) return null;
31071
+ const start = portRange?.start;
31072
+ const end = portRange?.end;
31073
+ const ok = typeof start === "number" && typeof end === "number" && Number.isFinite(start) && Number.isFinite(end) && start <= end;
31074
+ return {
31075
+ ok,
31076
+ label: "Hub registry portRange present for local stage",
31077
+ detail: ok ? void 0 : `${deployModel} needs PROJECT# META.portRange for mmi-cli stage \u2014 assign with: mmi-cli stage port-range ${repo}`
31078
+ };
31079
+ }
30214
31080
  async function verifyBootstrap(repo, repoClass, deps, releaseTrack) {
30215
31081
  const branchesWanted = expectedBranches(repoClass, releaseTrack);
30216
31082
  const baseBranch = releaseTrack === "trunk" || repoClass === "content" ? "main" : "development";
@@ -30304,6 +31170,8 @@ async function verifyBootstrap(repo, repoClass, deps, releaseTrack) {
30304
31170
  });
30305
31171
  }
30306
31172
  }
31173
+ const portRangeCheck = centralContainerPortRangeCheck(deps.deployModel, deps.projectMeta?.portRange, repo);
31174
+ if (portRangeCheck) checks.push(portRangeCheck);
30307
31175
  const readme = await contentText(deps, repo, baseBranch, "README.md");
30308
31176
  checks.push({
30309
31177
  ok: readme !== null && readme.includes("## Agent context"),
@@ -30573,13 +31441,13 @@ function registerBootstrapCommands(program3) {
30573
31441
  client: defaultGitHubClient(),
30574
31442
  projectMeta: meta,
30575
31443
  deployModel: typeof meta?.deployModel === "string" ? meta.deployModel : void 0,
30576
- readLocalFile: (path2) => path2 === "projects.json" && apiProjects != null ? apiProjects : (0, import_node_fs34.existsSync)(path2) ? (0, import_node_fs34.readFileSync)(path2, "utf8") : null,
31444
+ readLocalFile: (path2) => path2 === "projects.json" && apiProjects != null ? apiProjects : (0, import_node_fs38.existsSync)(path2) ? (0, import_node_fs38.readFileSync)(path2, "utf8") : null,
30577
31445
  // requiredGcpApis is stored as an array by a JSON write, but `org project set --var KEY=VALUE` stores a raw
30578
31446
  // comma-string — accept either so the seeded value verifies regardless of how it was written.
30579
31447
  // #3689: the same committed map the org access audit reads (#3664), so a sanctioned admin is not a
30580
31448
  // permanent bootstrap failure on one surface and an intended state on the other. Absent file → no
30581
31449
  // sanction, which is the pre-#3664 behaviour.
30582
- sanctionedAdmins: (0, import_node_fs34.existsSync)("access-matrix.json") ? entriesValueByCanonicalRepo(loadSanctionedAdmins((0, import_node_fs34.readFileSync)("access-matrix.json", "utf8")), repo) : void 0,
31450
+ sanctionedAdmins: (0, import_node_fs38.existsSync)("access-matrix.json") ? entriesValueByCanonicalRepo(loadSanctionedAdmins((0, import_node_fs38.readFileSync)("access-matrix.json", "utf8")), repo) : void 0,
30583
31451
  requiredGcpApis: (() => {
30584
31452
  const v = meta?.requiredGcpApis;
30585
31453
  if (Array.isArray(v)) return v;
@@ -30632,14 +31500,14 @@ function registerBootstrapCommands(program3) {
30632
31500
  bootstrap.command("drift").description("#3818: compare every org-owned whole-file seed against MMI-Hub's copy across the registry roster; read-only").option("--repo <owner/repo>", "audit one repo instead of the roster (never a fleet verdict)").option("--json", "machine-readable output").action(async () => {
30633
31501
  const o = { repo: rawValue("--repo", ""), json: rawFlag("--json") };
30634
31502
  const manifestPath = "skills/bootstrap/seeds/manifest.json";
30635
- if (!(0, import_node_fs34.existsSync)(manifestPath)) return fail(`bootstrap drift: ${manifestPath} not found; run from the MMI-Hub repo root \u2014 the Hub's copies ARE the reference this compares against`);
31503
+ if (!(0, import_node_fs38.existsSync)(manifestPath)) return fail(`bootstrap drift: ${manifestPath} not found; run from the MMI-Hub repo root \u2014 the Hub's copies ARE the reference this compares against`);
30636
31504
  const seedSource = await resolveHubSeedSource(execGitForSeedSource);
30637
31505
  if (!seedSource.ok) return fail(`bootstrap drift: ${seedSource.reason}`);
30638
- const manifest = loadBootstrapSeeds((0, import_node_fs34.readFileSync)(manifestPath, "utf8"));
31506
+ const manifest = loadBootstrapSeeds((0, import_node_fs38.readFileSync)(manifestPath, "utf8"));
30639
31507
  const hubContents = /* @__PURE__ */ new Map();
30640
31508
  for (const s of manifest.seeds) {
30641
31509
  if (s.ownership !== "org" || s.source !== "self") continue;
30642
- hubContents.set(s.target, (0, import_node_fs34.existsSync)(s.target) ? (0, import_node_fs34.readFileSync)(s.target, "utf8") : null);
31510
+ hubContents.set(s.target, (0, import_node_fs38.existsSync)(s.target) ? (0, import_node_fs38.readFileSync)(s.target, "utf8") : null);
30643
31511
  }
30644
31512
  let targets;
30645
31513
  let classOf = (_repo) => "deployable";
@@ -30757,10 +31625,10 @@ function registerBootstrapCommands(program3) {
30757
31625
  return;
30758
31626
  }
30759
31627
  const manifestPath = "skills/bootstrap/seeds/manifest.json";
30760
- if (!(0, import_node_fs34.existsSync)(manifestPath)) return fail(`bootstrap apply: ${manifestPath} not found; bootstrap runs from the MMI-Hub repo root by design \u2014 it stamps org-level resources (Project, Ruleset, secrets, access) through the GitHub App, which is only authorized from the Hub checkout`);
31628
+ if (!(0, import_node_fs38.existsSync)(manifestPath)) return fail(`bootstrap apply: ${manifestPath} not found; bootstrap runs from the MMI-Hub repo root by design \u2014 it stamps org-level resources (Project, Ruleset, secrets, access) through the GitHub App, which is only authorized from the Hub checkout`);
30761
31629
  const seedSource = await resolveHubSeedSource(execGitForSeedSource);
30762
31630
  if (!seedSource.ok) return fail(`bootstrap apply: ${seedSource.reason}`);
30763
- const manifest = loadBootstrapSeeds((0, import_node_fs34.readFileSync)(manifestPath, "utf8"));
31631
+ const manifest = loadBootstrapSeeds((0, import_node_fs38.readFileSync)(manifestPath, "utf8"));
30764
31632
  const baseBranch = o.class === "content" ? "main" : "development";
30765
31633
  const slug = parsedRepo.slug;
30766
31634
  const onlyTarget = o.only.trim();
@@ -30772,16 +31640,16 @@ function registerBootstrapCommands(program3) {
30772
31640
  }
30773
31641
  const onlyManagedBlock = onlyTarget ? seedsToApply[0]?.managedBlock != null : false;
30774
31642
  const gh = async (args) => execFileP2("gh", args, { timeout: 2e4 });
30775
- const readFile7 = (p) => (0, import_node_fs34.existsSync)(p) ? (0, import_node_fs34.readFileSync)(p, "utf8") : null;
31643
+ const readFile7 = (p) => (0, import_node_fs38.existsSync)(p) ? (0, import_node_fs38.readFileSync)(p, "utf8") : null;
30776
31644
  const enc = (p) => p.split("/").map(encodeURIComponent).join("/");
30777
31645
  const putSeed = async (target, content, ref, sha) => {
30778
- const tmp = (0, import_node_path32.join)((0, import_node_os15.tmpdir)(), `mmi-seed-${process.pid}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}.json`);
30779
- (0, import_node_fs34.writeFileSync)(tmp, JSON.stringify(contentPutBody(target, content, ref, sha)), "utf8");
31646
+ const tmp = (0, import_node_path35.join)((0, import_node_os17.tmpdir)(), `mmi-seed-${process.pid}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}.json`);
31647
+ (0, import_node_fs38.writeFileSync)(tmp, JSON.stringify(contentPutBody(target, content, ref, sha)), "utf8");
30780
31648
  try {
30781
31649
  await gh(contentPutInputArgs(repo, target, tmp));
30782
31650
  } finally {
30783
31651
  try {
30784
- (0, import_node_fs34.unlinkSync)(tmp);
31652
+ (0, import_node_fs38.unlinkSync)(tmp);
30785
31653
  } catch {
30786
31654
  }
30787
31655
  }
@@ -31054,10 +31922,32 @@ ${onlyManagedBlock ? `Only the marker-bounded Hub-managed block inside repo-owne
31054
31922
  }
31055
31923
  if (o.execute && !onlyTarget) {
31056
31924
  const cfg = await loadConfig();
31057
- const res = await registerProject(registerPayload, registryClientDeps(cfg));
31925
+ const reg = registryClientDeps(cfg);
31926
+ const res = await registerProject(registerPayload, reg);
31058
31927
  if (res.ok) {
31059
31928
  ddbWrites.push({ slug: registerPayload.slug, action: "register", record: registerPayload });
31060
31929
  applied.push(`ddb register ${registerPayload.slug}`);
31930
+ const deployModel = typeof registerPayload.deployModel === "string" ? registerPayload.deployModel : void 0;
31931
+ if (deployModel === "tenant-container" || deployModel === "solo-container") {
31932
+ const shortName = typeof registerPayload.name === "string" ? registerPayload.name : typeof registerPayload.slug === "string" ? registerPayload.slug : repo.split("/")[1] || repo;
31933
+ const assigned = await assignPersistedPortRange(shortName, registerPayload.slug, reg, {
31934
+ cwd: process.cwd(),
31935
+ moduleDir: __dirname
31936
+ });
31937
+ if (assigned.ok) {
31938
+ const [start, end] = assigned.range;
31939
+ ddbWrites.push({
31940
+ slug: registerPayload.slug,
31941
+ action: "portRange",
31942
+ record: { portRange: { start, end }, source: assigned.source, persisted: assigned.persisted }
31943
+ });
31944
+ applied.push(
31945
+ assigned.source === "meta" ? `portRange [${start}, ${end}] (already on META)` : `portRange [${start}, ${end}] (${assigned.persisted ? "persisted" : `META not persisted: ${assigned.persistError}`})`
31946
+ );
31947
+ } else {
31948
+ applied.push(`portRange (failed: ${assigned.error})`);
31949
+ }
31950
+ }
31061
31951
  } else {
31062
31952
  const why = res.error ?? `HTTP ${res.status}${res.body?.error ? ` \u2014 ${res.body.error}` : ""}`;
31063
31953
  applied.push(`ddb register ${registerPayload.slug} (failed: ${why})`);
@@ -31074,10 +31964,10 @@ LIVE apply to ${repo}:
31074
31964
  bootstrap.command("propagate").description("#4238: re-entrant canary\u2192wave tick \u2014 plan (or, with --execute, open) per-repo PRs fanning an org-owned seed out to the fleet").option("--target <path>", "the manifest target to propagate (an org-owned whole file or declared Hub-managed block)").option("--execute", "LIVE tick via gh (master-gated) \u2014 opens/reuses per-repo seed-propagate PRs; dry-run prints the plan only").option("--json", "machine-readable output").action(async () => {
31075
31965
  const o = { target: rawValue("--target", ""), execute: rawFlag("--execute"), json: rawFlag("--json") };
31076
31966
  const manifestPath = "skills/bootstrap/seeds/manifest.json";
31077
- if (!(0, import_node_fs34.existsSync)(manifestPath)) return fail(`bootstrap propagate: ${manifestPath} not found; run from the MMI-Hub repo root \u2014 the Hub's copies ARE the desired state this tick propagates`);
31967
+ if (!(0, import_node_fs38.existsSync)(manifestPath)) return fail(`bootstrap propagate: ${manifestPath} not found; run from the MMI-Hub repo root \u2014 the Hub's copies ARE the desired state this tick propagates`);
31078
31968
  const seedSource = await resolveHubSeedSource(execGitForSeedSource);
31079
31969
  if (!seedSource.ok) return fail(`bootstrap propagate: ${seedSource.reason}`);
31080
- const manifest = loadBootstrapSeeds((0, import_node_fs34.readFileSync)(manifestPath, "utf8"));
31970
+ const manifest = loadBootstrapSeeds((0, import_node_fs38.readFileSync)(manifestPath, "utf8"));
31081
31971
  const propagatable = manifest.seeds.filter(isPropagatableSeed);
31082
31972
  if (!o.target) {
31083
31973
  return fail(`bootstrap propagate: --target <path> is required \u2014 one of:
@@ -31086,9 +31976,9 @@ LIVE apply to ${repo}:
31086
31976
  const seed = propagatable.find((s) => s.target === o.target);
31087
31977
  if (!seed) return fail(`bootstrap propagate: --target '${o.target}' names no centrally propagatable seed in ${manifestPath}. Propagatable targets:
31088
31978
  ${propagatable.map((s) => s.target).join("\n ")}`);
31089
- if (!seed.managedBlock && !(0, import_node_fs34.existsSync)(seed.target)) return fail(`bootstrap propagate: the Hub's own copy of '${seed.target}' is missing \u2014 nothing to propagate`);
31090
- const hubContent = seed.managedBlock ? null : (0, import_node_fs34.readFileSync)(seed.target, "utf8");
31091
- const readSeedFile = (path2) => (0, import_node_fs34.existsSync)(path2) ? (0, import_node_fs34.readFileSync)(path2, "utf8") : null;
31979
+ if (!seed.managedBlock && !(0, import_node_fs38.existsSync)(seed.target)) return fail(`bootstrap propagate: the Hub's own copy of '${seed.target}' is missing \u2014 nothing to propagate`);
31980
+ const hubContent = seed.managedBlock ? null : (0, import_node_fs38.readFileSync)(seed.target, "utf8");
31981
+ const readSeedFile = (path2) => (0, import_node_fs38.existsSync)(path2) ? (0, import_node_fs38.readFileSync)(path2, "utf8") : null;
31092
31982
  const isWorkflowSeed = seed.target.startsWith(".github/workflows/");
31093
31983
  const cfg = await loadConfig();
31094
31984
  const projects = await fetchProjectsList(registryClientDeps(cfg));
@@ -31097,9 +31987,9 @@ LIVE apply to ${repo}:
31097
31987
  }
31098
31988
  const rosterRepos2 = collectRegistryRepos(projects).filter((r) => r.toLowerCase() !== "mutmutco/mmi-hub");
31099
31989
  let independentCount = rosterRepos2.length;
31100
- if ((0, import_node_fs34.existsSync)("projects.json")) {
31990
+ if ((0, import_node_fs38.existsSync)("projects.json")) {
31101
31991
  try {
31102
- const local = JSON.parse((0, import_node_fs34.readFileSync)("projects.json", "utf8"));
31992
+ const local = JSON.parse((0, import_node_fs38.readFileSync)("projects.json", "utf8"));
31103
31993
  const localRepos = /* @__PURE__ */ new Set();
31104
31994
  for (const p of local.projects ?? []) for (const r of p.repos ?? []) {
31105
31995
  const full = (r.includes("/") ? r : `mutmutco/${r}`).toLowerCase();
@@ -31246,15 +32136,15 @@ LIVE apply to ${repo}:
31246
32136
  } catch {
31247
32137
  existingSha = void 0;
31248
32138
  }
31249
- const tmp = (0, import_node_path32.join)((0, import_node_os15.tmpdir)(), `mmi-propagate-${process.pid}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}.json`);
32139
+ const tmp = (0, import_node_path35.join)((0, import_node_os17.tmpdir)(), `mmi-propagate-${process.pid}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}.json`);
31250
32140
  const desiredContent = desiredByRepo.get(rec.repo);
31251
32141
  if (desiredContent == null) return fail(`bootstrap propagate: no resolved content for ${rec.repo} ${seed.target} \u2014 refusing to write`);
31252
- (0, import_node_fs34.writeFileSync)(tmp, JSON.stringify(contentPutBody(seed.target, desiredContent, branch, existingSha)), "utf8");
32142
+ (0, import_node_fs38.writeFileSync)(tmp, JSON.stringify(contentPutBody(seed.target, desiredContent, branch, existingSha)), "utf8");
31253
32143
  try {
31254
32144
  await gh(contentPutInputArgs(rec.repo, seed.target, tmp));
31255
32145
  } finally {
31256
32146
  try {
31257
- (0, import_node_fs34.unlinkSync)(tmp);
32147
+ (0, import_node_fs38.unlinkSync)(tmp);
31258
32148
  } catch {
31259
32149
  }
31260
32150
  }
@@ -31321,10 +32211,10 @@ Rollback: \`mmi-cli devops bootstrap rollback ${rec.repo} --target ${seed.target
31321
32211
  return fail(`bootstrap rollback: ${e.message}`);
31322
32212
  }
31323
32213
  const manifestPath = "skills/bootstrap/seeds/manifest.json";
31324
- if (!(0, import_node_fs34.existsSync)(manifestPath)) return fail(`bootstrap rollback: ${manifestPath} not found; run from the MMI-Hub repo root \u2014 the manifest names which targets are org-owned and therefore propagated (and rollback-able)`);
32214
+ if (!(0, import_node_fs38.existsSync)(manifestPath)) return fail(`bootstrap rollback: ${manifestPath} not found; run from the MMI-Hub repo root \u2014 the manifest names which targets are org-owned and therefore propagated (and rollback-able)`);
31325
32215
  const seedSource = await resolveHubSeedSource(execGitForSeedSource);
31326
32216
  if (!seedSource.ok) return fail(`bootstrap rollback: ${seedSource.reason}`);
31327
- const manifest = loadBootstrapSeeds((0, import_node_fs34.readFileSync)(manifestPath, "utf8"));
32217
+ const manifest = loadBootstrapSeeds((0, import_node_fs38.readFileSync)(manifestPath, "utf8"));
31328
32218
  const propagatable = manifest.seeds.filter(isPropagatableSeed);
31329
32219
  if (!o.target) {
31330
32220
  return fail(`bootstrap rollback: --target <path> is required \u2014 one of:
@@ -31341,10 +32231,10 @@ Rollback: \`mmi-cli devops bootstrap rollback ${rec.repo} --target ${seed.target
31341
32231
  const enc = (p) => p.split("/").map(encodeURIComponent).join("/");
31342
32232
  let candidates;
31343
32233
  if (o.record) {
31344
- if (!(0, import_node_fs34.existsSync)(o.record)) return fail(`bootstrap rollback: --record '${o.record}' not found`);
32234
+ if (!(0, import_node_fs38.existsSync)(o.record)) return fail(`bootstrap rollback: --record '${o.record}' not found`);
31345
32235
  let parsed;
31346
32236
  try {
31347
- parsed = JSON.parse((0, import_node_fs34.readFileSync)(o.record, "utf8"));
32237
+ parsed = JSON.parse((0, import_node_fs38.readFileSync)(o.record, "utf8"));
31348
32238
  } catch (e) {
31349
32239
  return fail(`bootstrap rollback: --record '${o.record}' is not valid JSON: ${e.message}`);
31350
32240
  }
@@ -31421,13 +32311,13 @@ Rollback: \`mmi-cli devops bootstrap rollback ${rec.repo} --target ${seed.target
31421
32311
  } catch {
31422
32312
  existingSha = void 0;
31423
32313
  }
31424
- const tmp = (0, import_node_path32.join)((0, import_node_os15.tmpdir)(), `mmi-rollback-${process.pid}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}.json`);
31425
- (0, import_node_fs34.writeFileSync)(tmp, JSON.stringify(contentPutBody(seed.target, preSeedContent, plan.branch, existingSha)), "utf8");
32314
+ const tmp = (0, import_node_path35.join)((0, import_node_os17.tmpdir)(), `mmi-rollback-${process.pid}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}.json`);
32315
+ (0, import_node_fs38.writeFileSync)(tmp, JSON.stringify(contentPutBody(seed.target, preSeedContent, plan.branch, existingSha)), "utf8");
31426
32316
  try {
31427
32317
  await gh(contentPutInputArgs(repo, seed.target, tmp));
31428
32318
  } finally {
31429
32319
  try {
31430
- (0, import_node_fs34.unlinkSync)(tmp);
32320
+ (0, import_node_fs38.unlinkSync)(tmp);
31431
32321
  } catch {
31432
32322
  }
31433
32323
  }
@@ -31452,101 +32342,11 @@ Rollback: \`mmi-cli devops bootstrap rollback ${rec.repo} --target ${seed.target
31452
32342
  }
31453
32343
 
31454
32344
  // src/stage-commands.ts
31455
- var import_node_fs36 = require("node:fs");
31456
- var import_node_path34 = require("node:path");
32345
+ var import_node_fs39 = require("node:fs");
32346
+ var import_node_path36 = require("node:path");
31457
32347
  init_cli_shared();
31458
32348
  init_clean_exit();
31459
32349
 
31460
- // src/port-registry.ts
31461
- var import_node_fs35 = require("node:fs");
31462
- var import_node_path33 = require("node:path");
31463
-
31464
- // ../infra/port-geometry.mjs
31465
- var PORT_BLOCK = 100;
31466
- var PORT_SPAN = 10;
31467
- var PORT_FIRST = 3e3;
31468
-
31469
- // src/port-registry.ts
31470
- function nextPortBlock(registry2) {
31471
- const bases = Object.values(registry2).map(([start]) => start);
31472
- const base = bases.length ? Math.max(...bases) + PORT_BLOCK : PORT_FIRST;
31473
- return [base, base + PORT_SPAN];
31474
- }
31475
- function loadPortRegistry(path2) {
31476
- if (!(0, import_node_fs35.existsSync)(path2)) return {};
31477
- const raw = JSON.parse((0, import_node_fs35.readFileSync)(path2, "utf8"));
31478
- const out = {};
31479
- for (const [key, value] of Object.entries(raw)) {
31480
- if (Array.isArray(value) && value.length === 2 && value.every((n) => typeof n === "number")) {
31481
- out[key] = [value[0], value[1]];
31482
- }
31483
- }
31484
- return out;
31485
- }
31486
- function ensurePortRange(repo, path2) {
31487
- const registry2 = loadPortRegistry(path2);
31488
- const existing = registry2[repo];
31489
- if (existing) return existing;
31490
- const range = nextPortBlock(registry2);
31491
- const raw = (0, import_node_fs35.existsSync)(path2) ? JSON.parse((0, import_node_fs35.readFileSync)(path2, "utf8")) : {};
31492
- raw[repo] = range;
31493
- (0, import_node_fs35.writeFileSync)(path2, JSON.stringify(raw, null, 2) + "\n", "utf8");
31494
- return range;
31495
- }
31496
- function portCursorSeed(registry2) {
31497
- return nextPortBlock(registry2)[0];
31498
- }
31499
- function metaPortRange(meta) {
31500
- const r = meta?.portRange;
31501
- if (r && typeof r.start === "number" && typeof r.end === "number") return [r.start, r.end];
31502
- return null;
31503
- }
31504
- function decidePortRange(input) {
31505
- if (!input.metaReadOk) {
31506
- return { action: "fail", reason: "could not verify the existing port block (Hub registry read failed) \u2014 retry; NOT allocating (a re-allocation on an unverified read would advance the cursor and hand out a duplicate block)" };
31507
- }
31508
- if (input.metaPortRange) return { action: "return", range: input.metaPortRange };
31509
- return { action: "allocate" };
31510
- }
31511
- function existingPortRange(repo, registry2) {
31512
- return registry2[repo] ?? null;
31513
- }
31514
- function portRangeInfraAt(root, source) {
31515
- const registryPath = (0, import_node_path33.join)(root, "infra", "port-ranges.json");
31516
- const ddbScriptPath = (0, import_node_path33.join)(root, "infra", "port-ddb.mjs");
31517
- if (!(0, import_node_fs35.existsSync)(registryPath) || !(0, import_node_fs35.existsSync)(ddbScriptPath)) return null;
31518
- return { root, source, registryPath, ddbScriptPath };
31519
- }
31520
- function resolvePortRangeInfra(cwd, packageDir) {
31521
- const direct = portRangeInfraAt(cwd, "cwd");
31522
- if (direct) return direct;
31523
- for (let dir = cwd; ; dir = (0, import_node_path33.dirname)(dir)) {
31524
- const sibling = portRangeInfraAt((0, import_node_path33.join)(dir, "MMI-Hub"), "sibling-hub");
31525
- if (sibling) return sibling;
31526
- const parent = (0, import_node_path33.dirname)(dir);
31527
- if (parent === dir) break;
31528
- }
31529
- if (packageDir) {
31530
- const pkgRoot = (0, import_node_path33.join)(packageDir, "..", "..");
31531
- const pkgFrom = portRangeInfraAt(pkgRoot, "pkg-root");
31532
- if (pkgFrom) return pkgFrom;
31533
- }
31534
- return null;
31535
- }
31536
- async function ensurePortRangeAtomic(repo, path2, allocate, opts = {}) {
31537
- const registry2 = loadPortRegistry(path2);
31538
- const existing = existingPortRange(repo, registry2);
31539
- if (existing) return { range: existing, source: "existing" };
31540
- const seed = portCursorSeed(registry2);
31541
- try {
31542
- const range = await allocate(seed);
31543
- return { range, source: "ddb" };
31544
- } catch (e) {
31545
- if (!opts.quiet) console.warn(`port-registry: DDB allocator unreachable, falling back to committed file (${e.message})`);
31546
- return { range: ensurePortRange(repo, path2), source: "file" };
31547
- }
31548
- }
31549
-
31550
32350
  // src/stage-default.ts
31551
32351
  function shellFor(platform2 = process.platform) {
31552
32352
  return platform2 === "win32" ? "powershell" : "bash";
@@ -31554,14 +32354,20 @@ function shellFor(platform2 = process.platform) {
31554
32354
  function isCentralContainerModel(model) {
31555
32355
  return model === "tenant-container" || model === "solo-container";
31556
32356
  }
31557
- function deriveStageGap(inputs) {
32357
+ function stagePortRangeRecovery(repo = "<owner/repo>") {
32358
+ return `mmi-cli stage port-range ${repo}`;
32359
+ }
32360
+ function deriveStageGap(inputs, opts) {
31558
32361
  const missing = [];
31559
32362
  if (!isCentralContainerModel(inputs.deployModel)) {
31560
32363
  return `local stage default applies to central-container repos only (tenant-container/solo-container; registry deployModel = ${inputs.deployModel ?? "unset"})`;
31561
32364
  }
31562
32365
  if (!inputs.hasCompose) missing.push("docker-compose.yml");
31563
32366
  if (!inputs.portRange) missing.push("Hub registry portRange");
31564
- return missing.length ? `cannot derive a default local stage \u2014 missing: ${missing.join(", ")}` : null;
32367
+ if (!missing.length) return null;
32368
+ const base = `cannot derive a default local stage \u2014 missing: ${missing.join(", ")}`;
32369
+ if (!inputs.portRange) return `${base} \u2014 assign with: ${stagePortRangeRecovery(opts?.repo)}`;
32370
+ return base;
31565
32371
  }
31566
32372
  function deriveStage(inputs) {
31567
32373
  if (deriveStageGap(inputs) || !inputs.portRange) return null;
@@ -31592,7 +32398,7 @@ function stageUrlForPort(port) {
31592
32398
  return `http://127.0.0.1:${port}/`;
31593
32399
  }
31594
32400
  function decideStage(inputs) {
31595
- const { registry: registry2, hasCompose, hasEnvExample } = inputs;
32401
+ const { registry: registry2, hasCompose, hasEnvExample, repo } = inputs;
31596
32402
  const deriveInputs = {
31597
32403
  portRange: registry2.portRange,
31598
32404
  deployModel: registry2.deployModel,
@@ -31602,8 +32408,9 @@ function decideStage(inputs) {
31602
32408
  const derived = deriveStage(deriveInputs);
31603
32409
  if (derived) return { source: "derived", derived, registryError: registry2.error };
31604
32410
  const registryGap = registry2.error ? `Hub registry read failed (${registry2.error}) \u2014 cannot derive a default local stage` : null;
31605
- const gap = registryGap ?? deriveStageGap(deriveInputs) ?? "no registry-derived default available";
31606
- return { source: "none", gap, registryError: registry2.error };
32411
+ const gap = registryGap ?? deriveStageGap(deriveInputs, { repo }) ?? "no registry-derived default available";
32412
+ const recovery = !registry2.error && isCentralContainerModel(registry2.deployModel) && !registry2.portRange ? stagePortRangeRecovery(repo) : void 0;
32413
+ return { source: "none", gap, ...recovery ? { recovery } : {}, registryError: registry2.error };
31607
32414
  }
31608
32415
 
31609
32416
  // src/stage-live.ts
@@ -31813,14 +32620,17 @@ function registerStageCommands(program3) {
31813
32620
  }
31814
32621
  async function resolveStage() {
31815
32622
  const cfg = await loadConfig();
31816
- const read = await fetchProjectBySlugChecked(await repoSlug(), registryClientDeps(cfg)).catch((e) => ({ ok: false, error: e.message }));
32623
+ const slug = await repoSlug();
32624
+ const read = await fetchProjectBySlugChecked(slug, registryClientDeps(cfg)).catch((e) => ({ ok: false, error: e.message }));
31817
32625
  const project2 = read.ok ? read.project : null;
31818
32626
  const portRangeMeta = project2?.portRange ?? void 0;
31819
32627
  const portRange = portRangeMeta && typeof portRangeMeta.start === "number" && typeof portRangeMeta.end === "number" ? [portRangeMeta.start, portRangeMeta.end] : void 0;
32628
+ const repo = Array.isArray(project2?.repos) && typeof project2.repos[0] === "string" ? project2.repos[0] : slug;
31820
32629
  return decideStage({
31821
32630
  registry: { deployModel: project2?.deployModel, portRange, error: read.ok ? void 0 : read.error },
31822
- hasCompose: (0, import_node_fs36.existsSync)((0, import_node_path34.join)(process.cwd(), "docker-compose.yml")),
31823
- hasEnvExample: (0, import_node_fs36.existsSync)((0, import_node_path34.join)(process.cwd(), ".env.example"))
32631
+ hasCompose: (0, import_node_fs39.existsSync)((0, import_node_path36.join)(process.cwd(), "docker-compose.yml")),
32632
+ hasEnvExample: (0, import_node_fs39.existsSync)((0, import_node_path36.join)(process.cwd(), ".env.example")),
32633
+ repo
31824
32634
  });
31825
32635
  }
31826
32636
  async function fetchStageVaultEnvMerge() {
@@ -31865,7 +32675,16 @@ function registerStageCommands(program3) {
31865
32675
  }
31866
32676
  function stageStepsFor(res, stops = true) {
31867
32677
  if (res.source === "derived" && res.derived) return derivedStagePlan(res.derived, shellFor(), stops);
31868
- return [{ label: `no local stage to run \u2014 ${res.gap ?? "stage config gap"}` }];
32678
+ return [{ label: `no local stage to run \u2014 ${res.gap ?? "stage config gap"}`, ...res.recovery ? { command: res.recovery } : {} }];
32679
+ }
32680
+ function stageReceiptFields(res) {
32681
+ return {
32682
+ source: res.source,
32683
+ url: res.derived?.url,
32684
+ ...res.gap ? { gap: res.gap } : {},
32685
+ ...res.recovery ? { recovery: res.recovery } : {},
32686
+ ...res.registryError ? { registryError: res.registryError } : {}
32687
+ };
31869
32688
  }
31870
32689
  function reportedStageUrl(res, result) {
31871
32690
  if (!res.derived) return void 0;
@@ -31875,38 +32694,23 @@ function registerStageCommands(program3) {
31875
32694
  const cfg = await loadConfig();
31876
32695
  const reg = registryClientDeps(cfg);
31877
32696
  const slug = slugOf(repo);
31878
- const read = await fetchProjectBySlugChecked(slug, reg);
31879
- const decision = decidePortRange({ metaReadOk: read.ok, metaPortRange: read.ok ? metaPortRange(read.project) : null });
31880
- if (decision.action === "fail") {
31881
- return failGraceful(`stage port-range: ${decision.reason}${read.ok ? "" : ` (${read.error})`}`);
31882
- }
31883
- if (decision.action === "return") {
31884
- const [start2, end2] = decision.range;
31885
- printLine(o.json ? JSON.stringify({ repo, portRange: [start2, end2], source: "meta" }) : `${repo}: stage.portRange [${start2}, ${end2}]`);
32697
+ const assigned = await assignPersistedPortRange(repo, slug, reg, { cwd: process.cwd(), moduleDir: __dirname });
32698
+ if (!assigned.ok) return failGraceful(`stage port-range: ${assigned.error}`);
32699
+ const [start, end] = assigned.range;
32700
+ if (assigned.source === "meta") {
32701
+ printLine(o.json ? JSON.stringify({ repo, portRange: [start, end], source: "meta" }) : `${repo}: stage.portRange [${start}, ${end}]`);
31886
32702
  return;
31887
32703
  }
31888
- const infra = resolvePortRangeInfra(process.cwd(), __dirname);
31889
- if (!infra) {
31890
- return failGraceful(
31891
- `stage port-range: no MMI-Hub allocator files found (checked cwd ${process.cwd()}, sibling MMI-Hub dirs, and the installed package location); ensure the Hub's infra/port-ranges.json and infra/port-ddb.mjs are reachable`
31892
- );
31893
- }
31894
- const path2 = infra.registryPath;
31895
- const allocate = async (seed) => {
31896
- const { stdout } = await execFileP2("node", [infra.ddbScriptPath, String(seed)], { timeout: 15e3 });
31897
- const parsed = JSON.parse(stdout);
31898
- if (!Array.isArray(parsed.range) || parsed.range.length !== 2) throw new Error("port-ddb: no range in output");
31899
- return parsed.range;
31900
- };
31901
- const { range: [start, end], source } = await ensurePortRangeAtomic(repo, path2, allocate);
31902
- const write = await upsertProject(slug, { portRange: { start, end } }, reg);
31903
- if (!write.ok && source === "ddb") {
31904
- return failGraceful(`stage port-range: block [${start}, ${end}] was allocated (cursor advanced) but NOT recorded in the registry META (${write.error ?? `HTTP ${write.status}`}) \u2014 fix auth/connectivity and retry so the block is persisted; do not re-run blind`);
31905
- }
31906
32704
  if (o.json) {
31907
- printLine(JSON.stringify({ repo, portRange: [start, end], source: "allocated", persisted: write.ok, ...write.ok ? {} : { persistError: write.error ?? `HTTP ${write.status}` } }));
32705
+ printLine(JSON.stringify({
32706
+ repo,
32707
+ portRange: [start, end],
32708
+ source: "allocated",
32709
+ persisted: assigned.persisted,
32710
+ ...assigned.persisted ? {} : { persistError: assigned.persistError }
32711
+ }));
31908
32712
  } else {
31909
- printLine(`${repo}: stage.portRange [${start}, ${end}]${write.ok ? "" : ` (META not persisted: ${write.error ?? `HTTP ${write.status}`})`}`);
32713
+ printLine(`${repo}: stage.portRange [${start}, ${end}]${assigned.persisted ? "" : ` (META not persisted: ${assigned.persistError})`}`);
31910
32714
  }
31911
32715
  });
31912
32716
  async function stageLiveTarget() {
@@ -31992,7 +32796,7 @@ function registerStageCommands(program3) {
31992
32796
  }
31993
32797
  }
31994
32798
  const steps = stageStepsFor(res);
31995
- if (o.json) return console.log(JSON.stringify({ command: "stage", source: res.source, url: res.derived?.url, registryError: res.registryError, steps }, null, 2));
32799
+ if (o.json) return console.log(JSON.stringify({ command: "stage", ...stageReceiptFields(res), steps }, null, 2));
31996
32800
  console.log(renderSteps("mmi-cli stage: dry-run plan", steps));
31997
32801
  });
31998
32802
  stage.command("stop").description("stop the previous local stage process recorded in tmp/stage/state.json").option("--json", "machine-readable output").option("--apply", "kill the recorded process tree and remove the state file").action(async () => {
@@ -32013,7 +32817,7 @@ function registerStageCommands(program3) {
32013
32817
  const res = await resolveStage();
32014
32818
  if (!o.apply) {
32015
32819
  const steps = stageStepsFor(res, false);
32016
- if (o.json) return printLine(JSON.stringify({ command: "stage start", source: res.source, url: res.derived?.url, registryError: res.registryError, steps }, null, 2));
32820
+ if (o.json) return printLine(JSON.stringify({ command: "stage start", ...stageReceiptFields(res), steps }, null, 2));
32017
32821
  return printLine(renderSteps("mmi-cli stage start: dry-run plan", steps));
32018
32822
  }
32019
32823
  if (res.source === "none") return failGraceful(`stage start: ${res.gap}`);
@@ -32046,7 +32850,7 @@ function registerStageCommands(program3) {
32046
32850
  const res = await resolveStage();
32047
32851
  if (!o.apply) {
32048
32852
  const steps = stageStepsFor(res);
32049
- if (o.json) return printLine(JSON.stringify({ command: "stage run", source: res.source, url: res.derived?.url, registryError: res.registryError, steps }, null, 2));
32853
+ if (o.json) return printLine(JSON.stringify({ command: "stage run", ...stageReceiptFields(res), steps }, null, 2));
32050
32854
  return printLine(renderSteps("mmi-cli stage run: dry-run plan", steps));
32051
32855
  }
32052
32856
  if (res.source === "none") return failGraceful(`stage run: ${res.gap}`);
@@ -32078,9 +32882,9 @@ function registerStageCommands(program3) {
32078
32882
  }
32079
32883
 
32080
32884
  // src/merge-cleanup.ts
32081
- var import_node_fs37 = require("node:fs");
32082
- var import_node_path35 = require("node:path");
32083
- var import_node_os16 = require("node:os");
32885
+ var import_node_fs40 = require("node:fs");
32886
+ var import_node_path37 = require("node:path");
32887
+ var import_node_os18 = require("node:os");
32084
32888
  init_cli_shared();
32085
32889
 
32086
32890
  // src/config-load.ts
@@ -32357,13 +33161,13 @@ async function composeOverrideBodyFile(prNumber, repoArgs, gh) {
32357
33161
  const commits = JSON.parse(raw).commits ?? [];
32358
33162
  const body = squashBodyWithOverride(commits.map((c) => ({ headline: c.messageHeadline ?? "", body: c.messageBody ?? "" })), process.cwd());
32359
33163
  if (!body) return void 0;
32360
- const dir = (0, import_node_fs37.mkdtempSync)((0, import_node_path35.join)((0, import_node_os16.tmpdir)(), "mmi-squash-body-"));
32361
- const path2 = (0, import_node_path35.join)(dir, "body.txt");
32362
- (0, import_node_fs37.writeFileSync)(path2, `${body}
33164
+ const dir = (0, import_node_fs40.mkdtempSync)((0, import_node_path37.join)((0, import_node_os18.tmpdir)(), "mmi-squash-body-"));
33165
+ const path2 = (0, import_node_path37.join)(dir, "body.txt");
33166
+ (0, import_node_fs40.writeFileSync)(path2, `${body}
32363
33167
  `, "utf8");
32364
33168
  return { path: path2, cleanup: () => {
32365
33169
  try {
32366
- (0, import_node_fs37.rmSync)(dir, { recursive: true, force: true });
33170
+ (0, import_node_fs40.rmSync)(dir, { recursive: true, force: true });
32367
33171
  } catch {
32368
33172
  }
32369
33173
  } };
@@ -32570,7 +33374,9 @@ function registerBoardCommands(program3) {
32570
33374
  "Pass raw issue numbers/refs, not URLs.",
32571
33375
  "Claim already assigns and moves Status to In Progress, so do not also board move it.",
32572
33376
  "Multiple refs are handled as a batch and return per-item results.",
32573
- "--check is the live gate read (it calls GitHub); --dry-run only echoes the parsed argv plan."
33377
+ "--check is the live gate read (it calls GitHub); --dry-run only echoes the parsed argv plan.",
33378
+ // #5552: agents guess `oracle issue claim`; that route does not exist — board claim is the only write.
33379
+ "Never run `oracle issue claim` \u2014 claims are board mutations; only `oracle board claim <ref>` is valid."
32574
33380
  ]);
32575
33381
  board.command("show <issue>").description("print one board item (status, assignees, type, url) with its body and comments").option("--json", "machine-readable output").option("--repo <owner/repo>", "current repo for local issue numbers (defaults to git origin)").option("--allow-partial", "return the item even if its body/comments fetch fails").action(async (issueRef, o) => {
32576
33382
  try {
@@ -32859,18 +33665,33 @@ var PR_SNAPSHOT_READ_RETRIES = 3;
32859
33665
  var PR_SNAPSHOT_READ_DELAY_MS = 2e3;
32860
33666
  async function readRestPrSnapshotWithRetry(prNumber, repo, gh = defaultGhApi, options) {
32861
33667
  const retries = options?.retries ?? PR_SNAPSHOT_READ_RETRIES;
32862
- const delayMs = options?.delayMs ?? PR_SNAPSHOT_READ_DELAY_MS;
32863
- const sleep2 = options?.sleep ?? ((ms) => new Promise((resolve5) => setTimeout(resolve5, ms)));
33668
+ const untilMs = options?.retryTransientUntilMs;
33669
+ const delayMs = options?.delayMs ?? (untilMs !== void 0 ? PR_CHECKS_POLL_MS : PR_SNAPSHOT_READ_DELAY_MS);
33670
+ const sleep2 = options?.sleep ?? ((ms) => new Promise((resolve6) => setTimeout(resolve6, ms)));
33671
+ const now = options?.now ?? (() => Date.now());
32864
33672
  let lastError = "no attempt completed";
32865
- for (let attempt = 0; attempt < retries; attempt++) {
33673
+ let attempt = 0;
33674
+ for (; ; ) {
32866
33675
  try {
32867
33676
  return { state: "ok", snapshot: await fetchRestPrSnapshot(prNumber, repo, gh) };
32868
33677
  } catch (e) {
32869
33678
  lastError = readErrorText(e);
33679
+ attempt += 1;
33680
+ const retryable = isRetryableGitHubWaitReadError(e);
33681
+ if (!retryable) {
33682
+ return { state: "failed", error: `pulls read failed for #${prNumber} on ${repo}: ${lastError}` };
33683
+ }
33684
+ const canRetry = untilMs !== void 0 ? now() + delayMs < untilMs : attempt < retries;
33685
+ if (!canRetry) {
33686
+ return {
33687
+ state: "failed",
33688
+ error: `pulls read failed for #${prNumber} on ${repo} after ${attempt} attempts: ${lastError}`
33689
+ };
33690
+ }
33691
+ options?.onTransientRetry?.(lastError, attempt);
33692
+ await sleep2(delayMs);
32870
33693
  }
32871
- if (attempt < retries - 1) await sleep2(delayMs);
32872
33694
  }
32873
- return { state: "failed", error: `pulls read failed for #${prNumber} on ${repo} after ${retries} attempts: ${lastError}` };
32874
33695
  }
32875
33696
  async function fetchRestClosingGuardPayload(prNumber, repo, gh = defaultGhApi) {
32876
33697
  const pr2 = JSON.parse(await gh([`repos/${repo}/pulls/${prNumber}`]));
@@ -33160,8 +33981,8 @@ async function checkDocsIndexAtHead(opts, deps) {
33160
33981
  }
33161
33982
 
33162
33983
  // src/issue-commands.ts
33163
- var import_node_fs38 = require("node:fs");
33164
- var import_node_crypto14 = require("node:crypto");
33984
+ var import_node_fs41 = require("node:fs");
33985
+ var import_node_crypto16 = require("node:crypto");
33165
33986
  init_cli_shared();
33166
33987
  init_clean_exit();
33167
33988
  init_error_codes();
@@ -33353,7 +34174,7 @@ async function editIssue(client, options, deps = {}) {
33353
34174
  const url = `https://github.com/${repo}/issues/${parsed.number}`;
33354
34175
  const patch = {};
33355
34176
  let bodyChanged = false;
33356
- const textDeps = () => deps.textDeps ?? { readFile: (p, e) => Promise.resolve((0, import_node_fs38.readFileSync)(p, e)), readStdin: () => Promise.resolve("") };
34177
+ const textDeps = () => deps.textDeps ?? { readFile: (p, e) => Promise.resolve((0, import_node_fs41.readFileSync)(p, e)), readStdin: () => Promise.resolve("") };
33357
34178
  if (options.titleFile !== void 0) {
33358
34179
  patch.title = await resolveIssueTitle({ title: options.title, titleFile: options.titleFile }, textDeps());
33359
34180
  } else if (options.title !== void 0) {
@@ -33634,7 +34455,7 @@ function rowIdempotencyKey(batchKey, spec) {
33634
34455
  const identity = `${spec.type}
33635
34456
  ${spec.title.trim()}
33636
34457
  ${spec.body ?? ""}`;
33637
- const hash = (0, import_node_crypto14.createHash)("sha256").update(identity).digest("hex").slice(0, 16);
34458
+ const hash = (0, import_node_crypto16.createHash)("sha256").update(identity).digest("hex").slice(0, 16);
33638
34459
  return `${batchKey}:${hash}`;
33639
34460
  }
33640
34461
  var BATCH_SPEC_KEYS = /* @__PURE__ */ new Set(["type", "title", "body", "priority", "labels", "label", "parent", "repo", "surface"]);
@@ -33970,7 +34791,7 @@ function extendCreateCommand(issue2, batchAttach) {
33970
34791
  if (opts.batch) {
33971
34792
  let specs;
33972
34793
  try {
33973
- const raw = (0, import_node_fs38.readFileSync)(opts.batch, "utf8");
34794
+ const raw = (0, import_node_fs41.readFileSync)(opts.batch, "utf8");
33974
34795
  specs = JSON.parse(raw);
33975
34796
  if (!Array.isArray(specs)) throw new Error("batch file must contain a JSON array");
33976
34797
  } catch (e) {
@@ -34045,8 +34866,8 @@ ${lines}`, {
34045
34866
  }
34046
34867
 
34047
34868
  // src/train-commands.ts
34048
- var import_node_fs39 = require("node:fs");
34049
- var import_node_path36 = require("node:path");
34869
+ var import_node_fs42 = require("node:fs");
34870
+ var import_node_path38 = require("node:path");
34050
34871
  init_cli_shared();
34051
34872
  init_clean_exit();
34052
34873
  init_client_version();
@@ -34061,7 +34882,7 @@ function resolveReleaseBumpIntent(raw) {
34061
34882
  }
34062
34883
  function readRepoVersion() {
34063
34884
  try {
34064
- return JSON.parse((0, import_node_fs39.readFileSync)((0, import_node_path36.join)(process.cwd(), ".claude-plugin", "plugin.json"), "utf8")).version || void 0;
34885
+ return JSON.parse((0, import_node_fs42.readFileSync)((0, import_node_path38.join)(process.cwd(), ".claude-plugin", "plugin.json"), "utf8")).version || void 0;
34065
34886
  } catch {
34066
34887
  return void 0;
34067
34888
  }
@@ -34225,9 +35046,9 @@ function registerDeployCommands(program3) {
34225
35046
  init_cli_shared();
34226
35047
  init_github_client();
34227
35048
  init_cli_shared();
34228
- var import_node_fs40 = require("node:fs");
34229
- var import_node_os17 = require("node:os");
34230
- var import_node_path37 = require("node:path");
35049
+ var import_node_fs43 = require("node:fs");
35050
+ var import_node_os19 = require("node:os");
35051
+ var import_node_path39 = require("node:path");
34231
35052
  init_marketplace_autoupdate();
34232
35053
  var GC_GH_TIMEOUT_MS2 = 2e4;
34233
35054
  async function collectStatus() {
@@ -34346,7 +35167,7 @@ function onboardPluginGate(deps) {
34346
35167
  declared,
34347
35168
  settingsDeclared: readSettingsAutoUpdate(deps.readSettings(), MMI_MARKETPLACE_NAME)
34348
35169
  }).effective;
34349
- return autoUpdate ? { ok: false, detail: "background auto-update is ON \u2014 duplicate writer; run `mmi-hub update` outside Claude Code to disable it" } : { ok: true, detail: "background auto-update off; mmi-hub is the single release-gated writer" };
35170
+ return autoUpdate ? { ok: true, detail: "background auto-update on \u2014 org pin; thin catalog is release-gated" } : { ok: false, detail: "background auto-update is OFF \u2014 this machine will not pick up a new plugin release on its own; run `mmi-cli doctor` to pin it on" };
34350
35171
  }
34351
35172
  async function collectOnboardStatus(opts = {}) {
34352
35173
  const cfg = await loadConfig();
@@ -34428,10 +35249,10 @@ async function collectOnboardStatus(opts = {}) {
34428
35249
  else if (top) nextCommand = `mmi-cli oracle board claim ${top.number} # ${top.title}`;
34429
35250
  else nextCommand = "mmi-cli oracle board read \u2014 no claimable items found";
34430
35251
  }
34431
- const home = (0, import_node_os17.homedir)();
35252
+ const home = (0, import_node_os19.homedir)();
34432
35253
  const plugin = onboardPluginGate({
34433
- readKnown: () => readFileSyncSafe((0, import_node_path37.join)(home, ...KNOWN_MARKETPLACES_RELATIVE), import_node_fs40.readFileSync),
34434
- readSettings: () => readFileSyncSafe((0, import_node_path37.join)(home, ".claude", "settings.json"), import_node_fs40.readFileSync)
35254
+ readKnown: () => readFileSyncSafe((0, import_node_path39.join)(home, ...KNOWN_MARKETPLACES_RELATIVE), import_node_fs43.readFileSync),
35255
+ readSettings: () => readFileSyncSafe((0, import_node_path39.join)(home, ".claude", "settings.json"), import_node_fs43.readFileSync)
34435
35256
  });
34436
35257
  return { track, board, registry: registry2, secrets, plugin, estateCli, doors: opts.doors ?? [], nextCommand };
34437
35258
  }
@@ -34514,12 +35335,17 @@ var LOOP_PLAYBOOKS = {
34514
35335
  { label: "Orient in the current repository", command: "mmi-cli onboard" },
34515
35336
  { label: "Structure search: `find` is semantic default; `repo-index search` is hybrid. Cloud default (`--local` is checkout-only); verify repo/commit/path/lines and result retrieval metadata; status is runtime authority", command: "mmi-cli oracle find <q>" },
34516
35337
  { label: "Read the next board item", command: "mmi-cli oracle board read" },
35338
+ // #5552: claim is a board mutation only — never guess `oracle issue claim`. Ground unknown write
35339
+ // routes with `mmi-cli commands` / `mmi-cli explain` before invoking them.
35340
+ { label: "Claim board work (board mutation only \u2014 never `oracle issue claim`; ground unknown write routes via `commands` / `explain` first)", command: "mmi-cli oracle board claim <ref>" },
34517
35341
  { label: "Prepare the local workspace through the host surface" },
34518
35342
  { label: "Apply the repository test policy, then build the touched package", command: "mmi-cli tests policy --base origin/development && npm run build" },
34519
35343
  { label: "Publish the branch", command: "git push origin <branch>:<branch>" },
34520
35344
  { label: "Open the development-base PR", command: 'mmi-cli devops pr create --title "<title>" --body-file .jerv/PR_BODY.md --base development' },
34521
35345
  { label: "Wait for checks and land to development", command: "mmi-cli devops pr checks-wait <PR-number> && mmi-cli devops pr land <PR-number>" },
34522
- { label: "Release only after the gated train is authorized", command: "mmi-cli devops release --apply" }
35346
+ { label: "Release only after the gated train is authorized", command: "mmi-cli devops release --apply" },
35347
+ // #5552: learning-tagged filings are cloud-agent owned — file and return to the current task.
35348
+ { label: "Learning reports are fire-and-forget (file, then return to the current task \u2014 never claim/poll/duplicate the learning issue)", command: 'mmi-cli learning report --title "<one-line>" --body "<what hurt>"' }
34523
35349
  ]
34524
35350
  },
34525
35351
  "start-work": {
@@ -34527,6 +35353,7 @@ var LOOP_PLAYBOOKS = {
34527
35353
  steps: [
34528
35354
  { label: "Orient in the current repository", command: "mmi-cli onboard" },
34529
35355
  { label: "Read the board item", command: "mmi-cli oracle board show <issue-number>" },
35356
+ { label: "Claim the item (board mutation only \u2014 never `oracle issue claim`)", command: "mmi-cli oracle board claim <issue-number>" },
34530
35357
  { label: "Prepare the local workspace through the host surface" },
34531
35358
  { label: "Start a local stage (deployable repos)", command: "mmi-cli stage run --apply" }
34532
35359
  ]
@@ -35144,8 +35971,8 @@ function registerPrLifecycleCommands(program3) {
35144
35971
  }
35145
35972
 
35146
35973
  // src/post-merge-recon.ts
35147
- var import_node_fs41 = require("node:fs");
35148
- var import_node_path38 = require("node:path");
35974
+ var import_node_fs44 = require("node:fs");
35975
+ var import_node_path40 = require("node:path");
35149
35976
 
35150
35977
  // src/cross-repo-filing-issue.ts
35151
35978
  init_github_client();
@@ -35312,16 +36139,16 @@ function buildPostMergeReconRecovery(input) {
35312
36139
  }
35313
36140
  function writePostMergeReconRecovery(cwd, recovery) {
35314
36141
  const path2 = postMergeReconStatePath(cwd, recovery.repo, recovery.pr);
35315
- (0, import_node_fs41.mkdirSync)((0, import_node_path38.dirname)(path2), { recursive: true });
35316
- (0, import_node_fs41.writeFileSync)(path2, `${JSON.stringify(recovery, null, 2)}
36142
+ (0, import_node_fs44.mkdirSync)((0, import_node_path40.dirname)(path2), { recursive: true });
36143
+ (0, import_node_fs44.writeFileSync)(path2, `${JSON.stringify(recovery, null, 2)}
35317
36144
  `, "utf8");
35318
36145
  return path2;
35319
36146
  }
35320
36147
  function clearPostMergeReconRecovery(cwd, repo, pr2) {
35321
36148
  const path2 = postMergeReconStatePath(cwd, repo, pr2);
35322
- if (!(0, import_node_fs41.existsSync)(path2)) return;
36149
+ if (!(0, import_node_fs44.existsSync)(path2)) return;
35323
36150
  try {
35324
- (0, import_node_fs41.unlinkSync)(path2);
36151
+ (0, import_node_fs44.unlinkSync)(path2);
35325
36152
  } catch {
35326
36153
  }
35327
36154
  }
@@ -37140,7 +37967,8 @@ function diagnoseSurface(evidence) {
37140
37967
  const base = {
37141
37968
  descriptor: evidence.descriptor,
37142
37969
  ...evidence.installedVersion ? { installedVersion: evidence.installedVersion } : {},
37143
- ...evidence.releasedVersion ? { releasedVersion: evidence.releasedVersion } : {}
37970
+ ...evidence.releasedVersion ? { releasedVersion: evidence.releasedVersion } : {},
37971
+ ...evidence.receipt ? { receipt: evidence.receipt } : {}
37144
37972
  };
37145
37973
  if (!evidence.applicable) return { ...base, state: "skipped" };
37146
37974
  if (evidence.repair?.attempted && !evidence.repair.ok) {
@@ -37230,6 +38058,7 @@ function buildSurfaceDoctorCheck(diagnosis) {
37230
38058
  `install: ${descriptor.installMechanism} (${descriptor.installLocator})`,
37231
38059
  `repair owner: ${descriptor.repairOwner}`,
37232
38060
  `artifacts: ${descriptor.artifactIds.join(", ")}`,
38061
+ ...diagnosis.receipt ? [`receipt: ${diagnosis.receipt}`] : [],
37233
38062
  ...diagnosis.repairDetail ? [`heal: ${diagnosis.repairDetail}`] : []
37234
38063
  ]
37235
38064
  };
@@ -38445,18 +39274,18 @@ function parseOriginRepo(remoteUrl) {
38445
39274
  return `${match[1]}/${match[2]}`;
38446
39275
  }
38447
39276
  function ghHostsConfigPath(env, platform2) {
38448
- const sep3 = platform2 === "win32" ? "\\" : "/";
38449
- const join35 = (...parts) => parts.join(sep3);
39277
+ const sep4 = platform2 === "win32" ? "\\" : "/";
39278
+ const join36 = (...parts) => parts.join(sep4);
38450
39279
  const explicit = env.GH_CONFIG_DIR?.trim();
38451
- if (explicit) return join35(explicit, "hosts.yml");
39280
+ if (explicit) return join36(explicit, "hosts.yml");
38452
39281
  if (platform2 === "win32") {
38453
39282
  const appData = (env.AppData ?? env.APPDATA)?.trim();
38454
- return appData ? join35(appData, "GitHub CLI", "hosts.yml") : void 0;
39283
+ return appData ? join36(appData, "GitHub CLI", "hosts.yml") : void 0;
38455
39284
  }
38456
39285
  const xdg = env.XDG_CONFIG_HOME?.trim();
38457
- if (xdg) return join35(xdg, "gh", "hosts.yml");
39286
+ if (xdg) return join36(xdg, "gh", "hosts.yml");
38458
39287
  const home = env.HOME?.trim();
38459
- return home ? join35(home, ".config", "gh", "hosts.yml") : void 0;
39288
+ return home ? join36(home, ".config", "gh", "hosts.yml") : void 0;
38460
39289
  }
38461
39290
  function parseGhHostsAccounts(yaml, host = "github.com") {
38462
39291
  let hostIndent = null;
@@ -38506,19 +39335,41 @@ function ghAccountCaveat(announcedLogin, accounts) {
38506
39335
  }
38507
39336
 
38508
39337
  // src/doctor-io.ts
38509
- var import_node_fs42 = require("node:fs");
38510
- var import_node_os18 = require("node:os");
38511
- var import_node_path39 = require("node:path");
38512
- var import_node_child_process19 = require("node:child_process");
39338
+ var import_node_fs45 = require("node:fs");
39339
+ var import_node_os20 = require("node:os");
39340
+ var import_node_path41 = require("node:path");
39341
+ var import_node_child_process20 = require("node:child_process");
38513
39342
  var import_node_util8 = require("node:util");
38514
39343
  init_version_lag();
38515
39344
  init_plugin_guard_io();
38516
- var execFileP6 = (0, import_node_util8.promisify)(import_node_child_process19.execFile);
39345
+
39346
+ // src/discard-sink.ts
39347
+ function nodeDiscardSinkPath(platform2 = process.platform) {
39348
+ return platform2 === "win32" ? "\\\\.\\NUL" : "/dev/null";
39349
+ }
39350
+
39351
+ // src/doctor-io.ts
39352
+ var execFileP6 = (0, import_node_util8.promisify)(import_node_child_process20.execFile);
39353
+ function execFileCapture(file, args, opts = {}) {
39354
+ const sink = nodeDiscardSinkPath();
39355
+ const inFd = (0, import_node_fs45.openSync)(sink, "r");
39356
+ const errFd = (0, import_node_fs45.openSync)(sink, "w");
39357
+ try {
39358
+ return (0, import_node_child_process20.execFileSync)(file, args, {
39359
+ ...opts,
39360
+ encoding: "utf8",
39361
+ stdio: [inFd, "pipe", errFd]
39362
+ });
39363
+ } finally {
39364
+ (0, import_node_fs45.closeSync)(inFd);
39365
+ (0, import_node_fs45.closeSync)(errFd);
39366
+ }
39367
+ }
38517
39368
  var MMI_PLUGIN_ID2 = "mmi@mutmutco";
38518
39369
  function installedClaudePluginVersion() {
38519
39370
  try {
38520
39371
  const file = JSON.parse(
38521
- (0, import_node_fs42.readFileSync)((0, import_node_path39.join)((0, import_node_os18.homedir)(), ".claude", "plugins", "installed_plugins.json"), "utf8")
39372
+ (0, import_node_fs45.readFileSync)((0, import_node_path41.join)((0, import_node_os20.homedir)(), ".claude", "plugins", "installed_plugins.json"), "utf8")
38522
39373
  );
38523
39374
  const versions = (file.plugins?.[MMI_PLUGIN_ID2] ?? []).map((r) => r.version).filter((v) => Boolean(v));
38524
39375
  if (versions.length === 0) return void 0;
@@ -38529,7 +39380,7 @@ function installedClaudePluginVersion() {
38529
39380
  }
38530
39381
  function manifestVersion(path2) {
38531
39382
  try {
38532
- const manifest = JSON.parse((0, import_node_fs42.readFileSync)(path2, "utf8"));
39383
+ const manifest = JSON.parse((0, import_node_fs45.readFileSync)(path2, "utf8"));
38533
39384
  return typeof manifest.version === "string" && manifest.version.trim() ? manifest.version.trim() : void 0;
38534
39385
  } catch {
38535
39386
  return void 0;
@@ -38538,12 +39389,12 @@ function manifestVersion(path2) {
38538
39389
  function readHermesPluginEvidence(env = process.env) {
38539
39390
  const host = hermesConfigRoot(env);
38540
39391
  const root = hermesPluginRoot(env);
38541
- const installRecordPresent = (0, import_node_fs42.existsSync)(root);
38542
- const manifestPath = (0, import_node_path39.join)(root, "plugin.yaml");
39392
+ const installRecordPresent = (0, import_node_fs45.existsSync)(root);
39393
+ const manifestPath = (0, import_node_path41.join)(root, "plugin.yaml");
38543
39394
  let installedVersion;
38544
39395
  let manifest = "missing";
38545
39396
  try {
38546
- const text = (0, import_node_fs42.readFileSync)(manifestPath, "utf8");
39397
+ const text = (0, import_node_fs45.readFileSync)(manifestPath, "utf8");
38547
39398
  let version;
38548
39399
  try {
38549
39400
  const parsed = JSON.parse(text).version;
@@ -38557,20 +39408,20 @@ function readHermesPluginEvidence(env = process.env) {
38557
39408
  if (version) {
38558
39409
  installedVersion = version;
38559
39410
  manifest = "valid";
38560
- } else if ((0, import_node_fs42.existsSync)(manifestPath)) manifest = "invalid";
39411
+ } else if ((0, import_node_fs45.existsSync)(manifestPath)) manifest = "invalid";
38561
39412
  } catch {
38562
- if ((0, import_node_fs42.existsSync)(manifestPath)) manifest = "invalid";
39413
+ if ((0, import_node_fs45.existsSync)(manifestPath)) manifest = "invalid";
38563
39414
  }
38564
39415
  let skills = false;
38565
39416
  try {
38566
- skills = (0, import_node_fs42.existsSync)((0, import_node_path39.join)(root, "skills")) && (0, import_node_fs42.statSync)((0, import_node_path39.join)(root, "skills")).isDirectory();
39417
+ skills = (0, import_node_fs45.existsSync)((0, import_node_path41.join)(root, "skills")) && (0, import_node_fs45.statSync)((0, import_node_path41.join)(root, "skills")).isDirectory();
38567
39418
  } catch {
38568
39419
  }
38569
39420
  return {
38570
- hostPresent: (0, import_node_fs42.existsSync)(host),
39421
+ hostPresent: (0, import_node_fs45.existsSync)(host),
38571
39422
  installRecordPresent,
38572
39423
  manifest,
38573
- payloadPresent: (0, import_node_fs42.existsSync)((0, import_node_path39.join)(root, "__init__.py")) && skills && manifest === "valid",
39424
+ payloadPresent: (0, import_node_fs45.existsSync)((0, import_node_path41.join)(root, "__init__.py")) && skills && manifest === "valid",
38574
39425
  ...installedVersion ? { installedVersion } : {}
38575
39426
  };
38576
39427
  }
@@ -38578,7 +39429,7 @@ function installedSurfacePluginVersion(surface) {
38578
39429
  const token = surfaceToken(surface);
38579
39430
  if (token === "kilo") {
38580
39431
  try {
38581
- const stamp = (0, import_node_fs42.readFileSync)((0, import_node_path39.join)((0, import_node_os18.homedir)(), ".kilo", ".mmi-kilo-version"), "utf8").trim();
39432
+ const stamp = (0, import_node_fs45.readFileSync)((0, import_node_path41.join)((0, import_node_os20.homedir)(), ".kilo", ".mmi-kilo-version"), "utf8").trim();
38582
39433
  return stamp || void 0;
38583
39434
  } catch {
38584
39435
  return void 0;
@@ -38586,25 +39437,21 @@ function installedSurfacePluginVersion(surface) {
38586
39437
  }
38587
39438
  if (token === "hermes") return readHermesPluginEvidence().installedVersion;
38588
39439
  if (token === "cursor") {
38589
- return manifestVersion((0, import_node_path39.join)(cursorLocalPluginRoot(), ".cursor-plugin", "plugin.json"));
39440
+ return manifestVersion((0, import_node_path41.join)(cursorLocalPluginRoot(), ".cursor-plugin", "plugin.json"));
38590
39441
  }
38591
39442
  if (token === "jervcode") {
38592
39443
  return installedJervCodePackageVersion();
38593
39444
  }
38594
39445
  if (token === "kimi") {
38595
- return manifestVersion((0, import_node_path39.join)(surfaceConfigRoot(surface), "plugins", "managed", "mmi", ".kimi-plugin", "plugin.json"));
39446
+ return manifestVersion((0, import_node_path41.join)(surfaceConfigRoot(surface), "plugins", "managed", "mmi", ".kimi-plugin", "plugin.json"));
38596
39447
  }
38597
39448
  if (token === "claude") return installedClaudePluginVersion();
38598
39449
  if (token !== "codex") return void 0;
38599
39450
  try {
38600
- const raw = process.platform === "win32" ? (0, import_node_child_process19.execFileSync)("cmd.exe", ["/c", "codex", "plugin", "list", "--json"], {
38601
- encoding: "utf8",
38602
- stdio: ["ignore", "pipe", "ignore"],
39451
+ const raw = process.platform === "win32" ? execFileCapture("cmd.exe", ["/c", "codex", "plugin", "list", "--json"], {
38603
39452
  timeout: 15e3,
38604
39453
  windowsHide: true
38605
- }) : (0, import_node_child_process19.execFileSync)("codex", ["plugin", "list", "--json"], {
38606
- encoding: "utf8",
38607
- stdio: ["ignore", "pipe", "ignore"],
39454
+ }) : execFileCapture("codex", ["plugin", "list", "--json"], {
38608
39455
  timeout: 15e3,
38609
39456
  windowsHide: true
38610
39457
  });
@@ -38620,7 +39467,7 @@ function installedActivePluginVersion(surface = detectSurface(process.env)) {
38620
39467
  }
38621
39468
  function worktreeRootSync() {
38622
39469
  try {
38623
- const out = (0, import_node_child_process19.execFileSync)("git", ["rev-parse", "--show-toplevel"], { windowsHide: true, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
39470
+ const out = execFileCapture("git", ["rev-parse", "--show-toplevel"], { windowsHide: true });
38624
39471
  let root = out.endsWith("\n") ? out.slice(0, -1) : out;
38625
39472
  if (process.platform === "win32" && root.endsWith("\r")) root = root.slice(0, -1);
38626
39473
  return root || null;
@@ -38630,13 +39477,13 @@ function worktreeRootSync() {
38630
39477
  }
38631
39478
  var gitignorePath = () => {
38632
39479
  const root = worktreeRootSync();
38633
- return root === null ? null : (0, import_node_path39.join)(root, ".gitignore");
39480
+ return root === null ? null : (0, import_node_path41.join)(root, ".gitignore");
38634
39481
  };
38635
39482
  function readGitignore() {
38636
39483
  const path2 = gitignorePath();
38637
39484
  if (path2 === null) return null;
38638
39485
  try {
38639
- return (0, import_node_fs42.readFileSync)(path2, "utf8");
39486
+ return (0, import_node_fs45.readFileSync)(path2, "utf8");
38640
39487
  } catch {
38641
39488
  return null;
38642
39489
  }
@@ -38645,19 +39492,17 @@ function writeGitignore(content) {
38645
39492
  const path2 = gitignorePath();
38646
39493
  if (path2 === null) return false;
38647
39494
  try {
38648
- (0, import_node_fs42.writeFileSync)(path2, content, "utf8");
39495
+ (0, import_node_fs45.writeFileSync)(path2, content, "utf8");
38649
39496
  return true;
38650
39497
  } catch {
38651
39498
  return false;
38652
39499
  }
38653
39500
  }
38654
39501
  function lineEndingState(root) {
38655
- const attributesPresent = (0, import_node_fs42.existsSync)((0, import_node_path39.join)(root, ".gitattributes"));
39502
+ const attributesPresent = (0, import_node_fs45.existsSync)((0, import_node_path41.join)(root, ".gitattributes"));
38656
39503
  try {
38657
- const output = (0, import_node_child_process19.execFileSync)("git", ["-C", root, "ls-files", "--eol", "--", ":(glob)**/*.sh"], {
38658
- windowsHide: true,
38659
- encoding: "utf8",
38660
- stdio: ["ignore", "pipe", "ignore"]
39504
+ const output = execFileCapture("git", ["-C", root, "ls-files", "--eol", "--", ":(glob)**/*.sh"], {
39505
+ windowsHide: true
38661
39506
  });
38662
39507
  const crlfShellScripts = output.split(/\r?\n/).filter((line) => line.startsWith("i/crlf ")).map((line) => line.slice(line.indexOf(" ") + 1)).filter(Boolean);
38663
39508
  return { attributesPresent, crlfShellScripts };
@@ -38721,8 +39566,8 @@ ${r.stderr ?? ""}`).catch(() => "");
38721
39566
  function ghMultiAccountCaveat(announcedLogin) {
38722
39567
  try {
38723
39568
  const hostsPath = ghHostsConfigPath(process.env, process.platform);
38724
- if (!hostsPath || !(0, import_node_fs43.existsSync)(hostsPath)) return void 0;
38725
- return ghAccountCaveat(announcedLogin, parseGhHostsAccounts((0, import_node_fs43.readFileSync)(hostsPath, "utf8")));
39569
+ if (!hostsPath || !(0, import_node_fs46.existsSync)(hostsPath)) return void 0;
39570
+ return ghAccountCaveat(announcedLogin, parseGhHostsAccounts((0, import_node_fs46.readFileSync)(hostsPath, "utf8")));
38726
39571
  } catch {
38727
39572
  return void 0;
38728
39573
  }
@@ -38730,12 +39575,12 @@ function ghMultiAccountCaveat(announcedLogin) {
38730
39575
  var ENV_HEAL_LOCK_STALE_MS = 10 * 6e4;
38731
39576
  var ENV_HEAL_LOCK_MAX_WAIT_MS = 2 * 6e4;
38732
39577
  function envHealLockPath(home) {
38733
- return (0, import_node_path40.join)(home, ".claude", "plugins", ".mmi-env-heal.lock");
39578
+ return (0, import_node_path42.join)(home, ".claude", "plugins", ".mmi-env-heal.lock");
38734
39579
  }
38735
39580
  async function withEnvHealLock(what, run) {
38736
39581
  try {
38737
39582
  return await withFileLock(
38738
- envHealLockPath((0, import_node_os19.homedir)()),
39583
+ envHealLockPath((0, import_node_os21.homedir)()),
38739
39584
  { staleMs: ENV_HEAL_LOCK_STALE_MS, maxWaitMs: ENV_HEAL_LOCK_MAX_WAIT_MS, label: "mmi env-heal lock" },
38740
39585
  run
38741
39586
  );
@@ -38777,17 +39622,19 @@ function mmiDoctorDeps(opts = {}) {
38777
39622
  const descriptor = doctorSurface(token);
38778
39623
  const snapshot = snapshotPluginGuardInput(runtimeSurface, isOrgRepo);
38779
39624
  const hermes = token === "hermes" ? readHermesPluginEvidence(process.env) : void 0;
39625
+ const kimi = token === "kimi" ? kimiPluginHostEvidence(surfaceConfigRoot("kimi")) : void 0;
38780
39626
  const installedVersion = hermes?.installedVersion ?? installedSurfacePluginVersion(runtimeSurface);
38781
39627
  surfaceEvidence = {
38782
39628
  descriptor,
38783
39629
  // Hermes' root is the host evidence: a configured but uninstalled MMI tree is missing, while an
38784
39630
  // absent root is skipped. Other mature surfaces retain their established org/install applicability.
38785
39631
  applicable: hermes ? hermes.hostPresent : isOrgRepo || snapshot.installRecordPresent || snapshot.pluginCachePresent,
38786
- installRecordPresent: hermes?.installRecordPresent ?? snapshot.installRecordPresent,
39632
+ installRecordPresent: hermes?.installRecordPresent ?? (kimi ? kimi.registration === "healthy" : snapshot.installRecordPresent),
38787
39633
  deliveryPresent: hermes ? hermes.installRecordPresent : snapshot.marketplaceClonePresent,
38788
- payloadPresent: hermes?.payloadPresent ?? snapshot.pluginCachePresent,
39634
+ payloadPresent: hermes?.payloadPresent ?? (kimi ? kimi.healthy : snapshot.pluginCachePresent),
38789
39635
  manifest: hermes?.manifest ?? (installedVersion ? "valid" : snapshot.installRecordPresent ? "invalid" : "missing"),
38790
39636
  guardState: buildPluginGuardDecision(snapshot).state,
39637
+ ...kimi?.receipt ? { receipt: kimi.receipt } : {},
38791
39638
  ...installedVersion ? { installedVersion } : {}
38792
39639
  };
38793
39640
  return surfaceEvidence;
@@ -38830,7 +39677,7 @@ function mmiDoctorDeps(opts = {}) {
38830
39677
  const configRoot = surfaceConfigRoot(surface);
38831
39678
  const running = surface === "codex" ? installedActivePluginVersion(surface) : runningPluginVersion(process.env, resolveClientVersion());
38832
39679
  const plan = buildPluginCachePlan(
38833
- (0, import_node_os19.homedir)(),
39680
+ (0, import_node_os21.homedir)(),
38834
39681
  running,
38835
39682
  pluginCacheFsDeps(configRoot, () => 0),
38836
39683
  { configRoot, includeStaging: surface !== "codex" }
@@ -38858,14 +39705,14 @@ function mmiDoctorDeps(opts = {}) {
38858
39705
  const running = surface === "codex" ? installedActivePluginVersion(surface) : runningPluginVersion(process.env, resolveClientVersion());
38859
39706
  const installed = installedActivePluginVersion(surface);
38860
39707
  const plan = buildPluginCachePlan(
38861
- (0, import_node_os19.homedir)(),
39708
+ (0, import_node_os21.homedir)(),
38862
39709
  running,
38863
39710
  pluginCacheFsDeps(configRoot, () => 0),
38864
39711
  { configRoot, includeStaging: surface !== "codex", installedVersion: installed }
38865
39712
  );
38866
39713
  const result = applyPluginCachePlan(
38867
39714
  plan,
38868
- (p) => (0, import_node_fs43.rmSync)(p, { recursive: true }),
39715
+ (p) => (0, import_node_fs46.rmSync)(p, { recursive: true }),
38869
39716
  stagingApplyFsGuard(configRoot)
38870
39717
  );
38871
39718
  return {
@@ -38903,7 +39750,7 @@ function mmiDoctorDeps(opts = {}) {
38903
39750
  // adopted the generated routing index (MMG-Unlive: `docs/Archive/**` only, no index, no gate.yml) would
38904
39751
  // get a permanent — demanding an artifact it never asked for.
38905
39752
  docsIndexState: (root) => {
38906
- if (!(0, import_node_fs43.existsSync)((0, import_node_path40.join)(root, DOCS_INDEX_PATH))) return void 0;
39753
+ if (!(0, import_node_fs46.existsSync)((0, import_node_path42.join)(root, DOCS_INDEX_PATH))) return void 0;
38907
39754
  const real = createDocsIndexDeps(root);
38908
39755
  let docs2;
38909
39756
  const listDocs = () => docs2 ??= real.listDocs();
@@ -38912,7 +39759,7 @@ function mmiDoctorDeps(opts = {}) {
38912
39759
  },
38913
39760
  // #4168: working-tree heal — write if drifted, then re-check. Commit remains the operator's step.
38914
39761
  healDocsIndex: (root) => {
38915
- if (!(0, import_node_fs43.existsSync)((0, import_node_path40.join)(root, DOCS_INDEX_PATH))) return { drift: false, docCount: 0 };
39762
+ if (!(0, import_node_fs46.existsSync)((0, import_node_path42.join)(root, DOCS_INDEX_PATH))) return { drift: false, docCount: 0 };
38916
39763
  const real = createDocsIndexDeps(root);
38917
39764
  let docs2;
38918
39765
  const listDocs = () => docs2 ??= real.listDocs();
@@ -38933,8 +39780,8 @@ function mmiDoctorDeps(opts = {}) {
38933
39780
  });
38934
39781
  const raced = await Promise.race([
38935
39782
  work.then((r) => ({ ...r, timedOut: false })),
38936
- new Promise((resolve5) => {
38937
- ceiling = setTimeout(() => resolve5({ timedOut: true, scanned: 0, findings: 0, fixed: 0, failed: 0 }), BOARD_DOCTOR_TIMEOUT_MS);
39783
+ new Promise((resolve6) => {
39784
+ ceiling = setTimeout(() => resolve6({ timedOut: true, scanned: 0, findings: 0, fixed: 0, failed: 0 }), BOARD_DOCTOR_TIMEOUT_MS);
38938
39785
  })
38939
39786
  ]);
38940
39787
  if (raced.timedOut) return { scanned: 0, findings: 0, fixed: 0, failed: 0, timedOut: true };
@@ -38967,8 +39814,8 @@ function mmiDoctorDeps(opts = {}) {
38967
39814
  incomplete: nb.incomplete,
38968
39815
  timedOut: false
38969
39816
  })),
38970
- new Promise((resolve5) => {
38971
- ceiling = setTimeout(() => resolve5({ driftLines: [], incomplete: [], timedOut: true }), SCHEDULES_DRIFT_TIMEOUT_MS);
39817
+ new Promise((resolve6) => {
39818
+ ceiling = setTimeout(() => resolve6({ driftLines: [], incomplete: [], timedOut: true }), SCHEDULES_DRIFT_TIMEOUT_MS);
38972
39819
  })
38973
39820
  ]);
38974
39821
  if (!raced.timedOut && raced.incomplete.length === 0) writeSchedulesDriftCache(cachePath, raced.driftLines);
@@ -39004,7 +39851,7 @@ function mmiDoctorDeps(opts = {}) {
39004
39851
  repoIndexCloudState: async (root) => {
39005
39852
  let localV4 = { state: "absent" };
39006
39853
  try {
39007
- const parsed = JSON.parse((0, import_node_fs43.readFileSync)(repoIndexV4StorePath(root), "utf8"));
39854
+ const parsed = JSON.parse((0, import_node_fs46.readFileSync)(repoIndexV4StorePath(root), "utf8"));
39008
39855
  const state = parsed.status?.state;
39009
39856
  if (parsed.schemaVersion === 4 && (state === "ready" || state === "degraded")) {
39010
39857
  const chunks = parsed.manifest?.chunks?.length ?? 0;
@@ -39297,19 +40144,19 @@ program2.hook("preAction", async (_thisCommand, actionCommand) => {
39297
40144
  });
39298
40145
  var rules = program2.command("rules").description("org-managed .gitignore delivery");
39299
40146
  rules.command("gitignore").option("--write", "upsert the managed block into .gitignore (default: check only, non-zero exit on drift)").option("--json", "machine-readable output").description("verify (or --write) this repo's org-managed .gitignore block matches the SSOT").action((opts) => {
39300
- const path2 = (0, import_node_path40.join)(process.cwd(), ".gitignore");
39301
- const current = (0, import_node_fs43.existsSync)(path2) ? (0, import_node_fs43.readFileSync)(path2, "utf8") : null;
40147
+ const path2 = (0, import_node_path42.join)(process.cwd(), ".gitignore");
40148
+ const current = (0, import_node_fs46.existsSync)(path2) ? (0, import_node_fs46.readFileSync)(path2, "utf8") : null;
39302
40149
  const plan = planManagedGitignore(current);
39303
40150
  const drift = [...plan.added.map((l) => `+${l}`), ...plan.removed.map((l) => `-${l}`)].join(", ") || "block normalize";
39304
40151
  if (opts.json) {
39305
- if (opts.write && plan.changed) (0, import_node_fs43.writeFileSync)(path2, plan.content, "utf8");
40152
+ if (opts.write && plan.changed) (0, import_node_fs46.writeFileSync)(path2, plan.content, "utf8");
39306
40153
  console.log(JSON.stringify(plan, null, 2));
39307
40154
  if (!opts.write && plan.changed) process.exitCode = 1;
39308
40155
  return;
39309
40156
  }
39310
40157
  if (opts.write) {
39311
40158
  if (plan.changed) {
39312
- (0, import_node_fs43.writeFileSync)(path2, plan.content, "utf8");
40159
+ (0, import_node_fs46.writeFileSync)(path2, plan.content, "utf8");
39313
40160
  console.log(`mmi-cli devops org rules gitignore: updated .gitignore (${drift})`);
39314
40161
  } else {
39315
40162
  console.log("mmi-cli devops org rules gitignore: up to date");
@@ -39495,7 +40342,7 @@ function scheduleRelatedDiscovery(o) {
39495
40342
  try {
39496
40343
  const args = ["issue", "discover-related", "--number", String(o.number), "--title", o.title, "--body", o.body, "--fail-soft"];
39497
40344
  if (o.repo) args.push("--repo", o.repo);
39498
- spawnDetachedSelf(args, { spawn: import_node_child_process20.spawn, execPath: process.execPath, scriptPath: process.argv[1] }, { cwd: process.cwd() });
40345
+ spawnDetachedSelf(args, { spawn: import_node_child_process21.spawn, execPath: process.execPath, scriptPath: process.argv[1] }, { cwd: process.cwd() });
39499
40346
  } catch {
39500
40347
  }
39501
40348
  }
@@ -39895,6 +40742,20 @@ tests.command("policy").description("enforce this repo's test-policy.json agains
39895
40742
  await failGraceful(e.message);
39896
40743
  }
39897
40744
  });
40745
+ var distCmd = program2.command("dist").description("this repo's committed dist/BOM drift receipt \u2014 whether cli/dist, updater/dist and distribution-bom.json still match a fresh build of source");
40746
+ distCmd.command("status").description("rebuild every committed dist artifact to a temp dir and report committed vs rebuilt-expected sha256 plus the BOM's recorded dist identities \u2014 a visible, non-blocking receipt. Development checkouts may lag source until the release fold; drift NEVER fails the run, and nothing is refreshed for you (#5576)").option("--json", "machine-readable receipt: { ok, staleCount, artifacts[], bom, summary } (full hashes; drift still exits 0)").action(async (o) => {
40747
+ try {
40748
+ const root = await repoRoot();
40749
+ const receipt = runDistStatus(root);
40750
+ if (o.json) {
40751
+ consoleIo.log(JSON.stringify({ ok: true, staleCount: receipt.staleCount, artifacts: receipt.artifacts, bom: receipt.bom, summary: receipt.summary }, null, 2));
40752
+ return;
40753
+ }
40754
+ for (const line of renderDistDriftReceipt(receipt)) console.log(line);
40755
+ } catch (e) {
40756
+ await failGraceful(`dist status: ${e.message}`);
40757
+ }
40758
+ });
39898
40759
  async function reportWrite(label, res) {
39899
40760
  if (res.ok) {
39900
40761
  console.log(JSON.stringify(res.body));
@@ -40167,7 +41028,7 @@ project.command("set [owner/repo]").description("upsert project META (idempotent
40167
41028
  if (dupe) return fail(`org project set: KEY "${dupe}" was passed to both --var and --set; --set is an alias of --var, so pass each KEY once`);
40168
41029
  if (o.secretsFile) {
40169
41030
  try {
40170
- vars.push(`secrets=${(0, import_node_fs43.readFileSync)(o.secretsFile, "utf8")}`);
41031
+ vars.push(`secrets=${(0, import_node_fs46.readFileSync)(o.secretsFile, "utf8")}`);
40171
41032
  } catch (e) {
40172
41033
  return fail(`org project set: cannot read --secrets-file ${o.secretsFile}: ${e.message}`);
40173
41034
  }
@@ -40517,7 +41378,7 @@ function resolveCreateSurface(opts) {
40517
41378
  function surfaceWaived() {
40518
41379
  return rawFlag("--no-surface");
40519
41380
  }
40520
- var issue = program2.command("issue").description("issues \u2014 create and view with structured JSON (view; show is an alias for board-verb callers)");
41381
+ var issue = program2.command("issue").description("issues \u2014 create and view with structured JSON (view; show is an alias for board-verb callers). Claims are board mutations: use `oracle board claim`, never `oracle issue claim`");
40521
41382
  withExamples(mutating(
40522
41383
  issue.command("create").description("create an issue (type \u2014 label) and print {number,url,label} JSON").addOption(new Option("--type <type>", "bug | feature | task (sets the matching label; required unless --batch)").choices([...ISSUE_TYPES])).option("--title <title>", "issue title").option("--title-file <path|->", "read the issue title from a UTF-8 file, or from stdin with -").option("--body <body>", "issue body (markdown)").option("--body-file <path|->", "read issue body from a UTF-8 file. `-` (stdin) needs a heredoc, which the agent inline-body guard denies (#1473/#2125) \u2014 prefer a real path; a title with backticks needs --title-file for the same reason (#3381)").option("--priority <priority>", "urgent | high | medium | low (defaults to medium; sets the board Priority field only \u2014 never a priority:* label, #416)").option("--repo <owner/repo>", "target repo (defaults to the current repo)").option("--label <label...>", "extra label(s) to attach (repeatable; auto-created if missing)").option("--surface <surface>", "issue surface, with or without the surface: prefix (#3789). Required when the target repo runs the one-surface-label board rule; any value satisfies it, so this is not a closed enum").option("--no-surface", "file without a surface label on a repo that requires one \u2014 for a genuinely exempt filing (e.g. a coop proof issue that spans every surface)").option("--parent <ref>", "file as a native sub-issue of this parent (#123, owner/repo#123, or URL)").option("--no-related", "skip the auto related-issues comment"),
40523
41384
  // --dry-run/--validate-only plan: resolve the same title source and validate the same type, priority,
@@ -40821,7 +41682,7 @@ ${list}`);
40821
41682
  }
40822
41683
  console.log(JSON.stringify({ number: parsed.number, repo, item: result.item.text, checked, changed: true }));
40823
41684
  });
40824
- program2.command("report").description("file a friction report on the Hub board (Hub session auth, dedups open reports) and print {number,url} JSON").option("--title <title>", "one-line friction summary").option("--title-file <path|->", "read the friction summary from a UTF-8 file, or from stdin with -").option("--body <body>", "report body (markdown)").option("--body-file <path|->", "read report body from a UTF-8 file, or from stdin with -").addOption(new Option("--type <type>", "bug | feature | task (sets the matching label)").default("task").choices([...ISSUE_TYPES])).option("--priority <priority>", "urgent | high | medium | low (defaults to medium; sets the board Priority field only, #416)").option("--repo <owner/repo>", 'attribute the report to a different source repo than the current checkout for the "Filed via..." footer (rare \u2014 usually auto-detected; every report always lands on the org Hub, never an alternate target, #263)').option("--force", "file a new issue even when an open report looks like a duplicate").option("--json", "machine-readable output (already the default \u2014 report always prints JSON; #682)").action(async (o) => {
41685
+ program2.command("report").description("file a friction report on the Hub board (Hub session auth, dedups open reports) and print {number,url} JSON \u2014 learning-tagged; file and forget (cloud agents own the fix; do not claim/poll it)").option("--title <title>", "one-line friction summary").option("--title-file <path|->", "read the friction summary from a UTF-8 file, or from stdin with -").option("--body <body>", "report body (markdown)").option("--body-file <path|->", "read report body from a UTF-8 file, or from stdin with -").addOption(new Option("--type <type>", "bug | feature | task (sets the matching label)").default("task").choices([...ISSUE_TYPES])).option("--priority <priority>", "urgent | high | medium | low (defaults to medium; sets the board Priority field only, #416)").option("--repo <owner/repo>", 'attribute the report to a different source repo than the current checkout for the "Filed via..." footer (rare \u2014 usually auto-detected; every report always lands on the org Hub, never an alternate target, #263)').option("--force", "file a new issue even when an open report looks like a duplicate").option("--json", "machine-readable output (already the default \u2014 report always prints JSON; #682)").action(async (o) => {
40825
41686
  let body;
40826
41687
  let priority;
40827
41688
  let title;
@@ -40937,6 +41798,7 @@ program2.command("skill-lesson").description("file a skill-lesson on the Hub boa
40937
41798
  }
40938
41799
  const created = requireGhCreateOk(await ghCreate(args), "skill-lesson");
40939
41800
  const { projectItemId, onBoard } = await attachToProject(created.number, targetRepo3, priority);
41801
+ console.log(JSON.stringify({ ...created, projectItemId, onBoard }));
40940
41802
  });
40941
41803
  var pr = program2.command("pr").description("pull requests \u2014 reliable create with structured output");
40942
41804
  withExamples(pr.command("create").description("create a PR and print {number,url} JSON").option("--title <title>", "PR title").option("--title-file <path|->", "read the PR title from a UTF-8 file, or from stdin with -").option("--body <body>", "PR body (markdown)").option("--body-file <path|->", "read PR body from a UTF-8 file, or from stdin with -").option("--base <branch>", "base branch (defaults to the repo default)").option("--head <branch>", "head branch (defaults to the current branch)").option("--repo <owner/repo>", "target repo (defaults to the current repo)").option("--draft", "open the PR in draft state (#2667)").option("--json", "machine-readable output (default; accepted for parity)").action(async (o) => {
@@ -40991,11 +41853,11 @@ pr.command("view <number>").description("read a PR as structured JSON (merged st
40991
41853
  }
40992
41854
  });
40993
41855
  async function listCiWorkflowPaths(cwd = process.cwd()) {
40994
- const wfDir = (0, import_node_path40.join)(cwd, ".github", "workflows");
40995
- if (!(0, import_node_fs43.existsSync)(wfDir)) return [];
40996
- return (0, import_node_fs43.readdirSync)(wfDir).filter((name) => /\.(ya?ml)$/i.test(name)).filter((name) => {
41856
+ const wfDir = (0, import_node_path42.join)(cwd, ".github", "workflows");
41857
+ if (!(0, import_node_fs46.existsSync)(wfDir)) return [];
41858
+ return (0, import_node_fs46.readdirSync)(wfDir).filter((name) => /\.(ya?ml)$/i.test(name)).filter((name) => {
40997
41859
  try {
40998
- return workflowReportsPrChecks((0, import_node_fs43.readFileSync)((0, import_node_path40.join)(wfDir, name), "utf8"));
41860
+ return workflowReportsPrChecks((0, import_node_fs46.readFileSync)((0, import_node_path42.join)(wfDir, name), "utf8"));
40999
41861
  } catch {
41000
41862
  return true;
41001
41863
  }
@@ -41047,16 +41909,16 @@ function ciAuditDeps() {
41047
41909
  // gate re-seed step is skipped gracefully rather than failing mid-run.
41048
41910
  readSeedFile: (path2) => {
41049
41911
  if (!root) return null;
41050
- const fullPath = (0, import_node_path40.join)(root, path2);
41051
- return (0, import_node_fs43.existsSync)(fullPath) ? (0, import_node_fs43.readFileSync)(fullPath, "utf8") : null;
41912
+ const fullPath = (0, import_node_path42.join)(root, path2);
41913
+ return (0, import_node_fs46.existsSync)(fullPath) ? (0, import_node_fs46.readFileSync)(fullPath, "utf8") : null;
41052
41914
  }
41053
41915
  };
41054
41916
  }
41055
41917
  function hubRoot() {
41056
- const fromPkg = (0, import_node_path40.join)(__dirname, "..", "..");
41918
+ const fromPkg = (0, import_node_path42.join)(__dirname, "..", "..");
41057
41919
  const marker = "skills/bootstrap/seeds/manifest.json";
41058
- if ((0, import_node_fs43.existsSync)((0, import_node_path40.join)(fromPkg, marker))) return fromPkg;
41059
- if ((0, import_node_fs43.existsSync)((0, import_node_path40.join)(process.cwd(), marker))) return process.cwd();
41920
+ if ((0, import_node_fs46.existsSync)((0, import_node_path42.join)(fromPkg, marker))) return fromPkg;
41921
+ if ((0, import_node_fs46.existsSync)((0, import_node_path42.join)(process.cwd(), marker))) return process.cwd();
41060
41922
  return null;
41061
41923
  }
41062
41924
  async function waitLoopCorePool(label) {
@@ -41105,7 +41967,12 @@ pr.command("checks-wait <number>").description(`bounded wait for ALL checks on t
41105
41967
  timeoutMs = Math.round(minutes * 6e4);
41106
41968
  }
41107
41969
  const repo = await requireRepo(o.repo);
41108
- const snapshotRead = await readRestPrSnapshotWithRetry(number, repo);
41970
+ const budgetMs = timeoutMs ?? PR_CHECKS_TIMEOUT_MS;
41971
+ const waitStarted = Date.now();
41972
+ const snapshotRead = await readRestPrSnapshotWithRetry(number, repo, void 0, {
41973
+ retryTransientUntilMs: waitStarted + budgetMs,
41974
+ onTransientRetry: (error, attempt) => console.warn(`pr checks-wait: transient GitHub read (${error}) \u2014 retrying PR snapshot (attempt ${attempt}) within the wait budget`)
41975
+ });
41109
41976
  if (snapshotRead.state === "failed") {
41110
41977
  return fail(`pr checks-wait: cannot resolve PR #${number}'s base branch \u2014 ${snapshotRead.error}. Refusing to wait against an assumed base; retry when the API answers.`);
41111
41978
  }
@@ -41126,9 +41993,9 @@ pr.command("checks-wait <number>").description(`bounded wait for ALL checks on t
41126
41993
  // #5400: after grace, name "GitHub delivered zero runs" instead of burning the full budget as pending.
41127
41994
  pollHeadWorkflowRunCount: () => waitLoopHeadWorkflowRunCount("pr checks-wait", number, repo),
41128
41995
  baseBranch,
41129
- sleep: (ms) => new Promise((resolve5) => setTimeout(resolve5, ms)),
41996
+ sleep: (ms) => new Promise((resolve6) => setTimeout(resolve6, ms)),
41130
41997
  log: (message) => console.warn(message),
41131
- timeoutMs,
41998
+ timeoutMs: Math.max(1, budgetMs - (Date.now() - waitStarted)),
41132
41999
  // Liveness on stderr, one line per poll. A silent bounded wait is indistinguishable from a hang, and
41133
42000
  // an agent harness kills it on its own (shorter) deadline before the verdict ever prints (#2940).
41134
42001
  progress: ({ state, elapsedMs, remainingMs }) => console.warn(`pr checks-wait: ${state} \u2014 ${Math.round(elapsedMs / 1e3)}s elapsed, ${Math.round(remainingMs / 6e4)}m left`)
@@ -41222,7 +42089,7 @@ pr.command("land <number>").description("agent merge path (#1440): train probe \
41222
42089
  // #5400: same zero-runs delivery probe as checks-wait — do not burn the land budget on silence.
41223
42090
  pollHeadWorkflowRunCount: () => waitLoopHeadWorkflowRunCount("pr land", prNumber, repo),
41224
42091
  baseBranch: "development",
41225
- sleep: (ms) => new Promise((resolve5) => setTimeout(resolve5, ms)),
42092
+ sleep: (ms) => new Promise((resolve6) => setTimeout(resolve6, ms)),
41226
42093
  log: (message) => console.warn(message),
41227
42094
  // `pr land` inherits the same (raised) checks budget, so it needs the same liveness — otherwise the
41228
42095
  // 30m wait is SILENT and reads exactly like the hang #2940 was filed about, only three times longer.
@@ -41265,7 +42132,7 @@ pr.command("land <number>").description("agent merge path (#1440): train probe \
41265
42132
  } else {
41266
42133
  lastFailure = void 0;
41267
42134
  }
41268
- await new Promise((resolve5) => setTimeout(resolve5, PR_LAND_POLL_MS));
42135
+ await new Promise((resolve6) => setTimeout(resolve6, PR_LAND_POLL_MS));
41269
42136
  }
41270
42137
  if (lastFailure) {
41271
42138
  throw new Error(
@@ -41349,7 +42216,12 @@ jsonParity(pr.command("merge <number>").description("merge a PR (squash by defau
41349
42216
  const ciPolicy = await resolveMergeCiPolicyForCheckout(o.repo, ciHeadRef);
41350
42217
  if (o.wait) {
41351
42218
  const repo = await requireRepo(o.repo);
41352
- const snapshotRead = await readRestPrSnapshotWithRetry(number, repo);
42219
+ const budgetMs = PR_CHECKS_TIMEOUT_MS;
42220
+ const waitStarted = Date.now();
42221
+ const snapshotRead = await readRestPrSnapshotWithRetry(number, repo, void 0, {
42222
+ retryTransientUntilMs: waitStarted + budgetMs,
42223
+ onTransientRetry: (error, attempt) => console.warn(`pr merge: transient GitHub read (${error}) \u2014 retrying PR snapshot (attempt ${attempt}) within the --wait budget`)
42224
+ });
41353
42225
  if (snapshotRead.state === "failed") {
41354
42226
  console.error(`pr merge: cannot resolve PR #${number}'s base branch \u2014 ${snapshotRead.error}. Refusing to wait against an assumed base; retry when the API answers.`);
41355
42227
  process.exitCode = 1;
@@ -41365,8 +42237,9 @@ jsonParity(pr.command("merge <number>").description("merge a PR (squash by defau
41365
42237
  diagnoseFailure: () => waitLoopDiagnosis("pr merge --wait", number, repo),
41366
42238
  pollHeadWorkflowRunCount: () => waitLoopHeadWorkflowRunCount("pr merge --wait", number, repo),
41367
42239
  baseBranch,
41368
- sleep: (ms) => new Promise((resolve5) => setTimeout(resolve5, ms)),
42240
+ sleep: (ms) => new Promise((resolve6) => setTimeout(resolve6, ms)),
41369
42241
  log: (message) => console.warn(message),
42242
+ timeoutMs: Math.max(1, budgetMs - (Date.now() - waitStarted)),
41370
42243
  progress: ({ state, elapsedMs, remainingMs }) => console.warn(`pr merge: --wait checks \u2014 ${state}, ${Math.round(elapsedMs / 1e3)}s elapsed, ${Math.round(remainingMs / 6e4)}m left`)
41371
42244
  });
41372
42245
  if (wait.status !== "success" && wait.status !== "skipped") {
@@ -41400,7 +42273,7 @@ jsonParity(pr.command("merge <number>").description("merge a PR (squash by defau
41400
42273
  }
41401
42274
  if (!repoForPostCleanup) throw e;
41402
42275
  console.warn(`pr merge: gh GraphQL rate-limited \u2014 merging PR #${number} via REST PUT instead (#4588).`);
41403
- const commitMessage = bodyFile ? (0, import_node_fs43.readFileSync)(bodyFile, "utf8") : void 0;
42276
+ const commitMessage = bodyFile ? (0, import_node_fs46.readFileSync)(bodyFile, "utf8") : void 0;
41404
42277
  await defaultGitHubClient().rest("PUT", `repos/${repoForPostCleanup}/pulls/${number}/merge`, {
41405
42278
  body: { merge_method: method.slice(2), ...commitMessage ? { commit_message: commitMessage } : {} },
41406
42279
  timeoutMs: GH_MUTATION_TIMEOUT_MS
@@ -42096,12 +42969,12 @@ access.command("audit").description("audit collaborator roles + train-branch pus
42096
42969
  targets = resolution.targets;
42097
42970
  }
42098
42971
  const derivedMatrix = registryProjects ? accessMatrixFromProjects(registryProjects) : {};
42099
- const fileMatrix = (0, import_node_fs43.existsSync)("access-matrix.json") ? loadAccessMatrix((0, import_node_fs43.readFileSync)("access-matrix.json", "utf8")) : {};
42972
+ const fileMatrix = (0, import_node_fs46.existsSync)("access-matrix.json") ? loadAccessMatrix((0, import_node_fs46.readFileSync)("access-matrix.json", "utf8")) : {};
42100
42973
  const matrix = mergeAccessMatrix(fileMatrix, derivedMatrix);
42101
42974
  const derivedContracts = registryProjects ? dataAccessContractsFromProjects(registryProjects) : { consumers: {} };
42102
- const fileContracts = (0, import_node_fs43.existsSync)("data-access-contracts.json") ? loadDataAccessContracts((0, import_node_fs43.readFileSync)("data-access-contracts.json", "utf8")) : { consumers: {} };
42975
+ const fileContracts = (0, import_node_fs46.existsSync)("data-access-contracts.json") ? loadDataAccessContracts((0, import_node_fs46.readFileSync)("data-access-contracts.json", "utf8")) : { consumers: {} };
42103
42976
  const dataAccess = mergeDataAccessContracts(fileContracts, derivedContracts);
42104
- const sanctioned = (0, import_node_fs43.existsSync)("access-matrix.json") ? loadSanctionedAdmins((0, import_node_fs43.readFileSync)("access-matrix.json", "utf8")) : {};
42977
+ const sanctioned = (0, import_node_fs46.existsSync)("access-matrix.json") ? loadSanctionedAdmins((0, import_node_fs46.readFileSync)("access-matrix.json", "utf8")) : {};
42105
42978
  const report = await auditOrgAccess(targets, deps, matrix, dataAccess, sanctioned);
42106
42979
  console.log(o.json ? JSON.stringify(report, null, 2) : renderAccessReport(report));
42107
42980
  if (!report.ok) process.exitCode = 1;
@@ -42133,16 +43006,16 @@ function directoryBytes(path2) {
42133
43006
  let total = 0;
42134
43007
  let entries;
42135
43008
  try {
42136
- entries = (0, import_node_fs43.readdirSync)(path2, { withFileTypes: true });
43009
+ entries = (0, import_node_fs46.readdirSync)(path2, { withFileTypes: true });
42137
43010
  } catch {
42138
43011
  return 0;
42139
43012
  }
42140
43013
  for (const entry of entries) {
42141
- const child2 = (0, import_node_path40.join)(path2, entry.name);
43014
+ const child2 = (0, import_node_path42.join)(path2, entry.name);
42142
43015
  if (entry.isDirectory()) total += directoryBytes(child2);
42143
43016
  else {
42144
43017
  try {
42145
- total += (0, import_node_fs43.statSync)(child2).size;
43018
+ total += (0, import_node_fs46.statSync)(child2).size;
42146
43019
  } catch {
42147
43020
  }
42148
43021
  }
@@ -42150,25 +43023,25 @@ function directoryBytes(path2) {
42150
43023
  return total;
42151
43024
  }
42152
43025
  function listDirEntries(dir) {
42153
- return (0, import_node_fs43.readdirSync)(dir, { withFileTypes: true }).map((d) => ({ name: d.name, isDirectory: d.isDirectory() }));
43026
+ return (0, import_node_fs46.readdirSync)(dir, { withFileTypes: true }).map((d) => ({ name: d.name, isDirectory: d.isDirectory() }));
42154
43027
  }
42155
43028
  function readInstalledPluginRefs(configRoot) {
42156
43029
  const p = installedPluginsPathForConfig(configRoot);
42157
- if (!(0, import_node_fs43.existsSync)(p)) return [];
43030
+ if (!(0, import_node_fs46.existsSync)(p)) return [];
42158
43031
  try {
42159
- return installedPluginPaths((0, import_node_fs43.readFileSync)(p, "utf8"));
43032
+ return installedPluginPaths((0, import_node_fs46.readFileSync)(p, "utf8"));
42160
43033
  } catch {
42161
43034
  return null;
42162
43035
  }
42163
43036
  }
42164
43037
  function pluginCacheFsDeps(configRoot, dirBytes) {
42165
43038
  return {
42166
- exists: (p) => (0, import_node_fs43.existsSync)(p),
42167
- listVersionDirs: (root) => (0, import_node_fs43.readdirSync)(root, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name),
43039
+ exists: (p) => (0, import_node_fs46.existsSync)(p),
43040
+ listVersionDirs: (root) => (0, import_node_fs46.readdirSync)(root, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name),
42168
43041
  dirBytes,
42169
- listStagingDirs: (root) => (0, import_node_fs43.readdirSync)(root, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => {
43042
+ listStagingDirs: (root) => (0, import_node_fs46.readdirSync)(root, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => {
42170
43043
  try {
42171
- return { name: d.name, mtimeMs: newestMtimeMs((0, import_node_path40.join)(root, d.name), listDirEntries, (p) => (0, import_node_fs43.statSync)(p).mtimeMs) };
43044
+ return { name: d.name, mtimeMs: newestMtimeMs((0, import_node_path42.join)(root, d.name), listDirEntries, (p) => (0, import_node_fs46.statSync)(p).mtimeMs) };
42172
43045
  } catch {
42173
43046
  return { name: d.name, mtimeMs: Date.now() };
42174
43047
  }
@@ -42182,10 +43055,10 @@ function stagingApplyFsGuard(configRoot) {
42182
43055
  return {
42183
43056
  referencedPaths: () => readInstalledPluginRefs(configRoot),
42184
43057
  mtimeMs: (name) => {
42185
- const p = (0, import_node_path40.join)(stagingRoot, name);
42186
- if (!(0, import_node_fs43.existsSync)(p)) return null;
43058
+ const p = (0, import_node_path42.join)(stagingRoot, name);
43059
+ if (!(0, import_node_fs46.existsSync)(p)) return null;
42187
43060
  try {
42188
- return newestMtimeMs(p, listDirEntries, (q) => (0, import_node_fs43.statSync)(q).mtimeMs);
43061
+ return newestMtimeMs(p, listDirEntries, (q) => (0, import_node_fs46.statSync)(q).mtimeMs);
42189
43062
  } catch {
42190
43063
  return null;
42191
43064
  }
@@ -42205,13 +43078,13 @@ program2.command("plugin-prune").description(`prune stale cached MMI plugin vers
42205
43078
  return;
42206
43079
  }
42207
43080
  const plan = buildPluginCachePlan(
42208
- (0, import_node_os19.homedir)(),
43081
+ (0, import_node_os21.homedir)(),
42209
43082
  running,
42210
43083
  pluginCacheFsDeps(configRoot, directoryBytes),
42211
43084
  { withBytes: true, configRoot, includeStaging: surface !== "codex" }
42212
43085
  );
42213
43086
  const anythingToDelete = plan.prune.length > 0 || plan.staging.length > 0;
42214
- const result = o.apply && anythingToDelete ? applyPluginCachePlan(plan, (p) => (0, import_node_fs43.rmSync)(p, { recursive: true, force: true }), stagingApplyFsGuard(configRoot)) : void 0;
43087
+ const result = o.apply && anythingToDelete ? applyPluginCachePlan(plan, (p) => (0, import_node_fs46.rmSync)(p, { recursive: true, force: true }), stagingApplyFsGuard(configRoot)) : void 0;
42215
43088
  const warnings = plan.prune.length > 0 ? [CONCURRENT_SESSION_WARNING] : [];
42216
43089
  if (o.json) console.log(JSON.stringify({ ...plan, warnings, applied: result ?? null }));
42217
43090
  else console.log(renderPluginCachePlan(plan, result));