@mutmutco/cli 3.81.0 → 3.82.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 +38 -341
  2. package/package.json +1 -1
package/dist/main.cjs CHANGED
@@ -5295,9 +5295,6 @@ function explicitRepoWorktreesRoot(root, repoRoot2, rootDirs) {
5295
5295
  const repoDir = rootDirs.find((name) => name.toLowerCase() === repoName.toLowerCase());
5296
5296
  return repoDir ? (0, import_node_path7.join)(root, repoDir) : root;
5297
5297
  }
5298
- function strayWorktreeRootPaths(container, names, authoritativeRoot) {
5299
- return names.filter((name) => /worktrees/i.test(name)).map((name) => (0, import_node_path7.join)(container, name)).filter((path2) => !samePath(path2, authoritativeRoot));
5300
- }
5301
5298
  function classifySiblingWorktreeDir(entry) {
5302
5299
  if (!entry.ownedByCurrentRepo) {
5303
5300
  return { skip: { path: entry.path, reason: "unknown-git-state", detail: entry.detail ?? "not proven current-repo owned" } };
@@ -6261,7 +6258,7 @@ function readSettingsAutoUpdate(raw, name) {
6261
6258
  var AUTO_UPDATE_TOGGLE_STEPS = "`/plugin` \u2192 Marketplaces tab \u2192 select the marketplace \u2192 Enable auto-update";
6262
6259
  var CATALOG_CONTENT_REF = "main";
6263
6260
  var CATALOG_REF_PIN_STEPS = `add \`"ref": "${CATALOG_CONTENT_REF}"\` to this marketplace's \`source\` in ~/.claude/plugins/known_marketplaces.json, then restart Claude`;
6264
- var PIN_HEAL_COMMAND = "run `mmi-cli doctor --apply`";
6261
+ var PIN_HEAL_COMMAND = "run `mmi-cli doctor`";
6265
6262
  var ORG_MARKETPLACE_PINS = { autoUpdate: true, ref: CATALOG_CONTENT_REF };
6266
6263
  function resolveCatalogRef(probe) {
6267
6264
  const { name, registered, ref, autoUpdate } = probe;
@@ -6352,32 +6349,6 @@ var DEFAULT_SURFACE = "claude";
6352
6349
  function activityLogPath(cwd) {
6353
6350
  return repoRuntimeStatePath(cwd, "hooks", "activity.jsonl");
6354
6351
  }
6355
- var REDACTOR_WINDOW_MS = 48 * 60 * 60 * 1e3;
6356
- var REDACTOR_SCAN_BYTES = 512 * 1024;
6357
- function redactorLivenessProbe(cwd, now = /* @__PURE__ */ new Date()) {
6358
- try {
6359
- const raw = (0, import_node_fs10.readFileSync)(activityLogPath(cwd), "utf8");
6360
- const tail = raw.length > REDACTOR_SCAN_BYTES ? raw.slice(raw.length - REDACTOR_SCAN_BYTES) : raw;
6361
- const floor = now.getTime() - REDACTOR_WINDOW_MS;
6362
- let failed = 0;
6363
- let lastTs;
6364
- for (const line of tail.split("\n")) {
6365
- if (!line.includes('"secret-redact"') || !line.includes('"failed"')) continue;
6366
- try {
6367
- const row = JSON.parse(line);
6368
- if (row.script !== "secret-redact" || row.outcome !== "failed") continue;
6369
- const ts = row.ts ? Date.parse(row.ts) : Number.NaN;
6370
- if (Number.isNaN(ts) || ts < floor) continue;
6371
- failed += 1;
6372
- if (!lastTs || row.ts > lastTs) lastTs = row.ts;
6373
- } catch {
6374
- }
6375
- }
6376
- return { failed, ...lastTs ? { lastTs } : {} };
6377
- } catch {
6378
- return void 0;
6379
- }
6380
- }
6381
6352
  function appendHookActivity(cwd, entry) {
6382
6353
  try {
6383
6354
  const path2 = activityLogPath(cwd);
@@ -13943,62 +13914,6 @@ async function branchesMergedIntoBase(remote) {
13943
13914
  }
13944
13915
  return [...out];
13945
13916
  }
13946
- var STRAY_ROOT_WALK_BUDGET_MS = 4e3;
13947
- function measureWorktreeRoot(root, deadline) {
13948
- let dirs = 0;
13949
- let bytes = 0;
13950
- let partial = false;
13951
- const stack = [root];
13952
- let depth0 = true;
13953
- while (stack.length) {
13954
- if (Date.now() > deadline) {
13955
- partial = true;
13956
- break;
13957
- }
13958
- const current = stack.pop();
13959
- let entries;
13960
- try {
13961
- entries = (0, import_node_fs17.readdirSync)(current, { withFileTypes: true });
13962
- } catch {
13963
- continue;
13964
- }
13965
- for (const ent of entries) {
13966
- const child2 = (0, import_node_path15.join)(current, ent.name);
13967
- if (ent.isDirectory()) {
13968
- if (depth0) dirs++;
13969
- let isLink = false;
13970
- try {
13971
- (0, import_node_fs17.readlinkSync)(child2);
13972
- isLink = true;
13973
- } catch {
13974
- }
13975
- if (!isLink) stack.push(child2);
13976
- } else if (ent.isFile()) {
13977
- try {
13978
- bytes += (0, import_node_fs17.statSync)(child2).size;
13979
- } catch {
13980
- }
13981
- }
13982
- }
13983
- depth0 = false;
13984
- }
13985
- return partial ? { dirs, bytes, partial } : { dirs, bytes };
13986
- }
13987
- function worktreeRootsProbe(repoRoot2) {
13988
- const authoritative = siblingMmiWorktreesRoot(repoRoot2);
13989
- const container = (0, import_node_path15.dirname)(authoritative);
13990
- let names;
13991
- try {
13992
- names = (0, import_node_fs17.readdirSync)(container, { withFileTypes: true }).filter((ent) => ent.isDirectory()).map((ent) => ent.name);
13993
- } catch {
13994
- return { authoritative, stray: [] };
13995
- }
13996
- const deadline = Date.now() + STRAY_ROOT_WALK_BUDGET_MS;
13997
- return {
13998
- authoritative,
13999
- stray: strayWorktreeRootPaths(container, names, authoritative).map((path2) => ({ path: path2, ...measureWorktreeRoot(path2, deadline) }))
14000
- };
14001
- }
14002
13917
 
14003
13918
  // src/repo-resolve.ts
14004
13919
  function slugOf(repoOrSlug) {
@@ -20901,6 +20816,13 @@ async function mintInstallationToken(deps) {
20901
20816
  }
20902
20817
  return { token: body.token, expiresAt: body.expires_at };
20903
20818
  }
20819
+ async function pollTokenOrUndefined(deps) {
20820
+ try {
20821
+ return (await mintInstallationToken(deps)).token;
20822
+ } catch {
20823
+ return void 0;
20824
+ }
20825
+ }
20904
20826
  async function activateAppActor(commandPath3, env, mint) {
20905
20827
  const requested = (env[APP_ACTOR_ENV] ?? "").trim();
20906
20828
  if (!requested) return "personal";
@@ -20916,43 +20838,6 @@ async function activateAppActor(commandPath3, env, mint) {
20916
20838
  env.GH_TOKEN = minted.token;
20917
20839
  return "app";
20918
20840
  }
20919
- async function probeRatePools(fetchLike, token) {
20920
- if (!token) return null;
20921
- try {
20922
- const res = await fetchLike("https://api.github.com/rate_limit", {
20923
- method: "GET",
20924
- headers: {
20925
- accept: "application/vnd.github+json",
20926
- "user-agent": "mmi-cli-app-actor",
20927
- authorization: `Bearer ${token}`
20928
- }
20929
- });
20930
- if (!res.ok) return null;
20931
- const body = await res.json();
20932
- const pool = (p) => typeof p?.remaining === "number" && typeof p?.limit === "number" && typeof p?.reset === "number" ? { remaining: p.remaining, limit: p.limit, reset: p.reset } : null;
20933
- const core = pool(body.resources?.core);
20934
- const graphql = pool(body.resources?.graphql);
20935
- if (!core || !graphql) return null;
20936
- return { core, graphql };
20937
- } catch {
20938
- return null;
20939
- }
20940
- }
20941
- function renderPoolDetail(probe) {
20942
- const fmt = (p) => `${p.remaining}/${p.limit} (resets ${new Date(p.reset * 1e3).toISOString().slice(11, 16)}Z)`;
20943
- return `core ${fmt(probe.core)}, graphql ${fmt(probe.graphql)}`;
20944
- }
20945
- function checkGithubPools(probe) {
20946
- if (!probe) return [];
20947
- const lines = [];
20948
- lines.push(probe.personal ? { id: "github-pools-personal", ok: true, label: "github pools (personal)", detail: renderPoolDetail(probe.personal) } : { id: "github-pools-personal", ok: false, label: "github pools (personal)", detail: "rate_limit unreadable", fix: "check `gh auth status`" });
20949
- if (typeof probe.app === "string") {
20950
- lines.push({ id: "github-pools-app", ok: false, label: "github pools (app actor)", fix: probe.app });
20951
- } else if (probe.app) {
20952
- lines.push({ id: "github-pools-app", ok: true, label: "github pools (app actor)", detail: renderPoolDetail(probe.app) });
20953
- }
20954
- return lines;
20955
- }
20956
20841
 
20957
20842
  // src/box-commands.ts
20958
20843
  var import_node_fs25 = require("node:fs");
@@ -21461,12 +21346,6 @@ function spliceDoc(docText, generatedSection) {
21461
21346
  }
21462
21347
  return docText.slice(0, start) + generatedSection + docText.slice(end + DOC_END_MARKER.length);
21463
21348
  }
21464
- function driftClearableFrom(drift, repoName) {
21465
- if (drift.class !== "file-vs-registry-stale") return false;
21466
- const slash = drift.name.indexOf("/");
21467
- if (slash <= 0) return false;
21468
- return drift.name.slice(0, slash).toLowerCase() === repoName.toLowerCase();
21469
- }
21470
21349
  var HARBOUR_LLM_LAUNCHERS = /* @__PURE__ */ new Set(["cursor-agent"]);
21471
21350
  function isHarbourLlmLauncher(executor) {
21472
21351
  return HARBOUR_LLM_LAUNCHERS.has(executor);
@@ -24939,8 +24818,25 @@ function teardownWorktreeStage(worktreePath) {
24939
24818
 
24940
24819
  // src/pr-checks-rest.ts
24941
24820
  var REST_GH_TIMEOUT_MS = 2e4;
24821
+ var pollTokenProvider;
24822
+ var pollTokenOnce;
24823
+ function setPollTokenProvider(provider) {
24824
+ pollTokenProvider = provider;
24825
+ pollTokenOnce = void 0;
24826
+ }
24827
+ async function pollToken() {
24828
+ if (!pollTokenProvider) return void 0;
24829
+ pollTokenOnce ??= pollTokenProvider().catch(() => void 0);
24830
+ return pollTokenOnce;
24831
+ }
24942
24832
  async function defaultGhApi(args) {
24943
- const { stdout } = await execFileP2("gh", ["api", ...args], { timeout: REST_GH_TIMEOUT_MS });
24833
+ const token = await pollToken();
24834
+ const { stdout } = await execFileP2("gh", ["api", ...args], {
24835
+ timeout: REST_GH_TIMEOUT_MS,
24836
+ // Only when a token was actually minted: passing `env` at all would otherwise replace the
24837
+ // inherited environment wholesale, and `gh` needs the rest of it (PATH, GH_HOST, config home).
24838
+ ...token ? { env: { ...process.env, GH_TOKEN: token } } : {}
24839
+ });
24944
24840
  return stdout;
24945
24841
  }
24946
24842
  function classifyCheckRun(run) {
@@ -28235,7 +28131,7 @@ function planSurfaceRepair(diagnosis) {
28235
28131
  owner: "mmi-cli",
28236
28132
  command: "mmi-cli plugin heal",
28237
28133
  changes: ["replace the active MMI plugin installation", `reload via ${descriptor.reload}`],
28238
- instruction: `run \`mmi-cli doctor --apply\` (or \`mmi-cli plugin heal\`), then ${reloadInstruction(descriptor)}`
28134
+ instruction: `run \`mmi-cli doctor\` (or \`mmi-cli plugin heal\`), then ${reloadInstruction(descriptor)}`
28239
28135
  };
28240
28136
  }
28241
28137
  function buildSurfaceDoctorCheck(diagnosis) {
@@ -28341,34 +28237,6 @@ function checkRepoWorktrees(probe) {
28341
28237
  ]
28342
28238
  };
28343
28239
  }
28344
- function formatBytes(bytes, partial) {
28345
- const prefix = partial ? "\u2265" : "";
28346
- return bytes >= 1e9 ? `${prefix}${(bytes / 1e9).toFixed(2)} GB` : `${prefix}${(bytes / 1e6).toFixed(1)} MB`;
28347
- }
28348
- function checkWorktreeRoots(probe) {
28349
- if (!probe) return null;
28350
- const evidence = [
28351
- `scanned by worktree gc/list: ${probe.authoritative}`,
28352
- ...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"]
28353
- ];
28354
- if (!probe.stray.length) return { ok: true, id: "worktree-roots", label: "worktree roots", verbose: evidence };
28355
- const named = probe.stray.map((s) => `${s.path} (${s.dirs} dir(s), ${formatBytes(s.bytes, s.partial)})`).join("; ");
28356
- 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)" : "";
28357
- return {
28358
- ok: false,
28359
- id: "worktree-roots",
28360
- label: "worktree roots",
28361
- // #3485: report-only in EVERY mode means report-only in the exit code too. `worktree gc --root` does
28362
- // exist and is the eventual fix, but this row is deliberately not a gate: the remedy is a human
28363
- // review of directories that may hold unlanded work, and a size-based auto-sweep once nearly deleted
28364
- // an unlanded correctness fix. Counting it made `mmi-cli doctor` exit 1 until someone swept by hand.
28365
- reportOnly: true,
28366
- // The fix is a review, never a delete: `gc --root` still refuses anything it cannot classify as dead,
28367
- // and doctor itself — with or without --apply — never touches these directories.
28368
- 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}`,
28369
- verbose: evidence
28370
- };
28371
- }
28372
28240
  function checkCodexHookTrust(probe, displayName = "Codex") {
28373
28241
  if (!probe?.applicable) return null;
28374
28242
  const evidence = [
@@ -28479,81 +28347,6 @@ function checkTrainSync(result) {
28479
28347
  verbose: evidence
28480
28348
  };
28481
28349
  }
28482
- function checkSchedules(probe) {
28483
- if (!probe) return null;
28484
- const evidence = [
28485
- `${probe.armed} armed (${probe.live} live, ${probe.declared} declared)`,
28486
- ...probe.incomplete.map((i) => `incomplete: ${i}`),
28487
- ...probe.drift.map((d) => `drift: ${d}`)
28488
- ];
28489
- const clearableHere = probe.clearableHere ?? 0;
28490
- if (probe.incomplete.length) {
28491
- return {
28492
- ok: false,
28493
- id: "schedules",
28494
- label: "schedules",
28495
- // #3485: ruled report-only — an unreadable source is a read failure somewhere in the org, and
28496
- // nothing in this checkout clears it.
28497
- reportOnly: true,
28498
- detail: `${probe.incomplete.length} source(s) unreadable`,
28499
- fix: "run `mmi-cli org schedules` for the full report \u2014 the notebook may be missing armed entries",
28500
- verbose: evidence
28501
- };
28502
- }
28503
- if (probe.drift.length) {
28504
- return {
28505
- ok: false,
28506
- id: "schedules",
28507
- label: "schedules",
28508
- // #3485 ruled this row report-only because the drift classes are owned by other repos. #3492 makes
28509
- // that conditional rather than blanket: when a finding IS clearable from this checkout — a
28510
- // `file-vs-registry-stale` row for this repo, one `org schedules register` away — the row gates
28511
- // like any other actionable red. Exempting a fault the operator can fix in one command is the
28512
- // tolerated-red failure the tier exists to prevent, not an instance of it.
28513
- ...clearableHere > 0 ? {} : { reportOnly: true },
28514
- detail: `${probe.drift.length} drift finding(s)`,
28515
- fix: clearableHere > 0 ? `${clearableHere} of ${probe.drift.length} clearable from this repo \u2014 from an up-to-date checkout run \`mmi-cli org schedules register\` here, then \`mmi-cli org schedules\` for the rest (each line names its own per-class remedy, applied in the owning repo)` : "run `mmi-cli org schedules` \u2014 each drift line names its per-class remedy (file/registry/live mismatch or harbour enforcement), applied in the owning repo",
28516
- verbose: evidence
28517
- };
28518
- }
28519
- return { ok: true, id: "schedules", label: "schedules", detail: `${probe.armed} armed`, verbose: evidence };
28520
- }
28521
- function checkDocsAudit(probe) {
28522
- if (!probe) return null;
28523
- if (!probe.armed) {
28524
- return { ok: true, id: "docs-audit", label: "docs-audit", detail: "janitor not armed", verbose: [probe.detail] };
28525
- }
28526
- if (!probe.ok) {
28527
- return {
28528
- ok: false,
28529
- id: "docs-audit",
28530
- label: "docs-audit",
28531
- // #3485: ruled report-only — the remedy is re-running or re-arming the janitor in the repo that
28532
- // owns it. `mmi-cli docs audit record` can write a verdict from here, but hand-writing one to clear
28533
- // a dead-man check defeats the check (#3067 pillar 4: silence is an alarm, never a success).
28534
- reportOnly: true,
28535
- fix: `${probe.detail} \u2014 re-run the janitor in the owning repo or re-arm its schedule (the remedy never lives in this doctor)`,
28536
- verbose: [probe.detail]
28537
- };
28538
- }
28539
- return { ok: true, id: "docs-audit", label: "docs-audit", detail: probe.detail, verbose: [probe.detail] };
28540
- }
28541
- function checkRedactorLiveness(probe) {
28542
- if (!probe) return null;
28543
- const evidence = [`secret-redact failed rows in the last 48h: ${probe.failed}${probe.lastTs ? ` (last ${probe.lastTs})` : ""}`];
28544
- if (probe.failed === 0) {
28545
- return { ok: true, id: "redactor-liveness", label: "secret-redact liveness", verbose: evidence };
28546
- }
28547
- return {
28548
- ok: false,
28549
- id: "redactor-liveness",
28550
- label: "secret-redact liveness",
28551
- reportOnly: true,
28552
- detail: `${probe.failed} crash marker${probe.failed === 1 ? "" : "s"} in 48h`,
28553
- fix: "the redactor is crashing during scans \u2014 read `.git/mmi-runtime/hooks/activity.jsonl` for the error and fix it; every crash is a scan that never ran",
28554
- verbose: evidence
28555
- };
28556
- }
28557
28350
  function checkSessionPayload(probe) {
28558
28351
  if (!probe) return null;
28559
28352
  const { chars } = probe;
@@ -28608,13 +28401,14 @@ function gcReapable(plan) {
28608
28401
  return plan.branches.length + plan.trackingRefs.length + plan.worktreeDirs.length;
28609
28402
  }
28610
28403
  async function runDoctorClean(opts, io, deps) {
28611
- const applyEnv = Boolean(opts.apply) || Boolean(opts.preflight);
28612
- const applyRepo = Boolean(opts.apply) && opts.repoWrites !== false;
28404
+ const full = !opts.fast && !opts.banner && !opts.preflight;
28405
+ const applyEnv = full || Boolean(opts.preflight);
28406
+ const applyRepo = full && opts.repoWrites !== false;
28613
28407
  const lane = {
28614
28408
  banner: Boolean(opts.banner),
28615
28409
  fast: Boolean(opts.fast),
28616
28410
  preflight: Boolean(opts.preflight),
28617
- full: !opts.fast && !opts.banner && !opts.preflight
28411
+ full
28618
28412
  };
28619
28413
  const probeReleased = !opts.fast || Boolean(opts.self);
28620
28414
  const probeAws = !opts.fast && !opts.banner;
@@ -28712,9 +28506,6 @@ async function runDoctorClean(opts, io, deps) {
28712
28506
  async function runGithubAuthRow() {
28713
28507
  emitNow(checkGithubAuth({ login, ghInstalled: ghInstalled2, reach }));
28714
28508
  }
28715
- async function runGithubPoolsRows() {
28716
- for (const pool of checkGithubPools(await deps.githubPools())) emitNow(pool);
28717
- }
28718
28509
  async function runAwsRow() {
28719
28510
  const aws = checkAwsIdentity({ isOrgRepo, probed: probeAws, callerArn });
28720
28511
  if (aws) emitNow(aws);
@@ -28735,7 +28526,7 @@ async function runDoctorClean(opts, io, deps) {
28735
28526
  emitNow({ ok: true, id: "gitignore-block", label: "gitignore block", detail: "rewrote managed block", verbose: giEvidence });
28736
28527
  restartPending = true;
28737
28528
  } else {
28738
- emitNow({ ok: false, id: "gitignore-block", label: "gitignore block", fix: "run `mmi-cli doctor --apply` to write the org-managed .gitignore block", verbose: giEvidence });
28529
+ emitNow({ ok: false, id: "gitignore-block", label: "gitignore block", fix: "run `mmi-cli doctor` (without --no-repo-writes) to write the org-managed .gitignore block", verbose: giEvidence });
28739
28530
  }
28740
28531
  }
28741
28532
  async function runPluginCacheRow() {
@@ -28755,35 +28546,6 @@ async function runDoctorClean(opts, io, deps) {
28755
28546
  }
28756
28547
  for (const row of deps.marketplaceRows()) emitNow(row);
28757
28548
  }
28758
- async function runSchedulesRow() {
28759
- const probe = await deps.schedulesNotebook().catch((e) => ({
28760
- armed: 0,
28761
- live: 0,
28762
- declared: 0,
28763
- incomplete: [`schedules probe failed \u2014 ${e.message}`],
28764
- drift: []
28765
- }));
28766
- const sched = checkSchedules(probe);
28767
- if (sched) emitNow(sched);
28768
- }
28769
- async function runRedactorRow() {
28770
- const redactor = checkRedactorLiveness(deps.redactorLiveness());
28771
- if (redactor) emitNow(redactor);
28772
- }
28773
- async function runDocsAuditRow() {
28774
- const probe = await deps.docsAudit().catch((e) => ({
28775
- armed: true,
28776
- ok: false,
28777
- detail: `docs-audit probe failed \u2014 ${e.message}`
28778
- }));
28779
- const docsAudit2 = checkDocsAudit(probe);
28780
- if (docsAudit2) emitNow(docsAudit2);
28781
- }
28782
- async function runWorktreeRootsRow() {
28783
- const probe = await deps.worktreeRoots().catch(() => void 0);
28784
- const roots = checkWorktreeRoots(probe);
28785
- if (roots) emitNow(roots);
28786
- }
28787
28549
  async function runTrainSyncRow() {
28788
28550
  try {
28789
28551
  emitNow(checkTrainSync(await deps.syncTrain()));
@@ -28839,7 +28601,7 @@ async function runDoctorClean(opts, io, deps) {
28839
28601
  // #3574: `${n} stale` was prepended to `fix` because the ✗ branch would not render `detail` —
28840
28602
  // the same fact this row already puts in `detail` on its ✓ path four lines up. One convention now.
28841
28603
  detail: `${n} stale`,
28842
- fix: "run `mmi-cli doctor --apply` to reap merged branches, stale refs, and dead worktrees",
28604
+ fix: "run `mmi-cli doctor` (without --no-repo-writes) to reap merged branches, stale refs, and dead worktrees",
28843
28605
  verbose: gcEvidence
28844
28606
  });
28845
28607
  }
@@ -28853,7 +28615,7 @@ async function runDoctorClean(opts, io, deps) {
28853
28615
  emitNow({ id: "scratch", ok: true, label: "scratch", detail: pruned ? `removed ${pruned} aged item(s)` : "nothing stale", verbose: scratchEvidence });
28854
28616
  if (pruned) restartPending = true;
28855
28617
  } else {
28856
- emitNow(scratch.plan.safeAuto.length === 0 ? { id: "scratch", ok: true, label: "scratch", verbose: scratchEvidence } : { id: "scratch", ok: false, label: "scratch", detail: `${scratch.plan.safeAuto.length} aged item(s)`, fix: "run `mmi-cli doctor --apply`", verbose: scratchEvidence });
28618
+ emitNow(scratch.plan.safeAuto.length === 0 ? { id: "scratch", ok: true, label: "scratch", verbose: scratchEvidence } : { id: "scratch", ok: false, label: "scratch", detail: `${scratch.plan.safeAuto.length} aged item(s)`, fix: "run `mmi-cli doctor` (without --no-repo-writes)", verbose: scratchEvidence });
28857
28619
  }
28858
28620
  }
28859
28621
  const table = [
@@ -28862,17 +28624,12 @@ async function runDoctorClean(opts, io, deps) {
28862
28624
  { id: "plugin", when: true, run: runPluginRow },
28863
28625
  { id: "cli-version", when: true, run: runCliRow },
28864
28626
  { id: "github-auth", when: true, run: runGithubAuthRow },
28865
- { id: "github-pools", when: lane.full, run: runGithubPoolsRows },
28866
28627
  { id: "aws-identity", when: true, run: runAwsRow },
28867
28628
  { id: "repo-worktrees", when: true, run: runRepoWorktreesRow },
28868
28629
  { id: "gitignore-block", when: isOrgRepo, run: runGitignoreRow },
28869
28630
  { id: "plugin-cache", when: true, run: runPluginCacheRow },
28870
28631
  { id: "sessionstart-payload", when: true, run: runSessionPayloadRow },
28871
28632
  { id: "marketplace", when: true, run: runMarketplaceRows },
28872
- { id: "schedules", when: lane.full, run: runSchedulesRow },
28873
- { id: "redactor-liveness", when: lane.full, run: runRedactorRow },
28874
- { id: "docs-audit", when: lane.full, run: runDocsAuditRow },
28875
- { id: "worktree-roots", when: lane.full, run: runWorktreeRootsRow },
28876
28633
  // The two most expensive things in this file — a real `git fetch` plus train-branch fast-forward,
28877
28634
  // and a `gh`-backed gc sweep with a 20s timeout — so org repos on the full lane only (#3485).
28878
28635
  { id: "train-branches", when: isOrgRepo && lane.full, run: runTrainSyncRow },
@@ -29170,9 +28927,6 @@ async function withEnvHealLock(what, run) {
29170
28927
  return { ok: false, detail: `${what} could not take the env-heal lock \u2014 ${e.message}` };
29171
28928
  }
29172
28929
  }
29173
- function docsJanitorScheduleId(repo) {
29174
- return `${repo.split("/").pop()}/docs-janitor`;
29175
- }
29176
28930
  function throttledReleasedVersion() {
29177
28931
  let note;
29178
28932
  return {
@@ -29193,8 +28947,6 @@ function throttledReleasedVersion() {
29193
28947
  }
29194
28948
  function mmiDoctorDeps(opts = {}) {
29195
28949
  const throttled = opts.throttleReleasedRead ? throttledReleasedVersion() : void 0;
29196
- let notebook;
29197
- const notebookOnce = () => notebook ??= fetchNotebook();
29198
28950
  let surfaceEvidence;
29199
28951
  let surfaceEvidenceRead = false;
29200
28952
  const surfaceEvidenceOnce = (isOrgRepo) => {
@@ -29218,11 +28970,6 @@ function mmiDoctorDeps(opts = {}) {
29218
28970
  };
29219
28971
  return surfaceEvidence;
29220
28972
  };
29221
- const docsJanitorArmedAt = async (repo) => {
29222
- const wanted = docsJanitorScheduleId(repo);
29223
- const { entries } = await notebookOnce();
29224
- return entries.find((e) => e.scheduleId === wanted)?.armedAt;
29225
- };
29226
28973
  return {
29227
28974
  githubLogin,
29228
28975
  ghInstalled,
@@ -29261,24 +29008,6 @@ function mmiDoctorDeps(opts = {}) {
29261
29008
  // writes to the harness-owned cache itself. Same plan builder the verb uses, so the two never disagree.
29262
29009
  // No `withBytes` here: doctor runs on EVERY SessionStart, and sizing means recursively stat'ing every
29263
29010
  // stale version tree. The count comes from one cheap readdir; `plugin-prune` reports the MB.
29264
- // #3025: both identities' rate pools. Personal probes with the ambient token; the App line only exists
29265
- // when this machine can mint — a vault-denied read means "not an automation host", not a failure.
29266
- githubPools: async () => {
29267
- const deps = appActorDeps();
29268
- const personal = await probeRatePools(deps.fetch, await githubToken());
29269
- let app = null;
29270
- try {
29271
- const minted = await mintInstallationToken(deps);
29272
- app = await probeRatePools(deps.fetch, minted.token) ?? "token minted but rate_limit probe failed \u2014 check network/App permissions";
29273
- } catch (e) {
29274
- if (e instanceof AppActorError) {
29275
- if (e.code !== "vault-denied") app = e.message;
29276
- } else {
29277
- app = "app actor probe failed unexpectedly \u2014 run `MMI_ACTOR=app mmi-cli pr checks-wait --help` to reproduce";
29278
- }
29279
- }
29280
- return { personal, app };
29281
- },
29282
29011
  pluginCache: () => {
29283
29012
  const surface = detectSurface(process.env);
29284
29013
  const configRoot = surfaceConfigRoot(surface);
@@ -29297,41 +29026,9 @@ function mmiDoctorDeps(opts = {}) {
29297
29026
  stagingBytes: plan.stagingBytes
29298
29027
  };
29299
29028
  },
29300
- // #3008: the schedules-notebook drift probe, compacted for the check. Full doctor only — the gather in
29301
- // runDoctorClean skips this dep entirely on fast/banner/preflight runs.
29302
- schedulesNotebook: async () => {
29303
- const { entries, incomplete, drift, reconciliation } = await notebookOnce();
29304
- const repoName = await repoSlug().catch(() => "");
29305
- const clearableHere = repoName ? reconciliation.filter((d) => driftClearableFrom(d, repoName)).length : 0;
29306
- return {
29307
- armed: entries.length,
29308
- live: entries.filter((e) => e.resolved === "live").length,
29309
- declared: entries.filter((e) => e.resolved === "declared").length,
29310
- incomplete,
29311
- drift,
29312
- clearableHere
29313
- };
29314
- },
29315
- // #3075: the docs-audit dead-man verdict probe, compacted for the check. Full doctor only, exactly like
29316
- // schedulesNotebook — a registry read the SessionStart banner / --preflight / --fast lanes never pay for.
29317
- // The 404/absent route maps to `armed:false` (an informational "not armed" line), never a false RED.
29318
- docsAudit: async () => {
29319
- const repo = await currentRepoFullName();
29320
- const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
29321
- const armedAt = await docsJanitorArmedAt(repo).catch(() => void 0);
29322
- const status = docsAuditStatus(await readDocsAuditFetch(repo), { repo, today, armedAt });
29323
- return { armed: status.state !== "not-armed", ok: status.ok, detail: status.line };
29324
- },
29325
- // #3471: the competing-worktrees-roots probe. Full doctor only (the gather skips it on
29326
- // fast/banner/preflight), and READ-ONLY by construction — it stats, it never removes. Nothing here is
29327
- // wired to a reaper, in either mode.
29328
- worktreeRoots: async () => worktreeRootsProbe(await repoRoot()),
29329
29029
  // #3470: what the SessionStart hook LAST emitted, measured at emission by the session-start verb below.
29330
29030
  // A local record read — cheap enough for every lane, including the banner.
29331
29031
  sessionPayload: () => readSessionPayload(process.cwd()),
29332
- // #3630: recent secret-redact crash markers from the shared hook-activity trace — with the Stop
29333
- // hook retired, the full/scheduled doctor is the trace's only reader. Local tail-scan, fail-soft.
29334
- redactorLiveness: () => redactorLivenessProbe(process.cwd()),
29335
29032
  // #3485 items 9 and 8: why the MMI plugin has never printed an "updated — please restart" notice, and
29336
29033
  // which branch it would pick one up from. Two local file reads, no network, fail-soft to no rows.
29337
29034
  marketplaceRows: () => {
@@ -29342,8 +29039,8 @@ function mmiDoctorDeps(opts = {}) {
29342
29039
  MMI_MARKETPLACE_NAME,
29343
29040
  readFileSyncSafe((0, import_node_path34.join)(home, ...KNOWN_MARKETPLACES_RELATIVE), import_node_fs35.readFileSync),
29344
29041
  readFileSyncSafe((0, import_node_path34.join)(home, ".claude", "settings.json"), import_node_fs35.readFileSync),
29345
- // #3974: this CLI heals these rows, so report mode names `doctor --apply` rather than the hand
29346
- // edit. Unconditional — it is a fact about mmi-cli, not about whether --apply was passed.
29042
+ // #3974: this CLI heals these rows, so report mode names the doctor run rather than the hand
29043
+ // edit. Unconditional — it is a fact about mmi-cli, not about the lane this run is on.
29347
29044
  true
29348
29045
  );
29349
29046
  } catch {
@@ -29536,6 +29233,7 @@ function appActorDeps() {
29536
29233
  fetch: (url, init) => fetch(url, init)
29537
29234
  };
29538
29235
  }
29236
+ setPollTokenProvider(() => pollTokenOrUndefined(appActorDeps()));
29539
29237
  function shouldMarkWorktreeActivity(commandPath3) {
29540
29238
  return commandPath3.split(" ")[0] !== "worktree";
29541
29239
  }
@@ -32018,14 +31716,13 @@ access.command("audit").description("audit collaborator roles + train-branch pus
32018
31716
  });
32019
31717
  access.command("capabilities").description("enumerate your effective vault reach \u2014 every credential NAME + tier + scope you can read/use across project + org/master tiers (names only, no values) (#1615)").option("--repo <owner/repo>", "target repo (defaults to the current repo)").option("--json", "machine-readable output").action((o) => withSecrets((d) => secretsCapabilities(d, o)));
32020
31718
  var isWin2 = process.platform === "win32";
32021
- program2.command("doctor").description("check onboarding gates and auto-heal CLI/plugin wiring; use --verbose for the full audit checklist").option("--banner", "one-line resume summary; silent when all gates pass").option("--fast", "offline-safe fast lane; skips slow checks and network probes").option("--preflight", "eager version/plugin-heal (env repairs only \u2014 never the repo working tree) with upfront notice when stale; silent when healthy (#1871)").option("--verbose", "print the evidence behind every check \u2014 probes, resolved paths, versions compared, and the names behind each count (#2977)").option("--guide", "print the MMI Agentic Onboarding guide URL").option("--json", "machine-readable output (read-only inspection \u2014 performs no repairs)").option("--apply", "perform the same auto-repairs as the interactive run (combine with --json for a machine-readable repair run)").option("--no-repo-writes", "env/plugin repairs only \u2014 never mutate the repo working tree; report pending managed .gitignore repairs with the follow-up command (for train preflights)").option("--self", "verify CLI/plugin version parity and gh auth reach; suggests plugin-heal on a hard gap (reads the published version, so not offline-safe) (#2689)").addHelpText("after", "\nExit codes:\n 0 no hard gaps remain; advisory gaps may exist\n 1 one or more included hard checks failed\n\nThree rows (worktree roots, schedules, docs-audit) are report-only: they still print \u2717 with their fix,\nbut never move the exit code, because their remedy is a deliberate human review or lives in the repo\nthat owns the fault. Every other red row gates, including ones only you can clear by hand (#3485).\n\n--banner keeps the legacy SessionStart contract and returns 0 unless the process crashes.\n").action(async (opts) => {
31719
+ program2.command("doctor").description("heal CLI/plugin wiring and clean up repo cruft \u2014 repairs run by default (#3975); use --verbose for the full audit checklist").option("--banner", "one-line resume summary; silent when all gates pass").option("--fast", "offline-safe fast lane; read-only, skips slow checks and network probes").option("--preflight", "eager version/plugin-heal (env repairs only \u2014 never the repo working tree) with upfront notice when stale; silent when healthy (#1871)").option("--verbose", "print the evidence behind every check \u2014 probes, resolved paths, versions compared, and the names behind each count (#2977)").option("--guide", "print the MMI Agentic Onboarding guide URL").option("--json", "machine-readable output (an output format \u2014 repairs still run by lane)").option("--apply", "deprecated no-op: repairs run by default now (#3975); kept so older instructions still parse").option("--no-repo-writes", "env/plugin repairs only \u2014 never mutate the repo working tree; report pending managed .gitignore repairs with the follow-up command (for train preflights)").option("--self", "verify CLI/plugin version parity and gh auth reach; suggests plugin-heal on a hard gap (reads the published version, so not offline-safe) (#2689)").addHelpText("after", "\nExit codes:\n 0 no hard gaps remain; advisory gaps may exist\n 1 one or more included hard checks failed\n\nA plain run heals env drift (CLI, plugin, marketplace pins) and cleans repo cruft (gitignore block,\nmerged branches, dead worktrees, aged scratch) automatically (#3975). --no-repo-writes keeps the\nworking tree untouched for train preflights; --banner/--fast/--self are read-only lanes.\n\n--banner keeps the legacy SessionStart contract and returns 0 unless the process crashes.\n").action(async (opts) => {
32022
31720
  if (opts.guide) {
32023
31721
  consoleIo.log("MMI Agentic Onboarding: docs/Architecture/agentic-dev-environment.md");
32024
31722
  return;
32025
31723
  }
32026
31724
  process.exitCode = await runDoctorClean(
32027
31725
  {
32028
- apply: Boolean(opts.apply),
32029
31726
  repoWrites: opts.repoWrites,
32030
31727
  banner: opts.banner,
32031
31728
  preflight: opts.preflight,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mutmutco/cli",
3
- "version": "3.81.0",
3
+ "version": "3.82.0",
4
4
  "description": "MMI Future CLI — the org dev toolbox and shared cross-IDE engine for every registry-declared MMI coding surface.",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",