@mutmutco/cli 4.1.2 → 4.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/main.cjs CHANGED
@@ -151,19 +151,19 @@ function hardExit(code) {
151
151
  process.exit(code);
152
152
  }
153
153
  function flushStream(stream) {
154
- return new Promise((resolve5) => {
154
+ return new Promise((resolve6) => {
155
155
  try {
156
- stream.write("", () => resolve5());
156
+ stream.write("", () => resolve6());
157
157
  } catch {
158
- resolve5();
158
+ resolve6();
159
159
  }
160
160
  });
161
161
  }
162
162
  async function flushStdio(timeoutMs = STDIO_FLUSH_TIMEOUT_MS) {
163
163
  await Promise.race([
164
164
  Promise.all([flushStream(process.stdout), flushStream(process.stderr)]),
165
- new Promise((resolve5) => {
166
- setTimeout(resolve5, timeoutMs).unref?.();
165
+ new Promise((resolve6) => {
166
+ setTimeout(resolve6, timeoutMs).unref?.();
167
167
  })
168
168
  ]);
169
169
  }
@@ -171,7 +171,7 @@ async function cleanExit(code) {
171
171
  process.exitCode = code;
172
172
  await closeHttpPool();
173
173
  await flushStdio();
174
- await new Promise((resolve5) => setImmediate(resolve5));
174
+ await new Promise((resolve6) => setImmediate(resolve6));
175
175
  return void 0;
176
176
  }
177
177
  async function finishCliRun(watchdogMs = CLI_EXIT_WATCHDOG_MS) {
@@ -476,7 +476,7 @@ function createGitHubClient(options = {}) {
476
476
  if (!res.ok) throw await errorFromResponse(res);
477
477
  return res;
478
478
  }
479
- async function parseJson(res) {
479
+ async function parseJson2(res) {
480
480
  if (res.status === 204) return void 0;
481
481
  const text = await res.text();
482
482
  if (!text) return void 0;
@@ -485,14 +485,14 @@ function createGitHubClient(options = {}) {
485
485
  return {
486
486
  async rest(method, path2, init) {
487
487
  const res = await request(method, joinUrl(baseUrl, path2), init);
488
- return parseJson(res);
488
+ return parseJson2(res);
489
489
  },
490
490
  async restPaginate(path2, init) {
491
491
  const items = [];
492
492
  let url = withPerPage(joinUrl(baseUrl, path2));
493
493
  while (url) {
494
494
  const res = await request("GET", url, init);
495
- const page = await parseJson(res);
495
+ const page = await parseJson2(res);
496
496
  if (!Array.isArray(page)) {
497
497
  throw new GitHubApiError(
498
498
  `pagination page was not a JSON array (got ${page === void 0 ? "an empty body" : typeof page}) \u2014 the list read is PARTIAL and must not be treated as complete: ${url}. Retry the call.`,
@@ -509,7 +509,7 @@ function createGitHubClient(options = {}) {
509
509
  ...init,
510
510
  body: { query, ...variables ? { variables } : {} }
511
511
  });
512
- const parsed = await parseJson(res);
512
+ const parsed = await parseJson2(res);
513
513
  if (parsed?.errors?.length) {
514
514
  const message = parsed.errors.map((e) => e.message ?? e.type ?? "unknown GraphQL error").join("; ");
515
515
  const rateLimited = parsed.errors.some((e) => e.type === "RATE_LIMITED" || /rate limit/i.test(e.message ?? ""));
@@ -572,7 +572,7 @@ async function fetchWithRetry(fetchImpl, url, init, opts = {}) {
572
572
  const attempts = opts.attempts ?? 3;
573
573
  const baseDelayMs = opts.baseDelayMs ?? 250;
574
574
  const retryOn = opts.retryOn ?? ((res) => res.status >= 500);
575
- const sleep2 = opts.sleep ?? ((ms) => new Promise((resolve5) => setTimeout(resolve5, ms)));
575
+ const sleep2 = opts.sleep ?? ((ms) => new Promise((resolve6) => setTimeout(resolve6, ms)));
576
576
  let lastErr;
577
577
  for (let i = 0; i < attempts; i++) {
578
578
  const isLast = i === attempts - 1;
@@ -810,6 +810,8 @@ var init_house_map = __esm({
810
810
  // this repo's process-spawn contract
811
811
  tests: "core",
812
812
  // this repo's test-policy contract
813
+ dist: "core",
814
+ // this repo's committed dist/BOM drift receipt (#5576)
813
815
  doctor: "core",
814
816
  stage: "core",
815
817
  plugin: "core",
@@ -859,8 +861,8 @@ async function readStdin(opts = {}) {
859
861
  })().catch(() => {
860
862
  });
861
863
  let timer;
862
- const timeout = new Promise((resolve5) => {
863
- timer = setTimeout(resolve5, timeoutMs);
864
+ const timeout = new Promise((resolve6) => {
865
+ timer = setTimeout(resolve6, timeoutMs);
864
866
  });
865
867
  try {
866
868
  await Promise.race([drain, timeout]);
@@ -899,12 +901,12 @@ function killProcessTree(pid) {
899
901
  function execFileHard(file, args, options) {
900
902
  const { timeout, step, ...rest } = options;
901
903
  const started = Date.now();
902
- return new Promise((resolve5, reject) => {
904
+ return new Promise((resolve6, reject) => {
903
905
  const child2 = (0, import_node_child_process3.execFile)(file, args, { encoding: "utf8", windowsHide: true, ...rest, timeout: 0 }, (error, stdout, stderr) => {
904
906
  clearTimeout(timer);
905
907
  if (expired) return;
906
908
  if (error) reject(error);
907
- else resolve5({ stdout: String(stdout), stderr: String(stderr) });
909
+ else resolve6({ stdout: String(stdout), stderr: String(stderr) });
908
910
  });
909
911
  let expired = false;
910
912
  const timer = setTimeout(() => {
@@ -1331,6 +1333,142 @@ var init_plugin_guard = __esm({
1331
1333
  }
1332
1334
  });
1333
1335
 
1336
+ // src/kimi-registration.ts
1337
+ function validateKimiInstalledPlugin(document, pluginRoot, id) {
1338
+ if (!document || typeof document !== "object" || Array.isArray(document)) return "malformed-document";
1339
+ const plugins = document.plugins;
1340
+ if (!Array.isArray(plugins)) return "malformed-document";
1341
+ const entry = plugins.find(
1342
+ (candidate2) => candidate2 && typeof candidate2 === "object" && !Array.isArray(candidate2) && candidate2.id === id
1343
+ );
1344
+ if (!entry) return "absent-record";
1345
+ const record = entry;
1346
+ if (typeof record.root !== "string") return "malformed-record";
1347
+ if (record.root !== pluginRoot) return "foreign-root";
1348
+ return record.enabled ? "healthy" : "disabled";
1349
+ }
1350
+ var init_kimi_registration = __esm({
1351
+ "src/kimi-registration.ts"() {
1352
+ "use strict";
1353
+ }
1354
+ });
1355
+
1356
+ // src/host-doc-lock.ts
1357
+ function sharedDocLockPath(documentPath) {
1358
+ return `${documentPath}.jerv-lock`;
1359
+ }
1360
+ function lockContent(options) {
1361
+ return `${JSON.stringify(options, null, 2)}
1362
+ `;
1363
+ }
1364
+ function readHolder(lockPath) {
1365
+ try {
1366
+ const parsed = JSON.parse((0, import_node_fs18.readFileSync)(lockPath, "utf8"));
1367
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return null;
1368
+ return parsed;
1369
+ } catch {
1370
+ return null;
1371
+ }
1372
+ }
1373
+ function lockAgeMs(lockPath, now) {
1374
+ const holder = readHolder(lockPath);
1375
+ const declared = typeof holder?.acquiredAt === "string" ? Date.parse(holder.acquiredAt) : Number.NaN;
1376
+ if (!Number.isNaN(declared)) return Math.max(0, now - declared);
1377
+ try {
1378
+ return Math.max(0, now - (0, import_node_fs18.statSync)(lockPath).mtimeMs);
1379
+ } catch {
1380
+ return 0;
1381
+ }
1382
+ }
1383
+ function sharedDocLockTimeoutReceipt(documentPath, lockPath, holder, waitedMs) {
1384
+ const named = holder && typeof holder === "object";
1385
+ const who = named ? `held by ${String(holder.owner ?? "unknown")} (pid ${String(holder.pid ?? "?")}, host ${String(holder.host ?? "?")})` : `held by an unreadable lock at ${lockPath}`;
1386
+ return `shared-document lock timeout: ${documentPath} is ${who} \u2014 waited ${waitedMs}ms, nothing was written \u2014 retry next tick or remove ${lockPath} if stale`;
1387
+ }
1388
+ function acquireSharedDocLock(documentPath, options) {
1389
+ const lockPath = sharedDocLockPath(documentPath);
1390
+ const staleAfterMs = options.staleAfterMs ?? SHARED_DOC_LOCK_STALE_MS;
1391
+ const attempts = options.attempts ?? SHARED_DOC_LOCK_ATTEMPTS;
1392
+ const intervalMs = options.intervalMs ?? SHARED_DOC_LOCK_INTERVAL_MS;
1393
+ const identity = { owner: options.owner, pid: process.pid, host: options.host ?? (0, import_node_os6.hostname)(), acquiredAt: (/* @__PURE__ */ new Date()).toISOString() };
1394
+ const content = lockContent(identity);
1395
+ const sleep2 = (ms) => {
1396
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
1397
+ };
1398
+ for (let attempt = 0; attempt < attempts; attempt += 1) {
1399
+ let fd;
1400
+ try {
1401
+ fd = (0, import_node_fs18.openSync)(lockPath, "wx");
1402
+ (0, import_node_fs18.writeSync)(fd, content);
1403
+ (0, import_node_fs18.closeSync)(fd);
1404
+ return {
1405
+ documentPath,
1406
+ lockPath,
1407
+ owner: identity.owner,
1408
+ pid: identity.pid,
1409
+ host: identity.host,
1410
+ acquiredAt: identity.acquiredAt,
1411
+ release: () => releaseSharedDocLock({ lockPath, owner: identity.owner, pid: identity.pid })
1412
+ };
1413
+ } catch (error) {
1414
+ if (fd !== void 0) {
1415
+ try {
1416
+ (0, import_node_fs18.closeSync)(fd);
1417
+ } catch {
1418
+ }
1419
+ try {
1420
+ (0, import_node_fs18.unlinkSync)(lockPath);
1421
+ } catch {
1422
+ }
1423
+ }
1424
+ if (error?.code !== "EEXIST") throw error;
1425
+ }
1426
+ if (lockAgeMs(lockPath, Date.now()) >= staleAfterMs) {
1427
+ const aside = `${lockPath}.stale-${Date.now()}`;
1428
+ try {
1429
+ (0, import_node_fs18.renameSync)(lockPath, aside);
1430
+ } catch {
1431
+ continue;
1432
+ }
1433
+ try {
1434
+ (0, import_node_fs18.rmSync)(aside, { force: true });
1435
+ } catch {
1436
+ }
1437
+ continue;
1438
+ }
1439
+ if (attempt < attempts - 1) sleep2(intervalMs);
1440
+ }
1441
+ throw new Error(sharedDocLockTimeoutReceipt(documentPath, lockPath, readHolder(lockPath), Math.max(0, attempts * intervalMs)));
1442
+ }
1443
+ function releaseSharedDocLock(handle) {
1444
+ try {
1445
+ const holder = readHolder(handle.lockPath);
1446
+ if (holder && holder.owner === handle.owner && Number(holder.pid) === handle.pid) {
1447
+ (0, import_node_fs18.unlinkSync)(handle.lockPath);
1448
+ }
1449
+ } catch {
1450
+ }
1451
+ }
1452
+ function withSharedDocLock(documentPath, options, fn) {
1453
+ const lock = acquireSharedDocLock(documentPath, options);
1454
+ try {
1455
+ return fn(lock.documentPath);
1456
+ } finally {
1457
+ lock.release();
1458
+ }
1459
+ }
1460
+ var import_node_fs18, import_node_os6, SHARED_DOC_LOCK_STALE_MS, SHARED_DOC_LOCK_ATTEMPTS, SHARED_DOC_LOCK_INTERVAL_MS;
1461
+ var init_host_doc_lock = __esm({
1462
+ "src/host-doc-lock.ts"() {
1463
+ "use strict";
1464
+ import_node_fs18 = require("node:fs");
1465
+ import_node_os6 = require("node:os");
1466
+ SHARED_DOC_LOCK_STALE_MS = 3e4;
1467
+ SHARED_DOC_LOCK_ATTEMPTS = 50;
1468
+ SHARED_DOC_LOCK_INTERVAL_MS = 100;
1469
+ }
1470
+ });
1471
+
1334
1472
  // src/plugin-guard-io.ts
1335
1473
  var plugin_guard_io_exports = {};
1336
1474
  __export(plugin_guard_io_exports, {
@@ -1364,6 +1502,8 @@ __export(plugin_guard_io_exports, {
1364
1502
  hermesPluginTreeHealthy: () => hermesPluginTreeHealthy,
1365
1503
  installedJervCodePackageVersion: () => installedJervCodePackageVersion,
1366
1504
  kiloConfigListsPlugin: () => kiloConfigListsPlugin,
1505
+ kimiPluginHostEvidence: () => kimiPluginHostEvidence,
1506
+ kimiPluginRegistrationHealth: () => kimiPluginRegistrationHealth,
1367
1507
  kimiPluginTreeHealthy: () => kimiPluginTreeHealthy,
1368
1508
  legacyMmiPiPathEntries: () => legacyMmiPiPathEntries,
1369
1509
  marketplaceAddSupportsRef: () => marketplaceAddSupportsRef,
@@ -1525,12 +1665,12 @@ function surfaceHomeDir(surface) {
1525
1665
  if (surface === "hermes") return ".hermes";
1526
1666
  return ".claude";
1527
1667
  }
1528
- function hermesConfigRoot(env = process.env, home = (0, import_node_os6.homedir)(), platform2 = process.platform) {
1668
+ function hermesConfigRoot(env = process.env, home = (0, import_node_os7.homedir)(), platform2 = process.platform) {
1529
1669
  if (env.HERMES_HOME?.trim()) return env.HERMES_HOME.trim();
1530
1670
  if (platform2 === "win32") return (0, import_node_path16.join)(env.LOCALAPPDATA?.trim() || (0, import_node_path16.join)(home, "AppData", "Local"), "hermes");
1531
1671
  return (0, import_node_path16.join)(home, ".hermes");
1532
1672
  }
1533
- function surfaceConfigRoot(surface, env = process.env, home = (0, import_node_os6.homedir)()) {
1673
+ function surfaceConfigRoot(surface, env = process.env, home = (0, import_node_os7.homedir)()) {
1534
1674
  if (surface === "codex") return env.CODEX_HOME?.trim() || (0, import_node_path16.join)(home, ".codex");
1535
1675
  if (surface === "kimi") return env.KIMI_CODE_HOME?.trim() || (0, import_node_path16.join)(home, ".kimi-code");
1536
1676
  if (surface === "kilo") return env.KILO_CONFIG_DIR?.trim() || (0, import_node_path16.join)(home, ".config", "kilo");
@@ -1543,7 +1683,7 @@ function surfaceConfigRoot(surface, env = process.env, home = (0, import_node_os
1543
1683
  }
1544
1684
  function readInstalledPlugins(surface = detectSurface(process.env)) {
1545
1685
  try {
1546
- return JSON.parse((0, import_node_fs18.readFileSync)(installedPluginsPath(surface), "utf8"));
1686
+ return JSON.parse((0, import_node_fs19.readFileSync)(installedPluginsPath(surface), "utf8"));
1547
1687
  } catch {
1548
1688
  return null;
1549
1689
  }
@@ -1562,7 +1702,7 @@ function marketplaceCloneCandidates(surface, home, env = process.env) {
1562
1702
  if (surface === "jervcode") return [];
1563
1703
  return [(0, import_node_path16.join)(home, ".claude", "plugins", "marketplaces", "mutmutco")];
1564
1704
  }
1565
- function marketplaceClonePresent(surface, home, exists = import_node_fs18.existsSync, env = process.env) {
1705
+ function marketplaceClonePresent(surface, home, exists = import_node_fs19.existsSync, env = process.env) {
1566
1706
  return marketplaceCloneCandidates(surface, home, env).some(exists);
1567
1707
  }
1568
1708
  function runHostBinSync(bin, args) {
@@ -1597,7 +1737,7 @@ function codexPluginStatus() {
1597
1737
  }
1598
1738
  function countCodexHookCommands(path2) {
1599
1739
  try {
1600
- const parsed = JSON.parse((0, import_node_fs18.readFileSync)(path2, "utf8"));
1740
+ const parsed = JSON.parse((0, import_node_fs19.readFileSync)(path2, "utf8"));
1601
1741
  let count = 0;
1602
1742
  for (const groups of Object.values(parsed.hooks ?? {})) {
1603
1743
  for (const group of groups) {
@@ -1618,7 +1758,7 @@ function codexHookTrustState(status = codexPluginStatus()) {
1618
1758
  const requiredCount = countCodexHookCommands(hooksPath);
1619
1759
  let config = "";
1620
1760
  try {
1621
- config = (0, import_node_fs18.readFileSync)((0, import_node_path16.join)(root, "config.toml"), "utf8");
1761
+ config = (0, import_node_fs19.readFileSync)((0, import_node_path16.join)(root, "config.toml"), "utf8");
1622
1762
  } catch {
1623
1763
  return { applicable: true, trusted: false, trustedCount: 0, requiredCount };
1624
1764
  }
@@ -1641,7 +1781,7 @@ async function fetchNpmReleasedVersion() {
1641
1781
  return void 0;
1642
1782
  }
1643
1783
  }
1644
- function kiloConfigListsPlugin(configRoot, home = (0, import_node_os6.homedir)(), read = (p) => (0, import_node_fs18.readFileSync)(p, "utf8"), exists = import_node_fs18.existsSync) {
1784
+ function kiloConfigListsPlugin(configRoot, home = (0, import_node_os7.homedir)(), read = (p) => (0, import_node_fs19.readFileSync)(p, "utf8"), exists = import_node_fs19.existsSync) {
1645
1785
  const candidates = ["kilo.json", "kilo.jsonc", "opencode.json", "opencode.jsonc", "config.json"];
1646
1786
  for (const dir of [configRoot, (0, import_node_path16.join)(home, ".kilo")]) {
1647
1787
  for (const file of candidates) {
@@ -1661,10 +1801,10 @@ function kiloConfigListsPlugin(configRoot, home = (0, import_node_os6.homedir)()
1661
1801
  }
1662
1802
  return false;
1663
1803
  }
1664
- function cursorLocalPluginRoot(env = process.env, home = (0, import_node_os6.homedir)()) {
1804
+ function cursorLocalPluginRoot(env = process.env, home = (0, import_node_os7.homedir)()) {
1665
1805
  return (0, import_node_path16.join)(surfaceConfigRoot("cursor", env, home), "plugins", "local", "mmi");
1666
1806
  }
1667
- function cursorPluginTreeHealthy(root, exists = import_node_fs18.existsSync) {
1807
+ function cursorPluginTreeHealthy(root, exists = import_node_fs19.existsSync) {
1668
1808
  return [
1669
1809
  ".cursor-plugin/plugin.json",
1670
1810
  "skills/mmi/SKILL.md",
@@ -1673,31 +1813,52 @@ function cursorPluginTreeHealthy(root, exists = import_node_fs18.existsSync) {
1673
1813
  "scripts/hook-policy.mjs"
1674
1814
  ].every((path2) => exists((0, import_node_path16.join)(root, ...path2.split("/"))));
1675
1815
  }
1676
- function kimiPluginTreeHealthy(root, exists = import_node_fs18.existsSync) {
1816
+ function kimiPluginTreeHealthy(root, exists = import_node_fs19.existsSync) {
1677
1817
  return [
1678
1818
  ".kimi-plugin/plugin.json",
1679
1819
  "skills/mmi/SKILL.md",
1680
1820
  "scripts/hook-run.mjs"
1681
1821
  ].every((path2) => exists((0, import_node_path16.join)(root, ...path2.split("/"))));
1682
1822
  }
1683
- function jervcodeAgentDirs(env = process.env, home = (0, import_node_os6.homedir)()) {
1823
+ function kimiPluginRegistrationHealth(kimiHome, pluginRoot, exists = import_node_fs19.existsSync, read = (path2) => (0, import_node_fs19.readFileSync)(path2, "utf8")) {
1824
+ const path2 = (0, import_node_path16.join)(kimiHome, "plugins", "installed.json");
1825
+ if (!exists(path2)) return "absent-record";
1826
+ try {
1827
+ return validateKimiInstalledPlugin(JSON.parse(read(path2)), pluginRoot, "mmi");
1828
+ } catch {
1829
+ return "malformed-document";
1830
+ }
1831
+ }
1832
+ function kimiPluginHostEvidence(kimiHome) {
1833
+ const root = (0, import_node_path16.join)(kimiHome, "plugins", "managed", "mmi");
1834
+ const treeHealthy = kimiPluginTreeHealthy(root);
1835
+ const registration = kimiPluginRegistrationHealth(kimiHome, root);
1836
+ const healthy = treeHealthy && registration === "healthy";
1837
+ return {
1838
+ treeHealthy,
1839
+ registration,
1840
+ healthy,
1841
+ ...healthy ? {} : treeHealthy ? { receipt: `kimi:registration:${registration}` } : { receipt: "kimi:tree:missing-markers" }
1842
+ };
1843
+ }
1844
+ function jervcodeAgentDirs(env = process.env, home = (0, import_node_os7.homedir)()) {
1684
1845
  const primary = surfaceConfigRoot("jervcode", env, home);
1685
1846
  const legacy = (0, import_node_path16.join)(home, ".pi", "agent");
1686
1847
  const dirs = [primary];
1687
1848
  if (legacy !== primary) dirs.push(legacy);
1688
- return dirs.filter((dir) => (0, import_node_fs18.existsSync)(dir));
1849
+ return dirs.filter((dir) => (0, import_node_fs19.existsSync)(dir));
1689
1850
  }
1690
1851
  function readPiSettings(path2) {
1691
- if (!(0, import_node_fs18.existsSync)(path2)) return void 0;
1852
+ if (!(0, import_node_fs19.existsSync)(path2)) return void 0;
1692
1853
  try {
1693
- const parsed = JSON.parse((0, import_node_fs18.readFileSync)(path2, "utf8"));
1854
+ const parsed = JSON.parse((0, import_node_fs19.readFileSync)(path2, "utf8"));
1694
1855
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return null;
1695
1856
  return parsed;
1696
1857
  } catch {
1697
1858
  return null;
1698
1859
  }
1699
1860
  }
1700
- function mmiPiPackageEntry(env = process.env, home = (0, import_node_os6.homedir)()) {
1861
+ function mmiPiPackageEntry(env = process.env, home = (0, import_node_os7.homedir)()) {
1701
1862
  for (const dir of jervcodeAgentDirs(env, home)) {
1702
1863
  const settings = readPiSettings((0, import_node_path16.join)(dir, "settings.json"));
1703
1864
  const entries = Array.isArray(settings?.packages) ? settings.packages : [];
@@ -1708,7 +1869,7 @@ function mmiPiPackageEntry(env = process.env, home = (0, import_node_os6.homedir
1708
1869
  }
1709
1870
  return null;
1710
1871
  }
1711
- function legacyMmiPiPathEntries(env = process.env, home = (0, import_node_os6.homedir)()) {
1872
+ function legacyMmiPiPathEntries(env = process.env, home = (0, import_node_os7.homedir)()) {
1712
1873
  const found = [];
1713
1874
  for (const dir of jervcodeAgentDirs(env, home)) {
1714
1875
  const settings = readPiSettings((0, import_node_path16.join)(dir, "settings.json"));
@@ -1719,37 +1880,37 @@ function legacyMmiPiPathEntries(env = process.env, home = (0, import_node_os6.ho
1719
1880
  }
1720
1881
  return found;
1721
1882
  }
1722
- function mmiPiPackageRoot(env = process.env, home = (0, import_node_os6.homedir)()) {
1883
+ function mmiPiPackageRoot(env = process.env, home = (0, import_node_os7.homedir)()) {
1723
1884
  for (const dir of jervcodeAgentDirs(env, home)) {
1724
1885
  const root = (0, import_node_path16.join)(dir, "npm", "node_modules", ...JERVCODE_NPM_PACKAGE.split("/"));
1725
- if ((0, import_node_fs18.existsSync)((0, import_node_path16.join)(root, "package.json"))) return root;
1886
+ if ((0, import_node_fs19.existsSync)((0, import_node_path16.join)(root, "package.json"))) return root;
1726
1887
  }
1727
1888
  return null;
1728
1889
  }
1729
- function installedJervCodePackageVersion(env = process.env, home = (0, import_node_os6.homedir)()) {
1890
+ function installedJervCodePackageVersion(env = process.env, home = (0, import_node_os7.homedir)()) {
1730
1891
  const root = mmiPiPackageRoot(env, home);
1731
1892
  if (!root) return void 0;
1732
1893
  try {
1733
- const manifest = JSON.parse((0, import_node_fs18.readFileSync)((0, import_node_path16.join)(root, "package.json"), "utf8"));
1894
+ const manifest = JSON.parse((0, import_node_fs19.readFileSync)((0, import_node_path16.join)(root, "package.json"), "utf8"));
1734
1895
  return typeof manifest.version === "string" ? manifest.version : void 0;
1735
1896
  } catch {
1736
1897
  return void 0;
1737
1898
  }
1738
1899
  }
1739
- function mmiPiPackageHealthy(env = process.env, home = (0, import_node_os6.homedir)()) {
1900
+ function mmiPiPackageHealthy(env = process.env, home = (0, import_node_os7.homedir)()) {
1740
1901
  const root = mmiPiPackageRoot(env, home);
1741
- return Boolean(root && (0, import_node_fs18.existsSync)((0, import_node_path16.join)(root, "skills", "mmi", "SKILL.md")));
1902
+ return Boolean(root && (0, import_node_fs19.existsSync)((0, import_node_path16.join)(root, "skills", "mmi", "SKILL.md")));
1742
1903
  }
1743
1904
  function piDoctorEnv(agentDir) {
1744
1905
  return { ...process.env, PI_CODING_AGENT_DIR: agentDir };
1745
1906
  }
1746
- function hermesPluginRoot(env = process.env, home = (0, import_node_os6.homedir)()) {
1907
+ function hermesPluginRoot(env = process.env, home = (0, import_node_os7.homedir)()) {
1747
1908
  return (0, import_node_path16.join)(hermesConfigRoot(env, home), "plugins", "mmi");
1748
1909
  }
1749
- function hermesPluginTreeHealthy(root, exists = import_node_fs18.existsSync) {
1910
+ function hermesPluginTreeHealthy(root, exists = import_node_fs19.existsSync) {
1750
1911
  if (!["plugin.yaml", "__init__.py"].every((file) => exists((0, import_node_path16.join)(root, file)))) return false;
1751
1912
  try {
1752
- return (0, import_node_fs18.statSync)((0, import_node_path16.join)(root, "skills")).isDirectory();
1913
+ return (0, import_node_fs19.statSync)((0, import_node_path16.join)(root, "skills")).isDirectory();
1753
1914
  } catch {
1754
1915
  return false;
1755
1916
  }
@@ -1758,22 +1919,23 @@ function snapshotPluginGuardInput(surface = detectSurface(process.env), isOrgRep
1758
1919
  const root = surfaceConfigRoot(surface);
1759
1920
  const installed = readInstalledPlugins(surface);
1760
1921
  const codexStatus = surface === "codex" ? codexPluginStatus() : void 0;
1922
+ const kimi = surface === "kimi" ? kimiPluginHostEvidence(root) : void 0;
1761
1923
  const piEntry = surface === "jervcode" ? mmiPiPackageEntry() : null;
1762
1924
  return {
1763
1925
  isOrgRepo,
1764
- installRecordPresent: hasUserInstallRecord(installed, MMI_PLUGIN_ID) || hasProjectInstallRecord(installed, MMI_PLUGIN_ID, process.cwd()) || // Kimi's managed plugin directory is its native install record; it has no Claude-style ledger.
1765
- surface === "kimi" && (0, import_node_fs18.existsSync)((0, import_node_path16.join)(root, "plugins", "managed", "mmi")) || // kilo-p1: the install record is the config file itself.
1926
+ installRecordPresent: hasUserInstallRecord(installed, MMI_PLUGIN_ID) || hasProjectInstallRecord(installed, MMI_PLUGIN_ID, process.cwd()) || // Kimi loads only records in plugins/installed.json; a managed tree alone is inert.
1927
+ surface === "kimi" && kimi?.registration === "healthy" || // kilo-p1: the install record is the config file itself.
1766
1928
  surface === "kilo" && kiloConfigListsPlugin(root) || // #4188: jervcode's install record is the settings-file packages[] entry itself.
1767
- surface === "jervcode" && piEntry !== null || surface === "cursor" && (0, import_node_fs18.existsSync)(cursorLocalPluginRoot()) || surface === "hermes" && (0, import_node_fs18.existsSync)(hermesPluginRoot()),
1929
+ surface === "jervcode" && piEntry !== null || surface === "cursor" && (0, import_node_fs19.existsSync)(cursorLocalPluginRoot()) || surface === "hermes" && (0, import_node_fs19.existsSync)(hermesPluginRoot()),
1768
1930
  // Kilo has no marketplace to clone — the config file IS the install record, so this dimension of
1769
1931
  // the shared guard table is vacuously satisfied. Same for jervcode's settings entry.
1770
- marketplaceClonePresent: surface === "kimi" || surface === "kilo" || surface === "cursor" || surface === "jervcode" || surface === "hermes" ? true : marketplaceClonePresent(surface, (0, import_node_os6.homedir)()),
1932
+ marketplaceClonePresent: surface === "kimi" || surface === "kilo" || surface === "cursor" || surface === "jervcode" || surface === "hermes" ? true : marketplaceClonePresent(surface, (0, import_node_os7.homedir)()),
1771
1933
  // Kimi keeps no plugin cache dir — installs are copied to plugins/managed/<id> and run from there.
1772
1934
  // Kilo (kilo-p1) keeps no cache dir either: the plugin's server() provisions ~/.kilo behind the
1773
1935
  // version stamp, so the stamp's presence is the cache signal.
1774
- pluginCachePresent: surface === "jervcode" ? mmiPiPackageHealthy() : surface === "hermes" ? hermesPluginTreeHealthy(hermesPluginRoot()) : surface === "kilo" ? (0, import_node_fs18.existsSync)((0, import_node_path16.join)((0, import_node_os6.homedir)(), ".kilo", ".mmi-kilo-version")) : surface === "kimi" ? kimiPluginTreeHealthy((0, import_node_path16.join)(root, "plugins", "managed", "mmi")) : surface === "cursor" ? cursorPluginTreeHealthy(cursorLocalPluginRoot()) : surface === "codex" ? Boolean(
1775
- codexStatus?.installed && codexStatus.enabled && codexStatus.version && (0, import_node_fs18.existsSync)((0, import_node_path16.join)(root, "plugins", "cache", CODEX_MARKETPLACE, "mmi", codexStatus.version))
1776
- ) : (0, import_node_fs18.existsSync)((0, import_node_path16.join)(root, "plugins", "cache", "mutmutco", "mmi"))
1936
+ pluginCachePresent: surface === "jervcode" ? mmiPiPackageHealthy() : surface === "hermes" ? hermesPluginTreeHealthy(hermesPluginRoot()) : surface === "kilo" ? (0, import_node_fs19.existsSync)((0, import_node_path16.join)((0, import_node_os7.homedir)(), ".kilo", ".mmi-kilo-version")) : surface === "kimi" ? Boolean(kimi?.healthy) : surface === "cursor" ? cursorPluginTreeHealthy(cursorLocalPluginRoot()) : surface === "codex" ? Boolean(
1937
+ codexStatus?.installed && codexStatus.enabled && codexStatus.version && (0, import_node_fs19.existsSync)((0, import_node_path16.join)(root, "plugins", "cache", CODEX_MARKETPLACE, "mmi", codexStatus.version))
1938
+ ) : (0, import_node_fs19.existsSync)((0, import_node_path16.join)(root, "plugins", "cache", "mutmutco", "mmi"))
1777
1939
  };
1778
1940
  }
1779
1941
  function claudePluginGuardState(isOrgRepo) {
@@ -1800,7 +1962,7 @@ function captureCodexHookLauncher() {
1800
1962
  const files = ["mmi-hook", "mmi-hook.exe"].flatMap((name) => {
1801
1963
  const path2 = (0, import_node_path16.join)(root, "bin", name);
1802
1964
  try {
1803
- return [{ name, content: (0, import_node_fs18.readFileSync)(path2) }];
1965
+ return [{ name, content: (0, import_node_fs19.readFileSync)(path2) }];
1804
1966
  } catch {
1805
1967
  return [];
1806
1968
  }
@@ -1808,13 +1970,13 @@ function captureCodexHookLauncher() {
1808
1970
  return files.length === 2 ? { root, files } : void 0;
1809
1971
  }
1810
1972
  function restoreCodexHookLauncher(snapshot) {
1811
- if (!snapshot || (0, import_node_fs18.existsSync)((0, import_node_path16.join)(snapshot.root, "scripts", "hook-run.mjs"))) return false;
1973
+ if (!snapshot || (0, import_node_fs19.existsSync)((0, import_node_path16.join)(snapshot.root, "scripts", "hook-run.mjs"))) return false;
1812
1974
  const bin = (0, import_node_path16.join)(snapshot.root, "bin");
1813
- (0, import_node_fs18.mkdirSync)(bin, { recursive: true });
1975
+ (0, import_node_fs19.mkdirSync)(bin, { recursive: true });
1814
1976
  for (const file of snapshot.files) {
1815
1977
  const path2 = (0, import_node_path16.join)(bin, file.name);
1816
- (0, import_node_fs18.writeFileSync)(path2, file.content);
1817
- if (file.name === "mmi-hook") (0, import_node_fs18.chmodSync)(path2, 493);
1978
+ (0, import_node_fs19.writeFileSync)(path2, file.content);
1979
+ if (file.name === "mmi-hook") (0, import_node_fs19.chmodSync)(path2, 493);
1818
1980
  }
1819
1981
  return true;
1820
1982
  }
@@ -1823,13 +1985,13 @@ function canonicalCursorRemote(remote) {
1823
1985
  }
1824
1986
  function readJsonFile(path2) {
1825
1987
  try {
1826
- return JSON.parse((0, import_node_fs18.readFileSync)(path2, "utf8"));
1988
+ return JSON.parse((0, import_node_fs19.readFileSync)(path2, "utf8"));
1827
1989
  } catch {
1828
1990
  return null;
1829
1991
  }
1830
1992
  }
1831
1993
  async function cursorTreeManaged(target) {
1832
- if ((0, import_node_fs18.existsSync)((0, import_node_path16.join)(target, ".git"))) {
1994
+ if ((0, import_node_fs19.existsSync)((0, import_node_path16.join)(target, ".git"))) {
1833
1995
  try {
1834
1996
  const { stdout } = await runHostBin("git", ["-C", target, "remote", "get-url", "origin"], { timeout: 15e3 });
1835
1997
  if (canonicalCursorRemote(stdout)) return { ok: true };
@@ -1850,19 +2012,19 @@ async function installCursorPluginCheckout(env = process.env) {
1850
2012
  const pluginsRoot = (0, import_node_path16.join)(configRoot, "plugins");
1851
2013
  const target = (0, import_node_path16.join)(pluginsRoot, "local", "mmi");
1852
2014
  const source = env.MMI_CURSOR_PLUGIN_SOURCE?.trim();
1853
- if ((0, import_node_fs18.existsSync)(target) && !source) {
2015
+ if ((0, import_node_fs19.existsSync)(target) && !source) {
1854
2016
  const managed = await cursorTreeManaged(target);
1855
2017
  if (!managed.ok) return { ok: false, detail: managed.detail };
1856
2018
  }
1857
- (0, import_node_fs18.mkdirSync)((0, import_node_path16.join)(pluginsRoot, "local"), { recursive: true });
1858
- (0, import_node_fs18.mkdirSync)((0, import_node_path16.join)(pluginsRoot, "staging"), { recursive: true });
1859
- (0, import_node_fs18.mkdirSync)((0, import_node_path16.join)(pluginsRoot, "quarantine"), { recursive: true });
2019
+ (0, import_node_fs19.mkdirSync)((0, import_node_path16.join)(pluginsRoot, "local"), { recursive: true });
2020
+ (0, import_node_fs19.mkdirSync)((0, import_node_path16.join)(pluginsRoot, "staging"), { recursive: true });
2021
+ (0, import_node_fs19.mkdirSync)((0, import_node_path16.join)(pluginsRoot, "quarantine"), { recursive: true });
1860
2022
  const suffix = `${Date.now()}-${process.pid}`;
1861
2023
  const staged = (0, import_node_path16.join)(pluginsRoot, "staging", `mmi-${suffix}`);
1862
2024
  const quarantined = (0, import_node_path16.join)(pluginsRoot, "quarantine", `mmi-${suffix}`);
1863
2025
  try {
1864
2026
  if (source) {
1865
- (0, import_node_fs18.cpSync)(source, staged, {
2027
+ (0, import_node_fs19.cpSync)(source, staged, {
1866
2028
  recursive: true,
1867
2029
  filter: (path2) => !path2.split(/[\\/]/).some((part) => part === ".git" || part === "node_modules")
1868
2030
  });
@@ -1873,18 +2035,18 @@ async function installCursorPluginCheckout(env = process.env) {
1873
2035
  });
1874
2036
  }
1875
2037
  if (!cursorPluginTreeHealthy(staged)) {
1876
- (0, import_node_fs18.rmSync)(staged, { recursive: true, force: true });
2038
+ (0, import_node_fs19.rmSync)(staged, { recursive: true, force: true });
1877
2039
  return { ok: false, detail: "downloaded Cursor plugin is incomplete; existing install was preserved" };
1878
2040
  }
1879
2041
  let movedOld = false;
1880
- if ((0, import_node_fs18.existsSync)(target)) {
1881
- (0, import_node_fs18.renameSync)(target, quarantined);
2042
+ if ((0, import_node_fs19.existsSync)(target)) {
2043
+ (0, import_node_fs19.renameSync)(target, quarantined);
1882
2044
  movedOld = true;
1883
2045
  }
1884
2046
  try {
1885
- (0, import_node_fs18.renameSync)(staged, target);
2047
+ (0, import_node_fs19.renameSync)(staged, target);
1886
2048
  } catch (error) {
1887
- if (movedOld && !(0, import_node_fs18.existsSync)(target)) (0, import_node_fs18.renameSync)(quarantined, target);
2049
+ if (movedOld && !(0, import_node_fs19.existsSync)(target)) (0, import_node_fs19.renameSync)(quarantined, target);
1888
2050
  throw error;
1889
2051
  }
1890
2052
  return {
@@ -1892,7 +2054,7 @@ async function installCursorPluginCheckout(env = process.env) {
1892
2054
  detail: movedOld ? `installed canonical Cursor plugin; previous checkout quarantined at ${quarantined}` : `installed canonical Cursor plugin at ${target}`
1893
2055
  };
1894
2056
  } catch (error) {
1895
- if ((0, import_node_fs18.existsSync)(staged)) (0, import_node_fs18.rmSync)(staged, { recursive: true, force: true });
2057
+ if ((0, import_node_fs19.existsSync)(staged)) (0, import_node_fs19.rmSync)(staged, { recursive: true, force: true });
1896
2058
  return { ok: false, detail: error.message.trim().slice(0, 240).replace(/\s+/g, " ") };
1897
2059
  }
1898
2060
  }
@@ -1929,7 +2091,7 @@ async function runHealSteps(host, tableSteps, deps) {
1929
2091
  const refSupported = needsRefProbe ? await marketplaceAddRefSupported(host) : true;
1930
2092
  const { steps } = adaptHealStepsForRefSupport(tableSteps, refSupported);
1931
2093
  if (deps.banner) log(deps.banner(refSupported));
1932
- const pinsPath = (0, import_node_path16.join)((0, import_node_os6.homedir)(), ...KNOWN_MARKETPLACES_RELATIVE);
2094
+ const pinsPath = (0, import_node_path16.join)((0, import_node_os7.homedir)(), ...KNOWN_MARKETPLACES_RELATIVE);
1933
2095
  const pins = host === "claude" ? captureMarketplacePins(readKnownMarketplacesFile(pinsPath), [MMI_MARKETPLACE_NAME, JERV_MARKETPLACE_NAME]) : /* @__PURE__ */ new Map();
1934
2096
  let failure;
1935
2097
  try {
@@ -2052,7 +2214,7 @@ async function healActivePluginForDoctor(surface = detectSurface(process.env), o
2052
2214
  }
2053
2215
  function readKnownMarketplacesFile(path2) {
2054
2216
  try {
2055
- return (0, import_node_fs18.existsSync)(path2) ? (0, import_node_fs18.readFileSync)(path2, "utf8") : void 0;
2217
+ return (0, import_node_fs19.existsSync)(path2) ? (0, import_node_fs19.readFileSync)(path2, "utf8") : void 0;
2056
2218
  } catch {
2057
2219
  return void 0;
2058
2220
  }
@@ -2074,24 +2236,37 @@ function claudeCodeIsRunning(env = process.env, listProcesses = defaultProcessLi
2074
2236
  function defaultProcessList() {
2075
2237
  return isWin ? (0, import_node_child_process8.execFileSync)("powershell.exe", ["-NoProfile", "-Command", "Get-CimInstance Win32_Process | ForEach-Object { $_.CommandLine }"], { encoding: "utf8", windowsHide: true, maxBuffer: 32 * 1024 * 1024, timeout: 15e3 }) : (0, import_node_child_process8.execFileSync)("ps", ["-eo", "args="], { encoding: "utf8", windowsHide: true, timeout: 15e3 });
2076
2238
  }
2077
- function writeMarketplacePinsOnDisk(path2, pins, succeeded, failedVerb, declineWhileHostLive) {
2239
+ function writeMarketplacePinsOnDisk(path2, pins, succeeded, failedVerb, declineWhileHostLive, lock = {}) {
2078
2240
  if (pins.size === 0) return void 0;
2079
- const after = readKnownMarketplacesFile(path2);
2080
- const next = restoreMarketplacePins(after, pins);
2081
- if (next === null) return void 0;
2082
- const declined = declineWhileHostLive?.();
2083
- if (declined) return declined;
2084
2241
  try {
2085
- (0, import_node_fs18.writeFileSync)(path2, next, "utf8");
2086
- } catch {
2087
- return `could NOT ${failedVerb} ${[...pins.keys()].join(", ")} \u2014 set it by hand`;
2242
+ return withSharedDocLock(path2, { owner: MARKETPLACE_PINS_LOCK_OWNER, ...lock }, () => {
2243
+ const after = readKnownMarketplacesFile(path2);
2244
+ const next = restoreMarketplacePins(after, pins);
2245
+ if (next === null) return void 0;
2246
+ const declined = declineWhileHostLive?.();
2247
+ if (declined) return declined;
2248
+ const tmp = `${path2}.tmp-${process.pid}`;
2249
+ try {
2250
+ (0, import_node_fs19.writeFileSync)(tmp, next, "utf8");
2251
+ (0, import_node_fs19.renameSync)(tmp, path2);
2252
+ } catch {
2253
+ try {
2254
+ (0, import_node_fs19.rmSync)(tmp, { force: true });
2255
+ } catch {
2256
+ }
2257
+ return `could NOT ${failedVerb} ${[...pins.keys()].join(", ")} \u2014 set it by hand`;
2258
+ }
2259
+ const verify = readKnownMarketplacesFile(path2);
2260
+ const failed = [...pins].filter(([name, want]) => {
2261
+ const got = readKnownMarketplace(verify, name);
2262
+ return typeof want.autoUpdate === "boolean" && got.declared !== want.autoUpdate || want.ref !== void 0 && got.ref !== want.ref;
2263
+ });
2264
+ return failed.length ? `${failedVerb} did NOT take for ${failed.map(([n]) => n).join(", ")} \u2014 set it by hand` : succeeded(pins);
2265
+ });
2266
+ } catch (error) {
2267
+ if (error instanceof Error && error.message.startsWith("shared-document lock timeout:")) return error.message;
2268
+ throw error;
2088
2269
  }
2089
- const verify = readKnownMarketplacesFile(path2);
2090
- const failed = [...pins].filter(([name, want]) => {
2091
- const got = readKnownMarketplace(verify, name);
2092
- return typeof want.autoUpdate === "boolean" && got.declared !== want.autoUpdate || want.ref !== void 0 && got.ref !== want.ref;
2093
- });
2094
- return failed.length ? `${failedVerb} did NOT take for ${failed.map(([n]) => n).join(", ")} \u2014 set it by hand` : succeeded(pins);
2095
2270
  }
2096
2271
  function restoreMarketplacePinsOnDisk(path2, pins, hostIsRunning = claudeCodeIsRunning) {
2097
2272
  return writeMarketplacePinsOnDisk(
@@ -2101,7 +2276,7 @@ function restoreMarketplacePinsOnDisk(path2, pins, hostIsRunning = claudeCodeIsR
2101
2276
  "restore"
2102
2277
  );
2103
2278
  }
2104
- function applyOrgMarketplacePins(path2, names, hostIsRunning = claudeCodeIsRunning) {
2279
+ function applyOrgMarketplacePins(path2, names, hostIsRunning = claudeCodeIsRunning, lock = {}) {
2105
2280
  let landed = false;
2106
2281
  let blockedByHost = false;
2107
2282
  const detail = writeMarketplacePinsOnDisk(
@@ -2116,21 +2291,22 @@ function applyOrgMarketplacePins(path2, names, hostIsRunning = claudeCodeIsRunni
2116
2291
  if (!hostIsRunning()) return void 0;
2117
2292
  blockedByHost = true;
2118
2293
  return "not pinned \u2014 Claude Code is running and rewrites this registration from its own copy; quit it, then run `mmi-cli doctor`";
2119
- }
2294
+ },
2295
+ lock
2120
2296
  );
2121
2297
  return detail === void 0 ? void 0 : { detail, wrote: landed, blockedByHost };
2122
2298
  }
2123
2299
  function writeMarketplacePinPending(path2, names, now = Date.now()) {
2124
2300
  try {
2125
- (0, import_node_fs18.mkdirSync)((0, import_node_path16.dirname)(path2), { recursive: true });
2126
- (0, import_node_fs18.writeFileSync)(path2, `${JSON.stringify({ v: 1, names: [...names], at: new Date(now).toISOString() })}
2301
+ (0, import_node_fs19.mkdirSync)((0, import_node_path16.dirname)(path2), { recursive: true });
2302
+ (0, import_node_fs19.writeFileSync)(path2, `${JSON.stringify({ v: 1, names: [...names], at: new Date(now).toISOString() })}
2127
2303
  `, "utf8");
2128
2304
  } catch {
2129
2305
  }
2130
2306
  }
2131
2307
  function readMarketplacePinPending(path2, name, now = Date.now()) {
2132
2308
  try {
2133
- const parsed = JSON.parse((0, import_node_fs18.readFileSync)(path2, "utf8"));
2309
+ const parsed = JSON.parse((0, import_node_fs19.readFileSync)(path2, "utf8"));
2134
2310
  const at = typeof parsed.at === "string" ? Date.parse(parsed.at) : Number.NaN;
2135
2311
  if (parsed.v !== 1 || !Array.isArray(parsed.names) || !parsed.names.includes(name) || !Number.isFinite(at)) return void 0;
2136
2312
  if (now - at < 0 || now - at > 12 * 60 * 6e4) return void 0;
@@ -2218,18 +2394,20 @@ async function runPluginHeal(surface = detectSurface(process.env)) {
2218
2394
  ${recovery}${note}${pluginReadGrantNote()}`);
2219
2395
  }
2220
2396
  }
2221
- var import_node_fs18, import_node_child_process8, import_node_path16, import_node_os6, isWin, MMI_PLUGIN_ID, LEGACY_MMI_MARKETPLACE, CODEX_MARKETPLACE, CLAUDE_RECOVERY, CODEX_RECOVERY, CURSOR_RECOVERY, PLUGIN_SURFACE_HEAL, CLAUDE_PLUGIN_TIMEOUT_MS, NPM_VIEW_TIMEOUT_MS, installedPluginsPath, JERVCODE_NPM_PACKAGE, LEGACY_MMI_PI_PATH, PLUGIN_READ_REPO2;
2397
+ var import_node_fs19, import_node_child_process8, import_node_path16, import_node_os7, isWin, MMI_PLUGIN_ID, LEGACY_MMI_MARKETPLACE, CODEX_MARKETPLACE, CLAUDE_RECOVERY, CODEX_RECOVERY, CURSOR_RECOVERY, PLUGIN_SURFACE_HEAL, CLAUDE_PLUGIN_TIMEOUT_MS, NPM_VIEW_TIMEOUT_MS, installedPluginsPath, JERVCODE_NPM_PACKAGE, LEGACY_MMI_PI_PATH, PLUGIN_READ_REPO2, MARKETPLACE_PINS_LOCK_OWNER;
2222
2398
  var init_plugin_guard_io = __esm({
2223
2399
  "src/plugin-guard-io.ts"() {
2224
2400
  "use strict";
2225
- import_node_fs18 = require("node:fs");
2401
+ import_node_fs19 = require("node:fs");
2226
2402
  import_node_child_process8 = require("node:child_process");
2227
2403
  import_node_path16 = require("node:path");
2228
- import_node_os6 = require("node:os");
2404
+ import_node_os7 = require("node:os");
2229
2405
  init_marketplace_autoupdate();
2230
2406
  init_cli_shared();
2231
2407
  init_version_lag();
2232
2408
  init_plugin_guard();
2409
+ init_kimi_registration();
2410
+ init_host_doc_lock();
2233
2411
  isWin = process.platform === "win32";
2234
2412
  MMI_PLUGIN_ID = "mmi@mutmutco";
2235
2413
  LEGACY_MMI_MARKETPLACE = "mmi";
@@ -2290,6 +2468,7 @@ var init_plugin_guard_io = __esm({
2290
2468
  JERVCODE_NPM_PACKAGE = "@mutmutco/pi-plugin";
2291
2469
  LEGACY_MMI_PI_PATH = /[/\\]mutmutco[/\\]mmi[/\\]\d+\.\d+\.\d+[/\\]\.pi-plugin[/\\]?$/i;
2292
2470
  PLUGIN_READ_REPO2 = "mutmutco/MMI-Hub";
2471
+ MARKETPLACE_PINS_LOCK_OWNER = "mmi-hub:marketplace-pins";
2293
2472
  }
2294
2473
  });
2295
2474
 
@@ -5681,8 +5860,8 @@ var program = new Command();
5681
5860
 
5682
5861
  // src/index.ts
5683
5862
  var import_promises8 = require("node:fs/promises");
5684
- var import_node_fs43 = require("node:fs");
5685
- var import_node_child_process20 = require("node:child_process");
5863
+ var import_node_fs46 = require("node:fs");
5864
+ var import_node_child_process21 = require("node:child_process");
5686
5865
  init_cli_shared();
5687
5866
 
5688
5867
  // src/issue-surface.ts
@@ -6723,7 +6902,7 @@ function commandLadderHint() {
6723
6902
  }
6724
6903
 
6725
6904
  // src/index.ts
6726
- var import_node_path40 = require("node:path");
6905
+ var import_node_path42 = require("node:path");
6727
6906
 
6728
6907
  // src/merge-ci-policy.ts
6729
6908
  function resolveMergeCiPolicy(input) {
@@ -6843,6 +7022,32 @@ function conflictingResult(policy, baseBranch, waitedMs) {
6843
7022
  return { policy, status: "conflicting", reason: conflictingPrMessage(baseBranch), detail: "conflicting", waitedMs };
6844
7023
  }
6845
7024
  var PR_CHECKS_TIMEOUT_EXIT_CODE = 2;
7025
+ function isRetryableGitHubWaitReadError(e) {
7026
+ const status = typeof e?.status === "number" ? e.status : void 0;
7027
+ const code = typeof e?.code === "string" ? e.code : "";
7028
+ const text = [
7029
+ typeof e?.stderr === "string" ? e.stderr : "",
7030
+ typeof e?.stdout === "string" ? e.stdout : "",
7031
+ e instanceof Error ? e.message : String(e ?? "")
7032
+ ].join("\n");
7033
+ const codes = /* @__PURE__ */ new Set();
7034
+ if (typeof status === "number" && status > 0) codes.add(status);
7035
+ for (const m of text.matchAll(/\bHTTP\/?\d*(?:\.\d)?\s+(\d{3})\b/g)) codes.add(Number(m[1]));
7036
+ const rateLimited = e?.rateLimited === true || /API rate limit already exceeded|rate limit already exceeded|RATE_LIMITED|secondary rate|abuse detection/i.test(text);
7037
+ if ([401, 404, 409, 422].some((c) => codes.has(c))) return false;
7038
+ if (codes.has(403) && !rateLimited) return false;
7039
+ if (codes.has(429) || rateLimited) return true;
7040
+ if ([...codes].some((c) => c >= 500 && c < 600)) return true;
7041
+ if (/^(ETIMEDOUT|ECONNRESET|ECONNREFUSED|ENOTFOUND|EAI_AGAIN|ENETUNREACH)$/i.test(code) || /ETIMEDOUT|ECONNRESET|ECONNREFUSED|ENOTFOUND|EAI_AGAIN|socket hang up|network timeout|Client network socket disconnected/i.test(text)) {
7042
+ return true;
7043
+ }
7044
+ return /Something went wrong while executing your query/.test(text) || /^\s*unexpected end of JSON input\s*$/m.test(text);
7045
+ }
7046
+ function waitReadFailureMessage(e) {
7047
+ const err = e;
7048
+ const stderr = typeof err?.stderr === "string" ? err.stderr.trim() : "";
7049
+ return (stderr || err?.message || String(e)).replace(/\s+/g, " ").slice(0, 300);
7050
+ }
6846
7051
  var PR_CHECKS_POLL_MS = 3e4;
6847
7052
  var PR_CHECKS_TIMEOUT_MS = 30 * 6e4;
6848
7053
  var PR_CHECKS_SUCCESS_CONFIRMATIONS = 2;
@@ -6877,14 +7082,22 @@ async function waitForPrChecks(deps) {
6877
7082
  const baseBranch = deps.baseBranch ?? "development";
6878
7083
  const queuedStates = [];
6879
7084
  if (policy === "no-ci") {
6880
- const firstState = await deps.pollChecks();
6881
- if (firstState === "no-checks-reported") {
6882
- return { policy, status: "skipped", reason };
7085
+ try {
7086
+ const firstState = await deps.pollChecks();
7087
+ if (firstState === "no-checks-reported") {
7088
+ return { policy, status: "skipped", reason };
7089
+ }
7090
+ reason = NO_CI_LIVE_CHECKS_CONTRADICTION_REASON;
7091
+ deps.log?.(`merge CI policy contradiction: ${reason}; waiting for live PR checks`);
7092
+ policy = "wait-for-checks";
7093
+ queuedStates.push(firstState);
7094
+ } catch (e) {
7095
+ if (!isRetryableGitHubWaitReadError(e)) {
7096
+ return { policy, status: "failure", reason: waitReadFailureMessage(e), detail: "github-read-failed" };
7097
+ }
7098
+ deps.log?.(`merge CI policy no-ci probe hit a transient GitHub read failure \u2014 waiting: ${waitReadFailureMessage(e)}`);
7099
+ policy = "wait-for-checks";
6883
7100
  }
6884
- reason = NO_CI_LIVE_CHECKS_CONTRADICTION_REASON;
6885
- deps.log?.(`merge CI policy contradiction: ${reason}; waiting for live PR checks`);
6886
- policy = "wait-for-checks";
6887
- queuedStates.push(firstState);
6888
7101
  }
6889
7102
  if (deps.pollMergeable) {
6890
7103
  const mergeable = await resolveSettledMergeableState(deps.pollMergeable, deps.sleep);
@@ -6934,7 +7147,19 @@ async function waitForPrChecks(deps) {
6934
7147
  const mergeable = await deps.pollMergeable();
6935
7148
  if (mergeable === "CONFLICTING") return conflictingResult(policy, baseBranch, now() - started);
6936
7149
  }
6937
- const state = queuedStates.shift() ?? await deps.pollChecks();
7150
+ let state;
7151
+ try {
7152
+ state = queuedStates.shift() ?? await deps.pollChecks();
7153
+ } catch (e) {
7154
+ if (!isRetryableGitHubWaitReadError(e)) {
7155
+ return { policy, status: "failure", reason: waitReadFailureMessage(e), detail: "github-read-failed", waitedMs: now() - started };
7156
+ }
7157
+ lastDetail = `github-read-transient (${waitReadFailureMessage(e)})`;
7158
+ deps.log?.(`transient GitHub read failure \u2014 retrying within the wait budget: ${lastDetail}`);
7159
+ report("pending");
7160
+ await deps.sleep(PR_CHECKS_POLL_MS);
7161
+ continue;
7162
+ }
6938
7163
  report(state);
6939
7164
  if (state !== "success") successStreak = 0;
6940
7165
  if (state !== "failure") failureStreak = 0;
@@ -8449,15 +8674,9 @@ function gateSeedVars(cls, releaseTrack, runtime = "node", requiredCheckBranches
8449
8674
  const trackBranches = track === "trunk" ? ["main"] : track === "direct" ? ["development", "main"] : ["development", "rc", "main"];
8450
8675
  const rulesetBranches = requiredCheckBranches?.length ? [...requiredCheckBranches] : trackBranches;
8451
8676
  const rulesetRefs = JSON.stringify(rulesetBranches.map((branch) => `refs/heads/${branch}`));
8452
- const windowsCompat = {
8453
- // #5113: opt-in informational windows-latest proof. Default OFF — the CLI fills the rendered job
8454
- // YAML (or '') at the final layering step; never hand-passed.
8455
- GATE_WINDOWS_COMPAT_JOB_YAML: ""
8456
- };
8457
8677
  if (track === "trunk") {
8458
8678
  return {
8459
8679
  ...runtimeVars,
8460
- ...windowsCompat,
8461
8680
  GATE_PUSH_BRANCHES_YAML: "[main]",
8462
8681
  GATE_FULL_RUN_BRANCH: "main",
8463
8682
  GATE_RULESET_BRANCH_REFS_JSON: rulesetRefs
@@ -8466,7 +8685,6 @@ function gateSeedVars(cls, releaseTrack, runtime = "node", requiredCheckBranches
8466
8685
  if (track === "direct") {
8467
8686
  return {
8468
8687
  ...runtimeVars,
8469
- ...windowsCompat,
8470
8688
  GATE_PUSH_BRANCHES_YAML: "[development, main]",
8471
8689
  GATE_FULL_RUN_BRANCH: "development",
8472
8690
  GATE_RULESET_BRANCH_REFS_JSON: rulesetRefs
@@ -8474,7 +8692,6 @@ function gateSeedVars(cls, releaseTrack, runtime = "node", requiredCheckBranches
8474
8692
  }
8475
8693
  return {
8476
8694
  ...runtimeVars,
8477
- ...windowsCompat,
8478
8695
  GATE_PUSH_BRANCHES_YAML: "[development, rc, main]",
8479
8696
  GATE_FULL_RUN_BRANCH: "development",
8480
8697
  GATE_RULESET_BRANCH_REFS_JSON: rulesetRefs
@@ -8491,38 +8708,8 @@ function withDerivedRepoVars(vars, parsed, cls, releaseTrack, requiredCheckBranc
8491
8708
  for (const [key, value] of Object.entries(gateSeedVars(cls, track, runtime, requiredCheckBranches))) {
8492
8709
  out[key] ??= value;
8493
8710
  }
8494
- if (out.GATE_WINDOWS_COMPAT === "true" && !out.GATE_WINDOWS_COMPAT_JOB_YAML) {
8495
- out.GATE_WINDOWS_COMPAT_JOB_YAML = windowsCompatJobYaml(out);
8496
- }
8497
8711
  return out;
8498
8712
  }
8499
- function windowsCompatJobYaml(vars) {
8500
- const workdir = vars.GATE_WORKDIR ?? ".";
8501
- const cmd = vars.GATE_CMD ?? DEFAULT_GATE_CMD;
8502
- const install = vars.GATE_INSTALL_CMD ?? "npm ci";
8503
- const fullRunBranch = vars.GATE_FULL_RUN_BRANCH ?? "development";
8504
- const runtime = vars.GATE_RUNTIME === "python" ? "python" : "node";
8505
- const setup = runtime === "python" ? ` - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
8506
- with: { python-version: '${vars.GATE_PY_VERSION ?? DEFAULT_GATE_PY_VERSION}' }` : ` - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
8507
- with: { node-version: 24, cache: npm, cache-dependency-path: ${vars.GATE_CACHE_DEP_PATH ?? "package-lock.json"} }`;
8508
- return ` # MMI-Hub#5113: opt-in Windows compatibility proof \u2014 informational only. Do NOT add this job to a
8509
- # required-contexts ruleset without a deliberate repo decision: the required gate stays the Linux lane
8510
- # (faster, cheaper, where autonomous agents run). GitHub-hosted Windows minutes cost more than Linux,
8511
- # hence opt-in. defaults.run.shell=bash is Git for Windows bash on windows-latest, so the check syntax
8512
- # the Linux gate runs keeps working here.
8513
- windows-compat:
8514
- if: \${{ github.event_name == 'pull_request' || (github.event_name == 'push' && (github.ref_name == '${fullRunBranch}' || github.ref_name == 'main')) }}
8515
- runs-on: windows-latest
8516
- defaults:
8517
- run: { working-directory: ${workdir}, shell: bash }
8518
- steps:
8519
- - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
8520
- ${setup}
8521
- - run: ${install}
8522
- # Fast proof only \u2014 the full suite stays on the Linux gate.
8523
- - run: ${cmd}
8524
- `;
8525
- }
8526
8713
  function gateConfigToVars(gate) {
8527
8714
  const out = {};
8528
8715
  if (!gate || typeof gate !== "object") return out;
@@ -8533,7 +8720,6 @@ function gateConfigToVars(gate) {
8533
8720
  if (typeof gate.pyVersion === "string" && gate.pyVersion.trim()) out.GATE_PY_VERSION = gate.pyVersion;
8534
8721
  const seconds = typeof gate.maxSeconds === "number" ? String(gate.maxSeconds) : gate.maxSeconds;
8535
8722
  if (typeof seconds === "string" && /^\d+$/.test(seconds.trim()) && Number(seconds) > 0) out.GATE_MAX_SECONDS = seconds.trim();
8536
- if (gate.windowsCompat === true) out.GATE_WINDOWS_COMPAT = "true";
8537
8723
  return out;
8538
8724
  }
8539
8725
  function seedMatchesDeployModel(seed, deployModel) {
@@ -10396,7 +10582,7 @@ function rateLimitedReceipt(opts) {
10396
10582
  };
10397
10583
  }
10398
10584
  async function runWithRateLimitBackoff(operation, opts) {
10399
- const sleep2 = opts.sleep ?? ((ms) => new Promise((resolve5) => setTimeout(resolve5, ms)));
10585
+ const sleep2 = opts.sleep ?? ((ms) => new Promise((resolve6) => setTimeout(resolve6, ms)));
10400
10586
  const now = opts.now ?? Date.now;
10401
10587
  const log = opts.log ?? ((message) => console.warn(message));
10402
10588
  const capMs = opts.capMs ?? RATE_LIMIT_WAIT_CAP_MS;
@@ -10650,6 +10836,69 @@ function flagValue(args, flag) {
10650
10836
  if (i === -1 || i + 1 >= args.length) return void 0;
10651
10837
  return args[i + 1];
10652
10838
  }
10839
+ function isPrCreateRemoteHeadGraphqlNoise(text) {
10840
+ return /Head sha can't be blank/i.test(text) || /Head ref must be a branch/i.test(text) || /No commits between /i.test(text);
10841
+ }
10842
+ function unpushedHeadMessage(head) {
10843
+ return `pr create: head branch '${head}' is not on the remote \u2014 push the branch first`;
10844
+ }
10845
+ function emptyCompareMessage(base, head) {
10846
+ return `pr create: no commits between ${base} and ${head} \u2014 push the branch first if those commits are only local`;
10847
+ }
10848
+ function humanPrCreateRemoteHeadError(text, args) {
10849
+ if (!isPrCreateRemoteHeadGraphqlNoise(text)) return void 0;
10850
+ const head = flagValue(args, "--head") ?? "the head branch";
10851
+ const base = flagValue(args, "--base") ?? "the base";
10852
+ if (/Head sha can't be blank/i.test(text) || /Head ref must be a branch/i.test(text)) {
10853
+ return unpushedHeadMessage(head);
10854
+ }
10855
+ return emptyCompareMessage(base, head);
10856
+ }
10857
+ function isGhHttpNotFound(err) {
10858
+ const text = execErrorText(err);
10859
+ if (httpStatusCodes(text).includes(404)) return true;
10860
+ return /HTTP\s*404|Not Found \(HTTP 404\)|\(404\)/i.test(text);
10861
+ }
10862
+ function isForkStyleHead(head) {
10863
+ return head.includes(":");
10864
+ }
10865
+ async function defaultReadRemoteHead(exec, repo, head) {
10866
+ try {
10867
+ await exec("gh", ["api", `repos/${repo}/git/ref/heads/${encodeURIComponent(head)}`], { timeout: 15e3 });
10868
+ return "present";
10869
+ } catch (e) {
10870
+ if (isGhHttpNotFound(e)) return "absent";
10871
+ return "unknown";
10872
+ }
10873
+ }
10874
+ async function defaultCompareRefs(exec, repo, base, head) {
10875
+ try {
10876
+ const { stdout } = await exec(
10877
+ "gh",
10878
+ ["api", `repos/${repo}/compare/${encodeURIComponent(base)}...${encodeURIComponent(head)}`],
10879
+ { timeout: 15e3 }
10880
+ );
10881
+ const aheadBy = JSON.parse(stdout).ahead_by;
10882
+ return typeof aheadBy === "number" ? { aheadBy } : "unknown";
10883
+ } catch {
10884
+ return "unknown";
10885
+ }
10886
+ }
10887
+ async function preflightPrCreateRemoteHead(args, deps) {
10888
+ const repo = flagValue(args, "--repo");
10889
+ const head = flagValue(args, "--head");
10890
+ if (!repo || !head || isForkStyleHead(head)) return void 0;
10891
+ const readRemoteHead = deps.readRemoteHead ?? ((input) => defaultReadRemoteHead(deps.exec, input.repo, input.head));
10892
+ const presence = await readRemoteHead({ repo, head });
10893
+ if (presence === "absent") return unpushedHeadMessage(head);
10894
+ if (presence !== "present") return void 0;
10895
+ const base = flagValue(args, "--base");
10896
+ if (!base) return void 0;
10897
+ const compareRefs = deps.compareRefs ?? ((input) => defaultCompareRefs(deps.exec, input.repo, input.base, input.head));
10898
+ const compared = await compareRefs({ repo, base, head });
10899
+ if (compared !== "unknown" && compared.aheadBy <= 0) return emptyCompareMessage(base, head);
10900
+ return void 0;
10901
+ }
10653
10902
  function buildPrCreateRetryCommand(args) {
10654
10903
  const parts = ["mmi-cli", "devops", "pr", "create"];
10655
10904
  for (const flag of ["--repo", "--base", "--head", "--title"]) {
@@ -11057,13 +11306,21 @@ async function createPrViaRestFallback(args, swappedArgs, deps, knownPools) {
11057
11306
  }
11058
11307
  async function ghCreate(args, deps = {}) {
11059
11308
  const exec = deps.exec ?? execFileP2;
11060
- const sleep2 = deps.sleep ?? ((ms) => new Promise((resolve5) => setTimeout(resolve5, ms)));
11309
+ const sleep2 = deps.sleep ?? ((ms) => new Promise((resolve6) => setTimeout(resolve6, ms)));
11061
11310
  const now = deps.now ?? Date.now;
11062
11311
  const read = deps.readFile ?? import_promises.readFile;
11063
11312
  const restCreatePr = deps.restCreatePr ?? ((input) => defaultRestCreatePr(exec, input));
11064
11313
  const restCreateIssue = deps.restCreateIssue ?? ((input) => defaultRestCreateIssue(exec, input));
11065
11314
  const findOpenPr = deps.findOpenPr ?? ((input) => defaultFindOpenPr(exec, input));
11066
11315
  const readRateLimit = deps.readRateLimit ?? (() => defaultReadRateLimit(exec));
11316
+ if (args[0] === "pr") {
11317
+ const refusal = await preflightPrCreateRemoteHead(args, {
11318
+ exec,
11319
+ readRemoteHead: deps.readRemoteHead,
11320
+ compareRefs: deps.compareRefs
11321
+ });
11322
+ if (refusal) return fail(refusal);
11323
+ }
11067
11324
  const swapped = await bodyArgsViaFile(args);
11068
11325
  const restDeps = {
11069
11326
  exec,
@@ -11082,6 +11339,8 @@ async function ghCreate(args, deps = {}) {
11082
11339
  } catch (restErr) {
11083
11340
  await swapped.cleanup();
11084
11341
  const restText = execErrorText(restErr);
11342
+ const humanRest = humanPrCreateRemoteHeadError(restText, args);
11343
+ if (humanRest) return fail(humanRest);
11085
11344
  if (isGhRateLimitError(restText)) {
11086
11345
  const pools2 = await readRateLimit();
11087
11346
  return rateLimitedResult({
@@ -11172,6 +11431,10 @@ async function ghCreate(args, deps = {}) {
11172
11431
  message: `${context}: GraphQL rate-limited \u2014 ${rateLimitResetNote(pools.graphql?.reset ?? pools.core?.reset, now())}`
11173
11432
  });
11174
11433
  }
11434
+ if (args[0] === "pr") {
11435
+ const human = humanPrCreateRemoteHeadError(errText, args);
11436
+ if (human) return fail(human);
11437
+ }
11175
11438
  if (isUpstreamGitHubFault(faultText)) return fail(upstreamFaultMessage(args[0], faultText));
11176
11439
  return fail(`gh ${args[0]} create failed: ${(err.stderr || err.stdout || err.message || String(e)).trim()}${note ? ` (${note})` : ""}`);
11177
11440
  }
@@ -11204,9 +11467,9 @@ function isValidSecretKey(key) {
11204
11467
  return KEY_RE.test(key);
11205
11468
  }
11206
11469
  function classifyTier(_slug, key) {
11207
- const slash = key.indexOf("/");
11208
- if (slash === -1) return "project";
11209
- return key.slice(0, slash) === PROJECT_TIER_SEGMENT ? "project" : "org";
11470
+ const slash2 = key.indexOf("/");
11471
+ if (slash2 === -1) return "project";
11472
+ return key.slice(0, slash2) === PROJECT_TIER_SEGMENT ? "project" : "org";
11210
11473
  }
11211
11474
  function secretParamName(slug, key) {
11212
11475
  return `${SSM_ROOT}/${slug}/${key}`;
@@ -11476,8 +11739,8 @@ async function probeCapabilities(deps, repo) {
11476
11739
  }
11477
11740
  }
11478
11741
  function secretKeyLeaf(key) {
11479
- const slash = key.lastIndexOf("/");
11480
- return slash === -1 ? key : key.slice(slash + 1);
11742
+ const slash2 = key.lastIndexOf("/");
11743
+ return slash2 === -1 ? key : key.slice(slash2 + 1);
11481
11744
  }
11482
11745
  function resolveNotFoundGuidance(input) {
11483
11746
  const { key, repo, slug, report } = input;
@@ -12147,8 +12410,8 @@ async function secretsRevoke(deps, repo, login, key, _opts) {
12147
12410
  }
12148
12411
  var SECRET_COPY_BLOCKED_RE = /(?:ENC_KEY|ENCRYPTION_KEY|SECRET_KEY_BASE)/i;
12149
12412
  function isSecretCopyBlocked(key) {
12150
- const slash = key.indexOf("/");
12151
- const leaf = slash === -1 ? key : key.slice(slash + 1);
12413
+ const slash2 = key.indexOf("/");
12414
+ const leaf = slash2 === -1 ? key : key.slice(slash2 + 1);
12152
12415
  return SECRET_COPY_BLOCKED_RE.test(leaf);
12153
12416
  }
12154
12417
  function copyTierKey(stage, leaf) {
@@ -12303,9 +12566,9 @@ function parseSecretsUseArgv(tail) {
12303
12566
  const flags = {};
12304
12567
  const keys = [];
12305
12568
  const firstSep = tail.indexOf("--");
12306
- const sep3 = firstSep !== -1 && separatorIsOurs(tail.slice(0, firstSep)) ? firstSep : -1;
12307
- const head = sep3 === -1 ? tail : tail.slice(0, sep3);
12308
- let command = sep3 === -1 ? [] : tail.slice(sep3 + 1).slice();
12569
+ const sep4 = firstSep !== -1 && separatorIsOurs(tail.slice(0, firstSep)) ? firstSep : -1;
12570
+ const head = sep4 === -1 ? tail : tail.slice(0, sep4);
12571
+ let command = sep4 === -1 ? [] : tail.slice(sep4 + 1).slice();
12309
12572
  for (let i = 0; i < head.length; ) {
12310
12573
  const tok = head[i];
12311
12574
  const eq = tok.indexOf("=");
@@ -12367,8 +12630,8 @@ var PRE_SPAWN_DRAIN_TIMEOUT_MS = 2e3;
12367
12630
  async function drainHttpPoolBeforeSpawn() {
12368
12631
  const drained = await Promise.race([
12369
12632
  closeHttpPool().then(() => true),
12370
- new Promise((resolve5) => {
12371
- setTimeout(() => resolve5(false), PRE_SPAWN_DRAIN_TIMEOUT_MS).unref?.();
12633
+ new Promise((resolve6) => {
12634
+ setTimeout(() => resolve6(false), PRE_SPAWN_DRAIN_TIMEOUT_MS).unref?.();
12372
12635
  })
12373
12636
  ]);
12374
12637
  if (!drained) destroyHttpPool();
@@ -12868,10 +13131,10 @@ var rollout_plan_default = {
12868
13131
  note: "The v4.0.0 stamp happens at cut time (D6e #4463); until then the candidate is the origin/development head artifacts (built cli/dist + npm pack), identity proven by dist content hash (D6a)."
12869
13132
  },
12870
13133
  baseline: {
12871
- version: "4.1.2",
12872
- tag: "v4.1.2",
12873
- commit: "bb1a56a57cda",
12874
- npm: "@mutmutco/cli@4.1.2"
13134
+ version: "4.1.4",
13135
+ tag: "v4.1.4",
13136
+ commit: "5b2b9ef75abf",
13137
+ npm: "@mutmutco/cli@4.1.4"
12875
13138
  },
12876
13139
  exitCriterion: "fleet-n-of-n",
12877
13140
  hubOnlyShortcut: "forbidden",
@@ -12888,14 +13151,14 @@ var rollout_plan_default = {
12888
13151
  repo: "mutmutco/mmi-hub",
12889
13152
  role: "canary",
12890
13153
  schedule: "train",
12891
- v3Target: "v4.1.2"
13154
+ v3Target: "v4.1.4"
12892
13155
  }
12893
13156
  ],
12894
13157
  rollbackTrigger: "Any red inside the post-contract soak window: `devops train gate` FAIL attributable to the v4 doors, Hub endpoint health probe failure, a pre-v4 client admitted instead of receiving actionable HTTP 426, or npm consumer install/doctor failure on the v4-only dist.",
12895
13158
  rollback: {
12896
13159
  independent: true,
12897
- mechanism: "npm dist-tag latest -> 4.1.2 and redeploy the Hub Lambda from tag v4.1.2 (bb1a56a57cda); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
12898
- v3Target: "v4.1.2 (@mutmutco/cli@4.1.2, tag commit bb1a56a57cda \u2014 last known-good release carrying the repo-index v4-only contract)"
13160
+ mechanism: "npm dist-tag latest -> 4.1.4 and redeploy the Hub Lambda from tag v4.1.4 (5b2b9ef75abf); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
13161
+ v3Target: "v4.1.4 (@mutmutco/cli@4.1.4, tag commit 5b2b9ef75abf \u2014 last known-good release carrying the repo-index v4-only contract)"
12899
13162
  }
12900
13163
  },
12901
13164
  {
@@ -13402,6 +13665,10 @@ var DEFAULT_LIMIT = 30;
13402
13665
  var MAX_LIMIT = 100;
13403
13666
  var CHILDREN_MAX_TOTAL = 100;
13404
13667
  var CHILDREN_MAX_DEPTH = 6;
13668
+ var BOARD_STATUS_ALIAS_BATCH = 25;
13669
+ var BOARD_STATUS_LOOKUP_CONCURRENCY = 3;
13670
+ var CHILD_PROJECT_ITEMS_PAGE = 50;
13671
+ var CHILD_PROJECT_ITEMS_FIELDS = `projectItems(first:${CHILD_PROJECT_ITEMS_PAGE}){nodes{project{id} fieldValues(first:20){nodes{... on ProjectV2ItemFieldSingleSelectValue{name field{...on ProjectV2SingleSelectField{name}}}}}}}`;
13405
13672
  var ISSUE_LIST_FIELDS = "number,title,state,url,assignees,labels";
13406
13673
  var PR_LIST_FIELDS = "number,title,state,url,headRefName,baseRefName";
13407
13674
  var QueryReadError = class extends Error {
@@ -13485,23 +13752,43 @@ async function runIssueList(deps, opts) {
13485
13752
  return shapeIssueList(rows);
13486
13753
  }
13487
13754
  function childrenGraphqlArgs(owner, name, number) {
13488
- const query = "query($owner:String!,$name:String!){repository(owner:$owner,name:$name){issue(number:" + number + "){number subIssues(first:100){nodes{number title state url assignees(first:10){nodes{login}} repository{nameWithOwner} projectItems(first:5){nodes{project{id} fieldValues(first:20){nodes{... on ProjectV2ItemFieldSingleSelectValue{name field{...on ProjectV2SingleSelectField{name}}}}}}} timelineItems(first:30,itemTypes:[CROSS_REFERENCED_EVENT]){nodes{...on CrossReferencedEvent{source{...on PullRequest{number title state url}}}}}}}}}}";
13755
+ const query = "query($owner:String!,$name:String!){repository(owner:$owner,name:$name){issue(number:" + number + "){number subIssues(first:100){nodes{number title state url assignees(first:10){nodes{login}} repository{nameWithOwner} " + CHILD_PROJECT_ITEMS_FIELDS + " timelineItems(first:30,itemTypes:[CROSS_REFERENCED_EVENT]){nodes{...on CrossReferencedEvent{source{...on PullRequest{number title state url}}}}}}}}}}";
13489
13756
  return ["api", "graphql", "-f", `query=${query}`, "-f", `owner=${owner}`, "-f", `name=${name}`];
13490
13757
  }
13491
- function shapeChildNode(node, depth, boardProjectId) {
13492
- const n = node ?? {};
13493
- const assigneeNodes = n.assignees?.nodes ?? [];
13494
- let boardStatus = null;
13495
- for (const pi of n.projectItems?.nodes ?? []) {
13496
- if (!boardProjectId || pi?.project?.id !== boardProjectId) continue;
13758
+ function childrenBoardStatusGraphqlArgs(owner, name, numbers) {
13759
+ const aliases = numbers.map((n, i) => `i${i}:issue(number:${n}){number ${CHILD_PROJECT_ITEMS_FIELDS}}`).join(" ");
13760
+ const query = `query($owner:String!,$name:String!){repository(owner:$owner,name:$name){${aliases}}}`;
13761
+ return ["api", "graphql", "-f", `query=${query}`, "-f", `owner=${owner}`, "-f", `name=${name}`];
13762
+ }
13763
+ function boardStatusFromProjectItems(nodes, boardProjectId) {
13764
+ if (!boardProjectId) return null;
13765
+ for (const pi of Array.isArray(nodes) ? nodes : []) {
13766
+ if (pi?.project?.id !== boardProjectId) continue;
13497
13767
  const status = (pi?.fieldValues?.nodes ?? []).find(
13498
13768
  (fv) => fv?.field?.name === "Status" && fv?.name
13499
13769
  );
13500
- if (status) {
13501
- boardStatus = String(status.name);
13502
- break;
13503
- }
13770
+ if (status) return String(status.name);
13771
+ }
13772
+ return null;
13773
+ }
13774
+ function extractIssueBoardStatusMap(resp, boardProjectId) {
13775
+ const map = /* @__PURE__ */ new Map();
13776
+ const repo = resp?.data?.repository;
13777
+ if (!repo || typeof repo !== "object") return map;
13778
+ for (const node of Object.values(repo)) {
13779
+ if (!node || typeof node !== "object") continue;
13780
+ const n = node;
13781
+ const number = Number(n.number);
13782
+ if (!Number.isFinite(number)) continue;
13783
+ const status = boardStatusFromProjectItems(n.projectItems?.nodes, boardProjectId);
13784
+ if (status) map.set(number, status);
13504
13785
  }
13786
+ return map;
13787
+ }
13788
+ function shapeChildNode(node, depth, boardProjectId) {
13789
+ const n = node ?? {};
13790
+ const assigneeNodes = n.assignees?.nodes ?? [];
13791
+ const boardStatus = boardStatusFromProjectItems(n.projectItems?.nodes, boardProjectId);
13505
13792
  const linkedPrs = [];
13506
13793
  const seenPr = /* @__PURE__ */ new Set();
13507
13794
  for (const ev of n.timelineItems?.nodes ?? []) {
@@ -13550,6 +13837,42 @@ async function queryChildren(deps, owner, name, number) {
13550
13837
  }
13551
13838
  return extractChildrenResponse(resp);
13552
13839
  }
13840
+ async function fillMissingChildBoardStatus(deps, children, boardProjectId) {
13841
+ if (!boardProjectId) return;
13842
+ const missing = children.filter((c) => !c.boardStatus && trySplitRepo(c.repo));
13843
+ if (!missing.length) return;
13844
+ const byRepo = /* @__PURE__ */ new Map();
13845
+ for (const c of missing) {
13846
+ const nums = byRepo.get(c.repo) ?? [];
13847
+ if (!nums.includes(c.number)) nums.push(c.number);
13848
+ byRepo.set(c.repo, nums);
13849
+ }
13850
+ const jobs = [];
13851
+ for (const [repo, numbers] of byRepo) {
13852
+ for (let i = 0; i < numbers.length; i += BOARD_STATUS_ALIAS_BATCH) {
13853
+ jobs.push({ repo, numbers: numbers.slice(i, i + BOARD_STATUS_ALIAS_BATCH) });
13854
+ }
13855
+ }
13856
+ const maps = await mapBounded(jobs, BOARD_STATUS_LOOKUP_CONCURRENCY, async (job) => {
13857
+ try {
13858
+ const { owner, name } = splitRepo(job.repo);
13859
+ const resp = await deps.ghJson(childrenBoardStatusGraphqlArgs(owner, name, job.numbers), GH_LIST_TIMEOUT_MS);
13860
+ return extractIssueBoardStatusMap(resp, boardProjectId);
13861
+ } catch {
13862
+ return /* @__PURE__ */ new Map();
13863
+ }
13864
+ });
13865
+ const resolved = /* @__PURE__ */ new Map();
13866
+ for (let i = 0; i < jobs.length; i++) {
13867
+ const job = jobs[i];
13868
+ for (const [num, status] of maps[i] ?? []) resolved.set(childKey(job.repo, num), status);
13869
+ }
13870
+ for (const c of children) {
13871
+ if (c.boardStatus) continue;
13872
+ const status = resolved.get(childKey(c.repo, c.number));
13873
+ if (status) c.boardStatus = status;
13874
+ }
13875
+ }
13553
13876
  async function runIssueChildren(deps, epic, opts) {
13554
13877
  const ref = parseIssueRef(epic);
13555
13878
  const repo = ref.repo ?? await deps.resolveRepo(void 0);
@@ -13566,7 +13889,10 @@ async function runIssueChildren(deps, epic, opts) {
13566
13889
  out.push(child2);
13567
13890
  seen.add(childKey(child2.repo, child2.number));
13568
13891
  }
13569
- if (!opts.recursive || out.length >= CHILDREN_MAX_TOTAL) return out;
13892
+ if (!opts.recursive || out.length >= CHILDREN_MAX_TOTAL) {
13893
+ await fillMissingChildBoardStatus(deps, out, boardProjectId);
13894
+ return out;
13895
+ }
13570
13896
  const queue = [];
13571
13897
  for (const child2 of out) {
13572
13898
  const sp = trySplitRepo(child2.repo);
@@ -13592,6 +13918,7 @@ async function runIssueChildren(deps, epic, opts) {
13592
13918
  if (sp) queue.push({ owner: sp.owner, name: sp.name, number: child2.number, depth: frame.depth + 1 });
13593
13919
  }
13594
13920
  }
13921
+ await fillMissingChildBoardStatus(deps, out, boardProjectId);
13595
13922
  return out;
13596
13923
  }
13597
13924
  var FRONTIER_BUCKET_ORDER = {
@@ -13990,7 +14317,7 @@ function registerQueryCommands(program3) {
13990
14317
  queryFail("issue list", e);
13991
14318
  }
13992
14319
  });
13993
- issue2.command("children <epic>").description("walk an epic's native sub-issue tree \u2014 each child: number/title/state/repo/assignee/boardStatus/linkedPrs; --recursive walks deeper").option("--recursive", "walk the full sub-issue tree (bounded depth + total cap)").option("--repo <owner/repo>", "repo for a bare epic ref (defaults to the current repo)").option("--json", "machine-readable output (already the default \u2014 accepted for contract uniformity)").action(async (epic, o) => {
14320
+ issue2.command("children <epic>").description("walk an epic's native sub-issue tree \u2014 each child: number/title/state/repo/assignee/boardStatus/linkedPrs (boardStatus null is inconclusive, not off-board); --recursive walks deeper").option("--recursive", "walk the full sub-issue tree (bounded depth + total cap)").option("--repo <owner/repo>", "repo for a bare epic ref (defaults to the current repo)").option("--json", "machine-readable output (already the default \u2014 accepted for contract uniformity)").action(async (epic, o) => {
13994
14321
  try {
13995
14322
  const childrenDeps = { ...deps, resolveRepo: async (r) => deps.resolveRepo(r ?? o.repo) };
13996
14323
  const boardProjectId = await resolveBoardProjectId(o.repo);
@@ -14913,6 +15240,183 @@ rollback stays available: mmi-cli devops train enforce --apply --disarm`
14913
15240
  });
14914
15241
  }
14915
15242
 
15243
+ // src/actions-billing-preflight.ts
15244
+ var CANARY_WORKFLOW = "actions-job-start-canary.yml";
15245
+ var CANARY_REPO = "mutmutco/MMI-Hub";
15246
+ var ACTIONS_BILLING_BLOCK_RE = /job was not started because recent account payments have failed|account payments have failed|spending limit needs to be increased|Billing & plans/i;
15247
+ var CANARY_POLLS = 20;
15248
+ var CANARY_POLL_MS = 1e3;
15249
+ function isActionsBillingBlockText(text) {
15250
+ return ACTIONS_BILLING_BLOCK_RE.test(text);
15251
+ }
15252
+ function interpretActionsJobStart(input) {
15253
+ const text = input.text ?? "";
15254
+ if (isActionsBillingBlockText(text)) return "billing-blocked";
15255
+ const jobs = input.jobs ?? [];
15256
+ if (jobs.some((job) => isActionsBillingBlockText(JSON.stringify(job)))) return "billing-blocked";
15257
+ if (jobs.some((job) => Array.isArray(job.steps) && job.steps.length > 0 || Boolean(job.startedAt))) {
15258
+ return "started";
15259
+ }
15260
+ if (jobs.length > 0 && jobs.every((job) => {
15261
+ const finished = job.conclusion === "failure" || job.status === "completed";
15262
+ const empty = !Array.isArray(job.steps) || job.steps.length === 0;
15263
+ return finished && empty && !job.startedAt;
15264
+ })) {
15265
+ return "never-started";
15266
+ }
15267
+ return "pending";
15268
+ }
15269
+ function actionsBillingRefusal(detail) {
15270
+ return new Error(
15271
+ `release refused: GitHub Actions cannot start a hosted job (billing/spending). ${detail} Fix Billing & plans / the org spending limit, then rerun. Do not mint a new tag. If a tag is already on origin, use \`mmi-cli devops release --retry-publish <run-id> --apply\` on that exact run \u2014 never recut.`
15272
+ );
15273
+ }
15274
+ function parseJson(raw, label) {
15275
+ try {
15276
+ return JSON.parse(raw);
15277
+ } catch {
15278
+ throw new Error(`${label} was not valid JSON`);
15279
+ }
15280
+ }
15281
+ async function scanRepoForBillingBlock(deps, repo) {
15282
+ let raw;
15283
+ try {
15284
+ raw = await deps.run("gh", [
15285
+ "run",
15286
+ "list",
15287
+ "--repo",
15288
+ repo,
15289
+ "--limit",
15290
+ "8",
15291
+ "--json",
15292
+ "databaseId,conclusion,status,displayTitle,url"
15293
+ ]);
15294
+ } catch {
15295
+ return void 0;
15296
+ }
15297
+ const rows = parseJson(raw, `gh run list --repo ${repo}`);
15298
+ if (!Array.isArray(rows)) return void 0;
15299
+ for (const row of rows) {
15300
+ const blob = `${row.displayTitle ?? ""} ${row.conclusion ?? ""} ${row.url ?? ""}`;
15301
+ if (isActionsBillingBlockText(blob)) {
15302
+ return `${repo} run ${row.databaseId ?? row.url ?? "(unknown)"} already names a billing/spending block`;
15303
+ }
15304
+ if (row.conclusion !== "failure" && row.status !== "completed") continue;
15305
+ if (typeof row.databaseId !== "number") continue;
15306
+ try {
15307
+ const view = await deps.run("gh", [
15308
+ "run",
15309
+ "view",
15310
+ String(row.databaseId),
15311
+ "--repo",
15312
+ repo,
15313
+ "--json",
15314
+ "jobs,conclusion,displayTitle,url"
15315
+ ]);
15316
+ const parsed = parseJson(view, `gh run view ${row.databaseId}`);
15317
+ const verdict = interpretActionsJobStart({
15318
+ jobs: parsed.jobs,
15319
+ text: `${parsed.displayTitle ?? ""} ${parsed.conclusion ?? ""} ${view}`
15320
+ });
15321
+ if (verdict === "billing-blocked" || verdict === "never-started") {
15322
+ return `${repo} run ${row.databaseId} (${parsed.url ?? row.url ?? "no url"}): hosted job never started` + (verdict === "billing-blocked" ? " (billing/spending refusal)" : " (empty steps)");
15323
+ }
15324
+ } catch {
15325
+ }
15326
+ }
15327
+ return void 0;
15328
+ }
15329
+ async function correlateCanaryRun(deps, nonce) {
15330
+ const sleep2 = deps.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
15331
+ let lastError = "no matching canary run";
15332
+ for (let attempt = 0; attempt < 8; attempt++) {
15333
+ if (attempt > 0) await sleep2(CANARY_POLL_MS);
15334
+ let raw;
15335
+ try {
15336
+ raw = await deps.run("gh", [
15337
+ "run",
15338
+ "list",
15339
+ "--repo",
15340
+ CANARY_REPO,
15341
+ "--workflow",
15342
+ CANARY_WORKFLOW,
15343
+ "--limit",
15344
+ "10",
15345
+ "--json",
15346
+ "databaseId,displayTitle,status,createdAt"
15347
+ ]);
15348
+ } catch (e) {
15349
+ lastError = e instanceof Error ? e.message : String(e);
15350
+ continue;
15351
+ }
15352
+ const rows = parseJson(
15353
+ raw,
15354
+ "canary gh run list"
15355
+ );
15356
+ const match = rows.find((row) => (row.displayTitle ?? "").includes(nonce) && typeof row.databaseId === "number");
15357
+ if (match?.databaseId) return match.databaseId;
15358
+ }
15359
+ throw new Error(
15360
+ `could not correlate ${CANARY_WORKFLOW} on ${CANARY_REPO} (nonce ${nonce}): ${lastError}`
15361
+ );
15362
+ }
15363
+ async function assertActionsJobsCanStart(deps, targetRepo3) {
15364
+ const scanned = await scanRepoForBillingBlock(deps, targetRepo3);
15365
+ if (scanned) throw actionsBillingRefusal(scanned);
15366
+ if (targetRepo3.toLowerCase() !== CANARY_REPO.toLowerCase()) {
15367
+ const hubScan = await scanRepoForBillingBlock(deps, CANARY_REPO);
15368
+ if (hubScan) throw actionsBillingRefusal(hubScan);
15369
+ }
15370
+ const nonce = `5604-${(deps.now ?? Date.now)().toString(36)}`;
15371
+ try {
15372
+ await deps.run("gh", [
15373
+ "workflow",
15374
+ "run",
15375
+ CANARY_WORKFLOW,
15376
+ "--repo",
15377
+ CANARY_REPO,
15378
+ "-f",
15379
+ `nonce=${nonce}`
15380
+ ]);
15381
+ } catch (e) {
15382
+ throw actionsBillingRefusal(
15383
+ `could not dispatch ${CANARY_WORKFLOW} on ${CANARY_REPO}: ${e instanceof Error ? e.message : String(e)}. A hosted publish job may not start (the v1.54.2 class).`
15384
+ );
15385
+ }
15386
+ const runId = await correlateCanaryRun(deps, nonce);
15387
+ const sleep2 = deps.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
15388
+ let last = "pending";
15389
+ let url = `https://github.com/${CANARY_REPO}/actions/runs/${runId}`;
15390
+ for (let attempt = 0; attempt < CANARY_POLLS; attempt++) {
15391
+ if (attempt > 0) await sleep2(CANARY_POLL_MS);
15392
+ const view = await deps.run("gh", [
15393
+ "run",
15394
+ "view",
15395
+ String(runId),
15396
+ "--repo",
15397
+ CANARY_REPO,
15398
+ "--json",
15399
+ "jobs,status,conclusion,url,displayTitle"
15400
+ ]);
15401
+ const parsed = parseJson(view, `canary gh run view ${runId}`);
15402
+ if (parsed.url) url = parsed.url;
15403
+ last = interpretActionsJobStart({
15404
+ jobs: parsed.jobs,
15405
+ text: `${parsed.displayTitle ?? ""} ${parsed.conclusion ?? ""} ${view}`
15406
+ });
15407
+ if (last === "started") {
15408
+ await deps.run("gh", ["run", "cancel", String(runId), "--repo", CANARY_REPO]).catch(() => "");
15409
+ return;
15410
+ }
15411
+ if (last === "billing-blocked" || last === "never-started") {
15412
+ throw actionsBillingRefusal(`${url}: hosted canary ${last}`);
15413
+ }
15414
+ }
15415
+ throw actionsBillingRefusal(
15416
+ `${url}: hosted canary stayed ${last} after ${CANARY_POLLS} polls \u2014 cannot prove a hosted job can start`
15417
+ );
15418
+ }
15419
+
14916
15420
  // src/train-apply.ts
14917
15421
  var TRAIN_BUMP_INTENTS = ["major", "minor", "patch"];
14918
15422
  function reduceFollowUpOutcomes(outcomes) {
@@ -15298,7 +15802,7 @@ async function runMergeTreePreflight(deps, ours, theirs) {
15298
15802
  async function predictMergeConflicts(deps, ours, theirs) {
15299
15803
  return runMergeTreePreflight(deps, ours, theirs);
15300
15804
  }
15301
- async function mergeWithToleratedResolution(deps, sourceRef, label, resolve5, extraTolerated = []) {
15805
+ async function mergeWithToleratedResolution(deps, sourceRef, label, resolve6, extraTolerated = []) {
15302
15806
  try {
15303
15807
  await deps.run("git", ["merge", sourceRef, "--no-edit"]);
15304
15808
  return;
@@ -15312,7 +15816,7 @@ async function mergeWithToleratedResolution(deps, sourceRef, label, resolve5, ex
15312
15816
  unmerged.length === 0 ? `${label} merge failed without conflicted paths \u2014 merge aborted; inspect the repo state and rerun` : `${label} merge conflicts on untolerated path(s): ${blocking.join(", ")} \u2014 merge aborted (the train is misaligned; reconcile the branches via an approved alignment PR, then rerun)`
15313
15817
  );
15314
15818
  }
15315
- await deps.run("git", ["checkout", `--${resolve5}`, "--", ...unmerged]);
15819
+ await deps.run("git", ["checkout", `--${resolve6}`, "--", ...unmerged]);
15316
15820
  await deps.run("git", ["add", "--", ...unmerged]);
15317
15821
  await deps.run("git", ["commit", "--no-edit"]);
15318
15822
  }
@@ -15460,7 +15964,7 @@ var CORRELATE_SKEW_SLACK_MS = 1e4;
15460
15964
  var CORRELATE_PAGE_LIMIT = 50;
15461
15965
  var RUN_CONFIRM_ATTEMPTS = 3;
15462
15966
  var RUN_CONFIRM_DELAY_MS = 1e3;
15463
- var defaultSleep = (ms) => new Promise((resolve5) => setTimeout(resolve5, ms));
15967
+ var defaultSleep = (ms) => new Promise((resolve6) => setTimeout(resolve6, ms));
15464
15968
  function resolveSleep(deps) {
15465
15969
  return deps.sleep ?? defaultSleep;
15466
15970
  }
@@ -16348,6 +16852,7 @@ async function preflight(deps, ctx, stage, meta) {
16348
16852
  throw new Error(`${ctx.repo} is not Hub-deployed (deployModel=none) \u2014 the release train does not apply; use the project's own release path`);
16349
16853
  }
16350
16854
  await deps.runSelf(["secrets", "preflight", "--stage", stage, "--repo", ctx.repo]);
16855
+ await assertActionsJobsCanStart(deps, ctx.repo);
16351
16856
  enforceGateBudget(deps, ctx.repo);
16352
16857
  if (model === "hub-serverless") {
16353
16858
  await deps.run("node", ["scripts/release-distribution.mjs", "verify-deps"]);
@@ -18525,7 +19030,7 @@ async function mergeAutoWithTransientRetry(prNumber, repo, deps) {
18525
19030
  if (first.mergeStatus !== "failed") return first;
18526
19031
  const ready = await deps.probeMergeReady(prNumber, repo).catch(() => ({ open: false, mergeable: false, checksPassing: false }));
18527
19032
  if (!ready.open || !ready.mergeable || !ready.checksPassing) return first;
18528
- const sleep2 = deps.sleep ?? ((ms) => new Promise((resolve5) => setTimeout(resolve5, ms)));
19033
+ const sleep2 = deps.sleep ?? ((ms) => new Promise((resolve6) => setTimeout(resolve6, ms)));
18529
19034
  await sleep2(PR_LAND_MERGE_RETRY_DELAY_MS);
18530
19035
  const retried = await deps.mergeAuto(prNumber, repo);
18531
19036
  if (retried.mergeStatus !== "failed") return retried;
@@ -18536,7 +19041,7 @@ var AUTO_MERGE_CONFIRM_DELAY_MS = 3e3;
18536
19041
  async function confirmAutoMergeEnqueued(deps, options) {
18537
19042
  const retries = options?.retries ?? AUTO_MERGE_CONFIRM_RETRIES;
18538
19043
  const delayMs = options?.delayMs ?? AUTO_MERGE_CONFIRM_DELAY_MS;
18539
- const sleep2 = deps.sleep ?? ((ms) => new Promise((resolve5) => setTimeout(resolve5, ms)));
19044
+ const sleep2 = deps.sleep ?? ((ms) => new Promise((resolve6) => setTimeout(resolve6, ms)));
18540
19045
  for (let attempt = 0; attempt < retries; attempt++) {
18541
19046
  if (await deps.readMerged().catch(() => false)) return "merged";
18542
19047
  const stuck = await deps.readAutoMergeRequest().then((s) => s.trim()).catch(() => "");
@@ -18553,7 +19058,7 @@ async function confirmAutoMergeEnqueued(deps, options) {
18553
19058
  async function readGhPrStateWithRetry(fetchState, options) {
18554
19059
  const retries = options?.retries ?? PR_LAND_STATE_READ_RETRIES;
18555
19060
  const delayMs = options?.delayMs ?? PR_LAND_STATE_READ_DELAY_MS;
18556
- const sleep2 = options?.sleep ?? ((ms) => new Promise((resolve5) => setTimeout(resolve5, ms)));
19061
+ const sleep2 = options?.sleep ?? ((ms) => new Promise((resolve6) => setTimeout(resolve6, ms)));
18557
19062
  let lastError = "empty state";
18558
19063
  for (let attempt = 0; attempt < retries; attempt++) {
18559
19064
  try {
@@ -18687,7 +19192,7 @@ function healthPollIntervalMs() {
18687
19192
  return HEALTH_POLL_INTERVAL_MS;
18688
19193
  }
18689
19194
  function waitForProcessStability(child2, graceMs = earlyExitGraceMs()) {
18690
- return new Promise((resolve5, reject) => {
19195
+ return new Promise((resolve6, reject) => {
18691
19196
  let settled = false;
18692
19197
  const finish = (fn) => {
18693
19198
  if (settled) return;
@@ -18697,7 +19202,7 @@ function waitForProcessStability(child2, graceMs = earlyExitGraceMs()) {
18697
19202
  child2.removeAllListeners("exit");
18698
19203
  fn();
18699
19204
  };
18700
- const timer = setTimeout(() => finish(resolve5), graceMs);
19205
+ const timer = setTimeout(() => finish(resolve6), graceMs);
18701
19206
  child2.on("error", (err) => finish(() => reject(new Error(`stage process failed to start: ${err.message}`))));
18702
19207
  child2.on("exit", (code, signal) => {
18703
19208
  const detail = code != null ? `code ${code}` : signal ? `signal ${signal}` : "unknown reason";
@@ -18903,10 +19408,10 @@ function pickStagePort(range, isFree) {
18903
19408
  throw new Error(`no free stage port in range ${start}-${end} \u2014 every port is in use`);
18904
19409
  }
18905
19410
  function isPortFree(port) {
18906
- return new Promise((resolve5) => {
19411
+ return new Promise((resolve6) => {
18907
19412
  const srv = (0, import_node_net.createServer)();
18908
- srv.once("error", () => resolve5(false));
18909
- srv.once("listening", () => srv.close(() => resolve5(true)));
19413
+ srv.once("error", () => resolve6(false));
19414
+ srv.once("listening", () => srv.close(() => resolve6(true)));
18910
19415
  srv.listen(port, "127.0.0.1");
18911
19416
  });
18912
19417
  }
@@ -19117,7 +19622,7 @@ async function killTree(pid) {
19117
19622
  } catch {
19118
19623
  }
19119
19624
  }
19120
- await new Promise((resolve5) => setTimeout(resolve5, 500));
19625
+ await new Promise((resolve6) => setTimeout(resolve6, 500));
19121
19626
  try {
19122
19627
  process.kill(-pid, "SIGKILL");
19123
19628
  } catch {
@@ -19138,7 +19643,7 @@ async function waitForHealth(url, timeoutMs, anyStatus = false) {
19138
19643
  } catch (e) {
19139
19644
  last = e.message;
19140
19645
  }
19141
- await new Promise((resolve5) => setTimeout(resolve5, healthPollIntervalMs()));
19646
+ await new Promise((resolve6) => setTimeout(resolve6, healthPollIntervalMs()));
19142
19647
  }
19143
19648
  throw new Error(`stage health check timed out for ${url}${last ? ` (${last})` : ""}`);
19144
19649
  }
@@ -19373,12 +19878,12 @@ async function executeWaveLand(plan, deps) {
19373
19878
  }
19374
19879
 
19375
19880
  // src/index.ts
19376
- var import_node_os19 = require("node:os");
19881
+ var import_node_os21 = require("node:os");
19377
19882
 
19378
19883
  // src/board.ts
19379
19884
  var import_node_child_process9 = require("node:child_process");
19380
- var import_node_fs19 = require("node:fs");
19381
- var import_node_os8 = require("node:os");
19885
+ var import_node_fs20 = require("node:fs");
19886
+ var import_node_os9 = require("node:os");
19382
19887
  var import_node_path17 = require("node:path");
19383
19888
  var import_node_util6 = require("node:util");
19384
19889
  init_github_client();
@@ -19389,8 +19894,10 @@ init_client_version();
19389
19894
  var BOARD_SNAPSHOT_TIMEOUT_MS = 25e3;
19390
19895
  function isSnapshotShape(body) {
19391
19896
  const b = body;
19897
+ const rate = b?.github?.rateLimit;
19898
+ const githubValid = b?.github === void 0 || b.github.credential === "app_installation" && Number.isFinite(rate?.limit) && Number.isFinite(rate?.remaining) && Number.isFinite(rate?.cost) && typeof rate?.resetAt === "string";
19392
19899
  return Boolean(
19393
- b && typeof b === "object" && typeof b.project?.id === "string" && typeof b.project?.title === "string" && typeof b.viewer === "string" && b.viewer.length > 0 && Array.isArray(b.nodes) && Array.isArray(b.writableRepos) && Array.isArray(b.unreadableRepos) && Array.isArray(b.pullRequests) && Array.isArray(b.warnings) && typeof b.partial === "boolean"
19900
+ b && typeof b === "object" && typeof b.project?.id === "string" && typeof b.project?.title === "string" && typeof b.viewer === "string" && b.viewer.length > 0 && Array.isArray(b.nodes) && Array.isArray(b.writableRepos) && Array.isArray(b.unreadableRepos) && Array.isArray(b.pullRequests) && Array.isArray(b.warnings) && typeof b.partial === "boolean" && githubValid
19394
19901
  );
19395
19902
  }
19396
19903
  async function fetchHubBoardSnapshot(request, deps) {
@@ -19824,7 +20331,7 @@ async function postIssueComment(client, input) {
19824
20331
  var SKILL_LESSON_LABEL = "skill-lesson";
19825
20332
  var SKILL_LESSON_FILE_LABELS = [SKILL_LESSON_LABEL, LEARNING_LABEL];
19826
20333
  var SKILL_LESSON_LOOP_KIND = "lesson";
19827
- var SKILL_NAMES = ["bootstrap", "browser-automation", "doctor", "epic", "hotfix", "mmi", "onboard", "rcand", "release", "resume", "secrets", "stage"];
20334
+ var SKILL_NAMES = ["bootstrap", "browser-automation", "doctor", "epic", "hotfix", "mmi", "onboard", "rcand", "release", "repo-index-audit", "resume", "secrets", "stage"];
19828
20335
  function assertSkillName(name) {
19829
20336
  const match = SKILL_NAMES.find((skill) => skill === name);
19830
20337
  if (!match) throw new Error(`unknown skill "${name}" \u2014 expected one of: ${SKILL_NAMES.join(", ")}`);
@@ -19856,7 +20363,7 @@ function findDuplicateLesson(source, openLessons) {
19856
20363
 
19857
20364
  // src/session-identity.ts
19858
20365
  var import_node_crypto4 = require("node:crypto");
19859
- var import_node_os7 = require("node:os");
20366
+ var import_node_os8 = require("node:os");
19860
20367
  init_plugin_guard_io();
19861
20368
  var SESSION_ID_ENV_VARS = [
19862
20369
  "MMI_SESSION_ID",
@@ -19892,7 +20399,7 @@ function describeSessionIdentity(env = process.env) {
19892
20399
  return {
19893
20400
  session: readSessionId(env) ?? fallbackSessionId(surface),
19894
20401
  surface,
19895
- host: (0, import_node_os7.hostname)()
20402
+ host: (0, import_node_os8.hostname)()
19896
20403
  };
19897
20404
  }
19898
20405
 
@@ -20173,6 +20680,10 @@ function renderBoardItem(item) {
20173
20680
  }
20174
20681
  function renderBoardReport(report) {
20175
20682
  const lines = [`Board \xB7 ${report.project.title} \xB7 @${report.viewer}`, renderBoardSource()];
20683
+ if (report.github) {
20684
+ const { limit, remaining, cost, resetAt } = report.github.rateLimit;
20685
+ lines.push(`github: app installation \xB7 GraphQL ${remaining}/${limit} remaining \xB7 cost ${cost} \xB7 resets ${resetAt}`);
20686
+ }
20176
20687
  renderScope(lines, "PRIMARY", report.repo, report.primary, report.viewer);
20177
20688
  renderScope(lines, "SECONDARY", "Other repos on this project", report.secondary, report.viewer);
20178
20689
  if (report.warnings.length) {
@@ -20285,6 +20796,7 @@ async function readBoard(options, deps = {}) {
20285
20796
  let collected;
20286
20797
  let writable;
20287
20798
  let pullRequests;
20799
+ let github;
20288
20800
  let snapshotFallback;
20289
20801
  const attempt = deps.snapshot ? await fetchHubBoardSnapshot(
20290
20802
  {
@@ -20329,6 +20841,7 @@ async function readBoard(options, deps = {}) {
20329
20841
  unknown: new Set(snapshot.unreadableRepos.map((entry) => entry.repo.toLowerCase()))
20330
20842
  };
20331
20843
  pullRequests = snapshot.pullRequests;
20844
+ github = snapshot.github;
20332
20845
  } else {
20333
20846
  if (attempt?.state === "unavailable") snapshotFallback = attempt.reason;
20334
20847
  collected = await collectBoardItems(cfg, { repo: options.repo, allowPartial: options.allowPartial, activeOnly: true }, deps);
@@ -20349,7 +20862,8 @@ async function readBoard(options, deps = {}) {
20349
20862
  warnings: collected.warnings,
20350
20863
  partial: collected.partial,
20351
20864
  source: "live",
20352
- ...pullRequests ? { pullRequests } : {}
20865
+ ...pullRequests ? { pullRequests } : {},
20866
+ ...github ? { github } : {}
20353
20867
  };
20354
20868
  if (options.includeBundleDetails || options.includeAllBodies) {
20355
20869
  await attachBundleDetails(report, client, options.allowPartial ?? false, { all: options.includeAllBodies });
@@ -20521,7 +21035,14 @@ async function prepareClaimContext(options, selectors, deps, collected) {
20521
21035
  report[scope].claimable = filtered.claimable;
20522
21036
  report.warnings.push(...filtered.warnings);
20523
21037
  }
20524
- return { cfg, client, items: collected.items, writable: writableOrUnknown(writable), report };
21038
+ return {
21039
+ cfg,
21040
+ client,
21041
+ items: collected.items,
21042
+ writable: writableOrUnknown(writable),
21043
+ report,
21044
+ session: deps.session ?? describeSessionIdentity()
21045
+ };
20525
21046
  }
20526
21047
  async function claimOneBoardItem(ctx, selector, options) {
20527
21048
  const { cfg, client, report } = ctx;
@@ -20566,26 +21087,29 @@ async function claimOneBoardItem(ctx, selector, options) {
20566
21087
  const verdict = evaluateClaim(fresh, assignedLogin);
20567
21088
  if (!verdict.ok) throw new Error(verdict.reason);
20568
21089
  item = fresh;
21090
+ const refuseIfContested = async () => {
21091
+ if (options.force) return;
21092
+ const contest = await checkLaneContest(client, item, ctx.session);
21093
+ if (contest.contested) throw new Error(laneContestMessage(item.ref, contest, "claim"));
21094
+ };
21095
+ await refuseIfContested();
20569
21096
  if (verdict.alreadyClaimed) {
20570
- if (!options.force) {
20571
- const contest = await checkLaneContest(client, item);
20572
- if (contest.contested) throw new Error(laneContestMessage(item.ref, contest, "claim"));
20573
- }
20574
21097
  if (options.check) {
20575
21098
  return { item, viewer: report.viewer, repo: report.repo, status: "In Progress", partial: false, alreadyClaimed: true, checked: true };
20576
21099
  }
20577
- await postClaimMarkerComment(client, item);
21100
+ await postClaimMarkerComment(client, item, ctx.session);
20578
21101
  return { item, viewer: report.viewer, repo: report.repo, status: "In Progress", partial: false, alreadyClaimed: true };
20579
21102
  }
20580
21103
  if (options.check) {
20581
21104
  return { item, viewer: report.viewer, repo: report.repo, status: item.status, partial: false, checked: true };
20582
21105
  }
21106
+ await refuseIfContested();
20583
21107
  try {
20584
21108
  await client.rest("POST", `repos/${item.repository}/issues/${item.number}/assignees`, { body: { assignees: [assignedLogin] } });
20585
21109
  } catch (e) {
20586
21110
  throw new Error(`claim failed before board status changed: ${ghError(e)}`);
20587
21111
  }
20588
- await postClaimMarkerComment(client, item);
21112
+ await postClaimMarkerComment(client, item, ctx.session);
20589
21113
  try {
20590
21114
  await updateItemSingleSelect(client, cfg.projectId, item.itemId, cfg.statusFieldId, cfg.statusOptions["In Progress"]);
20591
21115
  } catch (e) {
@@ -20778,7 +21302,7 @@ async function setBoardItemPriority(client, cfg, itemId, priority) {
20778
21302
  await updateItemSingleSelect(client, cfg.projectId, itemId, cfg.priorityFieldId, optionId);
20779
21303
  return cliPriorityToFieldName(priority);
20780
21304
  }
20781
- var defaultRetrySleep = (ms) => new Promise((resolve5) => setTimeout(resolve5, ms));
21305
+ var defaultRetrySleep = (ms) => new Promise((resolve6) => setTimeout(resolve6, ms));
20782
21306
  async function resolveProjectItemIdWithRetry(client, cfg, selector, opts = {}) {
20783
21307
  const attempts = Math.max(1, opts.attempts ?? 5);
20784
21308
  const delayMs = opts.delayMs ?? 300;
@@ -21211,9 +21735,8 @@ function boardItemClaim(item) {
21211
21735
  currentlyClaimed: item.assignees.length > 0 && item.status === "In Progress"
21212
21736
  };
21213
21737
  }
21214
- async function postClaimMarkerComment(client, item) {
21738
+ async function postClaimMarkerComment(client, item, actor = describeSessionIdentity()) {
21215
21739
  try {
21216
- const actor = describeSessionIdentity();
21217
21740
  const marker = {
21218
21741
  v: 1,
21219
21742
  session: actor.session,
@@ -21236,7 +21759,7 @@ var CLAIM_SESSION_ACTIVITY_MS = 30 * 6e4;
21236
21759
  var CLAIM_SESSION_PROBE_CACHE_MS = 6e4;
21237
21760
  var claimSessionProbeCache = /* @__PURE__ */ new Map();
21238
21761
  function probeLocalClaimSession(marker, now = Date.now()) {
21239
- if (!marker.session || !marker.host || marker.host.toLowerCase() !== (0, import_node_os8.hostname)().toLowerCase()) return void 0;
21762
+ if (!marker.session || !marker.host || marker.host.toLowerCase() !== (0, import_node_os9.hostname)().toLowerCase()) return void 0;
21240
21763
  if (!marker.surface?.toLowerCase().startsWith("claude")) return void 0;
21241
21764
  const cacheKey = `${marker.host.toLowerCase()}/${marker.session}`;
21242
21765
  const cached = claimSessionProbeCache.get(cacheKey);
@@ -21245,17 +21768,17 @@ function probeLocalClaimSession(marker, now = Date.now()) {
21245
21768
  claimSessionProbeCache.set(cacheKey, { checkedAt: now, state });
21246
21769
  return state;
21247
21770
  };
21248
- const root = (0, import_node_path17.join)((0, import_node_os8.homedir)(), ".claude", "projects");
21771
+ const root = (0, import_node_path17.join)((0, import_node_os9.homedir)(), ".claude", "projects");
21249
21772
  try {
21250
21773
  const wanted = `${marker.session}.jsonl`.toLowerCase();
21251
21774
  const pending = [root];
21252
21775
  while (pending.length) {
21253
21776
  const dir = pending.pop();
21254
- for (const entry of (0, import_node_fs19.readdirSync)(dir, { withFileTypes: true })) {
21777
+ for (const entry of (0, import_node_fs20.readdirSync)(dir, { withFileTypes: true })) {
21255
21778
  const path2 = (0, import_node_path17.join)(dir, entry.name);
21256
21779
  if (entry.isDirectory()) pending.push(path2);
21257
21780
  else if (entry.isFile() && entry.name.toLowerCase() === wanted) {
21258
- return remember(now - (0, import_node_fs19.statSync)(path2).mtimeMs <= CLAIM_SESSION_ACTIVITY_MS ? "live" : "dead");
21781
+ return remember(now - (0, import_node_fs20.statSync)(path2).mtimeMs <= CLAIM_SESSION_ACTIVITY_MS ? "live" : "dead");
21259
21782
  }
21260
21783
  }
21261
21784
  }
@@ -21367,9 +21890,8 @@ function laneOwnership(marker, current) {
21367
21890
  }
21368
21891
  return "unknown";
21369
21892
  }
21370
- async function checkLaneContest(client, item) {
21893
+ async function checkLaneContest(client, item, actor = describeSessionIdentity()) {
21371
21894
  const evidence = await gatherClaimLiveness(client, item.repository, item.number, openPullsFetcher(client));
21372
- const actor = describeSessionIdentity();
21373
21895
  const ownership = laneOwnership(evidence.marker, actor);
21374
21896
  const live = ownership === "mine" ? [] : liveEvidenceLines(evidence, item.repository);
21375
21897
  const unverifiable = ownership === "mine" ? [] : evidence.failed;
@@ -21567,7 +22089,7 @@ async function resolveAutoAddBoardAttach(client, cfg, selector, priority, warn =
21567
22089
  }
21568
22090
 
21569
22091
  // src/issue-body.ts
21570
- var import_node_os9 = require("node:os");
22092
+ var import_node_os10 = require("node:os");
21571
22093
  init_error_codes();
21572
22094
  var TextArgError = class extends Error {
21573
22095
  constructor(message, code, offendingFlag) {
@@ -21580,7 +22102,7 @@ var TextArgError = class extends Error {
21580
22102
  offendingFlag;
21581
22103
  };
21582
22104
  function emptyStdinMessage(fileFlag) {
21583
- if ((0, import_node_os9.platform)() === "win32") {
22105
+ if ((0, import_node_os10.platform)() === "win32") {
21584
22106
  return `${fileFlag} - read empty stdin (on Windows, ${fileFlag} - is unreliable through the npm .cmd shim \u2014 use ${fileFlag} <path>, or pipe to \`node cli/dist/index.cjs\` directly)`;
21585
22107
  }
21586
22108
  return `${fileFlag} - read empty stdin (nothing piped \u2014 pass a heredoc/pipe, or ${fileFlag} <path>)`;
@@ -21681,12 +22203,13 @@ var PRIMARY_GROUPS = [
21681
22203
  ["Review and ship", ["pr", "ci", "rcand", "release", "hotfix", "train"]],
21682
22204
  // `tests` sits beside `docs` deliberately: both are deterministic, repo-local gates a workflow
21683
22205
  // step invokes (`docs refs`, `tests policy`), not org-plane operations (#3605). `spawn policy`
21684
- // joins them on the same footing (#3979).
21685
- ["Setup and support", ["bootstrap", "secrets", "docs", "repo-index", "tests", "spawn"]],
22206
+ // joins them on the same footing (#3979); `dist status` does too (#5576) — the checkout's own
22207
+ // dist/BOM freshness read.
22208
+ ["Setup and support", ["bootstrap", "secrets", "docs", "repo-index", "tests", "spawn", "dist"]],
21686
22209
  ["Coordinate and improve", ["wave", "report", "skill-lesson", "closure-rate"]]
21687
22210
  ];
21688
22211
  var OPERATIONAL_TOP_LEVEL = /* @__PURE__ */ new Set(["org", "runtime", "plugin"]);
21689
- var SUPPORT_PRIMARY = /* @__PURE__ */ new Set(["doctor", "whoami", "commands", "explain", "docs", "repo-index", "find", "tests", "spawn", "wave", "report", "skill-lesson", "closure-rate"]);
22212
+ var SUPPORT_PRIMARY = /* @__PURE__ */ new Set(["doctor", "whoami", "commands", "explain", "docs", "repo-index", "find", "tests", "spawn", "dist", "wave", "report", "skill-lesson", "closure-rate"]);
21690
22213
  var TOP_LEVEL_ORDER = /* @__PURE__ */ new Map();
21691
22214
  var HELP_GROUP_ORDER = /* @__PURE__ */ new Map();
21692
22215
  var topLevelPosition = 0;
@@ -21739,6 +22262,7 @@ var COMMAND_OWNERSHIP = {
21739
22262
  find: { module_owner: "cli/src/repo-index.ts", consumer: "agent-session" },
21740
22263
  tests: { module_owner: "cli/src/test-policy-core.ts", consumer: "repo-gates" },
21741
22264
  spawn: { module_owner: "cli/src/spawn-policy-core.ts", consumer: "repo-gates" },
22265
+ dist: { module_owner: "cli/src/dist-drift.ts", consumer: "repo-gates" },
21742
22266
  wave: { module_owner: "cli/src/wave-land.ts", consumer: "campaign-orchestrator" },
21743
22267
  report: { module_owner: "cli/src/report.ts", consumer: "campaign-orchestrator" },
21744
22268
  "skill-lesson": { module_owner: "cli/src/skill-lesson.ts", consumer: "campaign-orchestrator" },
@@ -22260,13 +22784,13 @@ init_hub_url();
22260
22784
  init_client_version();
22261
22785
 
22262
22786
  // src/claude-binary-doctor.ts
22263
- var import_node_fs21 = require("node:fs");
22264
- var import_node_os11 = require("node:os");
22787
+ var import_node_fs22 = require("node:fs");
22788
+ var import_node_os12 = require("node:os");
22265
22789
  var import_node_path19 = require("node:path");
22266
22790
 
22267
22791
  // src/jerv-cli-spawn.ts
22268
- var import_node_fs20 = require("node:fs");
22269
- var import_node_os10 = require("node:os");
22792
+ var import_node_fs21 = require("node:fs");
22793
+ var import_node_os11 = require("node:os");
22270
22794
  var import_node_path18 = require("node:path");
22271
22795
  init_cli_shared();
22272
22796
  var WIN_NAMES = ["jerv-cli.cmd", "jerv-cli.exe", "jerv-cli"];
@@ -22293,7 +22817,7 @@ function normalizeSpawnPathEntry(entry, platform2 = process.platform) {
22293
22817
  if (msys) return `${msys[1].toUpperCase()}:\\${msys[2].replace(/\//g, "\\")}`;
22294
22818
  return trimmed;
22295
22819
  }
22296
- function jervCliCandidateDirs(env = process.env, home = (0, import_node_os10.homedir)(), platform2 = process.platform) {
22820
+ function jervCliCandidateDirs(env = process.env, home = (0, import_node_os11.homedir)(), platform2 = process.platform) {
22297
22821
  const seen = /* @__PURE__ */ new Set();
22298
22822
  const out = [];
22299
22823
  const push = (dir) => {
@@ -22314,7 +22838,7 @@ function jervCliCandidateDirs(env = process.env, home = (0, import_node_os10.hom
22314
22838
  }
22315
22839
  return out;
22316
22840
  }
22317
- function jervCliCandidatePaths(env = process.env, home = (0, import_node_os10.homedir)(), platform2 = process.platform) {
22841
+ function jervCliCandidatePaths(env = process.env, home = (0, import_node_os11.homedir)(), platform2 = process.platform) {
22318
22842
  const names = platform2 === "win32" ? WIN_NAMES : POSIX_NAMES;
22319
22843
  const out = [];
22320
22844
  for (const dir of jervCliCandidateDirs(env, home, platform2)) {
@@ -22322,20 +22846,20 @@ function jervCliCandidatePaths(env = process.env, home = (0, import_node_os10.ho
22322
22846
  }
22323
22847
  return out;
22324
22848
  }
22325
- function resolveJervCliPath(env = process.env, home = (0, import_node_os10.homedir)(), platform2 = process.platform, exists = import_node_fs20.existsSync) {
22849
+ function resolveJervCliPath(env = process.env, home = (0, import_node_os11.homedir)(), platform2 = process.platform, exists = import_node_fs21.existsSync) {
22326
22850
  for (const candidate2 of jervCliCandidatePaths(env, home, platform2)) {
22327
22851
  if (exists(candidate2)) return candidate2;
22328
22852
  }
22329
22853
  return void 0;
22330
22854
  }
22331
- function resolveJervCliNodeEntry(shimPath, exists = import_node_fs20.existsSync) {
22855
+ function resolveJervCliNodeEntry(shimPath, exists = import_node_fs21.existsSync) {
22332
22856
  const entry = (0, import_node_path18.join)((0, import_node_path18.dirname)(shimPath), JERV_CLI_ENTRY);
22333
22857
  return exists(entry) ? entry : void 0;
22334
22858
  }
22335
22859
  function jervCliExecFileArgs(args, opts = {}) {
22336
22860
  const platform2 = opts.platform ?? process.platform;
22337
- const exists = opts.exists ?? import_node_fs20.existsSync;
22338
- const resolved = resolveJervCliPath(opts.env ?? process.env, opts.home ?? (0, import_node_os10.homedir)(), platform2, exists);
22861
+ const exists = opts.exists ?? import_node_fs21.existsSync;
22862
+ const resolved = resolveJervCliPath(opts.env ?? process.env, opts.home ?? (0, import_node_os11.homedir)(), platform2, exists);
22339
22863
  if (resolved) {
22340
22864
  const entry = resolveJervCliNodeEntry(resolved, exists);
22341
22865
  if (entry) {
@@ -22425,7 +22949,7 @@ function globalNodeModulesRoots(host) {
22425
22949
  };
22426
22950
  const prefix = env.npm_config_prefix?.trim();
22427
22951
  if (prefix) push(platform2 === "win32" ? (0, import_node_path19.join)(prefix, "node_modules") : (0, import_node_path19.join)(prefix, "lib", "node_modules"));
22428
- for (const dir of jervCliCandidateDirs(env, host.home ?? (0, import_node_os11.homedir)(), platform2)) {
22952
+ for (const dir of jervCliCandidateDirs(env, host.home ?? (0, import_node_os12.homedir)(), platform2)) {
22429
22953
  push((0, import_node_path19.join)(dir, "node_modules"));
22430
22954
  push((0, import_node_path19.join)((0, import_node_path19.dirname)(dir), "lib", "node_modules"));
22431
22955
  }
@@ -22434,16 +22958,16 @@ function globalNodeModulesRoots(host) {
22434
22958
  function readHead(path2) {
22435
22959
  let fd;
22436
22960
  try {
22437
- fd = (0, import_node_fs21.openSync)(path2, "r");
22961
+ fd = (0, import_node_fs22.openSync)(path2, "r");
22438
22962
  const buffer = new Uint8Array(MAGIC_HEAD_BYTES);
22439
- const read = (0, import_node_fs21.readSync)(fd, buffer, 0, MAGIC_HEAD_BYTES, 0);
22963
+ const read = (0, import_node_fs22.readSync)(fd, buffer, 0, MAGIC_HEAD_BYTES, 0);
22440
22964
  return buffer.subarray(0, read);
22441
22965
  } catch {
22442
22966
  return void 0;
22443
22967
  } finally {
22444
22968
  if (fd !== void 0) {
22445
22969
  try {
22446
- (0, import_node_fs21.closeSync)(fd);
22970
+ (0, import_node_fs22.closeSync)(fd);
22447
22971
  } catch {
22448
22972
  }
22449
22973
  }
@@ -22451,7 +22975,7 @@ function readHead(path2) {
22451
22975
  }
22452
22976
  function fileBytes(path2) {
22453
22977
  try {
22454
- return (0, import_node_fs21.statSync)(path2).size;
22978
+ return (0, import_node_fs22.statSync)(path2).size;
22455
22979
  } catch {
22456
22980
  return void 0;
22457
22981
  }
@@ -22465,13 +22989,13 @@ function readClaudeBinaryState(host = {}) {
22465
22989
  const arch = host.arch ?? process.arch;
22466
22990
  const magic = EXECUTABLE_MAGIC[platform2];
22467
22991
  if (!magic) return void 0;
22468
- const packageRoot = globalNodeModulesRoots(host).map((root) => (0, import_node_path19.join)(root, ...PACKAGE.split("/"))).find((dir) => (0, import_node_fs21.existsSync)((0, import_node_path19.join)(dir, "package.json")));
22992
+ const packageRoot = globalNodeModulesRoots(host).map((root) => (0, import_node_path19.join)(root, ...PACKAGE.split("/"))).find((dir) => (0, import_node_fs22.existsSync)((0, import_node_path19.join)(dir, "package.json")));
22469
22993
  if (!packageRoot) return void 0;
22470
22994
  const keys = platformPackageKeys(platform2, arch);
22471
22995
  const fallbackPackage = `${PACKAGE}-${keys[0]}`;
22472
22996
  let manifest;
22473
22997
  try {
22474
- manifest = JSON.parse((0, import_node_fs21.readFileSync)((0, import_node_path19.join)(packageRoot, "package.json"), "utf8"));
22998
+ manifest = JSON.parse((0, import_node_fs22.readFileSync)((0, import_node_path19.join)(packageRoot, "package.json"), "utf8"));
22475
22999
  } catch (e) {
22476
23000
  return {
22477
23001
  state: "unreadable",
@@ -22502,7 +23026,7 @@ function readClaudeBinaryState(host = {}) {
22502
23026
  (0, import_node_path19.join)(packageRoot, "node_modules", ...name.split("/"), binName),
22503
23027
  (0, import_node_path19.join)((0, import_node_path19.dirname)((0, import_node_path19.dirname)(packageRoot)), ...name.split("/"), binName)
22504
23028
  ];
22505
- const found = published.map((name) => ({ name, path: binIn(name).find((file) => (0, import_node_fs21.existsSync)(file)) })).find((c) => c.path);
23029
+ const found = published.map((name) => ({ name, path: binIn(name).find((file) => (0, import_node_fs22.existsSync)(file)) })).find((c) => c.path);
22506
23030
  const platformPackage = found?.name ?? published[0];
22507
23031
  let source;
22508
23032
  let sourceProblem;
@@ -22513,7 +23037,7 @@ function readClaudeBinaryState(host = {}) {
22513
23037
  } else {
22514
23038
  source = { path: found.path, bytes: fileBytes(found.path) ?? 0 };
22515
23039
  }
22516
- if (!(0, import_node_fs21.existsSync)(binPath)) {
23040
+ if (!(0, import_node_fs22.existsSync)(binPath)) {
22517
23041
  return { state: "missing", binPath, expectedMagic: magic.name, platformPackage, ...source ? { source } : {}, ...sourceProblem ? { sourceProblem } : {} };
22518
23042
  }
22519
23043
  const head = readHead(binPath);
@@ -22556,9 +23080,9 @@ function healClaudeBinary(host = {}, onStep) {
22556
23080
  const platform2 = host.platform ?? process.platform;
22557
23081
  const aside = `${probe.binPath}.stub-${Date.now()}`;
22558
23082
  let renamed = false;
22559
- if ((0, import_node_fs21.existsSync)(probe.binPath)) {
23083
+ if ((0, import_node_fs22.existsSync)(probe.binPath)) {
22560
23084
  try {
22561
- (0, import_node_fs21.renameSync)(probe.binPath, aside);
23085
+ (0, import_node_fs22.renameSync)(probe.binPath, aside);
22562
23086
  renamed = true;
22563
23087
  onStep?.(`renamed the stub aside: ${aside}`);
22564
23088
  } catch (e) {
@@ -22567,12 +23091,12 @@ function healClaudeBinary(host = {}, onStep) {
22567
23091
  }
22568
23092
  try {
22569
23093
  onStep?.(`copying ${probe.source.path} \u2192 ${probe.binPath} (${(probe.source.bytes / 1e6).toFixed(0)} MB)`);
22570
- (0, import_node_fs21.copyFileSync)(probe.source.path, probe.binPath);
22571
- if (platform2 !== "win32") (0, import_node_fs21.chmodSync)(probe.binPath, 493);
23094
+ (0, import_node_fs22.copyFileSync)(probe.source.path, probe.binPath);
23095
+ if (platform2 !== "win32") (0, import_node_fs22.chmodSync)(probe.binPath, 493);
22572
23096
  } catch (e) {
22573
23097
  if (renamed) {
22574
23098
  try {
22575
- (0, import_node_fs21.renameSync)(aside, probe.binPath);
23099
+ (0, import_node_fs22.renameSync)(aside, probe.binPath);
22576
23100
  } catch {
22577
23101
  return { ok: false, detail: `copy failed (${e.message}) and the stub could not be restored \u2014 the original is at ${aside}` };
22578
23102
  }
@@ -22586,7 +23110,7 @@ function healClaudeBinary(host = {}, onStep) {
22586
23110
  let kept = false;
22587
23111
  if (renamed) {
22588
23112
  try {
22589
- (0, import_node_fs21.rmSync)(aside);
23113
+ (0, import_node_fs22.rmSync)(aside);
22590
23114
  } catch {
22591
23115
  kept = true;
22592
23116
  }
@@ -22951,6 +23475,7 @@ function trainPlan(command, options = {}) {
22951
23475
  { label: "verify current branch is development", gated: true },
22952
23476
  { label: "verify registry META for this project", command: "mmi-cli oracle org project get <owner/repo>", gated: true },
22953
23477
  { label: "preflight required main secret names", command: "mmi-cli vault secrets preflight --stage main --repo <owner/repo>", gated: true },
23478
+ { label: "preflight GitHub Actions hosted job start (billing/spending) before minting a tag", command: "train dispatches actions-job-start-canary.yml on MMI-Hub (#5604); exact-run --retry-publish skips this", gated: true },
22954
23479
  { label: "merge development to main", gated: true },
22955
23480
  { label: "fold the version bump into the release commit (Hub: full distribution set; app repos: root package manifest) \u2014 runs inside the apply step, no separate bump PR", gated: true },
22956
23481
  { label: "tag release and publish GitHub Release", gated: true },
@@ -22966,6 +23491,7 @@ function trainPlan(command, options = {}) {
22966
23491
  { label: "guard: refuse if origin/rc carries content not in development (a dev -> main release would drop it)", command: "git rev-list --count --right-only --cherry-pick --no-merges origin/development...origin/rc", gated: true },
22967
23492
  { label: "verify registry META for this project", command: "mmi-cli oracle org project get <owner/repo>", gated: true },
22968
23493
  { label: "preflight required main secret names", command: "mmi-cli vault secrets preflight --stage main --repo <owner/repo>", gated: true },
23494
+ { label: "preflight GitHub Actions hosted job start (billing/spending) before minting a tag", command: "train dispatches actions-job-start-canary.yml on MMI-Hub (#5604); exact-run --retry-publish skips this", gated: true },
22969
23495
  { label: "merge development to main (rc skipped)", gated: true },
22970
23496
  { label: "fold the version bump into the release commit \u2014 runs inside the apply step, no separate bump PR", gated: true },
22971
23497
  { label: "tag release and publish GitHub Release", gated: true },
@@ -22980,6 +23506,7 @@ function trainPlan(command, options = {}) {
22980
23506
  { label: "verify current branch is rc", gated: true },
22981
23507
  { label: "verify registry META for this project", command: "mmi-cli oracle org project get <owner/repo>", gated: true },
22982
23508
  { label: "preflight required main secret names", command: "mmi-cli vault secrets preflight --stage main --repo <owner/repo>", gated: true },
23509
+ { label: "preflight GitHub Actions hosted job start (billing/spending) before minting a tag", command: "train dispatches actions-job-start-canary.yml on MMI-Hub (#5604); exact-run --retry-publish skips this", gated: true },
22983
23510
  { label: "verify every main-only hotfix commit is covered by the rc candidate (the guard runs automatically inside the apply step below; --ack <sha> overrides a verified, trailer-less port)", command: "mmi-cli devops release --apply [--ack <sha>]", gated: true },
22984
23511
  { label: "merge rc to main", gated: true },
22985
23512
  { label: "fold the version bump into the release commit (app repos: root package manifest) \u2014 runs inside the apply step, no separate bump PR", gated: true },
@@ -23068,14 +23595,14 @@ function renderVerifyBroker(input) {
23068
23595
 
23069
23596
  // src/tenant-artifact.ts
23070
23597
  var import_node_crypto5 = require("node:crypto");
23071
- var import_node_fs22 = require("node:fs");
23598
+ var import_node_fs23 = require("node:fs");
23072
23599
  var import_promises3 = require("node:fs/promises");
23073
23600
  var import_node_path20 = require("node:path");
23074
23601
  var ARTIFACT_ID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
23075
23602
  var MAX_BYTES = 5 * 1024 * 1024 * 1024;
23076
23603
  async function sha256File(path2) {
23077
23604
  const hash = (0, import_node_crypto5.createHash)("sha256");
23078
- for await (const chunk of (0, import_node_fs22.createReadStream)(path2)) hash.update(chunk);
23605
+ for await (const chunk of (0, import_node_fs23.createReadStream)(path2)) hash.update(chunk);
23079
23606
  return hash.digest("hex");
23080
23607
  }
23081
23608
  async function putTenantArtifact(repo, stage, inputPath, deps) {
@@ -23084,8 +23611,8 @@ async function putTenantArtifact(repo, stage, inputPath, deps) {
23084
23611
  const info = await (0, import_promises3.stat)(path2);
23085
23612
  if (!info.isFile()) throw new Error("tenant artifact put: input path must be a file");
23086
23613
  if (!Number.isSafeInteger(info.size) || info.size < 1 || info.size > MAX_BYTES) throw new Error(`tenant artifact put: file must be 1..${MAX_BYTES} bytes`);
23087
- const sha2563 = await sha256File(path2);
23088
- const prepared = await tenantArtifactUpload({ repo, stage, size: info.size, sha256: sha2563 }, deps);
23614
+ const sha2564 = await sha256File(path2);
23615
+ const prepared = await tenantArtifactUpload({ repo, stage, size: info.size, sha256: sha2564 }, deps);
23089
23616
  if (!prepared.ok) {
23090
23617
  const detail = prepared.body?.error ?? prepared.error ?? `HTTP ${prepared.status}`;
23091
23618
  throw new Error(`tenant artifact put: ${detail}`);
@@ -23098,7 +23625,7 @@ async function putTenantArtifact(repo, stage, inputPath, deps) {
23098
23625
  return [key, value];
23099
23626
  }));
23100
23627
  headers["content-length"] = String(info.size);
23101
- const stream = (0, import_node_fs22.createReadStream)(path2);
23628
+ const stream = (0, import_node_fs23.createReadStream)(path2);
23102
23629
  let uploaded;
23103
23630
  try {
23104
23631
  uploaded = await fetch(body.uploadUrl, {
@@ -23113,8 +23640,8 @@ async function putTenantArtifact(repo, stage, inputPath, deps) {
23113
23640
  throw error;
23114
23641
  }
23115
23642
  if (!uploaded.ok) throw new Error(`tenant artifact put: object upload failed (HTTP ${uploaded.status})`);
23116
- 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");
23117
- return { artifactId: body.artifactId, repo, stage, size: info.size, sha256: sha2563, expiresAt: body.expiresAt };
23643
+ if (body.size !== info.size || body.sha256 !== sha2564 || typeof body.expiresAt !== "string" || !Number.isFinite(Date.parse(body.expiresAt))) throw new Error("tenant artifact put: Hub returned inconsistent artifact metadata");
23644
+ return { artifactId: body.artifactId, repo, stage, size: info.size, sha256: sha2564, expiresAt: body.expiresAt };
23118
23645
  }
23119
23646
 
23120
23647
  // src/hotfix-coverage.ts
@@ -23300,7 +23827,7 @@ function clean3(out) {
23300
23827
  return out.trim();
23301
23828
  }
23302
23829
  function sleeper(deps) {
23303
- return deps.sleep ?? ((ms) => new Promise((resolve5) => setTimeout(resolve5, ms)));
23830
+ return deps.sleep ?? ((ms) => new Promise((resolve6) => setTimeout(resolve6, ms)));
23304
23831
  }
23305
23832
  function normalizeHotfixVersion(input) {
23306
23833
  const m = /^v?(\d+\.\d+\.\d+)$/.exec(input.trim());
@@ -24295,7 +24822,7 @@ function renderDeployPortDoctor(report) {
24295
24822
  // src/repo-index.ts
24296
24823
  var import_node_crypto6 = require("node:crypto");
24297
24824
  var import_node_child_process12 = require("node:child_process");
24298
- var import_node_fs23 = require("node:fs");
24825
+ var import_node_fs24 = require("node:fs");
24299
24826
  var import_node_path21 = require("node:path");
24300
24827
 
24301
24828
  // ../infra/repo-index-path-policy.mjs
@@ -24496,10 +25023,10 @@ function loadReadmeHints(cwd, candidatePaths) {
24496
25023
  for (const rel of readmes) {
24497
25024
  if (isHardDeniedPath(rel)) continue;
24498
25025
  const abs = (0, import_node_path21.join)(cwd, ...rel.split("/"));
24499
- if (!(0, import_node_fs23.existsSync)(abs)) continue;
25026
+ if (!(0, import_node_fs24.existsSync)(abs)) continue;
24500
25027
  let text;
24501
25028
  try {
24502
- text = (0, import_node_fs23.readFileSync)(abs, "utf8");
25029
+ text = (0, import_node_fs24.readFileSync)(abs, "utf8");
24503
25030
  } catch {
24504
25031
  continue;
24505
25032
  }
@@ -24535,10 +25062,10 @@ function rebuildRepoIndex(cwd, repoSlug3) {
24535
25062
  if (ignored.has(rel)) continue;
24536
25063
  if (isHardDeniedPath(rel)) continue;
24537
25064
  const abs = (0, import_node_path21.join)(cwd, ...rel.split("/"));
24538
- if (!(0, import_node_fs23.existsSync)(abs)) continue;
25065
+ if (!(0, import_node_fs24.existsSync)(abs)) continue;
24539
25066
  let text;
24540
25067
  try {
24541
- text = (0, import_node_fs23.readFileSync)(abs, "utf8");
25068
+ text = (0, import_node_fs24.readFileSync)(abs, "utf8");
24542
25069
  } catch {
24543
25070
  continue;
24544
25071
  }
@@ -24563,16 +25090,16 @@ function rebuildRepoIndex(cwd, repoSlug3) {
24563
25090
  entries
24564
25091
  };
24565
25092
  const store = repoIndexStorePath(cwd);
24566
- (0, import_node_fs23.mkdirSync)((0, import_node_path21.dirname)(store), { recursive: true });
24567
- (0, import_node_fs23.writeFileSync)(store, `${JSON.stringify(projection, null, 2)}
25093
+ (0, import_node_fs24.mkdirSync)((0, import_node_path21.dirname)(store), { recursive: true });
25094
+ (0, import_node_fs24.writeFileSync)(store, `${JSON.stringify(projection, null, 2)}
24568
25095
  `, "utf8");
24569
25096
  return projection;
24570
25097
  }
24571
25098
  function loadRepoIndex(cwd) {
24572
25099
  const store = repoIndexStorePath(cwd);
24573
- if (!(0, import_node_fs23.existsSync)(store)) return null;
25100
+ if (!(0, import_node_fs24.existsSync)(store)) return null;
24574
25101
  try {
24575
- const raw = JSON.parse((0, import_node_fs23.readFileSync)(store, "utf8"));
25102
+ const raw = JSON.parse((0, import_node_fs24.readFileSync)(store, "utf8"));
24576
25103
  if (raw?.schema !== REPO_INDEX_SCHEMA || !Array.isArray(raw.entries)) return null;
24577
25104
  return raw;
24578
25105
  } catch {
@@ -24656,8 +25183,8 @@ init_compat();
24656
25183
  // src/repo-index-v4/builder.ts
24657
25184
  var import_node_crypto9 = require("node:crypto");
24658
25185
  var import_node_child_process14 = require("node:child_process");
24659
- var import_node_fs25 = require("node:fs");
24660
- var import_node_os12 = require("node:os");
25186
+ var import_node_fs26 = require("node:fs");
25187
+ var import_node_os13 = require("node:os");
24661
25188
  var import_node_path23 = require("node:path");
24662
25189
 
24663
25190
  // ../infra/repo-index-material-buckets.mjs
@@ -24757,7 +25284,7 @@ function buildRepoIndexMaterialLayout(repo, chunks, embeddings) {
24757
25284
 
24758
25285
  // src/repo-index-v4/chunks.ts
24759
25286
  var import_node_crypto8 = require("node:crypto");
24760
- var import_node_fs24 = require("node:fs");
25287
+ var import_node_fs25 = require("node:fs");
24761
25288
  var import_node_path22 = require("node:path");
24762
25289
 
24763
25290
  // src/repo-index-v4/language.ts
@@ -24923,10 +25450,10 @@ async function buildStructuralChunksForPaths(cwd, repo, commit, paths) {
24923
25450
  const chunks = [];
24924
25451
  for (const path2 of paths) {
24925
25452
  const absolute = (0, import_node_path22.join)(cwd, ...path2.split("/"));
24926
- if (!(0, import_node_fs24.existsSync)(absolute)) continue;
25453
+ if (!(0, import_node_fs25.existsSync)(absolute)) continue;
24927
25454
  let source;
24928
25455
  try {
24929
- source = (0, import_node_fs24.readFileSync)(absolute, "utf8");
25456
+ source = (0, import_node_fs25.readFileSync)(absolute, "utf8");
24930
25457
  } catch {
24931
25458
  continue;
24932
25459
  }
@@ -24988,7 +25515,8 @@ function planRepoIndexV4Delta(opts) {
24988
25515
  const baseCommit = opts.baseCommit ? opts.baseCommit.toLowerCase() : null;
24989
25516
  const full = (fallbackReason) => ({ mode: "full", headCommit, fallbackReason, ...baseCommit ? { baseCommit } : {} });
24990
25517
  if (opts.forceFull) return full("explicit-full-rebuild");
24991
- if (!baseCommit || !COMMIT.test(baseCommit) || baseCommit === headCommit) return full("no-active-authority");
25518
+ if (!baseCommit || !COMMIT.test(baseCommit)) return full("no-active-authority");
25519
+ if (baseCommit === headCommit) return { mode: "unchanged", baseCommit, headCommit };
24992
25520
  if (opts.basePipelineCompatible === false) return full("incompatible-base-provenance");
24993
25521
  if (opts.hasPriorMaterial === false) return full("no-prior-material");
24994
25522
  const { git: git3 } = opts;
@@ -25054,7 +25582,7 @@ function gitInfo(cwd) {
25054
25582
  }
25055
25583
  function prior(cwd) {
25056
25584
  try {
25057
- const p = JSON.parse((0, import_node_fs25.readFileSync)(statePath(cwd), "utf8"));
25585
+ const p = JSON.parse((0, import_node_fs26.readFileSync)(statePath(cwd), "utf8"));
25058
25586
  return p?.schemaVersion === 4 && p?.manifest?.immutable === true ? p : null;
25059
25587
  } catch {
25060
25588
  return null;
@@ -25073,7 +25601,7 @@ function embeddingInput(cwd, chunk) {
25073
25601
  ${chunk.symbol ?? ""}
25074
25602
  ${chunk.blurb ?? ""}`;
25075
25603
  try {
25076
- const lines = (0, import_node_fs25.readFileSync)((0, import_node_path23.join)(cwd, ...chunk.path.split("/")), "utf8").split(/\r?\n/);
25604
+ const lines = (0, import_node_fs26.readFileSync)((0, import_node_path23.join)(cwd, ...chunk.path.split("/")), "utf8").split(/\r?\n/);
25077
25605
  const body = lines.slice(Math.max(0, (c.startLine ?? 1) - 1), Math.min(lines.length, c.endLine ?? lines.length)).join("\n");
25078
25606
  return body.slice(0, 1e5);
25079
25607
  } catch {
@@ -25088,18 +25616,18 @@ function runEmbedderOnce(cwd, chunks, modelDirectory, createdAt) {
25088
25616
  if (!chunks.length) return { ok: true, embeddings: [] };
25089
25617
  const orchestratorRunner = (0, import_node_path23.join)(process.cwd(), "repo-indexer", "src", "batch.mjs");
25090
25618
  const targetRunner = (0, import_node_path23.join)(cwd, "repo-indexer", "src", "batch.mjs");
25091
- const file = (0, import_node_fs25.existsSync)(orchestratorRunner) ? orchestratorRunner : targetRunner;
25092
- if (!(0, import_node_fs25.existsSync)(file)) return { ok: false, reason: "embeddings-unavailable" };
25619
+ const file = (0, import_node_fs26.existsSync)(orchestratorRunner) ? orchestratorRunner : targetRunner;
25620
+ if (!(0, import_node_fs26.existsSync)(file)) return { ok: false, reason: "embeddings-unavailable" };
25093
25621
  const request = { texts: chunks.map((chunk) => ({ id: chunk.id, text: embeddingInput(cwd, chunk) })), maxBatch: V4_EMBED_BATCH };
25094
25622
  const env = { ...process.env, ...modelDirectory ? { MMI_REPO_INDEXER_MODEL_DIR: modelDirectory } : {} };
25095
- const requestDir = (0, import_node_fs25.mkdtempSync)((0, import_node_path23.join)((0, import_node_os12.tmpdir)(), "mmi-repo-index-req-"));
25623
+ const requestDir = (0, import_node_fs26.mkdtempSync)((0, import_node_path23.join)((0, import_node_os13.tmpdir)(), "mmi-repo-index-req-"));
25096
25624
  const requestFile = (0, import_node_path23.join)(requestDir, "request.json");
25097
- (0, import_node_fs25.writeFileSync)(requestFile, JSON.stringify(request));
25625
+ (0, import_node_fs26.writeFileSync)(requestFile, JSON.stringify(request));
25098
25626
  let result;
25099
25627
  try {
25100
25628
  result = (0, import_node_child_process14.spawnSync)(process.execPath, [file, requestFile], { encoding: "utf8", windowsHide: true, timeout: V4_EMBED_TIMEOUT_MS, maxBuffer: V4_MAX_ARTIFACT_BYTES, env });
25101
25629
  } finally {
25102
- (0, import_node_fs25.rmSync)(requestDir, { recursive: true, force: true });
25630
+ (0, import_node_fs26.rmSync)(requestDir, { recursive: true, force: true });
25103
25631
  }
25104
25632
  if (result.error || result.status !== 0) {
25105
25633
  const cleanExit2 = result.error === void 0 && result.signal === void 0 && typeof result.status === "number" && result.status !== 0;
@@ -25232,8 +25760,8 @@ async function buildRepoIndexV4Detailed(cwd, repo, opts = {}) {
25232
25760
  const encoded = canonicalJson(envelope);
25233
25761
  if (Buffer.byteLength(encoded) > V4_MAX_ARTIFACT_BYTES) throw new Error(`repo-index v4 artifact exceeds ${V4_MAX_ARTIFACT_BYTES} byte ceiling`);
25234
25762
  const path2 = statePath(cwd);
25235
- (0, import_node_fs25.mkdirSync)((0, import_node_path23.dirname)(path2), { recursive: true });
25236
- (0, import_node_fs25.writeFileSync)(path2, `${JSON.stringify(envelope, null, 2)}
25763
+ (0, import_node_fs26.mkdirSync)((0, import_node_path23.dirname)(path2), { recursive: true });
25764
+ (0, import_node_fs26.writeFileSync)(path2, `${JSON.stringify(envelope, null, 2)}
25237
25765
  `, "utf8");
25238
25766
  const metrics = {
25239
25767
  mode: delta ? "delta" : "full",
@@ -25323,6 +25851,7 @@ function repoIndexV4BucketDigests(repo, chunks, embeddings) {
25323
25851
 
25324
25852
  // src/repo-index-cloud-client.ts
25325
25853
  var RETRY_ATTEMPTS2 = 3;
25854
+ var REPO_INDEX_GC_TIMEOUT_MS = 12e4;
25326
25855
  async function repoIndexSourceHostHeaders() {
25327
25856
  const { detectSurface: detectSurface2 } = await Promise.resolve().then(() => (init_plugin_guard_io(), plugin_guard_io_exports));
25328
25857
  return { [SOURCE_HOST_HEADER]: detectSurface2(process.env) };
@@ -25728,10 +26257,22 @@ async function statusRepoIndexCloud(repo, deps) {
25728
26257
  return { ok: false, error: e.message, code: "network" };
25729
26258
  }
25730
26259
  }
26260
+ function normalizeGcV4Repos(raw) {
26261
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) return {};
26262
+ const out = {};
26263
+ for (const [repo, value] of Object.entries(raw)) {
26264
+ if (!value || typeof value !== "object" || Array.isArray(value)) continue;
26265
+ const removed = Array.isArray(value.removed) ? value.removed.filter((k) => typeof k === "string") : [];
26266
+ const kept = Number(value.kept);
26267
+ out[repo] = { removed, kept: Number.isFinite(kept) ? kept : 0 };
26268
+ }
26269
+ return out;
26270
+ }
25731
26271
  async function gcRepoIndexCloud(deps) {
25732
26272
  if (!deps.baseUrl) return { ok: false, error: "Hub API URL not configured" };
25733
26273
  const token = await deps.token();
25734
26274
  if (!token) return { ok: false, error: "no Hub session token (run `gh auth login`)" };
26275
+ const timeoutMs = deps.timeoutMs ?? REPO_INDEX_GC_TIMEOUT_MS;
25735
26276
  try {
25736
26277
  const res = await fetchWithRetry(
25737
26278
  deps.fetch ?? fetch,
@@ -25741,19 +26282,38 @@ async function gcRepoIndexCloud(deps) {
25741
26282
  headers: { ...clientVersionHeaders(), Authorization: `Bearer ${token}`, "content-type": "application/json" },
25742
26283
  body: "{}"
25743
26284
  },
25744
- { attempts: RETRY_ATTEMPTS2, timeoutMs: deps.timeoutMs ?? REGISTRY_FETCH_TIMEOUT_MS, sleep: deps.retrySleep }
26285
+ // Single attempt: GC tombstones authorities and deletes S3 objects. A timed-out POST may still
26286
+ // finish on Hub; retrying would re-issue a destructive sweep without an authoritative receipt.
26287
+ { attempts: 1, timeoutMs, sleep: deps.retrySleep }
25745
26288
  );
25746
26289
  const body = await res.json().catch(() => ({}));
25747
26290
  if (!res.ok) return { ok: false, error: body.error ?? `gc HTTP ${res.status}` };
25748
- return { ok: true, removed: body.removed ?? [] };
26291
+ const removed = Array.isArray(body.removed) ? body.removed.filter((repo) => typeof repo === "string") : [];
26292
+ const kept = Number(body.kept);
26293
+ return {
26294
+ ok: true,
26295
+ removed,
26296
+ kept: Number.isFinite(kept) ? kept : 0,
26297
+ v4: {
26298
+ dryRun: body.v4?.dryRun === true,
26299
+ repos: normalizeGcV4Repos(body.v4?.repos)
26300
+ }
26301
+ };
25749
26302
  } catch (e) {
25750
- return { ok: false, error: e.message };
26303
+ const msg = e.message || String(e);
26304
+ if (/abort|timeout/i.test(msg)) {
26305
+ return {
26306
+ ok: false,
26307
+ error: `repo-index gc timed out after ${timeoutMs}ms \u2014 Hub may still have completed the destructive sweep; run \`mmi-cli oracle repo-index status --cloud --json\` and verify before retrying`
26308
+ };
26309
+ }
26310
+ return { ok: false, error: msg };
25751
26311
  }
25752
26312
  }
25753
26313
 
25754
26314
  // src/repo-index-sync.ts
25755
- var import_node_fs27 = require("node:fs");
25756
- var import_node_os13 = require("node:os");
26315
+ var import_node_fs28 = require("node:fs");
26316
+ var import_node_os14 = require("node:os");
25757
26317
  var import_node_path25 = require("node:path");
25758
26318
  var import_node_child_process15 = require("node:child_process");
25759
26319
 
@@ -25793,7 +26353,7 @@ function repoIndexRoster(projects) {
25793
26353
  }
25794
26354
 
25795
26355
  // src/repo-index-v4/edges.ts
25796
- var import_node_fs26 = require("node:fs");
26356
+ var import_node_fs27 = require("node:fs");
25797
26357
  var import_node_path24 = require("node:path");
25798
26358
  var V4_GRAPH_MAX_EDGES = 5e3;
25799
26359
  var V4_GRAPH_MAX_EDGES_PER_FILE = 64;
@@ -25883,9 +26443,9 @@ function buildGraphEdges(cwd, repo, commit, rosterRepos2) {
25883
26443
  for (const path2 of paths) {
25884
26444
  if (ignored.has(path2) || isHardDeniedPath(path2) || !SUPPORTED.has(extension(path2))) continue;
25885
26445
  const absolute = (0, import_node_path24.join)(cwd, ...path2.split("/"));
25886
- if (!(0, import_node_fs26.existsSync)(absolute)) continue;
26446
+ if (!(0, import_node_fs27.existsSync)(absolute)) continue;
25887
26447
  try {
25888
- edges.push(...graphEdgesForSource(repo, commit, path2, (0, import_node_fs26.readFileSync)(absolute, "utf8"), rosterRepos2));
26448
+ edges.push(...graphEdgesForSource(repo, commit, path2, (0, import_node_fs27.readFileSync)(absolute, "utf8"), rosterRepos2));
25889
26449
  } catch {
25890
26450
  }
25891
26451
  if (edges.length >= V4_GRAPH_MAX_EDGES) break;
@@ -25894,6 +26454,16 @@ function buildGraphEdges(cwd, repo, commit, rosterRepos2) {
25894
26454
  }
25895
26455
 
25896
26456
  // src/repo-index-sync.ts
26457
+ function execFileUtf8(file, args) {
26458
+ return new Promise((resolve6, reject) => {
26459
+ (0, import_node_child_process15.execFile)(file, args, { encoding: "utf8", windowsHide: true }, (error, stdout) => {
26460
+ if (error) reject(error);
26461
+ else resolve6(String(stdout ?? ""));
26462
+ });
26463
+ });
26464
+ }
26465
+ var ESTATE_CLASSIFY_CONCURRENCY = 8;
26466
+ var ESTATE_HEALTHY_CLASSIFY_BUDGET_MS = 5 * 601e3;
25897
26467
  var COMMIT3 = /^[a-f0-9]{40}$/;
25898
26468
  var SHA256 = /^[a-f0-9]{64}$/;
25899
26469
  var UTC_MILLIS = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/;
@@ -25929,17 +26499,28 @@ function checkoutExactCommit(repo, dest, token, commit) {
25929
26499
  const head = git3(["rev-parse", "HEAD"]).trim().toLowerCase();
25930
26500
  if (head !== commit) throw new Error(`checkout of ${repo} resolved ${head}, not the requested commit ${commit}`);
25931
26501
  }
25932
- function remoteHead(repo, token) {
26502
+ async function remoteHead(repo, token) {
25933
26503
  const basic = Buffer.from(`x-access-token:${token}`, "utf8").toString("base64");
25934
- const output = (0, import_node_child_process15.execFileSync)(
26504
+ const stdout = await execFileUtf8(
25935
26505
  "git",
25936
- ["-c", `http.extraHeader=Authorization: Basic ${basic}`, "ls-remote", "--exit-code", `https://github.com/${repo}.git`, "HEAD"],
25937
- { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"], windowsHide: true }
26506
+ ["-c", `http.extraHeader=Authorization: Basic ${basic}`, "ls-remote", "--exit-code", `https://github.com/${repo}.git`, "HEAD"]
25938
26507
  );
25939
- const match = String(output).match(/^([a-f0-9]{40})\s+HEAD$/m);
26508
+ const match = stdout.match(/^([a-f0-9]{40})\s+HEAD$/m);
25940
26509
  if (!match) throw new Error(`could not resolve remote HEAD for ${repo}`);
25941
26510
  return match[1];
25942
26511
  }
26512
+ async function mapLimit(items, limit, fn) {
26513
+ const out = new Array(items.length);
26514
+ let next = 0;
26515
+ const workers = Array.from({ length: Math.min(limit, items.length) }, async () => {
26516
+ while (next < items.length) {
26517
+ const i = next++;
26518
+ out[i] = await fn(items[i], i);
26519
+ }
26520
+ });
26521
+ await Promise.all(workers);
26522
+ return out;
26523
+ }
25943
26524
  function verifiedReadyBase(statusValue, repo) {
25944
26525
  if (!statusValue || typeof statusValue !== "object" || Array.isArray(statusValue)) return null;
25945
26526
  const status = statusValue;
@@ -26041,11 +26622,18 @@ async function syncEstateRepoIndex(opts) {
26041
26622
  const busy = new Set(
26042
26623
  (opts.skipRepos ?? []).map((repo) => normalizeRepoIndexRepo(repo)).filter((repo) => repo !== null)
26043
26624
  );
26044
- for (const repo of repos) {
26625
+ const emit = (progress) => {
26626
+ try {
26627
+ opts.onClassify?.(progress);
26628
+ } catch {
26629
+ }
26630
+ };
26631
+ const classified = await mapLimit(repos, ESTATE_CLASSIFY_CONCURRENCY, async (repo) => {
26045
26632
  if (busy.has(repo)) {
26046
- drift.push({ repo, reason: "busy-elsewhere", action: "skip" });
26047
- skipped.push(`${repo}: a per-repo reconcile run is already publishing it`);
26048
- continue;
26633
+ const row2 = { repo, reason: "busy-elsewhere", action: "skip" };
26634
+ const warning = `${repo}: a per-repo reconcile run is already publishing it`;
26635
+ emit({ row: row2, warning });
26636
+ return { repo, row: row2, warning, base: null, expectedActiveDigest: void 0 };
26049
26637
  }
26050
26638
  let base = null;
26051
26639
  let expectedActiveDigest;
@@ -26066,43 +26654,72 @@ async function syncEstateRepoIndex(opts) {
26066
26654
  }
26067
26655
  let targetCommit = requestedCommit || void 0;
26068
26656
  if (base && !forceFull) {
26657
+ let headUnreadable = false;
26069
26658
  if (!targetCommit) {
26070
26659
  try {
26071
- targetCommit = remoteHead(repo, opts.githubToken);
26660
+ targetCommit = await remoteHead(repo, opts.githubToken);
26072
26661
  } catch {
26073
- targetCommit = void 0;
26662
+ headUnreadable = true;
26074
26663
  }
26075
26664
  }
26076
26665
  const provenance = await fetchRepoIndexV4ProvenanceCloud(repo, opts.deps).catch(
26077
26666
  (error) => ({ ok: false, error: error.message })
26078
26667
  );
26079
26668
  if (provenance.ok && !provenance.deltaCompatible) {
26080
- drift.push({ repo, reason: "incompatible-provenance", action: "needs-full-rebuild", activeCommit: base.commit, ...targetCommit ? { targetCommit } : {} });
26081
- needsFullRebuild.push(repo);
26082
- skipped.push(`${repo}: DRIFT incompatible index provenance \u2014 the active authority at ${base.commit} was not built by ${CURRENT_REPO_INDEX_PROVENANCE_TOKEN}; migrate it explicitly with \`oracle repo-index sync-estate --repo ${repo} --full-rebuild ${CURRENT_REPO_INDEX_PROVENANCE_TOKEN}\``);
26083
- continue;
26669
+ const row2 = {
26670
+ repo,
26671
+ reason: "incompatible-provenance",
26672
+ action: "needs-full-rebuild",
26673
+ activeCommit: base.commit,
26674
+ ...targetCommit ? { targetCommit } : {}
26675
+ };
26676
+ const warning = `${repo}: DRIFT incompatible index provenance \u2014 the active authority at ${base.commit} was not built by ${CURRENT_REPO_INDEX_PROVENANCE_TOKEN}; migrate it explicitly with \`oracle repo-index sync-estate --repo ${repo} --full-rebuild ${CURRENT_REPO_INDEX_PROVENANCE_TOKEN}\``;
26677
+ emit({ row: row2, warning });
26678
+ return { repo, row: row2, warning, base, expectedActiveDigest };
26084
26679
  }
26085
26680
  if (targetCommit && targetCommit === base.commit) {
26086
- drift.push({ repo, reason: "healthy", action: "skip", activeCommit: base.commit, targetCommit });
26087
- skipped.push(`${repo}: unchanged verified-ready authority at ${targetCommit}`);
26088
- continue;
26681
+ const row2 = { repo, reason: "healthy", action: "skip", activeCommit: base.commit, targetCommit };
26682
+ const warning = `${repo}: unchanged verified-ready authority at ${targetCommit}`;
26683
+ emit({ row: row2, warning });
26684
+ return { repo, row: row2, warning, base, expectedActiveDigest };
26685
+ }
26686
+ if (headUnreadable) {
26687
+ const row2 = { repo, reason: "head-unreadable", action: "skip", activeCommit: base.commit };
26688
+ const warning = `${repo}: remote HEAD unreadable; leaving verified-ready authority at ${base.commit}`;
26689
+ emit({ row: row2, warning });
26690
+ return { repo, row: row2, warning, base, expectedActiveDigest };
26089
26691
  }
26090
26692
  }
26091
- drift.push({
26693
+ const row = {
26092
26694
  repo,
26093
26695
  reason,
26094
26696
  action: "build",
26095
26697
  ...base ? { activeCommit: base.commit } : {},
26096
26698
  ...targetCommit ? { targetCommit } : {}
26097
- });
26098
- if (opts.plan) continue;
26099
- const dir = (0, import_node_fs27.mkdtempSync)((0, import_node_path25.join)((0, import_node_os13.tmpdir)(), "mmi-repo-index-"));
26699
+ };
26700
+ emit({ row });
26701
+ return { repo, row, base, expectedActiveDigest };
26702
+ });
26703
+ for (const entry of classified) {
26704
+ drift.push(entry.row);
26705
+ if (entry.warning) skipped.push(entry.warning);
26706
+ if (entry.row.action === "needs-full-rebuild") needsFullRebuild.push(entry.repo);
26707
+ }
26708
+ if (opts.plan) return answer();
26709
+ for (const entry of classified) {
26710
+ if (entry.row.action !== "build") continue;
26711
+ const { repo, base, expectedActiveDigest } = entry;
26712
+ const dir = (0, import_node_fs28.mkdtempSync)((0, import_node_path25.join)((0, import_node_os14.tmpdir)(), "mmi-repo-index-"));
26100
26713
  try {
26101
26714
  shallowClone(repo, dir, opts.githubToken);
26102
26715
  if (requestedCommit) checkoutExactCommit(repo, dir, opts.githubToken, requestedCommit);
26103
26716
  let v4;
26104
26717
  try {
26105
26718
  const plan = await planDeltaBuild(repo, dir, base, opts.deps, opts.githubToken, forceFull);
26719
+ if (plan.plan.mode === "unchanged") {
26720
+ skipped.push(`${repo}: unchanged verified-ready authority at ${plan.plan.headCommit}`);
26721
+ continue;
26722
+ }
26106
26723
  if (base && plan.plan.mode === "full") skipped.push(`${repo}: full rebuild (${plan.plan.fallbackReason})`);
26107
26724
  v4 = await buildRepoIndexV4Detailed(dir, repo, {
26108
26725
  modelDirectory: process.env.MMI_REPO_INDEXER_MODEL_DIR,
@@ -26146,7 +26763,7 @@ async function syncEstateRepoIndex(opts) {
26146
26763
  failed.push({ repo, error: e.message });
26147
26764
  } finally {
26148
26765
  try {
26149
- (0, import_node_fs27.rmSync)(dir, { recursive: true, force: true });
26766
+ (0, import_node_fs28.rmSync)(dir, { recursive: true, force: true });
26150
26767
  } catch {
26151
26768
  }
26152
26769
  }
@@ -26155,7 +26772,7 @@ async function syncEstateRepoIndex(opts) {
26155
26772
  }
26156
26773
 
26157
26774
  // src/repo-index-health.ts
26158
- var import_node_fs28 = require("node:fs");
26775
+ var import_node_fs29 = require("node:fs");
26159
26776
 
26160
26777
  // testdata/repo-index-golden-queries.json
26161
26778
  var repo_index_golden_queries_default = {
@@ -26259,7 +26876,7 @@ function assertGoldenSuite(raw, source) {
26259
26876
  function loadGoldenSuite(path2) {
26260
26877
  let text;
26261
26878
  try {
26262
- text = (0, import_node_fs28.readFileSync)(path2, "utf8");
26879
+ text = (0, import_node_fs29.readFileSync)(path2, "utf8");
26263
26880
  } catch (e) {
26264
26881
  throw new Error(`golden suite unreadable at ${path2}: ${e.message}`);
26265
26882
  }
@@ -26429,7 +27046,7 @@ async function runRepoIndexHealth(opts) {
26429
27046
 
26430
27047
  // src/spawn-policy-core.ts
26431
27048
  var import_node_child_process16 = require("node:child_process");
26432
- var import_node_fs29 = require("node:fs");
27049
+ var import_node_fs30 = require("node:fs");
26433
27050
  var import_node_path26 = require("node:path");
26434
27051
  var SPAWNERS = ["spawn", "spawnSync", "exec", "execSync", "execFile", "execFileSync"];
26435
27052
  var CALL_SOURCE = String.raw`(^|[^.\w$])(${SPAWNERS.join("|")})\s*\(`;
@@ -26516,7 +27133,7 @@ function runSpawnPolicy(root) {
26516
27133
  for (const file of files) {
26517
27134
  let raw;
26518
27135
  try {
26519
- raw = (0, import_node_fs29.readFileSync)((0, import_node_path26.join)(root, file), "utf8");
27136
+ raw = (0, import_node_fs30.readFileSync)((0, import_node_path26.join)(root, file), "utf8");
26520
27137
  } catch {
26521
27138
  continue;
26522
27139
  }
@@ -26534,8 +27151,81 @@ function runSpawnPolicy(root) {
26534
27151
 
26535
27152
  // src/test-policy-core.ts
26536
27153
  var import_node_child_process17 = require("node:child_process");
26537
- var import_node_fs30 = require("node:fs");
27154
+ var import_node_fs31 = require("node:fs");
26538
27155
  var import_node_path27 = require("node:path");
27156
+
27157
+ // ../scripts/test-command-policy-core.mjs
27158
+ var TEST_COMMAND_CLASS = "test";
27159
+ function translateGlob(glob) {
27160
+ let out = "";
27161
+ for (let i = 0; i < glob.length; i += 1) {
27162
+ const char = glob[i];
27163
+ if (char === "*") {
27164
+ if (glob[i + 1] === "*") {
27165
+ if (glob[i + 2] === "/") {
27166
+ out += "(?:.*/)?";
27167
+ i += 2;
27168
+ } else {
27169
+ out += ".*";
27170
+ i += 1;
27171
+ }
27172
+ } else out += "[^/]*";
27173
+ } else if (char === "{") {
27174
+ const close = glob.indexOf("}", i);
27175
+ if (close === -1) out += "\\{";
27176
+ else {
27177
+ out += `(?:${glob.slice(i + 1, close).split(",").map(translateGlob).join("|")})`;
27178
+ i = close;
27179
+ }
27180
+ } else {
27181
+ out += /[.+?^${}()|[\]\\]/.test(char) ? `\\${char}` : char;
27182
+ }
27183
+ }
27184
+ return out;
27185
+ }
27186
+ function globToRegExp(glob) {
27187
+ return new RegExp(`^${translateGlob(glob)}$`);
27188
+ }
27189
+ function mandatoryGlobList(mandatory) {
27190
+ if (!Array.isArray(mandatory)) return [];
27191
+ return mandatory.map((entry) => typeof entry === "string" ? entry : entry?.glob).filter((glob) => typeof glob === "string");
27192
+ }
27193
+ function matchedMandatoryGlobs(paths, mandatory) {
27194
+ const globs = mandatoryGlobList(mandatory);
27195
+ const list = Array.isArray(paths) ? paths : [];
27196
+ return globs.filter((glob) => {
27197
+ const re = globToRegExp(glob);
27198
+ return list.some((path2) => re.test(path2));
27199
+ });
27200
+ }
27201
+ function evaluateTestCommandPolicy({ paths, mandatory, regulated = true } = {}) {
27202
+ const configuredMandatoryCount = mandatoryGlobList(mandatory).length;
27203
+ if (!regulated) {
27204
+ return {
27205
+ configuredMandatoryCount: 0,
27206
+ matchedMandatoryGlobs: [],
27207
+ matchedMandatoryCount: 0,
27208
+ testCommandsAllowed: true,
27209
+ reasonId: null,
27210
+ commandClasses: { allowed: [TEST_COMMAND_CLASS], refused: [] }
27211
+ };
27212
+ }
27213
+ const matched = matchedMandatoryGlobs(paths, mandatory);
27214
+ const testCommandsAllowed = matched.length > 0;
27215
+ return {
27216
+ configuredMandatoryCount,
27217
+ matchedMandatoryGlobs: matched,
27218
+ matchedMandatoryCount: matched.length,
27219
+ testCommandsAllowed,
27220
+ reasonId: testCommandsAllowed ? null : "test-command-outside-mandatory-zone",
27221
+ commandClasses: {
27222
+ allowed: testCommandsAllowed ? [TEST_COMMAND_CLASS] : [],
27223
+ refused: testCommandsAllowed ? [] : [TEST_COMMAND_CLASS]
27224
+ }
27225
+ };
27226
+ }
27227
+
27228
+ // src/test-policy-core.ts
26539
27229
  var POLICY_FILE = "test-policy.json";
26540
27230
  var TEST_RE = /\.(?:test|spec)\.[cm]?[jt]sx?$/;
26541
27231
  var PY_TEST_RE = /(?:^|\/)test_[^/]*\.py$|_test\.py$/;
@@ -26581,7 +27271,7 @@ function translate(glob) {
26581
27271
  }
26582
27272
  return out;
26583
27273
  }
26584
- function globToRegExp(glob) {
27274
+ function globToRegExp2(glob) {
26585
27275
  return new RegExp(`^${translate(glob)}$`);
26586
27276
  }
26587
27277
  function isTestPath(path2) {
@@ -26815,7 +27505,7 @@ function isMeaningfulChange(path2, before, after) {
26815
27505
  return !a.every((t, k) => t.text === b[k].text && t.nl === b[k].nl);
26816
27506
  }
26817
27507
  function annotateChangeMeaning(changed, policy, read) {
26818
- const matchers = (policy.mandatory ?? []).map((m) => globToRegExp(m.glob));
27508
+ const matchers = (policy.mandatory ?? []).map((m) => globToRegExp2(m.glob));
26819
27509
  return changed.map((file) => {
26820
27510
  if (file.status !== "M" || !SUPPORTED_SOURCE.test(file.path)) return file;
26821
27511
  if (!matchers.some((re) => re.test(file.path)) && !isTestPath(file.path)) return file;
@@ -26842,7 +27532,7 @@ function loadPolicy(root, readFile7 = readFileOrNull2) {
26842
27532
  }
26843
27533
  function readFileOrNull2(path2) {
26844
27534
  try {
26845
- return (0, import_node_fs30.readFileSync)(path2, "utf8");
27535
+ return (0, import_node_fs31.readFileSync)(path2, "utf8");
26846
27536
  } catch {
26847
27537
  return null;
26848
27538
  }
@@ -26853,7 +27543,7 @@ function removedPaths2(changed) {
26853
27543
  );
26854
27544
  }
26855
27545
  function classify(changed, policy, present = () => false) {
26856
- const matchers = (policy.mandatory ?? []).map((m) => ({ ...m, re: globToRegExp(m.glob) }));
27546
+ const matchers = (policy.mandatory ?? []).map((m) => ({ ...m, re: globToRegExp2(m.glob) }));
26857
27547
  const mandatoryHits = changed.filter((f) => matchers.some((m) => m.re.test(f.path)));
26858
27548
  const testChanges = changed.filter((f) => isTestPath(f.path));
26859
27549
  const addedTests = testChanges.filter((f) => f.status === "A");
@@ -26870,10 +27560,10 @@ function classify(changed, policy, present = () => false) {
26870
27560
  const removedProtected = [...removed].filter((p) => protectedBy.has(p)).map((p) => ({ path: p, why: protectedBy.get(p) ?? "" }));
26871
27561
  return { mandatoryHits, untestedHits, testChanges, meaningfulTestChanges, addedTests, removedProtected };
26872
27562
  }
26873
- function unresolvedProtectedEntries(policy, root, exists = (path2) => (0, import_node_fs30.existsSync)(path2)) {
27563
+ function unresolvedProtectedEntries(policy, root, exists = (path2) => (0, import_node_fs31.existsSync)(path2)) {
26874
27564
  return (policy.protected ?? []).map((p) => p.path).filter((p) => !exists((0, import_node_path27.join)(root, p)));
26875
27565
  }
26876
- function unresolvedSatisfiers(policy, root, exists = (path2) => (0, import_node_fs30.existsSync)(path2)) {
27566
+ function unresolvedSatisfiers(policy, root, exists = (path2) => (0, import_node_fs31.existsSync)(path2)) {
26877
27567
  const declared = (policy.mandatory ?? []).flatMap((m) => m.satisfiedBy ?? []);
26878
27568
  return [...new Set(declared)].filter((p) => !exists((0, import_node_path27.join)(root, p)));
26879
27569
  }
@@ -27064,7 +27754,7 @@ function blobAt(base, path2, cwd) {
27064
27754
  }
27065
27755
  function runTestPolicy(root, deps = {}) {
27066
27756
  const policy = deps.policy ?? loadPolicy(root);
27067
- const exists = deps.exists ?? ((path2) => (0, import_node_fs30.existsSync)(path2));
27757
+ const exists = deps.exists ?? ((path2) => (0, import_node_fs31.existsSync)(path2));
27068
27758
  const counts = { mandatoryCount: (policy.mandatory ?? []).length, protectedCount: (policy.protected ?? []).length };
27069
27759
  const base = deps.changed ? "(injected)" : resolveBase(root, deps.base);
27070
27760
  const refusal = deps.changed ? null : untrustworthyRange(root, base);
@@ -27103,7 +27793,23 @@ function runTestPolicy(root, deps = {}) {
27103
27793
  };
27104
27794
  const blocking = sift([...refusal ? [refusal] : [], ...lookup.refusals, ...staleFindings]);
27105
27795
  const findings = blocking.length > 0 ? blocking : sift(evaluate(changed, policy, present));
27106
- const result = { ok: findings.length === 0, findings, changedCount: changed.length, base, ...counts };
27796
+ const commandPolicy = evaluateTestCommandPolicy({
27797
+ paths: changed.map((f) => f.path),
27798
+ mandatory: policy.mandatory,
27799
+ regulated: policy.declared !== false
27800
+ });
27801
+ const result = {
27802
+ ok: findings.length === 0,
27803
+ findings,
27804
+ changedCount: changed.length,
27805
+ base,
27806
+ ...counts,
27807
+ matchedMandatoryGlobs: commandPolicy.matchedMandatoryGlobs,
27808
+ matchedMandatoryCount: commandPolicy.matchedMandatoryCount,
27809
+ testCommandsAllowed: commandPolicy.testCommandsAllowed,
27810
+ testCommandReasonId: commandPolicy.reasonId,
27811
+ commandClasses: commandPolicy.commandClasses
27812
+ };
27107
27813
  if (override) {
27108
27814
  result.overriddenBy = override;
27109
27815
  result.waived = waived;
@@ -27111,9 +27817,197 @@ function runTestPolicy(root, deps = {}) {
27111
27817
  return result;
27112
27818
  }
27113
27819
 
27114
- // src/project-info-sync.ts
27115
- var import_node_fs31 = require("node:fs");
27820
+ // src/dist-drift.ts
27821
+ var import_node_child_process18 = require("node:child_process");
27822
+ var import_node_crypto12 = require("node:crypto");
27823
+ var import_node_fs33 = require("node:fs");
27824
+ var import_node_os15 = require("node:os");
27825
+ var import_node_path29 = require("node:path");
27826
+
27827
+ // ../scripts/distribution-digest.mjs
27828
+ var import_node_crypto11 = require("node:crypto");
27829
+ var import_node_fs32 = require("node:fs");
27116
27830
  var import_node_path28 = require("node:path");
27831
+ var slash = (value) => value.replaceAll("\\", "/");
27832
+ function repoPath(root, declaredPath, label) {
27833
+ const absoluteRoot = (0, import_node_path28.resolve)(root);
27834
+ const target = (0, import_node_path28.resolve)(root, declaredPath);
27835
+ if (target !== absoluteRoot && !target.startsWith(`${absoluteRoot}${import_node_path28.sep}`)) {
27836
+ throw new Error(`${label} ${declaredPath} escapes the repository root`);
27837
+ }
27838
+ return target;
27839
+ }
27840
+ function digestFiles(files) {
27841
+ const hash = (0, import_node_crypto11.createHash)("sha256");
27842
+ for (const file of [...files].sort((a, b) => a.relative.localeCompare(b.relative))) {
27843
+ const content = file.stat.isSymbolicLink() ? Buffer.from((0, import_node_fs32.readlinkSync)(file.absolute), "utf8") : (0, import_node_fs32.readFileSync)(file.absolute);
27844
+ hash.update(file.relative, "utf8");
27845
+ hash.update("\0");
27846
+ hash.update(file.stat.isSymbolicLink() ? "symlink" : "file", "utf8");
27847
+ hash.update("\0");
27848
+ hash.update(String(content.length), "utf8");
27849
+ hash.update("\0");
27850
+ hash.update(content);
27851
+ hash.update("\0");
27852
+ }
27853
+ return `sha256:${hash.digest("hex")}`;
27854
+ }
27855
+ function digestPackedFiles(packageRoot, packedFiles) {
27856
+ return digestFiles(packedFiles.map((path2) => {
27857
+ const absolute = repoPath(packageRoot, path2, "packed artifact identity path");
27858
+ if (!(0, import_node_fs32.existsSync)(absolute)) throw new Error(`packed artifact identity path ${path2} does not exist`);
27859
+ return { absolute, relative: slash(path2), stat: (0, import_node_fs32.lstatSync)(absolute) };
27860
+ }));
27861
+ }
27862
+
27863
+ // src/dist-drift.ts
27864
+ var DIST_ARTIFACTS = [
27865
+ { path: "cli/dist/index.cjs", packageDir: "cli", output: "index.cjs" },
27866
+ { path: "cli/dist/main.cjs", packageDir: "cli", output: "main.cjs" },
27867
+ { path: "cli/dist/repo-index-v4.cjs", packageDir: "cli", output: "repo-index-v4.cjs" },
27868
+ { path: "updater/dist/index.cjs", packageDir: "updater", output: "index.cjs" }
27869
+ ];
27870
+ var BOM_DIST_TREE_ID = "mmi-cli-dist";
27871
+ var ABSENT = "absent";
27872
+ var sha2563 = (bytes) => `sha256:${(0, import_node_crypto12.createHash)("sha256").update(bytes).digest("hex")}`;
27873
+ function artifactDrift(path2, committedBytes, rebuiltBytes) {
27874
+ const committed = committedBytes ? sha2563(committedBytes) : ABSENT;
27875
+ const rebuiltExpected = rebuiltBytes ? sha2563(rebuiltBytes) : ABSENT;
27876
+ return { path: path2, status: committed === rebuiltExpected ? "current" : "stale", committed, rebuiltExpected };
27877
+ }
27878
+ function distTreeIdentity(labels, rebuilt, committed, digest) {
27879
+ const entries = [];
27880
+ for (const label of labels) {
27881
+ const bytes = rebuilt(label) ?? committed(label);
27882
+ if (!bytes) {
27883
+ return { rebuiltExpected: ABSENT, note: `recorded tree file ${label} is absent from the checkout` };
27884
+ }
27885
+ entries.push({ path: label, bytes });
27886
+ }
27887
+ return { rebuiltExpected: digest(entries) };
27888
+ }
27889
+ function npmPackIdentity(id, packageDir, identity, rebuilt, tree, digest) {
27890
+ const recorded = identity?.value ?? ABSENT;
27891
+ const kind = identity?.kind ?? "npm-pack";
27892
+ if (!identity || !Array.isArray(identity.files) || identity.files.length === 0) {
27893
+ return { id, kind, status: "stale", recorded, rebuiltExpected: ABSENT, note: "identity records no packed file list to recompute against" };
27894
+ }
27895
+ const entries = [];
27896
+ for (const label of identity.files) {
27897
+ const repoPath2 = `${packageDir}/${label}`;
27898
+ const bytes = label.startsWith("dist/") ? rebuilt(repoPath2) : tree(repoPath2);
27899
+ if (!bytes) {
27900
+ return { id, kind, status: "stale", recorded, rebuiltExpected: ABSENT, note: `packed file ${repoPath2} is absent from the checkout` };
27901
+ }
27902
+ entries.push({ path: label, bytes });
27903
+ }
27904
+ const rebuiltExpected = digest(entries);
27905
+ return { id, kind, status: recorded === rebuiltExpected ? "current" : "stale", recorded, rebuiltExpected };
27906
+ }
27907
+ function computeDistDriftReceipt(sources) {
27908
+ const artifacts = DIST_ARTIFACTS.map((spec) => artifactDrift(spec.path, sources.committed(spec.path), sources.rebuilt(spec.path)));
27909
+ const bomArtifact = (id) => sources.bom.artifacts?.find((entry) => entry.id === id);
27910
+ const cliDistDeclared = bomArtifact(BOM_DIST_TREE_ID)?.identity;
27911
+ const distTree = distTreeIdentity(sources.distTree(), sources.rebuilt, sources.committed, sources.digest);
27912
+ const distTreeRecorded = cliDistDeclared?.value ?? ABSENT;
27913
+ const identities = [
27914
+ { id: BOM_DIST_TREE_ID, kind: cliDistDeclared?.kind ?? "sha256-tree", status: distTreeRecorded === distTree.rebuiltExpected ? "current" : "stale", recorded: distTreeRecorded, rebuiltExpected: distTree.rebuiltExpected, ...distTree.note ? { note: distTree.note } : {} },
27915
+ npmPackIdentity("mmi-cli", "cli", bomArtifact("mmi-cli")?.identity, sources.rebuilt, sources.tree, sources.digest),
27916
+ npmPackIdentity("mmi-hub", "updater", bomArtifact("mmi-hub")?.identity, sources.rebuilt, sources.tree, sources.digest)
27917
+ ];
27918
+ const representative = identities[0];
27919
+ const bomStatus = identities.some((identity) => identity.status === "stale") ? "stale" : "current";
27920
+ const staleCount = artifacts.filter((a) => a.status === "stale").length + (bomStatus === "stale" ? 1 : 0);
27921
+ const summary = staleCount > 0 ? `dist/BOM: ${staleCount} artifact(s) stale \u2014 refresh with \`node scripts/release-distribution.mjs prepare\`` : "dist/BOM: current \u2014 committed dist and distribution-bom.json match a fresh rebuild of source";
27922
+ return {
27923
+ artifacts,
27924
+ bom: { status: bomStatus, recorded: representative.recorded, rebuiltExpected: representative.rebuiltExpected, identities },
27925
+ staleCount,
27926
+ summary
27927
+ };
27928
+ }
27929
+ function shortHash(value) {
27930
+ return value.startsWith("sha256:") ? `sha256:${value.slice(7, 23)}` : value;
27931
+ }
27932
+ function renderDistDriftReceipt(receipt) {
27933
+ const lines = [];
27934
+ for (const artifact of receipt.artifacts) {
27935
+ lines.push(`${artifact.path} ${artifact.status} committed=${shortHash(artifact.committed)} rebuilt-expected=${shortHash(artifact.rebuiltExpected)}`);
27936
+ }
27937
+ const line = `distribution-bom.json ${receipt.bom.status} committed=${shortHash(receipt.bom.recorded)} rebuilt-expected=${shortHash(receipt.bom.rebuiltExpected)}`;
27938
+ const otherStale = receipt.bom.identities.filter((identity) => identity.id !== BOM_DIST_TREE_ID && identity.status === "stale");
27939
+ lines.push(otherStale.length > 0 ? `${line} (${otherStale.map((identity) => `${identity.id} identity stale`).join("; ")})` : line);
27940
+ lines.push(receipt.summary);
27941
+ return lines;
27942
+ }
27943
+ function readOrNull(path2) {
27944
+ return (0, import_node_fs33.existsSync)(path2) ? (0, import_node_fs33.readFileSync)(path2) : null;
27945
+ }
27946
+ function walkFiles(root) {
27947
+ const files = [];
27948
+ const walk2 = (directory) => {
27949
+ for (const entry of (0, import_node_fs33.readdirSync)(directory, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
27950
+ const child2 = (0, import_node_path29.join)(directory, entry.name);
27951
+ if (entry.isDirectory()) walk2(child2);
27952
+ else files.push(child2);
27953
+ }
27954
+ };
27955
+ walk2(root);
27956
+ return files;
27957
+ }
27958
+ function bomPathFor(root) {
27959
+ try {
27960
+ const registry2 = JSON.parse((0, import_node_fs33.readFileSync)((0, import_node_path29.join)(root, "surfaces.json"), "utf8"));
27961
+ return (0, import_node_path29.join)(root, registry2?.sharedAgentCore?.releaseMetadata?.bomPath ?? "distribution-bom.json");
27962
+ } catch {
27963
+ return (0, import_node_path29.join)(root, "distribution-bom.json");
27964
+ }
27965
+ }
27966
+ function rebuildTo(packageRoot, outDir) {
27967
+ (0, import_node_child_process18.execFileSync)(process.execPath, ["build.mjs"], {
27968
+ cwd: packageRoot,
27969
+ env: { ...process.env, MMI_DIST_OUTDIR: outDir },
27970
+ windowsHide: true,
27971
+ stdio: "pipe",
27972
+ encoding: "utf8"
27973
+ });
27974
+ }
27975
+ function runDistStatus(root) {
27976
+ const stage = (0, import_node_fs33.mkdtempSync)((0, import_node_path29.join)((0, import_node_os15.tmpdir)(), "mmi-dist-drift-"));
27977
+ let overlayCount = 0;
27978
+ try {
27979
+ const cliOut = (0, import_node_path29.join)(stage, "cli-dist");
27980
+ const hubOut = (0, import_node_path29.join)(stage, "hub-dist");
27981
+ rebuildTo((0, import_node_path29.join)(root, "cli"), cliOut);
27982
+ rebuildTo((0, import_node_path29.join)(root, "updater"), hubOut);
27983
+ const outDirFor = (packageDir) => packageDir === "cli" ? cliOut : hubOut;
27984
+ const rebuilt = (path2) => {
27985
+ const spec = DIST_ARTIFACTS.find((entry) => entry.path === path2);
27986
+ return spec ? readOrNull((0, import_node_path29.join)(outDirFor(spec.packageDir), spec.output)) : null;
27987
+ };
27988
+ const committed = (path2) => readOrNull((0, import_node_path29.join)(root, path2));
27989
+ const tree = (path2) => readOrNull((0, import_node_path29.join)(root, path2));
27990
+ const distRoot = (0, import_node_path29.join)(root, "cli", "dist");
27991
+ const distTree = () => walkFiles(distRoot).map((absolute) => `cli/dist/${(0, import_node_path29.relative)(distRoot, absolute).replaceAll("\\", "/")}`);
27992
+ const bom = JSON.parse((0, import_node_fs33.readFileSync)(bomPathFor(root), "utf8"));
27993
+ const digest = (entries) => {
27994
+ const overlay = (0, import_node_path29.join)(stage, `overlay-${overlayCount++}`);
27995
+ for (const entry of entries) {
27996
+ const target = (0, import_node_path29.join)(overlay, entry.path);
27997
+ (0, import_node_fs33.mkdirSync)((0, import_node_path29.dirname)(target), { recursive: true });
27998
+ (0, import_node_fs33.writeFileSync)(target, entry.bytes);
27999
+ }
28000
+ return digestPackedFiles(overlay, entries.map((entry) => entry.path));
28001
+ };
28002
+ return computeDistDriftReceipt({ committed, tree, rebuilt, distTree, bom, digest });
28003
+ } finally {
28004
+ (0, import_node_fs33.rmSync)(stage, { recursive: true, force: true });
28005
+ }
28006
+ }
28007
+
28008
+ // src/project-info-sync.ts
28009
+ var import_node_fs34 = require("node:fs");
28010
+ var import_node_path30 = require("node:path");
27117
28011
  var UPDATE_PROJECT_INFO = `mutation($projectId: ID!, $shortDescription: String!, $readme: String!) {
27118
28012
  updateProjectV2(input: { projectId: $projectId, shortDescription: $shortDescription, readme: $readme }) {
27119
28013
  projectV2 { id }
@@ -27158,14 +28052,14 @@ function sharedName(entries, fallback) {
27158
28052
  }
27159
28053
  function buildProjectInfoSyncPlan(targetRepo3, project2, projects, repoRoot2) {
27160
28054
  if (!project2.projectId) throw new Error(`org project sync-info: ${targetRepo3} registry META has no projectId`);
27161
- const readmePath = (0, import_node_path28.join)(repoRoot2, "README.md");
27162
- if (!(0, import_node_fs31.existsSync)(readmePath)) throw new Error(`org project sync-info: ${targetRepo3} has no README.md`);
28055
+ const readmePath = (0, import_node_path30.join)(repoRoot2, "README.md");
28056
+ if (!(0, import_node_fs34.existsSync)(readmePath)) throw new Error(`org project sync-info: ${targetRepo3} has no README.md`);
27163
28057
  const entries = entriesFor(project2, projects);
27164
28058
  const memberRepos = [...new Set(entries.flatMap((entry) => entry.repos ?? []))].filter((repo) => /^[^/]+\/[^/]+$/.test(repo)).sort((a, b) => a.localeCompare(b));
27165
28059
  const projectName = sharedName(entries, project2.name?.trim() || targetRepo3.split("/").pop() || targetRepo3);
27166
28060
  if (!memberRepos.length) throw new Error(`org project sync-info: project ${projectName} has no registered member repos`);
27167
28061
  const entryNames = entries.map((entry) => entry.name?.trim()).filter((name) => Boolean(name));
27168
- const shortDescription = memberRepos.length === 1 ? shortDescriptionFromReadme((0, import_node_fs31.readFileSync)(readmePath, "utf8")) : `Shared work across ${new Intl.ListFormat("en", { type: "conjunction" }).format(entryNames)}.`;
28062
+ const shortDescription = memberRepos.length === 1 ? shortDescriptionFromReadme((0, import_node_fs34.readFileSync)(readmePath, "utf8")) : `Shared work across ${new Intl.ListFormat("en", { type: "conjunction" }).format(entryNames)}.`;
27169
28063
  const lines = [
27170
28064
  `# ${projectName}`,
27171
28065
  "",
@@ -27184,8 +28078,8 @@ function buildProjectInfoSyncPlan(targetRepo3, project2, projects, repoRoot2) {
27184
28078
  const targetBase = `https://github.com/${targetRepo3}`;
27185
28079
  const targetBranch = branchFor(targetRepo3, projects);
27186
28080
  const orgDocs = [
27187
- (0, import_node_fs31.existsSync)((0, import_node_path28.join)(repoRoot2, "docs", "org-readme.md")) ? `- [Org identity](${targetBase}/blob/${targetBranch}/docs/org-readme.md)` : "",
27188
- (0, import_node_fs31.existsSync)((0, import_node_path28.join)(repoRoot2, "docs", "org-architecture.md")) ? `- [Org architecture](${targetBase}/blob/${targetBranch}/docs/org-architecture.md)` : ""
28081
+ (0, import_node_fs34.existsSync)((0, import_node_path30.join)(repoRoot2, "docs", "org-readme.md")) ? `- [Org identity](${targetBase}/blob/${targetBranch}/docs/org-readme.md)` : "",
28082
+ (0, import_node_fs34.existsSync)((0, import_node_path30.join)(repoRoot2, "docs", "org-architecture.md")) ? `- [Org architecture](${targetBase}/blob/${targetBranch}/docs/org-architecture.md)` : ""
27189
28083
  ].filter(Boolean);
27190
28084
  if (orgDocs.length) lines.push("", "## Organisation docs", "", ...orgDocs);
27191
28085
  return { projectId: project2.projectId, projectName, targetRepo: targetRepo3, memberRepos, shortDescription, readme: `${lines.join("\n")}
@@ -28033,9 +28927,9 @@ function writeError(res) {
28033
28927
  }
28034
28928
 
28035
28929
  // src/secrets-commands.ts
28036
- var import_node_fs32 = require("node:fs");
28037
- var import_node_path29 = require("node:path");
28038
- var import_node_os14 = require("node:os");
28930
+ var import_node_fs35 = require("node:fs");
28931
+ var import_node_path31 = require("node:path");
28932
+ var import_node_os16 = require("node:os");
28039
28933
  init_cli_shared();
28040
28934
  init_hub_auth();
28041
28935
  init_github_client();
@@ -28140,18 +29034,18 @@ function collectMap(value, previous = []) {
28140
29034
  return [...previous, value];
28141
29035
  }
28142
29036
  async function decryptRailsCredentials(input) {
28143
- const appDir = (0, import_node_path29.resolve)(input.appDir ?? process.cwd());
29037
+ const appDir = (0, import_node_path31.resolve)(input.appDir ?? process.cwd());
28144
29038
  const credentialsFile = input.credentialsFile ?? DEFAULT_RAILS_CREDENTIALS_FILE;
28145
29039
  const masterKeyFile = input.masterKeyFile ?? DEFAULT_RAILS_MASTER_KEY_FILE;
28146
- const credentialsPath = (0, import_node_path29.resolve)(appDir, credentialsFile);
28147
- const masterKeyPath = (0, import_node_path29.resolve)(appDir, masterKeyFile);
29040
+ const credentialsPath = (0, import_node_path31.resolve)(appDir, credentialsFile);
29041
+ const masterKeyPath = (0, import_node_path31.resolve)(appDir, masterKeyFile);
28148
29042
  const env = {
28149
29043
  ...process.env,
28150
29044
  MMI_RAILS_CREDENTIALS_FILE: credentialsPath,
28151
29045
  MMI_RAILS_MASTER_KEY_FILE: masterKeyPath
28152
29046
  };
28153
- if ((0, import_node_fs32.existsSync)(masterKeyPath)) {
28154
- env.RAILS_MASTER_KEY = (0, import_node_fs32.readFileSync)(masterKeyPath, "utf8").trim();
29047
+ if ((0, import_node_fs35.existsSync)(masterKeyPath)) {
29048
+ env.RAILS_MASTER_KEY = (0, import_node_fs35.readFileSync)(masterKeyPath, "utf8").trim();
28155
29049
  }
28156
29050
  const script = [
28157
29051
  'require "json"',
@@ -28161,9 +29055,9 @@ async function decryptRailsCredentials(input) {
28161
29055
  'config = ActiveSupport::EncryptedConfiguration.new(config_path: config_path, key_path: key_path, env_key: "RAILS_MASTER_KEY", raise_if_missing_key: true)',
28162
29056
  "puts JSON.generate(config.config)"
28163
29057
  ].join("\n");
28164
- const scriptDir = (0, import_node_fs32.mkdtempSync)((0, import_node_path29.join)((0, import_node_os14.tmpdir)(), "mmi-rails-decrypt-"));
28165
- const scriptPath = (0, import_node_path29.join)(scriptDir, "decrypt.rb");
28166
- (0, import_node_fs32.writeFileSync)(scriptPath, script, "utf8");
29058
+ const scriptDir = (0, import_node_fs35.mkdtempSync)((0, import_node_path31.join)((0, import_node_os16.tmpdir)(), "mmi-rails-decrypt-"));
29059
+ const scriptPath = (0, import_node_path31.join)(scriptDir, "decrypt.rb");
29060
+ (0, import_node_fs35.writeFileSync)(scriptPath, script, "utf8");
28167
29061
  try {
28168
29062
  const args = ["exec", "ruby", scriptPath];
28169
29063
  const cmd = process.platform === "win32" ? "cmd.exe" : "bundle";
@@ -28175,7 +29069,7 @@ async function decryptRailsCredentials(input) {
28175
29069
  });
28176
29070
  return JSON.parse(stdout);
28177
29071
  } finally {
28178
- (0, import_node_fs32.rmSync)(scriptDir, { recursive: true, force: true });
29072
+ (0, import_node_fs35.rmSync)(scriptDir, { recursive: true, force: true });
28179
29073
  }
28180
29074
  }
28181
29075
  async function readSecretStdin() {
@@ -28265,7 +29159,7 @@ function registerSecretsCommands(program3) {
28265
29159
  let body;
28266
29160
  if (o.file) {
28267
29161
  try {
28268
- body = (0, import_node_fs32.readFileSync)((0, import_node_path29.resolve)(o.file), "utf8");
29162
+ body = (0, import_node_fs35.readFileSync)((0, import_node_path31.resolve)(o.file), "utf8");
28269
29163
  } catch (e) {
28270
29164
  return fail(`secrets org-catalog: cannot read --file ${o.file}: ${e.message}`);
28271
29165
  }
@@ -28370,7 +29264,7 @@ function registerSecretsCommands(program3) {
28370
29264
  {
28371
29265
  ...d,
28372
29266
  decryptRailsCredentials,
28373
- removeFile: (path2) => (0, import_node_fs32.unlinkSync)((0, import_node_path29.resolve)(o.appDir ?? process.cwd(), path2))
29267
+ removeFile: (path2) => (0, import_node_fs35.unlinkSync)((0, import_node_path31.resolve)(o.appDir ?? process.cwd(), path2))
28374
29268
  },
28375
29269
  {
28376
29270
  repo: o.repo,
@@ -28415,7 +29309,7 @@ function registerSecretsCommands(program3) {
28415
29309
  }
28416
29310
 
28417
29311
  // src/app-actor.ts
28418
- var import_node_crypto11 = require("node:crypto");
29312
+ var import_node_crypto13 = require("node:crypto");
28419
29313
  var APP_ACTOR_ENV = "MMI_ACTOR";
28420
29314
  var APP_VAULT_REPO = "mutmutco/MMI-Hub";
28421
29315
  var APP_VAULT_KEYS = ["GITHUB_APP_ID", "GITHUB_APP_INSTALLATION_ID", "GITHUB_APP_PRIVATE_KEY"];
@@ -28459,7 +29353,7 @@ function mintAppJwt(appId, privateKeyPem, nowSec) {
28459
29353
  exp: now + APP_JWT_TTL_S,
28460
29354
  iss: appId
28461
29355
  }));
28462
- const signer = (0, import_node_crypto11.createSign)("RSA-SHA256");
29356
+ const signer = (0, import_node_crypto13.createSign)("RSA-SHA256");
28463
29357
  signer.update(`${header}.${payload}`);
28464
29358
  return `${header}.${payload}.${signer.sign(privateKeyPem, "base64url")}`;
28465
29359
  }
@@ -28577,7 +29471,7 @@ function emitCliCallTelemetry(command) {
28577
29471
  }
28578
29472
 
28579
29473
  // src/box-commands.ts
28580
- var import_node_fs33 = require("node:fs");
29474
+ var import_node_fs36 = require("node:fs");
28581
29475
  init_clean_exit();
28582
29476
 
28583
29477
  // src/box.ts
@@ -28781,7 +29675,7 @@ function registerBoxCommands(program3) {
28781
29675
  }
28782
29676
  if (o.json) console.log(JSON.stringify({ box: found, incomplete }, null, 2));
28783
29677
  else if (o.ssh && o.script) {
28784
- (0, import_node_fs33.writeFileSync)(o.script, sshRecipeScript(found), "utf8");
29678
+ (0, import_node_fs36.writeFileSync)(o.script, sshRecipeScript(found), "utf8");
28785
29679
  console.log(`wrote ${o.script} \u2014 run: bash "${o.script}"`);
28786
29680
  } else if (o.ssh) console.log(`${formatSshRecipe(found)}
28787
29681
  ${SSH_RECIPE_AGENT_NOTE}`);
@@ -28796,12 +29690,12 @@ ${SSH_RECIPE_AGENT_NOTE}`);
28796
29690
 
28797
29691
  // src/schedules-commands.ts
28798
29692
  var import_promises4 = require("node:fs/promises");
28799
- var import_node_child_process18 = require("node:child_process");
29693
+ var import_node_child_process19 = require("node:child_process");
28800
29694
  var import_node_util7 = require("node:util");
28801
29695
  init_clean_exit();
28802
29696
  init_github_client();
28803
29697
  init_cli_shared();
28804
- var execFileP5 = (0, import_node_util7.promisify)(import_node_child_process18.execFile);
29698
+ var execFileP5 = (0, import_node_util7.promisify)(import_node_child_process19.execFile);
28805
29699
  var AWS_REGION = "eu-central-1";
28806
29700
  var AWS_TIMEOUT_MS = 3e4;
28807
29701
  var AWS_RETRY_DELAY_MS = 1500;
@@ -28913,7 +29807,7 @@ async function awsJson(args) {
28913
29807
  try {
28914
29808
  return await run();
28915
29809
  } catch {
28916
- await new Promise((resolve5) => setTimeout(resolve5, AWS_RETRY_DELAY_MS));
29810
+ await new Promise((resolve6) => setTimeout(resolve6, AWS_RETRY_DELAY_MS));
28917
29811
  return run();
28918
29812
  }
28919
29813
  }
@@ -29117,8 +30011,8 @@ function registerSchedulesCommands(program3) {
29117
30011
 
29118
30012
  // src/file-lock.ts
29119
30013
  var import_promises5 = require("node:fs/promises");
29120
- var import_node_path30 = require("node:path");
29121
- var sleep = (ms) => new Promise((resolve5) => setTimeout(resolve5, ms));
30014
+ var import_node_path32 = require("node:path");
30015
+ var sleep = (ms) => new Promise((resolve6) => setTimeout(resolve6, ms));
29122
30016
  var IMMEDIATE_RETRY_BUDGET = 3;
29123
30017
  var FileLockBusyError = class extends Error {
29124
30018
  lockPath;
@@ -29202,7 +30096,7 @@ async function releaseFileLock(lockPath, guard) {
29202
30096
  }
29203
30097
  async function withFileLock(lockPath, opts, fn) {
29204
30098
  const resolved = resolveFileLockOpts(opts);
29205
- await (0, import_promises5.mkdir)((0, import_node_path30.dirname)(lockPath), { recursive: true }).catch(() => void 0);
30099
+ await (0, import_promises5.mkdir)((0, import_node_path32.dirname)(lockPath), { recursive: true }).catch(() => void 0);
29206
30100
  const guard = await acquireFileLock(lockPath, resolved, Date.now() + resolved.maxWaitMs);
29207
30101
  try {
29208
30102
  return await fn();
@@ -29213,7 +30107,7 @@ async function withFileLock(lockPath, opts, fn) {
29213
30107
 
29214
30108
  // src/schedules-lift-command.ts
29215
30109
  var import_promises6 = require("node:fs/promises");
29216
- var import_node_path31 = require("node:path");
30110
+ var import_node_path33 = require("node:path");
29217
30111
  init_clean_exit();
29218
30112
  init_cli_shared();
29219
30113
  var DEFAULT_WORKFLOWS_DIR = ".github/workflows";
@@ -29242,7 +30136,7 @@ async function readWorkflowFiles(dir) {
29242
30136
  const files = [];
29243
30137
  for (const name of names.sort()) {
29244
30138
  if (!/\.ya?ml$/.test(name)) continue;
29245
- files.push({ path: `.github/workflows/${name}`, text: await (0, import_promises6.readFile)((0, import_node_path31.join)(dir, name), "utf8") });
30139
+ files.push({ path: `.github/workflows/${name}`, text: await (0, import_promises6.readFile)((0, import_node_path33.join)(dir, name), "utf8") });
29246
30140
  }
29247
30141
  return files;
29248
30142
  }
@@ -29329,13 +30223,13 @@ init_cli_shared();
29329
30223
  // src/edge-tunnel.ts
29330
30224
  var HOSTNAME_RE = /^(?=.{1,253}$)([a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)(\.[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)+$/;
29331
30225
  var UPSTREAM_RE = /^https?:\/\/[^/\s]+(?::\d+)?(?:\/.*)?$/;
29332
- function tunnelNameFromHostname(hostname3) {
29333
- return hostname3.replace(/\./g, "-").slice(0, 63);
30226
+ function tunnelNameFromHostname(hostname4) {
30227
+ return hostname4.replace(/\./g, "-").slice(0, 63);
29334
30228
  }
29335
- function planInfraTunnel(hostname3, upstream) {
29336
- const host = hostname3.trim().toLowerCase();
30229
+ function planInfraTunnel(hostname4, upstream) {
30230
+ const host = hostname4.trim().toLowerCase();
29337
30231
  const origin = upstream.trim();
29338
- if (!HOSTNAME_RE.test(host)) throw new Error(`invalid hostname ${JSON.stringify(hostname3)}`);
30232
+ if (!HOSTNAME_RE.test(host)) throw new Error(`invalid hostname ${JSON.stringify(hostname4)}`);
29339
30233
  if (!UPSTREAM_RE.test(origin)) throw new Error(`invalid upstream ${JSON.stringify(upstream)} \u2014 expected http(s)://host:port`);
29340
30234
  const tunnelName = tunnelNameFromHostname(host);
29341
30235
  const configYaml = [
@@ -29393,15 +30287,149 @@ function registerEdgeCommands(program3) {
29393
30287
  }
29394
30288
 
29395
30289
  // src/bootstrap-commands.ts
29396
- var import_node_fs34 = require("node:fs");
29397
- var import_node_os15 = require("node:os");
29398
- var import_node_path32 = require("node:path");
30290
+ var import_node_fs38 = require("node:fs");
30291
+ var import_node_os17 = require("node:os");
30292
+ var import_node_path35 = require("node:path");
29399
30293
  init_cli_shared();
29400
30294
  init_clean_exit();
29401
30295
  init_github_client();
29402
30296
 
30297
+ // src/port-range-assign.ts
30298
+ init_cli_shared();
30299
+
30300
+ // src/port-registry.ts
30301
+ var import_node_fs37 = require("node:fs");
30302
+ var import_node_path34 = require("node:path");
30303
+
30304
+ // ../infra/port-geometry.mjs
30305
+ var PORT_BLOCK = 100;
30306
+ var PORT_SPAN = 10;
30307
+ var PORT_FIRST = 3e3;
30308
+
30309
+ // src/port-registry.ts
30310
+ function nextPortBlock(registry2) {
30311
+ const bases = Object.values(registry2).map(([start]) => start);
30312
+ const base = bases.length ? Math.max(...bases) + PORT_BLOCK : PORT_FIRST;
30313
+ return [base, base + PORT_SPAN];
30314
+ }
30315
+ function loadPortRegistry(path2) {
30316
+ if (!(0, import_node_fs37.existsSync)(path2)) return {};
30317
+ const raw = JSON.parse((0, import_node_fs37.readFileSync)(path2, "utf8"));
30318
+ const out = {};
30319
+ for (const [key, value] of Object.entries(raw)) {
30320
+ if (Array.isArray(value) && value.length === 2 && value.every((n) => typeof n === "number")) {
30321
+ out[key] = [value[0], value[1]];
30322
+ }
30323
+ }
30324
+ return out;
30325
+ }
30326
+ function ensurePortRange(repo, path2) {
30327
+ const registry2 = loadPortRegistry(path2);
30328
+ const existing = registry2[repo];
30329
+ if (existing) return existing;
30330
+ const range = nextPortBlock(registry2);
30331
+ const raw = (0, import_node_fs37.existsSync)(path2) ? JSON.parse((0, import_node_fs37.readFileSync)(path2, "utf8")) : {};
30332
+ raw[repo] = range;
30333
+ (0, import_node_fs37.writeFileSync)(path2, JSON.stringify(raw, null, 2) + "\n", "utf8");
30334
+ return range;
30335
+ }
30336
+ function portCursorSeed(registry2) {
30337
+ return nextPortBlock(registry2)[0];
30338
+ }
30339
+ function metaPortRange(meta) {
30340
+ const r = meta?.portRange;
30341
+ if (r && typeof r.start === "number" && typeof r.end === "number") return [r.start, r.end];
30342
+ return null;
30343
+ }
30344
+ function decidePortRange(input) {
30345
+ if (!input.metaReadOk) {
30346
+ return { action: "fail", reason: "could not verify the existing port block (Hub registry read failed) \u2014 retry; NOT allocating (a re-allocation on an unverified read would advance the cursor and hand out a duplicate block)" };
30347
+ }
30348
+ if (input.metaPortRange) return { action: "return", range: input.metaPortRange };
30349
+ return { action: "allocate" };
30350
+ }
30351
+ function existingPortRange(repo, registry2) {
30352
+ return registry2[repo] ?? null;
30353
+ }
30354
+ function portRangeInfraAt(root, source) {
30355
+ const registryPath = (0, import_node_path34.join)(root, "infra", "port-ranges.json");
30356
+ const ddbScriptPath = (0, import_node_path34.join)(root, "infra", "port-ddb.mjs");
30357
+ if (!(0, import_node_fs37.existsSync)(registryPath) || !(0, import_node_fs37.existsSync)(ddbScriptPath)) return null;
30358
+ return { root, source, registryPath, ddbScriptPath };
30359
+ }
30360
+ function resolvePortRangeInfra(cwd, packageDir) {
30361
+ const direct = portRangeInfraAt(cwd, "cwd");
30362
+ if (direct) return direct;
30363
+ for (let dir = cwd; ; dir = (0, import_node_path34.dirname)(dir)) {
30364
+ const sibling = portRangeInfraAt((0, import_node_path34.join)(dir, "MMI-Hub"), "sibling-hub");
30365
+ if (sibling) return sibling;
30366
+ const parent = (0, import_node_path34.dirname)(dir);
30367
+ if (parent === dir) break;
30368
+ }
30369
+ if (packageDir) {
30370
+ const pkgRoot = (0, import_node_path34.join)(packageDir, "..", "..");
30371
+ const pkgFrom = portRangeInfraAt(pkgRoot, "pkg-root");
30372
+ if (pkgFrom) return pkgFrom;
30373
+ }
30374
+ return null;
30375
+ }
30376
+ async function ensurePortRangeAtomic(repo, path2, allocate, opts = {}) {
30377
+ const registry2 = loadPortRegistry(path2);
30378
+ const existing = existingPortRange(repo, registry2);
30379
+ if (existing) return { range: existing, source: "existing" };
30380
+ const seed = portCursorSeed(registry2);
30381
+ try {
30382
+ const range = await allocate(seed);
30383
+ return { range, source: "ddb" };
30384
+ } catch (e) {
30385
+ if (!opts.quiet) console.warn(`port-registry: DDB allocator unreachable, falling back to committed file (${e.message})`);
30386
+ return { range: ensurePortRange(repo, path2), source: "file" };
30387
+ }
30388
+ }
30389
+
30390
+ // src/port-range-assign.ts
30391
+ async function assignPersistedPortRange(repo, slug, reg, opts) {
30392
+ const read = await fetchProjectBySlugChecked(slug, reg);
30393
+ const decision = decidePortRange({ metaReadOk: read.ok, metaPortRange: read.ok ? metaPortRange(read.project) : null });
30394
+ if (decision.action === "fail") {
30395
+ return { ok: false, error: `${decision.reason}${read.ok ? "" : ` (${read.error})`}` };
30396
+ }
30397
+ if (decision.action === "return") {
30398
+ return { ok: true, range: decision.range, source: "meta", persisted: true };
30399
+ }
30400
+ const infra = resolvePortRangeInfra(opts.cwd, opts.moduleDir);
30401
+ if (!infra) {
30402
+ return {
30403
+ ok: false,
30404
+ error: `no MMI-Hub allocator files found (checked cwd ${opts.cwd}, sibling MMI-Hub dirs, and the installed package location); ensure the Hub's infra/port-ranges.json and infra/port-ddb.mjs are reachable`
30405
+ };
30406
+ }
30407
+ const path2 = infra.registryPath;
30408
+ const allocate = async (seed) => {
30409
+ const { stdout } = await execFileP2("node", [infra.ddbScriptPath, String(seed)], { timeout: 15e3 });
30410
+ const parsed = JSON.parse(stdout);
30411
+ if (!Array.isArray(parsed.range) || parsed.range.length !== 2) throw new Error("port-ddb: no range in output");
30412
+ return parsed.range;
30413
+ };
30414
+ const { range: [start, end], source } = await ensurePortRangeAtomic(repo, path2, allocate);
30415
+ const write = await upsertProject(slug, { portRange: { start, end } }, reg);
30416
+ if (!write.ok && source === "ddb") {
30417
+ return {
30418
+ ok: false,
30419
+ error: `block [${start}, ${end}] was allocated (cursor advanced) but NOT recorded in the registry META (${write.error ?? `HTTP ${write.status}`}) \u2014 fix auth/connectivity and retry so the block is persisted; do not re-run blind`
30420
+ };
30421
+ }
30422
+ return {
30423
+ ok: true,
30424
+ range: [start, end],
30425
+ source: "allocated",
30426
+ persisted: write.ok,
30427
+ ...write.ok ? {} : { persistError: write.error ?? `HTTP ${write.status}` }
30428
+ };
30429
+ }
30430
+
29403
30431
  // src/bootstrap-drift.ts
29404
- var import_node_crypto12 = require("node:crypto");
30432
+ var import_node_crypto14 = require("node:crypto");
29405
30433
  function byteComparableSeeds(manifest, cls) {
29406
30434
  return manifest.seeds.filter((s) => s.ownership === "org" && s.source === "self" && s.classes.includes(cls));
29407
30435
  }
@@ -29421,7 +30449,7 @@ function compareSeedBytes(hubContent, repoContent) {
29421
30449
  return normalize(hubContent) === normalize(repoContent) ? "match" : "drift";
29422
30450
  }
29423
30451
  function seedContentHash(content) {
29424
- return (0, import_node_crypto12.createHash)("sha256").update(content.replace(/\r\n/g, "\n"), "utf8").digest("hex");
30452
+ return (0, import_node_crypto14.createHash)("sha256").update(content.replace(/\r\n/g, "\n"), "utf8").digest("hex");
29425
30453
  }
29426
30454
  function auditRepoSeedDrift(repo, seeds, hubContents, repoReads) {
29427
30455
  const byTarget = new Map(repoReads.map((r) => [r.target, r.content]));
@@ -29582,7 +30610,7 @@ function renderPropagationReport(plan) {
29582
30610
  }
29583
30611
 
29584
30612
  // src/bootstrap-propagation-identity.ts
29585
- var import_node_crypto13 = require("node:crypto");
30613
+ var import_node_crypto15 = require("node:crypto");
29586
30614
  var PROPAGATION_BRANCH_PREFIX = "seed-propagate-";
29587
30615
  var TARGET_MARKER_NAME = "mmi-bootstrap-propagation-target";
29588
30616
  function safeBranchPart(value, maxLength, fallback) {
@@ -29595,7 +30623,7 @@ function repoSlug2(repo) {
29595
30623
  function propagationBranch(repo, target) {
29596
30624
  const repoPart = safeBranchPart(repoSlug2(repo), 32, "repo");
29597
30625
  const targetPart = safeBranchPart(target, 48, "target");
29598
- const hash = (0, import_node_crypto13.createHash)("sha256").update(repo.trim().toLowerCase()).update("\0").update(target).digest("hex").slice(0, 12);
30626
+ const hash = (0, import_node_crypto15.createHash)("sha256").update(repo.trim().toLowerCase()).update("\0").update(target).digest("hex").slice(0, 12);
29599
30627
  return `${PROPAGATION_BRANCH_PREFIX}${repoPart}-${targetPart}-${hash}`;
29600
30628
  }
29601
30629
  function legacyPropagationBranch(repo) {
@@ -30035,6 +31063,20 @@ function filledDocCheck(label, text, path2) {
30035
31063
  const unfilled = unfilledDocPlaceholders(text);
30036
31064
  return { ok: unfilled.length === 0, label, detail: unfilled.length ? `unfilled: ${unfilled.join(", ")}` : void 0 };
30037
31065
  }
31066
+ function isCentralContainerDeployModel(model) {
31067
+ return model === "tenant-container" || model === "solo-container";
31068
+ }
31069
+ function centralContainerPortRangeCheck(deployModel, portRange, repo) {
31070
+ if (!isCentralContainerDeployModel(deployModel)) return null;
31071
+ const start = portRange?.start;
31072
+ const end = portRange?.end;
31073
+ const ok = typeof start === "number" && typeof end === "number" && Number.isFinite(start) && Number.isFinite(end) && start <= end;
31074
+ return {
31075
+ ok,
31076
+ label: "Hub registry portRange present for local stage",
31077
+ detail: ok ? void 0 : `${deployModel} needs PROJECT# META.portRange for mmi-cli stage \u2014 assign with: mmi-cli stage port-range ${repo}`
31078
+ };
31079
+ }
30038
31080
  async function verifyBootstrap(repo, repoClass, deps, releaseTrack) {
30039
31081
  const branchesWanted = expectedBranches(repoClass, releaseTrack);
30040
31082
  const baseBranch = releaseTrack === "trunk" || repoClass === "content" ? "main" : "development";
@@ -30128,6 +31170,8 @@ async function verifyBootstrap(repo, repoClass, deps, releaseTrack) {
30128
31170
  });
30129
31171
  }
30130
31172
  }
31173
+ const portRangeCheck = centralContainerPortRangeCheck(deps.deployModel, deps.projectMeta?.portRange, repo);
31174
+ if (portRangeCheck) checks.push(portRangeCheck);
30131
31175
  const readme = await contentText(deps, repo, baseBranch, "README.md");
30132
31176
  checks.push({
30133
31177
  ok: readme !== null && readme.includes("## Agent context"),
@@ -30397,13 +31441,13 @@ function registerBootstrapCommands(program3) {
30397
31441
  client: defaultGitHubClient(),
30398
31442
  projectMeta: meta,
30399
31443
  deployModel: typeof meta?.deployModel === "string" ? meta.deployModel : void 0,
30400
- readLocalFile: (path2) => path2 === "projects.json" && apiProjects != null ? apiProjects : (0, import_node_fs34.existsSync)(path2) ? (0, import_node_fs34.readFileSync)(path2, "utf8") : null,
31444
+ readLocalFile: (path2) => path2 === "projects.json" && apiProjects != null ? apiProjects : (0, import_node_fs38.existsSync)(path2) ? (0, import_node_fs38.readFileSync)(path2, "utf8") : null,
30401
31445
  // requiredGcpApis is stored as an array by a JSON write, but `org project set --var KEY=VALUE` stores a raw
30402
31446
  // comma-string — accept either so the seeded value verifies regardless of how it was written.
30403
31447
  // #3689: the same committed map the org access audit reads (#3664), so a sanctioned admin is not a
30404
31448
  // permanent bootstrap failure on one surface and an intended state on the other. Absent file → no
30405
31449
  // sanction, which is the pre-#3664 behaviour.
30406
- sanctionedAdmins: (0, import_node_fs34.existsSync)("access-matrix.json") ? entriesValueByCanonicalRepo(loadSanctionedAdmins((0, import_node_fs34.readFileSync)("access-matrix.json", "utf8")), repo) : void 0,
31450
+ sanctionedAdmins: (0, import_node_fs38.existsSync)("access-matrix.json") ? entriesValueByCanonicalRepo(loadSanctionedAdmins((0, import_node_fs38.readFileSync)("access-matrix.json", "utf8")), repo) : void 0,
30407
31451
  requiredGcpApis: (() => {
30408
31452
  const v = meta?.requiredGcpApis;
30409
31453
  if (Array.isArray(v)) return v;
@@ -30456,14 +31500,14 @@ function registerBootstrapCommands(program3) {
30456
31500
  bootstrap.command("drift").description("#3818: compare every org-owned whole-file seed against MMI-Hub's copy across the registry roster; read-only").option("--repo <owner/repo>", "audit one repo instead of the roster (never a fleet verdict)").option("--json", "machine-readable output").action(async () => {
30457
31501
  const o = { repo: rawValue("--repo", ""), json: rawFlag("--json") };
30458
31502
  const manifestPath = "skills/bootstrap/seeds/manifest.json";
30459
- if (!(0, import_node_fs34.existsSync)(manifestPath)) return fail(`bootstrap drift: ${manifestPath} not found; run from the MMI-Hub repo root \u2014 the Hub's copies ARE the reference this compares against`);
31503
+ if (!(0, import_node_fs38.existsSync)(manifestPath)) return fail(`bootstrap drift: ${manifestPath} not found; run from the MMI-Hub repo root \u2014 the Hub's copies ARE the reference this compares against`);
30460
31504
  const seedSource = await resolveHubSeedSource(execGitForSeedSource);
30461
31505
  if (!seedSource.ok) return fail(`bootstrap drift: ${seedSource.reason}`);
30462
- const manifest = loadBootstrapSeeds((0, import_node_fs34.readFileSync)(manifestPath, "utf8"));
31506
+ const manifest = loadBootstrapSeeds((0, import_node_fs38.readFileSync)(manifestPath, "utf8"));
30463
31507
  const hubContents = /* @__PURE__ */ new Map();
30464
31508
  for (const s of manifest.seeds) {
30465
31509
  if (s.ownership !== "org" || s.source !== "self") continue;
30466
- hubContents.set(s.target, (0, import_node_fs34.existsSync)(s.target) ? (0, import_node_fs34.readFileSync)(s.target, "utf8") : null);
31510
+ hubContents.set(s.target, (0, import_node_fs38.existsSync)(s.target) ? (0, import_node_fs38.readFileSync)(s.target, "utf8") : null);
30467
31511
  }
30468
31512
  let targets;
30469
31513
  let classOf = (_repo) => "deployable";
@@ -30581,10 +31625,10 @@ function registerBootstrapCommands(program3) {
30581
31625
  return;
30582
31626
  }
30583
31627
  const manifestPath = "skills/bootstrap/seeds/manifest.json";
30584
- if (!(0, import_node_fs34.existsSync)(manifestPath)) return fail(`bootstrap apply: ${manifestPath} not found; bootstrap runs from the MMI-Hub repo root by design \u2014 it stamps org-level resources (Project, Ruleset, secrets, access) through the GitHub App, which is only authorized from the Hub checkout`);
31628
+ if (!(0, import_node_fs38.existsSync)(manifestPath)) return fail(`bootstrap apply: ${manifestPath} not found; bootstrap runs from the MMI-Hub repo root by design \u2014 it stamps org-level resources (Project, Ruleset, secrets, access) through the GitHub App, which is only authorized from the Hub checkout`);
30585
31629
  const seedSource = await resolveHubSeedSource(execGitForSeedSource);
30586
31630
  if (!seedSource.ok) return fail(`bootstrap apply: ${seedSource.reason}`);
30587
- const manifest = loadBootstrapSeeds((0, import_node_fs34.readFileSync)(manifestPath, "utf8"));
31631
+ const manifest = loadBootstrapSeeds((0, import_node_fs38.readFileSync)(manifestPath, "utf8"));
30588
31632
  const baseBranch = o.class === "content" ? "main" : "development";
30589
31633
  const slug = parsedRepo.slug;
30590
31634
  const onlyTarget = o.only.trim();
@@ -30596,16 +31640,16 @@ function registerBootstrapCommands(program3) {
30596
31640
  }
30597
31641
  const onlyManagedBlock = onlyTarget ? seedsToApply[0]?.managedBlock != null : false;
30598
31642
  const gh = async (args) => execFileP2("gh", args, { timeout: 2e4 });
30599
- const readFile7 = (p) => (0, import_node_fs34.existsSync)(p) ? (0, import_node_fs34.readFileSync)(p, "utf8") : null;
31643
+ const readFile7 = (p) => (0, import_node_fs38.existsSync)(p) ? (0, import_node_fs38.readFileSync)(p, "utf8") : null;
30600
31644
  const enc = (p) => p.split("/").map(encodeURIComponent).join("/");
30601
31645
  const putSeed = async (target, content, ref, sha) => {
30602
- 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`);
30603
- (0, import_node_fs34.writeFileSync)(tmp, JSON.stringify(contentPutBody(target, content, ref, sha)), "utf8");
31646
+ const tmp = (0, import_node_path35.join)((0, import_node_os17.tmpdir)(), `mmi-seed-${process.pid}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}.json`);
31647
+ (0, import_node_fs38.writeFileSync)(tmp, JSON.stringify(contentPutBody(target, content, ref, sha)), "utf8");
30604
31648
  try {
30605
31649
  await gh(contentPutInputArgs(repo, target, tmp));
30606
31650
  } finally {
30607
31651
  try {
30608
- (0, import_node_fs34.unlinkSync)(tmp);
31652
+ (0, import_node_fs38.unlinkSync)(tmp);
30609
31653
  } catch {
30610
31654
  }
30611
31655
  }
@@ -30878,10 +31922,32 @@ ${onlyManagedBlock ? `Only the marker-bounded Hub-managed block inside repo-owne
30878
31922
  }
30879
31923
  if (o.execute && !onlyTarget) {
30880
31924
  const cfg = await loadConfig();
30881
- const res = await registerProject(registerPayload, registryClientDeps(cfg));
31925
+ const reg = registryClientDeps(cfg);
31926
+ const res = await registerProject(registerPayload, reg);
30882
31927
  if (res.ok) {
30883
31928
  ddbWrites.push({ slug: registerPayload.slug, action: "register", record: registerPayload });
30884
31929
  applied.push(`ddb register ${registerPayload.slug}`);
31930
+ const deployModel = typeof registerPayload.deployModel === "string" ? registerPayload.deployModel : void 0;
31931
+ if (deployModel === "tenant-container" || deployModel === "solo-container") {
31932
+ const shortName = typeof registerPayload.name === "string" ? registerPayload.name : typeof registerPayload.slug === "string" ? registerPayload.slug : repo.split("/")[1] || repo;
31933
+ const assigned = await assignPersistedPortRange(shortName, registerPayload.slug, reg, {
31934
+ cwd: process.cwd(),
31935
+ moduleDir: __dirname
31936
+ });
31937
+ if (assigned.ok) {
31938
+ const [start, end] = assigned.range;
31939
+ ddbWrites.push({
31940
+ slug: registerPayload.slug,
31941
+ action: "portRange",
31942
+ record: { portRange: { start, end }, source: assigned.source, persisted: assigned.persisted }
31943
+ });
31944
+ applied.push(
31945
+ assigned.source === "meta" ? `portRange [${start}, ${end}] (already on META)` : `portRange [${start}, ${end}] (${assigned.persisted ? "persisted" : `META not persisted: ${assigned.persistError}`})`
31946
+ );
31947
+ } else {
31948
+ applied.push(`portRange (failed: ${assigned.error})`);
31949
+ }
31950
+ }
30885
31951
  } else {
30886
31952
  const why = res.error ?? `HTTP ${res.status}${res.body?.error ? ` \u2014 ${res.body.error}` : ""}`;
30887
31953
  applied.push(`ddb register ${registerPayload.slug} (failed: ${why})`);
@@ -30898,10 +31964,10 @@ LIVE apply to ${repo}:
30898
31964
  bootstrap.command("propagate").description("#4238: re-entrant canary\u2192wave tick \u2014 plan (or, with --execute, open) per-repo PRs fanning an org-owned seed out to the fleet").option("--target <path>", "the manifest target to propagate (an org-owned whole file or declared Hub-managed block)").option("--execute", "LIVE tick via gh (master-gated) \u2014 opens/reuses per-repo seed-propagate PRs; dry-run prints the plan only").option("--json", "machine-readable output").action(async () => {
30899
31965
  const o = { target: rawValue("--target", ""), execute: rawFlag("--execute"), json: rawFlag("--json") };
30900
31966
  const manifestPath = "skills/bootstrap/seeds/manifest.json";
30901
- if (!(0, import_node_fs34.existsSync)(manifestPath)) return fail(`bootstrap propagate: ${manifestPath} not found; run from the MMI-Hub repo root \u2014 the Hub's copies ARE the desired state this tick propagates`);
31967
+ if (!(0, import_node_fs38.existsSync)(manifestPath)) return fail(`bootstrap propagate: ${manifestPath} not found; run from the MMI-Hub repo root \u2014 the Hub's copies ARE the desired state this tick propagates`);
30902
31968
  const seedSource = await resolveHubSeedSource(execGitForSeedSource);
30903
31969
  if (!seedSource.ok) return fail(`bootstrap propagate: ${seedSource.reason}`);
30904
- const manifest = loadBootstrapSeeds((0, import_node_fs34.readFileSync)(manifestPath, "utf8"));
31970
+ const manifest = loadBootstrapSeeds((0, import_node_fs38.readFileSync)(manifestPath, "utf8"));
30905
31971
  const propagatable = manifest.seeds.filter(isPropagatableSeed);
30906
31972
  if (!o.target) {
30907
31973
  return fail(`bootstrap propagate: --target <path> is required \u2014 one of:
@@ -30910,9 +31976,9 @@ LIVE apply to ${repo}:
30910
31976
  const seed = propagatable.find((s) => s.target === o.target);
30911
31977
  if (!seed) return fail(`bootstrap propagate: --target '${o.target}' names no centrally propagatable seed in ${manifestPath}. Propagatable targets:
30912
31978
  ${propagatable.map((s) => s.target).join("\n ")}`);
30913
- 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`);
30914
- const hubContent = seed.managedBlock ? null : (0, import_node_fs34.readFileSync)(seed.target, "utf8");
30915
- const readSeedFile = (path2) => (0, import_node_fs34.existsSync)(path2) ? (0, import_node_fs34.readFileSync)(path2, "utf8") : null;
31979
+ if (!seed.managedBlock && !(0, import_node_fs38.existsSync)(seed.target)) return fail(`bootstrap propagate: the Hub's own copy of '${seed.target}' is missing \u2014 nothing to propagate`);
31980
+ const hubContent = seed.managedBlock ? null : (0, import_node_fs38.readFileSync)(seed.target, "utf8");
31981
+ const readSeedFile = (path2) => (0, import_node_fs38.existsSync)(path2) ? (0, import_node_fs38.readFileSync)(path2, "utf8") : null;
30916
31982
  const isWorkflowSeed = seed.target.startsWith(".github/workflows/");
30917
31983
  const cfg = await loadConfig();
30918
31984
  const projects = await fetchProjectsList(registryClientDeps(cfg));
@@ -30921,9 +31987,9 @@ LIVE apply to ${repo}:
30921
31987
  }
30922
31988
  const rosterRepos2 = collectRegistryRepos(projects).filter((r) => r.toLowerCase() !== "mutmutco/mmi-hub");
30923
31989
  let independentCount = rosterRepos2.length;
30924
- if ((0, import_node_fs34.existsSync)("projects.json")) {
31990
+ if ((0, import_node_fs38.existsSync)("projects.json")) {
30925
31991
  try {
30926
- const local = JSON.parse((0, import_node_fs34.readFileSync)("projects.json", "utf8"));
31992
+ const local = JSON.parse((0, import_node_fs38.readFileSync)("projects.json", "utf8"));
30927
31993
  const localRepos = /* @__PURE__ */ new Set();
30928
31994
  for (const p of local.projects ?? []) for (const r of p.repos ?? []) {
30929
31995
  const full = (r.includes("/") ? r : `mutmutco/${r}`).toLowerCase();
@@ -31070,15 +32136,15 @@ LIVE apply to ${repo}:
31070
32136
  } catch {
31071
32137
  existingSha = void 0;
31072
32138
  }
31073
- const tmp = (0, import_node_path32.join)((0, import_node_os15.tmpdir)(), `mmi-propagate-${process.pid}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}.json`);
32139
+ const tmp = (0, import_node_path35.join)((0, import_node_os17.tmpdir)(), `mmi-propagate-${process.pid}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}.json`);
31074
32140
  const desiredContent = desiredByRepo.get(rec.repo);
31075
32141
  if (desiredContent == null) return fail(`bootstrap propagate: no resolved content for ${rec.repo} ${seed.target} \u2014 refusing to write`);
31076
- (0, import_node_fs34.writeFileSync)(tmp, JSON.stringify(contentPutBody(seed.target, desiredContent, branch, existingSha)), "utf8");
32142
+ (0, import_node_fs38.writeFileSync)(tmp, JSON.stringify(contentPutBody(seed.target, desiredContent, branch, existingSha)), "utf8");
31077
32143
  try {
31078
32144
  await gh(contentPutInputArgs(rec.repo, seed.target, tmp));
31079
32145
  } finally {
31080
32146
  try {
31081
- (0, import_node_fs34.unlinkSync)(tmp);
32147
+ (0, import_node_fs38.unlinkSync)(tmp);
31082
32148
  } catch {
31083
32149
  }
31084
32150
  }
@@ -31145,10 +32211,10 @@ Rollback: \`mmi-cli devops bootstrap rollback ${rec.repo} --target ${seed.target
31145
32211
  return fail(`bootstrap rollback: ${e.message}`);
31146
32212
  }
31147
32213
  const manifestPath = "skills/bootstrap/seeds/manifest.json";
31148
- if (!(0, import_node_fs34.existsSync)(manifestPath)) return fail(`bootstrap rollback: ${manifestPath} not found; run from the MMI-Hub repo root \u2014 the manifest names which targets are org-owned and therefore propagated (and rollback-able)`);
32214
+ if (!(0, import_node_fs38.existsSync)(manifestPath)) return fail(`bootstrap rollback: ${manifestPath} not found; run from the MMI-Hub repo root \u2014 the manifest names which targets are org-owned and therefore propagated (and rollback-able)`);
31149
32215
  const seedSource = await resolveHubSeedSource(execGitForSeedSource);
31150
32216
  if (!seedSource.ok) return fail(`bootstrap rollback: ${seedSource.reason}`);
31151
- const manifest = loadBootstrapSeeds((0, import_node_fs34.readFileSync)(manifestPath, "utf8"));
32217
+ const manifest = loadBootstrapSeeds((0, import_node_fs38.readFileSync)(manifestPath, "utf8"));
31152
32218
  const propagatable = manifest.seeds.filter(isPropagatableSeed);
31153
32219
  if (!o.target) {
31154
32220
  return fail(`bootstrap rollback: --target <path> is required \u2014 one of:
@@ -31165,10 +32231,10 @@ Rollback: \`mmi-cli devops bootstrap rollback ${rec.repo} --target ${seed.target
31165
32231
  const enc = (p) => p.split("/").map(encodeURIComponent).join("/");
31166
32232
  let candidates;
31167
32233
  if (o.record) {
31168
- if (!(0, import_node_fs34.existsSync)(o.record)) return fail(`bootstrap rollback: --record '${o.record}' not found`);
32234
+ if (!(0, import_node_fs38.existsSync)(o.record)) return fail(`bootstrap rollback: --record '${o.record}' not found`);
31169
32235
  let parsed;
31170
32236
  try {
31171
- parsed = JSON.parse((0, import_node_fs34.readFileSync)(o.record, "utf8"));
32237
+ parsed = JSON.parse((0, import_node_fs38.readFileSync)(o.record, "utf8"));
31172
32238
  } catch (e) {
31173
32239
  return fail(`bootstrap rollback: --record '${o.record}' is not valid JSON: ${e.message}`);
31174
32240
  }
@@ -31245,13 +32311,13 @@ Rollback: \`mmi-cli devops bootstrap rollback ${rec.repo} --target ${seed.target
31245
32311
  } catch {
31246
32312
  existingSha = void 0;
31247
32313
  }
31248
- 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`);
31249
- (0, import_node_fs34.writeFileSync)(tmp, JSON.stringify(contentPutBody(seed.target, preSeedContent, plan.branch, existingSha)), "utf8");
32314
+ const tmp = (0, import_node_path35.join)((0, import_node_os17.tmpdir)(), `mmi-rollback-${process.pid}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}.json`);
32315
+ (0, import_node_fs38.writeFileSync)(tmp, JSON.stringify(contentPutBody(seed.target, preSeedContent, plan.branch, existingSha)), "utf8");
31250
32316
  try {
31251
32317
  await gh(contentPutInputArgs(repo, seed.target, tmp));
31252
32318
  } finally {
31253
32319
  try {
31254
- (0, import_node_fs34.unlinkSync)(tmp);
32320
+ (0, import_node_fs38.unlinkSync)(tmp);
31255
32321
  } catch {
31256
32322
  }
31257
32323
  }
@@ -31276,101 +32342,11 @@ Rollback: \`mmi-cli devops bootstrap rollback ${rec.repo} --target ${seed.target
31276
32342
  }
31277
32343
 
31278
32344
  // src/stage-commands.ts
31279
- var import_node_fs36 = require("node:fs");
31280
- var import_node_path34 = require("node:path");
32345
+ var import_node_fs39 = require("node:fs");
32346
+ var import_node_path36 = require("node:path");
31281
32347
  init_cli_shared();
31282
32348
  init_clean_exit();
31283
32349
 
31284
- // src/port-registry.ts
31285
- var import_node_fs35 = require("node:fs");
31286
- var import_node_path33 = require("node:path");
31287
-
31288
- // ../infra/port-geometry.mjs
31289
- var PORT_BLOCK = 100;
31290
- var PORT_SPAN = 10;
31291
- var PORT_FIRST = 3e3;
31292
-
31293
- // src/port-registry.ts
31294
- function nextPortBlock(registry2) {
31295
- const bases = Object.values(registry2).map(([start]) => start);
31296
- const base = bases.length ? Math.max(...bases) + PORT_BLOCK : PORT_FIRST;
31297
- return [base, base + PORT_SPAN];
31298
- }
31299
- function loadPortRegistry(path2) {
31300
- if (!(0, import_node_fs35.existsSync)(path2)) return {};
31301
- const raw = JSON.parse((0, import_node_fs35.readFileSync)(path2, "utf8"));
31302
- const out = {};
31303
- for (const [key, value] of Object.entries(raw)) {
31304
- if (Array.isArray(value) && value.length === 2 && value.every((n) => typeof n === "number")) {
31305
- out[key] = [value[0], value[1]];
31306
- }
31307
- }
31308
- return out;
31309
- }
31310
- function ensurePortRange(repo, path2) {
31311
- const registry2 = loadPortRegistry(path2);
31312
- const existing = registry2[repo];
31313
- if (existing) return existing;
31314
- const range = nextPortBlock(registry2);
31315
- const raw = (0, import_node_fs35.existsSync)(path2) ? JSON.parse((0, import_node_fs35.readFileSync)(path2, "utf8")) : {};
31316
- raw[repo] = range;
31317
- (0, import_node_fs35.writeFileSync)(path2, JSON.stringify(raw, null, 2) + "\n", "utf8");
31318
- return range;
31319
- }
31320
- function portCursorSeed(registry2) {
31321
- return nextPortBlock(registry2)[0];
31322
- }
31323
- function metaPortRange(meta) {
31324
- const r = meta?.portRange;
31325
- if (r && typeof r.start === "number" && typeof r.end === "number") return [r.start, r.end];
31326
- return null;
31327
- }
31328
- function decidePortRange(input) {
31329
- if (!input.metaReadOk) {
31330
- 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)" };
31331
- }
31332
- if (input.metaPortRange) return { action: "return", range: input.metaPortRange };
31333
- return { action: "allocate" };
31334
- }
31335
- function existingPortRange(repo, registry2) {
31336
- return registry2[repo] ?? null;
31337
- }
31338
- function portRangeInfraAt(root, source) {
31339
- const registryPath = (0, import_node_path33.join)(root, "infra", "port-ranges.json");
31340
- const ddbScriptPath = (0, import_node_path33.join)(root, "infra", "port-ddb.mjs");
31341
- if (!(0, import_node_fs35.existsSync)(registryPath) || !(0, import_node_fs35.existsSync)(ddbScriptPath)) return null;
31342
- return { root, source, registryPath, ddbScriptPath };
31343
- }
31344
- function resolvePortRangeInfra(cwd, packageDir) {
31345
- const direct = portRangeInfraAt(cwd, "cwd");
31346
- if (direct) return direct;
31347
- for (let dir = cwd; ; dir = (0, import_node_path33.dirname)(dir)) {
31348
- const sibling = portRangeInfraAt((0, import_node_path33.join)(dir, "MMI-Hub"), "sibling-hub");
31349
- if (sibling) return sibling;
31350
- const parent = (0, import_node_path33.dirname)(dir);
31351
- if (parent === dir) break;
31352
- }
31353
- if (packageDir) {
31354
- const pkgRoot = (0, import_node_path33.join)(packageDir, "..", "..");
31355
- const pkgFrom = portRangeInfraAt(pkgRoot, "pkg-root");
31356
- if (pkgFrom) return pkgFrom;
31357
- }
31358
- return null;
31359
- }
31360
- async function ensurePortRangeAtomic(repo, path2, allocate, opts = {}) {
31361
- const registry2 = loadPortRegistry(path2);
31362
- const existing = existingPortRange(repo, registry2);
31363
- if (existing) return { range: existing, source: "existing" };
31364
- const seed = portCursorSeed(registry2);
31365
- try {
31366
- const range = await allocate(seed);
31367
- return { range, source: "ddb" };
31368
- } catch (e) {
31369
- if (!opts.quiet) console.warn(`port-registry: DDB allocator unreachable, falling back to committed file (${e.message})`);
31370
- return { range: ensurePortRange(repo, path2), source: "file" };
31371
- }
31372
- }
31373
-
31374
32350
  // src/stage-default.ts
31375
32351
  function shellFor(platform2 = process.platform) {
31376
32352
  return platform2 === "win32" ? "powershell" : "bash";
@@ -31378,14 +32354,20 @@ function shellFor(platform2 = process.platform) {
31378
32354
  function isCentralContainerModel(model) {
31379
32355
  return model === "tenant-container" || model === "solo-container";
31380
32356
  }
31381
- function deriveStageGap(inputs) {
32357
+ function stagePortRangeRecovery(repo = "<owner/repo>") {
32358
+ return `mmi-cli stage port-range ${repo}`;
32359
+ }
32360
+ function deriveStageGap(inputs, opts) {
31382
32361
  const missing = [];
31383
32362
  if (!isCentralContainerModel(inputs.deployModel)) {
31384
32363
  return `local stage default applies to central-container repos only (tenant-container/solo-container; registry deployModel = ${inputs.deployModel ?? "unset"})`;
31385
32364
  }
31386
32365
  if (!inputs.hasCompose) missing.push("docker-compose.yml");
31387
32366
  if (!inputs.portRange) missing.push("Hub registry portRange");
31388
- return missing.length ? `cannot derive a default local stage \u2014 missing: ${missing.join(", ")}` : null;
32367
+ if (!missing.length) return null;
32368
+ const base = `cannot derive a default local stage \u2014 missing: ${missing.join(", ")}`;
32369
+ if (!inputs.portRange) return `${base} \u2014 assign with: ${stagePortRangeRecovery(opts?.repo)}`;
32370
+ return base;
31389
32371
  }
31390
32372
  function deriveStage(inputs) {
31391
32373
  if (deriveStageGap(inputs) || !inputs.portRange) return null;
@@ -31416,7 +32398,7 @@ function stageUrlForPort(port) {
31416
32398
  return `http://127.0.0.1:${port}/`;
31417
32399
  }
31418
32400
  function decideStage(inputs) {
31419
- const { registry: registry2, hasCompose, hasEnvExample } = inputs;
32401
+ const { registry: registry2, hasCompose, hasEnvExample, repo } = inputs;
31420
32402
  const deriveInputs = {
31421
32403
  portRange: registry2.portRange,
31422
32404
  deployModel: registry2.deployModel,
@@ -31426,8 +32408,9 @@ function decideStage(inputs) {
31426
32408
  const derived = deriveStage(deriveInputs);
31427
32409
  if (derived) return { source: "derived", derived, registryError: registry2.error };
31428
32410
  const registryGap = registry2.error ? `Hub registry read failed (${registry2.error}) \u2014 cannot derive a default local stage` : null;
31429
- const gap = registryGap ?? deriveStageGap(deriveInputs) ?? "no registry-derived default available";
31430
- return { source: "none", gap, registryError: registry2.error };
32411
+ const gap = registryGap ?? deriveStageGap(deriveInputs, { repo }) ?? "no registry-derived default available";
32412
+ const recovery = !registry2.error && isCentralContainerModel(registry2.deployModel) && !registry2.portRange ? stagePortRangeRecovery(repo) : void 0;
32413
+ return { source: "none", gap, ...recovery ? { recovery } : {}, registryError: registry2.error };
31431
32414
  }
31432
32415
 
31433
32416
  // src/stage-live.ts
@@ -31637,14 +32620,17 @@ function registerStageCommands(program3) {
31637
32620
  }
31638
32621
  async function resolveStage() {
31639
32622
  const cfg = await loadConfig();
31640
- const read = await fetchProjectBySlugChecked(await repoSlug(), registryClientDeps(cfg)).catch((e) => ({ ok: false, error: e.message }));
32623
+ const slug = await repoSlug();
32624
+ const read = await fetchProjectBySlugChecked(slug, registryClientDeps(cfg)).catch((e) => ({ ok: false, error: e.message }));
31641
32625
  const project2 = read.ok ? read.project : null;
31642
32626
  const portRangeMeta = project2?.portRange ?? void 0;
31643
32627
  const portRange = portRangeMeta && typeof portRangeMeta.start === "number" && typeof portRangeMeta.end === "number" ? [portRangeMeta.start, portRangeMeta.end] : void 0;
32628
+ const repo = Array.isArray(project2?.repos) && typeof project2.repos[0] === "string" ? project2.repos[0] : slug;
31644
32629
  return decideStage({
31645
32630
  registry: { deployModel: project2?.deployModel, portRange, error: read.ok ? void 0 : read.error },
31646
- hasCompose: (0, import_node_fs36.existsSync)((0, import_node_path34.join)(process.cwd(), "docker-compose.yml")),
31647
- hasEnvExample: (0, import_node_fs36.existsSync)((0, import_node_path34.join)(process.cwd(), ".env.example"))
32631
+ hasCompose: (0, import_node_fs39.existsSync)((0, import_node_path36.join)(process.cwd(), "docker-compose.yml")),
32632
+ hasEnvExample: (0, import_node_fs39.existsSync)((0, import_node_path36.join)(process.cwd(), ".env.example")),
32633
+ repo
31648
32634
  });
31649
32635
  }
31650
32636
  async function fetchStageVaultEnvMerge() {
@@ -31689,7 +32675,16 @@ function registerStageCommands(program3) {
31689
32675
  }
31690
32676
  function stageStepsFor(res, stops = true) {
31691
32677
  if (res.source === "derived" && res.derived) return derivedStagePlan(res.derived, shellFor(), stops);
31692
- return [{ label: `no local stage to run \u2014 ${res.gap ?? "stage config gap"}` }];
32678
+ return [{ label: `no local stage to run \u2014 ${res.gap ?? "stage config gap"}`, ...res.recovery ? { command: res.recovery } : {} }];
32679
+ }
32680
+ function stageReceiptFields(res) {
32681
+ return {
32682
+ source: res.source,
32683
+ url: res.derived?.url,
32684
+ ...res.gap ? { gap: res.gap } : {},
32685
+ ...res.recovery ? { recovery: res.recovery } : {},
32686
+ ...res.registryError ? { registryError: res.registryError } : {}
32687
+ };
31693
32688
  }
31694
32689
  function reportedStageUrl(res, result) {
31695
32690
  if (!res.derived) return void 0;
@@ -31699,38 +32694,23 @@ function registerStageCommands(program3) {
31699
32694
  const cfg = await loadConfig();
31700
32695
  const reg = registryClientDeps(cfg);
31701
32696
  const slug = slugOf(repo);
31702
- const read = await fetchProjectBySlugChecked(slug, reg);
31703
- const decision = decidePortRange({ metaReadOk: read.ok, metaPortRange: read.ok ? metaPortRange(read.project) : null });
31704
- if (decision.action === "fail") {
31705
- return failGraceful(`stage port-range: ${decision.reason}${read.ok ? "" : ` (${read.error})`}`);
31706
- }
31707
- if (decision.action === "return") {
31708
- const [start2, end2] = decision.range;
31709
- printLine(o.json ? JSON.stringify({ repo, portRange: [start2, end2], source: "meta" }) : `${repo}: stage.portRange [${start2}, ${end2}]`);
32697
+ const assigned = await assignPersistedPortRange(repo, slug, reg, { cwd: process.cwd(), moduleDir: __dirname });
32698
+ if (!assigned.ok) return failGraceful(`stage port-range: ${assigned.error}`);
32699
+ const [start, end] = assigned.range;
32700
+ if (assigned.source === "meta") {
32701
+ printLine(o.json ? JSON.stringify({ repo, portRange: [start, end], source: "meta" }) : `${repo}: stage.portRange [${start}, ${end}]`);
31710
32702
  return;
31711
32703
  }
31712
- const infra = resolvePortRangeInfra(process.cwd(), __dirname);
31713
- if (!infra) {
31714
- return failGraceful(
31715
- `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`
31716
- );
31717
- }
31718
- const path2 = infra.registryPath;
31719
- const allocate = async (seed) => {
31720
- const { stdout } = await execFileP2("node", [infra.ddbScriptPath, String(seed)], { timeout: 15e3 });
31721
- const parsed = JSON.parse(stdout);
31722
- if (!Array.isArray(parsed.range) || parsed.range.length !== 2) throw new Error("port-ddb: no range in output");
31723
- return parsed.range;
31724
- };
31725
- const { range: [start, end], source } = await ensurePortRangeAtomic(repo, path2, allocate);
31726
- const write = await upsertProject(slug, { portRange: { start, end } }, reg);
31727
- if (!write.ok && source === "ddb") {
31728
- 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`);
31729
- }
31730
32704
  if (o.json) {
31731
- printLine(JSON.stringify({ repo, portRange: [start, end], source: "allocated", persisted: write.ok, ...write.ok ? {} : { persistError: write.error ?? `HTTP ${write.status}` } }));
32705
+ printLine(JSON.stringify({
32706
+ repo,
32707
+ portRange: [start, end],
32708
+ source: "allocated",
32709
+ persisted: assigned.persisted,
32710
+ ...assigned.persisted ? {} : { persistError: assigned.persistError }
32711
+ }));
31732
32712
  } else {
31733
- printLine(`${repo}: stage.portRange [${start}, ${end}]${write.ok ? "" : ` (META not persisted: ${write.error ?? `HTTP ${write.status}`})`}`);
32713
+ printLine(`${repo}: stage.portRange [${start}, ${end}]${assigned.persisted ? "" : ` (META not persisted: ${assigned.persistError})`}`);
31734
32714
  }
31735
32715
  });
31736
32716
  async function stageLiveTarget() {
@@ -31816,7 +32796,7 @@ function registerStageCommands(program3) {
31816
32796
  }
31817
32797
  }
31818
32798
  const steps = stageStepsFor(res);
31819
- if (o.json) return console.log(JSON.stringify({ command: "stage", source: res.source, url: res.derived?.url, registryError: res.registryError, steps }, null, 2));
32799
+ if (o.json) return console.log(JSON.stringify({ command: "stage", ...stageReceiptFields(res), steps }, null, 2));
31820
32800
  console.log(renderSteps("mmi-cli stage: dry-run plan", steps));
31821
32801
  });
31822
32802
  stage.command("stop").description("stop the previous local stage process recorded in tmp/stage/state.json").option("--json", "machine-readable output").option("--apply", "kill the recorded process tree and remove the state file").action(async () => {
@@ -31837,7 +32817,7 @@ function registerStageCommands(program3) {
31837
32817
  const res = await resolveStage();
31838
32818
  if (!o.apply) {
31839
32819
  const steps = stageStepsFor(res, false);
31840
- if (o.json) return printLine(JSON.stringify({ command: "stage start", source: res.source, url: res.derived?.url, registryError: res.registryError, steps }, null, 2));
32820
+ if (o.json) return printLine(JSON.stringify({ command: "stage start", ...stageReceiptFields(res), steps }, null, 2));
31841
32821
  return printLine(renderSteps("mmi-cli stage start: dry-run plan", steps));
31842
32822
  }
31843
32823
  if (res.source === "none") return failGraceful(`stage start: ${res.gap}`);
@@ -31870,7 +32850,7 @@ function registerStageCommands(program3) {
31870
32850
  const res = await resolveStage();
31871
32851
  if (!o.apply) {
31872
32852
  const steps = stageStepsFor(res);
31873
- if (o.json) return printLine(JSON.stringify({ command: "stage run", source: res.source, url: res.derived?.url, registryError: res.registryError, steps }, null, 2));
32853
+ if (o.json) return printLine(JSON.stringify({ command: "stage run", ...stageReceiptFields(res), steps }, null, 2));
31874
32854
  return printLine(renderSteps("mmi-cli stage run: dry-run plan", steps));
31875
32855
  }
31876
32856
  if (res.source === "none") return failGraceful(`stage run: ${res.gap}`);
@@ -31902,9 +32882,9 @@ function registerStageCommands(program3) {
31902
32882
  }
31903
32883
 
31904
32884
  // src/merge-cleanup.ts
31905
- var import_node_fs37 = require("node:fs");
31906
- var import_node_path35 = require("node:path");
31907
- var import_node_os16 = require("node:os");
32885
+ var import_node_fs40 = require("node:fs");
32886
+ var import_node_path37 = require("node:path");
32887
+ var import_node_os18 = require("node:os");
31908
32888
  init_cli_shared();
31909
32889
 
31910
32890
  // src/config-load.ts
@@ -32181,13 +33161,13 @@ async function composeOverrideBodyFile(prNumber, repoArgs, gh) {
32181
33161
  const commits = JSON.parse(raw).commits ?? [];
32182
33162
  const body = squashBodyWithOverride(commits.map((c) => ({ headline: c.messageHeadline ?? "", body: c.messageBody ?? "" })), process.cwd());
32183
33163
  if (!body) return void 0;
32184
- const dir = (0, import_node_fs37.mkdtempSync)((0, import_node_path35.join)((0, import_node_os16.tmpdir)(), "mmi-squash-body-"));
32185
- const path2 = (0, import_node_path35.join)(dir, "body.txt");
32186
- (0, import_node_fs37.writeFileSync)(path2, `${body}
33164
+ const dir = (0, import_node_fs40.mkdtempSync)((0, import_node_path37.join)((0, import_node_os18.tmpdir)(), "mmi-squash-body-"));
33165
+ const path2 = (0, import_node_path37.join)(dir, "body.txt");
33166
+ (0, import_node_fs40.writeFileSync)(path2, `${body}
32187
33167
  `, "utf8");
32188
33168
  return { path: path2, cleanup: () => {
32189
33169
  try {
32190
- (0, import_node_fs37.rmSync)(dir, { recursive: true, force: true });
33170
+ (0, import_node_fs40.rmSync)(dir, { recursive: true, force: true });
32191
33171
  } catch {
32192
33172
  }
32193
33173
  } };
@@ -32394,7 +33374,9 @@ function registerBoardCommands(program3) {
32394
33374
  "Pass raw issue numbers/refs, not URLs.",
32395
33375
  "Claim already assigns and moves Status to In Progress, so do not also board move it.",
32396
33376
  "Multiple refs are handled as a batch and return per-item results.",
32397
- "--check is the live gate read (it calls GitHub); --dry-run only echoes the parsed argv plan."
33377
+ "--check is the live gate read (it calls GitHub); --dry-run only echoes the parsed argv plan.",
33378
+ // #5552: agents guess `oracle issue claim`; that route does not exist — board claim is the only write.
33379
+ "Never run `oracle issue claim` \u2014 claims are board mutations; only `oracle board claim <ref>` is valid."
32398
33380
  ]);
32399
33381
  board.command("show <issue>").description("print one board item (status, assignees, type, url) with its body and comments").option("--json", "machine-readable output").option("--repo <owner/repo>", "current repo for local issue numbers (defaults to git origin)").option("--allow-partial", "return the item even if its body/comments fetch fails").action(async (issueRef, o) => {
32400
33382
  try {
@@ -32683,18 +33665,33 @@ var PR_SNAPSHOT_READ_RETRIES = 3;
32683
33665
  var PR_SNAPSHOT_READ_DELAY_MS = 2e3;
32684
33666
  async function readRestPrSnapshotWithRetry(prNumber, repo, gh = defaultGhApi, options) {
32685
33667
  const retries = options?.retries ?? PR_SNAPSHOT_READ_RETRIES;
32686
- const delayMs = options?.delayMs ?? PR_SNAPSHOT_READ_DELAY_MS;
32687
- const sleep2 = options?.sleep ?? ((ms) => new Promise((resolve5) => setTimeout(resolve5, ms)));
33668
+ const untilMs = options?.retryTransientUntilMs;
33669
+ const delayMs = options?.delayMs ?? (untilMs !== void 0 ? PR_CHECKS_POLL_MS : PR_SNAPSHOT_READ_DELAY_MS);
33670
+ const sleep2 = options?.sleep ?? ((ms) => new Promise((resolve6) => setTimeout(resolve6, ms)));
33671
+ const now = options?.now ?? (() => Date.now());
32688
33672
  let lastError = "no attempt completed";
32689
- for (let attempt = 0; attempt < retries; attempt++) {
33673
+ let attempt = 0;
33674
+ for (; ; ) {
32690
33675
  try {
32691
33676
  return { state: "ok", snapshot: await fetchRestPrSnapshot(prNumber, repo, gh) };
32692
33677
  } catch (e) {
32693
33678
  lastError = readErrorText(e);
33679
+ attempt += 1;
33680
+ const retryable = isRetryableGitHubWaitReadError(e);
33681
+ if (!retryable) {
33682
+ return { state: "failed", error: `pulls read failed for #${prNumber} on ${repo}: ${lastError}` };
33683
+ }
33684
+ const canRetry = untilMs !== void 0 ? now() + delayMs < untilMs : attempt < retries;
33685
+ if (!canRetry) {
33686
+ return {
33687
+ state: "failed",
33688
+ error: `pulls read failed for #${prNumber} on ${repo} after ${attempt} attempts: ${lastError}`
33689
+ };
33690
+ }
33691
+ options?.onTransientRetry?.(lastError, attempt);
33692
+ await sleep2(delayMs);
32694
33693
  }
32695
- if (attempt < retries - 1) await sleep2(delayMs);
32696
33694
  }
32697
- return { state: "failed", error: `pulls read failed for #${prNumber} on ${repo} after ${retries} attempts: ${lastError}` };
32698
33695
  }
32699
33696
  async function fetchRestClosingGuardPayload(prNumber, repo, gh = defaultGhApi) {
32700
33697
  const pr2 = JSON.parse(await gh([`repos/${repo}/pulls/${prNumber}`]));
@@ -32984,8 +33981,8 @@ async function checkDocsIndexAtHead(opts, deps) {
32984
33981
  }
32985
33982
 
32986
33983
  // src/issue-commands.ts
32987
- var import_node_fs38 = require("node:fs");
32988
- var import_node_crypto14 = require("node:crypto");
33984
+ var import_node_fs41 = require("node:fs");
33985
+ var import_node_crypto16 = require("node:crypto");
32989
33986
  init_cli_shared();
32990
33987
  init_clean_exit();
32991
33988
  init_error_codes();
@@ -33177,7 +34174,7 @@ async function editIssue(client, options, deps = {}) {
33177
34174
  const url = `https://github.com/${repo}/issues/${parsed.number}`;
33178
34175
  const patch = {};
33179
34176
  let bodyChanged = false;
33180
- const textDeps = () => deps.textDeps ?? { readFile: (p, e) => Promise.resolve((0, import_node_fs38.readFileSync)(p, e)), readStdin: () => Promise.resolve("") };
34177
+ const textDeps = () => deps.textDeps ?? { readFile: (p, e) => Promise.resolve((0, import_node_fs41.readFileSync)(p, e)), readStdin: () => Promise.resolve("") };
33181
34178
  if (options.titleFile !== void 0) {
33182
34179
  patch.title = await resolveIssueTitle({ title: options.title, titleFile: options.titleFile }, textDeps());
33183
34180
  } else if (options.title !== void 0) {
@@ -33458,7 +34455,7 @@ function rowIdempotencyKey(batchKey, spec) {
33458
34455
  const identity = `${spec.type}
33459
34456
  ${spec.title.trim()}
33460
34457
  ${spec.body ?? ""}`;
33461
- const hash = (0, import_node_crypto14.createHash)("sha256").update(identity).digest("hex").slice(0, 16);
34458
+ const hash = (0, import_node_crypto16.createHash)("sha256").update(identity).digest("hex").slice(0, 16);
33462
34459
  return `${batchKey}:${hash}`;
33463
34460
  }
33464
34461
  var BATCH_SPEC_KEYS = /* @__PURE__ */ new Set(["type", "title", "body", "priority", "labels", "label", "parent", "repo", "surface"]);
@@ -33794,7 +34791,7 @@ function extendCreateCommand(issue2, batchAttach) {
33794
34791
  if (opts.batch) {
33795
34792
  let specs;
33796
34793
  try {
33797
- const raw = (0, import_node_fs38.readFileSync)(opts.batch, "utf8");
34794
+ const raw = (0, import_node_fs41.readFileSync)(opts.batch, "utf8");
33798
34795
  specs = JSON.parse(raw);
33799
34796
  if (!Array.isArray(specs)) throw new Error("batch file must contain a JSON array");
33800
34797
  } catch (e) {
@@ -33869,8 +34866,8 @@ ${lines}`, {
33869
34866
  }
33870
34867
 
33871
34868
  // src/train-commands.ts
33872
- var import_node_fs39 = require("node:fs");
33873
- var import_node_path36 = require("node:path");
34869
+ var import_node_fs42 = require("node:fs");
34870
+ var import_node_path38 = require("node:path");
33874
34871
  init_cli_shared();
33875
34872
  init_clean_exit();
33876
34873
  init_client_version();
@@ -33885,7 +34882,7 @@ function resolveReleaseBumpIntent(raw) {
33885
34882
  }
33886
34883
  function readRepoVersion() {
33887
34884
  try {
33888
- return JSON.parse((0, import_node_fs39.readFileSync)((0, import_node_path36.join)(process.cwd(), ".claude-plugin", "plugin.json"), "utf8")).version || void 0;
34885
+ return JSON.parse((0, import_node_fs42.readFileSync)((0, import_node_path38.join)(process.cwd(), ".claude-plugin", "plugin.json"), "utf8")).version || void 0;
33889
34886
  } catch {
33890
34887
  return void 0;
33891
34888
  }
@@ -34049,9 +35046,9 @@ function registerDeployCommands(program3) {
34049
35046
  init_cli_shared();
34050
35047
  init_github_client();
34051
35048
  init_cli_shared();
34052
- var import_node_fs40 = require("node:fs");
34053
- var import_node_os17 = require("node:os");
34054
- var import_node_path37 = require("node:path");
35049
+ var import_node_fs43 = require("node:fs");
35050
+ var import_node_os19 = require("node:os");
35051
+ var import_node_path39 = require("node:path");
34055
35052
  init_marketplace_autoupdate();
34056
35053
  var GC_GH_TIMEOUT_MS2 = 2e4;
34057
35054
  async function collectStatus() {
@@ -34170,7 +35167,7 @@ function onboardPluginGate(deps) {
34170
35167
  declared,
34171
35168
  settingsDeclared: readSettingsAutoUpdate(deps.readSettings(), MMI_MARKETPLACE_NAME)
34172
35169
  }).effective;
34173
- return autoUpdate ? { ok: false, detail: "background auto-update is ON \u2014 duplicate writer; run `mmi-hub update` outside Claude Code to disable it" } : { ok: true, detail: "background auto-update off; mmi-hub is the single release-gated writer" };
35170
+ return autoUpdate ? { ok: true, detail: "background auto-update on \u2014 org pin; thin catalog is release-gated" } : { ok: false, detail: "background auto-update is OFF \u2014 this machine will not pick up a new plugin release on its own; run `mmi-cli doctor` to pin it on" };
34174
35171
  }
34175
35172
  async function collectOnboardStatus(opts = {}) {
34176
35173
  const cfg = await loadConfig();
@@ -34252,10 +35249,10 @@ async function collectOnboardStatus(opts = {}) {
34252
35249
  else if (top) nextCommand = `mmi-cli oracle board claim ${top.number} # ${top.title}`;
34253
35250
  else nextCommand = "mmi-cli oracle board read \u2014 no claimable items found";
34254
35251
  }
34255
- const home = (0, import_node_os17.homedir)();
35252
+ const home = (0, import_node_os19.homedir)();
34256
35253
  const plugin = onboardPluginGate({
34257
- readKnown: () => readFileSyncSafe((0, import_node_path37.join)(home, ...KNOWN_MARKETPLACES_RELATIVE), import_node_fs40.readFileSync),
34258
- readSettings: () => readFileSyncSafe((0, import_node_path37.join)(home, ".claude", "settings.json"), import_node_fs40.readFileSync)
35254
+ readKnown: () => readFileSyncSafe((0, import_node_path39.join)(home, ...KNOWN_MARKETPLACES_RELATIVE), import_node_fs43.readFileSync),
35255
+ readSettings: () => readFileSyncSafe((0, import_node_path39.join)(home, ".claude", "settings.json"), import_node_fs43.readFileSync)
34259
35256
  });
34260
35257
  return { track, board, registry: registry2, secrets, plugin, estateCli, doors: opts.doors ?? [], nextCommand };
34261
35258
  }
@@ -34338,12 +35335,17 @@ var LOOP_PLAYBOOKS = {
34338
35335
  { label: "Orient in the current repository", command: "mmi-cli onboard" },
34339
35336
  { label: "Structure search: `find` is semantic default; `repo-index search` is hybrid. Cloud default (`--local` is checkout-only); verify repo/commit/path/lines and result retrieval metadata; status is runtime authority", command: "mmi-cli oracle find <q>" },
34340
35337
  { label: "Read the next board item", command: "mmi-cli oracle board read" },
35338
+ // #5552: claim is a board mutation only — never guess `oracle issue claim`. Ground unknown write
35339
+ // routes with `mmi-cli commands` / `mmi-cli explain` before invoking them.
35340
+ { label: "Claim board work (board mutation only \u2014 never `oracle issue claim`; ground unknown write routes via `commands` / `explain` first)", command: "mmi-cli oracle board claim <ref>" },
34341
35341
  { label: "Prepare the local workspace through the host surface" },
34342
35342
  { label: "Apply the repository test policy, then build the touched package", command: "mmi-cli tests policy --base origin/development && npm run build" },
34343
35343
  { label: "Publish the branch", command: "git push origin <branch>:<branch>" },
34344
35344
  { label: "Open the development-base PR", command: 'mmi-cli devops pr create --title "<title>" --body-file .jerv/PR_BODY.md --base development' },
34345
35345
  { label: "Wait for checks and land to development", command: "mmi-cli devops pr checks-wait <PR-number> && mmi-cli devops pr land <PR-number>" },
34346
- { label: "Release only after the gated train is authorized", command: "mmi-cli devops release --apply" }
35346
+ { label: "Release only after the gated train is authorized", command: "mmi-cli devops release --apply" },
35347
+ // #5552: learning-tagged filings are cloud-agent owned — file and return to the current task.
35348
+ { label: "Learning reports are fire-and-forget (file, then return to the current task \u2014 never claim/poll/duplicate the learning issue)", command: 'mmi-cli learning report --title "<one-line>" --body "<what hurt>"' }
34347
35349
  ]
34348
35350
  },
34349
35351
  "start-work": {
@@ -34351,6 +35353,7 @@ var LOOP_PLAYBOOKS = {
34351
35353
  steps: [
34352
35354
  { label: "Orient in the current repository", command: "mmi-cli onboard" },
34353
35355
  { label: "Read the board item", command: "mmi-cli oracle board show <issue-number>" },
35356
+ { label: "Claim the item (board mutation only \u2014 never `oracle issue claim`)", command: "mmi-cli oracle board claim <issue-number>" },
34354
35357
  { label: "Prepare the local workspace through the host surface" },
34355
35358
  { label: "Start a local stage (deployable repos)", command: "mmi-cli stage run --apply" }
34356
35359
  ]
@@ -34968,8 +35971,8 @@ function registerPrLifecycleCommands(program3) {
34968
35971
  }
34969
35972
 
34970
35973
  // src/post-merge-recon.ts
34971
- var import_node_fs41 = require("node:fs");
34972
- var import_node_path38 = require("node:path");
35974
+ var import_node_fs44 = require("node:fs");
35975
+ var import_node_path40 = require("node:path");
34973
35976
 
34974
35977
  // src/cross-repo-filing-issue.ts
34975
35978
  init_github_client();
@@ -35136,16 +36139,16 @@ function buildPostMergeReconRecovery(input) {
35136
36139
  }
35137
36140
  function writePostMergeReconRecovery(cwd, recovery) {
35138
36141
  const path2 = postMergeReconStatePath(cwd, recovery.repo, recovery.pr);
35139
- (0, import_node_fs41.mkdirSync)((0, import_node_path38.dirname)(path2), { recursive: true });
35140
- (0, import_node_fs41.writeFileSync)(path2, `${JSON.stringify(recovery, null, 2)}
36142
+ (0, import_node_fs44.mkdirSync)((0, import_node_path40.dirname)(path2), { recursive: true });
36143
+ (0, import_node_fs44.writeFileSync)(path2, `${JSON.stringify(recovery, null, 2)}
35141
36144
  `, "utf8");
35142
36145
  return path2;
35143
36146
  }
35144
36147
  function clearPostMergeReconRecovery(cwd, repo, pr2) {
35145
36148
  const path2 = postMergeReconStatePath(cwd, repo, pr2);
35146
- if (!(0, import_node_fs41.existsSync)(path2)) return;
36149
+ if (!(0, import_node_fs44.existsSync)(path2)) return;
35147
36150
  try {
35148
- (0, import_node_fs41.unlinkSync)(path2);
36151
+ (0, import_node_fs44.unlinkSync)(path2);
35149
36152
  } catch {
35150
36153
  }
35151
36154
  }
@@ -35568,6 +36571,7 @@ var surfaces_default = {
35568
36571
  targetPath: "packages/claude-plugin/scripts",
35569
36572
  include: [
35570
36573
  "pretooluse-shell-gates.mjs",
36574
+ "test-command-policy-core.mjs",
35571
36575
  "vault-edit-gate.mjs",
35572
36576
  "deny-gate-crash.mjs",
35573
36577
  "secret-echo-lint.mjs",
@@ -35694,6 +36698,7 @@ var surfaces_default = {
35694
36698
  targetPath: "packages/codex-plugin/scripts",
35695
36699
  include: [
35696
36700
  "pretooluse-shell-gates.mjs",
36701
+ "test-command-policy-core.mjs",
35697
36702
  "vault-edit-gate.mjs",
35698
36703
  "deny-gate-crash.mjs",
35699
36704
  "secret-echo-lint.mjs",
@@ -35812,6 +36817,7 @@ var surfaces_default = {
35812
36817
  targetPath: "packages/kimi-plugin/scripts",
35813
36818
  include: [
35814
36819
  "pretooluse-shell-gates.mjs",
36820
+ "test-command-policy-core.mjs",
35815
36821
  "vault-edit-gate.mjs",
35816
36822
  "deny-gate-crash.mjs",
35817
36823
  "secret-echo-lint.mjs",
@@ -35938,6 +36944,7 @@ var surfaces_default = {
35938
36944
  targetPath: "packages/cursor-plugin/scripts",
35939
36945
  include: [
35940
36946
  "pretooluse-shell-gates.mjs",
36947
+ "test-command-policy-core.mjs",
35941
36948
  "vault-edit-gate.mjs",
35942
36949
  "deny-gate-crash.mjs",
35943
36950
  "secret-echo-lint.mjs",
@@ -36060,6 +37067,7 @@ var surfaces_default = {
36060
37067
  targetPath: ".kilo-plugin/scripts",
36061
37068
  include: [
36062
37069
  "pretooluse-shell-gates.mjs",
37070
+ "test-command-policy-core.mjs",
36063
37071
  "vault-edit-gate.mjs",
36064
37072
  "deny-gate-crash.mjs",
36065
37073
  "secret-echo-lint.mjs",
@@ -36169,6 +37177,7 @@ var surfaces_default = {
36169
37177
  targetPath: ".pi-plugin/scripts",
36170
37178
  include: [
36171
37179
  "pretooluse-shell-gates.mjs",
37180
+ "test-command-policy-core.mjs",
36172
37181
  "vault-edit-gate.mjs",
36173
37182
  "deny-gate-crash.mjs",
36174
37183
  "secret-echo-lint.mjs",
@@ -36268,6 +37277,7 @@ var surfaces_default = {
36268
37277
  targetPath: "packages/hermes-plugin/scripts",
36269
37278
  include: [
36270
37279
  "pretooluse-shell-gates.mjs",
37280
+ "test-command-policy-core.mjs",
36271
37281
  "vault-edit-gate.mjs",
36272
37282
  "deny-gate-crash.mjs",
36273
37283
  "secret-echo-lint.mjs",
@@ -36957,7 +37967,8 @@ function diagnoseSurface(evidence) {
36957
37967
  const base = {
36958
37968
  descriptor: evidence.descriptor,
36959
37969
  ...evidence.installedVersion ? { installedVersion: evidence.installedVersion } : {},
36960
- ...evidence.releasedVersion ? { releasedVersion: evidence.releasedVersion } : {}
37970
+ ...evidence.releasedVersion ? { releasedVersion: evidence.releasedVersion } : {},
37971
+ ...evidence.receipt ? { receipt: evidence.receipt } : {}
36961
37972
  };
36962
37973
  if (!evidence.applicable) return { ...base, state: "skipped" };
36963
37974
  if (evidence.repair?.attempted && !evidence.repair.ok) {
@@ -37047,6 +38058,7 @@ function buildSurfaceDoctorCheck(diagnosis) {
37047
38058
  `install: ${descriptor.installMechanism} (${descriptor.installLocator})`,
37048
38059
  `repair owner: ${descriptor.repairOwner}`,
37049
38060
  `artifacts: ${descriptor.artifactIds.join(", ")}`,
38061
+ ...diagnosis.receipt ? [`receipt: ${diagnosis.receipt}`] : [],
37050
38062
  ...diagnosis.repairDetail ? [`heal: ${diagnosis.repairDetail}`] : []
37051
38063
  ]
37052
38064
  };
@@ -38262,18 +39274,18 @@ function parseOriginRepo(remoteUrl) {
38262
39274
  return `${match[1]}/${match[2]}`;
38263
39275
  }
38264
39276
  function ghHostsConfigPath(env, platform2) {
38265
- const sep3 = platform2 === "win32" ? "\\" : "/";
38266
- const join35 = (...parts) => parts.join(sep3);
39277
+ const sep4 = platform2 === "win32" ? "\\" : "/";
39278
+ const join36 = (...parts) => parts.join(sep4);
38267
39279
  const explicit = env.GH_CONFIG_DIR?.trim();
38268
- if (explicit) return join35(explicit, "hosts.yml");
39280
+ if (explicit) return join36(explicit, "hosts.yml");
38269
39281
  if (platform2 === "win32") {
38270
39282
  const appData = (env.AppData ?? env.APPDATA)?.trim();
38271
- return appData ? join35(appData, "GitHub CLI", "hosts.yml") : void 0;
39283
+ return appData ? join36(appData, "GitHub CLI", "hosts.yml") : void 0;
38272
39284
  }
38273
39285
  const xdg = env.XDG_CONFIG_HOME?.trim();
38274
- if (xdg) return join35(xdg, "gh", "hosts.yml");
39286
+ if (xdg) return join36(xdg, "gh", "hosts.yml");
38275
39287
  const home = env.HOME?.trim();
38276
- return home ? join35(home, ".config", "gh", "hosts.yml") : void 0;
39288
+ return home ? join36(home, ".config", "gh", "hosts.yml") : void 0;
38277
39289
  }
38278
39290
  function parseGhHostsAccounts(yaml, host = "github.com") {
38279
39291
  let hostIndent = null;
@@ -38323,19 +39335,41 @@ function ghAccountCaveat(announcedLogin, accounts) {
38323
39335
  }
38324
39336
 
38325
39337
  // src/doctor-io.ts
38326
- var import_node_fs42 = require("node:fs");
38327
- var import_node_os18 = require("node:os");
38328
- var import_node_path39 = require("node:path");
38329
- var import_node_child_process19 = require("node:child_process");
39338
+ var import_node_fs45 = require("node:fs");
39339
+ var import_node_os20 = require("node:os");
39340
+ var import_node_path41 = require("node:path");
39341
+ var import_node_child_process20 = require("node:child_process");
38330
39342
  var import_node_util8 = require("node:util");
38331
39343
  init_version_lag();
38332
39344
  init_plugin_guard_io();
38333
- var execFileP6 = (0, import_node_util8.promisify)(import_node_child_process19.execFile);
39345
+
39346
+ // src/discard-sink.ts
39347
+ function nodeDiscardSinkPath(platform2 = process.platform) {
39348
+ return platform2 === "win32" ? "\\\\.\\NUL" : "/dev/null";
39349
+ }
39350
+
39351
+ // src/doctor-io.ts
39352
+ var execFileP6 = (0, import_node_util8.promisify)(import_node_child_process20.execFile);
39353
+ function execFileCapture(file, args, opts = {}) {
39354
+ const sink = nodeDiscardSinkPath();
39355
+ const inFd = (0, import_node_fs45.openSync)(sink, "r");
39356
+ const errFd = (0, import_node_fs45.openSync)(sink, "w");
39357
+ try {
39358
+ return (0, import_node_child_process20.execFileSync)(file, args, {
39359
+ ...opts,
39360
+ encoding: "utf8",
39361
+ stdio: [inFd, "pipe", errFd]
39362
+ });
39363
+ } finally {
39364
+ (0, import_node_fs45.closeSync)(inFd);
39365
+ (0, import_node_fs45.closeSync)(errFd);
39366
+ }
39367
+ }
38334
39368
  var MMI_PLUGIN_ID2 = "mmi@mutmutco";
38335
39369
  function installedClaudePluginVersion() {
38336
39370
  try {
38337
39371
  const file = JSON.parse(
38338
- (0, import_node_fs42.readFileSync)((0, import_node_path39.join)((0, import_node_os18.homedir)(), ".claude", "plugins", "installed_plugins.json"), "utf8")
39372
+ (0, import_node_fs45.readFileSync)((0, import_node_path41.join)((0, import_node_os20.homedir)(), ".claude", "plugins", "installed_plugins.json"), "utf8")
38339
39373
  );
38340
39374
  const versions = (file.plugins?.[MMI_PLUGIN_ID2] ?? []).map((r) => r.version).filter((v) => Boolean(v));
38341
39375
  if (versions.length === 0) return void 0;
@@ -38346,7 +39380,7 @@ function installedClaudePluginVersion() {
38346
39380
  }
38347
39381
  function manifestVersion(path2) {
38348
39382
  try {
38349
- const manifest = JSON.parse((0, import_node_fs42.readFileSync)(path2, "utf8"));
39383
+ const manifest = JSON.parse((0, import_node_fs45.readFileSync)(path2, "utf8"));
38350
39384
  return typeof manifest.version === "string" && manifest.version.trim() ? manifest.version.trim() : void 0;
38351
39385
  } catch {
38352
39386
  return void 0;
@@ -38355,12 +39389,12 @@ function manifestVersion(path2) {
38355
39389
  function readHermesPluginEvidence(env = process.env) {
38356
39390
  const host = hermesConfigRoot(env);
38357
39391
  const root = hermesPluginRoot(env);
38358
- const installRecordPresent = (0, import_node_fs42.existsSync)(root);
38359
- const manifestPath = (0, import_node_path39.join)(root, "plugin.yaml");
39392
+ const installRecordPresent = (0, import_node_fs45.existsSync)(root);
39393
+ const manifestPath = (0, import_node_path41.join)(root, "plugin.yaml");
38360
39394
  let installedVersion;
38361
39395
  let manifest = "missing";
38362
39396
  try {
38363
- const text = (0, import_node_fs42.readFileSync)(manifestPath, "utf8");
39397
+ const text = (0, import_node_fs45.readFileSync)(manifestPath, "utf8");
38364
39398
  let version;
38365
39399
  try {
38366
39400
  const parsed = JSON.parse(text).version;
@@ -38374,20 +39408,20 @@ function readHermesPluginEvidence(env = process.env) {
38374
39408
  if (version) {
38375
39409
  installedVersion = version;
38376
39410
  manifest = "valid";
38377
- } else if ((0, import_node_fs42.existsSync)(manifestPath)) manifest = "invalid";
39411
+ } else if ((0, import_node_fs45.existsSync)(manifestPath)) manifest = "invalid";
38378
39412
  } catch {
38379
- if ((0, import_node_fs42.existsSync)(manifestPath)) manifest = "invalid";
39413
+ if ((0, import_node_fs45.existsSync)(manifestPath)) manifest = "invalid";
38380
39414
  }
38381
39415
  let skills = false;
38382
39416
  try {
38383
- skills = (0, import_node_fs42.existsSync)((0, import_node_path39.join)(root, "skills")) && (0, import_node_fs42.statSync)((0, import_node_path39.join)(root, "skills")).isDirectory();
39417
+ skills = (0, import_node_fs45.existsSync)((0, import_node_path41.join)(root, "skills")) && (0, import_node_fs45.statSync)((0, import_node_path41.join)(root, "skills")).isDirectory();
38384
39418
  } catch {
38385
39419
  }
38386
39420
  return {
38387
- hostPresent: (0, import_node_fs42.existsSync)(host),
39421
+ hostPresent: (0, import_node_fs45.existsSync)(host),
38388
39422
  installRecordPresent,
38389
39423
  manifest,
38390
- payloadPresent: (0, import_node_fs42.existsSync)((0, import_node_path39.join)(root, "__init__.py")) && skills && manifest === "valid",
39424
+ payloadPresent: (0, import_node_fs45.existsSync)((0, import_node_path41.join)(root, "__init__.py")) && skills && manifest === "valid",
38391
39425
  ...installedVersion ? { installedVersion } : {}
38392
39426
  };
38393
39427
  }
@@ -38395,7 +39429,7 @@ function installedSurfacePluginVersion(surface) {
38395
39429
  const token = surfaceToken(surface);
38396
39430
  if (token === "kilo") {
38397
39431
  try {
38398
- const stamp = (0, import_node_fs42.readFileSync)((0, import_node_path39.join)((0, import_node_os18.homedir)(), ".kilo", ".mmi-kilo-version"), "utf8").trim();
39432
+ const stamp = (0, import_node_fs45.readFileSync)((0, import_node_path41.join)((0, import_node_os20.homedir)(), ".kilo", ".mmi-kilo-version"), "utf8").trim();
38399
39433
  return stamp || void 0;
38400
39434
  } catch {
38401
39435
  return void 0;
@@ -38403,25 +39437,21 @@ function installedSurfacePluginVersion(surface) {
38403
39437
  }
38404
39438
  if (token === "hermes") return readHermesPluginEvidence().installedVersion;
38405
39439
  if (token === "cursor") {
38406
- return manifestVersion((0, import_node_path39.join)(cursorLocalPluginRoot(), ".cursor-plugin", "plugin.json"));
39440
+ return manifestVersion((0, import_node_path41.join)(cursorLocalPluginRoot(), ".cursor-plugin", "plugin.json"));
38407
39441
  }
38408
39442
  if (token === "jervcode") {
38409
39443
  return installedJervCodePackageVersion();
38410
39444
  }
38411
39445
  if (token === "kimi") {
38412
- return manifestVersion((0, import_node_path39.join)(surfaceConfigRoot(surface), "plugins", "managed", "mmi", ".kimi-plugin", "plugin.json"));
39446
+ return manifestVersion((0, import_node_path41.join)(surfaceConfigRoot(surface), "plugins", "managed", "mmi", ".kimi-plugin", "plugin.json"));
38413
39447
  }
38414
39448
  if (token === "claude") return installedClaudePluginVersion();
38415
39449
  if (token !== "codex") return void 0;
38416
39450
  try {
38417
- const raw = process.platform === "win32" ? (0, import_node_child_process19.execFileSync)("cmd.exe", ["/c", "codex", "plugin", "list", "--json"], {
38418
- encoding: "utf8",
38419
- stdio: ["ignore", "pipe", "ignore"],
39451
+ const raw = process.platform === "win32" ? execFileCapture("cmd.exe", ["/c", "codex", "plugin", "list", "--json"], {
38420
39452
  timeout: 15e3,
38421
39453
  windowsHide: true
38422
- }) : (0, import_node_child_process19.execFileSync)("codex", ["plugin", "list", "--json"], {
38423
- encoding: "utf8",
38424
- stdio: ["ignore", "pipe", "ignore"],
39454
+ }) : execFileCapture("codex", ["plugin", "list", "--json"], {
38425
39455
  timeout: 15e3,
38426
39456
  windowsHide: true
38427
39457
  });
@@ -38437,7 +39467,7 @@ function installedActivePluginVersion(surface = detectSurface(process.env)) {
38437
39467
  }
38438
39468
  function worktreeRootSync() {
38439
39469
  try {
38440
- const out = (0, import_node_child_process19.execFileSync)("git", ["rev-parse", "--show-toplevel"], { windowsHide: true, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
39470
+ const out = execFileCapture("git", ["rev-parse", "--show-toplevel"], { windowsHide: true });
38441
39471
  let root = out.endsWith("\n") ? out.slice(0, -1) : out;
38442
39472
  if (process.platform === "win32" && root.endsWith("\r")) root = root.slice(0, -1);
38443
39473
  return root || null;
@@ -38447,13 +39477,13 @@ function worktreeRootSync() {
38447
39477
  }
38448
39478
  var gitignorePath = () => {
38449
39479
  const root = worktreeRootSync();
38450
- return root === null ? null : (0, import_node_path39.join)(root, ".gitignore");
39480
+ return root === null ? null : (0, import_node_path41.join)(root, ".gitignore");
38451
39481
  };
38452
39482
  function readGitignore() {
38453
39483
  const path2 = gitignorePath();
38454
39484
  if (path2 === null) return null;
38455
39485
  try {
38456
- return (0, import_node_fs42.readFileSync)(path2, "utf8");
39486
+ return (0, import_node_fs45.readFileSync)(path2, "utf8");
38457
39487
  } catch {
38458
39488
  return null;
38459
39489
  }
@@ -38462,19 +39492,17 @@ function writeGitignore(content) {
38462
39492
  const path2 = gitignorePath();
38463
39493
  if (path2 === null) return false;
38464
39494
  try {
38465
- (0, import_node_fs42.writeFileSync)(path2, content, "utf8");
39495
+ (0, import_node_fs45.writeFileSync)(path2, content, "utf8");
38466
39496
  return true;
38467
39497
  } catch {
38468
39498
  return false;
38469
39499
  }
38470
39500
  }
38471
39501
  function lineEndingState(root) {
38472
- const attributesPresent = (0, import_node_fs42.existsSync)((0, import_node_path39.join)(root, ".gitattributes"));
39502
+ const attributesPresent = (0, import_node_fs45.existsSync)((0, import_node_path41.join)(root, ".gitattributes"));
38473
39503
  try {
38474
- const output = (0, import_node_child_process19.execFileSync)("git", ["-C", root, "ls-files", "--eol", "--", ":(glob)**/*.sh"], {
38475
- windowsHide: true,
38476
- encoding: "utf8",
38477
- stdio: ["ignore", "pipe", "ignore"]
39504
+ const output = execFileCapture("git", ["-C", root, "ls-files", "--eol", "--", ":(glob)**/*.sh"], {
39505
+ windowsHide: true
38478
39506
  });
38479
39507
  const crlfShellScripts = output.split(/\r?\n/).filter((line) => line.startsWith("i/crlf ")).map((line) => line.slice(line.indexOf(" ") + 1)).filter(Boolean);
38480
39508
  return { attributesPresent, crlfShellScripts };
@@ -38538,8 +39566,8 @@ ${r.stderr ?? ""}`).catch(() => "");
38538
39566
  function ghMultiAccountCaveat(announcedLogin) {
38539
39567
  try {
38540
39568
  const hostsPath = ghHostsConfigPath(process.env, process.platform);
38541
- if (!hostsPath || !(0, import_node_fs43.existsSync)(hostsPath)) return void 0;
38542
- return ghAccountCaveat(announcedLogin, parseGhHostsAccounts((0, import_node_fs43.readFileSync)(hostsPath, "utf8")));
39569
+ if (!hostsPath || !(0, import_node_fs46.existsSync)(hostsPath)) return void 0;
39570
+ return ghAccountCaveat(announcedLogin, parseGhHostsAccounts((0, import_node_fs46.readFileSync)(hostsPath, "utf8")));
38543
39571
  } catch {
38544
39572
  return void 0;
38545
39573
  }
@@ -38547,12 +39575,12 @@ function ghMultiAccountCaveat(announcedLogin) {
38547
39575
  var ENV_HEAL_LOCK_STALE_MS = 10 * 6e4;
38548
39576
  var ENV_HEAL_LOCK_MAX_WAIT_MS = 2 * 6e4;
38549
39577
  function envHealLockPath(home) {
38550
- return (0, import_node_path40.join)(home, ".claude", "plugins", ".mmi-env-heal.lock");
39578
+ return (0, import_node_path42.join)(home, ".claude", "plugins", ".mmi-env-heal.lock");
38551
39579
  }
38552
39580
  async function withEnvHealLock(what, run) {
38553
39581
  try {
38554
39582
  return await withFileLock(
38555
- envHealLockPath((0, import_node_os19.homedir)()),
39583
+ envHealLockPath((0, import_node_os21.homedir)()),
38556
39584
  { staleMs: ENV_HEAL_LOCK_STALE_MS, maxWaitMs: ENV_HEAL_LOCK_MAX_WAIT_MS, label: "mmi env-heal lock" },
38557
39585
  run
38558
39586
  );
@@ -38594,17 +39622,19 @@ function mmiDoctorDeps(opts = {}) {
38594
39622
  const descriptor = doctorSurface(token);
38595
39623
  const snapshot = snapshotPluginGuardInput(runtimeSurface, isOrgRepo);
38596
39624
  const hermes = token === "hermes" ? readHermesPluginEvidence(process.env) : void 0;
39625
+ const kimi = token === "kimi" ? kimiPluginHostEvidence(surfaceConfigRoot("kimi")) : void 0;
38597
39626
  const installedVersion = hermes?.installedVersion ?? installedSurfacePluginVersion(runtimeSurface);
38598
39627
  surfaceEvidence = {
38599
39628
  descriptor,
38600
39629
  // Hermes' root is the host evidence: a configured but uninstalled MMI tree is missing, while an
38601
39630
  // absent root is skipped. Other mature surfaces retain their established org/install applicability.
38602
39631
  applicable: hermes ? hermes.hostPresent : isOrgRepo || snapshot.installRecordPresent || snapshot.pluginCachePresent,
38603
- installRecordPresent: hermes?.installRecordPresent ?? snapshot.installRecordPresent,
39632
+ installRecordPresent: hermes?.installRecordPresent ?? (kimi ? kimi.registration === "healthy" : snapshot.installRecordPresent),
38604
39633
  deliveryPresent: hermes ? hermes.installRecordPresent : snapshot.marketplaceClonePresent,
38605
- payloadPresent: hermes?.payloadPresent ?? snapshot.pluginCachePresent,
39634
+ payloadPresent: hermes?.payloadPresent ?? (kimi ? kimi.healthy : snapshot.pluginCachePresent),
38606
39635
  manifest: hermes?.manifest ?? (installedVersion ? "valid" : snapshot.installRecordPresent ? "invalid" : "missing"),
38607
39636
  guardState: buildPluginGuardDecision(snapshot).state,
39637
+ ...kimi?.receipt ? { receipt: kimi.receipt } : {},
38608
39638
  ...installedVersion ? { installedVersion } : {}
38609
39639
  };
38610
39640
  return surfaceEvidence;
@@ -38647,7 +39677,7 @@ function mmiDoctorDeps(opts = {}) {
38647
39677
  const configRoot = surfaceConfigRoot(surface);
38648
39678
  const running = surface === "codex" ? installedActivePluginVersion(surface) : runningPluginVersion(process.env, resolveClientVersion());
38649
39679
  const plan = buildPluginCachePlan(
38650
- (0, import_node_os19.homedir)(),
39680
+ (0, import_node_os21.homedir)(),
38651
39681
  running,
38652
39682
  pluginCacheFsDeps(configRoot, () => 0),
38653
39683
  { configRoot, includeStaging: surface !== "codex" }
@@ -38675,14 +39705,14 @@ function mmiDoctorDeps(opts = {}) {
38675
39705
  const running = surface === "codex" ? installedActivePluginVersion(surface) : runningPluginVersion(process.env, resolveClientVersion());
38676
39706
  const installed = installedActivePluginVersion(surface);
38677
39707
  const plan = buildPluginCachePlan(
38678
- (0, import_node_os19.homedir)(),
39708
+ (0, import_node_os21.homedir)(),
38679
39709
  running,
38680
39710
  pluginCacheFsDeps(configRoot, () => 0),
38681
39711
  { configRoot, includeStaging: surface !== "codex", installedVersion: installed }
38682
39712
  );
38683
39713
  const result = applyPluginCachePlan(
38684
39714
  plan,
38685
- (p) => (0, import_node_fs43.rmSync)(p, { recursive: true }),
39715
+ (p) => (0, import_node_fs46.rmSync)(p, { recursive: true }),
38686
39716
  stagingApplyFsGuard(configRoot)
38687
39717
  );
38688
39718
  return {
@@ -38720,7 +39750,7 @@ function mmiDoctorDeps(opts = {}) {
38720
39750
  // adopted the generated routing index (MMG-Unlive: `docs/Archive/**` only, no index, no gate.yml) would
38721
39751
  // get a permanent — demanding an artifact it never asked for.
38722
39752
  docsIndexState: (root) => {
38723
- if (!(0, import_node_fs43.existsSync)((0, import_node_path40.join)(root, DOCS_INDEX_PATH))) return void 0;
39753
+ if (!(0, import_node_fs46.existsSync)((0, import_node_path42.join)(root, DOCS_INDEX_PATH))) return void 0;
38724
39754
  const real = createDocsIndexDeps(root);
38725
39755
  let docs2;
38726
39756
  const listDocs = () => docs2 ??= real.listDocs();
@@ -38729,7 +39759,7 @@ function mmiDoctorDeps(opts = {}) {
38729
39759
  },
38730
39760
  // #4168: working-tree heal — write if drifted, then re-check. Commit remains the operator's step.
38731
39761
  healDocsIndex: (root) => {
38732
- if (!(0, import_node_fs43.existsSync)((0, import_node_path40.join)(root, DOCS_INDEX_PATH))) return { drift: false, docCount: 0 };
39762
+ if (!(0, import_node_fs46.existsSync)((0, import_node_path42.join)(root, DOCS_INDEX_PATH))) return { drift: false, docCount: 0 };
38733
39763
  const real = createDocsIndexDeps(root);
38734
39764
  let docs2;
38735
39765
  const listDocs = () => docs2 ??= real.listDocs();
@@ -38750,8 +39780,8 @@ function mmiDoctorDeps(opts = {}) {
38750
39780
  });
38751
39781
  const raced = await Promise.race([
38752
39782
  work.then((r) => ({ ...r, timedOut: false })),
38753
- new Promise((resolve5) => {
38754
- ceiling = setTimeout(() => resolve5({ timedOut: true, scanned: 0, findings: 0, fixed: 0, failed: 0 }), BOARD_DOCTOR_TIMEOUT_MS);
39783
+ new Promise((resolve6) => {
39784
+ ceiling = setTimeout(() => resolve6({ timedOut: true, scanned: 0, findings: 0, fixed: 0, failed: 0 }), BOARD_DOCTOR_TIMEOUT_MS);
38755
39785
  })
38756
39786
  ]);
38757
39787
  if (raced.timedOut) return { scanned: 0, findings: 0, fixed: 0, failed: 0, timedOut: true };
@@ -38784,8 +39814,8 @@ function mmiDoctorDeps(opts = {}) {
38784
39814
  incomplete: nb.incomplete,
38785
39815
  timedOut: false
38786
39816
  })),
38787
- new Promise((resolve5) => {
38788
- ceiling = setTimeout(() => resolve5({ driftLines: [], incomplete: [], timedOut: true }), SCHEDULES_DRIFT_TIMEOUT_MS);
39817
+ new Promise((resolve6) => {
39818
+ ceiling = setTimeout(() => resolve6({ driftLines: [], incomplete: [], timedOut: true }), SCHEDULES_DRIFT_TIMEOUT_MS);
38789
39819
  })
38790
39820
  ]);
38791
39821
  if (!raced.timedOut && raced.incomplete.length === 0) writeSchedulesDriftCache(cachePath, raced.driftLines);
@@ -38821,7 +39851,7 @@ function mmiDoctorDeps(opts = {}) {
38821
39851
  repoIndexCloudState: async (root) => {
38822
39852
  let localV4 = { state: "absent" };
38823
39853
  try {
38824
- const parsed = JSON.parse((0, import_node_fs43.readFileSync)(repoIndexV4StorePath(root), "utf8"));
39854
+ const parsed = JSON.parse((0, import_node_fs46.readFileSync)(repoIndexV4StorePath(root), "utf8"));
38825
39855
  const state = parsed.status?.state;
38826
39856
  if (parsed.schemaVersion === 4 && (state === "ready" || state === "degraded")) {
38827
39857
  const chunks = parsed.manifest?.chunks?.length ?? 0;
@@ -39114,19 +40144,19 @@ program2.hook("preAction", async (_thisCommand, actionCommand) => {
39114
40144
  });
39115
40145
  var rules = program2.command("rules").description("org-managed .gitignore delivery");
39116
40146
  rules.command("gitignore").option("--write", "upsert the managed block into .gitignore (default: check only, non-zero exit on drift)").option("--json", "machine-readable output").description("verify (or --write) this repo's org-managed .gitignore block matches the SSOT").action((opts) => {
39117
- const path2 = (0, import_node_path40.join)(process.cwd(), ".gitignore");
39118
- const current = (0, import_node_fs43.existsSync)(path2) ? (0, import_node_fs43.readFileSync)(path2, "utf8") : null;
40147
+ const path2 = (0, import_node_path42.join)(process.cwd(), ".gitignore");
40148
+ const current = (0, import_node_fs46.existsSync)(path2) ? (0, import_node_fs46.readFileSync)(path2, "utf8") : null;
39119
40149
  const plan = planManagedGitignore(current);
39120
40150
  const drift = [...plan.added.map((l) => `+${l}`), ...plan.removed.map((l) => `-${l}`)].join(", ") || "block normalize";
39121
40151
  if (opts.json) {
39122
- if (opts.write && plan.changed) (0, import_node_fs43.writeFileSync)(path2, plan.content, "utf8");
40152
+ if (opts.write && plan.changed) (0, import_node_fs46.writeFileSync)(path2, plan.content, "utf8");
39123
40153
  console.log(JSON.stringify(plan, null, 2));
39124
40154
  if (!opts.write && plan.changed) process.exitCode = 1;
39125
40155
  return;
39126
40156
  }
39127
40157
  if (opts.write) {
39128
40158
  if (plan.changed) {
39129
- (0, import_node_fs43.writeFileSync)(path2, plan.content, "utf8");
40159
+ (0, import_node_fs46.writeFileSync)(path2, plan.content, "utf8");
39130
40160
  console.log(`mmi-cli devops org rules gitignore: updated .gitignore (${drift})`);
39131
40161
  } else {
39132
40162
  console.log("mmi-cli devops org rules gitignore: up to date");
@@ -39312,7 +40342,7 @@ function scheduleRelatedDiscovery(o) {
39312
40342
  try {
39313
40343
  const args = ["issue", "discover-related", "--number", String(o.number), "--title", o.title, "--body", o.body, "--fail-soft"];
39314
40344
  if (o.repo) args.push("--repo", o.repo);
39315
- spawnDetachedSelf(args, { spawn: import_node_child_process20.spawn, execPath: process.execPath, scriptPath: process.argv[1] }, { cwd: process.cwd() });
40345
+ spawnDetachedSelf(args, { spawn: import_node_child_process21.spawn, execPath: process.execPath, scriptPath: process.argv[1] }, { cwd: process.cwd() });
39316
40346
  } catch {
39317
40347
  }
39318
40348
  }
@@ -39553,7 +40583,15 @@ repoIndex.command("gc").description("remove v4 cloud authority material for repo
39553
40583
  consoleIo.log(JSON.stringify(res, null, 2));
39554
40584
  return;
39555
40585
  }
39556
- console.log(`repo-index: gc removed ${res.removed.length} orphan v4 authority record(s)`);
40586
+ let artifactsRemoved = 0;
40587
+ let artifactsKept = 0;
40588
+ for (const receipt of Object.values(res.v4.repos)) {
40589
+ artifactsRemoved += receipt.removed.length;
40590
+ artifactsKept += receipt.kept;
40591
+ }
40592
+ console.log(
40593
+ `repo-index: gc tombstoned ${res.removed.length} orphan authorit${res.removed.length === 1 ? "y" : "ies"}, removed ${artifactsRemoved} artifact(s); kept ${res.kept} authorit${res.kept === 1 ? "y" : "ies"}, ${artifactsKept} artifact(s)`
40594
+ );
39557
40595
  } catch (e) {
39558
40596
  return await failGraceful(e.message);
39559
40597
  }
@@ -39563,6 +40601,10 @@ repoIndex.command("sync-estate").description("Hub indexer: publish a pushed comm
39563
40601
  const cfg = await loadConfig();
39564
40602
  const gh = process.env.GH_TOKEN || process.env.GITHUB_TOKEN || "";
39565
40603
  if (!gh) await failGraceful("sync-estate needs GH_TOKEN or GITHUB_TOKEN with contents:read on target repos");
40604
+ if (!o.json) {
40605
+ console.log(`repo-index: pipeline provenance ${CURRENT_REPO_INDEX_PROVENANCE_TOKEN} \u2014 delta is the normal path${o.plan ? " (plan only: nothing was cloned, embedded or published)" : ""}`);
40606
+ }
40607
+ const streamedWarnings = /* @__PURE__ */ new Set();
39566
40608
  const res = await syncEstateRepoIndex({
39567
40609
  deps: registryClientDeps(cfg),
39568
40610
  repo: o.repo,
@@ -39570,26 +40612,30 @@ repoIndex.command("sync-estate").description("Hub indexer: publish a pushed comm
39570
40612
  fullRebuild: o.fullRebuild,
39571
40613
  skipRepos: o.skipRepo,
39572
40614
  plan: Boolean(o.plan),
39573
- githubToken: gh
40615
+ githubToken: gh,
40616
+ onClassify: o.json ? void 0 : ({ row, warning }) => {
40617
+ const at = row.targetCommit ? ` target=${row.targetCommit.slice(0, 12)}` : "";
40618
+ const active = row.activeCommit ? ` active=${row.activeCommit.slice(0, 12)}` : "";
40619
+ console.log(`repo-index: ${row.repo} ${row.reason} \u2192 ${row.action}${active}${at}`);
40620
+ if (warning) {
40621
+ streamedWarnings.add(warning);
40622
+ console.error(`repo-index: WARN ${warning}`);
40623
+ }
40624
+ }
39574
40625
  });
39575
40626
  if (o.json) {
39576
40627
  consoleIo.log(JSON.stringify(res, null, 2));
39577
40628
  if (!res.ok) process.exitCode = 1;
39578
40629
  return;
39579
40630
  }
39580
- console.log(`repo-index: pipeline provenance ${res.provenanceToken} \u2014 delta is the normal path${o.plan ? " (plan only: nothing was cloned, embedded or published)" : ""}`);
39581
- for (const row of res.drift) {
39582
- if (row.reason === "healthy") continue;
39583
- const at = row.targetCommit ? ` target=${row.targetCommit.slice(0, 12)}` : "";
39584
- const active = row.activeCommit ? ` active=${row.activeCommit.slice(0, 12)}` : "";
39585
- console.log(`repo-index: ${row.repo} ${row.reason} \u2192 ${row.action}${active}${at}`);
39586
- }
39587
40631
  for (const p of res.published) {
39588
40632
  const gap = p.embGap ?? Math.max(0, p.fileCount - (p.embCount ?? 0));
39589
40633
  console.log(`repo-index: published v4 ${p.repo} \u2014 ${p.fileCount} chunks emb=${p.embCount ?? 0}/${p.fileCount} gap=${gap} graph=${p.graphEdges ?? "unavailable"}`);
39590
40634
  if (p.metrics) console.log(`repo-index: ${formatV4BuildMetrics(p.repo, p.metrics)}`);
39591
40635
  }
39592
- for (const warning of res.skipped) console.error(`repo-index: WARN ${warning}`);
40636
+ for (const warning of res.skipped) {
40637
+ if (!streamedWarnings.has(warning)) console.error(`repo-index: WARN ${warning}`);
40638
+ }
39593
40639
  if (res.needsFullRebuild.length) {
39594
40640
  console.error(`repo-index: ${res.needsFullRebuild.length} repo(s) need an explicit tokened migration: ${res.needsFullRebuild.join(", ")}`);
39595
40641
  }
@@ -39668,7 +40714,7 @@ spawnCmd.command("policy").description("enforce the windowsHide contract across
39668
40714
  }
39669
40715
  });
39670
40716
  var tests = program2.command("tests").description("a repo's test-policy.json \u2014 the opt-in test contract and its enforcement");
39671
- tests.command("policy").description("enforce this repo's test-policy.json against the diff: a mandatory-zone change must carry a test, an unrequested new test file is refused, a `protected` test file may not be deleted or renamed away, and a `protected` entry naming a missing file is refused. Override any of them with a `Test-Policy-Override: <reason>` commit trailer (#3605)").option("--json", "machine-readable result: { ok, base, changedCount, findings[] }").option("--base <ref>", "comparison base (default: TEST_POLICY_BASE, then origin/development, then origin/main)").action(async (o) => {
40717
+ tests.command("policy").description("enforce this repo's test-policy.json against the diff: a mandatory-zone change must carry a test, an unrequested new test file is refused, a `protected` test file may not be deleted or renamed away, and a `protected` entry naming a missing file is refused. Override any of them with a `Test-Policy-Override: <reason>` commit trailer (#3605)").option("--json", "machine-readable result: { ok, base, changedCount, mandatoryCount, matchedMandatoryCount, matchedMandatoryGlobs, testCommandsAllowed, commandClasses, findings[] }").option("--base <ref>", "comparison base (default: TEST_POLICY_BASE, then origin/development, then origin/main)").action(async (o) => {
39672
40718
  try {
39673
40719
  const root = await repoRoot();
39674
40720
  const result = runTestPolicy(root, { base: o.base });
@@ -39684,8 +40730,9 @@ tests.command("policy").description("enforce this repo's test-policy.json agains
39684
40730
  return;
39685
40731
  }
39686
40732
  if (result.ok) {
40733
+ const commandVerdict = result.testCommandsAllowed ? "test commands: allowed" : `test commands: refused [${result.testCommandReasonId}]`;
39687
40734
  console.log(
39688
- `tests policy: OK (${result.changedCount} changed file(s); ${result.mandatoryCount} mandatory glob(s), ${result.protectedCount} protected file(s)).`
40735
+ `tests policy: OK (${result.changedCount} changed file(s); ${result.matchedMandatoryCount} of ${result.mandatoryCount} mandatory glob(s) matched, ${result.protectedCount} protected file(s); ${commandVerdict}).`
39689
40736
  );
39690
40737
  return;
39691
40738
  }
@@ -39695,6 +40742,20 @@ tests.command("policy").description("enforce this repo's test-policy.json agains
39695
40742
  await failGraceful(e.message);
39696
40743
  }
39697
40744
  });
40745
+ var distCmd = program2.command("dist").description("this repo's committed dist/BOM drift receipt \u2014 whether cli/dist, updater/dist and distribution-bom.json still match a fresh build of source");
40746
+ distCmd.command("status").description("rebuild every committed dist artifact to a temp dir and report committed vs rebuilt-expected sha256 plus the BOM's recorded dist identities \u2014 a visible, non-blocking receipt. Development checkouts may lag source until the release fold; drift NEVER fails the run, and nothing is refreshed for you (#5576)").option("--json", "machine-readable receipt: { ok, staleCount, artifacts[], bom, summary } (full hashes; drift still exits 0)").action(async (o) => {
40747
+ try {
40748
+ const root = await repoRoot();
40749
+ const receipt = runDistStatus(root);
40750
+ if (o.json) {
40751
+ consoleIo.log(JSON.stringify({ ok: true, staleCount: receipt.staleCount, artifacts: receipt.artifacts, bom: receipt.bom, summary: receipt.summary }, null, 2));
40752
+ return;
40753
+ }
40754
+ for (const line of renderDistDriftReceipt(receipt)) console.log(line);
40755
+ } catch (e) {
40756
+ await failGraceful(`dist status: ${e.message}`);
40757
+ }
40758
+ });
39698
40759
  async function reportWrite(label, res) {
39699
40760
  if (res.ok) {
39700
40761
  console.log(JSON.stringify(res.body));
@@ -39967,7 +41028,7 @@ project.command("set [owner/repo]").description("upsert project META (idempotent
39967
41028
  if (dupe) return fail(`org project set: KEY "${dupe}" was passed to both --var and --set; --set is an alias of --var, so pass each KEY once`);
39968
41029
  if (o.secretsFile) {
39969
41030
  try {
39970
- vars.push(`secrets=${(0, import_node_fs43.readFileSync)(o.secretsFile, "utf8")}`);
41031
+ vars.push(`secrets=${(0, import_node_fs46.readFileSync)(o.secretsFile, "utf8")}`);
39971
41032
  } catch (e) {
39972
41033
  return fail(`org project set: cannot read --secrets-file ${o.secretsFile}: ${e.message}`);
39973
41034
  }
@@ -40317,7 +41378,7 @@ function resolveCreateSurface(opts) {
40317
41378
  function surfaceWaived() {
40318
41379
  return rawFlag("--no-surface");
40319
41380
  }
40320
- var issue = program2.command("issue").description("issues \u2014 create and view with structured JSON (view; show is an alias for board-verb callers)");
41381
+ var issue = program2.command("issue").description("issues \u2014 create and view with structured JSON (view; show is an alias for board-verb callers). Claims are board mutations: use `oracle board claim`, never `oracle issue claim`");
40321
41382
  withExamples(mutating(
40322
41383
  issue.command("create").description("create an issue (type \u2014 label) and print {number,url,label} JSON").addOption(new Option("--type <type>", "bug | feature | task (sets the matching label; required unless --batch)").choices([...ISSUE_TYPES])).option("--title <title>", "issue title").option("--title-file <path|->", "read the issue title from a UTF-8 file, or from stdin with -").option("--body <body>", "issue body (markdown)").option("--body-file <path|->", "read issue body from a UTF-8 file. `-` (stdin) needs a heredoc, which the agent inline-body guard denies (#1473/#2125) \u2014 prefer a real path; a title with backticks needs --title-file for the same reason (#3381)").option("--priority <priority>", "urgent | high | medium | low (defaults to medium; sets the board Priority field only \u2014 never a priority:* label, #416)").option("--repo <owner/repo>", "target repo (defaults to the current repo)").option("--label <label...>", "extra label(s) to attach (repeatable; auto-created if missing)").option("--surface <surface>", "issue surface, with or without the surface: prefix (#3789). Required when the target repo runs the one-surface-label board rule; any value satisfies it, so this is not a closed enum").option("--no-surface", "file without a surface label on a repo that requires one \u2014 for a genuinely exempt filing (e.g. a coop proof issue that spans every surface)").option("--parent <ref>", "file as a native sub-issue of this parent (#123, owner/repo#123, or URL)").option("--no-related", "skip the auto related-issues comment"),
40323
41384
  // --dry-run/--validate-only plan: resolve the same title source and validate the same type, priority,
@@ -40621,7 +41682,7 @@ ${list}`);
40621
41682
  }
40622
41683
  console.log(JSON.stringify({ number: parsed.number, repo, item: result.item.text, checked, changed: true }));
40623
41684
  });
40624
- program2.command("report").description("file a friction report on the Hub board (Hub session auth, dedups open reports) and print {number,url} JSON").option("--title <title>", "one-line friction summary").option("--title-file <path|->", "read the friction summary from a UTF-8 file, or from stdin with -").option("--body <body>", "report body (markdown)").option("--body-file <path|->", "read report body from a UTF-8 file, or from stdin with -").addOption(new Option("--type <type>", "bug | feature | task (sets the matching label)").default("task").choices([...ISSUE_TYPES])).option("--priority <priority>", "urgent | high | medium | low (defaults to medium; sets the board Priority field only, #416)").option("--repo <owner/repo>", 'attribute the report to a different source repo than the current checkout for the "Filed via..." footer (rare \u2014 usually auto-detected; every report always lands on the org Hub, never an alternate target, #263)').option("--force", "file a new issue even when an open report looks like a duplicate").option("--json", "machine-readable output (already the default \u2014 report always prints JSON; #682)").action(async (o) => {
41685
+ program2.command("report").description("file a friction report on the Hub board (Hub session auth, dedups open reports) and print {number,url} JSON \u2014 learning-tagged; file and forget (cloud agents own the fix; do not claim/poll it)").option("--title <title>", "one-line friction summary").option("--title-file <path|->", "read the friction summary from a UTF-8 file, or from stdin with -").option("--body <body>", "report body (markdown)").option("--body-file <path|->", "read report body from a UTF-8 file, or from stdin with -").addOption(new Option("--type <type>", "bug | feature | task (sets the matching label)").default("task").choices([...ISSUE_TYPES])).option("--priority <priority>", "urgent | high | medium | low (defaults to medium; sets the board Priority field only, #416)").option("--repo <owner/repo>", 'attribute the report to a different source repo than the current checkout for the "Filed via..." footer (rare \u2014 usually auto-detected; every report always lands on the org Hub, never an alternate target, #263)').option("--force", "file a new issue even when an open report looks like a duplicate").option("--json", "machine-readable output (already the default \u2014 report always prints JSON; #682)").action(async (o) => {
40625
41686
  let body;
40626
41687
  let priority;
40627
41688
  let title;
@@ -40737,6 +41798,7 @@ program2.command("skill-lesson").description("file a skill-lesson on the Hub boa
40737
41798
  }
40738
41799
  const created = requireGhCreateOk(await ghCreate(args), "skill-lesson");
40739
41800
  const { projectItemId, onBoard } = await attachToProject(created.number, targetRepo3, priority);
41801
+ console.log(JSON.stringify({ ...created, projectItemId, onBoard }));
40740
41802
  });
40741
41803
  var pr = program2.command("pr").description("pull requests \u2014 reliable create with structured output");
40742
41804
  withExamples(pr.command("create").description("create a PR and print {number,url} JSON").option("--title <title>", "PR title").option("--title-file <path|->", "read the PR title from a UTF-8 file, or from stdin with -").option("--body <body>", "PR body (markdown)").option("--body-file <path|->", "read PR body from a UTF-8 file, or from stdin with -").option("--base <branch>", "base branch (defaults to the repo default)").option("--head <branch>", "head branch (defaults to the current branch)").option("--repo <owner/repo>", "target repo (defaults to the current repo)").option("--draft", "open the PR in draft state (#2667)").option("--json", "machine-readable output (default; accepted for parity)").action(async (o) => {
@@ -40791,11 +41853,11 @@ pr.command("view <number>").description("read a PR as structured JSON (merged st
40791
41853
  }
40792
41854
  });
40793
41855
  async function listCiWorkflowPaths(cwd = process.cwd()) {
40794
- const wfDir = (0, import_node_path40.join)(cwd, ".github", "workflows");
40795
- if (!(0, import_node_fs43.existsSync)(wfDir)) return [];
40796
- return (0, import_node_fs43.readdirSync)(wfDir).filter((name) => /\.(ya?ml)$/i.test(name)).filter((name) => {
41856
+ const wfDir = (0, import_node_path42.join)(cwd, ".github", "workflows");
41857
+ if (!(0, import_node_fs46.existsSync)(wfDir)) return [];
41858
+ return (0, import_node_fs46.readdirSync)(wfDir).filter((name) => /\.(ya?ml)$/i.test(name)).filter((name) => {
40797
41859
  try {
40798
- return workflowReportsPrChecks((0, import_node_fs43.readFileSync)((0, import_node_path40.join)(wfDir, name), "utf8"));
41860
+ return workflowReportsPrChecks((0, import_node_fs46.readFileSync)((0, import_node_path42.join)(wfDir, name), "utf8"));
40799
41861
  } catch {
40800
41862
  return true;
40801
41863
  }
@@ -40847,16 +41909,16 @@ function ciAuditDeps() {
40847
41909
  // gate re-seed step is skipped gracefully rather than failing mid-run.
40848
41910
  readSeedFile: (path2) => {
40849
41911
  if (!root) return null;
40850
- const fullPath = (0, import_node_path40.join)(root, path2);
40851
- return (0, import_node_fs43.existsSync)(fullPath) ? (0, import_node_fs43.readFileSync)(fullPath, "utf8") : null;
41912
+ const fullPath = (0, import_node_path42.join)(root, path2);
41913
+ return (0, import_node_fs46.existsSync)(fullPath) ? (0, import_node_fs46.readFileSync)(fullPath, "utf8") : null;
40852
41914
  }
40853
41915
  };
40854
41916
  }
40855
41917
  function hubRoot() {
40856
- const fromPkg = (0, import_node_path40.join)(__dirname, "..", "..");
41918
+ const fromPkg = (0, import_node_path42.join)(__dirname, "..", "..");
40857
41919
  const marker = "skills/bootstrap/seeds/manifest.json";
40858
- if ((0, import_node_fs43.existsSync)((0, import_node_path40.join)(fromPkg, marker))) return fromPkg;
40859
- if ((0, import_node_fs43.existsSync)((0, import_node_path40.join)(process.cwd(), marker))) return process.cwd();
41920
+ if ((0, import_node_fs46.existsSync)((0, import_node_path42.join)(fromPkg, marker))) return fromPkg;
41921
+ if ((0, import_node_fs46.existsSync)((0, import_node_path42.join)(process.cwd(), marker))) return process.cwd();
40860
41922
  return null;
40861
41923
  }
40862
41924
  async function waitLoopCorePool(label) {
@@ -40905,7 +41967,12 @@ pr.command("checks-wait <number>").description(`bounded wait for ALL checks on t
40905
41967
  timeoutMs = Math.round(minutes * 6e4);
40906
41968
  }
40907
41969
  const repo = await requireRepo(o.repo);
40908
- const snapshotRead = await readRestPrSnapshotWithRetry(number, repo);
41970
+ const budgetMs = timeoutMs ?? PR_CHECKS_TIMEOUT_MS;
41971
+ const waitStarted = Date.now();
41972
+ const snapshotRead = await readRestPrSnapshotWithRetry(number, repo, void 0, {
41973
+ retryTransientUntilMs: waitStarted + budgetMs,
41974
+ onTransientRetry: (error, attempt) => console.warn(`pr checks-wait: transient GitHub read (${error}) \u2014 retrying PR snapshot (attempt ${attempt}) within the wait budget`)
41975
+ });
40909
41976
  if (snapshotRead.state === "failed") {
40910
41977
  return fail(`pr checks-wait: cannot resolve PR #${number}'s base branch \u2014 ${snapshotRead.error}. Refusing to wait against an assumed base; retry when the API answers.`);
40911
41978
  }
@@ -40926,9 +41993,9 @@ pr.command("checks-wait <number>").description(`bounded wait for ALL checks on t
40926
41993
  // #5400: after grace, name "GitHub delivered zero runs" instead of burning the full budget as pending.
40927
41994
  pollHeadWorkflowRunCount: () => waitLoopHeadWorkflowRunCount("pr checks-wait", number, repo),
40928
41995
  baseBranch,
40929
- sleep: (ms) => new Promise((resolve5) => setTimeout(resolve5, ms)),
41996
+ sleep: (ms) => new Promise((resolve6) => setTimeout(resolve6, ms)),
40930
41997
  log: (message) => console.warn(message),
40931
- timeoutMs,
41998
+ timeoutMs: Math.max(1, budgetMs - (Date.now() - waitStarted)),
40932
41999
  // Liveness on stderr, one line per poll. A silent bounded wait is indistinguishable from a hang, and
40933
42000
  // an agent harness kills it on its own (shorter) deadline before the verdict ever prints (#2940).
40934
42001
  progress: ({ state, elapsedMs, remainingMs }) => console.warn(`pr checks-wait: ${state} \u2014 ${Math.round(elapsedMs / 1e3)}s elapsed, ${Math.round(remainingMs / 6e4)}m left`)
@@ -41022,7 +42089,7 @@ pr.command("land <number>").description("agent merge path (#1440): train probe \
41022
42089
  // #5400: same zero-runs delivery probe as checks-wait — do not burn the land budget on silence.
41023
42090
  pollHeadWorkflowRunCount: () => waitLoopHeadWorkflowRunCount("pr land", prNumber, repo),
41024
42091
  baseBranch: "development",
41025
- sleep: (ms) => new Promise((resolve5) => setTimeout(resolve5, ms)),
42092
+ sleep: (ms) => new Promise((resolve6) => setTimeout(resolve6, ms)),
41026
42093
  log: (message) => console.warn(message),
41027
42094
  // `pr land` inherits the same (raised) checks budget, so it needs the same liveness — otherwise the
41028
42095
  // 30m wait is SILENT and reads exactly like the hang #2940 was filed about, only three times longer.
@@ -41065,7 +42132,7 @@ pr.command("land <number>").description("agent merge path (#1440): train probe \
41065
42132
  } else {
41066
42133
  lastFailure = void 0;
41067
42134
  }
41068
- await new Promise((resolve5) => setTimeout(resolve5, PR_LAND_POLL_MS));
42135
+ await new Promise((resolve6) => setTimeout(resolve6, PR_LAND_POLL_MS));
41069
42136
  }
41070
42137
  if (lastFailure) {
41071
42138
  throw new Error(
@@ -41149,7 +42216,12 @@ jsonParity(pr.command("merge <number>").description("merge a PR (squash by defau
41149
42216
  const ciPolicy = await resolveMergeCiPolicyForCheckout(o.repo, ciHeadRef);
41150
42217
  if (o.wait) {
41151
42218
  const repo = await requireRepo(o.repo);
41152
- const snapshotRead = await readRestPrSnapshotWithRetry(number, repo);
42219
+ const budgetMs = PR_CHECKS_TIMEOUT_MS;
42220
+ const waitStarted = Date.now();
42221
+ const snapshotRead = await readRestPrSnapshotWithRetry(number, repo, void 0, {
42222
+ retryTransientUntilMs: waitStarted + budgetMs,
42223
+ onTransientRetry: (error, attempt) => console.warn(`pr merge: transient GitHub read (${error}) \u2014 retrying PR snapshot (attempt ${attempt}) within the --wait budget`)
42224
+ });
41153
42225
  if (snapshotRead.state === "failed") {
41154
42226
  console.error(`pr merge: cannot resolve PR #${number}'s base branch \u2014 ${snapshotRead.error}. Refusing to wait against an assumed base; retry when the API answers.`);
41155
42227
  process.exitCode = 1;
@@ -41165,8 +42237,9 @@ jsonParity(pr.command("merge <number>").description("merge a PR (squash by defau
41165
42237
  diagnoseFailure: () => waitLoopDiagnosis("pr merge --wait", number, repo),
41166
42238
  pollHeadWorkflowRunCount: () => waitLoopHeadWorkflowRunCount("pr merge --wait", number, repo),
41167
42239
  baseBranch,
41168
- sleep: (ms) => new Promise((resolve5) => setTimeout(resolve5, ms)),
42240
+ sleep: (ms) => new Promise((resolve6) => setTimeout(resolve6, ms)),
41169
42241
  log: (message) => console.warn(message),
42242
+ timeoutMs: Math.max(1, budgetMs - (Date.now() - waitStarted)),
41170
42243
  progress: ({ state, elapsedMs, remainingMs }) => console.warn(`pr merge: --wait checks \u2014 ${state}, ${Math.round(elapsedMs / 1e3)}s elapsed, ${Math.round(remainingMs / 6e4)}m left`)
41171
42244
  });
41172
42245
  if (wait.status !== "success" && wait.status !== "skipped") {
@@ -41200,7 +42273,7 @@ jsonParity(pr.command("merge <number>").description("merge a PR (squash by defau
41200
42273
  }
41201
42274
  if (!repoForPostCleanup) throw e;
41202
42275
  console.warn(`pr merge: gh GraphQL rate-limited \u2014 merging PR #${number} via REST PUT instead (#4588).`);
41203
- const commitMessage = bodyFile ? (0, import_node_fs43.readFileSync)(bodyFile, "utf8") : void 0;
42276
+ const commitMessage = bodyFile ? (0, import_node_fs46.readFileSync)(bodyFile, "utf8") : void 0;
41204
42277
  await defaultGitHubClient().rest("PUT", `repos/${repoForPostCleanup}/pulls/${number}/merge`, {
41205
42278
  body: { merge_method: method.slice(2), ...commitMessage ? { commit_message: commitMessage } : {} },
41206
42279
  timeoutMs: GH_MUTATION_TIMEOUT_MS
@@ -41896,12 +42969,12 @@ access.command("audit").description("audit collaborator roles + train-branch pus
41896
42969
  targets = resolution.targets;
41897
42970
  }
41898
42971
  const derivedMatrix = registryProjects ? accessMatrixFromProjects(registryProjects) : {};
41899
- const fileMatrix = (0, import_node_fs43.existsSync)("access-matrix.json") ? loadAccessMatrix((0, import_node_fs43.readFileSync)("access-matrix.json", "utf8")) : {};
42972
+ const fileMatrix = (0, import_node_fs46.existsSync)("access-matrix.json") ? loadAccessMatrix((0, import_node_fs46.readFileSync)("access-matrix.json", "utf8")) : {};
41900
42973
  const matrix = mergeAccessMatrix(fileMatrix, derivedMatrix);
41901
42974
  const derivedContracts = registryProjects ? dataAccessContractsFromProjects(registryProjects) : { consumers: {} };
41902
- const fileContracts = (0, import_node_fs43.existsSync)("data-access-contracts.json") ? loadDataAccessContracts((0, import_node_fs43.readFileSync)("data-access-contracts.json", "utf8")) : { consumers: {} };
42975
+ const fileContracts = (0, import_node_fs46.existsSync)("data-access-contracts.json") ? loadDataAccessContracts((0, import_node_fs46.readFileSync)("data-access-contracts.json", "utf8")) : { consumers: {} };
41903
42976
  const dataAccess = mergeDataAccessContracts(fileContracts, derivedContracts);
41904
- const sanctioned = (0, import_node_fs43.existsSync)("access-matrix.json") ? loadSanctionedAdmins((0, import_node_fs43.readFileSync)("access-matrix.json", "utf8")) : {};
42977
+ const sanctioned = (0, import_node_fs46.existsSync)("access-matrix.json") ? loadSanctionedAdmins((0, import_node_fs46.readFileSync)("access-matrix.json", "utf8")) : {};
41905
42978
  const report = await auditOrgAccess(targets, deps, matrix, dataAccess, sanctioned);
41906
42979
  console.log(o.json ? JSON.stringify(report, null, 2) : renderAccessReport(report));
41907
42980
  if (!report.ok) process.exitCode = 1;
@@ -41933,16 +43006,16 @@ function directoryBytes(path2) {
41933
43006
  let total = 0;
41934
43007
  let entries;
41935
43008
  try {
41936
- entries = (0, import_node_fs43.readdirSync)(path2, { withFileTypes: true });
43009
+ entries = (0, import_node_fs46.readdirSync)(path2, { withFileTypes: true });
41937
43010
  } catch {
41938
43011
  return 0;
41939
43012
  }
41940
43013
  for (const entry of entries) {
41941
- const child2 = (0, import_node_path40.join)(path2, entry.name);
43014
+ const child2 = (0, import_node_path42.join)(path2, entry.name);
41942
43015
  if (entry.isDirectory()) total += directoryBytes(child2);
41943
43016
  else {
41944
43017
  try {
41945
- total += (0, import_node_fs43.statSync)(child2).size;
43018
+ total += (0, import_node_fs46.statSync)(child2).size;
41946
43019
  } catch {
41947
43020
  }
41948
43021
  }
@@ -41950,25 +43023,25 @@ function directoryBytes(path2) {
41950
43023
  return total;
41951
43024
  }
41952
43025
  function listDirEntries(dir) {
41953
- return (0, import_node_fs43.readdirSync)(dir, { withFileTypes: true }).map((d) => ({ name: d.name, isDirectory: d.isDirectory() }));
43026
+ return (0, import_node_fs46.readdirSync)(dir, { withFileTypes: true }).map((d) => ({ name: d.name, isDirectory: d.isDirectory() }));
41954
43027
  }
41955
43028
  function readInstalledPluginRefs(configRoot) {
41956
43029
  const p = installedPluginsPathForConfig(configRoot);
41957
- if (!(0, import_node_fs43.existsSync)(p)) return [];
43030
+ if (!(0, import_node_fs46.existsSync)(p)) return [];
41958
43031
  try {
41959
- return installedPluginPaths((0, import_node_fs43.readFileSync)(p, "utf8"));
43032
+ return installedPluginPaths((0, import_node_fs46.readFileSync)(p, "utf8"));
41960
43033
  } catch {
41961
43034
  return null;
41962
43035
  }
41963
43036
  }
41964
43037
  function pluginCacheFsDeps(configRoot, dirBytes) {
41965
43038
  return {
41966
- exists: (p) => (0, import_node_fs43.existsSync)(p),
41967
- listVersionDirs: (root) => (0, import_node_fs43.readdirSync)(root, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name),
43039
+ exists: (p) => (0, import_node_fs46.existsSync)(p),
43040
+ listVersionDirs: (root) => (0, import_node_fs46.readdirSync)(root, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name),
41968
43041
  dirBytes,
41969
- listStagingDirs: (root) => (0, import_node_fs43.readdirSync)(root, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => {
43042
+ listStagingDirs: (root) => (0, import_node_fs46.readdirSync)(root, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => {
41970
43043
  try {
41971
- return { name: d.name, mtimeMs: newestMtimeMs((0, import_node_path40.join)(root, d.name), listDirEntries, (p) => (0, import_node_fs43.statSync)(p).mtimeMs) };
43044
+ return { name: d.name, mtimeMs: newestMtimeMs((0, import_node_path42.join)(root, d.name), listDirEntries, (p) => (0, import_node_fs46.statSync)(p).mtimeMs) };
41972
43045
  } catch {
41973
43046
  return { name: d.name, mtimeMs: Date.now() };
41974
43047
  }
@@ -41982,10 +43055,10 @@ function stagingApplyFsGuard(configRoot) {
41982
43055
  return {
41983
43056
  referencedPaths: () => readInstalledPluginRefs(configRoot),
41984
43057
  mtimeMs: (name) => {
41985
- const p = (0, import_node_path40.join)(stagingRoot, name);
41986
- if (!(0, import_node_fs43.existsSync)(p)) return null;
43058
+ const p = (0, import_node_path42.join)(stagingRoot, name);
43059
+ if (!(0, import_node_fs46.existsSync)(p)) return null;
41987
43060
  try {
41988
- return newestMtimeMs(p, listDirEntries, (q) => (0, import_node_fs43.statSync)(q).mtimeMs);
43061
+ return newestMtimeMs(p, listDirEntries, (q) => (0, import_node_fs46.statSync)(q).mtimeMs);
41989
43062
  } catch {
41990
43063
  return null;
41991
43064
  }
@@ -42005,13 +43078,13 @@ program2.command("plugin-prune").description(`prune stale cached MMI plugin vers
42005
43078
  return;
42006
43079
  }
42007
43080
  const plan = buildPluginCachePlan(
42008
- (0, import_node_os19.homedir)(),
43081
+ (0, import_node_os21.homedir)(),
42009
43082
  running,
42010
43083
  pluginCacheFsDeps(configRoot, directoryBytes),
42011
43084
  { withBytes: true, configRoot, includeStaging: surface !== "codex" }
42012
43085
  );
42013
43086
  const anythingToDelete = plan.prune.length > 0 || plan.staging.length > 0;
42014
- const result = o.apply && anythingToDelete ? applyPluginCachePlan(plan, (p) => (0, import_node_fs43.rmSync)(p, { recursive: true, force: true }), stagingApplyFsGuard(configRoot)) : void 0;
43087
+ const result = o.apply && anythingToDelete ? applyPluginCachePlan(plan, (p) => (0, import_node_fs46.rmSync)(p, { recursive: true, force: true }), stagingApplyFsGuard(configRoot)) : void 0;
42015
43088
  const warnings = plan.prune.length > 0 ? [CONCURRENT_SESSION_WARNING] : [];
42016
43089
  if (o.json) console.log(JSON.stringify({ ...plan, warnings, applied: result ?? null }));
42017
43090
  else console.log(renderPluginCachePlan(plan, result));