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