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