@mutmutco/cli 3.82.0 → 3.84.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 +493 -269
- 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
|
|
@@ -6431,7 +6431,14 @@ async function provisionWorktree(worktreeRoot, deps) {
|
|
|
6431
6431
|
});
|
|
6432
6432
|
const allDirs = scanInstallDirs(worktreeRoot, fs2);
|
|
6433
6433
|
const targets = npmInstallTargets(allDirs);
|
|
6434
|
-
|
|
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);
|
|
6435
6442
|
const installed = [];
|
|
6436
6443
|
for (const target of targets) {
|
|
6437
6444
|
const cwd = target.dir ? (0, import_node_path10.join)(worktreeRoot, target.dir) : worktreeRoot;
|
|
@@ -6595,7 +6602,7 @@ function commandLadderHint() {
|
|
|
6595
6602
|
}
|
|
6596
6603
|
|
|
6597
6604
|
// src/index.ts
|
|
6598
|
-
var
|
|
6605
|
+
var import_node_path35 = require("node:path");
|
|
6599
6606
|
|
|
6600
6607
|
// src/merge-ci-policy.ts
|
|
6601
6608
|
function resolveMergeCiPolicy(input) {
|
|
@@ -8027,7 +8034,7 @@ async function foldReleaseVersion(deps, model, tag, foldPaths) {
|
|
|
8027
8034
|
if (foldPaths.length === 0) return "no version manifest to fold \u2014 the tag is the version";
|
|
8028
8035
|
const version = tag.replace(/^v/, "");
|
|
8029
8036
|
if (model === "hub-serverless") {
|
|
8030
|
-
await deps.run("node", ["scripts/release-distribution.mjs", "prepare", version]);
|
|
8037
|
+
await deps.run("node", ["scripts/release-distribution.mjs", "prepare", version, "--source-commit", "HEAD"]);
|
|
8031
8038
|
} else {
|
|
8032
8039
|
await installAppFoldDeps(deps);
|
|
8033
8040
|
await deps.run("npm", ["version", version, "--no-git-tag-version", "--allow-same-version"]);
|
|
@@ -8617,16 +8624,33 @@ function partialTrainRecoveryError(cause, input) {
|
|
|
8617
8624
|
const resumeCommand = input.stage === "rc" ? "mmi-cli rcand --resume" : "mmi-cli release --resume";
|
|
8618
8625
|
const tenant2 = input.deployModel === "tenant-container";
|
|
8619
8626
|
const deployLine = tenant2 ? ` - deploy: dispatched by the train after the branch push; recover with \`mmi-cli runtime tenant redeploy ${input.repo} ${input.stage} --watch\` once the branch and release exist.` : ` - deploy: this repo is \`${input.deployModel ?? "not tenant-container"}\` \u2014 it has NO tenant runtime. Its own release/main-push workflows publish and deploy once the branch and Release exist. Do NOT run the tenant redeployer.`;
|
|
8620
|
-
|
|
8621
|
-
`${causeMessage}
|
|
8622
|
-
|
|
8623
|
-
partial train state for ${input.tag}:
|
|
8627
|
+
const stateBlock = `partial train state for ${input.tag}:
|
|
8624
8628
|
- tag ${input.tag}: PUSHED to origin
|
|
8625
8629
|
- origin/${branch}: NOT pushed
|
|
8626
8630
|
- ${releaseState}
|
|
8627
|
-
` + deployLine
|
|
8631
|
+
` + deployLine;
|
|
8632
|
+
const failedHubMainGate = input.stage === "main" && input.repo.toLowerCase() === HUB_REPO.toLowerCase() && /^required train check failed:/i.test(causeMessage);
|
|
8633
|
+
if (failedHubMainGate) {
|
|
8634
|
+
return new Error(
|
|
8635
|
+
`${causeMessage}
|
|
8636
|
+
|
|
8637
|
+
` + stateBlock + `
|
|
8638
|
+
|
|
8639
|
+
The tag is public and immutable, but it FAILED a required gate. Do not resume it unchanged: \`${resumeCommand}\` cannot alter tagged code or make the failed check pass.
|
|
8628
8640
|
|
|
8629
|
-
|
|
8641
|
+
Diagnose the failed check, then choose the proven recovery:
|
|
8642
|
+
- transient workflow failure: rerun the failed tag workflow; only after it passes, use \`${resumeCommand}\`.
|
|
8643
|
+
- tagged-code defect: obtain fresh destructive approval to remove ${input.tag}, run \`mmi-cli release --abort --apply\`, repair development, and cut the release again through the guarded train.
|
|
8644
|
+
|
|
8645
|
+
Do not push main or create the GitHub Release by hand.`
|
|
8646
|
+
);
|
|
8647
|
+
}
|
|
8648
|
+
return new Error(
|
|
8649
|
+
`${causeMessage}
|
|
8650
|
+
|
|
8651
|
+
` + stateBlock + `
|
|
8652
|
+
|
|
8653
|
+
The tag is public and immutable \u2014 do not delete or force-move it.
|
|
8630
8654
|
|
|
8631
8655
|
Resume it: ${resumeCommand}
|
|
8632
8656
|
|
|
@@ -11683,10 +11707,13 @@ async function executeWaveLand(plan, deps, opts = { preserveWorktree: true }) {
|
|
|
11683
11707
|
}
|
|
11684
11708
|
|
|
11685
11709
|
// src/index.ts
|
|
11686
|
-
var
|
|
11710
|
+
var import_node_os14 = require("node:os");
|
|
11687
11711
|
|
|
11688
11712
|
// src/board.ts
|
|
11689
11713
|
var import_node_child_process8 = require("node:child_process");
|
|
11714
|
+
var import_node_fs20 = require("node:fs");
|
|
11715
|
+
var import_node_os7 = require("node:os");
|
|
11716
|
+
var import_node_path18 = require("node:path");
|
|
11690
11717
|
var import_node_util6 = require("node:util");
|
|
11691
11718
|
|
|
11692
11719
|
// src/board-priority.ts
|
|
@@ -14841,6 +14868,25 @@ function applyOrgMarketplacePins(path2, names) {
|
|
|
14841
14868
|
"pin"
|
|
14842
14869
|
);
|
|
14843
14870
|
}
|
|
14871
|
+
function writeMarketplacePinPending(path2, names, now = Date.now()) {
|
|
14872
|
+
try {
|
|
14873
|
+
(0, import_node_fs18.mkdirSync)((0, import_node_path16.dirname)(path2), { recursive: true });
|
|
14874
|
+
(0, import_node_fs18.writeFileSync)(path2, `${JSON.stringify({ v: 1, names: [...names], at: new Date(now).toISOString() })}
|
|
14875
|
+
`, "utf8");
|
|
14876
|
+
} catch {
|
|
14877
|
+
}
|
|
14878
|
+
}
|
|
14879
|
+
function readMarketplacePinPending(path2, name, now = Date.now()) {
|
|
14880
|
+
try {
|
|
14881
|
+
const parsed = JSON.parse((0, import_node_fs18.readFileSync)(path2, "utf8"));
|
|
14882
|
+
const at = typeof parsed.at === "string" ? Date.parse(parsed.at) : Number.NaN;
|
|
14883
|
+
if (parsed.v !== 1 || !Array.isArray(parsed.names) || !parsed.names.includes(name) || !Number.isFinite(at)) return void 0;
|
|
14884
|
+
if (now - at < 0 || now - at > 12 * 60 * 6e4) return void 0;
|
|
14885
|
+
return { v: 1, names: parsed.names.filter((n) => typeof n === "string"), at: parsed.at };
|
|
14886
|
+
} catch {
|
|
14887
|
+
return void 0;
|
|
14888
|
+
}
|
|
14889
|
+
}
|
|
14844
14890
|
function restoredPinsLine(pins) {
|
|
14845
14891
|
const named = [...pins].map(([name, want]) => {
|
|
14846
14892
|
const fields = [
|
|
@@ -16129,6 +16175,38 @@ async function postClaimMarkerComment(client, item) {
|
|
|
16129
16175
|
`);
|
|
16130
16176
|
}
|
|
16131
16177
|
}
|
|
16178
|
+
var CLAIM_SESSION_ACTIVITY_MS = 30 * 6e4;
|
|
16179
|
+
var CLAIM_SESSION_PROBE_CACHE_MS = 6e4;
|
|
16180
|
+
var claimSessionProbeCache = /* @__PURE__ */ new Map();
|
|
16181
|
+
function probeLocalClaimSession(marker, now = Date.now()) {
|
|
16182
|
+
if (!marker.session || !marker.host || marker.host.toLowerCase() !== (0, import_node_os7.hostname)().toLowerCase()) return void 0;
|
|
16183
|
+
if (!marker.surface?.toLowerCase().startsWith("claude")) return void 0;
|
|
16184
|
+
const cacheKey = `${marker.host.toLowerCase()}/${marker.session}`;
|
|
16185
|
+
const cached = claimSessionProbeCache.get(cacheKey);
|
|
16186
|
+
if (cached && now - cached.checkedAt <= CLAIM_SESSION_PROBE_CACHE_MS) return cached.state;
|
|
16187
|
+
const remember = (state) => {
|
|
16188
|
+
claimSessionProbeCache.set(cacheKey, { checkedAt: now, state });
|
|
16189
|
+
return state;
|
|
16190
|
+
};
|
|
16191
|
+
const root = (0, import_node_path18.join)((0, import_node_os7.homedir)(), ".claude", "projects");
|
|
16192
|
+
try {
|
|
16193
|
+
const wanted = `${marker.session}.jsonl`.toLowerCase();
|
|
16194
|
+
const pending = [root];
|
|
16195
|
+
while (pending.length) {
|
|
16196
|
+
const dir = pending.pop();
|
|
16197
|
+
for (const entry of (0, import_node_fs20.readdirSync)(dir, { withFileTypes: true })) {
|
|
16198
|
+
const path2 = (0, import_node_path18.join)(dir, entry.name);
|
|
16199
|
+
if (entry.isDirectory()) pending.push(path2);
|
|
16200
|
+
else if (entry.isFile() && entry.name.toLowerCase() === wanted) {
|
|
16201
|
+
return remember(now - (0, import_node_fs20.statSync)(path2).mtimeMs <= CLAIM_SESSION_ACTIVITY_MS ? "live" : "dead");
|
|
16202
|
+
}
|
|
16203
|
+
}
|
|
16204
|
+
}
|
|
16205
|
+
return remember("dead");
|
|
16206
|
+
} catch {
|
|
16207
|
+
return remember("unverifiable");
|
|
16208
|
+
}
|
|
16209
|
+
}
|
|
16132
16210
|
function openPullsFetcher(client) {
|
|
16133
16211
|
const byRepo = /* @__PURE__ */ new Map();
|
|
16134
16212
|
return (repo) => {
|
|
@@ -16146,6 +16224,11 @@ async function gatherClaimLiveness(client, repo, number, fetchOpenPulls) {
|
|
|
16146
16224
|
const comments = await client.restPaginate(`repos/${repo}/issues/${number}/comments`);
|
|
16147
16225
|
out.marker = latestClaimMarker(comments.map((comment) => ({ body: comment.body ?? "" })));
|
|
16148
16226
|
out.markerAgeMs = out.marker ? Date.now() - Date.parse(out.marker.ts) : void 0;
|
|
16227
|
+
if (out.marker) {
|
|
16228
|
+
const state = probeLocalClaimSession(out.marker);
|
|
16229
|
+
if (state === "live" || state === "dead") out.sessionState = state;
|
|
16230
|
+
else if (state === "unverifiable") out.failed.push("session");
|
|
16231
|
+
}
|
|
16149
16232
|
} catch {
|
|
16150
16233
|
out.failed.push("comments");
|
|
16151
16234
|
}
|
|
@@ -16177,8 +16260,10 @@ async function gatherClaimLiveness(client, repo, number, fetchOpenPulls) {
|
|
|
16177
16260
|
}
|
|
16178
16261
|
function liveEvidenceLines(evidence, repo) {
|
|
16179
16262
|
const live = [];
|
|
16180
|
-
if (evidence.marker && evidence.
|
|
16181
|
-
live.push(`claim
|
|
16263
|
+
if (evidence.marker && evidence.sessionState === "live") {
|
|
16264
|
+
live.push(`claim session ${describeClaimMarker(evidence.marker)} has recent local transcript activity`);
|
|
16265
|
+
} else if (evidence.marker && evidence.sessionState !== "dead" && evidence.markerAgeMs !== void 0 && evidence.markerAgeMs < CLAIM_MARKER_LIVE_MS) {
|
|
16266
|
+
live.push(`claim marker from ${describeClaimMarker(evidence.marker)} is only ${formatClaimAge(evidence.markerAgeMs)} old (session not locally probeable)`);
|
|
16182
16267
|
}
|
|
16183
16268
|
if (evidence.openPr) live.push(evidence.openPr);
|
|
16184
16269
|
if (evidence.branch) live.push(`live branch ${evidence.branch} on ${repo}`);
|
|
@@ -16274,9 +16359,9 @@ async function boardDoctor(options, deps = {}) {
|
|
|
16274
16359
|
while (next < claimedItems.length) {
|
|
16275
16360
|
const item = claimedItems[next++];
|
|
16276
16361
|
const evidence = await gatherClaimLiveness(client, item.repository, item.number, fetchOpenPulls);
|
|
16277
|
-
const freshMarker = evidence.markerAgeMs !== void 0 && evidence.markerAgeMs < CLAIM_MARKER_LIVE_MS;
|
|
16362
|
+
const freshMarker = evidence.sessionState === "live" || evidence.sessionState !== "dead" && evidence.markerAgeMs !== void 0 && evidence.markerAgeMs < CLAIM_MARKER_LIVE_MS;
|
|
16278
16363
|
const bits = [
|
|
16279
|
-
evidence.marker ? `claim marker ${describeClaimMarker(evidence.marker)} ${formatClaimAge(evidence.markerAgeMs)} old` : evidence.failed.includes("comments") ? "claim marker unreadable" : "no claim marker",
|
|
16364
|
+
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",
|
|
16280
16365
|
evidence.openPr,
|
|
16281
16366
|
evidence.branch ? `branch ${evidence.branch}` : void 0
|
|
16282
16367
|
].filter((bit) => Boolean(bit)).join("; ");
|
|
@@ -16380,7 +16465,7 @@ async function resolveAutoAddBoardAttach(client, cfg, selector, priority, warn =
|
|
|
16380
16465
|
}
|
|
16381
16466
|
|
|
16382
16467
|
// src/issue-body.ts
|
|
16383
|
-
var
|
|
16468
|
+
var import_node_os8 = require("node:os");
|
|
16384
16469
|
var TextArgError = class extends Error {
|
|
16385
16470
|
constructor(message, code, offendingFlag) {
|
|
16386
16471
|
super(message);
|
|
@@ -16392,7 +16477,7 @@ var TextArgError = class extends Error {
|
|
|
16392
16477
|
offendingFlag;
|
|
16393
16478
|
};
|
|
16394
16479
|
function emptyStdinMessage(fileFlag) {
|
|
16395
|
-
if ((0,
|
|
16480
|
+
if ((0, import_node_os8.platform)() === "win32") {
|
|
16396
16481
|
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)`;
|
|
16397
16482
|
}
|
|
16398
16483
|
return `${fileFlag} - read empty stdin (nothing piped \u2014 pass a heredoc/pipe, or ${fileFlag} <path>)`;
|
|
@@ -17717,7 +17802,7 @@ async function runHotfixStart(deps, options) {
|
|
|
17717
17802
|
}
|
|
17718
17803
|
notes.push(`cherry-picked ${label} onto ${branch} (from origin/main, -x trailer recorded)`);
|
|
17719
17804
|
if (deployModel === "hub-serverless") {
|
|
17720
|
-
await deps.run("node", ["scripts/release-distribution.mjs", "prepare", version]);
|
|
17805
|
+
await deps.run("node", ["scripts/release-distribution.mjs", "prepare", version, "--source-commit", "HEAD"]);
|
|
17721
17806
|
const changedFiles = (await deps.run("node", ["scripts/release-distribution.mjs", "changed-files"])).split("\n").map((s) => s.trim()).filter(Boolean);
|
|
17722
17807
|
await deps.run("git", ["add", "-f", "--", ...changedFiles]);
|
|
17723
17808
|
const staged = await deps.run("git", ["diff", "--cached", "--name-only"]);
|
|
@@ -18575,8 +18660,8 @@ function renderAccessReport(report) {
|
|
|
18575
18660
|
|
|
18576
18661
|
// src/doc-refs-core.ts
|
|
18577
18662
|
var import_node_child_process10 = require("node:child_process");
|
|
18578
|
-
var
|
|
18579
|
-
var
|
|
18663
|
+
var import_node_fs21 = require("node:fs");
|
|
18664
|
+
var import_node_path19 = require("node:path");
|
|
18580
18665
|
var PIN_RE = /<!--\s*pinned by\s+([^:]+?)\s*:\s*"([^"]+)"[^"]*-->/;
|
|
18581
18666
|
var PIN_MENTION_RE = /<!--\s*pinned by\b/;
|
|
18582
18667
|
var FWD_RE = /<!--\s*forward-ref:\s*(\S+?)\s*-->/;
|
|
@@ -18632,7 +18717,7 @@ function checkPins(root, readFile9, docs2) {
|
|
|
18632
18717
|
findings.push({ kind: "malformed-pin", doc, line: pin.line, detail: pin.text });
|
|
18633
18718
|
continue;
|
|
18634
18719
|
}
|
|
18635
|
-
const source = readFile9((0,
|
|
18720
|
+
const source = readFile9((0, import_node_path19.join)(root, pin.file));
|
|
18636
18721
|
if (source == null) {
|
|
18637
18722
|
findings.push({ kind: "missing-test", doc, line: pin.line, detail: pin.file });
|
|
18638
18723
|
continue;
|
|
@@ -18692,7 +18777,7 @@ function checkRefs(root, deps, docs2) {
|
|
|
18692
18777
|
const candidates = [];
|
|
18693
18778
|
const direct = [];
|
|
18694
18779
|
for (const [doc, markdown] of Object.entries(docs2)) {
|
|
18695
|
-
const docDir =
|
|
18780
|
+
const docDir = import_node_path19.posix.dirname(doc);
|
|
18696
18781
|
const base = docDir === "." ? "" : docDir;
|
|
18697
18782
|
const covered = /* @__PURE__ */ new Set();
|
|
18698
18783
|
const markers = [];
|
|
@@ -18701,21 +18786,21 @@ function checkRefs(root, deps, docs2) {
|
|
|
18701
18786
|
direct.push({ kind: "malformed-forward-ref", doc, line: fwd.line, detail: fwd.text });
|
|
18702
18787
|
continue;
|
|
18703
18788
|
}
|
|
18704
|
-
const docRel =
|
|
18705
|
-
const rootRel =
|
|
18789
|
+
const docRel = import_node_path19.posix.normalize(import_node_path19.posix.join(base, fwd.target));
|
|
18790
|
+
const rootRel = import_node_path19.posix.normalize(fwd.target.replace(/^\/+/, ""));
|
|
18706
18791
|
markers.push({ target: fwd.target, line: fwd.line, docRel, rootRel });
|
|
18707
18792
|
covered.add(docRel);
|
|
18708
18793
|
covered.add(rootRel);
|
|
18709
18794
|
}
|
|
18710
18795
|
const links = extractLinks(markdown).map(({ target, line }) => {
|
|
18711
|
-
const resolved =
|
|
18712
|
-
return { target, line, resolved, missing: !exists((0,
|
|
18796
|
+
const resolved = import_node_path19.posix.normalize(import_node_path19.posix.join(base, target));
|
|
18797
|
+
return { target, line, resolved, missing: !exists((0, import_node_path19.join)(root, resolved)) };
|
|
18713
18798
|
});
|
|
18714
18799
|
for (const marker of markers) {
|
|
18715
18800
|
const coversMissing = links.some(
|
|
18716
18801
|
(l) => l.missing && (l.resolved === marker.docRel || l.resolved === marker.rootRel)
|
|
18717
18802
|
);
|
|
18718
|
-
if (!coversMissing && (exists((0,
|
|
18803
|
+
if (!coversMissing && (exists((0, import_node_path19.join)(root, marker.docRel)) || exists((0, import_node_path19.join)(root, marker.rootRel)))) {
|
|
18719
18804
|
direct.push({
|
|
18720
18805
|
kind: "stale-forward-ref",
|
|
18721
18806
|
doc,
|
|
@@ -18726,8 +18811,8 @@ function checkRefs(root, deps, docs2) {
|
|
|
18726
18811
|
}
|
|
18727
18812
|
for (const { ref, line } of extractRefs(markdown)) {
|
|
18728
18813
|
const first = ref.split("/")[0];
|
|
18729
|
-
if (!exists((0,
|
|
18730
|
-
if (!exists((0,
|
|
18814
|
+
if (!exists((0, import_node_path19.join)(root, first))) continue;
|
|
18815
|
+
if (!exists((0, import_node_path19.join)(root, ref))) candidates.push({ kind: "missing-path", doc, line, detail: ref });
|
|
18731
18816
|
}
|
|
18732
18817
|
for (const { target, line, resolved, missing } of links) {
|
|
18733
18818
|
if (resolved.startsWith("..")) {
|
|
@@ -18775,22 +18860,22 @@ function checkCommands(docs2, commandPaths) {
|
|
|
18775
18860
|
return { ok: findings.length === 0, findings, warnings: [] };
|
|
18776
18861
|
}
|
|
18777
18862
|
function readFileOrNull(path2) {
|
|
18778
|
-
return (0,
|
|
18863
|
+
return (0, import_node_fs21.existsSync)(path2) ? (0, import_node_fs21.readFileSync)(path2, "utf8") : null;
|
|
18779
18864
|
}
|
|
18780
18865
|
function walk(dir, root, out) {
|
|
18781
|
-
for (const entry of (0,
|
|
18782
|
-
const full = (0,
|
|
18783
|
-
if ((0,
|
|
18866
|
+
for (const entry of (0, import_node_fs21.readdirSync)(dir)) {
|
|
18867
|
+
const full = (0, import_node_path19.join)(dir, entry);
|
|
18868
|
+
if ((0, import_node_fs21.statSync)(full).isDirectory()) walk(full, root, out);
|
|
18784
18869
|
else if (entry.endsWith(".md")) out.push(full.slice(root.length + 1).replaceAll("\\", "/"));
|
|
18785
18870
|
}
|
|
18786
18871
|
return out;
|
|
18787
18872
|
}
|
|
18788
18873
|
function defaultListDocs(root) {
|
|
18789
|
-
const docsDir = (0,
|
|
18790
|
-
const docs2 = ((0,
|
|
18874
|
+
const docsDir = (0, import_node_path19.join)(root, "docs");
|
|
18875
|
+
const docs2 = ((0, import_node_fs21.existsSync)(docsDir) ? walk(docsDir, root, []) : []).filter(
|
|
18791
18876
|
(rel) => !SKIP_WALK.some((skip) => rel.startsWith(skip))
|
|
18792
18877
|
);
|
|
18793
|
-
return [...ROOT_DOCS.filter((rel) => (0,
|
|
18878
|
+
return [...ROOT_DOCS.filter((rel) => (0, import_node_fs21.existsSync)((0, import_node_path19.join)(root, rel))), ...docs2];
|
|
18794
18879
|
}
|
|
18795
18880
|
function defaultIsIgnored(root, relPaths, exec = import_node_child_process10.execFileSync) {
|
|
18796
18881
|
const inRepo = relPaths.filter((p) => !p.startsWith(".."));
|
|
@@ -18810,14 +18895,14 @@ function defaultIsIgnored(root, relPaths, exec = import_node_child_process10.exe
|
|
|
18810
18895
|
}
|
|
18811
18896
|
function runDocRefs(root, deps = {}) {
|
|
18812
18897
|
const readFile9 = deps.readFile ?? readFileOrNull;
|
|
18813
|
-
const exists = deps.exists ??
|
|
18898
|
+
const exists = deps.exists ?? import_node_fs21.existsSync;
|
|
18814
18899
|
const listDocs = deps.listDocs ?? defaultListDocs;
|
|
18815
18900
|
const isIgnored = deps.isIgnored ?? ((paths) => defaultIsIgnored(root, paths));
|
|
18816
18901
|
const commandPaths = deps.commandPaths ?? null;
|
|
18817
18902
|
const walked = listDocs(root);
|
|
18818
18903
|
const ignoredDocs = walked.length ? isIgnored(walked) : /* @__PURE__ */ new Set();
|
|
18819
18904
|
const docs2 = Object.fromEntries(
|
|
18820
|
-
walked.filter((rel) => !ignoredDocs.has(rel)).map((rel) => [rel, readFile9((0,
|
|
18905
|
+
walked.filter((rel) => !ignoredDocs.has(rel)).map((rel) => [rel, readFile9((0, import_node_path19.join)(root, rel))]).filter(([, body]) => body != null)
|
|
18821
18906
|
);
|
|
18822
18907
|
const refResult = checkRefs(root, { exists, isIgnored }, docs2);
|
|
18823
18908
|
const findings = [
|
|
@@ -18844,8 +18929,8 @@ function runDocRefs(root, deps = {}) {
|
|
|
18844
18929
|
|
|
18845
18930
|
// src/spawn-policy-core.ts
|
|
18846
18931
|
var import_node_child_process11 = require("node:child_process");
|
|
18847
|
-
var
|
|
18848
|
-
var
|
|
18932
|
+
var import_node_fs22 = require("node:fs");
|
|
18933
|
+
var import_node_path20 = require("node:path");
|
|
18849
18934
|
var SPAWNERS = ["spawn", "spawnSync", "exec", "execSync", "execFile", "execFileSync"];
|
|
18850
18935
|
var CALL_SOURCE = String.raw`(^|[^.\w$])(${SPAWNERS.join("|")})\s*\(`;
|
|
18851
18936
|
var SOURCE_EXT = /\.(ts|mts|cts|js|mjs|cjs)$/;
|
|
@@ -18931,7 +19016,7 @@ function runSpawnPolicy(root) {
|
|
|
18931
19016
|
for (const file of files) {
|
|
18932
19017
|
let raw;
|
|
18933
19018
|
try {
|
|
18934
|
-
raw = (0,
|
|
19019
|
+
raw = (0, import_node_fs22.readFileSync)((0, import_node_path20.join)(root, file), "utf8");
|
|
18935
19020
|
} catch {
|
|
18936
19021
|
continue;
|
|
18937
19022
|
}
|
|
@@ -18949,8 +19034,8 @@ function runSpawnPolicy(root) {
|
|
|
18949
19034
|
|
|
18950
19035
|
// src/test-policy-core.ts
|
|
18951
19036
|
var import_node_child_process12 = require("node:child_process");
|
|
18952
|
-
var
|
|
18953
|
-
var
|
|
19037
|
+
var import_node_fs23 = require("node:fs");
|
|
19038
|
+
var import_node_path21 = require("node:path");
|
|
18954
19039
|
var POLICY_FILE = "test-policy.json";
|
|
18955
19040
|
var TEST_RE = /\.(?:test|spec)\.[cm]?[jt]sx?$/;
|
|
18956
19041
|
var PY_TEST_RE = /(?:^|\/)test_[^/]*\.py$|_test\.py$/;
|
|
@@ -19003,7 +19088,7 @@ function isTestPath(path2) {
|
|
|
19003
19088
|
return TEST_RE.test(path2) || PY_TEST_RE.test(path2);
|
|
19004
19089
|
}
|
|
19005
19090
|
function loadPolicy(root, readFile9 = readFileOrNull2) {
|
|
19006
|
-
const raw = readFile9((0,
|
|
19091
|
+
const raw = readFile9((0, import_node_path21.join)(root, POLICY_FILE));
|
|
19007
19092
|
if (raw == null) return { mandatory: [], declared: false };
|
|
19008
19093
|
try {
|
|
19009
19094
|
return { ...JSON.parse(raw), declared: true };
|
|
@@ -19013,7 +19098,7 @@ function loadPolicy(root, readFile9 = readFileOrNull2) {
|
|
|
19013
19098
|
}
|
|
19014
19099
|
function readFileOrNull2(path2) {
|
|
19015
19100
|
try {
|
|
19016
|
-
return (0,
|
|
19101
|
+
return (0, import_node_fs23.readFileSync)(path2, "utf8");
|
|
19017
19102
|
} catch {
|
|
19018
19103
|
return null;
|
|
19019
19104
|
}
|
|
@@ -19040,12 +19125,12 @@ function classify(changed, policy, present = () => false) {
|
|
|
19040
19125
|
const removedProtected = [...removed].filter((p) => protectedBy.has(p)).map((p) => ({ path: p, why: protectedBy.get(p) ?? "" }));
|
|
19041
19126
|
return { mandatoryHits, untestedHits, testChanges, addedTests, removedProtected };
|
|
19042
19127
|
}
|
|
19043
|
-
function unresolvedProtectedEntries(policy, root, exists = (path2) => (0,
|
|
19044
|
-
return (policy.protected ?? []).map((p) => p.path).filter((p) => !exists((0,
|
|
19128
|
+
function unresolvedProtectedEntries(policy, root, exists = (path2) => (0, import_node_fs23.existsSync)(path2)) {
|
|
19129
|
+
return (policy.protected ?? []).map((p) => p.path).filter((p) => !exists((0, import_node_path21.join)(root, p)));
|
|
19045
19130
|
}
|
|
19046
|
-
function unresolvedSatisfiers(policy, root, exists = (path2) => (0,
|
|
19131
|
+
function unresolvedSatisfiers(policy, root, exists = (path2) => (0, import_node_fs23.existsSync)(path2)) {
|
|
19047
19132
|
const declared = (policy.mandatory ?? []).flatMap((m) => m.satisfiedBy ?? []);
|
|
19048
|
-
return [...new Set(declared)].filter((p) => !exists((0,
|
|
19133
|
+
return [...new Set(declared)].filter((p) => !exists((0, import_node_path21.join)(root, p)));
|
|
19049
19134
|
}
|
|
19050
19135
|
function evaluate(changed, policy, present = () => false) {
|
|
19051
19136
|
const { mandatoryHits, untestedHits, testChanges, addedTests, removedProtected } = classify(changed, policy, present);
|
|
@@ -19227,13 +19312,13 @@ function changedFilesSince(base, cwd) {
|
|
|
19227
19312
|
}
|
|
19228
19313
|
function runTestPolicy(root, deps = {}) {
|
|
19229
19314
|
const policy = deps.policy ?? loadPolicy(root);
|
|
19230
|
-
const exists = deps.exists ?? ((path2) => (0,
|
|
19315
|
+
const exists = deps.exists ?? ((path2) => (0, import_node_fs23.existsSync)(path2));
|
|
19231
19316
|
const counts = { mandatoryCount: (policy.mandatory ?? []).length, protectedCount: (policy.protected ?? []).length };
|
|
19232
19317
|
const base = deps.changed ? "(injected)" : resolveBase(root, deps.base);
|
|
19233
19318
|
const refusal = deps.changed ? null : untrustworthyRange(root, base);
|
|
19234
19319
|
const changed = deps.changed ?? (refusal ? [] : changedFilesSince(base, root));
|
|
19235
19320
|
const lookup = deps.override !== void 0 ? { override: deps.override, refusals: [] } : !deps.changed && !refusal ? readOverride(base, root) : { override: null, refusals: [] };
|
|
19236
|
-
const present = (path2) => exists((0,
|
|
19321
|
+
const present = (path2) => exists((0, import_node_path21.join)(root, path2));
|
|
19237
19322
|
const removedByThisDiff = removedPaths(changed);
|
|
19238
19323
|
const staleFindings = [];
|
|
19239
19324
|
const unresolved = unresolvedProtectedEntries(policy, root, exists).filter((p) => !removedByThisDiff.has(p));
|
|
@@ -19389,8 +19474,8 @@ function docsAuditStatus(fetch2, opts) {
|
|
|
19389
19474
|
}
|
|
19390
19475
|
|
|
19391
19476
|
// src/project-info-sync.ts
|
|
19392
|
-
var
|
|
19393
|
-
var
|
|
19477
|
+
var import_node_fs24 = require("node:fs");
|
|
19478
|
+
var import_node_path22 = require("node:path");
|
|
19394
19479
|
var UPDATE_PROJECT_INFO = `mutation($projectId: ID!, $shortDescription: String!, $readme: String!) {
|
|
19395
19480
|
updateProjectV2(input: { projectId: $projectId, shortDescription: $shortDescription, readme: $readme }) {
|
|
19396
19481
|
projectV2 { id }
|
|
@@ -19435,14 +19520,14 @@ function sharedName(entries, fallback) {
|
|
|
19435
19520
|
}
|
|
19436
19521
|
function buildProjectInfoSyncPlan(targetRepo2, project2, projects, repoRoot2) {
|
|
19437
19522
|
if (!project2.projectId) throw new Error(`org project sync-info: ${targetRepo2} registry META has no projectId`);
|
|
19438
|
-
const readmePath = (0,
|
|
19439
|
-
if (!(0,
|
|
19523
|
+
const readmePath = (0, import_node_path22.join)(repoRoot2, "README.md");
|
|
19524
|
+
if (!(0, import_node_fs24.existsSync)(readmePath)) throw new Error(`org project sync-info: ${targetRepo2} has no README.md`);
|
|
19440
19525
|
const entries = entriesFor(project2, projects);
|
|
19441
19526
|
const memberRepos = [...new Set(entries.flatMap((entry) => entry.repos ?? []))].filter((repo) => /^[^/]+\/[^/]+$/.test(repo)).sort((a, b) => a.localeCompare(b));
|
|
19442
19527
|
const projectName = sharedName(entries, project2.name?.trim() || targetRepo2.split("/").pop() || targetRepo2);
|
|
19443
19528
|
if (!memberRepos.length) throw new Error(`org project sync-info: project ${projectName} has no registered member repos`);
|
|
19444
19529
|
const entryNames = entries.map((entry) => entry.name?.trim()).filter((name) => Boolean(name));
|
|
19445
|
-
const shortDescription = memberRepos.length === 1 ? shortDescriptionFromReadme((0,
|
|
19530
|
+
const shortDescription = memberRepos.length === 1 ? shortDescriptionFromReadme((0, import_node_fs24.readFileSync)(readmePath, "utf8")) : `Shared work across ${new Intl.ListFormat("en", { type: "conjunction" }).format(entryNames)}.`;
|
|
19446
19531
|
const lines = [
|
|
19447
19532
|
`# ${projectName}`,
|
|
19448
19533
|
"",
|
|
@@ -19461,8 +19546,8 @@ function buildProjectInfoSyncPlan(targetRepo2, project2, projects, repoRoot2) {
|
|
|
19461
19546
|
const targetBase = `https://github.com/${targetRepo2}`;
|
|
19462
19547
|
const targetBranch = branchFor(targetRepo2, projects);
|
|
19463
19548
|
const orgDocs = [
|
|
19464
|
-
(0,
|
|
19465
|
-
(0,
|
|
19549
|
+
(0, import_node_fs24.existsSync)((0, import_node_path22.join)(repoRoot2, "docs", "org-readme.md")) ? `- [Org identity](${targetBase}/blob/${targetBranch}/docs/org-readme.md)` : "",
|
|
19550
|
+
(0, import_node_fs24.existsSync)((0, import_node_path22.join)(repoRoot2, "docs", "org-architecture.md")) ? `- [Org architecture](${targetBase}/blob/${targetBranch}/docs/org-architecture.md)` : ""
|
|
19466
19551
|
].filter(Boolean);
|
|
19467
19552
|
if (orgDocs.length) lines.push("", "## Organisation docs", "", ...orgDocs);
|
|
19468
19553
|
return { projectId: project2.projectId, projectName, targetRepo: targetRepo2, memberRepos, shortDescription, readme: `${lines.join("\n")}
|
|
@@ -20321,9 +20406,9 @@ function writeError(res) {
|
|
|
20321
20406
|
}
|
|
20322
20407
|
|
|
20323
20408
|
// src/secrets-commands.ts
|
|
20324
|
-
var
|
|
20325
|
-
var
|
|
20326
|
-
var
|
|
20409
|
+
var import_node_fs25 = require("node:fs");
|
|
20410
|
+
var import_node_path23 = require("node:path");
|
|
20411
|
+
var import_node_os9 = require("node:os");
|
|
20327
20412
|
|
|
20328
20413
|
// src/project-runtime.ts
|
|
20329
20414
|
function hasRuntimeSecretContract(contract) {
|
|
@@ -20446,18 +20531,18 @@ function collectMap(value, previous = []) {
|
|
|
20446
20531
|
return [...previous, value];
|
|
20447
20532
|
}
|
|
20448
20533
|
async function decryptRailsCredentials(input) {
|
|
20449
|
-
const appDir = (0,
|
|
20534
|
+
const appDir = (0, import_node_path23.resolve)(input.appDir ?? process.cwd());
|
|
20450
20535
|
const credentialsFile = input.credentialsFile ?? DEFAULT_RAILS_CREDENTIALS_FILE;
|
|
20451
20536
|
const masterKeyFile = input.masterKeyFile ?? DEFAULT_RAILS_MASTER_KEY_FILE;
|
|
20452
|
-
const credentialsPath = (0,
|
|
20453
|
-
const masterKeyPath = (0,
|
|
20537
|
+
const credentialsPath = (0, import_node_path23.resolve)(appDir, credentialsFile);
|
|
20538
|
+
const masterKeyPath = (0, import_node_path23.resolve)(appDir, masterKeyFile);
|
|
20454
20539
|
const env = {
|
|
20455
20540
|
...process.env,
|
|
20456
20541
|
MMI_RAILS_CREDENTIALS_FILE: credentialsPath,
|
|
20457
20542
|
MMI_RAILS_MASTER_KEY_FILE: masterKeyPath
|
|
20458
20543
|
};
|
|
20459
|
-
if ((0,
|
|
20460
|
-
env.RAILS_MASTER_KEY = (0,
|
|
20544
|
+
if ((0, import_node_fs25.existsSync)(masterKeyPath)) {
|
|
20545
|
+
env.RAILS_MASTER_KEY = (0, import_node_fs25.readFileSync)(masterKeyPath, "utf8").trim();
|
|
20461
20546
|
}
|
|
20462
20547
|
const script = [
|
|
20463
20548
|
'require "json"',
|
|
@@ -20467,9 +20552,9 @@ async function decryptRailsCredentials(input) {
|
|
|
20467
20552
|
'config = ActiveSupport::EncryptedConfiguration.new(config_path: config_path, key_path: key_path, env_key: "RAILS_MASTER_KEY", raise_if_missing_key: true)',
|
|
20468
20553
|
"puts JSON.generate(config.config)"
|
|
20469
20554
|
].join("\n");
|
|
20470
|
-
const scriptDir = (0,
|
|
20471
|
-
const scriptPath = (0,
|
|
20472
|
-
(0,
|
|
20555
|
+
const scriptDir = (0, import_node_fs25.mkdtempSync)((0, import_node_path23.join)((0, import_node_os9.tmpdir)(), "mmi-rails-decrypt-"));
|
|
20556
|
+
const scriptPath = (0, import_node_path23.join)(scriptDir, "decrypt.rb");
|
|
20557
|
+
(0, import_node_fs25.writeFileSync)(scriptPath, script, "utf8");
|
|
20473
20558
|
try {
|
|
20474
20559
|
const args = ["exec", "ruby", scriptPath];
|
|
20475
20560
|
const cmd = process.platform === "win32" ? "cmd.exe" : "bundle";
|
|
@@ -20481,7 +20566,7 @@ async function decryptRailsCredentials(input) {
|
|
|
20481
20566
|
});
|
|
20482
20567
|
return JSON.parse(stdout);
|
|
20483
20568
|
} finally {
|
|
20484
|
-
(0,
|
|
20569
|
+
(0, import_node_fs25.rmSync)(scriptDir, { recursive: true, force: true });
|
|
20485
20570
|
}
|
|
20486
20571
|
}
|
|
20487
20572
|
async function readSecretStdin() {
|
|
@@ -20571,7 +20656,7 @@ function registerSecretsCommands(program3) {
|
|
|
20571
20656
|
let body;
|
|
20572
20657
|
if (o.file) {
|
|
20573
20658
|
try {
|
|
20574
|
-
body = (0,
|
|
20659
|
+
body = (0, import_node_fs25.readFileSync)((0, import_node_path23.resolve)(o.file), "utf8");
|
|
20575
20660
|
} catch (e) {
|
|
20576
20661
|
return fail(`secrets org-catalog: cannot read --file ${o.file}: ${e.message}`);
|
|
20577
20662
|
}
|
|
@@ -20676,7 +20761,7 @@ function registerSecretsCommands(program3) {
|
|
|
20676
20761
|
{
|
|
20677
20762
|
...d,
|
|
20678
20763
|
decryptRailsCredentials,
|
|
20679
|
-
removeFile: (path2) => (0,
|
|
20764
|
+
removeFile: (path2) => (0, import_node_fs25.unlinkSync)((0, import_node_path23.resolve)(o.appDir ?? process.cwd(), path2))
|
|
20680
20765
|
},
|
|
20681
20766
|
{
|
|
20682
20767
|
repo: o.repo,
|
|
@@ -20840,7 +20925,7 @@ async function activateAppActor(commandPath3, env, mint) {
|
|
|
20840
20925
|
}
|
|
20841
20926
|
|
|
20842
20927
|
// src/box-commands.ts
|
|
20843
|
-
var
|
|
20928
|
+
var import_node_fs26 = require("node:fs");
|
|
20844
20929
|
|
|
20845
20930
|
// src/box.ts
|
|
20846
20931
|
var BOX_KEYS = {
|
|
@@ -21043,7 +21128,7 @@ function registerBoxCommands(program3) {
|
|
|
21043
21128
|
}
|
|
21044
21129
|
if (o.json) console.log(JSON.stringify({ box: found, incomplete }, null, 2));
|
|
21045
21130
|
else if (o.ssh && o.script) {
|
|
21046
|
-
(0,
|
|
21131
|
+
(0, import_node_fs26.writeFileSync)(o.script, sshRecipeScript(found), "utf8");
|
|
21047
21132
|
console.log(`wrote ${o.script} \u2014 run: bash "${o.script}"`);
|
|
21048
21133
|
} else if (o.ssh) console.log(`${formatSshRecipe(found)}
|
|
21049
21134
|
${SSH_RECIPE_AGENT_NOTE}`);
|
|
@@ -21729,7 +21814,7 @@ function registerSchedulesCommands(program3) {
|
|
|
21729
21814
|
|
|
21730
21815
|
// src/file-lock.ts
|
|
21731
21816
|
var import_promises4 = require("node:fs/promises");
|
|
21732
|
-
var
|
|
21817
|
+
var import_node_path24 = require("node:path");
|
|
21733
21818
|
var sleep = (ms) => new Promise((resolve5) => setTimeout(resolve5, ms));
|
|
21734
21819
|
var IMMEDIATE_RETRY_BUDGET = 3;
|
|
21735
21820
|
var FileLockBusyError = class extends Error {
|
|
@@ -21814,7 +21899,7 @@ async function releaseFileLock(lockPath, guard) {
|
|
|
21814
21899
|
}
|
|
21815
21900
|
async function withFileLock(lockPath, opts, fn) {
|
|
21816
21901
|
const resolved = resolveFileLockOpts(opts);
|
|
21817
|
-
await (0, import_promises4.mkdir)((0,
|
|
21902
|
+
await (0, import_promises4.mkdir)((0, import_node_path24.dirname)(lockPath), { recursive: true }).catch(() => void 0);
|
|
21818
21903
|
const guard = await acquireFileLock(lockPath, resolved, Date.now() + resolved.maxWaitMs);
|
|
21819
21904
|
try {
|
|
21820
21905
|
return await fn();
|
|
@@ -21825,7 +21910,7 @@ async function withFileLock(lockPath, opts, fn) {
|
|
|
21825
21910
|
|
|
21826
21911
|
// src/schedules-lift-command.ts
|
|
21827
21912
|
var import_promises5 = require("node:fs/promises");
|
|
21828
|
-
var
|
|
21913
|
+
var import_node_path25 = require("node:path");
|
|
21829
21914
|
|
|
21830
21915
|
// src/schedules-lift.ts
|
|
21831
21916
|
var SCHEDULE_HEADER_FIELDS = ["schedule", "what", "owner", "cadence", "executor", "llm", "output", "breaks", "kill"];
|
|
@@ -21931,7 +22016,7 @@ async function readWorkflowFiles(dir) {
|
|
|
21931
22016
|
const files = [];
|
|
21932
22017
|
for (const name of names.sort()) {
|
|
21933
22018
|
if (!/\.ya?ml$/.test(name)) continue;
|
|
21934
|
-
files.push({ path: `.github/workflows/${name}`, text: await (0, import_promises5.readFile)((0,
|
|
22019
|
+
files.push({ path: `.github/workflows/${name}`, text: await (0, import_promises5.readFile)((0, import_node_path25.join)(dir, name), "utf8") });
|
|
21935
22020
|
}
|
|
21936
22021
|
return files;
|
|
21937
22022
|
}
|
|
@@ -22015,13 +22100,13 @@ function registerSchedulesLiftCommand(program3, deps = {}) {
|
|
|
22015
22100
|
// src/edge-tunnel.ts
|
|
22016
22101
|
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])?)+$/;
|
|
22017
22102
|
var UPSTREAM_RE = /^https?:\/\/[^/\s]+(?::\d+)?(?:\/.*)?$/;
|
|
22018
|
-
function tunnelNameFromHostname(
|
|
22019
|
-
return
|
|
22103
|
+
function tunnelNameFromHostname(hostname3) {
|
|
22104
|
+
return hostname3.replace(/\./g, "-").slice(0, 63);
|
|
22020
22105
|
}
|
|
22021
|
-
function planInfraTunnel(
|
|
22022
|
-
const host =
|
|
22106
|
+
function planInfraTunnel(hostname3, upstream) {
|
|
22107
|
+
const host = hostname3.trim().toLowerCase();
|
|
22023
22108
|
const origin = upstream.trim();
|
|
22024
|
-
if (!HOSTNAME_RE.test(host)) throw new Error(`invalid hostname ${JSON.stringify(
|
|
22109
|
+
if (!HOSTNAME_RE.test(host)) throw new Error(`invalid hostname ${JSON.stringify(hostname3)}`);
|
|
22025
22110
|
if (!UPSTREAM_RE.test(origin)) throw new Error(`invalid upstream ${JSON.stringify(upstream)} \u2014 expected http(s)://host:port`);
|
|
22026
22111
|
const tunnelName = tunnelNameFromHostname(host);
|
|
22027
22112
|
const configYaml = [
|
|
@@ -22473,9 +22558,9 @@ function registerQueryCommands(program3) {
|
|
|
22473
22558
|
}
|
|
22474
22559
|
|
|
22475
22560
|
// src/bootstrap-commands.ts
|
|
22476
|
-
var
|
|
22477
|
-
var
|
|
22478
|
-
var
|
|
22561
|
+
var import_node_fs27 = require("node:fs");
|
|
22562
|
+
var import_node_os10 = require("node:os");
|
|
22563
|
+
var import_node_path26 = require("node:path");
|
|
22479
22564
|
|
|
22480
22565
|
// src/bootstrap-drift.ts
|
|
22481
22566
|
function byteComparableSeeds(manifest, cls) {
|
|
@@ -22981,7 +23066,7 @@ function renderBootstrapVerifyReport(report) {
|
|
|
22981
23066
|
// src/bootstrap-org-ruleset.ts
|
|
22982
23067
|
var ORG_NO_AGENT_FILES_RULESET_NAME = "mmi-no-agent-files-org";
|
|
22983
23068
|
var ORG_LOGIN = "mutmutco";
|
|
22984
|
-
var ORG_NO_AGENT_FILES_EXCLUDED_REPOS = ["Jerv-PowerTools"];
|
|
23069
|
+
var ORG_NO_AGENT_FILES_EXCLUDED_REPOS = ["Jerv-PowerTools", "Jerv-JervCode"];
|
|
22985
23070
|
var NO_AGENT_FILES_GLOBS = [
|
|
22986
23071
|
"AGENTS.md",
|
|
22987
23072
|
"**/AGENTS.md",
|
|
@@ -23058,8 +23143,8 @@ OK \u2014 live ruleset matches the codified IaC (${NO_AGENT_FILES_GLOBS.length}
|
|
|
23058
23143
|
`${head}`,
|
|
23059
23144
|
verb,
|
|
23060
23145
|
...plan.drift.map((d) => ` - ${d}`),
|
|
23061
|
-
"
|
|
23062
|
-
"
|
|
23146
|
+
"Read-only preview \u2014 a master-admin may reconcile this exact desired state with",
|
|
23147
|
+
"`mmi-cli bootstrap org-ruleset --apply`."
|
|
23063
23148
|
].join("\n");
|
|
23064
23149
|
}
|
|
23065
23150
|
async function fetchOrgNoAgentFilesRuleset(client, org = ORG_LOGIN) {
|
|
@@ -23068,6 +23153,27 @@ async function fetchOrgNoAgentFilesRuleset(client, org = ORG_LOGIN) {
|
|
|
23068
23153
|
if (summary?.id == null) return null;
|
|
23069
23154
|
return await client.rest("GET", `orgs/${org}/rulesets/${summary.id}`, { timeoutMs: 2e4 }) ?? null;
|
|
23070
23155
|
}
|
|
23156
|
+
async function reconcileOrgNoAgentFilesRuleset(plan, client, org = ORG_LOGIN) {
|
|
23157
|
+
if (plan.action === "noop") {
|
|
23158
|
+
return { action: "noop", id: plan.id, verified: true, desired: plan.desired };
|
|
23159
|
+
}
|
|
23160
|
+
if (plan.action === "create") {
|
|
23161
|
+
await client.rest("POST", `orgs/${org}/rulesets`, { body: plan.desired, timeoutMs: 2e4 });
|
|
23162
|
+
} else {
|
|
23163
|
+
if (plan.id == null) throw new Error(`cannot update '${plan.desired.name}': live ruleset id is missing`);
|
|
23164
|
+
await client.rest("PUT", `orgs/${org}/rulesets/${plan.id}`, { body: plan.desired, timeoutMs: 2e4 });
|
|
23165
|
+
}
|
|
23166
|
+
const verified = planOrgNoAgentFilesRuleset(await fetchOrgNoAgentFilesRuleset(client, org));
|
|
23167
|
+
if (verified.action !== "noop") {
|
|
23168
|
+
throw new Error(`org ruleset reconcile did not settle: ${verified.drift.join("; ")}`);
|
|
23169
|
+
}
|
|
23170
|
+
return {
|
|
23171
|
+
action: plan.action === "create" ? "created" : "updated",
|
|
23172
|
+
id: verified.id,
|
|
23173
|
+
verified: true,
|
|
23174
|
+
desired: verified.desired
|
|
23175
|
+
};
|
|
23176
|
+
}
|
|
23071
23177
|
|
|
23072
23178
|
// src/bootstrap-commands.ts
|
|
23073
23179
|
function registerBootstrapCommands(program3) {
|
|
@@ -23089,13 +23195,13 @@ function registerBootstrapCommands(program3) {
|
|
|
23089
23195
|
client: defaultGitHubClient(),
|
|
23090
23196
|
projectMeta: meta,
|
|
23091
23197
|
deployModel: typeof meta?.deployModel === "string" ? meta.deployModel : void 0,
|
|
23092
|
-
readLocalFile: (path2) => path2 === "projects.json" && apiProjects != null ? apiProjects : (0,
|
|
23198
|
+
readLocalFile: (path2) => path2 === "projects.json" && apiProjects != null ? apiProjects : (0, import_node_fs27.existsSync)(path2) ? (0, import_node_fs27.readFileSync)(path2, "utf8") : null,
|
|
23093
23199
|
// requiredGcpApis is stored as an array by a JSON write, but `org project set --var KEY=VALUE` stores a raw
|
|
23094
23200
|
// comma-string — accept either so the seeded value verifies regardless of how it was written.
|
|
23095
23201
|
// #3689: the same committed map the org access audit reads (#3664), so a sanctioned admin is not a
|
|
23096
23202
|
// permanent bootstrap failure on one surface and an intended state on the other. Absent file → no
|
|
23097
23203
|
// sanction, which is the pre-#3664 behaviour.
|
|
23098
|
-
sanctionedAdmins: (0,
|
|
23204
|
+
sanctionedAdmins: (0, import_node_fs27.existsSync)("access-matrix.json") ? entriesValueByCanonicalRepo(loadSanctionedAdmins((0, import_node_fs27.readFileSync)("access-matrix.json", "utf8")), repo) : void 0,
|
|
23099
23205
|
requiredGcpApis: (() => {
|
|
23100
23206
|
const v = meta?.requiredGcpApis;
|
|
23101
23207
|
if (Array.isArray(v)) return v;
|
|
@@ -23122,23 +23228,38 @@ function registerBootstrapCommands(program3) {
|
|
|
23122
23228
|
console.log(o.json ? JSON.stringify(report, null, 2) : renderBootstrapVerifyReport(report));
|
|
23123
23229
|
if (!report.ok) process.exitCode = 1;
|
|
23124
23230
|
});
|
|
23125
|
-
bootstrap.command("org-ruleset").description("drift-check the codified org no-agent-files push ruleset
|
|
23231
|
+
bootstrap.command("org-ruleset").description("drift-check the codified org no-agent-files push ruleset; optionally reconcile it under master authority (#2196/#4001)").option("--json", "machine-readable output").option("--apply", "create or update the one named org ruleset to exact desired state (master-admin required)").action(async () => {
|
|
23126
23232
|
const json = rawFlag("--json");
|
|
23127
|
-
const
|
|
23233
|
+
const apply = rawFlag("--apply");
|
|
23234
|
+
const client = defaultGitHubClient();
|
|
23235
|
+
const live = await fetchOrgNoAgentFilesRuleset(client);
|
|
23128
23236
|
const plan = planOrgNoAgentFilesRuleset(live);
|
|
23129
|
-
if (
|
|
23130
|
-
|
|
23131
|
-
|
|
23237
|
+
if (!apply) {
|
|
23238
|
+
if (json) console.log(JSON.stringify({ action: plan.action, id: plan.id, drift: plan.drift, desired: plan.desired }, null, 2));
|
|
23239
|
+
else console.log(renderOrgRulesetDriftReport(plan));
|
|
23240
|
+
if (plan.action !== "noop") process.exitCode = 1;
|
|
23241
|
+
return;
|
|
23242
|
+
}
|
|
23243
|
+
const verdict = await fetchTrainAuthority("mutmutco/MMI-Hub", registryClientDeps(await loadConfig()));
|
|
23244
|
+
if (!verdict.ok || verdict.authority.role !== "master") {
|
|
23245
|
+
return fail("bootstrap org-ruleset --apply: master-admin required");
|
|
23246
|
+
}
|
|
23247
|
+
const result = await reconcileOrgNoAgentFilesRuleset(plan, client);
|
|
23248
|
+
if (json) console.log(JSON.stringify(result, null, 2));
|
|
23249
|
+
else {
|
|
23250
|
+
console.log(`mmi-cli bootstrap org-ruleset --apply: ${result.action}`);
|
|
23251
|
+
console.log(renderOrgRulesetDriftReport(planOrgNoAgentFilesRuleset(await fetchOrgNoAgentFilesRuleset(client))));
|
|
23252
|
+
}
|
|
23132
23253
|
});
|
|
23133
23254
|
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 () => {
|
|
23134
23255
|
const o = { repo: rawValue("--repo", ""), json: rawFlag("--json") };
|
|
23135
23256
|
const manifestPath = "skills/bootstrap/seeds/manifest.json";
|
|
23136
|
-
if (!(0,
|
|
23137
|
-
const manifest = loadBootstrapSeeds((0,
|
|
23257
|
+
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`);
|
|
23258
|
+
const manifest = loadBootstrapSeeds((0, import_node_fs27.readFileSync)(manifestPath, "utf8"));
|
|
23138
23259
|
const hubContents = /* @__PURE__ */ new Map();
|
|
23139
23260
|
for (const s of manifest.seeds) {
|
|
23140
23261
|
if (s.ownership !== "org" || s.source !== "self") continue;
|
|
23141
|
-
hubContents.set(s.target, (0,
|
|
23262
|
+
hubContents.set(s.target, (0, import_node_fs27.existsSync)(s.target) ? (0, import_node_fs27.readFileSync)(s.target, "utf8") : null);
|
|
23142
23263
|
}
|
|
23143
23264
|
let targets;
|
|
23144
23265
|
let classOf = (_repo) => "deployable";
|
|
@@ -23215,8 +23336,8 @@ function registerBootstrapCommands(program3) {
|
|
|
23215
23336
|
return fail(`bootstrap apply: ${e.message}`);
|
|
23216
23337
|
}
|
|
23217
23338
|
const manifestPath = "skills/bootstrap/seeds/manifest.json";
|
|
23218
|
-
if (!(0,
|
|
23219
|
-
const manifest = loadBootstrapSeeds((0,
|
|
23339
|
+
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`);
|
|
23340
|
+
const manifest = loadBootstrapSeeds((0, import_node_fs27.readFileSync)(manifestPath, "utf8"));
|
|
23220
23341
|
const baseBranch = o.class === "content" ? "main" : "development";
|
|
23221
23342
|
const slug = parsedRepo.slug;
|
|
23222
23343
|
const onlyTarget = o.only.trim();
|
|
@@ -23227,16 +23348,16 @@ function registerBootstrapCommands(program3) {
|
|
|
23227
23348
|
${known}`);
|
|
23228
23349
|
}
|
|
23229
23350
|
const gh = async (args) => execFileP2("gh", args, { timeout: 2e4 });
|
|
23230
|
-
const readFile9 = (p) => (0,
|
|
23351
|
+
const readFile9 = (p) => (0, import_node_fs27.existsSync)(p) ? (0, import_node_fs27.readFileSync)(p, "utf8") : null;
|
|
23231
23352
|
const enc = (p) => p.split("/").map(encodeURIComponent).join("/");
|
|
23232
23353
|
const putSeed = async (target, content, ref, sha) => {
|
|
23233
|
-
const tmp = (0,
|
|
23234
|
-
(0,
|
|
23354
|
+
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`);
|
|
23355
|
+
(0, import_node_fs27.writeFileSync)(tmp, JSON.stringify(contentPutBody(target, content, ref, sha)), "utf8");
|
|
23235
23356
|
try {
|
|
23236
23357
|
await gh(contentPutInputArgs(repo, target, tmp));
|
|
23237
23358
|
} finally {
|
|
23238
23359
|
try {
|
|
23239
|
-
(0,
|
|
23360
|
+
(0, import_node_fs27.unlinkSync)(tmp);
|
|
23240
23361
|
} catch {
|
|
23241
23362
|
}
|
|
23242
23363
|
}
|
|
@@ -23488,12 +23609,12 @@ LIVE apply to ${repo}:
|
|
|
23488
23609
|
}
|
|
23489
23610
|
|
|
23490
23611
|
// src/stage-commands.ts
|
|
23491
|
-
var
|
|
23492
|
-
var
|
|
23612
|
+
var import_node_fs29 = require("node:fs");
|
|
23613
|
+
var import_node_path28 = require("node:path");
|
|
23493
23614
|
|
|
23494
23615
|
// src/port-registry.ts
|
|
23495
|
-
var
|
|
23496
|
-
var
|
|
23616
|
+
var import_node_fs28 = require("node:fs");
|
|
23617
|
+
var import_node_path27 = require("node:path");
|
|
23497
23618
|
|
|
23498
23619
|
// ../infra/port-geometry.mjs
|
|
23499
23620
|
var PORT_BLOCK = 100;
|
|
@@ -23507,8 +23628,8 @@ function nextPortBlock(registry2) {
|
|
|
23507
23628
|
return [base, base + PORT_SPAN];
|
|
23508
23629
|
}
|
|
23509
23630
|
function loadPortRegistry(path2) {
|
|
23510
|
-
if (!(0,
|
|
23511
|
-
const raw = JSON.parse((0,
|
|
23631
|
+
if (!(0, import_node_fs28.existsSync)(path2)) return {};
|
|
23632
|
+
const raw = JSON.parse((0, import_node_fs28.readFileSync)(path2, "utf8"));
|
|
23512
23633
|
const out = {};
|
|
23513
23634
|
for (const [key, value] of Object.entries(raw)) {
|
|
23514
23635
|
if (Array.isArray(value) && value.length === 2 && value.every((n) => typeof n === "number")) {
|
|
@@ -23522,9 +23643,9 @@ function ensurePortRange(repo, path2) {
|
|
|
23522
23643
|
const existing = registry2[repo];
|
|
23523
23644
|
if (existing) return existing;
|
|
23524
23645
|
const range = nextPortBlock(registry2);
|
|
23525
|
-
const raw = (0,
|
|
23646
|
+
const raw = (0, import_node_fs28.existsSync)(path2) ? JSON.parse((0, import_node_fs28.readFileSync)(path2, "utf8")) : {};
|
|
23526
23647
|
raw[repo] = range;
|
|
23527
|
-
(0,
|
|
23648
|
+
(0, import_node_fs28.writeFileSync)(path2, JSON.stringify(raw, null, 2) + "\n", "utf8");
|
|
23528
23649
|
return range;
|
|
23529
23650
|
}
|
|
23530
23651
|
function portCursorSeed(registry2) {
|
|
@@ -23546,22 +23667,22 @@ function existingPortRange(repo, registry2) {
|
|
|
23546
23667
|
return registry2[repo] ?? null;
|
|
23547
23668
|
}
|
|
23548
23669
|
function portRangeInfraAt(root, source) {
|
|
23549
|
-
const registryPath = (0,
|
|
23550
|
-
const ddbScriptPath = (0,
|
|
23551
|
-
if (!(0,
|
|
23670
|
+
const registryPath = (0, import_node_path27.join)(root, "infra", "port-ranges.json");
|
|
23671
|
+
const ddbScriptPath = (0, import_node_path27.join)(root, "infra", "port-ddb.mjs");
|
|
23672
|
+
if (!(0, import_node_fs28.existsSync)(registryPath) || !(0, import_node_fs28.existsSync)(ddbScriptPath)) return null;
|
|
23552
23673
|
return { root, source, registryPath, ddbScriptPath };
|
|
23553
23674
|
}
|
|
23554
23675
|
function resolvePortRangeInfra(cwd, packageDir) {
|
|
23555
23676
|
const direct = portRangeInfraAt(cwd, "cwd");
|
|
23556
23677
|
if (direct) return direct;
|
|
23557
|
-
for (let dir = cwd; ; dir = (0,
|
|
23558
|
-
const sibling = portRangeInfraAt((0,
|
|
23678
|
+
for (let dir = cwd; ; dir = (0, import_node_path27.dirname)(dir)) {
|
|
23679
|
+
const sibling = portRangeInfraAt((0, import_node_path27.join)(dir, "MMI-Hub"), "sibling-hub");
|
|
23559
23680
|
if (sibling) return sibling;
|
|
23560
|
-
const parent = (0,
|
|
23681
|
+
const parent = (0, import_node_path27.dirname)(dir);
|
|
23561
23682
|
if (parent === dir) break;
|
|
23562
23683
|
}
|
|
23563
23684
|
if (packageDir) {
|
|
23564
|
-
const pkgRoot = (0,
|
|
23685
|
+
const pkgRoot = (0, import_node_path27.join)(packageDir, "..", "..");
|
|
23565
23686
|
const pkgFrom = portRangeInfraAt(pkgRoot, "pkg-root");
|
|
23566
23687
|
if (pkgFrom) return pkgFrom;
|
|
23567
23688
|
}
|
|
@@ -23737,8 +23858,8 @@ function registerStageCommands(program3) {
|
|
|
23737
23858
|
const portRange = portRangeMeta && typeof portRangeMeta.start === "number" && typeof portRangeMeta.end === "number" ? [portRangeMeta.start, portRangeMeta.end] : void 0;
|
|
23738
23859
|
return decideStage({
|
|
23739
23860
|
registry: { deployModel: project2?.deployModel, portRange, error: read.ok ? void 0 : read.error },
|
|
23740
|
-
hasCompose: (0,
|
|
23741
|
-
hasEnvExample: (0,
|
|
23861
|
+
hasCompose: (0, import_node_fs29.existsSync)((0, import_node_path28.join)(process.cwd(), "docker-compose.yml")),
|
|
23862
|
+
hasEnvExample: (0, import_node_fs29.existsSync)((0, import_node_path28.join)(process.cwd(), ".env.example"))
|
|
23742
23863
|
});
|
|
23743
23864
|
}
|
|
23744
23865
|
async function fetchStageVaultEnvMerge() {
|
|
@@ -24176,10 +24297,10 @@ function registerBoardCommands(program3) {
|
|
|
24176
24297
|
}
|
|
24177
24298
|
|
|
24178
24299
|
// src/merge-cleanup.ts
|
|
24179
|
-
var
|
|
24300
|
+
var import_node_fs30 = require("node:fs");
|
|
24180
24301
|
var import_promises7 = require("node:fs/promises");
|
|
24181
|
-
var
|
|
24182
|
-
var
|
|
24302
|
+
var import_node_path30 = require("node:path");
|
|
24303
|
+
var import_node_os11 = require("node:os");
|
|
24183
24304
|
var import_node_child_process14 = require("node:child_process");
|
|
24184
24305
|
|
|
24185
24306
|
// src/board-advance.ts
|
|
@@ -24266,7 +24387,7 @@ function boardAdvanceFailureMessage(result) {
|
|
|
24266
24387
|
|
|
24267
24388
|
// src/deferred-registry-store.ts
|
|
24268
24389
|
var import_promises6 = require("node:fs/promises");
|
|
24269
|
-
var
|
|
24390
|
+
var import_node_path29 = require("node:path");
|
|
24270
24391
|
var sleep2 = (ms) => new Promise((resolve5) => setTimeout(resolve5, ms));
|
|
24271
24392
|
async function atomicWrite(target, contents) {
|
|
24272
24393
|
const tmp = `${target}.tmp-${process.pid}-${Math.random().toString(36).slice(2)}`;
|
|
@@ -24317,12 +24438,12 @@ function makeDeferredWorktreeStore(registryPath, lockOpts = {}) {
|
|
|
24317
24438
|
},
|
|
24318
24439
|
// Standalone atomic write — THROWS on failure (no best-effort swallow, #2846).
|
|
24319
24440
|
write: async (entries) => {
|
|
24320
|
-
await (0, import_promises6.mkdir)((0,
|
|
24441
|
+
await (0, import_promises6.mkdir)((0, import_node_path29.dirname)(registryPath), { recursive: true });
|
|
24321
24442
|
await atomicWrite(registryPath, serializeDeferredWorktrees(entries));
|
|
24322
24443
|
},
|
|
24323
24444
|
// Serialized read-modify-write under the repo-wide lock (#2846).
|
|
24324
24445
|
update: async (mutate) => {
|
|
24325
|
-
await (0, import_promises6.mkdir)((0,
|
|
24446
|
+
await (0, import_promises6.mkdir)((0, import_node_path29.dirname)(registryPath), { recursive: true });
|
|
24326
24447
|
const deadline = Date.now() + opts.maxWaitMs;
|
|
24327
24448
|
for (; ; ) {
|
|
24328
24449
|
const guard = await acquireLock(lockPath, opts, deadline);
|
|
@@ -24483,7 +24604,7 @@ async function applyGcPlan(plan, remote, opts = {}) {
|
|
|
24483
24604
|
);
|
|
24484
24605
|
const repoRoot2 = (await execFileP2("git", ["rev-parse", "--show-toplevel"], { timeout: GIT_TIMEOUT_MS }).catch(() => ({ stdout: "" }))).stdout.trim() || process.cwd();
|
|
24485
24606
|
const worktreeGitRoot = await currentRepoWorktreeGitRoot(repoRoot2);
|
|
24486
|
-
const primaryRepoRoot = worktreeGitRoot ? (0,
|
|
24607
|
+
const primaryRepoRoot = worktreeGitRoot ? (0, import_node_path30.dirname)((0, import_node_path30.dirname)(worktreeGitRoot)) : repoRoot2;
|
|
24487
24608
|
const gcActor = describeActor({ env: process.env, surface: detectSurface(process.env), cwd: process.cwd() });
|
|
24488
24609
|
const owners = readWorktreeOwners(primaryRepoRoot);
|
|
24489
24610
|
const removalNow = Date.now();
|
|
@@ -24514,7 +24635,7 @@ async function applyGcPlan(plan, remote, opts = {}) {
|
|
|
24514
24635
|
const cleanup = await cleanupPrMergeLocalBranch(branch.branch, {
|
|
24515
24636
|
beforeWorktrees,
|
|
24516
24637
|
startingPath: branch.worktreePath,
|
|
24517
|
-
pathExists: (p) => (0,
|
|
24638
|
+
pathExists: (p) => (0, import_node_fs30.existsSync)(p),
|
|
24518
24639
|
execGit: async (args) => (await execFileP2("git", args, { timeout: GIT_TIMEOUT_MS })).stdout,
|
|
24519
24640
|
teardownWorktreeStage,
|
|
24520
24641
|
deferredStore,
|
|
@@ -24542,7 +24663,7 @@ async function applyGcPlan(plan, remote, opts = {}) {
|
|
|
24542
24663
|
for (const wt of worktreeDirsToRemove) {
|
|
24543
24664
|
try {
|
|
24544
24665
|
const cleanupTarget = resolveSafeSiblingWorktreeCleanupTarget(wt.path, siblingRoot, {
|
|
24545
|
-
realpath: (path2) => (0,
|
|
24666
|
+
realpath: (path2) => (0, import_node_fs30.realpathSync)(path2)
|
|
24546
24667
|
});
|
|
24547
24668
|
if (!cleanupTarget.ok) {
|
|
24548
24669
|
result.failed.push(`${wt.path}: ${cleanupTarget.reason}`);
|
|
@@ -24607,13 +24728,13 @@ async function composeOverrideBodyFile(prNumber, repoArgs, gh) {
|
|
|
24607
24728
|
const commits = JSON.parse(raw).commits ?? [];
|
|
24608
24729
|
const body = squashBodyWithOverride(commits.map((c) => ({ headline: c.messageHeadline ?? "", body: c.messageBody ?? "" })), process.cwd());
|
|
24609
24730
|
if (!body) return void 0;
|
|
24610
|
-
const dir = (0,
|
|
24611
|
-
const path2 = (0,
|
|
24612
|
-
(0,
|
|
24731
|
+
const dir = (0, import_node_fs30.mkdtempSync)((0, import_node_path30.join)((0, import_node_os11.tmpdir)(), "mmi-squash-body-"));
|
|
24732
|
+
const path2 = (0, import_node_path30.join)(dir, "body.txt");
|
|
24733
|
+
(0, import_node_fs30.writeFileSync)(path2, `${body}
|
|
24613
24734
|
`, "utf8");
|
|
24614
24735
|
return { path: path2, cleanup: () => {
|
|
24615
24736
|
try {
|
|
24616
|
-
(0,
|
|
24737
|
+
(0, import_node_fs30.rmSync)(dir, { recursive: true, force: true });
|
|
24617
24738
|
} catch {
|
|
24618
24739
|
}
|
|
24619
24740
|
} };
|
|
@@ -24735,13 +24856,13 @@ var realWorktreeDirRemover = {
|
|
|
24735
24856
|
probe: (p) => {
|
|
24736
24857
|
let st;
|
|
24737
24858
|
try {
|
|
24738
|
-
st = (0,
|
|
24859
|
+
st = (0, import_node_fs30.lstatSync)(p);
|
|
24739
24860
|
} catch {
|
|
24740
24861
|
return null;
|
|
24741
24862
|
}
|
|
24742
24863
|
if (st.isSymbolicLink()) return "link";
|
|
24743
24864
|
try {
|
|
24744
|
-
(0,
|
|
24865
|
+
(0, import_node_fs30.readlinkSync)(p);
|
|
24745
24866
|
return "link";
|
|
24746
24867
|
} catch {
|
|
24747
24868
|
}
|
|
@@ -24749,7 +24870,7 @@ var realWorktreeDirRemover = {
|
|
|
24749
24870
|
},
|
|
24750
24871
|
readdir: (p) => {
|
|
24751
24872
|
try {
|
|
24752
|
-
return (0,
|
|
24873
|
+
return (0, import_node_fs30.readdirSync)(p);
|
|
24753
24874
|
} catch {
|
|
24754
24875
|
return [];
|
|
24755
24876
|
}
|
|
@@ -24758,9 +24879,9 @@ var realWorktreeDirRemover = {
|
|
|
24758
24879
|
// leaving the target); a file symlink with unlink. rmdir first, fall back to unlink.
|
|
24759
24880
|
detachLink: (p) => {
|
|
24760
24881
|
try {
|
|
24761
|
-
(0,
|
|
24882
|
+
(0, import_node_fs30.rmdirSync)(p);
|
|
24762
24883
|
} catch {
|
|
24763
|
-
(0,
|
|
24884
|
+
(0, import_node_fs30.unlinkSync)(p);
|
|
24764
24885
|
}
|
|
24765
24886
|
},
|
|
24766
24887
|
removeTree: (p) => (0, import_promises7.rm)(p, { recursive: true, force: true, maxRetries: 5, retryDelay: 200 })
|
|
@@ -24793,9 +24914,9 @@ async function worktreeHasStageState(worktreePath) {
|
|
|
24793
24914
|
}
|
|
24794
24915
|
}
|
|
24795
24916
|
function stageStateFileBelongsToWorktree(statePath, worktreePath) {
|
|
24796
|
-
if (!(0,
|
|
24917
|
+
if (!(0, import_node_fs30.existsSync)(statePath)) return false;
|
|
24797
24918
|
try {
|
|
24798
|
-
const state = JSON.parse((0,
|
|
24919
|
+
const state = JSON.parse((0, import_node_fs30.readFileSync)(statePath, "utf8"));
|
|
24799
24920
|
const recordedCwd = typeof state.identity?.cwd === "string" ? state.identity.cwd : typeof state.cwd === "string" ? state.cwd : "";
|
|
24800
24921
|
return Boolean(recordedCwd && isPathUnderDirectory(recordedCwd, worktreePath));
|
|
24801
24922
|
} catch {
|
|
@@ -25019,9 +25140,9 @@ async function fetchRestCorePool(gh = defaultGhApi) {
|
|
|
25019
25140
|
}
|
|
25020
25141
|
|
|
25021
25142
|
// src/worktree-lifecycle-commands.ts
|
|
25022
|
-
var
|
|
25143
|
+
var import_node_fs31 = require("node:fs");
|
|
25023
25144
|
var import_promises8 = require("node:fs/promises");
|
|
25024
|
-
var
|
|
25145
|
+
var import_node_path31 = require("node:path");
|
|
25025
25146
|
var GH_TIMEOUT_MS = 2e4;
|
|
25026
25147
|
var DEFAULT_BASE = "origin/development";
|
|
25027
25148
|
var DEFAULT_REMOTE = "origin";
|
|
@@ -25093,6 +25214,16 @@ function orphanLandSteps(lostBranch, hasStage) {
|
|
|
25093
25214
|
steps.push("prune worktree metadata");
|
|
25094
25215
|
return steps;
|
|
25095
25216
|
}
|
|
25217
|
+
function detachedLandSteps(hasStage) {
|
|
25218
|
+
const steps = [];
|
|
25219
|
+
if (hasStage) steps.push("stop spawned dev stage");
|
|
25220
|
+
steps.push("remove worktree (detached HEAD)");
|
|
25221
|
+
steps.push("prune worktree metadata");
|
|
25222
|
+
return steps;
|
|
25223
|
+
}
|
|
25224
|
+
function shouldContinueLandCleanup(removeStatus) {
|
|
25225
|
+
return removeStatus === "removed";
|
|
25226
|
+
}
|
|
25096
25227
|
function classifyStaleLeaks(input) {
|
|
25097
25228
|
const protectedBranches = input.protectedBranches ?? PROTECTED_BRANCHES2;
|
|
25098
25229
|
const leaks = [];
|
|
@@ -25157,7 +25288,7 @@ function classifyStaleLeaks(input) {
|
|
|
25157
25288
|
var defaultOrphanDirScanDeps = {
|
|
25158
25289
|
listDirs: (root) => {
|
|
25159
25290
|
try {
|
|
25160
|
-
return (0,
|
|
25291
|
+
return (0, import_node_fs31.readdirSync)(root, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => (0, import_node_path31.join)(root, e.name));
|
|
25161
25292
|
} catch {
|
|
25162
25293
|
return [];
|
|
25163
25294
|
}
|
|
@@ -25302,15 +25433,21 @@ function registerWorktreeCommands(program3) {
|
|
|
25302
25433
|
try {
|
|
25303
25434
|
const wtPath = (await execFileP2("git", ["rev-parse", "--show-toplevel"], { timeout: GIT_TIMEOUT_MS })).stdout.trim();
|
|
25304
25435
|
const headBorn = await execFileP2("git", ["-C", wtPath || ".", "rev-parse", "--verify", "--quiet", "HEAD"], { timeout: GIT_TIMEOUT_MS }).then(() => true).catch(() => false);
|
|
25305
|
-
const
|
|
25436
|
+
const symbolicBranch = (await execFileP2(
|
|
25437
|
+
"git",
|
|
25438
|
+
["-C", wtPath || ".", "symbolic-ref", "--quiet", "--short", "HEAD"],
|
|
25439
|
+
{ timeout: GIT_TIMEOUT_MS }
|
|
25440
|
+
).catch(() => ({ stdout: "" }))).stdout.trim();
|
|
25441
|
+
const detached = headBorn && !symbolicBranch;
|
|
25442
|
+
const branch = symbolicBranch || (detached ? "HEAD" : "");
|
|
25306
25443
|
if (!wtPath || !branch) return fail("worktree land: not inside a git worktree");
|
|
25307
|
-
const gitFile = (0,
|
|
25308
|
-
const isLinked = (0,
|
|
25444
|
+
const gitFile = (0, import_node_path31.join)(wtPath, ".git");
|
|
25445
|
+
const isLinked = (0, import_node_fs31.existsSync)(gitFile) && (0, import_node_fs31.statSync)(gitFile).isFile();
|
|
25309
25446
|
if (apply && !isLinked) {
|
|
25310
25447
|
return fail("worktree land: run from inside the linked worktree you want to land (this is the primary checkout)");
|
|
25311
25448
|
}
|
|
25312
25449
|
const commonDir = (await execFileP2("git", ["rev-parse", "--git-common-dir"], { timeout: GIT_TIMEOUT_MS }).catch(() => ({ stdout: "" }))).stdout.trim();
|
|
25313
|
-
const primaryCheckout = commonDir ? (0,
|
|
25450
|
+
const primaryCheckout = commonDir ? (0, import_node_path31.dirname)(commonDir) : wtPath;
|
|
25314
25451
|
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);
|
|
25315
25452
|
const orphan = classifyOrphanedWorktree({
|
|
25316
25453
|
branch,
|
|
@@ -25323,7 +25460,7 @@ function registerWorktreeCommands(program3) {
|
|
|
25323
25460
|
}
|
|
25324
25461
|
const stageSummary = readStageSummary(wtPath);
|
|
25325
25462
|
const hasStage = stageSummary != null;
|
|
25326
|
-
const steps = orphan.orphan ? orphanLandSteps(orphan.lostBranch, hasStage) : landSteps(branch, true, hasStage);
|
|
25463
|
+
const steps = detached ? detachedLandSteps(hasStage) : orphan.orphan ? orphanLandSteps(orphan.lostBranch, hasStage) : landSteps(branch, true, hasStage);
|
|
25327
25464
|
const plan = {
|
|
25328
25465
|
command: "worktree land",
|
|
25329
25466
|
branch,
|
|
@@ -25332,13 +25469,15 @@ function registerWorktreeCommands(program3) {
|
|
|
25332
25469
|
remote: o.remote,
|
|
25333
25470
|
hasStage,
|
|
25334
25471
|
steps,
|
|
25335
|
-
...orphan.orphan ? { orphan: { kind: orphan.kind, lostBranch: orphan.lostBranch, reason: orphan.reason } } : {}
|
|
25472
|
+
...orphan.orphan ? { orphan: { kind: orphan.kind, lostBranch: orphan.lostBranch, reason: orphan.reason } } : {},
|
|
25473
|
+
...detached ? { detached: true } : {}
|
|
25336
25474
|
};
|
|
25337
25475
|
if (!apply) {
|
|
25338
25476
|
const preview = { dryRun: true, ...plan, ...o.keepRemote ? { keepRemote: true } : {} };
|
|
25339
25477
|
if (o.json) return console.log(JSON.stringify(preview, null, 2));
|
|
25340
25478
|
const lines = [`worktree land: dry-run (pass --apply to execute)`, ` branch: ${branch}`, ` worktree: ${wtPath}`];
|
|
25341
|
-
if (
|
|
25479
|
+
if (detached) lines.push(" detached HEAD: no branch refs will be deleted");
|
|
25480
|
+
else if (orphan.orphan) lines.push(` orphaned: ${orphan.reason} \u2014 no branch refs will be deleted`);
|
|
25342
25481
|
if (hasStage) lines.push(` stage: port ${stageSummary.port} (will be stopped)`);
|
|
25343
25482
|
if (o.keepRemote && !orphan.orphan) lines.push(` remote branch: kept (--keep-remote)`);
|
|
25344
25483
|
for (const s of steps) lines.push(` - ${s}`);
|
|
@@ -25360,15 +25499,16 @@ function registerWorktreeCommands(program3) {
|
|
|
25360
25499
|
unreferenced: await isCommitUnreferenced(orphanTip, async (args) => (await execFileP2("git", ["-C", primaryCheckout, ...args], { timeout: GIT_TIMEOUT_MS })).stdout),
|
|
25361
25500
|
recoverCommand: `git -C "${primaryCheckout}" branch <name> ${orphanTip}`
|
|
25362
25501
|
} : void 0;
|
|
25363
|
-
const
|
|
25502
|
+
const treeOnly = orphan.orphan || detached;
|
|
25503
|
+
const landPrs = treeOnly ? void 0 : await fetchLandBranchPrs(branch);
|
|
25364
25504
|
const mergeVerdict = classifyLandBranchMergeState(landPrs);
|
|
25365
|
-
const landRefs =
|
|
25505
|
+
const landRefs = treeOnly ? { action: "proceed" } : decideWorktreeLandRefCleanup({ branch, prs: landPrs, keepRemote: Boolean(o.keepRemote) });
|
|
25366
25506
|
if (landRefs.action === "refuse") {
|
|
25367
25507
|
return fail(`worktree land: ${landRefs.message}`, { code: ERROR_CODES.ERR_BAD_ENUM });
|
|
25368
25508
|
}
|
|
25369
25509
|
const keepRemoteBranch = Boolean(o.keepRemote) || landRefs.action === "keep-remote";
|
|
25370
25510
|
if (landRefs.action === "keep-remote") console.warn(`worktree land: ${landRefs.message}.`);
|
|
25371
|
-
const reportedMergeState =
|
|
25511
|
+
const reportedMergeState = treeOnly ? "not-checked" : mergeVerdict.state;
|
|
25372
25512
|
const landOwner = lookupWorktreeOwner(primaryCheckout, wtPath);
|
|
25373
25513
|
const report = [];
|
|
25374
25514
|
if (hasStage) {
|
|
@@ -25388,6 +25528,24 @@ function registerWorktreeCommands(program3) {
|
|
|
25388
25528
|
step: "remove worktree",
|
|
25389
25529
|
status: removeOutcome.status === "removed" ? removeOutcome.recovery ? `done (${removeOutcome.recovery})` : "done" : `failed: ${removeOutcome.error ?? "lock held"} \u2014 run: git -C "${primaryCheckout}" worktree remove --force "${wtPath}"`
|
|
25390
25530
|
});
|
|
25531
|
+
if (!shouldContinueLandCleanup(removeOutcome.status)) {
|
|
25532
|
+
const result2 = {
|
|
25533
|
+
dryRun: false,
|
|
25534
|
+
...plan,
|
|
25535
|
+
...o.keepRemote ? { keepRemote: true } : {},
|
|
25536
|
+
mergeState: reportedMergeState,
|
|
25537
|
+
prNumbers: mergeVerdict.numbers,
|
|
25538
|
+
cleanupState: "partial",
|
|
25539
|
+
report
|
|
25540
|
+
};
|
|
25541
|
+
if (o.json) console.log(JSON.stringify(result2, null, 2));
|
|
25542
|
+
else {
|
|
25543
|
+
console.error(`worktree land: cleanup stopped after the worktree removal failed; branch refs and metadata were left untouched`);
|
|
25544
|
+
for (const row of report) console.log(` ${row.step}: ${row.status}`);
|
|
25545
|
+
}
|
|
25546
|
+
process.exitCode = 1;
|
|
25547
|
+
return;
|
|
25548
|
+
}
|
|
25391
25549
|
const landActor = describeActor({ env: process.env, surface: detectSurface(process.env), cwd: wtPath });
|
|
25392
25550
|
appendWorktreeEvent(primaryCheckout, {
|
|
25393
25551
|
action: removeOutcome.status === "removed" ? "removed" : "failed",
|
|
@@ -25402,8 +25560,11 @@ function registerWorktreeCommands(program3) {
|
|
|
25402
25560
|
reason: orphan.orphan ? `orphaned worktree \u2014 ${orphan.reason}${lastCommit ? `; last commit ${lastCommit.oid}${lastCommit.unreferenced ? " (on no ref)" : ""}` : ""}` : describeLandMergeState(mergeVerdict)
|
|
25403
25561
|
});
|
|
25404
25562
|
if (removeOutcome.status === "removed") dropWorktreeOwner(primaryCheckout, wtPath);
|
|
25405
|
-
if (
|
|
25406
|
-
report.push({
|
|
25563
|
+
if (treeOnly) {
|
|
25564
|
+
report.push({
|
|
25565
|
+
step: "delete branch refs",
|
|
25566
|
+
status: detached ? "skipped: detached HEAD has no branch refs" : `skipped: orphaned worktree \u2014 ${orphan.reason}`
|
|
25567
|
+
});
|
|
25407
25568
|
if (lastCommit) {
|
|
25408
25569
|
report.push({
|
|
25409
25570
|
step: "last commit",
|
|
@@ -25431,12 +25592,12 @@ function registerWorktreeCommands(program3) {
|
|
|
25431
25592
|
report
|
|
25432
25593
|
};
|
|
25433
25594
|
if (o.json) return console.log(JSON.stringify(result, null, 2));
|
|
25434
|
-
console.log(
|
|
25595
|
+
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)})`);
|
|
25435
25596
|
for (const r of report) console.log(` ${r.step}: ${r.status}`);
|
|
25436
25597
|
if (lastCommit?.unreferenced) {
|
|
25437
25598
|
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}`);
|
|
25438
25599
|
}
|
|
25439
|
-
if (!
|
|
25600
|
+
if (!treeOnly && mergeVerdict.state !== "merged") {
|
|
25440
25601
|
console.warn(`worktree land: '${branch}' was cleaned up but NOT merged \u2014 ${describeLandMergeState(mergeVerdict)}. Do not report this work as shipped.`);
|
|
25441
25602
|
}
|
|
25442
25603
|
if (report.some((r) => r.status.startsWith("failed"))) process.exitCode = 1;
|
|
@@ -25497,10 +25658,10 @@ async function gatherWorktreeContext() {
|
|
|
25497
25658
|
if (s) stages.push({ path: wt.path, port: s.port });
|
|
25498
25659
|
}
|
|
25499
25660
|
const worktreeGitRoot = await currentRepoWorktreeGitRoot(repoRoot2);
|
|
25500
|
-
const primaryRepoRoot = worktreeGitRoot ? (0,
|
|
25661
|
+
const primaryRepoRoot = worktreeGitRoot ? (0, import_node_path31.dirname)((0, import_node_path31.dirname)(worktreeGitRoot)) : repoRoot2;
|
|
25501
25662
|
const wtRoot = siblingMmiWorktreesRoot(primaryRepoRoot);
|
|
25502
25663
|
let orphanDirs = [];
|
|
25503
|
-
if ((0,
|
|
25664
|
+
if ((0, import_node_fs31.existsSync)(wtRoot)) {
|
|
25504
25665
|
orphanDirs = scanOrphanDirs(wtRoot, worktreeGitRoot, {
|
|
25505
25666
|
...defaultOrphanDirScanDeps,
|
|
25506
25667
|
listDirs: (root) => worktreeScanDirs(root, primaryRepoRoot, defaultOrphanDirScanDeps.listDirs, isRepoCheckoutDir)
|
|
@@ -25514,16 +25675,19 @@ async function bestEffortGit(args, cwd, step, timeoutMs = GIT_TIMEOUT_MS) {
|
|
|
25514
25675
|
await execFileP2("git", fullArgs, { timeout: timeoutMs });
|
|
25515
25676
|
return { step, status: "done" };
|
|
25516
25677
|
} catch (e) {
|
|
25517
|
-
const
|
|
25518
|
-
|
|
25678
|
+
const err = e;
|
|
25679
|
+
const detail = `${err.message}
|
|
25680
|
+
${err.stderr ?? ""}`;
|
|
25681
|
+
if (/not found|already|no such|does not exist|not a valid|couldn't|unable to resolve/i.test(detail)) {
|
|
25519
25682
|
return { step, status: "already gone" };
|
|
25520
25683
|
}
|
|
25684
|
+
const msg = (err.stderr?.trim() || err.message).split("\n")[0];
|
|
25521
25685
|
return { step, status: `failed: ${msg}` };
|
|
25522
25686
|
}
|
|
25523
25687
|
}
|
|
25524
25688
|
|
|
25525
25689
|
// src/issue-commands.ts
|
|
25526
|
-
var
|
|
25690
|
+
var import_node_fs32 = require("node:fs");
|
|
25527
25691
|
var import_node_crypto5 = require("node:crypto");
|
|
25528
25692
|
var ghRunner = async (args, timeoutMs) => (await execFileP2("gh", args, { timeout: timeoutMs })).stdout;
|
|
25529
25693
|
var ReparentConflictError = class extends Error {
|
|
@@ -25541,7 +25705,7 @@ async function editIssue(client, options, deps = {}) {
|
|
|
25541
25705
|
const url = `https://github.com/${repo}/issues/${parsed.number}`;
|
|
25542
25706
|
const patch = {};
|
|
25543
25707
|
let bodyChanged = false;
|
|
25544
|
-
const textDeps = () => deps.textDeps ?? { readFile: (p, e) => Promise.resolve((0,
|
|
25708
|
+
const textDeps = () => deps.textDeps ?? { readFile: (p, e) => Promise.resolve((0, import_node_fs32.readFileSync)(p, e)), readStdin: () => Promise.resolve("") };
|
|
25545
25709
|
if (options.titleFile !== void 0) {
|
|
25546
25710
|
patch.title = await resolveIssueTitle({ title: options.title, titleFile: options.titleFile }, textDeps());
|
|
25547
25711
|
} else if (options.title !== void 0) {
|
|
@@ -26104,7 +26268,7 @@ function extendCreateCommand(issue2, batchAttach) {
|
|
|
26104
26268
|
if (opts.batch) {
|
|
26105
26269
|
let specs;
|
|
26106
26270
|
try {
|
|
26107
|
-
const raw = (0,
|
|
26271
|
+
const raw = (0, import_node_fs32.readFileSync)(opts.batch, "utf8");
|
|
26108
26272
|
specs = JSON.parse(raw);
|
|
26109
26273
|
if (!Array.isArray(specs)) throw new Error("batch file must contain a JSON array");
|
|
26110
26274
|
} catch (e) {
|
|
@@ -26178,8 +26342,8 @@ ${lines}`, {
|
|
|
26178
26342
|
}
|
|
26179
26343
|
|
|
26180
26344
|
// src/train-commands.ts
|
|
26181
|
-
var
|
|
26182
|
-
var
|
|
26345
|
+
var import_node_fs33 = require("node:fs");
|
|
26346
|
+
var import_node_path32 = require("node:path");
|
|
26183
26347
|
|
|
26184
26348
|
// src/train-status.ts
|
|
26185
26349
|
function buildTrainStatusReport(input) {
|
|
@@ -26219,7 +26383,7 @@ function formatTrainStatus(r) {
|
|
|
26219
26383
|
// src/train-commands.ts
|
|
26220
26384
|
function readRepoVersion() {
|
|
26221
26385
|
try {
|
|
26222
|
-
return JSON.parse((0,
|
|
26386
|
+
return JSON.parse((0, import_node_fs33.readFileSync)((0, import_node_path32.join)(process.cwd(), ".claude-plugin", "plugin.json"), "utf8")).version || void 0;
|
|
26223
26387
|
} catch {
|
|
26224
26388
|
return void 0;
|
|
26225
26389
|
}
|
|
@@ -26365,11 +26529,12 @@ function registerDeployCommands(program3) {
|
|
|
26365
26529
|
}
|
|
26366
26530
|
|
|
26367
26531
|
// src/discovery-commands.ts
|
|
26368
|
-
var
|
|
26369
|
-
var
|
|
26370
|
-
var
|
|
26532
|
+
var import_node_fs34 = require("node:fs");
|
|
26533
|
+
var import_node_os12 = require("node:os");
|
|
26534
|
+
var import_node_path33 = require("node:path");
|
|
26371
26535
|
var GC_GH_TIMEOUT_MS3 = 2e4;
|
|
26372
26536
|
async function collectStatus() {
|
|
26537
|
+
const repo = await resolveRepo();
|
|
26373
26538
|
let branch = "";
|
|
26374
26539
|
try {
|
|
26375
26540
|
branch = (await execFileP2("git", ["rev-parse", "--abbrev-ref", "HEAD"], { timeout: GIT_TIMEOUT_MS })).stdout.trim();
|
|
@@ -26398,7 +26563,7 @@ async function collectStatus() {
|
|
|
26398
26563
|
}
|
|
26399
26564
|
let claimedItems = [];
|
|
26400
26565
|
try {
|
|
26401
|
-
const cfg = await
|
|
26566
|
+
const cfg = await loadConfigOrDiscover();
|
|
26402
26567
|
if (cfg.sagaApiUrl) {
|
|
26403
26568
|
const report = await readBoard({ config: cfg });
|
|
26404
26569
|
claimedItems = report.primary.userOwned.map((item) => ({
|
|
@@ -26421,7 +26586,7 @@ async function collectStatus() {
|
|
|
26421
26586
|
} catch {
|
|
26422
26587
|
stage = { running: false };
|
|
26423
26588
|
}
|
|
26424
|
-
return { branch, worktrees, myOpenPrs, claimedItems, stage };
|
|
26589
|
+
return { repo, branch, worktrees, myOpenPrs, claimedItems, stage };
|
|
26425
26590
|
}
|
|
26426
26591
|
var PRIORITY_RANK = {
|
|
26427
26592
|
Urgent: 0,
|
|
@@ -26429,14 +26594,14 @@ var PRIORITY_RANK = {
|
|
|
26429
26594
|
Medium: 2,
|
|
26430
26595
|
Low: 3
|
|
26431
26596
|
};
|
|
26432
|
-
async function recommendNext(deps) {
|
|
26433
|
-
const load = deps?.loadConfig ??
|
|
26597
|
+
async function recommendNext(repo, deps) {
|
|
26598
|
+
const load = deps?.loadConfig ?? loadConfigForRepo;
|
|
26434
26599
|
const reader = deps?.readBoard ?? readBoard;
|
|
26435
|
-
const cfg = await load();
|
|
26600
|
+
const cfg = await load(repo);
|
|
26436
26601
|
if (!cfg.sagaApiUrl) throw new Error("Hub API URL not configured \u2014 the board was NOT read (run `mmi-cli doctor`)");
|
|
26437
26602
|
let report;
|
|
26438
26603
|
try {
|
|
26439
|
-
report = await reader({ config: cfg });
|
|
26604
|
+
report = await reader({ config: cfg, repo });
|
|
26440
26605
|
} catch (e) {
|
|
26441
26606
|
throw new Error(
|
|
26442
26607
|
`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).`
|
|
@@ -26542,10 +26707,10 @@ async function collectOnboardStatus() {
|
|
|
26542
26707
|
else if (top) nextCommand = `mmi-cli board claim ${top.number} # ${top.title}`;
|
|
26543
26708
|
else nextCommand = "mmi-cli board read \u2014 no claimable items found";
|
|
26544
26709
|
}
|
|
26545
|
-
const home = (0,
|
|
26710
|
+
const home = (0, import_node_os12.homedir)();
|
|
26546
26711
|
const plugin = onboardPluginGate({
|
|
26547
|
-
readKnown: () => readFileSyncSafe((0,
|
|
26548
|
-
readSettings: () => readFileSyncSafe((0,
|
|
26712
|
+
readKnown: () => readFileSyncSafe((0, import_node_path33.join)(home, ...KNOWN_MARKETPLACES_RELATIVE), import_node_fs34.readFileSync),
|
|
26713
|
+
readSettings: () => readFileSyncSafe((0, import_node_path33.join)(home, ".claude", "settings.json"), import_node_fs34.readFileSync)
|
|
26549
26714
|
});
|
|
26550
26715
|
return { track, board, registry: registry2, secrets, plugin, nextCommand };
|
|
26551
26716
|
}
|
|
@@ -26556,6 +26721,7 @@ function registerDiscoveryCommands(program3) {
|
|
|
26556
26721
|
if (o.json) {
|
|
26557
26722
|
console.log(JSON.stringify(report, null, 2));
|
|
26558
26723
|
} else {
|
|
26724
|
+
console.log(`repo: ${report.repo ?? "unknown"}`);
|
|
26559
26725
|
console.log(`branch: ${report.branch}`);
|
|
26560
26726
|
console.log(`worktrees: ${report.worktrees.length} linked`);
|
|
26561
26727
|
console.log(`my open PRs: ${report.myOpenPrs.length}`);
|
|
@@ -26566,9 +26732,9 @@ function registerDiscoveryCommands(program3) {
|
|
|
26566
26732
|
fail(`status: ${e.message}`);
|
|
26567
26733
|
}
|
|
26568
26734
|
});
|
|
26569
|
-
program3.command("next").description("recommend the next actionable board item (claimable, unblocked, priority-ranked)").action(async () => {
|
|
26735
|
+
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) => {
|
|
26570
26736
|
try {
|
|
26571
|
-
const item = await recommendNext();
|
|
26737
|
+
const item = await recommendNext(o.repo);
|
|
26572
26738
|
if (!item) {
|
|
26573
26739
|
console.log("No claimable board items found (board read OK).");
|
|
26574
26740
|
return;
|
|
@@ -28688,17 +28854,17 @@ function parseOriginRepo(remoteUrl) {
|
|
|
28688
28854
|
}
|
|
28689
28855
|
function ghHostsConfigPath(env, platform2) {
|
|
28690
28856
|
const sep2 = platform2 === "win32" ? "\\" : "/";
|
|
28691
|
-
const
|
|
28857
|
+
const join30 = (...parts) => parts.join(sep2);
|
|
28692
28858
|
const explicit = env.GH_CONFIG_DIR?.trim();
|
|
28693
|
-
if (explicit) return
|
|
28859
|
+
if (explicit) return join30(explicit, "hosts.yml");
|
|
28694
28860
|
if (platform2 === "win32") {
|
|
28695
28861
|
const appData = (env.AppData ?? env.APPDATA)?.trim();
|
|
28696
|
-
return appData ?
|
|
28862
|
+
return appData ? join30(appData, "GitHub CLI", "hosts.yml") : void 0;
|
|
28697
28863
|
}
|
|
28698
28864
|
const xdg = env.XDG_CONFIG_HOME?.trim();
|
|
28699
|
-
if (xdg) return
|
|
28865
|
+
if (xdg) return join30(xdg, "gh", "hosts.yml");
|
|
28700
28866
|
const home = env.HOME?.trim();
|
|
28701
|
-
return home ?
|
|
28867
|
+
return home ? join30(home, ".config", "gh", "hosts.yml") : void 0;
|
|
28702
28868
|
}
|
|
28703
28869
|
function parseGhHostsAccounts(yaml, host = "github.com") {
|
|
28704
28870
|
let hostIndent = null;
|
|
@@ -28748,9 +28914,9 @@ function ghAccountCaveat(announcedLogin, accounts) {
|
|
|
28748
28914
|
}
|
|
28749
28915
|
|
|
28750
28916
|
// src/doctor-io.ts
|
|
28751
|
-
var
|
|
28752
|
-
var
|
|
28753
|
-
var
|
|
28917
|
+
var import_node_fs35 = require("node:fs");
|
|
28918
|
+
var import_node_os13 = require("node:os");
|
|
28919
|
+
var import_node_path34 = require("node:path");
|
|
28754
28920
|
var import_node_child_process15 = require("node:child_process");
|
|
28755
28921
|
var import_node_util8 = require("node:util");
|
|
28756
28922
|
var execFileP6 = (0, import_node_util8.promisify)(import_node_child_process15.execFile);
|
|
@@ -28758,7 +28924,7 @@ var MMI_PLUGIN_ID2 = "mmi@mutmutco";
|
|
|
28758
28924
|
function installedClaudePluginVersion() {
|
|
28759
28925
|
try {
|
|
28760
28926
|
const file = JSON.parse(
|
|
28761
|
-
(0,
|
|
28927
|
+
(0, import_node_fs35.readFileSync)((0, import_node_path34.join)((0, import_node_os13.homedir)(), ".claude", "plugins", "installed_plugins.json"), "utf8")
|
|
28762
28928
|
);
|
|
28763
28929
|
const versions = (file.plugins?.[MMI_PLUGIN_ID2] ?? []).map((r) => r.version).filter((v) => Boolean(v));
|
|
28764
28930
|
if (versions.length === 0) return void 0;
|
|
@@ -28769,7 +28935,7 @@ function installedClaudePluginVersion() {
|
|
|
28769
28935
|
}
|
|
28770
28936
|
function manifestVersion(path2) {
|
|
28771
28937
|
try {
|
|
28772
|
-
const manifest = JSON.parse((0,
|
|
28938
|
+
const manifest = JSON.parse((0, import_node_fs35.readFileSync)(path2, "utf8"));
|
|
28773
28939
|
return typeof manifest.version === "string" && manifest.version.trim() ? manifest.version.trim() : void 0;
|
|
28774
28940
|
} catch {
|
|
28775
28941
|
return void 0;
|
|
@@ -28779,17 +28945,17 @@ function installedSurfacePluginVersion(surface) {
|
|
|
28779
28945
|
const token = surfaceToken(surface);
|
|
28780
28946
|
if (token === "kilo") {
|
|
28781
28947
|
try {
|
|
28782
|
-
const stamp = (0,
|
|
28948
|
+
const stamp = (0, import_node_fs35.readFileSync)((0, import_node_path34.join)((0, import_node_os13.homedir)(), ".kilo", ".mmi-kilo-version"), "utf8").trim();
|
|
28783
28949
|
return stamp || void 0;
|
|
28784
28950
|
} catch {
|
|
28785
28951
|
return void 0;
|
|
28786
28952
|
}
|
|
28787
28953
|
}
|
|
28788
28954
|
if (token === "cursor") {
|
|
28789
|
-
return manifestVersion((0,
|
|
28955
|
+
return manifestVersion((0, import_node_path34.join)(cursorLocalPluginRoot(), ".cursor-plugin", "plugin.json"));
|
|
28790
28956
|
}
|
|
28791
28957
|
if (token === "kimi") {
|
|
28792
|
-
return manifestVersion((0,
|
|
28958
|
+
return manifestVersion((0, import_node_path34.join)(surfaceConfigRoot(surface), "plugins", "managed", "mmi", ".kimi-plugin", "plugin.json"));
|
|
28793
28959
|
}
|
|
28794
28960
|
if (token === "claude") return installedClaudePluginVersion();
|
|
28795
28961
|
if (token !== "codex") return void 0;
|
|
@@ -28827,13 +28993,13 @@ function worktreeRootSync() {
|
|
|
28827
28993
|
}
|
|
28828
28994
|
var gitignorePath = () => {
|
|
28829
28995
|
const root = worktreeRootSync();
|
|
28830
|
-
return root === null ? null : (0,
|
|
28996
|
+
return root === null ? null : (0, import_node_path34.join)(root, ".gitignore");
|
|
28831
28997
|
};
|
|
28832
28998
|
function readGitignore() {
|
|
28833
28999
|
const path2 = gitignorePath();
|
|
28834
29000
|
if (path2 === null) return null;
|
|
28835
29001
|
try {
|
|
28836
|
-
return (0,
|
|
29002
|
+
return (0, import_node_fs35.readFileSync)(path2, "utf8");
|
|
28837
29003
|
} catch {
|
|
28838
29004
|
return null;
|
|
28839
29005
|
}
|
|
@@ -28842,7 +29008,7 @@ function writeGitignore(content) {
|
|
|
28842
29008
|
const path2 = gitignorePath();
|
|
28843
29009
|
if (path2 === null) return false;
|
|
28844
29010
|
try {
|
|
28845
|
-
(0,
|
|
29011
|
+
(0, import_node_fs35.writeFileSync)(path2, content, "utf8");
|
|
28846
29012
|
return true;
|
|
28847
29013
|
} catch {
|
|
28848
29014
|
return false;
|
|
@@ -28866,7 +29032,7 @@ async function repoRoot() {
|
|
|
28866
29032
|
}
|
|
28867
29033
|
function hasRepoLocalWorktrees() {
|
|
28868
29034
|
const root = worktreeRootSync();
|
|
28869
|
-
return root !== null && (0,
|
|
29035
|
+
return root !== null && (0, import_node_fs35.existsSync)((0, import_node_path34.join)(root, ".worktrees"));
|
|
28870
29036
|
}
|
|
28871
29037
|
|
|
28872
29038
|
// src/index.ts
|
|
@@ -28902,8 +29068,8 @@ ${r.stderr ?? ""}`).catch(() => "");
|
|
|
28902
29068
|
function ghMultiAccountCaveat(announcedLogin) {
|
|
28903
29069
|
try {
|
|
28904
29070
|
const hostsPath = ghHostsConfigPath(process.env, process.platform);
|
|
28905
|
-
if (!hostsPath || !(0,
|
|
28906
|
-
return ghAccountCaveat(announcedLogin, parseGhHostsAccounts((0,
|
|
29071
|
+
if (!hostsPath || !(0, import_node_fs36.existsSync)(hostsPath)) return void 0;
|
|
29072
|
+
return ghAccountCaveat(announcedLogin, parseGhHostsAccounts((0, import_node_fs36.readFileSync)(hostsPath, "utf8")));
|
|
28907
29073
|
} catch {
|
|
28908
29074
|
return void 0;
|
|
28909
29075
|
}
|
|
@@ -28911,12 +29077,12 @@ function ghMultiAccountCaveat(announcedLogin) {
|
|
|
28911
29077
|
var ENV_HEAL_LOCK_STALE_MS = 10 * 6e4;
|
|
28912
29078
|
var ENV_HEAL_LOCK_MAX_WAIT_MS = 2 * 6e4;
|
|
28913
29079
|
function envHealLockPath(home) {
|
|
28914
|
-
return (0,
|
|
29080
|
+
return (0, import_node_path35.join)(home, ".claude", "plugins", ".mmi-env-heal.lock");
|
|
28915
29081
|
}
|
|
28916
29082
|
async function withEnvHealLock(what, run) {
|
|
28917
29083
|
try {
|
|
28918
29084
|
return await withFileLock(
|
|
28919
|
-
envHealLockPath((0,
|
|
29085
|
+
envHealLockPath((0, import_node_os14.homedir)()),
|
|
28920
29086
|
{ staleMs: ENV_HEAL_LOCK_STALE_MS, maxWaitMs: ENV_HEAL_LOCK_MAX_WAIT_MS, label: "mmi env-heal lock" },
|
|
28921
29087
|
run
|
|
28922
29088
|
);
|
|
@@ -29013,7 +29179,7 @@ function mmiDoctorDeps(opts = {}) {
|
|
|
29013
29179
|
const configRoot = surfaceConfigRoot(surface);
|
|
29014
29180
|
const running = surface === "codex" ? installedActivePluginVersion(surface) : runningPluginVersion(process.env, resolveClientVersion());
|
|
29015
29181
|
const plan = buildPluginCachePlan(
|
|
29016
|
-
(0,
|
|
29182
|
+
(0, import_node_os14.homedir)(),
|
|
29017
29183
|
running,
|
|
29018
29184
|
pluginCacheFsDeps(configRoot, () => 0),
|
|
29019
29185
|
{ configRoot, includeStaging: surface !== "codex" }
|
|
@@ -29034,15 +29200,31 @@ function mmiDoctorDeps(opts = {}) {
|
|
|
29034
29200
|
marketplaceRows: () => {
|
|
29035
29201
|
try {
|
|
29036
29202
|
if (detectSurface(process.env) === "codex") return [];
|
|
29037
|
-
const home = (0,
|
|
29038
|
-
|
|
29203
|
+
const home = (0, import_node_os14.homedir)();
|
|
29204
|
+
const rows = marketplaceRows(
|
|
29039
29205
|
MMI_MARKETPLACE_NAME,
|
|
29040
|
-
readFileSyncSafe((0,
|
|
29041
|
-
readFileSyncSafe((0,
|
|
29206
|
+
readFileSyncSafe((0, import_node_path35.join)(home, ...KNOWN_MARKETPLACES_RELATIVE), import_node_fs36.readFileSync),
|
|
29207
|
+
readFileSyncSafe((0, import_node_path35.join)(home, ".claude", "settings.json"), import_node_fs36.readFileSync),
|
|
29042
29208
|
// #3974: this CLI heals these rows, so report mode names the doctor run rather than the hand
|
|
29043
29209
|
// edit. Unconditional — it is a fact about mmi-cli, not about the lane this run is on.
|
|
29044
29210
|
true
|
|
29045
29211
|
);
|
|
29212
|
+
const pending = readMarketplacePinPending(
|
|
29213
|
+
(0, import_node_path35.join)(home, ".claude", "plugins", ".mmi-marketplace-pin-pending.json"),
|
|
29214
|
+
MMI_MARKETPLACE_NAME
|
|
29215
|
+
);
|
|
29216
|
+
if (!pending) return rows;
|
|
29217
|
+
return rows.map((row) => {
|
|
29218
|
+
if (row.id !== "marketplace-catalog-ref" || row.ok) return row;
|
|
29219
|
+
const { fix: _fix, reportOnly: _reportOnly, ...rest } = row;
|
|
29220
|
+
return {
|
|
29221
|
+
...rest,
|
|
29222
|
+
ok: true,
|
|
29223
|
+
warn: true,
|
|
29224
|
+
detail: `pending restart \u2014 doctor pinned main at ${pending.at}; restart Claude Code to load it`,
|
|
29225
|
+
verbose: [...row.verbose ?? [], "known_marketplaces.json was rewritten by the running Claude host after the verified doctor write"]
|
|
29226
|
+
};
|
|
29227
|
+
});
|
|
29046
29228
|
} catch {
|
|
29047
29229
|
return [];
|
|
29048
29230
|
}
|
|
@@ -29052,7 +29234,13 @@ function mmiDoctorDeps(opts = {}) {
|
|
|
29052
29234
|
healMarketplacePins: () => {
|
|
29053
29235
|
try {
|
|
29054
29236
|
if (detectSurface(process.env) === "codex") return void 0;
|
|
29055
|
-
|
|
29237
|
+
const home = (0, import_node_os14.homedir)();
|
|
29238
|
+
const names = [MMI_MARKETPLACE_NAME];
|
|
29239
|
+
const result = applyOrgMarketplacePins((0, import_node_path35.join)(home, ...KNOWN_MARKETPLACES_RELATIVE), names);
|
|
29240
|
+
if (result?.startsWith("pinned ")) {
|
|
29241
|
+
writeMarketplacePinPending((0, import_node_path35.join)(home, ".claude", "plugins", ".mmi-marketplace-pin-pending.json"), names);
|
|
29242
|
+
}
|
|
29243
|
+
return result;
|
|
29056
29244
|
} catch {
|
|
29057
29245
|
return void 0;
|
|
29058
29246
|
}
|
|
@@ -29258,19 +29446,19 @@ program2.hook("preAction", async (_thisCommand, actionCommand) => {
|
|
|
29258
29446
|
});
|
|
29259
29447
|
var rules = program2.command("rules").description("org-managed .gitignore delivery");
|
|
29260
29448
|
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) => {
|
|
29261
|
-
const path2 = (0,
|
|
29262
|
-
const current = (0,
|
|
29449
|
+
const path2 = (0, import_node_path35.join)(process.cwd(), ".gitignore");
|
|
29450
|
+
const current = (0, import_node_fs36.existsSync)(path2) ? (0, import_node_fs36.readFileSync)(path2, "utf8") : null;
|
|
29263
29451
|
const plan = planManagedGitignore(current);
|
|
29264
29452
|
const drift = [...plan.added.map((l) => `+${l}`), ...plan.removed.map((l) => `-${l}`)].join(", ") || "block normalize";
|
|
29265
29453
|
if (opts.json) {
|
|
29266
|
-
if (opts.write && plan.changed) (0,
|
|
29454
|
+
if (opts.write && plan.changed) (0, import_node_fs36.writeFileSync)(path2, plan.content, "utf8");
|
|
29267
29455
|
console.log(JSON.stringify(plan, null, 2));
|
|
29268
29456
|
if (!opts.write && plan.changed) process.exitCode = 1;
|
|
29269
29457
|
return;
|
|
29270
29458
|
}
|
|
29271
29459
|
if (opts.write) {
|
|
29272
29460
|
if (plan.changed) {
|
|
29273
|
-
(0,
|
|
29461
|
+
(0, import_node_fs36.writeFileSync)(path2, plan.content, "utf8");
|
|
29274
29462
|
console.log(`mmi-cli org rules gitignore: updated .gitignore (${drift})`);
|
|
29275
29463
|
} else {
|
|
29276
29464
|
console.log("mmi-cli org rules gitignore: up to date");
|
|
@@ -29425,8 +29613,8 @@ gcCmd.option("--dry-run", "show what would be deleted (default)").option("--appl
|
|
|
29425
29613
|
if (!Number.isFinite(limit) || limit < 1) return fail("worktree gc: --limit must be a positive integer");
|
|
29426
29614
|
let root;
|
|
29427
29615
|
if (o.root !== void 0) {
|
|
29428
|
-
root = (0,
|
|
29429
|
-
if (!(0,
|
|
29616
|
+
root = (0, import_node_path35.resolve)(o.root);
|
|
29617
|
+
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`);
|
|
29430
29618
|
const gcRepoRoot = (await execFileP2("git", ["rev-parse", "--show-toplevel"], { timeout: GIT_TIMEOUT_MS }).catch(() => ({ stdout: "" }))).stdout.trim() || process.cwd();
|
|
29431
29619
|
if (isPathUnderDirectory(gcRepoRoot, root)) {
|
|
29432
29620
|
return fail(`worktree gc: --root ${root} contains this checkout \u2014 name a worktrees root, not the repo or an ancestor of it`);
|
|
@@ -29491,7 +29679,7 @@ async function primaryCheckoutRoot(from) {
|
|
|
29491
29679
|
return primaryCheckoutRootOf(async (args) => (await execFileP2("git", ["-C", from, ...args], { timeout: GIT_TIMEOUT_MS })).stdout);
|
|
29492
29680
|
}
|
|
29493
29681
|
async function unprovenWorktreeReason(wtPath, repoRoot2) {
|
|
29494
|
-
if (!(0,
|
|
29682
|
+
if (!(0, import_node_fs36.existsSync)(wtPath)) return `${wtPath} does not exist on disk`;
|
|
29495
29683
|
const porcelain = (await execFileP2("git", ["-C", repoRoot2, "worktree", "list", "--porcelain"], { timeout: GIT_TIMEOUT_MS }).catch(() => ({ stdout: "" }))).stdout;
|
|
29496
29684
|
const registered = parseWorktreePorcelainEntries(porcelain);
|
|
29497
29685
|
if (!registered.length) {
|
|
@@ -29505,6 +29693,7 @@ async function unprovenWorktreeReason(wtPath, repoRoot2) {
|
|
|
29505
29693
|
function makeProvisionDeps(worktreeRoot, quiet, log) {
|
|
29506
29694
|
return {
|
|
29507
29695
|
runInstall: (command, cwd) => runWorktreeInstall(command, cwd, quiet),
|
|
29696
|
+
validateInstall: (cwd) => runWorktreeInstall("npm ls --depth=0 --json", cwd, true).then(() => true).catch(() => false),
|
|
29508
29697
|
primaryCheckout: () => primaryCheckoutRoot(worktreeRoot),
|
|
29509
29698
|
log
|
|
29510
29699
|
};
|
|
@@ -29512,26 +29701,26 @@ function makeProvisionDeps(worktreeRoot, quiet, log) {
|
|
|
29512
29701
|
function acquireWorktreeSetupLock(worktreeRoot) {
|
|
29513
29702
|
const lockPath = repoRuntimeStatePath(worktreeRoot, "worktree-setup.lock");
|
|
29514
29703
|
const take = () => {
|
|
29515
|
-
const fd = (0,
|
|
29704
|
+
const fd = (0, import_node_fs36.openSync)(lockPath, "wx");
|
|
29516
29705
|
try {
|
|
29517
|
-
(0,
|
|
29706
|
+
(0, import_node_fs36.writeSync)(fd, String(Date.now()));
|
|
29518
29707
|
} finally {
|
|
29519
|
-
(0,
|
|
29708
|
+
(0, import_node_fs36.closeSync)(fd);
|
|
29520
29709
|
}
|
|
29521
29710
|
return () => {
|
|
29522
29711
|
try {
|
|
29523
|
-
(0,
|
|
29712
|
+
(0, import_node_fs36.rmSync)(lockPath, { force: true });
|
|
29524
29713
|
} catch {
|
|
29525
29714
|
}
|
|
29526
29715
|
};
|
|
29527
29716
|
};
|
|
29528
29717
|
try {
|
|
29529
|
-
(0,
|
|
29718
|
+
(0, import_node_fs36.mkdirSync)((0, import_node_path35.dirname)(lockPath), { recursive: true });
|
|
29530
29719
|
return take();
|
|
29531
29720
|
} catch {
|
|
29532
29721
|
try {
|
|
29533
|
-
if (Date.now() - (0,
|
|
29534
|
-
(0,
|
|
29722
|
+
if (Date.now() - (0, import_node_fs36.statSync)(lockPath).mtimeMs > WORKTREE_SETUP_LOCK_TTL_MS) {
|
|
29723
|
+
(0, import_node_fs36.rmSync)(lockPath, { force: true });
|
|
29535
29724
|
return take();
|
|
29536
29725
|
}
|
|
29537
29726
|
} catch {
|
|
@@ -29587,22 +29776,52 @@ withExamples(mutating(
|
|
|
29587
29776
|
const localOnly = preferRemote && base !== preferRemote ? ` (no ${preferRemote} \u2014 local ref)` : "";
|
|
29588
29777
|
console.error(` base ${base} ${baseSha}${localOnly}`);
|
|
29589
29778
|
}
|
|
29590
|
-
|
|
29591
|
-
|
|
29592
|
-
|
|
29593
|
-
|
|
29594
|
-
|
|
29595
|
-
|
|
29596
|
-
|
|
29597
|
-
|
|
29779
|
+
const registered = parseWorktreePorcelainEntries((await execFileP2(
|
|
29780
|
+
"git",
|
|
29781
|
+
["-C", repoRoot2, "worktree", "list", "--porcelain"],
|
|
29782
|
+
{ timeout: GIT_TIMEOUT_MS }
|
|
29783
|
+
)).stdout);
|
|
29784
|
+
const exact = registered.find((entry) => samePath(entry.path, wtPath));
|
|
29785
|
+
let resumed = false;
|
|
29786
|
+
if (exact) {
|
|
29787
|
+
step = `resume existing worktree ${wtPath}`;
|
|
29788
|
+
if (exact.branch !== branch) {
|
|
29789
|
+
return fail(`worktree create: ${wtPath} is already registered for '${exact.branch ?? "detached HEAD"}', not '${branch}'`);
|
|
29790
|
+
}
|
|
29791
|
+
const status = (await execFileP2("git", ["-C", wtPath, "status", "--porcelain"], { timeout: GIT_TIMEOUT_MS })).stdout.trim();
|
|
29792
|
+
if (status) return fail(`worktree create: refusing to resume ${wtPath} \u2014 it has uncommitted changes`);
|
|
29793
|
+
const head = await revParseRef(`refs/heads/${branch}`);
|
|
29794
|
+
const baseOid = await revParseRef(base);
|
|
29795
|
+
if (!head || !baseOid) return fail(`worktree create: could not prove '${branch}' and '${base}' before resume`);
|
|
29796
|
+
const canFastForward = await execFileP2(
|
|
29797
|
+
"git",
|
|
29798
|
+
["-C", repoRoot2, "merge-base", "--is-ancestor", head, baseOid],
|
|
29799
|
+
{ timeout: GIT_TIMEOUT_MS }
|
|
29800
|
+
).then(() => true).catch(() => false);
|
|
29801
|
+
if (!canFastForward) {
|
|
29802
|
+
return fail(`worktree create: refusing to resume '${branch}' \u2014 it has local commits or diverges from ${base}`);
|
|
29803
|
+
}
|
|
29804
|
+
await execFileP2("git", ["-C", wtPath, "merge", "--ff-only", base], { timeout: GIT_TIMEOUT_MS });
|
|
29805
|
+
resumed = true;
|
|
29806
|
+
}
|
|
29807
|
+
if (!resumed) {
|
|
29808
|
+
step = `git worktree add ${wtPath}`;
|
|
29809
|
+
await addWorktreeRobust(wtPath, branch, base, {
|
|
29810
|
+
git: async (args) => (await execFileP2("git", args, { timeout: GH_MUTATION_TIMEOUT_MS })).stdout,
|
|
29811
|
+
revParse: async (ref) => {
|
|
29812
|
+
try {
|
|
29813
|
+
return (await execFileP2("git", ["rev-parse", "--verify", ref], { timeout: GIT_TIMEOUT_MS })).stdout.trim() || void 0;
|
|
29814
|
+
} catch {
|
|
29815
|
+
return void 0;
|
|
29816
|
+
}
|
|
29817
|
+
},
|
|
29818
|
+
deleteBranch: (b) => execFileP2("git", ["branch", "-D", b], { timeout: GIT_TIMEOUT_MS }).then(() => void 0),
|
|
29819
|
+
sleep: (ms) => new Promise((resolve5) => setTimeout(resolve5, ms)),
|
|
29820
|
+
log: (m) => {
|
|
29821
|
+
if (!o.json) console.error(` ${m}`);
|
|
29598
29822
|
}
|
|
29599
|
-
}
|
|
29600
|
-
|
|
29601
|
-
sleep: (ms) => new Promise((resolve5) => setTimeout(resolve5, ms)),
|
|
29602
|
-
log: (m) => {
|
|
29603
|
-
if (!o.json) console.error(` ${m}`);
|
|
29604
|
-
}
|
|
29605
|
-
});
|
|
29823
|
+
});
|
|
29824
|
+
}
|
|
29606
29825
|
step = "install deps + copy local-only config";
|
|
29607
29826
|
const report = await provisionWorktree(wtPath, makeProvisionDeps(wtPath, Boolean(o.json), (m) => {
|
|
29608
29827
|
if (!o.json) console.error(` ${m}`);
|
|
@@ -29642,12 +29861,13 @@ withExamples(mutating(
|
|
|
29642
29861
|
branch,
|
|
29643
29862
|
path: wtPath,
|
|
29644
29863
|
base,
|
|
29864
|
+
resumed,
|
|
29645
29865
|
...report,
|
|
29646
29866
|
...issueForm && selector ? { issue: `${selector.repo}#${selector.number}` } : {},
|
|
29647
29867
|
...issueForm && o.claim ? { claim: claimError ? { ok: false, error: claimError } : claim } : {}
|
|
29648
29868
|
}, null, 2));
|
|
29649
29869
|
}
|
|
29650
|
-
console.log(`worktree ready: ${wtPath} (branch ${branch} from ${base})`);
|
|
29870
|
+
console.log(`worktree ready: ${wtPath} (branch ${branch} ${resumed ? "resumed at" : "from"} ${base})`);
|
|
29651
29871
|
console.log(` installed: ${report.installed.map((i) => i.dir || ".").join(", ") || "none"}`);
|
|
29652
29872
|
console.log(` copied: ${report.copied.join(", ") || "none"}`);
|
|
29653
29873
|
if (issueForm && o.claim && selector) {
|
|
@@ -30133,7 +30353,7 @@ project.command("set [owner/repo]").description("upsert project META (idempotent
|
|
|
30133
30353
|
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`);
|
|
30134
30354
|
if (o.secretsFile) {
|
|
30135
30355
|
try {
|
|
30136
|
-
vars.push(`secrets=${(0,
|
|
30356
|
+
vars.push(`secrets=${(0, import_node_fs36.readFileSync)(o.secretsFile, "utf8")}`);
|
|
30137
30357
|
} catch (e) {
|
|
30138
30358
|
return fail(`org project set: cannot read --secrets-file ${o.secretsFile}: ${e.message}`);
|
|
30139
30359
|
}
|
|
@@ -30881,11 +31101,11 @@ pr.command("view <number>").description("read a PR as structured JSON (merged st
|
|
|
30881
31101
|
}
|
|
30882
31102
|
});
|
|
30883
31103
|
async function listCiWorkflowPaths(cwd = process.cwd()) {
|
|
30884
|
-
const wfDir = (0,
|
|
30885
|
-
if (!(0,
|
|
30886
|
-
return (0,
|
|
31104
|
+
const wfDir = (0, import_node_path35.join)(cwd, ".github", "workflows");
|
|
31105
|
+
if (!(0, import_node_fs36.existsSync)(wfDir)) return [];
|
|
31106
|
+
return (0, import_node_fs36.readdirSync)(wfDir).filter((name) => /\.(ya?ml)$/i.test(name)).filter((name) => {
|
|
30887
31107
|
try {
|
|
30888
|
-
return workflowReportsPrChecks((0,
|
|
31108
|
+
return workflowReportsPrChecks((0, import_node_fs36.readFileSync)((0, import_node_path35.join)(wfDir, name), "utf8"));
|
|
30889
31109
|
} catch {
|
|
30890
31110
|
return true;
|
|
30891
31111
|
}
|
|
@@ -30917,16 +31137,16 @@ function ciAuditDeps() {
|
|
|
30917
31137
|
// gate re-seed step is skipped gracefully rather than failing mid-run.
|
|
30918
31138
|
readSeedFile: (path2) => {
|
|
30919
31139
|
if (!root) return null;
|
|
30920
|
-
const fullPath = (0,
|
|
30921
|
-
return (0,
|
|
31140
|
+
const fullPath = (0, import_node_path35.join)(root, path2);
|
|
31141
|
+
return (0, import_node_fs36.existsSync)(fullPath) ? (0, import_node_fs36.readFileSync)(fullPath, "utf8") : null;
|
|
30922
31142
|
}
|
|
30923
31143
|
};
|
|
30924
31144
|
}
|
|
30925
31145
|
function hubRoot() {
|
|
30926
|
-
const fromPkg = (0,
|
|
31146
|
+
const fromPkg = (0, import_node_path35.join)(__dirname, "..", "..");
|
|
30927
31147
|
const marker = "skills/bootstrap/seeds/manifest.json";
|
|
30928
|
-
if ((0,
|
|
30929
|
-
if ((0,
|
|
31148
|
+
if ((0, import_node_fs36.existsSync)((0, import_node_path35.join)(fromPkg, marker))) return fromPkg;
|
|
31149
|
+
if ((0, import_node_fs36.existsSync)((0, import_node_path35.join)(process.cwd(), marker))) return process.cwd();
|
|
30930
31150
|
return null;
|
|
30931
31151
|
}
|
|
30932
31152
|
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) => {
|
|
@@ -31055,6 +31275,10 @@ pr.command("land <number>").description("agent merge path (#1440): train probe \
|
|
|
31055
31275
|
return false;
|
|
31056
31276
|
}
|
|
31057
31277
|
});
|
|
31278
|
+
if (result.status !== "failed") {
|
|
31279
|
+
const repoFlag = result.repo ? ` --repo ${result.repo}` : "";
|
|
31280
|
+
console.warn(`pr land: merge confirmed; cleanup pending \u2014 if this process is interrupted, resume with: mmi-cli pr merge ${number}${repoFlag} --squash`);
|
|
31281
|
+
}
|
|
31058
31282
|
if (result.status !== "failed") {
|
|
31059
31283
|
try {
|
|
31060
31284
|
const { stdout } = await execFileP2(process.execPath, [
|
|
@@ -31229,7 +31453,7 @@ jsonParity(pr.command("merge <number>").description("merge a PR (squash by defau
|
|
|
31229
31453
|
localCleanup = await cleanupPrMergeLocalBranch(headRef, {
|
|
31230
31454
|
beforeWorktrees,
|
|
31231
31455
|
startingPath,
|
|
31232
|
-
pathExists: (p) => (0,
|
|
31456
|
+
pathExists: (p) => (0, import_node_fs36.existsSync)(p),
|
|
31233
31457
|
execGit: async (args) => (await execFileP2("git", args, { timeout: GIT_TIMEOUT_MS })).stdout,
|
|
31234
31458
|
teardownWorktreeStage,
|
|
31235
31459
|
deferredStore,
|
|
@@ -31704,12 +31928,12 @@ access.command("audit").description("audit collaborator roles + train-branch pus
|
|
|
31704
31928
|
targets = resolution.targets;
|
|
31705
31929
|
}
|
|
31706
31930
|
const derivedMatrix = registryProjects ? accessMatrixFromProjects(registryProjects) : {};
|
|
31707
|
-
const fileMatrix = (0,
|
|
31931
|
+
const fileMatrix = (0, import_node_fs36.existsSync)("access-matrix.json") ? loadAccessMatrix((0, import_node_fs36.readFileSync)("access-matrix.json", "utf8")) : {};
|
|
31708
31932
|
const matrix = mergeAccessMatrix(fileMatrix, derivedMatrix);
|
|
31709
31933
|
const derivedContracts = registryProjects ? dataAccessContractsFromProjects(registryProjects) : { consumers: {} };
|
|
31710
|
-
const fileContracts = (0,
|
|
31934
|
+
const fileContracts = (0, import_node_fs36.existsSync)("data-access-contracts.json") ? loadDataAccessContracts((0, import_node_fs36.readFileSync)("data-access-contracts.json", "utf8")) : { consumers: {} };
|
|
31711
31935
|
const dataAccess = mergeDataAccessContracts(fileContracts, derivedContracts);
|
|
31712
|
-
const sanctioned = (0,
|
|
31936
|
+
const sanctioned = (0, import_node_fs36.existsSync)("access-matrix.json") ? loadSanctionedAdmins((0, import_node_fs36.readFileSync)("access-matrix.json", "utf8")) : {};
|
|
31713
31937
|
const report = await auditOrgAccess(targets, deps, matrix, dataAccess, sanctioned);
|
|
31714
31938
|
console.log(o.json ? JSON.stringify(report, null, 2) : renderAccessReport(report));
|
|
31715
31939
|
if (!report.ok) process.exitCode = 1;
|
|
@@ -31741,16 +31965,16 @@ function directoryBytes(path2) {
|
|
|
31741
31965
|
let total = 0;
|
|
31742
31966
|
let entries;
|
|
31743
31967
|
try {
|
|
31744
|
-
entries = (0,
|
|
31968
|
+
entries = (0, import_node_fs36.readdirSync)(path2, { withFileTypes: true });
|
|
31745
31969
|
} catch {
|
|
31746
31970
|
return 0;
|
|
31747
31971
|
}
|
|
31748
31972
|
for (const entry of entries) {
|
|
31749
|
-
const child2 = (0,
|
|
31973
|
+
const child2 = (0, import_node_path35.join)(path2, entry.name);
|
|
31750
31974
|
if (entry.isDirectory()) total += directoryBytes(child2);
|
|
31751
31975
|
else {
|
|
31752
31976
|
try {
|
|
31753
|
-
total += (0,
|
|
31977
|
+
total += (0, import_node_fs36.statSync)(child2).size;
|
|
31754
31978
|
} catch {
|
|
31755
31979
|
}
|
|
31756
31980
|
}
|
|
@@ -31758,25 +31982,25 @@ function directoryBytes(path2) {
|
|
|
31758
31982
|
return total;
|
|
31759
31983
|
}
|
|
31760
31984
|
function listDirEntries(dir) {
|
|
31761
|
-
return (0,
|
|
31985
|
+
return (0, import_node_fs36.readdirSync)(dir, { withFileTypes: true }).map((d) => ({ name: d.name, isDirectory: d.isDirectory() }));
|
|
31762
31986
|
}
|
|
31763
31987
|
function readInstalledPluginRefs(configRoot) {
|
|
31764
31988
|
const p = installedPluginsPathForConfig(configRoot);
|
|
31765
|
-
if (!(0,
|
|
31989
|
+
if (!(0, import_node_fs36.existsSync)(p)) return [];
|
|
31766
31990
|
try {
|
|
31767
|
-
return installedPluginPaths((0,
|
|
31991
|
+
return installedPluginPaths((0, import_node_fs36.readFileSync)(p, "utf8"));
|
|
31768
31992
|
} catch {
|
|
31769
31993
|
return null;
|
|
31770
31994
|
}
|
|
31771
31995
|
}
|
|
31772
31996
|
function pluginCacheFsDeps(configRoot, dirBytes) {
|
|
31773
31997
|
return {
|
|
31774
|
-
exists: (p) => (0,
|
|
31775
|
-
listVersionDirs: (root) => (0,
|
|
31998
|
+
exists: (p) => (0, import_node_fs36.existsSync)(p),
|
|
31999
|
+
listVersionDirs: (root) => (0, import_node_fs36.readdirSync)(root, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name),
|
|
31776
32000
|
dirBytes,
|
|
31777
|
-
listStagingDirs: (root) => (0,
|
|
32001
|
+
listStagingDirs: (root) => (0, import_node_fs36.readdirSync)(root, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => {
|
|
31778
32002
|
try {
|
|
31779
|
-
return { name: d.name, mtimeMs: newestMtimeMs((0,
|
|
32003
|
+
return { name: d.name, mtimeMs: newestMtimeMs((0, import_node_path35.join)(root, d.name), listDirEntries, (p) => (0, import_node_fs36.statSync)(p).mtimeMs) };
|
|
31780
32004
|
} catch {
|
|
31781
32005
|
return { name: d.name, mtimeMs: Date.now() };
|
|
31782
32006
|
}
|
|
@@ -31790,10 +32014,10 @@ function stagingApplyFsGuard(configRoot) {
|
|
|
31790
32014
|
return {
|
|
31791
32015
|
referencedPaths: () => readInstalledPluginRefs(configRoot),
|
|
31792
32016
|
mtimeMs: (name) => {
|
|
31793
|
-
const p = (0,
|
|
31794
|
-
if (!(0,
|
|
32017
|
+
const p = (0, import_node_path35.join)(stagingRoot, name);
|
|
32018
|
+
if (!(0, import_node_fs36.existsSync)(p)) return null;
|
|
31795
32019
|
try {
|
|
31796
|
-
return newestMtimeMs(p, listDirEntries, (q) => (0,
|
|
32020
|
+
return newestMtimeMs(p, listDirEntries, (q) => (0, import_node_fs36.statSync)(q).mtimeMs);
|
|
31797
32021
|
} catch {
|
|
31798
32022
|
return null;
|
|
31799
32023
|
}
|
|
@@ -31813,13 +32037,13 @@ program2.command("plugin-prune").description(`prune stale cached MMI plugin vers
|
|
|
31813
32037
|
return;
|
|
31814
32038
|
}
|
|
31815
32039
|
const plan = buildPluginCachePlan(
|
|
31816
|
-
(0,
|
|
32040
|
+
(0, import_node_os14.homedir)(),
|
|
31817
32041
|
running,
|
|
31818
32042
|
pluginCacheFsDeps(configRoot, directoryBytes),
|
|
31819
32043
|
{ withBytes: true, configRoot, includeStaging: surface !== "codex" }
|
|
31820
32044
|
);
|
|
31821
32045
|
const anythingToDelete = plan.prune.length > 0 || plan.staging.length > 0;
|
|
31822
|
-
const result = o.apply && anythingToDelete ? applyPluginCachePlan(plan, (p) => (0,
|
|
32046
|
+
const result = o.apply && anythingToDelete ? applyPluginCachePlan(plan, (p) => (0, import_node_fs36.rmSync)(p, { recursive: true, force: true }), stagingApplyFsGuard(configRoot)) : void 0;
|
|
31823
32047
|
const warnings = plan.prune.length > 0 ? [CONCURRENT_SESSION_WARNING] : [];
|
|
31824
32048
|
if (o.json) console.log(JSON.stringify({ ...plan, warnings, applied: result ?? null }));
|
|
31825
32049
|
else console.log(renderPluginCachePlan(plan, result));
|