@mutmutco/cli 3.8.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 +687 -305
- 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,13 +9834,26 @@ 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) {
|
|
9750
9841
|
if (!value) throw new Error(`${label} could not be resolved`);
|
|
9751
9842
|
return value;
|
|
9752
9843
|
}
|
|
9844
|
+
function surfaceTrainSubprocessFailure(e, file, args) {
|
|
9845
|
+
if (!(e instanceof Error)) return e;
|
|
9846
|
+
const err = e;
|
|
9847
|
+
const stderr = typeof err.stderr === "string" ? err.stderr.trim() : "";
|
|
9848
|
+
const stdout = typeof err.stdout === "string" ? err.stdout.trim() : "";
|
|
9849
|
+
const cmd = `${file} ${args.join(" ")}`.trim();
|
|
9850
|
+
if (!stderr && !stdout) {
|
|
9851
|
+
err.message = `${cmd} failed with no stderr output (exit ${err.code ?? "?"}) \u2014 likely a transient subprocess/network error. [${err.message}]`;
|
|
9852
|
+
} else if (/^Command failed/i.test(err.message) && stderr) {
|
|
9853
|
+
err.message = `${cmd} failed: ${stderr}`;
|
|
9854
|
+
}
|
|
9855
|
+
return err;
|
|
9856
|
+
}
|
|
9753
9857
|
function planTrainApplyRepoGuard(applyRepo, cwdRepo, rerun) {
|
|
9754
9858
|
if (cwdRepo && cwdRepo.toLowerCase() === applyRepo.toLowerCase()) return { ok: true };
|
|
9755
9859
|
const where = cwdRepo ? `this checkout is ${cwdRepo}` : "this checkout could not be identified";
|
|
@@ -9813,18 +9917,18 @@ async function verifyPublishedRelease(deps, repo, tag, expectedTarget, expectedS
|
|
|
9813
9917
|
if (release.targetCommitish !== expectedTarget) {
|
|
9814
9918
|
throw new Error(`Release ${tag} targetCommitish is ${String(release.targetCommitish || "(missing)")}, expected ${expectedTarget}`);
|
|
9815
9919
|
}
|
|
9816
|
-
const tagSha = requireValue(
|
|
9920
|
+
const tagSha = requireValue(clean2(await deps.run("git", ["rev-parse", `${tag}^{commit}`])), "release tag sha");
|
|
9817
9921
|
if (tagSha !== expectedSha) {
|
|
9818
9922
|
throw new Error(`Release ${tag} tag points at ${tagSha}, expected ${expectedSha}`);
|
|
9819
9923
|
}
|
|
9820
|
-
const branchOut =
|
|
9924
|
+
const branchOut = clean2(await runGitRemoteRead(deps, ["ls-remote", "origin", `refs/heads/${expectedTarget}`]));
|
|
9821
9925
|
const branchSha = requireValue(branchOut.split(/\s+/)[0] ?? "", `origin/${expectedTarget} sha`);
|
|
9822
9926
|
if (branchSha !== expectedSha) {
|
|
9823
9927
|
throw new Error(`origin/${expectedTarget} points at ${branchSha}, expected ${expectedSha}`);
|
|
9824
9928
|
}
|
|
9825
9929
|
}
|
|
9826
9930
|
async function ffOnlyPull(deps, branch) {
|
|
9827
|
-
await deps
|
|
9931
|
+
await runGitRemoteRead(deps, ["pull", "--ff-only", "origin", branch]);
|
|
9828
9932
|
}
|
|
9829
9933
|
var RELEASE_TOLERATED_PATHS = [".gitignore"];
|
|
9830
9934
|
var MERGE_TREE_PREFLIGHT_ATTEMPTS = 3;
|
|
@@ -9878,7 +9982,7 @@ function ensurePositiveCount(out, emptyMessage) {
|
|
|
9878
9982
|
if (count <= 0) throw new Error(emptyMessage);
|
|
9879
9983
|
}
|
|
9880
9984
|
async function remoteBranchExists(deps, branch) {
|
|
9881
|
-
const out =
|
|
9985
|
+
const out = clean2(await runGitRemoteRead(deps, ["ls-remote", "origin", `refs/heads/${branch}`]));
|
|
9882
9986
|
return out.length > 0;
|
|
9883
9987
|
}
|
|
9884
9988
|
async function loadReleaseTrackBranchHints(deps) {
|
|
@@ -9890,10 +9994,10 @@ async function loadReleaseTrackBranchHints(deps) {
|
|
|
9890
9994
|
return { hasDevelopmentBranch, hasMainBranch, hasRcBranch };
|
|
9891
9995
|
}
|
|
9892
9996
|
async function buildTrainApplyContext(deps) {
|
|
9893
|
-
const repo = requireValue(
|
|
9997
|
+
const repo = requireValue(clean2(await deps.run("gh", ["repo", "view", "--json", "nameWithOwner", "--jq", ".nameWithOwner"])), "repo");
|
|
9894
9998
|
const [owner, name] = repo.split("/");
|
|
9895
9999
|
if (!owner || !name) throw new Error(`repo must be owner/name, got ${repo}`);
|
|
9896
|
-
const login = requireValue(
|
|
10000
|
+
const login = requireValue(clean2(await deps.run("gh", ["api", "user", "--jq", ".login"])), "GitHub login");
|
|
9897
10001
|
const verdict = await deps.trainAuthority(repo);
|
|
9898
10002
|
if (!verdict.ok) throw new Error(`${commandAuthorityLabel(owner)}: train authority could not be verified (${verdict.error})`);
|
|
9899
10003
|
if (!verdict.train) {
|
|
@@ -9914,11 +10018,11 @@ async function requireCleanTree(deps) {
|
|
|
9914
10018
|
if (porcelainHasBlockingChanges(status)) throw new Error("working tree must be clean before train apply");
|
|
9915
10019
|
}
|
|
9916
10020
|
async function requireBranch(deps, branch) {
|
|
9917
|
-
const current =
|
|
10021
|
+
const current = clean2(await deps.run("git", ["rev-parse", "--abbrev-ref", "HEAD"]));
|
|
9918
10022
|
if (current !== branch) throw new Error(`must run from ${branch}, currently on ${current || "(unknown)"}`);
|
|
9919
10023
|
}
|
|
9920
10024
|
async function currentBranch(deps) {
|
|
9921
|
-
return
|
|
10025
|
+
return clean2(await deps.run("git", ["rev-parse", "--abbrev-ref", "HEAD"])) || "(unknown)";
|
|
9922
10026
|
}
|
|
9923
10027
|
async function restoreCheckoutAfterRelease(deps, startBranch) {
|
|
9924
10028
|
const status = await deps.run("git", ["status", "--porcelain"]);
|
|
@@ -9961,17 +10065,17 @@ async function restoreCheckoutAfterRelease(deps, startBranch) {
|
|
|
9961
10065
|
}
|
|
9962
10066
|
}
|
|
9963
10067
|
async function withCheckoutRestoredAfterRelease(deps, result, startBranch) {
|
|
9964
|
-
|
|
9965
|
-
|
|
9966
|
-
|
|
9967
|
-
};
|
|
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 } : {} };
|
|
9968
10072
|
}
|
|
9969
10073
|
function normGitPath(p) {
|
|
9970
10074
|
return p.replace(/\\/g, "/").replace(/\/+$/, "").toLowerCase();
|
|
9971
10075
|
}
|
|
9972
10076
|
async function releaseRcBranchLock(deps) {
|
|
9973
10077
|
const porcelain = await deps.run("git", ["worktree", "list", "--porcelain"]);
|
|
9974
|
-
const cwd = normGitPath(
|
|
10078
|
+
const cwd = normGitPath(clean2(await deps.run("git", ["rev-parse", "--show-toplevel"])));
|
|
9975
10079
|
for (const wt of parseWorktreePorcelain(porcelain)) {
|
|
9976
10080
|
if (wt.branch !== "rc") continue;
|
|
9977
10081
|
if (normGitPath(wt.path) === cwd) continue;
|
|
@@ -10038,6 +10142,32 @@ var defaultSleep2 = (ms) => new Promise((resolve5) => setTimeout(resolve5, ms));
|
|
|
10038
10142
|
function resolveSleep(deps) {
|
|
10039
10143
|
return deps.sleep ?? defaultSleep2;
|
|
10040
10144
|
}
|
|
10145
|
+
var GIT_NETWORK_ATTEMPTS = 3;
|
|
10146
|
+
var GIT_NETWORK_RETRY_DELAY_MS = 1e3;
|
|
10147
|
+
function isPersistentGitPushFailure(e) {
|
|
10148
|
+
const msg = `${e instanceof Error ? e.message : String(e)} ${String(e.stderr ?? "")}`;
|
|
10149
|
+
return /non-fast-forward|\[rejected\]|failed to push|protected branch|hook declined|permission denied|could not read Username|authentication failed|Authentication failed|403 Forbidden|GH006|GH013/i.test(msg);
|
|
10150
|
+
}
|
|
10151
|
+
async function runGitWithRetry(deps, args, shouldRetry) {
|
|
10152
|
+
const sleep = resolveSleep(deps);
|
|
10153
|
+
let lastError;
|
|
10154
|
+
for (let attempt = 1; attempt <= GIT_NETWORK_ATTEMPTS; attempt++) {
|
|
10155
|
+
try {
|
|
10156
|
+
return await deps.run("git", args);
|
|
10157
|
+
} catch (e) {
|
|
10158
|
+
lastError = e;
|
|
10159
|
+
if (attempt >= GIT_NETWORK_ATTEMPTS || !shouldRetry(e)) throw e;
|
|
10160
|
+
await sleep(GIT_NETWORK_RETRY_DELAY_MS * attempt);
|
|
10161
|
+
}
|
|
10162
|
+
}
|
|
10163
|
+
throw lastError;
|
|
10164
|
+
}
|
|
10165
|
+
function runGitRemoteRead(deps, args) {
|
|
10166
|
+
return runGitWithRetry(deps, args, () => true);
|
|
10167
|
+
}
|
|
10168
|
+
function runGitPush(deps, args) {
|
|
10169
|
+
return runGitWithRetry(deps, args, (e) => !isPersistentGitPushFailure(e));
|
|
10170
|
+
}
|
|
10041
10171
|
var TRAIN_CHECK_RUNS_JQ = "[.check_runs[]|{name:.name,status:.status,conclusion:.conclusion,startedAt:.started_at}]";
|
|
10042
10172
|
var TRAIN_COMMIT_STATUS_JQ = "[.statuses[]|{context:.context,state:.state}]";
|
|
10043
10173
|
var TRAIN_PROTECTION_CONTEXTS_JQ = "[.contexts[]]";
|
|
@@ -10161,7 +10291,7 @@ async function discoverRequiredCheckContexts(deps, ctx, branch) {
|
|
|
10161
10291
|
return [...contexts];
|
|
10162
10292
|
}
|
|
10163
10293
|
async function findOpenAlignmentPr(deps, ctx) {
|
|
10164
|
-
const out =
|
|
10294
|
+
const out = clean2(await deps.run("gh", ["pr", "list", "--repo", ctx.repo, "--base", "development", "--head", "main", "--state", "open", "--json", "number,url"]));
|
|
10165
10295
|
if (!out) return void 0;
|
|
10166
10296
|
const rows = JSON.parse(out);
|
|
10167
10297
|
const row = rows.find((r) => typeof r.number === "number" && typeof r.url === "string");
|
|
@@ -10189,14 +10319,14 @@ async function rollDevelopmentForward(deps, ctx, tag) {
|
|
|
10189
10319
|
note: `alignment PR already open: ${existing.url} \u2014 land it with \`mmi-cli pr merge ${existing.number} --auto --merge\``
|
|
10190
10320
|
};
|
|
10191
10321
|
}
|
|
10192
|
-
const ahead =
|
|
10322
|
+
const ahead = clean2(await deps.run("git", ["rev-list", "--count", "origin/development..main"]));
|
|
10193
10323
|
if (ahead === "0") {
|
|
10194
10324
|
return { status: "aligned", note: "development already contains the released main; nothing to roll forward" };
|
|
10195
10325
|
}
|
|
10196
10326
|
const body = `Carries the ${tag} release (including the version fold) from \`main\` back to \`development\`.
|
|
10197
10327
|
|
|
10198
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.`;
|
|
10199
|
-
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]));
|
|
10200
10330
|
const number = parsePrNumber(url);
|
|
10201
10331
|
return {
|
|
10202
10332
|
status: "pr-pending",
|
|
@@ -10312,12 +10442,15 @@ Recovery sequence:
|
|
|
10312
10442
|
Do not delete or force-move the pushed tag; rerun the train only after confirming the branch, release, and deploy states above.`
|
|
10313
10443
|
);
|
|
10314
10444
|
}
|
|
10315
|
-
async function
|
|
10316
|
-
const remoteOut = await deps
|
|
10317
|
-
|
|
10445
|
+
async function probeRemoteTag(deps, tag) {
|
|
10446
|
+
const remoteOut = await runGitRemoteRead(deps, ["ls-remote", "origin", `refs/tags/${tag}`]);
|
|
10447
|
+
return clean2(remoteOut).split(/\s+/)[0] || "";
|
|
10448
|
+
}
|
|
10449
|
+
async function ensureTagPushed(deps, tag, sha, probed) {
|
|
10450
|
+
const remoteSha = probed ? probed.remoteSha : await probeRemoteTag(deps, tag);
|
|
10318
10451
|
let localSha = "";
|
|
10319
10452
|
try {
|
|
10320
|
-
localSha =
|
|
10453
|
+
localSha = clean2(await deps.run("git", ["rev-parse", "--verify", `refs/tags/${tag}^{commit}`]));
|
|
10321
10454
|
} catch {
|
|
10322
10455
|
}
|
|
10323
10456
|
if (remoteSha) {
|
|
@@ -10335,12 +10468,15 @@ async function ensureTagPushed(deps, tag, sha) {
|
|
|
10335
10468
|
throw new Error(`local tag ${tag} already points at ${localSha}, not the intended ${sha} \u2014 delete the stale local tag (git tag -d ${tag}) and rerun`);
|
|
10336
10469
|
}
|
|
10337
10470
|
if (!localSha) await deps.run("git", ["tag", tag, sha]);
|
|
10338
|
-
await deps
|
|
10471
|
+
await runGitPush(deps, ["push", "origin", tag]);
|
|
10339
10472
|
return { note: `tag ${tag} pushed at ${sha.slice(0, 7)}`, pushed: true };
|
|
10340
10473
|
}
|
|
10341
|
-
|
|
10342
|
-
|
|
10343
|
-
|
|
10474
|
+
function probeRcResumeTags(deps, base) {
|
|
10475
|
+
return runGitRemoteRead(deps, ["ls-remote", "--tags", "origin", `refs/tags/${base}-rc.*`]);
|
|
10476
|
+
}
|
|
10477
|
+
async function resolveRcResumeTag(deps, base, sha, probed) {
|
|
10478
|
+
const out = probed ?? await runGitRemoteRead(deps, ["ls-remote", "--tags", "origin", `refs/tags/${base}-rc.*`]);
|
|
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));
|
|
10344
10480
|
const unique = [...new Set(onSha)];
|
|
10345
10481
|
if (unique.length === 0) return { tag: null };
|
|
10346
10482
|
const rcNum = (t) => Number.parseInt(t.replace(/^.*-rc\./, ""), 10);
|
|
@@ -10536,7 +10672,7 @@ async function preflightMergeToMain(deps, deployModel, remoteRef, blockingPrefix
|
|
|
10536
10672
|
async function executeMergeToMain(deps, sourceRef, mergeLabel, tolerated, predicted, preFold) {
|
|
10537
10673
|
await deps.run("git", ["checkout", "main"]);
|
|
10538
10674
|
await ffOnlyPull(deps, "main");
|
|
10539
|
-
preFold.mainSha =
|
|
10675
|
+
preFold.mainSha = clean2(await deps.run("git", ["rev-parse", "main"]));
|
|
10540
10676
|
if (predicted.length === 0) {
|
|
10541
10677
|
await deps.run("git", ["merge", sourceRef, "--no-edit"]);
|
|
10542
10678
|
} else {
|
|
@@ -10547,8 +10683,8 @@ async function probeFoldFailureState(deps) {
|
|
|
10547
10683
|
const branch = await currentBranch(deps);
|
|
10548
10684
|
const mergeInProgress = await deps.run("git", ["rev-parse", "-q", "--verify", "MERGE_HEAD"]).then(() => true).catch(() => false);
|
|
10549
10685
|
const dirty = porcelainHasBlockingChanges(await deps.run("git", ["status", "--porcelain"]).catch(() => ""));
|
|
10550
|
-
const mainSha = await deps.run("git", ["rev-parse", "main"]).then(
|
|
10551
|
-
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(() => "");
|
|
10552
10688
|
const originIsAncestorOfMain = Boolean(mainSha) && Boolean(originMainSha) ? await deps.run("git", ["merge-base", "--is-ancestor", "origin/main", "main"]).then(() => true).catch(() => false) : false;
|
|
10553
10689
|
return { branch, mergeInProgress, dirty, mainSha, originMainSha, originIsAncestorOfMain };
|
|
10554
10690
|
}
|
|
@@ -10590,6 +10726,49 @@ Recovery sequence:
|
|
|
10590
10726
|
${steps.join("\n")}`
|
|
10591
10727
|
);
|
|
10592
10728
|
}
|
|
10729
|
+
async function recoverFailedRcand(deps, cause, preRcSha) {
|
|
10730
|
+
const causeMessage = cause instanceof Error ? cause.message : String(cause);
|
|
10731
|
+
const branch = await currentBranch(deps).catch(() => "");
|
|
10732
|
+
const mergeInProgress = await deps.run("git", ["rev-parse", "-q", "--verify", "MERGE_HEAD"]).then(() => true).catch(() => false);
|
|
10733
|
+
if (mergeInProgress) {
|
|
10734
|
+
try {
|
|
10735
|
+
await deps.run("git", ["merge", "--abort"]);
|
|
10736
|
+
} catch (e) {
|
|
10737
|
+
return new Error(
|
|
10738
|
+
`${causeMessage}
|
|
10739
|
+
|
|
10740
|
+
rcand failed with an in-progress merge on ${branch || "(unknown branch)"}; automatic \`git merge --abort\` failed: ${e instanceof Error ? e.message : String(e)}. Nothing was pushed to origin.
|
|
10741
|
+
Recovery sequence:
|
|
10742
|
+
1. git merge --abort
|
|
10743
|
+
2. git checkout development
|
|
10744
|
+
3. git branch -f rc origin/rc`
|
|
10745
|
+
);
|
|
10746
|
+
}
|
|
10747
|
+
}
|
|
10748
|
+
const rcSha = await deps.run("git", ["rev-parse", "rc"]).then(clean2).catch(() => "");
|
|
10749
|
+
const rcDescends = Boolean(preRcSha) && Boolean(rcSha) ? await deps.run("git", ["merge-base", "--is-ancestor", preRcSha, "rc"]).then(() => true).catch(() => false) : false;
|
|
10750
|
+
if (branch === "rc" && rcDescends) {
|
|
10751
|
+
try {
|
|
10752
|
+
await deps.run("git", ["reset", "--hard", preRcSha]);
|
|
10753
|
+
await deps.run("git", ["checkout", "development"]);
|
|
10754
|
+
return new Error(
|
|
10755
|
+
`${causeMessage}
|
|
10756
|
+
|
|
10757
|
+
rcand failed after \`git checkout rc\`; local rc was reset to its pre-merge state (${shaLabel(preRcSha)}) and the checkout returned to development. Nothing was pushed to origin. Rerun \`mmi-cli rcand --apply\`.`
|
|
10758
|
+
);
|
|
10759
|
+
} catch {
|
|
10760
|
+
}
|
|
10761
|
+
}
|
|
10762
|
+
return new Error(
|
|
10763
|
+
`${causeMessage}
|
|
10764
|
+
|
|
10765
|
+
rcand failed leaving the checkout on ${branch || "(unknown)"} (pre-merge rc=${preRcSha ? shaLabel(preRcSha) : "not captured"}). Nothing was pushed to origin.
|
|
10766
|
+
Recovery sequence:
|
|
10767
|
+
1. git checkout development
|
|
10768
|
+
2. git branch -f rc origin/rc # discard the unpushed local rc merge
|
|
10769
|
+
3. mmi-cli rcand --apply`
|
|
10770
|
+
);
|
|
10771
|
+
}
|
|
10593
10772
|
async function recoverFailedFold(deps, cause, startBranch, preFoldMainSha) {
|
|
10594
10773
|
const causeMessage = cause instanceof Error ? cause.message : String(cause);
|
|
10595
10774
|
const probe = await probeFoldFailureState(deps);
|
|
@@ -10641,8 +10820,13 @@ async function runFoldStage(deps, startBranch, preFold, fn) {
|
|
|
10641
10820
|
throw await recoverFailedFold(deps, e, startBranch, preFold.mainSha);
|
|
10642
10821
|
}
|
|
10643
10822
|
}
|
|
10644
|
-
async function completeMainRelease(deps, ctx, meta, deployModel, watch, options, tag, releaseSha) {
|
|
10645
|
-
|
|
10823
|
+
async function completeMainRelease(deps, ctx, meta, deployModel, watch, options, tag, releaseSha, startBranch, preFold, tagProbe) {
|
|
10824
|
+
let tagPush;
|
|
10825
|
+
try {
|
|
10826
|
+
tagPush = await ensureTagPushed(deps, tag, releaseSha, tagProbe);
|
|
10827
|
+
} catch (e) {
|
|
10828
|
+
throw await recoverFailedFold(deps, e, startBranch, preFold.mainSha);
|
|
10829
|
+
}
|
|
10646
10830
|
const tagPushSince = (deps.now ?? Date.now)();
|
|
10647
10831
|
const requiredChecks = await discoverRequiredCheckContexts(deps, ctx, "main");
|
|
10648
10832
|
let checks;
|
|
@@ -10651,8 +10835,8 @@ async function completeMainRelease(deps, ctx, meta, deployModel, watch, options,
|
|
|
10651
10835
|
} catch (e) {
|
|
10652
10836
|
throw partialTrainRecoveryError(e, { repo: ctx.repo, tag, stage: "main" });
|
|
10653
10837
|
}
|
|
10654
|
-
await deps
|
|
10655
|
-
const releaseUrl =
|
|
10838
|
+
await runGitPush(deps, ["push", "origin", "main"]);
|
|
10839
|
+
const releaseUrl = clean2(await deps.run("gh", ["release", "create", tag, "--target", "main", "--generate-notes", "--latest", "--repo", ctx.repo])) || void 0;
|
|
10656
10840
|
await verifyPublishedRelease(deps, ctx.repo, tag, "main", releaseSha);
|
|
10657
10841
|
const announceNote = deps.announce ? (await deps.announce({ repo: ctx.repo, tag, summaryFile: options.announceSummaryFile })).note : void 0;
|
|
10658
10842
|
const autoRunSince = (deps.now ?? Date.now)();
|
|
@@ -10690,16 +10874,26 @@ async function runTrainApplyPipeline(mode, input) {
|
|
|
10690
10874
|
"nothing to promote: origin/development is not ahead of origin/rc"
|
|
10691
10875
|
);
|
|
10692
10876
|
const deployModel2 = await preflight(deps, ctx, "rc", meta);
|
|
10693
|
-
const releaseBase = requireValue(
|
|
10877
|
+
const releaseBase = requireValue(clean2(await deps.run("node", ["scripts/next-version.mjs", "cycle"])), "release base");
|
|
10694
10878
|
await releaseRcBranchLock(deps);
|
|
10879
|
+
const rcTagProbe = await probeRcResumeTags(deps, releaseBase);
|
|
10695
10880
|
await deps.run("git", ["checkout", "rc"]);
|
|
10696
10881
|
await ffOnlyPull(deps, "rc");
|
|
10697
|
-
await deps.run("git", ["
|
|
10698
|
-
|
|
10699
|
-
|
|
10700
|
-
|
|
10882
|
+
const preRcSha = requireValue(clean2(await deps.run("git", ["rev-parse", "rc"])), "pre-merge rc sha");
|
|
10883
|
+
let rcSha;
|
|
10884
|
+
let resume;
|
|
10885
|
+
let tag2;
|
|
10886
|
+
let tagPush;
|
|
10887
|
+
try {
|
|
10888
|
+
await deps.run("git", ["merge", "development", "--no-edit"]);
|
|
10889
|
+
rcSha = requireValue(clean2(await deps.run("git", ["rev-parse", "rc"])), "rc sha");
|
|
10890
|
+
resume = await resolveRcResumeTag(deps, releaseBase, rcSha, rcTagProbe);
|
|
10891
|
+
tag2 = resume.tag ?? requireValue(clean2(await deps.run("node", ["scripts/next-version.mjs", "rc"])), "rc tag");
|
|
10892
|
+
tagPush = await ensureTagPushed(deps, tag2, rcSha);
|
|
10893
|
+
} catch (e) {
|
|
10894
|
+
throw await recoverFailedRcand(deps, e, preRcSha);
|
|
10895
|
+
}
|
|
10701
10896
|
const resumeNote = resume.tag ? resume.note : void 0;
|
|
10702
|
-
const tagPush = await ensureTagPushed(deps, tag2, rcSha);
|
|
10703
10897
|
const tagPushSince = (deps.now ?? Date.now)();
|
|
10704
10898
|
const requiredChecks = await discoverRequiredCheckContexts(deps, ctx, "rc");
|
|
10705
10899
|
let checks2;
|
|
@@ -10709,7 +10903,7 @@ async function runTrainApplyPipeline(mode, input) {
|
|
|
10709
10903
|
throw partialTrainRecoveryError(e, { repo: ctx.repo, tag: tag2, stage: "rc" });
|
|
10710
10904
|
}
|
|
10711
10905
|
const autoRunSince = (deps.now ?? Date.now)();
|
|
10712
|
-
await deps
|
|
10906
|
+
await runGitPush(deps, ["push", "origin", "rc"]);
|
|
10713
10907
|
const d2 = await dispatchDeploy(deps, ctx, "rc", "rc", deployModel2, watch, autoRunSince, rcSha);
|
|
10714
10908
|
return { ...ctx, command, stage: "rc", ref: "rc", tag: tag2, deployModel: deployModel2, promoted: true, checks: checks2, resumeNote, dispatch: d2.note, runId: d2.runId, runUrl: d2.runUrl, workflowRuns: d2.workflowRuns, deployStatus: d2.deployStatus };
|
|
10715
10909
|
}
|
|
@@ -10732,8 +10926,8 @@ async function runTrainApplyPipeline(mode, input) {
|
|
|
10732
10926
|
"nothing to release: origin/development is not ahead of origin/main"
|
|
10733
10927
|
);
|
|
10734
10928
|
const deployModel2 = await preflight(deps, ctx, "main", meta);
|
|
10735
|
-
const tag2 = requireValue(
|
|
10736
|
-
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"])) : "";
|
|
10737
10931
|
const { foldPaths: foldPaths2, tolerated: tolerated2, predicted: predicted2 } = await preflightMergeToMain(
|
|
10738
10932
|
deps,
|
|
10739
10933
|
deployModel2,
|
|
@@ -10741,14 +10935,15 @@ async function runTrainApplyPipeline(mode, input) {
|
|
|
10741
10935
|
"development -> main merge would conflict on untolerated path(s)",
|
|
10742
10936
|
"The train is misaligned: reconcile main and development via an approved alignment PR, then rerun release."
|
|
10743
10937
|
);
|
|
10938
|
+
const tagProbe2 = { remoteSha: await probeRemoteTag(deps, tag2) };
|
|
10744
10939
|
const preFold2 = {};
|
|
10745
10940
|
const { versionFold: versionFold2, releaseSha: releaseSha2 } = await runFoldStage(deps, "development", preFold2, async () => {
|
|
10746
10941
|
await executeMergeToMain(deps, "development", "development -> main", tolerated2, predicted2, preFold2);
|
|
10747
10942
|
const versionFold3 = await foldReleaseVersion(deps, deployModel2, tag2, foldPaths2);
|
|
10748
|
-
const releaseSha3 = requireValue(
|
|
10943
|
+
const releaseSha3 = requireValue(clean2(await deps.run("git", ["rev-parse", "main"])), "release sha");
|
|
10749
10944
|
return { versionFold: versionFold3, releaseSha: releaseSha3 };
|
|
10750
10945
|
});
|
|
10751
|
-
const { checks: checks2, releaseUrl: releaseUrl2, announceNote: announceNote2, dispatch: d2 } = await completeMainRelease(deps, ctx, meta, deployModel2, watch, options, tag2, releaseSha2);
|
|
10946
|
+
const { checks: checks2, releaseUrl: releaseUrl2, announceNote: announceNote2, dispatch: d2 } = await completeMainRelease(deps, ctx, meta, deployModel2, watch, options, tag2, releaseSha2, "development", preFold2, tagProbe2);
|
|
10752
10947
|
const devRollForward2 = await rollDevelopmentForward(deps, ctx, tag2);
|
|
10753
10948
|
if (directTrack) {
|
|
10754
10949
|
return {
|
|
@@ -10823,16 +11018,17 @@ async function runTrainApplyPipeline(mode, input) {
|
|
|
10823
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].`
|
|
10824
11019
|
);
|
|
10825
11020
|
}
|
|
10826
|
-
const releasedRcSha =
|
|
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");
|
|
11023
|
+
const tagProbe = { remoteSha: await probeRemoteTag(deps, tag) };
|
|
10827
11024
|
const preFold = {};
|
|
10828
|
-
const {
|
|
11025
|
+
const { versionFold, releaseSha } = await runFoldStage(deps, "rc", preFold, async () => {
|
|
10829
11026
|
await executeMergeToMain(deps, "rc", "rc -> main", tolerated, predicted, preFold);
|
|
10830
|
-
const
|
|
10831
|
-
const
|
|
10832
|
-
|
|
10833
|
-
return { tag: tag2, versionFold: versionFold2, releaseSha: releaseSha2 };
|
|
11027
|
+
const versionFold2 = await foldReleaseVersion(deps, deployModel, tag, foldPaths);
|
|
11028
|
+
const releaseSha2 = requireValue(clean2(await deps.run("git", ["rev-parse", "main"])), "release sha");
|
|
11029
|
+
return { versionFold: versionFold2, releaseSha: releaseSha2 };
|
|
10834
11030
|
});
|
|
10835
|
-
const { checks, releaseUrl, announceNote, dispatch: d } = await completeMainRelease(deps, ctx, meta, deployModel, watch, options, tag, releaseSha);
|
|
11031
|
+
const { checks, releaseUrl, announceNote, dispatch: d } = await completeMainRelease(deps, ctx, meta, deployModel, watch, options, tag, releaseSha, "rc", preFold, tagProbe);
|
|
10836
11032
|
const retirement = await retireRcRuntime(deps, ctx, deployModel, d.deployStatus, releasedRcSha);
|
|
10837
11033
|
const devRollForward = await rollDevelopmentForward(deps, ctx, tag);
|
|
10838
11034
|
const rcAlignment = await pushRcAlignment(deps);
|
|
@@ -10866,7 +11062,7 @@ async function runTrainApply(command, deps, options = {}) {
|
|
|
10866
11062
|
const watch = options.watch ?? false;
|
|
10867
11063
|
const ctx = await buildTrainApplyContext(deps);
|
|
10868
11064
|
await requireCleanTree(deps);
|
|
10869
|
-
await deps
|
|
11065
|
+
await runGitRemoteRead(deps, ["fetch", "origin"]);
|
|
10870
11066
|
const meta = requireProjectMetaForTrain(await loadProjectMeta(deps, ctx), ctx.repo);
|
|
10871
11067
|
const branchHints = await loadReleaseTrackBranchHints(deps);
|
|
10872
11068
|
const directTrack = isHubControlRepo(ctx.repo) || resolveReleaseTrack(meta, branchHints) === "direct";
|
|
@@ -10948,7 +11144,7 @@ async function retireRcRuntime(deps, ctx, model, deployStatus, releasedRcSha) {
|
|
|
10948
11144
|
}
|
|
10949
11145
|
try {
|
|
10950
11146
|
await deps.run("git", ["fetch", "origin", "rc"]);
|
|
10951
|
-
const rcNow =
|
|
11147
|
+
const rcNow = clean2(await deps.run("git", ["rev-parse", "origin/rc"]));
|
|
10952
11148
|
if (rcNow !== releasedRcSha) {
|
|
10953
11149
|
return {
|
|
10954
11150
|
status: "skipped",
|
|
@@ -10979,7 +11175,7 @@ async function runTenantRedeploy(deps, options) {
|
|
|
10979
11175
|
const repo = options.repo;
|
|
10980
11176
|
const [owner, name] = repo.split("/");
|
|
10981
11177
|
if (!owner || !name) throw new Error(`repo must be owner/name, got ${repo}`);
|
|
10982
|
-
const login = requireValue(
|
|
11178
|
+
const login = requireValue(clean2(await deps.run("gh", ["api", "user", "--jq", ".login"])), "GitHub login");
|
|
10983
11179
|
const verdict = await deps.trainAuthority(repo);
|
|
10984
11180
|
if (!verdict.ok) throw new Error(`${commandAuthorityLabel(owner)}: train authority could not be verified (${verdict.error})`);
|
|
10985
11181
|
if (!verdict.train) throw new Error(`${commandAuthorityLabel(owner)}: @${login} is ${verdict.role} \u2014 no train authority on ${repo}`);
|
|
@@ -11219,7 +11415,7 @@ var HOTFIX_RUN_FIND_ATTEMPTS = 10;
|
|
|
11219
11415
|
var HOTFIX_RUN_FIND_DELAY_MS = 15e3;
|
|
11220
11416
|
var HOTFIX_VERIFY_ATTEMPTS = 5;
|
|
11221
11417
|
var HOTFIX_VERIFY_RETRY_MS = 6e3;
|
|
11222
|
-
function
|
|
11418
|
+
function clean3(out) {
|
|
11223
11419
|
return out.trim();
|
|
11224
11420
|
}
|
|
11225
11421
|
function sleeper(deps) {
|
|
@@ -11277,7 +11473,7 @@ async function resolveHotfixSource(deps, ctx, from) {
|
|
|
11277
11473
|
if (!sha2) throw new Error(`PR #${num} has no merge commit recorded \u2014 name the commit SHA explicitly`);
|
|
11278
11474
|
return { sha: sha2, label: `PR #${num} (${sha2.slice(0, 7)})` };
|
|
11279
11475
|
}
|
|
11280
|
-
const sha =
|
|
11476
|
+
const sha = clean3(await deps.run("git", ["rev-parse", "--verify", `${from}^{commit}`]));
|
|
11281
11477
|
if (!sha) throw new Error(`could not resolve commit ${from}`);
|
|
11282
11478
|
return { sha, label: sha.slice(0, 7) };
|
|
11283
11479
|
}
|
|
@@ -11305,7 +11501,7 @@ async function runHotfixStart(deps, options) {
|
|
|
11305
11501
|
};
|
|
11306
11502
|
}
|
|
11307
11503
|
const { sha, label } = await resolveHotfixSource(deps, ctx, options.from);
|
|
11308
|
-
const remoteBranch =
|
|
11504
|
+
const remoteBranch = clean3(await deps.run("git", ["ls-remote", "origin", `refs/heads/${branch}`]));
|
|
11309
11505
|
if (remoteBranch) {
|
|
11310
11506
|
await deps.run("git", ["checkout", branch]);
|
|
11311
11507
|
await deps.run("git", ["pull", "--ff-only", "origin", branch]);
|
|
@@ -11336,7 +11532,7 @@ async function runHotfixStart(deps, options) {
|
|
|
11336
11532
|
await deps.run("git", ["push", "-u", "origin", branch]);
|
|
11337
11533
|
}
|
|
11338
11534
|
const bumpNote = deployModel === "hub-serverless" ? " with the locked distribution bump" : "";
|
|
11339
|
-
const prUrl =
|
|
11535
|
+
const prUrl = clean3(await deps.run("gh", [
|
|
11340
11536
|
"pr",
|
|
11341
11537
|
"create",
|
|
11342
11538
|
"--repo",
|
|
@@ -11480,7 +11676,7 @@ async function runHotfixRelease(deps, versionInput, options = {}) {
|
|
|
11480
11676
|
}
|
|
11481
11677
|
let verifyNote;
|
|
11482
11678
|
if (deployModel === "hub-serverless") {
|
|
11483
|
-
const previousRef =
|
|
11679
|
+
const previousRef = clean3(await deps.run("git", ["rev-parse", "--abbrev-ref", "HEAD"]));
|
|
11484
11680
|
const publishSucceeded = runs.find((r) => r.workflow === "publish.yml")?.conclusion === "success";
|
|
11485
11681
|
try {
|
|
11486
11682
|
await deps.run("git", ["-c", "advice.detachedHead=false", "checkout", tag]);
|
|
@@ -11567,9 +11763,9 @@ async function runHotfixStatus(deps, versionInput) {
|
|
|
11567
11763
|
return { ...ctx, command: "hotfix-status", ...facts, ...deriveHotfixState(facts), warnings };
|
|
11568
11764
|
}
|
|
11569
11765
|
async function gatherHotfixFacts(deps, ctx, tag, version) {
|
|
11570
|
-
const branchExists = Boolean(
|
|
11766
|
+
const branchExists = Boolean(clean3(await deps.run("git", ["ls-remote", "origin", `refs/heads/${hotfixBranch(tag)}`])));
|
|
11571
11767
|
const pr2 = await findHotfixPr(deps, ctx, tag);
|
|
11572
|
-
const remoteTag =
|
|
11768
|
+
const remoteTag = clean3(await deps.run("git", ["ls-remote", "origin", `refs/tags/${tag}`]));
|
|
11573
11769
|
const tagPushed = Boolean(remoteTag);
|
|
11574
11770
|
const tagSha = remoteTag.split(/\s+/)[0] || "";
|
|
11575
11771
|
let releaseExists = false;
|
|
@@ -11603,7 +11799,7 @@ async function gatherHotfixFacts(deps, ctx, tag, version) {
|
|
|
11603
11799
|
}
|
|
11604
11800
|
}
|
|
11605
11801
|
}
|
|
11606
|
-
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");
|
|
11607
11803
|
return { tag, version, branchExists, pr: pr2 ? { number: pr2.number, state: pr2.state, url: pr2.url } : null, tagPushed, releaseExists, runs, npmVersion };
|
|
11608
11804
|
}
|
|
11609
11805
|
function compareHotfixVersions(a, b) {
|
|
@@ -11638,7 +11834,7 @@ async function findInFlightHotfixVersion(deps, ctx, latestMainTag) {
|
|
|
11638
11834
|
const m = typeof row.headRefName === "string" && /^hotfix\/(v\d+\.\d+\.\d+)/.exec(row.headRefName);
|
|
11639
11835
|
if (m) tags.add(m[1]);
|
|
11640
11836
|
}
|
|
11641
|
-
const branchOut =
|
|
11837
|
+
const branchOut = clean3(await deps.run("git", ["ls-remote", "origin", "refs/heads/hotfix/v*"]));
|
|
11642
11838
|
for (const line of branchOut.split("\n").filter(Boolean)) {
|
|
11643
11839
|
const ref = line.split(/\s+/)[1] ?? "";
|
|
11644
11840
|
const m = /^refs\/heads\/hotfix\/(v\d+\.\d+\.\d+)/.exec(ref);
|
|
@@ -12130,7 +12326,7 @@ function renderAccessReport(report) {
|
|
|
12130
12326
|
}
|
|
12131
12327
|
|
|
12132
12328
|
// src/bootstrap-verify.ts
|
|
12133
|
-
var
|
|
12329
|
+
var TRAIN_BRANCHES2 = ["development", "rc", "main"];
|
|
12134
12330
|
var requiredDocs = ["README.md", "architecture.md"];
|
|
12135
12331
|
var requiredIssueTemplates = [
|
|
12136
12332
|
".github/ISSUE_TEMPLATE/bug.yml",
|
|
@@ -12308,7 +12504,7 @@ async function verifyBootstrap(repo, repoClass, deps, releaseTrack) {
|
|
|
12308
12504
|
detail: hasPushAllowlist(protection) ? void 0 : "restrictions.users is empty or unset"
|
|
12309
12505
|
});
|
|
12310
12506
|
}
|
|
12311
|
-
const strayTrainBranches =
|
|
12507
|
+
const strayTrainBranches = TRAIN_BRANCHES2.filter((b) => !branchesWanted.includes(b) && branchNames.has(b));
|
|
12312
12508
|
checks.push({
|
|
12313
12509
|
ok: strayTrainBranches.length === 0,
|
|
12314
12510
|
label: "no stray train branch for this track",
|
|
@@ -12783,13 +12979,13 @@ var WIKI_STAMP_FILE = ".wiki-state.json";
|
|
|
12783
12979
|
function redactWikiSecrets(msg) {
|
|
12784
12980
|
return msg.replace(/x-access-token:[^@\s]+@/gi, "x-access-token:***@").replace(/(AUTHORIZATION:\s*basic\s+)[A-Za-z0-9+/=]+/gi, "$1***");
|
|
12785
12981
|
}
|
|
12982
|
+
function wikiAuthArgs(token) {
|
|
12983
|
+
return ["-c", `http.extraheader=AUTHORIZATION: basic ${Buffer.from(`x-access-token:${token}`).toString("base64")}`];
|
|
12984
|
+
}
|
|
12786
12985
|
function publishWikiViaGit(deps, opts) {
|
|
12787
12986
|
const dir = deps.mkdtemp();
|
|
12788
12987
|
const url = `https://github.com/${opts.repo}.wiki.git`;
|
|
12789
|
-
const authArgs =
|
|
12790
|
-
"-c",
|
|
12791
|
-
`http.extraheader=AUTHORIZATION: basic ${Buffer.from(`x-access-token:${opts.token}`).toString("base64")}`
|
|
12792
|
-
];
|
|
12988
|
+
const authArgs = wikiAuthArgs(opts.token);
|
|
12793
12989
|
try {
|
|
12794
12990
|
deps.gitRun(["init", "-q"], dir);
|
|
12795
12991
|
deps.gitRun(["remote", "add", "origin", url], dir);
|
|
@@ -12844,12 +13040,37 @@ function publishWikiViaGit(deps, opts) {
|
|
|
12844
13040
|
deps.rm(dir);
|
|
12845
13041
|
}
|
|
12846
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
|
+
}
|
|
12847
13067
|
function realWikiGitPublishDeps() {
|
|
12848
13068
|
return {
|
|
12849
13069
|
mkdtemp: () => (0, import_node_fs13.mkdtempSync)((0, import_node_path14.join)((0, import_node_os5.tmpdir)(), "wiki-")),
|
|
12850
13070
|
readdir: (dir) => (0, import_node_fs13.readdirSync)(dir),
|
|
12851
13071
|
copyFile: (src, dest) => (0, import_node_fs13.copyFileSync)(src, dest),
|
|
12852
13072
|
fileExists: (p) => (0, import_node_fs13.existsSync)(p),
|
|
13073
|
+
readFile: (p) => (0, import_node_fs13.readFileSync)(p, "utf8"),
|
|
12853
13074
|
rm: (p) => (0, import_node_fs13.rmSync)(p, { recursive: true, force: true }),
|
|
12854
13075
|
gitProbe: (args, cwd) => {
|
|
12855
13076
|
try {
|
|
@@ -12867,6 +13088,124 @@ function realWikiGitPublishDeps() {
|
|
|
12867
13088
|
};
|
|
12868
13089
|
}
|
|
12869
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
|
+
|
|
12870
13209
|
// src/project-readiness.ts
|
|
12871
13210
|
function stagesForTrack(meta) {
|
|
12872
13211
|
return branchesForTrack(resolveReleaseTrack(meta)).map((b) => b === "development" ? "dev" : b);
|
|
@@ -13659,6 +13998,11 @@ function parseFofuEnabledVar(raw) {
|
|
|
13659
13998
|
if (raw === "false") return false;
|
|
13660
13999
|
throw new Error("project set: fofuEnabled must be true or false");
|
|
13661
14000
|
}
|
|
14001
|
+
function parseRuntimeVaultOnlyVar(raw) {
|
|
14002
|
+
if (raw === "true") return true;
|
|
14003
|
+
if (raw === "false") return false;
|
|
14004
|
+
throw new Error("project set: runtimeVaultOnly must be true or false");
|
|
14005
|
+
}
|
|
13662
14006
|
function parseConsumesDesignSystemVar(raw) {
|
|
13663
14007
|
if (raw === "fofu") return raw;
|
|
13664
14008
|
throw new Error('project set: consumesDesignSystem must be "fofu"');
|
|
@@ -13736,6 +14080,7 @@ var SETTABLE_VAR_KEYS = [
|
|
|
13736
14080
|
"dsManifestPath",
|
|
13737
14081
|
"fofuEnabled",
|
|
13738
14082
|
"consumesDesignSystem",
|
|
14083
|
+
"runtimeVaultOnly",
|
|
13739
14084
|
"requiredGcpApis",
|
|
13740
14085
|
"requiredRuntimeSecrets",
|
|
13741
14086
|
"requiredBuildSecrets",
|
|
@@ -13758,6 +14103,7 @@ var SETTABLE_VAR_HINTS = {
|
|
|
13758
14103
|
dsManifestPath: "relative path to a package.json, e.g. web/package.json",
|
|
13759
14104
|
fofuEnabled: "true|false",
|
|
13760
14105
|
consumesDesignSystem: '"fofu"',
|
|
14106
|
+
runtimeVaultOnly: "true|false",
|
|
13761
14107
|
repos: 'JSON array, e.g. ["mutmutco/mm-foo"]',
|
|
13762
14108
|
oauth: "JSON {subdomains,domains,callbackPath,fofuSubdomain}",
|
|
13763
14109
|
requiredGcpApis: "comma-string",
|
|
@@ -13839,6 +14185,8 @@ function buildProjectSetPatch(input) {
|
|
|
13839
14185
|
patch[key] = parsePublishRequiredVar(raw);
|
|
13840
14186
|
} else if (key === "fofuEnabled") {
|
|
13841
14187
|
patch[key] = parseFofuEnabledVar(raw);
|
|
14188
|
+
} else if (key === "runtimeVaultOnly") {
|
|
14189
|
+
patch[key] = parseRuntimeVaultOnlyVar(raw);
|
|
13842
14190
|
} else if (key === "consumesDesignSystem") {
|
|
13843
14191
|
patch[key] = parseConsumesDesignSystemVar(raw);
|
|
13844
14192
|
} else if (key === "publishDir") {
|
|
@@ -13876,22 +14224,22 @@ var DEPLOY_STAGES = ["dev", "rc", "main"];
|
|
|
13876
14224
|
var DEPLOY_DOMAIN_RE = /^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)+$/;
|
|
13877
14225
|
var DEPLOY_SHELL_SAFE_RE = /^[A-Za-z0-9./:_?&=%-]*$/;
|
|
13878
14226
|
function buildSetDeployPatch(slug, input) {
|
|
13879
|
-
const
|
|
13880
|
-
const stage2 =
|
|
14227
|
+
const clean4 = (v) => typeof v === "string" && v.trim() !== "" ? v.trim() : void 0;
|
|
14228
|
+
const stage2 = clean4(input.stage);
|
|
13881
14229
|
if (!stage2 || !DEPLOY_STAGES.includes(stage2)) {
|
|
13882
14230
|
throw new Error(`project set-deploy: --stage must be one of: ${DEPLOY_STAGES.join(", ")}`);
|
|
13883
14231
|
}
|
|
13884
|
-
const substrate =
|
|
14232
|
+
const substrate = clean4(input.substrate) ?? "hetzner-ssh";
|
|
13885
14233
|
if (!DEPLOY_SUBSTRATES.includes(substrate)) {
|
|
13886
14234
|
throw new Error(`project set-deploy: --substrate must be one of: ${DEPLOY_SUBSTRATES.join(", ")}`);
|
|
13887
14235
|
}
|
|
13888
|
-
const sshHost =
|
|
14236
|
+
const sshHost = clean4(input.sshHost);
|
|
13889
14237
|
if (substrate === "hetzner-ssh" && !sshHost) {
|
|
13890
14238
|
throw new Error("project set-deploy: hetzner-ssh requires --ssh-host");
|
|
13891
14239
|
}
|
|
13892
|
-
const sshUser =
|
|
13893
|
-
const deployPath =
|
|
13894
|
-
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;
|
|
13895
14243
|
for (const [label, v] of [["--ssh-host", sshHost], ["--ssh-user", sshUser], ["--deploy-path", deployPath], ["--service", serviceName]]) {
|
|
13896
14244
|
if (v !== void 0 && !DEPLOY_SHELL_SAFE_RE.test(v)) throw new Error(`project set-deploy: ${label} contains unsafe characters`);
|
|
13897
14245
|
}
|
|
@@ -13900,9 +14248,9 @@ function buildSetDeployPatch(slug, input) {
|
|
|
13900
14248
|
port = Number(input.port);
|
|
13901
14249
|
if (!Number.isInteger(port) || port < 1 || port > 65535) throw new Error("project set-deploy: --port must be an integer 1..65535");
|
|
13902
14250
|
}
|
|
13903
|
-
const domain =
|
|
14251
|
+
const domain = clean4(input.domain);
|
|
13904
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)}`);
|
|
13905
|
-
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);
|
|
13906
14254
|
for (const a of aliases) {
|
|
13907
14255
|
if (!DEPLOY_DOMAIN_RE.test(a)) throw new Error(`project set-deploy: --alias must be a DNS hostname, got ${JSON.stringify(a)}`);
|
|
13908
14256
|
}
|
|
@@ -14809,8 +15157,8 @@ async function secretsUse(deps, key, opts) {
|
|
|
14809
15157
|
}
|
|
14810
15158
|
|
|
14811
15159
|
// src/secrets-commands.ts
|
|
14812
|
-
var
|
|
14813
|
-
var
|
|
15160
|
+
var import_node_fs15 = require("node:fs");
|
|
15161
|
+
var import_node_path16 = require("node:path");
|
|
14814
15162
|
var RAILS_CREDENTIALS_DECRYPT_TIMEOUT_MS = 3e4;
|
|
14815
15163
|
var DEFAULT_RAILS_CREDENTIALS_FILE = "config/credentials.yml.enc";
|
|
14816
15164
|
var DEFAULT_RAILS_MASTER_KEY_FILE = "config/master.key";
|
|
@@ -14818,18 +15166,18 @@ function collectMap(value, previous = []) {
|
|
|
14818
15166
|
return [...previous, value];
|
|
14819
15167
|
}
|
|
14820
15168
|
async function decryptRailsCredentials(input) {
|
|
14821
|
-
const appDir = (0,
|
|
15169
|
+
const appDir = (0, import_node_path16.resolve)(input.appDir ?? process.cwd());
|
|
14822
15170
|
const credentialsFile = input.credentialsFile ?? DEFAULT_RAILS_CREDENTIALS_FILE;
|
|
14823
15171
|
const masterKeyFile = input.masterKeyFile ?? DEFAULT_RAILS_MASTER_KEY_FILE;
|
|
14824
|
-
const credentialsPath = (0,
|
|
14825
|
-
const masterKeyPath = (0,
|
|
15172
|
+
const credentialsPath = (0, import_node_path16.resolve)(appDir, credentialsFile);
|
|
15173
|
+
const masterKeyPath = (0, import_node_path16.resolve)(appDir, masterKeyFile);
|
|
14826
15174
|
const env = {
|
|
14827
15175
|
...process.env,
|
|
14828
15176
|
MMI_RAILS_CREDENTIALS_FILE: credentialsPath,
|
|
14829
15177
|
MMI_RAILS_MASTER_KEY_FILE: masterKeyPath
|
|
14830
15178
|
};
|
|
14831
|
-
if ((0,
|
|
14832
|
-
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();
|
|
14833
15181
|
}
|
|
14834
15182
|
const script = [
|
|
14835
15183
|
'require "json"',
|
|
@@ -14895,7 +15243,7 @@ function registerSecretsCommands(program3) {
|
|
|
14895
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) => {
|
|
14896
15244
|
let body;
|
|
14897
15245
|
try {
|
|
14898
|
-
body = (0,
|
|
15246
|
+
body = (0, import_node_fs15.readFileSync)((0, import_node_path16.resolve)(o.file), "utf8");
|
|
14899
15247
|
} catch (e) {
|
|
14900
15248
|
return fail(`secrets org-catalog: cannot read --file ${o.file}: ${e.message}`);
|
|
14901
15249
|
}
|
|
@@ -14970,7 +15318,7 @@ function registerSecretsCommands(program3) {
|
|
|
14970
15318
|
{
|
|
14971
15319
|
...d,
|
|
14972
15320
|
decryptRailsCredentials,
|
|
14973
|
-
removeFile: (path2) => (0,
|
|
15321
|
+
removeFile: (path2) => (0, import_node_fs15.unlinkSync)((0, import_node_path16.resolve)(o.appDir ?? process.cwd(), path2))
|
|
14974
15322
|
},
|
|
14975
15323
|
{
|
|
14976
15324
|
repo: o.repo,
|
|
@@ -15059,10 +15407,10 @@ function registerEdgeCommands(program3) {
|
|
|
15059
15407
|
}
|
|
15060
15408
|
|
|
15061
15409
|
// src/doctor-run.ts
|
|
15062
|
-
var
|
|
15410
|
+
var import_node_fs19 = require("node:fs");
|
|
15063
15411
|
var import_node_child_process11 = require("node:child_process");
|
|
15064
15412
|
var import_promises2 = require("node:fs/promises");
|
|
15065
|
-
var
|
|
15413
|
+
var import_node_path19 = require("node:path");
|
|
15066
15414
|
var import_node_os7 = require("node:os");
|
|
15067
15415
|
|
|
15068
15416
|
// src/plugin-guard.ts
|
|
@@ -15085,9 +15433,9 @@ function buildGuardSessionStartLine(state, opts = {}) {
|
|
|
15085
15433
|
|
|
15086
15434
|
// src/cursor-plugin-seed.ts
|
|
15087
15435
|
var import_node_child_process10 = require("node:child_process");
|
|
15088
|
-
var
|
|
15436
|
+
var import_node_fs16 = require("node:fs");
|
|
15089
15437
|
var import_node_os6 = require("node:os");
|
|
15090
|
-
var
|
|
15438
|
+
var import_node_path17 = require("node:path");
|
|
15091
15439
|
var import_node_util7 = require("node:util");
|
|
15092
15440
|
|
|
15093
15441
|
// src/doctor.ts
|
|
@@ -16407,17 +16755,17 @@ function ghReleaseTarballApiArgs(tag) {
|
|
|
16407
16755
|
}
|
|
16408
16756
|
function cursorUserGlobalStatePath() {
|
|
16409
16757
|
if (process.platform === "win32") {
|
|
16410
|
-
const base = process.env.APPDATA || (0,
|
|
16411
|
-
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");
|
|
16412
16760
|
}
|
|
16413
16761
|
if (process.platform === "darwin") {
|
|
16414
|
-
return (0,
|
|
16762
|
+
return (0, import_node_path17.join)((0, import_node_os6.homedir)(), "Library", "Application Support", "Cursor", "User", "globalStorage", "state.vscdb");
|
|
16415
16763
|
}
|
|
16416
|
-
return (0,
|
|
16764
|
+
return (0, import_node_path17.join)((0, import_node_os6.homedir)(), ".config", "Cursor", "User", "globalStorage", "state.vscdb");
|
|
16417
16765
|
}
|
|
16418
16766
|
async function readCursorThirdPartyExtensibilityEnabled(execFileP5) {
|
|
16419
16767
|
const dbPath = cursorUserGlobalStatePath();
|
|
16420
|
-
if (!(0,
|
|
16768
|
+
if (!(0, import_node_fs16.existsSync)(dbPath)) return void 0;
|
|
16421
16769
|
try {
|
|
16422
16770
|
const { stdout } = await execFileP5("sqlite3", [dbPath, `SELECT value FROM ItemTable WHERE key = '${CURSOR_THIRD_PARTY_STATE_KEY}';`], {
|
|
16423
16771
|
timeout: 5e3
|
|
@@ -16431,57 +16779,57 @@ async function readCursorThirdPartyExtensibilityEnabled(execFileP5) {
|
|
|
16431
16779
|
}
|
|
16432
16780
|
}
|
|
16433
16781
|
function syncDirContents(src, dest) {
|
|
16434
|
-
(0,
|
|
16435
|
-
for (const name of (0,
|
|
16436
|
-
(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 });
|
|
16437
16785
|
}
|
|
16438
|
-
(0,
|
|
16786
|
+
(0, import_node_fs16.cpSync)(src, dest, { recursive: true });
|
|
16439
16787
|
}
|
|
16440
16788
|
function releaseTag(releasedVersion) {
|
|
16441
16789
|
return releasedVersion.startsWith("v") ? releasedVersion : `v${releasedVersion}`;
|
|
16442
16790
|
}
|
|
16443
16791
|
async function extractPluginMmiFromHubCheckout(hubCheckout, tag, tmpRoot, execFileP5) {
|
|
16444
|
-
const tarFile = (0,
|
|
16792
|
+
const tarFile = (0, import_node_path17.join)(tmpRoot, "archive.tar");
|
|
16445
16793
|
try {
|
|
16446
16794
|
await execFileP5("git", gitFetchReleaseTagArgs(hubCheckout, tag), { timeout: 6e4 });
|
|
16447
16795
|
await execFileP5("git", ["-C", hubCheckout, "archive", "--format=tar", `--output=${tarFile}`, tag, "plugins/mmi"], {
|
|
16448
16796
|
timeout: 6e4
|
|
16449
16797
|
});
|
|
16450
16798
|
await execFileP5("tar", ["-xf", tarFile, "-C", tmpRoot], { timeout: 6e4 });
|
|
16451
|
-
const pluginMmi = (0,
|
|
16452
|
-
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;
|
|
16453
16801
|
} catch {
|
|
16454
16802
|
return void 0;
|
|
16455
16803
|
}
|
|
16456
16804
|
}
|
|
16457
16805
|
async function downloadPluginMmiViaGh(tag, tmpRoot) {
|
|
16458
|
-
const tarPath = (0,
|
|
16806
|
+
const tarPath = (0, import_node_path17.join)(tmpRoot, "repo.tgz");
|
|
16459
16807
|
try {
|
|
16460
|
-
(0,
|
|
16808
|
+
(0, import_node_fs16.mkdirSync)(tmpRoot, { recursive: true });
|
|
16461
16809
|
const { stdout } = await execFileBuffer("gh", ghReleaseTarballApiArgs(tag), {
|
|
16462
16810
|
timeout: 12e4,
|
|
16463
16811
|
maxBuffer: 100 * 1024 * 1024,
|
|
16464
16812
|
encoding: "buffer",
|
|
16465
16813
|
windowsHide: true
|
|
16466
16814
|
});
|
|
16467
|
-
(0,
|
|
16815
|
+
(0, import_node_fs16.writeFileSync)(tarPath, stdout);
|
|
16468
16816
|
await execFileBuffer("tar", ["-xzf", tarPath, "-C", tmpRoot], { timeout: 12e4, windowsHide: true });
|
|
16469
|
-
const top = (0,
|
|
16817
|
+
const top = (0, import_node_fs16.readdirSync)(tmpRoot).find((entry) => entry !== "repo.tgz");
|
|
16470
16818
|
if (!top) return void 0;
|
|
16471
|
-
const pluginMmi = (0,
|
|
16472
|
-
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;
|
|
16473
16821
|
} catch {
|
|
16474
16822
|
return void 0;
|
|
16475
16823
|
}
|
|
16476
16824
|
}
|
|
16477
16825
|
async function resolvePluginMmiSource(releasedVersion, hubCheckout, tmpRoot, execFileP5) {
|
|
16478
|
-
(0,
|
|
16826
|
+
(0, import_node_fs16.mkdirSync)(tmpRoot, { recursive: true });
|
|
16479
16827
|
const tag = releaseTag(releasedVersion);
|
|
16480
16828
|
if (hubCheckout) {
|
|
16481
16829
|
const fromHub = await extractPluginMmiFromHubCheckout(hubCheckout, tag, tmpRoot, execFileP5);
|
|
16482
16830
|
if (fromHub) return fromHub;
|
|
16483
16831
|
}
|
|
16484
|
-
return downloadPluginMmiViaGh(tag, (0,
|
|
16832
|
+
return downloadPluginMmiViaGh(tag, (0, import_node_path17.join)(tmpRoot, "gh"));
|
|
16485
16833
|
}
|
|
16486
16834
|
function cursorPluginPinsNeedingSeed(pins, releasedVersion) {
|
|
16487
16835
|
if (!isSemverVersion2(releasedVersion)) return pins.filter((pin) => !pin.hasPluginJson || !pin.hasHooksJson || pin.isEmpty);
|
|
@@ -16495,7 +16843,7 @@ function cursorPluginCacheSeedTargets(pins, releasedVersion, cacheRoot, cacheRoo
|
|
|
16495
16843
|
const needing = cursorPluginPinsNeedingSeed(pins, releasedVersion);
|
|
16496
16844
|
if (needing.length > 0) return needing.map((pin) => pin.path);
|
|
16497
16845
|
if (pins.length === 0 && cacheRoot && cacheRootExists && isSemverVersion2(releasedVersion)) {
|
|
16498
|
-
return [(0,
|
|
16846
|
+
return [(0, import_node_path17.join)(cacheRoot, `v${releasedVersion.replace(/^v/, "")}`)];
|
|
16499
16847
|
}
|
|
16500
16848
|
return [];
|
|
16501
16849
|
}
|
|
@@ -16510,7 +16858,7 @@ async function applyCursorPluginCacheSeed(input) {
|
|
|
16510
16858
|
for (const dest of targets) {
|
|
16511
16859
|
syncDirContents(source, dest);
|
|
16512
16860
|
}
|
|
16513
|
-
(0,
|
|
16861
|
+
(0, import_node_fs16.rmSync)(tmpRoot, { recursive: true, force: true });
|
|
16514
16862
|
return true;
|
|
16515
16863
|
}
|
|
16516
16864
|
var CURSOR_ENABLED_PIN_LABEL = "Cursor Team Marketplace enabled plugin pin";
|
|
@@ -16869,9 +17217,9 @@ ${block}
|
|
|
16869
17217
|
}
|
|
16870
17218
|
|
|
16871
17219
|
// src/cli-doctor-shared.ts
|
|
16872
|
-
var import_node_fs16 = require("node:fs");
|
|
16873
|
-
var import_node_path17 = require("node:path");
|
|
16874
17220
|
var import_node_fs17 = require("node:fs");
|
|
17221
|
+
var import_node_path18 = require("node:path");
|
|
17222
|
+
var import_node_fs18 = require("node:fs");
|
|
16875
17223
|
var GC_GH_TIMEOUT_MS = 2e4;
|
|
16876
17224
|
async function awsCallerArn() {
|
|
16877
17225
|
try {
|
|
@@ -16917,7 +17265,7 @@ async function localBranchHeads() {
|
|
|
16917
17265
|
}
|
|
16918
17266
|
async function currentRepoWorktreeGitRoot(repoRoot) {
|
|
16919
17267
|
const gitCommonDir = (await execFileP2("git", ["rev-parse", "--git-common-dir"], { timeout: GIT_TIMEOUT_MS }).catch(() => ({ stdout: "" }))).stdout.trim();
|
|
16920
|
-
return gitCommonDir ? (0,
|
|
17268
|
+
return gitCommonDir ? (0, import_node_path18.resolve)(repoRoot, gitCommonDir, "worktrees") : "";
|
|
16921
17269
|
}
|
|
16922
17270
|
async function worktreeBranches() {
|
|
16923
17271
|
const { stdout } = await execFileP2("git", ["worktree", "list", "--porcelain"], { timeout: GIT_TIMEOUT_MS });
|
|
@@ -16937,18 +17285,18 @@ function resolveGitdirForWorktreeFile(worktreePath, content) {
|
|
|
16937
17285
|
const match = /^gitdir:\s*(.+)\s*$/im.exec(content);
|
|
16938
17286
|
if (!match?.[1]) return void 0;
|
|
16939
17287
|
const raw = match[1].trim();
|
|
16940
|
-
return (0,
|
|
17288
|
+
return (0, import_node_path18.isAbsolute)(raw) ? raw : (0, import_node_path18.resolve)(worktreePath, raw);
|
|
16941
17289
|
}
|
|
16942
17290
|
function metadataOwnsMissingWorktreeDir(worktreePath, worktreeGitRoot) {
|
|
16943
17291
|
if (!worktreeGitRoot) return false;
|
|
16944
17292
|
try {
|
|
16945
|
-
const entries = (0,
|
|
17293
|
+
const entries = (0, import_node_fs18.readdirSync)(worktreeGitRoot, { withFileTypes: true });
|
|
16946
17294
|
for (const ent of entries) {
|
|
16947
17295
|
if (!ent.isDirectory()) continue;
|
|
16948
17296
|
try {
|
|
16949
|
-
const gitdirPath = (0,
|
|
16950
|
-
const resolvedGitdir = (0,
|
|
16951
|
-
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;
|
|
16952
17300
|
} catch {
|
|
16953
17301
|
}
|
|
16954
17302
|
}
|
|
@@ -16958,7 +17306,7 @@ function metadataOwnsMissingWorktreeDir(worktreePath, worktreeGitRoot) {
|
|
|
16958
17306
|
}
|
|
16959
17307
|
function pathExistsKnown(path2) {
|
|
16960
17308
|
try {
|
|
16961
|
-
(0,
|
|
17309
|
+
(0, import_node_fs18.statSync)(path2);
|
|
16962
17310
|
return true;
|
|
16963
17311
|
} catch (e) {
|
|
16964
17312
|
const code = typeof e === "object" && e && "code" in e ? String(e.code ?? "") : "";
|
|
@@ -16967,10 +17315,10 @@ function pathExistsKnown(path2) {
|
|
|
16967
17315
|
}
|
|
16968
17316
|
}
|
|
16969
17317
|
function inspectSiblingWorktreeDir(path2, worktreeGitRoot) {
|
|
16970
|
-
const gitPath = (0,
|
|
17318
|
+
const gitPath = (0, import_node_path18.join)(path2, ".git");
|
|
16971
17319
|
let st;
|
|
16972
17320
|
try {
|
|
16973
|
-
st = (0,
|
|
17321
|
+
st = (0, import_node_fs18.lstatSync)(gitPath);
|
|
16974
17322
|
} catch (e) {
|
|
16975
17323
|
const code = typeof e === "object" && e && "code" in e ? String(e.code ?? "") : "";
|
|
16976
17324
|
if (code === "ENOENT" || code === "ENOTDIR") {
|
|
@@ -16987,7 +17335,7 @@ function inspectSiblingWorktreeDir(path2, worktreeGitRoot) {
|
|
|
16987
17335
|
if (st.isDirectory()) return { path: path2, gitType: "dir" };
|
|
16988
17336
|
if (!st.isFile()) return { path: path2, gitType: "other" };
|
|
16989
17337
|
try {
|
|
16990
|
-
const gitFileContent = (0,
|
|
17338
|
+
const gitFileContent = (0, import_node_fs17.readFileSync)(gitPath, "utf8");
|
|
16991
17339
|
const gitdir = resolveGitdirForWorktreeFile(path2, gitFileContent);
|
|
16992
17340
|
const gitDirExists = gitdir ? pathExistsKnown(gitdir) : false;
|
|
16993
17341
|
return {
|
|
@@ -17004,7 +17352,7 @@ function inspectSiblingWorktreeDir(path2, worktreeGitRoot) {
|
|
|
17004
17352
|
}
|
|
17005
17353
|
function inspectDeadWorktreeDirContent(path2) {
|
|
17006
17354
|
try {
|
|
17007
|
-
return { entries: (0,
|
|
17355
|
+
return { entries: (0, import_node_fs18.readdirSync)(path2) };
|
|
17008
17356
|
} catch (e) {
|
|
17009
17357
|
const code = typeof e === "object" && e && "code" in e ? String(e.code ?? "") : "";
|
|
17010
17358
|
return { error: code ? `unable to inspect directory contents (${code})` : "unable to inspect directory contents" };
|
|
@@ -17023,8 +17371,8 @@ async function siblingWorktreeDirs() {
|
|
|
17023
17371
|
const worktreeGitRoot = await currentRepoWorktreeGitRoot(repoRoot);
|
|
17024
17372
|
const siblingRoot = siblingMmiWorktreesRoot(repoRoot);
|
|
17025
17373
|
try {
|
|
17026
|
-
const entries = (0,
|
|
17027
|
-
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));
|
|
17028
17376
|
} catch {
|
|
17029
17377
|
return [];
|
|
17030
17378
|
}
|
|
@@ -17074,7 +17422,7 @@ async function fetchHubVersionInfo(baseUrl) {
|
|
|
17074
17422
|
}
|
|
17075
17423
|
function readRepoVersion() {
|
|
17076
17424
|
try {
|
|
17077
|
-
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;
|
|
17078
17426
|
} catch {
|
|
17079
17427
|
return void 0;
|
|
17080
17428
|
}
|
|
@@ -17226,11 +17574,11 @@ async function applyPluginHeal(token, surface, log, opts) {
|
|
|
17226
17574
|
}
|
|
17227
17575
|
var installedPluginsPath = (surface = detectSurface(process.env)) => {
|
|
17228
17576
|
const homeDir = surface === "codex" ? ".codex" : ".claude";
|
|
17229
|
-
return (0,
|
|
17577
|
+
return (0, import_node_path19.join)((0, import_node_os7.homedir)(), homeDir, "plugins", "installed_plugins.json");
|
|
17230
17578
|
};
|
|
17231
17579
|
function readInstalledPlugins(surface = detectSurface(process.env)) {
|
|
17232
17580
|
try {
|
|
17233
|
-
return JSON.parse((0,
|
|
17581
|
+
return JSON.parse((0, import_node_fs19.readFileSync)(installedPluginsPath(surface), "utf8"));
|
|
17234
17582
|
} catch {
|
|
17235
17583
|
return null;
|
|
17236
17584
|
}
|
|
@@ -17238,13 +17586,13 @@ function readInstalledPlugins(surface = detectSurface(process.env)) {
|
|
|
17238
17586
|
function marketplaceCloneCandidates(surface, home) {
|
|
17239
17587
|
if (surface === "codex") {
|
|
17240
17588
|
return [
|
|
17241
|
-
(0,
|
|
17242
|
-
(0,
|
|
17589
|
+
(0, import_node_path19.join)(home, ".codex", ".tmp", "marketplaces", "mutmutco"),
|
|
17590
|
+
(0, import_node_path19.join)(home, ".codex", "plugins", "marketplaces", "mutmutco")
|
|
17243
17591
|
];
|
|
17244
17592
|
}
|
|
17245
|
-
return [(0,
|
|
17593
|
+
return [(0, import_node_path19.join)(home, ".claude", "plugins", "marketplaces", "mutmutco")];
|
|
17246
17594
|
}
|
|
17247
|
-
function marketplaceClonePresent(surface, home, exists =
|
|
17595
|
+
function marketplaceClonePresent(surface, home, exists = import_node_fs19.existsSync) {
|
|
17248
17596
|
return marketplaceCloneCandidates(surface, home).some(exists);
|
|
17249
17597
|
}
|
|
17250
17598
|
function snapshotPluginGuardInput(surface = detectSurface(process.env), isOrgRepo = false) {
|
|
@@ -17254,14 +17602,14 @@ function snapshotPluginGuardInput(surface = detectSurface(process.env), isOrgRep
|
|
|
17254
17602
|
isOrgRepo,
|
|
17255
17603
|
installRecordPresent: hasUserInstallRecord(installed, MMI_PLUGIN_ID) || hasProjectInstallRecord(installed, MMI_PLUGIN_ID, process.cwd()),
|
|
17256
17604
|
marketplaceClonePresent: marketplaceClonePresent(surface, (0, import_node_os7.homedir)()),
|
|
17257
|
-
pluginCachePresent: (0,
|
|
17605
|
+
pluginCachePresent: (0, import_node_fs19.existsSync)((0, import_node_path19.join)((0, import_node_os7.homedir)(), homeDir, "plugins", "cache", "mutmutco", "mmi"))
|
|
17258
17606
|
};
|
|
17259
17607
|
}
|
|
17260
17608
|
function installedPluginSources() {
|
|
17261
17609
|
return ["claude", "codex"].map((surface) => {
|
|
17262
|
-
const recordPath = (0,
|
|
17610
|
+
const recordPath = (0, import_node_path19.join)((0, import_node_os7.homedir)(), `.${surface}`, "plugins", "installed_plugins.json");
|
|
17263
17611
|
try {
|
|
17264
|
-
return { surface, installed: JSON.parse((0,
|
|
17612
|
+
return { surface, installed: JSON.parse((0, import_node_fs19.readFileSync)(recordPath, "utf8")), recordPath };
|
|
17265
17613
|
} catch {
|
|
17266
17614
|
return { surface, installed: null, recordPath };
|
|
17267
17615
|
}
|
|
@@ -17269,7 +17617,7 @@ function installedPluginSources() {
|
|
|
17269
17617
|
}
|
|
17270
17618
|
function readClaudeSettings() {
|
|
17271
17619
|
try {
|
|
17272
|
-
return JSON.parse((0,
|
|
17620
|
+
return JSON.parse((0, import_node_fs19.readFileSync)((0, import_node_path19.join)(process.cwd(), ".claude", "settings.json"), "utf8"));
|
|
17273
17621
|
} catch {
|
|
17274
17622
|
return null;
|
|
17275
17623
|
}
|
|
@@ -17291,7 +17639,7 @@ function writeProjectInstallRecord(record) {
|
|
|
17291
17639
|
const list = file.plugins[MMI_PLUGIN_ID] ?? [];
|
|
17292
17640
|
list.push(record);
|
|
17293
17641
|
file.plugins[MMI_PLUGIN_ID] = list;
|
|
17294
|
-
(0,
|
|
17642
|
+
(0, import_node_fs19.writeFileSync)(installedPluginsPath(), `${JSON.stringify(file, null, 2)}
|
|
17295
17643
|
`, "utf8");
|
|
17296
17644
|
return true;
|
|
17297
17645
|
} catch {
|
|
@@ -17304,9 +17652,9 @@ function backupAndWriteInstalledPlugins(records, pluginId) {
|
|
|
17304
17652
|
if (!file) return false;
|
|
17305
17653
|
if (!file.plugins) file.plugins = {};
|
|
17306
17654
|
const path2 = installedPluginsPath();
|
|
17307
|
-
(0,
|
|
17655
|
+
(0, import_node_fs19.copyFileSync)(path2, `${path2}.bak`);
|
|
17308
17656
|
file.plugins[pluginId] = records;
|
|
17309
|
-
(0,
|
|
17657
|
+
(0, import_node_fs19.writeFileSync)(path2, `${JSON.stringify(file, null, 2)}
|
|
17310
17658
|
`, "utf8");
|
|
17311
17659
|
return true;
|
|
17312
17660
|
} catch {
|
|
@@ -17314,22 +17662,22 @@ function backupAndWriteInstalledPlugins(records, pluginId) {
|
|
|
17314
17662
|
}
|
|
17315
17663
|
}
|
|
17316
17664
|
function opencodeConfigDir() {
|
|
17317
|
-
return (0,
|
|
17665
|
+
return (0, import_node_path19.join)((0, import_node_os7.homedir)(), ".config", "opencode");
|
|
17318
17666
|
}
|
|
17319
17667
|
function opencodeConfigPath() {
|
|
17320
|
-
return (0,
|
|
17668
|
+
return (0, import_node_path19.join)(opencodeConfigDir(), "opencode.jsonc");
|
|
17321
17669
|
}
|
|
17322
17670
|
function opencodeCommandsDir() {
|
|
17323
|
-
return (0,
|
|
17671
|
+
return (0, import_node_path19.join)(opencodeConfigDir(), "commands");
|
|
17324
17672
|
}
|
|
17325
17673
|
function opencodeSkillsPath() {
|
|
17326
|
-
return (0,
|
|
17674
|
+
return (0, import_node_path19.join)(opencodeConfigDir(), "node_modules", "@mutmutco", "opencode-mmi", "skills");
|
|
17327
17675
|
}
|
|
17328
17676
|
function opencodeConfigSnapshot() {
|
|
17329
17677
|
const path2 = opencodeConfigPath();
|
|
17330
|
-
if (!(0,
|
|
17678
|
+
if (!(0, import_node_fs19.existsSync)(path2)) return { path: path2, hasConfig: false, hasPluginField: false, parseOk: true };
|
|
17331
17679
|
try {
|
|
17332
|
-
const raw = (0,
|
|
17680
|
+
const raw = (0, import_node_fs19.readFileSync)(path2, "utf8");
|
|
17333
17681
|
const parsed = JSON.parse(stripJsonc(raw));
|
|
17334
17682
|
const hasPluginField = Object.prototype.hasOwnProperty.call(parsed, "plugin");
|
|
17335
17683
|
const skillsPaths = Array.isArray(parsed.skills?.paths) ? parsed.skills.paths.filter((p) => typeof p === "string") : void 0;
|
|
@@ -17352,9 +17700,9 @@ function writeOpencodeConfigPlugin(snapshot) {
|
|
|
17352
17700
|
const plan = planOpencodeConfigWrite(snapshot.hasConfig ? snapshot.raw : void 0);
|
|
17353
17701
|
if (plan.action === "already") return true;
|
|
17354
17702
|
if (!plan.text || plan.action === "unsafe") return false;
|
|
17355
|
-
(0,
|
|
17356
|
-
if (snapshot.hasConfig) (0,
|
|
17357
|
-
(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");
|
|
17358
17706
|
return true;
|
|
17359
17707
|
} catch {
|
|
17360
17708
|
return false;
|
|
@@ -17369,9 +17717,9 @@ function writeOpencodeSkillsPath(snapshot, skillsPath) {
|
|
|
17369
17717
|
const normalized = skillsPath.replace(/\\/g, "/");
|
|
17370
17718
|
if (!paths.some((p) => p.replace(/\\/g, "/") === normalized)) paths.push(skillsPath.replace(/\\/g, "/"));
|
|
17371
17719
|
parsed.skills = { ...skills, paths };
|
|
17372
|
-
(0,
|
|
17373
|
-
if (snapshot.hasConfig && (0,
|
|
17374
|
-
(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)}
|
|
17375
17723
|
`, "utf8");
|
|
17376
17724
|
return true;
|
|
17377
17725
|
} catch {
|
|
@@ -17380,7 +17728,7 @@ function writeOpencodeSkillsPath(snapshot, skillsPath) {
|
|
|
17380
17728
|
}
|
|
17381
17729
|
function opencodeExistingCommands() {
|
|
17382
17730
|
try {
|
|
17383
|
-
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());
|
|
17384
17732
|
} catch {
|
|
17385
17733
|
return [];
|
|
17386
17734
|
}
|
|
@@ -17388,9 +17736,9 @@ function opencodeExistingCommands() {
|
|
|
17388
17736
|
function writeOpencodeCommandFiles() {
|
|
17389
17737
|
try {
|
|
17390
17738
|
const dir = opencodeCommandsDir();
|
|
17391
|
-
(0,
|
|
17739
|
+
(0, import_node_fs19.mkdirSync)(dir, { recursive: true });
|
|
17392
17740
|
for (const command of OPENCODE_WORKFLOW_COMMANDS) {
|
|
17393
|
-
(0,
|
|
17741
|
+
(0, import_node_fs19.writeFileSync)((0, import_node_path19.join)(dir, `${command}.md`), opencodeCommandMarkdown(command), "utf8");
|
|
17394
17742
|
}
|
|
17395
17743
|
return true;
|
|
17396
17744
|
} catch {
|
|
@@ -17399,12 +17747,12 @@ function writeOpencodeCommandFiles() {
|
|
|
17399
17747
|
}
|
|
17400
17748
|
function readOpencodeAdapterDiskVersion() {
|
|
17401
17749
|
const candidates = [
|
|
17402
|
-
(0,
|
|
17403
|
-
(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")
|
|
17404
17752
|
];
|
|
17405
17753
|
for (const path2 of candidates) {
|
|
17406
17754
|
try {
|
|
17407
|
-
const parsed = JSON.parse((0,
|
|
17755
|
+
const parsed = JSON.parse((0, import_node_fs19.readFileSync)(path2, "utf8"));
|
|
17408
17756
|
if (typeof parsed.version === "string" && parsed.version.trim()) return parsed.version.trim();
|
|
17409
17757
|
} catch {
|
|
17410
17758
|
continue;
|
|
@@ -17420,7 +17768,7 @@ async function forceInstallOpencodeMmiPlugins(snapshot, log) {
|
|
|
17420
17768
|
try {
|
|
17421
17769
|
const specs = opencodeMmiPluginSpecs(snapshot);
|
|
17422
17770
|
log(` \u21BB force-refreshing OpenCode MMI npm plugin(s): ${specs.join(", ")}\u2026`);
|
|
17423
|
-
(0,
|
|
17771
|
+
(0, import_node_fs19.mkdirSync)(opencodeConfigDir(), { recursive: true });
|
|
17424
17772
|
await runHostBin("npm", ["install", "--prefix", opencodeConfigDir(), "--force", ...specs], { timeout: NPM_UPDATE_TIMEOUT_MS });
|
|
17425
17773
|
return true;
|
|
17426
17774
|
} catch {
|
|
@@ -17428,12 +17776,12 @@ async function forceInstallOpencodeMmiPlugins(snapshot, log) {
|
|
|
17428
17776
|
}
|
|
17429
17777
|
}
|
|
17430
17778
|
function opencodePackagesRoot() {
|
|
17431
|
-
return (0,
|
|
17779
|
+
return (0, import_node_path19.join)((0, import_node_os7.homedir)(), ".cache", "opencode", "packages", "@mutmutco");
|
|
17432
17780
|
}
|
|
17433
17781
|
function opencodePluginCacheDirs() {
|
|
17434
17782
|
const root = opencodePackagesRoot();
|
|
17435
17783
|
try {
|
|
17436
|
-
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));
|
|
17437
17785
|
} catch {
|
|
17438
17786
|
return [];
|
|
17439
17787
|
}
|
|
@@ -17441,7 +17789,7 @@ function opencodePluginCacheDirs() {
|
|
|
17441
17789
|
function readOpencodeCacheDirVersion(cacheDir) {
|
|
17442
17790
|
try {
|
|
17443
17791
|
const parsed = JSON.parse(
|
|
17444
|
-
(0,
|
|
17792
|
+
(0, import_node_fs19.readFileSync)((0, import_node_path19.join)(cacheDir, "node_modules", "@mutmutco", "opencode-mmi", "package.json"), "utf8")
|
|
17445
17793
|
);
|
|
17446
17794
|
return typeof parsed.version === "string" && parsed.version.trim() ? parsed.version.trim() : void 0;
|
|
17447
17795
|
} catch {
|
|
@@ -17464,9 +17812,9 @@ function quarantineStaleOpencodePluginCaches(releasedVersion, log) {
|
|
|
17464
17812
|
});
|
|
17465
17813
|
if (!plan.quarantine) continue;
|
|
17466
17814
|
try {
|
|
17467
|
-
const quarantineRoot = (0,
|
|
17468
|
-
(0,
|
|
17469
|
-
(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)));
|
|
17470
17818
|
log(` \u21BB quarantined stale OpenCode plugin cache ${plan.cacheVersion} (< ${plan.releasedVersion}) \u2014 OpenCode reinstalls the current adapter on next start`);
|
|
17471
17819
|
moved = true;
|
|
17472
17820
|
} catch {
|
|
@@ -17492,30 +17840,30 @@ function opencodePluginVersionsForReport() {
|
|
|
17492
17840
|
}
|
|
17493
17841
|
function opencodeDesktopLogsRoot() {
|
|
17494
17842
|
if (process.platform === "win32") {
|
|
17495
|
-
const base = process.env.APPDATA || (0,
|
|
17496
|
-
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");
|
|
17497
17845
|
}
|
|
17498
17846
|
if (process.platform === "darwin") {
|
|
17499
|
-
return (0,
|
|
17847
|
+
return (0, import_node_path19.join)((0, import_node_os7.homedir)(), "Library", "Application Support", "ai.opencode.desktop", "logs");
|
|
17500
17848
|
}
|
|
17501
|
-
return (0,
|
|
17849
|
+
return (0, import_node_path19.join)((0, import_node_os7.homedir)(), ".config", "ai.opencode.desktop", "logs");
|
|
17502
17850
|
}
|
|
17503
17851
|
function opencodeDesktopBootstrapSnapshot() {
|
|
17504
17852
|
const root = opencodeDesktopLogsRoot();
|
|
17505
17853
|
try {
|
|
17506
|
-
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);
|
|
17507
17855
|
const newest = sessionDirs[0];
|
|
17508
17856
|
if (!newest) return [];
|
|
17509
|
-
const logPath = (0,
|
|
17510
|
-
const text = (0,
|
|
17511
|
-
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 }));
|
|
17512
17860
|
} catch {
|
|
17513
17861
|
return [];
|
|
17514
17862
|
}
|
|
17515
17863
|
}
|
|
17516
17864
|
function opencodeLegacyConfigSnapshot() {
|
|
17517
|
-
const legacyPath = (0,
|
|
17518
|
-
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 {};
|
|
17519
17867
|
const content = readTextFile(legacyPath);
|
|
17520
17868
|
if (content == null) return {};
|
|
17521
17869
|
const plugins = parseOpencodeLegacyConfigPlugins(content);
|
|
@@ -17527,24 +17875,24 @@ function opencodeLegacyConfigSnapshot() {
|
|
|
17527
17875
|
function quarantineOpencodeLegacyConfig(legacyPath) {
|
|
17528
17876
|
try {
|
|
17529
17877
|
const backupPath = `${legacyPath}.bak`;
|
|
17530
|
-
if ((0,
|
|
17531
|
-
(0,
|
|
17878
|
+
if ((0, import_node_fs19.existsSync)(backupPath)) return false;
|
|
17879
|
+
(0, import_node_fs19.renameSync)(legacyPath, backupPath);
|
|
17532
17880
|
return true;
|
|
17533
17881
|
} catch {
|
|
17534
17882
|
return false;
|
|
17535
17883
|
}
|
|
17536
17884
|
}
|
|
17537
17885
|
function cursorPluginCacheRoot() {
|
|
17538
|
-
return (0,
|
|
17886
|
+
return (0, import_node_path19.join)((0, import_node_os7.homedir)(), ".cursor", "plugins", "cache", "mutmutco", "mmi");
|
|
17539
17887
|
}
|
|
17540
17888
|
function cursorLogsRoot() {
|
|
17541
|
-
if (process.platform === "win32") return (0,
|
|
17542
|
-
if (process.platform === "darwin") return (0,
|
|
17543
|
-
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");
|
|
17544
17892
|
}
|
|
17545
17893
|
function safeReaddirNames(dir) {
|
|
17546
17894
|
try {
|
|
17547
|
-
return (0,
|
|
17895
|
+
return (0, import_node_fs19.readdirSync)(dir, { withFileTypes: true }).map((e) => e.name);
|
|
17548
17896
|
} catch {
|
|
17549
17897
|
return [];
|
|
17550
17898
|
}
|
|
@@ -17553,15 +17901,15 @@ function readCursorPluginLogText() {
|
|
|
17553
17901
|
const root = cursorLogsRoot();
|
|
17554
17902
|
const sessions = safeReaddirNames(root).sort().reverse();
|
|
17555
17903
|
for (const session of sessions.slice(0, 5)) {
|
|
17556
|
-
const sessionDir = (0,
|
|
17904
|
+
const sessionDir = (0, import_node_path19.join)(root, session);
|
|
17557
17905
|
const texts = [];
|
|
17558
17906
|
for (const win of safeReaddirNames(sessionDir).filter((n) => n.startsWith("window"))) {
|
|
17559
|
-
const dir = (0,
|
|
17907
|
+
const dir = (0, import_node_path19.join)(sessionDir, win, "exthost", "anysphere.cursor-agent-exec");
|
|
17560
17908
|
for (const file of safeReaddirNames(dir)) {
|
|
17561
17909
|
if (!/^Cursor Plugins.*\.log$/i.test(file)) continue;
|
|
17562
|
-
const path2 = (0,
|
|
17910
|
+
const path2 = (0, import_node_path19.join)(dir, file);
|
|
17563
17911
|
try {
|
|
17564
|
-
texts.push({ text: (0,
|
|
17912
|
+
texts.push({ text: (0, import_node_fs19.readFileSync)(path2, "utf8"), mtime: (0, import_node_fs19.statSync)(path2).mtimeMs });
|
|
17565
17913
|
} catch {
|
|
17566
17914
|
}
|
|
17567
17915
|
}
|
|
@@ -17575,32 +17923,32 @@ function readCursorPluginLogText() {
|
|
|
17575
17923
|
function cursorPluginCachePinSnapshots() {
|
|
17576
17924
|
const root = cursorPluginCacheRoot();
|
|
17577
17925
|
try {
|
|
17578
|
-
return (0,
|
|
17579
|
-
const path2 = (0,
|
|
17580
|
-
const pluginJson = (0,
|
|
17581
|
-
const hooksJson = (0,
|
|
17582
|
-
const cliBundle = (0,
|
|
17583
|
-
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");
|
|
17584
17932
|
let version;
|
|
17585
17933
|
try {
|
|
17586
|
-
const raw = JSON.parse((0,
|
|
17934
|
+
const raw = JSON.parse((0, import_node_fs19.readFileSync)(pluginJson, "utf8"));
|
|
17587
17935
|
version = typeof raw.version === "string" ? raw.version : void 0;
|
|
17588
17936
|
} catch {
|
|
17589
17937
|
version = void 0;
|
|
17590
17938
|
}
|
|
17591
17939
|
let isEmpty = true;
|
|
17592
17940
|
try {
|
|
17593
|
-
isEmpty = (0,
|
|
17941
|
+
isEmpty = (0, import_node_fs19.readdirSync)(path2).length === 0;
|
|
17594
17942
|
} catch {
|
|
17595
17943
|
isEmpty = true;
|
|
17596
17944
|
}
|
|
17597
17945
|
return {
|
|
17598
17946
|
name: entry.name,
|
|
17599
17947
|
path: path2,
|
|
17600
|
-
hasPluginJson: (0,
|
|
17601
|
-
hasHooksJson: (0,
|
|
17602
|
-
hasCliBundle: (0,
|
|
17603
|
-
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),
|
|
17604
17952
|
isEmpty,
|
|
17605
17953
|
version
|
|
17606
17954
|
};
|
|
@@ -17610,19 +17958,19 @@ function cursorPluginCachePinSnapshots() {
|
|
|
17610
17958
|
}
|
|
17611
17959
|
}
|
|
17612
17960
|
function hubCheckoutForCursorSeed() {
|
|
17613
|
-
const manifest = (0,
|
|
17614
|
-
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;
|
|
17615
17963
|
}
|
|
17616
17964
|
function mmiPluginCacheRootSnapshots() {
|
|
17617
17965
|
const roots = [
|
|
17618
|
-
{ surface: "claude", root: (0,
|
|
17619
|
-
{ 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") }
|
|
17620
17968
|
];
|
|
17621
17969
|
return roots.flatMap(({ surface, root }) => {
|
|
17622
17970
|
try {
|
|
17623
|
-
const entries = (0,
|
|
17971
|
+
const entries = (0, import_node_fs19.readdirSync)(root, { withFileTypes: true }).map((entry) => ({
|
|
17624
17972
|
name: entry.name,
|
|
17625
|
-
path: (0,
|
|
17973
|
+
path: (0, import_node_path19.join)(root, entry.name),
|
|
17626
17974
|
isDirectory: entry.isDirectory()
|
|
17627
17975
|
}));
|
|
17628
17976
|
return [{ surface, root, entries }];
|
|
@@ -17633,7 +17981,7 @@ function mmiPluginCacheRootSnapshots() {
|
|
|
17633
17981
|
}
|
|
17634
17982
|
function hasNestedMmiChild(versionDir) {
|
|
17635
17983
|
try {
|
|
17636
|
-
return (0,
|
|
17984
|
+
return (0, import_node_fs19.statSync)((0, import_node_path19.join)(versionDir, "mmi")).isDirectory();
|
|
17637
17985
|
} catch {
|
|
17638
17986
|
return false;
|
|
17639
17987
|
}
|
|
@@ -17644,10 +17992,10 @@ function nestedPluginTreeSnapshot() {
|
|
|
17644
17992
|
);
|
|
17645
17993
|
}
|
|
17646
17994
|
function uniqueQuarantineTarget(path2) {
|
|
17647
|
-
if (!(0,
|
|
17995
|
+
if (!(0, import_node_fs19.existsSync)(path2)) return path2;
|
|
17648
17996
|
for (let i = 1; i < 100; i += 1) {
|
|
17649
17997
|
const candidate = `${path2}-${i}`;
|
|
17650
|
-
if (!(0,
|
|
17998
|
+
if (!(0, import_node_fs19.existsSync)(candidate)) return candidate;
|
|
17651
17999
|
}
|
|
17652
18000
|
return `${path2}-${Date.now()}`;
|
|
17653
18001
|
}
|
|
@@ -17656,10 +18004,10 @@ function quarantinePluginCacheDirs(plan) {
|
|
|
17656
18004
|
const failed = [];
|
|
17657
18005
|
for (const move of plan) {
|
|
17658
18006
|
try {
|
|
17659
|
-
if (!(0,
|
|
18007
|
+
if (!(0, import_node_fs19.existsSync)(move.from)) continue;
|
|
17660
18008
|
const target = uniqueQuarantineTarget(move.to);
|
|
17661
|
-
(0,
|
|
17662
|
-
(0,
|
|
18009
|
+
(0, import_node_fs19.mkdirSync)((0, import_node_path19.dirname)(target), { recursive: true });
|
|
18010
|
+
(0, import_node_fs19.renameSync)(move.from, target);
|
|
17663
18011
|
moved += 1;
|
|
17664
18012
|
} catch {
|
|
17665
18013
|
failed.push(move);
|
|
@@ -17678,23 +18026,23 @@ async function robocopyMirrorEmpty(emptyDir, target) {
|
|
|
17678
18026
|
}
|
|
17679
18027
|
async function clearNestedPluginTreeDir(targetPath) {
|
|
17680
18028
|
try {
|
|
17681
|
-
if (!(0,
|
|
18029
|
+
if (!(0, import_node_fs19.existsSync)(targetPath)) return true;
|
|
17682
18030
|
if (isWin) {
|
|
17683
|
-
const emptyDir = (0,
|
|
17684
|
-
(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 });
|
|
17685
18033
|
try {
|
|
17686
18034
|
await robocopyMirrorEmpty(emptyDir, targetPath);
|
|
17687
|
-
(0,
|
|
18035
|
+
(0, import_node_fs19.rmSync)(targetPath, { recursive: true, force: true });
|
|
17688
18036
|
} finally {
|
|
17689
18037
|
try {
|
|
17690
|
-
(0,
|
|
18038
|
+
(0, import_node_fs19.rmSync)(emptyDir, { recursive: true, force: true });
|
|
17691
18039
|
} catch {
|
|
17692
18040
|
}
|
|
17693
18041
|
}
|
|
17694
|
-
return !(0,
|
|
18042
|
+
return !(0, import_node_fs19.existsSync)(targetPath);
|
|
17695
18043
|
}
|
|
17696
|
-
(0,
|
|
17697
|
-
return !(0,
|
|
18044
|
+
(0, import_node_fs19.rmSync)(targetPath, { recursive: true, force: true });
|
|
18045
|
+
return !(0, import_node_fs19.existsSync)(targetPath);
|
|
17698
18046
|
} catch {
|
|
17699
18047
|
return false;
|
|
17700
18048
|
}
|
|
@@ -17707,18 +18055,18 @@ async function applyNestedPluginTreeCleanup(paths, log) {
|
|
|
17707
18055
|
}
|
|
17708
18056
|
return true;
|
|
17709
18057
|
}
|
|
17710
|
-
var gitignorePath = () => (0,
|
|
18058
|
+
var gitignorePath = () => (0, import_node_path19.join)(process.cwd(), ".gitignore");
|
|
17711
18059
|
function readTextFile(path2) {
|
|
17712
18060
|
try {
|
|
17713
|
-
if (!(0,
|
|
17714
|
-
return (0,
|
|
18061
|
+
if (!(0, import_node_fs19.existsSync)(path2)) return null;
|
|
18062
|
+
return (0, import_node_fs19.readFileSync)(path2, "utf8");
|
|
17715
18063
|
} catch {
|
|
17716
18064
|
return null;
|
|
17717
18065
|
}
|
|
17718
18066
|
}
|
|
17719
18067
|
function mcpDirExists(path2) {
|
|
17720
18068
|
try {
|
|
17721
|
-
return (0,
|
|
18069
|
+
return (0, import_node_fs19.existsSync)(path2);
|
|
17722
18070
|
} catch {
|
|
17723
18071
|
return false;
|
|
17724
18072
|
}
|
|
@@ -17726,60 +18074,60 @@ function mcpDirExists(path2) {
|
|
|
17726
18074
|
function mcpConfigTargets() {
|
|
17727
18075
|
const cwd = process.cwd();
|
|
17728
18076
|
const home = (0, import_node_os7.homedir)();
|
|
17729
|
-
const cursorProjectDir = (0,
|
|
17730
|
-
const cursorUserDir = (0,
|
|
17731
|
-
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");
|
|
17732
18080
|
return [
|
|
17733
18081
|
// Claude Code project MCP — reconciled if present, never conjured (org seeds .cursor/mcp.json, not this).
|
|
17734
18082
|
{
|
|
17735
18083
|
host: "claude-code",
|
|
17736
18084
|
label: "Claude Code project MCP",
|
|
17737
|
-
path: (0,
|
|
18085
|
+
path: (0, import_node_path19.join)(cwd, ".mcp.json"),
|
|
17738
18086
|
format: "json",
|
|
17739
18087
|
present: true,
|
|
17740
18088
|
// parent is the repo root (cwd); the caller gates the whole reconcile on isOrgRepo
|
|
17741
18089
|
isRepoFile: true,
|
|
17742
18090
|
createWhenFileAbsent: false,
|
|
17743
|
-
content: readTextFile((0,
|
|
18091
|
+
content: readTextFile((0, import_node_path19.join)(cwd, ".mcp.json"))
|
|
17744
18092
|
},
|
|
17745
18093
|
// Cursor project MCP — the bootstrap-seeded surface; restored if its .cursor dir exists but the file is gone.
|
|
17746
18094
|
{
|
|
17747
18095
|
host: "cursor-project",
|
|
17748
18096
|
label: "Cursor project MCP",
|
|
17749
|
-
path: (0,
|
|
18097
|
+
path: (0, import_node_path19.join)(cursorProjectDir, "mcp.json"),
|
|
17750
18098
|
format: "json",
|
|
17751
18099
|
present: mcpDirExists(cursorProjectDir),
|
|
17752
18100
|
isRepoFile: true,
|
|
17753
18101
|
createWhenFileAbsent: true,
|
|
17754
|
-
content: readTextFile((0,
|
|
18102
|
+
content: readTextFile((0, import_node_path19.join)(cursorProjectDir, "mcp.json"))
|
|
17755
18103
|
},
|
|
17756
18104
|
// Cursor user MCP — global; written only when Cursor is installed (~/.cursor exists).
|
|
17757
18105
|
{
|
|
17758
18106
|
host: "cursor-user",
|
|
17759
18107
|
label: "Cursor user MCP",
|
|
17760
|
-
path: (0,
|
|
18108
|
+
path: (0, import_node_path19.join)(cursorUserDir, "mcp.json"),
|
|
17761
18109
|
format: "json",
|
|
17762
18110
|
present: mcpDirExists(cursorUserDir),
|
|
17763
18111
|
isRepoFile: false,
|
|
17764
18112
|
createWhenFileAbsent: true,
|
|
17765
|
-
content: readTextFile((0,
|
|
18113
|
+
content: readTextFile((0, import_node_path19.join)(cursorUserDir, "mcp.json"))
|
|
17766
18114
|
},
|
|
17767
18115
|
// Codex user config (TOML) — global; written only when Codex is installed (~/.codex exists).
|
|
17768
18116
|
{
|
|
17769
18117
|
host: "codex",
|
|
17770
18118
|
label: "Codex user config",
|
|
17771
|
-
path: (0,
|
|
18119
|
+
path: (0, import_node_path19.join)(codexDir, "config.toml"),
|
|
17772
18120
|
format: "toml",
|
|
17773
18121
|
present: mcpDirExists(codexDir),
|
|
17774
18122
|
isRepoFile: false,
|
|
17775
18123
|
createWhenFileAbsent: true,
|
|
17776
|
-
content: readTextFile((0,
|
|
18124
|
+
content: readTextFile((0, import_node_path19.join)(codexDir, "config.toml"))
|
|
17777
18125
|
}
|
|
17778
18126
|
];
|
|
17779
18127
|
}
|
|
17780
18128
|
function writeMcpConfigFile(path2, content) {
|
|
17781
18129
|
try {
|
|
17782
|
-
(0,
|
|
18130
|
+
(0, import_node_fs19.writeFileSync)(path2, content, "utf8");
|
|
17783
18131
|
return true;
|
|
17784
18132
|
} catch {
|
|
17785
18133
|
return false;
|
|
@@ -17859,7 +18207,7 @@ function strayBrowserArtifactPaths() {
|
|
|
17859
18207
|
const cwd = process.cwd();
|
|
17860
18208
|
return STRAY_BROWSER_ARTIFACT_DIRS.filter((rel) => {
|
|
17861
18209
|
try {
|
|
17862
|
-
return (0,
|
|
18210
|
+
return (0, import_node_fs19.existsSync)((0, import_node_path19.join)(cwd, rel));
|
|
17863
18211
|
} catch {
|
|
17864
18212
|
return false;
|
|
17865
18213
|
}
|
|
@@ -17867,14 +18215,14 @@ function strayBrowserArtifactPaths() {
|
|
|
17867
18215
|
}
|
|
17868
18216
|
function readGitignore() {
|
|
17869
18217
|
try {
|
|
17870
|
-
return (0,
|
|
18218
|
+
return (0, import_node_fs19.readFileSync)(gitignorePath(), "utf8");
|
|
17871
18219
|
} catch {
|
|
17872
18220
|
return null;
|
|
17873
18221
|
}
|
|
17874
18222
|
}
|
|
17875
18223
|
function writeGitignore(content) {
|
|
17876
18224
|
try {
|
|
17877
|
-
(0,
|
|
18225
|
+
(0, import_node_fs19.writeFileSync)(gitignorePath(), content, "utf8");
|
|
17878
18226
|
return true;
|
|
17879
18227
|
} catch {
|
|
17880
18228
|
return false;
|
|
@@ -17916,7 +18264,7 @@ async function runDoctor(opts, io = consoleIo, readOrigin) {
|
|
|
17916
18264
|
const semverPrefix = /^\d+\.\d+\.\d+/;
|
|
17917
18265
|
const isBehind = (installed2, released) => Boolean(installed2 && released && semverPrefix.test(installed2) && semverPrefix.test(released) && compareVersions(installed2, released) < 0);
|
|
17918
18266
|
const opencodeAdapterStale = isBehind(opencodeInstalledVersionForDoctor(), releasedVersion);
|
|
17919
|
-
const cursorCacheStale = (0,
|
|
18267
|
+
const cursorCacheStale = (0, import_node_fs19.existsSync)(cursorPluginCacheRoot()) && (cursorPluginCachePinSnapshots() ?? []).some((p) => isBehind(p.version, releasedVersion));
|
|
17920
18268
|
const healPlan = doctorHealPlan({
|
|
17921
18269
|
isOrgRepo,
|
|
17922
18270
|
surface,
|
|
@@ -17951,7 +18299,7 @@ async function runDoctor(opts, io = consoleIo, readOrigin) {
|
|
|
17951
18299
|
let onPath = pathProbe;
|
|
17952
18300
|
if (!onPath) {
|
|
17953
18301
|
const root = process.env.CLAUDE_PLUGIN_ROOT;
|
|
17954
|
-
if (root && (0,
|
|
18302
|
+
if (root && (0, import_node_fs19.existsSync)(`${root}/bin/mmi-cli${isWin ? ".cmd" : ""}`)) onPath = true;
|
|
17955
18303
|
}
|
|
17956
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" });
|
|
17957
18305
|
const reloadHint = reloadAction(surface);
|
|
@@ -18098,7 +18446,7 @@ async function runDoctor(opts, io = consoleIo, readOrigin) {
|
|
|
18098
18446
|
checks.push(gitignoreCheck);
|
|
18099
18447
|
checks.push(buildRepoLocalWorktreeCheck({
|
|
18100
18448
|
isOrgRepo,
|
|
18101
|
-
hasRepoLocalWorktrees: (0,
|
|
18449
|
+
hasRepoLocalWorktrees: (0, import_node_fs19.existsSync)((0, import_node_path19.join)(process.cwd(), ".worktrees"))
|
|
18102
18450
|
}));
|
|
18103
18451
|
let driftCheck = buildPluginConfigDriftCheck({ isOrgRepo, installed, surface });
|
|
18104
18452
|
if (!driftCheck.ok && driftCheck.recordsToWrite && repairLocal) {
|
|
@@ -18387,7 +18735,7 @@ async function runDoctor(opts, io = consoleIo, readOrigin) {
|
|
|
18387
18735
|
}
|
|
18388
18736
|
checks.push(nestedPluginTreeCheck);
|
|
18389
18737
|
const cursorCacheRoot = cursorPluginCacheRoot();
|
|
18390
|
-
const cursorCacheRootExists = (0,
|
|
18738
|
+
const cursorCacheRootExists = (0, import_node_fs19.existsSync)(cursorCacheRoot);
|
|
18391
18739
|
let cursorPins = cursorPluginCachePinSnapshots() ?? [];
|
|
18392
18740
|
checks.push(
|
|
18393
18741
|
buildCursorPluginCacheCleanupCheck({
|
|
@@ -18415,7 +18763,7 @@ async function runDoctor(opts, io = consoleIo, readOrigin) {
|
|
|
18415
18763
|
cacheRootExists: cursorCacheRootExists,
|
|
18416
18764
|
hubCheckout: hubCheckoutForCursorSeed(),
|
|
18417
18765
|
execFileP: execFileP2,
|
|
18418
|
-
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)),
|
|
18419
18767
|
log: (m) => io.err(m)
|
|
18420
18768
|
});
|
|
18421
18769
|
if (seeded) {
|
|
@@ -18610,23 +18958,23 @@ function mergeGuardHook(settings) {
|
|
|
18610
18958
|
next.hooks = hooks;
|
|
18611
18959
|
return next;
|
|
18612
18960
|
}
|
|
18613
|
-
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");
|
|
18614
18962
|
function ensureUserScopeGuardHook(opts = {}) {
|
|
18615
18963
|
const path2 = opts.settingsPath ?? userScopeSettingsPath();
|
|
18616
18964
|
try {
|
|
18617
18965
|
let current = null;
|
|
18618
|
-
if ((0,
|
|
18966
|
+
if ((0, import_node_fs19.existsSync)(path2)) {
|
|
18619
18967
|
try {
|
|
18620
|
-
current = JSON.parse((0,
|
|
18968
|
+
current = JSON.parse((0, import_node_fs19.readFileSync)(path2, "utf8"));
|
|
18621
18969
|
} catch {
|
|
18622
18970
|
return "failed";
|
|
18623
18971
|
}
|
|
18624
18972
|
}
|
|
18625
18973
|
if (settingsHasGuardHook(current)) return "already";
|
|
18626
18974
|
const merged = mergeGuardHook(current);
|
|
18627
|
-
(0,
|
|
18628
|
-
if ((0,
|
|
18629
|
-
(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)}
|
|
18630
18978
|
`, "utf8");
|
|
18631
18979
|
return "written";
|
|
18632
18980
|
} catch {
|
|
@@ -18763,7 +19111,7 @@ async function applyGcPlan(plan, remote) {
|
|
|
18763
19111
|
cleanupBranch: (branch, expectedHeadOid) => cleanupPrMergeLocalBranch(branch.branch, {
|
|
18764
19112
|
beforeWorktrees,
|
|
18765
19113
|
startingPath: branch.worktreePath,
|
|
18766
|
-
pathExists: (p) => (0,
|
|
19114
|
+
pathExists: (p) => (0, import_node_fs20.existsSync)(p),
|
|
18767
19115
|
execGit: async (args) => (await execFileP2("git", args, { timeout: GIT_TIMEOUT_MS })).stdout,
|
|
18768
19116
|
teardownWorktreeStage,
|
|
18769
19117
|
deferredStore,
|
|
@@ -18786,7 +19134,7 @@ async function applyGcPlan(plan, remote) {
|
|
|
18786
19134
|
for (const wt of plan.worktreeDirs) {
|
|
18787
19135
|
try {
|
|
18788
19136
|
const cleanupTarget = resolveSafeSiblingWorktreeCleanupTarget(wt.path, siblingRoot, {
|
|
18789
|
-
realpath: (path2) => (0,
|
|
19137
|
+
realpath: (path2) => (0, import_node_fs20.realpathSync)(path2)
|
|
18790
19138
|
});
|
|
18791
19139
|
if (!cleanupTarget.ok) {
|
|
18792
19140
|
result.failed.push(`${wt.path}: ${cleanupTarget.reason}`);
|
|
@@ -18829,13 +19177,13 @@ var program2 = new Command();
|
|
|
18829
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)");
|
|
18830
19178
|
var rules = program2.command("rules").description("org-managed .gitignore delivery");
|
|
18831
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) => {
|
|
18832
|
-
const path2 = (0,
|
|
18833
|
-
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;
|
|
18834
19182
|
const plan = planManagedGitignore(current);
|
|
18835
19183
|
const drift = [...plan.added.map((l) => `+${l}`), ...plan.removed.map((l) => `-${l}`)].join(", ") || "block normalize";
|
|
18836
19184
|
if (opts.write) {
|
|
18837
19185
|
if (plan.changed) {
|
|
18838
|
-
(0,
|
|
19186
|
+
(0, import_node_fs20.writeFileSync)(path2, plan.content, "utf8");
|
|
18839
19187
|
console.log(`mmi-cli rules gitignore: updated .gitignore (${drift})`);
|
|
18840
19188
|
} else {
|
|
18841
19189
|
console.log("mmi-cli rules gitignore: up to date");
|
|
@@ -19000,7 +19348,7 @@ function runWorktreeInstall(command, cwd, quiet) {
|
|
|
19000
19348
|
async function primaryCheckoutRoot(worktreeRoot) {
|
|
19001
19349
|
try {
|
|
19002
19350
|
const out = (await execFileP2("git", ["-C", worktreeRoot, "rev-parse", "--path-format=absolute", "--git-common-dir"], { timeout: GIT_TIMEOUT_MS })).stdout.trim();
|
|
19003
|
-
return out ? (0,
|
|
19351
|
+
return out ? (0, import_node_path20.dirname)(out) : void 0;
|
|
19004
19352
|
} catch {
|
|
19005
19353
|
return void 0;
|
|
19006
19354
|
}
|
|
@@ -19015,26 +19363,26 @@ function makeProvisionDeps(worktreeRoot, quiet, log) {
|
|
|
19015
19363
|
function acquireWorktreeSetupLock(worktreeRoot) {
|
|
19016
19364
|
const lockPath = repoRuntimeStatePath(worktreeRoot, "worktree-setup.lock");
|
|
19017
19365
|
const take = () => {
|
|
19018
|
-
const fd = (0,
|
|
19366
|
+
const fd = (0, import_node_fs20.openSync)(lockPath, "wx");
|
|
19019
19367
|
try {
|
|
19020
|
-
(0,
|
|
19368
|
+
(0, import_node_fs20.writeSync)(fd, String(Date.now()));
|
|
19021
19369
|
} finally {
|
|
19022
|
-
(0,
|
|
19370
|
+
(0, import_node_fs20.closeSync)(fd);
|
|
19023
19371
|
}
|
|
19024
19372
|
return () => {
|
|
19025
19373
|
try {
|
|
19026
|
-
(0,
|
|
19374
|
+
(0, import_node_fs20.rmSync)(lockPath, { force: true });
|
|
19027
19375
|
} catch {
|
|
19028
19376
|
}
|
|
19029
19377
|
};
|
|
19030
19378
|
};
|
|
19031
19379
|
try {
|
|
19032
|
-
(0,
|
|
19380
|
+
(0, import_node_fs20.mkdirSync)((0, import_node_path20.dirname)(lockPath), { recursive: true });
|
|
19033
19381
|
return take();
|
|
19034
19382
|
} catch {
|
|
19035
19383
|
try {
|
|
19036
|
-
if (Date.now() - (0,
|
|
19037
|
-
(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 });
|
|
19038
19386
|
return take();
|
|
19039
19387
|
}
|
|
19040
19388
|
} catch {
|
|
@@ -19524,7 +19872,7 @@ project.command("set [owner/repo]").description("upsert project META (idempotent
|
|
|
19524
19872
|
const vars = rawValues("--var");
|
|
19525
19873
|
if (o.secretsFile) {
|
|
19526
19874
|
try {
|
|
19527
|
-
vars.push(`secrets=${(0,
|
|
19875
|
+
vars.push(`secrets=${(0, import_node_fs20.readFileSync)(o.secretsFile, "utf8")}`);
|
|
19528
19876
|
} catch (e) {
|
|
19529
19877
|
return fail(`project set: cannot read --secrets-file ${o.secretsFile}: ${e.message}`);
|
|
19530
19878
|
}
|
|
@@ -20094,9 +20442,9 @@ pr.command("view <number>").description("read a PR as structured JSON (merged st
|
|
|
20094
20442
|
}
|
|
20095
20443
|
});
|
|
20096
20444
|
async function listCiWorkflowPaths(cwd = process.cwd()) {
|
|
20097
|
-
const wfDir = (0,
|
|
20098
|
-
if (!(0,
|
|
20099
|
-
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}`);
|
|
20100
20448
|
}
|
|
20101
20449
|
async function resolveMergeCiPolicyForCheckout(repoOpt) {
|
|
20102
20450
|
const repo = repoOpt ?? await resolveRepo();
|
|
@@ -20115,7 +20463,7 @@ function ciAuditDeps() {
|
|
|
20115
20463
|
// Continuous CI delivery (#1550): the gate re-seed renders from the Hub's on-disk seed templates. The
|
|
20116
20464
|
// reconcile runs IN the Hub checkout, so this is local-file I/O (no network fetch). Path is relative to
|
|
20117
20465
|
// the repo root (e.g. skills/bootstrap/seeds/gate.template.yml).
|
|
20118
|
-
readSeedFile: (path2) => (0,
|
|
20466
|
+
readSeedFile: (path2) => (0, import_node_fs20.existsSync)(path2) ? (0, import_node_fs20.readFileSync)(path2, "utf8") : null
|
|
20119
20467
|
};
|
|
20120
20468
|
}
|
|
20121
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) => {
|
|
@@ -20285,7 +20633,7 @@ async function createDeferredWorktreeStore() {
|
|
|
20285
20633
|
},
|
|
20286
20634
|
write: async (entries) => {
|
|
20287
20635
|
try {
|
|
20288
|
-
await (0, import_promises3.mkdir)((0,
|
|
20636
|
+
await (0, import_promises3.mkdir)((0, import_node_path20.dirname)(registryPath), { recursive: true });
|
|
20289
20637
|
await (0, import_promises3.writeFile)(registryPath, serializeDeferredWorktrees(entries), "utf8");
|
|
20290
20638
|
} catch {
|
|
20291
20639
|
}
|
|
@@ -20299,13 +20647,13 @@ var realWorktreeDirRemover = {
|
|
|
20299
20647
|
probe: (p) => {
|
|
20300
20648
|
let st;
|
|
20301
20649
|
try {
|
|
20302
|
-
st = (0,
|
|
20650
|
+
st = (0, import_node_fs20.lstatSync)(p);
|
|
20303
20651
|
} catch {
|
|
20304
20652
|
return null;
|
|
20305
20653
|
}
|
|
20306
20654
|
if (st.isSymbolicLink()) return "link";
|
|
20307
20655
|
try {
|
|
20308
|
-
(0,
|
|
20656
|
+
(0, import_node_fs20.readlinkSync)(p);
|
|
20309
20657
|
return "link";
|
|
20310
20658
|
} catch {
|
|
20311
20659
|
}
|
|
@@ -20313,7 +20661,7 @@ var realWorktreeDirRemover = {
|
|
|
20313
20661
|
},
|
|
20314
20662
|
readdir: (p) => {
|
|
20315
20663
|
try {
|
|
20316
|
-
return (0,
|
|
20664
|
+
return (0, import_node_fs20.readdirSync)(p);
|
|
20317
20665
|
} catch {
|
|
20318
20666
|
return [];
|
|
20319
20667
|
}
|
|
@@ -20322,9 +20670,9 @@ var realWorktreeDirRemover = {
|
|
|
20322
20670
|
// leaving the target); a file symlink with unlink. rmdir first, fall back to unlink.
|
|
20323
20671
|
detachLink: (p) => {
|
|
20324
20672
|
try {
|
|
20325
|
-
(0,
|
|
20673
|
+
(0, import_node_fs20.rmdirSync)(p);
|
|
20326
20674
|
} catch {
|
|
20327
|
-
(0,
|
|
20675
|
+
(0, import_node_fs20.unlinkSync)(p);
|
|
20328
20676
|
}
|
|
20329
20677
|
},
|
|
20330
20678
|
removeTree: (p) => (0, import_promises3.rm)(p, { recursive: true, force: true, maxRetries: 5, retryDelay: 200 })
|
|
@@ -20354,9 +20702,9 @@ async function worktreeHasStageState(worktreePath) {
|
|
|
20354
20702
|
}
|
|
20355
20703
|
}
|
|
20356
20704
|
function stageStateFileBelongsToWorktree(statePath, worktreePath) {
|
|
20357
|
-
if (!(0,
|
|
20705
|
+
if (!(0, import_node_fs20.existsSync)(statePath)) return false;
|
|
20358
20706
|
try {
|
|
20359
|
-
const state = JSON.parse((0,
|
|
20707
|
+
const state = JSON.parse((0, import_node_fs20.readFileSync)(statePath, "utf8"));
|
|
20360
20708
|
const recordedCwd = typeof state.identity?.cwd === "string" ? state.identity.cwd : typeof state.cwd === "string" ? state.cwd : "";
|
|
20361
20709
|
return Boolean(recordedCwd && isPathUnderDirectory(recordedCwd, worktreePath));
|
|
20362
20710
|
} catch {
|
|
@@ -20428,7 +20776,7 @@ pr.command("merge <number>").description("merge a PR (squash by default) and cle
|
|
|
20428
20776
|
localCleanup = await cleanupPrMergeLocalBranch(headRef, {
|
|
20429
20777
|
beforeWorktrees,
|
|
20430
20778
|
startingPath,
|
|
20431
|
-
pathExists: (p) => (0,
|
|
20779
|
+
pathExists: (p) => (0, import_node_fs20.existsSync)(p),
|
|
20432
20780
|
execGit: async (args) => (await execFileP2("git", args, { timeout: GIT_TIMEOUT_MS })).stdout,
|
|
20433
20781
|
teardownWorktreeStage,
|
|
20434
20782
|
deferredStore,
|
|
@@ -20649,7 +20997,7 @@ function stageScopedRunOpts(o) {
|
|
|
20649
20997
|
};
|
|
20650
20998
|
}
|
|
20651
20999
|
function printLine(value) {
|
|
20652
|
-
(0,
|
|
21000
|
+
(0, import_node_fs20.writeSync)(1, `${value}
|
|
20653
21001
|
`);
|
|
20654
21002
|
}
|
|
20655
21003
|
function stageKeepAlive() {
|
|
@@ -20663,8 +21011,8 @@ async function resolveStage() {
|
|
|
20663
21011
|
const portRange = portRangeMeta && typeof portRangeMeta.start === "number" && typeof portRangeMeta.end === "number" ? [portRangeMeta.start, portRangeMeta.end] : void 0;
|
|
20664
21012
|
return decideStage({
|
|
20665
21013
|
registry: { deployModel: project2?.deployModel, portRange, error: read.ok ? void 0 : read.error },
|
|
20666
|
-
hasCompose: (0,
|
|
20667
|
-
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"))
|
|
20668
21016
|
});
|
|
20669
21017
|
}
|
|
20670
21018
|
async function fetchStageVaultEnvMerge() {
|
|
@@ -20887,7 +21235,11 @@ function trainApplyDeps() {
|
|
|
20887
21235
|
return {
|
|
20888
21236
|
run: async (file, args) => {
|
|
20889
21237
|
const timeout = file === "node" && args[1] === "prepare" ? NODE_PREPARE_TIMEOUT_MS : file === "npm" ? NPM_TRAIN_TIMEOUT_MS : file !== "gh" ? GIT_TIMEOUT_MS : args[0] === "run" && args[1] === "watch" ? GH_RUN_WATCH_TIMEOUT_MS : GH_TRAIN_TIMEOUT_MS;
|
|
20890
|
-
|
|
21238
|
+
try {
|
|
21239
|
+
return isWin2 && file === "npm" ? (await execFileP2("cmd.exe", ["/c", "npm", ...args], { timeout })).stdout : (await execFileP2(file, args, { timeout })).stdout;
|
|
21240
|
+
} catch (e) {
|
|
21241
|
+
throw surfaceTrainSubprocessFailure(e, file, args);
|
|
21242
|
+
}
|
|
20891
21243
|
},
|
|
20892
21244
|
runSelf: async (args) => (await execFileP2(process.execPath, [process.argv[1], ...args], { timeout: 3e4 })).stdout,
|
|
20893
21245
|
trainAuthority: async (repo) => {
|
|
@@ -21113,6 +21465,27 @@ wiki.command("publish <pages-dir>").description("mint a 1h repo-scoped contents:
|
|
|
21113
21465
|
);
|
|
21114
21466
|
if (!ok) process.exitCode = 1;
|
|
21115
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
|
+
});
|
|
21116
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) => {
|
|
21117
21490
|
if (!o.repo) return fail("bootstrap: required option --repo <owner/repo> not specified");
|
|
21118
21491
|
if (o.apply) return fail("bootstrap: execution is not implemented yet; use the dry-run plan and the existing /bootstrap skill");
|
|
@@ -21132,7 +21505,7 @@ bootstrap.command("verify <repo>").description("audit whether an existing repo i
|
|
|
21132
21505
|
client: defaultGitHubClient(),
|
|
21133
21506
|
projectMeta: meta,
|
|
21134
21507
|
deployModel: typeof meta?.deployModel === "string" ? meta.deployModel : void 0,
|
|
21135
|
-
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,
|
|
21136
21509
|
// requiredGcpApis is stored as an array by a JSON write, but `project set --var KEY=VALUE` stores a raw
|
|
21137
21510
|
// comma-string — accept either so the seeded value verifies regardless of how it was written.
|
|
21138
21511
|
requiredGcpApis: (() => {
|
|
@@ -21183,12 +21556,12 @@ bootstrap.command("apply <repo>").description("idempotent seed apply from skills
|
|
|
21183
21556
|
return fail(`bootstrap apply: ${e.message}`);
|
|
21184
21557
|
}
|
|
21185
21558
|
const manifestPath = "skills/bootstrap/seeds/manifest.json";
|
|
21186
|
-
if (!(0,
|
|
21187
|
-
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"));
|
|
21188
21561
|
const baseBranch = o.class === "content" ? "main" : "development";
|
|
21189
21562
|
const slug = parsedRepo.slug;
|
|
21190
21563
|
const gh = async (args) => execFileP2("gh", args, { timeout: 2e4 });
|
|
21191
|
-
const readFile2 = (p) => (0,
|
|
21564
|
+
const readFile2 = (p) => (0, import_node_fs20.existsSync)(p) ? (0, import_node_fs20.readFileSync)(p, "utf8") : null;
|
|
21192
21565
|
const enc = (p) => p.split("/").map(encodeURIComponent).join("/");
|
|
21193
21566
|
const rawVars = {};
|
|
21194
21567
|
for (const value of rawValues("--var")) {
|
|
@@ -21485,16 +21858,16 @@ access.command("audit").description("audit collaborator roles + train-branch pus
|
|
|
21485
21858
|
const repoClass = o.class;
|
|
21486
21859
|
targets = [{ repo: o.repo, class: repoClass, releaseTrack: repoClass === "content" ? "trunk" : resolveReleaseTrack(meta, void 0, o.repo) }];
|
|
21487
21860
|
} else {
|
|
21488
|
-
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;
|
|
21489
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>");
|
|
21490
|
-
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;
|
|
21491
21864
|
targets = loadAccessTargets(projectsJson, fanoutJson);
|
|
21492
21865
|
}
|
|
21493
21866
|
const derivedMatrix = registryProjects ? accessMatrixFromProjects(registryProjects) : {};
|
|
21494
|
-
const fileMatrix = (0,
|
|
21867
|
+
const fileMatrix = (0, import_node_fs20.existsSync)("access-matrix.json") ? loadAccessMatrix((0, import_node_fs20.readFileSync)("access-matrix.json", "utf8")) : {};
|
|
21495
21868
|
const matrix = mergeAccessMatrix(fileMatrix, derivedMatrix);
|
|
21496
21869
|
const derivedContracts = registryProjects ? dataAccessContractsFromProjects(registryProjects) : { consumers: {} };
|
|
21497
|
-
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: {} };
|
|
21498
21871
|
const dataAccess = mergeDataAccessContracts(fileContracts, derivedContracts);
|
|
21499
21872
|
const report = await auditOrgAccess(targets, deps, matrix, dataAccess);
|
|
21500
21873
|
console.log(o.json ? JSON.stringify(report, null, 2) : renderAccessReport(report));
|
|
@@ -21533,6 +21906,15 @@ program2.command("session-start").description("run the SessionStart verbs (whoam
|
|
|
21533
21906
|
// never costs banner time and next session's glance renders instantly within budget.
|
|
21534
21907
|
scheduleRefresh: () => spawnDetachedSelf(["board", "slice-refresh", "--quiet"], { spawn: import_node_child_process12.spawn, execPath: process.execPath, scriptPath: process.argv[1] })
|
|
21535
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
|
+
},
|
|
21536
21918
|
doctor: (io) => runDoctor({ banner: true }, io)
|
|
21537
21919
|
});
|
|
21538
21920
|
await runSessionStart(parallel, sequential, consoleIo);
|