@mutmutco/cli 3.43.0 → 3.45.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 +523 -157
- package/package.json +1 -1
package/dist/main.cjs
CHANGED
|
@@ -3410,7 +3410,7 @@ var program = new Command();
|
|
|
3410
3410
|
|
|
3411
3411
|
// src/index.ts
|
|
3412
3412
|
var import_promises7 = require("node:fs/promises");
|
|
3413
|
-
var
|
|
3413
|
+
var import_node_fs30 = require("node:fs");
|
|
3414
3414
|
var import_node_child_process13 = require("node:child_process");
|
|
3415
3415
|
|
|
3416
3416
|
// src/cli-shared.ts
|
|
@@ -4061,6 +4061,12 @@ function commandPath(cmd) {
|
|
|
4061
4061
|
}
|
|
4062
4062
|
return parts.join(" ") || cmd.name();
|
|
4063
4063
|
}
|
|
4064
|
+
function jsonParity(cmd) {
|
|
4065
|
+
if (!cmd.options.some((o) => o.long === "--json")) {
|
|
4066
|
+
cmd.option("--json", "machine-readable output (default; accepted for parity)");
|
|
4067
|
+
}
|
|
4068
|
+
return cmd;
|
|
4069
|
+
}
|
|
4064
4070
|
function mutating(cmd, planFn) {
|
|
4065
4071
|
const marker = cmd;
|
|
4066
4072
|
if (marker.__mmiMutating) return cmd;
|
|
@@ -5604,6 +5610,9 @@ function samePath(a, b) {
|
|
|
5604
5610
|
function normPath(p) {
|
|
5605
5611
|
return p.replace(/\\/g, "/").replace(/\/+$/, "").toLowerCase();
|
|
5606
5612
|
}
|
|
5613
|
+
function toNativePath(p) {
|
|
5614
|
+
return process.platform === "win32" ? p.replace(/\//g, "\\") : p;
|
|
5615
|
+
}
|
|
5607
5616
|
function parseComposeLs(stdout) {
|
|
5608
5617
|
const text = stdout.trim();
|
|
5609
5618
|
if (!text) return [];
|
|
@@ -8910,9 +8919,10 @@ async function resolveWhoami(deps) {
|
|
|
8910
8919
|
if (ghLogin) return { login: ghLogin, source: "github", sessionExpiresAt: session?.expiresAt };
|
|
8911
8920
|
return { source: "unknown" };
|
|
8912
8921
|
}
|
|
8913
|
-
function whoamiLine(report) {
|
|
8922
|
+
function whoamiLine(report, caveat) {
|
|
8914
8923
|
if (!report.login) return null;
|
|
8915
|
-
|
|
8924
|
+
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.`;
|
|
8925
|
+
return caveat?.trim() ? `${line} ${caveat.trim()}` : line;
|
|
8916
8926
|
}
|
|
8917
8927
|
|
|
8918
8928
|
// src/command-ladder-hint.ts
|
|
@@ -9143,6 +9153,10 @@ async function waitForPrChecks(deps) {
|
|
|
9143
9153
|
if (state === "failure") {
|
|
9144
9154
|
failureStreak += 1;
|
|
9145
9155
|
if (failureStreak >= PR_CHECKS_FAILURE_CONFIRMATIONS) {
|
|
9156
|
+
const diagnosis = deps.diagnoseFailure ? await deps.diagnoseFailure().catch(() => null) : null;
|
|
9157
|
+
if (diagnosis?.cause === "ci-budget-kill") {
|
|
9158
|
+
return { policy, status: "failure", reason: diagnosis.reason, detail: "ci-budget-kill" };
|
|
9159
|
+
}
|
|
9146
9160
|
return { policy, status: "failure", reason, detail: "checks-failure" };
|
|
9147
9161
|
}
|
|
9148
9162
|
lastDetail = "confirming-failure";
|
|
@@ -9459,7 +9473,7 @@ function planManagedGitignore(current) {
|
|
|
9459
9473
|
}
|
|
9460
9474
|
|
|
9461
9475
|
// src/gate-budget.ts
|
|
9462
|
-
var BLESSED_RUN_WITH_BUDGET_SHA = "
|
|
9476
|
+
var BLESSED_RUN_WITH_BUDGET_SHA = "ea2cf8710949dec53566c29836e29cd7515a4e23";
|
|
9463
9477
|
var REMOTE_USE = /^mutmutco\/MMI-Hub\/\.github\/actions\/run-with-budget@(\S+)$/;
|
|
9464
9478
|
var LOCAL_USE = "./.github/actions/run-with-budget";
|
|
9465
9479
|
var FULL_SHA = /^[0-9a-f]{40}$/;
|
|
@@ -11229,7 +11243,9 @@ async function collectWaveStatus(deps) {
|
|
|
11229
11243
|
}
|
|
11230
11244
|
const headText = await deps.fetchHeadText?.(wt.branch).catch(() => void 0);
|
|
11231
11245
|
rows.push({
|
|
11232
|
-
|
|
11246
|
+
// #3338: emit NATIVE separators (see toNativePath). `-C wt.path` above and the `primary` compare
|
|
11247
|
+
// below keep git's raw form — only the emitted value is converted.
|
|
11248
|
+
path: toNativePath(wt.path),
|
|
11233
11249
|
branch: wt.branch,
|
|
11234
11250
|
primary: wt.path === primaryPath,
|
|
11235
11251
|
dirty,
|
|
@@ -11615,6 +11631,25 @@ async function unlinkSubIssue(runGh, parentRef, childRef, defaultRepo) {
|
|
|
11615
11631
|
${stdout.trim() || "(empty)"}`);
|
|
11616
11632
|
return result;
|
|
11617
11633
|
}
|
|
11634
|
+
function parseParentIssueUrl(apiUrl) {
|
|
11635
|
+
if (typeof apiUrl !== "string" || !apiUrl.trim()) return null;
|
|
11636
|
+
const match = /\/repos\/([^/]+\/[^/]+)\/issues\/(\d+)\/?$/.exec(apiUrl.trim());
|
|
11637
|
+
if (!match) return null;
|
|
11638
|
+
const [, repo, number] = match;
|
|
11639
|
+
return { repo, number: Number(number), url: `https://github.com/${repo}/issues/${number}` };
|
|
11640
|
+
}
|
|
11641
|
+
function resolveParentField(payload) {
|
|
11642
|
+
if (!payload || typeof payload !== "object") {
|
|
11643
|
+
return { parentReadError: "issue payload was empty or not an object" };
|
|
11644
|
+
}
|
|
11645
|
+
if (!("sub_issues_summary" in payload)) {
|
|
11646
|
+
return { parentReadError: "issue payload carries no sub-issue fields \u2014 parent state is unknown, not absent" };
|
|
11647
|
+
}
|
|
11648
|
+
const raw = payload.parent_issue_url;
|
|
11649
|
+
if (raw === void 0 || raw === null) return { parent: null };
|
|
11650
|
+
const parsed = typeof raw === "string" ? parseParentIssueUrl(raw) : null;
|
|
11651
|
+
return parsed ? { parent: parsed } : { parentReadError: `unrecognised parent_issue_url: ${JSON.stringify(raw)}` };
|
|
11652
|
+
}
|
|
11618
11653
|
function parentLinkFields(result, error) {
|
|
11619
11654
|
if (result) return { parent: result };
|
|
11620
11655
|
if (error) return { parentLinkError: error };
|
|
@@ -13332,7 +13367,7 @@ function appendPublishDispatch(deploy, publish) {
|
|
|
13332
13367
|
deployStatus: deploy.deployStatus === "failure" || publish.deployStatus === "failure" ? "failure" : deploy.deployStatus === "pending" || publish.deployStatus === "pending" ? "pending" : "success"
|
|
13333
13368
|
};
|
|
13334
13369
|
}
|
|
13335
|
-
async function watchOwnWorkflowRuns(deps, repo, targets, since, headSha) {
|
|
13370
|
+
async function watchOwnWorkflowRuns(deps, repo, targets, since, headSha, enumerateSha = false) {
|
|
13336
13371
|
const workflowRuns = [];
|
|
13337
13372
|
for (const target of targets) {
|
|
13338
13373
|
try {
|
|
@@ -13342,8 +13377,68 @@ async function watchOwnWorkflowRuns(deps, repo, targets, since, headSha) {
|
|
|
13342
13377
|
workflowRuns.push({ workflow: target.workflow, conclusion: "failure" });
|
|
13343
13378
|
}
|
|
13344
13379
|
}
|
|
13380
|
+
if (!enumerateSha) return workflowRuns;
|
|
13381
|
+
const seen = new Set(workflowRuns.map((r) => r.runId).filter((id) => typeof id === "number"));
|
|
13382
|
+
workflowRuns.push(...await discoverShaWorkflowRuns(deps, repo, headSha, seen));
|
|
13345
13383
|
return workflowRuns;
|
|
13346
13384
|
}
|
|
13385
|
+
var NON_DEPLOY_EVENTS = /* @__PURE__ */ new Set([
|
|
13386
|
+
"pull_request",
|
|
13387
|
+
"pull_request_target",
|
|
13388
|
+
"pull_request_review",
|
|
13389
|
+
"pull_request_review_comment",
|
|
13390
|
+
"schedule",
|
|
13391
|
+
"issues",
|
|
13392
|
+
"issue_comment",
|
|
13393
|
+
"discussion",
|
|
13394
|
+
"discussion_comment",
|
|
13395
|
+
"fork",
|
|
13396
|
+
"watch",
|
|
13397
|
+
"star",
|
|
13398
|
+
"public",
|
|
13399
|
+
"gollum",
|
|
13400
|
+
"create",
|
|
13401
|
+
"delete"
|
|
13402
|
+
]);
|
|
13403
|
+
var SHA_ENUM_LIMIT = 50;
|
|
13404
|
+
var NON_FAILING_CONCLUSIONS = /* @__PURE__ */ new Set(["success", "skipped", "neutral"]);
|
|
13405
|
+
async function discoverShaWorkflowRuns(deps, repo, headSha, seenRunIds) {
|
|
13406
|
+
const marker = `sha-enumeration(${headSha.slice(0, 7)})`;
|
|
13407
|
+
let rows;
|
|
13408
|
+
try {
|
|
13409
|
+
rows = JSON.parse(await deps.run("gh", [
|
|
13410
|
+
"run",
|
|
13411
|
+
"list",
|
|
13412
|
+
"--repo",
|
|
13413
|
+
repo,
|
|
13414
|
+
"--commit",
|
|
13415
|
+
headSha,
|
|
13416
|
+
"--limit",
|
|
13417
|
+
String(SHA_ENUM_LIMIT),
|
|
13418
|
+
"--json",
|
|
13419
|
+
"databaseId,url,event,status,conclusion,workflowName"
|
|
13420
|
+
]));
|
|
13421
|
+
if (!Array.isArray(rows)) throw new Error("run list did not return an array");
|
|
13422
|
+
} catch {
|
|
13423
|
+
return [{ workflow: marker, conclusion: "pending" }];
|
|
13424
|
+
}
|
|
13425
|
+
const extra = [];
|
|
13426
|
+
if (rows.length >= SHA_ENUM_LIMIT) extra.push({ workflow: `${marker} truncated`, conclusion: "pending" });
|
|
13427
|
+
for (const row of rows) {
|
|
13428
|
+
if (typeof row.databaseId !== "number" || seenRunIds.has(row.databaseId)) continue;
|
|
13429
|
+
if (NON_DEPLOY_EVENTS.has(row.event ?? "")) continue;
|
|
13430
|
+
extra.push({
|
|
13431
|
+
workflow: row.workflowName ?? `run:${row.databaseId}`,
|
|
13432
|
+
runId: row.databaseId,
|
|
13433
|
+
runUrl: row.url,
|
|
13434
|
+
// An in-flight run is reported UNVERIFIED, never watched. Blocking on an arbitrary discovered run
|
|
13435
|
+
// would let one long push workflow hang the release command and mask the failed rows behind it, and
|
|
13436
|
+
// `gh run watch` has no timeout. The NAMED targets are the ones worth blocking on (also #3382 review).
|
|
13437
|
+
conclusion: row.status === "completed" ? NON_FAILING_CONCLUSIONS.has(row.conclusion ?? "") ? "success" : "failure" : "pending"
|
|
13438
|
+
});
|
|
13439
|
+
}
|
|
13440
|
+
return extra;
|
|
13441
|
+
}
|
|
13347
13442
|
async function dispatchDeploy(deps, ctx, stage, ref, model, watch, autoRunSince, autoRunHeadSha, dispatchFailure = "throw", publishDir) {
|
|
13348
13443
|
if (model === "tenant-container" || model === "solo-container") {
|
|
13349
13444
|
const since = (deps.now ?? Date.now)();
|
|
@@ -13370,7 +13465,8 @@ async function dispatchDeploy(deps, ctx, stage, ref, model, watch, autoRunSince,
|
|
|
13370
13465
|
ctx.repo,
|
|
13371
13466
|
[{ workflow: "publish.yml", event: "release" }],
|
|
13372
13467
|
since,
|
|
13373
|
-
autoRunHeadSha
|
|
13468
|
+
autoRunHeadSha,
|
|
13469
|
+
true
|
|
13374
13470
|
);
|
|
13375
13471
|
const primary = workflowRuns[0];
|
|
13376
13472
|
return { note, runId: primary?.runId, runUrl: primary?.runUrl, workflowRuns, deployStatus: aggregateWorkflowRuns(workflowRuns) };
|
|
@@ -13384,7 +13480,7 @@ async function dispatchDeploy(deps, ctx, stage, ref, model, watch, autoRunSince,
|
|
|
13384
13480
|
{ workflow: "deploy.yml", event: "release" },
|
|
13385
13481
|
{ workflow: "publish.yml", event: "release" }
|
|
13386
13482
|
];
|
|
13387
|
-
const workflowRuns = await watchOwnWorkflowRuns(deps, HUB_REPO3, targets, since, autoRunHeadSha);
|
|
13483
|
+
const workflowRuns = await watchOwnWorkflowRuns(deps, HUB_REPO3, targets, since, autoRunHeadSha, ref !== "rc");
|
|
13388
13484
|
const primary = workflowRuns[0];
|
|
13389
13485
|
return {
|
|
13390
13486
|
note,
|
|
@@ -15336,11 +15432,15 @@ function renderDocsIndex(entries) {
|
|
|
15336
15432
|
}
|
|
15337
15433
|
return lines.join("\n") + "\n";
|
|
15338
15434
|
}
|
|
15435
|
+
function sameContent(a, b) {
|
|
15436
|
+
const normalize = (text) => text.replace(/\r\n/g, "\n");
|
|
15437
|
+
return normalize(a) === normalize(b);
|
|
15438
|
+
}
|
|
15339
15439
|
function docsIndex(deps, opts = {}) {
|
|
15340
15440
|
const entries = deps.listDocs().slice().sort().map((relPath) => extractDocMeta(relPath, deps.readDoc(relPath)));
|
|
15341
15441
|
const content = renderDocsIndex(entries);
|
|
15342
15442
|
const current = deps.readIndex();
|
|
15343
|
-
const drift = current
|
|
15443
|
+
const drift = current === null || !sameContent(current, content);
|
|
15344
15444
|
let wrote = false;
|
|
15345
15445
|
if (!opts.check && drift) {
|
|
15346
15446
|
deps.writeIndex(content);
|
|
@@ -15386,7 +15486,7 @@ var LINK_RE = /\]\(([^)#\s]+?\.md)(?:#[^)]*)?\)/g;
|
|
|
15386
15486
|
var CMD_RE = /`mmi-cli ((?:[a-z][a-z-]*)(?: [a-z][a-z-]*){0,3})/g;
|
|
15387
15487
|
var RETIRED_RE = /\b(retired|historical|removed|deleted|gone|no longer|superseded|legacy)\b/i;
|
|
15388
15488
|
var ROOT_DOCS = ["README.md", "architecture.md"];
|
|
15389
|
-
var SKIP_WALK = ["docs/Archive/", "docs/incidents/"];
|
|
15489
|
+
var SKIP_WALK = ["docs/Archive/", "docs/incidents/", "docs/research/"];
|
|
15390
15490
|
function stripFences(markdown) {
|
|
15391
15491
|
let inFence = false;
|
|
15392
15492
|
return markdown.split(/\r?\n/).map((line) => {
|
|
@@ -15487,13 +15587,18 @@ function checkRefs(root, deps, docs2) {
|
|
|
15487
15587
|
const docDir = import_node_path16.posix.dirname(doc);
|
|
15488
15588
|
for (const { target, line } of extractLinks(markdown)) {
|
|
15489
15589
|
const resolved = import_node_path16.posix.normalize(import_node_path16.posix.join(docDir === "." ? "" : docDir, target));
|
|
15590
|
+
if (resolved.startsWith("..")) {
|
|
15591
|
+
candidates.push({ kind: "missing-link", doc, line, detail: target, escapesRoot: true });
|
|
15592
|
+
continue;
|
|
15593
|
+
}
|
|
15490
15594
|
if (!exists((0, import_node_path16.join)(root, resolved))) {
|
|
15491
15595
|
candidates.push({ kind: "missing-link", doc, line, detail: target, resolved });
|
|
15492
15596
|
}
|
|
15493
15597
|
}
|
|
15494
15598
|
}
|
|
15495
|
-
const
|
|
15496
|
-
const
|
|
15599
|
+
const queryable = candidates.filter((c) => !c.escapesRoot);
|
|
15600
|
+
const ignored = queryable.length ? isIgnored(queryable.map((c) => c.kind === "missing-link" ? c.resolved : c.detail)) : /* @__PURE__ */ new Set();
|
|
15601
|
+
const findings = candidates.filter((c) => c.escapesRoot || !ignored.has(c.kind === "missing-link" ? c.resolved : c.detail)).map(({ resolved: _resolved, escapesRoot: _escapesRoot, ...finding }) => finding);
|
|
15497
15602
|
return { ok: findings.length === 0, findings };
|
|
15498
15603
|
}
|
|
15499
15604
|
function checkCommands(docs2, commandPaths) {
|
|
@@ -15538,10 +15643,12 @@ function defaultListDocs(root) {
|
|
|
15538
15643
|
return [...ROOT_DOCS.filter((rel) => (0, import_node_fs18.existsSync)((0, import_node_path16.join)(root, rel))), ...docs2];
|
|
15539
15644
|
}
|
|
15540
15645
|
function defaultIsIgnored(root, relPaths, exec = import_node_child_process9.execFileSync) {
|
|
15646
|
+
const inRepo = relPaths.filter((p) => !p.startsWith(".."));
|
|
15647
|
+
if (inRepo.length === 0) return /* @__PURE__ */ new Set();
|
|
15541
15648
|
try {
|
|
15542
15649
|
const out = exec("git", ["check-ignore", "--stdin"], {
|
|
15543
15650
|
cwd: root,
|
|
15544
|
-
input:
|
|
15651
|
+
input: inRepo.join("\n"),
|
|
15545
15652
|
encoding: "utf8"
|
|
15546
15653
|
});
|
|
15547
15654
|
return new Set(out.split(/\r?\n/).filter(Boolean));
|
|
@@ -17027,6 +17134,9 @@ function checkGithubPools(probe) {
|
|
|
17027
17134
|
return lines;
|
|
17028
17135
|
}
|
|
17029
17136
|
|
|
17137
|
+
// src/box-commands.ts
|
|
17138
|
+
var import_node_fs20 = require("node:fs");
|
|
17139
|
+
|
|
17030
17140
|
// src/box.ts
|
|
17031
17141
|
var BOX_KEYS = {
|
|
17032
17142
|
/** Opens every MM box INCLUDING the CI runner (`mmi-runner`). The general-purpose MM key. */
|
|
@@ -17128,6 +17238,12 @@ function formatSshRecipe(box) {
|
|
|
17128
17238
|
`ssh -i "$kf" -o StrictHostKeyChecking=accept-new root@${box.address} 'hostname; uptime'`
|
|
17129
17239
|
].join("\n");
|
|
17130
17240
|
}
|
|
17241
|
+
var SSH_RECIPE_AGENT_NOTE = "# agent callers: this is multi-line, so running it inline is denied (#1473/#2125).\n# Use `mmi-cli runtime box get <name> --ssh --script <path>` to write it to a file, then `bash <path>`.";
|
|
17242
|
+
function sshRecipeScript(box) {
|
|
17243
|
+
return `#!/usr/bin/env bash
|
|
17244
|
+
${formatSshRecipe(box)}
|
|
17245
|
+
`;
|
|
17246
|
+
}
|
|
17131
17247
|
|
|
17132
17248
|
// src/box-commands.ts
|
|
17133
17249
|
var HETZNER_API = "https://api.hetzner.cloud/v1/servers";
|
|
@@ -17200,7 +17316,7 @@ function registerBoxCommands(program3) {
|
|
|
17200
17316
|
await failGraceful(e.message);
|
|
17201
17317
|
}
|
|
17202
17318
|
});
|
|
17203
|
-
box.command("get <name>").description("one box by name \u2014 its address and ssh key path; --ssh prints a ready-to-run connect recipe").option("--json", "machine-readable output").option("--ssh", "print a copy-paste ssh recipe (materializes the key to a temp file; never echoes it)").action(async (name, o) => {
|
|
17319
|
+
box.command("get <name>").description("one box by name \u2014 its address and ssh key path; --ssh prints a ready-to-run connect recipe").option("--json", "machine-readable output").option("--ssh", "print a copy-paste ssh recipe (materializes the key to a temp file; never echoes it)").option("--script <path>", "with --ssh: write the recipe to <path> and print the one line to run \u2014 the runnable form for agent callers, whose transport denies a multi-line inline body (#3385)").action(async (name, o) => {
|
|
17204
17320
|
try {
|
|
17205
17321
|
const { boxes, incomplete } = await fetchInventory();
|
|
17206
17322
|
const result = lookupBox(name, boxes, incomplete);
|
|
@@ -17216,8 +17332,16 @@ function registerBoxCommands(program3) {
|
|
|
17216
17332
|
return;
|
|
17217
17333
|
}
|
|
17218
17334
|
const found = result.box;
|
|
17335
|
+
if (o.script && !o.ssh) {
|
|
17336
|
+
await failGraceful("runtime box get: --script requires --ssh (it writes the ssh connect recipe)");
|
|
17337
|
+
return;
|
|
17338
|
+
}
|
|
17219
17339
|
if (o.json) console.log(JSON.stringify({ box: found, incomplete }, null, 2));
|
|
17220
|
-
else if (o.ssh
|
|
17340
|
+
else if (o.ssh && o.script) {
|
|
17341
|
+
(0, import_node_fs20.writeFileSync)(o.script, sshRecipeScript(found), "utf8");
|
|
17342
|
+
console.log(`wrote ${o.script} \u2014 run: bash "${o.script}"`);
|
|
17343
|
+
} else if (o.ssh) console.log(`${formatSshRecipe(found)}
|
|
17344
|
+
${SSH_RECIPE_AGENT_NOTE}`);
|
|
17221
17345
|
else console.log(formatBoxTable([found]));
|
|
17222
17346
|
if (!o.json) warnIncomplete(incomplete);
|
|
17223
17347
|
if (incomplete.length) process.exitCode = 1;
|
|
@@ -17237,7 +17361,7 @@ var ORG = "mutmutco";
|
|
|
17237
17361
|
var DOC_START_MARKER = "<!-- schedules:inventory:start -->";
|
|
17238
17362
|
var DOC_END_MARKER = "<!-- schedules:inventory:end -->";
|
|
17239
17363
|
function isJervResource(name) {
|
|
17240
|
-
return /^jerv/i.test(name);
|
|
17364
|
+
return /^jerv-memory(?:-|$)/i.test(name);
|
|
17241
17365
|
}
|
|
17242
17366
|
function llmFromHeader(yamlText) {
|
|
17243
17367
|
const m = /^#\s*llm:\s*(.+)$/im.exec(yamlText);
|
|
@@ -17344,6 +17468,11 @@ function awsScheduleRefs(payload) {
|
|
|
17344
17468
|
}
|
|
17345
17469
|
var DECLARED_ENTRIES = [
|
|
17346
17470
|
{ name: "mmi-backup (mm-central, mmi-fofu, zuber, mmi-oguz)", cadence: "17 2 * * *", executor: "box cron.d", llm: "no", resolved: "declared", source: "/etc/cron.d/mmi-backup \u2192 /opt/mmi-control/nightly-backup.sh (verify: mmi-cli runtime box get <box> --ssh)" },
|
|
17471
|
+
// #3370: the floor under runner-hygiene.yml. That workflow runs ON the runner and needs checkout +
|
|
17472
|
+
// setup-node before it can prune, so at 100% it dies at checkout and never prunes — the cleanup is
|
|
17473
|
+
// unavailable exactly when it is needed. This one is root cron on the box: no checkout, no node, and
|
|
17474
|
+
// a no-op below 90% disk, which is why 15 minutes is affordable.
|
|
17475
|
+
{ name: "mmi-runner-hygiene-oob (mmi-runner)", cadence: "*/15 * * * *", executor: "box cron.d", llm: "no", resolved: "declared", source: "/etc/cron.d/mmi-runner-hygiene \u2192 /opt/mmi-control/runner-hygiene-oob.sh; acts only at >=90% disk (verify: mmi-cli runtime box get mmi-runner --ssh)" },
|
|
17347
17476
|
{ name: "zuber-bake", cadence: "*:05/30 (every 30 min)", executor: "zuber systemd timer", llm: "no", resolved: "declared", source: "zuber-bake.timer \u2192 rolling overlay bake, metro tier, next ~6h window (verify over ssh)" },
|
|
17348
17477
|
{ name: "zuber-bake-full", cadence: "00:30 UTC (03:30 TRT, daily)", executor: "zuber systemd timer", llm: "no", resolved: "declared", source: "zuber-bake-full.timer \u2192 full 24h overlay rebuild, all 81 il (verify over ssh)" }
|
|
17349
17478
|
];
|
|
@@ -17938,7 +18067,7 @@ function withArmingReport(body) {
|
|
|
17938
18067
|
function registerSchedulesLiftCommand(program3, deps = {}) {
|
|
17939
18068
|
const schedules = program3.commands.find((c) => c.name() === "schedules");
|
|
17940
18069
|
if (!schedules) throw new Error("schedules lift: registerSchedulesCommands must run first \u2014 the lift attaches to the `schedules` command");
|
|
17941
|
-
schedules.command("register").alias("lift").description(`register this repo's eight-field workflow headers as SCHEDULE# registry rows (C2, #3186; renamed from \`lift\`, #3219) \u2014 a per-repo replace; aborts the whole registration on any header violation; exits 75 when the registry is unreachable (#3187). Registering does NOT arm the clock: the fleet-clock reconciler arms the rows within ${FLEET_CLOCK_RECONCILE_WINDOW}, and the success JSON carries an \`arming\` block saying so (#3281)`).option("--repo <name>", "bare repo name for the join key (defaults to the origin remote basename, case-preserved)").option("--dir <path>", "workflows directory to lift (defaults to .github/workflows)").option("--dry-run", "print the {repo, schedules} body that would be POSTed; never write").action(async (o) => {
|
|
18070
|
+
jsonParity(schedules.command("register").alias("lift").description(`register this repo's eight-field workflow headers as SCHEDULE# registry rows (C2, #3186; renamed from \`lift\`, #3219) \u2014 a per-repo replace; aborts the whole registration on any header violation; exits 75 when the registry is unreachable (#3187). Registering does NOT arm the clock: the fleet-clock reconciler arms the rows within ${FLEET_CLOCK_RECONCILE_WINDOW}, and the success JSON carries an \`arming\` block saying so (#3281)`).option("--repo <name>", "bare repo name for the join key (defaults to the origin remote basename, case-preserved)").option("--dir <path>", "workflows directory to lift (defaults to .github/workflows)").option("--dry-run", "print the {repo, schedules} body that would be POSTed; never write")).action(async (o) => {
|
|
17942
18071
|
try {
|
|
17943
18072
|
const result = await runSchedulesLift({ repo: o.repo, dir: o.dir, dryRun: Boolean(o.dryRun) }, deps);
|
|
17944
18073
|
if (o.dryRun) {
|
|
@@ -18417,7 +18546,7 @@ function registerQueryCommands(program3) {
|
|
|
18417
18546
|
}
|
|
18418
18547
|
|
|
18419
18548
|
// src/bootstrap-commands.ts
|
|
18420
|
-
var
|
|
18549
|
+
var import_node_fs21 = require("node:fs");
|
|
18421
18550
|
|
|
18422
18551
|
// src/bootstrap-verify.ts
|
|
18423
18552
|
var TRAIN_BRANCHES2 = ["development", "rc", "main"];
|
|
@@ -18965,7 +19094,7 @@ function registerBootstrapCommands(program3) {
|
|
|
18965
19094
|
client: defaultGitHubClient(),
|
|
18966
19095
|
projectMeta: meta,
|
|
18967
19096
|
deployModel: typeof meta?.deployModel === "string" ? meta.deployModel : void 0,
|
|
18968
|
-
readLocalFile: (path2) => path2 === "projects.json" && apiProjects != null ? apiProjects : (0,
|
|
19097
|
+
readLocalFile: (path2) => path2 === "projects.json" && apiProjects != null ? apiProjects : (0, import_node_fs21.existsSync)(path2) ? (0, import_node_fs21.readFileSync)(path2, "utf8") : null,
|
|
18969
19098
|
// requiredGcpApis is stored as an array by a JSON write, but `org project set --var KEY=VALUE` stores a raw
|
|
18970
19099
|
// comma-string — accept either so the seeded value verifies regardless of how it was written.
|
|
18971
19100
|
requiredGcpApis: (() => {
|
|
@@ -19016,12 +19145,12 @@ function registerBootstrapCommands(program3) {
|
|
|
19016
19145
|
return fail(`bootstrap apply: ${e.message}`);
|
|
19017
19146
|
}
|
|
19018
19147
|
const manifestPath = "skills/bootstrap/seeds/manifest.json";
|
|
19019
|
-
if (!(0,
|
|
19020
|
-
const manifest = loadBootstrapSeeds((0,
|
|
19148
|
+
if (!(0, import_node_fs21.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`);
|
|
19149
|
+
const manifest = loadBootstrapSeeds((0, import_node_fs21.readFileSync)(manifestPath, "utf8"));
|
|
19021
19150
|
const baseBranch = o.class === "content" ? "main" : "development";
|
|
19022
19151
|
const slug = parsedRepo.slug;
|
|
19023
19152
|
const gh = async (args) => execFileP2("gh", args, { timeout: 2e4 });
|
|
19024
|
-
const readFile6 = (p) => (0,
|
|
19153
|
+
const readFile6 = (p) => (0, import_node_fs21.existsSync)(p) ? (0, import_node_fs21.readFileSync)(p, "utf8") : null;
|
|
19025
19154
|
const enc = (p) => p.split("/").map(encodeURIComponent).join("/");
|
|
19026
19155
|
const rawVars = {};
|
|
19027
19156
|
for (const value of cmdOpts.var ?? []) {
|
|
@@ -19217,11 +19346,11 @@ LIVE apply to ${repo}:
|
|
|
19217
19346
|
}
|
|
19218
19347
|
|
|
19219
19348
|
// src/stage-commands.ts
|
|
19220
|
-
var
|
|
19349
|
+
var import_node_fs23 = require("node:fs");
|
|
19221
19350
|
var import_node_path20 = require("node:path");
|
|
19222
19351
|
|
|
19223
19352
|
// src/port-registry.ts
|
|
19224
|
-
var
|
|
19353
|
+
var import_node_fs22 = require("node:fs");
|
|
19225
19354
|
var import_node_path19 = require("node:path");
|
|
19226
19355
|
|
|
19227
19356
|
// ../infra/port-geometry.mjs
|
|
@@ -19236,8 +19365,8 @@ function nextPortBlock(registry2) {
|
|
|
19236
19365
|
return [base, base + PORT_SPAN];
|
|
19237
19366
|
}
|
|
19238
19367
|
function loadPortRegistry(path2) {
|
|
19239
|
-
if (!(0,
|
|
19240
|
-
const raw = JSON.parse((0,
|
|
19368
|
+
if (!(0, import_node_fs22.existsSync)(path2)) return {};
|
|
19369
|
+
const raw = JSON.parse((0, import_node_fs22.readFileSync)(path2, "utf8"));
|
|
19241
19370
|
const out = {};
|
|
19242
19371
|
for (const [key, value] of Object.entries(raw)) {
|
|
19243
19372
|
if (Array.isArray(value) && value.length === 2 && value.every((n) => typeof n === "number")) {
|
|
@@ -19251,9 +19380,9 @@ function ensurePortRange(repo, path2) {
|
|
|
19251
19380
|
const existing = registry2[repo];
|
|
19252
19381
|
if (existing) return existing;
|
|
19253
19382
|
const range = nextPortBlock(registry2);
|
|
19254
|
-
const raw = (0,
|
|
19383
|
+
const raw = (0, import_node_fs22.existsSync)(path2) ? JSON.parse((0, import_node_fs22.readFileSync)(path2, "utf8")) : {};
|
|
19255
19384
|
raw[repo] = range;
|
|
19256
|
-
(0,
|
|
19385
|
+
(0, import_node_fs22.writeFileSync)(path2, JSON.stringify(raw, null, 2) + "\n", "utf8");
|
|
19257
19386
|
return range;
|
|
19258
19387
|
}
|
|
19259
19388
|
function portCursorSeed(registry2) {
|
|
@@ -19277,7 +19406,7 @@ function existingPortRange(repo, registry2) {
|
|
|
19277
19406
|
function portRangeInfraAt(root, source) {
|
|
19278
19407
|
const registryPath = (0, import_node_path19.join)(root, "infra", "port-ranges.json");
|
|
19279
19408
|
const ddbScriptPath = (0, import_node_path19.join)(root, "infra", "port-ddb.mjs");
|
|
19280
|
-
if (!(0,
|
|
19409
|
+
if (!(0, import_node_fs22.existsSync)(registryPath) || !(0, import_node_fs22.existsSync)(ddbScriptPath)) return null;
|
|
19281
19410
|
return { root, source, registryPath, ddbScriptPath };
|
|
19282
19411
|
}
|
|
19283
19412
|
function resolvePortRangeInfra(cwd, packageDir) {
|
|
@@ -19466,8 +19595,8 @@ function registerStageCommands(program3) {
|
|
|
19466
19595
|
const portRange = portRangeMeta && typeof portRangeMeta.start === "number" && typeof portRangeMeta.end === "number" ? [portRangeMeta.start, portRangeMeta.end] : void 0;
|
|
19467
19596
|
return decideStage({
|
|
19468
19597
|
registry: { deployModel: project2?.deployModel, portRange, error: read.ok ? void 0 : read.error },
|
|
19469
|
-
hasCompose: (0,
|
|
19470
|
-
hasEnvExample: (0,
|
|
19598
|
+
hasCompose: (0, import_node_fs23.existsSync)((0, import_node_path20.join)(process.cwd(), "docker-compose.yml")),
|
|
19599
|
+
hasEnvExample: (0, import_node_fs23.existsSync)((0, import_node_path20.join)(process.cwd(), ".env.example"))
|
|
19471
19600
|
});
|
|
19472
19601
|
}
|
|
19473
19602
|
async function fetchStageVaultEnvMerge() {
|
|
@@ -19902,7 +20031,7 @@ function registerBoardCommands(program3) {
|
|
|
19902
20031
|
}
|
|
19903
20032
|
|
|
19904
20033
|
// src/merge-cleanup.ts
|
|
19905
|
-
var
|
|
20034
|
+
var import_node_fs24 = require("node:fs");
|
|
19906
20035
|
var import_promises5 = require("node:fs/promises");
|
|
19907
20036
|
var import_node_child_process11 = require("node:child_process");
|
|
19908
20037
|
|
|
@@ -20226,7 +20355,7 @@ async function applyGcPlan(plan, remote) {
|
|
|
20226
20355
|
return cleanupPrMergeLocalBranch(branch.branch, {
|
|
20227
20356
|
beforeWorktrees,
|
|
20228
20357
|
startingPath: branch.worktreePath,
|
|
20229
|
-
pathExists: (p) => (0,
|
|
20358
|
+
pathExists: (p) => (0, import_node_fs24.existsSync)(p),
|
|
20230
20359
|
execGit: async (args) => (await execFileP2("git", args, { timeout: GIT_TIMEOUT_MS })).stdout,
|
|
20231
20360
|
teardownWorktreeStage,
|
|
20232
20361
|
deferredStore,
|
|
@@ -20252,7 +20381,7 @@ async function applyGcPlan(plan, remote) {
|
|
|
20252
20381
|
for (const wt of plan.worktreeDirs) {
|
|
20253
20382
|
try {
|
|
20254
20383
|
const cleanupTarget = resolveSafeSiblingWorktreeCleanupTarget(wt.path, siblingRoot, {
|
|
20255
|
-
realpath: (path2) => (0,
|
|
20384
|
+
realpath: (path2) => (0, import_node_fs24.realpathSync)(path2)
|
|
20256
20385
|
});
|
|
20257
20386
|
if (!cleanupTarget.ok) {
|
|
20258
20387
|
result.failed.push(`${wt.path}: ${cleanupTarget.reason}`);
|
|
@@ -20379,13 +20508,13 @@ var realWorktreeDirRemover = {
|
|
|
20379
20508
|
probe: (p) => {
|
|
20380
20509
|
let st;
|
|
20381
20510
|
try {
|
|
20382
|
-
st = (0,
|
|
20511
|
+
st = (0, import_node_fs24.lstatSync)(p);
|
|
20383
20512
|
} catch {
|
|
20384
20513
|
return null;
|
|
20385
20514
|
}
|
|
20386
20515
|
if (st.isSymbolicLink()) return "link";
|
|
20387
20516
|
try {
|
|
20388
|
-
(0,
|
|
20517
|
+
(0, import_node_fs24.readlinkSync)(p);
|
|
20389
20518
|
return "link";
|
|
20390
20519
|
} catch {
|
|
20391
20520
|
}
|
|
@@ -20393,7 +20522,7 @@ var realWorktreeDirRemover = {
|
|
|
20393
20522
|
},
|
|
20394
20523
|
readdir: (p) => {
|
|
20395
20524
|
try {
|
|
20396
|
-
return (0,
|
|
20525
|
+
return (0, import_node_fs24.readdirSync)(p);
|
|
20397
20526
|
} catch {
|
|
20398
20527
|
return [];
|
|
20399
20528
|
}
|
|
@@ -20402,9 +20531,9 @@ var realWorktreeDirRemover = {
|
|
|
20402
20531
|
// leaving the target); a file symlink with unlink. rmdir first, fall back to unlink.
|
|
20403
20532
|
detachLink: (p) => {
|
|
20404
20533
|
try {
|
|
20405
|
-
(0,
|
|
20534
|
+
(0, import_node_fs24.rmdirSync)(p);
|
|
20406
20535
|
} catch {
|
|
20407
|
-
(0,
|
|
20536
|
+
(0, import_node_fs24.unlinkSync)(p);
|
|
20408
20537
|
}
|
|
20409
20538
|
},
|
|
20410
20539
|
removeTree: (p) => (0, import_promises5.rm)(p, { recursive: true, force: true, maxRetries: 5, retryDelay: 200 })
|
|
@@ -20437,9 +20566,9 @@ async function worktreeHasStageState(worktreePath) {
|
|
|
20437
20566
|
}
|
|
20438
20567
|
}
|
|
20439
20568
|
function stageStateFileBelongsToWorktree(statePath, worktreePath) {
|
|
20440
|
-
if (!(0,
|
|
20569
|
+
if (!(0, import_node_fs24.existsSync)(statePath)) return false;
|
|
20441
20570
|
try {
|
|
20442
|
-
const state = JSON.parse((0,
|
|
20571
|
+
const state = JSON.parse((0, import_node_fs24.readFileSync)(statePath, "utf8"));
|
|
20443
20572
|
const recordedCwd = typeof state.identity?.cwd === "string" ? state.identity.cwd : typeof state.cwd === "string" ? state.cwd : "";
|
|
20444
20573
|
return Boolean(recordedCwd && isPathUnderDirectory(recordedCwd, worktreePath));
|
|
20445
20574
|
} catch {
|
|
@@ -20528,12 +20657,15 @@ function parseRestPrSnapshot(json) {
|
|
|
20528
20657
|
async function fetchRestPrSnapshot(prNumber, repo, gh = defaultGhApi) {
|
|
20529
20658
|
return parseRestPrSnapshot(JSON.parse(await gh([`repos/${repo}/pulls/${prNumber}`])));
|
|
20530
20659
|
}
|
|
20660
|
+
async function fetchHeadCheckRuns(headSha, repo, gh) {
|
|
20661
|
+
const runsOut = await gh(["--paginate", `repos/${repo}/commits/${headSha}/check-runs?per_page=100`, "--jq", ".check_runs[] | {id, name, status, conclusion, app_id: .app.id}"]);
|
|
20662
|
+
return dedupeLatestCheckRuns(parseNdjsonLines(runsOut));
|
|
20663
|
+
}
|
|
20531
20664
|
async function fetchHeadCheckBuckets(headSha, repo, gh) {
|
|
20532
|
-
const [
|
|
20533
|
-
|
|
20665
|
+
const [runs, statusesOut] = await Promise.all([
|
|
20666
|
+
fetchHeadCheckRuns(headSha, repo, gh),
|
|
20534
20667
|
gh(["--paginate", `repos/${repo}/commits/${headSha}/status?per_page=100`, "--jq", ".statuses[] | {context, state}"])
|
|
20535
20668
|
]);
|
|
20536
|
-
const runs = dedupeLatestCheckRuns(parseNdjsonLines(runsOut));
|
|
20537
20669
|
const statuses = parseNdjsonLines(statusesOut);
|
|
20538
20670
|
return [...runs.map(classifyCheckRun), ...statuses.map((s) => classifyCommitStatus(s.state))];
|
|
20539
20671
|
}
|
|
@@ -20555,6 +20687,48 @@ async function pollRestPrMerged(prNumber, repo, gh = defaultGhApi) {
|
|
|
20555
20687
|
return false;
|
|
20556
20688
|
}
|
|
20557
20689
|
}
|
|
20690
|
+
var CI_BUDGET_ANNOTATION_TITLE = "CI budget exceeded";
|
|
20691
|
+
function isErrorAnnotation(a) {
|
|
20692
|
+
const level = a.annotation_level?.toLowerCase();
|
|
20693
|
+
return level === "failure" || level === "error";
|
|
20694
|
+
}
|
|
20695
|
+
function classifyFailedChecks(failing) {
|
|
20696
|
+
const budgetKilled = [];
|
|
20697
|
+
const otherFailures = [];
|
|
20698
|
+
for (const check of failing) {
|
|
20699
|
+
const errors = check.annotations.filter(isErrorAnnotation);
|
|
20700
|
+
const allBudget = errors.length > 0 && errors.every((a) => a.title?.trim() === CI_BUDGET_ANNOTATION_TITLE);
|
|
20701
|
+
(allBudget ? budgetKilled : otherFailures).push(check.name);
|
|
20702
|
+
}
|
|
20703
|
+
if (budgetKilled.length && !otherFailures.length) {
|
|
20704
|
+
return {
|
|
20705
|
+
cause: "ci-budget-kill",
|
|
20706
|
+
budgetKilled,
|
|
20707
|
+
otherFailures,
|
|
20708
|
+
reason: `${budgetKilled.length} check(s) were KILLED for wall-clock, not failed: ${budgetKilled.join(", ")}. No test failed \u2014 this is runner contention (concurrent gates sharing one runner). Rerun once the other gates drain (gh run rerun --failed); do not debug the diff.`
|
|
20709
|
+
};
|
|
20710
|
+
}
|
|
20711
|
+
return {
|
|
20712
|
+
cause: "checks-failure",
|
|
20713
|
+
budgetKilled,
|
|
20714
|
+
otherFailures,
|
|
20715
|
+
reason: budgetKilled.length ? `checks failed: ${otherFailures.join(", ")}; separately, ${budgetKilled.join(", ")} was killed for wall-clock, not failed.` : `checks failed: ${otherFailures.join(", ") || "unknown"}.`
|
|
20716
|
+
};
|
|
20717
|
+
}
|
|
20718
|
+
async function diagnoseFailedRestChecks(prNumber, repo, gh = defaultGhApi) {
|
|
20719
|
+
try {
|
|
20720
|
+
const snapshot = await fetchRestPrSnapshot(prNumber, repo, gh);
|
|
20721
|
+
const failing = (await fetchHeadCheckRuns(snapshot.headSha, repo, gh)).filter((run) => classifyCheckRun(run) === "fail" && typeof run.id === "number");
|
|
20722
|
+
if (!failing.length) return null;
|
|
20723
|
+
const annotated = await Promise.all(failing.map(async (run) => ({
|
|
20724
|
+
name: run.name ?? `check-run ${run.id}`,
|
|
20725
|
+
annotations: JSON.parse(await gh([`repos/${repo}/check-runs/${run.id}/annotations`]))
|
|
20726
|
+
})));
|
|
20727
|
+
return classifyFailedChecks(annotated);
|
|
20728
|
+
} catch {
|
|
20729
|
+
return null;
|
|
20730
|
+
}
|
|
20731
|
+
}
|
|
20558
20732
|
async function fetchRestCorePool(gh = defaultGhApi) {
|
|
20559
20733
|
try {
|
|
20560
20734
|
const parsed = JSON.parse(await gh(["rate_limit"]));
|
|
@@ -20567,7 +20741,7 @@ async function fetchRestCorePool(gh = defaultGhApi) {
|
|
|
20567
20741
|
}
|
|
20568
20742
|
|
|
20569
20743
|
// src/worktree-lifecycle-commands.ts
|
|
20570
|
-
var
|
|
20744
|
+
var import_node_fs25 = require("node:fs");
|
|
20571
20745
|
var import_node_path22 = require("node:path");
|
|
20572
20746
|
var GH_TIMEOUT_MS = 2e4;
|
|
20573
20747
|
var DEFAULT_BASE = "origin/development";
|
|
@@ -20653,17 +20827,39 @@ function classifyStaleLeaks(input) {
|
|
|
20653
20827
|
}
|
|
20654
20828
|
const knownPaths = new Set(input.worktrees.map((w) => w.path));
|
|
20655
20829
|
for (const dir of input.orphanDirs) {
|
|
20656
|
-
if (
|
|
20657
|
-
|
|
20658
|
-
|
|
20659
|
-
|
|
20660
|
-
|
|
20661
|
-
|
|
20662
|
-
|
|
20663
|
-
|
|
20830
|
+
if (knownPaths.has(dir.path)) continue;
|
|
20831
|
+
leaks.push({
|
|
20832
|
+
kind: "orphan-dir",
|
|
20833
|
+
ref: dir.path,
|
|
20834
|
+
// Say only what the inspection proved. `orphaned-folder` proves the `.git` entry is gone while this
|
|
20835
|
+
// repo's worktree metadata still points at the directory — NOT that the directory is empty; gc
|
|
20836
|
+
// checks contents separately, at apply time.
|
|
20837
|
+
detail: dir.reason === "dangling-gitdir" ? "this repo's worktree directory whose git metadata is gone" : "this repo's worktree metadata points here, but the .git entry is gone",
|
|
20838
|
+
// gc --apply re-proves ownership before deleting anything; a raw `rm -rf` does not, so it is never
|
|
20839
|
+
// offered here — following it on a mis-classified dir would destroy another repo's live worktree.
|
|
20840
|
+
remediation: "mmi-cli worktree gc --apply"
|
|
20841
|
+
});
|
|
20664
20842
|
}
|
|
20665
20843
|
return leaks;
|
|
20666
20844
|
}
|
|
20845
|
+
var defaultOrphanDirScanDeps = {
|
|
20846
|
+
listDirs: (root) => {
|
|
20847
|
+
try {
|
|
20848
|
+
return (0, import_node_fs25.readdirSync)(root, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => (0, import_node_path22.join)(root, e.name));
|
|
20849
|
+
} catch {
|
|
20850
|
+
return [];
|
|
20851
|
+
}
|
|
20852
|
+
},
|
|
20853
|
+
inspect: inspectSiblingWorktreeDir
|
|
20854
|
+
};
|
|
20855
|
+
function scanOrphanDirs(worktreesRoot, worktreeGitRoot, deps = defaultOrphanDirScanDeps) {
|
|
20856
|
+
const candidates = [];
|
|
20857
|
+
for (const dir of deps.listDirs(worktreesRoot)) {
|
|
20858
|
+
const { cleanup } = classifySiblingWorktreeDir(deps.inspect(dir, worktreeGitRoot));
|
|
20859
|
+
if (cleanup) candidates.push({ path: dir, reason: cleanup.reason });
|
|
20860
|
+
}
|
|
20861
|
+
return candidates;
|
|
20862
|
+
}
|
|
20667
20863
|
function formatStaleLeaks(leaks) {
|
|
20668
20864
|
if (!leaks.length) return "worktree list --stale: no leaks found";
|
|
20669
20865
|
const lines = [`worktree list --stale: ${leaks.length} leak(s)`];
|
|
@@ -20706,7 +20902,7 @@ function registerWorktreeCommands(program3) {
|
|
|
20706
20902
|
return fail(`worktree land: refusing to land protected branch '${branch}'`, { code: ERROR_CODES.ERR_BAD_ENUM });
|
|
20707
20903
|
}
|
|
20708
20904
|
const gitFile = (0, import_node_path22.join)(wtPath, ".git");
|
|
20709
|
-
const isLinked = (0,
|
|
20905
|
+
const isLinked = (0, import_node_fs25.existsSync)(gitFile) && (0, import_node_fs25.statSync)(gitFile).isFile();
|
|
20710
20906
|
if (apply && !isLinked) {
|
|
20711
20907
|
return fail("worktree land: run from inside the linked worktree you want to land (this is the primary checkout)");
|
|
20712
20908
|
}
|
|
@@ -20795,7 +20991,7 @@ async function gatherWorktreeContext() {
|
|
|
20795
20991
|
const porcelain = (await execFileP2("git", ["worktree", "list", "--porcelain"], { timeout: GIT_TIMEOUT_MS }).catch(() => ({ stdout: "" }))).stdout;
|
|
20796
20992
|
const parsed = parseWorktreePorcelain(porcelain);
|
|
20797
20993
|
const primaryPath = parsed[0]?.path ?? repoRoot2;
|
|
20798
|
-
const worktrees = parsed.map((w) => ({ path: w.path, branch: w.branch, primary: w.path === primaryPath }));
|
|
20994
|
+
const worktrees = parsed.map((w) => ({ path: toNativePath(w.path), branch: w.branch, primary: w.path === primaryPath }));
|
|
20799
20995
|
const branchOut = (await execFileP2("git", ["branch", "--format=%(refname:short)"], { timeout: GIT_TIMEOUT_MS }).catch(() => ({ stdout: "" }))).stdout;
|
|
20800
20996
|
const localBranches = branchOut.split(/\r?\n/).map((l) => l.trim()).filter(Boolean);
|
|
20801
20997
|
const currentBranch2 = (await execFileP2("git", ["rev-parse", "--abbrev-ref", "HEAD"], { timeout: GIT_TIMEOUT_MS }).catch(() => ({ stdout: "" }))).stdout.trim() || void 0;
|
|
@@ -20821,18 +21017,10 @@ async function gatherWorktreeContext() {
|
|
|
20821
21017
|
const s = readStageSummary(wt.path);
|
|
20822
21018
|
if (s) stages.push({ path: wt.path, port: s.port });
|
|
20823
21019
|
}
|
|
20824
|
-
const orphanDirs = [];
|
|
20825
21020
|
const wtRoot = siblingMmiWorktreesRoot(repoRoot2);
|
|
20826
|
-
|
|
20827
|
-
|
|
20828
|
-
|
|
20829
|
-
entries = (0, import_node_fs24.readdirSync)(wtRoot, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => (0, import_node_path22.join)(wtRoot, e.name));
|
|
20830
|
-
} catch {
|
|
20831
|
-
}
|
|
20832
|
-
const known = new Set(worktrees.map((w) => w.path));
|
|
20833
|
-
for (const dir of entries) {
|
|
20834
|
-
if (!known.has(dir)) orphanDirs.push(dir);
|
|
20835
|
-
}
|
|
21021
|
+
let orphanDirs = [];
|
|
21022
|
+
if ((0, import_node_fs25.existsSync)(wtRoot)) {
|
|
21023
|
+
orphanDirs = scanOrphanDirs(wtRoot, await currentRepoWorktreeGitRoot(repoRoot2));
|
|
20836
21024
|
}
|
|
20837
21025
|
return { worktrees, localBranches, currentBranch: currentBranch2, openPrBranches, closedBranches, stages, orphanDirs };
|
|
20838
21026
|
}
|
|
@@ -20851,7 +21039,7 @@ async function bestEffortGit(args, cwd, step, timeoutMs = GIT_TIMEOUT_MS) {
|
|
|
20851
21039
|
}
|
|
20852
21040
|
|
|
20853
21041
|
// src/issue-commands.ts
|
|
20854
|
-
var
|
|
21042
|
+
var import_node_fs26 = require("node:fs");
|
|
20855
21043
|
var import_node_crypto5 = require("node:crypto");
|
|
20856
21044
|
var ghRunner = async (args, timeoutMs) => (await execFileP2("gh", args, { timeout: timeoutMs })).stdout;
|
|
20857
21045
|
async function editIssue(client, options, deps = {}) {
|
|
@@ -20865,7 +21053,7 @@ async function editIssue(client, options, deps = {}) {
|
|
|
20865
21053
|
if (options.body !== void 0 || options.bodyFile !== void 0) {
|
|
20866
21054
|
let body;
|
|
20867
21055
|
if (options.bodyFile) {
|
|
20868
|
-
const td = deps.textDeps ?? { readFile: (p, e) => Promise.resolve((0,
|
|
21056
|
+
const td = deps.textDeps ?? { readFile: (p, e) => Promise.resolve((0, import_node_fs26.readFileSync)(p, e)), readStdin: () => Promise.resolve("") };
|
|
20869
21057
|
body = await resolveIssueBody({ body: options.body, bodyFile: options.bodyFile }, td);
|
|
20870
21058
|
} else {
|
|
20871
21059
|
body = options.body ?? "";
|
|
@@ -21130,6 +21318,7 @@ function validateBatchSpecs(specs) {
|
|
|
21130
21318
|
async function createIssuesBatch(specs, options, deps = {}) {
|
|
21131
21319
|
const ensureLabels = deps.ensureLabels ?? ensureLabelsExist;
|
|
21132
21320
|
const client = deps.client ?? defaultGitHubClient();
|
|
21321
|
+
const runCreate = deps.runCreate ?? (async (args) => (await execFileP2("gh", args, { timeout: GH_MUTATION_TIMEOUT_MS })).stdout);
|
|
21133
21322
|
const validation = validateBatchSpecs(specs);
|
|
21134
21323
|
if (!validation.ok) {
|
|
21135
21324
|
const lines = validation.errors.map((e) => ` row ${e.row}: ${e.error}`).join("\n");
|
|
@@ -21166,20 +21355,33 @@ ${lines}`);
|
|
|
21166
21355
|
if (rowKey) {
|
|
21167
21356
|
body = appendIdempotencyMarker(body, rowKey);
|
|
21168
21357
|
}
|
|
21169
|
-
const
|
|
21358
|
+
const rowPriority = spec.priority ? priority : options.priority ?? priority;
|
|
21359
|
+
const args = buildIssueArgs({ type, title: spec.title, body, priority: rowPriority, repo: rowRepo(spec), labels: spec.labels });
|
|
21170
21360
|
const swapped = await bodyArgsViaFile(args);
|
|
21361
|
+
let row;
|
|
21171
21362
|
try {
|
|
21172
|
-
const
|
|
21363
|
+
const stdout = await runCreate(swapped.args);
|
|
21173
21364
|
const result = parseCreatedUrl(stdout);
|
|
21174
|
-
|
|
21365
|
+
row = { row: entry.row, number: result.number, url: result.url };
|
|
21366
|
+
created.push(row);
|
|
21175
21367
|
} finally {
|
|
21176
21368
|
await swapped.cleanup();
|
|
21177
21369
|
}
|
|
21178
|
-
if (
|
|
21370
|
+
if (deps.attach) {
|
|
21371
|
+
const attached = await deps.attach(row.number, rowRepo(spec), rowPriority);
|
|
21372
|
+
row.onBoard = attached.onBoard;
|
|
21373
|
+
if (attached.projectItemId) row.projectItemId = attached.projectItemId;
|
|
21374
|
+
}
|
|
21375
|
+
const parentRef = spec.parent ?? options.parent;
|
|
21376
|
+
if (parentRef) {
|
|
21179
21377
|
try {
|
|
21180
21378
|
const run = deps.runGh ?? ghRunner;
|
|
21181
|
-
await linkSubIssue(run,
|
|
21182
|
-
} catch {
|
|
21379
|
+
row.parent = await linkSubIssue(run, parentRef, row.url, rowRepo(spec));
|
|
21380
|
+
} catch (e) {
|
|
21381
|
+
const err = e;
|
|
21382
|
+
row.parentLinkError = (err.stderr || err.message || String(e)).trim();
|
|
21383
|
+
process.stderr.write(`warning: issue #${row.number} created but NOT linked under ${parentRef}: ${row.parentLinkError}
|
|
21384
|
+
`);
|
|
21183
21385
|
}
|
|
21184
21386
|
}
|
|
21185
21387
|
} catch (e) {
|
|
@@ -21212,7 +21414,7 @@ function parseCloseReason(raw) {
|
|
|
21212
21414
|
if (trimmed === "duplicateof" || trimmed === "duplicate") return { reason: "duplicate-of" };
|
|
21213
21415
|
throw new Error(`unknown close reason "${raw}" \u2014 expected completed, not-planned, or duplicate-of`);
|
|
21214
21416
|
}
|
|
21215
|
-
function registerIssueLifecycleCommands(program3) {
|
|
21417
|
+
function registerIssueLifecycleCommands(program3, deps = {}) {
|
|
21216
21418
|
const issue2 = program3.commands.find((c) => c.name() === "issue");
|
|
21217
21419
|
if (!issue2) return;
|
|
21218
21420
|
mutating(
|
|
@@ -21335,9 +21537,9 @@ function registerIssueLifecycleCommands(program3) {
|
|
|
21335
21537
|
return failGraceful(`issue unlink-child: ${(err.stderr || err.message || String(e)).trim()}${note ? ` (${note})` : ""}`);
|
|
21336
21538
|
}
|
|
21337
21539
|
});
|
|
21338
|
-
extendCreateCommand(issue2);
|
|
21540
|
+
extendCreateCommand(issue2, deps.attach);
|
|
21339
21541
|
}
|
|
21340
|
-
function extendCreateCommand(issue2) {
|
|
21542
|
+
function extendCreateCommand(issue2, batchAttach) {
|
|
21341
21543
|
const createCmd = issue2.commands.find((c) => c.name() === "create");
|
|
21342
21544
|
if (!createCmd) return;
|
|
21343
21545
|
createCmd.option("--batch <file.json>", "create multiple issues from a JSON array file (pre-validates all rows before creating any)").option("--idempotency-key <key>", "create-if-not-exists: skip creation when an issue with this key already exists (retried loops must not duplicate)");
|
|
@@ -21348,7 +21550,7 @@ function extendCreateCommand(issue2) {
|
|
|
21348
21550
|
if (opts.batch) {
|
|
21349
21551
|
let specs;
|
|
21350
21552
|
try {
|
|
21351
|
-
const raw = (0,
|
|
21553
|
+
const raw = (0, import_node_fs26.readFileSync)(opts.batch, "utf8");
|
|
21352
21554
|
specs = JSON.parse(raw);
|
|
21353
21555
|
if (!Array.isArray(specs)) throw new Error("batch file must contain a JSON array");
|
|
21354
21556
|
} catch (e) {
|
|
@@ -21360,13 +21562,27 @@ function extendCreateCommand(issue2) {
|
|
|
21360
21562
|
return fail(`issue create --batch: validation failed (${validation.errors.length} error(s)):
|
|
21361
21563
|
${lines}`);
|
|
21362
21564
|
}
|
|
21565
|
+
const batchParent = opts.parent;
|
|
21566
|
+
const batchPriority = opts.priority ? normalizePriority(opts.priority) : void 0;
|
|
21363
21567
|
if (opts.dryRun || opts.validateOnly) {
|
|
21364
|
-
const planned = validation.validated.map((v) => ({
|
|
21568
|
+
const planned = validation.validated.map((v) => ({
|
|
21569
|
+
row: v.row,
|
|
21570
|
+
type: v.type,
|
|
21571
|
+
title: v.spec.title,
|
|
21572
|
+
priority: v.spec.priority ? v.priority : batchPriority ?? v.priority,
|
|
21573
|
+
repo: v.spec.repo,
|
|
21574
|
+
...v.spec.parent ?? batchParent ? { parent: v.spec.parent ?? batchParent } : {}
|
|
21575
|
+
}));
|
|
21365
21576
|
console.log(JSON.stringify(opts.validateOnly ? { ok: true, planned } : { dry_run: true, planned }));
|
|
21366
21577
|
return;
|
|
21367
21578
|
}
|
|
21368
21579
|
try {
|
|
21369
|
-
const result = await createIssuesBatch(specs, {
|
|
21580
|
+
const result = await createIssuesBatch(specs, {
|
|
21581
|
+
repo: opts.repo,
|
|
21582
|
+
idempotencyKey: opts.idempotencyKey,
|
|
21583
|
+
parent: batchParent,
|
|
21584
|
+
priority: batchPriority
|
|
21585
|
+
}, { attach: batchAttach });
|
|
21370
21586
|
console.log(JSON.stringify(result));
|
|
21371
21587
|
if (result.failures.length) process.exitCode = 1;
|
|
21372
21588
|
} catch (e) {
|
|
@@ -21392,11 +21608,11 @@ ${lines}`);
|
|
|
21392
21608
|
}
|
|
21393
21609
|
|
|
21394
21610
|
// src/train-commands.ts
|
|
21395
|
-
var
|
|
21611
|
+
var import_node_fs28 = require("node:fs");
|
|
21396
21612
|
var import_node_path24 = require("node:path");
|
|
21397
21613
|
|
|
21398
21614
|
// src/plugin-guard-io.ts
|
|
21399
|
-
var
|
|
21615
|
+
var import_node_fs27 = require("node:fs");
|
|
21400
21616
|
var import_node_path23 = require("node:path");
|
|
21401
21617
|
var import_node_os6 = require("node:os");
|
|
21402
21618
|
|
|
@@ -21531,7 +21747,7 @@ var installedPluginsPath2 = (surface = detectSurface(process.env)) => {
|
|
|
21531
21747
|
};
|
|
21532
21748
|
function readInstalledPlugins(surface = detectSurface(process.env)) {
|
|
21533
21749
|
try {
|
|
21534
|
-
return JSON.parse((0,
|
|
21750
|
+
return JSON.parse((0, import_node_fs27.readFileSync)(installedPluginsPath2(surface), "utf8"));
|
|
21535
21751
|
} catch {
|
|
21536
21752
|
return null;
|
|
21537
21753
|
}
|
|
@@ -21545,7 +21761,7 @@ function marketplaceCloneCandidates(surface, home) {
|
|
|
21545
21761
|
}
|
|
21546
21762
|
return [(0, import_node_path23.join)(home, ".claude", "plugins", "marketplaces", "mutmutco")];
|
|
21547
21763
|
}
|
|
21548
|
-
function marketplaceClonePresent(surface, home, exists =
|
|
21764
|
+
function marketplaceClonePresent(surface, home, exists = import_node_fs27.existsSync) {
|
|
21549
21765
|
return marketplaceCloneCandidates(surface, home).some(exists);
|
|
21550
21766
|
}
|
|
21551
21767
|
async function fetchNpmReleasedVersion() {
|
|
@@ -21572,7 +21788,7 @@ function snapshotPluginGuardInput(surface = detectSurface(process.env), isOrgRep
|
|
|
21572
21788
|
isOrgRepo,
|
|
21573
21789
|
installRecordPresent: hasUserInstallRecord(installed, MMI_PLUGIN_ID) || hasProjectInstallRecord(installed, MMI_PLUGIN_ID, process.cwd()),
|
|
21574
21790
|
marketplaceClonePresent: marketplaceClonePresent(surface, (0, import_node_os6.homedir)()),
|
|
21575
|
-
pluginCachePresent: (0,
|
|
21791
|
+
pluginCachePresent: (0, import_node_fs27.existsSync)((0, import_node_path23.join)((0, import_node_os6.homedir)(), homeDir, "plugins", "cache", "mutmutco", "mmi"))
|
|
21576
21792
|
};
|
|
21577
21793
|
}
|
|
21578
21794
|
function claudePluginGuardState(isOrgRepo) {
|
|
@@ -21707,7 +21923,7 @@ function formatTrainStatus(r) {
|
|
|
21707
21923
|
// src/train-commands.ts
|
|
21708
21924
|
function readRepoVersion() {
|
|
21709
21925
|
try {
|
|
21710
|
-
return JSON.parse((0,
|
|
21926
|
+
return JSON.parse((0, import_node_fs28.readFileSync)((0, import_node_path24.join)(process.cwd(), ".claude-plugin", "plugin.json"), "utf8")).version || void 0;
|
|
21711
21927
|
} catch {
|
|
21712
21928
|
return void 0;
|
|
21713
21929
|
}
|
|
@@ -21918,12 +22134,14 @@ async function recommendNext(deps) {
|
|
|
21918
22134
|
const load = deps?.loadConfig ?? loadConfig;
|
|
21919
22135
|
const reader = deps?.readBoard ?? readBoard;
|
|
21920
22136
|
const cfg = await load();
|
|
21921
|
-
if (!cfg.sagaApiUrl)
|
|
22137
|
+
if (!cfg.sagaApiUrl) throw new Error("Hub API URL not configured \u2014 the board was NOT read (run `mmi-cli doctor`)");
|
|
21922
22138
|
let report;
|
|
21923
22139
|
try {
|
|
21924
22140
|
report = await reader({ config: cfg });
|
|
21925
|
-
} catch {
|
|
21926
|
-
|
|
22141
|
+
} catch (e) {
|
|
22142
|
+
throw new Error(
|
|
22143
|
+
`board unreachable \u2014 ${e.message}. This is NOT an empty board: confirm the ACTIVE gh account can see this repo (\`gh auth status\`, \`gh repo view <owner/repo>\`; \`gh auth switch --user <other>\` when more than one account is present).`
|
|
22144
|
+
);
|
|
21927
22145
|
}
|
|
21928
22146
|
const claimable = report.primary.claimable;
|
|
21929
22147
|
if (!claimable.length) return null;
|
|
@@ -21997,12 +22215,16 @@ async function collectOnboardStatus() {
|
|
|
21997
22215
|
} else if (!board.ok) {
|
|
21998
22216
|
nextCommand = "mmi-cli board read";
|
|
21999
22217
|
} else {
|
|
22000
|
-
|
|
22001
|
-
|
|
22002
|
-
|
|
22003
|
-
|
|
22004
|
-
|
|
22218
|
+
let top = null;
|
|
22219
|
+
let readError;
|
|
22220
|
+
try {
|
|
22221
|
+
top = await recommendNext();
|
|
22222
|
+
} catch (e) {
|
|
22223
|
+
readError = e.message;
|
|
22005
22224
|
}
|
|
22225
|
+
if (readError) nextCommand = `mmi-cli board read \u2014 ${readError}`;
|
|
22226
|
+
else if (top) nextCommand = `mmi-cli board claim ${top.number} # ${top.title}`;
|
|
22227
|
+
else nextCommand = "mmi-cli board read \u2014 no claimable items found";
|
|
22006
22228
|
}
|
|
22007
22229
|
return { track, board, registry: registry2, secrets, nextCommand };
|
|
22008
22230
|
}
|
|
@@ -22027,7 +22249,7 @@ function registerDiscoveryCommands(program3) {
|
|
|
22027
22249
|
try {
|
|
22028
22250
|
const item = await recommendNext();
|
|
22029
22251
|
if (!item) {
|
|
22030
|
-
console.log("No claimable board items found.");
|
|
22252
|
+
console.log("No claimable board items found (board read OK).");
|
|
22031
22253
|
return;
|
|
22032
22254
|
}
|
|
22033
22255
|
const priorityNote = item.priority ? ` [${item.priority}]` : "";
|
|
@@ -22794,14 +23016,30 @@ function doctorReportExitCode(checks) {
|
|
|
22794
23016
|
// src/doctor-clean.ts
|
|
22795
23017
|
function checkGithubAuth(probe) {
|
|
22796
23018
|
const login = probe.login?.trim();
|
|
22797
|
-
const
|
|
23019
|
+
const authed = Boolean(login);
|
|
23020
|
+
const reach = probe.reach;
|
|
23021
|
+
if (authed && reach?.repo && reach.reachable === false) {
|
|
23022
|
+
const rescuers = reach.otherAccountsThatCanSee ?? [];
|
|
23023
|
+
return {
|
|
23024
|
+
ok: false,
|
|
23025
|
+
label: "github auth",
|
|
23026
|
+
fix: rescuers.length ? `the active gh account (${login}) cannot see ${reach.repo}; another account is logged in \u2014 try: gh auth switch --user ${rescuers[0]}` : `the active gh account (${login}) cannot see ${reach.repo} \u2014 check org membership, or: gh auth login`,
|
|
23027
|
+
verbose: [
|
|
23028
|
+
`gh installed: ${probe.ghInstalled ? "yes" : "no"}`,
|
|
23029
|
+
`authenticated as: ${login}`,
|
|
23030
|
+
`can resolve ${reach.repo}: NO`,
|
|
23031
|
+
rescuers.length ? `other gh accounts logged in: ${rescuers.join(", ")} \u2014 switching is the usual fix` : "no other gh account is logged in, so this is not a wrong-account mix-up"
|
|
23032
|
+
]
|
|
23033
|
+
};
|
|
23034
|
+
}
|
|
22798
23035
|
return {
|
|
22799
|
-
ok,
|
|
23036
|
+
ok: authed,
|
|
22800
23037
|
label: "github auth",
|
|
22801
23038
|
fix: probe.ghInstalled ? 'run: gh auth login --hostname github.com --git-protocol https --web --scopes "project"' : "install GitHub CLI (https://cli.github.com), then: gh auth login",
|
|
22802
23039
|
verbose: [
|
|
22803
23040
|
`gh installed: ${probe.ghInstalled ? "yes" : "no"}`,
|
|
22804
|
-
`authenticated as: ${login || "(none)"}
|
|
23041
|
+
`authenticated as: ${login || "(none)"}`,
|
|
23042
|
+
...reach?.repo ? [`can resolve ${reach.repo}: ${reach.reachable === true ? "yes" : "not probed"}`] : []
|
|
22805
23043
|
]
|
|
22806
23044
|
};
|
|
22807
23045
|
}
|
|
@@ -22974,17 +23212,22 @@ function gcReapable(plan) {
|
|
|
22974
23212
|
}
|
|
22975
23213
|
async function runDoctorClean(opts, io, deps) {
|
|
22976
23214
|
const apply = Boolean(opts.apply);
|
|
22977
|
-
const [login, isOrgRepo, released, callerArn] = await Promise.all([
|
|
23215
|
+
const [login, isOrgRepo, released, callerArn, reach] = await Promise.all([
|
|
22978
23216
|
deps.githubLogin(),
|
|
22979
23217
|
deps.isOrgRepo(),
|
|
22980
23218
|
opts.fast ? Promise.resolve(void 0) : deps.releasedVersion(),
|
|
22981
|
-
opts.fast ? Promise.resolve(void 0) : deps.awsCallerArn()
|
|
23219
|
+
opts.fast ? Promise.resolve(void 0) : deps.awsCallerArn(),
|
|
23220
|
+
// #3365. Skipped on the banner/preflight lanes, which run on every SessionStart and must not pay a
|
|
23221
|
+
// network round-trip — and on a bare `--fast`, whose contract is offline-safe. It DOES run on
|
|
23222
|
+
// `--self`, which rides the fast lane but is the interactive "is my setup actually fine?" diagnostic,
|
|
23223
|
+
// and is exactly where the wrong-active-account trap has to be caught. Fail-soft either way.
|
|
23224
|
+
opts.banner || opts.preflight || opts.fast && !opts.self ? Promise.resolve(void 0) : deps.githubRepoReach?.() ?? Promise.resolve(void 0)
|
|
22982
23225
|
]);
|
|
22983
23226
|
const ghInstalled2 = login ? true : await deps.ghInstalled();
|
|
22984
23227
|
const installed = deps.installedPluginVersion();
|
|
22985
23228
|
const checks = [];
|
|
22986
23229
|
let restartPending = false;
|
|
22987
|
-
checks.push(checkGithubAuth({ login, ghInstalled: ghInstalled2 }));
|
|
23230
|
+
checks.push(checkGithubAuth({ login, ghInstalled: ghInstalled2, reach }));
|
|
22988
23231
|
if (!opts.fast && !opts.banner && !opts.preflight && deps.githubPools) {
|
|
22989
23232
|
checks.push(...checkGithubPools(await deps.githubPools()));
|
|
22990
23233
|
}
|
|
@@ -23158,8 +23401,86 @@ async function runDoctorClean(opts, io, deps) {
|
|
|
23158
23401
|
return exitCode;
|
|
23159
23402
|
}
|
|
23160
23403
|
|
|
23404
|
+
// src/gh-account-visibility.ts
|
|
23405
|
+
function parseGhAuthAccounts(stdout) {
|
|
23406
|
+
const accounts = [];
|
|
23407
|
+
for (const raw of stdout.split(/\r?\n/)) {
|
|
23408
|
+
const match = /Logged in to \S+ (?:account|as)\s+([A-Za-z0-9-]+)/.exec(raw);
|
|
23409
|
+
if (!match?.[1]) continue;
|
|
23410
|
+
accounts.push({ login: match[1], active: /\(active\)/.test(raw) });
|
|
23411
|
+
}
|
|
23412
|
+
return accounts;
|
|
23413
|
+
}
|
|
23414
|
+
function parseOriginRepo(remoteUrl) {
|
|
23415
|
+
const url = remoteUrl.trim();
|
|
23416
|
+
if (!url) return void 0;
|
|
23417
|
+
const match = /github\.com[/:]([^/\s]+)\/([^/\s]+?)(?:\.git)?\/?$/.exec(url);
|
|
23418
|
+
if (!match) return void 0;
|
|
23419
|
+
return `${match[1]}/${match[2]}`;
|
|
23420
|
+
}
|
|
23421
|
+
function ghHostsConfigPath(env, platform2) {
|
|
23422
|
+
const sep2 = platform2 === "win32" ? "\\" : "/";
|
|
23423
|
+
const join24 = (...parts) => parts.join(sep2);
|
|
23424
|
+
const explicit = env.GH_CONFIG_DIR?.trim();
|
|
23425
|
+
if (explicit) return join24(explicit, "hosts.yml");
|
|
23426
|
+
if (platform2 === "win32") {
|
|
23427
|
+
const appData = (env.AppData ?? env.APPDATA)?.trim();
|
|
23428
|
+
return appData ? join24(appData, "GitHub CLI", "hosts.yml") : void 0;
|
|
23429
|
+
}
|
|
23430
|
+
const xdg = env.XDG_CONFIG_HOME?.trim();
|
|
23431
|
+
if (xdg) return join24(xdg, "gh", "hosts.yml");
|
|
23432
|
+
const home = env.HOME?.trim();
|
|
23433
|
+
return home ? join24(home, ".config", "gh", "hosts.yml") : void 0;
|
|
23434
|
+
}
|
|
23435
|
+
function parseGhHostsAccounts(yaml, host = "github.com") {
|
|
23436
|
+
let hostIndent = null;
|
|
23437
|
+
let usersIndent = null;
|
|
23438
|
+
let loginIndent = null;
|
|
23439
|
+
let activeLogin;
|
|
23440
|
+
const logins = [];
|
|
23441
|
+
for (const raw of yaml.split(/\r?\n/)) {
|
|
23442
|
+
if (!raw.trim() || raw.trimStart().startsWith("#")) continue;
|
|
23443
|
+
const indent = raw.length - raw.trimStart().length;
|
|
23444
|
+
const keyMatch = /^"?([A-Za-z0-9._-]+)"?\s*:(.*)$/.exec(raw.trim());
|
|
23445
|
+
if (!keyMatch) continue;
|
|
23446
|
+
const key = keyMatch[1];
|
|
23447
|
+
if (hostIndent === null) {
|
|
23448
|
+
if (key === host) hostIndent = indent;
|
|
23449
|
+
continue;
|
|
23450
|
+
}
|
|
23451
|
+
if (indent <= hostIndent) break;
|
|
23452
|
+
if (usersIndent !== null && indent > usersIndent) {
|
|
23453
|
+
if (loginIndent === null) loginIndent = indent;
|
|
23454
|
+
if (indent === loginIndent && !/token/i.test(key)) logins.push(key);
|
|
23455
|
+
continue;
|
|
23456
|
+
}
|
|
23457
|
+
usersIndent = null;
|
|
23458
|
+
loginIndent = null;
|
|
23459
|
+
if (key === "users") {
|
|
23460
|
+
usersIndent = indent;
|
|
23461
|
+
continue;
|
|
23462
|
+
}
|
|
23463
|
+
if (key === "user") {
|
|
23464
|
+
const value = keyMatch[2].trim().replace(/^["']|["']$/g, "");
|
|
23465
|
+
if (value) activeLogin = value;
|
|
23466
|
+
}
|
|
23467
|
+
}
|
|
23468
|
+
const unique = [...new Set(logins)];
|
|
23469
|
+
if (!unique.length && activeLogin) unique.push(activeLogin);
|
|
23470
|
+
return unique.map((login) => ({ login, active: login === activeLogin }));
|
|
23471
|
+
}
|
|
23472
|
+
function ghAccountCaveat(announcedLogin, accounts) {
|
|
23473
|
+
if (accounts.length <= 1) return void 0;
|
|
23474
|
+
const active = accounts.find((a) => a.active)?.login;
|
|
23475
|
+
if (active && active !== announcedLogin) {
|
|
23476
|
+
return `NOTE: gh's active account is ${active}, not ${announcedLogin} \u2014 repo reads and pushes use ${active}. A repo that reads as "not found" is that mismatch, not lost access: gh auth switch --user ${announcedLogin}`;
|
|
23477
|
+
}
|
|
23478
|
+
const others = accounts.filter((a) => a.login !== active).map((a) => a.login);
|
|
23479
|
+
return `NOTE: gh has ${accounts.length} accounts (active: ${active ?? "unknown"}; also ${others.join(", ")}) \u2014 a repo that reads as "not found" is usually the wrong active account, not lost access: gh auth status, then gh auth switch`;
|
|
23480
|
+
}
|
|
23481
|
+
|
|
23161
23482
|
// src/doctor-io.ts
|
|
23162
|
-
var
|
|
23483
|
+
var import_node_fs29 = require("node:fs");
|
|
23163
23484
|
var import_node_os7 = require("node:os");
|
|
23164
23485
|
var import_node_path25 = require("node:path");
|
|
23165
23486
|
var import_node_child_process12 = require("node:child_process");
|
|
@@ -23169,7 +23490,7 @@ var MMI_PLUGIN_ID2 = "mmi@mutmutco";
|
|
|
23169
23490
|
function installedClaudePluginVersion() {
|
|
23170
23491
|
try {
|
|
23171
23492
|
const file = JSON.parse(
|
|
23172
|
-
(0,
|
|
23493
|
+
(0, import_node_fs29.readFileSync)((0, import_node_path25.join)((0, import_node_os7.homedir)(), ".claude", "plugins", "installed_plugins.json"), "utf8")
|
|
23173
23494
|
);
|
|
23174
23495
|
const versions = (file.plugins?.[MMI_PLUGIN_ID2] ?? []).map((r) => r.version).filter((v) => Boolean(v));
|
|
23175
23496
|
if (versions.length === 0) return void 0;
|
|
@@ -23196,7 +23517,7 @@ function readGitignore() {
|
|
|
23196
23517
|
const path2 = gitignorePath();
|
|
23197
23518
|
if (path2 === null) return null;
|
|
23198
23519
|
try {
|
|
23199
|
-
return (0,
|
|
23520
|
+
return (0, import_node_fs29.readFileSync)(path2, "utf8");
|
|
23200
23521
|
} catch {
|
|
23201
23522
|
return null;
|
|
23202
23523
|
}
|
|
@@ -23205,7 +23526,7 @@ function writeGitignore(content) {
|
|
|
23205
23526
|
const path2 = gitignorePath();
|
|
23206
23527
|
if (path2 === null) return false;
|
|
23207
23528
|
try {
|
|
23208
|
-
(0,
|
|
23529
|
+
(0, import_node_fs29.writeFileSync)(path2, content, "utf8");
|
|
23209
23530
|
return true;
|
|
23210
23531
|
} catch {
|
|
23211
23532
|
return false;
|
|
@@ -23229,7 +23550,7 @@ async function repoRoot() {
|
|
|
23229
23550
|
}
|
|
23230
23551
|
function hasRepoLocalWorktrees() {
|
|
23231
23552
|
const root = worktreeRootSync();
|
|
23232
|
-
return root !== null && (0,
|
|
23553
|
+
return root !== null && (0, import_node_fs29.existsSync)((0, import_node_path25.join)(root, ".worktrees"));
|
|
23233
23554
|
}
|
|
23234
23555
|
|
|
23235
23556
|
// src/index.ts
|
|
@@ -23250,10 +23571,31 @@ async function readDocsAuditFetch(repo) {
|
|
|
23250
23571
|
verdict: row ? { repo: row.repo, date: row.date, shaRange: row.shaRange, outcome: row.outcome, checkerVendor: row.checkerVendor } : null
|
|
23251
23572
|
};
|
|
23252
23573
|
}
|
|
23574
|
+
async function githubRepoReachProbe() {
|
|
23575
|
+
const remote = await execFileP2("git", ["remote", "get-url", "origin"], { timeout: GIT_TIMEOUT_MS }).then((r) => r.stdout).catch(() => "");
|
|
23576
|
+
const repo = parseOriginRepo(remote);
|
|
23577
|
+
if (!repo) return void 0;
|
|
23578
|
+
const reachable = await execFileP2("gh", ["repo", "view", repo, "--json", "name"], { timeout: GH_MUTATION_TIMEOUT_MS }).then(() => true).catch(() => false);
|
|
23579
|
+
if (reachable) return { repo, reachable: true };
|
|
23580
|
+
const status = await execFileP2("gh", ["auth", "status"], { timeout: GH_MUTATION_TIMEOUT_MS }).then((r) => `${r.stdout}
|
|
23581
|
+
${r.stderr ?? ""}`).catch(() => "");
|
|
23582
|
+
const others = parseGhAuthAccounts(status).filter((a) => !a.active).map((a) => a.login);
|
|
23583
|
+
return { repo, reachable: false, otherAccountsThatCanSee: others };
|
|
23584
|
+
}
|
|
23585
|
+
function ghMultiAccountCaveat(announcedLogin) {
|
|
23586
|
+
try {
|
|
23587
|
+
const hostsPath = ghHostsConfigPath(process.env, process.platform);
|
|
23588
|
+
if (!hostsPath || !(0, import_node_fs30.existsSync)(hostsPath)) return void 0;
|
|
23589
|
+
return ghAccountCaveat(announcedLogin, parseGhHostsAccounts((0, import_node_fs30.readFileSync)(hostsPath, "utf8")));
|
|
23590
|
+
} catch {
|
|
23591
|
+
return void 0;
|
|
23592
|
+
}
|
|
23593
|
+
}
|
|
23253
23594
|
function mmiDoctorDeps() {
|
|
23254
23595
|
return {
|
|
23255
23596
|
githubLogin,
|
|
23256
23597
|
ghInstalled,
|
|
23598
|
+
githubRepoReach: githubRepoReachProbe,
|
|
23257
23599
|
awsCallerArn,
|
|
23258
23600
|
isOrgRepo: () => isOrgRepoRoot(),
|
|
23259
23601
|
installedPluginVersion: installedClaudePluginVersion,
|
|
@@ -23457,18 +23799,18 @@ program2.hook("preAction", async (_thisCommand, actionCommand) => {
|
|
|
23457
23799
|
var rules = program2.command("rules").description("org-managed .gitignore delivery");
|
|
23458
23800
|
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) => {
|
|
23459
23801
|
const path2 = (0, import_node_path26.join)(process.cwd(), ".gitignore");
|
|
23460
|
-
const current = (0,
|
|
23802
|
+
const current = (0, import_node_fs30.existsSync)(path2) ? (0, import_node_fs30.readFileSync)(path2, "utf8") : null;
|
|
23461
23803
|
const plan = planManagedGitignore(current);
|
|
23462
23804
|
const drift = [...plan.added.map((l) => `+${l}`), ...plan.removed.map((l) => `-${l}`)].join(", ") || "block normalize";
|
|
23463
23805
|
if (opts.json) {
|
|
23464
|
-
if (opts.write && plan.changed) (0,
|
|
23806
|
+
if (opts.write && plan.changed) (0, import_node_fs30.writeFileSync)(path2, plan.content, "utf8");
|
|
23465
23807
|
console.log(JSON.stringify(plan, null, 2));
|
|
23466
23808
|
if (!opts.write && plan.changed) process.exitCode = 1;
|
|
23467
23809
|
return;
|
|
23468
23810
|
}
|
|
23469
23811
|
if (opts.write) {
|
|
23470
23812
|
if (plan.changed) {
|
|
23471
|
-
(0,
|
|
23813
|
+
(0, import_node_fs30.writeFileSync)(path2, plan.content, "utf8");
|
|
23472
23814
|
console.log(`mmi-cli org rules gitignore: updated .gitignore (${drift})`);
|
|
23473
23815
|
} else {
|
|
23474
23816
|
console.log("mmi-cli org rules gitignore: up to date");
|
|
@@ -23594,7 +23936,7 @@ gcCmd.option("--dry-run", "show what would be deleted (default)").option("--appl
|
|
|
23594
23936
|
if (!Number.isFinite(limit) || limit < 1) return fail("worktree gc: --limit must be a positive integer");
|
|
23595
23937
|
try {
|
|
23596
23938
|
const plan = await gcPlan(o.remote, limit);
|
|
23597
|
-
if (o.apply && !o.json) console.log(formatGcPlan(plan,
|
|
23939
|
+
if (o.apply && !o.json) console.log(formatGcPlan(plan, true));
|
|
23598
23940
|
let applyResult;
|
|
23599
23941
|
if (o.apply) {
|
|
23600
23942
|
const deferredStore = await createDeferredWorktreeStore();
|
|
@@ -23658,26 +24000,26 @@ function makeProvisionDeps(worktreeRoot, quiet, log) {
|
|
|
23658
24000
|
function acquireWorktreeSetupLock(worktreeRoot) {
|
|
23659
24001
|
const lockPath = repoRuntimeStatePath(worktreeRoot, "worktree-setup.lock");
|
|
23660
24002
|
const take = () => {
|
|
23661
|
-
const fd = (0,
|
|
24003
|
+
const fd = (0, import_node_fs30.openSync)(lockPath, "wx");
|
|
23662
24004
|
try {
|
|
23663
|
-
(0,
|
|
24005
|
+
(0, import_node_fs30.writeSync)(fd, String(Date.now()));
|
|
23664
24006
|
} finally {
|
|
23665
|
-
(0,
|
|
24007
|
+
(0, import_node_fs30.closeSync)(fd);
|
|
23666
24008
|
}
|
|
23667
24009
|
return () => {
|
|
23668
24010
|
try {
|
|
23669
|
-
(0,
|
|
24011
|
+
(0, import_node_fs30.rmSync)(lockPath, { force: true });
|
|
23670
24012
|
} catch {
|
|
23671
24013
|
}
|
|
23672
24014
|
};
|
|
23673
24015
|
};
|
|
23674
24016
|
try {
|
|
23675
|
-
(0,
|
|
24017
|
+
(0, import_node_fs30.mkdirSync)((0, import_node_path26.dirname)(lockPath), { recursive: true });
|
|
23676
24018
|
return take();
|
|
23677
24019
|
} catch {
|
|
23678
24020
|
try {
|
|
23679
|
-
if (Date.now() - (0,
|
|
23680
|
-
(0,
|
|
24021
|
+
if (Date.now() - (0, import_node_fs30.statSync)(lockPath).mtimeMs > WORKTREE_SETUP_LOCK_TTL_MS) {
|
|
24022
|
+
(0, import_node_fs30.rmSync)(lockPath, { force: true });
|
|
23681
24023
|
return take();
|
|
23682
24024
|
}
|
|
23683
24025
|
} catch {
|
|
@@ -23687,7 +24029,7 @@ function acquireWorktreeSetupLock(worktreeRoot) {
|
|
|
23687
24029
|
}
|
|
23688
24030
|
var worktree = program2.command("worktree").description("self-provisioning worktrees \u2014 install deps + copy local-only config");
|
|
23689
24031
|
withExamples(mutating(
|
|
23690
|
-
worktree.command("create <branch-or-issue>").description("create a worktree from a base ref and provision it (install deps + copy local-only config); an issue ref derives the <issue>-<slug> branch, and --claim claims its board item (#2996, formerly worktree new)").option("--from <ref>", "base ref to branch from", "origin/development").option("--base <ref>", "alias of --from (git's own word for the base ref)").option("--path <path>", "worktree path (default: ../mmi-worktrees/<branch>)").option("--remote <name>", "remote to fetch when --from is a <remote>/<branch> ref", "origin").option("--slug <slug>", "issue-ref form: override the slug derived from the issue title").option("--claim", "issue-ref form: claim the issue's board item after provisioning").option("--for <login>", "with --claim: assign the board item to this login instead of @me"),
|
|
24032
|
+
worktree.command("create <branch-or-issue>").description("create a worktree from a base ref and provision it (install deps + copy local-only config); an issue ref derives the <issue>-<slug> branch, and --claim claims its board item (#2996, formerly worktree new)").option("--from <ref>", "base ref to branch from", "origin/development").option("--base <ref>", "alias of --from (git's own word for the base ref)").option("--path <path>", "worktree path (default: ../mmi-worktrees/<branch> \u2014 authoritative; worktree gc/list scan only this root)").option("--remote <name>", "remote to fetch when --from is a <remote>/<branch> ref", "origin").option("--slug <slug>", "issue-ref form: override the slug derived from the issue title").option("--claim", "issue-ref form: claim the issue's board item after provisioning").option("--for <login>", "with --claim: assign the board item to this login instead of @me"),
|
|
23691
24033
|
worktreeCreatePlan
|
|
23692
24034
|
).action(async (target, o, cmd) => {
|
|
23693
24035
|
try {
|
|
@@ -24119,7 +24461,7 @@ project.command("set [owner/repo]").description("upsert project META (idempotent
|
|
|
24119
24461
|
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`);
|
|
24120
24462
|
if (o.secretsFile) {
|
|
24121
24463
|
try {
|
|
24122
|
-
vars.push(`secrets=${(0,
|
|
24464
|
+
vars.push(`secrets=${(0, import_node_fs30.readFileSync)(o.secretsFile, "utf8")}`);
|
|
24123
24465
|
} catch (e) {
|
|
24124
24466
|
return fail(`org project set: cannot read --secrets-file ${o.secretsFile}: ${e.message}`);
|
|
24125
24467
|
}
|
|
@@ -24385,11 +24727,14 @@ function resolveCreatePriority(raw, command) {
|
|
|
24385
24727
|
});
|
|
24386
24728
|
}
|
|
24387
24729
|
}
|
|
24388
|
-
function resolveCreateType(raw, command) {
|
|
24730
|
+
function resolveCreateType(raw, command, labels) {
|
|
24389
24731
|
if (raw === void 0 || raw === "") {
|
|
24390
|
-
|
|
24732
|
+
const nearMiss = labels?.find((l) => ISSUE_TYPES.includes(l));
|
|
24733
|
+
const hint = nearMiss ? ` (you passed --label ${nearMiss} \u2014 did you mean --type ${nearMiss}?)` : "";
|
|
24734
|
+
fail(`${command}: --type is required (bug | feature | task) unless --batch supplies it per row${hint}`, {
|
|
24391
24735
|
code: ERROR_CODES.ERR_MISSING_FLAG,
|
|
24392
|
-
offending_flag: "--type"
|
|
24736
|
+
offending_flag: "--type",
|
|
24737
|
+
...nearMiss ? { corrected_command: `${command} --type ${nearMiss} \u2026` } : {}
|
|
24393
24738
|
});
|
|
24394
24739
|
}
|
|
24395
24740
|
if (!ISSUE_TYPES.includes(raw)) {
|
|
@@ -24403,12 +24748,12 @@ function resolveCreateType(raw, command) {
|
|
|
24403
24748
|
}
|
|
24404
24749
|
var issue = program2.command("issue").description("issues \u2014 reliable create with structured output");
|
|
24405
24750
|
withExamples(mutating(
|
|
24406
|
-
issue.command("create").description("create an issue (type \u2192 label) and print {number,url,label} JSON").option("--type <type>", "bug | feature | task (sets the matching label; required unless --batch)").option("--title <title>", "issue title").option("--title-file <path|->", "read the issue title from a UTF-8 file, or from stdin with -").option("--body <body>", "issue body (markdown)").option("--body-file <path|->", "read issue body from a UTF-8 file,
|
|
24751
|
+
issue.command("create").description("create an issue (type \u2192 label) and print {number,url,label} JSON").option("--type <type>", "bug | feature | task (sets the matching label; required unless --batch)").option("--title <title>", "issue title").option("--title-file <path|->", "read the issue title from a UTF-8 file, or from stdin with -").option("--body <body>", "issue body (markdown)").option("--body-file <path|->", "read issue body from a UTF-8 file. `-` (stdin) needs a heredoc, which the agent inline-body guard denies (#1473/#2125) \u2014 prefer a real path; a title with backticks or newlines needs --title-file for the same reason (#3381)").option("--priority <priority>", "urgent | high | medium | low (defaults to medium; sets the board Priority field only \u2014 never a priority:* label, #416)").option("--repo <owner/repo>", "target repo (defaults to the current repo)").option("--label <label...>", "extra label(s) to attach (repeatable; auto-created if missing)").option("--parent <ref>", "file as a native sub-issue of this parent (#123, owner/repo#123, or URL)").option("--no-related", "skip the auto related-issues comment"),
|
|
24407
24752
|
// --dry-run/--validate-only plan: validate --type + --priority (mirrors the action — a bad enum fails
|
|
24408
24753
|
// ERR_BAD_ENUM, a missing priority defaults to medium) then echo the resolved create intent. Refs
|
|
24409
24754
|
// (`--parent`) and title-source are validated by the action on a real run.
|
|
24410
24755
|
(opts) => {
|
|
24411
|
-
const type = resolveCreateType(opts.type, "issue create");
|
|
24756
|
+
const type = resolveCreateType(opts.type, "issue create", opts.label);
|
|
24412
24757
|
const priority = resolveCreatePriority(opts.priority, "issue create");
|
|
24413
24758
|
return { command: "issue create", type, title: opts.title ?? opts.titleFile, priority, repo: opts.repo };
|
|
24414
24759
|
}
|
|
@@ -24421,7 +24766,7 @@ withExamples(mutating(
|
|
|
24421
24766
|
let extraLabels = [];
|
|
24422
24767
|
let targetRepo2;
|
|
24423
24768
|
try {
|
|
24424
|
-
issueType = resolveCreateType(o.type, "issue create");
|
|
24769
|
+
issueType = resolveCreateType(o.type, "issue create", o.label);
|
|
24425
24770
|
title = await resolveIssueTitle({ title: o.title, titleFile: o.titleFile }, { readFile: import_promises7.readFile, readStdin });
|
|
24426
24771
|
body = await resolveIssueBody({ body: o.body, bodyFile: o.bodyFile }, { readFile: import_promises7.readFile, readStdin });
|
|
24427
24772
|
if (o.idempotencyKey) body = appendIdempotencyMarker(body, o.idempotencyKey);
|
|
@@ -24476,6 +24821,16 @@ withExamples(mutating(
|
|
|
24476
24821
|
"--type is one of bug|feature|task.",
|
|
24477
24822
|
"--dry-run and --validate-only pre-validate the create plan before any issue is written."
|
|
24478
24823
|
]);
|
|
24824
|
+
async function readParentField(number, repo) {
|
|
24825
|
+
let payload;
|
|
24826
|
+
try {
|
|
24827
|
+
payload = await ghJson(["api", `repos/${repo}/issues/${number}`]);
|
|
24828
|
+
} catch (e) {
|
|
24829
|
+
const err = e;
|
|
24830
|
+
return { parentReadError: (err.stderr || err.message || String(e)).trim() };
|
|
24831
|
+
}
|
|
24832
|
+
return resolveParentField(payload);
|
|
24833
|
+
}
|
|
24479
24834
|
issue.command("view <number>").description('read an issue as structured JSON \u2014 the mmi-cli path for non-board issue reads (#2347). --comments folds in every comment; --context also adds linkedPrs + children (the one-shot "load the whole item" read, #2894)').option("--repo <owner/repo>", "target repo (defaults to the current repo)").option("--json [fields...]", 'gh --json field list (overrides the default field set). Accepts commas, spaces, or repeated --json flags \u2014 in PowerShell an unquoted comma list is an array literal, so QUOTE it: --json "number,title,body,url"').option("--comments", 'include every comment (body + comments in one call) \u2014 the agentic "read the whole item before working it" read (#2894)').option("--context", "full working context in one call: implies --comments and also adds linkedPrs and, for an epic, a children summary (#2894)").action(async (number, o) => {
|
|
24480
24835
|
const n = Number(number);
|
|
24481
24836
|
if (!Number.isInteger(n) || n <= 0) return fail("issue view: <number> must be a positive integer");
|
|
@@ -24486,11 +24841,11 @@ issue.command("view <number>").description('read an issue as structured JSON \u2
|
|
|
24486
24841
|
if (o.comments || o.context) {
|
|
24487
24842
|
const boardProjectId = o.context ? await resolveBoardProjectId(o.repo) : void 0;
|
|
24488
24843
|
const data2 = await runIssueViewContext(queryDeps(), { number: n, repo, comments: Boolean(o.comments), context: Boolean(o.context), jsonFields: fields, boardProjectId });
|
|
24489
|
-
console.log(JSON.stringify(data2));
|
|
24844
|
+
console.log(JSON.stringify({ ...data2, ...await readParentField(n, repo) }));
|
|
24490
24845
|
return;
|
|
24491
24846
|
}
|
|
24492
24847
|
const data = await ghJson(["issue", "view", String(n), "--repo", repo, "--json", fields]);
|
|
24493
|
-
console.log(JSON.stringify(data));
|
|
24848
|
+
console.log(JSON.stringify({ ...data, ...await readParentField(n, repo) }));
|
|
24494
24849
|
} catch (e) {
|
|
24495
24850
|
const err = e;
|
|
24496
24851
|
const raw = (err.stderr || err.message || String(e)).trim();
|
|
@@ -24536,7 +24891,7 @@ issue.command("discover-related").description("find related issues for an existi
|
|
|
24536
24891
|
return fail(`issue discover-related: ${(err.stderr || err.message || String(e)).trim()}`);
|
|
24537
24892
|
}
|
|
24538
24893
|
});
|
|
24539
|
-
issue.command("link-child <parent> <child>").description("link an existing issue as a native sub-issue of a parent and print {parentNumber,subIssueNumber,totalCount} JSON").option("--repo <owner/repo>", "repo for bare refs on either side (defaults to the current repo)").action(async (parentRef, childRef, o) => {
|
|
24894
|
+
jsonParity(issue.command("link-child <parent> <child>").description("link an existing issue as a native sub-issue of a parent and print {parentNumber,subIssueNumber,totalCount} JSON").option("--repo <owner/repo>", "repo for bare refs on either side (defaults to the current repo)")).action(async (parentRef, childRef, o) => {
|
|
24540
24895
|
const defaultRepo = await resolveRepo(o.repo);
|
|
24541
24896
|
try {
|
|
24542
24897
|
const result = await linkSubIssue(ghRunner2, parentRef, childRef, defaultRepo);
|
|
@@ -24547,7 +24902,7 @@ issue.command("link-child <parent> <child>").description("link an existing issue
|
|
|
24547
24902
|
return fail(`issue link-child: ${(err.stderr || err.message || String(e)).trim()}${note ? ` (${note})` : ""}`);
|
|
24548
24903
|
}
|
|
24549
24904
|
});
|
|
24550
|
-
issue.command("comment <ref>").description("post a Markdown comment to an issue and print {number,repo,url,commentUrl} JSON").option("--body <body>", "comment body (markdown; prefer --body-file for multiline Markdown)").option("--body-file <path|->", "read comment body from a UTF-8 file, or from stdin with -").option("--repo <owner/repo>", "repo for a bare ref (defaults to the current repo)").action(async (ref, o) => {
|
|
24905
|
+
jsonParity(issue.command("comment <ref>").description("post a Markdown comment to an issue and print {number,repo,url,commentUrl} JSON").option("--body <body>", "comment body (markdown; prefer --body-file for multiline Markdown)").option("--body-file <path|->", "read comment body from a UTF-8 file, or from stdin with -").option("--repo <owner/repo>", "repo for a bare ref (defaults to the current repo)")).action(async (ref, o) => {
|
|
24551
24906
|
let parsed;
|
|
24552
24907
|
try {
|
|
24553
24908
|
parsed = parseIssueRef(ref);
|
|
@@ -24570,7 +24925,7 @@ issue.command("comment <ref>").description("post a Markdown comment to an issue
|
|
|
24570
24925
|
return fail(`issue comment: ${(err.stderr || err.message || String(e)).trim()}`);
|
|
24571
24926
|
}
|
|
24572
24927
|
});
|
|
24573
|
-
issue.command("check <ref>").description("tick (or with --off untick) a task-list checkbox in an issue/epic body by its item text and print {number,repo,item,checked,changed} JSON").requiredOption("--item <text>", "the checklist item to match \u2014 exact item text, else a unique substring").option("--off", "untick the item ([x] \u2192 [ ]) instead of ticking it").option("--repo <owner/repo>", "repo for a bare ref (defaults to the current repo)").action(async (ref, o) => {
|
|
24928
|
+
jsonParity(issue.command("check <ref>").description("tick (or with --off untick) a task-list checkbox in an issue/epic body by its item text and print {number,repo,item,checked,changed} JSON").requiredOption("--item <text>", "the checklist item to match \u2014 exact item text, else a unique substring").option("--off", "untick the item ([x] \u2192 [ ]) instead of ticking it").option("--repo <owner/repo>", "repo for a bare ref (defaults to the current repo)")).action(async (ref, o) => {
|
|
24574
24929
|
let parsed;
|
|
24575
24930
|
try {
|
|
24576
24931
|
parsed = parseIssueRef(ref);
|
|
@@ -24755,10 +25110,10 @@ pr.command("view <number>").description("read a PR as structured JSON (merged st
|
|
|
24755
25110
|
});
|
|
24756
25111
|
async function listCiWorkflowPaths(cwd = process.cwd()) {
|
|
24757
25112
|
const wfDir = (0, import_node_path26.join)(cwd, ".github", "workflows");
|
|
24758
|
-
if (!(0,
|
|
24759
|
-
return (0,
|
|
25113
|
+
if (!(0, import_node_fs30.existsSync)(wfDir)) return [];
|
|
25114
|
+
return (0, import_node_fs30.readdirSync)(wfDir).filter((name) => /\.(ya?ml)$/i.test(name)).filter((name) => {
|
|
24760
25115
|
try {
|
|
24761
|
-
return workflowReportsPrChecks((0,
|
|
25116
|
+
return workflowReportsPrChecks((0, import_node_fs30.readFileSync)((0, import_node_path26.join)(wfDir, name), "utf8"));
|
|
24762
25117
|
} catch {
|
|
24763
25118
|
return true;
|
|
24764
25119
|
}
|
|
@@ -24786,15 +25141,15 @@ function ciAuditDeps() {
|
|
|
24786
25141
|
readSeedFile: (path2) => {
|
|
24787
25142
|
if (!root) return null;
|
|
24788
25143
|
const fullPath = (0, import_node_path26.join)(root, path2);
|
|
24789
|
-
return (0,
|
|
25144
|
+
return (0, import_node_fs30.existsSync)(fullPath) ? (0, import_node_fs30.readFileSync)(fullPath, "utf8") : null;
|
|
24790
25145
|
}
|
|
24791
25146
|
};
|
|
24792
25147
|
}
|
|
24793
25148
|
function hubRoot() {
|
|
24794
25149
|
const fromPkg = (0, import_node_path26.join)(__dirname, "..", "..");
|
|
24795
25150
|
const marker = "skills/bootstrap/seeds/manifest.json";
|
|
24796
|
-
if ((0,
|
|
24797
|
-
if ((0,
|
|
25151
|
+
if ((0, import_node_fs30.existsSync)((0, import_node_path26.join)(fromPkg, marker))) return fromPkg;
|
|
25152
|
+
if ((0, import_node_fs30.existsSync)((0, import_node_path26.join)(process.cwd(), marker))) return process.cwd();
|
|
24798
25153
|
return null;
|
|
24799
25154
|
}
|
|
24800
25155
|
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) => {
|
|
@@ -24816,6 +25171,9 @@ pr.command("checks-wait <number>").description(`bounded wait for PR checks; skip
|
|
|
24816
25171
|
pollChecks: () => pollRestPrChecks(number, repo),
|
|
24817
25172
|
pollMergeable: () => pollRestPrMergeable(number, repo),
|
|
24818
25173
|
pollRateLimit: () => fetchRestCorePool(),
|
|
25174
|
+
// #3388: on a confirmed red, read the failing runs' annotations so a wall-clock budget kill stops
|
|
25175
|
+
// reading as "your tests failed". One call per failing run, only at the verdict.
|
|
25176
|
+
diagnoseFailure: () => diagnoseFailedRestChecks(number, repo),
|
|
24819
25177
|
baseBranch,
|
|
24820
25178
|
sleep: (ms) => new Promise((resolve5) => setTimeout(resolve5, ms)),
|
|
24821
25179
|
log: (message) => console.warn(message),
|
|
@@ -24831,6 +25189,8 @@ pr.command("checks-wait <number>").description(`bounded wait for PR checks; skip
|
|
|
24831
25189
|
printLine(`pr checks-wait: timeout \u2014 waited ${Math.round((result.waitedMs ?? 0) / 6e4)}m, last state: ${result.detail ?? "pending"}. No check failed; re-run to keep waiting, or pass --timeout <minutes>.`);
|
|
24832
25190
|
} else if (result.status === "rate-limited") {
|
|
24833
25191
|
printLine(`pr checks-wait: rate-limited \u2014 ${result.reason ?? "REST pool below floor"}. No check failed; re-run after the pool resets.`);
|
|
25192
|
+
} else if (result.detail === "ci-budget-kill") {
|
|
25193
|
+
printLine(`pr checks-wait: failure (wall-clock budget kill, NOT a test failure) \u2014 ${result.reason}`);
|
|
24834
25194
|
} else printLine(`pr checks-wait: ${result.status}${result.reason ? ` \u2014 ${result.reason}` : ""}${result.detail ? ` (${result.detail})` : ""}`);
|
|
24835
25195
|
if (result.status === "failure" || result.status === "conflicting") process.exitCode = 1;
|
|
24836
25196
|
if (result.status === "timeout" || result.status === "rate-limited") process.exitCode = PR_CHECKS_TIMEOUT_EXIT_CODE;
|
|
@@ -24861,6 +25221,9 @@ pr.command("land <number>").description("agent merge path (#1440): train probe \
|
|
|
24861
25221
|
// other base), so the fast-fail message's base branch is always 'development' here.
|
|
24862
25222
|
pollMergeable: () => pollRestPrMergeable(prNumber, repo),
|
|
24863
25223
|
pollRateLimit: () => fetchRestCorePool(),
|
|
25224
|
+
// #3388: `pr land` is the batch path — the one most likely to self-DOS the shared runner and
|
|
25225
|
+
// then read its own wall-clock kill as a broken diff.
|
|
25226
|
+
diagnoseFailure: () => diagnoseFailedRestChecks(prNumber, repo),
|
|
24864
25227
|
baseBranch: "development",
|
|
24865
25228
|
sleep: (ms) => new Promise((resolve5) => setTimeout(resolve5, ms)),
|
|
24866
25229
|
log: (message) => console.warn(message),
|
|
@@ -24928,7 +25291,7 @@ pr.command("land <number>").description("agent merge path (#1440): train probe \
|
|
|
24928
25291
|
}
|
|
24929
25292
|
if (result.status === "failed" || result.cleanupError) process.exitCode = 1;
|
|
24930
25293
|
});
|
|
24931
|
-
pr.command("merge <number>").description("merge a PR (squash by default) and clean up its branch + worktree \u2014 no leftover local branch; on no-ci repos run pr ci-policy / checks-wait first (#1432)").option("--squash", "squash merge (default)").option("--merge", "create a merge commit").option("--rebase", "rebase merge").option("--repo <owner/repo>", "target repo (defaults to the current repo)").option("--auto", "enable auto-merge \u2014 merge once the base-branch policy is satisfied (use for policy-gated repos)").option("--wait", `wait for checks to reach a terminal passing verdict before merging (default budget ${PR_CHECKS_TIMEOUT_MS / 6e4}m)`).option("--preserve-worktree", "keep the local PR worktree/stage/branch for an active multi-issue batch (#1888)").option("--force", "acknowledge and merge past a remaining prunable/severe scratch housekeeping block (advisory kept plans/ never blocks, #3012)").action(async (number, o) => {
|
|
25294
|
+
jsonParity(pr.command("merge <number>").description("merge a PR (squash by default) and clean up its branch + worktree \u2014 no leftover local branch; on no-ci repos run pr ci-policy / checks-wait first (#1432)").option("--squash", "squash merge (default)").option("--merge", "create a merge commit").option("--rebase", "rebase merge").option("--repo <owner/repo>", "target repo (defaults to the current repo)").option("--auto", "enable auto-merge \u2014 merge once the base-branch policy is satisfied (use for policy-gated repos)").option("--wait", `wait for checks to reach a terminal passing verdict before merging (default budget ${PR_CHECKS_TIMEOUT_MS / 6e4}m)`).option("--preserve-worktree", "keep the local PR worktree/stage/branch for an active multi-issue batch (#1888)").option("--force", "acknowledge and merge past a remaining prunable/severe scratch housekeeping block (advisory kept plans/ never blocks, #3012)")).action(async (number, o) => {
|
|
24932
25295
|
const method = o.rebase ? "--rebase" : o.merge ? "--merge" : "--squash";
|
|
24933
25296
|
const repoArgs = o.repo ? ["--repo", o.repo] : [];
|
|
24934
25297
|
const [headRef, baseRef, headRefOid] = (await execFileP2("gh", ["pr", "view", number, ...repoArgs, "--json", "headRefName,baseRefName,headRefOid", "--jq", '.headRefName + " " + .baseRefName + " " + (.headRefOid // "")'], { timeout: GC_GH_TIMEOUT_MS2 })).stdout.trim().split(/\s+/);
|
|
@@ -24947,6 +25310,7 @@ pr.command("merge <number>").description("merge a PR (squash by default) and cle
|
|
|
24947
25310
|
pollChecks: () => pollRestPrChecks(number, repo),
|
|
24948
25311
|
pollMergeable: () => pollRestPrMergeable(number, repo),
|
|
24949
25312
|
pollRateLimit: () => fetchRestCorePool(),
|
|
25313
|
+
diagnoseFailure: () => diagnoseFailedRestChecks(number, repo),
|
|
24950
25314
|
baseBranch,
|
|
24951
25315
|
sleep: (ms) => new Promise((resolve5) => setTimeout(resolve5, ms)),
|
|
24952
25316
|
log: (message) => console.warn(message),
|
|
@@ -25033,7 +25397,7 @@ pr.command("merge <number>").description("merge a PR (squash by default) and cle
|
|
|
25033
25397
|
localCleanup = await cleanupPrMergeLocalBranch(headRef, {
|
|
25034
25398
|
beforeWorktrees,
|
|
25035
25399
|
startingPath,
|
|
25036
|
-
pathExists: (p) => (0,
|
|
25400
|
+
pathExists: (p) => (0, import_node_fs30.existsSync)(p),
|
|
25037
25401
|
execGit: async (args) => (await execFileP2("git", args, { timeout: GIT_TIMEOUT_MS })).stdout,
|
|
25038
25402
|
teardownWorktreeStage,
|
|
25039
25403
|
deferredStore,
|
|
@@ -25078,7 +25442,7 @@ pr.command("merge <number>").description("merge a PR (squash by default) and cle
|
|
|
25078
25442
|
});
|
|
25079
25443
|
registerQueryCommands(program2);
|
|
25080
25444
|
registerWorktreeCommands(program2);
|
|
25081
|
-
registerIssueLifecycleCommands(program2);
|
|
25445
|
+
registerIssueLifecycleCommands(program2, { attach: attachToProject });
|
|
25082
25446
|
registerTrainCommands(program2);
|
|
25083
25447
|
registerDeployCommands(program2);
|
|
25084
25448
|
registerDiscoveryCommands(program2);
|
|
@@ -25166,7 +25530,8 @@ function renderDeployLine(d) {
|
|
|
25166
25530
|
else if (d.runUrl) parts.push(`run ${d.runUrl}`);
|
|
25167
25531
|
if (d.deployStatus === "success") parts.push("deploy: SUCCEEDED");
|
|
25168
25532
|
else if (d.deployStatus === "failure") parts.push("deploy: FAILED (promotion stands; retry the deploy, do not re-tag)");
|
|
25169
|
-
else if (d.runId != null) parts.push(`deploy: dispatched (watch: gh run watch ${d.runId} --repo mutmutco/MMI-Hub --exit-status)`);
|
|
25533
|
+
else if (d.runId != null) parts.push(`deploy: UNVERIFIED \u2014 dispatched, not resolved (watch: gh run watch ${d.runId} --repo mutmutco/MMI-Hub --exit-status)`);
|
|
25534
|
+
else parts.push("deploy: UNVERIFIED \u2014 no run correlated or watched; resolve every workflow run on the release SHA before calling this release healthy (#3322)");
|
|
25170
25535
|
return parts.join("; ");
|
|
25171
25536
|
}
|
|
25172
25537
|
function renderTrainApply(commandName, r) {
|
|
@@ -25336,7 +25701,7 @@ access.command("role [repo]").description("D14 train authority for a repo (serve
|
|
|
25336
25701
|
const verdict = await fetchTrainAuthority(repo, registryClientDeps(cfg));
|
|
25337
25702
|
if (!verdict.ok) {
|
|
25338
25703
|
if (o.json) {
|
|
25339
|
-
console.log(JSON.stringify({ repo, train: false, error: verdict.error }));
|
|
25704
|
+
console.log(JSON.stringify({ repo, train: false, verified: false, error: verdict.error }));
|
|
25340
25705
|
process.exitCode = 1;
|
|
25341
25706
|
return;
|
|
25342
25707
|
}
|
|
@@ -25344,7 +25709,7 @@ access.command("role [repo]").description("D14 train authority for a repo (serve
|
|
|
25344
25709
|
}
|
|
25345
25710
|
const a = verdict.authority;
|
|
25346
25711
|
if (o.json) {
|
|
25347
|
-
console.log(JSON.stringify(a));
|
|
25712
|
+
console.log(JSON.stringify({ ...a, verified: true }));
|
|
25348
25713
|
if (!a.train) process.exitCode = 1;
|
|
25349
25714
|
return;
|
|
25350
25715
|
}
|
|
@@ -25373,10 +25738,10 @@ access.command("audit").description("audit collaborator roles + train-branch pus
|
|
|
25373
25738
|
targets = resolution.targets;
|
|
25374
25739
|
}
|
|
25375
25740
|
const derivedMatrix = registryProjects ? accessMatrixFromProjects(registryProjects) : {};
|
|
25376
|
-
const fileMatrix = (0,
|
|
25741
|
+
const fileMatrix = (0, import_node_fs30.existsSync)("access-matrix.json") ? loadAccessMatrix((0, import_node_fs30.readFileSync)("access-matrix.json", "utf8")) : {};
|
|
25377
25742
|
const matrix = mergeAccessMatrix(fileMatrix, derivedMatrix);
|
|
25378
25743
|
const derivedContracts = registryProjects ? dataAccessContractsFromProjects(registryProjects) : { consumers: {} };
|
|
25379
|
-
const fileContracts = (0,
|
|
25744
|
+
const fileContracts = (0, import_node_fs30.existsSync)("data-access-contracts.json") ? loadDataAccessContracts((0, import_node_fs30.readFileSync)("data-access-contracts.json", "utf8")) : { consumers: {} };
|
|
25380
25745
|
const dataAccess = mergeDataAccessContracts(fileContracts, derivedContracts);
|
|
25381
25746
|
const report = await auditOrgAccess(targets, deps, matrix, dataAccess);
|
|
25382
25747
|
console.log(o.json ? JSON.stringify(report, null, 2) : renderAccessReport(report));
|
|
@@ -25396,6 +25761,7 @@ program2.command("doctor").description("check onboarding gates and auto-heal CLI
|
|
|
25396
25761
|
banner: opts.banner,
|
|
25397
25762
|
preflight: opts.preflight,
|
|
25398
25763
|
fast: opts.fast || opts.self,
|
|
25764
|
+
self: opts.self,
|
|
25399
25765
|
json: opts.json,
|
|
25400
25766
|
verbose: opts.verbose
|
|
25401
25767
|
},
|
|
@@ -25409,7 +25775,7 @@ function directoryBytes(path2) {
|
|
|
25409
25775
|
let total = 0;
|
|
25410
25776
|
let entries;
|
|
25411
25777
|
try {
|
|
25412
|
-
entries = (0,
|
|
25778
|
+
entries = (0, import_node_fs30.readdirSync)(path2, { withFileTypes: true });
|
|
25413
25779
|
} catch {
|
|
25414
25780
|
return 0;
|
|
25415
25781
|
}
|
|
@@ -25418,7 +25784,7 @@ function directoryBytes(path2) {
|
|
|
25418
25784
|
if (entry.isDirectory()) total += directoryBytes(child2);
|
|
25419
25785
|
else {
|
|
25420
25786
|
try {
|
|
25421
|
-
total += (0,
|
|
25787
|
+
total += (0, import_node_fs30.statSync)(child2).size;
|
|
25422
25788
|
} catch {
|
|
25423
25789
|
}
|
|
25424
25790
|
}
|
|
@@ -25426,25 +25792,25 @@ function directoryBytes(path2) {
|
|
|
25426
25792
|
return total;
|
|
25427
25793
|
}
|
|
25428
25794
|
function listDirEntries(dir) {
|
|
25429
|
-
return (0,
|
|
25795
|
+
return (0, import_node_fs30.readdirSync)(dir, { withFileTypes: true }).map((d) => ({ name: d.name, isDirectory: d.isDirectory() }));
|
|
25430
25796
|
}
|
|
25431
25797
|
function readInstalledPluginRefs(home) {
|
|
25432
25798
|
const p = installedPluginsPath(home);
|
|
25433
|
-
if (!(0,
|
|
25799
|
+
if (!(0, import_node_fs30.existsSync)(p)) return [];
|
|
25434
25800
|
try {
|
|
25435
|
-
return installedPluginPaths((0,
|
|
25801
|
+
return installedPluginPaths((0, import_node_fs30.readFileSync)(p, "utf8"));
|
|
25436
25802
|
} catch {
|
|
25437
25803
|
return null;
|
|
25438
25804
|
}
|
|
25439
25805
|
}
|
|
25440
25806
|
function pluginCacheFsDeps(home, dirBytes) {
|
|
25441
25807
|
return {
|
|
25442
|
-
exists: (p) => (0,
|
|
25443
|
-
listVersionDirs: (root) => (0,
|
|
25808
|
+
exists: (p) => (0, import_node_fs30.existsSync)(p),
|
|
25809
|
+
listVersionDirs: (root) => (0, import_node_fs30.readdirSync)(root, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name),
|
|
25444
25810
|
dirBytes,
|
|
25445
|
-
listStagingDirs: (root) => (0,
|
|
25811
|
+
listStagingDirs: (root) => (0, import_node_fs30.readdirSync)(root, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => {
|
|
25446
25812
|
try {
|
|
25447
|
-
return { name: d.name, mtimeMs: newestMtimeMs((0, import_node_path26.join)(root, d.name), listDirEntries, (p) => (0,
|
|
25813
|
+
return { name: d.name, mtimeMs: newestMtimeMs((0, import_node_path26.join)(root, d.name), listDirEntries, (p) => (0, import_node_fs30.statSync)(p).mtimeMs) };
|
|
25448
25814
|
} catch {
|
|
25449
25815
|
return { name: d.name, mtimeMs: Date.now() };
|
|
25450
25816
|
}
|
|
@@ -25459,9 +25825,9 @@ function stagingApplyFsGuard(home) {
|
|
|
25459
25825
|
referencedPaths: () => readInstalledPluginRefs(home),
|
|
25460
25826
|
mtimeMs: (name) => {
|
|
25461
25827
|
const p = (0, import_node_path26.join)(stagingRoot, name);
|
|
25462
|
-
if (!(0,
|
|
25828
|
+
if (!(0, import_node_fs30.existsSync)(p)) return null;
|
|
25463
25829
|
try {
|
|
25464
|
-
return newestMtimeMs(p, listDirEntries, (q) => (0,
|
|
25830
|
+
return newestMtimeMs(p, listDirEntries, (q) => (0, import_node_fs30.statSync)(q).mtimeMs);
|
|
25465
25831
|
} catch {
|
|
25466
25832
|
return null;
|
|
25467
25833
|
}
|
|
@@ -25477,7 +25843,7 @@ program2.command("plugin-prune").description(`prune stale cached MMI plugin vers
|
|
|
25477
25843
|
{ withBytes: true }
|
|
25478
25844
|
);
|
|
25479
25845
|
const anythingToDelete = plan.prune.length > 0 || plan.staging.length > 0;
|
|
25480
|
-
const result = o.apply && anythingToDelete ? applyPluginCachePlan(plan, (p) => (0,
|
|
25846
|
+
const result = o.apply && anythingToDelete ? applyPluginCachePlan(plan, (p) => (0, import_node_fs30.rmSync)(p, { recursive: true, force: true }), stagingApplyFsGuard((0, import_node_os8.homedir)())) : void 0;
|
|
25481
25847
|
const warnings = plan.prune.length > 0 ? [CONCURRENT_SESSION_WARNING] : [];
|
|
25482
25848
|
if (o.json) console.log(JSON.stringify({ ...plan, warnings, applied: result ?? null }));
|
|
25483
25849
|
else console.log(renderPluginCachePlan(plan, result));
|
|
@@ -25502,7 +25868,7 @@ program2.command("session-start").description("run the SessionStart verbs (whoam
|
|
|
25502
25868
|
hubSession: async () => hubAuthSession({ baseUrl: (await loadConfig()).sagaApiUrl ?? defaultHubUrl(), githubToken }),
|
|
25503
25869
|
ghLogin: githubLogin
|
|
25504
25870
|
});
|
|
25505
|
-
const line = whoamiLine(report);
|
|
25871
|
+
const line = whoamiLine(report, report.login ? ghMultiAccountCaveat(report.login) : void 0);
|
|
25506
25872
|
if (line) io.log(line);
|
|
25507
25873
|
},
|
|
25508
25874
|
boardSlice: (io) => runBoardSlice(io, {
|