@mutmutco/cli 2.54.1 → 2.55.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/main.cjs CHANGED
@@ -3408,7 +3408,7 @@ var program = new Command();
3408
3408
 
3409
3409
  // src/index.ts
3410
3410
  var import_promises8 = require("node:fs/promises");
3411
- var import_node_fs28 = require("node:fs");
3411
+ var import_node_fs29 = require("node:fs");
3412
3412
 
3413
3413
  // src/rules-sync.ts
3414
3414
  function normalizeEol(s) {
@@ -5594,7 +5594,7 @@ async function restoreDirtyOrgSpineToHead(deps, options = {}) {
5594
5594
  }
5595
5595
 
5596
5596
  // src/index.ts
5597
- var import_node_child_process13 = require("node:child_process");
5597
+ var import_node_child_process15 = require("node:child_process");
5598
5598
 
5599
5599
  // src/cli-shared.ts
5600
5600
  var import_promises = require("node:fs/promises");
@@ -9955,14 +9955,564 @@ function registerCoopCommands(program3) {
9955
9955
  });
9956
9956
  }
9957
9957
 
9958
- // src/throttle-commands.ts
9958
+ // src/overlord.ts
9959
9959
  var import_node_child_process8 = require("node:child_process");
9960
- var import_node_fs14 = require("node:fs");
9960
+ var import_node_fs15 = require("node:fs");
9961
+ var import_node_os3 = require("node:os");
9961
9962
  var import_node_path13 = require("node:path");
