@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.
Files changed (2) hide show
  1. package/dist/main.cjs +435 -74
  2. 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
- return (0, import_node_path7.basename)(parent).toLowerCase() === "mmi-worktrees" ? parent : (0, import_node_path7.join)(parent, "mmi-worktrees");
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
- " Cross-slug (org-infra): `mmi-cli secrets use GLOBAL_API_KEY --slug cloudflare -- <cmd>` consumes it keyless (master-gated)."
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
- deps.err(`Secret ${key} was not found.`);
7155
- deps.err(`Approved escalation: mmi-cli secrets request ${key} --repo ${repo} --reason "<why it is needed>"`);
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 siblingRoot = siblingMmiWorktreesRoot(repoRoot2);
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
- const entries = (0, import_node_fs10.readdirSync)(siblingRoot, { withFileTypes: true });
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
- async function gcPlan(remote, limit) {
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 import_node_path26 = require("node:path");
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
- if ((override !== void 0 || hideFromParent) && metadata.discovery !== "primary") {
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 basename3 = workflowPath.split("/").pop() ?? workflowPath;
17733
+ const basename4 = workflowPath.split("/").pop() ?? workflowPath;
17491
17734
  return {
17492
- name: `${repo}/${basename3.replace(/\.ya?ml$/, "")}`,
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") target = Arn.split(":").pop() ?? "";
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: `aws scheduler schedule ${Name} (eu-central-1)`,
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.startsWith("aws-scheduler") && entry.executor.includes("mmi-hub-schedule-dispatcher")) return null;
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 basename3 = wf.path.split("/").pop() ?? wf.path;
17798
- const name = `${repo}/${basename3.replace(/\.ya?ml$/, "")}`;
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 basename3 = workflowPath.split("/").pop() ?? workflowPath;
18022
- const expectedId = `${repo}/${basename3.replace(/\.ya?ml$/, "")}`;
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
- "Fetch CF account creds (master-gated): mmi-cli secrets use --slug cloudflare API_TOKEN -- <cmd>",
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 use with secrets use --slug cloudflare)").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) => {
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 import_node_path22 = require("node:path");
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, import_node_path22.join)(root, e.name));
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, import_node_path22.join)(wtPath, ".git");
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, import_node_path22.dirname)(commonDir) : wtPath;
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 wtRoot = siblingMmiWorktreesRoot(repoRoot2);
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, await currentRepoWorktreeGitRoot(repoRoot2));
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 import_node_path24 = require("node:path");
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 import_node_path23 = require("node:path");
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, import_node_path23.join)((0, import_node_os6.homedir)(), homeDir, "plugins", "installed_plugins.json");
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, import_node_path23.join)(home, ".codex", ".tmp", "marketplaces", "mutmutco"),
21923
- (0, import_node_path23.join)(home, ".codex", "plugins", "marketplaces", "mutmutco")
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, import_node_path23.join)(home, ".claude", "plugins", "marketplaces", "mutmutco")];
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, import_node_path23.join)((0, import_node_os6.homedir)(), homeDir, "plugins", "cache", "mutmutco", "mmi"))
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, import_node_path24.join)(process.cwd(), ".claude-plugin", "plugin.json"), "utf8")).version || void 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 (apply && gi.content && deps.writeGitignore(gi.content)) {
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 (apply) {
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 (apply && scratch.plan.safeAuto.length > 0) {
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 import_node_path25 = require("node:path");
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, import_node_path25.join)((0, import_node_os7.homedir)(), ".claude", "plugins", "installed_plugins.json"), "utf8")
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, import_node_path25.join)(root, ".gitignore");
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, import_node_path25.join)(root, ".worktrees"));
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, import_node_path26.join)(process.cwd(), ".gitignore");
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 root = (await execFileP2("git", ["rev-parse", "--show-toplevel"], { timeout: GIT_TIMEOUT_MS }).catch(() => ({ stdout: "" }))).stdout.trim() || process.cwd();
24093
- const run = executeScratchGc(root, { apply: Boolean(o.apply) });
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, import_node_path26.dirname)(lockPath), { recursive: true });
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; 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"),
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, import_node_path26.join)(cwd, ".github", "workflows");
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, import_node_path26.join)(wfDir, name), "utf8"));
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, import_node_path26.join)(root, path2);
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, import_node_path26.join)(__dirname, "..", "..");
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, import_node_path26.join)(fromPkg, marker))) return fromPkg;
25325
- if ((0, import_node_fs30.existsSync)((0, import_node_path26.join)(process.cwd(), marker))) return process.cwd();
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) && !noRepoWrites,
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, import_node_path26.join)(path2, entry.name);
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, import_node_path26.join)(root, d.name), listDirEntries, (p) => (0, import_node_fs30.statSync)(p).mtimeMs) };
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, import_node_path26.join)(stagingRoot, name);
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, consoleIo);
26082
- for (const line of scratchGcLines(process.cwd())) consoleIo.log(line);
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
- consoleIo.log(worktreeBanner);
26445
+ bannerIo.log(worktreeBanner);
26087
26446
  }
26088
- appendHookActivity(process.cwd(), { event: "SessionStart", script: "session-start", outcome: "ran", action: "session-start hook completed" });
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.49.0",
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",