@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/README.md +1 -0
- package/dist/main.cjs +1120 -381
- package/dist/overlord-controller.cjs +322 -0
- package/package.json +2 -1
package/dist/main.cjs
CHANGED
|
@@ -3408,7 +3408,7 @@ var program = new Command();
|
|
|
3408
3408
|
|
|
3409
3409
|
// src/index.ts
|
|
3410
3410
|
var import_promises8 = require("node:fs/promises");
|
|
3411
|
-
var
|
|
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
|
|
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/
|
|
9958
|
+
// src/overlord.ts
|
|
9959
9959
|
var import_node_child_process8 = require("node:child_process");
|
|
9960
|
-
var
|
|
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
|
-
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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
|
|
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)(
|
|
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
|
|
11032
|
-
var
|
|
11033
|
-
var BOARD_SLICE_CACHE_FILE = (0,
|
|
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,
|
|
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,
|
|
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,
|
|
11052
|
-
(0,
|
|
11053
|
-
(0,
|
|
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,
|
|
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
|
|
11185
|
-
var
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
11785
|
+
const abs = dir ? (0, import_node_path16.join)(root, dir) : root;
|
|
11216
11786
|
return {
|
|
11217
11787
|
dir,
|
|
11218
|
-
hasPackageJson: fs2.isFile((0,
|
|
11219
|
-
hasLockfile: fs2.isFile((0,
|
|
11220
|
-
hasNodeModules: fs2.isDir((0,
|
|
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,
|
|
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,
|
|
11241
|
-
(0,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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
|
|
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
|
|
12613
|
-
var
|
|
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,
|
|
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
|
|
13749
|
-
var
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
13819
|
-
if (!(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
|
|
13849
|
-
var
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
13906
|
-
const p = (0,
|
|
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,
|
|
13996
|
-
if (!(0,
|
|
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,
|
|
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,
|
|
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,
|
|
14040
|
-
deps.mkdir((0,
|
|
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,
|
|
14047
|
-
deps.mkdir((0,
|
|
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,
|
|
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
|
|
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,
|
|
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
|
|
14817
|
-
var
|
|
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,
|
|
14832
|
-
const raw = JSON.parse((0,
|
|
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,
|
|
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,
|
|
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,
|
|
14871
|
-
const ddbScriptPath = (0,
|
|
14872
|
-
if (!(0,
|
|
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,
|
|
14879
|
-
const sibling = portRangeInfraAt((0,
|
|
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,
|
|
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
|
|
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
|
|
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
|
|
16615
|
-
var
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
17238
|
+
(0, import_node_fs22.writeFileSync)(planPath(slug), content, "utf8");
|
|
16655
17239
|
},
|
|
16656
17240
|
removeLocal: (slug) => {
|
|
16657
17241
|
try {
|
|
16658
|
-
(0,
|
|
17242
|
+
(0, import_node_fs22.rmSync)(planPath(slug));
|
|
16659
17243
|
} catch {
|
|
16660
17244
|
}
|
|
16661
17245
|
},
|
|
16662
17246
|
readMetaRaw: () => {
|
|
16663
17247
|
try {
|
|
16664
|
-
return (0,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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
|
|
17487
|
-
var
|
|
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,
|
|
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,
|
|
17499
|
-
const masterKeyPath = (0,
|
|
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,
|
|
17506
|
-
env.RAILS_MASTER_KEY = (0,
|
|
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,
|
|
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
|
|
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
|
|
17810
|
-
var
|
|
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
|
|
17831
|
-
var
|
|
17832
|
-
var
|
|
17833
|
-
var
|
|
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)(
|
|
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,
|
|
17851
|
-
return (0,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
17875
|
-
for (const name of (0,
|
|
17876
|
-
(0,
|
|
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,
|
|
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,
|
|
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,
|
|
17892
|
-
return (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,
|
|
18483
|
+
const tarPath = (0, import_node_path22.join)(tmpRoot, "repo.tgz");
|
|
17899
18484
|
try {
|
|
17900
|
-
(0,
|
|
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,
|
|
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,
|
|
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,
|
|
17912
|
-
return (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,
|
|
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,
|
|
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,
|
|
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}
|
|
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
|
|
19055
|
-
var
|
|
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,
|
|
19063
|
-
if (!(0,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
19157
|
-
const resolvedGitdir = (0,
|
|
19158
|
-
if (sameWorktreeMetadataPath((0,
|
|
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,
|
|
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,
|
|
19821
|
+
const gitPath = (0, import_node_path24.join)(path2, ".git");
|
|
19178
19822
|
let st;
|
|
19179
19823
|
try {
|
|
19180
|
-
st = (0,
|
|
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,
|
|
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,
|
|
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,
|
|
19234
|
-
return entries.filter((ent) => ent.isDirectory()).map((ent) => inspectSiblingWorktreeDir((0,
|
|
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,
|
|
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
|
|
19408
|
-
if (!
|
|
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
|
-
|
|
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,
|
|
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,
|
|
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,
|
|
19436
|
-
pluginCachePresent: (0,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
20162
|
+
(0, import_node_fs28.copyFileSync)(path2, `${path2}.bak`);
|
|
19487
20163
|
file.plugins[pluginId] = records;
|
|
19488
|
-
(0,
|
|
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,
|
|
20172
|
+
return (0, import_node_path25.join)((0, import_node_os6.homedir)(), ".config", "opencode");
|
|
19497
20173
|
}
|
|
19498
20174
|
function opencodeConfigPath() {
|
|
19499
|
-
return (0,
|
|
20175
|
+
return (0, import_node_path25.join)(opencodeConfigDir(), "opencode.jsonc");
|
|
19500
20176
|
}
|
|
19501
20177
|
function opencodeCommandsDir() {
|
|
19502
|
-
return (0,
|
|
20178
|
+
return (0, import_node_path25.join)(opencodeConfigDir(), "commands");
|
|
19503
20179
|
}
|
|
19504
20180
|
function opencodeSkillsPath() {
|
|
19505
|
-
return (0,
|
|
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,
|
|
20185
|
+
if (!(0, import_node_fs28.existsSync)(path2)) return { path: path2, hasConfig: false, hasPluginField: false, parseOk: true };
|
|
19510
20186
|
try {
|
|
19511
|
-
const raw = (0,
|
|
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,
|
|
19535
|
-
if (snapshot.hasConfig) (0,
|
|
19536
|
-
(0,
|
|
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,
|
|
19552
|
-
if (snapshot.hasConfig && (0,
|
|
19553
|
-
(0,
|
|
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,
|
|
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,
|
|
20246
|
+
(0, import_node_fs28.mkdirSync)(dir, { recursive: true });
|
|
19571
20247
|
for (const command of OPENCODE_WORKFLOW_COMMANDS) {
|
|
19572
|
-
(0,
|
|
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,
|
|
19582
|
-
(0,
|
|
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,
|
|
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,
|
|
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,
|
|
19618
|
-
return (0,
|
|
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,
|
|
20297
|
+
return (0, import_node_path25.join)((0, import_node_os6.homedir)(), "Library", "Application Support", "ai.opencode.desktop", "logs");
|
|
19622
20298
|
}
|
|
19623
|
-
return (0,
|
|
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,
|
|
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,
|
|
19632
|
-
const text = (0,
|
|
19633
|
-
return opencodeAgentDirectoriesFromLog(text).filter((directory) => !(0,
|
|
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,
|
|
19640
|
-
if (!(0,
|
|
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,
|
|
19653
|
-
(0,
|
|
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,
|
|
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,
|
|
19666
|
-
const path2 = (0,
|
|
19667
|
-
const pluginJson = (0,
|
|
19668
|
-
const hooksJson = (0,
|
|
19669
|
-
const cliBundle = (0,
|
|
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,
|
|
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,
|
|
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,
|
|
19687
|
-
hasHooksJson: (0,
|
|
19688
|
-
hasCliBundle: (0,
|
|
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,
|
|
19699
|
-
return (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,
|
|
19704
|
-
{ surface: "codex", root: (0,
|
|
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,
|
|
20387
|
+
const entries = (0, import_node_fs28.readdirSync)(root, { withFileTypes: true }).map((entry) => ({
|
|
19709
20388
|
name: entry.name,
|
|
19710
|
-
path: (0,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
20423
|
+
if (!(0, import_node_fs28.existsSync)(move.from)) continue;
|
|
19745
20424
|
const target = uniqueQuarantineTarget(move.to);
|
|
19746
|
-
(0,
|
|
19747
|
-
(0,
|
|
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,
|
|
20445
|
+
if (!(0, import_node_fs28.existsSync)(targetPath)) return true;
|
|
19767
20446
|
if (isWin) {
|
|
19768
|
-
const emptyDir = (0,
|
|
19769
|
-
(0,
|
|
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,
|
|
20451
|
+
(0, import_node_fs28.rmSync)(targetPath, { recursive: true, force: true });
|
|
19773
20452
|
} finally {
|
|
19774
20453
|
try {
|
|
19775
|
-
(0,
|
|
20454
|
+
(0, import_node_fs28.rmSync)(emptyDir, { recursive: true, force: true });
|
|
19776
20455
|
} catch {
|
|
19777
20456
|
}
|
|
19778
20457
|
}
|
|
19779
|
-
return !(0,
|
|
20458
|
+
return !(0, import_node_fs28.existsSync)(targetPath);
|
|
19780
20459
|
}
|
|
19781
|
-
(0,
|
|
19782
|
-
return !(0,
|
|
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,
|
|
20474
|
+
var gitignorePath = () => (0, import_node_path25.join)(process.cwd(), ".gitignore");
|
|
19796
20475
|
function readTextFile(path2) {
|
|
19797
20476
|
try {
|
|
19798
|
-
if (!(0,
|
|
19799
|
-
return (0,
|
|
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,
|
|
20485
|
+
const home = (0, import_node_os6.homedir)();
|
|
19807
20486
|
const candidates = [
|
|
19808
|
-
(0,
|
|
19809
|
-
(0,
|
|
19810
|
-
(0,
|
|
19811
|
-
(0,
|
|
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,
|
|
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,
|
|
19844
|
-
const queue = parseQueue(readTextFile((0,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
21201
|
+
if ((0, import_node_fs28.existsSync)(path2)) {
|
|
20492
21202
|
try {
|
|
20493
|
-
current = JSON.parse((0,
|
|
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,
|
|
20501
|
-
if ((0,
|
|
20502
|
-
(0,
|
|
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
|
-
${
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
20753
|
-
const current = (0,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
21688
|
+
const lockPath = (0, import_node_path26.join)(worktreeRoot, ".mmi", "worktree-setup.lock");
|
|
20973
21689
|
const take = () => {
|
|
20974
|
-
const fd = (0,
|
|
21690
|
+
const fd = (0, import_node_fs29.openSync)(lockPath, "wx");
|
|
20975
21691
|
try {
|
|
20976
|
-
(0,
|
|
21692
|
+
(0, import_node_fs29.writeSync)(fd, String(Date.now()));
|
|
20977
21693
|
} finally {
|
|
20978
|
-
(0,
|
|
21694
|
+
(0, import_node_fs29.closeSync)(fd);
|
|
20979
21695
|
}
|
|
20980
21696
|
return () => {
|
|
20981
21697
|
try {
|
|
20982
|
-
(0,
|
|
21698
|
+
(0, import_node_fs29.rmSync)(lockPath, { force: true });
|
|
20983
21699
|
} catch {
|
|
20984
21700
|
}
|
|
20985
21701
|
};
|
|
20986
21702
|
};
|
|
20987
21703
|
try {
|
|
20988
|
-
(0,
|
|
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,
|
|
20993
|
-
(0,
|
|
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,
|
|
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,
|
|
21998
|
-
if (!(0,
|
|
21999
|
-
return (0,
|
|
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,
|
|
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:
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
22947
|
+
(0, import_node_fs29.rmdirSync)(p);
|
|
22209
22948
|
} catch {
|
|
22210
|
-
(0,
|
|
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,
|
|
22979
|
+
if (!(0, import_node_fs29.existsSync)(statePath)) return false;
|
|
22241
22980
|
try {
|
|
22242
|
-
const state = JSON.parse((0,
|
|
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,
|
|
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,
|
|
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,
|
|
22530
|
-
hasEnvExample: (0,
|
|
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,
|
|
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,
|
|
23037
|
-
const manifest = loadBootstrapSeeds((0,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
});
|