@mutmutco/cli 3.63.0 → 3.64.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 +447 -215
- 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));
|
|
@@ -5525,38 +5527,38 @@ function safeWorktreeRemoveCommand(safeCwd, targetPath) {
|
|
|
5525
5527
|
function errorMessage(error) {
|
|
5526
5528
|
return error instanceof Error ? error.message : String(error);
|
|
5527
5529
|
}
|
|
5528
|
-
async function fastForwardCurrentBranch(
|
|
5530
|
+
async function fastForwardCurrentBranch(git2) {
|
|
5529
5531
|
try {
|
|
5530
|
-
const before = (await
|
|
5531
|
-
await
|
|
5532
|
-
const after = (await
|
|
5532
|
+
const before = (await git2(["rev-parse", "HEAD"])).trim();
|
|
5533
|
+
await git2(["pull", "--ff-only"]);
|
|
5534
|
+
const after = (await git2(["rev-parse", "HEAD"])).trim();
|
|
5533
5535
|
return { status: before === after ? "up-to-date" : "fast-forwarded" };
|
|
5534
5536
|
} catch (e) {
|
|
5535
5537
|
return { status: "failed", error: errorMessage(e) };
|
|
5536
5538
|
}
|
|
5537
5539
|
}
|
|
5538
|
-
async function returnCheckoutToBase(
|
|
5540
|
+
async function returnCheckoutToBase(git2, base, mergedBranch, currentBranch2) {
|
|
5539
5541
|
if (currentBranch2 === mergedBranch) {
|
|
5540
|
-
const porcelain = await
|
|
5542
|
+
const porcelain = await git2(["status", "--porcelain"]).catch(() => void 0);
|
|
5541
5543
|
const dirt = porcelain === void 0 ? "tracked-changes" : classifyWorktreeDirt(porcelain);
|
|
5542
5544
|
if (!isRemovableDirt(dirt)) {
|
|
5543
5545
|
return { report: { branch: base, switched: false, sync: "skipped", reason: dirt === "untracked-files" ? "untracked-files" : "dirty-worktree" }, canDeleteBranch: false };
|
|
5544
5546
|
}
|
|
5545
5547
|
try {
|
|
5546
|
-
await
|
|
5548
|
+
await git2(["checkout", base]);
|
|
5547
5549
|
} catch (e) {
|
|
5548
5550
|
return {
|
|
5549
5551
|
report: { branch: base, switched: false, sync: "skipped", reason: "checkout-failed", error: errorMessage(e) },
|
|
5550
5552
|
canDeleteBranch: false
|
|
5551
5553
|
};
|
|
5552
5554
|
}
|
|
5553
|
-
const ff2 = await fastForwardCurrentBranch(
|
|
5555
|
+
const ff2 = await fastForwardCurrentBranch(git2);
|
|
5554
5556
|
return { report: { branch: base, switched: true, sync: ff2.status, ...ff2.error ? { error: ff2.error } : {} }, canDeleteBranch: true };
|
|
5555
5557
|
}
|
|
5556
5558
|
if (currentBranch2 !== base) {
|
|
5557
5559
|
return { report: { branch: base, switched: false, sync: "skipped", reason: "not-on-base" }, canDeleteBranch: true };
|
|
5558
5560
|
}
|
|
5559
|
-
const ff = await fastForwardCurrentBranch(
|
|
5561
|
+
const ff = await fastForwardCurrentBranch(git2);
|
|
5560
5562
|
return { report: { branch: base, switched: false, sync: ff.status, ...ff.error ? { error: ff.error } : {} }, canDeleteBranch: true };
|
|
5561
5563
|
}
|
|
5562
5564
|
async function cleanupPrMergeLocalBranch(branch, options) {
|
|
@@ -5597,17 +5599,17 @@ async function cleanupPrMergeLocalBranch(branch, options) {
|
|
|
5597
5599
|
const safeCwd = selectSafeWorktreeCwd([...afterWorktrees, ...beforeWorktrees], wtPath, {
|
|
5598
5600
|
pathExists: options.pathExists
|
|
5599
5601
|
});
|
|
5600
|
-
const
|
|
5602
|
+
const git2 = (args) => safeCwd ? options.execGit(["-C", safeCwd, ...args]) : options.execGit(args);
|
|
5601
5603
|
const expectedHeadOid = options.expectedHeadOid;
|
|
5602
5604
|
const verifyReviewedBranchHead = async () => {
|
|
5603
5605
|
if (!expectedHeadOid) {
|
|
5604
|
-
const remaining = await
|
|
5606
|
+
const remaining = await git2(["branch", "--list", branch]).catch(() => "");
|
|
5605
5607
|
report.localBranch = branchMissingFromList(branch, remaining) ? { name: branch, status: "already-gone" } : { name: branch, status: "not-attempted", reason: "branch-head-unverified" };
|
|
5606
5608
|
return false;
|
|
5607
5609
|
}
|
|
5608
|
-
const currentHead = (await
|
|
5610
|
+
const currentHead = (await git2(["rev-parse", `refs/heads/${branch}`]).catch(() => "") || "").trim();
|
|
5609
5611
|
if (!currentHead) {
|
|
5610
|
-
const remaining = await
|
|
5612
|
+
const remaining = await git2(["branch", "--list", branch]).catch(() => "");
|
|
5611
5613
|
report.localBranch = branchMissingFromList(branch, remaining) ? { name: branch, status: "already-gone" } : { name: branch, status: "not-attempted", reason: "branch-head-unverified" };
|
|
5612
5614
|
return false;
|
|
5613
5615
|
}
|
|
@@ -5642,7 +5644,7 @@ async function cleanupPrMergeLocalBranch(branch, options) {
|
|
|
5642
5644
|
return report;
|
|
5643
5645
|
}
|
|
5644
5646
|
const removeDeps = {
|
|
5645
|
-
git,
|
|
5647
|
+
git: git2,
|
|
5646
5648
|
sleep: options.sleep ?? defaultSleep,
|
|
5647
5649
|
detachReparsePoints: options.detachReparsePoints,
|
|
5648
5650
|
// #3064: junction-safe deferred sweep too
|
|
@@ -5662,7 +5664,7 @@ async function cleanupPrMergeLocalBranch(branch, options) {
|
|
|
5662
5664
|
chdir: options.chdir
|
|
5663
5665
|
});
|
|
5664
5666
|
const outcome = await removeWorktreeWithRecovery(wtPath, {
|
|
5665
|
-
git,
|
|
5667
|
+
git: git2,
|
|
5666
5668
|
sleep: options.sleep ?? defaultSleep,
|
|
5667
5669
|
detachReparsePoints: options.detachReparsePoints,
|
|
5668
5670
|
// #3064: reach the actual pr-merge teardown path
|
|
@@ -5713,10 +5715,10 @@ async function cleanupPrMergeLocalBranch(branch, options) {
|
|
|
5713
5715
|
return report;
|
|
5714
5716
|
}
|
|
5715
5717
|
}
|
|
5716
|
-
const current = (await
|
|
5718
|
+
const current = (await git2(["rev-parse", "--abbrev-ref", "HEAD"]).catch(() => "") || "").trim();
|
|
5717
5719
|
if (options.returnToBranch && options.returnToBranch !== branch) {
|
|
5718
5720
|
const { report: checkout, canDeleteBranch } = await returnCheckoutToBase(
|
|
5719
|
-
|
|
5721
|
+
git2,
|
|
5720
5722
|
options.returnToBranch,
|
|
5721
5723
|
branch,
|
|
5722
5724
|
current
|
|
@@ -5735,16 +5737,16 @@ async function cleanupPrMergeLocalBranch(branch, options) {
|
|
|
5735
5737
|
}
|
|
5736
5738
|
const reviewedHeadOid = expectedHeadOid;
|
|
5737
5739
|
try {
|
|
5738
|
-
await
|
|
5740
|
+
await git2(["update-ref", "-d", `refs/heads/${branch}`, reviewedHeadOid]);
|
|
5739
5741
|
report.localBranch = { name: branch, status: "deleted" };
|
|
5740
5742
|
} catch (e) {
|
|
5741
|
-
const remaining = await
|
|
5743
|
+
const remaining = await git2(["branch", "--list", branch]).catch(() => "");
|
|
5742
5744
|
report.localBranch = branchMissingFromList(branch, remaining) ? { name: branch, status: "already-gone" } : { name: branch, status: "failed", error: errorMessage(e) };
|
|
5743
5745
|
}
|
|
5744
5746
|
if (report.localBranch.status === "deleted" || report.localBranch.status === "already-gone") {
|
|
5745
|
-
await
|
|
5747
|
+
await git2(["config", "--local", "--unset-all", PRESERVED_WORKTREE_CONFIG, `^${escapeGitConfigValueRegex(branch)}$`]).catch(() => void 0);
|
|
5746
5748
|
}
|
|
5747
|
-
if (wtPath) await
|
|
5749
|
+
if (wtPath) await git2(["worktree", "prune"]).catch(() => "");
|
|
5748
5750
|
return report;
|
|
5749
5751
|
}
|
|
5750
5752
|
function formatGcPlan(plan, apply) {
|
|
@@ -6242,9 +6244,9 @@ function defaultWorktreePath(repoRoot2, branch) {
|
|
|
6242
6244
|
const safe = capWorktreeDirName(branch.replace(/[/\\]+/g, "-"));
|
|
6243
6245
|
return (0, import_node_path10.join)((0, import_node_path10.dirname)(repoRoot2), "mmi-worktrees", (0, import_node_path10.basename)(repoRoot2), safe);
|
|
6244
6246
|
}
|
|
6245
|
-
async function primaryCheckoutRootOf(
|
|
6247
|
+
async function primaryCheckoutRootOf(git2) {
|
|
6246
6248
|
try {
|
|
6247
|
-
const out = (await
|
|
6249
|
+
const out = (await git2(["rev-parse", "--path-format=absolute", "--git-common-dir"])).trim();
|
|
6248
6250
|
return out ? (0, import_node_path10.dirname)(out) : void 0;
|
|
6249
6251
|
} catch {
|
|
6250
6252
|
return void 0;
|
|
@@ -6321,7 +6323,7 @@ async function resolveWhoami(deps) {
|
|
|
6321
6323
|
}
|
|
6322
6324
|
function whoamiLine(report, caveat) {
|
|
6323
6325
|
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.`;
|
|
6326
|
+
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
6327
|
return caveat?.trim() ? `${line} ${caveat.trim()}` : line;
|
|
6326
6328
|
}
|
|
6327
6329
|
function authorityLine(report) {
|
|
@@ -6363,7 +6365,7 @@ function commandLadderHint() {
|
|
|
6363
6365
|
}
|
|
6364
6366
|
|
|
6365
6367
|
// src/index.ts
|
|
6366
|
-
var
|
|
6368
|
+
var import_node_path31 = require("node:path");
|
|
6367
6369
|
|
|
6368
6370
|
// src/merge-ci-policy.ts
|
|
6369
6371
|
function resolveMergeCiPolicy(input) {
|
|
@@ -11212,8 +11214,8 @@ function shouldSkipInactiveBoardNode(node, cfg) {
|
|
|
11212
11214
|
}
|
|
11213
11215
|
async function collectBoardItems(cfg, options, deps) {
|
|
11214
11216
|
const client = deps.client ?? defaultGitHubClient();
|
|
11215
|
-
const
|
|
11216
|
-
const currentRepo = options.repo ?? repoFromRemoteUrl(await
|
|
11217
|
+
const git2 = deps.git ?? defaultGit;
|
|
11218
|
+
const currentRepo = options.repo ?? repoFromRemoteUrl(await git2(["remote", "get-url", "origin"])) ?? "";
|
|
11217
11219
|
if (!currentRepo) throw new Error("could not resolve current GitHub repo; pass --repo owner/repo");
|
|
11218
11220
|
const warnings = [];
|
|
11219
11221
|
const nodes = [];
|
|
@@ -11324,8 +11326,8 @@ function findBoardItem(items, selector, board) {
|
|
|
11324
11326
|
return found;
|
|
11325
11327
|
}
|
|
11326
11328
|
async function resolveCurrentRepo(options, deps) {
|
|
11327
|
-
const
|
|
11328
|
-
const currentRepo = options.repo ?? repoFromRemoteUrl(await
|
|
11329
|
+
const git2 = deps.git ?? defaultGit;
|
|
11330
|
+
const currentRepo = options.repo ?? repoFromRemoteUrl(await git2(["remote", "get-url", "origin"])) ?? "";
|
|
11329
11331
|
if (!currentRepo) throw new Error("could not resolve current GitHub repo; pass --repo owner/repo");
|
|
11330
11332
|
return currentRepo;
|
|
11331
11333
|
}
|
|
@@ -11532,8 +11534,8 @@ async function claimBoardIssues(options, deps = {}) {
|
|
|
11532
11534
|
async function moveBoardIssues(options, deps = {}) {
|
|
11533
11535
|
const cfg = resolveBoardConfig(options.config);
|
|
11534
11536
|
const client = deps.client ?? defaultGitHubClient();
|
|
11535
|
-
const
|
|
11536
|
-
const currentRepo = options.repo ?? repoFromRemoteUrl(await
|
|
11537
|
+
const git2 = deps.git ?? defaultGit;
|
|
11538
|
+
const currentRepo = options.repo ?? repoFromRemoteUrl(await git2(["remote", "get-url", "origin"])) ?? "";
|
|
11537
11539
|
if (!currentRepo) throw new Error("could not resolve current GitHub repo; pass --repo owner/repo");
|
|
11538
11540
|
const selectors = [];
|
|
11539
11541
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -11617,8 +11619,8 @@ async function moveBoardIssues(options, deps = {}) {
|
|
|
11617
11619
|
async function unclaimBoardIssue(options, deps = {}) {
|
|
11618
11620
|
const cfg = resolveBoardConfig(options.config);
|
|
11619
11621
|
const client = deps.client ?? defaultGitHubClient();
|
|
11620
|
-
const
|
|
11621
|
-
const currentRepo = options.repo ?? repoFromRemoteUrl(await
|
|
11622
|
+
const git2 = deps.git ?? defaultGit;
|
|
11623
|
+
const currentRepo = options.repo ?? repoFromRemoteUrl(await git2(["remote", "get-url", "origin"])) ?? "";
|
|
11622
11624
|
if (!currentRepo) throw new Error("could not resolve current GitHub repo; pass --repo owner/repo");
|
|
11623
11625
|
const selector = parseIssueSelector(options.selector, currentRepo);
|
|
11624
11626
|
const { viewer, item } = await fetchIssueProjectItem(client, cfg, selector);
|
|
@@ -12450,11 +12452,13 @@ var PRIMARY_GROUPS = [
|
|
|
12450
12452
|
["Orient", ["onboard", "status", "next", "doctor", "whoami", "commands", "explain"]],
|
|
12451
12453
|
["Plan and work", ["board", "issue", "worktree", "stage"]],
|
|
12452
12454
|
["Review and ship", ["pr", "ci", "rcand", "release", "hotfix", "train"]],
|
|
12453
|
-
|
|
12455
|
+
// `tests` sits beside `docs` deliberately: both are deterministic, repo-local gates a workflow
|
|
12456
|
+
// step invokes (`docs refs`, `tests policy`), not org-plane operations (#3605).
|
|
12457
|
+
["Setup and support", ["bootstrap", "secrets", "docs", "tests"]],
|
|
12454
12458
|
["Coordinate and improve", ["wave", "report", "skill-lesson"]]
|
|
12455
12459
|
];
|
|
12456
12460
|
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"]);
|
|
12461
|
+
var SUPPORT_PRIMARY = /* @__PURE__ */ new Set(["doctor", "whoami", "commands", "explain", "docs", "tests", "wave", "report", "skill-lesson"]);
|
|
12458
12462
|
var TOP_LEVEL_ORDER = /* @__PURE__ */ new Map();
|
|
12459
12463
|
var HELP_GROUP_ORDER = /* @__PURE__ */ new Map();
|
|
12460
12464
|
var topLevelPosition = 0;
|
|
@@ -15031,31 +15035,31 @@ var CHERRY_TRAILER = /\(cherry picked from commit ([0-9a-f]{7,40})\)/g;
|
|
|
15031
15035
|
function checkHotfixCoverage(options = {}) {
|
|
15032
15036
|
const { cwd = process.cwd(), mainRef = "origin/main", rcRef = "origin/rc", manifestPaths = [] } = options;
|
|
15033
15037
|
const ack = (options.ack ?? []).filter(Boolean);
|
|
15034
|
-
const
|
|
15038
|
+
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
15039
|
const revList = (range) => {
|
|
15036
|
-
const out =
|
|
15040
|
+
const out = git2(["rev-list", "--no-merges", range]).trim();
|
|
15037
15041
|
return out ? out.split("\n") : [];
|
|
15038
15042
|
};
|
|
15039
15043
|
const isAncestor = (sha, ref) => {
|
|
15040
15044
|
try {
|
|
15041
|
-
|
|
15045
|
+
git2(["merge-base", "--is-ancestor", sha, ref]);
|
|
15042
15046
|
return true;
|
|
15043
15047
|
} catch {
|
|
15044
15048
|
return false;
|
|
15045
15049
|
}
|
|
15046
15050
|
};
|
|
15047
15051
|
const patchId = (sha) => {
|
|
15048
|
-
const diff =
|
|
15052
|
+
const diff = git2(["show", "--no-color", "--pretty=format:", sha]);
|
|
15049
15053
|
if (!diff.trim()) return null;
|
|
15050
|
-
const out =
|
|
15054
|
+
const out = git2(["patch-id", "--stable"], { input: diff }).trim();
|
|
15051
15055
|
return out ? out.split(" ")[0] : null;
|
|
15052
15056
|
};
|
|
15053
15057
|
const changedPaths = (sha) => {
|
|
15054
|
-
const out =
|
|
15058
|
+
const out = git2(["show", "--no-color", "--name-only", "--pretty=format:", sha]).trim();
|
|
15055
15059
|
return out ? out.split("\n") : [];
|
|
15056
15060
|
};
|
|
15057
15061
|
const cherrySources = (sha) => {
|
|
15058
|
-
const message =
|
|
15062
|
+
const message = git2(["log", "-1", "--format=%B", sha]);
|
|
15059
15063
|
return [...message.matchAll(CHERRY_TRAILER)].map((m) => m[1]);
|
|
15060
15064
|
};
|
|
15061
15065
|
const mainOnly = revList(`${rcRef}..${mainRef}`);
|
|
@@ -15070,7 +15074,7 @@ function checkHotfixCoverage(options = {}) {
|
|
|
15070
15074
|
}
|
|
15071
15075
|
};
|
|
15072
15076
|
const commits = mainOnly.map((sha) => {
|
|
15073
|
-
const subject =
|
|
15077
|
+
const subject = git2(["log", "-1", "--format=%s", sha]).trim();
|
|
15074
15078
|
const paths = changedPaths(sha);
|
|
15075
15079
|
if (paths.length > 0 && paths.every((p) => manifestPaths.includes(p))) {
|
|
15076
15080
|
return { sha, subject, coverage: "exempt-distribution" };
|
|
@@ -15099,21 +15103,21 @@ function checkHotfixCoverage(options = {}) {
|
|
|
15099
15103
|
}
|
|
15100
15104
|
function checkHotfixCarries(options) {
|
|
15101
15105
|
const { cwd = process.cwd(), branch, baseRef, targets } = options;
|
|
15102
|
-
const
|
|
15106
|
+
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
15107
|
const isAncestor = (sha, ref) => {
|
|
15104
15108
|
try {
|
|
15105
|
-
|
|
15109
|
+
git2(["merge-base", "--is-ancestor", sha, ref]);
|
|
15106
15110
|
return true;
|
|
15107
15111
|
} catch {
|
|
15108
15112
|
return false;
|
|
15109
15113
|
}
|
|
15110
15114
|
};
|
|
15111
15115
|
const revList = (range) => {
|
|
15112
|
-
const out =
|
|
15116
|
+
const out = git2(["rev-list", "--no-merges", range]).trim();
|
|
15113
15117
|
return out ? out.split("\n") : [];
|
|
15114
15118
|
};
|
|
15115
15119
|
const cherrySources = (sha) => {
|
|
15116
|
-
const message =
|
|
15120
|
+
const message = git2(["log", "-1", "--format=%B", sha]);
|
|
15117
15121
|
return [...message.matchAll(CHERRY_TRAILER)].map((m) => m[1]);
|
|
15118
15122
|
};
|
|
15119
15123
|
const carried = /* @__PURE__ */ new Set();
|
|
@@ -16364,6 +16368,162 @@ function runDocRefs(root, deps = {}) {
|
|
|
16364
16368
|
return { ok: findings.length === 0, findings, docCount: Object.keys(docs2).length };
|
|
16365
16369
|
}
|
|
16366
16370
|
|
|
16371
|
+
// src/test-policy-core.ts
|
|
16372
|
+
var import_node_child_process10 = require("node:child_process");
|
|
16373
|
+
var import_node_fs19 = require("node:fs");
|
|
16374
|
+
var import_node_path17 = require("node:path");
|
|
16375
|
+
var POLICY_FILE = "test-policy.json";
|
|
16376
|
+
var TEST_RE = /\.(?:test|spec)\.[cm]?[jt]sx?$/;
|
|
16377
|
+
var PY_TEST_RE = /(?:^|\/)test_[^/]*\.py$|_test\.py$/;
|
|
16378
|
+
var OVERRIDE_RE = /^Test-Policy-Override:\s*(.+)$/im;
|
|
16379
|
+
function translate(glob) {
|
|
16380
|
+
let out = "";
|
|
16381
|
+
for (let i = 0; i < glob.length; i++) {
|
|
16382
|
+
const c = glob[i];
|
|
16383
|
+
if (c === "*") {
|
|
16384
|
+
if (glob[i + 1] === "*") {
|
|
16385
|
+
if (glob[i + 2] === "/") {
|
|
16386
|
+
out += "(?:.*/)?";
|
|
16387
|
+
i += 2;
|
|
16388
|
+
} else {
|
|
16389
|
+
out += ".*";
|
|
16390
|
+
i += 1;
|
|
16391
|
+
}
|
|
16392
|
+
} else {
|
|
16393
|
+
out += "[^/]*";
|
|
16394
|
+
}
|
|
16395
|
+
continue;
|
|
16396
|
+
}
|
|
16397
|
+
if (c === "{") {
|
|
16398
|
+
const close = glob.indexOf("}", i);
|
|
16399
|
+
if (close !== -1) {
|
|
16400
|
+
const alts = glob.slice(i + 1, close).split(",").map((a) => translate(a));
|
|
16401
|
+
out += `(?:${alts.join("|")})`;
|
|
16402
|
+
i = close;
|
|
16403
|
+
continue;
|
|
16404
|
+
}
|
|
16405
|
+
}
|
|
16406
|
+
out += /[.+?^${}()|[\]\\]/.test(c) ? `\\${c}` : c;
|
|
16407
|
+
}
|
|
16408
|
+
return out;
|
|
16409
|
+
}
|
|
16410
|
+
function globToRegExp(glob) {
|
|
16411
|
+
return new RegExp(`^${translate(glob)}$`);
|
|
16412
|
+
}
|
|
16413
|
+
function isTestPath(path2) {
|
|
16414
|
+
return TEST_RE.test(path2) || PY_TEST_RE.test(path2);
|
|
16415
|
+
}
|
|
16416
|
+
function loadPolicy(root, readFile7 = readFileOrNull2) {
|
|
16417
|
+
const raw = readFile7((0, import_node_path17.join)(root, POLICY_FILE));
|
|
16418
|
+
if (raw == null) return { mandatory: [], declared: false };
|
|
16419
|
+
try {
|
|
16420
|
+
return { ...JSON.parse(raw), declared: true };
|
|
16421
|
+
} catch (error) {
|
|
16422
|
+
throw new Error(`${POLICY_FILE} is not valid JSON: ${error.message}`);
|
|
16423
|
+
}
|
|
16424
|
+
}
|
|
16425
|
+
function readFileOrNull2(path2) {
|
|
16426
|
+
try {
|
|
16427
|
+
return (0, import_node_fs19.readFileSync)(path2, "utf8");
|
|
16428
|
+
} catch {
|
|
16429
|
+
return null;
|
|
16430
|
+
}
|
|
16431
|
+
}
|
|
16432
|
+
function classify(changed, policy) {
|
|
16433
|
+
const matchers = (policy.mandatory ?? []).map((m) => ({ ...m, re: globToRegExp(m.glob) }));
|
|
16434
|
+
const mandatoryHits = changed.filter((f) => matchers.some((m) => m.re.test(f.path)));
|
|
16435
|
+
const testChanges = changed.filter((f) => isTestPath(f.path));
|
|
16436
|
+
const addedTests = testChanges.filter((f) => f.status === "A");
|
|
16437
|
+
const protectedBy = new Map((policy.protected ?? []).map((p) => [p.path, p.why ?? ""]));
|
|
16438
|
+
const removedProtected = changed.map((f) => f.status === "D" ? f.path : f.status === "R" || f.status === "C" ? f.from : void 0).filter((p) => typeof p === "string" && protectedBy.has(p)).map((p) => ({ path: p, why: protectedBy.get(p) ?? "" }));
|
|
16439
|
+
return { mandatoryHits, testChanges, addedTests, removedProtected };
|
|
16440
|
+
}
|
|
16441
|
+
function unresolvedProtectedEntries(policy, root, exists = (path2) => (0, import_node_fs19.existsSync)(path2)) {
|
|
16442
|
+
return (policy.protected ?? []).map((p) => p.path).filter((p) => !exists((0, import_node_path17.join)(root, p)));
|
|
16443
|
+
}
|
|
16444
|
+
function evaluate(changed, policy) {
|
|
16445
|
+
const { mandatoryHits, testChanges, addedTests, removedProtected } = classify(changed, policy);
|
|
16446
|
+
const findings = [];
|
|
16447
|
+
if (removedProtected.length > 0) {
|
|
16448
|
+
findings.push({
|
|
16449
|
+
kind: "protected-removed",
|
|
16450
|
+
paths: removedProtected.map((f) => f.path),
|
|
16451
|
+
detail: `PROTECTED TEST FILE(S) REMOVED \u2014 this change deletes or renames away ${removedProtected.length} file(s) test-policy.json protects:
|
|
16452
|
+
` + removedProtected.map((f) => ` ${f.path}
|
|
16453
|
+
${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>"
|
|
16454
|
+
});
|
|
16455
|
+
}
|
|
16456
|
+
if (mandatoryHits.length > 0 && testChanges.length === 0) {
|
|
16457
|
+
findings.push({
|
|
16458
|
+
kind: "mandatory-zone-untested",
|
|
16459
|
+
paths: mandatoryHits.map((f) => f.path),
|
|
16460
|
+
detail: `MANDATORY ZONE UNTESTED \u2014 this change touches ${mandatoryHits.length} path(s) in the mandatory zone but changes no test:
|
|
16461
|
+
` + mandatoryHits.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."
|
|
16462
|
+
});
|
|
16463
|
+
}
|
|
16464
|
+
if (policy.declared !== false && addedTests.length > 0 && mandatoryHits.length === 0) {
|
|
16465
|
+
findings.push({
|
|
16466
|
+
kind: "unrequested-test-file",
|
|
16467
|
+
paths: addedTests.map((f) => f.path),
|
|
16468
|
+
detail: `UNREQUESTED TEST FILE(S) \u2014 this change adds ${addedTests.length} new test file(s) but touches nothing in the mandatory zone:
|
|
16469
|
+
` + 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>"
|
|
16470
|
+
});
|
|
16471
|
+
}
|
|
16472
|
+
return findings;
|
|
16473
|
+
}
|
|
16474
|
+
function git(args, cwd) {
|
|
16475
|
+
return (0, import_node_child_process10.execFileSync)("git", args, { cwd, encoding: "utf8", maxBuffer: 32 * 1024 * 1024 });
|
|
16476
|
+
}
|
|
16477
|
+
function resolveBase(cwd, explicit) {
|
|
16478
|
+
const candidates = [explicit, process.env.TEST_POLICY_BASE, "origin/development", "origin/main"].filter(Boolean);
|
|
16479
|
+
for (const ref of candidates) {
|
|
16480
|
+
try {
|
|
16481
|
+
return git(["merge-base", "HEAD", ref], cwd).trim();
|
|
16482
|
+
} catch {
|
|
16483
|
+
}
|
|
16484
|
+
}
|
|
16485
|
+
return null;
|
|
16486
|
+
}
|
|
16487
|
+
function changedFilesSince(base, cwd) {
|
|
16488
|
+
const raw = git(["diff", "--name-status", "-M", `${base}...HEAD`], cwd).trim();
|
|
16489
|
+
if (!raw) return [];
|
|
16490
|
+
return raw.split("\n").map((line) => {
|
|
16491
|
+
const parts = line.split(" ");
|
|
16492
|
+
const norm = (p) => p.replace(/\\/g, "/");
|
|
16493
|
+
const row = { status: parts[0][0], path: norm(parts[parts.length - 1]) };
|
|
16494
|
+
if (parts.length > 2) row.from = norm(parts[1]);
|
|
16495
|
+
return row;
|
|
16496
|
+
});
|
|
16497
|
+
}
|
|
16498
|
+
function runTestPolicy(root, deps = {}) {
|
|
16499
|
+
const policy = deps.policy ?? loadPolicy(root);
|
|
16500
|
+
const exists = deps.exists ?? ((path2) => (0, import_node_fs19.existsSync)(path2));
|
|
16501
|
+
const counts = { mandatoryCount: (policy.mandatory ?? []).length, protectedCount: (policy.protected ?? []).length };
|
|
16502
|
+
const unresolved = unresolvedProtectedEntries(policy, root, exists);
|
|
16503
|
+
if (unresolved.length > 0) {
|
|
16504
|
+
return {
|
|
16505
|
+
ok: false,
|
|
16506
|
+
base: null,
|
|
16507
|
+
changedCount: 0,
|
|
16508
|
+
...counts,
|
|
16509
|
+
findings: [{
|
|
16510
|
+
kind: "stale-protected-entry",
|
|
16511
|
+
paths: unresolved,
|
|
16512
|
+
detail: `STALE PROTECTED ENTRY \u2014 test-policy.json protects ${unresolved.length} path(s) that do not exist:
|
|
16513
|
+
` + 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."
|
|
16514
|
+
}]
|
|
16515
|
+
};
|
|
16516
|
+
}
|
|
16517
|
+
const base = deps.changed ? "(injected)" : resolveBase(root, deps.base);
|
|
16518
|
+
if (!deps.changed && !base) return { ok: true, findings: [], changedCount: 0, base: null, ...counts };
|
|
16519
|
+
const changed = deps.changed ?? changedFilesSince(base, root);
|
|
16520
|
+
if (changed.length === 0) return { ok: true, findings: [], changedCount: 0, base, ...counts };
|
|
16521
|
+
const override = deps.overrideReason !== void 0 ? deps.overrideReason : OVERRIDE_RE.exec(git(["log", `${base}..HEAD`, "--format=%B"], root))?.[1]?.trim() ?? null;
|
|
16522
|
+
if (override) return { ok: true, findings: [], changedCount: changed.length, base, overriddenBy: override, ...counts };
|
|
16523
|
+
const findings = evaluate(changed, policy);
|
|
16524
|
+
return { ok: findings.length === 0, findings, changedCount: changed.length, base, ...counts };
|
|
16525
|
+
}
|
|
16526
|
+
|
|
16367
16527
|
// src/docs-audit-command.ts
|
|
16368
16528
|
function serializeOutcome(outcome) {
|
|
16369
16529
|
switch (outcome.kind) {
|
|
@@ -17305,8 +17465,8 @@ function writeError(res) {
|
|
|
17305
17465
|
}
|
|
17306
17466
|
|
|
17307
17467
|
// src/secrets-commands.ts
|
|
17308
|
-
var
|
|
17309
|
-
var
|
|
17468
|
+
var import_node_fs20 = require("node:fs");
|
|
17469
|
+
var import_node_path18 = require("node:path");
|
|
17310
17470
|
var import_node_os5 = require("node:os");
|
|
17311
17471
|
|
|
17312
17472
|
// src/project-runtime.ts
|
|
@@ -17430,18 +17590,18 @@ function collectMap(value, previous = []) {
|
|
|
17430
17590
|
return [...previous, value];
|
|
17431
17591
|
}
|
|
17432
17592
|
async function decryptRailsCredentials(input) {
|
|
17433
|
-
const appDir = (0,
|
|
17593
|
+
const appDir = (0, import_node_path18.resolve)(input.appDir ?? process.cwd());
|
|
17434
17594
|
const credentialsFile = input.credentialsFile ?? DEFAULT_RAILS_CREDENTIALS_FILE;
|
|
17435
17595
|
const masterKeyFile = input.masterKeyFile ?? DEFAULT_RAILS_MASTER_KEY_FILE;
|
|
17436
|
-
const credentialsPath = (0,
|
|
17437
|
-
const masterKeyPath = (0,
|
|
17596
|
+
const credentialsPath = (0, import_node_path18.resolve)(appDir, credentialsFile);
|
|
17597
|
+
const masterKeyPath = (0, import_node_path18.resolve)(appDir, masterKeyFile);
|
|
17438
17598
|
const env = {
|
|
17439
17599
|
...process.env,
|
|
17440
17600
|
MMI_RAILS_CREDENTIALS_FILE: credentialsPath,
|
|
17441
17601
|
MMI_RAILS_MASTER_KEY_FILE: masterKeyPath
|
|
17442
17602
|
};
|
|
17443
|
-
if ((0,
|
|
17444
|
-
env.RAILS_MASTER_KEY = (0,
|
|
17603
|
+
if ((0, import_node_fs20.existsSync)(masterKeyPath)) {
|
|
17604
|
+
env.RAILS_MASTER_KEY = (0, import_node_fs20.readFileSync)(masterKeyPath, "utf8").trim();
|
|
17445
17605
|
}
|
|
17446
17606
|
const script = [
|
|
17447
17607
|
'require "json"',
|
|
@@ -17451,9 +17611,9 @@ async function decryptRailsCredentials(input) {
|
|
|
17451
17611
|
'config = ActiveSupport::EncryptedConfiguration.new(config_path: config_path, key_path: key_path, env_key: "RAILS_MASTER_KEY", raise_if_missing_key: true)',
|
|
17452
17612
|
"puts JSON.generate(config.config)"
|
|
17453
17613
|
].join("\n");
|
|
17454
|
-
const scriptDir = (0,
|
|
17455
|
-
const scriptPath = (0,
|
|
17456
|
-
(0,
|
|
17614
|
+
const scriptDir = (0, import_node_fs20.mkdtempSync)((0, import_node_path18.join)((0, import_node_os5.tmpdir)(), "mmi-rails-decrypt-"));
|
|
17615
|
+
const scriptPath = (0, import_node_path18.join)(scriptDir, "decrypt.rb");
|
|
17616
|
+
(0, import_node_fs20.writeFileSync)(scriptPath, script, "utf8");
|
|
17457
17617
|
try {
|
|
17458
17618
|
const args = ["exec", "ruby", scriptPath];
|
|
17459
17619
|
const cmd = process.platform === "win32" ? "cmd.exe" : "bundle";
|
|
@@ -17465,7 +17625,7 @@ async function decryptRailsCredentials(input) {
|
|
|
17465
17625
|
});
|
|
17466
17626
|
return JSON.parse(stdout);
|
|
17467
17627
|
} finally {
|
|
17468
|
-
(0,
|
|
17628
|
+
(0, import_node_fs20.rmSync)(scriptDir, { recursive: true, force: true });
|
|
17469
17629
|
}
|
|
17470
17630
|
}
|
|
17471
17631
|
async function readSecretStdin() {
|
|
@@ -17555,7 +17715,7 @@ function registerSecretsCommands(program3) {
|
|
|
17555
17715
|
let body;
|
|
17556
17716
|
if (o.file) {
|
|
17557
17717
|
try {
|
|
17558
|
-
body = (0,
|
|
17718
|
+
body = (0, import_node_fs20.readFileSync)((0, import_node_path18.resolve)(o.file), "utf8");
|
|
17559
17719
|
} catch (e) {
|
|
17560
17720
|
return fail(`secrets org-catalog: cannot read --file ${o.file}: ${e.message}`);
|
|
17561
17721
|
}
|
|
@@ -17660,7 +17820,7 @@ function registerSecretsCommands(program3) {
|
|
|
17660
17820
|
{
|
|
17661
17821
|
...d,
|
|
17662
17822
|
decryptRailsCredentials,
|
|
17663
|
-
removeFile: (path2) => (0,
|
|
17823
|
+
removeFile: (path2) => (0, import_node_fs20.unlinkSync)((0, import_node_path18.resolve)(o.appDir ?? process.cwd(), path2))
|
|
17664
17824
|
},
|
|
17665
17825
|
{
|
|
17666
17826
|
repo: o.repo,
|
|
@@ -17854,7 +18014,7 @@ function checkGithubPools(probe) {
|
|
|
17854
18014
|
}
|
|
17855
18015
|
|
|
17856
18016
|
// src/box-commands.ts
|
|
17857
|
-
var
|
|
18017
|
+
var import_node_fs21 = require("node:fs");
|
|
17858
18018
|
|
|
17859
18019
|
// src/box.ts
|
|
17860
18020
|
var BOX_KEYS = {
|
|
@@ -18057,7 +18217,7 @@ function registerBoxCommands(program3) {
|
|
|
18057
18217
|
}
|
|
18058
18218
|
if (o.json) console.log(JSON.stringify({ box: found, incomplete }, null, 2));
|
|
18059
18219
|
else if (o.ssh && o.script) {
|
|
18060
|
-
(0,
|
|
18220
|
+
(0, import_node_fs21.writeFileSync)(o.script, sshRecipeScript(found), "utf8");
|
|
18061
18221
|
console.log(`wrote ${o.script} \u2014 run: bash "${o.script}"`);
|
|
18062
18222
|
} else if (o.ssh) console.log(`${formatSshRecipe(found)}
|
|
18063
18223
|
${SSH_RECIPE_AGENT_NOTE}`);
|
|
@@ -18072,7 +18232,7 @@ ${SSH_RECIPE_AGENT_NOTE}`);
|
|
|
18072
18232
|
|
|
18073
18233
|
// src/schedules-commands.ts
|
|
18074
18234
|
var import_promises2 = require("node:fs/promises");
|
|
18075
|
-
var
|
|
18235
|
+
var import_node_child_process11 = require("node:child_process");
|
|
18076
18236
|
var import_node_util7 = require("node:util");
|
|
18077
18237
|
|
|
18078
18238
|
// src/schedules.ts
|
|
@@ -18512,7 +18672,7 @@ function assembleReconciliation(githubEntries2, readRepos, registry2, activeWork
|
|
|
18512
18672
|
}
|
|
18513
18673
|
|
|
18514
18674
|
// src/schedules-commands.ts
|
|
18515
|
-
var execFileP5 = (0, import_node_util7.promisify)(
|
|
18675
|
+
var execFileP5 = (0, import_node_util7.promisify)(import_node_child_process11.execFile);
|
|
18516
18676
|
var AWS_REGION = "eu-central-1";
|
|
18517
18677
|
var AWS_TIMEOUT_MS = 3e4;
|
|
18518
18678
|
var AWS_RETRY_DELAY_MS = 1500;
|
|
@@ -18749,7 +18909,7 @@ function registerSchedulesCommands(program3) {
|
|
|
18749
18909
|
|
|
18750
18910
|
// src/file-lock.ts
|
|
18751
18911
|
var import_promises3 = require("node:fs/promises");
|
|
18752
|
-
var
|
|
18912
|
+
var import_node_path19 = require("node:path");
|
|
18753
18913
|
var sleep = (ms) => new Promise((resolve5) => setTimeout(resolve5, ms));
|
|
18754
18914
|
var IMMEDIATE_RETRY_BUDGET = 3;
|
|
18755
18915
|
var FileLockBusyError = class extends Error {
|
|
@@ -18834,7 +18994,7 @@ async function releaseFileLock(lockPath, guard) {
|
|
|
18834
18994
|
}
|
|
18835
18995
|
async function withFileLock(lockPath, opts, fn) {
|
|
18836
18996
|
const resolved = resolveFileLockOpts(opts);
|
|
18837
|
-
await (0, import_promises3.mkdir)((0,
|
|
18997
|
+
await (0, import_promises3.mkdir)((0, import_node_path19.dirname)(lockPath), { recursive: true }).catch(() => void 0);
|
|
18838
18998
|
const guard = await acquireFileLock(lockPath, resolved, Date.now() + resolved.maxWaitMs);
|
|
18839
18999
|
try {
|
|
18840
19000
|
return await fn();
|
|
@@ -18845,7 +19005,7 @@ async function withFileLock(lockPath, opts, fn) {
|
|
|
18845
19005
|
|
|
18846
19006
|
// src/schedules-lift-command.ts
|
|
18847
19007
|
var import_promises4 = require("node:fs/promises");
|
|
18848
|
-
var
|
|
19008
|
+
var import_node_path20 = require("node:path");
|
|
18849
19009
|
|
|
18850
19010
|
// src/schedules-lift.ts
|
|
18851
19011
|
var SCHEDULE_HEADER_FIELDS = ["schedule", "what", "owner", "cadence", "executor", "llm", "output", "breaks", "kill"];
|
|
@@ -18951,7 +19111,7 @@ async function readWorkflowFiles(dir) {
|
|
|
18951
19111
|
const files = [];
|
|
18952
19112
|
for (const name of names.sort()) {
|
|
18953
19113
|
if (!/\.ya?ml$/.test(name)) continue;
|
|
18954
|
-
files.push({ path: `.github/workflows/${name}`, text: await (0, import_promises4.readFile)((0,
|
|
19114
|
+
files.push({ path: `.github/workflows/${name}`, text: await (0, import_promises4.readFile)((0, import_node_path20.join)(dir, name), "utf8") });
|
|
18955
19115
|
}
|
|
18956
19116
|
return files;
|
|
18957
19117
|
}
|
|
@@ -19493,7 +19653,7 @@ function registerQueryCommands(program3) {
|
|
|
19493
19653
|
}
|
|
19494
19654
|
|
|
19495
19655
|
// src/bootstrap-commands.ts
|
|
19496
|
-
var
|
|
19656
|
+
var import_node_fs22 = require("node:fs");
|
|
19497
19657
|
|
|
19498
19658
|
// src/bootstrap-verify.ts
|
|
19499
19659
|
var TRAIN_BRANCHES2 = ["development", "rc", "main"];
|
|
@@ -20036,7 +20196,7 @@ function registerBootstrapCommands(program3) {
|
|
|
20036
20196
|
client: defaultGitHubClient(),
|
|
20037
20197
|
projectMeta: meta,
|
|
20038
20198
|
deployModel: typeof meta?.deployModel === "string" ? meta.deployModel : void 0,
|
|
20039
|
-
readLocalFile: (path2) => path2 === "projects.json" && apiProjects != null ? apiProjects : (0,
|
|
20199
|
+
readLocalFile: (path2) => path2 === "projects.json" && apiProjects != null ? apiProjects : (0, import_node_fs22.existsSync)(path2) ? (0, import_node_fs22.readFileSync)(path2, "utf8") : null,
|
|
20040
20200
|
// requiredGcpApis is stored as an array by a JSON write, but `org project set --var KEY=VALUE` stores a raw
|
|
20041
20201
|
// comma-string — accept either so the seeded value verifies regardless of how it was written.
|
|
20042
20202
|
requiredGcpApis: (() => {
|
|
@@ -20091,12 +20251,12 @@ function registerBootstrapCommands(program3) {
|
|
|
20091
20251
|
return fail(`bootstrap apply: ${e.message}`);
|
|
20092
20252
|
}
|
|
20093
20253
|
const manifestPath = "skills/bootstrap/seeds/manifest.json";
|
|
20094
|
-
if (!(0,
|
|
20095
|
-
const manifest = loadBootstrapSeeds((0,
|
|
20254
|
+
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`);
|
|
20255
|
+
const manifest = loadBootstrapSeeds((0, import_node_fs22.readFileSync)(manifestPath, "utf8"));
|
|
20096
20256
|
const baseBranch = o.class === "content" ? "main" : "development";
|
|
20097
20257
|
const slug = parsedRepo.slug;
|
|
20098
20258
|
const gh = async (args) => execFileP2("gh", args, { timeout: 2e4 });
|
|
20099
|
-
const readFile7 = (p) => (0,
|
|
20259
|
+
const readFile7 = (p) => (0, import_node_fs22.existsSync)(p) ? (0, import_node_fs22.readFileSync)(p, "utf8") : null;
|
|
20100
20260
|
const enc = (p) => p.split("/").map(encodeURIComponent).join("/");
|
|
20101
20261
|
const rawVars = {};
|
|
20102
20262
|
for (const value of cmdOpts.var ?? []) {
|
|
@@ -20330,12 +20490,12 @@ LIVE apply to ${repo}:
|
|
|
20330
20490
|
}
|
|
20331
20491
|
|
|
20332
20492
|
// src/stage-commands.ts
|
|
20333
|
-
var
|
|
20334
|
-
var
|
|
20493
|
+
var import_node_fs24 = require("node:fs");
|
|
20494
|
+
var import_node_path22 = require("node:path");
|
|
20335
20495
|
|
|
20336
20496
|
// src/port-registry.ts
|
|
20337
|
-
var
|
|
20338
|
-
var
|
|
20497
|
+
var import_node_fs23 = require("node:fs");
|
|
20498
|
+
var import_node_path21 = require("node:path");
|
|
20339
20499
|
|
|
20340
20500
|
// ../infra/port-geometry.mjs
|
|
20341
20501
|
var PORT_BLOCK = 100;
|
|
@@ -20349,8 +20509,8 @@ function nextPortBlock(registry2) {
|
|
|
20349
20509
|
return [base, base + PORT_SPAN];
|
|
20350
20510
|
}
|
|
20351
20511
|
function loadPortRegistry(path2) {
|
|
20352
|
-
if (!(0,
|
|
20353
|
-
const raw = JSON.parse((0,
|
|
20512
|
+
if (!(0, import_node_fs23.existsSync)(path2)) return {};
|
|
20513
|
+
const raw = JSON.parse((0, import_node_fs23.readFileSync)(path2, "utf8"));
|
|
20354
20514
|
const out = {};
|
|
20355
20515
|
for (const [key, value] of Object.entries(raw)) {
|
|
20356
20516
|
if (Array.isArray(value) && value.length === 2 && value.every((n) => typeof n === "number")) {
|
|
@@ -20364,9 +20524,9 @@ function ensurePortRange(repo, path2) {
|
|
|
20364
20524
|
const existing = registry2[repo];
|
|
20365
20525
|
if (existing) return existing;
|
|
20366
20526
|
const range = nextPortBlock(registry2);
|
|
20367
|
-
const raw = (0,
|
|
20527
|
+
const raw = (0, import_node_fs23.existsSync)(path2) ? JSON.parse((0, import_node_fs23.readFileSync)(path2, "utf8")) : {};
|
|
20368
20528
|
raw[repo] = range;
|
|
20369
|
-
(0,
|
|
20529
|
+
(0, import_node_fs23.writeFileSync)(path2, JSON.stringify(raw, null, 2) + "\n", "utf8");
|
|
20370
20530
|
return range;
|
|
20371
20531
|
}
|
|
20372
20532
|
function portCursorSeed(registry2) {
|
|
@@ -20388,22 +20548,22 @@ function existingPortRange(repo, registry2) {
|
|
|
20388
20548
|
return registry2[repo] ?? null;
|
|
20389
20549
|
}
|
|
20390
20550
|
function portRangeInfraAt(root, source) {
|
|
20391
|
-
const registryPath = (0,
|
|
20392
|
-
const ddbScriptPath = (0,
|
|
20393
|
-
if (!(0,
|
|
20551
|
+
const registryPath = (0, import_node_path21.join)(root, "infra", "port-ranges.json");
|
|
20552
|
+
const ddbScriptPath = (0, import_node_path21.join)(root, "infra", "port-ddb.mjs");
|
|
20553
|
+
if (!(0, import_node_fs23.existsSync)(registryPath) || !(0, import_node_fs23.existsSync)(ddbScriptPath)) return null;
|
|
20394
20554
|
return { root, source, registryPath, ddbScriptPath };
|
|
20395
20555
|
}
|
|
20396
20556
|
function resolvePortRangeInfra(cwd, packageDir) {
|
|
20397
20557
|
const direct = portRangeInfraAt(cwd, "cwd");
|
|
20398
20558
|
if (direct) return direct;
|
|
20399
|
-
for (let dir = cwd; ; dir = (0,
|
|
20400
|
-
const sibling = portRangeInfraAt((0,
|
|
20559
|
+
for (let dir = cwd; ; dir = (0, import_node_path21.dirname)(dir)) {
|
|
20560
|
+
const sibling = portRangeInfraAt((0, import_node_path21.join)(dir, "MMI-Hub"), "sibling-hub");
|
|
20401
20561
|
if (sibling) return sibling;
|
|
20402
|
-
const parent = (0,
|
|
20562
|
+
const parent = (0, import_node_path21.dirname)(dir);
|
|
20403
20563
|
if (parent === dir) break;
|
|
20404
20564
|
}
|
|
20405
20565
|
if (packageDir) {
|
|
20406
|
-
const pkgRoot = (0,
|
|
20566
|
+
const pkgRoot = (0, import_node_path21.join)(packageDir, "..", "..");
|
|
20407
20567
|
const pkgFrom = portRangeInfraAt(pkgRoot, "pkg-root");
|
|
20408
20568
|
if (pkgFrom) return pkgFrom;
|
|
20409
20569
|
}
|
|
@@ -20579,8 +20739,8 @@ function registerStageCommands(program3) {
|
|
|
20579
20739
|
const portRange = portRangeMeta && typeof portRangeMeta.start === "number" && typeof portRangeMeta.end === "number" ? [portRangeMeta.start, portRangeMeta.end] : void 0;
|
|
20580
20740
|
return decideStage({
|
|
20581
20741
|
registry: { deployModel: project2?.deployModel, portRange, error: read.ok ? void 0 : read.error },
|
|
20582
|
-
hasCompose: (0,
|
|
20583
|
-
hasEnvExample: (0,
|
|
20742
|
+
hasCompose: (0, import_node_fs24.existsSync)((0, import_node_path22.join)(process.cwd(), "docker-compose.yml")),
|
|
20743
|
+
hasEnvExample: (0, import_node_fs24.existsSync)((0, import_node_path22.join)(process.cwd(), ".env.example"))
|
|
20584
20744
|
});
|
|
20585
20745
|
}
|
|
20586
20746
|
async function fetchStageVaultEnvMerge() {
|
|
@@ -21012,10 +21172,10 @@ function registerBoardCommands(program3) {
|
|
|
21012
21172
|
}
|
|
21013
21173
|
|
|
21014
21174
|
// src/merge-cleanup.ts
|
|
21015
|
-
var
|
|
21175
|
+
var import_node_fs27 = require("node:fs");
|
|
21016
21176
|
var import_promises6 = require("node:fs/promises");
|
|
21017
|
-
var
|
|
21018
|
-
var
|
|
21177
|
+
var import_node_path26 = require("node:path");
|
|
21178
|
+
var import_node_child_process12 = require("node:child_process");
|
|
21019
21179
|
|
|
21020
21180
|
// src/board-advance.ts
|
|
21021
21181
|
function repoOf2(ref) {
|
|
@@ -21101,7 +21261,7 @@ function boardAdvanceFailureMessage(result) {
|
|
|
21101
21261
|
|
|
21102
21262
|
// src/deferred-registry-store.ts
|
|
21103
21263
|
var import_promises5 = require("node:fs/promises");
|
|
21104
|
-
var
|
|
21264
|
+
var import_node_path23 = require("node:path");
|
|
21105
21265
|
var sleep2 = (ms) => new Promise((resolve5) => setTimeout(resolve5, ms));
|
|
21106
21266
|
async function atomicWrite(target, contents) {
|
|
21107
21267
|
const tmp = `${target}.tmp-${process.pid}-${Math.random().toString(36).slice(2)}`;
|
|
@@ -21152,12 +21312,12 @@ function makeDeferredWorktreeStore(registryPath, lockOpts = {}) {
|
|
|
21152
21312
|
},
|
|
21153
21313
|
// Standalone atomic write — THROWS on failure (no best-effort swallow, #2846).
|
|
21154
21314
|
write: async (entries) => {
|
|
21155
|
-
await (0, import_promises5.mkdir)((0,
|
|
21315
|
+
await (0, import_promises5.mkdir)((0, import_node_path23.dirname)(registryPath), { recursive: true });
|
|
21156
21316
|
await atomicWrite(registryPath, serializeDeferredWorktrees(entries));
|
|
21157
21317
|
},
|
|
21158
21318
|
// Serialized read-modify-write under the repo-wide lock (#2846).
|
|
21159
21319
|
update: async (mutate) => {
|
|
21160
|
-
await (0, import_promises5.mkdir)((0,
|
|
21320
|
+
await (0, import_promises5.mkdir)((0, import_node_path23.dirname)(registryPath), { recursive: true });
|
|
21161
21321
|
const deadline = Date.now() + opts.maxWaitMs;
|
|
21162
21322
|
for (; ; ) {
|
|
21163
21323
|
const guard = await acquireLock(lockPath, opts, deadline);
|
|
@@ -21181,8 +21341,8 @@ function makeDeferredWorktreeStore(registryPath, lockOpts = {}) {
|
|
|
21181
21341
|
}
|
|
21182
21342
|
|
|
21183
21343
|
// src/plugin-guard-io.ts
|
|
21184
|
-
var
|
|
21185
|
-
var
|
|
21344
|
+
var import_node_fs25 = require("node:fs");
|
|
21345
|
+
var import_node_path24 = require("node:path");
|
|
21186
21346
|
var import_node_os6 = require("node:os");
|
|
21187
21347
|
|
|
21188
21348
|
// src/plugin-guard.ts
|
|
@@ -21315,11 +21475,11 @@ function runHostBin(bin, args, opts) {
|
|
|
21315
21475
|
}
|
|
21316
21476
|
var installedPluginsPath2 = (surface = detectSurface(process.env)) => {
|
|
21317
21477
|
const homeDir = surface === "codex" ? ".codex" : ".claude";
|
|
21318
|
-
return (0,
|
|
21478
|
+
return (0, import_node_path24.join)((0, import_node_os6.homedir)(), homeDir, "plugins", "installed_plugins.json");
|
|
21319
21479
|
};
|
|
21320
21480
|
function readInstalledPlugins(surface = detectSurface(process.env)) {
|
|
21321
21481
|
try {
|
|
21322
|
-
return JSON.parse((0,
|
|
21482
|
+
return JSON.parse((0, import_node_fs25.readFileSync)(installedPluginsPath2(surface), "utf8"));
|
|
21323
21483
|
} catch {
|
|
21324
21484
|
return null;
|
|
21325
21485
|
}
|
|
@@ -21327,13 +21487,13 @@ function readInstalledPlugins(surface = detectSurface(process.env)) {
|
|
|
21327
21487
|
function marketplaceCloneCandidates(surface, home) {
|
|
21328
21488
|
if (surface === "codex") {
|
|
21329
21489
|
return [
|
|
21330
|
-
(0,
|
|
21331
|
-
(0,
|
|
21490
|
+
(0, import_node_path24.join)(home, ".codex", ".tmp", "marketplaces", "mutmutco"),
|
|
21491
|
+
(0, import_node_path24.join)(home, ".codex", "plugins", "marketplaces", "mutmutco")
|
|
21332
21492
|
];
|
|
21333
21493
|
}
|
|
21334
|
-
return [(0,
|
|
21494
|
+
return [(0, import_node_path24.join)(home, ".claude", "plugins", "marketplaces", "mutmutco")];
|
|
21335
21495
|
}
|
|
21336
|
-
function marketplaceClonePresent(surface, home, exists =
|
|
21496
|
+
function marketplaceClonePresent(surface, home, exists = import_node_fs25.existsSync) {
|
|
21337
21497
|
return marketplaceCloneCandidates(surface, home).some(exists);
|
|
21338
21498
|
}
|
|
21339
21499
|
async function fetchNpmReleasedVersion() {
|
|
@@ -21361,7 +21521,7 @@ function snapshotPluginGuardInput(surface = detectSurface(process.env), isOrgRep
|
|
|
21361
21521
|
isOrgRepo,
|
|
21362
21522
|
installRecordPresent: hasUserInstallRecord(installed, MMI_PLUGIN_ID) || hasProjectInstallRecord(installed, MMI_PLUGIN_ID, process.cwd()),
|
|
21363
21523
|
marketplaceClonePresent: marketplaceClonePresent(surface, (0, import_node_os6.homedir)()),
|
|
21364
|
-
pluginCachePresent: (0,
|
|
21524
|
+
pluginCachePresent: (0, import_node_fs25.existsSync)((0, import_node_path24.join)((0, import_node_os6.homedir)(), homeDir, "plugins", "cache", "mutmutco", "mmi"))
|
|
21365
21525
|
};
|
|
21366
21526
|
}
|
|
21367
21527
|
function claudePluginGuardState(isOrgRepo) {
|
|
@@ -21407,7 +21567,7 @@ async function applyPluginHeal(surface, log, opts) {
|
|
|
21407
21567
|
const refSupported = await marketplaceAddRefSupported("claude");
|
|
21408
21568
|
const { steps } = adaptHealStepsForRefSupport(tableSteps, refSupported);
|
|
21409
21569
|
log(healBannerLine("claude", "claude", refSupported));
|
|
21410
|
-
const pinsPath = (0,
|
|
21570
|
+
const pinsPath = (0, import_node_path24.join)((0, import_node_os6.homedir)(), ...KNOWN_MARKETPLACES_RELATIVE);
|
|
21411
21571
|
const pins = captureMarketplacePins(readKnownMarketplacesFile(pinsPath), [MMI_MARKETPLACE_NAME, JERV_MARKETPLACE_NAME]);
|
|
21412
21572
|
for (const step of steps) {
|
|
21413
21573
|
if (healStepAborts(step, await runClaudePlugin([...step.args]))) return false;
|
|
@@ -21430,7 +21590,7 @@ async function healClaudePluginForDoctor(surface = detectSurface(process.env)) {
|
|
|
21430
21590
|
}
|
|
21431
21591
|
function readKnownMarketplacesFile(path2) {
|
|
21432
21592
|
try {
|
|
21433
|
-
return (0,
|
|
21593
|
+
return (0, import_node_fs25.existsSync)(path2) ? (0, import_node_fs25.readFileSync)(path2, "utf8") : void 0;
|
|
21434
21594
|
} catch {
|
|
21435
21595
|
return void 0;
|
|
21436
21596
|
}
|
|
@@ -21441,7 +21601,7 @@ function restoreMarketplacePinsOnDisk(path2, pins) {
|
|
|
21441
21601
|
const next = restoreMarketplacePins(after, pins);
|
|
21442
21602
|
if (next === null) return void 0;
|
|
21443
21603
|
try {
|
|
21444
|
-
(0,
|
|
21604
|
+
(0, import_node_fs25.writeFileSync)(path2, next, "utf8");
|
|
21445
21605
|
} catch {
|
|
21446
21606
|
return `could NOT restore ${[...pins.keys()].join(", ")} \u2014 re-pin by hand`;
|
|
21447
21607
|
}
|
|
@@ -21488,9 +21648,9 @@ async function runPluginHeal(surface = detectSurface(process.env)) {
|
|
|
21488
21648
|
}
|
|
21489
21649
|
|
|
21490
21650
|
// src/worktree-ownership.ts
|
|
21491
|
-
var
|
|
21651
|
+
var import_node_fs26 = require("node:fs");
|
|
21492
21652
|
var import_node_os7 = require("node:os");
|
|
21493
|
-
var
|
|
21653
|
+
var import_node_path25 = require("node:path");
|
|
21494
21654
|
var OWNERS_FILE = "worktree-owners.json";
|
|
21495
21655
|
var EVENTS_FILE = "worktree-events.jsonl";
|
|
21496
21656
|
var WORKTREE_ACTIVITY_WINDOW_MS = 30 * 6e4;
|
|
@@ -21587,7 +21747,7 @@ function describeActorShort(actor) {
|
|
|
21587
21747
|
}
|
|
21588
21748
|
function readOwners(primaryRoot) {
|
|
21589
21749
|
try {
|
|
21590
|
-
return parseWorktreeOwners((0,
|
|
21750
|
+
return parseWorktreeOwners((0, import_node_fs26.readFileSync)(worktreeOwnersPath(primaryRoot), "utf8"));
|
|
21591
21751
|
} catch {
|
|
21592
21752
|
return [];
|
|
21593
21753
|
}
|
|
@@ -21595,8 +21755,8 @@ function readOwners(primaryRoot) {
|
|
|
21595
21755
|
function writeOwners(primaryRoot, entries) {
|
|
21596
21756
|
try {
|
|
21597
21757
|
const path2 = worktreeOwnersPath(primaryRoot);
|
|
21598
|
-
(0,
|
|
21599
|
-
(0,
|
|
21758
|
+
(0, import_node_fs26.mkdirSync)((0, import_node_path25.dirname)(path2), { recursive: true });
|
|
21759
|
+
(0, import_node_fs26.writeFileSync)(path2, serializeWorktreeOwners(entries), "utf8");
|
|
21600
21760
|
} catch {
|
|
21601
21761
|
}
|
|
21602
21762
|
}
|
|
@@ -21629,8 +21789,8 @@ function dropWorktreeOwner(primaryRoot, path2) {
|
|
|
21629
21789
|
function appendWorktreeEvent(primaryRoot, event) {
|
|
21630
21790
|
try {
|
|
21631
21791
|
const path2 = worktreeEventsPath(primaryRoot);
|
|
21632
|
-
(0,
|
|
21633
|
-
(0,
|
|
21792
|
+
(0, import_node_fs26.mkdirSync)((0, import_node_path25.dirname)(path2), { recursive: true });
|
|
21793
|
+
(0, import_node_fs26.appendFileSync)(path2, `${JSON.stringify({ at: event.at ?? (/* @__PURE__ */ new Date()).toISOString(), ...event })}
|
|
21634
21794
|
`, "utf8");
|
|
21635
21795
|
} catch {
|
|
21636
21796
|
}
|
|
@@ -21638,7 +21798,7 @@ function appendWorktreeEvent(primaryRoot, event) {
|
|
|
21638
21798
|
function readWorktreeEvents(primaryRoot, limit = 50) {
|
|
21639
21799
|
let text;
|
|
21640
21800
|
try {
|
|
21641
|
-
text = (0,
|
|
21801
|
+
text = (0, import_node_fs26.readFileSync)(worktreeEventsPath(primaryRoot), "utf8");
|
|
21642
21802
|
} catch {
|
|
21643
21803
|
return [];
|
|
21644
21804
|
}
|
|
@@ -21788,7 +21948,7 @@ async function applyGcPlan(plan, remote, opts = {}) {
|
|
|
21788
21948
|
);
|
|
21789
21949
|
const repoRoot2 = (await execFileP2("git", ["rev-parse", "--show-toplevel"], { timeout: GIT_TIMEOUT_MS }).catch(() => ({ stdout: "" }))).stdout.trim() || process.cwd();
|
|
21790
21950
|
const worktreeGitRoot = await currentRepoWorktreeGitRoot(repoRoot2);
|
|
21791
|
-
const primaryRepoRoot = worktreeGitRoot ? (0,
|
|
21951
|
+
const primaryRepoRoot = worktreeGitRoot ? (0, import_node_path26.dirname)((0, import_node_path26.dirname)(worktreeGitRoot)) : repoRoot2;
|
|
21792
21952
|
const gcActor = describeActor({ env: process.env, surface: detectSurface(process.env), cwd: process.cwd() });
|
|
21793
21953
|
const owners = readWorktreeOwners(primaryRepoRoot);
|
|
21794
21954
|
const removalNow = Date.now();
|
|
@@ -21819,7 +21979,7 @@ async function applyGcPlan(plan, remote, opts = {}) {
|
|
|
21819
21979
|
const cleanup = await cleanupPrMergeLocalBranch(branch.branch, {
|
|
21820
21980
|
beforeWorktrees,
|
|
21821
21981
|
startingPath: branch.worktreePath,
|
|
21822
|
-
pathExists: (p) => (0,
|
|
21982
|
+
pathExists: (p) => (0, import_node_fs27.existsSync)(p),
|
|
21823
21983
|
execGit: async (args) => (await execFileP2("git", args, { timeout: GIT_TIMEOUT_MS })).stdout,
|
|
21824
21984
|
teardownWorktreeStage,
|
|
21825
21985
|
deferredStore,
|
|
@@ -21847,7 +22007,7 @@ async function applyGcPlan(plan, remote, opts = {}) {
|
|
|
21847
22007
|
for (const wt of worktreeDirsToRemove) {
|
|
21848
22008
|
try {
|
|
21849
22009
|
const cleanupTarget = resolveSafeSiblingWorktreeCleanupTarget(wt.path, siblingRoot, {
|
|
21850
|
-
realpath: (path2) => (0,
|
|
22010
|
+
realpath: (path2) => (0, import_node_fs27.realpathSync)(path2)
|
|
21851
22011
|
});
|
|
21852
22012
|
if (!cleanupTarget.ok) {
|
|
21853
22013
|
result.failed.push(`${wt.path}: ${cleanupTarget.reason}`);
|
|
@@ -21998,7 +22158,7 @@ async function remoteBranchExists2(branch, options = {}) {
|
|
|
21998
22158
|
}
|
|
21999
22159
|
var COMPOSE_TIMEOUT_MS = 12e4;
|
|
22000
22160
|
function spawnDeferredGcSweep() {
|
|
22001
|
-
spawnDetachedSelf(["worktree", "gc", "sweep-deferred", "--quiet"], { spawn:
|
|
22161
|
+
spawnDetachedSelf(["worktree", "gc", "sweep-deferred", "--quiet"], { spawn: import_node_child_process12.spawn, execPath: process.execPath, scriptPath: process.argv[1] });
|
|
22002
22162
|
}
|
|
22003
22163
|
async function createDeferredWorktreeStore() {
|
|
22004
22164
|
try {
|
|
@@ -22012,13 +22172,13 @@ var realWorktreeDirRemover = {
|
|
|
22012
22172
|
probe: (p) => {
|
|
22013
22173
|
let st;
|
|
22014
22174
|
try {
|
|
22015
|
-
st = (0,
|
|
22175
|
+
st = (0, import_node_fs27.lstatSync)(p);
|
|
22016
22176
|
} catch {
|
|
22017
22177
|
return null;
|
|
22018
22178
|
}
|
|
22019
22179
|
if (st.isSymbolicLink()) return "link";
|
|
22020
22180
|
try {
|
|
22021
|
-
(0,
|
|
22181
|
+
(0, import_node_fs27.readlinkSync)(p);
|
|
22022
22182
|
return "link";
|
|
22023
22183
|
} catch {
|
|
22024
22184
|
}
|
|
@@ -22026,7 +22186,7 @@ var realWorktreeDirRemover = {
|
|
|
22026
22186
|
},
|
|
22027
22187
|
readdir: (p) => {
|
|
22028
22188
|
try {
|
|
22029
|
-
return (0,
|
|
22189
|
+
return (0, import_node_fs27.readdirSync)(p);
|
|
22030
22190
|
} catch {
|
|
22031
22191
|
return [];
|
|
22032
22192
|
}
|
|
@@ -22035,9 +22195,9 @@ var realWorktreeDirRemover = {
|
|
|
22035
22195
|
// leaving the target); a file symlink with unlink. rmdir first, fall back to unlink.
|
|
22036
22196
|
detachLink: (p) => {
|
|
22037
22197
|
try {
|
|
22038
|
-
(0,
|
|
22198
|
+
(0, import_node_fs27.rmdirSync)(p);
|
|
22039
22199
|
} catch {
|
|
22040
|
-
(0,
|
|
22200
|
+
(0, import_node_fs27.unlinkSync)(p);
|
|
22041
22201
|
}
|
|
22042
22202
|
},
|
|
22043
22203
|
removeTree: (p) => (0, import_promises6.rm)(p, { recursive: true, force: true, maxRetries: 5, retryDelay: 200 })
|
|
@@ -22070,9 +22230,9 @@ async function worktreeHasStageState(worktreePath) {
|
|
|
22070
22230
|
}
|
|
22071
22231
|
}
|
|
22072
22232
|
function stageStateFileBelongsToWorktree(statePath, worktreePath) {
|
|
22073
|
-
if (!(0,
|
|
22233
|
+
if (!(0, import_node_fs27.existsSync)(statePath)) return false;
|
|
22074
22234
|
try {
|
|
22075
|
-
const state = JSON.parse((0,
|
|
22235
|
+
const state = JSON.parse((0, import_node_fs27.readFileSync)(statePath, "utf8"));
|
|
22076
22236
|
const recordedCwd = typeof state.identity?.cwd === "string" ? state.identity.cwd : typeof state.cwd === "string" ? state.cwd : "";
|
|
22077
22237
|
return Boolean(recordedCwd && isPathUnderDirectory(recordedCwd, worktreePath));
|
|
22078
22238
|
} catch {
|
|
@@ -22279,8 +22439,8 @@ async function fetchRestCorePool(gh = defaultGhApi) {
|
|
|
22279
22439
|
}
|
|
22280
22440
|
|
|
22281
22441
|
// src/worktree-lifecycle-commands.ts
|
|
22282
|
-
var
|
|
22283
|
-
var
|
|
22442
|
+
var import_node_fs28 = require("node:fs");
|
|
22443
|
+
var import_node_path27 = require("node:path");
|
|
22284
22444
|
var GH_TIMEOUT_MS = 2e4;
|
|
22285
22445
|
var DEFAULT_BASE = "origin/development";
|
|
22286
22446
|
var DEFAULT_REMOTE = "origin";
|
|
@@ -22383,7 +22543,7 @@ function classifyStaleLeaks(input) {
|
|
|
22383
22543
|
var defaultOrphanDirScanDeps = {
|
|
22384
22544
|
listDirs: (root) => {
|
|
22385
22545
|
try {
|
|
22386
|
-
return (0,
|
|
22546
|
+
return (0, import_node_fs28.readdirSync)(root, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => (0, import_node_path27.join)(root, e.name));
|
|
22387
22547
|
} catch {
|
|
22388
22548
|
return [];
|
|
22389
22549
|
}
|
|
@@ -22530,13 +22690,13 @@ function registerWorktreeCommands(program3) {
|
|
|
22530
22690
|
if (PROTECTED_BRANCHES2.has(branch)) {
|
|
22531
22691
|
return fail(`worktree land: refusing to land protected branch '${branch}'`, { code: ERROR_CODES.ERR_BAD_ENUM });
|
|
22532
22692
|
}
|
|
22533
|
-
const gitFile = (0,
|
|
22534
|
-
const isLinked = (0,
|
|
22693
|
+
const gitFile = (0, import_node_path27.join)(wtPath, ".git");
|
|
22694
|
+
const isLinked = (0, import_node_fs28.existsSync)(gitFile) && (0, import_node_fs28.statSync)(gitFile).isFile();
|
|
22535
22695
|
if (apply && !isLinked) {
|
|
22536
22696
|
return fail("worktree land: run from inside the linked worktree you want to land (this is the primary checkout)");
|
|
22537
22697
|
}
|
|
22538
22698
|
const commonDir = (await execFileP2("git", ["rev-parse", "--git-common-dir"], { timeout: GIT_TIMEOUT_MS }).catch(() => ({ stdout: "" }))).stdout.trim();
|
|
22539
|
-
const primaryCheckout = commonDir ? (0,
|
|
22699
|
+
const primaryCheckout = commonDir ? (0, import_node_path27.dirname)(commonDir) : wtPath;
|
|
22540
22700
|
const stageSummary = readStageSummary(wtPath);
|
|
22541
22701
|
const hasStage = stageSummary != null;
|
|
22542
22702
|
const steps = landSteps(branch, true, hasStage);
|
|
@@ -22688,10 +22848,10 @@ async function gatherWorktreeContext() {
|
|
|
22688
22848
|
if (s) stages.push({ path: wt.path, port: s.port });
|
|
22689
22849
|
}
|
|
22690
22850
|
const worktreeGitRoot = await currentRepoWorktreeGitRoot(repoRoot2);
|
|
22691
|
-
const primaryRepoRoot = worktreeGitRoot ? (0,
|
|
22851
|
+
const primaryRepoRoot = worktreeGitRoot ? (0, import_node_path27.dirname)((0, import_node_path27.dirname)(worktreeGitRoot)) : repoRoot2;
|
|
22692
22852
|
const wtRoot = siblingMmiWorktreesRoot(primaryRepoRoot);
|
|
22693
22853
|
let orphanDirs = [];
|
|
22694
|
-
if ((0,
|
|
22854
|
+
if ((0, import_node_fs28.existsSync)(wtRoot)) {
|
|
22695
22855
|
orphanDirs = scanOrphanDirs(wtRoot, worktreeGitRoot, {
|
|
22696
22856
|
...defaultOrphanDirScanDeps,
|
|
22697
22857
|
listDirs: (root) => worktreeScanDirs(root, primaryRepoRoot, defaultOrphanDirScanDeps.listDirs, isRepoCheckoutDir)
|
|
@@ -22714,7 +22874,7 @@ async function bestEffortGit(args, cwd, step, timeoutMs = GIT_TIMEOUT_MS) {
|
|
|
22714
22874
|
}
|
|
22715
22875
|
|
|
22716
22876
|
// src/issue-commands.ts
|
|
22717
|
-
var
|
|
22877
|
+
var import_node_fs29 = require("node:fs");
|
|
22718
22878
|
var import_node_crypto5 = require("node:crypto");
|
|
22719
22879
|
var ghRunner = async (args, timeoutMs) => (await execFileP2("gh", args, { timeout: timeoutMs })).stdout;
|
|
22720
22880
|
async function editIssue(client, options, deps = {}) {
|
|
@@ -22724,11 +22884,16 @@ async function editIssue(client, options, deps = {}) {
|
|
|
22724
22884
|
const url = `https://github.com/${repo}/issues/${parsed.number}`;
|
|
22725
22885
|
const patch = {};
|
|
22726
22886
|
let bodyChanged = false;
|
|
22727
|
-
|
|
22887
|
+
const textDeps = () => deps.textDeps ?? { readFile: (p, e) => Promise.resolve((0, import_node_fs29.readFileSync)(p, e)), readStdin: () => Promise.resolve("") };
|
|
22888
|
+
if (options.titleFile !== void 0) {
|
|
22889
|
+
patch.title = await resolveIssueTitle({ title: options.title, titleFile: options.titleFile }, textDeps());
|
|
22890
|
+
} else if (options.title !== void 0) {
|
|
22891
|
+
patch.title = options.title;
|
|
22892
|
+
}
|
|
22728
22893
|
if (options.body !== void 0 || options.bodyFile !== void 0) {
|
|
22729
22894
|
let body;
|
|
22730
22895
|
if (options.bodyFile) {
|
|
22731
|
-
const td =
|
|
22896
|
+
const td = textDeps();
|
|
22732
22897
|
body = await resolveIssueBody({ body: options.body, bodyFile: options.bodyFile }, td);
|
|
22733
22898
|
} else {
|
|
22734
22899
|
body = options.body ?? "";
|
|
@@ -23093,7 +23258,7 @@ function registerIssueLifecycleCommands(program3, deps = {}) {
|
|
|
23093
23258
|
const issue2 = program3.commands.find((c) => c.name() === "issue");
|
|
23094
23259
|
if (!issue2) return;
|
|
23095
23260
|
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)"),
|
|
23261
|
+
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
23262
|
(opts, args) => ({ command: "issue edit", ref: args[0], fields: Object.keys(opts).filter((k) => k !== "repo" && opts[k] !== void 0) })
|
|
23098
23263
|
).action(async (ref, o) => {
|
|
23099
23264
|
try {
|
|
@@ -23102,6 +23267,7 @@ function registerIssueLifecycleCommands(program3, deps = {}) {
|
|
|
23102
23267
|
ref,
|
|
23103
23268
|
defaultRepo,
|
|
23104
23269
|
title: o.title,
|
|
23270
|
+
titleFile: o.titleFile,
|
|
23105
23271
|
body: o.body,
|
|
23106
23272
|
bodyFile: o.bodyFile,
|
|
23107
23273
|
addLabel: o.addLabel,
|
|
@@ -23225,7 +23391,7 @@ function extendCreateCommand(issue2, batchAttach) {
|
|
|
23225
23391
|
if (opts.batch) {
|
|
23226
23392
|
let specs;
|
|
23227
23393
|
try {
|
|
23228
|
-
const raw = (0,
|
|
23394
|
+
const raw = (0, import_node_fs29.readFileSync)(opts.batch, "utf8");
|
|
23229
23395
|
specs = JSON.parse(raw);
|
|
23230
23396
|
if (!Array.isArray(specs)) throw new Error("batch file must contain a JSON array");
|
|
23231
23397
|
} catch (e) {
|
|
@@ -23283,8 +23449,8 @@ ${lines}`);
|
|
|
23283
23449
|
}
|
|
23284
23450
|
|
|
23285
23451
|
// src/train-commands.ts
|
|
23286
|
-
var
|
|
23287
|
-
var
|
|
23452
|
+
var import_node_fs30 = require("node:fs");
|
|
23453
|
+
var import_node_path28 = require("node:path");
|
|
23288
23454
|
|
|
23289
23455
|
// src/train-status.ts
|
|
23290
23456
|
function buildTrainStatusReport(input) {
|
|
@@ -23324,7 +23490,7 @@ function formatTrainStatus(r) {
|
|
|
23324
23490
|
// src/train-commands.ts
|
|
23325
23491
|
function readRepoVersion() {
|
|
23326
23492
|
try {
|
|
23327
|
-
return JSON.parse((0,
|
|
23493
|
+
return JSON.parse((0, import_node_fs30.readFileSync)((0, import_node_path28.join)(process.cwd(), ".claude-plugin", "plugin.json"), "utf8")).version || void 0;
|
|
23328
23494
|
} catch {
|
|
23329
23495
|
return void 0;
|
|
23330
23496
|
}
|
|
@@ -23470,9 +23636,9 @@ function registerDeployCommands(program3) {
|
|
|
23470
23636
|
}
|
|
23471
23637
|
|
|
23472
23638
|
// src/discovery-commands.ts
|
|
23473
|
-
var
|
|
23639
|
+
var import_node_fs31 = require("node:fs");
|
|
23474
23640
|
var import_node_os8 = require("node:os");
|
|
23475
|
-
var
|
|
23641
|
+
var import_node_path29 = require("node:path");
|
|
23476
23642
|
var GC_GH_TIMEOUT_MS3 = 2e4;
|
|
23477
23643
|
async function collectStatus() {
|
|
23478
23644
|
let branch = "";
|
|
@@ -23649,8 +23815,8 @@ async function collectOnboardStatus() {
|
|
|
23649
23815
|
}
|
|
23650
23816
|
const home = (0, import_node_os8.homedir)();
|
|
23651
23817
|
const plugin = onboardPluginGate({
|
|
23652
|
-
readKnown: () => readFileSyncSafe((0,
|
|
23653
|
-
readSettings: () => readFileSyncSafe((0,
|
|
23818
|
+
readKnown: () => readFileSyncSafe((0, import_node_path29.join)(home, ...KNOWN_MARKETPLACES_RELATIVE), import_node_fs31.readFileSync),
|
|
23819
|
+
readSettings: () => readFileSyncSafe((0, import_node_path29.join)(home, ".claude", "settings.json"), import_node_fs31.readFileSync)
|
|
23654
23820
|
});
|
|
23655
23821
|
return { track, board, registry: registry2, secrets, plugin, nextCommand };
|
|
23656
23822
|
}
|
|
@@ -25010,17 +25176,17 @@ function parseOriginRepo(remoteUrl) {
|
|
|
25010
25176
|
}
|
|
25011
25177
|
function ghHostsConfigPath(env, platform2) {
|
|
25012
25178
|
const sep2 = platform2 === "win32" ? "\\" : "/";
|
|
25013
|
-
const
|
|
25179
|
+
const join26 = (...parts) => parts.join(sep2);
|
|
25014
25180
|
const explicit = env.GH_CONFIG_DIR?.trim();
|
|
25015
|
-
if (explicit) return
|
|
25181
|
+
if (explicit) return join26(explicit, "hosts.yml");
|
|
25016
25182
|
if (platform2 === "win32") {
|
|
25017
25183
|
const appData = (env.AppData ?? env.APPDATA)?.trim();
|
|
25018
|
-
return appData ?
|
|
25184
|
+
return appData ? join26(appData, "GitHub CLI", "hosts.yml") : void 0;
|
|
25019
25185
|
}
|
|
25020
25186
|
const xdg = env.XDG_CONFIG_HOME?.trim();
|
|
25021
|
-
if (xdg) return
|
|
25187
|
+
if (xdg) return join26(xdg, "gh", "hosts.yml");
|
|
25022
25188
|
const home = env.HOME?.trim();
|
|
25023
|
-
return home ?
|
|
25189
|
+
return home ? join26(home, ".config", "gh", "hosts.yml") : void 0;
|
|
25024
25190
|
}
|
|
25025
25191
|
function parseGhHostsAccounts(yaml, host = "github.com") {
|
|
25026
25192
|
let hostIndent = null;
|
|
@@ -25070,17 +25236,17 @@ function ghAccountCaveat(announcedLogin, accounts) {
|
|
|
25070
25236
|
}
|
|
25071
25237
|
|
|
25072
25238
|
// src/doctor-io.ts
|
|
25073
|
-
var
|
|
25239
|
+
var import_node_fs32 = require("node:fs");
|
|
25074
25240
|
var import_node_os9 = require("node:os");
|
|
25075
|
-
var
|
|
25076
|
-
var
|
|
25241
|
+
var import_node_path30 = require("node:path");
|
|
25242
|
+
var import_node_child_process13 = require("node:child_process");
|
|
25077
25243
|
var import_node_util8 = require("node:util");
|
|
25078
|
-
var execFileP6 = (0, import_node_util8.promisify)(
|
|
25244
|
+
var execFileP6 = (0, import_node_util8.promisify)(import_node_child_process13.execFile);
|
|
25079
25245
|
var MMI_PLUGIN_ID2 = "mmi@mutmutco";
|
|
25080
25246
|
function installedClaudePluginVersion() {
|
|
25081
25247
|
try {
|
|
25082
25248
|
const file = JSON.parse(
|
|
25083
|
-
(0,
|
|
25249
|
+
(0, import_node_fs32.readFileSync)((0, import_node_path30.join)((0, import_node_os9.homedir)(), ".claude", "plugins", "installed_plugins.json"), "utf8")
|
|
25084
25250
|
);
|
|
25085
25251
|
const versions = (file.plugins?.[MMI_PLUGIN_ID2] ?? []).map((r) => r.version).filter((v) => Boolean(v));
|
|
25086
25252
|
if (versions.length === 0) return void 0;
|
|
@@ -25091,7 +25257,7 @@ function installedClaudePluginVersion() {
|
|
|
25091
25257
|
}
|
|
25092
25258
|
function worktreeRootSync() {
|
|
25093
25259
|
try {
|
|
25094
|
-
const out = (0,
|
|
25260
|
+
const out = (0, import_node_child_process13.execFileSync)("git", ["rev-parse", "--show-toplevel"], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
|
|
25095
25261
|
let root = out.endsWith("\n") ? out.slice(0, -1) : out;
|
|
25096
25262
|
if (process.platform === "win32" && root.endsWith("\r")) root = root.slice(0, -1);
|
|
25097
25263
|
return root || null;
|
|
@@ -25101,13 +25267,13 @@ function worktreeRootSync() {
|
|
|
25101
25267
|
}
|
|
25102
25268
|
var gitignorePath = () => {
|
|
25103
25269
|
const root = worktreeRootSync();
|
|
25104
|
-
return root === null ? null : (0,
|
|
25270
|
+
return root === null ? null : (0, import_node_path30.join)(root, ".gitignore");
|
|
25105
25271
|
};
|
|
25106
25272
|
function readGitignore() {
|
|
25107
25273
|
const path2 = gitignorePath();
|
|
25108
25274
|
if (path2 === null) return null;
|
|
25109
25275
|
try {
|
|
25110
|
-
return (0,
|
|
25276
|
+
return (0, import_node_fs32.readFileSync)(path2, "utf8");
|
|
25111
25277
|
} catch {
|
|
25112
25278
|
return null;
|
|
25113
25279
|
}
|
|
@@ -25116,7 +25282,7 @@ function writeGitignore(content) {
|
|
|
25116
25282
|
const path2 = gitignorePath();
|
|
25117
25283
|
if (path2 === null) return false;
|
|
25118
25284
|
try {
|
|
25119
|
-
(0,
|
|
25285
|
+
(0, import_node_fs32.writeFileSync)(path2, content, "utf8");
|
|
25120
25286
|
return true;
|
|
25121
25287
|
} catch {
|
|
25122
25288
|
return false;
|
|
@@ -25140,7 +25306,7 @@ async function repoRoot() {
|
|
|
25140
25306
|
}
|
|
25141
25307
|
function hasRepoLocalWorktrees() {
|
|
25142
25308
|
const root = worktreeRootSync();
|
|
25143
|
-
return root !== null && (0,
|
|
25309
|
+
return root !== null && (0, import_node_fs32.existsSync)((0, import_node_path30.join)(root, ".worktrees"));
|
|
25144
25310
|
}
|
|
25145
25311
|
|
|
25146
25312
|
// src/index.ts
|
|
@@ -25175,8 +25341,8 @@ ${r.stderr ?? ""}`).catch(() => "");
|
|
|
25175
25341
|
function ghMultiAccountCaveat(announcedLogin) {
|
|
25176
25342
|
try {
|
|
25177
25343
|
const hostsPath = ghHostsConfigPath(process.env, process.platform);
|
|
25178
|
-
if (!hostsPath || !(0,
|
|
25179
|
-
return ghAccountCaveat(announcedLogin, parseGhHostsAccounts((0,
|
|
25344
|
+
if (!hostsPath || !(0, import_node_fs33.existsSync)(hostsPath)) return void 0;
|
|
25345
|
+
return ghAccountCaveat(announcedLogin, parseGhHostsAccounts((0, import_node_fs33.readFileSync)(hostsPath, "utf8")));
|
|
25180
25346
|
} catch {
|
|
25181
25347
|
return void 0;
|
|
25182
25348
|
}
|
|
@@ -25184,7 +25350,7 @@ function ghMultiAccountCaveat(announcedLogin) {
|
|
|
25184
25350
|
var ENV_HEAL_LOCK_STALE_MS = 10 * 6e4;
|
|
25185
25351
|
var ENV_HEAL_LOCK_MAX_WAIT_MS = 2 * 6e4;
|
|
25186
25352
|
function envHealLockPath(home) {
|
|
25187
|
-
return (0,
|
|
25353
|
+
return (0, import_node_path31.join)(home, ".claude", "plugins", ".mmi-env-heal.lock");
|
|
25188
25354
|
}
|
|
25189
25355
|
async function withEnvHealLock(what, run) {
|
|
25190
25356
|
try {
|
|
@@ -25335,8 +25501,8 @@ function mmiDoctorDeps(opts = {}) {
|
|
|
25335
25501
|
const home = (0, import_node_os10.homedir)();
|
|
25336
25502
|
return marketplaceRows(
|
|
25337
25503
|
MMI_MARKETPLACE_NAME,
|
|
25338
|
-
readFileSyncSafe((0,
|
|
25339
|
-
readFileSyncSafe((0,
|
|
25504
|
+
readFileSyncSafe((0, import_node_path31.join)(home, ...KNOWN_MARKETPLACES_RELATIVE), import_node_fs33.readFileSync),
|
|
25505
|
+
readFileSyncSafe((0, import_node_path31.join)(home, ".claude", "settings.json"), import_node_fs33.readFileSync)
|
|
25340
25506
|
);
|
|
25341
25507
|
} catch {
|
|
25342
25508
|
return [];
|
|
@@ -25370,6 +25536,35 @@ function allLongFlags() {
|
|
|
25370
25536
|
cachedLongFlags = [...acc];
|
|
25371
25537
|
return cachedLongFlags;
|
|
25372
25538
|
}
|
|
25539
|
+
var cachedCommandPaths;
|
|
25540
|
+
function allCommandPaths() {
|
|
25541
|
+
if (cachedCommandPaths) return cachedCommandPaths;
|
|
25542
|
+
const acc = [];
|
|
25543
|
+
const walk2 = (node, trail) => {
|
|
25544
|
+
if (!isCanonicalSuggestion(node)) return;
|
|
25545
|
+
const here = [...trail, node.name];
|
|
25546
|
+
if (here.length) acc.push(here.join(" "));
|
|
25547
|
+
for (const child2 of node.subcommands) walk2(child2, here);
|
|
25548
|
+
};
|
|
25549
|
+
for (const child2 of buildCommandManifest(program2).tree.subcommands) walk2(child2, []);
|
|
25550
|
+
cachedCommandPaths = acc;
|
|
25551
|
+
return cachedCommandPaths;
|
|
25552
|
+
}
|
|
25553
|
+
function suggestCommandPath(token, paths) {
|
|
25554
|
+
const all = [...paths];
|
|
25555
|
+
const leafOf = (p) => p.slice(p.lastIndexOf(" ") + 1);
|
|
25556
|
+
const exact = all.filter((p) => leafOf(p) === token);
|
|
25557
|
+
if (exact.length === 1) return exact[0];
|
|
25558
|
+
if (exact.length > 1) return void 0;
|
|
25559
|
+
const leaves = /* @__PURE__ */ new Map();
|
|
25560
|
+
for (const p of all) {
|
|
25561
|
+
const leaf = leafOf(p);
|
|
25562
|
+
leaves.set(leaf, [...leaves.get(leaf) ?? [], p]);
|
|
25563
|
+
}
|
|
25564
|
+
const near = didYouMean(token, leaves.keys());
|
|
25565
|
+
const hits = near ? leaves.get(near) ?? [] : [];
|
|
25566
|
+
return hits.length === 1 ? hits[0] : void 0;
|
|
25567
|
+
}
|
|
25373
25568
|
function argvWantsJson2() {
|
|
25374
25569
|
return process.argv.some((a) => a === "--json" || a.startsWith("--json="));
|
|
25375
25570
|
}
|
|
@@ -25378,6 +25573,7 @@ var PARSE_HINT_SENTINEL = "@@mmi-parse-hint@@";
|
|
|
25378
25573
|
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
25574
|
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
25575
|
var lastParseErrorKind = "other";
|
|
25576
|
+
var lastUnknownCommand;
|
|
25381
25577
|
function classifyParseError(plain) {
|
|
25382
25578
|
if (/unknown command/i.test(plain)) return "unknown-command";
|
|
25383
25579
|
if (/unknown option|missing required argument|too many arguments|argument missing/i.test(plain)) {
|
|
@@ -25407,6 +25603,10 @@ function commandPath2(cmd) {
|
|
|
25407
25603
|
return parts.join(" ");
|
|
25408
25604
|
}
|
|
25409
25605
|
function resolveParseHint() {
|
|
25606
|
+
if (lastParseErrorKind === "unknown-command") {
|
|
25607
|
+
const path3 = lastUnknownCommand ? suggestCommandPath(lastUnknownCommand, allCommandPaths()) : void 0;
|
|
25608
|
+
return path3 ? `(did you mean \`mmi-cli ${path3}\`? ${DISCOVERY_HINT})` : STALE_HINT;
|
|
25609
|
+
}
|
|
25410
25610
|
if (lastParseErrorKind !== "bad-arguments") return STALE_HINT;
|
|
25411
25611
|
const cmd = resolveCommandFromArgv(program2, process.argv.slice(2));
|
|
25412
25612
|
if (!cmd) return `(${DISCOVERY_HINT})`;
|
|
@@ -25425,6 +25625,7 @@ function envelopeAwareWriteErr(str) {
|
|
|
25425
25625
|
return;
|
|
25426
25626
|
}
|
|
25427
25627
|
lastParseErrorKind = classifyParseError(plain);
|
|
25628
|
+
lastUnknownCommand = /unknown command '?([\w:-]+)'?/.exec(plain)?.[1];
|
|
25428
25629
|
const match = /unknown option '([^']+)'/.exec(plain);
|
|
25429
25630
|
if (match) {
|
|
25430
25631
|
const flag = match[1];
|
|
@@ -25480,19 +25681,19 @@ program2.hook("preAction", async (_thisCommand, actionCommand) => {
|
|
|
25480
25681
|
});
|
|
25481
25682
|
var rules = program2.command("rules").description("org-managed .gitignore delivery");
|
|
25482
25683
|
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,
|
|
25684
|
+
const path2 = (0, import_node_path31.join)(process.cwd(), ".gitignore");
|
|
25685
|
+
const current = (0, import_node_fs33.existsSync)(path2) ? (0, import_node_fs33.readFileSync)(path2, "utf8") : null;
|
|
25485
25686
|
const plan = planManagedGitignore(current);
|
|
25486
25687
|
const drift = [...plan.added.map((l) => `+${l}`), ...plan.removed.map((l) => `-${l}`)].join(", ") || "block normalize";
|
|
25487
25688
|
if (opts.json) {
|
|
25488
|
-
if (opts.write && plan.changed) (0,
|
|
25689
|
+
if (opts.write && plan.changed) (0, import_node_fs33.writeFileSync)(path2, plan.content, "utf8");
|
|
25489
25690
|
console.log(JSON.stringify(plan, null, 2));
|
|
25490
25691
|
if (!opts.write && plan.changed) process.exitCode = 1;
|
|
25491
25692
|
return;
|
|
25492
25693
|
}
|
|
25493
25694
|
if (opts.write) {
|
|
25494
25695
|
if (plan.changed) {
|
|
25495
|
-
(0,
|
|
25696
|
+
(0, import_node_fs33.writeFileSync)(path2, plan.content, "utf8");
|
|
25496
25697
|
console.log(`mmi-cli org rules gitignore: updated .gitignore (${drift})`);
|
|
25497
25698
|
} else {
|
|
25498
25699
|
console.log("mmi-cli org rules gitignore: up to date");
|
|
@@ -25618,8 +25819,8 @@ gcCmd.option("--dry-run", "show what would be deleted (default)").option("--appl
|
|
|
25618
25819
|
if (!Number.isFinite(limit) || limit < 1) return fail("worktree gc: --limit must be a positive integer");
|
|
25619
25820
|
let root;
|
|
25620
25821
|
if (o.root !== void 0) {
|
|
25621
|
-
root = (0,
|
|
25622
|
-
if (!(0,
|
|
25822
|
+
root = (0, import_node_path31.resolve)(o.root);
|
|
25823
|
+
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
25824
|
const gcRepoRoot = (await execFileP2("git", ["rev-parse", "--show-toplevel"], { timeout: GIT_TIMEOUT_MS }).catch(() => ({ stdout: "" }))).stdout.trim() || process.cwd();
|
|
25624
25825
|
if (isPathUnderDirectory(gcRepoRoot, root)) {
|
|
25625
25826
|
return fail(`worktree gc: --root ${root} contains this checkout \u2014 name a worktrees root, not the repo or an ancestor of it`);
|
|
@@ -25661,7 +25862,7 @@ function runWorktreeInstall(command, cwd, quiet) {
|
|
|
25661
25862
|
const file = isWin2 ? "cmd.exe" : bin;
|
|
25662
25863
|
const spawnArgs = isWin2 ? ["/c", bin, ...args] : args;
|
|
25663
25864
|
return new Promise((resolve5, reject) => {
|
|
25664
|
-
const child2 = (0,
|
|
25865
|
+
const child2 = (0, import_node_child_process14.spawn)(file, spawnArgs, { cwd, stdio: quiet ? "ignore" : "inherit", windowsHide: true });
|
|
25665
25866
|
const timer = setTimeout(() => {
|
|
25666
25867
|
try {
|
|
25667
25868
|
child2.kill();
|
|
@@ -25684,7 +25885,7 @@ async function primaryCheckoutRoot(from) {
|
|
|
25684
25885
|
return primaryCheckoutRootOf(async (args) => (await execFileP2("git", ["-C", from, ...args], { timeout: GIT_TIMEOUT_MS })).stdout);
|
|
25685
25886
|
}
|
|
25686
25887
|
async function unprovenWorktreeReason(wtPath, repoRoot2) {
|
|
25687
|
-
if (!(0,
|
|
25888
|
+
if (!(0, import_node_fs33.existsSync)(wtPath)) return `${wtPath} does not exist on disk`;
|
|
25688
25889
|
const porcelain = (await execFileP2("git", ["-C", repoRoot2, "worktree", "list", "--porcelain"], { timeout: GIT_TIMEOUT_MS }).catch(() => ({ stdout: "" }))).stdout;
|
|
25689
25890
|
const registered = parseWorktreePorcelainEntries(porcelain);
|
|
25690
25891
|
if (!registered.length) {
|
|
@@ -25705,26 +25906,26 @@ function makeProvisionDeps(worktreeRoot, quiet, log) {
|
|
|
25705
25906
|
function acquireWorktreeSetupLock(worktreeRoot) {
|
|
25706
25907
|
const lockPath = repoRuntimeStatePath(worktreeRoot, "worktree-setup.lock");
|
|
25707
25908
|
const take = () => {
|
|
25708
|
-
const fd = (0,
|
|
25909
|
+
const fd = (0, import_node_fs33.openSync)(lockPath, "wx");
|
|
25709
25910
|
try {
|
|
25710
|
-
(0,
|
|
25911
|
+
(0, import_node_fs33.writeSync)(fd, String(Date.now()));
|
|
25711
25912
|
} finally {
|
|
25712
|
-
(0,
|
|
25913
|
+
(0, import_node_fs33.closeSync)(fd);
|
|
25713
25914
|
}
|
|
25714
25915
|
return () => {
|
|
25715
25916
|
try {
|
|
25716
|
-
(0,
|
|
25917
|
+
(0, import_node_fs33.rmSync)(lockPath, { force: true });
|
|
25717
25918
|
} catch {
|
|
25718
25919
|
}
|
|
25719
25920
|
};
|
|
25720
25921
|
};
|
|
25721
25922
|
try {
|
|
25722
|
-
(0,
|
|
25923
|
+
(0, import_node_fs33.mkdirSync)((0, import_node_path31.dirname)(lockPath), { recursive: true });
|
|
25723
25924
|
return take();
|
|
25724
25925
|
} catch {
|
|
25725
25926
|
try {
|
|
25726
|
-
if (Date.now() - (0,
|
|
25727
|
-
(0,
|
|
25927
|
+
if (Date.now() - (0, import_node_fs33.statSync)(lockPath).mtimeMs > WORKTREE_SETUP_LOCK_TTL_MS) {
|
|
25928
|
+
(0, import_node_fs33.rmSync)(lockPath, { force: true });
|
|
25728
25929
|
return take();
|
|
25729
25930
|
}
|
|
25730
25931
|
} catch {
|
|
@@ -25932,7 +26133,7 @@ function scheduleRelatedDiscovery(o) {
|
|
|
25932
26133
|
try {
|
|
25933
26134
|
const args = ["issue", "discover-related", "--number", String(o.number), "--title", o.title, "--body", o.body, "--fail-soft"];
|
|
25934
26135
|
if (o.repo) args.push("--repo", o.repo);
|
|
25935
|
-
spawnDetachedSelf(args, { spawn:
|
|
26136
|
+
spawnDetachedSelf(args, { spawn: import_node_child_process14.spawn, execPath: process.execPath, scriptPath: process.argv[1] }, { cwd: process.cwd() });
|
|
25936
26137
|
} catch {
|
|
25937
26138
|
}
|
|
25938
26139
|
}
|
|
@@ -25979,6 +26180,36 @@ docs.command("refs").description("deterministic doc reference gate: every backti
|
|
|
25979
26180
|
await failGraceful(e.message);
|
|
25980
26181
|
}
|
|
25981
26182
|
});
|
|
26183
|
+
var tests = program2.command("tests").description("a repo's test-policy.json \u2014 the opt-in test contract and its enforcement");
|
|
26184
|
+
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) => {
|
|
26185
|
+
try {
|
|
26186
|
+
const root = await repoRoot();
|
|
26187
|
+
const result = runTestPolicy(root, { base: o.base });
|
|
26188
|
+
if (o.json) {
|
|
26189
|
+
consoleIo.log(JSON.stringify(result, null, 2));
|
|
26190
|
+
if (!result.ok) process.exitCode = 1;
|
|
26191
|
+
return;
|
|
26192
|
+
}
|
|
26193
|
+
if (result.base === null && result.ok) {
|
|
26194
|
+
console.log("tests policy: no comparison base (origin/development or origin/main) \u2014 skipping.");
|
|
26195
|
+
return;
|
|
26196
|
+
}
|
|
26197
|
+
if (result.overriddenBy) {
|
|
26198
|
+
console.log(`tests policy: overridden by commit trailer \u2014 ${result.overriddenBy}`);
|
|
26199
|
+
return;
|
|
26200
|
+
}
|
|
26201
|
+
if (result.ok) {
|
|
26202
|
+
console.log(
|
|
26203
|
+
`tests policy: OK (${result.changedCount} changed file(s); ${result.mandatoryCount} mandatory glob(s), ${result.protectedCount} protected file(s)).`
|
|
26204
|
+
);
|
|
26205
|
+
return;
|
|
26206
|
+
}
|
|
26207
|
+
for (const f of result.findings) console.error(`tests policy: ${f.detail}`);
|
|
26208
|
+
process.exitCode = 1;
|
|
26209
|
+
} catch (e) {
|
|
26210
|
+
await failGraceful(e.message);
|
|
26211
|
+
}
|
|
26212
|
+
});
|
|
25982
26213
|
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
26214
|
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
26215
|
try {
|
|
@@ -26194,7 +26425,7 @@ project.command("set [owner/repo]").description("upsert project META (idempotent
|
|
|
26194
26425
|
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
26426
|
if (o.secretsFile) {
|
|
26196
26427
|
try {
|
|
26197
|
-
vars.push(`secrets=${(0,
|
|
26428
|
+
vars.push(`secrets=${(0, import_node_fs33.readFileSync)(o.secretsFile, "utf8")}`);
|
|
26198
26429
|
} catch (e) {
|
|
26199
26430
|
return fail(`org project set: cannot read --secrets-file ${o.secretsFile}: ${e.message}`);
|
|
26200
26431
|
}
|
|
@@ -26850,11 +27081,11 @@ pr.command("view <number>").description("read a PR as structured JSON (merged st
|
|
|
26850
27081
|
}
|
|
26851
27082
|
});
|
|
26852
27083
|
async function listCiWorkflowPaths(cwd = process.cwd()) {
|
|
26853
|
-
const wfDir = (0,
|
|
26854
|
-
if (!(0,
|
|
26855
|
-
return (0,
|
|
27084
|
+
const wfDir = (0, import_node_path31.join)(cwd, ".github", "workflows");
|
|
27085
|
+
if (!(0, import_node_fs33.existsSync)(wfDir)) return [];
|
|
27086
|
+
return (0, import_node_fs33.readdirSync)(wfDir).filter((name) => /\.(ya?ml)$/i.test(name)).filter((name) => {
|
|
26856
27087
|
try {
|
|
26857
|
-
return workflowReportsPrChecks((0,
|
|
27088
|
+
return workflowReportsPrChecks((0, import_node_fs33.readFileSync)((0, import_node_path31.join)(wfDir, name), "utf8"));
|
|
26858
27089
|
} catch {
|
|
26859
27090
|
return true;
|
|
26860
27091
|
}
|
|
@@ -26881,16 +27112,16 @@ function ciAuditDeps() {
|
|
|
26881
27112
|
// gate re-seed step is skipped gracefully rather than failing mid-run.
|
|
26882
27113
|
readSeedFile: (path2) => {
|
|
26883
27114
|
if (!root) return null;
|
|
26884
|
-
const fullPath = (0,
|
|
26885
|
-
return (0,
|
|
27115
|
+
const fullPath = (0, import_node_path31.join)(root, path2);
|
|
27116
|
+
return (0, import_node_fs33.existsSync)(fullPath) ? (0, import_node_fs33.readFileSync)(fullPath, "utf8") : null;
|
|
26886
27117
|
}
|
|
26887
27118
|
};
|
|
26888
27119
|
}
|
|
26889
27120
|
function hubRoot() {
|
|
26890
|
-
const fromPkg = (0,
|
|
27121
|
+
const fromPkg = (0, import_node_path31.join)(__dirname, "..", "..");
|
|
26891
27122
|
const marker = "skills/bootstrap/seeds/manifest.json";
|
|
26892
|
-
if ((0,
|
|
26893
|
-
if ((0,
|
|
27123
|
+
if ((0, import_node_fs33.existsSync)((0, import_node_path31.join)(fromPkg, marker))) return fromPkg;
|
|
27124
|
+
if ((0, import_node_fs33.existsSync)((0, import_node_path31.join)(process.cwd(), marker))) return process.cwd();
|
|
26894
27125
|
return null;
|
|
26895
27126
|
}
|
|
26896
27127
|
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) => {
|
|
@@ -27167,7 +27398,7 @@ jsonParity(pr.command("merge <number>").description("merge a PR (squash by defau
|
|
|
27167
27398
|
localCleanup = await cleanupPrMergeLocalBranch(headRef, {
|
|
27168
27399
|
beforeWorktrees,
|
|
27169
27400
|
startingPath,
|
|
27170
|
-
pathExists: (p) => (0,
|
|
27401
|
+
pathExists: (p) => (0, import_node_fs33.existsSync)(p),
|
|
27171
27402
|
execGit: async (args) => (await execFileP2("git", args, { timeout: GIT_TIMEOUT_MS })).stdout,
|
|
27172
27403
|
teardownWorktreeStage,
|
|
27173
27404
|
deferredStore,
|
|
@@ -27508,10 +27739,10 @@ access.command("audit").description("audit collaborator roles + train-branch pus
|
|
|
27508
27739
|
targets = resolution.targets;
|
|
27509
27740
|
}
|
|
27510
27741
|
const derivedMatrix = registryProjects ? accessMatrixFromProjects(registryProjects) : {};
|
|
27511
|
-
const fileMatrix = (0,
|
|
27742
|
+
const fileMatrix = (0, import_node_fs33.existsSync)("access-matrix.json") ? loadAccessMatrix((0, import_node_fs33.readFileSync)("access-matrix.json", "utf8")) : {};
|
|
27512
27743
|
const matrix = mergeAccessMatrix(fileMatrix, derivedMatrix);
|
|
27513
27744
|
const derivedContracts = registryProjects ? dataAccessContractsFromProjects(registryProjects) : { consumers: {} };
|
|
27514
|
-
const fileContracts = (0,
|
|
27745
|
+
const fileContracts = (0, import_node_fs33.existsSync)("data-access-contracts.json") ? loadDataAccessContracts((0, import_node_fs33.readFileSync)("data-access-contracts.json", "utf8")) : { consumers: {} };
|
|
27515
27746
|
const dataAccess = mergeDataAccessContracts(fileContracts, derivedContracts);
|
|
27516
27747
|
const report = await auditOrgAccess(targets, deps, matrix, dataAccess);
|
|
27517
27748
|
console.log(o.json ? JSON.stringify(report, null, 2) : renderAccessReport(report));
|
|
@@ -27545,16 +27776,16 @@ function directoryBytes(path2) {
|
|
|
27545
27776
|
let total = 0;
|
|
27546
27777
|
let entries;
|
|
27547
27778
|
try {
|
|
27548
|
-
entries = (0,
|
|
27779
|
+
entries = (0, import_node_fs33.readdirSync)(path2, { withFileTypes: true });
|
|
27549
27780
|
} catch {
|
|
27550
27781
|
return 0;
|
|
27551
27782
|
}
|
|
27552
27783
|
for (const entry of entries) {
|
|
27553
|
-
const child2 = (0,
|
|
27784
|
+
const child2 = (0, import_node_path31.join)(path2, entry.name);
|
|
27554
27785
|
if (entry.isDirectory()) total += directoryBytes(child2);
|
|
27555
27786
|
else {
|
|
27556
27787
|
try {
|
|
27557
|
-
total += (0,
|
|
27788
|
+
total += (0, import_node_fs33.statSync)(child2).size;
|
|
27558
27789
|
} catch {
|
|
27559
27790
|
}
|
|
27560
27791
|
}
|
|
@@ -27562,25 +27793,25 @@ function directoryBytes(path2) {
|
|
|
27562
27793
|
return total;
|
|
27563
27794
|
}
|
|
27564
27795
|
function listDirEntries(dir) {
|
|
27565
|
-
return (0,
|
|
27796
|
+
return (0, import_node_fs33.readdirSync)(dir, { withFileTypes: true }).map((d) => ({ name: d.name, isDirectory: d.isDirectory() }));
|
|
27566
27797
|
}
|
|
27567
27798
|
function readInstalledPluginRefs(home) {
|
|
27568
27799
|
const p = installedPluginsPath(home);
|
|
27569
|
-
if (!(0,
|
|
27800
|
+
if (!(0, import_node_fs33.existsSync)(p)) return [];
|
|
27570
27801
|
try {
|
|
27571
|
-
return installedPluginPaths((0,
|
|
27802
|
+
return installedPluginPaths((0, import_node_fs33.readFileSync)(p, "utf8"));
|
|
27572
27803
|
} catch {
|
|
27573
27804
|
return null;
|
|
27574
27805
|
}
|
|
27575
27806
|
}
|
|
27576
27807
|
function pluginCacheFsDeps(home, dirBytes) {
|
|
27577
27808
|
return {
|
|
27578
|
-
exists: (p) => (0,
|
|
27579
|
-
listVersionDirs: (root) => (0,
|
|
27809
|
+
exists: (p) => (0, import_node_fs33.existsSync)(p),
|
|
27810
|
+
listVersionDirs: (root) => (0, import_node_fs33.readdirSync)(root, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name),
|
|
27580
27811
|
dirBytes,
|
|
27581
|
-
listStagingDirs: (root) => (0,
|
|
27812
|
+
listStagingDirs: (root) => (0, import_node_fs33.readdirSync)(root, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => {
|
|
27582
27813
|
try {
|
|
27583
|
-
return { name: d.name, mtimeMs: newestMtimeMs((0,
|
|
27814
|
+
return { name: d.name, mtimeMs: newestMtimeMs((0, import_node_path31.join)(root, d.name), listDirEntries, (p) => (0, import_node_fs33.statSync)(p).mtimeMs) };
|
|
27584
27815
|
} catch {
|
|
27585
27816
|
return { name: d.name, mtimeMs: Date.now() };
|
|
27586
27817
|
}
|
|
@@ -27594,10 +27825,10 @@ function stagingApplyFsGuard(home) {
|
|
|
27594
27825
|
return {
|
|
27595
27826
|
referencedPaths: () => readInstalledPluginRefs(home),
|
|
27596
27827
|
mtimeMs: (name) => {
|
|
27597
|
-
const p = (0,
|
|
27598
|
-
if (!(0,
|
|
27828
|
+
const p = (0, import_node_path31.join)(stagingRoot, name);
|
|
27829
|
+
if (!(0, import_node_fs33.existsSync)(p)) return null;
|
|
27599
27830
|
try {
|
|
27600
|
-
return newestMtimeMs(p, listDirEntries, (q) => (0,
|
|
27831
|
+
return newestMtimeMs(p, listDirEntries, (q) => (0, import_node_fs33.statSync)(q).mtimeMs);
|
|
27601
27832
|
} catch {
|
|
27602
27833
|
return null;
|
|
27603
27834
|
}
|
|
@@ -27613,7 +27844,7 @@ program2.command("plugin-prune").description(`prune stale cached MMI plugin vers
|
|
|
27613
27844
|
{ withBytes: true }
|
|
27614
27845
|
);
|
|
27615
27846
|
const anythingToDelete = plan.prune.length > 0 || plan.staging.length > 0;
|
|
27616
|
-
const result = o.apply && anythingToDelete ? applyPluginCachePlan(plan, (p) => (0,
|
|
27847
|
+
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
27848
|
const warnings = plan.prune.length > 0 ? [CONCURRENT_SESSION_WARNING] : [];
|
|
27618
27849
|
if (o.json) console.log(JSON.stringify({ ...plan, warnings, applied: result ?? null }));
|
|
27619
27850
|
else console.log(renderPluginCachePlan(plan, result));
|
|
@@ -27676,7 +27907,7 @@ program2.command("session-start").description("run the SessionStart verbs (whoam
|
|
|
27676
27907
|
for (const line of scratchGcLines(process.cwd())) bannerIo.log(line);
|
|
27677
27908
|
const worktreeBanner = worktreeAutoProvisionBanner(process.cwd());
|
|
27678
27909
|
if (worktreeBanner) {
|
|
27679
|
-
spawnDetachedSelf(["worktree", "setup", "--quiet"], { spawn:
|
|
27910
|
+
spawnDetachedSelf(["worktree", "setup", "--quiet"], { spawn: import_node_child_process14.spawn, execPath: process.execPath, scriptPath: process.argv[1] });
|
|
27680
27911
|
bannerIo.log(worktreeBanner);
|
|
27681
27912
|
}
|
|
27682
27913
|
if (isLinkedWorktree(process.cwd())) {
|
|
@@ -27701,5 +27932,6 @@ program2.parseAsync(process.argv).then(() => finishCliRun()).catch((e) => failGr
|
|
|
27701
27932
|
isOrgRegisteredRepo,
|
|
27702
27933
|
registryClientDeps,
|
|
27703
27934
|
repoSlug,
|
|
27704
|
-
shouldMarkWorktreeActivity
|
|
27935
|
+
shouldMarkWorktreeActivity,
|
|
27936
|
+
suggestCommandPath
|
|
27705
27937
|
});
|