@mutmutco/cli 4.1.3 → 4.1.5

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;
@@ -7995,6 +8220,7 @@ function createDocsIndexDeps(repoRoot2) {
7995
8220
 
7996
8221
  // src/gate-budget.ts
7997
8222
  var BLESSED_RUN_WITH_BUDGET_SHA = "ea2cf8710949dec53566c29836e29cd7515a4e23";
8223
+ var BLESSED_RUNNER_GATE_SHA = "dfe45da73f05190fb721563a1411e0bcd47093f4";
7998
8224
  var REMOTE_USE = /^mutmutco\/MMI-Hub\/\.github\/actions\/run-with-budget@(\S+)$/;
7999
8225
  var LOCAL_USE = "./.github/actions/run-with-budget";
8000
8226
  var FULL_SHA = /^[0-9a-f]{40}$/;
@@ -8443,21 +8669,16 @@ function gateSeedVars(cls, releaseTrack, runtime = "node", requiredCheckBranches
8443
8669
  GATE_PY_VERSION: DEFAULT_GATE_PY_VERSION,
8444
8670
  GATE_MAX_SECONDS: DEFAULT_GATE_MAX_SECONDS,
8445
8671
  // The pin is the CLI's blessed SHA, not an operator knob — one central place to bump (#3178).
8446
- GATE_BUDGET_SHA: BLESSED_RUN_WITH_BUDGET_SHA
8672
+ GATE_BUDGET_SHA: BLESSED_RUN_WITH_BUDGET_SHA,
8673
+ GATE_RUNNER_SHA: BLESSED_RUNNER_GATE_SHA
8447
8674
  };
8448
8675
  const track = releaseTrack ?? (cls === "content" ? "trunk" : "full");
8449
8676
  const trackBranches = track === "trunk" ? ["main"] : track === "direct" ? ["development", "main"] : ["development", "rc", "main"];
8450
8677
  const rulesetBranches = requiredCheckBranches?.length ? [...requiredCheckBranches] : trackBranches;
8451
8678
  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
8679
  if (track === "trunk") {
8458
8680
  return {
8459
8681
  ...runtimeVars,
8460
- ...windowsCompat,
8461
8682
  GATE_PUSH_BRANCHES_YAML: "[main]",
8462
8683
  GATE_FULL_RUN_BRANCH: "main",
8463
8684
  GATE_RULESET_BRANCH_REFS_JSON: rulesetRefs
@@ -8466,7 +8687,6 @@ function gateSeedVars(cls, releaseTrack, runtime = "node", requiredCheckBranches
8466
8687
  if (track === "direct") {
8467
8688
  return {
8468
8689
  ...runtimeVars,
8469
- ...windowsCompat,
8470
8690
  GATE_PUSH_BRANCHES_YAML: "[development, main]",
8471
8691
  GATE_FULL_RUN_BRANCH: "development",
8472
8692
  GATE_RULESET_BRANCH_REFS_JSON: rulesetRefs
@@ -8474,7 +8694,6 @@ function gateSeedVars(cls, releaseTrack, runtime = "node", requiredCheckBranches
8474
8694
  }
8475
8695
  return {
8476
8696
  ...runtimeVars,
8477
- ...windowsCompat,
8478
8697
  GATE_PUSH_BRANCHES_YAML: "[development, rc, main]",
8479
8698
  GATE_FULL_RUN_BRANCH: "development",
8480
8699
  GATE_RULESET_BRANCH_REFS_JSON: rulesetRefs
@@ -8491,38 +8710,8 @@ function withDerivedRepoVars(vars, parsed, cls, releaseTrack, requiredCheckBranc
8491
8710
  for (const [key, value] of Object.entries(gateSeedVars(cls, track, runtime, requiredCheckBranches))) {
8492
8711
  out[key] ??= value;
8493
8712
  }
8494
- if (out.GATE_WINDOWS_COMPAT === "true" && !out.GATE_WINDOWS_COMPAT_JOB_YAML) {
8495
- out.GATE_WINDOWS_COMPAT_JOB_YAML = windowsCompatJobYaml(out);
8496
- }
8497
8713
  return out;
8498
8714
  }
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
8715
  function gateConfigToVars(gate) {
8527
8716
  const out = {};
8528
8717
  if (!gate || typeof gate !== "object") return out;
@@ -8533,7 +8722,6 @@ function gateConfigToVars(gate) {
8533
8722
  if (typeof gate.pyVersion === "string" && gate.pyVersion.trim()) out.GATE_PY_VERSION = gate.pyVersion;
8534
8723
  const seconds = typeof gate.maxSeconds === "number" ? String(gate.maxSeconds) : gate.maxSeconds;
8535
8724
  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
8725
  return out;
8538
8726
  }
8539
8727
  function seedMatchesDeployModel(seed, deployModel) {
@@ -8848,6 +9036,25 @@ function parseVerifyBroker(stdout) {
8848
9036
  return out;
8849
9037
  }
8850
9038
 
9039
+ // src/release-auxiliary-workflows.ts
9040
+ var AUXILIARY_RELEASE_WORKFLOW_NAMES = /* @__PURE__ */ new Set([
9041
+ "Wake Merv on hub release",
9042
+ "Wake Merv on learning issues"
9043
+ ]);
9044
+ var AUXILIARY_RELEASE_CHECK_CONTEXTS = /* @__PURE__ */ new Set([
9045
+ ...AUXILIARY_RELEASE_WORKFLOW_NAMES,
9046
+ "wake-merv"
9047
+ ]);
9048
+ function isAuxiliaryReleaseWorkflowRun(row) {
9049
+ return AUXILIARY_RELEASE_WORKFLOW_NAMES.has(row.workflowName ?? "");
9050
+ }
9051
+ function isReleaseShaDeployEnumerationCandidate(row, nonDeployEvents) {
9052
+ if (nonDeployEvents.has(row.event ?? "")) return false;
9053
+ if (isAuxiliaryReleaseWorkflowRun(row)) return false;
9054
+ if (row.event === "release") return false;
9055
+ return true;
9056
+ }
9057
+
8851
9058
  // src/train-apply.ts
8852
9059
  var import_node_fs15 = require("node:fs");
8853
9060
  var import_promises2 = require("node:fs/promises");
@@ -10396,7 +10603,7 @@ function rateLimitedReceipt(opts) {
10396
10603
  };
10397
10604
  }
10398
10605
  async function runWithRateLimitBackoff(operation, opts) {
10399
- const sleep2 = opts.sleep ?? ((ms) => new Promise((resolve5) => setTimeout(resolve5, ms)));
10606
+ const sleep2 = opts.sleep ?? ((ms) => new Promise((resolve6) => setTimeout(resolve6, ms)));
10400
10607
  const now = opts.now ?? Date.now;
10401
10608
  const log = opts.log ?? ((message) => console.warn(message));
10402
10609
  const capMs = opts.capMs ?? RATE_LIMIT_WAIT_CAP_MS;
@@ -10650,6 +10857,69 @@ function flagValue(args, flag) {
10650
10857
  if (i === -1 || i + 1 >= args.length) return void 0;
10651
10858
  return args[i + 1];
10652
10859
  }
10860
+ function isPrCreateRemoteHeadGraphqlNoise(text) {
10861
+ 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);
10862
+ }
10863
+ function unpushedHeadMessage(head) {
10864
+ return `pr create: head branch '${head}' is not on the remote \u2014 push the branch first`;
10865
+ }
10866
+ function emptyCompareMessage(base, head) {
10867
+ return `pr create: no commits between ${base} and ${head} \u2014 push the branch first if those commits are only local`;
10868
+ }
10869
+ function humanPrCreateRemoteHeadError(text, args) {
10870
+ if (!isPrCreateRemoteHeadGraphqlNoise(text)) return void 0;
10871
+ const head = flagValue(args, "--head") ?? "the head branch";
10872
+ const base = flagValue(args, "--base") ?? "the base";
10873
+ if (/Head sha can't be blank/i.test(text) || /Head ref must be a branch/i.test(text)) {
10874
+ return unpushedHeadMessage(head);
10875
+ }
10876
+ return emptyCompareMessage(base, head);
10877
+ }
10878
+ function isGhHttpNotFound(err) {
10879
+ const text = execErrorText(err);
10880
+ if (httpStatusCodes(text).includes(404)) return true;
10881
+ return /HTTP\s*404|Not Found \(HTTP 404\)|\(404\)/i.test(text);
10882
+ }
10883
+ function isForkStyleHead(head) {
10884
+ return head.includes(":");
10885
+ }
10886
+ async function defaultReadRemoteHead(exec, repo, head) {
10887
+ try {
10888
+ await exec("gh", ["api", `repos/${repo}/git/ref/heads/${encodeURIComponent(head)}`], { timeout: 15e3 });
10889
+ return "present";
10890
+ } catch (e) {
10891
+ if (isGhHttpNotFound(e)) return "absent";
10892
+ return "unknown";
10893
+ }
10894
+ }
10895
+ async function defaultCompareRefs(exec, repo, base, head) {
10896
+ try {
10897
+ const { stdout } = await exec(
10898
+ "gh",
10899
+ ["api", `repos/${repo}/compare/${encodeURIComponent(base)}...${encodeURIComponent(head)}`],
10900
+ { timeout: 15e3 }
10901
+ );
10902
+ const aheadBy = JSON.parse(stdout).ahead_by;
10903
+ return typeof aheadBy === "number" ? { aheadBy } : "unknown";
10904
+ } catch {
10905
+ return "unknown";
10906
+ }
10907
+ }
10908
+ async function preflightPrCreateRemoteHead(args, deps) {
10909
+ const repo = flagValue(args, "--repo");
10910
+ const head = flagValue(args, "--head");
10911
+ if (!repo || !head || isForkStyleHead(head)) return void 0;
10912
+ const readRemoteHead = deps.readRemoteHead ?? ((input) => defaultReadRemoteHead(deps.exec, input.repo, input.head));
10913
+ const presence = await readRemoteHead({ repo, head });
10914
+ if (presence === "absent") return unpushedHeadMessage(head);
10915
+ if (presence !== "present") return void 0;
10916
+ const base = flagValue(args, "--base");
10917
+ if (!base) return void 0;
10918
+ const compareRefs = deps.compareRefs ?? ((input) => defaultCompareRefs(deps.exec, input.repo, input.base, input.head));
10919
+ const compared = await compareRefs({ repo, base, head });
10920
+ if (compared !== "unknown" && compared.aheadBy <= 0) return emptyCompareMessage(base, head);
10921
+ return void 0;
10922
+ }
10653
10923
  function buildPrCreateRetryCommand(args) {
10654
10924
  const parts = ["mmi-cli", "devops", "pr", "create"];
10655
10925
  for (const flag of ["--repo", "--base", "--head", "--title"]) {
@@ -11057,13 +11327,21 @@ async function createPrViaRestFallback(args, swappedArgs, deps, knownPools) {
11057
11327
  }
11058
11328
  async function ghCreate(args, deps = {}) {
11059
11329
  const exec = deps.exec ?? execFileP2;
11060
- const sleep2 = deps.sleep ?? ((ms) => new Promise((resolve5) => setTimeout(resolve5, ms)));
11330
+ const sleep2 = deps.sleep ?? ((ms) => new Promise((resolve6) => setTimeout(resolve6, ms)));
11061
11331
  const now = deps.now ?? Date.now;
11062
11332
  const read = deps.readFile ?? import_promises.readFile;
11063
11333
  const restCreatePr = deps.restCreatePr ?? ((input) => defaultRestCreatePr(exec, input));
11064
11334
  const restCreateIssue = deps.restCreateIssue ?? ((input) => defaultRestCreateIssue(exec, input));
11065
11335
  const findOpenPr = deps.findOpenPr ?? ((input) => defaultFindOpenPr(exec, input));
11066
11336
  const readRateLimit = deps.readRateLimit ?? (() => defaultReadRateLimit(exec));
11337
+ if (args[0] === "pr") {
11338
+ const refusal = await preflightPrCreateRemoteHead(args, {
11339
+ exec,
11340
+ readRemoteHead: deps.readRemoteHead,
11341
+ compareRefs: deps.compareRefs
11342
+ });
11343
+ if (refusal) return fail(refusal);
11344
+ }
11067
11345
  const swapped = await bodyArgsViaFile(args);
11068
11346
  const restDeps = {
11069
11347
  exec,
@@ -11082,6 +11360,8 @@ async function ghCreate(args, deps = {}) {
11082
11360
  } catch (restErr) {
11083
11361
  await swapped.cleanup();
11084
11362
  const restText = execErrorText(restErr);
11363
+ const humanRest = humanPrCreateRemoteHeadError(restText, args);
11364
+ if (humanRest) return fail(humanRest);
11085
11365
  if (isGhRateLimitError(restText)) {
11086
11366
  const pools2 = await readRateLimit();
11087
11367
  return rateLimitedResult({
@@ -11172,6 +11452,10 @@ async function ghCreate(args, deps = {}) {
11172
11452
  message: `${context}: GraphQL rate-limited \u2014 ${rateLimitResetNote(pools.graphql?.reset ?? pools.core?.reset, now())}`
11173
11453
  });
11174
11454
  }
11455
+ if (args[0] === "pr") {
11456
+ const human = humanPrCreateRemoteHeadError(errText, args);
11457
+ if (human) return fail(human);
11458
+ }
11175
11459
  if (isUpstreamGitHubFault(faultText)) return fail(upstreamFaultMessage(args[0], faultText));
11176
11460
  return fail(`gh ${args[0]} create failed: ${(err.stderr || err.stdout || err.message || String(e)).trim()}${note ? ` (${note})` : ""}`);
11177
11461
  }
@@ -11204,9 +11488,9 @@ function isValidSecretKey(key) {
11204
11488
  return KEY_RE.test(key);
11205
11489
  }
11206
11490
  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";
11491
+ const slash2 = key.indexOf("/");
11492
+ if (slash2 === -1) return "project";
11493
+ return key.slice(0, slash2) === PROJECT_TIER_SEGMENT ? "project" : "org";
11210
11494
  }
11211
11495
  function secretParamName(slug, key) {
11212
11496
  return `${SSM_ROOT}/${slug}/${key}`;
@@ -11476,8 +11760,8 @@ async function probeCapabilities(deps, repo) {
11476
11760
  }
11477
11761
  }
11478
11762
  function secretKeyLeaf(key) {
11479
- const slash = key.lastIndexOf("/");
11480
- return slash === -1 ? key : key.slice(slash + 1);
11763
+ const slash2 = key.lastIndexOf("/");
11764
+ return slash2 === -1 ? key : key.slice(slash2 + 1);
11481
11765
  }
11482
11766
  function resolveNotFoundGuidance(input) {
11483
11767
  const { key, repo, slug, report } = input;
@@ -12147,8 +12431,8 @@ async function secretsRevoke(deps, repo, login, key, _opts) {
12147
12431
  }
12148
12432
  var SECRET_COPY_BLOCKED_RE = /(?:ENC_KEY|ENCRYPTION_KEY|SECRET_KEY_BASE)/i;
12149
12433
  function isSecretCopyBlocked(key) {
12150
- const slash = key.indexOf("/");
12151
- const leaf = slash === -1 ? key : key.slice(slash + 1);
12434
+ const slash2 = key.indexOf("/");
12435
+ const leaf = slash2 === -1 ? key : key.slice(slash2 + 1);
12152
12436
  return SECRET_COPY_BLOCKED_RE.test(leaf);
12153
12437
  }
12154
12438
  function copyTierKey(stage, leaf) {
@@ -12303,9 +12587,9 @@ function parseSecretsUseArgv(tail) {
12303
12587
  const flags = {};
12304
12588
  const keys = [];
12305
12589
  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();
12590
+ const sep4 = firstSep !== -1 && separatorIsOurs(tail.slice(0, firstSep)) ? firstSep : -1;
12591
+ const head = sep4 === -1 ? tail : tail.slice(0, sep4);
12592
+ let command = sep4 === -1 ? [] : tail.slice(sep4 + 1).slice();
12309
12593
  for (let i = 0; i < head.length; ) {
12310
12594
  const tok = head[i];
12311
12595
  const eq = tok.indexOf("=");
@@ -12367,8 +12651,8 @@ var PRE_SPAWN_DRAIN_TIMEOUT_MS = 2e3;
12367
12651
  async function drainHttpPoolBeforeSpawn() {
12368
12652
  const drained = await Promise.race([
12369
12653
  closeHttpPool().then(() => true),
12370
- new Promise((resolve5) => {
12371
- setTimeout(() => resolve5(false), PRE_SPAWN_DRAIN_TIMEOUT_MS).unref?.();
12654
+ new Promise((resolve6) => {
12655
+ setTimeout(() => resolve6(false), PRE_SPAWN_DRAIN_TIMEOUT_MS).unref?.();
12372
12656
  })
12373
12657
  ]);
12374
12658
  if (!drained) destroyHttpPool();
@@ -12868,10 +13152,10 @@ var rollout_plan_default = {
12868
13152
  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
13153
  },
12870
13154
  baseline: {
12871
- version: "4.1.3",
12872
- tag: "v4.1.3",
12873
- commit: "3164484f3f37",
12874
- npm: "@mutmutco/cli@4.1.3"
13155
+ version: "4.1.5",
13156
+ tag: "v4.1.5",
13157
+ commit: "1cc8a83f5646",
13158
+ npm: "@mutmutco/cli@4.1.5"
12875
13159
  },
12876
13160
  exitCriterion: "fleet-n-of-n",
12877
13161
  hubOnlyShortcut: "forbidden",
@@ -12888,14 +13172,14 @@ var rollout_plan_default = {
12888
13172
  repo: "mutmutco/mmi-hub",
12889
13173
  role: "canary",
12890
13174
  schedule: "train",
12891
- v3Target: "v4.1.3"
13175
+ v3Target: "v4.1.5"
12892
13176
  }
12893
13177
  ],
12894
13178
  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
13179
  rollback: {
12896
13180
  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)"
13181
+ mechanism: "npm dist-tag latest -> 4.1.5 and redeploy the Hub Lambda from tag v4.1.5 (1cc8a83f5646); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
13182
+ v3Target: "v4.1.5 (@mutmutco/cli@4.1.5, tag commit 1cc8a83f5646 \u2014 last known-good release carrying the repo-index v4-only contract)"
12899
13183
  }
12900
13184
  },
12901
13185
  {
@@ -13402,6 +13686,10 @@ var DEFAULT_LIMIT = 30;
13402
13686
  var MAX_LIMIT = 100;
13403
13687
  var CHILDREN_MAX_TOTAL = 100;
13404
13688
  var CHILDREN_MAX_DEPTH = 6;
13689
+ var BOARD_STATUS_ALIAS_BATCH = 25;
13690
+ var BOARD_STATUS_LOOKUP_CONCURRENCY = 3;
13691
+ var CHILD_PROJECT_ITEMS_PAGE = 50;
13692
+ 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
13693
  var ISSUE_LIST_FIELDS = "number,title,state,url,assignees,labels";
13406
13694
  var PR_LIST_FIELDS = "number,title,state,url,headRefName,baseRefName";
13407
13695
  var QueryReadError = class extends Error {
@@ -13485,23 +13773,43 @@ async function runIssueList(deps, opts) {
13485
13773
  return shapeIssueList(rows);
13486
13774
  }
13487
13775
  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}}}}}}}}}}";
13776
+ 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
13777
  return ["api", "graphql", "-f", `query=${query}`, "-f", `owner=${owner}`, "-f", `name=${name}`];
13490
13778
  }
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;
13779
+ function childrenBoardStatusGraphqlArgs(owner, name, numbers) {
13780
+ const aliases = numbers.map((n, i) => `i${i}:issue(number:${n}){number ${CHILD_PROJECT_ITEMS_FIELDS}}`).join(" ");
13781
+ const query = `query($owner:String!,$name:String!){repository(owner:$owner,name:$name){${aliases}}}`;
13782
+ return ["api", "graphql", "-f", `query=${query}`, "-f", `owner=${owner}`, "-f", `name=${name}`];
13783
+ }
13784
+ function boardStatusFromProjectItems(nodes, boardProjectId) {
13785
+ if (!boardProjectId) return null;
13786
+ for (const pi of Array.isArray(nodes) ? nodes : []) {
13787
+ if (pi?.project?.id !== boardProjectId) continue;
13497
13788
  const status = (pi?.fieldValues?.nodes ?? []).find(
13498
13789
  (fv) => fv?.field?.name === "Status" && fv?.name
13499
13790
  );
13500
- if (status) {
13501
- boardStatus = String(status.name);
13502
- break;
13503
- }
13791
+ if (status) return String(status.name);
13792
+ }
13793
+ return null;
13794
+ }
13795
+ function extractIssueBoardStatusMap(resp, boardProjectId) {
13796
+ const map = /* @__PURE__ */ new Map();
13797
+ const repo = resp?.data?.repository;
13798
+ if (!repo || typeof repo !== "object") return map;
13799
+ for (const node of Object.values(repo)) {
13800
+ if (!node || typeof node !== "object") continue;
13801
+ const n = node;
13802
+ const number = Number(n.number);
13803
+ if (!Number.isFinite(number)) continue;
13804
+ const status = boardStatusFromProjectItems(n.projectItems?.nodes, boardProjectId);
13805
+ if (status) map.set(number, status);
13504
13806
  }
13807
+ return map;
13808
+ }
13809
+ function shapeChildNode(node, depth, boardProjectId) {
13810
+ const n = node ?? {};
13811
+ const assigneeNodes = n.assignees?.nodes ?? [];
13812
+ const boardStatus = boardStatusFromProjectItems(n.projectItems?.nodes, boardProjectId);
13505
13813
  const linkedPrs = [];
13506
13814
  const seenPr = /* @__PURE__ */ new Set();
13507
13815
  for (const ev of n.timelineItems?.nodes ?? []) {
@@ -13550,6 +13858,42 @@ async function queryChildren(deps, owner, name, number) {
13550
13858
  }
13551
13859
  return extractChildrenResponse(resp);
13552
13860
  }
13861
+ async function fillMissingChildBoardStatus(deps, children, boardProjectId) {
13862
+ if (!boardProjectId) return;
13863
+ const missing = children.filter((c) => !c.boardStatus && trySplitRepo(c.repo));
13864
+ if (!missing.length) return;
13865
+ const byRepo = /* @__PURE__ */ new Map();
13866
+ for (const c of missing) {
13867
+ const nums = byRepo.get(c.repo) ?? [];
13868
+ if (!nums.includes(c.number)) nums.push(c.number);
13869
+ byRepo.set(c.repo, nums);
13870
+ }
13871
+ const jobs = [];
13872
+ for (const [repo, numbers] of byRepo) {
13873
+ for (let i = 0; i < numbers.length; i += BOARD_STATUS_ALIAS_BATCH) {
13874
+ jobs.push({ repo, numbers: numbers.slice(i, i + BOARD_STATUS_ALIAS_BATCH) });
13875
+ }
13876
+ }
13877
+ const maps = await mapBounded(jobs, BOARD_STATUS_LOOKUP_CONCURRENCY, async (job) => {
13878
+ try {
13879
+ const { owner, name } = splitRepo(job.repo);
13880
+ const resp = await deps.ghJson(childrenBoardStatusGraphqlArgs(owner, name, job.numbers), GH_LIST_TIMEOUT_MS);
13881
+ return extractIssueBoardStatusMap(resp, boardProjectId);
13882
+ } catch {
13883
+ return /* @__PURE__ */ new Map();
13884
+ }
13885
+ });
13886
+ const resolved = /* @__PURE__ */ new Map();
13887
+ for (let i = 0; i < jobs.length; i++) {
13888
+ const job = jobs[i];
13889
+ for (const [num, status] of maps[i] ?? []) resolved.set(childKey(job.repo, num), status);
13890
+ }
13891
+ for (const c of children) {
13892
+ if (c.boardStatus) continue;
13893
+ const status = resolved.get(childKey(c.repo, c.number));
13894
+ if (status) c.boardStatus = status;
13895
+ }
13896
+ }
13553
13897
  async function runIssueChildren(deps, epic, opts) {
13554
13898
  const ref = parseIssueRef(epic);
13555
13899
  const repo = ref.repo ?? await deps.resolveRepo(void 0);
@@ -13566,7 +13910,10 @@ async function runIssueChildren(deps, epic, opts) {
13566
13910
  out.push(child2);
13567
13911
  seen.add(childKey(child2.repo, child2.number));
13568
13912
  }
13569
- if (!opts.recursive || out.length >= CHILDREN_MAX_TOTAL) return out;
13913
+ if (!opts.recursive || out.length >= CHILDREN_MAX_TOTAL) {
13914
+ await fillMissingChildBoardStatus(deps, out, boardProjectId);
13915
+ return out;
13916
+ }
13570
13917
  const queue = [];
13571
13918
  for (const child2 of out) {
13572
13919
  const sp = trySplitRepo(child2.repo);
@@ -13592,6 +13939,7 @@ async function runIssueChildren(deps, epic, opts) {
13592
13939
  if (sp) queue.push({ owner: sp.owner, name: sp.name, number: child2.number, depth: frame.depth + 1 });
13593
13940
  }
13594
13941
  }
13942
+ await fillMissingChildBoardStatus(deps, out, boardProjectId);
13595
13943
  return out;
13596
13944
  }
13597
13945
  var FRONTIER_BUCKET_ORDER = {
@@ -13990,7 +14338,7 @@ function registerQueryCommands(program3) {
13990
14338
  queryFail("issue list", e);
13991
14339
  }
13992
14340
  });
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) => {
14341
+ 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
14342
  try {
13995
14343
  const childrenDeps = { ...deps, resolveRepo: async (r) => deps.resolveRepo(r ?? o.repo) };
13996
14344
  const boardProjectId = await resolveBoardProjectId(o.repo);
@@ -14913,6 +15261,183 @@ rollback stays available: mmi-cli devops train enforce --apply --disarm`
14913
15261
  });
14914
15262
  }
14915
15263
 
15264
+ // src/actions-billing-preflight.ts
15265
+ var CANARY_WORKFLOW = "actions-job-start-canary.yml";
15266
+ var CANARY_REPO = "mutmutco/MMI-Hub";
15267
+ 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;
15268
+ var CANARY_POLLS = 20;
15269
+ var CANARY_POLL_MS = 1e3;
15270
+ function isActionsBillingBlockText(text) {
15271
+ return ACTIONS_BILLING_BLOCK_RE.test(text);
15272
+ }
15273
+ function interpretActionsJobStart(input) {
15274
+ const text = input.text ?? "";
15275
+ if (isActionsBillingBlockText(text)) return "billing-blocked";
15276
+ const jobs = input.jobs ?? [];
15277
+ if (jobs.some((job) => isActionsBillingBlockText(JSON.stringify(job)))) return "billing-blocked";
15278
+ if (jobs.some((job) => Array.isArray(job.steps) && job.steps.length > 0 || Boolean(job.startedAt))) {
15279
+ return "started";
15280
+ }
15281
+ if (jobs.length > 0 && jobs.every((job) => {
15282
+ const finished = job.conclusion === "failure" || job.status === "completed";
15283
+ const empty = !Array.isArray(job.steps) || job.steps.length === 0;
15284
+ return finished && empty && !job.startedAt;
15285
+ })) {
15286
+ return "never-started";
15287
+ }
15288
+ return "pending";
15289
+ }
15290
+ function actionsBillingRefusal(detail) {
15291
+ return new Error(
15292
+ `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.`
15293
+ );
15294
+ }
15295
+ function parseJson(raw, label) {
15296
+ try {
15297
+ return JSON.parse(raw);
15298
+ } catch {
15299
+ throw new Error(`${label} was not valid JSON`);
15300
+ }
15301
+ }
15302
+ async function scanRepoForBillingBlock(deps, repo) {
15303
+ let raw;
15304
+ try {
15305
+ raw = await deps.run("gh", [
15306
+ "run",
15307
+ "list",
15308
+ "--repo",
15309
+ repo,
15310
+ "--limit",
15311
+ "8",
15312
+ "--json",
15313
+ "databaseId,conclusion,status,displayTitle,url"
15314
+ ]);
15315
+ } catch {
15316
+ return void 0;
15317
+ }
15318
+ const rows = parseJson(raw, `gh run list --repo ${repo}`);
15319
+ if (!Array.isArray(rows)) return void 0;
15320
+ for (const row of rows) {
15321
+ const blob = `${row.displayTitle ?? ""} ${row.conclusion ?? ""} ${row.url ?? ""}`;
15322
+ if (isActionsBillingBlockText(blob)) {
15323
+ return `${repo} run ${row.databaseId ?? row.url ?? "(unknown)"} already names a billing/spending block`;
15324
+ }
15325
+ if (row.conclusion !== "failure" && row.status !== "completed") continue;
15326
+ if (typeof row.databaseId !== "number") continue;
15327
+ try {
15328
+ const view = await deps.run("gh", [
15329
+ "run",
15330
+ "view",
15331
+ String(row.databaseId),
15332
+ "--repo",
15333
+ repo,
15334
+ "--json",
15335
+ "jobs,conclusion,displayTitle,url"
15336
+ ]);
15337
+ const parsed = parseJson(view, `gh run view ${row.databaseId}`);
15338
+ const verdict = interpretActionsJobStart({
15339
+ jobs: parsed.jobs,
15340
+ text: `${parsed.displayTitle ?? ""} ${parsed.conclusion ?? ""} ${view}`
15341
+ });
15342
+ if (verdict === "billing-blocked" || verdict === "never-started") {
15343
+ return `${repo} run ${row.databaseId} (${parsed.url ?? row.url ?? "no url"}): hosted job never started` + (verdict === "billing-blocked" ? " (billing/spending refusal)" : " (empty steps)");
15344
+ }
15345
+ } catch {
15346
+ }
15347
+ }
15348
+ return void 0;
15349
+ }
15350
+ async function correlateCanaryRun(deps, nonce) {
15351
+ const sleep2 = deps.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
15352
+ let lastError = "no matching canary run";
15353
+ for (let attempt = 0; attempt < 8; attempt++) {
15354
+ if (attempt > 0) await sleep2(CANARY_POLL_MS);
15355
+ let raw;
15356
+ try {
15357
+ raw = await deps.run("gh", [
15358
+ "run",
15359
+ "list",
15360
+ "--repo",
15361
+ CANARY_REPO,
15362
+ "--workflow",
15363
+ CANARY_WORKFLOW,
15364
+ "--limit",
15365
+ "10",
15366
+ "--json",
15367
+ "databaseId,displayTitle,status,createdAt"
15368
+ ]);
15369
+ } catch (e) {
15370
+ lastError = e instanceof Error ? e.message : String(e);
15371
+ continue;
15372
+ }
15373
+ const rows = parseJson(
15374
+ raw,
15375
+ "canary gh run list"
15376
+ );
15377
+ const match = rows.find((row) => (row.displayTitle ?? "").includes(nonce) && typeof row.databaseId === "number");
15378
+ if (match?.databaseId) return match.databaseId;
15379
+ }
15380
+ throw new Error(
15381
+ `could not correlate ${CANARY_WORKFLOW} on ${CANARY_REPO} (nonce ${nonce}): ${lastError}`
15382
+ );
15383
+ }
15384
+ async function assertActionsJobsCanStart(deps, targetRepo3) {
15385
+ const scanned = await scanRepoForBillingBlock(deps, targetRepo3);
15386
+ if (scanned) throw actionsBillingRefusal(scanned);
15387
+ if (targetRepo3.toLowerCase() !== CANARY_REPO.toLowerCase()) {
15388
+ const hubScan = await scanRepoForBillingBlock(deps, CANARY_REPO);
15389
+ if (hubScan) throw actionsBillingRefusal(hubScan);
15390
+ }
15391
+ const nonce = `5604-${(deps.now ?? Date.now)().toString(36)}`;
15392
+ try {
15393
+ await deps.run("gh", [
15394
+ "workflow",
15395
+ "run",
15396
+ CANARY_WORKFLOW,
15397
+ "--repo",
15398
+ CANARY_REPO,
15399
+ "-f",
15400
+ `nonce=${nonce}`
15401
+ ]);
15402
+ } catch (e) {
15403
+ throw actionsBillingRefusal(
15404
+ `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).`
15405
+ );
15406
+ }
15407
+ const runId = await correlateCanaryRun(deps, nonce);
15408
+ const sleep2 = deps.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
15409
+ let last = "pending";
15410
+ let url = `https://github.com/${CANARY_REPO}/actions/runs/${runId}`;
15411
+ for (let attempt = 0; attempt < CANARY_POLLS; attempt++) {
15412
+ if (attempt > 0) await sleep2(CANARY_POLL_MS);
15413
+ const view = await deps.run("gh", [
15414
+ "run",
15415
+ "view",
15416
+ String(runId),
15417
+ "--repo",
15418
+ CANARY_REPO,
15419
+ "--json",
15420
+ "jobs,status,conclusion,url,displayTitle"
15421
+ ]);
15422
+ const parsed = parseJson(view, `canary gh run view ${runId}`);
15423
+ if (parsed.url) url = parsed.url;
15424
+ last = interpretActionsJobStart({
15425
+ jobs: parsed.jobs,
15426
+ text: `${parsed.displayTitle ?? ""} ${parsed.conclusion ?? ""} ${view}`
15427
+ });
15428
+ if (last === "started") {
15429
+ await deps.run("gh", ["run", "cancel", String(runId), "--repo", CANARY_REPO]).catch(() => "");
15430
+ return;
15431
+ }
15432
+ if (last === "billing-blocked" || last === "never-started") {
15433
+ throw actionsBillingRefusal(`${url}: hosted canary ${last}`);
15434
+ }
15435
+ }
15436
+ throw actionsBillingRefusal(
15437
+ `${url}: hosted canary stayed ${last} after ${CANARY_POLLS} polls \u2014 cannot prove a hosted job can start`
15438
+ );
15439
+ }
15440
+
14916
15441
  // src/train-apply.ts
14917
15442
  var TRAIN_BUMP_INTENTS = ["major", "minor", "patch"];
14918
15443
  function reduceFollowUpOutcomes(outcomes) {
@@ -15298,7 +15823,7 @@ async function runMergeTreePreflight(deps, ours, theirs) {
15298
15823
  async function predictMergeConflicts(deps, ours, theirs) {
15299
15824
  return runMergeTreePreflight(deps, ours, theirs);
15300
15825
  }
15301
- async function mergeWithToleratedResolution(deps, sourceRef, label, resolve5, extraTolerated = []) {
15826
+ async function mergeWithToleratedResolution(deps, sourceRef, label, resolve6, extraTolerated = []) {
15302
15827
  try {
15303
15828
  await deps.run("git", ["merge", sourceRef, "--no-edit"]);
15304
15829
  return;
@@ -15312,7 +15837,7 @@ async function mergeWithToleratedResolution(deps, sourceRef, label, resolve5, ex
15312
15837
  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
15838
  );
15314
15839
  }
15315
- await deps.run("git", ["checkout", `--${resolve5}`, "--", ...unmerged]);
15840
+ await deps.run("git", ["checkout", `--${resolve6}`, "--", ...unmerged]);
15316
15841
  await deps.run("git", ["add", "--", ...unmerged]);
15317
15842
  await deps.run("git", ["commit", "--no-edit"]);
15318
15843
  }
@@ -15460,7 +15985,7 @@ var CORRELATE_SKEW_SLACK_MS = 1e4;
15460
15985
  var CORRELATE_PAGE_LIMIT = 50;
15461
15986
  var RUN_CONFIRM_ATTEMPTS = 3;
15462
15987
  var RUN_CONFIRM_DELAY_MS = 1e3;
15463
- var defaultSleep = (ms) => new Promise((resolve5) => setTimeout(resolve5, ms));
15988
+ var defaultSleep = (ms) => new Promise((resolve6) => setTimeout(resolve6, ms));
15464
15989
  function resolveSleep(deps) {
15465
15990
  return deps.sleep ?? defaultSleep;
15466
15991
  }
@@ -16198,7 +16723,7 @@ async function discoverShaWorkflowRuns(deps, repo, headSha, seenRunIds) {
16198
16723
  if (rows.length >= SHA_ENUM_LIMIT) extra.push({ workflow: `${marker} truncated`, conclusion: "pending" });
16199
16724
  for (const row of rows) {
16200
16725
  if (typeof row.databaseId !== "number" || seenRunIds.has(row.databaseId)) continue;
16201
- if (NON_DEPLOY_EVENTS.has(row.event ?? "")) continue;
16726
+ if (!isReleaseShaDeployEnumerationCandidate(row, NON_DEPLOY_EVENTS)) continue;
16202
16727
  extra.push({
16203
16728
  workflow: row.workflowName ?? `run:${row.databaseId}`,
16204
16729
  runId: row.databaseId,
@@ -16348,6 +16873,7 @@ async function preflight(deps, ctx, stage, meta) {
16348
16873
  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
16874
  }
16350
16875
  await deps.runSelf(["secrets", "preflight", "--stage", stage, "--repo", ctx.repo]);
16876
+ await assertActionsJobsCanStart(deps, ctx.repo);
16351
16877
  enforceGateBudget(deps, ctx.repo);
16352
16878
  if (model === "hub-serverless") {
16353
16879
  await deps.run("node", ["scripts/release-distribution.mjs", "verify-deps"]);
@@ -16575,6 +17101,68 @@ async function runFoldStage(deps, startBranch, preFold, resumeCommand, fn) {
16575
17101
  throw await recoverFailedFold(deps, e, startBranch, preFold.mainSha, resumeCommand);
16576
17102
  }
16577
17103
  }
17104
+ var GATE_NODE_BUNDLED_NPM = {
17105
+ "24.19.0": "11.17.0"
17106
+ };
17107
+ function parseGateNodeVersionPins(workflowBody) {
17108
+ if (!/runner-node-toolchain/.test(workflowBody)) return [];
17109
+ const pins = [];
17110
+ for (const match of workflowBody.matchAll(/^\s*node-version:\s*['"]?([^'"\s#]+)/gm)) {
17111
+ pins.push(match[1].replace(/^['"]|['"]$/g, ""));
17112
+ }
17113
+ return pins;
17114
+ }
17115
+ function preferGateNodePin(pins) {
17116
+ const exact = pins.filter((pin) => /^\d+\.\d+\.\d+/.test(pin));
17117
+ if (exact.length) return [...exact].sort().at(-1);
17118
+ return pins[0];
17119
+ }
17120
+ function bundledNpmForNodePin(nodePin) {
17121
+ return GATE_NODE_BUNDLED_NPM[nodePin];
17122
+ }
17123
+ function resolveGreenGateNpmFromGateWorkflows(files) {
17124
+ if (!files?.length) return void 0;
17125
+ const pin = preferGateNodePin(files.flatMap((file) => parseGateNodeVersionPins(file.body)));
17126
+ return pin ? bundledNpmForNodePin(pin) : void 0;
17127
+ }
17128
+ function npmMajor(version) {
17129
+ const match = /^(\d+)/.exec(version.trim());
17130
+ return match ? Number(match[1]) : void 0;
17131
+ }
17132
+ function subprocessFailureDetail(e) {
17133
+ if (!(e instanceof Error)) return String(e);
17134
+ const err = e;
17135
+ const parts = [err.message];
17136
+ if (typeof err.stderr === "string" && err.stderr.trim()) parts.push(err.stderr.trim());
17137
+ if (typeof err.stdout === "string" && err.stdout.trim()) parts.push(err.stdout.trim());
17138
+ return parts.join("\n");
17139
+ }
17140
+ function isNpmInternalModuleNotFound(detail) {
17141
+ if (!/(?:MODULE_NOT_FOUND|Cannot find module)/i.test(detail)) return false;
17142
+ return /(?:node_modules\/(?:npm|libnpmpublish|@npmcli)\/|Require stack:[\s\S]*node_modules\/npm\/)/i.test(detail);
17143
+ }
17144
+ function classifyPublishDryRunFailure(opts) {
17145
+ const underlying = subprocessFailureDetail(opts.underlying);
17146
+ const cmd = `npm ${opts.publishArgs.join(" ")}`;
17147
+ if (isNpmInternalModuleNotFound(underlying)) {
17148
+ const gateNpm = opts.gateNpm?.trim();
17149
+ const localNpm = opts.localNpm.trim();
17150
+ if (gateNpm && localNpm && localNpm !== gateNpm) {
17151
+ const localMajor = npmMajor(localNpm);
17152
+ const gateMajor = npmMajor(gateNpm);
17153
+ const majorNote = localMajor !== void 0 && gateMajor !== void 0 && localMajor !== gateMajor ? ` (npm major ${localMajor} locally vs ${gateMajor} on the green gate)` : "";
17154
+ return new Error(
17155
+ `${opts.repo}: pre-tag publish dry run failed inside npm internals (${cmd}) \u2014 local npm ${localNpm} \u2260 green-gate npm ${gateNpm}${majorNote}. This is toolchain corruption / an npm-major mismatch (#5616/#4578), NOT a malformed publish surface. Compare toolchains BEFORE inspecting package contents: align local npm to the green gate (\`npm install -g npm@${gateNpm}\`), then rerun. Underlying error: ${underlying}`
17156
+ );
17157
+ }
17158
+ return new Error(
17159
+ `${opts.repo}: pre-tag publish dry run failed inside npm internals (${cmd}) \u2014 local npm ${localNpm}${gateNpm ? ` (green-gate npm ${gateNpm} when known)` : ""}. This is toolchain corruption, NOT a malformed publish surface (#5616). Compare \`npm -v\` with the green gate log's toolchain-preflight line (#3446) BEFORE inspecting package contents; repair or align the local npm install, then rerun. Underlying error: ${underlying}`
17160
+ );
17161
+ }
17162
+ return new Error(
17163
+ `${opts.repo}: pre-tag publish dry run failed for '${opts.publishDir}' (${cmd}) \u2014 refusing to tag a release that would fail to publish (#2753). Fix the publish surface (deleted dir, malformed package.json, missing files), then rerun. Underlying error: ${underlying}`
17164
+ );
17165
+ }
16578
17166
  async function verifyPublishDryRun(deps, ctx, meta, deployModel) {
16579
17167
  if (!(deployModel === "registry-publish" || meta.publishRequired === true)) return;
16580
17168
  const publishDir = typeof meta.publishDir === "string" && meta.publishDir.trim() ? meta.publishDir.trim() : ".";
@@ -16582,9 +17170,20 @@ async function verifyPublishDryRun(deps, ctx, meta, deployModel) {
16582
17170
  try {
16583
17171
  await deps.run("npm", publishArgs);
16584
17172
  } catch (e) {
16585
- throw new Error(
16586
- `${ctx.repo}: pre-tag publish dry run failed for '${publishDir}' (npm ${publishArgs.join(" ")}) \u2014 refusing to tag a release that would fail to publish (#2753). Fix the publish surface (deleted dir, malformed package.json, missing files), then rerun. Underlying error: ${e instanceof Error ? e.message : String(e)}`
16587
- );
17173
+ let localNpm = "version-unreadable";
17174
+ try {
17175
+ localNpm = clean2(await deps.run("npm", ["-v"]));
17176
+ } catch {
17177
+ }
17178
+ const gateNpm = resolveGreenGateNpmFromGateWorkflows((deps.readGateWorkflows ?? readLocalGateWorkflows)());
17179
+ throw classifyPublishDryRunFailure({
17180
+ repo: ctx.repo,
17181
+ publishDir,
17182
+ publishArgs,
17183
+ underlying: e,
17184
+ localNpm,
17185
+ gateNpm
17186
+ });
16588
17187
  }
16589
17188
  }
16590
17189
  var TRUE_MERGE_GATE_BRANCH_PREFIX = "train/check/";
@@ -18525,7 +19124,7 @@ async function mergeAutoWithTransientRetry(prNumber, repo, deps) {
18525
19124
  if (first.mergeStatus !== "failed") return first;
18526
19125
  const ready = await deps.probeMergeReady(prNumber, repo).catch(() => ({ open: false, mergeable: false, checksPassing: false }));
18527
19126
  if (!ready.open || !ready.mergeable || !ready.checksPassing) return first;
18528
- const sleep2 = deps.sleep ?? ((ms) => new Promise((resolve5) => setTimeout(resolve5, ms)));
19127
+ const sleep2 = deps.sleep ?? ((ms) => new Promise((resolve6) => setTimeout(resolve6, ms)));
18529
19128
  await sleep2(PR_LAND_MERGE_RETRY_DELAY_MS);
18530
19129
  const retried = await deps.mergeAuto(prNumber, repo);
18531
19130
  if (retried.mergeStatus !== "failed") return retried;
@@ -18536,7 +19135,7 @@ var AUTO_MERGE_CONFIRM_DELAY_MS = 3e3;
18536
19135
  async function confirmAutoMergeEnqueued(deps, options) {
18537
19136
  const retries = options?.retries ?? AUTO_MERGE_CONFIRM_RETRIES;
18538
19137
  const delayMs = options?.delayMs ?? AUTO_MERGE_CONFIRM_DELAY_MS;
18539
- const sleep2 = deps.sleep ?? ((ms) => new Promise((resolve5) => setTimeout(resolve5, ms)));
19138
+ const sleep2 = deps.sleep ?? ((ms) => new Promise((resolve6) => setTimeout(resolve6, ms)));
18540
19139
  for (let attempt = 0; attempt < retries; attempt++) {
18541
19140
  if (await deps.readMerged().catch(() => false)) return "merged";
18542
19141
  const stuck = await deps.readAutoMergeRequest().then((s) => s.trim()).catch(() => "");
@@ -18553,7 +19152,7 @@ async function confirmAutoMergeEnqueued(deps, options) {
18553
19152
  async function readGhPrStateWithRetry(fetchState, options) {
18554
19153
  const retries = options?.retries ?? PR_LAND_STATE_READ_RETRIES;
18555
19154
  const delayMs = options?.delayMs ?? PR_LAND_STATE_READ_DELAY_MS;
18556
- const sleep2 = options?.sleep ?? ((ms) => new Promise((resolve5) => setTimeout(resolve5, ms)));
19155
+ const sleep2 = options?.sleep ?? ((ms) => new Promise((resolve6) => setTimeout(resolve6, ms)));
18557
19156
  let lastError = "empty state";
18558
19157
  for (let attempt = 0; attempt < retries; attempt++) {
18559
19158
  try {
@@ -18687,7 +19286,7 @@ function healthPollIntervalMs() {
18687
19286
  return HEALTH_POLL_INTERVAL_MS;
18688
19287
  }
18689
19288
  function waitForProcessStability(child2, graceMs = earlyExitGraceMs()) {
18690
- return new Promise((resolve5, reject) => {
19289
+ return new Promise((resolve6, reject) => {
18691
19290
  let settled = false;
18692
19291
  const finish = (fn) => {
18693
19292
  if (settled) return;
@@ -18697,7 +19296,7 @@ function waitForProcessStability(child2, graceMs = earlyExitGraceMs()) {
18697
19296
  child2.removeAllListeners("exit");
18698
19297
  fn();
18699
19298
  };
18700
- const timer = setTimeout(() => finish(resolve5), graceMs);
19299
+ const timer = setTimeout(() => finish(resolve6), graceMs);
18701
19300
  child2.on("error", (err) => finish(() => reject(new Error(`stage process failed to start: ${err.message}`))));
18702
19301
  child2.on("exit", (code, signal) => {
18703
19302
  const detail = code != null ? `code ${code}` : signal ? `signal ${signal}` : "unknown reason";
@@ -18903,10 +19502,10 @@ function pickStagePort(range, isFree) {
18903
19502
  throw new Error(`no free stage port in range ${start}-${end} \u2014 every port is in use`);
18904
19503
  }
18905
19504
  function isPortFree(port) {
18906
- return new Promise((resolve5) => {
19505
+ return new Promise((resolve6) => {
18907
19506
  const srv = (0, import_node_net.createServer)();
18908
- srv.once("error", () => resolve5(false));
18909
- srv.once("listening", () => srv.close(() => resolve5(true)));
19507
+ srv.once("error", () => resolve6(false));
19508
+ srv.once("listening", () => srv.close(() => resolve6(true)));
18910
19509
  srv.listen(port, "127.0.0.1");
18911
19510
  });
18912
19511
  }
@@ -19117,7 +19716,7 @@ async function killTree(pid) {
19117
19716
  } catch {
19118
19717
  }
19119
19718
  }
19120
- await new Promise((resolve5) => setTimeout(resolve5, 500));
19719
+ await new Promise((resolve6) => setTimeout(resolve6, 500));
19121
19720
  try {
19122
19721
  process.kill(-pid, "SIGKILL");
19123
19722
  } catch {
@@ -19138,7 +19737,7 @@ async function waitForHealth(url, timeoutMs, anyStatus = false) {
19138
19737
  } catch (e) {
19139
19738
  last = e.message;
19140
19739
  }
19141
- await new Promise((resolve5) => setTimeout(resolve5, healthPollIntervalMs()));
19740
+ await new Promise((resolve6) => setTimeout(resolve6, healthPollIntervalMs()));
19142
19741
  }
19143
19742
  throw new Error(`stage health check timed out for ${url}${last ? ` (${last})` : ""}`);
19144
19743
  }
@@ -19373,12 +19972,12 @@ async function executeWaveLand(plan, deps) {
19373
19972
  }
19374
19973
 
19375
19974
  // src/index.ts
19376
- var import_node_os19 = require("node:os");
19975
+ var import_node_os21 = require("node:os");
19377
19976
 
19378
19977
  // src/board.ts
19379
19978
  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");
19979
+ var import_node_fs20 = require("node:fs");
19980
+ var import_node_os9 = require("node:os");
19382
19981
  var import_node_path17 = require("node:path");
19383
19982
  var import_node_util6 = require("node:util");
19384
19983
  init_github_client();
@@ -19826,7 +20425,7 @@ async function postIssueComment(client, input) {
19826
20425
  var SKILL_LESSON_LABEL = "skill-lesson";
19827
20426
  var SKILL_LESSON_FILE_LABELS = [SKILL_LESSON_LABEL, LEARNING_LABEL];
19828
20427
  var SKILL_LESSON_LOOP_KIND = "lesson";
19829
- var SKILL_NAMES = ["bootstrap", "browser-automation", "doctor", "epic", "hotfix", "mmi", "onboard", "rcand", "release", "resume", "secrets", "stage"];
20428
+ var SKILL_NAMES = ["bootstrap", "browser-automation", "doctor", "epic", "hotfix", "mmi", "onboard", "rcand", "release", "repo-index-audit", "resume", "secrets", "stage"];
19830
20429
  function assertSkillName(name) {
19831
20430
  const match = SKILL_NAMES.find((skill) => skill === name);
19832
20431
  if (!match) throw new Error(`unknown skill "${name}" \u2014 expected one of: ${SKILL_NAMES.join(", ")}`);
@@ -19858,7 +20457,7 @@ function findDuplicateLesson(source, openLessons) {
19858
20457
 
19859
20458
  // src/session-identity.ts
19860
20459
  var import_node_crypto4 = require("node:crypto");
19861
- var import_node_os7 = require("node:os");
20460
+ var import_node_os8 = require("node:os");
19862
20461
  init_plugin_guard_io();
19863
20462
  var SESSION_ID_ENV_VARS = [
19864
20463
  "MMI_SESSION_ID",
@@ -19894,7 +20493,7 @@ function describeSessionIdentity(env = process.env) {
19894
20493
  return {
19895
20494
  session: readSessionId(env) ?? fallbackSessionId(surface),
19896
20495
  surface,
19897
- host: (0, import_node_os7.hostname)()
20496
+ host: (0, import_node_os8.hostname)()
19898
20497
  };
19899
20498
  }
19900
20499
 
@@ -20530,7 +21129,14 @@ async function prepareClaimContext(options, selectors, deps, collected) {
20530
21129
  report[scope].claimable = filtered.claimable;
20531
21130
  report.warnings.push(...filtered.warnings);
20532
21131
  }
20533
- return { cfg, client, items: collected.items, writable: writableOrUnknown(writable), report };
21132
+ return {
21133
+ cfg,
21134
+ client,
21135
+ items: collected.items,
21136
+ writable: writableOrUnknown(writable),
21137
+ report,
21138
+ session: deps.session ?? describeSessionIdentity()
21139
+ };
20534
21140
  }
20535
21141
  async function claimOneBoardItem(ctx, selector, options) {
20536
21142
  const { cfg, client, report } = ctx;
@@ -20575,26 +21181,29 @@ async function claimOneBoardItem(ctx, selector, options) {
20575
21181
  const verdict = evaluateClaim(fresh, assignedLogin);
20576
21182
  if (!verdict.ok) throw new Error(verdict.reason);
20577
21183
  item = fresh;
21184
+ const refuseIfContested = async () => {
21185
+ if (options.force) return;
21186
+ const contest = await checkLaneContest(client, item, ctx.session);
21187
+ if (contest.contested) throw new Error(laneContestMessage(item.ref, contest, "claim"));
21188
+ };
21189
+ await refuseIfContested();
20578
21190
  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
21191
  if (options.check) {
20584
21192
  return { item, viewer: report.viewer, repo: report.repo, status: "In Progress", partial: false, alreadyClaimed: true, checked: true };
20585
21193
  }
20586
- await postClaimMarkerComment(client, item);
21194
+ await postClaimMarkerComment(client, item, ctx.session);
20587
21195
  return { item, viewer: report.viewer, repo: report.repo, status: "In Progress", partial: false, alreadyClaimed: true };
20588
21196
  }
20589
21197
  if (options.check) {
20590
21198
  return { item, viewer: report.viewer, repo: report.repo, status: item.status, partial: false, checked: true };
20591
21199
  }
21200
+ await refuseIfContested();
20592
21201
  try {
20593
21202
  await client.rest("POST", `repos/${item.repository}/issues/${item.number}/assignees`, { body: { assignees: [assignedLogin] } });
20594
21203
  } catch (e) {
20595
21204
  throw new Error(`claim failed before board status changed: ${ghError(e)}`);
20596
21205
  }
20597
- await postClaimMarkerComment(client, item);
21206
+ await postClaimMarkerComment(client, item, ctx.session);
20598
21207
  try {
20599
21208
  await updateItemSingleSelect(client, cfg.projectId, item.itemId, cfg.statusFieldId, cfg.statusOptions["In Progress"]);
20600
21209
  } catch (e) {
@@ -20787,7 +21396,7 @@ async function setBoardItemPriority(client, cfg, itemId, priority) {
20787
21396
  await updateItemSingleSelect(client, cfg.projectId, itemId, cfg.priorityFieldId, optionId);
20788
21397
  return cliPriorityToFieldName(priority);
20789
21398
  }
20790
- var defaultRetrySleep = (ms) => new Promise((resolve5) => setTimeout(resolve5, ms));
21399
+ var defaultRetrySleep = (ms) => new Promise((resolve6) => setTimeout(resolve6, ms));
20791
21400
  async function resolveProjectItemIdWithRetry(client, cfg, selector, opts = {}) {
20792
21401
  const attempts = Math.max(1, opts.attempts ?? 5);
20793
21402
  const delayMs = opts.delayMs ?? 300;
@@ -21220,9 +21829,8 @@ function boardItemClaim(item) {
21220
21829
  currentlyClaimed: item.assignees.length > 0 && item.status === "In Progress"
21221
21830
  };
21222
21831
  }
21223
- async function postClaimMarkerComment(client, item) {
21832
+ async function postClaimMarkerComment(client, item, actor = describeSessionIdentity()) {
21224
21833
  try {
21225
- const actor = describeSessionIdentity();
21226
21834
  const marker = {
21227
21835
  v: 1,
21228
21836
  session: actor.session,
@@ -21245,7 +21853,7 @@ var CLAIM_SESSION_ACTIVITY_MS = 30 * 6e4;
21245
21853
  var CLAIM_SESSION_PROBE_CACHE_MS = 6e4;
21246
21854
  var claimSessionProbeCache = /* @__PURE__ */ new Map();
21247
21855
  function probeLocalClaimSession(marker, now = Date.now()) {
21248
- if (!marker.session || !marker.host || marker.host.toLowerCase() !== (0, import_node_os8.hostname)().toLowerCase()) return void 0;
21856
+ if (!marker.session || !marker.host || marker.host.toLowerCase() !== (0, import_node_os9.hostname)().toLowerCase()) return void 0;
21249
21857
  if (!marker.surface?.toLowerCase().startsWith("claude")) return void 0;
21250
21858
  const cacheKey = `${marker.host.toLowerCase()}/${marker.session}`;
21251
21859
  const cached = claimSessionProbeCache.get(cacheKey);
@@ -21254,17 +21862,17 @@ function probeLocalClaimSession(marker, now = Date.now()) {
21254
21862
  claimSessionProbeCache.set(cacheKey, { checkedAt: now, state });
21255
21863
  return state;
21256
21864
  };
21257
- const root = (0, import_node_path17.join)((0, import_node_os8.homedir)(), ".claude", "projects");
21865
+ const root = (0, import_node_path17.join)((0, import_node_os9.homedir)(), ".claude", "projects");
21258
21866
  try {
21259
21867
  const wanted = `${marker.session}.jsonl`.toLowerCase();
21260
21868
  const pending = [root];
21261
21869
  while (pending.length) {
21262
21870
  const dir = pending.pop();
21263
- for (const entry of (0, import_node_fs19.readdirSync)(dir, { withFileTypes: true })) {
21871
+ for (const entry of (0, import_node_fs20.readdirSync)(dir, { withFileTypes: true })) {
21264
21872
  const path2 = (0, import_node_path17.join)(dir, entry.name);
21265
21873
  if (entry.isDirectory()) pending.push(path2);
21266
21874
  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");
21875
+ return remember(now - (0, import_node_fs20.statSync)(path2).mtimeMs <= CLAIM_SESSION_ACTIVITY_MS ? "live" : "dead");
21268
21876
  }
21269
21877
  }
21270
21878
  }
@@ -21376,9 +21984,8 @@ function laneOwnership(marker, current) {
21376
21984
  }
21377
21985
  return "unknown";
21378
21986
  }
21379
- async function checkLaneContest(client, item) {
21987
+ async function checkLaneContest(client, item, actor = describeSessionIdentity()) {
21380
21988
  const evidence = await gatherClaimLiveness(client, item.repository, item.number, openPullsFetcher(client));
21381
- const actor = describeSessionIdentity();
21382
21989
  const ownership = laneOwnership(evidence.marker, actor);
21383
21990
  const live = ownership === "mine" ? [] : liveEvidenceLines(evidence, item.repository);
21384
21991
  const unverifiable = ownership === "mine" ? [] : evidence.failed;
@@ -21576,7 +22183,7 @@ async function resolveAutoAddBoardAttach(client, cfg, selector, priority, warn =
21576
22183
  }
21577
22184
 
21578
22185
  // src/issue-body.ts
21579
- var import_node_os9 = require("node:os");
22186
+ var import_node_os10 = require("node:os");
21580
22187
  init_error_codes();
21581
22188
  var TextArgError = class extends Error {
21582
22189
  constructor(message, code, offendingFlag) {
@@ -21589,7 +22196,7 @@ var TextArgError = class extends Error {
21589
22196
  offendingFlag;
21590
22197
  };
21591
22198
  function emptyStdinMessage(fileFlag) {
21592
- if ((0, import_node_os9.platform)() === "win32") {
22199
+ if ((0, import_node_os10.platform)() === "win32") {
21593
22200
  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
22201
  }
21595
22202
  return `${fileFlag} - read empty stdin (nothing piped \u2014 pass a heredoc/pipe, or ${fileFlag} <path>)`;
@@ -21690,12 +22297,13 @@ var PRIMARY_GROUPS = [
21690
22297
  ["Review and ship", ["pr", "ci", "rcand", "release", "hotfix", "train"]],
21691
22298
  // `tests` sits beside `docs` deliberately: both are deterministic, repo-local gates a workflow
21692
22299
  // 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"]],
22300
+ // joins them on the same footing (#3979); `dist status` does too (#5576) — the checkout's own
22301
+ // dist/BOM freshness read.
22302
+ ["Setup and support", ["bootstrap", "secrets", "docs", "repo-index", "tests", "spawn", "dist"]],
21695
22303
  ["Coordinate and improve", ["wave", "report", "skill-lesson", "closure-rate"]]
21696
22304
  ];
21697
22305
  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"]);
22306
+ var SUPPORT_PRIMARY = /* @__PURE__ */ new Set(["doctor", "whoami", "commands", "explain", "docs", "repo-index", "find", "tests", "spawn", "dist", "wave", "report", "skill-lesson", "closure-rate"]);
21699
22307
  var TOP_LEVEL_ORDER = /* @__PURE__ */ new Map();
21700
22308
  var HELP_GROUP_ORDER = /* @__PURE__ */ new Map();
21701
22309
  var topLevelPosition = 0;
@@ -21748,6 +22356,7 @@ var COMMAND_OWNERSHIP = {
21748
22356
  find: { module_owner: "cli/src/repo-index.ts", consumer: "agent-session" },
21749
22357
  tests: { module_owner: "cli/src/test-policy-core.ts", consumer: "repo-gates" },
21750
22358
  spawn: { module_owner: "cli/src/spawn-policy-core.ts", consumer: "repo-gates" },
22359
+ dist: { module_owner: "cli/src/dist-drift.ts", consumer: "repo-gates" },
21751
22360
  wave: { module_owner: "cli/src/wave-land.ts", consumer: "campaign-orchestrator" },
21752
22361
  report: { module_owner: "cli/src/report.ts", consumer: "campaign-orchestrator" },
21753
22362
  "skill-lesson": { module_owner: "cli/src/skill-lesson.ts", consumer: "campaign-orchestrator" },
@@ -22269,13 +22878,13 @@ init_hub_url();
22269
22878
  init_client_version();
22270
22879
 
22271
22880
  // src/claude-binary-doctor.ts
22272
- var import_node_fs21 = require("node:fs");
22273
- var import_node_os11 = require("node:os");
22881
+ var import_node_fs22 = require("node:fs");
22882
+ var import_node_os12 = require("node:os");
22274
22883
  var import_node_path19 = require("node:path");
22275
22884
 
22276
22885
  // src/jerv-cli-spawn.ts
22277
- var import_node_fs20 = require("node:fs");
22278
- var import_node_os10 = require("node:os");
22886
+ var import_node_fs21 = require("node:fs");
22887
+ var import_node_os11 = require("node:os");
22279
22888
  var import_node_path18 = require("node:path");
22280
22889
  init_cli_shared();
22281
22890
  var WIN_NAMES = ["jerv-cli.cmd", "jerv-cli.exe", "jerv-cli"];
@@ -22302,7 +22911,7 @@ function normalizeSpawnPathEntry(entry, platform2 = process.platform) {
22302
22911
  if (msys) return `${msys[1].toUpperCase()}:\\${msys[2].replace(/\//g, "\\")}`;
22303
22912
  return trimmed;
22304
22913
  }
22305
- function jervCliCandidateDirs(env = process.env, home = (0, import_node_os10.homedir)(), platform2 = process.platform) {
22914
+ function jervCliCandidateDirs(env = process.env, home = (0, import_node_os11.homedir)(), platform2 = process.platform) {
22306
22915
  const seen = /* @__PURE__ */ new Set();
22307
22916
  const out = [];
22308
22917
  const push = (dir) => {
@@ -22323,7 +22932,7 @@ function jervCliCandidateDirs(env = process.env, home = (0, import_node_os10.hom
22323
22932
  }
22324
22933
  return out;
22325
22934
  }
22326
- function jervCliCandidatePaths(env = process.env, home = (0, import_node_os10.homedir)(), platform2 = process.platform) {
22935
+ function jervCliCandidatePaths(env = process.env, home = (0, import_node_os11.homedir)(), platform2 = process.platform) {
22327
22936
  const names = platform2 === "win32" ? WIN_NAMES : POSIX_NAMES;
22328
22937
  const out = [];
22329
22938
  for (const dir of jervCliCandidateDirs(env, home, platform2)) {
@@ -22331,20 +22940,20 @@ function jervCliCandidatePaths(env = process.env, home = (0, import_node_os10.ho
22331
22940
  }
22332
22941
  return out;
22333
22942
  }
22334
- function resolveJervCliPath(env = process.env, home = (0, import_node_os10.homedir)(), platform2 = process.platform, exists = import_node_fs20.existsSync) {
22943
+ function resolveJervCliPath(env = process.env, home = (0, import_node_os11.homedir)(), platform2 = process.platform, exists = import_node_fs21.existsSync) {
22335
22944
  for (const candidate2 of jervCliCandidatePaths(env, home, platform2)) {
22336
22945
  if (exists(candidate2)) return candidate2;
22337
22946
  }
22338
22947
  return void 0;
22339
22948
  }
22340
- function resolveJervCliNodeEntry(shimPath, exists = import_node_fs20.existsSync) {
22949
+ function resolveJervCliNodeEntry(shimPath, exists = import_node_fs21.existsSync) {
22341
22950
  const entry = (0, import_node_path18.join)((0, import_node_path18.dirname)(shimPath), JERV_CLI_ENTRY);
22342
22951
  return exists(entry) ? entry : void 0;
22343
22952
  }
22344
22953
  function jervCliExecFileArgs(args, opts = {}) {
22345
22954
  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);
22955
+ const exists = opts.exists ?? import_node_fs21.existsSync;
22956
+ const resolved = resolveJervCliPath(opts.env ?? process.env, opts.home ?? (0, import_node_os11.homedir)(), platform2, exists);
22348
22957
  if (resolved) {
22349
22958
  const entry = resolveJervCliNodeEntry(resolved, exists);
22350
22959
  if (entry) {
@@ -22434,7 +23043,7 @@ function globalNodeModulesRoots(host) {
22434
23043
  };
22435
23044
  const prefix = env.npm_config_prefix?.trim();
22436
23045
  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)) {
23046
+ for (const dir of jervCliCandidateDirs(env, host.home ?? (0, import_node_os12.homedir)(), platform2)) {
22438
23047
  push((0, import_node_path19.join)(dir, "node_modules"));
22439
23048
  push((0, import_node_path19.join)((0, import_node_path19.dirname)(dir), "lib", "node_modules"));
22440
23049
  }
@@ -22443,16 +23052,16 @@ function globalNodeModulesRoots(host) {
22443
23052
  function readHead(path2) {
22444
23053
  let fd;
22445
23054
  try {
22446
- fd = (0, import_node_fs21.openSync)(path2, "r");
23055
+ fd = (0, import_node_fs22.openSync)(path2, "r");
22447
23056
  const buffer = new Uint8Array(MAGIC_HEAD_BYTES);
22448
- const read = (0, import_node_fs21.readSync)(fd, buffer, 0, MAGIC_HEAD_BYTES, 0);
23057
+ const read = (0, import_node_fs22.readSync)(fd, buffer, 0, MAGIC_HEAD_BYTES, 0);
22449
23058
  return buffer.subarray(0, read);
22450
23059
  } catch {
22451
23060
  return void 0;
22452
23061
  } finally {
22453
23062
  if (fd !== void 0) {
22454
23063
  try {
22455
- (0, import_node_fs21.closeSync)(fd);
23064
+ (0, import_node_fs22.closeSync)(fd);
22456
23065
  } catch {
22457
23066
  }
22458
23067
  }
@@ -22460,7 +23069,7 @@ function readHead(path2) {
22460
23069
  }
22461
23070
  function fileBytes(path2) {
22462
23071
  try {
22463
- return (0, import_node_fs21.statSync)(path2).size;
23072
+ return (0, import_node_fs22.statSync)(path2).size;
22464
23073
  } catch {
22465
23074
  return void 0;
22466
23075
  }
@@ -22474,13 +23083,13 @@ function readClaudeBinaryState(host = {}) {
22474
23083
  const arch = host.arch ?? process.arch;
22475
23084
  const magic = EXECUTABLE_MAGIC[platform2];
22476
23085
  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")));
23086
+ 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
23087
  if (!packageRoot) return void 0;
22479
23088
  const keys = platformPackageKeys(platform2, arch);
22480
23089
  const fallbackPackage = `${PACKAGE}-${keys[0]}`;
22481
23090
  let manifest;
22482
23091
  try {
22483
- manifest = JSON.parse((0, import_node_fs21.readFileSync)((0, import_node_path19.join)(packageRoot, "package.json"), "utf8"));
23092
+ manifest = JSON.parse((0, import_node_fs22.readFileSync)((0, import_node_path19.join)(packageRoot, "package.json"), "utf8"));
22484
23093
  } catch (e) {
22485
23094
  return {
22486
23095
  state: "unreadable",
@@ -22511,7 +23120,7 @@ function readClaudeBinaryState(host = {}) {
22511
23120
  (0, import_node_path19.join)(packageRoot, "node_modules", ...name.split("/"), binName),
22512
23121
  (0, import_node_path19.join)((0, import_node_path19.dirname)((0, import_node_path19.dirname)(packageRoot)), ...name.split("/"), binName)
22513
23122
  ];
22514
- const found = published.map((name) => ({ name, path: binIn(name).find((file) => (0, import_node_fs21.existsSync)(file)) })).find((c) => c.path);
23123
+ const found = published.map((name) => ({ name, path: binIn(name).find((file) => (0, import_node_fs22.existsSync)(file)) })).find((c) => c.path);
22515
23124
  const platformPackage = found?.name ?? published[0];
22516
23125
  let source;
22517
23126
  let sourceProblem;
@@ -22522,7 +23131,7 @@ function readClaudeBinaryState(host = {}) {
22522
23131
  } else {
22523
23132
  source = { path: found.path, bytes: fileBytes(found.path) ?? 0 };
22524
23133
  }
22525
- if (!(0, import_node_fs21.existsSync)(binPath)) {
23134
+ if (!(0, import_node_fs22.existsSync)(binPath)) {
22526
23135
  return { state: "missing", binPath, expectedMagic: magic.name, platformPackage, ...source ? { source } : {}, ...sourceProblem ? { sourceProblem } : {} };
22527
23136
  }
22528
23137
  const head = readHead(binPath);
@@ -22565,9 +23174,9 @@ function healClaudeBinary(host = {}, onStep) {
22565
23174
  const platform2 = host.platform ?? process.platform;
22566
23175
  const aside = `${probe.binPath}.stub-${Date.now()}`;
22567
23176
  let renamed = false;
22568
- if ((0, import_node_fs21.existsSync)(probe.binPath)) {
23177
+ if ((0, import_node_fs22.existsSync)(probe.binPath)) {
22569
23178
  try {
22570
- (0, import_node_fs21.renameSync)(probe.binPath, aside);
23179
+ (0, import_node_fs22.renameSync)(probe.binPath, aside);
22571
23180
  renamed = true;
22572
23181
  onStep?.(`renamed the stub aside: ${aside}`);
22573
23182
  } catch (e) {
@@ -22576,12 +23185,12 @@ function healClaudeBinary(host = {}, onStep) {
22576
23185
  }
22577
23186
  try {
22578
23187
  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);
23188
+ (0, import_node_fs22.copyFileSync)(probe.source.path, probe.binPath);
23189
+ if (platform2 !== "win32") (0, import_node_fs22.chmodSync)(probe.binPath, 493);
22581
23190
  } catch (e) {
22582
23191
  if (renamed) {
22583
23192
  try {
22584
- (0, import_node_fs21.renameSync)(aside, probe.binPath);
23193
+ (0, import_node_fs22.renameSync)(aside, probe.binPath);
22585
23194
  } catch {
22586
23195
  return { ok: false, detail: `copy failed (${e.message}) and the stub could not be restored \u2014 the original is at ${aside}` };
22587
23196
  }
@@ -22595,7 +23204,7 @@ function healClaudeBinary(host = {}, onStep) {
22595
23204
  let kept = false;
22596
23205
  if (renamed) {
22597
23206
  try {
22598
- (0, import_node_fs21.rmSync)(aside);
23207
+ (0, import_node_fs22.rmSync)(aside);
22599
23208
  } catch {
22600
23209
  kept = true;
22601
23210
  }
@@ -22960,6 +23569,7 @@ function trainPlan(command, options = {}) {
22960
23569
  { label: "verify current branch is development", gated: true },
22961
23570
  { label: "verify registry META for this project", command: "mmi-cli oracle org project get <owner/repo>", gated: true },
22962
23571
  { label: "preflight required main secret names", command: "mmi-cli vault secrets preflight --stage main --repo <owner/repo>", gated: true },
23572
+ { 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
23573
  { label: "merge development to main", gated: true },
22964
23574
  { 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
23575
  { label: "tag release and publish GitHub Release", gated: true },
@@ -22975,6 +23585,7 @@ function trainPlan(command, options = {}) {
22975
23585
  { 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
23586
  { label: "verify registry META for this project", command: "mmi-cli oracle org project get <owner/repo>", gated: true },
22977
23587
  { label: "preflight required main secret names", command: "mmi-cli vault secrets preflight --stage main --repo <owner/repo>", gated: true },
23588
+ { 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
23589
  { label: "merge development to main (rc skipped)", gated: true },
22979
23590
  { label: "fold the version bump into the release commit \u2014 runs inside the apply step, no separate bump PR", gated: true },
22980
23591
  { label: "tag release and publish GitHub Release", gated: true },
@@ -22989,6 +23600,7 @@ function trainPlan(command, options = {}) {
22989
23600
  { label: "verify current branch is rc", gated: true },
22990
23601
  { label: "verify registry META for this project", command: "mmi-cli oracle org project get <owner/repo>", gated: true },
22991
23602
  { label: "preflight required main secret names", command: "mmi-cli vault secrets preflight --stage main --repo <owner/repo>", gated: true },
23603
+ { 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
23604
  { 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
23605
  { label: "merge rc to main", gated: true },
22994
23606
  { 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 +23689,14 @@ function renderVerifyBroker(input) {
23077
23689
 
23078
23690
  // src/tenant-artifact.ts
23079
23691
  var import_node_crypto5 = require("node:crypto");
23080
- var import_node_fs22 = require("node:fs");
23692
+ var import_node_fs23 = require("node:fs");
23081
23693
  var import_promises3 = require("node:fs/promises");
23082
23694
  var import_node_path20 = require("node:path");
23083
23695
  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
23696
  var MAX_BYTES = 5 * 1024 * 1024 * 1024;
23085
23697
  async function sha256File(path2) {
23086
23698
  const hash = (0, import_node_crypto5.createHash)("sha256");
23087
- for await (const chunk of (0, import_node_fs22.createReadStream)(path2)) hash.update(chunk);
23699
+ for await (const chunk of (0, import_node_fs23.createReadStream)(path2)) hash.update(chunk);
23088
23700
  return hash.digest("hex");
23089
23701
  }
23090
23702
  async function putTenantArtifact(repo, stage, inputPath, deps) {
@@ -23093,8 +23705,8 @@ async function putTenantArtifact(repo, stage, inputPath, deps) {
23093
23705
  const info = await (0, import_promises3.stat)(path2);
23094
23706
  if (!info.isFile()) throw new Error("tenant artifact put: input path must be a file");
23095
23707
  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);
23708
+ const sha2564 = await sha256File(path2);
23709
+ const prepared = await tenantArtifactUpload({ repo, stage, size: info.size, sha256: sha2564 }, deps);
23098
23710
  if (!prepared.ok) {
23099
23711
  const detail = prepared.body?.error ?? prepared.error ?? `HTTP ${prepared.status}`;
23100
23712
  throw new Error(`tenant artifact put: ${detail}`);
@@ -23107,7 +23719,7 @@ async function putTenantArtifact(repo, stage, inputPath, deps) {
23107
23719
  return [key, value];
23108
23720
  }));
23109
23721
  headers["content-length"] = String(info.size);
23110
- const stream = (0, import_node_fs22.createReadStream)(path2);
23722
+ const stream = (0, import_node_fs23.createReadStream)(path2);
23111
23723
  let uploaded;
23112
23724
  try {
23113
23725
  uploaded = await fetch(body.uploadUrl, {
@@ -23122,8 +23734,8 @@ async function putTenantArtifact(repo, stage, inputPath, deps) {
23122
23734
  throw error;
23123
23735
  }
23124
23736
  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 };
23737
+ 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");
23738
+ return { artifactId: body.artifactId, repo, stage, size: info.size, sha256: sha2564, expiresAt: body.expiresAt };
23127
23739
  }
23128
23740
 
23129
23741
  // src/hotfix-coverage.ts
@@ -23309,7 +23921,7 @@ function clean3(out) {
23309
23921
  return out.trim();
23310
23922
  }
23311
23923
  function sleeper(deps) {
23312
- return deps.sleep ?? ((ms) => new Promise((resolve5) => setTimeout(resolve5, ms)));
23924
+ return deps.sleep ?? ((ms) => new Promise((resolve6) => setTimeout(resolve6, ms)));
23313
23925
  }
23314
23926
  function normalizeHotfixVersion(input) {
23315
23927
  const m = /^v?(\d+\.\d+\.\d+)$/.exec(input.trim());
@@ -24304,7 +24916,7 @@ function renderDeployPortDoctor(report) {
24304
24916
  // src/repo-index.ts
24305
24917
  var import_node_crypto6 = require("node:crypto");
24306
24918
  var import_node_child_process12 = require("node:child_process");
24307
- var import_node_fs23 = require("node:fs");
24919
+ var import_node_fs24 = require("node:fs");
24308
24920
  var import_node_path21 = require("node:path");
24309
24921
 
24310
24922
  // ../infra/repo-index-path-policy.mjs
@@ -24505,10 +25117,10 @@ function loadReadmeHints(cwd, candidatePaths) {
24505
25117
  for (const rel of readmes) {
24506
25118
  if (isHardDeniedPath(rel)) continue;
24507
25119
  const abs = (0, import_node_path21.join)(cwd, ...rel.split("/"));
24508
- if (!(0, import_node_fs23.existsSync)(abs)) continue;
25120
+ if (!(0, import_node_fs24.existsSync)(abs)) continue;
24509
25121
  let text;
24510
25122
  try {
24511
- text = (0, import_node_fs23.readFileSync)(abs, "utf8");
25123
+ text = (0, import_node_fs24.readFileSync)(abs, "utf8");
24512
25124
  } catch {
24513
25125
  continue;
24514
25126
  }
@@ -24544,10 +25156,10 @@ function rebuildRepoIndex(cwd, repoSlug3) {
24544
25156
  if (ignored.has(rel)) continue;
24545
25157
  if (isHardDeniedPath(rel)) continue;
24546
25158
  const abs = (0, import_node_path21.join)(cwd, ...rel.split("/"));
24547
- if (!(0, import_node_fs23.existsSync)(abs)) continue;
25159
+ if (!(0, import_node_fs24.existsSync)(abs)) continue;
24548
25160
  let text;
24549
25161
  try {
24550
- text = (0, import_node_fs23.readFileSync)(abs, "utf8");
25162
+ text = (0, import_node_fs24.readFileSync)(abs, "utf8");
24551
25163
  } catch {
24552
25164
  continue;
24553
25165
  }
@@ -24572,16 +25184,16 @@ function rebuildRepoIndex(cwd, repoSlug3) {
24572
25184
  entries
24573
25185
  };
24574
25186
  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)}
25187
+ (0, import_node_fs24.mkdirSync)((0, import_node_path21.dirname)(store), { recursive: true });
25188
+ (0, import_node_fs24.writeFileSync)(store, `${JSON.stringify(projection, null, 2)}
24577
25189
  `, "utf8");
24578
25190
  return projection;
24579
25191
  }
24580
25192
  function loadRepoIndex(cwd) {
24581
25193
  const store = repoIndexStorePath(cwd);
24582
- if (!(0, import_node_fs23.existsSync)(store)) return null;
25194
+ if (!(0, import_node_fs24.existsSync)(store)) return null;
24583
25195
  try {
24584
- const raw = JSON.parse((0, import_node_fs23.readFileSync)(store, "utf8"));
25196
+ const raw = JSON.parse((0, import_node_fs24.readFileSync)(store, "utf8"));
24585
25197
  if (raw?.schema !== REPO_INDEX_SCHEMA || !Array.isArray(raw.entries)) return null;
24586
25198
  return raw;
24587
25199
  } catch {
@@ -24665,8 +25277,8 @@ init_compat();
24665
25277
  // src/repo-index-v4/builder.ts
24666
25278
  var import_node_crypto9 = require("node:crypto");
24667
25279
  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");
25280
+ var import_node_fs26 = require("node:fs");
25281
+ var import_node_os13 = require("node:os");
24670
25282
  var import_node_path23 = require("node:path");
24671
25283
 
24672
25284
  // ../infra/repo-index-material-buckets.mjs
@@ -24766,7 +25378,7 @@ function buildRepoIndexMaterialLayout(repo, chunks, embeddings) {
24766
25378
 
24767
25379
  // src/repo-index-v4/chunks.ts
24768
25380
  var import_node_crypto8 = require("node:crypto");
24769
- var import_node_fs24 = require("node:fs");
25381
+ var import_node_fs25 = require("node:fs");
24770
25382
  var import_node_path22 = require("node:path");
24771
25383
 
24772
25384
  // src/repo-index-v4/language.ts
@@ -24932,10 +25544,10 @@ async function buildStructuralChunksForPaths(cwd, repo, commit, paths) {
24932
25544
  const chunks = [];
24933
25545
  for (const path2 of paths) {
24934
25546
  const absolute = (0, import_node_path22.join)(cwd, ...path2.split("/"));
24935
- if (!(0, import_node_fs24.existsSync)(absolute)) continue;
25547
+ if (!(0, import_node_fs25.existsSync)(absolute)) continue;
24936
25548
  let source;
24937
25549
  try {
24938
- source = (0, import_node_fs24.readFileSync)(absolute, "utf8");
25550
+ source = (0, import_node_fs25.readFileSync)(absolute, "utf8");
24939
25551
  } catch {
24940
25552
  continue;
24941
25553
  }
@@ -24997,7 +25609,8 @@ function planRepoIndexV4Delta(opts) {
24997
25609
  const baseCommit = opts.baseCommit ? opts.baseCommit.toLowerCase() : null;
24998
25610
  const full = (fallbackReason) => ({ mode: "full", headCommit, fallbackReason, ...baseCommit ? { baseCommit } : {} });
24999
25611
  if (opts.forceFull) return full("explicit-full-rebuild");
25000
- if (!baseCommit || !COMMIT.test(baseCommit) || baseCommit === headCommit) return full("no-active-authority");
25612
+ if (!baseCommit || !COMMIT.test(baseCommit)) return full("no-active-authority");
25613
+ if (baseCommit === headCommit) return { mode: "unchanged", baseCommit, headCommit };
25001
25614
  if (opts.basePipelineCompatible === false) return full("incompatible-base-provenance");
25002
25615
  if (opts.hasPriorMaterial === false) return full("no-prior-material");
25003
25616
  const { git: git3 } = opts;
@@ -25063,7 +25676,7 @@ function gitInfo(cwd) {
25063
25676
  }
25064
25677
  function prior(cwd) {
25065
25678
  try {
25066
- const p = JSON.parse((0, import_node_fs25.readFileSync)(statePath(cwd), "utf8"));
25679
+ const p = JSON.parse((0, import_node_fs26.readFileSync)(statePath(cwd), "utf8"));
25067
25680
  return p?.schemaVersion === 4 && p?.manifest?.immutable === true ? p : null;
25068
25681
  } catch {
25069
25682
  return null;
@@ -25082,7 +25695,7 @@ function embeddingInput(cwd, chunk) {
25082
25695
  ${chunk.symbol ?? ""}
25083
25696
  ${chunk.blurb ?? ""}`;
25084
25697
  try {
25085
- const lines = (0, import_node_fs25.readFileSync)((0, import_node_path23.join)(cwd, ...chunk.path.split("/")), "utf8").split(/\r?\n/);
25698
+ const lines = (0, import_node_fs26.readFileSync)((0, import_node_path23.join)(cwd, ...chunk.path.split("/")), "utf8").split(/\r?\n/);
25086
25699
  const body = lines.slice(Math.max(0, (c.startLine ?? 1) - 1), Math.min(lines.length, c.endLine ?? lines.length)).join("\n");
25087
25700
  return body.slice(0, 1e5);
25088
25701
  } catch {
@@ -25097,18 +25710,18 @@ function runEmbedderOnce(cwd, chunks, modelDirectory, createdAt) {
25097
25710
  if (!chunks.length) return { ok: true, embeddings: [] };
25098
25711
  const orchestratorRunner = (0, import_node_path23.join)(process.cwd(), "repo-indexer", "src", "batch.mjs");
25099
25712
  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" };
25713
+ const file = (0, import_node_fs26.existsSync)(orchestratorRunner) ? orchestratorRunner : targetRunner;
25714
+ if (!(0, import_node_fs26.existsSync)(file)) return { ok: false, reason: "embeddings-unavailable" };
25102
25715
  const request = { texts: chunks.map((chunk) => ({ id: chunk.id, text: embeddingInput(cwd, chunk) })), maxBatch: V4_EMBED_BATCH };
25103
25716
  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-"));
25717
+ const requestDir = (0, import_node_fs26.mkdtempSync)((0, import_node_path23.join)((0, import_node_os13.tmpdir)(), "mmi-repo-index-req-"));
25105
25718
  const requestFile = (0, import_node_path23.join)(requestDir, "request.json");
25106
- (0, import_node_fs25.writeFileSync)(requestFile, JSON.stringify(request));
25719
+ (0, import_node_fs26.writeFileSync)(requestFile, JSON.stringify(request));
25107
25720
  let result;
25108
25721
  try {
25109
25722
  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
25723
  } finally {
25111
- (0, import_node_fs25.rmSync)(requestDir, { recursive: true, force: true });
25724
+ (0, import_node_fs26.rmSync)(requestDir, { recursive: true, force: true });
25112
25725
  }
25113
25726
  if (result.error || result.status !== 0) {
25114
25727
  const cleanExit2 = result.error === void 0 && result.signal === void 0 && typeof result.status === "number" && result.status !== 0;
@@ -25241,8 +25854,8 @@ async function buildRepoIndexV4Detailed(cwd, repo, opts = {}) {
25241
25854
  const encoded = canonicalJson(envelope);
25242
25855
  if (Buffer.byteLength(encoded) > V4_MAX_ARTIFACT_BYTES) throw new Error(`repo-index v4 artifact exceeds ${V4_MAX_ARTIFACT_BYTES} byte ceiling`);
25243
25856
  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)}
25857
+ (0, import_node_fs26.mkdirSync)((0, import_node_path23.dirname)(path2), { recursive: true });
25858
+ (0, import_node_fs26.writeFileSync)(path2, `${JSON.stringify(envelope, null, 2)}
25246
25859
  `, "utf8");
25247
25860
  const metrics = {
25248
25861
  mode: delta ? "delta" : "full",
@@ -25793,8 +26406,8 @@ async function gcRepoIndexCloud(deps) {
25793
26406
  }
25794
26407
 
25795
26408
  // src/repo-index-sync.ts
25796
- var import_node_fs27 = require("node:fs");
25797
- var import_node_os13 = require("node:os");
26409
+ var import_node_fs28 = require("node:fs");
26410
+ var import_node_os14 = require("node:os");
25798
26411
  var import_node_path25 = require("node:path");
25799
26412
  var import_node_child_process15 = require("node:child_process");
25800
26413
 
@@ -25834,7 +26447,7 @@ function repoIndexRoster(projects) {
25834
26447
  }
25835
26448
 
25836
26449
  // src/repo-index-v4/edges.ts
25837
- var import_node_fs26 = require("node:fs");
26450
+ var import_node_fs27 = require("node:fs");
25838
26451
  var import_node_path24 = require("node:path");
25839
26452
  var V4_GRAPH_MAX_EDGES = 5e3;
25840
26453
  var V4_GRAPH_MAX_EDGES_PER_FILE = 64;
@@ -25924,9 +26537,9 @@ function buildGraphEdges(cwd, repo, commit, rosterRepos2) {
25924
26537
  for (const path2 of paths) {
25925
26538
  if (ignored.has(path2) || isHardDeniedPath(path2) || !SUPPORTED.has(extension(path2))) continue;
25926
26539
  const absolute = (0, import_node_path24.join)(cwd, ...path2.split("/"));
25927
- if (!(0, import_node_fs26.existsSync)(absolute)) continue;
26540
+ if (!(0, import_node_fs27.existsSync)(absolute)) continue;
25928
26541
  try {
25929
- edges.push(...graphEdgesForSource(repo, commit, path2, (0, import_node_fs26.readFileSync)(absolute, "utf8"), rosterRepos2));
26542
+ edges.push(...graphEdgesForSource(repo, commit, path2, (0, import_node_fs27.readFileSync)(absolute, "utf8"), rosterRepos2));
25930
26543
  } catch {
25931
26544
  }
25932
26545
  if (edges.length >= V4_GRAPH_MAX_EDGES) break;
@@ -25936,10 +26549,10 @@ function buildGraphEdges(cwd, repo, commit, rosterRepos2) {
25936
26549
 
25937
26550
  // src/repo-index-sync.ts
25938
26551
  function execFileUtf8(file, args) {
25939
- return new Promise((resolve5, reject) => {
26552
+ return new Promise((resolve6, reject) => {
25940
26553
  (0, import_node_child_process15.execFile)(file, args, { encoding: "utf8", windowsHide: true }, (error, stdout) => {
25941
26554
  if (error) reject(error);
25942
- else resolve5(String(stdout ?? ""));
26555
+ else resolve6(String(stdout ?? ""));
25943
26556
  });
25944
26557
  });
25945
26558
  }
@@ -26135,11 +26748,12 @@ async function syncEstateRepoIndex(opts) {
26135
26748
  }
26136
26749
  let targetCommit = requestedCommit || void 0;
26137
26750
  if (base && !forceFull) {
26751
+ let headUnreadable = false;
26138
26752
  if (!targetCommit) {
26139
26753
  try {
26140
26754
  targetCommit = await remoteHead(repo, opts.githubToken);
26141
26755
  } catch {
26142
- targetCommit = void 0;
26756
+ headUnreadable = true;
26143
26757
  }
26144
26758
  }
26145
26759
  const provenance = await fetchRepoIndexV4ProvenanceCloud(repo, opts.deps).catch(
@@ -26163,6 +26777,12 @@ async function syncEstateRepoIndex(opts) {
26163
26777
  emit({ row: row2, warning });
26164
26778
  return { repo, row: row2, warning, base, expectedActiveDigest };
26165
26779
  }
26780
+ if (headUnreadable) {
26781
+ const row2 = { repo, reason: "head-unreadable", action: "skip", activeCommit: base.commit };
26782
+ const warning = `${repo}: remote HEAD unreadable; leaving verified-ready authority at ${base.commit}`;
26783
+ emit({ row: row2, warning });
26784
+ return { repo, row: row2, warning, base, expectedActiveDigest };
26785
+ }
26166
26786
  }
26167
26787
  const row = {
26168
26788
  repo,
@@ -26183,13 +26803,17 @@ async function syncEstateRepoIndex(opts) {
26183
26803
  for (const entry of classified) {
26184
26804
  if (entry.row.action !== "build") continue;
26185
26805
  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-"));
26806
+ const dir = (0, import_node_fs28.mkdtempSync)((0, import_node_path25.join)((0, import_node_os14.tmpdir)(), "mmi-repo-index-"));
26187
26807
  try {
26188
26808
  shallowClone(repo, dir, opts.githubToken);
26189
26809
  if (requestedCommit) checkoutExactCommit(repo, dir, opts.githubToken, requestedCommit);
26190
26810
  let v4;
26191
26811
  try {
26192
26812
  const plan = await planDeltaBuild(repo, dir, base, opts.deps, opts.githubToken, forceFull);
26813
+ if (plan.plan.mode === "unchanged") {
26814
+ skipped.push(`${repo}: unchanged verified-ready authority at ${plan.plan.headCommit}`);
26815
+ continue;
26816
+ }
26193
26817
  if (base && plan.plan.mode === "full") skipped.push(`${repo}: full rebuild (${plan.plan.fallbackReason})`);
26194
26818
  v4 = await buildRepoIndexV4Detailed(dir, repo, {
26195
26819
  modelDirectory: process.env.MMI_REPO_INDEXER_MODEL_DIR,
@@ -26233,7 +26857,7 @@ async function syncEstateRepoIndex(opts) {
26233
26857
  failed.push({ repo, error: e.message });
26234
26858
  } finally {
26235
26859
  try {
26236
- (0, import_node_fs27.rmSync)(dir, { recursive: true, force: true });
26860
+ (0, import_node_fs28.rmSync)(dir, { recursive: true, force: true });
26237
26861
  } catch {
26238
26862
  }
26239
26863
  }
@@ -26242,7 +26866,7 @@ async function syncEstateRepoIndex(opts) {
26242
26866
  }
26243
26867
 
26244
26868
  // src/repo-index-health.ts
26245
- var import_node_fs28 = require("node:fs");
26869
+ var import_node_fs29 = require("node:fs");
26246
26870
 
26247
26871
  // testdata/repo-index-golden-queries.json
26248
26872
  var repo_index_golden_queries_default = {
@@ -26346,7 +26970,7 @@ function assertGoldenSuite(raw, source) {
26346
26970
  function loadGoldenSuite(path2) {
26347
26971
  let text;
26348
26972
  try {
26349
- text = (0, import_node_fs28.readFileSync)(path2, "utf8");
26973
+ text = (0, import_node_fs29.readFileSync)(path2, "utf8");
26350
26974
  } catch (e) {
26351
26975
  throw new Error(`golden suite unreadable at ${path2}: ${e.message}`);
26352
26976
  }
@@ -26516,7 +27140,7 @@ async function runRepoIndexHealth(opts) {
26516
27140
 
26517
27141
  // src/spawn-policy-core.ts
26518
27142
  var import_node_child_process16 = require("node:child_process");
26519
- var import_node_fs29 = require("node:fs");
27143
+ var import_node_fs30 = require("node:fs");
26520
27144
  var import_node_path26 = require("node:path");
26521
27145
  var SPAWNERS = ["spawn", "spawnSync", "exec", "execSync", "execFile", "execFileSync"];
26522
27146
  var CALL_SOURCE = String.raw`(^|[^.\w$])(${SPAWNERS.join("|")})\s*\(`;
@@ -26603,7 +27227,7 @@ function runSpawnPolicy(root) {
26603
27227
  for (const file of files) {
26604
27228
  let raw;
26605
27229
  try {
26606
- raw = (0, import_node_fs29.readFileSync)((0, import_node_path26.join)(root, file), "utf8");
27230
+ raw = (0, import_node_fs30.readFileSync)((0, import_node_path26.join)(root, file), "utf8");
26607
27231
  } catch {
26608
27232
  continue;
26609
27233
  }
@@ -26621,7 +27245,7 @@ function runSpawnPolicy(root) {
26621
27245
 
26622
27246
  // src/test-policy-core.ts
26623
27247
  var import_node_child_process17 = require("node:child_process");
26624
- var import_node_fs30 = require("node:fs");
27248
+ var import_node_fs31 = require("node:fs");
26625
27249
  var import_node_path27 = require("node:path");
26626
27250
 
26627
27251
  // ../scripts/test-command-policy-core.mjs
@@ -27002,7 +27626,7 @@ function loadPolicy(root, readFile7 = readFileOrNull2) {
27002
27626
  }
27003
27627
  function readFileOrNull2(path2) {
27004
27628
  try {
27005
- return (0, import_node_fs30.readFileSync)(path2, "utf8");
27629
+ return (0, import_node_fs31.readFileSync)(path2, "utf8");
27006
27630
  } catch {
27007
27631
  return null;
27008
27632
  }
@@ -27030,10 +27654,10 @@ function classify(changed, policy, present = () => false) {
27030
27654
  const removedProtected = [...removed].filter((p) => protectedBy.has(p)).map((p) => ({ path: p, why: protectedBy.get(p) ?? "" }));
27031
27655
  return { mandatoryHits, untestedHits, testChanges, meaningfulTestChanges, addedTests, removedProtected };
27032
27656
  }
27033
- function unresolvedProtectedEntries(policy, root, exists = (path2) => (0, import_node_fs30.existsSync)(path2)) {
27657
+ function unresolvedProtectedEntries(policy, root, exists = (path2) => (0, import_node_fs31.existsSync)(path2)) {
27034
27658
  return (policy.protected ?? []).map((p) => p.path).filter((p) => !exists((0, import_node_path27.join)(root, p)));
27035
27659
  }
27036
- function unresolvedSatisfiers(policy, root, exists = (path2) => (0, import_node_fs30.existsSync)(path2)) {
27660
+ function unresolvedSatisfiers(policy, root, exists = (path2) => (0, import_node_fs31.existsSync)(path2)) {
27037
27661
  const declared = (policy.mandatory ?? []).flatMap((m) => m.satisfiedBy ?? []);
27038
27662
  return [...new Set(declared)].filter((p) => !exists((0, import_node_path27.join)(root, p)));
27039
27663
  }
@@ -27224,7 +27848,7 @@ function blobAt(base, path2, cwd) {
27224
27848
  }
27225
27849
  function runTestPolicy(root, deps = {}) {
27226
27850
  const policy = deps.policy ?? loadPolicy(root);
27227
- const exists = deps.exists ?? ((path2) => (0, import_node_fs30.existsSync)(path2));
27851
+ const exists = deps.exists ?? ((path2) => (0, import_node_fs31.existsSync)(path2));
27228
27852
  const counts = { mandatoryCount: (policy.mandatory ?? []).length, protectedCount: (policy.protected ?? []).length };
27229
27853
  const base = deps.changed ? "(injected)" : resolveBase(root, deps.base);
27230
27854
  const refusal = deps.changed ? null : untrustworthyRange(root, base);
@@ -27287,9 +27911,197 @@ function runTestPolicy(root, deps = {}) {
27287
27911
  return result;
27288
27912
  }
27289
27913
 
27290
- // src/project-info-sync.ts
27291
- var import_node_fs31 = require("node:fs");
27914
+ // src/dist-drift.ts
27915
+ var import_node_child_process18 = require("node:child_process");
27916
+ var import_node_crypto12 = require("node:crypto");
27917
+ var import_node_fs33 = require("node:fs");
27918
+ var import_node_os15 = require("node:os");
27919
+ var import_node_path29 = require("node:path");
27920
+
27921
+ // ../scripts/distribution-digest.mjs
27922
+ var import_node_crypto11 = require("node:crypto");
27923
+ var import_node_fs32 = require("node:fs");
27292
27924
  var import_node_path28 = require("node:path");
27925
+ var slash = (value) => value.replaceAll("\\", "/");
27926
+ function repoPath(root, declaredPath, label) {
27927
+ const absoluteRoot = (0, import_node_path28.resolve)(root);
27928
+ const target = (0, import_node_path28.resolve)(root, declaredPath);
27929
+ if (target !== absoluteRoot && !target.startsWith(`${absoluteRoot}${import_node_path28.sep}`)) {
27930
+ throw new Error(`${label} ${declaredPath} escapes the repository root`);
27931
+ }
27932
+ return target;
27933
+ }
27934
+ function digestFiles(files) {
27935
+ const hash = (0, import_node_crypto11.createHash)("sha256");
27936
+ for (const file of [...files].sort((a, b) => a.relative.localeCompare(b.relative))) {
27937
+ const content = file.stat.isSymbolicLink() ? Buffer.from((0, import_node_fs32.readlinkSync)(file.absolute), "utf8") : (0, import_node_fs32.readFileSync)(file.absolute);
27938
+ hash.update(file.relative, "utf8");
27939
+ hash.update("\0");
27940
+ hash.update(file.stat.isSymbolicLink() ? "symlink" : "file", "utf8");
27941
+ hash.update("\0");
27942
+ hash.update(String(content.length), "utf8");
27943
+ hash.update("\0");
27944
+ hash.update(content);
27945
+ hash.update("\0");
27946
+ }
27947
+ return `sha256:${hash.digest("hex")}`;
27948
+ }
27949
+ function digestPackedFiles(packageRoot, packedFiles) {
27950
+ return digestFiles(packedFiles.map((path2) => {
27951
+ const absolute = repoPath(packageRoot, path2, "packed artifact identity path");
27952
+ if (!(0, import_node_fs32.existsSync)(absolute)) throw new Error(`packed artifact identity path ${path2} does not exist`);
27953
+ return { absolute, relative: slash(path2), stat: (0, import_node_fs32.lstatSync)(absolute) };
27954
+ }));
27955
+ }
27956
+
27957
+ // src/dist-drift.ts
27958
+ var DIST_ARTIFACTS = [
27959
+ { path: "cli/dist/index.cjs", packageDir: "cli", output: "index.cjs" },
27960
+ { path: "cli/dist/main.cjs", packageDir: "cli", output: "main.cjs" },
27961
+ { path: "cli/dist/repo-index-v4.cjs", packageDir: "cli", output: "repo-index-v4.cjs" },
27962
+ { path: "updater/dist/index.cjs", packageDir: "updater", output: "index.cjs" }
27963
+ ];
27964
+ var BOM_DIST_TREE_ID = "mmi-cli-dist";
27965
+ var ABSENT = "absent";
27966
+ var sha2563 = (bytes) => `sha256:${(0, import_node_crypto12.createHash)("sha256").update(bytes).digest("hex")}`;
27967
+ function artifactDrift(path2, committedBytes, rebuiltBytes) {
27968
+ const committed = committedBytes ? sha2563(committedBytes) : ABSENT;
27969
+ const rebuiltExpected = rebuiltBytes ? sha2563(rebuiltBytes) : ABSENT;
27970
+ return { path: path2, status: committed === rebuiltExpected ? "current" : "stale", committed, rebuiltExpected };
27971
+ }
27972
+ function distTreeIdentity(labels, rebuilt, committed, digest) {
27973
+ const entries = [];
27974
+ for (const label of labels) {
27975
+ const bytes = rebuilt(label) ?? committed(label);
27976
+ if (!bytes) {
27977
+ return { rebuiltExpected: ABSENT, note: `recorded tree file ${label} is absent from the checkout` };
27978
+ }
27979
+ entries.push({ path: label, bytes });
27980
+ }
27981
+ return { rebuiltExpected: digest(entries) };
27982
+ }
27983
+ function npmPackIdentity(id, packageDir, identity, rebuilt, tree, digest) {
27984
+ const recorded = identity?.value ?? ABSENT;
27985
+ const kind = identity?.kind ?? "npm-pack";
27986
+ if (!identity || !Array.isArray(identity.files) || identity.files.length === 0) {
27987
+ return { id, kind, status: "stale", recorded, rebuiltExpected: ABSENT, note: "identity records no packed file list to recompute against" };
27988
+ }
27989
+ const entries = [];
27990
+ for (const label of identity.files) {
27991
+ const repoPath2 = `${packageDir}/${label}`;
27992
+ const bytes = label.startsWith("dist/") ? rebuilt(repoPath2) : tree(repoPath2);
27993
+ if (!bytes) {
27994
+ return { id, kind, status: "stale", recorded, rebuiltExpected: ABSENT, note: `packed file ${repoPath2} is absent from the checkout` };
27995
+ }
27996
+ entries.push({ path: label, bytes });
27997
+ }
27998
+ const rebuiltExpected = digest(entries);
27999
+ return { id, kind, status: recorded === rebuiltExpected ? "current" : "stale", recorded, rebuiltExpected };
28000
+ }
28001
+ function computeDistDriftReceipt(sources) {
28002
+ const artifacts = DIST_ARTIFACTS.map((spec) => artifactDrift(spec.path, sources.committed(spec.path), sources.rebuilt(spec.path)));
28003
+ const bomArtifact = (id) => sources.bom.artifacts?.find((entry) => entry.id === id);
28004
+ const cliDistDeclared = bomArtifact(BOM_DIST_TREE_ID)?.identity;
28005
+ const distTree = distTreeIdentity(sources.distTree(), sources.rebuilt, sources.committed, sources.digest);
28006
+ const distTreeRecorded = cliDistDeclared?.value ?? ABSENT;
28007
+ const identities = [
28008
+ { 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 } : {} },
28009
+ npmPackIdentity("mmi-cli", "cli", bomArtifact("mmi-cli")?.identity, sources.rebuilt, sources.tree, sources.digest),
28010
+ npmPackIdentity("mmi-hub", "updater", bomArtifact("mmi-hub")?.identity, sources.rebuilt, sources.tree, sources.digest)
28011
+ ];
28012
+ const representative = identities[0];
28013
+ const bomStatus = identities.some((identity) => identity.status === "stale") ? "stale" : "current";
28014
+ const staleCount = artifacts.filter((a) => a.status === "stale").length + (bomStatus === "stale" ? 1 : 0);
28015
+ 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";
28016
+ return {
28017
+ artifacts,
28018
+ bom: { status: bomStatus, recorded: representative.recorded, rebuiltExpected: representative.rebuiltExpected, identities },
28019
+ staleCount,
28020
+ summary
28021
+ };
28022
+ }
28023
+ function shortHash(value) {
28024
+ return value.startsWith("sha256:") ? `sha256:${value.slice(7, 23)}` : value;
28025
+ }
28026
+ function renderDistDriftReceipt(receipt) {
28027
+ const lines = [];
28028
+ for (const artifact of receipt.artifacts) {
28029
+ lines.push(`${artifact.path} ${artifact.status} committed=${shortHash(artifact.committed)} rebuilt-expected=${shortHash(artifact.rebuiltExpected)}`);
28030
+ }
28031
+ const line = `distribution-bom.json ${receipt.bom.status} committed=${shortHash(receipt.bom.recorded)} rebuilt-expected=${shortHash(receipt.bom.rebuiltExpected)}`;
28032
+ const otherStale = receipt.bom.identities.filter((identity) => identity.id !== BOM_DIST_TREE_ID && identity.status === "stale");
28033
+ lines.push(otherStale.length > 0 ? `${line} (${otherStale.map((identity) => `${identity.id} identity stale`).join("; ")})` : line);
28034
+ lines.push(receipt.summary);
28035
+ return lines;
28036
+ }
28037
+ function readOrNull(path2) {
28038
+ return (0, import_node_fs33.existsSync)(path2) ? (0, import_node_fs33.readFileSync)(path2) : null;
28039
+ }
28040
+ function walkFiles(root) {
28041
+ const files = [];
28042
+ const walk2 = (directory) => {
28043
+ for (const entry of (0, import_node_fs33.readdirSync)(directory, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
28044
+ const child2 = (0, import_node_path29.join)(directory, entry.name);
28045
+ if (entry.isDirectory()) walk2(child2);
28046
+ else files.push(child2);
28047
+ }
28048
+ };
28049
+ walk2(root);
28050
+ return files;
28051
+ }
28052
+ function bomPathFor(root) {
28053
+ try {
28054
+ const registry2 = JSON.parse((0, import_node_fs33.readFileSync)((0, import_node_path29.join)(root, "surfaces.json"), "utf8"));
28055
+ return (0, import_node_path29.join)(root, registry2?.sharedAgentCore?.releaseMetadata?.bomPath ?? "distribution-bom.json");
28056
+ } catch {
28057
+ return (0, import_node_path29.join)(root, "distribution-bom.json");
28058
+ }
28059
+ }
28060
+ function rebuildTo(packageRoot, outDir) {
28061
+ (0, import_node_child_process18.execFileSync)(process.execPath, ["build.mjs"], {
28062
+ cwd: packageRoot,
28063
+ env: { ...process.env, MMI_DIST_OUTDIR: outDir },
28064
+ windowsHide: true,
28065
+ stdio: "pipe",
28066
+ encoding: "utf8"
28067
+ });
28068
+ }
28069
+ function runDistStatus(root) {
28070
+ const stage = (0, import_node_fs33.mkdtempSync)((0, import_node_path29.join)((0, import_node_os15.tmpdir)(), "mmi-dist-drift-"));
28071
+ let overlayCount = 0;
28072
+ try {
28073
+ const cliOut = (0, import_node_path29.join)(stage, "cli-dist");
28074
+ const hubOut = (0, import_node_path29.join)(stage, "hub-dist");
28075
+ rebuildTo((0, import_node_path29.join)(root, "cli"), cliOut);
28076
+ rebuildTo((0, import_node_path29.join)(root, "updater"), hubOut);
28077
+ const outDirFor = (packageDir) => packageDir === "cli" ? cliOut : hubOut;
28078
+ const rebuilt = (path2) => {
28079
+ const spec = DIST_ARTIFACTS.find((entry) => entry.path === path2);
28080
+ return spec ? readOrNull((0, import_node_path29.join)(outDirFor(spec.packageDir), spec.output)) : null;
28081
+ };
28082
+ const committed = (path2) => readOrNull((0, import_node_path29.join)(root, path2));
28083
+ const tree = (path2) => readOrNull((0, import_node_path29.join)(root, path2));
28084
+ const distRoot = (0, import_node_path29.join)(root, "cli", "dist");
28085
+ const distTree = () => walkFiles(distRoot).map((absolute) => `cli/dist/${(0, import_node_path29.relative)(distRoot, absolute).replaceAll("\\", "/")}`);
28086
+ const bom = JSON.parse((0, import_node_fs33.readFileSync)(bomPathFor(root), "utf8"));
28087
+ const digest = (entries) => {
28088
+ const overlay = (0, import_node_path29.join)(stage, `overlay-${overlayCount++}`);
28089
+ for (const entry of entries) {
28090
+ const target = (0, import_node_path29.join)(overlay, entry.path);
28091
+ (0, import_node_fs33.mkdirSync)((0, import_node_path29.dirname)(target), { recursive: true });
28092
+ (0, import_node_fs33.writeFileSync)(target, entry.bytes);
28093
+ }
28094
+ return digestPackedFiles(overlay, entries.map((entry) => entry.path));
28095
+ };
28096
+ return computeDistDriftReceipt({ committed, tree, rebuilt, distTree, bom, digest });
28097
+ } finally {
28098
+ (0, import_node_fs33.rmSync)(stage, { recursive: true, force: true });
28099
+ }
28100
+ }
28101
+
28102
+ // src/project-info-sync.ts
28103
+ var import_node_fs34 = require("node:fs");
28104
+ var import_node_path30 = require("node:path");
27293
28105
  var UPDATE_PROJECT_INFO = `mutation($projectId: ID!, $shortDescription: String!, $readme: String!) {
27294
28106
  updateProjectV2(input: { projectId: $projectId, shortDescription: $shortDescription, readme: $readme }) {
27295
28107
  projectV2 { id }
@@ -27334,14 +28146,14 @@ function sharedName(entries, fallback) {
27334
28146
  }
27335
28147
  function buildProjectInfoSyncPlan(targetRepo3, project2, projects, repoRoot2) {
27336
28148
  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`);
28149
+ const readmePath = (0, import_node_path30.join)(repoRoot2, "README.md");
28150
+ if (!(0, import_node_fs34.existsSync)(readmePath)) throw new Error(`org project sync-info: ${targetRepo3} has no README.md`);
27339
28151
  const entries = entriesFor(project2, projects);
27340
28152
  const memberRepos = [...new Set(entries.flatMap((entry) => entry.repos ?? []))].filter((repo) => /^[^/]+\/[^/]+$/.test(repo)).sort((a, b) => a.localeCompare(b));
27341
28153
  const projectName = sharedName(entries, project2.name?.trim() || targetRepo3.split("/").pop() || targetRepo3);
27342
28154
  if (!memberRepos.length) throw new Error(`org project sync-info: project ${projectName} has no registered member repos`);
27343
28155
  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)}.`;
28156
+ 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
28157
  const lines = [
27346
28158
  `# ${projectName}`,
27347
28159
  "",
@@ -27360,8 +28172,8 @@ function buildProjectInfoSyncPlan(targetRepo3, project2, projects, repoRoot2) {
27360
28172
  const targetBase = `https://github.com/${targetRepo3}`;
27361
28173
  const targetBranch = branchFor(targetRepo3, projects);
27362
28174
  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)` : ""
28175
+ (0, import_node_fs34.existsSync)((0, import_node_path30.join)(repoRoot2, "docs", "org-readme.md")) ? `- [Org identity](${targetBase}/blob/${targetBranch}/docs/org-readme.md)` : "",
28176
+ (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
28177
  ].filter(Boolean);
27366
28178
  if (orgDocs.length) lines.push("", "## Organisation docs", "", ...orgDocs);
27367
28179
  return { projectId: project2.projectId, projectName, targetRepo: targetRepo3, memberRepos, shortDescription, readme: `${lines.join("\n")}
@@ -28209,9 +29021,9 @@ function writeError(res) {
28209
29021
  }
28210
29022
 
28211
29023
  // 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");
29024
+ var import_node_fs35 = require("node:fs");
29025
+ var import_node_path31 = require("node:path");
29026
+ var import_node_os16 = require("node:os");
28215
29027
  init_cli_shared();
28216
29028
  init_hub_auth();
28217
29029
  init_github_client();
@@ -28316,18 +29128,18 @@ function collectMap(value, previous = []) {
28316
29128
  return [...previous, value];
28317
29129
  }
28318
29130
  async function decryptRailsCredentials(input) {
28319
- const appDir = (0, import_node_path29.resolve)(input.appDir ?? process.cwd());
29131
+ const appDir = (0, import_node_path31.resolve)(input.appDir ?? process.cwd());
28320
29132
  const credentialsFile = input.credentialsFile ?? DEFAULT_RAILS_CREDENTIALS_FILE;
28321
29133
  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);
29134
+ const credentialsPath = (0, import_node_path31.resolve)(appDir, credentialsFile);
29135
+ const masterKeyPath = (0, import_node_path31.resolve)(appDir, masterKeyFile);
28324
29136
  const env = {
28325
29137
  ...process.env,
28326
29138
  MMI_RAILS_CREDENTIALS_FILE: credentialsPath,
28327
29139
  MMI_RAILS_MASTER_KEY_FILE: masterKeyPath
28328
29140
  };
28329
- if ((0, import_node_fs32.existsSync)(masterKeyPath)) {
28330
- env.RAILS_MASTER_KEY = (0, import_node_fs32.readFileSync)(masterKeyPath, "utf8").trim();
29141
+ if ((0, import_node_fs35.existsSync)(masterKeyPath)) {
29142
+ env.RAILS_MASTER_KEY = (0, import_node_fs35.readFileSync)(masterKeyPath, "utf8").trim();
28331
29143
  }
28332
29144
  const script = [
28333
29145
  'require "json"',
@@ -28337,9 +29149,9 @@ async function decryptRailsCredentials(input) {
28337
29149
  'config = ActiveSupport::EncryptedConfiguration.new(config_path: config_path, key_path: key_path, env_key: "RAILS_MASTER_KEY", raise_if_missing_key: true)',
28338
29150
  "puts JSON.generate(config.config)"
28339
29151
  ].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");
29152
+ const scriptDir = (0, import_node_fs35.mkdtempSync)((0, import_node_path31.join)((0, import_node_os16.tmpdir)(), "mmi-rails-decrypt-"));
29153
+ const scriptPath = (0, import_node_path31.join)(scriptDir, "decrypt.rb");
29154
+ (0, import_node_fs35.writeFileSync)(scriptPath, script, "utf8");
28343
29155
  try {
28344
29156
  const args = ["exec", "ruby", scriptPath];
28345
29157
  const cmd = process.platform === "win32" ? "cmd.exe" : "bundle";
@@ -28351,7 +29163,7 @@ async function decryptRailsCredentials(input) {
28351
29163
  });
28352
29164
  return JSON.parse(stdout);
28353
29165
  } finally {
28354
- (0, import_node_fs32.rmSync)(scriptDir, { recursive: true, force: true });
29166
+ (0, import_node_fs35.rmSync)(scriptDir, { recursive: true, force: true });
28355
29167
  }
28356
29168
  }
28357
29169
  async function readSecretStdin() {
@@ -28441,7 +29253,7 @@ function registerSecretsCommands(program3) {
28441
29253
  let body;
28442
29254
  if (o.file) {
28443
29255
  try {
28444
- body = (0, import_node_fs32.readFileSync)((0, import_node_path29.resolve)(o.file), "utf8");
29256
+ body = (0, import_node_fs35.readFileSync)((0, import_node_path31.resolve)(o.file), "utf8");
28445
29257
  } catch (e) {
28446
29258
  return fail(`secrets org-catalog: cannot read --file ${o.file}: ${e.message}`);
28447
29259
  }
@@ -28546,7 +29358,7 @@ function registerSecretsCommands(program3) {
28546
29358
  {
28547
29359
  ...d,
28548
29360
  decryptRailsCredentials,
28549
- removeFile: (path2) => (0, import_node_fs32.unlinkSync)((0, import_node_path29.resolve)(o.appDir ?? process.cwd(), path2))
29361
+ removeFile: (path2) => (0, import_node_fs35.unlinkSync)((0, import_node_path31.resolve)(o.appDir ?? process.cwd(), path2))
28550
29362
  },
28551
29363
  {
28552
29364
  repo: o.repo,
@@ -28591,7 +29403,7 @@ function registerSecretsCommands(program3) {
28591
29403
  }
28592
29404
 
28593
29405
  // src/app-actor.ts
28594
- var import_node_crypto11 = require("node:crypto");
29406
+ var import_node_crypto13 = require("node:crypto");
28595
29407
  var APP_ACTOR_ENV = "MMI_ACTOR";
28596
29408
  var APP_VAULT_REPO = "mutmutco/MMI-Hub";
28597
29409
  var APP_VAULT_KEYS = ["GITHUB_APP_ID", "GITHUB_APP_INSTALLATION_ID", "GITHUB_APP_PRIVATE_KEY"];
@@ -28635,7 +29447,7 @@ function mintAppJwt(appId, privateKeyPem, nowSec) {
28635
29447
  exp: now + APP_JWT_TTL_S,
28636
29448
  iss: appId
28637
29449
  }));
28638
- const signer = (0, import_node_crypto11.createSign)("RSA-SHA256");
29450
+ const signer = (0, import_node_crypto13.createSign)("RSA-SHA256");
28639
29451
  signer.update(`${header}.${payload}`);
28640
29452
  return `${header}.${payload}.${signer.sign(privateKeyPem, "base64url")}`;
28641
29453
  }
@@ -28753,7 +29565,7 @@ function emitCliCallTelemetry(command) {
28753
29565
  }
28754
29566
 
28755
29567
  // src/box-commands.ts
28756
- var import_node_fs33 = require("node:fs");
29568
+ var import_node_fs36 = require("node:fs");
28757
29569
  init_clean_exit();
28758
29570
 
28759
29571
  // src/box.ts
@@ -28957,7 +29769,7 @@ function registerBoxCommands(program3) {
28957
29769
  }
28958
29770
  if (o.json) console.log(JSON.stringify({ box: found, incomplete }, null, 2));
28959
29771
  else if (o.ssh && o.script) {
28960
- (0, import_node_fs33.writeFileSync)(o.script, sshRecipeScript(found), "utf8");
29772
+ (0, import_node_fs36.writeFileSync)(o.script, sshRecipeScript(found), "utf8");
28961
29773
  console.log(`wrote ${o.script} \u2014 run: bash "${o.script}"`);
28962
29774
  } else if (o.ssh) console.log(`${formatSshRecipe(found)}
28963
29775
  ${SSH_RECIPE_AGENT_NOTE}`);
@@ -28972,12 +29784,12 @@ ${SSH_RECIPE_AGENT_NOTE}`);
28972
29784
 
28973
29785
  // src/schedules-commands.ts
28974
29786
  var import_promises4 = require("node:fs/promises");
28975
- var import_node_child_process18 = require("node:child_process");
29787
+ var import_node_child_process19 = require("node:child_process");
28976
29788
  var import_node_util7 = require("node:util");
28977
29789
  init_clean_exit();
28978
29790
  init_github_client();
28979
29791
  init_cli_shared();
28980
- var execFileP5 = (0, import_node_util7.promisify)(import_node_child_process18.execFile);
29792
+ var execFileP5 = (0, import_node_util7.promisify)(import_node_child_process19.execFile);
28981
29793
  var AWS_REGION = "eu-central-1";
28982
29794
  var AWS_TIMEOUT_MS = 3e4;
28983
29795
  var AWS_RETRY_DELAY_MS = 1500;
@@ -29089,7 +29901,7 @@ async function awsJson(args) {
29089
29901
  try {
29090
29902
  return await run();
29091
29903
  } catch {
29092
- await new Promise((resolve5) => setTimeout(resolve5, AWS_RETRY_DELAY_MS));
29904
+ await new Promise((resolve6) => setTimeout(resolve6, AWS_RETRY_DELAY_MS));
29093
29905
  return run();
29094
29906
  }
29095
29907
  }
@@ -29293,8 +30105,8 @@ function registerSchedulesCommands(program3) {
29293
30105
 
29294
30106
  // src/file-lock.ts
29295
30107
  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));
30108
+ var import_node_path32 = require("node:path");
30109
+ var sleep = (ms) => new Promise((resolve6) => setTimeout(resolve6, ms));
29298
30110
  var IMMEDIATE_RETRY_BUDGET = 3;
29299
30111
  var FileLockBusyError = class extends Error {
29300
30112
  lockPath;
@@ -29378,7 +30190,7 @@ async function releaseFileLock(lockPath, guard) {
29378
30190
  }
29379
30191
  async function withFileLock(lockPath, opts, fn) {
29380
30192
  const resolved = resolveFileLockOpts(opts);
29381
- await (0, import_promises5.mkdir)((0, import_node_path30.dirname)(lockPath), { recursive: true }).catch(() => void 0);
30193
+ await (0, import_promises5.mkdir)((0, import_node_path32.dirname)(lockPath), { recursive: true }).catch(() => void 0);
29382
30194
  const guard = await acquireFileLock(lockPath, resolved, Date.now() + resolved.maxWaitMs);
29383
30195
  try {
29384
30196
  return await fn();
@@ -29389,7 +30201,7 @@ async function withFileLock(lockPath, opts, fn) {
29389
30201
 
29390
30202
  // src/schedules-lift-command.ts
29391
30203
  var import_promises6 = require("node:fs/promises");
29392
- var import_node_path31 = require("node:path");
30204
+ var import_node_path33 = require("node:path");
29393
30205
  init_clean_exit();
29394
30206
  init_cli_shared();
29395
30207
  var DEFAULT_WORKFLOWS_DIR = ".github/workflows";
@@ -29418,7 +30230,7 @@ async function readWorkflowFiles(dir) {
29418
30230
  const files = [];
29419
30231
  for (const name of names.sort()) {
29420
30232
  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") });
30233
+ files.push({ path: `.github/workflows/${name}`, text: await (0, import_promises6.readFile)((0, import_node_path33.join)(dir, name), "utf8") });
29422
30234
  }
29423
30235
  return files;
29424
30236
  }
@@ -29505,13 +30317,13 @@ init_cli_shared();
29505
30317
  // src/edge-tunnel.ts
29506
30318
  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
30319
  var UPSTREAM_RE = /^https?:\/\/[^/\s]+(?::\d+)?(?:\/.*)?$/;
29508
- function tunnelNameFromHostname(hostname3) {
29509
- return hostname3.replace(/\./g, "-").slice(0, 63);
30320
+ function tunnelNameFromHostname(hostname4) {
30321
+ return hostname4.replace(/\./g, "-").slice(0, 63);
29510
30322
  }
29511
- function planInfraTunnel(hostname3, upstream) {
29512
- const host = hostname3.trim().toLowerCase();
30323
+ function planInfraTunnel(hostname4, upstream) {
30324
+ const host = hostname4.trim().toLowerCase();
29513
30325
  const origin = upstream.trim();
29514
- if (!HOSTNAME_RE.test(host)) throw new Error(`invalid hostname ${JSON.stringify(hostname3)}`);
30326
+ if (!HOSTNAME_RE.test(host)) throw new Error(`invalid hostname ${JSON.stringify(hostname4)}`);
29515
30327
  if (!UPSTREAM_RE.test(origin)) throw new Error(`invalid upstream ${JSON.stringify(upstream)} \u2014 expected http(s)://host:port`);
29516
30328
  const tunnelName = tunnelNameFromHostname(host);
29517
30329
  const configYaml = [
@@ -29569,15 +30381,149 @@ function registerEdgeCommands(program3) {
29569
30381
  }
29570
30382
 
29571
30383
  // 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");
30384
+ var import_node_fs38 = require("node:fs");
30385
+ var import_node_os17 = require("node:os");
30386
+ var import_node_path35 = require("node:path");
29575
30387
  init_cli_shared();
29576
30388
  init_clean_exit();
29577
30389
  init_github_client();
29578
30390
 
30391
+ // src/port-range-assign.ts
30392
+ init_cli_shared();
30393
+
30394
+ // src/port-registry.ts
30395
+ var import_node_fs37 = require("node:fs");
30396
+ var import_node_path34 = require("node:path");
30397
+
30398
+ // ../infra/port-geometry.mjs
30399
+ var PORT_BLOCK = 100;
30400
+ var PORT_SPAN = 10;
30401
+ var PORT_FIRST = 3e3;
30402
+
30403
+ // src/port-registry.ts
30404
+ function nextPortBlock(registry2) {
30405
+ const bases = Object.values(registry2).map(([start]) => start);
30406
+ const base = bases.length ? Math.max(...bases) + PORT_BLOCK : PORT_FIRST;
30407
+ return [base, base + PORT_SPAN];
30408
+ }
30409
+ function loadPortRegistry(path2) {
30410
+ if (!(0, import_node_fs37.existsSync)(path2)) return {};
30411
+ const raw = JSON.parse((0, import_node_fs37.readFileSync)(path2, "utf8"));
30412
+ const out = {};
30413
+ for (const [key, value] of Object.entries(raw)) {
30414
+ if (Array.isArray(value) && value.length === 2 && value.every((n) => typeof n === "number")) {
30415
+ out[key] = [value[0], value[1]];
30416
+ }
30417
+ }
30418
+ return out;
30419
+ }
30420
+ function ensurePortRange(repo, path2) {
30421
+ const registry2 = loadPortRegistry(path2);
30422
+ const existing = registry2[repo];
30423
+ if (existing) return existing;
30424
+ const range = nextPortBlock(registry2);
30425
+ const raw = (0, import_node_fs37.existsSync)(path2) ? JSON.parse((0, import_node_fs37.readFileSync)(path2, "utf8")) : {};
30426
+ raw[repo] = range;
30427
+ (0, import_node_fs37.writeFileSync)(path2, JSON.stringify(raw, null, 2) + "\n", "utf8");
30428
+ return range;
30429
+ }
30430
+ function portCursorSeed(registry2) {
30431
+ return nextPortBlock(registry2)[0];
30432
+ }
30433
+ function metaPortRange(meta) {
30434
+ const r = meta?.portRange;
30435
+ if (r && typeof r.start === "number" && typeof r.end === "number") return [r.start, r.end];
30436
+ return null;
30437
+ }
30438
+ function decidePortRange(input) {
30439
+ if (!input.metaReadOk) {
30440
+ 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)" };
30441
+ }
30442
+ if (input.metaPortRange) return { action: "return", range: input.metaPortRange };
30443
+ return { action: "allocate" };
30444
+ }
30445
+ function existingPortRange(repo, registry2) {
30446
+ return registry2[repo] ?? null;
30447
+ }
30448
+ function portRangeInfraAt(root, source) {
30449
+ const registryPath = (0, import_node_path34.join)(root, "infra", "port-ranges.json");
30450
+ const ddbScriptPath = (0, import_node_path34.join)(root, "infra", "port-ddb.mjs");
30451
+ if (!(0, import_node_fs37.existsSync)(registryPath) || !(0, import_node_fs37.existsSync)(ddbScriptPath)) return null;
30452
+ return { root, source, registryPath, ddbScriptPath };
30453
+ }
30454
+ function resolvePortRangeInfra(cwd, packageDir) {
30455
+ const direct = portRangeInfraAt(cwd, "cwd");
30456
+ if (direct) return direct;
30457
+ for (let dir = cwd; ; dir = (0, import_node_path34.dirname)(dir)) {
30458
+ const sibling = portRangeInfraAt((0, import_node_path34.join)(dir, "MMI-Hub"), "sibling-hub");
30459
+ if (sibling) return sibling;
30460
+ const parent = (0, import_node_path34.dirname)(dir);
30461
+ if (parent === dir) break;
30462
+ }
30463
+ if (packageDir) {
30464
+ const pkgRoot = (0, import_node_path34.join)(packageDir, "..", "..");
30465
+ const pkgFrom = portRangeInfraAt(pkgRoot, "pkg-root");
30466
+ if (pkgFrom) return pkgFrom;
30467
+ }
30468
+ return null;
30469
+ }
30470
+ async function ensurePortRangeAtomic(repo, path2, allocate, opts = {}) {
30471
+ const registry2 = loadPortRegistry(path2);
30472
+ const existing = existingPortRange(repo, registry2);
30473
+ if (existing) return { range: existing, source: "existing" };
30474
+ const seed = portCursorSeed(registry2);
30475
+ try {
30476
+ const range = await allocate(seed);
30477
+ return { range, source: "ddb" };
30478
+ } catch (e) {
30479
+ if (!opts.quiet) console.warn(`port-registry: DDB allocator unreachable, falling back to committed file (${e.message})`);
30480
+ return { range: ensurePortRange(repo, path2), source: "file" };
30481
+ }
30482
+ }
30483
+
30484
+ // src/port-range-assign.ts
30485
+ async function assignPersistedPortRange(repo, slug, reg, opts) {
30486
+ const read = await fetchProjectBySlugChecked(slug, reg);
30487
+ const decision = decidePortRange({ metaReadOk: read.ok, metaPortRange: read.ok ? metaPortRange(read.project) : null });
30488
+ if (decision.action === "fail") {
30489
+ return { ok: false, error: `${decision.reason}${read.ok ? "" : ` (${read.error})`}` };
30490
+ }
30491
+ if (decision.action === "return") {
30492
+ return { ok: true, range: decision.range, source: "meta", persisted: true };
30493
+ }
30494
+ const infra = resolvePortRangeInfra(opts.cwd, opts.moduleDir);
30495
+ if (!infra) {
30496
+ return {
30497
+ ok: false,
30498
+ 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`
30499
+ };
30500
+ }
30501
+ const path2 = infra.registryPath;
30502
+ const allocate = async (seed) => {
30503
+ const { stdout } = await execFileP2("node", [infra.ddbScriptPath, String(seed)], { timeout: 15e3 });
30504
+ const parsed = JSON.parse(stdout);
30505
+ if (!Array.isArray(parsed.range) || parsed.range.length !== 2) throw new Error("port-ddb: no range in output");
30506
+ return parsed.range;
30507
+ };
30508
+ const { range: [start, end], source } = await ensurePortRangeAtomic(repo, path2, allocate);
30509
+ const write = await upsertProject(slug, { portRange: { start, end } }, reg);
30510
+ if (!write.ok && source === "ddb") {
30511
+ return {
30512
+ ok: false,
30513
+ 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`
30514
+ };
30515
+ }
30516
+ return {
30517
+ ok: true,
30518
+ range: [start, end],
30519
+ source: "allocated",
30520
+ persisted: write.ok,
30521
+ ...write.ok ? {} : { persistError: write.error ?? `HTTP ${write.status}` }
30522
+ };
30523
+ }
30524
+
29579
30525
  // src/bootstrap-drift.ts
29580
- var import_node_crypto12 = require("node:crypto");
30526
+ var import_node_crypto14 = require("node:crypto");
29581
30527
  function byteComparableSeeds(manifest, cls) {
29582
30528
  return manifest.seeds.filter((s) => s.ownership === "org" && s.source === "self" && s.classes.includes(cls));
29583
30529
  }
@@ -29597,7 +30543,7 @@ function compareSeedBytes(hubContent, repoContent) {
29597
30543
  return normalize(hubContent) === normalize(repoContent) ? "match" : "drift";
29598
30544
  }
29599
30545
  function seedContentHash(content) {
29600
- return (0, import_node_crypto12.createHash)("sha256").update(content.replace(/\r\n/g, "\n"), "utf8").digest("hex");
30546
+ return (0, import_node_crypto14.createHash)("sha256").update(content.replace(/\r\n/g, "\n"), "utf8").digest("hex");
29601
30547
  }
29602
30548
  function auditRepoSeedDrift(repo, seeds, hubContents, repoReads) {
29603
30549
  const byTarget = new Map(repoReads.map((r) => [r.target, r.content]));
@@ -29758,7 +30704,7 @@ function renderPropagationReport(plan) {
29758
30704
  }
29759
30705
 
29760
30706
  // src/bootstrap-propagation-identity.ts
29761
- var import_node_crypto13 = require("node:crypto");
30707
+ var import_node_crypto15 = require("node:crypto");
29762
30708
  var PROPAGATION_BRANCH_PREFIX = "seed-propagate-";
29763
30709
  var TARGET_MARKER_NAME = "mmi-bootstrap-propagation-target";
29764
30710
  function safeBranchPart(value, maxLength, fallback) {
@@ -29771,7 +30717,7 @@ function repoSlug2(repo) {
29771
30717
  function propagationBranch(repo, target) {
29772
30718
  const repoPart = safeBranchPart(repoSlug2(repo), 32, "repo");
29773
30719
  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);
30720
+ const hash = (0, import_node_crypto15.createHash)("sha256").update(repo.trim().toLowerCase()).update("\0").update(target).digest("hex").slice(0, 12);
29775
30721
  return `${PROPAGATION_BRANCH_PREFIX}${repoPart}-${targetPart}-${hash}`;
29776
30722
  }
29777
30723
  function legacyPropagationBranch(repo) {
@@ -30211,6 +31157,20 @@ function filledDocCheck(label, text, path2) {
30211
31157
  const unfilled = unfilledDocPlaceholders(text);
30212
31158
  return { ok: unfilled.length === 0, label, detail: unfilled.length ? `unfilled: ${unfilled.join(", ")}` : void 0 };
30213
31159
  }
31160
+ function isCentralContainerDeployModel(model) {
31161
+ return model === "tenant-container" || model === "solo-container";
31162
+ }
31163
+ function centralContainerPortRangeCheck(deployModel, portRange, repo) {
31164
+ if (!isCentralContainerDeployModel(deployModel)) return null;
31165
+ const start = portRange?.start;
31166
+ const end = portRange?.end;
31167
+ const ok = typeof start === "number" && typeof end === "number" && Number.isFinite(start) && Number.isFinite(end) && start <= end;
31168
+ return {
31169
+ ok,
31170
+ label: "Hub registry portRange present for local stage",
31171
+ detail: ok ? void 0 : `${deployModel} needs PROJECT# META.portRange for mmi-cli stage \u2014 assign with: mmi-cli stage port-range ${repo}`
31172
+ };
31173
+ }
30214
31174
  async function verifyBootstrap(repo, repoClass, deps, releaseTrack) {
30215
31175
  const branchesWanted = expectedBranches(repoClass, releaseTrack);
30216
31176
  const baseBranch = releaseTrack === "trunk" || repoClass === "content" ? "main" : "development";
@@ -30304,6 +31264,8 @@ async function verifyBootstrap(repo, repoClass, deps, releaseTrack) {
30304
31264
  });
30305
31265
  }
30306
31266
  }
31267
+ const portRangeCheck = centralContainerPortRangeCheck(deps.deployModel, deps.projectMeta?.portRange, repo);
31268
+ if (portRangeCheck) checks.push(portRangeCheck);
30307
31269
  const readme = await contentText(deps, repo, baseBranch, "README.md");
30308
31270
  checks.push({
30309
31271
  ok: readme !== null && readme.includes("## Agent context"),
@@ -30573,13 +31535,13 @@ function registerBootstrapCommands(program3) {
30573
31535
  client: defaultGitHubClient(),
30574
31536
  projectMeta: meta,
30575
31537
  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,
31538
+ readLocalFile: (path2) => path2 === "projects.json" && apiProjects != null ? apiProjects : (0, import_node_fs38.existsSync)(path2) ? (0, import_node_fs38.readFileSync)(path2, "utf8") : null,
30577
31539
  // requiredGcpApis is stored as an array by a JSON write, but `org project set --var KEY=VALUE` stores a raw
30578
31540
  // comma-string — accept either so the seeded value verifies regardless of how it was written.
30579
31541
  // #3689: the same committed map the org access audit reads (#3664), so a sanctioned admin is not a
30580
31542
  // permanent bootstrap failure on one surface and an intended state on the other. Absent file → no
30581
31543
  // 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,
31544
+ sanctionedAdmins: (0, import_node_fs38.existsSync)("access-matrix.json") ? entriesValueByCanonicalRepo(loadSanctionedAdmins((0, import_node_fs38.readFileSync)("access-matrix.json", "utf8")), repo) : void 0,
30583
31545
  requiredGcpApis: (() => {
30584
31546
  const v = meta?.requiredGcpApis;
30585
31547
  if (Array.isArray(v)) return v;
@@ -30632,14 +31594,14 @@ function registerBootstrapCommands(program3) {
30632
31594
  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
31595
  const o = { repo: rawValue("--repo", ""), json: rawFlag("--json") };
30634
31596
  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`);
31597
+ 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
31598
  const seedSource = await resolveHubSeedSource(execGitForSeedSource);
30637
31599
  if (!seedSource.ok) return fail(`bootstrap drift: ${seedSource.reason}`);
30638
- const manifest = loadBootstrapSeeds((0, import_node_fs34.readFileSync)(manifestPath, "utf8"));
31600
+ const manifest = loadBootstrapSeeds((0, import_node_fs38.readFileSync)(manifestPath, "utf8"));
30639
31601
  const hubContents = /* @__PURE__ */ new Map();
30640
31602
  for (const s of manifest.seeds) {
30641
31603
  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);
31604
+ hubContents.set(s.target, (0, import_node_fs38.existsSync)(s.target) ? (0, import_node_fs38.readFileSync)(s.target, "utf8") : null);
30643
31605
  }
30644
31606
  let targets;
30645
31607
  let classOf = (_repo) => "deployable";
@@ -30757,10 +31719,10 @@ function registerBootstrapCommands(program3) {
30757
31719
  return;
30758
31720
  }
30759
31721
  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`);
31722
+ 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
31723
  const seedSource = await resolveHubSeedSource(execGitForSeedSource);
30762
31724
  if (!seedSource.ok) return fail(`bootstrap apply: ${seedSource.reason}`);
30763
- const manifest = loadBootstrapSeeds((0, import_node_fs34.readFileSync)(manifestPath, "utf8"));
31725
+ const manifest = loadBootstrapSeeds((0, import_node_fs38.readFileSync)(manifestPath, "utf8"));
30764
31726
  const baseBranch = o.class === "content" ? "main" : "development";
30765
31727
  const slug = parsedRepo.slug;
30766
31728
  const onlyTarget = o.only.trim();
@@ -30772,16 +31734,16 @@ function registerBootstrapCommands(program3) {
30772
31734
  }
30773
31735
  const onlyManagedBlock = onlyTarget ? seedsToApply[0]?.managedBlock != null : false;
30774
31736
  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;
31737
+ const readFile7 = (p) => (0, import_node_fs38.existsSync)(p) ? (0, import_node_fs38.readFileSync)(p, "utf8") : null;
30776
31738
  const enc = (p) => p.split("/").map(encodeURIComponent).join("/");
30777
31739
  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");
31740
+ 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`);
31741
+ (0, import_node_fs38.writeFileSync)(tmp, JSON.stringify(contentPutBody(target, content, ref, sha)), "utf8");
30780
31742
  try {
30781
31743
  await gh(contentPutInputArgs(repo, target, tmp));
30782
31744
  } finally {
30783
31745
  try {
30784
- (0, import_node_fs34.unlinkSync)(tmp);
31746
+ (0, import_node_fs38.unlinkSync)(tmp);
30785
31747
  } catch {
30786
31748
  }
30787
31749
  }
@@ -31054,10 +32016,32 @@ ${onlyManagedBlock ? `Only the marker-bounded Hub-managed block inside repo-owne
31054
32016
  }
31055
32017
  if (o.execute && !onlyTarget) {
31056
32018
  const cfg = await loadConfig();
31057
- const res = await registerProject(registerPayload, registryClientDeps(cfg));
32019
+ const reg = registryClientDeps(cfg);
32020
+ const res = await registerProject(registerPayload, reg);
31058
32021
  if (res.ok) {
31059
32022
  ddbWrites.push({ slug: registerPayload.slug, action: "register", record: registerPayload });
31060
32023
  applied.push(`ddb register ${registerPayload.slug}`);
32024
+ const deployModel = typeof registerPayload.deployModel === "string" ? registerPayload.deployModel : void 0;
32025
+ if (deployModel === "tenant-container" || deployModel === "solo-container") {
32026
+ const shortName = typeof registerPayload.name === "string" ? registerPayload.name : typeof registerPayload.slug === "string" ? registerPayload.slug : repo.split("/")[1] || repo;
32027
+ const assigned = await assignPersistedPortRange(shortName, registerPayload.slug, reg, {
32028
+ cwd: process.cwd(),
32029
+ moduleDir: __dirname
32030
+ });
32031
+ if (assigned.ok) {
32032
+ const [start, end] = assigned.range;
32033
+ ddbWrites.push({
32034
+ slug: registerPayload.slug,
32035
+ action: "portRange",
32036
+ record: { portRange: { start, end }, source: assigned.source, persisted: assigned.persisted }
32037
+ });
32038
+ applied.push(
32039
+ assigned.source === "meta" ? `portRange [${start}, ${end}] (already on META)` : `portRange [${start}, ${end}] (${assigned.persisted ? "persisted" : `META not persisted: ${assigned.persistError}`})`
32040
+ );
32041
+ } else {
32042
+ applied.push(`portRange (failed: ${assigned.error})`);
32043
+ }
32044
+ }
31061
32045
  } else {
31062
32046
  const why = res.error ?? `HTTP ${res.status}${res.body?.error ? ` \u2014 ${res.body.error}` : ""}`;
31063
32047
  applied.push(`ddb register ${registerPayload.slug} (failed: ${why})`);
@@ -31074,10 +32058,10 @@ LIVE apply to ${repo}:
31074
32058
  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
32059
  const o = { target: rawValue("--target", ""), execute: rawFlag("--execute"), json: rawFlag("--json") };
31076
32060
  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`);
32061
+ 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
32062
  const seedSource = await resolveHubSeedSource(execGitForSeedSource);
31079
32063
  if (!seedSource.ok) return fail(`bootstrap propagate: ${seedSource.reason}`);
31080
- const manifest = loadBootstrapSeeds((0, import_node_fs34.readFileSync)(manifestPath, "utf8"));
32064
+ const manifest = loadBootstrapSeeds((0, import_node_fs38.readFileSync)(manifestPath, "utf8"));
31081
32065
  const propagatable = manifest.seeds.filter(isPropagatableSeed);
31082
32066
  if (!o.target) {
31083
32067
  return fail(`bootstrap propagate: --target <path> is required \u2014 one of:
@@ -31086,9 +32070,9 @@ LIVE apply to ${repo}:
31086
32070
  const seed = propagatable.find((s) => s.target === o.target);
31087
32071
  if (!seed) return fail(`bootstrap propagate: --target '${o.target}' names no centrally propagatable seed in ${manifestPath}. Propagatable targets:
31088
32072
  ${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;
32073
+ 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`);
32074
+ const hubContent = seed.managedBlock ? null : (0, import_node_fs38.readFileSync)(seed.target, "utf8");
32075
+ const readSeedFile = (path2) => (0, import_node_fs38.existsSync)(path2) ? (0, import_node_fs38.readFileSync)(path2, "utf8") : null;
31092
32076
  const isWorkflowSeed = seed.target.startsWith(".github/workflows/");
31093
32077
  const cfg = await loadConfig();
31094
32078
  const projects = await fetchProjectsList(registryClientDeps(cfg));
@@ -31097,9 +32081,9 @@ LIVE apply to ${repo}:
31097
32081
  }
31098
32082
  const rosterRepos2 = collectRegistryRepos(projects).filter((r) => r.toLowerCase() !== "mutmutco/mmi-hub");
31099
32083
  let independentCount = rosterRepos2.length;
31100
- if ((0, import_node_fs34.existsSync)("projects.json")) {
32084
+ if ((0, import_node_fs38.existsSync)("projects.json")) {
31101
32085
  try {
31102
- const local = JSON.parse((0, import_node_fs34.readFileSync)("projects.json", "utf8"));
32086
+ const local = JSON.parse((0, import_node_fs38.readFileSync)("projects.json", "utf8"));
31103
32087
  const localRepos = /* @__PURE__ */ new Set();
31104
32088
  for (const p of local.projects ?? []) for (const r of p.repos ?? []) {
31105
32089
  const full = (r.includes("/") ? r : `mutmutco/${r}`).toLowerCase();
@@ -31246,15 +32230,15 @@ LIVE apply to ${repo}:
31246
32230
  } catch {
31247
32231
  existingSha = void 0;
31248
32232
  }
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`);
32233
+ 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
32234
  const desiredContent = desiredByRepo.get(rec.repo);
31251
32235
  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");
32236
+ (0, import_node_fs38.writeFileSync)(tmp, JSON.stringify(contentPutBody(seed.target, desiredContent, branch, existingSha)), "utf8");
31253
32237
  try {
31254
32238
  await gh(contentPutInputArgs(rec.repo, seed.target, tmp));
31255
32239
  } finally {
31256
32240
  try {
31257
- (0, import_node_fs34.unlinkSync)(tmp);
32241
+ (0, import_node_fs38.unlinkSync)(tmp);
31258
32242
  } catch {
31259
32243
  }
31260
32244
  }
@@ -31321,10 +32305,10 @@ Rollback: \`mmi-cli devops bootstrap rollback ${rec.repo} --target ${seed.target
31321
32305
  return fail(`bootstrap rollback: ${e.message}`);
31322
32306
  }
31323
32307
  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)`);
32308
+ 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
32309
  const seedSource = await resolveHubSeedSource(execGitForSeedSource);
31326
32310
  if (!seedSource.ok) return fail(`bootstrap rollback: ${seedSource.reason}`);
31327
- const manifest = loadBootstrapSeeds((0, import_node_fs34.readFileSync)(manifestPath, "utf8"));
32311
+ const manifest = loadBootstrapSeeds((0, import_node_fs38.readFileSync)(manifestPath, "utf8"));
31328
32312
  const propagatable = manifest.seeds.filter(isPropagatableSeed);
31329
32313
  if (!o.target) {
31330
32314
  return fail(`bootstrap rollback: --target <path> is required \u2014 one of:
@@ -31341,10 +32325,10 @@ Rollback: \`mmi-cli devops bootstrap rollback ${rec.repo} --target ${seed.target
31341
32325
  const enc = (p) => p.split("/").map(encodeURIComponent).join("/");
31342
32326
  let candidates;
31343
32327
  if (o.record) {
31344
- if (!(0, import_node_fs34.existsSync)(o.record)) return fail(`bootstrap rollback: --record '${o.record}' not found`);
32328
+ if (!(0, import_node_fs38.existsSync)(o.record)) return fail(`bootstrap rollback: --record '${o.record}' not found`);
31345
32329
  let parsed;
31346
32330
  try {
31347
- parsed = JSON.parse((0, import_node_fs34.readFileSync)(o.record, "utf8"));
32331
+ parsed = JSON.parse((0, import_node_fs38.readFileSync)(o.record, "utf8"));
31348
32332
  } catch (e) {
31349
32333
  return fail(`bootstrap rollback: --record '${o.record}' is not valid JSON: ${e.message}`);
31350
32334
  }
@@ -31421,13 +32405,13 @@ Rollback: \`mmi-cli devops bootstrap rollback ${rec.repo} --target ${seed.target
31421
32405
  } catch {
31422
32406
  existingSha = void 0;
31423
32407
  }
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");
32408
+ 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`);
32409
+ (0, import_node_fs38.writeFileSync)(tmp, JSON.stringify(contentPutBody(seed.target, preSeedContent, plan.branch, existingSha)), "utf8");
31426
32410
  try {
31427
32411
  await gh(contentPutInputArgs(repo, seed.target, tmp));
31428
32412
  } finally {
31429
32413
  try {
31430
- (0, import_node_fs34.unlinkSync)(tmp);
32414
+ (0, import_node_fs38.unlinkSync)(tmp);
31431
32415
  } catch {
31432
32416
  }
31433
32417
  }
@@ -31452,101 +32436,11 @@ Rollback: \`mmi-cli devops bootstrap rollback ${rec.repo} --target ${seed.target
31452
32436
  }
31453
32437
 
31454
32438
  // src/stage-commands.ts
31455
- var import_node_fs36 = require("node:fs");
31456
- var import_node_path34 = require("node:path");
32439
+ var import_node_fs39 = require("node:fs");
32440
+ var import_node_path36 = require("node:path");
31457
32441
  init_cli_shared();
31458
32442
  init_clean_exit();
31459
32443
 
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
32444
  // src/stage-default.ts
31551
32445
  function shellFor(platform2 = process.platform) {
31552
32446
  return platform2 === "win32" ? "powershell" : "bash";
@@ -31554,14 +32448,20 @@ function shellFor(platform2 = process.platform) {
31554
32448
  function isCentralContainerModel(model) {
31555
32449
  return model === "tenant-container" || model === "solo-container";
31556
32450
  }
31557
- function deriveStageGap(inputs) {
32451
+ function stagePortRangeRecovery(repo = "<owner/repo>") {
32452
+ return `mmi-cli stage port-range ${repo}`;
32453
+ }
32454
+ function deriveStageGap(inputs, opts) {
31558
32455
  const missing = [];
31559
32456
  if (!isCentralContainerModel(inputs.deployModel)) {
31560
32457
  return `local stage default applies to central-container repos only (tenant-container/solo-container; registry deployModel = ${inputs.deployModel ?? "unset"})`;
31561
32458
  }
31562
32459
  if (!inputs.hasCompose) missing.push("docker-compose.yml");
31563
32460
  if (!inputs.portRange) missing.push("Hub registry portRange");
31564
- return missing.length ? `cannot derive a default local stage \u2014 missing: ${missing.join(", ")}` : null;
32461
+ if (!missing.length) return null;
32462
+ const base = `cannot derive a default local stage \u2014 missing: ${missing.join(", ")}`;
32463
+ if (!inputs.portRange) return `${base} \u2014 assign with: ${stagePortRangeRecovery(opts?.repo)}`;
32464
+ return base;
31565
32465
  }
31566
32466
  function deriveStage(inputs) {
31567
32467
  if (deriveStageGap(inputs) || !inputs.portRange) return null;
@@ -31592,7 +32492,7 @@ function stageUrlForPort(port) {
31592
32492
  return `http://127.0.0.1:${port}/`;
31593
32493
  }
31594
32494
  function decideStage(inputs) {
31595
- const { registry: registry2, hasCompose, hasEnvExample } = inputs;
32495
+ const { registry: registry2, hasCompose, hasEnvExample, repo } = inputs;
31596
32496
  const deriveInputs = {
31597
32497
  portRange: registry2.portRange,
31598
32498
  deployModel: registry2.deployModel,
@@ -31602,8 +32502,9 @@ function decideStage(inputs) {
31602
32502
  const derived = deriveStage(deriveInputs);
31603
32503
  if (derived) return { source: "derived", derived, registryError: registry2.error };
31604
32504
  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 };
32505
+ const gap = registryGap ?? deriveStageGap(deriveInputs, { repo }) ?? "no registry-derived default available";
32506
+ const recovery = !registry2.error && isCentralContainerModel(registry2.deployModel) && !registry2.portRange ? stagePortRangeRecovery(repo) : void 0;
32507
+ return { source: "none", gap, ...recovery ? { recovery } : {}, registryError: registry2.error };
31607
32508
  }
31608
32509
 
31609
32510
  // src/stage-live.ts
@@ -31813,14 +32714,17 @@ function registerStageCommands(program3) {
31813
32714
  }
31814
32715
  async function resolveStage() {
31815
32716
  const cfg = await loadConfig();
31816
- const read = await fetchProjectBySlugChecked(await repoSlug(), registryClientDeps(cfg)).catch((e) => ({ ok: false, error: e.message }));
32717
+ const slug = await repoSlug();
32718
+ const read = await fetchProjectBySlugChecked(slug, registryClientDeps(cfg)).catch((e) => ({ ok: false, error: e.message }));
31817
32719
  const project2 = read.ok ? read.project : null;
31818
32720
  const portRangeMeta = project2?.portRange ?? void 0;
31819
32721
  const portRange = portRangeMeta && typeof portRangeMeta.start === "number" && typeof portRangeMeta.end === "number" ? [portRangeMeta.start, portRangeMeta.end] : void 0;
32722
+ const repo = Array.isArray(project2?.repos) && typeof project2.repos[0] === "string" ? project2.repos[0] : slug;
31820
32723
  return decideStage({
31821
32724
  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"))
32725
+ hasCompose: (0, import_node_fs39.existsSync)((0, import_node_path36.join)(process.cwd(), "docker-compose.yml")),
32726
+ hasEnvExample: (0, import_node_fs39.existsSync)((0, import_node_path36.join)(process.cwd(), ".env.example")),
32727
+ repo
31824
32728
  });
31825
32729
  }
31826
32730
  async function fetchStageVaultEnvMerge() {
@@ -31865,7 +32769,16 @@ function registerStageCommands(program3) {
31865
32769
  }
31866
32770
  function stageStepsFor(res, stops = true) {
31867
32771
  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"}` }];
32772
+ return [{ label: `no local stage to run \u2014 ${res.gap ?? "stage config gap"}`, ...res.recovery ? { command: res.recovery } : {} }];
32773
+ }
32774
+ function stageReceiptFields(res) {
32775
+ return {
32776
+ source: res.source,
32777
+ url: res.derived?.url,
32778
+ ...res.gap ? { gap: res.gap } : {},
32779
+ ...res.recovery ? { recovery: res.recovery } : {},
32780
+ ...res.registryError ? { registryError: res.registryError } : {}
32781
+ };
31869
32782
  }
31870
32783
  function reportedStageUrl(res, result) {
31871
32784
  if (!res.derived) return void 0;
@@ -31875,38 +32788,23 @@ function registerStageCommands(program3) {
31875
32788
  const cfg = await loadConfig();
31876
32789
  const reg = registryClientDeps(cfg);
31877
32790
  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}]`);
32791
+ const assigned = await assignPersistedPortRange(repo, slug, reg, { cwd: process.cwd(), moduleDir: __dirname });
32792
+ if (!assigned.ok) return failGraceful(`stage port-range: ${assigned.error}`);
32793
+ const [start, end] = assigned.range;
32794
+ if (assigned.source === "meta") {
32795
+ printLine(o.json ? JSON.stringify({ repo, portRange: [start, end], source: "meta" }) : `${repo}: stage.portRange [${start}, ${end}]`);
31886
32796
  return;
31887
32797
  }
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
32798
  if (o.json) {
31907
- printLine(JSON.stringify({ repo, portRange: [start, end], source: "allocated", persisted: write.ok, ...write.ok ? {} : { persistError: write.error ?? `HTTP ${write.status}` } }));
32799
+ printLine(JSON.stringify({
32800
+ repo,
32801
+ portRange: [start, end],
32802
+ source: "allocated",
32803
+ persisted: assigned.persisted,
32804
+ ...assigned.persisted ? {} : { persistError: assigned.persistError }
32805
+ }));
31908
32806
  } else {
31909
- printLine(`${repo}: stage.portRange [${start}, ${end}]${write.ok ? "" : ` (META not persisted: ${write.error ?? `HTTP ${write.status}`})`}`);
32807
+ printLine(`${repo}: stage.portRange [${start}, ${end}]${assigned.persisted ? "" : ` (META not persisted: ${assigned.persistError})`}`);
31910
32808
  }
31911
32809
  });
31912
32810
  async function stageLiveTarget() {
@@ -31992,7 +32890,7 @@ function registerStageCommands(program3) {
31992
32890
  }
31993
32891
  }
31994
32892
  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));
32893
+ if (o.json) return console.log(JSON.stringify({ command: "stage", ...stageReceiptFields(res), steps }, null, 2));
31996
32894
  console.log(renderSteps("mmi-cli stage: dry-run plan", steps));
31997
32895
  });
31998
32896
  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 +32911,7 @@ function registerStageCommands(program3) {
32013
32911
  const res = await resolveStage();
32014
32912
  if (!o.apply) {
32015
32913
  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));
32914
+ if (o.json) return printLine(JSON.stringify({ command: "stage start", ...stageReceiptFields(res), steps }, null, 2));
32017
32915
  return printLine(renderSteps("mmi-cli stage start: dry-run plan", steps));
32018
32916
  }
32019
32917
  if (res.source === "none") return failGraceful(`stage start: ${res.gap}`);
@@ -32046,7 +32944,7 @@ function registerStageCommands(program3) {
32046
32944
  const res = await resolveStage();
32047
32945
  if (!o.apply) {
32048
32946
  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));
32947
+ if (o.json) return printLine(JSON.stringify({ command: "stage run", ...stageReceiptFields(res), steps }, null, 2));
32050
32948
  return printLine(renderSteps("mmi-cli stage run: dry-run plan", steps));
32051
32949
  }
32052
32950
  if (res.source === "none") return failGraceful(`stage run: ${res.gap}`);
@@ -32078,9 +32976,9 @@ function registerStageCommands(program3) {
32078
32976
  }
32079
32977
 
32080
32978
  // 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");
32979
+ var import_node_fs40 = require("node:fs");
32980
+ var import_node_path37 = require("node:path");
32981
+ var import_node_os18 = require("node:os");
32084
32982
  init_cli_shared();
32085
32983
 
32086
32984
  // src/config-load.ts
@@ -32357,13 +33255,13 @@ async function composeOverrideBodyFile(prNumber, repoArgs, gh) {
32357
33255
  const commits = JSON.parse(raw).commits ?? [];
32358
33256
  const body = squashBodyWithOverride(commits.map((c) => ({ headline: c.messageHeadline ?? "", body: c.messageBody ?? "" })), process.cwd());
32359
33257
  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}
33258
+ const dir = (0, import_node_fs40.mkdtempSync)((0, import_node_path37.join)((0, import_node_os18.tmpdir)(), "mmi-squash-body-"));
33259
+ const path2 = (0, import_node_path37.join)(dir, "body.txt");
33260
+ (0, import_node_fs40.writeFileSync)(path2, `${body}
32363
33261
  `, "utf8");
32364
33262
  return { path: path2, cleanup: () => {
32365
33263
  try {
32366
- (0, import_node_fs37.rmSync)(dir, { recursive: true, force: true });
33264
+ (0, import_node_fs40.rmSync)(dir, { recursive: true, force: true });
32367
33265
  } catch {
32368
33266
  }
32369
33267
  } };
@@ -32503,7 +33401,7 @@ function registerBoardCommands(program3) {
32503
33401
  includeBundleDetails: o.bundleDetails,
32504
33402
  includeAllBodies: o.bodies,
32505
33403
  allowPartial: o.allowPartial
32506
- }, { snapshot: registryClientDeps(config) });
33404
+ }, o.direct ? {} : { snapshot: registryClientDeps(config) });
32507
33405
  console.log(o.json ? JSON.stringify(report) : renderBoardReport(report));
32508
33406
  } catch (e) {
32509
33407
  return failGraceful(`board read failed: ${withDiscoverMissDetail(e.message)}`);
@@ -32513,7 +33411,7 @@ function registerBoardCommands(program3) {
32513
33411
  return alreadyClaimed ? `Check ${ref}: claimed and In Progress, no live contest - claim would renew the lease (nothing written)` : `Check ${ref}: free - claim would proceed (nothing written)`;
32514
33412
  }
32515
33413
  const board = program3.command("board").description("read, claim, show, and move Project v2 work items for the current repo");
32516
- board.command("read", { isDefault: true }).description("read the board and print user-owned, claimable, and taken items").option("--json", "machine-readable output").option("--repo <owner/repo>", "current repo (defaults to git origin)").option("--bundle-details", "fetch body/comments only for user-owned and claimable issues").option("--bodies", "fetch body/comments for EVERY scoped row, including taken and unowned in-flight ones \u2014 for consumers that scope by Status rather than ownership (#4861); implies --bundle-details and costs one extra read per row").option("--allow-partial", "return partial board results when later page/detail reads fail").addHelpText("after", "\nread is always the authoritative live GitHub Project v2 board (#4926).\n--allow-partial applies to the paginated path and detail reads.\n").action((o) => runBoardRead(o));
33414
+ board.command("read", { isDefault: true }).description("read the board and print user-owned, claimable, and taken items").option("--json", "machine-readable output").option("--repo <owner/repo>", "current repo (defaults to git origin)").option("--direct", "bypass the Hub snapshot and read the live board through direct GitHub GraphQL").option("--bundle-details", "fetch body/comments only for user-owned and claimable issues").option("--bodies", "fetch body/comments for EVERY scoped row, including taken and unowned in-flight ones \u2014 for consumers that scope by Status rather than ownership (#4861); implies --bundle-details and costs one extra read per row").option("--allow-partial", "return partial board results when later page/detail reads fail").addHelpText("after", "\nread is always the authoritative live GitHub Project v2 board (#4926).\n--direct skips the Hub snapshot and uses the existing direct GitHub GraphQL read immediately.\n--allow-partial applies to the paginated path and detail reads.\n").action((o) => runBoardRead(o));
32517
33415
  withExamples(mutating(
32518
33416
  board.command("claim <issues...>").description("claim issues: assign them and move their Project v2 Status to In Progress \u2014 idempotent, so an item already yours and In Progress succeeds unchanged (one or more refs)").addHelpText("after", "\nevery claim stamps a lane-identity marker comment on the issue (`<!-- mmi-claim: \u2026 -->`,\nsurface/session@host) so other agents can attribute the hold (#3727). The session is the\nhost-exported id when the surface provides one, otherwise a per-process `synth-` fallback \u2014\na claim is never anonymous (#5245). `board show`, doctor and unclaim read the latest marker.\n").option("--json", "machine-readable output").option("--repo <owner/repo>", "current repo for local issue numbers (defaults to git origin)").option("--for <login>", "assign to this login instead of @me \u2014 agent claims on behalf of the master").option("--force", "take an item already claimed by another lane that shows live evidence of active work (#3727)").option("--check", "read-only: run every claim gate and report the verdict, writing nothing \u2014 exits 1 with the same refusal a real claim would raise (#4511)").option("--allow-partial", "return success JSON if assignment succeeds but the status move fails"),
32519
33417
  (_opts, args) => ({ command: "board claim", issues: args[0] ?? [] })
@@ -32570,7 +33468,9 @@ function registerBoardCommands(program3) {
32570
33468
  "Pass raw issue numbers/refs, not URLs.",
32571
33469
  "Claim already assigns and moves Status to In Progress, so do not also board move it.",
32572
33470
  "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."
33471
+ "--check is the live gate read (it calls GitHub); --dry-run only echoes the parsed argv plan.",
33472
+ // #5552: agents guess `oracle issue claim`; that route does not exist — board claim is the only write.
33473
+ "Never run `oracle issue claim` \u2014 claims are board mutations; only `oracle board claim <ref>` is valid."
32574
33474
  ]);
32575
33475
  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
33476
  try {
@@ -32792,12 +33692,13 @@ function derivePollState(buckets) {
32792
33692
  return anyPending ? "pending" : "success";
32793
33693
  }
32794
33694
  function partitionByRequired(entries, requiredContexts) {
33695
+ const scoped = entries.filter((entry) => !AUXILIARY_RELEASE_CHECK_CONTEXTS.has(entry.name));
32795
33696
  if (!requiredContexts || requiredContexts.size === 0) {
32796
- return { relevant: entries.map((e) => e.bucket), ignoredFailures: [] };
33697
+ return { relevant: scoped.map((e) => e.bucket), ignoredFailures: [] };
32797
33698
  }
32798
33699
  const relevant = [];
32799
33700
  const ignoredFailures = [];
32800
- for (const entry of entries) {
33701
+ for (const entry of scoped) {
32801
33702
  if (requiredContexts.has(entry.name)) {
32802
33703
  relevant.push(entry.bucket);
32803
33704
  } else if (entry.bucket === "fail") {
@@ -32859,18 +33760,33 @@ var PR_SNAPSHOT_READ_RETRIES = 3;
32859
33760
  var PR_SNAPSHOT_READ_DELAY_MS = 2e3;
32860
33761
  async function readRestPrSnapshotWithRetry(prNumber, repo, gh = defaultGhApi, options) {
32861
33762
  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)));
33763
+ const untilMs = options?.retryTransientUntilMs;
33764
+ const delayMs = options?.delayMs ?? (untilMs !== void 0 ? PR_CHECKS_POLL_MS : PR_SNAPSHOT_READ_DELAY_MS);
33765
+ const sleep2 = options?.sleep ?? ((ms) => new Promise((resolve6) => setTimeout(resolve6, ms)));
33766
+ const now = options?.now ?? (() => Date.now());
32864
33767
  let lastError = "no attempt completed";
32865
- for (let attempt = 0; attempt < retries; attempt++) {
33768
+ let attempt = 0;
33769
+ for (; ; ) {
32866
33770
  try {
32867
33771
  return { state: "ok", snapshot: await fetchRestPrSnapshot(prNumber, repo, gh) };
32868
33772
  } catch (e) {
32869
33773
  lastError = readErrorText(e);
33774
+ attempt += 1;
33775
+ const retryable = isRetryableGitHubWaitReadError(e);
33776
+ if (!retryable) {
33777
+ return { state: "failed", error: `pulls read failed for #${prNumber} on ${repo}: ${lastError}` };
33778
+ }
33779
+ const canRetry = untilMs !== void 0 ? now() + delayMs < untilMs : attempt < retries;
33780
+ if (!canRetry) {
33781
+ return {
33782
+ state: "failed",
33783
+ error: `pulls read failed for #${prNumber} on ${repo} after ${attempt} attempts: ${lastError}`
33784
+ };
33785
+ }
33786
+ options?.onTransientRetry?.(lastError, attempt);
33787
+ await sleep2(delayMs);
32870
33788
  }
32871
- if (attempt < retries - 1) await sleep2(delayMs);
32872
33789
  }
32873
- return { state: "failed", error: `pulls read failed for #${prNumber} on ${repo} after ${retries} attempts: ${lastError}` };
32874
33790
  }
32875
33791
  async function fetchRestClosingGuardPayload(prNumber, repo, gh = defaultGhApi) {
32876
33792
  const pr2 = JSON.parse(await gh([`repos/${repo}/pulls/${prNumber}`]));
@@ -32888,8 +33804,8 @@ async function fetchHeadCheckEntries(headSha, repo, gh) {
32888
33804
  ]);
32889
33805
  const statuses = parseNdjsonLines(statusesOut);
32890
33806
  return [
32891
- ...runs.map((run) => ({ name: run.name ?? `check-run ${run.id ?? "unknown"}`, bucket: classifyCheckRun(run) })),
32892
- ...statuses.map((s) => ({ name: s.context ?? "unknown-status", bucket: classifyCommitStatus(s.state) }))
33807
+ ...runs.filter((run) => !AUXILIARY_RELEASE_CHECK_CONTEXTS.has(run.name ?? "")).map((run) => ({ name: run.name ?? `check-run ${run.id ?? "unknown"}`, bucket: classifyCheckRun(run) })),
33808
+ ...statuses.filter((s) => !AUXILIARY_RELEASE_CHECK_CONTEXTS.has(s.context ?? "")).map((s) => ({ name: s.context ?? "unknown-status", bucket: classifyCommitStatus(s.state) }))
32893
33809
  ];
32894
33810
  }
32895
33811
  async function pollRestPrChecks(prNumber, repo, gh = defaultGhApi, requiredContexts) {
@@ -33160,8 +34076,8 @@ async function checkDocsIndexAtHead(opts, deps) {
33160
34076
  }
33161
34077
 
33162
34078
  // src/issue-commands.ts
33163
- var import_node_fs38 = require("node:fs");
33164
- var import_node_crypto14 = require("node:crypto");
34079
+ var import_node_fs41 = require("node:fs");
34080
+ var import_node_crypto16 = require("node:crypto");
33165
34081
  init_cli_shared();
33166
34082
  init_clean_exit();
33167
34083
  init_error_codes();
@@ -33353,7 +34269,7 @@ async function editIssue(client, options, deps = {}) {
33353
34269
  const url = `https://github.com/${repo}/issues/${parsed.number}`;
33354
34270
  const patch = {};
33355
34271
  let bodyChanged = false;
33356
- const textDeps = () => deps.textDeps ?? { readFile: (p, e) => Promise.resolve((0, import_node_fs38.readFileSync)(p, e)), readStdin: () => Promise.resolve("") };
34272
+ const textDeps = () => deps.textDeps ?? { readFile: (p, e) => Promise.resolve((0, import_node_fs41.readFileSync)(p, e)), readStdin: () => Promise.resolve("") };
33357
34273
  if (options.titleFile !== void 0) {
33358
34274
  patch.title = await resolveIssueTitle({ title: options.title, titleFile: options.titleFile }, textDeps());
33359
34275
  } else if (options.title !== void 0) {
@@ -33634,7 +34550,7 @@ function rowIdempotencyKey(batchKey, spec) {
33634
34550
  const identity = `${spec.type}
33635
34551
  ${spec.title.trim()}
33636
34552
  ${spec.body ?? ""}`;
33637
- const hash = (0, import_node_crypto14.createHash)("sha256").update(identity).digest("hex").slice(0, 16);
34553
+ const hash = (0, import_node_crypto16.createHash)("sha256").update(identity).digest("hex").slice(0, 16);
33638
34554
  return `${batchKey}:${hash}`;
33639
34555
  }
33640
34556
  var BATCH_SPEC_KEYS = /* @__PURE__ */ new Set(["type", "title", "body", "priority", "labels", "label", "parent", "repo", "surface"]);
@@ -33970,7 +34886,7 @@ function extendCreateCommand(issue2, batchAttach) {
33970
34886
  if (opts.batch) {
33971
34887
  let specs;
33972
34888
  try {
33973
- const raw = (0, import_node_fs38.readFileSync)(opts.batch, "utf8");
34889
+ const raw = (0, import_node_fs41.readFileSync)(opts.batch, "utf8");
33974
34890
  specs = JSON.parse(raw);
33975
34891
  if (!Array.isArray(specs)) throw new Error("batch file must contain a JSON array");
33976
34892
  } catch (e) {
@@ -34045,8 +34961,8 @@ ${lines}`, {
34045
34961
  }
34046
34962
 
34047
34963
  // src/train-commands.ts
34048
- var import_node_fs39 = require("node:fs");
34049
- var import_node_path36 = require("node:path");
34964
+ var import_node_fs42 = require("node:fs");
34965
+ var import_node_path38 = require("node:path");
34050
34966
  init_cli_shared();
34051
34967
  init_clean_exit();
34052
34968
  init_client_version();
@@ -34061,7 +34977,7 @@ function resolveReleaseBumpIntent(raw) {
34061
34977
  }
34062
34978
  function readRepoVersion() {
34063
34979
  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;
34980
+ return JSON.parse((0, import_node_fs42.readFileSync)((0, import_node_path38.join)(process.cwd(), ".claude-plugin", "plugin.json"), "utf8")).version || void 0;
34065
34981
  } catch {
34066
34982
  return void 0;
34067
34983
  }
@@ -34225,9 +35141,9 @@ function registerDeployCommands(program3) {
34225
35141
  init_cli_shared();
34226
35142
  init_github_client();
34227
35143
  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");
35144
+ var import_node_fs43 = require("node:fs");
35145
+ var import_node_os19 = require("node:os");
35146
+ var import_node_path39 = require("node:path");
34231
35147
  init_marketplace_autoupdate();
34232
35148
  var GC_GH_TIMEOUT_MS2 = 2e4;
34233
35149
  async function collectStatus() {
@@ -34346,7 +35262,7 @@ function onboardPluginGate(deps) {
34346
35262
  declared,
34347
35263
  settingsDeclared: readSettingsAutoUpdate(deps.readSettings(), MMI_MARKETPLACE_NAME)
34348
35264
  }).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" };
35265
+ 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
35266
  }
34351
35267
  async function collectOnboardStatus(opts = {}) {
34352
35268
  const cfg = await loadConfig();
@@ -34428,10 +35344,10 @@ async function collectOnboardStatus(opts = {}) {
34428
35344
  else if (top) nextCommand = `mmi-cli oracle board claim ${top.number} # ${top.title}`;
34429
35345
  else nextCommand = "mmi-cli oracle board read \u2014 no claimable items found";
34430
35346
  }
34431
- const home = (0, import_node_os17.homedir)();
35347
+ const home = (0, import_node_os19.homedir)();
34432
35348
  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)
35349
+ readKnown: () => readFileSyncSafe((0, import_node_path39.join)(home, ...KNOWN_MARKETPLACES_RELATIVE), import_node_fs43.readFileSync),
35350
+ readSettings: () => readFileSyncSafe((0, import_node_path39.join)(home, ".claude", "settings.json"), import_node_fs43.readFileSync)
34435
35351
  });
34436
35352
  return { track, board, registry: registry2, secrets, plugin, estateCli, doors: opts.doors ?? [], nextCommand };
34437
35353
  }
@@ -34514,12 +35430,17 @@ var LOOP_PLAYBOOKS = {
34514
35430
  { label: "Orient in the current repository", command: "mmi-cli onboard" },
34515
35431
  { 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
35432
  { label: "Read the next board item", command: "mmi-cli oracle board read" },
35433
+ // #5552: claim is a board mutation only — never guess `oracle issue claim`. Ground unknown write
35434
+ // routes with `mmi-cli commands` / `mmi-cli explain` before invoking them.
35435
+ { 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
35436
  { label: "Prepare the local workspace through the host surface" },
34518
35437
  { label: "Apply the repository test policy, then build the touched package", command: "mmi-cli tests policy --base origin/development && npm run build" },
34519
35438
  { label: "Publish the branch", command: "git push origin <branch>:<branch>" },
34520
35439
  { label: "Open the development-base PR", command: 'mmi-cli devops pr create --title "<title>" --body-file .jerv/PR_BODY.md --base development' },
34521
35440
  { 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" }
35441
+ { label: "Release only after the gated train is authorized", command: "mmi-cli devops release --apply" },
35442
+ // #5552: learning-tagged filings are cloud-agent owned — file and return to the current task.
35443
+ { 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
35444
  ]
34524
35445
  },
34525
35446
  "start-work": {
@@ -34527,6 +35448,7 @@ var LOOP_PLAYBOOKS = {
34527
35448
  steps: [
34528
35449
  { label: "Orient in the current repository", command: "mmi-cli onboard" },
34529
35450
  { label: "Read the board item", command: "mmi-cli oracle board show <issue-number>" },
35451
+ { label: "Claim the item (board mutation only \u2014 never `oracle issue claim`)", command: "mmi-cli oracle board claim <issue-number>" },
34530
35452
  { label: "Prepare the local workspace through the host surface" },
34531
35453
  { label: "Start a local stage (deployable repos)", command: "mmi-cli stage run --apply" }
34532
35454
  ]
@@ -35144,8 +36066,8 @@ function registerPrLifecycleCommands(program3) {
35144
36066
  }
35145
36067
 
35146
36068
  // src/post-merge-recon.ts
35147
- var import_node_fs41 = require("node:fs");
35148
- var import_node_path38 = require("node:path");
36069
+ var import_node_fs44 = require("node:fs");
36070
+ var import_node_path40 = require("node:path");
35149
36071
 
35150
36072
  // src/cross-repo-filing-issue.ts
35151
36073
  init_github_client();
@@ -35312,16 +36234,16 @@ function buildPostMergeReconRecovery(input) {
35312
36234
  }
35313
36235
  function writePostMergeReconRecovery(cwd, recovery) {
35314
36236
  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)}
36237
+ (0, import_node_fs44.mkdirSync)((0, import_node_path40.dirname)(path2), { recursive: true });
36238
+ (0, import_node_fs44.writeFileSync)(path2, `${JSON.stringify(recovery, null, 2)}
35317
36239
  `, "utf8");
35318
36240
  return path2;
35319
36241
  }
35320
36242
  function clearPostMergeReconRecovery(cwd, repo, pr2) {
35321
36243
  const path2 = postMergeReconStatePath(cwd, repo, pr2);
35322
- if (!(0, import_node_fs41.existsSync)(path2)) return;
36244
+ if (!(0, import_node_fs44.existsSync)(path2)) return;
35323
36245
  try {
35324
- (0, import_node_fs41.unlinkSync)(path2);
36246
+ (0, import_node_fs44.unlinkSync)(path2);
35325
36247
  } catch {
35326
36248
  }
35327
36249
  }
@@ -37140,7 +38062,8 @@ function diagnoseSurface(evidence) {
37140
38062
  const base = {
37141
38063
  descriptor: evidence.descriptor,
37142
38064
  ...evidence.installedVersion ? { installedVersion: evidence.installedVersion } : {},
37143
- ...evidence.releasedVersion ? { releasedVersion: evidence.releasedVersion } : {}
38065
+ ...evidence.releasedVersion ? { releasedVersion: evidence.releasedVersion } : {},
38066
+ ...evidence.receipt ? { receipt: evidence.receipt } : {}
37144
38067
  };
37145
38068
  if (!evidence.applicable) return { ...base, state: "skipped" };
37146
38069
  if (evidence.repair?.attempted && !evidence.repair.ok) {
@@ -37230,6 +38153,7 @@ function buildSurfaceDoctorCheck(diagnosis) {
37230
38153
  `install: ${descriptor.installMechanism} (${descriptor.installLocator})`,
37231
38154
  `repair owner: ${descriptor.repairOwner}`,
37232
38155
  `artifacts: ${descriptor.artifactIds.join(", ")}`,
38156
+ ...diagnosis.receipt ? [`receipt: ${diagnosis.receipt}`] : [],
37233
38157
  ...diagnosis.repairDetail ? [`heal: ${diagnosis.repairDetail}`] : []
37234
38158
  ]
37235
38159
  };
@@ -38445,18 +39369,18 @@ function parseOriginRepo(remoteUrl) {
38445
39369
  return `${match[1]}/${match[2]}`;
38446
39370
  }
38447
39371
  function ghHostsConfigPath(env, platform2) {
38448
- const sep3 = platform2 === "win32" ? "\\" : "/";
38449
- const join35 = (...parts) => parts.join(sep3);
39372
+ const sep4 = platform2 === "win32" ? "\\" : "/";
39373
+ const join36 = (...parts) => parts.join(sep4);
38450
39374
  const explicit = env.GH_CONFIG_DIR?.trim();
38451
- if (explicit) return join35(explicit, "hosts.yml");
39375
+ if (explicit) return join36(explicit, "hosts.yml");
38452
39376
  if (platform2 === "win32") {
38453
39377
  const appData = (env.AppData ?? env.APPDATA)?.trim();
38454
- return appData ? join35(appData, "GitHub CLI", "hosts.yml") : void 0;
39378
+ return appData ? join36(appData, "GitHub CLI", "hosts.yml") : void 0;
38455
39379
  }
38456
39380
  const xdg = env.XDG_CONFIG_HOME?.trim();
38457
- if (xdg) return join35(xdg, "gh", "hosts.yml");
39381
+ if (xdg) return join36(xdg, "gh", "hosts.yml");
38458
39382
  const home = env.HOME?.trim();
38459
- return home ? join35(home, ".config", "gh", "hosts.yml") : void 0;
39383
+ return home ? join36(home, ".config", "gh", "hosts.yml") : void 0;
38460
39384
  }
38461
39385
  function parseGhHostsAccounts(yaml, host = "github.com") {
38462
39386
  let hostIndent = null;
@@ -38506,19 +39430,41 @@ function ghAccountCaveat(announcedLogin, accounts) {
38506
39430
  }
38507
39431
 
38508
39432
  // 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");
39433
+ var import_node_fs45 = require("node:fs");
39434
+ var import_node_os20 = require("node:os");
39435
+ var import_node_path41 = require("node:path");
39436
+ var import_node_child_process20 = require("node:child_process");
38513
39437
  var import_node_util8 = require("node:util");
38514
39438
  init_version_lag();
38515
39439
  init_plugin_guard_io();
38516
- var execFileP6 = (0, import_node_util8.promisify)(import_node_child_process19.execFile);
39440
+
39441
+ // src/discard-sink.ts
39442
+ function nodeDiscardSinkPath(platform2 = process.platform) {
39443
+ return platform2 === "win32" ? "\\\\.\\NUL" : "/dev/null";
39444
+ }
39445
+
39446
+ // src/doctor-io.ts
39447
+ var execFileP6 = (0, import_node_util8.promisify)(import_node_child_process20.execFile);
39448
+ function execFileCapture(file, args, opts = {}) {
39449
+ const sink = nodeDiscardSinkPath();
39450
+ const inFd = (0, import_node_fs45.openSync)(sink, "r");
39451
+ const errFd = (0, import_node_fs45.openSync)(sink, "w");
39452
+ try {
39453
+ return (0, import_node_child_process20.execFileSync)(file, args, {
39454
+ ...opts,
39455
+ encoding: "utf8",
39456
+ stdio: [inFd, "pipe", errFd]
39457
+ });
39458
+ } finally {
39459
+ (0, import_node_fs45.closeSync)(inFd);
39460
+ (0, import_node_fs45.closeSync)(errFd);
39461
+ }
39462
+ }
38517
39463
  var MMI_PLUGIN_ID2 = "mmi@mutmutco";
38518
39464
  function installedClaudePluginVersion() {
38519
39465
  try {
38520
39466
  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")
39467
+ (0, import_node_fs45.readFileSync)((0, import_node_path41.join)((0, import_node_os20.homedir)(), ".claude", "plugins", "installed_plugins.json"), "utf8")
38522
39468
  );
38523
39469
  const versions = (file.plugins?.[MMI_PLUGIN_ID2] ?? []).map((r) => r.version).filter((v) => Boolean(v));
38524
39470
  if (versions.length === 0) return void 0;
@@ -38529,7 +39475,7 @@ function installedClaudePluginVersion() {
38529
39475
  }
38530
39476
  function manifestVersion(path2) {
38531
39477
  try {
38532
- const manifest = JSON.parse((0, import_node_fs42.readFileSync)(path2, "utf8"));
39478
+ const manifest = JSON.parse((0, import_node_fs45.readFileSync)(path2, "utf8"));
38533
39479
  return typeof manifest.version === "string" && manifest.version.trim() ? manifest.version.trim() : void 0;
38534
39480
  } catch {
38535
39481
  return void 0;
@@ -38538,12 +39484,12 @@ function manifestVersion(path2) {
38538
39484
  function readHermesPluginEvidence(env = process.env) {
38539
39485
  const host = hermesConfigRoot(env);
38540
39486
  const root = hermesPluginRoot(env);
38541
- const installRecordPresent = (0, import_node_fs42.existsSync)(root);
38542
- const manifestPath = (0, import_node_path39.join)(root, "plugin.yaml");
39487
+ const installRecordPresent = (0, import_node_fs45.existsSync)(root);
39488
+ const manifestPath = (0, import_node_path41.join)(root, "plugin.yaml");
38543
39489
  let installedVersion;
38544
39490
  let manifest = "missing";
38545
39491
  try {
38546
- const text = (0, import_node_fs42.readFileSync)(manifestPath, "utf8");
39492
+ const text = (0, import_node_fs45.readFileSync)(manifestPath, "utf8");
38547
39493
  let version;
38548
39494
  try {
38549
39495
  const parsed = JSON.parse(text).version;
@@ -38557,20 +39503,20 @@ function readHermesPluginEvidence(env = process.env) {
38557
39503
  if (version) {
38558
39504
  installedVersion = version;
38559
39505
  manifest = "valid";
38560
- } else if ((0, import_node_fs42.existsSync)(manifestPath)) manifest = "invalid";
39506
+ } else if ((0, import_node_fs45.existsSync)(manifestPath)) manifest = "invalid";
38561
39507
  } catch {
38562
- if ((0, import_node_fs42.existsSync)(manifestPath)) manifest = "invalid";
39508
+ if ((0, import_node_fs45.existsSync)(manifestPath)) manifest = "invalid";
38563
39509
  }
38564
39510
  let skills = false;
38565
39511
  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();
39512
+ 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
39513
  } catch {
38568
39514
  }
38569
39515
  return {
38570
- hostPresent: (0, import_node_fs42.existsSync)(host),
39516
+ hostPresent: (0, import_node_fs45.existsSync)(host),
38571
39517
  installRecordPresent,
38572
39518
  manifest,
38573
- payloadPresent: (0, import_node_fs42.existsSync)((0, import_node_path39.join)(root, "__init__.py")) && skills && manifest === "valid",
39519
+ payloadPresent: (0, import_node_fs45.existsSync)((0, import_node_path41.join)(root, "__init__.py")) && skills && manifest === "valid",
38574
39520
  ...installedVersion ? { installedVersion } : {}
38575
39521
  };
38576
39522
  }
@@ -38578,7 +39524,7 @@ function installedSurfacePluginVersion(surface) {
38578
39524
  const token = surfaceToken(surface);
38579
39525
  if (token === "kilo") {
38580
39526
  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();
39527
+ const stamp = (0, import_node_fs45.readFileSync)((0, import_node_path41.join)((0, import_node_os20.homedir)(), ".kilo", ".mmi-kilo-version"), "utf8").trim();
38582
39528
  return stamp || void 0;
38583
39529
  } catch {
38584
39530
  return void 0;
@@ -38586,25 +39532,21 @@ function installedSurfacePluginVersion(surface) {
38586
39532
  }
38587
39533
  if (token === "hermes") return readHermesPluginEvidence().installedVersion;
38588
39534
  if (token === "cursor") {
38589
- return manifestVersion((0, import_node_path39.join)(cursorLocalPluginRoot(), ".cursor-plugin", "plugin.json"));
39535
+ return manifestVersion((0, import_node_path41.join)(cursorLocalPluginRoot(), ".cursor-plugin", "plugin.json"));
38590
39536
  }
38591
39537
  if (token === "jervcode") {
38592
39538
  return installedJervCodePackageVersion();
38593
39539
  }
38594
39540
  if (token === "kimi") {
38595
- return manifestVersion((0, import_node_path39.join)(surfaceConfigRoot(surface), "plugins", "managed", "mmi", ".kimi-plugin", "plugin.json"));
39541
+ return manifestVersion((0, import_node_path41.join)(surfaceConfigRoot(surface), "plugins", "managed", "mmi", ".kimi-plugin", "plugin.json"));
38596
39542
  }
38597
39543
  if (token === "claude") return installedClaudePluginVersion();
38598
39544
  if (token !== "codex") return void 0;
38599
39545
  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"],
39546
+ const raw = process.platform === "win32" ? execFileCapture("cmd.exe", ["/c", "codex", "plugin", "list", "--json"], {
38603
39547
  timeout: 15e3,
38604
39548
  windowsHide: true
38605
- }) : (0, import_node_child_process19.execFileSync)("codex", ["plugin", "list", "--json"], {
38606
- encoding: "utf8",
38607
- stdio: ["ignore", "pipe", "ignore"],
39549
+ }) : execFileCapture("codex", ["plugin", "list", "--json"], {
38608
39550
  timeout: 15e3,
38609
39551
  windowsHide: true
38610
39552
  });
@@ -38620,7 +39562,7 @@ function installedActivePluginVersion(surface = detectSurface(process.env)) {
38620
39562
  }
38621
39563
  function worktreeRootSync() {
38622
39564
  try {
38623
- const out = (0, import_node_child_process19.execFileSync)("git", ["rev-parse", "--show-toplevel"], { windowsHide: true, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
39565
+ const out = execFileCapture("git", ["rev-parse", "--show-toplevel"], { windowsHide: true });
38624
39566
  let root = out.endsWith("\n") ? out.slice(0, -1) : out;
38625
39567
  if (process.platform === "win32" && root.endsWith("\r")) root = root.slice(0, -1);
38626
39568
  return root || null;
@@ -38630,13 +39572,13 @@ function worktreeRootSync() {
38630
39572
  }
38631
39573
  var gitignorePath = () => {
38632
39574
  const root = worktreeRootSync();
38633
- return root === null ? null : (0, import_node_path39.join)(root, ".gitignore");
39575
+ return root === null ? null : (0, import_node_path41.join)(root, ".gitignore");
38634
39576
  };
38635
39577
  function readGitignore() {
38636
39578
  const path2 = gitignorePath();
38637
39579
  if (path2 === null) return null;
38638
39580
  try {
38639
- return (0, import_node_fs42.readFileSync)(path2, "utf8");
39581
+ return (0, import_node_fs45.readFileSync)(path2, "utf8");
38640
39582
  } catch {
38641
39583
  return null;
38642
39584
  }
@@ -38645,19 +39587,17 @@ function writeGitignore(content) {
38645
39587
  const path2 = gitignorePath();
38646
39588
  if (path2 === null) return false;
38647
39589
  try {
38648
- (0, import_node_fs42.writeFileSync)(path2, content, "utf8");
39590
+ (0, import_node_fs45.writeFileSync)(path2, content, "utf8");
38649
39591
  return true;
38650
39592
  } catch {
38651
39593
  return false;
38652
39594
  }
38653
39595
  }
38654
39596
  function lineEndingState(root) {
38655
- const attributesPresent = (0, import_node_fs42.existsSync)((0, import_node_path39.join)(root, ".gitattributes"));
39597
+ const attributesPresent = (0, import_node_fs45.existsSync)((0, import_node_path41.join)(root, ".gitattributes"));
38656
39598
  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"]
39599
+ const output = execFileCapture("git", ["-C", root, "ls-files", "--eol", "--", ":(glob)**/*.sh"], {
39600
+ windowsHide: true
38661
39601
  });
38662
39602
  const crlfShellScripts = output.split(/\r?\n/).filter((line) => line.startsWith("i/crlf ")).map((line) => line.slice(line.indexOf(" ") + 1)).filter(Boolean);
38663
39603
  return { attributesPresent, crlfShellScripts };
@@ -38721,8 +39661,8 @@ ${r.stderr ?? ""}`).catch(() => "");
38721
39661
  function ghMultiAccountCaveat(announcedLogin) {
38722
39662
  try {
38723
39663
  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")));
39664
+ if (!hostsPath || !(0, import_node_fs46.existsSync)(hostsPath)) return void 0;
39665
+ return ghAccountCaveat(announcedLogin, parseGhHostsAccounts((0, import_node_fs46.readFileSync)(hostsPath, "utf8")));
38726
39666
  } catch {
38727
39667
  return void 0;
38728
39668
  }
@@ -38730,12 +39670,12 @@ function ghMultiAccountCaveat(announcedLogin) {
38730
39670
  var ENV_HEAL_LOCK_STALE_MS = 10 * 6e4;
38731
39671
  var ENV_HEAL_LOCK_MAX_WAIT_MS = 2 * 6e4;
38732
39672
  function envHealLockPath(home) {
38733
- return (0, import_node_path40.join)(home, ".claude", "plugins", ".mmi-env-heal.lock");
39673
+ return (0, import_node_path42.join)(home, ".claude", "plugins", ".mmi-env-heal.lock");
38734
39674
  }
38735
39675
  async function withEnvHealLock(what, run) {
38736
39676
  try {
38737
39677
  return await withFileLock(
38738
- envHealLockPath((0, import_node_os19.homedir)()),
39678
+ envHealLockPath((0, import_node_os21.homedir)()),
38739
39679
  { staleMs: ENV_HEAL_LOCK_STALE_MS, maxWaitMs: ENV_HEAL_LOCK_MAX_WAIT_MS, label: "mmi env-heal lock" },
38740
39680
  run
38741
39681
  );
@@ -38777,17 +39717,19 @@ function mmiDoctorDeps(opts = {}) {
38777
39717
  const descriptor = doctorSurface(token);
38778
39718
  const snapshot = snapshotPluginGuardInput(runtimeSurface, isOrgRepo);
38779
39719
  const hermes = token === "hermes" ? readHermesPluginEvidence(process.env) : void 0;
39720
+ const kimi = token === "kimi" ? kimiPluginHostEvidence(surfaceConfigRoot("kimi")) : void 0;
38780
39721
  const installedVersion = hermes?.installedVersion ?? installedSurfacePluginVersion(runtimeSurface);
38781
39722
  surfaceEvidence = {
38782
39723
  descriptor,
38783
39724
  // Hermes' root is the host evidence: a configured but uninstalled MMI tree is missing, while an
38784
39725
  // absent root is skipped. Other mature surfaces retain their established org/install applicability.
38785
39726
  applicable: hermes ? hermes.hostPresent : isOrgRepo || snapshot.installRecordPresent || snapshot.pluginCachePresent,
38786
- installRecordPresent: hermes?.installRecordPresent ?? snapshot.installRecordPresent,
39727
+ installRecordPresent: hermes?.installRecordPresent ?? (kimi ? kimi.registration === "healthy" : snapshot.installRecordPresent),
38787
39728
  deliveryPresent: hermes ? hermes.installRecordPresent : snapshot.marketplaceClonePresent,
38788
- payloadPresent: hermes?.payloadPresent ?? snapshot.pluginCachePresent,
39729
+ payloadPresent: hermes?.payloadPresent ?? (kimi ? kimi.healthy : snapshot.pluginCachePresent),
38789
39730
  manifest: hermes?.manifest ?? (installedVersion ? "valid" : snapshot.installRecordPresent ? "invalid" : "missing"),
38790
39731
  guardState: buildPluginGuardDecision(snapshot).state,
39732
+ ...kimi?.receipt ? { receipt: kimi.receipt } : {},
38791
39733
  ...installedVersion ? { installedVersion } : {}
38792
39734
  };
38793
39735
  return surfaceEvidence;
@@ -38830,7 +39772,7 @@ function mmiDoctorDeps(opts = {}) {
38830
39772
  const configRoot = surfaceConfigRoot(surface);
38831
39773
  const running = surface === "codex" ? installedActivePluginVersion(surface) : runningPluginVersion(process.env, resolveClientVersion());
38832
39774
  const plan = buildPluginCachePlan(
38833
- (0, import_node_os19.homedir)(),
39775
+ (0, import_node_os21.homedir)(),
38834
39776
  running,
38835
39777
  pluginCacheFsDeps(configRoot, () => 0),
38836
39778
  { configRoot, includeStaging: surface !== "codex" }
@@ -38858,14 +39800,14 @@ function mmiDoctorDeps(opts = {}) {
38858
39800
  const running = surface === "codex" ? installedActivePluginVersion(surface) : runningPluginVersion(process.env, resolveClientVersion());
38859
39801
  const installed = installedActivePluginVersion(surface);
38860
39802
  const plan = buildPluginCachePlan(
38861
- (0, import_node_os19.homedir)(),
39803
+ (0, import_node_os21.homedir)(),
38862
39804
  running,
38863
39805
  pluginCacheFsDeps(configRoot, () => 0),
38864
39806
  { configRoot, includeStaging: surface !== "codex", installedVersion: installed }
38865
39807
  );
38866
39808
  const result = applyPluginCachePlan(
38867
39809
  plan,
38868
- (p) => (0, import_node_fs43.rmSync)(p, { recursive: true }),
39810
+ (p) => (0, import_node_fs46.rmSync)(p, { recursive: true }),
38869
39811
  stagingApplyFsGuard(configRoot)
38870
39812
  );
38871
39813
  return {
@@ -38903,7 +39845,7 @@ function mmiDoctorDeps(opts = {}) {
38903
39845
  // adopted the generated routing index (MMG-Unlive: `docs/Archive/**` only, no index, no gate.yml) would
38904
39846
  // get a permanent — demanding an artifact it never asked for.
38905
39847
  docsIndexState: (root) => {
38906
- if (!(0, import_node_fs43.existsSync)((0, import_node_path40.join)(root, DOCS_INDEX_PATH))) return void 0;
39848
+ if (!(0, import_node_fs46.existsSync)((0, import_node_path42.join)(root, DOCS_INDEX_PATH))) return void 0;
38907
39849
  const real = createDocsIndexDeps(root);
38908
39850
  let docs2;
38909
39851
  const listDocs = () => docs2 ??= real.listDocs();
@@ -38912,7 +39854,7 @@ function mmiDoctorDeps(opts = {}) {
38912
39854
  },
38913
39855
  // #4168: working-tree heal — write if drifted, then re-check. Commit remains the operator's step.
38914
39856
  healDocsIndex: (root) => {
38915
- if (!(0, import_node_fs43.existsSync)((0, import_node_path40.join)(root, DOCS_INDEX_PATH))) return { drift: false, docCount: 0 };
39857
+ if (!(0, import_node_fs46.existsSync)((0, import_node_path42.join)(root, DOCS_INDEX_PATH))) return { drift: false, docCount: 0 };
38916
39858
  const real = createDocsIndexDeps(root);
38917
39859
  let docs2;
38918
39860
  const listDocs = () => docs2 ??= real.listDocs();
@@ -38933,8 +39875,8 @@ function mmiDoctorDeps(opts = {}) {
38933
39875
  });
38934
39876
  const raced = await Promise.race([
38935
39877
  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);
39878
+ new Promise((resolve6) => {
39879
+ ceiling = setTimeout(() => resolve6({ timedOut: true, scanned: 0, findings: 0, fixed: 0, failed: 0 }), BOARD_DOCTOR_TIMEOUT_MS);
38938
39880
  })
38939
39881
  ]);
38940
39882
  if (raced.timedOut) return { scanned: 0, findings: 0, fixed: 0, failed: 0, timedOut: true };
@@ -38967,8 +39909,8 @@ function mmiDoctorDeps(opts = {}) {
38967
39909
  incomplete: nb.incomplete,
38968
39910
  timedOut: false
38969
39911
  })),
38970
- new Promise((resolve5) => {
38971
- ceiling = setTimeout(() => resolve5({ driftLines: [], incomplete: [], timedOut: true }), SCHEDULES_DRIFT_TIMEOUT_MS);
39912
+ new Promise((resolve6) => {
39913
+ ceiling = setTimeout(() => resolve6({ driftLines: [], incomplete: [], timedOut: true }), SCHEDULES_DRIFT_TIMEOUT_MS);
38972
39914
  })
38973
39915
  ]);
38974
39916
  if (!raced.timedOut && raced.incomplete.length === 0) writeSchedulesDriftCache(cachePath, raced.driftLines);
@@ -39004,7 +39946,7 @@ function mmiDoctorDeps(opts = {}) {
39004
39946
  repoIndexCloudState: async (root) => {
39005
39947
  let localV4 = { state: "absent" };
39006
39948
  try {
39007
- const parsed = JSON.parse((0, import_node_fs43.readFileSync)(repoIndexV4StorePath(root), "utf8"));
39949
+ const parsed = JSON.parse((0, import_node_fs46.readFileSync)(repoIndexV4StorePath(root), "utf8"));
39008
39950
  const state = parsed.status?.state;
39009
39951
  if (parsed.schemaVersion === 4 && (state === "ready" || state === "degraded")) {
39010
39952
  const chunks = parsed.manifest?.chunks?.length ?? 0;
@@ -39297,19 +40239,19 @@ program2.hook("preAction", async (_thisCommand, actionCommand) => {
39297
40239
  });
39298
40240
  var rules = program2.command("rules").description("org-managed .gitignore delivery");
39299
40241
  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;
40242
+ const path2 = (0, import_node_path42.join)(process.cwd(), ".gitignore");
40243
+ const current = (0, import_node_fs46.existsSync)(path2) ? (0, import_node_fs46.readFileSync)(path2, "utf8") : null;
39302
40244
  const plan = planManagedGitignore(current);
39303
40245
  const drift = [...plan.added.map((l) => `+${l}`), ...plan.removed.map((l) => `-${l}`)].join(", ") || "block normalize";
39304
40246
  if (opts.json) {
39305
- if (opts.write && plan.changed) (0, import_node_fs43.writeFileSync)(path2, plan.content, "utf8");
40247
+ if (opts.write && plan.changed) (0, import_node_fs46.writeFileSync)(path2, plan.content, "utf8");
39306
40248
  console.log(JSON.stringify(plan, null, 2));
39307
40249
  if (!opts.write && plan.changed) process.exitCode = 1;
39308
40250
  return;
39309
40251
  }
39310
40252
  if (opts.write) {
39311
40253
  if (plan.changed) {
39312
- (0, import_node_fs43.writeFileSync)(path2, plan.content, "utf8");
40254
+ (0, import_node_fs46.writeFileSync)(path2, plan.content, "utf8");
39313
40255
  console.log(`mmi-cli devops org rules gitignore: updated .gitignore (${drift})`);
39314
40256
  } else {
39315
40257
  console.log("mmi-cli devops org rules gitignore: up to date");
@@ -39495,7 +40437,7 @@ function scheduleRelatedDiscovery(o) {
39495
40437
  try {
39496
40438
  const args = ["issue", "discover-related", "--number", String(o.number), "--title", o.title, "--body", o.body, "--fail-soft"];
39497
40439
  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() });
40440
+ spawnDetachedSelf(args, { spawn: import_node_child_process21.spawn, execPath: process.execPath, scriptPath: process.argv[1] }, { cwd: process.cwd() });
39499
40441
  } catch {
39500
40442
  }
39501
40443
  }
@@ -39895,6 +40837,20 @@ tests.command("policy").description("enforce this repo's test-policy.json agains
39895
40837
  await failGraceful(e.message);
39896
40838
  }
39897
40839
  });
40840
+ 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");
40841
+ 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) => {
40842
+ try {
40843
+ const root = await repoRoot();
40844
+ const receipt = runDistStatus(root);
40845
+ if (o.json) {
40846
+ consoleIo.log(JSON.stringify({ ok: true, staleCount: receipt.staleCount, artifacts: receipt.artifacts, bom: receipt.bom, summary: receipt.summary }, null, 2));
40847
+ return;
40848
+ }
40849
+ for (const line of renderDistDriftReceipt(receipt)) console.log(line);
40850
+ } catch (e) {
40851
+ await failGraceful(`dist status: ${e.message}`);
40852
+ }
40853
+ });
39898
40854
  async function reportWrite(label, res) {
39899
40855
  if (res.ok) {
39900
40856
  console.log(JSON.stringify(res.body));
@@ -40167,7 +41123,7 @@ project.command("set [owner/repo]").description("upsert project META (idempotent
40167
41123
  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
41124
  if (o.secretsFile) {
40169
41125
  try {
40170
- vars.push(`secrets=${(0, import_node_fs43.readFileSync)(o.secretsFile, "utf8")}`);
41126
+ vars.push(`secrets=${(0, import_node_fs46.readFileSync)(o.secretsFile, "utf8")}`);
40171
41127
  } catch (e) {
40172
41128
  return fail(`org project set: cannot read --secrets-file ${o.secretsFile}: ${e.message}`);
40173
41129
  }
@@ -40517,7 +41473,7 @@ function resolveCreateSurface(opts) {
40517
41473
  function surfaceWaived() {
40518
41474
  return rawFlag("--no-surface");
40519
41475
  }
40520
- var issue = program2.command("issue").description("issues \u2014 create and view with structured JSON (view; show is an alias for board-verb callers)");
41476
+ 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
41477
  withExamples(mutating(
40522
41478
  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
41479
  // --dry-run/--validate-only plan: resolve the same title source and validate the same type, priority,
@@ -40821,7 +41777,7 @@ ${list}`);
40821
41777
  }
40822
41778
  console.log(JSON.stringify({ number: parsed.number, repo, item: result.item.text, checked, changed: true }));
40823
41779
  });
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) => {
41780
+ 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
41781
  let body;
40826
41782
  let priority;
40827
41783
  let title;
@@ -40937,6 +41893,7 @@ program2.command("skill-lesson").description("file a skill-lesson on the Hub boa
40937
41893
  }
40938
41894
  const created = requireGhCreateOk(await ghCreate(args), "skill-lesson");
40939
41895
  const { projectItemId, onBoard } = await attachToProject(created.number, targetRepo3, priority);
41896
+ console.log(JSON.stringify({ ...created, projectItemId, onBoard }));
40940
41897
  });
40941
41898
  var pr = program2.command("pr").description("pull requests \u2014 reliable create with structured output");
40942
41899
  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 +41948,11 @@ pr.command("view <number>").description("read a PR as structured JSON (merged st
40991
41948
  }
40992
41949
  });
40993
41950
  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) => {
41951
+ const wfDir = (0, import_node_path42.join)(cwd, ".github", "workflows");
41952
+ if (!(0, import_node_fs46.existsSync)(wfDir)) return [];
41953
+ return (0, import_node_fs46.readdirSync)(wfDir).filter((name) => /\.(ya?ml)$/i.test(name)).filter((name) => {
40997
41954
  try {
40998
- return workflowReportsPrChecks((0, import_node_fs43.readFileSync)((0, import_node_path40.join)(wfDir, name), "utf8"));
41955
+ return workflowReportsPrChecks((0, import_node_fs46.readFileSync)((0, import_node_path42.join)(wfDir, name), "utf8"));
40999
41956
  } catch {
41000
41957
  return true;
41001
41958
  }
@@ -41047,16 +42004,16 @@ function ciAuditDeps() {
41047
42004
  // gate re-seed step is skipped gracefully rather than failing mid-run.
41048
42005
  readSeedFile: (path2) => {
41049
42006
  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;
42007
+ const fullPath = (0, import_node_path42.join)(root, path2);
42008
+ return (0, import_node_fs46.existsSync)(fullPath) ? (0, import_node_fs46.readFileSync)(fullPath, "utf8") : null;
41052
42009
  }
41053
42010
  };
41054
42011
  }
41055
42012
  function hubRoot() {
41056
- const fromPkg = (0, import_node_path40.join)(__dirname, "..", "..");
42013
+ const fromPkg = (0, import_node_path42.join)(__dirname, "..", "..");
41057
42014
  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();
42015
+ if ((0, import_node_fs46.existsSync)((0, import_node_path42.join)(fromPkg, marker))) return fromPkg;
42016
+ if ((0, import_node_fs46.existsSync)((0, import_node_path42.join)(process.cwd(), marker))) return process.cwd();
41060
42017
  return null;
41061
42018
  }
41062
42019
  async function waitLoopCorePool(label) {
@@ -41105,7 +42062,12 @@ pr.command("checks-wait <number>").description(`bounded wait for ALL checks on t
41105
42062
  timeoutMs = Math.round(minutes * 6e4);
41106
42063
  }
41107
42064
  const repo = await requireRepo(o.repo);
41108
- const snapshotRead = await readRestPrSnapshotWithRetry(number, repo);
42065
+ const budgetMs = timeoutMs ?? PR_CHECKS_TIMEOUT_MS;
42066
+ const waitStarted = Date.now();
42067
+ const snapshotRead = await readRestPrSnapshotWithRetry(number, repo, void 0, {
42068
+ retryTransientUntilMs: waitStarted + budgetMs,
42069
+ onTransientRetry: (error, attempt) => console.warn(`pr checks-wait: transient GitHub read (${error}) \u2014 retrying PR snapshot (attempt ${attempt}) within the wait budget`)
42070
+ });
41109
42071
  if (snapshotRead.state === "failed") {
41110
42072
  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
42073
  }
@@ -41126,9 +42088,9 @@ pr.command("checks-wait <number>").description(`bounded wait for ALL checks on t
41126
42088
  // #5400: after grace, name "GitHub delivered zero runs" instead of burning the full budget as pending.
41127
42089
  pollHeadWorkflowRunCount: () => waitLoopHeadWorkflowRunCount("pr checks-wait", number, repo),
41128
42090
  baseBranch,
41129
- sleep: (ms) => new Promise((resolve5) => setTimeout(resolve5, ms)),
42091
+ sleep: (ms) => new Promise((resolve6) => setTimeout(resolve6, ms)),
41130
42092
  log: (message) => console.warn(message),
41131
- timeoutMs,
42093
+ timeoutMs: Math.max(1, budgetMs - (Date.now() - waitStarted)),
41132
42094
  // Liveness on stderr, one line per poll. A silent bounded wait is indistinguishable from a hang, and
41133
42095
  // an agent harness kills it on its own (shorter) deadline before the verdict ever prints (#2940).
41134
42096
  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 +42184,7 @@ pr.command("land <number>").description("agent merge path (#1440): train probe \
41222
42184
  // #5400: same zero-runs delivery probe as checks-wait — do not burn the land budget on silence.
41223
42185
  pollHeadWorkflowRunCount: () => waitLoopHeadWorkflowRunCount("pr land", prNumber, repo),
41224
42186
  baseBranch: "development",
41225
- sleep: (ms) => new Promise((resolve5) => setTimeout(resolve5, ms)),
42187
+ sleep: (ms) => new Promise((resolve6) => setTimeout(resolve6, ms)),
41226
42188
  log: (message) => console.warn(message),
41227
42189
  // `pr land` inherits the same (raised) checks budget, so it needs the same liveness — otherwise the
41228
42190
  // 30m wait is SILENT and reads exactly like the hang #2940 was filed about, only three times longer.
@@ -41265,7 +42227,7 @@ pr.command("land <number>").description("agent merge path (#1440): train probe \
41265
42227
  } else {
41266
42228
  lastFailure = void 0;
41267
42229
  }
41268
- await new Promise((resolve5) => setTimeout(resolve5, PR_LAND_POLL_MS));
42230
+ await new Promise((resolve6) => setTimeout(resolve6, PR_LAND_POLL_MS));
41269
42231
  }
41270
42232
  if (lastFailure) {
41271
42233
  throw new Error(
@@ -41349,7 +42311,12 @@ jsonParity(pr.command("merge <number>").description("merge a PR (squash by defau
41349
42311
  const ciPolicy = await resolveMergeCiPolicyForCheckout(o.repo, ciHeadRef);
41350
42312
  if (o.wait) {
41351
42313
  const repo = await requireRepo(o.repo);
41352
- const snapshotRead = await readRestPrSnapshotWithRetry(number, repo);
42314
+ const budgetMs = PR_CHECKS_TIMEOUT_MS;
42315
+ const waitStarted = Date.now();
42316
+ const snapshotRead = await readRestPrSnapshotWithRetry(number, repo, void 0, {
42317
+ retryTransientUntilMs: waitStarted + budgetMs,
42318
+ onTransientRetry: (error, attempt) => console.warn(`pr merge: transient GitHub read (${error}) \u2014 retrying PR snapshot (attempt ${attempt}) within the --wait budget`)
42319
+ });
41353
42320
  if (snapshotRead.state === "failed") {
41354
42321
  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
42322
  process.exitCode = 1;
@@ -41365,8 +42332,9 @@ jsonParity(pr.command("merge <number>").description("merge a PR (squash by defau
41365
42332
  diagnoseFailure: () => waitLoopDiagnosis("pr merge --wait", number, repo),
41366
42333
  pollHeadWorkflowRunCount: () => waitLoopHeadWorkflowRunCount("pr merge --wait", number, repo),
41367
42334
  baseBranch,
41368
- sleep: (ms) => new Promise((resolve5) => setTimeout(resolve5, ms)),
42335
+ sleep: (ms) => new Promise((resolve6) => setTimeout(resolve6, ms)),
41369
42336
  log: (message) => console.warn(message),
42337
+ timeoutMs: Math.max(1, budgetMs - (Date.now() - waitStarted)),
41370
42338
  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
42339
  });
41372
42340
  if (wait.status !== "success" && wait.status !== "skipped") {
@@ -41400,7 +42368,7 @@ jsonParity(pr.command("merge <number>").description("merge a PR (squash by defau
41400
42368
  }
41401
42369
  if (!repoForPostCleanup) throw e;
41402
42370
  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;
42371
+ const commitMessage = bodyFile ? (0, import_node_fs46.readFileSync)(bodyFile, "utf8") : void 0;
41404
42372
  await defaultGitHubClient().rest("PUT", `repos/${repoForPostCleanup}/pulls/${number}/merge`, {
41405
42373
  body: { merge_method: method.slice(2), ...commitMessage ? { commit_message: commitMessage } : {} },
41406
42374
  timeoutMs: GH_MUTATION_TIMEOUT_MS
@@ -42096,12 +43064,12 @@ access.command("audit").description("audit collaborator roles + train-branch pus
42096
43064
  targets = resolution.targets;
42097
43065
  }
42098
43066
  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")) : {};
43067
+ const fileMatrix = (0, import_node_fs46.existsSync)("access-matrix.json") ? loadAccessMatrix((0, import_node_fs46.readFileSync)("access-matrix.json", "utf8")) : {};
42100
43068
  const matrix = mergeAccessMatrix(fileMatrix, derivedMatrix);
42101
43069
  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: {} };
43070
+ const fileContracts = (0, import_node_fs46.existsSync)("data-access-contracts.json") ? loadDataAccessContracts((0, import_node_fs46.readFileSync)("data-access-contracts.json", "utf8")) : { consumers: {} };
42103
43071
  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")) : {};
43072
+ const sanctioned = (0, import_node_fs46.existsSync)("access-matrix.json") ? loadSanctionedAdmins((0, import_node_fs46.readFileSync)("access-matrix.json", "utf8")) : {};
42105
43073
  const report = await auditOrgAccess(targets, deps, matrix, dataAccess, sanctioned);
42106
43074
  console.log(o.json ? JSON.stringify(report, null, 2) : renderAccessReport(report));
42107
43075
  if (!report.ok) process.exitCode = 1;
@@ -42133,16 +43101,16 @@ function directoryBytes(path2) {
42133
43101
  let total = 0;
42134
43102
  let entries;
42135
43103
  try {
42136
- entries = (0, import_node_fs43.readdirSync)(path2, { withFileTypes: true });
43104
+ entries = (0, import_node_fs46.readdirSync)(path2, { withFileTypes: true });
42137
43105
  } catch {
42138
43106
  return 0;
42139
43107
  }
42140
43108
  for (const entry of entries) {
42141
- const child2 = (0, import_node_path40.join)(path2, entry.name);
43109
+ const child2 = (0, import_node_path42.join)(path2, entry.name);
42142
43110
  if (entry.isDirectory()) total += directoryBytes(child2);
42143
43111
  else {
42144
43112
  try {
42145
- total += (0, import_node_fs43.statSync)(child2).size;
43113
+ total += (0, import_node_fs46.statSync)(child2).size;
42146
43114
  } catch {
42147
43115
  }
42148
43116
  }
@@ -42150,25 +43118,25 @@ function directoryBytes(path2) {
42150
43118
  return total;
42151
43119
  }
42152
43120
  function listDirEntries(dir) {
42153
- return (0, import_node_fs43.readdirSync)(dir, { withFileTypes: true }).map((d) => ({ name: d.name, isDirectory: d.isDirectory() }));
43121
+ return (0, import_node_fs46.readdirSync)(dir, { withFileTypes: true }).map((d) => ({ name: d.name, isDirectory: d.isDirectory() }));
42154
43122
  }
42155
43123
  function readInstalledPluginRefs(configRoot) {
42156
43124
  const p = installedPluginsPathForConfig(configRoot);
42157
- if (!(0, import_node_fs43.existsSync)(p)) return [];
43125
+ if (!(0, import_node_fs46.existsSync)(p)) return [];
42158
43126
  try {
42159
- return installedPluginPaths((0, import_node_fs43.readFileSync)(p, "utf8"));
43127
+ return installedPluginPaths((0, import_node_fs46.readFileSync)(p, "utf8"));
42160
43128
  } catch {
42161
43129
  return null;
42162
43130
  }
42163
43131
  }
42164
43132
  function pluginCacheFsDeps(configRoot, dirBytes) {
42165
43133
  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),
43134
+ exists: (p) => (0, import_node_fs46.existsSync)(p),
43135
+ listVersionDirs: (root) => (0, import_node_fs46.readdirSync)(root, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name),
42168
43136
  dirBytes,
42169
- listStagingDirs: (root) => (0, import_node_fs43.readdirSync)(root, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => {
43137
+ listStagingDirs: (root) => (0, import_node_fs46.readdirSync)(root, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => {
42170
43138
  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) };
43139
+ return { name: d.name, mtimeMs: newestMtimeMs((0, import_node_path42.join)(root, d.name), listDirEntries, (p) => (0, import_node_fs46.statSync)(p).mtimeMs) };
42172
43140
  } catch {
42173
43141
  return { name: d.name, mtimeMs: Date.now() };
42174
43142
  }
@@ -42182,10 +43150,10 @@ function stagingApplyFsGuard(configRoot) {
42182
43150
  return {
42183
43151
  referencedPaths: () => readInstalledPluginRefs(configRoot),
42184
43152
  mtimeMs: (name) => {
42185
- const p = (0, import_node_path40.join)(stagingRoot, name);
42186
- if (!(0, import_node_fs43.existsSync)(p)) return null;
43153
+ const p = (0, import_node_path42.join)(stagingRoot, name);
43154
+ if (!(0, import_node_fs46.existsSync)(p)) return null;
42187
43155
  try {
42188
- return newestMtimeMs(p, listDirEntries, (q) => (0, import_node_fs43.statSync)(q).mtimeMs);
43156
+ return newestMtimeMs(p, listDirEntries, (q) => (0, import_node_fs46.statSync)(q).mtimeMs);
42189
43157
  } catch {
42190
43158
  return null;
42191
43159
  }
@@ -42205,13 +43173,13 @@ program2.command("plugin-prune").description(`prune stale cached MMI plugin vers
42205
43173
  return;
42206
43174
  }
42207
43175
  const plan = buildPluginCachePlan(
42208
- (0, import_node_os19.homedir)(),
43176
+ (0, import_node_os21.homedir)(),
42209
43177
  running,
42210
43178
  pluginCacheFsDeps(configRoot, directoryBytes),
42211
43179
  { withBytes: true, configRoot, includeStaging: surface !== "codex" }
42212
43180
  );
42213
43181
  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;
43182
+ const result = o.apply && anythingToDelete ? applyPluginCachePlan(plan, (p) => (0, import_node_fs46.rmSync)(p, { recursive: true, force: true }), stagingApplyFsGuard(configRoot)) : void 0;
42215
43183
  const warnings = plan.prune.length > 0 ? [CONCURRENT_SESSION_WARNING] : [];
42216
43184
  if (o.json) console.log(JSON.stringify({ ...plan, warnings, applied: result ?? null }));
42217
43185
  else console.log(renderPluginCachePlan(plan, result));