@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.
- package/dist/main.cjs +38 -341
- 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
|
|
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
|
|
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
|
|
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
|
|
28612
|
-
const
|
|
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
|
|
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 --
|
|
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 --
|
|
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 --
|
|
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
|
|
29346
|
-
// edit. Unconditional — it is a fact about mmi-cli, not about
|
|
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("
|
|
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