@mutmutco/cli 3.81.0 → 3.83.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 +459 -594
  2. package/package.json +1 -1
package/dist/main.cjs CHANGED
@@ -3416,7 +3416,7 @@ var program = new Command();
3416
3416
 
3417
3417
  // src/index.ts
3418
3418
  var import_promises10 = require("node:fs/promises");
3419
- var import_node_fs35 = require("node:fs");
3419
+ var import_node_fs36 = require("node:fs");
3420
3420
  var import_node_child_process16 = require("node:child_process");
3421
3421
 
3422
3422
  // src/cli-shared.ts
@@ -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);
@@ -6460,7 +6431,14 @@ async function provisionWorktree(worktreeRoot, deps) {
6460
6431
  });
6461
6432
  const allDirs = scanInstallDirs(worktreeRoot, fs2);
6462
6433
  const targets = npmInstallTargets(allDirs);
6463
- const skippedInstall = allDirs.filter((d) => d.hasPackageJson && d.hasNodeModules).map((d) => d.dir);
6434
+ if (deps.validateInstall) {
6435
+ for (const dir of allDirs.filter((d) => d.hasPackageJson && d.hasLockfile && d.hasNodeModules)) {
6436
+ const cwd = dir.dir ? (0, import_node_path10.join)(worktreeRoot, dir.dir) : worktreeRoot;
6437
+ if (!await deps.validateInstall(cwd)) targets.push({ dir: dir.dir, command: "npm ci" });
6438
+ }
6439
+ }
6440
+ const targetDirs = new Set(targets.map((target) => target.dir));
6441
+ const skippedInstall = allDirs.filter((d) => d.hasPackageJson && d.hasNodeModules && !targetDirs.has(d.dir)).map((d) => d.dir);
6464
6442
  const installed = [];
