@mutmutco/cli 3.3.0 → 3.5.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 +698 -477
  2. package/package.json +1 -1
package/dist/main.cjs CHANGED
@@ -3408,7 +3408,7 @@ var program = new Command();
3408
3408
 
3409
3409
  // src/index.ts
3410
3410
  var import_promises3 = require("node:fs/promises");
3411
- var import_node_fs18 = require("node:fs");
3411
+ var import_node_fs19 = require("node:fs");
3412
3412
 
3413
3413
  // src/bootstrap-org-ruleset.ts
3414
3414
  var ORG_NO_AGENT_FILES_RULESET_NAME = "mmi-no-agent-files-org";
@@ -3502,7 +3502,7 @@ async function fetchOrgNoAgentFilesRuleset(client, org = ORG_LOGIN) {
3502
3502
  }
3503
3503
 
3504
3504
  // src/index.ts
3505
- var import_node_child_process11 = require("node:child_process");
3505
+ var import_node_child_process12 = require("node:child_process");
3506
3506
 
3507
3507
  // src/cli-shared.ts
3508
3508
  var import_node_child_process3 = require("node:child_process");
@@ -3605,7 +3605,7 @@ function hardExit(code) {
3605
3605
  async function cleanExit(code) {
3606
3606
  process.exitCode = code;
3607
3607
  await closeHttpPool();
3608
- await new Promise((resolve6) => setImmediate(resolve6));
3608
+ await new Promise((resolve5) => setImmediate(resolve5));
3609
3609
  process.exit(code);
3610
3610
  }
3611
3611
  async function failGraceful(msg) {
@@ -3770,7 +3770,7 @@ async function fetchWithRetry(fetchImpl, url, init, opts = {}) {
3770
3770
  const attempts = opts.attempts ?? 3;
3771
3771
  const baseDelayMs = opts.baseDelayMs ?? 250;
3772
3772
  const retryOn = opts.retryOn ?? ((res) => res.status >= 500);
3773
- const sleep = opts.sleep ?? ((ms) => new Promise((resolve6) => setTimeout(resolve6, ms)));
3773
+ const sleep = opts.sleep ?? ((ms) => new Promise((resolve5) => setTimeout(resolve5, ms)));
3774
3774
  let lastErr;
3775
3775
  for (let i = 0; i < attempts; i++) {
3776
3776
  const isLast = i === attempts - 1;
@@ -3914,8 +3914,8 @@ async function readStdin(opts = {}) {
3914
3914
  })().catch(() => {
3915
3915
  });
3916
3916
  let timer;
3917
- const timeout = new Promise((resolve6) => {
3918
- timer = setTimeout(resolve6, timeoutMs);
3917
+ const timeout = new Promise((resolve5) => {
3918
+ timer = setTimeout(resolve5, timeoutMs);
3919
3919
  });
3920
3920
  try {
3921
3921
  await Promise.race([drain, timeout]);
@@ -5257,7 +5257,7 @@ async function setBoardItemPriority(client, cfg, itemId, priority) {
5257
5257
  await updateItemSingleSelect(client, cfg.projectId, itemId, cfg.priorityFieldId, optionId);
5258
5258
  return cliPriorityToFieldName(priority);
5259
5259
  }
5260
- var defaultRetrySleep = (ms) => new Promise((resolve6) => setTimeout(resolve6, ms));
5260
+ var defaultRetrySleep = (ms) => new Promise((resolve5) => setTimeout(resolve5, ms));
5261
5261
  async function resolveProjectItemIdWithRetry(client, cfg, selector, opts = {}) {
5262
5262
  const attempts = Math.max(1, opts.attempts ?? 5);
5263
5263
  const delayMs = opts.delayMs ?? 300;
@@ -5861,7 +5861,7 @@ function whoamiLine(report) {
5861
5861
  }
5862
5862
 
5863
5863
  // src/index.ts
5864
- var import_node_path18 = require("node:path");
5864
+ var import_node_path19 = require("node:path");
5865
5865
 
5866
5866
  // src/merge-ci-policy.ts
5867
5867
  function resolveMergeCiPolicy(input) {
@@ -6265,11 +6265,10 @@ function parseOwnerRepo(repo) {
6265
6265
  if (owner.includes("\\") || name.includes("\\")) throw new Error("repo must be owner/repo");
6266
6266
  return { owner, name, slug: name.toLowerCase(), fullName: `${owner}/${name}` };
6267
6267
  }
6268
- var DEFAULT_INSTALL_CMD = "npm ci";
6269
6268
  var DEFAULT_GATE_CMD = "npm run check";
6270
6269
  var DEFAULT_GATE_PY_VERSION = "3.11";
6271
6270
  var GATE_RUNTIME_DEFAULTS = {
6272
- node: { cmd: DEFAULT_GATE_CMD, install: DEFAULT_INSTALL_CMD },
6271
+ node: { cmd: DEFAULT_GATE_CMD, install: "npm ci" },
6273
6272
  python: { cmd: "pytest", install: 'pip install -e ".[dev]"' }
6274
6273
  };
6275
6274
  function gateSeedVars(cls, releaseTrack, runtime = "node") {
@@ -6311,7 +6310,6 @@ function withDerivedRepoVars(vars, parsed, cls, releaseTrack) {
6311
6310
  out.REPO_NAME ??= parsed.name;
6312
6311
  out.REPO_SLUG ??= parsed.slug;
6313
6312
  out.CLASS ??= cls;
6314
- out.INSTALL_CMD ??= DEFAULT_INSTALL_CMD;
6315
6313
  const track = releaseTrack ?? resolveBootstrapReleaseTrack(cls);
6316
6314
  const runtime = out.GATE_RUNTIME === "python" ? "python" : "node";
6317
6315
  for (const [key, value] of Object.entries(gateSeedVars(cls, track, runtime))) {
@@ -7022,7 +7020,7 @@ async function mergeAutoWithTransientRetry(prNumber, repo, deps) {
7022
7020
  if (first.mergeStatus !== "failed") return first;
7023
7021
  const ready = await deps.probeMergeReady(prNumber, repo).catch(() => ({ open: false, mergeable: false, checksPassing: false }));
7024
7022
  if (!ready.open || !ready.mergeable || !ready.checksPassing) return first;
7025
- const sleep = deps.sleep ?? ((ms) => new Promise((resolve6) => setTimeout(resolve6, ms)));
7023
+ const sleep = deps.sleep ?? ((ms) => new Promise((resolve5) => setTimeout(resolve5, ms)));
7026
7024
  await sleep(PR_LAND_MERGE_RETRY_DELAY_MS);
7027
7025
  const retried = await deps.mergeAuto(prNumber, repo);
7028
7026
  if (retried.mergeStatus !== "failed") return retried;
@@ -7031,7 +7029,7 @@ async function mergeAutoWithTransientRetry(prNumber, repo, deps) {
7031
7029
  async function readGhPrStateWithRetry(fetchState, options) {
7032
7030
  const retries = options?.retries ?? PR_LAND_STATE_READ_RETRIES;
7033
7031
  const delayMs = options?.delayMs ?? PR_LAND_STATE_READ_DELAY_MS;
7034
- const sleep = options?.sleep ?? ((ms) => new Promise((resolve6) => setTimeout(resolve6, ms)));
7032
+ const sleep = options?.sleep ?? ((ms) => new Promise((resolve5) => setTimeout(resolve5, ms)));
7035
7033
  let lastError = "empty state";
7036
7034
  for (let attempt = 0; attempt < retries; attempt++) {
7037
7035
  try {
@@ -7164,7 +7162,7 @@ async function sweepDeferredWorktreesWithRetry(store, deps, opts = {}) {
7164
7162
  }
7165
7163
  return last;
7166
7164
  }
7167
- var defaultSleep = (ms) => new Promise((resolve6) => setTimeout(resolve6, ms));
7165
+ var defaultSleep = (ms) => new Promise((resolve5) => setTimeout(resolve5, ms));
7168
7166
  async function removeWorktreeWithRecovery(wtPath, deps) {
7169
7167
  const maxAttempts = deps.maxAttempts ?? 3;
7170
7168
  const backoff = deps.backoffMs ?? [250, 1e3];
@@ -8035,7 +8033,7 @@ var execFileP4 = (0, import_node_util6.promisify)(import_node_child_process6.exe
8035
8033
  var DOCKER_TIMEOUT_MS = 15e3;
8036
8034
  var EARLY_EXIT_GRACE_MS = 2e3;
8037
8035
  function waitForProcessStability(child, graceMs = EARLY_EXIT_GRACE_MS) {
8038
- return new Promise((resolve6, reject) => {
8036
+ return new Promise((resolve5, reject) => {
8039
8037
  let settled = false;
8040
8038
  const finish = (fn) => {
8041
8039
  if (settled) return;
@@ -8045,7 +8043,7 @@ function waitForProcessStability(child, graceMs = EARLY_EXIT_GRACE_MS) {
8045
8043
  child.removeAllListeners("exit");
8046
8044
  fn();
8047
8045
  };
8048
- const timer = setTimeout(() => finish(resolve6), graceMs);
8046
+ const timer = setTimeout(() => finish(resolve5), graceMs);
8049
8047
  child.on("error", (err) => finish(() => reject(new Error(`stage process failed to start: ${err.message}`))));
8050
8048
  child.on("exit", (code, signal) => {
8051
8049
  const detail = code != null ? `code ${code}` : signal ? `signal ${signal}` : "unknown reason";
@@ -8255,10 +8253,10 @@ function pickStagePort(range, isFree) {
8255
8253
  throw new Error(`no free stage port in range ${start}-${end} \u2014 every port is in use`);
8256
8254
  }
8257
8255
  function isPortFree(port) {
8258
- return new Promise((resolve6) => {
8256
+ return new Promise((resolve5) => {
8259
8257
  const srv = (0, import_node_net.createServer)();
8260
- srv.once("error", () => resolve6(false));
8261
- srv.once("listening", () => srv.close(() => resolve6(true)));
8258
+ srv.once("error", () => resolve5(false));
8259
+ srv.once("listening", () => srv.close(() => resolve5(true)));
8262
8260
  srv.listen(port, "127.0.0.1");
8263
8261
  });
8264
8262
  }
@@ -8469,7 +8467,7 @@ async function killTree(pid) {
8469
8467
  } catch {
8470
8468
  }
8471
8469
  }
8472
- await new Promise((resolve6) => setTimeout(resolve6, 500));
8470
+ await new Promise((resolve5) => setTimeout(resolve5, 500));
8473
8471
  try {
8474
8472
  process.kill(-pid, "SIGKILL");
8475
8473
  } catch {
@@ -8490,7 +8488,7 @@ async function waitForHealth(url, timeoutMs, anyStatus = false) {
8490
8488
  } catch (e) {
8491
8489
  last = e.message;
8492
8490
  }
8493
- await new Promise((resolve6) => setTimeout(resolve6, 1e3));
8491
+ await new Promise((resolve5) => setTimeout(resolve5, 1e3));
8494
8492
  }
8495
8493
  throw new Error(`stage health check timed out for ${url}${last ? ` (${last})` : ""}`);
8496
8494
  }
@@ -8907,6 +8905,14 @@ ${line}
8907
8905
  `;
8908
8906
  }
8909
8907
 
8908
+ // src/issue-view-json.ts
8909
+ var DEFAULT_ISSUE_VIEW_FIELDS = "number,title,state,url,labels,author,assignees,milestone,body";
8910
+ function normalizeIssueViewJsonFields(tokens2) {
8911
+ if (!tokens2 || tokens2.length === 0) return DEFAULT_ISSUE_VIEW_FIELDS;
8912
+ const fields = tokens2.flatMap((token) => token.split(/[,\s]+/)).map((field) => field.trim()).filter(Boolean);
8913
+ return fields.length ? fields.join(",") : DEFAULT_ISSUE_VIEW_FIELDS;
8914
+ }
8915
+
8910
8916
  // src/sub-issue.ts
8911
8917
  function parseIssueRef(ref) {
8912
8918
  const trimmed = ref.trim();
@@ -9507,7 +9513,7 @@ function bootstrapPlan(repo, repoClass) {
9507
9513
  { label: `apply branch protection / allowlist: ${protectedBranches}`, gated: true },
9508
9514
  { label: "attach GitHub Project v2 and register Hub registry META", gated: true },
9509
9515
  { label: "seed README.md and architecture.md", gated: true },
9510
- { label: "commit .cursor/rules/<repo>.mdc and .cursor/environment.json", gated: true },
9516
+ { label: "commit .cursor/rules/<repo>.mdc", gated: true },
9511
9517
  { label: `register fanout target on ${repoClass === "content" ? "main" : "development"}`, gated: true }
9512
9518
  ];
9513
9519
  }
@@ -9801,7 +9807,7 @@ async function runMergeTreePreflight(deps, ours, theirs) {
9801
9807
  async function predictMergeConflicts(deps, ours, theirs) {
9802
9808
  return runMergeTreePreflight(deps, ours, theirs);
9803
9809
  }
9804
- async function mergeWithToleratedResolution(deps, sourceRef, label, resolve6, extraTolerated = []) {
9810
+ async function mergeWithToleratedResolution(deps, sourceRef, label, resolve5, extraTolerated = []) {
9805
9811
  try {
9806
9812
  await deps.run("git", ["merge", sourceRef, "--no-edit"]);
9807
9813
  return;
@@ -9815,7 +9821,7 @@ async function mergeWithToleratedResolution(deps, sourceRef, label, resolve6, ex
9815
9821
  unmerged.length === 0 ? `${label} merge failed without conflicted paths \u2014 merge aborted; inspect the repo state and rerun` : `${label} merge conflicts on untolerated path(s): ${blocking.join(", ")} \u2014 merge aborted (the train is misaligned; reconcile the branches via an approved alignment PR, then rerun)`
9816
9822
  );
9817
9823
  }
9818
- await deps.run("git", ["checkout", `--${resolve6}`, "--", ...unmerged]);
9824
+ await deps.run("git", ["checkout", `--${resolve5}`, "--", ...unmerged]);
9819
9825
  await deps.run("git", ["add", "--", ...unmerged]);
9820
9826
  await deps.run("git", ["commit", "--no-edit"]);
9821
9827
  }
@@ -9981,7 +9987,7 @@ function requireProjectMetaForTrain(load, repo) {
9981
9987
  var CORRELATE_ATTEMPTS = 5;
9982
9988
  var CORRELATE_DELAY_MS = 1500;
9983
9989
  var CORRELATE_SKEW_SLACK_MS = 1e4;
9984
- var defaultSleep2 = (ms) => new Promise((resolve6) => setTimeout(resolve6, ms));
9990
+ var defaultSleep2 = (ms) => new Promise((resolve5) => setTimeout(resolve5, ms));
9985
9991
  function resolveSleep(deps) {
9986
9992
  return deps.sleep ?? defaultSleep2;
9987
9993
  }
@@ -10898,7 +10904,7 @@ async function retireRcRuntime(deps, ctx, model, deployStatus, releasedRcSha) {
10898
10904
  }
10899
10905
  async function runTenantRedeploy(deps, options) {
10900
10906
  const { stage: stage2 } = options;
10901
- const ref = options.ref ?? stage2;
10907
+ const ref = options.ref ?? (stage2 === "dev" ? "development" : stage2);
10902
10908
  const watch = options.watch ?? false;
10903
10909
  const repo = options.repo;
10904
10910
  const [owner, name] = repo.split("/");
@@ -11147,7 +11153,7 @@ function clean2(out) {
11147
11153
  return out.trim();
11148
11154
  }
11149
11155
  function sleeper(deps) {
11150
- return deps.sleep ?? ((ms) => new Promise((resolve6) => setTimeout(resolve6, ms)));
11156
+ return deps.sleep ?? ((ms) => new Promise((resolve5) => setTimeout(resolve5, ms)));
11151
11157
  }
11152
11158
  function normalizeHotfixVersion(input) {
11153
11159
  const m = /^v?(\d+\.\d+\.\d+)$/.exec(input.trim());
@@ -11582,12 +11588,46 @@ async function findInFlightHotfixVersion(deps, ctx, latestMainTag) {
11582
11588
  return { inFlight: null, superseded };
11583
11589
  }
11584
11590
 
11585
- // src/release-announce.ts
11586
- var ANNOUNCE_REPO = "mutmutco/MMI-Hub";
11591
+ // src/slack-alert.ts
11587
11592
  var SSM_REGION = "eu-central-1";
11588
11593
  var SSM_TOKEN_PARAM = "/mmi-future/_org/slack/SLACK_BOT_TOKEN";
11589
11594
  var SSM_CHANNEL_PARAM = "/mmi-future/_org/slack/SLACK_ALERTS_CHANNEL";
11590
11595
  var SLACK_TIMEOUT_MS = 1e4;
11596
+ async function ssmParameter(deps, name, decrypt) {
11597
+ const args = [
11598
+ "ssm",
11599
+ "get-parameter",
11600
+ "--region",
11601
+ SSM_REGION,
11602
+ "--name",
11603
+ name,
11604
+ "--query",
11605
+ "Parameter.Value",
11606
+ "--output",
11607
+ "text",
11608
+ ...decrypt ? ["--with-decryption"] : []
11609
+ ];
11610
+ const value = (await deps.run("aws", args)).trim();
11611
+ if (!value || value === "None") throw new Error(`SSM parameter ${name} is empty`);
11612
+ return value;
11613
+ }
11614
+ async function postToAlertsChannel(deps, text) {
11615
+ const token = await ssmParameter(deps, SSM_TOKEN_PARAM, true);
11616
+ const channel = await ssmParameter(deps, SSM_CHANNEL_PARAM, false);
11617
+ const fetchImpl = deps.fetchImpl ?? fetch;
11618
+ const res = await fetchImpl("https://slack.com/api/chat.postMessage", {
11619
+ method: "POST",
11620
+ headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json; charset=utf-8" },
11621
+ body: JSON.stringify({ channel, text, unfurl_links: false }),
11622
+ signal: AbortSignal.timeout(SLACK_TIMEOUT_MS)
11623
+ });
11624
+ const json = await res.json().catch(() => ({}));
11625
+ if (!json.ok) throw new Error(`slack postMessage failed: ${json.error ?? `http ${res.status}`}`);
11626
+ return { status: "posted", note: "posted to the alerts channel" };
11627
+ }
11628
+
11629
+ // src/release-announce.ts
11630
+ var ANNOUNCE_REPO = "mutmutco/MMI-Hub";
11591
11631
  var MAX_BULLETS = 6;
11592
11632
  var MAX_SUMMARY_LINES = 8;
11593
11633
  function neutralizeHubProductBrands(text) {
@@ -11662,24 +11702,6 @@ function formatAnnouncement(args) {
11662
11702
  function summaryFileLines(content) {
11663
11703
  return content.split("\n").map((l) => l.trim()).filter(Boolean).slice(0, MAX_SUMMARY_LINES).map((l) => escapeMrkdwn(neutralizeHubProductBrands(l.replace(/^[•*-]\s*/, "")))).filter(Boolean).map((l) => `\u2022 ${l}`);
11664
11704
  }
11665
- async function ssmParameter(deps, name, decrypt) {
11666
- const args = [
11667
- "ssm",
11668
- "get-parameter",
11669
- "--region",
11670
- SSM_REGION,
11671
- "--name",
11672
- name,
11673
- "--query",
11674
- "Parameter.Value",
11675
- "--output",
11676
- "text",
11677
- ...decrypt ? ["--with-decryption"] : []
11678
- ];
11679
- const value = (await deps.run("aws", args)).trim();
11680
- if (!value || value === "None") throw new Error(`SSM parameter ${name} is empty`);
11681
- return value;
11682
- }
11683
11705
  async function announceRelease(deps, args) {
11684
11706
  if (args.repo !== ANNOUNCE_REPO) {
11685
11707
  return { status: "skipped", note: `announce is Hub-only \u2014 ${args.repo} skipped` };
@@ -11701,18 +11723,8 @@ async function announceRelease(deps, args) {
11701
11723
  }
11702
11724
  if (lines.length === 0) lines = curateReleaseNotes(view.body ?? "");
11703
11725
  if (lines.length === 0) lines = ["\u2022 maintenance release \u2014 see the notes for details"];
11704
- const token = await ssmParameter(deps, SSM_TOKEN_PARAM, true);
11705
- const channel = await ssmParameter(deps, SSM_CHANNEL_PARAM, false);
11706
11726
  const text = formatAnnouncement({ repo: args.repo, tag: args.tag, lines, releaseUrl });
11707
- const fetchImpl = deps.fetchImpl ?? fetch;
11708
- const res = await fetchImpl("https://slack.com/api/chat.postMessage", {
11709
- method: "POST",
11710
- headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json; charset=utf-8" },
11711
- body: JSON.stringify({ channel, text, unfurl_links: false }),
11712
- signal: AbortSignal.timeout(SLACK_TIMEOUT_MS)
11713
- });
11714
- const json = await res.json().catch(() => ({}));
11715
- if (!json.ok) throw new Error(`slack postMessage failed: ${json.error ?? `http ${res.status}`}`);
11727
+ await postToAlertsChannel(deps, text);
11716
11728
  return { status: "announced", note: `announced ${args.tag} to the alerts channel` };
11717
11729
  } catch (e) {
11718
11730
  return { status: "failed", note: `announce failed (release unaffected): ${e.message}` };
@@ -12269,7 +12281,6 @@ async function verifyBootstrap(repo, repoClass, deps, releaseTrack) {
12269
12281
  });
12270
12282
  }
12271
12283
  }
12272
- checks.push({ ok: await contentExists2(deps, repo, baseBranch, ".cursor/environment.json"), label: "Cursor environment committed" });
12273
12284
  const readme = await contentText(deps, repo, baseBranch, "README.md");
12274
12285
  checks.push({
12275
12286
  ok: readme !== null && readme.includes("## Agent context"),
@@ -12678,26 +12689,112 @@ async function wikiPublish(deps, opts) {
12678
12689
  deps.err("wiki publish: <pages-dir> is required");
12679
12690
  return false;
12680
12691
  }
12681
- const script = deps.scriptPath();
12682
- if (!deps.scriptExists(script)) {
12683
- deps.err(`wiki publish: publish script not found at ${script} \u2014 run from the repo checkout root`);
12684
- return false;
12685
- }
12686
12692
  const minted = await deps.mint(opts.repo);
12687
12693
  if (!minted.ok) {
12688
12694
  deps.err(`wiki publish failed: could not mint a scoped wiki token for ${opts.repo}: ${minted.error}`);
12689
12695
  return false;
12690
12696
  }
12691
- const env = { ...process.env, WIKI_REPO: opts.repo, WIKI_TOKEN: minted.mint.token };
12692
- const code = deps.spawn("node", [script, opts.pagesDir], env);
12693
- if (code !== 0) {
12694
- deps.err(`wiki publish failed: publish script exited ${code} (token was scoped to ${opts.repo}, contents:write, expires ${minted.mint.expiresAt})`);
12697
+ const ok = deps.publish({ repo: opts.repo, token: minted.mint.token, pagesDir: opts.pagesDir });
12698
+ if (!ok) {
12695
12699
  return false;
12696
12700
  }
12697
12701
  deps.log(`wiki published for ${opts.repo} (scoped token, expired/discarded)`);
12698
12702
  return true;
12699
12703
  }
12700
12704
 
12705
+ // src/wiki-publish-git.ts
12706
+ var import_node_child_process8 = require("node:child_process");
12707
+ var import_node_fs13 = require("node:fs");
12708
+ var import_node_os5 = require("node:os");
12709
+ var import_node_path14 = require("node:path");
12710
+ var WIKI_STAMP_FILE = ".wiki-state.json";
12711
+ function redactWikiSecrets(msg) {
12712
+ return msg.replace(/x-access-token:[^@\s]+@/gi, "x-access-token:***@").replace(/(AUTHORIZATION:\s*basic\s+)[A-Za-z0-9+/=]+/gi, "$1***");
12713
+ }
12714
+ function publishWikiViaGit(deps, opts) {
12715
+ const dir = deps.mkdtemp();
12716
+ const url = `https://github.com/${opts.repo}.wiki.git`;
12717
+ const authArgs = [
12718
+ "-c",
12719
+ `http.extraheader=AUTHORIZATION: basic ${Buffer.from(`x-access-token:${opts.token}`).toString("base64")}`
12720
+ ];
12721
+ try {
12722
+ deps.gitRun(["init", "-q"], dir);
12723
+ deps.gitRun(["remote", "add", "origin", url], dir);
12724
+ if (deps.gitProbe([...authArgs, "fetch", "--depth", "1", "origin", "master"], dir)) {
12725
+ deps.gitRun(["reset", "--hard", "FETCH_HEAD"], dir);
12726
+ }
12727
+ let copied = 0;
12728
+ for (const f of deps.readdir(opts.pagesDir)) {
12729
+ if (f.endsWith(".md")) {
12730
+ deps.copyFile((0, import_node_path14.join)(opts.pagesDir, f), (0, import_node_path14.join)(dir, f));
12731
+ copied++;
12732
+ }
12733
+ }
12734
+ if (!copied) {
12735
+ deps.err(`wiki publish: no .md pages found in ${opts.pagesDir}`);
12736
+ return false;
12737
+ }
12738
+ if (deps.fileExists((0, import_node_path14.join)(opts.pagesDir, WIKI_STAMP_FILE))) {
12739
+ deps.copyFile((0, import_node_path14.join)(opts.pagesDir, WIKI_STAMP_FILE), (0, import_node_path14.join)(dir, WIKI_STAMP_FILE));
12740
+ }
12741
+ deps.gitRun(["add", "-A"], dir);
12742
+ if (deps.gitProbe(["diff", "--cached", "--quiet"], dir)) {
12743
+ deps.log("wiki publish: already up to date");
12744
+ return true;
12745
+ }
12746
+ deps.gitRun(
12747
+ [
12748
+ "-c",
12749
+ "user.email=mmi-github-app[bot]@users.noreply.github.com",
12750
+ "-c",
12751
+ "user.name=mmi-github-app[bot]",
12752
+ "commit",
12753
+ "-m",
12754
+ "docs(wiki): refresh MMI Future project wiki"
12755
+ ],
12756
+ dir
12757
+ );
12758
+ try {
12759
+ deps.gitRun([...authArgs, "push", "origin", "HEAD:master"], dir);
12760
+ } catch (e) {
12761
+ deps.err(
12762
+ `wiki publish failed: push to ${opts.repo}.wiki.git rejected: ${redactWikiSecrets(e.message)} \u2014 if the wiki advanced before this push, re-run wiki publish to pick up the latest wiki state`
12763
+ );
12764
+ return false;
12765
+ }
12766
+ deps.log(`wiki publish: published ${copied} page(s) to ${opts.repo} wiki`);
12767
+ return true;
12768
+ } catch (e) {
12769
+ deps.err(`wiki publish failed: ${redactWikiSecrets(e.message)}`);
12770
+ return false;
12771
+ } finally {
12772
+ deps.rm(dir);
12773
+ }
12774
+ }
12775
+ function realWikiGitPublishDeps() {
12776
+ return {
12777
+ mkdtemp: () => (0, import_node_fs13.mkdtempSync)((0, import_node_path14.join)((0, import_node_os5.tmpdir)(), "wiki-")),
12778
+ readdir: (dir) => (0, import_node_fs13.readdirSync)(dir),
12779
+ copyFile: (src, dest) => (0, import_node_fs13.copyFileSync)(src, dest),
12780
+ fileExists: (p) => (0, import_node_fs13.existsSync)(p),
12781
+ rm: (p) => (0, import_node_fs13.rmSync)(p, { recursive: true, force: true }),
12782
+ gitProbe: (args, cwd) => {
12783
+ try {
12784
+ (0, import_node_child_process8.execFileSync)("git", args, { cwd, stdio: "pipe" });
12785
+ return true;
12786
+ } catch {
12787
+ return false;
12788
+ }
12789
+ },
12790
+ gitRun: (args, cwd) => {
12791
+ (0, import_node_child_process8.execFileSync)("git", args, { cwd, stdio: "inherit" });
12792
+ },
12793
+ log: (msg) => console.log(msg),
12794
+ err: (msg) => console.error(msg)
12795
+ };
12796
+ }
12797
+
12701
12798
  // src/project-readiness.ts
12702
12799
  function stagesForTrack(meta) {
12703
12800
  return branchesForTrack(resolveReleaseTrack(meta)).map((b) => b === "development" ? "dev" : b);
@@ -12796,7 +12893,7 @@ function runtimeSecretStreamGap(stage2, meta, presentSecrets) {
12796
12893
  for (const name of stageRequiredSecrets(stage2, meta).map(materializedRuntimeSecretName)) {
12797
12894
  if (seen.has(name)) continue;
12798
12895
  seen.add(name);
12799
- if (!streamed.has(name) && presentSecrets.has(stageKey(stage2, name))) gap.push(name);
12896
+ if (!streamed.has(name) && (presentSecrets.has(stageKey(stage2, name)) || presentSecrets.has(name))) gap.push(name);
12800
12897
  }
12801
12898
  return gap;
12802
12899
  }
@@ -13040,9 +13137,11 @@ async function buildV2Doctor(repoOrSlug, deps) {
13040
13137
  return [stage2, { required, ok: required ? await deps.hasDeployState(slug, stage2) : true }];
13041
13138
  })));
13042
13139
  const secrets = Object.fromEntries(STAGES.map((stage2) => {
13043
- const required = stageInTrack(meta, stage2) ? stageRequiredSecrets(stage2, meta).map((key) => stageKey(stage2, key)) : [];
13044
- const present = required.filter((key) => presentSecrets.has(key));
13045
- const missing = required.filter((key) => !presentSecrets.has(key));
13140
+ const names = stageInTrack(meta, stage2) ? stageRequiredSecrets(stage2, meta) : [];
13141
+ const satisfied = (key) => presentSecrets.has(stageKey(stage2, key)) || !key.includes("/") && presentSecrets.has(key);
13142
+ const required = names.map((key) => stageKey(stage2, key));
13143
+ const present = names.filter(satisfied).map((key) => stageKey(stage2, key));
13144
+ const missing = names.filter((key) => !satisfied(key)).map((key) => stageKey(stage2, key));
13046
13145
  return [stage2, { required, present, missing }];
13047
13146
  }));
13048
13147
  const runtimeSecretStreamWarnings = Object.fromEntries(STAGES.map((stage2) => [
@@ -13171,7 +13270,6 @@ var DEFAULT_DOMAINS = ["mutatismutandis.co", "mutmut.co"];
13171
13270
  var DEFAULT_CALLBACK_PATH = "/api/auth/callback";
13172
13271
  var ENV_PREFIXES = ["", "dev", "rc"];
13173
13272
  var LOOPBACK = ["http://localhost", "http://127.0.0.1"];
13174
- var SSM_ENVS = ["dev", "rc", "main"];
13175
13273
  var SSM_NAMES = ["GOOGLE_CLIENT_ID", "GOOGLE_CLIENT_SECRET"];
13176
13274
  var uniq = (xs) => [...new Set(xs)];
13177
13275
  function defaultSubdomain2(slug) {
@@ -13202,7 +13300,7 @@ function expectedRedirectUris(cfg) {
13202
13300
  ]);
13203
13301
  }
13204
13302
  function oauthSsmKeys() {
13205
- return SSM_ENVS.flatMap((env) => SSM_NAMES.map((name) => `${env}/${name}`));
13303
+ return [...SSM_NAMES];
13206
13304
  }
13207
13305
  function parseOauthClientJson(input) {
13208
13306
  let parsed;
@@ -13276,11 +13374,20 @@ function parseSecretsCatalogVar(raw) {
13276
13374
  throw new Error("project set: secrets must be a map keyed by canonical KEY");
13277
13375
  }
13278
13376
  const nonEmpty = (v) => typeof v === "string" && v.trim().length > 0;
13377
+ const overrideMapKey = /^([A-Z_][A-Z0-9_]*)@(dev|rc|main)$/;
13279
13378
  for (const [mapKey, value] of Object.entries(parsed)) {
13280
13379
  if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error(`project set: secrets["${mapKey}"] must be an object`);
13281
13380
  const e = value;
13282
- if (typeof e.key !== "string" || !SECRET_ENV_NAME_RE.test(e.key) || e.key !== mapKey) {
13283
- throw new Error(`project set: secrets["${mapKey}"].key must equal the map key and be an env-name (UPPER_SNAKE)`);
13381
+ if (typeof e.key !== "string" || !SECRET_ENV_NAME_RE.test(e.key)) {
13382
+ throw new Error(`project set: secrets["${mapKey}"].key must be an env-name (UPPER_SNAKE)`);
13383
+ }
13384
+ const override = overrideMapKey.exec(mapKey);
13385
+ if (override) {
13386
+ if (e.key !== override[1] || !Array.isArray(e.stages) || e.stages.length !== 1 || e.stages[0] !== override[2]) {
13387
+ throw new Error(`project set: secrets["${mapKey}"] is an override entry (#2528) \u2014 key must be ${override[1]} and stages exactly ["${override[2]}"]`);
13388
+ }
13389
+ } else if (e.key !== mapKey) {
13390
+ throw new Error(`project set: secrets["${mapKey}"].key must equal the map key (or use the KEY@<stage> override form, #2528)`);
13284
13391
  }
13285
13392
  if (!nonEmpty(e.purpose) || !nonEmpty(e.group) || !nonEmpty(e.owner)) throw new Error(`project set: secrets["${mapKey}"] needs non-empty purpose, group, owner`);
13286
13393
  if (e.provider !== void 0 && !nonEmpty(e.provider)) throw new Error(`project set: secrets["${mapKey}"].provider must be a non-empty string`);
@@ -13811,7 +13918,7 @@ function writeError(res) {
13811
13918
  }
13812
13919
 
13813
13920
  // src/secrets.ts
13814
- var import_node_child_process8 = require("node:child_process");
13921
+ var import_node_child_process9 = require("node:child_process");
13815
13922
  var OWNER2 = "mutmutco";
13816
13923
  var SSM_ROOT = "/mmi-future";
13817
13924
  var PROJECT_TIER_SEGMENT = "dev";
@@ -13827,8 +13934,7 @@ function classifyTier(_slug, key) {
13827
13934
  return key.slice(0, slash) === PROJECT_TIER_SEGMENT ? "project" : "org";
13828
13935
  }
13829
13936
  function secretParamName(slug, key) {
13830
- const rel = key.includes("/") ? key : `${PROJECT_TIER_SEGMENT}/${key}`;
13831
- return `${SSM_ROOT}/${slug}/${rel}`;
13937
+ return `${SSM_ROOT}/${slug}/${key}`;
13832
13938
  }
13833
13939
  function formatSecretList(items) {
13834
13940
  if (!items.length) return "no secrets";
@@ -13847,10 +13953,10 @@ function vaultPointer(slug) {
13847
13953
  org: [`/mmi-future/{shared,cloudflare,mmi-hub,...}/* (org-infra, master-gated)`]
13848
13954
  },
13849
13955
  stages: ["dev", "rc", "main"],
13850
- // Google OAuth is one client per repo; creds live at every stage under the standard key names
13851
- // (local is port-agnostic and reuses the dev tier). See the oauth-everywhere convention.
13956
+ // Google OAuth is one client per repo = ONE stageless value pair at the slug root (#2244/#2528);
13957
+ // every stage (and local) reads the same pair. The staged {dev,rc,main}/GOOGLE_* shape is retired.
13852
13958
  wellKnown: {
13853
- googleOAuth: ["dev/GOOGLE_CLIENT_ID", "dev/GOOGLE_CLIENT_SECRET", "rc/GOOGLE_CLIENT_ID", "rc/GOOGLE_CLIENT_SECRET", "main/GOOGLE_CLIENT_ID", "main/GOOGLE_CLIENT_SECRET"]
13959
+ googleOAuth: ["GOOGLE_CLIENT_ID", "GOOGLE_CLIENT_SECRET"]
13854
13960
  }
13855
13961
  };
13856
13962
  }
@@ -14106,8 +14212,9 @@ function formatCatalog(r) {
14106
14212
  return lines.join("\n");
14107
14213
  }
14108
14214
  function formatDrift(d) {
14109
- if (d.ok) return "vault doctor: zero drift \u2014 SSM matches the catalog.";
14110
- const lines = ["vault doctor: drift found", ""];
14215
+ const shadows = d.stagedShadow ?? [];
14216
+ if (d.ok && !shadows.length) return "vault doctor: zero drift \u2014 SSM matches the catalog.";
14217
+ const lines = [d.ok ? "vault doctor: zero drift \u2014 SSM matches the catalog. Advisories:" : "vault doctor: drift found", ""];
14111
14218
  const section = (title, rows) => {
14112
14219
  if (!rows.length) return;
14113
14220
  lines.push(`${title} (${rows.length}):`, ...rows.map((r) => ` - ${r}`), "");
@@ -14116,6 +14223,10 @@ function formatDrift(d) {
14116
14223
  section("ORPHAN in SSM, undeclared \u2014 declare or delete", d.orphan.map((o) => o.path));
14117
14224
  section("OFF-SCHEME path", d.offScheme.map((o) => `${o.path} (${o.reason})`));
14118
14225
  section("STAGE-SPLIT \u2014 collapse to one shared key", (d.stageSplit ?? []).map((s) => `${s.slug}/${s.leaf} split across ${s.stages.join("/")}`));
14226
+ section(
14227
+ "STAGED SHADOW \u2014 the staged override WINS its stage over the stageless canonical (#2522)",
14228
+ shadows.map((s) => `${s.path} shadows ${s.canonicalPath}${s.declared ? "" : " (UNDECLARED residue \u2014 triage per #2526)"}`)
14229
+ );
14119
14230
  section("DUPLICATE org-infra secret", d.duplicate.map((dup) => `${dup.logical}: ${dup.paths.join(" , ")}`));
14120
14231
  return lines.join("\n").trimEnd();
14121
14232
  }
@@ -14220,10 +14331,11 @@ async function secretsPreflight(deps, opts) {
14220
14331
  }
14221
14332
  const { secrets } = await res.json();
14222
14333
  const present = new Set((secrets ?? []).map((s) => s.key));
14223
- const required = opts.required.map((key) => stageKey2(opts.stage, key));
14224
- const missing = required.filter((key) => !present.has(key));
14334
+ const missing = opts.required.filter(
14335
+ (key) => !present.has(stageKey2(opts.stage, key)) && !(key.includes("/") ? false : present.has(key))
14336
+ );
14225
14337
  if (missing.length) {
14226
- deps.log(`missing ${missing.join(", ")}`);
14338
+ deps.log(`missing ${missing.map((key) => key.includes("/") ? key : `${stageKey2(opts.stage, key)} (and no stageless ${key})`).join(", ")}`);
14227
14339
  return false;
14228
14340
  }
14229
14341
  deps.log(`all required ${opts.stage} secret names are present`);
@@ -14255,7 +14367,8 @@ async function secretsGet(deps, key, opts) {
14255
14367
  );
14256
14368
  return false;
14257
14369
  }
14258
- const { value } = await res.json();
14370
+ const { value, notes } = await res.json();
14371
+ for (const note of notes ?? []) deps.err(`note: ${note}`);
14259
14372
  deps.log(value ?? "");
14260
14373
  return true;
14261
14374
  }
@@ -14335,6 +14448,8 @@ async function putSecret(deps, key, value, opts) {
14335
14448
  );
14336
14449
  return false;
14337
14450
  }
14451
+ const { warnings } = await res.json();
14452
+ for (const warning of warnings ?? []) deps.err(`warning: ${warning}`);
14338
14453
  const provider = providerForSecretKey(key);
14339
14454
  if (provider) {
14340
14455
  const result = await verifySecretValue(deps, key, value, opts);
@@ -14542,7 +14657,7 @@ async function secretsCopy(deps, opts) {
14542
14657
  return true;
14543
14658
  }
14544
14659
  function defaultSpawn(command, args, env) {
14545
- const r = (0, import_node_child_process8.spawnSync)(command, args, { stdio: "inherit", env });
14660
+ const r = (0, import_node_child_process9.spawnSync)(command, args, { stdio: "inherit", env });
14546
14661
  if (r.error) throw r.error;
14547
14662
  return r.status ?? 1;
14548
14663
  }
@@ -14602,8 +14717,8 @@ async function secretsUse(deps, key, opts) {
14602
14717
  }
14603
14718
 
14604
14719
  // src/secrets-commands.ts
14605
- var import_node_fs13 = require("node:fs");
14606
- var import_node_path14 = require("node:path");
14720
+ var import_node_fs14 = require("node:fs");
14721
+ var import_node_path15 = require("node:path");
14607
14722
  var RAILS_CREDENTIALS_DECRYPT_TIMEOUT_MS = 3e4;
14608
14723
  var DEFAULT_RAILS_CREDENTIALS_FILE = "config/credentials.yml.enc";
14609
14724
  var DEFAULT_RAILS_MASTER_KEY_FILE = "config/master.key";
@@ -14611,18 +14726,18 @@ function collectMap(value, previous = []) {
14611
14726
  return [...previous, value];
14612
14727
  }
14613
14728
  async function decryptRailsCredentials(input) {
14614
- const appDir = (0, import_node_path14.resolve)(input.appDir ?? process.cwd());
14729
+ const appDir = (0, import_node_path15.resolve)(input.appDir ?? process.cwd());
14615
14730
  const credentialsFile = input.credentialsFile ?? DEFAULT_RAILS_CREDENTIALS_FILE;
14616
14731
  const masterKeyFile = input.masterKeyFile ?? DEFAULT_RAILS_MASTER_KEY_FILE;
14617
- const credentialsPath = (0, import_node_path14.resolve)(appDir, credentialsFile);
14618
- const masterKeyPath = (0, import_node_path14.resolve)(appDir, masterKeyFile);
14732
+ const credentialsPath = (0, import_node_path15.resolve)(appDir, credentialsFile);
14733
+ const masterKeyPath = (0, import_node_path15.resolve)(appDir, masterKeyFile);
14619
14734
  const env = {
14620
14735
  ...process.env,
14621
14736
  MMI_RAILS_CREDENTIALS_FILE: credentialsPath,
14622
14737
  MMI_RAILS_MASTER_KEY_FILE: masterKeyPath
14623
14738
  };
14624
- if ((0, import_node_fs13.existsSync)(masterKeyPath)) {
14625
- env.RAILS_MASTER_KEY = (0, import_node_fs13.readFileSync)(masterKeyPath, "utf8").trim();
14739
+ if ((0, import_node_fs14.existsSync)(masterKeyPath)) {
14740
+ env.RAILS_MASTER_KEY = (0, import_node_fs14.readFileSync)(masterKeyPath, "utf8").trim();
14626
14741
  }
14627
14742
  const script = [
14628
14743
  'require "json"',
@@ -14688,7 +14803,7 @@ function registerSecretsCommands(program3) {
14688
14803
  secrets.command("org-catalog").description("MASTER-ONLY: set the _org/<provider>/* catalog declarations from a JSON file (#2244)").requiredOption("--file <path>", 'JSON file: { "entries": { "<provider>/<KEY>": {key,purpose,group,owner,provider,stages[],consumers[]} } }').action((o) => withSecrets(async (d) => {
14689
14804
  let body;
14690
14805
  try {
14691
- body = (0, import_node_fs13.readFileSync)((0, import_node_path14.resolve)(o.file), "utf8");
14806
+ body = (0, import_node_fs14.readFileSync)((0, import_node_path15.resolve)(o.file), "utf8");
14692
14807
  } catch (e) {
14693
14808
  return fail(`secrets org-catalog: cannot read --file ${o.file}: ${e.message}`);
14694
14809
  }
@@ -14730,7 +14845,7 @@ function registerSecretsCommands(program3) {
14730
14845
  const ok = await secretsVerify(d, key, o);
14731
14846
  if (!ok) process.exitCode = 1;
14732
14847
  }));
14733
- secrets.command("set <key>").description("write/rotate a secret; value is read from stdin (never an argument)").option("--repo <owner/repo>", "target repo (defaults to the current repo)").action((key, o) => withSecrets(async (d) => {
14848
+ secrets.command("set <key>").description("write/rotate a secret; value from stdin (never an argument). DECLARE-FIRST (#2528): the key must be a declared catalog coordinate \u2014 bare <KEY> = the stageless canonical, <stage>/<KEY> = a declared per-stage override; undeclared writes are rejected with the fix").option("--repo <owner/repo>", "target repo (defaults to the current repo)").action((key, o) => withSecrets(async (d) => {
14734
14849
  const ok = await secretsSet(d, key, o);
14735
14850
  if (!ok) process.exitCode = 1;
14736
14851
  }));
@@ -14763,7 +14878,7 @@ function registerSecretsCommands(program3) {
14763
14878
  {
14764
14879
  ...d,
14765
14880
  decryptRailsCredentials,
14766
- removeFile: (path2) => (0, import_node_fs13.unlinkSync)((0, import_node_path14.resolve)(o.appDir ?? process.cwd(), path2))
14881
+ removeFile: (path2) => (0, import_node_fs14.unlinkSync)((0, import_node_path15.resolve)(o.appDir ?? process.cwd(), path2))
14767
14882
  },
14768
14883
  {
14769
14884
  repo: o.repo,
@@ -14852,11 +14967,11 @@ function registerEdgeCommands(program3) {
14852
14967
  }
14853
14968
 
14854
14969
  // src/doctor-run.ts
14855
- var import_node_fs17 = require("node:fs");
14856
- var import_node_child_process10 = require("node:child_process");
14970
+ var import_node_fs18 = require("node:fs");
14971
+ var import_node_child_process11 = require("node:child_process");
14857
14972
  var import_promises2 = require("node:fs/promises");
14858
- var import_node_path17 = require("node:path");
14859
- var import_node_os6 = require("node:os");
14973
+ var import_node_path18 = require("node:path");
14974
+ var import_node_os7 = require("node:os");
14860
14975
 
14861
14976
  // src/plugin-guard.ts
14862
14977
  function buildPluginGuardDecision(i) {
@@ -14868,140 +14983,20 @@ function buildPluginGuardDecision(i) {
14868
14983
  function buildGuardSessionStartLine(state, opts = {}) {
14869
14984
  if (state === "healthy" || state === "not-org") return { exitCode: 0 };
14870
14985
  const recovery = opts.recovery ?? "mmi-cli plugin-heal";
14986
+ const restartHint = opts.restartHint ?? "restart your agent host / reload plugins";
14871
14987
  const reason = state === "no-install" ? "MMI plugin is not installed for this user/session" : "MMI plugin is installed but its marketplace/cache is unresolved";
14872
14988
  return {
14873
- line: `[mmi-guard] ${reason}; run ${recovery} and restart Claude Code / reload plugins.`,
14989
+ line: `[mmi-guard] ${reason}; run ${recovery} and ${restartHint}.`,
14874
14990
  exitCode: 1
14875
14991
  };
14876
14992
  }
14877
14993
 
14878
14994
  // src/cursor-plugin-seed.ts
14879
- var import_node_child_process9 = require("node:child_process");
14880
- var import_node_fs14 = require("node:fs");
14881
- var import_node_os5 = require("node:os");
14882
- var import_node_path15 = require("node:path");
14995
+ var import_node_child_process10 = require("node:child_process");
14996
+ var import_node_fs15 = require("node:fs");
14997
+ var import_node_os6 = require("node:os");
14998
+ var import_node_path16 = require("node:path");
14883
14999
  var import_node_util7 = require("node:util");
14884
- function isSemverVersion(v) {
14885
- return typeof v === "string" && /^v?\d+\.\d+\.\d+/.test(v.trim());
14886
- }
14887
- var MMI_HUB_REPO = "mutmutco/MMI-Hub";
14888
- var CURSOR_THIRD_PARTY_STATE_KEY = "cursor/thirdPartyExtensibilityEnabled";
14889
- var PLUGIN_JSON_REL = ".cursor-plugin/plugin.json";
14890
- var execFileBuffer = (0, import_node_util7.promisify)(import_node_child_process9.execFile);
14891
- function gitFetchReleaseTagArgs(hubCheckout, tag) {
14892
- return ["-C", hubCheckout, "fetch", "origin", "tag", tag, "--quiet"];
14893
- }
14894
- function ghReleaseTarballApiArgs(tag) {
14895
- return ["api", `repos/${MMI_HUB_REPO}/tarball/refs/tags/${tag}`];
14896
- }
14897
- function cursorUserGlobalStatePath() {
14898
- if (process.platform === "win32") {
14899
- const base = process.env.APPDATA || (0, import_node_path15.join)((0, import_node_os5.homedir)(), "AppData", "Roaming");
14900
- return (0, import_node_path15.join)(base, "Cursor", "User", "globalStorage", "state.vscdb");
14901
- }
14902
- if (process.platform === "darwin") {
14903
- return (0, import_node_path15.join)((0, import_node_os5.homedir)(), "Library", "Application Support", "Cursor", "User", "globalStorage", "state.vscdb");
14904
- }
14905
- return (0, import_node_path15.join)((0, import_node_os5.homedir)(), ".config", "Cursor", "User", "globalStorage", "state.vscdb");
14906
- }
14907
- async function readCursorThirdPartyExtensibilityEnabled(execFileP5) {
14908
- const dbPath = cursorUserGlobalStatePath();
14909
- if (!(0, import_node_fs14.existsSync)(dbPath)) return void 0;
14910
- try {
14911
- const { stdout } = await execFileP5("sqlite3", [dbPath, `SELECT value FROM ItemTable WHERE key = '${CURSOR_THIRD_PARTY_STATE_KEY}';`], {
14912
- timeout: 5e3
14913
- });
14914
- const value = stdout.trim().toLowerCase();
14915
- if (value === "true") return true;
14916
- if (value === "false") return false;
14917
- return void 0;
14918
- } catch {
14919
- return void 0;
14920
- }
14921
- }
14922
- function syncDirContents(src, dest) {
14923
- (0, import_node_fs14.mkdirSync)(dest, { recursive: true });
14924
- for (const name of (0, import_node_fs14.readdirSync)(dest)) {
14925
- (0, import_node_fs14.rmSync)((0, import_node_path15.join)(dest, name), { recursive: true, force: true });
14926
- }
14927
- (0, import_node_fs14.cpSync)(src, dest, { recursive: true });
14928
- }
14929
- function releaseTag(releasedVersion) {
14930
- return releasedVersion.startsWith("v") ? releasedVersion : `v${releasedVersion}`;
14931
- }
14932
- async function extractPluginMmiFromHubCheckout(hubCheckout, tag, tmpRoot, execFileP5) {
14933
- const tarFile = (0, import_node_path15.join)(tmpRoot, "archive.tar");
14934
- try {
14935
- await execFileP5("git", gitFetchReleaseTagArgs(hubCheckout, tag), { timeout: 6e4 });
14936
- await execFileP5("git", ["-C", hubCheckout, "archive", "--format=tar", `--output=${tarFile}`, tag, "plugins/mmi"], {
14937
- timeout: 6e4
14938
- });
14939
- await execFileP5("tar", ["-xf", tarFile, "-C", tmpRoot], { timeout: 6e4 });
14940
- const pluginMmi = (0, import_node_path15.join)(tmpRoot, "plugins", "mmi");
14941
- return (0, import_node_fs14.existsSync)((0, import_node_path15.join)(pluginMmi, PLUGIN_JSON_REL)) ? pluginMmi : void 0;
14942
- } catch {
14943
- return void 0;
14944
- }
14945
- }
14946
- async function downloadPluginMmiViaGh(tag, tmpRoot) {
14947
- const tarPath = (0, import_node_path15.join)(tmpRoot, "repo.tgz");
14948
- try {
14949
- (0, import_node_fs14.mkdirSync)(tmpRoot, { recursive: true });
14950
- const { stdout } = await execFileBuffer("gh", ghReleaseTarballApiArgs(tag), {
14951
- timeout: 12e4,
14952
- maxBuffer: 100 * 1024 * 1024,
14953
- encoding: "buffer",
14954
- windowsHide: true
14955
- });
14956
- (0, import_node_fs14.writeFileSync)(tarPath, stdout);
14957
- await execFileBuffer("tar", ["-xzf", tarPath, "-C", tmpRoot], { timeout: 12e4, windowsHide: true });
14958
- const top = (0, import_node_fs14.readdirSync)(tmpRoot).find((entry) => entry !== "repo.tgz");
14959
- if (!top) return void 0;
14960
- const pluginMmi = (0, import_node_path15.join)(tmpRoot, top, "plugins", "mmi");
14961
- return (0, import_node_fs14.existsSync)((0, import_node_path15.join)(pluginMmi, PLUGIN_JSON_REL)) ? pluginMmi : void 0;
14962
- } catch {
14963
- return void 0;
14964
- }
14965
- }
14966
- async function resolvePluginMmiSource(releasedVersion, hubCheckout, tmpRoot, execFileP5) {
14967
- (0, import_node_fs14.mkdirSync)(tmpRoot, { recursive: true });
14968
- const tag = releaseTag(releasedVersion);
14969
- if (hubCheckout) {
14970
- const fromHub = await extractPluginMmiFromHubCheckout(hubCheckout, tag, tmpRoot, execFileP5);
14971
- if (fromHub) return fromHub;
14972
- }
14973
- return downloadPluginMmiViaGh(tag, (0, import_node_path15.join)(tmpRoot, "gh"));
14974
- }
14975
- function cursorPluginPinsNeedingSeed(pins, releasedVersion) {
14976
- if (!isSemverVersion(releasedVersion)) return pins.filter((pin) => !pin.hasPluginJson || !pin.hasHooksJson || pin.isEmpty);
14977
- return pins.filter((pin) => {
14978
- if (!pin.hasPluginJson || !pin.hasHooksJson || pin.isEmpty) return true;
14979
- if (isSemverVersion(pin.version) && compareVersions(pin.version, releasedVersion) < 0) return true;
14980
- return false;
14981
- });
14982
- }
14983
- function cursorPluginCacheSeedTargets(pins, releasedVersion, cacheRoot, cacheRootExists = false) {
14984
- const needing = cursorPluginPinsNeedingSeed(pins, releasedVersion);
14985
- if (needing.length > 0) return needing.map((pin) => pin.path);
14986
- if (pins.length === 0 && cacheRoot && cacheRootExists && isSemverVersion(releasedVersion)) {
14987
- return [(0, import_node_path15.join)(cacheRoot, `v${releasedVersion.replace(/^v/, "")}`)];
14988
- }
14989
- return [];
14990
- }
14991
- async function applyCursorPluginCacheSeed(input) {
14992
- if (!isSemverVersion(input.releasedVersion)) return false;
14993
- const targets = cursorPluginCacheSeedTargets(input.pins, input.releasedVersion, input.cacheRoot, input.cacheRootExists);
14994
- if (targets.length === 0) return false;
14995
- const tmpRoot = await input.mkdtemp("mmi-cursor-seed-");
14996
- const source = await resolvePluginMmiSource(input.releasedVersion, input.hubCheckout, tmpRoot, input.execFileP);
14997
- if (!source) return false;
14998
- input.log(` \u21BB seeding Cursor MMI plugin cache \u2192 ${input.releasedVersion}\u2026`);
14999
- for (const dest of targets) {
15000
- syncDirContents(source, dest);
15001
- }
15002
- (0, import_node_fs14.rmSync)(tmpRoot, { recursive: true, force: true });
15003
- return true;
15004
- }
15005
15000
 
15006
15001
  // src/doctor.ts
15007
15002
  var GH_PROJECT_LOGIN_FIX = 'run: gh auth login --hostname github.com --git-protocol https --web --scopes "project"';
@@ -15285,8 +15280,8 @@ function buildCursorPluginCacheCleanupCheck(input) {
15285
15280
  label: CURSOR_PLUGIN_CACHE_CLEANUP_LABEL,
15286
15281
  fix: CURSOR_PLUGIN_CACHE_CLEANUP_FIX
15287
15282
  };
15288
- if (!input.isOrgRepo || !isSemverVersion2(input.releasedVersion)) return base;
15289
- const semverPins = input.pins.filter((pin) => isSemverVersion2(pin.version));
15283
+ if (!input.isOrgRepo || !isSemverVersion(input.releasedVersion)) return base;
15284
+ const semverPins = input.pins.filter((pin) => isSemverVersion(pin.version));
15290
15285
  if (semverPins.length === 0) return base;
15291
15286
  const activeVersion = semverPins.map((pin) => pin.version).reduce((a, b) => compareVersions(a, b) >= 0 ? a : b);
15292
15287
  const protectedVersions = new Set(
@@ -15336,7 +15331,7 @@ function codexEnabledPluginVersionFromList(jsonText, pluginId = MMI_PLUGIN_ID) {
15336
15331
  const parsed = JSON.parse(jsonText);
15337
15332
  const rows = Array.isArray(parsed?.installed) ? parsed.installed : [];
15338
15333
  const row = rows.find((r) => r?.pluginId === pluginId && r.installed === true && r.enabled === true);
15339
- return isSemverVersion2(row?.version) ? row.version.trim().replace(/^v/, "") : void 0;
15334
+ return isSemverVersion(row?.version) ? row.version.trim().replace(/^v/, "") : void 0;
15340
15335
  } catch {
15341
15336
  return void 0;
15342
15337
  }
@@ -15347,9 +15342,9 @@ function buildCodexActiveCacheCheck(input) {
15347
15342
  label: CODEX_ACTIVE_CACHE_LABEL,
15348
15343
  fix: CODEX_PLUGIN_RECOVERY
15349
15344
  };
15350
- if (!input.isOrgRepo || !isSemverVersion2(input.releasedVersion)) return base;
15345
+ if (!input.isOrgRepo || !isSemverVersion(input.releasedVersion)) return base;
15351
15346
  if (input.codexPresent === false) return base;
15352
- if (isSemverVersion2(input.codexActiveVersion)) {
15347
+ if (isSemverVersion(input.codexActiveVersion)) {
15353
15348
  if (compareVersions(input.codexActiveVersion, input.releasedVersion) < 0) {
15354
15349
  return {
15355
15350
  ...base,
@@ -15361,11 +15356,11 @@ function buildCodexActiveCacheCheck(input) {
15361
15356
  }
15362
15357
  return { ...base, activeCacheVersion: input.codexActiveVersion, releasedVersion: input.releasedVersion };
15363
15358
  }
15364
- if (isSemverVersion2(input.codexRecordVersion) && compareVersions(input.codexRecordVersion, input.releasedVersion) < 0) {
15359
+ if (isSemverVersion(input.codexRecordVersion) && compareVersions(input.codexRecordVersion, input.releasedVersion) < 0) {
15365
15360
  return base;
15366
15361
  }
15367
15362
  const activeCacheVersion = highestSemver(input.codexCacheVersions ?? []);
15368
- const cacheCurrent = isSemverVersion2(activeCacheVersion) && compareVersions(activeCacheVersion, input.releasedVersion) >= 0;
15363
+ const cacheCurrent = isSemverVersion(activeCacheVersion) && compareVersions(activeCacheVersion, input.releasedVersion) >= 0;
15369
15364
  if (cacheCurrent) {
15370
15365
  return { ...base, activeCacheVersion, releasedVersion: input.releasedVersion };
15371
15366
  }
@@ -15444,7 +15439,7 @@ function buildSessionSkillRootCheck(input) {
15444
15439
  const sessionVersion = parseSessionSkillRootVersion(input.pluginRoot);
15445
15440
  if (!sessionVersion) return base;
15446
15441
  const activeCacheVersion = highestSemver(input.cacheVersions ?? []);
15447
- if (!isSemverVersion2(activeCacheVersion)) return { ...base, sessionVersion };
15442
+ if (!isSemverVersion(activeCacheVersion)) return { ...base, sessionVersion };
15448
15443
  if (compareVersions(sessionVersion, activeCacheVersion) >= 0) {
15449
15444
  return { ...base, sessionVersion, activeCacheVersion };
15450
15445
  }
@@ -15526,7 +15521,8 @@ var PLUGIN_SURFACE_HEAL = {
15526
15521
  ],
15527
15522
  pluginRunner: "codex",
15528
15523
  fix: (surface) => `${CODEX_RECOVERY} # then ${reloadAction(surface)}`,
15529
- updateRecipe: [CODEX_RECOVERY, "codex plugin list # verify mmi@mutmutco shows the released version"]
15524
+ updateRecipe: [CODEX_RECOVERY, "codex plugin list # verify mmi@mutmutco shows the released version"],
15525
+ guardRecovery: "$env:MMI_AGENT_SURFACE='codex'; mmi-cli plugin-heal"
15530
15526
  },
15531
15527
  cursor: {
15532
15528
  delivery: "cursor-cache",
@@ -15610,7 +15606,7 @@ function renderSurfaceGuide(label, steps) {
15610
15606
  }
15611
15607
  function highestSemver(versions) {
15612
15608
  return versions.reduce((best, v) => {
15613
- if (!isSemverVersion2(v)) return best;
15609
+ if (!isSemverVersion(v)) return best;
15614
15610
  if (best === void 0) return v;
15615
15611
  return compareVersions(v, best) > 0 ? v : best;
15616
15612
  }, void 0);
@@ -15622,12 +15618,12 @@ function buildPluginUpdateReport(input) {
15622
15618
  const opencodePlugin = highestSemver(input.opencodePluginVersions ?? []);
15623
15619
  return {
15624
15620
  versions: {
15625
- ...isSemverVersion2(input.cliVersion) ? { cli: input.cliVersion } : {},
15621
+ ...isSemverVersion(input.cliVersion) ? { cli: input.cliVersion } : {},
15626
15622
  ...claudePlugin ? { claudePlugin } : {},
15627
15623
  ...codexMarketplace ? { codexMarketplace } : {},
15628
15624
  ...codexActiveCache ? { codexActiveCache } : {},
15629
15625
  ...opencodePlugin ? { opencodePlugin } : {},
15630
- ...isSemverVersion2(input.releasedVersion) ? { released: input.releasedVersion } : {},
15626
+ ...isSemverVersion(input.releasedVersion) ? { released: input.releasedVersion } : {},
15631
15627
  ...input.releasedVersionSource ? { releasedSource: input.releasedVersionSource } : {}
15632
15628
  },
15633
15629
  recipes: PLUGIN_UPDATE_RECIPES
@@ -15651,14 +15647,15 @@ function renderPluginVersionBlock(report) {
15651
15647
  }
15652
15648
  function buildDoctorJsonPayload(input) {
15653
15649
  return {
15654
- ok: input.checks.every((c) => c.ok),
15650
+ ok: doctorExitCode(input.checks) === 0,
15651
+ hasAdvisories: input.checks.some((c) => !c.ok && isAdvisoryDoctorCheck(c)),
15655
15652
  checks: input.checks,
15656
15653
  updateReport: input.updateReport,
15657
15654
  ...input.resources.length ? { resources: input.resources } : {}
15658
15655
  };
15659
15656
  }
15660
15657
  var INSTALLED_PLUGIN_VERSION_LABEL = "installed MMI plugin version (vs latest release)";
15661
- function isSemverVersion2(v) {
15658
+ function isSemverVersion(v) {
15662
15659
  return typeof v === "string" && /^v?\d+\.\d+\.\d+/.test(v.trim());
15663
15660
  }
15664
15661
  function staleRecordCommand(surface) {
@@ -15681,7 +15678,7 @@ function buildInstalledPluginVersionCheck(input) {
15681
15678
  fix: pluginRecoveryFix(input.surface),
15682
15679
  pluginId
15683
15680
  };
15684
- if (!input.isOrgRepo || !isSemverVersion2(input.releasedVersion)) return base;
15681
+ if (!input.isOrgRepo || !isSemverVersion(input.releasedVersion)) return base;
15685
15682
  if (input.surface === "cursor") return base;
15686
15683
  const stale = [];
15687
15684
  let sawRecord = false;
@@ -15691,7 +15688,7 @@ function buildInstalledPluginVersionCheck(input) {
15691
15688
  if (!Array.isArray(records) || records.length === 0) continue;
15692
15689
  sawRecord = true;
15693
15690
  const installedVersion = bestRecord(records).version;
15694
- if (!isSemverVersion2(installedVersion)) continue;
15691
+ if (!isSemverVersion(installedVersion)) continue;
15695
15692
  if (compareVersions(installedVersion, input.releasedVersion) >= 0) {
15696
15693
  currentVersion = currentVersion ?? installedVersion;
15697
15694
  } else {
@@ -15746,14 +15743,14 @@ function buildManagedPluginDriftCheck(input) {
15746
15743
  for (const source of input.sources) {
15747
15744
  if (source.directVersions && descriptor.id in source.directVersions) {
15748
15745
  const version2 = source.directVersions[descriptor.id];
15749
- if (isSemverVersion2(version2)) versions.push({ surface: source.surface, version: normalize(version2) });
15746
+ if (isSemverVersion(version2)) versions.push({ surface: source.surface, version: normalize(version2) });
15750
15747
  continue;
15751
15748
  }
15752
15749
  const records = source.installed?.plugins?.[descriptor.id];
15753
15750
  if (!Array.isArray(records) || records.length === 0) continue;
15754
15751
  const recordVersion = bestRecord(records).version;
15755
- const version = isSemverVersion2(recordVersion) ? recordVersion : highestSemver(source.cacheVersions?.[descriptor.id] ?? []);
15756
- if (!isSemverVersion2(version)) continue;
15752
+ const version = isSemverVersion(recordVersion) ? recordVersion : highestSemver(source.cacheVersions?.[descriptor.id] ?? []);
15753
+ if (!isSemverVersion(version)) continue;
15757
15754
  versions.push({ surface: source.surface, version: normalize(version) });
15758
15755
  }
15759
15756
  if (versions.length < 2) continue;
@@ -15768,8 +15765,8 @@ var OPENCODE_VERSION_LABEL = "installed OpenCode MMI adapter version (vs latest
15768
15765
  function buildOpencodeVersionCheck(input) {
15769
15766
  const fix = pluginRecoveryFix("opencode");
15770
15767
  const base = { ok: true, label: OPENCODE_VERSION_LABEL, fix };
15771
- if (!input.isOrgRepo || !isSemverVersion2(input.releasedVersion)) return base;
15772
- if (!isSemverVersion2(input.installedVersion)) {
15768
+ if (!input.isOrgRepo || !isSemverVersion(input.releasedVersion)) return base;
15769
+ if (!isSemverVersion(input.installedVersion)) {
15773
15770
  return { ...base, ok: false, severityOverride: "hard", releasedVersion: input.releasedVersion };
15774
15771
  }
15775
15772
  if (compareVersions(input.installedVersion, input.releasedVersion) >= 0) {
@@ -15786,7 +15783,7 @@ function pickOpencodeActiveVersion(input) {
15786
15783
  }
15787
15784
  function planOpencodePluginCacheQuarantine(input) {
15788
15785
  if (!input.cacheExists) return { quarantine: false };
15789
- if (!isSemverVersion2(input.cacheVersion) || !isSemverVersion2(input.releasedVersion)) return { quarantine: false };
15786
+ if (!isSemverVersion(input.cacheVersion) || !isSemverVersion(input.releasedVersion)) return { quarantine: false };
15790
15787
  if (compareVersions(input.cacheVersion, input.releasedVersion) < 0) {
15791
15788
  return { quarantine: true, cacheVersion: input.cacheVersion, releasedVersion: input.releasedVersion };
15792
15789
  }
@@ -16022,8 +16019,8 @@ function buildCursorPluginInstallCheck(input) {
16022
16019
  };
16023
16020
  }
16024
16021
  }
16025
- if (input.cacheRootExists && isSemverVersion2(input.releasedVersion) && input.pins.some((pin) => isSemverVersion2(pin.version) && compareVersions(pin.version, input.releasedVersion) < 0)) {
16026
- const stale = input.pins.filter((pin) => isSemverVersion2(pin.version) && compareVersions(pin.version, input.releasedVersion) < 0).map((pin) => `${pin.version} at ${pin.name}`).join(", ");
16022
+ if (input.cacheRootExists && isSemverVersion(input.releasedVersion) && input.pins.some((pin) => isSemverVersion(pin.version) && compareVersions(pin.version, input.releasedVersion) < 0)) {
16023
+ const stale = input.pins.filter((pin) => isSemverVersion(pin.version) && compareVersions(pin.version, input.releasedVersion) < 0).map((pin) => `${pin.version} at ${pin.name}`).join(", ");
16027
16024
  return {
16028
16025
  ...base,
16029
16026
  ok: false,
@@ -16217,18 +16214,19 @@ function isAdvisoryDoctorCheck(check) {
16217
16214
  function doctorExitCode(checks) {
16218
16215
  return checks.some((c) => !c.ok && !isAdvisoryDoctorCheck(c)) ? 1 : 0;
16219
16216
  }
16220
- function renderPluginUpdateReportStaleOnly(report) {
16217
+ function renderPluginUpdateReportStaleOnly(report, allChecksPass = false) {
16221
16218
  const v = report.versions;
16222
16219
  const released = v.released;
16223
16220
  if (!released) return [];
16224
- const isStale = (current) => Boolean(current && isSemverVersion2(current) && compareVersions(current, released) < 0);
16221
+ const isStale = (current) => Boolean(current && isSemverVersion(current) && compareVersions(current, released) < 0);
16225
16222
  const blocks = [];
16226
16223
  for (const surface of PLUGIN_GUIDE_SURFACES) {
16227
16224
  if (!surface.versionKeys.some((k) => isStale(v[k]))) continue;
16228
16225
  blocks.push(...renderSurfaceGuide(surface.label, report.recipes[surface.key]));
16229
16226
  }
16230
16227
  if (!blocks.length) return [];
16231
- return ["Update commands (stale surfaces):", ...blocks];
16228
+ const heading = allChecksPass ? "Optional updates available (all checks passed):" : "Update commands (stale surfaces):";
16229
+ return [heading, ...blocks];
16232
16230
  }
16233
16231
  var DOCTOR_VERBOSE_HINT = "Run mmi-cli doctor --verbose for the full audit checklist + version report.";
16234
16232
  function doctorCheckGlyph(check) {
@@ -16274,7 +16272,7 @@ function doctorHumanLines(input) {
16274
16272
  if (input.shouldApply && input.pluginReloadRequired) {
16275
16273
  lines.push("", `\u21BB ${input.reloadHint ?? "reload"} so the healed plugin/MCP installs load.`);
16276
16274
  }
16277
- const stale = renderPluginUpdateReportStaleOnly(input.updateReport);
16275
+ const stale = renderPluginUpdateReportStaleOnly(input.updateReport, gaps.length === 0);
16278
16276
  if (stale.length) {
16279
16277
  lines.push("", ...stale);
16280
16278
  } else if (!input.verbose) {
@@ -16344,7 +16342,7 @@ function buildPluginResolvabilityCheck(input) {
16344
16342
  return {
16345
16343
  ok: false,
16346
16344
  label: PLUGIN_RESOLVABILITY_LABEL,
16347
- fix: "run: mmi-cli plugin-heal",
16345
+ fix: `run: ${PLUGIN_SURFACE_HEAL[surfaceToken(surface) ?? "claude"]?.recovery ?? PLUGIN_SURFACE_HEAL.claude.recovery}`,
16348
16346
  state
16349
16347
  };
16350
16348
  default: {
@@ -16354,6 +16352,176 @@ function buildPluginResolvabilityCheck(input) {
16354
16352
  }
16355
16353
  }
16356
16354
 
16355
+ // src/cursor-plugin-seed.ts
16356
+ function isSemverVersion2(v) {
16357
+ return typeof v === "string" && /^v?\d+\.\d+\.\d+/.test(v.trim());
16358
+ }
16359
+ var MMI_HUB_REPO = "mutmutco/MMI-Hub";
16360
+ var CURSOR_THIRD_PARTY_STATE_KEY = "cursor/thirdPartyExtensibilityEnabled";
16361
+ var PLUGIN_JSON_REL = ".cursor-plugin/plugin.json";
16362
+ var execFileBuffer = (0, import_node_util7.promisify)(import_node_child_process10.execFile);
16363
+ function gitFetchReleaseTagArgs(hubCheckout, tag) {
16364
+ return ["-C", hubCheckout, "fetch", "origin", "tag", tag, "--quiet"];
16365
+ }
16366
+ function ghReleaseTarballApiArgs(tag) {
16367
+ return ["api", `repos/${MMI_HUB_REPO}/tarball/refs/tags/${tag}`];
16368
+ }
16369
+ function cursorUserGlobalStatePath() {
16370
+ if (process.platform === "win32") {
16371
+ const base = process.env.APPDATA || (0, import_node_path16.join)((0, import_node_os6.homedir)(), "AppData", "Roaming");
16372
+ return (0, import_node_path16.join)(base, "Cursor", "User", "globalStorage", "state.vscdb");
16373
+ }
16374
+ if (process.platform === "darwin") {
16375
+ return (0, import_node_path16.join)((0, import_node_os6.homedir)(), "Library", "Application Support", "Cursor", "User", "globalStorage", "state.vscdb");
16376
+ }
16377
+ return (0, import_node_path16.join)((0, import_node_os6.homedir)(), ".config", "Cursor", "User", "globalStorage", "state.vscdb");
16378
+ }
16379
+ async function readCursorThirdPartyExtensibilityEnabled(execFileP5) {
16380
+ const dbPath = cursorUserGlobalStatePath();
16381
+ if (!(0, import_node_fs15.existsSync)(dbPath)) return void 0;
16382
+ try {
16383
+ const { stdout } = await execFileP5("sqlite3", [dbPath, `SELECT value FROM ItemTable WHERE key = '${CURSOR_THIRD_PARTY_STATE_KEY}';`], {
16384
+ timeout: 5e3
16385
+ });
16386
+ const value = stdout.trim().toLowerCase();
16387
+ if (value === "true") return true;
16388
+ if (value === "false") return false;
16389
+ return void 0;
16390
+ } catch {
16391
+ return void 0;
16392
+ }
16393
+ }
16394
+ function syncDirContents(src, dest) {
16395
+ (0, import_node_fs15.mkdirSync)(dest, { recursive: true });
16396
+ for (const name of (0, import_node_fs15.readdirSync)(dest)) {
16397
+ (0, import_node_fs15.rmSync)((0, import_node_path16.join)(dest, name), { recursive: true, force: true });
16398
+ }
16399
+ (0, import_node_fs15.cpSync)(src, dest, { recursive: true });
16400
+ }
16401
+ function releaseTag(releasedVersion) {
16402
+ return releasedVersion.startsWith("v") ? releasedVersion : `v${releasedVersion}`;
16403
+ }
16404
+ async function extractPluginMmiFromHubCheckout(hubCheckout, tag, tmpRoot, execFileP5) {
16405
+ const tarFile = (0, import_node_path16.join)(tmpRoot, "archive.tar");
16406
+ try {
16407
+ await execFileP5("git", gitFetchReleaseTagArgs(hubCheckout, tag), { timeout: 6e4 });
16408
+ await execFileP5("git", ["-C", hubCheckout, "archive", "--format=tar", `--output=${tarFile}`, tag, "plugins/mmi"], {
16409
+ timeout: 6e4
16410
+ });
16411
+ await execFileP5("tar", ["-xf", tarFile, "-C", tmpRoot], { timeout: 6e4 });
16412
+ const pluginMmi = (0, import_node_path16.join)(tmpRoot, "plugins", "mmi");
16413
+ return (0, import_node_fs15.existsSync)((0, import_node_path16.join)(pluginMmi, PLUGIN_JSON_REL)) ? pluginMmi : void 0;
16414
+ } catch {
16415
+ return void 0;
16416
+ }
16417
+ }
16418
+ async function downloadPluginMmiViaGh(tag, tmpRoot) {
16419
+ const tarPath = (0, import_node_path16.join)(tmpRoot, "repo.tgz");
16420
+ try {
16421
+ (0, import_node_fs15.mkdirSync)(tmpRoot, { recursive: true });
16422
+ const { stdout } = await execFileBuffer("gh", ghReleaseTarballApiArgs(tag), {
16423
+ timeout: 12e4,
16424
+ maxBuffer: 100 * 1024 * 1024,
16425
+ encoding: "buffer",
16426
+ windowsHide: true
16427
+ });
16428
+ (0, import_node_fs15.writeFileSync)(tarPath, stdout);
16429
+ await execFileBuffer("tar", ["-xzf", tarPath, "-C", tmpRoot], { timeout: 12e4, windowsHide: true });
16430
+ const top = (0, import_node_fs15.readdirSync)(tmpRoot).find((entry) => entry !== "repo.tgz");
16431
+ if (!top) return void 0;
16432
+ const pluginMmi = (0, import_node_path16.join)(tmpRoot, top, "plugins", "mmi");
16433
+ return (0, import_node_fs15.existsSync)((0, import_node_path16.join)(pluginMmi, PLUGIN_JSON_REL)) ? pluginMmi : void 0;
16434
+ } catch {
16435
+ return void 0;
16436
+ }
16437
+ }
16438
+ async function resolvePluginMmiSource(releasedVersion, hubCheckout, tmpRoot, execFileP5) {
16439
+ (0, import_node_fs15.mkdirSync)(tmpRoot, { recursive: true });
16440
+ const tag = releaseTag(releasedVersion);
16441
+ if (hubCheckout) {
16442
+ const fromHub = await extractPluginMmiFromHubCheckout(hubCheckout, tag, tmpRoot, execFileP5);
16443
+ if (fromHub) return fromHub;
16444
+ }
16445
+ return downloadPluginMmiViaGh(tag, (0, import_node_path16.join)(tmpRoot, "gh"));
16446
+ }
16447
+ function cursorPluginPinsNeedingSeed(pins, releasedVersion) {
16448
+ if (!isSemverVersion2(releasedVersion)) return pins.filter((pin) => !pin.hasPluginJson || !pin.hasHooksJson || pin.isEmpty);
16449
+ return pins.filter((pin) => {
16450
+ if (!pin.hasPluginJson || !pin.hasHooksJson || pin.isEmpty) return true;
16451
+ if (isSemverVersion2(pin.version) && compareVersions(pin.version, releasedVersion) < 0) return true;
16452
+ return false;
16453
+ });
16454
+ }
16455
+ function cursorPluginCacheSeedTargets(pins, releasedVersion, cacheRoot, cacheRootExists = false) {
16456
+ const needing = cursorPluginPinsNeedingSeed(pins, releasedVersion);
16457
+ if (needing.length > 0) return needing.map((pin) => pin.path);
16458
+ if (pins.length === 0 && cacheRoot && cacheRootExists && isSemverVersion2(releasedVersion)) {
16459
+ return [(0, import_node_path16.join)(cacheRoot, `v${releasedVersion.replace(/^v/, "")}`)];
16460
+ }
16461
+ return [];
16462
+ }
16463
+ async function applyCursorPluginCacheSeed(input) {
16464
+ if (!isSemverVersion2(input.releasedVersion)) return false;
16465
+ const targets = cursorPluginCacheSeedTargets(input.pins, input.releasedVersion, input.cacheRoot, input.cacheRootExists);
16466
+ if (targets.length === 0) return false;
16467
+ const tmpRoot = await input.mkdtemp("mmi-cursor-seed-");
16468
+ const source = await resolvePluginMmiSource(input.releasedVersion, input.hubCheckout, tmpRoot, input.execFileP);
16469
+ if (!source) return false;
16470
+ input.log(` \u21BB seeding Cursor MMI plugin cache \u2192 ${input.releasedVersion}\u2026`);
16471
+ for (const dest of targets) {
16472
+ syncDirContents(source, dest);
16473
+ }
16474
+ (0, import_node_fs15.rmSync)(tmpRoot, { recursive: true, force: true });
16475
+ return true;
16476
+ }
16477
+ var CURSOR_ENABLED_PIN_LABEL = "Cursor Team Marketplace enabled plugin pin";
16478
+ function cursorSplitBrainCapabilities() {
16479
+ const skills = OPENCODE_WORKFLOW_COMMANDS.map((c) => `/${c}`).join(", ");
16480
+ return `org skills (${skills}) and SessionStart hooks`;
16481
+ }
16482
+ function cursorEnabledPinRecovery() {
16483
+ return "refresh the MMI Team Marketplace in Cursor Dashboard \u2192 Settings \u2192 Plugins (Update), or run `mmi-cli doctor --apply` to seed the enabled pin; then restart Cursor";
16484
+ }
16485
+ var CURSOR_FOUND_CACHED_MMI_RE = /Found cached plugin:\s*mmi\s+at\s+.*?[\\/]([0-9a-f]{6,40})\b/gi;
16486
+ var CURSOR_MMI_LOADED_RE = /loadClaudePlugin\s+mmi@mutmutco\s+loaded/i;
16487
+ var CURSOR_GIT_AUTH_FAIL_RE = /unable to get password from user/i;
16488
+ var CURSOR_SAFE_PIN_NAME_RE = /^[0-9a-zA-Z._-]+$/;
16489
+ function parseCursorPluginLog(text) {
16490
+ let enabledPin;
16491
+ for (const m of text.matchAll(CURSOR_FOUND_CACHED_MMI_RE)) enabledPin = m[1];
16492
+ const loadSucceeded = CURSOR_MMI_LOADED_RE.test(text);
16493
+ const loadFailed = CURSOR_GIT_AUTH_FAIL_RE.test(text) && !loadSucceeded;
16494
+ return { enabledPin, loadSucceeded, loadFailed };
16495
+ }
16496
+ function buildCursorEnabledPinCheck(input) {
16497
+ const label = CURSOR_ENABLED_PIN_LABEL;
16498
+ const healthy = { ok: true, label, fix: "" };
16499
+ if (!input.isOrgRepo) return healthy;
16500
+ if (!input.enabledPin && !input.loadFailed) return healthy;
16501
+ if (input.loadSucceeded && !input.loadFailed) return healthy;
16502
+ const caps = cursorSplitBrainCapabilities();
16503
+ if (input.loadFailed) {
16504
+ return {
16505
+ ok: false,
16506
+ severityOverride: "hard",
16507
+ label,
16508
+ fix: `Cursor Team Marketplace failed to load mmi@mutmutco${input.enabledPin ? ` (${input.enabledPin})` : ""}: mmi-cli is on PATH and the Hub session is valid, but the MMI plugin did not load \u2014 ${caps} are UNAVAILABLE in Cursor. ${cursorEnabledPinRecovery()}`
16509
+ };
16510
+ }
16511
+ const enabledDir = input.pins.find((p) => p.name === input.enabledPin);
16512
+ const complete = !!enabledDir && !enabledDir.isEmpty && enabledDir.hasPluginJson && enabledDir.hasHooksJson;
16513
+ if (!complete) {
16514
+ const others = input.pins.filter((p) => p.name !== input.enabledPin && CURSOR_SAFE_PIN_NAME_RE.test(p.name)).map((p) => p.name);
16515
+ return {
16516
+ ok: false,
16517
+ severityOverride: "hard",
16518
+ label,
16519
+ fix: `Cursor is configured to load mmi@mutmutco at commit ${input.enabledPin}, but that pin is ${enabledDir ? "incomplete" : "missing"} in the cache${others.length ? ` (a complete pin at ${others.join(", ")} is present but will NOT be loaded)` : ""} \u2014 ${caps} are unavailable in Cursor. ${cursorEnabledPinRecovery()}`
16520
+ };
16521
+ }
16522
+ return healthy;
16523
+ }
16524
+
16357
16525
  // src/mcp-reconcile.ts
16358
16526
  var PLAYWRIGHT_MCP_SPEC = {
16359
16527
  name: "playwright",
@@ -16662,9 +16830,9 @@ ${block}
16662
16830
  }
16663
16831
 
16664
16832
  // src/cli-doctor-shared.ts
16665
- var import_node_fs15 = require("node:fs");
16666
- var import_node_path16 = require("node:path");
16667
16833
  var import_node_fs16 = require("node:fs");
16834
+ var import_node_path17 = require("node:path");
16835
+ var import_node_fs17 = require("node:fs");
16668
16836
  var GC_GH_TIMEOUT_MS = 2e4;
16669
16837
  async function awsCallerArn() {
16670
16838
  try {
@@ -16710,7 +16878,7 @@ async function localBranchHeads() {
16710
16878
  }
16711
16879
  async function currentRepoWorktreeGitRoot(repoRoot) {
16712
16880
  const gitCommonDir = (await execFileP2("git", ["rev-parse", "--git-common-dir"], { timeout: GIT_TIMEOUT_MS }).catch(() => ({ stdout: "" }))).stdout.trim();
16713
- return gitCommonDir ? (0, import_node_path16.resolve)(repoRoot, gitCommonDir, "worktrees") : "";
16881
+ return gitCommonDir ? (0, import_node_path17.resolve)(repoRoot, gitCommonDir, "worktrees") : "";
16714
16882
  }
16715
16883
  async function worktreeBranches() {
16716
16884
  const { stdout } = await execFileP2("git", ["worktree", "list", "--porcelain"], { timeout: GIT_TIMEOUT_MS });
@@ -16730,18 +16898,18 @@ function resolveGitdirForWorktreeFile(worktreePath, content) {
16730
16898
  const match = /^gitdir:\s*(.+)\s*$/im.exec(content);
16731
16899
  if (!match?.[1]) return void 0;
16732
16900
  const raw = match[1].trim();
16733
- return (0, import_node_path16.isAbsolute)(raw) ? raw : (0, import_node_path16.resolve)(worktreePath, raw);
16901
+ return (0, import_node_path17.isAbsolute)(raw) ? raw : (0, import_node_path17.resolve)(worktreePath, raw);
16734
16902
  }
16735
16903
  function metadataOwnsMissingWorktreeDir(worktreePath, worktreeGitRoot) {
16736
16904
  if (!worktreeGitRoot) return false;
16737
16905
  try {
16738
- const entries = (0, import_node_fs16.readdirSync)(worktreeGitRoot, { withFileTypes: true });
16906
+ const entries = (0, import_node_fs17.readdirSync)(worktreeGitRoot, { withFileTypes: true });
16739
16907
  for (const ent of entries) {
16740
16908
  if (!ent.isDirectory()) continue;
16741
16909
  try {
16742
- const gitdirPath = (0, import_node_fs15.readFileSync)((0, import_node_path16.join)(worktreeGitRoot, ent.name, "gitdir"), "utf8").trim();
16743
- const resolvedGitdir = (0, import_node_path16.isAbsolute)(gitdirPath) ? gitdirPath : (0, import_node_path16.resolve)(worktreeGitRoot, ent.name, gitdirPath);
16744
- if (sameWorktreeMetadataPath((0, import_node_path16.dirname)(resolvedGitdir), worktreePath)) return true;
16910
+ const gitdirPath = (0, import_node_fs16.readFileSync)((0, import_node_path17.join)(worktreeGitRoot, ent.name, "gitdir"), "utf8").trim();
16911
+ const resolvedGitdir = (0, import_node_path17.isAbsolute)(gitdirPath) ? gitdirPath : (0, import_node_path17.resolve)(worktreeGitRoot, ent.name, gitdirPath);
16912
+ if (sameWorktreeMetadataPath((0, import_node_path17.dirname)(resolvedGitdir), worktreePath)) return true;
16745
16913
  } catch {
16746
16914
  }
16747
16915
  }
@@ -16751,7 +16919,7 @@ function metadataOwnsMissingWorktreeDir(worktreePath, worktreeGitRoot) {
16751
16919
  }
16752
16920
  function pathExistsKnown(path2) {
16753
16921
  try {
16754
- (0, import_node_fs16.statSync)(path2);
16922
+ (0, import_node_fs17.statSync)(path2);
16755
16923
  return true;
16756
16924
  } catch (e) {
16757
16925
  const code = typeof e === "object" && e && "code" in e ? String(e.code ?? "") : "";
@@ -16760,10 +16928,10 @@ function pathExistsKnown(path2) {
16760
16928
  }
16761
16929
  }
16762
16930
  function inspectSiblingWorktreeDir(path2, worktreeGitRoot) {
16763
- const gitPath = (0, import_node_path16.join)(path2, ".git");
16931
+ const gitPath = (0, import_node_path17.join)(path2, ".git");
16764
16932
  let st;
16765
16933
  try {
16766
- st = (0, import_node_fs16.lstatSync)(gitPath);
16934
+ st = (0, import_node_fs17.lstatSync)(gitPath);
16767
16935
  } catch (e) {
16768
16936
  const code = typeof e === "object" && e && "code" in e ? String(e.code ?? "") : "";
16769
16937
  if (code === "ENOENT" || code === "ENOTDIR") {
@@ -16780,7 +16948,7 @@ function inspectSiblingWorktreeDir(path2, worktreeGitRoot) {
16780
16948
  if (st.isDirectory()) return { path: path2, gitType: "dir" };
16781
16949
  if (!st.isFile()) return { path: path2, gitType: "other" };
16782
16950
  try {
16783
- const gitFileContent = (0, import_node_fs15.readFileSync)(gitPath, "utf8");
16951
+ const gitFileContent = (0, import_node_fs16.readFileSync)(gitPath, "utf8");
16784
16952
  const gitdir = resolveGitdirForWorktreeFile(path2, gitFileContent);
16785
16953
  const gitDirExists = gitdir ? pathExistsKnown(gitdir) : false;
16786
16954
  return {
@@ -16797,7 +16965,7 @@ function inspectSiblingWorktreeDir(path2, worktreeGitRoot) {
16797
16965
  }
16798
16966
  function inspectDeadWorktreeDirContent(path2) {
16799
16967
  try {
16800
- return { entries: (0, import_node_fs16.readdirSync)(path2) };
16968
+ return { entries: (0, import_node_fs17.readdirSync)(path2) };
16801
16969
  } catch (e) {
16802
16970
  const code = typeof e === "object" && e && "code" in e ? String(e.code ?? "") : "";
16803
16971
  return { error: code ? `unable to inspect directory contents (${code})` : "unable to inspect directory contents" };
@@ -16816,8 +16984,8 @@ async function siblingWorktreeDirs() {
16816
16984
  const worktreeGitRoot = await currentRepoWorktreeGitRoot(repoRoot);
16817
16985
  const siblingRoot = siblingMmiWorktreesRoot(repoRoot);
16818
16986
  try {
16819
- const entries = (0, import_node_fs16.readdirSync)(siblingRoot, { withFileTypes: true });
16820
- return entries.filter((ent) => ent.isDirectory()).map((ent) => inspectSiblingWorktreeDir((0, import_node_path16.join)(siblingRoot, ent.name), worktreeGitRoot)).filter((entry) => Boolean(entry));
16987
+ const entries = (0, import_node_fs17.readdirSync)(siblingRoot, { withFileTypes: true });
16988
+ return entries.filter((ent) => ent.isDirectory()).map((ent) => inspectSiblingWorktreeDir((0, import_node_path17.join)(siblingRoot, ent.name), worktreeGitRoot)).filter((entry) => Boolean(entry));
16821
16989
  } catch {
16822
16990
  return [];
16823
16991
  }
@@ -16867,7 +17035,7 @@ async function fetchHubVersionInfo(baseUrl) {
16867
17035
  }
16868
17036
  function readRepoVersion() {
16869
17037
  try {
16870
- return JSON.parse((0, import_node_fs17.readFileSync)((0, import_node_path17.join)(process.cwd(), ".claude-plugin", "plugin.json"), "utf8")).version || void 0;
17038
+ return JSON.parse((0, import_node_fs18.readFileSync)((0, import_node_path18.join)(process.cwd(), ".claude-plugin", "plugin.json"), "utf8")).version || void 0;
16871
17039
  } catch {
16872
17040
  return void 0;
16873
17041
  }
@@ -16933,15 +17101,15 @@ function reexecAttemptOutcome(strategy, code) {
16933
17101
  return "result";
16934
17102
  }
16935
17103
  function spawnReexecAttempt(command, args, env) {
16936
- return new Promise((resolve6) => {
17104
+ return new Promise((resolve5) => {
16937
17105
  let settled = false;
16938
17106
  const done = (code) => {
16939
17107
  if (!settled) {
16940
17108
  settled = true;
16941
- resolve6(code);
17109
+ resolve5(code);
16942
17110
  }
16943
17111
  };
16944
- const child = (0, import_node_child_process10.spawn)(command, args, { stdio: "inherit", env });
17112
+ const child = (0, import_node_child_process11.spawn)(command, args, { stdio: "inherit", env });
16945
17113
  child.on("error", () => done(-1));
16946
17114
  child.on("exit", (code) => done(code ?? 0));
16947
17115
  });
@@ -16994,6 +17162,14 @@ ${stderr}`);
16994
17162
  return false;
16995
17163
  }
16996
17164
  }
17165
+ function healBannerLine(bin, token, refSupported) {
17166
+ const installVerb = token === "codex" ? "add" : "install";
17167
+ return refSupported ? ` \u21BB reinstalling the MMI plugin via \`${bin} plugin\` (marketplace remove \u2192 add --ref main \u2192 ${installVerb})\u2026` : ` \u21BB reinstalling the MMI plugin via \`${bin} plugin\` (marketplace remove \u2192 add \u2192 ${installVerb}; release ref pinned by the marketplace manifest)\u2026`;
17168
+ }
17169
+ function refAbsenceNote(bin, refSupported) {
17170
+ return refSupported ? "" : `
17171
+ Note: \`${bin}\` has no \`--ref\` option; the marketplace manifest pins the released branch, so none is needed (#2516).`;
17172
+ }
16997
17173
  async function applyPluginHeal(token, surface, log, opts) {
16998
17174
  if (!opts?.force && surfaceToken(surface) !== token) return false;
16999
17175
  const descriptor = PLUGIN_SURFACE_HEAL[token];
@@ -17002,13 +17178,8 @@ async function applyPluginHeal(token, surface, log, opts) {
17002
17178
  const bin = descriptor.pluginRunner === "codex" ? "codex" : "claude";
17003
17179
  const runner = descriptor.pluginRunner === "codex" ? runCodexPlugin : runClaudePlugin;
17004
17180
  const refSupported = await marketplaceAddRefSupported(bin);
17005
- const { steps, strippedRef } = adaptHealStepsForRefSupport(tableSteps, refSupported);
17006
- log(
17007
- refSupported ? ` \u21BB reinstalling the MMI plugin via \`${bin} plugin\` (marketplace remove \u2192 add --ref main \u2192 ${token === "codex" ? "add" : "install"})\u2026` : ` \u21BB reinstalling the MMI plugin via \`${bin} plugin\` (marketplace remove \u2192 add \u2192 ${token === "codex" ? "add" : "install"})\u2026`
17008
- );
17009
- if (strippedRef) {
17010
- log(` \u26A0 \`${bin}\` has no \`--ref\` option \u2014 cloning the default branch; update \`${bin}\` to pin the released \`main\` branch (#2080)`);
17011
- }
17181
+ const { steps } = adaptHealStepsForRefSupport(tableSteps, refSupported);
17182
+ log(healBannerLine(bin, token, refSupported));
17012
17183
  for (const step of steps) {
17013
17184
  if (healStepAborts(step, await runner([...step.args]))) return false;
17014
17185
  }
@@ -17016,30 +17187,42 @@ async function applyPluginHeal(token, surface, log, opts) {
17016
17187
  }
17017
17188
  var installedPluginsPath = (surface = detectSurface(process.env)) => {
17018
17189
  const homeDir = surface === "codex" ? ".codex" : ".claude";
17019
- return (0, import_node_path17.join)((0, import_node_os6.homedir)(), homeDir, "plugins", "installed_plugins.json");
17190
+ return (0, import_node_path18.join)((0, import_node_os7.homedir)(), homeDir, "plugins", "installed_plugins.json");
17020
17191
  };
17021
17192
  function readInstalledPlugins(surface = detectSurface(process.env)) {
17022
17193
  try {
17023
- return JSON.parse((0, import_node_fs17.readFileSync)(installedPluginsPath(surface), "utf8"));
17194
+ return JSON.parse((0, import_node_fs18.readFileSync)(installedPluginsPath(surface), "utf8"));
17024
17195
  } catch {
17025
17196
  return null;
17026
17197
  }
17027
17198
  }
17199
+ function marketplaceCloneCandidates(surface, home) {
17200
+ if (surface === "codex") {
17201
+ return [
17202
+ (0, import_node_path18.join)(home, ".codex", ".tmp", "marketplaces", "mutmutco"),
17203
+ (0, import_node_path18.join)(home, ".codex", "plugins", "marketplaces", "mutmutco")
17204
+ ];
17205
+ }
17206
+ return [(0, import_node_path18.join)(home, ".claude", "plugins", "marketplaces", "mutmutco")];
17207
+ }
17208
+ function marketplaceClonePresent(surface, home, exists = import_node_fs18.existsSync) {
17209
+ return marketplaceCloneCandidates(surface, home).some(exists);
17210
+ }
17028
17211
  function snapshotPluginGuardInput(surface = detectSurface(process.env), isOrgRepo = false) {
17029
17212
  const homeDir = surface === "codex" ? ".codex" : ".claude";
17030
17213
  const installed = readInstalledPlugins(surface);
17031
17214
  return {
17032
17215
  isOrgRepo,
17033
17216
  installRecordPresent: hasUserInstallRecord(installed, MMI_PLUGIN_ID) || hasProjectInstallRecord(installed, MMI_PLUGIN_ID, process.cwd()),
17034
- marketplaceClonePresent: (0, import_node_fs17.existsSync)((0, import_node_path17.join)((0, import_node_os6.homedir)(), homeDir, "plugins", "marketplaces", "mutmutco")),
17035
- pluginCachePresent: (0, import_node_fs17.existsSync)((0, import_node_path17.join)((0, import_node_os6.homedir)(), homeDir, "plugins", "cache", "mutmutco", "mmi"))
17217
+ marketplaceClonePresent: marketplaceClonePresent(surface, (0, import_node_os7.homedir)()),
17218
+ pluginCachePresent: (0, import_node_fs18.existsSync)((0, import_node_path18.join)((0, import_node_os7.homedir)(), homeDir, "plugins", "cache", "mutmutco", "mmi"))
17036
17219
  };
17037
17220
  }
17038
17221
  function installedPluginSources() {
17039
17222
  return ["claude", "codex"].map((surface) => {
17040
- const recordPath = (0, import_node_path17.join)((0, import_node_os6.homedir)(), `.${surface}`, "plugins", "installed_plugins.json");
17223
+ const recordPath = (0, import_node_path18.join)((0, import_node_os7.homedir)(), `.${surface}`, "plugins", "installed_plugins.json");
17041
17224
  try {
17042
- return { surface, installed: JSON.parse((0, import_node_fs17.readFileSync)(recordPath, "utf8")), recordPath };
17225
+ return { surface, installed: JSON.parse((0, import_node_fs18.readFileSync)(recordPath, "utf8")), recordPath };
17043
17226
  } catch {
17044
17227
  return { surface, installed: null, recordPath };
17045
17228
  }
@@ -17052,8 +17235,8 @@ function managedPluginSurfaceSources() {
17052
17235
  const segments = parseManagedPluginId(descriptor.id);
17053
17236
  if (!segments) continue;
17054
17237
  try {
17055
- cacheVersions[descriptor.id] = (0, import_node_fs17.readdirSync)(
17056
- (0, import_node_path17.join)((0, import_node_os6.homedir)(), `.${surface}`, "plugins", "cache", segments.marketplace, segments.name),
17238
+ cacheVersions[descriptor.id] = (0, import_node_fs18.readdirSync)(
17239
+ (0, import_node_path18.join)((0, import_node_os7.homedir)(), `.${surface}`, "plugins", "cache", segments.marketplace, segments.name),
17057
17240
  { withFileTypes: true }
17058
17241
  ).filter((entry) => entry.isDirectory()).map((entry) => entry.name);
17059
17242
  } catch {
@@ -17073,8 +17256,8 @@ function managedPluginCursorDirectVersions() {
17073
17256
  const segments = parseManagedPluginId(descriptor.id);
17074
17257
  if (!segments) continue;
17075
17258
  try {
17076
- const versions = (0, import_node_fs17.readdirSync)(
17077
- (0, import_node_path17.join)((0, import_node_os6.homedir)(), ".cursor", "plugins", "cache", segments.marketplace, segments.name),
17259
+ const versions = (0, import_node_fs18.readdirSync)(
17260
+ (0, import_node_path18.join)((0, import_node_os7.homedir)(), ".cursor", "plugins", "cache", segments.marketplace, segments.name),
17078
17261
  { withFileTypes: true }
17079
17262
  ).filter((entry) => entry.isDirectory() && !entry.name.startsWith(".")).map((entry) => entry.name);
17080
17263
  out[descriptor.id] = highestSemver(versions);
@@ -17093,7 +17276,7 @@ function managedPluginOpencodeDirectVersions() {
17093
17276
  }
17094
17277
  function readOpencodePackageVersionFrom(packageJsonPath) {
17095
17278
  try {
17096
- const parsed = JSON.parse((0, import_node_fs17.readFileSync)(packageJsonPath, "utf8"));
17279
+ const parsed = JSON.parse((0, import_node_fs18.readFileSync)(packageJsonPath, "utf8"));
17097
17280
  return typeof parsed.version === "string" && parsed.version.trim() ? parsed.version.trim() : void 0;
17098
17281
  } catch {
17099
17282
  return void 0;
@@ -17103,14 +17286,14 @@ function readOpencodePackageVersion(packageName) {
17103
17286
  const [scope, name] = packageName.startsWith("@") ? packageName.slice(1).split("/", 2) : [void 0, packageName];
17104
17287
  if (!name) return void 0;
17105
17288
  const diskCandidates = [
17106
- (0, import_node_path17.join)(opencodeConfigDir(), "node_modules", ...scope ? [`@${scope}`] : [], name, "package.json"),
17107
- (0, import_node_path17.join)((0, import_node_os6.homedir)(), ".cache", "opencode", "node_modules", ...scope ? [`@${scope}`] : [], name, "package.json")
17289
+ (0, import_node_path18.join)(opencodeConfigDir(), "node_modules", ...scope ? [`@${scope}`] : [], name, "package.json"),
17290
+ (0, import_node_path18.join)((0, import_node_os7.homedir)(), ".cache", "opencode", "node_modules", ...scope ? [`@${scope}`] : [], name, "package.json")
17108
17291
  ];
17109
17292
  const diskVersion = diskCandidates.map(readOpencodePackageVersionFrom).find((v) => v !== void 0);
17110
- const packagesRoot = (0, import_node_path17.join)((0, import_node_os6.homedir)(), ".cache", "opencode", "packages", ...scope ? [`@${scope}`] : []);
17293
+ const packagesRoot = (0, import_node_path18.join)((0, import_node_os7.homedir)(), ".cache", "opencode", "packages", ...scope ? [`@${scope}`] : []);
17111
17294
  let cacheVersion;
17112
17295
  try {
17113
- const cacheVersions = (0, import_node_fs17.readdirSync)(packagesRoot, { withFileTypes: true }).filter((e) => e.isDirectory() && e.name.startsWith(`${name}@`) && !e.name.startsWith(".")).map((e) => readOpencodePackageVersionFrom((0, import_node_path17.join)(packagesRoot, e.name, "node_modules", ...scope ? [`@${scope}`] : [], name, "package.json"))).filter((v) => Boolean(v));
17296
+ const cacheVersions = (0, import_node_fs18.readdirSync)(packagesRoot, { withFileTypes: true }).filter((e) => e.isDirectory() && e.name.startsWith(`${name}@`) && !e.name.startsWith(".")).map((e) => readOpencodePackageVersionFrom((0, import_node_path18.join)(packagesRoot, e.name, "node_modules", ...scope ? [`@${scope}`] : [], name, "package.json"))).filter((v) => Boolean(v));
17114
17297
  if (cacheVersions.length) cacheVersion = cacheVersions.reduce((lowest, v) => compareVersions(v, lowest) < 0 ? v : lowest);
17115
17298
  } catch {
17116
17299
  }
@@ -17118,7 +17301,7 @@ function readOpencodePackageVersion(packageName) {
17118
17301
  }
17119
17302
  function readClaudeSettings() {
17120
17303
  try {
17121
- return JSON.parse((0, import_node_fs17.readFileSync)((0, import_node_path17.join)(process.cwd(), ".claude", "settings.json"), "utf8"));
17304
+ return JSON.parse((0, import_node_fs18.readFileSync)((0, import_node_path18.join)(process.cwd(), ".claude", "settings.json"), "utf8"));
17122
17305
  } catch {
17123
17306
  return null;
17124
17307
  }
@@ -17140,7 +17323,7 @@ function writeProjectInstallRecord(record) {
17140
17323
  const list = file.plugins[MMI_PLUGIN_ID] ?? [];
17141
17324
  list.push(record);
17142
17325
  file.plugins[MMI_PLUGIN_ID] = list;
17143
- (0, import_node_fs17.writeFileSync)(installedPluginsPath(), `${JSON.stringify(file, null, 2)}
17326
+ (0, import_node_fs18.writeFileSync)(installedPluginsPath(), `${JSON.stringify(file, null, 2)}
17144
17327
  `, "utf8");
17145
17328
  return true;
17146
17329
  } catch {
@@ -17153,9 +17336,9 @@ function backupAndWriteInstalledPlugins(records, pluginId) {
17153
17336
  if (!file) return false;
17154
17337
  if (!file.plugins) file.plugins = {};
17155
17338
  const path2 = installedPluginsPath();
17156
- (0, import_node_fs17.copyFileSync)(path2, `${path2}.bak`);
17339
+ (0, import_node_fs18.copyFileSync)(path2, `${path2}.bak`);
17157
17340
  file.plugins[pluginId] = records;
17158
- (0, import_node_fs17.writeFileSync)(path2, `${JSON.stringify(file, null, 2)}
17341
+ (0, import_node_fs18.writeFileSync)(path2, `${JSON.stringify(file, null, 2)}
17159
17342
  `, "utf8");
17160
17343
  return true;
17161
17344
  } catch {
@@ -17163,22 +17346,22 @@ function backupAndWriteInstalledPlugins(records, pluginId) {
17163
17346
  }
17164
17347
  }
17165
17348
  function opencodeConfigDir() {
17166
- return (0, import_node_path17.join)((0, import_node_os6.homedir)(), ".config", "opencode");
17349
+ return (0, import_node_path18.join)((0, import_node_os7.homedir)(), ".config", "opencode");
17167
17350
  }
17168
17351
  function opencodeConfigPath() {
17169
- return (0, import_node_path17.join)(opencodeConfigDir(), "opencode.jsonc");
17352
+ return (0, import_node_path18.join)(opencodeConfigDir(), "opencode.jsonc");
17170
17353
  }
17171
17354
  function opencodeCommandsDir() {
17172
- return (0, import_node_path17.join)(opencodeConfigDir(), "commands");
17355
+ return (0, import_node_path18.join)(opencodeConfigDir(), "commands");
17173
17356
  }
17174
17357
  function opencodeSkillsPath() {
17175
- return (0, import_node_path17.join)(opencodeConfigDir(), "node_modules", "@mutmutco", "opencode-mmi", "skills");
17358
+ return (0, import_node_path18.join)(opencodeConfigDir(), "node_modules", "@mutmutco", "opencode-mmi", "skills");
17176
17359
  }
17177
17360
  function opencodeConfigSnapshot() {
17178
17361
  const path2 = opencodeConfigPath();
17179
- if (!(0, import_node_fs17.existsSync)(path2)) return { path: path2, hasConfig: false, hasPluginField: false, parseOk: true };
17362
+ if (!(0, import_node_fs18.existsSync)(path2)) return { path: path2, hasConfig: false, hasPluginField: false, parseOk: true };
17180
17363
  try {
17181
- const raw = (0, import_node_fs17.readFileSync)(path2, "utf8");
17364
+ const raw = (0, import_node_fs18.readFileSync)(path2, "utf8");
17182
17365
  const parsed = JSON.parse(stripJsonc(raw));
17183
17366
  const hasPluginField = Object.prototype.hasOwnProperty.call(parsed, "plugin");
17184
17367
  const skillsPaths = Array.isArray(parsed.skills?.paths) ? parsed.skills.paths.filter((p) => typeof p === "string") : void 0;
@@ -17201,9 +17384,9 @@ function writeOpencodeConfigPlugin(snapshot) {
17201
17384
  const plan = planOpencodeConfigWrite(snapshot.hasConfig ? snapshot.raw : void 0);
17202
17385
  if (plan.action === "already") return true;
17203
17386
  if (!plan.text || plan.action === "unsafe") return false;
17204
- (0, import_node_fs17.mkdirSync)((0, import_node_path17.dirname)(path2), { recursive: true });
17205
- if (snapshot.hasConfig) (0, import_node_fs17.copyFileSync)(path2, `${path2}.bak`);
17206
- (0, import_node_fs17.writeFileSync)(path2, plan.text, "utf8");
17387
+ (0, import_node_fs18.mkdirSync)((0, import_node_path18.dirname)(path2), { recursive: true });
17388
+ if (snapshot.hasConfig) (0, import_node_fs18.copyFileSync)(path2, `${path2}.bak`);
17389
+ (0, import_node_fs18.writeFileSync)(path2, plan.text, "utf8");
17207
17390
  return true;
17208
17391
  } catch {
17209
17392
  return false;
@@ -17218,9 +17401,9 @@ function writeOpencodeSkillsPath(snapshot, skillsPath) {
17218
17401
  const normalized = skillsPath.replace(/\\/g, "/");
17219
17402
  if (!paths.some((p) => p.replace(/\\/g, "/") === normalized)) paths.push(skillsPath.replace(/\\/g, "/"));
17220
17403
  parsed.skills = { ...skills, paths };
17221
- (0, import_node_fs17.mkdirSync)((0, import_node_path17.dirname)(snapshot.path), { recursive: true });
17222
- if (snapshot.hasConfig && (0, import_node_fs17.existsSync)(snapshot.path)) (0, import_node_fs17.copyFileSync)(snapshot.path, `${snapshot.path}.bak`);
17223
- (0, import_node_fs17.writeFileSync)(snapshot.path, `${JSON.stringify(parsed, null, 2)}
17404
+ (0, import_node_fs18.mkdirSync)((0, import_node_path18.dirname)(snapshot.path), { recursive: true });
17405
+ if (snapshot.hasConfig && (0, import_node_fs18.existsSync)(snapshot.path)) (0, import_node_fs18.copyFileSync)(snapshot.path, `${snapshot.path}.bak`);
17406
+ (0, import_node_fs18.writeFileSync)(snapshot.path, `${JSON.stringify(parsed, null, 2)}
17224
17407
  `, "utf8");
17225
17408
  return true;
17226
17409
  } catch {
@@ -17229,7 +17412,7 @@ function writeOpencodeSkillsPath(snapshot, skillsPath) {
17229
17412
  }
17230
17413
  function opencodeExistingCommands() {
17231
17414
  try {
17232
- return (0, import_node_fs17.readdirSync)(opencodeCommandsDir(), { withFileTypes: true }).filter((entry) => entry.isFile() && entry.name.endsWith(".md")).map((entry) => entry.name.slice(0, -3).toLowerCase());
17415
+ return (0, import_node_fs18.readdirSync)(opencodeCommandsDir(), { withFileTypes: true }).filter((entry) => entry.isFile() && entry.name.endsWith(".md")).map((entry) => entry.name.slice(0, -3).toLowerCase());
17233
17416
  } catch {
17234
17417
  return [];
17235
17418
  }
@@ -17237,9 +17420,9 @@ function opencodeExistingCommands() {
17237
17420
  function writeOpencodeCommandFiles() {
17238
17421
  try {
17239
17422
  const dir = opencodeCommandsDir();
17240
- (0, import_node_fs17.mkdirSync)(dir, { recursive: true });
17423
+ (0, import_node_fs18.mkdirSync)(dir, { recursive: true });
17241
17424
  for (const command of OPENCODE_WORKFLOW_COMMANDS) {
17242
- (0, import_node_fs17.writeFileSync)((0, import_node_path17.join)(dir, `${command}.md`), opencodeCommandMarkdown(command), "utf8");
17425
+ (0, import_node_fs18.writeFileSync)((0, import_node_path18.join)(dir, `${command}.md`), opencodeCommandMarkdown(command), "utf8");
17243
17426
  }
17244
17427
  return true;
17245
17428
  } catch {
@@ -17248,12 +17431,12 @@ function writeOpencodeCommandFiles() {
17248
17431
  }
17249
17432
  function readOpencodeAdapterDiskVersion() {
17250
17433
  const candidates = [
17251
- (0, import_node_path17.join)(opencodeConfigDir(), "node_modules", "@mutmutco", "opencode-mmi", "package.json"),
17252
- (0, import_node_path17.join)((0, import_node_os6.homedir)(), ".cache", "opencode", "node_modules", "@mutmutco", "opencode-mmi", "package.json")
17434
+ (0, import_node_path18.join)(opencodeConfigDir(), "node_modules", "@mutmutco", "opencode-mmi", "package.json"),
17435
+ (0, import_node_path18.join)((0, import_node_os7.homedir)(), ".cache", "opencode", "node_modules", "@mutmutco", "opencode-mmi", "package.json")
17253
17436
  ];
17254
17437
  for (const path2 of candidates) {
17255
17438
  try {
17256
- const parsed = JSON.parse((0, import_node_fs17.readFileSync)(path2, "utf8"));
17439
+ const parsed = JSON.parse((0, import_node_fs18.readFileSync)(path2, "utf8"));
17257
17440
  if (typeof parsed.version === "string" && parsed.version.trim()) return parsed.version.trim();
17258
17441
  } catch {
17259
17442
  continue;
@@ -17269,7 +17452,7 @@ async function forceInstallOpencodeMmiPlugins(snapshot, log) {
17269
17452
  try {
17270
17453
  const specs = opencodeMmiPluginSpecs(snapshot);
17271
17454
  log(` \u21BB force-refreshing OpenCode MMI npm plugin(s): ${specs.join(", ")}\u2026`);
17272
- (0, import_node_fs17.mkdirSync)(opencodeConfigDir(), { recursive: true });
17455
+ (0, import_node_fs18.mkdirSync)(opencodeConfigDir(), { recursive: true });
17273
17456
  await runHostBin("npm", ["install", "--prefix", opencodeConfigDir(), "--force", ...specs], { timeout: NPM_UPDATE_TIMEOUT_MS });
17274
17457
  return true;
17275
17458
  } catch {
@@ -17277,12 +17460,12 @@ async function forceInstallOpencodeMmiPlugins(snapshot, log) {
17277
17460
  }
17278
17461
  }
17279
17462
  function opencodePackagesRoot() {
17280
- return (0, import_node_path17.join)((0, import_node_os6.homedir)(), ".cache", "opencode", "packages", "@mutmutco");
17463
+ return (0, import_node_path18.join)((0, import_node_os7.homedir)(), ".cache", "opencode", "packages", "@mutmutco");
17281
17464
  }
17282
17465
  function opencodePluginCacheDirs() {
17283
17466
  const root = opencodePackagesRoot();
17284
17467
  try {
17285
- return (0, import_node_fs17.readdirSync)(root, { withFileTypes: true }).filter((e) => e.isDirectory() && e.name.startsWith("opencode-mmi@") && !e.name.startsWith(".")).map((e) => (0, import_node_path17.join)(root, e.name));
17468
+ return (0, import_node_fs18.readdirSync)(root, { withFileTypes: true }).filter((e) => e.isDirectory() && e.name.startsWith("opencode-mmi@") && !e.name.startsWith(".")).map((e) => (0, import_node_path18.join)(root, e.name));
17286
17469
  } catch {
17287
17470
  return [];
17288
17471
  }
@@ -17290,7 +17473,7 @@ function opencodePluginCacheDirs() {
17290
17473
  function readOpencodeCacheDirVersion(cacheDir) {
17291
17474
  try {
17292
17475
  const parsed = JSON.parse(
17293
- (0, import_node_fs17.readFileSync)((0, import_node_path17.join)(cacheDir, "node_modules", "@mutmutco", "opencode-mmi", "package.json"), "utf8")
17476
+ (0, import_node_fs18.readFileSync)((0, import_node_path18.join)(cacheDir, "node_modules", "@mutmutco", "opencode-mmi", "package.json"), "utf8")
17294
17477
  );
17295
17478
  return typeof parsed.version === "string" && parsed.version.trim() ? parsed.version.trim() : void 0;
17296
17479
  } catch {
@@ -17313,9 +17496,9 @@ function quarantineStaleOpencodePluginCaches(releasedVersion, log) {
17313
17496
  });
17314
17497
  if (!plan.quarantine) continue;
17315
17498
  try {
17316
- const quarantineRoot = (0, import_node_path17.join)(opencodePackagesRoot(), ".mmi-quarantine", stamp);
17317
- (0, import_node_fs17.mkdirSync)(quarantineRoot, { recursive: true });
17318
- (0, import_node_fs17.renameSync)(cacheDir, (0, import_node_path17.join)(quarantineRoot, (0, import_node_path17.basename)(cacheDir)));
17499
+ const quarantineRoot = (0, import_node_path18.join)(opencodePackagesRoot(), ".mmi-quarantine", stamp);
17500
+ (0, import_node_fs18.mkdirSync)(quarantineRoot, { recursive: true });
17501
+ (0, import_node_fs18.renameSync)(cacheDir, (0, import_node_path18.join)(quarantineRoot, (0, import_node_path18.basename)(cacheDir)));
17319
17502
  log(` \u21BB quarantined stale OpenCode plugin cache ${plan.cacheVersion} (< ${plan.releasedVersion}) \u2014 OpenCode reinstalls the current adapter on next start`);
17320
17503
  moved = true;
17321
17504
  } catch {
@@ -17341,30 +17524,30 @@ function opencodePluginVersionsForReport() {
17341
17524
  }
17342
17525
  function opencodeDesktopLogsRoot() {
17343
17526
  if (process.platform === "win32") {
17344
- const base = process.env.APPDATA || (0, import_node_path17.join)((0, import_node_os6.homedir)(), "AppData", "Roaming");
17345
- return (0, import_node_path17.join)(base, "ai.opencode.desktop", "logs");
17527
+ const base = process.env.APPDATA || (0, import_node_path18.join)((0, import_node_os7.homedir)(), "AppData", "Roaming");
17528
+ return (0, import_node_path18.join)(base, "ai.opencode.desktop", "logs");
17346
17529
  }
17347
17530
  if (process.platform === "darwin") {
17348
- return (0, import_node_path17.join)((0, import_node_os6.homedir)(), "Library", "Application Support", "ai.opencode.desktop", "logs");
17531
+ return (0, import_node_path18.join)((0, import_node_os7.homedir)(), "Library", "Application Support", "ai.opencode.desktop", "logs");
17349
17532
  }
17350
- return (0, import_node_path17.join)((0, import_node_os6.homedir)(), ".config", "ai.opencode.desktop", "logs");
17533
+ return (0, import_node_path18.join)((0, import_node_os7.homedir)(), ".config", "ai.opencode.desktop", "logs");
17351
17534
  }
17352
17535
  function opencodeDesktopBootstrapSnapshot() {
17353
17536
  const root = opencodeDesktopLogsRoot();
17354
17537
  try {
17355
- const sessionDirs = (0, import_node_fs17.readdirSync)(root, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => (0, import_node_path17.join)(root, entry.name)).sort((a, b) => (0, import_node_fs17.statSync)(b).mtimeMs - (0, import_node_fs17.statSync)(a).mtimeMs);
17538
+ const sessionDirs = (0, import_node_fs18.readdirSync)(root, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => (0, import_node_path18.join)(root, entry.name)).sort((a, b) => (0, import_node_fs18.statSync)(b).mtimeMs - (0, import_node_fs18.statSync)(a).mtimeMs);
17356
17539
  const newest = sessionDirs[0];
17357
17540
  if (!newest) return [];
17358
- const logPath = (0, import_node_path17.join)(newest, "renderer.log");
17359
- const text = (0, import_node_fs17.readFileSync)(logPath, "utf8");
17360
- return opencodeAgentDirectoriesFromLog(text).filter((directory) => !(0, import_node_fs17.existsSync)(directory)).map((directory) => ({ directory, logPath }));
17541
+ const logPath = (0, import_node_path18.join)(newest, "renderer.log");
17542
+ const text = (0, import_node_fs18.readFileSync)(logPath, "utf8");
17543
+ return opencodeAgentDirectoriesFromLog(text).filter((directory) => !(0, import_node_fs18.existsSync)(directory)).map((directory) => ({ directory, logPath }));
17361
17544
  } catch {
17362
17545
  return [];
17363
17546
  }
17364
17547
  }
17365
17548
  function opencodeLegacyConfigSnapshot() {
17366
- const legacyPath = (0, import_node_path17.join)((0, import_node_os6.homedir)(), ".opencode", "opencode.json");
17367
- if (!(0, import_node_fs17.existsSync)(legacyPath)) return {};
17549
+ const legacyPath = (0, import_node_path18.join)((0, import_node_os7.homedir)(), ".opencode", "opencode.json");
17550
+ if (!(0, import_node_fs18.existsSync)(legacyPath)) return {};
17368
17551
  const content = readTextFile(legacyPath);
17369
17552
  if (content == null) return {};
17370
17553
  const plugins = parseOpencodeLegacyConfigPlugins(content);
@@ -17376,45 +17559,80 @@ function opencodeLegacyConfigSnapshot() {
17376
17559
  function quarantineOpencodeLegacyConfig(legacyPath) {
17377
17560
  try {
17378
17561
  const backupPath = `${legacyPath}.bak`;
17379
- if ((0, import_node_fs17.existsSync)(backupPath)) return false;
17380
- (0, import_node_fs17.renameSync)(legacyPath, backupPath);
17562
+ if ((0, import_node_fs18.existsSync)(backupPath)) return false;
17563
+ (0, import_node_fs18.renameSync)(legacyPath, backupPath);
17381
17564
  return true;
17382
17565
  } catch {
17383
17566
  return false;
17384
17567
  }
17385
17568
  }
17386
17569
  function cursorPluginCacheRoot() {
17387
- return (0, import_node_path17.join)((0, import_node_os6.homedir)(), ".cursor", "plugins", "cache", "mutmutco", "mmi");
17570
+ return (0, import_node_path18.join)((0, import_node_os7.homedir)(), ".cursor", "plugins", "cache", "mutmutco", "mmi");
17571
+ }
17572
+ function cursorLogsRoot() {
17573
+ if (process.platform === "win32") return (0, import_node_path18.join)((0, import_node_os7.homedir)(), "AppData", "Roaming", "Cursor", "logs");
17574
+ if (process.platform === "darwin") return (0, import_node_path18.join)((0, import_node_os7.homedir)(), "Library", "Application Support", "Cursor", "logs");
17575
+ return (0, import_node_path18.join)((0, import_node_os7.homedir)(), ".config", "Cursor", "logs");
17576
+ }
17577
+ function safeReaddirNames(dir) {
17578
+ try {
17579
+ return (0, import_node_fs18.readdirSync)(dir, { withFileTypes: true }).map((e) => e.name);
17580
+ } catch {
17581
+ return [];
17582
+ }
17583
+ }
17584
+ function readCursorPluginLogText() {
17585
+ const root = cursorLogsRoot();
17586
+ const sessions = safeReaddirNames(root).sort().reverse();
17587
+ for (const session of sessions.slice(0, 5)) {
17588
+ const sessionDir = (0, import_node_path18.join)(root, session);
17589
+ const texts = [];
17590
+ for (const win of safeReaddirNames(sessionDir).filter((n) => n.startsWith("window"))) {
17591
+ const dir = (0, import_node_path18.join)(sessionDir, win, "exthost", "anysphere.cursor-agent-exec");
17592
+ for (const file of safeReaddirNames(dir)) {
17593
+ if (!/^Cursor Plugins.*\.log$/i.test(file)) continue;
17594
+ const path2 = (0, import_node_path18.join)(dir, file);
17595
+ try {
17596
+ texts.push({ text: (0, import_node_fs18.readFileSync)(path2, "utf8"), mtime: (0, import_node_fs18.statSync)(path2).mtimeMs });
17597
+ } catch {
17598
+ }
17599
+ }
17600
+ }
17601
+ if (texts.length) {
17602
+ return texts.sort((a, b) => b.mtime - a.mtime).map((t) => t.text).join("\n");
17603
+ }
17604
+ }
17605
+ return void 0;
17388
17606
  }
17389
17607
  function cursorPluginCachePinSnapshots() {
17390
17608
  const root = cursorPluginCacheRoot();
17391
17609
  try {
17392
- return (0, import_node_fs17.readdirSync)(root, { withFileTypes: true }).filter((entry) => entry.isDirectory() && !entry.name.startsWith(".")).map((entry) => {
17393
- const path2 = (0, import_node_path17.join)(root, entry.name);
17394
- const pluginJson = (0, import_node_path17.join)(path2, ".cursor-plugin", "plugin.json");
17395
- const hooksJson = (0, import_node_path17.join)(path2, "hooks", "hooks.json");
17396
- const cliBundle = (0, import_node_path17.join)(path2, "cli", "dist", "index.cjs");
17397
- const cursorHook = (0, import_node_path17.join)(path2, "scripts", "cursor-hook.mjs");
17610
+ return (0, import_node_fs18.readdirSync)(root, { withFileTypes: true }).filter((entry) => entry.isDirectory() && !entry.name.startsWith(".")).map((entry) => {
17611
+ const path2 = (0, import_node_path18.join)(root, entry.name);
17612
+ const pluginJson = (0, import_node_path18.join)(path2, ".cursor-plugin", "plugin.json");
17613
+ const hooksJson = (0, import_node_path18.join)(path2, "hooks", "hooks.json");
17614
+ const cliBundle = (0, import_node_path18.join)(path2, "cli", "dist", "index.cjs");
17615
+ const cursorHook = (0, import_node_path18.join)(path2, "scripts", "cursor-hook.mjs");
17398
17616
  let version;
17399
17617
  try {
17400
- const raw = JSON.parse((0, import_node_fs17.readFileSync)(pluginJson, "utf8"));
17618
+ const raw = JSON.parse((0, import_node_fs18.readFileSync)(pluginJson, "utf8"));
17401
17619
  version = typeof raw.version === "string" ? raw.version : void 0;
17402
17620
  } catch {
17403
17621
  version = void 0;
17404
17622
  }
17405
17623
  let isEmpty = true;
17406
17624
  try {
17407
- isEmpty = (0, import_node_fs17.readdirSync)(path2).length === 0;
17625
+ isEmpty = (0, import_node_fs18.readdirSync)(path2).length === 0;
17408
17626
  } catch {
17409
17627
  isEmpty = true;
17410
17628
  }
17411
17629
  return {
17412
17630
  name: entry.name,
17413
17631
  path: path2,
17414
- hasPluginJson: (0, import_node_fs17.existsSync)(pluginJson),
17415
- hasHooksJson: (0, import_node_fs17.existsSync)(hooksJson),
17416
- hasCliBundle: (0, import_node_fs17.existsSync)(cliBundle),
17417
- hasCursorHookScript: (0, import_node_fs17.existsSync)(cursorHook),
17632
+ hasPluginJson: (0, import_node_fs18.existsSync)(pluginJson),
17633
+ hasHooksJson: (0, import_node_fs18.existsSync)(hooksJson),
17634
+ hasCliBundle: (0, import_node_fs18.existsSync)(cliBundle),
17635
+ hasCursorHookScript: (0, import_node_fs18.existsSync)(cursorHook),
17418
17636
  isEmpty,
17419
17637
  version
17420
17638
  };
@@ -17424,19 +17642,19 @@ function cursorPluginCachePinSnapshots() {
17424
17642
  }
17425
17643
  }
17426
17644
  function hubCheckoutForCursorSeed() {
17427
- const manifest = (0, import_node_path17.join)(process.cwd(), "plugins", "mmi", ".cursor-plugin", "plugin.json");
17428
- return (0, import_node_fs17.existsSync)(manifest) ? process.cwd() : void 0;
17645
+ const manifest = (0, import_node_path18.join)(process.cwd(), "plugins", "mmi", ".cursor-plugin", "plugin.json");
17646
+ return (0, import_node_fs18.existsSync)(manifest) ? process.cwd() : void 0;
17429
17647
  }
17430
17648
  function mmiPluginCacheRootSnapshots() {
17431
17649
  const roots = [
17432
- { surface: "claude", root: (0, import_node_path17.join)((0, import_node_os6.homedir)(), ".claude", "plugins", "cache", "mutmutco", "mmi") },
17433
- { surface: "codex", root: (0, import_node_path17.join)((0, import_node_os6.homedir)(), ".codex", "plugins", "cache", "mutmutco", "mmi") }
17650
+ { surface: "claude", root: (0, import_node_path18.join)((0, import_node_os7.homedir)(), ".claude", "plugins", "cache", "mutmutco", "mmi") },
17651
+ { surface: "codex", root: (0, import_node_path18.join)((0, import_node_os7.homedir)(), ".codex", "plugins", "cache", "mutmutco", "mmi") }
17434
17652
  ];
17435
17653
  return roots.flatMap(({ surface, root }) => {
17436
17654
  try {
17437
- const entries = (0, import_node_fs17.readdirSync)(root, { withFileTypes: true }).map((entry) => ({
17655
+ const entries = (0, import_node_fs18.readdirSync)(root, { withFileTypes: true }).map((entry) => ({
17438
17656
  name: entry.name,
17439
- path: (0, import_node_path17.join)(root, entry.name),
17657
+ path: (0, import_node_path18.join)(root, entry.name),
17440
17658
  isDirectory: entry.isDirectory()
17441
17659
  }));
17442
17660
  return [{ surface, root, entries }];
@@ -17447,7 +17665,7 @@ function mmiPluginCacheRootSnapshots() {
17447
17665
  }
17448
17666
  function hasNestedMmiChild(versionDir) {
17449
17667
  try {
17450
- return (0, import_node_fs17.statSync)((0, import_node_path17.join)(versionDir, "mmi")).isDirectory();
17668
+ return (0, import_node_fs18.statSync)((0, import_node_path18.join)(versionDir, "mmi")).isDirectory();
17451
17669
  } catch {
17452
17670
  return false;
17453
17671
  }
@@ -17458,10 +17676,10 @@ function nestedPluginTreeSnapshot() {
17458
17676
  );
17459
17677
  }
17460
17678
  function uniqueQuarantineTarget(path2) {
17461
- if (!(0, import_node_fs17.existsSync)(path2)) return path2;
17679
+ if (!(0, import_node_fs18.existsSync)(path2)) return path2;
17462
17680
  for (let i = 1; i < 100; i += 1) {
17463
17681
  const candidate = `${path2}-${i}`;
17464
- if (!(0, import_node_fs17.existsSync)(candidate)) return candidate;
17682
+ if (!(0, import_node_fs18.existsSync)(candidate)) return candidate;
17465
17683
  }
17466
17684
  return `${path2}-${Date.now()}`;
17467
17685
  }
@@ -17470,10 +17688,10 @@ function quarantinePluginCacheDirs(plan) {
17470
17688
  const failed = [];
17471
17689
  for (const move of plan) {
17472
17690
  try {
17473
- if (!(0, import_node_fs17.existsSync)(move.from)) continue;
17691
+ if (!(0, import_node_fs18.existsSync)(move.from)) continue;
17474
17692
  const target = uniqueQuarantineTarget(move.to);
17475
- (0, import_node_fs17.mkdirSync)((0, import_node_path17.dirname)(target), { recursive: true });
17476
- (0, import_node_fs17.renameSync)(move.from, target);
17693
+ (0, import_node_fs18.mkdirSync)((0, import_node_path18.dirname)(target), { recursive: true });
17694
+ (0, import_node_fs18.renameSync)(move.from, target);
17477
17695
  moved += 1;
17478
17696
  } catch {
17479
17697
  failed.push(move);
@@ -17492,23 +17710,23 @@ async function robocopyMirrorEmpty(emptyDir, target) {
17492
17710
  }
17493
17711
  async function clearNestedPluginTreeDir(targetPath) {
17494
17712
  try {
17495
- if (!(0, import_node_fs17.existsSync)(targetPath)) return true;
17713
+ if (!(0, import_node_fs18.existsSync)(targetPath)) return true;
17496
17714
  if (isWin) {
17497
- const emptyDir = (0, import_node_path17.join)((0, import_node_os6.tmpdir)(), `mmi-empty-${Date.now()}`);
17498
- (0, import_node_fs17.mkdirSync)(emptyDir, { recursive: true });
17715
+ const emptyDir = (0, import_node_path18.join)((0, import_node_os7.tmpdir)(), `mmi-empty-${Date.now()}`);
17716
+ (0, import_node_fs18.mkdirSync)(emptyDir, { recursive: true });
17499
17717
  try {
17500
17718
  await robocopyMirrorEmpty(emptyDir, targetPath);
17501
- (0, import_node_fs17.rmSync)(targetPath, { recursive: true, force: true });
17719
+ (0, import_node_fs18.rmSync)(targetPath, { recursive: true, force: true });
17502
17720
  } finally {
17503
17721
  try {
17504
- (0, import_node_fs17.rmSync)(emptyDir, { recursive: true, force: true });
17722
+ (0, import_node_fs18.rmSync)(emptyDir, { recursive: true, force: true });
17505
17723
  } catch {
17506
17724
  }
17507
17725
  }
17508
- return !(0, import_node_fs17.existsSync)(targetPath);
17726
+ return !(0, import_node_fs18.existsSync)(targetPath);
17509
17727
  }
17510
- (0, import_node_fs17.rmSync)(targetPath, { recursive: true, force: true });
17511
- return !(0, import_node_fs17.existsSync)(targetPath);
17728
+ (0, import_node_fs18.rmSync)(targetPath, { recursive: true, force: true });
17729
+ return !(0, import_node_fs18.existsSync)(targetPath);
17512
17730
  } catch {
17513
17731
  return false;
17514
17732
  }
@@ -17521,79 +17739,79 @@ async function applyNestedPluginTreeCleanup(paths, log) {
17521
17739
  }
17522
17740
  return true;
17523
17741
  }
17524
- var gitignorePath = () => (0, import_node_path17.join)(process.cwd(), ".gitignore");
17742
+ var gitignorePath = () => (0, import_node_path18.join)(process.cwd(), ".gitignore");
17525
17743
  function readTextFile(path2) {
17526
17744
  try {
17527
- if (!(0, import_node_fs17.existsSync)(path2)) return null;
17528
- return (0, import_node_fs17.readFileSync)(path2, "utf8");
17745
+ if (!(0, import_node_fs18.existsSync)(path2)) return null;
17746
+ return (0, import_node_fs18.readFileSync)(path2, "utf8");
17529
17747
  } catch {
17530
17748
  return null;
17531
17749
  }
17532
17750
  }
17533
17751
  function mcpDirExists(path2) {
17534
17752
  try {
17535
- return (0, import_node_fs17.existsSync)(path2);
17753
+ return (0, import_node_fs18.existsSync)(path2);
17536
17754
  } catch {
17537
17755
  return false;
17538
17756
  }
17539
17757
  }
17540
17758
  function mcpConfigTargets() {
17541
17759
  const cwd = process.cwd();
17542
- const home = (0, import_node_os6.homedir)();
17543
- const cursorProjectDir = (0, import_node_path17.join)(cwd, ".cursor");
17544
- const cursorUserDir = (0, import_node_path17.join)(home, ".cursor");
17545
- const codexDir = (0, import_node_path17.join)(home, ".codex");
17760
+ const home = (0, import_node_os7.homedir)();
17761
+ const cursorProjectDir = (0, import_node_path18.join)(cwd, ".cursor");
17762
+ const cursorUserDir = (0, import_node_path18.join)(home, ".cursor");
17763
+ const codexDir = (0, import_node_path18.join)(home, ".codex");
17546
17764
  return [
17547
17765
  // Claude Code project MCP — reconciled if present, never conjured (org seeds .cursor/mcp.json, not this).
17548
17766
  {
17549
17767
  host: "claude-code",
17550
17768
  label: "Claude Code project MCP",
17551
- path: (0, import_node_path17.join)(cwd, ".mcp.json"),
17769
+ path: (0, import_node_path18.join)(cwd, ".mcp.json"),
17552
17770
  format: "json",
17553
17771
  present: true,
17554
17772
  // parent is the repo root (cwd); the caller gates the whole reconcile on isOrgRepo
17555
17773
  isRepoFile: true,
17556
17774
  createWhenFileAbsent: false,
17557
- content: readTextFile((0, import_node_path17.join)(cwd, ".mcp.json"))
17775
+ content: readTextFile((0, import_node_path18.join)(cwd, ".mcp.json"))
17558
17776
  },
17559
17777
  // Cursor project MCP — the bootstrap-seeded surface; restored if its .cursor dir exists but the file is gone.
17560
17778
  {
17561
17779
  host: "cursor-project",
17562
17780
  label: "Cursor project MCP",
17563
- path: (0, import_node_path17.join)(cursorProjectDir, "mcp.json"),
17781
+ path: (0, import_node_path18.join)(cursorProjectDir, "mcp.json"),
17564
17782
  format: "json",
17565
17783
  present: mcpDirExists(cursorProjectDir),
17566
17784
  isRepoFile: true,
17567
17785
  createWhenFileAbsent: true,
17568
- content: readTextFile((0, import_node_path17.join)(cursorProjectDir, "mcp.json"))
17786
+ content: readTextFile((0, import_node_path18.join)(cursorProjectDir, "mcp.json"))
17569
17787
  },
17570
17788
  // Cursor user MCP — global; written only when Cursor is installed (~/.cursor exists).
17571
17789
  {
17572
17790
  host: "cursor-user",
17573
17791
  label: "Cursor user MCP",
17574
- path: (0, import_node_path17.join)(cursorUserDir, "mcp.json"),
17792
+ path: (0, import_node_path18.join)(cursorUserDir, "mcp.json"),
17575
17793
  format: "json",
17576
17794
  present: mcpDirExists(cursorUserDir),
17577
17795
  isRepoFile: false,
17578
17796
  createWhenFileAbsent: true,
17579
- content: readTextFile((0, import_node_path17.join)(cursorUserDir, "mcp.json"))
17797
+ content: readTextFile((0, import_node_path18.join)(cursorUserDir, "mcp.json"))
17580
17798
  },
17581
17799
  // Codex user config (TOML) — global; written only when Codex is installed (~/.codex exists).
17582
17800
  {
17583
17801
  host: "codex",
17584
17802
  label: "Codex user config",
17585
- path: (0, import_node_path17.join)(codexDir, "config.toml"),
17803
+ path: (0, import_node_path18.join)(codexDir, "config.toml"),
17586
17804
  format: "toml",
17587
17805
  present: mcpDirExists(codexDir),
17588
17806
  isRepoFile: false,
17589
17807
  createWhenFileAbsent: true,
17590
- content: readTextFile((0, import_node_path17.join)(codexDir, "config.toml"))
17808
+ content: readTextFile((0, import_node_path18.join)(codexDir, "config.toml"))
17591
17809
  }
17592
17810
  ];
17593
17811
  }
17594
17812
  function writeMcpConfigFile(path2, content) {
17595
17813
  try {
17596
- (0, import_node_fs17.writeFileSync)(path2, content, "utf8");
17814
+ (0, import_node_fs18.writeFileSync)(path2, content, "utf8");
17597
17815
  return true;
17598
17816
  } catch {
17599
17817
  return false;
@@ -17673,7 +17891,7 @@ function strayBrowserArtifactPaths() {
17673
17891
  const cwd = process.cwd();
17674
17892
  return STRAY_BROWSER_ARTIFACT_DIRS.filter((rel) => {
17675
17893
  try {
17676
- return (0, import_node_fs17.existsSync)((0, import_node_path17.join)(cwd, rel));
17894
+ return (0, import_node_fs18.existsSync)((0, import_node_path18.join)(cwd, rel));
17677
17895
  } catch {
17678
17896
  return false;
17679
17897
  }
@@ -17681,14 +17899,14 @@ function strayBrowserArtifactPaths() {
17681
17899
  }
17682
17900
  function readGitignore() {
17683
17901
  try {
17684
- return (0, import_node_fs17.readFileSync)(gitignorePath(), "utf8");
17902
+ return (0, import_node_fs18.readFileSync)(gitignorePath(), "utf8");
17685
17903
  } catch {
17686
17904
  return null;
17687
17905
  }
17688
17906
  }
17689
17907
  function writeGitignore(content) {
17690
17908
  try {
17691
- (0, import_node_fs17.writeFileSync)(gitignorePath(), content, "utf8");
17909
+ (0, import_node_fs18.writeFileSync)(gitignorePath(), content, "utf8");
17692
17910
  return true;
17693
17911
  } catch {
17694
17912
  return false;
@@ -17730,7 +17948,7 @@ async function runDoctor(opts, io = consoleIo, readOrigin) {
17730
17948
  const semverPrefix = /^\d+\.\d+\.\d+/;
17731
17949
  const isBehind = (installed2, released) => Boolean(installed2 && released && semverPrefix.test(installed2) && semverPrefix.test(released) && compareVersions(installed2, released) < 0);
17732
17950
  const opencodeAdapterStale = isBehind(opencodeInstalledVersionForDoctor(), releasedVersion);
17733
- const cursorCacheStale = (0, import_node_fs17.existsSync)(cursorPluginCacheRoot()) && (cursorPluginCachePinSnapshots() ?? []).some((p) => isBehind(p.version, releasedVersion));
17951
+ const cursorCacheStale = (0, import_node_fs18.existsSync)(cursorPluginCacheRoot()) && (cursorPluginCachePinSnapshots() ?? []).some((p) => isBehind(p.version, releasedVersion));
17734
17952
  const healPlan = doctorHealPlan({
17735
17953
  isOrgRepo,
17736
17954
  surface,
@@ -17765,7 +17983,7 @@ async function runDoctor(opts, io = consoleIo, readOrigin) {
17765
17983
  let onPath = pathProbe;
17766
17984
  if (!onPath) {
17767
17985
  const root = process.env.CLAUDE_PLUGIN_ROOT;
17768
- if (root && (0, import_node_fs17.existsSync)(`${root}/bin/mmi-cli${isWin ? ".cmd" : ""}`)) onPath = true;
17986
+ if (root && (0, import_node_fs18.existsSync)(`${root}/bin/mmi-cli${isWin ? ".cmd" : ""}`)) onPath = true;
17769
17987
  }
17770
17988
  checks.push({ ok: onPath, label: "mmi-cli on PATH", fix: "auto-provisioned at session start \u2014 reopen the session, or install the MMI plugin" });
17771
17989
  const reloadHint = reloadAction(surface);
@@ -17912,7 +18130,7 @@ async function runDoctor(opts, io = consoleIo, readOrigin) {
17912
18130
  checks.push(gitignoreCheck);
17913
18131
  checks.push(buildRepoLocalWorktreeCheck({
17914
18132
  isOrgRepo,
17915
- hasRepoLocalWorktrees: (0, import_node_fs17.existsSync)((0, import_node_path17.join)(process.cwd(), ".worktrees"))
18133
+ hasRepoLocalWorktrees: (0, import_node_fs18.existsSync)((0, import_node_path18.join)(process.cwd(), ".worktrees"))
17916
18134
  }));
17917
18135
  let driftCheck = buildPluginConfigDriftCheck({ isOrgRepo, installed, surface });
17918
18136
  if (!driftCheck.ok && driftCheck.recordsToWrite && repairLocal) {
@@ -18202,7 +18420,7 @@ async function runDoctor(opts, io = consoleIo, readOrigin) {
18202
18420
  }
18203
18421
  checks.push(nestedPluginTreeCheck);
18204
18422
  const cursorCacheRoot = cursorPluginCacheRoot();
18205
- const cursorCacheRootExists = (0, import_node_fs17.existsSync)(cursorCacheRoot);
18423
+ const cursorCacheRootExists = (0, import_node_fs18.existsSync)(cursorCacheRoot);
18206
18424
  let cursorPins = cursorPluginCachePinSnapshots() ?? [];
18207
18425
  checks.push(
18208
18426
  buildCursorPluginCacheCleanupCheck({
@@ -18230,7 +18448,7 @@ async function runDoctor(opts, io = consoleIo, readOrigin) {
18230
18448
  cacheRootExists: cursorCacheRootExists,
18231
18449
  hubCheckout: hubCheckoutForCursorSeed(),
18232
18450
  execFileP: execFileP2,
18233
- mkdtemp: (prefix) => (0, import_promises2.mkdtemp)((0, import_node_path17.join)((0, import_node_os6.tmpdir)(), prefix)),
18451
+ mkdtemp: (prefix) => (0, import_promises2.mkdtemp)((0, import_node_path18.join)((0, import_node_os7.tmpdir)(), prefix)),
18234
18452
  log: (m) => io.err(m)
18235
18453
  });
18236
18454
  if (seeded) {
@@ -18263,6 +18481,11 @@ async function runDoctor(opts, io = consoleIo, readOrigin) {
18263
18481
  }
18264
18482
  }
18265
18483
  checks.push(cursorPluginCheck);
18484
+ if (!opts.banner) {
18485
+ const cursorLogText = readCursorPluginLogText();
18486
+ const enabledPinSignal = cursorLogText ? parseCursorPluginLog(cursorLogText) : {};
18487
+ checks.push(buildCursorEnabledPinCheck({ isOrgRepo, pins: cursorPins, ...enabledPinSignal }));
18488
+ }
18266
18489
  const cursorThirdPartyEnabled = await readCursorThirdPartyExtensibilityEnabled(execFileP2);
18267
18490
  checks.push(
18268
18491
  buildCursorThirdPartyExtensibilityCheck({
@@ -18420,29 +18643,33 @@ function mergeGuardHook(settings) {
18420
18643
  next.hooks = hooks;
18421
18644
  return next;
18422
18645
  }
18423
- var userScopeSettingsPath = (surface = detectSurface(process.env)) => (0, import_node_path17.join)((0, import_node_os6.homedir)(), surface === "codex" ? ".codex" : ".claude", "settings.json");
18646
+ var userScopeSettingsPath = (surface = detectSurface(process.env)) => (0, import_node_path18.join)((0, import_node_os7.homedir)(), surface === "codex" ? ".codex" : ".claude", "settings.json");
18424
18647
  function ensureUserScopeGuardHook(opts = {}) {
18425
18648
  const path2 = opts.settingsPath ?? userScopeSettingsPath();
18426
18649
  try {
18427
18650
  let current = null;
18428
- if ((0, import_node_fs17.existsSync)(path2)) {
18651
+ if ((0, import_node_fs18.existsSync)(path2)) {
18429
18652
  try {
18430
- current = JSON.parse((0, import_node_fs17.readFileSync)(path2, "utf8"));
18653
+ current = JSON.parse((0, import_node_fs18.readFileSync)(path2, "utf8"));
18431
18654
  } catch {
18432
18655
  return "failed";
18433
18656
  }
18434
18657
  }
18435
18658
  if (settingsHasGuardHook(current)) return "already";
18436
18659
  const merged = mergeGuardHook(current);
18437
- (0, import_node_fs17.mkdirSync)((0, import_node_path17.dirname)(path2), { recursive: true });
18438
- if ((0, import_node_fs17.existsSync)(path2)) (0, import_node_fs17.copyFileSync)(path2, `${path2}.bak`);
18439
- (0, import_node_fs17.writeFileSync)(path2, `${JSON.stringify(merged, null, 2)}
18660
+ (0, import_node_fs18.mkdirSync)((0, import_node_path18.dirname)(path2), { recursive: true });
18661
+ if ((0, import_node_fs18.existsSync)(path2)) (0, import_node_fs18.copyFileSync)(path2, `${path2}.bak`);
18662
+ (0, import_node_fs18.writeFileSync)(path2, `${JSON.stringify(merged, null, 2)}
18440
18663
  `, "utf8");
18441
18664
  return "written";
18442
18665
  } catch {
18443
18666
  return "failed";
18444
18667
  }
18445
18668
  }
18669
+ function guardRecoveryCommand(surface) {
18670
+ const token = surfaceToken(surface);
18671
+ return token ? PLUGIN_SURFACE_HEAL[token]?.guardRecovery : void 0;
18672
+ }
18446
18673
  async function runGuard(opts = {}, readOrigin) {
18447
18674
  void opts;
18448
18675
  try {
@@ -18450,7 +18677,7 @@ async function runGuard(opts = {}, readOrigin) {
18450
18677
  const isOrgRepo = readOrigin ? await isOrgRepoRoot(readOrigin) : await isOrgRepoRoot();
18451
18678
  const input = snapshotPluginGuardInput(surface, isOrgRepo);
18452
18679
  const { state } = buildPluginGuardDecision(input);
18453
- const { line, exitCode } = buildGuardSessionStartLine(state);
18680
+ const { line, exitCode } = buildGuardSessionStartLine(state, { recovery: guardRecoveryCommand(surface) });
18454
18681
  if (line) console.error(line);
18455
18682
  process.exitCode = exitCode;
18456
18683
  } catch {
@@ -18473,8 +18700,7 @@ async function runPluginHeal(surface = detectSurface(process.env)) {
18473
18700
  const bin = descriptor.pluginRunner === "codex" ? "codex" : "claude";
18474
18701
  const refSupported = await marketplaceAddRefSupported(bin);
18475
18702
  const recovery = refSupported ? descriptor.recovery : recoveryWithoutRef(descriptor.recovery);
18476
- const note = refSupported ? "" : `
18477
- Note: \`${bin}\` has no \`--ref\` option here; update \`${bin}\` to pin the released \`main\` branch.`;
18703
+ const note = refAbsenceNote(bin, refSupported);
18478
18704
  console.log(` \u2717 Auto-heal failed or was skipped. Run manually:
18479
18705
  ${recovery}${note}`);
18480
18706
  }
@@ -18570,7 +18796,7 @@ async function applyGcPlan(plan, remote) {
18570
18796
  cleanupBranch: (branch, expectedHeadOid) => cleanupPrMergeLocalBranch(branch.branch, {
18571
18797
  beforeWorktrees,
18572
18798
  startingPath: branch.worktreePath,
18573
- pathExists: (p) => (0, import_node_fs18.existsSync)(p),
18799
+ pathExists: (p) => (0, import_node_fs19.existsSync)(p),
18574
18800
  execGit: async (args) => (await execFileP2("git", args, { timeout: GIT_TIMEOUT_MS })).stdout,
18575
18801
  teardownWorktreeStage,
18576
18802
  deferredStore,
@@ -18593,7 +18819,7 @@ async function applyGcPlan(plan, remote) {
18593
18819
  for (const wt of plan.worktreeDirs) {
18594
18820
  try {
18595
18821
  const cleanupTarget = resolveSafeSiblingWorktreeCleanupTarget(wt.path, siblingRoot, {
18596
- realpath: (path2) => (0, import_node_fs18.realpathSync)(path2)
18822
+ realpath: (path2) => (0, import_node_fs19.realpathSync)(path2)
18597
18823
  });
18598
18824
  if (!cleanupTarget.ok) {
18599
18825
  result.failed.push(`${wt.path}: ${cleanupTarget.reason}`);
@@ -18636,13 +18862,13 @@ var program2 = new Command();
18636
18862
  program2.name("mmi-cli").description("MMI Future CLI \u2014 org rules delivery, Jervaise-only continuity. The engine the plugin SessionStart hook drives.").version(resolveClientVersion()).showHelpAfterError("(run `mmi-cli commands` to list every subcommand + its flags, or `mmi-cli commands --json` to ground against it)");
18637
18863
  var rules = program2.command("rules").description("org-managed .gitignore delivery");
18638
18864
  rules.command("gitignore").option("--write", "upsert the managed block into .gitignore (default: check only, non-zero exit on drift)").description("verify (or --write) this repo's org-managed .gitignore block matches the SSOT").action((opts) => {
18639
- const path2 = (0, import_node_path18.join)(process.cwd(), ".gitignore");
18640
- const current = (0, import_node_fs18.existsSync)(path2) ? (0, import_node_fs18.readFileSync)(path2, "utf8") : null;
18865
+ const path2 = (0, import_node_path19.join)(process.cwd(), ".gitignore");
18866
+ const current = (0, import_node_fs19.existsSync)(path2) ? (0, import_node_fs19.readFileSync)(path2, "utf8") : null;
18641
18867
  const plan = planManagedGitignore(current);
18642
18868
  const drift = [...plan.added.map((l) => `+${l}`), ...plan.removed.map((l) => `-${l}`)].join(", ") || "block normalize";
18643
18869
  if (opts.write) {
18644
18870
  if (plan.changed) {
18645
- (0, import_node_fs18.writeFileSync)(path2, plan.content, "utf8");
18871
+ (0, import_node_fs19.writeFileSync)(path2, plan.content, "utf8");
18646
18872
  console.log(`mmi-cli rules gitignore: updated .gitignore (${drift})`);
18647
18873
  } else {
18648
18874
  console.log("mmi-cli rules gitignore: up to date");
@@ -18784,8 +19010,8 @@ function runWorktreeInstall(command, cwd, quiet) {
18784
19010
  const [bin, ...args] = command.split(" ");
18785
19011
  const file = isWin2 ? "cmd.exe" : bin;
18786
19012
  const spawnArgs = isWin2 ? ["/c", bin, ...args] : args;
18787
- return new Promise((resolve6, reject) => {
18788
- const child = (0, import_node_child_process11.spawn)(file, spawnArgs, { cwd, stdio: quiet ? "ignore" : "inherit", windowsHide: true });
19013
+ return new Promise((resolve5, reject) => {
19014
+ const child = (0, import_node_child_process12.spawn)(file, spawnArgs, { cwd, stdio: quiet ? "ignore" : "inherit", windowsHide: true });
18789
19015
  const timer = setTimeout(() => {
18790
19016
  try {
18791
19017
  child.kill();
@@ -18799,7 +19025,7 @@ function runWorktreeInstall(command, cwd, quiet) {
18799
19025
  });
18800
19026
  child.on("exit", (code) => {
18801
19027
  clearTimeout(timer);
18802
- if (code === 0) resolve6();
19028
+ if (code === 0) resolve5();
18803
19029
  else reject(new Error(`${command} exited ${code} in ${cwd}`));
18804
19030
  });
18805
19031
  });
@@ -18807,7 +19033,7 @@ function runWorktreeInstall(command, cwd, quiet) {
18807
19033
  async function primaryCheckoutRoot(worktreeRoot) {
18808
19034
  try {
18809
19035
  const out = (await execFileP2("git", ["-C", worktreeRoot, "rev-parse", "--path-format=absolute", "--git-common-dir"], { timeout: GIT_TIMEOUT_MS })).stdout.trim();
18810
- return out ? (0, import_node_path18.dirname)(out) : void 0;
19036
+ return out ? (0, import_node_path19.dirname)(out) : void 0;
18811
19037
  } catch {
18812
19038
  return void 0;
18813
19039
  }
@@ -18822,26 +19048,26 @@ function makeProvisionDeps(worktreeRoot, quiet, log) {
18822
19048
  function acquireWorktreeSetupLock(worktreeRoot) {
18823
19049
  const lockPath = repoRuntimeStatePath(worktreeRoot, "worktree-setup.lock");
18824
19050
  const take = () => {
18825
- const fd = (0, import_node_fs18.openSync)(lockPath, "wx");
19051
+ const fd = (0, import_node_fs19.openSync)(lockPath, "wx");
18826
19052
  try {
18827
- (0, import_node_fs18.writeSync)(fd, String(Date.now()));
19053
+ (0, import_node_fs19.writeSync)(fd, String(Date.now()));
18828
19054
  } finally {
18829
- (0, import_node_fs18.closeSync)(fd);
19055
+ (0, import_node_fs19.closeSync)(fd);
18830
19056
  }
18831
19057
  return () => {
18832
19058
  try {
18833
- (0, import_node_fs18.rmSync)(lockPath, { force: true });
19059
+ (0, import_node_fs19.rmSync)(lockPath, { force: true });
18834
19060
  } catch {
18835
19061
  }
18836
19062
  };
18837
19063
  };
18838
19064
  try {
18839
- (0, import_node_fs18.mkdirSync)((0, import_node_path18.dirname)(lockPath), { recursive: true });
19065
+ (0, import_node_fs19.mkdirSync)((0, import_node_path19.dirname)(lockPath), { recursive: true });
18840
19066
  return take();
18841
19067
  } catch {
18842
19068
  try {
18843
- if (Date.now() - (0, import_node_fs18.statSync)(lockPath).mtimeMs > WORKTREE_SETUP_LOCK_TTL_MS) {
18844
- (0, import_node_fs18.rmSync)(lockPath, { force: true });
19069
+ if (Date.now() - (0, import_node_fs19.statSync)(lockPath).mtimeMs > WORKTREE_SETUP_LOCK_TTL_MS) {
19070
+ (0, import_node_fs19.rmSync)(lockPath, { force: true });
18845
19071
  return take();
18846
19072
  }
18847
19073
  } catch {
@@ -18980,7 +19206,7 @@ function scheduleRelatedDiscovery(o) {
18980
19206
  try {
18981
19207
  const args = ["issue", "discover-related", "--number", String(o.number), "--title", o.title, "--body", o.body];
18982
19208
  if (o.repo) args.push("--repo", o.repo);
18983
- (0, import_node_child_process11.spawn)(process.execPath, [process.argv[1], ...args], {
19209
+ (0, import_node_child_process12.spawn)(process.execPath, [process.argv[1], ...args], {
18984
19210
  detached: true,
18985
19211
  stdio: "ignore",
18986
19212
  windowsHide: true,
@@ -19036,8 +19262,8 @@ tenant.command("readiness <owner/repo> <stage>").description("alias for tenant s
19036
19262
  console.log(JSON.stringify(result, null, 2));
19037
19263
  if (result.publicProbe?.ok === false) process.exitCode = 1;
19038
19264
  });
19039
- tenant.command("redeploy <owner/repo> <stage>").description("re-dispatch the central tenant-deploy.yml for an already-promoted ref (no re-tag/merge); train-authority gated").option("--ref <ref>", "ref to deploy (defaults to the stage branch rc/main \u2014 the promoted ref)").option("--watch", "block on the dispatched run and report its outcome (gh run watch --exit-status)").option("--json", "machine-readable output").action(async (repo, stage2, o) => {
19040
- if (stage2 !== "rc" && stage2 !== "main") return fail("tenant redeploy: <stage> must be rc or main");
19265
+ tenant.command("redeploy <owner/repo> <stage>").description("re-dispatch the central tenant-deploy.yml for an already-promoted ref (no re-tag/merge); train-authority gated").option("--ref <ref>", "ref to deploy (defaults to the stage branch rc/main, or `development` for dev \u2014 the promoted/staging ref)").option("--watch", "block on the dispatched run and report its outcome (gh run watch --exit-status)").option("--json", "machine-readable output").action(async (repo, stage2, o) => {
19266
+ if (stage2 !== "dev" && stage2 !== "rc" && stage2 !== "main") return fail("tenant redeploy: <stage> must be dev, rc, or main");
19041
19267
  try {
19042
19268
  const result = await runTenantRedeploy(trainApplyDeps(), { repo, stage: stage2, ref: o.ref, watch: o.watch });
19043
19269
  return printLine(o.json ? JSON.stringify(result, null, 2) : renderTenantRedeploy(result));
@@ -19073,8 +19299,8 @@ tenant.command("sweep-rc").description("discover (and optionally retire) running
19073
19299
  async function resolveDnsBounded(host, timeoutMs = 3e3) {
19074
19300
  const { lookup } = await import("node:dns/promises");
19075
19301
  const probe = lookup(host).then(() => true).catch((e) => dnsErrorToResolution(e?.code));
19076
- const timeout = new Promise((resolve6) => {
19077
- setTimeout(() => resolve6(void 0), timeoutMs).unref?.();
19302
+ const timeout = new Promise((resolve5) => {
19303
+ setTimeout(() => resolve5(void 0), timeoutMs).unref?.();
19078
19304
  });
19079
19305
  return Promise.race([probe, timeout]);
19080
19306
  }
@@ -19336,7 +19562,7 @@ project.command("set [owner/repo]").description("upsert project META (idempotent
19336
19562
  const vars = rawValues("--var");
19337
19563
  if (o.secretsFile) {
19338
19564
  try {
19339
- vars.push(`secrets=${(0, import_node_fs18.readFileSync)(o.secretsFile, "utf8")}`);
19565
+ vars.push(`secrets=${(0, import_node_fs19.readFileSync)(o.secretsFile, "utf8")}`);
19340
19566
  } catch (e) {
19341
19567
  return fail(`project set: cannot read --secrets-file ${o.secretsFile}: ${e.message}`);
19342
19568
  }
@@ -19498,7 +19724,7 @@ SSM cred params (under /mmi-future/${slug}/):`);
19498
19724
  ssm.forEach((k) => console.log(` ${k}`));
19499
19725
  console.log("\nProvision/repair the Console client per docs/Guides/oauth-provision.md; store creds with `mmi-cli oauth set-creds`.");
19500
19726
  });
19501
- oauth.command("set-creds").description('store the OAuth client into the canonical {dev,rc,main}/GOOGLE_CLIENT_* SSM keys (pipe the Console "Download JSON" on stdin)').option("--repo <owner/repo>", "target repo (defaults to the current repo)").action(async (o) => {
19727
+ oauth.command("set-creds").description('store the OAuth client into the canonical stageless GOOGLE_CLIENT_ID/SECRET pair (pipe the Console "Download JSON" on stdin)').option("--repo <owner/repo>", "target repo (defaults to the current repo)").action(async (o) => {
19502
19728
  const raw = await readStdin();
19503
19729
  if (!raw.trim()) {
19504
19730
  return fail("oauth set-creds: pipe the Google client JSON on stdin \u2014 e.g.\n mmi-cli oauth set-creds --repo <owner/repo> < client.json");
@@ -19517,10 +19743,10 @@ oauth.command("set-creds").description('store the OAuth client into the canonica
19517
19743
  return;
19518
19744
  }
19519
19745
  }
19520
- console.log(`OAuth client stored in all ${oauthSsmKeys().length} canonical keys. Run \`mmi-cli oauth verify\` to confirm the client is port-agnostic.`);
19746
+ console.log(`OAuth client stored in the ${oauthSsmKeys().length} canonical stageless keys. Run \`mmi-cli oauth verify\` to confirm the client is port-agnostic.`);
19521
19747
  });
19522
19748
  });
19523
- oauth.command("verify").description("probe Google authorize with an arbitrary port (:9123) to confirm the client is port-agnostic").option("--repo <owner/repo>", "target repo (defaults to the current repo)").option("--client-id <id>", "OAuth client_id (else read dev/GOOGLE_CLIENT_ID from SSM)").option("--json", "machine-readable output").action(async (o) => {
19749
+ oauth.command("verify").description("probe Google authorize with an arbitrary port (:9123) to confirm the client is port-agnostic").option("--repo <owner/repo>", "target repo (defaults to the current repo)").option("--client-id <id>", "OAuth client_id (else read the stageless GOOGLE_CLIENT_ID from SSM)").option("--json", "machine-readable output").action(async (o) => {
19524
19750
  const cfg = await loadConfig();
19525
19751
  const slug = (o.repo ? o.repo.split("/").pop() : cfg.project ?? await repoSlug()).toLowerCase();
19526
19752
  const meta = await fetchProjectBySlug(slug, registryClientDeps(cfg));
@@ -19533,11 +19759,11 @@ oauth.command("verify").description("probe Google authorize with an arbitrary po
19533
19759
  let clientId = o.clientId;
19534
19760
  if (!clientId) {
19535
19761
  await withSecrets(async (d) => {
19536
- clientId = await fetchSecretValue(d, "dev/GOOGLE_CLIENT_ID", { repo: o.repo }) ?? void 0;
19762
+ clientId = await fetchSecretValue(d, "GOOGLE_CLIENT_ID", { repo: o.repo }) ?? void 0;
19537
19763
  });
19538
19764
  }
19539
19765
  if (!clientId) {
19540
- return failGraceful("oauth verify: no client_id (pass --client-id, or provision the repo so dev/GOOGLE_CLIENT_ID exists)");
19766
+ return failGraceful("oauth verify: no client_id (pass --client-id, or provision the repo so GOOGLE_CLIENT_ID exists)");
19541
19767
  }
19542
19768
  const redirectUri = probeRedirectUri(oc.callbackPath);
19543
19769
  let body = "";
@@ -19624,18 +19850,20 @@ issue.command("create").description("create an issue (type \u2192 label) and pri
19624
19850
  ...parentLinkFields(parent, parentLinkError)
19625
19851
  }));
19626
19852
  });
19627
- issue.command("view <number>").description("read an issue as structured JSON \u2014 the mmi-cli path for non-board issue reads (#2347)").option("--repo <owner/repo>", "target repo (defaults to the current repo)").option("--json <fields>", "comma-separated gh --json field list (overrides the default field set)").action(async (number, o) => {
19853
+ issue.command("view <number>").description("read an issue as structured JSON \u2014 the mmi-cli path for non-board issue reads (#2347)").option("--repo <owner/repo>", "target repo (defaults to the current repo)").option("--json [fields...]", 'gh --json field list (overrides the default field set). Accepts commas, spaces, or repeated --json flags \u2014 in PowerShell an unquoted comma list is an array literal, so QUOTE it: --json "number,title,body,url"').action(async (number, o) => {
19628
19854
  const n = Number(number);
19629
19855
  if (!Number.isInteger(n) || n <= 0) return fail("issue view: <number> must be a positive integer");
19630
19856
  const repo = await resolveRepo(o.repo);
19631
19857
  if (!repo) return fail("issue view: could not resolve repo (pass --repo <owner/repo>)");
19632
- const fields = o.json && o.json.trim() ? o.json.trim() : "number,title,state,url,labels,author,assignees,milestone,body";
19858
+ const fields = normalizeIssueViewJsonFields(o.json);
19633
19859
  try {
19634
19860
  const data = await ghJson(["issue", "view", String(n), "--repo", repo, "--json", fields]);
19635
19861
  console.log(JSON.stringify(data));
19636
19862
  } catch (e) {
19637
19863
  const err = e;
19638
- return fail(`issue view: ${(err.stderr || err.message || String(e)).trim()}`);
19864
+ const raw = (err.stderr || err.message || String(e)).trim();
19865
+ const argvNote = o.json ? ` (received --json argv: ${JSON.stringify(o.json)}, normalized to "${fields}")` : "";
19866
+ return fail(`issue view: ${raw}${argvNote}`);
19639
19867
  }
19640
19868
  });
19641
19869
  issue.command("discover-related").description("find related issues for an existing issue and post only high-confidence links").requiredOption("--number <number>", "created issue number").requiredOption("--title <title>", "created issue title").requiredOption("--body <body>", "created issue body").option("--repo <owner/repo>", "target repo (defaults to the current repo)").option("--json", "print candidates instead of posting").action(async (o) => {
@@ -19904,9 +20132,9 @@ pr.command("view <number>").description("read a PR as structured JSON (merged st
19904
20132
  }
19905
20133
  });
19906
20134
  async function listCiWorkflowPaths(cwd = process.cwd()) {
19907
- const wfDir = (0, import_node_path18.join)(cwd, ".github", "workflows");
19908
- if (!(0, import_node_fs18.existsSync)(wfDir)) return [];
19909
- return (0, import_node_fs18.readdirSync)(wfDir).filter((name) => /\.(ya?ml)$/i.test(name)).map((name) => `.github/workflows/${name}`);
20135
+ const wfDir = (0, import_node_path19.join)(cwd, ".github", "workflows");
20136
+ if (!(0, import_node_fs19.existsSync)(wfDir)) return [];
20137
+ return (0, import_node_fs19.readdirSync)(wfDir).filter((name) => /\.(ya?ml)$/i.test(name)).map((name) => `.github/workflows/${name}`);
19910
20138
  }
19911
20139
  async function resolveMergeCiPolicyForCheckout(repoOpt) {
19912
20140
  const repo = repoOpt ?? await resolveRepo();
@@ -19925,7 +20153,7 @@ function ciAuditDeps() {
19925
20153
  // Continuous CI delivery (#1550): the gate re-seed renders from the Hub's on-disk seed templates. The
19926
20154
  // reconcile runs IN the Hub checkout, so this is local-file I/O (no network fetch). Path is relative to
19927
20155
  // the repo root (e.g. skills/bootstrap/seeds/gate.template.yml).
19928
- readSeedFile: (path2) => (0, import_node_fs18.existsSync)(path2) ? (0, import_node_fs18.readFileSync)(path2, "utf8") : null
20156
+ readSeedFile: (path2) => (0, import_node_fs19.existsSync)(path2) ? (0, import_node_fs19.readFileSync)(path2, "utf8") : null
19929
20157
  };
19930
20158
  }
19931
20159
  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) => {
@@ -19955,7 +20183,7 @@ pr.command("checks-wait <number>").description("bounded wait for PR checks; skip
19955
20183
  const result = await waitForPrChecks({
19956
20184
  resolvePolicy: () => resolveMergeCiPolicyForCheckout(o.repo),
19957
20185
  pollChecks: () => pollGhPrChecks(number, repoArgs),
19958
- sleep: (ms) => new Promise((resolve6) => setTimeout(resolve6, ms))
20186
+ sleep: (ms) => new Promise((resolve5) => setTimeout(resolve5, ms))
19959
20187
  });
19960
20188
  if (o.json) printLine(JSON.stringify(result));
19961
20189
  else printLine(`pr checks-wait: ${result.status}${result.reason ? ` \u2014 ${result.reason}` : ""}${result.detail ? ` (${result.detail})` : ""}`);
@@ -19982,7 +20210,7 @@ pr.command("land <number>").description("agent merge path (#1440): train probe \
19982
20210
  waitForChecks: (prNumber, repo) => waitForPrChecks({
19983
20211
  resolvePolicy: () => resolveRepoMergeCiPolicy(repo, ciAuditDeps()),
19984
20212
  pollChecks: () => pollGhPrChecks(prNumber, repo ? ["--repo", repo] : []),
19985
- sleep: (ms) => new Promise((resolve6) => setTimeout(resolve6, ms))
20213
+ sleep: (ms) => new Promise((resolve5) => setTimeout(resolve5, ms))
19986
20214
  }),
19987
20215
  // #2425: re-probe used ONLY to decide whether a failed `gh pr merge --auto` is worth retrying — a
19988
20216
  // fresh read of state/mergeable/checks, independent of the merge call's own (possibly stale) error.
@@ -20033,7 +20261,7 @@ pr.command("land <number>").description("agent merge path (#1440): train probe \
20033
20261
  while (Date.now() < deadlineMs) {
20034
20262
  const stateRead = await readGhPrStateWithRetry(async () => (await execFileP2("gh", ["pr", "view", prNumber, ...args, "--json", "state", "--jq", ".state"], { timeout: GC_GH_TIMEOUT_MS2 })).stdout, { retries: 2, delayMs: 1e3 });
20035
20263
  if (stateRead.ok && stateRead.state === "MERGED") return true;
20036
- await new Promise((resolve6) => setTimeout(resolve6, PR_LAND_POLL_MS));
20264
+ await new Promise((resolve5) => setTimeout(resolve5, PR_LAND_POLL_MS));
20037
20265
  }
20038
20266
  return false;
20039
20267
  }
@@ -20077,7 +20305,7 @@ async function remoteBranchExists2(branch, options = {}) {
20077
20305
  }
20078
20306
  var COMPOSE_TIMEOUT_MS = 12e4;
20079
20307
  function spawnDeferredGcSweep() {
20080
- spawnDetachedSelf(["gc", "sweep-deferred", "--quiet"], { spawn: import_node_child_process11.spawn, execPath: process.execPath, scriptPath: process.argv[1] });
20308
+ spawnDetachedSelf(["gc", "sweep-deferred", "--quiet"], { spawn: import_node_child_process12.spawn, execPath: process.execPath, scriptPath: process.argv[1] });
20081
20309
  }
20082
20310
  async function createDeferredWorktreeStore() {
20083
20311
  try {
@@ -20093,7 +20321,7 @@ async function createDeferredWorktreeStore() {
20093
20321
  },
20094
20322
  write: async (entries) => {
20095
20323
  try {
20096
- await (0, import_promises3.mkdir)((0, import_node_path18.dirname)(registryPath), { recursive: true });
20324
+ await (0, import_promises3.mkdir)((0, import_node_path19.dirname)(registryPath), { recursive: true });
20097
20325
  await (0, import_promises3.writeFile)(registryPath, serializeDeferredWorktrees(entries), "utf8");
20098
20326
  } catch {
20099
20327
  }
@@ -20107,13 +20335,13 @@ var realWorktreeDirRemover = {
20107
20335
  probe: (p) => {
20108
20336
  let st;
20109
20337
  try {
20110
- st = (0, import_node_fs18.lstatSync)(p);
20338
+ st = (0, import_node_fs19.lstatSync)(p);
20111
20339
  } catch {
20112
20340
  return null;
20113
20341
  }
20114
20342
  if (st.isSymbolicLink()) return "link";
20115
20343
  try {
20116
- (0, import_node_fs18.readlinkSync)(p);
20344
+ (0, import_node_fs19.readlinkSync)(p);
20117
20345
  return "link";
20118
20346
  } catch {
20119
20347
  }
@@ -20121,7 +20349,7 @@ var realWorktreeDirRemover = {
20121
20349
  },
20122
20350
  readdir: (p) => {
20123
20351
  try {
20124
- return (0, import_node_fs18.readdirSync)(p);
20352
+ return (0, import_node_fs19.readdirSync)(p);
20125
20353
  } catch {
20126
20354
  return [];
20127
20355
  }
@@ -20130,9 +20358,9 @@ var realWorktreeDirRemover = {
20130
20358
  // leaving the target); a file symlink with unlink. rmdir first, fall back to unlink.
20131
20359
  detachLink: (p) => {
20132
20360
  try {
20133
- (0, import_node_fs18.rmdirSync)(p);
20361
+ (0, import_node_fs19.rmdirSync)(p);
20134
20362
  } catch {
20135
- (0, import_node_fs18.unlinkSync)(p);
20363
+ (0, import_node_fs19.unlinkSync)(p);
20136
20364
  }
20137
20365
  },
20138
20366
  removeTree: (p) => (0, import_promises3.rm)(p, { recursive: true, force: true, maxRetries: 5, retryDelay: 200 })
@@ -20147,7 +20375,7 @@ async function resolvePrimaryCheckout(execGit) {
20147
20375
  function worktreeRemoveDeps(execGit) {
20148
20376
  return {
20149
20377
  git: execGit,
20150
- sleep: (ms) => new Promise((resolve6) => setTimeout(resolve6, ms)),
20378
+ sleep: (ms) => new Promise((resolve5) => setTimeout(resolve5, ms)),
20151
20379
  removeWorktreeDir: async (worktreePath) => removeWorktreeTree(worktreePath, await resolvePrimaryCheckout(execGit), realWorktreeDirRemover)
20152
20380
  };
20153
20381
  }
@@ -20162,9 +20390,9 @@ async function worktreeHasStageState(worktreePath) {
20162
20390
  }
20163
20391
  }
20164
20392
  function stageStateFileBelongsToWorktree(statePath, worktreePath) {
20165
- if (!(0, import_node_fs18.existsSync)(statePath)) return false;
20393
+ if (!(0, import_node_fs19.existsSync)(statePath)) return false;
20166
20394
  try {
20167
- const state = JSON.parse((0, import_node_fs18.readFileSync)(statePath, "utf8"));
20395
+ const state = JSON.parse((0, import_node_fs19.readFileSync)(statePath, "utf8"));
20168
20396
  const recordedCwd = typeof state.identity?.cwd === "string" ? state.identity.cwd : typeof state.cwd === "string" ? state.cwd : "";
20169
20397
  return Boolean(recordedCwd && isPathUnderDirectory(recordedCwd, worktreePath));
20170
20398
  } catch {
@@ -20230,7 +20458,7 @@ pr.command("merge <number>").description("merge a PR (squash by default) and cle
20230
20458
  localCleanup = await cleanupPrMergeLocalBranch(headRef, {
20231
20459
  beforeWorktrees,
20232
20460
  startingPath,
20233
- pathExists: (p) => (0, import_node_fs18.existsSync)(p),
20461
+ pathExists: (p) => (0, import_node_fs19.existsSync)(p),
20234
20462
  execGit: async (args) => (await execFileP2("git", args, { timeout: GIT_TIMEOUT_MS })).stdout,
20235
20463
  teardownWorktreeStage,
20236
20464
  deferredStore,
@@ -20451,7 +20679,7 @@ function stageScopedRunOpts(o) {
20451
20679
  };
20452
20680
  }
20453
20681
  function printLine(value) {
20454
- (0, import_node_fs18.writeSync)(1, `${value}
20682
+ (0, import_node_fs19.writeSync)(1, `${value}
20455
20683
  `);
20456
20684
  }
20457
20685
  function stageKeepAlive() {
@@ -20465,8 +20693,8 @@ async function resolveStage() {
20465
20693
  const portRange = portRangeMeta && typeof portRangeMeta.start === "number" && typeof portRangeMeta.end === "number" ? [portRangeMeta.start, portRangeMeta.end] : void 0;
20466
20694
  return decideStage({
20467
20695
  registry: { deployModel: project2?.deployModel, portRange, error: read.ok ? void 0 : read.error },
20468
- hasCompose: (0, import_node_fs18.existsSync)((0, import_node_path18.join)(process.cwd(), "docker-compose.yml")),
20469
- hasEnvExample: (0, import_node_fs18.existsSync)((0, import_node_path18.join)(process.cwd(), ".env.example"))
20696
+ hasCompose: (0, import_node_fs19.existsSync)((0, import_node_path19.join)(process.cwd(), "docker-compose.yml")),
20697
+ hasEnvExample: (0, import_node_fs19.existsSync)((0, import_node_path19.join)(process.cwd(), ".env.example"))
20470
20698
  });
20471
20699
  }
20472
20700
  async function fetchStageVaultEnvMerge() {
@@ -20900,21 +21128,14 @@ ${r.repo}: applied=[${r.applied.join("; ")}] skipped=[${r.skipped.join("; ")}]${
20900
21128
  if (!audit.ok) process.exitCode = 1;
20901
21129
  });
20902
21130
  var wiki = program2.command("wiki").description("release wiki lane \u2014 publish generated pages via a short-lived, repo-scoped minted token");
20903
- wiki.command("publish <pages-dir>").description("mint a 1h repo-scoped contents:write token (master-gated) and publish the generated .md pages to <repo>.wiki.git via scripts/wiki-publish.mjs; the token stays in memory (never printed/committed). Fails loud on the credential gap \u2014 never a silent skip").option("--repo <owner/repo>", "target repo (defaults to the cwd repo)").action(async (pagesDir, o) => {
21131
+ wiki.command("publish <pages-dir>").description("mint a 1h repo-scoped contents:write token (master-gated) and publish the generated .md pages to <repo>.wiki.git; the token stays in memory (never printed/committed). Runs from any org repo checkout \u2014 the publisher ships inside mmi-cli, no repo-local script required. Fails loud on the credential gap \u2014 never a silent skip").option("--repo <owner/repo>", "target repo (defaults to the cwd repo)").action(async (pagesDir, o) => {
20904
21132
  const repo = await resolveRepo(o.repo);
20905
21133
  if (!repo) return fail("wiki publish: could not determine the target repo (pass --repo owner/name)");
20906
21134
  const cfg = await loadConfig();
20907
21135
  const ok = await wikiPublish(
20908
21136
  {
20909
21137
  mint: (r) => mintWikiToken(r, registryClientDeps(cfg)),
20910
- // scripts/wiki-publish.mjs lives at the repo root; Lane B runs there. Resolve against cwd.
20911
- scriptPath: () => (0, import_node_path18.resolve)(process.cwd(), "scripts", "wiki-publish.mjs"),
20912
- scriptExists: (p) => (0, import_node_fs18.existsSync)(p),
20913
- spawn: (command, args, env) => {
20914
- const r = (0, import_node_child_process11.spawnSync)(command, args, { stdio: "inherit", env });
20915
- if (r.error) throw r.error;
20916
- return r.status ?? 1;
20917
- },
21138
+ publish: (o2) => publishWikiViaGit(realWikiGitPublishDeps(), o2),
20918
21139
  log: (msg) => console.log(msg),
20919
21140
  err: (msg) => console.error(msg)
20920
21141
  },
@@ -20941,7 +21162,7 @@ bootstrap.command("verify <repo>").description("audit whether an existing repo i
20941
21162
  client: defaultGitHubClient(),
20942
21163
  projectMeta: meta,
20943
21164
  deployModel: typeof meta?.deployModel === "string" ? meta.deployModel : void 0,
20944
- readLocalFile: (path2) => path2 === "projects.json" && apiProjects != null ? apiProjects : (0, import_node_fs18.existsSync)(path2) ? (0, import_node_fs18.readFileSync)(path2, "utf8") : null,
21165
+ readLocalFile: (path2) => path2 === "projects.json" && apiProjects != null ? apiProjects : (0, import_node_fs19.existsSync)(path2) ? (0, import_node_fs19.readFileSync)(path2, "utf8") : null,
20945
21166
  // requiredGcpApis is stored as an array by a JSON write, but `project set --var KEY=VALUE` stores a raw
20946
21167
  // comma-string — accept either so the seeded value verifies regardless of how it was written.
20947
21168
  requiredGcpApis: (() => {
@@ -20992,12 +21213,12 @@ bootstrap.command("apply <repo>").description("idempotent seed apply from skills
20992
21213
  return fail(`bootstrap apply: ${e.message}`);
20993
21214
  }
20994
21215
  const manifestPath = "skills/bootstrap/seeds/manifest.json";
20995
- if (!(0, import_node_fs18.existsSync)(manifestPath)) return fail(`bootstrap apply: ${manifestPath} not found; run from the MMI-Hub repo root`);
20996
- const manifest = loadBootstrapSeeds((0, import_node_fs18.readFileSync)(manifestPath, "utf8"));
21216
+ if (!(0, import_node_fs19.existsSync)(manifestPath)) return fail(`bootstrap apply: ${manifestPath} not found; run from the MMI-Hub repo root`);
21217
+ const manifest = loadBootstrapSeeds((0, import_node_fs19.readFileSync)(manifestPath, "utf8"));
20997
21218
  const baseBranch = o.class === "content" ? "main" : "development";
20998
21219
  const slug = parsedRepo.slug;
20999
21220
  const gh = async (args) => execFileP2("gh", args, { timeout: 2e4 });
21000
- const readFile2 = (p) => (0, import_node_fs18.existsSync)(p) ? (0, import_node_fs18.readFileSync)(p, "utf8") : null;
21221
+ const readFile2 = (p) => (0, import_node_fs19.existsSync)(p) ? (0, import_node_fs19.readFileSync)(p, "utf8") : null;
21001
21222
  const enc = (p) => p.split("/").map(encodeURIComponent).join("/");
21002
21223
  const rawVars = {};
21003
21224
  for (const value of rawValues("--var")) {
@@ -21294,16 +21515,16 @@ access.command("audit").description("audit collaborator roles + train-branch pus
21294
21515
  const repoClass = o.class;
21295
21516
  targets = [{ repo: o.repo, class: repoClass, releaseTrack: repoClass === "content" ? "trunk" : resolveReleaseTrack(meta, void 0, o.repo) }];
21296
21517
  } else {
21297
- const projectsJson = registryProjects ? JSON.stringify({ projects: registryProjects }) : (0, import_node_fs18.existsSync)("projects.json") ? (0, import_node_fs18.readFileSync)("projects.json", "utf8") : null;
21518
+ const projectsJson = registryProjects ? JSON.stringify({ projects: registryProjects }) : (0, import_node_fs19.existsSync)("projects.json") ? (0, import_node_fs19.readFileSync)("projects.json", "utf8") : null;
21298
21519
  if (!projectsJson) return failGraceful("access audit: no project registry \u2014 Hub API unreachable and projects.json not found; run from the MMI-Hub repo root or pass --repo <owner/repo>");
21299
- const fanoutJson = (0, import_node_fs18.existsSync)(".github/fanout-targets.json") ? (0, import_node_fs18.readFileSync)(".github/fanout-targets.json", "utf8") : null;
21520
+ const fanoutJson = (0, import_node_fs19.existsSync)(".github/fanout-targets.json") ? (0, import_node_fs19.readFileSync)(".github/fanout-targets.json", "utf8") : null;
21300
21521
  targets = loadAccessTargets(projectsJson, fanoutJson);
21301
21522
  }
21302
21523
  const derivedMatrix = registryProjects ? accessMatrixFromProjects(registryProjects) : {};
21303
- const fileMatrix = (0, import_node_fs18.existsSync)("access-matrix.json") ? loadAccessMatrix((0, import_node_fs18.readFileSync)("access-matrix.json", "utf8")) : {};
21524
+ const fileMatrix = (0, import_node_fs19.existsSync)("access-matrix.json") ? loadAccessMatrix((0, import_node_fs19.readFileSync)("access-matrix.json", "utf8")) : {};
21304
21525
  const matrix = mergeAccessMatrix(fileMatrix, derivedMatrix);
21305
21526
  const derivedContracts = registryProjects ? dataAccessContractsFromProjects(registryProjects) : { consumers: {} };
21306
- const fileContracts = (0, import_node_fs18.existsSync)("data-access-contracts.json") ? loadDataAccessContracts((0, import_node_fs18.readFileSync)("data-access-contracts.json", "utf8")) : { consumers: {} };
21527
+ const fileContracts = (0, import_node_fs19.existsSync)("data-access-contracts.json") ? loadDataAccessContracts((0, import_node_fs19.readFileSync)("data-access-contracts.json", "utf8")) : { consumers: {} };
21307
21528
  const dataAccess = mergeDataAccessContracts(fileContracts, derivedContracts);
21308
21529
  const report = await auditOrgAccess(targets, deps, matrix, dataAccess);
21309
21530
  console.log(o.json ? JSON.stringify(report, null, 2) : renderAccessReport(report));
@@ -21340,7 +21561,7 @@ program2.command("session-start").description("run the SessionStart verbs (whoam
21340
21561
  readBoard,
21341
21562
  // #1813: warm the slice cache out-of-band (detached) so the ~20s live read
21342
21563
  // never costs banner time and next session's glance renders instantly within budget.
21343
- scheduleRefresh: () => spawnDetachedSelf(["board", "slice-refresh", "--quiet"], { spawn: import_node_child_process11.spawn, execPath: process.execPath, scriptPath: process.argv[1] })
21564
+ scheduleRefresh: () => spawnDetachedSelf(["board", "slice-refresh", "--quiet"], { spawn: import_node_child_process12.spawn, execPath: process.execPath, scriptPath: process.argv[1] })
21344
21565
  }),
21345
21566
  doctor: (io) => runDoctor({ banner: true }, io)
21346
21567
  });
@@ -21348,7 +21569,7 @@ program2.command("session-start").description("run the SessionStart verbs (whoam
21348
21569
  for (const line of scratchGcLines(process.cwd())) consoleIo.log(line);
21349
21570
  const worktreeBanner = worktreeAutoProvisionBanner(process.cwd());
21350
21571
  if (worktreeBanner) {
21351
- spawnDetachedSelf(["worktree", "setup", "--quiet"], { spawn: import_node_child_process11.spawn, execPath: process.execPath, scriptPath: process.argv[1] });
21572
+ spawnDetachedSelf(["worktree", "setup", "--quiet"], { spawn: import_node_child_process12.spawn, execPath: process.execPath, scriptPath: process.argv[1] });
21352
21573
  consoleIo.log(worktreeBanner);
21353
21574
  }
21354
21575
  });