@mutmutco/cli 4.1.3 → 4.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/main.cjs +1503 -630
- 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();
|
|
@@ -19826,7 +20331,7 @@ async function postIssueComment(client, input) {
|
|
|
19826
20331
|
var SKILL_LESSON_LABEL = "skill-lesson";
|
|
19827
20332
|
var SKILL_LESSON_FILE_LABELS = [SKILL_LESSON_LABEL, LEARNING_LABEL];
|
|
19828
20333
|
var SKILL_LESSON_LOOP_KIND = "lesson";
|
|
19829
|
-
var SKILL_NAMES = ["bootstrap", "browser-automation", "doctor", "epic", "hotfix", "mmi", "onboard", "rcand", "release", "resume", "secrets", "stage"];
|
|
20334
|
+
var SKILL_NAMES = ["bootstrap", "browser-automation", "doctor", "epic", "hotfix", "mmi", "onboard", "rcand", "release", "repo-index-audit", "resume", "secrets", "stage"];
|
|
19830
20335
|
function assertSkillName(name) {
|
|
19831
20336
|
const match = SKILL_NAMES.find((skill) => skill === name);
|
|
19832
20337
|
if (!match) throw new Error(`unknown skill "${name}" \u2014 expected one of: ${SKILL_NAMES.join(", ")}`);
|
|
@@ -19858,7 +20363,7 @@ function findDuplicateLesson(source, openLessons) {
|
|
|
19858
20363
|
|
|
19859
20364
|
// src/session-identity.ts
|
|
19860
20365
|
var import_node_crypto4 = require("node:crypto");
|
|
19861
|
-
var
|
|
20366
|
+
var import_node_os8 = require("node:os");
|
|
19862
20367
|
init_plugin_guard_io();
|
|
19863
20368
|
var SESSION_ID_ENV_VARS = [
|
|
19864
20369
|
"MMI_SESSION_ID",
|
|
@@ -19894,7 +20399,7 @@ function describeSessionIdentity(env = process.env) {
|
|
|
19894
20399
|
return {
|
|
19895
20400
|
session: readSessionId(env) ?? fallbackSessionId(surface),
|
|
19896
20401
|
surface,
|
|
19897
|
-
host: (0,
|
|
20402
|
+
host: (0, import_node_os8.hostname)()
|
|
19898
20403
|
};
|
|
19899
20404
|
}
|
|
19900
20405
|
|
|
@@ -20530,7 +21035,14 @@ async function prepareClaimContext(options, selectors, deps, collected) {
|
|
|
20530
21035
|
report[scope].claimable = filtered.claimable;
|
|
20531
21036
|
report.warnings.push(...filtered.warnings);
|
|
20532
21037
|
}
|
|
20533
|
-
return {
|
|
21038
|
+
return {
|
|
21039
|
+
cfg,
|
|
21040
|
+
client,
|
|
21041
|
+
items: collected.items,
|
|
21042
|
+
writable: writableOrUnknown(writable),
|
|
21043
|
+
report,
|
|
21044
|
+
session: deps.session ?? describeSessionIdentity()
|
|
21045
|
+
};
|
|
20534
21046
|
}
|
|
20535
21047
|
async function claimOneBoardItem(ctx, selector, options) {
|
|
20536
21048
|
const { cfg, client, report } = ctx;
|
|
@@ -20575,26 +21087,29 @@ async function claimOneBoardItem(ctx, selector, options) {
|
|
|
20575
21087
|
const verdict = evaluateClaim(fresh, assignedLogin);
|
|
20576
21088
|
if (!verdict.ok) throw new Error(verdict.reason);
|
|
20577
21089
|
item = fresh;
|
|
21090
|
+
const refuseIfContested = async () => {
|
|
21091
|
+
if (options.force) return;
|
|
21092
|
+
const contest = await checkLaneContest(client, item, ctx.session);
|
|
21093
|
+
if (contest.contested) throw new Error(laneContestMessage(item.ref, contest, "claim"));
|
|
21094
|
+
};
|
|
21095
|
+
await refuseIfContested();
|
|
20578
21096
|
if (verdict.alreadyClaimed) {
|
|
20579
|
-
if (!options.force) {
|
|
20580
|
-
const contest = await checkLaneContest(client, item);
|
|
20581
|
-
if (contest.contested) throw new Error(laneContestMessage(item.ref, contest, "claim"));
|
|
20582
|
-
}
|
|
20583
21097
|
if (options.check) {
|
|
20584
21098
|
return { item, viewer: report.viewer, repo: report.repo, status: "In Progress", partial: false, alreadyClaimed: true, checked: true };
|
|
20585
21099
|
}
|
|
20586
|
-
await postClaimMarkerComment(client, item);
|
|
21100
|
+
await postClaimMarkerComment(client, item, ctx.session);
|
|
20587
21101
|
return { item, viewer: report.viewer, repo: report.repo, status: "In Progress", partial: false, alreadyClaimed: true };
|
|
20588
21102
|
}
|
|
20589
21103
|
if (options.check) {
|
|
20590
21104
|
return { item, viewer: report.viewer, repo: report.repo, status: item.status, partial: false, checked: true };
|
|
20591
21105
|
}
|
|
21106
|
+
await refuseIfContested();
|
|
20592
21107
|
try {
|
|
20593
21108
|
await client.rest("POST", `repos/${item.repository}/issues/${item.number}/assignees`, { body: { assignees: [assignedLogin] } });
|
|
20594
21109
|
} catch (e) {
|
|
20595
21110
|
throw new Error(`claim failed before board status changed: ${ghError(e)}`);
|
|
20596
21111
|
}
|
|
20597
|
-
await postClaimMarkerComment(client, item);
|
|
21112
|
+
await postClaimMarkerComment(client, item, ctx.session);
|
|
20598
21113
|
try {
|
|
20599
21114
|
await updateItemSingleSelect(client, cfg.projectId, item.itemId, cfg.statusFieldId, cfg.statusOptions["In Progress"]);
|
|
20600
21115
|
} catch (e) {
|
|
@@ -20787,7 +21302,7 @@ async function setBoardItemPriority(client, cfg, itemId, priority) {
|
|
|
20787
21302
|
await updateItemSingleSelect(client, cfg.projectId, itemId, cfg.priorityFieldId, optionId);
|
|
20788
21303
|
return cliPriorityToFieldName(priority);
|
|
20789
21304
|
}
|
|
20790
|
-
var defaultRetrySleep = (ms) => new Promise((
|
|
21305
|
+
var defaultRetrySleep = (ms) => new Promise((resolve6) => setTimeout(resolve6, ms));
|
|
20791
21306
|
async function resolveProjectItemIdWithRetry(client, cfg, selector, opts = {}) {
|
|
20792
21307
|
const attempts = Math.max(1, opts.attempts ?? 5);
|
|
20793
21308
|
const delayMs = opts.delayMs ?? 300;
|
|
@@ -21220,9 +21735,8 @@ function boardItemClaim(item) {
|
|
|
21220
21735
|
currentlyClaimed: item.assignees.length > 0 && item.status === "In Progress"
|
|
21221
21736
|
};
|
|
21222
21737
|
}
|
|
21223
|
-
async function postClaimMarkerComment(client, item) {
|
|
21738
|
+
async function postClaimMarkerComment(client, item, actor = describeSessionIdentity()) {
|
|
21224
21739
|
try {
|
|
21225
|
-
const actor = describeSessionIdentity();
|
|
21226
21740
|
const marker = {
|
|
21227
21741
|
v: 1,
|
|
21228
21742
|
session: actor.session,
|
|
@@ -21245,7 +21759,7 @@ var CLAIM_SESSION_ACTIVITY_MS = 30 * 6e4;
|
|
|
21245
21759
|
var CLAIM_SESSION_PROBE_CACHE_MS = 6e4;
|
|
21246
21760
|
var claimSessionProbeCache = /* @__PURE__ */ new Map();
|
|
21247
21761
|
function probeLocalClaimSession(marker, now = Date.now()) {
|
|
21248
|
-
if (!marker.session || !marker.host || marker.host.toLowerCase() !== (0,
|
|
21762
|
+
if (!marker.session || !marker.host || marker.host.toLowerCase() !== (0, import_node_os9.hostname)().toLowerCase()) return void 0;
|
|
21249
21763
|
if (!marker.surface?.toLowerCase().startsWith("claude")) return void 0;
|
|
21250
21764
|
const cacheKey = `${marker.host.toLowerCase()}/${marker.session}`;
|
|
21251
21765
|
const cached = claimSessionProbeCache.get(cacheKey);
|
|
@@ -21254,17 +21768,17 @@ function probeLocalClaimSession(marker, now = Date.now()) {
|
|
|
21254
21768
|
claimSessionProbeCache.set(cacheKey, { checkedAt: now, state });
|
|
21255
21769
|
return state;
|
|
21256
21770
|
};
|
|
21257
|
-
const root = (0, import_node_path17.join)((0,
|
|
21771
|
+
const root = (0, import_node_path17.join)((0, import_node_os9.homedir)(), ".claude", "projects");
|
|
21258
21772
|
try {
|
|
21259
21773
|
const wanted = `${marker.session}.jsonl`.toLowerCase();
|
|
21260
21774
|
const pending = [root];
|
|
21261
21775
|
while (pending.length) {
|
|
21262
21776
|
const dir = pending.pop();
|
|
21263
|
-
for (const entry of (0,
|
|
21777
|
+
for (const entry of (0, import_node_fs20.readdirSync)(dir, { withFileTypes: true })) {
|
|
21264
21778
|
const path2 = (0, import_node_path17.join)(dir, entry.name);
|
|
21265
21779
|
if (entry.isDirectory()) pending.push(path2);
|
|
21266
21780
|
else if (entry.isFile() && entry.name.toLowerCase() === wanted) {
|
|
21267
|
-
return remember(now - (0,
|
|
21781
|
+
return remember(now - (0, import_node_fs20.statSync)(path2).mtimeMs <= CLAIM_SESSION_ACTIVITY_MS ? "live" : "dead");
|
|
21268
21782
|
}
|
|
21269
21783
|
}
|
|
21270
21784
|
}
|
|
@@ -21376,9 +21890,8 @@ function laneOwnership(marker, current) {
|
|
|
21376
21890
|
}
|
|
21377
21891
|
return "unknown";
|
|
21378
21892
|
}
|
|
21379
|
-
async function checkLaneContest(client, item) {
|
|
21893
|
+
async function checkLaneContest(client, item, actor = describeSessionIdentity()) {
|
|
21380
21894
|
const evidence = await gatherClaimLiveness(client, item.repository, item.number, openPullsFetcher(client));
|
|
21381
|
-
const actor = describeSessionIdentity();
|
|
21382
21895
|
const ownership = laneOwnership(evidence.marker, actor);
|
|
21383
21896
|
const live = ownership === "mine" ? [] : liveEvidenceLines(evidence, item.repository);
|
|
21384
21897
|
const unverifiable = ownership === "mine" ? [] : evidence.failed;
|
|
@@ -21576,7 +22089,7 @@ async function resolveAutoAddBoardAttach(client, cfg, selector, priority, warn =
|
|
|
21576
22089
|
}
|
|
21577
22090
|
|
|
21578
22091
|
// src/issue-body.ts
|
|
21579
|
-
var
|
|
22092
|
+
var import_node_os10 = require("node:os");
|
|
21580
22093
|
init_error_codes();
|
|
21581
22094
|
var TextArgError = class extends Error {
|
|
21582
22095
|
constructor(message, code, offendingFlag) {
|
|
@@ -21589,7 +22102,7 @@ var TextArgError = class extends Error {
|
|
|
21589
22102
|
offendingFlag;
|
|
21590
22103
|
};
|
|
21591
22104
|
function emptyStdinMessage(fileFlag) {
|
|
21592
|
-
if ((0,
|
|
22105
|
+
if ((0, import_node_os10.platform)() === "win32") {
|
|
21593
22106
|
return `${fileFlag} - read empty stdin (on Windows, ${fileFlag} - is unreliable through the npm .cmd shim \u2014 use ${fileFlag} <path>, or pipe to \`node cli/dist/index.cjs\` directly)`;
|
|
21594
22107
|
}
|
|
21595
22108
|
return `${fileFlag} - read empty stdin (nothing piped \u2014 pass a heredoc/pipe, or ${fileFlag} <path>)`;
|
|
@@ -21690,12 +22203,13 @@ var PRIMARY_GROUPS = [
|
|
|
21690
22203
|
["Review and ship", ["pr", "ci", "rcand", "release", "hotfix", "train"]],
|
|
21691
22204
|
// `tests` sits beside `docs` deliberately: both are deterministic, repo-local gates a workflow
|
|
21692
22205
|
// step invokes (`docs refs`, `tests policy`), not org-plane operations (#3605). `spawn policy`
|
|
21693
|
-
// joins them on the same footing (#3979)
|
|
21694
|
-
|
|
22206
|
+
// joins them on the same footing (#3979); `dist status` does too (#5576) — the checkout's own
|
|
22207
|
+
// dist/BOM freshness read.
|
|
22208
|
+
["Setup and support", ["bootstrap", "secrets", "docs", "repo-index", "tests", "spawn", "dist"]],
|
|
21695
22209
|
["Coordinate and improve", ["wave", "report", "skill-lesson", "closure-rate"]]
|
|
21696
22210
|
];
|
|
21697
22211
|
var OPERATIONAL_TOP_LEVEL = /* @__PURE__ */ new Set(["org", "runtime", "plugin"]);
|
|
21698
|
-
var SUPPORT_PRIMARY = /* @__PURE__ */ new Set(["doctor", "whoami", "commands", "explain", "docs", "repo-index", "find", "tests", "spawn", "wave", "report", "skill-lesson", "closure-rate"]);
|
|
22212
|
+
var SUPPORT_PRIMARY = /* @__PURE__ */ new Set(["doctor", "whoami", "commands", "explain", "docs", "repo-index", "find", "tests", "spawn", "dist", "wave", "report", "skill-lesson", "closure-rate"]);
|
|
21699
22213
|
var TOP_LEVEL_ORDER = /* @__PURE__ */ new Map();
|
|
21700
22214
|
var HELP_GROUP_ORDER = /* @__PURE__ */ new Map();
|
|
21701
22215
|
var topLevelPosition = 0;
|
|
@@ -21748,6 +22262,7 @@ var COMMAND_OWNERSHIP = {
|
|
|
21748
22262
|
find: { module_owner: "cli/src/repo-index.ts", consumer: "agent-session" },
|
|
21749
22263
|
tests: { module_owner: "cli/src/test-policy-core.ts", consumer: "repo-gates" },
|
|
21750
22264
|
spawn: { module_owner: "cli/src/spawn-policy-core.ts", consumer: "repo-gates" },
|
|
22265
|
+
dist: { module_owner: "cli/src/dist-drift.ts", consumer: "repo-gates" },
|
|
21751
22266
|
wave: { module_owner: "cli/src/wave-land.ts", consumer: "campaign-orchestrator" },
|
|
21752
22267
|
report: { module_owner: "cli/src/report.ts", consumer: "campaign-orchestrator" },
|
|
21753
22268
|
"skill-lesson": { module_owner: "cli/src/skill-lesson.ts", consumer: "campaign-orchestrator" },
|
|
@@ -22269,13 +22784,13 @@ init_hub_url();
|
|
|
22269
22784
|
init_client_version();
|
|
22270
22785
|
|
|
22271
22786
|
// src/claude-binary-doctor.ts
|
|
22272
|
-
var
|
|
22273
|
-
var
|
|
22787
|
+
var import_node_fs22 = require("node:fs");
|
|
22788
|
+
var import_node_os12 = require("node:os");
|
|
22274
22789
|
var import_node_path19 = require("node:path");
|
|
22275
22790
|
|
|
22276
22791
|
// src/jerv-cli-spawn.ts
|
|
22277
|
-
var
|
|
22278
|
-
var
|
|
22792
|
+
var import_node_fs21 = require("node:fs");
|
|
22793
|
+
var import_node_os11 = require("node:os");
|
|
22279
22794
|
var import_node_path18 = require("node:path");
|
|
22280
22795
|
init_cli_shared();
|
|
22281
22796
|
var WIN_NAMES = ["jerv-cli.cmd", "jerv-cli.exe", "jerv-cli"];
|
|
@@ -22302,7 +22817,7 @@ function normalizeSpawnPathEntry(entry, platform2 = process.platform) {
|
|
|
22302
22817
|
if (msys) return `${msys[1].toUpperCase()}:\\${msys[2].replace(/\//g, "\\")}`;
|
|
22303
22818
|
return trimmed;
|
|
22304
22819
|
}
|
|
22305
|
-
function jervCliCandidateDirs(env = process.env, home = (0,
|
|
22820
|
+
function jervCliCandidateDirs(env = process.env, home = (0, import_node_os11.homedir)(), platform2 = process.platform) {
|
|
22306
22821
|
const seen = /* @__PURE__ */ new Set();
|
|
22307
22822
|
const out = [];
|
|
22308
22823
|
const push = (dir) => {
|
|
@@ -22323,7 +22838,7 @@ function jervCliCandidateDirs(env = process.env, home = (0, import_node_os10.hom
|
|
|
22323
22838
|
}
|
|
22324
22839
|
return out;
|
|
22325
22840
|
}
|
|
22326
|
-
function jervCliCandidatePaths(env = process.env, home = (0,
|
|
22841
|
+
function jervCliCandidatePaths(env = process.env, home = (0, import_node_os11.homedir)(), platform2 = process.platform) {
|
|
22327
22842
|
const names = platform2 === "win32" ? WIN_NAMES : POSIX_NAMES;
|
|
22328
22843
|
const out = [];
|
|
22329
22844
|
for (const dir of jervCliCandidateDirs(env, home, platform2)) {
|
|
@@ -22331,20 +22846,20 @@ function jervCliCandidatePaths(env = process.env, home = (0, import_node_os10.ho
|
|
|
22331
22846
|
}
|
|
22332
22847
|
return out;
|
|
22333
22848
|
}
|
|
22334
|
-
function resolveJervCliPath(env = process.env, home = (0,
|
|
22849
|
+
function resolveJervCliPath(env = process.env, home = (0, import_node_os11.homedir)(), platform2 = process.platform, exists = import_node_fs21.existsSync) {
|
|
22335
22850
|
for (const candidate2 of jervCliCandidatePaths(env, home, platform2)) {
|
|
22336
22851
|
if (exists(candidate2)) return candidate2;
|
|
22337
22852
|
}
|
|
22338
22853
|
return void 0;
|
|
22339
22854
|
}
|
|
22340
|
-
function resolveJervCliNodeEntry(shimPath, exists =
|
|
22855
|
+
function resolveJervCliNodeEntry(shimPath, exists = import_node_fs21.existsSync) {
|
|
22341
22856
|
const entry = (0, import_node_path18.join)((0, import_node_path18.dirname)(shimPath), JERV_CLI_ENTRY);
|
|
22342
22857
|
return exists(entry) ? entry : void 0;
|
|
22343
22858
|
}
|
|
22344
22859
|
function jervCliExecFileArgs(args, opts = {}) {
|
|
22345
22860
|
const platform2 = opts.platform ?? process.platform;
|
|
22346
|
-
const exists = opts.exists ??
|
|
22347
|
-
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);
|
|
22348
22863
|
if (resolved) {
|
|
22349
22864
|
const entry = resolveJervCliNodeEntry(resolved, exists);
|
|
22350
22865
|
if (entry) {
|
|
@@ -22434,7 +22949,7 @@ function globalNodeModulesRoots(host) {
|
|
|
22434
22949
|
};
|
|
22435
22950
|
const prefix = env.npm_config_prefix?.trim();
|
|
22436
22951
|
if (prefix) push(platform2 === "win32" ? (0, import_node_path19.join)(prefix, "node_modules") : (0, import_node_path19.join)(prefix, "lib", "node_modules"));
|
|
22437
|
-
for (const dir of jervCliCandidateDirs(env, host.home ?? (0,
|
|
22952
|
+
for (const dir of jervCliCandidateDirs(env, host.home ?? (0, import_node_os12.homedir)(), platform2)) {
|
|
22438
22953
|
push((0, import_node_path19.join)(dir, "node_modules"));
|
|
22439
22954
|
push((0, import_node_path19.join)((0, import_node_path19.dirname)(dir), "lib", "node_modules"));
|
|
22440
22955
|
}
|
|
@@ -22443,16 +22958,16 @@ function globalNodeModulesRoots(host) {
|
|
|
22443
22958
|
function readHead(path2) {
|
|
22444
22959
|
let fd;
|
|
22445
22960
|
try {
|
|
22446
|
-
fd = (0,
|
|
22961
|
+
fd = (0, import_node_fs22.openSync)(path2, "r");
|
|
22447
22962
|
const buffer = new Uint8Array(MAGIC_HEAD_BYTES);
|
|
22448
|
-
const read = (0,
|
|
22963
|
+
const read = (0, import_node_fs22.readSync)(fd, buffer, 0, MAGIC_HEAD_BYTES, 0);
|
|
22449
22964
|
return buffer.subarray(0, read);
|
|
22450
22965
|
} catch {
|
|
22451
22966
|
return void 0;
|
|
22452
22967
|
} finally {
|
|
22453
22968
|
if (fd !== void 0) {
|
|
22454
22969
|
try {
|
|
22455
|
-
(0,
|
|
22970
|
+
(0, import_node_fs22.closeSync)(fd);
|
|
22456
22971
|
} catch {
|
|
22457
22972
|
}
|
|
22458
22973
|
}
|
|
@@ -22460,7 +22975,7 @@ function readHead(path2) {
|
|
|
22460
22975
|
}
|
|
22461
22976
|
function fileBytes(path2) {
|
|
22462
22977
|
try {
|
|
22463
|
-
return (0,
|
|
22978
|
+
return (0, import_node_fs22.statSync)(path2).size;
|
|
22464
22979
|
} catch {
|
|
22465
22980
|
return void 0;
|
|
22466
22981
|
}
|
|
@@ -22474,13 +22989,13 @@ function readClaudeBinaryState(host = {}) {
|
|
|
22474
22989
|
const arch = host.arch ?? process.arch;
|
|
22475
22990
|
const magic = EXECUTABLE_MAGIC[platform2];
|
|
22476
22991
|
if (!magic) return void 0;
|
|
22477
|
-
const packageRoot = globalNodeModulesRoots(host).map((root) => (0, import_node_path19.join)(root, ...PACKAGE.split("/"))).find((dir) => (0,
|
|
22992
|
+
const packageRoot = globalNodeModulesRoots(host).map((root) => (0, import_node_path19.join)(root, ...PACKAGE.split("/"))).find((dir) => (0, import_node_fs22.existsSync)((0, import_node_path19.join)(dir, "package.json")));
|
|
22478
22993
|
if (!packageRoot) return void 0;
|
|
22479
22994
|
const keys = platformPackageKeys(platform2, arch);
|
|
22480
22995
|
const fallbackPackage = `${PACKAGE}-${keys[0]}`;
|
|
22481
22996
|
let manifest;
|
|
22482
22997
|
try {
|
|
22483
|
-
manifest = JSON.parse((0,
|
|
22998
|
+
manifest = JSON.parse((0, import_node_fs22.readFileSync)((0, import_node_path19.join)(packageRoot, "package.json"), "utf8"));
|
|
22484
22999
|
} catch (e) {
|
|
22485
23000
|
return {
|
|
22486
23001
|
state: "unreadable",
|
|
@@ -22511,7 +23026,7 @@ function readClaudeBinaryState(host = {}) {
|
|
|
22511
23026
|
(0, import_node_path19.join)(packageRoot, "node_modules", ...name.split("/"), binName),
|
|
22512
23027
|
(0, import_node_path19.join)((0, import_node_path19.dirname)((0, import_node_path19.dirname)(packageRoot)), ...name.split("/"), binName)
|
|
22513
23028
|
];
|
|
22514
|
-
const found = published.map((name) => ({ name, path: binIn(name).find((file) => (0,
|
|
23029
|
+
const found = published.map((name) => ({ name, path: binIn(name).find((file) => (0, import_node_fs22.existsSync)(file)) })).find((c) => c.path);
|
|
22515
23030
|
const platformPackage = found?.name ?? published[0];
|
|
22516
23031
|
let source;
|
|
22517
23032
|
let sourceProblem;
|
|
@@ -22522,7 +23037,7 @@ function readClaudeBinaryState(host = {}) {
|
|
|
22522
23037
|
} else {
|
|
22523
23038
|
source = { path: found.path, bytes: fileBytes(found.path) ?? 0 };
|
|
22524
23039
|
}
|
|
22525
|
-
if (!(0,
|
|
23040
|
+
if (!(0, import_node_fs22.existsSync)(binPath)) {
|
|
22526
23041
|
return { state: "missing", binPath, expectedMagic: magic.name, platformPackage, ...source ? { source } : {}, ...sourceProblem ? { sourceProblem } : {} };
|
|
22527
23042
|
}
|
|
22528
23043
|
const head = readHead(binPath);
|
|
@@ -22565,9 +23080,9 @@ function healClaudeBinary(host = {}, onStep) {
|
|
|
22565
23080
|
const platform2 = host.platform ?? process.platform;
|
|
22566
23081
|
const aside = `${probe.binPath}.stub-${Date.now()}`;
|
|
22567
23082
|
let renamed = false;
|
|
22568
|
-
if ((0,
|
|
23083
|
+
if ((0, import_node_fs22.existsSync)(probe.binPath)) {
|
|
22569
23084
|
try {
|
|
22570
|
-
(0,
|
|
23085
|
+
(0, import_node_fs22.renameSync)(probe.binPath, aside);
|
|
22571
23086
|
renamed = true;
|
|
22572
23087
|
onStep?.(`renamed the stub aside: ${aside}`);
|
|
22573
23088
|
} catch (e) {
|
|
@@ -22576,12 +23091,12 @@ function healClaudeBinary(host = {}, onStep) {
|
|
|
22576
23091
|
}
|
|
22577
23092
|
try {
|
|
22578
23093
|
onStep?.(`copying ${probe.source.path} \u2192 ${probe.binPath} (${(probe.source.bytes / 1e6).toFixed(0)} MB)`);
|
|
22579
|
-
(0,
|
|
22580
|
-
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);
|
|
22581
23096
|
} catch (e) {
|
|
22582
23097
|
if (renamed) {
|
|
22583
23098
|
try {
|
|
22584
|
-
(0,
|
|
23099
|
+
(0, import_node_fs22.renameSync)(aside, probe.binPath);
|
|
22585
23100
|
} catch {
|
|
22586
23101
|
return { ok: false, detail: `copy failed (${e.message}) and the stub could not be restored \u2014 the original is at ${aside}` };
|
|
22587
23102
|
}
|
|
@@ -22595,7 +23110,7 @@ function healClaudeBinary(host = {}, onStep) {
|
|
|
22595
23110
|
let kept = false;
|
|
22596
23111
|
if (renamed) {
|
|
22597
23112
|
try {
|
|
22598
|
-
(0,
|
|
23113
|
+
(0, import_node_fs22.rmSync)(aside);
|
|
22599
23114
|
} catch {
|
|
22600
23115
|
kept = true;
|
|
22601
23116
|
}
|
|
@@ -22960,6 +23475,7 @@ function trainPlan(command, options = {}) {
|
|
|
22960
23475
|
{ label: "verify current branch is development", gated: true },
|
|
22961
23476
|
{ label: "verify registry META for this project", command: "mmi-cli oracle org project get <owner/repo>", gated: true },
|
|
22962
23477
|
{ label: "preflight required main secret names", command: "mmi-cli vault secrets preflight --stage main --repo <owner/repo>", gated: true },
|
|
23478
|
+
{ label: "preflight GitHub Actions hosted job start (billing/spending) before minting a tag", command: "train dispatches actions-job-start-canary.yml on MMI-Hub (#5604); exact-run --retry-publish skips this", gated: true },
|
|
22963
23479
|
{ label: "merge development to main", gated: true },
|
|
22964
23480
|
{ label: "fold the version bump into the release commit (Hub: full distribution set; app repos: root package manifest) \u2014 runs inside the apply step, no separate bump PR", gated: true },
|
|
22965
23481
|
{ label: "tag release and publish GitHub Release", gated: true },
|
|
@@ -22975,6 +23491,7 @@ function trainPlan(command, options = {}) {
|
|
|
22975
23491
|
{ label: "guard: refuse if origin/rc carries content not in development (a dev -> main release would drop it)", command: "git rev-list --count --right-only --cherry-pick --no-merges origin/development...origin/rc", gated: true },
|
|
22976
23492
|
{ label: "verify registry META for this project", command: "mmi-cli oracle org project get <owner/repo>", gated: true },
|
|
22977
23493
|
{ label: "preflight required main secret names", command: "mmi-cli vault secrets preflight --stage main --repo <owner/repo>", gated: true },
|
|
23494
|
+
{ label: "preflight GitHub Actions hosted job start (billing/spending) before minting a tag", command: "train dispatches actions-job-start-canary.yml on MMI-Hub (#5604); exact-run --retry-publish skips this", gated: true },
|
|
22978
23495
|
{ label: "merge development to main (rc skipped)", gated: true },
|
|
22979
23496
|
{ label: "fold the version bump into the release commit \u2014 runs inside the apply step, no separate bump PR", gated: true },
|
|
22980
23497
|
{ label: "tag release and publish GitHub Release", gated: true },
|
|
@@ -22989,6 +23506,7 @@ function trainPlan(command, options = {}) {
|
|
|
22989
23506
|
{ label: "verify current branch is rc", gated: true },
|
|
22990
23507
|
{ label: "verify registry META for this project", command: "mmi-cli oracle org project get <owner/repo>", gated: true },
|
|
22991
23508
|
{ label: "preflight required main secret names", command: "mmi-cli vault secrets preflight --stage main --repo <owner/repo>", gated: true },
|
|
23509
|
+
{ label: "preflight GitHub Actions hosted job start (billing/spending) before minting a tag", command: "train dispatches actions-job-start-canary.yml on MMI-Hub (#5604); exact-run --retry-publish skips this", gated: true },
|
|
22992
23510
|
{ label: "verify every main-only hotfix commit is covered by the rc candidate (the guard runs automatically inside the apply step below; --ack <sha> overrides a verified, trailer-less port)", command: "mmi-cli devops release --apply [--ack <sha>]", gated: true },
|
|
22993
23511
|
{ label: "merge rc to main", gated: true },
|
|
22994
23512
|
{ label: "fold the version bump into the release commit (app repos: root package manifest) \u2014 runs inside the apply step, no separate bump PR", gated: true },
|
|
@@ -23077,14 +23595,14 @@ function renderVerifyBroker(input) {
|
|
|
23077
23595
|
|
|
23078
23596
|
// src/tenant-artifact.ts
|
|
23079
23597
|
var import_node_crypto5 = require("node:crypto");
|
|
23080
|
-
var
|
|
23598
|
+
var import_node_fs23 = require("node:fs");
|
|
23081
23599
|
var import_promises3 = require("node:fs/promises");
|
|
23082
23600
|
var import_node_path20 = require("node:path");
|
|
23083
23601
|
var ARTIFACT_ID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
|
|
23084
23602
|
var MAX_BYTES = 5 * 1024 * 1024 * 1024;
|
|
23085
23603
|
async function sha256File(path2) {
|
|
23086
23604
|
const hash = (0, import_node_crypto5.createHash)("sha256");
|
|
23087
|
-
for await (const chunk of (0,
|
|
23605
|
+
for await (const chunk of (0, import_node_fs23.createReadStream)(path2)) hash.update(chunk);
|
|
23088
23606
|
return hash.digest("hex");
|
|
23089
23607
|
}
|
|
23090
23608
|
async function putTenantArtifact(repo, stage, inputPath, deps) {
|
|
@@ -23093,8 +23611,8 @@ async function putTenantArtifact(repo, stage, inputPath, deps) {
|
|
|
23093
23611
|
const info = await (0, import_promises3.stat)(path2);
|
|
23094
23612
|
if (!info.isFile()) throw new Error("tenant artifact put: input path must be a file");
|
|
23095
23613
|
if (!Number.isSafeInteger(info.size) || info.size < 1 || info.size > MAX_BYTES) throw new Error(`tenant artifact put: file must be 1..${MAX_BYTES} bytes`);
|
|
23096
|
-
const
|
|
23097
|
-
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);
|
|
23098
23616
|
if (!prepared.ok) {
|
|
23099
23617
|
const detail = prepared.body?.error ?? prepared.error ?? `HTTP ${prepared.status}`;
|
|
23100
23618
|
throw new Error(`tenant artifact put: ${detail}`);
|
|
@@ -23107,7 +23625,7 @@ async function putTenantArtifact(repo, stage, inputPath, deps) {
|
|
|
23107
23625
|
return [key, value];
|
|
23108
23626
|
}));
|
|
23109
23627
|
headers["content-length"] = String(info.size);
|
|
23110
|
-
const stream = (0,
|
|
23628
|
+
const stream = (0, import_node_fs23.createReadStream)(path2);
|
|
23111
23629
|
let uploaded;
|
|
23112
23630
|
try {
|
|
23113
23631
|
uploaded = await fetch(body.uploadUrl, {
|
|
@@ -23122,8 +23640,8 @@ async function putTenantArtifact(repo, stage, inputPath, deps) {
|
|
|
23122
23640
|
throw error;
|
|
23123
23641
|
}
|
|
23124
23642
|
if (!uploaded.ok) throw new Error(`tenant artifact put: object upload failed (HTTP ${uploaded.status})`);
|
|
23125
|
-
if (body.size !== info.size || body.sha256 !==
|
|
23126
|
-
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 };
|
|
23127
23645
|
}
|
|
23128
23646
|
|
|
23129
23647
|
// src/hotfix-coverage.ts
|
|
@@ -23309,7 +23827,7 @@ function clean3(out) {
|
|
|
23309
23827
|
return out.trim();
|
|
23310
23828
|
}
|
|
23311
23829
|
function sleeper(deps) {
|
|
23312
|
-
return deps.sleep ?? ((ms) => new Promise((
|
|
23830
|
+
return deps.sleep ?? ((ms) => new Promise((resolve6) => setTimeout(resolve6, ms)));
|
|
23313
23831
|
}
|
|
23314
23832
|
function normalizeHotfixVersion(input) {
|
|
23315
23833
|
const m = /^v?(\d+\.\d+\.\d+)$/.exec(input.trim());
|
|
@@ -24304,7 +24822,7 @@ function renderDeployPortDoctor(report) {
|
|
|
24304
24822
|
// src/repo-index.ts
|
|
24305
24823
|
var import_node_crypto6 = require("node:crypto");
|
|
24306
24824
|
var import_node_child_process12 = require("node:child_process");
|
|
24307
|
-
var
|
|
24825
|
+
var import_node_fs24 = require("node:fs");
|
|
24308
24826
|
var import_node_path21 = require("node:path");
|
|
24309
24827
|
|
|
24310
24828
|
// ../infra/repo-index-path-policy.mjs
|
|
@@ -24505,10 +25023,10 @@ function loadReadmeHints(cwd, candidatePaths) {
|
|
|
24505
25023
|
for (const rel of readmes) {
|
|
24506
25024
|
if (isHardDeniedPath(rel)) continue;
|
|
24507
25025
|
const abs = (0, import_node_path21.join)(cwd, ...rel.split("/"));
|
|
24508
|
-
if (!(0,
|
|
25026
|
+
if (!(0, import_node_fs24.existsSync)(abs)) continue;
|
|
24509
25027
|
let text;
|
|
24510
25028
|
try {
|
|
24511
|
-
text = (0,
|
|
25029
|
+
text = (0, import_node_fs24.readFileSync)(abs, "utf8");
|
|
24512
25030
|
} catch {
|
|
24513
25031
|
continue;
|
|
24514
25032
|
}
|
|
@@ -24544,10 +25062,10 @@ function rebuildRepoIndex(cwd, repoSlug3) {
|
|
|
24544
25062
|
if (ignored.has(rel)) continue;
|
|
24545
25063
|
if (isHardDeniedPath(rel)) continue;
|
|
24546
25064
|
const abs = (0, import_node_path21.join)(cwd, ...rel.split("/"));
|
|
24547
|
-
if (!(0,
|
|
25065
|
+
if (!(0, import_node_fs24.existsSync)(abs)) continue;
|
|
24548
25066
|
let text;
|
|
24549
25067
|
try {
|
|
24550
|
-
text = (0,
|
|
25068
|
+
text = (0, import_node_fs24.readFileSync)(abs, "utf8");
|
|
24551
25069
|
} catch {
|
|
24552
25070
|
continue;
|
|
24553
25071
|
}
|
|
@@ -24572,16 +25090,16 @@ function rebuildRepoIndex(cwd, repoSlug3) {
|
|
|
24572
25090
|
entries
|
|
24573
25091
|
};
|
|
24574
25092
|
const store = repoIndexStorePath(cwd);
|
|
24575
|
-
(0,
|
|
24576
|
-
(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)}
|
|
24577
25095
|
`, "utf8");
|
|
24578
25096
|
return projection;
|
|
24579
25097
|
}
|
|
24580
25098
|
function loadRepoIndex(cwd) {
|
|
24581
25099
|
const store = repoIndexStorePath(cwd);
|
|
24582
|
-
if (!(0,
|
|
25100
|
+
if (!(0, import_node_fs24.existsSync)(store)) return null;
|
|
24583
25101
|
try {
|
|
24584
|
-
const raw = JSON.parse((0,
|
|
25102
|
+
const raw = JSON.parse((0, import_node_fs24.readFileSync)(store, "utf8"));
|
|
24585
25103
|
if (raw?.schema !== REPO_INDEX_SCHEMA || !Array.isArray(raw.entries)) return null;
|
|
24586
25104
|
return raw;
|
|
24587
25105
|
} catch {
|
|
@@ -24665,8 +25183,8 @@ init_compat();
|
|
|
24665
25183
|
// src/repo-index-v4/builder.ts
|
|
24666
25184
|
var import_node_crypto9 = require("node:crypto");
|
|
24667
25185
|
var import_node_child_process14 = require("node:child_process");
|
|
24668
|
-
var
|
|
24669
|
-
var
|
|
25186
|
+
var import_node_fs26 = require("node:fs");
|
|
25187
|
+
var import_node_os13 = require("node:os");
|
|
24670
25188
|
var import_node_path23 = require("node:path");
|
|
24671
25189
|
|
|
24672
25190
|
// ../infra/repo-index-material-buckets.mjs
|
|
@@ -24766,7 +25284,7 @@ function buildRepoIndexMaterialLayout(repo, chunks, embeddings) {
|
|
|
24766
25284
|
|
|
24767
25285
|
// src/repo-index-v4/chunks.ts
|
|
24768
25286
|
var import_node_crypto8 = require("node:crypto");
|
|
24769
|
-
var
|
|
25287
|
+
var import_node_fs25 = require("node:fs");
|
|
24770
25288
|
var import_node_path22 = require("node:path");
|
|
24771
25289
|
|
|
24772
25290
|
// src/repo-index-v4/language.ts
|
|
@@ -24932,10 +25450,10 @@ async function buildStructuralChunksForPaths(cwd, repo, commit, paths) {
|
|
|
24932
25450
|
const chunks = [];
|
|
24933
25451
|
for (const path2 of paths) {
|
|
24934
25452
|
const absolute = (0, import_node_path22.join)(cwd, ...path2.split("/"));
|
|
24935
|
-
if (!(0,
|
|
25453
|
+
if (!(0, import_node_fs25.existsSync)(absolute)) continue;
|
|
24936
25454
|
let source;
|
|
24937
25455
|
try {
|
|
24938
|
-
source = (0,
|
|
25456
|
+
source = (0, import_node_fs25.readFileSync)(absolute, "utf8");
|
|
24939
25457
|
} catch {
|
|
24940
25458
|
continue;
|
|
24941
25459
|
}
|
|
@@ -24997,7 +25515,8 @@ function planRepoIndexV4Delta(opts) {
|
|
|
24997
25515
|
const baseCommit = opts.baseCommit ? opts.baseCommit.toLowerCase() : null;
|
|
24998
25516
|
const full = (fallbackReason) => ({ mode: "full", headCommit, fallbackReason, ...baseCommit ? { baseCommit } : {} });
|
|
24999
25517
|
if (opts.forceFull) return full("explicit-full-rebuild");
|
|
25000
|
-
if (!baseCommit || !COMMIT.test(baseCommit)
|
|
25518
|
+
if (!baseCommit || !COMMIT.test(baseCommit)) return full("no-active-authority");
|
|
25519
|
+
if (baseCommit === headCommit) return { mode: "unchanged", baseCommit, headCommit };
|
|
25001
25520
|
if (opts.basePipelineCompatible === false) return full("incompatible-base-provenance");
|
|
25002
25521
|
if (opts.hasPriorMaterial === false) return full("no-prior-material");
|
|
25003
25522
|
const { git: git3 } = opts;
|
|
@@ -25063,7 +25582,7 @@ function gitInfo(cwd) {
|
|
|
25063
25582
|
}
|
|
25064
25583
|
function prior(cwd) {
|
|
25065
25584
|
try {
|
|
25066
|
-
const p = JSON.parse((0,
|
|
25585
|
+
const p = JSON.parse((0, import_node_fs26.readFileSync)(statePath(cwd), "utf8"));
|
|
25067
25586
|
return p?.schemaVersion === 4 && p?.manifest?.immutable === true ? p : null;
|
|
25068
25587
|
} catch {
|
|
25069
25588
|
return null;
|
|
@@ -25082,7 +25601,7 @@ function embeddingInput(cwd, chunk) {
|
|
|
25082
25601
|
${chunk.symbol ?? ""}
|
|
25083
25602
|
${chunk.blurb ?? ""}`;
|
|
25084
25603
|
try {
|
|
25085
|
-
const lines = (0,
|
|
25604
|
+
const lines = (0, import_node_fs26.readFileSync)((0, import_node_path23.join)(cwd, ...chunk.path.split("/")), "utf8").split(/\r?\n/);
|
|
25086
25605
|
const body = lines.slice(Math.max(0, (c.startLine ?? 1) - 1), Math.min(lines.length, c.endLine ?? lines.length)).join("\n");
|
|
25087
25606
|
return body.slice(0, 1e5);
|
|
25088
25607
|
} catch {
|
|
@@ -25097,18 +25616,18 @@ function runEmbedderOnce(cwd, chunks, modelDirectory, createdAt) {
|
|
|
25097
25616
|
if (!chunks.length) return { ok: true, embeddings: [] };
|
|
25098
25617
|
const orchestratorRunner = (0, import_node_path23.join)(process.cwd(), "repo-indexer", "src", "batch.mjs");
|
|
25099
25618
|
const targetRunner = (0, import_node_path23.join)(cwd, "repo-indexer", "src", "batch.mjs");
|
|
25100
|
-
const file = (0,
|
|
25101
|
-
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" };
|
|
25102
25621
|
const request = { texts: chunks.map((chunk) => ({ id: chunk.id, text: embeddingInput(cwd, chunk) })), maxBatch: V4_EMBED_BATCH };
|
|
25103
25622
|
const env = { ...process.env, ...modelDirectory ? { MMI_REPO_INDEXER_MODEL_DIR: modelDirectory } : {} };
|
|
25104
|
-
const requestDir = (0,
|
|
25623
|
+
const requestDir = (0, import_node_fs26.mkdtempSync)((0, import_node_path23.join)((0, import_node_os13.tmpdir)(), "mmi-repo-index-req-"));
|
|
25105
25624
|
const requestFile = (0, import_node_path23.join)(requestDir, "request.json");
|
|
25106
|
-
(0,
|
|
25625
|
+
(0, import_node_fs26.writeFileSync)(requestFile, JSON.stringify(request));
|
|
25107
25626
|
let result;
|
|
25108
25627
|
try {
|
|
25109
25628
|
result = (0, import_node_child_process14.spawnSync)(process.execPath, [file, requestFile], { encoding: "utf8", windowsHide: true, timeout: V4_EMBED_TIMEOUT_MS, maxBuffer: V4_MAX_ARTIFACT_BYTES, env });
|
|
25110
25629
|
} finally {
|
|
25111
|
-
(0,
|
|
25630
|
+
(0, import_node_fs26.rmSync)(requestDir, { recursive: true, force: true });
|
|
25112
25631
|
}
|
|
25113
25632
|
if (result.error || result.status !== 0) {
|
|
25114
25633
|
const cleanExit2 = result.error === void 0 && result.signal === void 0 && typeof result.status === "number" && result.status !== 0;
|
|
@@ -25241,8 +25760,8 @@ async function buildRepoIndexV4Detailed(cwd, repo, opts = {}) {
|
|
|
25241
25760
|
const encoded = canonicalJson(envelope);
|
|
25242
25761
|
if (Buffer.byteLength(encoded) > V4_MAX_ARTIFACT_BYTES) throw new Error(`repo-index v4 artifact exceeds ${V4_MAX_ARTIFACT_BYTES} byte ceiling`);
|
|
25243
25762
|
const path2 = statePath(cwd);
|
|
25244
|
-
(0,
|
|
25245
|
-
(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)}
|
|
25246
25765
|
`, "utf8");
|
|
25247
25766
|
const metrics = {
|
|
25248
25767
|
mode: delta ? "delta" : "full",
|
|
@@ -25793,8 +26312,8 @@ async function gcRepoIndexCloud(deps) {
|
|
|
25793
26312
|
}
|
|
25794
26313
|
|
|
25795
26314
|
// src/repo-index-sync.ts
|
|
25796
|
-
var
|
|
25797
|
-
var
|
|
26315
|
+
var import_node_fs28 = require("node:fs");
|
|
26316
|
+
var import_node_os14 = require("node:os");
|
|
25798
26317
|
var import_node_path25 = require("node:path");
|
|
25799
26318
|
var import_node_child_process15 = require("node:child_process");
|
|
25800
26319
|
|
|
@@ -25834,7 +26353,7 @@ function repoIndexRoster(projects) {
|
|
|
25834
26353
|
}
|
|
25835
26354
|
|
|
25836
26355
|
// src/repo-index-v4/edges.ts
|
|
25837
|
-
var
|
|
26356
|
+
var import_node_fs27 = require("node:fs");
|
|
25838
26357
|
var import_node_path24 = require("node:path");
|
|
25839
26358
|
var V4_GRAPH_MAX_EDGES = 5e3;
|
|
25840
26359
|
var V4_GRAPH_MAX_EDGES_PER_FILE = 64;
|
|
@@ -25924,9 +26443,9 @@ function buildGraphEdges(cwd, repo, commit, rosterRepos2) {
|
|
|
25924
26443
|
for (const path2 of paths) {
|
|
25925
26444
|
if (ignored.has(path2) || isHardDeniedPath(path2) || !SUPPORTED.has(extension(path2))) continue;
|
|
25926
26445
|
const absolute = (0, import_node_path24.join)(cwd, ...path2.split("/"));
|
|
25927
|
-
if (!(0,
|
|
26446
|
+
if (!(0, import_node_fs27.existsSync)(absolute)) continue;
|
|
25928
26447
|
try {
|
|
25929
|
-
edges.push(...graphEdgesForSource(repo, commit, path2, (0,
|
|
26448
|
+
edges.push(...graphEdgesForSource(repo, commit, path2, (0, import_node_fs27.readFileSync)(absolute, "utf8"), rosterRepos2));
|
|
25930
26449
|
} catch {
|
|
25931
26450
|
}
|
|
25932
26451
|
if (edges.length >= V4_GRAPH_MAX_EDGES) break;
|
|
@@ -25936,10 +26455,10 @@ function buildGraphEdges(cwd, repo, commit, rosterRepos2) {
|
|
|
25936
26455
|
|
|
25937
26456
|
// src/repo-index-sync.ts
|
|
25938
26457
|
function execFileUtf8(file, args) {
|
|
25939
|
-
return new Promise((
|
|
26458
|
+
return new Promise((resolve6, reject) => {
|
|
25940
26459
|
(0, import_node_child_process15.execFile)(file, args, { encoding: "utf8", windowsHide: true }, (error, stdout) => {
|
|
25941
26460
|
if (error) reject(error);
|
|
25942
|
-
else
|
|
26461
|
+
else resolve6(String(stdout ?? ""));
|
|
25943
26462
|
});
|
|
25944
26463
|
});
|
|
25945
26464
|
}
|
|
@@ -26135,11 +26654,12 @@ async function syncEstateRepoIndex(opts) {
|
|
|
26135
26654
|
}
|
|
26136
26655
|
let targetCommit = requestedCommit || void 0;
|
|
26137
26656
|
if (base && !forceFull) {
|
|
26657
|
+
let headUnreadable = false;
|
|
26138
26658
|
if (!targetCommit) {
|
|
26139
26659
|
try {
|
|
26140
26660
|
targetCommit = await remoteHead(repo, opts.githubToken);
|
|
26141
26661
|
} catch {
|
|
26142
|
-
|
|
26662
|
+
headUnreadable = true;
|
|
26143
26663
|
}
|
|
26144
26664
|
}
|
|
26145
26665
|
const provenance = await fetchRepoIndexV4ProvenanceCloud(repo, opts.deps).catch(
|
|
@@ -26163,6 +26683,12 @@ async function syncEstateRepoIndex(opts) {
|
|
|
26163
26683
|
emit({ row: row2, warning });
|
|
26164
26684
|
return { repo, row: row2, warning, base, expectedActiveDigest };
|
|
26165
26685
|
}
|
|
26686
|
+
if (headUnreadable) {
|
|
26687
|
+
const row2 = { repo, reason: "head-unreadable", action: "skip", activeCommit: base.commit };
|
|
26688
|
+
const warning = `${repo}: remote HEAD unreadable; leaving verified-ready authority at ${base.commit}`;
|
|
26689
|
+
emit({ row: row2, warning });
|
|
26690
|
+
return { repo, row: row2, warning, base, expectedActiveDigest };
|
|
26691
|
+
}
|
|
26166
26692
|
}
|
|
26167
26693
|
const row = {
|
|
26168
26694
|
repo,
|
|
@@ -26183,13 +26709,17 @@ async function syncEstateRepoIndex(opts) {
|
|
|
26183
26709
|
for (const entry of classified) {
|
|
26184
26710
|
if (entry.row.action !== "build") continue;
|
|
26185
26711
|
const { repo, base, expectedActiveDigest } = entry;
|
|
26186
|
-
const dir = (0,
|
|
26712
|
+
const dir = (0, import_node_fs28.mkdtempSync)((0, import_node_path25.join)((0, import_node_os14.tmpdir)(), "mmi-repo-index-"));
|
|
26187
26713
|
try {
|
|
26188
26714
|
shallowClone(repo, dir, opts.githubToken);
|
|
26189
26715
|
if (requestedCommit) checkoutExactCommit(repo, dir, opts.githubToken, requestedCommit);
|
|
26190
26716
|
let v4;
|
|
26191
26717
|
try {
|
|
26192
26718
|
const plan = await planDeltaBuild(repo, dir, base, opts.deps, opts.githubToken, forceFull);
|
|
26719
|
+
if (plan.plan.mode === "unchanged") {
|
|
26720
|
+
skipped.push(`${repo}: unchanged verified-ready authority at ${plan.plan.headCommit}`);
|
|
26721
|
+
continue;
|
|
26722
|
+
}
|
|
26193
26723
|
if (base && plan.plan.mode === "full") skipped.push(`${repo}: full rebuild (${plan.plan.fallbackReason})`);
|
|
26194
26724
|
v4 = await buildRepoIndexV4Detailed(dir, repo, {
|
|
26195
26725
|
modelDirectory: process.env.MMI_REPO_INDEXER_MODEL_DIR,
|
|
@@ -26233,7 +26763,7 @@ async function syncEstateRepoIndex(opts) {
|
|
|
26233
26763
|
failed.push({ repo, error: e.message });
|
|
26234
26764
|
} finally {
|
|
26235
26765
|
try {
|
|
26236
|
-
(0,
|
|
26766
|
+
(0, import_node_fs28.rmSync)(dir, { recursive: true, force: true });
|
|
26237
26767
|
} catch {
|
|
26238
26768
|
}
|
|
26239
26769
|
}
|
|
@@ -26242,7 +26772,7 @@ async function syncEstateRepoIndex(opts) {
|
|
|
26242
26772
|
}
|
|
26243
26773
|
|
|
26244
26774
|
// src/repo-index-health.ts
|
|
26245
|
-
var
|
|
26775
|
+
var import_node_fs29 = require("node:fs");
|
|
26246
26776
|
|
|
26247
26777
|
// testdata/repo-index-golden-queries.json
|
|
26248
26778
|
var repo_index_golden_queries_default = {
|
|
@@ -26346,7 +26876,7 @@ function assertGoldenSuite(raw, source) {
|
|
|
26346
26876
|
function loadGoldenSuite(path2) {
|
|
26347
26877
|
let text;
|
|
26348
26878
|
try {
|
|
26349
|
-
text = (0,
|
|
26879
|
+
text = (0, import_node_fs29.readFileSync)(path2, "utf8");
|
|
26350
26880
|
} catch (e) {
|
|
26351
26881
|
throw new Error(`golden suite unreadable at ${path2}: ${e.message}`);
|
|
26352
26882
|
}
|
|
@@ -26516,7 +27046,7 @@ async function runRepoIndexHealth(opts) {
|
|
|
26516
27046
|
|
|
26517
27047
|
// src/spawn-policy-core.ts
|
|
26518
27048
|
var import_node_child_process16 = require("node:child_process");
|
|
26519
|
-
var
|
|
27049
|
+
var import_node_fs30 = require("node:fs");
|
|
26520
27050
|
var import_node_path26 = require("node:path");
|
|
26521
27051
|
var SPAWNERS = ["spawn", "spawnSync", "exec", "execSync", "execFile", "execFileSync"];
|
|
26522
27052
|
var CALL_SOURCE = String.raw`(^|[^.\w$])(${SPAWNERS.join("|")})\s*\(`;
|
|
@@ -26603,7 +27133,7 @@ function runSpawnPolicy(root) {
|
|
|
26603
27133
|
for (const file of files) {
|
|
26604
27134
|
let raw;
|
|
26605
27135
|
try {
|
|
26606
|
-
raw = (0,
|
|
27136
|
+
raw = (0, import_node_fs30.readFileSync)((0, import_node_path26.join)(root, file), "utf8");
|
|
26607
27137
|
} catch {
|
|
26608
27138
|
continue;
|
|
26609
27139
|
}
|
|
@@ -26621,7 +27151,7 @@ function runSpawnPolicy(root) {
|
|
|
26621
27151
|
|
|
26622
27152
|
// src/test-policy-core.ts
|
|
26623
27153
|
var import_node_child_process17 = require("node:child_process");
|
|
26624
|
-
var
|
|
27154
|
+
var import_node_fs31 = require("node:fs");
|
|
26625
27155
|
var import_node_path27 = require("node:path");
|
|
26626
27156
|
|
|
26627
27157
|
// ../scripts/test-command-policy-core.mjs
|
|
@@ -27002,7 +27532,7 @@ function loadPolicy(root, readFile7 = readFileOrNull2) {
|
|
|
27002
27532
|
}
|
|
27003
27533
|
function readFileOrNull2(path2) {
|
|
27004
27534
|
try {
|
|
27005
|
-
return (0,
|
|
27535
|
+
return (0, import_node_fs31.readFileSync)(path2, "utf8");
|
|
27006
27536
|
} catch {
|
|
27007
27537
|
return null;
|
|
27008
27538
|
}
|
|
@@ -27030,10 +27560,10 @@ function classify(changed, policy, present = () => false) {
|
|
|
27030
27560
|
const removedProtected = [...removed].filter((p) => protectedBy.has(p)).map((p) => ({ path: p, why: protectedBy.get(p) ?? "" }));
|
|
27031
27561
|
return { mandatoryHits, untestedHits, testChanges, meaningfulTestChanges, addedTests, removedProtected };
|
|
27032
27562
|
}
|
|
27033
|
-
function unresolvedProtectedEntries(policy, root, exists = (path2) => (0,
|
|
27563
|
+
function unresolvedProtectedEntries(policy, root, exists = (path2) => (0, import_node_fs31.existsSync)(path2)) {
|
|
27034
27564
|
return (policy.protected ?? []).map((p) => p.path).filter((p) => !exists((0, import_node_path27.join)(root, p)));
|
|
27035
27565
|
}
|
|
27036
|
-
function unresolvedSatisfiers(policy, root, exists = (path2) => (0,
|
|
27566
|
+
function unresolvedSatisfiers(policy, root, exists = (path2) => (0, import_node_fs31.existsSync)(path2)) {
|
|
27037
27567
|
const declared = (policy.mandatory ?? []).flatMap((m) => m.satisfiedBy ?? []);
|
|
27038
27568
|
return [...new Set(declared)].filter((p) => !exists((0, import_node_path27.join)(root, p)));
|
|
27039
27569
|
}
|
|
@@ -27224,7 +27754,7 @@ function blobAt(base, path2, cwd) {
|
|
|
27224
27754
|
}
|
|
27225
27755
|
function runTestPolicy(root, deps = {}) {
|
|
27226
27756
|
const policy = deps.policy ?? loadPolicy(root);
|
|
27227
|
-
const exists = deps.exists ?? ((path2) => (0,
|
|
27757
|
+
const exists = deps.exists ?? ((path2) => (0, import_node_fs31.existsSync)(path2));
|
|
27228
27758
|
const counts = { mandatoryCount: (policy.mandatory ?? []).length, protectedCount: (policy.protected ?? []).length };
|
|
27229
27759
|
const base = deps.changed ? "(injected)" : resolveBase(root, deps.base);
|
|
27230
27760
|
const refusal = deps.changed ? null : untrustworthyRange(root, base);
|
|
@@ -27287,9 +27817,197 @@ function runTestPolicy(root, deps = {}) {
|
|
|
27287
27817
|
return result;
|
|
27288
27818
|
}
|
|
27289
27819
|
|
|
27290
|
-
// src/
|
|
27291
|
-
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");
|
|
27292
27830
|
var import_node_path28 = require("node:path");
|
|
27831
|
+
var slash = (value) => value.replaceAll("\\", "/");
|
|
27832
|
+
function repoPath(root, declaredPath, label) {
|
|
27833
|
+
const absoluteRoot = (0, import_node_path28.resolve)(root);
|
|
27834
|
+
const target = (0, import_node_path28.resolve)(root, declaredPath);
|
|
27835
|
+
if (target !== absoluteRoot && !target.startsWith(`${absoluteRoot}${import_node_path28.sep}`)) {
|
|
27836
|
+
throw new Error(`${label} ${declaredPath} escapes the repository root`);
|
|
27837
|
+
}
|
|
27838
|
+
return target;
|
|
27839
|
+
}
|
|
27840
|
+
function digestFiles(files) {
|
|
27841
|
+
const hash = (0, import_node_crypto11.createHash)("sha256");
|
|
27842
|
+
for (const file of [...files].sort((a, b) => a.relative.localeCompare(b.relative))) {
|
|
27843
|
+
const content = file.stat.isSymbolicLink() ? Buffer.from((0, import_node_fs32.readlinkSync)(file.absolute), "utf8") : (0, import_node_fs32.readFileSync)(file.absolute);
|
|
27844
|
+
hash.update(file.relative, "utf8");
|
|
27845
|
+
hash.update("\0");
|
|
27846
|
+
hash.update(file.stat.isSymbolicLink() ? "symlink" : "file", "utf8");
|
|
27847
|
+
hash.update("\0");
|
|
27848
|
+
hash.update(String(content.length), "utf8");
|
|
27849
|
+
hash.update("\0");
|
|
27850
|
+
hash.update(content);
|
|
27851
|
+
hash.update("\0");
|
|
27852
|
+
}
|
|
27853
|
+
return `sha256:${hash.digest("hex")}`;
|
|
27854
|
+
}
|
|
27855
|
+
function digestPackedFiles(packageRoot, packedFiles) {
|
|
27856
|
+
return digestFiles(packedFiles.map((path2) => {
|
|
27857
|
+
const absolute = repoPath(packageRoot, path2, "packed artifact identity path");
|
|
27858
|
+
if (!(0, import_node_fs32.existsSync)(absolute)) throw new Error(`packed artifact identity path ${path2} does not exist`);
|
|
27859
|
+
return { absolute, relative: slash(path2), stat: (0, import_node_fs32.lstatSync)(absolute) };
|
|
27860
|
+
}));
|
|
27861
|
+
}
|
|
27862
|
+
|
|
27863
|
+
// src/dist-drift.ts
|
|
27864
|
+
var DIST_ARTIFACTS = [
|
|
27865
|
+
{ path: "cli/dist/index.cjs", packageDir: "cli", output: "index.cjs" },
|
|
27866
|
+
{ path: "cli/dist/main.cjs", packageDir: "cli", output: "main.cjs" },
|
|
27867
|
+
{ path: "cli/dist/repo-index-v4.cjs", packageDir: "cli", output: "repo-index-v4.cjs" },
|
|
27868
|
+
{ path: "updater/dist/index.cjs", packageDir: "updater", output: "index.cjs" }
|
|
27869
|
+
];
|
|
27870
|
+
var BOM_DIST_TREE_ID = "mmi-cli-dist";
|
|
27871
|
+
var ABSENT = "absent";
|
|
27872
|
+
var sha2563 = (bytes) => `sha256:${(0, import_node_crypto12.createHash)("sha256").update(bytes).digest("hex")}`;
|
|
27873
|
+
function artifactDrift(path2, committedBytes, rebuiltBytes) {
|
|
27874
|
+
const committed = committedBytes ? sha2563(committedBytes) : ABSENT;
|
|
27875
|
+
const rebuiltExpected = rebuiltBytes ? sha2563(rebuiltBytes) : ABSENT;
|
|
27876
|
+
return { path: path2, status: committed === rebuiltExpected ? "current" : "stale", committed, rebuiltExpected };
|
|
27877
|
+
}
|
|
27878
|
+
function distTreeIdentity(labels, rebuilt, committed, digest) {
|
|
27879
|
+
const entries = [];
|
|
27880
|
+
for (const label of labels) {
|
|
27881
|
+
const bytes = rebuilt(label) ?? committed(label);
|
|
27882
|
+
if (!bytes) {
|
|
27883
|
+
return { rebuiltExpected: ABSENT, note: `recorded tree file ${label} is absent from the checkout` };
|
|
27884
|
+
}
|
|
27885
|
+
entries.push({ path: label, bytes });
|
|
27886
|
+
}
|
|
27887
|
+
return { rebuiltExpected: digest(entries) };
|
|
27888
|
+
}
|
|
27889
|
+
function npmPackIdentity(id, packageDir, identity, rebuilt, tree, digest) {
|
|
27890
|
+
const recorded = identity?.value ?? ABSENT;
|
|
27891
|
+
const kind = identity?.kind ?? "npm-pack";
|
|
27892
|
+
if (!identity || !Array.isArray(identity.files) || identity.files.length === 0) {
|
|
27893
|
+
return { id, kind, status: "stale", recorded, rebuiltExpected: ABSENT, note: "identity records no packed file list to recompute against" };
|
|
27894
|
+
}
|
|
27895
|
+
const entries = [];
|
|
27896
|
+
for (const label of identity.files) {
|
|
27897
|
+
const repoPath2 = `${packageDir}/${label}`;
|
|
27898
|
+
const bytes = label.startsWith("dist/") ? rebuilt(repoPath2) : tree(repoPath2);
|
|
27899
|
+
if (!bytes) {
|
|
27900
|
+
return { id, kind, status: "stale", recorded, rebuiltExpected: ABSENT, note: `packed file ${repoPath2} is absent from the checkout` };
|
|
27901
|
+
}
|
|
27902
|
+
entries.push({ path: label, bytes });
|
|
27903
|
+
}
|
|
27904
|
+
const rebuiltExpected = digest(entries);
|
|
27905
|
+
return { id, kind, status: recorded === rebuiltExpected ? "current" : "stale", recorded, rebuiltExpected };
|
|
27906
|
+
}
|
|
27907
|
+
function computeDistDriftReceipt(sources) {
|
|
27908
|
+
const artifacts = DIST_ARTIFACTS.map((spec) => artifactDrift(spec.path, sources.committed(spec.path), sources.rebuilt(spec.path)));
|
|
27909
|
+
const bomArtifact = (id) => sources.bom.artifacts?.find((entry) => entry.id === id);
|
|
27910
|
+
const cliDistDeclared = bomArtifact(BOM_DIST_TREE_ID)?.identity;
|
|
27911
|
+
const distTree = distTreeIdentity(sources.distTree(), sources.rebuilt, sources.committed, sources.digest);
|
|
27912
|
+
const distTreeRecorded = cliDistDeclared?.value ?? ABSENT;
|
|
27913
|
+
const identities = [
|
|
27914
|
+
{ id: BOM_DIST_TREE_ID, kind: cliDistDeclared?.kind ?? "sha256-tree", status: distTreeRecorded === distTree.rebuiltExpected ? "current" : "stale", recorded: distTreeRecorded, rebuiltExpected: distTree.rebuiltExpected, ...distTree.note ? { note: distTree.note } : {} },
|
|
27915
|
+
npmPackIdentity("mmi-cli", "cli", bomArtifact("mmi-cli")?.identity, sources.rebuilt, sources.tree, sources.digest),
|
|
27916
|
+
npmPackIdentity("mmi-hub", "updater", bomArtifact("mmi-hub")?.identity, sources.rebuilt, sources.tree, sources.digest)
|
|
27917
|
+
];
|
|
27918
|
+
const representative = identities[0];
|
|
27919
|
+
const bomStatus = identities.some((identity) => identity.status === "stale") ? "stale" : "current";
|
|
27920
|
+
const staleCount = artifacts.filter((a) => a.status === "stale").length + (bomStatus === "stale" ? 1 : 0);
|
|
27921
|
+
const summary = staleCount > 0 ? `dist/BOM: ${staleCount} artifact(s) stale \u2014 refresh with \`node scripts/release-distribution.mjs prepare\`` : "dist/BOM: current \u2014 committed dist and distribution-bom.json match a fresh rebuild of source";
|
|
27922
|
+
return {
|
|
27923
|
+
artifacts,
|
|
27924
|
+
bom: { status: bomStatus, recorded: representative.recorded, rebuiltExpected: representative.rebuiltExpected, identities },
|
|
27925
|
+
staleCount,
|
|
27926
|
+
summary
|
|
27927
|
+
};
|
|
27928
|
+
}
|
|
27929
|
+
function shortHash(value) {
|
|
27930
|
+
return value.startsWith("sha256:") ? `sha256:${value.slice(7, 23)}` : value;
|
|
27931
|
+
}
|
|
27932
|
+
function renderDistDriftReceipt(receipt) {
|
|
27933
|
+
const lines = [];
|
|
27934
|
+
for (const artifact of receipt.artifacts) {
|
|
27935
|
+
lines.push(`${artifact.path} ${artifact.status} committed=${shortHash(artifact.committed)} rebuilt-expected=${shortHash(artifact.rebuiltExpected)}`);
|
|
27936
|
+
}
|
|
27937
|
+
const line = `distribution-bom.json ${receipt.bom.status} committed=${shortHash(receipt.bom.recorded)} rebuilt-expected=${shortHash(receipt.bom.rebuiltExpected)}`;
|
|
27938
|
+
const otherStale = receipt.bom.identities.filter((identity) => identity.id !== BOM_DIST_TREE_ID && identity.status === "stale");
|
|
27939
|
+
lines.push(otherStale.length > 0 ? `${line} (${otherStale.map((identity) => `${identity.id} identity stale`).join("; ")})` : line);
|
|
27940
|
+
lines.push(receipt.summary);
|
|
27941
|
+
return lines;
|
|
27942
|
+
}
|
|
27943
|
+
function readOrNull(path2) {
|
|
27944
|
+
return (0, import_node_fs33.existsSync)(path2) ? (0, import_node_fs33.readFileSync)(path2) : null;
|
|
27945
|
+
}
|
|
27946
|
+
function walkFiles(root) {
|
|
27947
|
+
const files = [];
|
|
27948
|
+
const walk2 = (directory) => {
|
|
27949
|
+
for (const entry of (0, import_node_fs33.readdirSync)(directory, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
|
|
27950
|
+
const child2 = (0, import_node_path29.join)(directory, entry.name);
|
|
27951
|
+
if (entry.isDirectory()) walk2(child2);
|
|
27952
|
+
else files.push(child2);
|
|
27953
|
+
}
|
|
27954
|
+
};
|
|
27955
|
+
walk2(root);
|
|
27956
|
+
return files;
|
|
27957
|
+
}
|
|
27958
|
+
function bomPathFor(root) {
|
|
27959
|
+
try {
|
|
27960
|
+
const registry2 = JSON.parse((0, import_node_fs33.readFileSync)((0, import_node_path29.join)(root, "surfaces.json"), "utf8"));
|
|
27961
|
+
return (0, import_node_path29.join)(root, registry2?.sharedAgentCore?.releaseMetadata?.bomPath ?? "distribution-bom.json");
|
|
27962
|
+
} catch {
|
|
27963
|
+
return (0, import_node_path29.join)(root, "distribution-bom.json");
|
|
27964
|
+
}
|
|
27965
|
+
}
|
|
27966
|
+
function rebuildTo(packageRoot, outDir) {
|
|
27967
|
+
(0, import_node_child_process18.execFileSync)(process.execPath, ["build.mjs"], {
|
|
27968
|
+
cwd: packageRoot,
|
|
27969
|
+
env: { ...process.env, MMI_DIST_OUTDIR: outDir },
|
|
27970
|
+
windowsHide: true,
|
|
27971
|
+
stdio: "pipe",
|
|
27972
|
+
encoding: "utf8"
|
|
27973
|
+
});
|
|
27974
|
+
}
|
|
27975
|
+
function runDistStatus(root) {
|
|
27976
|
+
const stage = (0, import_node_fs33.mkdtempSync)((0, import_node_path29.join)((0, import_node_os15.tmpdir)(), "mmi-dist-drift-"));
|
|
27977
|
+
let overlayCount = 0;
|
|
27978
|
+
try {
|
|
27979
|
+
const cliOut = (0, import_node_path29.join)(stage, "cli-dist");
|
|
27980
|
+
const hubOut = (0, import_node_path29.join)(stage, "hub-dist");
|
|
27981
|
+
rebuildTo((0, import_node_path29.join)(root, "cli"), cliOut);
|
|
27982
|
+
rebuildTo((0, import_node_path29.join)(root, "updater"), hubOut);
|
|
27983
|
+
const outDirFor = (packageDir) => packageDir === "cli" ? cliOut : hubOut;
|
|
27984
|
+
const rebuilt = (path2) => {
|
|
27985
|
+
const spec = DIST_ARTIFACTS.find((entry) => entry.path === path2);
|
|
27986
|
+
return spec ? readOrNull((0, import_node_path29.join)(outDirFor(spec.packageDir), spec.output)) : null;
|
|
27987
|
+
};
|
|
27988
|
+
const committed = (path2) => readOrNull((0, import_node_path29.join)(root, path2));
|
|
27989
|
+
const tree = (path2) => readOrNull((0, import_node_path29.join)(root, path2));
|
|
27990
|
+
const distRoot = (0, import_node_path29.join)(root, "cli", "dist");
|
|
27991
|
+
const distTree = () => walkFiles(distRoot).map((absolute) => `cli/dist/${(0, import_node_path29.relative)(distRoot, absolute).replaceAll("\\", "/")}`);
|
|
27992
|
+
const bom = JSON.parse((0, import_node_fs33.readFileSync)(bomPathFor(root), "utf8"));
|
|
27993
|
+
const digest = (entries) => {
|
|
27994
|
+
const overlay = (0, import_node_path29.join)(stage, `overlay-${overlayCount++}`);
|
|
27995
|
+
for (const entry of entries) {
|
|
27996
|
+
const target = (0, import_node_path29.join)(overlay, entry.path);
|
|
27997
|
+
(0, import_node_fs33.mkdirSync)((0, import_node_path29.dirname)(target), { recursive: true });
|
|
27998
|
+
(0, import_node_fs33.writeFileSync)(target, entry.bytes);
|
|
27999
|
+
}
|
|
28000
|
+
return digestPackedFiles(overlay, entries.map((entry) => entry.path));
|
|
28001
|
+
};
|
|
28002
|
+
return computeDistDriftReceipt({ committed, tree, rebuilt, distTree, bom, digest });
|
|
28003
|
+
} finally {
|
|
28004
|
+
(0, import_node_fs33.rmSync)(stage, { recursive: true, force: true });
|
|
28005
|
+
}
|
|
28006
|
+
}
|
|
28007
|
+
|
|
28008
|
+
// src/project-info-sync.ts
|
|
28009
|
+
var import_node_fs34 = require("node:fs");
|
|
28010
|
+
var import_node_path30 = require("node:path");
|
|
27293
28011
|
var UPDATE_PROJECT_INFO = `mutation($projectId: ID!, $shortDescription: String!, $readme: String!) {
|
|
27294
28012
|
updateProjectV2(input: { projectId: $projectId, shortDescription: $shortDescription, readme: $readme }) {
|
|
27295
28013
|
projectV2 { id }
|
|
@@ -27334,14 +28052,14 @@ function sharedName(entries, fallback) {
|
|
|
27334
28052
|
}
|
|
27335
28053
|
function buildProjectInfoSyncPlan(targetRepo3, project2, projects, repoRoot2) {
|
|
27336
28054
|
if (!project2.projectId) throw new Error(`org project sync-info: ${targetRepo3} registry META has no projectId`);
|
|
27337
|
-
const readmePath = (0,
|
|
27338
|
-
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`);
|
|
27339
28057
|
const entries = entriesFor(project2, projects);
|
|
27340
28058
|
const memberRepos = [...new Set(entries.flatMap((entry) => entry.repos ?? []))].filter((repo) => /^[^/]+\/[^/]+$/.test(repo)).sort((a, b) => a.localeCompare(b));
|
|
27341
28059
|
const projectName = sharedName(entries, project2.name?.trim() || targetRepo3.split("/").pop() || targetRepo3);
|
|
27342
28060
|
if (!memberRepos.length) throw new Error(`org project sync-info: project ${projectName} has no registered member repos`);
|
|
27343
28061
|
const entryNames = entries.map((entry) => entry.name?.trim()).filter((name) => Boolean(name));
|
|
27344
|
-
const shortDescription = memberRepos.length === 1 ? shortDescriptionFromReadme((0,
|
|
28062
|
+
const shortDescription = memberRepos.length === 1 ? shortDescriptionFromReadme((0, import_node_fs34.readFileSync)(readmePath, "utf8")) : `Shared work across ${new Intl.ListFormat("en", { type: "conjunction" }).format(entryNames)}.`;
|
|
27345
28063
|
const lines = [
|
|
27346
28064
|
`# ${projectName}`,
|
|
27347
28065
|
"",
|
|
@@ -27360,8 +28078,8 @@ function buildProjectInfoSyncPlan(targetRepo3, project2, projects, repoRoot2) {
|
|
|
27360
28078
|
const targetBase = `https://github.com/${targetRepo3}`;
|
|
27361
28079
|
const targetBranch = branchFor(targetRepo3, projects);
|
|
27362
28080
|
const orgDocs = [
|
|
27363
|
-
(0,
|
|
27364
|
-
(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)` : ""
|
|
27365
28083
|
].filter(Boolean);
|
|
27366
28084
|
if (orgDocs.length) lines.push("", "## Organisation docs", "", ...orgDocs);
|
|
27367
28085
|
return { projectId: project2.projectId, projectName, targetRepo: targetRepo3, memberRepos, shortDescription, readme: `${lines.join("\n")}
|
|
@@ -28209,9 +28927,9 @@ function writeError(res) {
|
|
|
28209
28927
|
}
|
|
28210
28928
|
|
|
28211
28929
|
// src/secrets-commands.ts
|
|
28212
|
-
var
|
|
28213
|
-
var
|
|
28214
|
-
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");
|
|
28215
28933
|
init_cli_shared();
|
|
28216
28934
|
init_hub_auth();
|
|
28217
28935
|
init_github_client();
|
|
@@ -28316,18 +29034,18 @@ function collectMap(value, previous = []) {
|
|
|
28316
29034
|
return [...previous, value];
|
|
28317
29035
|
}
|
|
28318
29036
|
async function decryptRailsCredentials(input) {
|
|
28319
|
-
const appDir = (0,
|
|
29037
|
+
const appDir = (0, import_node_path31.resolve)(input.appDir ?? process.cwd());
|
|
28320
29038
|
const credentialsFile = input.credentialsFile ?? DEFAULT_RAILS_CREDENTIALS_FILE;
|
|
28321
29039
|
const masterKeyFile = input.masterKeyFile ?? DEFAULT_RAILS_MASTER_KEY_FILE;
|
|
28322
|
-
const credentialsPath = (0,
|
|
28323
|
-
const masterKeyPath = (0,
|
|
29040
|
+
const credentialsPath = (0, import_node_path31.resolve)(appDir, credentialsFile);
|
|
29041
|
+
const masterKeyPath = (0, import_node_path31.resolve)(appDir, masterKeyFile);
|
|
28324
29042
|
const env = {
|
|
28325
29043
|
...process.env,
|
|
28326
29044
|
MMI_RAILS_CREDENTIALS_FILE: credentialsPath,
|
|
28327
29045
|
MMI_RAILS_MASTER_KEY_FILE: masterKeyPath
|
|
28328
29046
|
};
|
|
28329
|
-
if ((0,
|
|
28330
|
-
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();
|
|
28331
29049
|
}
|
|
28332
29050
|
const script = [
|
|
28333
29051
|
'require "json"',
|
|
@@ -28337,9 +29055,9 @@ async function decryptRailsCredentials(input) {
|
|
|
28337
29055
|
'config = ActiveSupport::EncryptedConfiguration.new(config_path: config_path, key_path: key_path, env_key: "RAILS_MASTER_KEY", raise_if_missing_key: true)',
|
|
28338
29056
|
"puts JSON.generate(config.config)"
|
|
28339
29057
|
].join("\n");
|
|
28340
|
-
const scriptDir = (0,
|
|
28341
|
-
const scriptPath = (0,
|
|
28342
|
-
(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");
|
|
28343
29061
|
try {
|
|
28344
29062
|
const args = ["exec", "ruby", scriptPath];
|
|
28345
29063
|
const cmd = process.platform === "win32" ? "cmd.exe" : "bundle";
|
|
@@ -28351,7 +29069,7 @@ async function decryptRailsCredentials(input) {
|
|
|
28351
29069
|
});
|
|
28352
29070
|
return JSON.parse(stdout);
|
|
28353
29071
|
} finally {
|
|
28354
|
-
(0,
|
|
29072
|
+
(0, import_node_fs35.rmSync)(scriptDir, { recursive: true, force: true });
|
|
28355
29073
|
}
|
|
28356
29074
|
}
|
|
28357
29075
|
async function readSecretStdin() {
|
|
@@ -28441,7 +29159,7 @@ function registerSecretsCommands(program3) {
|
|
|
28441
29159
|
let body;
|
|
28442
29160
|
if (o.file) {
|
|
28443
29161
|
try {
|
|
28444
|
-
body = (0,
|
|
29162
|
+
body = (0, import_node_fs35.readFileSync)((0, import_node_path31.resolve)(o.file), "utf8");
|
|
28445
29163
|
} catch (e) {
|
|
28446
29164
|
return fail(`secrets org-catalog: cannot read --file ${o.file}: ${e.message}`);
|
|
28447
29165
|
}
|
|
@@ -28546,7 +29264,7 @@ function registerSecretsCommands(program3) {
|
|
|
28546
29264
|
{
|
|
28547
29265
|
...d,
|
|
28548
29266
|
decryptRailsCredentials,
|
|
28549
|
-
removeFile: (path2) => (0,
|
|
29267
|
+
removeFile: (path2) => (0, import_node_fs35.unlinkSync)((0, import_node_path31.resolve)(o.appDir ?? process.cwd(), path2))
|
|
28550
29268
|
},
|
|
28551
29269
|
{
|
|
28552
29270
|
repo: o.repo,
|
|
@@ -28591,7 +29309,7 @@ function registerSecretsCommands(program3) {
|
|
|
28591
29309
|
}
|
|
28592
29310
|
|
|
28593
29311
|
// src/app-actor.ts
|
|
28594
|
-
var
|
|
29312
|
+
var import_node_crypto13 = require("node:crypto");
|
|
28595
29313
|
var APP_ACTOR_ENV = "MMI_ACTOR";
|
|
28596
29314
|
var APP_VAULT_REPO = "mutmutco/MMI-Hub";
|
|
28597
29315
|
var APP_VAULT_KEYS = ["GITHUB_APP_ID", "GITHUB_APP_INSTALLATION_ID", "GITHUB_APP_PRIVATE_KEY"];
|
|
@@ -28635,7 +29353,7 @@ function mintAppJwt(appId, privateKeyPem, nowSec) {
|
|
|
28635
29353
|
exp: now + APP_JWT_TTL_S,
|
|
28636
29354
|
iss: appId
|
|
28637
29355
|
}));
|
|
28638
|
-
const signer = (0,
|
|
29356
|
+
const signer = (0, import_node_crypto13.createSign)("RSA-SHA256");
|
|
28639
29357
|
signer.update(`${header}.${payload}`);
|
|
28640
29358
|
return `${header}.${payload}.${signer.sign(privateKeyPem, "base64url")}`;
|
|
28641
29359
|
}
|
|
@@ -28753,7 +29471,7 @@ function emitCliCallTelemetry(command) {
|
|
|
28753
29471
|
}
|
|
28754
29472
|
|
|
28755
29473
|
// src/box-commands.ts
|
|
28756
|
-
var
|
|
29474
|
+
var import_node_fs36 = require("node:fs");
|
|
28757
29475
|
init_clean_exit();
|
|
28758
29476
|
|
|
28759
29477
|
// src/box.ts
|
|
@@ -28957,7 +29675,7 @@ function registerBoxCommands(program3) {
|
|
|
28957
29675
|
}
|
|
28958
29676
|
if (o.json) console.log(JSON.stringify({ box: found, incomplete }, null, 2));
|
|
28959
29677
|
else if (o.ssh && o.script) {
|
|
28960
|
-
(0,
|
|
29678
|
+
(0, import_node_fs36.writeFileSync)(o.script, sshRecipeScript(found), "utf8");
|
|
28961
29679
|
console.log(`wrote ${o.script} \u2014 run: bash "${o.script}"`);
|
|
28962
29680
|
} else if (o.ssh) console.log(`${formatSshRecipe(found)}
|
|
28963
29681
|
${SSH_RECIPE_AGENT_NOTE}`);
|
|
@@ -28972,12 +29690,12 @@ ${SSH_RECIPE_AGENT_NOTE}`);
|
|
|
28972
29690
|
|
|
28973
29691
|
// src/schedules-commands.ts
|
|
28974
29692
|
var import_promises4 = require("node:fs/promises");
|
|
28975
|
-
var
|
|
29693
|
+
var import_node_child_process19 = require("node:child_process");
|
|
28976
29694
|
var import_node_util7 = require("node:util");
|
|
28977
29695
|
init_clean_exit();
|
|
28978
29696
|
init_github_client();
|
|
28979
29697
|
init_cli_shared();
|
|
28980
|
-
var execFileP5 = (0, import_node_util7.promisify)(
|
|
29698
|
+
var execFileP5 = (0, import_node_util7.promisify)(import_node_child_process19.execFile);
|
|
28981
29699
|
var AWS_REGION = "eu-central-1";
|
|
28982
29700
|
var AWS_TIMEOUT_MS = 3e4;
|
|
28983
29701
|
var AWS_RETRY_DELAY_MS = 1500;
|
|
@@ -29089,7 +29807,7 @@ async function awsJson(args) {
|
|
|
29089
29807
|
try {
|
|
29090
29808
|
return await run();
|
|
29091
29809
|
} catch {
|
|
29092
|
-
await new Promise((
|
|
29810
|
+
await new Promise((resolve6) => setTimeout(resolve6, AWS_RETRY_DELAY_MS));
|
|
29093
29811
|
return run();
|
|
29094
29812
|
}
|
|
29095
29813
|
}
|
|
@@ -29293,8 +30011,8 @@ function registerSchedulesCommands(program3) {
|
|
|
29293
30011
|
|
|
29294
30012
|
// src/file-lock.ts
|
|
29295
30013
|
var import_promises5 = require("node:fs/promises");
|
|
29296
|
-
var
|
|
29297
|
-
var sleep = (ms) => new Promise((
|
|
30014
|
+
var import_node_path32 = require("node:path");
|
|
30015
|
+
var sleep = (ms) => new Promise((resolve6) => setTimeout(resolve6, ms));
|
|
29298
30016
|
var IMMEDIATE_RETRY_BUDGET = 3;
|
|
29299
30017
|
var FileLockBusyError = class extends Error {
|
|
29300
30018
|
lockPath;
|
|
@@ -29378,7 +30096,7 @@ async function releaseFileLock(lockPath, guard) {
|
|
|
29378
30096
|
}
|
|
29379
30097
|
async function withFileLock(lockPath, opts, fn) {
|
|
29380
30098
|
const resolved = resolveFileLockOpts(opts);
|
|
29381
|
-
await (0, import_promises5.mkdir)((0,
|
|
30099
|
+
await (0, import_promises5.mkdir)((0, import_node_path32.dirname)(lockPath), { recursive: true }).catch(() => void 0);
|
|
29382
30100
|
const guard = await acquireFileLock(lockPath, resolved, Date.now() + resolved.maxWaitMs);
|
|
29383
30101
|
try {
|
|
29384
30102
|
return await fn();
|
|
@@ -29389,7 +30107,7 @@ async function withFileLock(lockPath, opts, fn) {
|
|
|
29389
30107
|
|
|
29390
30108
|
// src/schedules-lift-command.ts
|
|
29391
30109
|
var import_promises6 = require("node:fs/promises");
|
|
29392
|
-
var
|
|
30110
|
+
var import_node_path33 = require("node:path");
|
|
29393
30111
|
init_clean_exit();
|
|
29394
30112
|
init_cli_shared();
|
|
29395
30113
|
var DEFAULT_WORKFLOWS_DIR = ".github/workflows";
|
|
@@ -29418,7 +30136,7 @@ async function readWorkflowFiles(dir) {
|
|
|
29418
30136
|
const files = [];
|
|
29419
30137
|
for (const name of names.sort()) {
|
|
29420
30138
|
if (!/\.ya?ml$/.test(name)) continue;
|
|
29421
|
-
files.push({ path: `.github/workflows/${name}`, text: await (0, import_promises6.readFile)((0,
|
|
30139
|
+
files.push({ path: `.github/workflows/${name}`, text: await (0, import_promises6.readFile)((0, import_node_path33.join)(dir, name), "utf8") });
|
|
29422
30140
|
}
|
|
29423
30141
|
return files;
|
|
29424
30142
|
}
|
|
@@ -29505,13 +30223,13 @@ init_cli_shared();
|
|
|
29505
30223
|
// src/edge-tunnel.ts
|
|
29506
30224
|
var HOSTNAME_RE = /^(?=.{1,253}$)([a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)(\.[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)+$/;
|
|
29507
30225
|
var UPSTREAM_RE = /^https?:\/\/[^/\s]+(?::\d+)?(?:\/.*)?$/;
|
|
29508
|
-
function tunnelNameFromHostname(
|
|
29509
|
-
return
|
|
30226
|
+
function tunnelNameFromHostname(hostname4) {
|
|
30227
|
+
return hostname4.replace(/\./g, "-").slice(0, 63);
|
|
29510
30228
|
}
|
|
29511
|
-
function planInfraTunnel(
|
|
29512
|
-
const host =
|
|
30229
|
+
function planInfraTunnel(hostname4, upstream) {
|
|
30230
|
+
const host = hostname4.trim().toLowerCase();
|
|
29513
30231
|
const origin = upstream.trim();
|
|
29514
|
-
if (!HOSTNAME_RE.test(host)) throw new Error(`invalid hostname ${JSON.stringify(
|
|
30232
|
+
if (!HOSTNAME_RE.test(host)) throw new Error(`invalid hostname ${JSON.stringify(hostname4)}`);
|
|
29515
30233
|
if (!UPSTREAM_RE.test(origin)) throw new Error(`invalid upstream ${JSON.stringify(upstream)} \u2014 expected http(s)://host:port`);
|
|
29516
30234
|
const tunnelName = tunnelNameFromHostname(host);
|
|
29517
30235
|
const configYaml = [
|
|
@@ -29569,15 +30287,149 @@ function registerEdgeCommands(program3) {
|
|
|
29569
30287
|
}
|
|
29570
30288
|
|
|
29571
30289
|
// src/bootstrap-commands.ts
|
|
29572
|
-
var
|
|
29573
|
-
var
|
|
29574
|
-
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");
|
|
29575
30293
|
init_cli_shared();
|
|
29576
30294
|
init_clean_exit();
|
|
29577
30295
|
init_github_client();
|
|
29578
30296
|
|
|
30297
|
+
// src/port-range-assign.ts
|
|
30298
|
+
init_cli_shared();
|
|
30299
|
+
|
|
30300
|
+
// src/port-registry.ts
|
|
30301
|
+
var import_node_fs37 = require("node:fs");
|
|
30302
|
+
var import_node_path34 = require("node:path");
|
|
30303
|
+
|
|
30304
|
+
// ../infra/port-geometry.mjs
|
|
30305
|
+
var PORT_BLOCK = 100;
|
|
30306
|
+
var PORT_SPAN = 10;
|
|
30307
|
+
var PORT_FIRST = 3e3;
|
|
30308
|
+
|
|
30309
|
+
// src/port-registry.ts
|
|
30310
|
+
function nextPortBlock(registry2) {
|
|
30311
|
+
const bases = Object.values(registry2).map(([start]) => start);
|
|
30312
|
+
const base = bases.length ? Math.max(...bases) + PORT_BLOCK : PORT_FIRST;
|
|
30313
|
+
return [base, base + PORT_SPAN];
|
|
30314
|
+
}
|
|
30315
|
+
function loadPortRegistry(path2) {
|
|
30316
|
+
if (!(0, import_node_fs37.existsSync)(path2)) return {};
|
|
30317
|
+
const raw = JSON.parse((0, import_node_fs37.readFileSync)(path2, "utf8"));
|
|
30318
|
+
const out = {};
|
|
30319
|
+
for (const [key, value] of Object.entries(raw)) {
|
|
30320
|
+
if (Array.isArray(value) && value.length === 2 && value.every((n) => typeof n === "number")) {
|
|
30321
|
+
out[key] = [value[0], value[1]];
|
|
30322
|
+
}
|
|
30323
|
+
}
|
|
30324
|
+
return out;
|
|
30325
|
+
}
|
|
30326
|
+
function ensurePortRange(repo, path2) {
|
|
30327
|
+
const registry2 = loadPortRegistry(path2);
|
|
30328
|
+
const existing = registry2[repo];
|
|
30329
|
+
if (existing) return existing;
|
|
30330
|
+
const range = nextPortBlock(registry2);
|
|
30331
|
+
const raw = (0, import_node_fs37.existsSync)(path2) ? JSON.parse((0, import_node_fs37.readFileSync)(path2, "utf8")) : {};
|
|
30332
|
+
raw[repo] = range;
|
|
30333
|
+
(0, import_node_fs37.writeFileSync)(path2, JSON.stringify(raw, null, 2) + "\n", "utf8");
|
|
30334
|
+
return range;
|
|
30335
|
+
}
|
|
30336
|
+
function portCursorSeed(registry2) {
|
|
30337
|
+
return nextPortBlock(registry2)[0];
|
|
30338
|
+
}
|
|
30339
|
+
function metaPortRange(meta) {
|
|
30340
|
+
const r = meta?.portRange;
|
|
30341
|
+
if (r && typeof r.start === "number" && typeof r.end === "number") return [r.start, r.end];
|
|
30342
|
+
return null;
|
|
30343
|
+
}
|
|
30344
|
+
function decidePortRange(input) {
|
|
30345
|
+
if (!input.metaReadOk) {
|
|
30346
|
+
return { action: "fail", reason: "could not verify the existing port block (Hub registry read failed) \u2014 retry; NOT allocating (a re-allocation on an unverified read would advance the cursor and hand out a duplicate block)" };
|
|
30347
|
+
}
|
|
30348
|
+
if (input.metaPortRange) return { action: "return", range: input.metaPortRange };
|
|
30349
|
+
return { action: "allocate" };
|
|
30350
|
+
}
|
|
30351
|
+
function existingPortRange(repo, registry2) {
|
|
30352
|
+
return registry2[repo] ?? null;
|
|
30353
|
+
}
|
|
30354
|
+
function portRangeInfraAt(root, source) {
|
|
30355
|
+
const registryPath = (0, import_node_path34.join)(root, "infra", "port-ranges.json");
|
|
30356
|
+
const ddbScriptPath = (0, import_node_path34.join)(root, "infra", "port-ddb.mjs");
|
|
30357
|
+
if (!(0, import_node_fs37.existsSync)(registryPath) || !(0, import_node_fs37.existsSync)(ddbScriptPath)) return null;
|
|
30358
|
+
return { root, source, registryPath, ddbScriptPath };
|
|
30359
|
+
}
|
|
30360
|
+
function resolvePortRangeInfra(cwd, packageDir) {
|
|
30361
|
+
const direct = portRangeInfraAt(cwd, "cwd");
|
|
30362
|
+
if (direct) return direct;
|
|
30363
|
+
for (let dir = cwd; ; dir = (0, import_node_path34.dirname)(dir)) {
|
|
30364
|
+
const sibling = portRangeInfraAt((0, import_node_path34.join)(dir, "MMI-Hub"), "sibling-hub");
|
|
30365
|
+
if (sibling) return sibling;
|
|
30366
|
+
const parent = (0, import_node_path34.dirname)(dir);
|
|
30367
|
+
if (parent === dir) break;
|
|
30368
|
+
}
|
|
30369
|
+
if (packageDir) {
|
|
30370
|
+
const pkgRoot = (0, import_node_path34.join)(packageDir, "..", "..");
|
|
30371
|
+
const pkgFrom = portRangeInfraAt(pkgRoot, "pkg-root");
|
|
30372
|
+
if (pkgFrom) return pkgFrom;
|
|
30373
|
+
}
|
|
30374
|
+
return null;
|
|
30375
|
+
}
|
|
30376
|
+
async function ensurePortRangeAtomic(repo, path2, allocate, opts = {}) {
|
|
30377
|
+
const registry2 = loadPortRegistry(path2);
|
|
30378
|
+
const existing = existingPortRange(repo, registry2);
|
|
30379
|
+
if (existing) return { range: existing, source: "existing" };
|
|
30380
|
+
const seed = portCursorSeed(registry2);
|
|
30381
|
+
try {
|
|
30382
|
+
const range = await allocate(seed);
|
|
30383
|
+
return { range, source: "ddb" };
|
|
30384
|
+
} catch (e) {
|
|
30385
|
+
if (!opts.quiet) console.warn(`port-registry: DDB allocator unreachable, falling back to committed file (${e.message})`);
|
|
30386
|
+
return { range: ensurePortRange(repo, path2), source: "file" };
|
|
30387
|
+
}
|
|
30388
|
+
}
|
|
30389
|
+
|
|
30390
|
+
// src/port-range-assign.ts
|
|
30391
|
+
async function assignPersistedPortRange(repo, slug, reg, opts) {
|
|
30392
|
+
const read = await fetchProjectBySlugChecked(slug, reg);
|
|
30393
|
+
const decision = decidePortRange({ metaReadOk: read.ok, metaPortRange: read.ok ? metaPortRange(read.project) : null });
|
|
30394
|
+
if (decision.action === "fail") {
|
|
30395
|
+
return { ok: false, error: `${decision.reason}${read.ok ? "" : ` (${read.error})`}` };
|
|
30396
|
+
}
|
|
30397
|
+
if (decision.action === "return") {
|
|
30398
|
+
return { ok: true, range: decision.range, source: "meta", persisted: true };
|
|
30399
|
+
}
|
|
30400
|
+
const infra = resolvePortRangeInfra(opts.cwd, opts.moduleDir);
|
|
30401
|
+
if (!infra) {
|
|
30402
|
+
return {
|
|
30403
|
+
ok: false,
|
|
30404
|
+
error: `no MMI-Hub allocator files found (checked cwd ${opts.cwd}, sibling MMI-Hub dirs, and the installed package location); ensure the Hub's infra/port-ranges.json and infra/port-ddb.mjs are reachable`
|
|
30405
|
+
};
|
|
30406
|
+
}
|
|
30407
|
+
const path2 = infra.registryPath;
|
|
30408
|
+
const allocate = async (seed) => {
|
|
30409
|
+
const { stdout } = await execFileP2("node", [infra.ddbScriptPath, String(seed)], { timeout: 15e3 });
|
|
30410
|
+
const parsed = JSON.parse(stdout);
|
|
30411
|
+
if (!Array.isArray(parsed.range) || parsed.range.length !== 2) throw new Error("port-ddb: no range in output");
|
|
30412
|
+
return parsed.range;
|
|
30413
|
+
};
|
|
30414
|
+
const { range: [start, end], source } = await ensurePortRangeAtomic(repo, path2, allocate);
|
|
30415
|
+
const write = await upsertProject(slug, { portRange: { start, end } }, reg);
|
|
30416
|
+
if (!write.ok && source === "ddb") {
|
|
30417
|
+
return {
|
|
30418
|
+
ok: false,
|
|
30419
|
+
error: `block [${start}, ${end}] was allocated (cursor advanced) but NOT recorded in the registry META (${write.error ?? `HTTP ${write.status}`}) \u2014 fix auth/connectivity and retry so the block is persisted; do not re-run blind`
|
|
30420
|
+
};
|
|
30421
|
+
}
|
|
30422
|
+
return {
|
|
30423
|
+
ok: true,
|
|
30424
|
+
range: [start, end],
|
|
30425
|
+
source: "allocated",
|
|
30426
|
+
persisted: write.ok,
|
|
30427
|
+
...write.ok ? {} : { persistError: write.error ?? `HTTP ${write.status}` }
|
|
30428
|
+
};
|
|
30429
|
+
}
|
|
30430
|
+
|
|
29579
30431
|
// src/bootstrap-drift.ts
|
|
29580
|
-
var
|
|
30432
|
+
var import_node_crypto14 = require("node:crypto");
|
|
29581
30433
|
function byteComparableSeeds(manifest, cls) {
|
|
29582
30434
|
return manifest.seeds.filter((s) => s.ownership === "org" && s.source === "self" && s.classes.includes(cls));
|
|
29583
30435
|
}
|
|
@@ -29597,7 +30449,7 @@ function compareSeedBytes(hubContent, repoContent) {
|
|
|
29597
30449
|
return normalize(hubContent) === normalize(repoContent) ? "match" : "drift";
|
|
29598
30450
|
}
|
|
29599
30451
|
function seedContentHash(content) {
|
|
29600
|
-
return (0,
|
|
30452
|
+
return (0, import_node_crypto14.createHash)("sha256").update(content.replace(/\r\n/g, "\n"), "utf8").digest("hex");
|
|
29601
30453
|
}
|
|
29602
30454
|
function auditRepoSeedDrift(repo, seeds, hubContents, repoReads) {
|
|
29603
30455
|
const byTarget = new Map(repoReads.map((r) => [r.target, r.content]));
|
|
@@ -29758,7 +30610,7 @@ function renderPropagationReport(plan) {
|
|
|
29758
30610
|
}
|
|
29759
30611
|
|
|
29760
30612
|
// src/bootstrap-propagation-identity.ts
|
|
29761
|
-
var
|
|
30613
|
+
var import_node_crypto15 = require("node:crypto");
|
|
29762
30614
|
var PROPAGATION_BRANCH_PREFIX = "seed-propagate-";
|
|
29763
30615
|
var TARGET_MARKER_NAME = "mmi-bootstrap-propagation-target";
|
|
29764
30616
|
function safeBranchPart(value, maxLength, fallback) {
|
|
@@ -29771,7 +30623,7 @@ function repoSlug2(repo) {
|
|
|
29771
30623
|
function propagationBranch(repo, target) {
|
|
29772
30624
|
const repoPart = safeBranchPart(repoSlug2(repo), 32, "repo");
|
|
29773
30625
|
const targetPart = safeBranchPart(target, 48, "target");
|
|
29774
|
-
const hash = (0,
|
|
30626
|
+
const hash = (0, import_node_crypto15.createHash)("sha256").update(repo.trim().toLowerCase()).update("\0").update(target).digest("hex").slice(0, 12);
|
|
29775
30627
|
return `${PROPAGATION_BRANCH_PREFIX}${repoPart}-${targetPart}-${hash}`;
|
|
29776
30628
|
}
|
|
29777
30629
|
function legacyPropagationBranch(repo) {
|
|
@@ -30211,6 +31063,20 @@ function filledDocCheck(label, text, path2) {
|
|
|
30211
31063
|
const unfilled = unfilledDocPlaceholders(text);
|
|
30212
31064
|
return { ok: unfilled.length === 0, label, detail: unfilled.length ? `unfilled: ${unfilled.join(", ")}` : void 0 };
|
|
30213
31065
|
}
|
|
31066
|
+
function isCentralContainerDeployModel(model) {
|
|
31067
|
+
return model === "tenant-container" || model === "solo-container";
|
|
31068
|
+
}
|
|
31069
|
+
function centralContainerPortRangeCheck(deployModel, portRange, repo) {
|
|
31070
|
+
if (!isCentralContainerDeployModel(deployModel)) return null;
|
|
31071
|
+
const start = portRange?.start;
|
|
31072
|
+
const end = portRange?.end;
|
|
31073
|
+
const ok = typeof start === "number" && typeof end === "number" && Number.isFinite(start) && Number.isFinite(end) && start <= end;
|
|
31074
|
+
return {
|
|
31075
|
+
ok,
|
|
31076
|
+
label: "Hub registry portRange present for local stage",
|
|
31077
|
+
detail: ok ? void 0 : `${deployModel} needs PROJECT# META.portRange for mmi-cli stage \u2014 assign with: mmi-cli stage port-range ${repo}`
|
|
31078
|
+
};
|
|
31079
|
+
}
|
|
30214
31080
|
async function verifyBootstrap(repo, repoClass, deps, releaseTrack) {
|
|
30215
31081
|
const branchesWanted = expectedBranches(repoClass, releaseTrack);
|
|
30216
31082
|
const baseBranch = releaseTrack === "trunk" || repoClass === "content" ? "main" : "development";
|
|
@@ -30304,6 +31170,8 @@ async function verifyBootstrap(repo, repoClass, deps, releaseTrack) {
|
|
|
30304
31170
|
});
|
|
30305
31171
|
}
|
|
30306
31172
|
}
|
|
31173
|
+
const portRangeCheck = centralContainerPortRangeCheck(deps.deployModel, deps.projectMeta?.portRange, repo);
|
|
31174
|
+
if (portRangeCheck) checks.push(portRangeCheck);
|
|
30307
31175
|
const readme = await contentText(deps, repo, baseBranch, "README.md");
|
|
30308
31176
|
checks.push({
|
|
30309
31177
|
ok: readme !== null && readme.includes("## Agent context"),
|
|
@@ -30573,13 +31441,13 @@ function registerBootstrapCommands(program3) {
|
|
|
30573
31441
|
client: defaultGitHubClient(),
|
|
30574
31442
|
projectMeta: meta,
|
|
30575
31443
|
deployModel: typeof meta?.deployModel === "string" ? meta.deployModel : void 0,
|
|
30576
|
-
readLocalFile: (path2) => path2 === "projects.json" && apiProjects != null ? apiProjects : (0,
|
|
31444
|
+
readLocalFile: (path2) => path2 === "projects.json" && apiProjects != null ? apiProjects : (0, import_node_fs38.existsSync)(path2) ? (0, import_node_fs38.readFileSync)(path2, "utf8") : null,
|
|
30577
31445
|
// requiredGcpApis is stored as an array by a JSON write, but `org project set --var KEY=VALUE` stores a raw
|
|
30578
31446
|
// comma-string — accept either so the seeded value verifies regardless of how it was written.
|
|
30579
31447
|
// #3689: the same committed map the org access audit reads (#3664), so a sanctioned admin is not a
|
|
30580
31448
|
// permanent bootstrap failure on one surface and an intended state on the other. Absent file → no
|
|
30581
31449
|
// sanction, which is the pre-#3664 behaviour.
|
|
30582
|
-
sanctionedAdmins: (0,
|
|
31450
|
+
sanctionedAdmins: (0, import_node_fs38.existsSync)("access-matrix.json") ? entriesValueByCanonicalRepo(loadSanctionedAdmins((0, import_node_fs38.readFileSync)("access-matrix.json", "utf8")), repo) : void 0,
|
|
30583
31451
|
requiredGcpApis: (() => {
|
|
30584
31452
|
const v = meta?.requiredGcpApis;
|
|
30585
31453
|
if (Array.isArray(v)) return v;
|
|
@@ -30632,14 +31500,14 @@ function registerBootstrapCommands(program3) {
|
|
|
30632
31500
|
bootstrap.command("drift").description("#3818: compare every org-owned whole-file seed against MMI-Hub's copy across the registry roster; read-only").option("--repo <owner/repo>", "audit one repo instead of the roster (never a fleet verdict)").option("--json", "machine-readable output").action(async () => {
|
|
30633
31501
|
const o = { repo: rawValue("--repo", ""), json: rawFlag("--json") };
|
|
30634
31502
|
const manifestPath = "skills/bootstrap/seeds/manifest.json";
|
|
30635
|
-
if (!(0,
|
|
31503
|
+
if (!(0, import_node_fs38.existsSync)(manifestPath)) return fail(`bootstrap drift: ${manifestPath} not found; run from the MMI-Hub repo root \u2014 the Hub's copies ARE the reference this compares against`);
|
|
30636
31504
|
const seedSource = await resolveHubSeedSource(execGitForSeedSource);
|
|
30637
31505
|
if (!seedSource.ok) return fail(`bootstrap drift: ${seedSource.reason}`);
|
|
30638
|
-
const manifest = loadBootstrapSeeds((0,
|
|
31506
|
+
const manifest = loadBootstrapSeeds((0, import_node_fs38.readFileSync)(manifestPath, "utf8"));
|
|
30639
31507
|
const hubContents = /* @__PURE__ */ new Map();
|
|
30640
31508
|
for (const s of manifest.seeds) {
|
|
30641
31509
|
if (s.ownership !== "org" || s.source !== "self") continue;
|
|
30642
|
-
hubContents.set(s.target, (0,
|
|
31510
|
+
hubContents.set(s.target, (0, import_node_fs38.existsSync)(s.target) ? (0, import_node_fs38.readFileSync)(s.target, "utf8") : null);
|
|
30643
31511
|
}
|
|
30644
31512
|
let targets;
|
|
30645
31513
|
let classOf = (_repo) => "deployable";
|
|
@@ -30757,10 +31625,10 @@ function registerBootstrapCommands(program3) {
|
|
|
30757
31625
|
return;
|
|
30758
31626
|
}
|
|
30759
31627
|
const manifestPath = "skills/bootstrap/seeds/manifest.json";
|
|
30760
|
-
if (!(0,
|
|
31628
|
+
if (!(0, import_node_fs38.existsSync)(manifestPath)) return fail(`bootstrap apply: ${manifestPath} not found; bootstrap runs from the MMI-Hub repo root by design \u2014 it stamps org-level resources (Project, Ruleset, secrets, access) through the GitHub App, which is only authorized from the Hub checkout`);
|
|
30761
31629
|
const seedSource = await resolveHubSeedSource(execGitForSeedSource);
|
|
30762
31630
|
if (!seedSource.ok) return fail(`bootstrap apply: ${seedSource.reason}`);
|
|
30763
|
-
const manifest = loadBootstrapSeeds((0,
|
|
31631
|
+
const manifest = loadBootstrapSeeds((0, import_node_fs38.readFileSync)(manifestPath, "utf8"));
|
|
30764
31632
|
const baseBranch = o.class === "content" ? "main" : "development";
|
|
30765
31633
|
const slug = parsedRepo.slug;
|
|
30766
31634
|
const onlyTarget = o.only.trim();
|
|
@@ -30772,16 +31640,16 @@ function registerBootstrapCommands(program3) {
|
|
|
30772
31640
|
}
|
|
30773
31641
|
const onlyManagedBlock = onlyTarget ? seedsToApply[0]?.managedBlock != null : false;
|
|
30774
31642
|
const gh = async (args) => execFileP2("gh", args, { timeout: 2e4 });
|
|
30775
|
-
const readFile7 = (p) => (0,
|
|
31643
|
+
const readFile7 = (p) => (0, import_node_fs38.existsSync)(p) ? (0, import_node_fs38.readFileSync)(p, "utf8") : null;
|
|
30776
31644
|
const enc = (p) => p.split("/").map(encodeURIComponent).join("/");
|
|
30777
31645
|
const putSeed = async (target, content, ref, sha) => {
|
|
30778
|
-
const tmp = (0,
|
|
30779
|
-
(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");
|
|
30780
31648
|
try {
|
|
30781
31649
|
await gh(contentPutInputArgs(repo, target, tmp));
|
|
30782
31650
|
} finally {
|
|
30783
31651
|
try {
|
|
30784
|
-
(0,
|
|
31652
|
+
(0, import_node_fs38.unlinkSync)(tmp);
|
|
30785
31653
|
} catch {
|
|
30786
31654
|
}
|
|
30787
31655
|
}
|
|
@@ -31054,10 +31922,32 @@ ${onlyManagedBlock ? `Only the marker-bounded Hub-managed block inside repo-owne
|
|
|
31054
31922
|
}
|
|
31055
31923
|
if (o.execute && !onlyTarget) {
|
|
31056
31924
|
const cfg = await loadConfig();
|
|
31057
|
-
const
|
|
31925
|
+
const reg = registryClientDeps(cfg);
|
|
31926
|
+
const res = await registerProject(registerPayload, reg);
|
|
31058
31927
|
if (res.ok) {
|
|
31059
31928
|
ddbWrites.push({ slug: registerPayload.slug, action: "register", record: registerPayload });
|
|
31060
31929
|
applied.push(`ddb register ${registerPayload.slug}`);
|
|
31930
|
+
const deployModel = typeof registerPayload.deployModel === "string" ? registerPayload.deployModel : void 0;
|
|
31931
|
+
if (deployModel === "tenant-container" || deployModel === "solo-container") {
|
|
31932
|
+
const shortName = typeof registerPayload.name === "string" ? registerPayload.name : typeof registerPayload.slug === "string" ? registerPayload.slug : repo.split("/")[1] || repo;
|
|
31933
|
+
const assigned = await assignPersistedPortRange(shortName, registerPayload.slug, reg, {
|
|
31934
|
+
cwd: process.cwd(),
|
|
31935
|
+
moduleDir: __dirname
|
|
31936
|
+
});
|
|
31937
|
+
if (assigned.ok) {
|
|
31938
|
+
const [start, end] = assigned.range;
|
|
31939
|
+
ddbWrites.push({
|
|
31940
|
+
slug: registerPayload.slug,
|
|
31941
|
+
action: "portRange",
|
|
31942
|
+
record: { portRange: { start, end }, source: assigned.source, persisted: assigned.persisted }
|
|
31943
|
+
});
|
|
31944
|
+
applied.push(
|
|
31945
|
+
assigned.source === "meta" ? `portRange [${start}, ${end}] (already on META)` : `portRange [${start}, ${end}] (${assigned.persisted ? "persisted" : `META not persisted: ${assigned.persistError}`})`
|
|
31946
|
+
);
|
|
31947
|
+
} else {
|
|
31948
|
+
applied.push(`portRange (failed: ${assigned.error})`);
|
|
31949
|
+
}
|
|
31950
|
+
}
|
|
31061
31951
|
} else {
|
|
31062
31952
|
const why = res.error ?? `HTTP ${res.status}${res.body?.error ? ` \u2014 ${res.body.error}` : ""}`;
|
|
31063
31953
|
applied.push(`ddb register ${registerPayload.slug} (failed: ${why})`);
|
|
@@ -31074,10 +31964,10 @@ LIVE apply to ${repo}:
|
|
|
31074
31964
|
bootstrap.command("propagate").description("#4238: re-entrant canary\u2192wave tick \u2014 plan (or, with --execute, open) per-repo PRs fanning an org-owned seed out to the fleet").option("--target <path>", "the manifest target to propagate (an org-owned whole file or declared Hub-managed block)").option("--execute", "LIVE tick via gh (master-gated) \u2014 opens/reuses per-repo seed-propagate PRs; dry-run prints the plan only").option("--json", "machine-readable output").action(async () => {
|
|
31075
31965
|
const o = { target: rawValue("--target", ""), execute: rawFlag("--execute"), json: rawFlag("--json") };
|
|
31076
31966
|
const manifestPath = "skills/bootstrap/seeds/manifest.json";
|
|
31077
|
-
if (!(0,
|
|
31967
|
+
if (!(0, import_node_fs38.existsSync)(manifestPath)) return fail(`bootstrap propagate: ${manifestPath} not found; run from the MMI-Hub repo root \u2014 the Hub's copies ARE the desired state this tick propagates`);
|
|
31078
31968
|
const seedSource = await resolveHubSeedSource(execGitForSeedSource);
|
|
31079
31969
|
if (!seedSource.ok) return fail(`bootstrap propagate: ${seedSource.reason}`);
|
|
31080
|
-
const manifest = loadBootstrapSeeds((0,
|
|
31970
|
+
const manifest = loadBootstrapSeeds((0, import_node_fs38.readFileSync)(manifestPath, "utf8"));
|
|
31081
31971
|
const propagatable = manifest.seeds.filter(isPropagatableSeed);
|
|
31082
31972
|
if (!o.target) {
|
|
31083
31973
|
return fail(`bootstrap propagate: --target <path> is required \u2014 one of:
|
|
@@ -31086,9 +31976,9 @@ LIVE apply to ${repo}:
|
|
|
31086
31976
|
const seed = propagatable.find((s) => s.target === o.target);
|
|
31087
31977
|
if (!seed) return fail(`bootstrap propagate: --target '${o.target}' names no centrally propagatable seed in ${manifestPath}. Propagatable targets:
|
|
31088
31978
|
${propagatable.map((s) => s.target).join("\n ")}`);
|
|
31089
|
-
if (!seed.managedBlock && !(0,
|
|
31090
|
-
const hubContent = seed.managedBlock ? null : (0,
|
|
31091
|
-
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;
|
|
31092
31982
|
const isWorkflowSeed = seed.target.startsWith(".github/workflows/");
|
|
31093
31983
|
const cfg = await loadConfig();
|
|
31094
31984
|
const projects = await fetchProjectsList(registryClientDeps(cfg));
|
|
@@ -31097,9 +31987,9 @@ LIVE apply to ${repo}:
|
|
|
31097
31987
|
}
|
|
31098
31988
|
const rosterRepos2 = collectRegistryRepos(projects).filter((r) => r.toLowerCase() !== "mutmutco/mmi-hub");
|
|
31099
31989
|
let independentCount = rosterRepos2.length;
|
|
31100
|
-
if ((0,
|
|
31990
|
+
if ((0, import_node_fs38.existsSync)("projects.json")) {
|
|
31101
31991
|
try {
|
|
31102
|
-
const local = JSON.parse((0,
|
|
31992
|
+
const local = JSON.parse((0, import_node_fs38.readFileSync)("projects.json", "utf8"));
|
|
31103
31993
|
const localRepos = /* @__PURE__ */ new Set();
|
|
31104
31994
|
for (const p of local.projects ?? []) for (const r of p.repos ?? []) {
|
|
31105
31995
|
const full = (r.includes("/") ? r : `mutmutco/${r}`).toLowerCase();
|
|
@@ -31246,15 +32136,15 @@ LIVE apply to ${repo}:
|
|
|
31246
32136
|
} catch {
|
|
31247
32137
|
existingSha = void 0;
|
|
31248
32138
|
}
|
|
31249
|
-
const tmp = (0,
|
|
32139
|
+
const tmp = (0, import_node_path35.join)((0, import_node_os17.tmpdir)(), `mmi-propagate-${process.pid}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}.json`);
|
|
31250
32140
|
const desiredContent = desiredByRepo.get(rec.repo);
|
|
31251
32141
|
if (desiredContent == null) return fail(`bootstrap propagate: no resolved content for ${rec.repo} ${seed.target} \u2014 refusing to write`);
|
|
31252
|
-
(0,
|
|
32142
|
+
(0, import_node_fs38.writeFileSync)(tmp, JSON.stringify(contentPutBody(seed.target, desiredContent, branch, existingSha)), "utf8");
|
|
31253
32143
|
try {
|
|
31254
32144
|
await gh(contentPutInputArgs(rec.repo, seed.target, tmp));
|
|
31255
32145
|
} finally {
|
|
31256
32146
|
try {
|
|
31257
|
-
(0,
|
|
32147
|
+
(0, import_node_fs38.unlinkSync)(tmp);
|
|
31258
32148
|
} catch {
|
|
31259
32149
|
}
|
|
31260
32150
|
}
|
|
@@ -31321,10 +32211,10 @@ Rollback: \`mmi-cli devops bootstrap rollback ${rec.repo} --target ${seed.target
|
|
|
31321
32211
|
return fail(`bootstrap rollback: ${e.message}`);
|
|
31322
32212
|
}
|
|
31323
32213
|
const manifestPath = "skills/bootstrap/seeds/manifest.json";
|
|
31324
|
-
if (!(0,
|
|
32214
|
+
if (!(0, import_node_fs38.existsSync)(manifestPath)) return fail(`bootstrap rollback: ${manifestPath} not found; run from the MMI-Hub repo root \u2014 the manifest names which targets are org-owned and therefore propagated (and rollback-able)`);
|
|
31325
32215
|
const seedSource = await resolveHubSeedSource(execGitForSeedSource);
|
|
31326
32216
|
if (!seedSource.ok) return fail(`bootstrap rollback: ${seedSource.reason}`);
|
|
31327
|
-
const manifest = loadBootstrapSeeds((0,
|
|
32217
|
+
const manifest = loadBootstrapSeeds((0, import_node_fs38.readFileSync)(manifestPath, "utf8"));
|
|
31328
32218
|
const propagatable = manifest.seeds.filter(isPropagatableSeed);
|
|
31329
32219
|
if (!o.target) {
|
|
31330
32220
|
return fail(`bootstrap rollback: --target <path> is required \u2014 one of:
|
|
@@ -31341,10 +32231,10 @@ Rollback: \`mmi-cli devops bootstrap rollback ${rec.repo} --target ${seed.target
|
|
|
31341
32231
|
const enc = (p) => p.split("/").map(encodeURIComponent).join("/");
|
|
31342
32232
|
let candidates;
|
|
31343
32233
|
if (o.record) {
|
|
31344
|
-
if (!(0,
|
|
32234
|
+
if (!(0, import_node_fs38.existsSync)(o.record)) return fail(`bootstrap rollback: --record '${o.record}' not found`);
|
|
31345
32235
|
let parsed;
|
|
31346
32236
|
try {
|
|
31347
|
-
parsed = JSON.parse((0,
|
|
32237
|
+
parsed = JSON.parse((0, import_node_fs38.readFileSync)(o.record, "utf8"));
|
|
31348
32238
|
} catch (e) {
|
|
31349
32239
|
return fail(`bootstrap rollback: --record '${o.record}' is not valid JSON: ${e.message}`);
|
|
31350
32240
|
}
|
|
@@ -31421,13 +32311,13 @@ Rollback: \`mmi-cli devops bootstrap rollback ${rec.repo} --target ${seed.target
|
|
|
31421
32311
|
} catch {
|
|
31422
32312
|
existingSha = void 0;
|
|
31423
32313
|
}
|
|
31424
|
-
const tmp = (0,
|
|
31425
|
-
(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");
|
|
31426
32316
|
try {
|
|
31427
32317
|
await gh(contentPutInputArgs(repo, seed.target, tmp));
|
|
31428
32318
|
} finally {
|
|
31429
32319
|
try {
|
|
31430
|
-
(0,
|
|
32320
|
+
(0, import_node_fs38.unlinkSync)(tmp);
|
|
31431
32321
|
} catch {
|
|
31432
32322
|
}
|
|
31433
32323
|
}
|
|
@@ -31452,101 +32342,11 @@ Rollback: \`mmi-cli devops bootstrap rollback ${rec.repo} --target ${seed.target
|
|
|
31452
32342
|
}
|
|
31453
32343
|
|
|
31454
32344
|
// src/stage-commands.ts
|
|
31455
|
-
var
|
|
31456
|
-
var
|
|
32345
|
+
var import_node_fs39 = require("node:fs");
|
|
32346
|
+
var import_node_path36 = require("node:path");
|
|
31457
32347
|
init_cli_shared();
|
|
31458
32348
|
init_clean_exit();
|
|
31459
32349
|
|
|
31460
|
-
// src/port-registry.ts
|
|
31461
|
-
var import_node_fs35 = require("node:fs");
|
|
31462
|
-
var import_node_path33 = require("node:path");
|
|
31463
|
-
|
|
31464
|
-
// ../infra/port-geometry.mjs
|
|
31465
|
-
var PORT_BLOCK = 100;
|
|
31466
|
-
var PORT_SPAN = 10;
|
|
31467
|
-
var PORT_FIRST = 3e3;
|
|
31468
|
-
|
|
31469
|
-
// src/port-registry.ts
|
|
31470
|
-
function nextPortBlock(registry2) {
|
|
31471
|
-
const bases = Object.values(registry2).map(([start]) => start);
|
|
31472
|
-
const base = bases.length ? Math.max(...bases) + PORT_BLOCK : PORT_FIRST;
|
|
31473
|
-
return [base, base + PORT_SPAN];
|
|
31474
|
-
}
|
|
31475
|
-
function loadPortRegistry(path2) {
|
|
31476
|
-
if (!(0, import_node_fs35.existsSync)(path2)) return {};
|
|
31477
|
-
const raw = JSON.parse((0, import_node_fs35.readFileSync)(path2, "utf8"));
|
|
31478
|
-
const out = {};
|
|
31479
|
-
for (const [key, value] of Object.entries(raw)) {
|
|
31480
|
-
if (Array.isArray(value) && value.length === 2 && value.every((n) => typeof n === "number")) {
|
|
31481
|
-
out[key] = [value[0], value[1]];
|
|
31482
|
-
}
|
|
31483
|
-
}
|
|
31484
|
-
return out;
|
|
31485
|
-
}
|
|
31486
|
-
function ensurePortRange(repo, path2) {
|
|
31487
|
-
const registry2 = loadPortRegistry(path2);
|
|
31488
|
-
const existing = registry2[repo];
|
|
31489
|
-
if (existing) return existing;
|
|
31490
|
-
const range = nextPortBlock(registry2);
|
|
31491
|
-
const raw = (0, import_node_fs35.existsSync)(path2) ? JSON.parse((0, import_node_fs35.readFileSync)(path2, "utf8")) : {};
|
|
31492
|
-
raw[repo] = range;
|
|
31493
|
-
(0, import_node_fs35.writeFileSync)(path2, JSON.stringify(raw, null, 2) + "\n", "utf8");
|
|
31494
|
-
return range;
|
|
31495
|
-
}
|
|
31496
|
-
function portCursorSeed(registry2) {
|
|
31497
|
-
return nextPortBlock(registry2)[0];
|
|
31498
|
-
}
|
|
31499
|
-
function metaPortRange(meta) {
|
|
31500
|
-
const r = meta?.portRange;
|
|
31501
|
-
if (r && typeof r.start === "number" && typeof r.end === "number") return [r.start, r.end];
|
|
31502
|
-
return null;
|
|
31503
|
-
}
|
|
31504
|
-
function decidePortRange(input) {
|
|
31505
|
-
if (!input.metaReadOk) {
|
|
31506
|
-
return { action: "fail", reason: "could not verify the existing port block (Hub registry read failed) \u2014 retry; NOT allocating (a re-allocation on an unverified read would advance the cursor and hand out a duplicate block)" };
|
|
31507
|
-
}
|
|
31508
|
-
if (input.metaPortRange) return { action: "return", range: input.metaPortRange };
|
|
31509
|
-
return { action: "allocate" };
|
|
31510
|
-
}
|
|
31511
|
-
function existingPortRange(repo, registry2) {
|
|
31512
|
-
return registry2[repo] ?? null;
|
|
31513
|
-
}
|
|
31514
|
-
function portRangeInfraAt(root, source) {
|
|
31515
|
-
const registryPath = (0, import_node_path33.join)(root, "infra", "port-ranges.json");
|
|
31516
|
-
const ddbScriptPath = (0, import_node_path33.join)(root, "infra", "port-ddb.mjs");
|
|
31517
|
-
if (!(0, import_node_fs35.existsSync)(registryPath) || !(0, import_node_fs35.existsSync)(ddbScriptPath)) return null;
|
|
31518
|
-
return { root, source, registryPath, ddbScriptPath };
|
|
31519
|
-
}
|
|
31520
|
-
function resolvePortRangeInfra(cwd, packageDir) {
|
|
31521
|
-
const direct = portRangeInfraAt(cwd, "cwd");
|
|
31522
|
-
if (direct) return direct;
|
|
31523
|
-
for (let dir = cwd; ; dir = (0, import_node_path33.dirname)(dir)) {
|
|
31524
|
-
const sibling = portRangeInfraAt((0, import_node_path33.join)(dir, "MMI-Hub"), "sibling-hub");
|
|
31525
|
-
if (sibling) return sibling;
|
|
31526
|
-
const parent = (0, import_node_path33.dirname)(dir);
|
|
31527
|
-
if (parent === dir) break;
|
|
31528
|
-
}
|
|
31529
|
-
if (packageDir) {
|
|
31530
|
-
const pkgRoot = (0, import_node_path33.join)(packageDir, "..", "..");
|
|
31531
|
-
const pkgFrom = portRangeInfraAt(pkgRoot, "pkg-root");
|
|
31532
|
-
if (pkgFrom) return pkgFrom;
|
|
31533
|
-
}
|
|
31534
|
-
return null;
|
|
31535
|
-
}
|
|
31536
|
-
async function ensurePortRangeAtomic(repo, path2, allocate, opts = {}) {
|
|
31537
|
-
const registry2 = loadPortRegistry(path2);
|
|
31538
|
-
const existing = existingPortRange(repo, registry2);
|
|
31539
|
-
if (existing) return { range: existing, source: "existing" };
|
|
31540
|
-
const seed = portCursorSeed(registry2);
|
|
31541
|
-
try {
|
|
31542
|
-
const range = await allocate(seed);
|
|
31543
|
-
return { range, source: "ddb" };
|
|
31544
|
-
} catch (e) {
|
|
31545
|
-
if (!opts.quiet) console.warn(`port-registry: DDB allocator unreachable, falling back to committed file (${e.message})`);
|
|
31546
|
-
return { range: ensurePortRange(repo, path2), source: "file" };
|
|
31547
|
-
}
|
|
31548
|
-
}
|
|
31549
|
-
|
|
31550
32350
|
// src/stage-default.ts
|
|
31551
32351
|
function shellFor(platform2 = process.platform) {
|
|
31552
32352
|
return platform2 === "win32" ? "powershell" : "bash";
|
|
@@ -31554,14 +32354,20 @@ function shellFor(platform2 = process.platform) {
|
|
|
31554
32354
|
function isCentralContainerModel(model) {
|
|
31555
32355
|
return model === "tenant-container" || model === "solo-container";
|
|
31556
32356
|
}
|
|
31557
|
-
function
|
|
32357
|
+
function stagePortRangeRecovery(repo = "<owner/repo>") {
|
|
32358
|
+
return `mmi-cli stage port-range ${repo}`;
|
|
32359
|
+
}
|
|
32360
|
+
function deriveStageGap(inputs, opts) {
|
|
31558
32361
|
const missing = [];
|
|
31559
32362
|
if (!isCentralContainerModel(inputs.deployModel)) {
|
|
31560
32363
|
return `local stage default applies to central-container repos only (tenant-container/solo-container; registry deployModel = ${inputs.deployModel ?? "unset"})`;
|
|
31561
32364
|
}
|
|
31562
32365
|
if (!inputs.hasCompose) missing.push("docker-compose.yml");
|
|
31563
32366
|
if (!inputs.portRange) missing.push("Hub registry portRange");
|
|
31564
|
-
|
|
32367
|
+
if (!missing.length) return null;
|
|
32368
|
+
const base = `cannot derive a default local stage \u2014 missing: ${missing.join(", ")}`;
|
|
32369
|
+
if (!inputs.portRange) return `${base} \u2014 assign with: ${stagePortRangeRecovery(opts?.repo)}`;
|
|
32370
|
+
return base;
|
|
31565
32371
|
}
|
|
31566
32372
|
function deriveStage(inputs) {
|
|
31567
32373
|
if (deriveStageGap(inputs) || !inputs.portRange) return null;
|
|
@@ -31592,7 +32398,7 @@ function stageUrlForPort(port) {
|
|
|
31592
32398
|
return `http://127.0.0.1:${port}/`;
|
|
31593
32399
|
}
|
|
31594
32400
|
function decideStage(inputs) {
|
|
31595
|
-
const { registry: registry2, hasCompose, hasEnvExample } = inputs;
|
|
32401
|
+
const { registry: registry2, hasCompose, hasEnvExample, repo } = inputs;
|
|
31596
32402
|
const deriveInputs = {
|
|
31597
32403
|
portRange: registry2.portRange,
|
|
31598
32404
|
deployModel: registry2.deployModel,
|
|
@@ -31602,8 +32408,9 @@ function decideStage(inputs) {
|
|
|
31602
32408
|
const derived = deriveStage(deriveInputs);
|
|
31603
32409
|
if (derived) return { source: "derived", derived, registryError: registry2.error };
|
|
31604
32410
|
const registryGap = registry2.error ? `Hub registry read failed (${registry2.error}) \u2014 cannot derive a default local stage` : null;
|
|
31605
|
-
const gap = registryGap ?? deriveStageGap(deriveInputs) ?? "no registry-derived default available";
|
|
31606
|
-
|
|
32411
|
+
const gap = registryGap ?? deriveStageGap(deriveInputs, { repo }) ?? "no registry-derived default available";
|
|
32412
|
+
const recovery = !registry2.error && isCentralContainerModel(registry2.deployModel) && !registry2.portRange ? stagePortRangeRecovery(repo) : void 0;
|
|
32413
|
+
return { source: "none", gap, ...recovery ? { recovery } : {}, registryError: registry2.error };
|
|
31607
32414
|
}
|
|
31608
32415
|
|
|
31609
32416
|
// src/stage-live.ts
|
|
@@ -31813,14 +32620,17 @@ function registerStageCommands(program3) {
|
|
|
31813
32620
|
}
|
|
31814
32621
|
async function resolveStage() {
|
|
31815
32622
|
const cfg = await loadConfig();
|
|
31816
|
-
const
|
|
32623
|
+
const slug = await repoSlug();
|
|
32624
|
+
const read = await fetchProjectBySlugChecked(slug, registryClientDeps(cfg)).catch((e) => ({ ok: false, error: e.message }));
|
|
31817
32625
|
const project2 = read.ok ? read.project : null;
|
|
31818
32626
|
const portRangeMeta = project2?.portRange ?? void 0;
|
|
31819
32627
|
const portRange = portRangeMeta && typeof portRangeMeta.start === "number" && typeof portRangeMeta.end === "number" ? [portRangeMeta.start, portRangeMeta.end] : void 0;
|
|
32628
|
+
const repo = Array.isArray(project2?.repos) && typeof project2.repos[0] === "string" ? project2.repos[0] : slug;
|
|
31820
32629
|
return decideStage({
|
|
31821
32630
|
registry: { deployModel: project2?.deployModel, portRange, error: read.ok ? void 0 : read.error },
|
|
31822
|
-
hasCompose: (0,
|
|
31823
|
-
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
|
|
31824
32634
|
});
|
|
31825
32635
|
}
|
|
31826
32636
|
async function fetchStageVaultEnvMerge() {
|
|
@@ -31865,7 +32675,16 @@ function registerStageCommands(program3) {
|
|
|
31865
32675
|
}
|
|
31866
32676
|
function stageStepsFor(res, stops = true) {
|
|
31867
32677
|
if (res.source === "derived" && res.derived) return derivedStagePlan(res.derived, shellFor(), stops);
|
|
31868
|
-
return [{ label: `no local stage to run \u2014 ${res.gap ?? "stage config gap"}
|
|
32678
|
+
return [{ label: `no local stage to run \u2014 ${res.gap ?? "stage config gap"}`, ...res.recovery ? { command: res.recovery } : {} }];
|
|
32679
|
+
}
|
|
32680
|
+
function stageReceiptFields(res) {
|
|
32681
|
+
return {
|
|
32682
|
+
source: res.source,
|
|
32683
|
+
url: res.derived?.url,
|
|
32684
|
+
...res.gap ? { gap: res.gap } : {},
|
|
32685
|
+
...res.recovery ? { recovery: res.recovery } : {},
|
|
32686
|
+
...res.registryError ? { registryError: res.registryError } : {}
|
|
32687
|
+
};
|
|
31869
32688
|
}
|
|
31870
32689
|
function reportedStageUrl(res, result) {
|
|
31871
32690
|
if (!res.derived) return void 0;
|
|
@@ -31875,38 +32694,23 @@ function registerStageCommands(program3) {
|
|
|
31875
32694
|
const cfg = await loadConfig();
|
|
31876
32695
|
const reg = registryClientDeps(cfg);
|
|
31877
32696
|
const slug = slugOf(repo);
|
|
31878
|
-
const
|
|
31879
|
-
|
|
31880
|
-
|
|
31881
|
-
|
|
31882
|
-
|
|
31883
|
-
if (decision.action === "return") {
|
|
31884
|
-
const [start2, end2] = decision.range;
|
|
31885
|
-
printLine(o.json ? JSON.stringify({ repo, portRange: [start2, end2], source: "meta" }) : `${repo}: stage.portRange [${start2}, ${end2}]`);
|
|
32697
|
+
const assigned = await assignPersistedPortRange(repo, slug, reg, { cwd: process.cwd(), moduleDir: __dirname });
|
|
32698
|
+
if (!assigned.ok) return failGraceful(`stage port-range: ${assigned.error}`);
|
|
32699
|
+
const [start, end] = assigned.range;
|
|
32700
|
+
if (assigned.source === "meta") {
|
|
32701
|
+
printLine(o.json ? JSON.stringify({ repo, portRange: [start, end], source: "meta" }) : `${repo}: stage.portRange [${start}, ${end}]`);
|
|
31886
32702
|
return;
|
|
31887
32703
|
}
|
|
31888
|
-
const infra = resolvePortRangeInfra(process.cwd(), __dirname);
|
|
31889
|
-
if (!infra) {
|
|
31890
|
-
return failGraceful(
|
|
31891
|
-
`stage port-range: no MMI-Hub allocator files found (checked cwd ${process.cwd()}, sibling MMI-Hub dirs, and the installed package location); ensure the Hub's infra/port-ranges.json and infra/port-ddb.mjs are reachable`
|
|
31892
|
-
);
|
|
31893
|
-
}
|
|
31894
|
-
const path2 = infra.registryPath;
|
|
31895
|
-
const allocate = async (seed) => {
|
|
31896
|
-
const { stdout } = await execFileP2("node", [infra.ddbScriptPath, String(seed)], { timeout: 15e3 });
|
|
31897
|
-
const parsed = JSON.parse(stdout);
|
|
31898
|
-
if (!Array.isArray(parsed.range) || parsed.range.length !== 2) throw new Error("port-ddb: no range in output");
|
|
31899
|
-
return parsed.range;
|
|
31900
|
-
};
|
|
31901
|
-
const { range: [start, end], source } = await ensurePortRangeAtomic(repo, path2, allocate);
|
|
31902
|
-
const write = await upsertProject(slug, { portRange: { start, end } }, reg);
|
|
31903
|
-
if (!write.ok && source === "ddb") {
|
|
31904
|
-
return failGraceful(`stage port-range: block [${start}, ${end}] was allocated (cursor advanced) but NOT recorded in the registry META (${write.error ?? `HTTP ${write.status}`}) \u2014 fix auth/connectivity and retry so the block is persisted; do not re-run blind`);
|
|
31905
|
-
}
|
|
31906
32704
|
if (o.json) {
|
|
31907
|
-
printLine(JSON.stringify({
|
|
32705
|
+
printLine(JSON.stringify({
|
|
32706
|
+
repo,
|
|
32707
|
+
portRange: [start, end],
|
|
32708
|
+
source: "allocated",
|
|
32709
|
+
persisted: assigned.persisted,
|
|
32710
|
+
...assigned.persisted ? {} : { persistError: assigned.persistError }
|
|
32711
|
+
}));
|
|
31908
32712
|
} else {
|
|
31909
|
-
printLine(`${repo}: stage.portRange [${start}, ${end}]${
|
|
32713
|
+
printLine(`${repo}: stage.portRange [${start}, ${end}]${assigned.persisted ? "" : ` (META not persisted: ${assigned.persistError})`}`);
|
|
31910
32714
|
}
|
|
31911
32715
|
});
|
|
31912
32716
|
async function stageLiveTarget() {
|
|
@@ -31992,7 +32796,7 @@ function registerStageCommands(program3) {
|
|
|
31992
32796
|
}
|
|
31993
32797
|
}
|
|
31994
32798
|
const steps = stageStepsFor(res);
|
|
31995
|
-
if (o.json) return console.log(JSON.stringify({ command: "stage",
|
|
32799
|
+
if (o.json) return console.log(JSON.stringify({ command: "stage", ...stageReceiptFields(res), steps }, null, 2));
|
|
31996
32800
|
console.log(renderSteps("mmi-cli stage: dry-run plan", steps));
|
|
31997
32801
|
});
|
|
31998
32802
|
stage.command("stop").description("stop the previous local stage process recorded in tmp/stage/state.json").option("--json", "machine-readable output").option("--apply", "kill the recorded process tree and remove the state file").action(async () => {
|
|
@@ -32013,7 +32817,7 @@ function registerStageCommands(program3) {
|
|
|
32013
32817
|
const res = await resolveStage();
|
|
32014
32818
|
if (!o.apply) {
|
|
32015
32819
|
const steps = stageStepsFor(res, false);
|
|
32016
|
-
if (o.json) return printLine(JSON.stringify({ command: "stage start",
|
|
32820
|
+
if (o.json) return printLine(JSON.stringify({ command: "stage start", ...stageReceiptFields(res), steps }, null, 2));
|
|
32017
32821
|
return printLine(renderSteps("mmi-cli stage start: dry-run plan", steps));
|
|
32018
32822
|
}
|
|
32019
32823
|
if (res.source === "none") return failGraceful(`stage start: ${res.gap}`);
|
|
@@ -32046,7 +32850,7 @@ function registerStageCommands(program3) {
|
|
|
32046
32850
|
const res = await resolveStage();
|
|
32047
32851
|
if (!o.apply) {
|
|
32048
32852
|
const steps = stageStepsFor(res);
|
|
32049
|
-
if (o.json) return printLine(JSON.stringify({ command: "stage run",
|
|
32853
|
+
if (o.json) return printLine(JSON.stringify({ command: "stage run", ...stageReceiptFields(res), steps }, null, 2));
|
|
32050
32854
|
return printLine(renderSteps("mmi-cli stage run: dry-run plan", steps));
|
|
32051
32855
|
}
|
|
32052
32856
|
if (res.source === "none") return failGraceful(`stage run: ${res.gap}`);
|
|
@@ -32078,9 +32882,9 @@ function registerStageCommands(program3) {
|
|
|
32078
32882
|
}
|
|
32079
32883
|
|
|
32080
32884
|
// src/merge-cleanup.ts
|
|
32081
|
-
var
|
|
32082
|
-
var
|
|
32083
|
-
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");
|
|
32084
32888
|
init_cli_shared();
|
|
32085
32889
|
|
|
32086
32890
|
// src/config-load.ts
|
|
@@ -32357,13 +33161,13 @@ async function composeOverrideBodyFile(prNumber, repoArgs, gh) {
|
|
|
32357
33161
|
const commits = JSON.parse(raw).commits ?? [];
|
|
32358
33162
|
const body = squashBodyWithOverride(commits.map((c) => ({ headline: c.messageHeadline ?? "", body: c.messageBody ?? "" })), process.cwd());
|
|
32359
33163
|
if (!body) return void 0;
|
|
32360
|
-
const dir = (0,
|
|
32361
|
-
const path2 = (0,
|
|
32362
|
-
(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}
|
|
32363
33167
|
`, "utf8");
|
|
32364
33168
|
return { path: path2, cleanup: () => {
|
|
32365
33169
|
try {
|
|
32366
|
-
(0,
|
|
33170
|
+
(0, import_node_fs40.rmSync)(dir, { recursive: true, force: true });
|
|
32367
33171
|
} catch {
|
|
32368
33172
|
}
|
|
32369
33173
|
} };
|
|
@@ -32570,7 +33374,9 @@ function registerBoardCommands(program3) {
|
|
|
32570
33374
|
"Pass raw issue numbers/refs, not URLs.",
|
|
32571
33375
|
"Claim already assigns and moves Status to In Progress, so do not also board move it.",
|
|
32572
33376
|
"Multiple refs are handled as a batch and return per-item results.",
|
|
32573
|
-
"--check is the live gate read (it calls GitHub); --dry-run only echoes the parsed argv plan."
|
|
33377
|
+
"--check is the live gate read (it calls GitHub); --dry-run only echoes the parsed argv plan.",
|
|
33378
|
+
// #5552: agents guess `oracle issue claim`; that route does not exist — board claim is the only write.
|
|
33379
|
+
"Never run `oracle issue claim` \u2014 claims are board mutations; only `oracle board claim <ref>` is valid."
|
|
32574
33380
|
]);
|
|
32575
33381
|
board.command("show <issue>").description("print one board item (status, assignees, type, url) with its body and comments").option("--json", "machine-readable output").option("--repo <owner/repo>", "current repo for local issue numbers (defaults to git origin)").option("--allow-partial", "return the item even if its body/comments fetch fails").action(async (issueRef, o) => {
|
|
32576
33382
|
try {
|
|
@@ -32859,18 +33665,33 @@ var PR_SNAPSHOT_READ_RETRIES = 3;
|
|
|
32859
33665
|
var PR_SNAPSHOT_READ_DELAY_MS = 2e3;
|
|
32860
33666
|
async function readRestPrSnapshotWithRetry(prNumber, repo, gh = defaultGhApi, options) {
|
|
32861
33667
|
const retries = options?.retries ?? PR_SNAPSHOT_READ_RETRIES;
|
|
32862
|
-
const
|
|
32863
|
-
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());
|
|
32864
33672
|
let lastError = "no attempt completed";
|
|
32865
|
-
|
|
33673
|
+
let attempt = 0;
|
|
33674
|
+
for (; ; ) {
|
|
32866
33675
|
try {
|
|
32867
33676
|
return { state: "ok", snapshot: await fetchRestPrSnapshot(prNumber, repo, gh) };
|
|
32868
33677
|
} catch (e) {
|
|
32869
33678
|
lastError = readErrorText(e);
|
|
33679
|
+
attempt += 1;
|
|
33680
|
+
const retryable = isRetryableGitHubWaitReadError(e);
|
|
33681
|
+
if (!retryable) {
|
|
33682
|
+
return { state: "failed", error: `pulls read failed for #${prNumber} on ${repo}: ${lastError}` };
|
|
33683
|
+
}
|
|
33684
|
+
const canRetry = untilMs !== void 0 ? now() + delayMs < untilMs : attempt < retries;
|
|
33685
|
+
if (!canRetry) {
|
|
33686
|
+
return {
|
|
33687
|
+
state: "failed",
|
|
33688
|
+
error: `pulls read failed for #${prNumber} on ${repo} after ${attempt} attempts: ${lastError}`
|
|
33689
|
+
};
|
|
33690
|
+
}
|
|
33691
|
+
options?.onTransientRetry?.(lastError, attempt);
|
|
33692
|
+
await sleep2(delayMs);
|
|
32870
33693
|
}
|
|
32871
|
-
if (attempt < retries - 1) await sleep2(delayMs);
|
|
32872
33694
|
}
|
|
32873
|
-
return { state: "failed", error: `pulls read failed for #${prNumber} on ${repo} after ${retries} attempts: ${lastError}` };
|
|
32874
33695
|
}
|
|
32875
33696
|
async function fetchRestClosingGuardPayload(prNumber, repo, gh = defaultGhApi) {
|
|
32876
33697
|
const pr2 = JSON.parse(await gh([`repos/${repo}/pulls/${prNumber}`]));
|
|
@@ -33160,8 +33981,8 @@ async function checkDocsIndexAtHead(opts, deps) {
|
|
|
33160
33981
|
}
|
|
33161
33982
|
|
|
33162
33983
|
// src/issue-commands.ts
|
|
33163
|
-
var
|
|
33164
|
-
var
|
|
33984
|
+
var import_node_fs41 = require("node:fs");
|
|
33985
|
+
var import_node_crypto16 = require("node:crypto");
|
|
33165
33986
|
init_cli_shared();
|
|
33166
33987
|
init_clean_exit();
|
|
33167
33988
|
init_error_codes();
|
|
@@ -33353,7 +34174,7 @@ async function editIssue(client, options, deps = {}) {
|
|
|
33353
34174
|
const url = `https://github.com/${repo}/issues/${parsed.number}`;
|
|
33354
34175
|
const patch = {};
|
|
33355
34176
|
let bodyChanged = false;
|
|
33356
|
-
const textDeps = () => deps.textDeps ?? { readFile: (p, e) => Promise.resolve((0,
|
|
34177
|
+
const textDeps = () => deps.textDeps ?? { readFile: (p, e) => Promise.resolve((0, import_node_fs41.readFileSync)(p, e)), readStdin: () => Promise.resolve("") };
|
|
33357
34178
|
if (options.titleFile !== void 0) {
|
|
33358
34179
|
patch.title = await resolveIssueTitle({ title: options.title, titleFile: options.titleFile }, textDeps());
|
|
33359
34180
|
} else if (options.title !== void 0) {
|
|
@@ -33634,7 +34455,7 @@ function rowIdempotencyKey(batchKey, spec) {
|
|
|
33634
34455
|
const identity = `${spec.type}
|
|
33635
34456
|
${spec.title.trim()}
|
|
33636
34457
|
${spec.body ?? ""}`;
|
|
33637
|
-
const hash = (0,
|
|
34458
|
+
const hash = (0, import_node_crypto16.createHash)("sha256").update(identity).digest("hex").slice(0, 16);
|
|
33638
34459
|
return `${batchKey}:${hash}`;
|
|
33639
34460
|
}
|
|
33640
34461
|
var BATCH_SPEC_KEYS = /* @__PURE__ */ new Set(["type", "title", "body", "priority", "labels", "label", "parent", "repo", "surface"]);
|
|
@@ -33970,7 +34791,7 @@ function extendCreateCommand(issue2, batchAttach) {
|
|
|
33970
34791
|
if (opts.batch) {
|
|
33971
34792
|
let specs;
|
|
33972
34793
|
try {
|
|
33973
|
-
const raw = (0,
|
|
34794
|
+
const raw = (0, import_node_fs41.readFileSync)(opts.batch, "utf8");
|
|
33974
34795
|
specs = JSON.parse(raw);
|
|
33975
34796
|
if (!Array.isArray(specs)) throw new Error("batch file must contain a JSON array");
|
|
33976
34797
|
} catch (e) {
|
|
@@ -34045,8 +34866,8 @@ ${lines}`, {
|
|
|
34045
34866
|
}
|
|
34046
34867
|
|
|
34047
34868
|
// src/train-commands.ts
|
|
34048
|
-
var
|
|
34049
|
-
var
|
|
34869
|
+
var import_node_fs42 = require("node:fs");
|
|
34870
|
+
var import_node_path38 = require("node:path");
|
|
34050
34871
|
init_cli_shared();
|
|
34051
34872
|
init_clean_exit();
|
|
34052
34873
|
init_client_version();
|
|
@@ -34061,7 +34882,7 @@ function resolveReleaseBumpIntent(raw) {
|
|
|
34061
34882
|
}
|
|
34062
34883
|
function readRepoVersion() {
|
|
34063
34884
|
try {
|
|
34064
|
-
return JSON.parse((0,
|
|
34885
|
+
return JSON.parse((0, import_node_fs42.readFileSync)((0, import_node_path38.join)(process.cwd(), ".claude-plugin", "plugin.json"), "utf8")).version || void 0;
|
|
34065
34886
|
} catch {
|
|
34066
34887
|
return void 0;
|
|
34067
34888
|
}
|
|
@@ -34225,9 +35046,9 @@ function registerDeployCommands(program3) {
|
|
|
34225
35046
|
init_cli_shared();
|
|
34226
35047
|
init_github_client();
|
|
34227
35048
|
init_cli_shared();
|
|
34228
|
-
var
|
|
34229
|
-
var
|
|
34230
|
-
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");
|
|
34231
35052
|
init_marketplace_autoupdate();
|
|
34232
35053
|
var GC_GH_TIMEOUT_MS2 = 2e4;
|
|
34233
35054
|
async function collectStatus() {
|
|
@@ -34346,7 +35167,7 @@ function onboardPluginGate(deps) {
|
|
|
34346
35167
|
declared,
|
|
34347
35168
|
settingsDeclared: readSettingsAutoUpdate(deps.readSettings(), MMI_MARKETPLACE_NAME)
|
|
34348
35169
|
}).effective;
|
|
34349
|
-
return autoUpdate ? { ok:
|
|
35170
|
+
return autoUpdate ? { ok: true, detail: "background auto-update on \u2014 org pin; thin catalog is release-gated" } : { ok: false, detail: "background auto-update is OFF \u2014 this machine will not pick up a new plugin release on its own; run `mmi-cli doctor` to pin it on" };
|
|
34350
35171
|
}
|
|
34351
35172
|
async function collectOnboardStatus(opts = {}) {
|
|
34352
35173
|
const cfg = await loadConfig();
|
|
@@ -34428,10 +35249,10 @@ async function collectOnboardStatus(opts = {}) {
|
|
|
34428
35249
|
else if (top) nextCommand = `mmi-cli oracle board claim ${top.number} # ${top.title}`;
|
|
34429
35250
|
else nextCommand = "mmi-cli oracle board read \u2014 no claimable items found";
|
|
34430
35251
|
}
|
|
34431
|
-
const home = (0,
|
|
35252
|
+
const home = (0, import_node_os19.homedir)();
|
|
34432
35253
|
const plugin = onboardPluginGate({
|
|
34433
|
-
readKnown: () => readFileSyncSafe((0,
|
|
34434
|
-
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)
|
|
34435
35256
|
});
|
|
34436
35257
|
return { track, board, registry: registry2, secrets, plugin, estateCli, doors: opts.doors ?? [], nextCommand };
|
|
34437
35258
|
}
|
|
@@ -34514,12 +35335,17 @@ var LOOP_PLAYBOOKS = {
|
|
|
34514
35335
|
{ label: "Orient in the current repository", command: "mmi-cli onboard" },
|
|
34515
35336
|
{ label: "Structure search: `find` is semantic default; `repo-index search` is hybrid. Cloud default (`--local` is checkout-only); verify repo/commit/path/lines and result retrieval metadata; status is runtime authority", command: "mmi-cli oracle find <q>" },
|
|
34516
35337
|
{ label: "Read the next board item", command: "mmi-cli oracle board read" },
|
|
35338
|
+
// #5552: claim is a board mutation only — never guess `oracle issue claim`. Ground unknown write
|
|
35339
|
+
// routes with `mmi-cli commands` / `mmi-cli explain` before invoking them.
|
|
35340
|
+
{ label: "Claim board work (board mutation only \u2014 never `oracle issue claim`; ground unknown write routes via `commands` / `explain` first)", command: "mmi-cli oracle board claim <ref>" },
|
|
34517
35341
|
{ label: "Prepare the local workspace through the host surface" },
|
|
34518
35342
|
{ label: "Apply the repository test policy, then build the touched package", command: "mmi-cli tests policy --base origin/development && npm run build" },
|
|
34519
35343
|
{ label: "Publish the branch", command: "git push origin <branch>:<branch>" },
|
|
34520
35344
|
{ label: "Open the development-base PR", command: 'mmi-cli devops pr create --title "<title>" --body-file .jerv/PR_BODY.md --base development' },
|
|
34521
35345
|
{ label: "Wait for checks and land to development", command: "mmi-cli devops pr checks-wait <PR-number> && mmi-cli devops pr land <PR-number>" },
|
|
34522
|
-
{ label: "Release only after the gated train is authorized", command: "mmi-cli devops release --apply" }
|
|
35346
|
+
{ label: "Release only after the gated train is authorized", command: "mmi-cli devops release --apply" },
|
|
35347
|
+
// #5552: learning-tagged filings are cloud-agent owned — file and return to the current task.
|
|
35348
|
+
{ label: "Learning reports are fire-and-forget (file, then return to the current task \u2014 never claim/poll/duplicate the learning issue)", command: 'mmi-cli learning report --title "<one-line>" --body "<what hurt>"' }
|
|
34523
35349
|
]
|
|
34524
35350
|
},
|
|
34525
35351
|
"start-work": {
|
|
@@ -34527,6 +35353,7 @@ var LOOP_PLAYBOOKS = {
|
|
|
34527
35353
|
steps: [
|
|
34528
35354
|
{ label: "Orient in the current repository", command: "mmi-cli onboard" },
|
|
34529
35355
|
{ label: "Read the board item", command: "mmi-cli oracle board show <issue-number>" },
|
|
35356
|
+
{ label: "Claim the item (board mutation only \u2014 never `oracle issue claim`)", command: "mmi-cli oracle board claim <issue-number>" },
|
|
34530
35357
|
{ label: "Prepare the local workspace through the host surface" },
|
|
34531
35358
|
{ label: "Start a local stage (deployable repos)", command: "mmi-cli stage run --apply" }
|
|
34532
35359
|
]
|
|
@@ -35144,8 +35971,8 @@ function registerPrLifecycleCommands(program3) {
|
|
|
35144
35971
|
}
|
|
35145
35972
|
|
|
35146
35973
|
// src/post-merge-recon.ts
|
|
35147
|
-
var
|
|
35148
|
-
var
|
|
35974
|
+
var import_node_fs44 = require("node:fs");
|
|
35975
|
+
var import_node_path40 = require("node:path");
|
|
35149
35976
|
|
|
35150
35977
|
// src/cross-repo-filing-issue.ts
|
|
35151
35978
|
init_github_client();
|
|
@@ -35312,16 +36139,16 @@ function buildPostMergeReconRecovery(input) {
|
|
|
35312
36139
|
}
|
|
35313
36140
|
function writePostMergeReconRecovery(cwd, recovery) {
|
|
35314
36141
|
const path2 = postMergeReconStatePath(cwd, recovery.repo, recovery.pr);
|
|
35315
|
-
(0,
|
|
35316
|
-
(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)}
|
|
35317
36144
|
`, "utf8");
|
|
35318
36145
|
return path2;
|
|
35319
36146
|
}
|
|
35320
36147
|
function clearPostMergeReconRecovery(cwd, repo, pr2) {
|
|
35321
36148
|
const path2 = postMergeReconStatePath(cwd, repo, pr2);
|
|
35322
|
-
if (!(0,
|
|
36149
|
+
if (!(0, import_node_fs44.existsSync)(path2)) return;
|
|
35323
36150
|
try {
|
|
35324
|
-
(0,
|
|
36151
|
+
(0, import_node_fs44.unlinkSync)(path2);
|
|
35325
36152
|
} catch {
|
|
35326
36153
|
}
|
|
35327
36154
|
}
|
|
@@ -37140,7 +37967,8 @@ function diagnoseSurface(evidence) {
|
|
|
37140
37967
|
const base = {
|
|
37141
37968
|
descriptor: evidence.descriptor,
|
|
37142
37969
|
...evidence.installedVersion ? { installedVersion: evidence.installedVersion } : {},
|
|
37143
|
-
...evidence.releasedVersion ? { releasedVersion: evidence.releasedVersion } : {}
|
|
37970
|
+
...evidence.releasedVersion ? { releasedVersion: evidence.releasedVersion } : {},
|
|
37971
|
+
...evidence.receipt ? { receipt: evidence.receipt } : {}
|
|
37144
37972
|
};
|
|
37145
37973
|
if (!evidence.applicable) return { ...base, state: "skipped" };
|
|
37146
37974
|
if (evidence.repair?.attempted && !evidence.repair.ok) {
|
|
@@ -37230,6 +38058,7 @@ function buildSurfaceDoctorCheck(diagnosis) {
|
|
|
37230
38058
|
`install: ${descriptor.installMechanism} (${descriptor.installLocator})`,
|
|
37231
38059
|
`repair owner: ${descriptor.repairOwner}`,
|
|
37232
38060
|
`artifacts: ${descriptor.artifactIds.join(", ")}`,
|
|
38061
|
+
...diagnosis.receipt ? [`receipt: ${diagnosis.receipt}`] : [],
|
|
37233
38062
|
...diagnosis.repairDetail ? [`heal: ${diagnosis.repairDetail}`] : []
|
|
37234
38063
|
]
|
|
37235
38064
|
};
|
|
@@ -38445,18 +39274,18 @@ function parseOriginRepo(remoteUrl) {
|
|
|
38445
39274
|
return `${match[1]}/${match[2]}`;
|
|
38446
39275
|
}
|
|
38447
39276
|
function ghHostsConfigPath(env, platform2) {
|
|
38448
|
-
const
|
|
38449
|
-
const
|
|
39277
|
+
const sep4 = platform2 === "win32" ? "\\" : "/";
|
|
39278
|
+
const join36 = (...parts) => parts.join(sep4);
|
|
38450
39279
|
const explicit = env.GH_CONFIG_DIR?.trim();
|
|
38451
|
-
if (explicit) return
|
|
39280
|
+
if (explicit) return join36(explicit, "hosts.yml");
|
|
38452
39281
|
if (platform2 === "win32") {
|
|
38453
39282
|
const appData = (env.AppData ?? env.APPDATA)?.trim();
|
|
38454
|
-
return appData ?
|
|
39283
|
+
return appData ? join36(appData, "GitHub CLI", "hosts.yml") : void 0;
|
|
38455
39284
|
}
|
|
38456
39285
|
const xdg = env.XDG_CONFIG_HOME?.trim();
|
|
38457
|
-
if (xdg) return
|
|
39286
|
+
if (xdg) return join36(xdg, "gh", "hosts.yml");
|
|
38458
39287
|
const home = env.HOME?.trim();
|
|
38459
|
-
return home ?
|
|
39288
|
+
return home ? join36(home, ".config", "gh", "hosts.yml") : void 0;
|
|
38460
39289
|
}
|
|
38461
39290
|
function parseGhHostsAccounts(yaml, host = "github.com") {
|
|
38462
39291
|
let hostIndent = null;
|
|
@@ -38506,19 +39335,41 @@ function ghAccountCaveat(announcedLogin, accounts) {
|
|
|
38506
39335
|
}
|
|
38507
39336
|
|
|
38508
39337
|
// src/doctor-io.ts
|
|
38509
|
-
var
|
|
38510
|
-
var
|
|
38511
|
-
var
|
|
38512
|
-
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");
|
|
38513
39342
|
var import_node_util8 = require("node:util");
|
|
38514
39343
|
init_version_lag();
|
|
38515
39344
|
init_plugin_guard_io();
|
|
38516
|
-
|
|
39345
|
+
|
|
39346
|
+
// src/discard-sink.ts
|
|
39347
|
+
function nodeDiscardSinkPath(platform2 = process.platform) {
|
|
39348
|
+
return platform2 === "win32" ? "\\\\.\\NUL" : "/dev/null";
|
|
39349
|
+
}
|
|
39350
|
+
|
|
39351
|
+
// src/doctor-io.ts
|
|
39352
|
+
var execFileP6 = (0, import_node_util8.promisify)(import_node_child_process20.execFile);
|
|
39353
|
+
function execFileCapture(file, args, opts = {}) {
|
|
39354
|
+
const sink = nodeDiscardSinkPath();
|
|
39355
|
+
const inFd = (0, import_node_fs45.openSync)(sink, "r");
|
|
39356
|
+
const errFd = (0, import_node_fs45.openSync)(sink, "w");
|
|
39357
|
+
try {
|
|
39358
|
+
return (0, import_node_child_process20.execFileSync)(file, args, {
|
|
39359
|
+
...opts,
|
|
39360
|
+
encoding: "utf8",
|
|
39361
|
+
stdio: [inFd, "pipe", errFd]
|
|
39362
|
+
});
|
|
39363
|
+
} finally {
|
|
39364
|
+
(0, import_node_fs45.closeSync)(inFd);
|
|
39365
|
+
(0, import_node_fs45.closeSync)(errFd);
|
|
39366
|
+
}
|
|
39367
|
+
}
|
|
38517
39368
|
var MMI_PLUGIN_ID2 = "mmi@mutmutco";
|
|
38518
39369
|
function installedClaudePluginVersion() {
|
|
38519
39370
|
try {
|
|
38520
39371
|
const file = JSON.parse(
|
|
38521
|
-
(0,
|
|
39372
|
+
(0, import_node_fs45.readFileSync)((0, import_node_path41.join)((0, import_node_os20.homedir)(), ".claude", "plugins", "installed_plugins.json"), "utf8")
|
|
38522
39373
|
);
|
|
38523
39374
|
const versions = (file.plugins?.[MMI_PLUGIN_ID2] ?? []).map((r) => r.version).filter((v) => Boolean(v));
|
|
38524
39375
|
if (versions.length === 0) return void 0;
|
|
@@ -38529,7 +39380,7 @@ function installedClaudePluginVersion() {
|
|
|
38529
39380
|
}
|
|
38530
39381
|
function manifestVersion(path2) {
|
|
38531
39382
|
try {
|
|
38532
|
-
const manifest = JSON.parse((0,
|
|
39383
|
+
const manifest = JSON.parse((0, import_node_fs45.readFileSync)(path2, "utf8"));
|
|
38533
39384
|
return typeof manifest.version === "string" && manifest.version.trim() ? manifest.version.trim() : void 0;
|
|
38534
39385
|
} catch {
|
|
38535
39386
|
return void 0;
|
|
@@ -38538,12 +39389,12 @@ function manifestVersion(path2) {
|
|
|
38538
39389
|
function readHermesPluginEvidence(env = process.env) {
|
|
38539
39390
|
const host = hermesConfigRoot(env);
|
|
38540
39391
|
const root = hermesPluginRoot(env);
|
|
38541
|
-
const installRecordPresent = (0,
|
|
38542
|
-
const manifestPath = (0,
|
|
39392
|
+
const installRecordPresent = (0, import_node_fs45.existsSync)(root);
|
|
39393
|
+
const manifestPath = (0, import_node_path41.join)(root, "plugin.yaml");
|
|
38543
39394
|
let installedVersion;
|
|
38544
39395
|
let manifest = "missing";
|
|
38545
39396
|
try {
|
|
38546
|
-
const text = (0,
|
|
39397
|
+
const text = (0, import_node_fs45.readFileSync)(manifestPath, "utf8");
|
|
38547
39398
|
let version;
|
|
38548
39399
|
try {
|
|
38549
39400
|
const parsed = JSON.parse(text).version;
|
|
@@ -38557,20 +39408,20 @@ function readHermesPluginEvidence(env = process.env) {
|
|
|
38557
39408
|
if (version) {
|
|
38558
39409
|
installedVersion = version;
|
|
38559
39410
|
manifest = "valid";
|
|
38560
|
-
} else if ((0,
|
|
39411
|
+
} else if ((0, import_node_fs45.existsSync)(manifestPath)) manifest = "invalid";
|
|
38561
39412
|
} catch {
|
|
38562
|
-
if ((0,
|
|
39413
|
+
if ((0, import_node_fs45.existsSync)(manifestPath)) manifest = "invalid";
|
|
38563
39414
|
}
|
|
38564
39415
|
let skills = false;
|
|
38565
39416
|
try {
|
|
38566
|
-
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();
|
|
38567
39418
|
} catch {
|
|
38568
39419
|
}
|
|
38569
39420
|
return {
|
|
38570
|
-
hostPresent: (0,
|
|
39421
|
+
hostPresent: (0, import_node_fs45.existsSync)(host),
|
|
38571
39422
|
installRecordPresent,
|
|
38572
39423
|
manifest,
|
|
38573
|
-
payloadPresent: (0,
|
|
39424
|
+
payloadPresent: (0, import_node_fs45.existsSync)((0, import_node_path41.join)(root, "__init__.py")) && skills && manifest === "valid",
|
|
38574
39425
|
...installedVersion ? { installedVersion } : {}
|
|
38575
39426
|
};
|
|
38576
39427
|
}
|
|
@@ -38578,7 +39429,7 @@ function installedSurfacePluginVersion(surface) {
|
|
|
38578
39429
|
const token = surfaceToken(surface);
|
|
38579
39430
|
if (token === "kilo") {
|
|
38580
39431
|
try {
|
|
38581
|
-
const stamp = (0,
|
|
39432
|
+
const stamp = (0, import_node_fs45.readFileSync)((0, import_node_path41.join)((0, import_node_os20.homedir)(), ".kilo", ".mmi-kilo-version"), "utf8").trim();
|
|
38582
39433
|
return stamp || void 0;
|
|
38583
39434
|
} catch {
|
|
38584
39435
|
return void 0;
|
|
@@ -38586,25 +39437,21 @@ function installedSurfacePluginVersion(surface) {
|
|
|
38586
39437
|
}
|
|
38587
39438
|
if (token === "hermes") return readHermesPluginEvidence().installedVersion;
|
|
38588
39439
|
if (token === "cursor") {
|
|
38589
|
-
return manifestVersion((0,
|
|
39440
|
+
return manifestVersion((0, import_node_path41.join)(cursorLocalPluginRoot(), ".cursor-plugin", "plugin.json"));
|
|
38590
39441
|
}
|
|
38591
39442
|
if (token === "jervcode") {
|
|
38592
39443
|
return installedJervCodePackageVersion();
|
|
38593
39444
|
}
|
|
38594
39445
|
if (token === "kimi") {
|
|
38595
|
-
return manifestVersion((0,
|
|
39446
|
+
return manifestVersion((0, import_node_path41.join)(surfaceConfigRoot(surface), "plugins", "managed", "mmi", ".kimi-plugin", "plugin.json"));
|
|
38596
39447
|
}
|
|
38597
39448
|
if (token === "claude") return installedClaudePluginVersion();
|
|
38598
39449
|
if (token !== "codex") return void 0;
|
|
38599
39450
|
try {
|
|
38600
|
-
const raw = process.platform === "win32" ? (
|
|
38601
|
-
encoding: "utf8",
|
|
38602
|
-
stdio: ["ignore", "pipe", "ignore"],
|
|
39451
|
+
const raw = process.platform === "win32" ? execFileCapture("cmd.exe", ["/c", "codex", "plugin", "list", "--json"], {
|
|
38603
39452
|
timeout: 15e3,
|
|
38604
39453
|
windowsHide: true
|
|
38605
|
-
}) : (
|
|
38606
|
-
encoding: "utf8",
|
|
38607
|
-
stdio: ["ignore", "pipe", "ignore"],
|
|
39454
|
+
}) : execFileCapture("codex", ["plugin", "list", "--json"], {
|
|
38608
39455
|
timeout: 15e3,
|
|
38609
39456
|
windowsHide: true
|
|
38610
39457
|
});
|
|
@@ -38620,7 +39467,7 @@ function installedActivePluginVersion(surface = detectSurface(process.env)) {
|
|
|
38620
39467
|
}
|
|
38621
39468
|
function worktreeRootSync() {
|
|
38622
39469
|
try {
|
|
38623
|
-
const out = (
|
|
39470
|
+
const out = execFileCapture("git", ["rev-parse", "--show-toplevel"], { windowsHide: true });
|
|
38624
39471
|
let root = out.endsWith("\n") ? out.slice(0, -1) : out;
|
|
38625
39472
|
if (process.platform === "win32" && root.endsWith("\r")) root = root.slice(0, -1);
|
|
38626
39473
|
return root || null;
|
|
@@ -38630,13 +39477,13 @@ function worktreeRootSync() {
|
|
|
38630
39477
|
}
|
|
38631
39478
|
var gitignorePath = () => {
|
|
38632
39479
|
const root = worktreeRootSync();
|
|
38633
|
-
return root === null ? null : (0,
|
|
39480
|
+
return root === null ? null : (0, import_node_path41.join)(root, ".gitignore");
|
|
38634
39481
|
};
|
|
38635
39482
|
function readGitignore() {
|
|
38636
39483
|
const path2 = gitignorePath();
|
|
38637
39484
|
if (path2 === null) return null;
|
|
38638
39485
|
try {
|
|
38639
|
-
return (0,
|
|
39486
|
+
return (0, import_node_fs45.readFileSync)(path2, "utf8");
|
|
38640
39487
|
} catch {
|
|
38641
39488
|
return null;
|
|
38642
39489
|
}
|
|
@@ -38645,19 +39492,17 @@ function writeGitignore(content) {
|
|
|
38645
39492
|
const path2 = gitignorePath();
|
|
38646
39493
|
if (path2 === null) return false;
|
|
38647
39494
|
try {
|
|
38648
|
-
(0,
|
|
39495
|
+
(0, import_node_fs45.writeFileSync)(path2, content, "utf8");
|
|
38649
39496
|
return true;
|
|
38650
39497
|
} catch {
|
|
38651
39498
|
return false;
|
|
38652
39499
|
}
|
|
38653
39500
|
}
|
|
38654
39501
|
function lineEndingState(root) {
|
|
38655
|
-
const attributesPresent = (0,
|
|
39502
|
+
const attributesPresent = (0, import_node_fs45.existsSync)((0, import_node_path41.join)(root, ".gitattributes"));
|
|
38656
39503
|
try {
|
|
38657
|
-
const output = (
|
|
38658
|
-
windowsHide: true
|
|
38659
|
-
encoding: "utf8",
|
|
38660
|
-
stdio: ["ignore", "pipe", "ignore"]
|
|
39504
|
+
const output = execFileCapture("git", ["-C", root, "ls-files", "--eol", "--", ":(glob)**/*.sh"], {
|
|
39505
|
+
windowsHide: true
|
|
38661
39506
|
});
|
|
38662
39507
|
const crlfShellScripts = output.split(/\r?\n/).filter((line) => line.startsWith("i/crlf ")).map((line) => line.slice(line.indexOf(" ") + 1)).filter(Boolean);
|
|
38663
39508
|
return { attributesPresent, crlfShellScripts };
|
|
@@ -38721,8 +39566,8 @@ ${r.stderr ?? ""}`).catch(() => "");
|
|
|
38721
39566
|
function ghMultiAccountCaveat(announcedLogin) {
|
|
38722
39567
|
try {
|
|
38723
39568
|
const hostsPath = ghHostsConfigPath(process.env, process.platform);
|
|
38724
|
-
if (!hostsPath || !(0,
|
|
38725
|
-
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")));
|
|
38726
39571
|
} catch {
|
|
38727
39572
|
return void 0;
|
|
38728
39573
|
}
|
|
@@ -38730,12 +39575,12 @@ function ghMultiAccountCaveat(announcedLogin) {
|
|
|
38730
39575
|
var ENV_HEAL_LOCK_STALE_MS = 10 * 6e4;
|
|
38731
39576
|
var ENV_HEAL_LOCK_MAX_WAIT_MS = 2 * 6e4;
|
|
38732
39577
|
function envHealLockPath(home) {
|
|
38733
|
-
return (0,
|
|
39578
|
+
return (0, import_node_path42.join)(home, ".claude", "plugins", ".mmi-env-heal.lock");
|
|
38734
39579
|
}
|
|
38735
39580
|
async function withEnvHealLock(what, run) {
|
|
38736
39581
|
try {
|
|
38737
39582
|
return await withFileLock(
|
|
38738
|
-
envHealLockPath((0,
|
|
39583
|
+
envHealLockPath((0, import_node_os21.homedir)()),
|
|
38739
39584
|
{ staleMs: ENV_HEAL_LOCK_STALE_MS, maxWaitMs: ENV_HEAL_LOCK_MAX_WAIT_MS, label: "mmi env-heal lock" },
|
|
38740
39585
|
run
|
|
38741
39586
|
);
|
|
@@ -38777,17 +39622,19 @@ function mmiDoctorDeps(opts = {}) {
|
|
|
38777
39622
|
const descriptor = doctorSurface(token);
|
|
38778
39623
|
const snapshot = snapshotPluginGuardInput(runtimeSurface, isOrgRepo);
|
|
38779
39624
|
const hermes = token === "hermes" ? readHermesPluginEvidence(process.env) : void 0;
|
|
39625
|
+
const kimi = token === "kimi" ? kimiPluginHostEvidence(surfaceConfigRoot("kimi")) : void 0;
|
|
38780
39626
|
const installedVersion = hermes?.installedVersion ?? installedSurfacePluginVersion(runtimeSurface);
|
|
38781
39627
|
surfaceEvidence = {
|
|
38782
39628
|
descriptor,
|
|
38783
39629
|
// Hermes' root is the host evidence: a configured but uninstalled MMI tree is missing, while an
|
|
38784
39630
|
// absent root is skipped. Other mature surfaces retain their established org/install applicability.
|
|
38785
39631
|
applicable: hermes ? hermes.hostPresent : isOrgRepo || snapshot.installRecordPresent || snapshot.pluginCachePresent,
|
|
38786
|
-
installRecordPresent: hermes?.installRecordPresent ?? snapshot.installRecordPresent,
|
|
39632
|
+
installRecordPresent: hermes?.installRecordPresent ?? (kimi ? kimi.registration === "healthy" : snapshot.installRecordPresent),
|
|
38787
39633
|
deliveryPresent: hermes ? hermes.installRecordPresent : snapshot.marketplaceClonePresent,
|
|
38788
|
-
payloadPresent: hermes?.payloadPresent ?? snapshot.pluginCachePresent,
|
|
39634
|
+
payloadPresent: hermes?.payloadPresent ?? (kimi ? kimi.healthy : snapshot.pluginCachePresent),
|
|
38789
39635
|
manifest: hermes?.manifest ?? (installedVersion ? "valid" : snapshot.installRecordPresent ? "invalid" : "missing"),
|
|
38790
39636
|
guardState: buildPluginGuardDecision(snapshot).state,
|
|
39637
|
+
...kimi?.receipt ? { receipt: kimi.receipt } : {},
|
|
38791
39638
|
...installedVersion ? { installedVersion } : {}
|
|
38792
39639
|
};
|
|
38793
39640
|
return surfaceEvidence;
|
|
@@ -38830,7 +39677,7 @@ function mmiDoctorDeps(opts = {}) {
|
|
|
38830
39677
|
const configRoot = surfaceConfigRoot(surface);
|
|
38831
39678
|
const running = surface === "codex" ? installedActivePluginVersion(surface) : runningPluginVersion(process.env, resolveClientVersion());
|
|
38832
39679
|
const plan = buildPluginCachePlan(
|
|
38833
|
-
(0,
|
|
39680
|
+
(0, import_node_os21.homedir)(),
|
|
38834
39681
|
running,
|
|
38835
39682
|
pluginCacheFsDeps(configRoot, () => 0),
|
|
38836
39683
|
{ configRoot, includeStaging: surface !== "codex" }
|
|
@@ -38858,14 +39705,14 @@ function mmiDoctorDeps(opts = {}) {
|
|
|
38858
39705
|
const running = surface === "codex" ? installedActivePluginVersion(surface) : runningPluginVersion(process.env, resolveClientVersion());
|
|
38859
39706
|
const installed = installedActivePluginVersion(surface);
|
|
38860
39707
|
const plan = buildPluginCachePlan(
|
|
38861
|
-
(0,
|
|
39708
|
+
(0, import_node_os21.homedir)(),
|
|
38862
39709
|
running,
|
|
38863
39710
|
pluginCacheFsDeps(configRoot, () => 0),
|
|
38864
39711
|
{ configRoot, includeStaging: surface !== "codex", installedVersion: installed }
|
|
38865
39712
|
);
|
|
38866
39713
|
const result = applyPluginCachePlan(
|
|
38867
39714
|
plan,
|
|
38868
|
-
(p) => (0,
|
|
39715
|
+
(p) => (0, import_node_fs46.rmSync)(p, { recursive: true }),
|
|
38869
39716
|
stagingApplyFsGuard(configRoot)
|
|
38870
39717
|
);
|
|
38871
39718
|
return {
|
|
@@ -38903,7 +39750,7 @@ function mmiDoctorDeps(opts = {}) {
|
|
|
38903
39750
|
// adopted the generated routing index (MMG-Unlive: `docs/Archive/**` only, no index, no gate.yml) would
|
|
38904
39751
|
// get a permanent — demanding an artifact it never asked for.
|
|
38905
39752
|
docsIndexState: (root) => {
|
|
38906
|
-
if (!(0,
|
|
39753
|
+
if (!(0, import_node_fs46.existsSync)((0, import_node_path42.join)(root, DOCS_INDEX_PATH))) return void 0;
|
|
38907
39754
|
const real = createDocsIndexDeps(root);
|
|
38908
39755
|
let docs2;
|
|
38909
39756
|
const listDocs = () => docs2 ??= real.listDocs();
|
|
@@ -38912,7 +39759,7 @@ function mmiDoctorDeps(opts = {}) {
|
|
|
38912
39759
|
},
|
|
38913
39760
|
// #4168: working-tree heal — write if drifted, then re-check. Commit remains the operator's step.
|
|
38914
39761
|
healDocsIndex: (root) => {
|
|
38915
|
-
if (!(0,
|
|
39762
|
+
if (!(0, import_node_fs46.existsSync)((0, import_node_path42.join)(root, DOCS_INDEX_PATH))) return { drift: false, docCount: 0 };
|
|
38916
39763
|
const real = createDocsIndexDeps(root);
|
|
38917
39764
|
let docs2;
|
|
38918
39765
|
const listDocs = () => docs2 ??= real.listDocs();
|
|
@@ -38933,8 +39780,8 @@ function mmiDoctorDeps(opts = {}) {
|
|
|
38933
39780
|
});
|
|
38934
39781
|
const raced = await Promise.race([
|
|
38935
39782
|
work.then((r) => ({ ...r, timedOut: false })),
|
|
38936
|
-
new Promise((
|
|
38937
|
-
ceiling = setTimeout(() =>
|
|
39783
|
+
new Promise((resolve6) => {
|
|
39784
|
+
ceiling = setTimeout(() => resolve6({ timedOut: true, scanned: 0, findings: 0, fixed: 0, failed: 0 }), BOARD_DOCTOR_TIMEOUT_MS);
|
|
38938
39785
|
})
|
|
38939
39786
|
]);
|
|
38940
39787
|
if (raced.timedOut) return { scanned: 0, findings: 0, fixed: 0, failed: 0, timedOut: true };
|
|
@@ -38967,8 +39814,8 @@ function mmiDoctorDeps(opts = {}) {
|
|
|
38967
39814
|
incomplete: nb.incomplete,
|
|
38968
39815
|
timedOut: false
|
|
38969
39816
|
})),
|
|
38970
|
-
new Promise((
|
|
38971
|
-
ceiling = setTimeout(() =>
|
|
39817
|
+
new Promise((resolve6) => {
|
|
39818
|
+
ceiling = setTimeout(() => resolve6({ driftLines: [], incomplete: [], timedOut: true }), SCHEDULES_DRIFT_TIMEOUT_MS);
|
|
38972
39819
|
})
|
|
38973
39820
|
]);
|
|
38974
39821
|
if (!raced.timedOut && raced.incomplete.length === 0) writeSchedulesDriftCache(cachePath, raced.driftLines);
|
|
@@ -39004,7 +39851,7 @@ function mmiDoctorDeps(opts = {}) {
|
|
|
39004
39851
|
repoIndexCloudState: async (root) => {
|
|
39005
39852
|
let localV4 = { state: "absent" };
|
|
39006
39853
|
try {
|
|
39007
|
-
const parsed = JSON.parse((0,
|
|
39854
|
+
const parsed = JSON.parse((0, import_node_fs46.readFileSync)(repoIndexV4StorePath(root), "utf8"));
|
|
39008
39855
|
const state = parsed.status?.state;
|
|
39009
39856
|
if (parsed.schemaVersion === 4 && (state === "ready" || state === "degraded")) {
|
|
39010
39857
|
const chunks = parsed.manifest?.chunks?.length ?? 0;
|
|
@@ -39297,19 +40144,19 @@ program2.hook("preAction", async (_thisCommand, actionCommand) => {
|
|
|
39297
40144
|
});
|
|
39298
40145
|
var rules = program2.command("rules").description("org-managed .gitignore delivery");
|
|
39299
40146
|
rules.command("gitignore").option("--write", "upsert the managed block into .gitignore (default: check only, non-zero exit on drift)").option("--json", "machine-readable output").description("verify (or --write) this repo's org-managed .gitignore block matches the SSOT").action((opts) => {
|
|
39300
|
-
const path2 = (0,
|
|
39301
|
-
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;
|
|
39302
40149
|
const plan = planManagedGitignore(current);
|
|
39303
40150
|
const drift = [...plan.added.map((l) => `+${l}`), ...plan.removed.map((l) => `-${l}`)].join(", ") || "block normalize";
|
|
39304
40151
|
if (opts.json) {
|
|
39305
|
-
if (opts.write && plan.changed) (0,
|
|
40152
|
+
if (opts.write && plan.changed) (0, import_node_fs46.writeFileSync)(path2, plan.content, "utf8");
|
|
39306
40153
|
console.log(JSON.stringify(plan, null, 2));
|
|
39307
40154
|
if (!opts.write && plan.changed) process.exitCode = 1;
|
|
39308
40155
|
return;
|
|
39309
40156
|
}
|
|
39310
40157
|
if (opts.write) {
|
|
39311
40158
|
if (plan.changed) {
|
|
39312
|
-
(0,
|
|
40159
|
+
(0, import_node_fs46.writeFileSync)(path2, plan.content, "utf8");
|
|
39313
40160
|
console.log(`mmi-cli devops org rules gitignore: updated .gitignore (${drift})`);
|
|
39314
40161
|
} else {
|
|
39315
40162
|
console.log("mmi-cli devops org rules gitignore: up to date");
|
|
@@ -39495,7 +40342,7 @@ function scheduleRelatedDiscovery(o) {
|
|
|
39495
40342
|
try {
|
|
39496
40343
|
const args = ["issue", "discover-related", "--number", String(o.number), "--title", o.title, "--body", o.body, "--fail-soft"];
|
|
39497
40344
|
if (o.repo) args.push("--repo", o.repo);
|
|
39498
|
-
spawnDetachedSelf(args, { spawn:
|
|
40345
|
+
spawnDetachedSelf(args, { spawn: import_node_child_process21.spawn, execPath: process.execPath, scriptPath: process.argv[1] }, { cwd: process.cwd() });
|
|
39499
40346
|
} catch {
|
|
39500
40347
|
}
|
|
39501
40348
|
}
|
|
@@ -39895,6 +40742,20 @@ tests.command("policy").description("enforce this repo's test-policy.json agains
|
|
|
39895
40742
|
await failGraceful(e.message);
|
|
39896
40743
|
}
|
|
39897
40744
|
});
|
|
40745
|
+
var distCmd = program2.command("dist").description("this repo's committed dist/BOM drift receipt \u2014 whether cli/dist, updater/dist and distribution-bom.json still match a fresh build of source");
|
|
40746
|
+
distCmd.command("status").description("rebuild every committed dist artifact to a temp dir and report committed vs rebuilt-expected sha256 plus the BOM's recorded dist identities \u2014 a visible, non-blocking receipt. Development checkouts may lag source until the release fold; drift NEVER fails the run, and nothing is refreshed for you (#5576)").option("--json", "machine-readable receipt: { ok, staleCount, artifacts[], bom, summary } (full hashes; drift still exits 0)").action(async (o) => {
|
|
40747
|
+
try {
|
|
40748
|
+
const root = await repoRoot();
|
|
40749
|
+
const receipt = runDistStatus(root);
|
|
40750
|
+
if (o.json) {
|
|
40751
|
+
consoleIo.log(JSON.stringify({ ok: true, staleCount: receipt.staleCount, artifacts: receipt.artifacts, bom: receipt.bom, summary: receipt.summary }, null, 2));
|
|
40752
|
+
return;
|
|
40753
|
+
}
|
|
40754
|
+
for (const line of renderDistDriftReceipt(receipt)) console.log(line);
|
|
40755
|
+
} catch (e) {
|
|
40756
|
+
await failGraceful(`dist status: ${e.message}`);
|
|
40757
|
+
}
|
|
40758
|
+
});
|
|
39898
40759
|
async function reportWrite(label, res) {
|
|
39899
40760
|
if (res.ok) {
|
|
39900
40761
|
console.log(JSON.stringify(res.body));
|
|
@@ -40167,7 +41028,7 @@ project.command("set [owner/repo]").description("upsert project META (idempotent
|
|
|
40167
41028
|
if (dupe) return fail(`org project set: KEY "${dupe}" was passed to both --var and --set; --set is an alias of --var, so pass each KEY once`);
|
|
40168
41029
|
if (o.secretsFile) {
|
|
40169
41030
|
try {
|
|
40170
|
-
vars.push(`secrets=${(0,
|
|
41031
|
+
vars.push(`secrets=${(0, import_node_fs46.readFileSync)(o.secretsFile, "utf8")}`);
|
|
40171
41032
|
} catch (e) {
|
|
40172
41033
|
return fail(`org project set: cannot read --secrets-file ${o.secretsFile}: ${e.message}`);
|
|
40173
41034
|
}
|
|
@@ -40517,7 +41378,7 @@ function resolveCreateSurface(opts) {
|
|
|
40517
41378
|
function surfaceWaived() {
|
|
40518
41379
|
return rawFlag("--no-surface");
|
|
40519
41380
|
}
|
|
40520
|
-
var issue = program2.command("issue").description("issues \u2014 create and view with structured JSON (view; show is an alias for board-verb callers)");
|
|
41381
|
+
var issue = program2.command("issue").description("issues \u2014 create and view with structured JSON (view; show is an alias for board-verb callers). Claims are board mutations: use `oracle board claim`, never `oracle issue claim`");
|
|
40521
41382
|
withExamples(mutating(
|
|
40522
41383
|
issue.command("create").description("create an issue (type \u2014 label) and print {number,url,label} JSON").addOption(new Option("--type <type>", "bug | feature | task (sets the matching label; required unless --batch)").choices([...ISSUE_TYPES])).option("--title <title>", "issue title").option("--title-file <path|->", "read the issue title from a UTF-8 file, or from stdin with -").option("--body <body>", "issue body (markdown)").option("--body-file <path|->", "read issue body from a UTF-8 file. `-` (stdin) needs a heredoc, which the agent inline-body guard denies (#1473/#2125) \u2014 prefer a real path; a title with backticks needs --title-file for the same reason (#3381)").option("--priority <priority>", "urgent | high | medium | low (defaults to medium; sets the board Priority field only \u2014 never a priority:* label, #416)").option("--repo <owner/repo>", "target repo (defaults to the current repo)").option("--label <label...>", "extra label(s) to attach (repeatable; auto-created if missing)").option("--surface <surface>", "issue surface, with or without the surface: prefix (#3789). Required when the target repo runs the one-surface-label board rule; any value satisfies it, so this is not a closed enum").option("--no-surface", "file without a surface label on a repo that requires one \u2014 for a genuinely exempt filing (e.g. a coop proof issue that spans every surface)").option("--parent <ref>", "file as a native sub-issue of this parent (#123, owner/repo#123, or URL)").option("--no-related", "skip the auto related-issues comment"),
|
|
40523
41384
|
// --dry-run/--validate-only plan: resolve the same title source and validate the same type, priority,
|
|
@@ -40821,7 +41682,7 @@ ${list}`);
|
|
|
40821
41682
|
}
|
|
40822
41683
|
console.log(JSON.stringify({ number: parsed.number, repo, item: result.item.text, checked, changed: true }));
|
|
40823
41684
|
});
|
|
40824
|
-
program2.command("report").description("file a friction report on the Hub board (Hub session auth, dedups open reports) and print {number,url} JSON").option("--title <title>", "one-line friction summary").option("--title-file <path|->", "read the friction summary from a UTF-8 file, or from stdin with -").option("--body <body>", "report body (markdown)").option("--body-file <path|->", "read report body from a UTF-8 file, or from stdin with -").addOption(new Option("--type <type>", "bug | feature | task (sets the matching label)").default("task").choices([...ISSUE_TYPES])).option("--priority <priority>", "urgent | high | medium | low (defaults to medium; sets the board Priority field only, #416)").option("--repo <owner/repo>", 'attribute the report to a different source repo than the current checkout for the "Filed via..." footer (rare \u2014 usually auto-detected; every report always lands on the org Hub, never an alternate target, #263)').option("--force", "file a new issue even when an open report looks like a duplicate").option("--json", "machine-readable output (already the default \u2014 report always prints JSON; #682)").action(async (o) => {
|
|
41685
|
+
program2.command("report").description("file a friction report on the Hub board (Hub session auth, dedups open reports) and print {number,url} JSON \u2014 learning-tagged; file and forget (cloud agents own the fix; do not claim/poll it)").option("--title <title>", "one-line friction summary").option("--title-file <path|->", "read the friction summary from a UTF-8 file, or from stdin with -").option("--body <body>", "report body (markdown)").option("--body-file <path|->", "read report body from a UTF-8 file, or from stdin with -").addOption(new Option("--type <type>", "bug | feature | task (sets the matching label)").default("task").choices([...ISSUE_TYPES])).option("--priority <priority>", "urgent | high | medium | low (defaults to medium; sets the board Priority field only, #416)").option("--repo <owner/repo>", 'attribute the report to a different source repo than the current checkout for the "Filed via..." footer (rare \u2014 usually auto-detected; every report always lands on the org Hub, never an alternate target, #263)').option("--force", "file a new issue even when an open report looks like a duplicate").option("--json", "machine-readable output (already the default \u2014 report always prints JSON; #682)").action(async (o) => {
|
|
40825
41686
|
let body;
|
|
40826
41687
|
let priority;
|
|
40827
41688
|
let title;
|
|
@@ -40937,6 +41798,7 @@ program2.command("skill-lesson").description("file a skill-lesson on the Hub boa
|
|
|
40937
41798
|
}
|
|
40938
41799
|
const created = requireGhCreateOk(await ghCreate(args), "skill-lesson");
|
|
40939
41800
|
const { projectItemId, onBoard } = await attachToProject(created.number, targetRepo3, priority);
|
|
41801
|
+
console.log(JSON.stringify({ ...created, projectItemId, onBoard }));
|
|
40940
41802
|
});
|
|
40941
41803
|
var pr = program2.command("pr").description("pull requests \u2014 reliable create with structured output");
|
|
40942
41804
|
withExamples(pr.command("create").description("create a PR and print {number,url} JSON").option("--title <title>", "PR title").option("--title-file <path|->", "read the PR title from a UTF-8 file, or from stdin with -").option("--body <body>", "PR body (markdown)").option("--body-file <path|->", "read PR body from a UTF-8 file, or from stdin with -").option("--base <branch>", "base branch (defaults to the repo default)").option("--head <branch>", "head branch (defaults to the current branch)").option("--repo <owner/repo>", "target repo (defaults to the current repo)").option("--draft", "open the PR in draft state (#2667)").option("--json", "machine-readable output (default; accepted for parity)").action(async (o) => {
|
|
@@ -40991,11 +41853,11 @@ pr.command("view <number>").description("read a PR as structured JSON (merged st
|
|
|
40991
41853
|
}
|
|
40992
41854
|
});
|
|
40993
41855
|
async function listCiWorkflowPaths(cwd = process.cwd()) {
|
|
40994
|
-
const wfDir = (0,
|
|
40995
|
-
if (!(0,
|
|
40996
|
-
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) => {
|
|
40997
41859
|
try {
|
|
40998
|
-
return workflowReportsPrChecks((0,
|
|
41860
|
+
return workflowReportsPrChecks((0, import_node_fs46.readFileSync)((0, import_node_path42.join)(wfDir, name), "utf8"));
|
|
40999
41861
|
} catch {
|
|
41000
41862
|
return true;
|
|
41001
41863
|
}
|
|
@@ -41047,16 +41909,16 @@ function ciAuditDeps() {
|
|
|
41047
41909
|
// gate re-seed step is skipped gracefully rather than failing mid-run.
|
|
41048
41910
|
readSeedFile: (path2) => {
|
|
41049
41911
|
if (!root) return null;
|
|
41050
|
-
const fullPath = (0,
|
|
41051
|
-
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;
|
|
41052
41914
|
}
|
|
41053
41915
|
};
|
|
41054
41916
|
}
|
|
41055
41917
|
function hubRoot() {
|
|
41056
|
-
const fromPkg = (0,
|
|
41918
|
+
const fromPkg = (0, import_node_path42.join)(__dirname, "..", "..");
|
|
41057
41919
|
const marker = "skills/bootstrap/seeds/manifest.json";
|
|
41058
|
-
if ((0,
|
|
41059
|
-
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();
|
|
41060
41922
|
return null;
|
|
41061
41923
|
}
|
|
41062
41924
|
async function waitLoopCorePool(label) {
|
|
@@ -41105,7 +41967,12 @@ pr.command("checks-wait <number>").description(`bounded wait for ALL checks on t
|
|
|
41105
41967
|
timeoutMs = Math.round(minutes * 6e4);
|
|
41106
41968
|
}
|
|
41107
41969
|
const repo = await requireRepo(o.repo);
|
|
41108
|
-
const
|
|
41970
|
+
const budgetMs = timeoutMs ?? PR_CHECKS_TIMEOUT_MS;
|
|
41971
|
+
const waitStarted = Date.now();
|
|
41972
|
+
const snapshotRead = await readRestPrSnapshotWithRetry(number, repo, void 0, {
|
|
41973
|
+
retryTransientUntilMs: waitStarted + budgetMs,
|
|
41974
|
+
onTransientRetry: (error, attempt) => console.warn(`pr checks-wait: transient GitHub read (${error}) \u2014 retrying PR snapshot (attempt ${attempt}) within the wait budget`)
|
|
41975
|
+
});
|
|
41109
41976
|
if (snapshotRead.state === "failed") {
|
|
41110
41977
|
return fail(`pr checks-wait: cannot resolve PR #${number}'s base branch \u2014 ${snapshotRead.error}. Refusing to wait against an assumed base; retry when the API answers.`);
|
|
41111
41978
|
}
|
|
@@ -41126,9 +41993,9 @@ pr.command("checks-wait <number>").description(`bounded wait for ALL checks on t
|
|
|
41126
41993
|
// #5400: after grace, name "GitHub delivered zero runs" instead of burning the full budget as pending.
|
|
41127
41994
|
pollHeadWorkflowRunCount: () => waitLoopHeadWorkflowRunCount("pr checks-wait", number, repo),
|
|
41128
41995
|
baseBranch,
|
|
41129
|
-
sleep: (ms) => new Promise((
|
|
41996
|
+
sleep: (ms) => new Promise((resolve6) => setTimeout(resolve6, ms)),
|
|
41130
41997
|
log: (message) => console.warn(message),
|
|
41131
|
-
timeoutMs,
|
|
41998
|
+
timeoutMs: Math.max(1, budgetMs - (Date.now() - waitStarted)),
|
|
41132
41999
|
// Liveness on stderr, one line per poll. A silent bounded wait is indistinguishable from a hang, and
|
|
41133
42000
|
// an agent harness kills it on its own (shorter) deadline before the verdict ever prints (#2940).
|
|
41134
42001
|
progress: ({ state, elapsedMs, remainingMs }) => console.warn(`pr checks-wait: ${state} \u2014 ${Math.round(elapsedMs / 1e3)}s elapsed, ${Math.round(remainingMs / 6e4)}m left`)
|
|
@@ -41222,7 +42089,7 @@ pr.command("land <number>").description("agent merge path (#1440): train probe \
|
|
|
41222
42089
|
// #5400: same zero-runs delivery probe as checks-wait — do not burn the land budget on silence.
|
|
41223
42090
|
pollHeadWorkflowRunCount: () => waitLoopHeadWorkflowRunCount("pr land", prNumber, repo),
|
|
41224
42091
|
baseBranch: "development",
|
|
41225
|
-
sleep: (ms) => new Promise((
|
|
42092
|
+
sleep: (ms) => new Promise((resolve6) => setTimeout(resolve6, ms)),
|
|
41226
42093
|
log: (message) => console.warn(message),
|
|
41227
42094
|
// `pr land` inherits the same (raised) checks budget, so it needs the same liveness — otherwise the
|
|
41228
42095
|
// 30m wait is SILENT and reads exactly like the hang #2940 was filed about, only three times longer.
|
|
@@ -41265,7 +42132,7 @@ pr.command("land <number>").description("agent merge path (#1440): train probe \
|
|
|
41265
42132
|
} else {
|
|
41266
42133
|
lastFailure = void 0;
|
|
41267
42134
|
}
|
|
41268
|
-
await new Promise((
|
|
42135
|
+
await new Promise((resolve6) => setTimeout(resolve6, PR_LAND_POLL_MS));
|
|
41269
42136
|
}
|
|
41270
42137
|
if (lastFailure) {
|
|
41271
42138
|
throw new Error(
|
|
@@ -41349,7 +42216,12 @@ jsonParity(pr.command("merge <number>").description("merge a PR (squash by defau
|
|
|
41349
42216
|
const ciPolicy = await resolveMergeCiPolicyForCheckout(o.repo, ciHeadRef);
|
|
41350
42217
|
if (o.wait) {
|
|
41351
42218
|
const repo = await requireRepo(o.repo);
|
|
41352
|
-
const
|
|
42219
|
+
const budgetMs = PR_CHECKS_TIMEOUT_MS;
|
|
42220
|
+
const waitStarted = Date.now();
|
|
42221
|
+
const snapshotRead = await readRestPrSnapshotWithRetry(number, repo, void 0, {
|
|
42222
|
+
retryTransientUntilMs: waitStarted + budgetMs,
|
|
42223
|
+
onTransientRetry: (error, attempt) => console.warn(`pr merge: transient GitHub read (${error}) \u2014 retrying PR snapshot (attempt ${attempt}) within the --wait budget`)
|
|
42224
|
+
});
|
|
41353
42225
|
if (snapshotRead.state === "failed") {
|
|
41354
42226
|
console.error(`pr merge: cannot resolve PR #${number}'s base branch \u2014 ${snapshotRead.error}. Refusing to wait against an assumed base; retry when the API answers.`);
|
|
41355
42227
|
process.exitCode = 1;
|
|
@@ -41365,8 +42237,9 @@ jsonParity(pr.command("merge <number>").description("merge a PR (squash by defau
|
|
|
41365
42237
|
diagnoseFailure: () => waitLoopDiagnosis("pr merge --wait", number, repo),
|
|
41366
42238
|
pollHeadWorkflowRunCount: () => waitLoopHeadWorkflowRunCount("pr merge --wait", number, repo),
|
|
41367
42239
|
baseBranch,
|
|
41368
|
-
sleep: (ms) => new Promise((
|
|
42240
|
+
sleep: (ms) => new Promise((resolve6) => setTimeout(resolve6, ms)),
|
|
41369
42241
|
log: (message) => console.warn(message),
|
|
42242
|
+
timeoutMs: Math.max(1, budgetMs - (Date.now() - waitStarted)),
|
|
41370
42243
|
progress: ({ state, elapsedMs, remainingMs }) => console.warn(`pr merge: --wait checks \u2014 ${state}, ${Math.round(elapsedMs / 1e3)}s elapsed, ${Math.round(remainingMs / 6e4)}m left`)
|
|
41371
42244
|
});
|
|
41372
42245
|
if (wait.status !== "success" && wait.status !== "skipped") {
|
|
@@ -41400,7 +42273,7 @@ jsonParity(pr.command("merge <number>").description("merge a PR (squash by defau
|
|
|
41400
42273
|
}
|
|
41401
42274
|
if (!repoForPostCleanup) throw e;
|
|
41402
42275
|
console.warn(`pr merge: gh GraphQL rate-limited \u2014 merging PR #${number} via REST PUT instead (#4588).`);
|
|
41403
|
-
const commitMessage = bodyFile ? (0,
|
|
42276
|
+
const commitMessage = bodyFile ? (0, import_node_fs46.readFileSync)(bodyFile, "utf8") : void 0;
|
|
41404
42277
|
await defaultGitHubClient().rest("PUT", `repos/${repoForPostCleanup}/pulls/${number}/merge`, {
|
|
41405
42278
|
body: { merge_method: method.slice(2), ...commitMessage ? { commit_message: commitMessage } : {} },
|
|
41406
42279
|
timeoutMs: GH_MUTATION_TIMEOUT_MS
|
|
@@ -42096,12 +42969,12 @@ access.command("audit").description("audit collaborator roles + train-branch pus
|
|
|
42096
42969
|
targets = resolution.targets;
|
|
42097
42970
|
}
|
|
42098
42971
|
const derivedMatrix = registryProjects ? accessMatrixFromProjects(registryProjects) : {};
|
|
42099
|
-
const fileMatrix = (0,
|
|
42972
|
+
const fileMatrix = (0, import_node_fs46.existsSync)("access-matrix.json") ? loadAccessMatrix((0, import_node_fs46.readFileSync)("access-matrix.json", "utf8")) : {};
|
|
42100
42973
|
const matrix = mergeAccessMatrix(fileMatrix, derivedMatrix);
|
|
42101
42974
|
const derivedContracts = registryProjects ? dataAccessContractsFromProjects(registryProjects) : { consumers: {} };
|
|
42102
|
-
const fileContracts = (0,
|
|
42975
|
+
const fileContracts = (0, import_node_fs46.existsSync)("data-access-contracts.json") ? loadDataAccessContracts((0, import_node_fs46.readFileSync)("data-access-contracts.json", "utf8")) : { consumers: {} };
|
|
42103
42976
|
const dataAccess = mergeDataAccessContracts(fileContracts, derivedContracts);
|
|
42104
|
-
const sanctioned = (0,
|
|
42977
|
+
const sanctioned = (0, import_node_fs46.existsSync)("access-matrix.json") ? loadSanctionedAdmins((0, import_node_fs46.readFileSync)("access-matrix.json", "utf8")) : {};
|
|
42105
42978
|
const report = await auditOrgAccess(targets, deps, matrix, dataAccess, sanctioned);
|
|
42106
42979
|
console.log(o.json ? JSON.stringify(report, null, 2) : renderAccessReport(report));
|
|
42107
42980
|
if (!report.ok) process.exitCode = 1;
|
|
@@ -42133,16 +43006,16 @@ function directoryBytes(path2) {
|
|
|
42133
43006
|
let total = 0;
|
|
42134
43007
|
let entries;
|
|
42135
43008
|
try {
|
|
42136
|
-
entries = (0,
|
|
43009
|
+
entries = (0, import_node_fs46.readdirSync)(path2, { withFileTypes: true });
|
|
42137
43010
|
} catch {
|
|
42138
43011
|
return 0;
|
|
42139
43012
|
}
|
|
42140
43013
|
for (const entry of entries) {
|
|
42141
|
-
const child2 = (0,
|
|
43014
|
+
const child2 = (0, import_node_path42.join)(path2, entry.name);
|
|
42142
43015
|
if (entry.isDirectory()) total += directoryBytes(child2);
|
|
42143
43016
|
else {
|
|
42144
43017
|
try {
|
|
42145
|
-
total += (0,
|
|
43018
|
+
total += (0, import_node_fs46.statSync)(child2).size;
|
|
42146
43019
|
} catch {
|
|
42147
43020
|
}
|
|
42148
43021
|
}
|
|
@@ -42150,25 +43023,25 @@ function directoryBytes(path2) {
|
|
|
42150
43023
|
return total;
|
|
42151
43024
|
}
|
|
42152
43025
|
function listDirEntries(dir) {
|
|
42153
|
-
return (0,
|
|
43026
|
+
return (0, import_node_fs46.readdirSync)(dir, { withFileTypes: true }).map((d) => ({ name: d.name, isDirectory: d.isDirectory() }));
|
|
42154
43027
|
}
|
|
42155
43028
|
function readInstalledPluginRefs(configRoot) {
|
|
42156
43029
|
const p = installedPluginsPathForConfig(configRoot);
|
|
42157
|
-
if (!(0,
|
|
43030
|
+
if (!(0, import_node_fs46.existsSync)(p)) return [];
|
|
42158
43031
|
try {
|
|
42159
|
-
return installedPluginPaths((0,
|
|
43032
|
+
return installedPluginPaths((0, import_node_fs46.readFileSync)(p, "utf8"));
|
|
42160
43033
|
} catch {
|
|
42161
43034
|
return null;
|
|
42162
43035
|
}
|
|
42163
43036
|
}
|
|
42164
43037
|
function pluginCacheFsDeps(configRoot, dirBytes) {
|
|
42165
43038
|
return {
|
|
42166
|
-
exists: (p) => (0,
|
|
42167
|
-
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),
|
|
42168
43041
|
dirBytes,
|
|
42169
|
-
listStagingDirs: (root) => (0,
|
|
43042
|
+
listStagingDirs: (root) => (0, import_node_fs46.readdirSync)(root, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => {
|
|
42170
43043
|
try {
|
|
42171
|
-
return { name: d.name, mtimeMs: newestMtimeMs((0,
|
|
43044
|
+
return { name: d.name, mtimeMs: newestMtimeMs((0, import_node_path42.join)(root, d.name), listDirEntries, (p) => (0, import_node_fs46.statSync)(p).mtimeMs) };
|
|
42172
43045
|
} catch {
|
|
42173
43046
|
return { name: d.name, mtimeMs: Date.now() };
|
|
42174
43047
|
}
|
|
@@ -42182,10 +43055,10 @@ function stagingApplyFsGuard(configRoot) {
|
|
|
42182
43055
|
return {
|
|
42183
43056
|
referencedPaths: () => readInstalledPluginRefs(configRoot),
|
|
42184
43057
|
mtimeMs: (name) => {
|
|
42185
|
-
const p = (0,
|
|
42186
|
-
if (!(0,
|
|
43058
|
+
const p = (0, import_node_path42.join)(stagingRoot, name);
|
|
43059
|
+
if (!(0, import_node_fs46.existsSync)(p)) return null;
|
|
42187
43060
|
try {
|
|
42188
|
-
return newestMtimeMs(p, listDirEntries, (q) => (0,
|
|
43061
|
+
return newestMtimeMs(p, listDirEntries, (q) => (0, import_node_fs46.statSync)(q).mtimeMs);
|
|
42189
43062
|
} catch {
|
|
42190
43063
|
return null;
|
|
42191
43064
|
}
|
|
@@ -42205,13 +43078,13 @@ program2.command("plugin-prune").description(`prune stale cached MMI plugin vers
|
|
|
42205
43078
|
return;
|
|
42206
43079
|
}
|
|
42207
43080
|
const plan = buildPluginCachePlan(
|
|
42208
|
-
(0,
|
|
43081
|
+
(0, import_node_os21.homedir)(),
|
|
42209
43082
|
running,
|
|
42210
43083
|
pluginCacheFsDeps(configRoot, directoryBytes),
|
|
42211
43084
|
{ withBytes: true, configRoot, includeStaging: surface !== "codex" }
|
|
42212
43085
|
);
|
|
42213
43086
|
const anythingToDelete = plan.prune.length > 0 || plan.staging.length > 0;
|
|
42214
|
-
const result = o.apply && anythingToDelete ? applyPluginCachePlan(plan, (p) => (0,
|
|
43087
|
+
const result = o.apply && anythingToDelete ? applyPluginCachePlan(plan, (p) => (0, import_node_fs46.rmSync)(p, { recursive: true, force: true }), stagingApplyFsGuard(configRoot)) : void 0;
|
|
42215
43088
|
const warnings = plan.prune.length > 0 ? [CONCURRENT_SESSION_WARNING] : [];
|
|
42216
43089
|
if (o.json) console.log(JSON.stringify({ ...plan, warnings, applied: result ?? null }));
|
|
42217
43090
|
else console.log(renderPluginCachePlan(plan, result));
|