6465
6443
  for (const target of targets) {
6466
6444
  const cwd = target.dir ? (0, import_node_path10.join)(worktreeRoot, target.dir) : worktreeRoot;
@@ -6624,7 +6602,7 @@ function commandLadderHint() {
6624
6602
  }
6625
6603
 
6626
6604
  // src/index.ts
6627
- var import_node_path34 = require("node:path");
6605
+ var import_node_path35 = require("node:path");
6628
6606
 
6629
6607
  // src/merge-ci-policy.ts
6630
6608
  function resolveMergeCiPolicy(input) {
@@ -8056,7 +8034,7 @@ async function foldReleaseVersion(deps, model, tag, foldPaths) {
8056
8034
  if (foldPaths.length === 0) return "no version manifest to fold \u2014 the tag is the version";
8057
8035
  const version = tag.replace(/^v/, "");
8058
8036
  if (model === "hub-serverless") {
8059
- await deps.run("node", ["scripts/release-distribution.mjs", "prepare", version]);
8037
+ await deps.run("node", ["scripts/release-distribution.mjs", "prepare", version, "--source-commit", "HEAD"]);
8060
8038
  } else {
8061
8039
  await installAppFoldDeps(deps);
8062
8040
  await deps.run("npm", ["version", version, "--no-git-tag-version", "--allow-same-version"]);
@@ -11712,10 +11690,13 @@ async function executeWaveLand(plan, deps, opts = { preserveWorktree: true }) {
11712
11690
  }
11713
11691
 
11714
11692
  // src/index.ts
11715
- var import_node_os13 = require("node:os");
11693
+ var import_node_os14 = require("node:os");
11716
11694
 
11717
11695
  // src/board.ts
11718
11696
  var import_node_child_process8 = require("node:child_process");
11697
+ var import_node_fs20 = require("node:fs");
11698
+ var import_node_os7 = require("node:os");
11699
+ var import_node_path18 = require("node:path");
11719
11700
  var import_node_util6 = require("node:util");
11720
11701
 
11721
11702
  // src/board-priority.ts
@@ -13943,62 +13924,6 @@ async function branchesMergedIntoBase(remote) {
13943
13924
  }
13944
13925
  return [...out];
13945
13926
  }
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
13927
 
14003
13928
  // src/repo-resolve.ts
14004
13929
  function slugOf(repoOrSlug) {
@@ -14926,6 +14851,25 @@ function applyOrgMarketplacePins(path2, names) {
14926
14851
  "pin"
14927
14852
  );
14928
14853
  }
14854
+ function writeMarketplacePinPending(path2, names, now = Date.now()) {
14855
+ try {
14856
+ (0, import_node_fs18.mkdirSync)((0, import_node_path16.dirname)(path2), { recursive: true });
14857
+ (0, import_node_fs18.writeFileSync)(path2, `${JSON.stringify({ v: 1, names: [...names], at: new Date(now).toISOString() })}
14858
+ `, "utf8");
14859
+ } catch {
14860
+ }
14861
+ }
14862
+ function readMarketplacePinPending(path2, name, now = Date.now()) {
14863
+ try {
14864
+ const parsed = JSON.parse((0, import_node_fs18.readFileSync)(path2, "utf8"));
14865
+ const at = typeof parsed.at === "string" ? Date.parse(parsed.at) : Number.NaN;
14866
+ if (parsed.v !== 1 || !Array.isArray(parsed.names) || !parsed.names.includes(name) || !Number.isFinite(at)) return void 0;
14867
+ if (now - at < 0 || now - at > 12 * 60 * 6e4) return void 0;
14868
+ return { v: 1, names: parsed.names.filter((n) => typeof n === "string"), at: parsed.at };
14869
+ } catch {
14870
+ return void 0;
14871
+ }
14872
+ }
14929
14873
  function restoredPinsLine(pins) {
14930
14874
  const named = [...pins].map(([name, want]) => {
14931
14875
  const fields = [
@@ -16214,6 +16158,38 @@ async function postClaimMarkerComment(client, item) {
16214
16158
  `);
16215
16159
  }
16216
16160
  }
16161
+ var CLAIM_SESSION_ACTIVITY_MS = 30 * 6e4;
16162
+ var CLAIM_SESSION_PROBE_CACHE_MS = 6e4;
16163
+ var claimSessionProbeCache = /* @__PURE__ */ new Map();
16164
+ function probeLocalClaimSession(marker, now = Date.now()) {
16165
+ if (!marker.session || !marker.host || marker.host.toLowerCase() !== (0, import_node_os7.hostname)().toLowerCase()) return void 0;
16166
+ if (!marker.surface?.toLowerCase().startsWith("claude")) return void 0;
16167
+ const cacheKey = `${marker.host.toLowerCase()}/${marker.session}`;
16168
+ const cached = claimSessionProbeCache.get(cacheKey);
16169
+ if (cached && now - cached.checkedAt <= CLAIM_SESSION_PROBE_CACHE_MS) return cached.state;
16170
+ const remember = (state) => {
16171
+ claimSessionProbeCache.set(cacheKey, { checkedAt: now, state });
16172
+ return state;
16173
+ };
16174
+ const root = (0, import_node_path18.join)((0, import_node_os7.homedir)(), ".claude", "projects");
16175
+ try {
16176
+ const wanted = `${marker.session}.jsonl`.toLowerCase();
16177
+ const pending = [root];
16178
+ while (pending.length) {
16179
+ const dir = pending.pop();
16180
+ for (const entry of (0, import_node_fs20.readdirSync)(dir, { withFileTypes: true })) {
16181
+ const path2 = (0, import_node_path18.join)(dir, entry.name);
16182
+ if (entry.isDirectory()) pending.push(path2);
16183
+ else if (entry.isFile() && entry.name.toLowerCase() === wanted) {
16184
+ return remember(now - (0, import_node_fs20.statSync)(path2).mtimeMs <= CLAIM_SESSION_ACTIVITY_MS ? "live" : "dead");
16185
+ }
16186
+ }
16187
+ }
16188
+ return remember("dead");
16189
+ } catch {
16190
+ return remember("unverifiable");
16191
+ }
16192
+ }
16217
16193
  function openPullsFetcher(client) {
16218
16194
  const byRepo = /* @__PURE__ */ new Map();
16219
16195
  return (repo) => {
@@ -16231,6 +16207,11 @@ async function gatherClaimLiveness(client, repo, number, fetchOpenPulls) {
16231
16207
  const comments = await client.restPaginate(`repos/${repo}/issues/${number}/comments`);
16232
16208
  out.marker = latestClaimMarker(comments.map((comment) => ({ body: comment.body ?? "" })));
16233
16209
  out.markerAgeMs = out.marker ? Date.now() - Date.parse(out.marker.ts) : void 0;
16210
+ if (out.marker) {
16211
+ const state = probeLocalClaimSession(out.marker);
16212
+ if (state === "live" || state === "dead") out.sessionState = state;
16213
+ else if (state === "unverifiable") out.failed.push("session");
16214
+ }
16234
16215
  } catch {
16235
16216
  out.failed.push("comments");
16236
16217
  }
@@ -16262,8 +16243,10 @@ async function gatherClaimLiveness(client, repo, number, fetchOpenPulls) {
16262
16243
  }
16263
16244
  function liveEvidenceLines(evidence, repo) {
16264
16245
  const live = [];
16265
- if (evidence.marker && evidence.markerAgeMs !== void 0 && evidence.markerAgeMs < CLAIM_MARKER_LIVE_MS) {
16266
- live.push(`claim marker from ${describeClaimMarker(evidence.marker)} is only ${formatClaimAge(evidence.markerAgeMs)} old`);
16246
+ if (evidence.marker && evidence.sessionState === "live") {
16247
+ live.push(`claim session ${describeClaimMarker(evidence.marker)} has recent local transcript activity`);
16248
+ } else if (evidence.marker && evidence.sessionState !== "dead" && evidence.markerAgeMs !== void 0 && evidence.markerAgeMs < CLAIM_MARKER_LIVE_MS) {
16249
+ live.push(`claim marker from ${describeClaimMarker(evidence.marker)} is only ${formatClaimAge(evidence.markerAgeMs)} old (session not locally probeable)`);
16267
16250
  }
16268
16251
  if (evidence.openPr) live.push(evidence.openPr);
16269
16252
  if (evidence.branch) live.push(`live branch ${evidence.branch} on ${repo}`);
@@ -16359,9 +16342,9 @@ async function boardDoctor(options, deps = {}) {
16359
16342
  while (next < claimedItems.length) {
16360
16343
  const item = claimedItems[next++];
16361
16344
  const evidence = await gatherClaimLiveness(client, item.repository, item.number, fetchOpenPulls);
16362
- const freshMarker = evidence.markerAgeMs !== void 0 && evidence.markerAgeMs < CLAIM_MARKER_LIVE_MS;
16345
+ const freshMarker = evidence.sessionState === "live" || evidence.sessionState !== "dead" && evidence.markerAgeMs !== void 0 && evidence.markerAgeMs < CLAIM_MARKER_LIVE_MS;
16363
16346
  const bits = [
16364
- evidence.marker ? `claim marker ${describeClaimMarker(evidence.marker)} ${formatClaimAge(evidence.markerAgeMs)} old` : evidence.failed.includes("comments") ? "claim marker unreadable" : "no claim marker",
16347
+ evidence.marker ? `claim marker ${describeClaimMarker(evidence.marker)} ${formatClaimAge(evidence.markerAgeMs)} old` + (evidence.sessionState ? `; local session ${evidence.sessionState}` : "") : evidence.failed.includes("comments") ? "claim marker unreadable" : "no claim marker",
16365
16348
  evidence.openPr,
16366
16349
  evidence.branch ? `branch ${evidence.branch}` : void 0
16367
16350
  ].filter((bit) => Boolean(bit)).join("; ");
@@ -16465,7 +16448,7 @@ async function resolveAutoAddBoardAttach(client, cfg, selector, priority, warn =
16465
16448
  }
16466
16449
 
16467
16450
  // src/issue-body.ts
16468
- var import_node_os7 = require("node:os");
16451
+ var import_node_os8 = require("node:os");
16469
16452
  var TextArgError = class extends Error {
16470
16453
  constructor(message, code, offendingFlag) {
16471
16454
  super(message);
@@ -16477,7 +16460,7 @@ var TextArgError = class extends Error {
16477
16460
  offendingFlag;
16478
16461
  };
16479
16462
  function emptyStdinMessage(fileFlag) {
16480
- if ((0, import_node_os7.platform)() === "win32") {
16463
+ if ((0, import_node_os8.platform)() === "win32") {
16481
16464
  return `${fileFlag} - read empty stdin (on Windows, ${fileFlag} - is unreliable through the npm .cmd shim \u2014 use ${fileFlag} <path>, or pipe to \`node cli/dist/index.cjs\` directly)`;
16482
16465
  }
16483
16466
  return `${fileFlag} - read empty stdin (nothing piped \u2014 pass a heredoc/pipe, or ${fileFlag} <path>)`;
@@ -17802,7 +17785,7 @@ async function runHotfixStart(deps, options) {
17802
17785
  }
17803
17786
  notes.push(`cherry-picked ${label} onto ${branch} (from origin/main, -x trailer recorded)`);
17804
17787
  if (deployModel === "hub-serverless") {
17805
- await deps.run("node", ["scripts/release-distribution.mjs", "prepare", version]);
17788
+ await deps.run("node", ["scripts/release-distribution.mjs", "prepare", version, "--source-commit", "HEAD"]);
17806
17789
  const changedFiles = (await deps.run("node", ["scripts/release-distribution.mjs", "changed-files"])).split("\n").map((s) => s.trim()).filter(Boolean);
17807
17790
  await deps.run("git", ["add", "-f", "--", ...changedFiles]);
17808
17791
  const staged = await deps.run("git", ["diff", "--cached", "--name-only"]);
@@ -18660,8 +18643,8 @@ function renderAccessReport(report) {
18660
18643
 
18661
18644
  // src/doc-refs-core.ts
18662
18645
  var import_node_child_process10 = require("node:child_process");
18663
- var import_node_fs20 = require("node:fs");
18664
- var import_node_path18 = require("node:path");
18646
+ var import_node_fs21 = require("node:fs");
18647
+ var import_node_path19 = require("node:path");
18665
18648
  var PIN_RE = /<!--\s*pinned by\s+([^:]+?)\s*:\s*"([^"]+)"[^"]*-->/;
18666
18649
  var PIN_MENTION_RE = /<!--\s*pinned by\b/;
18667
18650
  var FWD_RE = /<!--\s*forward-ref:\s*(\S+?)\s*-->/;
@@ -18717,7 +18700,7 @@ function checkPins(root, readFile9, docs2) {
18717
18700
  findings.push({ kind: "malformed-pin", doc, line: pin.line, detail: pin.text });
18718
18701
  continue;
18719
18702
  }
18720
- const source = readFile9((0, import_node_path18.join)(root, pin.file));
18703
+ const source = readFile9((0, import_node_path19.join)(root, pin.file));
18721
18704
  if (source == null) {
18722
18705
  findings.push({ kind: "missing-test", doc, line: pin.line, detail: pin.file });
18723
18706
  continue;
@@ -18777,7 +18760,7 @@ function checkRefs(root, deps, docs2) {
18777
18760
  const candidates = [];
18778
18761
  const direct = [];
18779
18762
  for (const [doc, markdown] of Object.entries(docs2)) {
18780
- const docDir = import_node_path18.posix.dirname(doc);
18763
+ const docDir = import_node_path19.posix.dirname(doc);
18781
18764
  const base = docDir === "." ? "" : docDir;
18782
18765
  const covered = /* @__PURE__ */ new Set();
18783
18766
  const markers = [];
@@ -18786,21 +18769,21 @@ function checkRefs(root, deps, docs2) {
18786
18769
  direct.push({ kind: "malformed-forward-ref", doc, line: fwd.line, detail: fwd.text });
18787
18770
  continue;
18788
18771
  }
18789
- const docRel = import_node_path18.posix.normalize(import_node_path18.posix.join(base, fwd.target));
18790
- const rootRel = import_node_path18.posix.normalize(fwd.target.replace(/^\/+/, ""));
18772
+ const docRel = import_node_path19.posix.normalize(import_node_path19.posix.join(base, fwd.target));
18773
+ const rootRel = import_node_path19.posix.normalize(fwd.target.replace(/^\/+/, ""));
18791
18774
  markers.push({ target: fwd.target, line: fwd.line, docRel, rootRel });
18792
18775
  covered.add(docRel);
18793
18776
  covered.add(rootRel);
18794
18777
  }
18795
18778
  const links = extractLinks(markdown).map(({ target, line }) => {
18796
- const resolved = import_node_path18.posix.normalize(import_node_path18.posix.join(base, target));
18797
- return { target, line, resolved, missing: !exists((0, import_node_path18.join)(root, resolved)) };
18779
+ const resolved = import_node_path19.posix.normalize(import_node_path19.posix.join(base, target));
18780
+ return { target, line, resolved, missing: !exists((0, import_node_path19.join)(root, resolved)) };
18798
18781
  });
18799
18782
  for (const marker of markers) {
18800
18783
  const coversMissing = links.some(
18801
18784
  (l) => l.missing && (l.resolved === marker.docRel || l.resolved === marker.rootRel)
18802
18785
  );
18803
- if (!coversMissing && (exists((0, import_node_path18.join)(root, marker.docRel)) || exists((0, import_node_path18.join)(root, marker.rootRel)))) {
18786
+ if (!coversMissing && (exists((0, import_node_path19.join)(root, marker.docRel)) || exists((0, import_node_path19.join)(root, marker.rootRel)))) {
18804
18787
  direct.push({
18805
18788
  kind: "stale-forward-ref",
18806
18789
  doc,
@@ -18811,8 +18794,8 @@ function checkRefs(root, deps, docs2) {
18811
18794
  }
18812
18795
  for (const { ref, line } of extractRefs(markdown)) {
18813
18796
  const first = ref.split("/")[0];
18814
- if (!exists((0, import_node_path18.join)(root, first))) continue;
18815
- if (!exists((0, import_node_path18.join)(root, ref))) candidates.push({ kind: "missing-path", doc, line, detail: ref });
18797
+ if (!exists((0, import_node_path19.join)(root, first))) continue;
18798
+ if (!exists((0, import_node_path19.join)(root, ref))) candidates.push({ kind: "missing-path", doc, line, detail: ref });
18816
18799
  }
18817
18800
  for (const { target, line, resolved, missing } of links) {
18818
18801
  if (resolved.startsWith("..")) {
@@ -18860,22 +18843,22 @@ function checkCommands(docs2, commandPaths) {
18860
18843
  return { ok: findings.length === 0, findings, warnings: [] };
18861
18844
  }
18862
18845
  function readFileOrNull(path2) {
18863
- return (0, import_node_fs20.existsSync)(path2) ? (0, import_node_fs20.readFileSync)(path2, "utf8") : null;
18846
+ return (0, import_node_fs21.existsSync)(path2) ? (0, import_node_fs21.readFileSync)(path2, "utf8") : null;
18864
18847
  }
18865
18848
  function walk(dir, root, out) {
18866
- for (const entry of (0, import_node_fs20.readdirSync)(dir)) {
18867
- const full = (0, import_node_path18.join)(dir, entry);
18868
- if ((0, import_node_fs20.statSync)(full).isDirectory()) walk(full, root, out);
18849
+ for (const entry of (0, import_node_fs21.readdirSync)(dir)) {
18850
+ const full = (0, import_node_path19.join)(dir, entry);
18851
+ if ((0, import_node_fs21.statSync)(full).isDirectory()) walk(full, root, out);
18869
18852
  else if (entry.endsWith(".md")) out.push(full.slice(root.length + 1).replaceAll("\\", "/"));
18870
18853
  }
18871
18854
  return out;
18872
18855
  }
18873
18856
  function defaultListDocs(root) {
18874
- const docsDir = (0, import_node_path18.join)(root, "docs");
18875
- const docs2 = ((0, import_node_fs20.existsSync)(docsDir) ? walk(docsDir, root, []) : []).filter(
18857
+ const docsDir = (0, import_node_path19.join)(root, "docs");
18858
+ const docs2 = ((0, import_node_fs21.existsSync)(docsDir) ? walk(docsDir, root, []) : []).filter(
18876
18859
  (rel) => !SKIP_WALK.some((skip) => rel.startsWith(skip))
18877
18860
  );
18878
- return [...ROOT_DOCS.filter((rel) => (0, import_node_fs20.existsSync)((0, import_node_path18.join)(root, rel))), ...docs2];
18861
+ return [...ROOT_DOCS.filter((rel) => (0, import_node_fs21.existsSync)((0, import_node_path19.join)(root, rel))), ...docs2];
18879
18862
  }
18880
18863
  function defaultIsIgnored(root, relPaths, exec = import_node_child_process10.execFileSync) {
18881
18864
  const inRepo = relPaths.filter((p) => !p.startsWith(".."));
@@ -18895,14 +18878,14 @@ function defaultIsIgnored(root, relPaths, exec = import_node_child_process10.exe
18895
18878
  }
18896
18879
  function runDocRefs(root, deps = {}) {
18897
18880
  const readFile9 = deps.readFile ?? readFileOrNull;
18898
- const exists = deps.exists ?? import_node_fs20.existsSync;
18881
+ const exists = deps.exists ?? import_node_fs21.existsSync;
18899
18882
  const listDocs = deps.listDocs ?? defaultListDocs;
18900
18883
  const isIgnored = deps.isIgnored ?? ((paths) => defaultIsIgnored(root, paths));
18901
18884
  const commandPaths = deps.commandPaths ?? null;
18902
18885
  const walked = listDocs(root);
18903
18886
  const ignoredDocs = walked.length ? isIgnored(walked) : /* @__PURE__ */ new Set();
18904
18887
  const docs2 = Object.fromEntries(
18905
- walked.filter((rel) => !ignoredDocs.has(rel)).map((rel) => [rel, readFile9((0, import_node_path18.join)(root, rel))]).filter(([, body]) => body != null)
18888
+ walked.filter((rel) => !ignoredDocs.has(rel)).map((rel) => [rel, readFile9((0, import_node_path19.join)(root, rel))]).filter(([, body]) => body != null)
18906
18889
  );
18907
18890
  const refResult = checkRefs(root, { exists, isIgnored }, docs2);
18908
18891
  const findings = [
@@ -18929,8 +18912,8 @@ function runDocRefs(root, deps = {}) {
18929
18912
 
18930
18913
  // src/spawn-policy-core.ts
18931
18914
  var import_node_child_process11 = require("node:child_process");
18932
- var import_node_fs21 = require("node:fs");
18933
- var import_node_path19 = require("node:path");
18915
+ var import_node_fs22 = require("node:fs");
18916
+ var import_node_path20 = require("node:path");
18934
18917
  var SPAWNERS = ["spawn", "spawnSync", "exec", "execSync", "execFile", "execFileSync"];
18935
18918
  var CALL_SOURCE = String.raw`(^|[^.\w$])(${SPAWNERS.join("|")})\s*\(`;
18936
18919
  var SOURCE_EXT = /\.(ts|mts|cts|js|mjs|cjs)$/;
@@ -19016,7 +18999,7 @@ function runSpawnPolicy(root) {
19016
18999
  for (const file of files) {
19017
19000
  let raw;
19018
19001
  try {
19019
- raw = (0, import_node_fs21.readFileSync)((0, import_node_path19.join)(root, file), "utf8");
19002
+ raw = (0, import_node_fs22.readFileSync)((0, import_node_path20.join)(root, file), "utf8");
19020
19003
  } catch {
19021
19004
  continue;
19022
19005
  }
@@ -19034,8 +19017,8 @@ function runSpawnPolicy(root) {
19034
19017
 
19035
19018
  // src/test-policy-core.ts
19036
19019
  var import_node_child_process12 = require("node:child_process");
19037
- var import_node_fs22 = require("node:fs");
19038
- var import_node_path20 = require("node:path");
19020
+ var import_node_fs23 = require("node:fs");
19021
+ var import_node_path21 = require("node:path");
19039
19022
  var POLICY_FILE = "test-policy.json";
19040
19023
  var TEST_RE = /\.(?:test|spec)\.[cm]?[jt]sx?$/;
19041
19024
  var PY_TEST_RE = /(?:^|\/)test_[^/]*\.py$|_test\.py$/;
@@ -19088,7 +19071,7 @@ function isTestPath(path2) {
19088
19071
  return TEST_RE.test(path2) || PY_TEST_RE.test(path2);
19089
19072
  }
19090
19073
  function loadPolicy(root, readFile9 = readFileOrNull2) {
19091
- const raw = readFile9((0, import_node_path20.join)(root, POLICY_FILE));
19074
+ const raw = readFile9((0, import_node_path21.join)(root, POLICY_FILE));
19092
19075
  if (raw == null) return { mandatory: [], declared: false };
19093
19076
  try {
19094
19077
  return { ...JSON.parse(raw), declared: true };
@@ -19098,7 +19081,7 @@ function loadPolicy(root, readFile9 = readFileOrNull2) {
19098
19081
  }
19099
19082
  function readFileOrNull2(path2) {
19100
19083
  try {
19101
- return (0, import_node_fs22.readFileSync)(path2, "utf8");
19084
+ return (0, import_node_fs23.readFileSync)(path2, "utf8");
19102
19085
  } catch {
19103
19086
  return null;
19104
19087
  }
@@ -19125,12 +19108,12 @@ function classify(changed, policy, present = () => false) {
19125
19108
  const removedProtected = [...removed].filter((p) => protectedBy.has(p)).map((p) => ({ path: p, why: protectedBy.get(p) ?? "" }));
19126
19109
  return { mandatoryHits, untestedHits, testChanges, addedTests, removedProtected };
19127
19110
  }
19128
- function unresolvedProtectedEntries(policy, root, exists = (path2) => (0, import_node_fs22.existsSync)(path2)) {
19129
- return (policy.protected ?? []).map((p) => p.path).filter((p) => !exists((0, import_node_path20.join)(root, p)));
19111
+ function unresolvedProtectedEntries(policy, root, exists = (path2) => (0, import_node_fs23.existsSync)(path2)) {
19112
+ return (policy.protected ?? []).map((p) => p.path).filter((p) => !exists((0, import_node_path21.join)(root, p)));
19130
19113
  }
19131
- function unresolvedSatisfiers(policy, root, exists = (path2) => (0, import_node_fs22.existsSync)(path2)) {
19114
+ function unresolvedSatisfiers(policy, root, exists = (path2) => (0, import_node_fs23.existsSync)(path2)) {
19132
19115
  const declared = (policy.mandatory ?? []).flatMap((m) => m.satisfiedBy ?? []);
19133
- return [...new Set(declared)].filter((p) => !exists((0, import_node_path20.join)(root, p)));
19116
+ return [...new Set(declared)].filter((p) => !exists((0, import_node_path21.join)(root, p)));
19134
19117
  }
19135
19118
  function evaluate(changed, policy, present = () => false) {
19136
19119
  const { mandatoryHits, untestedHits, testChanges, addedTests, removedProtected } = classify(changed, policy, present);
@@ -19312,13 +19295,13 @@ function changedFilesSince(base, cwd) {
19312
19295
  }
19313
19296
  function runTestPolicy(root, deps = {}) {
19314
19297
  const policy = deps.policy ?? loadPolicy(root);
19315
- const exists = deps.exists ?? ((path2) => (0, import_node_fs22.existsSync)(path2));
19298
+ const exists = deps.exists ?? ((path2) => (0, import_node_fs23.existsSync)(path2));
19316
19299
  const counts = { mandatoryCount: (policy.mandatory ?? []).length, protectedCount: (policy.protected ?? []).length };
19317
19300
  const base = deps.changed ? "(injected)" : resolveBase(root, deps.base);
19318
19301
  const refusal = deps.changed ? null : untrustworthyRange(root, base);
19319
19302
  const changed = deps.changed ?? (refusal ? [] : changedFilesSince(base, root));
19320
19303
  const lookup = deps.override !== void 0 ? { override: deps.override, refusals: [] } : !deps.changed && !refusal ? readOverride(base, root) : { override: null, refusals: [] };
19321
- const present = (path2) => exists((0, import_node_path20.join)(root, path2));
19304
+ const present = (path2) => exists((0, import_node_path21.join)(root, path2));
19322
19305
  const removedByThisDiff = removedPaths(changed);
19323
19306
  const staleFindings = [];
19324
19307
  const unresolved = unresolvedProtectedEntries(policy, root, exists).filter((p) => !removedByThisDiff.has(p));
@@ -19474,8 +19457,8 @@ function docsAuditStatus(fetch2, opts) {
19474
19457
  }
19475
19458
 
19476
19459
  // src/project-info-sync.ts
19477
- var import_node_fs23 = require("node:fs");
19478
- var import_node_path21 = require("node:path");
19460
+ var import_node_fs24 = require("node:fs");
19461
+ var import_node_path22 = require("node:path");
19479
19462
  var UPDATE_PROJECT_INFO = `mutation($projectId: ID!, $shortDescription: String!, $readme: String!) {
19480
19463
  updateProjectV2(input: { projectId: $projectId, shortDescription: $shortDescription, readme: $readme }) {
19481
19464
  projectV2 { id }
@@ -19520,14 +19503,14 @@ function sharedName(entries, fallback) {
19520
19503
  }
19521
19504
  function buildProjectInfoSyncPlan(targetRepo2, project2, projects, repoRoot2) {
19522
19505
  if (!project2.projectId) throw new Error(`org project sync-info: ${targetRepo2} registry META has no projectId`);
19523
- const readmePath = (0, import_node_path21.join)(repoRoot2, "README.md");
19524
- if (!(0, import_node_fs23.existsSync)(readmePath)) throw new Error(`org project sync-info: ${targetRepo2} has no README.md`);
19506
+ const readmePath = (0, import_node_path22.join)(repoRoot2, "README.md");
19507
+ if (!(0, import_node_fs24.existsSync)(readmePath)) throw new Error(`org project sync-info: ${targetRepo2} has no README.md`);
19525
19508
  const entries = entriesFor(project2, projects);
19526
19509
  const memberRepos = [...new Set(entries.flatMap((entry) => entry.repos ?? []))].filter((repo) => /^[^/]+\/[^/]+$/.test(repo)).sort((a, b) => a.localeCompare(b));
19527
19510
  const projectName = sharedName(entries, project2.name?.trim() || targetRepo2.split("/").pop() || targetRepo2);
19528
19511
  if (!memberRepos.length) throw new Error(`org project sync-info: project ${projectName} has no registered member repos`);
19529
19512
  const entryNames = entries.map((entry) => entry.name?.trim()).filter((name) => Boolean(name));
19530
- const shortDescription = memberRepos.length === 1 ? shortDescriptionFromReadme((0, import_node_fs23.readFileSync)(readmePath, "utf8")) : `Shared work across ${new Intl.ListFormat("en", { type: "conjunction" }).format(entryNames)}.`;
19513
+ const shortDescription = memberRepos.length === 1 ? shortDescriptionFromReadme((0, import_node_fs24.readFileSync)(readmePath, "utf8")) : `Shared work across ${new Intl.ListFormat("en", { type: "conjunction" }).format(entryNames)}.`;
19531
19514
  const lines = [
19532
19515
  `# ${projectName}`,
19533
19516
  "",
@@ -19546,8 +19529,8 @@ function buildProjectInfoSyncPlan(targetRepo2, project2, projects, repoRoot2) {
19546
19529
  const targetBase = `https://github.com/${targetRepo2}`;
19547
19530
  const targetBranch = branchFor(targetRepo2, projects);
19548
19531
  const orgDocs = [
19549
- (0, import_node_fs23.existsSync)((0, import_node_path21.join)(repoRoot2, "docs", "org-readme.md")) ? `- [Org identity](${targetBase}/blob/${targetBranch}/docs/org-readme.md)` : "",
19550
- (0, import_node_fs23.existsSync)((0, import_node_path21.join)(repoRoot2, "docs", "org-architecture.md")) ? `- [Org architecture](${targetBase}/blob/${targetBranch}/docs/org-architecture.md)` : ""
19532
+ (0, import_node_fs24.existsSync)((0, import_node_path22.join)(repoRoot2, "docs", "org-readme.md")) ? `- [Org identity](${targetBase}/blob/${targetBranch}/docs/org-readme.md)` : "",
19533
+ (0, import_node_fs24.existsSync)((0, import_node_path22.join)(repoRoot2, "docs", "org-architecture.md")) ? `- [Org architecture](${targetBase}/blob/${targetBranch}/docs/org-architecture.md)` : ""
19551
19534
  ].filter(Boolean);
19552
19535
  if (orgDocs.length) lines.push("", "## Organisation docs", "", ...orgDocs);
19553
19536
  return { projectId: project2.projectId, projectName, targetRepo: targetRepo2, memberRepos, shortDescription, readme: `${lines.join("\n")}
@@ -20406,9 +20389,9 @@ function writeError(res) {
20406
20389
  }
20407
20390
 
20408
20391
  // src/secrets-commands.ts
20409
- var import_node_fs24 = require("node:fs");
20410
- var import_node_path22 = require("node:path");
20411
- var import_node_os8 = require("node:os");
20392
+ var import_node_fs25 = require("node:fs");
20393
+ var import_node_path23 = require("node:path");
20394
+ var import_node_os9 = require("node:os");
20412
20395
 
20413
20396
  // src/project-runtime.ts
20414
20397
  function hasRuntimeSecretContract(contract) {
@@ -20531,18 +20514,18 @@ function collectMap(value, previous = []) {
20531
20514
  return [...previous, value];
20532
20515
  }
20533
20516
  async function decryptRailsCredentials(input) {
20534
- const appDir = (0, import_node_path22.resolve)(input.appDir ?? process.cwd());
20517
+ const appDir = (0, import_node_path23.resolve)(input.appDir ?? process.cwd());
20535
20518
  const credentialsFile = input.credentialsFile ?? DEFAULT_RAILS_CREDENTIALS_FILE;
20536
20519
  const masterKeyFile = input.masterKeyFile ?? DEFAULT_RAILS_MASTER_KEY_FILE;
20537
- const credentialsPath = (0, import_node_path22.resolve)(appDir, credentialsFile);
20538
- const masterKeyPath = (0, import_node_path22.resolve)(appDir, masterKeyFile);
20520
+ const credentialsPath = (0, import_node_path23.resolve)(appDir, credentialsFile);
20521
+ const masterKeyPath = (0, import_node_path23.resolve)(appDir, masterKeyFile);
20539
20522
  const env = {
20540
20523
  ...process.env,
20541
20524
  MMI_RAILS_CREDENTIALS_FILE: credentialsPath,
20542
20525
  MMI_RAILS_MASTER_KEY_FILE: masterKeyPath
20543
20526
  };
20544
- if ((0, import_node_fs24.existsSync)(masterKeyPath)) {
20545
- env.RAILS_MASTER_KEY = (0, import_node_fs24.readFileSync)(masterKeyPath, "utf8").trim();
20527
+ if ((0, import_node_fs25.existsSync)(masterKeyPath)) {
20528
+ env.RAILS_MASTER_KEY = (0, import_node_fs25.readFileSync)(masterKeyPath, "utf8").trim();
20546
20529
  }
20547
20530
  const script = [
20548
20531
  'require "json"',
@@ -20552,9 +20535,9 @@ async function decryptRailsCredentials(input) {
20552
20535
  'config = ActiveSupport::EncryptedConfiguration.new(config_path: config_path, key_path: key_path, env_key: "RAILS_MASTER_KEY", raise_if_missing_key: true)',
20553
20536
  "puts JSON.generate(config.config)"
20554
20537
  ].join("\n");
20555
- const scriptDir = (0, import_node_fs24.mkdtempSync)((0, import_node_path22.join)((0, import_node_os8.tmpdir)(), "mmi-rails-decrypt-"));
20556
- const scriptPath = (0, import_node_path22.join)(scriptDir, "decrypt.rb");
20557
- (0, import_node_fs24.writeFileSync)(scriptPath, script, "utf8");
20538
+ const scriptDir = (0, import_node_fs25.mkdtempSync)((0, import_node_path23.join)((0, import_node_os9.tmpdir)(), "mmi-rails-decrypt-"));
20539
+ const scriptPath = (0, import_node_path23.join)(scriptDir, "decrypt.rb");
20540
+ (0, import_node_fs25.writeFileSync)(scriptPath, script, "utf8");
20558
20541
  try {
20559
20542
  const args = ["exec", "ruby", scriptPath];
20560
20543
  const cmd = process.platform === "win32" ? "cmd.exe" : "bundle";
@@ -20566,7 +20549,7 @@ async function decryptRailsCredentials(input) {
20566
20549
  });
20567
20550
  return JSON.parse(stdout);
20568
20551
  } finally {
20569
- (0, import_node_fs24.rmSync)(scriptDir, { recursive: true, force: true });
20552
+ (0, import_node_fs25.rmSync)(scriptDir, { recursive: true, force: true });
20570
20553
  }
20571
20554
  }
20572
20555
  async function readSecretStdin() {
@@ -20656,7 +20639,7 @@ function registerSecretsCommands(program3) {
20656
20639
  let body;
20657
20640
  if (o.file) {
20658
20641
  try {
20659
- body = (0, import_node_fs24.readFileSync)((0, import_node_path22.resolve)(o.file), "utf8");
20642
+ body = (0, import_node_fs25.readFileSync)((0, import_node_path23.resolve)(o.file), "utf8");
20660
20643
  } catch (e) {
20661
20644
  return fail(`secrets org-catalog: cannot read --file ${o.file}: ${e.message}`);
20662
20645
  }
@@ -20761,7 +20744,7 @@ function registerSecretsCommands(program3) {
20761
20744
  {
20762
20745
  ...d,
20763
20746
  decryptRailsCredentials,
20764
- removeFile: (path2) => (0, import_node_fs24.unlinkSync)((0, import_node_path22.resolve)(o.appDir ?? process.cwd(), path2))
20747
+ removeFile: (path2) => (0, import_node_fs25.unlinkSync)((0, import_node_path23.resolve)(o.appDir ?? process.cwd(), path2))
20765
20748
  },
20766
20749
  {
20767
20750
  repo: o.repo,
@@ -20901,6 +20884,13 @@ async function mintInstallationToken(deps) {
20901
20884
  }
20902
20885
  return { token: body.token, expiresAt: body.expires_at };
20903
20886
  }
20887
+ async function pollTokenOrUndefined(deps) {
20888
+ try {
20889
+ return (await mintInstallationToken(deps)).token;
20890
+ } catch {
20891
+ return void 0;
20892
+ }
20893
+ }
20904
20894
  async function activateAppActor(commandPath3, env, mint) {
20905
20895
  const requested = (env[APP_ACTOR_ENV] ?? "").trim();
20906
20896
  if (!requested) return "personal";
@@ -20916,46 +20906,9 @@ async function activateAppActor(commandPath3, env, mint) {
20916
20906
  env.GH_TOKEN = minted.token;
20917
20907
  return "app";
20918
20908
  }
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
20909
 
20957
20910
  // src/box-commands.ts
20958
- var import_node_fs25 = require("node:fs");
20911
+ var import_node_fs26 = require("node:fs");
20959
20912
 
20960
20913
  // src/box.ts
20961
20914
  var BOX_KEYS = {
@@ -21158,7 +21111,7 @@ function registerBoxCommands(program3) {
21158
21111
  }
21159
21112
  if (o.json) console.log(JSON.stringify({ box: found, incomplete }, null, 2));
21160
21113
  else if (o.ssh && o.script) {
21161
- (0, import_node_fs25.writeFileSync)(o.script, sshRecipeScript(found), "utf8");
21114
+ (0, import_node_fs26.writeFileSync)(o.script, sshRecipeScript(found), "utf8");
21162
21115
  console.log(`wrote ${o.script} \u2014 run: bash "${o.script}"`);
21163
21116
  } else if (o.ssh) console.log(`${formatSshRecipe(found)}
21164
21117
  ${SSH_RECIPE_AGENT_NOTE}`);
@@ -21461,12 +21414,6 @@ function spliceDoc(docText, generatedSection) {
21461
21414
  }
21462
21415
  return docText.slice(0, start) + generatedSection + docText.slice(end + DOC_END_MARKER.length);
21463
21416
  }
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
21417
  var HARBOUR_LLM_LAUNCHERS = /* @__PURE__ */ new Set(["cursor-agent"]);
21471
21418
  function isHarbourLlmLauncher(executor) {
21472
21419
  return HARBOUR_LLM_LAUNCHERS.has(executor);
@@ -21850,7 +21797,7 @@ function registerSchedulesCommands(program3) {
21850
21797
 
21851
21798
  // src/file-lock.ts
21852
21799
  var import_promises4 = require("node:fs/promises");
21853
- var import_node_path23 = require("node:path");
21800
+ var import_node_path24 = require("node:path");
21854
21801
  var sleep = (ms) => new Promise((resolve5) => setTimeout(resolve5, ms));
21855
21802
  var IMMEDIATE_RETRY_BUDGET = 3;
21856
21803
  var FileLockBusyError = class extends Error {
@@ -21935,7 +21882,7 @@ async function releaseFileLock(lockPath, guard) {
21935
21882
  }
21936
21883
  async function withFileLock(lockPath, opts, fn) {
21937
21884
  const resolved = resolveFileLockOpts(opts);
21938
- await (0, import_promises4.mkdir)((0, import_node_path23.dirname)(lockPath), { recursive: true }).catch(() => void 0);
21885
+ await (0, import_promises4.mkdir)((0, import_node_path24.dirname)(lockPath), { recursive: true }).catch(() => void 0);
21939
21886
  const guard = await acquireFileLock(lockPath, resolved, Date.now() + resolved.maxWaitMs);
21940
21887
  try {
21941
21888
  return await fn();
@@ -21946,7 +21893,7 @@ async function withFileLock(lockPath, opts, fn) {
21946
21893
 
21947
21894
  // src/schedules-lift-command.ts
21948
21895
  var import_promises5 = require("node:fs/promises");
21949
- var import_node_path24 = require("node:path");
21896
+ var import_node_path25 = require("node:path");
21950
21897
 
21951
21898
  // src/schedules-lift.ts
21952
21899
  var SCHEDULE_HEADER_FIELDS = ["schedule", "what", "owner", "cadence", "executor", "llm", "output", "breaks", "kill"];
@@ -22052,7 +21999,7 @@ async function readWorkflowFiles(dir) {
22052
21999
  const files = [];
22053
22000
  for (const name of names.sort()) {
22054
22001
  if (!/\.ya?ml$/.test(name)) continue;
22055
- files.push({ path: `.github/workflows/${name}`, text: await (0, import_promises5.readFile)((0, import_node_path24.join)(dir, name), "utf8") });
22002
+ files.push({ path: `.github/workflows/${name}`, text: await (0, import_promises5.readFile)((0, import_node_path25.join)(dir, name), "utf8") });
22056
22003
  }
22057
22004
  return files;
22058
22005
  }
@@ -22136,13 +22083,13 @@ function registerSchedulesLiftCommand(program3, deps = {}) {
22136
22083
  // src/edge-tunnel.ts
22137
22084
  var HOSTNAME_RE = /^(?=.{1,253}$)([a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)(\.[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)+$/;
22138
22085
  var UPSTREAM_RE = /^https?:\/\/[^/\s]+(?::\d+)?(?:\/.*)?$/;
22139
- function tunnelNameFromHostname(hostname2) {
22140
- return hostname2.replace(/\./g, "-").slice(0, 63);
22086
+ function tunnelNameFromHostname(hostname3) {
22087
+ return hostname3.replace(/\./g, "-").slice(0, 63);
22141
22088
  }
22142
- function planInfraTunnel(hostname2, upstream) {
22143
- const host = hostname2.trim().toLowerCase();
22089
+ function planInfraTunnel(hostname3, upstream) {
22090
+ const host = hostname3.trim().toLowerCase();
22144
22091
  const origin = upstream.trim();
22145
- if (!HOSTNAME_RE.test(host)) throw new Error(`invalid hostname ${JSON.stringify(hostname2)}`);
22092
+ if (!HOSTNAME_RE.test(host)) throw new Error(`invalid hostname ${JSON.stringify(hostname3)}`);
22146
22093
  if (!UPSTREAM_RE.test(origin)) throw new Error(`invalid upstream ${JSON.stringify(upstream)} \u2014 expected http(s)://host:port`);
22147
22094
  const tunnelName = tunnelNameFromHostname(host);
22148
22095
  const configYaml = [
@@ -22594,9 +22541,9 @@ function registerQueryCommands(program3) {
22594
22541
  }
22595
22542
 
22596
22543
  // src/bootstrap-commands.ts
22597
- var import_node_fs26 = require("node:fs");
22598
- var import_node_os9 = require("node:os");
22599
- var import_node_path25 = require("node:path");
22544
+ var import_node_fs27 = require("node:fs");
22545
+ var import_node_os10 = require("node:os");
22546
+ var import_node_path26 = require("node:path");
22600
22547
 
22601
22548
  // src/bootstrap-drift.ts
22602
22549
  function byteComparableSeeds(manifest, cls) {
@@ -23210,13 +23157,13 @@ function registerBootstrapCommands(program3) {
23210
23157
  client: defaultGitHubClient(),
23211
23158
  projectMeta: meta,
23212
23159
  deployModel: typeof meta?.deployModel === "string" ? meta.deployModel : void 0,
23213
- readLocalFile: (path2) => path2 === "projects.json" && apiProjects != null ? apiProjects : (0, import_node_fs26.existsSync)(path2) ? (0, import_node_fs26.readFileSync)(path2, "utf8") : null,
23160
+ readLocalFile: (path2) => path2 === "projects.json" && apiProjects != null ? apiProjects : (0, import_node_fs27.existsSync)(path2) ? (0, import_node_fs27.readFileSync)(path2, "utf8") : null,
23214
23161
  // requiredGcpApis is stored as an array by a JSON write, but `org project set --var KEY=VALUE` stores a raw
23215
23162
  // comma-string — accept either so the seeded value verifies regardless of how it was written.
23216
23163
  // #3689: the same committed map the org access audit reads (#3664), so a sanctioned admin is not a
23217
23164
  // permanent bootstrap failure on one surface and an intended state on the other. Absent file → no
23218
23165
  // sanction, which is the pre-#3664 behaviour.
23219
- sanctionedAdmins: (0, import_node_fs26.existsSync)("access-matrix.json") ? entriesValueByCanonicalRepo(loadSanctionedAdmins((0, import_node_fs26.readFileSync)("access-matrix.json", "utf8")), repo) : void 0,
23166
+ sanctionedAdmins: (0, import_node_fs27.existsSync)("access-matrix.json") ? entriesValueByCanonicalRepo(loadSanctionedAdmins((0, import_node_fs27.readFileSync)("access-matrix.json", "utf8")), repo) : void 0,
23220
23167
  requiredGcpApis: (() => {
23221
23168
  const v = meta?.requiredGcpApis;
23222
23169
  if (Array.isArray(v)) return v;
@@ -23254,12 +23201,12 @@ function registerBootstrapCommands(program3) {
23254
23201
  bootstrap.command("drift").description("#3818: compare every org-owned whole-file seed against MMI-Hub's copy across the registry roster; read-only").option("--repo <owner/repo>", "audit one repo instead of the roster (never a fleet verdict)").option("--json", "machine-readable output").action(async () => {
23255
23202
  const o = { repo: rawValue("--repo", ""), json: rawFlag("--json") };
23256
23203
  const manifestPath = "skills/bootstrap/seeds/manifest.json";
23257
- if (!(0, import_node_fs26.existsSync)(manifestPath)) return fail(`bootstrap drift: ${manifestPath} not found; run from the MMI-Hub repo root \u2014 the Hub's copies ARE the reference this compares against`);
23258
- const manifest = loadBootstrapSeeds((0, import_node_fs26.readFileSync)(manifestPath, "utf8"));
23204
+ if (!(0, import_node_fs27.existsSync)(manifestPath)) return fail(`bootstrap drift: ${manifestPath} not found; run from the MMI-Hub repo root \u2014 the Hub's copies ARE the reference this compares against`);
23205
+ const manifest = loadBootstrapSeeds((0, import_node_fs27.readFileSync)(manifestPath, "utf8"));
23259
23206
  const hubContents = /* @__PURE__ */ new Map();
23260
23207
  for (const s of manifest.seeds) {
23261
23208
  if (s.ownership !== "org" || s.source !== "self") continue;
23262
- hubContents.set(s.target, (0, import_node_fs26.existsSync)(s.target) ? (0, import_node_fs26.readFileSync)(s.target, "utf8") : null);
23209
+ hubContents.set(s.target, (0, import_node_fs27.existsSync)(s.target) ? (0, import_node_fs27.readFileSync)(s.target, "utf8") : null);
23263
23210
  }
23264
23211
  let targets;
23265
23212
  let classOf = (_repo) => "deployable";
@@ -23336,8 +23283,8 @@ function registerBootstrapCommands(program3) {
23336
23283
  return fail(`bootstrap apply: ${e.message}`);
23337
23284
  }
23338
23285
  const manifestPath = "skills/bootstrap/seeds/manifest.json";
23339
- if (!(0, import_node_fs26.existsSync)(manifestPath)) return fail(`bootstrap apply: ${manifestPath} not found; bootstrap runs from the MMI-Hub repo root by design \u2014 it stamps org-level resources (Project, Ruleset, secrets, access) through the GitHub App, which is only authorized from the Hub checkout`);
23340
- const manifest = loadBootstrapSeeds((0, import_node_fs26.readFileSync)(manifestPath, "utf8"));
23286
+ if (!(0, import_node_fs27.existsSync)(manifestPath)) return fail(`bootstrap apply: ${manifestPath} not found; bootstrap runs from the MMI-Hub repo root by design \u2014 it stamps org-level resources (Project, Ruleset, secrets, access) through the GitHub App, which is only authorized from the Hub checkout`);
23287
+ const manifest = loadBootstrapSeeds((0, import_node_fs27.readFileSync)(manifestPath, "utf8"));
23341
23288
  const baseBranch = o.class === "content" ? "main" : "development";
23342
23289
  const slug = parsedRepo.slug;
23343
23290
  const onlyTarget = o.only.trim();
@@ -23348,16 +23295,16 @@ function registerBootstrapCommands(program3) {
23348
23295
  ${known}`);
23349
23296
  }
23350
23297
  const gh = async (args) => execFileP2("gh", args, { timeout: 2e4 });
23351
- const readFile9 = (p) => (0, import_node_fs26.existsSync)(p) ? (0, import_node_fs26.readFileSync)(p, "utf8") : null;
23298
+ const readFile9 = (p) => (0, import_node_fs27.existsSync)(p) ? (0, import_node_fs27.readFileSync)(p, "utf8") : null;
23352
23299
  const enc = (p) => p.split("/").map(encodeURIComponent).join("/");
23353
23300
  const putSeed = async (target, content, ref, sha) => {
23354
- const tmp = (0, import_node_path25.join)((0, import_node_os9.tmpdir)(), `mmi-seed-${process.pid}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}.json`);
23355
- (0, import_node_fs26.writeFileSync)(tmp, JSON.stringify(contentPutBody(target, content, ref, sha)), "utf8");
23301
+ const tmp = (0, import_node_path26.join)((0, import_node_os10.tmpdir)(), `mmi-seed-${process.pid}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}.json`);
23302
+ (0, import_node_fs27.writeFileSync)(tmp, JSON.stringify(contentPutBody(target, content, ref, sha)), "utf8");
23356
23303
  try {
23357
23304
  await gh(contentPutInputArgs(repo, target, tmp));
23358
23305
  } finally {
23359
23306
  try {
23360
- (0, import_node_fs26.unlinkSync)(tmp);
23307
+ (0, import_node_fs27.unlinkSync)(tmp);
23361
23308
  } catch {
23362
23309
  }
23363
23310
  }
@@ -23609,12 +23556,12 @@ LIVE apply to ${repo}:
23609
23556
  }
23610
23557
 
23611
23558
  // src/stage-commands.ts
23612
- var import_node_fs28 = require("node:fs");
23613
- var import_node_path27 = require("node:path");
23559
+ var import_node_fs29 = require("node:fs");
23560
+ var import_node_path28 = require("node:path");
23614
23561
 
23615
23562
  // src/port-registry.ts
23616
- var import_node_fs27 = require("node:fs");
23617
- var import_node_path26 = require("node:path");
23563
+ var import_node_fs28 = require("node:fs");
23564
+ var import_node_path27 = require("node:path");
23618
23565
 
23619
23566
  // ../infra/port-geometry.mjs
23620
23567
  var PORT_BLOCK = 100;
@@ -23628,8 +23575,8 @@ function nextPortBlock(registry2) {
23628
23575
  return [base, base + PORT_SPAN];
23629
23576
  }
23630
23577
  function loadPortRegistry(path2) {
23631
- if (!(0, import_node_fs27.existsSync)(path2)) return {};
23632
- const raw = JSON.parse((0, import_node_fs27.readFileSync)(path2, "utf8"));
23578
+ if (!(0, import_node_fs28.existsSync)(path2)) return {};
23579
+ const raw = JSON.parse((0, import_node_fs28.readFileSync)(path2, "utf8"));
23633
23580
  const out = {};
23634
23581
  for (const [key, value] of Object.entries(raw)) {
23635
23582
  if (Array.isArray(value) && value.length === 2 && value.every((n) => typeof n === "number")) {
@@ -23643,9 +23590,9 @@ function ensurePortRange(repo, path2) {
23643
23590
  const existing = registry2[repo];
23644
23591
  if (existing) return existing;
23645
23592
  const range = nextPortBlock(registry2);
23646
- const raw = (0, import_node_fs27.existsSync)(path2) ? JSON.parse((0, import_node_fs27.readFileSync)(path2, "utf8")) : {};
23593
+ const raw = (0, import_node_fs28.existsSync)(path2) ? JSON.parse((0, import_node_fs28.readFileSync)(path2, "utf8")) : {};
23647
23594
  raw[repo] = range;
23648
- (0, import_node_fs27.writeFileSync)(path2, JSON.stringify(raw, null, 2) + "\n", "utf8");
23595
+ (0, import_node_fs28.writeFileSync)(path2, JSON.stringify(raw, null, 2) + "\n", "utf8");
23649
23596
  return range;
23650
23597
  }
23651
23598
  function portCursorSeed(registry2) {
@@ -23667,22 +23614,22 @@ function existingPortRange(repo, registry2) {
23667
23614
  return registry2[repo] ?? null;
23668
23615
  }
23669
23616
  function portRangeInfraAt(root, source) {
23670
- const registryPath = (0, import_node_path26.join)(root, "infra", "port-ranges.json");
23671
- const ddbScriptPath = (0, import_node_path26.join)(root, "infra", "port-ddb.mjs");
23672
- if (!(0, import_node_fs27.existsSync)(registryPath) || !(0, import_node_fs27.existsSync)(ddbScriptPath)) return null;
23617
+ const registryPath = (0, import_node_path27.join)(root, "infra", "port-ranges.json");
23618
+ const ddbScriptPath = (0, import_node_path27.join)(root, "infra", "port-ddb.mjs");
23619
+ if (!(0, import_node_fs28.existsSync)(registryPath) || !(0, import_node_fs28.existsSync)(ddbScriptPath)) return null;
23673
23620
  return { root, source, registryPath, ddbScriptPath };
23674
23621
  }
23675
23622
  function resolvePortRangeInfra(cwd, packageDir) {
23676
23623
  const direct = portRangeInfraAt(cwd, "cwd");
23677
23624
  if (direct) return direct;
23678
- for (let dir = cwd; ; dir = (0, import_node_path26.dirname)(dir)) {
23679
- const sibling = portRangeInfraAt((0, import_node_path26.join)(dir, "MMI-Hub"), "sibling-hub");
23625
+ for (let dir = cwd; ; dir = (0, import_node_path27.dirname)(dir)) {
23626
+ const sibling = portRangeInfraAt((0, import_node_path27.join)(dir, "MMI-Hub"), "sibling-hub");
23680
23627
  if (sibling) return sibling;
23681
- const parent = (0, import_node_path26.dirname)(dir);
23628
+ const parent = (0, import_node_path27.dirname)(dir);
23682
23629
  if (parent === dir) break;
23683
23630
  }
23684
23631
  if (packageDir) {
23685
- const pkgRoot = (0, import_node_path26.join)(packageDir, "..", "..");
23632
+ const pkgRoot = (0, import_node_path27.join)(packageDir, "..", "..");
23686
23633
  const pkgFrom = portRangeInfraAt(pkgRoot, "pkg-root");
23687
23634
  if (pkgFrom) return pkgFrom;
23688
23635
  }
@@ -23858,8 +23805,8 @@ function registerStageCommands(program3) {
23858
23805
  const portRange = portRangeMeta && typeof portRangeMeta.start === "number" && typeof portRangeMeta.end === "number" ? [portRangeMeta.start, portRangeMeta.end] : void 0;
23859
23806
  return decideStage({
23860
23807
  registry: { deployModel: project2?.deployModel, portRange, error: read.ok ? void 0 : read.error },
23861
- hasCompose: (0, import_node_fs28.existsSync)((0, import_node_path27.join)(process.cwd(), "docker-compose.yml")),
23862
- hasEnvExample: (0, import_node_fs28.existsSync)((0, import_node_path27.join)(process.cwd(), ".env.example"))
23808
+ hasCompose: (0, import_node_fs29.existsSync)((0, import_node_path28.join)(process.cwd(), "docker-compose.yml")),
23809
+ hasEnvExample: (0, import_node_fs29.existsSync)((0, import_node_path28.join)(process.cwd(), ".env.example"))
23863
23810
  });
23864
23811
  }
23865
23812
  async function fetchStageVaultEnvMerge() {
@@ -24297,10 +24244,10 @@ function registerBoardCommands(program3) {
24297
24244
  }
24298
24245
 
24299
24246
  // src/merge-cleanup.ts
24300
- var import_node_fs29 = require("node:fs");
24247
+ var import_node_fs30 = require("node:fs");
24301
24248
  var import_promises7 = require("node:fs/promises");
24302
- var import_node_path29 = require("node:path");
24303
- var import_node_os10 = require("node:os");
24249
+ var import_node_path30 = require("node:path");
24250
+ var import_node_os11 = require("node:os");
24304
24251
  var import_node_child_process14 = require("node:child_process");
24305
24252
 
24306
24253
  // src/board-advance.ts
@@ -24387,7 +24334,7 @@ function boardAdvanceFailureMessage(result) {
24387
24334
 
24388
24335
  // src/deferred-registry-store.ts
24389
24336
  var import_promises6 = require("node:fs/promises");
24390
- var import_node_path28 = require("node:path");
24337
+ var import_node_path29 = require("node:path");
24391
24338
  var sleep2 = (ms) => new Promise((resolve5) => setTimeout(resolve5, ms));
24392
24339
  async function atomicWrite(target, contents) {
24393
24340
  const tmp = `${target}.tmp-${process.pid}-${Math.random().toString(36).slice(2)}`;
@@ -24438,12 +24385,12 @@ function makeDeferredWorktreeStore(registryPath, lockOpts = {}) {
24438
24385
  },
24439
24386
  // Standalone atomic write — THROWS on failure (no best-effort swallow, #2846).
24440
24387
  write: async (entries) => {
24441
- await (0, import_promises6.mkdir)((0, import_node_path28.dirname)(registryPath), { recursive: true });
24388
+ await (0, import_promises6.mkdir)((0, import_node_path29.dirname)(registryPath), { recursive: true });
24442
24389
  await atomicWrite(registryPath, serializeDeferredWorktrees(entries));
24443
24390
  },
24444
24391
  // Serialized read-modify-write under the repo-wide lock (#2846).
24445
24392
  update: async (mutate) => {
24446
- await (0, import_promises6.mkdir)((0, import_node_path28.dirname)(registryPath), { recursive: true });
24393
+ await (0, import_promises6.mkdir)((0, import_node_path29.dirname)(registryPath), { recursive: true });
24447
24394
  const deadline = Date.now() + opts.maxWaitMs;
24448
24395
  for (; ; ) {
24449
24396
  const guard = await acquireLock(lockPath, opts, deadline);
@@ -24604,7 +24551,7 @@ async function applyGcPlan(plan, remote, opts = {}) {
24604
24551
  );
24605
24552
  const repoRoot2 = (await execFileP2("git", ["rev-parse", "--show-toplevel"], { timeout: GIT_TIMEOUT_MS }).catch(() => ({ stdout: "" }))).stdout.trim() || process.cwd();
24606
24553
  const worktreeGitRoot = await currentRepoWorktreeGitRoot(repoRoot2);
24607
- const primaryRepoRoot = worktreeGitRoot ? (0, import_node_path29.dirname)((0, import_node_path29.dirname)(worktreeGitRoot)) : repoRoot2;
24554
+ const primaryRepoRoot = worktreeGitRoot ? (0, import_node_path30.dirname)((0, import_node_path30.dirname)(worktreeGitRoot)) : repoRoot2;
24608
24555
  const gcActor = describeActor({ env: process.env, surface: detectSurface(process.env), cwd: process.cwd() });
24609
24556
  const owners = readWorktreeOwners(primaryRepoRoot);
24610
24557
  const removalNow = Date.now();
@@ -24635,7 +24582,7 @@ async function applyGcPlan(plan, remote, opts = {}) {
24635
24582
  const cleanup = await cleanupPrMergeLocalBranch(branch.branch, {
24636
24583
  beforeWorktrees,
24637
24584
  startingPath: branch.worktreePath,
24638
- pathExists: (p) => (0, import_node_fs29.existsSync)(p),
24585
+ pathExists: (p) => (0, import_node_fs30.existsSync)(p),
24639
24586
  execGit: async (args) => (await execFileP2("git", args, { timeout: GIT_TIMEOUT_MS })).stdout,
24640
24587
  teardownWorktreeStage,
24641
24588
  deferredStore,
@@ -24663,7 +24610,7 @@ async function applyGcPlan(plan, remote, opts = {}) {
24663
24610
  for (const wt of worktreeDirsToRemove) {
24664
24611
  try {
24665
24612
  const cleanupTarget = resolveSafeSiblingWorktreeCleanupTarget(wt.path, siblingRoot, {
24666
- realpath: (path2) => (0, import_node_fs29.realpathSync)(path2)
24613
+ realpath: (path2) => (0, import_node_fs30.realpathSync)(path2)
24667
24614
  });
24668
24615
  if (!cleanupTarget.ok) {
24669
24616
  result.failed.push(`${wt.path}: ${cleanupTarget.reason}`);
@@ -24728,13 +24675,13 @@ async function composeOverrideBodyFile(prNumber, repoArgs, gh) {
24728
24675
  const commits = JSON.parse(raw).commits ?? [];
24729
24676
  const body = squashBodyWithOverride(commits.map((c) => ({ headline: c.messageHeadline ?? "", body: c.messageBody ?? "" })), process.cwd());
24730
24677
  if (!body) return void 0;
24731
- const dir = (0, import_node_fs29.mkdtempSync)((0, import_node_path29.join)((0, import_node_os10.tmpdir)(), "mmi-squash-body-"));
24732
- const path2 = (0, import_node_path29.join)(dir, "body.txt");
24733
- (0, import_node_fs29.writeFileSync)(path2, `${body}
24678
+ const dir = (0, import_node_fs30.mkdtempSync)((0, import_node_path30.join)((0, import_node_os11.tmpdir)(), "mmi-squash-body-"));
24679
+ const path2 = (0, import_node_path30.join)(dir, "body.txt");
24680
+ (0, import_node_fs30.writeFileSync)(path2, `${body}
24734
24681
  `, "utf8");
24735
24682
  return { path: path2, cleanup: () => {
24736
24683
  try {
24737
- (0, import_node_fs29.rmSync)(dir, { recursive: true, force: true });
24684
+ (0, import_node_fs30.rmSync)(dir, { recursive: true, force: true });
24738
24685
  } catch {
24739
24686
  }
24740
24687
  } };
@@ -24856,13 +24803,13 @@ var realWorktreeDirRemover = {
24856
24803
  probe: (p) => {
24857
24804
  let st;
24858
24805
  try {
24859
- st = (0, import_node_fs29.lstatSync)(p);
24806
+ st = (0, import_node_fs30.lstatSync)(p);
24860
24807
  } catch {
24861
24808
  return null;
24862
24809
  }
24863
24810
  if (st.isSymbolicLink()) return "link";
24864
24811
  try {
24865
- (0, import_node_fs29.readlinkSync)(p);
24812
+ (0, import_node_fs30.readlinkSync)(p);
24866
24813
  return "link";
24867
24814
  } catch {
24868
24815
  }
@@ -24870,7 +24817,7 @@ var realWorktreeDirRemover = {
24870
24817
  },
24871
24818
  readdir: (p) => {
24872
24819
  try {
24873
- return (0, import_node_fs29.readdirSync)(p);
24820
+ return (0, import_node_fs30.readdirSync)(p);
24874
24821
  } catch {
24875
24822
  return [];
24876
24823
  }
@@ -24879,9 +24826,9 @@ var realWorktreeDirRemover = {
24879
24826
  // leaving the target); a file symlink with unlink. rmdir first, fall back to unlink.
24880
24827
  detachLink: (p) => {
24881
24828
  try {
24882
- (0, import_node_fs29.rmdirSync)(p);
24829
+ (0, import_node_fs30.rmdirSync)(p);
24883
24830
  } catch {
24884
- (0, import_node_fs29.unlinkSync)(p);
24831
+ (0, import_node_fs30.unlinkSync)(p);
24885
24832
  }
24886
24833
  },
24887
24834
  removeTree: (p) => (0, import_promises7.rm)(p, { recursive: true, force: true, maxRetries: 5, retryDelay: 200 })
@@ -24914,9 +24861,9 @@ async function worktreeHasStageState(worktreePath) {
24914
24861
  }
24915
24862
  }
24916
24863
  function stageStateFileBelongsToWorktree(statePath, worktreePath) {
24917
- if (!(0, import_node_fs29.existsSync)(statePath)) return false;
24864
+ if (!(0, import_node_fs30.existsSync)(statePath)) return false;
24918
24865
  try {
24919
- const state = JSON.parse((0, import_node_fs29.readFileSync)(statePath, "utf8"));
24866
+ const state = JSON.parse((0, import_node_fs30.readFileSync)(statePath, "utf8"));
24920
24867
  const recordedCwd = typeof state.identity?.cwd === "string" ? state.identity.cwd : typeof state.cwd === "string" ? state.cwd : "";
24921
24868
  return Boolean(recordedCwd && isPathUnderDirectory(recordedCwd, worktreePath));
24922
24869
  } catch {
@@ -24939,8 +24886,25 @@ function teardownWorktreeStage(worktreePath) {
24939
24886
 
24940
24887
  // src/pr-checks-rest.ts
24941
24888
  var REST_GH_TIMEOUT_MS = 2e4;
24889
+ var pollTokenProvider;
24890
+ var pollTokenOnce;
24891
+ function setPollTokenProvider(provider) {
24892
+ pollTokenProvider = provider;
24893
+ pollTokenOnce = void 0;
24894
+ }
24895
+ async function pollToken() {
24896
+ if (!pollTokenProvider) return void 0;
24897
+ pollTokenOnce ??= pollTokenProvider().catch(() => void 0);
24898
+ return pollTokenOnce;
24899
+ }
24942
24900
  async function defaultGhApi(args) {
24943
- const { stdout } = await execFileP2("gh", ["api", ...args], { timeout: REST_GH_TIMEOUT_MS });
24901
+ const token = await pollToken();
24902
+ const { stdout } = await execFileP2("gh", ["api", ...args], {
24903
+ timeout: REST_GH_TIMEOUT_MS,
24904
+ // Only when a token was actually minted: passing `env` at all would otherwise replace the
24905
+ // inherited environment wholesale, and `gh` needs the rest of it (PATH, GH_HOST, config home).
24906
+ ...token ? { env: { ...process.env, GH_TOKEN: token } } : {}
24907
+ });
24944
24908
  return stdout;
24945
24909
  }
24946
24910
  function classifyCheckRun(run) {
@@ -25123,9 +25087,9 @@ async function fetchRestCorePool(gh = defaultGhApi) {
25123
25087
  }
25124
25088
 
25125
25089
  // src/worktree-lifecycle-commands.ts
25126
- var import_node_fs30 = require("node:fs");
25090
+ var import_node_fs31 = require("node:fs");
25127
25091
  var import_promises8 = require("node:fs/promises");
25128
- var import_node_path30 = require("node:path");
25092
+ var import_node_path31 = require("node:path");
25129
25093
  var GH_TIMEOUT_MS = 2e4;
25130
25094
  var DEFAULT_BASE = "origin/development";
25131
25095
  var DEFAULT_REMOTE = "origin";
@@ -25197,6 +25161,16 @@ function orphanLandSteps(lostBranch, hasStage) {
25197
25161
  steps.push("prune worktree metadata");
25198
25162
  return steps;
25199
25163
  }
25164
+ function detachedLandSteps(hasStage) {
25165
+ const steps = [];
25166
+ if (hasStage) steps.push("stop spawned dev stage");
25167
+ steps.push("remove worktree (detached HEAD)");
25168
+ steps.push("prune worktree metadata");
25169
+ return steps;
25170
+ }
25171
+ function shouldContinueLandCleanup(removeStatus) {
25172
+ return removeStatus === "removed";
25173
+ }
25200
25174
  function classifyStaleLeaks(input) {
25201
25175
  const protectedBranches = input.protectedBranches ?? PROTECTED_BRANCHES2;
25202
25176
  const leaks = [];
@@ -25261,7 +25235,7 @@ function classifyStaleLeaks(input) {
25261
25235
  var defaultOrphanDirScanDeps = {
25262
25236
  listDirs: (root) => {
25263
25237
  try {
25264
- return (0, import_node_fs30.readdirSync)(root, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => (0, import_node_path30.join)(root, e.name));
25238
+ return (0, import_node_fs31.readdirSync)(root, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => (0, import_node_path31.join)(root, e.name));
25265
25239
  } catch {
25266
25240
  return [];
25267
25241
  }
@@ -25406,15 +25380,21 @@ function registerWorktreeCommands(program3) {
25406
25380
  try {
25407
25381
  const wtPath = (await execFileP2("git", ["rev-parse", "--show-toplevel"], { timeout: GIT_TIMEOUT_MS })).stdout.trim();
25408
25382
  const headBorn = await execFileP2("git", ["-C", wtPath || ".", "rev-parse", "--verify", "--quiet", "HEAD"], { timeout: GIT_TIMEOUT_MS }).then(() => true).catch(() => false);
25409
- const branch = headBorn ? (await execFileP2("git", ["rev-parse", "--abbrev-ref", "HEAD"], { timeout: GIT_TIMEOUT_MS })).stdout.trim() : (await execFileP2("git", ["symbolic-ref", "--quiet", "--short", "HEAD"], { timeout: GIT_TIMEOUT_MS }).catch(() => ({ stdout: "" }))).stdout.trim();
25383
+ const symbolicBranch = (await execFileP2(
25384
+ "git",
25385
+ ["-C", wtPath || ".", "symbolic-ref", "--quiet", "--short", "HEAD"],
25386
+ { timeout: GIT_TIMEOUT_MS }
25387
+ ).catch(() => ({ stdout: "" }))).stdout.trim();
25388
+ const detached = headBorn && !symbolicBranch;
25389
+ const branch = symbolicBranch || (detached ? "HEAD" : "");
25410
25390
  if (!wtPath || !branch) return fail("worktree land: not inside a git worktree");
25411
- const gitFile = (0, import_node_path30.join)(wtPath, ".git");
25412
- const isLinked = (0, import_node_fs30.existsSync)(gitFile) && (0, import_node_fs30.statSync)(gitFile).isFile();
25391
+ const gitFile = (0, import_node_path31.join)(wtPath, ".git");
25392
+ const isLinked = (0, import_node_fs31.existsSync)(gitFile) && (0, import_node_fs31.statSync)(gitFile).isFile();
25413
25393
  if (apply && !isLinked) {
25414
25394
  return fail("worktree land: run from inside the linked worktree you want to land (this is the primary checkout)");
25415
25395
  }
25416
25396
  const commonDir = (await execFileP2("git", ["rev-parse", "--git-common-dir"], { timeout: GIT_TIMEOUT_MS }).catch(() => ({ stdout: "" }))).stdout.trim();
25417
- const primaryCheckout = commonDir ? (0, import_node_path30.dirname)(commonDir) : wtPath;
25397
+ const primaryCheckout = commonDir ? (0, import_node_path31.dirname)(commonDir) : wtPath;
25418
25398
  const localBranchNames = await execFileP2("git", ["-C", primaryCheckout, "for-each-ref", "--format=%(refname:short)", "refs/heads"], { timeout: GIT_TIMEOUT_MS }).then(({ stdout }) => new Set((stdout || "").split("\n").map((l) => l.trim()).filter(Boolean))).catch(() => void 0);
25419
25399
  const orphan = classifyOrphanedWorktree({
25420
25400
  branch,
@@ -25427,7 +25407,7 @@ function registerWorktreeCommands(program3) {
25427
25407
  }
25428
25408
  const stageSummary = readStageSummary(wtPath);
25429
25409
  const hasStage = stageSummary != null;
25430
- const steps = orphan.orphan ? orphanLandSteps(orphan.lostBranch, hasStage) : landSteps(branch, true, hasStage);
25410
+ const steps = detached ? detachedLandSteps(hasStage) : orphan.orphan ? orphanLandSteps(orphan.lostBranch, hasStage) : landSteps(branch, true, hasStage);
25431
25411
  const plan = {
25432
25412
  command: "worktree land",
25433
25413
  branch,
@@ -25436,13 +25416,15 @@ function registerWorktreeCommands(program3) {
25436
25416
  remote: o.remote,
25437
25417
  hasStage,
25438
25418
  steps,
25439
- ...orphan.orphan ? { orphan: { kind: orphan.kind, lostBranch: orphan.lostBranch, reason: orphan.reason } } : {}
25419
+ ...orphan.orphan ? { orphan: { kind: orphan.kind, lostBranch: orphan.lostBranch, reason: orphan.reason } } : {},
25420
+ ...detached ? { detached: true } : {}
25440
25421
  };
25441
25422
  if (!apply) {
25442
25423
  const preview = { dryRun: true, ...plan, ...o.keepRemote ? { keepRemote: true } : {} };
25443
25424
  if (o.json) return console.log(JSON.stringify(preview, null, 2));
25444
25425
  const lines = [`worktree land: dry-run (pass --apply to execute)`, ` branch: ${branch}`, ` worktree: ${wtPath}`];
25445
- if (orphan.orphan) lines.push(` orphaned: ${orphan.reason} \u2014 no branch refs will be deleted`);
25426
+ if (detached) lines.push(" detached HEAD: no branch refs will be deleted");
25427
+ else if (orphan.orphan) lines.push(` orphaned: ${orphan.reason} \u2014 no branch refs will be deleted`);
25446
25428
  if (hasStage) lines.push(` stage: port ${stageSummary.port} (will be stopped)`);
25447
25429
  if (o.keepRemote && !orphan.orphan) lines.push(` remote branch: kept (--keep-remote)`);
25448
25430
  for (const s of steps) lines.push(` - ${s}`);
@@ -25464,15 +25446,16 @@ function registerWorktreeCommands(program3) {
25464
25446
  unreferenced: await isCommitUnreferenced(orphanTip, async (args) => (await execFileP2("git", ["-C", primaryCheckout, ...args], { timeout: GIT_TIMEOUT_MS })).stdout),
25465
25447
  recoverCommand: `git -C "${primaryCheckout}" branch <name> ${orphanTip}`
25466
25448
  } : void 0;
25467
- const landPrs = orphan.orphan ? void 0 : await fetchLandBranchPrs(branch);
25449
+ const treeOnly = orphan.orphan || detached;
25450
+ const landPrs = treeOnly ? void 0 : await fetchLandBranchPrs(branch);
25468
25451
  const mergeVerdict = classifyLandBranchMergeState(landPrs);
25469
- const landRefs = orphan.orphan ? { action: "proceed" } : decideWorktreeLandRefCleanup({ branch, prs: landPrs, keepRemote: Boolean(o.keepRemote) });
25452
+ const landRefs = treeOnly ? { action: "proceed" } : decideWorktreeLandRefCleanup({ branch, prs: landPrs, keepRemote: Boolean(o.keepRemote) });
25470
25453
  if (landRefs.action === "refuse") {
25471
25454
  return fail(`worktree land: ${landRefs.message}`, { code: ERROR_CODES.ERR_BAD_ENUM });
25472
25455
  }
25473
25456
  const keepRemoteBranch = Boolean(o.keepRemote) || landRefs.action === "keep-remote";
25474
25457
  if (landRefs.action === "keep-remote") console.warn(`worktree land: ${landRefs.message}.`);
25475
- const reportedMergeState = orphan.orphan ? "not-checked" : mergeVerdict.state;
25458
+ const reportedMergeState = treeOnly ? "not-checked" : mergeVerdict.state;
25476
25459
  const landOwner = lookupWorktreeOwner(primaryCheckout, wtPath);
25477
25460
  const report = [];
25478
25461
  if (hasStage) {
@@ -25492,6 +25475,24 @@ function registerWorktreeCommands(program3) {
25492
25475
  step: "remove worktree",
25493
25476
  status: removeOutcome.status === "removed" ? removeOutcome.recovery ? `done (${removeOutcome.recovery})` : "done" : `failed: ${removeOutcome.error ?? "lock held"} \u2014 run: git -C "${primaryCheckout}" worktree remove --force "${wtPath}"`
25494
25477
  });
25478
+ if (!shouldContinueLandCleanup(removeOutcome.status)) {
25479
+ const result2 = {
25480
+ dryRun: false,
25481
+ ...plan,
25482
+ ...o.keepRemote ? { keepRemote: true } : {},
25483
+ mergeState: reportedMergeState,
25484
+ prNumbers: mergeVerdict.numbers,
25485
+ cleanupState: "partial",
25486
+ report
25487
+ };
25488
+ if (o.json) console.log(JSON.stringify(result2, null, 2));
25489
+ else {
25490
+ console.error(`worktree land: cleanup stopped after the worktree removal failed; branch refs and metadata were left untouched`);
25491
+ for (const row of report) console.log(` ${row.step}: ${row.status}`);
25492
+ }
25493
+ process.exitCode = 1;
25494
+ return;
25495
+ }
25495
25496
  const landActor = describeActor({ env: process.env, surface: detectSurface(process.env), cwd: wtPath });
25496
25497
  appendWorktreeEvent(primaryCheckout, {
25497
25498
  action: removeOutcome.status === "removed" ? "removed" : "failed",
@@ -25506,8 +25507,11 @@ function registerWorktreeCommands(program3) {
25506
25507
  reason: orphan.orphan ? `orphaned worktree \u2014 ${orphan.reason}${lastCommit ? `; last commit ${lastCommit.oid}${lastCommit.unreferenced ? " (on no ref)" : ""}` : ""}` : describeLandMergeState(mergeVerdict)
25507
25508
  });
25508
25509
  if (removeOutcome.status === "removed") dropWorktreeOwner(primaryCheckout, wtPath);
25509
- if (orphan.orphan) {
25510
- report.push({ step: "delete branch refs", status: `skipped: orphaned worktree \u2014 ${orphan.reason}` });
25510
+ if (treeOnly) {
25511
+ report.push({
25512
+ step: "delete branch refs",
25513
+ status: detached ? "skipped: detached HEAD has no branch refs" : `skipped: orphaned worktree \u2014 ${orphan.reason}`
25514
+ });
25511
25515
  if (lastCommit) {
25512
25516
  report.push({
25513
25517
  step: "last commit",
@@ -25535,12 +25539,12 @@ function registerWorktreeCommands(program3) {
25535
25539
  report
25536
25540
  };
25537
25541
  if (o.json) return console.log(JSON.stringify(result, null, 2));
25538
- console.log(orphan.orphan ? `worktree land: removed orphaned worktree ${wtPath} (${orphan.reason}); no branch refs touched` : `worktree land: cleaned up branch ${branch} (${describeLandMergeState(mergeVerdict)})`);
25542
+ console.log(treeOnly ? `worktree land: removed ${detached ? "detached" : "orphaned"} worktree ${wtPath}${detached ? "" : ` (${orphan.reason})`}; no branch refs touched` : `worktree land: cleaned up branch ${branch} (${describeLandMergeState(mergeVerdict)})`);
25539
25543
  for (const r of report) console.log(` ${r.step}: ${r.status}`);
25540
25544
  if (lastCommit?.unreferenced) {
25541
25545
  console.warn(`worktree land: commit ${lastCommit.oid} was on no ref but this worktree's reflog \u2014 normal after a squash merge, but if that work was NOT merged, recover it now: ${lastCommit.recoverCommand}`);
25542
25546
  }
25543
- if (!orphan.orphan && mergeVerdict.state !== "merged") {
25547
+ if (!treeOnly && mergeVerdict.state !== "merged") {
25544
25548
  console.warn(`worktree land: '${branch}' was cleaned up but NOT merged \u2014 ${describeLandMergeState(mergeVerdict)}. Do not report this work as shipped.`);
25545
25549
  }
25546
25550
  if (report.some((r) => r.status.startsWith("failed"))) process.exitCode = 1;
@@ -25601,10 +25605,10 @@ async function gatherWorktreeContext() {
25601
25605
  if (s) stages.push({ path: wt.path, port: s.port });
25602
25606
  }
25603
25607
  const worktreeGitRoot = await currentRepoWorktreeGitRoot(repoRoot2);
25604
- const primaryRepoRoot = worktreeGitRoot ? (0, import_node_path30.dirname)((0, import_node_path30.dirname)(worktreeGitRoot)) : repoRoot2;
25608
+ const primaryRepoRoot = worktreeGitRoot ? (0, import_node_path31.dirname)((0, import_node_path31.dirname)(worktreeGitRoot)) : repoRoot2;
25605
25609
  const wtRoot = siblingMmiWorktreesRoot(primaryRepoRoot);
25606
25610
  let orphanDirs = [];
25607
- if ((0, import_node_fs30.existsSync)(wtRoot)) {
25611
+ if ((0, import_node_fs31.existsSync)(wtRoot)) {
25608
25612
  orphanDirs = scanOrphanDirs(wtRoot, worktreeGitRoot, {
25609
25613
  ...defaultOrphanDirScanDeps,
25610
25614
  listDirs: (root) => worktreeScanDirs(root, primaryRepoRoot, defaultOrphanDirScanDeps.listDirs, isRepoCheckoutDir)
@@ -25627,7 +25631,7 @@ async function bestEffortGit(args, cwd, step, timeoutMs = GIT_TIMEOUT_MS) {
25627
25631
  }
25628
25632
 
25629
25633
  // src/issue-commands.ts
25630
- var import_node_fs31 = require("node:fs");
25634
+ var import_node_fs32 = require("node:fs");
25631
25635
  var import_node_crypto5 = require("node:crypto");
25632
25636
  var ghRunner = async (args, timeoutMs) => (await execFileP2("gh", args, { timeout: timeoutMs })).stdout;
25633
25637
  var ReparentConflictError = class extends Error {
@@ -25645,7 +25649,7 @@ async function editIssue(client, options, deps = {}) {
25645
25649
  const url = `https://github.com/${repo}/issues/${parsed.number}`;
25646
25650
  const patch = {};
25647
25651
  let bodyChanged = false;
25648
- const textDeps = () => deps.textDeps ?? { readFile: (p, e) => Promise.resolve((0, import_node_fs31.readFileSync)(p, e)), readStdin: () => Promise.resolve("") };
25652
+ const textDeps = () => deps.textDeps ?? { readFile: (p, e) => Promise.resolve((0, import_node_fs32.readFileSync)(p, e)), readStdin: () => Promise.resolve("") };
25649
25653
  if (options.titleFile !== void 0) {
25650
25654
  patch.title = await resolveIssueTitle({ title: options.title, titleFile: options.titleFile }, textDeps());
25651
25655
  } else if (options.title !== void 0) {
@@ -26208,7 +26212,7 @@ function extendCreateCommand(issue2, batchAttach) {
26208
26212
  if (opts.batch) {
26209
26213
  let specs;
26210
26214
  try {
26211
- const raw = (0, import_node_fs31.readFileSync)(opts.batch, "utf8");
26215
+ const raw = (0, import_node_fs32.readFileSync)(opts.batch, "utf8");
26212
26216
  specs = JSON.parse(raw);
26213
26217
  if (!Array.isArray(specs)) throw new Error("batch file must contain a JSON array");
26214
26218
  } catch (e) {
@@ -26282,8 +26286,8 @@ ${lines}`, {
26282
26286
  }
26283
26287
 
26284
26288
  // src/train-commands.ts
26285
- var import_node_fs32 = require("node:fs");
26286
- var import_node_path31 = require("node:path");
26289
+ var import_node_fs33 = require("node:fs");
26290
+ var import_node_path32 = require("node:path");
26287
26291
 
26288
26292
  // src/train-status.ts
26289
26293
  function buildTrainStatusReport(input) {
@@ -26323,7 +26327,7 @@ function formatTrainStatus(r) {
26323
26327
  // src/train-commands.ts
26324
26328
  function readRepoVersion() {
26325
26329
  try {
26326
- return JSON.parse((0, import_node_fs32.readFileSync)((0, import_node_path31.join)(process.cwd(), ".claude-plugin", "plugin.json"), "utf8")).version || void 0;
26330
+ return JSON.parse((0, import_node_fs33.readFileSync)((0, import_node_path32.join)(process.cwd(), ".claude-plugin", "plugin.json"), "utf8")).version || void 0;
26327
26331
  } catch {
26328
26332
  return void 0;
26329
26333
  }
@@ -26469,11 +26473,12 @@ function registerDeployCommands(program3) {
26469
26473
  }
26470
26474
 
26471
26475
  // src/discovery-commands.ts
26472
- var import_node_fs33 = require("node:fs");
26473
- var import_node_os11 = require("node:os");
26474
- var import_node_path32 = require("node:path");
26476
+ var import_node_fs34 = require("node:fs");
26477
+ var import_node_os12 = require("node:os");
26478
+ var import_node_path33 = require("node:path");
26475
26479
  var GC_GH_TIMEOUT_MS3 = 2e4;
26476
26480
  async function collectStatus() {
26481
+ const repo = await resolveRepo();
26477
26482
  let branch = "";
26478
26483
  try {
26479
26484
  branch = (await execFileP2("git", ["rev-parse", "--abbrev-ref", "HEAD"], { timeout: GIT_TIMEOUT_MS })).stdout.trim();
@@ -26502,7 +26507,7 @@ async function collectStatus() {
26502
26507
  }
26503
26508
  let claimedItems = [];
26504
26509
  try {
26505
- const cfg = await loadConfig();
26510
+ const cfg = await loadConfigOrDiscover();
26506
26511
  if (cfg.sagaApiUrl) {
26507
26512
  const report = await readBoard({ config: cfg });
26508
26513
  claimedItems = report.primary.userOwned.map((item) => ({
@@ -26525,7 +26530,7 @@ async function collectStatus() {
26525
26530
  } catch {
26526
26531
  stage = { running: false };
26527
26532
  }
26528
- return { branch, worktrees, myOpenPrs, claimedItems, stage };
26533
+ return { repo, branch, worktrees, myOpenPrs, claimedItems, stage };
26529
26534
  }
26530
26535
  var PRIORITY_RANK = {
26531
26536
  Urgent: 0,
@@ -26533,14 +26538,14 @@ var PRIORITY_RANK = {
26533
26538
  Medium: 2,
26534
26539
  Low: 3
26535
26540
  };
26536
- async function recommendNext(deps) {
26537
- const load = deps?.loadConfig ?? loadConfig;
26541
+ async function recommendNext(repo, deps) {
26542
+ const load = deps?.loadConfig ?? loadConfigForRepo;
26538
26543
  const reader = deps?.readBoard ?? readBoard;
26539
- const cfg = await load();
26544
+ const cfg = await load(repo);
26540
26545
  if (!cfg.sagaApiUrl) throw new Error("Hub API URL not configured \u2014 the board was NOT read (run `mmi-cli doctor`)");
26541
26546
  let report;
26542
26547
  try {
26543
- report = await reader({ config: cfg });
26548
+ report = await reader({ config: cfg, repo });
26544
26549
  } catch (e) {
26545
26550
  throw new Error(
26546
26551
  `board unreachable \u2014 ${e.message}. This is NOT an empty board: confirm the ACTIVE gh account can see this repo (\`gh auth status\`, \`gh repo view <owner/repo>\`; \`gh auth switch --user <other>\` when more than one account is present).`
@@ -26646,10 +26651,10 @@ async function collectOnboardStatus() {
26646
26651
  else if (top) nextCommand = `mmi-cli board claim ${top.number} # ${top.title}`;
26647
26652
  else nextCommand = "mmi-cli board read \u2014 no claimable items found";
26648
26653
  }
26649
- const home = (0, import_node_os11.homedir)();
26654
+ const home = (0, import_node_os12.homedir)();
26650
26655
  const plugin = onboardPluginGate({
26651
- readKnown: () => readFileSyncSafe((0, import_node_path32.join)(home, ...KNOWN_MARKETPLACES_RELATIVE), import_node_fs33.readFileSync),
26652
- readSettings: () => readFileSyncSafe((0, import_node_path32.join)(home, ".claude", "settings.json"), import_node_fs33.readFileSync)
26656
+ readKnown: () => readFileSyncSafe((0, import_node_path33.join)(home, ...KNOWN_MARKETPLACES_RELATIVE), import_node_fs34.readFileSync),
26657
+ readSettings: () => readFileSyncSafe((0, import_node_path33.join)(home, ".claude", "settings.json"), import_node_fs34.readFileSync)
26653
26658
  });
26654
26659
  return { track, board, registry: registry2, secrets, plugin, nextCommand };
26655
26660
  }
@@ -26660,6 +26665,7 @@ function registerDiscoveryCommands(program3) {
26660
26665
  if (o.json) {
26661
26666
  console.log(JSON.stringify(report, null, 2));
26662
26667
  } else {
26668
+ console.log(`repo: ${report.repo ?? "unknown"}`);
26663
26669
  console.log(`branch: ${report.branch}`);
26664
26670
  console.log(`worktrees: ${report.worktrees.length} linked`);
26665
26671
  console.log(`my open PRs: ${report.myOpenPrs.length}`);
@@ -26670,9 +26676,9 @@ function registerDiscoveryCommands(program3) {
26670
26676
  fail(`status: ${e.message}`);
26671
26677
  }
26672
26678
  });
26673
- program3.command("next").description("recommend the next actionable board item (claimable, unblocked, priority-ranked)").action(async () => {
26679
+ program3.command("next").description("recommend the next actionable board item (claimable, unblocked, priority-ranked)").option("--repo <owner/repo>", "current repo (defaults to git origin)").action(async (o) => {
26674
26680
  try {
26675
- const item = await recommendNext();
26681
+ const item = await recommendNext(o.repo);
26676
26682
  if (!item) {
26677
26683
  console.log("No claimable board items found (board read OK).");
26678
26684
  return;
@@ -28235,7 +28241,7 @@ function planSurfaceRepair(diagnosis) {
28235
28241
  owner: "mmi-cli",
28236
28242
  command: "mmi-cli plugin heal",
28237
28243
  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)}`
28244
+ instruction: `run \`mmi-cli doctor\` (or \`mmi-cli plugin heal\`), then ${reloadInstruction(descriptor)}`
28239
28245
  };
28240
28246
  }
28241
28247
  function buildSurfaceDoctorCheck(diagnosis) {
@@ -28341,34 +28347,6 @@ function checkRepoWorktrees(probe) {
28341
28347
  ]
28342
28348
  };
28343
28349
  }
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
28350
  function checkCodexHookTrust(probe, displayName = "Codex") {
28373
28351
  if (!probe?.applicable) return null;
28374
28352
  const evidence = [
@@ -28479,81 +28457,6 @@ function checkTrainSync(result) {
28479
28457
  verbose: evidence
28480
28458
  };
28481
28459
  }
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
28460
  function checkSessionPayload(probe) {
28558
28461
  if (!probe) return null;
28559
28462
  const { chars } = probe;
@@ -28608,13 +28511,14 @@ function gcReapable(plan) {
28608
28511
  return plan.branches.length + plan.trackingRefs.length + plan.worktreeDirs.length;
28609
28512
  }
28610
28513
  async function runDoctorClean(opts, io, deps) {
28611
- const applyEnv = Boolean(opts.apply) || Boolean(opts.preflight);
28612
- const applyRepo = Boolean(opts.apply) && opts.repoWrites !== false;
28514
+ const full = !opts.fast && !opts.banner && !opts.preflight;
28515
+ const applyEnv = full || Boolean(opts.preflight);
28516
+ const applyRepo = full && opts.repoWrites !== false;
28613
28517
  const lane = {
28614
28518
  banner: Boolean(opts.banner),
28615
28519
  fast: Boolean(opts.fast),
28616
28520
  preflight: Boolean(opts.preflight),
28617
- full: !opts.fast && !opts.banner && !opts.preflight
28521
+ full
28618
28522
  };
28619
28523
  const probeReleased = !opts.fast || Boolean(opts.self);
28620
28524
  const probeAws = !opts.fast && !opts.banner;
@@ -28712,9 +28616,6 @@ async function runDoctorClean(opts, io, deps) {
28712
28616
  async function runGithubAuthRow() {
28713
28617
  emitNow(checkGithubAuth({ login, ghInstalled: ghInstalled2, reach }));
28714
28618
  }
28715
- async function runGithubPoolsRows() {
28716
- for (const pool of checkGithubPools(await deps.githubPools())) emitNow(pool);
28717
- }
28718
28619
  async function runAwsRow() {
28719
28620
  const aws = checkAwsIdentity({ isOrgRepo, probed: probeAws, callerArn });
28720
28621
  if (aws) emitNow(aws);
@@ -28735,7 +28636,7 @@ async function runDoctorClean(opts, io, deps) {
28735
28636
  emitNow({ ok: true, id: "gitignore-block", label: "gitignore block", detail: "rewrote managed block", verbose: giEvidence });
28736
28637
  restartPending = true;
28737
28638
  } 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 });
28639
+ 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
28640
  }
28740
28641
  }
28741
28642
  async function runPluginCacheRow() {
@@ -28755,35 +28656,6 @@ async function runDoctorClean(opts, io, deps) {
28755
28656
  }
28756
28657
  for (const row of deps.marketplaceRows()) emitNow(row);
28757
28658
  }
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
28659
  async function runTrainSyncRow() {
28788
28660
  try {
28789
28661
  emitNow(checkTrainSync(await deps.syncTrain()));
@@ -28839,7 +28711,7 @@ async function runDoctorClean(opts, io, deps) {
28839
28711
  // #3574: `${n} stale` was prepended to `fix` because the ✗ branch would not render `detail` —
28840
28712
  // the same fact this row already puts in `detail` on its ✓ path four lines up. One convention now.
28841
28713
  detail: `${n} stale`,
28842
- fix: "run `mmi-cli doctor --apply` to reap merged branches, stale refs, and dead worktrees",
28714
+ fix: "run `mmi-cli doctor` (without --no-repo-writes) to reap merged branches, stale refs, and dead worktrees",
28843
28715
  verbose: gcEvidence
28844
28716
  });
28845
28717
  }
@@ -28853,7 +28725,7 @@ async function runDoctorClean(opts, io, deps) {
28853
28725
  emitNow({ id: "scratch", ok: true, label: "scratch", detail: pruned ? `removed ${pruned} aged item(s)` : "nothing stale", verbose: scratchEvidence });
28854
28726
  if (pruned) restartPending = true;
28855
28727
  } 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 });
28728
+ 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
28729
  }
28858
28730
  }
28859
28731
  const table = [
@@ -28862,17 +28734,12 @@ async function runDoctorClean(opts, io, deps) {
28862
28734
  { id: "plugin", when: true, run: runPluginRow },
28863
28735
  { id: "cli-version", when: true, run: runCliRow },
28864
28736
  { id: "github-auth", when: true, run: runGithubAuthRow },
28865
- { id: "github-pools", when: lane.full, run: runGithubPoolsRows },
28866
28737
  { id: "aws-identity", when: true, run: runAwsRow },
28867
28738
  { id: "repo-worktrees", when: true, run: runRepoWorktreesRow },
28868
28739
  { id: "gitignore-block", when: isOrgRepo, run: runGitignoreRow },
28869
28740
  { id: "plugin-cache", when: true, run: runPluginCacheRow },
28870
28741
  { id: "sessionstart-payload", when: true, run: runSessionPayloadRow },
28871
28742
  { 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
28743
  // The two most expensive things in this file — a real `git fetch` plus train-branch fast-forward,
28877
28744
  // and a `gh`-backed gc sweep with a 20s timeout — so org repos on the full lane only (#3485).
28878
28745
  { id: "train-branches", when: isOrgRepo && lane.full, run: runTrainSyncRow },
@@ -28931,17 +28798,17 @@ function parseOriginRepo(remoteUrl) {
28931
28798
  }
28932
28799
  function ghHostsConfigPath(env, platform2) {
28933
28800
  const sep2 = platform2 === "win32" ? "\\" : "/";
28934
- const join29 = (...parts) => parts.join(sep2);
28801
+ const join30 = (...parts) => parts.join(sep2);
28935
28802
  const explicit = env.GH_CONFIG_DIR?.trim();
28936
- if (explicit) return join29(explicit, "hosts.yml");
28803
+ if (explicit) return join30(explicit, "hosts.yml");
28937
28804
  if (platform2 === "win32") {
28938
28805
  const appData = (env.AppData ?? env.APPDATA)?.trim();
28939
- return appData ? join29(appData, "GitHub CLI", "hosts.yml") : void 0;
28806
+ return appData ? join30(appData, "GitHub CLI", "hosts.yml") : void 0;
28940
28807
  }
28941
28808
  const xdg = env.XDG_CONFIG_HOME?.trim();
28942
- if (xdg) return join29(xdg, "gh", "hosts.yml");
28809
+ if (xdg) return join30(xdg, "gh", "hosts.yml");
28943
28810
  const home = env.HOME?.trim();
28944
- return home ? join29(home, ".config", "gh", "hosts.yml") : void 0;
28811
+ return home ? join30(home, ".config", "gh", "hosts.yml") : void 0;
28945
28812
  }
28946
28813
  function parseGhHostsAccounts(yaml, host = "github.com") {
28947
28814
  let hostIndent = null;
@@ -28991,9 +28858,9 @@ function ghAccountCaveat(announcedLogin, accounts) {
28991
28858
  }
28992
28859
 
28993
28860
  // src/doctor-io.ts
28994
- var import_node_fs34 = require("node:fs");
28995
- var import_node_os12 = require("node:os");
28996
- var import_node_path33 = require("node:path");
28861
+ var import_node_fs35 = require("node:fs");
28862
+ var import_node_os13 = require("node:os");
28863
+ var import_node_path34 = require("node:path");
28997
28864
  var import_node_child_process15 = require("node:child_process");
28998
28865
  var import_node_util8 = require("node:util");
28999
28866
  var execFileP6 = (0, import_node_util8.promisify)(import_node_child_process15.execFile);
@@ -29001,7 +28868,7 @@ var MMI_PLUGIN_ID2 = "mmi@mutmutco";
29001
28868
  function installedClaudePluginVersion() {
29002
28869
  try {
29003
28870
  const file = JSON.parse(
29004
- (0, import_node_fs34.readFileSync)((0, import_node_path33.join)((0, import_node_os12.homedir)(), ".claude", "plugins", "installed_plugins.json"), "utf8")
28871
+ (0, import_node_fs35.readFileSync)((0, import_node_path34.join)((0, import_node_os13.homedir)(), ".claude", "plugins", "installed_plugins.json"), "utf8")
29005
28872
  );
29006
28873
  const versions = (file.plugins?.[MMI_PLUGIN_ID2] ?? []).map((r) => r.version).filter((v) => Boolean(v));
29007
28874
  if (versions.length === 0) return void 0;
@@ -29012,7 +28879,7 @@ function installedClaudePluginVersion() {
29012
28879
  }
29013
28880
  function manifestVersion(path2) {
29014
28881
  try {
29015
- const manifest = JSON.parse((0, import_node_fs34.readFileSync)(path2, "utf8"));
28882
+ const manifest = JSON.parse((0, import_node_fs35.readFileSync)(path2, "utf8"));
29016
28883
  return typeof manifest.version === "string" && manifest.version.trim() ? manifest.version.trim() : void 0;
29017
28884
  } catch {
29018
28885
  return void 0;
@@ -29022,17 +28889,17 @@ function installedSurfacePluginVersion(surface) {
29022
28889
  const token = surfaceToken(surface);
29023
28890
  if (token === "kilo") {
29024
28891
  try {
29025
- const stamp = (0, import_node_fs34.readFileSync)((0, import_node_path33.join)((0, import_node_os12.homedir)(), ".kilo", ".mmi-kilo-version"), "utf8").trim();
28892
+ const stamp = (0, import_node_fs35.readFileSync)((0, import_node_path34.join)((0, import_node_os13.homedir)(), ".kilo", ".mmi-kilo-version"), "utf8").trim();
29026
28893
  return stamp || void 0;
29027
28894
  } catch {
29028
28895
  return void 0;
29029
28896
  }
29030
28897
  }
29031
28898
  if (token === "cursor") {
29032
- return manifestVersion((0, import_node_path33.join)(cursorLocalPluginRoot(), ".cursor-plugin", "plugin.json"));
28899
+ return manifestVersion((0, import_node_path34.join)(cursorLocalPluginRoot(), ".cursor-plugin", "plugin.json"));
29033
28900
  }
29034
28901
  if (token === "kimi") {
29035
- return manifestVersion((0, import_node_path33.join)(surfaceConfigRoot(surface), "plugins", "managed", "mmi", ".kimi-plugin", "plugin.json"));
28902
+ return manifestVersion((0, import_node_path34.join)(surfaceConfigRoot(surface), "plugins", "managed", "mmi", ".kimi-plugin", "plugin.json"));
29036
28903
  }
29037
28904
  if (token === "claude") return installedClaudePluginVersion();
29038
28905
  if (token !== "codex") return void 0;
@@ -29070,13 +28937,13 @@ function worktreeRootSync() {
29070
28937
  }
29071
28938
  var gitignorePath = () => {
29072
28939
  const root = worktreeRootSync();
29073
- return root === null ? null : (0, import_node_path33.join)(root, ".gitignore");
28940
+ return root === null ? null : (0, import_node_path34.join)(root, ".gitignore");
29074
28941
  };
29075
28942
  function readGitignore() {
29076
28943
  const path2 = gitignorePath();
29077
28944
  if (path2 === null) return null;
29078
28945
  try {
29079
- return (0, import_node_fs34.readFileSync)(path2, "utf8");
28946
+ return (0, import_node_fs35.readFileSync)(path2, "utf8");
29080
28947
  } catch {
29081
28948
  return null;
29082
28949
  }
@@ -29085,7 +28952,7 @@ function writeGitignore(content) {
29085
28952
  const path2 = gitignorePath();
29086
28953
  if (path2 === null) return false;
29087
28954
  try {
29088
- (0, import_node_fs34.writeFileSync)(path2, content, "utf8");
28955
+ (0, import_node_fs35.writeFileSync)(path2, content, "utf8");
29089
28956
  return true;
29090
28957
  } catch {
29091
28958
  return false;
@@ -29109,7 +28976,7 @@ async function repoRoot() {
29109
28976
  }
29110
28977
  function hasRepoLocalWorktrees() {
29111
28978
  const root = worktreeRootSync();
29112
- return root !== null && (0, import_node_fs34.existsSync)((0, import_node_path33.join)(root, ".worktrees"));
28979
+ return root !== null && (0, import_node_fs35.existsSync)((0, import_node_path34.join)(root, ".worktrees"));
29113
28980
  }
29114
28981
 
29115
28982
  // src/index.ts
@@ -29145,8 +29012,8 @@ ${r.stderr ?? ""}`).catch(() => "");
29145
29012
  function ghMultiAccountCaveat(announcedLogin) {
29146
29013
  try {
29147
29014
  const hostsPath = ghHostsConfigPath(process.env, process.platform);
29148
- if (!hostsPath || !(0, import_node_fs35.existsSync)(hostsPath)) return void 0;
29149
- return ghAccountCaveat(announcedLogin, parseGhHostsAccounts((0, import_node_fs35.readFileSync)(hostsPath, "utf8")));
29015
+ if (!hostsPath || !(0, import_node_fs36.existsSync)(hostsPath)) return void 0;
29016
+ return ghAccountCaveat(announcedLogin, parseGhHostsAccounts((0, import_node_fs36.readFileSync)(hostsPath, "utf8")));
29150
29017
  } catch {
29151
29018
  return void 0;
29152
29019
  }
@@ -29154,12 +29021,12 @@ function ghMultiAccountCaveat(announcedLogin) {
29154
29021
  var ENV_HEAL_LOCK_STALE_MS = 10 * 6e4;
29155
29022
  var ENV_HEAL_LOCK_MAX_WAIT_MS = 2 * 6e4;
29156
29023
  function envHealLockPath(home) {
29157
- return (0, import_node_path34.join)(home, ".claude", "plugins", ".mmi-env-heal.lock");
29024
+ return (0, import_node_path35.join)(home, ".claude", "plugins", ".mmi-env-heal.lock");
29158
29025
  }
29159
29026
  async function withEnvHealLock(what, run) {
29160
29027
  try {
29161
29028
  return await withFileLock(
29162
- envHealLockPath((0, import_node_os13.homedir)()),
29029
+ envHealLockPath((0, import_node_os14.homedir)()),
29163
29030
  { staleMs: ENV_HEAL_LOCK_STALE_MS, maxWaitMs: ENV_HEAL_LOCK_MAX_WAIT_MS, label: "mmi env-heal lock" },
29164
29031
  run
29165
29032
  );
@@ -29170,9 +29037,6 @@ async function withEnvHealLock(what, run) {
29170
29037
  return { ok: false, detail: `${what} could not take the env-heal lock \u2014 ${e.message}` };
29171
29038
  }
29172
29039
  }
29173
- function docsJanitorScheduleId(repo) {
29174
- return `${repo.split("/").pop()}/docs-janitor`;
29175
- }
29176
29040
  function throttledReleasedVersion() {
29177
29041
  let note;
29178
29042
  return {
@@ -29193,8 +29057,6 @@ function throttledReleasedVersion() {
29193
29057
  }
29194
29058
  function mmiDoctorDeps(opts = {}) {
29195
29059
  const throttled = opts.throttleReleasedRead ? throttledReleasedVersion() : void 0;
29196
- let notebook;
29197
- const notebookOnce = () => notebook ??= fetchNotebook();
29198
29060
  let surfaceEvidence;
29199
29061
  let surfaceEvidenceRead = false;
29200
29062
  const surfaceEvidenceOnce = (isOrgRepo) => {
@@ -29218,11 +29080,6 @@ function mmiDoctorDeps(opts = {}) {
29218
29080
  };
29219
29081
  return surfaceEvidence;
29220
29082
  };
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
29083
  return {
29227
29084
  githubLogin,
29228
29085
  ghInstalled,
@@ -29261,30 +29118,12 @@ function mmiDoctorDeps(opts = {}) {
29261
29118
  // writes to the harness-owned cache itself. Same plan builder the verb uses, so the two never disagree.
29262
29119
  // No `withBytes` here: doctor runs on EVERY SessionStart, and sizing means recursively stat'ing every
29263
29120
  // 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
29121
  pluginCache: () => {
29283
29122
  const surface = detectSurface(process.env);
29284
29123
  const configRoot = surfaceConfigRoot(surface);
29285
29124
  const running = surface === "codex" ? installedActivePluginVersion(surface) : runningPluginVersion(process.env, resolveClientVersion());
29286
29125
  const plan = buildPluginCachePlan(
29287
- (0, import_node_os13.homedir)(),
29126
+ (0, import_node_os14.homedir)(),
29288
29127
  running,
29289
29128
  pluginCacheFsDeps(configRoot, () => 0),
29290
29129
  { configRoot, includeStaging: surface !== "codex" }
@@ -29297,55 +29136,39 @@ function mmiDoctorDeps(opts = {}) {
29297
29136
  stagingBytes: plan.stagingBytes
29298
29137
  };
29299
29138
  },
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
29139
  // #3470: what the SessionStart hook LAST emitted, measured at emission by the session-start verb below.
29330
29140
  // A local record read — cheap enough for every lane, including the banner.
29331
29141
  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
29142
  // #3485 items 9 and 8: why the MMI plugin has never printed an "updated — please restart" notice, and
29336
29143
  // which branch it would pick one up from. Two local file reads, no network, fail-soft to no rows.
29337
29144
  marketplaceRows: () => {
29338
29145
  try {
29339
29146
  if (detectSurface(process.env) === "codex") return [];
29340
- const home = (0, import_node_os13.homedir)();
29341
- return marketplaceRows(
29147
+ const home = (0, import_node_os14.homedir)();
29148
+ const rows = marketplaceRows(
29342
29149
  MMI_MARKETPLACE_NAME,
29343
- readFileSyncSafe((0, import_node_path34.join)(home, ...KNOWN_MARKETPLACES_RELATIVE), import_node_fs35.readFileSync),
29344
- 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.
29150
+ readFileSyncSafe((0, import_node_path35.join)(home, ...KNOWN_MARKETPLACES_RELATIVE), import_node_fs36.readFileSync),
29151
+ readFileSyncSafe((0, import_node_path35.join)(home, ".claude", "settings.json"), import_node_fs36.readFileSync),
29152
+ // #3974: this CLI heals these rows, so report mode names the doctor run rather than the hand
29153
+ // edit. Unconditional — it is a fact about mmi-cli, not about the lane this run is on.
29347
29154
  true
29348
29155
  );
29156
+ const pending = readMarketplacePinPending(
29157
+ (0, import_node_path35.join)(home, ".claude", "plugins", ".mmi-marketplace-pin-pending.json"),
29158
+ MMI_MARKETPLACE_NAME
29159
+ );
29160
+ if (!pending) return rows;
29161
+ return rows.map((row) => {
29162
+ if (row.id !== "marketplace-catalog-ref" || row.ok) return row;
29163
+ const { fix: _fix, reportOnly: _reportOnly, ...rest } = row;
29164
+ return {
29165
+ ...rest,
29166
+ ok: true,
29167
+ warn: true,
29168
+ detail: `pending restart \u2014 doctor pinned main at ${pending.at}; restart Claude Code to load it`,
29169
+ verbose: [...row.verbose ?? [], "known_marketplaces.json was rewritten by the running Claude host after the verified doctor write"]
29170
+ };
29171
+ });
29349
29172
  } catch {
29350
29173
  return [];
29351
29174
  }
@@ -29355,7 +29178,13 @@ function mmiDoctorDeps(opts = {}) {
29355
29178
  healMarketplacePins: () => {
29356
29179
  try {
29357
29180
  if (detectSurface(process.env) === "codex") return void 0;
29358
- return applyOrgMarketplacePins((0, import_node_path34.join)((0, import_node_os13.homedir)(), ...KNOWN_MARKETPLACES_RELATIVE), [MMI_MARKETPLACE_NAME]);
29181
+ const home = (0, import_node_os14.homedir)();
29182
+ const names = [MMI_MARKETPLACE_NAME];
29183
+ const result = applyOrgMarketplacePins((0, import_node_path35.join)(home, ...KNOWN_MARKETPLACES_RELATIVE), names);
29184
+ if (result?.startsWith("pinned ")) {
29185
+ writeMarketplacePinPending((0, import_node_path35.join)(home, ".claude", "plugins", ".mmi-marketplace-pin-pending.json"), names);
29186
+ }
29187
+ return result;
29359
29188
  } catch {
29360
29189
  return void 0;
29361
29190
  }
@@ -29536,6 +29365,7 @@ function appActorDeps() {
29536
29365
  fetch: (url, init) => fetch(url, init)
29537
29366
  };
29538
29367
  }
29368
+ setPollTokenProvider(() => pollTokenOrUndefined(appActorDeps()));
29539
29369
  function shouldMarkWorktreeActivity(commandPath3) {
29540
29370
  return commandPath3.split(" ")[0] !== "worktree";
29541
29371
  }
@@ -29560,19 +29390,19 @@ program2.hook("preAction", async (_thisCommand, actionCommand) => {
29560
29390
  });
29561
29391
  var rules = program2.command("rules").description("org-managed .gitignore delivery");
29562
29392
  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) => {
29563
- const path2 = (0, import_node_path34.join)(process.cwd(), ".gitignore");
29564
- const current = (0, import_node_fs35.existsSync)(path2) ? (0, import_node_fs35.readFileSync)(path2, "utf8") : null;
29393
+ const path2 = (0, import_node_path35.join)(process.cwd(), ".gitignore");
29394
+ const current = (0, import_node_fs36.existsSync)(path2) ? (0, import_node_fs36.readFileSync)(path2, "utf8") : null;
29565
29395
  const plan = planManagedGitignore(current);
29566
29396
  const drift = [...plan.added.map((l) => `+${l}`), ...plan.removed.map((l) => `-${l}`)].join(", ") || "block normalize";
29567
29397
  if (opts.json) {
29568
- if (opts.write && plan.changed) (0, import_node_fs35.writeFileSync)(path2, plan.content, "utf8");
29398
+ if (opts.write && plan.changed) (0, import_node_fs36.writeFileSync)(path2, plan.content, "utf8");
29569
29399
  console.log(JSON.stringify(plan, null, 2));
29570
29400
  if (!opts.write && plan.changed) process.exitCode = 1;
29571
29401
  return;
29572
29402
  }
29573
29403
  if (opts.write) {
29574
29404
  if (plan.changed) {
29575
- (0, import_node_fs35.writeFileSync)(path2, plan.content, "utf8");
29405
+ (0, import_node_fs36.writeFileSync)(path2, plan.content, "utf8");
29576
29406
  console.log(`mmi-cli org rules gitignore: updated .gitignore (${drift})`);
29577
29407
  } else {
29578
29408
  console.log("mmi-cli org rules gitignore: up to date");
@@ -29727,8 +29557,8 @@ gcCmd.option("--dry-run", "show what would be deleted (default)").option("--appl
29727
29557
  if (!Number.isFinite(limit) || limit < 1) return fail("worktree gc: --limit must be a positive integer");
29728
29558
  let root;
29729
29559
  if (o.root !== void 0) {
29730
- root = (0, import_node_path34.resolve)(o.root);
29731
- if (!(0, import_node_fs35.existsSync)(root) || !(0, import_node_fs35.statSync)(root).isDirectory()) return fail(`worktree gc: --root ${o.root} is not a directory`);
29560
+ root = (0, import_node_path35.resolve)(o.root);
29561
+ if (!(0, import_node_fs36.existsSync)(root) || !(0, import_node_fs36.statSync)(root).isDirectory()) return fail(`worktree gc: --root ${o.root} is not a directory`);
29732
29562
  const gcRepoRoot = (await execFileP2("git", ["rev-parse", "--show-toplevel"], { timeout: GIT_TIMEOUT_MS }).catch(() => ({ stdout: "" }))).stdout.trim() || process.cwd();
29733
29563
  if (isPathUnderDirectory(gcRepoRoot, root)) {
29734
29564
  return fail(`worktree gc: --root ${root} contains this checkout \u2014 name a worktrees root, not the repo or an ancestor of it`);
@@ -29793,7 +29623,7 @@ async function primaryCheckoutRoot(from) {
29793
29623
  return primaryCheckoutRootOf(async (args) => (await execFileP2("git", ["-C", from, ...args], { timeout: GIT_TIMEOUT_MS })).stdout);
29794
29624
  }
29795
29625
  async function unprovenWorktreeReason(wtPath, repoRoot2) {
29796
- if (!(0, import_node_fs35.existsSync)(wtPath)) return `${wtPath} does not exist on disk`;
29626
+ if (!(0, import_node_fs36.existsSync)(wtPath)) return `${wtPath} does not exist on disk`;
29797
29627
  const porcelain = (await execFileP2("git", ["-C", repoRoot2, "worktree", "list", "--porcelain"], { timeout: GIT_TIMEOUT_MS }).catch(() => ({ stdout: "" }))).stdout;
29798
29628
  const registered = parseWorktreePorcelainEntries(porcelain);
29799
29629
  if (!registered.length) {
@@ -29807,6 +29637,7 @@ async function unprovenWorktreeReason(wtPath, repoRoot2) {
29807
29637
  function makeProvisionDeps(worktreeRoot, quiet, log) {
29808
29638
  return {
29809
29639
  runInstall: (command, cwd) => runWorktreeInstall(command, cwd, quiet),
29640
+ validateInstall: (cwd) => runWorktreeInstall("npm ls --depth=0 --json", cwd, true).then(() => true).catch(() => false),
29810
29641
  primaryCheckout: () => primaryCheckoutRoot(worktreeRoot),
29811
29642
  log
29812
29643
  };
@@ -29814,26 +29645,26 @@ function makeProvisionDeps(worktreeRoot, quiet, log) {
29814
29645
  function acquireWorktreeSetupLock(worktreeRoot) {
29815
29646
  const lockPath = repoRuntimeStatePath(worktreeRoot, "worktree-setup.lock");
29816
29647
  const take = () => {
29817
- const fd = (0, import_node_fs35.openSync)(lockPath, "wx");
29648
+ const fd = (0, import_node_fs36.openSync)(lockPath, "wx");
29818
29649
  try {
29819
- (0, import_node_fs35.writeSync)(fd, String(Date.now()));
29650
+ (0, import_node_fs36.writeSync)(fd, String(Date.now()));
29820
29651
  } finally {
29821
- (0, import_node_fs35.closeSync)(fd);
29652
+ (0, import_node_fs36.closeSync)(fd);
29822
29653
  }
29823
29654
  return () => {
29824
29655
  try {
29825
- (0, import_node_fs35.rmSync)(lockPath, { force: true });
29656
+ (0, import_node_fs36.rmSync)(lockPath, { force: true });
29826
29657
  } catch {
29827
29658
  }
29828
29659
  };
29829
29660
  };
29830
29661
  try {
29831
- (0, import_node_fs35.mkdirSync)((0, import_node_path34.dirname)(lockPath), { recursive: true });
29662
+ (0, import_node_fs36.mkdirSync)((0, import_node_path35.dirname)(lockPath), { recursive: true });
29832
29663
  return take();
29833
29664
  } catch {
29834
29665
  try {
29835
- if (Date.now() - (0, import_node_fs35.statSync)(lockPath).mtimeMs > WORKTREE_SETUP_LOCK_TTL_MS) {
29836
- (0, import_node_fs35.rmSync)(lockPath, { force: true });
29666
+ if (Date.now() - (0, import_node_fs36.statSync)(lockPath).mtimeMs > WORKTREE_SETUP_LOCK_TTL_MS) {
29667
+ (0, import_node_fs36.rmSync)(lockPath, { force: true });
29837
29668
  return take();
29838
29669
  }
29839
29670
  } catch {
@@ -29889,22 +29720,52 @@ withExamples(mutating(
29889
29720
  const localOnly = preferRemote && base !== preferRemote ? ` (no ${preferRemote} \u2014 local ref)` : "";
29890
29721
  console.error(` base ${base} ${baseSha}${localOnly}`);
29891
29722
  }
29892
- step = `git worktree add ${wtPath}`;
29893
- await addWorktreeRobust(wtPath, branch, base, {
29894
- git: async (args) => (await execFileP2("git", args, { timeout: GH_MUTATION_TIMEOUT_MS })).stdout,
29895
- revParse: async (ref) => {
29896
- try {
29897
- return (await execFileP2("git", ["rev-parse", "--verify", ref], { timeout: GIT_TIMEOUT_MS })).stdout.trim() || void 0;
29898
- } catch {
29899
- return void 0;
29723
+ const registered = parseWorktreePorcelainEntries((await execFileP2(
29724
+ "git",
29725
+ ["-C", repoRoot2, "worktree", "list", "--porcelain"],
29726
+ { timeout: GIT_TIMEOUT_MS }
29727
+ )).stdout);
29728
+ const exact = registered.find((entry) => samePath(entry.path, wtPath));
29729
+ let resumed = false;
29730
+ if (exact) {
29731
+ step = `resume existing worktree ${wtPath}`;
29732
+ if (exact.branch !== branch) {
29733
+ return fail(`worktree create: ${wtPath} is already registered for '${exact.branch ?? "detached HEAD"}', not '${branch}'`);
29734
+ }
29735
+ const status = (await execFileP2("git", ["-C", wtPath, "status", "--porcelain"], { timeout: GIT_TIMEOUT_MS })).stdout.trim();
29736
+ if (status) return fail(`worktree create: refusing to resume ${wtPath} \u2014 it has uncommitted changes`);
29737
+ const head = await revParseRef(`refs/heads/${branch}`);
29738
+ const baseOid = await revParseRef(base);
29739
+ if (!head || !baseOid) return fail(`worktree create: could not prove '${branch}' and '${base}' before resume`);
29740
+ const canFastForward = await execFileP2(
29741
+ "git",
29742
+ ["-C", repoRoot2, "merge-base", "--is-ancestor", head, baseOid],
29743
+ { timeout: GIT_TIMEOUT_MS }
29744
+ ).then(() => true).catch(() => false);
29745
+ if (!canFastForward) {
29746
+ return fail(`worktree create: refusing to resume '${branch}' \u2014 it has local commits or diverges from ${base}`);
29747
+ }
29748
+ await execFileP2("git", ["-C", wtPath, "merge", "--ff-only", base], { timeout: GIT_TIMEOUT_MS });
29749
+ resumed = true;
29750
+ }
29751
+ if (!resumed) {
29752
+ step = `git worktree add ${wtPath}`;
29753
+ await addWorktreeRobust(wtPath, branch, base, {
29754
+ git: async (args) => (await execFileP2("git", args, { timeout: GH_MUTATION_TIMEOUT_MS })).stdout,
29755
+ revParse: async (ref) => {
29756
+ try {
29757
+ return (await execFileP2("git", ["rev-parse", "--verify", ref], { timeout: GIT_TIMEOUT_MS })).stdout.trim() || void 0;
29758
+ } catch {
29759
+ return void 0;
29760
+ }
29761
+ },
29762
+ deleteBranch: (b) => execFileP2("git", ["branch", "-D", b], { timeout: GIT_TIMEOUT_MS }).then(() => void 0),
29763
+ sleep: (ms) => new Promise((resolve5) => setTimeout(resolve5, ms)),
29764
+ log: (m) => {
29765
+ if (!o.json) console.error(` ${m}`);
29900
29766
  }
29901
- },
29902
- deleteBranch: (b) => execFileP2("git", ["branch", "-D", b], { timeout: GIT_TIMEOUT_MS }).then(() => void 0),
29903
- sleep: (ms) => new Promise((resolve5) => setTimeout(resolve5, ms)),
29904
- log: (m) => {
29905
- if (!o.json) console.error(` ${m}`);
29906
- }
29907
- });
29767
+ });
29768
+ }
29908
29769
  step = "install deps + copy local-only config";
29909
29770
  const report = await provisionWorktree(wtPath, makeProvisionDeps(wtPath, Boolean(o.json), (m) => {
29910
29771
  if (!o.json) console.error(` ${m}`);
@@ -29944,12 +29805,13 @@ withExamples(mutating(
29944
29805
  branch,
29945
29806
  path: wtPath,
29946
29807
  base,
29808
+ resumed,
29947
29809
  ...report,
29948
29810
  ...issueForm && selector ? { issue: `${selector.repo}#${selector.number}` } : {},
29949
29811
  ...issueForm && o.claim ? { claim: claimError ? { ok: false, error: claimError } : claim } : {}
29950
29812
  }, null, 2));
29951
29813
  }
29952
- console.log(`worktree ready: ${wtPath} (branch ${branch} from ${base})`);
29814
+ console.log(`worktree ready: ${wtPath} (branch ${branch} ${resumed ? "resumed at" : "from"} ${base})`);
29953
29815
  console.log(` installed: ${report.installed.map((i) => i.dir || ".").join(", ") || "none"}`);
29954
29816
  console.log(` copied: ${report.copied.join(", ") || "none"}`);
29955
29817
  if (issueForm && o.claim && selector) {
@@ -30435,7 +30297,7 @@ project.command("set [owner/repo]").description("upsert project META (idempotent
30435
30297
  if (dupe) return fail(`org project set: KEY "${dupe}" was passed to both --var and --set; --set is an alias of --var, so pass each KEY once`);
30436
30298
  if (o.secretsFile) {
30437
30299
  try {
30438
- vars.push(`secrets=${(0, import_node_fs35.readFileSync)(o.secretsFile, "utf8")}`);
30300
+ vars.push(`secrets=${(0, import_node_fs36.readFileSync)(o.secretsFile, "utf8")}`);
30439
30301
  } catch (e) {
30440
30302
  return fail(`org project set: cannot read --secrets-file ${o.secretsFile}: ${e.message}`);
30441
30303
  }
@@ -31183,11 +31045,11 @@ pr.command("view <number>").description("read a PR as structured JSON (merged st
31183
31045
  }
31184
31046
  });
31185
31047
  async function listCiWorkflowPaths(cwd = process.cwd()) {
31186
- const wfDir = (0, import_node_path34.join)(cwd, ".github", "workflows");
31187
- if (!(0, import_node_fs35.existsSync)(wfDir)) return [];
31188
- return (0, import_node_fs35.readdirSync)(wfDir).filter((name) => /\.(ya?ml)$/i.test(name)).filter((name) => {
31048
+ const wfDir = (0, import_node_path35.join)(cwd, ".github", "workflows");
31049
+ if (!(0, import_node_fs36.existsSync)(wfDir)) return [];
31050
+ return (0, import_node_fs36.readdirSync)(wfDir).filter((name) => /\.(ya?ml)$/i.test(name)).filter((name) => {
31189
31051
  try {
31190
- return workflowReportsPrChecks((0, import_node_fs35.readFileSync)((0, import_node_path34.join)(wfDir, name), "utf8"));
31052
+ return workflowReportsPrChecks((0, import_node_fs36.readFileSync)((0, import_node_path35.join)(wfDir, name), "utf8"));
31191
31053
  } catch {
31192
31054
  return true;
31193
31055
  }
@@ -31219,16 +31081,16 @@ function ciAuditDeps() {
31219
31081
  // gate re-seed step is skipped gracefully rather than failing mid-run.
31220
31082
  readSeedFile: (path2) => {
31221
31083
  if (!root) return null;
31222
- const fullPath = (0, import_node_path34.join)(root, path2);
31223
- return (0, import_node_fs35.existsSync)(fullPath) ? (0, import_node_fs35.readFileSync)(fullPath, "utf8") : null;
31084
+ const fullPath = (0, import_node_path35.join)(root, path2);
31085
+ return (0, import_node_fs36.existsSync)(fullPath) ? (0, import_node_fs36.readFileSync)(fullPath, "utf8") : null;
31224
31086
  }
31225
31087
  };
31226
31088
  }
31227
31089
  function hubRoot() {
31228
- const fromPkg = (0, import_node_path34.join)(__dirname, "..", "..");
31090
+ const fromPkg = (0, import_node_path35.join)(__dirname, "..", "..");
31229
31091
  const marker = "skills/bootstrap/seeds/manifest.json";
31230
- if ((0, import_node_fs35.existsSync)((0, import_node_path34.join)(fromPkg, marker))) return fromPkg;
31231
- if ((0, import_node_fs35.existsSync)((0, import_node_path34.join)(process.cwd(), marker))) return process.cwd();
31092
+ if ((0, import_node_fs36.existsSync)((0, import_node_path35.join)(fromPkg, marker))) return fromPkg;
31093
+ if ((0, import_node_fs36.existsSync)((0, import_node_path35.join)(process.cwd(), marker))) return process.cwd();
31232
31094
  return null;
31233
31095
  }
31234
31096
  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) => {
@@ -31357,6 +31219,10 @@ pr.command("land <number>").description("agent merge path (#1440): train probe \
31357
31219
  return false;
31358
31220
  }
31359
31221
  });
31222
+ if (result.status !== "failed") {
31223
+ const repoFlag = result.repo ? ` --repo ${result.repo}` : "";
31224
+ console.warn(`pr land: merge confirmed; cleanup pending \u2014 if this process is interrupted, resume with: mmi-cli pr merge ${number}${repoFlag} --squash`);
31225
+ }
31360
31226
  if (result.status !== "failed") {
31361
31227
  try {
31362
31228
  const { stdout } = await execFileP2(process.execPath, [
@@ -31531,7 +31397,7 @@ jsonParity(pr.command("merge <number>").description("merge a PR (squash by defau
31531
31397
  localCleanup = await cleanupPrMergeLocalBranch(headRef, {
31532
31398
  beforeWorktrees,
31533
31399
  startingPath,
31534
- pathExists: (p) => (0, import_node_fs35.existsSync)(p),
31400
+ pathExists: (p) => (0, import_node_fs36.existsSync)(p),
31535
31401
  execGit: async (args) => (await execFileP2("git", args, { timeout: GIT_TIMEOUT_MS })).stdout,
31536
31402
  teardownWorktreeStage,
31537
31403
  deferredStore,
@@ -32006,26 +31872,25 @@ access.command("audit").description("audit collaborator roles + train-branch pus
32006
31872
  targets = resolution.targets;
32007
31873
  }
32008
31874
  const derivedMatrix = registryProjects ? accessMatrixFromProjects(registryProjects) : {};
32009
- const fileMatrix = (0, import_node_fs35.existsSync)("access-matrix.json") ? loadAccessMatrix((0, import_node_fs35.readFileSync)("access-matrix.json", "utf8")) : {};
31875
+ const fileMatrix = (0, import_node_fs36.existsSync)("access-matrix.json") ? loadAccessMatrix((0, import_node_fs36.readFileSync)("access-matrix.json", "utf8")) : {};
32010
31876
  const matrix = mergeAccessMatrix(fileMatrix, derivedMatrix);
32011
31877
  const derivedContracts = registryProjects ? dataAccessContractsFromProjects(registryProjects) : { consumers: {} };
32012
- const fileContracts = (0, import_node_fs35.existsSync)("data-access-contracts.json") ? loadDataAccessContracts((0, import_node_fs35.readFileSync)("data-access-contracts.json", "utf8")) : { consumers: {} };
31878
+ const fileContracts = (0, import_node_fs36.existsSync)("data-access-contracts.json") ? loadDataAccessContracts((0, import_node_fs36.readFileSync)("data-access-contracts.json", "utf8")) : { consumers: {} };
32013
31879
  const dataAccess = mergeDataAccessContracts(fileContracts, derivedContracts);
32014
- const sanctioned = (0, import_node_fs35.existsSync)("access-matrix.json") ? loadSanctionedAdmins((0, import_node_fs35.readFileSync)("access-matrix.json", "utf8")) : {};
31880
+ const sanctioned = (0, import_node_fs36.existsSync)("access-matrix.json") ? loadSanctionedAdmins((0, import_node_fs36.readFileSync)("access-matrix.json", "utf8")) : {};
32015
31881
  const report = await auditOrgAccess(targets, deps, matrix, dataAccess, sanctioned);
32016
31882
  console.log(o.json ? JSON.stringify(report, null, 2) : renderAccessReport(report));
32017
31883
  if (!report.ok) process.exitCode = 1;
32018
31884
  });
32019
31885
  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
31886
  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) => {
31887
+ 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
31888
  if (opts.guide) {
32023
31889
  consoleIo.log("MMI Agentic Onboarding: docs/Architecture/agentic-dev-environment.md");
32024
31890
  return;
32025
31891
  }
32026
31892
  process.exitCode = await runDoctorClean(
32027
31893
  {
32028
- apply: Boolean(opts.apply),
32029
31894
  repoWrites: opts.repoWrites,
32030
31895
  banner: opts.banner,
32031
31896
  preflight: opts.preflight,
@@ -32044,16 +31909,16 @@ function directoryBytes(path2) {
32044
31909
  let total = 0;
32045
31910
  let entries;
32046
31911
  try {
32047
- entries = (0, import_node_fs35.readdirSync)(path2, { withFileTypes: true });
31912
+ entries = (0, import_node_fs36.readdirSync)(path2, { withFileTypes: true });
32048
31913
  } catch {
32049
31914
  return 0;
32050
31915
  }
32051
31916
  for (const entry of entries) {
32052
- const child2 = (0, import_node_path34.join)(path2, entry.name);
31917
+ const child2 = (0, import_node_path35.join)(path2, entry.name);
32053
31918
  if (entry.isDirectory()) total += directoryBytes(child2);
32054
31919
  else {
32055
31920
  try {
32056
- total += (0, import_node_fs35.statSync)(child2).size;
31921
+ total += (0, import_node_fs36.statSync)(child2).size;
32057
31922
  } catch {
32058
31923
  }
32059
31924
  }
@@ -32061,25 +31926,25 @@ function directoryBytes(path2) {
32061
31926
  return total;
32062
31927
  }
32063
31928
  function listDirEntries(dir) {
32064
- return (0, import_node_fs35.readdirSync)(dir, { withFileTypes: true }).map((d) => ({ name: d.name, isDirectory: d.isDirectory() }));
31929
+ return (0, import_node_fs36.readdirSync)(dir, { withFileTypes: true }).map((d) => ({ name: d.name, isDirectory: d.isDirectory() }));
32065
31930
  }
32066
31931
  function readInstalledPluginRefs(configRoot) {
32067
31932
  const p = installedPluginsPathForConfig(configRoot);
32068
- if (!(0, import_node_fs35.existsSync)(p)) return [];
31933
+ if (!(0, import_node_fs36.existsSync)(p)) return [];
32069
31934
  try {
32070
- return installedPluginPaths((0, import_node_fs35.readFileSync)(p, "utf8"));
31935
+ return installedPluginPaths((0, import_node_fs36.readFileSync)(p, "utf8"));
32071
31936
  } catch {
32072
31937
  return null;
32073
31938
  }
32074
31939
  }
32075
31940
  function pluginCacheFsDeps(configRoot, dirBytes) {
32076
31941
  return {
32077
- exists: (p) => (0, import_node_fs35.existsSync)(p),
32078
- listVersionDirs: (root) => (0, import_node_fs35.readdirSync)(root, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name),
31942
+ exists: (p) => (0, import_node_fs36.existsSync)(p),
31943
+ listVersionDirs: (root) => (0, import_node_fs36.readdirSync)(root, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name),
32079
31944
  dirBytes,
32080
- listStagingDirs: (root) => (0, import_node_fs35.readdirSync)(root, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => {
31945
+ listStagingDirs: (root) => (0, import_node_fs36.readdirSync)(root, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => {
32081
31946
  try {
32082
- return { name: d.name, mtimeMs: newestMtimeMs((0, import_node_path34.join)(root, d.name), listDirEntries, (p) => (0, import_node_fs35.statSync)(p).mtimeMs) };
31947
+ return { name: d.name, mtimeMs: newestMtimeMs((0, import_node_path35.join)(root, d.name), listDirEntries, (p) => (0, import_node_fs36.statSync)(p).mtimeMs) };
32083
31948
  } catch {
32084
31949
  return { name: d.name, mtimeMs: Date.now() };
32085
31950
  }
@@ -32093,10 +31958,10 @@ function stagingApplyFsGuard(configRoot) {
32093
31958
  return {
32094
31959
  referencedPaths: () => readInstalledPluginRefs(configRoot),
32095
31960
  mtimeMs: (name) => {
32096
- const p = (0, import_node_path34.join)(stagingRoot, name);
32097
- if (!(0, import_node_fs35.existsSync)(p)) return null;
31961
+ const p = (0, import_node_path35.join)(stagingRoot, name);
31962
+ if (!(0, import_node_fs36.existsSync)(p)) return null;
32098
31963
  try {
32099
- return newestMtimeMs(p, listDirEntries, (q) => (0, import_node_fs35.statSync)(q).mtimeMs);
31964
+ return newestMtimeMs(p, listDirEntries, (q) => (0, import_node_fs36.statSync)(q).mtimeMs);
32100
31965
  } catch {
32101
31966
  return null;
32102
31967
  }
@@ -32116,13 +31981,13 @@ program2.command("plugin-prune").description(`prune stale cached MMI plugin vers
32116
31981
  return;
32117
31982
  }
32118
31983
  const plan = buildPluginCachePlan(
32119
- (0, import_node_os13.homedir)(),
31984
+ (0, import_node_os14.homedir)(),
32120
31985
  running,
32121
31986
  pluginCacheFsDeps(configRoot, directoryBytes),
32122
31987
  { withBytes: true, configRoot, includeStaging: surface !== "codex" }
32123
31988
  );
32124
31989
  const anythingToDelete = plan.prune.length > 0 || plan.staging.length > 0;
32125
- const result = o.apply && anythingToDelete ? applyPluginCachePlan(plan, (p) => (0, import_node_fs35.rmSync)(p, { recursive: true, force: true }), stagingApplyFsGuard(configRoot)) : void 0;
31990
+ const result = o.apply && anythingToDelete ? applyPluginCachePlan(plan, (p) => (0, import_node_fs36.rmSync)(p, { recursive: true, force: true }), stagingApplyFsGuard(configRoot)) : void 0;
32126
31991
  const warnings = plan.prune.length > 0 ? [CONCURRENT_SESSION_WARNING] : [];
32127
31992
  if (o.json) console.log(JSON.stringify({ ...plan, warnings, applied: result ?? null }));
32128
31993
  else console.log(renderPluginCachePlan(plan, result));