@mutmutco/cli 3.49.0 → 3.51.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 +435 -74
- package/package.json +1 -1
package/dist/main.cjs
CHANGED
|
@@ -4478,6 +4478,52 @@ async function runSessionStart(parallel, sequential, io) {
|
|
|
4478
4478
|
for (const lines of buffered) flush(lines, io);
|
|
4479
4479
|
for (const step of sequential) flush(await runBufferedStep(step), io);
|
|
4480
4480
|
}
|
|
4481
|
+
var SESSION_PAYLOAD_CAP_CHARS = 1e4;
|
|
4482
|
+
var SESSION_PAYLOAD_BUDGET_CHARS = 8e3;
|
|
4483
|
+
function sessionPayloadStatus(chars) {
|
|
4484
|
+
if (chars > SESSION_PAYLOAD_CAP_CHARS) return "red";
|
|
4485
|
+
if (chars > SESSION_PAYLOAD_BUDGET_CHARS) return "amber";
|
|
4486
|
+
return "ok";
|
|
4487
|
+
}
|
|
4488
|
+
function measuredSessionIo(io) {
|
|
4489
|
+
let chars = 0;
|
|
4490
|
+
return {
|
|
4491
|
+
io: {
|
|
4492
|
+
log: (text) => {
|
|
4493
|
+
chars += text.length + 1;
|
|
4494
|
+
io.log(text);
|
|
4495
|
+
},
|
|
4496
|
+
err: (text) => io.err(text)
|
|
4497
|
+
},
|
|
4498
|
+
chars: () => chars
|
|
4499
|
+
};
|
|
4500
|
+
}
|
|
4501
|
+
function sessionPayloadStatePath(cwd) {
|
|
4502
|
+
return repoRuntimeStatePath(cwd, "session-payload.json");
|
|
4503
|
+
}
|
|
4504
|
+
function recordSessionPayload(cwd, chars, now = /* @__PURE__ */ new Date()) {
|
|
4505
|
+
try {
|
|
4506
|
+
const path2 = sessionPayloadStatePath(cwd);
|
|
4507
|
+
(0, import_node_fs8.mkdirSync)((0, import_node_path6.dirname)(path2), { recursive: true });
|
|
4508
|
+
const record = { chars, status: sessionPayloadStatus(chars), at: now.toISOString() };
|
|
4509
|
+
(0, import_node_fs8.writeFileSync)(path2, `${JSON.stringify(record)}
|
|
4510
|
+
`, "utf8");
|
|
4511
|
+
} catch {
|
|
4512
|
+
}
|
|
4513
|
+
}
|
|
4514
|
+
function readSessionPayload(cwd) {
|
|
4515
|
+
try {
|
|
4516
|
+
const parsed = JSON.parse((0, import_node_fs8.readFileSync)(sessionPayloadStatePath(cwd), "utf8"));
|
|
4517
|
+
if (typeof parsed?.chars !== "number" || !Number.isFinite(parsed.chars)) return void 0;
|
|
4518
|
+
return {
|
|
4519
|
+
chars: parsed.chars,
|
|
4520
|
+
status: sessionPayloadStatus(parsed.chars),
|
|
4521
|
+
at: typeof parsed.at === "string" ? parsed.at : ""
|
|
4522
|
+
};
|
|
4523
|
+
} catch {
|
|
4524
|
+
return void 0;
|
|
4525
|
+
}
|
|
4526
|
+
}
|
|
4481
4527
|
function buildSessionStartPlan(verbs) {
|
|
4482
4528
|
return {
|
|
4483
4529
|
parallel: [
|
|
@@ -5375,7 +5421,33 @@ function resolveSafeSiblingWorktreeCleanupTarget(worktreePath, siblingRoot, deps
|
|
|
5375
5421
|
}
|
|
5376
5422
|
function siblingMmiWorktreesRoot(repoRoot2) {
|
|
5377
5423
|
const parent = (0, import_node_path7.dirname)(repoRoot2);
|
|
5378
|
-
|
|
5424
|
+
if ((0, import_node_path7.basename)(parent).toLowerCase() === "mmi-worktrees") return parent;
|
|
5425
|
+
const grandparent = (0, import_node_path7.dirname)(parent);
|
|
5426
|
+
if ((0, import_node_path7.basename)(grandparent).toLowerCase() === "mmi-worktrees") return grandparent;
|
|
5427
|
+
return (0, import_node_path7.join)(parent, "mmi-worktrees");
|
|
5428
|
+
}
|
|
5429
|
+
function worktreeScanDirs(root, repoRoot2, listDirs, isRepoCheckout) {
|
|
5430
|
+
const projectsDir = (0, import_node_path7.dirname)(root);
|
|
5431
|
+
const ownName = (0, import_node_path7.basename)(repoRoot2).toLowerCase();
|
|
5432
|
+
const flat = [];
|
|
5433
|
+
let ownContainer = null;
|
|
5434
|
+
for (const dir of listDirs(root)) {
|
|
5435
|
+
const name = (0, import_node_path7.basename)(dir);
|
|
5436
|
+
if (isRepoCheckout((0, import_node_path7.join)(projectsDir, name))) {
|
|
5437
|
+
if (name.toLowerCase() === ownName) ownContainer = dir;
|
|
5438
|
+
continue;
|
|
5439
|
+
}
|
|
5440
|
+
flat.push(dir);
|
|
5441
|
+
}
|
|
5442
|
+
return ownContainer ? [...flat, ...listDirs(ownContainer)] : flat;
|
|
5443
|
+
}
|
|
5444
|
+
function explicitRepoWorktreesRoot(root, repoRoot2, rootDirs) {
|
|
5445
|
+
const repoName = (0, import_node_path7.basename)(repoRoot2);
|
|
5446
|
+
const repoDir = rootDirs.find((name) => name.toLowerCase() === repoName.toLowerCase());
|
|
5447
|
+
return repoDir ? (0, import_node_path7.join)(root, repoDir) : root;
|
|
5448
|
+
}
|
|
5449
|
+
function strayWorktreeRootPaths(container, names, authoritativeRoot) {
|
|
5450
|
+
return names.filter((name) => /worktrees/i.test(name)).map((name) => (0, import_node_path7.join)(container, name)).filter((path2) => !samePath(path2, authoritativeRoot));
|
|
5379
5451
|
}
|
|
5380
5452
|
function classifySiblingWorktreeDir(entry) {
|
|
5381
5453
|
if (!entry.ownedByCurrentRepo) {
|
|
@@ -6235,6 +6307,7 @@ var import_node_child_process5 = require("node:child_process");
|
|
|
6235
6307
|
var OWNER = "mutmutco";
|
|
6236
6308
|
var SSM_ROOT = "/mmi-future";
|
|
6237
6309
|
var PROJECT_TIER_SEGMENT = "dev";
|
|
6310
|
+
var ORG_INFRA_SLUG = "_org";
|
|
6238
6311
|
var KEY_RE = /^(?:[a-z][a-z0-9-]*\/)?[A-Za-z][A-Za-z0-9_]*$/;
|
|
6239
6312
|
function isValidSecretKey(key) {
|
|
6240
6313
|
if (!key || key.length > 256) return false;
|
|
@@ -6445,7 +6518,7 @@ no vault credentials visible`;
|
|
|
6445
6518
|
...lines,
|
|
6446
6519
|
"",
|
|
6447
6520
|
"+ = usable now. Consume keyless: `mmi-cli secrets use <KEY> -- <cmd>` (inject, never printed); validate: `mmi-cli secrets verify <KEY>`. No command prints a raw value (#2844).",
|
|
6448
|
-
|
|
6521
|
+
` Org-infra (#3463): \`--slug\` is ALWAYS \`${ORG_INFRA_SLUG}\`; the provider is the key's FIRST SEGMENT \u2014 \`mmi-cli secrets use cloudflare/GLOBAL_API_KEY --slug ${ORG_INFRA_SLUG} -- <cmd>\` (master-gated).`
|
|
6449
6522
|
].join("\n");
|
|
6450
6523
|
}
|
|
6451
6524
|
async function secretsCapabilities(deps, opts) {
|
|
@@ -6487,6 +6560,81 @@ async function secretsCapabilities(deps, opts) {
|
|
|
6487
6560
|
}
|
|
6488
6561
|
deps.log(formatCapabilities(report));
|
|
6489
6562
|
}
|
|
6563
|
+
var PROBE_TIMEOUT_MS = 4e3;
|
|
6564
|
+
async function probeCapabilities(deps, repo) {
|
|
6565
|
+
try {
|
|
6566
|
+
const qs = new URLSearchParams({ repo }).toString();
|
|
6567
|
+
const res = await deps.fetch(`${deps.apiUrl}/secrets/capabilities?${qs}`, {
|
|
6568
|
+
method: "GET",
|
|
6569
|
+
headers: await deps.headers(),
|
|
6570
|
+
signal: AbortSignal.timeout(PROBE_TIMEOUT_MS)
|
|
6571
|
+
});
|
|
6572
|
+
if (!res.ok) return void 0;
|
|
6573
|
+
return await res.json();
|
|
6574
|
+
} catch {
|
|
6575
|
+
return void 0;
|
|
6576
|
+
}
|
|
6577
|
+
}
|
|
6578
|
+
function secretKeyLeaf(key) {
|
|
6579
|
+
const slash = key.lastIndexOf("/");
|
|
6580
|
+
return slash === -1 ? key : key.slice(slash + 1);
|
|
6581
|
+
}
|
|
6582
|
+
function resolveNotFoundGuidance(input) {
|
|
6583
|
+
const { key, repo, slug, report } = input;
|
|
6584
|
+
const tried = secretParamName(slug ?? "<repo-slug>", key);
|
|
6585
|
+
const isMaster = report?.role === "master";
|
|
6586
|
+
const mnemonic = `Org-infra shape: \`--slug\` is ALWAYS \`${ORG_INFRA_SLUG}\`; the provider is the key's FIRST SEGMENT (e.g. \`cloudflare/API_TOKEN --slug ${ORG_INFRA_SLUG}\`).`;
|
|
6587
|
+
const escalate = (target) => target && target.slug === ORG_INFRA_SLUG ? `Approved escalation: mmi-cli secrets request ${key} --repo ${repo} --reason "org-infra grant for ${target.scope} (key ${target.key}, slug ${ORG_INFRA_SLUG}) \u2014 <why it is needed>"` : `Approved escalation: mmi-cli secrets request ${key} --repo ${repo} --reason "<why it is needed>"`;
|
|
6588
|
+
const leaf = secretKeyLeaf(key);
|
|
6589
|
+
const sameLeaf = (report?.capabilities ?? []).filter(
|
|
6590
|
+
(c) => secretKeyLeaf(c.key) === leaf && !(c.slug === slug && c.key === key)
|
|
6591
|
+
);
|
|
6592
|
+
const exact = sameLeaf.filter((c) => slug ? c.key === `${slug}/${leaf}` : false);
|
|
6593
|
+
const unambiguous = exact.length === 1 ? exact : sameLeaf.length === 1 ? sameLeaf : [];
|
|
6594
|
+
const usable = unambiguous.find((c) => c.accessible);
|
|
6595
|
+
if (usable) {
|
|
6596
|
+
return {
|
|
6597
|
+
reachable: true,
|
|
6598
|
+
lines: [
|
|
6599
|
+
`Secret ${key} was not found at ${tried}.`,
|
|
6600
|
+
`It lives at ${usable.scope} and you can already consume it \u2014 no request needed:`,
|
|
6601
|
+
` mmi-cli secrets use ${usable.key} --slug ${usable.slug} -- <cmd>`,
|
|
6602
|
+
mnemonic
|
|
6603
|
+
]
|
|
6604
|
+
};
|
|
6605
|
+
}
|
|
6606
|
+
if (!unambiguous.length && sameLeaf.length > 1) {
|
|
6607
|
+
const lines2 = [
|
|
6608
|
+
`Secret ${key} was not found at ${tried}.`,
|
|
6609
|
+
`${sameLeaf.length} different keys are named ${leaf} \u2014 pick the one you meant, this command cannot:`,
|
|
6610
|
+
...sameLeaf.map((c) => ` ${c.accessible ? "+" : " "} mmi-cli secrets use ${c.key} --slug ${c.slug} -- <cmd> (${c.scope})`),
|
|
6611
|
+
mnemonic
|
|
6612
|
+
];
|
|
6613
|
+
if (!isMaster) lines2.push(escalate(sameLeaf.find((c) => !c.accessible)));
|
|
6614
|
+
return { reachable: false, lines: lines2 };
|
|
6615
|
+
}
|
|
6616
|
+
if (unambiguous.length) {
|
|
6617
|
+
const other = unambiguous[0];
|
|
6618
|
+
const lines2 = [
|
|
6619
|
+
`Secret ${key} was not found at ${tried}.`,
|
|
6620
|
+
`A key named ${leaf} exists at ${other.scope}, but it is not granted to you (this is an ACCESS gap, not a missing key).`
|
|
6621
|
+
];
|
|
6622
|
+
if (isMaster) lines2.push("You are master \u2014 grant yourself directly: `mmi-cli secrets grant <repo> <login> <provider>/<KEY>`.");
|
|
6623
|
+
else lines2.push(escalate(other));
|
|
6624
|
+
return { reachable: false, lines: lines2 };
|
|
6625
|
+
}
|
|
6626
|
+
const lines = [
|
|
6627
|
+
`Secret ${key} was not found at ${tried}, and no key named ${leaf} is visible to you.`,
|
|
6628
|
+
`Locate it by intent first: \`mmi-cli secrets find "<what it is for>"\` \u2014 it returns the exact keyless-use command.`,
|
|
6629
|
+
mnemonic
|
|
6630
|
+
];
|
|
6631
|
+
if (isMaster) {
|
|
6632
|
+
lines.push("You are master: if it truly does not exist yet, create it with `mmi-cli secrets set` rather than requesting a grant from yourself.");
|
|
6633
|
+
} else {
|
|
6634
|
+
lines.push(escalate());
|
|
6635
|
+
}
|
|
6636
|
+
return { reachable: false, lines };
|
|
6637
|
+
}
|
|
6490
6638
|
async function fetchCatalog(deps, label, repo, extra) {
|
|
6491
6639
|
const qs = new URLSearchParams({ repo, ...extra }).toString();
|
|
6492
6640
|
let res;
|
|
@@ -6783,6 +6931,16 @@ async function secretsRequest(deps, key, opts) {
|
|
|
6783
6931
|
return false;
|
|
6784
6932
|
}
|
|
6785
6933
|
const repo = await targetRepo(deps, opts);
|
|
6934
|
+
const requester = await probeCapabilities(deps, repo);
|
|
6935
|
+
if (requester?.role === "master") {
|
|
6936
|
+
deps.err(`secrets request: you are master on ${repo} \u2014 a request would file an issue and notify yourself.`);
|
|
6937
|
+
const candidates = (requester.capabilities ?? []).filter((c) => secretKeyLeaf(c.key) === secretKeyLeaf(key) && c.accessible);
|
|
6938
|
+
const reachable = candidates.length === 1 ? candidates[0] : void 0;
|
|
6939
|
+
if (reachable) deps.err(`${key} is already reachable: \`mmi-cli secrets use ${reachable.key} --slug ${reachable.slug} -- <cmd>\``);
|
|
6940
|
+
else if (candidates.length > 1) deps.err(`${candidates.length} keys are named ${secretKeyLeaf(key)} \u2014 pick one with \`mmi-cli secrets find "<what it is for>"\`.`);
|
|
6941
|
+
else deps.err(`Grant directly with \`mmi-cli secrets grant <repo> <login> <provider>/<KEY>\`, or create it with \`mmi-cli secrets set\` if it does not exist yet.`);
|
|
6942
|
+
return false;
|
|
6943
|
+
}
|
|
6786
6944
|
const res = await deps.fetch(`${deps.apiUrl}/secrets/request`, {
|
|
6787
6945
|
method: "POST",
|
|
6788
6946
|
headers: await deps.headers({ "content-type": "application/json" }),
|
|
@@ -7151,8 +7309,8 @@ async function secretsUse(deps, key, opts) {
|
|
|
7151
7309
|
if (!res.ok) {
|
|
7152
7310
|
const body = await readJsonBody(res);
|
|
7153
7311
|
if (res.status === 404 && body.code === "secret_not_found") {
|
|
7154
|
-
|
|
7155
|
-
|
|
7312
|
+
const guidance = resolveNotFoundGuidance({ key, repo, slug, report: await probeCapabilities(deps, repo) });
|
|
7313
|
+
for (const line of guidance.lines) deps.err(line);
|
|
7156
7314
|
return false;
|
|
7157
7315
|
}
|
|
7158
7316
|
deps.err(
|
|
@@ -7377,18 +7535,37 @@ async function preservedBranches() {
|
|
|
7377
7535
|
return [];
|
|
7378
7536
|
}
|
|
7379
7537
|
}
|
|
7380
|
-
async function siblingWorktreeDirs() {
|
|
7538
|
+
async function siblingWorktreeDirs(explicitRoot) {
|
|
7381
7539
|
const repoRoot2 = (await execFileP2("git", ["rev-parse", "--show-toplevel"], { timeout: GIT_TIMEOUT_MS }).catch(() => ({ stdout: "" }))).stdout.trim() || process.cwd();
|
|
7382
7540
|
const worktreeGitRoot = await currentRepoWorktreeGitRoot(repoRoot2);
|
|
7383
|
-
const
|
|
7541
|
+
const primaryRepoRoot = worktreeGitRoot ? (0, import_node_path8.dirname)((0, import_node_path8.dirname)(worktreeGitRoot)) : repoRoot2;
|
|
7542
|
+
try {
|
|
7543
|
+
const dirs = explicitRoot ? listDirsIn(resolveExplicitScanRoot(explicitRoot, primaryRepoRoot)) : worktreeScanDirs(siblingMmiWorktreesRoot(primaryRepoRoot), primaryRepoRoot, listDirsIn, isRepoCheckoutDir);
|
|
7544
|
+
return dirs.map((dir) => inspectSiblingWorktreeDir(dir, worktreeGitRoot)).filter((entry) => Boolean(entry));
|
|
7545
|
+
} catch {
|
|
7546
|
+
return [];
|
|
7547
|
+
}
|
|
7548
|
+
}
|
|
7549
|
+
function listDirsIn(dir) {
|
|
7384
7550
|
try {
|
|
7385
|
-
|
|
7386
|
-
return entries.filter((ent) => ent.isDirectory()).map((ent) => inspectSiblingWorktreeDir((0, import_node_path8.join)(siblingRoot, ent.name), worktreeGitRoot)).filter((entry) => Boolean(entry));
|
|
7551
|
+
return (0, import_node_fs10.readdirSync)(dir, { withFileTypes: true }).filter((ent) => ent.isDirectory()).map((ent) => (0, import_node_path8.join)(dir, ent.name));
|
|
7387
7552
|
} catch {
|
|
7388
7553
|
return [];
|
|
7389
7554
|
}
|
|
7390
7555
|
}
|
|
7391
|
-
|
|
7556
|
+
function isRepoCheckoutDir(dir) {
|
|
7557
|
+
return (0, import_node_fs10.existsSync)((0, import_node_path8.join)(dir, ".git"));
|
|
7558
|
+
}
|
|
7559
|
+
function resolveExplicitScanRoot(explicitRoot, repoRoot2) {
|
|
7560
|
+
let rootDirs;
|
|
7561
|
+
try {
|
|
7562
|
+
rootDirs = (0, import_node_fs10.readdirSync)(explicitRoot, { withFileTypes: true }).filter((ent) => ent.isDirectory()).map((ent) => ent.name);
|
|
7563
|
+
} catch {
|
|
7564
|
+
return explicitRoot;
|
|
7565
|
+
}
|
|
7566
|
+
return explicitRepoWorktreesRoot(explicitRoot, repoRoot2, rootDirs);
|
|
7567
|
+
}
|
|
7568
|
+
async function gcPlan(remote, limit, opts = {}) {
|
|
7392
7569
|
const [branches, heads, current, stale, prs, worktrees, siblingDirs, preserved] = await Promise.all([
|
|
7393
7570
|
gitOut(["branch", "--format=%(refname:short)"]),
|
|
7394
7571
|
localBranchHeads(),
|
|
@@ -7398,7 +7575,7 @@ async function gcPlan(remote, limit) {
|
|
|
7398
7575
|
}),
|
|
7399
7576
|
ghPrs(limit),
|
|
7400
7577
|
worktreeBranches(),
|
|
7401
|
-
siblingWorktreeDirs(),
|
|
7578
|
+
siblingWorktreeDirs(opts.root),
|
|
7402
7579
|
preservedBranches()
|
|
7403
7580
|
]);
|
|
7404
7581
|
return buildGcPlan({
|
|
@@ -7413,6 +7590,62 @@ async function gcPlan(remote, limit) {
|
|
|
7413
7590
|
preservedBranches: preserved
|
|
7414
7591
|
});
|
|
7415
7592
|
}
|
|
7593
|
+
var STRAY_ROOT_WALK_BUDGET_MS = 4e3;
|
|
7594
|
+
function measureWorktreeRoot(root, deadline) {
|
|
7595
|
+
let dirs = 0;
|
|
7596
|
+
let bytes = 0;
|
|
7597
|
+
let partial = false;
|
|
7598
|
+
const stack = [root];
|
|
7599
|
+
let depth0 = true;
|
|
7600
|
+
while (stack.length) {
|
|
7601
|
+
if (Date.now() > deadline) {
|
|
7602
|
+
partial = true;
|
|
7603
|
+
break;
|
|
7604
|
+
}
|
|
7605
|
+
const current = stack.pop();
|
|
7606
|
+
let entries;
|
|
7607
|
+
try {
|
|
7608
|
+
entries = (0, import_node_fs10.readdirSync)(current, { withFileTypes: true });
|
|
7609
|
+
} catch {
|
|
7610
|
+
continue;
|
|
7611
|
+
}
|
|
7612
|
+
for (const ent of entries) {
|
|
7613
|
+
const child2 = (0, import_node_path8.join)(current, ent.name);
|
|
7614
|
+
if (ent.isDirectory()) {
|
|
7615
|
+
if (depth0) dirs++;
|
|
7616
|
+
let isLink = false;
|
|
7617
|
+
try {
|
|
7618
|
+
(0, import_node_fs10.readlinkSync)(child2);
|
|
7619
|
+
isLink = true;
|
|
7620
|
+
} catch {
|
|
7621
|
+
}
|
|
7622
|
+
if (!isLink) stack.push(child2);
|
|
7623
|
+
} else if (ent.isFile()) {
|
|
7624
|
+
try {
|
|
7625
|
+
bytes += (0, import_node_fs10.statSync)(child2).size;
|
|
7626
|
+
} catch {
|
|
7627
|
+
}
|
|
7628
|
+
}
|
|
7629
|
+
}
|
|
7630
|
+
depth0 = false;
|
|
7631
|
+
}
|
|
7632
|
+
return partial ? { dirs, bytes, partial } : { dirs, bytes };
|
|
7633
|
+
}
|
|
7634
|
+
function worktreeRootsProbe(repoRoot2) {
|
|
7635
|
+
const authoritative = siblingMmiWorktreesRoot(repoRoot2);
|
|
7636
|
+
const container = (0, import_node_path8.dirname)(authoritative);
|
|
7637
|
+
let names;
|
|
7638
|
+
try {
|
|
7639
|
+
names = (0, import_node_fs10.readdirSync)(container, { withFileTypes: true }).filter((ent) => ent.isDirectory()).map((ent) => ent.name);
|
|
7640
|
+
} catch {
|
|
7641
|
+
return { authoritative, stray: [] };
|
|
7642
|
+
}
|
|
7643
|
+
const deadline = Date.now() + STRAY_ROOT_WALK_BUDGET_MS;
|
|
7644
|
+
return {
|
|
7645
|
+
authoritative,
|
|
7646
|
+
stray: strayWorktreeRootPaths(container, names, authoritative).map((path2) => ({ path: path2, ...measureWorktreeRoot(path2, deadline) }))
|
|
7647
|
+
};
|
|
7648
|
+
}
|
|
7416
7649
|
|
|
7417
7650
|
// src/repo-resolve.ts
|
|
7418
7651
|
function slugOf(repoOrSlug) {
|
|
@@ -8889,7 +9122,7 @@ async function provisionWorktree(worktreeRoot, deps) {
|
|
|
8889
9122
|
}
|
|
8890
9123
|
function defaultWorktreePath(repoRoot2, branch) {
|
|
8891
9124
|
const safe = branch.replace(/[/\\]+/g, "-");
|
|
8892
|
-
return (0, import_node_path11.join)((0, import_node_path11.dirname)(repoRoot2), "mmi-worktrees", safe);
|
|
9125
|
+
return (0, import_node_path11.join)((0, import_node_path11.dirname)(repoRoot2), "mmi-worktrees", (0, import_node_path11.basename)(repoRoot2), safe);
|
|
8893
9126
|
}
|
|
8894
9127
|
async function primaryCheckoutRootOf(git) {
|
|
8895
9128
|
try {
|
|
@@ -9000,7 +9233,7 @@ function commandLadderHint() {
|
|
|
9000
9233
|
}
|
|
9001
9234
|
|
|
9002
9235
|
// src/index.ts
|
|
9003
|
-
var
|
|
9236
|
+
var import_node_path27 = require("node:path");
|
|
9004
9237
|
|
|
9005
9238
|
// src/merge-ci-policy.ts
|
|
9006
9239
|
function resolveMergeCiPolicy(input) {
|
|
@@ -9453,7 +9686,7 @@ var MANAGED_GITIGNORE_LINES = [
|
|
|
9453
9686
|
// and `.cursor/rules/` because a repo may want its rules tracked despite the wall.
|
|
9454
9687
|
".codex/",
|
|
9455
9688
|
".agents/",
|
|
9456
|
-
// #2321 doctrine: the canonical worktree home is the SIBLING `../mmi-worktrees/<branch>` (outside the tree,
|
|
9689
|
+
// #2321 doctrine: the canonical worktree home is the SIBLING `../mmi-worktrees/<RepoName>/<branch>` (outside the tree,
|
|
9457
9690
|
// via `mmi-cli worktree create`), so a repo-local `.worktrees/` is NOT un-ignored org-wide — `mmi-cli
|
|
9458
9691
|
// doctor` flags one explicitly instead (buildRepoLocalWorktreeCheck) rather than baking the fallback path
|
|
9459
9692
|
// into every repo's .gitignore.
|
|
@@ -11793,6 +12026,10 @@ var PATH_OVERRIDES = {
|
|
|
11793
12026
|
"secrets grant": { category: "admin", discovery: "all-only", help_group: "Operations" },
|
|
11794
12027
|
"secrets revoke": { category: "admin", discovery: "all-only", help_group: "Operations" }
|
|
11795
12028
|
};
|
|
12029
|
+
var DEFAULT_ADMIN_MARKER = "(master-only)";
|
|
12030
|
+
var ADMIN_MARKERS = {
|
|
12031
|
+
"secrets org-catalog": "(writes master-only)"
|
|
12032
|
+
};
|
|
11796
12033
|
var HIDDEN_OPTIONS = {
|
|
11797
12034
|
"worktree create": ["--base"],
|
|
11798
12035
|
"org project set": ["--set"]
|
|
@@ -11837,9 +12074,15 @@ function classifyTree(command, path2, inherited, hideFromParent) {
|
|
|
11837
12074
|
const override = PATH_OVERRIDES[path2];
|
|
11838
12075
|
const metadata = override ?? inherited;
|
|
11839
12076
|
setCommandMetadata(command, metadata);
|
|
11840
|
-
|
|
12077
|
+
const hideByOverride = override !== void 0 && metadata.discovery === "hidden";
|
|
12078
|
+
if ((hideByOverride || hideFromParent) && metadata.discovery !== "primary") {
|
|
11841
12079
|
command._hidden = true;
|
|
11842
12080
|
}
|
|
12081
|
+
if (override !== void 0 && metadata.discovery === "all-only" && metadata.category === "admin" && !command.summary()) {
|
|
12082
|
+
const marker = ADMIN_MARKERS[path2] ?? DEFAULT_ADMIN_MARKER;
|
|
12083
|
+
const described = command.description();
|
|
12084
|
+
command.summary(described ? `${marker} ${described}` : marker);
|
|
12085
|
+
}
|
|
11843
12086
|
for (const option of command.options) {
|
|
11844
12087
|
if ((HIDDEN_OPTIONS[path2] ?? []).includes(option.long ?? option.short ?? "")) option.hideHelp();
|
|
11845
12088
|
}
|
|
@@ -17487,9 +17730,9 @@ function extractWorkflowCrons(yamlText) {
|
|
|
17487
17730
|
function workflowEntry(repo, workflowPath, yamlText) {
|
|
17488
17731
|
const crons = extractWorkflowCrons(yamlText);
|
|
17489
17732
|
if (!crons.length) return null;
|
|
17490
|
-
const
|
|
17733
|
+
const basename4 = workflowPath.split("/").pop() ?? workflowPath;
|
|
17491
17734
|
return {
|
|
17492
|
-
name: `${repo}/${
|
|
17735
|
+
name: `${repo}/${basename4.replace(/\.ya?ml$/, "")}`,
|
|
17493
17736
|
cadence: crons.join(" + "),
|
|
17494
17737
|
executor: "github-actions",
|
|
17495
17738
|
llm: llmFromHeader(yamlText),
|
|
@@ -17521,16 +17764,30 @@ function awsRuleEntries(payload) {
|
|
|
17521
17764
|
}
|
|
17522
17765
|
return entries;
|
|
17523
17766
|
}
|
|
17767
|
+
function lambdaTargetFromArn(arn) {
|
|
17768
|
+
const marker = ":function:";
|
|
17769
|
+
const i = arn.indexOf(marker);
|
|
17770
|
+
if (i < 0) return { functionName: arn.split(":").pop() ?? "" };
|
|
17771
|
+
const rest = arn.slice(i + marker.length);
|
|
17772
|
+
const q = rest.indexOf(":");
|
|
17773
|
+
if (q < 0) return { functionName: rest };
|
|
17774
|
+
return { functionName: rest.slice(0, q), qualifier: rest.slice(q + 1) };
|
|
17775
|
+
}
|
|
17524
17776
|
function awsScheduleEntry(payload) {
|
|
17525
17777
|
if (!payload || typeof payload !== "object") return null;
|
|
17526
17778
|
const { Name, ScheduleExpression, State, Target, Description } = payload;
|
|
17527
17779
|
if (typeof Name !== "string" || typeof ScheduleExpression !== "string") return null;
|
|
17528
17780
|
if (State !== "ENABLED" || isJervResource(Name)) return null;
|
|
17529
17781
|
let target = "";
|
|
17782
|
+
let qualifier;
|
|
17530
17783
|
let scheduleId;
|
|
17531
17784
|
if (Target && typeof Target === "object") {
|
|
17532
17785
|
const { Arn, Input } = Target;
|
|
17533
|
-
if (typeof Arn === "string")
|
|
17786
|
+
if (typeof Arn === "string") {
|
|
17787
|
+
const parsed = lambdaTargetFromArn(Arn);
|
|
17788
|
+
target = parsed.functionName;
|
|
17789
|
+
qualifier = parsed.qualifier;
|
|
17790
|
+
}
|
|
17534
17791
|
if (typeof Input === "string") {
|
|
17535
17792
|
try {
|
|
17536
17793
|
const parsed = JSON.parse(Input);
|
|
@@ -17539,13 +17796,15 @@ function awsScheduleEntry(payload) {
|
|
|
17539
17796
|
}
|
|
17540
17797
|
}
|
|
17541
17798
|
}
|
|
17799
|
+
const sourceBase = `aws scheduler schedule ${Name} (eu-central-1)`;
|
|
17800
|
+
const source = qualifier && target ? `${sourceBase} \u2192 ${target}:${qualifier}` : sourceBase;
|
|
17542
17801
|
return {
|
|
17543
17802
|
name: Name,
|
|
17544
17803
|
cadence: ScheduleExpression,
|
|
17545
17804
|
executor: target ? `aws-scheduler \u2192 ${target}` : "aws-scheduler",
|
|
17546
17805
|
llm: readLlmDeclaration(typeof Description === "string" ? Description : void 0),
|
|
17547
17806
|
resolved: "live",
|
|
17548
|
-
source
|
|
17807
|
+
source,
|
|
17549
17808
|
...scheduleId ? { scheduleId } : {}
|
|
17550
17809
|
};
|
|
17551
17810
|
}
|
|
@@ -17701,7 +17960,7 @@ function strayCronDrifts(workflows) {
|
|
|
17701
17960
|
function unlauncheredLlmDrift(entry) {
|
|
17702
17961
|
if (entry.llm !== "yes") return null;
|
|
17703
17962
|
if (isHarbourLlmLauncher(entry.executor)) return null;
|
|
17704
|
-
if (entry.executor
|
|
17963
|
+
if (entry.executor === "aws-scheduler \u2192 mmi-hub-schedule-dispatcher") return null;
|
|
17705
17964
|
const allowed = [...HARBOUR_LLM_LAUNCHERS].join(", ");
|
|
17706
17965
|
return {
|
|
17707
17966
|
class: "unlaunchered-llm",
|
|
@@ -17794,8 +18053,8 @@ async function repoWorkflowEntries(client, repo) {
|
|
|
17794
18053
|
);
|
|
17795
18054
|
if (typeof contents?.content !== "string" || contents.encoding !== "base64") continue;
|
|
17796
18055
|
const text = Buffer.from(contents.content, "base64").toString("utf8");
|
|
17797
|
-
const
|
|
17798
|
-
const name = `${repo}/${
|
|
18056
|
+
const basename4 = wf.path.split("/").pop() ?? wf.path;
|
|
18057
|
+
const name = `${repo}/${basename4.replace(/\.ya?ml$/, "")}`;
|
|
17799
18058
|
workflows.push({ name, yamlText: text });
|
|
17800
18059
|
const entry = workflowEntry(repo, wf.path, text);
|
|
17801
18060
|
if (entry) entries.push(entry);
|
|
@@ -18018,8 +18277,8 @@ function scheduleRecordFromWorkflow(repo, workflowPath, yamlText) {
|
|
|
18018
18277
|
const crons = extractWorkflowCrons(yamlText);
|
|
18019
18278
|
const header = parseScheduleHeader(yamlText);
|
|
18020
18279
|
if (!crons.length && !header.schedule) return null;
|
|
18021
|
-
const
|
|
18022
|
-
const expectedId = `${repo}/${
|
|
18280
|
+
const basename4 = workflowPath.split("/").pop() ?? workflowPath;
|
|
18281
|
+
const expectedId = `${repo}/${basename4.replace(/\.ya?ml$/, "")}`;
|
|
18023
18282
|
const missing = SCHEDULE_HEADER_FIELDS.filter((f) => !header[f]);
|
|
18024
18283
|
if (missing.length) {
|
|
18025
18284
|
throw new Error(`schedules lift: ${expectedId} (${workflowPath}) is a scheduled workflow but is missing the eight-field entry-template header field(s): ${missing.join(", ")} \u2014 see docs/schedules.md "The entry template".`);
|
|
@@ -18207,7 +18466,9 @@ function planInfraTunnel(hostname, upstream) {
|
|
|
18207
18466
|
].join("\n");
|
|
18208
18467
|
const steps = [
|
|
18209
18468
|
"Install cloudflared on the box (or run locally for dev-only tunnels).",
|
|
18210
|
-
|
|
18469
|
+
// #3463: `--slug <provider> <KEY>` is the retired shape and resolves to nothing. Org-infra is one
|
|
18470
|
+
// slug (`_org`) with the provider as the key's first segment.
|
|
18471
|
+
"Fetch CF account creds (master-gated): mmi-cli secrets use cloudflare/API_TOKEN --slug _org -- <cmd>",
|
|
18211
18472
|
"Create tunnel: cloudflared tunnel create " + tunnelName,
|
|
18212
18473
|
"Route DNS: cloudflared tunnel route dns " + tunnelName + " " + host,
|
|
18213
18474
|
"Write config (below) to /etc/cloudflared/config.yml and run: cloudflared tunnel run " + tunnelName,
|
|
@@ -18232,7 +18493,7 @@ function formatInfraTunnelPlan(plan) {
|
|
|
18232
18493
|
function registerEdgeCommands(program3) {
|
|
18233
18494
|
const edge = program3.command("edge").description("infra-service edge helpers (non-tenant loopback services)");
|
|
18234
18495
|
const tunnel = edge.command("tunnel").description("Cloudflare Tunnel planning for loopback infra services");
|
|
18235
|
-
tunnel.command("plan").description("print a cloudflared tunnel plan (no API calls \u2014
|
|
18496
|
+
tunnel.command("plan").description("print a cloudflared tunnel plan (no API calls \u2014 pair with `secrets use cloudflare/API_TOKEN --slug _org`)").requiredOption("--hostname <host>", "public hostname (e.g. open-meteo.tools.mutatismutandis.co)").requiredOption("--upstream <url>", "loopback origin (e.g. http://127.0.0.1:8080)").option("--json", "machine-readable output").action((o) => {
|
|
18236
18497
|
try {
|
|
18237
18498
|
const plan = planInfraTunnel(o.hostname, o.upstream);
|
|
18238
18499
|
if (o.json) {
|
|
@@ -20129,6 +20390,7 @@ function registerBoardCommands(program3) {
|
|
|
20129
20390
|
// src/merge-cleanup.ts
|
|
20130
20391
|
var import_node_fs24 = require("node:fs");
|
|
20131
20392
|
var import_promises5 = require("node:fs/promises");
|
|
20393
|
+
var import_node_path22 = require("node:path");
|
|
20132
20394
|
var import_node_child_process11 = require("node:child_process");
|
|
20133
20395
|
|
|
20134
20396
|
// src/board-advance.ts
|
|
@@ -20438,7 +20700,7 @@ function assertPrMergeHousekeepingClean(startingPath, context, options = {}) {
|
|
|
20438
20700
|
if (verdict.blocked) throw new Error(verdict.reason);
|
|
20439
20701
|
return housekeeping;
|
|
20440
20702
|
}
|
|
20441
|
-
async function applyGcPlan(plan, remote) {
|
|
20703
|
+
async function applyGcPlan(plan, remote, opts = {}) {
|
|
20442
20704
|
const result = { removedBranches: [], removedRemoteBranches: [], removedTrackingRefs: [], removedWorktreeDirs: [], failed: [], pruned: false };
|
|
20443
20705
|
const beforeWorktrees = parseWorktreePorcelain(
|
|
20444
20706
|
(await execFileP2("git", ["worktree", "list", "--porcelain"], { timeout: GIT_TIMEOUT_MS }).catch(() => ({ stdout: "" }))).stdout
|
|
@@ -20472,8 +20734,9 @@ async function applyGcPlan(plan, remote) {
|
|
|
20472
20734
|
result.failed.push(...branchTracking.failed);
|
|
20473
20735
|
const removeDeps = worktreeRemoveDeps(async (args) => (await execFileP2("git", args, { timeout: GIT_TIMEOUT_MS })).stdout);
|
|
20474
20736
|
const repoRoot2 = (await execFileP2("git", ["rev-parse", "--show-toplevel"], { timeout: GIT_TIMEOUT_MS }).catch(() => ({ stdout: "" }))).stdout.trim() || process.cwd();
|
|
20475
|
-
const siblingRoot = siblingMmiWorktreesRoot(repoRoot2);
|
|
20476
20737
|
const worktreeGitRoot = await currentRepoWorktreeGitRoot(repoRoot2);
|
|
20738
|
+
const primaryRepoRoot = worktreeGitRoot ? (0, import_node_path22.dirname)((0, import_node_path22.dirname)(worktreeGitRoot)) : repoRoot2;
|
|
20739
|
+
const siblingRoot = opts.root ? resolveExplicitScanRoot(opts.root, primaryRepoRoot) : siblingMmiWorktreesRoot(primaryRepoRoot);
|
|
20477
20740
|
for (const wt of plan.worktreeDirs) {
|
|
20478
20741
|
try {
|
|
20479
20742
|
const cleanupTarget = resolveSafeSiblingWorktreeCleanupTarget(wt.path, siblingRoot, {
|
|
@@ -20906,7 +21169,7 @@ async function fetchRestCorePool(gh = defaultGhApi) {
|
|
|
20906
21169
|
|
|
20907
21170
|
// src/worktree-lifecycle-commands.ts
|
|
20908
21171
|
var import_node_fs25 = require("node:fs");
|
|
20909
|
-
var
|
|
21172
|
+
var import_node_path23 = require("node:path");
|
|
20910
21173
|
var GH_TIMEOUT_MS = 2e4;
|
|
20911
21174
|
var DEFAULT_BASE = "origin/development";
|
|
20912
21175
|
var DEFAULT_REMOTE = "origin";
|
|
@@ -21009,7 +21272,7 @@ function classifyStaleLeaks(input) {
|
|
|
21009
21272
|
var defaultOrphanDirScanDeps = {
|
|
21010
21273
|
listDirs: (root) => {
|
|
21011
21274
|
try {
|
|
21012
|
-
return (0, import_node_fs25.readdirSync)(root, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => (0,
|
|
21275
|
+
return (0, import_node_fs25.readdirSync)(root, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => (0, import_node_path23.join)(root, e.name));
|
|
21013
21276
|
} catch {
|
|
21014
21277
|
return [];
|
|
21015
21278
|
}
|
|
@@ -21065,13 +21328,13 @@ function registerWorktreeCommands(program3) {
|
|
|
21065
21328
|
if (PROTECTED_BRANCHES2.has(branch)) {
|
|
21066
21329
|
return fail(`worktree land: refusing to land protected branch '${branch}'`, { code: ERROR_CODES.ERR_BAD_ENUM });
|
|
21067
21330
|
}
|
|
21068
|
-
const gitFile = (0,
|
|
21331
|
+
const gitFile = (0, import_node_path23.join)(wtPath, ".git");
|
|
21069
21332
|
const isLinked = (0, import_node_fs25.existsSync)(gitFile) && (0, import_node_fs25.statSync)(gitFile).isFile();
|
|
21070
21333
|
if (apply && !isLinked) {
|
|
21071
21334
|
return fail("worktree land: run from inside the linked worktree you want to land (this is the primary checkout)");
|
|
21072
21335
|
}
|
|
21073
21336
|
const commonDir = (await execFileP2("git", ["rev-parse", "--git-common-dir"], { timeout: GIT_TIMEOUT_MS }).catch(() => ({ stdout: "" }))).stdout.trim();
|
|
21074
|
-
const primaryCheckout = commonDir ? (0,
|
|
21337
|
+
const primaryCheckout = commonDir ? (0, import_node_path23.dirname)(commonDir) : wtPath;
|
|
21075
21338
|
const stageSummary = readStageSummary(wtPath);
|
|
21076
21339
|
const hasStage = stageSummary != null;
|
|
21077
21340
|
const steps = landSteps(branch, true, hasStage);
|
|
@@ -21181,10 +21444,15 @@ async function gatherWorktreeContext() {
|
|
|
21181
21444
|
const s = readStageSummary(wt.path);
|
|
21182
21445
|
if (s) stages.push({ path: wt.path, port: s.port });
|
|
21183
21446
|
}
|
|
21184
|
-
const
|
|
21447
|
+
const worktreeGitRoot = await currentRepoWorktreeGitRoot(repoRoot2);
|
|
21448
|
+
const primaryRepoRoot = worktreeGitRoot ? (0, import_node_path23.dirname)((0, import_node_path23.dirname)(worktreeGitRoot)) : repoRoot2;
|
|
21449
|
+
const wtRoot = siblingMmiWorktreesRoot(primaryRepoRoot);
|
|
21185
21450
|
let orphanDirs = [];
|
|
21186
21451
|
if ((0, import_node_fs25.existsSync)(wtRoot)) {
|
|
21187
|
-
orphanDirs = scanOrphanDirs(wtRoot,
|
|
21452
|
+
orphanDirs = scanOrphanDirs(wtRoot, worktreeGitRoot, {
|
|
21453
|
+
...defaultOrphanDirScanDeps,
|
|
21454
|
+
listDirs: (root) => worktreeScanDirs(root, primaryRepoRoot, defaultOrphanDirScanDeps.listDirs, isRepoCheckoutDir)
|
|
21455
|
+
});
|
|
21188
21456
|
}
|
|
21189
21457
|
return { worktrees, localBranches, currentBranch: currentBranch2, openPrBranches, closedBranches, stages, orphanDirs };
|
|
21190
21458
|
}
|
|
@@ -21773,11 +22041,11 @@ ${lines}`);
|
|
|
21773
22041
|
|
|
21774
22042
|
// src/train-commands.ts
|
|
21775
22043
|
var import_node_fs28 = require("node:fs");
|
|
21776
|
-
var
|
|
22044
|
+
var import_node_path25 = require("node:path");
|
|
21777
22045
|
|
|
21778
22046
|
// src/plugin-guard-io.ts
|
|
21779
22047
|
var import_node_fs27 = require("node:fs");
|
|
21780
|
-
var
|
|
22048
|
+
var import_node_path24 = require("node:path");
|
|
21781
22049
|
var import_node_os6 = require("node:os");
|
|
21782
22050
|
|
|
21783
22051
|
// src/plugin-guard.ts
|
|
@@ -21907,7 +22175,7 @@ function runHostBin(bin, args, opts) {
|
|
|
21907
22175
|
}
|
|
21908
22176
|
var installedPluginsPath2 = (surface = detectSurface(process.env)) => {
|
|
21909
22177
|
const homeDir = surface === "codex" ? ".codex" : ".claude";
|
|
21910
|
-
return (0,
|
|
22178
|
+
return (0, import_node_path24.join)((0, import_node_os6.homedir)(), homeDir, "plugins", "installed_plugins.json");
|
|
21911
22179
|
};
|
|
21912
22180
|
function readInstalledPlugins(surface = detectSurface(process.env)) {
|
|
21913
22181
|
try {
|
|
@@ -21919,11 +22187,11 @@ function readInstalledPlugins(surface = detectSurface(process.env)) {
|
|
|
21919
22187
|
function marketplaceCloneCandidates(surface, home) {
|
|
21920
22188
|
if (surface === "codex") {
|
|
21921
22189
|
return [
|
|
21922
|
-
(0,
|
|
21923
|
-
(0,
|
|
22190
|
+
(0, import_node_path24.join)(home, ".codex", ".tmp", "marketplaces", "mutmutco"),
|
|
22191
|
+
(0, import_node_path24.join)(home, ".codex", "plugins", "marketplaces", "mutmutco")
|
|
21924
22192
|
];
|
|
21925
22193
|
}
|
|
21926
|
-
return [(0,
|
|
22194
|
+
return [(0, import_node_path24.join)(home, ".claude", "plugins", "marketplaces", "mutmutco")];
|
|
21927
22195
|
}
|
|
21928
22196
|
function marketplaceClonePresent(surface, home, exists = import_node_fs27.existsSync) {
|
|
21929
22197
|
return marketplaceCloneCandidates(surface, home).some(exists);
|
|
@@ -21953,7 +22221,7 @@ function snapshotPluginGuardInput(surface = detectSurface(process.env), isOrgRep
|
|
|
21953
22221
|
isOrgRepo,
|
|
21954
22222
|
installRecordPresent: hasUserInstallRecord(installed, MMI_PLUGIN_ID) || hasProjectInstallRecord(installed, MMI_PLUGIN_ID, process.cwd()),
|
|
21955
22223
|
marketplaceClonePresent: marketplaceClonePresent(surface, (0, import_node_os6.homedir)()),
|
|
21956
|
-
pluginCachePresent: (0, import_node_fs27.existsSync)((0,
|
|
22224
|
+
pluginCachePresent: (0, import_node_fs27.existsSync)((0, import_node_path24.join)((0, import_node_os6.homedir)(), homeDir, "plugins", "cache", "mutmutco", "mmi"))
|
|
21957
22225
|
};
|
|
21958
22226
|
}
|
|
21959
22227
|
function claudePluginGuardState(isOrgRepo) {
|
|
@@ -22088,7 +22356,7 @@ function formatTrainStatus(r) {
|
|
|
22088
22356
|
// src/train-commands.ts
|
|
22089
22357
|
function readRepoVersion() {
|
|
22090
22358
|
try {
|
|
22091
|
-
return JSON.parse((0, import_node_fs28.readFileSync)((0,
|
|
22359
|
+
return JSON.parse((0, import_node_fs28.readFileSync)((0, import_node_path25.join)(process.cwd(), ".claude-plugin", "plugin.json"), "utf8")).version || void 0;
|
|
22092
22360
|
} catch {
|
|
22093
22361
|
return void 0;
|
|
22094
22362
|
}
|
|
@@ -23231,6 +23499,28 @@ function checkRepoWorktrees(probe) {
|
|
|
23231
23499
|
]
|
|
23232
23500
|
};
|
|
23233
23501
|
}
|
|
23502
|
+
function formatBytes(bytes, partial) {
|
|
23503
|
+
const prefix = partial ? "\u2265" : "";
|
|
23504
|
+
return bytes >= 1e9 ? `${prefix}${(bytes / 1e9).toFixed(2)} GB` : `${prefix}${(bytes / 1e6).toFixed(1)} MB`;
|
|
23505
|
+
}
|
|
23506
|
+
function checkWorktreeRoots(probe) {
|
|
23507
|
+
if (!probe) return null;
|
|
23508
|
+
const evidence = [
|
|
23509
|
+
`scanned by worktree gc/list: ${probe.authoritative}`,
|
|
23510
|
+
...probe.stray.length ? probe.stray.map((s) => `unreachable by any gc: ${s.path} \u2014 ${s.dirs} dir(s), ${formatBytes(s.bytes, s.partial)}`) : ["no other *worktrees* directory beside this repo"]
|
|
23511
|
+
];
|
|
23512
|
+
if (!probe.stray.length) return { ok: true, label: "worktree roots", verbose: evidence };
|
|
23513
|
+
const named = probe.stray.map((s) => `${s.path} (${s.dirs} dir(s), ${formatBytes(s.bytes, s.partial)})`).join("; ");
|
|
23514
|
+
const cutShort = probe.stray.some((s) => s.partial) ? " (a \u2265 figure is a floor \u2014 the size walk hit its time budget, so the real total is larger)" : "";
|
|
23515
|
+
return {
|
|
23516
|
+
ok: false,
|
|
23517
|
+
label: "worktree roots",
|
|
23518
|
+
// The fix is a review, never a delete: `gc --root` still refuses anything it cannot classify as dead,
|
|
23519
|
+
// and doctor itself — with or without --apply — never touches these directories.
|
|
23520
|
+
fix: `${probe.stray.length} worktrees root(s) outside ${probe.authoritative} \u2014 ${named}; no gc scans them. Review, then sweep deliberately from the owning repo with \`mmi-cli worktree gc --root <path> --apply\` (report-only here: doctor never deletes these)${cutShort}`,
|
|
23521
|
+
verbose: evidence
|
|
23522
|
+
};
|
|
23523
|
+
}
|
|
23234
23524
|
function pluginHealTrigger(probe) {
|
|
23235
23525
|
if (probe.guardState === "no-install" || probe.guardState === "unresolved") return "unresolved";
|
|
23236
23526
|
const behind = Boolean(probe.installed && probe.released) && compareVersions(probe.installed, probe.released) < 0;
|
|
@@ -23368,6 +23658,45 @@ function checkDocsAudit(probe) {
|
|
|
23368
23658
|
}
|
|
23369
23659
|
return { ok: true, label: "docs-audit", detail: probe.detail, verbose: [probe.detail] };
|
|
23370
23660
|
}
|
|
23661
|
+
function checkSessionPayload(probe) {
|
|
23662
|
+
if (!probe) return null;
|
|
23663
|
+
const { chars } = probe;
|
|
23664
|
+
const evidence = [
|
|
23665
|
+
`last emitted: ${chars} characters${probe.at ? ` (${probe.at})` : ""}`,
|
|
23666
|
+
`budget ${SESSION_PAYLOAD_BUDGET_CHARS}, Claude Code cap ${SESSION_PAYLOAD_CAP_CHARS}`,
|
|
23667
|
+
// Named in the evidence because the units are the whole trap: the cap is documented in characters
|
|
23668
|
+
// while the runtime's overflow message reports bytes, and they diverge on the non-ASCII the doctrine
|
|
23669
|
+
// blocks preserve. A reader comparing this number to a byte figure is comparing two different things.
|
|
23670
|
+
"measured in CHARACTERS (the documented unit of the cap), never bytes"
|
|
23671
|
+
];
|
|
23672
|
+
const status = sessionPayloadStatus(chars);
|
|
23673
|
+
if (status === "red") {
|
|
23674
|
+
return {
|
|
23675
|
+
ok: false,
|
|
23676
|
+
label: "SessionStart payload",
|
|
23677
|
+
fix: `${chars} characters exceeds the ${SESSION_PAYLOAD_CAP_CHARS}-character additionalContext cap \u2014 Claude Code truncates it to a ~2,000-character preview with no error, so agents are starting on partial doctrine; cut a block from the session-start banner`,
|
|
23678
|
+
verbose: evidence
|
|
23679
|
+
};
|
|
23680
|
+
}
|
|
23681
|
+
if (status === "amber") {
|
|
23682
|
+
return {
|
|
23683
|
+
ok: true,
|
|
23684
|
+
// warn: the banner lane must print this too. Without it the banner's failing-only filter made the
|
|
23685
|
+
// amber tier dead code on the one lane that runs every session — drift past the budget was
|
|
23686
|
+
// invisible until it became a truncation. Still ✓, still never moves the exit code.
|
|
23687
|
+
warn: true,
|
|
23688
|
+
label: "SessionStart payload",
|
|
23689
|
+
detail: `amber \u2014 ${chars} characters, over the ${SESSION_PAYLOAD_BUDGET_CHARS}-character budget and ${SESSION_PAYLOAD_CAP_CHARS - chars} from the hard cap`,
|
|
23690
|
+
verbose: evidence
|
|
23691
|
+
};
|
|
23692
|
+
}
|
|
23693
|
+
return {
|
|
23694
|
+
ok: true,
|
|
23695
|
+
label: "SessionStart payload",
|
|
23696
|
+
detail: `${chars} of ${SESSION_PAYLOAD_BUDGET_CHARS} budgeted characters`,
|
|
23697
|
+
verbose: evidence
|
|
23698
|
+
};
|
|
23699
|
+
}
|
|
23371
23700
|
function planGitignore(current) {
|
|
23372
23701
|
const { content, changed } = upsertManagedGitignoreBlock(current);
|
|
23373
23702
|
return changed ? { ok: false, content } : { ok: true };
|
|
@@ -23377,6 +23706,7 @@ function gcReapable(plan) {
|
|
|
23377
23706
|
}
|
|
23378
23707
|
async function runDoctorClean(opts, io, deps) {
|
|
23379
23708
|
const apply = Boolean(opts.apply);
|
|
23709
|
+
const applyRepo = apply && opts.repoWrites !== false;
|
|
23380
23710
|
const [login, isOrgRepo, released, callerArn, reach] = await Promise.all([
|
|
23381
23711
|
deps.githubLogin(),
|
|
23382
23712
|
deps.isOrgRepo(),
|
|
@@ -23408,7 +23738,7 @@ async function runDoctorClean(opts, io, deps) {
|
|
|
23408
23738
|
];
|
|
23409
23739
|
if (gi.ok) {
|
|
23410
23740
|
checks.push({ ok: true, label: "gitignore block", verbose: giEvidence });
|
|
23411
|
-
} else if (
|
|
23741
|
+
} else if (applyRepo && gi.content && deps.writeGitignore(gi.content)) {
|
|
23412
23742
|
checks.push({ ok: true, label: "gitignore block", detail: "rewrote managed block", verbose: giEvidence });
|
|
23413
23743
|
restartPending = true;
|
|
23414
23744
|
} else {
|
|
@@ -23463,6 +23793,10 @@ async function runDoctorClean(opts, io, deps) {
|
|
|
23463
23793
|
checks.push(checkCliVersion(cliInput));
|
|
23464
23794
|
}
|
|
23465
23795
|
checks.push(checkPluginCache(deps.pluginCache()));
|
|
23796
|
+
if (deps.sessionPayload) {
|
|
23797
|
+
const payload = checkSessionPayload(deps.sessionPayload());
|
|
23798
|
+
if (payload) checks.push(payload);
|
|
23799
|
+
}
|
|
23466
23800
|
if (!opts.fast && !opts.banner && !opts.preflight) {
|
|
23467
23801
|
const probe = await deps.schedulesNotebook().catch((e) => ({
|
|
23468
23802
|
armed: 0,
|
|
@@ -23483,6 +23817,11 @@ async function runDoctorClean(opts, io, deps) {
|
|
|
23483
23817
|
const docsAudit2 = checkDocsAudit(probe);
|
|
23484
23818
|
if (docsAudit2) checks.push(docsAudit2);
|
|
23485
23819
|
}
|
|
23820
|
+
if (!opts.fast && !opts.banner && !opts.preflight && deps.worktreeRoots) {
|
|
23821
|
+
const probe = await deps.worktreeRoots().catch(() => void 0);
|
|
23822
|
+
const roots = checkWorktreeRoots(probe);
|
|
23823
|
+
if (roots) checks.push(roots);
|
|
23824
|
+
}
|
|
23486
23825
|
if (isOrgRepo && !opts.fast && !opts.banner) {
|
|
23487
23826
|
try {
|
|
23488
23827
|
checks.push(checkTrainSync(await deps.syncTrain()));
|
|
@@ -23498,7 +23837,7 @@ async function runDoctorClean(opts, io, deps) {
|
|
|
23498
23837
|
const repoRoot2 = await deps.repoRoot();
|
|
23499
23838
|
try {
|
|
23500
23839
|
const plan = await deps.gcPlan("origin", 200);
|
|
23501
|
-
if (
|
|
23840
|
+
if (applyRepo) {
|
|
23502
23841
|
const r = await deps.applyGcPlan(plan, "origin");
|
|
23503
23842
|
const reaped = r.removedBranches.length + r.removedRemoteBranches.length + r.removedWorktreeDirs.length + r.removedTrackingRefs.length;
|
|
23504
23843
|
const detailParts = [];
|
|
@@ -23538,7 +23877,7 @@ async function runDoctorClean(opts, io, deps) {
|
|
|
23538
23877
|
}
|
|
23539
23878
|
const scratch = deps.executeScratchGc(repoRoot2, { apply: false });
|
|
23540
23879
|
const scratchEvidence = scratch.plan.safeAuto.length ? scratch.plan.safeAuto.map((c) => `aged: ${c.path} (${c.reason})`) : ["nothing aged"];
|
|
23541
|
-
if (
|
|
23880
|
+
if (applyRepo && scratch.plan.safeAuto.length > 0) {
|
|
23542
23881
|
const applied = deps.executeScratchGc(repoRoot2, { apply: true });
|
|
23543
23882
|
const pruned = applied.applied?.pruned.length ?? 0;
|
|
23544
23883
|
checks.push({ ok: true, label: "scratch", detail: pruned ? `removed ${pruned} aged item(s)` : "nothing stale", verbose: scratchEvidence });
|
|
@@ -23554,7 +23893,7 @@ async function runDoctorClean(opts, io, deps) {
|
|
|
23554
23893
|
return exitCode;
|
|
23555
23894
|
}
|
|
23556
23895
|
if (opts.banner) {
|
|
23557
|
-
const actionable = checks.filter((c) => !c.ok);
|
|
23896
|
+
const actionable = checks.filter((c) => !c.ok || c.warn);
|
|
23558
23897
|
for (const c of actionable) {
|
|
23559
23898
|
io.log(renderReport([c], { restartPending: false }));
|
|
23560
23899
|
if (opts.verbose) for (const evidence of c.verbose ?? []) io.log(`${VERBOSE_INDENT}${evidence}`);
|
|
@@ -23647,7 +23986,7 @@ function ghAccountCaveat(announcedLogin, accounts) {
|
|
|
23647
23986
|
// src/doctor-io.ts
|
|
23648
23987
|
var import_node_fs29 = require("node:fs");
|
|
23649
23988
|
var import_node_os7 = require("node:os");
|
|
23650
|
-
var
|
|
23989
|
+
var import_node_path26 = require("node:path");
|
|
23651
23990
|
var import_node_child_process12 = require("node:child_process");
|
|
23652
23991
|
var import_node_util8 = require("node:util");
|
|
23653
23992
|
var execFileP6 = (0, import_node_util8.promisify)(import_node_child_process12.execFile);
|
|
@@ -23655,7 +23994,7 @@ var MMI_PLUGIN_ID2 = "mmi@mutmutco";
|
|
|
23655
23994
|
function installedClaudePluginVersion() {
|
|
23656
23995
|
try {
|
|
23657
23996
|
const file = JSON.parse(
|
|
23658
|
-
(0, import_node_fs29.readFileSync)((0,
|
|
23997
|
+
(0, import_node_fs29.readFileSync)((0, import_node_path26.join)((0, import_node_os7.homedir)(), ".claude", "plugins", "installed_plugins.json"), "utf8")
|
|
23659
23998
|
);
|
|
23660
23999
|
const versions = (file.plugins?.[MMI_PLUGIN_ID2] ?? []).map((r) => r.version).filter((v) => Boolean(v));
|
|
23661
24000
|
if (versions.length === 0) return void 0;
|
|
@@ -23676,7 +24015,7 @@ function worktreeRootSync() {
|
|
|
23676
24015
|
}
|
|
23677
24016
|
var gitignorePath = () => {
|
|
23678
24017
|
const root = worktreeRootSync();
|
|
23679
|
-
return root === null ? null : (0,
|
|
24018
|
+
return root === null ? null : (0, import_node_path26.join)(root, ".gitignore");
|
|
23680
24019
|
};
|
|
23681
24020
|
function readGitignore() {
|
|
23682
24021
|
const path2 = gitignorePath();
|
|
@@ -23715,7 +24054,7 @@ async function repoRoot() {
|
|
|
23715
24054
|
}
|
|
23716
24055
|
function hasRepoLocalWorktrees() {
|
|
23717
24056
|
const root = worktreeRootSync();
|
|
23718
|
-
return root !== null && (0, import_node_fs29.existsSync)((0,
|
|
24057
|
+
return root !== null && (0, import_node_fs29.existsSync)((0, import_node_path26.join)(root, ".worktrees"));
|
|
23719
24058
|
}
|
|
23720
24059
|
|
|
23721
24060
|
// src/index.ts
|
|
@@ -23838,7 +24177,14 @@ function mmiDoctorDeps() {
|
|
|
23838
24177
|
const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
23839
24178
|
const status = docsAuditStatus(await readDocsAuditFetch(repo), { repo, today });
|
|
23840
24179
|
return { armed: status.state !== "not-armed", ok: status.ok, detail: status.line };
|
|
23841
|
-
}
|
|
24180
|
+
},
|
|
24181
|
+
// #3471: the competing-worktrees-roots probe. Full doctor only (the gather skips it on
|
|
24182
|
+
// fast/banner/preflight), and READ-ONLY by construction — it stats, it never removes. Nothing here is
|
|
24183
|
+
// wired to a reaper, in either mode.
|
|
24184
|
+
worktreeRoots: async () => worktreeRootsProbe(await repoRoot()),
|
|
24185
|
+
// #3470: what the SessionStart hook LAST emitted, measured at emission by the session-start verb below.
|
|
24186
|
+
// A local record read — cheap enough for every lane, including the banner.
|
|
24187
|
+
sessionPayload: () => readSessionPayload(process.cwd())
|
|
23842
24188
|
};
|
|
23843
24189
|
}
|
|
23844
24190
|
async function requireFreshTrainCli(commandName) {
|
|
@@ -23963,7 +24309,7 @@ program2.hook("preAction", async (_thisCommand, actionCommand) => {
|
|
|
23963
24309
|
});
|
|
23964
24310
|
var rules = program2.command("rules").description("org-managed .gitignore delivery");
|
|
23965
24311
|
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) => {
|
|
23966
|
-
const path2 = (0,
|
|
24312
|
+
const path2 = (0, import_node_path27.join)(process.cwd(), ".gitignore");
|
|
23967
24313
|
const current = (0, import_node_fs30.existsSync)(path2) ? (0, import_node_fs30.readFileSync)(path2, "utf8") : null;
|
|
23968
24314
|
const plan = planManagedGitignore(current);
|
|
23969
24315
|
const drift = [...plan.added.map((l) => `+${l}`), ...plan.removed.map((l) => `-${l}`)].join(", ") || "block normalize";
|
|
@@ -24085,12 +24431,12 @@ gcCmd.command("sweep-deferred").description("retry IDE-locked deferred worktree
|
|
|
24085
24431
|
process.exit(process.exitCode ?? 0);
|
|
24086
24432
|
});
|
|
24087
24433
|
});
|
|
24088
|
-
gcCmd.option("--dry-run", "show what would be deleted (default)").option("--apply", "delete only the listed clean merged/closed PR local+remote branches, linked worktrees, and stale tracking refs").option("--json", "machine-readable output").option("--scratch", "prune safe local scratch (#1864) instead of git branches/refs; local plans are surfaced advisory-only, never auto-pruned").option("--remote <name>", "remote name", "origin").option("--limit <n>", "PRs to inspect per state", "200").action(async (o) => {
|
|
24434
|
+
gcCmd.option("--dry-run", "show what would be deleted (default)").option("--apply", "delete only the listed clean merged/closed PR local+remote branches, linked worktrees, and stale tracking refs").option("--json", "machine-readable output").option("--scratch", "prune safe local scratch (#1864) instead of git branches/refs; local plans are surfaced advisory-only, never auto-pruned").option("--remote <name>", "remote name", "origin").option("--limit <n>", "PRs to inspect per state", "200").option("--root <path>", "sweep an explicitly named worktrees root instead of the authoritative ../mmi-worktrees (#3471) \u2014 descends into this repo container when present; ownership, dead-dir, and content guards still apply").action(async (o) => {
|
|
24089
24435
|
if (o.apply && o.dryRun) return fail("worktree gc: choose either --dry-run or --apply");
|
|
24090
24436
|
if (o.scratch) {
|
|
24091
24437
|
try {
|
|
24092
|
-
const
|
|
24093
|
-
const run = executeScratchGc(
|
|
24438
|
+
const root2 = (await execFileP2("git", ["rev-parse", "--show-toplevel"], { timeout: GIT_TIMEOUT_MS }).catch(() => ({ stdout: "" }))).stdout.trim() || process.cwd();
|
|
24439
|
+
const run = executeScratchGc(root2, { apply: Boolean(o.apply) });
|
|
24094
24440
|
if (o.json) return console.log(JSON.stringify({ dryRun: !o.apply, ...run }, null, 2));
|
|
24095
24441
|
return console.log(formatScratchGcPlan(run.plan, Boolean(o.apply), run.applied));
|
|
24096
24442
|
} catch (e) {
|
|
@@ -24099,8 +24445,19 @@ gcCmd.option("--dry-run", "show what would be deleted (default)").option("--appl
|
|
|
24099
24445
|
}
|
|
24100
24446
|
const limit = Number.parseInt(o.limit, 10);
|
|
24101
24447
|
if (!Number.isFinite(limit) || limit < 1) return fail("worktree gc: --limit must be a positive integer");
|
|
24448
|
+
let root;
|
|
24449
|
+
if (o.root !== void 0) {
|
|
24450
|
+
root = (0, import_node_path27.resolve)(o.root);
|
|
24451
|
+
if (!(0, import_node_fs30.existsSync)(root) || !(0, import_node_fs30.statSync)(root).isDirectory()) return fail(`worktree gc: --root ${o.root} is not a directory`);
|
|
24452
|
+
const gcRepoRoot = (await execFileP2("git", ["rev-parse", "--show-toplevel"], { timeout: GIT_TIMEOUT_MS }).catch(() => ({ stdout: "" }))).stdout.trim() || process.cwd();
|
|
24453
|
+
if (isPathUnderDirectory(gcRepoRoot, root)) {
|
|
24454
|
+
return fail(`worktree gc: --root ${root} contains this checkout \u2014 name a worktrees root, not the repo or an ancestor of it`);
|
|
24455
|
+
}
|
|
24456
|
+
}
|
|
24102
24457
|
try {
|
|
24103
|
-
const plan = await gcPlan(o.remote, limit);
|
|
24458
|
+
const plan = await gcPlan(o.remote, limit, { root });
|
|
24459
|
+
if (root && !o.json) console.log(`worktree gc: scanning the explicitly named root ${root} for worktrees proven to belong to this repo
|
|
24460
|
+
`);
|
|
24104
24461
|
if (o.apply && !o.json) console.log(formatGcPlan(plan, true));
|
|
24105
24462
|
let applyResult;
|
|
24106
24463
|
if (o.apply) {
|
|
@@ -24111,10 +24468,10 @@ gcCmd.option("--dry-run", "show what would be deleted (default)").option("--appl
|
|
|
24111
24468
|
// git fsmonitor daemon from inheriting this sweep's stdio pipe and wedging the git call on Windows.
|
|
24112
24469
|
worktreeRemoveDeps(async (args) => (await execFileP2("git", ["-c", "core.fsmonitor=false", ...args], { timeout: GIT_TIMEOUT_MS })).stdout)
|
|
24113
24470
|
).catch(() => void 0);
|
|
24114
|
-
applyResult = await applyGcPlan(plan, o.remote);
|
|
24471
|
+
applyResult = await applyGcPlan(plan, o.remote, { root });
|
|
24115
24472
|
}
|
|
24116
24473
|
if (o.json) {
|
|
24117
|
-
console.log(JSON.stringify({ dryRun: !o.apply, remote: o.remote, plan, applyResult }, null, 2));
|
|
24474
|
+
console.log(JSON.stringify({ dryRun: !o.apply, remote: o.remote, ...root ? { root } : {}, plan, applyResult }, null, 2));
|
|
24118
24475
|
} else if (!o.apply) {
|
|
24119
24476
|
console.log(formatGcPlan(plan, false));
|
|
24120
24477
|
} else {
|
|
@@ -24179,7 +24536,7 @@ function acquireWorktreeSetupLock(worktreeRoot) {
|
|
|
24179
24536
|
};
|
|
24180
24537
|
};
|
|
24181
24538
|
try {
|
|
24182
|
-
(0, import_node_fs30.mkdirSync)((0,
|
|
24539
|
+
(0, import_node_fs30.mkdirSync)((0, import_node_path27.dirname)(lockPath), { recursive: true });
|
|
24183
24540
|
return take();
|
|
24184
24541
|
} catch {
|
|
24185
24542
|
try {
|
|
@@ -24194,7 +24551,7 @@ function acquireWorktreeSetupLock(worktreeRoot) {
|
|
|
24194
24551
|
}
|
|
24195
24552
|
var worktree = program2.command("worktree").description("self-provisioning worktrees \u2014 install deps + copy local-only config");
|
|
24196
24553
|
withExamples(mutating(
|
|
24197
|
-
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;
|
|
24554
|
+
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/<RepoName>/<branch> \u2014 authoritative; the <RepoName> segment is what makes ownership provable from the path, #3471. gc/list read this root in BOTH the nested and the legacy flat shape)").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"),
|
|
24198
24555
|
worktreeCreatePlan
|
|
24199
24556
|
).action(async (target, o, cmd) => {
|
|
24200
24557
|
try {
|
|
@@ -25282,11 +25639,11 @@ pr.command("view <number>").description("read a PR as structured JSON (merged st
|
|
|
25282
25639
|
}
|
|
25283
25640
|
});
|
|
25284
25641
|
async function listCiWorkflowPaths(cwd = process.cwd()) {
|
|
25285
|
-
const wfDir = (0,
|
|
25642
|
+
const wfDir = (0, import_node_path27.join)(cwd, ".github", "workflows");
|
|
25286
25643
|
if (!(0, import_node_fs30.existsSync)(wfDir)) return [];
|
|
25287
25644
|
return (0, import_node_fs30.readdirSync)(wfDir).filter((name) => /\.(ya?ml)$/i.test(name)).filter((name) => {
|
|
25288
25645
|
try {
|
|
25289
|
-
return workflowReportsPrChecks((0, import_node_fs30.readFileSync)((0,
|
|
25646
|
+
return workflowReportsPrChecks((0, import_node_fs30.readFileSync)((0, import_node_path27.join)(wfDir, name), "utf8"));
|
|
25290
25647
|
} catch {
|
|
25291
25648
|
return true;
|
|
25292
25649
|
}
|
|
@@ -25313,16 +25670,16 @@ function ciAuditDeps() {
|
|
|
25313
25670
|
// gate re-seed step is skipped gracefully rather than failing mid-run.
|
|
25314
25671
|
readSeedFile: (path2) => {
|
|
25315
25672
|
if (!root) return null;
|
|
25316
|
-
const fullPath = (0,
|
|
25673
|
+
const fullPath = (0, import_node_path27.join)(root, path2);
|
|
25317
25674
|
return (0, import_node_fs30.existsSync)(fullPath) ? (0, import_node_fs30.readFileSync)(fullPath, "utf8") : null;
|
|
25318
25675
|
}
|
|
25319
25676
|
};
|
|
25320
25677
|
}
|
|
25321
25678
|
function hubRoot() {
|
|
25322
|
-
const fromPkg = (0,
|
|
25679
|
+
const fromPkg = (0, import_node_path27.join)(__dirname, "..", "..");
|
|
25323
25680
|
const marker = "skills/bootstrap/seeds/manifest.json";
|
|
25324
|
-
if ((0, import_node_fs30.existsSync)((0,
|
|
25325
|
-
if ((0, import_node_fs30.existsSync)((0,
|
|
25681
|
+
if ((0, import_node_fs30.existsSync)((0, import_node_path27.join)(fromPkg, marker))) return fromPkg;
|
|
25682
|
+
if ((0, import_node_fs30.existsSync)((0, import_node_path27.join)(process.cwd(), marker))) return process.cwd();
|
|
25326
25683
|
return null;
|
|
25327
25684
|
}
|
|
25328
25685
|
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) => {
|
|
@@ -25929,10 +26286,10 @@ program2.command("doctor").description("check onboarding gates and auto-heal CLI
|
|
|
25929
26286
|
consoleIo.log("MMI Agentic Onboarding: docs/Architecture/agentic-dev-environment.md");
|
|
25930
26287
|
return;
|
|
25931
26288
|
}
|
|
25932
|
-
const noRepoWrites = opts.repoWrites === false;
|
|
25933
26289
|
process.exitCode = await runDoctorClean(
|
|
25934
26290
|
{
|
|
25935
|
-
apply: Boolean(opts.apply)
|
|
26291
|
+
apply: Boolean(opts.apply),
|
|
26292
|
+
repoWrites: opts.repoWrites,
|
|
25936
26293
|
banner: opts.banner,
|
|
25937
26294
|
preflight: opts.preflight,
|
|
25938
26295
|
fast: opts.fast || opts.self,
|
|
@@ -25955,7 +26312,7 @@ function directoryBytes(path2) {
|
|
|
25955
26312
|
return 0;
|
|
25956
26313
|
}
|
|
25957
26314
|
for (const entry of entries) {
|
|
25958
|
-
const child2 = (0,
|
|
26315
|
+
const child2 = (0, import_node_path27.join)(path2, entry.name);
|
|
25959
26316
|
if (entry.isDirectory()) total += directoryBytes(child2);
|
|
25960
26317
|
else {
|
|
25961
26318
|
try {
|
|
@@ -25985,7 +26342,7 @@ function pluginCacheFsDeps(home, dirBytes) {
|
|
|
25985
26342
|
dirBytes,
|
|
25986
26343
|
listStagingDirs: (root) => (0, import_node_fs30.readdirSync)(root, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => {
|
|
25987
26344
|
try {
|
|
25988
|
-
return { name: d.name, mtimeMs: newestMtimeMs((0,
|
|
26345
|
+
return { name: d.name, mtimeMs: newestMtimeMs((0, import_node_path27.join)(root, d.name), listDirEntries, (p) => (0, import_node_fs30.statSync)(p).mtimeMs) };
|
|
25989
26346
|
} catch {
|
|
25990
26347
|
return { name: d.name, mtimeMs: Date.now() };
|
|
25991
26348
|
}
|
|
@@ -25999,7 +26356,7 @@ function stagingApplyFsGuard(home) {
|
|
|
25999
26356
|
return {
|
|
26000
26357
|
referencedPaths: () => readInstalledPluginRefs(home),
|
|
26001
26358
|
mtimeMs: (name) => {
|
|
26002
|
-
const p = (0,
|
|
26359
|
+
const p = (0, import_node_path27.join)(stagingRoot, name);
|
|
26003
26360
|
if (!(0, import_node_fs30.existsSync)(p)) return null;
|
|
26004
26361
|
try {
|
|
26005
26362
|
return newestMtimeMs(p, listDirEntries, (q) => (0, import_node_fs30.statSync)(q).mtimeMs);
|
|
@@ -26035,6 +26392,8 @@ program2.command("session-start").description("run the SessionStart verbs (whoam
|
|
|
26035
26392
|
return;
|
|
26036
26393
|
}
|
|
26037
26394
|
spawnDeferredGcSweep();
|
|
26395
|
+
const measured = measuredSessionIo(consoleIo);
|
|
26396
|
+
const bannerIo = measured.io;
|
|
26038
26397
|
const { parallel, sequential } = buildSessionStartPlan({
|
|
26039
26398
|
// whoami (#879): surface the resolved human so agents act --for them without asking. Silent
|
|
26040
26399
|
// when unknown — a missing gh login must not noise or fail the banner.
|
|
@@ -26078,14 +26437,16 @@ program2.command("session-start").description("run the SessionStart verbs (whoam
|
|
|
26078
26437
|
await runDoctorClean({ banner: true }, io, mmiDoctorDeps());
|
|
26079
26438
|
}
|
|
26080
26439
|
});
|
|
26081
|
-
await runSessionStart(parallel, sequential,
|
|
26082
|
-
for (const line of scratchGcLines(process.cwd()))
|
|
26440
|
+
await runSessionStart(parallel, sequential, bannerIo);
|
|
26441
|
+
for (const line of scratchGcLines(process.cwd())) bannerIo.log(line);
|
|
26083
26442
|
const worktreeBanner = worktreeAutoProvisionBanner(process.cwd());
|
|
26084
26443
|
if (worktreeBanner) {
|
|
26085
26444
|
spawnDetachedSelf(["worktree", "setup", "--quiet"], { spawn: import_node_child_process13.spawn, execPath: process.execPath, scriptPath: process.argv[1] });
|
|
26086
|
-
|
|
26445
|
+
bannerIo.log(worktreeBanner);
|
|
26087
26446
|
}
|
|
26088
|
-
|
|
26447
|
+
const payloadChars = measured.chars();
|
|
26448
|
+
recordSessionPayload(process.cwd(), payloadChars);
|
|
26449
|
+
appendHookActivity(process.cwd(), { event: "SessionStart", script: "session-start", outcome: "ran", action: "session-start hook completed", payloadChars });
|
|
26089
26450
|
});
|
|
26090
26451
|
installProcessBackstop();
|
|
26091
26452
|
consolidateCommandNamespaces(program2);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mutmutco/cli",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.51.0",
|
|
4
4
|
"description": "MMI Future CLI — the org dev toolbox (board, registry, keyless secrets, release train, bootstrap, doctor) and the cross-IDE engine the plugin's session-start hook drives.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "UNLICENSED",
|