@mutmutco/cli 3.81.0 → 3.83.0
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 +459 -594
- package/package.json +1 -1
package/dist/main.cjs
CHANGED
|
@@ -3416,7 +3416,7 @@ var program = new Command();
|
|
|
3416
3416
|
|
|
3417
3417
|
// src/index.ts
|
|
3418
3418
|
var import_promises10 = require("node:fs/promises");
|
|
3419
|
-
var
|
|
3419
|
+
var import_node_fs36 = require("node:fs");
|
|
3420
3420
|
var import_node_child_process16 = require("node:child_process");
|
|
3421
3421
|
|
|
3422
3422
|
// src/cli-shared.ts
|
|
@@ -5295,9 +5295,6 @@ function explicitRepoWorktreesRoot(root, repoRoot2, rootDirs) {
|
|
|
5295
5295
|
const repoDir = rootDirs.find((name) => name.toLowerCase() === repoName.toLowerCase());
|
|
5296
5296
|
return repoDir ? (0, import_node_path7.join)(root, repoDir) : root;
|
|
5297
5297
|
}
|
|
5298
|
-
function strayWorktreeRootPaths(container, names, authoritativeRoot) {
|
|
5299
|
-
return names.filter((name) => /worktrees/i.test(name)).map((name) => (0, import_node_path7.join)(container, name)).filter((path2) => !samePath(path2, authoritativeRoot));
|
|
5300
|
-
}
|
|
5301
5298
|
function classifySiblingWorktreeDir(entry) {
|
|
5302
5299
|
if (!entry.ownedByCurrentRepo) {
|
|
5303
5300
|
return { skip: { path: entry.path, reason: "unknown-git-state", detail: entry.detail ?? "not proven current-repo owned" } };
|
|
@@ -6261,7 +6258,7 @@ function readSettingsAutoUpdate(raw, name) {
|
|
|
6261
6258
|
var AUTO_UPDATE_TOGGLE_STEPS = "`/plugin` \u2192 Marketplaces tab \u2192 select the marketplace \u2192 Enable auto-update";
|
|
6262
6259
|
var CATALOG_CONTENT_REF = "main";
|
|
6263
6260
|
var CATALOG_REF_PIN_STEPS = `add \`"ref": "${CATALOG_CONTENT_REF}"\` to this marketplace's \`source\` in ~/.claude/plugins/known_marketplaces.json, then restart Claude`;
|
|
6264
|
-
var PIN_HEAL_COMMAND = "run `mmi-cli doctor
|
|
6261
|
+
var PIN_HEAL_COMMAND = "run `mmi-cli doctor`";
|
|
6265
6262
|
var ORG_MARKETPLACE_PINS = { autoUpdate: true, ref: CATALOG_CONTENT_REF };
|
|
6266
6263
|
function resolveCatalogRef(probe) {
|
|
6267
6264
|
const { name, registered, ref, autoUpdate } = probe;
|
|
@@ -6352,32 +6349,6 @@ var DEFAULT_SURFACE = "claude";
|
|
|
6352
6349
|
function activityLogPath(cwd) {
|
|
6353
6350
|
return repoRuntimeStatePath(cwd, "hooks", "activity.jsonl");
|
|
6354
6351
|
}
|
|
6355
|
-
var REDACTOR_WINDOW_MS = 48 * 60 * 60 * 1e3;
|
|
6356
|
-
var REDACTOR_SCAN_BYTES = 512 * 1024;
|
|
6357
|
-
function redactorLivenessProbe(cwd, now = /* @__PURE__ */ new Date()) {
|
|
6358
|
-
try {
|
|
6359
|
-
const raw = (0, import_node_fs10.readFileSync)(activityLogPath(cwd), "utf8");
|
|
6360
|
-
const tail = raw.length > REDACTOR_SCAN_BYTES ? raw.slice(raw.length - REDACTOR_SCAN_BYTES) : raw;
|
|
6361
|
-
const floor = now.getTime() - REDACTOR_WINDOW_MS;
|
|
6362
|
-
let failed = 0;
|
|
6363
|
-
let lastTs;
|
|
6364
|
-
for (const line of tail.split("\n")) {
|
|
6365
|
-
if (!line.includes('"secret-redact"') || !line.includes('"failed"')) continue;
|
|
6366
|
-
try {
|
|
6367
|
-
const row = JSON.parse(line);
|
|
6368
|
-
if (row.script !== "secret-redact" || row.outcome !== "failed") continue;
|
|
6369
|
-
const ts = row.ts ? Date.parse(row.ts) : Number.NaN;
|
|
6370
|
-
if (Number.isNaN(ts) || ts < floor) continue;
|
|
6371
|
-
failed += 1;
|
|
6372
|
-
if (!lastTs || row.ts > lastTs) lastTs = row.ts;
|
|
6373
|
-
} catch {
|
|
6374
|
-
}
|
|
6375
|
-
}
|
|
6376
|
-
return { failed, ...lastTs ? { lastTs } : {} };
|
|
6377
|
-
} catch {
|
|
6378
|
-
return void 0;
|
|
6379
|
-
}
|
|
6380
|
-
}
|
|
6381
6352
|
function appendHookActivity(cwd, entry) {
|
|
6382
6353
|
try {
|
|
6383
6354
|
const path2 = activityLogPath(cwd);
|
|
@@ -6460,7 +6431,14 @@ async function provisionWorktree(worktreeRoot, deps) {
|
|
|
6460
6431
|
});
|
|
6461
6432
|
const allDirs = scanInstallDirs(worktreeRoot, fs2);
|
|
6462
6433
|
const targets = npmInstallTargets(allDirs);
|
|
6463
|
-
|
|
6434
|
+
if (deps.validateInstall) {
|
|
6435
|
+
for (const dir of allDirs.filter((d) => d.hasPackageJson && d.hasLockfile && d.hasNodeModules)) {
|
|
6436
|
+
const cwd = dir.dir ? (0, import_node_path10.join)(worktreeRoot, dir.dir) : worktreeRoot;
|
|
6437
|
+
if (!await deps.validateInstall(cwd)) targets.push({ dir: dir.dir, command: "npm ci" });
|
|
6438
|
+
}
|
|
6439
|
+
}
|
|
6440
|
+
const targetDirs = new Set(targets.map((target) => target.dir));
|
|
6441
|
+
const skippedInstall = allDirs.filter((d) => d.hasPackageJson && d.hasNodeModules && !targetDirs.has(d.dir)).map((d) => d.dir);
|
|
6464
6442
|
const installed = [];
|
|
6465
6443
|
for (const target of targets) {
|
|
6466
6444
|
const cwd = target.dir ? (0, import_node_path10.join)(worktreeRoot, target.dir) : worktreeRoot;
|
|
@@ -6624,7 +6602,7 @@ function commandLadderHint() {
|
|
|
6624
6602
|
}
|
|
6625
6603
|
|
|
6626
6604
|
// src/index.ts
|
|
6627
|
-
var
|
|
6605
|
+
var import_node_path35 = require("node:path");
|
|
6628
6606
|
|
|
6629
6607
|
// src/merge-ci-policy.ts
|
|
6630
6608
|
function resolveMergeCiPolicy(input) {
|
|
@@ -8056,7 +8034,7 @@ async function foldReleaseVersion(deps, model, tag, foldPaths) {
|
|
|
8056
8034
|
if (foldPaths.length === 0) return "no version manifest to fold \u2014 the tag is the version";
|
|
8057
8035
|
const version = tag.replace(/^v/, "");
|
|
8058
8036
|
if (model === "hub-serverless") {
|
|
8059
|
-
await deps.run("node", ["scripts/release-distribution.mjs", "prepare", version]);
|
|
8037
|
+
await deps.run("node", ["scripts/release-distribution.mjs", "prepare", version, "--source-commit", "HEAD"]);
|
|
8060
8038
|
} else {
|
|
8061
8039
|
await installAppFoldDeps(deps);
|
|
8062
8040
|
await deps.run("npm", ["version", version, "--no-git-tag-version", "--allow-same-version"]);
|
|
@@ -11712,10 +11690,13 @@ async function executeWaveLand(plan, deps, opts = { preserveWorktree: true }) {
|
|
|
11712
11690
|
}
|
|
11713
11691
|
|
|
11714
11692
|
// src/index.ts
|
|
11715
|
-
var
|
|
11693
|
+
var import_node_os14 = require("node:os");
|
|
11716
11694
|
|
|
11717
11695
|
// src/board.ts
|
|
11718
11696
|
var import_node_child_process8 = require("node:child_process");
|
|
11697
|
+
var import_node_fs20 = require("node:fs");
|
|
11698
|
+
var import_node_os7 = require("node:os");
|
|
11699
|
+
var import_node_path18 = require("node:path");
|
|
11719
11700
|
var import_node_util6 = require("node:util");
|
|
11720
11701
|
|
|
11721
11702
|
// src/board-priority.ts
|
|
@@ -13943,62 +13924,6 @@ async function branchesMergedIntoBase(remote) {
|
|
|
13943
13924
|
}
|
|
13944
13925
|
return [...out];
|
|
13945
13926
|
}
|
|
13946
|
-
var STRAY_ROOT_WALK_BUDGET_MS = 4e3;
|
|
13947
|
-
function measureWorktreeRoot(root, deadline) {
|
|
13948
|
-
let dirs = 0;
|
|
13949
|
-
let bytes = 0;
|
|
13950
|
-
let partial = false;
|
|
13951
|
-
const stack = [root];
|
|
13952
|
-
let depth0 = true;
|
|
13953
|
-
while (stack.length) {
|
|
13954
|
-
if (Date.now() > deadline) {
|
|
13955
|
-
partial = true;
|
|
13956
|
-
break;
|
|
13957
|
-
}
|
|
13958
|
-
const current = stack.pop();
|
|
13959
|
-
let entries;
|
|
13960
|
-
try {
|
|
13961
|
-
entries = (0, import_node_fs17.readdirSync)(current, { withFileTypes: true });
|
|
13962
|
-
} catch {
|
|
13963
|
-
continue;
|
|
13964
|
-
}
|
|
13965
|
-
for (const ent of entries) {
|
|
13966
|
-
const child2 = (0, import_node_path15.join)(current, ent.name);
|
|
13967
|
-
if (ent.isDirectory()) {
|
|
13968
|
-
if (depth0) dirs++;
|
|
13969
|
-
let isLink = false;
|
|
13970
|
-
try {
|
|
13971
|
-
(0, import_node_fs17.readlinkSync)(child2);
|
|
13972
|
-
isLink = true;
|
|
13973
|
-
} catch {
|
|
13974
|
-
}
|
|
13975
|
-
if (!isLink) stack.push(child2);
|
|
13976
|
-
} else if (ent.isFile()) {
|
|
13977
|
-
try {
|
|
13978
|
-
bytes += (0, import_node_fs17.statSync)(child2).size;
|
|
13979
|
-
} catch {
|
|
13980
|
-
}
|
|
13981
|
-
}
|
|
13982
|
-
}
|
|
13983
|
-
depth0 = false;
|
|
13984
|
-
}
|
|
13985
|
-
return partial ? { dirs, bytes, partial } : { dirs, bytes };
|
|
13986
|
-
}
|
|
13987
|
-
function worktreeRootsProbe(repoRoot2) {
|
|
13988
|
-
const authoritative = siblingMmiWorktreesRoot(repoRoot2);
|
|
13989
|
-
const container = (0, import_node_path15.dirname)(authoritative);
|
|
13990
|
-
let names;
|
|
13991
|
-
try {
|
|
13992
|
-
names = (0, import_node_fs17.readdirSync)(container, { withFileTypes: true }).filter((ent) => ent.isDirectory()).map((ent) => ent.name);
|
|
13993
|
-
} catch {
|
|
13994
|
-
return { authoritative, stray: [] };
|
|
13995
|
-
}
|
|
13996
|
-
const deadline = Date.now() + STRAY_ROOT_WALK_BUDGET_MS;
|
|
13997
|
-
return {
|
|
13998
|
-
authoritative,
|
|
13999
|
-
stray: strayWorktreeRootPaths(container, names, authoritative).map((path2) => ({ path: path2, ...measureWorktreeRoot(path2, deadline) }))
|
|
14000
|
-
};
|
|
14001
|
-
}
|
|
14002
13927
|
|
|
14003
13928
|
// src/repo-resolve.ts
|
|
14004
13929
|
function slugOf(repoOrSlug) {
|
|
@@ -14926,6 +14851,25 @@ function applyOrgMarketplacePins(path2, names) {
|
|
|
14926
14851
|
"pin"
|
|
14927
14852
|
);
|
|
14928
14853
|
}
|
|
14854
|
+
function writeMarketplacePinPending(path2, names, now = Date.now()) {
|
|
14855
|
+
try {
|
|
14856
|
+
(0, import_node_fs18.mkdirSync)((0, import_node_path16.dirname)(path2), { recursive: true });
|
|
14857
|
+
(0, import_node_fs18.writeFileSync)(path2, `${JSON.stringify({ v: 1, names: [...names], at: new Date(now).toISOString() })}
|
|
14858
|
+
`, "utf8");
|
|
14859
|
+
} catch {
|
|
14860
|
+
}
|
|
14861
|
+
}
|
|
14862
|
+
function readMarketplacePinPending(path2, name, now = Date.now()) {
|
|
14863
|
+
try {
|
|
14864
|
+
const parsed = JSON.parse((0, import_node_fs18.readFileSync)(path2, "utf8"));
|
|
14865
|
+
const at = typeof parsed.at === "string" ? Date.parse(parsed.at) : Number.NaN;
|
|
14866
|
+
if (parsed.v !== 1 || !Array.isArray(parsed.names) || !parsed.names.includes(name) || !Number.isFinite(at)) return void 0;
|
|
14867
|
+
if (now - at < 0 || now - at > 12 * 60 * 6e4) return void 0;
|
|
14868
|
+
return { v: 1, names: parsed.names.filter((n) => typeof n === "string"), at: parsed.at };
|
|
14869
|
+
} catch {
|
|
14870
|
+
return void 0;
|
|
14871
|
+
}
|
|
14872
|
+
}
|
|
14929
14873
|
function restoredPinsLine(pins) {
|
|
14930
14874
|
const named = [...pins].map(([name, want]) => {
|
|
14931
14875
|
const fields = [
|
|
@@ -16214,6 +16158,38 @@ async function postClaimMarkerComment(client, item) {
|
|
|
16214
16158
|
`);
|
|
16215
16159
|
}
|
|
16216
16160
|
}
|
|
16161
|
+
var CLAIM_SESSION_ACTIVITY_MS = 30 * 6e4;
|
|
16162
|
+
var CLAIM_SESSION_PROBE_CACHE_MS = 6e4;
|
|
16163
|
+
var claimSessionProbeCache = /* @__PURE__ */ new Map();
|
|
16164
|
+
function probeLocalClaimSession(marker, now = Date.now()) {
|
|
16165
|
+
if (!marker.session || !marker.host || marker.host.toLowerCase() !== (0, import_node_os7.hostname)().toLowerCase()) return void 0;
|
|
16166
|
+
if (!marker.surface?.toLowerCase().startsWith("claude")) return void 0;
|
|
16167
|
+
const cacheKey = `${marker.host.toLowerCase()}/${marker.session}`;
|
|
16168
|
+
const cached = claimSessionProbeCache.get(cacheKey);
|
|
16169
|
+
if (cached && now - cached.checkedAt <= CLAIM_SESSION_PROBE_CACHE_MS) return cached.state;
|
|
16170
|
+
const remember = (state) => {
|
|
16171
|
+
claimSessionProbeCache.set(cacheKey, { checkedAt: now, state });
|
|
16172
|
+
return state;
|
|
16173
|
+
};
|
|
16174
|
+
const root = (0, import_node_path18.join)((0, import_node_os7.homedir)(), ".claude", "projects");
|
|
16175
|
+
try {
|
|
16176
|
+
const wanted = `${marker.session}.jsonl`.toLowerCase();
|
|
16177
|
+
const pending = [root];
|
|
16178
|
+
while (pending.length) {
|
|
16179
|
+
const dir = pending.pop();
|
|
16180
|
+
for (const entry of (0, import_node_fs20.readdirSync)(dir, { withFileTypes: true })) {
|
|
16181
|
+
const path2 = (0, import_node_path18.join)(dir, entry.name);
|
|
16182
|
+
if (entry.isDirectory()) pending.push(path2);
|
|
16183
|
+
else if (entry.isFile() && entry.name.toLowerCase() === wanted) {
|
|
16184
|
+
return remember(now - (0, import_node_fs20.statSync)(path2).mtimeMs <= CLAIM_SESSION_ACTIVITY_MS ? "live" : "dead");
|
|
16185
|
+
}
|
|
16186
|
+
}
|
|
16187
|
+
}
|
|
16188
|
+
return remember("dead");
|
|
16189
|
+
} catch {
|
|
16190
|
+
return remember("unverifiable");
|
|
16191
|
+
}
|
|
16192
|
+
}
|
|
16217
16193
|
function openPullsFetcher(client) {
|
|
16218
16194
|
const byRepo = /* @__PURE__ */ new Map();
|
|
16219
16195
|
return (repo) => {
|
|
@@ -16231,6 +16207,11 @@ async function gatherClaimLiveness(client, repo, number, fetchOpenPulls) {
|
|
|
16231
16207
|
const comments = await client.restPaginate(`repos/${repo}/issues/${number}/comments`);
|
|
16232
16208
|
out.marker = latestClaimMarker(comments.map((comment) => ({ body: comment.body ?? "" })));
|
|
16233
16209
|
out.markerAgeMs = out.marker ? Date.now() - Date.parse(out.marker.ts) : void 0;
|
|
16210
|
+
if (out.marker) {
|
|
16211
|
+
const state = probeLocalClaimSession(out.marker);
|
|
16212
|
+
if (state === "live" || state === "dead") out.sessionState = state;
|
|
16213
|
+
else if (state === "unverifiable") out.failed.push("session");
|
|
16214
|
+
}
|
|
16234
16215
|
} catch {
|
|
16235
16216
|
out.failed.push("comments");
|
|
16236
16217
|
}
|
|
@@ -16262,8 +16243,10 @@ async function gatherClaimLiveness(client, repo, number, fetchOpenPulls) {
|
|
|
16262
16243
|
}
|
|
16263
16244
|
function liveEvidenceLines(evidence, repo) {
|
|
16264
16245
|
const live = [];
|
|
16265
|
-
if (evidence.marker && evidence.
|
|
16266
|
-
live.push(`claim
|
|
16246
|
+
if (evidence.marker && evidence.sessionState === "live") {
|
|
16247
|
+
live.push(`claim session ${describeClaimMarker(evidence.marker)} has recent local transcript activity`);
|
|
16248
|
+
} else if (evidence.marker && evidence.sessionState !== "dead" && evidence.markerAgeMs !== void 0 && evidence.markerAgeMs < CLAIM_MARKER_LIVE_MS) {
|
|
16249
|
+
live.push(`claim marker from ${describeClaimMarker(evidence.marker)} is only ${formatClaimAge(evidence.markerAgeMs)} old (session not locally probeable)`);
|
|
16267
16250
|
}
|
|
16268
16251
|
if (evidence.openPr) live.push(evidence.openPr);
|
|
16269
16252
|
if (evidence.branch) live.push(`live branch ${evidence.branch} on ${repo}`);
|
|
@@ -16359,9 +16342,9 @@ async function boardDoctor(options, deps = {}) {
|
|
|
16359
16342
|
while (next < claimedItems.length) {
|
|
16360
16343
|
const item = claimedItems[next++];
|
|
16361
16344
|
const evidence = await gatherClaimLiveness(client, item.repository, item.number, fetchOpenPulls);
|
|
16362
|
-
const freshMarker = evidence.markerAgeMs !== void 0 && evidence.markerAgeMs < CLAIM_MARKER_LIVE_MS;
|
|
16345
|
+
const freshMarker = evidence.sessionState === "live" || evidence.sessionState !== "dead" && evidence.markerAgeMs !== void 0 && evidence.markerAgeMs < CLAIM_MARKER_LIVE_MS;
|
|
16363
16346
|
const bits = [
|
|
16364
|
-
evidence.marker ? `claim marker ${describeClaimMarker(evidence.marker)} ${formatClaimAge(evidence.markerAgeMs)} old` : evidence.failed.includes("comments") ? "claim marker unreadable" : "no claim marker",
|
|
16347
|
+
evidence.marker ? `claim marker ${describeClaimMarker(evidence.marker)} ${formatClaimAge(evidence.markerAgeMs)} old` + (evidence.sessionState ? `; local session ${evidence.sessionState}` : "") : evidence.failed.includes("comments") ? "claim marker unreadable" : "no claim marker",
|
|
16365
16348
|
evidence.openPr,
|
|
16366
16349
|
evidence.branch ? `branch ${evidence.branch}` : void 0
|
|
16367
16350
|
].filter((bit) => Boolean(bit)).join("; ");
|
|
@@ -16465,7 +16448,7 @@ async function resolveAutoAddBoardAttach(client, cfg, selector, priority, warn =
|
|
|
16465
16448
|
}
|
|
16466
16449
|
|
|
16467
16450
|
// src/issue-body.ts
|
|
16468
|
-
var
|
|
16451
|
+
var import_node_os8 = require("node:os");
|
|
16469
16452
|
var TextArgError = class extends Error {
|
|
16470
16453
|
constructor(message, code, offendingFlag) {
|
|
16471
16454
|
super(message);
|
|
@@ -16477,7 +16460,7 @@ var TextArgError = class extends Error {
|
|
|
16477
16460
|
offendingFlag;
|
|
16478
16461
|
};
|
|
16479
16462
|
function emptyStdinMessage(fileFlag) {
|
|
16480
|
-
if ((0,
|
|
16463
|
+
if ((0, import_node_os8.platform)() === "win32") {
|
|
16481
16464
|
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)`;
|
|
16482
16465
|
}
|
|
16483
16466
|
return `${fileFlag} - read empty stdin (nothing piped \u2014 pass a heredoc/pipe, or ${fileFlag} <path>)`;
|
|
@@ -17802,7 +17785,7 @@ async function runHotfixStart(deps, options) {
|
|
|
17802
17785
|
}
|
|
17803
17786
|
notes.push(`cherry-picked ${label} onto ${branch} (from origin/main, -x trailer recorded)`);
|
|
17804
17787
|
if (deployModel === "hub-serverless") {
|
|
17805
|
-
await deps.run("node", ["scripts/release-distribution.mjs", "prepare", version]);
|
|
17788
|
+
await deps.run("node", ["scripts/release-distribution.mjs", "prepare", version, "--source-commit", "HEAD"]);
|
|
17806
17789
|
const changedFiles = (await deps.run("node", ["scripts/release-distribution.mjs", "changed-files"])).split("\n").map((s) => s.trim()).filter(Boolean);
|
|
17807
17790
|
await deps.run("git", ["add", "-f", "--", ...changedFiles]);
|
|
17808
17791
|
const staged = await deps.run("git", ["diff", "--cached", "--name-only"]);
|
|
@@ -18660,8 +18643,8 @@ function renderAccessReport(report) {
|
|
|
18660
18643
|
|
|
18661
18644
|
// src/doc-refs-core.ts
|
|
18662
18645
|
var import_node_child_process10 = require("node:child_process");
|
|
18663
|
-
var
|
|
18664
|
-
var
|
|
18646
|
+
var import_node_fs21 = require("node:fs");
|
|
18647
|
+
var import_node_path19 = require("node:path");
|
|
18665
18648
|
var PIN_RE = /<!--\s*pinned by\s+([^:]+?)\s*:\s*"([^"]+)"[^"]*-->/;
|
|
18666
18649
|
var PIN_MENTION_RE = /<!--\s*pinned by\b/;
|
|
18667
18650
|
var FWD_RE = /<!--\s*forward-ref:\s*(\S+?)\s*-->/;
|
|
@@ -18717,7 +18700,7 @@ function checkPins(root, readFile9, docs2) {
|
|
|
18717
18700
|
findings.push({ kind: "malformed-pin", doc, line: pin.line, detail: pin.text });
|
|
18718
18701
|
continue;
|
|
18719
18702
|
}
|
|
18720
|
-
const source = readFile9((0,
|
|
18703
|
+
const source = readFile9((0, import_node_path19.join)(root, pin.file));
|
|
18721
18704
|
if (source == null) {
|
|
18722
18705
|
findings.push({ kind: "missing-test", doc, line: pin.line, detail: pin.file });
|
|
18723
18706
|
continue;
|
|
@@ -18777,7 +18760,7 @@ function checkRefs(root, deps, docs2) {
|
|
|
18777
18760
|
const candidates = [];
|
|
18778
18761
|
const direct = [];
|
|
18779
18762
|
for (const [doc, markdown] of Object.entries(docs2)) {
|
|
18780
|
-
const docDir =
|
|
18763
|
+
const docDir = import_node_path19.posix.dirname(doc);
|
|
18781
18764
|
const base = docDir === "." ? "" : docDir;
|
|
18782
18765
|
const covered = /* @__PURE__ */ new Set();
|
|
18783
18766
|
const markers = [];
|
|
@@ -18786,21 +18769,21 @@ function checkRefs(root, deps, docs2) {
|
|
|
18786
18769
|
direct.push({ kind: "malformed-forward-ref", doc, line: fwd.line, detail: fwd.text });
|
|
18787
18770
|
continue;
|
|
18788
18771
|
}
|
|
18789
|
-
const docRel =
|
|
18790
|
-
const rootRel =
|
|
18772
|
+
const docRel = import_node_path19.posix.normalize(import_node_path19.posix.join(base, fwd.target));
|
|
18773
|
+
const rootRel = import_node_path19.posix.normalize(fwd.target.replace(/^\/+/, ""));
|
|
18791
18774
|
markers.push({ target: fwd.target, line: fwd.line, docRel, rootRel });
|
|
18792
18775
|
covered.add(docRel);
|
|
18793
18776
|
covered.add(rootRel);
|
|
18794
18777
|
}
|
|
18795
18778
|
const links = extractLinks(markdown).map(({ target, line }) => {
|
|
18796
|
-
const resolved =
|
|
18797
|
-
return { target, line, resolved, missing: !exists((0,
|
|
18779
|
+
const resolved = import_node_path19.posix.normalize(import_node_path19.posix.join(base, target));
|
|
18780
|
+
return { target, line, resolved, missing: !exists((0, import_node_path19.join)(root, resolved)) };
|
|
18798
18781
|
});
|
|
18799
18782
|
for (const marker of markers) {
|
|
18800
18783
|
const coversMissing = links.some(
|
|
18801
18784
|
(l) => l.missing && (l.resolved === marker.docRel || l.resolved === marker.rootRel)
|
|
18802
18785
|
);
|
|
18803
|
-
if (!coversMissing && (exists((0,
|
|
18786
|
+
if (!coversMissing && (exists((0, import_node_path19.join)(root, marker.docRel)) || exists((0, import_node_path19.join)(root, marker.rootRel)))) {
|
|
18804
18787
|
direct.push({
|
|
18805
18788
|
kind: "stale-forward-ref",
|
|
18806
18789
|
doc,
|
|
@@ -18811,8 +18794,8 @@ function checkRefs(root, deps, docs2) {
|
|
|
18811
18794
|
}
|
|
18812
18795
|
for (const { ref, line } of extractRefs(markdown)) {
|
|
18813
18796
|
const first = ref.split("/")[0];
|
|
18814
|
-
if (!exists((0,
|
|
18815
|
-
if (!exists((0,
|
|
18797
|
+
if (!exists((0, import_node_path19.join)(root, first))) continue;
|
|
18798
|
+
if (!exists((0, import_node_path19.join)(root, ref))) candidates.push({ kind: "missing-path", doc, line, detail: ref });
|
|
18816
18799
|
}
|
|
18817
18800
|
for (const { target, line, resolved, missing } of links) {
|
|
18818
18801
|
if (resolved.startsWith("..")) {
|
|
@@ -18860,22 +18843,22 @@ function checkCommands(docs2, commandPaths) {
|
|
|
18860
18843
|
return { ok: findings.length === 0, findings, warnings: [] };
|
|
18861
18844
|
}
|
|
18862
18845
|
function readFileOrNull(path2) {
|
|
18863
|
-
return (0,
|
|
18846
|
+
return (0, import_node_fs21.existsSync)(path2) ? (0, import_node_fs21.readFileSync)(path2, "utf8") : null;
|
|
18864
18847
|
}
|
|
18865
18848
|
function walk(dir, root, out) {
|
|
18866
|
-
for (const entry of (0,
|
|
18867
|
-
const full = (0,
|
|
18868
|
-
if ((0,
|
|
18849
|
+
for (const entry of (0, import_node_fs21.readdirSync)(dir)) {
|
|
18850
|
+
const full = (0, import_node_path19.join)(dir, entry);
|
|
18851
|
+
if ((0, import_node_fs21.statSync)(full).isDirectory()) walk(full, root, out);
|
|
18869
18852
|
else if (entry.endsWith(".md")) out.push(full.slice(root.length + 1).replaceAll("\\", "/"));
|
|
18870
18853
|
}
|
|
18871
18854
|
return out;
|
|
18872
18855
|
}
|
|
18873
18856
|
function defaultListDocs(root) {
|
|
18874
|
-
const docsDir = (0,
|
|
18875
|
-
const docs2 = ((0,
|
|
18857
|
+
const docsDir = (0, import_node_path19.join)(root, "docs");
|
|
18858
|
+
const docs2 = ((0, import_node_fs21.existsSync)(docsDir) ? walk(docsDir, root, []) : []).filter(
|
|
18876
18859
|
(rel) => !SKIP_WALK.some((skip) => rel.startsWith(skip))
|
|
18877
18860
|
);
|
|
18878
|
-
return [...ROOT_DOCS.filter((rel) => (0,
|
|
18861
|
+
return [...ROOT_DOCS.filter((rel) => (0, import_node_fs21.existsSync)((0, import_node_path19.join)(root, rel))), ...docs2];
|
|
18879
18862
|
}
|
|
18880
18863
|
function defaultIsIgnored(root, relPaths, exec = import_node_child_process10.execFileSync) {
|
|
18881
18864
|
const inRepo = relPaths.filter((p) => !p.startsWith(".."));
|
|
@@ -18895,14 +18878,14 @@ function defaultIsIgnored(root, relPaths, exec = import_node_child_process10.exe
|
|
|
18895
18878
|
}
|
|
18896
18879
|
function runDocRefs(root, deps = {}) {
|
|
18897
18880
|
const readFile9 = deps.readFile ?? readFileOrNull;
|
|
18898
|
-
const exists = deps.exists ??
|
|
18881
|
+
const exists = deps.exists ?? import_node_fs21.existsSync;
|
|
18899
18882
|
const listDocs = deps.listDocs ?? defaultListDocs;
|
|
18900
18883
|
const isIgnored = deps.isIgnored ?? ((paths) => defaultIsIgnored(root, paths));
|
|
18901
18884
|
const commandPaths = deps.commandPaths ?? null;
|
|
18902
18885
|
const walked = listDocs(root);
|
|
18903
18886
|
const ignoredDocs = walked.length ? isIgnored(walked) : /* @__PURE__ */ new Set();
|
|
18904
18887
|
const docs2 = Object.fromEntries(
|
|
18905
|
-
walked.filter((rel) => !ignoredDocs.has(rel)).map((rel) => [rel, readFile9((0,
|
|
18888
|
+
walked.filter((rel) => !ignoredDocs.has(rel)).map((rel) => [rel, readFile9((0, import_node_path19.join)(root, rel))]).filter(([, body]) => body != null)
|
|
18906
18889
|
);
|
|
18907
18890
|
const refResult = checkRefs(root, { exists, isIgnored }, docs2);
|
|
18908
18891
|
const findings = [
|
|
@@ -18929,8 +18912,8 @@ function runDocRefs(root, deps = {}) {
|
|
|
18929
18912
|
|
|
18930
18913
|
// src/spawn-policy-core.ts
|
|
18931
18914
|
var import_node_child_process11 = require("node:child_process");
|
|
18932
|
-
var
|
|
18933
|
-
var
|
|
18915
|
+
var import_node_fs22 = require("node:fs");
|
|
18916
|
+
var import_node_path20 = require("node:path");
|
|
18934
18917
|
var SPAWNERS = ["spawn", "spawnSync", "exec", "execSync", "execFile", "execFileSync"];
|
|
18935
18918
|
var CALL_SOURCE = String.raw`(^|[^.\w$])(${SPAWNERS.join("|")})\s*\(`;
|
|
18936
18919
|
var SOURCE_EXT = /\.(ts|mts|cts|js|mjs|cjs)$/;
|
|
@@ -19016,7 +18999,7 @@ function runSpawnPolicy(root) {
|
|
|
19016
18999
|
for (const file of files) {
|
|
19017
19000
|
let raw;
|
|
19018
19001
|
try {
|
|
19019
|
-
raw = (0,
|
|
19002
|
+
raw = (0, import_node_fs22.readFileSync)((0, import_node_path20.join)(root, file), "utf8");
|
|
19020
19003
|
} catch {
|
|
19021
19004
|
continue;
|
|
19022
19005
|
}
|
|
@@ -19034,8 +19017,8 @@ function runSpawnPolicy(root) {
|
|
|
19034
19017
|
|
|
19035
19018
|
// src/test-policy-core.ts
|
|
19036
19019
|
var import_node_child_process12 = require("node:child_process");
|
|
19037
|
-
var
|
|
19038
|
-
var
|
|
19020
|
+
var import_node_fs23 = require("node:fs");
|
|
19021
|
+
var import_node_path21 = require("node:path");
|
|
19039
19022
|
var POLICY_FILE = "test-policy.json";
|
|
19040
19023
|
var TEST_RE = /\.(?:test|spec)\.[cm]?[jt]sx?$/;
|
|
19041
19024
|
var PY_TEST_RE = /(?:^|\/)test_[^/]*\.py$|_test\.py$/;
|
|
@@ -19088,7 +19071,7 @@ function isTestPath(path2) {
|
|
|
19088
19071
|
return TEST_RE.test(path2) || PY_TEST_RE.test(path2);
|
|
19089
19072
|
}
|
|
19090
19073
|
function loadPolicy(root, readFile9 = readFileOrNull2) {
|
|
19091
|
-
const raw = readFile9((0,
|
|
19074
|
+
const raw = readFile9((0, import_node_path21.join)(root, POLICY_FILE));
|
|
19092
19075
|
if (raw == null) return { mandatory: [], declared: false };
|
|
19093
19076
|
try {
|
|
19094
19077
|
return { ...JSON.parse(raw), declared: true };
|
|
@@ -19098,7 +19081,7 @@ function loadPolicy(root, readFile9 = readFileOrNull2) {
|
|
|
19098
19081
|
}
|
|
19099
19082
|
function readFileOrNull2(path2) {
|
|
19100
19083
|
try {
|
|
19101
|
-
return (0,
|
|
19084
|
+
return (0, import_node_fs23.readFileSync)(path2, "utf8");
|
|
19102
19085
|
} catch {
|
|
19103
19086
|
return null;
|
|
19104
19087
|
}
|
|
@@ -19125,12 +19108,12 @@ function classify(changed, policy, present = () => false) {
|
|
|
19125
19108
|
const removedProtected = [...removed].filter((p) => protectedBy.has(p)).map((p) => ({ path: p, why: protectedBy.get(p) ?? "" }));
|
|
19126
19109
|
return { mandatoryHits, untestedHits, testChanges, addedTests, removedProtected };
|
|
19127
19110
|
}
|
|
19128
|
-
function unresolvedProtectedEntries(policy, root, exists = (path2) => (0,
|
|
19129
|
-
return (policy.protected ?? []).map((p) => p.path).filter((p) => !exists((0,
|
|
19111
|
+
function unresolvedProtectedEntries(policy, root, exists = (path2) => (0, import_node_fs23.existsSync)(path2)) {
|
|
19112
|
+
return (policy.protected ?? []).map((p) => p.path).filter((p) => !exists((0, import_node_path21.join)(root, p)));
|
|
19130
19113
|
}
|
|
19131
|
-
function unresolvedSatisfiers(policy, root, exists = (path2) => (0,
|
|
19114
|
+
function unresolvedSatisfiers(policy, root, exists = (path2) => (0, import_node_fs23.existsSync)(path2)) {
|
|
19132
19115
|
const declared = (policy.mandatory ?? []).flatMap((m) => m.satisfiedBy ?? []);
|
|
19133
|
-
return [...new Set(declared)].filter((p) => !exists((0,
|
|
19116
|
+
return [...new Set(declared)].filter((p) => !exists((0, import_node_path21.join)(root, p)));
|
|
19134
19117
|
}
|
|
19135
19118
|
function evaluate(changed, policy, present = () => false) {
|
|
19136
19119
|
const { mandatoryHits, untestedHits, testChanges, addedTests, removedProtected } = classify(changed, policy, present);
|
|
@@ -19312,13 +19295,13 @@ function changedFilesSince(base, cwd) {
|
|
|
19312
19295
|
}
|
|
19313
19296
|
function runTestPolicy(root, deps = {}) {
|
|
19314
19297
|
const policy = deps.policy ?? loadPolicy(root);
|
|
19315
|
-
const exists = deps.exists ?? ((path2) => (0,
|
|
19298
|
+
const exists = deps.exists ?? ((path2) => (0, import_node_fs23.existsSync)(path2));
|
|
19316
19299
|
const counts = { mandatoryCount: (policy.mandatory ?? []).length, protectedCount: (policy.protected ?? []).length };
|
|
19317
19300
|
const base = deps.changed ? "(injected)" : resolveBase(root, deps.base);
|
|
19318
19301
|
const refusal = deps.changed ? null : untrustworthyRange(root, base);
|
|
19319
19302
|
const changed = deps.changed ?? (refusal ? [] : changedFilesSince(base, root));
|
|
19320
19303
|
const lookup = deps.override !== void 0 ? { override: deps.override, refusals: [] } : !deps.changed && !refusal ? readOverride(base, root) : { override: null, refusals: [] };
|
|
19321
|
-
const present = (path2) => exists((0,
|
|
19304
|
+
const present = (path2) => exists((0, import_node_path21.join)(root, path2));
|
|
19322
19305
|
const removedByThisDiff = removedPaths(changed);
|
|
19323
19306
|
const staleFindings = [];
|
|
19324
19307
|
const unresolved = unresolvedProtectedEntries(policy, root, exists).filter((p) => !removedByThisDiff.has(p));
|
|
@@ -19474,8 +19457,8 @@ function docsAuditStatus(fetch2, opts) {
|
|
|
19474
19457
|
}
|
|
19475
19458
|
|
|
19476
19459
|
// src/project-info-sync.ts
|
|
19477
|
-
var
|
|
19478
|
-
var
|
|
19460
|
+
var import_node_fs24 = require("node:fs");
|
|
19461
|
+
var import_node_path22 = require("node:path");
|
|
19479
19462
|
var UPDATE_PROJECT_INFO = `mutation($projectId: ID!, $shortDescription: String!, $readme: String!) {
|
|
19480
19463
|
updateProjectV2(input: { projectId: $projectId, shortDescription: $shortDescription, readme: $readme }) {
|
|
19481
19464
|
projectV2 { id }
|
|
@@ -19520,14 +19503,14 @@ function sharedName(entries, fallback) {
|
|
|
19520
19503
|
}
|
|
19521
19504
|
function buildProjectInfoSyncPlan(targetRepo2, project2, projects, repoRoot2) {
|
|
19522
19505
|
if (!project2.projectId) throw new Error(`org project sync-info: ${targetRepo2} registry META has no projectId`);
|
|
19523
|
-
const readmePath = (0,
|
|
19524
|
-
if (!(0,
|
|
19506
|
+
const readmePath = (0, import_node_path22.join)(repoRoot2, "README.md");
|
|
19507
|
+
if (!(0, import_node_fs24.existsSync)(readmePath)) throw new Error(`org project sync-info: ${targetRepo2} has no README.md`);
|
|
19525
19508
|
const entries = entriesFor(project2, projects);
|
|
19526
19509
|
const memberRepos = [...new Set(entries.flatMap((entry) => entry.repos ?? []))].filter((repo) => /^[^/]+\/[^/]+$/.test(repo)).sort((a, b) => a.localeCompare(b));
|
|
19527
19510
|
const projectName = sharedName(entries, project2.name?.trim() || targetRepo2.split("/").pop() || targetRepo2);
|
|
19528
19511
|
if (!memberRepos.length) throw new Error(`org project sync-info: project ${projectName} has no registered member repos`);
|
|
19529
19512
|
const entryNames = entries.map((entry) => entry.name?.trim()).filter((name) => Boolean(name));
|
|
19530
|
-
const shortDescription = memberRepos.length === 1 ? shortDescriptionFromReadme((0,
|
|
19513
|
+
const shortDescription = memberRepos.length === 1 ? shortDescriptionFromReadme((0, import_node_fs24.readFileSync)(readmePath, "utf8")) : `Shared work across ${new Intl.ListFormat("en", { type: "conjunction" }).format(entryNames)}.`;
|
|
19531
19514
|
const lines = [
|
|
19532
19515
|
`# ${projectName}`,
|
|
19533
19516
|
"",
|
|
@@ -19546,8 +19529,8 @@ function buildProjectInfoSyncPlan(targetRepo2, project2, projects, repoRoot2) {
|
|
|
19546
19529
|
const targetBase = `https://github.com/${targetRepo2}`;
|
|
19547
19530
|
const targetBranch = branchFor(targetRepo2, projects);
|
|
19548
19531
|
const orgDocs = [
|
|
19549
|
-
(0,
|
|
19550
|
-
(0,
|
|
19532
|
+
(0, import_node_fs24.existsSync)((0, import_node_path22.join)(repoRoot2, "docs", "org-readme.md")) ? `- [Org identity](${targetBase}/blob/${targetBranch}/docs/org-readme.md)` : "",
|
|
19533
|
+
(0, import_node_fs24.existsSync)((0, import_node_path22.join)(repoRoot2, "docs", "org-architecture.md")) ? `- [Org architecture](${targetBase}/blob/${targetBranch}/docs/org-architecture.md)` : ""
|
|
19551
19534
|
].filter(Boolean);
|
|
19552
19535
|
if (orgDocs.length) lines.push("", "## Organisation docs", "", ...orgDocs);
|
|
19553
19536
|
return { projectId: project2.projectId, projectName, targetRepo: targetRepo2, memberRepos, shortDescription, readme: `${lines.join("\n")}
|
|
@@ -20406,9 +20389,9 @@ function writeError(res) {
|
|
|
20406
20389
|
}
|
|
20407
20390
|
|
|
20408
20391
|
// src/secrets-commands.ts
|
|
20409
|
-
var
|
|
20410
|
-
var
|
|
20411
|
-
var
|
|
20392
|
+
var import_node_fs25 = require("node:fs");
|
|
20393
|
+
var import_node_path23 = require("node:path");
|
|
20394
|
+
var import_node_os9 = require("node:os");
|
|
20412
20395
|
|
|
20413
20396
|
// src/project-runtime.ts
|
|
20414
20397
|
function hasRuntimeSecretContract(contract) {
|
|
@@ -20531,18 +20514,18 @@ function collectMap(value, previous = []) {
|
|
|
20531
20514
|
return [...previous, value];
|
|
20532
20515
|
}
|
|
20533
20516
|
async function decryptRailsCredentials(input) {
|
|
20534
|
-
const appDir = (0,
|
|
20517
|
+
const appDir = (0, import_node_path23.resolve)(input.appDir ?? process.cwd());
|
|
20535
20518
|
const credentialsFile = input.credentialsFile ?? DEFAULT_RAILS_CREDENTIALS_FILE;
|
|
20536
20519
|
const masterKeyFile = input.masterKeyFile ?? DEFAULT_RAILS_MASTER_KEY_FILE;
|
|
20537
|
-
const credentialsPath = (0,
|
|
20538
|
-
const masterKeyPath = (0,
|
|
20520
|
+
const credentialsPath = (0, import_node_path23.resolve)(appDir, credentialsFile);
|
|
20521
|
+
const masterKeyPath = (0, import_node_path23.resolve)(appDir, masterKeyFile);
|
|
20539
20522
|
const env = {
|
|
20540
20523
|
...process.env,
|
|
20541
20524
|
MMI_RAILS_CREDENTIALS_FILE: credentialsPath,
|
|
20542
20525
|
MMI_RAILS_MASTER_KEY_FILE: masterKeyPath
|
|
20543
20526
|
};
|
|
20544
|
-
if ((0,
|
|
20545
|
-
env.RAILS_MASTER_KEY = (0,
|
|
20527
|
+
if ((0, import_node_fs25.existsSync)(masterKeyPath)) {
|
|
20528
|
+
env.RAILS_MASTER_KEY = (0, import_node_fs25.readFileSync)(masterKeyPath, "utf8").trim();
|
|
20546
20529
|
}
|
|
20547
20530
|
const script = [
|
|
20548
20531
|
'require "json"',
|
|
@@ -20552,9 +20535,9 @@ async function decryptRailsCredentials(input) {
|
|
|
20552
20535
|
'config = ActiveSupport::EncryptedConfiguration.new(config_path: config_path, key_path: key_path, env_key: "RAILS_MASTER_KEY", raise_if_missing_key: true)',
|
|
20553
20536
|
"puts JSON.generate(config.config)"
|
|
20554
20537
|
].join("\n");
|
|
20555
|
-
const scriptDir = (0,
|
|
20556
|
-
const scriptPath = (0,
|
|
20557
|
-
(0,
|
|
20538
|
+
const scriptDir = (0, import_node_fs25.mkdtempSync)((0, import_node_path23.join)((0, import_node_os9.tmpdir)(), "mmi-rails-decrypt-"));
|
|
20539
|
+
const scriptPath = (0, import_node_path23.join)(scriptDir, "decrypt.rb");
|
|
20540
|
+
(0, import_node_fs25.writeFileSync)(scriptPath, script, "utf8");
|
|
20558
20541
|
try {
|
|
20559
20542
|
const args = ["exec", "ruby", scriptPath];
|
|
20560
20543
|
const cmd = process.platform === "win32" ? "cmd.exe" : "bundle";
|
|
@@ -20566,7 +20549,7 @@ async function decryptRailsCredentials(input) {
|
|
|
20566
20549
|
});
|
|
20567
20550
|
return JSON.parse(stdout);
|
|
20568
20551
|
} finally {
|
|
20569
|
-
(0,
|
|
20552
|
+
(0, import_node_fs25.rmSync)(scriptDir, { recursive: true, force: true });
|
|
20570
20553
|
}
|
|
20571
20554
|
}
|
|
20572
20555
|
async function readSecretStdin() {
|
|
@@ -20656,7 +20639,7 @@ function registerSecretsCommands(program3) {
|
|
|
20656
20639
|
let body;
|
|
20657
20640
|
if (o.file) {
|
|
20658
20641
|
try {
|
|
20659
|
-
body = (0,
|
|
20642
|
+
body = (0, import_node_fs25.readFileSync)((0, import_node_path23.resolve)(o.file), "utf8");
|
|
20660
20643
|
} catch (e) {
|
|
20661
20644
|
return fail(`secrets org-catalog: cannot read --file ${o.file}: ${e.message}`);
|
|
20662
20645
|
}
|
|
@@ -20761,7 +20744,7 @@ function registerSecretsCommands(program3) {
|
|
|
20761
20744
|
{
|
|
20762
20745
|
...d,
|
|
20763
20746
|
decryptRailsCredentials,
|
|
20764
|
-
removeFile: (path2) => (0,
|
|
20747
|
+
removeFile: (path2) => (0, import_node_fs25.unlinkSync)((0, import_node_path23.resolve)(o.appDir ?? process.cwd(), path2))
|
|
20765
20748
|
},
|
|
20766
20749
|
{
|
|
20767
20750
|
repo: o.repo,
|
|
@@ -20901,6 +20884,13 @@ async function mintInstallationToken(deps) {
|
|
|
20901
20884
|
}
|
|
20902
20885
|
return { token: body.token, expiresAt: body.expires_at };
|
|
20903
20886
|
}
|
|
20887
|
+
async function pollTokenOrUndefined(deps) {
|
|
20888
|
+
try {
|
|
20889
|
+
return (await mintInstallationToken(deps)).token;
|
|
20890
|
+
} catch {
|
|
20891
|
+
return void 0;
|
|
20892
|
+
}
|
|
20893
|
+
}
|
|
20904
20894
|
async function activateAppActor(commandPath3, env, mint) {
|
|
20905
20895
|
const requested = (env[APP_ACTOR_ENV] ?? "").trim();
|
|
20906
20896
|
if (!requested) return "personal";
|
|
@@ -20916,46 +20906,9 @@ async function activateAppActor(commandPath3, env, mint) {
|
|
|
20916
20906
|
env.GH_TOKEN = minted.token;
|
|
20917
20907
|
return "app";
|
|
20918
20908
|
}
|
|
20919
|
-
async function probeRatePools(fetchLike, token) {
|
|
20920
|
-
if (!token) return null;
|
|
20921
|
-
try {
|
|
20922
|
-
const res = await fetchLike("https://api.github.com/rate_limit", {
|
|
20923
|
-
method: "GET",
|
|
20924
|
-
headers: {
|
|
20925
|
-
accept: "application/vnd.github+json",
|
|
20926
|
-
"user-agent": "mmi-cli-app-actor",
|
|
20927
|
-
authorization: `Bearer ${token}`
|
|
20928
|
-
}
|
|
20929
|
-
});
|
|
20930
|
-
if (!res.ok) return null;
|
|
20931
|
-
const body = await res.json();
|
|
20932
|
-
const pool = (p) => typeof p?.remaining === "number" && typeof p?.limit === "number" && typeof p?.reset === "number" ? { remaining: p.remaining, limit: p.limit, reset: p.reset } : null;
|
|
20933
|
-
const core = pool(body.resources?.core);
|
|
20934
|
-
const graphql = pool(body.resources?.graphql);
|
|
20935
|
-
if (!core || !graphql) return null;
|
|
20936
|
-
return { core, graphql };
|
|
20937
|
-
} catch {
|
|
20938
|
-
return null;
|
|
20939
|
-
}
|
|
20940
|
-
}
|
|
20941
|
-
function renderPoolDetail(probe) {
|
|
20942
|
-
const fmt = (p) => `${p.remaining}/${p.limit} (resets ${new Date(p.reset * 1e3).toISOString().slice(11, 16)}Z)`;
|
|
20943
|
-
return `core ${fmt(probe.core)}, graphql ${fmt(probe.graphql)}`;
|
|
20944
|
-
}
|
|
20945
|
-
function checkGithubPools(probe) {
|
|
20946
|
-
if (!probe) return [];
|
|
20947
|
-
const lines = [];
|
|
20948
|
-
lines.push(probe.personal ? { id: "github-pools-personal", ok: true, label: "github pools (personal)", detail: renderPoolDetail(probe.personal) } : { id: "github-pools-personal", ok: false, label: "github pools (personal)", detail: "rate_limit unreadable", fix: "check `gh auth status`" });
|
|
20949
|
-
if (typeof probe.app === "string") {
|
|
20950
|
-
lines.push({ id: "github-pools-app", ok: false, label: "github pools (app actor)", fix: probe.app });
|
|
20951
|
-
} else if (probe.app) {
|
|
20952
|
-
lines.push({ id: "github-pools-app", ok: true, label: "github pools (app actor)", detail: renderPoolDetail(probe.app) });
|
|
20953
|
-
}
|
|
20954
|
-
return lines;
|
|
20955
|
-
}
|
|
20956
20909
|
|
|
20957
20910
|
// src/box-commands.ts
|
|
20958
|
-
var
|
|
20911
|
+
var import_node_fs26 = require("node:fs");
|
|
20959
20912
|
|
|
20960
20913
|
// src/box.ts
|
|
20961
20914
|
var BOX_KEYS = {
|
|
@@ -21158,7 +21111,7 @@ function registerBoxCommands(program3) {
|
|
|
21158
21111
|
}
|
|
21159
21112
|
if (o.json) console.log(JSON.stringify({ box: found, incomplete }, null, 2));
|
|
21160
21113
|
else if (o.ssh && o.script) {
|
|
21161
|
-
(0,
|
|
21114
|
+
(0, import_node_fs26.writeFileSync)(o.script, sshRecipeScript(found), "utf8");
|
|
21162
21115
|
console.log(`wrote ${o.script} \u2014 run: bash "${o.script}"`);
|
|
21163
21116
|
} else if (o.ssh) console.log(`${formatSshRecipe(found)}
|
|
21164
21117
|
${SSH_RECIPE_AGENT_NOTE}`);
|
|
@@ -21461,12 +21414,6 @@ function spliceDoc(docText, generatedSection) {
|
|
|
21461
21414
|
}
|
|
21462
21415
|
return docText.slice(0, start) + generatedSection + docText.slice(end + DOC_END_MARKER.length);
|
|
21463
21416
|
}
|
|
21464
|
-
function driftClearableFrom(drift, repoName) {
|
|
21465
|
-
if (drift.class !== "file-vs-registry-stale") return false;
|
|
21466
|
-
const slash = drift.name.indexOf("/");
|
|
21467
|
-
if (slash <= 0) return false;
|
|
21468
|
-
return drift.name.slice(0, slash).toLowerCase() === repoName.toLowerCase();
|
|
21469
|
-
}
|
|
21470
21417
|
var HARBOUR_LLM_LAUNCHERS = /* @__PURE__ */ new Set(["cursor-agent"]);
|
|
21471
21418
|
function isHarbourLlmLauncher(executor) {
|
|
21472
21419
|
return HARBOUR_LLM_LAUNCHERS.has(executor);
|
|
@@ -21850,7 +21797,7 @@ function registerSchedulesCommands(program3) {
|
|
|
21850
21797
|
|
|
21851
21798
|
// src/file-lock.ts
|
|
21852
21799
|
var import_promises4 = require("node:fs/promises");
|
|
21853
|
-
var
|
|
21800
|
+
var import_node_path24 = require("node:path");
|
|
21854
21801
|
var sleep = (ms) => new Promise((resolve5) => setTimeout(resolve5, ms));
|
|
21855
21802
|
var IMMEDIATE_RETRY_BUDGET = 3;
|
|
21856
21803
|
var FileLockBusyError = class extends Error {
|
|
@@ -21935,7 +21882,7 @@ async function releaseFileLock(lockPath, guard) {
|
|
|
21935
21882
|
}
|
|
21936
21883
|
async function withFileLock(lockPath, opts, fn) {
|
|
21937
21884
|
const resolved = resolveFileLockOpts(opts);
|
|
21938
|
-
await (0, import_promises4.mkdir)((0,
|
|
21885
|
+
await (0, import_promises4.mkdir)((0, import_node_path24.dirname)(lockPath), { recursive: true }).catch(() => void 0);
|
|
21939
21886
|
const guard = await acquireFileLock(lockPath, resolved, Date.now() + resolved.maxWaitMs);
|
|
21940
21887
|
try {
|
|
21941
21888
|
return await fn();
|
|
@@ -21946,7 +21893,7 @@ async function withFileLock(lockPath, opts, fn) {
|
|
|
21946
21893
|
|
|
21947
21894
|
// src/schedules-lift-command.ts
|
|
21948
21895
|
var import_promises5 = require("node:fs/promises");
|
|
21949
|
-
var
|
|
21896
|
+
var import_node_path25 = require("node:path");
|
|
21950
21897
|
|
|
21951
21898
|
// src/schedules-lift.ts
|
|
21952
21899
|
var SCHEDULE_HEADER_FIELDS = ["schedule", "what", "owner", "cadence", "executor", "llm", "output", "breaks", "kill"];
|
|
@@ -22052,7 +21999,7 @@ async function readWorkflowFiles(dir) {
|
|
|
22052
21999
|
const files = [];
|
|
22053
22000
|
for (const name of names.sort()) {
|
|
22054
22001
|
if (!/\.ya?ml$/.test(name)) continue;
|
|
22055
|
-
files.push({ path: `.github/workflows/${name}`, text: await (0, import_promises5.readFile)((0,
|
|
22002
|
+
files.push({ path: `.github/workflows/${name}`, text: await (0, import_promises5.readFile)((0, import_node_path25.join)(dir, name), "utf8") });
|
|
22056
22003
|
}
|
|
22057
22004
|
return files;
|
|
22058
22005
|
}
|
|
@@ -22136,13 +22083,13 @@ function registerSchedulesLiftCommand(program3, deps = {}) {
|
|
|
22136
22083
|
// src/edge-tunnel.ts
|
|
22137
22084
|
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])?)+$/;
|
|
22138
22085
|
var UPSTREAM_RE = /^https?:\/\/[^/\s]+(?::\d+)?(?:\/.*)?$/;
|
|
22139
|
-
function tunnelNameFromHostname(
|
|
22140
|
-
return
|
|
22086
|
+
function tunnelNameFromHostname(hostname3) {
|
|
22087
|
+
return hostname3.replace(/\./g, "-").slice(0, 63);
|
|
22141
22088
|
}
|
|
22142
|
-
function planInfraTunnel(
|
|
22143
|
-
const host =
|
|
22089
|
+
function planInfraTunnel(hostname3, upstream) {
|
|
22090
|
+
const host = hostname3.trim().toLowerCase();
|
|
22144
22091
|
const origin = upstream.trim();
|
|
22145
|
-
if (!HOSTNAME_RE.test(host)) throw new Error(`invalid hostname ${JSON.stringify(
|
|
22092
|
+
if (!HOSTNAME_RE.test(host)) throw new Error(`invalid hostname ${JSON.stringify(hostname3)}`);
|
|
22146
22093
|
if (!UPSTREAM_RE.test(origin)) throw new Error(`invalid upstream ${JSON.stringify(upstream)} \u2014 expected http(s)://host:port`);
|
|
22147
22094
|
const tunnelName = tunnelNameFromHostname(host);
|
|
22148
22095
|
const configYaml = [
|
|
@@ -22594,9 +22541,9 @@ function registerQueryCommands(program3) {
|
|
|
22594
22541
|
}
|
|
22595
22542
|
|
|
22596
22543
|
// src/bootstrap-commands.ts
|
|
22597
|
-
var
|
|
22598
|
-
var
|
|
22599
|
-
var
|
|
22544
|
+
var import_node_fs27 = require("node:fs");
|
|
22545
|
+
var import_node_os10 = require("node:os");
|
|
22546
|
+
var import_node_path26 = require("node:path");
|
|
22600
22547
|
|
|
22601
22548
|
// src/bootstrap-drift.ts
|
|
22602
22549
|
function byteComparableSeeds(manifest, cls) {
|
|
@@ -23210,13 +23157,13 @@ function registerBootstrapCommands(program3) {
|
|
|
23210
23157
|
client: defaultGitHubClient(),
|
|
23211
23158
|
projectMeta: meta,
|
|
23212
23159
|
deployModel: typeof meta?.deployModel === "string" ? meta.deployModel : void 0,
|
|
23213
|
-
readLocalFile: (path2) => path2 === "projects.json" && apiProjects != null ? apiProjects : (0,
|
|
23160
|
+
readLocalFile: (path2) => path2 === "projects.json" && apiProjects != null ? apiProjects : (0, import_node_fs27.existsSync)(path2) ? (0, import_node_fs27.readFileSync)(path2, "utf8") : null,
|
|
23214
23161
|
// requiredGcpApis is stored as an array by a JSON write, but `org project set --var KEY=VALUE` stores a raw
|
|
23215
23162
|
// comma-string — accept either so the seeded value verifies regardless of how it was written.
|
|
23216
23163
|
// #3689: the same committed map the org access audit reads (#3664), so a sanctioned admin is not a
|
|
23217
23164
|
// permanent bootstrap failure on one surface and an intended state on the other. Absent file → no
|
|
23218
23165
|
// sanction, which is the pre-#3664 behaviour.
|
|
23219
|
-
sanctionedAdmins: (0,
|
|
23166
|
+
sanctionedAdmins: (0, import_node_fs27.existsSync)("access-matrix.json") ? entriesValueByCanonicalRepo(loadSanctionedAdmins((0, import_node_fs27.readFileSync)("access-matrix.json", "utf8")), repo) : void 0,
|
|
23220
23167
|
requiredGcpApis: (() => {
|
|
23221
23168
|
const v = meta?.requiredGcpApis;
|
|
23222
23169
|
if (Array.isArray(v)) return v;
|
|
@@ -23254,12 +23201,12 @@ function registerBootstrapCommands(program3) {
|
|
|
23254
23201
|
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 () => {
|
|
23255
23202
|
const o = { repo: rawValue("--repo", ""), json: rawFlag("--json") };
|
|
23256
23203
|
const manifestPath = "skills/bootstrap/seeds/manifest.json";
|
|
23257
|
-
if (!(0,
|
|
23258
|
-
const manifest = loadBootstrapSeeds((0,
|
|
23204
|
+
if (!(0, import_node_fs27.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`);
|
|
23205
|
+
const manifest = loadBootstrapSeeds((0, import_node_fs27.readFileSync)(manifestPath, "utf8"));
|
|
23259
23206
|
const hubContents = /* @__PURE__ */ new Map();
|
|
23260
23207
|
for (const s of manifest.seeds) {
|
|
23261
23208
|
if (s.ownership !== "org" || s.source !== "self") continue;
|
|
23262
|
-
hubContents.set(s.target, (0,
|
|
23209
|
+
hubContents.set(s.target, (0, import_node_fs27.existsSync)(s.target) ? (0, import_node_fs27.readFileSync)(s.target, "utf8") : null);
|
|
23263
23210
|
}
|
|
23264
23211
|
let targets;
|
|
23265
23212
|
let classOf = (_repo) => "deployable";
|
|
@@ -23336,8 +23283,8 @@ function registerBootstrapCommands(program3) {
|
|
|
23336
23283
|
return fail(`bootstrap apply: ${e.message}`);
|
|
23337
23284
|
}
|
|
23338
23285
|
const manifestPath = "skills/bootstrap/seeds/manifest.json";
|
|
23339
|
-
if (!(0,
|
|
23340
|
-
const manifest = loadBootstrapSeeds((0,
|
|
23286
|
+
if (!(0, import_node_fs27.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`);
|
|
23287
|
+
const manifest = loadBootstrapSeeds((0, import_node_fs27.readFileSync)(manifestPath, "utf8"));
|
|
23341
23288
|
const baseBranch = o.class === "content" ? "main" : "development";
|
|
23342
23289
|
const slug = parsedRepo.slug;
|
|
23343
23290
|
const onlyTarget = o.only.trim();
|
|
@@ -23348,16 +23295,16 @@ function registerBootstrapCommands(program3) {
|
|
|
23348
23295
|
${known}`);
|
|
23349
23296
|
}
|
|
23350
23297
|
const gh = async (args) => execFileP2("gh", args, { timeout: 2e4 });
|
|
23351
|
-
const readFile9 = (p) => (0,
|
|
23298
|
+
const readFile9 = (p) => (0, import_node_fs27.existsSync)(p) ? (0, import_node_fs27.readFileSync)(p, "utf8") : null;
|
|
23352
23299
|
const enc = (p) => p.split("/").map(encodeURIComponent).join("/");
|
|
23353
23300
|
const putSeed = async (target, content, ref, sha) => {
|
|
23354
|
-
const tmp = (0,
|
|
23355
|
-
(0,
|
|
23301
|
+
const tmp = (0, import_node_path26.join)((0, import_node_os10.tmpdir)(), `mmi-seed-${process.pid}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}.json`);
|
|
23302
|
+
(0, import_node_fs27.writeFileSync)(tmp, JSON.stringify(contentPutBody(target, content, ref, sha)), "utf8");
|
|
23356
23303
|
try {
|
|
23357
23304
|
await gh(contentPutInputArgs(repo, target, tmp));
|
|
23358
23305
|
} finally {
|
|
23359
23306
|
try {
|
|
23360
|
-
(0,
|
|
23307
|
+
(0, import_node_fs27.unlinkSync)(tmp);
|
|
23361
23308
|
} catch {
|
|
23362
23309
|
}
|
|
23363
23310
|
}
|
|
@@ -23609,12 +23556,12 @@ LIVE apply to ${repo}:
|
|
|
23609
23556
|
}
|
|
23610
23557
|
|
|
23611
23558
|
// src/stage-commands.ts
|
|
23612
|
-
var
|
|
23613
|
-
var
|
|
23559
|
+
var import_node_fs29 = require("node:fs");
|
|
23560
|
+
var import_node_path28 = require("node:path");
|
|
23614
23561
|
|
|
23615
23562
|
// src/port-registry.ts
|
|
23616
|
-
var
|
|
23617
|
-
var
|
|
23563
|
+
var import_node_fs28 = require("node:fs");
|
|
23564
|
+
var import_node_path27 = require("node:path");
|
|
23618
23565
|
|
|
23619
23566
|
// ../infra/port-geometry.mjs
|
|
23620
23567
|
var PORT_BLOCK = 100;
|
|
@@ -23628,8 +23575,8 @@ function nextPortBlock(registry2) {
|
|
|
23628
23575
|
return [base, base + PORT_SPAN];
|
|
23629
23576
|
}
|
|
23630
23577
|
function loadPortRegistry(path2) {
|
|
23631
|
-
if (!(0,
|
|
23632
|
-
const raw = JSON.parse((0,
|
|
23578
|
+
if (!(0, import_node_fs28.existsSync)(path2)) return {};
|
|
23579
|
+
const raw = JSON.parse((0, import_node_fs28.readFileSync)(path2, "utf8"));
|
|
23633
23580
|
const out = {};
|
|
23634
23581
|
for (const [key, value] of Object.entries(raw)) {
|
|
23635
23582
|
if (Array.isArray(value) && value.length === 2 && value.every((n) => typeof n === "number")) {
|
|
@@ -23643,9 +23590,9 @@ function ensurePortRange(repo, path2) {
|
|
|
23643
23590
|
const existing = registry2[repo];
|
|
23644
23591
|
if (existing) return existing;
|
|
23645
23592
|
const range = nextPortBlock(registry2);
|
|
23646
|
-
const raw = (0,
|
|
23593
|
+
const raw = (0, import_node_fs28.existsSync)(path2) ? JSON.parse((0, import_node_fs28.readFileSync)(path2, "utf8")) : {};
|
|
23647
23594
|
raw[repo] = range;
|
|
23648
|
-
(0,
|
|
23595
|
+
(0, import_node_fs28.writeFileSync)(path2, JSON.stringify(raw, null, 2) + "\n", "utf8");
|
|
23649
23596
|
return range;
|
|
23650
23597
|
}
|
|
23651
23598
|
function portCursorSeed(registry2) {
|
|
@@ -23667,22 +23614,22 @@ function existingPortRange(repo, registry2) {
|
|
|
23667
23614
|
return registry2[repo] ?? null;
|
|
23668
23615
|
}
|
|
23669
23616
|
function portRangeInfraAt(root, source) {
|
|
23670
|
-
const registryPath = (0,
|
|
23671
|
-
const ddbScriptPath = (0,
|
|
23672
|
-
if (!(0,
|
|
23617
|
+
const registryPath = (0, import_node_path27.join)(root, "infra", "port-ranges.json");
|
|
23618
|
+
const ddbScriptPath = (0, import_node_path27.join)(root, "infra", "port-ddb.mjs");
|
|
23619
|
+
if (!(0, import_node_fs28.existsSync)(registryPath) || !(0, import_node_fs28.existsSync)(ddbScriptPath)) return null;
|
|
23673
23620
|
return { root, source, registryPath, ddbScriptPath };
|
|
23674
23621
|
}
|
|
23675
23622
|
function resolvePortRangeInfra(cwd, packageDir) {
|
|
23676
23623
|
const direct = portRangeInfraAt(cwd, "cwd");
|
|
23677
23624
|
if (direct) return direct;
|
|
23678
|
-
for (let dir = cwd; ; dir = (0,
|
|
23679
|
-
const sibling = portRangeInfraAt((0,
|
|
23625
|
+
for (let dir = cwd; ; dir = (0, import_node_path27.dirname)(dir)) {
|
|
23626
|
+
const sibling = portRangeInfraAt((0, import_node_path27.join)(dir, "MMI-Hub"), "sibling-hub");
|
|
23680
23627
|
if (sibling) return sibling;
|
|
23681
|
-
const parent = (0,
|
|
23628
|
+
const parent = (0, import_node_path27.dirname)(dir);
|
|
23682
23629
|
if (parent === dir) break;
|
|
23683
23630
|
}
|
|
23684
23631
|
if (packageDir) {
|
|
23685
|
-
const pkgRoot = (0,
|
|
23632
|
+
const pkgRoot = (0, import_node_path27.join)(packageDir, "..", "..");
|
|
23686
23633
|
const pkgFrom = portRangeInfraAt(pkgRoot, "pkg-root");
|
|
23687
23634
|
if (pkgFrom) return pkgFrom;
|
|
23688
23635
|
}
|
|
@@ -23858,8 +23805,8 @@ function registerStageCommands(program3) {
|
|
|
23858
23805
|
const portRange = portRangeMeta && typeof portRangeMeta.start === "number" && typeof portRangeMeta.end === "number" ? [portRangeMeta.start, portRangeMeta.end] : void 0;
|
|
23859
23806
|
return decideStage({
|
|
23860
23807
|
registry: { deployModel: project2?.deployModel, portRange, error: read.ok ? void 0 : read.error },
|
|
23861
|
-
hasCompose: (0,
|
|
23862
|
-
hasEnvExample: (0,
|
|
23808
|
+
hasCompose: (0, import_node_fs29.existsSync)((0, import_node_path28.join)(process.cwd(), "docker-compose.yml")),
|
|
23809
|
+
hasEnvExample: (0, import_node_fs29.existsSync)((0, import_node_path28.join)(process.cwd(), ".env.example"))
|
|
23863
23810
|
});
|
|
23864
23811
|
}
|
|
23865
23812
|
async function fetchStageVaultEnvMerge() {
|
|
@@ -24297,10 +24244,10 @@ function registerBoardCommands(program3) {
|
|
|
24297
24244
|
}
|
|
24298
24245
|
|
|
24299
24246
|
// src/merge-cleanup.ts
|
|
24300
|
-
var
|
|
24247
|
+
var import_node_fs30 = require("node:fs");
|
|
24301
24248
|
var import_promises7 = require("node:fs/promises");
|
|
24302
|
-
var
|
|
24303
|
-
var
|
|
24249
|
+
var import_node_path30 = require("node:path");
|
|
24250
|
+
var import_node_os11 = require("node:os");
|
|
24304
24251
|
var import_node_child_process14 = require("node:child_process");
|
|
24305
24252
|
|
|
24306
24253
|
// src/board-advance.ts
|
|
@@ -24387,7 +24334,7 @@ function boardAdvanceFailureMessage(result) {
|
|
|
24387
24334
|
|
|
24388
24335
|
// src/deferred-registry-store.ts
|
|
24389
24336
|
var import_promises6 = require("node:fs/promises");
|
|
24390
|
-
var
|
|
24337
|
+
var import_node_path29 = require("node:path");
|
|
24391
24338
|
var sleep2 = (ms) => new Promise((resolve5) => setTimeout(resolve5, ms));
|
|
24392
24339
|
async function atomicWrite(target, contents) {
|
|
24393
24340
|
const tmp = `${target}.tmp-${process.pid}-${Math.random().toString(36).slice(2)}`;
|
|
@@ -24438,12 +24385,12 @@ function makeDeferredWorktreeStore(registryPath, lockOpts = {}) {
|
|
|
24438
24385
|
},
|
|
24439
24386
|
// Standalone atomic write — THROWS on failure (no best-effort swallow, #2846).
|
|
24440
24387
|
write: async (entries) => {
|
|
24441
|
-
await (0, import_promises6.mkdir)((0,
|
|
24388
|
+
await (0, import_promises6.mkdir)((0, import_node_path29.dirname)(registryPath), { recursive: true });
|
|
24442
24389
|
await atomicWrite(registryPath, serializeDeferredWorktrees(entries));
|
|
24443
24390
|
},
|
|
24444
24391
|
// Serialized read-modify-write under the repo-wide lock (#2846).
|
|
24445
24392
|
update: async (mutate) => {
|
|
24446
|
-
await (0, import_promises6.mkdir)((0,
|
|
24393
|
+
await (0, import_promises6.mkdir)((0, import_node_path29.dirname)(registryPath), { recursive: true });
|
|
24447
24394
|
const deadline = Date.now() + opts.maxWaitMs;
|
|
24448
24395
|
for (; ; ) {
|
|
24449
24396
|
const guard = await acquireLock(lockPath, opts, deadline);
|
|
@@ -24604,7 +24551,7 @@ async function applyGcPlan(plan, remote, opts = {}) {
|
|
|
24604
24551
|
);
|
|
24605
24552
|
const repoRoot2 = (await execFileP2("git", ["rev-parse", "--show-toplevel"], { timeout: GIT_TIMEOUT_MS }).catch(() => ({ stdout: "" }))).stdout.trim() || process.cwd();
|
|
24606
24553
|
const worktreeGitRoot = await currentRepoWorktreeGitRoot(repoRoot2);
|
|
24607
|
-
const primaryRepoRoot = worktreeGitRoot ? (0,
|
|
24554
|
+
const primaryRepoRoot = worktreeGitRoot ? (0, import_node_path30.dirname)((0, import_node_path30.dirname)(worktreeGitRoot)) : repoRoot2;
|
|
24608
24555
|
const gcActor = describeActor({ env: process.env, surface: detectSurface(process.env), cwd: process.cwd() });
|
|
24609
24556
|
const owners = readWorktreeOwners(primaryRepoRoot);
|
|
24610
24557
|
const removalNow = Date.now();
|
|
@@ -24635,7 +24582,7 @@ async function applyGcPlan(plan, remote, opts = {}) {
|
|
|
24635
24582
|
const cleanup = await cleanupPrMergeLocalBranch(branch.branch, {
|
|
24636
24583
|
beforeWorktrees,
|
|
24637
24584
|
startingPath: branch.worktreePath,
|
|
24638
|
-
pathExists: (p) => (0,
|
|
24585
|
+
pathExists: (p) => (0, import_node_fs30.existsSync)(p),
|
|
24639
24586
|
execGit: async (args) => (await execFileP2("git", args, { timeout: GIT_TIMEOUT_MS })).stdout,
|
|
24640
24587
|
teardownWorktreeStage,
|
|
24641
24588
|
deferredStore,
|
|
@@ -24663,7 +24610,7 @@ async function applyGcPlan(plan, remote, opts = {}) {
|
|
|
24663
24610
|
for (const wt of worktreeDirsToRemove) {
|
|
24664
24611
|
try {
|
|
24665
24612
|
const cleanupTarget = resolveSafeSiblingWorktreeCleanupTarget(wt.path, siblingRoot, {
|
|
24666
|
-
realpath: (path2) => (0,
|
|
24613
|
+
realpath: (path2) => (0, import_node_fs30.realpathSync)(path2)
|
|
24667
24614
|
});
|
|
24668
24615
|
if (!cleanupTarget.ok) {
|
|
24669
24616
|
result.failed.push(`${wt.path}: ${cleanupTarget.reason}`);
|
|
@@ -24728,13 +24675,13 @@ async function composeOverrideBodyFile(prNumber, repoArgs, gh) {
|
|
|
24728
24675
|
const commits = JSON.parse(raw).commits ?? [];
|
|
24729
24676
|
const body = squashBodyWithOverride(commits.map((c) => ({ headline: c.messageHeadline ?? "", body: c.messageBody ?? "" })), process.cwd());
|
|
24730
24677
|
if (!body) return void 0;
|
|
24731
|
-
const dir = (0,
|
|
24732
|
-
const path2 = (0,
|
|
24733
|
-
(0,
|
|
24678
|
+
const dir = (0, import_node_fs30.mkdtempSync)((0, import_node_path30.join)((0, import_node_os11.tmpdir)(), "mmi-squash-body-"));
|
|
24679
|
+
const path2 = (0, import_node_path30.join)(dir, "body.txt");
|
|
24680
|
+
(0, import_node_fs30.writeFileSync)(path2, `${body}
|
|
24734
24681
|
`, "utf8");
|
|
24735
24682
|
return { path: path2, cleanup: () => {
|
|
24736
24683
|
try {
|
|
24737
|
-
(0,
|
|
24684
|
+
(0, import_node_fs30.rmSync)(dir, { recursive: true, force: true });
|
|
24738
24685
|
} catch {
|
|
24739
24686
|
}
|
|
24740
24687
|
} };
|
|
@@ -24856,13 +24803,13 @@ var realWorktreeDirRemover = {
|
|
|
24856
24803
|
probe: (p) => {
|
|
24857
24804
|
let st;
|
|
24858
24805
|
try {
|
|
24859
|
-
st = (0,
|
|
24806
|
+
st = (0, import_node_fs30.lstatSync)(p);
|
|
24860
24807
|
} catch {
|
|
24861
24808
|
return null;
|
|
24862
24809
|
}
|
|
24863
24810
|
if (st.isSymbolicLink()) return "link";
|
|
24864
24811
|
try {
|
|
24865
|
-
(0,
|
|
24812
|
+
(0, import_node_fs30.readlinkSync)(p);
|
|
24866
24813
|
return "link";
|
|
24867
24814
|
} catch {
|
|
24868
24815
|
}
|
|
@@ -24870,7 +24817,7 @@ var realWorktreeDirRemover = {
|
|
|
24870
24817
|
},
|
|
24871
24818
|
readdir: (p) => {
|
|
24872
24819
|
try {
|
|
24873
|
-
return (0,
|
|
24820
|
+
return (0, import_node_fs30.readdirSync)(p);
|
|
24874
24821
|
} catch {
|
|
24875
24822
|
return [];
|
|
24876
24823
|
}
|
|
@@ -24879,9 +24826,9 @@ var realWorktreeDirRemover = {
|
|
|
24879
24826
|
// leaving the target); a file symlink with unlink. rmdir first, fall back to unlink.
|
|
24880
24827
|
detachLink: (p) => {
|
|
24881
24828
|
try {
|
|
24882
|
-
(0,
|
|
24829
|
+
(0, import_node_fs30.rmdirSync)(p);
|
|
24883
24830
|
} catch {
|
|
24884
|
-
(0,
|
|
24831
|
+
(0, import_node_fs30.unlinkSync)(p);
|
|
24885
24832
|
}
|
|
24886
24833
|
},
|
|
24887
24834
|
removeTree: (p) => (0, import_promises7.rm)(p, { recursive: true, force: true, maxRetries: 5, retryDelay: 200 })
|
|
@@ -24914,9 +24861,9 @@ async function worktreeHasStageState(worktreePath) {
|
|
|
24914
24861
|
}
|
|
24915
24862
|
}
|
|
24916
24863
|
function stageStateFileBelongsToWorktree(statePath, worktreePath) {
|
|
24917
|
-
if (!(0,
|
|
24864
|
+
if (!(0, import_node_fs30.existsSync)(statePath)) return false;
|
|
24918
24865
|
try {
|
|
24919
|
-
const state = JSON.parse((0,
|
|
24866
|
+
const state = JSON.parse((0, import_node_fs30.readFileSync)(statePath, "utf8"));
|
|
24920
24867
|
const recordedCwd = typeof state.identity?.cwd === "string" ? state.identity.cwd : typeof state.cwd === "string" ? state.cwd : "";
|
|
24921
24868
|
return Boolean(recordedCwd && isPathUnderDirectory(recordedCwd, worktreePath));
|
|
24922
24869
|
} catch {
|
|
@@ -24939,8 +24886,25 @@ function teardownWorktreeStage(worktreePath) {
|
|
|
24939
24886
|
|
|
24940
24887
|
// src/pr-checks-rest.ts
|
|
24941
24888
|
var REST_GH_TIMEOUT_MS = 2e4;
|
|
24889
|
+
var pollTokenProvider;
|
|
24890
|
+
var pollTokenOnce;
|
|
24891
|
+
function setPollTokenProvider(provider) {
|
|
24892
|
+
pollTokenProvider = provider;
|
|
24893
|
+
pollTokenOnce = void 0;
|
|
24894
|
+
}
|
|
24895
|
+
async function pollToken() {
|
|
24896
|
+
if (!pollTokenProvider) return void 0;
|
|
24897
|
+
pollTokenOnce ??= pollTokenProvider().catch(() => void 0);
|
|
24898
|
+
return pollTokenOnce;
|
|
24899
|
+
}
|
|
24942
24900
|
async function defaultGhApi(args) {
|
|
24943
|
-
const
|
|
24901
|
+
const token = await pollToken();
|
|
24902
|
+
const { stdout } = await execFileP2("gh", ["api", ...args], {
|
|
24903
|
+
timeout: REST_GH_TIMEOUT_MS,
|
|
24904
|
+
// Only when a token was actually minted: passing `env` at all would otherwise replace the
|
|
24905
|
+
// inherited environment wholesale, and `gh` needs the rest of it (PATH, GH_HOST, config home).
|
|
24906
|
+
...token ? { env: { ...process.env, GH_TOKEN: token } } : {}
|
|
24907
|
+
});
|
|
24944
24908
|
return stdout;
|
|
24945
24909
|
}
|
|
24946
24910
|
function classifyCheckRun(run) {
|
|
@@ -25123,9 +25087,9 @@ async function fetchRestCorePool(gh = defaultGhApi) {
|
|
|
25123
25087
|
}
|
|
25124
25088
|
|
|
25125
25089
|
// src/worktree-lifecycle-commands.ts
|
|
25126
|
-
var
|
|
25090
|
+
var import_node_fs31 = require("node:fs");
|
|
25127
25091
|
var import_promises8 = require("node:fs/promises");
|
|
25128
|
-
var
|
|
25092
|
+
var import_node_path31 = require("node:path");
|
|
25129
25093
|
var GH_TIMEOUT_MS = 2e4;
|
|
25130
25094
|
var DEFAULT_BASE = "origin/development";
|
|
25131
25095
|
var DEFAULT_REMOTE = "origin";
|
|
@@ -25197,6 +25161,16 @@ function orphanLandSteps(lostBranch, hasStage) {
|
|
|
25197
25161
|
steps.push("prune worktree metadata");
|
|
25198
25162
|
return steps;
|
|
25199
25163
|
}
|
|
25164
|
+
function detachedLandSteps(hasStage) {
|
|
25165
|
+
const steps = [];
|
|
25166
|
+
if (hasStage) steps.push("stop spawned dev stage");
|
|
25167
|
+
steps.push("remove worktree (detached HEAD)");
|
|
25168
|
+
steps.push("prune worktree metadata");
|
|
25169
|
+
return steps;
|
|
25170
|
+
}
|
|
25171
|
+
function shouldContinueLandCleanup(removeStatus) {
|
|
25172
|
+
return removeStatus === "removed";
|
|
25173
|
+
}
|
|
25200
25174
|
function classifyStaleLeaks(input) {
|
|
25201
25175
|
const protectedBranches = input.protectedBranches ?? PROTECTED_BRANCHES2;
|
|
25202
25176
|
const leaks = [];
|
|
@@ -25261,7 +25235,7 @@ function classifyStaleLeaks(input) {
|
|
|
25261
25235
|
var defaultOrphanDirScanDeps = {
|
|
25262
25236
|
listDirs: (root) => {
|
|
25263
25237
|
try {
|
|
25264
|
-
return (0,
|
|
25238
|
+
return (0, import_node_fs31.readdirSync)(root, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => (0, import_node_path31.join)(root, e.name));
|
|
25265
25239
|
} catch {
|
|
25266
25240
|
return [];
|
|
25267
25241
|
}
|
|
@@ -25406,15 +25380,21 @@ function registerWorktreeCommands(program3) {
|
|
|
25406
25380
|
try {
|
|
25407
25381
|
const wtPath = (await execFileP2("git", ["rev-parse", "--show-toplevel"], { timeout: GIT_TIMEOUT_MS })).stdout.trim();
|
|
25408
25382
|
const headBorn = await execFileP2("git", ["-C", wtPath || ".", "rev-parse", "--verify", "--quiet", "HEAD"], { timeout: GIT_TIMEOUT_MS }).then(() => true).catch(() => false);
|
|
25409
|
-
const
|
|
25383
|
+
const symbolicBranch = (await execFileP2(
|
|
25384
|
+
"git",
|
|
25385
|
+
["-C", wtPath || ".", "symbolic-ref", "--quiet", "--short", "HEAD"],
|
|
25386
|
+
{ timeout: GIT_TIMEOUT_MS }
|
|
25387
|
+
).catch(() => ({ stdout: "" }))).stdout.trim();
|
|
25388
|
+
const detached = headBorn && !symbolicBranch;
|
|
25389
|
+
const branch = symbolicBranch || (detached ? "HEAD" : "");
|
|
25410
25390
|
if (!wtPath || !branch) return fail("worktree land: not inside a git worktree");
|
|
25411
|
-
const gitFile = (0,
|
|
25412
|
-
const isLinked = (0,
|
|
25391
|
+
const gitFile = (0, import_node_path31.join)(wtPath, ".git");
|
|
25392
|
+
const isLinked = (0, import_node_fs31.existsSync)(gitFile) && (0, import_node_fs31.statSync)(gitFile).isFile();
|
|
25413
25393
|
if (apply && !isLinked) {
|
|
25414
25394
|
return fail("worktree land: run from inside the linked worktree you want to land (this is the primary checkout)");
|
|
25415
25395
|
}
|
|
25416
25396
|
const commonDir = (await execFileP2("git", ["rev-parse", "--git-common-dir"], { timeout: GIT_TIMEOUT_MS }).catch(() => ({ stdout: "" }))).stdout.trim();
|
|
25417
|
-
const primaryCheckout = commonDir ? (0,
|
|
25397
|
+
const primaryCheckout = commonDir ? (0, import_node_path31.dirname)(commonDir) : wtPath;
|
|
25418
25398
|
const localBranchNames = await execFileP2("git", ["-C", primaryCheckout, "for-each-ref", "--format=%(refname:short)", "refs/heads"], { timeout: GIT_TIMEOUT_MS }).then(({ stdout }) => new Set((stdout || "").split("\n").map((l) => l.trim()).filter(Boolean))).catch(() => void 0);
|
|
25419
25399
|
const orphan = classifyOrphanedWorktree({
|
|
25420
25400
|
branch,
|
|
@@ -25427,7 +25407,7 @@ function registerWorktreeCommands(program3) {
|
|
|
25427
25407
|
}
|
|
25428
25408
|
const stageSummary = readStageSummary(wtPath);
|
|
25429
25409
|
const hasStage = stageSummary != null;
|
|
25430
|
-
const steps = orphan.orphan ? orphanLandSteps(orphan.lostBranch, hasStage) : landSteps(branch, true, hasStage);
|
|
25410
|
+
const steps = detached ? detachedLandSteps(hasStage) : orphan.orphan ? orphanLandSteps(orphan.lostBranch, hasStage) : landSteps(branch, true, hasStage);
|
|
25431
25411
|
const plan = {
|
|
25432
25412
|
command: "worktree land",
|
|
25433
25413
|
branch,
|
|
@@ -25436,13 +25416,15 @@ function registerWorktreeCommands(program3) {
|
|
|
25436
25416
|
remote: o.remote,
|
|
25437
25417
|
hasStage,
|
|
25438
25418
|
steps,
|
|
25439
|
-
...orphan.orphan ? { orphan: { kind: orphan.kind, lostBranch: orphan.lostBranch, reason: orphan.reason } } : {}
|
|
25419
|
+
...orphan.orphan ? { orphan: { kind: orphan.kind, lostBranch: orphan.lostBranch, reason: orphan.reason } } : {},
|
|
25420
|
+
...detached ? { detached: true } : {}
|
|
25440
25421
|
};
|
|
25441
25422
|
if (!apply) {
|
|
25442
25423
|
const preview = { dryRun: true, ...plan, ...o.keepRemote ? { keepRemote: true } : {} };
|
|
25443
25424
|
if (o.json) return console.log(JSON.stringify(preview, null, 2));
|
|
25444
25425
|
const lines = [`worktree land: dry-run (pass --apply to execute)`, ` branch: ${branch}`, ` worktree: ${wtPath}`];
|
|
25445
|
-
if (
|
|
25426
|
+
if (detached) lines.push(" detached HEAD: no branch refs will be deleted");
|
|
25427
|
+
else if (orphan.orphan) lines.push(` orphaned: ${orphan.reason} \u2014 no branch refs will be deleted`);
|
|
25446
25428
|
if (hasStage) lines.push(` stage: port ${stageSummary.port} (will be stopped)`);
|
|
25447
25429
|
if (o.keepRemote && !orphan.orphan) lines.push(` remote branch: kept (--keep-remote)`);
|
|
25448
25430
|
for (const s of steps) lines.push(` - ${s}`);
|
|
@@ -25464,15 +25446,16 @@ function registerWorktreeCommands(program3) {
|
|
|
25464
25446
|
unreferenced: await isCommitUnreferenced(orphanTip, async (args) => (await execFileP2("git", ["-C", primaryCheckout, ...args], { timeout: GIT_TIMEOUT_MS })).stdout),
|
|
25465
25447
|
recoverCommand: `git -C "${primaryCheckout}" branch <name> ${orphanTip}`
|
|
25466
25448
|
} : void 0;
|
|
25467
|
-
const
|
|
25449
|
+
const treeOnly = orphan.orphan || detached;
|
|
25450
|
+
const landPrs = treeOnly ? void 0 : await fetchLandBranchPrs(branch);
|
|
25468
25451
|
const mergeVerdict = classifyLandBranchMergeState(landPrs);
|
|
25469
|
-
const landRefs =
|
|
25452
|
+
const landRefs = treeOnly ? { action: "proceed" } : decideWorktreeLandRefCleanup({ branch, prs: landPrs, keepRemote: Boolean(o.keepRemote) });
|
|
25470
25453
|
if (landRefs.action === "refuse") {
|
|
25471
25454
|
return fail(`worktree land: ${landRefs.message}`, { code: ERROR_CODES.ERR_BAD_ENUM });
|
|
25472
25455
|
}
|
|
25473
25456
|
const keepRemoteBranch = Boolean(o.keepRemote) || landRefs.action === "keep-remote";
|
|
25474
25457
|
if (landRefs.action === "keep-remote") console.warn(`worktree land: ${landRefs.message}.`);
|
|
25475
|
-
const reportedMergeState =
|
|
25458
|
+
const reportedMergeState = treeOnly ? "not-checked" : mergeVerdict.state;
|
|
25476
25459
|
const landOwner = lookupWorktreeOwner(primaryCheckout, wtPath);
|
|
25477
25460
|
const report = [];
|
|
25478
25461
|
if (hasStage) {
|
|
@@ -25492,6 +25475,24 @@ function registerWorktreeCommands(program3) {
|
|
|
25492
25475
|
step: "remove worktree",
|
|
25493
25476
|
status: removeOutcome.status === "removed" ? removeOutcome.recovery ? `done (${removeOutcome.recovery})` : "done" : `failed: ${removeOutcome.error ?? "lock held"} \u2014 run: git -C "${primaryCheckout}" worktree remove --force "${wtPath}"`
|
|
25494
25477
|
});
|
|
25478
|
+
if (!shouldContinueLandCleanup(removeOutcome.status)) {
|
|
25479
|
+
const result2 = {
|
|
25480
|
+
dryRun: false,
|
|
25481
|
+
...plan,
|
|
25482
|
+
...o.keepRemote ? { keepRemote: true } : {},
|
|
25483
|
+
mergeState: reportedMergeState,
|
|
25484
|
+
prNumbers: mergeVerdict.numbers,
|
|
25485
|
+
cleanupState: "partial",
|
|
25486
|
+
report
|
|
25487
|
+
};
|
|
25488
|
+
if (o.json) console.log(JSON.stringify(result2, null, 2));
|
|
25489
|
+
else {
|
|
25490
|
+
console.error(`worktree land: cleanup stopped after the worktree removal failed; branch refs and metadata were left untouched`);
|
|
25491
|
+
for (const row of report) console.log(` ${row.step}: ${row.status}`);
|
|
25492
|
+
}
|
|
25493
|
+
process.exitCode = 1;
|
|
25494
|
+
return;
|
|
25495
|
+
}
|
|
25495
25496
|
const landActor = describeActor({ env: process.env, surface: detectSurface(process.env), cwd: wtPath });
|
|
25496
25497
|
appendWorktreeEvent(primaryCheckout, {
|
|
25497
25498
|
action: removeOutcome.status === "removed" ? "removed" : "failed",
|
|
@@ -25506,8 +25507,11 @@ function registerWorktreeCommands(program3) {
|
|
|
25506
25507
|
reason: orphan.orphan ? `orphaned worktree \u2014 ${orphan.reason}${lastCommit ? `; last commit ${lastCommit.oid}${lastCommit.unreferenced ? " (on no ref)" : ""}` : ""}` : describeLandMergeState(mergeVerdict)
|
|
25507
25508
|
});
|
|
25508
25509
|
if (removeOutcome.status === "removed") dropWorktreeOwner(primaryCheckout, wtPath);
|
|
25509
|
-
if (
|
|
25510
|
-
report.push({
|
|
25510
|
+
if (treeOnly) {
|
|
25511
|
+
report.push({
|
|
25512
|
+
step: "delete branch refs",
|
|
25513
|
+
status: detached ? "skipped: detached HEAD has no branch refs" : `skipped: orphaned worktree \u2014 ${orphan.reason}`
|
|
25514
|
+
});
|
|
25511
25515
|
if (lastCommit) {
|
|
25512
25516
|
report.push({
|
|
25513
25517
|
step: "last commit",
|
|
@@ -25535,12 +25539,12 @@ function registerWorktreeCommands(program3) {
|
|
|
25535
25539
|
report
|
|
25536
25540
|
};
|
|
25537
25541
|
if (o.json) return console.log(JSON.stringify(result, null, 2));
|
|
25538
|
-
console.log(
|
|
25542
|
+
console.log(treeOnly ? `worktree land: removed ${detached ? "detached" : "orphaned"} worktree ${wtPath}${detached ? "" : ` (${orphan.reason})`}; no branch refs touched` : `worktree land: cleaned up branch ${branch} (${describeLandMergeState(mergeVerdict)})`);
|
|
25539
25543
|
for (const r of report) console.log(` ${r.step}: ${r.status}`);
|
|
25540
25544
|
if (lastCommit?.unreferenced) {
|
|
25541
25545
|
console.warn(`worktree land: commit ${lastCommit.oid} was on no ref but this worktree's reflog \u2014 normal after a squash merge, but if that work was NOT merged, recover it now: ${lastCommit.recoverCommand}`);
|
|
25542
25546
|
}
|
|
25543
|
-
if (!
|
|
25547
|
+
if (!treeOnly && mergeVerdict.state !== "merged") {
|
|
25544
25548
|
console.warn(`worktree land: '${branch}' was cleaned up but NOT merged \u2014 ${describeLandMergeState(mergeVerdict)}. Do not report this work as shipped.`);
|
|
25545
25549
|
}
|
|
25546
25550
|
if (report.some((r) => r.status.startsWith("failed"))) process.exitCode = 1;
|
|
@@ -25601,10 +25605,10 @@ async function gatherWorktreeContext() {
|
|
|
25601
25605
|
if (s) stages.push({ path: wt.path, port: s.port });
|
|
25602
25606
|
}
|
|
25603
25607
|
const worktreeGitRoot = await currentRepoWorktreeGitRoot(repoRoot2);
|
|
25604
|
-
const primaryRepoRoot = worktreeGitRoot ? (0,
|
|
25608
|
+
const primaryRepoRoot = worktreeGitRoot ? (0, import_node_path31.dirname)((0, import_node_path31.dirname)(worktreeGitRoot)) : repoRoot2;
|
|
25605
25609
|
const wtRoot = siblingMmiWorktreesRoot(primaryRepoRoot);
|
|
25606
25610
|
let orphanDirs = [];
|
|
25607
|
-
if ((0,
|
|
25611
|
+
if ((0, import_node_fs31.existsSync)(wtRoot)) {
|
|
25608
25612
|
orphanDirs = scanOrphanDirs(wtRoot, worktreeGitRoot, {
|
|
25609
25613
|
...defaultOrphanDirScanDeps,
|
|
25610
25614
|
listDirs: (root) => worktreeScanDirs(root, primaryRepoRoot, defaultOrphanDirScanDeps.listDirs, isRepoCheckoutDir)
|
|
@@ -25627,7 +25631,7 @@ async function bestEffortGit(args, cwd, step, timeoutMs = GIT_TIMEOUT_MS) {
|
|
|
25627
25631
|
}
|
|
25628
25632
|
|
|
25629
25633
|
// src/issue-commands.ts
|
|
25630
|
-
var
|
|
25634
|
+
var import_node_fs32 = require("node:fs");
|
|
25631
25635
|
var import_node_crypto5 = require("node:crypto");
|
|
25632
25636
|
var ghRunner = async (args, timeoutMs) => (await execFileP2("gh", args, { timeout: timeoutMs })).stdout;
|
|
25633
25637
|
var ReparentConflictError = class extends Error {
|
|
@@ -25645,7 +25649,7 @@ async function editIssue(client, options, deps = {}) {
|
|
|
25645
25649
|
const url = `https://github.com/${repo}/issues/${parsed.number}`;
|
|
25646
25650
|
const patch = {};
|
|
25647
25651
|
let bodyChanged = false;
|
|
25648
|
-
const textDeps = () => deps.textDeps ?? { readFile: (p, e) => Promise.resolve((0,
|
|
25652
|
+
const textDeps = () => deps.textDeps ?? { readFile: (p, e) => Promise.resolve((0, import_node_fs32.readFileSync)(p, e)), readStdin: () => Promise.resolve("") };
|
|
25649
25653
|
if (options.titleFile !== void 0) {
|
|
25650
25654
|
patch.title = await resolveIssueTitle({ title: options.title, titleFile: options.titleFile }, textDeps());
|
|
25651
25655
|
} else if (options.title !== void 0) {
|
|
@@ -26208,7 +26212,7 @@ function extendCreateCommand(issue2, batchAttach) {
|
|
|
26208
26212
|
if (opts.batch) {
|
|
26209
26213
|
let specs;
|
|
26210
26214
|
try {
|
|
26211
|
-
const raw = (0,
|
|
26215
|
+
const raw = (0, import_node_fs32.readFileSync)(opts.batch, "utf8");
|
|
26212
26216
|
specs = JSON.parse(raw);
|
|
26213
26217
|
if (!Array.isArray(specs)) throw new Error("batch file must contain a JSON array");
|
|
26214
26218
|
} catch (e) {
|
|
@@ -26282,8 +26286,8 @@ ${lines}`, {
|
|
|
26282
26286
|
}
|
|
26283
26287
|
|
|
26284
26288
|
// src/train-commands.ts
|
|
26285
|
-
var
|
|
26286
|
-
var
|
|
26289
|
+
var import_node_fs33 = require("node:fs");
|
|
26290
|
+
var import_node_path32 = require("node:path");
|
|
26287
26291
|
|
|
26288
26292
|
// src/train-status.ts
|
|
26289
26293
|
function buildTrainStatusReport(input) {
|
|
@@ -26323,7 +26327,7 @@ function formatTrainStatus(r) {
|
|
|
26323
26327
|
// src/train-commands.ts
|
|
26324
26328
|
function readRepoVersion() {
|
|
26325
26329
|
try {
|
|
26326
|
-
return JSON.parse((0,
|
|
26330
|
+
return JSON.parse((0, import_node_fs33.readFileSync)((0, import_node_path32.join)(process.cwd(), ".claude-plugin", "plugin.json"), "utf8")).version || void 0;
|
|
26327
26331
|
} catch {
|
|
26328
26332
|
return void 0;
|
|
26329
26333
|
}
|
|
@@ -26469,11 +26473,12 @@ function registerDeployCommands(program3) {
|
|
|
26469
26473
|
}
|
|
26470
26474
|
|
|
26471
26475
|
// src/discovery-commands.ts
|
|
26472
|
-
var
|
|
26473
|
-
var
|
|
26474
|
-
var
|
|
26476
|
+
var import_node_fs34 = require("node:fs");
|
|
26477
|
+
var import_node_os12 = require("node:os");
|
|
26478
|
+
var import_node_path33 = require("node:path");
|
|
26475
26479
|
var GC_GH_TIMEOUT_MS3 = 2e4;
|
|
26476
26480
|
async function collectStatus() {
|
|
26481
|
+
const repo = await resolveRepo();
|
|
26477
26482
|
let branch = "";
|
|
26478
26483
|
try {
|
|
26479
26484
|
branch = (await execFileP2("git", ["rev-parse", "--abbrev-ref", "HEAD"], { timeout: GIT_TIMEOUT_MS })).stdout.trim();
|
|
@@ -26502,7 +26507,7 @@ async function collectStatus() {
|
|
|
26502
26507
|
}
|
|
26503
26508
|
let claimedItems = [];
|
|
26504
26509
|
try {
|
|
26505
|
-
const cfg = await
|
|
26510
|
+
const cfg = await loadConfigOrDiscover();
|
|
26506
26511
|
if (cfg.sagaApiUrl) {
|
|
26507
26512
|
const report = await readBoard({ config: cfg });
|
|
26508
26513
|
claimedItems = report.primary.userOwned.map((item) => ({
|
|
@@ -26525,7 +26530,7 @@ async function collectStatus() {
|
|
|
26525
26530
|
} catch {
|
|
26526
26531
|
stage = { running: false };
|
|
26527
26532
|
}
|
|
26528
|
-
return { branch, worktrees, myOpenPrs, claimedItems, stage };
|
|
26533
|
+
return { repo, branch, worktrees, myOpenPrs, claimedItems, stage };
|
|
26529
26534
|
}
|
|
26530
26535
|
var PRIORITY_RANK = {
|
|
26531
26536
|
Urgent: 0,
|
|
@@ -26533,14 +26538,14 @@ var PRIORITY_RANK = {
|
|
|
26533
26538
|
Medium: 2,
|
|
26534
26539
|
Low: 3
|
|
26535
26540
|
};
|
|
26536
|
-
async function recommendNext(deps) {
|
|
26537
|
-
const load = deps?.loadConfig ??
|
|
26541
|
+
async function recommendNext(repo, deps) {
|
|
26542
|
+
const load = deps?.loadConfig ?? loadConfigForRepo;
|
|
26538
26543
|
const reader = deps?.readBoard ?? readBoard;
|
|
26539
|
-
const cfg = await load();
|
|
26544
|
+
const cfg = await load(repo);
|
|
26540
26545
|
if (!cfg.sagaApiUrl) throw new Error("Hub API URL not configured \u2014 the board was NOT read (run `mmi-cli doctor`)");
|
|
26541
26546
|
let report;
|
|
26542
26547
|
try {
|
|
26543
|
-
report = await reader({ config: cfg });
|
|
26548
|
+
report = await reader({ config: cfg, repo });
|
|
26544
26549
|
} catch (e) {
|
|
26545
26550
|
throw new Error(
|
|
26546
26551
|
`board unreachable \u2014 ${e.message}. This is NOT an empty board: confirm the ACTIVE gh account can see this repo (\`gh auth status\`, \`gh repo view <owner/repo>\`; \`gh auth switch --user <other>\` when more than one account is present).`
|
|
@@ -26646,10 +26651,10 @@ async function collectOnboardStatus() {
|
|
|
26646
26651
|
else if (top) nextCommand = `mmi-cli board claim ${top.number} # ${top.title}`;
|
|
26647
26652
|
else nextCommand = "mmi-cli board read \u2014 no claimable items found";
|
|
26648
26653
|
}
|
|
26649
|
-
const home = (0,
|
|
26654
|
+
const home = (0, import_node_os12.homedir)();
|
|
26650
26655
|
const plugin = onboardPluginGate({
|
|
26651
|
-
readKnown: () => readFileSyncSafe((0,
|
|
26652
|
-
readSettings: () => readFileSyncSafe((0,
|
|
26656
|
+
readKnown: () => readFileSyncSafe((0, import_node_path33.join)(home, ...KNOWN_MARKETPLACES_RELATIVE), import_node_fs34.readFileSync),
|
|
26657
|
+
readSettings: () => readFileSyncSafe((0, import_node_path33.join)(home, ".claude", "settings.json"), import_node_fs34.readFileSync)
|
|
26653
26658
|
});
|
|
26654
26659
|
return { track, board, registry: registry2, secrets, plugin, nextCommand };
|
|
26655
26660
|
}
|
|
@@ -26660,6 +26665,7 @@ function registerDiscoveryCommands(program3) {
|
|
|
26660
26665
|
if (o.json) {
|
|
26661
26666
|
console.log(JSON.stringify(report, null, 2));
|
|
26662
26667
|
} else {
|
|
26668
|
+
console.log(`repo: ${report.repo ?? "unknown"}`);
|
|
26663
26669
|
console.log(`branch: ${report.branch}`);
|
|
26664
26670
|
console.log(`worktrees: ${report.worktrees.length} linked`);
|
|
26665
26671
|
console.log(`my open PRs: ${report.myOpenPrs.length}`);
|
|
@@ -26670,9 +26676,9 @@ function registerDiscoveryCommands(program3) {
|
|
|
26670
26676
|
fail(`status: ${e.message}`);
|
|
26671
26677
|
}
|
|
26672
26678
|
});
|
|
26673
|
-
program3.command("next").description("recommend the next actionable board item (claimable, unblocked, priority-ranked)").action(async () => {
|
|
26679
|
+
program3.command("next").description("recommend the next actionable board item (claimable, unblocked, priority-ranked)").option("--repo <owner/repo>", "current repo (defaults to git origin)").action(async (o) => {
|
|
26674
26680
|
try {
|
|
26675
|
-
const item = await recommendNext();
|
|
26681
|
+
const item = await recommendNext(o.repo);
|
|
26676
26682
|
if (!item) {
|
|
26677
26683
|
console.log("No claimable board items found (board read OK).");
|
|
26678
26684
|
return;
|
|
@@ -28235,7 +28241,7 @@ function planSurfaceRepair(diagnosis) {
|
|
|
28235
28241
|
owner: "mmi-cli",
|
|
28236
28242
|
command: "mmi-cli plugin heal",
|
|
28237
28243
|
changes: ["replace the active MMI plugin installation", `reload via ${descriptor.reload}`],
|
|
28238
|
-
instruction: `run \`mmi-cli doctor
|
|
28244
|
+
instruction: `run \`mmi-cli doctor\` (or \`mmi-cli plugin heal\`), then ${reloadInstruction(descriptor)}`
|
|
28239
28245
|
};
|
|
28240
28246
|
}
|
|
28241
28247
|
function buildSurfaceDoctorCheck(diagnosis) {
|
|
@@ -28341,34 +28347,6 @@ function checkRepoWorktrees(probe) {
|
|
|
28341
28347
|
]
|
|
28342
28348
|
};
|
|
28343
28349
|
}
|
|
28344
|
-
function formatBytes(bytes, partial) {
|
|
28345
|
-
const prefix = partial ? "\u2265" : "";
|
|
28346
|
-
return bytes >= 1e9 ? `${prefix}${(bytes / 1e9).toFixed(2)} GB` : `${prefix}${(bytes / 1e6).toFixed(1)} MB`;
|
|
28347
|
-
}
|
|
28348
|
-
function checkWorktreeRoots(probe) {
|
|
28349
|
-
if (!probe) return null;
|
|
28350
|
-
const evidence = [
|
|
28351
|
-
`scanned by worktree gc/list: ${probe.authoritative}`,
|
|
28352
|
-
...probe.stray.length ? probe.stray.map((s) => `unreachable by any gc: ${s.path} \u2014 ${s.dirs} dir(s), ${formatBytes(s.bytes, s.partial)}`) : ["no other *worktrees* directory beside this repo"]
|
|
28353
|
-
];
|
|
28354
|
-
if (!probe.stray.length) return { ok: true, id: "worktree-roots", label: "worktree roots", verbose: evidence };
|
|
28355
|
-
const named = probe.stray.map((s) => `${s.path} (${s.dirs} dir(s), ${formatBytes(s.bytes, s.partial)})`).join("; ");
|
|
28356
|
-
const cutShort = probe.stray.some((s) => s.partial) ? " (a \u2265 figure is a floor \u2014 the size walk hit its time budget, so the real total is larger)" : "";
|
|
28357
|
-
return {
|
|
28358
|
-
ok: false,
|
|
28359
|
-
id: "worktree-roots",
|
|
28360
|
-
label: "worktree roots",
|
|
28361
|
-
// #3485: report-only in EVERY mode means report-only in the exit code too. `worktree gc --root` does
|
|
28362
|
-
// exist and is the eventual fix, but this row is deliberately not a gate: the remedy is a human
|
|
28363
|
-
// review of directories that may hold unlanded work, and a size-based auto-sweep once nearly deleted
|
|
28364
|
-
// an unlanded correctness fix. Counting it made `mmi-cli doctor` exit 1 until someone swept by hand.
|
|
28365
|
-
reportOnly: true,
|
|
28366
|
-
// The fix is a review, never a delete: `gc --root` still refuses anything it cannot classify as dead,
|
|
28367
|
-
// and doctor itself — with or without --apply — never touches these directories.
|
|
28368
|
-
fix: `${probe.stray.length} worktrees root(s) outside ${probe.authoritative} \u2014 ${named}; no gc scans them. Review, then sweep deliberately from the owning repo with \`mmi-cli worktree gc --root <path> --apply\` (report-only here: doctor never deletes these)${cutShort}`,
|
|
28369
|
-
verbose: evidence
|
|
28370
|
-
};
|
|
28371
|
-
}
|
|
28372
28350
|
function checkCodexHookTrust(probe, displayName = "Codex") {
|
|
28373
28351
|
if (!probe?.applicable) return null;
|
|
28374
28352
|
const evidence = [
|
|
@@ -28479,81 +28457,6 @@ function checkTrainSync(result) {
|
|
|
28479
28457
|
verbose: evidence
|
|
28480
28458
|
};
|
|
28481
28459
|
}
|
|
28482
|
-
function checkSchedules(probe) {
|
|
28483
|
-
if (!probe) return null;
|
|
28484
|
-
const evidence = [
|
|
28485
|
-
`${probe.armed} armed (${probe.live} live, ${probe.declared} declared)`,
|
|
28486
|
-
...probe.incomplete.map((i) => `incomplete: ${i}`),
|
|
28487
|
-
...probe.drift.map((d) => `drift: ${d}`)
|
|
28488
|
-
];
|
|
28489
|
-
const clearableHere = probe.clearableHere ?? 0;
|
|
28490
|
-
if (probe.incomplete.length) {
|
|
28491
|
-
return {
|
|
28492
|
-
ok: false,
|
|
28493
|
-
id: "schedules",
|
|
28494
|
-
label: "schedules",
|
|
28495
|
-
// #3485: ruled report-only — an unreadable source is a read failure somewhere in the org, and
|
|
28496
|
-
// nothing in this checkout clears it.
|
|
28497
|
-
reportOnly: true,
|
|
28498
|
-
detail: `${probe.incomplete.length} source(s) unreadable`,
|
|
28499
|
-
fix: "run `mmi-cli org schedules` for the full report \u2014 the notebook may be missing armed entries",
|
|
28500
|
-
verbose: evidence
|
|
28501
|
-
};
|
|
28502
|
-
}
|
|
28503
|
-
if (probe.drift.length) {
|
|
28504
|
-
return {
|
|
28505
|
-
ok: false,
|
|
28506
|
-
id: "schedules",
|
|
28507
|
-
label: "schedules",
|
|
28508
|
-
// #3485 ruled this row report-only because the drift classes are owned by other repos. #3492 makes
|
|
28509
|
-
// that conditional rather than blanket: when a finding IS clearable from this checkout — a
|
|
28510
|
-
// `file-vs-registry-stale` row for this repo, one `org schedules register` away — the row gates
|
|
28511
|
-
// like any other actionable red. Exempting a fault the operator can fix in one command is the
|
|
28512
|
-
// tolerated-red failure the tier exists to prevent, not an instance of it.
|
|
28513
|
-
...clearableHere > 0 ? {} : { reportOnly: true },
|
|
28514
|
-
detail: `${probe.drift.length} drift finding(s)`,
|
|
28515
|
-
fix: clearableHere > 0 ? `${clearableHere} of ${probe.drift.length} clearable from this repo \u2014 from an up-to-date checkout run \`mmi-cli org schedules register\` here, then \`mmi-cli org schedules\` for the rest (each line names its own per-class remedy, applied in the owning repo)` : "run `mmi-cli org schedules` \u2014 each drift line names its per-class remedy (file/registry/live mismatch or harbour enforcement), applied in the owning repo",
|
|
28516
|
-
verbose: evidence
|
|
28517
|
-
};
|
|
28518
|
-
}
|
|
28519
|
-
return { ok: true, id: "schedules", label: "schedules", detail: `${probe.armed} armed`, verbose: evidence };
|
|
28520
|
-
}
|
|
28521
|
-
function checkDocsAudit(probe) {
|
|
28522
|
-
if (!probe) return null;
|
|
28523
|
-
if (!probe.armed) {
|
|
28524
|
-
return { ok: true, id: "docs-audit", label: "docs-audit", detail: "janitor not armed", verbose: [probe.detail] };
|
|
28525
|
-
}
|
|
28526
|
-
if (!probe.ok) {
|
|
28527
|
-
return {
|
|
28528
|
-
ok: false,
|
|
28529
|
-
id: "docs-audit",
|
|
28530
|
-
label: "docs-audit",
|
|
28531
|
-
// #3485: ruled report-only — the remedy is re-running or re-arming the janitor in the repo that
|
|
28532
|
-
// owns it. `mmi-cli docs audit record` can write a verdict from here, but hand-writing one to clear
|
|
28533
|
-
// a dead-man check defeats the check (#3067 pillar 4: silence is an alarm, never a success).
|
|
28534
|
-
reportOnly: true,
|
|
28535
|
-
fix: `${probe.detail} \u2014 re-run the janitor in the owning repo or re-arm its schedule (the remedy never lives in this doctor)`,
|
|
28536
|
-
verbose: [probe.detail]
|
|
28537
|
-
};
|
|
28538
|
-
}
|
|
28539
|
-
return { ok: true, id: "docs-audit", label: "docs-audit", detail: probe.detail, verbose: [probe.detail] };
|
|
28540
|
-
}
|
|
28541
|
-
function checkRedactorLiveness(probe) {
|
|
28542
|
-
if (!probe) return null;
|
|
28543
|
-
const evidence = [`secret-redact failed rows in the last 48h: ${probe.failed}${probe.lastTs ? ` (last ${probe.lastTs})` : ""}`];
|
|
28544
|
-
if (probe.failed === 0) {
|
|
28545
|
-
return { ok: true, id: "redactor-liveness", label: "secret-redact liveness", verbose: evidence };
|
|
28546
|
-
}
|
|
28547
|
-
return {
|
|
28548
|
-
ok: false,
|
|
28549
|
-
id: "redactor-liveness",
|
|
28550
|
-
label: "secret-redact liveness",
|
|
28551
|
-
reportOnly: true,
|
|
28552
|
-
detail: `${probe.failed} crash marker${probe.failed === 1 ? "" : "s"} in 48h`,
|
|
28553
|
-
fix: "the redactor is crashing during scans \u2014 read `.git/mmi-runtime/hooks/activity.jsonl` for the error and fix it; every crash is a scan that never ran",
|
|
28554
|
-
verbose: evidence
|
|
28555
|
-
};
|
|
28556
|
-
}
|
|
28557
28460
|
function checkSessionPayload(probe) {
|
|
28558
28461
|
if (!probe) return null;
|
|
28559
28462
|
const { chars } = probe;
|
|
@@ -28608,13 +28511,14 @@ function gcReapable(plan) {
|
|
|
28608
28511
|
return plan.branches.length + plan.trackingRefs.length + plan.worktreeDirs.length;
|
|
28609
28512
|
}
|
|
28610
28513
|
async function runDoctorClean(opts, io, deps) {
|
|
28611
|
-
const
|
|
28612
|
-
const
|
|
28514
|
+
const full = !opts.fast && !opts.banner && !opts.preflight;
|
|
28515
|
+
const applyEnv = full || Boolean(opts.preflight);
|
|
28516
|
+
const applyRepo = full && opts.repoWrites !== false;
|
|
28613
28517
|
const lane = {
|
|
28614
28518
|
banner: Boolean(opts.banner),
|
|
28615
28519
|
fast: Boolean(opts.fast),
|
|
28616
28520
|
preflight: Boolean(opts.preflight),
|
|
28617
|
-
full
|
|
28521
|
+
full
|
|
28618
28522
|
};
|
|
28619
28523
|
const probeReleased = !opts.fast || Boolean(opts.self);
|
|
28620
28524
|
const probeAws = !opts.fast && !opts.banner;
|
|
@@ -28712,9 +28616,6 @@ async function runDoctorClean(opts, io, deps) {
|
|
|
28712
28616
|
async function runGithubAuthRow() {
|
|
28713
28617
|
emitNow(checkGithubAuth({ login, ghInstalled: ghInstalled2, reach }));
|
|
28714
28618
|
}
|
|
28715
|
-
async function runGithubPoolsRows() {
|
|
28716
|
-
for (const pool of checkGithubPools(await deps.githubPools())) emitNow(pool);
|
|
28717
|
-
}
|
|
28718
28619
|
async function runAwsRow() {
|
|
28719
28620
|
const aws = checkAwsIdentity({ isOrgRepo, probed: probeAws, callerArn });
|
|
28720
28621
|
if (aws) emitNow(aws);
|
|
@@ -28735,7 +28636,7 @@ async function runDoctorClean(opts, io, deps) {
|
|
|
28735
28636
|
emitNow({ ok: true, id: "gitignore-block", label: "gitignore block", detail: "rewrote managed block", verbose: giEvidence });
|
|
28736
28637
|
restartPending = true;
|
|
28737
28638
|
} else {
|
|
28738
|
-
emitNow({ ok: false, id: "gitignore-block", label: "gitignore block", fix: "run `mmi-cli doctor --
|
|
28639
|
+
emitNow({ ok: false, id: "gitignore-block", label: "gitignore block", fix: "run `mmi-cli doctor` (without --no-repo-writes) to write the org-managed .gitignore block", verbose: giEvidence });
|
|
28739
28640
|
}
|
|
28740
28641
|
}
|
|
28741
28642
|
async function runPluginCacheRow() {
|
|
@@ -28755,35 +28656,6 @@ async function runDoctorClean(opts, io, deps) {
|
|
|
28755
28656
|
}
|
|
28756
28657
|
for (const row of deps.marketplaceRows()) emitNow(row);
|
|
28757
28658
|
}
|
|
28758
|
-
async function runSchedulesRow() {
|
|
28759
|
-
const probe = await deps.schedulesNotebook().catch((e) => ({
|
|
28760
|
-
armed: 0,
|
|
28761
|
-
live: 0,
|
|
28762
|
-
declared: 0,
|
|
28763
|
-
incomplete: [`schedules probe failed \u2014 ${e.message}`],
|
|
28764
|
-
drift: []
|
|
28765
|
-
}));
|
|
28766
|
-
const sched = checkSchedules(probe);
|
|
28767
|
-
if (sched) emitNow(sched);
|
|
28768
|
-
}
|
|
28769
|
-
async function runRedactorRow() {
|
|
28770
|
-
const redactor = checkRedactorLiveness(deps.redactorLiveness());
|
|
28771
|
-
if (redactor) emitNow(redactor);
|
|
28772
|
-
}
|
|
28773
|
-
async function runDocsAuditRow() {
|
|
28774
|
-
const probe = await deps.docsAudit().catch((e) => ({
|
|
28775
|
-
armed: true,
|
|
28776
|
-
ok: false,
|
|
28777
|
-
detail: `docs-audit probe failed \u2014 ${e.message}`
|
|
28778
|
-
}));
|
|
28779
|
-
const docsAudit2 = checkDocsAudit(probe);
|
|
28780
|
-
if (docsAudit2) emitNow(docsAudit2);
|
|
28781
|
-
}
|
|
28782
|
-
async function runWorktreeRootsRow() {
|
|
28783
|
-
const probe = await deps.worktreeRoots().catch(() => void 0);
|
|
28784
|
-
const roots = checkWorktreeRoots(probe);
|
|
28785
|
-
if (roots) emitNow(roots);
|
|
28786
|
-
}
|
|
28787
28659
|
async function runTrainSyncRow() {
|
|
28788
28660
|
try {
|
|
28789
28661
|
emitNow(checkTrainSync(await deps.syncTrain()));
|
|
@@ -28839,7 +28711,7 @@ async function runDoctorClean(opts, io, deps) {
|
|
|
28839
28711
|
// #3574: `${n} stale` was prepended to `fix` because the ✗ branch would not render `detail` —
|
|
28840
28712
|
// the same fact this row already puts in `detail` on its ✓ path four lines up. One convention now.
|
|
28841
28713
|
detail: `${n} stale`,
|
|
28842
|
-
fix: "run `mmi-cli doctor --
|
|
28714
|
+
fix: "run `mmi-cli doctor` (without --no-repo-writes) to reap merged branches, stale refs, and dead worktrees",
|
|
28843
28715
|
verbose: gcEvidence
|
|
28844
28716
|
});
|
|
28845
28717
|
}
|
|
@@ -28853,7 +28725,7 @@ async function runDoctorClean(opts, io, deps) {
|
|
|
28853
28725
|
emitNow({ id: "scratch", ok: true, label: "scratch", detail: pruned ? `removed ${pruned} aged item(s)` : "nothing stale", verbose: scratchEvidence });
|
|
28854
28726
|
if (pruned) restartPending = true;
|
|
28855
28727
|
} else {
|
|
28856
|
-
emitNow(scratch.plan.safeAuto.length === 0 ? { id: "scratch", ok: true, label: "scratch", verbose: scratchEvidence } : { id: "scratch", ok: false, label: "scratch", detail: `${scratch.plan.safeAuto.length} aged item(s)`, fix: "run `mmi-cli doctor --
|
|
28728
|
+
emitNow(scratch.plan.safeAuto.length === 0 ? { id: "scratch", ok: true, label: "scratch", verbose: scratchEvidence } : { id: "scratch", ok: false, label: "scratch", detail: `${scratch.plan.safeAuto.length} aged item(s)`, fix: "run `mmi-cli doctor` (without --no-repo-writes)", verbose: scratchEvidence });
|
|
28857
28729
|
}
|
|
28858
28730
|
}
|
|
28859
28731
|
const table = [
|
|
@@ -28862,17 +28734,12 @@ async function runDoctorClean(opts, io, deps) {
|
|
|
28862
28734
|
{ id: "plugin", when: true, run: runPluginRow },
|
|
28863
28735
|
{ id: "cli-version", when: true, run: runCliRow },
|
|
28864
28736
|
{ id: "github-auth", when: true, run: runGithubAuthRow },
|
|
28865
|
-
{ id: "github-pools", when: lane.full, run: runGithubPoolsRows },
|
|
28866
28737
|
{ id: "aws-identity", when: true, run: runAwsRow },
|
|
28867
28738
|
{ id: "repo-worktrees", when: true, run: runRepoWorktreesRow },
|
|
28868
28739
|
{ id: "gitignore-block", when: isOrgRepo, run: runGitignoreRow },
|
|
28869
28740
|
{ id: "plugin-cache", when: true, run: runPluginCacheRow },
|
|
28870
28741
|
{ id: "sessionstart-payload", when: true, run: runSessionPayloadRow },
|
|
28871
28742
|
{ id: "marketplace", when: true, run: runMarketplaceRows },
|
|
28872
|
-
{ id: "schedules", when: lane.full, run: runSchedulesRow },
|
|
28873
|
-
{ id: "redactor-liveness", when: lane.full, run: runRedactorRow },
|
|
28874
|
-
{ id: "docs-audit", when: lane.full, run: runDocsAuditRow },
|
|
28875
|
-
{ id: "worktree-roots", when: lane.full, run: runWorktreeRootsRow },
|
|
28876
28743
|
// The two most expensive things in this file — a real `git fetch` plus train-branch fast-forward,
|
|
28877
28744
|
// and a `gh`-backed gc sweep with a 20s timeout — so org repos on the full lane only (#3485).
|
|
28878
28745
|
{ id: "train-branches", when: isOrgRepo && lane.full, run: runTrainSyncRow },
|
|
@@ -28931,17 +28798,17 @@ function parseOriginRepo(remoteUrl) {
|
|
|
28931
28798
|
}
|
|
28932
28799
|
function ghHostsConfigPath(env, platform2) {
|
|
28933
28800
|
const sep2 = platform2 === "win32" ? "\\" : "/";
|
|
28934
|
-
const
|
|
28801
|
+
const join30 = (...parts) => parts.join(sep2);
|
|
28935
28802
|
const explicit = env.GH_CONFIG_DIR?.trim();
|
|
28936
|
-
if (explicit) return
|
|
28803
|
+
if (explicit) return join30(explicit, "hosts.yml");
|
|
28937
28804
|
if (platform2 === "win32") {
|
|
28938
28805
|
const appData = (env.AppData ?? env.APPDATA)?.trim();
|
|
28939
|
-
return appData ?
|
|
28806
|
+
return appData ? join30(appData, "GitHub CLI", "hosts.yml") : void 0;
|
|
28940
28807
|
}
|
|
28941
28808
|
const xdg = env.XDG_CONFIG_HOME?.trim();
|
|
28942
|
-
if (xdg) return
|
|
28809
|
+
if (xdg) return join30(xdg, "gh", "hosts.yml");
|
|
28943
28810
|
const home = env.HOME?.trim();
|
|
28944
|
-
return home ?
|
|
28811
|
+
return home ? join30(home, ".config", "gh", "hosts.yml") : void 0;
|
|
28945
28812
|
}
|
|
28946
28813
|
function parseGhHostsAccounts(yaml, host = "github.com") {
|
|
28947
28814
|
let hostIndent = null;
|
|
@@ -28991,9 +28858,9 @@ function ghAccountCaveat(announcedLogin, accounts) {
|
|
|
28991
28858
|
}
|
|
28992
28859
|
|
|
28993
28860
|
// src/doctor-io.ts
|
|
28994
|
-
var
|
|
28995
|
-
var
|
|
28996
|
-
var
|
|
28861
|
+
var import_node_fs35 = require("node:fs");
|
|
28862
|
+
var import_node_os13 = require("node:os");
|
|
28863
|
+
var import_node_path34 = require("node:path");
|
|
28997
28864
|
var import_node_child_process15 = require("node:child_process");
|
|
28998
28865
|
var import_node_util8 = require("node:util");
|
|
28999
28866
|
var execFileP6 = (0, import_node_util8.promisify)(import_node_child_process15.execFile);
|
|
@@ -29001,7 +28868,7 @@ var MMI_PLUGIN_ID2 = "mmi@mutmutco";
|
|
|
29001
28868
|
function installedClaudePluginVersion() {
|
|
29002
28869
|
try {
|
|
29003
28870
|
const file = JSON.parse(
|
|
29004
|
-
(0,
|
|
28871
|
+
(0, import_node_fs35.readFileSync)((0, import_node_path34.join)((0, import_node_os13.homedir)(), ".claude", "plugins", "installed_plugins.json"), "utf8")
|
|
29005
28872
|
);
|
|
29006
28873
|
const versions = (file.plugins?.[MMI_PLUGIN_ID2] ?? []).map((r) => r.version).filter((v) => Boolean(v));
|
|
29007
28874
|
if (versions.length === 0) return void 0;
|
|
@@ -29012,7 +28879,7 @@ function installedClaudePluginVersion() {
|
|
|
29012
28879
|
}
|
|
29013
28880
|
function manifestVersion(path2) {
|
|
29014
28881
|
try {
|
|
29015
|
-
const manifest = JSON.parse((0,
|
|
28882
|
+
const manifest = JSON.parse((0, import_node_fs35.readFileSync)(path2, "utf8"));
|
|
29016
28883
|
return typeof manifest.version === "string" && manifest.version.trim() ? manifest.version.trim() : void 0;
|
|
29017
28884
|
} catch {
|
|
29018
28885
|
return void 0;
|
|
@@ -29022,17 +28889,17 @@ function installedSurfacePluginVersion(surface) {
|
|
|
29022
28889
|
const token = surfaceToken(surface);
|
|
29023
28890
|
if (token === "kilo") {
|
|
29024
28891
|
try {
|
|
29025
|
-
const stamp = (0,
|
|
28892
|
+
const stamp = (0, import_node_fs35.readFileSync)((0, import_node_path34.join)((0, import_node_os13.homedir)(), ".kilo", ".mmi-kilo-version"), "utf8").trim();
|
|
29026
28893
|
return stamp || void 0;
|
|
29027
28894
|
} catch {
|
|
29028
28895
|
return void 0;
|
|
29029
28896
|
}
|
|
29030
28897
|
}
|
|
29031
28898
|
if (token === "cursor") {
|
|
29032
|
-
return manifestVersion((0,
|
|
28899
|
+
return manifestVersion((0, import_node_path34.join)(cursorLocalPluginRoot(), ".cursor-plugin", "plugin.json"));
|
|
29033
28900
|
}
|
|
29034
28901
|
if (token === "kimi") {
|
|
29035
|
-
return manifestVersion((0,
|
|
28902
|
+
return manifestVersion((0, import_node_path34.join)(surfaceConfigRoot(surface), "plugins", "managed", "mmi", ".kimi-plugin", "plugin.json"));
|
|
29036
28903
|
}
|
|
29037
28904
|
if (token === "claude") return installedClaudePluginVersion();
|
|
29038
28905
|
if (token !== "codex") return void 0;
|
|
@@ -29070,13 +28937,13 @@ function worktreeRootSync() {
|
|
|
29070
28937
|
}
|
|
29071
28938
|
var gitignorePath = () => {
|
|
29072
28939
|
const root = worktreeRootSync();
|
|
29073
|
-
return root === null ? null : (0,
|
|
28940
|
+
return root === null ? null : (0, import_node_path34.join)(root, ".gitignore");
|
|
29074
28941
|
};
|
|
29075
28942
|
function readGitignore() {
|
|
29076
28943
|
const path2 = gitignorePath();
|
|
29077
28944
|
if (path2 === null) return null;
|
|
29078
28945
|
try {
|
|
29079
|
-
return (0,
|
|
28946
|
+
return (0, import_node_fs35.readFileSync)(path2, "utf8");
|
|
29080
28947
|
} catch {
|
|
29081
28948
|
return null;
|
|
29082
28949
|
}
|
|
@@ -29085,7 +28952,7 @@ function writeGitignore(content) {
|
|
|
29085
28952
|
const path2 = gitignorePath();
|
|
29086
28953
|
if (path2 === null) return false;
|
|
29087
28954
|
try {
|
|
29088
|
-
(0,
|
|
28955
|
+
(0, import_node_fs35.writeFileSync)(path2, content, "utf8");
|
|
29089
28956
|
return true;
|
|
29090
28957
|
} catch {
|
|
29091
28958
|
return false;
|
|
@@ -29109,7 +28976,7 @@ async function repoRoot() {
|
|
|
29109
28976
|
}
|
|
29110
28977
|
function hasRepoLocalWorktrees() {
|
|
29111
28978
|
const root = worktreeRootSync();
|
|
29112
|
-
return root !== null && (0,
|
|
28979
|
+
return root !== null && (0, import_node_fs35.existsSync)((0, import_node_path34.join)(root, ".worktrees"));
|
|
29113
28980
|
}
|
|
29114
28981
|
|
|
29115
28982
|
// src/index.ts
|
|
@@ -29145,8 +29012,8 @@ ${r.stderr ?? ""}`).catch(() => "");
|
|
|
29145
29012
|
function ghMultiAccountCaveat(announcedLogin) {
|
|
29146
29013
|
try {
|
|
29147
29014
|
const hostsPath = ghHostsConfigPath(process.env, process.platform);
|
|
29148
|
-
if (!hostsPath || !(0,
|
|
29149
|
-
return ghAccountCaveat(announcedLogin, parseGhHostsAccounts((0,
|
|
29015
|
+
if (!hostsPath || !(0, import_node_fs36.existsSync)(hostsPath)) return void 0;
|
|
29016
|
+
return ghAccountCaveat(announcedLogin, parseGhHostsAccounts((0, import_node_fs36.readFileSync)(hostsPath, "utf8")));
|
|
29150
29017
|
} catch {
|
|
29151
29018
|
return void 0;
|
|
29152
29019
|
}
|
|
@@ -29154,12 +29021,12 @@ function ghMultiAccountCaveat(announcedLogin) {
|
|
|
29154
29021
|
var ENV_HEAL_LOCK_STALE_MS = 10 * 6e4;
|
|
29155
29022
|
var ENV_HEAL_LOCK_MAX_WAIT_MS = 2 * 6e4;
|
|
29156
29023
|
function envHealLockPath(home) {
|
|
29157
|
-
return (0,
|
|
29024
|
+
return (0, import_node_path35.join)(home, ".claude", "plugins", ".mmi-env-heal.lock");
|
|
29158
29025
|
}
|
|
29159
29026
|
async function withEnvHealLock(what, run) {
|
|
29160
29027
|
try {
|
|
29161
29028
|
return await withFileLock(
|
|
29162
|
-
envHealLockPath((0,
|
|
29029
|
+
envHealLockPath((0, import_node_os14.homedir)()),
|
|
29163
29030
|
{ staleMs: ENV_HEAL_LOCK_STALE_MS, maxWaitMs: ENV_HEAL_LOCK_MAX_WAIT_MS, label: "mmi env-heal lock" },
|
|
29164
29031
|
run
|
|
29165
29032
|
);
|
|
@@ -29170,9 +29037,6 @@ async function withEnvHealLock(what, run) {
|
|
|
29170
29037
|
return { ok: false, detail: `${what} could not take the env-heal lock \u2014 ${e.message}` };
|
|
29171
29038
|
}
|
|
29172
29039
|
}
|
|
29173
|
-
function docsJanitorScheduleId(repo) {
|
|
29174
|
-
return `${repo.split("/").pop()}/docs-janitor`;
|
|
29175
|
-
}
|
|
29176
29040
|
function throttledReleasedVersion() {
|
|
29177
29041
|
let note;
|
|
29178
29042
|
return {
|
|
@@ -29193,8 +29057,6 @@ function throttledReleasedVersion() {
|
|
|
29193
29057
|
}
|
|
29194
29058
|
function mmiDoctorDeps(opts = {}) {
|
|
29195
29059
|
const throttled = opts.throttleReleasedRead ? throttledReleasedVersion() : void 0;
|
|
29196
|
-
let notebook;
|
|
29197
|
-
const notebookOnce = () => notebook ??= fetchNotebook();
|
|
29198
29060
|
let surfaceEvidence;
|
|
29199
29061
|
let surfaceEvidenceRead = false;
|
|
29200
29062
|
const surfaceEvidenceOnce = (isOrgRepo) => {
|
|
@@ -29218,11 +29080,6 @@ function mmiDoctorDeps(opts = {}) {
|
|
|
29218
29080
|
};
|
|
29219
29081
|
return surfaceEvidence;
|
|
29220
29082
|
};
|
|
29221
|
-
const docsJanitorArmedAt = async (repo) => {
|
|
29222
|
-
const wanted = docsJanitorScheduleId(repo);
|
|
29223
|
-
const { entries } = await notebookOnce();
|
|
29224
|
-
return entries.find((e) => e.scheduleId === wanted)?.armedAt;
|
|
29225
|
-
};
|
|
29226
29083
|
return {
|
|
29227
29084
|
githubLogin,
|
|
29228
29085
|
ghInstalled,
|
|
@@ -29261,30 +29118,12 @@ function mmiDoctorDeps(opts = {}) {
|
|
|
29261
29118
|
// writes to the harness-owned cache itself. Same plan builder the verb uses, so the two never disagree.
|
|
29262
29119
|
// No `withBytes` here: doctor runs on EVERY SessionStart, and sizing means recursively stat'ing every
|
|
29263
29120
|
// stale version tree. The count comes from one cheap readdir; `plugin-prune` reports the MB.
|
|
29264
|
-
// #3025: both identities' rate pools. Personal probes with the ambient token; the App line only exists
|
|
29265
|
-
// when this machine can mint — a vault-denied read means "not an automation host", not a failure.
|
|
29266
|
-
githubPools: async () => {
|
|
29267
|
-
const deps = appActorDeps();
|
|
29268
|
-
const personal = await probeRatePools(deps.fetch, await githubToken());
|
|
29269
|
-
let app = null;
|
|
29270
|
-
try {
|
|
29271
|
-
const minted = await mintInstallationToken(deps);
|
|
29272
|
-
app = await probeRatePools(deps.fetch, minted.token) ?? "token minted but rate_limit probe failed \u2014 check network/App permissions";
|
|
29273
|
-
} catch (e) {
|
|
29274
|
-
if (e instanceof AppActorError) {
|
|
29275
|
-
if (e.code !== "vault-denied") app = e.message;
|
|
29276
|
-
} else {
|
|
29277
|
-
app = "app actor probe failed unexpectedly \u2014 run `MMI_ACTOR=app mmi-cli pr checks-wait --help` to reproduce";
|
|
29278
|
-
}
|
|
29279
|
-
}
|
|
29280
|
-
return { personal, app };
|
|
29281
|
-
},
|
|
29282
29121
|
pluginCache: () => {
|
|
29283
29122
|
const surface = detectSurface(process.env);
|
|
29284
29123
|
const configRoot = surfaceConfigRoot(surface);
|
|
29285
29124
|
const running = surface === "codex" ? installedActivePluginVersion(surface) : runningPluginVersion(process.env, resolveClientVersion());
|
|
29286
29125
|
const plan = buildPluginCachePlan(
|
|
29287
|
-
(0,
|
|
29126
|
+
(0, import_node_os14.homedir)(),
|
|
29288
29127
|
running,
|
|
29289
29128
|
pluginCacheFsDeps(configRoot, () => 0),
|
|
29290
29129
|
{ configRoot, includeStaging: surface !== "codex" }
|
|
@@ -29297,55 +29136,39 @@ function mmiDoctorDeps(opts = {}) {
|
|
|
29297
29136
|
stagingBytes: plan.stagingBytes
|
|
29298
29137
|
};
|
|
29299
29138
|
},
|
|
29300
|
-
// #3008: the schedules-notebook drift probe, compacted for the check. Full doctor only — the gather in
|
|
29301
|
-
// runDoctorClean skips this dep entirely on fast/banner/preflight runs.
|
|
29302
|
-
schedulesNotebook: async () => {
|
|
29303
|
-
const { entries, incomplete, drift, reconciliation } = await notebookOnce();
|
|
29304
|
-
const repoName = await repoSlug().catch(() => "");
|
|
29305
|
-
const clearableHere = repoName ? reconciliation.filter((d) => driftClearableFrom(d, repoName)).length : 0;
|
|
29306
|
-
return {
|
|
29307
|
-
armed: entries.length,
|
|
29308
|
-
live: entries.filter((e) => e.resolved === "live").length,
|
|
29309
|
-
declared: entries.filter((e) => e.resolved === "declared").length,
|
|
29310
|
-
incomplete,
|
|
29311
|
-
drift,
|
|
29312
|
-
clearableHere
|
|
29313
|
-
};
|
|
29314
|
-
},
|
|
29315
|
-
// #3075: the docs-audit dead-man verdict probe, compacted for the check. Full doctor only, exactly like
|
|
29316
|
-
// schedulesNotebook — a registry read the SessionStart banner / --preflight / --fast lanes never pay for.
|
|
29317
|
-
// The 404/absent route maps to `armed:false` (an informational "not armed" line), never a false RED.
|
|
29318
|
-
docsAudit: async () => {
|
|
29319
|
-
const repo = await currentRepoFullName();
|
|
29320
|
-
const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
29321
|
-
const armedAt = await docsJanitorArmedAt(repo).catch(() => void 0);
|
|
29322
|
-
const status = docsAuditStatus(await readDocsAuditFetch(repo), { repo, today, armedAt });
|
|
29323
|
-
return { armed: status.state !== "not-armed", ok: status.ok, detail: status.line };
|
|
29324
|
-
},
|
|
29325
|
-
// #3471: the competing-worktrees-roots probe. Full doctor only (the gather skips it on
|
|
29326
|
-
// fast/banner/preflight), and READ-ONLY by construction — it stats, it never removes. Nothing here is
|
|
29327
|
-
// wired to a reaper, in either mode.
|
|
29328
|
-
worktreeRoots: async () => worktreeRootsProbe(await repoRoot()),
|
|
29329
29139
|
// #3470: what the SessionStart hook LAST emitted, measured at emission by the session-start verb below.
|
|
29330
29140
|
// A local record read — cheap enough for every lane, including the banner.
|
|
29331
29141
|
sessionPayload: () => readSessionPayload(process.cwd()),
|
|
29332
|
-
// #3630: recent secret-redact crash markers from the shared hook-activity trace — with the Stop
|
|
29333
|
-
// hook retired, the full/scheduled doctor is the trace's only reader. Local tail-scan, fail-soft.
|
|
29334
|
-
redactorLiveness: () => redactorLivenessProbe(process.cwd()),
|
|
29335
29142
|
// #3485 items 9 and 8: why the MMI plugin has never printed an "updated — please restart" notice, and
|
|
29336
29143
|
// which branch it would pick one up from. Two local file reads, no network, fail-soft to no rows.
|
|
29337
29144
|
marketplaceRows: () => {
|
|
29338
29145
|
try {
|
|
29339
29146
|
if (detectSurface(process.env) === "codex") return [];
|
|
29340
|
-
const home = (0,
|
|
29341
|
-
|
|
29147
|
+
const home = (0, import_node_os14.homedir)();
|
|
29148
|
+
const rows = marketplaceRows(
|
|
29342
29149
|
MMI_MARKETPLACE_NAME,
|
|
29343
|
-
readFileSyncSafe((0,
|
|
29344
|
-
readFileSyncSafe((0,
|
|
29345
|
-
// #3974: this CLI heals these rows, so report mode names
|
|
29346
|
-
// edit. Unconditional — it is a fact about mmi-cli, not about
|
|
29150
|
+
readFileSyncSafe((0, import_node_path35.join)(home, ...KNOWN_MARKETPLACES_RELATIVE), import_node_fs36.readFileSync),
|
|
29151
|
+
readFileSyncSafe((0, import_node_path35.join)(home, ".claude", "settings.json"), import_node_fs36.readFileSync),
|
|
29152
|
+
// #3974: this CLI heals these rows, so report mode names the doctor run rather than the hand
|
|
29153
|
+
// edit. Unconditional — it is a fact about mmi-cli, not about the lane this run is on.
|
|
29347
29154
|
true
|
|
29348
29155
|
);
|
|
29156
|
+
const pending = readMarketplacePinPending(
|
|
29157
|
+
(0, import_node_path35.join)(home, ".claude", "plugins", ".mmi-marketplace-pin-pending.json"),
|
|
29158
|
+
MMI_MARKETPLACE_NAME
|
|
29159
|
+
);
|
|
29160
|
+
if (!pending) return rows;
|
|
29161
|
+
return rows.map((row) => {
|
|
29162
|
+
if (row.id !== "marketplace-catalog-ref" || row.ok) return row;
|
|
29163
|
+
const { fix: _fix, reportOnly: _reportOnly, ...rest } = row;
|
|
29164
|
+
return {
|
|
29165
|
+
...rest,
|
|
29166
|
+
ok: true,
|
|
29167
|
+
warn: true,
|
|
29168
|
+
detail: `pending restart \u2014 doctor pinned main at ${pending.at}; restart Claude Code to load it`,
|
|
29169
|
+
verbose: [...row.verbose ?? [], "known_marketplaces.json was rewritten by the running Claude host after the verified doctor write"]
|
|
29170
|
+
};
|
|
29171
|
+
});
|
|
29349
29172
|
} catch {
|
|
29350
29173
|
return [];
|
|
29351
29174
|
}
|
|
@@ -29355,7 +29178,13 @@ function mmiDoctorDeps(opts = {}) {
|
|
|
29355
29178
|
healMarketplacePins: () => {
|
|
29356
29179
|
try {
|
|
29357
29180
|
if (detectSurface(process.env) === "codex") return void 0;
|
|
29358
|
-
|
|
29181
|
+
const home = (0, import_node_os14.homedir)();
|
|
29182
|
+
const names = [MMI_MARKETPLACE_NAME];
|
|
29183
|
+
const result = applyOrgMarketplacePins((0, import_node_path35.join)(home, ...KNOWN_MARKETPLACES_RELATIVE), names);
|
|
29184
|
+
if (result?.startsWith("pinned ")) {
|
|
29185
|
+
writeMarketplacePinPending((0, import_node_path35.join)(home, ".claude", "plugins", ".mmi-marketplace-pin-pending.json"), names);
|
|
29186
|
+
}
|
|
29187
|
+
return result;
|
|
29359
29188
|
} catch {
|
|
29360
29189
|
return void 0;
|
|
29361
29190
|
}
|
|
@@ -29536,6 +29365,7 @@ function appActorDeps() {
|
|
|
29536
29365
|
fetch: (url, init) => fetch(url, init)
|
|
29537
29366
|
};
|
|
29538
29367
|
}
|
|
29368
|
+
setPollTokenProvider(() => pollTokenOrUndefined(appActorDeps()));
|
|
29539
29369
|
function shouldMarkWorktreeActivity(commandPath3) {
|
|
29540
29370
|
return commandPath3.split(" ")[0] !== "worktree";
|
|
29541
29371
|
}
|
|
@@ -29560,19 +29390,19 @@ program2.hook("preAction", async (_thisCommand, actionCommand) => {
|
|
|
29560
29390
|
});
|
|
29561
29391
|
var rules = program2.command("rules").description("org-managed .gitignore delivery");
|
|
29562
29392
|
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) => {
|
|
29563
|
-
const path2 = (0,
|
|
29564
|
-
const current = (0,
|
|
29393
|
+
const path2 = (0, import_node_path35.join)(process.cwd(), ".gitignore");
|
|
29394
|
+
const current = (0, import_node_fs36.existsSync)(path2) ? (0, import_node_fs36.readFileSync)(path2, "utf8") : null;
|
|
29565
29395
|
const plan = planManagedGitignore(current);
|
|
29566
29396
|
const drift = [...plan.added.map((l) => `+${l}`), ...plan.removed.map((l) => `-${l}`)].join(", ") || "block normalize";
|
|
29567
29397
|
if (opts.json) {
|
|
29568
|
-
if (opts.write && plan.changed) (0,
|
|
29398
|
+
if (opts.write && plan.changed) (0, import_node_fs36.writeFileSync)(path2, plan.content, "utf8");
|
|
29569
29399
|
console.log(JSON.stringify(plan, null, 2));
|
|
29570
29400
|
if (!opts.write && plan.changed) process.exitCode = 1;
|
|
29571
29401
|
return;
|
|
29572
29402
|
}
|
|
29573
29403
|
if (opts.write) {
|
|
29574
29404
|
if (plan.changed) {
|
|
29575
|
-
(0,
|
|
29405
|
+
(0, import_node_fs36.writeFileSync)(path2, plan.content, "utf8");
|
|
29576
29406
|
console.log(`mmi-cli org rules gitignore: updated .gitignore (${drift})`);
|
|
29577
29407
|
} else {
|
|
29578
29408
|
console.log("mmi-cli org rules gitignore: up to date");
|
|
@@ -29727,8 +29557,8 @@ gcCmd.option("--dry-run", "show what would be deleted (default)").option("--appl
|
|
|
29727
29557
|
if (!Number.isFinite(limit) || limit < 1) return fail("worktree gc: --limit must be a positive integer");
|
|
29728
29558
|
let root;
|
|
29729
29559
|
if (o.root !== void 0) {
|
|
29730
|
-
root = (0,
|
|
29731
|
-
if (!(0,
|
|
29560
|
+
root = (0, import_node_path35.resolve)(o.root);
|
|
29561
|
+
if (!(0, import_node_fs36.existsSync)(root) || !(0, import_node_fs36.statSync)(root).isDirectory()) return fail(`worktree gc: --root ${o.root} is not a directory`);
|
|
29732
29562
|
const gcRepoRoot = (await execFileP2("git", ["rev-parse", "--show-toplevel"], { timeout: GIT_TIMEOUT_MS }).catch(() => ({ stdout: "" }))).stdout.trim() || process.cwd();
|
|
29733
29563
|
if (isPathUnderDirectory(gcRepoRoot, root)) {
|
|
29734
29564
|
return fail(`worktree gc: --root ${root} contains this checkout \u2014 name a worktrees root, not the repo or an ancestor of it`);
|
|
@@ -29793,7 +29623,7 @@ async function primaryCheckoutRoot(from) {
|
|
|
29793
29623
|
return primaryCheckoutRootOf(async (args) => (await execFileP2("git", ["-C", from, ...args], { timeout: GIT_TIMEOUT_MS })).stdout);
|
|
29794
29624
|
}
|
|
29795
29625
|
async function unprovenWorktreeReason(wtPath, repoRoot2) {
|
|
29796
|
-
if (!(0,
|
|
29626
|
+
if (!(0, import_node_fs36.existsSync)(wtPath)) return `${wtPath} does not exist on disk`;
|
|
29797
29627
|
const porcelain = (await execFileP2("git", ["-C", repoRoot2, "worktree", "list", "--porcelain"], { timeout: GIT_TIMEOUT_MS }).catch(() => ({ stdout: "" }))).stdout;
|
|
29798
29628
|
const registered = parseWorktreePorcelainEntries(porcelain);
|
|
29799
29629
|
if (!registered.length) {
|
|
@@ -29807,6 +29637,7 @@ async function unprovenWorktreeReason(wtPath, repoRoot2) {
|
|
|
29807
29637
|
function makeProvisionDeps(worktreeRoot, quiet, log) {
|
|
29808
29638
|
return {
|
|
29809
29639
|
runInstall: (command, cwd) => runWorktreeInstall(command, cwd, quiet),
|
|
29640
|
+
validateInstall: (cwd) => runWorktreeInstall("npm ls --depth=0 --json", cwd, true).then(() => true).catch(() => false),
|
|
29810
29641
|
primaryCheckout: () => primaryCheckoutRoot(worktreeRoot),
|
|
29811
29642
|
log
|
|
29812
29643
|
};
|
|
@@ -29814,26 +29645,26 @@ function makeProvisionDeps(worktreeRoot, quiet, log) {
|
|
|
29814
29645
|
function acquireWorktreeSetupLock(worktreeRoot) {
|
|
29815
29646
|
const lockPath = repoRuntimeStatePath(worktreeRoot, "worktree-setup.lock");
|
|
29816
29647
|
const take = () => {
|
|
29817
|
-
const fd = (0,
|
|
29648
|
+
const fd = (0, import_node_fs36.openSync)(lockPath, "wx");
|
|
29818
29649
|
try {
|
|
29819
|
-
(0,
|
|
29650
|
+
(0, import_node_fs36.writeSync)(fd, String(Date.now()));
|
|
29820
29651
|
} finally {
|
|
29821
|
-
(0,
|
|
29652
|
+
(0, import_node_fs36.closeSync)(fd);
|
|
29822
29653
|
}
|
|
29823
29654
|
return () => {
|
|
29824
29655
|
try {
|
|
29825
|
-
(0,
|
|
29656
|
+
(0, import_node_fs36.rmSync)(lockPath, { force: true });
|
|
29826
29657
|
} catch {
|
|
29827
29658
|
}
|
|
29828
29659
|
};
|
|
29829
29660
|
};
|
|
29830
29661
|
try {
|
|
29831
|
-
(0,
|
|
29662
|
+
(0, import_node_fs36.mkdirSync)((0, import_node_path35.dirname)(lockPath), { recursive: true });
|
|
29832
29663
|
return take();
|
|
29833
29664
|
} catch {
|
|
29834
29665
|
try {
|
|
29835
|
-
if (Date.now() - (0,
|
|
29836
|
-
(0,
|
|
29666
|
+
if (Date.now() - (0, import_node_fs36.statSync)(lockPath).mtimeMs > WORKTREE_SETUP_LOCK_TTL_MS) {
|
|
29667
|
+
(0, import_node_fs36.rmSync)(lockPath, { force: true });
|
|
29837
29668
|
return take();
|
|
29838
29669
|
}
|
|
29839
29670
|
} catch {
|
|
@@ -29889,22 +29720,52 @@ withExamples(mutating(
|
|
|
29889
29720
|
const localOnly = preferRemote && base !== preferRemote ? ` (no ${preferRemote} \u2014 local ref)` : "";
|
|
29890
29721
|
console.error(` base ${base} ${baseSha}${localOnly}`);
|
|
29891
29722
|
}
|
|
29892
|
-
|
|
29893
|
-
|
|
29894
|
-
|
|
29895
|
-
|
|
29896
|
-
|
|
29897
|
-
|
|
29898
|
-
|
|
29899
|
-
|
|
29723
|
+
const registered = parseWorktreePorcelainEntries((await execFileP2(
|
|
29724
|
+
"git",
|
|
29725
|
+
["-C", repoRoot2, "worktree", "list", "--porcelain"],
|
|
29726
|
+
{ timeout: GIT_TIMEOUT_MS }
|
|
29727
|
+
)).stdout);
|
|
29728
|
+
const exact = registered.find((entry) => samePath(entry.path, wtPath));
|
|
29729
|
+
let resumed = false;
|
|
29730
|
+
if (exact) {
|
|
29731
|
+
step = `resume existing worktree ${wtPath}`;
|
|
29732
|
+
if (exact.branch !== branch) {
|
|
29733
|
+
return fail(`worktree create: ${wtPath} is already registered for '${exact.branch ?? "detached HEAD"}', not '${branch}'`);
|
|
29734
|
+
}
|
|
29735
|
+
const status = (await execFileP2("git", ["-C", wtPath, "status", "--porcelain"], { timeout: GIT_TIMEOUT_MS })).stdout.trim();
|
|
29736
|
+
if (status) return fail(`worktree create: refusing to resume ${wtPath} \u2014 it has uncommitted changes`);
|
|
29737
|
+
const head = await revParseRef(`refs/heads/${branch}`);
|
|
29738
|
+
const baseOid = await revParseRef(base);
|
|
29739
|
+
if (!head || !baseOid) return fail(`worktree create: could not prove '${branch}' and '${base}' before resume`);
|
|
29740
|
+
const canFastForward = await execFileP2(
|
|
29741
|
+
"git",
|
|
29742
|
+
["-C", repoRoot2, "merge-base", "--is-ancestor", head, baseOid],
|
|
29743
|
+
{ timeout: GIT_TIMEOUT_MS }
|
|
29744
|
+
).then(() => true).catch(() => false);
|
|
29745
|
+
if (!canFastForward) {
|
|
29746
|
+
return fail(`worktree create: refusing to resume '${branch}' \u2014 it has local commits or diverges from ${base}`);
|
|
29747
|
+
}
|
|
29748
|
+
await execFileP2("git", ["-C", wtPath, "merge", "--ff-only", base], { timeout: GIT_TIMEOUT_MS });
|
|
29749
|
+
resumed = true;
|
|
29750
|
+
}
|
|
29751
|
+
if (!resumed) {
|
|
29752
|
+
step = `git worktree add ${wtPath}`;
|
|
29753
|
+
await addWorktreeRobust(wtPath, branch, base, {
|
|
29754
|
+
git: async (args) => (await execFileP2("git", args, { timeout: GH_MUTATION_TIMEOUT_MS })).stdout,
|
|
29755
|
+
revParse: async (ref) => {
|
|
29756
|
+
try {
|
|
29757
|
+
return (await execFileP2("git", ["rev-parse", "--verify", ref], { timeout: GIT_TIMEOUT_MS })).stdout.trim() || void 0;
|
|
29758
|
+
} catch {
|
|
29759
|
+
return void 0;
|
|
29760
|
+
}
|
|
29761
|
+
},
|
|
29762
|
+
deleteBranch: (b) => execFileP2("git", ["branch", "-D", b], { timeout: GIT_TIMEOUT_MS }).then(() => void 0),
|
|
29763
|
+
sleep: (ms) => new Promise((resolve5) => setTimeout(resolve5, ms)),
|
|
29764
|
+
log: (m) => {
|
|
29765
|
+
if (!o.json) console.error(` ${m}`);
|
|
29900
29766
|
}
|
|
29901
|
-
}
|
|
29902
|
-
|
|
29903
|
-
sleep: (ms) => new Promise((resolve5) => setTimeout(resolve5, ms)),
|
|
29904
|
-
log: (m) => {
|
|
29905
|
-
if (!o.json) console.error(` ${m}`);
|
|
29906
|
-
}
|
|
29907
|
-
});
|
|
29767
|
+
});
|
|
29768
|
+
}
|
|
29908
29769
|
step = "install deps + copy local-only config";
|
|
29909
29770
|
const report = await provisionWorktree(wtPath, makeProvisionDeps(wtPath, Boolean(o.json), (m) => {
|
|
29910
29771
|
if (!o.json) console.error(` ${m}`);
|
|
@@ -29944,12 +29805,13 @@ withExamples(mutating(
|
|
|
29944
29805
|
branch,
|
|
29945
29806
|
path: wtPath,
|
|
29946
29807
|
base,
|
|
29808
|
+
resumed,
|
|
29947
29809
|
...report,
|
|
29948
29810
|
...issueForm && selector ? { issue: `${selector.repo}#${selector.number}` } : {},
|
|
29949
29811
|
...issueForm && o.claim ? { claim: claimError ? { ok: false, error: claimError } : claim } : {}
|
|
29950
29812
|
}, null, 2));
|
|
29951
29813
|
}
|
|
29952
|
-
console.log(`worktree ready: ${wtPath} (branch ${branch} from ${base})`);
|
|
29814
|
+
console.log(`worktree ready: ${wtPath} (branch ${branch} ${resumed ? "resumed at" : "from"} ${base})`);
|
|
29953
29815
|
console.log(` installed: ${report.installed.map((i) => i.dir || ".").join(", ") || "none"}`);
|
|
29954
29816
|
console.log(` copied: ${report.copied.join(", ") || "none"}`);
|
|
29955
29817
|
if (issueForm && o.claim && selector) {
|
|
@@ -30435,7 +30297,7 @@ project.command("set [owner/repo]").description("upsert project META (idempotent
|
|
|
30435
30297
|
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`);
|
|
30436
30298
|
if (o.secretsFile) {
|
|
30437
30299
|
try {
|
|
30438
|
-
vars.push(`secrets=${(0,
|
|
30300
|
+
vars.push(`secrets=${(0, import_node_fs36.readFileSync)(o.secretsFile, "utf8")}`);
|
|
30439
30301
|
} catch (e) {
|
|
30440
30302
|
return fail(`org project set: cannot read --secrets-file ${o.secretsFile}: ${e.message}`);
|
|
30441
30303
|
}
|
|
@@ -31183,11 +31045,11 @@ pr.command("view <number>").description("read a PR as structured JSON (merged st
|
|
|
31183
31045
|
}
|
|
31184
31046
|
});
|
|
31185
31047
|
async function listCiWorkflowPaths(cwd = process.cwd()) {
|
|
31186
|
-
const wfDir = (0,
|
|
31187
|
-
if (!(0,
|
|
31188
|
-
return (0,
|
|
31048
|
+
const wfDir = (0, import_node_path35.join)(cwd, ".github", "workflows");
|
|
31049
|
+
if (!(0, import_node_fs36.existsSync)(wfDir)) return [];
|
|
31050
|
+
return (0, import_node_fs36.readdirSync)(wfDir).filter((name) => /\.(ya?ml)$/i.test(name)).filter((name) => {
|
|
31189
31051
|
try {
|
|
31190
|
-
return workflowReportsPrChecks((0,
|
|
31052
|
+
return workflowReportsPrChecks((0, import_node_fs36.readFileSync)((0, import_node_path35.join)(wfDir, name), "utf8"));
|
|
31191
31053
|
} catch {
|
|
31192
31054
|
return true;
|
|
31193
31055
|
}
|
|
@@ -31219,16 +31081,16 @@ function ciAuditDeps() {
|
|
|
31219
31081
|
// gate re-seed step is skipped gracefully rather than failing mid-run.
|
|
31220
31082
|
readSeedFile: (path2) => {
|
|
31221
31083
|
if (!root) return null;
|
|
31222
|
-
const fullPath = (0,
|
|
31223
|
-
return (0,
|
|
31084
|
+
const fullPath = (0, import_node_path35.join)(root, path2);
|
|
31085
|
+
return (0, import_node_fs36.existsSync)(fullPath) ? (0, import_node_fs36.readFileSync)(fullPath, "utf8") : null;
|
|
31224
31086
|
}
|
|
31225
31087
|
};
|
|
31226
31088
|
}
|
|
31227
31089
|
function hubRoot() {
|
|
31228
|
-
const fromPkg = (0,
|
|
31090
|
+
const fromPkg = (0, import_node_path35.join)(__dirname, "..", "..");
|
|
31229
31091
|
const marker = "skills/bootstrap/seeds/manifest.json";
|
|
31230
|
-
if ((0,
|
|
31231
|
-
if ((0,
|
|
31092
|
+
if ((0, import_node_fs36.existsSync)((0, import_node_path35.join)(fromPkg, marker))) return fromPkg;
|
|
31093
|
+
if ((0, import_node_fs36.existsSync)((0, import_node_path35.join)(process.cwd(), marker))) return process.cwd();
|
|
31232
31094
|
return null;
|
|
31233
31095
|
}
|
|
31234
31096
|
pr.command("ci-policy").description("report merge CI policy: wait-for-checks vs no-ci (for grind/build agents)").option("--json", "machine-readable output").option("--repo <owner/repo>", "target repo (defaults to the current checkout)").action(async (o) => {
|
|
@@ -31357,6 +31219,10 @@ pr.command("land <number>").description("agent merge path (#1440): train probe \
|
|
|
31357
31219
|
return false;
|
|
31358
31220
|
}
|
|
31359
31221
|
});
|
|
31222
|
+
if (result.status !== "failed") {
|
|
31223
|
+
const repoFlag = result.repo ? ` --repo ${result.repo}` : "";
|
|
31224
|
+
console.warn(`pr land: merge confirmed; cleanup pending \u2014 if this process is interrupted, resume with: mmi-cli pr merge ${number}${repoFlag} --squash`);
|
|
31225
|
+
}
|
|
31360
31226
|
if (result.status !== "failed") {
|
|
31361
31227
|
try {
|
|
31362
31228
|
const { stdout } = await execFileP2(process.execPath, [
|
|
@@ -31531,7 +31397,7 @@ jsonParity(pr.command("merge <number>").description("merge a PR (squash by defau
|
|
|
31531
31397
|
localCleanup = await cleanupPrMergeLocalBranch(headRef, {
|
|
31532
31398
|
beforeWorktrees,
|
|
31533
31399
|
startingPath,
|
|
31534
|
-
pathExists: (p) => (0,
|
|
31400
|
+
pathExists: (p) => (0, import_node_fs36.existsSync)(p),
|
|
31535
31401
|
execGit: async (args) => (await execFileP2("git", args, { timeout: GIT_TIMEOUT_MS })).stdout,
|
|
31536
31402
|
teardownWorktreeStage,
|
|
31537
31403
|
deferredStore,
|
|
@@ -32006,26 +31872,25 @@ access.command("audit").description("audit collaborator roles + train-branch pus
|
|
|
32006
31872
|
targets = resolution.targets;
|
|
32007
31873
|
}
|
|
32008
31874
|
const derivedMatrix = registryProjects ? accessMatrixFromProjects(registryProjects) : {};
|
|
32009
|
-
const fileMatrix = (0,
|
|
31875
|
+
const fileMatrix = (0, import_node_fs36.existsSync)("access-matrix.json") ? loadAccessMatrix((0, import_node_fs36.readFileSync)("access-matrix.json", "utf8")) : {};
|
|
32010
31876
|
const matrix = mergeAccessMatrix(fileMatrix, derivedMatrix);
|
|
32011
31877
|
const derivedContracts = registryProjects ? dataAccessContractsFromProjects(registryProjects) : { consumers: {} };
|
|
32012
|
-
const fileContracts = (0,
|
|
31878
|
+
const fileContracts = (0, import_node_fs36.existsSync)("data-access-contracts.json") ? loadDataAccessContracts((0, import_node_fs36.readFileSync)("data-access-contracts.json", "utf8")) : { consumers: {} };
|
|
32013
31879
|
const dataAccess = mergeDataAccessContracts(fileContracts, derivedContracts);
|
|
32014
|
-
const sanctioned = (0,
|
|
31880
|
+
const sanctioned = (0, import_node_fs36.existsSync)("access-matrix.json") ? loadSanctionedAdmins((0, import_node_fs36.readFileSync)("access-matrix.json", "utf8")) : {};
|
|
32015
31881
|
const report = await auditOrgAccess(targets, deps, matrix, dataAccess, sanctioned);
|
|
32016
31882
|
console.log(o.json ? JSON.stringify(report, null, 2) : renderAccessReport(report));
|
|
32017
31883
|
if (!report.ok) process.exitCode = 1;
|
|
32018
31884
|
});
|
|
32019
31885
|
access.command("capabilities").description("enumerate your effective vault reach \u2014 every credential NAME + tier + scope you can read/use across project + org/master tiers (names only, no values) (#1615)").option("--repo <owner/repo>", "target repo (defaults to the current repo)").option("--json", "machine-readable output").action((o) => withSecrets((d) => secretsCapabilities(d, o)));
|
|
32020
31886
|
var isWin2 = process.platform === "win32";
|
|
32021
|
-
program2.command("doctor").description("
|
|
31887
|
+
program2.command("doctor").description("heal CLI/plugin wiring and clean up repo cruft \u2014 repairs run by default (#3975); use --verbose for the full audit checklist").option("--banner", "one-line resume summary; silent when all gates pass").option("--fast", "offline-safe fast lane; read-only, skips slow checks and network probes").option("--preflight", "eager version/plugin-heal (env repairs only \u2014 never the repo working tree) with upfront notice when stale; silent when healthy (#1871)").option("--verbose", "print the evidence behind every check \u2014 probes, resolved paths, versions compared, and the names behind each count (#2977)").option("--guide", "print the MMI Agentic Onboarding guide URL").option("--json", "machine-readable output (an output format \u2014 repairs still run by lane)").option("--apply", "deprecated no-op: repairs run by default now (#3975); kept so older instructions still parse").option("--no-repo-writes", "env/plugin repairs only \u2014 never mutate the repo working tree; report pending managed .gitignore repairs with the follow-up command (for train preflights)").option("--self", "verify CLI/plugin version parity and gh auth reach; suggests plugin-heal on a hard gap (reads the published version, so not offline-safe) (#2689)").addHelpText("after", "\nExit codes:\n 0 no hard gaps remain; advisory gaps may exist\n 1 one or more included hard checks failed\n\nA plain run heals env drift (CLI, plugin, marketplace pins) and cleans repo cruft (gitignore block,\nmerged branches, dead worktrees, aged scratch) automatically (#3975). --no-repo-writes keeps the\nworking tree untouched for train preflights; --banner/--fast/--self are read-only lanes.\n\n--banner keeps the legacy SessionStart contract and returns 0 unless the process crashes.\n").action(async (opts) => {
|
|
32022
31888
|
if (opts.guide) {
|
|
32023
31889
|
consoleIo.log("MMI Agentic Onboarding: docs/Architecture/agentic-dev-environment.md");
|
|
32024
31890
|
return;
|
|
32025
31891
|
}
|
|
32026
31892
|
process.exitCode = await runDoctorClean(
|
|
32027
31893
|
{
|
|
32028
|
-
apply: Boolean(opts.apply),
|
|
32029
31894
|
repoWrites: opts.repoWrites,
|
|
32030
31895
|
banner: opts.banner,
|
|
32031
31896
|
preflight: opts.preflight,
|
|
@@ -32044,16 +31909,16 @@ function directoryBytes(path2) {
|
|
|
32044
31909
|
let total = 0;
|
|
32045
31910
|
let entries;
|
|
32046
31911
|
try {
|
|
32047
|
-
entries = (0,
|
|
31912
|
+
entries = (0, import_node_fs36.readdirSync)(path2, { withFileTypes: true });
|
|
32048
31913
|
} catch {
|
|
32049
31914
|
return 0;
|
|
32050
31915
|
}
|
|
32051
31916
|
for (const entry of entries) {
|
|
32052
|
-
const child2 = (0,
|
|
31917
|
+
const child2 = (0, import_node_path35.join)(path2, entry.name);
|
|
32053
31918
|
if (entry.isDirectory()) total += directoryBytes(child2);
|
|
32054
31919
|
else {
|
|
32055
31920
|
try {
|
|
32056
|
-
total += (0,
|
|
31921
|
+
total += (0, import_node_fs36.statSync)(child2).size;
|
|
32057
31922
|
} catch {
|
|
32058
31923
|
}
|
|
32059
31924
|
}
|
|
@@ -32061,25 +31926,25 @@ function directoryBytes(path2) {
|
|
|
32061
31926
|
return total;
|
|
32062
31927
|
}
|
|
32063
31928
|
function listDirEntries(dir) {
|
|
32064
|
-
return (0,
|
|
31929
|
+
return (0, import_node_fs36.readdirSync)(dir, { withFileTypes: true }).map((d) => ({ name: d.name, isDirectory: d.isDirectory() }));
|
|
32065
31930
|
}
|
|
32066
31931
|
function readInstalledPluginRefs(configRoot) {
|
|
32067
31932
|
const p = installedPluginsPathForConfig(configRoot);
|
|
32068
|
-
if (!(0,
|
|
31933
|
+
if (!(0, import_node_fs36.existsSync)(p)) return [];
|
|
32069
31934
|
try {
|
|
32070
|
-
return installedPluginPaths((0,
|
|
31935
|
+
return installedPluginPaths((0, import_node_fs36.readFileSync)(p, "utf8"));
|
|
32071
31936
|
} catch {
|
|
32072
31937
|
return null;
|
|
32073
31938
|
}
|
|
32074
31939
|
}
|
|
32075
31940
|
function pluginCacheFsDeps(configRoot, dirBytes) {
|
|
32076
31941
|
return {
|
|
32077
|
-
exists: (p) => (0,
|
|
32078
|
-
listVersionDirs: (root) => (0,
|
|
31942
|
+
exists: (p) => (0, import_node_fs36.existsSync)(p),
|
|
31943
|
+
listVersionDirs: (root) => (0, import_node_fs36.readdirSync)(root, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name),
|
|
32079
31944
|
dirBytes,
|
|
32080
|
-
listStagingDirs: (root) => (0,
|
|
31945
|
+
listStagingDirs: (root) => (0, import_node_fs36.readdirSync)(root, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => {
|
|
32081
31946
|
try {
|
|
32082
|
-
return { name: d.name, mtimeMs: newestMtimeMs((0,
|
|
31947
|
+
return { name: d.name, mtimeMs: newestMtimeMs((0, import_node_path35.join)(root, d.name), listDirEntries, (p) => (0, import_node_fs36.statSync)(p).mtimeMs) };
|
|
32083
31948
|
} catch {
|
|
32084
31949
|
return { name: d.name, mtimeMs: Date.now() };
|
|
32085
31950
|
}
|
|
@@ -32093,10 +31958,10 @@ function stagingApplyFsGuard(configRoot) {
|
|
|
32093
31958
|
return {
|
|
32094
31959
|
referencedPaths: () => readInstalledPluginRefs(configRoot),
|
|
32095
31960
|
mtimeMs: (name) => {
|
|
32096
|
-
const p = (0,
|
|
32097
|
-
if (!(0,
|
|
31961
|
+
const p = (0, import_node_path35.join)(stagingRoot, name);
|
|
31962
|
+
if (!(0, import_node_fs36.existsSync)(p)) return null;
|
|
32098
31963
|
try {
|
|
32099
|
-
return newestMtimeMs(p, listDirEntries, (q) => (0,
|
|
31964
|
+
return newestMtimeMs(p, listDirEntries, (q) => (0, import_node_fs36.statSync)(q).mtimeMs);
|
|
32100
31965
|
} catch {
|
|
32101
31966
|
return null;
|
|
32102
31967
|
}
|
|
@@ -32116,13 +31981,13 @@ program2.command("plugin-prune").description(`prune stale cached MMI plugin vers
|
|
|
32116
31981
|
return;
|
|
32117
31982
|
}
|
|
32118
31983
|
const plan = buildPluginCachePlan(
|
|
32119
|
-
(0,
|
|
31984
|
+
(0, import_node_os14.homedir)(),
|
|
32120
31985
|
running,
|
|
32121
31986
|
pluginCacheFsDeps(configRoot, directoryBytes),
|
|
32122
31987
|
{ withBytes: true, configRoot, includeStaging: surface !== "codex" }
|
|
32123
31988
|
);
|
|
32124
31989
|
const anythingToDelete = plan.prune.length > 0 || plan.staging.length > 0;
|
|
32125
|
-
const result = o.apply && anythingToDelete ? applyPluginCachePlan(plan, (p) => (0,
|
|
31990
|
+
const result = o.apply && anythingToDelete ? applyPluginCachePlan(plan, (p) => (0, import_node_fs36.rmSync)(p, { recursive: true, force: true }), stagingApplyFsGuard(configRoot)) : void 0;
|
|
32126
31991
|
const warnings = plan.prune.length > 0 ? [CONCURRENT_SESSION_WARNING] : [];
|
|
32127
31992
|
if (o.json) console.log(JSON.stringify({ ...plan, warnings, applied: result ?? null }));
|
|
32128
31993
|
else console.log(renderPluginCachePlan(plan, result));
|