@mutmutco/cli 3.82.0 → 3.84.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 +493 -269
  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
@@ -6431,7 +6431,14 @@ async function provisionWorktree(worktreeRoot, deps) {
6431
6431
  });
6432
6432
  const allDirs = scanInstallDirs(worktreeRoot, fs2);
6433
6433
  const targets = npmInstallTargets(allDirs);
6434
- 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);
6435
6442
  const installed = [];
6436
6443
  for (const target of targets) {
6437
6444
  const cwd = target.dir ? (0, import_node_path10.join)(worktreeRoot, target.dir) : worktreeRoot;
@@ -6595,7 +6602,7 @@ function commandLadderHint() {
6595
6602
  }
6596
6603
 
6597
6604
  // src/index.ts
6598
- var import_node_path34 = require("node:path");
6605
+ var import_node_path35 = require("node:path");
6599
6606
 
6600
6607
  // src/merge-ci-policy.ts
6601
6608
  function resolveMergeCiPolicy(input) {
@@ -8027,7 +8034,7 @@ async function foldReleaseVersion(deps, model, tag, foldPaths) {
8027
8034
  if (foldPaths.length === 0) return "no version manifest to fold \u2014 the tag is the version";
8028
8035
  const version = tag.replace(/^v/, "");
8029
8036
  if (model === "hub-serverless") {
8030
- await deps.run("node", ["scripts/release-distribution.mjs", "prepare", version]);
8037
+ await deps.run("node", ["scripts/release-distribution.mjs", "prepare", version, "--source-commit", "HEAD"]);
8031
8038
  } else {
8032
8039
  await installAppFoldDeps(deps);
8033
8040
  await deps.run("npm", ["version", version, "--no-git-tag-version", "--allow-same-version"]);
@@ -8617,16 +8624,33 @@ function partialTrainRecoveryError(cause, input) {
8617
8624
  const resumeCommand = input.stage === "rc" ? "mmi-cli rcand --resume" : "mmi-cli release --resume";
8618
8625
  const tenant2 = input.deployModel === "tenant-container";
8619
8626
  const deployLine = tenant2 ? ` - deploy: dispatched by the train after the branch push; recover with \`mmi-cli runtime tenant redeploy ${input.repo} ${input.stage} --watch\` once the branch and release exist.` : ` - deploy: this repo is \`${input.deployModel ?? "not tenant-container"}\` \u2014 it has NO tenant runtime. Its own release/main-push workflows publish and deploy once the branch and Release exist. Do NOT run the tenant redeployer.`;
8620
- return new Error(
8621
- `${causeMessage}
8622
-
8623
- partial train state for ${input.tag}:
8627
+ const stateBlock = `partial train state for ${input.tag}:
8624
8628
  - tag ${input.tag}: PUSHED to origin
8625
8629
  - origin/${branch}: NOT pushed
8626
8630
  - ${releaseState}
8627
- ` + deployLine + `
8631
+ ` + deployLine;
8632
+ const failedHubMainGate = input.stage === "main" && input.repo.toLowerCase() === HUB_REPO.toLowerCase() && /^required train check failed:/i.test(causeMessage);
8633
+ if (failedHubMainGate) {
8634
+ return new Error(
8635
+ `${causeMessage}
8636
+
8637
+ ` + stateBlock + `
8638
+
8639
+ The tag is public and immutable, but it FAILED a required gate. Do not resume it unchanged: \`${resumeCommand}\` cannot alter tagged code or make the failed check pass.
8628
8640
 
8629
- The tag is public and correct \u2014 do not delete or force-move it.
8641
+ Diagnose the failed check, then choose the proven recovery:
8642
+ - transient workflow failure: rerun the failed tag workflow; only after it passes, use \`${resumeCommand}\`.
8643
+ - tagged-code defect: obtain fresh destructive approval to remove ${input.tag}, run \`mmi-cli release --abort --apply\`, repair development, and cut the release again through the guarded train.
8644
+
8645
+ Do not push main or create the GitHub Release by hand.`
8646
+ );
8647
+ }
8648
+ return new Error(
8649
+ `${causeMessage}
8650
+
8651
+ ` + stateBlock + `
8652
+
8653
+ The tag is public and immutable \u2014 do not delete or force-move it.
8630
8654
 
8631
8655
  Resume it: ${resumeCommand}
8632
8656
 
@@ -11683,10 +11707,13 @@ async function executeWaveLand(plan, deps, opts = { preserveWorktree: true }) {
11683
11707
  }
11684
11708
 
11685
11709
  // src/index.ts
11686
- var import_node_os13 = require("node:os");
11710
+ var import_node_os14 = require("node:os");
11687
11711
 
11688
11712
  // src/board.ts
11689
11713
  var import_node_child_process8 = require("node:child_process");
11714
+ var import_node_fs20 = require("node:fs");
11715
+ var import_node_os7 = require("node:os");
11716
+ var import_node_path18 = require("node:path");
11690
11717
  var import_node_util6 = require("node:util");
11691
11718
 
11692
11719
  // src/board-priority.ts
@@ -14841,6 +14868,25 @@ function applyOrgMarketplacePins(path2, names) {
14841
14868
  "pin"
14842
14869
  );
14843
14870
  }
14871
+ function writeMarketplacePinPending(path2, names, now = Date.now()) {
14872
+ try {
14873
+ (0, import_node_fs18.mkdirSync)((0, import_node_path16.dirname)(path2), { recursive: true });
14874
+ (0, import_node_fs18.writeFileSync)(path2, `${JSON.stringify({ v: 1, names: [...names], at: new Date(now).toISOString() })}
14875
+ `, "utf8");
14876
+ } catch {
14877
+ }
14878
+ }
14879
+ function readMarketplacePinPending(path2, name, now = Date.now()) {
14880
+ try {
14881
+ const parsed = JSON.parse((0, import_node_fs18.readFileSync)(path2, "utf8"));
14882
+ const at = typeof parsed.at === "string" ? Date.parse(parsed.at) : Number.NaN;
14883
+ if (parsed.v !== 1 || !Array.isArray(parsed.names) || !parsed.names.includes(name) || !Number.isFinite(at)) return void 0;
14884
+ if (now - at < 0 || now - at > 12 * 60 * 6e4) return void 0;
14885
+ return { v: 1, names: parsed.names.filter((n) => typeof n === "string"), at: parsed.at };
14886
+ } catch {
14887
+ return void 0;
14888
+ }
14889
+ }
14844
14890
  function restoredPinsLine(pins) {
14845
14891
  const named = [...pins].map(([name, want]) => {
14846
14892
  const fields = [
@@ -16129,6 +16175,38 @@ async function postClaimMarkerComment(client, item) {
16129
16175
  `);
16130
16176
  }
16131
16177
  }
16178
+ var CLAIM_SESSION_ACTIVITY_MS = 30 * 6e4;
16179
+ var CLAIM_SESSION_PROBE_CACHE_MS = 6e4;
16180
+ var claimSessionProbeCache = /* @__PURE__ */ new Map();
16181
+ function probeLocalClaimSession(marker, now = Date.now()) {
16182
+ if (!marker.session || !marker.host || marker.host.toLowerCase() !== (0, import_node_os7.hostname)().toLowerCase()) return void 0;
16183
+ if (!marker.surface?.toLowerCase().startsWith("claude")) return void 0;
16184
+ const cacheKey = `${marker.host.toLowerCase()}/${marker.session}`;
16185
+ const cached = claimSessionProbeCache.get(cacheKey);
16186
+ if (cached && now - cached.checkedAt <= CLAIM_SESSION_PROBE_CACHE_MS) return cached.state;
16187
+ const remember = (state) => {
16188
+ claimSessionProbeCache.set(cacheKey, { checkedAt: now, state });
16189
+ return state;
16190
+ };
16191
+ const root = (0, import_node_path18.join)((0, import_node_os7.homedir)(), ".claude", "projects");
16192
+ try {
16193
+ const wanted = `${marker.session}.jsonl`.toLowerCase();
16194
+ const pending = [root];
16195
+ while (pending.length) {
16196
+ const dir = pending.pop();
16197
+ for (const entry of (0, import_node_fs20.readdirSync)(dir, { withFileTypes: true })) {
16198
+ const path2 = (0, import_node_path18.join)(dir, entry.name);
16199
+ if (entry.isDirectory()) pending.push(path2);
16200
+ else if (entry.isFile() && entry.name.toLowerCase() === wanted) {
16201
+ return remember(now - (0, import_node_fs20.statSync)(path2).mtimeMs <= CLAIM_SESSION_ACTIVITY_MS ? "live" : "dead");
16202
+ }
16203
+ }
16204
+ }
16205
+ return remember("dead");
16206
+ } catch {
16207
+ return remember("unverifiable");
16208
+ }
16209
+ }
16132
16210
  function openPullsFetcher(client) {
16133
16211
  const byRepo = /* @__PURE__ */ new Map();
16134
16212
  return (repo) => {
@@ -16146,6 +16224,11 @@ async function gatherClaimLiveness(client, repo, number, fetchOpenPulls) {
16146
16224
  const comments = await client.restPaginate(`repos/${repo}/issues/${number}/comments`);
16147
16225
  out.marker = latestClaimMarker(comments.map((comment) => ({ body: comment.body ?? "" })));
16148
16226
  out.markerAgeMs = out.marker ? Date.now() - Date.parse(out.marker.ts) : void 0;
16227
+ if (out.marker) {
16228
+ const state = probeLocalClaimSession(out.marker);
16229
+ if (state === "live" || state === "dead") out.sessionState = state;
16230
+ else if (state === "unverifiable") out.failed.push("session");
16231
+ }
16149
16232
  } catch {
16150
16233
  out.failed.push("comments");
16151
16234
  }
@@ -16177,8 +16260,10 @@ async function gatherClaimLiveness(client, repo, number, fetchOpenPulls) {
16177
16260
  }
16178
16261
  function liveEvidenceLines(evidence, repo) {
16179
16262
  const live = [];
16180
- if (evidence.marker && evidence.markerAgeMs !== void 0 && evidence.markerAgeMs < CLAIM_MARKER_LIVE_MS) {
16181
- live.push(`claim marker from ${describeClaimMarker(evidence.marker)} is only ${formatClaimAge(evidence.markerAgeMs)} old`);
16263
+ if (evidence.marker && evidence.sessionState === "live") {
16264
+ live.push(`claim session ${describeClaimMarker(evidence.marker)} has recent local transcript activity`);
16265
+ } else if (evidence.marker && evidence.sessionState !== "dead" && 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 (session not locally probeable)`);
16182
16267
  }
16183
16268
  if (evidence.openPr) live.push(evidence.openPr);
16184
16269
  if (evidence.branch) live.push(`live branch ${evidence.branch} on ${repo}`);
@@ -16274,9 +16359,9 @@ async function boardDoctor(options, deps = {}) {
16274
16359
  while (next < claimedItems.length) {
16275
16360
  const item = claimedItems[next++];
16276
16361
  const evidence = await gatherClaimLiveness(client, item.repository, item.number, fetchOpenPulls);
16277
- const freshMarker = evidence.markerAgeMs !== void 0 && evidence.markerAgeMs < CLAIM_MARKER_LIVE_MS;
16362
+ const freshMarker = evidence.sessionState === "live" || evidence.sessionState !== "dead" && evidence.markerAgeMs !== void 0 && evidence.markerAgeMs < CLAIM_MARKER_LIVE_MS;
16278
16363
  const bits = [
16279
- evidence.marker ? `claim marker ${describeClaimMarker(evidence.marker)} ${formatClaimAge(evidence.markerAgeMs)} old` : evidence.failed.includes("comments") ? "claim marker unreadable" : "no claim marker",
16364
+ 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",
16280
16365
  evidence.openPr,
16281
16366
  evidence.branch ? `branch ${evidence.branch}` : void 0
16282
16367
  ].filter((bit) => Boolean(bit)).join("; ");
@@ -16380,7 +16465,7 @@ async function resolveAutoAddBoardAttach(client, cfg, selector, priority, warn =
16380
16465
  }
16381
16466
 
16382
16467
  // src/issue-body.ts
16383
- var import_node_os7 = require("node:os");
16468
+ var import_node_os8 = require("node:os");
16384
16469
  var TextArgError = class extends Error {
16385
16470
  constructor(message, code, offendingFlag) {
16386
16471
  super(message);
@@ -16392,7 +16477,7 @@ var TextArgError = class extends Error {
16392
16477
  offendingFlag;
16393
16478
  };
16394
16479
  function emptyStdinMessage(fileFlag) {
16395
- if ((0, import_node_os7.platform)() === "win32") {
16480
+ if ((0, import_node_os8.platform)() === "win32") {
16396
16481
  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)`;
16397
16482
  }
16398
16483
  return `${fileFlag} - read empty stdin (nothing piped \u2014 pass a heredoc/pipe, or ${fileFlag} <path>)`;
@@ -17717,7 +17802,7 @@ async function runHotfixStart(deps, options) {
17717
17802
  }
17718
17803
  notes.push(`cherry-picked ${label} onto ${branch} (from origin/main, -x trailer recorded)`);
17719
17804
  if (deployModel === "hub-serverless") {
17720
- await deps.run("node", ["scripts/release-distribution.mjs", "prepare", version]);
17805
+ await deps.run("node", ["scripts/release-distribution.mjs", "prepare", version, "--source-commit", "HEAD"]);
17721
17806
  const changedFiles = (await deps.run("node", ["scripts/release-distribution.mjs", "changed-files"])).split("\n").map((s) => s.trim()).filter(Boolean);
17722
17807
  await deps.run("git", ["add", "-f", "--", ...changedFiles]);
17723
17808
  const staged = await deps.run("git", ["diff", "--cached", "--name-only"]);
@@ -18575,8 +18660,8 @@ function renderAccessReport(report) {
18575
18660
 
18576
18661
  // src/doc-refs-core.ts
18577
18662
  var import_node_child_process10 = require("node:child_process");
18578
- var import_node_fs20 = require("node:fs");
18579
- var import_node_path18 = require("node:path");
18663
+ var import_node_fs21 = require("node:fs");
18664
+ var import_node_path19 = require("node:path");
18580
18665
  var PIN_RE = /<!--\s*pinned by\s+([^:]+?)\s*:\s*"([^"]+)"[^"]*-->/;
18581
18666
  var PIN_MENTION_RE = /<!--\s*pinned by\b/;
18582
18667
  var FWD_RE = /<!--\s*forward-ref:\s*(\S+?)\s*-->/;
@@ -18632,7 +18717,7 @@ function checkPins(root, readFile9, docs2) {
18632
18717
  findings.push({ kind: "malformed-pin", doc, line: pin.line, detail: pin.text });
18633
18718
  continue;
18634
18719
  }
18635
- const source = readFile9((0, import_node_path18.join)(root, pin.file));
18720
+ const source = readFile9((0, import_node_path19.join)(root, pin.file));
18636
18721
  if (source == null) {
18637
18722
  findings.push({ kind: "missing-test", doc, line: pin.line, detail: pin.file });
18638
18723
  continue;
@@ -18692,7 +18777,7 @@ function checkRefs(root, deps, docs2) {
18692
18777
  const candidates = [];
18693
18778
  const direct = [];
18694
18779
  for (const [doc, markdown] of Object.entries(docs2)) {
18695
- const docDir = import_node_path18.posix.dirname(doc);
18780
+ const docDir = import_node_path19.posix.dirname(doc);
18696
18781
  const base = docDir === "." ? "" : docDir;
18697
18782
  const covered = /* @__PURE__ */ new Set();
18698
18783
  const markers = [];
@@ -18701,21 +18786,21 @@ function checkRefs(root, deps, docs2) {
18701
18786
  direct.push({ kind: "malformed-forward-ref", doc, line: fwd.line, detail: fwd.text });
18702
18787
  continue;
18703
18788
  }
18704
- const docRel = import_node_path18.posix.normalize(import_node_path18.posix.join(base, fwd.target));
18705
- const rootRel = import_node_path18.posix.normalize(fwd.target.replace(/^\/+/, ""));
18789
+ const docRel = import_node_path19.posix.normalize(import_node_path19.posix.join(base, fwd.target));
18790
+ const rootRel = import_node_path19.posix.normalize(fwd.target.replace(/^\/+/, ""));
18706
18791
  markers.push({ target: fwd.target, line: fwd.line, docRel, rootRel });
18707
18792
  covered.add(docRel);
18708
18793
  covered.add(rootRel);
18709
18794
  }
18710
18795
  const links = extractLinks(markdown).map(({ target, line }) => {
18711
- const resolved = import_node_path18.posix.normalize(import_node_path18.posix.join(base, target));
18712
- return { target, line, resolved, missing: !exists((0, import_node_path18.join)(root, resolved)) };
18796
+ const resolved = import_node_path19.posix.normalize(import_node_path19.posix.join(base, target));
18797
+ return { target, line, resolved, missing: !exists((0, import_node_path19.join)(root, resolved)) };
18713
18798
  });
18714
18799
  for (const marker of markers) {
18715
18800
  const coversMissing = links.some(
18716
18801
  (l) => l.missing && (l.resolved === marker.docRel || l.resolved === marker.rootRel)
18717
18802
  );
18718
- if (!coversMissing && (exists((0, import_node_path18.join)(root, marker.docRel)) || exists((0, import_node_path18.join)(root, marker.rootRel)))) {
18803
+ if (!coversMissing && (exists((0, import_node_path19.join)(root, marker.docRel)) || exists((0, import_node_path19.join)(root, marker.rootRel)))) {
18719
18804
  direct.push({
18720
18805
  kind: "stale-forward-ref",
18721
18806
  doc,
@@ -18726,8 +18811,8 @@ function checkRefs(root, deps, docs2) {
18726
18811
  }
18727
18812
  for (const { ref, line } of extractRefs(markdown)) {
18728
18813
  const first = ref.split("/")[0];
18729
- if (!exists((0, import_node_path18.join)(root, first))) continue;
18730
- if (!exists((0, import_node_path18.join)(root, ref))) candidates.push({ kind: "missing-path", doc, line, detail: ref });
18814
+ if (!exists((0, import_node_path19.join)(root, first))) continue;
18815
+ if (!exists((0, import_node_path19.join)(root, ref))) candidates.push({ kind: "missing-path", doc, line, detail: ref });
18731
18816
  }
18732
18817
  for (const { target, line, resolved, missing } of links) {
18733
18818
  if (resolved.startsWith("..")) {
@@ -18775,22 +18860,22 @@ function checkCommands(docs2, commandPaths) {
18775
18860
  return { ok: findings.length === 0, findings, warnings: [] };
18776
18861
  }
18777
18862
  function readFileOrNull(path2) {
18778
- return (0, import_node_fs20.existsSync)(path2) ? (0, import_node_fs20.readFileSync)(path2, "utf8") : null;
18863
+ return (0, import_node_fs21.existsSync)(path2) ? (0, import_node_fs21.readFileSync)(path2, "utf8") : null;
18779
18864
  }
18780
18865
  function walk(dir, root, out) {
18781
- for (const entry of (0, import_node_fs20.readdirSync)(dir)) {
18782
- const full = (0, import_node_path18.join)(dir, entry);
18783
- if ((0, import_node_fs20.statSync)(full).isDirectory()) walk(full, root, out);
18866
+ for (const entry of (0, import_node_fs21.readdirSync)(dir)) {
18867
+ const full = (0, import_node_path19.join)(dir, entry);
18868
+ if ((0, import_node_fs21.statSync)(full).isDirectory()) walk(full, root, out);
18784
18869
  else if (entry.endsWith(".md")) out.push(full.slice(root.length + 1).replaceAll("\\", "/"));
18785
18870
  }
18786
18871
  return out;
18787
18872
  }
18788
18873
  function defaultListDocs(root) {
18789
- const docsDir = (0, import_node_path18.join)(root, "docs");
18790
- const docs2 = ((0, import_node_fs20.existsSync)(docsDir) ? walk(docsDir, root, []) : []).filter(
18874
+ const docsDir = (0, import_node_path19.join)(root, "docs");
18875
+ const docs2 = ((0, import_node_fs21.existsSync)(docsDir) ? walk(docsDir, root, []) : []).filter(
18791
18876
  (rel) => !SKIP_WALK.some((skip) => rel.startsWith(skip))
18792
18877
  );
18793
- return [...ROOT_DOCS.filter((rel) => (0, import_node_fs20.existsSync)((0, import_node_path18.join)(root, rel))), ...docs2];
18878
+ return [...ROOT_DOCS.filter((rel) => (0, import_node_fs21.existsSync)((0, import_node_path19.join)(root, rel))), ...docs2];
18794
18879
  }
18795
18880
  function defaultIsIgnored(root, relPaths, exec = import_node_child_process10.execFileSync) {
18796
18881
  const inRepo = relPaths.filter((p) => !p.startsWith(".."));
@@ -18810,14 +18895,14 @@ function defaultIsIgnored(root, relPaths, exec = import_node_child_process10.exe
18810
18895
  }
18811
18896
  function runDocRefs(root, deps = {}) {
18812
18897
  const readFile9 = deps.readFile ?? readFileOrNull;
18813
- const exists = deps.exists ?? import_node_fs20.existsSync;
18898
+ const exists = deps.exists ?? import_node_fs21.existsSync;
18814
18899
  const listDocs = deps.listDocs ?? defaultListDocs;
18815
18900
  const isIgnored = deps.isIgnored ?? ((paths) => defaultIsIgnored(root, paths));
18816
18901
  const commandPaths = deps.commandPaths ?? null;
18817
18902
  const walked = listDocs(root);
18818
18903
  const ignoredDocs = walked.length ? isIgnored(walked) : /* @__PURE__ */ new Set();
18819
18904
  const docs2 = Object.fromEntries(
18820
- walked.filter((rel) => !ignoredDocs.has(rel)).map((rel) => [rel, readFile9((0, import_node_path18.join)(root, rel))]).filter(([, body]) => body != null)
18905
+ walked.filter((rel) => !ignoredDocs.has(rel)).map((rel) => [rel, readFile9((0, import_node_path19.join)(root, rel))]).filter(([, body]) => body != null)
18821
18906
  );
18822
18907
  const refResult = checkRefs(root, { exists, isIgnored }, docs2);
18823
18908
  const findings = [
@@ -18844,8 +18929,8 @@ function runDocRefs(root, deps = {}) {
18844
18929
 
18845
18930
  // src/spawn-policy-core.ts
18846
18931
  var import_node_child_process11 = require("node:child_process");
18847
- var import_node_fs21 = require("node:fs");
18848
- var import_node_path19 = require("node:path");
18932
+ var import_node_fs22 = require("node:fs");
18933
+ var import_node_path20 = require("node:path");
18849
18934
  var SPAWNERS = ["spawn", "spawnSync", "exec", "execSync", "execFile", "execFileSync"];
18850
18935
  var CALL_SOURCE = String.raw`(^|[^.\w$])(${SPAWNERS.join("|")})\s*\(`;
18851
18936
  var SOURCE_EXT = /\.(ts|mts|cts|js|mjs|cjs)$/;
@@ -18931,7 +19016,7 @@ function runSpawnPolicy(root) {
18931
19016
  for (const file of files) {
18932
19017
  let raw;
18933
19018
  try {
18934
- raw = (0, import_node_fs21.readFileSync)((0, import_node_path19.join)(root, file), "utf8");
19019
+ raw = (0, import_node_fs22.readFileSync)((0, import_node_path20.join)(root, file), "utf8");
18935
19020
  } catch {
18936
19021
  continue;
18937
19022
  }
@@ -18949,8 +19034,8 @@ function runSpawnPolicy(root) {
18949
19034
 
18950
19035
  // src/test-policy-core.ts
18951
19036
  var import_node_child_process12 = require("node:child_process");
18952
- var import_node_fs22 = require("node:fs");
18953
- var import_node_path20 = require("node:path");
19037
+ var import_node_fs23 = require("node:fs");
19038
+ var import_node_path21 = require("node:path");
18954
19039
  var POLICY_FILE = "test-policy.json";
18955
19040
  var TEST_RE = /\.(?:test|spec)\.[cm]?[jt]sx?$/;
18956
19041
  var PY_TEST_RE = /(?:^|\/)test_[^/]*\.py$|_test\.py$/;
@@ -19003,7 +19088,7 @@ function isTestPath(path2) {
19003
19088
  return TEST_RE.test(path2) || PY_TEST_RE.test(path2);
19004
19089
  }
19005
19090
  function loadPolicy(root, readFile9 = readFileOrNull2) {
19006
- const raw = readFile9((0, import_node_path20.join)(root, POLICY_FILE));
19091
+ const raw = readFile9((0, import_node_path21.join)(root, POLICY_FILE));
19007
19092
  if (raw == null) return { mandatory: [], declared: false };
19008
19093
  try {
19009
19094
  return { ...JSON.parse(raw), declared: true };
@@ -19013,7 +19098,7 @@ function loadPolicy(root, readFile9 = readFileOrNull2) {
19013
19098
  }
19014
19099
  function readFileOrNull2(path2) {
19015
19100
  try {
19016
- return (0, import_node_fs22.readFileSync)(path2, "utf8");
19101
+ return (0, import_node_fs23.readFileSync)(path2, "utf8");
19017
19102
  } catch {
19018
19103
  return null;
19019
19104
  }
@@ -19040,12 +19125,12 @@ function classify(changed, policy, present = () => false) {
19040
19125
  const removedProtected = [...removed].filter((p) => protectedBy.has(p)).map((p) => ({ path: p, why: protectedBy.get(p) ?? "" }));
19041
19126
  return { mandatoryHits, untestedHits, testChanges, addedTests, removedProtected };
19042
19127
  }
19043
- function unresolvedProtectedEntries(policy, root, exists = (path2) => (0, import_node_fs22.existsSync)(path2)) {
19044
- return (policy.protected ?? []).map((p) => p.path).filter((p) => !exists((0, import_node_path20.join)(root, p)));
19128
+ function unresolvedProtectedEntries(policy, root, exists = (path2) => (0, import_node_fs23.existsSync)(path2)) {
19129
+ return (policy.protected ?? []).map((p) => p.path).filter((p) => !exists((0, import_node_path21.join)(root, p)));
19045
19130
  }
19046
- function unresolvedSatisfiers(policy, root, exists = (path2) => (0, import_node_fs22.existsSync)(path2)) {
19131
+ function unresolvedSatisfiers(policy, root, exists = (path2) => (0, import_node_fs23.existsSync)(path2)) {
19047
19132
  const declared = (policy.mandatory ?? []).flatMap((m) => m.satisfiedBy ?? []);
19048
- return [...new Set(declared)].filter((p) => !exists((0, import_node_path20.join)(root, p)));
19133
+ return [...new Set(declared)].filter((p) => !exists((0, import_node_path21.join)(root, p)));
19049
19134
  }
19050
19135
  function evaluate(changed, policy, present = () => false) {
19051
19136
  const { mandatoryHits, untestedHits, testChanges, addedTests, removedProtected } = classify(changed, policy, present);
@@ -19227,13 +19312,13 @@ function changedFilesSince(base, cwd) {
19227
19312
  }
19228
19313
  function runTestPolicy(root, deps = {}) {
19229
19314
  const policy = deps.policy ?? loadPolicy(root);
19230
- const exists = deps.exists ?? ((path2) => (0, import_node_fs22.existsSync)(path2));
19315
+ const exists = deps.exists ?? ((path2) => (0, import_node_fs23.existsSync)(path2));
19231
19316
  const counts = { mandatoryCount: (policy.mandatory ?? []).length, protectedCount: (policy.protected ?? []).length };
19232
19317
  const base = deps.changed ? "(injected)" : resolveBase(root, deps.base);
19233
19318
  const refusal = deps.changed ? null : untrustworthyRange(root, base);
19234
19319
  const changed = deps.changed ?? (refusal ? [] : changedFilesSince(base, root));
19235
19320
  const lookup = deps.override !== void 0 ? { override: deps.override, refusals: [] } : !deps.changed && !refusal ? readOverride(base, root) : { override: null, refusals: [] };
19236
- const present = (path2) => exists((0, import_node_path20.join)(root, path2));
19321
+ const present = (path2) => exists((0, import_node_path21.join)(root, path2));
19237
19322
  const removedByThisDiff = removedPaths(changed);
19238
19323
  const staleFindings = [];
19239
19324
  const unresolved = unresolvedProtectedEntries(policy, root, exists).filter((p) => !removedByThisDiff.has(p));
@@ -19389,8 +19474,8 @@ function docsAuditStatus(fetch2, opts) {
19389
19474
  }
19390
19475
 
19391
19476
  // src/project-info-sync.ts
19392
- var import_node_fs23 = require("node:fs");
19393
- var import_node_path21 = require("node:path");
19477
+ var import_node_fs24 = require("node:fs");
19478
+ var import_node_path22 = require("node:path");
19394
19479
  var UPDATE_PROJECT_INFO = `mutation($projectId: ID!, $shortDescription: String!, $readme: String!) {
19395
19480
  updateProjectV2(input: { projectId: $projectId, shortDescription: $shortDescription, readme: $readme }) {
19396
19481
  projectV2 { id }
@@ -19435,14 +19520,14 @@ function sharedName(entries, fallback) {
19435
19520
  }
19436
19521
  function buildProjectInfoSyncPlan(targetRepo2, project2, projects, repoRoot2) {
19437
19522
  if (!project2.projectId) throw new Error(`org project sync-info: ${targetRepo2} registry META has no projectId`);
19438
- const readmePath = (0, import_node_path21.join)(repoRoot2, "README.md");
19439
- if (!(0, import_node_fs23.existsSync)(readmePath)) throw new Error(`org project sync-info: ${targetRepo2} has no README.md`);
19523
+ const readmePath = (0, import_node_path22.join)(repoRoot2, "README.md");
19524
+ if (!(0, import_node_fs24.existsSync)(readmePath)) throw new Error(`org project sync-info: ${targetRepo2} has no README.md`);
19440
19525
  const entries = entriesFor(project2, projects);
19441
19526
  const memberRepos = [...new Set(entries.flatMap((entry) => entry.repos ?? []))].filter((repo) => /^[^/]+\/[^/]+$/.test(repo)).sort((a, b) => a.localeCompare(b));
19442
19527
  const projectName = sharedName(entries, project2.name?.trim() || targetRepo2.split("/").pop() || targetRepo2);
19443
19528
  if (!memberRepos.length) throw new Error(`org project sync-info: project ${projectName} has no registered member repos`);
19444
19529
  const entryNames = entries.map((entry) => entry.name?.trim()).filter((name) => Boolean(name));
19445
- const shortDescription = memberRepos.length === 1 ? shortDescriptionFromReadme((0, import_node_fs23.readFileSync)(readmePath, "utf8")) : `Shared work across ${new Intl.ListFormat("en", { type: "conjunction" }).format(entryNames)}.`;
19530
+ const shortDescription = memberRepos.length === 1 ? shortDescriptionFromReadme((0, import_node_fs24.readFileSync)(readmePath, "utf8")) : `Shared work across ${new Intl.ListFormat("en", { type: "conjunction" }).format(entryNames)}.`;
19446
19531
  const lines = [
19447
19532
  `# ${projectName}`,
19448
19533
  "",
@@ -19461,8 +19546,8 @@ function buildProjectInfoSyncPlan(targetRepo2, project2, projects, repoRoot2) {
19461
19546
  const targetBase = `https://github.com/${targetRepo2}`;
19462
19547
  const targetBranch = branchFor(targetRepo2, projects);
19463
19548
  const orgDocs = [
19464
- (0, import_node_fs23.existsSync)((0, import_node_path21.join)(repoRoot2, "docs", "org-readme.md")) ? `- [Org identity](${targetBase}/blob/${targetBranch}/docs/org-readme.md)` : "",
19465
- (0, import_node_fs23.existsSync)((0, import_node_path21.join)(repoRoot2, "docs", "org-architecture.md")) ? `- [Org architecture](${targetBase}/blob/${targetBranch}/docs/org-architecture.md)` : ""
19549
+ (0, import_node_fs24.existsSync)((0, import_node_path22.join)(repoRoot2, "docs", "org-readme.md")) ? `- [Org identity](${targetBase}/blob/${targetBranch}/docs/org-readme.md)` : "",
19550
+ (0, import_node_fs24.existsSync)((0, import_node_path22.join)(repoRoot2, "docs", "org-architecture.md")) ? `- [Org architecture](${targetBase}/blob/${targetBranch}/docs/org-architecture.md)` : ""
19466
19551
  ].filter(Boolean);
19467
19552
  if (orgDocs.length) lines.push("", "## Organisation docs", "", ...orgDocs);
19468
19553
  return { projectId: project2.projectId, projectName, targetRepo: targetRepo2, memberRepos, shortDescription, readme: `${lines.join("\n")}
@@ -20321,9 +20406,9 @@ function writeError(res) {
20321
20406
  }
20322
20407
 
20323
20408
  // src/secrets-commands.ts
20324
- var import_node_fs24 = require("node:fs");
20325
- var import_node_path22 = require("node:path");
20326
- var import_node_os8 = require("node:os");
20409
+ var import_node_fs25 = require("node:fs");
20410
+ var import_node_path23 = require("node:path");
20411
+ var import_node_os9 = require("node:os");
20327
20412
 
20328
20413
  // src/project-runtime.ts
20329
20414
  function hasRuntimeSecretContract(contract) {
@@ -20446,18 +20531,18 @@ function collectMap(value, previous = []) {
20446
20531
  return [...previous, value];
20447
20532
  }
20448
20533
  async function decryptRailsCredentials(input) {
20449
- const appDir = (0, import_node_path22.resolve)(input.appDir ?? process.cwd());
20534
+ const appDir = (0, import_node_path23.resolve)(input.appDir ?? process.cwd());
20450
20535
  const credentialsFile = input.credentialsFile ?? DEFAULT_RAILS_CREDENTIALS_FILE;
20451
20536
  const masterKeyFile = input.masterKeyFile ?? DEFAULT_RAILS_MASTER_KEY_FILE;
20452
- const credentialsPath = (0, import_node_path22.resolve)(appDir, credentialsFile);
20453
- const masterKeyPath = (0, import_node_path22.resolve)(appDir, masterKeyFile);
20537
+ const credentialsPath = (0, import_node_path23.resolve)(appDir, credentialsFile);
20538
+ const masterKeyPath = (0, import_node_path23.resolve)(appDir, masterKeyFile);
20454
20539
  const env = {
20455
20540
  ...process.env,
20456
20541
  MMI_RAILS_CREDENTIALS_FILE: credentialsPath,
20457
20542
  MMI_RAILS_MASTER_KEY_FILE: masterKeyPath
20458
20543
  };
20459
- if ((0, import_node_fs24.existsSync)(masterKeyPath)) {
20460
- env.RAILS_MASTER_KEY = (0, import_node_fs24.readFileSync)(masterKeyPath, "utf8").trim();
20544
+ if ((0, import_node_fs25.existsSync)(masterKeyPath)) {
20545
+ env.RAILS_MASTER_KEY = (0, import_node_fs25.readFileSync)(masterKeyPath, "utf8").trim();
20461
20546
  }
20462
20547
  const script = [
20463
20548
  'require "json"',
@@ -20467,9 +20552,9 @@ async function decryptRailsCredentials(input) {
20467
20552
  'config = ActiveSupport::EncryptedConfiguration.new(config_path: config_path, key_path: key_path, env_key: "RAILS_MASTER_KEY", raise_if_missing_key: true)',
20468
20553
  "puts JSON.generate(config.config)"
20469
20554
  ].join("\n");
20470
- const scriptDir = (0, import_node_fs24.mkdtempSync)((0, import_node_path22.join)((0, import_node_os8.tmpdir)(), "mmi-rails-decrypt-"));
20471
- const scriptPath = (0, import_node_path22.join)(scriptDir, "decrypt.rb");
20472
- (0, import_node_fs24.writeFileSync)(scriptPath, script, "utf8");
20555
+ const scriptDir = (0, import_node_fs25.mkdtempSync)((0, import_node_path23.join)((0, import_node_os9.tmpdir)(), "mmi-rails-decrypt-"));
20556
+ const scriptPath = (0, import_node_path23.join)(scriptDir, "decrypt.rb");
20557
+ (0, import_node_fs25.writeFileSync)(scriptPath, script, "utf8");
20473
20558
  try {
20474
20559
  const args = ["exec", "ruby", scriptPath];
20475
20560
  const cmd = process.platform === "win32" ? "cmd.exe" : "bundle";
@@ -20481,7 +20566,7 @@ async function decryptRailsCredentials(input) {
20481
20566
  });
20482
20567
  return JSON.parse(stdout);
20483
20568
  } finally {
20484
- (0, import_node_fs24.rmSync)(scriptDir, { recursive: true, force: true });
20569
+ (0, import_node_fs25.rmSync)(scriptDir, { recursive: true, force: true });
20485
20570
  }
20486
20571
  }
20487
20572
  async function readSecretStdin() {
@@ -20571,7 +20656,7 @@ function registerSecretsCommands(program3) {
20571
20656
  let body;
20572
20657
  if (o.file) {
20573
20658
  try {
20574
- body = (0, import_node_fs24.readFileSync)((0, import_node_path22.resolve)(o.file), "utf8");
20659
+ body = (0, import_node_fs25.readFileSync)((0, import_node_path23.resolve)(o.file), "utf8");
20575
20660
  } catch (e) {
20576
20661
  return fail(`secrets org-catalog: cannot read --file ${o.file}: ${e.message}`);
20577
20662
  }
@@ -20676,7 +20761,7 @@ function registerSecretsCommands(program3) {
20676
20761
  {
20677
20762
  ...d,
20678
20763
  decryptRailsCredentials,
20679
- removeFile: (path2) => (0, import_node_fs24.unlinkSync)((0, import_node_path22.resolve)(o.appDir ?? process.cwd(), path2))
20764
+ removeFile: (path2) => (0, import_node_fs25.unlinkSync)((0, import_node_path23.resolve)(o.appDir ?? process.cwd(), path2))
20680
20765
  },
20681
20766
  {
20682
20767
  repo: o.repo,
@@ -20840,7 +20925,7 @@ async function activateAppActor(commandPath3, env, mint) {
20840
20925
  }
20841
20926
 
20842
20927
  // src/box-commands.ts
20843
- var import_node_fs25 = require("node:fs");
20928
+ var import_node_fs26 = require("node:fs");
20844
20929
 
20845
20930
  // src/box.ts
20846
20931
  var BOX_KEYS = {
@@ -21043,7 +21128,7 @@ function registerBoxCommands(program3) {
21043
21128
  }
21044
21129
  if (o.json) console.log(JSON.stringify({ box: found, incomplete }, null, 2));
21045
21130
  else if (o.ssh && o.script) {
21046
- (0, import_node_fs25.writeFileSync)(o.script, sshRecipeScript(found), "utf8");
21131
+ (0, import_node_fs26.writeFileSync)(o.script, sshRecipeScript(found), "utf8");
21047
21132
  console.log(`wrote ${o.script} \u2014 run: bash "${o.script}"`);
21048
21133
  } else if (o.ssh) console.log(`${formatSshRecipe(found)}
21049
21134
  ${SSH_RECIPE_AGENT_NOTE}`);
@@ -21729,7 +21814,7 @@ function registerSchedulesCommands(program3) {
21729
21814
 
21730
21815
  // src/file-lock.ts
21731
21816
  var import_promises4 = require("node:fs/promises");
21732
- var import_node_path23 = require("node:path");
21817
+ var import_node_path24 = require("node:path");
21733
21818
  var sleep = (ms) => new Promise((resolve5) => setTimeout(resolve5, ms));
21734
21819
  var IMMEDIATE_RETRY_BUDGET = 3;
21735
21820
  var FileLockBusyError = class extends Error {
@@ -21814,7 +21899,7 @@ async function releaseFileLock(lockPath, guard) {
21814
21899
  }
21815
21900
  async function withFileLock(lockPath, opts, fn) {
21816
21901
  const resolved = resolveFileLockOpts(opts);
21817
- await (0, import_promises4.mkdir)((0, import_node_path23.dirname)(lockPath), { recursive: true }).catch(() => void 0);
21902
+ await (0, import_promises4.mkdir)((0, import_node_path24.dirname)(lockPath), { recursive: true }).catch(() => void 0);
21818
21903
  const guard = await acquireFileLock(lockPath, resolved, Date.now() + resolved.maxWaitMs);
21819
21904
  try {
21820
21905
  return await fn();
@@ -21825,7 +21910,7 @@ async function withFileLock(lockPath, opts, fn) {
21825
21910
 
21826
21911
  // src/schedules-lift-command.ts
21827
21912
  var import_promises5 = require("node:fs/promises");
21828
- var import_node_path24 = require("node:path");
21913
+ var import_node_path25 = require("node:path");
21829
21914
 
21830
21915
  // src/schedules-lift.ts
21831
21916
  var SCHEDULE_HEADER_FIELDS = ["schedule", "what", "owner", "cadence", "executor", "llm", "output", "breaks", "kill"];
@@ -21931,7 +22016,7 @@ async function readWorkflowFiles(dir) {
21931
22016
  const files = [];
21932
22017
  for (const name of names.sort()) {
21933
22018
  if (!/\.ya?ml$/.test(name)) continue;
21934
- files.push({ path: `.github/workflows/${name}`, text: await (0, import_promises5.readFile)((0, import_node_path24.join)(dir, name), "utf8") });
22019
+ files.push({ path: `.github/workflows/${name}`, text: await (0, import_promises5.readFile)((0, import_node_path25.join)(dir, name), "utf8") });
21935
22020
  }
21936
22021
  return files;
21937
22022
  }
@@ -22015,13 +22100,13 @@ function registerSchedulesLiftCommand(program3, deps = {}) {
22015
22100
  // src/edge-tunnel.ts
22016
22101
  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])?)+$/;
22017
22102
  var UPSTREAM_RE = /^https?:\/\/[^/\s]+(?::\d+)?(?:\/.*)?$/;
22018
- function tunnelNameFromHostname(hostname2) {
22019
- return hostname2.replace(/\./g, "-").slice(0, 63);
22103
+ function tunnelNameFromHostname(hostname3) {
22104
+ return hostname3.replace(/\./g, "-").slice(0, 63);
22020
22105
  }
22021
- function planInfraTunnel(hostname2, upstream) {
22022
- const host = hostname2.trim().toLowerCase();
22106
+ function planInfraTunnel(hostname3, upstream) {
22107
+ const host = hostname3.trim().toLowerCase();
22023
22108
  const origin = upstream.trim();
22024
- if (!HOSTNAME_RE.test(host)) throw new Error(`invalid hostname ${JSON.stringify(hostname2)}`);
22109
+ if (!HOSTNAME_RE.test(host)) throw new Error(`invalid hostname ${JSON.stringify(hostname3)}`);
22025
22110
  if (!UPSTREAM_RE.test(origin)) throw new Error(`invalid upstream ${JSON.stringify(upstream)} \u2014 expected http(s)://host:port`);
22026
22111
  const tunnelName = tunnelNameFromHostname(host);
22027
22112
  const configYaml = [
@@ -22473,9 +22558,9 @@ function registerQueryCommands(program3) {
22473
22558
  }
22474
22559
 
22475
22560
  // src/bootstrap-commands.ts
22476
- var import_node_fs26 = require("node:fs");
22477
- var import_node_os9 = require("node:os");
22478
- var import_node_path25 = require("node:path");
22561
+ var import_node_fs27 = require("node:fs");
22562
+ var import_node_os10 = require("node:os");
22563
+ var import_node_path26 = require("node:path");
22479
22564
 
22480
22565
  // src/bootstrap-drift.ts
22481
22566
  function byteComparableSeeds(manifest, cls) {
@@ -22981,7 +23066,7 @@ function renderBootstrapVerifyReport(report) {
22981
23066
  // src/bootstrap-org-ruleset.ts
22982
23067
  var ORG_NO_AGENT_FILES_RULESET_NAME = "mmi-no-agent-files-org";
22983
23068
  var ORG_LOGIN = "mutmutco";
22984
- var ORG_NO_AGENT_FILES_EXCLUDED_REPOS = ["Jerv-PowerTools"];
23069
+ var ORG_NO_AGENT_FILES_EXCLUDED_REPOS = ["Jerv-PowerTools", "Jerv-JervCode"];
22985
23070
  var NO_AGENT_FILES_GLOBS = [
22986
23071
  "AGENTS.md",
22987
23072
  "**/AGENTS.md",
@@ -23058,8 +23143,8 @@ OK \u2014 live ruleset matches the codified IaC (${NO_AGENT_FILES_GLOBS.length}
23058
23143
  `${head}`,
23059
23144
  verb,
23060
23145
  ...plan.drift.map((d) => ` - ${d}`),
23061
- "This command is read-only; it never mutates the live ruleset. Recreate/repair via the GitHub UI or a",
23062
- "deliberate POST/PUT of orgNoAgentFilesRulesetDesired() \u2014 see cli/src/bootstrap-org-ruleset.ts."
23146
+ "Read-only preview \u2014 a master-admin may reconcile this exact desired state with",
23147
+ "`mmi-cli bootstrap org-ruleset --apply`."
23063
23148
  ].join("\n");
23064
23149
  }
23065
23150
  async function fetchOrgNoAgentFilesRuleset(client, org = ORG_LOGIN) {
@@ -23068,6 +23153,27 @@ async function fetchOrgNoAgentFilesRuleset(client, org = ORG_LOGIN) {
23068
23153
  if (summary?.id == null) return null;
23069
23154
  return await client.rest("GET", `orgs/${org}/rulesets/${summary.id}`, { timeoutMs: 2e4 }) ?? null;
23070
23155
  }
23156
+ async function reconcileOrgNoAgentFilesRuleset(plan, client, org = ORG_LOGIN) {
23157
+ if (plan.action === "noop") {
23158
+ return { action: "noop", id: plan.id, verified: true, desired: plan.desired };
23159
+ }
23160
+ if (plan.action === "create") {
23161
+ await client.rest("POST", `orgs/${org}/rulesets`, { body: plan.desired, timeoutMs: 2e4 });
23162
+ } else {
23163
+ if (plan.id == null) throw new Error(`cannot update '${plan.desired.name}': live ruleset id is missing`);
23164
+ await client.rest("PUT", `orgs/${org}/rulesets/${plan.id}`, { body: plan.desired, timeoutMs: 2e4 });
23165
+ }
23166
+ const verified = planOrgNoAgentFilesRuleset(await fetchOrgNoAgentFilesRuleset(client, org));
23167
+ if (verified.action !== "noop") {
23168
+ throw new Error(`org ruleset reconcile did not settle: ${verified.drift.join("; ")}`);
23169
+ }
23170
+ return {
23171
+ action: plan.action === "create" ? "created" : "updated",
23172
+ id: verified.id,
23173
+ verified: true,
23174
+ desired: verified.desired
23175
+ };
23176
+ }
23071
23177
 
23072
23178
  // src/bootstrap-commands.ts
23073
23179
  function registerBootstrapCommands(program3) {
@@ -23089,13 +23195,13 @@ function registerBootstrapCommands(program3) {
23089
23195
  client: defaultGitHubClient(),
23090
23196
  projectMeta: meta,
23091
23197
  deployModel: typeof meta?.deployModel === "string" ? meta.deployModel : void 0,
23092
- readLocalFile: (path2) => path2 === "projects.json" && apiProjects != null ? apiProjects : (0, import_node_fs26.existsSync)(path2) ? (0, import_node_fs26.readFileSync)(path2, "utf8") : null,
23198
+ readLocalFile: (path2) => path2 === "projects.json" && apiProjects != null ? apiProjects : (0, import_node_fs27.existsSync)(path2) ? (0, import_node_fs27.readFileSync)(path2, "utf8") : null,
23093
23199
  // requiredGcpApis is stored as an array by a JSON write, but `org project set --var KEY=VALUE` stores a raw
23094
23200
  // comma-string — accept either so the seeded value verifies regardless of how it was written.
23095
23201
  // #3689: the same committed map the org access audit reads (#3664), so a sanctioned admin is not a
23096
23202
  // permanent bootstrap failure on one surface and an intended state on the other. Absent file → no
23097
23203
  // sanction, which is the pre-#3664 behaviour.
23098
- sanctionedAdmins: (0, import_node_fs26.existsSync)("access-matrix.json") ? entriesValueByCanonicalRepo(loadSanctionedAdmins((0, import_node_fs26.readFileSync)("access-matrix.json", "utf8")), repo) : void 0,
23204
+ sanctionedAdmins: (0, import_node_fs27.existsSync)("access-matrix.json") ? entriesValueByCanonicalRepo(loadSanctionedAdmins((0, import_node_fs27.readFileSync)("access-matrix.json", "utf8")), repo) : void 0,
23099
23205
  requiredGcpApis: (() => {
23100
23206
  const v = meta?.requiredGcpApis;
23101
23207
  if (Array.isArray(v)) return v;
@@ -23122,23 +23228,38 @@ function registerBootstrapCommands(program3) {
23122
23228
  console.log(o.json ? JSON.stringify(report, null, 2) : renderBootstrapVerifyReport(report));
23123
23229
  if (!report.ok) process.exitCode = 1;
23124
23230
  });
23125
- bootstrap.command("org-ruleset").description("drift-check the codified org no-agent-files push ruleset (mmi-no-agent-files-org) against live; read-only, never mutates (#2196)").option("--json", "machine-readable output").action(async () => {
23231
+ bootstrap.command("org-ruleset").description("drift-check the codified org no-agent-files push ruleset; optionally reconcile it under master authority (#2196/#4001)").option("--json", "machine-readable output").option("--apply", "create or update the one named org ruleset to exact desired state (master-admin required)").action(async () => {
23126
23232
  const json = rawFlag("--json");
23127
- const live = await fetchOrgNoAgentFilesRuleset(defaultGitHubClient());
23233
+ const apply = rawFlag("--apply");
23234
+ const client = defaultGitHubClient();
23235
+ const live = await fetchOrgNoAgentFilesRuleset(client);
23128
23236
  const plan = planOrgNoAgentFilesRuleset(live);
23129
- if (json) console.log(JSON.stringify({ action: plan.action, id: plan.id, drift: plan.drift, desired: plan.desired }, null, 2));
23130
- else console.log(renderOrgRulesetDriftReport(plan));
23131
- if (plan.action !== "noop") process.exitCode = 1;
23237
+ if (!apply) {
23238
+ if (json) console.log(JSON.stringify({ action: plan.action, id: plan.id, drift: plan.drift, desired: plan.desired }, null, 2));
23239
+ else console.log(renderOrgRulesetDriftReport(plan));
23240
+ if (plan.action !== "noop") process.exitCode = 1;
23241
+ return;
23242
+ }
23243
+ const verdict = await fetchTrainAuthority("mutmutco/MMI-Hub", registryClientDeps(await loadConfig()));
23244
+ if (!verdict.ok || verdict.authority.role !== "master") {
23245
+ return fail("bootstrap org-ruleset --apply: master-admin required");
23246
+ }
23247
+ const result = await reconcileOrgNoAgentFilesRuleset(plan, client);
23248
+ if (json) console.log(JSON.stringify(result, null, 2));
23249
+ else {
23250
+ console.log(`mmi-cli bootstrap org-ruleset --apply: ${result.action}`);
23251
+ console.log(renderOrgRulesetDriftReport(planOrgNoAgentFilesRuleset(await fetchOrgNoAgentFilesRuleset(client))));
23252
+ }
23132
23253
  });
23133
23254
  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 () => {
23134
23255
  const o = { repo: rawValue("--repo", ""), json: rawFlag("--json") };
23135
23256
  const manifestPath = "skills/bootstrap/seeds/manifest.json";
23136
- 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`);
23137
- const manifest = loadBootstrapSeeds((0, import_node_fs26.readFileSync)(manifestPath, "utf8"));
23257
+ 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`);
23258
+ const manifest = loadBootstrapSeeds((0, import_node_fs27.readFileSync)(manifestPath, "utf8"));
23138
23259
  const hubContents = /* @__PURE__ */ new Map();
23139
23260
  for (const s of manifest.seeds) {
23140
23261
  if (s.ownership !== "org" || s.source !== "self") continue;
23141
- hubContents.set(s.target, (0, import_node_fs26.existsSync)(s.target) ? (0, import_node_fs26.readFileSync)(s.target, "utf8") : null);
23262
+ hubContents.set(s.target, (0, import_node_fs27.existsSync)(s.target) ? (0, import_node_fs27.readFileSync)(s.target, "utf8") : null);
23142
23263
  }
23143
23264
  let targets;
23144
23265
  let classOf = (_repo) => "deployable";
@@ -23215,8 +23336,8 @@ function registerBootstrapCommands(program3) {
23215
23336
  return fail(`bootstrap apply: ${e.message}`);
23216
23337
  }
23217
23338
  const manifestPath = "skills/bootstrap/seeds/manifest.json";
23218
- 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`);
23219
- const manifest = loadBootstrapSeeds((0, import_node_fs26.readFileSync)(manifestPath, "utf8"));
23339
+ 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`);
23340
+ const manifest = loadBootstrapSeeds((0, import_node_fs27.readFileSync)(manifestPath, "utf8"));
23220
23341
  const baseBranch = o.class === "content" ? "main" : "development";
23221
23342
  const slug = parsedRepo.slug;
23222
23343
  const onlyTarget = o.only.trim();
@@ -23227,16 +23348,16 @@ function registerBootstrapCommands(program3) {
23227
23348
  ${known}`);
23228
23349
  }
23229
23350
  const gh = async (args) => execFileP2("gh", args, { timeout: 2e4 });
23230
- const readFile9 = (p) => (0, import_node_fs26.existsSync)(p) ? (0, import_node_fs26.readFileSync)(p, "utf8") : null;
23351
+ const readFile9 = (p) => (0, import_node_fs27.existsSync)(p) ? (0, import_node_fs27.readFileSync)(p, "utf8") : null;
23231
23352
  const enc = (p) => p.split("/").map(encodeURIComponent).join("/");
23232
23353
  const putSeed = async (target, content, ref, sha) => {
23233
- 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`);
23234
- (0, import_node_fs26.writeFileSync)(tmp, JSON.stringify(contentPutBody(target, content, ref, sha)), "utf8");
23354
+ 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`);
23355
+ (0, import_node_fs27.writeFileSync)(tmp, JSON.stringify(contentPutBody(target, content, ref, sha)), "utf8");
23235
23356
  try {
23236
23357
  await gh(contentPutInputArgs(repo, target, tmp));
23237
23358
  } finally {
23238
23359
  try {
23239
- (0, import_node_fs26.unlinkSync)(tmp);
23360
+ (0, import_node_fs27.unlinkSync)(tmp);
23240
23361
  } catch {
23241
23362
  }
23242
23363
  }
@@ -23488,12 +23609,12 @@ LIVE apply to ${repo}:
23488
23609
  }
23489
23610
 
23490
23611
  // src/stage-commands.ts
23491
- var import_node_fs28 = require("node:fs");
23492
- var import_node_path27 = require("node:path");
23612
+ var import_node_fs29 = require("node:fs");
23613
+ var import_node_path28 = require("node:path");
23493
23614
 
23494
23615
  // src/port-registry.ts
23495
- var import_node_fs27 = require("node:fs");
23496
- var import_node_path26 = require("node:path");
23616
+ var import_node_fs28 = require("node:fs");
23617
+ var import_node_path27 = require("node:path");
23497
23618
 
23498
23619
  // ../infra/port-geometry.mjs
23499
23620
  var PORT_BLOCK = 100;
@@ -23507,8 +23628,8 @@ function nextPortBlock(registry2) {
23507
23628
  return [base, base + PORT_SPAN];
23508
23629
  }
23509
23630
  function loadPortRegistry(path2) {
23510
- if (!(0, import_node_fs27.existsSync)(path2)) return {};
23511
- const raw = JSON.parse((0, import_node_fs27.readFileSync)(path2, "utf8"));
23631
+ if (!(0, import_node_fs28.existsSync)(path2)) return {};
23632
+ const raw = JSON.parse((0, import_node_fs28.readFileSync)(path2, "utf8"));
23512
23633
  const out = {};
23513
23634
  for (const [key, value] of Object.entries(raw)) {
23514
23635
  if (Array.isArray(value) && value.length === 2 && value.every((n) => typeof n === "number")) {
@@ -23522,9 +23643,9 @@ function ensurePortRange(repo, path2) {
23522
23643
  const existing = registry2[repo];
23523
23644
  if (existing) return existing;
23524
23645
  const range = nextPortBlock(registry2);
23525
- const raw = (0, import_node_fs27.existsSync)(path2) ? JSON.parse((0, import_node_fs27.readFileSync)(path2, "utf8")) : {};
23646
+ const raw = (0, import_node_fs28.existsSync)(path2) ? JSON.parse((0, import_node_fs28.readFileSync)(path2, "utf8")) : {};
23526
23647
  raw[repo] = range;
23527
- (0, import_node_fs27.writeFileSync)(path2, JSON.stringify(raw, null, 2) + "\n", "utf8");
23648
+ (0, import_node_fs28.writeFileSync)(path2, JSON.stringify(raw, null, 2) + "\n", "utf8");
23528
23649
  return range;
23529
23650
  }
23530
23651
  function portCursorSeed(registry2) {
@@ -23546,22 +23667,22 @@ function existingPortRange(repo, registry2) {
23546
23667
  return registry2[repo] ?? null;
23547
23668
  }
23548
23669
  function portRangeInfraAt(root, source) {
23549
- const registryPath = (0, import_node_path26.join)(root, "infra", "port-ranges.json");
23550
- const ddbScriptPath = (0, import_node_path26.join)(root, "infra", "port-ddb.mjs");
23551
- if (!(0, import_node_fs27.existsSync)(registryPath) || !(0, import_node_fs27.existsSync)(ddbScriptPath)) return null;
23670
+ const registryPath = (0, import_node_path27.join)(root, "infra", "port-ranges.json");
23671
+ const ddbScriptPath = (0, import_node_path27.join)(root, "infra", "port-ddb.mjs");
23672
+ if (!(0, import_node_fs28.existsSync)(registryPath) || !(0, import_node_fs28.existsSync)(ddbScriptPath)) return null;
23552
23673
  return { root, source, registryPath, ddbScriptPath };
23553
23674
  }
23554
23675
  function resolvePortRangeInfra(cwd, packageDir) {
23555
23676
  const direct = portRangeInfraAt(cwd, "cwd");
23556
23677
  if (direct) return direct;
23557
- for (let dir = cwd; ; dir = (0, import_node_path26.dirname)(dir)) {
23558
- const sibling = portRangeInfraAt((0, import_node_path26.join)(dir, "MMI-Hub"), "sibling-hub");
23678
+ for (let dir = cwd; ; dir = (0, import_node_path27.dirname)(dir)) {
23679
+ const sibling = portRangeInfraAt((0, import_node_path27.join)(dir, "MMI-Hub"), "sibling-hub");
23559
23680
  if (sibling) return sibling;
23560
- const parent = (0, import_node_path26.dirname)(dir);
23681
+ const parent = (0, import_node_path27.dirname)(dir);
23561
23682
  if (parent === dir) break;
23562
23683
  }
23563
23684
  if (packageDir) {
23564
- const pkgRoot = (0, import_node_path26.join)(packageDir, "..", "..");
23685
+ const pkgRoot = (0, import_node_path27.join)(packageDir, "..", "..");
23565
23686
  const pkgFrom = portRangeInfraAt(pkgRoot, "pkg-root");
23566
23687
  if (pkgFrom) return pkgFrom;
23567
23688
  }
@@ -23737,8 +23858,8 @@ function registerStageCommands(program3) {
23737
23858
  const portRange = portRangeMeta && typeof portRangeMeta.start === "number" && typeof portRangeMeta.end === "number" ? [portRangeMeta.start, portRangeMeta.end] : void 0;
23738
23859
  return decideStage({
23739
23860
  registry: { deployModel: project2?.deployModel, portRange, error: read.ok ? void 0 : read.error },
23740
- hasCompose: (0, import_node_fs28.existsSync)((0, import_node_path27.join)(process.cwd(), "docker-compose.yml")),
23741
- hasEnvExample: (0, import_node_fs28.existsSync)((0, import_node_path27.join)(process.cwd(), ".env.example"))
23861
+ hasCompose: (0, import_node_fs29.existsSync)((0, import_node_path28.join)(process.cwd(), "docker-compose.yml")),
23862
+ hasEnvExample: (0, import_node_fs29.existsSync)((0, import_node_path28.join)(process.cwd(), ".env.example"))
23742
23863
  });
23743
23864
  }
23744
23865
  async function fetchStageVaultEnvMerge() {
@@ -24176,10 +24297,10 @@ function registerBoardCommands(program3) {
24176
24297
  }
24177
24298
 
24178
24299
  // src/merge-cleanup.ts
24179
- var import_node_fs29 = require("node:fs");
24300
+ var import_node_fs30 = require("node:fs");
24180
24301
  var import_promises7 = require("node:fs/promises");
24181
- var import_node_path29 = require("node:path");
24182
- var import_node_os10 = require("node:os");
24302
+ var import_node_path30 = require("node:path");
24303
+ var import_node_os11 = require("node:os");
24183
24304
  var import_node_child_process14 = require("node:child_process");
24184
24305
 
24185
24306
  // src/board-advance.ts
@@ -24266,7 +24387,7 @@ function boardAdvanceFailureMessage(result) {
24266
24387
 
24267
24388
  // src/deferred-registry-store.ts
24268
24389
  var import_promises6 = require("node:fs/promises");
24269
- var import_node_path28 = require("node:path");
24390
+ var import_node_path29 = require("node:path");
24270
24391
  var sleep2 = (ms) => new Promise((resolve5) => setTimeout(resolve5, ms));
24271
24392
  async function atomicWrite(target, contents) {
24272
24393
  const tmp = `${target}.tmp-${process.pid}-${Math.random().toString(36).slice(2)}`;
@@ -24317,12 +24438,12 @@ function makeDeferredWorktreeStore(registryPath, lockOpts = {}) {
24317
24438
  },
24318
24439
  // Standalone atomic write — THROWS on failure (no best-effort swallow, #2846).
24319
24440
  write: async (entries) => {
24320
- await (0, import_promises6.mkdir)((0, import_node_path28.dirname)(registryPath), { recursive: true });
24441
+ await (0, import_promises6.mkdir)((0, import_node_path29.dirname)(registryPath), { recursive: true });
24321
24442
  await atomicWrite(registryPath, serializeDeferredWorktrees(entries));
24322
24443
  },
24323
24444
  // Serialized read-modify-write under the repo-wide lock (#2846).
24324
24445
  update: async (mutate) => {
24325
- await (0, import_promises6.mkdir)((0, import_node_path28.dirname)(registryPath), { recursive: true });
24446
+ await (0, import_promises6.mkdir)((0, import_node_path29.dirname)(registryPath), { recursive: true });
24326
24447
  const deadline = Date.now() + opts.maxWaitMs;
24327
24448
  for (; ; ) {
24328
24449
  const guard = await acquireLock(lockPath, opts, deadline);
@@ -24483,7 +24604,7 @@ async function applyGcPlan(plan, remote, opts = {}) {
24483
24604
  );
24484
24605
  const repoRoot2 = (await execFileP2("git", ["rev-parse", "--show-toplevel"], { timeout: GIT_TIMEOUT_MS }).catch(() => ({ stdout: "" }))).stdout.trim() || process.cwd();
24485
24606
  const worktreeGitRoot = await currentRepoWorktreeGitRoot(repoRoot2);
24486
- const primaryRepoRoot = worktreeGitRoot ? (0, import_node_path29.dirname)((0, import_node_path29.dirname)(worktreeGitRoot)) : repoRoot2;
24607
+ const primaryRepoRoot = worktreeGitRoot ? (0, import_node_path30.dirname)((0, import_node_path30.dirname)(worktreeGitRoot)) : repoRoot2;
24487
24608
  const gcActor = describeActor({ env: process.env, surface: detectSurface(process.env), cwd: process.cwd() });
24488
24609
  const owners = readWorktreeOwners(primaryRepoRoot);
24489
24610
  const removalNow = Date.now();
@@ -24514,7 +24635,7 @@ async function applyGcPlan(plan, remote, opts = {}) {
24514
24635
  const cleanup = await cleanupPrMergeLocalBranch(branch.branch, {
24515
24636
  beforeWorktrees,
24516
24637
  startingPath: branch.worktreePath,
24517
- pathExists: (p) => (0, import_node_fs29.existsSync)(p),
24638
+ pathExists: (p) => (0, import_node_fs30.existsSync)(p),
24518
24639
  execGit: async (args) => (await execFileP2("git", args, { timeout: GIT_TIMEOUT_MS })).stdout,
24519
24640
  teardownWorktreeStage,
24520
24641
  deferredStore,
@@ -24542,7 +24663,7 @@ async function applyGcPlan(plan, remote, opts = {}) {
24542
24663
  for (const wt of worktreeDirsToRemove) {
24543
24664
  try {
24544
24665
  const cleanupTarget = resolveSafeSiblingWorktreeCleanupTarget(wt.path, siblingRoot, {
24545
- realpath: (path2) => (0, import_node_fs29.realpathSync)(path2)
24666
+ realpath: (path2) => (0, import_node_fs30.realpathSync)(path2)
24546
24667
  });
24547
24668
  if (!cleanupTarget.ok) {
24548
24669
  result.failed.push(`${wt.path}: ${cleanupTarget.reason}`);
@@ -24607,13 +24728,13 @@ async function composeOverrideBodyFile(prNumber, repoArgs, gh) {
24607
24728
  const commits = JSON.parse(raw).commits ?? [];
24608
24729
  const body = squashBodyWithOverride(commits.map((c) => ({ headline: c.messageHeadline ?? "", body: c.messageBody ?? "" })), process.cwd());
24609
24730
  if (!body) return void 0;
24610
- const dir = (0, import_node_fs29.mkdtempSync)((0, import_node_path29.join)((0, import_node_os10.tmpdir)(), "mmi-squash-body-"));
24611
- const path2 = (0, import_node_path29.join)(dir, "body.txt");
24612
- (0, import_node_fs29.writeFileSync)(path2, `${body}
24731
+ const dir = (0, import_node_fs30.mkdtempSync)((0, import_node_path30.join)((0, import_node_os11.tmpdir)(), "mmi-squash-body-"));
24732
+ const path2 = (0, import_node_path30.join)(dir, "body.txt");
24733
+ (0, import_node_fs30.writeFileSync)(path2, `${body}
24613
24734
  `, "utf8");
24614
24735
  return { path: path2, cleanup: () => {
24615
24736
  try {
24616
- (0, import_node_fs29.rmSync)(dir, { recursive: true, force: true });
24737
+ (0, import_node_fs30.rmSync)(dir, { recursive: true, force: true });
24617
24738
  } catch {
24618
24739
  }
24619
24740
  } };
@@ -24735,13 +24856,13 @@ var realWorktreeDirRemover = {
24735
24856
  probe: (p) => {
24736
24857
  let st;
24737
24858
  try {
24738
- st = (0, import_node_fs29.lstatSync)(p);
24859
+ st = (0, import_node_fs30.lstatSync)(p);
24739
24860
  } catch {
24740
24861
  return null;
24741
24862
  }
24742
24863
  if (st.isSymbolicLink()) return "link";
24743
24864
  try {
24744
- (0, import_node_fs29.readlinkSync)(p);
24865
+ (0, import_node_fs30.readlinkSync)(p);
24745
24866
  return "link";
24746
24867
  } catch {
24747
24868
  }
@@ -24749,7 +24870,7 @@ var realWorktreeDirRemover = {
24749
24870
  },
24750
24871
  readdir: (p) => {
24751
24872
  try {
24752
- return (0, import_node_fs29.readdirSync)(p);
24873
+ return (0, import_node_fs30.readdirSync)(p);
24753
24874
  } catch {
24754
24875
  return [];
24755
24876
  }
@@ -24758,9 +24879,9 @@ var realWorktreeDirRemover = {
24758
24879
  // leaving the target); a file symlink with unlink. rmdir first, fall back to unlink.
24759
24880
  detachLink: (p) => {
24760
24881
  try {
24761
- (0, import_node_fs29.rmdirSync)(p);
24882
+ (0, import_node_fs30.rmdirSync)(p);
24762
24883
  } catch {
24763
- (0, import_node_fs29.unlinkSync)(p);
24884
+ (0, import_node_fs30.unlinkSync)(p);
24764
24885
  }
24765
24886
  },
24766
24887
  removeTree: (p) => (0, import_promises7.rm)(p, { recursive: true, force: true, maxRetries: 5, retryDelay: 200 })
@@ -24793,9 +24914,9 @@ async function worktreeHasStageState(worktreePath) {
24793
24914
  }
24794
24915
  }
24795
24916
  function stageStateFileBelongsToWorktree(statePath, worktreePath) {
24796
- if (!(0, import_node_fs29.existsSync)(statePath)) return false;
24917
+ if (!(0, import_node_fs30.existsSync)(statePath)) return false;
24797
24918
  try {
24798
- const state = JSON.parse((0, import_node_fs29.readFileSync)(statePath, "utf8"));
24919
+ const state = JSON.parse((0, import_node_fs30.readFileSync)(statePath, "utf8"));
24799
24920
  const recordedCwd = typeof state.identity?.cwd === "string" ? state.identity.cwd : typeof state.cwd === "string" ? state.cwd : "";
24800
24921
  return Boolean(recordedCwd && isPathUnderDirectory(recordedCwd, worktreePath));
24801
24922
  } catch {
@@ -25019,9 +25140,9 @@ async function fetchRestCorePool(gh = defaultGhApi) {
25019
25140
  }
25020
25141
 
25021
25142
  // src/worktree-lifecycle-commands.ts
25022
- var import_node_fs30 = require("node:fs");
25143
+ var import_node_fs31 = require("node:fs");
25023
25144
  var import_promises8 = require("node:fs/promises");
25024
- var import_node_path30 = require("node:path");
25145
+ var import_node_path31 = require("node:path");
25025
25146
  var GH_TIMEOUT_MS = 2e4;
25026
25147
  var DEFAULT_BASE = "origin/development";
25027
25148
  var DEFAULT_REMOTE = "origin";
@@ -25093,6 +25214,16 @@ function orphanLandSteps(lostBranch, hasStage) {
25093
25214
  steps.push("prune worktree metadata");
25094
25215
  return steps;
25095
25216
  }
25217
+ function detachedLandSteps(hasStage) {
25218
+ const steps = [];
25219
+ if (hasStage) steps.push("stop spawned dev stage");
25220
+ steps.push("remove worktree (detached HEAD)");
25221
+ steps.push("prune worktree metadata");
25222
+ return steps;
25223
+ }
25224
+ function shouldContinueLandCleanup(removeStatus) {
25225
+ return removeStatus === "removed";
25226
+ }
25096
25227
  function classifyStaleLeaks(input) {
25097
25228
  const protectedBranches = input.protectedBranches ?? PROTECTED_BRANCHES2;
25098
25229
  const leaks = [];
@@ -25157,7 +25288,7 @@ function classifyStaleLeaks(input) {
25157
25288
  var defaultOrphanDirScanDeps = {
25158
25289
  listDirs: (root) => {
25159
25290
  try {
25160
- return (0, import_node_fs30.readdirSync)(root, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => (0, import_node_path30.join)(root, e.name));
25291
+ return (0, import_node_fs31.readdirSync)(root, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => (0, import_node_path31.join)(root, e.name));
25161
25292
  } catch {
25162
25293
  return [];
25163
25294
  }
@@ -25302,15 +25433,21 @@ function registerWorktreeCommands(program3) {
25302
25433
  try {
25303
25434
  const wtPath = (await execFileP2("git", ["rev-parse", "--show-toplevel"], { timeout: GIT_TIMEOUT_MS })).stdout.trim();
25304
25435
  const headBorn = await execFileP2("git", ["-C", wtPath || ".", "rev-parse", "--verify", "--quiet", "HEAD"], { timeout: GIT_TIMEOUT_MS }).then(() => true).catch(() => false);
25305
- 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();
25436
+ const symbolicBranch = (await execFileP2(
25437
+ "git",
25438
+ ["-C", wtPath || ".", "symbolic-ref", "--quiet", "--short", "HEAD"],
25439
+ { timeout: GIT_TIMEOUT_MS }
25440
+ ).catch(() => ({ stdout: "" }))).stdout.trim();
25441
+ const detached = headBorn && !symbolicBranch;
25442
+ const branch = symbolicBranch || (detached ? "HEAD" : "");
25306
25443
  if (!wtPath || !branch) return fail("worktree land: not inside a git worktree");
25307
- const gitFile = (0, import_node_path30.join)(wtPath, ".git");
25308
- const isLinked = (0, import_node_fs30.existsSync)(gitFile) && (0, import_node_fs30.statSync)(gitFile).isFile();
25444
+ const gitFile = (0, import_node_path31.join)(wtPath, ".git");
25445
+ const isLinked = (0, import_node_fs31.existsSync)(gitFile) && (0, import_node_fs31.statSync)(gitFile).isFile();
25309
25446
  if (apply && !isLinked) {
25310
25447
  return fail("worktree land: run from inside the linked worktree you want to land (this is the primary checkout)");
25311
25448
  }
25312
25449
  const commonDir = (await execFileP2("git", ["rev-parse", "--git-common-dir"], { timeout: GIT_TIMEOUT_MS }).catch(() => ({ stdout: "" }))).stdout.trim();
25313
- const primaryCheckout = commonDir ? (0, import_node_path30.dirname)(commonDir) : wtPath;
25450
+ const primaryCheckout = commonDir ? (0, import_node_path31.dirname)(commonDir) : wtPath;
25314
25451
  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);
25315
25452
  const orphan = classifyOrphanedWorktree({
25316
25453
  branch,
@@ -25323,7 +25460,7 @@ function registerWorktreeCommands(program3) {
25323
25460
  }
25324
25461
  const stageSummary = readStageSummary(wtPath);
25325
25462
  const hasStage = stageSummary != null;
25326
- const steps = orphan.orphan ? orphanLandSteps(orphan.lostBranch, hasStage) : landSteps(branch, true, hasStage);
25463
+ const steps = detached ? detachedLandSteps(hasStage) : orphan.orphan ? orphanLandSteps(orphan.lostBranch, hasStage) : landSteps(branch, true, hasStage);
25327
25464
  const plan = {
25328
25465
  command: "worktree land",
25329
25466
  branch,
@@ -25332,13 +25469,15 @@ function registerWorktreeCommands(program3) {
25332
25469
  remote: o.remote,
25333
25470
  hasStage,
25334
25471
  steps,
25335
- ...orphan.orphan ? { orphan: { kind: orphan.kind, lostBranch: orphan.lostBranch, reason: orphan.reason } } : {}
25472
+ ...orphan.orphan ? { orphan: { kind: orphan.kind, lostBranch: orphan.lostBranch, reason: orphan.reason } } : {},
25473
+ ...detached ? { detached: true } : {}
25336
25474
  };
25337
25475
  if (!apply) {
25338
25476
  const preview = { dryRun: true, ...plan, ...o.keepRemote ? { keepRemote: true } : {} };
25339
25477
  if (o.json) return console.log(JSON.stringify(preview, null, 2));
25340
25478
  const lines = [`worktree land: dry-run (pass --apply to execute)`, ` branch: ${branch}`, ` worktree: ${wtPath}`];
25341
- if (orphan.orphan) lines.push(` orphaned: ${orphan.reason} \u2014 no branch refs will be deleted`);
25479
+ if (detached) lines.push(" detached HEAD: no branch refs will be deleted");
25480
+ else if (orphan.orphan) lines.push(` orphaned: ${orphan.reason} \u2014 no branch refs will be deleted`);
25342
25481
  if (hasStage) lines.push(` stage: port ${stageSummary.port} (will be stopped)`);
25343
25482
  if (o.keepRemote && !orphan.orphan) lines.push(` remote branch: kept (--keep-remote)`);
25344
25483
  for (const s of steps) lines.push(` - ${s}`);
@@ -25360,15 +25499,16 @@ function registerWorktreeCommands(program3) {
25360
25499
  unreferenced: await isCommitUnreferenced(orphanTip, async (args) => (await execFileP2("git", ["-C", primaryCheckout, ...args], { timeout: GIT_TIMEOUT_MS })).stdout),
25361
25500
  recoverCommand: `git -C "${primaryCheckout}" branch <name> ${orphanTip}`
25362
25501
  } : void 0;
25363
- const landPrs = orphan.orphan ? void 0 : await fetchLandBranchPrs(branch);
25502
+ const treeOnly = orphan.orphan || detached;
25503
+ const landPrs = treeOnly ? void 0 : await fetchLandBranchPrs(branch);
25364
25504
  const mergeVerdict = classifyLandBranchMergeState(landPrs);
25365
- const landRefs = orphan.orphan ? { action: "proceed" } : decideWorktreeLandRefCleanup({ branch, prs: landPrs, keepRemote: Boolean(o.keepRemote) });
25505
+ const landRefs = treeOnly ? { action: "proceed" } : decideWorktreeLandRefCleanup({ branch, prs: landPrs, keepRemote: Boolean(o.keepRemote) });
25366
25506
  if (landRefs.action === "refuse") {
25367
25507
  return fail(`worktree land: ${landRefs.message}`, { code: ERROR_CODES.ERR_BAD_ENUM });
25368
25508
  }
25369
25509
  const keepRemoteBranch = Boolean(o.keepRemote) || landRefs.action === "keep-remote";
25370
25510
  if (landRefs.action === "keep-remote") console.warn(`worktree land: ${landRefs.message}.`);
25371
- const reportedMergeState = orphan.orphan ? "not-checked" : mergeVerdict.state;
25511
+ const reportedMergeState = treeOnly ? "not-checked" : mergeVerdict.state;
25372
25512
  const landOwner = lookupWorktreeOwner(primaryCheckout, wtPath);
25373
25513
  const report = [];
25374
25514
  if (hasStage) {
@@ -25388,6 +25528,24 @@ function registerWorktreeCommands(program3) {
25388
25528
  step: "remove worktree",
25389
25529
  status: removeOutcome.status === "removed" ? removeOutcome.recovery ? `done (${removeOutcome.recovery})` : "done" : `failed: ${removeOutcome.error ?? "lock held"} \u2014 run: git -C "${primaryCheckout}" worktree remove --force "${wtPath}"`
25390
25530
  });
25531
+ if (!shouldContinueLandCleanup(removeOutcome.status)) {
25532
+ const result2 = {
25533
+ dryRun: false,
25534
+ ...plan,
25535
+ ...o.keepRemote ? { keepRemote: true } : {},
25536
+ mergeState: reportedMergeState,
25537
+ prNumbers: mergeVerdict.numbers,
25538
+ cleanupState: "partial",
25539
+ report
25540
+ };
25541
+ if (o.json) console.log(JSON.stringify(result2, null, 2));
25542
+ else {
25543
+ console.error(`worktree land: cleanup stopped after the worktree removal failed; branch refs and metadata were left untouched`);
25544
+ for (const row of report) console.log(` ${row.step}: ${row.status}`);
25545
+ }
25546
+ process.exitCode = 1;
25547
+ return;
25548
+ }
25391
25549
  const landActor = describeActor({ env: process.env, surface: detectSurface(process.env), cwd: wtPath });
25392
25550
  appendWorktreeEvent(primaryCheckout, {
25393
25551
  action: removeOutcome.status === "removed" ? "removed" : "failed",
@@ -25402,8 +25560,11 @@ function registerWorktreeCommands(program3) {
25402
25560
  reason: orphan.orphan ? `orphaned worktree \u2014 ${orphan.reason}${lastCommit ? `; last commit ${lastCommit.oid}${lastCommit.unreferenced ? " (on no ref)" : ""}` : ""}` : describeLandMergeState(mergeVerdict)
25403
25561
  });
25404
25562
  if (removeOutcome.status === "removed") dropWorktreeOwner(primaryCheckout, wtPath);
25405
- if (orphan.orphan) {
25406
- report.push({ step: "delete branch refs", status: `skipped: orphaned worktree \u2014 ${orphan.reason}` });
25563
+ if (treeOnly) {
25564
+ report.push({
25565
+ step: "delete branch refs",
25566
+ status: detached ? "skipped: detached HEAD has no branch refs" : `skipped: orphaned worktree \u2014 ${orphan.reason}`
25567
+ });
25407
25568
  if (lastCommit) {
25408
25569
  report.push({
25409
25570
  step: "last commit",
@@ -25431,12 +25592,12 @@ function registerWorktreeCommands(program3) {
25431
25592
  report
25432
25593
  };
25433
25594
  if (o.json) return console.log(JSON.stringify(result, null, 2));
25434
- console.log(orphan.orphan ? `worktree land: removed orphaned worktree ${wtPath} (${orphan.reason}); no branch refs touched` : `worktree land: cleaned up branch ${branch} (${describeLandMergeState(mergeVerdict)})`);
25595
+ 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)})`);
25435
25596
  for (const r of report) console.log(` ${r.step}: ${r.status}`);
25436
25597
  if (lastCommit?.unreferenced) {
25437
25598
  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}`);
25438
25599
  }
25439
- if (!orphan.orphan && mergeVerdict.state !== "merged") {
25600
+ if (!treeOnly && mergeVerdict.state !== "merged") {
25440
25601
  console.warn(`worktree land: '${branch}' was cleaned up but NOT merged \u2014 ${describeLandMergeState(mergeVerdict)}. Do not report this work as shipped.`);
25441
25602
  }
25442
25603
  if (report.some((r) => r.status.startsWith("failed"))) process.exitCode = 1;
@@ -25497,10 +25658,10 @@ async function gatherWorktreeContext() {
25497
25658
  if (s) stages.push({ path: wt.path, port: s.port });
25498
25659
  }
25499
25660
  const worktreeGitRoot = await currentRepoWorktreeGitRoot(repoRoot2);
25500
- const primaryRepoRoot = worktreeGitRoot ? (0, import_node_path30.dirname)((0, import_node_path30.dirname)(worktreeGitRoot)) : repoRoot2;
25661
+ const primaryRepoRoot = worktreeGitRoot ? (0, import_node_path31.dirname)((0, import_node_path31.dirname)(worktreeGitRoot)) : repoRoot2;
25501
25662
  const wtRoot = siblingMmiWorktreesRoot(primaryRepoRoot);
25502
25663
  let orphanDirs = [];
25503
- if ((0, import_node_fs30.existsSync)(wtRoot)) {
25664
+ if ((0, import_node_fs31.existsSync)(wtRoot)) {
25504
25665
  orphanDirs = scanOrphanDirs(wtRoot, worktreeGitRoot, {
25505
25666
  ...defaultOrphanDirScanDeps,
25506
25667
  listDirs: (root) => worktreeScanDirs(root, primaryRepoRoot, defaultOrphanDirScanDeps.listDirs, isRepoCheckoutDir)
@@ -25514,16 +25675,19 @@ async function bestEffortGit(args, cwd, step, timeoutMs = GIT_TIMEOUT_MS) {
25514
25675
  await execFileP2("git", fullArgs, { timeout: timeoutMs });
25515
25676
  return { step, status: "done" };
25516
25677
  } catch (e) {
25517
- const msg = e.message.split("\n")[0];
25518
- if (/not found|already|no such|does not exist|not a valid|couldn't|unable to resolve/i.test(msg)) {
25678
+ const err = e;
25679
+ const detail = `${err.message}
25680
+ ${err.stderr ?? ""}`;
25681
+ if (/not found|already|no such|does not exist|not a valid|couldn't|unable to resolve/i.test(detail)) {
25519
25682
  return { step, status: "already gone" };
25520
25683
  }
25684
+ const msg = (err.stderr?.trim() || err.message).split("\n")[0];
25521
25685
  return { step, status: `failed: ${msg}` };
25522
25686
  }
25523
25687
  }
25524
25688
 
25525
25689
  // src/issue-commands.ts
25526
- var import_node_fs31 = require("node:fs");
25690
+ var import_node_fs32 = require("node:fs");
25527
25691
  var import_node_crypto5 = require("node:crypto");
25528
25692
  var ghRunner = async (args, timeoutMs) => (await execFileP2("gh", args, { timeout: timeoutMs })).stdout;
25529
25693
  var ReparentConflictError = class extends Error {
@@ -25541,7 +25705,7 @@ async function editIssue(client, options, deps = {}) {
25541
25705
  const url = `https://github.com/${repo}/issues/${parsed.number}`;
25542
25706
  const patch = {};
25543
25707
  let bodyChanged = false;
25544
- const textDeps = () => deps.textDeps ?? { readFile: (p, e) => Promise.resolve((0, import_node_fs31.readFileSync)(p, e)), readStdin: () => Promise.resolve("") };
25708
+ const textDeps = () => deps.textDeps ?? { readFile: (p, e) => Promise.resolve((0, import_node_fs32.readFileSync)(p, e)), readStdin: () => Promise.resolve("") };
25545
25709
  if (options.titleFile !== void 0) {
25546
25710
  patch.title = await resolveIssueTitle({ title: options.title, titleFile: options.titleFile }, textDeps());
25547
25711
  } else if (options.title !== void 0) {
@@ -26104,7 +26268,7 @@ function extendCreateCommand(issue2, batchAttach) {
26104
26268
  if (opts.batch) {
26105
26269
  let specs;
26106
26270
  try {
26107
- const raw = (0, import_node_fs31.readFileSync)(opts.batch, "utf8");
26271
+ const raw = (0, import_node_fs32.readFileSync)(opts.batch, "utf8");
26108
26272
  specs = JSON.parse(raw);
26109
26273
  if (!Array.isArray(specs)) throw new Error("batch file must contain a JSON array");
26110
26274
  } catch (e) {
@@ -26178,8 +26342,8 @@ ${lines}`, {
26178
26342
  }
26179
26343
 
26180
26344
  // src/train-commands.ts
26181
- var import_node_fs32 = require("node:fs");
26182
- var import_node_path31 = require("node:path");
26345
+ var import_node_fs33 = require("node:fs");
26346
+ var import_node_path32 = require("node:path");
26183
26347
 
26184
26348
  // src/train-status.ts
26185
26349
  function buildTrainStatusReport(input) {
@@ -26219,7 +26383,7 @@ function formatTrainStatus(r) {
26219
26383
  // src/train-commands.ts
26220
26384
  function readRepoVersion() {
26221
26385
  try {
26222
- return JSON.parse((0, import_node_fs32.readFileSync)((0, import_node_path31.join)(process.cwd(), ".claude-plugin", "plugin.json"), "utf8")).version || void 0;
26386
+ return JSON.parse((0, import_node_fs33.readFileSync)((0, import_node_path32.join)(process.cwd(), ".claude-plugin", "plugin.json"), "utf8")).version || void 0;
26223
26387
  } catch {
26224
26388
  return void 0;
26225
26389
  }
@@ -26365,11 +26529,12 @@ function registerDeployCommands(program3) {
26365
26529
  }
26366
26530
 
26367
26531
  // src/discovery-commands.ts
26368
- var import_node_fs33 = require("node:fs");
26369
- var import_node_os11 = require("node:os");
26370
- var import_node_path32 = require("node:path");
26532
+ var import_node_fs34 = require("node:fs");
26533
+ var import_node_os12 = require("node:os");
26534
+ var import_node_path33 = require("node:path");
26371
26535
  var GC_GH_TIMEOUT_MS3 = 2e4;
26372
26536
  async function collectStatus() {
26537
+ const repo = await resolveRepo();
26373
26538
  let branch = "";
26374
26539
  try {
26375
26540
  branch = (await execFileP2("git", ["rev-parse", "--abbrev-ref", "HEAD"], { timeout: GIT_TIMEOUT_MS })).stdout.trim();
@@ -26398,7 +26563,7 @@ async function collectStatus() {
26398
26563
  }
26399
26564
  let claimedItems = [];
26400
26565
  try {
26401
- const cfg = await loadConfig();
26566
+ const cfg = await loadConfigOrDiscover();
26402
26567
  if (cfg.sagaApiUrl) {
26403
26568
  const report = await readBoard({ config: cfg });
26404
26569
  claimedItems = report.primary.userOwned.map((item) => ({
@@ -26421,7 +26586,7 @@ async function collectStatus() {
26421
26586
  } catch {
26422
26587
  stage = { running: false };
26423
26588
  }
26424
- return { branch, worktrees, myOpenPrs, claimedItems, stage };
26589
+ return { repo, branch, worktrees, myOpenPrs, claimedItems, stage };
26425
26590
  }
26426
26591
  var PRIORITY_RANK = {
26427
26592
  Urgent: 0,
@@ -26429,14 +26594,14 @@ var PRIORITY_RANK = {
26429
26594
  Medium: 2,
26430
26595
  Low: 3
26431
26596
  };
26432
- async function recommendNext(deps) {
26433
- const load = deps?.loadConfig ?? loadConfig;
26597
+ async function recommendNext(repo, deps) {
26598
+ const load = deps?.loadConfig ?? loadConfigForRepo;
26434
26599
  const reader = deps?.readBoard ?? readBoard;
26435
- const cfg = await load();
26600
+ const cfg = await load(repo);
26436
26601
  if (!cfg.sagaApiUrl) throw new Error("Hub API URL not configured \u2014 the board was NOT read (run `mmi-cli doctor`)");
26437
26602
  let report;
26438
26603
  try {
26439
- report = await reader({ config: cfg });
26604
+ report = await reader({ config: cfg, repo });
26440
26605
  } catch (e) {
26441
26606
  throw new Error(
26442
26607
  `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).`
@@ -26542,10 +26707,10 @@ async function collectOnboardStatus() {
26542
26707
  else if (top) nextCommand = `mmi-cli board claim ${top.number} # ${top.title}`;
26543
26708
  else nextCommand = "mmi-cli board read \u2014 no claimable items found";
26544
26709
  }
26545
- const home = (0, import_node_os11.homedir)();
26710
+ const home = (0, import_node_os12.homedir)();
26546
26711
  const plugin = onboardPluginGate({
26547
- readKnown: () => readFileSyncSafe((0, import_node_path32.join)(home, ...KNOWN_MARKETPLACES_RELATIVE), import_node_fs33.readFileSync),
26548
- readSettings: () => readFileSyncSafe((0, import_node_path32.join)(home, ".claude", "settings.json"), import_node_fs33.readFileSync)
26712
+ readKnown: () => readFileSyncSafe((0, import_node_path33.join)(home, ...KNOWN_MARKETPLACES_RELATIVE), import_node_fs34.readFileSync),
26713
+ readSettings: () => readFileSyncSafe((0, import_node_path33.join)(home, ".claude", "settings.json"), import_node_fs34.readFileSync)
26549
26714
  });
26550
26715
  return { track, board, registry: registry2, secrets, plugin, nextCommand };
26551
26716
  }
@@ -26556,6 +26721,7 @@ function registerDiscoveryCommands(program3) {
26556
26721
  if (o.json) {
26557
26722
  console.log(JSON.stringify(report, null, 2));
26558
26723
  } else {
26724
+ console.log(`repo: ${report.repo ?? "unknown"}`);
26559
26725
  console.log(`branch: ${report.branch}`);
26560
26726
  console.log(`worktrees: ${report.worktrees.length} linked`);
26561
26727
  console.log(`my open PRs: ${report.myOpenPrs.length}`);
@@ -26566,9 +26732,9 @@ function registerDiscoveryCommands(program3) {
26566
26732
  fail(`status: ${e.message}`);
26567
26733
  }
26568
26734
  });
26569
- program3.command("next").description("recommend the next actionable board item (claimable, unblocked, priority-ranked)").action(async () => {
26735
+ 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) => {
26570
26736
  try {
26571
- const item = await recommendNext();
26737
+ const item = await recommendNext(o.repo);
26572
26738
  if (!item) {
26573
26739
  console.log("No claimable board items found (board read OK).");
26574
26740
  return;
@@ -28688,17 +28854,17 @@ function parseOriginRepo(remoteUrl) {
28688
28854
  }
28689
28855
  function ghHostsConfigPath(env, platform2) {
28690
28856
  const sep2 = platform2 === "win32" ? "\\" : "/";
28691
- const join29 = (...parts) => parts.join(sep2);
28857
+ const join30 = (...parts) => parts.join(sep2);
28692
28858
  const explicit = env.GH_CONFIG_DIR?.trim();
28693
- if (explicit) return join29(explicit, "hosts.yml");
28859
+ if (explicit) return join30(explicit, "hosts.yml");
28694
28860
  if (platform2 === "win32") {
28695
28861
  const appData = (env.AppData ?? env.APPDATA)?.trim();
28696
- return appData ? join29(appData, "GitHub CLI", "hosts.yml") : void 0;
28862
+ return appData ? join30(appData, "GitHub CLI", "hosts.yml") : void 0;
28697
28863
  }
28698
28864
  const xdg = env.XDG_CONFIG_HOME?.trim();
28699
- if (xdg) return join29(xdg, "gh", "hosts.yml");
28865
+ if (xdg) return join30(xdg, "gh", "hosts.yml");
28700
28866
  const home = env.HOME?.trim();
28701
- return home ? join29(home, ".config", "gh", "hosts.yml") : void 0;
28867
+ return home ? join30(home, ".config", "gh", "hosts.yml") : void 0;
28702
28868
  }
28703
28869
  function parseGhHostsAccounts(yaml, host = "github.com") {
28704
28870
  let hostIndent = null;
@@ -28748,9 +28914,9 @@ function ghAccountCaveat(announcedLogin, accounts) {
28748
28914
  }
28749
28915
 
28750
28916
  // src/doctor-io.ts
28751
- var import_node_fs34 = require("node:fs");
28752
- var import_node_os12 = require("node:os");
28753
- var import_node_path33 = require("node:path");
28917
+ var import_node_fs35 = require("node:fs");
28918
+ var import_node_os13 = require("node:os");
28919
+ var import_node_path34 = require("node:path");
28754
28920
  var import_node_child_process15 = require("node:child_process");
28755
28921
  var import_node_util8 = require("node:util");
28756
28922
  var execFileP6 = (0, import_node_util8.promisify)(import_node_child_process15.execFile);
@@ -28758,7 +28924,7 @@ var MMI_PLUGIN_ID2 = "mmi@mutmutco";
28758
28924
  function installedClaudePluginVersion() {
28759
28925
  try {
28760
28926
  const file = JSON.parse(
28761
- (0, import_node_fs34.readFileSync)((0, import_node_path33.join)((0, import_node_os12.homedir)(), ".claude", "plugins", "installed_plugins.json"), "utf8")
28927
+ (0, import_node_fs35.readFileSync)((0, import_node_path34.join)((0, import_node_os13.homedir)(), ".claude", "plugins", "installed_plugins.json"), "utf8")
28762
28928
  );
28763
28929
  const versions = (file.plugins?.[MMI_PLUGIN_ID2] ?? []).map((r) => r.version).filter((v) => Boolean(v));
28764
28930
  if (versions.length === 0) return void 0;
@@ -28769,7 +28935,7 @@ function installedClaudePluginVersion() {
28769
28935
  }
28770
28936
  function manifestVersion(path2) {
28771
28937
  try {
28772
- const manifest = JSON.parse((0, import_node_fs34.readFileSync)(path2, "utf8"));
28938
+ const manifest = JSON.parse((0, import_node_fs35.readFileSync)(path2, "utf8"));
28773
28939
  return typeof manifest.version === "string" && manifest.version.trim() ? manifest.version.trim() : void 0;
28774
28940
  } catch {
28775
28941
  return void 0;
@@ -28779,17 +28945,17 @@ function installedSurfacePluginVersion(surface) {
28779
28945
  const token = surfaceToken(surface);
28780
28946
  if (token === "kilo") {
28781
28947
  try {
28782
- const stamp = (0, import_node_fs34.readFileSync)((0, import_node_path33.join)((0, import_node_os12.homedir)(), ".kilo", ".mmi-kilo-version"), "utf8").trim();
28948
+ const stamp = (0, import_node_fs35.readFileSync)((0, import_node_path34.join)((0, import_node_os13.homedir)(), ".kilo", ".mmi-kilo-version"), "utf8").trim();
28783
28949
  return stamp || void 0;
28784
28950
  } catch {
28785
28951
  return void 0;
28786
28952
  }
28787
28953
  }
28788
28954
  if (token === "cursor") {
28789
- return manifestVersion((0, import_node_path33.join)(cursorLocalPluginRoot(), ".cursor-plugin", "plugin.json"));
28955
+ return manifestVersion((0, import_node_path34.join)(cursorLocalPluginRoot(), ".cursor-plugin", "plugin.json"));
28790
28956
  }
28791
28957
  if (token === "kimi") {
28792
- return manifestVersion((0, import_node_path33.join)(surfaceConfigRoot(surface), "plugins", "managed", "mmi", ".kimi-plugin", "plugin.json"));
28958
+ return manifestVersion((0, import_node_path34.join)(surfaceConfigRoot(surface), "plugins", "managed", "mmi", ".kimi-plugin", "plugin.json"));
28793
28959
  }
28794
28960
  if (token === "claude") return installedClaudePluginVersion();
28795
28961
  if (token !== "codex") return void 0;
@@ -28827,13 +28993,13 @@ function worktreeRootSync() {
28827
28993
  }
28828
28994
  var gitignorePath = () => {
28829
28995
  const root = worktreeRootSync();
28830
- return root === null ? null : (0, import_node_path33.join)(root, ".gitignore");
28996
+ return root === null ? null : (0, import_node_path34.join)(root, ".gitignore");
28831
28997
  };
28832
28998
  function readGitignore() {
28833
28999
  const path2 = gitignorePath();
28834
29000
  if (path2 === null) return null;
28835
29001
  try {
28836
- return (0, import_node_fs34.readFileSync)(path2, "utf8");
29002
+ return (0, import_node_fs35.readFileSync)(path2, "utf8");
28837
29003
  } catch {
28838
29004
  return null;
28839
29005
  }
@@ -28842,7 +29008,7 @@ function writeGitignore(content) {
28842
29008
  const path2 = gitignorePath();
28843
29009
  if (path2 === null) return false;
28844
29010
  try {
28845
- (0, import_node_fs34.writeFileSync)(path2, content, "utf8");
29011
+ (0, import_node_fs35.writeFileSync)(path2, content, "utf8");
28846
29012
  return true;
28847
29013
  } catch {
28848
29014
  return false;
@@ -28866,7 +29032,7 @@ async function repoRoot() {
28866
29032
  }
28867
29033
  function hasRepoLocalWorktrees() {
28868
29034
  const root = worktreeRootSync();
28869
- return root !== null && (0, import_node_fs34.existsSync)((0, import_node_path33.join)(root, ".worktrees"));
29035
+ return root !== null && (0, import_node_fs35.existsSync)((0, import_node_path34.join)(root, ".worktrees"));
28870
29036
  }
28871
29037
 
28872
29038
  // src/index.ts
@@ -28902,8 +29068,8 @@ ${r.stderr ?? ""}`).catch(() => "");
28902
29068
  function ghMultiAccountCaveat(announcedLogin) {
28903
29069
  try {
28904
29070
  const hostsPath = ghHostsConfigPath(process.env, process.platform);
28905
- if (!hostsPath || !(0, import_node_fs35.existsSync)(hostsPath)) return void 0;
28906
- return ghAccountCaveat(announcedLogin, parseGhHostsAccounts((0, import_node_fs35.readFileSync)(hostsPath, "utf8")));
29071
+ if (!hostsPath || !(0, import_node_fs36.existsSync)(hostsPath)) return void 0;
29072
+ return ghAccountCaveat(announcedLogin, parseGhHostsAccounts((0, import_node_fs36.readFileSync)(hostsPath, "utf8")));
28907
29073
  } catch {
28908
29074
  return void 0;
28909
29075
  }
@@ -28911,12 +29077,12 @@ function ghMultiAccountCaveat(announcedLogin) {
28911
29077
  var ENV_HEAL_LOCK_STALE_MS = 10 * 6e4;
28912
29078
  var ENV_HEAL_LOCK_MAX_WAIT_MS = 2 * 6e4;
28913
29079
  function envHealLockPath(home) {
28914
- return (0, import_node_path34.join)(home, ".claude", "plugins", ".mmi-env-heal.lock");
29080
+ return (0, import_node_path35.join)(home, ".claude", "plugins", ".mmi-env-heal.lock");
28915
29081
  }
28916
29082
  async function withEnvHealLock(what, run) {
28917
29083
  try {
28918
29084
  return await withFileLock(
28919
- envHealLockPath((0, import_node_os13.homedir)()),
29085
+ envHealLockPath((0, import_node_os14.homedir)()),
28920
29086
  { staleMs: ENV_HEAL_LOCK_STALE_MS, maxWaitMs: ENV_HEAL_LOCK_MAX_WAIT_MS, label: "mmi env-heal lock" },
28921
29087
  run
28922
29088
  );
@@ -29013,7 +29179,7 @@ function mmiDoctorDeps(opts = {}) {
29013
29179
  const configRoot = surfaceConfigRoot(surface);
29014
29180
  const running = surface === "codex" ? installedActivePluginVersion(surface) : runningPluginVersion(process.env, resolveClientVersion());
29015
29181
  const plan = buildPluginCachePlan(
29016
- (0, import_node_os13.homedir)(),
29182
+ (0, import_node_os14.homedir)(),
29017
29183
  running,
29018
29184
  pluginCacheFsDeps(configRoot, () => 0),
29019
29185
  { configRoot, includeStaging: surface !== "codex" }
@@ -29034,15 +29200,31 @@ function mmiDoctorDeps(opts = {}) {
29034
29200
  marketplaceRows: () => {
29035
29201
  try {
29036
29202
  if (detectSurface(process.env) === "codex") return [];
29037
- const home = (0, import_node_os13.homedir)();
29038
- return marketplaceRows(
29203
+ const home = (0, import_node_os14.homedir)();
29204
+ const rows = marketplaceRows(
29039
29205
  MMI_MARKETPLACE_NAME,
29040
- readFileSyncSafe((0, import_node_path34.join)(home, ...KNOWN_MARKETPLACES_RELATIVE), import_node_fs35.readFileSync),
29041
- readFileSyncSafe((0, import_node_path34.join)(home, ".claude", "settings.json"), import_node_fs35.readFileSync),
29206
+ readFileSyncSafe((0, import_node_path35.join)(home, ...KNOWN_MARKETPLACES_RELATIVE), import_node_fs36.readFileSync),
29207
+ readFileSyncSafe((0, import_node_path35.join)(home, ".claude", "settings.json"), import_node_fs36.readFileSync),
29042
29208
  // #3974: this CLI heals these rows, so report mode names the doctor run rather than the hand
29043
29209
  // edit. Unconditional — it is a fact about mmi-cli, not about the lane this run is on.
29044
29210
  true
29045
29211
  );
29212
+ const pending = readMarketplacePinPending(
29213
+ (0, import_node_path35.join)(home, ".claude", "plugins", ".mmi-marketplace-pin-pending.json"),
29214
+ MMI_MARKETPLACE_NAME
29215
+ );
29216
+ if (!pending) return rows;
29217
+ return rows.map((row) => {
29218
+ if (row.id !== "marketplace-catalog-ref" || row.ok) return row;
29219
+ const { fix: _fix, reportOnly: _reportOnly, ...rest } = row;
29220
+ return {
29221
+ ...rest,
29222
+ ok: true,
29223
+ warn: true,
29224
+ detail: `pending restart \u2014 doctor pinned main at ${pending.at}; restart Claude Code to load it`,
29225
+ verbose: [...row.verbose ?? [], "known_marketplaces.json was rewritten by the running Claude host after the verified doctor write"]
29226
+ };
29227
+ });
29046
29228
  } catch {
29047
29229
  return [];
29048
29230
  }
@@ -29052,7 +29234,13 @@ function mmiDoctorDeps(opts = {}) {
29052
29234
  healMarketplacePins: () => {
29053
29235
  try {
29054
29236
  if (detectSurface(process.env) === "codex") return void 0;
29055
- return applyOrgMarketplacePins((0, import_node_path34.join)((0, import_node_os13.homedir)(), ...KNOWN_MARKETPLACES_RELATIVE), [MMI_MARKETPLACE_NAME]);
29237
+ const home = (0, import_node_os14.homedir)();
29238
+ const names = [MMI_MARKETPLACE_NAME];
29239
+ const result = applyOrgMarketplacePins((0, import_node_path35.join)(home, ...KNOWN_MARKETPLACES_RELATIVE), names);
29240
+ if (result?.startsWith("pinned ")) {
29241
+ writeMarketplacePinPending((0, import_node_path35.join)(home, ".claude", "plugins", ".mmi-marketplace-pin-pending.json"), names);
29242
+ }
29243
+ return result;
29056
29244
  } catch {
29057
29245
  return void 0;
29058
29246
  }
@@ -29258,19 +29446,19 @@ program2.hook("preAction", async (_thisCommand, actionCommand) => {
29258
29446
  });
29259
29447
  var rules = program2.command("rules").description("org-managed .gitignore delivery");
29260
29448
  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) => {
29261
- const path2 = (0, import_node_path34.join)(process.cwd(), ".gitignore");
29262
- const current = (0, import_node_fs35.existsSync)(path2) ? (0, import_node_fs35.readFileSync)(path2, "utf8") : null;
29449
+ const path2 = (0, import_node_path35.join)(process.cwd(), ".gitignore");
29450
+ const current = (0, import_node_fs36.existsSync)(path2) ? (0, import_node_fs36.readFileSync)(path2, "utf8") : null;
29263
29451
  const plan = planManagedGitignore(current);
29264
29452
  const drift = [...plan.added.map((l) => `+${l}`), ...plan.removed.map((l) => `-${l}`)].join(", ") || "block normalize";
29265
29453
  if (opts.json) {
29266
- if (opts.write && plan.changed) (0, import_node_fs35.writeFileSync)(path2, plan.content, "utf8");
29454
+ if (opts.write && plan.changed) (0, import_node_fs36.writeFileSync)(path2, plan.content, "utf8");
29267
29455
  console.log(JSON.stringify(plan, null, 2));
29268
29456
  if (!opts.write && plan.changed) process.exitCode = 1;
29269
29457
  return;
29270
29458
  }
29271
29459
  if (opts.write) {
29272
29460
  if (plan.changed) {
29273
- (0, import_node_fs35.writeFileSync)(path2, plan.content, "utf8");
29461
+ (0, import_node_fs36.writeFileSync)(path2, plan.content, "utf8");
29274
29462
  console.log(`mmi-cli org rules gitignore: updated .gitignore (${drift})`);
29275
29463
  } else {
29276
29464
  console.log("mmi-cli org rules gitignore: up to date");
@@ -29425,8 +29613,8 @@ gcCmd.option("--dry-run", "show what would be deleted (default)").option("--appl
29425
29613
  if (!Number.isFinite(limit) || limit < 1) return fail("worktree gc: --limit must be a positive integer");
29426
29614
  let root;
29427
29615
  if (o.root !== void 0) {
29428
- root = (0, import_node_path34.resolve)(o.root);
29429
- 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`);
29616
+ root = (0, import_node_path35.resolve)(o.root);
29617
+ 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`);
29430
29618
  const gcRepoRoot = (await execFileP2("git", ["rev-parse", "--show-toplevel"], { timeout: GIT_TIMEOUT_MS }).catch(() => ({ stdout: "" }))).stdout.trim() || process.cwd();
29431
29619
  if (isPathUnderDirectory(gcRepoRoot, root)) {
29432
29620
  return fail(`worktree gc: --root ${root} contains this checkout \u2014 name a worktrees root, not the repo or an ancestor of it`);
@@ -29491,7 +29679,7 @@ async function primaryCheckoutRoot(from) {
29491
29679
  return primaryCheckoutRootOf(async (args) => (await execFileP2("git", ["-C", from, ...args], { timeout: GIT_TIMEOUT_MS })).stdout);
29492
29680
  }
29493
29681
  async function unprovenWorktreeReason(wtPath, repoRoot2) {
29494
- if (!(0, import_node_fs35.existsSync)(wtPath)) return `${wtPath} does not exist on disk`;
29682
+ if (!(0, import_node_fs36.existsSync)(wtPath)) return `${wtPath} does not exist on disk`;
29495
29683
  const porcelain = (await execFileP2("git", ["-C", repoRoot2, "worktree", "list", "--porcelain"], { timeout: GIT_TIMEOUT_MS }).catch(() => ({ stdout: "" }))).stdout;
29496
29684
  const registered = parseWorktreePorcelainEntries(porcelain);
29497
29685
  if (!registered.length) {
@@ -29505,6 +29693,7 @@ async function unprovenWorktreeReason(wtPath, repoRoot2) {
29505
29693
  function makeProvisionDeps(worktreeRoot, quiet, log) {
29506
29694
  return {
29507
29695
  runInstall: (command, cwd) => runWorktreeInstall(command, cwd, quiet),
29696
+ validateInstall: (cwd) => runWorktreeInstall("npm ls --depth=0 --json", cwd, true).then(() => true).catch(() => false),
29508
29697
  primaryCheckout: () => primaryCheckoutRoot(worktreeRoot),
29509
29698
  log
29510
29699
  };
@@ -29512,26 +29701,26 @@ function makeProvisionDeps(worktreeRoot, quiet, log) {
29512
29701
  function acquireWorktreeSetupLock(worktreeRoot) {
29513
29702
  const lockPath = repoRuntimeStatePath(worktreeRoot, "worktree-setup.lock");
29514
29703
  const take = () => {
29515
- const fd = (0, import_node_fs35.openSync)(lockPath, "wx");
29704
+ const fd = (0, import_node_fs36.openSync)(lockPath, "wx");
29516
29705
  try {
29517
- (0, import_node_fs35.writeSync)(fd, String(Date.now()));
29706
+ (0, import_node_fs36.writeSync)(fd, String(Date.now()));
29518
29707
  } finally {
29519
- (0, import_node_fs35.closeSync)(fd);
29708
+ (0, import_node_fs36.closeSync)(fd);
29520
29709
  }
29521
29710
  return () => {
29522
29711
  try {
29523
- (0, import_node_fs35.rmSync)(lockPath, { force: true });
29712
+ (0, import_node_fs36.rmSync)(lockPath, { force: true });
29524
29713
  } catch {
29525
29714
  }
29526
29715
  };
29527
29716
  };
29528
29717
  try {
29529
- (0, import_node_fs35.mkdirSync)((0, import_node_path34.dirname)(lockPath), { recursive: true });
29718
+ (0, import_node_fs36.mkdirSync)((0, import_node_path35.dirname)(lockPath), { recursive: true });
29530
29719
  return take();
29531
29720
  } catch {
29532
29721
  try {
29533
- if (Date.now() - (0, import_node_fs35.statSync)(lockPath).mtimeMs > WORKTREE_SETUP_LOCK_TTL_MS) {
29534
- (0, import_node_fs35.rmSync)(lockPath, { force: true });
29722
+ if (Date.now() - (0, import_node_fs36.statSync)(lockPath).mtimeMs > WORKTREE_SETUP_LOCK_TTL_MS) {
29723
+ (0, import_node_fs36.rmSync)(lockPath, { force: true });
29535
29724
  return take();
29536
29725
  }
29537
29726
  } catch {
@@ -29587,22 +29776,52 @@ withExamples(mutating(
29587
29776
  const localOnly = preferRemote && base !== preferRemote ? ` (no ${preferRemote} \u2014 local ref)` : "";
29588
29777
  console.error(` base ${base} ${baseSha}${localOnly}`);
29589
29778
  }
29590
- step = `git worktree add ${wtPath}`;
29591
- await addWorktreeRobust(wtPath, branch, base, {
29592
- git: async (args) => (await execFileP2("git", args, { timeout: GH_MUTATION_TIMEOUT_MS })).stdout,
29593
- revParse: async (ref) => {
29594
- try {
29595
- return (await execFileP2("git", ["rev-parse", "--verify", ref], { timeout: GIT_TIMEOUT_MS })).stdout.trim() || void 0;
29596
- } catch {
29597
- return void 0;
29779
+ const registered = parseWorktreePorcelainEntries((await execFileP2(
29780
+ "git",
29781
+ ["-C", repoRoot2, "worktree", "list", "--porcelain"],
29782
+ { timeout: GIT_TIMEOUT_MS }
29783
+ )).stdout);
29784
+ const exact = registered.find((entry) => samePath(entry.path, wtPath));
29785
+ let resumed = false;
29786
+ if (exact) {
29787
+ step = `resume existing worktree ${wtPath}`;
29788
+ if (exact.branch !== branch) {
29789
+ return fail(`worktree create: ${wtPath} is already registered for '${exact.branch ?? "detached HEAD"}', not '${branch}'`);
29790
+ }
29791
+ const status = (await execFileP2("git", ["-C", wtPath, "status", "--porcelain"], { timeout: GIT_TIMEOUT_MS })).stdout.trim();
29792
+ if (status) return fail(`worktree create: refusing to resume ${wtPath} \u2014 it has uncommitted changes`);
29793
+ const head = await revParseRef(`refs/heads/${branch}`);
29794
+ const baseOid = await revParseRef(base);
29795
+ if (!head || !baseOid) return fail(`worktree create: could not prove '${branch}' and '${base}' before resume`);
29796
+ const canFastForward = await execFileP2(
29797
+ "git",
29798
+ ["-C", repoRoot2, "merge-base", "--is-ancestor", head, baseOid],
29799
+ { timeout: GIT_TIMEOUT_MS }
29800
+ ).then(() => true).catch(() => false);
29801
+ if (!canFastForward) {
29802
+ return fail(`worktree create: refusing to resume '${branch}' \u2014 it has local commits or diverges from ${base}`);
29803
+ }
29804
+ await execFileP2("git", ["-C", wtPath, "merge", "--ff-only", base], { timeout: GIT_TIMEOUT_MS });
29805
+ resumed = true;
29806
+ }
29807
+ if (!resumed) {
29808
+ step = `git worktree add ${wtPath}`;
29809
+ await addWorktreeRobust(wtPath, branch, base, {
29810
+ git: async (args) => (await execFileP2("git", args, { timeout: GH_MUTATION_TIMEOUT_MS })).stdout,
29811
+ revParse: async (ref) => {
29812
+ try {
29813
+ return (await execFileP2("git", ["rev-parse", "--verify", ref], { timeout: GIT_TIMEOUT_MS })).stdout.trim() || void 0;
29814
+ } catch {
29815
+ return void 0;
29816
+ }
29817
+ },
29818
+ deleteBranch: (b) => execFileP2("git", ["branch", "-D", b], { timeout: GIT_TIMEOUT_MS }).then(() => void 0),
29819
+ sleep: (ms) => new Promise((resolve5) => setTimeout(resolve5, ms)),
29820
+ log: (m) => {
29821
+ if (!o.json) console.error(` ${m}`);
29598
29822
  }
29599
- },
29600
- deleteBranch: (b) => execFileP2("git", ["branch", "-D", b], { timeout: GIT_TIMEOUT_MS }).then(() => void 0),
29601
- sleep: (ms) => new Promise((resolve5) => setTimeout(resolve5, ms)),
29602
- log: (m) => {
29603
- if (!o.json) console.error(` ${m}`);
29604
- }
29605
- });
29823
+ });
29824
+ }
29606
29825
  step = "install deps + copy local-only config";
29607
29826
  const report = await provisionWorktree(wtPath, makeProvisionDeps(wtPath, Boolean(o.json), (m) => {
29608
29827
  if (!o.json) console.error(` ${m}`);
@@ -29642,12 +29861,13 @@ withExamples(mutating(
29642
29861
  branch,
29643
29862
  path: wtPath,
29644
29863
  base,
29864
+ resumed,
29645
29865
  ...report,
29646
29866
  ...issueForm && selector ? { issue: `${selector.repo}#${selector.number}` } : {},
29647
29867
  ...issueForm && o.claim ? { claim: claimError ? { ok: false, error: claimError } : claim } : {}
29648
29868
  }, null, 2));
29649
29869
  }
29650
- console.log(`worktree ready: ${wtPath} (branch ${branch} from ${base})`);
29870
+ console.log(`worktree ready: ${wtPath} (branch ${branch} ${resumed ? "resumed at" : "from"} ${base})`);
29651
29871
  console.log(` installed: ${report.installed.map((i) => i.dir || ".").join(", ") || "none"}`);
29652
29872
  console.log(` copied: ${report.copied.join(", ") || "none"}`);
29653
29873
  if (issueForm && o.claim && selector) {
@@ -30133,7 +30353,7 @@ project.command("set [owner/repo]").description("upsert project META (idempotent
30133
30353
  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`);
30134
30354
  if (o.secretsFile) {
30135
30355
  try {
30136
- vars.push(`secrets=${(0, import_node_fs35.readFileSync)(o.secretsFile, "utf8")}`);
30356
+ vars.push(`secrets=${(0, import_node_fs36.readFileSync)(o.secretsFile, "utf8")}`);
30137
30357
  } catch (e) {
30138
30358
  return fail(`org project set: cannot read --secrets-file ${o.secretsFile}: ${e.message}`);
30139
30359
  }
@@ -30881,11 +31101,11 @@ pr.command("view <number>").description("read a PR as structured JSON (merged st
30881
31101
  }
30882
31102
  });
30883
31103
  async function listCiWorkflowPaths(cwd = process.cwd()) {
30884
- const wfDir = (0, import_node_path34.join)(cwd, ".github", "workflows");
30885
- if (!(0, import_node_fs35.existsSync)(wfDir)) return [];
30886
- return (0, import_node_fs35.readdirSync)(wfDir).filter((name) => /\.(ya?ml)$/i.test(name)).filter((name) => {
31104
+ const wfDir = (0, import_node_path35.join)(cwd, ".github", "workflows");
31105
+ if (!(0, import_node_fs36.existsSync)(wfDir)) return [];
31106
+ return (0, import_node_fs36.readdirSync)(wfDir).filter((name) => /\.(ya?ml)$/i.test(name)).filter((name) => {
30887
31107
  try {
30888
- return workflowReportsPrChecks((0, import_node_fs35.readFileSync)((0, import_node_path34.join)(wfDir, name), "utf8"));
31108
+ return workflowReportsPrChecks((0, import_node_fs36.readFileSync)((0, import_node_path35.join)(wfDir, name), "utf8"));
30889
31109
  } catch {
30890
31110
  return true;
30891
31111
  }
@@ -30917,16 +31137,16 @@ function ciAuditDeps() {
30917
31137
  // gate re-seed step is skipped gracefully rather than failing mid-run.
30918
31138
  readSeedFile: (path2) => {
30919
31139
  if (!root) return null;
30920
- const fullPath = (0, import_node_path34.join)(root, path2);
30921
- return (0, import_node_fs35.existsSync)(fullPath) ? (0, import_node_fs35.readFileSync)(fullPath, "utf8") : null;
31140
+ const fullPath = (0, import_node_path35.join)(root, path2);
31141
+ return (0, import_node_fs36.existsSync)(fullPath) ? (0, import_node_fs36.readFileSync)(fullPath, "utf8") : null;
30922
31142
  }
30923
31143
  };
30924
31144
  }
30925
31145
  function hubRoot() {
30926
- const fromPkg = (0, import_node_path34.join)(__dirname, "..", "..");
31146
+ const fromPkg = (0, import_node_path35.join)(__dirname, "..", "..");
30927
31147
  const marker = "skills/bootstrap/seeds/manifest.json";
30928
- if ((0, import_node_fs35.existsSync)((0, import_node_path34.join)(fromPkg, marker))) return fromPkg;
30929
- if ((0, import_node_fs35.existsSync)((0, import_node_path34.join)(process.cwd(), marker))) return process.cwd();
31148
+ if ((0, import_node_fs36.existsSync)((0, import_node_path35.join)(fromPkg, marker))) return fromPkg;
31149
+ if ((0, import_node_fs36.existsSync)((0, import_node_path35.join)(process.cwd(), marker))) return process.cwd();
30930
31150
  return null;
30931
31151
  }
30932
31152
  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) => {
@@ -31055,6 +31275,10 @@ pr.command("land <number>").description("agent merge path (#1440): train probe \
31055
31275
  return false;
31056
31276
  }
31057
31277
  });
31278
+ if (result.status !== "failed") {
31279
+ const repoFlag = result.repo ? ` --repo ${result.repo}` : "";
31280
+ console.warn(`pr land: merge confirmed; cleanup pending \u2014 if this process is interrupted, resume with: mmi-cli pr merge ${number}${repoFlag} --squash`);
31281
+ }
31058
31282
  if (result.status !== "failed") {
31059
31283
  try {
31060
31284
  const { stdout } = await execFileP2(process.execPath, [
@@ -31229,7 +31453,7 @@ jsonParity(pr.command("merge <number>").description("merge a PR (squash by defau
31229
31453
  localCleanup = await cleanupPrMergeLocalBranch(headRef, {
31230
31454
  beforeWorktrees,
31231
31455
  startingPath,
31232
- pathExists: (p) => (0, import_node_fs35.existsSync)(p),
31456
+ pathExists: (p) => (0, import_node_fs36.existsSync)(p),
31233
31457
  execGit: async (args) => (await execFileP2("git", args, { timeout: GIT_TIMEOUT_MS })).stdout,
31234
31458
  teardownWorktreeStage,
31235
31459
  deferredStore,
@@ -31704,12 +31928,12 @@ access.command("audit").description("audit collaborator roles + train-branch pus
31704
31928
  targets = resolution.targets;
31705
31929
  }
31706
31930
  const derivedMatrix = registryProjects ? accessMatrixFromProjects(registryProjects) : {};
31707
- const fileMatrix = (0, import_node_fs35.existsSync)("access-matrix.json") ? loadAccessMatrix((0, import_node_fs35.readFileSync)("access-matrix.json", "utf8")) : {};
31931
+ const fileMatrix = (0, import_node_fs36.existsSync)("access-matrix.json") ? loadAccessMatrix((0, import_node_fs36.readFileSync)("access-matrix.json", "utf8")) : {};
31708
31932
  const matrix = mergeAccessMatrix(fileMatrix, derivedMatrix);
31709
31933
  const derivedContracts = registryProjects ? dataAccessContractsFromProjects(registryProjects) : { consumers: {} };
31710
- const fileContracts = (0, import_node_fs35.existsSync)("data-access-contracts.json") ? loadDataAccessContracts((0, import_node_fs35.readFileSync)("data-access-contracts.json", "utf8")) : { consumers: {} };
31934
+ const fileContracts = (0, import_node_fs36.existsSync)("data-access-contracts.json") ? loadDataAccessContracts((0, import_node_fs36.readFileSync)("data-access-contracts.json", "utf8")) : { consumers: {} };
31711
31935
  const dataAccess = mergeDataAccessContracts(fileContracts, derivedContracts);
31712
- const sanctioned = (0, import_node_fs35.existsSync)("access-matrix.json") ? loadSanctionedAdmins((0, import_node_fs35.readFileSync)("access-matrix.json", "utf8")) : {};
31936
+ const sanctioned = (0, import_node_fs36.existsSync)("access-matrix.json") ? loadSanctionedAdmins((0, import_node_fs36.readFileSync)("access-matrix.json", "utf8")) : {};
31713
31937
  const report = await auditOrgAccess(targets, deps, matrix, dataAccess, sanctioned);
31714
31938
  console.log(o.json ? JSON.stringify(report, null, 2) : renderAccessReport(report));
31715
31939
  if (!report.ok) process.exitCode = 1;
@@ -31741,16 +31965,16 @@ function directoryBytes(path2) {
31741
31965
  let total = 0;
31742
31966
  let entries;
31743
31967
  try {
31744
- entries = (0, import_node_fs35.readdirSync)(path2, { withFileTypes: true });
31968
+ entries = (0, import_node_fs36.readdirSync)(path2, { withFileTypes: true });
31745
31969
  } catch {
31746
31970
  return 0;
31747
31971
  }
31748
31972
  for (const entry of entries) {
31749
- const child2 = (0, import_node_path34.join)(path2, entry.name);
31973
+ const child2 = (0, import_node_path35.join)(path2, entry.name);
31750
31974
  if (entry.isDirectory()) total += directoryBytes(child2);
31751
31975
  else {
31752
31976
  try {
31753
- total += (0, import_node_fs35.statSync)(child2).size;
31977
+ total += (0, import_node_fs36.statSync)(child2).size;
31754
31978
  } catch {
31755
31979
  }
31756
31980
  }
@@ -31758,25 +31982,25 @@ function directoryBytes(path2) {
31758
31982
  return total;
31759
31983
  }
31760
31984
  function listDirEntries(dir) {
31761
- return (0, import_node_fs35.readdirSync)(dir, { withFileTypes: true }).map((d) => ({ name: d.name, isDirectory: d.isDirectory() }));
31985
+ return (0, import_node_fs36.readdirSync)(dir, { withFileTypes: true }).map((d) => ({ name: d.name, isDirectory: d.isDirectory() }));
31762
31986
  }
31763
31987
  function readInstalledPluginRefs(configRoot) {
31764
31988
  const p = installedPluginsPathForConfig(configRoot);
31765
- if (!(0, import_node_fs35.existsSync)(p)) return [];
31989
+ if (!(0, import_node_fs36.existsSync)(p)) return [];
31766
31990
  try {
31767
- return installedPluginPaths((0, import_node_fs35.readFileSync)(p, "utf8"));
31991
+ return installedPluginPaths((0, import_node_fs36.readFileSync)(p, "utf8"));
31768
31992
  } catch {
31769
31993
  return null;
31770
31994
  }
31771
31995
  }
31772
31996
  function pluginCacheFsDeps(configRoot, dirBytes) {
31773
31997
  return {
31774
- exists: (p) => (0, import_node_fs35.existsSync)(p),
31775
- listVersionDirs: (root) => (0, import_node_fs35.readdirSync)(root, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name),
31998
+ exists: (p) => (0, import_node_fs36.existsSync)(p),
31999
+ listVersionDirs: (root) => (0, import_node_fs36.readdirSync)(root, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name),
31776
32000
  dirBytes,
31777
- listStagingDirs: (root) => (0, import_node_fs35.readdirSync)(root, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => {
32001
+ listStagingDirs: (root) => (0, import_node_fs36.readdirSync)(root, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => {
31778
32002
  try {
31779
- return { name: d.name, mtimeMs: newestMtimeMs((0, import_node_path34.join)(root, d.name), listDirEntries, (p) => (0, import_node_fs35.statSync)(p).mtimeMs) };
32003
+ return { name: d.name, mtimeMs: newestMtimeMs((0, import_node_path35.join)(root, d.name), listDirEntries, (p) => (0, import_node_fs36.statSync)(p).mtimeMs) };
31780
32004
  } catch {
31781
32005
  return { name: d.name, mtimeMs: Date.now() };
31782
32006
  }
@@ -31790,10 +32014,10 @@ function stagingApplyFsGuard(configRoot) {
31790
32014
  return {
31791
32015
  referencedPaths: () => readInstalledPluginRefs(configRoot),
31792
32016
  mtimeMs: (name) => {
31793
- const p = (0, import_node_path34.join)(stagingRoot, name);
31794
- if (!(0, import_node_fs35.existsSync)(p)) return null;
32017
+ const p = (0, import_node_path35.join)(stagingRoot, name);
32018
+ if (!(0, import_node_fs36.existsSync)(p)) return null;
31795
32019
  try {
31796
- return newestMtimeMs(p, listDirEntries, (q) => (0, import_node_fs35.statSync)(q).mtimeMs);
32020
+ return newestMtimeMs(p, listDirEntries, (q) => (0, import_node_fs36.statSync)(q).mtimeMs);
31797
32021
  } catch {
31798
32022
  return null;
31799
32023
  }
@@ -31813,13 +32037,13 @@ program2.command("plugin-prune").description(`prune stale cached MMI plugin vers
31813
32037
  return;
31814
32038
  }
31815
32039
  const plan = buildPluginCachePlan(
31816
- (0, import_node_os13.homedir)(),
32040
+ (0, import_node_os14.homedir)(),
31817
32041
  running,
31818
32042
  pluginCacheFsDeps(configRoot, directoryBytes),
31819
32043
  { withBytes: true, configRoot, includeStaging: surface !== "codex" }
31820
32044
  );
31821
32045
  const anythingToDelete = plan.prune.length > 0 || plan.staging.length > 0;
31822
- const result = o.apply && anythingToDelete ? applyPluginCachePlan(plan, (p) => (0, import_node_fs35.rmSync)(p, { recursive: true, force: true }), stagingApplyFsGuard(configRoot)) : void 0;
32046
+ const result = o.apply && anythingToDelete ? applyPluginCachePlan(plan, (p) => (0, import_node_fs36.rmSync)(p, { recursive: true, force: true }), stagingApplyFsGuard(configRoot)) : void 0;
31823
32047
  const warnings = plan.prune.length > 0 ? [CONCURRENT_SESSION_WARNING] : [];
31824
32048
  if (o.json) console.log(JSON.stringify({ ...plan, warnings, applied: result ?? null }));
31825
32049
  else console.log(renderPluginCachePlan(plan, result));