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