9962
- var THROTTLE_TRACE_REL = (0, import_node_path13.join)(".mmi", "throttle", "trace.jsonl");
9963
+
9964
+ // src/atomic-write.ts
9965
+ var import_node_fs14 = require("node:fs");
9966
+ function atomicWriteFileSync(path2, content) {
9967
+ const tmp = `${path2}.${process.pid}.tmp`;
9968
+ (0, import_node_fs14.writeFileSync)(tmp, content, "utf8");
9969
+ (0, import_node_fs14.renameSync)(tmp, path2);
9970
+ }
9971
+
9972
+ // src/overlord.ts
9973
+ var OVERLORD_DEFAULT_COUNT = 3;
9974
+ var OVERLORD_MIN_COUNT = 3;
9975
+ var OVERLORD_MAX_COUNT = 6;
9976
+ var GENERIC_STOP_NAMES = /* @__PURE__ */ new Set([
9977
+ "windowsterminal",
9978
+ "windows terminal",
9979
+ "pwsh",
9980
+ "powershell",
9981
+ "opencode",
9982
+ "codex",
9983
+ "codex-fugu"
9984
+ ]);
9985
+ function numericCountArg(arg) {
9986
+ const match = /^--([0-9]+)$/.exec(arg);
9987
+ return match ? Number(match[1]) : void 0;
9988
+ }
9989
+ function isoNow(now = () => /* @__PURE__ */ new Date()) {
9990
+ return now().toISOString();
9991
+ }
9992
+ function defaultRunId() {
9993
+ return `overlord-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
9994
+ }
9995
+ function defaultRunToken() {
9996
+ return Math.random().toString(36).slice(2) + Math.random().toString(36).slice(2);
9997
+ }
9998
+ function defaultMessageId() {
9999
+ return `msg-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
10000
+ }
10001
+ function servantSlotId(slot) {
10002
+ return slot.name.replace(/\s+/g, "-").toLowerCase();
10003
+ }
10004
+ function parseOverlordCount(args) {
10005
+ const counts = args.map(numericCountArg).filter((n) => n !== void 0);
10006
+ if (counts.length === 0) return OVERLORD_DEFAULT_COUNT;
10007
+ if (counts.length > 1) throw new Error("provide only one overlord servant count");
10008
+ const count = counts[0];
10009
+ if (count < OVERLORD_MIN_COUNT || count > OVERLORD_MAX_COUNT) {
10010
+ throw new Error(`overlord servant count must be between ${OVERLORD_MIN_COUNT} and ${OVERLORD_MAX_COUNT}`);
10011
+ }
10012
+ return count;
10013
+ }
10014
+ function buildServantLayout(count) {
10015
+ if (count < OVERLORD_MIN_COUNT || count > OVERLORD_MAX_COUNT) {
10016
+ throw new Error(`overlord servant count must be between ${OVERLORD_MIN_COUNT} and ${OVERLORD_MAX_COUNT}`);
10017
+ }
10018
+ const slots = [{
10019
+ name: "fugu ultra 1",
10020
+ role: "ultra",
10021
+ model: "fugu-ultra",
10022
+ index: 1
10023
+ }];
10024
+ for (let i = 1; i < count; i++) {
10025
+ slots.push({
10026
+ name: `fugu normal ${i}`,
10027
+ role: "normal",
10028
+ model: "fugu",
10029
+ index: i
10030
+ });
10031
+ }
10032
+ return slots;
10033
+ }
10034
+ function validateCodexFuguHelp(helpText) {
10035
+ const problems = [];
10036
+ if (!/(?:^|\s)-a(?:,|\s)|--ask-for-approval/.test(helpText)) problems.push("missing approval flag");
10037
+ if (!/(?:^|\s)-s(?:,|\s)|--sandbox/.test(helpText)) problems.push("missing sandbox flag");
10038
+ if (!/(?:^|\s)-c(?:,|\s)|--config/.test(helpText)) problems.push("missing config override flag");
10039
+ if (!/--no-alt-screen/.test(helpText)) problems.push("missing no-alt-screen flag");
10040
+ if (!/(?:^|\s)-C(?:,|\s)|--cwd|--cd|--workdir/.test(helpText)) problems.push("missing cwd flag");
10041
+ return problems;
10042
+ }
10043
+ function evaluateCodexFuguPreflight(input) {
10044
+ const problems = [];
10045
+ if (!input.codexFound) problems.push("codex is not installed or not on PATH");
10046
+ if (!input.fuguFound) problems.push("codex-fugu is not installed or not on PATH");
10047
+ if (!input.authConfigured && !input.envNames.includes("OPENAI_API_KEY") && !input.envNames.includes("CODEX_API_KEY")) {
10048
+ problems.push("missing API key environment: OPENAI_API_KEY or CODEX_API_KEY");
10049
+ }
10050
+ problems.push(...validateCodexFuguHelp(input.helpText ?? ""));
10051
+ const status = input.statusText ?? "";
10052
+ const modelCatalog = input.modelCatalogText ?? "";
10053
+ if (!/\bfugu-ultra\b/i.test(`${status}
10054
+ ${modelCatalog}`)) problems.push("fugu-ultra is not available");
10055
+ if (/native windows codex[^\n]*\/c\/users|\/c\/users[^\n]*native windows codex/i.test(status)) {
10056
+ problems.push("native Windows Codex is configured with a Git Bash /c/Users path");
10057
+ }
10058
+ return { ok: problems.length === 0, problems };
10059
+ }
10060
+ function defaultOverlordStatePath(cwd) {
10061
+ return (0, import_node_path13.join)(cwd, "tmp", "overlord", "runs.json");
10062
+ }
10063
+ function readOverlordRegistry(statePath) {
10064
+ if (!(0, import_node_fs15.existsSync)(statePath)) return { runs: {} };
10065
+ try {
10066
+ const parsed = JSON.parse((0, import_node_fs15.readFileSync)(statePath, "utf8"));
10067
+ return { activeRunId: parsed.activeRunId, runs: parsed.runs ?? {} };
10068
+ } catch {
10069
+ return { runs: {} };
10070
+ }
10071
+ }
10072
+ function writeOverlordRegistry(statePath, registry2) {
10073
+ (0, import_node_fs15.mkdirSync)((0, import_node_path13.dirname)(statePath), { recursive: true });
10074
+ atomicWriteFileSync(statePath, `${JSON.stringify(registry2, null, 2)}
10075
+ `);
10076
+ }
10077
+ function buildOverlordRun(options) {
10078
+ const runId = options.runId?.() ?? defaultRunId();
10079
+ const runToken = options.runToken?.() ?? defaultRunToken();
10080
+ const statePath = defaultOverlordStatePath(options.cwd);
10081
+ const journalDir = (0, import_node_path13.join)(options.cwd, "tmp", "overlord", runId);
10082
+ const timestamp = isoNow(options.now);
10083
+ return {
10084
+ runId,
10085
+ runToken,
10086
+ task: options.task,
10087
+ state: "starting",
10088
+ createdAt: timestamp,
10089
+ updatedAt: timestamp,
10090
+ worktree: options.cwd,
10091
+ statePath,
10092
+ journalDir,
10093
+ servants: buildServantLayout(options.count).map((slot) => ({
10094
+ ...slot,
10095
+ slotId: servantSlotId(slot),
10096
+ profile: "consultation",
10097
+ state: "planned",
10098
+ journalPath: (0, import_node_path13.join)(journalDir, `${servantSlotId(slot)}.log`),
10099
+ composerSubmitMode: "unknown"
10100
+ })),
10101
+ ownedResources: []
10102
+ };
10103
+ }
10104
+ function recordOverlordHeartbeat(run, options) {
10105
+ const timestamp = isoNow(options.now);
10106
+ const controllerResource = {
10107
+ kind: "process",
10108
+ pid: options.controllerPid,
10109
+ commandName: "mmi-cli overlord controller",
10110
+ runId: run.runId,
10111
+ runToken: run.runToken,
10112
+ fingerprint: options.fingerprint
10113
+ };
10114
+ const others = run.ownedResources.filter((resource) => resource.commandName !== "mmi-cli overlord controller");
10115
+ return {
10116
+ ...run,
10117
+ state: run.state === "starting" ? "active" : run.state,
10118
+ updatedAt: timestamp,
10119
+ controllerPid: options.controllerPid,
10120
+ controllerFingerprint: options.fingerprint,
10121
+ lastControllerHeartbeatAt: timestamp,
10122
+ ownedResources: [controllerResource, ...others]
10123
+ };
10124
+ }
10125
+ function buildOverlordStartupPlan(args, cwd) {
10126
+ const count = parseOverlordCount(args);
10127
+ const task = args.filter((arg) => numericCountArg(arg) === void 0).join(" ").trim();
10128
+ return {
10129
+ count,
10130
+ servants: buildServantLayout(count),
10131
+ task,
10132
+ statePath: defaultOverlordStatePath(cwd),
10133
+ nextPhase: "preflight"
10134
+ };
10135
+ }
10136
+ function normalizedCommandName(resource) {
10137
+ return (resource.commandName ?? resource.title ?? "").trim().toLowerCase();
10138
+ }
10139
+ function planStopResource(resource, context) {
10140
+ const exactOwner = resource.runId === context.runId && resource.runToken === context.runToken && Boolean(resource.fingerprint);
10141
+ if (exactOwner) return { action: "stop", reason: "owned" };
10142
+ const name = normalizedCommandName(resource);
10143
+ if (!name || GENERIC_STOP_NAMES.has(name)) return { action: "refuse", reason: "ownership not proven" };
10144
+ return { action: "refuse", reason: "ownership not proven" };
10145
+ }
10146
+ function summarizeOverlordRun(run, probe) {
10147
+ const controller = run.controllerPid == null ? "not-started" : probe.isPidAlive(run.controllerPid) ? "alive" : "lost";
10148
+ return {
10149
+ active: run.state !== "stopped" && run.state !== "failed",
10150
+ runId: run.runId,
10151
+ state: run.state,
10152
+ controller,
10153
+ servants: run.servants.map((servant) => {
10154
+ if (servant.pid == null) return { name: servant.name, state: "not-started" };
10155
+ if (!probe.isPidAlive(servant.pid)) return { name: servant.name, state: "lost" };
10156
+ if (servant.state === "ready" && servant.lastAckAt && servant.composerSubmitMode !== "unknown") {
10157
+ return { name: servant.name, state: "ready" };
10158
+ }
10159
+ return { name: servant.name, state: servant.state };
10160
+ })
10161
+ };
10162
+ }
10163
+ function planOverlordRunStop(run) {
10164
+ const killPids = [];
10165
+ const uncertain = [];
10166
+ for (const resource of run.ownedResources) {
10167
+ const plan2 = planStopResource(resource, { runId: run.runId, runToken: run.runToken });
10168
+ if (plan2.action === "stop" && resource.pid != null) killPids.push(resource.pid);
10169
+ else uncertain.push(resource);
10170
+ }
10171
+ return { killPids, uncertain };
10172
+ }
10173
+ function controllerFingerprint(run) {
10174
+ return `mmi-overlord-controller:${run.runId}:${run.worktree}`;
10175
+ }
10176
+ function defaultStartController(run) {
10177
+ const scriptPath = (0, import_node_path13.join)(__dirname, "overlord-controller.cjs");
10178
+ const fingerprint = controllerFingerprint(run);
10179
+ const child = (0, import_node_child_process8.spawn)(process.execPath, [scriptPath, run.runId, run.statePath, fingerprint], {
10180
+ detached: true,
10181
+ stdio: "ignore",
10182
+ windowsHide: true,
10183
+ cwd: run.worktree
10184
+ });
10185
+ child.unref();
10186
+ return { pid: child.pid, fingerprint };
10187
+ }
10188
+ function defaultIsPidAlive(pid) {
10189
+ if (!Number.isFinite(pid) || pid <= 0) return false;
10190
+ try {
10191
+ process.kill(pid, 0);
10192
+ return true;
10193
+ } catch {
10194
+ return false;
10195
+ }
10196
+ }
10197
+ function defaultKillPid(pid) {
10198
+ process.kill(pid);
10199
+ }
10200
+ function shellQuote2(arg) {
10201
+ return /^[A-Za-z0-9_./:-]+$/.test(arg) ? arg : `"${arg.replace(/"/g, '\\"')}"`;
10202
+ }
10203
+ function boundedCommandText(command, args) {
10204
+ const shell2 = process.platform === "win32";
10205
+ const file = shell2 ? [command, ...args].map(shellQuote2).join(" ") : command;
10206
+ const result = (0, import_node_child_process8.spawnSync)(file, shell2 ? [] : args, {
10207
+ encoding: "utf8",
10208
+ shell: shell2,
10209
+ timeout: 5e3,
10210
+ windowsHide: true,
10211
+ env: {
10212
+ ...process.env,
10213
+ CODEX_FUGU_NO_NOTICE: "1",
10214
+ CODEX_FUGU_NO_UPDATE: "1"
10215
+ }
10216
+ });
10217
+ const text = `${result.stdout ?? ""}
10218
+ ${result.stderr ?? ""}`.trim();
10219
+ if (result.error && result.error.code === "ENOENT") return { found: false, text };
10220
+ return { found: !result.error, text };
10221
+ }
10222
+ function readFuguModelCatalogText() {
10223
+ const codexHome = process.env.CODEX_HOME ?? (0, import_node_path13.join)((0, import_node_os3.homedir)(), ".codex");
10224
+ const catalogPath = (0, import_node_path13.join)(codexHome, "fugu.json");
10225
+ try {
10226
+ return (0, import_node_fs15.existsSync)(catalogPath) ? (0, import_node_fs15.readFileSync)(catalogPath, "utf8") : "";
10227
+ } catch {
10228
+ return "";
10229
+ }
10230
+ }
10231
+ function hasCodexAuthEvidence() {
10232
+ const codexHome = process.env.CODEX_HOME ?? (0, import_node_path13.join)((0, import_node_os3.homedir)(), ".codex");
10233
+ return (0, import_node_fs15.existsSync)((0, import_node_path13.join)(codexHome, "auth.json"));
10234
+ }
10235
+ function collectCodexFuguPreflight() {
10236
+ const codexHelp = boundedCommandText("codex", ["--help"]);
10237
+ const fuguHelp = boundedCommandText("codex-fugu", ["--help"]);
10238
+ const fuguStatus = boundedCommandText("codex-fugu", ["--status"]);
10239
+ return evaluateCodexFuguPreflight({
10240
+ codexFound: codexHelp.found,
10241
+ fuguFound: fuguHelp.found,
10242
+ helpText: fuguHelp.text || codexHelp.text,
10243
+ statusText: fuguStatus.text,
10244
+ modelCatalogText: readFuguModelCatalogText(),
10245
+ authConfigured: hasCodexAuthEvidence(),
10246
+ envNames: Object.keys(process.env)
10247
+ });
10248
+ }
10249
+ function renderCodexFuguPreflightFailure(report) {
10250
+ const lines = [
10251
+ "Overlord setup needed",
10252
+ "The servant pool was not started because Codex/Fugu preflight failed.",
10253
+ "",
10254
+ "Problems:",
10255
+ ...report.problems.map((problem) => `- ${problem}`),
10256
+ "",
10257
+ "Fixes:"
10258
+ ];
10259
+ if (report.problems.some((problem) => problem.includes("codex is not installed"))) {
10260
+ lines.push("- Install or update Codex, then confirm with `codex --version`.");
10261
+ }
10262
+ if (report.problems.some((problem) => problem.includes("codex-fugu is not installed"))) {
10263
+ lines.push("- Install or repair codex-fugu, then confirm with `codex-fugu --status`.");
10264
+ }
10265
+ if (report.problems.some((problem) => problem.includes("missing API key"))) {
10266
+ lines.push("- Set OPENAI_API_KEY or CODEX_API_KEY in the launching shell; do not paste key values into chat.");
10267
+ }
10268
+ if (report.problems.some((problem) => problem.includes("fugu-ultra"))) {
10269
+ lines.push("- Recheck or update Fugu configs so `fugu-ultra` appears in the Fugu model catalog.");
10270
+ }
10271
+ if (report.problems.some((problem) => problem.includes("missing ") && problem.includes("flag"))) {
10272
+ lines.push("- Update Codex/Fugu until `codex-fugu --help` exposes approval, sandbox, config, cwd, and no-alt-screen flags.");
10273
+ }
10274
+ if (report.problems.some((problem) => problem.includes("/c/Users"))) {
10275
+ lines.push("- Launch from a native shell adapter or repair Fugu path configuration so Windows paths stay native.");
10276
+ }
10277
+ return lines.join("\n");
10278
+ }
10279
+ function findActiveRun(registry2) {
10280
+ const runId = registry2.activeRunId;
10281
+ if (!runId) return void 0;
10282
+ return registry2.runs[runId];
10283
+ }
10284
+ function normalizeServantTarget(target) {
10285
+ return target.trim().toLowerCase().replace(/\s+/g, "-");
10286
+ }
10287
+ function hasServantTarget(run, target) {
10288
+ const normalized = normalizeServantTarget(target);
10289
+ return normalized === "all" || run.servants.some(
10290
+ (servant) => servant.slotId === normalized || normalizeServantTarget(servant.name) === normalized
10291
+ );
10292
+ }
10293
+ function renderOverlordStatus(summary, run) {
10294
+ const servants = summary.servants.map((servant) => `- ${servant.name}: ${servant.state}`).join("\n");
10295
+ return [
10296
+ `Overlord run ${summary.runId}`,
10297
+ `State: ${summary.state}`,
10298
+ `Task: ${run.task || "not provided yet"}`,
10299
+ `Controller: ${summary.controller}`,
10300
+ "Servants:",
10301
+ servants
10302
+ ].join("\n");
10303
+ }
10304
+ function runJson(run, extra = {}) {
10305
+ return {
10306
+ ok: true,
10307
+ runId: run.runId,
10308
+ state: run.state,
10309
+ task: run.task,
10310
+ count: run.servants.length,
10311
+ controllerPid: run.controllerPid,
10312
+ statePath: run.statePath,
10313
+ servants: run.servants.map((servant) => ({
10314
+ name: servant.name,
10315
+ role: servant.role,
10316
+ model: servant.model,
10317
+ state: servant.state,
10318
+ pid: servant.pid,
10319
+ journalPath: servant.journalPath
10320
+ })),
10321
+ ...extra
10322
+ };
10323
+ }
10324
+ function wantsJson(options, command) {
10325
+ return Boolean(options.json || command?.parent?.opts?.().json);
10326
+ }
10327
+ function countArgsFromOptions(options) {
10328
+ return ["3", "4", "5", "6"].filter((key) => options[key]).map((key) => `--${key}`);
10329
+ }
10330
+ function registerOverlordCommands(program3, deps = {}) {
10331
+ const out = deps.out ?? ((text) => process.stdout.write(text));
10332
+ const err = deps.err ?? ((text) => process.stderr.write(text));
10333
+ const cwd = deps.cwd ?? (() => process.cwd());
10334
+ const now = deps.now ?? (() => /* @__PURE__ */ new Date());
10335
+ const preflight2 = deps.preflight ?? collectCodexFuguPreflight;
10336
+ const startController = deps.startController ?? defaultStartController;
10337
+ const isPidAlive = deps.isPidAlive ?? defaultIsPidAlive;
10338
+ const killPid = deps.killPid ?? defaultKillPid;
10339
+ const overlord = program3.command("overlord").description("coordinate one Ultra and normal Fugu servants for hard org work").allowUnknownOption(true).argument("[task...]", "task for the Overlord system").option("--3", "run one Ultra and two normal Fugus").option("--4", "run one Ultra and three normal Fugus").option("--5", "run one Ultra and four normal Fugus").option("--6", "run one Ultra and five normal Fugus").option("--json", "print machine-readable output").action((task = [], options) => {
10340
+ try {
10341
+ const args = [...countArgsFromOptions(options), ...task];
10342
+ const root = cwd();
10343
+ const plan2 = buildOverlordStartupPlan(args, root);
10344
+ const preflightReport = preflight2();
10345
+ if (!preflightReport.ok) {
10346
+ err(`${renderCodexFuguPreflightFailure(preflightReport)}
10347
+ `);
10348
+ process.exitCode = 1;
10349
+ return;
10350
+ }
10351
+ const registry2 = readOverlordRegistry(plan2.statePath);
10352
+ const active = findActiveRun(registry2);
10353
+ if (active && active.state !== "stopped" && active.state !== "failed") {
10354
+ throw new Error(`active Overlord run already exists: ${active.runId}`);
10355
+ }
10356
+ let run = buildOverlordRun({
10357
+ task: plan2.task,
10358
+ cwd: root,
10359
+ count: plan2.count,
10360
+ now,
10361
+ runId: deps.runId,
10362
+ runToken: deps.runToken
10363
+ });
10364
+ writeOverlordRegistry(plan2.statePath, { activeRunId: run.runId, runs: { ...registry2.runs, [run.runId]: run } });
10365
+ const controller = startController(run);
10366
+ if (controller.pid != null) {
10367
+ run = recordOverlordHeartbeat(run, {
10368
+ controllerPid: controller.pid,
10369
+ fingerprint: controller.fingerprint,
10370
+ now
10371
+ });
10372
+ writeOverlordRegistry(plan2.statePath, { activeRunId: run.runId, runs: { ...registry2.runs, [run.runId]: run } });
10373
+ }
10374
+ if (options.json) out(`${JSON.stringify(runJson(run, { nextPhase: "consult-servants" }), null, 2)}
10375
+ `);
10376
+ else {
10377
+ out(`${[
10378
+ "Overlord startup",
10379
+ `Task: ${run.task || "not provided yet"}`,
10380
+ `Run: ${run.runId}`,
10381
+ `Servants: ${run.servants.length} total`,
10382
+ `Controller: ${run.controllerPid ? `started (pid ${run.controllerPid})` : "launch requested"}`,
10383
+ "Next: consult servants, interview the human, then print an approved todo list."
10384
+ ].join("\n")}
10385
+ `);
10386
+ }
10387
+ } catch (e) {
10388
+ const message = e instanceof Error ? e.message : String(e);
10389
+ if (options.json) out(`${JSON.stringify({ ok: false, error: message }, null, 2)}
10390
+ `);
10391
+ else err(`overlord: ${message}
10392
+ `);
10393
+ process.exitCode = 1;
10394
+ }
10395
+ });
10396
+ overlord.command("send").description("queue an assignment or redirect for the active Overlord servant controller").argument("<target>", "servant slot id/name, or all").argument("[message...]", "message to deliver to the servant PTY").option("--json", "print machine-readable output").action((target, message = [], options, command) => {
10397
+ const json = wantsJson(options, command);
10398
+ try {
10399
+ const statePath = defaultOverlordStatePath(cwd());
10400
+ const registry2 = readOverlordRegistry(statePath);
10401
+ const run = findActiveRun(registry2);
10402
+ if (!run) throw new Error("no active Overlord run found");
10403
+ const normalized = normalizeServantTarget(target);
10404
+ if (!hasServantTarget(run, normalized)) throw new Error(`unknown Overlord servant target: ${target}`);
10405
+ const text = message.join(" ").trim();
10406
+ if (!text) throw new Error("message is required");
10407
+ const queued = {
10408
+ id: deps.runId?.() ?? defaultMessageId(),
10409
+ target: normalized,
10410
+ text,
10411
+ createdAt: isoNow(now)
10412
+ };
10413
+ const next = {
10414
+ ...run,
10415
+ updatedAt: isoNow(now),
10416
+ messages: [...run.messages ?? [], queued]
10417
+ };
10418
+ writeOverlordRegistry(statePath, {
10419
+ ...registry2,
10420
+ runs: { ...registry2.runs, [run.runId]: next }
10421
+ });
10422
+ const payload = { queued: 1, runId: run.runId, target: normalized, messageId: queued.id, statePath };
10423
+ if (json) out(`${JSON.stringify(payload, null, 2)}
10424
+ `);
10425
+ else out(`Queued message ${queued.id} for ${normalized}.
10426
+ State: ${statePath}
10427
+ `);
10428
+ } catch (e) {
10429
+ const messageText = e instanceof Error ? e.message : String(e);
10430
+ if (json) out(`${JSON.stringify({ ok: false, error: messageText }, null, 2)}
10431
+ `);
10432
+ else err(`overlord send: ${messageText}
10433
+ `);
10434
+ process.exitCode = 1;
10435
+ }
10436
+ });
10437
+ overlord.command("status").description("show the active Overlord run and servant liveness").option("--json", "print machine-readable output").action((options, command) => {
10438
+ const json = wantsJson(options, command);
10439
+ const statePath = defaultOverlordStatePath(cwd());
10440
+ const registry2 = readOverlordRegistry(statePath);
10441
+ const run = findActiveRun(registry2);
10442
+ if (!run) {
10443
+ const payload2 = { active: false, statePath, message: "no active Overlord run found" };
10444
+ if (json) out(`${JSON.stringify(payload2, null, 2)}
10445
+ `);
10446
+ else out(`No active Overlord run found.
10447
+ State: ${payload2.statePath}
10448
+ `);
10449
+ return;
10450
+ }
10451
+ const summary = summarizeOverlordRun(run, { isPidAlive });
10452
+ const payload = { ...summary, statePath, task: run.task };
10453
+ if (json) out(`${JSON.stringify(payload, null, 2)}
10454
+ `);
10455
+ else out(`${renderOverlordStatus(summary, run)}
10456
+ State: ${statePath}
10457
+ `);
10458
+ });
10459
+ overlord.command("stop").description("stop only exact run-owned Overlord resources").option("--json", "print machine-readable output").action((options, command) => {
10460
+ const json = wantsJson(options, command);
10461
+ const statePath = defaultOverlordStatePath(cwd());
10462
+ const registry2 = readOverlordRegistry(statePath);
10463
+ const run = findActiveRun(registry2);
10464
+ if (!run) {
10465
+ const payload2 = { stopped: 0, uncertain: 0, statePath, message: "no active Overlord run found" };
10466
+ if (json) out(`${JSON.stringify(payload2, null, 2)}
10467
+ `);
10468
+ else out(`No active Overlord run found. Nothing stopped.
10469
+ State: ${payload2.statePath}
10470
+ `);
10471
+ return;
10472
+ }
10473
+ const stopPlan = planOverlordRunStop(run);
10474
+ for (const pid of stopPlan.killPids) {
10475
+ try {
10476
+ killPid(pid);
10477
+ } catch {
10478
+ }
10479
+ }
10480
+ const stoppedRun = {
10481
+ ...run,
10482
+ state: "stopped",
10483
+ updatedAt: isoNow(now)
10484
+ };
10485
+ writeOverlordRegistry(statePath, {
10486
+ runs: { ...registry2.runs, [run.runId]: stoppedRun }
10487
+ });
10488
+ const payload = {
10489
+ runId: run.runId,
10490
+ stopped: stopPlan.killPids.length,
10491
+ uncertain: stopPlan.uncertain.length,
10492
+ statePath
10493
+ };
10494
+ if (json) out(`${JSON.stringify(payload, null, 2)}
10495
+ `);
10496
+ else {
10497
+ out(`${[
10498
+ `Stopped ${payload.stopped} Overlord-owned process(es).`,
10499
+ payload.uncertain ? `Left ${payload.uncertain} uncertain resource(s) untouched.` : "No uncertain resources.",
10500
+ `State: ${statePath}`
10501
+ ].join("\n")}
10502
+ `);
10503
+ }
10504
+ });
10505
+ return overlord;
10506
+ }
10507
+
10508
+ // src/throttle-commands.ts
10509
+ var import_node_child_process9 = require("node:child_process");
10510
+ var import_node_fs16 = require("node:fs");
10511
+ var import_node_path14 = require("node:path");
10512
+ var THROTTLE_TRACE_REL = (0, import_node_path14.join)(".mmi", "throttle", "trace.jsonl");
9963
10513
  function resolveRepoGitRoot(cwd = process.cwd()) {
9964
10514
  try {
9965
- const root = (0, import_node_child_process8.execFileSync)("git", ["-C", cwd, "rev-parse", "--show-toplevel"], {
10515
+ const root = (0, import_node_child_process9.execFileSync)("git", ["-C", cwd, "rev-parse", "--show-toplevel"], {
9966
10516
  encoding: "utf8",
9967
10517
  timeout: 5e3
9968
10518
  }).trim();
@@ -9972,7 +10522,7 @@ function resolveRepoGitRoot(cwd = process.cwd()) {
9972
10522
  }
9973
10523
  }
9974
10524
  function resolveThrottleTracePath(cwd = process.cwd()) {
9975
- return (0, import_node_path13.join)(resolveRepoGitRoot(cwd), THROTTLE_TRACE_REL);
10525
+ return (0, import_node_path14.join)(resolveRepoGitRoot(cwd), THROTTLE_TRACE_REL);
9976
10526
  }
9977
10527
  function resolveModeFromEnv() {
9978
10528
  const v = String(process.env.MMI_THROTTLE_MODE ?? "block").trim().toLowerCase();
@@ -9992,6 +10542,12 @@ function parseTraceLines(raw) {
9992
10542
  }
9993
10543
  return out;
9994
10544
  }
10545
+ function commandSafetyClass(reasonId) {
10546
+ if (reasonId === "shell_dialect_redirect") return "dialect/redirect mismatch";
10547
+ if (reasonId === "shell_log_dump") return "unbounded log/text dump";
10548
+ if (reasonId.startsWith("shell_")) return "shell command safety";
10549
+ return void 0;
10550
+ }
9995
10551
  function summarizeTrace(entries) {
9996
10552
  let denials = 0;
9997
10553
  let readBytesWouldBlock = 0;
@@ -9999,6 +10555,7 @@ function summarizeTrace(entries) {
9999
10555
  const byReason = {};
10000
10556
  const bySurface = {};
10001
10557
  const byMode = {};
10558
+ const commandSafetyBySurface = {};
10002
10559
  for (const e of entries) {
10003
10560
  denials += 1;
10004
10561
  const tool = e.tool ?? "unknown";
@@ -10009,22 +10566,27 @@ function summarizeTrace(entries) {
10009
10566
  bySurface[surface] = (bySurface[surface] ?? 0) + 1;
10010
10567
  const mode = e.mode ?? "unknown";
10011
10568
  byMode[mode] = (byMode[mode] ?? 0) + 1;
10569
+ const safetyClass = commandSafetyClass(reason);
10570
+ if (safetyClass) {
10571
+ commandSafetyBySurface[surface] = commandSafetyBySurface[surface] ?? {};
10572
+ commandSafetyBySurface[surface][safetyClass] = (commandSafetyBySurface[surface][safetyClass] ?? 0) + 1;
10573
+ }
10012
10574
  if (reason === "read_unbounded_large") {
10013
10575
  readBytesWouldBlock += Number(e.fileBytes) || 0;
10014
10576
  }
10015
10577
  }
10016
- return { denials, readBytesWouldBlock, byTool, byReason, bySurface, byMode };
10578
+ return { denials, readBytesWouldBlock, byTool, byReason, bySurface, byMode, commandSafetyBySurface };
10017
10579
  }
10018
10580
  function runThrottleReport(io, tracePath = resolveThrottleTracePath()) {
10019
10581
  const mode = resolveModeFromEnv();
10020
- if (!(0, import_node_fs14.existsSync)(tracePath)) {
10582
+ if (!(0, import_node_fs16.existsSync)(tracePath)) {
10021
10583
  io.log(`Throttle: no trace at ${tracePath} (gates have not denied anything yet).`);
10022
10584
  io.log(`Active mode: ${mode} (MMI_THROTTLE_MODE env; default block).`);
10023
10585
  return 0;
10024
10586
  }
10025
10587
  let raw = "";
10026
10588
  try {
10027
- raw = (0, import_node_fs14.readFileSync)(tracePath, "utf8");
10589
+ raw = (0, import_node_fs16.readFileSync)(tracePath, "utf8");
10028
10590
  } catch (e) {
10029
10591
  io.err(`Throttle: could not read trace: ${e.message}`);
10030
10592
  return 1;
@@ -10059,6 +10621,14 @@ function runThrottleReport(io, tracePath = resolveThrottleTracePath()) {
10059
10621
  io.log(" by surface:");
10060
10622
  for (const [name, count] of surfaces) io.log(` ${name}: ${count}`);
10061
10623
  }
10624
+ const safetySurfaces = Object.entries(s.commandSafetyBySurface).sort((a, b) => a[0].localeCompare(b[0]));
10625
+ if (safetySurfaces.length) {
10626
+ io.log(" command safety (quoting/dialect/shell) by surface:");
10627
+ for (const [surface, classes] of safetySurfaces) {
10628
+ const detail = Object.entries(classes).sort((a, b) => b[1] - a[1]).map(([cls, count]) => `${cls}: ${count}`).join(", ");
10629
+ io.log(` ${surface}: ${detail}`);
10630
+ }
10631
+ }
10062
10632
  return 0;
10063
10633
  }
10064
10634
  function registerThrottleCommands(program3) {
@@ -10091,7 +10661,7 @@ async function syncDocs(deps, docs2 = SYNCED_DOCS) {
10091
10661
  }
10092
10662
 
10093
10663
  // src/board.ts
10094
- var import_node_child_process9 = require("node:child_process");
10664
+ var import_node_child_process10 = require("node:child_process");
10095
10665
  var import_node_util6 = require("node:util");
10096
10666
 
10097
10667
  // src/board-priority.ts
@@ -10199,7 +10769,7 @@ async function filterDependencyBlockedClaimables(items, client, opts = {}) {
10199
10769
  var BOARD_STATUSES = ["Todo", "In Progress", "In Review", "Done"];
10200
10770
 
10201
10771
  // src/board.ts
10202
- var rawExecFileP3 = (0, import_node_util6.promisify)(import_node_child_process9.execFile);
10772
+ var rawExecFileP3 = (0, import_node_util6.promisify)(import_node_child_process10.execFile);
10203
10773
  var BOARD_GIT_TIMEOUT_MS = 1e4;
10204
10774
  var WRITE_PROBE_CONCURRENCY = 8;
10205
10775
  var CLAIM_CONCURRENCY = 5;
@@ -11028,16 +11598,16 @@ function ghError(e) {
11028
11598
  }
11029
11599
 
11030
11600
  // src/board-slice-cache.ts
11031
- var import_node_fs15 = require("node:fs");
11032
- var import_node_path14 = require("node:path");
11033
- var BOARD_SLICE_CACHE_FILE = (0, import_node_path14.join)(".mmi", "board-slice.json");
11601
+ var import_node_fs17 = require("node:fs");
11602
+ var import_node_path15 = require("node:path");
11603
+ var BOARD_SLICE_CACHE_FILE = (0, import_node_path15.join)(".mmi", "board-slice.json");
11034
11604
  var BOARD_SLICE_CACHE_TTL_MS = 10 * 60 * 1e3;
11035
11605
  function boardSliceCachePath(cwd) {
11036
- return (0, import_node_path14.join)(cwd, BOARD_SLICE_CACHE_FILE);
11606
+ return (0, import_node_path15.join)(cwd, BOARD_SLICE_CACHE_FILE);
11037
11607
  }
11038
11608
  function readCachedBoardSlice(cwd) {
11039
11609
  try {
11040
- const parsed = JSON.parse((0, import_node_fs15.readFileSync)(boardSliceCachePath(cwd), "utf8"));
11610
+ const parsed = JSON.parse((0, import_node_fs17.readFileSync)(boardSliceCachePath(cwd), "utf8"));
11041
11611
  if (typeof parsed.ts !== "number") return null;
11042
11612
  return { block: typeof parsed.block === "string" ? parsed.block : null, ts: parsed.ts };
11043
11613
  } catch {
@@ -11048,12 +11618,12 @@ function writeCachedBoardSlice(cwd, slice) {
11048
11618
  const path2 = boardSliceCachePath(cwd);
11049
11619
  const tmp = `${path2}.${process.pid}.tmp`;
11050
11620
  try {
11051
- (0, import_node_fs15.mkdirSync)((0, import_node_path14.dirname)(path2), { recursive: true });
11052
- (0, import_node_fs15.writeFileSync)(tmp, JSON.stringify(slice));
11053
- (0, import_node_fs15.renameSync)(tmp, path2);
11621
+ (0, import_node_fs17.mkdirSync)((0, import_node_path15.dirname)(path2), { recursive: true });
11622
+ (0, import_node_fs17.writeFileSync)(tmp, JSON.stringify(slice));
11623
+ (0, import_node_fs17.renameSync)(tmp, path2);
11054
11624
  } catch {
11055
11625
  try {
11056
- (0, import_node_fs15.rmSync)(tmp, { force: true });
11626
+ (0, import_node_fs17.rmSync)(tmp, { force: true });
11057
11627
  } catch {
11058
11628
  }
11059
11629
  }
@@ -11181,8 +11751,8 @@ async function refreshBoardSliceCache(deps) {
11181
11751
  }
11182
11752
 
11183
11753
  // src/worktree.ts
11184
- var import_node_fs16 = require("node:fs");
11185
- var import_node_path15 = require("node:path");
11754
+ var import_node_fs18 = require("node:fs");
11755
+ var import_node_path16 = require("node:path");
11186
11756
  var LOCAL_ONLY_FILES = [".claude/settings.local.json"];
11187
11757
  var PKG = "package.json";
11188
11758
  var LOCKFILE = "package-lock.json";
@@ -11190,21 +11760,21 @@ var NODE_MODULES = "node_modules";
11190
11760
  var realFsProbe = {
11191
11761
  isDir: (p) => {
11192
11762
  try {
11193
- return (0, import_node_fs16.statSync)(p).isDirectory();
11763
+ return (0, import_node_fs18.statSync)(p).isDirectory();
11194
11764
  } catch {
11195
11765
  return false;
11196
11766
  }
11197
11767
  },
11198
11768
  isFile: (p) => {
11199
11769
  try {
11200
- return (0, import_node_fs16.statSync)(p).isFile();
11770
+ return (0, import_node_fs18.statSync)(p).isFile();
11201
11771
  } catch {
11202
11772
  return false;
11203
11773
  }
11204
11774
  },
11205
11775
  listDirs: (p) => {
11206
11776
  try {
11207
- return (0, import_node_fs16.readdirSync)(p, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
11777
+ return (0, import_node_fs18.readdirSync)(p, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
11208
11778
  } catch {
11209
11779
  return [];
11210
11780
  }
@@ -11212,12 +11782,12 @@ var realFsProbe = {
11212
11782
  };
11213
11783
  function scanInstallDirs(root, fs2 = realFsProbe) {
11214
11784
  const factsFor = (dir) => {
11215
- const abs = dir ? (0, import_node_path15.join)(root, dir) : root;
11785
+ const abs = dir ? (0, import_node_path16.join)(root, dir) : root;
11216
11786
  return {
11217
11787
  dir,
11218
- hasPackageJson: fs2.isFile((0, import_node_path15.join)(abs, PKG)),
11219
- hasLockfile: fs2.isFile((0, import_node_path15.join)(abs, LOCKFILE)),
11220
- hasNodeModules: fs2.isDir((0, import_node_path15.join)(abs, NODE_MODULES))
11788
+ hasPackageJson: fs2.isFile((0, import_node_path16.join)(abs, PKG)),
11789
+ hasLockfile: fs2.isFile((0, import_node_path16.join)(abs, LOCKFILE)),
11790
+ hasNodeModules: fs2.isDir((0, import_node_path16.join)(abs, NODE_MODULES))
11221
11791
  };
11222
11792
  };
11223
11793
  const children = fs2.listDirs(root).filter((name) => name !== NODE_MODULES && name !== ".git");
@@ -11227,7 +11797,7 @@ function npmInstallTargets(dirs) {
11227
11797
  return dirs.filter((d) => d.hasPackageJson && !d.hasNodeModules).map((d) => ({ dir: d.dir, command: d.hasLockfile ? "npm ci" : "npm install" }));
11228
11798
  }
11229
11799
  function isLinkedWorktree(root, fs2 = realFsProbe) {
11230
- return fs2.isFile((0, import_node_path15.join)(root, ".git"));
11800
+ return fs2.isFile((0, import_node_path16.join)(root, ".git"));
11231
11801
  }
11232
11802
  function worktreeAutoProvisionBanner(root, fs2 = realFsProbe) {
11233
11803
  if (!isLinkedWorktree(root, fs2)) return null;
@@ -11237,8 +11807,8 @@ function worktreeAutoProvisionBanner(root, fs2 = realFsProbe) {
11237
11807
  return `[worktree] provisioning tooling in the background (deps in ${where} + local config) \u2014 \`mmi-cli worktree setup\` to redo`;
11238
11808
  }
11239
11809
  function defaultCopyFile(from, to) {
11240
- (0, import_node_fs16.mkdirSync)((0, import_node_path15.dirname)(to), { recursive: true });
11241
- (0, import_node_fs16.copyFileSync)(from, to);
11810
+ (0, import_node_fs18.mkdirSync)((0, import_node_path16.dirname)(to), { recursive: true });
11811
+ (0, import_node_fs18.copyFileSync)(from, to);
11242
11812
  }
11243
11813
  async function provisionWorktree(worktreeRoot, deps) {
11244
11814
  const fs2 = deps.fs ?? realFsProbe;
@@ -11250,7 +11820,7 @@ async function provisionWorktree(worktreeRoot, deps) {
11250
11820
  const skippedInstall = allDirs.filter((d) => d.hasPackageJson && d.hasNodeModules).map((d) => d.dir);
11251
11821
  const installed = [];
11252
11822
  for (const target of targets) {
11253
- const cwd = target.dir ? (0, import_node_path15.join)(worktreeRoot, target.dir) : worktreeRoot;
11823
+ const cwd = target.dir ? (0, import_node_path16.join)(worktreeRoot, target.dir) : worktreeRoot;
11254
11824
  log(`installing deps: ${target.command} in ${target.dir || "."}`);
11255
11825
  await deps.runInstall(target.command, cwd);
11256
11826
  installed.push(target);
@@ -11259,7 +11829,7 @@ async function provisionWorktree(worktreeRoot, deps) {
11259
11829
  const copySkipped = [];
11260
11830
  const primary = await deps.primaryCheckout();
11261
11831
  for (const rel of LOCAL_ONLY_FILES) {
11262
- const dest = (0, import_node_path15.join)(worktreeRoot, rel);
11832
+ const dest = (0, import_node_path16.join)(worktreeRoot, rel);
11263
11833
  if (fs2.isFile(dest)) {
11264
11834
  copySkipped.push({ file: rel, reason: "already-present" });
11265
11835
  continue;
@@ -11268,11 +11838,11 @@ async function provisionWorktree(worktreeRoot, deps) {
11268
11838
  copySkipped.push({ file: rel, reason: "no-primary" });
11269
11839
  continue;
11270
11840
  }
11271
- if (!fs2.isFile((0, import_node_path15.join)(primary, rel))) {
11841
+ if (!fs2.isFile((0, import_node_path16.join)(primary, rel))) {
11272
11842
  copySkipped.push({ file: rel, reason: "absent-in-primary" });
11273
11843
  continue;
11274
11844
  }
11275
- copyFile((0, import_node_path15.join)(primary, rel), dest);
11845
+ copyFile((0, import_node_path16.join)(primary, rel), dest);
11276
11846
  copied.push(rel);
11277
11847
  log(`copied local config: ${rel}`);
11278
11848
  }
@@ -11280,7 +11850,7 @@ async function provisionWorktree(worktreeRoot, deps) {
11280
11850
  }
11281
11851
  function defaultWorktreePath(repoRoot, branch) {
11282
11852
  const safe = branch.replace(/[/\\]+/g, "-");
11283
- return (0, import_node_path15.join)((0, import_node_path15.dirname)(repoRoot), "mmi-worktrees", safe);
11853
+ return (0, import_node_path16.join)((0, import_node_path16.dirname)(repoRoot), "mmi-worktrees", safe);
11284
11854
  }
11285
11855
  function resolveWorktreeBase(from, remote) {
11286
11856
  const remotePrefix = `${remote}/`;
@@ -11427,7 +11997,7 @@ function whoamiLine(report) {
11427
11997
  }
11428
11998
 
11429
11999
  // src/index.ts
11430
- var import_node_path25 = require("node:path");
12000
+ var import_node_path26 = require("node:path");
11431
12001
 
11432
12002
  // src/merge-ci-policy.ts
11433
12003
  function resolveMergeCiPolicy(input) {
@@ -12609,8 +13179,8 @@ async function resolveAutoAddBoardAttach(client, cfg, selector, priority, warn =
12609
13179
 
12610
13180
  // src/gh-create.ts
12611
13181
  var import_promises5 = require("node:fs/promises");
12612
- var import_node_os3 = require("node:os");
12613
- var import_node_path16 = require("node:path");
13182
+ var import_node_os4 = require("node:os");
13183
+ var import_node_path17 = require("node:path");
12614
13184
  var import_node_crypto5 = require("node:crypto");
12615
13185
  var ISSUE_TYPES = ["bug", "feature", "task"];
12616
13186
  var GH_MUTATION_TIMEOUT_MS = 12e4;
@@ -12651,7 +13221,7 @@ async function bodyArgsViaFile(args, deps = {}) {
12651
13221
  } };
12652
13222
  const write = deps.write ?? import_promises5.writeFile;
12653
13223
  const remove = deps.remove ?? import_promises5.unlink;
12654
- const file = (0, import_node_path16.join)(deps.dir ?? (0, import_node_os3.tmpdir)(), `mmi-gh-body-${process.pid}-${(0, import_node_crypto5.randomBytes)(4).toString("hex")}.md`);
13224
+ const file = (0, import_node_path17.join)(deps.dir ?? (0, import_node_os4.tmpdir)(), `mmi-gh-body-${process.pid}-${(0, import_node_crypto5.randomBytes)(4).toString("hex")}.md`);
12655
13225
  await write(file, args[i + 1], "utf8");
12656
13226
  return {
12657
13227
  args: [...args.slice(0, i), "--body-file", file, ...args.slice(i + 2)],
@@ -12696,6 +13266,92 @@ function buildPrArgs({ title, body, base, head, repo }) {
12696
13266
  return args;
12697
13267
  }
12698
13268
 
13269
+ // src/sub-issue.ts
13270
+ function parseIssueRef(ref) {
13271
+ const trimmed = ref.trim();
13272
+ const url = trimmed.match(/^https:\/\/github\.com\/([^/]+\/[^/]+)\/issues\/(\d+)$/i);
13273
+ if (url) return { repo: url[1], number: Number(url[2]) };
13274
+ const qualified = trimmed.match(/^([^/\s#]+\/[^/\s#]+)#(\d+)$/);
13275
+ if (qualified) return { repo: qualified[1], number: Number(qualified[2]) };
13276
+ const bare = trimmed.match(/^#?(\d+)$/);
13277
+ if (bare) return { number: Number(bare[1]) };
13278
+ throw new Error(`invalid issue reference "${ref}" \u2014 expected #123, 123, owner/repo#123, or an issue URL`);
13279
+ }
13280
+ function buildResolveIdArgs(ref) {
13281
+ const args = ["issue", "view", String(ref.number), "--json", "id", "--jq", ".id"];
13282
+ if (ref.repo) args.push("--repo", ref.repo);
13283
+ return args;
13284
+ }
13285
+ function buildAddSubIssueArgs(parentId, subIssueId) {
13286
+ if (!parentId) throw new Error("addSubIssue: parentId is required");
13287
+ if (!subIssueId) throw new Error("addSubIssue: subIssueId is required");
13288
+ return [
13289
+ "api",
13290
+ "graphql",
13291
+ "-f",
13292
+ "query=mutation($p:ID!,$c:ID!){addSubIssue(input:{issueId:$p,subIssueId:$c}){issue{number subIssues{totalCount}} subIssue{number}}}",
13293
+ "-f",
13294
+ `p=${parentId}`,
13295
+ "-f",
13296
+ `c=${subIssueId}`
13297
+ ];
13298
+ }
13299
+ function parseAddSubIssueResult(stdout) {
13300
+ try {
13301
+ const issue2 = JSON.parse(stdout)?.data?.addSubIssue;
13302
+ const parentNumber = issue2?.issue?.number;
13303
+ const subIssueNumber = issue2?.subIssue?.number;
13304
+ const totalCount = issue2?.issue?.subIssues?.totalCount;
13305
+ if (typeof parentNumber !== "number" || typeof subIssueNumber !== "number") return void 0;
13306
+ return { parentNumber, subIssueNumber, totalCount: typeof totalCount === "number" ? totalCount : 0 };
13307
+ } catch {
13308
+ return void 0;
13309
+ }
13310
+ }
13311
+ var RESOLVE_ID_TIMEOUT_MS = 1e4;
13312
+ async function resolveIssueNodeId(runGh, ref, fallbackRepo) {
13313
+ const resolved = ref.repo ? ref : { ...ref, repo: fallbackRepo };
13314
+ const id = (await runGh(buildResolveIdArgs(resolved), RESOLVE_ID_TIMEOUT_MS)).trim();
13315
+ if (!id) throw new Error(`could not resolve node id for issue #${ref.number}${resolved.repo ? ` in ${resolved.repo}` : ""}`);
13316
+ return id;
13317
+ }
13318
+ async function linkSubIssue(runGh, parentRef, childRef, defaultRepo) {
13319
+ const parent = parseIssueRef(parentRef);
13320
+ const child = parseIssueRef(childRef);
13321
+ const parentId = await resolveIssueNodeId(runGh, parent, defaultRepo);
13322
+ const subIssueId = await resolveIssueNodeId(runGh, child, defaultRepo);
13323
+ const stdout = await runGh(buildAddSubIssueArgs(parentId, subIssueId), GH_MUTATION_TIMEOUT_MS);
13324
+ const result = parseAddSubIssueResult(stdout);
13325
+ if (!result) throw new Error(`addSubIssue returned an unexpected response:
13326
+ ${stdout.trim() || "(empty)"}`);
13327
+ return result;
13328
+ }
13329
+ function parentLinkFields(result, error) {
13330
+ if (result) return { parent: result };
13331
+ if (error) return { parentLinkError: error };
13332
+ return {};
13333
+ }
13334
+
13335
+ // src/issue-comment.ts
13336
+ async function postIssueComment(client, input) {
13337
+ const parsed = parseIssueRef(input.ref);
13338
+ const repo = parsed.repo ?? input.defaultRepo;
13339
+ if (!repo) throw new Error("could not resolve repo \u2014 pass --repo owner/repo");
13340
+ if (input.body.trim().length === 0) throw new Error("comment body is empty");
13341
+ const comment = await client.rest(
13342
+ "POST",
13343
+ `repos/${repo}/issues/${parsed.number}/comments`,
13344
+ { body: { body: input.body } }
13345
+ );
13346
+ if (!comment?.html_url) throw new Error("GitHub did not return a comment URL");
13347
+ return {
13348
+ number: parsed.number,
13349
+ repo,
13350
+ url: `https://github.com/${repo}/issues/${parsed.number}`,
13351
+ commentUrl: comment.html_url
13352
+ };
13353
+ }
13354
+
12699
13355
  // src/issue-check.ts
12700
13356
  var CHECKLIST_RE = /^([ \t]*[-*+] \[)([ xX])(\] )(.*)$/gm;
12701
13357
  function findChecklistItems(body) {
@@ -12978,72 +13634,6 @@ Related work discovered by mmi-cli:
12978
13634
  ${lines.join("\n")}`;
12979
13635
  }
12980
13636
 
12981
- // src/sub-issue.ts
12982
- function parseIssueRef(ref) {
12983
- const trimmed = ref.trim();
12984
- const url = trimmed.match(/^https:\/\/github\.com\/([^/]+\/[^/]+)\/issues\/(\d+)$/i);
12985
- if (url) return { repo: url[1], number: Number(url[2]) };
12986
- const qualified = trimmed.match(/^([^/\s#]+\/[^/\s#]+)#(\d+)$/);
12987
- if (qualified) return { repo: qualified[1], number: Number(qualified[2]) };
12988
- const bare = trimmed.match(/^#?(\d+)$/);
12989
- if (bare) return { number: Number(bare[1]) };
12990
- throw new Error(`invalid issue reference "${ref}" \u2014 expected #123, 123, owner/repo#123, or an issue URL`);
12991
- }
12992
- function buildResolveIdArgs(ref) {
12993
- const args = ["issue", "view", String(ref.number), "--json", "id", "--jq", ".id"];
12994
- if (ref.repo) args.push("--repo", ref.repo);
12995
- return args;
12996
- }
12997
- function buildAddSubIssueArgs(parentId, subIssueId) {
12998
- if (!parentId) throw new Error("addSubIssue: parentId is required");
12999
- if (!subIssueId) throw new Error("addSubIssue: subIssueId is required");
13000
- return [
13001
- "api",
13002
- "graphql",
13003
- "-f",
13004
- "query=mutation($p:ID!,$c:ID!){addSubIssue(input:{issueId:$p,subIssueId:$c}){issue{number subIssues{totalCount}} subIssue{number}}}",
13005
- "-f",
13006
- `p=${parentId}`,
13007
- "-f",
13008
- `c=${subIssueId}`
13009
- ];
13010
- }
13011
- function parseAddSubIssueResult(stdout) {
13012
- try {
13013
- const issue2 = JSON.parse(stdout)?.data?.addSubIssue;
13014
- const parentNumber = issue2?.issue?.number;
13015
- const subIssueNumber = issue2?.subIssue?.number;
13016
- const totalCount = issue2?.issue?.subIssues?.totalCount;
13017
- if (typeof parentNumber !== "number" || typeof subIssueNumber !== "number") return void 0;
13018
- return { parentNumber, subIssueNumber, totalCount: typeof totalCount === "number" ? totalCount : 0 };
13019
- } catch {
13020
- return void 0;
13021
- }
13022
- }
13023
- var RESOLVE_ID_TIMEOUT_MS = 1e4;
13024
- async function resolveIssueNodeId(runGh, ref, fallbackRepo) {
13025
- const resolved = ref.repo ? ref : { ...ref, repo: fallbackRepo };
13026
- const id = (await runGh(buildResolveIdArgs(resolved), RESOLVE_ID_TIMEOUT_MS)).trim();
13027
- if (!id) throw new Error(`could not resolve node id for issue #${ref.number}${resolved.repo ? ` in ${resolved.repo}` : ""}`);
13028
- return id;
13029
- }
13030
- async function linkSubIssue(runGh, parentRef, childRef, defaultRepo) {
13031
- const parent = parseIssueRef(parentRef);
13032
- const child = parseIssueRef(childRef);
13033
- const parentId = await resolveIssueNodeId(runGh, parent, defaultRepo);
13034
- const subIssueId = await resolveIssueNodeId(runGh, child, defaultRepo);
13035
- const stdout = await runGh(buildAddSubIssueArgs(parentId, subIssueId), GH_MUTATION_TIMEOUT_MS);
13036
- const result = parseAddSubIssueResult(stdout);
13037
- if (!result) throw new Error(`addSubIssue returned an unexpected response:
13038
- ${stdout.trim() || "(empty)"}`);
13039
- return result;
13040
- }
13041
- function parentLinkFields(result, error) {
13042
- if (result) return { parent: result };
13043
- if (error) return { parentLinkError: error };
13044
- return {};
13045
- }
13046
-
13047
13637
  // src/report.ts
13048
13638
  var HUB_REPO3 = "mutmutco/MMI-Hub";
13049
13639
  var REPORT_LABEL = "report";
@@ -13080,7 +13670,7 @@ ${buildReportBody(body, sourceRepo)}`;
13080
13670
 
13081
13671
  // src/skill-lesson.ts
13082
13672
  var SKILL_LESSON_LABEL = "skill-lesson";
13083
- var SKILL_NAMES = ["bootstrap", "browser-automation", "build", "coop", "grind", "handoff", "hotfix", "mmi", "rcand", "release", "secrets", "stage"];
13673
+ var SKILL_NAMES = ["bootstrap", "browser-automation", "build", "coop", "grind", "handoff", "hotfix", "mmi", "overlord", "rcand", "release", "secrets", "stage"];
13084
13674
  function assertSkillName(name) {
13085
13675
  const match = SKILL_NAMES.find((skill) => skill === name);
13086
13676
  if (!match) throw new Error(`unknown skill "${name}" \u2014 expected one of: ${SKILL_NAMES.join(", ")}`);
@@ -13745,8 +14335,8 @@ async function runStageLiveDown(deps, t) {
13745
14335
  }
13746
14336
 
13747
14337
  // src/design-system.ts
13748
- var import_node_fs17 = require("node:fs");
13749
- var import_node_path17 = require("node:path");
14338
+ var import_node_fs19 = require("node:fs");
14339
+ var import_node_path18 = require("node:path");
13750
14340
  var UI_PACKAGE_CANDIDATES = ["@mutmutco/ui-dashboard", "@mutmutco/ui", "@mutmutco/theme"];
13751
14341
  var DESIGN_SYSTEM_VERSION_LABEL = "@mutmutco design-system npm package (vs @latest)";
13752
14342
  function dashboardConsumerRegistryFix(error) {
@@ -13795,17 +14385,17 @@ function buildDesignSystemVersionCheck(input) {
13795
14385
  }
13796
14386
  function readJsonFile(path2) {
13797
14387
  try {
13798
- return JSON.parse((0, import_node_fs17.readFileSync)(path2, "utf8"));
14388
+ return JSON.parse((0, import_node_fs19.readFileSync)(path2, "utf8"));
13799
14389
  } catch {
13800
14390
  return void 0;
13801
14391
  }
13802
14392
  }
13803
14393
  function isUiFactoryCheckout(root) {
13804
- const pkg = readJsonFile((0, import_node_path17.join)(root, "package.json"));
14394
+ const pkg = readJsonFile((0, import_node_path18.join)(root, "package.json"));
13805
14395
  return pkg?.name === "mmd-ui" && pkg?.private === true;
13806
14396
  }
13807
14397
  function resolveDeclaredUiPackage(root) {
13808
- const pkg = readJsonFile((0, import_node_path17.join)(root, "package.json"));
14398
+ const pkg = readJsonFile((0, import_node_path18.join)(root, "package.json"));
13809
14399
  if (!pkg) return void 0;
13810
14400
  const deps = { ...pkg.dependencies, ...pkg.devDependencies };
13811
14401
  for (const name of UI_PACKAGE_CANDIDATES) {
@@ -13815,8 +14405,8 @@ function resolveDeclaredUiPackage(root) {
13815
14405
  return void 0;
13816
14406
  }
13817
14407
  function readLockfileInstalledVersion(root, packageName) {
13818
- const lockPath = (0, import_node_path17.join)(root, "package-lock.json");
13819
- if (!(0, import_node_fs17.existsSync)(lockPath)) return void 0;
14408
+ const lockPath = (0, import_node_path18.join)(root, "package-lock.json");
14409
+ if (!(0, import_node_fs19.existsSync)(lockPath)) return void 0;
13820
14410
  const lock = readJsonFile(lockPath);
13821
14411
  const node = lock?.packages?.[`node_modules/${packageName}`];
13822
14412
  const version = node?.version?.trim();
@@ -13845,18 +14435,8 @@ function designSystemSnapshot(root) {
13845
14435
 
13846
14436
  // src/design-system-registry.ts
13847
14437
  var import_node_crypto6 = require("node:crypto");
13848
- var import_node_fs19 = require("node:fs");
13849
- var import_node_path18 = require("node:path");
13850
-
13851
- // src/atomic-write.ts
13852
- var import_node_fs18 = require("node:fs");
13853
- function atomicWriteFileSync(path2, content) {
13854
- const tmp = `${path2}.${process.pid}.tmp`;
13855
- (0, import_node_fs18.writeFileSync)(tmp, content, "utf8");
13856
- (0, import_node_fs18.renameSync)(tmp, path2);
13857
- }
13858
-
13859
- // src/design-system-registry.ts
14438
+ var import_node_fs20 = require("node:fs");
14439
+ var import_node_path19 = require("node:path");
13860
14440
  var DESIGN_SYSTEM_CACHE_DIR = ".mmi/design-system/components";
13861
14441
  var DESIGN_SYSTEM_MANIFEST_PATH = ".mmi/design-system/manifest.json";
13862
14442
  var REGISTRY_COMPONENTS_LABEL = "@mutmutco registry components (.mmi cache vs live registry)";
@@ -13864,13 +14444,13 @@ var REGISTRY_FIX = "run `mmi-cli doctor --apply` to pull registry components int
13864
14444
  var REGISTRY_UNREACHABLE_FIX = "live @mutmutco registry unreachable \u2014 verify `components.json` `@mutmutco` registry URL and network, then retry `mmi-cli doctor`";
13865
14445
  function readJsonFile2(path2) {
13866
14446
  try {
13867
- return JSON.parse((0, import_node_fs19.readFileSync)(path2, "utf8"));
14447
+ return JSON.parse((0, import_node_fs20.readFileSync)(path2, "utf8"));
13868
14448
  } catch {
13869
14449
  return void 0;
13870
14450
  }
13871
14451
  }
13872
14452
  function readComponentsJson(root) {
13873
- return readJsonFile2((0, import_node_path18.join)(root, "components.json"));
14453
+ return readJsonFile2((0, import_node_path19.join)(root, "components.json"));
13874
14454
  }
13875
14455
  function hasMutmutcoRegistry(root) {
13876
14456
  const url = readComponentsJson(root)?.registries?.["@mutmutco"];
@@ -13878,7 +14458,7 @@ function hasMutmutcoRegistry(root) {
13878
14458
  }
13879
14459
  function resolveCacheDir(root) {
13880
14460
  const custom = readComponentsJson(root)?.mmi?.cacheDir;
13881
- return (0, import_node_path18.join)(root, custom ?? DESIGN_SYSTEM_CACHE_DIR);
14461
+ return (0, import_node_path19.join)(root, custom ?? DESIGN_SYSTEM_CACHE_DIR);
13882
14462
  }
13883
14463
  function resolveRegistryUrlTemplate(root) {
13884
14464
  return readComponentsJson(root)?.registries?.["@mutmutco"];
@@ -13887,7 +14467,7 @@ function registryItemUrl(template, name) {
13887
14467
  return template.replace("{name}", name);
13888
14468
  }
13889
14469
  function readDesignSystemManifest(root) {
13890
- const raw = readJsonFile2((0, import_node_path18.join)(root, DESIGN_SYSTEM_MANIFEST_PATH));
14470
+ const raw = readJsonFile2((0, import_node_path19.join)(root, DESIGN_SYSTEM_MANIFEST_PATH));
13891
14471
  if (!raw || !Array.isArray(raw.components)) return void 0;
13892
14472
  return raw;
13893
14473
  }
@@ -13899,11 +14479,11 @@ function listInstalledRegistryComponents(root) {
13899
14479
  return scanCachedComponentNames(resolveCacheDir(root));
13900
14480
  }
13901
14481
  function scanCachedComponentNames(cacheDir) {
13902
- if (!(0, import_node_fs19.existsSync)(cacheDir)) return [];
14482
+ if (!(0, import_node_fs20.existsSync)(cacheDir)) return [];
13903
14483
  const names = /* @__PURE__ */ new Set();
13904
14484
  const walk = (dir) => {
13905
- for (const ent of (0, import_node_fs19.readdirSync)(dir, { withFileTypes: true })) {
13906
- const p = (0, import_node_path18.join)(dir, ent.name);
14485
+ for (const ent of (0, import_node_fs20.readdirSync)(dir, { withFileTypes: true })) {
14486
+ const p = (0, import_node_path19.join)(dir, ent.name);
13907
14487
  if (ent.isDirectory()) walk(p);
13908
14488
  else if (ent.isFile() && /\.(tsx?|jsx?)$/.test(ent.name)) {
13909
14489
  names.add(ent.name.replace(/\.(tsx|ts|jsx|js)$/, ""));
@@ -13992,13 +14572,13 @@ async function gatherRegistryComponentsState(root, targetVersion, deps) {
13992
14572
  let componentStale = false;
13993
14573
  for (const file of item.files) {
13994
14574
  if (!file.target || file.content == null) continue;
13995
- const cachePath = (0, import_node_path18.join)(cacheDir, cacheRelativePath(file.target));
13996
- if (!(0, import_node_fs19.existsSync)(cachePath)) {
14575
+ const cachePath = (0, import_node_path19.join)(cacheDir, cacheRelativePath(file.target));
14576
+ if (!(0, import_node_fs20.existsSync)(cachePath)) {
13997
14577
  componentStale = true;
13998
14578
  break;
13999
14579
  }
14000
14580
  try {
14001
- if (contentHash((0, import_node_fs19.readFileSync)(cachePath, "utf8")) !== contentHash(file.content)) {
14581
+ if (contentHash((0, import_node_fs20.readFileSync)(cachePath, "utf8")) !== contentHash(file.content)) {
14002
14582
  componentStale = true;
14003
14583
  break;
14004
14584
  }
@@ -14008,7 +14588,7 @@ async function gatherRegistryComponentsState(root, targetVersion, deps) {
14008
14588
  }
14009
14589
  }
14010
14590
  if (componentStale) {
14011
- if ((0, import_node_fs19.existsSync)((0, import_node_path18.join)(cacheDir, "ui", `${name}.tsx`)) || (0, import_node_fs19.existsSync)((0, import_node_path18.join)(cacheDir, `${name}.tsx`))) {
14591
+ if ((0, import_node_fs20.existsSync)((0, import_node_path19.join)(cacheDir, "ui", `${name}.tsx`)) || (0, import_node_fs20.existsSync)((0, import_node_path19.join)(cacheDir, `${name}.tsx`))) {
14012
14592
  stale.push(name);
14013
14593
  } else {
14014
14594
  missing.push(name);
@@ -14036,15 +14616,15 @@ async function applyRegistryComponentsSync(root, components, targetVersion, log,
14036
14616
  if (!item) return { ok: false };
14037
14617
  for (const file of item.files) {
14038
14618
  if (!file.target || file.content == null) continue;
14039
- const outPath = (0, import_node_path18.join)(cacheDir, cacheRelativePath(file.target));
14040
- deps.mkdir((0, import_node_path18.dirname)(outPath));
14619
+ const outPath = (0, import_node_path19.join)(cacheDir, cacheRelativePath(file.target));
14620
+ deps.mkdir((0, import_node_path19.dirname)(outPath));
14041
14621
  const body = file.content.endsWith("\n") ? file.content : `${file.content}
14042
14622
  `;
14043
14623
  deps.writeFile(outPath, body);
14044
14624
  }
14045
14625
  }
14046
- const manifestPath = (0, import_node_path18.join)(root, DESIGN_SYSTEM_MANIFEST_PATH);
14047
- deps.mkdir((0, import_node_path18.dirname)(manifestPath));
14626
+ const manifestPath = (0, import_node_path19.join)(root, DESIGN_SYSTEM_MANIFEST_PATH);
14627
+ deps.mkdir((0, import_node_path19.dirname)(manifestPath));
14048
14628
  const manifest = {
14049
14629
  version: targetVersion,
14050
14630
  syncedAt: (/* @__PURE__ */ new Date()).toISOString(),
@@ -14059,7 +14639,7 @@ function defaultRegistrySyncDeps() {
14059
14639
  return {
14060
14640
  fetch,
14061
14641
  writeFile: (path2, content) => atomicWriteFileSync(path2, content),
14062
- mkdir: (path2) => (0, import_node_fs19.mkdirSync)(path2, { recursive: true })
14642
+ mkdir: (path2) => (0, import_node_fs20.mkdirSync)(path2, { recursive: true })
14063
14643
  };
14064
14644
  }
14065
14645
 
@@ -14085,12 +14665,12 @@ function renderVerifySecrets(body) {
14085
14665
  }
14086
14666
 
14087
14667
  // src/hotfix-coverage.ts
14088
- var import_node_child_process10 = require("node:child_process");
14668
+ var import_node_child_process11 = require("node:child_process");
14089
14669
  var CHERRY_TRAILER = /\(cherry picked from commit ([0-9a-f]{7,40})\)/g;
14090
14670
  function checkHotfixCoverage(options = {}) {
14091
14671
  const { cwd = process.cwd(), mainRef = "origin/main", rcRef = "origin/rc", manifestPaths = [] } = options;
14092
14672
  const ack = (options.ack ?? []).filter(Boolean);
14093
- const git = options.git ?? ((args, opts) => (0, import_node_child_process10.execFileSync)("git", args, { cwd, encoding: "utf8", input: opts?.input, stdio: ["pipe", "pipe", "pipe"] }));
14673
+ const git = options.git ?? ((args, opts) => (0, import_node_child_process11.execFileSync)("git", args, { cwd, encoding: "utf8", input: opts?.input, stdio: ["pipe", "pipe", "pipe"] }));
14094
14674
  const revList = (range) => {
14095
14675
  const out = git(["rev-list", "--no-merges", range]).trim();
14096
14676
  return out ? out.split("\n") : [];
@@ -14813,8 +15393,8 @@ async function announceRelease(deps, args) {
14813
15393
  }
14814
15394
 
14815
15395
  // src/port-registry.ts
14816
- var import_node_fs20 = require("node:fs");
14817
- var import_node_path19 = require("node:path");
15396
+ var import_node_fs21 = require("node:fs");
15397
+ var import_node_path20 = require("node:path");
14818
15398
 
14819
15399
  // ../infra/port-geometry.mjs
14820
15400
  var PORT_BLOCK = 100;
@@ -14828,8 +15408,8 @@ function nextPortBlock(registry2) {
14828
15408
  return [base, base + PORT_SPAN];
14829
15409
  }
14830
15410
  function loadPortRegistry(path2) {
14831
- if (!(0, import_node_fs20.existsSync)(path2)) return {};
14832
- const raw = JSON.parse((0, import_node_fs20.readFileSync)(path2, "utf8"));
15411
+ if (!(0, import_node_fs21.existsSync)(path2)) return {};
15412
+ const raw = JSON.parse((0, import_node_fs21.readFileSync)(path2, "utf8"));
14833
15413
  const out = {};
14834
15414
  for (const [key, value] of Object.entries(raw)) {
14835
15415
  if (Array.isArray(value) && value.length === 2 && value.every((n) => typeof n === "number")) {
@@ -14843,9 +15423,9 @@ function ensurePortRange(repo, path2) {
14843
15423
  const existing = registry2[repo];
14844
15424
  if (existing) return existing;
14845
15425
  const range = nextPortBlock(registry2);
14846
- const raw = (0, import_node_fs20.existsSync)(path2) ? JSON.parse((0, import_node_fs20.readFileSync)(path2, "utf8")) : {};
15426
+ const raw = (0, import_node_fs21.existsSync)(path2) ? JSON.parse((0, import_node_fs21.readFileSync)(path2, "utf8")) : {};
14847
15427
  raw[repo] = range;
14848
- (0, import_node_fs20.writeFileSync)(path2, JSON.stringify(raw, null, 2) + "\n", "utf8");
15428
+ (0, import_node_fs21.writeFileSync)(path2, JSON.stringify(raw, null, 2) + "\n", "utf8");
14849
15429
  return range;
14850
15430
  }
14851
15431
  function portCursorSeed(registry2) {
@@ -14867,18 +15447,18 @@ function existingPortRange(repo, registry2) {
14867
15447
  return registry2[repo] ?? null;
14868
15448
  }
14869
15449
  function portRangeInfraAt(root, source) {
14870
- const registryPath = (0, import_node_path19.join)(root, "infra", "port-ranges.json");
14871
- const ddbScriptPath = (0, import_node_path19.join)(root, "infra", "port-ddb.mjs");
14872
- if (!(0, import_node_fs20.existsSync)(registryPath) || !(0, import_node_fs20.existsSync)(ddbScriptPath)) return null;
15450
+ const registryPath = (0, import_node_path20.join)(root, "infra", "port-ranges.json");
15451
+ const ddbScriptPath = (0, import_node_path20.join)(root, "infra", "port-ddb.mjs");
15452
+ if (!(0, import_node_fs21.existsSync)(registryPath) || !(0, import_node_fs21.existsSync)(ddbScriptPath)) return null;
14873
15453
  return { root, source, registryPath, ddbScriptPath };
14874
15454
  }
14875
15455
  function resolvePortRangeInfra(cwd) {
14876
15456
  const direct = portRangeInfraAt(cwd, "cwd");
14877
15457
  if (direct) return direct;
14878
- for (let dir = cwd; ; dir = (0, import_node_path19.dirname)(dir)) {
14879
- const sibling = portRangeInfraAt((0, import_node_path19.join)(dir, "MMI-Hub"), "sibling-hub");
15458
+ for (let dir = cwd; ; dir = (0, import_node_path20.dirname)(dir)) {
15459
+ const sibling = portRangeInfraAt((0, import_node_path20.join)(dir, "MMI-Hub"), "sibling-hub");
14880
15460
  if (sibling) return sibling;
14881
- const parent = (0, import_node_path19.dirname)(dir);
15461
+ const parent = (0, import_node_path20.dirname)(dir);
14882
15462
  if (parent === dir) return null;
14883
15463
  }
14884
15464
  }
@@ -16592,8 +17172,12 @@ function resolveKbSource(rawBase) {
16592
17172
  if (!m) return DEFAULT_KB;
16593
17173
  return { owner: m[1], repo: m[2], ref: m[3] };
16594
17174
  }
16595
- function buildKbGetArgs(src, path2) {
17175
+ function normalizeKbPath(path2) {
16596
17176
  const clean4 = path2.replace(/^\/+/, "");
17177
+ return clean4 === "kb" || clean4.startsWith("kb/") ? clean4 : `kb/${clean4}`;
17178
+ }
17179
+ function buildKbGetArgs(src, path2) {
17180
+ const clean4 = normalizeKbPath(path2);
16597
17181
  return ["api", `repos/${src.owner}/${src.repo}/contents/${clean4}?ref=${src.ref}`, "-H", "Accept: application/vnd.github.raw"];
16598
17182
  }
16599
17183
  function buildKbTreeArgs(src) {
@@ -16606,20 +17190,20 @@ function parseKbTree(stdout, prefix) {
16606
17190
  } catch {
16607
17191
  return [];
16608
17192
  }
16609
- const pre = prefix ? prefix.replace(/^\/+/, "") : void 0;
16610
- return tree.filter((t) => t.type === "blob" && typeof t.path === "string" && t.path.startsWith("kb/")).map((t) => t.path).filter((p) => pre ? p.startsWith(pre) : true).sort();
17193
+ const pre = prefix ? normalizeKbPath(prefix) : void 0;
17194
+ return tree.filter((t) => t.type === "blob" && typeof t.path === "string" && t.path.startsWith("kb/") && !t.path.split("/").some((s) => s.startsWith("."))).map((t) => t.path).filter((p) => pre ? p.startsWith(pre) : true).sort();
16611
17195
  }
16612
17196
 
16613
17197
  // src/northstar-commands.ts
16614
- var import_node_fs21 = require("node:fs");
16615
- var import_node_child_process11 = require("node:child_process");
17198
+ var import_node_fs22 = require("node:fs");
17199
+ var import_node_child_process12 = require("node:child_process");
16616
17200
  var import_promises6 = require("node:fs/promises");
16617
17201
  var planSyncDetached = false;
16618
17202
  function detachPlanSync() {
16619
17203
  if (planSyncDetached) return;
16620
17204
  planSyncDetached = true;
16621
17205
  try {
16622
- (0, import_node_child_process11.spawn)(process.execPath, [process.argv[1], "northstar", "sync", "--quiet"], {
17206
+ (0, import_node_child_process12.spawn)(process.execPath, [process.argv[1], "northstar", "sync", "--quiet"], {
16623
17207
  detached: true,
16624
17208
  stdio: "ignore",
16625
17209
  windowsHide: true,
@@ -16629,7 +17213,7 @@ function detachPlanSync() {
16629
17213
  }
16630
17214
  }
16631
17215
  function makePlanDeps(cfg, io = consoleIo) {
16632
- const ensureDir = () => (0, import_node_fs21.mkdirSync)(PLANS_DIR, { recursive: true });
17216
+ const ensureDir = () => (0, import_node_fs22.mkdirSync)(PLANS_DIR, { recursive: true });
16633
17217
  return {
16634
17218
  apiUrl: cfg.sagaApiUrl,
16635
17219
  fetch: (url, init = {}) => fetch(url, { ...init, signal: init.signal ?? AbortSignal.timeout(1e4) }),
@@ -16637,31 +17221,31 @@ function makePlanDeps(cfg, io = consoleIo) {
16637
17221
  project: async () => (await sagaKey(cfg)).project,
16638
17222
  readLocal: (slug) => {
16639
17223
  try {
16640
- return (0, import_node_fs21.readFileSync)(planPath(slug), "utf8");
17224
+ return (0, import_node_fs22.readFileSync)(planPath(slug), "utf8");
16641
17225
  } catch {
16642
17226
  return null;
16643
17227
  }
16644
17228
  },
16645
17229
  listLocalSlugs: () => {
16646
17230
  try {
16647
- return (0, import_node_fs21.readdirSync)(PLANS_DIR, { withFileTypes: true }).filter((entry) => entry.isFile() && /^[A-Za-z0-9][A-Za-z0-9_-]*\.md$/.test(entry.name)).map((entry) => entry.name.replace(/\.md$/, ""));
17231
+ return (0, import_node_fs22.readdirSync)(PLANS_DIR, { withFileTypes: true }).filter((entry) => entry.isFile() && /^[A-Za-z0-9][A-Za-z0-9_-]*\.md$/.test(entry.name)).map((entry) => entry.name.replace(/\.md$/, ""));
16648
17232
  } catch {
16649
17233
  return [];
16650
17234
  }
16651
17235
  },
16652
17236
  writeLocal: (slug, content) => {
16653
17237
  ensureDir();
16654
- (0, import_node_fs21.writeFileSync)(planPath(slug), content, "utf8");
17238
+ (0, import_node_fs22.writeFileSync)(planPath(slug), content, "utf8");
16655
17239
  },
16656
17240
  removeLocal: (slug) => {
16657
17241
  try {
16658
- (0, import_node_fs21.rmSync)(planPath(slug));
17242
+ (0, import_node_fs22.rmSync)(planPath(slug));
16659
17243
  } catch {
16660
17244
  }
16661
17245
  },
16662
17246
  readMetaRaw: () => {
16663
17247
  try {
16664
- return (0, import_node_fs21.readFileSync)(META_FILE, "utf8");
17248
+ return (0, import_node_fs22.readFileSync)(META_FILE, "utf8");
16665
17249
  } catch {
16666
17250
  return null;
16667
17251
  }
@@ -16672,7 +17256,7 @@ function makePlanDeps(cfg, io = consoleIo) {
16672
17256
  },
16673
17257
  readIndexRaw: () => {
16674
17258
  try {
16675
- return (0, import_node_fs21.readFileSync)(INDEX_FILE, "utf8");
17259
+ return (0, import_node_fs22.readFileSync)(INDEX_FILE, "utf8");
16676
17260
  } catch {
16677
17261
  return null;
16678
17262
  }
@@ -16683,7 +17267,7 @@ function makePlanDeps(cfg, io = consoleIo) {
16683
17267
  },
16684
17268
  readQueueRaw: () => {
16685
17269
  try {
16686
- return (0, import_node_fs21.readFileSync)(QUEUE_FILE, "utf8");
17270
+ return (0, import_node_fs22.readFileSync)(QUEUE_FILE, "utf8");
16687
17271
  } catch {
16688
17272
  return null;
16689
17273
  }
@@ -16705,7 +17289,7 @@ function openInEditor(path2) {
16705
17289
  return;
16706
17290
  }
16707
17291
  try {
16708
- (0, import_node_child_process11.spawn)(editor, [path2], { stdio: "inherit" });
17292
+ (0, import_node_child_process12.spawn)(editor, [path2], { stdio: "inherit" });
16709
17293
  } catch {
16710
17294
  console.log(`open ${path2} manually`);
16711
17295
  }
@@ -16743,7 +17327,7 @@ function repoInfoFromRemote(remote) {
16743
17327
  }
16744
17328
  function readStageUrl() {
16745
17329
  try {
16746
- const state = JSON.parse((0, import_node_fs21.readFileSync)("tmp/stage/state.json", "utf8"));
17330
+ const state = JSON.parse((0, import_node_fs22.readFileSync)("tmp/stage/state.json", "utf8"));
16747
17331
  if (typeof state.url === "string" && state.url.trim()) return state.url.trim();
16748
17332
  if (typeof state.port === "number" && Number.isFinite(state.port)) return `http://127.0.0.1:${state.port}/`;
16749
17333
  if (typeof state.healthUrl === "string" && state.healthUrl.trim()) {
@@ -17483,8 +18067,8 @@ async function secretsUse(deps, key, opts) {
17483
18067
  }
17484
18068
 
17485
18069
  // src/secrets-commands.ts
17486
- var import_node_fs22 = require("node:fs");
17487
- var import_node_path20 = require("node:path");
18070
+ var import_node_fs23 = require("node:fs");
18071
+ var import_node_path21 = require("node:path");
17488
18072
  var RAILS_CREDENTIALS_DECRYPT_TIMEOUT_MS = 3e4;
17489
18073
  var DEFAULT_RAILS_CREDENTIALS_FILE = "config/credentials.yml.enc";
17490
18074
  var DEFAULT_RAILS_MASTER_KEY_FILE = "config/master.key";
@@ -17492,18 +18076,18 @@ function collectMap(value, previous = []) {
17492
18076
  return [...previous, value];
17493
18077
  }
17494
18078
  async function decryptRailsCredentials(input) {
17495
- const appDir = (0, import_node_path20.resolve)(input.appDir ?? process.cwd());
18079
+ const appDir = (0, import_node_path21.resolve)(input.appDir ?? process.cwd());
17496
18080
  const credentialsFile = input.credentialsFile ?? DEFAULT_RAILS_CREDENTIALS_FILE;
17497
18081
  const masterKeyFile = input.masterKeyFile ?? DEFAULT_RAILS_MASTER_KEY_FILE;
17498
- const credentialsPath = (0, import_node_path20.resolve)(appDir, credentialsFile);
17499
- const masterKeyPath = (0, import_node_path20.resolve)(appDir, masterKeyFile);
18082
+ const credentialsPath = (0, import_node_path21.resolve)(appDir, credentialsFile);
18083
+ const masterKeyPath = (0, import_node_path21.resolve)(appDir, masterKeyFile);
17500
18084
  const env = {
17501
18085
  ...process.env,
17502
18086
  MMI_RAILS_CREDENTIALS_FILE: credentialsPath,
17503
18087
  MMI_RAILS_MASTER_KEY_FILE: masterKeyPath
17504
18088
  };
17505
- if ((0, import_node_fs22.existsSync)(masterKeyPath)) {
17506
- env.RAILS_MASTER_KEY = (0, import_node_fs22.readFileSync)(masterKeyPath, "utf8").trim();
18089
+ if ((0, import_node_fs23.existsSync)(masterKeyPath)) {
18090
+ env.RAILS_MASTER_KEY = (0, import_node_fs23.readFileSync)(masterKeyPath, "utf8").trim();
17507
18091
  }
17508
18092
  const script = [
17509
18093
  'require "json"',
@@ -17628,7 +18212,7 @@ function registerSecretsCommands(program3) {
17628
18212
  {
17629
18213
  ...d,
17630
18214
  decryptRailsCredentials,
17631
- removeFile: (path2) => (0, import_node_fs22.unlinkSync)((0, import_node_path20.resolve)(o.appDir ?? process.cwd(), path2))
18215
+ removeFile: (path2) => (0, import_node_fs23.unlinkSync)((0, import_node_path21.resolve)(o.appDir ?? process.cwd(), path2))
17632
18216
  },
17633
18217
  {
17634
18218
  repo: o.repo,
@@ -17804,10 +18388,11 @@ function authorizeBodyHasMismatch(body) {
17804
18388
  }
17805
18389
 
17806
18390
  // src/doctor-run.ts
17807
- var import_node_fs27 = require("node:fs");
18391
+ var import_node_fs28 = require("node:fs");
18392
+ var import_node_child_process14 = require("node:child_process");
17808
18393
  var import_promises7 = require("node:fs/promises");
17809
- var import_node_path24 = require("node:path");
17810
- var import_node_os5 = require("node:os");
18394
+ var import_node_path25 = require("node:path");
18395
+ var import_node_os6 = require("node:os");
17811
18396
 
17812
18397
  // src/plugin-guard.ts
17813
18398
  function buildPluginGuardDecision(i) {
@@ -17827,10 +18412,10 @@ function buildGuardSessionStartLine(state, opts = {}) {
17827
18412
  }
17828
18413
 
17829
18414
  // src/cursor-plugin-seed.ts
17830
- var import_node_child_process12 = require("node:child_process");
17831
- var import_node_fs23 = require("node:fs");
17832
- var import_node_os4 = require("node:os");
17833
- var import_node_path21 = require("node:path");
18415
+ var import_node_child_process13 = require("node:child_process");
18416
+ var import_node_fs24 = require("node:fs");
18417
+ var import_node_os5 = require("node:os");
18418
+ var import_node_path22 = require("node:path");
17834
18419
  var import_node_util7 = require("node:util");
17835
18420
  function isSemverVersion(v) {
17836
18421
  return typeof v === "string" && /^v?\d+\.\d+\.\d+/.test(v.trim());
@@ -17838,7 +18423,7 @@ function isSemverVersion(v) {
17838
18423
  var MMI_HUB_REPO = "mutmutco/MMI-Hub";
17839
18424
  var CURSOR_THIRD_PARTY_STATE_KEY = "cursor/thirdPartyExtensibilityEnabled";
17840
18425
  var PLUGIN_JSON_REL = ".cursor-plugin/plugin.json";
17841
- var execFileBuffer = (0, import_node_util7.promisify)(import_node_child_process12.execFile);
18426
+ var execFileBuffer = (0, import_node_util7.promisify)(import_node_child_process13.execFile);
17842
18427
  function gitFetchReleaseTagArgs(hubCheckout, tag) {
17843
18428
  return ["-C", hubCheckout, "fetch", "origin", "tag", tag, "--quiet"];
17844
18429
  }
@@ -17847,17 +18432,17 @@ function ghReleaseTarballApiArgs(tag) {
17847
18432
  }
17848
18433
  function cursorUserGlobalStatePath() {
17849
18434
  if (process.platform === "win32") {
17850
- const base = process.env.APPDATA || (0, import_node_path21.join)((0, import_node_os4.homedir)(), "AppData", "Roaming");
17851
- return (0, import_node_path21.join)(base, "Cursor", "User", "globalStorage", "state.vscdb");
18435
+ const base = process.env.APPDATA || (0, import_node_path22.join)((0, import_node_os5.homedir)(), "AppData", "Roaming");
18436
+ return (0, import_node_path22.join)(base, "Cursor", "User", "globalStorage", "state.vscdb");
17852
18437
  }
17853
18438
  if (process.platform === "darwin") {
17854
- return (0, import_node_path21.join)((0, import_node_os4.homedir)(), "Library", "Application Support", "Cursor", "User", "globalStorage", "state.vscdb");
18439
+ return (0, import_node_path22.join)((0, import_node_os5.homedir)(), "Library", "Application Support", "Cursor", "User", "globalStorage", "state.vscdb");
17855
18440
  }
17856
- return (0, import_node_path21.join)((0, import_node_os4.homedir)(), ".config", "Cursor", "User", "globalStorage", "state.vscdb");
18441
+ return (0, import_node_path22.join)((0, import_node_os5.homedir)(), ".config", "Cursor", "User", "globalStorage", "state.vscdb");
17857
18442
  }
17858
18443
  async function readCursorThirdPartyExtensibilityEnabled(execFileP5) {
17859
18444
  const dbPath = cursorUserGlobalStatePath();
17860
- if (!(0, import_node_fs23.existsSync)(dbPath)) return void 0;
18445
+ if (!(0, import_node_fs24.existsSync)(dbPath)) return void 0;
17861
18446
  try {
17862
18447
  const { stdout } = await execFileP5("sqlite3", [dbPath, `SELECT value FROM ItemTable WHERE key = '${CURSOR_THIRD_PARTY_STATE_KEY}';`], {
17863
18448
  timeout: 5e3
@@ -17871,57 +18456,57 @@ async function readCursorThirdPartyExtensibilityEnabled(execFileP5) {
17871
18456
  }
17872
18457
  }
17873
18458
  function syncDirContents(src, dest) {
17874
- (0, import_node_fs23.mkdirSync)(dest, { recursive: true });
17875
- for (const name of (0, import_node_fs23.readdirSync)(dest)) {
17876
- (0, import_node_fs23.rmSync)((0, import_node_path21.join)(dest, name), { recursive: true, force: true });
18459
+ (0, import_node_fs24.mkdirSync)(dest, { recursive: true });
18460
+ for (const name of (0, import_node_fs24.readdirSync)(dest)) {
18461
+ (0, import_node_fs24.rmSync)((0, import_node_path22.join)(dest, name), { recursive: true, force: true });
17877
18462
  }
17878
- (0, import_node_fs23.cpSync)(src, dest, { recursive: true });
18463
+ (0, import_node_fs24.cpSync)(src, dest, { recursive: true });
17879
18464
  }
17880
18465
  function releaseTag(releasedVersion) {
17881
18466
  return releasedVersion.startsWith("v") ? releasedVersion : `v${releasedVersion}`;
17882
18467
  }
17883
18468
  async function extractPluginMmiFromHubCheckout(hubCheckout, tag, tmpRoot, execFileP5) {
17884
- const tarFile = (0, import_node_path21.join)(tmpRoot, "archive.tar");
18469
+ const tarFile = (0, import_node_path22.join)(tmpRoot, "archive.tar");
17885
18470
  try {
17886
18471
  await execFileP5("git", gitFetchReleaseTagArgs(hubCheckout, tag), { timeout: 6e4 });
17887
18472
  await execFileP5("git", ["-C", hubCheckout, "archive", "--format=tar", `--output=${tarFile}`, tag, "plugins/mmi"], {
17888
18473
  timeout: 6e4
17889
18474
  });
17890
18475
  await execFileP5("tar", ["-xf", tarFile, "-C", tmpRoot], { timeout: 6e4 });
17891
- const pluginMmi = (0, import_node_path21.join)(tmpRoot, "plugins", "mmi");
17892
- return (0, import_node_fs23.existsSync)((0, import_node_path21.join)(pluginMmi, PLUGIN_JSON_REL)) ? pluginMmi : void 0;
18476
+ const pluginMmi = (0, import_node_path22.join)(tmpRoot, "plugins", "mmi");
18477
+ return (0, import_node_fs24.existsSync)((0, import_node_path22.join)(pluginMmi, PLUGIN_JSON_REL)) ? pluginMmi : void 0;
17893
18478
  } catch {
17894
18479
  return void 0;
17895
18480
  }
17896
18481
  }
17897
18482
  async function downloadPluginMmiViaGh(tag, tmpRoot) {
17898
- const tarPath = (0, import_node_path21.join)(tmpRoot, "repo.tgz");
18483
+ const tarPath = (0, import_node_path22.join)(tmpRoot, "repo.tgz");
17899
18484
  try {
17900
- (0, import_node_fs23.mkdirSync)(tmpRoot, { recursive: true });
18485
+ (0, import_node_fs24.mkdirSync)(tmpRoot, { recursive: true });
17901
18486
  const { stdout } = await execFileBuffer("gh", ghReleaseTarballApiArgs(tag), {
17902
18487
  timeout: 12e4,
17903
18488
  maxBuffer: 100 * 1024 * 1024,
17904
18489
  encoding: "buffer",
17905
18490
  windowsHide: true
17906
18491
  });
17907
- (0, import_node_fs23.writeFileSync)(tarPath, stdout);
18492
+ (0, import_node_fs24.writeFileSync)(tarPath, stdout);
17908
18493
  await execFileBuffer("tar", ["-xzf", tarPath, "-C", tmpRoot], { timeout: 12e4, windowsHide: true });
17909
- const top = (0, import_node_fs23.readdirSync)(tmpRoot).find((entry) => entry !== "repo.tgz");
18494
+ const top = (0, import_node_fs24.readdirSync)(tmpRoot).find((entry) => entry !== "repo.tgz");
17910
18495
  if (!top) return void 0;
17911
- const pluginMmi = (0, import_node_path21.join)(tmpRoot, top, "plugins", "mmi");
17912
- return (0, import_node_fs23.existsSync)((0, import_node_path21.join)(pluginMmi, PLUGIN_JSON_REL)) ? pluginMmi : void 0;
18496
+ const pluginMmi = (0, import_node_path22.join)(tmpRoot, top, "plugins", "mmi");
18497
+ return (0, import_node_fs24.existsSync)((0, import_node_path22.join)(pluginMmi, PLUGIN_JSON_REL)) ? pluginMmi : void 0;
17913
18498
  } catch {
17914
18499
  return void 0;
17915
18500
  }
17916
18501
  }
17917
18502
  async function resolvePluginMmiSource(releasedVersion, hubCheckout, tmpRoot, execFileP5) {
17918
- (0, import_node_fs23.mkdirSync)(tmpRoot, { recursive: true });
18503
+ (0, import_node_fs24.mkdirSync)(tmpRoot, { recursive: true });
17919
18504
  const tag = releaseTag(releasedVersion);
17920
18505
  if (hubCheckout) {
17921
18506
  const fromHub = await extractPluginMmiFromHubCheckout(hubCheckout, tag, tmpRoot, execFileP5);
17922
18507
  if (fromHub) return fromHub;
17923
18508
  }
17924
- return downloadPluginMmiViaGh(tag, (0, import_node_path21.join)(tmpRoot, "gh"));
18509
+ return downloadPluginMmiViaGh(tag, (0, import_node_path22.join)(tmpRoot, "gh"));
17925
18510
  }
17926
18511
  function cursorPluginPinsNeedingSeed(pins, releasedVersion) {
17927
18512
  if (!isSemverVersion(releasedVersion)) return pins.filter((pin) => !pin.hasPluginJson || !pin.hasHooksJson || pin.isEmpty);
@@ -17942,7 +18527,7 @@ async function applyCursorPluginCacheSeed(input) {
17942
18527
  for (const pin of pinsToSeed) {
17943
18528
  syncDirContents(source, pin.path);
17944
18529
  }
17945
- (0, import_node_fs23.rmSync)(tmpRoot, { recursive: true, force: true });
18530
+ (0, import_node_fs24.rmSync)(tmpRoot, { recursive: true, force: true });
17946
18531
  return true;
17947
18532
  }
17948
18533
 
@@ -18269,6 +18854,30 @@ function buildNestedPluginTreeCheck(input) {
18269
18854
  fix: `${nested.length} self-nested MMI plugin cache tree(s) (#1126) exceed MAX_PATH and can't self-clean \u2014 run: ${nestedPluginTreeCleanupCommand(nested.map((n) => n.path), input.isWindows)}`
18270
18855
  };
18271
18856
  }
18857
+ var CODEX_ACTIVE_CACHE_LABEL = "Codex active plugin cache (vs latest release)";
18858
+ function buildCodexActiveCacheCheck(input) {
18859
+ const base = {
18860
+ ok: true,
18861
+ label: CODEX_ACTIVE_CACHE_LABEL,
18862
+ fix: CODEX_PLUGIN_RECOVERY
18863
+ };
18864
+ if (!input.isOrgRepo || !isSemverVersion2(input.releasedVersion)) return base;
18865
+ if (isSemverVersion2(input.codexRecordVersion) && compareVersions(input.codexRecordVersion, input.releasedVersion) < 0) {
18866
+ return base;
18867
+ }
18868
+ const activeCacheVersion = highestSemver(input.codexCacheVersions ?? []);
18869
+ const cacheCurrent = isSemverVersion2(activeCacheVersion) && compareVersions(activeCacheVersion, input.releasedVersion) >= 0;
18870
+ if (cacheCurrent) {
18871
+ return { ...base, activeCacheVersion, releasedVersion: input.releasedVersion };
18872
+ }
18873
+ return {
18874
+ ...base,
18875
+ ok: false,
18876
+ activeCacheVersion,
18877
+ releasedVersion: input.releasedVersion,
18878
+ fix: `Codex active plugin cache is ${activeCacheVersion ?? "missing the released version"} < ${input.releasedVersion} while the installed record is current \u2014 run \`mmi-cli doctor --apply\` (forces the Codex reinstall when \`codex\` is on PATH; restart Codex after), or manually: ${CODEX_PLUGIN_RECOVERY}`
18879
+ };
18880
+ }
18272
18881
  function detectSurface(env) {
18273
18882
  const has = (k) => Boolean(env[k]?.trim());
18274
18883
  if (env.MMI_AGENT_SURFACE === "codex" || has("CODEX_HOME") || (env.CLAUDE_PLUGIN_ROOT ?? "").includes(".codex")) {
@@ -18397,6 +19006,25 @@ var CODEX_PLUGIN_HEAL_STEPS = PLUGIN_SURFACE_HEAL.codex.healSteps;
18397
19006
  function healStepAborts(step, ok) {
18398
19007
  return !ok && step.gated;
18399
19008
  }
19009
+ function marketplaceAddSupportsRef(helpText) {
19010
+ if (!helpText) return false;
19011
+ return /(^|\s)--ref(\b|=)/.test(helpText);
19012
+ }
19013
+ function adaptHealStepsForRefSupport(steps, refSupported) {
19014
+ if (refSupported) return { steps: [...steps], strippedRef: false };
19015
+ let strippedRef = false;
19016
+ const adapted = steps.map((step) => {
19017
+ const refIdx = step.args.indexOf("--ref");
19018
+ const isAdd = step.args.includes("marketplace") && step.args.includes("add");
19019
+ if (!isAdd || refIdx === -1) return step;
19020
+ strippedRef = true;
19021
+ return { ...step, args: [...step.args.slice(0, refIdx), ...step.args.slice(refIdx + 2)] };
19022
+ });
19023
+ return { steps: adapted, strippedRef };
19024
+ }
19025
+ function recoveryWithoutRef(recovery) {
19026
+ return recovery.replace(/ --ref \S+/g, "");
19027
+ }
18400
19028
  function pluginRecoveryFix(surface) {
18401
19029
  const token = surfaceToken(surface);
18402
19030
  if (token) return PLUGIN_SURFACE_HEAL[token].fix(surface);
@@ -18737,7 +19365,7 @@ function cursorPluginInstallFix(input) {
18737
19365
  const cacheDir = joinCachePath(input.cacheRoot, pin);
18738
19366
  const autoSeed = "run `mmi-cli doctor --apply` to seed plugins/mmi from the latest release into the active pin";
18739
19367
  const localSeed = input.hubCheckout ? `temporary fallback: copy ${joinCachePath(input.hubCheckout, "plugins", "mmi")} to ${cacheDir}, then restart Cursor` : `temporary fallback: copy plugins/mmi from a local MMI-Hub checkout to ${cacheDir}, then restart Cursor`;
18740
- return `Cursor plugin cache at ${cacheDir} is empty or missing ${CURSOR_PLUGIN_JSON_REL} or ${CURSOR_HOOKS_JSON_REL} \u2014 ${autoSeed}; ${marketplaceRefresh}; ${authSteps}; ${localSeed}; ${logHint}; ${guide}`;
19368
+ return `Cursor plugin cache at ${cacheDir} is empty or missing ${CURSOR_PLUGIN_JSON_REL}, ${CURSOR_HOOKS_JSON_REL}, or the shell-dialect guard scripts \u2014 ${autoSeed}; ${marketplaceRefresh}; ${authSteps}; ${localSeed}; ${logHint}; ${guide}`;
18741
19369
  }
18742
19370
  function buildCursorPluginInstallCheck(input) {
18743
19371
  const base = {
@@ -18759,7 +19387,7 @@ function buildCursorPluginInstallCheck(input) {
18759
19387
  };
18760
19388
  }
18761
19389
  for (const pin of input.pins) {
18762
- if (!pin.hasPluginJson || !pin.hasHooksJson || pin.isEmpty) {
19390
+ if (!pin.hasPluginJson || !pin.hasHooksJson || pin.hasShellDialectGuard === false || pin.isEmpty) {
18763
19391
  return {
18764
19392
  ...base,
18765
19393
  ok: false,
@@ -18976,6 +19604,22 @@ function buildSelfUpdateHaltPayload(input) {
18976
19604
  checks: input.checks
18977
19605
  };
18978
19606
  }
19607
+ var DOCTOR_POST_SELF_UPDATE_ENV = "MMI_DOCTOR_POST_SELF_UPDATE";
19608
+ function buildSelfUpdateReexecArgs(opts) {
19609
+ const args = ["doctor"];
19610
+ if (opts.banner) args.push("--banner");
19611
+ if (opts.preflight) args.push("--preflight");
19612
+ if (opts.verbose) args.push("--verbose");
19613
+ if (opts.guide) args.push("--guide");
19614
+ if (opts.json) args.push("--json");
19615
+ if (opts.apply) args.push("--apply");
19616
+ if (opts.noRepoWrites) args.push("--no-repo-writes");
19617
+ return args;
19618
+ }
19619
+ function selfUpdateReexecLine(report) {
19620
+ const to = report.releasedVersion ?? "latest";
19621
+ return `\u21BB mmi-cli updated \u2192 ${to}. Re-running \`mmi-cli doctor\` under the new CLI\u2026`;
19622
+ }
18979
19623
  function preflightOutcome(input) {
18980
19624
  if (input.gaps.length) {
18981
19625
  return { healed: false, line: `\u26A0 MMI preflight: ${input.gaps.length} item(s) still need attention \u2014 ${input.gaps.map((g) => g.fix).join(" \xB7 ")}` };
@@ -19051,16 +19695,16 @@ function buildPluginResolvabilityCheck(input) {
19051
19695
  }
19052
19696
 
19053
19697
  // src/kb-drift-report.ts
19054
- var import_node_fs24 = require("node:fs");
19055
- var import_node_path22 = require("node:path");
19698
+ var import_node_fs25 = require("node:fs");
19699
+ var import_node_path23 = require("node:path");
19056
19700
  function yesterdayIso() {
19057
19701
  const d = /* @__PURE__ */ new Date();
19058
19702
  d.setUTCDate(d.getUTCDate() - 1);
19059
19703
  return d.toISOString().slice(0, 10);
19060
19704
  }
19061
19705
  async function fetchLatestKbDriftReport(execFileP5, repoRoot) {
19062
- const sagaIo = (0, import_node_path22.join)(repoRoot, "infra", "saga-io.mjs");
19063
- if (!(0, import_node_fs24.existsSync)(sagaIo)) return null;
19706
+ const sagaIo = (0, import_node_path23.join)(repoRoot, "infra", "saga-io.mjs");
19707
+ if (!(0, import_node_fs25.existsSync)(sagaIo)) return null;
19064
19708
  const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
19065
19709
  for (const date of [today, yesterdayIso()]) {
19066
19710
  try {
@@ -19076,9 +19720,9 @@ async function fetchLatestKbDriftReport(execFileP5, repoRoot) {
19076
19720
  }
19077
19721
 
19078
19722
  // src/cli-doctor-shared.ts
19079
- var import_node_fs25 = require("node:fs");
19080
- var import_node_path23 = require("node:path");
19081
19723
  var import_node_fs26 = require("node:fs");
19724
+ var import_node_path24 = require("node:path");
19725
+ var import_node_fs27 = require("node:fs");
19082
19726
  var GC_GH_TIMEOUT_MS = 2e4;
19083
19727
  async function awsCallerArn() {
19084
19728
  try {
@@ -19124,7 +19768,7 @@ async function localBranchHeads() {
19124
19768
  }
19125
19769
  async function currentRepoWorktreeGitRoot(repoRoot) {
19126
19770
  const gitCommonDir = (await execFileP2("git", ["rev-parse", "--git-common-dir"], { timeout: GIT_TIMEOUT_MS }).catch(() => ({ stdout: "" }))).stdout.trim();
19127
- return gitCommonDir ? (0, import_node_path23.resolve)(repoRoot, gitCommonDir, "worktrees") : "";
19771
+ return gitCommonDir ? (0, import_node_path24.resolve)(repoRoot, gitCommonDir, "worktrees") : "";
19128
19772
  }
19129
19773
  async function worktreeBranches() {
19130
19774
  const { stdout } = await execFileP2("git", ["worktree", "list", "--porcelain"], { timeout: GIT_TIMEOUT_MS });
@@ -19144,18 +19788,18 @@ function resolveGitdirForWorktreeFile(worktreePath, content) {
19144
19788
  const match = /^gitdir:\s*(.+)\s*$/im.exec(content);
19145
19789
  if (!match?.[1]) return void 0;
19146
19790
  const raw = match[1].trim();
19147
- return (0, import_node_path23.isAbsolute)(raw) ? raw : (0, import_node_path23.resolve)(worktreePath, raw);
19791
+ return (0, import_node_path24.isAbsolute)(raw) ? raw : (0, import_node_path24.resolve)(worktreePath, raw);
19148
19792
  }
19149
19793
  function metadataOwnsMissingWorktreeDir(worktreePath, worktreeGitRoot) {
19150
19794
  if (!worktreeGitRoot) return false;
19151
19795
  try {
19152
- const entries = (0, import_node_fs26.readdirSync)(worktreeGitRoot, { withFileTypes: true });
19796
+ const entries = (0, import_node_fs27.readdirSync)(worktreeGitRoot, { withFileTypes: true });
19153
19797
  for (const ent of entries) {
19154
19798
  if (!ent.isDirectory()) continue;
19155
19799
  try {
19156
- const gitdirPath = (0, import_node_fs25.readFileSync)((0, import_node_path23.join)(worktreeGitRoot, ent.name, "gitdir"), "utf8").trim();
19157
- const resolvedGitdir = (0, import_node_path23.isAbsolute)(gitdirPath) ? gitdirPath : (0, import_node_path23.resolve)(worktreeGitRoot, ent.name, gitdirPath);
19158
- if (sameWorktreeMetadataPath((0, import_node_path23.dirname)(resolvedGitdir), worktreePath)) return true;
19800
+ const gitdirPath = (0, import_node_fs26.readFileSync)((0, import_node_path24.join)(worktreeGitRoot, ent.name, "gitdir"), "utf8").trim();
19801
+ const resolvedGitdir = (0, import_node_path24.isAbsolute)(gitdirPath) ? gitdirPath : (0, import_node_path24.resolve)(worktreeGitRoot, ent.name, gitdirPath);
19802
+ if (sameWorktreeMetadataPath((0, import_node_path24.dirname)(resolvedGitdir), worktreePath)) return true;
19159
19803
  } catch {
19160
19804
  }
19161
19805
  }
@@ -19165,7 +19809,7 @@ function metadataOwnsMissingWorktreeDir(worktreePath, worktreeGitRoot) {
19165
19809
  }
19166
19810
  function pathExistsKnown(path2) {
19167
19811
  try {
19168
- (0, import_node_fs26.statSync)(path2);
19812
+ (0, import_node_fs27.statSync)(path2);
19169
19813
  return true;
19170
19814
  } catch (e) {
19171
19815
  const code = typeof e === "object" && e && "code" in e ? String(e.code ?? "") : "";
@@ -19174,10 +19818,10 @@ function pathExistsKnown(path2) {
19174
19818
  }
19175
19819
  }
19176
19820
  function inspectSiblingWorktreeDir(path2, worktreeGitRoot) {
19177
- const gitPath = (0, import_node_path23.join)(path2, ".git");
19821
+ const gitPath = (0, import_node_path24.join)(path2, ".git");
19178
19822
  let st;
19179
19823
  try {
19180
- st = (0, import_node_fs26.lstatSync)(gitPath);
19824
+ st = (0, import_node_fs27.lstatSync)(gitPath);
19181
19825
  } catch (e) {
19182
19826
  const code = typeof e === "object" && e && "code" in e ? String(e.code ?? "") : "";
19183
19827
  if (code === "ENOENT" || code === "ENOTDIR") {
@@ -19194,7 +19838,7 @@ function inspectSiblingWorktreeDir(path2, worktreeGitRoot) {
19194
19838
  if (st.isDirectory()) return { path: path2, gitType: "dir" };
19195
19839
  if (!st.isFile()) return { path: path2, gitType: "other" };
19196
19840
  try {
19197
- const gitFileContent = (0, import_node_fs25.readFileSync)(gitPath, "utf8");
19841
+ const gitFileContent = (0, import_node_fs26.readFileSync)(gitPath, "utf8");
19198
19842
  const gitdir = resolveGitdirForWorktreeFile(path2, gitFileContent);
19199
19843
  const gitDirExists = gitdir ? pathExistsKnown(gitdir) : false;
19200
19844
  return {
@@ -19211,7 +19855,7 @@ function inspectSiblingWorktreeDir(path2, worktreeGitRoot) {
19211
19855
  }
19212
19856
  function inspectDeadWorktreeDirContent(path2) {
19213
19857
  try {
19214
- return { entries: (0, import_node_fs26.readdirSync)(path2) };
19858
+ return { entries: (0, import_node_fs27.readdirSync)(path2) };
19215
19859
  } catch (e) {
19216
19860
  const code = typeof e === "object" && e && "code" in e ? String(e.code ?? "") : "";
19217
19861
  return { error: code ? `unable to inspect directory contents (${code})` : "unable to inspect directory contents" };
@@ -19230,8 +19874,8 @@ async function siblingWorktreeDirs() {
19230
19874
  const worktreeGitRoot = await currentRepoWorktreeGitRoot(repoRoot);
19231
19875
  const siblingRoot = siblingMmiWorktreesRoot(repoRoot);
19232
19876
  try {
19233
- const entries = (0, import_node_fs26.readdirSync)(siblingRoot, { withFileTypes: true });
19234
- return entries.filter((ent) => ent.isDirectory()).map((ent) => inspectSiblingWorktreeDir((0, import_node_path23.join)(siblingRoot, ent.name), worktreeGitRoot)).filter((entry) => Boolean(entry));
19877
+ const entries = (0, import_node_fs27.readdirSync)(siblingRoot, { withFileTypes: true });
19878
+ return entries.filter((ent) => ent.isDirectory()).map((ent) => inspectSiblingWorktreeDir((0, import_node_path24.join)(siblingRoot, ent.name), worktreeGitRoot)).filter((entry) => Boolean(entry));
19235
19879
  } catch {
19236
19880
  return [];
19237
19881
  }
@@ -19281,7 +19925,7 @@ async function fetchHubVersionInfo(baseUrl) {
19281
19925
  }
19282
19926
  function readRepoVersion() {
19283
19927
  try {
19284
- return JSON.parse((0, import_node_fs27.readFileSync)((0, import_node_path24.join)(process.cwd(), ".claude-plugin", "plugin.json"), "utf8")).version || void 0;
19928
+ return JSON.parse((0, import_node_fs28.readFileSync)((0, import_node_path25.join)(process.cwd(), ".claude-plugin", "plugin.json"), "utf8")).version || void 0;
19285
19929
  } catch {
19286
19930
  return void 0;
19287
19931
  }
@@ -19382,6 +20026,21 @@ var CLAUDE_PLUGIN_TIMEOUT_MS = 12e4;
19382
20026
  function runHostBin(bin, args, opts) {
19383
20027
  return isWin ? execFileP2("cmd.exe", ["/c", bin, ...args], opts) : execFileP2(bin, args, opts);
19384
20028
  }
20029
+ function reexecMmiCli(args) {
20030
+ return new Promise((resolve6) => {
20031
+ let settled = false;
20032
+ const done = (code) => {
20033
+ if (!settled) {
20034
+ settled = true;
20035
+ resolve6(code);
20036
+ }
20037
+ };
20038
+ const env = { ...process.env, [DOCTOR_POST_SELF_UPDATE_ENV]: "1" };
20039
+ const child = isWin ? (0, import_node_child_process14.spawn)("cmd.exe", ["/c", "mmi-cli", ...args], { stdio: "inherit", env }) : (0, import_node_child_process14.spawn)("mmi-cli", args, { stdio: "inherit", env });
20040
+ child.on("error", () => done(-1));
20041
+ child.on("exit", (code) => done(code ?? 0));
20042
+ });
20043
+ }
19385
20044
  function hostBinAvailable(bin) {
19386
20045
  return execFileP2(isWin ? "where" : "which", [bin]).then(() => true).catch(() => false);
19387
20046
  }
@@ -19401,15 +20060,32 @@ async function runCodexPlugin(args) {
19401
20060
  return false;
19402
20061
  }
19403
20062
  }
20063
+ async function marketplaceAddRefSupported(bin) {
20064
+ try {
20065
+ const { stdout, stderr } = await runHostBin(bin, ["plugin", "marketplace", "add", "--help"], {
20066
+ timeout: CLAUDE_PLUGIN_TIMEOUT_MS
20067
+ });
20068
+ return marketplaceAddSupportsRef(`${stdout}
20069
+ ${stderr}`);
20070
+ } catch {
20071
+ return false;
20072
+ }
20073
+ }
19404
20074
  async function applyPluginHeal(token, surface, log, opts) {
19405
20075
  if (!opts?.force && surfaceToken(surface) !== token) return false;
19406
20076
  const descriptor = PLUGIN_SURFACE_HEAL[token];
19407
- const steps = descriptor.healSteps;
19408
- if (!steps) return false;
20077
+ const tableSteps = descriptor.healSteps;
20078
+ if (!tableSteps) return false;
20079
+ const bin = descriptor.pluginRunner === "codex" ? "codex" : "claude";
19409
20080
  const runner = descriptor.pluginRunner === "codex" ? runCodexPlugin : runClaudePlugin;
20081
+ const refSupported = await marketplaceAddRefSupported(bin);
20082
+ const { steps, strippedRef } = adaptHealStepsForRefSupport(tableSteps, refSupported);
19410
20083
  log(
19411
- token === "codex" ? " \u21BB reinstalling the MMI plugin via `codex plugin` (marketplace remove \u2192 add --ref main \u2192 add)\u2026" : " \u21BB reinstalling the MMI plugin via `claude plugin` (marketplace remove \u2192 add \u2192 install)\u2026"
20084
+ refSupported ? ` \u21BB reinstalling the MMI plugin via \`${bin} plugin\` (marketplace remove \u2192 add --ref main \u2192 ${token === "codex" ? "add" : "install"})\u2026` : ` \u21BB reinstalling the MMI plugin via \`${bin} plugin\` (marketplace remove \u2192 add \u2192 ${token === "codex" ? "add" : "install"})\u2026`
19412
20085
  );
20086
+ if (strippedRef) {
20087
+ log(` \u26A0 \`${bin}\` has no \`--ref\` option \u2014 cloning the default branch; update \`${bin}\` to pin the released \`main\` branch (#2080)`);
20088
+ }
19413
20089
  for (const step of steps) {
19414
20090
  if (healStepAborts(step, await runner([...step.args]))) return false;
19415
20091
  }
@@ -19417,11 +20093,11 @@ async function applyPluginHeal(token, surface, log, opts) {
19417
20093
  }
19418
20094
  var installedPluginsPath = (surface = detectSurface(process.env)) => {
19419
20095
  const homeDir = surface === "codex" ? ".codex" : ".claude";
19420
- return (0, import_node_path24.join)((0, import_node_os5.homedir)(), homeDir, "plugins", "installed_plugins.json");
20096
+ return (0, import_node_path25.join)((0, import_node_os6.homedir)(), homeDir, "plugins", "installed_plugins.json");
19421
20097
  };
19422
20098
  function readInstalledPlugins(surface = detectSurface(process.env)) {
19423
20099
  try {
19424
- return JSON.parse((0, import_node_fs27.readFileSync)(installedPluginsPath(surface), "utf8"));
20100
+ return JSON.parse((0, import_node_fs28.readFileSync)(installedPluginsPath(surface), "utf8"));
19425
20101
  } catch {
19426
20102
  return null;
19427
20103
  }
@@ -19432,15 +20108,15 @@ function snapshotPluginGuardInput(surface = detectSurface(process.env), isOrgRep
19432
20108
  return {
19433
20109
  isOrgRepo,
19434
20110
  installRecordPresent: hasUserInstallRecord(installed, MMI_PLUGIN_ID) || hasProjectInstallRecord(installed, MMI_PLUGIN_ID, process.cwd()),
19435
- marketplaceClonePresent: (0, import_node_fs27.existsSync)((0, import_node_path24.join)((0, import_node_os5.homedir)(), homeDir, "plugins", "marketplaces", "mutmutco")),
19436
- pluginCachePresent: (0, import_node_fs27.existsSync)((0, import_node_path24.join)((0, import_node_os5.homedir)(), homeDir, "plugins", "cache", "mutmutco", "mmi"))
20111
+ marketplaceClonePresent: (0, import_node_fs28.existsSync)((0, import_node_path25.join)((0, import_node_os6.homedir)(), homeDir, "plugins", "marketplaces", "mutmutco")),
20112
+ pluginCachePresent: (0, import_node_fs28.existsSync)((0, import_node_path25.join)((0, import_node_os6.homedir)(), homeDir, "plugins", "cache", "mutmutco", "mmi"))
19437
20113
  };
19438
20114
  }
19439
20115
  function installedPluginSources() {
19440
20116
  return ["claude", "codex"].map((surface) => {
19441
- const recordPath = (0, import_node_path24.join)((0, import_node_os5.homedir)(), `.${surface}`, "plugins", "installed_plugins.json");
20117
+ const recordPath = (0, import_node_path25.join)((0, import_node_os6.homedir)(), `.${surface}`, "plugins", "installed_plugins.json");
19442
20118
  try {
19443
- return { surface, installed: JSON.parse((0, import_node_fs27.readFileSync)(recordPath, "utf8")), recordPath };
20119
+ return { surface, installed: JSON.parse((0, import_node_fs28.readFileSync)(recordPath, "utf8")), recordPath };
19444
20120
  } catch {
19445
20121
  return { surface, installed: null, recordPath };
19446
20122
  }
@@ -19448,7 +20124,7 @@ function installedPluginSources() {
19448
20124
  }
19449
20125
  function readClaudeSettings() {
19450
20126
  try {
19451
- return JSON.parse((0, import_node_fs27.readFileSync)((0, import_node_path24.join)(process.cwd(), ".claude", "settings.json"), "utf8"));
20127
+ return JSON.parse((0, import_node_fs28.readFileSync)((0, import_node_path25.join)(process.cwd(), ".claude", "settings.json"), "utf8"));
19452
20128
  } catch {
19453
20129
  return null;
19454
20130
  }
@@ -19470,7 +20146,7 @@ function writeProjectInstallRecord(record) {
19470
20146
  const list = file.plugins[MMI_PLUGIN_ID] ?? [];
19471
20147
  list.push(record);
19472
20148
  file.plugins[MMI_PLUGIN_ID] = list;
19473
- (0, import_node_fs27.writeFileSync)(installedPluginsPath(), `${JSON.stringify(file, null, 2)}
20149
+ (0, import_node_fs28.writeFileSync)(installedPluginsPath(), `${JSON.stringify(file, null, 2)}
19474
20150
  `, "utf8");
19475
20151
  return true;
19476
20152
  } catch {
@@ -19483,9 +20159,9 @@ function backupAndWriteInstalledPlugins(records, pluginId) {
19483
20159
  if (!file) return false;
19484
20160
  if (!file.plugins) file.plugins = {};
19485
20161
  const path2 = installedPluginsPath();
19486
- (0, import_node_fs27.copyFileSync)(path2, `${path2}.bak`);
20162
+ (0, import_node_fs28.copyFileSync)(path2, `${path2}.bak`);
19487
20163
  file.plugins[pluginId] = records;
19488
- (0, import_node_fs27.writeFileSync)(path2, `${JSON.stringify(file, null, 2)}
20164
+ (0, import_node_fs28.writeFileSync)(path2, `${JSON.stringify(file, null, 2)}
19489
20165
  `, "utf8");
19490
20166
  return true;
19491
20167
  } catch {
@@ -19493,22 +20169,22 @@ function backupAndWriteInstalledPlugins(records, pluginId) {
19493
20169
  }
19494
20170
  }
19495
20171
  function opencodeConfigDir() {
19496
- return (0, import_node_path24.join)((0, import_node_os5.homedir)(), ".config", "opencode");
20172
+ return (0, import_node_path25.join)((0, import_node_os6.homedir)(), ".config", "opencode");
19497
20173
  }
19498
20174
  function opencodeConfigPath() {
19499
- return (0, import_node_path24.join)(opencodeConfigDir(), "opencode.jsonc");
20175
+ return (0, import_node_path25.join)(opencodeConfigDir(), "opencode.jsonc");
19500
20176
  }
19501
20177
  function opencodeCommandsDir() {
19502
- return (0, import_node_path24.join)(opencodeConfigDir(), "commands");
20178
+ return (0, import_node_path25.join)(opencodeConfigDir(), "commands");
19503
20179
  }
19504
20180
  function opencodeSkillsPath() {
19505
- return (0, import_node_path24.join)(opencodeConfigDir(), "node_modules", "@mutmutco", "opencode-mmi", "skills");
20181
+ return (0, import_node_path25.join)(opencodeConfigDir(), "node_modules", "@mutmutco", "opencode-mmi", "skills");
19506
20182
  }
19507
20183
  function opencodeConfigSnapshot() {
19508
20184
  const path2 = opencodeConfigPath();
19509
- if (!(0, import_node_fs27.existsSync)(path2)) return { path: path2, hasConfig: false, hasPluginField: false, parseOk: true };
20185
+ if (!(0, import_node_fs28.existsSync)(path2)) return { path: path2, hasConfig: false, hasPluginField: false, parseOk: true };
19510
20186
  try {
19511
- const raw = (0, import_node_fs27.readFileSync)(path2, "utf8");
20187
+ const raw = (0, import_node_fs28.readFileSync)(path2, "utf8");
19512
20188
  const parsed = JSON.parse(stripJsonc(raw));
19513
20189
  const hasPluginField = Object.prototype.hasOwnProperty.call(parsed, "plugin");
19514
20190
  const skillsPaths = Array.isArray(parsed.skills?.paths) ? parsed.skills.paths.filter((p) => typeof p === "string") : void 0;
@@ -19531,9 +20207,9 @@ function writeOpencodeConfigPlugin(snapshot) {
19531
20207
  const plan2 = planOpencodeConfigWrite(snapshot.hasConfig ? snapshot.raw : void 0);
19532
20208
  if (plan2.action === "already") return true;
19533
20209
  if (!plan2.text || plan2.action === "unsafe") return false;
19534
- (0, import_node_fs27.mkdirSync)((0, import_node_path24.dirname)(path2), { recursive: true });
19535
- if (snapshot.hasConfig) (0, import_node_fs27.copyFileSync)(path2, `${path2}.bak`);
19536
- (0, import_node_fs27.writeFileSync)(path2, plan2.text, "utf8");
20210
+ (0, import_node_fs28.mkdirSync)((0, import_node_path25.dirname)(path2), { recursive: true });
20211
+ if (snapshot.hasConfig) (0, import_node_fs28.copyFileSync)(path2, `${path2}.bak`);
20212
+ (0, import_node_fs28.writeFileSync)(path2, plan2.text, "utf8");
19537
20213
  return true;
19538
20214
  } catch {
19539
20215
  return false;
@@ -19548,9 +20224,9 @@ function writeOpencodeSkillsPath(snapshot, skillsPath) {
19548
20224
  const normalized = skillsPath.replace(/\\/g, "/");
19549
20225
  if (!paths.some((p) => p.replace(/\\/g, "/") === normalized)) paths.push(skillsPath.replace(/\\/g, "/"));
19550
20226
  parsed.skills = { ...skills, paths };
19551
- (0, import_node_fs27.mkdirSync)((0, import_node_path24.dirname)(snapshot.path), { recursive: true });
19552
- if (snapshot.hasConfig && (0, import_node_fs27.existsSync)(snapshot.path)) (0, import_node_fs27.copyFileSync)(snapshot.path, `${snapshot.path}.bak`);
19553
- (0, import_node_fs27.writeFileSync)(snapshot.path, `${JSON.stringify(parsed, null, 2)}
20227
+ (0, import_node_fs28.mkdirSync)((0, import_node_path25.dirname)(snapshot.path), { recursive: true });
20228
+ if (snapshot.hasConfig && (0, import_node_fs28.existsSync)(snapshot.path)) (0, import_node_fs28.copyFileSync)(snapshot.path, `${snapshot.path}.bak`);
20229
+ (0, import_node_fs28.writeFileSync)(snapshot.path, `${JSON.stringify(parsed, null, 2)}
19554
20230
  `, "utf8");
19555
20231
  return true;
19556
20232
  } catch {
@@ -19559,7 +20235,7 @@ function writeOpencodeSkillsPath(snapshot, skillsPath) {
19559
20235
  }
19560
20236
  function opencodeExistingCommands() {
19561
20237
  try {
19562
- return (0, import_node_fs27.readdirSync)(opencodeCommandsDir(), { withFileTypes: true }).filter((entry) => entry.isFile() && entry.name.endsWith(".md")).map((entry) => entry.name.slice(0, -3).toLowerCase());
20238
+ return (0, import_node_fs28.readdirSync)(opencodeCommandsDir(), { withFileTypes: true }).filter((entry) => entry.isFile() && entry.name.endsWith(".md")).map((entry) => entry.name.slice(0, -3).toLowerCase());
19563
20239
  } catch {
19564
20240
  return [];
19565
20241
  }
@@ -19567,9 +20243,9 @@ function opencodeExistingCommands() {
19567
20243
  function writeOpencodeCommandFiles() {
19568
20244
  try {
19569
20245
  const dir = opencodeCommandsDir();
19570
- (0, import_node_fs27.mkdirSync)(dir, { recursive: true });
20246
+ (0, import_node_fs28.mkdirSync)(dir, { recursive: true });
19571
20247
  for (const command of OPENCODE_WORKFLOW_COMMANDS) {
19572
- (0, import_node_fs27.writeFileSync)((0, import_node_path24.join)(dir, `${command}.md`), opencodeCommandMarkdown(command), "utf8");
20248
+ (0, import_node_fs28.writeFileSync)((0, import_node_path25.join)(dir, `${command}.md`), opencodeCommandMarkdown(command), "utf8");
19573
20249
  }
19574
20250
  return true;
19575
20251
  } catch {
@@ -19578,12 +20254,12 @@ function writeOpencodeCommandFiles() {
19578
20254
  }
19579
20255
  function readOpencodeAdapterDiskVersion() {
19580
20256
  const candidates = [
19581
- (0, import_node_path24.join)(opencodeConfigDir(), "node_modules", "@mutmutco", "opencode-mmi", "package.json"),
19582
- (0, import_node_path24.join)((0, import_node_os5.homedir)(), ".cache", "opencode", "node_modules", "@mutmutco", "opencode-mmi", "package.json")
20257
+ (0, import_node_path25.join)(opencodeConfigDir(), "node_modules", "@mutmutco", "opencode-mmi", "package.json"),
20258
+ (0, import_node_path25.join)((0, import_node_os6.homedir)(), ".cache", "opencode", "node_modules", "@mutmutco", "opencode-mmi", "package.json")
19583
20259
  ];
19584
20260
  for (const path2 of candidates) {
19585
20261
  try {
19586
- const parsed = JSON.parse((0, import_node_fs27.readFileSync)(path2, "utf8"));
20262
+ const parsed = JSON.parse((0, import_node_fs28.readFileSync)(path2, "utf8"));
19587
20263
  if (typeof parsed.version === "string" && parsed.version.trim()) return parsed.version.trim();
19588
20264
  } catch {
19589
20265
  continue;
@@ -19599,7 +20275,7 @@ async function forceInstallOpencodeMmiPlugins(snapshot, log) {
19599
20275
  try {
19600
20276
  const specs = opencodeMmiPluginSpecs(snapshot);
19601
20277
  log(` \u21BB force-refreshing OpenCode MMI npm plugin(s): ${specs.join(", ")}\u2026`);
19602
- (0, import_node_fs27.mkdirSync)(opencodeConfigDir(), { recursive: true });
20278
+ (0, import_node_fs28.mkdirSync)(opencodeConfigDir(), { recursive: true });
19603
20279
  await runHostBin("npm", ["install", "--prefix", opencodeConfigDir(), "--force", ...specs], { timeout: NPM_UPDATE_TIMEOUT_MS });
19604
20280
  return true;
19605
20281
  } catch {
@@ -19614,30 +20290,30 @@ function opencodePluginVersionsForReport() {
19614
20290
  }
19615
20291
  function opencodeDesktopLogsRoot() {
19616
20292
  if (process.platform === "win32") {
19617
- const base = process.env.APPDATA || (0, import_node_path24.join)((0, import_node_os5.homedir)(), "AppData", "Roaming");
19618
- return (0, import_node_path24.join)(base, "ai.opencode.desktop", "logs");
20293
+ const base = process.env.APPDATA || (0, import_node_path25.join)((0, import_node_os6.homedir)(), "AppData", "Roaming");
20294
+ return (0, import_node_path25.join)(base, "ai.opencode.desktop", "logs");
19619
20295
  }
19620
20296
  if (process.platform === "darwin") {
19621
- return (0, import_node_path24.join)((0, import_node_os5.homedir)(), "Library", "Application Support", "ai.opencode.desktop", "logs");
20297
+ return (0, import_node_path25.join)((0, import_node_os6.homedir)(), "Library", "Application Support", "ai.opencode.desktop", "logs");
19622
20298
  }
19623
- return (0, import_node_path24.join)((0, import_node_os5.homedir)(), ".config", "ai.opencode.desktop", "logs");
20299
+ return (0, import_node_path25.join)((0, import_node_os6.homedir)(), ".config", "ai.opencode.desktop", "logs");
19624
20300
  }
19625
20301
  function opencodeDesktopBootstrapSnapshot() {
19626
20302
  const root = opencodeDesktopLogsRoot();
19627
20303
  try {
19628
- const sessionDirs = (0, import_node_fs27.readdirSync)(root, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => (0, import_node_path24.join)(root, entry.name)).sort((a, b) => (0, import_node_fs27.statSync)(b).mtimeMs - (0, import_node_fs27.statSync)(a).mtimeMs);
20304
+ const sessionDirs = (0, import_node_fs28.readdirSync)(root, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => (0, import_node_path25.join)(root, entry.name)).sort((a, b) => (0, import_node_fs28.statSync)(b).mtimeMs - (0, import_node_fs28.statSync)(a).mtimeMs);
19629
20305
  const newest = sessionDirs[0];
19630
20306
  if (!newest) return [];
19631
- const logPath = (0, import_node_path24.join)(newest, "renderer.log");
19632
- const text = (0, import_node_fs27.readFileSync)(logPath, "utf8");
19633
- return opencodeAgentDirectoriesFromLog(text).filter((directory) => !(0, import_node_fs27.existsSync)(directory)).map((directory) => ({ directory, logPath }));
20307
+ const logPath = (0, import_node_path25.join)(newest, "renderer.log");
20308
+ const text = (0, import_node_fs28.readFileSync)(logPath, "utf8");
20309
+ return opencodeAgentDirectoriesFromLog(text).filter((directory) => !(0, import_node_fs28.existsSync)(directory)).map((directory) => ({ directory, logPath }));
19634
20310
  } catch {
19635
20311
  return [];
19636
20312
  }
19637
20313
  }
19638
20314
  function opencodeLegacyConfigSnapshot() {
19639
- const legacyPath = (0, import_node_path24.join)((0, import_node_os5.homedir)(), ".opencode", "opencode.json");
19640
- if (!(0, import_node_fs27.existsSync)(legacyPath)) return {};
20315
+ const legacyPath = (0, import_node_path25.join)((0, import_node_os6.homedir)(), ".opencode", "opencode.json");
20316
+ if (!(0, import_node_fs28.existsSync)(legacyPath)) return {};
19641
20317
  const content = readTextFile(legacyPath);
19642
20318
  if (content == null) return {};
19643
20319
  const plugins = parseOpencodeLegacyConfigPlugins(content);
@@ -19649,43 +20325,46 @@ function opencodeLegacyConfigSnapshot() {
19649
20325
  function quarantineOpencodeLegacyConfig(legacyPath) {
19650
20326
  try {
19651
20327
  const backupPath = `${legacyPath}.bak`;
19652
- if ((0, import_node_fs27.existsSync)(backupPath)) return false;
19653
- (0, import_node_fs27.renameSync)(legacyPath, backupPath);
20328
+ if ((0, import_node_fs28.existsSync)(backupPath)) return false;
20329
+ (0, import_node_fs28.renameSync)(legacyPath, backupPath);
19654
20330
  return true;
19655
20331
  } catch {
19656
20332
  return false;
19657
20333
  }
19658
20334
  }
19659
20335
  function cursorPluginCacheRoot() {
19660
- return (0, import_node_path24.join)((0, import_node_os5.homedir)(), ".cursor", "plugins", "cache", "mutmutco", "mmi");
20336
+ return (0, import_node_path25.join)((0, import_node_os6.homedir)(), ".cursor", "plugins", "cache", "mutmutco", "mmi");
19661
20337
  }
19662
20338
  function cursorPluginCachePinSnapshots() {
19663
20339
  const root = cursorPluginCacheRoot();
19664
20340
  try {
19665
- return (0, import_node_fs27.readdirSync)(root, { withFileTypes: true }).filter((entry) => entry.isDirectory() && !entry.name.startsWith(".")).map((entry) => {
19666
- const path2 = (0, import_node_path24.join)(root, entry.name);
19667
- const pluginJson = (0, import_node_path24.join)(path2, ".cursor-plugin", "plugin.json");
19668
- const hooksJson = (0, import_node_path24.join)(path2, "hooks", "hooks.json");
19669
- const cliBundle = (0, import_node_path24.join)(path2, "cli", "dist", "index.cjs");
20341
+ return (0, import_node_fs28.readdirSync)(root, { withFileTypes: true }).filter((entry) => entry.isDirectory() && !entry.name.startsWith(".")).map((entry) => {
20342
+ const path2 = (0, import_node_path25.join)(root, entry.name);
20343
+ const pluginJson = (0, import_node_path25.join)(path2, ".cursor-plugin", "plugin.json");
20344
+ const hooksJson = (0, import_node_path25.join)(path2, "hooks", "hooks.json");
20345
+ const cliBundle = (0, import_node_path25.join)(path2, "cli", "dist", "index.cjs");
20346
+ const throttleGateCursor = (0, import_node_path25.join)(path2, "scripts", "throttle-gate-cursor.mjs");
20347
+ const throttleCore = (0, import_node_path25.join)(path2, "scripts", "throttle-core.mjs");
19670
20348
  let version;
19671
20349
  try {
19672
- const raw = JSON.parse((0, import_node_fs27.readFileSync)(pluginJson, "utf8"));
20350
+ const raw = JSON.parse((0, import_node_fs28.readFileSync)(pluginJson, "utf8"));
19673
20351
  version = typeof raw.version === "string" ? raw.version : void 0;
19674
20352
  } catch {
19675
20353
  version = void 0;
19676
20354
  }
19677
20355
  let isEmpty = true;
19678
20356
  try {
19679
- isEmpty = (0, import_node_fs27.readdirSync)(path2).length === 0;
20357
+ isEmpty = (0, import_node_fs28.readdirSync)(path2).length === 0;
19680
20358
  } catch {
19681
20359
  isEmpty = true;
19682
20360
  }
19683
20361
  return {
19684
20362
  name: entry.name,
19685
20363
  path: path2,
19686
- hasPluginJson: (0, import_node_fs27.existsSync)(pluginJson),
19687
- hasHooksJson: (0, import_node_fs27.existsSync)(hooksJson),
19688
- hasCliBundle: (0, import_node_fs27.existsSync)(cliBundle),
20364
+ hasPluginJson: (0, import_node_fs28.existsSync)(pluginJson),
20365
+ hasHooksJson: (0, import_node_fs28.existsSync)(hooksJson),
20366
+ hasCliBundle: (0, import_node_fs28.existsSync)(cliBundle),
20367
+ hasShellDialectGuard: (0, import_node_fs28.existsSync)(throttleGateCursor) && (0, import_node_fs28.existsSync)(throttleCore),
19689
20368
  isEmpty,
19690
20369
  version
19691
20370
  };
@@ -19695,19 +20374,19 @@ function cursorPluginCachePinSnapshots() {
19695
20374
  }
19696
20375
  }
19697
20376
  function hubCheckoutForCursorSeed() {
19698
- const manifest = (0, import_node_path24.join)(process.cwd(), "plugins", "mmi", ".cursor-plugin", "plugin.json");
19699
- return (0, import_node_fs27.existsSync)(manifest) ? process.cwd() : void 0;
20377
+ const manifest = (0, import_node_path25.join)(process.cwd(), "plugins", "mmi", ".cursor-plugin", "plugin.json");
20378
+ return (0, import_node_fs28.existsSync)(manifest) ? process.cwd() : void 0;
19700
20379
  }
19701
20380
  function mmiPluginCacheRootSnapshots() {
19702
20381
  const roots = [
19703
- { surface: "claude", root: (0, import_node_path24.join)((0, import_node_os5.homedir)(), ".claude", "plugins", "cache", "mutmutco", "mmi") },
19704
- { surface: "codex", root: (0, import_node_path24.join)((0, import_node_os5.homedir)(), ".codex", "plugins", "cache", "mutmutco", "mmi") }
20382
+ { surface: "claude", root: (0, import_node_path25.join)((0, import_node_os6.homedir)(), ".claude", "plugins", "cache", "mutmutco", "mmi") },
20383
+ { surface: "codex", root: (0, import_node_path25.join)((0, import_node_os6.homedir)(), ".codex", "plugins", "cache", "mutmutco", "mmi") }
19705
20384
  ];
19706
20385
  return roots.flatMap(({ surface, root }) => {
19707
20386
  try {
19708
- const entries = (0, import_node_fs27.readdirSync)(root, { withFileTypes: true }).map((entry) => ({
20387
+ const entries = (0, import_node_fs28.readdirSync)(root, { withFileTypes: true }).map((entry) => ({
19709
20388
  name: entry.name,
19710
- path: (0, import_node_path24.join)(root, entry.name),
20389
+ path: (0, import_node_path25.join)(root, entry.name),
19711
20390
  isDirectory: entry.isDirectory()
19712
20391
  }));
19713
20392
  return [{ surface, root, entries }];
@@ -19718,7 +20397,7 @@ function mmiPluginCacheRootSnapshots() {
19718
20397
  }
19719
20398
  function hasNestedMmiChild(versionDir) {
19720
20399
  try {
19721
- return (0, import_node_fs27.statSync)((0, import_node_path24.join)(versionDir, "mmi")).isDirectory();
20400
+ return (0, import_node_fs28.statSync)((0, import_node_path25.join)(versionDir, "mmi")).isDirectory();
19722
20401
  } catch {
19723
20402
  return false;
19724
20403
  }
@@ -19729,10 +20408,10 @@ function nestedPluginTreeSnapshot() {
19729
20408
  );
19730
20409
  }
19731
20410
  function uniqueQuarantineTarget(path2) {
19732
- if (!(0, import_node_fs27.existsSync)(path2)) return path2;
20411
+ if (!(0, import_node_fs28.existsSync)(path2)) return path2;
19733
20412
  for (let i = 1; i < 100; i += 1) {
19734
20413
  const candidate = `${path2}-${i}`;
19735
- if (!(0, import_node_fs27.existsSync)(candidate)) return candidate;
20414
+ if (!(0, import_node_fs28.existsSync)(candidate)) return candidate;
19736
20415
  }
19737
20416
  return `${path2}-${Date.now()}`;
19738
20417
  }
@@ -19741,10 +20420,10 @@ function quarantinePluginCacheDirs(plan2) {
19741
20420
  const failed = [];
19742
20421
  for (const move of plan2) {
19743
20422
  try {
19744
- if (!(0, import_node_fs27.existsSync)(move.from)) continue;
20423
+ if (!(0, import_node_fs28.existsSync)(move.from)) continue;
19745
20424
  const target = uniqueQuarantineTarget(move.to);
19746
- (0, import_node_fs27.mkdirSync)((0, import_node_path24.dirname)(target), { recursive: true });
19747
- (0, import_node_fs27.renameSync)(move.from, target);
20425
+ (0, import_node_fs28.mkdirSync)((0, import_node_path25.dirname)(target), { recursive: true });
20426
+ (0, import_node_fs28.renameSync)(move.from, target);
19748
20427
  moved += 1;
19749
20428
  } catch {
19750
20429
  failed.push(move);
@@ -19763,23 +20442,23 @@ async function robocopyMirrorEmpty(emptyDir, target) {
19763
20442
  }
19764
20443
  async function clearNestedPluginTreeDir(targetPath) {
19765
20444
  try {
19766
- if (!(0, import_node_fs27.existsSync)(targetPath)) return true;
20445
+ if (!(0, import_node_fs28.existsSync)(targetPath)) return true;
19767
20446
  if (isWin) {
19768
- const emptyDir = (0, import_node_path24.join)((0, import_node_os5.tmpdir)(), `mmi-empty-${Date.now()}`);
19769
- (0, import_node_fs27.mkdirSync)(emptyDir, { recursive: true });
20447
+ const emptyDir = (0, import_node_path25.join)((0, import_node_os6.tmpdir)(), `mmi-empty-${Date.now()}`);
20448
+ (0, import_node_fs28.mkdirSync)(emptyDir, { recursive: true });
19770
20449
  try {
19771
20450
  await robocopyMirrorEmpty(emptyDir, targetPath);
19772
- (0, import_node_fs27.rmSync)(targetPath, { recursive: true, force: true });
20451
+ (0, import_node_fs28.rmSync)(targetPath, { recursive: true, force: true });
19773
20452
  } finally {
19774
20453
  try {
19775
- (0, import_node_fs27.rmSync)(emptyDir, { recursive: true, force: true });
20454
+ (0, import_node_fs28.rmSync)(emptyDir, { recursive: true, force: true });
19776
20455
  } catch {
19777
20456
  }
19778
20457
  }
19779
- return !(0, import_node_fs27.existsSync)(targetPath);
20458
+ return !(0, import_node_fs28.existsSync)(targetPath);
19780
20459
  }
19781
- (0, import_node_fs27.rmSync)(targetPath, { recursive: true, force: true });
19782
- return !(0, import_node_fs27.existsSync)(targetPath);
20460
+ (0, import_node_fs28.rmSync)(targetPath, { recursive: true, force: true });
20461
+ return !(0, import_node_fs28.existsSync)(targetPath);
19783
20462
  } catch {
19784
20463
  return false;
19785
20464
  }
@@ -19792,23 +20471,23 @@ async function applyNestedPluginTreeCleanup(paths, log) {
19792
20471
  }
19793
20472
  return true;
19794
20473
  }
19795
- var gitignorePath = () => (0, import_node_path24.join)(process.cwd(), ".gitignore");
20474
+ var gitignorePath = () => (0, import_node_path25.join)(process.cwd(), ".gitignore");
19796
20475
  function readTextFile(path2) {
19797
20476
  try {
19798
- if (!(0, import_node_fs27.existsSync)(path2)) return null;
19799
- return (0, import_node_fs27.readFileSync)(path2, "utf8");
20477
+ if (!(0, import_node_fs28.existsSync)(path2)) return null;
20478
+ return (0, import_node_fs28.readFileSync)(path2, "utf8");
19800
20479
  } catch {
19801
20480
  return null;
19802
20481
  }
19803
20482
  }
19804
20483
  function playwrightMcpConfigSnapshots() {
19805
20484
  const cwd = process.cwd();
19806
- const home = (0, import_node_os5.homedir)();
20485
+ const home = (0, import_node_os6.homedir)();
19807
20486
  const candidates = [
19808
- (0, import_node_path24.join)(cwd, ".mcp.json"),
19809
- (0, import_node_path24.join)(cwd, ".cursor", "mcp.json"),
19810
- (0, import_node_path24.join)(home, ".cursor", "mcp.json"),
19811
- (0, import_node_path24.join)(home, ".codex", "config.toml")
20487
+ (0, import_node_path25.join)(cwd, ".mcp.json"),
20488
+ (0, import_node_path25.join)(cwd, ".cursor", "mcp.json"),
20489
+ (0, import_node_path25.join)(home, ".cursor", "mcp.json"),
20490
+ (0, import_node_path25.join)(home, ".codex", "config.toml")
19812
20491
  ];
19813
20492
  const out = [];
19814
20493
  for (const path2 of candidates) {
@@ -19821,7 +20500,7 @@ function strayBrowserArtifactPaths() {
19821
20500
  const cwd = process.cwd();
19822
20501
  return STRAY_BROWSER_ARTIFACT_DIRS.filter((rel) => {
19823
20502
  try {
19824
- return (0, import_node_fs27.existsSync)((0, import_node_path24.join)(cwd, rel));
20503
+ return (0, import_node_fs28.existsSync)((0, import_node_path25.join)(cwd, rel));
19825
20504
  } catch {
19826
20505
  return false;
19827
20506
  }
@@ -19840,8 +20519,8 @@ function latestIso(values) {
19840
20519
  return best;
19841
20520
  }
19842
20521
  function latestNorthstarContinuityAt() {
19843
- const meta = parseMeta(readTextFile((0, import_node_path24.join)(process.cwd(), META_FILE)));
19844
- const queue = parseQueue(readTextFile((0, import_node_path24.join)(process.cwd(), QUEUE_FILE)));
20522
+ const meta = parseMeta(readTextFile((0, import_node_path25.join)(process.cwd(), META_FILE)));
20523
+ const queue = parseQueue(readTextFile((0, import_node_path25.join)(process.cwd(), QUEUE_FILE)));
19845
20524
  return latestIso([
19846
20525
  ...Object.values(meta).map((entry) => entry.syncedAt),
19847
20526
  ...queue.map((entry) => entry.queuedAt)
@@ -19855,14 +20534,14 @@ async function latestBranchWorkAt() {
19855
20534
  }
19856
20535
  function readGitignore() {
19857
20536
  try {
19858
- return (0, import_node_fs27.readFileSync)(gitignorePath(), "utf8");
20537
+ return (0, import_node_fs28.readFileSync)(gitignorePath(), "utf8");
19859
20538
  } catch {
19860
20539
  return null;
19861
20540
  }
19862
20541
  }
19863
20542
  function writeGitignore(content) {
19864
20543
  try {
19865
- (0, import_node_fs27.writeFileSync)(gitignorePath(), content, "utf8");
20544
+ (0, import_node_fs28.writeFileSync)(gitignorePath(), content, "utf8");
19866
20545
  return true;
19867
20546
  } catch {
19868
20547
  return false;
@@ -19901,7 +20580,7 @@ async function runDoctor(opts, io = consoleIo) {
19901
20580
  const semverPrefix = /^\d+\.\d+\.\d+/;
19902
20581
  const isBehind = (installed2, released) => Boolean(installed2 && released && semverPrefix.test(installed2) && semverPrefix.test(released) && compareVersions(installed2, released) < 0);
19903
20582
  const opencodeAdapterStale = isBehind(opencodeInstalledVersionForDoctor(), releasedVersion);
19904
- const cursorCacheStale = (0, import_node_fs27.existsSync)(cursorPluginCacheRoot()) && (cursorPluginCachePinSnapshots() ?? []).some((p) => isBehind(p.version, releasedVersion));
20583
+ const cursorCacheStale = (0, import_node_fs28.existsSync)(cursorPluginCacheRoot()) && (cursorPluginCachePinSnapshots() ?? []).some((p) => isBehind(p.version, releasedVersion));
19905
20584
  const healPlan = doctorHealPlan({
19906
20585
  isOrgRepo: Boolean(cfg.sagaApiUrl),
19907
20586
  surface,
@@ -19936,7 +20615,7 @@ async function runDoctor(opts, io = consoleIo) {
19936
20615
  let onPath = pathProbe;
19937
20616
  if (!onPath) {
19938
20617
  const root = process.env.CLAUDE_PLUGIN_ROOT;
19939
- if (root && (0, import_node_fs27.existsSync)(`${root}/bin/mmi-cli${isWin ? ".cmd" : ""}`)) onPath = true;
20618
+ if (root && (0, import_node_fs28.existsSync)(`${root}/bin/mmi-cli${isWin ? ".cmd" : ""}`)) onPath = true;
19940
20619
  }
19941
20620
  checks.push({ ok: onPath, label: "mmi-cli on PATH", fix: "auto-provisioned at session start \u2014 reopen the session, or install the MMI plugin" });
19942
20621
  const reloadHint = reloadAction(surface);
@@ -19950,7 +20629,7 @@ async function runDoctor(opts, io = consoleIo) {
19950
20629
  releasedVersion
19951
20630
  });
19952
20631
  let selfUpdatedCli = false;
19953
- if (repairFull) {
20632
+ if (repairFull && !process.env[DOCTOR_POST_SELF_UPDATE_ENV]) {
19954
20633
  const updated = await applyVersionAutoUpdate(versionReport, (m) => io.err(m));
19955
20634
  versionReport = updated.report;
19956
20635
  selfUpdatedCli = updated.applied === "npm";
@@ -19958,6 +20637,12 @@ async function runDoctor(opts, io = consoleIo) {
19958
20637
  if (!versionReport.ok) versionReport = { ...versionReport, fix: pluginRecoveryFix(surface) };
19959
20638
  checks.push(versionReport);
19960
20639
  if (selfUpdatedCli) {
20640
+ if (!opts.json) io.err(selfUpdateReexecLine(versionReport));
20641
+ const code = await reexecMmiCli(buildSelfUpdateReexecArgs(opts));
20642
+ if (code >= 0) {
20643
+ if (code > 0) process.exitCode = code;
20644
+ return;
20645
+ }
19961
20646
  if (opts.json) io.log(JSON.stringify(buildSelfUpdateHaltPayload({ checks, updatedTo: versionReport.releasedVersion }), null, 2));
19962
20647
  else io.err(selfUpdateHaltLine(versionReport));
19963
20648
  return;
@@ -20209,6 +20894,11 @@ async function runDoctor(opts, io = consoleIo) {
20209
20894
  }
20210
20895
  checks.push(legacyOpenCodeCheck);
20211
20896
  }
20897
+ const codexRecordVersion = () => {
20898
+ const versions = installedPluginVersions(installedPluginSources().find((src) => src.surface === "codex")?.installed ?? null).filter((v) => /^v?\d+\.\d+\.\d+/.test(v));
20899
+ return versions.sort((a, b) => compareVersions(b, a))[0];
20900
+ };
20901
+ const codexCacheVersions = () => mmiPluginCacheRootSnapshots().filter((r) => r.surface === "codex").flatMap((r) => r.entries.filter((e) => e.isDirectory).map((e) => e.name));
20212
20902
  let cacheCleanupCheck = buildMmiPluginCacheCleanupCheck({
20213
20903
  isOrgRepo: Boolean(cfg.sagaApiUrl),
20214
20904
  roots: mmiPluginCacheRootSnapshots(),
@@ -20240,6 +20930,26 @@ async function runDoctor(opts, io = consoleIo) {
20240
20930
  };
20241
20931
  }
20242
20932
  checks.push(cacheCleanupCheck);
20933
+ let codexActiveCacheCheck = buildCodexActiveCacheCheck({
20934
+ isOrgRepo: Boolean(cfg.sagaApiUrl),
20935
+ releasedVersion,
20936
+ codexCacheVersions: codexCacheVersions(),
20937
+ codexRecordVersion: codexRecordVersion()
20938
+ });
20939
+ if (!codexActiveCacheCheck.ok && repairFull) {
20940
+ const canDriveCodex = surfaceToken(surface) === "codex" || await hostBinAvailable("codex");
20941
+ if (canDriveCodex && await applyPluginHeal("codex", surface, (m) => io.err(m), { force: true })) {
20942
+ markPluginReloadRequired();
20943
+ io.err(` \u21BB restored Codex MMI plugin cache \u2192 ${releasedVersion ?? "latest"} via codex plugin \u2014 ${reloadAction("codex")} to load the new commands`);
20944
+ codexActiveCacheCheck = buildCodexActiveCacheCheck({
20945
+ isOrgRepo: Boolean(cfg.sagaApiUrl),
20946
+ releasedVersion,
20947
+ codexCacheVersions: codexCacheVersions(),
20948
+ codexRecordVersion: codexRecordVersion()
20949
+ });
20950
+ }
20951
+ }
20952
+ checks.push(codexActiveCacheCheck);
20243
20953
  let nestedPluginTreeCheck = buildNestedPluginTreeCheck({
20244
20954
  isOrgRepo: Boolean(cfg.sagaApiUrl),
20245
20955
  isWindows: isWin,
@@ -20274,7 +20984,7 @@ async function runDoctor(opts, io = consoleIo) {
20274
20984
  isOrgRepo: Boolean(cfg.sagaApiUrl),
20275
20985
  surface,
20276
20986
  cacheRoot: cursorCacheRoot,
20277
- cacheRootExists: (0, import_node_fs27.existsSync)(cursorCacheRoot),
20987
+ cacheRootExists: (0, import_node_fs28.existsSync)(cursorCacheRoot),
20278
20988
  pins: cursorPins,
20279
20989
  hubCheckout: hubCheckoutForCursorSeed(),
20280
20990
  releasedVersion
@@ -20285,7 +20995,7 @@ async function runDoctor(opts, io = consoleIo) {
20285
20995
  releasedVersion,
20286
20996
  hubCheckout: hubCheckoutForCursorSeed(),
20287
20997
  execFileP: execFileP2,
20288
- mkdtemp: (prefix) => (0, import_promises7.mkdtemp)((0, import_node_path24.join)((0, import_node_os5.tmpdir)(), prefix)),
20998
+ mkdtemp: (prefix) => (0, import_promises7.mkdtemp)((0, import_node_path25.join)((0, import_node_os6.tmpdir)(), prefix)),
20289
20999
  log: (m) => io.err(m)
20290
21000
  });
20291
21001
  if (seeded) {
@@ -20294,7 +21004,7 @@ async function runDoctor(opts, io = consoleIo) {
20294
21004
  isOrgRepo: Boolean(cfg.sagaApiUrl),
20295
21005
  surface,
20296
21006
  cacheRoot: cursorCacheRoot,
20297
- cacheRootExists: (0, import_node_fs27.existsSync)(cursorCacheRoot),
21007
+ cacheRootExists: (0, import_node_fs28.existsSync)(cursorCacheRoot),
20298
21008
  pins: cursorPins,
20299
21009
  hubCheckout: hubCheckoutForCursorSeed(),
20300
21010
  releasedVersion
@@ -20483,23 +21193,23 @@ function mergeGuardHook(settings) {
20483
21193
  next.hooks = hooks;
20484
21194
  return next;
20485
21195
  }
20486
- var userScopeSettingsPath = (surface = detectSurface(process.env)) => (0, import_node_path24.join)((0, import_node_os5.homedir)(), surface === "codex" ? ".codex" : ".claude", "settings.json");
21196
+ var userScopeSettingsPath = (surface = detectSurface(process.env)) => (0, import_node_path25.join)((0, import_node_os6.homedir)(), surface === "codex" ? ".codex" : ".claude", "settings.json");
20487
21197
  function ensureUserScopeGuardHook(opts = {}) {
20488
21198
  const path2 = opts.settingsPath ?? userScopeSettingsPath();
20489
21199
  try {
20490
21200
  let current = null;
20491
- if ((0, import_node_fs27.existsSync)(path2)) {
21201
+ if ((0, import_node_fs28.existsSync)(path2)) {
20492
21202
  try {
20493
- current = JSON.parse((0, import_node_fs27.readFileSync)(path2, "utf8"));
21203
+ current = JSON.parse((0, import_node_fs28.readFileSync)(path2, "utf8"));
20494
21204
  } catch {
20495
21205
  return "failed";
20496
21206
  }
20497
21207
  }
20498
21208
  if (settingsHasGuardHook(current)) return "already";
20499
21209
  const merged = mergeGuardHook(current);
20500
- (0, import_node_fs27.mkdirSync)((0, import_node_path24.dirname)(path2), { recursive: true });
20501
- if ((0, import_node_fs27.existsSync)(path2)) (0, import_node_fs27.copyFileSync)(path2, `${path2}.bak`);
20502
- (0, import_node_fs27.writeFileSync)(path2, `${JSON.stringify(merged, null, 2)}
21210
+ (0, import_node_fs28.mkdirSync)((0, import_node_path25.dirname)(path2), { recursive: true });
21211
+ if ((0, import_node_fs28.existsSync)(path2)) (0, import_node_fs28.copyFileSync)(path2, `${path2}.bak`);
21212
+ (0, import_node_fs28.writeFileSync)(path2, `${JSON.stringify(merged, null, 2)}
20503
21213
  `, "utf8");
20504
21214
  return "written";
20505
21215
  } catch {
@@ -20533,8 +21243,13 @@ async function runPluginHeal(surface = detectSurface(process.env)) {
20533
21243
  if (healed) {
20534
21244
  console.log(` \u2713 MMI plugin reinstalled \u2014 ${reloadAction(surface)} to load MMI commands`);
20535
21245
  } else {
21246
+ const bin = descriptor.pluginRunner === "codex" ? "codex" : "claude";
21247
+ const refSupported = await marketplaceAddRefSupported(bin);
21248
+ const recovery = refSupported ? descriptor.recovery : recoveryWithoutRef(descriptor.recovery);
21249
+ const note = refSupported ? "" : `
21250
+ Note: \`${bin}\` has no \`--ref\` option here; update \`${bin}\` to pin the released \`main\` branch.`;
20536
21251
  console.log(` \u2717 Auto-heal failed or was skipped. Run manually:
20537
- ${descriptor.recovery}`);
21252
+ ${recovery}${note}`);
20538
21253
  }
20539
21254
  }
20540
21255
 
@@ -20629,7 +21344,7 @@ async function applyGcPlan(plan2, remote) {
20629
21344
  cleanupBranch: (branch, expectedHeadOid) => cleanupPrMergeLocalBranch(branch.branch, {
20630
21345
  beforeWorktrees,
20631
21346
  startingPath: branch.worktreePath,
20632
- pathExists: (p) => (0, import_node_fs28.existsSync)(p),
21347
+ pathExists: (p) => (0, import_node_fs29.existsSync)(p),
20633
21348
  execGit: async (args) => (await execFileP2("git", args, { timeout: GIT_TIMEOUT_MS })).stdout,
20634
21349
  teardownWorktreeStage,
20635
21350
  deferredStore,
@@ -20652,7 +21367,7 @@ async function applyGcPlan(plan2, remote) {
20652
21367
  for (const wt of plan2.worktreeDirs) {
20653
21368
  try {
20654
21369
  const cleanupTarget = resolveSafeSiblingWorktreeCleanupTarget(wt.path, siblingRoot, {
20655
- realpath: (path2) => (0, import_node_fs28.realpathSync)(path2)
21370
+ realpath: (path2) => (0, import_node_fs29.realpathSync)(path2)
20656
21371
  });
20657
21372
  if (!cleanupTarget.ok) {
20658
21373
  result.failed.push(`${wt.path}: ${cleanupTarget.reason}`);
@@ -20732,10 +21447,10 @@ async function runRulesSync(opts, io = consoleIo) {
20732
21447
  for (const entry of fetched) {
20733
21448
  if ("error" in entry) continue;
20734
21449
  const { file, source } = entry;
20735
- const current = (0, import_node_fs28.existsSync)(file) ? await (0, import_promises8.readFile)(file, "utf8") : null;
21450
+ const current = (0, import_node_fs29.existsSync)(file) ? await (0, import_promises8.readFile)(file, "utf8") : null;
20736
21451
  if (needsUpdate(source, current)) {
20737
21452
  const slash = file.lastIndexOf("/");
20738
- if (slash > 0) (0, import_node_fs28.mkdirSync)(file.slice(0, slash), { recursive: true });
21453
+ if (slash > 0) (0, import_node_fs29.mkdirSync)(file.slice(0, slash), { recursive: true });
20739
21454
  await (0, import_promises8.writeFile)(file, normalizeEol(source), "utf8");
20740
21455
  changed++;
20741
21456
  if (!opts.quiet) io.log(`mmi-cli rules: updated ${file}`);
@@ -20749,13 +21464,13 @@ rules.command("sync").option("--quiet", "stay silent unless something changed or
20749
21464
  if (!await runRulesSync(opts)) process.exitCode = 1;
20750
21465
  });
20751
21466
  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) => {
20752
- const path2 = (0, import_node_path25.join)(process.cwd(), ".gitignore");
20753
- const current = (0, import_node_fs28.existsSync)(path2) ? (0, import_node_fs28.readFileSync)(path2, "utf8") : null;
21467
+ const path2 = (0, import_node_path26.join)(process.cwd(), ".gitignore");
21468
+ const current = (0, import_node_fs29.existsSync)(path2) ? (0, import_node_fs29.readFileSync)(path2, "utf8") : null;
20754
21469
  const plan2 = planManagedGitignore(current);
20755
21470
  const drift = [...plan2.added.map((l) => `+${l}`), ...plan2.removed.map((l) => `-${l}`)].join(", ") || "block normalize";
20756
21471
  if (opts.write) {
20757
21472
  if (plan2.changed) {
20758
- (0, import_node_fs28.writeFileSync)(path2, plan2.content, "utf8");
21473
+ (0, import_node_fs29.writeFileSync)(path2, plan2.content, "utf8");
20759
21474
  console.log(`mmi-cli rules gitignore: updated .gitignore (${drift})`);
20760
21475
  } else {
20761
21476
  console.log("mmi-cli rules gitignore: up to date");
@@ -20782,7 +21497,7 @@ async function runDocsSync(opts, io = consoleIo) {
20782
21497
  return null;
20783
21498
  }
20784
21499
  },
20785
- localContent: async (f) => (0, import_node_fs28.existsSync)(f) ? await (0, import_promises8.readFile)(f, "utf8") : null,
21500
+ localContent: async (f) => (0, import_node_fs29.existsSync)(f) ? await (0, import_promises8.readFile)(f, "utf8") : null,
20786
21501
  writeDoc: async (f, c) => {
20787
21502
  await (0, import_promises8.writeFile)(f, c, "utf8");
20788
21503
  }
@@ -20796,6 +21511,7 @@ docs.command("sync").option("--quiet", "stay silent unless something changed or
20796
21511
  registerSagaCommands(program2);
20797
21512
  registerHandoffCommands(program2);
20798
21513
  registerCoopCommands(program2);
21514
+ registerOverlordCommands(program2);
20799
21515
  registerThrottleCommands(program2);
20800
21516
  program2.command("commands").description("print the command manifest \u2014 every subcommand + its flags (ground against this instead of guessing)").option("--json", "machine-readable JSON: { name, version, tree, index } \u2014 index is a flat list of every leaf command path").action((o) => {
20801
21517
  const manifest = buildCommandManifest(program2);
@@ -20934,7 +21650,7 @@ function runWorktreeInstall(command, cwd, quiet) {
20934
21650
  const file = isWin2 ? "cmd.exe" : bin;
20935
21651
  const spawnArgs = isWin2 ? ["/c", bin, ...args] : args;
20936
21652
  return new Promise((resolve6, reject) => {
20937
- const child = (0, import_node_child_process13.spawn)(file, spawnArgs, { cwd, stdio: quiet ? "ignore" : "inherit", windowsHide: true });
21653
+ const child = (0, import_node_child_process15.spawn)(file, spawnArgs, { cwd, stdio: quiet ? "ignore" : "inherit", windowsHide: true });
20938
21654
  const timer = setTimeout(() => {
20939
21655
  try {
20940
21656
  child.kill();
@@ -20956,7 +21672,7 @@ function runWorktreeInstall(command, cwd, quiet) {
20956
21672
  async function primaryCheckoutRoot(worktreeRoot) {
20957
21673
  try {
20958
21674
  const out = (await execFileP2("git", ["-C", worktreeRoot, "rev-parse", "--path-format=absolute", "--git-common-dir"], { timeout: GIT_TIMEOUT_MS })).stdout.trim();
20959
- return out ? (0, import_node_path25.dirname)(out) : void 0;
21675
+ return out ? (0, import_node_path26.dirname)(out) : void 0;
20960
21676
  } catch {
20961
21677
  return void 0;
20962
21678
  }
@@ -20969,28 +21685,28 @@ function makeProvisionDeps(worktreeRoot, quiet, log) {
20969
21685
  };
20970
21686
  }
20971
21687
  function acquireWorktreeSetupLock(worktreeRoot) {
20972
- const lockPath = (0, import_node_path25.join)(worktreeRoot, ".mmi", "worktree-setup.lock");
21688
+ const lockPath = (0, import_node_path26.join)(worktreeRoot, ".mmi", "worktree-setup.lock");
20973
21689
  const take = () => {
20974
- const fd = (0, import_node_fs28.openSync)(lockPath, "wx");
21690
+ const fd = (0, import_node_fs29.openSync)(lockPath, "wx");
20975
21691
  try {
20976
- (0, import_node_fs28.writeSync)(fd, String(Date.now()));
21692
+ (0, import_node_fs29.writeSync)(fd, String(Date.now()));
20977
21693
  } finally {
20978
- (0, import_node_fs28.closeSync)(fd);
21694
+ (0, import_node_fs29.closeSync)(fd);
20979
21695
  }
20980
21696
  return () => {
20981
21697
  try {
20982
- (0, import_node_fs28.rmSync)(lockPath, { force: true });
21698
+ (0, import_node_fs29.rmSync)(lockPath, { force: true });
20983
21699
  } catch {
20984
21700
  }
20985
21701
  };
20986
21702
  };
20987
21703
  try {
20988
- (0, import_node_fs28.mkdirSync)((0, import_node_path25.dirname)(lockPath), { recursive: true });
21704
+ (0, import_node_fs29.mkdirSync)((0, import_node_path26.dirname)(lockPath), { recursive: true });
20989
21705
  return take();
20990
21706
  } catch {
20991
21707
  try {
20992
- if (Date.now() - (0, import_node_fs28.statSync)(lockPath).mtimeMs > WORKTREE_SETUP_LOCK_TTL_MS) {
20993
- (0, import_node_fs28.rmSync)(lockPath, { force: true });
21708
+ if (Date.now() - (0, import_node_fs29.statSync)(lockPath).mtimeMs > WORKTREE_SETUP_LOCK_TTL_MS) {
21709
+ (0, import_node_fs29.rmSync)(lockPath, { force: true });
20994
21710
  return take();
20995
21711
  }
20996
21712
  } catch {
@@ -21152,7 +21868,7 @@ function scheduleRelatedDiscovery(o) {
21152
21868
  try {
21153
21869
  const args = ["issue", "discover-related", "--number", String(o.number), "--title", o.title, "--body", o.body];
21154
21870
  if (o.repo) args.push("--repo", o.repo);
21155
- (0, import_node_child_process13.spawn)(process.execPath, [process.argv[1], ...args], {
21871
+ (0, import_node_child_process15.spawn)(process.execPath, [process.argv[1], ...args], {
21156
21872
  detached: true,
21157
21873
  stdio: "ignore",
21158
21874
  windowsHide: true,
@@ -21680,6 +22396,29 @@ issue.command("link-child <parent> <child>").description("link an existing issue
21680
22396
  return fail(`issue link-child: ${(err.stderr || err.message || String(e)).trim()}${note ? ` (${note})` : ""}`);
21681
22397
  }
21682
22398
  });
22399
+ issue.command("comment <ref>").description("post a Markdown comment to an issue and print {number,repo,url,commentUrl} JSON").option("--body <body>", "comment body (markdown; prefer --body-file for multiline Markdown)").option("--body-file <path|->", "read comment body from a UTF-8 file, or from stdin with -").option("--repo <owner/repo>", "repo for a bare ref (defaults to the current repo)").action(async (ref, o) => {
22400
+ let parsed;
22401
+ try {
22402
+ parsed = parseIssueRef(ref);
22403
+ } catch (e) {
22404
+ return fail(`issue comment: ${e.message}`);
22405
+ }
22406
+ let body;
22407
+ try {
22408
+ body = await resolveIssueBody({ body: o.body, bodyFile: o.bodyFile }, { readFile: import_promises8.readFile, readStdin });
22409
+ } catch (e) {
22410
+ return fail(`issue comment: ${e.message}`);
22411
+ }
22412
+ const repo = await resolveRepo(parsed.repo ?? o.repo);
22413
+ if (!repo) return fail("issue comment: could not resolve repo \u2014 pass --repo owner/repo");
22414
+ try {
22415
+ const result = await postIssueComment(defaultGitHubClient(), { ref, defaultRepo: repo, body });
22416
+ console.log(JSON.stringify(result));
22417
+ } catch (e) {
22418
+ const err = e;
22419
+ return fail(`issue comment: ${(err.stderr || err.message || String(e)).trim()}`);
22420
+ }
22421
+ });
21683
22422
  issue.command("check <ref>").description("tick (or with --off untick) a task-list checkbox in an issue/epic body by its item text and print {number,repo,item,checked,changed} JSON").requiredOption("--item <text>", "the checklist item to match \u2014 exact item text, else a unique substring").option("--off", "untick the item ([x] \u2192 [ ]) instead of ticking it").option("--repo <owner/repo>", "repo for a bare ref (defaults to the current repo)").action(async (ref, o) => {
21684
22423
  let parsed;
21685
22424
  try {
@@ -21994,9 +22733,9 @@ pr.command("create").description("create a PR and print {number,url} JSON").opti
21994
22733
  console.log(JSON.stringify(created));
21995
22734
  });
21996
22735
  async function listCiWorkflowPaths(cwd = process.cwd()) {
21997
- const wfDir = (0, import_node_path25.join)(cwd, ".github", "workflows");
21998
- if (!(0, import_node_fs28.existsSync)(wfDir)) return [];
21999
- return (0, import_node_fs28.readdirSync)(wfDir).filter((name) => /\.(ya?ml)$/i.test(name)).map((name) => `.github/workflows/${name}`);
22736
+ const wfDir = (0, import_node_path26.join)(cwd, ".github", "workflows");
22737
+ if (!(0, import_node_fs29.existsSync)(wfDir)) return [];
22738
+ return (0, import_node_fs29.readdirSync)(wfDir).filter((name) => /\.(ya?ml)$/i.test(name)).map((name) => `.github/workflows/${name}`);
22000
22739
  }
22001
22740
  async function resolveMergeCiPolicyForCheckout(repoOpt) {
22002
22741
  const repo = repoOpt ?? await resolveRepo();
@@ -22015,7 +22754,7 @@ function ciAuditDeps() {
22015
22754
  // Continuous CI delivery (#1550): the gate re-seed renders from the Hub's on-disk seed templates. The
22016
22755
  // reconcile runs IN the Hub checkout, so this is local-file I/O (no network fetch). Path is relative to
22017
22756
  // the repo root (e.g. skills/bootstrap/seeds/gate.template.yml).
22018
- readSeedFile: (path2) => (0, import_node_fs28.existsSync)(path2) ? (0, import_node_fs28.readFileSync)(path2, "utf8") : null
22757
+ readSeedFile: (path2) => (0, import_node_fs29.existsSync)(path2) ? (0, import_node_fs29.readFileSync)(path2, "utf8") : null
22019
22758
  };
22020
22759
  }
22021
22760
  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) => {
@@ -22152,7 +22891,7 @@ async function remoteBranchExists2(branch, options = {}) {
22152
22891
  }
22153
22892
  var COMPOSE_TIMEOUT_MS = 12e4;
22154
22893
  function spawnDeferredGcSweep() {
22155
- spawnDetachedSelf(["gc", "sweep-deferred", "--quiet"], { spawn: import_node_child_process13.spawn, execPath: process.execPath, scriptPath: process.argv[1] });
22894
+ spawnDetachedSelf(["gc", "sweep-deferred", "--quiet"], { spawn: import_node_child_process15.spawn, execPath: process.execPath, scriptPath: process.argv[1] });
22156
22895
  }
22157
22896
  async function createDeferredWorktreeStore() {
22158
22897
  try {
@@ -22168,7 +22907,7 @@ async function createDeferredWorktreeStore() {
22168
22907
  },
22169
22908
  write: async (entries) => {
22170
22909
  try {
22171
- await (0, import_promises8.mkdir)((0, import_node_path25.dirname)(registryPath), { recursive: true });
22910
+ await (0, import_promises8.mkdir)((0, import_node_path26.dirname)(registryPath), { recursive: true });
22172
22911
  await (0, import_promises8.writeFile)(registryPath, serializeDeferredWorktrees(entries), "utf8");
22173
22912
  } catch {
22174
22913
  }
@@ -22182,13 +22921,13 @@ var realWorktreeDirRemover = {
22182
22921
  probe: (p) => {
22183
22922
  let st;
22184
22923
  try {
22185
- st = (0, import_node_fs28.lstatSync)(p);
22924
+ st = (0, import_node_fs29.lstatSync)(p);
22186
22925
  } catch {
22187
22926
  return null;
22188
22927
  }
22189
22928
  if (st.isSymbolicLink()) return "link";
22190
22929
  try {
22191
- (0, import_node_fs28.readlinkSync)(p);
22930
+ (0, import_node_fs29.readlinkSync)(p);
22192
22931
  return "link";
22193
22932
  } catch {
22194
22933
  }
@@ -22196,7 +22935,7 @@ var realWorktreeDirRemover = {
22196
22935
  },
22197
22936
  readdir: (p) => {
22198
22937
  try {
22199
- return (0, import_node_fs28.readdirSync)(p);
22938
+ return (0, import_node_fs29.readdirSync)(p);
22200
22939
  } catch {
22201
22940
  return [];
22202
22941
  }
@@ -22205,9 +22944,9 @@ var realWorktreeDirRemover = {
22205
22944
  // leaving the target); a file symlink with unlink. rmdir first, fall back to unlink.
22206
22945
  detachLink: (p) => {
22207
22946
  try {
22208
- (0, import_node_fs28.rmdirSync)(p);
22947
+ (0, import_node_fs29.rmdirSync)(p);
22209
22948
  } catch {
22210
- (0, import_node_fs28.unlinkSync)(p);
22949
+ (0, import_node_fs29.unlinkSync)(p);
22211
22950
  }
22212
22951
  },
22213
22952
  removeTree: (p) => (0, import_promises8.rm)(p, { recursive: true, force: true, maxRetries: 5, retryDelay: 200 })
@@ -22237,9 +22976,9 @@ async function worktreeHasStageState(worktreePath) {
22237
22976
  }
22238
22977
  }
22239
22978
  function stageStateFileBelongsToWorktree(statePath, worktreePath) {
22240
- if (!(0, import_node_fs28.existsSync)(statePath)) return false;
22979
+ if (!(0, import_node_fs29.existsSync)(statePath)) return false;
22241
22980
  try {
22242
- const state = JSON.parse((0, import_node_fs28.readFileSync)(statePath, "utf8"));
22981
+ const state = JSON.parse((0, import_node_fs29.readFileSync)(statePath, "utf8"));
22243
22982
  const recordedCwd = typeof state.identity?.cwd === "string" ? state.identity.cwd : typeof state.cwd === "string" ? state.cwd : "";
22244
22983
  return Boolean(recordedCwd && isPathUnderDirectory(recordedCwd, worktreePath));
22245
22984
  } catch {
@@ -22312,7 +23051,7 @@ pr.command("merge <number>").description("merge a PR (squash by default) and cle
22312
23051
  } : await cleanupPrMergeLocalBranch(headRef, {
22313
23052
  beforeWorktrees,
22314
23053
  startingPath,
22315
- pathExists: (p) => (0, import_node_fs28.existsSync)(p),
23054
+ pathExists: (p) => (0, import_node_fs29.existsSync)(p),
22316
23055
  execGit: async (args) => (await execFileP2("git", args, { timeout: GIT_TIMEOUT_MS })).stdout,
22317
23056
  teardownWorktreeStage,
22318
23057
  deferredStore,
@@ -22509,7 +23248,7 @@ function stageScopedRunOpts(o) {
22509
23248
  };
22510
23249
  }
22511
23250
  function printLine(value) {
22512
- (0, import_node_fs28.writeSync)(1, `${value}
23251
+ (0, import_node_fs29.writeSync)(1, `${value}
22513
23252
  `);
22514
23253
  }
22515
23254
  function stageKeepAlive() {
@@ -22526,8 +23265,8 @@ async function resolveStage() {
22526
23265
  local,
22527
23266
  shell: shellFor(),
22528
23267
  registry: { deployModel: project2?.deployModel, portRange, error: read.ok ? void 0 : read.error },
22529
- hasCompose: (0, import_node_fs28.existsSync)((0, import_node_path25.join)(process.cwd(), "docker-compose.yml")),
22530
- hasEnvExample: (0, import_node_fs28.existsSync)((0, import_node_path25.join)(process.cwd(), ".env.example"))
23268
+ hasCompose: (0, import_node_fs29.existsSync)((0, import_node_path26.join)(process.cwd(), "docker-compose.yml")),
23269
+ hasEnvExample: (0, import_node_fs29.existsSync)((0, import_node_path26.join)(process.cwd(), ".env.example"))
22531
23270
  });
22532
23271
  }
22533
23272
  async function fetchStageVaultEnvMerge() {
@@ -22989,7 +23728,7 @@ bootstrap.command("verify <repo>").description("audit whether an existing repo i
22989
23728
  client: defaultGitHubClient(),
22990
23729
  projectMeta: meta,
22991
23730
  deployModel: typeof meta?.deployModel === "string" ? meta.deployModel : void 0,
22992
- readLocalFile: (path2) => path2 === "projects.json" && apiProjects != null ? apiProjects : (0, import_node_fs28.existsSync)(path2) ? (0, import_node_fs28.readFileSync)(path2, "utf8") : null,
23731
+ readLocalFile: (path2) => path2 === "projects.json" && apiProjects != null ? apiProjects : (0, import_node_fs29.existsSync)(path2) ? (0, import_node_fs29.readFileSync)(path2, "utf8") : null,
22993
23732
  // requiredGcpApis is stored as an array by a JSON write, but `project set --var KEY=VALUE` stores a raw
22994
23733
  // comma-string — accept either so the seeded value verifies regardless of how it was written.
22995
23734
  requiredGcpApis: (() => {
@@ -23033,12 +23772,12 @@ bootstrap.command("apply <repo>").description("idempotent seed apply from skills
23033
23772
  return fail(`bootstrap apply: ${e.message}`);
23034
23773
  }
23035
23774
  const manifestPath = "skills/bootstrap/seeds/manifest.json";
23036
- if (!(0, import_node_fs28.existsSync)(manifestPath)) return fail(`bootstrap apply: ${manifestPath} not found; run from the MMI-Hub repo root`);
23037
- const manifest = loadBootstrapSeeds((0, import_node_fs28.readFileSync)(manifestPath, "utf8"));
23775
+ if (!(0, import_node_fs29.existsSync)(manifestPath)) return fail(`bootstrap apply: ${manifestPath} not found; run from the MMI-Hub repo root`);
23776
+ const manifest = loadBootstrapSeeds((0, import_node_fs29.readFileSync)(manifestPath, "utf8"));
23038
23777
  const baseBranch = o.class === "content" ? "main" : "development";
23039
23778
  const slug = parsedRepo.slug;
23040
23779
  const gh = async (args) => execFileP2("gh", args, { timeout: 2e4 });
23041
- const readFile7 = (p) => (0, import_node_fs28.existsSync)(p) ? (0, import_node_fs28.readFileSync)(p, "utf8") : null;
23780
+ const readFile7 = (p) => (0, import_node_fs29.existsSync)(p) ? (0, import_node_fs29.readFileSync)(p, "utf8") : null;
23042
23781
  const enc = (p) => p.split("/").map(encodeURIComponent).join("/");
23043
23782
  const rawVars = {};
23044
23783
  for (const value of rawValues("--var")) {
@@ -23289,16 +24028,16 @@ access.command("audit").description("audit collaborator roles + train-branch pus
23289
24028
  if (o.class !== "deployable" && o.class !== "content") return failGraceful("access audit: --class must be deployable or content");
23290
24029
  targets = [{ repo: o.repo, class: o.class }];
23291
24030
  } else {
23292
- const projectsJson = registryProjects ? JSON.stringify({ projects: registryProjects }) : (0, import_node_fs28.existsSync)("projects.json") ? (0, import_node_fs28.readFileSync)("projects.json", "utf8") : null;
24031
+ const projectsJson = registryProjects ? JSON.stringify({ projects: registryProjects }) : (0, import_node_fs29.existsSync)("projects.json") ? (0, import_node_fs29.readFileSync)("projects.json", "utf8") : null;
23293
24032
  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>");
23294
- const fanoutJson = (0, import_node_fs28.existsSync)(".github/fanout-targets.json") ? (0, import_node_fs28.readFileSync)(".github/fanout-targets.json", "utf8") : null;
24033
+ const fanoutJson = (0, import_node_fs29.existsSync)(".github/fanout-targets.json") ? (0, import_node_fs29.readFileSync)(".github/fanout-targets.json", "utf8") : null;
23295
24034
  targets = loadAccessTargets(projectsJson, fanoutJson);
23296
24035
  }
23297
24036
  const derivedMatrix = registryProjects ? accessMatrixFromProjects(registryProjects) : {};
23298
- const fileMatrix = (0, import_node_fs28.existsSync)("access-matrix.json") ? loadAccessMatrix((0, import_node_fs28.readFileSync)("access-matrix.json", "utf8")) : {};
24037
+ const fileMatrix = (0, import_node_fs29.existsSync)("access-matrix.json") ? loadAccessMatrix((0, import_node_fs29.readFileSync)("access-matrix.json", "utf8")) : {};
23299
24038
  const matrix = mergeAccessMatrix(fileMatrix, derivedMatrix);
23300
24039
  const derivedContracts = registryProjects ? dataAccessContractsFromProjects(registryProjects) : { consumers: {} };
23301
- const fileContracts = (0, import_node_fs28.existsSync)("data-access-contracts.json") ? loadDataAccessContracts((0, import_node_fs28.readFileSync)("data-access-contracts.json", "utf8")) : { consumers: {} };
24040
+ const fileContracts = (0, import_node_fs29.existsSync)("data-access-contracts.json") ? loadDataAccessContracts((0, import_node_fs29.readFileSync)("data-access-contracts.json", "utf8")) : { consumers: {} };
23302
24041
  const dataAccess = mergeDataAccessContracts(fileContracts, derivedContracts);
23303
24042
  const report = await auditOrgAccess(targets, deps, matrix, dataAccess);
23304
24043
  console.log(o.json ? JSON.stringify(report, null, 2) : renderAccessReport(report));
@@ -23399,7 +24138,7 @@ program2.command("session-start").description("run the SessionStart verbs (rules
23399
24138
  } catch (e) {
23400
24139
  console.error(`[mmi-hook] saga session failed: ${e.message}`);
23401
24140
  }
23402
- spawnDetachedSelf(["docs", "sync", "--quiet"], { spawn: import_node_child_process13.spawn, execPath: process.execPath, scriptPath: process.argv[1] });
24141
+ spawnDetachedSelf(["docs", "sync", "--quiet"], { spawn: import_node_child_process15.spawn, execPath: process.execPath, scriptPath: process.argv[1] });
23403
24142
  spawnDeferredGcSweep();
23404
24143
  let northstarInjected = false;
23405
24144
  const { parallel, sequential } = buildSessionStartPlan({
@@ -23441,7 +24180,7 @@ program2.command("session-start").description("run the SessionStart verbs (rules
23441
24180
  readBoard,
23442
24181
  // #1813: warm the slice cache out-of-band (detached, like docs sync) so the ~20s live read
23443
24182
  // never costs banner time and next session's glance renders instantly within budget.
23444
- scheduleRefresh: () => spawnDetachedSelf(["board", "slice-refresh", "--quiet"], { spawn: import_node_child_process13.spawn, execPath: process.execPath, scriptPath: process.argv[1] })
24183
+ scheduleRefresh: () => spawnDetachedSelf(["board", "slice-refresh", "--quiet"], { spawn: import_node_child_process15.spawn, execPath: process.execPath, scriptPath: process.argv[1] })
23445
24184
  }),
23446
24185
  spineReconcile: async (io) => {
23447
24186
  const cfg = await loadConfig();
@@ -23465,7 +24204,7 @@ program2.command("session-start").description("run the SessionStart verbs (rules
23465
24204
  for (const line of scratchGcLines(process.cwd())) consoleIo.log(line);
23466
24205
  const worktreeBanner = worktreeAutoProvisionBanner(process.cwd());
23467
24206
  if (worktreeBanner) {
23468
- spawnDetachedSelf(["worktree", "setup", "--quiet"], { spawn: import_node_child_process13.spawn, execPath: process.execPath, scriptPath: process.argv[1] });
24207
+ spawnDetachedSelf(["worktree", "setup", "--quiet"], { spawn: import_node_child_process15.spawn, execPath: process.execPath, scriptPath: process.argv[1] });
23469
24208
  consoleIo.log(worktreeBanner);
23470
24209
  }
23471
24210
  });