@mutmutco/cli 3.63.0 → 3.65.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 +527 -229
- package/package.json +1 -1
package/dist/main.cjs
CHANGED
|
@@ -38,7 +38,8 @@ __export(index_exports, {
|
|
|
38
38
|
isOrgRegisteredRepo: () => isOrgRegisteredRepo,
|
|
39
39
|
registryClientDeps: () => registryClientDeps,
|
|
40
40
|
repoSlug: () => repoSlug,
|
|
41
|
-
shouldMarkWorktreeActivity: () => shouldMarkWorktreeActivity
|
|
41
|
+
shouldMarkWorktreeActivity: () => shouldMarkWorktreeActivity,
|
|
42
|
+
suggestCommandPath: () => suggestCommandPath
|
|
42
43
|
});
|
|
43
44
|
module.exports = __toCommonJS(index_exports);
|
|
44
45
|
|
|
@@ -3412,8 +3413,8 @@ var program = new Command();
|
|
|
3412
3413
|
|
|
3413
3414
|
// src/index.ts
|
|
3414
3415
|
var import_promises8 = require("node:fs/promises");
|
|
3415
|
-
var
|
|
3416
|
-
var
|
|
3416
|
+
var import_node_fs33 = require("node:fs");
|
|
3417
|
+
var import_node_child_process14 = require("node:child_process");
|
|
3417
3418
|
|
|
3418
3419
|
// src/cli-shared.ts
|
|
3419
3420
|
var import_node_child_process3 = require("node:child_process");
|
|
@@ -3655,6 +3656,7 @@ function didYouMean(input, candidates) {
|
|
|
3655
3656
|
const cand = strip(candidate);
|
|
3656
3657
|
if (!cand) continue;
|
|
3657
3658
|
if (cand === target) continue;
|
|
3659
|
+
if (cand === `${target}-file` || target === `${cand}-file`) continue;
|
|
3658
3660
|
const distance = levenshtein(target, cand);
|
|
3659
3661
|
const prefix = cand.startsWith(target) || target.startsWith(cand);
|
|
3660
3662
|
const threshold = Math.max(2, Math.ceil(cand.length * 0.4));
|
|
@@ -5163,16 +5165,20 @@ function validateDeadWorktreeDirContent(planned, inspected) {
|
|
|
5163
5165
|
return { ok: false, reason: "unknown dead worktree cleanup kind" };
|
|
5164
5166
|
}
|
|
5165
5167
|
function validateBranchCleanupHead(planned, currentHeads) {
|
|
5168
|
+
const currentHeadOid = currentHeads.find((h) => h.branch === planned.branch)?.oid;
|
|
5169
|
+
if (planned.containedInBase && planned.plannedHeadOid) {
|
|
5170
|
+
if (!currentHeadOid) return { ok: false, reason: "local branch head could not be read" };
|
|
5171
|
+
return currentHeadOid === planned.plannedHeadOid ? { ok: true } : { ok: false, reason: `branch moved since planning (${currentHeadOid} != ${planned.plannedHeadOid})` };
|
|
5172
|
+
}
|
|
5166
5173
|
const reviewedHeadOids = planned.reviewedHeadOids?.filter(Boolean) ?? [];
|
|
5167
5174
|
if (!reviewedHeadOids.length) {
|
|
5168
5175
|
return { ok: false, reason: "planned branch head was not verified against a PR head" };
|
|
5169
5176
|
}
|
|
5170
|
-
|
|
5171
|
-
if (!currentHead) {
|
|
5177
|
+
if (!currentHeadOid) {
|
|
5172
5178
|
return { ok: false, reason: "local branch head could not be verified against the PR head" };
|
|
5173
5179
|
}
|
|
5174
|
-
if (!reviewedHeadOids.includes(
|
|
5175
|
-
return { ok: false, reason: `${
|
|
5180
|
+
if (!reviewedHeadOids.includes(currentHeadOid)) {
|
|
5181
|
+
return { ok: false, reason: `${currentHeadOid} != ${reviewedHeadOids.join("|")}` };
|
|
5176
5182
|
}
|
|
5177
5183
|
return { ok: true };
|
|
5178
5184
|
}
|
|
@@ -5272,6 +5278,7 @@ function buildGcPlan(inputs) {
|
|
|
5272
5278
|
const unpushedWorktrees = new Set((inputs.worktrees ?? []).filter((w) => w.unpushed).map((w) => w.branch));
|
|
5273
5279
|
const preservedBranches2 = new Set(inputs.preservedBranches ?? []);
|
|
5274
5280
|
const preservedWorktrees = new Set((inputs.worktrees ?? []).filter((w) => w.preserved).map((w) => w.branch));
|
|
5281
|
+
const mergedIntoBase = new Set((inputs.mergedIntoBase ?? []).map((b) => b.trim()).filter(Boolean));
|
|
5275
5282
|
const skipped = [];
|
|
5276
5283
|
const branches = [];
|
|
5277
5284
|
const skipTrackingBranches = /* @__PURE__ */ new Set();
|
|
@@ -5311,7 +5318,8 @@ function buildGcPlan(inputs) {
|
|
|
5311
5318
|
continue;
|
|
5312
5319
|
}
|
|
5313
5320
|
const localHead = branchHeads?.get(branch);
|
|
5314
|
-
|
|
5321
|
+
const containedInBase = mergedIntoBase.has(branch);
|
|
5322
|
+
if (!containedInBase && (worktree2?.unpushed || branchHeads && (!localHead || !state.headOids.length || !state.headOids.includes(localHead)))) {
|
|
5315
5323
|
const detail = worktree2?.unpushed ? worktree2.path : localHead && state.headOids.length ? `${localHead} != ${state.headOids.join("|")}` : "local branch head could not be verified against the PR head";
|
|
5316
5324
|
skipped.push({ branch, reason: "unpushed-branch", detail });
|
|
5317
5325
|
skipTrackingBranches.add(branch);
|
|
@@ -5322,7 +5330,8 @@ function buildGcPlan(inputs) {
|
|
|
5322
5330
|
prState: state.state,
|
|
5323
5331
|
prNumbers: state.numbers,
|
|
5324
5332
|
worktreePath: worktree2?.path,
|
|
5325
|
-
...state.headOids.length ? { reviewedHeadOids: state.headOids } : {}
|
|
5333
|
+
...state.headOids.length ? { reviewedHeadOids: state.headOids } : {},
|
|
5334
|
+
...containedInBase ? { containedInBase: true, ...localHead ? { plannedHeadOid: localHead } : {} } : {}
|
|
5326
5335
|
});
|
|
5327
5336
|
}
|
|
5328
5337
|
const trackingRefs = [...new Set(inputs.staleTrackingRefs ?? [])].map((ref) => {
|
|
@@ -5525,38 +5534,38 @@ function safeWorktreeRemoveCommand(safeCwd, targetPath) {
|
|
|
5525
5534
|
function errorMessage(error) {
|
|
5526
5535
|
return error instanceof Error ? error.message : String(error);
|
|
5527
5536
|
}
|
|
5528
|
-
async function fastForwardCurrentBranch(
|
|
5537
|
+
async function fastForwardCurrentBranch(git2) {
|
|
5529
5538
|
try {
|
|
5530
|
-
const before = (await
|
|
5531
|
-
await
|
|
5532
|
-
const after = (await
|
|
5539
|
+
const before = (await git2(["rev-parse", "HEAD"])).trim();
|
|
5540
|
+
await git2(["pull", "--ff-only"]);
|
|
5541
|
+
const after = (await git2(["rev-parse", "HEAD"])).trim();
|
|
5533
5542
|
return { status: before === after ? "up-to-date" : "fast-forwarded" };
|
|
5534
5543
|
} catch (e) {
|
|
5535
5544
|
return { status: "failed", error: errorMessage(e) };
|
|
5536
5545
|
}
|
|
5537
5546
|
}
|
|
5538
|
-
async function returnCheckoutToBase(
|
|
5547
|
+
async function returnCheckoutToBase(git2, base, mergedBranch, currentBranch2) {
|
|
5539
5548
|
if (currentBranch2 === mergedBranch) {
|
|
5540
|
-
const porcelain = await
|
|
5549
|
+
const porcelain = await git2(["status", "--porcelain"]).catch(() => void 0);
|
|
5541
5550
|
const dirt = porcelain === void 0 ? "tracked-changes" : classifyWorktreeDirt(porcelain);
|
|
5542
5551
|
if (!isRemovableDirt(dirt)) {
|
|
5543
5552
|
return { report: { branch: base, switched: false, sync: "skipped", reason: dirt === "untracked-files" ? "untracked-files" : "dirty-worktree" }, canDeleteBranch: false };
|
|
5544
5553
|
}
|
|
5545
5554
|
try {
|
|
5546
|
-
await
|
|
5555
|
+
await git2(["checkout", base]);
|
|
5547
5556
|
} catch (e) {
|
|
5548
5557
|
return {
|
|
5549
5558
|
report: { branch: base, switched: false, sync: "skipped", reason: "checkout-failed", error: errorMessage(e) },
|
|
5550
5559
|
canDeleteBranch: false
|
|
5551
5560
|
};
|
|
5552
5561
|
}
|
|
5553
|
-
const ff2 = await fastForwardCurrentBranch(
|
|
5562
|
+
const ff2 = await fastForwardCurrentBranch(git2);
|
|
5554
5563
|
return { report: { branch: base, switched: true, sync: ff2.status, ...ff2.error ? { error: ff2.error } : {} }, canDeleteBranch: true };
|
|
5555
5564
|
}
|
|
5556
5565
|
if (currentBranch2 !== base) {
|
|
5557
5566
|
return { report: { branch: base, switched: false, sync: "skipped", reason: "not-on-base" }, canDeleteBranch: true };
|
|
5558
5567
|
}
|
|
5559
|
-
const ff = await fastForwardCurrentBranch(
|
|
5568
|
+
const ff = await fastForwardCurrentBranch(git2);
|
|
5560
5569
|
return { report: { branch: base, switched: false, sync: ff.status, ...ff.error ? { error: ff.error } : {} }, canDeleteBranch: true };
|
|
5561
5570
|
}
|
|
5562
5571
|
async function cleanupPrMergeLocalBranch(branch, options) {
|
|
@@ -5597,17 +5606,17 @@ async function cleanupPrMergeLocalBranch(branch, options) {
|
|
|
5597
5606
|
const safeCwd = selectSafeWorktreeCwd([...afterWorktrees, ...beforeWorktrees], wtPath, {
|
|
5598
5607
|
pathExists: options.pathExists
|
|
5599
5608
|
});
|
|
5600
|
-
const
|
|
5609
|
+
const git2 = (args) => safeCwd ? options.execGit(["-C", safeCwd, ...args]) : options.execGit(args);
|
|
5601
5610
|
const expectedHeadOid = options.expectedHeadOid;
|
|
5602
5611
|
const verifyReviewedBranchHead = async () => {
|
|
5603
5612
|
if (!expectedHeadOid) {
|
|
5604
|
-
const remaining = await
|
|
5613
|
+
const remaining = await git2(["branch", "--list", branch]).catch(() => "");
|
|
5605
5614
|
report.localBranch = branchMissingFromList(branch, remaining) ? { name: branch, status: "already-gone" } : { name: branch, status: "not-attempted", reason: "branch-head-unverified" };
|
|
5606
5615
|
return false;
|
|
5607
5616
|
}
|
|
5608
|
-
const currentHead = (await
|
|
5617
|
+
const currentHead = (await git2(["rev-parse", `refs/heads/${branch}`]).catch(() => "") || "").trim();
|
|
5609
5618
|
if (!currentHead) {
|
|
5610
|
-
const remaining = await
|
|
5619
|
+
const remaining = await git2(["branch", "--list", branch]).catch(() => "");
|
|
5611
5620
|
report.localBranch = branchMissingFromList(branch, remaining) ? { name: branch, status: "already-gone" } : { name: branch, status: "not-attempted", reason: "branch-head-unverified" };
|
|
5612
5621
|
return false;
|
|
5613
5622
|
}
|
|
@@ -5642,7 +5651,7 @@ async function cleanupPrMergeLocalBranch(branch, options) {
|
|
|
5642
5651
|
return report;
|
|
5643
5652
|
}
|
|
5644
5653
|
const removeDeps = {
|
|
5645
|
-
git,
|
|
5654
|
+
git: git2,
|
|
5646
5655
|
sleep: options.sleep ?? defaultSleep,
|
|
5647
5656
|
detachReparsePoints: options.detachReparsePoints,
|
|
5648
5657
|
// #3064: junction-safe deferred sweep too
|
|
@@ -5662,7 +5671,7 @@ async function cleanupPrMergeLocalBranch(branch, options) {
|
|
|
5662
5671
|
chdir: options.chdir
|
|
5663
5672
|
});
|
|
5664
5673
|
const outcome = await removeWorktreeWithRecovery(wtPath, {
|
|
5665
|
-
git,
|
|
5674
|
+
git: git2,
|
|
5666
5675
|
sleep: options.sleep ?? defaultSleep,
|
|
5667
5676
|
detachReparsePoints: options.detachReparsePoints,
|
|
5668
5677
|
// #3064: reach the actual pr-merge teardown path
|
|
@@ -5713,10 +5722,10 @@ async function cleanupPrMergeLocalBranch(branch, options) {
|
|
|
5713
5722
|
return report;
|
|
5714
5723
|
}
|
|
5715
5724
|
}
|
|
5716
|
-
const current = (await
|
|
5725
|
+
const current = (await git2(["rev-parse", "--abbrev-ref", "HEAD"]).catch(() => "") || "").trim();
|
|
5717
5726
|
if (options.returnToBranch && options.returnToBranch !== branch) {
|
|
5718
5727
|
const { report: checkout, canDeleteBranch } = await returnCheckoutToBase(
|
|
5719
|
-
|
|
5728
|
+
git2,
|
|
5720
5729
|
options.returnToBranch,
|
|
5721
5730
|
branch,
|
|
5722
5731
|
current
|
|
@@ -5735,16 +5744,16 @@ async function cleanupPrMergeLocalBranch(branch, options) {
|
|
|
5735
5744
|
}
|
|
5736
5745
|
const reviewedHeadOid = expectedHeadOid;
|
|
5737
5746
|
try {
|
|
5738
|
-
await
|
|
5747
|
+
await git2(["update-ref", "-d", `refs/heads/${branch}`, reviewedHeadOid]);
|
|
5739
5748
|
report.localBranch = { name: branch, status: "deleted" };
|
|
5740
5749
|
} catch (e) {
|
|
5741
|
-
const remaining = await
|
|
5750
|
+
const remaining = await git2(["branch", "--list", branch]).catch(() => "");
|
|
5742
5751
|
report.localBranch = branchMissingFromList(branch, remaining) ? { name: branch, status: "already-gone" } : { name: branch, status: "failed", error: errorMessage(e) };
|
|
5743
5752
|
}
|
|
5744
5753
|
if (report.localBranch.status === "deleted" || report.localBranch.status === "already-gone") {
|
|
5745
|
-
await
|
|
5754
|
+
await git2(["config", "--local", "--unset-all", PRESERVED_WORKTREE_CONFIG, `^${escapeGitConfigValueRegex(branch)}$`]).catch(() => void 0);
|
|
5746
5755
|
}
|
|
5747
|
-
if (wtPath) await
|
|
5756
|
+
if (wtPath) await git2(["worktree", "prune"]).catch(() => "");
|
|
5748
5757
|
return report;
|
|
5749
5758
|
}
|
|
5750
5759
|
function formatGcPlan(plan, apply) {
|
|
@@ -6242,9 +6251,9 @@ function defaultWorktreePath(repoRoot2, branch) {
|
|
|
6242
6251
|
const safe = capWorktreeDirName(branch.replace(/[/\\]+/g, "-"));
|
|
6243
6252
|
return (0, import_node_path10.join)((0, import_node_path10.dirname)(repoRoot2), "mmi-worktrees", (0, import_node_path10.basename)(repoRoot2), safe);
|
|
6244
6253
|
}
|
|
6245
|
-
async function primaryCheckoutRootOf(
|
|
6254
|
+
async function primaryCheckoutRootOf(git2) {
|
|
6246
6255
|
try {
|
|
6247
|
-
const out = (await
|
|
6256
|
+
const out = (await git2(["rev-parse", "--path-format=absolute", "--git-common-dir"])).trim();
|
|
6248
6257
|
return out ? (0, import_node_path10.dirname)(out) : void 0;
|
|
6249
6258
|
} catch {
|
|
6250
6259
|
return void 0;
|
|
@@ -6321,7 +6330,7 @@ async function resolveWhoami(deps) {
|
|
|
6321
6330
|
}
|
|
6322
6331
|
function whoamiLine(report, caveat) {
|
|
6323
6332
|
if (!report.login) return null;
|
|
6324
|
-
const line = `current human: ${report.login} (source: ${report.source}) \u2014 act for this login (e.g. claim --for ${report.login}); do not ask who the user is.`;
|
|
6333
|
+
const line = `current human: ${report.login} (source: ${report.source}) \u2014 act for this login (e.g. mmi-cli board claim <n> --for ${report.login}); do not ask who the user is.`;
|
|
6325
6334
|
return caveat?.trim() ? `${line} ${caveat.trim()}` : line;
|
|
6326
6335
|
}
|
|
6327
6336
|
function authorityLine(report) {
|
|
@@ -6363,7 +6372,7 @@ function commandLadderHint() {
|
|
|
6363
6372
|
}
|
|
6364
6373
|
|
|
6365
6374
|
// src/index.ts
|
|
6366
|
-
var
|
|
6375
|
+
var import_node_path31 = require("node:path");
|
|
6367
6376
|
|
|
6368
6377
|
// src/merge-ci-policy.ts
|
|
6369
6378
|
function resolveMergeCiPolicy(input) {
|
|
@@ -10809,7 +10818,7 @@ function resolveExplicitScanRoot(explicitRoot, repoRoot2) {
|
|
|
10809
10818
|
return explicitRepoWorktreesRoot(explicitRoot, repoRoot2, rootDirs);
|
|
10810
10819
|
}
|
|
10811
10820
|
async function gcPlan(remote, limit, opts = {}) {
|
|
10812
|
-
const [branches, heads, current, stale, prs, worktrees, siblingDirs, preserved] = await Promise.all([
|
|
10821
|
+
const [branches, heads, current, stale, prs, worktrees, siblingDirs, preserved, mergedIntoBase] = await Promise.all([
|
|
10813
10822
|
gitOut(["branch", "--format=%(refname:short)"]),
|
|
10814
10823
|
localBranchHeads(),
|
|
10815
10824
|
gitOut(["rev-parse", "--abbrev-ref", "HEAD"]),
|
|
@@ -10819,7 +10828,8 @@ async function gcPlan(remote, limit, opts = {}) {
|
|
|
10819
10828
|
ghPrs(limit),
|
|
10820
10829
|
worktreeBranches(),
|
|
10821
10830
|
siblingWorktreeDirs(opts.root),
|
|
10822
|
-
preservedBranches()
|
|
10831
|
+
preservedBranches(),
|
|
10832
|
+
branchesMergedIntoBase(remote)
|
|
10823
10833
|
]);
|
|
10824
10834
|
return buildGcPlan({
|
|
10825
10835
|
localBranches: branches.split(/\r?\n/).map((b) => b.trim()).filter(Boolean),
|
|
@@ -10830,9 +10840,23 @@ async function gcPlan(remote, limit, opts = {}) {
|
|
|
10830
10840
|
pullRequests: prs,
|
|
10831
10841
|
worktrees,
|
|
10832
10842
|
remote,
|
|
10833
|
-
preservedBranches: preserved
|
|
10843
|
+
preservedBranches: preserved,
|
|
10844
|
+
mergedIntoBase
|
|
10834
10845
|
});
|
|
10835
10846
|
}
|
|
10847
|
+
async function branchesMergedIntoBase(remote) {
|
|
10848
|
+
const out = /* @__PURE__ */ new Set();
|
|
10849
|
+
for (const base of ["development", "main", "master"]) {
|
|
10850
|
+
let listed;
|
|
10851
|
+
try {
|
|
10852
|
+
listed = await gitOut(["branch", "--merged", `${remote}/${base}`, "--format=%(refname:short)"]);
|
|
10853
|
+
} catch {
|
|
10854
|
+
continue;
|
|
10855
|
+
}
|
|
10856
|
+
for (const b of listed.split(/\r?\n/).map((x) => x.trim()).filter(Boolean)) out.add(b);
|
|
10857
|
+
}
|
|
10858
|
+
return [...out];
|
|
10859
|
+
}
|
|
10836
10860
|
var STRAY_ROOT_WALK_BUDGET_MS = 4e3;
|
|
10837
10861
|
function measureWorktreeRoot(root, deadline) {
|
|
10838
10862
|
let dirs = 0;
|
|
@@ -11212,8 +11236,8 @@ function shouldSkipInactiveBoardNode(node, cfg) {
|
|
|
11212
11236
|
}
|
|
11213
11237
|
async function collectBoardItems(cfg, options, deps) {
|
|
11214
11238
|
const client = deps.client ?? defaultGitHubClient();
|
|
11215
|
-
const
|
|
11216
|
-
const currentRepo = options.repo ?? repoFromRemoteUrl(await
|
|
11239
|
+
const git2 = deps.git ?? defaultGit;
|
|
11240
|
+
const currentRepo = options.repo ?? repoFromRemoteUrl(await git2(["remote", "get-url", "origin"])) ?? "";
|
|
11217
11241
|
if (!currentRepo) throw new Error("could not resolve current GitHub repo; pass --repo owner/repo");
|
|
11218
11242
|
const warnings = [];
|
|
11219
11243
|
const nodes = [];
|
|
@@ -11324,8 +11348,8 @@ function findBoardItem(items, selector, board) {
|
|
|
11324
11348
|
return found;
|
|
11325
11349
|
}
|
|
11326
11350
|
async function resolveCurrentRepo(options, deps) {
|
|
11327
|
-
const
|
|
11328
|
-
const currentRepo = options.repo ?? repoFromRemoteUrl(await
|
|
11351
|
+
const git2 = deps.git ?? defaultGit;
|
|
11352
|
+
const currentRepo = options.repo ?? repoFromRemoteUrl(await git2(["remote", "get-url", "origin"])) ?? "";
|
|
11329
11353
|
if (!currentRepo) throw new Error("could not resolve current GitHub repo; pass --repo owner/repo");
|
|
11330
11354
|
return currentRepo;
|
|
11331
11355
|
}
|
|
@@ -11532,8 +11556,8 @@ async function claimBoardIssues(options, deps = {}) {
|
|
|
11532
11556
|
async function moveBoardIssues(options, deps = {}) {
|
|
11533
11557
|
const cfg = resolveBoardConfig(options.config);
|
|
11534
11558
|
const client = deps.client ?? defaultGitHubClient();
|
|
11535
|
-
const
|
|
11536
|
-
const currentRepo = options.repo ?? repoFromRemoteUrl(await
|
|
11559
|
+
const git2 = deps.git ?? defaultGit;
|
|
11560
|
+
const currentRepo = options.repo ?? repoFromRemoteUrl(await git2(["remote", "get-url", "origin"])) ?? "";
|
|
11537
11561
|
if (!currentRepo) throw new Error("could not resolve current GitHub repo; pass --repo owner/repo");
|
|
11538
11562
|
const selectors = [];
|
|
11539
11563
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -11617,8 +11641,8 @@ async function moveBoardIssues(options, deps = {}) {
|
|
|
11617
11641
|
async function unclaimBoardIssue(options, deps = {}) {
|
|
11618
11642
|
const cfg = resolveBoardConfig(options.config);
|
|
11619
11643
|
const client = deps.client ?? defaultGitHubClient();
|
|
11620
|
-
const
|
|
11621
|
-
const currentRepo = options.repo ?? repoFromRemoteUrl(await
|
|
11644
|
+
const git2 = deps.git ?? defaultGit;
|
|
11645
|
+
const currentRepo = options.repo ?? repoFromRemoteUrl(await git2(["remote", "get-url", "origin"])) ?? "";
|
|
11622
11646
|
if (!currentRepo) throw new Error("could not resolve current GitHub repo; pass --repo owner/repo");
|
|
11623
11647
|
const selector = parseIssueSelector(options.selector, currentRepo);
|
|
11624
11648
|
const { viewer, item } = await fetchIssueProjectItem(client, cfg, selector);
|
|
@@ -12450,11 +12474,13 @@ var PRIMARY_GROUPS = [
|
|
|
12450
12474
|
["Orient", ["onboard", "status", "next", "doctor", "whoami", "commands", "explain"]],
|
|
12451
12475
|
["Plan and work", ["board", "issue", "worktree", "stage"]],
|
|
12452
12476
|
["Review and ship", ["pr", "ci", "rcand", "release", "hotfix", "train"]],
|
|
12453
|
-
|
|
12477
|
+
// `tests` sits beside `docs` deliberately: both are deterministic, repo-local gates a workflow
|
|
12478
|
+
// step invokes (`docs refs`, `tests policy`), not org-plane operations (#3605).
|
|
12479
|
+
["Setup and support", ["bootstrap", "secrets", "docs", "tests"]],
|
|
12454
12480
|
["Coordinate and improve", ["wave", "report", "skill-lesson"]]
|
|
12455
12481
|
];
|
|
12456
12482
|
var OPERATIONAL_TOP_LEVEL = /* @__PURE__ */ new Set(["org", "runtime", "plugin"]);
|
|
12457
|
-
var SUPPORT_PRIMARY = /* @__PURE__ */ new Set(["doctor", "whoami", "commands", "explain", "docs", "wave", "report", "skill-lesson"]);
|
|
12483
|
+
var SUPPORT_PRIMARY = /* @__PURE__ */ new Set(["doctor", "whoami", "commands", "explain", "docs", "tests", "wave", "report", "skill-lesson"]);
|
|
12458
12484
|
var TOP_LEVEL_ORDER = /* @__PURE__ */ new Map();
|
|
12459
12485
|
var HELP_GROUP_ORDER = /* @__PURE__ */ new Map();
|
|
12460
12486
|
var topLevelPosition = 0;
|
|
@@ -15031,31 +15057,31 @@ var CHERRY_TRAILER = /\(cherry picked from commit ([0-9a-f]{7,40})\)/g;
|
|
|
15031
15057
|
function checkHotfixCoverage(options = {}) {
|
|
15032
15058
|
const { cwd = process.cwd(), mainRef = "origin/main", rcRef = "origin/rc", manifestPaths = [] } = options;
|
|
15033
15059
|
const ack = (options.ack ?? []).filter(Boolean);
|
|
15034
|
-
const
|
|
15060
|
+
const git2 = options.git ?? ((args, opts) => (0, import_node_child_process8.execFileSync)("git", args, { cwd, encoding: "utf8", input: opts?.input, stdio: ["pipe", "pipe", "pipe"] }));
|
|
15035
15061
|
const revList = (range) => {
|
|
15036
|
-
const out =
|
|
15062
|
+
const out = git2(["rev-list", "--no-merges", range]).trim();
|
|
15037
15063
|
return out ? out.split("\n") : [];
|
|
15038
15064
|
};
|
|
15039
15065
|
const isAncestor = (sha, ref) => {
|
|
15040
15066
|
try {
|
|
15041
|
-
|
|
15067
|
+
git2(["merge-base", "--is-ancestor", sha, ref]);
|
|
15042
15068
|
return true;
|
|
15043
15069
|
} catch {
|
|
15044
15070
|
return false;
|
|
15045
15071
|
}
|
|
15046
15072
|
};
|
|
15047
15073
|
const patchId = (sha) => {
|
|
15048
|
-
const diff =
|
|
15074
|
+
const diff = git2(["show", "--no-color", "--pretty=format:", sha]);
|
|
15049
15075
|
if (!diff.trim()) return null;
|
|
15050
|
-
const out =
|
|
15076
|
+
const out = git2(["patch-id", "--stable"], { input: diff }).trim();
|
|
15051
15077
|
return out ? out.split(" ")[0] : null;
|
|
15052
15078
|
};
|
|
15053
15079
|
const changedPaths = (sha) => {
|
|
15054
|
-
const out =
|
|
15080
|
+
const out = git2(["show", "--no-color", "--name-only", "--pretty=format:", sha]).trim();
|
|
15055
15081
|
return out ? out.split("\n") : [];
|
|
15056
15082
|
};
|
|
15057
15083
|
const cherrySources = (sha) => {
|
|
15058
|
-
const message =
|
|
15084
|
+
const message = git2(["log", "-1", "--format=%B", sha]);
|
|
15059
15085
|
return [...message.matchAll(CHERRY_TRAILER)].map((m) => m[1]);
|
|
15060
15086
|
};
|
|
15061
15087
|
const mainOnly = revList(`${rcRef}..${mainRef}`);
|
|
@@ -15070,7 +15096,7 @@ function checkHotfixCoverage(options = {}) {
|
|
|
15070
15096
|
}
|
|
15071
15097
|
};
|
|
15072
15098
|
const commits = mainOnly.map((sha) => {
|
|
15073
|
-
const subject =
|
|
15099
|
+
const subject = git2(["log", "-1", "--format=%s", sha]).trim();
|
|
15074
15100
|
const paths = changedPaths(sha);
|
|
15075
15101
|
if (paths.length > 0 && paths.every((p) => manifestPaths.includes(p))) {
|
|
15076
15102
|
return { sha, subject, coverage: "exempt-distribution" };
|
|
@@ -15099,21 +15125,21 @@ function checkHotfixCoverage(options = {}) {
|
|
|
15099
15125
|
}
|
|
15100
15126
|
function checkHotfixCarries(options) {
|
|
15101
15127
|
const { cwd = process.cwd(), branch, baseRef, targets } = options;
|
|
15102
|
-
const
|
|
15128
|
+
const git2 = options.git ?? ((args, opts) => (0, import_node_child_process8.execFileSync)("git", args, { cwd, encoding: "utf8", input: opts?.input, stdio: ["pipe", "pipe", "pipe"] }));
|
|
15103
15129
|
const isAncestor = (sha, ref) => {
|
|
15104
15130
|
try {
|
|
15105
|
-
|
|
15131
|
+
git2(["merge-base", "--is-ancestor", sha, ref]);
|
|
15106
15132
|
return true;
|
|
15107
15133
|
} catch {
|
|
15108
15134
|
return false;
|
|
15109
15135
|
}
|
|
15110
15136
|
};
|
|
15111
15137
|
const revList = (range) => {
|
|
15112
|
-
const out =
|
|
15138
|
+
const out = git2(["rev-list", "--no-merges", range]).trim();
|
|
15113
15139
|
return out ? out.split("\n") : [];
|
|
15114
15140
|
};
|
|
15115
15141
|
const cherrySources = (sha) => {
|
|
15116
|
-
const message =
|
|
15142
|
+
const message = git2(["log", "-1", "--format=%B", sha]);
|
|
15117
15143
|
return [...message.matchAll(CHERRY_TRAILER)].map((m) => m[1]);
|
|
15118
15144
|
};
|
|
15119
15145
|
const carried = /* @__PURE__ */ new Set();
|
|
@@ -16364,6 +16390,188 @@ function runDocRefs(root, deps = {}) {
|
|
|
16364
16390
|
return { ok: findings.length === 0, findings, docCount: Object.keys(docs2).length };
|
|
16365
16391
|
}
|
|
16366
16392
|
|
|
16393
|
+
// src/test-policy-core.ts
|
|
16394
|
+
var import_node_child_process10 = require("node:child_process");
|
|
16395
|
+
var import_node_fs19 = require("node:fs");
|
|
16396
|
+
var import_node_path17 = require("node:path");
|
|
16397
|
+
var POLICY_FILE = "test-policy.json";
|
|
16398
|
+
var TEST_RE = /\.(?:test|spec)\.[cm]?[jt]sx?$/;
|
|
16399
|
+
var PY_TEST_RE = /(?:^|\/)test_[^/]*\.py$|_test\.py$/;
|
|
16400
|
+
var OVERRIDE_RE = /^Test-Policy-Override:\s*(.+)$/im;
|
|
16401
|
+
function translate(glob) {
|
|
16402
|
+
let out = "";
|
|
16403
|
+
for (let i = 0; i < glob.length; i++) {
|
|
16404
|
+
const c = glob[i];
|
|
16405
|
+
if (c === "*") {
|
|
16406
|
+
if (glob[i + 1] === "*") {
|
|
16407
|
+
if (glob[i + 2] === "/") {
|
|
16408
|
+
out += "(?:.*/)?";
|
|
16409
|
+
i += 2;
|
|
16410
|
+
} else {
|
|
16411
|
+
out += ".*";
|
|
16412
|
+
i += 1;
|
|
16413
|
+
}
|
|
16414
|
+
} else {
|
|
16415
|
+
out += "[^/]*";
|
|
16416
|
+
}
|
|
16417
|
+
continue;
|
|
16418
|
+
}
|
|
16419
|
+
if (c === "{") {
|
|
16420
|
+
const close = glob.indexOf("}", i);
|
|
16421
|
+
if (close !== -1) {
|
|
16422
|
+
const alts = glob.slice(i + 1, close).split(",").map((a) => translate(a));
|
|
16423
|
+
out += `(?:${alts.join("|")})`;
|
|
16424
|
+
i = close;
|
|
16425
|
+
continue;
|
|
16426
|
+
}
|
|
16427
|
+
}
|
|
16428
|
+
out += /[.+?^${}()|[\]\\]/.test(c) ? `\\${c}` : c;
|
|
16429
|
+
}
|
|
16430
|
+
return out;
|
|
16431
|
+
}
|
|
16432
|
+
function globToRegExp(glob) {
|
|
16433
|
+
return new RegExp(`^${translate(glob)}$`);
|
|
16434
|
+
}
|
|
16435
|
+
function isTestPath(path2) {
|
|
16436
|
+
return TEST_RE.test(path2) || PY_TEST_RE.test(path2);
|
|
16437
|
+
}
|
|
16438
|
+
function loadPolicy(root, readFile7 = readFileOrNull2) {
|
|
16439
|
+
const raw = readFile7((0, import_node_path17.join)(root, POLICY_FILE));
|
|
16440
|
+
if (raw == null) return { mandatory: [], declared: false };
|
|
16441
|
+
try {
|
|
16442
|
+
return { ...JSON.parse(raw), declared: true };
|
|
16443
|
+
} catch (error) {
|
|
16444
|
+
throw new Error(`${POLICY_FILE} is not valid JSON: ${error.message}`);
|
|
16445
|
+
}
|
|
16446
|
+
}
|
|
16447
|
+
function readFileOrNull2(path2) {
|
|
16448
|
+
try {
|
|
16449
|
+
return (0, import_node_fs19.readFileSync)(path2, "utf8");
|
|
16450
|
+
} catch {
|
|
16451
|
+
return null;
|
|
16452
|
+
}
|
|
16453
|
+
}
|
|
16454
|
+
function removedPaths(changed) {
|
|
16455
|
+
return new Set(
|
|
16456
|
+
changed.map((f) => f.status === "D" ? f.path : f.status === "R" || f.status === "C" ? f.from : void 0).filter((p) => typeof p === "string")
|
|
16457
|
+
);
|
|
16458
|
+
}
|
|
16459
|
+
function classify(changed, policy, present = () => false) {
|
|
16460
|
+
const matchers = (policy.mandatory ?? []).map((m) => ({ ...m, re: globToRegExp(m.glob) }));
|
|
16461
|
+
const mandatoryHits = changed.filter((f) => matchers.some((m) => m.re.test(f.path)));
|
|
16462
|
+
const testChanges = changed.filter((f) => isTestPath(f.path));
|
|
16463
|
+
const addedTests = testChanges.filter((f) => f.status === "A");
|
|
16464
|
+
const removed = removedPaths(changed);
|
|
16465
|
+
const discharged = (m) => (m.satisfiedBy?.length ?? 0) > 0 && m.satisfiedBy.every((p) => present(p) && !removed.has(p));
|
|
16466
|
+
const untestedHits = changed.filter((f) => matchers.some((m) => m.re.test(f.path) && !discharged(m)));
|
|
16467
|
+
const protectedBy = new Map((policy.protected ?? []).map((p) => [p.path, p.why ?? ""]));
|
|
16468
|
+
for (const m of policy.mandatory ?? []) {
|
|
16469
|
+
for (const p of m.satisfiedBy ?? []) {
|
|
16470
|
+
if (!protectedBy.has(p)) protectedBy.set(p, `the standing coverage \`${m.glob}\` is discharged by. Removing it silently empties that glob.`);
|
|
16471
|
+
}
|
|
16472
|
+
}
|
|
16473
|
+
const removedProtected = [...removed].filter((p) => protectedBy.has(p)).map((p) => ({ path: p, why: protectedBy.get(p) ?? "" }));
|
|
16474
|
+
return { mandatoryHits, untestedHits, testChanges, addedTests, removedProtected };
|
|
16475
|
+
}
|
|
16476
|
+
function unresolvedProtectedEntries(policy, root, exists = (path2) => (0, import_node_fs19.existsSync)(path2)) {
|
|
16477
|
+
return (policy.protected ?? []).map((p) => p.path).filter((p) => !exists((0, import_node_path17.join)(root, p)));
|
|
16478
|
+
}
|
|
16479
|
+
function unresolvedSatisfiers(policy, root, exists = (path2) => (0, import_node_fs19.existsSync)(path2)) {
|
|
16480
|
+
const declared = (policy.mandatory ?? []).flatMap((m) => m.satisfiedBy ?? []);
|
|
16481
|
+
return [...new Set(declared)].filter((p) => !exists((0, import_node_path17.join)(root, p)));
|
|
16482
|
+
}
|
|
16483
|
+
function evaluate(changed, policy, present = () => false) {
|
|
16484
|
+
const { mandatoryHits, untestedHits, testChanges, addedTests, removedProtected } = classify(changed, policy, present);
|
|
16485
|
+
const findings = [];
|
|
16486
|
+
if (removedProtected.length > 0) {
|
|
16487
|
+
findings.push({
|
|
16488
|
+
kind: "protected-removed",
|
|
16489
|
+
paths: removedProtected.map((f) => f.path),
|
|
16490
|
+
detail: `PROTECTED TEST FILE(S) REMOVED \u2014 this change deletes or renames away ${removedProtected.length} file(s) test-policy.json protects:
|
|
16491
|
+
` + removedProtected.map((f) => ` ${f.path}
|
|
16492
|
+
${f.why}`).join("\n") + "\n If you are removing one because it looked unrequired, read the reason above first \u2014 it is\n the answer to exactly that question. If this removal was asked for, add a commit trailer:\n Test-Policy-Override: <reason>"
|
|
16493
|
+
});
|
|
16494
|
+
}
|
|
16495
|
+
if (untestedHits.length > 0 && testChanges.length === 0) {
|
|
16496
|
+
findings.push({
|
|
16497
|
+
kind: "mandatory-zone-untested",
|
|
16498
|
+
paths: untestedHits.map((f) => f.path),
|
|
16499
|
+
detail: `MANDATORY ZONE UNTESTED \u2014 this change touches ${untestedHits.length} path(s) in the mandatory zone but changes no test:
|
|
16500
|
+
` + untestedHits.map((f) => ` ${f.path}`).join("\n") + "\n These are the paths where a silent failure costs real money, real data, a security breach,\n or production. Add or update a test that would fail if the guarded property broke."
|
|
16501
|
+
});
|
|
16502
|
+
}
|
|
16503
|
+
if (policy.declared !== false && addedTests.length > 0 && mandatoryHits.length === 0) {
|
|
16504
|
+
findings.push({
|
|
16505
|
+
kind: "unrequested-test-file",
|
|
16506
|
+
paths: addedTests.map((f) => f.path),
|
|
16507
|
+
detail: `UNREQUESTED TEST FILE(S) \u2014 this change adds ${addedTests.length} new test file(s) but touches nothing in the mandatory zone:
|
|
16508
|
+
` + addedTests.map((f) => ` ${f.path}`).join("\n") + "\n Tests are OPT-IN in this estate. Write one only if its absence means catastrophe \u2014 money\n lost, data destroyed, a security breach, or production down. If a silent failure costs\n nothing but a re-run, delete the file and move on. If this test was asked for, add a commit\n trailer: Test-Policy-Override: <reason>"
|
|
16509
|
+
});
|
|
16510
|
+
}
|
|
16511
|
+
return findings;
|
|
16512
|
+
}
|
|
16513
|
+
function git(args, cwd) {
|
|
16514
|
+
return (0, import_node_child_process10.execFileSync)("git", args, { cwd, encoding: "utf8", maxBuffer: 32 * 1024 * 1024 });
|
|
16515
|
+
}
|
|
16516
|
+
function resolveBase(cwd, explicit) {
|
|
16517
|
+
const candidates = [explicit, process.env.TEST_POLICY_BASE, "origin/development", "origin/main"].filter(Boolean);
|
|
16518
|
+
for (const ref of candidates) {
|
|
16519
|
+
try {
|
|
16520
|
+
return git(["merge-base", "HEAD", ref], cwd).trim();
|
|
16521
|
+
} catch {
|
|
16522
|
+
}
|
|
16523
|
+
}
|
|
16524
|
+
return null;
|
|
16525
|
+
}
|
|
16526
|
+
function changedFilesSince(base, cwd) {
|
|
16527
|
+
const raw = git(["diff", "--name-status", "-M", `${base}...HEAD`], cwd).trim();
|
|
16528
|
+
if (!raw) return [];
|
|
16529
|
+
return raw.split("\n").map((line) => {
|
|
16530
|
+
const parts = line.split(" ");
|
|
16531
|
+
const norm = (p) => p.replace(/\\/g, "/");
|
|
16532
|
+
const row = { status: parts[0][0], path: norm(parts[parts.length - 1]) };
|
|
16533
|
+
if (parts.length > 2) row.from = norm(parts[1]);
|
|
16534
|
+
return row;
|
|
16535
|
+
});
|
|
16536
|
+
}
|
|
16537
|
+
function runTestPolicy(root, deps = {}) {
|
|
16538
|
+
const policy = deps.policy ?? loadPolicy(root);
|
|
16539
|
+
const exists = deps.exists ?? ((path2) => (0, import_node_fs19.existsSync)(path2));
|
|
16540
|
+
const counts = { mandatoryCount: (policy.mandatory ?? []).length, protectedCount: (policy.protected ?? []).length };
|
|
16541
|
+
const base = deps.changed ? "(injected)" : resolveBase(root, deps.base);
|
|
16542
|
+
const changed = deps.changed ?? (base ? changedFilesSince(base, root) : []);
|
|
16543
|
+
const override = deps.overrideReason !== void 0 ? deps.overrideReason : !deps.changed && base ? OVERRIDE_RE.exec(git(["log", `${base}..HEAD`, "--format=%B"], root))?.[1]?.trim() ?? null : null;
|
|
16544
|
+
if (override) return { ok: true, findings: [], changedCount: changed.length, base, overriddenBy: override, ...counts };
|
|
16545
|
+
const present = (path2) => exists((0, import_node_path17.join)(root, path2));
|
|
16546
|
+
const removedByThisDiff = removedPaths(changed);
|
|
16547
|
+
const staleFindings = [];
|
|
16548
|
+
const unresolved = unresolvedProtectedEntries(policy, root, exists).filter((p) => !removedByThisDiff.has(p));
|
|
16549
|
+
if (unresolved.length > 0) {
|
|
16550
|
+
staleFindings.push({
|
|
16551
|
+
kind: "stale-protected-entry",
|
|
16552
|
+
paths: unresolved,
|
|
16553
|
+
detail: `STALE PROTECTED ENTRY \u2014 test-policy.json protects ${unresolved.length} path(s) that do not exist:
|
|
16554
|
+
` + unresolved.map((p) => ` ${p}`).join("\n") + "\n An entry naming a missing file reads as protection while protecting nothing.\n Restore the file, or remove its entry."
|
|
16555
|
+
});
|
|
16556
|
+
}
|
|
16557
|
+
const staleSatisfiers = unresolvedSatisfiers(policy, root, exists).filter((p) => !removedByThisDiff.has(p));
|
|
16558
|
+
if (staleSatisfiers.length > 0) {
|
|
16559
|
+
staleFindings.push({
|
|
16560
|
+
kind: "stale-satisfied-by",
|
|
16561
|
+
paths: staleSatisfiers,
|
|
16562
|
+
detail: `STALE STANDING COVERAGE \u2014 a mandatory glob claims ${staleSatisfiers.length} path(s) as satisfiedBy that do not exist:
|
|
16563
|
+
` + staleSatisfiers.map((p) => ` ${p}`).join("\n") + "\n A glob discharged by coverage that is not there is a glob enforcing nothing, quietly.\n Restore the file, or drop it from satisfiedBy so the glob asks for a test again."
|
|
16564
|
+
});
|
|
16565
|
+
}
|
|
16566
|
+
if (staleFindings.length > 0) {
|
|
16567
|
+
return { ok: false, base, changedCount: changed.length, ...counts, findings: staleFindings };
|
|
16568
|
+
}
|
|
16569
|
+
if (!deps.changed && !base) return { ok: true, findings: [], changedCount: 0, base: null, ...counts };
|
|
16570
|
+
if (changed.length === 0) return { ok: true, findings: [], changedCount: 0, base, ...counts };
|
|
16571
|
+
const findings = evaluate(changed, policy, present);
|
|
16572
|
+
return { ok: findings.length === 0, findings, changedCount: changed.length, base, ...counts };
|
|
16573
|
+
}
|
|
16574
|
+
|
|
16367
16575
|
// src/docs-audit-command.ts
|
|
16368
16576
|
function serializeOutcome(outcome) {
|
|
16369
16577
|
switch (outcome.kind) {
|
|
@@ -17305,8 +17513,8 @@ function writeError(res) {
|
|
|
17305
17513
|
}
|
|
17306
17514
|
|
|
17307
17515
|
// src/secrets-commands.ts
|
|
17308
|
-
var
|
|
17309
|
-
var
|
|
17516
|
+
var import_node_fs20 = require("node:fs");
|
|
17517
|
+
var import_node_path18 = require("node:path");
|
|
17310
17518
|
var import_node_os5 = require("node:os");
|
|
17311
17519
|
|
|
17312
17520
|
// src/project-runtime.ts
|
|
@@ -17430,18 +17638,18 @@ function collectMap(value, previous = []) {
|
|
|
17430
17638
|
return [...previous, value];
|
|
17431
17639
|
}
|
|
17432
17640
|
async function decryptRailsCredentials(input) {
|
|
17433
|
-
const appDir = (0,
|
|
17641
|
+
const appDir = (0, import_node_path18.resolve)(input.appDir ?? process.cwd());
|
|
17434
17642
|
const credentialsFile = input.credentialsFile ?? DEFAULT_RAILS_CREDENTIALS_FILE;
|
|
17435
17643
|
const masterKeyFile = input.masterKeyFile ?? DEFAULT_RAILS_MASTER_KEY_FILE;
|
|
17436
|
-
const credentialsPath = (0,
|
|
17437
|
-
const masterKeyPath = (0,
|
|
17644
|
+
const credentialsPath = (0, import_node_path18.resolve)(appDir, credentialsFile);
|
|
17645
|
+
const masterKeyPath = (0, import_node_path18.resolve)(appDir, masterKeyFile);
|
|
17438
17646
|
const env = {
|
|
17439
17647
|
...process.env,
|
|
17440
17648
|
MMI_RAILS_CREDENTIALS_FILE: credentialsPath,
|
|
17441
17649
|
MMI_RAILS_MASTER_KEY_FILE: masterKeyPath
|
|
17442
17650
|
};
|
|
17443
|
-
if ((0,
|
|
17444
|
-
env.RAILS_MASTER_KEY = (0,
|
|
17651
|
+
if ((0, import_node_fs20.existsSync)(masterKeyPath)) {
|
|
17652
|
+
env.RAILS_MASTER_KEY = (0, import_node_fs20.readFileSync)(masterKeyPath, "utf8").trim();
|
|
17445
17653
|
}
|
|
17446
17654
|
const script = [
|
|
17447
17655
|
'require "json"',
|
|
@@ -17451,9 +17659,9 @@ async function decryptRailsCredentials(input) {
|
|
|
17451
17659
|
'config = ActiveSupport::EncryptedConfiguration.new(config_path: config_path, key_path: key_path, env_key: "RAILS_MASTER_KEY", raise_if_missing_key: true)',
|
|
17452
17660
|
"puts JSON.generate(config.config)"
|
|
17453
17661
|
].join("\n");
|
|
17454
|
-
const scriptDir = (0,
|
|
17455
|
-
const scriptPath = (0,
|
|
17456
|
-
(0,
|
|
17662
|
+
const scriptDir = (0, import_node_fs20.mkdtempSync)((0, import_node_path18.join)((0, import_node_os5.tmpdir)(), "mmi-rails-decrypt-"));
|
|
17663
|
+
const scriptPath = (0, import_node_path18.join)(scriptDir, "decrypt.rb");
|
|
17664
|
+
(0, import_node_fs20.writeFileSync)(scriptPath, script, "utf8");
|
|
17457
17665
|
try {
|
|
17458
17666
|
const args = ["exec", "ruby", scriptPath];
|
|
17459
17667
|
const cmd = process.platform === "win32" ? "cmd.exe" : "bundle";
|
|
@@ -17465,7 +17673,7 @@ async function decryptRailsCredentials(input) {
|
|
|
17465
17673
|
});
|
|
17466
17674
|
return JSON.parse(stdout);
|
|
17467
17675
|
} finally {
|
|
17468
|
-
(0,
|
|
17676
|
+
(0, import_node_fs20.rmSync)(scriptDir, { recursive: true, force: true });
|
|
17469
17677
|
}
|
|
17470
17678
|
}
|
|
17471
17679
|
async function readSecretStdin() {
|
|
@@ -17555,7 +17763,7 @@ function registerSecretsCommands(program3) {
|
|
|
17555
17763
|
let body;
|
|
17556
17764
|
if (o.file) {
|
|
17557
17765
|
try {
|
|
17558
|
-
body = (0,
|
|
17766
|
+
body = (0, import_node_fs20.readFileSync)((0, import_node_path18.resolve)(o.file), "utf8");
|
|
17559
17767
|
} catch (e) {
|
|
17560
17768
|
return fail(`secrets org-catalog: cannot read --file ${o.file}: ${e.message}`);
|
|
17561
17769
|
}
|
|
@@ -17660,7 +17868,7 @@ function registerSecretsCommands(program3) {
|
|
|
17660
17868
|
{
|
|
17661
17869
|
...d,
|
|
17662
17870
|
decryptRailsCredentials,
|
|
17663
|
-
removeFile: (path2) => (0,
|
|
17871
|
+
removeFile: (path2) => (0, import_node_fs20.unlinkSync)((0, import_node_path18.resolve)(o.appDir ?? process.cwd(), path2))
|
|
17664
17872
|
},
|
|
17665
17873
|
{
|
|
17666
17874
|
repo: o.repo,
|
|
@@ -17854,7 +18062,7 @@ function checkGithubPools(probe) {
|
|
|
17854
18062
|
}
|
|
17855
18063
|
|
|
17856
18064
|
// src/box-commands.ts
|
|
17857
|
-
var
|
|
18065
|
+
var import_node_fs21 = require("node:fs");
|
|
17858
18066
|
|
|
17859
18067
|
// src/box.ts
|
|
17860
18068
|
var BOX_KEYS = {
|
|
@@ -18057,7 +18265,7 @@ function registerBoxCommands(program3) {
|
|
|
18057
18265
|
}
|
|
18058
18266
|
if (o.json) console.log(JSON.stringify({ box: found, incomplete }, null, 2));
|
|
18059
18267
|
else if (o.ssh && o.script) {
|
|
18060
|
-
(0,
|
|
18268
|
+
(0, import_node_fs21.writeFileSync)(o.script, sshRecipeScript(found), "utf8");
|
|
18061
18269
|
console.log(`wrote ${o.script} \u2014 run: bash "${o.script}"`);
|
|
18062
18270
|
} else if (o.ssh) console.log(`${formatSshRecipe(found)}
|
|
18063
18271
|
${SSH_RECIPE_AGENT_NOTE}`);
|
|
@@ -18072,7 +18280,7 @@ ${SSH_RECIPE_AGENT_NOTE}`);
|
|
|
18072
18280
|
|
|
18073
18281
|
// src/schedules-commands.ts
|
|
18074
18282
|
var import_promises2 = require("node:fs/promises");
|
|
18075
|
-
var
|
|
18283
|
+
var import_node_child_process11 = require("node:child_process");
|
|
18076
18284
|
var import_node_util7 = require("node:util");
|
|
18077
18285
|
|
|
18078
18286
|
// src/schedules.ts
|
|
@@ -18512,7 +18720,7 @@ function assembleReconciliation(githubEntries2, readRepos, registry2, activeWork
|
|
|
18512
18720
|
}
|
|
18513
18721
|
|
|
18514
18722
|
// src/schedules-commands.ts
|
|
18515
|
-
var execFileP5 = (0, import_node_util7.promisify)(
|
|
18723
|
+
var execFileP5 = (0, import_node_util7.promisify)(import_node_child_process11.execFile);
|
|
18516
18724
|
var AWS_REGION = "eu-central-1";
|
|
18517
18725
|
var AWS_TIMEOUT_MS = 3e4;
|
|
18518
18726
|
var AWS_RETRY_DELAY_MS = 1500;
|
|
@@ -18749,7 +18957,7 @@ function registerSchedulesCommands(program3) {
|
|
|
18749
18957
|
|
|
18750
18958
|
// src/file-lock.ts
|
|
18751
18959
|
var import_promises3 = require("node:fs/promises");
|
|
18752
|
-
var
|
|
18960
|
+
var import_node_path19 = require("node:path");
|
|
18753
18961
|
var sleep = (ms) => new Promise((resolve5) => setTimeout(resolve5, ms));
|
|
18754
18962
|
var IMMEDIATE_RETRY_BUDGET = 3;
|
|
18755
18963
|
var FileLockBusyError = class extends Error {
|
|
@@ -18834,7 +19042,7 @@ async function releaseFileLock(lockPath, guard) {
|
|
|
18834
19042
|
}
|
|
18835
19043
|
async function withFileLock(lockPath, opts, fn) {
|
|
18836
19044
|
const resolved = resolveFileLockOpts(opts);
|
|
18837
|
-
await (0, import_promises3.mkdir)((0,
|
|
19045
|
+
await (0, import_promises3.mkdir)((0, import_node_path19.dirname)(lockPath), { recursive: true }).catch(() => void 0);
|
|
18838
19046
|
const guard = await acquireFileLock(lockPath, resolved, Date.now() + resolved.maxWaitMs);
|
|
18839
19047
|
try {
|
|
18840
19048
|
return await fn();
|
|
@@ -18845,7 +19053,7 @@ async function withFileLock(lockPath, opts, fn) {
|
|
|
18845
19053
|
|
|
18846
19054
|
// src/schedules-lift-command.ts
|
|
18847
19055
|
var import_promises4 = require("node:fs/promises");
|
|
18848
|
-
var
|
|
19056
|
+
var import_node_path20 = require("node:path");
|
|
18849
19057
|
|
|
18850
19058
|
// src/schedules-lift.ts
|
|
18851
19059
|
var SCHEDULE_HEADER_FIELDS = ["schedule", "what", "owner", "cadence", "executor", "llm", "output", "breaks", "kill"];
|
|
@@ -18951,7 +19159,7 @@ async function readWorkflowFiles(dir) {
|
|
|
18951
19159
|
const files = [];
|
|
18952
19160
|
for (const name of names.sort()) {
|
|
18953
19161
|
if (!/\.ya?ml$/.test(name)) continue;
|
|
18954
|
-
files.push({ path: `.github/workflows/${name}`, text: await (0, import_promises4.readFile)((0,
|
|
19162
|
+
files.push({ path: `.github/workflows/${name}`, text: await (0, import_promises4.readFile)((0, import_node_path20.join)(dir, name), "utf8") });
|
|
18955
19163
|
}
|
|
18956
19164
|
return files;
|
|
18957
19165
|
}
|
|
@@ -19493,7 +19701,7 @@ function registerQueryCommands(program3) {
|
|
|
19493
19701
|
}
|
|
19494
19702
|
|
|
19495
19703
|
// src/bootstrap-commands.ts
|
|
19496
|
-
var
|
|
19704
|
+
var import_node_fs22 = require("node:fs");
|
|
19497
19705
|
|
|
19498
19706
|
// src/bootstrap-verify.ts
|
|
19499
19707
|
var TRAIN_BRANCHES2 = ["development", "rc", "main"];
|
|
@@ -20036,7 +20244,7 @@ function registerBootstrapCommands(program3) {
|
|
|
20036
20244
|
client: defaultGitHubClient(),
|
|
20037
20245
|
projectMeta: meta,
|
|
20038
20246
|
deployModel: typeof meta?.deployModel === "string" ? meta.deployModel : void 0,
|
|
20039
|
-
readLocalFile: (path2) => path2 === "projects.json" && apiProjects != null ? apiProjects : (0,
|
|
20247
|
+
readLocalFile: (path2) => path2 === "projects.json" && apiProjects != null ? apiProjects : (0, import_node_fs22.existsSync)(path2) ? (0, import_node_fs22.readFileSync)(path2, "utf8") : null,
|
|
20040
20248
|
// requiredGcpApis is stored as an array by a JSON write, but `org project set --var KEY=VALUE` stores a raw
|
|
20041
20249
|
// comma-string — accept either so the seeded value verifies regardless of how it was written.
|
|
20042
20250
|
requiredGcpApis: (() => {
|
|
@@ -20091,12 +20299,12 @@ function registerBootstrapCommands(program3) {
|
|
|
20091
20299
|
return fail(`bootstrap apply: ${e.message}`);
|
|
20092
20300
|
}
|
|
20093
20301
|
const manifestPath = "skills/bootstrap/seeds/manifest.json";
|
|
20094
|
-
if (!(0,
|
|
20095
|
-
const manifest = loadBootstrapSeeds((0,
|
|
20302
|
+
if (!(0, import_node_fs22.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`);
|
|
20303
|
+
const manifest = loadBootstrapSeeds((0, import_node_fs22.readFileSync)(manifestPath, "utf8"));
|
|
20096
20304
|
const baseBranch = o.class === "content" ? "main" : "development";
|
|
20097
20305
|
const slug = parsedRepo.slug;
|
|
20098
20306
|
const gh = async (args) => execFileP2("gh", args, { timeout: 2e4 });
|
|
20099
|
-
const readFile7 = (p) => (0,
|
|
20307
|
+
const readFile7 = (p) => (0, import_node_fs22.existsSync)(p) ? (0, import_node_fs22.readFileSync)(p, "utf8") : null;
|
|
20100
20308
|
const enc = (p) => p.split("/").map(encodeURIComponent).join("/");
|
|
20101
20309
|
const rawVars = {};
|
|
20102
20310
|
for (const value of cmdOpts.var ?? []) {
|
|
@@ -20330,12 +20538,12 @@ LIVE apply to ${repo}:
|
|
|
20330
20538
|
}
|
|
20331
20539
|
|
|
20332
20540
|
// src/stage-commands.ts
|
|
20333
|
-
var
|
|
20334
|
-
var
|
|
20541
|
+
var import_node_fs24 = require("node:fs");
|
|
20542
|
+
var import_node_path22 = require("node:path");
|
|
20335
20543
|
|
|
20336
20544
|
// src/port-registry.ts
|
|
20337
|
-
var
|
|
20338
|
-
var
|
|
20545
|
+
var import_node_fs23 = require("node:fs");
|
|
20546
|
+
var import_node_path21 = require("node:path");
|
|
20339
20547
|
|
|
20340
20548
|
// ../infra/port-geometry.mjs
|
|
20341
20549
|
var PORT_BLOCK = 100;
|
|
@@ -20349,8 +20557,8 @@ function nextPortBlock(registry2) {
|
|
|
20349
20557
|
return [base, base + PORT_SPAN];
|
|
20350
20558
|
}
|
|
20351
20559
|
function loadPortRegistry(path2) {
|
|
20352
|
-
if (!(0,
|
|
20353
|
-
const raw = JSON.parse((0,
|
|
20560
|
+
if (!(0, import_node_fs23.existsSync)(path2)) return {};
|
|
20561
|
+
const raw = JSON.parse((0, import_node_fs23.readFileSync)(path2, "utf8"));
|
|
20354
20562
|
const out = {};
|
|
20355
20563
|
for (const [key, value] of Object.entries(raw)) {
|
|
20356
20564
|
if (Array.isArray(value) && value.length === 2 && value.every((n) => typeof n === "number")) {
|
|
@@ -20364,9 +20572,9 @@ function ensurePortRange(repo, path2) {
|
|
|
20364
20572
|
const existing = registry2[repo];
|
|
20365
20573
|
if (existing) return existing;
|
|
20366
20574
|
const range = nextPortBlock(registry2);
|
|
20367
|
-
const raw = (0,
|
|
20575
|
+
const raw = (0, import_node_fs23.existsSync)(path2) ? JSON.parse((0, import_node_fs23.readFileSync)(path2, "utf8")) : {};
|
|
20368
20576
|
raw[repo] = range;
|
|
20369
|
-
(0,
|
|
20577
|
+
(0, import_node_fs23.writeFileSync)(path2, JSON.stringify(raw, null, 2) + "\n", "utf8");
|
|
20370
20578
|
return range;
|
|
20371
20579
|
}
|
|
20372
20580
|
function portCursorSeed(registry2) {
|
|
@@ -20388,22 +20596,22 @@ function existingPortRange(repo, registry2) {
|
|
|
20388
20596
|
return registry2[repo] ?? null;
|
|
20389
20597
|
}
|
|
20390
20598
|
function portRangeInfraAt(root, source) {
|
|
20391
|
-
const registryPath = (0,
|
|
20392
|
-
const ddbScriptPath = (0,
|
|
20393
|
-
if (!(0,
|
|
20599
|
+
const registryPath = (0, import_node_path21.join)(root, "infra", "port-ranges.json");
|
|
20600
|
+
const ddbScriptPath = (0, import_node_path21.join)(root, "infra", "port-ddb.mjs");
|
|
20601
|
+
if (!(0, import_node_fs23.existsSync)(registryPath) || !(0, import_node_fs23.existsSync)(ddbScriptPath)) return null;
|
|
20394
20602
|
return { root, source, registryPath, ddbScriptPath };
|
|
20395
20603
|
}
|
|
20396
20604
|
function resolvePortRangeInfra(cwd, packageDir) {
|
|
20397
20605
|
const direct = portRangeInfraAt(cwd, "cwd");
|
|
20398
20606
|
if (direct) return direct;
|
|
20399
|
-
for (let dir = cwd; ; dir = (0,
|
|
20400
|
-
const sibling = portRangeInfraAt((0,
|
|
20607
|
+
for (let dir = cwd; ; dir = (0, import_node_path21.dirname)(dir)) {
|
|
20608
|
+
const sibling = portRangeInfraAt((0, import_node_path21.join)(dir, "MMI-Hub"), "sibling-hub");
|
|
20401
20609
|
if (sibling) return sibling;
|
|
20402
|
-
const parent = (0,
|
|
20610
|
+
const parent = (0, import_node_path21.dirname)(dir);
|
|
20403
20611
|
if (parent === dir) break;
|
|
20404
20612
|
}
|
|
20405
20613
|
if (packageDir) {
|
|
20406
|
-
const pkgRoot = (0,
|
|
20614
|
+
const pkgRoot = (0, import_node_path21.join)(packageDir, "..", "..");
|
|
20407
20615
|
const pkgFrom = portRangeInfraAt(pkgRoot, "pkg-root");
|
|
20408
20616
|
if (pkgFrom) return pkgFrom;
|
|
20409
20617
|
}
|
|
@@ -20579,8 +20787,8 @@ function registerStageCommands(program3) {
|
|
|
20579
20787
|
const portRange = portRangeMeta && typeof portRangeMeta.start === "number" && typeof portRangeMeta.end === "number" ? [portRangeMeta.start, portRangeMeta.end] : void 0;
|
|
20580
20788
|
return decideStage({
|
|
20581
20789
|
registry: { deployModel: project2?.deployModel, portRange, error: read.ok ? void 0 : read.error },
|
|
20582
|
-
hasCompose: (0,
|
|
20583
|
-
hasEnvExample: (0,
|
|
20790
|
+
hasCompose: (0, import_node_fs24.existsSync)((0, import_node_path22.join)(process.cwd(), "docker-compose.yml")),
|
|
20791
|
+
hasEnvExample: (0, import_node_fs24.existsSync)((0, import_node_path22.join)(process.cwd(), ".env.example"))
|
|
20584
20792
|
});
|
|
20585
20793
|
}
|
|
20586
20794
|
async function fetchStageVaultEnvMerge() {
|
|
@@ -21012,10 +21220,10 @@ function registerBoardCommands(program3) {
|
|
|
21012
21220
|
}
|
|
21013
21221
|
|
|
21014
21222
|
// src/merge-cleanup.ts
|
|
21015
|
-
var
|
|
21223
|
+
var import_node_fs27 = require("node:fs");
|
|
21016
21224
|
var import_promises6 = require("node:fs/promises");
|
|
21017
|
-
var
|
|
21018
|
-
var
|
|
21225
|
+
var import_node_path26 = require("node:path");
|
|
21226
|
+
var import_node_child_process12 = require("node:child_process");
|
|
21019
21227
|
|
|
21020
21228
|
// src/board-advance.ts
|
|
21021
21229
|
function repoOf2(ref) {
|
|
@@ -21101,7 +21309,7 @@ function boardAdvanceFailureMessage(result) {
|
|
|
21101
21309
|
|
|
21102
21310
|
// src/deferred-registry-store.ts
|
|
21103
21311
|
var import_promises5 = require("node:fs/promises");
|
|
21104
|
-
var
|
|
21312
|
+
var import_node_path23 = require("node:path");
|
|
21105
21313
|
var sleep2 = (ms) => new Promise((resolve5) => setTimeout(resolve5, ms));
|
|
21106
21314
|
async function atomicWrite(target, contents) {
|
|
21107
21315
|
const tmp = `${target}.tmp-${process.pid}-${Math.random().toString(36).slice(2)}`;
|
|
@@ -21152,12 +21360,12 @@ function makeDeferredWorktreeStore(registryPath, lockOpts = {}) {
|
|
|
21152
21360
|
},
|
|
21153
21361
|
// Standalone atomic write — THROWS on failure (no best-effort swallow, #2846).
|
|
21154
21362
|
write: async (entries) => {
|
|
21155
|
-
await (0, import_promises5.mkdir)((0,
|
|
21363
|
+
await (0, import_promises5.mkdir)((0, import_node_path23.dirname)(registryPath), { recursive: true });
|
|
21156
21364
|
await atomicWrite(registryPath, serializeDeferredWorktrees(entries));
|
|
21157
21365
|
},
|
|
21158
21366
|
// Serialized read-modify-write under the repo-wide lock (#2846).
|
|
21159
21367
|
update: async (mutate) => {
|
|
21160
|
-
await (0, import_promises5.mkdir)((0,
|
|
21368
|
+
await (0, import_promises5.mkdir)((0, import_node_path23.dirname)(registryPath), { recursive: true });
|
|
21161
21369
|
const deadline = Date.now() + opts.maxWaitMs;
|
|
21162
21370
|
for (; ; ) {
|
|
21163
21371
|
const guard = await acquireLock(lockPath, opts, deadline);
|
|
@@ -21181,8 +21389,8 @@ function makeDeferredWorktreeStore(registryPath, lockOpts = {}) {
|
|
|
21181
21389
|
}
|
|
21182
21390
|
|
|
21183
21391
|
// src/plugin-guard-io.ts
|
|
21184
|
-
var
|
|
21185
|
-
var
|
|
21392
|
+
var import_node_fs25 = require("node:fs");
|
|
21393
|
+
var import_node_path24 = require("node:path");
|
|
21186
21394
|
var import_node_os6 = require("node:os");
|
|
21187
21395
|
|
|
21188
21396
|
// src/plugin-guard.ts
|
|
@@ -21315,11 +21523,11 @@ function runHostBin(bin, args, opts) {
|
|
|
21315
21523
|
}
|
|
21316
21524
|
var installedPluginsPath2 = (surface = detectSurface(process.env)) => {
|
|
21317
21525
|
const homeDir = surface === "codex" ? ".codex" : ".claude";
|
|
21318
|
-
return (0,
|
|
21526
|
+
return (0, import_node_path24.join)((0, import_node_os6.homedir)(), homeDir, "plugins", "installed_plugins.json");
|
|
21319
21527
|
};
|
|
21320
21528
|
function readInstalledPlugins(surface = detectSurface(process.env)) {
|
|
21321
21529
|
try {
|
|
21322
|
-
return JSON.parse((0,
|
|
21530
|
+
return JSON.parse((0, import_node_fs25.readFileSync)(installedPluginsPath2(surface), "utf8"));
|
|
21323
21531
|
} catch {
|
|
21324
21532
|
return null;
|
|
21325
21533
|
}
|
|
@@ -21327,13 +21535,13 @@ function readInstalledPlugins(surface = detectSurface(process.env)) {
|
|
|
21327
21535
|
function marketplaceCloneCandidates(surface, home) {
|
|
21328
21536
|
if (surface === "codex") {
|
|
21329
21537
|
return [
|
|
21330
|
-
(0,
|
|
21331
|
-
(0,
|
|
21538
|
+
(0, import_node_path24.join)(home, ".codex", ".tmp", "marketplaces", "mutmutco"),
|
|
21539
|
+
(0, import_node_path24.join)(home, ".codex", "plugins", "marketplaces", "mutmutco")
|
|
21332
21540
|
];
|
|
21333
21541
|
}
|
|
21334
|
-
return [(0,
|
|
21542
|
+
return [(0, import_node_path24.join)(home, ".claude", "plugins", "marketplaces", "mutmutco")];
|
|
21335
21543
|
}
|
|
21336
|
-
function marketplaceClonePresent(surface, home, exists =
|
|
21544
|
+
function marketplaceClonePresent(surface, home, exists = import_node_fs25.existsSync) {
|
|
21337
21545
|
return marketplaceCloneCandidates(surface, home).some(exists);
|
|
21338
21546
|
}
|
|
21339
21547
|
async function fetchNpmReleasedVersion() {
|
|
@@ -21361,7 +21569,7 @@ function snapshotPluginGuardInput(surface = detectSurface(process.env), isOrgRep
|
|
|
21361
21569
|
isOrgRepo,
|
|
21362
21570
|
installRecordPresent: hasUserInstallRecord(installed, MMI_PLUGIN_ID) || hasProjectInstallRecord(installed, MMI_PLUGIN_ID, process.cwd()),
|
|
21363
21571
|
marketplaceClonePresent: marketplaceClonePresent(surface, (0, import_node_os6.homedir)()),
|
|
21364
|
-
pluginCachePresent: (0,
|
|
21572
|
+
pluginCachePresent: (0, import_node_fs25.existsSync)((0, import_node_path24.join)((0, import_node_os6.homedir)(), homeDir, "plugins", "cache", "mutmutco", "mmi"))
|
|
21365
21573
|
};
|
|
21366
21574
|
}
|
|
21367
21575
|
function claudePluginGuardState(isOrgRepo) {
|
|
@@ -21407,7 +21615,7 @@ async function applyPluginHeal(surface, log, opts) {
|
|
|
21407
21615
|
const refSupported = await marketplaceAddRefSupported("claude");
|
|
21408
21616
|
const { steps } = adaptHealStepsForRefSupport(tableSteps, refSupported);
|
|
21409
21617
|
log(healBannerLine("claude", "claude", refSupported));
|
|
21410
|
-
const pinsPath = (0,
|
|
21618
|
+
const pinsPath = (0, import_node_path24.join)((0, import_node_os6.homedir)(), ...KNOWN_MARKETPLACES_RELATIVE);
|
|
21411
21619
|
const pins = captureMarketplacePins(readKnownMarketplacesFile(pinsPath), [MMI_MARKETPLACE_NAME, JERV_MARKETPLACE_NAME]);
|
|
21412
21620
|
for (const step of steps) {
|
|
21413
21621
|
if (healStepAborts(step, await runClaudePlugin([...step.args]))) return false;
|
|
@@ -21430,7 +21638,7 @@ async function healClaudePluginForDoctor(surface = detectSurface(process.env)) {
|
|
|
21430
21638
|
}
|
|
21431
21639
|
function readKnownMarketplacesFile(path2) {
|
|
21432
21640
|
try {
|
|
21433
|
-
return (0,
|
|
21641
|
+
return (0, import_node_fs25.existsSync)(path2) ? (0, import_node_fs25.readFileSync)(path2, "utf8") : void 0;
|
|
21434
21642
|
} catch {
|
|
21435
21643
|
return void 0;
|
|
21436
21644
|
}
|
|
@@ -21441,7 +21649,7 @@ function restoreMarketplacePinsOnDisk(path2, pins) {
|
|
|
21441
21649
|
const next = restoreMarketplacePins(after, pins);
|
|
21442
21650
|
if (next === null) return void 0;
|
|
21443
21651
|
try {
|
|
21444
|
-
(0,
|
|
21652
|
+
(0, import_node_fs25.writeFileSync)(path2, next, "utf8");
|
|
21445
21653
|
} catch {
|
|
21446
21654
|
return `could NOT restore ${[...pins.keys()].join(", ")} \u2014 re-pin by hand`;
|
|
21447
21655
|
}
|
|
@@ -21488,9 +21696,9 @@ async function runPluginHeal(surface = detectSurface(process.env)) {
|
|
|
21488
21696
|
}
|
|
21489
21697
|
|
|
21490
21698
|
// src/worktree-ownership.ts
|
|
21491
|
-
var
|
|
21699
|
+
var import_node_fs26 = require("node:fs");
|
|
21492
21700
|
var import_node_os7 = require("node:os");
|
|
21493
|
-
var
|
|
21701
|
+
var import_node_path25 = require("node:path");
|
|
21494
21702
|
var OWNERS_FILE = "worktree-owners.json";
|
|
21495
21703
|
var EVENTS_FILE = "worktree-events.jsonl";
|
|
21496
21704
|
var WORKTREE_ACTIVITY_WINDOW_MS = 30 * 6e4;
|
|
@@ -21587,7 +21795,7 @@ function describeActorShort(actor) {
|
|
|
21587
21795
|
}
|
|
21588
21796
|
function readOwners(primaryRoot) {
|
|
21589
21797
|
try {
|
|
21590
|
-
return parseWorktreeOwners((0,
|
|
21798
|
+
return parseWorktreeOwners((0, import_node_fs26.readFileSync)(worktreeOwnersPath(primaryRoot), "utf8"));
|
|
21591
21799
|
} catch {
|
|
21592
21800
|
return [];
|
|
21593
21801
|
}
|
|
@@ -21595,8 +21803,8 @@ function readOwners(primaryRoot) {
|
|
|
21595
21803
|
function writeOwners(primaryRoot, entries) {
|
|
21596
21804
|
try {
|
|
21597
21805
|
const path2 = worktreeOwnersPath(primaryRoot);
|
|
21598
|
-
(0,
|
|
21599
|
-
(0,
|
|
21806
|
+
(0, import_node_fs26.mkdirSync)((0, import_node_path25.dirname)(path2), { recursive: true });
|
|
21807
|
+
(0, import_node_fs26.writeFileSync)(path2, serializeWorktreeOwners(entries), "utf8");
|
|
21600
21808
|
} catch {
|
|
21601
21809
|
}
|
|
21602
21810
|
}
|
|
@@ -21629,8 +21837,8 @@ function dropWorktreeOwner(primaryRoot, path2) {
|
|
|
21629
21837
|
function appendWorktreeEvent(primaryRoot, event) {
|
|
21630
21838
|
try {
|
|
21631
21839
|
const path2 = worktreeEventsPath(primaryRoot);
|
|
21632
|
-
(0,
|
|
21633
|
-
(0,
|
|
21840
|
+
(0, import_node_fs26.mkdirSync)((0, import_node_path25.dirname)(path2), { recursive: true });
|
|
21841
|
+
(0, import_node_fs26.appendFileSync)(path2, `${JSON.stringify({ at: event.at ?? (/* @__PURE__ */ new Date()).toISOString(), ...event })}
|
|
21634
21842
|
`, "utf8");
|
|
21635
21843
|
} catch {
|
|
21636
21844
|
}
|
|
@@ -21638,7 +21846,7 @@ function appendWorktreeEvent(primaryRoot, event) {
|
|
|
21638
21846
|
function readWorktreeEvents(primaryRoot, limit = 50) {
|
|
21639
21847
|
let text;
|
|
21640
21848
|
try {
|
|
21641
|
-
text = (0,
|
|
21849
|
+
text = (0, import_node_fs26.readFileSync)(worktreeEventsPath(primaryRoot), "utf8");
|
|
21642
21850
|
} catch {
|
|
21643
21851
|
return [];
|
|
21644
21852
|
}
|
|
@@ -21715,7 +21923,18 @@ function recordGcWorktreeRemoval(primaryRoot, actor, owners, path2, branch) {
|
|
|
21715
21923
|
});
|
|
21716
21924
|
dropWorktreeOwner(primaryRoot, path2);
|
|
21717
21925
|
}
|
|
21718
|
-
function
|
|
21926
|
+
function renderPrLandCleanupLines(cleanup) {
|
|
21927
|
+
const report = cleanup;
|
|
21928
|
+
const wt = report?.worktree;
|
|
21929
|
+
if (!wt?.path) return [];
|
|
21930
|
+
const lines = [];
|
|
21931
|
+
if (wt.status === "removed") lines.push(`pr land: removed worktree ${wt.path} \u2014 that directory is gone; cd elsewhere before your next command`);
|
|
21932
|
+
else if (wt.status === "deferred") lines.push(`pr land: worktree ${wt.path} could not be removed yet and is registered for a later sweep`);
|
|
21933
|
+
else lines.push(`pr land: worktree ${wt.path} \u2014 ${wt.status ?? "unknown"}${wt.reason ? ` (${wt.reason})` : ""}`);
|
|
21934
|
+
if (wt.deferredNote) lines.push(`pr land: ${wt.deferredNote}`);
|
|
21935
|
+
return lines;
|
|
21936
|
+
}
|
|
21937
|
+
function renderGcApplyResult(result, skipped = []) {
|
|
21719
21938
|
const lines = ["worktree gc apply result:"];
|
|
21720
21939
|
lines.push(` branches removed: ${result.removedBranches.length ? result.removedBranches.join(", ") : "none"}`);
|
|
21721
21940
|
lines.push(` remote branches removed: ${result.removedRemoteBranches.length ? result.removedRemoteBranches.join(", ") : "none"}`);
|
|
@@ -21732,9 +21951,15 @@ function renderGcApplyResult(result) {
|
|
|
21732
21951
|
}
|
|
21733
21952
|
const removedNothing = result.removedBranches.length === 0 && result.removedRemoteBranches.length === 0 && result.removedTrackingRefs.length === 0 && result.removedWorktreeDirs.length === 0;
|
|
21734
21953
|
if (removedNothing && !result.refused.length) {
|
|
21735
|
-
|
|
21736
|
-
|
|
21737
|
-
|
|
21954
|
+
const actionable = skipped.filter((s) => s.reason !== "protected" && s.reason !== "current-branch");
|
|
21955
|
+
if (actionable.length) {
|
|
21956
|
+
lines.push(` NOTHING WAS REMOVED \u2014 every candidate was skipped for a stated reason (${actionable.length}):`);
|
|
21957
|
+
for (const s of actionable) lines.push(` - ${s.branch}: ${s.reason}${s.detail ? ` (${s.detail})` : ""}`);
|
|
21958
|
+
} else {
|
|
21959
|
+
lines.push(" NOTHING WAS REMOVED. A stale worktree whose directory still exists is not reachable by");
|
|
21960
|
+
lines.push(" this verb \u2014 remove it from inside it with `mmi-cli worktree land --apply`, or delete the");
|
|
21961
|
+
lines.push(" directory and re-run to clear its registration.");
|
|
21962
|
+
}
|
|
21738
21963
|
}
|
|
21739
21964
|
return lines.join("\n");
|
|
21740
21965
|
}
|
|
@@ -21788,7 +22013,7 @@ async function applyGcPlan(plan, remote, opts = {}) {
|
|
|
21788
22013
|
);
|
|
21789
22014
|
const repoRoot2 = (await execFileP2("git", ["rev-parse", "--show-toplevel"], { timeout: GIT_TIMEOUT_MS }).catch(() => ({ stdout: "" }))).stdout.trim() || process.cwd();
|
|
21790
22015
|
const worktreeGitRoot = await currentRepoWorktreeGitRoot(repoRoot2);
|
|
21791
|
-
const primaryRepoRoot = worktreeGitRoot ? (0,
|
|
22016
|
+
const primaryRepoRoot = worktreeGitRoot ? (0, import_node_path26.dirname)((0, import_node_path26.dirname)(worktreeGitRoot)) : repoRoot2;
|
|
21792
22017
|
const gcActor = describeActor({ env: process.env, surface: detectSurface(process.env), cwd: process.cwd() });
|
|
21793
22018
|
const owners = readWorktreeOwners(primaryRepoRoot);
|
|
21794
22019
|
const removalNow = Date.now();
|
|
@@ -21819,7 +22044,7 @@ async function applyGcPlan(plan, remote, opts = {}) {
|
|
|
21819
22044
|
const cleanup = await cleanupPrMergeLocalBranch(branch.branch, {
|
|
21820
22045
|
beforeWorktrees,
|
|
21821
22046
|
startingPath: branch.worktreePath,
|
|
21822
|
-
pathExists: (p) => (0,
|
|
22047
|
+
pathExists: (p) => (0, import_node_fs27.existsSync)(p),
|
|
21823
22048
|
execGit: async (args) => (await execFileP2("git", args, { timeout: GIT_TIMEOUT_MS })).stdout,
|
|
21824
22049
|
teardownWorktreeStage,
|
|
21825
22050
|
deferredStore,
|
|
@@ -21847,7 +22072,7 @@ async function applyGcPlan(plan, remote, opts = {}) {
|
|
|
21847
22072
|
for (const wt of worktreeDirsToRemove) {
|
|
21848
22073
|
try {
|
|
21849
22074
|
const cleanupTarget = resolveSafeSiblingWorktreeCleanupTarget(wt.path, siblingRoot, {
|
|
21850
|
-
realpath: (path2) => (0,
|
|
22075
|
+
realpath: (path2) => (0, import_node_fs27.realpathSync)(path2)
|
|
21851
22076
|
});
|
|
21852
22077
|
if (!cleanupTarget.ok) {
|
|
21853
22078
|
result.failed.push(`${wt.path}: ${cleanupTarget.reason}`);
|
|
@@ -21998,7 +22223,7 @@ async function remoteBranchExists2(branch, options = {}) {
|
|
|
21998
22223
|
}
|
|
21999
22224
|
var COMPOSE_TIMEOUT_MS = 12e4;
|
|
22000
22225
|
function spawnDeferredGcSweep() {
|
|
22001
|
-
spawnDetachedSelf(["worktree", "gc", "sweep-deferred", "--quiet"], { spawn:
|
|
22226
|
+
spawnDetachedSelf(["worktree", "gc", "sweep-deferred", "--quiet"], { spawn: import_node_child_process12.spawn, execPath: process.execPath, scriptPath: process.argv[1] });
|
|
22002
22227
|
}
|
|
22003
22228
|
async function createDeferredWorktreeStore() {
|
|
22004
22229
|
try {
|
|
@@ -22012,13 +22237,13 @@ var realWorktreeDirRemover = {
|
|
|
22012
22237
|
probe: (p) => {
|
|
22013
22238
|
let st;
|
|
22014
22239
|
try {
|
|
22015
|
-
st = (0,
|
|
22240
|
+
st = (0, import_node_fs27.lstatSync)(p);
|
|
22016
22241
|
} catch {
|
|
22017
22242
|
return null;
|
|
22018
22243
|
}
|
|
22019
22244
|
if (st.isSymbolicLink()) return "link";
|
|
22020
22245
|
try {
|
|
22021
|
-
(0,
|
|
22246
|
+
(0, import_node_fs27.readlinkSync)(p);
|
|
22022
22247
|
return "link";
|
|
22023
22248
|
} catch {
|
|
22024
22249
|
}
|
|
@@ -22026,7 +22251,7 @@ var realWorktreeDirRemover = {
|
|
|
22026
22251
|
},
|
|
22027
22252
|
readdir: (p) => {
|
|
22028
22253
|
try {
|
|
22029
|
-
return (0,
|
|
22254
|
+
return (0, import_node_fs27.readdirSync)(p);
|
|
22030
22255
|
} catch {
|
|
22031
22256
|
return [];
|
|
22032
22257
|
}
|
|
@@ -22035,9 +22260,9 @@ var realWorktreeDirRemover = {
|
|
|
22035
22260
|
// leaving the target); a file symlink with unlink. rmdir first, fall back to unlink.
|
|
22036
22261
|
detachLink: (p) => {
|
|
22037
22262
|
try {
|
|
22038
|
-
(0,
|
|
22263
|
+
(0, import_node_fs27.rmdirSync)(p);
|
|
22039
22264
|
} catch {
|
|
22040
|
-
(0,
|
|
22265
|
+
(0, import_node_fs27.unlinkSync)(p);
|
|
22041
22266
|
}
|
|
22042
22267
|
},
|
|
22043
22268
|
removeTree: (p) => (0, import_promises6.rm)(p, { recursive: true, force: true, maxRetries: 5, retryDelay: 200 })
|
|
@@ -22070,9 +22295,9 @@ async function worktreeHasStageState(worktreePath) {
|
|
|
22070
22295
|
}
|
|
22071
22296
|
}
|
|
22072
22297
|
function stageStateFileBelongsToWorktree(statePath, worktreePath) {
|
|
22073
|
-
if (!(0,
|
|
22298
|
+
if (!(0, import_node_fs27.existsSync)(statePath)) return false;
|
|
22074
22299
|
try {
|
|
22075
|
-
const state = JSON.parse((0,
|
|
22300
|
+
const state = JSON.parse((0, import_node_fs27.readFileSync)(statePath, "utf8"));
|
|
22076
22301
|
const recordedCwd = typeof state.identity?.cwd === "string" ? state.identity.cwd : typeof state.cwd === "string" ? state.cwd : "";
|
|
22077
22302
|
return Boolean(recordedCwd && isPathUnderDirectory(recordedCwd, worktreePath));
|
|
22078
22303
|
} catch {
|
|
@@ -22279,8 +22504,8 @@ async function fetchRestCorePool(gh = defaultGhApi) {
|
|
|
22279
22504
|
}
|
|
22280
22505
|
|
|
22281
22506
|
// src/worktree-lifecycle-commands.ts
|
|
22282
|
-
var
|
|
22283
|
-
var
|
|
22507
|
+
var import_node_fs28 = require("node:fs");
|
|
22508
|
+
var import_node_path27 = require("node:path");
|
|
22284
22509
|
var GH_TIMEOUT_MS = 2e4;
|
|
22285
22510
|
var DEFAULT_BASE = "origin/development";
|
|
22286
22511
|
var DEFAULT_REMOTE = "origin";
|
|
@@ -22383,7 +22608,7 @@ function classifyStaleLeaks(input) {
|
|
|
22383
22608
|
var defaultOrphanDirScanDeps = {
|
|
22384
22609
|
listDirs: (root) => {
|
|
22385
22610
|
try {
|
|
22386
|
-
return (0,
|
|
22611
|
+
return (0, import_node_fs28.readdirSync)(root, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => (0, import_node_path27.join)(root, e.name));
|
|
22387
22612
|
} catch {
|
|
22388
22613
|
return [];
|
|
22389
22614
|
}
|
|
@@ -22530,13 +22755,13 @@ function registerWorktreeCommands(program3) {
|
|
|
22530
22755
|
if (PROTECTED_BRANCHES2.has(branch)) {
|
|
22531
22756
|
return fail(`worktree land: refusing to land protected branch '${branch}'`, { code: ERROR_CODES.ERR_BAD_ENUM });
|
|
22532
22757
|
}
|
|
22533
|
-
const gitFile = (0,
|
|
22534
|
-
const isLinked = (0,
|
|
22758
|
+
const gitFile = (0, import_node_path27.join)(wtPath, ".git");
|
|
22759
|
+
const isLinked = (0, import_node_fs28.existsSync)(gitFile) && (0, import_node_fs28.statSync)(gitFile).isFile();
|
|
22535
22760
|
if (apply && !isLinked) {
|
|
22536
22761
|
return fail("worktree land: run from inside the linked worktree you want to land (this is the primary checkout)");
|
|
22537
22762
|
}
|
|
22538
22763
|
const commonDir = (await execFileP2("git", ["rev-parse", "--git-common-dir"], { timeout: GIT_TIMEOUT_MS }).catch(() => ({ stdout: "" }))).stdout.trim();
|
|
22539
|
-
const primaryCheckout = commonDir ? (0,
|
|
22764
|
+
const primaryCheckout = commonDir ? (0, import_node_path27.dirname)(commonDir) : wtPath;
|
|
22540
22765
|
const stageSummary = readStageSummary(wtPath);
|
|
22541
22766
|
const hasStage = stageSummary != null;
|
|
22542
22767
|
const steps = landSteps(branch, true, hasStage);
|
|
@@ -22688,10 +22913,10 @@ async function gatherWorktreeContext() {
|
|
|
22688
22913
|
if (s) stages.push({ path: wt.path, port: s.port });
|
|
22689
22914
|
}
|
|
22690
22915
|
const worktreeGitRoot = await currentRepoWorktreeGitRoot(repoRoot2);
|
|
22691
|
-
const primaryRepoRoot = worktreeGitRoot ? (0,
|
|
22916
|
+
const primaryRepoRoot = worktreeGitRoot ? (0, import_node_path27.dirname)((0, import_node_path27.dirname)(worktreeGitRoot)) : repoRoot2;
|
|
22692
22917
|
const wtRoot = siblingMmiWorktreesRoot(primaryRepoRoot);
|
|
22693
22918
|
let orphanDirs = [];
|
|
22694
|
-
if ((0,
|
|
22919
|
+
if ((0, import_node_fs28.existsSync)(wtRoot)) {
|
|
22695
22920
|
orphanDirs = scanOrphanDirs(wtRoot, worktreeGitRoot, {
|
|
22696
22921
|
...defaultOrphanDirScanDeps,
|
|
22697
22922
|
listDirs: (root) => worktreeScanDirs(root, primaryRepoRoot, defaultOrphanDirScanDeps.listDirs, isRepoCheckoutDir)
|
|
@@ -22714,7 +22939,7 @@ async function bestEffortGit(args, cwd, step, timeoutMs = GIT_TIMEOUT_MS) {
|
|
|
22714
22939
|
}
|
|
22715
22940
|
|
|
22716
22941
|
// src/issue-commands.ts
|
|
22717
|
-
var
|
|
22942
|
+
var import_node_fs29 = require("node:fs");
|
|
22718
22943
|
var import_node_crypto5 = require("node:crypto");
|
|
22719
22944
|
var ghRunner = async (args, timeoutMs) => (await execFileP2("gh", args, { timeout: timeoutMs })).stdout;
|
|
22720
22945
|
async function editIssue(client, options, deps = {}) {
|
|
@@ -22724,11 +22949,16 @@ async function editIssue(client, options, deps = {}) {
|
|
|
22724
22949
|
const url = `https://github.com/${repo}/issues/${parsed.number}`;
|
|
22725
22950
|
const patch = {};
|
|
22726
22951
|
let bodyChanged = false;
|
|
22727
|
-
|
|
22952
|
+
const textDeps = () => deps.textDeps ?? { readFile: (p, e) => Promise.resolve((0, import_node_fs29.readFileSync)(p, e)), readStdin: () => Promise.resolve("") };
|
|
22953
|
+
if (options.titleFile !== void 0) {
|
|
22954
|
+
patch.title = await resolveIssueTitle({ title: options.title, titleFile: options.titleFile }, textDeps());
|
|
22955
|
+
} else if (options.title !== void 0) {
|
|
22956
|
+
patch.title = options.title;
|
|
22957
|
+
}
|
|
22728
22958
|
if (options.body !== void 0 || options.bodyFile !== void 0) {
|
|
22729
22959
|
let body;
|
|
22730
22960
|
if (options.bodyFile) {
|
|
22731
|
-
const td =
|
|
22961
|
+
const td = textDeps();
|
|
22732
22962
|
body = await resolveIssueBody({ body: options.body, bodyFile: options.bodyFile }, td);
|
|
22733
22963
|
} else {
|
|
22734
22964
|
body = options.body ?? "";
|
|
@@ -23093,7 +23323,7 @@ function registerIssueLifecycleCommands(program3, deps = {}) {
|
|
|
23093
23323
|
const issue2 = program3.commands.find((c) => c.name() === "issue");
|
|
23094
23324
|
if (!issue2) return;
|
|
23095
23325
|
mutating(
|
|
23096
|
-
issue2.command("edit <ref>").description("edit an issue title/body/labels or reparent it (--parent) \u2014 reparenting is impossible via raw gh").option("--title <title>", "new title").option("--body <body>", "new body (markdown)").option("--body-file <path|->", "read new body from a UTF-8 file, or stdin with -").option("--add-label <label...>", "label(s) to add (repeatable)").option("--remove-label <label...>", "label(s) to remove (repeatable)").option("--parent <ref>", "reparent: link under this parent (#123, owner/repo#123, or URL)").option("--repo <owner/repo>", "repo for a bare ref (defaults to the current repo)"),
|
|
23326
|
+
issue2.command("edit <ref>").description("edit an issue title/body/labels or reparent it (--parent) \u2014 reparenting is impossible via raw gh").option("--title <title>", "new title").option("--title-file <path|->", "read the new title from a UTF-8 file, or stdin with - (a title with backticks or newlines needs this, #3381)").option("--body <body>", "new body (markdown)").option("--body-file <path|->", "read new body from a UTF-8 file, or stdin with -").option("--add-label <label...>", "label(s) to add (repeatable)").option("--remove-label <label...>", "label(s) to remove (repeatable)").option("--parent <ref>", "reparent: link under this parent (#123, owner/repo#123, or URL)").option("--repo <owner/repo>", "repo for a bare ref (defaults to the current repo)"),
|
|
23097
23327
|
(opts, args) => ({ command: "issue edit", ref: args[0], fields: Object.keys(opts).filter((k) => k !== "repo" && opts[k] !== void 0) })
|
|
23098
23328
|
).action(async (ref, o) => {
|
|
23099
23329
|
try {
|
|
@@ -23102,6 +23332,7 @@ function registerIssueLifecycleCommands(program3, deps = {}) {
|
|
|
23102
23332
|
ref,
|
|
23103
23333
|
defaultRepo,
|
|
23104
23334
|
title: o.title,
|
|
23335
|
+
titleFile: o.titleFile,
|
|
23105
23336
|
body: o.body,
|
|
23106
23337
|
bodyFile: o.bodyFile,
|
|
23107
23338
|
addLabel: o.addLabel,
|
|
@@ -23225,7 +23456,7 @@ function extendCreateCommand(issue2, batchAttach) {
|
|
|
23225
23456
|
if (opts.batch) {
|
|
23226
23457
|
let specs;
|
|
23227
23458
|
try {
|
|
23228
|
-
const raw = (0,
|
|
23459
|
+
const raw = (0, import_node_fs29.readFileSync)(opts.batch, "utf8");
|
|
23229
23460
|
specs = JSON.parse(raw);
|
|
23230
23461
|
if (!Array.isArray(specs)) throw new Error("batch file must contain a JSON array");
|
|
23231
23462
|
} catch (e) {
|
|
@@ -23283,8 +23514,8 @@ ${lines}`);
|
|
|
23283
23514
|
}
|
|
23284
23515
|
|
|
23285
23516
|
// src/train-commands.ts
|
|
23286
|
-
var
|
|
23287
|
-
var
|
|
23517
|
+
var import_node_fs30 = require("node:fs");
|
|
23518
|
+
var import_node_path28 = require("node:path");
|
|
23288
23519
|
|
|
23289
23520
|
// src/train-status.ts
|
|
23290
23521
|
function buildTrainStatusReport(input) {
|
|
@@ -23324,7 +23555,7 @@ function formatTrainStatus(r) {
|
|
|
23324
23555
|
// src/train-commands.ts
|
|
23325
23556
|
function readRepoVersion() {
|
|
23326
23557
|
try {
|
|
23327
|
-
return JSON.parse((0,
|
|
23558
|
+
return JSON.parse((0, import_node_fs30.readFileSync)((0, import_node_path28.join)(process.cwd(), ".claude-plugin", "plugin.json"), "utf8")).version || void 0;
|
|
23328
23559
|
} catch {
|
|
23329
23560
|
return void 0;
|
|
23330
23561
|
}
|
|
@@ -23470,9 +23701,9 @@ function registerDeployCommands(program3) {
|
|
|
23470
23701
|
}
|
|
23471
23702
|
|
|
23472
23703
|
// src/discovery-commands.ts
|
|
23473
|
-
var
|
|
23704
|
+
var import_node_fs31 = require("node:fs");
|
|
23474
23705
|
var import_node_os8 = require("node:os");
|
|
23475
|
-
var
|
|
23706
|
+
var import_node_path29 = require("node:path");
|
|
23476
23707
|
var GC_GH_TIMEOUT_MS3 = 2e4;
|
|
23477
23708
|
async function collectStatus() {
|
|
23478
23709
|
let branch = "";
|
|
@@ -23649,8 +23880,8 @@ async function collectOnboardStatus() {
|
|
|
23649
23880
|
}
|
|
23650
23881
|
const home = (0, import_node_os8.homedir)();
|
|
23651
23882
|
const plugin = onboardPluginGate({
|
|
23652
|
-
readKnown: () => readFileSyncSafe((0,
|
|
23653
|
-
readSettings: () => readFileSyncSafe((0,
|
|
23883
|
+
readKnown: () => readFileSyncSafe((0, import_node_path29.join)(home, ...KNOWN_MARKETPLACES_RELATIVE), import_node_fs31.readFileSync),
|
|
23884
|
+
readSettings: () => readFileSyncSafe((0, import_node_path29.join)(home, ".claude", "settings.json"), import_node_fs31.readFileSync)
|
|
23654
23885
|
});
|
|
23655
23886
|
return { track, board, registry: registry2, secrets, plugin, nextCommand };
|
|
23656
23887
|
}
|
|
@@ -25010,17 +25241,17 @@ function parseOriginRepo(remoteUrl) {
|
|
|
25010
25241
|
}
|
|
25011
25242
|
function ghHostsConfigPath(env, platform2) {
|
|
25012
25243
|
const sep2 = platform2 === "win32" ? "\\" : "/";
|
|
25013
|
-
const
|
|
25244
|
+
const join26 = (...parts) => parts.join(sep2);
|
|
25014
25245
|
const explicit = env.GH_CONFIG_DIR?.trim();
|
|
25015
|
-
if (explicit) return
|
|
25246
|
+
if (explicit) return join26(explicit, "hosts.yml");
|
|
25016
25247
|
if (platform2 === "win32") {
|
|
25017
25248
|
const appData = (env.AppData ?? env.APPDATA)?.trim();
|
|
25018
|
-
return appData ?
|
|
25249
|
+
return appData ? join26(appData, "GitHub CLI", "hosts.yml") : void 0;
|
|
25019
25250
|
}
|
|
25020
25251
|
const xdg = env.XDG_CONFIG_HOME?.trim();
|
|
25021
|
-
if (xdg) return
|
|
25252
|
+
if (xdg) return join26(xdg, "gh", "hosts.yml");
|
|
25022
25253
|
const home = env.HOME?.trim();
|
|
25023
|
-
return home ?
|
|
25254
|
+
return home ? join26(home, ".config", "gh", "hosts.yml") : void 0;
|
|
25024
25255
|
}
|
|
25025
25256
|
function parseGhHostsAccounts(yaml, host = "github.com") {
|
|
25026
25257
|
let hostIndent = null;
|
|
@@ -25070,17 +25301,17 @@ function ghAccountCaveat(announcedLogin, accounts) {
|
|
|
25070
25301
|
}
|
|
25071
25302
|
|
|
25072
25303
|
// src/doctor-io.ts
|
|
25073
|
-
var
|
|
25304
|
+
var import_node_fs32 = require("node:fs");
|
|
25074
25305
|
var import_node_os9 = require("node:os");
|
|
25075
|
-
var
|
|
25076
|
-
var
|
|
25306
|
+
var import_node_path30 = require("node:path");
|
|
25307
|
+
var import_node_child_process13 = require("node:child_process");
|
|
25077
25308
|
var import_node_util8 = require("node:util");
|
|
25078
|
-
var execFileP6 = (0, import_node_util8.promisify)(
|
|
25309
|
+
var execFileP6 = (0, import_node_util8.promisify)(import_node_child_process13.execFile);
|
|
25079
25310
|
var MMI_PLUGIN_ID2 = "mmi@mutmutco";
|
|
25080
25311
|
function installedClaudePluginVersion() {
|
|
25081
25312
|
try {
|
|
25082
25313
|
const file = JSON.parse(
|
|
25083
|
-
(0,
|
|
25314
|
+
(0, import_node_fs32.readFileSync)((0, import_node_path30.join)((0, import_node_os9.homedir)(), ".claude", "plugins", "installed_plugins.json"), "utf8")
|
|
25084
25315
|
);
|
|
25085
25316
|
const versions = (file.plugins?.[MMI_PLUGIN_ID2] ?? []).map((r) => r.version).filter((v) => Boolean(v));
|
|
25086
25317
|
if (versions.length === 0) return void 0;
|
|
@@ -25091,7 +25322,7 @@ function installedClaudePluginVersion() {
|
|
|
25091
25322
|
}
|
|
25092
25323
|
function worktreeRootSync() {
|
|
25093
25324
|
try {
|
|
25094
|
-
const out = (0,
|
|
25325
|
+
const out = (0, import_node_child_process13.execFileSync)("git", ["rev-parse", "--show-toplevel"], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
|
|
25095
25326
|
let root = out.endsWith("\n") ? out.slice(0, -1) : out;
|
|
25096
25327
|
if (process.platform === "win32" && root.endsWith("\r")) root = root.slice(0, -1);
|
|
25097
25328
|
return root || null;
|
|
@@ -25101,13 +25332,13 @@ function worktreeRootSync() {
|
|
|
25101
25332
|
}
|
|
25102
25333
|
var gitignorePath = () => {
|
|
25103
25334
|
const root = worktreeRootSync();
|
|
25104
|
-
return root === null ? null : (0,
|
|
25335
|
+
return root === null ? null : (0, import_node_path30.join)(root, ".gitignore");
|
|
25105
25336
|
};
|
|
25106
25337
|
function readGitignore() {
|
|
25107
25338
|
const path2 = gitignorePath();
|
|
25108
25339
|
if (path2 === null) return null;
|
|
25109
25340
|
try {
|
|
25110
|
-
return (0,
|
|
25341
|
+
return (0, import_node_fs32.readFileSync)(path2, "utf8");
|
|
25111
25342
|
} catch {
|
|
25112
25343
|
return null;
|
|
25113
25344
|
}
|
|
@@ -25116,7 +25347,7 @@ function writeGitignore(content) {
|
|
|
25116
25347
|
const path2 = gitignorePath();
|
|
25117
25348
|
if (path2 === null) return false;
|
|
25118
25349
|
try {
|
|
25119
|
-
(0,
|
|
25350
|
+
(0, import_node_fs32.writeFileSync)(path2, content, "utf8");
|
|
25120
25351
|
return true;
|
|
25121
25352
|
} catch {
|
|
25122
25353
|
return false;
|
|
@@ -25140,7 +25371,7 @@ async function repoRoot() {
|
|
|
25140
25371
|
}
|
|
25141
25372
|
function hasRepoLocalWorktrees() {
|
|
25142
25373
|
const root = worktreeRootSync();
|
|
25143
|
-
return root !== null && (0,
|
|
25374
|
+
return root !== null && (0, import_node_fs32.existsSync)((0, import_node_path30.join)(root, ".worktrees"));
|
|
25144
25375
|
}
|
|
25145
25376
|
|
|
25146
25377
|
// src/index.ts
|
|
@@ -25175,8 +25406,8 @@ ${r.stderr ?? ""}`).catch(() => "");
|
|
|
25175
25406
|
function ghMultiAccountCaveat(announcedLogin) {
|
|
25176
25407
|
try {
|
|
25177
25408
|
const hostsPath = ghHostsConfigPath(process.env, process.platform);
|
|
25178
|
-
if (!hostsPath || !(0,
|
|
25179
|
-
return ghAccountCaveat(announcedLogin, parseGhHostsAccounts((0,
|
|
25409
|
+
if (!hostsPath || !(0, import_node_fs33.existsSync)(hostsPath)) return void 0;
|
|
25410
|
+
return ghAccountCaveat(announcedLogin, parseGhHostsAccounts((0, import_node_fs33.readFileSync)(hostsPath, "utf8")));
|
|
25180
25411
|
} catch {
|
|
25181
25412
|
return void 0;
|
|
25182
25413
|
}
|
|
@@ -25184,7 +25415,7 @@ function ghMultiAccountCaveat(announcedLogin) {
|
|
|
25184
25415
|
var ENV_HEAL_LOCK_STALE_MS = 10 * 6e4;
|
|
25185
25416
|
var ENV_HEAL_LOCK_MAX_WAIT_MS = 2 * 6e4;
|
|
25186
25417
|
function envHealLockPath(home) {
|
|
25187
|
-
return (0,
|
|
25418
|
+
return (0, import_node_path31.join)(home, ".claude", "plugins", ".mmi-env-heal.lock");
|
|
25188
25419
|
}
|
|
25189
25420
|
async function withEnvHealLock(what, run) {
|
|
25190
25421
|
try {
|
|
@@ -25335,8 +25566,8 @@ function mmiDoctorDeps(opts = {}) {
|
|
|
25335
25566
|
const home = (0, import_node_os10.homedir)();
|
|
25336
25567
|
return marketplaceRows(
|
|
25337
25568
|
MMI_MARKETPLACE_NAME,
|
|
25338
|
-
readFileSyncSafe((0,
|
|
25339
|
-
readFileSyncSafe((0,
|
|
25569
|
+
readFileSyncSafe((0, import_node_path31.join)(home, ...KNOWN_MARKETPLACES_RELATIVE), import_node_fs33.readFileSync),
|
|
25570
|
+
readFileSyncSafe((0, import_node_path31.join)(home, ".claude", "settings.json"), import_node_fs33.readFileSync)
|
|
25340
25571
|
);
|
|
25341
25572
|
} catch {
|
|
25342
25573
|
return [];
|
|
@@ -25370,6 +25601,35 @@ function allLongFlags() {
|
|
|
25370
25601
|
cachedLongFlags = [...acc];
|
|
25371
25602
|
return cachedLongFlags;
|
|
25372
25603
|
}
|
|
25604
|
+
var cachedCommandPaths;
|
|
25605
|
+
function allCommandPaths() {
|
|
25606
|
+
if (cachedCommandPaths) return cachedCommandPaths;
|
|
25607
|
+
const acc = [];
|
|
25608
|
+
const walk2 = (node, trail) => {
|
|
25609
|
+
if (!isCanonicalSuggestion(node)) return;
|
|
25610
|
+
const here = [...trail, node.name];
|
|
25611
|
+
if (here.length) acc.push(here.join(" "));
|
|
25612
|
+
for (const child2 of node.subcommands) walk2(child2, here);
|
|
25613
|
+
};
|
|
25614
|
+
for (const child2 of buildCommandManifest(program2).tree.subcommands) walk2(child2, []);
|
|
25615
|
+
cachedCommandPaths = acc;
|
|
25616
|
+
return cachedCommandPaths;
|
|
25617
|
+
}
|
|
25618
|
+
function suggestCommandPath(token, paths) {
|
|
25619
|
+
const all = [...paths];
|
|
25620
|
+
const leafOf = (p) => p.slice(p.lastIndexOf(" ") + 1);
|
|
25621
|
+
const exact = all.filter((p) => leafOf(p) === token);
|
|
25622
|
+
if (exact.length === 1) return exact[0];
|
|
25623
|
+
if (exact.length > 1) return void 0;
|
|
25624
|
+
const leaves = /* @__PURE__ */ new Map();
|
|
25625
|
+
for (const p of all) {
|
|
25626
|
+
const leaf = leafOf(p);
|
|
25627
|
+
leaves.set(leaf, [...leaves.get(leaf) ?? [], p]);
|
|
25628
|
+
}
|
|
25629
|
+
const near = didYouMean(token, leaves.keys());
|
|
25630
|
+
const hits = near ? leaves.get(near) ?? [] : [];
|
|
25631
|
+
return hits.length === 1 ? hits[0] : void 0;
|
|
25632
|
+
}
|
|
25373
25633
|
function argvWantsJson2() {
|
|
25374
25634
|
return process.argv.some((a) => a === "--json" || a.startsWith("--json="));
|
|
25375
25635
|
}
|
|
@@ -25378,6 +25638,7 @@ var PARSE_HINT_SENTINEL = "@@mmi-parse-hint@@";
|
|
|
25378
25638
|
var DISCOVERY_HINT = "run `mmi-cli commands` for the agentic-coding map, `mmi-cli explain <command>` for detail, or `mmi-cli commands --json --primary` for machine discovery";
|
|
25379
25639
|
var STALE_HINT = `(${DISCOVERY_HINT}). If you expected this command to exist, your installed mmi-cli may be stale \u2014 update it (see: mmi-cli doctor)`;
|
|
25380
25640
|
var lastParseErrorKind = "other";
|
|
25641
|
+
var lastUnknownCommand;
|
|
25381
25642
|
function classifyParseError(plain) {
|
|
25382
25643
|
if (/unknown command/i.test(plain)) return "unknown-command";
|
|
25383
25644
|
if (/unknown option|missing required argument|too many arguments|argument missing/i.test(plain)) {
|
|
@@ -25407,6 +25668,10 @@ function commandPath2(cmd) {
|
|
|
25407
25668
|
return parts.join(" ");
|
|
25408
25669
|
}
|
|
25409
25670
|
function resolveParseHint() {
|
|
25671
|
+
if (lastParseErrorKind === "unknown-command") {
|
|
25672
|
+
const path3 = lastUnknownCommand ? suggestCommandPath(lastUnknownCommand, allCommandPaths()) : void 0;
|
|
25673
|
+
return path3 ? `(did you mean \`mmi-cli ${path3}\`? ${DISCOVERY_HINT})` : STALE_HINT;
|
|
25674
|
+
}
|
|
25410
25675
|
if (lastParseErrorKind !== "bad-arguments") return STALE_HINT;
|
|
25411
25676
|
const cmd = resolveCommandFromArgv(program2, process.argv.slice(2));
|
|
25412
25677
|
if (!cmd) return `(${DISCOVERY_HINT})`;
|
|
@@ -25425,6 +25690,7 @@ function envelopeAwareWriteErr(str) {
|
|
|
25425
25690
|
return;
|
|
25426
25691
|
}
|
|
25427
25692
|
lastParseErrorKind = classifyParseError(plain);
|
|
25693
|
+
lastUnknownCommand = /unknown command '?([\w:-]+)'?/.exec(plain)?.[1];
|
|
25428
25694
|
const match = /unknown option '([^']+)'/.exec(plain);
|
|
25429
25695
|
if (match) {
|
|
25430
25696
|
const flag = match[1];
|
|
@@ -25480,19 +25746,19 @@ program2.hook("preAction", async (_thisCommand, actionCommand) => {
|
|
|
25480
25746
|
});
|
|
25481
25747
|
var rules = program2.command("rules").description("org-managed .gitignore delivery");
|
|
25482
25748
|
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) => {
|
|
25483
|
-
const path2 = (0,
|
|
25484
|
-
const current = (0,
|
|
25749
|
+
const path2 = (0, import_node_path31.join)(process.cwd(), ".gitignore");
|
|
25750
|
+
const current = (0, import_node_fs33.existsSync)(path2) ? (0, import_node_fs33.readFileSync)(path2, "utf8") : null;
|
|
25485
25751
|
const plan = planManagedGitignore(current);
|
|
25486
25752
|
const drift = [...plan.added.map((l) => `+${l}`), ...plan.removed.map((l) => `-${l}`)].join(", ") || "block normalize";
|
|
25487
25753
|
if (opts.json) {
|
|
25488
|
-
if (opts.write && plan.changed) (0,
|
|
25754
|
+
if (opts.write && plan.changed) (0, import_node_fs33.writeFileSync)(path2, plan.content, "utf8");
|
|
25489
25755
|
console.log(JSON.stringify(plan, null, 2));
|
|
25490
25756
|
if (!opts.write && plan.changed) process.exitCode = 1;
|
|
25491
25757
|
return;
|
|
25492
25758
|
}
|
|
25493
25759
|
if (opts.write) {
|
|
25494
25760
|
if (plan.changed) {
|
|
25495
|
-
(0,
|
|
25761
|
+
(0, import_node_fs33.writeFileSync)(path2, plan.content, "utf8");
|
|
25496
25762
|
console.log(`mmi-cli org rules gitignore: updated .gitignore (${drift})`);
|
|
25497
25763
|
} else {
|
|
25498
25764
|
console.log("mmi-cli org rules gitignore: up to date");
|
|
@@ -25618,8 +25884,8 @@ gcCmd.option("--dry-run", "show what would be deleted (default)").option("--appl
|
|
|
25618
25884
|
if (!Number.isFinite(limit) || limit < 1) return fail("worktree gc: --limit must be a positive integer");
|
|
25619
25885
|
let root;
|
|
25620
25886
|
if (o.root !== void 0) {
|
|
25621
|
-
root = (0,
|
|
25622
|
-
if (!(0,
|
|
25887
|
+
root = (0, import_node_path31.resolve)(o.root);
|
|
25888
|
+
if (!(0, import_node_fs33.existsSync)(root) || !(0, import_node_fs33.statSync)(root).isDirectory()) return fail(`worktree gc: --root ${o.root} is not a directory`);
|
|
25623
25889
|
const gcRepoRoot = (await execFileP2("git", ["rev-parse", "--show-toplevel"], { timeout: GIT_TIMEOUT_MS }).catch(() => ({ stdout: "" }))).stdout.trim() || process.cwd();
|
|
25624
25890
|
if (isPathUnderDirectory(gcRepoRoot, root)) {
|
|
25625
25891
|
return fail(`worktree gc: --root ${root} contains this checkout \u2014 name a worktrees root, not the repo or an ancestor of it`);
|
|
@@ -25647,7 +25913,7 @@ gcCmd.option("--dry-run", "show what would be deleted (default)").option("--appl
|
|
|
25647
25913
|
console.log(formatGcPlan(plan, false));
|
|
25648
25914
|
} else {
|
|
25649
25915
|
if (applyResult) console.log(`
|
|
25650
|
-
${renderGcApplyResult(applyResult)}`);
|
|
25916
|
+
${renderGcApplyResult(applyResult, plan.skipped)}`);
|
|
25651
25917
|
}
|
|
25652
25918
|
if (applyResult?.failed.length) process.exitCode = 1;
|
|
25653
25919
|
} catch (e) {
|
|
@@ -25661,7 +25927,7 @@ function runWorktreeInstall(command, cwd, quiet) {
|
|
|
25661
25927
|
const file = isWin2 ? "cmd.exe" : bin;
|
|
25662
25928
|
const spawnArgs = isWin2 ? ["/c", bin, ...args] : args;
|
|
25663
25929
|
return new Promise((resolve5, reject) => {
|
|
25664
|
-
const child2 = (0,
|
|
25930
|
+
const child2 = (0, import_node_child_process14.spawn)(file, spawnArgs, { cwd, stdio: quiet ? "ignore" : "inherit", windowsHide: true });
|
|
25665
25931
|
const timer = setTimeout(() => {
|
|
25666
25932
|
try {
|
|
25667
25933
|
child2.kill();
|
|
@@ -25684,7 +25950,7 @@ async function primaryCheckoutRoot(from) {
|
|
|
25684
25950
|
return primaryCheckoutRootOf(async (args) => (await execFileP2("git", ["-C", from, ...args], { timeout: GIT_TIMEOUT_MS })).stdout);
|
|
25685
25951
|
}
|
|
25686
25952
|
async function unprovenWorktreeReason(wtPath, repoRoot2) {
|
|
25687
|
-
if (!(0,
|
|
25953
|
+
if (!(0, import_node_fs33.existsSync)(wtPath)) return `${wtPath} does not exist on disk`;
|
|
25688
25954
|
const porcelain = (await execFileP2("git", ["-C", repoRoot2, "worktree", "list", "--porcelain"], { timeout: GIT_TIMEOUT_MS }).catch(() => ({ stdout: "" }))).stdout;
|
|
25689
25955
|
const registered = parseWorktreePorcelainEntries(porcelain);
|
|
25690
25956
|
if (!registered.length) {
|
|
@@ -25705,26 +25971,26 @@ function makeProvisionDeps(worktreeRoot, quiet, log) {
|
|
|
25705
25971
|
function acquireWorktreeSetupLock(worktreeRoot) {
|
|
25706
25972
|
const lockPath = repoRuntimeStatePath(worktreeRoot, "worktree-setup.lock");
|
|
25707
25973
|
const take = () => {
|
|
25708
|
-
const fd = (0,
|
|
25974
|
+
const fd = (0, import_node_fs33.openSync)(lockPath, "wx");
|
|
25709
25975
|
try {
|
|
25710
|
-
(0,
|
|
25976
|
+
(0, import_node_fs33.writeSync)(fd, String(Date.now()));
|
|
25711
25977
|
} finally {
|
|
25712
|
-
(0,
|
|
25978
|
+
(0, import_node_fs33.closeSync)(fd);
|
|
25713
25979
|
}
|
|
25714
25980
|
return () => {
|
|
25715
25981
|
try {
|
|
25716
|
-
(0,
|
|
25982
|
+
(0, import_node_fs33.rmSync)(lockPath, { force: true });
|
|
25717
25983
|
} catch {
|
|
25718
25984
|
}
|
|
25719
25985
|
};
|
|
25720
25986
|
};
|
|
25721
25987
|
try {
|
|
25722
|
-
(0,
|
|
25988
|
+
(0, import_node_fs33.mkdirSync)((0, import_node_path31.dirname)(lockPath), { recursive: true });
|
|
25723
25989
|
return take();
|
|
25724
25990
|
} catch {
|
|
25725
25991
|
try {
|
|
25726
|
-
if (Date.now() - (0,
|
|
25727
|
-
(0,
|
|
25992
|
+
if (Date.now() - (0, import_node_fs33.statSync)(lockPath).mtimeMs > WORKTREE_SETUP_LOCK_TTL_MS) {
|
|
25993
|
+
(0, import_node_fs33.rmSync)(lockPath, { force: true });
|
|
25728
25994
|
return take();
|
|
25729
25995
|
}
|
|
25730
25996
|
} catch {
|
|
@@ -25932,7 +26198,7 @@ function scheduleRelatedDiscovery(o) {
|
|
|
25932
26198
|
try {
|
|
25933
26199
|
const args = ["issue", "discover-related", "--number", String(o.number), "--title", o.title, "--body", o.body, "--fail-soft"];
|
|
25934
26200
|
if (o.repo) args.push("--repo", o.repo);
|
|
25935
|
-
spawnDetachedSelf(args, { spawn:
|
|
26201
|
+
spawnDetachedSelf(args, { spawn: import_node_child_process14.spawn, execPath: process.execPath, scriptPath: process.argv[1] }, { cwd: process.cwd() });
|
|
25936
26202
|
} catch {
|
|
25937
26203
|
}
|
|
25938
26204
|
}
|
|
@@ -25979,6 +26245,36 @@ docs.command("refs").description("deterministic doc reference gate: every backti
|
|
|
25979
26245
|
await failGraceful(e.message);
|
|
25980
26246
|
}
|
|
25981
26247
|
});
|
|
26248
|
+
var tests = program2.command("tests").description("a repo's test-policy.json \u2014 the opt-in test contract and its enforcement");
|
|
26249
|
+
tests.command("policy").description("enforce this repo's test-policy.json against the diff: a mandatory-zone change must carry a test, an unrequested new test file is refused, a `protected` test file may not be deleted or renamed away, and a `protected` entry naming a missing file is refused. Override any of them with a `Test-Policy-Override: <reason>` commit trailer (#3605)").option("--json", "machine-readable result: { ok, base, changedCount, findings[] }").option("--base <ref>", "comparison base (default: TEST_POLICY_BASE, then origin/development, then origin/main)").action(async (o) => {
|
|
26250
|
+
try {
|
|
26251
|
+
const root = await repoRoot();
|
|
26252
|
+
const result = runTestPolicy(root, { base: o.base });
|
|
26253
|
+
if (o.json) {
|
|
26254
|
+
consoleIo.log(JSON.stringify(result, null, 2));
|
|
26255
|
+
if (!result.ok) process.exitCode = 1;
|
|
26256
|
+
return;
|
|
26257
|
+
}
|
|
26258
|
+
if (result.base === null && result.ok) {
|
|
26259
|
+
console.log("tests policy: no comparison base (origin/development or origin/main) \u2014 skipping.");
|
|
26260
|
+
return;
|
|
26261
|
+
}
|
|
26262
|
+
if (result.overriddenBy) {
|
|
26263
|
+
console.log(`tests policy: overridden by commit trailer \u2014 ${result.overriddenBy}`);
|
|
26264
|
+
return;
|
|
26265
|
+
}
|
|
26266
|
+
if (result.ok) {
|
|
26267
|
+
console.log(
|
|
26268
|
+
`tests policy: OK (${result.changedCount} changed file(s); ${result.mandatoryCount} mandatory glob(s), ${result.protectedCount} protected file(s)).`
|
|
26269
|
+
);
|
|
26270
|
+
return;
|
|
26271
|
+
}
|
|
26272
|
+
for (const f of result.findings) console.error(`tests policy: ${f.detail}`);
|
|
26273
|
+
process.exitCode = 1;
|
|
26274
|
+
} catch (e) {
|
|
26275
|
+
await failGraceful(e.message);
|
|
26276
|
+
}
|
|
26277
|
+
});
|
|
25982
26278
|
var docsAudit = program2.command("docs-audit").description("the docs janitor verdict ledger \u2014 record a dated run verdict, or read the dead-man status back");
|
|
25983
26279
|
docsAudit.command("record").description("write a dated janitor verdict for one repo to the registry ledger (master-only server-side)").option("--repo <owner/name>", "the repo the verdict is for (default: the current repo)").option("--date <YYYY-MM-DD>", "the ISO day the run examined (default: today)").requiredOption("--sha-range <a..b>", "the git range the janitor read (e.g. <lastVerdictSha>..HEAD)").requiredOption("--outcome <kind>", "clean | refreshed | failed").option("--count <n>", "docs refreshed (required when --outcome refreshed)").option("--reason <text>", "why the run failed (required when --outcome failed)").requiredOption("--checker-vendor <vendor>", "which vendor's model actually ran the check").action(async (o) => {
|
|
25984
26280
|
try {
|
|
@@ -26194,7 +26490,7 @@ project.command("set [owner/repo]").description("upsert project META (idempotent
|
|
|
26194
26490
|
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`);
|
|
26195
26491
|
if (o.secretsFile) {
|
|
26196
26492
|
try {
|
|
26197
|
-
vars.push(`secrets=${(0,
|
|
26493
|
+
vars.push(`secrets=${(0, import_node_fs33.readFileSync)(o.secretsFile, "utf8")}`);
|
|
26198
26494
|
} catch (e) {
|
|
26199
26495
|
return fail(`org project set: cannot read --secrets-file ${o.secretsFile}: ${e.message}`);
|
|
26200
26496
|
}
|
|
@@ -26850,11 +27146,11 @@ pr.command("view <number>").description("read a PR as structured JSON (merged st
|
|
|
26850
27146
|
}
|
|
26851
27147
|
});
|
|
26852
27148
|
async function listCiWorkflowPaths(cwd = process.cwd()) {
|
|
26853
|
-
const wfDir = (0,
|
|
26854
|
-
if (!(0,
|
|
26855
|
-
return (0,
|
|
27149
|
+
const wfDir = (0, import_node_path31.join)(cwd, ".github", "workflows");
|
|
27150
|
+
if (!(0, import_node_fs33.existsSync)(wfDir)) return [];
|
|
27151
|
+
return (0, import_node_fs33.readdirSync)(wfDir).filter((name) => /\.(ya?ml)$/i.test(name)).filter((name) => {
|
|
26856
27152
|
try {
|
|
26857
|
-
return workflowReportsPrChecks((0,
|
|
27153
|
+
return workflowReportsPrChecks((0, import_node_fs33.readFileSync)((0, import_node_path31.join)(wfDir, name), "utf8"));
|
|
26858
27154
|
} catch {
|
|
26859
27155
|
return true;
|
|
26860
27156
|
}
|
|
@@ -26881,16 +27177,16 @@ function ciAuditDeps() {
|
|
|
26881
27177
|
// gate re-seed step is skipped gracefully rather than failing mid-run.
|
|
26882
27178
|
readSeedFile: (path2) => {
|
|
26883
27179
|
if (!root) return null;
|
|
26884
|
-
const fullPath = (0,
|
|
26885
|
-
return (0,
|
|
27180
|
+
const fullPath = (0, import_node_path31.join)(root, path2);
|
|
27181
|
+
return (0, import_node_fs33.existsSync)(fullPath) ? (0, import_node_fs33.readFileSync)(fullPath, "utf8") : null;
|
|
26886
27182
|
}
|
|
26887
27183
|
};
|
|
26888
27184
|
}
|
|
26889
27185
|
function hubRoot() {
|
|
26890
|
-
const fromPkg = (0,
|
|
27186
|
+
const fromPkg = (0, import_node_path31.join)(__dirname, "..", "..");
|
|
26891
27187
|
const marker = "skills/bootstrap/seeds/manifest.json";
|
|
26892
|
-
if ((0,
|
|
26893
|
-
if ((0,
|
|
27188
|
+
if ((0, import_node_fs33.existsSync)((0, import_node_path31.join)(fromPkg, marker))) return fromPkg;
|
|
27189
|
+
if ((0, import_node_fs33.existsSync)((0, import_node_path31.join)(process.cwd(), marker))) return process.cwd();
|
|
26894
27190
|
return null;
|
|
26895
27191
|
}
|
|
26896
27192
|
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) => {
|
|
@@ -27037,6 +27333,7 @@ pr.command("land <number>").description("agent merge path (#1440): train probe \
|
|
|
27037
27333
|
if (o.json) printLine(JSON.stringify(result));
|
|
27038
27334
|
else {
|
|
27039
27335
|
printLine(`pr land: ${result.status}${result.error ? ` \u2014 ${result.error}` : ""}`);
|
|
27336
|
+
for (const line of renderPrLandCleanupLines(result.cleanup)) printLine(line);
|
|
27040
27337
|
if (result.cleanupError) printLine(`pr land cleanup: ${result.cleanupError}`);
|
|
27041
27338
|
}
|
|
27042
27339
|
if (result.status === "failed" || result.cleanupError) process.exitCode = 1;
|
|
@@ -27167,7 +27464,7 @@ jsonParity(pr.command("merge <number>").description("merge a PR (squash by defau
|
|
|
27167
27464
|
localCleanup = await cleanupPrMergeLocalBranch(headRef, {
|
|
27168
27465
|
beforeWorktrees,
|
|
27169
27466
|
startingPath,
|
|
27170
|
-
pathExists: (p) => (0,
|
|
27467
|
+
pathExists: (p) => (0, import_node_fs33.existsSync)(p),
|
|
27171
27468
|
execGit: async (args) => (await execFileP2("git", args, { timeout: GIT_TIMEOUT_MS })).stdout,
|
|
27172
27469
|
teardownWorktreeStage,
|
|
27173
27470
|
deferredStore,
|
|
@@ -27508,10 +27805,10 @@ access.command("audit").description("audit collaborator roles + train-branch pus
|
|
|
27508
27805
|
targets = resolution.targets;
|
|
27509
27806
|
}
|
|
27510
27807
|
const derivedMatrix = registryProjects ? accessMatrixFromProjects(registryProjects) : {};
|
|
27511
|
-
const fileMatrix = (0,
|
|
27808
|
+
const fileMatrix = (0, import_node_fs33.existsSync)("access-matrix.json") ? loadAccessMatrix((0, import_node_fs33.readFileSync)("access-matrix.json", "utf8")) : {};
|
|
27512
27809
|
const matrix = mergeAccessMatrix(fileMatrix, derivedMatrix);
|
|
27513
27810
|
const derivedContracts = registryProjects ? dataAccessContractsFromProjects(registryProjects) : { consumers: {} };
|
|
27514
|
-
const fileContracts = (0,
|
|
27811
|
+
const fileContracts = (0, import_node_fs33.existsSync)("data-access-contracts.json") ? loadDataAccessContracts((0, import_node_fs33.readFileSync)("data-access-contracts.json", "utf8")) : { consumers: {} };
|
|
27515
27812
|
const dataAccess = mergeDataAccessContracts(fileContracts, derivedContracts);
|
|
27516
27813
|
const report = await auditOrgAccess(targets, deps, matrix, dataAccess);
|
|
27517
27814
|
console.log(o.json ? JSON.stringify(report, null, 2) : renderAccessReport(report));
|
|
@@ -27545,16 +27842,16 @@ function directoryBytes(path2) {
|
|
|
27545
27842
|
let total = 0;
|
|
27546
27843
|
let entries;
|
|
27547
27844
|
try {
|
|
27548
|
-
entries = (0,
|
|
27845
|
+
entries = (0, import_node_fs33.readdirSync)(path2, { withFileTypes: true });
|
|
27549
27846
|
} catch {
|
|
27550
27847
|
return 0;
|
|
27551
27848
|
}
|
|
27552
27849
|
for (const entry of entries) {
|
|
27553
|
-
const child2 = (0,
|
|
27850
|
+
const child2 = (0, import_node_path31.join)(path2, entry.name);
|
|
27554
27851
|
if (entry.isDirectory()) total += directoryBytes(child2);
|
|
27555
27852
|
else {
|
|
27556
27853
|
try {
|
|
27557
|
-
total += (0,
|
|
27854
|
+
total += (0, import_node_fs33.statSync)(child2).size;
|
|
27558
27855
|
} catch {
|
|
27559
27856
|
}
|
|
27560
27857
|
}
|
|
@@ -27562,25 +27859,25 @@ function directoryBytes(path2) {
|
|
|
27562
27859
|
return total;
|
|
27563
27860
|
}
|
|
27564
27861
|
function listDirEntries(dir) {
|
|
27565
|
-
return (0,
|
|
27862
|
+
return (0, import_node_fs33.readdirSync)(dir, { withFileTypes: true }).map((d) => ({ name: d.name, isDirectory: d.isDirectory() }));
|
|
27566
27863
|
}
|
|
27567
27864
|
function readInstalledPluginRefs(home) {
|
|
27568
27865
|
const p = installedPluginsPath(home);
|
|
27569
|
-
if (!(0,
|
|
27866
|
+
if (!(0, import_node_fs33.existsSync)(p)) return [];
|
|
27570
27867
|
try {
|
|
27571
|
-
return installedPluginPaths((0,
|
|
27868
|
+
return installedPluginPaths((0, import_node_fs33.readFileSync)(p, "utf8"));
|
|
27572
27869
|
} catch {
|
|
27573
27870
|
return null;
|
|
27574
27871
|
}
|
|
27575
27872
|
}
|
|
27576
27873
|
function pluginCacheFsDeps(home, dirBytes) {
|
|
27577
27874
|
return {
|
|
27578
|
-
exists: (p) => (0,
|
|
27579
|
-
listVersionDirs: (root) => (0,
|
|
27875
|
+
exists: (p) => (0, import_node_fs33.existsSync)(p),
|
|
27876
|
+
listVersionDirs: (root) => (0, import_node_fs33.readdirSync)(root, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name),
|
|
27580
27877
|
dirBytes,
|
|
27581
|
-
listStagingDirs: (root) => (0,
|
|
27878
|
+
listStagingDirs: (root) => (0, import_node_fs33.readdirSync)(root, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => {
|
|
27582
27879
|
try {
|
|
27583
|
-
return { name: d.name, mtimeMs: newestMtimeMs((0,
|
|
27880
|
+
return { name: d.name, mtimeMs: newestMtimeMs((0, import_node_path31.join)(root, d.name), listDirEntries, (p) => (0, import_node_fs33.statSync)(p).mtimeMs) };
|
|
27584
27881
|
} catch {
|
|
27585
27882
|
return { name: d.name, mtimeMs: Date.now() };
|
|
27586
27883
|
}
|
|
@@ -27594,10 +27891,10 @@ function stagingApplyFsGuard(home) {
|
|
|
27594
27891
|
return {
|
|
27595
27892
|
referencedPaths: () => readInstalledPluginRefs(home),
|
|
27596
27893
|
mtimeMs: (name) => {
|
|
27597
|
-
const p = (0,
|
|
27598
|
-
if (!(0,
|
|
27894
|
+
const p = (0, import_node_path31.join)(stagingRoot, name);
|
|
27895
|
+
if (!(0, import_node_fs33.existsSync)(p)) return null;
|
|
27599
27896
|
try {
|
|
27600
|
-
return newestMtimeMs(p, listDirEntries, (q) => (0,
|
|
27897
|
+
return newestMtimeMs(p, listDirEntries, (q) => (0, import_node_fs33.statSync)(q).mtimeMs);
|
|
27601
27898
|
} catch {
|
|
27602
27899
|
return null;
|
|
27603
27900
|
}
|
|
@@ -27613,7 +27910,7 @@ program2.command("plugin-prune").description(`prune stale cached MMI plugin vers
|
|
|
27613
27910
|
{ withBytes: true }
|
|
27614
27911
|
);
|
|
27615
27912
|
const anythingToDelete = plan.prune.length > 0 || plan.staging.length > 0;
|
|
27616
|
-
const result = o.apply && anythingToDelete ? applyPluginCachePlan(plan, (p) => (0,
|
|
27913
|
+
const result = o.apply && anythingToDelete ? applyPluginCachePlan(plan, (p) => (0, import_node_fs33.rmSync)(p, { recursive: true, force: true }), stagingApplyFsGuard((0, import_node_os10.homedir)())) : void 0;
|
|
27617
27914
|
const warnings = plan.prune.length > 0 ? [CONCURRENT_SESSION_WARNING] : [];
|
|
27618
27915
|
if (o.json) console.log(JSON.stringify({ ...plan, warnings, applied: result ?? null }));
|
|
27619
27916
|
else console.log(renderPluginCachePlan(plan, result));
|
|
@@ -27676,7 +27973,7 @@ program2.command("session-start").description("run the SessionStart verbs (whoam
|
|
|
27676
27973
|
for (const line of scratchGcLines(process.cwd())) bannerIo.log(line);
|
|
27677
27974
|
const worktreeBanner = worktreeAutoProvisionBanner(process.cwd());
|
|
27678
27975
|
if (worktreeBanner) {
|
|
27679
|
-
spawnDetachedSelf(["worktree", "setup", "--quiet"], { spawn:
|
|
27976
|
+
spawnDetachedSelf(["worktree", "setup", "--quiet"], { spawn: import_node_child_process14.spawn, execPath: process.execPath, scriptPath: process.argv[1] });
|
|
27680
27977
|
bannerIo.log(worktreeBanner);
|
|
27681
27978
|
}
|
|
27682
27979
|
if (isLinkedWorktree(process.cwd())) {
|
|
@@ -27701,5 +27998,6 @@ program2.parseAsync(process.argv).then(() => finishCliRun()).catch((e) => failGr
|
|
|
27701
27998
|
isOrgRegisteredRepo,
|
|
27702
27999
|
registryClientDeps,
|
|
27703
28000
|
repoSlug,
|
|
27704
|
-
shouldMarkWorktreeActivity
|
|
28001
|
+
shouldMarkWorktreeActivity,
|
|
28002
|
+
suggestCommandPath
|
|
27705
28003
|
});
|