@mutmutco/cli 3.9.0 → 3.10.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 +551 -287
- 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_fs20 = 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";
|
|
@@ -4524,7 +4524,12 @@ function buildSessionStartPlan(verbs) {
|
|
|
4524
4524
|
{ name: "board slice", run: verbs.boardSlice }
|
|
4525
4525
|
],
|
|
4526
4526
|
sequential: [
|
|
4527
|
-
//
|
|
4527
|
+
// local train sync (#2582): fast-forward local development/main/rc to origin BEFORE doctor + before
|
|
4528
|
+
// the agent works, so a checkout never lags behind a release someone pushed to origin (and the
|
|
4529
|
+
// deferred main -> development alignment merge lands locally). Sequential + fail-soft: it runs one
|
|
4530
|
+
// bounded `git fetch` + ff, silent unless a branch actually moved, and never blocks the banner.
|
|
4531
|
+
{ name: "train sync", run: verbs.syncTrain },
|
|
4532
|
+
// Doctor runs last so it sees the final (synced) tree.
|
|
4528
4533
|
{ name: "doctor", run: verbs.doctor }
|
|
4529
4534
|
]
|
|
4530
4535
|
};
|
|
@@ -4566,6 +4571,103 @@ function scratchGcLines(cwd, env = process.env, now = Date.now()) {
|
|
|
4566
4571
|
}
|
|
4567
4572
|
}
|
|
4568
4573
|
|
|
4574
|
+
// src/git-clean-tree.ts
|
|
4575
|
+
function isAgentScratchPath(path2) {
|
|
4576
|
+
const normalized = path2.replace(/\\/g, "/").trim();
|
|
4577
|
+
return /^tmp_[^/]+$/.test(normalized);
|
|
4578
|
+
}
|
|
4579
|
+
function porcelainHasBlockingChanges(porcelain) {
|
|
4580
|
+
return porcelain.split("\n").some((line) => {
|
|
4581
|
+
const trimmed = line.trim();
|
|
4582
|
+
if (!trimmed) return false;
|
|
4583
|
+
const path2 = trimmed.slice(3).split(" -> ")[0]?.trim() ?? "";
|
|
4584
|
+
return path2 !== "" && !isAgentScratchPath(path2);
|
|
4585
|
+
});
|
|
4586
|
+
}
|
|
4587
|
+
|
|
4588
|
+
// src/local-train-sync.ts
|
|
4589
|
+
var TRAIN_BRANCHES = ["development", "main", "rc"];
|
|
4590
|
+
function clean(out) {
|
|
4591
|
+
return out.trim();
|
|
4592
|
+
}
|
|
4593
|
+
async function revParse(run, ref) {
|
|
4594
|
+
try {
|
|
4595
|
+
return clean(await run("git", ["rev-parse", "--verify", "--quiet", ref]));
|
|
4596
|
+
} catch {
|
|
4597
|
+
return "";
|
|
4598
|
+
}
|
|
4599
|
+
}
|
|
4600
|
+
async function isFastForward(run, ancestor, descendant) {
|
|
4601
|
+
try {
|
|
4602
|
+
await run("git", ["merge-base", "--is-ancestor", ancestor, descendant]);
|
|
4603
|
+
return true;
|
|
4604
|
+
} catch {
|
|
4605
|
+
return false;
|
|
4606
|
+
}
|
|
4607
|
+
}
|
|
4608
|
+
async function syncOneBranch(run, branch, currentBranch2, warn) {
|
|
4609
|
+
const localSha = await revParse(run, `refs/heads/${branch}`);
|
|
4610
|
+
if (!localSha) {
|
|
4611
|
+
return { branch, status: "skipped", reason: "no-local-branch", note: `${branch}: no local branch \u2014 skipped` };
|
|
4612
|
+
}
|
|
4613
|
+
const remoteSha = await revParse(run, `refs/remotes/origin/${branch}`);
|
|
4614
|
+
if (!remoteSha) {
|
|
4615
|
+
return { branch, status: "skipped", reason: "no-remote-branch", note: `${branch}: no origin/${branch} \u2014 skipped` };
|
|
4616
|
+
}
|
|
4617
|
+
if (localSha === remoteSha) {
|
|
4618
|
+
return { branch, status: "already-current", note: `${branch}: already at origin/${branch}` };
|
|
4619
|
+
}
|
|
4620
|
+
if (!await isFastForward(run, localSha, remoteSha)) {
|
|
4621
|
+
const note = `${branch}: local diverged from origin/${branch} \u2014 left untouched (never force)`;
|
|
4622
|
+
warn?.(note);
|
|
4623
|
+
return { branch, status: "skipped", reason: "diverged", note };
|
|
4624
|
+
}
|
|
4625
|
+
try {
|
|
4626
|
+
if (branch === currentBranch2) {
|
|
4627
|
+
const porcelain = await run("git", ["status", "--porcelain"]);
|
|
4628
|
+
if (porcelainHasBlockingChanges(porcelain)) {
|
|
4629
|
+
return { branch, status: "skipped", reason: "current-dirty", note: `${branch}: checked out with local changes \u2014 skipped (commit/stash, then rerun)` };
|
|
4630
|
+
}
|
|
4631
|
+
await run("git", ["merge", "--ff-only", `origin/${branch}`]);
|
|
4632
|
+
} else {
|
|
4633
|
+
await run("git", ["fetch", "origin", `${branch}:${branch}`]);
|
|
4634
|
+
}
|
|
4635
|
+
return { branch, status: "synced", note: `${branch}: fast-forwarded to origin/${branch}` };
|
|
4636
|
+
} catch (e) {
|
|
4637
|
+
const note = `${branch}: fast-forward failed \u2014 ${e instanceof Error ? e.message : String(e)}`;
|
|
4638
|
+
warn?.(note);
|
|
4639
|
+
return { branch, status: "skipped", reason: "ff-failed", note };
|
|
4640
|
+
}
|
|
4641
|
+
}
|
|
4642
|
+
async function syncLocalTrainBranches(run, options = {}) {
|
|
4643
|
+
const branches = options.branches ?? TRAIN_BRANCHES;
|
|
4644
|
+
const doFetch = options.fetch ?? true;
|
|
4645
|
+
const fetchRun = options.fetchRun ?? ((args) => run("git", args));
|
|
4646
|
+
if (doFetch) {
|
|
4647
|
+
try {
|
|
4648
|
+
await fetchRun(["fetch", "origin"]);
|
|
4649
|
+
} catch (e) {
|
|
4650
|
+
options.warn?.(`local-train-sync: git fetch origin failed \u2014 syncing against cached refs (${e instanceof Error ? e.message : String(e)})`);
|
|
4651
|
+
}
|
|
4652
|
+
}
|
|
4653
|
+
let currentBranch2 = "";
|
|
4654
|
+
try {
|
|
4655
|
+
currentBranch2 = clean(await run("git", ["rev-parse", "--abbrev-ref", "HEAD"]));
|
|
4656
|
+
} catch {
|
|
4657
|
+
currentBranch2 = "";
|
|
4658
|
+
}
|
|
4659
|
+
const results = [];
|
|
4660
|
+
for (const branch of branches) {
|
|
4661
|
+
results.push(await syncOneBranch(run, branch, currentBranch2, options.warn));
|
|
4662
|
+
}
|
|
4663
|
+
return { branches: results, changed: results.some((r) => r.status === "synced") };
|
|
4664
|
+
}
|
|
4665
|
+
function localTrainSyncBannerLine(result) {
|
|
4666
|
+
const moved = result.branches.filter((b) => b.status === "synced").map((b) => b.branch);
|
|
4667
|
+
if (moved.length === 0) return "";
|
|
4668
|
+
return `synced local ${moved.join(", ")} \u2192 origin`;
|
|
4669
|
+
}
|
|
4670
|
+
|
|
4569
4671
|
// src/board.ts
|
|
4570
4672
|
var import_node_child_process5 = require("node:child_process");
|
|
4571
4673
|
var import_node_util5 = require("node:util");
|
|
@@ -5868,7 +5970,7 @@ function whoamiLine(report) {
|
|
|
5868
5970
|
}
|
|
5869
5971
|
|
|
5870
5972
|
// src/index.ts
|
|
5871
|
-
var
|
|
5973
|
+
var import_node_path20 = require("node:path");
|
|
5872
5974
|
|
|
5873
5975
|
// src/merge-ci-policy.ts
|
|
5874
5976
|
function resolveMergeCiPolicy(input) {
|
|
@@ -6148,6 +6250,9 @@ var MANAGED_GITIGNORE_LINES = [
|
|
|
6148
6250
|
// doctor` flags one explicitly instead (buildRepoLocalWorktreeCheck) rather than baking the fallback path
|
|
6149
6251
|
// into every repo's .gitignore.
|
|
6150
6252
|
".aws-sam/",
|
|
6253
|
+
// jerv-cli lease markers — the ephemeral per-worktree lease ledger `jerv-cli lease open` writes into a
|
|
6254
|
+
// worktree root; never tracked (it lives only for the life of the worktree). Hub#2586.
|
|
6255
|
+
".jerv-lease.json",
|
|
6151
6256
|
"/*.png",
|
|
6152
6257
|
// Runtime secrets/config are vault-only (AGENTS.md "Privileged ops") — never commit a local .env. The
|
|
6153
6258
|
// .env.example reference file stays tracked.
|
|
@@ -9689,20 +9794,6 @@ async function runStageLiveDown(deps, t) {
|
|
|
9689
9794
|
};
|
|
9690
9795
|
}
|
|
9691
9796
|
|
|
9692
|
-
// src/git-clean-tree.ts
|
|
9693
|
-
function isAgentScratchPath(path2) {
|
|
9694
|
-
const normalized = path2.replace(/\\/g, "/").trim();
|
|
9695
|
-
return /^tmp_[^/]+$/.test(normalized);
|
|
9696
|
-
}
|
|
9697
|
-
function porcelainHasBlockingChanges(porcelain) {
|
|
9698
|
-
return porcelain.split("\n").some((line) => {
|
|
9699
|
-
const trimmed = line.trim();
|
|
9700
|
-
if (!trimmed) return false;
|
|
9701
|
-
const path2 = trimmed.slice(3).split(" -> ")[0]?.trim() ?? "";
|
|
9702
|
-
return path2 !== "" && !isAgentScratchPath(path2);
|
|
9703
|
-
});
|
|
9704
|
-
}
|
|
9705
|
-
|
|
9706
9797
|
// src/tenant-control-parse.ts
|
|
9707
9798
|
var OUTPUT_BEGIN = "mmi-control-output-begin";
|
|
9708
9799
|
var OUTPUT_END = "mmi-control-output-end";
|
|
@@ -9743,7 +9834,7 @@ function resolveDeployModel2(meta, repo) {
|
|
|
9743
9834
|
if (isDeployModel(m)) return m;
|
|
9744
9835
|
return resolveDeployModel(meta, repo);
|
|
9745
9836
|
}
|
|
9746
|
-
function
|
|
9837
|
+
function clean2(out) {
|
|
9747
9838
|
return out.trim();
|
|
9748
9839
|
}
|
|
9749
9840
|
function requireValue(value, label) {
|
|
@@ -9826,11 +9917,11 @@ async function verifyPublishedRelease(deps, repo, tag, expectedTarget, expectedS
|
|
|
9826
9917
|
if (release.targetCommitish !== expectedTarget) {
|
|
9827
9918
|
throw new Error(`Release ${tag} targetCommitish is ${String(release.targetCommitish || "(missing)")}, expected ${expectedTarget}`);
|
|
9828
9919
|
}
|
|
9829
|
-
const tagSha = requireValue(
|
|
9920
|
+
const tagSha = requireValue(clean2(await deps.run("git", ["rev-parse", `${tag}^{commit}`])), "release tag sha");
|
|
9830
9921
|
if (tagSha !== expectedSha) {
|
|
9831
9922
|
throw new Error(`Release ${tag} tag points at ${tagSha}, expected ${expectedSha}`);
|
|
9832
9923
|
}
|
|
9833
|
-
const branchOut =
|
|
9924
|
+
const branchOut = clean2(await runGitRemoteRead(deps, ["ls-remote", "origin", `refs/heads/${expectedTarget}`]));
|
|
9834
9925
|
const branchSha = requireValue(branchOut.split(/\s+/)[0] ?? "", `origin/${expectedTarget} sha`);
|
|
9835
9926
|
if (branchSha !== expectedSha) {
|
|
9836
9927
|
throw new Error(`origin/${expectedTarget} points at ${branchSha}, expected ${expectedSha}`);
|
|
@@ -9891,7 +9982,7 @@ function ensurePositiveCount(out, emptyMessage) {
|
|
|
9891
9982
|
if (count <= 0) throw new Error(emptyMessage);
|
|
9892
9983
|
}
|
|
9893
9984
|
async function remoteBranchExists(deps, branch) {
|
|
9894
|
-
const out =
|
|
9985
|
+
const out = clean2(await runGitRemoteRead(deps, ["ls-remote", "origin", `refs/heads/${branch}`]));
|
|
9895
9986
|
return out.length > 0;
|
|
9896
9987
|
}
|
|
9897
9988
|
async function loadReleaseTrackBranchHints(deps) {
|
|
@@ -9903,10 +9994,10 @@ async function loadReleaseTrackBranchHints(deps) {
|
|
|
9903
9994
|
return { hasDevelopmentBranch, hasMainBranch, hasRcBranch };
|
|
9904
9995
|
}
|
|
9905
9996
|
async function buildTrainApplyContext(deps) {
|
|
9906
|
-
const repo = requireValue(
|
|
9997
|
+
const repo = requireValue(clean2(await deps.run("gh", ["repo", "view", "--json", "nameWithOwner", "--jq", ".nameWithOwner"])), "repo");
|
|
9907
9998
|
const [owner, name] = repo.split("/");
|
|
9908
9999
|
if (!owner || !name) throw new Error(`repo must be owner/name, got ${repo}`);
|
|
9909
|
-
const login = requireValue(
|
|
10000
|
+
const login = requireValue(clean2(await deps.run("gh", ["api", "user", "--jq", ".login"])), "GitHub login");
|
|
9910
10001
|
const verdict = await deps.trainAuthority(repo);
|
|
9911
10002
|
if (!verdict.ok) throw new Error(`${commandAuthorityLabel(owner)}: train authority could not be verified (${verdict.error})`);
|
|
9912
10003
|
if (!verdict.train) {
|
|
@@ -9927,11 +10018,11 @@ async function requireCleanTree(deps) {
|
|
|
9927
10018
|
if (porcelainHasBlockingChanges(status)) throw new Error("working tree must be clean before train apply");
|
|
9928
10019
|
}
|
|
9929
10020
|
async function requireBranch(deps, branch) {
|
|
9930
|
-
const current =
|
|
10021
|
+
const current = clean2(await deps.run("git", ["rev-parse", "--abbrev-ref", "HEAD"]));
|
|
9931
10022
|
if (current !== branch) throw new Error(`must run from ${branch}, currently on ${current || "(unknown)"}`);
|
|
9932
10023
|
}
|
|
9933
10024
|
async function currentBranch(deps) {
|
|
9934
|
-
return
|
|
10025
|
+
return clean2(await deps.run("git", ["rev-parse", "--abbrev-ref", "HEAD"])) || "(unknown)";
|
|
9935
10026
|
}
|
|
9936
10027
|
async function restoreCheckoutAfterRelease(deps, startBranch) {
|
|
9937
10028
|
const status = await deps.run("git", ["status", "--porcelain"]);
|
|
@@ -9974,17 +10065,17 @@ async function restoreCheckoutAfterRelease(deps, startBranch) {
|
|
|
9974
10065
|
}
|
|
9975
10066
|
}
|
|
9976
10067
|
async function withCheckoutRestoredAfterRelease(deps, result, startBranch) {
|
|
9977
|
-
|
|
9978
|
-
|
|
9979
|
-
|
|
9980
|
-
};
|
|
10068
|
+
const checkout = await restoreCheckoutAfterRelease(deps, startBranch);
|
|
10069
|
+
const branches = TRAIN_BRANCHES.filter((b) => b !== startBranch);
|
|
10070
|
+
const localSync = await syncLocalTrainBranches(deps.run, { branches, fetch: false, warn: deps.warn }).catch(() => void 0);
|
|
10071
|
+
return { ...result, checkout, ...localSync ? { localSync } : {} };
|
|
9981
10072
|
}
|
|
9982
10073
|
function normGitPath(p) {
|
|
9983
10074
|
return p.replace(/\\/g, "/").replace(/\/+$/, "").toLowerCase();
|
|
9984
10075
|
}
|
|
9985
10076
|
async function releaseRcBranchLock(deps) {
|
|
9986
10077
|
const porcelain = await deps.run("git", ["worktree", "list", "--porcelain"]);
|
|
9987
|
-
const cwd = normGitPath(
|
|
10078
|
+
const cwd = normGitPath(clean2(await deps.run("git", ["rev-parse", "--show-toplevel"])));
|
|
9988
10079
|
for (const wt of parseWorktreePorcelain(porcelain)) {
|
|
9989
10080
|
if (wt.branch !== "rc") continue;
|
|
9990
10081
|
if (normGitPath(wt.path) === cwd) continue;
|
|
@@ -10200,7 +10291,7 @@ async function discoverRequiredCheckContexts(deps, ctx, branch) {
|
|
|
10200
10291
|
return [...contexts];
|
|
10201
10292
|
}
|
|
10202
10293
|
async function findOpenAlignmentPr(deps, ctx) {
|
|
10203
|
-
const out =
|
|
10294
|
+
const out = clean2(await deps.run("gh", ["pr", "list", "--repo", ctx.repo, "--base", "development", "--head", "main", "--state", "open", "--json", "number,url"]));
|
|
10204
10295
|
if (!out) return void 0;
|
|
10205
10296
|
const rows = JSON.parse(out);
|
|
10206
10297
|
const row = rows.find((r) => typeof r.number === "number" && typeof r.url === "string");
|
|
@@ -10228,14 +10319,14 @@ async function rollDevelopmentForward(deps, ctx, tag) {
|
|
|
10228
10319
|
note: `alignment PR already open: ${existing.url} \u2014 land it with \`mmi-cli pr merge ${existing.number} --auto --merge\``
|
|
10229
10320
|
};
|
|
10230
10321
|
}
|
|
10231
|
-
const ahead =
|
|
10322
|
+
const ahead = clean2(await deps.run("git", ["rev-list", "--count", "origin/development..main"]));
|
|
10232
10323
|
if (ahead === "0") {
|
|
10233
10324
|
return { status: "aligned", note: "development already contains the released main; nothing to roll forward" };
|
|
10234
10325
|
}
|
|
10235
10326
|
const body = `Carries the ${tag} release (including the version fold) from \`main\` back to \`development\`.
|
|
10236
10327
|
|
|
10237
10328
|
\`development\` requires status checks, so the release train opens this alignment PR instead of a direct push of the un-checked merge commit (#1143). Land it with a **true merge** \u2014 \`mmi-cli pr merge <n> --auto --merge\` (not squash) so the merge parentage survives and the misalignment guard stays satisfied. \`--auto\` waits out the checks this PR triggers, which otherwise block an immediate merge right after the release.`;
|
|
10238
|
-
const url =
|
|
10329
|
+
const url = clean2(await deps.run("gh", ["pr", "create", "--repo", ctx.repo, "--base", "development", "--head", "main", "--title", `chore(release): align development to ${tag}`, "--body", body]));
|
|
10239
10330
|
const number = parsePrNumber(url);
|
|
10240
10331
|
return {
|
|
10241
10332
|
status: "pr-pending",
|
|
@@ -10353,13 +10444,13 @@ Do not delete or force-move the pushed tag; rerun the train only after confirmin
|
|
|
10353
10444
|
}
|
|
10354
10445
|
async function probeRemoteTag(deps, tag) {
|
|
10355
10446
|
const remoteOut = await runGitRemoteRead(deps, ["ls-remote", "origin", `refs/tags/${tag}`]);
|
|
10356
|
-
return
|
|
10447
|
+
return clean2(remoteOut).split(/\s+/)[0] || "";
|
|
10357
10448
|
}
|
|
10358
10449
|
async function ensureTagPushed(deps, tag, sha, probed) {
|
|
10359
10450
|
const remoteSha = probed ? probed.remoteSha : await probeRemoteTag(deps, tag);
|
|
10360
10451
|
let localSha = "";
|
|
10361
10452
|
try {
|
|
10362
|
-
localSha =
|
|
10453
|
+
localSha = clean2(await deps.run("git", ["rev-parse", "--verify", `refs/tags/${tag}^{commit}`]));
|
|
10363
10454
|
} catch {
|
|
10364
10455
|
}
|
|
10365
10456
|
if (remoteSha) {
|
|
@@ -10385,7 +10476,7 @@ function probeRcResumeTags(deps, base) {
|
|
|
10385
10476
|
}
|
|
10386
10477
|
async function resolveRcResumeTag(deps, base, sha, probed) {
|
|
10387
10478
|
const out = probed ?? await runGitRemoteRead(deps, ["ls-remote", "--tags", "origin", `refs/tags/${base}-rc.*`]);
|
|
10388
|
-
const onSha =
|
|
10479
|
+
const onSha = clean2(out).split("\n").map((line) => line.trim()).filter(Boolean).map((line) => line.split(/\s+/)).filter(([refSha]) => refSha === sha).map(([, ref]) => ref.replace(/^refs\/tags\//, "").replace(/\^\{\}$/, "")).filter((ref) => new RegExp(`^${base.replace(/\./g, "\\.")}-rc\\.\\d+$`).test(ref));
|
|
10389
10480
|
const unique = [...new Set(onSha)];
|
|
10390
10481
|
if (unique.length === 0) return { tag: null };
|
|
10391
10482
|
const rcNum = (t) => Number.parseInt(t.replace(/^.*-rc\./, ""), 10);
|
|
@@ -10581,7 +10672,7 @@ async function preflightMergeToMain(deps, deployModel, remoteRef, blockingPrefix
|
|
|
10581
10672
|
async function executeMergeToMain(deps, sourceRef, mergeLabel, tolerated, predicted, preFold) {
|
|
10582
10673
|
await deps.run("git", ["checkout", "main"]);
|
|
10583
10674
|
await ffOnlyPull(deps, "main");
|
|
10584
|
-
preFold.mainSha =
|
|
10675
|
+
preFold.mainSha = clean2(await deps.run("git", ["rev-parse", "main"]));
|
|
10585
10676
|
if (predicted.length === 0) {
|
|
10586
10677
|
await deps.run("git", ["merge", sourceRef, "--no-edit"]);
|
|
10587
10678
|
} else {
|
|
@@ -10592,8 +10683,8 @@ async function probeFoldFailureState(deps) {
|
|
|
10592
10683
|
const branch = await currentBranch(deps);
|
|
10593
10684
|
const mergeInProgress = await deps.run("git", ["rev-parse", "-q", "--verify", "MERGE_HEAD"]).then(() => true).catch(() => false);
|
|
10594
10685
|
const dirty = porcelainHasBlockingChanges(await deps.run("git", ["status", "--porcelain"]).catch(() => ""));
|
|
10595
|
-
const mainSha = await deps.run("git", ["rev-parse", "main"]).then(
|
|
10596
|
-
const originMainSha = await deps.run("git", ["rev-parse", "origin/main"]).then(
|
|
10686
|
+
const mainSha = await deps.run("git", ["rev-parse", "main"]).then(clean2).catch(() => "");
|
|
10687
|
+
const originMainSha = await deps.run("git", ["rev-parse", "origin/main"]).then(clean2).catch(() => "");
|
|
10597
10688
|
const originIsAncestorOfMain = Boolean(mainSha) && Boolean(originMainSha) ? await deps.run("git", ["merge-base", "--is-ancestor", "origin/main", "main"]).then(() => true).catch(() => false) : false;
|
|
10598
10689
|
return { branch, mergeInProgress, dirty, mainSha, originMainSha, originIsAncestorOfMain };
|
|
10599
10690
|
}
|
|
@@ -10654,7 +10745,7 @@ Recovery sequence:
|
|
|
10654
10745
|
);
|
|
10655
10746
|
}
|
|
10656
10747
|
}
|
|
10657
|
-
const rcSha = await deps.run("git", ["rev-parse", "rc"]).then(
|
|
10748
|
+
const rcSha = await deps.run("git", ["rev-parse", "rc"]).then(clean2).catch(() => "");
|
|
10658
10749
|
const rcDescends = Boolean(preRcSha) && Boolean(rcSha) ? await deps.run("git", ["merge-base", "--is-ancestor", preRcSha, "rc"]).then(() => true).catch(() => false) : false;
|
|
10659
10750
|
if (branch === "rc" && rcDescends) {
|
|
10660
10751
|
try {
|
|
@@ -10745,7 +10836,7 @@ async function completeMainRelease(deps, ctx, meta, deployModel, watch, options,
|
|
|
10745
10836
|
throw partialTrainRecoveryError(e, { repo: ctx.repo, tag, stage: "main" });
|
|
10746
10837
|
}
|
|
10747
10838
|
await runGitPush(deps, ["push", "origin", "main"]);
|
|
10748
|
-
const releaseUrl =
|
|
10839
|
+
const releaseUrl = clean2(await deps.run("gh", ["release", "create", tag, "--target", "main", "--generate-notes", "--latest", "--repo", ctx.repo])) || void 0;
|
|
10749
10840
|
await verifyPublishedRelease(deps, ctx.repo, tag, "main", releaseSha);
|
|
10750
10841
|
const announceNote = deps.announce ? (await deps.announce({ repo: ctx.repo, tag, summaryFile: options.announceSummaryFile })).note : void 0;
|
|
10751
10842
|
const autoRunSince = (deps.now ?? Date.now)();
|
|
@@ -10783,21 +10874,21 @@ async function runTrainApplyPipeline(mode, input) {
|
|
|
10783
10874
|
"nothing to promote: origin/development is not ahead of origin/rc"
|
|
10784
10875
|
);
|
|
10785
10876
|
const deployModel2 = await preflight(deps, ctx, "rc", meta);
|
|
10786
|
-
const releaseBase = requireValue(
|
|
10877
|
+
const releaseBase = requireValue(clean2(await deps.run("node", ["scripts/next-version.mjs", "cycle"])), "release base");
|
|
10787
10878
|
await releaseRcBranchLock(deps);
|
|
10788
10879
|
const rcTagProbe = await probeRcResumeTags(deps, releaseBase);
|
|
10789
10880
|
await deps.run("git", ["checkout", "rc"]);
|
|
10790
10881
|
await ffOnlyPull(deps, "rc");
|
|
10791
|
-
const preRcSha = requireValue(
|
|
10882
|
+
const preRcSha = requireValue(clean2(await deps.run("git", ["rev-parse", "rc"])), "pre-merge rc sha");
|
|
10792
10883
|
let rcSha;
|
|
10793
10884
|
let resume;
|
|
10794
10885
|
let tag2;
|
|
10795
10886
|
let tagPush;
|
|
10796
10887
|
try {
|
|
10797
10888
|
await deps.run("git", ["merge", "development", "--no-edit"]);
|
|
10798
|
-
rcSha = requireValue(
|
|
10889
|
+
rcSha = requireValue(clean2(await deps.run("git", ["rev-parse", "rc"])), "rc sha");
|
|
10799
10890
|
resume = await resolveRcResumeTag(deps, releaseBase, rcSha, rcTagProbe);
|
|
10800
|
-
tag2 = resume.tag ?? requireValue(
|
|
10891
|
+
tag2 = resume.tag ?? requireValue(clean2(await deps.run("node", ["scripts/next-version.mjs", "rc"])), "rc tag");
|
|
10801
10892
|
tagPush = await ensureTagPushed(deps, tag2, rcSha);
|
|
10802
10893
|
} catch (e) {
|
|
10803
10894
|
throw await recoverFailedRcand(deps, e, preRcSha);
|
|
@@ -10835,8 +10926,8 @@ async function runTrainApplyPipeline(mode, input) {
|
|
|
10835
10926
|
"nothing to release: origin/development is not ahead of origin/main"
|
|
10836
10927
|
);
|
|
10837
10928
|
const deployModel2 = await preflight(deps, ctx, "main", meta);
|
|
10838
|
-
const tag2 = requireValue(
|
|
10839
|
-
const rcShaAtRelease = !directTrack && hasRcBranch ?
|
|
10929
|
+
const tag2 = requireValue(clean2(await deps.run("node", ["scripts/next-version.mjs", "cycle"])), "release tag");
|
|
10930
|
+
const rcShaAtRelease = !directTrack && hasRcBranch ? clean2(await deps.run("git", ["rev-parse", "origin/rc"])) : "";
|
|
10840
10931
|
const { foldPaths: foldPaths2, tolerated: tolerated2, predicted: predicted2 } = await preflightMergeToMain(
|
|
10841
10932
|
deps,
|
|
10842
10933
|
deployModel2,
|
|
@@ -10849,7 +10940,7 @@ async function runTrainApplyPipeline(mode, input) {
|
|
|
10849
10940
|
const { versionFold: versionFold2, releaseSha: releaseSha2 } = await runFoldStage(deps, "development", preFold2, async () => {
|
|
10850
10941
|
await executeMergeToMain(deps, "development", "development -> main", tolerated2, predicted2, preFold2);
|
|
10851
10942
|
const versionFold3 = await foldReleaseVersion(deps, deployModel2, tag2, foldPaths2);
|
|
10852
|
-
const releaseSha3 = requireValue(
|
|
10943
|
+
const releaseSha3 = requireValue(clean2(await deps.run("git", ["rev-parse", "main"])), "release sha");
|
|
10853
10944
|
return { versionFold: versionFold3, releaseSha: releaseSha3 };
|
|
10854
10945
|
});
|
|
10855
10946
|
const { checks: checks2, releaseUrl: releaseUrl2, announceNote: announceNote2, dispatch: d2 } = await completeMainRelease(deps, ctx, meta, deployModel2, watch, options, tag2, releaseSha2, "development", preFold2, tagProbe2);
|
|
@@ -10927,14 +11018,14 @@ async function runTrainApplyPipeline(mode, input) {
|
|
|
10927
11018
|
`hotfix-coverage: ${coverage.uncovered.length} main-only commit(s) not proven in origin/rc \u2014 the candidate would revert a prod hotfix: ${list}. Re-cut /rcand from development, or have the authorized human verify the content is in the candidate and rerun release with --ack <sha>[,<sha>\u2026].`
|
|
10928
11019
|
);
|
|
10929
11020
|
}
|
|
10930
|
-
const releasedRcSha =
|
|
10931
|
-
const tag = requireValue(
|
|
11021
|
+
const releasedRcSha = clean2(await deps.run("git", ["rev-parse", "origin/rc"]));
|
|
11022
|
+
const tag = requireValue(clean2(await deps.run("node", ["scripts/next-version.mjs", "release"])), "release tag");
|
|
10932
11023
|
const tagProbe = { remoteSha: await probeRemoteTag(deps, tag) };
|
|
10933
11024
|
const preFold = {};
|
|
10934
11025
|
const { versionFold, releaseSha } = await runFoldStage(deps, "rc", preFold, async () => {
|
|
10935
11026
|
await executeMergeToMain(deps, "rc", "rc -> main", tolerated, predicted, preFold);
|
|
10936
11027
|
const versionFold2 = await foldReleaseVersion(deps, deployModel, tag, foldPaths);
|
|
10937
|
-
const releaseSha2 = requireValue(
|
|
11028
|
+
const releaseSha2 = requireValue(clean2(await deps.run("git", ["rev-parse", "main"])), "release sha");
|
|
10938
11029
|
return { versionFold: versionFold2, releaseSha: releaseSha2 };
|
|
10939
11030
|
});
|
|
10940
11031
|
const { checks, releaseUrl, announceNote, dispatch: d } = await completeMainRelease(deps, ctx, meta, deployModel, watch, options, tag, releaseSha, "rc", preFold, tagProbe);
|
|
@@ -11053,7 +11144,7 @@ async function retireRcRuntime(deps, ctx, model, deployStatus, releasedRcSha) {
|
|
|
11053
11144
|
}
|
|
11054
11145
|
try {
|
|
11055
11146
|
await deps.run("git", ["fetch", "origin", "rc"]);
|
|
11056
|
-
const rcNow =
|
|
11147
|
+
const rcNow = clean2(await deps.run("git", ["rev-parse", "origin/rc"]));
|
|
11057
11148
|
if (rcNow !== releasedRcSha) {
|
|
11058
11149
|
return {
|
|
11059
11150
|
status: "skipped",
|
|
@@ -11084,7 +11175,7 @@ async function runTenantRedeploy(deps, options) {
|
|
|
11084
11175
|
const repo = options.repo;
|
|
11085
11176
|
const [owner, name] = repo.split("/");
|
|
11086
11177
|
if (!owner || !name) throw new Error(`repo must be owner/name, got ${repo}`);
|
|
11087
|
-
const login = requireValue(
|
|
11178
|
+
const login = requireValue(clean2(await deps.run("gh", ["api", "user", "--jq", ".login"])), "GitHub login");
|
|
11088
11179
|
const verdict = await deps.trainAuthority(repo);
|
|
11089
11180
|
if (!verdict.ok) throw new Error(`${commandAuthorityLabel(owner)}: train authority could not be verified (${verdict.error})`);
|
|
11090
11181
|
if (!verdict.train) throw new Error(`${commandAuthorityLabel(owner)}: @${login} is ${verdict.role} \u2014 no train authority on ${repo}`);
|
|
@@ -11324,7 +11415,7 @@ var HOTFIX_RUN_FIND_ATTEMPTS = 10;
|
|
|
11324
11415
|
var HOTFIX_RUN_FIND_DELAY_MS = 15e3;
|
|
11325
11416
|
var HOTFIX_VERIFY_ATTEMPTS = 5;
|
|
11326
11417
|
var HOTFIX_VERIFY_RETRY_MS = 6e3;
|
|
11327
|
-
function
|
|
11418
|
+
function clean3(out) {
|
|
11328
11419
|
return out.trim();
|
|
11329
11420
|
}
|
|
11330
11421
|
function sleeper(deps) {
|
|
@@ -11382,7 +11473,7 @@ async function resolveHotfixSource(deps, ctx, from) {
|
|
|
11382
11473
|
if (!sha2) throw new Error(`PR #${num} has no merge commit recorded \u2014 name the commit SHA explicitly`);
|
|
11383
11474
|
return { sha: sha2, label: `PR #${num} (${sha2.slice(0, 7)})` };
|
|
11384
11475
|
}
|
|
11385
|
-
const sha =
|
|
11476
|
+
const sha = clean3(await deps.run("git", ["rev-parse", "--verify", `${from}^{commit}`]));
|
|
11386
11477
|
if (!sha) throw new Error(`could not resolve commit ${from}`);
|
|
11387
11478
|
return { sha, label: sha.slice(0, 7) };
|
|
11388
11479
|
}
|
|
@@ -11410,7 +11501,7 @@ async function runHotfixStart(deps, options) {
|
|
|
11410
11501
|
};
|
|
11411
11502
|
}
|
|
11412
11503
|
const { sha, label } = await resolveHotfixSource(deps, ctx, options.from);
|
|
11413
|
-
const remoteBranch =
|
|
11504
|
+
const remoteBranch = clean3(await deps.run("git", ["ls-remote", "origin", `refs/heads/${branch}`]));
|
|
11414
11505
|
if (remoteBranch) {
|
|
11415
11506
|
await deps.run("git", ["checkout", branch]);
|
|
11416
11507
|
await deps.run("git", ["pull", "--ff-only", "origin", branch]);
|
|
@@ -11441,7 +11532,7 @@ async function runHotfixStart(deps, options) {
|
|
|
11441
11532
|
await deps.run("git", ["push", "-u", "origin", branch]);
|
|
11442
11533
|
}
|
|
11443
11534
|
const bumpNote = deployModel === "hub-serverless" ? " with the locked distribution bump" : "";
|
|
11444
|
-
const prUrl =
|
|
11535
|
+
const prUrl = clean3(await deps.run("gh", [
|
|
11445
11536
|
"pr",
|
|
11446
11537
|
"create",
|
|
11447
11538
|
"--repo",
|
|
@@ -11585,7 +11676,7 @@ async function runHotfixRelease(deps, versionInput, options = {}) {
|
|
|
11585
11676
|
}
|
|
11586
11677
|
let verifyNote;
|
|
11587
11678
|
if (deployModel === "hub-serverless") {
|
|
11588
|
-
const previousRef =
|
|
11679
|
+
const previousRef = clean3(await deps.run("git", ["rev-parse", "--abbrev-ref", "HEAD"]));
|
|
11589
11680
|
const publishSucceeded = runs.find((r) => r.workflow === "publish.yml")?.conclusion === "success";
|
|
11590
11681
|
try {
|
|
11591
11682
|
await deps.run("git", ["-c", "advice.detachedHead=false", "checkout", tag]);
|
|
@@ -11672,9 +11763,9 @@ async function runHotfixStatus(deps, versionInput) {
|
|
|
11672
11763
|
return { ...ctx, command: "hotfix-status", ...facts, ...deriveHotfixState(facts), warnings };
|
|
11673
11764
|
}
|
|
11674
11765
|
async function gatherHotfixFacts(deps, ctx, tag, version) {
|
|
11675
|
-
const branchExists = Boolean(
|
|
11766
|
+
const branchExists = Boolean(clean3(await deps.run("git", ["ls-remote", "origin", `refs/heads/${hotfixBranch(tag)}`])));
|
|
11676
11767
|
const pr2 = await findHotfixPr(deps, ctx, tag);
|
|
11677
|
-
const remoteTag =
|
|
11768
|
+
const remoteTag = clean3(await deps.run("git", ["ls-remote", "origin", `refs/tags/${tag}`]));
|
|
11678
11769
|
const tagPushed = Boolean(remoteTag);
|
|
11679
11770
|
const tagSha = remoteTag.split(/\s+/)[0] || "";
|
|
11680
11771
|
let releaseExists = false;
|
|
@@ -11708,7 +11799,7 @@ async function gatherHotfixFacts(deps, ctx, tag, version) {
|
|
|
11708
11799
|
}
|
|
11709
11800
|
}
|
|
11710
11801
|
}
|
|
11711
|
-
const npmVersion = await deps.run("npm", ["view", "@mutmutco/cli", "version", "--silent"]).then(
|
|
11802
|
+
const npmVersion = await deps.run("npm", ["view", "@mutmutco/cli", "version", "--silent"]).then(clean3, () => "unknown");
|
|
11712
11803
|
return { tag, version, branchExists, pr: pr2 ? { number: pr2.number, state: pr2.state, url: pr2.url } : null, tagPushed, releaseExists, runs, npmVersion };
|
|
11713
11804
|
}
|
|
11714
11805
|
function compareHotfixVersions(a, b) {
|
|
@@ -11743,7 +11834,7 @@ async function findInFlightHotfixVersion(deps, ctx, latestMainTag) {
|
|
|
11743
11834
|
const m = typeof row.headRefName === "string" && /^hotfix\/(v\d+\.\d+\.\d+)/.exec(row.headRefName);
|
|
11744
11835
|
if (m) tags.add(m[1]);
|
|
11745
11836
|
}
|
|
11746
|
-
const branchOut =
|
|
11837
|
+
const branchOut = clean3(await deps.run("git", ["ls-remote", "origin", "refs/heads/hotfix/v*"]));
|
|
11747
11838
|
for (const line of branchOut.split("\n").filter(Boolean)) {
|
|
11748
11839
|
const ref = line.split(/\s+/)[1] ?? "";
|
|
11749
11840
|
const m = /^refs\/heads\/hotfix\/(v\d+\.\d+\.\d+)/.exec(ref);
|
|
@@ -12235,7 +12326,7 @@ function renderAccessReport(report) {
|
|
|
12235
12326
|
}
|
|
12236
12327
|
|
|
12237
12328
|
// src/bootstrap-verify.ts
|
|
12238
|
-
var
|
|
12329
|
+
var TRAIN_BRANCHES2 = ["development", "rc", "main"];
|
|
12239
12330
|
var requiredDocs = ["README.md", "architecture.md"];
|
|
12240
12331
|
var requiredIssueTemplates = [
|
|
12241
12332
|
".github/ISSUE_TEMPLATE/bug.yml",
|
|
@@ -12413,7 +12504,7 @@ async function verifyBootstrap(repo, repoClass, deps, releaseTrack) {
|
|
|
12413
12504
|
detail: hasPushAllowlist(protection) ? void 0 : "restrictions.users is empty or unset"
|
|
12414
12505
|
});
|
|
12415
12506
|
}
|
|
12416
|
-
const strayTrainBranches =
|
|
12507
|
+
const strayTrainBranches = TRAIN_BRANCHES2.filter((b) => !branchesWanted.includes(b) && branchNames.has(b));
|
|
12417
12508
|
checks.push({
|
|
12418
12509
|
ok: strayTrainBranches.length === 0,
|
|
12419
12510
|
label: "no stray train branch for this track",
|
|
@@ -12888,13 +12979,13 @@ var WIKI_STAMP_FILE = ".wiki-state.json";
|
|
|
12888
12979
|
function redactWikiSecrets(msg) {
|
|
12889
12980
|
return msg.replace(/x-access-token:[^@\s]+@/gi, "x-access-token:***@").replace(/(AUTHORIZATION:\s*basic\s+)[A-Za-z0-9+/=]+/gi, "$1***");
|
|
12890
12981
|
}
|
|
12982
|
+
function wikiAuthArgs(token) {
|
|
12983
|
+
return ["-c", `http.extraheader=AUTHORIZATION: basic ${Buffer.from(`x-access-token:${token}`).toString("base64")}`];
|
|
12984
|
+
}
|
|
12891
12985
|
function publishWikiViaGit(deps, opts) {
|
|
12892
12986
|
const dir = deps.mkdtemp();
|
|
12893
12987
|
const url = `https://github.com/${opts.repo}.wiki.git`;
|
|
12894
|
-
const authArgs =
|
|
12895
|
-
"-c",
|
|
12896
|
-
`http.extraheader=AUTHORIZATION: basic ${Buffer.from(`x-access-token:${opts.token}`).toString("base64")}`
|
|
12897
|
-
];
|
|
12988
|
+
const authArgs = wikiAuthArgs(opts.token);
|
|
12898
12989
|
try {
|
|
12899
12990
|
deps.gitRun(["init", "-q"], dir);
|
|
12900
12991
|
deps.gitRun(["remote", "add", "origin", url], dir);
|
|
@@ -12949,12 +13040,37 @@ function publishWikiViaGit(deps, opts) {
|
|
|
12949
13040
|
deps.rm(dir);
|
|
12950
13041
|
}
|
|
12951
13042
|
}
|
|
13043
|
+
function readWikiStampViaGit(deps, opts) {
|
|
13044
|
+
const dir = deps.mkdtemp();
|
|
13045
|
+
const url = `https://github.com/${opts.repo}.wiki.git`;
|
|
13046
|
+
const authArgs = wikiAuthArgs(opts.token);
|
|
13047
|
+
try {
|
|
13048
|
+
deps.gitRun(["init", "-q"], dir);
|
|
13049
|
+
deps.gitRun(["remote", "add", "origin", url], dir);
|
|
13050
|
+
if (!deps.gitProbe([...authArgs, "ls-remote", "origin"], dir)) {
|
|
13051
|
+
return { ok: false, error: `could not reach ${opts.repo}.wiki.git to read the wiki stamp \u2014 auth or network failure` };
|
|
13052
|
+
}
|
|
13053
|
+
if (!deps.gitProbe([...authArgs, "fetch", "--depth", "1", "origin", "master"], dir)) {
|
|
13054
|
+
return { ok: true, stamp: {} };
|
|
13055
|
+
}
|
|
13056
|
+
deps.gitRun(["reset", "--hard", "FETCH_HEAD"], dir);
|
|
13057
|
+
const stampPath = (0, import_node_path14.join)(dir, WIKI_STAMP_FILE);
|
|
13058
|
+
if (!deps.fileExists(stampPath)) return { ok: true, stamp: {} };
|
|
13059
|
+
const parsed = JSON.parse(deps.readFile(stampPath));
|
|
13060
|
+
return { ok: true, stamp: parsed && typeof parsed === "object" ? parsed : {} };
|
|
13061
|
+
} catch (e) {
|
|
13062
|
+
return { ok: false, error: redactWikiSecrets(e.message) };
|
|
13063
|
+
} finally {
|
|
13064
|
+
deps.rm(dir);
|
|
13065
|
+
}
|
|
13066
|
+
}
|
|
12952
13067
|
function realWikiGitPublishDeps() {
|
|
12953
13068
|
return {
|
|
12954
13069
|
mkdtemp: () => (0, import_node_fs13.mkdtempSync)((0, import_node_path14.join)((0, import_node_os5.tmpdir)(), "wiki-")),
|
|
12955
13070
|
readdir: (dir) => (0, import_node_fs13.readdirSync)(dir),
|
|
12956
13071
|
copyFile: (src, dest) => (0, import_node_fs13.copyFileSync)(src, dest),
|
|
12957
13072
|
fileExists: (p) => (0, import_node_fs13.existsSync)(p),
|
|
13073
|
+
readFile: (p) => (0, import_node_fs13.readFileSync)(p, "utf8"),
|
|
12958
13074
|
rm: (p) => (0, import_node_fs13.rmSync)(p, { recursive: true, force: true }),
|
|
12959
13075
|
gitProbe: (args, cwd) => {
|
|
12960
13076
|
try {
|
|
@@ -12972,6 +13088,124 @@ function realWikiGitPublishDeps() {
|
|
|
12972
13088
|
};
|
|
12973
13089
|
}
|
|
12974
13090
|
|
|
13091
|
+
// src/wiki-pages.ts
|
|
13092
|
+
var import_node_crypto4 = require("node:crypto");
|
|
13093
|
+
var import_node_fs14 = require("node:fs");
|
|
13094
|
+
var import_node_path15 = require("node:path");
|
|
13095
|
+
var MARKER_RE = /<!--\s*wiki:page\s+([^>]*?)-->/g;
|
|
13096
|
+
var attr = (raw, key) => {
|
|
13097
|
+
const m = new RegExp(`${key}\\s*=\\s*"([^"]*)"`).exec(raw);
|
|
13098
|
+
return m ? m[1].trim() : "";
|
|
13099
|
+
};
|
|
13100
|
+
var sanitizeSlug = (s) => String(s ?? "").trim().replace(/[^\w.-]+/g, "-").replace(/^-+|-+$/g, "");
|
|
13101
|
+
function extractWikiMarkers(text, sourcePath = "") {
|
|
13102
|
+
const out = [];
|
|
13103
|
+
for (const m of String(text ?? "").matchAll(MARKER_RE)) {
|
|
13104
|
+
const raw = m[1];
|
|
13105
|
+
const slug = sanitizeSlug(attr(raw, "slug"));
|
|
13106
|
+
const title = attr(raw, "title");
|
|
13107
|
+
if (!slug || !title) continue;
|
|
13108
|
+
out.push({ slug, title, artifact: attr(raw, "artifact"), sourcePath });
|
|
13109
|
+
}
|
|
13110
|
+
return out;
|
|
13111
|
+
}
|
|
13112
|
+
var DURABLE_GLOBS = [
|
|
13113
|
+
"architecture.md",
|
|
13114
|
+
"docs/Architecture",
|
|
13115
|
+
"kb/architecture",
|
|
13116
|
+
"docs/org-architecture.md",
|
|
13117
|
+
"docs/org-readme.md"
|
|
13118
|
+
];
|
|
13119
|
+
var walkMarkdown = (root, rel) => {
|
|
13120
|
+
const abs = (0, import_node_path15.join)(root, rel);
|
|
13121
|
+
let st;
|
|
13122
|
+
try {
|
|
13123
|
+
st = (0, import_node_fs14.statSync)(abs);
|
|
13124
|
+
} catch {
|
|
13125
|
+
return [];
|
|
13126
|
+
}
|
|
13127
|
+
if (st.isFile()) return rel.endsWith(".md") ? [rel] : [];
|
|
13128
|
+
return (0, import_node_fs14.readdirSync)(abs).flatMap((name) => walkMarkdown(root, (0, import_node_path15.join)(rel, name)));
|
|
13129
|
+
};
|
|
13130
|
+
function collectFeaturePages({ listDocs, readDoc }) {
|
|
13131
|
+
const seen = /* @__PURE__ */ new Set();
|
|
13132
|
+
const pages = [];
|
|
13133
|
+
for (const path2 of listDocs()) {
|
|
13134
|
+
for (const mk of extractWikiMarkers(readDoc(path2), path2)) {
|
|
13135
|
+
if (seen.has(mk.slug)) continue;
|
|
13136
|
+
seen.add(mk.slug);
|
|
13137
|
+
pages.push(mk);
|
|
13138
|
+
}
|
|
13139
|
+
}
|
|
13140
|
+
return pages;
|
|
13141
|
+
}
|
|
13142
|
+
function fsReaders(rootDir) {
|
|
13143
|
+
return {
|
|
13144
|
+
listDocs: () => DURABLE_GLOBS.flatMap((g) => walkMarkdown(rootDir, g)),
|
|
13145
|
+
readDoc: (p) => {
|
|
13146
|
+
try {
|
|
13147
|
+
return (0, import_node_fs14.readFileSync)((0, import_node_path15.join)(rootDir, p), "utf8");
|
|
13148
|
+
} catch {
|
|
13149
|
+
return "";
|
|
13150
|
+
}
|
|
13151
|
+
}
|
|
13152
|
+
};
|
|
13153
|
+
}
|
|
13154
|
+
function hashPageInputs({ slug, title, source }) {
|
|
13155
|
+
return (0, import_node_crypto4.createHash)("sha256").update(JSON.stringify([slug ?? "", title ?? "", source ?? ""])).digest("hex");
|
|
13156
|
+
}
|
|
13157
|
+
function computePageStamp(pages, readSource) {
|
|
13158
|
+
const stamp = {};
|
|
13159
|
+
for (const p of pages ?? []) {
|
|
13160
|
+
stamp[p.slug] = {
|
|
13161
|
+
hash: hashPageInputs({ slug: p.slug, title: p.title, source: readSource(p.sourcePath) }),
|
|
13162
|
+
sourcePath: p.sourcePath
|
|
13163
|
+
};
|
|
13164
|
+
}
|
|
13165
|
+
return stamp;
|
|
13166
|
+
}
|
|
13167
|
+
function selectChangedPages(pages, prevStamp, readSource) {
|
|
13168
|
+
const prev = prevStamp && typeof prevStamp === "object" ? prevStamp : {};
|
|
13169
|
+
const backfill = Object.keys(prev).length === 0;
|
|
13170
|
+
return (pages ?? []).filter((p) => {
|
|
13171
|
+
if (backfill) return true;
|
|
13172
|
+
const prior = prev[p.slug];
|
|
13173
|
+
if (!prior) return true;
|
|
13174
|
+
const cur = hashPageInputs({ slug: p.slug, title: p.title, source: readSource(p.sourcePath) });
|
|
13175
|
+
return prior.hash !== cur;
|
|
13176
|
+
});
|
|
13177
|
+
}
|
|
13178
|
+
|
|
13179
|
+
// src/wiki-plan-command.ts
|
|
13180
|
+
async function wikiPlan(deps, opts) {
|
|
13181
|
+
const wikiRepo = `${opts.repo}.wiki.git`;
|
|
13182
|
+
const pages = deps.collectPages();
|
|
13183
|
+
if (pages.length === 0) {
|
|
13184
|
+
emit(deps, { repo: opts.repo, wikiRepo, backfill: false, pages: [], stamp: {} });
|
|
13185
|
+
return true;
|
|
13186
|
+
}
|
|
13187
|
+
const read = await deps.mintAndReadStamp(opts.repo);
|
|
13188
|
+
if (!read.ok) {
|
|
13189
|
+
deps.err(`wiki plan failed: could not read the wiki stamp for ${opts.repo}: ${read.error}`);
|
|
13190
|
+
return false;
|
|
13191
|
+
}
|
|
13192
|
+
const prevStamp = read.stamp;
|
|
13193
|
+
const backfill = Object.keys(prevStamp).length === 0;
|
|
13194
|
+
const changed = new Set(selectChangedPages(pages, prevStamp, deps.readSource).map((p) => p.slug));
|
|
13195
|
+
const stamp = computePageStamp(pages, deps.readSource);
|
|
13196
|
+
emit(deps, {
|
|
13197
|
+
repo: opts.repo,
|
|
13198
|
+
wikiRepo,
|
|
13199
|
+
backfill,
|
|
13200
|
+
pages: pages.map((p) => ({ ...p, changed: changed.has(p.slug) })),
|
|
13201
|
+
stamp
|
|
13202
|
+
});
|
|
13203
|
+
return true;
|
|
13204
|
+
}
|
|
13205
|
+
function emit(deps, result) {
|
|
13206
|
+
deps.out(JSON.stringify(result, null, 2));
|
|
13207
|
+
}
|
|
13208
|
+
|
|
12975
13209
|
// src/project-readiness.ts
|
|
12976
13210
|
function stagesForTrack(meta) {
|
|
12977
13211
|
return branchesForTrack(resolveReleaseTrack(meta)).map((b) => b === "development" ? "dev" : b);
|
|
@@ -13990,22 +14224,22 @@ var DEPLOY_STAGES = ["dev", "rc", "main"];
|
|
|
13990
14224
|
var DEPLOY_DOMAIN_RE = /^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)+$/;
|
|
13991
14225
|
var DEPLOY_SHELL_SAFE_RE = /^[A-Za-z0-9./:_?&=%-]*$/;
|
|
13992
14226
|
function buildSetDeployPatch(slug, input) {
|
|
13993
|
-
const
|
|
13994
|
-
const stage2 =
|
|
14227
|
+
const clean4 = (v) => typeof v === "string" && v.trim() !== "" ? v.trim() : void 0;
|
|
14228
|
+
const stage2 = clean4(input.stage);
|
|
13995
14229
|
if (!stage2 || !DEPLOY_STAGES.includes(stage2)) {
|
|
13996
14230
|
throw new Error(`project set-deploy: --stage must be one of: ${DEPLOY_STAGES.join(", ")}`);
|
|
13997
14231
|
}
|
|
13998
|
-
const substrate =
|
|
14232
|
+
const substrate = clean4(input.substrate) ?? "hetzner-ssh";
|
|
13999
14233
|
if (!DEPLOY_SUBSTRATES.includes(substrate)) {
|
|
14000
14234
|
throw new Error(`project set-deploy: --substrate must be one of: ${DEPLOY_SUBSTRATES.join(", ")}`);
|
|
14001
14235
|
}
|
|
14002
|
-
const sshHost =
|
|
14236
|
+
const sshHost = clean4(input.sshHost);
|
|
14003
14237
|
if (substrate === "hetzner-ssh" && !sshHost) {
|
|
14004
14238
|
throw new Error("project set-deploy: hetzner-ssh requires --ssh-host");
|
|
14005
14239
|
}
|
|
14006
|
-
const sshUser =
|
|
14007
|
-
const deployPath =
|
|
14008
|
-
const serviceName =
|
|
14240
|
+
const sshUser = clean4(input.sshUser) ?? "root";
|
|
14241
|
+
const deployPath = clean4(input.deployPath) ?? `/opt/mmi/${slug}/${stage2}`;
|
|
14242
|
+
const serviceName = clean4(input.service) ?? slug;
|
|
14009
14243
|
for (const [label, v] of [["--ssh-host", sshHost], ["--ssh-user", sshUser], ["--deploy-path", deployPath], ["--service", serviceName]]) {
|
|
14010
14244
|
if (v !== void 0 && !DEPLOY_SHELL_SAFE_RE.test(v)) throw new Error(`project set-deploy: ${label} contains unsafe characters`);
|
|
14011
14245
|
}
|
|
@@ -14014,9 +14248,9 @@ function buildSetDeployPatch(slug, input) {
|
|
|
14014
14248
|
port = Number(input.port);
|
|
14015
14249
|
if (!Number.isInteger(port) || port < 1 || port > 65535) throw new Error("project set-deploy: --port must be an integer 1..65535");
|
|
14016
14250
|
}
|
|
14017
|
-
const domain =
|
|
14251
|
+
const domain = clean4(input.domain);
|
|
14018
14252
|
if (domain !== void 0 && !DEPLOY_DOMAIN_RE.test(domain)) throw new Error(`project set-deploy: --domain must be a DNS hostname, got ${JSON.stringify(input.domain)}`);
|
|
14019
|
-
const aliases = (Array.isArray(input.aliases) ? input.aliases : []).map((a) =>
|
|
14253
|
+
const aliases = (Array.isArray(input.aliases) ? input.aliases : []).map((a) => clean4(a)).filter((a) => a !== void 0);
|
|
14020
14254
|
for (const a of aliases) {
|
|
14021
14255
|
if (!DEPLOY_DOMAIN_RE.test(a)) throw new Error(`project set-deploy: --alias must be a DNS hostname, got ${JSON.stringify(a)}`);
|
|
14022
14256
|
}
|
|
@@ -14923,8 +15157,8 @@ async function secretsUse(deps, key, opts) {
|
|
|
14923
15157
|
}
|
|
14924
15158
|
|
|
14925
15159
|
// src/secrets-commands.ts
|
|
14926
|
-
var
|
|
14927
|
-
var
|
|
15160
|
+
var import_node_fs15 = require("node:fs");
|
|
15161
|
+
var import_node_path16 = require("node:path");
|
|
14928
15162
|
var RAILS_CREDENTIALS_DECRYPT_TIMEOUT_MS = 3e4;
|
|
14929
15163
|
var DEFAULT_RAILS_CREDENTIALS_FILE = "config/credentials.yml.enc";
|
|
14930
15164
|
var DEFAULT_RAILS_MASTER_KEY_FILE = "config/master.key";
|
|
@@ -14932,18 +15166,18 @@ function collectMap(value, previous = []) {
|
|
|
14932
15166
|
return [...previous, value];
|
|
14933
15167
|
}
|
|
14934
15168
|
async function decryptRailsCredentials(input) {
|
|
14935
|
-
const appDir = (0,
|
|
15169
|
+
const appDir = (0, import_node_path16.resolve)(input.appDir ?? process.cwd());
|
|
14936
15170
|
const credentialsFile = input.credentialsFile ?? DEFAULT_RAILS_CREDENTIALS_FILE;
|
|
14937
15171
|
const masterKeyFile = input.masterKeyFile ?? DEFAULT_RAILS_MASTER_KEY_FILE;
|
|
14938
|
-
const credentialsPath = (0,
|
|
14939
|
-
const masterKeyPath = (0,
|
|
15172
|
+
const credentialsPath = (0, import_node_path16.resolve)(appDir, credentialsFile);
|
|
15173
|
+
const masterKeyPath = (0, import_node_path16.resolve)(appDir, masterKeyFile);
|
|
14940
15174
|
const env = {
|
|
14941
15175
|
...process.env,
|
|
14942
15176
|
MMI_RAILS_CREDENTIALS_FILE: credentialsPath,
|
|
14943
15177
|
MMI_RAILS_MASTER_KEY_FILE: masterKeyPath
|
|
14944
15178
|
};
|
|
14945
|
-
if ((0,
|
|
14946
|
-
env.RAILS_MASTER_KEY = (0,
|
|
15179
|
+
if ((0, import_node_fs15.existsSync)(masterKeyPath)) {
|
|
15180
|
+
env.RAILS_MASTER_KEY = (0, import_node_fs15.readFileSync)(masterKeyPath, "utf8").trim();
|
|
14947
15181
|
}
|
|
14948
15182
|
const script = [
|
|
14949
15183
|
'require "json"',
|
|
@@ -15009,7 +15243,7 @@ function registerSecretsCommands(program3) {
|
|
|
15009
15243
|
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) => {
|
|
15010
15244
|
let body;
|
|
15011
15245
|
try {
|
|
15012
|
-
body = (0,
|
|
15246
|
+
body = (0, import_node_fs15.readFileSync)((0, import_node_path16.resolve)(o.file), "utf8");
|
|
15013
15247
|
} catch (e) {
|
|
15014
15248
|
return fail(`secrets org-catalog: cannot read --file ${o.file}: ${e.message}`);
|
|
15015
15249
|
}
|
|
@@ -15084,7 +15318,7 @@ function registerSecretsCommands(program3) {
|
|
|
15084
15318
|
{
|
|
15085
15319
|
...d,
|
|
15086
15320
|
decryptRailsCredentials,
|
|
15087
|
-
removeFile: (path2) => (0,
|
|
15321
|
+
removeFile: (path2) => (0, import_node_fs15.unlinkSync)((0, import_node_path16.resolve)(o.appDir ?? process.cwd(), path2))
|
|
15088
15322
|
},
|
|
15089
15323
|
{
|
|
15090
15324
|
repo: o.repo,
|
|
@@ -15173,10 +15407,10 @@ function registerEdgeCommands(program3) {
|
|
|
15173
15407
|
}
|
|
15174
15408
|
|
|
15175
15409
|
// src/doctor-run.ts
|
|
15176
|
-
var
|
|
15410
|
+
var import_node_fs19 = require("node:fs");
|
|
15177
15411
|
var import_node_child_process11 = require("node:child_process");
|
|
15178
15412
|
var import_promises2 = require("node:fs/promises");
|
|
15179
|
-
var
|
|
15413
|
+
var import_node_path19 = require("node:path");
|
|
15180
15414
|
var import_node_os7 = require("node:os");
|
|
15181
15415
|
|
|
15182
15416
|
// src/plugin-guard.ts
|
|
@@ -15199,9 +15433,9 @@ function buildGuardSessionStartLine(state, opts = {}) {
|
|
|
15199
15433
|
|
|
15200
15434
|
// src/cursor-plugin-seed.ts
|
|
15201
15435
|
var import_node_child_process10 = require("node:child_process");
|
|
15202
|
-
var
|
|
15436
|
+
var import_node_fs16 = require("node:fs");
|
|
15203
15437
|
var import_node_os6 = require("node:os");
|
|
15204
|
-
var
|
|
15438
|
+
var import_node_path17 = require("node:path");
|
|
15205
15439
|
var import_node_util7 = require("node:util");
|
|
15206
15440
|
|
|
15207
15441
|
// src/doctor.ts
|
|
@@ -16521,17 +16755,17 @@ function ghReleaseTarballApiArgs(tag) {
|
|
|
16521
16755
|
}
|
|
16522
16756
|
function cursorUserGlobalStatePath() {
|
|
16523
16757
|
if (process.platform === "win32") {
|
|
16524
|
-
const base = process.env.APPDATA || (0,
|
|
16525
|
-
return (0,
|
|
16758
|
+
const base = process.env.APPDATA || (0, import_node_path17.join)((0, import_node_os6.homedir)(), "AppData", "Roaming");
|
|
16759
|
+
return (0, import_node_path17.join)(base, "Cursor", "User", "globalStorage", "state.vscdb");
|
|
16526
16760
|
}
|
|
16527
16761
|
if (process.platform === "darwin") {
|
|
16528
|
-
return (0,
|
|
16762
|
+
return (0, import_node_path17.join)((0, import_node_os6.homedir)(), "Library", "Application Support", "Cursor", "User", "globalStorage", "state.vscdb");
|
|
16529
16763
|
}
|
|
16530
|
-
return (0,
|
|
16764
|
+
return (0, import_node_path17.join)((0, import_node_os6.homedir)(), ".config", "Cursor", "User", "globalStorage", "state.vscdb");
|
|
16531
16765
|
}
|
|
16532
16766
|
async function readCursorThirdPartyExtensibilityEnabled(execFileP5) {
|
|
16533
16767
|
const dbPath = cursorUserGlobalStatePath();
|
|
16534
|
-
if (!(0,
|
|
16768
|
+
if (!(0, import_node_fs16.existsSync)(dbPath)) return void 0;
|
|
16535
16769
|
try {
|
|
16536
16770
|
const { stdout } = await execFileP5("sqlite3", [dbPath, `SELECT value FROM ItemTable WHERE key = '${CURSOR_THIRD_PARTY_STATE_KEY}';`], {
|
|
16537
16771
|
timeout: 5e3
|
|
@@ -16545,57 +16779,57 @@ async function readCursorThirdPartyExtensibilityEnabled(execFileP5) {
|
|
|
16545
16779
|
}
|
|
16546
16780
|
}
|
|
16547
16781
|
function syncDirContents(src, dest) {
|
|
16548
|
-
(0,
|
|
16549
|
-
for (const name of (0,
|
|
16550
|
-
(0,
|
|
16782
|
+
(0, import_node_fs16.mkdirSync)(dest, { recursive: true });
|
|
16783
|
+
for (const name of (0, import_node_fs16.readdirSync)(dest)) {
|
|
16784
|
+
(0, import_node_fs16.rmSync)((0, import_node_path17.join)(dest, name), { recursive: true, force: true });
|
|
16551
16785
|
}
|
|
16552
|
-
(0,
|
|
16786
|
+
(0, import_node_fs16.cpSync)(src, dest, { recursive: true });
|
|
16553
16787
|
}
|
|
16554
16788
|
function releaseTag(releasedVersion) {
|
|
16555
16789
|
return releasedVersion.startsWith("v") ? releasedVersion : `v${releasedVersion}`;
|
|
16556
16790
|
}
|
|
16557
16791
|
async function extractPluginMmiFromHubCheckout(hubCheckout, tag, tmpRoot, execFileP5) {
|
|
16558
|
-
const tarFile = (0,
|
|
16792
|
+
const tarFile = (0, import_node_path17.join)(tmpRoot, "archive.tar");
|
|
16559
16793
|
try {
|
|
16560
16794
|
await execFileP5("git", gitFetchReleaseTagArgs(hubCheckout, tag), { timeout: 6e4 });
|
|
16561
16795
|
await execFileP5("git", ["-C", hubCheckout, "archive", "--format=tar", `--output=${tarFile}`, tag, "plugins/mmi"], {
|
|
16562
16796
|
timeout: 6e4
|
|
16563
16797
|
});
|
|
16564
16798
|
await execFileP5("tar", ["-xf", tarFile, "-C", tmpRoot], { timeout: 6e4 });
|
|
16565
|
-
const pluginMmi = (0,
|
|
16566
|
-
return (0,
|
|
16799
|
+
const pluginMmi = (0, import_node_path17.join)(tmpRoot, "plugins", "mmi");
|
|
16800
|
+
return (0, import_node_fs16.existsSync)((0, import_node_path17.join)(pluginMmi, PLUGIN_JSON_REL)) ? pluginMmi : void 0;
|
|
16567
16801
|
} catch {
|
|
16568
16802
|
return void 0;
|
|
16569
16803
|
}
|
|
16570
16804
|
}
|
|
16571
16805
|
async function downloadPluginMmiViaGh(tag, tmpRoot) {
|
|
16572
|
-
const tarPath = (0,
|
|
16806
|
+
const tarPath = (0, import_node_path17.join)(tmpRoot, "repo.tgz");
|
|
16573
16807
|
try {
|
|
16574
|
-
(0,
|
|
16808
|
+
(0, import_node_fs16.mkdirSync)(tmpRoot, { recursive: true });
|
|
16575
16809
|
const { stdout } = await execFileBuffer("gh", ghReleaseTarballApiArgs(tag), {
|
|
16576
16810
|
timeout: 12e4,
|
|
16577
16811
|
maxBuffer: 100 * 1024 * 1024,
|
|
16578
16812
|
encoding: "buffer",
|
|
16579
16813
|
windowsHide: true
|
|
16580
16814
|
});
|
|
16581
|
-
(0,
|
|
16815
|
+
(0, import_node_fs16.writeFileSync)(tarPath, stdout);
|
|
16582
16816
|
await execFileBuffer("tar", ["-xzf", tarPath, "-C", tmpRoot], { timeout: 12e4, windowsHide: true });
|
|
16583
|
-
const top = (0,
|
|
16817
|
+
const top = (0, import_node_fs16.readdirSync)(tmpRoot).find((entry) => entry !== "repo.tgz");
|
|
16584
16818
|
if (!top) return void 0;
|
|
16585
|
-
const pluginMmi = (0,
|
|
16586
|
-
return (0,
|
|
16819
|
+
const pluginMmi = (0, import_node_path17.join)(tmpRoot, top, "plugins", "mmi");
|
|
16820
|
+
return (0, import_node_fs16.existsSync)((0, import_node_path17.join)(pluginMmi, PLUGIN_JSON_REL)) ? pluginMmi : void 0;
|
|
16587
16821
|
} catch {
|
|
16588
16822
|
return void 0;
|
|
16589
16823
|
}
|
|
16590
16824
|
}
|
|
16591
16825
|
async function resolvePluginMmiSource(releasedVersion, hubCheckout, tmpRoot, execFileP5) {
|
|
16592
|
-
(0,
|
|
16826
|
+
(0, import_node_fs16.mkdirSync)(tmpRoot, { recursive: true });
|
|
16593
16827
|
const tag = releaseTag(releasedVersion);
|
|
16594
16828
|
if (hubCheckout) {
|
|
16595
16829
|
const fromHub = await extractPluginMmiFromHubCheckout(hubCheckout, tag, tmpRoot, execFileP5);
|
|
16596
16830
|
if (fromHub) return fromHub;
|
|
16597
16831
|
}
|
|
16598
|
-
return downloadPluginMmiViaGh(tag, (0,
|
|
16832
|
+
return downloadPluginMmiViaGh(tag, (0, import_node_path17.join)(tmpRoot, "gh"));
|
|
16599
16833
|
}
|
|
16600
16834
|
function cursorPluginPinsNeedingSeed(pins, releasedVersion) {
|
|
16601
16835
|
if (!isSemverVersion2(releasedVersion)) return pins.filter((pin) => !pin.hasPluginJson || !pin.hasHooksJson || pin.isEmpty);
|
|
@@ -16609,7 +16843,7 @@ function cursorPluginCacheSeedTargets(pins, releasedVersion, cacheRoot, cacheRoo
|
|
|
16609
16843
|
const needing = cursorPluginPinsNeedingSeed(pins, releasedVersion);
|
|
16610
16844
|
if (needing.length > 0) return needing.map((pin) => pin.path);
|
|
16611
16845
|
if (pins.length === 0 && cacheRoot && cacheRootExists && isSemverVersion2(releasedVersion)) {
|
|
16612
|
-
return [(0,
|
|
16846
|
+
return [(0, import_node_path17.join)(cacheRoot, `v${releasedVersion.replace(/^v/, "")}`)];
|
|
16613
16847
|
}
|
|
16614
16848
|
return [];
|
|
16615
16849
|
}
|
|
@@ -16624,7 +16858,7 @@ async function applyCursorPluginCacheSeed(input) {
|
|
|
16624
16858
|
for (const dest of targets) {
|
|
16625
16859
|
syncDirContents(source, dest);
|
|
16626
16860
|
}
|
|
16627
|
-
(0,
|
|
16861
|
+
(0, import_node_fs16.rmSync)(tmpRoot, { recursive: true, force: true });
|
|
16628
16862
|
return true;
|
|
16629
16863
|
}
|
|
16630
16864
|
var CURSOR_ENABLED_PIN_LABEL = "Cursor Team Marketplace enabled plugin pin";
|
|
@@ -16983,9 +17217,9 @@ ${block}
|
|
|
16983
17217
|
}
|
|
16984
17218
|
|
|
16985
17219
|
// src/cli-doctor-shared.ts
|
|
16986
|
-
var import_node_fs16 = require("node:fs");
|
|
16987
|
-
var import_node_path17 = require("node:path");
|
|
16988
17220
|
var import_node_fs17 = require("node:fs");
|
|
17221
|
+
var import_node_path18 = require("node:path");
|
|
17222
|
+
var import_node_fs18 = require("node:fs");
|
|
16989
17223
|
var GC_GH_TIMEOUT_MS = 2e4;
|
|
16990
17224
|
async function awsCallerArn() {
|
|
16991
17225
|
try {
|
|
@@ -17031,7 +17265,7 @@ async function localBranchHeads() {
|
|
|
17031
17265
|
}
|
|
17032
17266
|
async function currentRepoWorktreeGitRoot(repoRoot) {
|
|
17033
17267
|
const gitCommonDir = (await execFileP2("git", ["rev-parse", "--git-common-dir"], { timeout: GIT_TIMEOUT_MS }).catch(() => ({ stdout: "" }))).stdout.trim();
|
|
17034
|
-
return gitCommonDir ? (0,
|
|
17268
|
+
return gitCommonDir ? (0, import_node_path18.resolve)(repoRoot, gitCommonDir, "worktrees") : "";
|
|
17035
17269
|
}
|
|
17036
17270
|
async function worktreeBranches() {
|
|
17037
17271
|
const { stdout } = await execFileP2("git", ["worktree", "list", "--porcelain"], { timeout: GIT_TIMEOUT_MS });
|
|
@@ -17051,18 +17285,18 @@ function resolveGitdirForWorktreeFile(worktreePath, content) {
|
|
|
17051
17285
|
const match = /^gitdir:\s*(.+)\s*$/im.exec(content);
|
|
17052
17286
|
if (!match?.[1]) return void 0;
|
|
17053
17287
|
const raw = match[1].trim();
|
|
17054
|
-
return (0,
|
|
17288
|
+
return (0, import_node_path18.isAbsolute)(raw) ? raw : (0, import_node_path18.resolve)(worktreePath, raw);
|
|
17055
17289
|
}
|
|
17056
17290
|
function metadataOwnsMissingWorktreeDir(worktreePath, worktreeGitRoot) {
|
|
17057
17291
|
if (!worktreeGitRoot) return false;
|
|
17058
17292
|
try {
|
|
17059
|
-
const entries = (0,
|
|
17293
|
+
const entries = (0, import_node_fs18.readdirSync)(worktreeGitRoot, { withFileTypes: true });
|
|
17060
17294
|
for (const ent of entries) {
|
|
17061
17295
|
if (!ent.isDirectory()) continue;
|
|
17062
17296
|
try {
|
|
17063
|
-
const gitdirPath = (0,
|
|
17064
|
-
const resolvedGitdir = (0,
|
|
17065
|
-
if (sameWorktreeMetadataPath((0,
|
|
17297
|
+
const gitdirPath = (0, import_node_fs17.readFileSync)((0, import_node_path18.join)(worktreeGitRoot, ent.name, "gitdir"), "utf8").trim();
|
|
17298
|
+
const resolvedGitdir = (0, import_node_path18.isAbsolute)(gitdirPath) ? gitdirPath : (0, import_node_path18.resolve)(worktreeGitRoot, ent.name, gitdirPath);
|
|
17299
|
+
if (sameWorktreeMetadataPath((0, import_node_path18.dirname)(resolvedGitdir), worktreePath)) return true;
|
|
17066
17300
|
} catch {
|
|
17067
17301
|
}
|
|
17068
17302
|
}
|
|
@@ -17072,7 +17306,7 @@ function metadataOwnsMissingWorktreeDir(worktreePath, worktreeGitRoot) {
|
|
|
17072
17306
|
}
|
|
17073
17307
|
function pathExistsKnown(path2) {
|
|
17074
17308
|
try {
|
|
17075
|
-
(0,
|
|
17309
|
+
(0, import_node_fs18.statSync)(path2);
|
|
17076
17310
|
return true;
|
|
17077
17311
|
} catch (e) {
|
|
17078
17312
|
const code = typeof e === "object" && e && "code" in e ? String(e.code ?? "") : "";
|
|
@@ -17081,10 +17315,10 @@ function pathExistsKnown(path2) {
|
|
|
17081
17315
|
}
|
|
17082
17316
|
}
|
|
17083
17317
|
function inspectSiblingWorktreeDir(path2, worktreeGitRoot) {
|
|
17084
|
-
const gitPath = (0,
|
|
17318
|
+
const gitPath = (0, import_node_path18.join)(path2, ".git");
|
|
17085
17319
|
let st;
|
|
17086
17320
|
try {
|
|
17087
|
-
st = (0,
|
|
17321
|
+
st = (0, import_node_fs18.lstatSync)(gitPath);
|
|
17088
17322
|
} catch (e) {
|
|
17089
17323
|
const code = typeof e === "object" && e && "code" in e ? String(e.code ?? "") : "";
|
|
17090
17324
|
if (code === "ENOENT" || code === "ENOTDIR") {
|
|
@@ -17101,7 +17335,7 @@ function inspectSiblingWorktreeDir(path2, worktreeGitRoot) {
|
|
|
17101
17335
|
if (st.isDirectory()) return { path: path2, gitType: "dir" };
|
|
17102
17336
|
if (!st.isFile()) return { path: path2, gitType: "other" };
|
|
17103
17337
|
try {
|
|
17104
|
-
const gitFileContent = (0,
|
|
17338
|
+
const gitFileContent = (0, import_node_fs17.readFileSync)(gitPath, "utf8");
|
|
17105
17339
|
const gitdir = resolveGitdirForWorktreeFile(path2, gitFileContent);
|
|
17106
17340
|
const gitDirExists = gitdir ? pathExistsKnown(gitdir) : false;
|
|
17107
17341
|
return {
|
|
@@ -17118,7 +17352,7 @@ function inspectSiblingWorktreeDir(path2, worktreeGitRoot) {
|
|
|
17118
17352
|
}
|
|
17119
17353
|
function inspectDeadWorktreeDirContent(path2) {
|
|
17120
17354
|
try {
|
|
17121
|
-
return { entries: (0,
|
|
17355
|
+
return { entries: (0, import_node_fs18.readdirSync)(path2) };
|
|
17122
17356
|
} catch (e) {
|
|
17123
17357
|
const code = typeof e === "object" && e && "code" in e ? String(e.code ?? "") : "";
|
|
17124
17358
|
return { error: code ? `unable to inspect directory contents (${code})` : "unable to inspect directory contents" };
|
|
@@ -17137,8 +17371,8 @@ async function siblingWorktreeDirs() {
|
|
|
17137
17371
|
const worktreeGitRoot = await currentRepoWorktreeGitRoot(repoRoot);
|
|
17138
17372
|
const siblingRoot = siblingMmiWorktreesRoot(repoRoot);
|
|
17139
17373
|
try {
|
|
17140
|
-
const entries = (0,
|
|
17141
|
-
return entries.filter((ent) => ent.isDirectory()).map((ent) => inspectSiblingWorktreeDir((0,
|
|
17374
|
+
const entries = (0, import_node_fs18.readdirSync)(siblingRoot, { withFileTypes: true });
|
|
17375
|
+
return entries.filter((ent) => ent.isDirectory()).map((ent) => inspectSiblingWorktreeDir((0, import_node_path18.join)(siblingRoot, ent.name), worktreeGitRoot)).filter((entry) => Boolean(entry));
|
|
17142
17376
|
} catch {
|
|
17143
17377
|
return [];
|
|
17144
17378
|
}
|
|
@@ -17188,7 +17422,7 @@ async function fetchHubVersionInfo(baseUrl) {
|
|
|
17188
17422
|
}
|
|
17189
17423
|
function readRepoVersion() {
|
|
17190
17424
|
try {
|
|
17191
|
-
return JSON.parse((0,
|
|
17425
|
+
return JSON.parse((0, import_node_fs19.readFileSync)((0, import_node_path19.join)(process.cwd(), ".claude-plugin", "plugin.json"), "utf8")).version || void 0;
|
|
17192
17426
|
} catch {
|
|
17193
17427
|
return void 0;
|
|
17194
17428
|
}
|
|
@@ -17340,11 +17574,11 @@ async function applyPluginHeal(token, surface, log, opts) {
|
|
|
17340
17574
|
}
|
|
17341
17575
|
var installedPluginsPath = (surface = detectSurface(process.env)) => {
|
|
17342
17576
|
const homeDir = surface === "codex" ? ".codex" : ".claude";
|
|
17343
|
-
return (0,
|
|
17577
|
+
return (0, import_node_path19.join)((0, import_node_os7.homedir)(), homeDir, "plugins", "installed_plugins.json");
|
|
17344
17578
|
};
|
|
17345
17579
|
function readInstalledPlugins(surface = detectSurface(process.env)) {
|
|
17346
17580
|
try {
|
|
17347
|
-
return JSON.parse((0,
|
|
17581
|
+
return JSON.parse((0, import_node_fs19.readFileSync)(installedPluginsPath(surface), "utf8"));
|
|
17348
17582
|
} catch {
|
|
17349
17583
|
return null;
|
|
17350
17584
|
}
|
|
@@ -17352,13 +17586,13 @@ function readInstalledPlugins(surface = detectSurface(process.env)) {
|
|
|
17352
17586
|
function marketplaceCloneCandidates(surface, home) {
|
|
17353
17587
|
if (surface === "codex") {
|
|
17354
17588
|
return [
|
|
17355
|
-
(0,
|
|
17356
|
-
(0,
|
|
17589
|
+
(0, import_node_path19.join)(home, ".codex", ".tmp", "marketplaces", "mutmutco"),
|
|
17590
|
+
(0, import_node_path19.join)(home, ".codex", "plugins", "marketplaces", "mutmutco")
|
|
17357
17591
|
];
|
|
17358
17592
|
}
|
|
17359
|
-
return [(0,
|
|
17593
|
+
return [(0, import_node_path19.join)(home, ".claude", "plugins", "marketplaces", "mutmutco")];
|
|
17360
17594
|
}
|
|
17361
|
-
function marketplaceClonePresent(surface, home, exists =
|
|
17595
|
+
function marketplaceClonePresent(surface, home, exists = import_node_fs19.existsSync) {
|
|
17362
17596
|
return marketplaceCloneCandidates(surface, home).some(exists);
|
|
17363
17597
|
}
|
|
17364
17598
|
function snapshotPluginGuardInput(surface = detectSurface(process.env), isOrgRepo = false) {
|
|
@@ -17368,14 +17602,14 @@ function snapshotPluginGuardInput(surface = detectSurface(process.env), isOrgRep
|
|
|
17368
17602
|
isOrgRepo,
|
|
17369
17603
|
installRecordPresent: hasUserInstallRecord(installed, MMI_PLUGIN_ID) || hasProjectInstallRecord(installed, MMI_PLUGIN_ID, process.cwd()),
|
|
17370
17604
|
marketplaceClonePresent: marketplaceClonePresent(surface, (0, import_node_os7.homedir)()),
|
|
17371
|
-
pluginCachePresent: (0,
|
|
17605
|
+
pluginCachePresent: (0, import_node_fs19.existsSync)((0, import_node_path19.join)((0, import_node_os7.homedir)(), homeDir, "plugins", "cache", "mutmutco", "mmi"))
|
|
17372
17606
|
};
|
|
17373
17607
|
}
|
|
17374
17608
|
function installedPluginSources() {
|
|
17375
17609
|
return ["claude", "codex"].map((surface) => {
|
|
17376
|
-
const recordPath = (0,
|
|
17610
|
+
const recordPath = (0, import_node_path19.join)((0, import_node_os7.homedir)(), `.${surface}`, "plugins", "installed_plugins.json");
|
|
17377
17611
|
try {
|
|
17378
|
-
return { surface, installed: JSON.parse((0,
|
|
17612
|
+
return { surface, installed: JSON.parse((0, import_node_fs19.readFileSync)(recordPath, "utf8")), recordPath };
|
|
17379
17613
|
} catch {
|
|
17380
17614
|
return { surface, installed: null, recordPath };
|
|
17381
17615
|
}
|
|
@@ -17383,7 +17617,7 @@ function installedPluginSources() {
|
|
|
17383
17617
|
}
|
|
17384
17618
|
function readClaudeSettings() {
|
|
17385
17619
|
try {
|
|
17386
|
-
return JSON.parse((0,
|
|
17620
|
+
return JSON.parse((0, import_node_fs19.readFileSync)((0, import_node_path19.join)(process.cwd(), ".claude", "settings.json"), "utf8"));
|
|
17387
17621
|
} catch {
|
|
17388
17622
|
return null;
|
|
17389
17623
|
}
|
|
@@ -17405,7 +17639,7 @@ function writeProjectInstallRecord(record) {
|
|
|
17405
17639
|
const list = file.plugins[MMI_PLUGIN_ID] ?? [];
|
|
17406
17640
|
list.push(record);
|
|
17407
17641
|
file.plugins[MMI_PLUGIN_ID] = list;
|
|
17408
|
-
(0,
|
|
17642
|
+
(0, import_node_fs19.writeFileSync)(installedPluginsPath(), `${JSON.stringify(file, null, 2)}
|
|
17409
17643
|
`, "utf8");
|
|
17410
17644
|
return true;
|
|
17411
17645
|
} catch {
|
|
@@ -17418,9 +17652,9 @@ function backupAndWriteInstalledPlugins(records, pluginId) {
|
|
|
17418
17652
|
if (!file) return false;
|
|
17419
17653
|
if (!file.plugins) file.plugins = {};
|
|
17420
17654
|
const path2 = installedPluginsPath();
|
|
17421
|
-
(0,
|
|
17655
|
+
(0, import_node_fs19.copyFileSync)(path2, `${path2}.bak`);
|
|
17422
17656
|
file.plugins[pluginId] = records;
|
|
17423
|
-
(0,
|
|
17657
|
+
(0, import_node_fs19.writeFileSync)(path2, `${JSON.stringify(file, null, 2)}
|
|
17424
17658
|
`, "utf8");
|
|
17425
17659
|
return true;
|
|
17426
17660
|
} catch {
|
|
@@ -17428,22 +17662,22 @@ function backupAndWriteInstalledPlugins(records, pluginId) {
|
|
|
17428
17662
|
}
|
|
17429
17663
|
}
|
|
17430
17664
|
function opencodeConfigDir() {
|
|
17431
|
-
return (0,
|
|
17665
|
+
return (0, import_node_path19.join)((0, import_node_os7.homedir)(), ".config", "opencode");
|
|
17432
17666
|
}
|
|
17433
17667
|
function opencodeConfigPath() {
|
|
17434
|
-
return (0,
|
|
17668
|
+
return (0, import_node_path19.join)(opencodeConfigDir(), "opencode.jsonc");
|
|
17435
17669
|
}
|
|
17436
17670
|
function opencodeCommandsDir() {
|
|
17437
|
-
return (0,
|
|
17671
|
+
return (0, import_node_path19.join)(opencodeConfigDir(), "commands");
|
|
17438
17672
|
}
|
|
17439
17673
|
function opencodeSkillsPath() {
|
|
17440
|
-
return (0,
|
|
17674
|
+
return (0, import_node_path19.join)(opencodeConfigDir(), "node_modules", "@mutmutco", "opencode-mmi", "skills");
|
|
17441
17675
|
}
|
|
17442
17676
|
function opencodeConfigSnapshot() {
|
|
17443
17677
|
const path2 = opencodeConfigPath();
|
|
17444
|
-
if (!(0,
|
|
17678
|
+
if (!(0, import_node_fs19.existsSync)(path2)) return { path: path2, hasConfig: false, hasPluginField: false, parseOk: true };
|
|
17445
17679
|
try {
|
|
17446
|
-
const raw = (0,
|
|
17680
|
+
const raw = (0, import_node_fs19.readFileSync)(path2, "utf8");
|
|
17447
17681
|
const parsed = JSON.parse(stripJsonc(raw));
|
|
17448
17682
|
const hasPluginField = Object.prototype.hasOwnProperty.call(parsed, "plugin");
|
|
17449
17683
|
const skillsPaths = Array.isArray(parsed.skills?.paths) ? parsed.skills.paths.filter((p) => typeof p === "string") : void 0;
|
|
@@ -17466,9 +17700,9 @@ function writeOpencodeConfigPlugin(snapshot) {
|
|
|
17466
17700
|
const plan = planOpencodeConfigWrite(snapshot.hasConfig ? snapshot.raw : void 0);
|
|
17467
17701
|
if (plan.action === "already") return true;
|
|
17468
17702
|
if (!plan.text || plan.action === "unsafe") return false;
|
|
17469
|
-
(0,
|
|
17470
|
-
if (snapshot.hasConfig) (0,
|
|
17471
|
-
(0,
|
|
17703
|
+
(0, import_node_fs19.mkdirSync)((0, import_node_path19.dirname)(path2), { recursive: true });
|
|
17704
|
+
if (snapshot.hasConfig) (0, import_node_fs19.copyFileSync)(path2, `${path2}.bak`);
|
|
17705
|
+
(0, import_node_fs19.writeFileSync)(path2, plan.text, "utf8");
|
|
17472
17706
|
return true;
|
|
17473
17707
|
} catch {
|
|
17474
17708
|
return false;
|
|
@@ -17483,9 +17717,9 @@ function writeOpencodeSkillsPath(snapshot, skillsPath) {
|
|
|
17483
17717
|
const normalized = skillsPath.replace(/\\/g, "/");
|
|
17484
17718
|
if (!paths.some((p) => p.replace(/\\/g, "/") === normalized)) paths.push(skillsPath.replace(/\\/g, "/"));
|
|
17485
17719
|
parsed.skills = { ...skills, paths };
|
|
17486
|
-
(0,
|
|
17487
|
-
if (snapshot.hasConfig && (0,
|
|
17488
|
-
(0,
|
|
17720
|
+
(0, import_node_fs19.mkdirSync)((0, import_node_path19.dirname)(snapshot.path), { recursive: true });
|
|
17721
|
+
if (snapshot.hasConfig && (0, import_node_fs19.existsSync)(snapshot.path)) (0, import_node_fs19.copyFileSync)(snapshot.path, `${snapshot.path}.bak`);
|
|
17722
|
+
(0, import_node_fs19.writeFileSync)(snapshot.path, `${JSON.stringify(parsed, null, 2)}
|
|
17489
17723
|
`, "utf8");
|
|
17490
17724
|
return true;
|
|
17491
17725
|
} catch {
|
|
@@ -17494,7 +17728,7 @@ function writeOpencodeSkillsPath(snapshot, skillsPath) {
|
|
|
17494
17728
|
}
|
|
17495
17729
|
function opencodeExistingCommands() {
|
|
17496
17730
|
try {
|
|
17497
|
-
return (0,
|
|
17731
|
+
return (0, import_node_fs19.readdirSync)(opencodeCommandsDir(), { withFileTypes: true }).filter((entry) => entry.isFile() && entry.name.endsWith(".md")).map((entry) => entry.name.slice(0, -3).toLowerCase());
|
|
17498
17732
|
} catch {
|
|
17499
17733
|
return [];
|
|
17500
17734
|
}
|
|
@@ -17502,9 +17736,9 @@ function opencodeExistingCommands() {
|
|
|
17502
17736
|
function writeOpencodeCommandFiles() {
|
|
17503
17737
|
try {
|
|
17504
17738
|
const dir = opencodeCommandsDir();
|
|
17505
|
-
(0,
|
|
17739
|
+
(0, import_node_fs19.mkdirSync)(dir, { recursive: true });
|
|
17506
17740
|
for (const command of OPENCODE_WORKFLOW_COMMANDS) {
|
|
17507
|
-
(0,
|
|
17741
|
+
(0, import_node_fs19.writeFileSync)((0, import_node_path19.join)(dir, `${command}.md`), opencodeCommandMarkdown(command), "utf8");
|
|
17508
17742
|
}
|
|
17509
17743
|
return true;
|
|
17510
17744
|
} catch {
|
|
@@ -17513,12 +17747,12 @@ function writeOpencodeCommandFiles() {
|
|
|
17513
17747
|
}
|
|
17514
17748
|
function readOpencodeAdapterDiskVersion() {
|
|
17515
17749
|
const candidates = [
|
|
17516
|
-
(0,
|
|
17517
|
-
(0,
|
|
17750
|
+
(0, import_node_path19.join)(opencodeConfigDir(), "node_modules", "@mutmutco", "opencode-mmi", "package.json"),
|
|
17751
|
+
(0, import_node_path19.join)((0, import_node_os7.homedir)(), ".cache", "opencode", "node_modules", "@mutmutco", "opencode-mmi", "package.json")
|
|
17518
17752
|
];
|
|
17519
17753
|
for (const path2 of candidates) {
|
|
17520
17754
|
try {
|
|
17521
|
-
const parsed = JSON.parse((0,
|
|
17755
|
+
const parsed = JSON.parse((0, import_node_fs19.readFileSync)(path2, "utf8"));
|
|
17522
17756
|
if (typeof parsed.version === "string" && parsed.version.trim()) return parsed.version.trim();
|
|
17523
17757
|
} catch {
|
|
17524
17758
|
continue;
|
|
@@ -17534,7 +17768,7 @@ async function forceInstallOpencodeMmiPlugins(snapshot, log) {
|
|
|
17534
17768
|
try {
|
|
17535
17769
|
const specs = opencodeMmiPluginSpecs(snapshot);
|
|
17536
17770
|
log(` \u21BB force-refreshing OpenCode MMI npm plugin(s): ${specs.join(", ")}\u2026`);
|
|
17537
|
-
(0,
|
|
17771
|
+
(0, import_node_fs19.mkdirSync)(opencodeConfigDir(), { recursive: true });
|
|
17538
17772
|
await runHostBin("npm", ["install", "--prefix", opencodeConfigDir(), "--force", ...specs], { timeout: NPM_UPDATE_TIMEOUT_MS });
|
|
17539
17773
|
return true;
|
|
17540
17774
|
} catch {
|
|
@@ -17542,12 +17776,12 @@ async function forceInstallOpencodeMmiPlugins(snapshot, log) {
|
|
|
17542
17776
|
}
|
|
17543
17777
|
}
|
|
17544
17778
|
function opencodePackagesRoot() {
|
|
17545
|
-
return (0,
|
|
17779
|
+
return (0, import_node_path19.join)((0, import_node_os7.homedir)(), ".cache", "opencode", "packages", "@mutmutco");
|
|
17546
17780
|
}
|
|
17547
17781
|
function opencodePluginCacheDirs() {
|
|
17548
17782
|
const root = opencodePackagesRoot();
|
|
17549
17783
|
try {
|
|
17550
|
-
return (0,
|
|
17784
|
+
return (0, import_node_fs19.readdirSync)(root, { withFileTypes: true }).filter((e) => e.isDirectory() && e.name.startsWith("opencode-mmi@") && !e.name.startsWith(".")).map((e) => (0, import_node_path19.join)(root, e.name));
|
|
17551
17785
|
} catch {
|
|
17552
17786
|
return [];
|
|
17553
17787
|
}
|
|
@@ -17555,7 +17789,7 @@ function opencodePluginCacheDirs() {
|
|
|
17555
17789
|
function readOpencodeCacheDirVersion(cacheDir) {
|
|
17556
17790
|
try {
|
|
17557
17791
|
const parsed = JSON.parse(
|
|
17558
|
-
(0,
|
|
17792
|
+
(0, import_node_fs19.readFileSync)((0, import_node_path19.join)(cacheDir, "node_modules", "@mutmutco", "opencode-mmi", "package.json"), "utf8")
|
|
17559
17793
|
);
|
|
17560
17794
|
return typeof parsed.version === "string" && parsed.version.trim() ? parsed.version.trim() : void 0;
|
|
17561
17795
|
} catch {
|
|
@@ -17578,9 +17812,9 @@ function quarantineStaleOpencodePluginCaches(releasedVersion, log) {
|
|
|
17578
17812
|
});
|
|
17579
17813
|
if (!plan.quarantine) continue;
|
|
17580
17814
|
try {
|
|
17581
|
-
const quarantineRoot = (0,
|
|
17582
|
-
(0,
|
|
17583
|
-
(0,
|
|
17815
|
+
const quarantineRoot = (0, import_node_path19.join)(opencodePackagesRoot(), ".mmi-quarantine", stamp);
|
|
17816
|
+
(0, import_node_fs19.mkdirSync)(quarantineRoot, { recursive: true });
|
|
17817
|
+
(0, import_node_fs19.renameSync)(cacheDir, (0, import_node_path19.join)(quarantineRoot, (0, import_node_path19.basename)(cacheDir)));
|
|
17584
17818
|
log(` \u21BB quarantined stale OpenCode plugin cache ${plan.cacheVersion} (< ${plan.releasedVersion}) \u2014 OpenCode reinstalls the current adapter on next start`);
|
|
17585
17819
|
moved = true;
|
|
17586
17820
|
} catch {
|
|
@@ -17606,30 +17840,30 @@ function opencodePluginVersionsForReport() {
|
|
|
17606
17840
|
}
|
|
17607
17841
|
function opencodeDesktopLogsRoot() {
|
|
17608
17842
|
if (process.platform === "win32") {
|
|
17609
|
-
const base = process.env.APPDATA || (0,
|
|
17610
|
-
return (0,
|
|
17843
|
+
const base = process.env.APPDATA || (0, import_node_path19.join)((0, import_node_os7.homedir)(), "AppData", "Roaming");
|
|
17844
|
+
return (0, import_node_path19.join)(base, "ai.opencode.desktop", "logs");
|
|
17611
17845
|
}
|
|
17612
17846
|
if (process.platform === "darwin") {
|
|
17613
|
-
return (0,
|
|
17847
|
+
return (0, import_node_path19.join)((0, import_node_os7.homedir)(), "Library", "Application Support", "ai.opencode.desktop", "logs");
|
|
17614
17848
|
}
|
|
17615
|
-
return (0,
|
|
17849
|
+
return (0, import_node_path19.join)((0, import_node_os7.homedir)(), ".config", "ai.opencode.desktop", "logs");
|
|
17616
17850
|
}
|
|
17617
17851
|
function opencodeDesktopBootstrapSnapshot() {
|
|
17618
17852
|
const root = opencodeDesktopLogsRoot();
|
|
17619
17853
|
try {
|
|
17620
|
-
const sessionDirs = (0,
|
|
17854
|
+
const sessionDirs = (0, import_node_fs19.readdirSync)(root, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => (0, import_node_path19.join)(root, entry.name)).sort((a, b) => (0, import_node_fs19.statSync)(b).mtimeMs - (0, import_node_fs19.statSync)(a).mtimeMs);
|
|
17621
17855
|
const newest = sessionDirs[0];
|
|
17622
17856
|
if (!newest) return [];
|
|
17623
|
-
const logPath = (0,
|
|
17624
|
-
const text = (0,
|
|
17625
|
-
return opencodeAgentDirectoriesFromLog(text).filter((directory) => !(0,
|
|
17857
|
+
const logPath = (0, import_node_path19.join)(newest, "renderer.log");
|
|
17858
|
+
const text = (0, import_node_fs19.readFileSync)(logPath, "utf8");
|
|
17859
|
+
return opencodeAgentDirectoriesFromLog(text).filter((directory) => !(0, import_node_fs19.existsSync)(directory)).map((directory) => ({ directory, logPath }));
|
|
17626
17860
|
} catch {
|
|
17627
17861
|
return [];
|
|
17628
17862
|
}
|
|
17629
17863
|
}
|
|
17630
17864
|
function opencodeLegacyConfigSnapshot() {
|
|
17631
|
-
const legacyPath = (0,
|
|
17632
|
-
if (!(0,
|
|
17865
|
+
const legacyPath = (0, import_node_path19.join)((0, import_node_os7.homedir)(), ".opencode", "opencode.json");
|
|
17866
|
+
if (!(0, import_node_fs19.existsSync)(legacyPath)) return {};
|
|
17633
17867
|
const content = readTextFile(legacyPath);
|
|
17634
17868
|
if (content == null) return {};
|
|
17635
17869
|
const plugins = parseOpencodeLegacyConfigPlugins(content);
|
|
@@ -17641,24 +17875,24 @@ function opencodeLegacyConfigSnapshot() {
|
|
|
17641
17875
|
function quarantineOpencodeLegacyConfig(legacyPath) {
|
|
17642
17876
|
try {
|
|
17643
17877
|
const backupPath = `${legacyPath}.bak`;
|
|
17644
|
-
if ((0,
|
|
17645
|
-
(0,
|
|
17878
|
+
if ((0, import_node_fs19.existsSync)(backupPath)) return false;
|
|
17879
|
+
(0, import_node_fs19.renameSync)(legacyPath, backupPath);
|
|
17646
17880
|
return true;
|
|
17647
17881
|
} catch {
|
|
17648
17882
|
return false;
|
|
17649
17883
|
}
|
|
17650
17884
|
}
|
|
17651
17885
|
function cursorPluginCacheRoot() {
|
|
17652
|
-
return (0,
|
|
17886
|
+
return (0, import_node_path19.join)((0, import_node_os7.homedir)(), ".cursor", "plugins", "cache", "mutmutco", "mmi");
|
|
17653
17887
|
}
|
|
17654
17888
|
function cursorLogsRoot() {
|
|
17655
|
-
if (process.platform === "win32") return (0,
|
|
17656
|
-
if (process.platform === "darwin") return (0,
|
|
17657
|
-
return (0,
|
|
17889
|
+
if (process.platform === "win32") return (0, import_node_path19.join)((0, import_node_os7.homedir)(), "AppData", "Roaming", "Cursor", "logs");
|
|
17890
|
+
if (process.platform === "darwin") return (0, import_node_path19.join)((0, import_node_os7.homedir)(), "Library", "Application Support", "Cursor", "logs");
|
|
17891
|
+
return (0, import_node_path19.join)((0, import_node_os7.homedir)(), ".config", "Cursor", "logs");
|
|
17658
17892
|
}
|
|
17659
17893
|
function safeReaddirNames(dir) {
|
|
17660
17894
|
try {
|
|
17661
|
-
return (0,
|
|
17895
|
+
return (0, import_node_fs19.readdirSync)(dir, { withFileTypes: true }).map((e) => e.name);
|
|
17662
17896
|
} catch {
|
|
17663
17897
|
return [];
|
|
17664
17898
|
}
|
|
@@ -17667,15 +17901,15 @@ function readCursorPluginLogText() {
|
|
|
17667
17901
|
const root = cursorLogsRoot();
|
|
17668
17902
|
const sessions = safeReaddirNames(root).sort().reverse();
|
|
17669
17903
|
for (const session of sessions.slice(0, 5)) {
|
|
17670
|
-
const sessionDir = (0,
|
|
17904
|
+
const sessionDir = (0, import_node_path19.join)(root, session);
|
|
17671
17905
|
const texts = [];
|
|
17672
17906
|
for (const win of safeReaddirNames(sessionDir).filter((n) => n.startsWith("window"))) {
|
|
17673
|
-
const dir = (0,
|
|
17907
|
+
const dir = (0, import_node_path19.join)(sessionDir, win, "exthost", "anysphere.cursor-agent-exec");
|
|
17674
17908
|
for (const file of safeReaddirNames(dir)) {
|
|
17675
17909
|
if (!/^Cursor Plugins.*\.log$/i.test(file)) continue;
|
|
17676
|
-
const path2 = (0,
|
|
17910
|
+
const path2 = (0, import_node_path19.join)(dir, file);
|
|
17677
17911
|
try {
|
|
17678
|
-
texts.push({ text: (0,
|
|
17912
|
+
texts.push({ text: (0, import_node_fs19.readFileSync)(path2, "utf8"), mtime: (0, import_node_fs19.statSync)(path2).mtimeMs });
|
|
17679
17913
|
} catch {
|
|
17680
17914
|
}
|
|
17681
17915
|
}
|
|
@@ -17689,32 +17923,32 @@ function readCursorPluginLogText() {
|
|
|
17689
17923
|
function cursorPluginCachePinSnapshots() {
|
|
17690
17924
|
const root = cursorPluginCacheRoot();
|
|
17691
17925
|
try {
|
|
17692
|
-
return (0,
|
|
17693
|
-
const path2 = (0,
|
|
17694
|
-
const pluginJson = (0,
|
|
17695
|
-
const hooksJson = (0,
|
|
17696
|
-
const cliBundle = (0,
|
|
17697
|
-
const cursorHook = (0,
|
|
17926
|
+
return (0, import_node_fs19.readdirSync)(root, { withFileTypes: true }).filter((entry) => entry.isDirectory() && !entry.name.startsWith(".")).map((entry) => {
|
|
17927
|
+
const path2 = (0, import_node_path19.join)(root, entry.name);
|
|
17928
|
+
const pluginJson = (0, import_node_path19.join)(path2, ".cursor-plugin", "plugin.json");
|
|
17929
|
+
const hooksJson = (0, import_node_path19.join)(path2, "hooks", "hooks.json");
|
|
17930
|
+
const cliBundle = (0, import_node_path19.join)(path2, "cli", "dist", "index.cjs");
|
|
17931
|
+
const cursorHook = (0, import_node_path19.join)(path2, "scripts", "cursor-hook.mjs");
|
|
17698
17932
|
let version;
|
|
17699
17933
|
try {
|
|
17700
|
-
const raw = JSON.parse((0,
|
|
17934
|
+
const raw = JSON.parse((0, import_node_fs19.readFileSync)(pluginJson, "utf8"));
|
|
17701
17935
|
version = typeof raw.version === "string" ? raw.version : void 0;
|
|
17702
17936
|
} catch {
|
|
17703
17937
|
version = void 0;
|
|
17704
17938
|
}
|
|
17705
17939
|
let isEmpty = true;
|
|
17706
17940
|
try {
|
|
17707
|
-
isEmpty = (0,
|
|
17941
|
+
isEmpty = (0, import_node_fs19.readdirSync)(path2).length === 0;
|
|
17708
17942
|
} catch {
|
|
17709
17943
|
isEmpty = true;
|
|
17710
17944
|
}
|
|
17711
17945
|
return {
|
|
17712
17946
|
name: entry.name,
|
|
17713
17947
|
path: path2,
|
|
17714
|
-
hasPluginJson: (0,
|
|
17715
|
-
hasHooksJson: (0,
|
|
17716
|
-
hasCliBundle: (0,
|
|
17717
|
-
hasCursorHookScript: (0,
|
|
17948
|
+
hasPluginJson: (0, import_node_fs19.existsSync)(pluginJson),
|
|
17949
|
+
hasHooksJson: (0, import_node_fs19.existsSync)(hooksJson),
|
|
17950
|
+
hasCliBundle: (0, import_node_fs19.existsSync)(cliBundle),
|
|
17951
|
+
hasCursorHookScript: (0, import_node_fs19.existsSync)(cursorHook),
|
|
17718
17952
|
isEmpty,
|
|
17719
17953
|
version
|
|
17720
17954
|
};
|
|
@@ -17724,19 +17958,19 @@ function cursorPluginCachePinSnapshots() {
|
|
|
17724
17958
|
}
|
|
17725
17959
|
}
|
|
17726
17960
|
function hubCheckoutForCursorSeed() {
|
|
17727
|
-
const manifest = (0,
|
|
17728
|
-
return (0,
|
|
17961
|
+
const manifest = (0, import_node_path19.join)(process.cwd(), "plugins", "mmi", ".cursor-plugin", "plugin.json");
|
|
17962
|
+
return (0, import_node_fs19.existsSync)(manifest) ? process.cwd() : void 0;
|
|
17729
17963
|
}
|
|
17730
17964
|
function mmiPluginCacheRootSnapshots() {
|
|
17731
17965
|
const roots = [
|
|
17732
|
-
{ surface: "claude", root: (0,
|
|
17733
|
-
{ surface: "codex", root: (0,
|
|
17966
|
+
{ surface: "claude", root: (0, import_node_path19.join)((0, import_node_os7.homedir)(), ".claude", "plugins", "cache", "mutmutco", "mmi") },
|
|
17967
|
+
{ surface: "codex", root: (0, import_node_path19.join)((0, import_node_os7.homedir)(), ".codex", "plugins", "cache", "mutmutco", "mmi") }
|
|
17734
17968
|
];
|
|
17735
17969
|
return roots.flatMap(({ surface, root }) => {
|
|
17736
17970
|
try {
|
|
17737
|
-
const entries = (0,
|
|
17971
|
+
const entries = (0, import_node_fs19.readdirSync)(root, { withFileTypes: true }).map((entry) => ({
|
|
17738
17972
|
name: entry.name,
|
|
17739
|
-
path: (0,
|
|
17973
|
+
path: (0, import_node_path19.join)(root, entry.name),
|
|
17740
17974
|
isDirectory: entry.isDirectory()
|
|
17741
17975
|
}));
|
|
17742
17976
|
return [{ surface, root, entries }];
|
|
@@ -17747,7 +17981,7 @@ function mmiPluginCacheRootSnapshots() {
|
|
|
17747
17981
|
}
|
|
17748
17982
|
function hasNestedMmiChild(versionDir) {
|
|
17749
17983
|
try {
|
|
17750
|
-
return (0,
|
|
17984
|
+
return (0, import_node_fs19.statSync)((0, import_node_path19.join)(versionDir, "mmi")).isDirectory();
|
|
17751
17985
|
} catch {
|
|
17752
17986
|
return false;
|
|
17753
17987
|
}
|
|
@@ -17758,10 +17992,10 @@ function nestedPluginTreeSnapshot() {
|
|
|
17758
17992
|
);
|
|
17759
17993
|
}
|
|
17760
17994
|
function uniqueQuarantineTarget(path2) {
|
|
17761
|
-
if (!(0,
|
|
17995
|
+
if (!(0, import_node_fs19.existsSync)(path2)) return path2;
|
|
17762
17996
|
for (let i = 1; i < 100; i += 1) {
|
|
17763
17997
|
const candidate = `${path2}-${i}`;
|
|
17764
|
-
if (!(0,
|
|
17998
|
+
if (!(0, import_node_fs19.existsSync)(candidate)) return candidate;
|
|
17765
17999
|
}
|
|
17766
18000
|
return `${path2}-${Date.now()}`;
|
|
17767
18001
|
}
|
|
@@ -17770,10 +18004,10 @@ function quarantinePluginCacheDirs(plan) {
|
|
|
17770
18004
|
const failed = [];
|
|
17771
18005
|
for (const move of plan) {
|
|
17772
18006
|
try {
|
|
17773
|
-
if (!(0,
|
|
18007
|
+
if (!(0, import_node_fs19.existsSync)(move.from)) continue;
|
|
17774
18008
|
const target = uniqueQuarantineTarget(move.to);
|
|
17775
|
-
(0,
|
|
17776
|
-
(0,
|
|
18009
|
+
(0, import_node_fs19.mkdirSync)((0, import_node_path19.dirname)(target), { recursive: true });
|
|
18010
|
+
(0, import_node_fs19.renameSync)(move.from, target);
|
|
17777
18011
|
moved += 1;
|
|
17778
18012
|
} catch {
|
|
17779
18013
|
failed.push(move);
|
|
@@ -17792,23 +18026,23 @@ async function robocopyMirrorEmpty(emptyDir, target) {
|
|
|
17792
18026
|
}
|
|
17793
18027
|
async function clearNestedPluginTreeDir(targetPath) {
|
|
17794
18028
|
try {
|
|
17795
|
-
if (!(0,
|
|
18029
|
+
if (!(0, import_node_fs19.existsSync)(targetPath)) return true;
|
|
17796
18030
|
if (isWin) {
|
|
17797
|
-
const emptyDir = (0,
|
|
17798
|
-
(0,
|
|
18031
|
+
const emptyDir = (0, import_node_path19.join)((0, import_node_os7.tmpdir)(), `mmi-empty-${Date.now()}`);
|
|
18032
|
+
(0, import_node_fs19.mkdirSync)(emptyDir, { recursive: true });
|
|
17799
18033
|
try {
|
|
17800
18034
|
await robocopyMirrorEmpty(emptyDir, targetPath);
|
|
17801
|
-
(0,
|
|
18035
|
+
(0, import_node_fs19.rmSync)(targetPath, { recursive: true, force: true });
|
|
17802
18036
|
} finally {
|
|
17803
18037
|
try {
|
|
17804
|
-
(0,
|
|
18038
|
+
(0, import_node_fs19.rmSync)(emptyDir, { recursive: true, force: true });
|
|
17805
18039
|
} catch {
|
|
17806
18040
|
}
|
|
17807
18041
|
}
|
|
17808
|
-
return !(0,
|
|
18042
|
+
return !(0, import_node_fs19.existsSync)(targetPath);
|
|
17809
18043
|
}
|
|
17810
|
-
(0,
|
|
17811
|
-
return !(0,
|
|
18044
|
+
(0, import_node_fs19.rmSync)(targetPath, { recursive: true, force: true });
|
|
18045
|
+
return !(0, import_node_fs19.existsSync)(targetPath);
|
|
17812
18046
|
} catch {
|
|
17813
18047
|
return false;
|
|
17814
18048
|
}
|
|
@@ -17821,18 +18055,18 @@ async function applyNestedPluginTreeCleanup(paths, log) {
|
|
|
17821
18055
|
}
|
|
17822
18056
|
return true;
|
|
17823
18057
|
}
|
|
17824
|
-
var gitignorePath = () => (0,
|
|
18058
|
+
var gitignorePath = () => (0, import_node_path19.join)(process.cwd(), ".gitignore");
|
|
17825
18059
|
function readTextFile(path2) {
|
|
17826
18060
|
try {
|
|
17827
|
-
if (!(0,
|
|
17828
|
-
return (0,
|
|
18061
|
+
if (!(0, import_node_fs19.existsSync)(path2)) return null;
|
|
18062
|
+
return (0, import_node_fs19.readFileSync)(path2, "utf8");
|
|
17829
18063
|
} catch {
|
|
17830
18064
|
return null;
|
|
17831
18065
|
}
|
|
17832
18066
|
}
|
|
17833
18067
|
function mcpDirExists(path2) {
|
|
17834
18068
|
try {
|
|
17835
|
-
return (0,
|
|
18069
|
+
return (0, import_node_fs19.existsSync)(path2);
|
|
17836
18070
|
} catch {
|
|
17837
18071
|
return false;
|
|
17838
18072
|
}
|
|
@@ -17840,60 +18074,60 @@ function mcpDirExists(path2) {
|
|
|
17840
18074
|
function mcpConfigTargets() {
|
|
17841
18075
|
const cwd = process.cwd();
|
|
17842
18076
|
const home = (0, import_node_os7.homedir)();
|
|
17843
|
-
const cursorProjectDir = (0,
|
|
17844
|
-
const cursorUserDir = (0,
|
|
17845
|
-
const codexDir = (0,
|
|
18077
|
+
const cursorProjectDir = (0, import_node_path19.join)(cwd, ".cursor");
|
|
18078
|
+
const cursorUserDir = (0, import_node_path19.join)(home, ".cursor");
|
|
18079
|
+
const codexDir = (0, import_node_path19.join)(home, ".codex");
|
|
17846
18080
|
return [
|
|
17847
18081
|
// Claude Code project MCP — reconciled if present, never conjured (org seeds .cursor/mcp.json, not this).
|
|
17848
18082
|
{
|
|
17849
18083
|
host: "claude-code",
|
|
17850
18084
|
label: "Claude Code project MCP",
|
|
17851
|
-
path: (0,
|
|
18085
|
+
path: (0, import_node_path19.join)(cwd, ".mcp.json"),
|
|
17852
18086
|
format: "json",
|
|
17853
18087
|
present: true,
|
|
17854
18088
|
// parent is the repo root (cwd); the caller gates the whole reconcile on isOrgRepo
|
|
17855
18089
|
isRepoFile: true,
|
|
17856
18090
|
createWhenFileAbsent: false,
|
|
17857
|
-
content: readTextFile((0,
|
|
18091
|
+
content: readTextFile((0, import_node_path19.join)(cwd, ".mcp.json"))
|
|
17858
18092
|
},
|
|
17859
18093
|
// Cursor project MCP — the bootstrap-seeded surface; restored if its .cursor dir exists but the file is gone.
|
|
17860
18094
|
{
|
|
17861
18095
|
host: "cursor-project",
|
|
17862
18096
|
label: "Cursor project MCP",
|
|
17863
|
-
path: (0,
|
|
18097
|
+
path: (0, import_node_path19.join)(cursorProjectDir, "mcp.json"),
|
|
17864
18098
|
format: "json",
|
|
17865
18099
|
present: mcpDirExists(cursorProjectDir),
|
|
17866
18100
|
isRepoFile: true,
|
|
17867
18101
|
createWhenFileAbsent: true,
|
|
17868
|
-
content: readTextFile((0,
|
|
18102
|
+
content: readTextFile((0, import_node_path19.join)(cursorProjectDir, "mcp.json"))
|
|
17869
18103
|
},
|
|
17870
18104
|
// Cursor user MCP — global; written only when Cursor is installed (~/.cursor exists).
|
|
17871
18105
|
{
|
|
17872
18106
|
host: "cursor-user",
|
|
17873
18107
|
label: "Cursor user MCP",
|
|
17874
|
-
path: (0,
|
|
18108
|
+
path: (0, import_node_path19.join)(cursorUserDir, "mcp.json"),
|
|
17875
18109
|
format: "json",
|
|
17876
18110
|
present: mcpDirExists(cursorUserDir),
|
|
17877
18111
|
isRepoFile: false,
|
|
17878
18112
|
createWhenFileAbsent: true,
|
|
17879
|
-
content: readTextFile((0,
|
|
18113
|
+
content: readTextFile((0, import_node_path19.join)(cursorUserDir, "mcp.json"))
|
|
17880
18114
|
},
|
|
17881
18115
|
// Codex user config (TOML) — global; written only when Codex is installed (~/.codex exists).
|
|
17882
18116
|
{
|
|
17883
18117
|
host: "codex",
|
|
17884
18118
|
label: "Codex user config",
|
|
17885
|
-
path: (0,
|
|
18119
|
+
path: (0, import_node_path19.join)(codexDir, "config.toml"),
|
|
17886
18120
|
format: "toml",
|
|
17887
18121
|
present: mcpDirExists(codexDir),
|
|
17888
18122
|
isRepoFile: false,
|
|
17889
18123
|
createWhenFileAbsent: true,
|
|
17890
|
-
content: readTextFile((0,
|
|
18124
|
+
content: readTextFile((0, import_node_path19.join)(codexDir, "config.toml"))
|
|
17891
18125
|
}
|
|
17892
18126
|
];
|
|
17893
18127
|
}
|
|
17894
18128
|
function writeMcpConfigFile(path2, content) {
|
|
17895
18129
|
try {
|
|
17896
|
-
(0,
|
|
18130
|
+
(0, import_node_fs19.writeFileSync)(path2, content, "utf8");
|
|
17897
18131
|
return true;
|
|
17898
18132
|
} catch {
|
|
17899
18133
|
return false;
|
|
@@ -17973,7 +18207,7 @@ function strayBrowserArtifactPaths() {
|
|
|
17973
18207
|
const cwd = process.cwd();
|
|
17974
18208
|
return STRAY_BROWSER_ARTIFACT_DIRS.filter((rel) => {
|
|
17975
18209
|
try {
|
|
17976
|
-
return (0,
|
|
18210
|
+
return (0, import_node_fs19.existsSync)((0, import_node_path19.join)(cwd, rel));
|
|
17977
18211
|
} catch {
|
|
17978
18212
|
return false;
|
|
17979
18213
|
}
|
|
@@ -17981,14 +18215,14 @@ function strayBrowserArtifactPaths() {
|
|
|
17981
18215
|
}
|
|
17982
18216
|
function readGitignore() {
|
|
17983
18217
|
try {
|
|
17984
|
-
return (0,
|
|
18218
|
+
return (0, import_node_fs19.readFileSync)(gitignorePath(), "utf8");
|
|
17985
18219
|
} catch {
|
|
17986
18220
|
return null;
|
|
17987
18221
|
}
|
|
17988
18222
|
}
|
|
17989
18223
|
function writeGitignore(content) {
|
|
17990
18224
|
try {
|
|
17991
|
-
(0,
|
|
18225
|
+
(0, import_node_fs19.writeFileSync)(gitignorePath(), content, "utf8");
|
|
17992
18226
|
return true;
|
|
17993
18227
|
} catch {
|
|
17994
18228
|
return false;
|
|
@@ -18030,7 +18264,7 @@ async function runDoctor(opts, io = consoleIo, readOrigin) {
|
|
|
18030
18264
|
const semverPrefix = /^\d+\.\d+\.\d+/;
|
|
18031
18265
|
const isBehind = (installed2, released) => Boolean(installed2 && released && semverPrefix.test(installed2) && semverPrefix.test(released) && compareVersions(installed2, released) < 0);
|
|
18032
18266
|
const opencodeAdapterStale = isBehind(opencodeInstalledVersionForDoctor(), releasedVersion);
|
|
18033
|
-
const cursorCacheStale = (0,
|
|
18267
|
+
const cursorCacheStale = (0, import_node_fs19.existsSync)(cursorPluginCacheRoot()) && (cursorPluginCachePinSnapshots() ?? []).some((p) => isBehind(p.version, releasedVersion));
|
|
18034
18268
|
const healPlan = doctorHealPlan({
|
|
18035
18269
|
isOrgRepo,
|
|
18036
18270
|
surface,
|
|
@@ -18065,7 +18299,7 @@ async function runDoctor(opts, io = consoleIo, readOrigin) {
|
|
|
18065
18299
|
let onPath = pathProbe;
|
|
18066
18300
|
if (!onPath) {
|
|
18067
18301
|
const root = process.env.CLAUDE_PLUGIN_ROOT;
|
|
18068
|
-
if (root && (0,
|
|
18302
|
+
if (root && (0, import_node_fs19.existsSync)(`${root}/bin/mmi-cli${isWin ? ".cmd" : ""}`)) onPath = true;
|
|
18069
18303
|
}
|
|
18070
18304
|
checks.push({ ok: onPath, label: "mmi-cli on PATH", fix: "auto-provisioned at session start \u2014 reopen the session, or install the MMI plugin" });
|
|
18071
18305
|
const reloadHint = reloadAction(surface);
|
|
@@ -18212,7 +18446,7 @@ async function runDoctor(opts, io = consoleIo, readOrigin) {
|
|
|
18212
18446
|
checks.push(gitignoreCheck);
|
|
18213
18447
|
checks.push(buildRepoLocalWorktreeCheck({
|
|
18214
18448
|
isOrgRepo,
|
|
18215
|
-
hasRepoLocalWorktrees: (0,
|
|
18449
|
+
hasRepoLocalWorktrees: (0, import_node_fs19.existsSync)((0, import_node_path19.join)(process.cwd(), ".worktrees"))
|
|
18216
18450
|
}));
|
|
18217
18451
|
let driftCheck = buildPluginConfigDriftCheck({ isOrgRepo, installed, surface });
|
|
18218
18452
|
if (!driftCheck.ok && driftCheck.recordsToWrite && repairLocal) {
|
|
@@ -18501,7 +18735,7 @@ async function runDoctor(opts, io = consoleIo, readOrigin) {
|
|
|
18501
18735
|
}
|
|
18502
18736
|
checks.push(nestedPluginTreeCheck);
|
|
18503
18737
|
const cursorCacheRoot = cursorPluginCacheRoot();
|
|
18504
|
-
const cursorCacheRootExists = (0,
|
|
18738
|
+
const cursorCacheRootExists = (0, import_node_fs19.existsSync)(cursorCacheRoot);
|
|
18505
18739
|
let cursorPins = cursorPluginCachePinSnapshots() ?? [];
|
|
18506
18740
|
checks.push(
|
|
18507
18741
|
buildCursorPluginCacheCleanupCheck({
|
|
@@ -18529,7 +18763,7 @@ async function runDoctor(opts, io = consoleIo, readOrigin) {
|
|
|
18529
18763
|
cacheRootExists: cursorCacheRootExists,
|
|
18530
18764
|
hubCheckout: hubCheckoutForCursorSeed(),
|
|
18531
18765
|
execFileP: execFileP2,
|
|
18532
|
-
mkdtemp: (prefix) => (0, import_promises2.mkdtemp)((0,
|
|
18766
|
+
mkdtemp: (prefix) => (0, import_promises2.mkdtemp)((0, import_node_path19.join)((0, import_node_os7.tmpdir)(), prefix)),
|
|
18533
18767
|
log: (m) => io.err(m)
|
|
18534
18768
|
});
|
|
18535
18769
|
if (seeded) {
|
|
@@ -18724,23 +18958,23 @@ function mergeGuardHook(settings) {
|
|
|
18724
18958
|
next.hooks = hooks;
|
|
18725
18959
|
return next;
|
|
18726
18960
|
}
|
|
18727
|
-
var userScopeSettingsPath = (surface = detectSurface(process.env)) => (0,
|
|
18961
|
+
var userScopeSettingsPath = (surface = detectSurface(process.env)) => (0, import_node_path19.join)((0, import_node_os7.homedir)(), surface === "codex" ? ".codex" : ".claude", "settings.json");
|
|
18728
18962
|
function ensureUserScopeGuardHook(opts = {}) {
|
|
18729
18963
|
const path2 = opts.settingsPath ?? userScopeSettingsPath();
|
|
18730
18964
|
try {
|
|
18731
18965
|
let current = null;
|
|
18732
|
-
if ((0,
|
|
18966
|
+
if ((0, import_node_fs19.existsSync)(path2)) {
|
|
18733
18967
|
try {
|
|
18734
|
-
current = JSON.parse((0,
|
|
18968
|
+
current = JSON.parse((0, import_node_fs19.readFileSync)(path2, "utf8"));
|
|
18735
18969
|
} catch {
|
|
18736
18970
|
return "failed";
|
|
18737
18971
|
}
|
|
18738
18972
|
}
|
|
18739
18973
|
if (settingsHasGuardHook(current)) return "already";
|
|
18740
18974
|
const merged = mergeGuardHook(current);
|
|
18741
|
-
(0,
|
|
18742
|
-
if ((0,
|
|
18743
|
-
(0,
|
|
18975
|
+
(0, import_node_fs19.mkdirSync)((0, import_node_path19.dirname)(path2), { recursive: true });
|
|
18976
|
+
if ((0, import_node_fs19.existsSync)(path2)) (0, import_node_fs19.copyFileSync)(path2, `${path2}.bak`);
|
|
18977
|
+
(0, import_node_fs19.writeFileSync)(path2, `${JSON.stringify(merged, null, 2)}
|
|
18744
18978
|
`, "utf8");
|
|
18745
18979
|
return "written";
|
|
18746
18980
|
} catch {
|
|
@@ -18877,7 +19111,7 @@ async function applyGcPlan(plan, remote) {
|
|
|
18877
19111
|
cleanupBranch: (branch, expectedHeadOid) => cleanupPrMergeLocalBranch(branch.branch, {
|
|
18878
19112
|
beforeWorktrees,
|
|
18879
19113
|
startingPath: branch.worktreePath,
|
|
18880
|
-
pathExists: (p) => (0,
|
|
19114
|
+
pathExists: (p) => (0, import_node_fs20.existsSync)(p),
|
|
18881
19115
|
execGit: async (args) => (await execFileP2("git", args, { timeout: GIT_TIMEOUT_MS })).stdout,
|
|
18882
19116
|
teardownWorktreeStage,
|
|
18883
19117
|
deferredStore,
|
|
@@ -18900,7 +19134,7 @@ async function applyGcPlan(plan, remote) {
|
|
|
18900
19134
|
for (const wt of plan.worktreeDirs) {
|
|
18901
19135
|
try {
|
|
18902
19136
|
const cleanupTarget = resolveSafeSiblingWorktreeCleanupTarget(wt.path, siblingRoot, {
|
|
18903
|
-
realpath: (path2) => (0,
|
|
19137
|
+
realpath: (path2) => (0, import_node_fs20.realpathSync)(path2)
|
|
18904
19138
|
});
|
|
18905
19139
|
if (!cleanupTarget.ok) {
|
|
18906
19140
|
result.failed.push(`${wt.path}: ${cleanupTarget.reason}`);
|
|
@@ -18943,13 +19177,13 @@ var program2 = new Command();
|
|
|
18943
19177
|
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)");
|
|
18944
19178
|
var rules = program2.command("rules").description("org-managed .gitignore delivery");
|
|
18945
19179
|
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) => {
|
|
18946
|
-
const path2 = (0,
|
|
18947
|
-
const current = (0,
|
|
19180
|
+
const path2 = (0, import_node_path20.join)(process.cwd(), ".gitignore");
|
|
19181
|
+
const current = (0, import_node_fs20.existsSync)(path2) ? (0, import_node_fs20.readFileSync)(path2, "utf8") : null;
|
|
18948
19182
|
const plan = planManagedGitignore(current);
|
|
18949
19183
|
const drift = [...plan.added.map((l) => `+${l}`), ...plan.removed.map((l) => `-${l}`)].join(", ") || "block normalize";
|
|
18950
19184
|
if (opts.write) {
|
|
18951
19185
|
if (plan.changed) {
|
|
18952
|
-
(0,
|
|
19186
|
+
(0, import_node_fs20.writeFileSync)(path2, plan.content, "utf8");
|
|
18953
19187
|
console.log(`mmi-cli rules gitignore: updated .gitignore (${drift})`);
|
|
18954
19188
|
} else {
|
|
18955
19189
|
console.log("mmi-cli rules gitignore: up to date");
|
|
@@ -19114,7 +19348,7 @@ function runWorktreeInstall(command, cwd, quiet) {
|
|
|
19114
19348
|
async function primaryCheckoutRoot(worktreeRoot) {
|
|
19115
19349
|
try {
|
|
19116
19350
|
const out = (await execFileP2("git", ["-C", worktreeRoot, "rev-parse", "--path-format=absolute", "--git-common-dir"], { timeout: GIT_TIMEOUT_MS })).stdout.trim();
|
|
19117
|
-
return out ? (0,
|
|
19351
|
+
return out ? (0, import_node_path20.dirname)(out) : void 0;
|
|
19118
19352
|
} catch {
|
|
19119
19353
|
return void 0;
|
|
19120
19354
|
}
|
|
@@ -19129,26 +19363,26 @@ function makeProvisionDeps(worktreeRoot, quiet, log) {
|
|
|
19129
19363
|
function acquireWorktreeSetupLock(worktreeRoot) {
|
|
19130
19364
|
const lockPath = repoRuntimeStatePath(worktreeRoot, "worktree-setup.lock");
|
|
19131
19365
|
const take = () => {
|
|
19132
|
-
const fd = (0,
|
|
19366
|
+
const fd = (0, import_node_fs20.openSync)(lockPath, "wx");
|
|
19133
19367
|
try {
|
|
19134
|
-
(0,
|
|
19368
|
+
(0, import_node_fs20.writeSync)(fd, String(Date.now()));
|
|
19135
19369
|
} finally {
|
|
19136
|
-
(0,
|
|
19370
|
+
(0, import_node_fs20.closeSync)(fd);
|
|
19137
19371
|
}
|
|
19138
19372
|
return () => {
|
|
19139
19373
|
try {
|
|
19140
|
-
(0,
|
|
19374
|
+
(0, import_node_fs20.rmSync)(lockPath, { force: true });
|
|
19141
19375
|
} catch {
|
|
19142
19376
|
}
|
|
19143
19377
|
};
|
|
19144
19378
|
};
|
|
19145
19379
|
try {
|
|
19146
|
-
(0,
|
|
19380
|
+
(0, import_node_fs20.mkdirSync)((0, import_node_path20.dirname)(lockPath), { recursive: true });
|
|
19147
19381
|
return take();
|
|
19148
19382
|
} catch {
|
|
19149
19383
|
try {
|
|
19150
|
-
if (Date.now() - (0,
|
|
19151
|
-
(0,
|
|
19384
|
+
if (Date.now() - (0, import_node_fs20.statSync)(lockPath).mtimeMs > WORKTREE_SETUP_LOCK_TTL_MS) {
|
|
19385
|
+
(0, import_node_fs20.rmSync)(lockPath, { force: true });
|
|
19152
19386
|
return take();
|
|
19153
19387
|
}
|
|
19154
19388
|
} catch {
|
|
@@ -19638,7 +19872,7 @@ project.command("set [owner/repo]").description("upsert project META (idempotent
|
|
|
19638
19872
|
const vars = rawValues("--var");
|
|
19639
19873
|
if (o.secretsFile) {
|
|
19640
19874
|
try {
|
|
19641
|
-
vars.push(`secrets=${(0,
|
|
19875
|
+
vars.push(`secrets=${(0, import_node_fs20.readFileSync)(o.secretsFile, "utf8")}`);
|
|
19642
19876
|
} catch (e) {
|
|
19643
19877
|
return fail(`project set: cannot read --secrets-file ${o.secretsFile}: ${e.message}`);
|
|
19644
19878
|
}
|
|
@@ -20208,9 +20442,9 @@ pr.command("view <number>").description("read a PR as structured JSON (merged st
|
|
|
20208
20442
|
}
|
|
20209
20443
|
});
|
|
20210
20444
|
async function listCiWorkflowPaths(cwd = process.cwd()) {
|
|
20211
|
-
const wfDir = (0,
|
|
20212
|
-
if (!(0,
|
|
20213
|
-
return (0,
|
|
20445
|
+
const wfDir = (0, import_node_path20.join)(cwd, ".github", "workflows");
|
|
20446
|
+
if (!(0, import_node_fs20.existsSync)(wfDir)) return [];
|
|
20447
|
+
return (0, import_node_fs20.readdirSync)(wfDir).filter((name) => /\.(ya?ml)$/i.test(name)).map((name) => `.github/workflows/${name}`);
|
|
20214
20448
|
}
|
|
20215
20449
|
async function resolveMergeCiPolicyForCheckout(repoOpt) {
|
|
20216
20450
|
const repo = repoOpt ?? await resolveRepo();
|
|
@@ -20229,7 +20463,7 @@ function ciAuditDeps() {
|
|
|
20229
20463
|
// Continuous CI delivery (#1550): the gate re-seed renders from the Hub's on-disk seed templates. The
|
|
20230
20464
|
// reconcile runs IN the Hub checkout, so this is local-file I/O (no network fetch). Path is relative to
|
|
20231
20465
|
// the repo root (e.g. skills/bootstrap/seeds/gate.template.yml).
|
|
20232
|
-
readSeedFile: (path2) => (0,
|
|
20466
|
+
readSeedFile: (path2) => (0, import_node_fs20.existsSync)(path2) ? (0, import_node_fs20.readFileSync)(path2, "utf8") : null
|
|
20233
20467
|
};
|
|
20234
20468
|
}
|
|
20235
20469
|
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) => {
|
|
@@ -20399,7 +20633,7 @@ async function createDeferredWorktreeStore() {
|
|
|
20399
20633
|
},
|
|
20400
20634
|
write: async (entries) => {
|
|
20401
20635
|
try {
|
|
20402
|
-
await (0, import_promises3.mkdir)((0,
|
|
20636
|
+
await (0, import_promises3.mkdir)((0, import_node_path20.dirname)(registryPath), { recursive: true });
|
|
20403
20637
|
await (0, import_promises3.writeFile)(registryPath, serializeDeferredWorktrees(entries), "utf8");
|
|
20404
20638
|
} catch {
|
|
20405
20639
|
}
|
|
@@ -20413,13 +20647,13 @@ var realWorktreeDirRemover = {
|
|
|
20413
20647
|
probe: (p) => {
|
|
20414
20648
|
let st;
|
|
20415
20649
|
try {
|
|
20416
|
-
st = (0,
|
|
20650
|
+
st = (0, import_node_fs20.lstatSync)(p);
|
|
20417
20651
|
} catch {
|
|
20418
20652
|
return null;
|
|
20419
20653
|
}
|
|
20420
20654
|
if (st.isSymbolicLink()) return "link";
|
|
20421
20655
|
try {
|
|
20422
|
-
(0,
|
|
20656
|
+
(0, import_node_fs20.readlinkSync)(p);
|
|
20423
20657
|
return "link";
|
|
20424
20658
|
} catch {
|
|
20425
20659
|
}
|
|
@@ -20427,7 +20661,7 @@ var realWorktreeDirRemover = {
|
|
|
20427
20661
|
},
|
|
20428
20662
|
readdir: (p) => {
|
|
20429
20663
|
try {
|
|
20430
|
-
return (0,
|
|
20664
|
+
return (0, import_node_fs20.readdirSync)(p);
|
|
20431
20665
|
} catch {
|
|
20432
20666
|
return [];
|
|
20433
20667
|
}
|
|
@@ -20436,9 +20670,9 @@ var realWorktreeDirRemover = {
|
|
|
20436
20670
|
// leaving the target); a file symlink with unlink. rmdir first, fall back to unlink.
|
|
20437
20671
|
detachLink: (p) => {
|
|
20438
20672
|
try {
|
|
20439
|
-
(0,
|
|
20673
|
+
(0, import_node_fs20.rmdirSync)(p);
|
|
20440
20674
|
} catch {
|
|
20441
|
-
(0,
|
|
20675
|
+
(0, import_node_fs20.unlinkSync)(p);
|
|
20442
20676
|
}
|
|
20443
20677
|
},
|
|
20444
20678
|
removeTree: (p) => (0, import_promises3.rm)(p, { recursive: true, force: true, maxRetries: 5, retryDelay: 200 })
|
|
@@ -20468,9 +20702,9 @@ async function worktreeHasStageState(worktreePath) {
|
|
|
20468
20702
|
}
|
|
20469
20703
|
}
|
|
20470
20704
|
function stageStateFileBelongsToWorktree(statePath, worktreePath) {
|
|
20471
|
-
if (!(0,
|
|
20705
|
+
if (!(0, import_node_fs20.existsSync)(statePath)) return false;
|
|
20472
20706
|
try {
|
|
20473
|
-
const state = JSON.parse((0,
|
|
20707
|
+
const state = JSON.parse((0, import_node_fs20.readFileSync)(statePath, "utf8"));
|
|
20474
20708
|
const recordedCwd = typeof state.identity?.cwd === "string" ? state.identity.cwd : typeof state.cwd === "string" ? state.cwd : "";
|
|
20475
20709
|
return Boolean(recordedCwd && isPathUnderDirectory(recordedCwd, worktreePath));
|
|
20476
20710
|
} catch {
|
|
@@ -20542,7 +20776,7 @@ pr.command("merge <number>").description("merge a PR (squash by default) and cle
|
|
|
20542
20776
|
localCleanup = await cleanupPrMergeLocalBranch(headRef, {
|
|
20543
20777
|
beforeWorktrees,
|
|
20544
20778
|
startingPath,
|
|
20545
|
-
pathExists: (p) => (0,
|
|
20779
|
+
pathExists: (p) => (0, import_node_fs20.existsSync)(p),
|
|
20546
20780
|
execGit: async (args) => (await execFileP2("git", args, { timeout: GIT_TIMEOUT_MS })).stdout,
|
|
20547
20781
|
teardownWorktreeStage,
|
|
20548
20782
|
deferredStore,
|
|
@@ -20763,7 +20997,7 @@ function stageScopedRunOpts(o) {
|
|
|
20763
20997
|
};
|
|
20764
20998
|
}
|
|
20765
20999
|
function printLine(value) {
|
|
20766
|
-
(0,
|
|
21000
|
+
(0, import_node_fs20.writeSync)(1, `${value}
|
|
20767
21001
|
`);
|
|
20768
21002
|
}
|
|
20769
21003
|
function stageKeepAlive() {
|
|
@@ -20777,8 +21011,8 @@ async function resolveStage() {
|
|
|
20777
21011
|
const portRange = portRangeMeta && typeof portRangeMeta.start === "number" && typeof portRangeMeta.end === "number" ? [portRangeMeta.start, portRangeMeta.end] : void 0;
|
|
20778
21012
|
return decideStage({
|
|
20779
21013
|
registry: { deployModel: project2?.deployModel, portRange, error: read.ok ? void 0 : read.error },
|
|
20780
|
-
hasCompose: (0,
|
|
20781
|
-
hasEnvExample: (0,
|
|
21014
|
+
hasCompose: (0, import_node_fs20.existsSync)((0, import_node_path20.join)(process.cwd(), "docker-compose.yml")),
|
|
21015
|
+
hasEnvExample: (0, import_node_fs20.existsSync)((0, import_node_path20.join)(process.cwd(), ".env.example"))
|
|
20782
21016
|
});
|
|
20783
21017
|
}
|
|
20784
21018
|
async function fetchStageVaultEnvMerge() {
|
|
@@ -21231,6 +21465,27 @@ wiki.command("publish <pages-dir>").description("mint a 1h repo-scoped contents:
|
|
|
21231
21465
|
);
|
|
21232
21466
|
if (!ok) process.exitCode = 1;
|
|
21233
21467
|
});
|
|
21468
|
+
wiki.command("plan").description("compute the release wiki plan for the cwd repo \u2014 collect its opt-in `wiki:page` pages, read the previous incremental stamp from <repo>.wiki.git (scoped minted token), and print a JSON plan naming which pages changed plus the fresh full stamp. Runs from ANY org repo checkout (no repo-local script). Fails loud on the credential/read gap \u2014 never a silent skip").option("--repo <owner/repo>", "target repo (defaults to the cwd repo)").action(async (o) => {
|
|
21469
|
+
const repo = await resolveRepo(o.repo);
|
|
21470
|
+
if (!repo) return fail("wiki plan: could not determine the target repo (pass --repo owner/name)");
|
|
21471
|
+
const cfg = await loadConfig();
|
|
21472
|
+
const readers = fsReaders(process.cwd());
|
|
21473
|
+
const ok = await wikiPlan(
|
|
21474
|
+
{
|
|
21475
|
+
collectPages: () => collectFeaturePages(readers),
|
|
21476
|
+
readSource: (p) => readers.readDoc(p),
|
|
21477
|
+
mintAndReadStamp: async (r) => {
|
|
21478
|
+
const minted = await mintWikiToken(r, registryClientDeps(cfg));
|
|
21479
|
+
if (!minted.ok) return { ok: false, error: minted.error };
|
|
21480
|
+
return readWikiStampViaGit(realWikiGitPublishDeps(), { repo: r, token: minted.mint.token });
|
|
21481
|
+
},
|
|
21482
|
+
out: (json) => console.log(json),
|
|
21483
|
+
err: (msg) => console.error(msg)
|
|
21484
|
+
},
|
|
21485
|
+
{ repo }
|
|
21486
|
+
);
|
|
21487
|
+
if (!ok) process.exitCode = 1;
|
|
21488
|
+
});
|
|
21234
21489
|
var bootstrap = program2.command("bootstrap").description("plan repo bootstrap operations; mutations require master-admin approval").option("--repo <owner/repo>", "target repo").option("--class <class>", "deployable | content", "deployable").option("--json", "machine-readable output").option("--apply", "reserved for future bootstrap execution after explicit master-admin approval").action((o) => {
|
|
21235
21490
|
if (!o.repo) return fail("bootstrap: required option --repo <owner/repo> not specified");
|
|
21236
21491
|
if (o.apply) return fail("bootstrap: execution is not implemented yet; use the dry-run plan and the existing /bootstrap skill");
|
|
@@ -21250,7 +21505,7 @@ bootstrap.command("verify <repo>").description("audit whether an existing repo i
|
|
|
21250
21505
|
client: defaultGitHubClient(),
|
|
21251
21506
|
projectMeta: meta,
|
|
21252
21507
|
deployModel: typeof meta?.deployModel === "string" ? meta.deployModel : void 0,
|
|
21253
|
-
readLocalFile: (path2) => path2 === "projects.json" && apiProjects != null ? apiProjects : (0,
|
|
21508
|
+
readLocalFile: (path2) => path2 === "projects.json" && apiProjects != null ? apiProjects : (0, import_node_fs20.existsSync)(path2) ? (0, import_node_fs20.readFileSync)(path2, "utf8") : null,
|
|
21254
21509
|
// requiredGcpApis is stored as an array by a JSON write, but `project set --var KEY=VALUE` stores a raw
|
|
21255
21510
|
// comma-string — accept either so the seeded value verifies regardless of how it was written.
|
|
21256
21511
|
requiredGcpApis: (() => {
|
|
@@ -21301,12 +21556,12 @@ bootstrap.command("apply <repo>").description("idempotent seed apply from skills
|
|
|
21301
21556
|
return fail(`bootstrap apply: ${e.message}`);
|
|
21302
21557
|
}
|
|
21303
21558
|
const manifestPath = "skills/bootstrap/seeds/manifest.json";
|
|
21304
|
-
if (!(0,
|
|
21305
|
-
const manifest = loadBootstrapSeeds((0,
|
|
21559
|
+
if (!(0, import_node_fs20.existsSync)(manifestPath)) return fail(`bootstrap apply: ${manifestPath} not found; run from the MMI-Hub repo root`);
|
|
21560
|
+
const manifest = loadBootstrapSeeds((0, import_node_fs20.readFileSync)(manifestPath, "utf8"));
|
|
21306
21561
|
const baseBranch = o.class === "content" ? "main" : "development";
|
|
21307
21562
|
const slug = parsedRepo.slug;
|
|
21308
21563
|
const gh = async (args) => execFileP2("gh", args, { timeout: 2e4 });
|
|
21309
|
-
const readFile2 = (p) => (0,
|
|
21564
|
+
const readFile2 = (p) => (0, import_node_fs20.existsSync)(p) ? (0, import_node_fs20.readFileSync)(p, "utf8") : null;
|
|
21310
21565
|
const enc = (p) => p.split("/").map(encodeURIComponent).join("/");
|
|
21311
21566
|
const rawVars = {};
|
|
21312
21567
|
for (const value of rawValues("--var")) {
|
|
@@ -21603,16 +21858,16 @@ access.command("audit").description("audit collaborator roles + train-branch pus
|
|
|
21603
21858
|
const repoClass = o.class;
|
|
21604
21859
|
targets = [{ repo: o.repo, class: repoClass, releaseTrack: repoClass === "content" ? "trunk" : resolveReleaseTrack(meta, void 0, o.repo) }];
|
|
21605
21860
|
} else {
|
|
21606
|
-
const projectsJson = registryProjects ? JSON.stringify({ projects: registryProjects }) : (0,
|
|
21861
|
+
const projectsJson = registryProjects ? JSON.stringify({ projects: registryProjects }) : (0, import_node_fs20.existsSync)("projects.json") ? (0, import_node_fs20.readFileSync)("projects.json", "utf8") : null;
|
|
21607
21862
|
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>");
|
|
21608
|
-
const fanoutJson = (0,
|
|
21863
|
+
const fanoutJson = (0, import_node_fs20.existsSync)(".github/fanout-targets.json") ? (0, import_node_fs20.readFileSync)(".github/fanout-targets.json", "utf8") : null;
|
|
21609
21864
|
targets = loadAccessTargets(projectsJson, fanoutJson);
|
|
21610
21865
|
}
|
|
21611
21866
|
const derivedMatrix = registryProjects ? accessMatrixFromProjects(registryProjects) : {};
|
|
21612
|
-
const fileMatrix = (0,
|
|
21867
|
+
const fileMatrix = (0, import_node_fs20.existsSync)("access-matrix.json") ? loadAccessMatrix((0, import_node_fs20.readFileSync)("access-matrix.json", "utf8")) : {};
|
|
21613
21868
|
const matrix = mergeAccessMatrix(fileMatrix, derivedMatrix);
|
|
21614
21869
|
const derivedContracts = registryProjects ? dataAccessContractsFromProjects(registryProjects) : { consumers: {} };
|
|
21615
|
-
const fileContracts = (0,
|
|
21870
|
+
const fileContracts = (0, import_node_fs20.existsSync)("data-access-contracts.json") ? loadDataAccessContracts((0, import_node_fs20.readFileSync)("data-access-contracts.json", "utf8")) : { consumers: {} };
|
|
21616
21871
|
const dataAccess = mergeDataAccessContracts(fileContracts, derivedContracts);
|
|
21617
21872
|
const report = await auditOrgAccess(targets, deps, matrix, dataAccess);
|
|
21618
21873
|
console.log(o.json ? JSON.stringify(report, null, 2) : renderAccessReport(report));
|
|
@@ -21651,6 +21906,15 @@ program2.command("session-start").description("run the SessionStart verbs (whoam
|
|
|
21651
21906
|
// never costs banner time and next session's glance renders instantly within budget.
|
|
21652
21907
|
scheduleRefresh: () => spawnDetachedSelf(["board", "slice-refresh", "--quiet"], { spawn: import_node_child_process12.spawn, execPath: process.execPath, scriptPath: process.argv[1] })
|
|
21653
21908
|
}),
|
|
21909
|
+
// local train sync (#2582): ff local development/main/rc to origin so this checkout never lags a
|
|
21910
|
+
// release pushed to origin (incl. the deferred main -> development alignment merge). Bounded git,
|
|
21911
|
+
// fail-soft, silent unless a branch moved.
|
|
21912
|
+
syncTrain: async (io) => {
|
|
21913
|
+
const gitRun = async (file, args) => (await execFileP2(file, args, { timeout: GIT_TIMEOUT_MS })).stdout;
|
|
21914
|
+
const result = await syncLocalTrainBranches(gitRun, { warn: (m) => io.err(`[mmi-hook] train sync: ${m}`) });
|
|
21915
|
+
const line = localTrainSyncBannerLine(result);
|
|
21916
|
+
if (line) io.log(line);
|
|
21917
|
+
},
|
|
21654
21918
|
doctor: (io) => runDoctor({ banner: true }, io)
|
|
21655
21919
|
});
|
|
21656
21920
|
await runSessionStart(parallel, sequential, consoleIo);
|