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