@mutmutco/cli 3.2.0 → 3.4.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 +835 -453
  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();
@@ -9219,9 +9225,23 @@ function parseNpmVersion(stdout) {
9219
9225
  function staleTrainCliMessage(report, commandName) {
9220
9226
  return `running mmi-cli ${report.currentVersion} is stale against released ${report.releasedVersion}; this is recoverable \u2014 run \`mmi-cli doctor --apply --no-repo-writes\` to update to ${report.releasedVersion}, then rerun ${commandName} so it uses the current train path`;
9221
9227
  }
9222
- function versionAutoUpdateAction(report, hasPluginRoot) {
9228
+ async function resolveReleasedVersion(runners) {
9229
+ try {
9230
+ const npmVersion = parseNpmVersion(await runners.npm());
9231
+ if (npmVersion) return { version: npmVersion, source: "npm" };
9232
+ } catch {
9233
+ }
9234
+ try {
9235
+ const ghVersion = parseManifestVersion(await runners.gh());
9236
+ if (ghVersion) return { version: ghVersion, source: "gh" };
9237
+ } catch {
9238
+ }
9239
+ return { version: void 0, source: "unavailable" };
9240
+ }
9241
+ function versionAutoUpdateAction(report, hasPluginRoot, releasedSource) {
9223
9242
  if (report.ok || report.staleAgainst !== "released") return "none";
9224
- return hasPluginRoot ? "plugin-pull" : "npm";
9243
+ if (hasPluginRoot) return "plugin-pull";
9244
+ return releasedSource === "gh" ? "npm-unreachable" : "npm";
9225
9245
  }
9226
9246
 
9227
9247
  // src/issue-related.ts
@@ -9493,7 +9513,7 @@ function bootstrapPlan(repo, repoClass) {
9493
9513
  { label: `apply branch protection / allowlist: ${protectedBranches}`, gated: true },
9494
9514
  { label: "attach GitHub Project v2 and register Hub registry META", gated: true },
9495
9515
  { label: "seed README.md and architecture.md", gated: true },
9496
- { label: "commit .cursor/rules/<repo>.mdc and .cursor/environment.json", gated: true },
9516
+ { label: "commit .cursor/rules/<repo>.mdc", gated: true },
9497
9517
  { label: `register fanout target on ${repoClass === "content" ? "main" : "development"}`, gated: true }
9498
9518
  ];
9499
9519
  }
@@ -9787,7 +9807,7 @@ async function runMergeTreePreflight(deps, ours, theirs) {
9787
9807
  async function predictMergeConflicts(deps, ours, theirs) {
9788
9808
  return runMergeTreePreflight(deps, ours, theirs);
9789
9809
  }
9790
- async function mergeWithToleratedResolution(deps, sourceRef, label, resolve6, extraTolerated = []) {
9810
+ async function mergeWithToleratedResolution(deps, sourceRef, label, resolve5, extraTolerated = []) {
9791
9811
  try {
9792
9812
  await deps.run("git", ["merge", sourceRef, "--no-edit"]);
9793
9813
  return;
@@ -9801,7 +9821,7 @@ async function mergeWithToleratedResolution(deps, sourceRef, label, resolve6, ex
9801
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)`
9802
9822
  );
9803
9823
  }
9804
- await deps.run("git", ["checkout", `--${resolve6}`, "--", ...unmerged]);
9824
+ await deps.run("git", ["checkout", `--${resolve5}`, "--", ...unmerged]);
9805
9825
  await deps.run("git", ["add", "--", ...unmerged]);
9806
9826
  await deps.run("git", ["commit", "--no-edit"]);
9807
9827
  }
@@ -9967,7 +9987,7 @@ function requireProjectMetaForTrain(load, repo) {
9967
9987
  var CORRELATE_ATTEMPTS = 5;
9968
9988
  var CORRELATE_DELAY_MS = 1500;
9969
9989
  var CORRELATE_SKEW_SLACK_MS = 1e4;
9970
- var defaultSleep2 = (ms) => new Promise((resolve6) => setTimeout(resolve6, ms));
9990
+ var defaultSleep2 = (ms) => new Promise((resolve5) => setTimeout(resolve5, ms));
9971
9991
  function resolveSleep(deps) {
9972
9992
  return deps.sleep ?? defaultSleep2;
9973
9993
  }
@@ -11133,7 +11153,7 @@ function clean2(out) {
11133
11153
  return out.trim();
11134
11154
  }
11135
11155
  function sleeper(deps) {
11136
- return deps.sleep ?? ((ms) => new Promise((resolve6) => setTimeout(resolve6, ms)));
11156
+ return deps.sleep ?? ((ms) => new Promise((resolve5) => setTimeout(resolve5, ms)));
11137
11157
  }
11138
11158
  function normalizeHotfixVersion(input) {
11139
11159
  const m = /^v?(\d+\.\d+\.\d+)$/.exec(input.trim());
@@ -11568,12 +11588,46 @@ async function findInFlightHotfixVersion(deps, ctx, latestMainTag) {
11568
11588
  return { inFlight: null, superseded };
11569
11589
  }
11570
11590
 
11571
- // src/release-announce.ts
11572
- var ANNOUNCE_REPO = "mutmutco/MMI-Hub";
11591
+ // src/slack-alert.ts
11573
11592
  var SSM_REGION = "eu-central-1";
11574
11593
  var SSM_TOKEN_PARAM = "/mmi-future/_org/slack/SLACK_BOT_TOKEN";
11575
11594
  var SSM_CHANNEL_PARAM = "/mmi-future/_org/slack/SLACK_ALERTS_CHANNEL";
11576
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";
11577
11631
  var MAX_BULLETS = 6;
11578
11632
  var MAX_SUMMARY_LINES = 8;
11579
11633
  function neutralizeHubProductBrands(text) {
@@ -11648,24 +11702,6 @@ function formatAnnouncement(args) {
11648
11702
  function summaryFileLines(content) {
11649
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}`);
11650
11704
  }
11651
- async function ssmParameter(deps, name, decrypt) {
11652
- const args = [
11653
- "ssm",
11654
- "get-parameter",
11655
- "--region",
11656
- SSM_REGION,
11657
- "--name",
11658
- name,
11659
- "--query",
11660
- "Parameter.Value",
11661
- "--output",
11662
- "text",
11663
- ...decrypt ? ["--with-decryption"] : []
11664
- ];
11665
- const value = (await deps.run("aws", args)).trim();
11666
- if (!value || value === "None") throw new Error(`SSM parameter ${name} is empty`);
11667
- return value;
11668
- }
11669
11705
  async function announceRelease(deps, args) {
11670
11706
  if (args.repo !== ANNOUNCE_REPO) {
11671
11707
  return { status: "skipped", note: `announce is Hub-only \u2014 ${args.repo} skipped` };
@@ -11687,18 +11723,8 @@ async function announceRelease(deps, args) {
11687
11723
  }
11688
11724
  if (lines.length === 0) lines = curateReleaseNotes(view.body ?? "");
11689
11725
  if (lines.length === 0) lines = ["\u2022 maintenance release \u2014 see the notes for details"];
11690
- const token = await ssmParameter(deps, SSM_TOKEN_PARAM, true);
11691
- const channel = await ssmParameter(deps, SSM_CHANNEL_PARAM, false);
11692
11726
  const text = formatAnnouncement({ repo: args.repo, tag: args.tag, lines, releaseUrl });
11693
- const fetchImpl = deps.fetchImpl ?? fetch;
11694
- const res = await fetchImpl("https://slack.com/api/chat.postMessage", {
11695
- method: "POST",
11696
- headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json; charset=utf-8" },
11697
- body: JSON.stringify({ channel, text, unfurl_links: false }),
11698
- signal: AbortSignal.timeout(SLACK_TIMEOUT_MS)
11699
- });
11700
- const json = await res.json().catch(() => ({}));
11701
- if (!json.ok) throw new Error(`slack postMessage failed: ${json.error ?? `http ${res.status}`}`);
11727
+ await postToAlertsChannel(deps, text);
11702
11728
  return { status: "announced", note: `announced ${args.tag} to the alerts channel` };
11703
11729
  } catch (e) {
11704
11730
  return { status: "failed", note: `announce failed (release unaffected): ${e.message}` };
@@ -12255,7 +12281,6 @@ async function verifyBootstrap(repo, repoClass, deps, releaseTrack) {
12255
12281
  });
12256
12282
  }
12257
12283
  }
12258
- checks.push({ ok: await contentExists2(deps, repo, baseBranch, ".cursor/environment.json"), label: "Cursor environment committed" });
12259
12284
  const readme = await contentText(deps, repo, baseBranch, "README.md");
12260
12285
  checks.push({
12261
12286
  ok: readme !== null && readme.includes("## Agent context"),
@@ -12508,6 +12533,19 @@ async function fetchTrainAuthority(repo, deps) {
12508
12533
  return { ok: false, error: e.message };
12509
12534
  }
12510
12535
  }
12536
+ async function mintWikiToken(repo, deps) {
12537
+ const res = await postJson("/wiki-mint", { repo }, deps, "POST", { noRetry: true });
12538
+ if (res.error) return { ok: false, error: res.error };
12539
+ const body = res.body ?? {};
12540
+ if (res.status === 426) return { ok: false, error: upgradeRequiredError({ status: 426 }, res.body) };
12541
+ if (res.status === 403) return { ok: false, error: `master-admin only (HTTP 403)${body.error ? ` \u2014 ${body.error}` : ""}` };
12542
+ if (res.status === 404) {
12543
+ return { ok: false, error: "the Hub API did not recognize /wiki-mint (HTTP 404) \u2014 the deployed Hub predates this command; it answers once a Hub release carrying it is deployed" };
12544
+ }
12545
+ if (!res.ok) return { ok: false, error: `wiki-mint HTTP ${res.status}${body.error ? ` \u2014 ${body.error}` : ""}` };
12546
+ if (!body.token || !body.expiresAt) return { ok: false, error: "malformed wiki-mint response (no token)" };
12547
+ return { ok: true, mint: { token: body.token, expiresAt: body.expiresAt, repository: body.repository ?? repo, permissions: body.permissions ?? {} } };
12548
+ }
12511
12549
  async function fetchProjectsList(deps) {
12512
12550
  if (!deps.baseUrl) return null;
12513
12551
  const token = await deps.token();
@@ -12645,6 +12683,118 @@ async function tenantDeploy(payload, deps) {
12645
12683
  return postJson("/tenant-deploy", payload, deps, "POST", { noRetry: true, timeoutMs: TENANT_DEPLOY_TIMEOUT_MS });
12646
12684
  }
12647
12685
 
12686
+ // src/wiki-publish-command.ts
12687
+ async function wikiPublish(deps, opts) {
12688
+ if (!opts.pagesDir) {
12689
+ deps.err("wiki publish: <pages-dir> is required");
12690
+ return false;
12691
+ }
12692
+ const minted = await deps.mint(opts.repo);
12693
+ if (!minted.ok) {
12694
+ deps.err(`wiki publish failed: could not mint a scoped wiki token for ${opts.repo}: ${minted.error}`);
12695
+ return false;
12696
+ }
12697
+ const ok = deps.publish({ repo: opts.repo, token: minted.mint.token, pagesDir: opts.pagesDir });
12698
+ if (!ok) {
12699
+ return false;
12700
+ }
12701
+ deps.log(`wiki published for ${opts.repo} (scoped token, expired/discarded)`);
12702
+ return true;
12703
+ }
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
+
12648
12798
  // src/project-readiness.ts
12649
12799
  function stagesForTrack(meta) {
12650
12800
  return branchesForTrack(resolveReleaseTrack(meta)).map((b) => b === "development" ? "dev" : b);
@@ -13758,7 +13908,7 @@ function writeError(res) {
13758
13908
  }
13759
13909
 
13760
13910
  // src/secrets.ts
13761
- var import_node_child_process8 = require("node:child_process");
13911
+ var import_node_child_process9 = require("node:child_process");
13762
13912
  var OWNER2 = "mutmutco";
13763
13913
  var SSM_ROOT = "/mmi-future";
13764
13914
  var PROJECT_TIER_SEGMENT = "dev";
@@ -13788,7 +13938,9 @@ function vaultPointer(slug) {
13788
13938
  slug,
13789
13939
  root,
13790
13940
  tiers: {
13791
- project: `${root}/{dev,rc,main}/* (project-admin self-serve for this repo)`,
13941
+ // #2482: a stageless catalog entry (stages: []) lives at the slug ROOT — one value per repo
13942
+ // alongside the staged tree for per-stage entries. Both are the same project-admin wall (#2032).
13943
+ project: `${root}/<KEY> (stageless, one value per repo) + ${root}/{dev,rc,main}/* (staged entries) (project-admin self-serve for this repo)`,
13792
13944
  org: [`/mmi-future/{shared,cloudflare,mmi-hub,...}/* (org-infra, master-gated)`]
13793
13945
  },
13794
13946
  stages: ["dev", "rc", "main"],
@@ -14487,7 +14639,7 @@ async function secretsCopy(deps, opts) {
14487
14639
  return true;
14488
14640
  }
14489
14641
  function defaultSpawn(command, args, env) {
14490
- const r = (0, import_node_child_process8.spawnSync)(command, args, { stdio: "inherit", env });
14642
+ const r = (0, import_node_child_process9.spawnSync)(command, args, { stdio: "inherit", env });
14491
14643
  if (r.error) throw r.error;
14492
14644
  return r.status ?? 1;
14493
14645
  }
@@ -14547,8 +14699,8 @@ async function secretsUse(deps, key, opts) {
14547
14699
  }
14548
14700
 
14549
14701
  // src/secrets-commands.ts
14550
- var import_node_fs13 = require("node:fs");
14551
- var import_node_path14 = require("node:path");
14702
+ var import_node_fs14 = require("node:fs");
14703
+ var import_node_path15 = require("node:path");
14552
14704
  var RAILS_CREDENTIALS_DECRYPT_TIMEOUT_MS = 3e4;
14553
14705
  var DEFAULT_RAILS_CREDENTIALS_FILE = "config/credentials.yml.enc";
14554
14706
  var DEFAULT_RAILS_MASTER_KEY_FILE = "config/master.key";
@@ -14556,18 +14708,18 @@ function collectMap(value, previous = []) {
14556
14708
  return [...previous, value];
14557
14709
  }
14558
14710
  async function decryptRailsCredentials(input) {
14559
- const appDir = (0, import_node_path14.resolve)(input.appDir ?? process.cwd());
14711
+ const appDir = (0, import_node_path15.resolve)(input.appDir ?? process.cwd());
14560
14712
  const credentialsFile = input.credentialsFile ?? DEFAULT_RAILS_CREDENTIALS_FILE;
14561
14713
  const masterKeyFile = input.masterKeyFile ?? DEFAULT_RAILS_MASTER_KEY_FILE;
14562
- const credentialsPath = (0, import_node_path14.resolve)(appDir, credentialsFile);
14563
- const masterKeyPath = (0, import_node_path14.resolve)(appDir, masterKeyFile);
14714
+ const credentialsPath = (0, import_node_path15.resolve)(appDir, credentialsFile);
14715
+ const masterKeyPath = (0, import_node_path15.resolve)(appDir, masterKeyFile);
14564
14716
  const env = {
14565
14717
  ...process.env,
14566
14718
  MMI_RAILS_CREDENTIALS_FILE: credentialsPath,
14567
14719
  MMI_RAILS_MASTER_KEY_FILE: masterKeyPath
14568
14720
  };
14569
- if ((0, import_node_fs13.existsSync)(masterKeyPath)) {
14570
- env.RAILS_MASTER_KEY = (0, import_node_fs13.readFileSync)(masterKeyPath, "utf8").trim();
14721
+ if ((0, import_node_fs14.existsSync)(masterKeyPath)) {
14722
+ env.RAILS_MASTER_KEY = (0, import_node_fs14.readFileSync)(masterKeyPath, "utf8").trim();
14571
14723
  }
14572
14724
  const script = [
14573
14725
  'require "json"',
@@ -14633,7 +14785,7 @@ function registerSecretsCommands(program3) {
14633
14785
  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) => {
14634
14786
  let body;
14635
14787
  try {
14636
- body = (0, import_node_fs13.readFileSync)((0, import_node_path14.resolve)(o.file), "utf8");
14788
+ body = (0, import_node_fs14.readFileSync)((0, import_node_path15.resolve)(o.file), "utf8");
14637
14789
  } catch (e) {
14638
14790
  return fail(`secrets org-catalog: cannot read --file ${o.file}: ${e.message}`);
14639
14791
  }
@@ -14708,7 +14860,7 @@ function registerSecretsCommands(program3) {
14708
14860
  {
14709
14861
  ...d,
14710
14862
  decryptRailsCredentials,
14711
- removeFile: (path2) => (0, import_node_fs13.unlinkSync)((0, import_node_path14.resolve)(o.appDir ?? process.cwd(), path2))
14863
+ removeFile: (path2) => (0, import_node_fs14.unlinkSync)((0, import_node_path15.resolve)(o.appDir ?? process.cwd(), path2))
14712
14864
  },
14713
14865
  {
14714
14866
  repo: o.repo,
@@ -14797,11 +14949,11 @@ function registerEdgeCommands(program3) {
14797
14949
  }
14798
14950
 
14799
14951
  // src/doctor-run.ts
14800
- var import_node_fs17 = require("node:fs");
14801
- var import_node_child_process10 = require("node:child_process");
14952
+ var import_node_fs18 = require("node:fs");
14953
+ var import_node_child_process11 = require("node:child_process");
14802
14954
  var import_promises2 = require("node:fs/promises");
14803
- var import_node_path17 = require("node:path");
14804
- var import_node_os6 = require("node:os");
14955
+ var import_node_path18 = require("node:path");
14956
+ var import_node_os7 = require("node:os");
14805
14957
 
14806
14958
  // src/plugin-guard.ts
14807
14959
  function buildPluginGuardDecision(i) {
@@ -14813,140 +14965,20 @@ function buildPluginGuardDecision(i) {
14813
14965
  function buildGuardSessionStartLine(state, opts = {}) {
14814
14966
  if (state === "healthy" || state === "not-org") return { exitCode: 0 };
14815
14967
  const recovery = opts.recovery ?? "mmi-cli plugin-heal";
14968
+ const restartHint = opts.restartHint ?? "restart your agent host / reload plugins";
14816
14969
  const reason = state === "no-install" ? "MMI plugin is not installed for this user/session" : "MMI plugin is installed but its marketplace/cache is unresolved";
14817
14970
  return {
14818
- line: `[mmi-guard] ${reason}; run ${recovery} and restart Claude Code / reload plugins.`,
14971
+ line: `[mmi-guard] ${reason}; run ${recovery} and ${restartHint}.`,
14819
14972
  exitCode: 1
14820
14973
  };
14821
14974
  }
14822
14975
 
14823
14976
  // src/cursor-plugin-seed.ts
14824
- var import_node_child_process9 = require("node:child_process");
14825
- var import_node_fs14 = require("node:fs");
14826
- var import_node_os5 = require("node:os");
14827
- var import_node_path15 = require("node:path");
14977
+ var import_node_child_process10 = require("node:child_process");
14978
+ var import_node_fs15 = require("node:fs");
14979
+ var import_node_os6 = require("node:os");
14980
+ var import_node_path16 = require("node:path");
14828
14981
  var import_node_util7 = require("node:util");
14829
- function isSemverVersion(v) {
14830
- return typeof v === "string" && /^v?\d+\.\d+\.\d+/.test(v.trim());
14831
- }
14832
- var MMI_HUB_REPO = "mutmutco/MMI-Hub";
14833
- var CURSOR_THIRD_PARTY_STATE_KEY = "cursor/thirdPartyExtensibilityEnabled";
14834
- var PLUGIN_JSON_REL = ".cursor-plugin/plugin.json";
14835
- var execFileBuffer = (0, import_node_util7.promisify)(import_node_child_process9.execFile);
14836
- function gitFetchReleaseTagArgs(hubCheckout, tag) {
14837
- return ["-C", hubCheckout, "fetch", "origin", "tag", tag, "--quiet"];
14838
- }
14839
- function ghReleaseTarballApiArgs(tag) {
14840
- return ["api", `repos/${MMI_HUB_REPO}/tarball/refs/tags/${tag}`];
14841
- }
14842
- function cursorUserGlobalStatePath() {
14843
- if (process.platform === "win32") {
14844
- const base = process.env.APPDATA || (0, import_node_path15.join)((0, import_node_os5.homedir)(), "AppData", "Roaming");
14845
- return (0, import_node_path15.join)(base, "Cursor", "User", "globalStorage", "state.vscdb");
14846
- }
14847
- if (process.platform === "darwin") {
14848
- return (0, import_node_path15.join)((0, import_node_os5.homedir)(), "Library", "Application Support", "Cursor", "User", "globalStorage", "state.vscdb");
14849
- }
14850
- return (0, import_node_path15.join)((0, import_node_os5.homedir)(), ".config", "Cursor", "User", "globalStorage", "state.vscdb");
14851
- }
14852
- async function readCursorThirdPartyExtensibilityEnabled(execFileP5) {
14853
- const dbPath = cursorUserGlobalStatePath();
14854
- if (!(0, import_node_fs14.existsSync)(dbPath)) return void 0;
14855
- try {
14856
- const { stdout } = await execFileP5("sqlite3", [dbPath, `SELECT value FROM ItemTable WHERE key = '${CURSOR_THIRD_PARTY_STATE_KEY}';`], {
14857
- timeout: 5e3
14858
- });
14859
- const value = stdout.trim().toLowerCase();
14860
- if (value === "true") return true;
14861
- if (value === "false") return false;
14862
- return void 0;
14863
- } catch {
14864
- return void 0;
14865
- }
14866
- }
14867
- function syncDirContents(src, dest) {
14868
- (0, import_node_fs14.mkdirSync)(dest, { recursive: true });
14869
- for (const name of (0, import_node_fs14.readdirSync)(dest)) {
14870
- (0, import_node_fs14.rmSync)((0, import_node_path15.join)(dest, name), { recursive: true, force: true });
14871
- }
14872
- (0, import_node_fs14.cpSync)(src, dest, { recursive: true });
14873
- }
14874
- function releaseTag(releasedVersion) {
14875
- return releasedVersion.startsWith("v") ? releasedVersion : `v${releasedVersion}`;
14876
- }
14877
- async function extractPluginMmiFromHubCheckout(hubCheckout, tag, tmpRoot, execFileP5) {
14878
- const tarFile = (0, import_node_path15.join)(tmpRoot, "archive.tar");
14879
- try {
14880
- await execFileP5("git", gitFetchReleaseTagArgs(hubCheckout, tag), { timeout: 6e4 });
14881
- await execFileP5("git", ["-C", hubCheckout, "archive", "--format=tar", `--output=${tarFile}`, tag, "plugins/mmi"], {
14882
- timeout: 6e4
14883
- });
14884
- await execFileP5("tar", ["-xf", tarFile, "-C", tmpRoot], { timeout: 6e4 });
14885
- const pluginMmi = (0, import_node_path15.join)(tmpRoot, "plugins", "mmi");
14886
- return (0, import_node_fs14.existsSync)((0, import_node_path15.join)(pluginMmi, PLUGIN_JSON_REL)) ? pluginMmi : void 0;
14887
- } catch {
14888
- return void 0;
14889
- }
14890
- }
14891
- async function downloadPluginMmiViaGh(tag, tmpRoot) {
14892
- const tarPath = (0, import_node_path15.join)(tmpRoot, "repo.tgz");
14893
- try {
14894
- (0, import_node_fs14.mkdirSync)(tmpRoot, { recursive: true });
14895
- const { stdout } = await execFileBuffer("gh", ghReleaseTarballApiArgs(tag), {
14896
- timeout: 12e4,
14897
- maxBuffer: 100 * 1024 * 1024,
14898
- encoding: "buffer",
14899
- windowsHide: true
14900
- });
14901
- (0, import_node_fs14.writeFileSync)(tarPath, stdout);
14902
- await execFileBuffer("tar", ["-xzf", tarPath, "-C", tmpRoot], { timeout: 12e4, windowsHide: true });
14903
- const top = (0, import_node_fs14.readdirSync)(tmpRoot).find((entry) => entry !== "repo.tgz");
14904
- if (!top) return void 0;
14905
- const pluginMmi = (0, import_node_path15.join)(tmpRoot, top, "plugins", "mmi");
14906
- return (0, import_node_fs14.existsSync)((0, import_node_path15.join)(pluginMmi, PLUGIN_JSON_REL)) ? pluginMmi : void 0;
14907
- } catch {
14908
- return void 0;
14909
- }
14910
- }
14911
- async function resolvePluginMmiSource(releasedVersion, hubCheckout, tmpRoot, execFileP5) {
14912
- (0, import_node_fs14.mkdirSync)(tmpRoot, { recursive: true });
14913
- const tag = releaseTag(releasedVersion);
14914
- if (hubCheckout) {
14915
- const fromHub = await extractPluginMmiFromHubCheckout(hubCheckout, tag, tmpRoot, execFileP5);
14916
- if (fromHub) return fromHub;
14917
- }
14918
- return downloadPluginMmiViaGh(tag, (0, import_node_path15.join)(tmpRoot, "gh"));
14919
- }
14920
- function cursorPluginPinsNeedingSeed(pins, releasedVersion) {
14921
- if (!isSemverVersion(releasedVersion)) return pins.filter((pin) => !pin.hasPluginJson || !pin.hasHooksJson || pin.isEmpty);
14922
- return pins.filter((pin) => {
14923
- if (!pin.hasPluginJson || !pin.hasHooksJson || pin.isEmpty) return true;
14924
- if (isSemverVersion(pin.version) && compareVersions(pin.version, releasedVersion) < 0) return true;
14925
- return false;
14926
- });
14927
- }
14928
- function cursorPluginCacheSeedTargets(pins, releasedVersion, cacheRoot, cacheRootExists = false) {
14929
- const needing = cursorPluginPinsNeedingSeed(pins, releasedVersion);
14930
- if (needing.length > 0) return needing.map((pin) => pin.path);
14931
- if (pins.length === 0 && cacheRoot && cacheRootExists && isSemverVersion(releasedVersion)) {
14932
- return [(0, import_node_path15.join)(cacheRoot, `v${releasedVersion.replace(/^v/, "")}`)];
14933
- }
14934
- return [];
14935
- }
14936
- async function applyCursorPluginCacheSeed(input) {
14937
- if (!isSemverVersion(input.releasedVersion)) return false;
14938
- const targets = cursorPluginCacheSeedTargets(input.pins, input.releasedVersion, input.cacheRoot, input.cacheRootExists);
14939
- if (targets.length === 0) return false;
14940
- const tmpRoot = await input.mkdtemp("mmi-cursor-seed-");
14941
- const source = await resolvePluginMmiSource(input.releasedVersion, input.hubCheckout, tmpRoot, input.execFileP);
14942
- if (!source) return false;
14943
- input.log(` \u21BB seeding Cursor MMI plugin cache \u2192 ${input.releasedVersion}\u2026`);
14944
- for (const dest of targets) {
14945
- syncDirContents(source, dest);
14946
- }
14947
- (0, import_node_fs14.rmSync)(tmpRoot, { recursive: true, force: true });
14948
- return true;
14949
- }
14950
14982
 
14951
14983
  // src/doctor.ts
14952
14984
  var GH_PROJECT_LOGIN_FIX = 'run: gh auth login --hostname github.com --git-protocol https --web --scopes "project"';
@@ -15230,8 +15262,8 @@ function buildCursorPluginCacheCleanupCheck(input) {
15230
15262
  label: CURSOR_PLUGIN_CACHE_CLEANUP_LABEL,
15231
15263
  fix: CURSOR_PLUGIN_CACHE_CLEANUP_FIX
15232
15264
  };
15233
- if (!input.isOrgRepo || !isSemverVersion2(input.releasedVersion)) return base;
15234
- const semverPins = input.pins.filter((pin) => isSemverVersion2(pin.version));
15265
+ if (!input.isOrgRepo || !isSemverVersion(input.releasedVersion)) return base;
15266
+ const semverPins = input.pins.filter((pin) => isSemverVersion(pin.version));
15235
15267
  if (semverPins.length === 0) return base;
15236
15268
  const activeVersion = semverPins.map((pin) => pin.version).reduce((a, b) => compareVersions(a, b) >= 0 ? a : b);
15237
15269
  const protectedVersions = new Set(
@@ -15281,7 +15313,7 @@ function codexEnabledPluginVersionFromList(jsonText, pluginId = MMI_PLUGIN_ID) {
15281
15313
  const parsed = JSON.parse(jsonText);
15282
15314
  const rows = Array.isArray(parsed?.installed) ? parsed.installed : [];
15283
15315
  const row = rows.find((r) => r?.pluginId === pluginId && r.installed === true && r.enabled === true);
15284
- return isSemverVersion2(row?.version) ? row.version.trim().replace(/^v/, "") : void 0;
15316
+ return isSemverVersion(row?.version) ? row.version.trim().replace(/^v/, "") : void 0;
15285
15317
  } catch {
15286
15318
  return void 0;
15287
15319
  }
@@ -15292,8 +15324,9 @@ function buildCodexActiveCacheCheck(input) {
15292
15324
  label: CODEX_ACTIVE_CACHE_LABEL,
15293
15325
  fix: CODEX_PLUGIN_RECOVERY
15294
15326
  };
15295
- if (!input.isOrgRepo || !isSemverVersion2(input.releasedVersion)) return base;
15296
- if (isSemverVersion2(input.codexActiveVersion)) {
15327
+ if (!input.isOrgRepo || !isSemverVersion(input.releasedVersion)) return base;
15328
+ if (input.codexPresent === false) return base;
15329
+ if (isSemverVersion(input.codexActiveVersion)) {
15297
15330
  if (compareVersions(input.codexActiveVersion, input.releasedVersion) < 0) {
15298
15331
  return {
15299
15332
  ...base,
@@ -15305,11 +15338,11 @@ function buildCodexActiveCacheCheck(input) {
15305
15338
  }
15306
15339
  return { ...base, activeCacheVersion: input.codexActiveVersion, releasedVersion: input.releasedVersion };
15307
15340
  }
15308
- if (isSemverVersion2(input.codexRecordVersion) && compareVersions(input.codexRecordVersion, input.releasedVersion) < 0) {
15341
+ if (isSemverVersion(input.codexRecordVersion) && compareVersions(input.codexRecordVersion, input.releasedVersion) < 0) {
15309
15342
  return base;
15310
15343
  }
15311
15344
  const activeCacheVersion = highestSemver(input.codexCacheVersions ?? []);
15312
- const cacheCurrent = isSemverVersion2(activeCacheVersion) && compareVersions(activeCacheVersion, input.releasedVersion) >= 0;
15345
+ const cacheCurrent = isSemverVersion(activeCacheVersion) && compareVersions(activeCacheVersion, input.releasedVersion) >= 0;
15313
15346
  if (cacheCurrent) {
15314
15347
  return { ...base, activeCacheVersion, releasedVersion: input.releasedVersion };
15315
15348
  }
@@ -15388,7 +15421,7 @@ function buildSessionSkillRootCheck(input) {
15388
15421
  const sessionVersion = parseSessionSkillRootVersion(input.pluginRoot);
15389
15422
  if (!sessionVersion) return base;
15390
15423
  const activeCacheVersion = highestSemver(input.cacheVersions ?? []);
15391
- if (!isSemverVersion2(activeCacheVersion)) return { ...base, sessionVersion };
15424
+ if (!isSemverVersion(activeCacheVersion)) return { ...base, sessionVersion };
15392
15425
  if (compareVersions(sessionVersion, activeCacheVersion) >= 0) {
15393
15426
  return { ...base, sessionVersion, activeCacheVersion };
15394
15427
  }
@@ -15470,7 +15503,8 @@ var PLUGIN_SURFACE_HEAL = {
15470
15503
  ],
15471
15504
  pluginRunner: "codex",
15472
15505
  fix: (surface) => `${CODEX_RECOVERY} # then ${reloadAction(surface)}`,
15473
- updateRecipe: [CODEX_RECOVERY, "codex plugin list # verify mmi@mutmutco shows the released version"]
15506
+ updateRecipe: [CODEX_RECOVERY, "codex plugin list # verify mmi@mutmutco shows the released version"],
15507
+ guardRecovery: "$env:MMI_AGENT_SURFACE='codex'; mmi-cli plugin-heal"
15474
15508
  },
15475
15509
  cursor: {
15476
15510
  delivery: "cursor-cache",
@@ -15554,7 +15588,7 @@ function renderSurfaceGuide(label, steps) {
15554
15588
  }
15555
15589
  function highestSemver(versions) {
15556
15590
  return versions.reduce((best, v) => {
15557
- if (!isSemverVersion2(v)) return best;
15591
+ if (!isSemverVersion(v)) return best;
15558
15592
  if (best === void 0) return v;
15559
15593
  return compareVersions(v, best) > 0 ? v : best;
15560
15594
  }, void 0);
@@ -15566,17 +15600,18 @@ function buildPluginUpdateReport(input) {
15566
15600
  const opencodePlugin = highestSemver(input.opencodePluginVersions ?? []);
15567
15601
  return {
15568
15602
  versions: {
15569
- ...isSemverVersion2(input.cliVersion) ? { cli: input.cliVersion } : {},
15603
+ ...isSemverVersion(input.cliVersion) ? { cli: input.cliVersion } : {},
15570
15604
  ...claudePlugin ? { claudePlugin } : {},
15571
15605
  ...codexMarketplace ? { codexMarketplace } : {},
15572
15606
  ...codexActiveCache ? { codexActiveCache } : {},
15573
15607
  ...opencodePlugin ? { opencodePlugin } : {},
15574
- ...isSemverVersion2(input.releasedVersion) ? { released: input.releasedVersion } : {}
15608
+ ...isSemverVersion(input.releasedVersion) ? { released: input.releasedVersion } : {},
15609
+ ...input.releasedVersionSource ? { releasedSource: input.releasedVersionSource } : {}
15575
15610
  },
15576
15611
  recipes: PLUGIN_UPDATE_RECIPES
15577
15612
  };
15578
15613
  }
15579
- function renderPluginUpdateReport(report) {
15614
+ function renderPluginVersionBlock(report) {
15580
15615
  const v = report.versions;
15581
15616
  const show = (x) => x ?? "unknown";
15582
15617
  const versionRows = [
@@ -15587,27 +15622,22 @@ function renderPluginUpdateReport(report) {
15587
15622
  ["OpenCode plugin", show(v.opencodePlugin)]
15588
15623
  ];
15589
15624
  const pad = Math.max(...versionRows.map(([label]) => label.length));
15590
- const lines = [
15625
+ return [
15591
15626
  `MMI versions (target release: ${show(v.released)})`,
15592
- ...versionRows.map(([label, value]) => ` ${label.padEnd(pad)} ${value}`),
15593
- "",
15594
- "Update commands by surface"
15627
+ ...versionRows.map(([label, value]) => ` ${label.padEnd(pad)} ${value}`)
15595
15628
  ];
15596
- for (const surface of PLUGIN_GUIDE_SURFACES) {
15597
- lines.push(...renderSurfaceGuide(surface.label, report.recipes[surface.key]));
15598
- }
15599
- return lines;
15600
15629
  }
15601
15630
  function buildDoctorJsonPayload(input) {
15602
15631
  return {
15603
- ok: input.checks.every((c) => c.ok),
15632
+ ok: doctorExitCode(input.checks) === 0,
15633
+ hasAdvisories: input.checks.some((c) => !c.ok && isAdvisoryDoctorCheck(c)),
15604
15634
  checks: input.checks,
15605
15635
  updateReport: input.updateReport,
15606
15636
  ...input.resources.length ? { resources: input.resources } : {}
15607
15637
  };
15608
15638
  }
15609
15639
  var INSTALLED_PLUGIN_VERSION_LABEL = "installed MMI plugin version (vs latest release)";
15610
- function isSemverVersion2(v) {
15640
+ function isSemverVersion(v) {
15611
15641
  return typeof v === "string" && /^v?\d+\.\d+\.\d+/.test(v.trim());
15612
15642
  }
15613
15643
  function staleRecordCommand(surface) {
@@ -15630,7 +15660,7 @@ function buildInstalledPluginVersionCheck(input) {
15630
15660
  fix: pluginRecoveryFix(input.surface),
15631
15661
  pluginId
15632
15662
  };
15633
- if (!input.isOrgRepo || !isSemverVersion2(input.releasedVersion)) return base;
15663
+ if (!input.isOrgRepo || !isSemverVersion(input.releasedVersion)) return base;
15634
15664
  if (input.surface === "cursor") return base;
15635
15665
  const stale = [];
15636
15666
  let sawRecord = false;
@@ -15640,7 +15670,7 @@ function buildInstalledPluginVersionCheck(input) {
15640
15670
  if (!Array.isArray(records) || records.length === 0) continue;
15641
15671
  sawRecord = true;
15642
15672
  const installedVersion = bestRecord(records).version;
15643
- if (!isSemverVersion2(installedVersion)) continue;
15673
+ if (!isSemverVersion(installedVersion)) continue;
15644
15674
  if (compareVersions(installedVersion, input.releasedVersion) >= 0) {
15645
15675
  currentVersion = currentVersion ?? installedVersion;
15646
15676
  } else {
@@ -15695,14 +15725,14 @@ function buildManagedPluginDriftCheck(input) {
15695
15725
  for (const source of input.sources) {
15696
15726
  if (source.directVersions && descriptor.id in source.directVersions) {
15697
15727
  const version2 = source.directVersions[descriptor.id];
15698
- if (isSemverVersion2(version2)) versions.push({ surface: source.surface, version: normalize(version2) });
15728
+ if (isSemverVersion(version2)) versions.push({ surface: source.surface, version: normalize(version2) });
15699
15729
  continue;
15700
15730
  }
15701
15731
  const records = source.installed?.plugins?.[descriptor.id];
15702
15732
  if (!Array.isArray(records) || records.length === 0) continue;
15703
15733
  const recordVersion = bestRecord(records).version;
15704
- const version = isSemverVersion2(recordVersion) ? recordVersion : highestSemver(source.cacheVersions?.[descriptor.id] ?? []);
15705
- if (!isSemverVersion2(version)) continue;
15734
+ const version = isSemverVersion(recordVersion) ? recordVersion : highestSemver(source.cacheVersions?.[descriptor.id] ?? []);
15735
+ if (!isSemverVersion(version)) continue;
15706
15736
  versions.push({ surface: source.surface, version: normalize(version) });
15707
15737
  }
15708
15738
  if (versions.length < 2) continue;
@@ -15717,8 +15747,8 @@ var OPENCODE_VERSION_LABEL = "installed OpenCode MMI adapter version (vs latest
15717
15747
  function buildOpencodeVersionCheck(input) {
15718
15748
  const fix = pluginRecoveryFix("opencode");
15719
15749
  const base = { ok: true, label: OPENCODE_VERSION_LABEL, fix };
15720
- if (!input.isOrgRepo || !isSemverVersion2(input.releasedVersion)) return base;
15721
- if (!isSemverVersion2(input.installedVersion)) {
15750
+ if (!input.isOrgRepo || !isSemverVersion(input.releasedVersion)) return base;
15751
+ if (!isSemverVersion(input.installedVersion)) {
15722
15752
  return { ...base, ok: false, severityOverride: "hard", releasedVersion: input.releasedVersion };
15723
15753
  }
15724
15754
  if (compareVersions(input.installedVersion, input.releasedVersion) >= 0) {
@@ -15735,7 +15765,7 @@ function pickOpencodeActiveVersion(input) {
15735
15765
  }
15736
15766
  function planOpencodePluginCacheQuarantine(input) {
15737
15767
  if (!input.cacheExists) return { quarantine: false };
15738
- if (!isSemverVersion2(input.cacheVersion) || !isSemverVersion2(input.releasedVersion)) return { quarantine: false };
15768
+ if (!isSemverVersion(input.cacheVersion) || !isSemverVersion(input.releasedVersion)) return { quarantine: false };
15739
15769
  if (compareVersions(input.cacheVersion, input.releasedVersion) < 0) {
15740
15770
  return { quarantine: true, cacheVersion: input.cacheVersion, releasedVersion: input.releasedVersion };
15741
15771
  }
@@ -15971,8 +16001,8 @@ function buildCursorPluginInstallCheck(input) {
15971
16001
  };
15972
16002
  }
15973
16003
  }
15974
- if (input.cacheRootExists && isSemverVersion2(input.releasedVersion) && input.pins.some((pin) => isSemverVersion2(pin.version) && compareVersions(pin.version, input.releasedVersion) < 0)) {
15975
- const stale = input.pins.filter((pin) => isSemverVersion2(pin.version) && compareVersions(pin.version, input.releasedVersion) < 0).map((pin) => `${pin.version} at ${pin.name}`).join(", ");
16004
+ if (input.cacheRootExists && isSemverVersion(input.releasedVersion) && input.pins.some((pin) => isSemverVersion(pin.version) && compareVersions(pin.version, input.releasedVersion) < 0)) {
16005
+ const stale = input.pins.filter((pin) => isSemverVersion(pin.version) && compareVersions(pin.version, input.releasedVersion) < 0).map((pin) => `${pin.version} at ${pin.name}`).join(", ");
15976
16006
  return {
15977
16007
  ...base,
15978
16008
  ok: false,
@@ -16166,18 +16196,19 @@ function isAdvisoryDoctorCheck(check) {
16166
16196
  function doctorExitCode(checks) {
16167
16197
  return checks.some((c) => !c.ok && !isAdvisoryDoctorCheck(c)) ? 1 : 0;
16168
16198
  }
16169
- function renderPluginUpdateReportStaleOnly(report) {
16199
+ function renderPluginUpdateReportStaleOnly(report, allChecksPass = false) {
16170
16200
  const v = report.versions;
16171
16201
  const released = v.released;
16172
16202
  if (!released) return [];
16173
- const isStale = (current) => Boolean(current && isSemverVersion2(current) && compareVersions(current, released) < 0);
16203
+ const isStale = (current) => Boolean(current && isSemverVersion(current) && compareVersions(current, released) < 0);
16174
16204
  const blocks = [];
16175
16205
  for (const surface of PLUGIN_GUIDE_SURFACES) {
16176
16206
  if (!surface.versionKeys.some((k) => isStale(v[k]))) continue;
16177
16207
  blocks.push(...renderSurfaceGuide(surface.label, report.recipes[surface.key]));
16178
16208
  }
16179
16209
  if (!blocks.length) return [];
16180
- return ["Update commands (stale surfaces):", ...blocks];
16210
+ const heading = allChecksPass ? "Optional updates available (all checks passed):" : "Update commands (stale surfaces):";
16211
+ return [heading, ...blocks];
16181
16212
  }
16182
16213
  var DOCTOR_VERBOSE_HINT = "Run mmi-cli doctor --verbose for the full audit checklist + version report.";
16183
16214
  function doctorCheckGlyph(check) {
@@ -16223,14 +16254,14 @@ function doctorHumanLines(input) {
16223
16254
  if (input.shouldApply && input.pluginReloadRequired) {
16224
16255
  lines.push("", `\u21BB ${input.reloadHint ?? "reload"} so the healed plugin/MCP installs load.`);
16225
16256
  }
16226
- const stale = renderPluginUpdateReportStaleOnly(input.updateReport);
16257
+ const stale = renderPluginUpdateReportStaleOnly(input.updateReport, gaps.length === 0);
16227
16258
  if (stale.length) {
16228
16259
  lines.push("", ...stale);
16229
16260
  } else if (!input.verbose) {
16230
16261
  lines.push("", DOCTOR_VERBOSE_HINT);
16231
16262
  }
16232
16263
  if (!input.verbose) return lines;
16233
- lines.push("", ...renderPluginUpdateReport(input.updateReport));
16264
+ lines.push("", ...renderPluginVersionBlock(input.updateReport));
16234
16265
  lines.push(
16235
16266
  "",
16236
16267
  doctorSummaryLine({ checks: input.checks, updateReport: input.updateReport, shouldApply: input.shouldApply, healedCount: input.healedCount }),
@@ -16293,7 +16324,7 @@ function buildPluginResolvabilityCheck(input) {
16293
16324
  return {
16294
16325
  ok: false,
16295
16326
  label: PLUGIN_RESOLVABILITY_LABEL,
16296
- fix: "run: mmi-cli plugin-heal",
16327
+ fix: `run: ${PLUGIN_SURFACE_HEAL[surfaceToken(surface) ?? "claude"]?.recovery ?? PLUGIN_SURFACE_HEAL.claude.recovery}`,
16297
16328
  state
16298
16329
  };
16299
16330
  default: {
@@ -16303,6 +16334,176 @@ function buildPluginResolvabilityCheck(input) {
16303
16334
  }
16304
16335
  }
16305
16336
 
16337
+ // src/cursor-plugin-seed.ts
16338
+ function isSemverVersion2(v) {
16339
+ return typeof v === "string" && /^v?\d+\.\d+\.\d+/.test(v.trim());
16340
+ }
16341
+ var MMI_HUB_REPO = "mutmutco/MMI-Hub";
16342
+ var CURSOR_THIRD_PARTY_STATE_KEY = "cursor/thirdPartyExtensibilityEnabled";
16343
+ var PLUGIN_JSON_REL = ".cursor-plugin/plugin.json";
16344
+ var execFileBuffer = (0, import_node_util7.promisify)(import_node_child_process10.execFile);
16345
+ function gitFetchReleaseTagArgs(hubCheckout, tag) {
16346
+ return ["-C", hubCheckout, "fetch", "origin", "tag", tag, "--quiet"];
16347
+ }
16348
+ function ghReleaseTarballApiArgs(tag) {
16349
+ return ["api", `repos/${MMI_HUB_REPO}/tarball/refs/tags/${tag}`];
16350
+ }
16351
+ function cursorUserGlobalStatePath() {
16352
+ if (process.platform === "win32") {
16353
+ const base = process.env.APPDATA || (0, import_node_path16.join)((0, import_node_os6.homedir)(), "AppData", "Roaming");
16354
+ return (0, import_node_path16.join)(base, "Cursor", "User", "globalStorage", "state.vscdb");
16355
+ }
16356
+ if (process.platform === "darwin") {
16357
+ return (0, import_node_path16.join)((0, import_node_os6.homedir)(), "Library", "Application Support", "Cursor", "User", "globalStorage", "state.vscdb");
16358
+ }
16359
+ return (0, import_node_path16.join)((0, import_node_os6.homedir)(), ".config", "Cursor", "User", "globalStorage", "state.vscdb");
16360
+ }
16361
+ async function readCursorThirdPartyExtensibilityEnabled(execFileP5) {
16362
+ const dbPath = cursorUserGlobalStatePath();
16363
+ if (!(0, import_node_fs15.existsSync)(dbPath)) return void 0;
16364
+ try {
16365
+ const { stdout } = await execFileP5("sqlite3", [dbPath, `SELECT value FROM ItemTable WHERE key = '${CURSOR_THIRD_PARTY_STATE_KEY}';`], {
16366
+ timeout: 5e3
16367
+ });
16368
+ const value = stdout.trim().toLowerCase();
16369
+ if (value === "true") return true;
16370
+ if (value === "false") return false;
16371
+ return void 0;
16372
+ } catch {
16373
+ return void 0;
16374
+ }
16375
+ }
16376
+ function syncDirContents(src, dest) {
16377
+ (0, import_node_fs15.mkdirSync)(dest, { recursive: true });
16378
+ for (const name of (0, import_node_fs15.readdirSync)(dest)) {
16379
+ (0, import_node_fs15.rmSync)((0, import_node_path16.join)(dest, name), { recursive: true, force: true });
16380
+ }
16381
+ (0, import_node_fs15.cpSync)(src, dest, { recursive: true });
16382
+ }
16383
+ function releaseTag(releasedVersion) {
16384
+ return releasedVersion.startsWith("v") ? releasedVersion : `v${releasedVersion}`;
16385
+ }
16386
+ async function extractPluginMmiFromHubCheckout(hubCheckout, tag, tmpRoot, execFileP5) {
16387
+ const tarFile = (0, import_node_path16.join)(tmpRoot, "archive.tar");
16388
+ try {
16389
+ await execFileP5("git", gitFetchReleaseTagArgs(hubCheckout, tag), { timeout: 6e4 });
16390
+ await execFileP5("git", ["-C", hubCheckout, "archive", "--format=tar", `--output=${tarFile}`, tag, "plugins/mmi"], {
16391
+ timeout: 6e4
16392
+ });
16393
+ await execFileP5("tar", ["-xf", tarFile, "-C", tmpRoot], { timeout: 6e4 });
16394
+ const pluginMmi = (0, import_node_path16.join)(tmpRoot, "plugins", "mmi");
16395
+ return (0, import_node_fs15.existsSync)((0, import_node_path16.join)(pluginMmi, PLUGIN_JSON_REL)) ? pluginMmi : void 0;
16396
+ } catch {
16397
+ return void 0;
16398
+ }
16399
+ }
16400
+ async function downloadPluginMmiViaGh(tag, tmpRoot) {
16401
+ const tarPath = (0, import_node_path16.join)(tmpRoot, "repo.tgz");
16402
+ try {
16403
+ (0, import_node_fs15.mkdirSync)(tmpRoot, { recursive: true });
16404
+ const { stdout } = await execFileBuffer("gh", ghReleaseTarballApiArgs(tag), {
16405
+ timeout: 12e4,
16406
+ maxBuffer: 100 * 1024 * 1024,
16407
+ encoding: "buffer",
16408
+ windowsHide: true
16409
+ });
16410
+ (0, import_node_fs15.writeFileSync)(tarPath, stdout);
16411
+ await execFileBuffer("tar", ["-xzf", tarPath, "-C", tmpRoot], { timeout: 12e4, windowsHide: true });
16412
+ const top = (0, import_node_fs15.readdirSync)(tmpRoot).find((entry) => entry !== "repo.tgz");
16413
+ if (!top) return void 0;
16414
+ const pluginMmi = (0, import_node_path16.join)(tmpRoot, top, "plugins", "mmi");
16415
+ return (0, import_node_fs15.existsSync)((0, import_node_path16.join)(pluginMmi, PLUGIN_JSON_REL)) ? pluginMmi : void 0;
16416
+ } catch {
16417
+ return void 0;
16418
+ }
16419
+ }
16420
+ async function resolvePluginMmiSource(releasedVersion, hubCheckout, tmpRoot, execFileP5) {
16421
+ (0, import_node_fs15.mkdirSync)(tmpRoot, { recursive: true });
16422
+ const tag = releaseTag(releasedVersion);
16423
+ if (hubCheckout) {
16424
+ const fromHub = await extractPluginMmiFromHubCheckout(hubCheckout, tag, tmpRoot, execFileP5);
16425
+ if (fromHub) return fromHub;
16426
+ }
16427
+ return downloadPluginMmiViaGh(tag, (0, import_node_path16.join)(tmpRoot, "gh"));
16428
+ }
16429
+ function cursorPluginPinsNeedingSeed(pins, releasedVersion) {
16430
+ if (!isSemverVersion2(releasedVersion)) return pins.filter((pin) => !pin.hasPluginJson || !pin.hasHooksJson || pin.isEmpty);
16431
+ return pins.filter((pin) => {
16432
+ if (!pin.hasPluginJson || !pin.hasHooksJson || pin.isEmpty) return true;
16433
+ if (isSemverVersion2(pin.version) && compareVersions(pin.version, releasedVersion) < 0) return true;
16434
+ return false;
16435
+ });
16436
+ }
16437
+ function cursorPluginCacheSeedTargets(pins, releasedVersion, cacheRoot, cacheRootExists = false) {
16438
+ const needing = cursorPluginPinsNeedingSeed(pins, releasedVersion);
16439
+ if (needing.length > 0) return needing.map((pin) => pin.path);
16440
+ if (pins.length === 0 && cacheRoot && cacheRootExists && isSemverVersion2(releasedVersion)) {
16441
+ return [(0, import_node_path16.join)(cacheRoot, `v${releasedVersion.replace(/^v/, "")}`)];
16442
+ }
16443
+ return [];
16444
+ }
16445
+ async function applyCursorPluginCacheSeed(input) {
16446
+ if (!isSemverVersion2(input.releasedVersion)) return false;
16447
+ const targets = cursorPluginCacheSeedTargets(input.pins, input.releasedVersion, input.cacheRoot, input.cacheRootExists);
16448
+ if (targets.length === 0) return false;
16449
+ const tmpRoot = await input.mkdtemp("mmi-cursor-seed-");
16450
+ const source = await resolvePluginMmiSource(input.releasedVersion, input.hubCheckout, tmpRoot, input.execFileP);
16451
+ if (!source) return false;
16452
+ input.log(` \u21BB seeding Cursor MMI plugin cache \u2192 ${input.releasedVersion}\u2026`);
16453
+ for (const dest of targets) {
16454
+ syncDirContents(source, dest);
16455
+ }
16456
+ (0, import_node_fs15.rmSync)(tmpRoot, { recursive: true, force: true });
16457
+ return true;
16458
+ }
16459
+ var CURSOR_ENABLED_PIN_LABEL = "Cursor Team Marketplace enabled plugin pin";
16460
+ function cursorSplitBrainCapabilities() {
16461
+ const skills = OPENCODE_WORKFLOW_COMMANDS.map((c) => `/${c}`).join(", ");
16462
+ return `org skills (${skills}) and SessionStart hooks`;
16463
+ }
16464
+ function cursorEnabledPinRecovery() {
16465
+ 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";
16466
+ }
16467
+ var CURSOR_FOUND_CACHED_MMI_RE = /Found cached plugin:\s*mmi\s+at\s+.*?[\\/]([0-9a-f]{6,40})\b/gi;
16468
+ var CURSOR_MMI_LOADED_RE = /loadClaudePlugin\s+mmi@mutmutco\s+loaded/i;
16469
+ var CURSOR_GIT_AUTH_FAIL_RE = /unable to get password from user/i;
16470
+ var CURSOR_SAFE_PIN_NAME_RE = /^[0-9a-zA-Z._-]+$/;
16471
+ function parseCursorPluginLog(text) {
16472
+ let enabledPin;
16473
+ for (const m of text.matchAll(CURSOR_FOUND_CACHED_MMI_RE)) enabledPin = m[1];
16474
+ const loadSucceeded = CURSOR_MMI_LOADED_RE.test(text);
16475
+ const loadFailed = CURSOR_GIT_AUTH_FAIL_RE.test(text) && !loadSucceeded;
16476
+ return { enabledPin, loadSucceeded, loadFailed };
16477
+ }
16478
+ function buildCursorEnabledPinCheck(input) {
16479
+ const label = CURSOR_ENABLED_PIN_LABEL;
16480
+ const healthy = { ok: true, label, fix: "" };
16481
+ if (!input.isOrgRepo) return healthy;
16482
+ if (!input.enabledPin && !input.loadFailed) return healthy;
16483
+ if (input.loadSucceeded && !input.loadFailed) return healthy;
16484
+ const caps = cursorSplitBrainCapabilities();
16485
+ if (input.loadFailed) {
16486
+ return {
16487
+ ok: false,
16488
+ severityOverride: "hard",
16489
+ label,
16490
+ 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()}`
16491
+ };
16492
+ }
16493
+ const enabledDir = input.pins.find((p) => p.name === input.enabledPin);
16494
+ const complete = !!enabledDir && !enabledDir.isEmpty && enabledDir.hasPluginJson && enabledDir.hasHooksJson;
16495
+ if (!complete) {
16496
+ const others = input.pins.filter((p) => p.name !== input.enabledPin && CURSOR_SAFE_PIN_NAME_RE.test(p.name)).map((p) => p.name);
16497
+ return {
16498
+ ok: false,
16499
+ severityOverride: "hard",
16500
+ label,
16501
+ 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()}`
16502
+ };
16503
+ }
16504
+ return healthy;
16505
+ }
16506
+
16306
16507
  // src/mcp-reconcile.ts
16307
16508
  var PLAYWRIGHT_MCP_SPEC = {
16308
16509
  name: "playwright",
@@ -16347,6 +16548,7 @@ function extractTomlServer(content, spec) {
16347
16548
  }
16348
16549
  function detectMcpState(target, spec) {
16349
16550
  if (!target.present) return { target, state: "skip", action: "skip", reasons: [] };
16551
+ if (target.format === "claude-cli") return detectClaudeCliMcpState(target, spec);
16350
16552
  if (target.content == null) {
16351
16553
  if (target.createWhenFileAbsent) return { target, state: "absent", action: "install", reasons: [] };
16352
16554
  return { target, state: "skip", action: "skip", reasons: [] };
@@ -16370,6 +16572,56 @@ function detectMcpState(target, spec) {
16370
16572
  function planMcpReconcile(targets, spec) {
16371
16573
  return targets.map((t) => detectMcpState(t, spec));
16372
16574
  }
16575
+ function parseClaudeMcpGetOutput(stdout) {
16576
+ const commandMatch = /^\s*Command:\s*(.+)$/m.exec(stdout);
16577
+ if (commandMatch == null) return null;
16578
+ const argsMatch = /^\s*Args:\s*(.*)$/m.exec(stdout);
16579
+ const env = {};
16580
+ const lines = stdout.replace(/\r\n/g, "\n").split("\n");
16581
+ const headerIdx = lines.findIndex((l) => /^\s*Environment:\s*$/.test(l));
16582
+ if (headerIdx !== -1) {
16583
+ for (const line of lines.slice(headerIdx + 1)) {
16584
+ if (line.trim() === "") continue;
16585
+ if (!/^[ \t]/.test(line)) break;
16586
+ const kv = /^\s*([A-Za-z_][A-Za-z0-9_]*)=(.*)$/.exec(line);
16587
+ if (kv != null) env[kv[1]] = kv[2];
16588
+ }
16589
+ }
16590
+ return { command: commandMatch[1].trim(), args: (argsMatch?.[1] ?? "").trim(), env };
16591
+ }
16592
+ function detectClaudeCliMcpState(target, spec) {
16593
+ if (target.content == null) {
16594
+ return { target, state: "absent", action: "install", reasons: [] };
16595
+ }
16596
+ const matchedName = target.claudeCliMatchedName ?? spec.name;
16597
+ const isLegacy = matchedName !== spec.name;
16598
+ const parsed = parseClaudeMcpGetOutput(target.content);
16599
+ if (parsed == null) {
16600
+ const reasons2 = isLegacy ? ["legacy-name", "unparseable"] : ["unparseable"];
16601
+ return { target, state: "drifted", action: "reconcile", reasons: reasons2, matchedName };
16602
+ }
16603
+ const reasons = [];
16604
+ if (isLegacy) reasons.push("legacy-name");
16605
+ if (textHasPlaywrightVisionCap(parsed.args)) reasons.push("vision-cap");
16606
+ if (!textHasCanonicalPlaywrightOutputDir(parsed.args)) reasons.push("output-dir");
16607
+ if (reasons.length === 0) return { target, state: "healthy", action: "none", reasons: [], matchedName };
16608
+ return { target, state: "drifted", action: "reconcile", reasons, matchedName };
16609
+ }
16610
+ var CLAUDE_MCP_USER_SCOPE = "user";
16611
+ function planClaudeCliMcpHeal(item, spec) {
16612
+ if (item.action !== "install" && item.action !== "reconcile") return [];
16613
+ const commands = [];
16614
+ if (item.action === "reconcile" && item.matchedName != null) {
16615
+ commands.push({ op: "remove", args: ["remove", item.matchedName, "-s", CLAUDE_MCP_USER_SCOPE] });
16616
+ }
16617
+ const parsed = item.target.content != null ? parseClaudeMcpGetOutput(item.target.content) : null;
16618
+ const envFlags = Object.entries(parsed?.env ?? {}).flatMap(([k, v]) => ["-e", `${k}=${v}`]);
16619
+ commands.push({
16620
+ op: "add",
16621
+ args: ["add", spec.name, "-s", CLAUDE_MCP_USER_SCOPE, ...envFlags, "--", spec.command, ...spec.args]
16622
+ });
16623
+ return commands;
16624
+ }
16373
16625
  function buildMcpReconcileCheck(plan) {
16374
16626
  const base = {
16375
16627
  ok: true,
@@ -16560,9 +16812,9 @@ ${block}
16560
16812
  }
16561
16813
 
16562
16814
  // src/cli-doctor-shared.ts
16563
- var import_node_fs15 = require("node:fs");
16564
- var import_node_path16 = require("node:path");
16565
16815
  var import_node_fs16 = require("node:fs");
16816
+ var import_node_path17 = require("node:path");
16817
+ var import_node_fs17 = require("node:fs");
16566
16818
  var GC_GH_TIMEOUT_MS = 2e4;
16567
16819
  async function awsCallerArn() {
16568
16820
  try {
@@ -16608,7 +16860,7 @@ async function localBranchHeads() {
16608
16860
  }
16609
16861
  async function currentRepoWorktreeGitRoot(repoRoot) {
16610
16862
  const gitCommonDir = (await execFileP2("git", ["rev-parse", "--git-common-dir"], { timeout: GIT_TIMEOUT_MS }).catch(() => ({ stdout: "" }))).stdout.trim();
16611
- return gitCommonDir ? (0, import_node_path16.resolve)(repoRoot, gitCommonDir, "worktrees") : "";
16863
+ return gitCommonDir ? (0, import_node_path17.resolve)(repoRoot, gitCommonDir, "worktrees") : "";
16612
16864
  }
16613
16865
  async function worktreeBranches() {
16614
16866
  const { stdout } = await execFileP2("git", ["worktree", "list", "--porcelain"], { timeout: GIT_TIMEOUT_MS });
@@ -16628,18 +16880,18 @@ function resolveGitdirForWorktreeFile(worktreePath, content) {
16628
16880
  const match = /^gitdir:\s*(.+)\s*$/im.exec(content);
16629
16881
  if (!match?.[1]) return void 0;
16630
16882
  const raw = match[1].trim();
16631
- return (0, import_node_path16.isAbsolute)(raw) ? raw : (0, import_node_path16.resolve)(worktreePath, raw);
16883
+ return (0, import_node_path17.isAbsolute)(raw) ? raw : (0, import_node_path17.resolve)(worktreePath, raw);
16632
16884
  }
16633
16885
  function metadataOwnsMissingWorktreeDir(worktreePath, worktreeGitRoot) {
16634
16886
  if (!worktreeGitRoot) return false;
16635
16887
  try {
16636
- const entries = (0, import_node_fs16.readdirSync)(worktreeGitRoot, { withFileTypes: true });
16888
+ const entries = (0, import_node_fs17.readdirSync)(worktreeGitRoot, { withFileTypes: true });
16637
16889
  for (const ent of entries) {
16638
16890
  if (!ent.isDirectory()) continue;
16639
16891
  try {
16640
- const gitdirPath = (0, import_node_fs15.readFileSync)((0, import_node_path16.join)(worktreeGitRoot, ent.name, "gitdir"), "utf8").trim();
16641
- const resolvedGitdir = (0, import_node_path16.isAbsolute)(gitdirPath) ? gitdirPath : (0, import_node_path16.resolve)(worktreeGitRoot, ent.name, gitdirPath);
16642
- if (sameWorktreeMetadataPath((0, import_node_path16.dirname)(resolvedGitdir), worktreePath)) return true;
16892
+ const gitdirPath = (0, import_node_fs16.readFileSync)((0, import_node_path17.join)(worktreeGitRoot, ent.name, "gitdir"), "utf8").trim();
16893
+ const resolvedGitdir = (0, import_node_path17.isAbsolute)(gitdirPath) ? gitdirPath : (0, import_node_path17.resolve)(worktreeGitRoot, ent.name, gitdirPath);
16894
+ if (sameWorktreeMetadataPath((0, import_node_path17.dirname)(resolvedGitdir), worktreePath)) return true;
16643
16895
  } catch {
16644
16896
  }
16645
16897
  }
@@ -16649,7 +16901,7 @@ function metadataOwnsMissingWorktreeDir(worktreePath, worktreeGitRoot) {
16649
16901
  }
16650
16902
  function pathExistsKnown(path2) {
16651
16903
  try {
16652
- (0, import_node_fs16.statSync)(path2);
16904
+ (0, import_node_fs17.statSync)(path2);
16653
16905
  return true;
16654
16906
  } catch (e) {
16655
16907
  const code = typeof e === "object" && e && "code" in e ? String(e.code ?? "") : "";
@@ -16658,10 +16910,10 @@ function pathExistsKnown(path2) {
16658
16910
  }
16659
16911
  }
16660
16912
  function inspectSiblingWorktreeDir(path2, worktreeGitRoot) {
16661
- const gitPath = (0, import_node_path16.join)(path2, ".git");
16913
+ const gitPath = (0, import_node_path17.join)(path2, ".git");
16662
16914
  let st;
16663
16915
  try {
16664
- st = (0, import_node_fs16.lstatSync)(gitPath);
16916
+ st = (0, import_node_fs17.lstatSync)(gitPath);
16665
16917
  } catch (e) {
16666
16918
  const code = typeof e === "object" && e && "code" in e ? String(e.code ?? "") : "";
16667
16919
  if (code === "ENOENT" || code === "ENOTDIR") {
@@ -16678,7 +16930,7 @@ function inspectSiblingWorktreeDir(path2, worktreeGitRoot) {
16678
16930
  if (st.isDirectory()) return { path: path2, gitType: "dir" };
16679
16931
  if (!st.isFile()) return { path: path2, gitType: "other" };
16680
16932
  try {
16681
- const gitFileContent = (0, import_node_fs15.readFileSync)(gitPath, "utf8");
16933
+ const gitFileContent = (0, import_node_fs16.readFileSync)(gitPath, "utf8");
16682
16934
  const gitdir = resolveGitdirForWorktreeFile(path2, gitFileContent);
16683
16935
  const gitDirExists = gitdir ? pathExistsKnown(gitdir) : false;
16684
16936
  return {
@@ -16695,7 +16947,7 @@ function inspectSiblingWorktreeDir(path2, worktreeGitRoot) {
16695
16947
  }
16696
16948
  function inspectDeadWorktreeDirContent(path2) {
16697
16949
  try {
16698
- return { entries: (0, import_node_fs16.readdirSync)(path2) };
16950
+ return { entries: (0, import_node_fs17.readdirSync)(path2) };
16699
16951
  } catch (e) {
16700
16952
  const code = typeof e === "object" && e && "code" in e ? String(e.code ?? "") : "";
16701
16953
  return { error: code ? `unable to inspect directory contents (${code})` : "unable to inspect directory contents" };
@@ -16714,8 +16966,8 @@ async function siblingWorktreeDirs() {
16714
16966
  const worktreeGitRoot = await currentRepoWorktreeGitRoot(repoRoot);
16715
16967
  const siblingRoot = siblingMmiWorktreesRoot(repoRoot);
16716
16968
  try {
16717
- const entries = (0, import_node_fs16.readdirSync)(siblingRoot, { withFileTypes: true });
16718
- return entries.filter((ent) => ent.isDirectory()).map((ent) => inspectSiblingWorktreeDir((0, import_node_path16.join)(siblingRoot, ent.name), worktreeGitRoot)).filter((entry) => Boolean(entry));
16969
+ const entries = (0, import_node_fs17.readdirSync)(siblingRoot, { withFileTypes: true });
16970
+ return entries.filter((ent) => ent.isDirectory()).map((ent) => inspectSiblingWorktreeDir((0, import_node_path17.join)(siblingRoot, ent.name), worktreeGitRoot)).filter((entry) => Boolean(entry));
16719
16971
  } catch {
16720
16972
  return [];
16721
16973
  }
@@ -16765,26 +17017,28 @@ async function fetchHubVersionInfo(baseUrl) {
16765
17017
  }
16766
17018
  function readRepoVersion() {
16767
17019
  try {
16768
- return JSON.parse((0, import_node_fs17.readFileSync)((0, import_node_path17.join)(process.cwd(), ".claude-plugin", "plugin.json"), "utf8")).version || void 0;
17020
+ return JSON.parse((0, import_node_fs18.readFileSync)((0, import_node_path18.join)(process.cwd(), ".claude-plugin", "plugin.json"), "utf8")).version || void 0;
16769
17021
  } catch {
16770
17022
  return void 0;
16771
17023
  }
16772
17024
  }
16773
17025
  async function fetchReleasedVersion() {
16774
- try {
16775
- const { stdout } = await execFileP2("gh", pluginManifestVersionArgs(), { timeout: 5e3 });
16776
- return parseManifestVersion(stdout);
16777
- } catch {
16778
- return void 0;
16779
- }
17026
+ return resolveReleasedVersion({
17027
+ npm: async () => (await runHostBin("npm", npmReleasedVersionArgs(), { timeout: NPM_VIEW_TIMEOUT_MS })).stdout,
17028
+ gh: async () => (await execFileP2("gh", pluginManifestVersionArgs(), { timeout: 5e3 })).stdout
17029
+ });
16780
17030
  }
16781
17031
  var NPM_UPDATE_TIMEOUT_MS = 12e4;
16782
17032
  var NPM_VIEW_TIMEOUT_MS = 15e3;
16783
17033
  var PLUGIN_PULL_TIMEOUT_MS = 3e4;
16784
- async function applyVersionAutoUpdate(report, log) {
16785
- const action = versionAutoUpdateAction(report, Boolean(process.env.CLAUDE_PLUGIN_ROOT));
17034
+ async function applyVersionAutoUpdate(report, log, releasedSource) {
17035
+ const action = versionAutoUpdateAction(report, Boolean(process.env.CLAUDE_PLUGIN_ROOT), releasedSource);
16786
17036
  if (action === "none") return { report, applied: "none" };
16787
17037
  const target = report.releasedVersion ?? "latest";
17038
+ if (action === "npm-unreachable") {
17039
+ log(` \u2717 mmi-cli ${report.currentVersion} is stale (released ${target}, resolved via gh) \u2014 npm registry unreachable this pass; when npm is back: npm install -g @mutmutco/cli@latest`);
17040
+ return { report, applied: "none" };
17041
+ }
16788
17042
  if (action === "plugin-pull") {
16789
17043
  try {
16790
17044
  const root = (await execFileP2("git", ["-C", process.env.CLAUDE_PLUGIN_ROOT, "rev-parse", "--show-toplevel"], { timeout: PLUGIN_PULL_TIMEOUT_MS })).stdout.trim();
@@ -16829,15 +17083,15 @@ function reexecAttemptOutcome(strategy, code) {
16829
17083
  return "result";
16830
17084
  }
16831
17085
  function spawnReexecAttempt(command, args, env) {
16832
- return new Promise((resolve6) => {
17086
+ return new Promise((resolve5) => {
16833
17087
  let settled = false;
16834
17088
  const done = (code) => {
16835
17089
  if (!settled) {
16836
17090
  settled = true;
16837
- resolve6(code);
17091
+ resolve5(code);
16838
17092
  }
16839
17093
  };
16840
- const child = (0, import_node_child_process10.spawn)(command, args, { stdio: "inherit", env });
17094
+ const child = (0, import_node_child_process11.spawn)(command, args, { stdio: "inherit", env });
16841
17095
  child.on("error", () => done(-1));
16842
17096
  child.on("exit", (code) => done(code ?? 0));
16843
17097
  });
@@ -16912,30 +17166,42 @@ async function applyPluginHeal(token, surface, log, opts) {
16912
17166
  }
16913
17167
  var installedPluginsPath = (surface = detectSurface(process.env)) => {
16914
17168
  const homeDir = surface === "codex" ? ".codex" : ".claude";
16915
- return (0, import_node_path17.join)((0, import_node_os6.homedir)(), homeDir, "plugins", "installed_plugins.json");
17169
+ return (0, import_node_path18.join)((0, import_node_os7.homedir)(), homeDir, "plugins", "installed_plugins.json");
16916
17170
  };
16917
17171
  function readInstalledPlugins(surface = detectSurface(process.env)) {
16918
17172
  try {
16919
- return JSON.parse((0, import_node_fs17.readFileSync)(installedPluginsPath(surface), "utf8"));
17173
+ return JSON.parse((0, import_node_fs18.readFileSync)(installedPluginsPath(surface), "utf8"));
16920
17174
  } catch {
16921
17175
  return null;
16922
17176
  }
16923
17177
  }
17178
+ function marketplaceCloneCandidates(surface, home) {
17179
+ if (surface === "codex") {
17180
+ return [
17181
+ (0, import_node_path18.join)(home, ".codex", ".tmp", "marketplaces", "mutmutco"),
17182
+ (0, import_node_path18.join)(home, ".codex", "plugins", "marketplaces", "mutmutco")
17183
+ ];
17184
+ }
17185
+ return [(0, import_node_path18.join)(home, ".claude", "plugins", "marketplaces", "mutmutco")];
17186
+ }
17187
+ function marketplaceClonePresent(surface, home, exists = import_node_fs18.existsSync) {
17188
+ return marketplaceCloneCandidates(surface, home).some(exists);
17189
+ }
16924
17190
  function snapshotPluginGuardInput(surface = detectSurface(process.env), isOrgRepo = false) {
16925
17191
  const homeDir = surface === "codex" ? ".codex" : ".claude";
16926
17192
  const installed = readInstalledPlugins(surface);
16927
17193
  return {
16928
17194
  isOrgRepo,
16929
17195
  installRecordPresent: hasUserInstallRecord(installed, MMI_PLUGIN_ID) || hasProjectInstallRecord(installed, MMI_PLUGIN_ID, process.cwd()),
16930
- marketplaceClonePresent: (0, import_node_fs17.existsSync)((0, import_node_path17.join)((0, import_node_os6.homedir)(), homeDir, "plugins", "marketplaces", "mutmutco")),
16931
- pluginCachePresent: (0, import_node_fs17.existsSync)((0, import_node_path17.join)((0, import_node_os6.homedir)(), homeDir, "plugins", "cache", "mutmutco", "mmi"))
17196
+ marketplaceClonePresent: marketplaceClonePresent(surface, (0, import_node_os7.homedir)()),
17197
+ pluginCachePresent: (0, import_node_fs18.existsSync)((0, import_node_path18.join)((0, import_node_os7.homedir)(), homeDir, "plugins", "cache", "mutmutco", "mmi"))
16932
17198
  };
16933
17199
  }
16934
17200
  function installedPluginSources() {
16935
17201
  return ["claude", "codex"].map((surface) => {
16936
- const recordPath = (0, import_node_path17.join)((0, import_node_os6.homedir)(), `.${surface}`, "plugins", "installed_plugins.json");
17202
+ const recordPath = (0, import_node_path18.join)((0, import_node_os7.homedir)(), `.${surface}`, "plugins", "installed_plugins.json");
16937
17203
  try {
16938
- return { surface, installed: JSON.parse((0, import_node_fs17.readFileSync)(recordPath, "utf8")), recordPath };
17204
+ return { surface, installed: JSON.parse((0, import_node_fs18.readFileSync)(recordPath, "utf8")), recordPath };
16939
17205
  } catch {
16940
17206
  return { surface, installed: null, recordPath };
16941
17207
  }
@@ -16948,8 +17214,8 @@ function managedPluginSurfaceSources() {
16948
17214
  const segments = parseManagedPluginId(descriptor.id);
16949
17215
  if (!segments) continue;
16950
17216
  try {
16951
- cacheVersions[descriptor.id] = (0, import_node_fs17.readdirSync)(
16952
- (0, import_node_path17.join)((0, import_node_os6.homedir)(), `.${surface}`, "plugins", "cache", segments.marketplace, segments.name),
17217
+ cacheVersions[descriptor.id] = (0, import_node_fs18.readdirSync)(
17218
+ (0, import_node_path18.join)((0, import_node_os7.homedir)(), `.${surface}`, "plugins", "cache", segments.marketplace, segments.name),
16953
17219
  { withFileTypes: true }
16954
17220
  ).filter((entry) => entry.isDirectory()).map((entry) => entry.name);
16955
17221
  } catch {
@@ -16969,8 +17235,8 @@ function managedPluginCursorDirectVersions() {
16969
17235
  const segments = parseManagedPluginId(descriptor.id);
16970
17236
  if (!segments) continue;
16971
17237
  try {
16972
- const versions = (0, import_node_fs17.readdirSync)(
16973
- (0, import_node_path17.join)((0, import_node_os6.homedir)(), ".cursor", "plugins", "cache", segments.marketplace, segments.name),
17238
+ const versions = (0, import_node_fs18.readdirSync)(
17239
+ (0, import_node_path18.join)((0, import_node_os7.homedir)(), ".cursor", "plugins", "cache", segments.marketplace, segments.name),
16974
17240
  { withFileTypes: true }
16975
17241
  ).filter((entry) => entry.isDirectory() && !entry.name.startsWith(".")).map((entry) => entry.name);
16976
17242
  out[descriptor.id] = highestSemver(versions);
@@ -16989,7 +17255,7 @@ function managedPluginOpencodeDirectVersions() {
16989
17255
  }
16990
17256
  function readOpencodePackageVersionFrom(packageJsonPath) {
16991
17257
  try {
16992
- const parsed = JSON.parse((0, import_node_fs17.readFileSync)(packageJsonPath, "utf8"));
17258
+ const parsed = JSON.parse((0, import_node_fs18.readFileSync)(packageJsonPath, "utf8"));
16993
17259
  return typeof parsed.version === "string" && parsed.version.trim() ? parsed.version.trim() : void 0;
16994
17260
  } catch {
16995
17261
  return void 0;
@@ -16999,14 +17265,14 @@ function readOpencodePackageVersion(packageName) {
16999
17265
  const [scope, name] = packageName.startsWith("@") ? packageName.slice(1).split("/", 2) : [void 0, packageName];
17000
17266
  if (!name) return void 0;
17001
17267
  const diskCandidates = [
17002
- (0, import_node_path17.join)(opencodeConfigDir(), "node_modules", ...scope ? [`@${scope}`] : [], name, "package.json"),
17003
- (0, import_node_path17.join)((0, import_node_os6.homedir)(), ".cache", "opencode", "node_modules", ...scope ? [`@${scope}`] : [], name, "package.json")
17268
+ (0, import_node_path18.join)(opencodeConfigDir(), "node_modules", ...scope ? [`@${scope}`] : [], name, "package.json"),
17269
+ (0, import_node_path18.join)((0, import_node_os7.homedir)(), ".cache", "opencode", "node_modules", ...scope ? [`@${scope}`] : [], name, "package.json")
17004
17270
  ];
17005
17271
  const diskVersion = diskCandidates.map(readOpencodePackageVersionFrom).find((v) => v !== void 0);
17006
- const packagesRoot = (0, import_node_path17.join)((0, import_node_os6.homedir)(), ".cache", "opencode", "packages", ...scope ? [`@${scope}`] : []);
17272
+ const packagesRoot = (0, import_node_path18.join)((0, import_node_os7.homedir)(), ".cache", "opencode", "packages", ...scope ? [`@${scope}`] : []);
17007
17273
  let cacheVersion;
17008
17274
  try {
17009
- 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));
17275
+ 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));
17010
17276
  if (cacheVersions.length) cacheVersion = cacheVersions.reduce((lowest, v) => compareVersions(v, lowest) < 0 ? v : lowest);
17011
17277
  } catch {
17012
17278
  }
@@ -17014,7 +17280,7 @@ function readOpencodePackageVersion(packageName) {
17014
17280
  }
17015
17281
  function readClaudeSettings() {
17016
17282
  try {
17017
- return JSON.parse((0, import_node_fs17.readFileSync)((0, import_node_path17.join)(process.cwd(), ".claude", "settings.json"), "utf8"));
17283
+ return JSON.parse((0, import_node_fs18.readFileSync)((0, import_node_path18.join)(process.cwd(), ".claude", "settings.json"), "utf8"));
17018
17284
  } catch {
17019
17285
  return null;
17020
17286
  }
@@ -17036,7 +17302,7 @@ function writeProjectInstallRecord(record) {
17036
17302
  const list = file.plugins[MMI_PLUGIN_ID] ?? [];
17037
17303
  list.push(record);
17038
17304
  file.plugins[MMI_PLUGIN_ID] = list;
17039
- (0, import_node_fs17.writeFileSync)(installedPluginsPath(), `${JSON.stringify(file, null, 2)}
17305
+ (0, import_node_fs18.writeFileSync)(installedPluginsPath(), `${JSON.stringify(file, null, 2)}
17040
17306
  `, "utf8");
17041
17307
  return true;
17042
17308
  } catch {
@@ -17049,9 +17315,9 @@ function backupAndWriteInstalledPlugins(records, pluginId) {
17049
17315
  if (!file) return false;
17050
17316
  if (!file.plugins) file.plugins = {};
17051
17317
  const path2 = installedPluginsPath();
17052
- (0, import_node_fs17.copyFileSync)(path2, `${path2}.bak`);
17318
+ (0, import_node_fs18.copyFileSync)(path2, `${path2}.bak`);
17053
17319
  file.plugins[pluginId] = records;
17054
- (0, import_node_fs17.writeFileSync)(path2, `${JSON.stringify(file, null, 2)}
17320
+ (0, import_node_fs18.writeFileSync)(path2, `${JSON.stringify(file, null, 2)}
17055
17321
  `, "utf8");
17056
17322
  return true;
17057
17323
  } catch {
@@ -17059,22 +17325,22 @@ function backupAndWriteInstalledPlugins(records, pluginId) {
17059
17325
  }
17060
17326
  }
17061
17327
  function opencodeConfigDir() {
17062
- return (0, import_node_path17.join)((0, import_node_os6.homedir)(), ".config", "opencode");
17328
+ return (0, import_node_path18.join)((0, import_node_os7.homedir)(), ".config", "opencode");
17063
17329
  }
17064
17330
  function opencodeConfigPath() {
17065
- return (0, import_node_path17.join)(opencodeConfigDir(), "opencode.jsonc");
17331
+ return (0, import_node_path18.join)(opencodeConfigDir(), "opencode.jsonc");
17066
17332
  }
17067
17333
  function opencodeCommandsDir() {
17068
- return (0, import_node_path17.join)(opencodeConfigDir(), "commands");
17334
+ return (0, import_node_path18.join)(opencodeConfigDir(), "commands");
17069
17335
  }
17070
17336
  function opencodeSkillsPath() {
17071
- return (0, import_node_path17.join)(opencodeConfigDir(), "node_modules", "@mutmutco", "opencode-mmi", "skills");
17337
+ return (0, import_node_path18.join)(opencodeConfigDir(), "node_modules", "@mutmutco", "opencode-mmi", "skills");
17072
17338
  }
17073
17339
  function opencodeConfigSnapshot() {
17074
17340
  const path2 = opencodeConfigPath();
17075
- if (!(0, import_node_fs17.existsSync)(path2)) return { path: path2, hasConfig: false, hasPluginField: false, parseOk: true };
17341
+ if (!(0, import_node_fs18.existsSync)(path2)) return { path: path2, hasConfig: false, hasPluginField: false, parseOk: true };
17076
17342
  try {
17077
- const raw = (0, import_node_fs17.readFileSync)(path2, "utf8");
17343
+ const raw = (0, import_node_fs18.readFileSync)(path2, "utf8");
17078
17344
  const parsed = JSON.parse(stripJsonc(raw));
17079
17345
  const hasPluginField = Object.prototype.hasOwnProperty.call(parsed, "plugin");
17080
17346
  const skillsPaths = Array.isArray(parsed.skills?.paths) ? parsed.skills.paths.filter((p) => typeof p === "string") : void 0;
@@ -17097,9 +17363,9 @@ function writeOpencodeConfigPlugin(snapshot) {
17097
17363
  const plan = planOpencodeConfigWrite(snapshot.hasConfig ? snapshot.raw : void 0);
17098
17364
  if (plan.action === "already") return true;
17099
17365
  if (!plan.text || plan.action === "unsafe") return false;
17100
- (0, import_node_fs17.mkdirSync)((0, import_node_path17.dirname)(path2), { recursive: true });
17101
- if (snapshot.hasConfig) (0, import_node_fs17.copyFileSync)(path2, `${path2}.bak`);
17102
- (0, import_node_fs17.writeFileSync)(path2, plan.text, "utf8");
17366
+ (0, import_node_fs18.mkdirSync)((0, import_node_path18.dirname)(path2), { recursive: true });
17367
+ if (snapshot.hasConfig) (0, import_node_fs18.copyFileSync)(path2, `${path2}.bak`);
17368
+ (0, import_node_fs18.writeFileSync)(path2, plan.text, "utf8");
17103
17369
  return true;
17104
17370
  } catch {
17105
17371
  return false;
@@ -17114,9 +17380,9 @@ function writeOpencodeSkillsPath(snapshot, skillsPath) {
17114
17380
  const normalized = skillsPath.replace(/\\/g, "/");
17115
17381
  if (!paths.some((p) => p.replace(/\\/g, "/") === normalized)) paths.push(skillsPath.replace(/\\/g, "/"));
17116
17382
  parsed.skills = { ...skills, paths };
17117
- (0, import_node_fs17.mkdirSync)((0, import_node_path17.dirname)(snapshot.path), { recursive: true });
17118
- if (snapshot.hasConfig && (0, import_node_fs17.existsSync)(snapshot.path)) (0, import_node_fs17.copyFileSync)(snapshot.path, `${snapshot.path}.bak`);
17119
- (0, import_node_fs17.writeFileSync)(snapshot.path, `${JSON.stringify(parsed, null, 2)}
17383
+ (0, import_node_fs18.mkdirSync)((0, import_node_path18.dirname)(snapshot.path), { recursive: true });
17384
+ if (snapshot.hasConfig && (0, import_node_fs18.existsSync)(snapshot.path)) (0, import_node_fs18.copyFileSync)(snapshot.path, `${snapshot.path}.bak`);
17385
+ (0, import_node_fs18.writeFileSync)(snapshot.path, `${JSON.stringify(parsed, null, 2)}
17120
17386
  `, "utf8");
17121
17387
  return true;
17122
17388
  } catch {
@@ -17125,7 +17391,7 @@ function writeOpencodeSkillsPath(snapshot, skillsPath) {
17125
17391
  }
17126
17392
  function opencodeExistingCommands() {
17127
17393
  try {
17128
- 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());
17394
+ 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());
17129
17395
  } catch {
17130
17396
  return [];
17131
17397
  }
@@ -17133,9 +17399,9 @@ function opencodeExistingCommands() {
17133
17399
  function writeOpencodeCommandFiles() {
17134
17400
  try {
17135
17401
  const dir = opencodeCommandsDir();
17136
- (0, import_node_fs17.mkdirSync)(dir, { recursive: true });
17402
+ (0, import_node_fs18.mkdirSync)(dir, { recursive: true });
17137
17403
  for (const command of OPENCODE_WORKFLOW_COMMANDS) {
17138
- (0, import_node_fs17.writeFileSync)((0, import_node_path17.join)(dir, `${command}.md`), opencodeCommandMarkdown(command), "utf8");
17404
+ (0, import_node_fs18.writeFileSync)((0, import_node_path18.join)(dir, `${command}.md`), opencodeCommandMarkdown(command), "utf8");
17139
17405
  }
17140
17406
  return true;
17141
17407
  } catch {
@@ -17144,12 +17410,12 @@ function writeOpencodeCommandFiles() {
17144
17410
  }
17145
17411
  function readOpencodeAdapterDiskVersion() {
17146
17412
  const candidates = [
17147
- (0, import_node_path17.join)(opencodeConfigDir(), "node_modules", "@mutmutco", "opencode-mmi", "package.json"),
17148
- (0, import_node_path17.join)((0, import_node_os6.homedir)(), ".cache", "opencode", "node_modules", "@mutmutco", "opencode-mmi", "package.json")
17413
+ (0, import_node_path18.join)(opencodeConfigDir(), "node_modules", "@mutmutco", "opencode-mmi", "package.json"),
17414
+ (0, import_node_path18.join)((0, import_node_os7.homedir)(), ".cache", "opencode", "node_modules", "@mutmutco", "opencode-mmi", "package.json")
17149
17415
  ];
17150
17416
  for (const path2 of candidates) {
17151
17417
  try {
17152
- const parsed = JSON.parse((0, import_node_fs17.readFileSync)(path2, "utf8"));
17418
+ const parsed = JSON.parse((0, import_node_fs18.readFileSync)(path2, "utf8"));
17153
17419
  if (typeof parsed.version === "string" && parsed.version.trim()) return parsed.version.trim();
17154
17420
  } catch {
17155
17421
  continue;
@@ -17165,7 +17431,7 @@ async function forceInstallOpencodeMmiPlugins(snapshot, log) {
17165
17431
  try {
17166
17432
  const specs = opencodeMmiPluginSpecs(snapshot);
17167
17433
  log(` \u21BB force-refreshing OpenCode MMI npm plugin(s): ${specs.join(", ")}\u2026`);
17168
- (0, import_node_fs17.mkdirSync)(opencodeConfigDir(), { recursive: true });
17434
+ (0, import_node_fs18.mkdirSync)(opencodeConfigDir(), { recursive: true });
17169
17435
  await runHostBin("npm", ["install", "--prefix", opencodeConfigDir(), "--force", ...specs], { timeout: NPM_UPDATE_TIMEOUT_MS });
17170
17436
  return true;
17171
17437
  } catch {
@@ -17173,12 +17439,12 @@ async function forceInstallOpencodeMmiPlugins(snapshot, log) {
17173
17439
  }
17174
17440
  }
17175
17441
  function opencodePackagesRoot() {
17176
- return (0, import_node_path17.join)((0, import_node_os6.homedir)(), ".cache", "opencode", "packages", "@mutmutco");
17442
+ return (0, import_node_path18.join)((0, import_node_os7.homedir)(), ".cache", "opencode", "packages", "@mutmutco");
17177
17443
  }
17178
17444
  function opencodePluginCacheDirs() {
17179
17445
  const root = opencodePackagesRoot();
17180
17446
  try {
17181
- 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));
17447
+ 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));
17182
17448
  } catch {
17183
17449
  return [];
17184
17450
  }
@@ -17186,7 +17452,7 @@ function opencodePluginCacheDirs() {
17186
17452
  function readOpencodeCacheDirVersion(cacheDir) {
17187
17453
  try {
17188
17454
  const parsed = JSON.parse(
17189
- (0, import_node_fs17.readFileSync)((0, import_node_path17.join)(cacheDir, "node_modules", "@mutmutco", "opencode-mmi", "package.json"), "utf8")
17455
+ (0, import_node_fs18.readFileSync)((0, import_node_path18.join)(cacheDir, "node_modules", "@mutmutco", "opencode-mmi", "package.json"), "utf8")
17190
17456
  );
17191
17457
  return typeof parsed.version === "string" && parsed.version.trim() ? parsed.version.trim() : void 0;
17192
17458
  } catch {
@@ -17209,9 +17475,9 @@ function quarantineStaleOpencodePluginCaches(releasedVersion, log) {
17209
17475
  });
17210
17476
  if (!plan.quarantine) continue;
17211
17477
  try {
17212
- const quarantineRoot = (0, import_node_path17.join)(opencodePackagesRoot(), ".mmi-quarantine", stamp);
17213
- (0, import_node_fs17.mkdirSync)(quarantineRoot, { recursive: true });
17214
- (0, import_node_fs17.renameSync)(cacheDir, (0, import_node_path17.join)(quarantineRoot, (0, import_node_path17.basename)(cacheDir)));
17478
+ const quarantineRoot = (0, import_node_path18.join)(opencodePackagesRoot(), ".mmi-quarantine", stamp);
17479
+ (0, import_node_fs18.mkdirSync)(quarantineRoot, { recursive: true });
17480
+ (0, import_node_fs18.renameSync)(cacheDir, (0, import_node_path18.join)(quarantineRoot, (0, import_node_path18.basename)(cacheDir)));
17215
17481
  log(` \u21BB quarantined stale OpenCode plugin cache ${plan.cacheVersion} (< ${plan.releasedVersion}) \u2014 OpenCode reinstalls the current adapter on next start`);
17216
17482
  moved = true;
17217
17483
  } catch {
@@ -17237,30 +17503,30 @@ function opencodePluginVersionsForReport() {
17237
17503
  }
17238
17504
  function opencodeDesktopLogsRoot() {
17239
17505
  if (process.platform === "win32") {
17240
- const base = process.env.APPDATA || (0, import_node_path17.join)((0, import_node_os6.homedir)(), "AppData", "Roaming");
17241
- return (0, import_node_path17.join)(base, "ai.opencode.desktop", "logs");
17506
+ const base = process.env.APPDATA || (0, import_node_path18.join)((0, import_node_os7.homedir)(), "AppData", "Roaming");
17507
+ return (0, import_node_path18.join)(base, "ai.opencode.desktop", "logs");
17242
17508
  }
17243
17509
  if (process.platform === "darwin") {
17244
- return (0, import_node_path17.join)((0, import_node_os6.homedir)(), "Library", "Application Support", "ai.opencode.desktop", "logs");
17510
+ return (0, import_node_path18.join)((0, import_node_os7.homedir)(), "Library", "Application Support", "ai.opencode.desktop", "logs");
17245
17511
  }
17246
- return (0, import_node_path17.join)((0, import_node_os6.homedir)(), ".config", "ai.opencode.desktop", "logs");
17512
+ return (0, import_node_path18.join)((0, import_node_os7.homedir)(), ".config", "ai.opencode.desktop", "logs");
17247
17513
  }
17248
17514
  function opencodeDesktopBootstrapSnapshot() {
17249
17515
  const root = opencodeDesktopLogsRoot();
17250
17516
  try {
17251
- 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);
17517
+ 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);
17252
17518
  const newest = sessionDirs[0];
17253
17519
  if (!newest) return [];
17254
- const logPath = (0, import_node_path17.join)(newest, "renderer.log");
17255
- const text = (0, import_node_fs17.readFileSync)(logPath, "utf8");
17256
- return opencodeAgentDirectoriesFromLog(text).filter((directory) => !(0, import_node_fs17.existsSync)(directory)).map((directory) => ({ directory, logPath }));
17520
+ const logPath = (0, import_node_path18.join)(newest, "renderer.log");
17521
+ const text = (0, import_node_fs18.readFileSync)(logPath, "utf8");
17522
+ return opencodeAgentDirectoriesFromLog(text).filter((directory) => !(0, import_node_fs18.existsSync)(directory)).map((directory) => ({ directory, logPath }));
17257
17523
  } catch {
17258
17524
  return [];
17259
17525
  }
17260
17526
  }
17261
17527
  function opencodeLegacyConfigSnapshot() {
17262
- const legacyPath = (0, import_node_path17.join)((0, import_node_os6.homedir)(), ".opencode", "opencode.json");
17263
- if (!(0, import_node_fs17.existsSync)(legacyPath)) return {};
17528
+ const legacyPath = (0, import_node_path18.join)((0, import_node_os7.homedir)(), ".opencode", "opencode.json");
17529
+ if (!(0, import_node_fs18.existsSync)(legacyPath)) return {};
17264
17530
  const content = readTextFile(legacyPath);
17265
17531
  if (content == null) return {};
17266
17532
  const plugins = parseOpencodeLegacyConfigPlugins(content);
@@ -17272,45 +17538,80 @@ function opencodeLegacyConfigSnapshot() {
17272
17538
  function quarantineOpencodeLegacyConfig(legacyPath) {
17273
17539
  try {
17274
17540
  const backupPath = `${legacyPath}.bak`;
17275
- if ((0, import_node_fs17.existsSync)(backupPath)) return false;
17276
- (0, import_node_fs17.renameSync)(legacyPath, backupPath);
17541
+ if ((0, import_node_fs18.existsSync)(backupPath)) return false;
17542
+ (0, import_node_fs18.renameSync)(legacyPath, backupPath);
17277
17543
  return true;
17278
17544
  } catch {
17279
17545
  return false;
17280
17546
  }
17281
17547
  }
17282
17548
  function cursorPluginCacheRoot() {
17283
- return (0, import_node_path17.join)((0, import_node_os6.homedir)(), ".cursor", "plugins", "cache", "mutmutco", "mmi");
17549
+ return (0, import_node_path18.join)((0, import_node_os7.homedir)(), ".cursor", "plugins", "cache", "mutmutco", "mmi");
17550
+ }
17551
+ function cursorLogsRoot() {
17552
+ if (process.platform === "win32") return (0, import_node_path18.join)((0, import_node_os7.homedir)(), "AppData", "Roaming", "Cursor", "logs");
17553
+ if (process.platform === "darwin") return (0, import_node_path18.join)((0, import_node_os7.homedir)(), "Library", "Application Support", "Cursor", "logs");
17554
+ return (0, import_node_path18.join)((0, import_node_os7.homedir)(), ".config", "Cursor", "logs");
17555
+ }
17556
+ function safeReaddirNames(dir) {
17557
+ try {
17558
+ return (0, import_node_fs18.readdirSync)(dir, { withFileTypes: true }).map((e) => e.name);
17559
+ } catch {
17560
+ return [];
17561
+ }
17562
+ }
17563
+ function readCursorPluginLogText() {
17564
+ const root = cursorLogsRoot();
17565
+ const sessions = safeReaddirNames(root).sort().reverse();
17566
+ for (const session of sessions.slice(0, 5)) {
17567
+ const sessionDir = (0, import_node_path18.join)(root, session);
17568
+ const texts = [];
17569
+ for (const win of safeReaddirNames(sessionDir).filter((n) => n.startsWith("window"))) {
17570
+ const dir = (0, import_node_path18.join)(sessionDir, win, "exthost", "anysphere.cursor-agent-exec");
17571
+ for (const file of safeReaddirNames(dir)) {
17572
+ if (!/^Cursor Plugins.*\.log$/i.test(file)) continue;
17573
+ const path2 = (0, import_node_path18.join)(dir, file);
17574
+ try {
17575
+ texts.push({ text: (0, import_node_fs18.readFileSync)(path2, "utf8"), mtime: (0, import_node_fs18.statSync)(path2).mtimeMs });
17576
+ } catch {
17577
+ }
17578
+ }
17579
+ }
17580
+ if (texts.length) {
17581
+ return texts.sort((a, b) => b.mtime - a.mtime).map((t) => t.text).join("\n");
17582
+ }
17583
+ }
17584
+ return void 0;
17284
17585
  }
17285
17586
  function cursorPluginCachePinSnapshots() {
17286
17587
  const root = cursorPluginCacheRoot();
17287
17588
  try {
17288
- return (0, import_node_fs17.readdirSync)(root, { withFileTypes: true }).filter((entry) => entry.isDirectory() && !entry.name.startsWith(".")).map((entry) => {
17289
- const path2 = (0, import_node_path17.join)(root, entry.name);
17290
- const pluginJson = (0, import_node_path17.join)(path2, ".cursor-plugin", "plugin.json");
17291
- const hooksJson = (0, import_node_path17.join)(path2, "hooks", "hooks.json");
17292
- const cliBundle = (0, import_node_path17.join)(path2, "cli", "dist", "index.cjs");
17293
- const cursorHook = (0, import_node_path17.join)(path2, "scripts", "cursor-hook.mjs");
17589
+ return (0, import_node_fs18.readdirSync)(root, { withFileTypes: true }).filter((entry) => entry.isDirectory() && !entry.name.startsWith(".")).map((entry) => {
17590
+ const path2 = (0, import_node_path18.join)(root, entry.name);
17591
+ const pluginJson = (0, import_node_path18.join)(path2, ".cursor-plugin", "plugin.json");
17592
+ const hooksJson = (0, import_node_path18.join)(path2, "hooks", "hooks.json");
17593
+ const cliBundle = (0, import_node_path18.join)(path2, "cli", "dist", "index.cjs");
17594
+ const cursorHook = (0, import_node_path18.join)(path2, "scripts", "cursor-hook.mjs");
17294
17595
  let version;
17295
17596
  try {
17296
- const raw = JSON.parse((0, import_node_fs17.readFileSync)(pluginJson, "utf8"));
17597
+ const raw = JSON.parse((0, import_node_fs18.readFileSync)(pluginJson, "utf8"));
17297
17598
  version = typeof raw.version === "string" ? raw.version : void 0;
17298
17599
  } catch {
17299
17600
  version = void 0;
17300
17601
  }
17301
17602
  let isEmpty = true;
17302
17603
  try {
17303
- isEmpty = (0, import_node_fs17.readdirSync)(path2).length === 0;
17604
+ isEmpty = (0, import_node_fs18.readdirSync)(path2).length === 0;
17304
17605
  } catch {
17305
17606
  isEmpty = true;
17306
17607
  }
17307
17608
  return {
17308
17609
  name: entry.name,
17309
17610
  path: path2,
17310
- hasPluginJson: (0, import_node_fs17.existsSync)(pluginJson),
17311
- hasHooksJson: (0, import_node_fs17.existsSync)(hooksJson),
17312
- hasCliBundle: (0, import_node_fs17.existsSync)(cliBundle),
17313
- hasCursorHookScript: (0, import_node_fs17.existsSync)(cursorHook),
17611
+ hasPluginJson: (0, import_node_fs18.existsSync)(pluginJson),
17612
+ hasHooksJson: (0, import_node_fs18.existsSync)(hooksJson),
17613
+ hasCliBundle: (0, import_node_fs18.existsSync)(cliBundle),
17614
+ hasCursorHookScript: (0, import_node_fs18.existsSync)(cursorHook),
17314
17615
  isEmpty,
17315
17616
  version
17316
17617
  };
@@ -17320,19 +17621,19 @@ function cursorPluginCachePinSnapshots() {
17320
17621
  }
17321
17622
  }
17322
17623
  function hubCheckoutForCursorSeed() {
17323
- const manifest = (0, import_node_path17.join)(process.cwd(), "plugins", "mmi", ".cursor-plugin", "plugin.json");
17324
- return (0, import_node_fs17.existsSync)(manifest) ? process.cwd() : void 0;
17624
+ const manifest = (0, import_node_path18.join)(process.cwd(), "plugins", "mmi", ".cursor-plugin", "plugin.json");
17625
+ return (0, import_node_fs18.existsSync)(manifest) ? process.cwd() : void 0;
17325
17626
  }
17326
17627
  function mmiPluginCacheRootSnapshots() {
17327
17628
  const roots = [
17328
- { surface: "claude", root: (0, import_node_path17.join)((0, import_node_os6.homedir)(), ".claude", "plugins", "cache", "mutmutco", "mmi") },
17329
- { surface: "codex", root: (0, import_node_path17.join)((0, import_node_os6.homedir)(), ".codex", "plugins", "cache", "mutmutco", "mmi") }
17629
+ { surface: "claude", root: (0, import_node_path18.join)((0, import_node_os7.homedir)(), ".claude", "plugins", "cache", "mutmutco", "mmi") },
17630
+ { surface: "codex", root: (0, import_node_path18.join)((0, import_node_os7.homedir)(), ".codex", "plugins", "cache", "mutmutco", "mmi") }
17330
17631
  ];
17331
17632
  return roots.flatMap(({ surface, root }) => {
17332
17633
  try {
17333
- const entries = (0, import_node_fs17.readdirSync)(root, { withFileTypes: true }).map((entry) => ({
17634
+ const entries = (0, import_node_fs18.readdirSync)(root, { withFileTypes: true }).map((entry) => ({
17334
17635
  name: entry.name,
17335
- path: (0, import_node_path17.join)(root, entry.name),
17636
+ path: (0, import_node_path18.join)(root, entry.name),
17336
17637
  isDirectory: entry.isDirectory()
17337
17638
  }));
17338
17639
  return [{ surface, root, entries }];
@@ -17343,7 +17644,7 @@ function mmiPluginCacheRootSnapshots() {
17343
17644
  }
17344
17645
  function hasNestedMmiChild(versionDir) {
17345
17646
  try {
17346
- return (0, import_node_fs17.statSync)((0, import_node_path17.join)(versionDir, "mmi")).isDirectory();
17647
+ return (0, import_node_fs18.statSync)((0, import_node_path18.join)(versionDir, "mmi")).isDirectory();
17347
17648
  } catch {
17348
17649
  return false;
17349
17650
  }
@@ -17354,10 +17655,10 @@ function nestedPluginTreeSnapshot() {
17354
17655
  );
17355
17656
  }
17356
17657
  function uniqueQuarantineTarget(path2) {
17357
- if (!(0, import_node_fs17.existsSync)(path2)) return path2;
17658
+ if (!(0, import_node_fs18.existsSync)(path2)) return path2;
17358
17659
  for (let i = 1; i < 100; i += 1) {
17359
17660
  const candidate = `${path2}-${i}`;
17360
- if (!(0, import_node_fs17.existsSync)(candidate)) return candidate;
17661
+ if (!(0, import_node_fs18.existsSync)(candidate)) return candidate;
17361
17662
  }
17362
17663
  return `${path2}-${Date.now()}`;
17363
17664
  }
@@ -17366,10 +17667,10 @@ function quarantinePluginCacheDirs(plan) {
17366
17667
  const failed = [];
17367
17668
  for (const move of plan) {
17368
17669
  try {
17369
- if (!(0, import_node_fs17.existsSync)(move.from)) continue;
17670
+ if (!(0, import_node_fs18.existsSync)(move.from)) continue;
17370
17671
  const target = uniqueQuarantineTarget(move.to);
17371
- (0, import_node_fs17.mkdirSync)((0, import_node_path17.dirname)(target), { recursive: true });
17372
- (0, import_node_fs17.renameSync)(move.from, target);
17672
+ (0, import_node_fs18.mkdirSync)((0, import_node_path18.dirname)(target), { recursive: true });
17673
+ (0, import_node_fs18.renameSync)(move.from, target);
17373
17674
  moved += 1;
17374
17675
  } catch {
17375
17676
  failed.push(move);
@@ -17388,23 +17689,23 @@ async function robocopyMirrorEmpty(emptyDir, target) {
17388
17689
  }
17389
17690
  async function clearNestedPluginTreeDir(targetPath) {
17390
17691
  try {
17391
- if (!(0, import_node_fs17.existsSync)(targetPath)) return true;
17692
+ if (!(0, import_node_fs18.existsSync)(targetPath)) return true;
17392
17693
  if (isWin) {
17393
- const emptyDir = (0, import_node_path17.join)((0, import_node_os6.tmpdir)(), `mmi-empty-${Date.now()}`);
17394
- (0, import_node_fs17.mkdirSync)(emptyDir, { recursive: true });
17694
+ const emptyDir = (0, import_node_path18.join)((0, import_node_os7.tmpdir)(), `mmi-empty-${Date.now()}`);
17695
+ (0, import_node_fs18.mkdirSync)(emptyDir, { recursive: true });
17395
17696
  try {
17396
17697
  await robocopyMirrorEmpty(emptyDir, targetPath);
17397
- (0, import_node_fs17.rmSync)(targetPath, { recursive: true, force: true });
17698
+ (0, import_node_fs18.rmSync)(targetPath, { recursive: true, force: true });
17398
17699
  } finally {
17399
17700
  try {
17400
- (0, import_node_fs17.rmSync)(emptyDir, { recursive: true, force: true });
17701
+ (0, import_node_fs18.rmSync)(emptyDir, { recursive: true, force: true });
17401
17702
  } catch {
17402
17703
  }
17403
17704
  }
17404
- return !(0, import_node_fs17.existsSync)(targetPath);
17705
+ return !(0, import_node_fs18.existsSync)(targetPath);
17405
17706
  }
17406
- (0, import_node_fs17.rmSync)(targetPath, { recursive: true, force: true });
17407
- return !(0, import_node_fs17.existsSync)(targetPath);
17707
+ (0, import_node_fs18.rmSync)(targetPath, { recursive: true, force: true });
17708
+ return !(0, import_node_fs18.existsSync)(targetPath);
17408
17709
  } catch {
17409
17710
  return false;
17410
17711
  }
@@ -17417,95 +17718,129 @@ async function applyNestedPluginTreeCleanup(paths, log) {
17417
17718
  }
17418
17719
  return true;
17419
17720
  }
17420
- var gitignorePath = () => (0, import_node_path17.join)(process.cwd(), ".gitignore");
17721
+ var gitignorePath = () => (0, import_node_path18.join)(process.cwd(), ".gitignore");
17421
17722
  function readTextFile(path2) {
17422
17723
  try {
17423
- if (!(0, import_node_fs17.existsSync)(path2)) return null;
17424
- return (0, import_node_fs17.readFileSync)(path2, "utf8");
17724
+ if (!(0, import_node_fs18.existsSync)(path2)) return null;
17725
+ return (0, import_node_fs18.readFileSync)(path2, "utf8");
17425
17726
  } catch {
17426
17727
  return null;
17427
17728
  }
17428
17729
  }
17429
17730
  function mcpDirExists(path2) {
17430
17731
  try {
17431
- return (0, import_node_fs17.existsSync)(path2);
17732
+ return (0, import_node_fs18.existsSync)(path2);
17432
17733
  } catch {
17433
17734
  return false;
17434
17735
  }
17435
17736
  }
17436
17737
  function mcpConfigTargets() {
17437
17738
  const cwd = process.cwd();
17438
- const home = (0, import_node_os6.homedir)();
17439
- const cursorProjectDir = (0, import_node_path17.join)(cwd, ".cursor");
17440
- const cursorUserDir = (0, import_node_path17.join)(home, ".cursor");
17441
- const codexDir = (0, import_node_path17.join)(home, ".codex");
17739
+ const home = (0, import_node_os7.homedir)();
17740
+ const cursorProjectDir = (0, import_node_path18.join)(cwd, ".cursor");
17741
+ const cursorUserDir = (0, import_node_path18.join)(home, ".cursor");
17742
+ const codexDir = (0, import_node_path18.join)(home, ".codex");
17442
17743
  return [
17443
17744
  // Claude Code project MCP — reconciled if present, never conjured (org seeds .cursor/mcp.json, not this).
17444
17745
  {
17445
17746
  host: "claude-code",
17446
17747
  label: "Claude Code project MCP",
17447
- path: (0, import_node_path17.join)(cwd, ".mcp.json"),
17748
+ path: (0, import_node_path18.join)(cwd, ".mcp.json"),
17448
17749
  format: "json",
17449
17750
  present: true,
17450
17751
  // parent is the repo root (cwd); the caller gates the whole reconcile on isOrgRepo
17451
17752
  isRepoFile: true,
17452
17753
  createWhenFileAbsent: false,
17453
- content: readTextFile((0, import_node_path17.join)(cwd, ".mcp.json"))
17754
+ content: readTextFile((0, import_node_path18.join)(cwd, ".mcp.json"))
17454
17755
  },
17455
17756
  // Cursor project MCP — the bootstrap-seeded surface; restored if its .cursor dir exists but the file is gone.
17456
17757
  {
17457
17758
  host: "cursor-project",
17458
17759
  label: "Cursor project MCP",
17459
- path: (0, import_node_path17.join)(cursorProjectDir, "mcp.json"),
17760
+ path: (0, import_node_path18.join)(cursorProjectDir, "mcp.json"),
17460
17761
  format: "json",
17461
17762
  present: mcpDirExists(cursorProjectDir),
17462
17763
  isRepoFile: true,
17463
17764
  createWhenFileAbsent: true,
17464
- content: readTextFile((0, import_node_path17.join)(cursorProjectDir, "mcp.json"))
17765
+ content: readTextFile((0, import_node_path18.join)(cursorProjectDir, "mcp.json"))
17465
17766
  },
17466
17767
  // Cursor user MCP — global; written only when Cursor is installed (~/.cursor exists).
17467
17768
  {
17468
17769
  host: "cursor-user",
17469
17770
  label: "Cursor user MCP",
17470
- path: (0, import_node_path17.join)(cursorUserDir, "mcp.json"),
17771
+ path: (0, import_node_path18.join)(cursorUserDir, "mcp.json"),
17471
17772
  format: "json",
17472
17773
  present: mcpDirExists(cursorUserDir),
17473
17774
  isRepoFile: false,
17474
17775
  createWhenFileAbsent: true,
17475
- content: readTextFile((0, import_node_path17.join)(cursorUserDir, "mcp.json"))
17776
+ content: readTextFile((0, import_node_path18.join)(cursorUserDir, "mcp.json"))
17476
17777
  },
17477
17778
  // Codex user config (TOML) — global; written only when Codex is installed (~/.codex exists).
17478
17779
  {
17479
17780
  host: "codex",
17480
17781
  label: "Codex user config",
17481
- path: (0, import_node_path17.join)(codexDir, "config.toml"),
17782
+ path: (0, import_node_path18.join)(codexDir, "config.toml"),
17482
17783
  format: "toml",
17483
17784
  present: mcpDirExists(codexDir),
17484
17785
  isRepoFile: false,
17485
17786
  createWhenFileAbsent: true,
17486
- content: readTextFile((0, import_node_path17.join)(codexDir, "config.toml"))
17787
+ content: readTextFile((0, import_node_path18.join)(codexDir, "config.toml"))
17487
17788
  }
17488
17789
  ];
17489
17790
  }
17490
17791
  function writeMcpConfigFile(path2, content) {
17491
17792
  try {
17492
- (0, import_node_fs17.writeFileSync)(path2, content, "utf8");
17793
+ (0, import_node_fs18.writeFileSync)(path2, content, "utf8");
17493
17794
  return true;
17494
17795
  } catch {
17495
17796
  return false;
17496
17797
  }
17497
17798
  }
17799
+ var CLAUDE_CLI_MCP_LABEL = "Claude Code MCP store (user scope)";
17800
+ var CLAUDE_CLI_MCP_PATH = "claude mcp (user scope)";
17801
+ async function claudeCliMcpTarget(spec) {
17802
+ const base = {
17803
+ host: "claude-cli",
17804
+ label: CLAUDE_CLI_MCP_LABEL,
17805
+ path: CLAUDE_CLI_MCP_PATH,
17806
+ format: "claude-cli",
17807
+ isRepoFile: false,
17808
+ createWhenFileAbsent: true
17809
+ };
17810
+ if (!await hostBinAvailable("claude")) return { ...base, present: false, content: null };
17811
+ for (const name of [spec.name, ...spec.legacyNames]) {
17812
+ try {
17813
+ const { stdout } = await runHostBin("claude", ["mcp", "get", name], { timeout: CLAUDE_PLUGIN_TIMEOUT_MS });
17814
+ return { ...base, present: true, content: stdout, claudeCliMatchedName: name };
17815
+ } catch {
17816
+ }
17817
+ }
17818
+ return { ...base, present: true, content: null };
17819
+ }
17820
+ async function runClaudeCliMcpCommands(commands) {
17821
+ for (const cmd of commands) {
17822
+ try {
17823
+ await runHostBin("claude", ["mcp", ...cmd.args], { timeout: CLAUDE_PLUGIN_TIMEOUT_MS });
17824
+ } catch {
17825
+ return false;
17826
+ }
17827
+ }
17828
+ return true;
17829
+ }
17498
17830
  async function reconcileMcpRegistrations(input) {
17499
17831
  if (!input.isOrgRepo) return buildMcpReconcileCheck([]);
17500
- const targets = mcpConfigTargets();
17832
+ const snapshot = async () => [...mcpConfigTargets(), await claudeCliMcpTarget(PLAYWRIGHT_MCP_SPEC)];
17833
+ const targets = await snapshot();
17501
17834
  if (!input.apply) return buildMcpReconcileCheck(planMcpReconcile(targets, PLAYWRIGHT_MCP_SPEC));
17502
- const results = applyMcpReconcile(targets, PLAYWRIGHT_MCP_SPEC, {
17835
+ const fileTargets = targets.filter((t) => t.format !== "claude-cli");
17836
+ const results = applyMcpReconcile(fileTargets, PLAYWRIGHT_MCP_SPEC, {
17503
17837
  repoWritesAllowed: input.repoWritesAllowed,
17504
17838
  write: writeMcpConfigFile
17505
17839
  });
17506
17840
  for (const r of results) {
17507
17841
  if (r.outcome === "wrote") {
17508
17842
  input.onHealed();
17843
+ input.onReloadRequired();
17509
17844
  const verb = r.action === "install" ? "registered" : "reconciled";
17510
17845
  input.log(` \u21BB ${verb} Playwright MCP in ${r.target.path} \u2014 restart the host to load it`);
17511
17846
  } else if (r.outcome === "unparseable") {
@@ -17514,13 +17849,28 @@ async function reconcileMcpRegistrations(input) {
17514
17849
  input.log(` \u26A0 ${r.target.label}: failed to write ${r.target.path}`);
17515
17850
  }
17516
17851
  }
17517
- return buildMcpReconcileCheck(planMcpReconcile(mcpConfigTargets(), PLAYWRIGHT_MCP_SPEC));
17852
+ const claudeTarget = targets.find((t) => t.format === "claude-cli");
17853
+ if (claudeTarget != null) {
17854
+ const item = detectMcpState(claudeTarget, PLAYWRIGHT_MCP_SPEC);
17855
+ if (item.action === "install" || item.action === "reconcile") {
17856
+ const commands = planClaudeCliMcpHeal(item, PLAYWRIGHT_MCP_SPEC);
17857
+ if (await runClaudeCliMcpCommands(commands)) {
17858
+ input.onHealed();
17859
+ input.onReloadRequired();
17860
+ const verb = item.action === "install" ? "registered" : "reconciled";
17861
+ input.log(` \u21BB ${verb} Playwright MCP via \`claude mcp add\` (${CLAUDE_CLI_MCP_PATH}) \u2014 restart Claude Code to load it`);
17862
+ } else {
17863
+ input.log(` \u26A0 ${CLAUDE_CLI_MCP_LABEL}: \`claude mcp add\` failed for ${PLAYWRIGHT_MCP_SPEC.name} \u2014 run it by hand`);
17864
+ }
17865
+ }
17866
+ }
17867
+ return buildMcpReconcileCheck(planMcpReconcile(await snapshot(), PLAYWRIGHT_MCP_SPEC));
17518
17868
  }
17519
17869
  function strayBrowserArtifactPaths() {
17520
17870
  const cwd = process.cwd();
17521
17871
  return STRAY_BROWSER_ARTIFACT_DIRS.filter((rel) => {
17522
17872
  try {
17523
- return (0, import_node_fs17.existsSync)((0, import_node_path17.join)(cwd, rel));
17873
+ return (0, import_node_fs18.existsSync)((0, import_node_path18.join)(cwd, rel));
17524
17874
  } catch {
17525
17875
  return false;
17526
17876
  }
@@ -17528,14 +17878,14 @@ function strayBrowserArtifactPaths() {
17528
17878
  }
17529
17879
  function readGitignore() {
17530
17880
  try {
17531
- return (0, import_node_fs17.readFileSync)(gitignorePath(), "utf8");
17881
+ return (0, import_node_fs18.readFileSync)(gitignorePath(), "utf8");
17532
17882
  } catch {
17533
17883
  return null;
17534
17884
  }
17535
17885
  }
17536
17886
  function writeGitignore(content) {
17537
17887
  try {
17538
- (0, import_node_fs17.writeFileSync)(gitignorePath(), content, "utf8");
17888
+ (0, import_node_fs18.writeFileSync)(gitignorePath(), content, "utf8");
17539
17889
  return true;
17540
17890
  } catch {
17541
17891
  return false;
@@ -17553,7 +17903,7 @@ async function runDoctor(opts, io = consoleIo, readOrigin) {
17553
17903
  const checks = [];
17554
17904
  const REWRITE_KEY = "url.https://github.com/.insteadOf";
17555
17905
  const CLONE_FIX = 'run: git config --global url."https://github.com/".insteadOf "git@github.com:"';
17556
- const [login, pathProbe, releasedVersion, cfg, callerArn, cloneProbe, isOrgRepo] = await Promise.all([
17906
+ const [login, pathProbe, releasedVersionResolution, cfg, callerArn, cloneProbe, isOrgRepo] = await Promise.all([
17557
17907
  githubLogin(),
17558
17908
  execFileP2(isWin ? "where" : "which", ["mmi-cli"]).then(() => true).catch(() => false),
17559
17909
  fetchReleasedVersion(),
@@ -17565,6 +17915,7 @@ async function runDoctor(opts, io = consoleIo, readOrigin) {
17565
17915
  // not the now-always-defaulted `cfg.sagaApiUrl`. Independent git read — resolved concurrently here.
17566
17916
  readOrigin ? isOrgRepoRoot(readOrigin) : isOrgRepoRoot()
17567
17917
  ]);
17918
+ const releasedVersion = releasedVersionResolution.version;
17568
17919
  const surface = detectSurface(process.env);
17569
17920
  const versionReportProbe = buildVersionLagReport({
17570
17921
  currentVersion: resolveClientVersion(),
@@ -17576,7 +17927,7 @@ async function runDoctor(opts, io = consoleIo, readOrigin) {
17576
17927
  const semverPrefix = /^\d+\.\d+\.\d+/;
17577
17928
  const isBehind = (installed2, released) => Boolean(installed2 && released && semverPrefix.test(installed2) && semverPrefix.test(released) && compareVersions(installed2, released) < 0);
17578
17929
  const opencodeAdapterStale = isBehind(opencodeInstalledVersionForDoctor(), releasedVersion);
17579
- const cursorCacheStale = (0, import_node_fs17.existsSync)(cursorPluginCacheRoot()) && (cursorPluginCachePinSnapshots() ?? []).some((p) => isBehind(p.version, releasedVersion));
17930
+ const cursorCacheStale = (0, import_node_fs18.existsSync)(cursorPluginCacheRoot()) && (cursorPluginCachePinSnapshots() ?? []).some((p) => isBehind(p.version, releasedVersion));
17580
17931
  const healPlan = doctorHealPlan({
17581
17932
  isOrgRepo,
17582
17933
  surface,
@@ -17611,7 +17962,7 @@ async function runDoctor(opts, io = consoleIo, readOrigin) {
17611
17962
  let onPath = pathProbe;
17612
17963
  if (!onPath) {
17613
17964
  const root = process.env.CLAUDE_PLUGIN_ROOT;
17614
- if (root && (0, import_node_fs17.existsSync)(`${root}/bin/mmi-cli${isWin ? ".cmd" : ""}`)) onPath = true;
17965
+ if (root && (0, import_node_fs18.existsSync)(`${root}/bin/mmi-cli${isWin ? ".cmd" : ""}`)) onPath = true;
17615
17966
  }
17616
17967
  checks.push({ ok: onPath, label: "mmi-cli on PATH", fix: "auto-provisioned at session start \u2014 reopen the session, or install the MMI plugin" });
17617
17968
  const reloadHint = reloadAction(surface);
@@ -17630,7 +17981,7 @@ async function runDoctor(opts, io = consoleIo, readOrigin) {
17630
17981
  });
17631
17982
  let selfUpdatedCli = false;
17632
17983
  if (repairFull && !process.env[DOCTOR_POST_SELF_UPDATE_ENV]) {
17633
- const updated = await applyVersionAutoUpdate(versionReport, (m) => io.err(m));
17984
+ const updated = await applyVersionAutoUpdate(versionReport, (m) => io.err(m), releasedVersionResolution.source);
17634
17985
  versionReport = updated.report;
17635
17986
  selfUpdatedCli = updated.applied === "npm";
17636
17987
  }
@@ -17758,7 +18109,7 @@ async function runDoctor(opts, io = consoleIo, readOrigin) {
17758
18109
  checks.push(gitignoreCheck);
17759
18110
  checks.push(buildRepoLocalWorktreeCheck({
17760
18111
  isOrgRepo,
17761
- hasRepoLocalWorktrees: (0, import_node_fs17.existsSync)((0, import_node_path17.join)(process.cwd(), ".worktrees"))
18112
+ hasRepoLocalWorktrees: (0, import_node_fs18.existsSync)((0, import_node_path18.join)(process.cwd(), ".worktrees"))
17762
18113
  }));
17763
18114
  let driftCheck = buildPluginConfigDriftCheck({ isOrgRepo, installed, surface });
17764
18115
  if (!driftCheck.ok && driftCheck.recordsToWrite && repairLocal) {
@@ -17978,15 +18329,16 @@ async function runDoctor(opts, io = consoleIo, readOrigin) {
17978
18329
  };
17979
18330
  }
17980
18331
  checks.push(cacheCleanupCheck);
18332
+ const canDriveCodex = surfaceToken(surface) === "codex" || await hostBinAvailable("codex");
17981
18333
  let codexActiveCacheCheck = buildCodexActiveCacheCheck({
17982
18334
  isOrgRepo,
17983
18335
  releasedVersion,
17984
18336
  codexCacheVersions: codexCacheVersions(),
17985
18337
  codexRecordVersion: codexRecordVersion(),
17986
- codexActiveVersion: await codexActiveVersion()
18338
+ codexActiveVersion: await codexActiveVersion(),
18339
+ codexPresent: canDriveCodex
17987
18340
  });
17988
18341
  if (!codexActiveCacheCheck.ok && repairFull) {
17989
- const canDriveCodex = surfaceToken(surface) === "codex" || await hostBinAvailable("codex");
17990
18342
  if (canDriveCodex && await applyPluginHeal("codex", surface, (m) => io.err(m), { force: true })) {
17991
18343
  markPluginReloadRequired();
17992
18344
  markHealed();
@@ -17996,7 +18348,8 @@ async function runDoctor(opts, io = consoleIo, readOrigin) {
17996
18348
  releasedVersion,
17997
18349
  codexCacheVersions: codexCacheVersions(),
17998
18350
  codexRecordVersion: codexRecordVersion(),
17999
- codexActiveVersion: await codexActiveVersion()
18351
+ codexActiveVersion: await codexActiveVersion(),
18352
+ codexPresent: canDriveCodex
18000
18353
  });
18001
18354
  }
18002
18355
  }
@@ -18046,7 +18399,7 @@ async function runDoctor(opts, io = consoleIo, readOrigin) {
18046
18399
  }
18047
18400
  checks.push(nestedPluginTreeCheck);
18048
18401
  const cursorCacheRoot = cursorPluginCacheRoot();
18049
- const cursorCacheRootExists = (0, import_node_fs17.existsSync)(cursorCacheRoot);
18402
+ const cursorCacheRootExists = (0, import_node_fs18.existsSync)(cursorCacheRoot);
18050
18403
  let cursorPins = cursorPluginCachePinSnapshots() ?? [];
18051
18404
  checks.push(
18052
18405
  buildCursorPluginCacheCleanupCheck({
@@ -18074,7 +18427,7 @@ async function runDoctor(opts, io = consoleIo, readOrigin) {
18074
18427
  cacheRootExists: cursorCacheRootExists,
18075
18428
  hubCheckout: hubCheckoutForCursorSeed(),
18076
18429
  execFileP: execFileP2,
18077
- mkdtemp: (prefix) => (0, import_promises2.mkdtemp)((0, import_node_path17.join)((0, import_node_os6.tmpdir)(), prefix)),
18430
+ mkdtemp: (prefix) => (0, import_promises2.mkdtemp)((0, import_node_path18.join)((0, import_node_os7.tmpdir)(), prefix)),
18078
18431
  log: (m) => io.err(m)
18079
18432
  });
18080
18433
  if (seeded) {
@@ -18107,6 +18460,11 @@ async function runDoctor(opts, io = consoleIo, readOrigin) {
18107
18460
  }
18108
18461
  }
18109
18462
  checks.push(cursorPluginCheck);
18463
+ if (!opts.banner) {
18464
+ const cursorLogText = readCursorPluginLogText();
18465
+ const enabledPinSignal = cursorLogText ? parseCursorPluginLog(cursorLogText) : {};
18466
+ checks.push(buildCursorEnabledPinCheck({ isOrgRepo, pins: cursorPins, ...enabledPinSignal }));
18467
+ }
18110
18468
  const cursorThirdPartyEnabled = await readCursorThirdPartyExtensibilityEnabled(execFileP2);
18111
18469
  checks.push(
18112
18470
  buildCursorThirdPartyExtensibilityCheck({
@@ -18130,7 +18488,8 @@ async function runDoctor(opts, io = consoleIo, readOrigin) {
18130
18488
  apply: Boolean(opts.apply),
18131
18489
  repoWritesAllowed,
18132
18490
  log: (m) => io.err(m),
18133
- onHealed: markHealed
18491
+ onHealed: markHealed,
18492
+ onReloadRequired: markPluginReloadRequired
18134
18493
  })
18135
18494
  );
18136
18495
  }
@@ -18204,7 +18563,8 @@ async function runDoctor(opts, io = consoleIo, readOrigin) {
18204
18563
  codexPluginVersions: sourceVersions("codex"),
18205
18564
  codexCacheVersions: cacheVersionsFor("codex"),
18206
18565
  opencodePluginVersions: opencodePluginVersionsForReport(),
18207
- releasedVersion
18566
+ releasedVersion,
18567
+ releasedVersionSource: releasedVersionResolution.source
18208
18568
  });
18209
18569
  }
18210
18570
  });
@@ -18262,29 +18622,33 @@ function mergeGuardHook(settings) {
18262
18622
  next.hooks = hooks;
18263
18623
  return next;
18264
18624
  }
18265
- var userScopeSettingsPath = (surface = detectSurface(process.env)) => (0, import_node_path17.join)((0, import_node_os6.homedir)(), surface === "codex" ? ".codex" : ".claude", "settings.json");
18625
+ var userScopeSettingsPath = (surface = detectSurface(process.env)) => (0, import_node_path18.join)((0, import_node_os7.homedir)(), surface === "codex" ? ".codex" : ".claude", "settings.json");
18266
18626
  function ensureUserScopeGuardHook(opts = {}) {
18267
18627
  const path2 = opts.settingsPath ?? userScopeSettingsPath();
18268
18628
  try {
18269
18629
  let current = null;
18270
- if ((0, import_node_fs17.existsSync)(path2)) {
18630
+ if ((0, import_node_fs18.existsSync)(path2)) {
18271
18631
  try {
18272
- current = JSON.parse((0, import_node_fs17.readFileSync)(path2, "utf8"));
18632
+ current = JSON.parse((0, import_node_fs18.readFileSync)(path2, "utf8"));
18273
18633
  } catch {
18274
18634
  return "failed";
18275
18635
  }
18276
18636
  }
18277
18637
  if (settingsHasGuardHook(current)) return "already";
18278
18638
  const merged = mergeGuardHook(current);
18279
- (0, import_node_fs17.mkdirSync)((0, import_node_path17.dirname)(path2), { recursive: true });
18280
- if ((0, import_node_fs17.existsSync)(path2)) (0, import_node_fs17.copyFileSync)(path2, `${path2}.bak`);
18281
- (0, import_node_fs17.writeFileSync)(path2, `${JSON.stringify(merged, null, 2)}
18639
+ (0, import_node_fs18.mkdirSync)((0, import_node_path18.dirname)(path2), { recursive: true });
18640
+ if ((0, import_node_fs18.existsSync)(path2)) (0, import_node_fs18.copyFileSync)(path2, `${path2}.bak`);
18641
+ (0, import_node_fs18.writeFileSync)(path2, `${JSON.stringify(merged, null, 2)}
18282
18642
  `, "utf8");
18283
18643
  return "written";
18284
18644
  } catch {
18285
18645
  return "failed";
18286
18646
  }
18287
18647
  }
18648
+ function guardRecoveryCommand(surface) {
18649
+ const token = surfaceToken(surface);
18650
+ return token ? PLUGIN_SURFACE_HEAL[token]?.guardRecovery : void 0;
18651
+ }
18288
18652
  async function runGuard(opts = {}, readOrigin) {
18289
18653
  void opts;
18290
18654
  try {
@@ -18292,7 +18656,7 @@ async function runGuard(opts = {}, readOrigin) {
18292
18656
  const isOrgRepo = readOrigin ? await isOrgRepoRoot(readOrigin) : await isOrgRepoRoot();
18293
18657
  const input = snapshotPluginGuardInput(surface, isOrgRepo);
18294
18658
  const { state } = buildPluginGuardDecision(input);
18295
- const { line, exitCode } = buildGuardSessionStartLine(state);
18659
+ const { line, exitCode } = buildGuardSessionStartLine(state, { recovery: guardRecoveryCommand(surface) });
18296
18660
  if (line) console.error(line);
18297
18661
  process.exitCode = exitCode;
18298
18662
  } catch {
@@ -18412,7 +18776,7 @@ async function applyGcPlan(plan, remote) {
18412
18776
  cleanupBranch: (branch, expectedHeadOid) => cleanupPrMergeLocalBranch(branch.branch, {
18413
18777
  beforeWorktrees,
18414
18778
  startingPath: branch.worktreePath,
18415
- pathExists: (p) => (0, import_node_fs18.existsSync)(p),
18779
+ pathExists: (p) => (0, import_node_fs19.existsSync)(p),
18416
18780
  execGit: async (args) => (await execFileP2("git", args, { timeout: GIT_TIMEOUT_MS })).stdout,
18417
18781
  teardownWorktreeStage,
18418
18782
  deferredStore,
@@ -18435,7 +18799,7 @@ async function applyGcPlan(plan, remote) {
18435
18799
  for (const wt of plan.worktreeDirs) {
18436
18800
  try {
18437
18801
  const cleanupTarget = resolveSafeSiblingWorktreeCleanupTarget(wt.path, siblingRoot, {
18438
- realpath: (path2) => (0, import_node_fs18.realpathSync)(path2)
18802
+ realpath: (path2) => (0, import_node_fs19.realpathSync)(path2)
18439
18803
  });
18440
18804
  if (!cleanupTarget.ok) {
18441
18805
  result.failed.push(`${wt.path}: ${cleanupTarget.reason}`);
@@ -18478,13 +18842,13 @@ var program2 = new Command();
18478
18842
  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)");
18479
18843
  var rules = program2.command("rules").description("org-managed .gitignore delivery");
18480
18844
  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) => {
18481
- const path2 = (0, import_node_path18.join)(process.cwd(), ".gitignore");
18482
- const current = (0, import_node_fs18.existsSync)(path2) ? (0, import_node_fs18.readFileSync)(path2, "utf8") : null;
18845
+ const path2 = (0, import_node_path19.join)(process.cwd(), ".gitignore");
18846
+ const current = (0, import_node_fs19.existsSync)(path2) ? (0, import_node_fs19.readFileSync)(path2, "utf8") : null;
18483
18847
  const plan = planManagedGitignore(current);
18484
18848
  const drift = [...plan.added.map((l) => `+${l}`), ...plan.removed.map((l) => `-${l}`)].join(", ") || "block normalize";
18485
18849
  if (opts.write) {
18486
18850
  if (plan.changed) {
18487
- (0, import_node_fs18.writeFileSync)(path2, plan.content, "utf8");
18851
+ (0, import_node_fs19.writeFileSync)(path2, plan.content, "utf8");
18488
18852
  console.log(`mmi-cli rules gitignore: updated .gitignore (${drift})`);
18489
18853
  } else {
18490
18854
  console.log("mmi-cli rules gitignore: up to date");
@@ -18626,8 +18990,8 @@ function runWorktreeInstall(command, cwd, quiet) {
18626
18990
  const [bin, ...args] = command.split(" ");
18627
18991
  const file = isWin2 ? "cmd.exe" : bin;
18628
18992
  const spawnArgs = isWin2 ? ["/c", bin, ...args] : args;
18629
- return new Promise((resolve6, reject) => {
18630
- const child = (0, import_node_child_process11.spawn)(file, spawnArgs, { cwd, stdio: quiet ? "ignore" : "inherit", windowsHide: true });
18993
+ return new Promise((resolve5, reject) => {
18994
+ const child = (0, import_node_child_process12.spawn)(file, spawnArgs, { cwd, stdio: quiet ? "ignore" : "inherit", windowsHide: true });
18631
18995
  const timer = setTimeout(() => {
18632
18996
  try {
18633
18997
  child.kill();
@@ -18641,7 +19005,7 @@ function runWorktreeInstall(command, cwd, quiet) {
18641
19005
  });
18642
19006
  child.on("exit", (code) => {
18643
19007
  clearTimeout(timer);
18644
- if (code === 0) resolve6();
19008
+ if (code === 0) resolve5();
18645
19009
  else reject(new Error(`${command} exited ${code} in ${cwd}`));
18646
19010
  });
18647
19011
  });
@@ -18649,7 +19013,7 @@ function runWorktreeInstall(command, cwd, quiet) {
18649
19013
  async function primaryCheckoutRoot(worktreeRoot) {
18650
19014
  try {
18651
19015
  const out = (await execFileP2("git", ["-C", worktreeRoot, "rev-parse", "--path-format=absolute", "--git-common-dir"], { timeout: GIT_TIMEOUT_MS })).stdout.trim();
18652
- return out ? (0, import_node_path18.dirname)(out) : void 0;
19016
+ return out ? (0, import_node_path19.dirname)(out) : void 0;
18653
19017
  } catch {
18654
19018
  return void 0;
18655
19019
  }
@@ -18664,26 +19028,26 @@ function makeProvisionDeps(worktreeRoot, quiet, log) {
18664
19028
  function acquireWorktreeSetupLock(worktreeRoot) {
18665
19029
  const lockPath = repoRuntimeStatePath(worktreeRoot, "worktree-setup.lock");
18666
19030
  const take = () => {
18667
- const fd = (0, import_node_fs18.openSync)(lockPath, "wx");
19031
+ const fd = (0, import_node_fs19.openSync)(lockPath, "wx");
18668
19032
  try {
18669
- (0, import_node_fs18.writeSync)(fd, String(Date.now()));
19033
+ (0, import_node_fs19.writeSync)(fd, String(Date.now()));
18670
19034
  } finally {
18671
- (0, import_node_fs18.closeSync)(fd);
19035
+ (0, import_node_fs19.closeSync)(fd);
18672
19036
  }
18673
19037
  return () => {
18674
19038
  try {
18675
- (0, import_node_fs18.rmSync)(lockPath, { force: true });
19039
+ (0, import_node_fs19.rmSync)(lockPath, { force: true });
18676
19040
  } catch {
18677
19041
  }
18678
19042
  };
18679
19043
  };
18680
19044
  try {
18681
- (0, import_node_fs18.mkdirSync)((0, import_node_path18.dirname)(lockPath), { recursive: true });
19045
+ (0, import_node_fs19.mkdirSync)((0, import_node_path19.dirname)(lockPath), { recursive: true });
18682
19046
  return take();
18683
19047
  } catch {
18684
19048
  try {
18685
- if (Date.now() - (0, import_node_fs18.statSync)(lockPath).mtimeMs > WORKTREE_SETUP_LOCK_TTL_MS) {
18686
- (0, import_node_fs18.rmSync)(lockPath, { force: true });
19049
+ if (Date.now() - (0, import_node_fs19.statSync)(lockPath).mtimeMs > WORKTREE_SETUP_LOCK_TTL_MS) {
19050
+ (0, import_node_fs19.rmSync)(lockPath, { force: true });
18687
19051
  return take();
18688
19052
  }
18689
19053
  } catch {
@@ -18822,7 +19186,7 @@ function scheduleRelatedDiscovery(o) {
18822
19186
  try {
18823
19187
  const args = ["issue", "discover-related", "--number", String(o.number), "--title", o.title, "--body", o.body];
18824
19188
  if (o.repo) args.push("--repo", o.repo);
18825
- (0, import_node_child_process11.spawn)(process.execPath, [process.argv[1], ...args], {
19189
+ (0, import_node_child_process12.spawn)(process.execPath, [process.argv[1], ...args], {
18826
19190
  detached: true,
18827
19191
  stdio: "ignore",
18828
19192
  windowsHide: true,
@@ -18915,8 +19279,8 @@ tenant.command("sweep-rc").description("discover (and optionally retire) running
18915
19279
  async function resolveDnsBounded(host, timeoutMs = 3e3) {
18916
19280
  const { lookup } = await import("node:dns/promises");
18917
19281
  const probe = lookup(host).then(() => true).catch((e) => dnsErrorToResolution(e?.code));
18918
- const timeout = new Promise((resolve6) => {
18919
- setTimeout(() => resolve6(void 0), timeoutMs).unref?.();
19282
+ const timeout = new Promise((resolve5) => {
19283
+ setTimeout(() => resolve5(void 0), timeoutMs).unref?.();
18920
19284
  });
18921
19285
  return Promise.race([probe, timeout]);
18922
19286
  }
@@ -19178,7 +19542,7 @@ project.command("set [owner/repo]").description("upsert project META (idempotent
19178
19542
  const vars = rawValues("--var");
19179
19543
  if (o.secretsFile) {
19180
19544
  try {
19181
- vars.push(`secrets=${(0, import_node_fs18.readFileSync)(o.secretsFile, "utf8")}`);
19545
+ vars.push(`secrets=${(0, import_node_fs19.readFileSync)(o.secretsFile, "utf8")}`);
19182
19546
  } catch (e) {
19183
19547
  return fail(`project set: cannot read --secrets-file ${o.secretsFile}: ${e.message}`);
19184
19548
  }
@@ -19466,18 +19830,20 @@ issue.command("create").description("create an issue (type \u2192 label) and pri
19466
19830
  ...parentLinkFields(parent, parentLinkError)
19467
19831
  }));
19468
19832
  });
19469
- 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) => {
19833
+ 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) => {
19470
19834
  const n = Number(number);
19471
19835
  if (!Number.isInteger(n) || n <= 0) return fail("issue view: <number> must be a positive integer");
19472
19836
  const repo = await resolveRepo(o.repo);
19473
19837
  if (!repo) return fail("issue view: could not resolve repo (pass --repo <owner/repo>)");
19474
- const fields = o.json && o.json.trim() ? o.json.trim() : "number,title,state,url,labels,author,assignees,milestone,body";
19838
+ const fields = normalizeIssueViewJsonFields(o.json);
19475
19839
  try {
19476
19840
  const data = await ghJson(["issue", "view", String(n), "--repo", repo, "--json", fields]);
19477
19841
  console.log(JSON.stringify(data));
19478
19842
  } catch (e) {
19479
19843
  const err = e;
19480
- return fail(`issue view: ${(err.stderr || err.message || String(e)).trim()}`);
19844
+ const raw = (err.stderr || err.message || String(e)).trim();
19845
+ const argvNote = o.json ? ` (received --json argv: ${JSON.stringify(o.json)}, normalized to "${fields}")` : "";
19846
+ return fail(`issue view: ${raw}${argvNote}`);
19481
19847
  }
19482
19848
  });
19483
19849
  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) => {
@@ -19746,9 +20112,9 @@ pr.command("view <number>").description("read a PR as structured JSON (merged st
19746
20112
  }
19747
20113
  });
19748
20114
  async function listCiWorkflowPaths(cwd = process.cwd()) {
19749
- const wfDir = (0, import_node_path18.join)(cwd, ".github", "workflows");
19750
- if (!(0, import_node_fs18.existsSync)(wfDir)) return [];
19751
- return (0, import_node_fs18.readdirSync)(wfDir).filter((name) => /\.(ya?ml)$/i.test(name)).map((name) => `.github/workflows/${name}`);
20115
+ const wfDir = (0, import_node_path19.join)(cwd, ".github", "workflows");
20116
+ if (!(0, import_node_fs19.existsSync)(wfDir)) return [];
20117
+ return (0, import_node_fs19.readdirSync)(wfDir).filter((name) => /\.(ya?ml)$/i.test(name)).map((name) => `.github/workflows/${name}`);
19752
20118
  }
19753
20119
  async function resolveMergeCiPolicyForCheckout(repoOpt) {
19754
20120
  const repo = repoOpt ?? await resolveRepo();
@@ -19767,7 +20133,7 @@ function ciAuditDeps() {
19767
20133
  // Continuous CI delivery (#1550): the gate re-seed renders from the Hub's on-disk seed templates. The
19768
20134
  // reconcile runs IN the Hub checkout, so this is local-file I/O (no network fetch). Path is relative to
19769
20135
  // the repo root (e.g. skills/bootstrap/seeds/gate.template.yml).
19770
- readSeedFile: (path2) => (0, import_node_fs18.existsSync)(path2) ? (0, import_node_fs18.readFileSync)(path2, "utf8") : null
20136
+ readSeedFile: (path2) => (0, import_node_fs19.existsSync)(path2) ? (0, import_node_fs19.readFileSync)(path2, "utf8") : null
19771
20137
  };
19772
20138
  }
19773
20139
  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) => {
@@ -19797,7 +20163,7 @@ pr.command("checks-wait <number>").description("bounded wait for PR checks; skip
19797
20163
  const result = await waitForPrChecks({
19798
20164
  resolvePolicy: () => resolveMergeCiPolicyForCheckout(o.repo),
19799
20165
  pollChecks: () => pollGhPrChecks(number, repoArgs),
19800
- sleep: (ms) => new Promise((resolve6) => setTimeout(resolve6, ms))
20166
+ sleep: (ms) => new Promise((resolve5) => setTimeout(resolve5, ms))
19801
20167
  });
19802
20168
  if (o.json) printLine(JSON.stringify(result));
19803
20169
  else printLine(`pr checks-wait: ${result.status}${result.reason ? ` \u2014 ${result.reason}` : ""}${result.detail ? ` (${result.detail})` : ""}`);
@@ -19824,7 +20190,7 @@ pr.command("land <number>").description("agent merge path (#1440): train probe \
19824
20190
  waitForChecks: (prNumber, repo) => waitForPrChecks({
19825
20191
  resolvePolicy: () => resolveRepoMergeCiPolicy(repo, ciAuditDeps()),
19826
20192
  pollChecks: () => pollGhPrChecks(prNumber, repo ? ["--repo", repo] : []),
19827
- sleep: (ms) => new Promise((resolve6) => setTimeout(resolve6, ms))
20193
+ sleep: (ms) => new Promise((resolve5) => setTimeout(resolve5, ms))
19828
20194
  }),
19829
20195
  // #2425: re-probe used ONLY to decide whether a failed `gh pr merge --auto` is worth retrying — a
19830
20196
  // fresh read of state/mergeable/checks, independent of the merge call's own (possibly stale) error.
@@ -19875,7 +20241,7 @@ pr.command("land <number>").description("agent merge path (#1440): train probe \
19875
20241
  while (Date.now() < deadlineMs) {
19876
20242
  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 });
19877
20243
  if (stateRead.ok && stateRead.state === "MERGED") return true;
19878
- await new Promise((resolve6) => setTimeout(resolve6, PR_LAND_POLL_MS));
20244
+ await new Promise((resolve5) => setTimeout(resolve5, PR_LAND_POLL_MS));
19879
20245
  }
19880
20246
  return false;
19881
20247
  }
@@ -19919,7 +20285,7 @@ async function remoteBranchExists2(branch, options = {}) {
19919
20285
  }
19920
20286
  var COMPOSE_TIMEOUT_MS = 12e4;
19921
20287
  function spawnDeferredGcSweep() {
19922
- spawnDetachedSelf(["gc", "sweep-deferred", "--quiet"], { spawn: import_node_child_process11.spawn, execPath: process.execPath, scriptPath: process.argv[1] });
20288
+ spawnDetachedSelf(["gc", "sweep-deferred", "--quiet"], { spawn: import_node_child_process12.spawn, execPath: process.execPath, scriptPath: process.argv[1] });
19923
20289
  }
19924
20290
  async function createDeferredWorktreeStore() {
19925
20291
  try {
@@ -19935,7 +20301,7 @@ async function createDeferredWorktreeStore() {
19935
20301
  },
19936
20302
  write: async (entries) => {
19937
20303
  try {
19938
- await (0, import_promises3.mkdir)((0, import_node_path18.dirname)(registryPath), { recursive: true });
20304
+ await (0, import_promises3.mkdir)((0, import_node_path19.dirname)(registryPath), { recursive: true });
19939
20305
  await (0, import_promises3.writeFile)(registryPath, serializeDeferredWorktrees(entries), "utf8");
19940
20306
  } catch {
19941
20307
  }
@@ -19949,13 +20315,13 @@ var realWorktreeDirRemover = {
19949
20315
  probe: (p) => {
19950
20316
  let st;
19951
20317
  try {
19952
- st = (0, import_node_fs18.lstatSync)(p);
20318
+ st = (0, import_node_fs19.lstatSync)(p);
19953
20319
  } catch {
19954
20320
  return null;
19955
20321
  }
19956
20322
  if (st.isSymbolicLink()) return "link";
19957
20323
  try {
19958
- (0, import_node_fs18.readlinkSync)(p);
20324
+ (0, import_node_fs19.readlinkSync)(p);
19959
20325
  return "link";
19960
20326
  } catch {
19961
20327
  }
@@ -19963,7 +20329,7 @@ var realWorktreeDirRemover = {
19963
20329
  },
19964
20330
  readdir: (p) => {
19965
20331
  try {
19966
- return (0, import_node_fs18.readdirSync)(p);
20332
+ return (0, import_node_fs19.readdirSync)(p);
19967
20333
  } catch {
19968
20334
  return [];
19969
20335
  }
@@ -19972,9 +20338,9 @@ var realWorktreeDirRemover = {
19972
20338
  // leaving the target); a file symlink with unlink. rmdir first, fall back to unlink.
19973
20339
  detachLink: (p) => {
19974
20340
  try {
19975
- (0, import_node_fs18.rmdirSync)(p);
20341
+ (0, import_node_fs19.rmdirSync)(p);
19976
20342
  } catch {
19977
- (0, import_node_fs18.unlinkSync)(p);
20343
+ (0, import_node_fs19.unlinkSync)(p);
19978
20344
  }
19979
20345
  },
19980
20346
  removeTree: (p) => (0, import_promises3.rm)(p, { recursive: true, force: true, maxRetries: 5, retryDelay: 200 })
@@ -19989,7 +20355,7 @@ async function resolvePrimaryCheckout(execGit) {
19989
20355
  function worktreeRemoveDeps(execGit) {
19990
20356
  return {
19991
20357
  git: execGit,
19992
- sleep: (ms) => new Promise((resolve6) => setTimeout(resolve6, ms)),
20358
+ sleep: (ms) => new Promise((resolve5) => setTimeout(resolve5, ms)),
19993
20359
  removeWorktreeDir: async (worktreePath) => removeWorktreeTree(worktreePath, await resolvePrimaryCheckout(execGit), realWorktreeDirRemover)
19994
20360
  };
19995
20361
  }
@@ -20004,9 +20370,9 @@ async function worktreeHasStageState(worktreePath) {
20004
20370
  }
20005
20371
  }
20006
20372
  function stageStateFileBelongsToWorktree(statePath, worktreePath) {
20007
- if (!(0, import_node_fs18.existsSync)(statePath)) return false;
20373
+ if (!(0, import_node_fs19.existsSync)(statePath)) return false;
20008
20374
  try {
20009
- const state = JSON.parse((0, import_node_fs18.readFileSync)(statePath, "utf8"));
20375
+ const state = JSON.parse((0, import_node_fs19.readFileSync)(statePath, "utf8"));
20010
20376
  const recordedCwd = typeof state.identity?.cwd === "string" ? state.identity.cwd : typeof state.cwd === "string" ? state.cwd : "";
20011
20377
  return Boolean(recordedCwd && isPathUnderDirectory(recordedCwd, worktreePath));
20012
20378
  } catch {
@@ -20072,7 +20438,7 @@ pr.command("merge <number>").description("merge a PR (squash by default) and cle
20072
20438
  localCleanup = await cleanupPrMergeLocalBranch(headRef, {
20073
20439
  beforeWorktrees,
20074
20440
  startingPath,
20075
- pathExists: (p) => (0, import_node_fs18.existsSync)(p),
20441
+ pathExists: (p) => (0, import_node_fs19.existsSync)(p),
20076
20442
  execGit: async (args) => (await execFileP2("git", args, { timeout: GIT_TIMEOUT_MS })).stdout,
20077
20443
  teardownWorktreeStage,
20078
20444
  deferredStore,
@@ -20293,7 +20659,7 @@ function stageScopedRunOpts(o) {
20293
20659
  };
20294
20660
  }
20295
20661
  function printLine(value) {
20296
- (0, import_node_fs18.writeSync)(1, `${value}
20662
+ (0, import_node_fs19.writeSync)(1, `${value}
20297
20663
  `);
20298
20664
  }
20299
20665
  function stageKeepAlive() {
@@ -20307,8 +20673,8 @@ async function resolveStage() {
20307
20673
  const portRange = portRangeMeta && typeof portRangeMeta.start === "number" && typeof portRangeMeta.end === "number" ? [portRangeMeta.start, portRangeMeta.end] : void 0;
20308
20674
  return decideStage({
20309
20675
  registry: { deployModel: project2?.deployModel, portRange, error: read.ok ? void 0 : read.error },
20310
- hasCompose: (0, import_node_fs18.existsSync)((0, import_node_path18.join)(process.cwd(), "docker-compose.yml")),
20311
- hasEnvExample: (0, import_node_fs18.existsSync)((0, import_node_path18.join)(process.cwd(), ".env.example"))
20676
+ hasCompose: (0, import_node_fs19.existsSync)((0, import_node_path19.join)(process.cwd(), "docker-compose.yml")),
20677
+ hasEnvExample: (0, import_node_fs19.existsSync)((0, import_node_path19.join)(process.cwd(), ".env.example"))
20312
20678
  });
20313
20679
  }
20314
20680
  async function fetchStageVaultEnvMerge() {
@@ -20741,6 +21107,22 @@ ${r.repo}: applied=[${r.applied.join("; ")}] skipped=[${r.skipped.join("; ")}]${
20741
21107
  }
20742
21108
  if (!audit.ok) process.exitCode = 1;
20743
21109
  });
21110
+ var wiki = program2.command("wiki").description("release wiki lane \u2014 publish generated pages via a short-lived, repo-scoped minted token");
21111
+ 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) => {
21112
+ const repo = await resolveRepo(o.repo);
21113
+ if (!repo) return fail("wiki publish: could not determine the target repo (pass --repo owner/name)");
21114
+ const cfg = await loadConfig();
21115
+ const ok = await wikiPublish(
21116
+ {
21117
+ mint: (r) => mintWikiToken(r, registryClientDeps(cfg)),
21118
+ publish: (o2) => publishWikiViaGit(realWikiGitPublishDeps(), o2),
21119
+ log: (msg) => console.log(msg),
21120
+ err: (msg) => console.error(msg)
21121
+ },
21122
+ { repo, pagesDir }
21123
+ );
21124
+ if (!ok) process.exitCode = 1;
21125
+ });
20744
21126
  var bootstrap = program2.command("bootstrap").description("plan repo bootstrap operations; mutations require master-admin approval").option("--repo <owner/repo>", "target repo").option("--class <class>", "deployable | content", "deployable").option("--json", "machine-readable output").option("--apply", "reserved for future bootstrap execution after explicit master-admin approval").action((o) => {
20745
21127
  if (!o.repo) return fail("bootstrap: required option --repo <owner/repo> not specified");
20746
21128
  if (o.apply) return fail("bootstrap: execution is not implemented yet; use the dry-run plan and the existing /bootstrap skill");
@@ -20760,7 +21142,7 @@ bootstrap.command("verify <repo>").description("audit whether an existing repo i
20760
21142
  client: defaultGitHubClient(),
20761
21143
  projectMeta: meta,
20762
21144
  deployModel: typeof meta?.deployModel === "string" ? meta.deployModel : void 0,
20763
- readLocalFile: (path2) => path2 === "projects.json" && apiProjects != null ? apiProjects : (0, import_node_fs18.existsSync)(path2) ? (0, import_node_fs18.readFileSync)(path2, "utf8") : null,
21145
+ readLocalFile: (path2) => path2 === "projects.json" && apiProjects != null ? apiProjects : (0, import_node_fs19.existsSync)(path2) ? (0, import_node_fs19.readFileSync)(path2, "utf8") : null,
20764
21146
  // requiredGcpApis is stored as an array by a JSON write, but `project set --var KEY=VALUE` stores a raw
20765
21147
  // comma-string — accept either so the seeded value verifies regardless of how it was written.
20766
21148
  requiredGcpApis: (() => {
@@ -20811,12 +21193,12 @@ bootstrap.command("apply <repo>").description("idempotent seed apply from skills
20811
21193
  return fail(`bootstrap apply: ${e.message}`);
20812
21194
  }
20813
21195
  const manifestPath = "skills/bootstrap/seeds/manifest.json";
20814
- if (!(0, import_node_fs18.existsSync)(manifestPath)) return fail(`bootstrap apply: ${manifestPath} not found; run from the MMI-Hub repo root`);
20815
- const manifest = loadBootstrapSeeds((0, import_node_fs18.readFileSync)(manifestPath, "utf8"));
21196
+ if (!(0, import_node_fs19.existsSync)(manifestPath)) return fail(`bootstrap apply: ${manifestPath} not found; run from the MMI-Hub repo root`);
21197
+ const manifest = loadBootstrapSeeds((0, import_node_fs19.readFileSync)(manifestPath, "utf8"));
20816
21198
  const baseBranch = o.class === "content" ? "main" : "development";
20817
21199
  const slug = parsedRepo.slug;
20818
21200
  const gh = async (args) => execFileP2("gh", args, { timeout: 2e4 });
20819
- const readFile2 = (p) => (0, import_node_fs18.existsSync)(p) ? (0, import_node_fs18.readFileSync)(p, "utf8") : null;
21201
+ const readFile2 = (p) => (0, import_node_fs19.existsSync)(p) ? (0, import_node_fs19.readFileSync)(p, "utf8") : null;
20820
21202
  const enc = (p) => p.split("/").map(encodeURIComponent).join("/");
20821
21203
  const rawVars = {};
20822
21204
  for (const value of rawValues("--var")) {
@@ -21113,16 +21495,16 @@ access.command("audit").description("audit collaborator roles + train-branch pus
21113
21495
  const repoClass = o.class;
21114
21496
  targets = [{ repo: o.repo, class: repoClass, releaseTrack: repoClass === "content" ? "trunk" : resolveReleaseTrack(meta, void 0, o.repo) }];
21115
21497
  } else {
21116
- const projectsJson = registryProjects ? JSON.stringify({ projects: registryProjects }) : (0, import_node_fs18.existsSync)("projects.json") ? (0, import_node_fs18.readFileSync)("projects.json", "utf8") : null;
21498
+ const projectsJson = registryProjects ? JSON.stringify({ projects: registryProjects }) : (0, import_node_fs19.existsSync)("projects.json") ? (0, import_node_fs19.readFileSync)("projects.json", "utf8") : null;
21117
21499
  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>");
21118
- const fanoutJson = (0, import_node_fs18.existsSync)(".github/fanout-targets.json") ? (0, import_node_fs18.readFileSync)(".github/fanout-targets.json", "utf8") : null;
21500
+ const fanoutJson = (0, import_node_fs19.existsSync)(".github/fanout-targets.json") ? (0, import_node_fs19.readFileSync)(".github/fanout-targets.json", "utf8") : null;
21119
21501
  targets = loadAccessTargets(projectsJson, fanoutJson);
21120
21502
  }
21121
21503
  const derivedMatrix = registryProjects ? accessMatrixFromProjects(registryProjects) : {};
21122
- const fileMatrix = (0, import_node_fs18.existsSync)("access-matrix.json") ? loadAccessMatrix((0, import_node_fs18.readFileSync)("access-matrix.json", "utf8")) : {};
21504
+ const fileMatrix = (0, import_node_fs19.existsSync)("access-matrix.json") ? loadAccessMatrix((0, import_node_fs19.readFileSync)("access-matrix.json", "utf8")) : {};
21123
21505
  const matrix = mergeAccessMatrix(fileMatrix, derivedMatrix);
21124
21506
  const derivedContracts = registryProjects ? dataAccessContractsFromProjects(registryProjects) : { consumers: {} };
21125
- const fileContracts = (0, import_node_fs18.existsSync)("data-access-contracts.json") ? loadDataAccessContracts((0, import_node_fs18.readFileSync)("data-access-contracts.json", "utf8")) : { consumers: {} };
21507
+ const fileContracts = (0, import_node_fs19.existsSync)("data-access-contracts.json") ? loadDataAccessContracts((0, import_node_fs19.readFileSync)("data-access-contracts.json", "utf8")) : { consumers: {} };
21126
21508
  const dataAccess = mergeDataAccessContracts(fileContracts, derivedContracts);
21127
21509
  const report = await auditOrgAccess(targets, deps, matrix, dataAccess);
21128
21510
  console.log(o.json ? JSON.stringify(report, null, 2) : renderAccessReport(report));
@@ -21159,7 +21541,7 @@ program2.command("session-start").description("run the SessionStart verbs (whoam
21159
21541
  readBoard,
21160
21542
  // #1813: warm the slice cache out-of-band (detached) so the ~20s live read
21161
21543
  // never costs banner time and next session's glance renders instantly within budget.
21162
- scheduleRefresh: () => spawnDetachedSelf(["board", "slice-refresh", "--quiet"], { spawn: import_node_child_process11.spawn, execPath: process.execPath, scriptPath: process.argv[1] })
21544
+ scheduleRefresh: () => spawnDetachedSelf(["board", "slice-refresh", "--quiet"], { spawn: import_node_child_process12.spawn, execPath: process.execPath, scriptPath: process.argv[1] })
21163
21545
  }),
21164
21546
  doctor: (io) => runDoctor({ banner: true }, io)
21165
21547
  });
@@ -21167,7 +21549,7 @@ program2.command("session-start").description("run the SessionStart verbs (whoam
21167
21549
  for (const line of scratchGcLines(process.cwd())) consoleIo.log(line);
21168
21550
  const worktreeBanner = worktreeAutoProvisionBanner(process.cwd());
21169
21551
  if (worktreeBanner) {
21170
- spawnDetachedSelf(["worktree", "setup", "--quiet"], { spawn: import_node_child_process11.spawn, execPath: process.execPath, scriptPath: process.argv[1] });
21552
+ spawnDetachedSelf(["worktree", "setup", "--quiet"], { spawn: import_node_child_process12.spawn, execPath: process.execPath, scriptPath: process.argv[1] });
21171
21553
  consoleIo.log(worktreeBanner);
21172
21554
  }
21173
21555
  });