@mutmutco/cli 3.10.0 → 3.12.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/index.cjs +0 -1
- package/dist/main.cjs +1019 -410
- 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_fs21 = 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";
|
|
@@ -3540,7 +3540,6 @@ function resolveClientVersionManifestCandidates(distDir = __dirname) {
|
|
|
3540
3540
|
return [
|
|
3541
3541
|
(0, import_node_path2.join)(distDir, "..", "..", ".claude-plugin", "plugin.json"),
|
|
3542
3542
|
(0, import_node_path2.join)(distDir, "..", "..", ".cursor-plugin", "plugin.json"),
|
|
3543
|
-
(0, import_node_path2.join)(distDir, "..", "..", ".codex-plugin", "plugin.json"),
|
|
3544
3543
|
(0, import_node_path2.join)(distDir, "..", "package.json")
|
|
3545
3544
|
];
|
|
3546
3545
|
}
|
|
@@ -4521,7 +4520,10 @@ function buildSessionStartPlan(verbs) {
|
|
|
4521
4520
|
// Identity reads are memoized process-wide, so this adds no extra gh/Hub round-trip.
|
|
4522
4521
|
{ name: "whoami", run: verbs.whoami },
|
|
4523
4522
|
// board slice (#1230): assigned + claimable glance, 3s cap, fail-soft — flushed after whoami.
|
|
4524
|
-
{ name: "board slice", run: verbs.boardSlice }
|
|
4523
|
+
{ name: "board slice", run: verbs.boardSlice },
|
|
4524
|
+
// command-ladder hint (#2609): compact gh→mmi-cli cheat sheet so agents reach for mmi-cli first,
|
|
4525
|
+
// not after a denied gh call. <1KB, fail-soft — never blocks the banner.
|
|
4526
|
+
{ name: "command ladder", run: verbs.commandLadderHint }
|
|
4525
4527
|
],
|
|
4526
4528
|
sequential: [
|
|
4527
4529
|
// local train sync (#2582): fast-forward local development/main/rc to origin BEFORE doctor + before
|
|
@@ -4955,7 +4957,7 @@ function parseIssueSelector(selector, defaultRepo) {
|
|
|
4955
4957
|
throw new Error(`expected an issue selector like 123, #123, owner/repo#123, or a GitHub issue URL`);
|
|
4956
4958
|
}
|
|
4957
4959
|
function partitionBoardItems(items, viewer, currentRepo, writableRepos) {
|
|
4958
|
-
const empty = () => ({ userOwned: [], claimable: [], taken: [] });
|
|
4960
|
+
const empty = () => ({ userOwned: [], claimable: [], taken: [], unownedInFlight: [] });
|
|
4959
4961
|
const groups = { primary: empty(), secondary: empty() };
|
|
4960
4962
|
const viewerKey = viewer.toLowerCase();
|
|
4961
4963
|
const repoKey = currentRepo.toLowerCase();
|
|
@@ -4970,6 +4972,8 @@ function partitionBoardItems(items, viewer, currentRepo, writableRepos) {
|
|
|
4970
4972
|
groups[scope].claimable.push(item);
|
|
4971
4973
|
} else if (item.assignees.length > 0) {
|
|
4972
4974
|
groups[scope].taken.push(item);
|
|
4975
|
+
} else {
|
|
4976
|
+
groups[scope].unownedInFlight.push(item);
|
|
4973
4977
|
}
|
|
4974
4978
|
}
|
|
4975
4979
|
sortBuckets(groups.primary);
|
|
@@ -5640,6 +5644,7 @@ function renderScope(lines, label, title, buckets) {
|
|
|
5640
5644
|
lines.push("", `${label} \xB7 ${title}`);
|
|
5641
5645
|
renderOwned(lines, buckets.userOwned);
|
|
5642
5646
|
renderClaimable(lines, buckets.claimable);
|
|
5647
|
+
renderUnowned(lines, buckets.unownedInFlight);
|
|
5643
5648
|
renderTaken(lines, buckets.taken);
|
|
5644
5649
|
}
|
|
5645
5650
|
function renderOwned(lines, items) {
|
|
@@ -5662,17 +5667,23 @@ function renderTaken(lines, items) {
|
|
|
5662
5667
|
lines.push("Taken");
|
|
5663
5668
|
for (const item of items) lines.push(` ${item.ref} \xB7 ${item.status} \xB7 @${item.assignees.join(", @")}`);
|
|
5664
5669
|
}
|
|
5670
|
+
function renderUnowned(lines, items) {
|
|
5671
|
+
if (!items.length) return;
|
|
5672
|
+
lines.push("Unowned in-flight (unassigned \u2014 needs an owner)");
|
|
5673
|
+
for (const item of items) lines.push(` ${item.status} \xB7 ${renderTitledItem(item)}`);
|
|
5674
|
+
}
|
|
5665
5675
|
function renderTitledItem(item) {
|
|
5666
5676
|
const pri = item.priority ? ` \xB7 ${item.priority}` : "";
|
|
5667
5677
|
return `${item.ref} - [${item.type ?? "item"}]${pri} ${item.title}`;
|
|
5668
5678
|
}
|
|
5669
5679
|
function hasItems(buckets) {
|
|
5670
|
-
return buckets.userOwned.length > 0 || buckets.claimable.length > 0 || buckets.taken.length > 0;
|
|
5680
|
+
return buckets.userOwned.length > 0 || buckets.claimable.length > 0 || buckets.taken.length > 0 || buckets.unownedInFlight.length > 0;
|
|
5671
5681
|
}
|
|
5672
5682
|
function sortBuckets(buckets) {
|
|
5673
5683
|
buckets.userOwned.sort(compareItems);
|
|
5674
5684
|
buckets.claimable.sort(compareItems);
|
|
5675
5685
|
buckets.taken.sort(compareItems);
|
|
5686
|
+
buckets.unownedInFlight.sort(compareItems);
|
|
5676
5687
|
}
|
|
5677
5688
|
function compareItems(a, b) {
|
|
5678
5689
|
return (STATUS_ORDER.get(a.status) ?? 99) - (STATUS_ORDER.get(b.status) ?? 99) || a.repository.localeCompare(b.repository) || a.number - b.number;
|
|
@@ -5838,9 +5849,31 @@ async function refreshBoardSliceCache(deps) {
|
|
|
5838
5849
|
}
|
|
5839
5850
|
}
|
|
5840
5851
|
|
|
5841
|
-
// src/
|
|
5852
|
+
// src/hook-activity.ts
|
|
5842
5853
|
var import_node_fs9 = require("node:fs");
|
|
5843
5854
|
var import_node_path9 = require("node:path");
|
|
5855
|
+
var DEFAULT_SURFACE = "claude";
|
|
5856
|
+
function activityLogPath(cwd) {
|
|
5857
|
+
return repoRuntimeStatePath(cwd, "hooks", "activity.jsonl");
|
|
5858
|
+
}
|
|
5859
|
+
function appendHookActivity(cwd, entry) {
|
|
5860
|
+
try {
|
|
5861
|
+
const path2 = activityLogPath(cwd);
|
|
5862
|
+
const line = {
|
|
5863
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5864
|
+
surface: DEFAULT_SURFACE,
|
|
5865
|
+
...entry
|
|
5866
|
+
};
|
|
5867
|
+
(0, import_node_fs9.mkdirSync)((0, import_node_path9.dirname)(path2), { recursive: true });
|
|
5868
|
+
(0, import_node_fs9.appendFileSync)(path2, `${JSON.stringify(line)}
|
|
5869
|
+
`, "utf8");
|
|
5870
|
+
} catch {
|
|
5871
|
+
}
|
|
5872
|
+
}
|
|
5873
|
+
|
|
5874
|
+
// src/worktree.ts
|
|
5875
|
+
var import_node_fs10 = require("node:fs");
|
|
5876
|
+
var import_node_path10 = require("node:path");
|
|
5844
5877
|
var LOCAL_ONLY_FILES = [".claude/settings.local.json"];
|
|
5845
5878
|
var PKG = "package.json";
|
|
5846
5879
|
var LOCKFILE = "package-lock.json";
|
|
@@ -5848,21 +5881,21 @@ var NODE_MODULES = "node_modules";
|
|
|
5848
5881
|
var realFsProbe = {
|
|
5849
5882
|
isDir: (p) => {
|
|
5850
5883
|
try {
|
|
5851
|
-
return (0,
|
|
5884
|
+
return (0, import_node_fs10.statSync)(p).isDirectory();
|
|
5852
5885
|
} catch {
|
|
5853
5886
|
return false;
|
|
5854
5887
|
}
|
|
5855
5888
|
},
|
|
5856
5889
|
isFile: (p) => {
|
|
5857
5890
|
try {
|
|
5858
|
-
return (0,
|
|
5891
|
+
return (0, import_node_fs10.statSync)(p).isFile();
|
|
5859
5892
|
} catch {
|
|
5860
5893
|
return false;
|
|
5861
5894
|
}
|
|
5862
5895
|
},
|
|
5863
5896
|
listDirs: (p) => {
|
|
5864
5897
|
try {
|
|
5865
|
-
return (0,
|
|
5898
|
+
return (0, import_node_fs10.readdirSync)(p, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
|
|
5866
5899
|
} catch {
|
|
5867
5900
|
return [];
|
|
5868
5901
|
}
|
|
@@ -5870,12 +5903,12 @@ var realFsProbe = {
|
|
|
5870
5903
|
};
|
|
5871
5904
|
function scanInstallDirs(root, fs2 = realFsProbe) {
|
|
5872
5905
|
const factsFor = (dir) => {
|
|
5873
|
-
const abs = dir ? (0,
|
|
5906
|
+
const abs = dir ? (0, import_node_path10.join)(root, dir) : root;
|
|
5874
5907
|
return {
|
|
5875
5908
|
dir,
|
|
5876
|
-
hasPackageJson: fs2.isFile((0,
|
|
5877
|
-
hasLockfile: fs2.isFile((0,
|
|
5878
|
-
hasNodeModules: fs2.isDir((0,
|
|
5909
|
+
hasPackageJson: fs2.isFile((0, import_node_path10.join)(abs, PKG)),
|
|
5910
|
+
hasLockfile: fs2.isFile((0, import_node_path10.join)(abs, LOCKFILE)),
|
|
5911
|
+
hasNodeModules: fs2.isDir((0, import_node_path10.join)(abs, NODE_MODULES))
|
|
5879
5912
|
};
|
|
5880
5913
|
};
|
|
5881
5914
|
const children = fs2.listDirs(root).filter((name) => name !== NODE_MODULES && name !== ".git");
|
|
@@ -5885,7 +5918,7 @@ function npmInstallTargets(dirs) {
|
|
|
5885
5918
|
return dirs.filter((d) => d.hasPackageJson && !d.hasNodeModules).map((d) => ({ dir: d.dir, command: d.hasLockfile ? "npm ci" : "npm install" }));
|
|
5886
5919
|
}
|
|
5887
5920
|
function isLinkedWorktree(root, fs2 = realFsProbe) {
|
|
5888
|
-
return fs2.isFile((0,
|
|
5921
|
+
return fs2.isFile((0, import_node_path10.join)(root, ".git"));
|
|
5889
5922
|
}
|
|
5890
5923
|
function worktreeAutoProvisionBanner(root, fs2 = realFsProbe) {
|
|
5891
5924
|
if (!isLinkedWorktree(root, fs2)) return null;
|
|
@@ -5895,8 +5928,8 @@ function worktreeAutoProvisionBanner(root, fs2 = realFsProbe) {
|
|
|
5895
5928
|
return `[worktree] provisioning tooling in the background (deps in ${where} + local config) \u2014 \`mmi-cli worktree setup\` to redo`;
|
|
5896
5929
|
}
|
|
5897
5930
|
function defaultCopyFile(from, to) {
|
|
5898
|
-
(0,
|
|
5899
|
-
(0,
|
|
5931
|
+
(0, import_node_fs10.mkdirSync)((0, import_node_path10.dirname)(to), { recursive: true });
|
|
5932
|
+
(0, import_node_fs10.copyFileSync)(from, to);
|
|
5900
5933
|
}
|
|
5901
5934
|
async function provisionWorktree(worktreeRoot, deps) {
|
|
5902
5935
|
const fs2 = deps.fs ?? realFsProbe;
|
|
@@ -5908,7 +5941,7 @@ async function provisionWorktree(worktreeRoot, deps) {
|
|
|
5908
5941
|
const skippedInstall = allDirs.filter((d) => d.hasPackageJson && d.hasNodeModules).map((d) => d.dir);
|
|
5909
5942
|
const installed = [];
|
|
5910
5943
|
for (const target of targets) {
|
|
5911
|
-
const cwd = target.dir ? (0,
|
|
5944
|
+
const cwd = target.dir ? (0, import_node_path10.join)(worktreeRoot, target.dir) : worktreeRoot;
|
|
5912
5945
|
log(`installing deps: ${target.command} in ${target.dir || "."}`);
|
|
5913
5946
|
await deps.runInstall(target.command, cwd);
|
|
5914
5947
|
installed.push(target);
|
|
@@ -5917,7 +5950,7 @@ async function provisionWorktree(worktreeRoot, deps) {
|
|
|
5917
5950
|
const copySkipped = [];
|
|
5918
5951
|
const primary = await deps.primaryCheckout();
|
|
5919
5952
|
for (const rel of LOCAL_ONLY_FILES) {
|
|
5920
|
-
const dest = (0,
|
|
5953
|
+
const dest = (0, import_node_path10.join)(worktreeRoot, rel);
|
|
5921
5954
|
if (fs2.isFile(dest)) {
|
|
5922
5955
|
copySkipped.push({ file: rel, reason: "already-present" });
|
|
5923
5956
|
continue;
|
|
@@ -5926,11 +5959,11 @@ async function provisionWorktree(worktreeRoot, deps) {
|
|
|
5926
5959
|
copySkipped.push({ file: rel, reason: "no-primary" });
|
|
5927
5960
|
continue;
|
|
5928
5961
|
}
|
|
5929
|
-
if (!fs2.isFile((0,
|
|
5962
|
+
if (!fs2.isFile((0, import_node_path10.join)(primary, rel))) {
|
|
5930
5963
|
copySkipped.push({ file: rel, reason: "absent-in-primary" });
|
|
5931
5964
|
continue;
|
|
5932
5965
|
}
|
|
5933
|
-
copyFile((0,
|
|
5966
|
+
copyFile((0, import_node_path10.join)(primary, rel), dest);
|
|
5934
5967
|
copied.push(rel);
|
|
5935
5968
|
log(`copied local config: ${rel}`);
|
|
5936
5969
|
}
|
|
@@ -5938,13 +5971,50 @@ async function provisionWorktree(worktreeRoot, deps) {
|
|
|
5938
5971
|
}
|
|
5939
5972
|
function defaultWorktreePath(repoRoot, branch) {
|
|
5940
5973
|
const safe = branch.replace(/[/\\]+/g, "-");
|
|
5941
|
-
return (0,
|
|
5974
|
+
return (0, import_node_path10.join)((0, import_node_path10.dirname)(repoRoot), "mmi-worktrees", safe);
|
|
5942
5975
|
}
|
|
5943
5976
|
function resolveWorktreeBase(from, remote) {
|
|
5944
5977
|
const remotePrefix = `${remote}/`;
|
|
5945
5978
|
const fetchBranch = from.startsWith(remotePrefix) ? from.slice(remotePrefix.length) : void 0;
|
|
5946
5979
|
return { base: from, fetchBranch };
|
|
5947
5980
|
}
|
|
5981
|
+
var GIT_CONFIG_LOCK_RE = /could not lock config file|unable to write upstream branch configuration/i;
|
|
5982
|
+
function isGitConfigLockError(error) {
|
|
5983
|
+
return GIT_CONFIG_LOCK_RE.test(error instanceof Error ? error.message : String(error));
|
|
5984
|
+
}
|
|
5985
|
+
var ADD_WORKTREE_BACKOFF_MS = [100, 250, 500, 750, 1e3];
|
|
5986
|
+
var ADD_WORKTREE_MAX_ATTEMPTS = 6;
|
|
5987
|
+
async function cleanupOrphanBranch(branch, base, preExistingOid, deps) {
|
|
5988
|
+
if (preExistingOid !== void 0) return;
|
|
5989
|
+
const currentOid = await deps.revParse(`refs/heads/${branch}`);
|
|
5990
|
+
if (currentOid === void 0) return;
|
|
5991
|
+
const baseOid = await deps.revParse(base);
|
|
5992
|
+
if (baseOid !== void 0 && currentOid !== baseOid) return;
|
|
5993
|
+
await deps.deleteBranch(branch);
|
|
5994
|
+
}
|
|
5995
|
+
async function addWorktreeRobust(wtPath, branch, base, deps) {
|
|
5996
|
+
const maxAttempts = deps.maxAttempts ?? ADD_WORKTREE_MAX_ATTEMPTS;
|
|
5997
|
+
const backoff = deps.backoffMs ?? ADD_WORKTREE_BACKOFF_MS;
|
|
5998
|
+
const log = deps.log ?? (() => {
|
|
5999
|
+
});
|
|
6000
|
+
const preExistingOid = await deps.revParse(`refs/heads/${branch}`);
|
|
6001
|
+
for (let i = 0; i < maxAttempts; i++) {
|
|
6002
|
+
try {
|
|
6003
|
+
await deps.git(["worktree", "add", wtPath, "-b", branch, base]);
|
|
6004
|
+
return;
|
|
6005
|
+
} catch (e) {
|
|
6006
|
+
await cleanupOrphanBranch(branch, base, preExistingOid, deps).catch(() => {
|
|
6007
|
+
});
|
|
6008
|
+
const retriesLeft = i < maxAttempts - 1;
|
|
6009
|
+
if (retriesLeft && isGitConfigLockError(e)) {
|
|
6010
|
+
log(`worktree add: .git/config lock busy, retrying (${i + 1}/${maxAttempts - 1})`);
|
|
6011
|
+
await deps.sleep(backoff[Math.min(i, backoff.length - 1)]);
|
|
6012
|
+
continue;
|
|
6013
|
+
}
|
|
6014
|
+
throw e;
|
|
6015
|
+
}
|
|
6016
|
+
}
|
|
6017
|
+
}
|
|
5948
6018
|
|
|
5949
6019
|
// src/whoami.ts
|
|
5950
6020
|
async function resolveWhoami(deps) {
|
|
@@ -5969,8 +6039,39 @@ function whoamiLine(report) {
|
|
|
5969
6039
|
return `current human: ${report.login} (source: ${report.source}) \u2014 act for this login (e.g. claim --for ${report.login}); do not ask who the user is.`;
|
|
5970
6040
|
}
|
|
5971
6041
|
|
|
6042
|
+
// src/command-ladder-hint.ts
|
|
6043
|
+
var COMMAND_LADDER_WRITES = [
|
|
6044
|
+
{ gh: "gh issue create", replacement: "mmi-cli issue create" },
|
|
6045
|
+
{ gh: "gh issue comment", replacement: "mmi-cli issue comment" },
|
|
6046
|
+
{ gh: "gh pr create", replacement: "mmi-cli pr create" },
|
|
6047
|
+
{ gh: "gh pr merge", replacement: "mmi-cli pr merge (or mmi-cli pr land)" }
|
|
6048
|
+
];
|
|
6049
|
+
var COMMAND_LADDER_READS = [
|
|
6050
|
+
{ gh: "gh issue view <N>", replacement: "mmi-cli issue view <N>" },
|
|
6051
|
+
{ gh: "gh pr view <N>", replacement: "mmi-cli pr view <N>" },
|
|
6052
|
+
{ gh: "gh pr checks <N>", replacement: "mmi-cli pr checks-wait <N>" }
|
|
6053
|
+
];
|
|
6054
|
+
var COMMAND_LADDER_BOARD = [
|
|
6055
|
+
{ gh: "gh issue list / project", replacement: "mmi-cli board read" }
|
|
6056
|
+
];
|
|
6057
|
+
function commandLadderHint() {
|
|
6058
|
+
const pad = 28;
|
|
6059
|
+
const lines = [];
|
|
6060
|
+
lines.push("[mmi] Command ladder (#2609): reach for mmi-cli first");
|
|
6061
|
+
for (const e of COMMAND_LADDER_WRITES) {
|
|
6062
|
+
lines.push(`${e.gh.padEnd(pad)} \u2192 ${e.replacement}`);
|
|
6063
|
+
}
|
|
6064
|
+
for (const e of COMMAND_LADDER_READS) {
|
|
6065
|
+
lines.push(`${e.gh.padEnd(pad)} \u2192 ${e.replacement}`);
|
|
6066
|
+
}
|
|
6067
|
+
for (const e of COMMAND_LADDER_BOARD) {
|
|
6068
|
+
lines.push(`${e.gh.padEnd(pad)} \u2192 ${e.replacement}`);
|
|
6069
|
+
}
|
|
6070
|
+
return lines.join("\n");
|
|
6071
|
+
}
|
|
6072
|
+
|
|
5972
6073
|
// src/index.ts
|
|
5973
|
-
var
|
|
6074
|
+
var import_node_path21 = require("node:path");
|
|
5974
6075
|
|
|
5975
6076
|
// src/merge-ci-policy.ts
|
|
5976
6077
|
function resolveMergeCiPolicy(input) {
|
|
@@ -6912,7 +7013,7 @@ async function auditRepoCi(repo, deps) {
|
|
|
6912
7013
|
ok: hasRequiredChecks,
|
|
6913
7014
|
label: "product required status checks active",
|
|
6914
7015
|
detail: hasRequiredChecks ? void 0 : `no required status checks active \u2014 activate ${PRODUCT_RULESET_REF} as a repo ruleset`,
|
|
6915
|
-
remediation: hasRequiredChecks ? void 0 : `Import ${PRODUCT_RULESET_REF} as an active repository ruleset (GitHub \u2192 Settings \u2192 Rules \u2192 Rulesets) \u2014 target: bootstrap
|
|
7016
|
+
remediation: hasRequiredChecks ? void 0 : `Import ${PRODUCT_RULESET_REF} as an active repository ruleset (GitHub \u2192 Settings \u2192 Rules \u2192 Rulesets) \u2014 target: bootstrap apply --execute automation (#1440)`
|
|
6916
7017
|
});
|
|
6917
7018
|
if (hasGateWorkflow) {
|
|
6918
7019
|
const emitted = registryRequiredContexts(meta) ?? await getEmittedPrContexts();
|
|
@@ -7221,6 +7322,13 @@ async function runPrLand(prNumber, options, deps) {
|
|
|
7221
7322
|
error: `checks-wait ${checksWait.status}${checksWait.detail ? `: ${checksWait.detail}` : ""}`
|
|
7222
7323
|
};
|
|
7223
7324
|
}
|
|
7325
|
+
const prState = await deps.readPrState(prNumber, repo);
|
|
7326
|
+
if (prState.mergeStateStatus === "DIRTY" || prState.mergeable === "CONFLICTING") {
|
|
7327
|
+
return {
|
|
7328
|
+
...base,
|
|
7329
|
+
error: `pr land #${prNumber}: branch conflicts with base \u2014 rebase/merge development, rebuild any checked-in artifacts, then re-push before landing`
|
|
7330
|
+
};
|
|
7331
|
+
}
|
|
7224
7332
|
const merge = await mergeAutoWithTransientRetry(prNumber, repo, deps);
|
|
7225
7333
|
base.mergeStatus = merge.mergeStatus;
|
|
7226
7334
|
if (merge.mergeStatus === "failed") {
|
|
@@ -7241,10 +7349,10 @@ async function runPrLand(prNumber, options, deps) {
|
|
|
7241
7349
|
}
|
|
7242
7350
|
|
|
7243
7351
|
// src/wave-status.ts
|
|
7244
|
-
var
|
|
7352
|
+
var import_node_fs12 = require("node:fs");
|
|
7245
7353
|
|
|
7246
7354
|
// src/gc.ts
|
|
7247
|
-
var
|
|
7355
|
+
var import_node_path11 = require("node:path");
|
|
7248
7356
|
var DEFERRED_SWEEP_COMMAND = "mmi-cli gc sweep-deferred";
|
|
7249
7357
|
var DEFERRED_NOTE = "Worktree cleanup queued (IDE lock). Detached sweep will retry automatically \u2014 no human action required.";
|
|
7250
7358
|
var PRESERVED_WORKTREE_CONFIG = "mmi.preservedWorktreeBranch";
|
|
@@ -7515,8 +7623,8 @@ function resolveSafeSiblingWorktreeCleanupTarget(worktreePath, siblingRoot, deps
|
|
|
7515
7623
|
return { ok: true, path: worktreePath };
|
|
7516
7624
|
}
|
|
7517
7625
|
function siblingMmiWorktreesRoot(repoRoot) {
|
|
7518
|
-
const parent = (0,
|
|
7519
|
-
return (0,
|
|
7626
|
+
const parent = (0, import_node_path11.dirname)(repoRoot);
|
|
7627
|
+
return (0, import_node_path11.basename)(parent).toLowerCase() === "mmi-worktrees" ? parent : (0, import_node_path11.join)(parent, "mmi-worktrees");
|
|
7520
7628
|
}
|
|
7521
7629
|
function classifySiblingWorktreeDir(entry) {
|
|
7522
7630
|
if (!entry.ownedByCurrentRepo) {
|
|
@@ -8177,8 +8285,8 @@ function formatGcPlan(plan, apply) {
|
|
|
8177
8285
|
|
|
8178
8286
|
// src/stage-runner.ts
|
|
8179
8287
|
var import_node_child_process6 = require("node:child_process");
|
|
8180
|
-
var
|
|
8181
|
-
var
|
|
8288
|
+
var import_node_fs11 = require("node:fs");
|
|
8289
|
+
var import_node_path12 = require("node:path");
|
|
8182
8290
|
var import_node_net = require("node:net");
|
|
8183
8291
|
var import_node_util6 = require("node:util");
|
|
8184
8292
|
var execFileP4 = (0, import_node_util6.promisify)(import_node_child_process6.execFile);
|
|
@@ -8295,11 +8403,11 @@ function appendForceRecreate(up) {
|
|
|
8295
8403
|
return `${up.trimEnd()} --force-recreate`;
|
|
8296
8404
|
}
|
|
8297
8405
|
function stageStatePath(cwd = process.cwd()) {
|
|
8298
|
-
return (0,
|
|
8406
|
+
return (0, import_node_path12.join)(cwd, "tmp", "stage", "state.json");
|
|
8299
8407
|
}
|
|
8300
8408
|
function stageGlobalStatePath(cwd = process.cwd(), gitCommonDir = ".git") {
|
|
8301
|
-
const dir = (0,
|
|
8302
|
-
return (0,
|
|
8409
|
+
const dir = (0, import_node_path12.isAbsolute)(gitCommonDir) ? gitCommonDir : (0, import_node_path12.resolve)(cwd, gitCommonDir);
|
|
8410
|
+
return (0, import_node_path12.join)(dir, "mmi", "stage", "state.json");
|
|
8303
8411
|
}
|
|
8304
8412
|
function normPath2(path2) {
|
|
8305
8413
|
return path2.replace(/\\/g, "/").replace(/\/+$/, "").toLowerCase();
|
|
@@ -8523,14 +8631,14 @@ function stageProcessEnv(stagePort, extraEnv) {
|
|
|
8523
8631
|
}
|
|
8524
8632
|
async function ensureStageRuntimeEnv(config, opts, cwd) {
|
|
8525
8633
|
if (!config.ensureEnv) return;
|
|
8526
|
-
const target = (0,
|
|
8527
|
-
const example = (0,
|
|
8528
|
-
if (!(0,
|
|
8529
|
-
(0,
|
|
8530
|
-
} else if ((0,
|
|
8531
|
-
const stale = detectStaleEnvFile((0,
|
|
8532
|
-
exampleMtimeMs: (0,
|
|
8533
|
-
targetMtimeMs: (0,
|
|
8634
|
+
const target = (0, import_node_path12.join)(cwd, config.ensureEnv.target);
|
|
8635
|
+
const example = (0, import_node_path12.join)(cwd, config.ensureEnv.example);
|
|
8636
|
+
if (!(0, import_node_fs11.existsSync)(target) && (0, import_node_fs11.existsSync)(example)) {
|
|
8637
|
+
(0, import_node_fs11.copyFileSync)(example, target);
|
|
8638
|
+
} else if ((0, import_node_fs11.existsSync)(target) && (0, import_node_fs11.existsSync)(example)) {
|
|
8639
|
+
const stale = detectStaleEnvFile((0, import_node_fs11.readFileSync)(example, "utf8"), (0, import_node_fs11.readFileSync)(target, "utf8"), {
|
|
8640
|
+
exampleMtimeMs: (0, import_node_fs11.statSync)(example).mtimeMs,
|
|
8641
|
+
targetMtimeMs: (0, import_node_fs11.statSync)(target).mtimeMs
|
|
8534
8642
|
});
|
|
8535
8643
|
if (stale) {
|
|
8536
8644
|
const msg = `stale ${config.ensureEnv.target} (${stale}) \u2014 delete it or refresh from ${config.ensureEnv.example} before re-running /stage`;
|
|
@@ -8538,8 +8646,8 @@ async function ensureStageRuntimeEnv(config, opts, cwd) {
|
|
|
8538
8646
|
console.error(`mmi-cli stage: ${msg} (allowed via --allow-stale-env)`);
|
|
8539
8647
|
}
|
|
8540
8648
|
}
|
|
8541
|
-
if (opts.vaultEnvMerge && Object.keys(opts.vaultEnvMerge).length && (0,
|
|
8542
|
-
(0,
|
|
8649
|
+
if (opts.vaultEnvMerge && Object.keys(opts.vaultEnvMerge).length && (0, import_node_fs11.existsSync)(target)) {
|
|
8650
|
+
(0, import_node_fs11.writeFileSync)(target, mergeEnvSecretsIntoFile((0, import_node_fs11.readFileSync)(target, "utf8"), opts.vaultEnvMerge), "utf8");
|
|
8543
8651
|
}
|
|
8544
8652
|
}
|
|
8545
8653
|
async function gitText(cwd, args) {
|
|
@@ -8569,20 +8677,20 @@ async function resolveGlobalStatePath(cwd, explicit) {
|
|
|
8569
8677
|
return void 0;
|
|
8570
8678
|
}
|
|
8571
8679
|
function readState(path2) {
|
|
8572
|
-
if (!(0,
|
|
8680
|
+
if (!(0, import_node_fs11.existsSync)(path2)) return null;
|
|
8573
8681
|
try {
|
|
8574
|
-
return JSON.parse((0,
|
|
8682
|
+
return JSON.parse((0, import_node_fs11.readFileSync)(path2, "utf8"));
|
|
8575
8683
|
} catch {
|
|
8576
8684
|
return null;
|
|
8577
8685
|
}
|
|
8578
8686
|
}
|
|
8579
8687
|
function mkdirFor(path2) {
|
|
8580
8688
|
const dir = path2.slice(0, Math.max(path2.lastIndexOf("/"), path2.lastIndexOf("\\")));
|
|
8581
|
-
(0,
|
|
8689
|
+
(0, import_node_fs11.mkdirSync)(dir, { recursive: true });
|
|
8582
8690
|
}
|
|
8583
8691
|
function writeState(path2, state) {
|
|
8584
8692
|
mkdirFor(path2);
|
|
8585
|
-
(0,
|
|
8693
|
+
(0, import_node_fs11.writeFileSync)(path2, JSON.stringify(state, null, 2), "utf8");
|
|
8586
8694
|
}
|
|
8587
8695
|
function writeStagePortReservation(port, cwd, statePath, globalStatePath, now) {
|
|
8588
8696
|
const reservation = {
|
|
@@ -8602,7 +8710,7 @@ async function cleanupStageState(state, paths, timeoutMs, fallbackCwd) {
|
|
|
8602
8710
|
await shell(state.teardown.command.trim(), state.teardown.cwd || state.cwd || fallbackCwd, timeoutMs);
|
|
8603
8711
|
}
|
|
8604
8712
|
for (const path2 of [...new Set(paths.filter((p) => Boolean(p)))]) {
|
|
8605
|
-
(0,
|
|
8713
|
+
(0, import_node_fs11.rmSync)(path2, { force: true });
|
|
8606
8714
|
}
|
|
8607
8715
|
}
|
|
8608
8716
|
async function killTree(pid) {
|
|
@@ -8684,6 +8792,7 @@ async function startStage(config = {}, opts = {}) {
|
|
|
8684
8792
|
if (!opts.envPrepared) await ensureStageRuntimeEnv(config, opts, cwd);
|
|
8685
8793
|
if (stagePort != null && portGuard) await ensureStagePortAvailable(stagePort, cwd, portGuard);
|
|
8686
8794
|
const extraEnv = stageExtraEnv(config, stagePort);
|
|
8795
|
+
const vaultProcessEnv = opts.vaultEnvMerge ?? {};
|
|
8687
8796
|
let up = sub(config.up.trim());
|
|
8688
8797
|
if (opts.forceRecreate) up = appendForceRecreate(up);
|
|
8689
8798
|
const identity = await resolveStageIdentity(cwd);
|
|
@@ -8696,7 +8805,8 @@ async function startStage(config = {}, opts = {}) {
|
|
|
8696
8805
|
detached: process.platform !== "win32",
|
|
8697
8806
|
windowsHide: true,
|
|
8698
8807
|
stdio: "ignore",
|
|
8699
|
-
|
|
8808
|
+
// Vault secrets first so the stage-selection contract (MMI_STAGE/MMI_PORT/…) always wins on any collision.
|
|
8809
|
+
env: { ...process.env, ...vaultProcessEnv, ...stageProcessEnv(stagePort, extraEnv) }
|
|
8700
8810
|
});
|
|
8701
8811
|
const state = {
|
|
8702
8812
|
pid: child.pid ?? 0,
|
|
@@ -8758,8 +8868,8 @@ async function runStage(config = {}, opts = {}) {
|
|
|
8758
8868
|
await ensureStageRuntimeEnv(config, opts, cwd);
|
|
8759
8869
|
if (build) await shell(sub(build), cwd, timeoutMs, stageProcessEnv(stagePort, extraEnv));
|
|
8760
8870
|
} catch (e) {
|
|
8761
|
-
(0,
|
|
8762
|
-
if (globalStatePath && globalStatePath !== statePath) (0,
|
|
8871
|
+
(0, import_node_fs11.rmSync)(statePath, { force: true });
|
|
8872
|
+
if (globalStatePath && globalStatePath !== statePath) (0, import_node_fs11.rmSync)(globalStatePath, { force: true });
|
|
8763
8873
|
throw e;
|
|
8764
8874
|
}
|
|
8765
8875
|
const started = await startStage(config, {
|
|
@@ -8784,9 +8894,9 @@ function parseNextFromHead(headText) {
|
|
|
8784
8894
|
}
|
|
8785
8895
|
function readStageSummary(worktreePath) {
|
|
8786
8896
|
const statePath = stageStatePath(worktreePath);
|
|
8787
|
-
if (!(0,
|
|
8897
|
+
if (!(0, import_node_fs12.existsSync)(statePath)) return void 0;
|
|
8788
8898
|
try {
|
|
8789
|
-
const state = JSON.parse((0,
|
|
8899
|
+
const state = JSON.parse((0, import_node_fs12.readFileSync)(statePath, "utf8"));
|
|
8790
8900
|
const port = typeof state.port === "number" ? state.port : void 0;
|
|
8791
8901
|
if (port == null || !Number.isInteger(port) || port <= 0) return void 0;
|
|
8792
8902
|
return { port, url: typeof state.url === "string" ? state.url : void 0 };
|
|
@@ -8902,7 +9012,7 @@ async function resolveAutoAddBoardAttach(client, cfg, selector, priority, warn =
|
|
|
8902
9012
|
// src/gh-create.ts
|
|
8903
9013
|
var import_promises = require("node:fs/promises");
|
|
8904
9014
|
var import_node_os3 = require("node:os");
|
|
8905
|
-
var
|
|
9015
|
+
var import_node_path13 = require("node:path");
|
|
8906
9016
|
var import_node_crypto3 = require("node:crypto");
|
|
8907
9017
|
var ISSUE_TYPES = ["bug", "feature", "task"];
|
|
8908
9018
|
var GH_MUTATION_TIMEOUT_MS = 12e4;
|
|
@@ -8945,8 +9055,8 @@ async function bodyArgsViaFile(args, deps = {}) {
|
|
|
8945
9055
|
const remove = deps.remove ?? import_promises.unlink;
|
|
8946
9056
|
const ensureDir = deps.ensureDir ?? import_promises.mkdir;
|
|
8947
9057
|
const dir = deps.dir ?? (0, import_node_os3.tmpdir)();
|
|
8948
|
-
const file = (0,
|
|
8949
|
-
await ensureDir((0,
|
|
9058
|
+
const file = (0, import_node_path13.join)(dir, `mmi-gh-body-${process.pid}-${(0, import_node_crypto3.randomBytes)(4).toString("hex")}.md`);
|
|
9059
|
+
await ensureDir((0, import_node_path13.dirname)(file), { recursive: true }).catch(() => {
|
|
8950
9060
|
});
|
|
8951
9061
|
await write(file, args[i + 1], "utf8");
|
|
8952
9062
|
return {
|
|
@@ -8983,12 +9093,13 @@ function parseAddedItemId(stdout) {
|
|
|
8983
9093
|
function isAlreadyOnBoardError(stderr) {
|
|
8984
9094
|
return /already exists in (?:this|the) project/i.test(stderr);
|
|
8985
9095
|
}
|
|
8986
|
-
function buildPrArgs({ title, body, base, head, repo }) {
|
|
9096
|
+
function buildPrArgs({ title, body, base, head, repo, draft }) {
|
|
8987
9097
|
const args = ["pr", "create"];
|
|
8988
9098
|
if (repo) args.push("--repo", repo);
|
|
8989
9099
|
args.push("--title", title, "--body", body);
|
|
8990
9100
|
if (base) args.push("--base", base);
|
|
8991
9101
|
if (head) args.push("--head", head);
|
|
9102
|
+
if (draft) args.push("--draft");
|
|
8992
9103
|
return args;
|
|
8993
9104
|
}
|
|
8994
9105
|
|
|
@@ -9683,7 +9794,6 @@ function deriveStageGap(inputs) {
|
|
|
9683
9794
|
return `local stage default applies to central-container repos only (tenant-container/solo-container; registry deployModel = ${inputs.deployModel ?? "unset"})`;
|
|
9684
9795
|
}
|
|
9685
9796
|
if (!inputs.hasCompose) missing.push("docker-compose.yml");
|
|
9686
|
-
if (!inputs.hasEnvExample) missing.push(".env.example");
|
|
9687
9797
|
if (!inputs.portRange) missing.push("Hub registry portRange");
|
|
9688
9798
|
return missing.length ? `cannot derive a default local stage \u2014 missing: ${missing.join(", ")}` : null;
|
|
9689
9799
|
}
|
|
@@ -9699,7 +9809,9 @@ function deriveStage(inputs) {
|
|
|
9699
9809
|
// answered" — any HTTP status — is the health signal, not a 2xx on the bare root.
|
|
9700
9810
|
healthAnyStatus: true,
|
|
9701
9811
|
portRange: inputs.portRange,
|
|
9702
|
-
|
|
9812
|
+
// #2655: only bootstrap a `.env` when the repo ships `.env.example` (legacy). Without it, the stage is
|
|
9813
|
+
// vault-native — the runner injects the declared dev secrets into the compose process env (no disk file).
|
|
9814
|
+
...inputs.hasEnvExample ? { ensureEnv: { example: ".env.example", target: ".env" } } : {},
|
|
9703
9815
|
// MMI_PORT is the host-published port (the free STAGE_PORT); PORT is intentionally absent so compose
|
|
9704
9816
|
// reads the container's bind port from .env rather than this overriding it to the publish port (#1505).
|
|
9705
9817
|
env: {
|
|
@@ -9777,7 +9889,9 @@ async function runStageLiveUp(deps, t) {
|
|
|
9777
9889
|
ref: t.ref,
|
|
9778
9890
|
ip,
|
|
9779
9891
|
dispatched: ["tenant-deploy.yml", "tenant-control.yml"],
|
|
9780
|
-
|
|
9892
|
+
// #2656: the gate now writes a skip+block pair and self-verifies from the runner (a non-allowed vantage),
|
|
9893
|
+
// so a gate that failed to close fails the run RED. The stage is private only once that gate run is green.
|
|
9894
|
+
message: `dispatched the dev deploy of ${t.ref} and the Cloudflare edge gate for ${t.host} \u2192 ${ip}; watch the runs in ${STAGE_LIVE_HUB_REPO} Actions and treat the stage as private ONLY once the cf-gate-allow run is green (it self-verifies the host is blocked) \u2014 tear down with: mmi-cli stage --live --down --apply`
|
|
9781
9895
|
};
|
|
9782
9896
|
}
|
|
9783
9897
|
async function runStageLiveDown(deps, t) {
|
|
@@ -10446,7 +10560,16 @@ async function probeRemoteTag(deps, tag) {
|
|
|
10446
10560
|
const remoteOut = await runGitRemoteRead(deps, ["ls-remote", "origin", `refs/tags/${tag}`]);
|
|
10447
10561
|
return clean2(remoteOut).split(/\s+/)[0] || "";
|
|
10448
10562
|
}
|
|
10449
|
-
async function
|
|
10563
|
+
async function isStrayUnreleasedTag(deps, tag, remoteSha, repo) {
|
|
10564
|
+
try {
|
|
10565
|
+
await deps.run("git", ["merge-base", "--is-ancestor", remoteSha, "origin/main"]);
|
|
10566
|
+
return false;
|
|
10567
|
+
} catch (e) {
|
|
10568
|
+
if (e.code !== 1) return false;
|
|
10569
|
+
}
|
|
10570
|
+
return deps.run("gh", ["release", "view", tag, "--repo", repo, "--json", "tagName"]).then(() => false).catch((e) => /not found|HTTP 404/i.test(e instanceof Error ? e.message : String(e)));
|
|
10571
|
+
}
|
|
10572
|
+
async function ensureTagPushed(deps, tag, sha, probed, releaseRepo) {
|
|
10450
10573
|
const remoteSha = probed ? probed.remoteSha : await probeRemoteTag(deps, tag);
|
|
10451
10574
|
let localSha = "";
|
|
10452
10575
|
try {
|
|
@@ -10455,8 +10578,14 @@ async function ensureTagPushed(deps, tag, sha, probed) {
|
|
|
10455
10578
|
}
|
|
10456
10579
|
if (remoteSha) {
|
|
10457
10580
|
if (remoteSha !== sha) {
|
|
10581
|
+
const mismatch = `tag ${tag} already exists on origin at ${remoteSha}, but this run intends ${sha}`;
|
|
10582
|
+
if (releaseRepo && await isStrayUnreleasedTag(deps, tag, remoteSha, releaseRepo)) {
|
|
10583
|
+
throw new Error(
|
|
10584
|
+
`${mismatch}. The existing tag is not reachable from origin/main and has no GitHub Release \u2014 it was pushed outside the train, not by a completed release. Sanctioned recovery: delete the stray tag (git push origin --delete ${tag}; git tag -d ${tag} if it exists locally), check the repo's Actions for any workflow the stray tag already triggered, then rerun mmi-cli release --apply (if a publish already ran off the stray tag, mint the next version instead of reusing this one). Never complete the release by hand \u2014 a manual GitHub Release or branch push bypasses the train and leaves main behind.`
|
|
10585
|
+
);
|
|
10586
|
+
}
|
|
10458
10587
|
throw new Error(
|
|
10459
|
-
|
|
10588
|
+
`${mismatch} \u2014 refusing to touch a pushed tag. Repair manually: either release the existing tagged commit (reset the local branch to ${remoteSha} and rerun), or mint the next candidate version so a fresh tag is used. Never force-move or delete a pushed tag.`
|
|
10460
10589
|
);
|
|
10461
10590
|
}
|
|
10462
10591
|
if (localSha && localSha !== sha) {
|
|
@@ -10823,7 +10952,7 @@ async function runFoldStage(deps, startBranch, preFold, fn) {
|
|
|
10823
10952
|
async function completeMainRelease(deps, ctx, meta, deployModel, watch, options, tag, releaseSha, startBranch, preFold, tagProbe) {
|
|
10824
10953
|
let tagPush;
|
|
10825
10954
|
try {
|
|
10826
|
-
tagPush = await ensureTagPushed(deps, tag, releaseSha, tagProbe);
|
|
10955
|
+
tagPush = await ensureTagPushed(deps, tag, releaseSha, tagProbe, ctx.repo);
|
|
10827
10956
|
} catch (e) {
|
|
10828
10957
|
throw await recoverFailedFold(deps, e, startBranch, preFold.mainSha);
|
|
10829
10958
|
}
|
|
@@ -12000,8 +12129,8 @@ async function announceRelease(deps, args) {
|
|
|
12000
12129
|
}
|
|
12001
12130
|
|
|
12002
12131
|
// src/port-registry.ts
|
|
12003
|
-
var
|
|
12004
|
-
var
|
|
12132
|
+
var import_node_fs13 = require("node:fs");
|
|
12133
|
+
var import_node_path14 = require("node:path");
|
|
12005
12134
|
|
|
12006
12135
|
// ../infra/port-geometry.mjs
|
|
12007
12136
|
var PORT_BLOCK = 100;
|
|
@@ -12015,8 +12144,8 @@ function nextPortBlock(registry2) {
|
|
|
12015
12144
|
return [base, base + PORT_SPAN];
|
|
12016
12145
|
}
|
|
12017
12146
|
function loadPortRegistry(path2) {
|
|
12018
|
-
if (!(0,
|
|
12019
|
-
const raw = JSON.parse((0,
|
|
12147
|
+
if (!(0, import_node_fs13.existsSync)(path2)) return {};
|
|
12148
|
+
const raw = JSON.parse((0, import_node_fs13.readFileSync)(path2, "utf8"));
|
|
12020
12149
|
const out = {};
|
|
12021
12150
|
for (const [key, value] of Object.entries(raw)) {
|
|
12022
12151
|
if (Array.isArray(value) && value.length === 2 && value.every((n) => typeof n === "number")) {
|
|
@@ -12030,9 +12159,9 @@ function ensurePortRange(repo, path2) {
|
|
|
12030
12159
|
const existing = registry2[repo];
|
|
12031
12160
|
if (existing) return existing;
|
|
12032
12161
|
const range = nextPortBlock(registry2);
|
|
12033
|
-
const raw = (0,
|
|
12162
|
+
const raw = (0, import_node_fs13.existsSync)(path2) ? JSON.parse((0, import_node_fs13.readFileSync)(path2, "utf8")) : {};
|
|
12034
12163
|
raw[repo] = range;
|
|
12035
|
-
(0,
|
|
12164
|
+
(0, import_node_fs13.writeFileSync)(path2, JSON.stringify(raw, null, 2) + "\n", "utf8");
|
|
12036
12165
|
return range;
|
|
12037
12166
|
}
|
|
12038
12167
|
function portCursorSeed(registry2) {
|
|
@@ -12054,20 +12183,26 @@ function existingPortRange(repo, registry2) {
|
|
|
12054
12183
|
return registry2[repo] ?? null;
|
|
12055
12184
|
}
|
|
12056
12185
|
function portRangeInfraAt(root, source) {
|
|
12057
|
-
const registryPath = (0,
|
|
12058
|
-
const ddbScriptPath = (0,
|
|
12059
|
-
if (!(0,
|
|
12186
|
+
const registryPath = (0, import_node_path14.join)(root, "infra", "port-ranges.json");
|
|
12187
|
+
const ddbScriptPath = (0, import_node_path14.join)(root, "infra", "port-ddb.mjs");
|
|
12188
|
+
if (!(0, import_node_fs13.existsSync)(registryPath) || !(0, import_node_fs13.existsSync)(ddbScriptPath)) return null;
|
|
12060
12189
|
return { root, source, registryPath, ddbScriptPath };
|
|
12061
12190
|
}
|
|
12062
|
-
function resolvePortRangeInfra(cwd) {
|
|
12191
|
+
function resolvePortRangeInfra(cwd, packageDir) {
|
|
12063
12192
|
const direct = portRangeInfraAt(cwd, "cwd");
|
|
12064
12193
|
if (direct) return direct;
|
|
12065
|
-
for (let dir = cwd; ; dir = (0,
|
|
12066
|
-
const sibling = portRangeInfraAt((0,
|
|
12194
|
+
for (let dir = cwd; ; dir = (0, import_node_path14.dirname)(dir)) {
|
|
12195
|
+
const sibling = portRangeInfraAt((0, import_node_path14.join)(dir, "MMI-Hub"), "sibling-hub");
|
|
12067
12196
|
if (sibling) return sibling;
|
|
12068
|
-
const parent = (0,
|
|
12069
|
-
if (parent === dir)
|
|
12197
|
+
const parent = (0, import_node_path14.dirname)(dir);
|
|
12198
|
+
if (parent === dir) break;
|
|
12199
|
+
}
|
|
12200
|
+
if (packageDir) {
|
|
12201
|
+
const pkgRoot = (0, import_node_path14.join)(packageDir, "..", "..");
|
|
12202
|
+
const pkgFrom = portRangeInfraAt(pkgRoot, "pkg-root");
|
|
12203
|
+
if (pkgFrom) return pkgFrom;
|
|
12070
12204
|
}
|
|
12205
|
+
return null;
|
|
12071
12206
|
}
|
|
12072
12207
|
async function ensurePortRangeAtomic(repo, path2, allocate, opts = {}) {
|
|
12073
12208
|
const registry2 = loadPortRegistry(path2);
|
|
@@ -12954,27 +13089,39 @@ async function tenantDeploy(payload, deps) {
|
|
|
12954
13089
|
// src/wiki-publish-command.ts
|
|
12955
13090
|
async function wikiPublish(deps, opts) {
|
|
12956
13091
|
if (!opts.pagesDir) {
|
|
12957
|
-
|
|
13092
|
+
if (opts.json) {
|
|
13093
|
+
deps.log(JSON.stringify({ ok: false, error: "pages-dir is required" }, null, 2));
|
|
13094
|
+
} else {
|
|
13095
|
+
deps.err("wiki publish: <pages-dir> is required");
|
|
13096
|
+
}
|
|
12958
13097
|
return false;
|
|
12959
13098
|
}
|
|
12960
13099
|
const minted = await deps.mint(opts.repo);
|
|
12961
13100
|
if (!minted.ok) {
|
|
12962
|
-
|
|
13101
|
+
if (opts.json) {
|
|
13102
|
+
deps.log(JSON.stringify({ ok: false, error: minted.error }, null, 2));
|
|
13103
|
+
} else {
|
|
13104
|
+
deps.err(`wiki publish failed: could not mint a scoped wiki token for ${opts.repo}: ${minted.error}`);
|
|
13105
|
+
}
|
|
12963
13106
|
return false;
|
|
12964
13107
|
}
|
|
12965
|
-
const ok = deps.publish({ repo: opts.repo, token: minted.mint.token, pagesDir: opts.pagesDir });
|
|
13108
|
+
const ok = deps.publish({ repo: opts.repo, token: minted.mint.token, pagesDir: opts.pagesDir, json: opts.json });
|
|
12966
13109
|
if (!ok) {
|
|
12967
13110
|
return false;
|
|
12968
13111
|
}
|
|
12969
|
-
|
|
13112
|
+
if (opts.json) {
|
|
13113
|
+
deps.log(JSON.stringify({ ok: true, repo: opts.repo }, null, 2));
|
|
13114
|
+
} else {
|
|
13115
|
+
deps.log(`wiki published for ${opts.repo} (scoped token, expired/discarded)`);
|
|
13116
|
+
}
|
|
12970
13117
|
return true;
|
|
12971
13118
|
}
|
|
12972
13119
|
|
|
12973
13120
|
// src/wiki-publish-git.ts
|
|
12974
13121
|
var import_node_child_process8 = require("node:child_process");
|
|
12975
|
-
var
|
|
13122
|
+
var import_node_fs14 = require("node:fs");
|
|
12976
13123
|
var import_node_os5 = require("node:os");
|
|
12977
|
-
var
|
|
13124
|
+
var import_node_path15 = require("node:path");
|
|
12978
13125
|
var WIKI_STAMP_FILE = ".wiki-state.json";
|
|
12979
13126
|
function redactWikiSecrets(msg) {
|
|
12980
13127
|
return msg.replace(/x-access-token:[^@\s]+@/gi, "x-access-token:***@").replace(/(AUTHORIZATION:\s*basic\s+)[A-Za-z0-9+/=]+/gi, "$1***");
|
|
@@ -12986,6 +13133,7 @@ function publishWikiViaGit(deps, opts) {
|
|
|
12986
13133
|
const dir = deps.mkdtemp();
|
|
12987
13134
|
const url = `https://github.com/${opts.repo}.wiki.git`;
|
|
12988
13135
|
const authArgs = wikiAuthArgs(opts.token);
|
|
13136
|
+
const json = opts.json ?? false;
|
|
12989
13137
|
try {
|
|
12990
13138
|
deps.gitRun(["init", "-q"], dir);
|
|
12991
13139
|
deps.gitRun(["remote", "add", "origin", url], dir);
|
|
@@ -12995,20 +13143,20 @@ function publishWikiViaGit(deps, opts) {
|
|
|
12995
13143
|
let copied = 0;
|
|
12996
13144
|
for (const f of deps.readdir(opts.pagesDir)) {
|
|
12997
13145
|
if (f.endsWith(".md")) {
|
|
12998
|
-
deps.copyFile((0,
|
|
13146
|
+
deps.copyFile((0, import_node_path15.join)(opts.pagesDir, f), (0, import_node_path15.join)(dir, f));
|
|
12999
13147
|
copied++;
|
|
13000
13148
|
}
|
|
13001
13149
|
}
|
|
13002
13150
|
if (!copied) {
|
|
13003
|
-
deps.err(`wiki publish: no .md pages found in ${opts.pagesDir}`);
|
|
13151
|
+
if (!json) deps.err(`wiki publish: no .md pages found in ${opts.pagesDir}`);
|
|
13004
13152
|
return false;
|
|
13005
13153
|
}
|
|
13006
|
-
if (deps.fileExists((0,
|
|
13007
|
-
deps.copyFile((0,
|
|
13154
|
+
if (deps.fileExists((0, import_node_path15.join)(opts.pagesDir, WIKI_STAMP_FILE))) {
|
|
13155
|
+
deps.copyFile((0, import_node_path15.join)(opts.pagesDir, WIKI_STAMP_FILE), (0, import_node_path15.join)(dir, WIKI_STAMP_FILE));
|
|
13008
13156
|
}
|
|
13009
13157
|
deps.gitRun(["add", "-A"], dir);
|
|
13010
13158
|
if (deps.gitProbe(["diff", "--cached", "--quiet"], dir)) {
|
|
13011
|
-
deps.log("wiki publish: already up to date");
|
|
13159
|
+
if (!json) deps.log("wiki publish: already up to date");
|
|
13012
13160
|
return true;
|
|
13013
13161
|
}
|
|
13014
13162
|
deps.gitRun(
|
|
@@ -13026,15 +13174,15 @@ function publishWikiViaGit(deps, opts) {
|
|
|
13026
13174
|
try {
|
|
13027
13175
|
deps.gitRun([...authArgs, "push", "origin", "HEAD:master"], dir);
|
|
13028
13176
|
} catch (e) {
|
|
13029
|
-
deps.err(
|
|
13177
|
+
if (!json) deps.err(
|
|
13030
13178
|
`wiki publish failed: push to ${opts.repo}.wiki.git rejected: ${redactWikiSecrets(e.message)} \u2014 if the wiki advanced before this push, re-run wiki publish to pick up the latest wiki state`
|
|
13031
13179
|
);
|
|
13032
13180
|
return false;
|
|
13033
13181
|
}
|
|
13034
|
-
deps.log(`wiki publish: published ${copied} page(s) to ${opts.repo} wiki`);
|
|
13182
|
+
if (!json) deps.log(`wiki publish: published ${copied} page(s) to ${opts.repo} wiki`);
|
|
13035
13183
|
return true;
|
|
13036
13184
|
} catch (e) {
|
|
13037
|
-
deps.err(`wiki publish failed: ${redactWikiSecrets(e.message)}`);
|
|
13185
|
+
if (!json) deps.err(`wiki publish failed: ${redactWikiSecrets(e.message)}`);
|
|
13038
13186
|
return false;
|
|
13039
13187
|
} finally {
|
|
13040
13188
|
deps.rm(dir);
|
|
@@ -13054,7 +13202,7 @@ function readWikiStampViaGit(deps, opts) {
|
|
|
13054
13202
|
return { ok: true, stamp: {} };
|
|
13055
13203
|
}
|
|
13056
13204
|
deps.gitRun(["reset", "--hard", "FETCH_HEAD"], dir);
|
|
13057
|
-
const stampPath = (0,
|
|
13205
|
+
const stampPath = (0, import_node_path15.join)(dir, WIKI_STAMP_FILE);
|
|
13058
13206
|
if (!deps.fileExists(stampPath)) return { ok: true, stamp: {} };
|
|
13059
13207
|
const parsed = JSON.parse(deps.readFile(stampPath));
|
|
13060
13208
|
return { ok: true, stamp: parsed && typeof parsed === "object" ? parsed : {} };
|
|
@@ -13066,12 +13214,12 @@ function readWikiStampViaGit(deps, opts) {
|
|
|
13066
13214
|
}
|
|
13067
13215
|
function realWikiGitPublishDeps() {
|
|
13068
13216
|
return {
|
|
13069
|
-
mkdtemp: () => (0,
|
|
13070
|
-
readdir: (dir) => (0,
|
|
13071
|
-
copyFile: (src, dest) => (0,
|
|
13072
|
-
fileExists: (p) => (0,
|
|
13073
|
-
readFile: (p) => (0,
|
|
13074
|
-
rm: (p) => (0,
|
|
13217
|
+
mkdtemp: () => (0, import_node_fs14.mkdtempSync)((0, import_node_path15.join)((0, import_node_os5.tmpdir)(), "wiki-")),
|
|
13218
|
+
readdir: (dir) => (0, import_node_fs14.readdirSync)(dir),
|
|
13219
|
+
copyFile: (src, dest) => (0, import_node_fs14.copyFileSync)(src, dest),
|
|
13220
|
+
fileExists: (p) => (0, import_node_fs14.existsSync)(p),
|
|
13221
|
+
readFile: (p) => (0, import_node_fs14.readFileSync)(p, "utf8"),
|
|
13222
|
+
rm: (p) => (0, import_node_fs14.rmSync)(p, { recursive: true, force: true }),
|
|
13075
13223
|
gitProbe: (args, cwd) => {
|
|
13076
13224
|
try {
|
|
13077
13225
|
(0, import_node_child_process8.execFileSync)("git", args, { cwd, stdio: "pipe" });
|
|
@@ -13090,8 +13238,8 @@ function realWikiGitPublishDeps() {
|
|
|
13090
13238
|
|
|
13091
13239
|
// src/wiki-pages.ts
|
|
13092
13240
|
var import_node_crypto4 = require("node:crypto");
|
|
13093
|
-
var
|
|
13094
|
-
var
|
|
13241
|
+
var import_node_fs15 = require("node:fs");
|
|
13242
|
+
var import_node_path16 = require("node:path");
|
|
13095
13243
|
var MARKER_RE = /<!--\s*wiki:page\s+([^>]*?)-->/g;
|
|
13096
13244
|
var attr = (raw, key) => {
|
|
13097
13245
|
const m = new RegExp(`${key}\\s*=\\s*"([^"]*)"`).exec(raw);
|
|
@@ -13117,15 +13265,15 @@ var DURABLE_GLOBS = [
|
|
|
13117
13265
|
"docs/org-readme.md"
|
|
13118
13266
|
];
|
|
13119
13267
|
var walkMarkdown = (root, rel) => {
|
|
13120
|
-
const abs = (0,
|
|
13268
|
+
const abs = (0, import_node_path16.join)(root, rel);
|
|
13121
13269
|
let st;
|
|
13122
13270
|
try {
|
|
13123
|
-
st = (0,
|
|
13271
|
+
st = (0, import_node_fs15.statSync)(abs);
|
|
13124
13272
|
} catch {
|
|
13125
13273
|
return [];
|
|
13126
13274
|
}
|
|
13127
13275
|
if (st.isFile()) return rel.endsWith(".md") ? [rel] : [];
|
|
13128
|
-
return (0,
|
|
13276
|
+
return (0, import_node_fs15.readdirSync)(abs).flatMap((name) => walkMarkdown(root, (0, import_node_path16.join)(rel, name)));
|
|
13129
13277
|
};
|
|
13130
13278
|
function collectFeaturePages({ listDocs, readDoc }) {
|
|
13131
13279
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -13144,7 +13292,7 @@ function fsReaders(rootDir) {
|
|
|
13144
13292
|
listDocs: () => DURABLE_GLOBS.flatMap((g) => walkMarkdown(rootDir, g)),
|
|
13145
13293
|
readDoc: (p) => {
|
|
13146
13294
|
try {
|
|
13147
|
-
return (0,
|
|
13295
|
+
return (0, import_node_fs15.readFileSync)((0, import_node_path16.join)(rootDir, p), "utf8");
|
|
13148
13296
|
} catch {
|
|
13149
13297
|
return "";
|
|
13150
13298
|
}
|
|
@@ -14473,7 +14621,12 @@ async function targetRepo(deps, opts) {
|
|
|
14473
14621
|
return opts.repo ?? repoOf(await vaultSlug(deps, opts));
|
|
14474
14622
|
}
|
|
14475
14623
|
async function secretsWhere(deps, opts) {
|
|
14476
|
-
|
|
14624
|
+
const p = vaultPointer(await vaultSlug(deps, opts));
|
|
14625
|
+
if (opts.json) {
|
|
14626
|
+
deps.log(JSON.stringify(p, null, 2));
|
|
14627
|
+
return;
|
|
14628
|
+
}
|
|
14629
|
+
deps.log(formatVaultPointer(p));
|
|
14477
14630
|
}
|
|
14478
14631
|
async function readErr(res) {
|
|
14479
14632
|
try {
|
|
@@ -14536,6 +14689,10 @@ async function secretsList(deps, opts) {
|
|
|
14536
14689
|
return;
|
|
14537
14690
|
}
|
|
14538
14691
|
const { secrets } = await res.json();
|
|
14692
|
+
if (opts.json) {
|
|
14693
|
+
deps.log(JSON.stringify({ secrets: secrets ?? [] }, null, 2));
|
|
14694
|
+
return;
|
|
14695
|
+
}
|
|
14539
14696
|
deps.log(formatSecretList(secrets ?? []));
|
|
14540
14697
|
}
|
|
14541
14698
|
var CAPABILITIES_TIMEOUT_MS = 2e4;
|
|
@@ -14751,6 +14908,13 @@ function requiredRuntimeSecretNames(stage2, contract, opts = {}) {
|
|
|
14751
14908
|
function stageKey2(stage2, key) {
|
|
14752
14909
|
return key.includes("/") ? key : `${stage2}/${key}`;
|
|
14753
14910
|
}
|
|
14911
|
+
function keyDeclaredPerStage(declaredSecrets, key) {
|
|
14912
|
+
if (!declaredSecrets) return void 0;
|
|
14913
|
+
const entry = declaredSecrets[key];
|
|
14914
|
+
if (!entry) return void 0;
|
|
14915
|
+
if (!Array.isArray(entry.stages) || entry.stages.length === 0) return false;
|
|
14916
|
+
return true;
|
|
14917
|
+
}
|
|
14754
14918
|
async function secretsPreflight(deps, opts) {
|
|
14755
14919
|
const repo = await targetRepo(deps, opts);
|
|
14756
14920
|
const qs = new URLSearchParams({ repo }).toString();
|
|
@@ -14771,9 +14935,23 @@ async function secretsPreflight(deps, opts) {
|
|
|
14771
14935
|
}
|
|
14772
14936
|
const { secrets } = await res.json();
|
|
14773
14937
|
const present = new Set((secrets ?? []).map((s) => s.key));
|
|
14774
|
-
const missing = opts.required.filter(
|
|
14775
|
-
|
|
14776
|
-
|
|
14938
|
+
const missing = opts.required.filter((key) => {
|
|
14939
|
+
const perStage = keyDeclaredPerStage(opts.declaredSecrets, key);
|
|
14940
|
+
if (key.includes("/")) {
|
|
14941
|
+
return !present.has(key);
|
|
14942
|
+
}
|
|
14943
|
+
if (perStage === false) {
|
|
14944
|
+
return !present.has(key);
|
|
14945
|
+
}
|
|
14946
|
+
if (perStage === true) {
|
|
14947
|
+
return !present.has(stageKey2(opts.stage, key));
|
|
14948
|
+
}
|
|
14949
|
+
return !present.has(stageKey2(opts.stage, key)) && !present.has(key);
|
|
14950
|
+
});
|
|
14951
|
+
if (opts.json) {
|
|
14952
|
+
deps.log(JSON.stringify({ ok: missing.length === 0, stage: opts.stage, required: opts.required, missing, present: Array.from(present) }, null, 2));
|
|
14953
|
+
return missing.length === 0;
|
|
14954
|
+
}
|
|
14777
14955
|
if (missing.length) {
|
|
14778
14956
|
deps.log(`missing ${missing.map((key) => key.includes("/") ? key : `${stageKey2(opts.stage, key)} (and no stageless ${key})`).join(", ")}`);
|
|
14779
14957
|
return false;
|
|
@@ -14998,9 +15176,6 @@ async function secretsImportRailsCredentials(deps, opts) {
|
|
|
14998
15176
|
}
|
|
14999
15177
|
return true;
|
|
15000
15178
|
}
|
|
15001
|
-
async function secretsEdit(deps, key, opts) {
|
|
15002
|
-
return secretsSet(deps, key, opts);
|
|
15003
|
-
}
|
|
15004
15179
|
async function secretsRemove(deps, key, opts) {
|
|
15005
15180
|
if (!isValidSecretKey(key)) return deps.err(`invalid secret key ${JSON.stringify(key)}`);
|
|
15006
15181
|
const repo = await targetRepo(deps, opts);
|
|
@@ -15096,8 +15271,12 @@ async function secretsCopy(deps, opts) {
|
|
|
15096
15271
|
}
|
|
15097
15272
|
return true;
|
|
15098
15273
|
}
|
|
15274
|
+
function resolveSpawnTarget(command, args, platform2 = process.platform) {
|
|
15275
|
+
return platform2 === "win32" ? { cmd: "cmd.exe", args: ["/d", "/s", "/c", command, ...args] } : { cmd: command, args };
|
|
15276
|
+
}
|
|
15099
15277
|
function defaultSpawn(command, args, env) {
|
|
15100
|
-
const
|
|
15278
|
+
const target = resolveSpawnTarget(command, args);
|
|
15279
|
+
const r = (0, import_node_child_process9.spawnSync)(target.cmd, target.args, { stdio: "inherit", env });
|
|
15101
15280
|
if (r.error) throw r.error;
|
|
15102
15281
|
return r.status ?? 1;
|
|
15103
15282
|
}
|
|
@@ -15157,8 +15336,8 @@ async function secretsUse(deps, key, opts) {
|
|
|
15157
15336
|
}
|
|
15158
15337
|
|
|
15159
15338
|
// src/secrets-commands.ts
|
|
15160
|
-
var
|
|
15161
|
-
var
|
|
15339
|
+
var import_node_fs16 = require("node:fs");
|
|
15340
|
+
var import_node_path17 = require("node:path");
|
|
15162
15341
|
var RAILS_CREDENTIALS_DECRYPT_TIMEOUT_MS = 3e4;
|
|
15163
15342
|
var DEFAULT_RAILS_CREDENTIALS_FILE = "config/credentials.yml.enc";
|
|
15164
15343
|
var DEFAULT_RAILS_MASTER_KEY_FILE = "config/master.key";
|
|
@@ -15166,18 +15345,18 @@ function collectMap(value, previous = []) {
|
|
|
15166
15345
|
return [...previous, value];
|
|
15167
15346
|
}
|
|
15168
15347
|
async function decryptRailsCredentials(input) {
|
|
15169
|
-
const appDir = (0,
|
|
15348
|
+
const appDir = (0, import_node_path17.resolve)(input.appDir ?? process.cwd());
|
|
15170
15349
|
const credentialsFile = input.credentialsFile ?? DEFAULT_RAILS_CREDENTIALS_FILE;
|
|
15171
15350
|
const masterKeyFile = input.masterKeyFile ?? DEFAULT_RAILS_MASTER_KEY_FILE;
|
|
15172
|
-
const credentialsPath = (0,
|
|
15173
|
-
const masterKeyPath = (0,
|
|
15351
|
+
const credentialsPath = (0, import_node_path17.resolve)(appDir, credentialsFile);
|
|
15352
|
+
const masterKeyPath = (0, import_node_path17.resolve)(appDir, masterKeyFile);
|
|
15174
15353
|
const env = {
|
|
15175
15354
|
...process.env,
|
|
15176
15355
|
MMI_RAILS_CREDENTIALS_FILE: credentialsPath,
|
|
15177
15356
|
MMI_RAILS_MASTER_KEY_FILE: masterKeyPath
|
|
15178
15357
|
};
|
|
15179
|
-
if ((0,
|
|
15180
|
-
env.RAILS_MASTER_KEY = (0,
|
|
15358
|
+
if ((0, import_node_fs16.existsSync)(masterKeyPath)) {
|
|
15359
|
+
env.RAILS_MASTER_KEY = (0, import_node_fs16.readFileSync)(masterKeyPath, "utf8").trim();
|
|
15181
15360
|
}
|
|
15182
15361
|
const script = [
|
|
15183
15362
|
'require "json"',
|
|
@@ -15231,8 +15410,8 @@ async function withSecrets(run) {
|
|
|
15231
15410
|
}
|
|
15232
15411
|
function registerSecretsCommands(program3) {
|
|
15233
15412
|
const secrets = program3.command("secrets").description("two-tier project secrets \u2014 self-serve your repo dev/* tier; org tier is master-gated");
|
|
15234
|
-
secrets.command("where").description("print where this repo's secrets live \u2014 the two-tier vault layout + well-known keys (no values)").option("--repo <owner/repo>", "target repo (defaults to the current repo)").action((o) => withSecrets((d) => secretsWhere(d, o)));
|
|
15235
|
-
secrets.command("list").description("list secret NAMES + tier for this repo (never values)").option("--repo <owner/repo>", "target repo (defaults to the current repo)").action((o) => withSecrets((d) => secretsList(d, o)));
|
|
15413
|
+
secrets.command("where").description("print where this repo's secrets live \u2014 the two-tier vault layout + well-known keys (no values)").option("--repo <owner/repo>", "target repo (defaults to the current repo)").option("--json", "machine-readable output").action((o) => withSecrets((d) => secretsWhere(d, o)));
|
|
15414
|
+
secrets.command("list").description("list secret NAMES + tier for this repo (never values)").option("--repo <owner/repo>", "target repo (defaults to the current repo)").option("--json", "machine-readable output").action((o) => withSecrets((d) => secretsList(d, o)));
|
|
15236
15415
|
secrets.command("find <intent>").description("resolve a plain-language intent to the canonical secret(s) + the exact get command (master-only, #2244)").option("--repo <owner/repo>", "target repo (defaults to the current repo)").option("--json", "machine-readable output").action((intent, o) => withSecrets(async (d) => {
|
|
15237
15416
|
if (!await secretsFind(d, intent, o)) process.exitCode = 1;
|
|
15238
15417
|
}));
|
|
@@ -15243,13 +15422,13 @@ function registerSecretsCommands(program3) {
|
|
|
15243
15422
|
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) => {
|
|
15244
15423
|
let body;
|
|
15245
15424
|
try {
|
|
15246
|
-
body = (0,
|
|
15425
|
+
body = (0, import_node_fs16.readFileSync)((0, import_node_path17.resolve)(o.file), "utf8");
|
|
15247
15426
|
} catch (e) {
|
|
15248
15427
|
return fail(`secrets org-catalog: cannot read --file ${o.file}: ${e.message}`);
|
|
15249
15428
|
}
|
|
15250
15429
|
if (!await secretsOrgCatalogSet(d, body)) process.exitCode = 1;
|
|
15251
15430
|
}));
|
|
15252
|
-
secrets.command("preflight").description("check required stage secret names for a deploy/train without reading values").requiredOption("--stage <dev|rc|main>", "stage to check").option("--repo <owner/repo>", "target repo (defaults to the current repo)").option("--required <KEY...>", "required keys; bare keys are scoped under --stage").action(async (o) => {
|
|
15431
|
+
secrets.command("preflight").description("check required stage secret names for a deploy/train without reading values").requiredOption("--stage <dev|rc|main>", "stage to check").option("--repo <owner/repo>", "target repo (defaults to the current repo)").option("--required <KEY...>", "required keys; bare keys are scoped under --stage").option("--json", "machine-readable output").action(async (o) => {
|
|
15253
15432
|
if (!["dev", "rc", "main"].includes(o.stage)) {
|
|
15254
15433
|
return fail("secrets preflight: --stage must be dev, rc, or main");
|
|
15255
15434
|
}
|
|
@@ -15270,7 +15449,7 @@ function registerSecretsCommands(program3) {
|
|
|
15270
15449
|
if (!o.required?.length && centralContainer && meta && !hasRuntimeSecretContract(meta.requiredRuntimeSecrets)) {
|
|
15271
15450
|
d.err('secrets preflight: requiredRuntimeSecrets is unset: treating as an empty contract (no required keys). Declare an explicit {"dev":[],"rc":[],"main":[]} in registry META to silence this warning.');
|
|
15272
15451
|
}
|
|
15273
|
-
const ok = await secretsPreflight(d, { repo: o.repo, stage: o.stage, required });
|
|
15452
|
+
const ok = await secretsPreflight(d, { repo: o.repo, stage: o.stage, required, json: o.json, declaredSecrets: meta?.secrets });
|
|
15274
15453
|
if (!ok) process.exitCode = 1;
|
|
15275
15454
|
});
|
|
15276
15455
|
secrets.command("get <key>").description("print one secret value, RAW \u2014 MASTER-ONLY (WS6 #2184); non-master: `secrets use` to consume keyless or `secrets verify` to validate").option("--repo <owner/repo>", "target repo (defaults to the current repo)").option("--slug <slug>", "vault slug override for org-infra namespaces (cloudflare, shared, \u2026) \u2014 master-gated").action((key, o) => withSecrets(async (d) => {
|
|
@@ -15285,14 +15464,12 @@ function registerSecretsCommands(program3) {
|
|
|
15285
15464
|
const ok = await secretsVerify(d, key, o);
|
|
15286
15465
|
if (!ok) process.exitCode = 1;
|
|
15287
15466
|
}));
|
|
15288
|
-
|
|
15467
|
+
const setHandler = (key, o) => withSecrets(async (d) => {
|
|
15289
15468
|
const ok = await secretsSet(d, key, o);
|
|
15290
15469
|
if (!ok) process.exitCode = 1;
|
|
15291
|
-
})
|
|
15292
|
-
secrets.command("
|
|
15293
|
-
|
|
15294
|
-
if (!ok) process.exitCode = 1;
|
|
15295
|
-
}));
|
|
15470
|
+
});
|
|
15471
|
+
secrets.command("set <key>").description("write/rotate a secret; value from stdin (never an argument). DECLARE-FIRST (#2528): the key must be a declared catalog coordinate \u2014 bare <KEY> = the stageless canonical, <stage>/<KEY> = a declared per-stage override; undeclared writes are rejected with the fix").option("--repo <owner/repo>", "target repo (defaults to the current repo)").action(setHandler);
|
|
15472
|
+
secrets.command("edit <key>").description("alias for set \u2014 replace a secret value (read from stdin)").option("--repo <owner/repo>", "target repo (defaults to the current repo)").action(setHandler);
|
|
15296
15473
|
secrets.command("copy").description("copy provider keys between vault tiers (audit-logged; org-tier source is master-gated)").requiredOption("--from <stage>", "source tier: dev, rc, or main").requiredOption("--to <stage>", "destination tier: dev, rc, or main").requiredOption("--keys <names>", "comma-separated secret names (encryption keys blocked)").option("--dry-run", "report copies without writing").option("--repo <owner/repo>", "target repo (defaults to the current repo)").action((o) => withSecrets(async (d) => {
|
|
15297
15474
|
const stages = ["dev", "rc", "main"];
|
|
15298
15475
|
if (!stages.includes(o.from) || !stages.includes(o.to)) {
|
|
@@ -15318,7 +15495,7 @@ function registerSecretsCommands(program3) {
|
|
|
15318
15495
|
{
|
|
15319
15496
|
...d,
|
|
15320
15497
|
decryptRailsCredentials,
|
|
15321
|
-
removeFile: (path2) => (0,
|
|
15498
|
+
removeFile: (path2) => (0, import_node_fs16.unlinkSync)((0, import_node_path17.resolve)(o.appDir ?? process.cwd(), path2))
|
|
15322
15499
|
},
|
|
15323
15500
|
{
|
|
15324
15501
|
repo: o.repo,
|
|
@@ -15407,10 +15584,10 @@ function registerEdgeCommands(program3) {
|
|
|
15407
15584
|
}
|
|
15408
15585
|
|
|
15409
15586
|
// src/doctor-run.ts
|
|
15410
|
-
var
|
|
15587
|
+
var import_node_fs20 = require("node:fs");
|
|
15411
15588
|
var import_node_child_process11 = require("node:child_process");
|
|
15412
15589
|
var import_promises2 = require("node:fs/promises");
|
|
15413
|
-
var
|
|
15590
|
+
var import_node_path20 = require("node:path");
|
|
15414
15591
|
var import_node_os7 = require("node:os");
|
|
15415
15592
|
|
|
15416
15593
|
// src/plugin-guard.ts
|
|
@@ -15433,13 +15610,14 @@ function buildGuardSessionStartLine(state, opts = {}) {
|
|
|
15433
15610
|
|
|
15434
15611
|
// src/cursor-plugin-seed.ts
|
|
15435
15612
|
var import_node_child_process10 = require("node:child_process");
|
|
15436
|
-
var
|
|
15613
|
+
var import_node_fs17 = require("node:fs");
|
|
15437
15614
|
var import_node_os6 = require("node:os");
|
|
15438
|
-
var
|
|
15615
|
+
var import_node_path18 = require("node:path");
|
|
15439
15616
|
var import_node_util7 = require("node:util");
|
|
15440
15617
|
|
|
15441
15618
|
// src/doctor.ts
|
|
15442
15619
|
var GH_PROJECT_LOGIN_FIX = 'run: gh auth login --hostname github.com --git-protocol https --web --scopes "project"';
|
|
15620
|
+
var GITHUB_AUTH_LABEL = "GitHub auth identity (gh ops)";
|
|
15443
15621
|
var AWS_CROSS_ACCOUNT_LABEL = "AWS cross-account identity (master-agent audits)";
|
|
15444
15622
|
var AWS_CROSS_ACCOUNT_FIX = "use a non-root IAM user/session profile for master-agent AWS checks; set AWS_PROFILE or AWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEY (plus AWS_SESSION_TOKEN for temporary credentials), then verify `aws sts get-caller-identity` does not end in :root";
|
|
15445
15623
|
var MMI_AGENTIC_ONBOARDING_GUIDE = {
|
|
@@ -15453,7 +15631,7 @@ function buildGithubAuthCheck(input) {
|
|
|
15453
15631
|
const ok = Boolean(input.login?.trim());
|
|
15454
15632
|
return {
|
|
15455
15633
|
ok,
|
|
15456
|
-
label:
|
|
15634
|
+
label: GITHUB_AUTH_LABEL,
|
|
15457
15635
|
fix: input.ghInstalled ? GH_PROJECT_LOGIN_FIX : `install GitHub CLI (https://cli.github.com), then: ${GH_PROJECT_LOGIN_FIX.replace(/^run: /, "")}`
|
|
15458
15636
|
};
|
|
15459
15637
|
}
|
|
@@ -15631,6 +15809,136 @@ function buildRepoLocalWorktreeCheck(input) {
|
|
|
15631
15809
|
fix: REPO_LOCAL_WORKTREE_FIX
|
|
15632
15810
|
};
|
|
15633
15811
|
}
|
|
15812
|
+
var LOCKFILE_OPTIONALS_LABEL = "npm lockfile keeps optionalDependencies across platforms (Windows lockfile drop #2594)";
|
|
15813
|
+
var LOCKFILE_OPTIONALS_FIX = "regenerate the lockfile without platform filtering: `rm package-lock.json && npm install` on a machine that resolves every optionalDependency, then commit it (a Windows-only lockfile drops Linux-only optionals on CI)";
|
|
15814
|
+
function buildLockfileOptionalsCheck(input) {
|
|
15815
|
+
const base = {
|
|
15816
|
+
ok: true,
|
|
15817
|
+
label: LOCKFILE_OPTIONALS_LABEL,
|
|
15818
|
+
fix: LOCKFILE_OPTIONALS_FIX,
|
|
15819
|
+
missingOptionals: []
|
|
15820
|
+
};
|
|
15821
|
+
if (!input.isOrgRepo) return base;
|
|
15822
|
+
const optionalDeps = input.packageJsonOptionalDeps ?? {};
|
|
15823
|
+
const packages = input.lockfilePackages ?? {};
|
|
15824
|
+
const missingOptionals = Object.keys(optionalDeps).filter((name) => !(`node_modules/${name}` in packages));
|
|
15825
|
+
if (missingOptionals.length === 0) return base;
|
|
15826
|
+
return {
|
|
15827
|
+
...base,
|
|
15828
|
+
ok: false,
|
|
15829
|
+
missingOptionals,
|
|
15830
|
+
detail: `${missingOptionals.length} optionalDependency entr${missingOptionals.length === 1 ? "y" : "ies"} missing from the lockfile packages map: ${missingOptionals.join(", ")}`
|
|
15831
|
+
};
|
|
15832
|
+
}
|
|
15833
|
+
var FILE_ENCODING_LABEL = "managed file encoding (UTF-8, no BOM, no mojibake)";
|
|
15834
|
+
var FILE_ENCODING_FIX = 're-save the flagged file(s) as UTF-8 without BOM (VS Code: status-bar encoding button \u2192 "Save with Encoding" \u2192 UTF-8), and set `PYTHONUTF8=1` for Python tooling on Windows so it never falls back to cp1252';
|
|
15835
|
+
function inspectFileEncoding(bytes) {
|
|
15836
|
+
const bom = bytes.length >= 3 && bytes[0] === 239 && bytes[1] === 187 && bytes[2] === 191;
|
|
15837
|
+
let invalidUtf8 = false;
|
|
15838
|
+
let replacementChars = false;
|
|
15839
|
+
try {
|
|
15840
|
+
const text = new TextDecoder("utf-8", { fatal: true }).decode(bytes);
|
|
15841
|
+
replacementChars = text.includes("\uFFFD");
|
|
15842
|
+
} catch {
|
|
15843
|
+
invalidUtf8 = true;
|
|
15844
|
+
replacementChars = new TextDecoder("utf-8", { fatal: false }).decode(bytes).includes("\uFFFD");
|
|
15845
|
+
}
|
|
15846
|
+
return { bom, invalidUtf8, replacementChars };
|
|
15847
|
+
}
|
|
15848
|
+
function buildManagedFileEncodingCheck(input) {
|
|
15849
|
+
const base = {
|
|
15850
|
+
ok: true,
|
|
15851
|
+
label: FILE_ENCODING_LABEL,
|
|
15852
|
+
fix: FILE_ENCODING_FIX,
|
|
15853
|
+
findings: []
|
|
15854
|
+
};
|
|
15855
|
+
if (!input.isOrgRepo) return base;
|
|
15856
|
+
const defective = input.findings.filter((f) => f.bom || f.invalidUtf8 || f.replacementChars);
|
|
15857
|
+
if (defective.length === 0) return base;
|
|
15858
|
+
return {
|
|
15859
|
+
...base,
|
|
15860
|
+
ok: false,
|
|
15861
|
+
severityOverride: "advisory",
|
|
15862
|
+
findings: defective,
|
|
15863
|
+
detail: `${defective.length} managed file(s) not clean UTF-8: ${defective.map((f) => f.path).join(", ")}`
|
|
15864
|
+
};
|
|
15865
|
+
}
|
|
15866
|
+
var LOCAL_BEHIND_ORIGIN_LABEL = "local release branches current with origin (development/main/rc)";
|
|
15867
|
+
var LOCAL_BEHIND_ORIGIN_FIX = "run `git fetch origin && git switch <branch> && git pull --ff-only` for each flagged branch (purely behind, so the fast-forward is safe)";
|
|
15868
|
+
var RELEASE_TRACKING_BRANCHES = /* @__PURE__ */ new Set(["development", "main", "master", "rc", "release"]);
|
|
15869
|
+
function isReleaseTrackingBranch(name) {
|
|
15870
|
+
if (!name) return false;
|
|
15871
|
+
const base = name.split("/").pop() ?? name;
|
|
15872
|
+
if (RELEASE_TRACKING_BRANCHES.has(base)) return true;
|
|
15873
|
+
return name.split("/")[0] === "release";
|
|
15874
|
+
}
|
|
15875
|
+
function buildStaleLocalBranchCheck(input) {
|
|
15876
|
+
const base = {
|
|
15877
|
+
ok: true,
|
|
15878
|
+
label: LOCAL_BEHIND_ORIGIN_LABEL,
|
|
15879
|
+
fix: LOCAL_BEHIND_ORIGIN_FIX,
|
|
15880
|
+
stale: []
|
|
15881
|
+
};
|
|
15882
|
+
if (!input.isOrgRepo) return base;
|
|
15883
|
+
const stale = input.branches.filter((b) => isReleaseTrackingBranch(b.name) && b.behind > 0 && b.ahead === 0).map((b) => ({ name: b.name, behind: b.behind }));
|
|
15884
|
+
if (stale.length === 0) return base;
|
|
15885
|
+
return {
|
|
15886
|
+
...base,
|
|
15887
|
+
ok: false,
|
|
15888
|
+
severityOverride: "advisory",
|
|
15889
|
+
stale,
|
|
15890
|
+
detail: `${stale.length} release branch(es) behind origin: ${stale.map((s) => `${s.name} (-${s.behind})`).join(", ")}`
|
|
15891
|
+
};
|
|
15892
|
+
}
|
|
15893
|
+
var REDACTOR_LIVENESS_LABEL = "secret-redact hook liveness (PostToolUse redactor crashes)";
|
|
15894
|
+
var REDACTOR_LIVENESS_FIX = 'grep the hook activity log for \'"script":"secret-redact","outcome":"failed"\' (path: `git rev-parse --git-path mmi-runtime/hooks/activity.jsonl`); reinstall the MMI plugin or run `mmi-cli doctor --apply` to repair, then reopen the session \u2014 the redactor stayed fail-open so tool calls proceeded, but secrets may have passed through unredacted while it was down';
|
|
15895
|
+
function summarizeRedactorFailures(lines) {
|
|
15896
|
+
const parsed = [];
|
|
15897
|
+
for (const raw of lines) {
|
|
15898
|
+
const trimmed = raw.trim();
|
|
15899
|
+
if (!trimmed) continue;
|
|
15900
|
+
try {
|
|
15901
|
+
parsed.push(JSON.parse(trimmed));
|
|
15902
|
+
} catch {
|
|
15903
|
+
}
|
|
15904
|
+
}
|
|
15905
|
+
let sessionStartIdx = -1;
|
|
15906
|
+
for (let i = 0; i < parsed.length; i += 1) {
|
|
15907
|
+
if (parsed[i].event === "SessionStart") sessionStartIdx = i;
|
|
15908
|
+
}
|
|
15909
|
+
const start = sessionStartIdx >= 0 ? sessionStartIdx : 0;
|
|
15910
|
+
let failedCount = 0;
|
|
15911
|
+
let lastFailedTs;
|
|
15912
|
+
let lastFailedAction;
|
|
15913
|
+
for (let i = start; i < parsed.length; i += 1) {
|
|
15914
|
+
const e = parsed[i];
|
|
15915
|
+
if (e.script === "secret-redact" && e.outcome === "failed") {
|
|
15916
|
+
failedCount += 1;
|
|
15917
|
+
lastFailedTs = e.ts;
|
|
15918
|
+
lastFailedAction = e.action;
|
|
15919
|
+
}
|
|
15920
|
+
}
|
|
15921
|
+
return { failedCount, lastFailedTs, lastFailedAction };
|
|
15922
|
+
}
|
|
15923
|
+
function buildRedactorLivenessCheck(input) {
|
|
15924
|
+
const base = {
|
|
15925
|
+
ok: true,
|
|
15926
|
+
label: REDACTOR_LIVENESS_LABEL,
|
|
15927
|
+
fix: REDACTOR_LIVENESS_FIX,
|
|
15928
|
+
failedCount: 0
|
|
15929
|
+
};
|
|
15930
|
+
if (!input.isOrgRepo) return base;
|
|
15931
|
+
if (input.failedCount === 0) return base;
|
|
15932
|
+
const when = input.lastFailedTs ? ` (most recent ${input.lastFailedTs})` : "";
|
|
15933
|
+
const reason = input.lastFailedAction ? `: ${input.lastFailedAction}` : "";
|
|
15934
|
+
return {
|
|
15935
|
+
ok: false,
|
|
15936
|
+
label: REDACTOR_LIVENESS_LABEL,
|
|
15937
|
+
fix: `secret-redact crashed ${input.failedCount} time(s) this session${when}${reason}. ${REDACTOR_LIVENESS_FIX}`,
|
|
15938
|
+
failedCount: input.failedCount,
|
|
15939
|
+
lastFailedTs: input.lastFailedTs
|
|
15940
|
+
};
|
|
15941
|
+
}
|
|
15634
15942
|
var SCRATCH_GC_LABEL = "scratch housekeeping (tmp/, plans/, browser artifacts)";
|
|
15635
15943
|
var SCRATCH_GC_FIX = "run `mmi-cli gc --scratch --dry-run` to inspect; run `mmi-cli gc --scratch --apply` to prune safe scratch; for kept plans run `mmi-cli northstar status` or `mmi-cli northstar sync --wait` first";
|
|
15636
15944
|
function buildScratchGcCheck(plan) {
|
|
@@ -16089,7 +16397,16 @@ function buildDoctorJsonPayload(input) {
|
|
|
16089
16397
|
return {
|
|
16090
16398
|
ok: doctorExitCode(input.checks) === 0,
|
|
16091
16399
|
hasAdvisories: input.checks.some((c) => !c.ok && isAdvisoryDoctorCheck(c)),
|
|
16092
|
-
checks: input.checks
|
|
16400
|
+
checks: input.checks.map((check) => {
|
|
16401
|
+
const metadata = doctorMetadataForCheck(check);
|
|
16402
|
+
return {
|
|
16403
|
+
...check,
|
|
16404
|
+
...metadata,
|
|
16405
|
+
label: check.label,
|
|
16406
|
+
metadata,
|
|
16407
|
+
durationMs: check.durationMs ?? 0
|
|
16408
|
+
};
|
|
16409
|
+
}),
|
|
16093
16410
|
updateReport: input.updateReport,
|
|
16094
16411
|
...input.resources.length ? { resources: input.resources } : {}
|
|
16095
16412
|
};
|
|
@@ -16148,6 +16465,25 @@ function buildInstalledPluginVersionCheck(input) {
|
|
|
16148
16465
|
staleSurfaces: stale
|
|
16149
16466
|
};
|
|
16150
16467
|
}
|
|
16468
|
+
var CLI_VERSION_DRIFT_LABEL = "mmi-cli version drift (installed vs latest published)";
|
|
16469
|
+
var CLI_VERSION_DRIFT_FIX = "run: mmi-cli doctor --apply --no-repo-writes (or npm install -g @mutmutco/cli@latest)";
|
|
16470
|
+
function buildCliVersionDriftCheck(input) {
|
|
16471
|
+
const base = {
|
|
16472
|
+
ok: true,
|
|
16473
|
+
label: CLI_VERSION_DRIFT_LABEL,
|
|
16474
|
+
fix: CLI_VERSION_DRIFT_FIX,
|
|
16475
|
+
currentVersion: input.currentVersion,
|
|
16476
|
+
releasedVersion: input.releasedVersion
|
|
16477
|
+
};
|
|
16478
|
+
if (!input.releasedVersion || !isSemverVersion(input.releasedVersion) || !isSemverVersion(input.currentVersion)) return base;
|
|
16479
|
+
if (compareVersions(input.currentVersion, input.releasedVersion) >= 0) return base;
|
|
16480
|
+
return {
|
|
16481
|
+
...base,
|
|
16482
|
+
ok: false,
|
|
16483
|
+
label: `${CLI_VERSION_DRIFT_LABEL}: ${input.currentVersion} < ${input.releasedVersion}`,
|
|
16484
|
+
fix: `${CLI_VERSION_DRIFT_FIX} (installed ${input.currentVersion} is behind published ${input.releasedVersion})`
|
|
16485
|
+
};
|
|
16486
|
+
}
|
|
16151
16487
|
var OPENCODE_VERSION_LABEL = "installed OpenCode MMI adapter version (vs latest release)";
|
|
16152
16488
|
function buildOpencodeVersionCheck(input) {
|
|
16153
16489
|
const fix = pluginRecoveryFix("opencode");
|
|
@@ -16445,13 +16781,13 @@ function buildCursorHookCliCheck(input) {
|
|
|
16445
16781
|
return base;
|
|
16446
16782
|
}
|
|
16447
16783
|
var HUB_COMPAT_FIX = "update mmi-cli (npm i -g @mutmutco/cli) / refresh the MMI plugin, then rerun doctor";
|
|
16784
|
+
var HUB_COMPAT_LABEL = "Hub compatibility (client version vs Hub minimum)";
|
|
16448
16785
|
function buildHubCompatCheck(input) {
|
|
16449
|
-
const label = "Hub compatibility (client version vs Hub minimum)";
|
|
16450
16786
|
const min = input.versionInfo?.minClientVersion;
|
|
16451
16787
|
if (!input.isOrgRepo || !min || !parseSemver(min) || !parseSemver(input.installedVersion)) {
|
|
16452
|
-
return { ok: true, label, fix: HUB_COMPAT_FIX };
|
|
16788
|
+
return { ok: true, label: HUB_COMPAT_LABEL, fix: HUB_COMPAT_FIX };
|
|
16453
16789
|
}
|
|
16454
|
-
return { ok: versionAtLeast(input.installedVersion, min), label: `${
|
|
16790
|
+
return { ok: versionAtLeast(input.installedVersion, min), label: `${HUB_COMPAT_LABEL}: requires >= ${min}`, fix: HUB_COMPAT_FIX };
|
|
16455
16791
|
}
|
|
16456
16792
|
var HUB_DEPLOY_FRESHNESS_LABEL = "Hub deploy freshness (x-hub-version vs CLI / release)";
|
|
16457
16793
|
var HUB_DEPLOY_FRESHNESS_FIX = "prod Hub updates on /release (MMI-Hub is direct-track); lag vs development is expected between releases";
|
|
@@ -16486,8 +16822,6 @@ function buildHubDeployFreshnessCheck(input) {
|
|
|
16486
16822
|
staleAgainst
|
|
16487
16823
|
};
|
|
16488
16824
|
}
|
|
16489
|
-
var PLAYWRIGHT_MCP_VISION_CAP_LABEL = "Playwright MCP vision caps (--caps=vision prohibited)";
|
|
16490
|
-
var PLAYWRIGHT_MCP_OUTPUT_DIR_LABEL = "Playwright MCP output dir (use tmp/playwright-mcp)";
|
|
16491
16825
|
function textHasPlaywrightMcp(content) {
|
|
16492
16826
|
const normalized = content.replace(/\r\n/g, "\n");
|
|
16493
16827
|
return /@playwright\/mcp/.test(normalized) || /mcp_servers\.playwright/.test(normalized) || /"playwright"\s*:\s*\{/.test(normalized) || /\bmcpServers\b/.test(normalized);
|
|
@@ -16545,7 +16879,7 @@ function buildSelfUpdateHaltPayload(input) {
|
|
|
16545
16879
|
checks: input.checks
|
|
16546
16880
|
};
|
|
16547
16881
|
}
|
|
16548
|
-
var DOCTOR_SELF_UPDATE_HALT_EXIT_CODE =
|
|
16882
|
+
var DOCTOR_SELF_UPDATE_HALT_EXIT_CODE = 2;
|
|
16549
16883
|
var DOCTOR_SELF_UPDATE_HALT_SENTINEL = "MMI_DOCTOR_SELF_UPDATE_HALT";
|
|
16550
16884
|
function selfUpdateHaltSentinelLine(input) {
|
|
16551
16885
|
const to = input.report.releasedVersion ?? "latest";
|
|
@@ -16555,8 +16889,11 @@ var DOCTOR_POST_SELF_UPDATE_ENV = "MMI_DOCTOR_POST_SELF_UPDATE";
|
|
|
16555
16889
|
function buildSelfUpdateReexecArgs(opts) {
|
|
16556
16890
|
const args = ["doctor"];
|
|
16557
16891
|
if (opts.banner) args.push("--banner");
|
|
16892
|
+
if (opts.fast) args.push("--fast");
|
|
16558
16893
|
if (opts.preflight) args.push("--preflight");
|
|
16559
16894
|
if (opts.verbose) args.push("--verbose");
|
|
16895
|
+
if (opts.ci) args.push("--ci");
|
|
16896
|
+
if (opts.strictAdvisory) args.push("--strict-advisory");
|
|
16560
16897
|
if (opts.guide) args.push("--guide");
|
|
16561
16898
|
if (opts.json) args.push("--json");
|
|
16562
16899
|
if (opts.apply) args.push("--apply");
|
|
@@ -16577,28 +16914,12 @@ function preflightOutcome(input) {
|
|
|
16577
16914
|
function pluginAutonomousHaltLine(reloadHint) {
|
|
16578
16915
|
return `\u26A0 PLUGIN RELOAD REQUIRED \u2014 mmi:* skills and agent types are unavailable until you ${reloadHint}. Halt autonomous /grind and /build until then.`;
|
|
16579
16916
|
}
|
|
16580
|
-
var DOCTOR_EXTENDED_CHECK_LABELS = /* @__PURE__ */ new Set([
|
|
16581
|
-
AWS_CROSS_ACCOUNT_LABEL,
|
|
16582
|
-
HUB_DEPLOY_FRESHNESS_LABEL,
|
|
16583
|
-
PLAYWRIGHT_MCP_VISION_CAP_LABEL,
|
|
16584
|
-
PLAYWRIGHT_MCP_OUTPUT_DIR_LABEL,
|
|
16585
|
-
BROWSER_ARTIFACTS_LABEL,
|
|
16586
|
-
SCRATCH_GC_LABEL,
|
|
16587
|
-
GIT_GC_LABEL,
|
|
16588
|
-
OPENCODE_VERSION_LABEL,
|
|
16589
|
-
OPENCODE_DESKTOP_BOOTSTRAP_LABEL
|
|
16590
|
-
]);
|
|
16591
|
-
function isDoctorExtendedCheck(label) {
|
|
16592
|
-
if (DOCTOR_EXTENDED_CHECK_LABELS.has(label)) return true;
|
|
16593
|
-
if (label.startsWith(HUB_DEPLOY_FRESHNESS_LABEL)) return true;
|
|
16594
|
-
if (label.startsWith("@mutmutco design-system") || label.startsWith("@mutmutco registry components")) return true;
|
|
16595
|
-
return false;
|
|
16596
|
-
}
|
|
16597
16917
|
function isAdvisoryDoctorCheck(check) {
|
|
16598
16918
|
if (check.severityOverride) return check.severityOverride === "advisory";
|
|
16599
|
-
return
|
|
16919
|
+
return (check.metadata ?? doctorMetadataForLabel(check.label)).severity === "advisory";
|
|
16600
16920
|
}
|
|
16601
|
-
function doctorExitCode(checks) {
|
|
16921
|
+
function doctorExitCode(checks, opts = {}) {
|
|
16922
|
+
if (opts.doctorError) return 2;
|
|
16602
16923
|
return checks.some((c) => !c.ok && !isAdvisoryDoctorCheck(c)) ? 1 : 0;
|
|
16603
16924
|
}
|
|
16604
16925
|
function renderPluginUpdateReportStaleOnly(report, allChecksPass = false) {
|
|
@@ -16624,6 +16945,7 @@ function doctorVerdictLine(input) {
|
|
|
16624
16945
|
const gaps = input.checks.filter((c) => !c.ok);
|
|
16625
16946
|
const hardGaps = gaps.filter((c) => !isAdvisoryDoctorCheck(c));
|
|
16626
16947
|
const softGaps = gaps.filter((c) => isAdvisoryDoctorCheck(c));
|
|
16948
|
+
const skipSuffix = input.skippedSlowChecks ? ` \xB7 skipped ${input.skippedSlowChecks} slow checks` : "";
|
|
16627
16949
|
if (input.shouldApply) {
|
|
16628
16950
|
if (hardGaps.length > 0) {
|
|
16629
16951
|
return `\u2717 ${hardGaps.length} repair${hardGaps.length === 1 ? "" : "s"} failed \u2014 see the items above.`;
|
|
@@ -16637,19 +16959,30 @@ function doctorVerdictLine(input) {
|
|
|
16637
16959
|
}
|
|
16638
16960
|
const attention = gaps.length;
|
|
16639
16961
|
if (attention > 0) {
|
|
16640
|
-
return `\u26A0 ${attention} item${attention === 1 ? " needs" : "s need"} attention \u2014 run \`mmi-cli doctor --apply\` to heal.`;
|
|
16962
|
+
return `\u26A0 ${attention} item${attention === 1 ? " needs" : "s need"} attention \u2014 run \`mmi-cli doctor --apply\` to heal${skipSuffix}.`;
|
|
16641
16963
|
}
|
|
16642
|
-
return
|
|
16964
|
+
return `\u2713 healthy \xB7 ${input.checks.length} checks \xB7 0 gaps${skipSuffix}`;
|
|
16965
|
+
}
|
|
16966
|
+
function doctorCheckSummaryLine(check) {
|
|
16967
|
+
const meta = doctorMetadataForCheck(check);
|
|
16968
|
+
return ` ${doctorCheckGlyph(check)} ${meta.id} \u2014 ${meta.protects.join("; ")} \u2014 ${check.fix}`;
|
|
16643
16969
|
}
|
|
16644
16970
|
function doctorHumanLines(input) {
|
|
16645
16971
|
const gaps = input.checks.filter((c) => !c.ok);
|
|
16972
|
+
const healthyCount = input.checks.length - gaps.length;
|
|
16646
16973
|
const lines = [
|
|
16647
|
-
doctorVerdictLine({ checks: input.checks, shouldApply: input.shouldApply, healedCount: input.healedCount })
|
|
16648
|
-
"",
|
|
16649
|
-
"Checks",
|
|
16650
|
-
...input.checks.map((c) => ` ${doctorCheckGlyph(c)} ${c.label}`)
|
|
16974
|
+
doctorVerdictLine({ checks: input.checks, shouldApply: input.shouldApply, healedCount: input.healedCount, skippedSlowChecks: input.skippedSlowChecks })
|
|
16651
16975
|
];
|
|
16652
|
-
if (gaps.length > 0) {
|
|
16976
|
+
if (gaps.length > 0 || input.verbose) {
|
|
16977
|
+
lines.push("", "Checks");
|
|
16978
|
+
if (input.verbose) {
|
|
16979
|
+
lines.push(...input.checks.map((c) => ` ${doctorCheckGlyph(c)} ${doctorMetadataForCheck(c).id} \u2014 ${c.label}`));
|
|
16980
|
+
} else {
|
|
16981
|
+
lines.push(...gaps.map(doctorCheckSummaryLine));
|
|
16982
|
+
if (healthyCount > 0) lines.push(` \u2713 ${healthyCount} healthy check${healthyCount === 1 ? "" : "s"} collapsed`);
|
|
16983
|
+
}
|
|
16984
|
+
}
|
|
16985
|
+
if (gaps.length > 0 && input.verbose) {
|
|
16653
16986
|
lines.push("", input.shouldApply ? "Repairs" : "Will fix on --apply");
|
|
16654
16987
|
for (const c of gaps) {
|
|
16655
16988
|
const mark = input.shouldApply ? isAdvisoryDoctorCheck(c) ? "\u26A0" : "\u2717" : "\u2192";
|
|
@@ -16659,10 +16992,10 @@ function doctorHumanLines(input) {
|
|
|
16659
16992
|
if (input.shouldApply && input.pluginReloadRequired) {
|
|
16660
16993
|
lines.push("", `\u21BB ${input.reloadHint ?? "reload"} so the healed plugin/MCP installs load.`);
|
|
16661
16994
|
}
|
|
16662
|
-
const stale = renderPluginUpdateReportStaleOnly(input.updateReport, gaps.length === 0);
|
|
16995
|
+
const stale = input.verbose || gaps.length > 0 ? renderPluginUpdateReportStaleOnly(input.updateReport, gaps.length === 0) : [];
|
|
16663
16996
|
if (stale.length) {
|
|
16664
16997
|
lines.push("", ...stale);
|
|
16665
|
-
} else if (!input.verbose) {
|
|
16998
|
+
} else if (!input.verbose && gaps.length > 0) {
|
|
16666
16999
|
lines.push("", DOCTOR_VERBOSE_HINT);
|
|
16667
17000
|
}
|
|
16668
17001
|
if (!input.verbose) return lines;
|
|
@@ -16710,6 +17043,100 @@ function doctorAuditLines(input) {
|
|
|
16710
17043
|
return lines;
|
|
16711
17044
|
}
|
|
16712
17045
|
var PLUGIN_RESOLVABILITY_LABEL = "MMI plugin resolvability (marketplace + cache present)";
|
|
17046
|
+
var MMI_CLI_ON_PATH_LABEL = "mmi-cli on PATH";
|
|
17047
|
+
var PLUGIN_CLONE_LABEL = "plugin git clone (SSH\u2192HTTPS rewrite)";
|
|
17048
|
+
var MCP_RECONCILE_LABEL = "MCP server registrations (org-managed Playwright)";
|
|
17049
|
+
var CURSOR_ENABLED_PIN_LABEL = "Cursor Team Marketplace enabled plugin pin";
|
|
17050
|
+
var DOCTOR_CHECK_METADATA = [
|
|
17051
|
+
{ id: "github.auth", category: "auth", severity: "hard", cost: "fast", heal: "none", label: GITHUB_AUTH_LABEL, protects: ["GitHub issue, PR, project, and registry operations use a resolved viewer identity"] },
|
|
17052
|
+
{ id: "cli.path", category: "repo", severity: "hard", cost: "fast", heal: "auto", label: MMI_CLI_ON_PATH_LABEL, protects: ["Session hooks and surface commands can invoke mmi-cli"] },
|
|
17053
|
+
{ id: "hub.compat", category: "hub", severity: "hard", cost: "fast", heal: "auto", label: HUB_COMPAT_LABEL, protects: ["Client stays above the Hub minimum version floor"] },
|
|
17054
|
+
{ id: "hub.deploy-freshness", category: "hub", severity: "advisory", cost: "slow", heal: "none", label: HUB_DEPLOY_FRESHNESS_LABEL, protects: ["Operators can see prod Hub lag against release and local CLI versions"] },
|
|
17055
|
+
{ id: "cli.version-drift", category: "repo", severity: "advisory", cost: "slow", heal: "none", label: CLI_VERSION_DRIFT_LABEL, protects: ["The installed mmi-cli is not behind the npm-published version (stale installs miss commands)"] },
|
|
17056
|
+
{ id: "aws.cross-account", category: "aws", severity: "advisory", cost: "slow", heal: "none", label: AWS_CROSS_ACCOUNT_LABEL, protects: ["Master-agent AWS audits do not start from root credentials"] },
|
|
17057
|
+
{ id: "plugin.clone-rewrite", category: "plugin", severity: "hard", cost: "fast", heal: "none", label: PLUGIN_CLONE_LABEL, protects: ["Plugin marketplace clones can use the HTTPS rewrite expected on this machine"] },
|
|
17058
|
+
{ id: "plugin.install-record", category: "plugin", severity: "hard", cost: "fast", heal: "auto", label: PLUGIN_LABEL, protects: ["Claude/Codex plugin loader sees mmi@mutmutco installed for this project"] },
|
|
17059
|
+
{ id: "plugin.legacy-id", category: "plugin", severity: "hard", cost: "fast", heal: "surface", label: LEGACY_PLUGIN_ID_LABEL, protects: ["Legacy mmi@mmi rows cannot mask the canonical mmi@mutmutco install"] },
|
|
17060
|
+
{ id: "plugin.config-drift", category: "plugin", severity: "hard", cost: "fast", heal: "auto", label: PLUGIN_DRIFT_LABEL, protects: ["Duplicate or stale plugin rows collapse to one current user-scope row"] },
|
|
17061
|
+
{ id: "plugin.resolvability", category: "plugin", severity: "hard", cost: "fast", heal: "surface", label: PLUGIN_RESOLVABILITY_LABEL, protects: ["Marketplace clone and plugin cache both exist for the active surface"] },
|
|
17062
|
+
{ id: "plugin.installed-version", category: "plugin", severity: "hard", cost: "fast", heal: "surface", label: INSTALLED_PLUGIN_VERSION_LABEL, protects: ["Claude/Codex installed plugin records reach the latest release"] },
|
|
17063
|
+
{ id: "plugin.cache-cleanup", category: "plugin", severity: "hard", cost: "fast", heal: "auto", label: MMI_PLUGIN_CACHE_CLEANUP_LABEL, protects: ["Stale Claude/Codex cache directories do not shadow current installs"] },
|
|
17064
|
+
{ id: "plugin.nested-cache", category: "plugin", severity: "hard", cost: "fast", heal: "surface", label: NESTED_PLUGIN_TREE_LABEL, protects: ["Self-nested plugin cache trees do not exceed host path limits"] },
|
|
17065
|
+
{ id: "codex.active-cache", category: "plugin", severity: "hard", cost: "fast", heal: "surface", label: CODEX_ACTIVE_CACHE_LABEL, protects: ["Codex actually loads the released plugin cache"] },
|
|
17066
|
+
{ id: "session.skill-root", category: "plugin", severity: "advisory", cost: "fast", heal: "none", label: SESSION_SKILL_ROOT_LABEL, protects: ["Running sessions know when a restart is needed to load current skills"] },
|
|
17067
|
+
{ id: "repo.gitignore-block", category: "repo", severity: "hard", cost: "fast", heal: "auto", label: GITIGNORE_BLOCK_LABEL, protects: ["Org-managed ignore entries keep generated and scratch files out of git"] },
|
|
17068
|
+
{ id: "repo.worktree-location", category: "repo", severity: "hard", cost: "fast", heal: "none", label: REPO_LOCAL_WORKTREE_LABEL, protects: ["Worktrees stay in the canonical sibling root"] },
|
|
17069
|
+
{ id: "hooks.redactor-liveness", category: "hooks", severity: "advisory", cost: "fast", heal: "none", label: REDACTOR_LIVENESS_LABEL, protects: ["A crashed secret-redactor cannot silently pass secrets into model context forever"] },
|
|
17070
|
+
{ id: "repo.scratch-gc", category: "repo", severity: "advisory", cost: "slow", heal: "auto", label: SCRATCH_GC_LABEL, protects: ["Scratch, plan, and browser artifact clutter stays bounded"] },
|
|
17071
|
+
{ id: "repo.git-gc", category: "git", severity: "advisory", cost: "slow", heal: "auto", label: GIT_GC_LABEL, protects: ["Merged branches, stale refs, and dead worktree metadata stay bounded"] },
|
|
17072
|
+
{ id: "browser.artifacts", category: "browser", severity: "advisory", cost: "slow", heal: "none", label: BROWSER_ARTIFACTS_LABEL, protects: ["Playwright/browser output stays under tmp/playwright-mcp"] },
|
|
17073
|
+
{ id: "mcp.playwright-registration", category: "browser", severity: "hard", cost: "fast", heal: "auto", label: MCP_RECONCILE_LABEL, protects: ["Org-managed Playwright MCP registrations exist and stay drift-free"] },
|
|
17074
|
+
{ id: "opencode.version", category: "opencode", severity: "hard", cost: "fast", heal: "surface", label: OPENCODE_VERSION_LABEL, protects: ["OpenCode loads the released MMI adapter"] },
|
|
17075
|
+
{ id: "opencode.hook-active", category: "opencode", severity: "hard", cost: "fast", heal: "surface", label: OPENCODE_HOOK_ACTIVE_LABEL, protects: ["OpenCode shell hooks stamp the active MMI adapter surface"] },
|
|
17076
|
+
{ id: "opencode.config-plugin", category: "opencode", severity: "hard", cost: "fast", heal: "auto", label: OPENCODE_CONFIG_PLUGIN_LABEL, protects: ["OpenCode config includes the MMI npm adapter"] },
|
|
17077
|
+
{ id: "opencode.surface-assets", category: "opencode", severity: "hard", cost: "fast", heal: "auto", label: OPENCODE_SURFACE_ASSETS_LABEL, protects: ["OpenCode commands and skills are materialized"] },
|
|
17078
|
+
{ id: "opencode.desktop-bootstrap", category: "opencode", severity: "advisory", cost: "slow", heal: "none", label: OPENCODE_DESKTOP_BOOTSTRAP_LABEL, protects: ["OpenCode Desktop is not pinned to a deleted project bootstrap"] },
|
|
17079
|
+
{ id: "opencode.legacy-config", category: "opencode", severity: "hard", cost: "fast", heal: "none", label: OPENCODE_LEGACY_CONFIG_LABEL, protects: ["Legacy OpenCode config cannot load stale bare plugin ids"] },
|
|
17080
|
+
{ id: "cursor.install", category: "cursor", severity: "hard", cost: "fast", heal: "auto", label: CURSOR_PLUGIN_INSTALL_LABEL, protects: ["Cursor Team Marketplace cache exists and is complete"] },
|
|
17081
|
+
{ id: "cursor.cache-cleanup", category: "cursor", severity: "advisory", cost: "fast", heal: "none", label: CURSOR_PLUGIN_CACHE_CLEANUP_LABEL, protects: ["Old Cursor cache pins are visible without unsafe automatic deletion"] },
|
|
17082
|
+
{ id: "cursor.enabled-pin", category: "cursor", severity: "hard", cost: "fast", heal: "none", label: CURSOR_ENABLED_PIN_LABEL, protects: ["Cursor Team Marketplace enabled pin matches the healthy cache"] },
|
|
17083
|
+
{ id: "cursor.third-party-extensibility", category: "cursor", severity: "hard", cost: "fast", heal: "none", label: CURSOR_THIRD_PARTY_EXTENSIBILITY_LABEL, protects: ["Cursor does not import third-party hooks that break the Team Marketplace plugin"] },
|
|
17084
|
+
{ id: "cursor.hook-cli", category: "cursor", severity: "hard", cost: "fast", heal: "surface", label: CURSOR_HOOK_CLI_LABEL, protects: ["Cursor hooks can reach a bundled or PATH mmi-cli"] },
|
|
17085
|
+
// Incident coverage (#2594) — three real incident classes that previously had zero doctor coverage.
|
|
17086
|
+
{ id: "repo-hygiene.lockfile-platform-optionals", category: "repo-hygiene", severity: "hard", cost: "fast", heal: "surface", label: LOCKFILE_OPTIONALS_LABEL, protects: ["A Windows-generated lockfile does not silently drop Linux-only optionalDependencies before CI"] },
|
|
17087
|
+
{ id: "repo-hygiene.file-encoding", category: "repo-hygiene", severity: "advisory", cost: "slow", heal: "surface", label: FILE_ENCODING_LABEL, protects: ["Managed source and config files stay clean UTF-8 without BOM or mojibake"] },
|
|
17088
|
+
{ id: "repo-hygiene.local-behind-origin", category: "repo-hygiene", severity: "advisory", cost: "slow", heal: "surface", label: LOCAL_BEHIND_ORIGIN_LABEL, protects: ["Local release branches (development/main/rc) do not fall behind origin after a release"] }
|
|
17089
|
+
];
|
|
17090
|
+
var DOCTOR_CHECK_METADATA_BY_LABEL = Object.fromEntries(
|
|
17091
|
+
DOCTOR_CHECK_METADATA.map((meta) => [meta.label, meta])
|
|
17092
|
+
);
|
|
17093
|
+
var DOCTOR_EXTENDED_CHECK_LABELS = new Set(
|
|
17094
|
+
DOCTOR_CHECK_METADATA.filter((meta) => meta.cost === "slow").map((meta) => meta.label)
|
|
17095
|
+
);
|
|
17096
|
+
function derivedDoctorCheckMetadata(label) {
|
|
17097
|
+
if (label.startsWith(HUB_COMPAT_LABEL)) return { ...DOCTOR_CHECK_METADATA_BY_LABEL[HUB_COMPAT_LABEL], label };
|
|
17098
|
+
if (label.startsWith(HUB_DEPLOY_FRESHNESS_LABEL)) return { ...DOCTOR_CHECK_METADATA_BY_LABEL[HUB_DEPLOY_FRESHNESS_LABEL], label };
|
|
17099
|
+
if (label.startsWith("@mutmutco design-system")) {
|
|
17100
|
+
return {
|
|
17101
|
+
id: "registry.design-system-components",
|
|
17102
|
+
category: "hub",
|
|
17103
|
+
severity: "advisory",
|
|
17104
|
+
cost: "slow",
|
|
17105
|
+
heal: "none",
|
|
17106
|
+
label,
|
|
17107
|
+
protects: ["Design-system component cache can be compared against the live registry"]
|
|
17108
|
+
};
|
|
17109
|
+
}
|
|
17110
|
+
if (label.startsWith("@mutmutco registry components")) {
|
|
17111
|
+
return {
|
|
17112
|
+
id: "registry.components",
|
|
17113
|
+
category: "hub",
|
|
17114
|
+
severity: "advisory",
|
|
17115
|
+
cost: "slow",
|
|
17116
|
+
heal: "none",
|
|
17117
|
+
label,
|
|
17118
|
+
protects: ["Repo component cache can be compared against the live registry"]
|
|
17119
|
+
};
|
|
17120
|
+
}
|
|
17121
|
+
return void 0;
|
|
17122
|
+
}
|
|
17123
|
+
function fallbackDoctorCheckMetadata(label) {
|
|
17124
|
+
return {
|
|
17125
|
+
id: `legacy.${label.toLowerCase().replace(/[^a-z0-9]+/g, ".").replace(/(^\.|\.$)/g, "") || "unknown"}`,
|
|
17126
|
+
category: "repo",
|
|
17127
|
+
severity: "hard",
|
|
17128
|
+
cost: "fast",
|
|
17129
|
+
heal: "none",
|
|
17130
|
+
label,
|
|
17131
|
+
protects: ["Legacy check without catalog metadata"]
|
|
17132
|
+
};
|
|
17133
|
+
}
|
|
17134
|
+
function doctorMetadataForLabel(label) {
|
|
17135
|
+
return DOCTOR_CHECK_METADATA_BY_LABEL[label] ?? derivedDoctorCheckMetadata(label) ?? fallbackDoctorCheckMetadata(label);
|
|
17136
|
+
}
|
|
17137
|
+
function doctorMetadataForCheck(check) {
|
|
17138
|
+
return check.metadata ?? doctorMetadataForLabel(check.label);
|
|
17139
|
+
}
|
|
16713
17140
|
function buildPluginResolvabilityCheck(input) {
|
|
16714
17141
|
const { state } = buildPluginGuardDecision(input);
|
|
16715
17142
|
const surface = input.surface ?? "shell";
|
|
@@ -16755,17 +17182,17 @@ function ghReleaseTarballApiArgs(tag) {
|
|
|
16755
17182
|
}
|
|
16756
17183
|
function cursorUserGlobalStatePath() {
|
|
16757
17184
|
if (process.platform === "win32") {
|
|
16758
|
-
const base = process.env.APPDATA || (0,
|
|
16759
|
-
return (0,
|
|
17185
|
+
const base = process.env.APPDATA || (0, import_node_path18.join)((0, import_node_os6.homedir)(), "AppData", "Roaming");
|
|
17186
|
+
return (0, import_node_path18.join)(base, "Cursor", "User", "globalStorage", "state.vscdb");
|
|
16760
17187
|
}
|
|
16761
17188
|
if (process.platform === "darwin") {
|
|
16762
|
-
return (0,
|
|
17189
|
+
return (0, import_node_path18.join)((0, import_node_os6.homedir)(), "Library", "Application Support", "Cursor", "User", "globalStorage", "state.vscdb");
|
|
16763
17190
|
}
|
|
16764
|
-
return (0,
|
|
17191
|
+
return (0, import_node_path18.join)((0, import_node_os6.homedir)(), ".config", "Cursor", "User", "globalStorage", "state.vscdb");
|
|
16765
17192
|
}
|
|
16766
17193
|
async function readCursorThirdPartyExtensibilityEnabled(execFileP5) {
|
|
16767
17194
|
const dbPath = cursorUserGlobalStatePath();
|
|
16768
|
-
if (!(0,
|
|
17195
|
+
if (!(0, import_node_fs17.existsSync)(dbPath)) return void 0;
|
|
16769
17196
|
try {
|
|
16770
17197
|
const { stdout } = await execFileP5("sqlite3", [dbPath, `SELECT value FROM ItemTable WHERE key = '${CURSOR_THIRD_PARTY_STATE_KEY}';`], {
|
|
16771
17198
|
timeout: 5e3
|
|
@@ -16779,57 +17206,57 @@ async function readCursorThirdPartyExtensibilityEnabled(execFileP5) {
|
|
|
16779
17206
|
}
|
|
16780
17207
|
}
|
|
16781
17208
|
function syncDirContents(src, dest) {
|
|
16782
|
-
(0,
|
|
16783
|
-
for (const name of (0,
|
|
16784
|
-
(0,
|
|
17209
|
+
(0, import_node_fs17.mkdirSync)(dest, { recursive: true });
|
|
17210
|
+
for (const name of (0, import_node_fs17.readdirSync)(dest)) {
|
|
17211
|
+
(0, import_node_fs17.rmSync)((0, import_node_path18.join)(dest, name), { recursive: true, force: true });
|
|
16785
17212
|
}
|
|
16786
|
-
(0,
|
|
17213
|
+
(0, import_node_fs17.cpSync)(src, dest, { recursive: true });
|
|
16787
17214
|
}
|
|
16788
17215
|
function releaseTag(releasedVersion) {
|
|
16789
17216
|
return releasedVersion.startsWith("v") ? releasedVersion : `v${releasedVersion}`;
|
|
16790
17217
|
}
|
|
16791
17218
|
async function extractPluginMmiFromHubCheckout(hubCheckout, tag, tmpRoot, execFileP5) {
|
|
16792
|
-
const tarFile = (0,
|
|
17219
|
+
const tarFile = (0, import_node_path18.join)(tmpRoot, "archive.tar");
|
|
16793
17220
|
try {
|
|
16794
17221
|
await execFileP5("git", gitFetchReleaseTagArgs(hubCheckout, tag), { timeout: 6e4 });
|
|
16795
17222
|
await execFileP5("git", ["-C", hubCheckout, "archive", "--format=tar", `--output=${tarFile}`, tag, "plugins/mmi"], {
|
|
16796
17223
|
timeout: 6e4
|
|
16797
17224
|
});
|
|
16798
17225
|
await execFileP5("tar", ["-xf", tarFile, "-C", tmpRoot], { timeout: 6e4 });
|
|
16799
|
-
const pluginMmi = (0,
|
|
16800
|
-
return (0,
|
|
17226
|
+
const pluginMmi = (0, import_node_path18.join)(tmpRoot, "plugins", "mmi");
|
|
17227
|
+
return (0, import_node_fs17.existsSync)((0, import_node_path18.join)(pluginMmi, PLUGIN_JSON_REL)) ? pluginMmi : void 0;
|
|
16801
17228
|
} catch {
|
|
16802
17229
|
return void 0;
|
|
16803
17230
|
}
|
|
16804
17231
|
}
|
|
16805
17232
|
async function downloadPluginMmiViaGh(tag, tmpRoot) {
|
|
16806
|
-
const tarPath = (0,
|
|
17233
|
+
const tarPath = (0, import_node_path18.join)(tmpRoot, "repo.tgz");
|
|
16807
17234
|
try {
|
|
16808
|
-
(0,
|
|
17235
|
+
(0, import_node_fs17.mkdirSync)(tmpRoot, { recursive: true });
|
|
16809
17236
|
const { stdout } = await execFileBuffer("gh", ghReleaseTarballApiArgs(tag), {
|
|
16810
17237
|
timeout: 12e4,
|
|
16811
17238
|
maxBuffer: 100 * 1024 * 1024,
|
|
16812
17239
|
encoding: "buffer",
|
|
16813
17240
|
windowsHide: true
|
|
16814
17241
|
});
|
|
16815
|
-
(0,
|
|
17242
|
+
(0, import_node_fs17.writeFileSync)(tarPath, stdout);
|
|
16816
17243
|
await execFileBuffer("tar", ["-xzf", tarPath, "-C", tmpRoot], { timeout: 12e4, windowsHide: true });
|
|
16817
|
-
const top = (0,
|
|
17244
|
+
const top = (0, import_node_fs17.readdirSync)(tmpRoot).find((entry) => entry !== "repo.tgz");
|
|
16818
17245
|
if (!top) return void 0;
|
|
16819
|
-
const pluginMmi = (0,
|
|
16820
|
-
return (0,
|
|
17246
|
+
const pluginMmi = (0, import_node_path18.join)(tmpRoot, top, "plugins", "mmi");
|
|
17247
|
+
return (0, import_node_fs17.existsSync)((0, import_node_path18.join)(pluginMmi, PLUGIN_JSON_REL)) ? pluginMmi : void 0;
|
|
16821
17248
|
} catch {
|
|
16822
17249
|
return void 0;
|
|
16823
17250
|
}
|
|
16824
17251
|
}
|
|
16825
17252
|
async function resolvePluginMmiSource(releasedVersion, hubCheckout, tmpRoot, execFileP5) {
|
|
16826
|
-
(0,
|
|
17253
|
+
(0, import_node_fs17.mkdirSync)(tmpRoot, { recursive: true });
|
|
16827
17254
|
const tag = releaseTag(releasedVersion);
|
|
16828
17255
|
if (hubCheckout) {
|
|
16829
17256
|
const fromHub = await extractPluginMmiFromHubCheckout(hubCheckout, tag, tmpRoot, execFileP5);
|
|
16830
17257
|
if (fromHub) return fromHub;
|
|
16831
17258
|
}
|
|
16832
|
-
return downloadPluginMmiViaGh(tag, (0,
|
|
17259
|
+
return downloadPluginMmiViaGh(tag, (0, import_node_path18.join)(tmpRoot, "gh"));
|
|
16833
17260
|
}
|
|
16834
17261
|
function cursorPluginPinsNeedingSeed(pins, releasedVersion) {
|
|
16835
17262
|
if (!isSemverVersion2(releasedVersion)) return pins.filter((pin) => !pin.hasPluginJson || !pin.hasHooksJson || pin.isEmpty);
|
|
@@ -16843,7 +17270,7 @@ function cursorPluginCacheSeedTargets(pins, releasedVersion, cacheRoot, cacheRoo
|
|
|
16843
17270
|
const needing = cursorPluginPinsNeedingSeed(pins, releasedVersion);
|
|
16844
17271
|
if (needing.length > 0) return needing.map((pin) => pin.path);
|
|
16845
17272
|
if (pins.length === 0 && cacheRoot && cacheRootExists && isSemverVersion2(releasedVersion)) {
|
|
16846
|
-
return [(0,
|
|
17273
|
+
return [(0, import_node_path18.join)(cacheRoot, `v${releasedVersion.replace(/^v/, "")}`)];
|
|
16847
17274
|
}
|
|
16848
17275
|
return [];
|
|
16849
17276
|
}
|
|
@@ -16858,10 +17285,10 @@ async function applyCursorPluginCacheSeed(input) {
|
|
|
16858
17285
|
for (const dest of targets) {
|
|
16859
17286
|
syncDirContents(source, dest);
|
|
16860
17287
|
}
|
|
16861
|
-
(0,
|
|
17288
|
+
(0, import_node_fs17.rmSync)(tmpRoot, { recursive: true, force: true });
|
|
16862
17289
|
return true;
|
|
16863
17290
|
}
|
|
16864
|
-
var
|
|
17291
|
+
var CURSOR_ENABLED_PIN_LABEL2 = "Cursor Team Marketplace enabled plugin pin";
|
|
16865
17292
|
function cursorSplitBrainCapabilities() {
|
|
16866
17293
|
const skills = OPENCODE_WORKFLOW_COMMANDS.map((c) => `/${c}`).join(", ");
|
|
16867
17294
|
return `org skills (${skills}) and SessionStart hooks`;
|
|
@@ -16881,7 +17308,7 @@ function parseCursorPluginLog(text) {
|
|
|
16881
17308
|
return { enabledPin, loadSucceeded, loadFailed };
|
|
16882
17309
|
}
|
|
16883
17310
|
function buildCursorEnabledPinCheck(input) {
|
|
16884
|
-
const label =
|
|
17311
|
+
const label = CURSOR_ENABLED_PIN_LABEL2;
|
|
16885
17312
|
const healthy = { ok: true, label, fix: "" };
|
|
16886
17313
|
if (!input.isOrgRepo) return healthy;
|
|
16887
17314
|
if (!input.enabledPin && !input.loadFailed) return healthy;
|
|
@@ -16916,7 +17343,7 @@ var PLAYWRIGHT_MCP_SPEC = {
|
|
|
16916
17343
|
args: ["-y", "@playwright/mcp@latest", "--output-dir", "tmp/playwright-mcp"],
|
|
16917
17344
|
legacyNames: []
|
|
16918
17345
|
};
|
|
16919
|
-
var
|
|
17346
|
+
var MCP_RECONCILE_LABEL2 = "MCP server registrations (org-managed Playwright)";
|
|
16920
17347
|
var MCP_RECONCILE_FIX = "register/reconcile the org Playwright MCP server (npx -y @playwright/mcp@latest --output-dir tmp/playwright-mcp) \u2014 run `mmi-cli doctor --apply`; see skills/browser-automation/SKILL.md and bootstrap seed mcp-playwright.template.json";
|
|
16921
17348
|
function extractServerBlock(content, format, spec) {
|
|
16922
17349
|
return format === "json" ? extractJsonServer(content, spec) : extractTomlServer(content, spec);
|
|
@@ -17030,7 +17457,7 @@ function planClaudeCliMcpHeal(item, spec) {
|
|
|
17030
17457
|
function buildMcpReconcileCheck(plan) {
|
|
17031
17458
|
const base = {
|
|
17032
17459
|
ok: true,
|
|
17033
|
-
label:
|
|
17460
|
+
label: MCP_RECONCILE_LABEL2,
|
|
17034
17461
|
fix: MCP_RECONCILE_FIX,
|
|
17035
17462
|
severityOverride: "advisory"
|
|
17036
17463
|
};
|
|
@@ -17217,9 +17644,9 @@ ${block}
|
|
|
17217
17644
|
}
|
|
17218
17645
|
|
|
17219
17646
|
// src/cli-doctor-shared.ts
|
|
17220
|
-
var import_node_fs17 = require("node:fs");
|
|
17221
|
-
var import_node_path18 = require("node:path");
|
|
17222
17647
|
var import_node_fs18 = require("node:fs");
|
|
17648
|
+
var import_node_path19 = require("node:path");
|
|
17649
|
+
var import_node_fs19 = require("node:fs");
|
|
17223
17650
|
var GC_GH_TIMEOUT_MS = 2e4;
|
|
17224
17651
|
async function awsCallerArn() {
|
|
17225
17652
|
try {
|
|
@@ -17265,7 +17692,7 @@ async function localBranchHeads() {
|
|
|
17265
17692
|
}
|
|
17266
17693
|
async function currentRepoWorktreeGitRoot(repoRoot) {
|
|
17267
17694
|
const gitCommonDir = (await execFileP2("git", ["rev-parse", "--git-common-dir"], { timeout: GIT_TIMEOUT_MS }).catch(() => ({ stdout: "" }))).stdout.trim();
|
|
17268
|
-
return gitCommonDir ? (0,
|
|
17695
|
+
return gitCommonDir ? (0, import_node_path19.resolve)(repoRoot, gitCommonDir, "worktrees") : "";
|
|
17269
17696
|
}
|
|
17270
17697
|
async function worktreeBranches() {
|
|
17271
17698
|
const { stdout } = await execFileP2("git", ["worktree", "list", "--porcelain"], { timeout: GIT_TIMEOUT_MS });
|
|
@@ -17285,18 +17712,18 @@ function resolveGitdirForWorktreeFile(worktreePath, content) {
|
|
|
17285
17712
|
const match = /^gitdir:\s*(.+)\s*$/im.exec(content);
|
|
17286
17713
|
if (!match?.[1]) return void 0;
|
|
17287
17714
|
const raw = match[1].trim();
|
|
17288
|
-
return (0,
|
|
17715
|
+
return (0, import_node_path19.isAbsolute)(raw) ? raw : (0, import_node_path19.resolve)(worktreePath, raw);
|
|
17289
17716
|
}
|
|
17290
17717
|
function metadataOwnsMissingWorktreeDir(worktreePath, worktreeGitRoot) {
|
|
17291
17718
|
if (!worktreeGitRoot) return false;
|
|
17292
17719
|
try {
|
|
17293
|
-
const entries = (0,
|
|
17720
|
+
const entries = (0, import_node_fs19.readdirSync)(worktreeGitRoot, { withFileTypes: true });
|
|
17294
17721
|
for (const ent of entries) {
|
|
17295
17722
|
if (!ent.isDirectory()) continue;
|
|
17296
17723
|
try {
|
|
17297
|
-
const gitdirPath = (0,
|
|
17298
|
-
const resolvedGitdir = (0,
|
|
17299
|
-
if (sameWorktreeMetadataPath((0,
|
|
17724
|
+
const gitdirPath = (0, import_node_fs18.readFileSync)((0, import_node_path19.join)(worktreeGitRoot, ent.name, "gitdir"), "utf8").trim();
|
|
17725
|
+
const resolvedGitdir = (0, import_node_path19.isAbsolute)(gitdirPath) ? gitdirPath : (0, import_node_path19.resolve)(worktreeGitRoot, ent.name, gitdirPath);
|
|
17726
|
+
if (sameWorktreeMetadataPath((0, import_node_path19.dirname)(resolvedGitdir), worktreePath)) return true;
|
|
17300
17727
|
} catch {
|
|
17301
17728
|
}
|
|
17302
17729
|
}
|
|
@@ -17306,7 +17733,7 @@ function metadataOwnsMissingWorktreeDir(worktreePath, worktreeGitRoot) {
|
|
|
17306
17733
|
}
|
|
17307
17734
|
function pathExistsKnown(path2) {
|
|
17308
17735
|
try {
|
|
17309
|
-
(0,
|
|
17736
|
+
(0, import_node_fs19.statSync)(path2);
|
|
17310
17737
|
return true;
|
|
17311
17738
|
} catch (e) {
|
|
17312
17739
|
const code = typeof e === "object" && e && "code" in e ? String(e.code ?? "") : "";
|
|
@@ -17315,10 +17742,10 @@ function pathExistsKnown(path2) {
|
|
|
17315
17742
|
}
|
|
17316
17743
|
}
|
|
17317
17744
|
function inspectSiblingWorktreeDir(path2, worktreeGitRoot) {
|
|
17318
|
-
const gitPath = (0,
|
|
17745
|
+
const gitPath = (0, import_node_path19.join)(path2, ".git");
|
|
17319
17746
|
let st;
|
|
17320
17747
|
try {
|
|
17321
|
-
st = (0,
|
|
17748
|
+
st = (0, import_node_fs19.lstatSync)(gitPath);
|
|
17322
17749
|
} catch (e) {
|
|
17323
17750
|
const code = typeof e === "object" && e && "code" in e ? String(e.code ?? "") : "";
|
|
17324
17751
|
if (code === "ENOENT" || code === "ENOTDIR") {
|
|
@@ -17335,7 +17762,7 @@ function inspectSiblingWorktreeDir(path2, worktreeGitRoot) {
|
|
|
17335
17762
|
if (st.isDirectory()) return { path: path2, gitType: "dir" };
|
|
17336
17763
|
if (!st.isFile()) return { path: path2, gitType: "other" };
|
|
17337
17764
|
try {
|
|
17338
|
-
const gitFileContent = (0,
|
|
17765
|
+
const gitFileContent = (0, import_node_fs18.readFileSync)(gitPath, "utf8");
|
|
17339
17766
|
const gitdir = resolveGitdirForWorktreeFile(path2, gitFileContent);
|
|
17340
17767
|
const gitDirExists = gitdir ? pathExistsKnown(gitdir) : false;
|
|
17341
17768
|
return {
|
|
@@ -17352,7 +17779,7 @@ function inspectSiblingWorktreeDir(path2, worktreeGitRoot) {
|
|
|
17352
17779
|
}
|
|
17353
17780
|
function inspectDeadWorktreeDirContent(path2) {
|
|
17354
17781
|
try {
|
|
17355
|
-
return { entries: (0,
|
|
17782
|
+
return { entries: (0, import_node_fs19.readdirSync)(path2) };
|
|
17356
17783
|
} catch (e) {
|
|
17357
17784
|
const code = typeof e === "object" && e && "code" in e ? String(e.code ?? "") : "";
|
|
17358
17785
|
return { error: code ? `unable to inspect directory contents (${code})` : "unable to inspect directory contents" };
|
|
@@ -17371,8 +17798,8 @@ async function siblingWorktreeDirs() {
|
|
|
17371
17798
|
const worktreeGitRoot = await currentRepoWorktreeGitRoot(repoRoot);
|
|
17372
17799
|
const siblingRoot = siblingMmiWorktreesRoot(repoRoot);
|
|
17373
17800
|
try {
|
|
17374
|
-
const entries = (0,
|
|
17375
|
-
return entries.filter((ent) => ent.isDirectory()).map((ent) => inspectSiblingWorktreeDir((0,
|
|
17801
|
+
const entries = (0, import_node_fs19.readdirSync)(siblingRoot, { withFileTypes: true });
|
|
17802
|
+
return entries.filter((ent) => ent.isDirectory()).map((ent) => inspectSiblingWorktreeDir((0, import_node_path19.join)(siblingRoot, ent.name), worktreeGitRoot)).filter((entry) => Boolean(entry));
|
|
17376
17803
|
} catch {
|
|
17377
17804
|
return [];
|
|
17378
17805
|
}
|
|
@@ -17422,7 +17849,7 @@ async function fetchHubVersionInfo(baseUrl) {
|
|
|
17422
17849
|
}
|
|
17423
17850
|
function readRepoVersion() {
|
|
17424
17851
|
try {
|
|
17425
|
-
return JSON.parse((0,
|
|
17852
|
+
return JSON.parse((0, import_node_fs20.readFileSync)((0, import_node_path20.join)(process.cwd(), ".claude-plugin", "plugin.json"), "utf8")).version || void 0;
|
|
17426
17853
|
} catch {
|
|
17427
17854
|
return void 0;
|
|
17428
17855
|
}
|
|
@@ -17574,11 +18001,11 @@ async function applyPluginHeal(token, surface, log, opts) {
|
|
|
17574
18001
|
}
|
|
17575
18002
|
var installedPluginsPath = (surface = detectSurface(process.env)) => {
|
|
17576
18003
|
const homeDir = surface === "codex" ? ".codex" : ".claude";
|
|
17577
|
-
return (0,
|
|
18004
|
+
return (0, import_node_path20.join)((0, import_node_os7.homedir)(), homeDir, "plugins", "installed_plugins.json");
|
|
17578
18005
|
};
|
|
17579
18006
|
function readInstalledPlugins(surface = detectSurface(process.env)) {
|
|
17580
18007
|
try {
|
|
17581
|
-
return JSON.parse((0,
|
|
18008
|
+
return JSON.parse((0, import_node_fs20.readFileSync)(installedPluginsPath(surface), "utf8"));
|
|
17582
18009
|
} catch {
|
|
17583
18010
|
return null;
|
|
17584
18011
|
}
|
|
@@ -17586,13 +18013,13 @@ function readInstalledPlugins(surface = detectSurface(process.env)) {
|
|
|
17586
18013
|
function marketplaceCloneCandidates(surface, home) {
|
|
17587
18014
|
if (surface === "codex") {
|
|
17588
18015
|
return [
|
|
17589
|
-
(0,
|
|
17590
|
-
(0,
|
|
18016
|
+
(0, import_node_path20.join)(home, ".codex", ".tmp", "marketplaces", "mutmutco"),
|
|
18017
|
+
(0, import_node_path20.join)(home, ".codex", "plugins", "marketplaces", "mutmutco")
|
|
17591
18018
|
];
|
|
17592
18019
|
}
|
|
17593
|
-
return [(0,
|
|
18020
|
+
return [(0, import_node_path20.join)(home, ".claude", "plugins", "marketplaces", "mutmutco")];
|
|
17594
18021
|
}
|
|
17595
|
-
function marketplaceClonePresent(surface, home, exists =
|
|
18022
|
+
function marketplaceClonePresent(surface, home, exists = import_node_fs20.existsSync) {
|
|
17596
18023
|
return marketplaceCloneCandidates(surface, home).some(exists);
|
|
17597
18024
|
}
|
|
17598
18025
|
function snapshotPluginGuardInput(surface = detectSurface(process.env), isOrgRepo = false) {
|
|
@@ -17602,14 +18029,14 @@ function snapshotPluginGuardInput(surface = detectSurface(process.env), isOrgRep
|
|
|
17602
18029
|
isOrgRepo,
|
|
17603
18030
|
installRecordPresent: hasUserInstallRecord(installed, MMI_PLUGIN_ID) || hasProjectInstallRecord(installed, MMI_PLUGIN_ID, process.cwd()),
|
|
17604
18031
|
marketplaceClonePresent: marketplaceClonePresent(surface, (0, import_node_os7.homedir)()),
|
|
17605
|
-
pluginCachePresent: (0,
|
|
18032
|
+
pluginCachePresent: (0, import_node_fs20.existsSync)((0, import_node_path20.join)((0, import_node_os7.homedir)(), homeDir, "plugins", "cache", "mutmutco", "mmi"))
|
|
17606
18033
|
};
|
|
17607
18034
|
}
|
|
17608
18035
|
function installedPluginSources() {
|
|
17609
18036
|
return ["claude", "codex"].map((surface) => {
|
|
17610
|
-
const recordPath = (0,
|
|
18037
|
+
const recordPath = (0, import_node_path20.join)((0, import_node_os7.homedir)(), `.${surface}`, "plugins", "installed_plugins.json");
|
|
17611
18038
|
try {
|
|
17612
|
-
return { surface, installed: JSON.parse((0,
|
|
18039
|
+
return { surface, installed: JSON.parse((0, import_node_fs20.readFileSync)(recordPath, "utf8")), recordPath };
|
|
17613
18040
|
} catch {
|
|
17614
18041
|
return { surface, installed: null, recordPath };
|
|
17615
18042
|
}
|
|
@@ -17617,7 +18044,7 @@ function installedPluginSources() {
|
|
|
17617
18044
|
}
|
|
17618
18045
|
function readClaudeSettings() {
|
|
17619
18046
|
try {
|
|
17620
|
-
return JSON.parse((0,
|
|
18047
|
+
return JSON.parse((0, import_node_fs20.readFileSync)((0, import_node_path20.join)(process.cwd(), ".claude", "settings.json"), "utf8"));
|
|
17621
18048
|
} catch {
|
|
17622
18049
|
return null;
|
|
17623
18050
|
}
|
|
@@ -17639,7 +18066,7 @@ function writeProjectInstallRecord(record) {
|
|
|
17639
18066
|
const list = file.plugins[MMI_PLUGIN_ID] ?? [];
|
|
17640
18067
|
list.push(record);
|
|
17641
18068
|
file.plugins[MMI_PLUGIN_ID] = list;
|
|
17642
|
-
(0,
|
|
18069
|
+
(0, import_node_fs20.writeFileSync)(installedPluginsPath(), `${JSON.stringify(file, null, 2)}
|
|
17643
18070
|
`, "utf8");
|
|
17644
18071
|
return true;
|
|
17645
18072
|
} catch {
|
|
@@ -17652,9 +18079,9 @@ function backupAndWriteInstalledPlugins(records, pluginId) {
|
|
|
17652
18079
|
if (!file) return false;
|
|
17653
18080
|
if (!file.plugins) file.plugins = {};
|
|
17654
18081
|
const path2 = installedPluginsPath();
|
|
17655
|
-
(0,
|
|
18082
|
+
(0, import_node_fs20.copyFileSync)(path2, `${path2}.bak`);
|
|
17656
18083
|
file.plugins[pluginId] = records;
|
|
17657
|
-
(0,
|
|
18084
|
+
(0, import_node_fs20.writeFileSync)(path2, `${JSON.stringify(file, null, 2)}
|
|
17658
18085
|
`, "utf8");
|
|
17659
18086
|
return true;
|
|
17660
18087
|
} catch {
|
|
@@ -17662,22 +18089,22 @@ function backupAndWriteInstalledPlugins(records, pluginId) {
|
|
|
17662
18089
|
}
|
|
17663
18090
|
}
|
|
17664
18091
|
function opencodeConfigDir() {
|
|
17665
|
-
return (0,
|
|
18092
|
+
return (0, import_node_path20.join)((0, import_node_os7.homedir)(), ".config", "opencode");
|
|
17666
18093
|
}
|
|
17667
18094
|
function opencodeConfigPath() {
|
|
17668
|
-
return (0,
|
|
18095
|
+
return (0, import_node_path20.join)(opencodeConfigDir(), "opencode.jsonc");
|
|
17669
18096
|
}
|
|
17670
18097
|
function opencodeCommandsDir() {
|
|
17671
|
-
return (0,
|
|
18098
|
+
return (0, import_node_path20.join)(opencodeConfigDir(), "commands");
|
|
17672
18099
|
}
|
|
17673
18100
|
function opencodeSkillsPath() {
|
|
17674
|
-
return (0,
|
|
18101
|
+
return (0, import_node_path20.join)(opencodeConfigDir(), "node_modules", "@mutmutco", "opencode-mmi", "skills");
|
|
17675
18102
|
}
|
|
17676
18103
|
function opencodeConfigSnapshot() {
|
|
17677
18104
|
const path2 = opencodeConfigPath();
|
|
17678
|
-
if (!(0,
|
|
18105
|
+
if (!(0, import_node_fs20.existsSync)(path2)) return { path: path2, hasConfig: false, hasPluginField: false, parseOk: true };
|
|
17679
18106
|
try {
|
|
17680
|
-
const raw = (0,
|
|
18107
|
+
const raw = (0, import_node_fs20.readFileSync)(path2, "utf8");
|
|
17681
18108
|
const parsed = JSON.parse(stripJsonc(raw));
|
|
17682
18109
|
const hasPluginField = Object.prototype.hasOwnProperty.call(parsed, "plugin");
|
|
17683
18110
|
const skillsPaths = Array.isArray(parsed.skills?.paths) ? parsed.skills.paths.filter((p) => typeof p === "string") : void 0;
|
|
@@ -17700,9 +18127,9 @@ function writeOpencodeConfigPlugin(snapshot) {
|
|
|
17700
18127
|
const plan = planOpencodeConfigWrite(snapshot.hasConfig ? snapshot.raw : void 0);
|
|
17701
18128
|
if (plan.action === "already") return true;
|
|
17702
18129
|
if (!plan.text || plan.action === "unsafe") return false;
|
|
17703
|
-
(0,
|
|
17704
|
-
if (snapshot.hasConfig) (0,
|
|
17705
|
-
(0,
|
|
18130
|
+
(0, import_node_fs20.mkdirSync)((0, import_node_path20.dirname)(path2), { recursive: true });
|
|
18131
|
+
if (snapshot.hasConfig) (0, import_node_fs20.copyFileSync)(path2, `${path2}.bak`);
|
|
18132
|
+
(0, import_node_fs20.writeFileSync)(path2, plan.text, "utf8");
|
|
17706
18133
|
return true;
|
|
17707
18134
|
} catch {
|
|
17708
18135
|
return false;
|
|
@@ -17717,9 +18144,9 @@ function writeOpencodeSkillsPath(snapshot, skillsPath) {
|
|
|
17717
18144
|
const normalized = skillsPath.replace(/\\/g, "/");
|
|
17718
18145
|
if (!paths.some((p) => p.replace(/\\/g, "/") === normalized)) paths.push(skillsPath.replace(/\\/g, "/"));
|
|
17719
18146
|
parsed.skills = { ...skills, paths };
|
|
17720
|
-
(0,
|
|
17721
|
-
if (snapshot.hasConfig && (0,
|
|
17722
|
-
(0,
|
|
18147
|
+
(0, import_node_fs20.mkdirSync)((0, import_node_path20.dirname)(snapshot.path), { recursive: true });
|
|
18148
|
+
if (snapshot.hasConfig && (0, import_node_fs20.existsSync)(snapshot.path)) (0, import_node_fs20.copyFileSync)(snapshot.path, `${snapshot.path}.bak`);
|
|
18149
|
+
(0, import_node_fs20.writeFileSync)(snapshot.path, `${JSON.stringify(parsed, null, 2)}
|
|
17723
18150
|
`, "utf8");
|
|
17724
18151
|
return true;
|
|
17725
18152
|
} catch {
|
|
@@ -17728,7 +18155,7 @@ function writeOpencodeSkillsPath(snapshot, skillsPath) {
|
|
|
17728
18155
|
}
|
|
17729
18156
|
function opencodeExistingCommands() {
|
|
17730
18157
|
try {
|
|
17731
|
-
return (0,
|
|
18158
|
+
return (0, import_node_fs20.readdirSync)(opencodeCommandsDir(), { withFileTypes: true }).filter((entry) => entry.isFile() && entry.name.endsWith(".md")).map((entry) => entry.name.slice(0, -3).toLowerCase());
|
|
17732
18159
|
} catch {
|
|
17733
18160
|
return [];
|
|
17734
18161
|
}
|
|
@@ -17736,9 +18163,9 @@ function opencodeExistingCommands() {
|
|
|
17736
18163
|
function writeOpencodeCommandFiles() {
|
|
17737
18164
|
try {
|
|
17738
18165
|
const dir = opencodeCommandsDir();
|
|
17739
|
-
(0,
|
|
18166
|
+
(0, import_node_fs20.mkdirSync)(dir, { recursive: true });
|
|
17740
18167
|
for (const command of OPENCODE_WORKFLOW_COMMANDS) {
|
|
17741
|
-
(0,
|
|
18168
|
+
(0, import_node_fs20.writeFileSync)((0, import_node_path20.join)(dir, `${command}.md`), opencodeCommandMarkdown(command), "utf8");
|
|
17742
18169
|
}
|
|
17743
18170
|
return true;
|
|
17744
18171
|
} catch {
|
|
@@ -17747,12 +18174,12 @@ function writeOpencodeCommandFiles() {
|
|
|
17747
18174
|
}
|
|
17748
18175
|
function readOpencodeAdapterDiskVersion() {
|
|
17749
18176
|
const candidates = [
|
|
17750
|
-
(0,
|
|
17751
|
-
(0,
|
|
18177
|
+
(0, import_node_path20.join)(opencodeConfigDir(), "node_modules", "@mutmutco", "opencode-mmi", "package.json"),
|
|
18178
|
+
(0, import_node_path20.join)((0, import_node_os7.homedir)(), ".cache", "opencode", "node_modules", "@mutmutco", "opencode-mmi", "package.json")
|
|
17752
18179
|
];
|
|
17753
18180
|
for (const path2 of candidates) {
|
|
17754
18181
|
try {
|
|
17755
|
-
const parsed = JSON.parse((0,
|
|
18182
|
+
const parsed = JSON.parse((0, import_node_fs20.readFileSync)(path2, "utf8"));
|
|
17756
18183
|
if (typeof parsed.version === "string" && parsed.version.trim()) return parsed.version.trim();
|
|
17757
18184
|
} catch {
|
|
17758
18185
|
continue;
|
|
@@ -17768,7 +18195,7 @@ async function forceInstallOpencodeMmiPlugins(snapshot, log) {
|
|
|
17768
18195
|
try {
|
|
17769
18196
|
const specs = opencodeMmiPluginSpecs(snapshot);
|
|
17770
18197
|
log(` \u21BB force-refreshing OpenCode MMI npm plugin(s): ${specs.join(", ")}\u2026`);
|
|
17771
|
-
(0,
|
|
18198
|
+
(0, import_node_fs20.mkdirSync)(opencodeConfigDir(), { recursive: true });
|
|
17772
18199
|
await runHostBin("npm", ["install", "--prefix", opencodeConfigDir(), "--force", ...specs], { timeout: NPM_UPDATE_TIMEOUT_MS });
|
|
17773
18200
|
return true;
|
|
17774
18201
|
} catch {
|
|
@@ -17776,12 +18203,12 @@ async function forceInstallOpencodeMmiPlugins(snapshot, log) {
|
|
|
17776
18203
|
}
|
|
17777
18204
|
}
|
|
17778
18205
|
function opencodePackagesRoot() {
|
|
17779
|
-
return (0,
|
|
18206
|
+
return (0, import_node_path20.join)((0, import_node_os7.homedir)(), ".cache", "opencode", "packages", "@mutmutco");
|
|
17780
18207
|
}
|
|
17781
18208
|
function opencodePluginCacheDirs() {
|
|
17782
18209
|
const root = opencodePackagesRoot();
|
|
17783
18210
|
try {
|
|
17784
|
-
return (0,
|
|
18211
|
+
return (0, import_node_fs20.readdirSync)(root, { withFileTypes: true }).filter((e) => e.isDirectory() && e.name.startsWith("opencode-mmi@") && !e.name.startsWith(".")).map((e) => (0, import_node_path20.join)(root, e.name));
|
|
17785
18212
|
} catch {
|
|
17786
18213
|
return [];
|
|
17787
18214
|
}
|
|
@@ -17789,7 +18216,7 @@ function opencodePluginCacheDirs() {
|
|
|
17789
18216
|
function readOpencodeCacheDirVersion(cacheDir) {
|
|
17790
18217
|
try {
|
|
17791
18218
|
const parsed = JSON.parse(
|
|
17792
|
-
(0,
|
|
18219
|
+
(0, import_node_fs20.readFileSync)((0, import_node_path20.join)(cacheDir, "node_modules", "@mutmutco", "opencode-mmi", "package.json"), "utf8")
|
|
17793
18220
|
);
|
|
17794
18221
|
return typeof parsed.version === "string" && parsed.version.trim() ? parsed.version.trim() : void 0;
|
|
17795
18222
|
} catch {
|
|
@@ -17812,9 +18239,9 @@ function quarantineStaleOpencodePluginCaches(releasedVersion, log) {
|
|
|
17812
18239
|
});
|
|
17813
18240
|
if (!plan.quarantine) continue;
|
|
17814
18241
|
try {
|
|
17815
|
-
const quarantineRoot = (0,
|
|
17816
|
-
(0,
|
|
17817
|
-
(0,
|
|
18242
|
+
const quarantineRoot = (0, import_node_path20.join)(opencodePackagesRoot(), ".mmi-quarantine", stamp);
|
|
18243
|
+
(0, import_node_fs20.mkdirSync)(quarantineRoot, { recursive: true });
|
|
18244
|
+
(0, import_node_fs20.renameSync)(cacheDir, (0, import_node_path20.join)(quarantineRoot, (0, import_node_path20.basename)(cacheDir)));
|
|
17818
18245
|
log(` \u21BB quarantined stale OpenCode plugin cache ${plan.cacheVersion} (< ${plan.releasedVersion}) \u2014 OpenCode reinstalls the current adapter on next start`);
|
|
17819
18246
|
moved = true;
|
|
17820
18247
|
} catch {
|
|
@@ -17840,30 +18267,30 @@ function opencodePluginVersionsForReport() {
|
|
|
17840
18267
|
}
|
|
17841
18268
|
function opencodeDesktopLogsRoot() {
|
|
17842
18269
|
if (process.platform === "win32") {
|
|
17843
|
-
const base = process.env.APPDATA || (0,
|
|
17844
|
-
return (0,
|
|
18270
|
+
const base = process.env.APPDATA || (0, import_node_path20.join)((0, import_node_os7.homedir)(), "AppData", "Roaming");
|
|
18271
|
+
return (0, import_node_path20.join)(base, "ai.opencode.desktop", "logs");
|
|
17845
18272
|
}
|
|
17846
18273
|
if (process.platform === "darwin") {
|
|
17847
|
-
return (0,
|
|
18274
|
+
return (0, import_node_path20.join)((0, import_node_os7.homedir)(), "Library", "Application Support", "ai.opencode.desktop", "logs");
|
|
17848
18275
|
}
|
|
17849
|
-
return (0,
|
|
18276
|
+
return (0, import_node_path20.join)((0, import_node_os7.homedir)(), ".config", "ai.opencode.desktop", "logs");
|
|
17850
18277
|
}
|
|
17851
18278
|
function opencodeDesktopBootstrapSnapshot() {
|
|
17852
18279
|
const root = opencodeDesktopLogsRoot();
|
|
17853
18280
|
try {
|
|
17854
|
-
const sessionDirs = (0,
|
|
18281
|
+
const sessionDirs = (0, import_node_fs20.readdirSync)(root, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => (0, import_node_path20.join)(root, entry.name)).sort((a, b) => (0, import_node_fs20.statSync)(b).mtimeMs - (0, import_node_fs20.statSync)(a).mtimeMs);
|
|
17855
18282
|
const newest = sessionDirs[0];
|
|
17856
18283
|
if (!newest) return [];
|
|
17857
|
-
const logPath = (0,
|
|
17858
|
-
const text = (0,
|
|
17859
|
-
return opencodeAgentDirectoriesFromLog(text).filter((directory) => !(0,
|
|
18284
|
+
const logPath = (0, import_node_path20.join)(newest, "renderer.log");
|
|
18285
|
+
const text = (0, import_node_fs20.readFileSync)(logPath, "utf8");
|
|
18286
|
+
return opencodeAgentDirectoriesFromLog(text).filter((directory) => !(0, import_node_fs20.existsSync)(directory)).map((directory) => ({ directory, logPath }));
|
|
17860
18287
|
} catch {
|
|
17861
18288
|
return [];
|
|
17862
18289
|
}
|
|
17863
18290
|
}
|
|
17864
18291
|
function opencodeLegacyConfigSnapshot() {
|
|
17865
|
-
const legacyPath = (0,
|
|
17866
|
-
if (!(0,
|
|
18292
|
+
const legacyPath = (0, import_node_path20.join)((0, import_node_os7.homedir)(), ".opencode", "opencode.json");
|
|
18293
|
+
if (!(0, import_node_fs20.existsSync)(legacyPath)) return {};
|
|
17867
18294
|
const content = readTextFile(legacyPath);
|
|
17868
18295
|
if (content == null) return {};
|
|
17869
18296
|
const plugins = parseOpencodeLegacyConfigPlugins(content);
|
|
@@ -17875,24 +18302,24 @@ function opencodeLegacyConfigSnapshot() {
|
|
|
17875
18302
|
function quarantineOpencodeLegacyConfig(legacyPath) {
|
|
17876
18303
|
try {
|
|
17877
18304
|
const backupPath = `${legacyPath}.bak`;
|
|
17878
|
-
if ((0,
|
|
17879
|
-
(0,
|
|
18305
|
+
if ((0, import_node_fs20.existsSync)(backupPath)) return false;
|
|
18306
|
+
(0, import_node_fs20.renameSync)(legacyPath, backupPath);
|
|
17880
18307
|
return true;
|
|
17881
18308
|
} catch {
|
|
17882
18309
|
return false;
|
|
17883
18310
|
}
|
|
17884
18311
|
}
|
|
17885
18312
|
function cursorPluginCacheRoot() {
|
|
17886
|
-
return (0,
|
|
18313
|
+
return (0, import_node_path20.join)((0, import_node_os7.homedir)(), ".cursor", "plugins", "cache", "mutmutco", "mmi");
|
|
17887
18314
|
}
|
|
17888
18315
|
function cursorLogsRoot() {
|
|
17889
|
-
if (process.platform === "win32") return (0,
|
|
17890
|
-
if (process.platform === "darwin") return (0,
|
|
17891
|
-
return (0,
|
|
18316
|
+
if (process.platform === "win32") return (0, import_node_path20.join)((0, import_node_os7.homedir)(), "AppData", "Roaming", "Cursor", "logs");
|
|
18317
|
+
if (process.platform === "darwin") return (0, import_node_path20.join)((0, import_node_os7.homedir)(), "Library", "Application Support", "Cursor", "logs");
|
|
18318
|
+
return (0, import_node_path20.join)((0, import_node_os7.homedir)(), ".config", "Cursor", "logs");
|
|
17892
18319
|
}
|
|
17893
18320
|
function safeReaddirNames(dir) {
|
|
17894
18321
|
try {
|
|
17895
|
-
return (0,
|
|
18322
|
+
return (0, import_node_fs20.readdirSync)(dir, { withFileTypes: true }).map((e) => e.name);
|
|
17896
18323
|
} catch {
|
|
17897
18324
|
return [];
|
|
17898
18325
|
}
|
|
@@ -17901,15 +18328,15 @@ function readCursorPluginLogText() {
|
|
|
17901
18328
|
const root = cursorLogsRoot();
|
|
17902
18329
|
const sessions = safeReaddirNames(root).sort().reverse();
|
|
17903
18330
|
for (const session of sessions.slice(0, 5)) {
|
|
17904
|
-
const sessionDir = (0,
|
|
18331
|
+
const sessionDir = (0, import_node_path20.join)(root, session);
|
|
17905
18332
|
const texts = [];
|
|
17906
18333
|
for (const win of safeReaddirNames(sessionDir).filter((n) => n.startsWith("window"))) {
|
|
17907
|
-
const dir = (0,
|
|
18334
|
+
const dir = (0, import_node_path20.join)(sessionDir, win, "exthost", "anysphere.cursor-agent-exec");
|
|
17908
18335
|
for (const file of safeReaddirNames(dir)) {
|
|
17909
18336
|
if (!/^Cursor Plugins.*\.log$/i.test(file)) continue;
|
|
17910
|
-
const path2 = (0,
|
|
18337
|
+
const path2 = (0, import_node_path20.join)(dir, file);
|
|
17911
18338
|
try {
|
|
17912
|
-
texts.push({ text: (0,
|
|
18339
|
+
texts.push({ text: (0, import_node_fs20.readFileSync)(path2, "utf8"), mtime: (0, import_node_fs20.statSync)(path2).mtimeMs });
|
|
17913
18340
|
} catch {
|
|
17914
18341
|
}
|
|
17915
18342
|
}
|
|
@@ -17923,32 +18350,32 @@ function readCursorPluginLogText() {
|
|
|
17923
18350
|
function cursorPluginCachePinSnapshots() {
|
|
17924
18351
|
const root = cursorPluginCacheRoot();
|
|
17925
18352
|
try {
|
|
17926
|
-
return (0,
|
|
17927
|
-
const path2 = (0,
|
|
17928
|
-
const pluginJson = (0,
|
|
17929
|
-
const hooksJson = (0,
|
|
17930
|
-
const cliBundle = (0,
|
|
17931
|
-
const cursorHook = (0,
|
|
18353
|
+
return (0, import_node_fs20.readdirSync)(root, { withFileTypes: true }).filter((entry) => entry.isDirectory() && !entry.name.startsWith(".")).map((entry) => {
|
|
18354
|
+
const path2 = (0, import_node_path20.join)(root, entry.name);
|
|
18355
|
+
const pluginJson = (0, import_node_path20.join)(path2, ".cursor-plugin", "plugin.json");
|
|
18356
|
+
const hooksJson = (0, import_node_path20.join)(path2, "hooks", "hooks.json");
|
|
18357
|
+
const cliBundle = (0, import_node_path20.join)(path2, "cli", "dist", "index.cjs");
|
|
18358
|
+
const cursorHook = (0, import_node_path20.join)(path2, "scripts", "cursor-hook.mjs");
|
|
17932
18359
|
let version;
|
|
17933
18360
|
try {
|
|
17934
|
-
const raw = JSON.parse((0,
|
|
18361
|
+
const raw = JSON.parse((0, import_node_fs20.readFileSync)(pluginJson, "utf8"));
|
|
17935
18362
|
version = typeof raw.version === "string" ? raw.version : void 0;
|
|
17936
18363
|
} catch {
|
|
17937
18364
|
version = void 0;
|
|
17938
18365
|
}
|
|
17939
18366
|
let isEmpty = true;
|
|
17940
18367
|
try {
|
|
17941
|
-
isEmpty = (0,
|
|
18368
|
+
isEmpty = (0, import_node_fs20.readdirSync)(path2).length === 0;
|
|
17942
18369
|
} catch {
|
|
17943
18370
|
isEmpty = true;
|
|
17944
18371
|
}
|
|
17945
18372
|
return {
|
|
17946
18373
|
name: entry.name,
|
|
17947
18374
|
path: path2,
|
|
17948
|
-
hasPluginJson: (0,
|
|
17949
|
-
hasHooksJson: (0,
|
|
17950
|
-
hasCliBundle: (0,
|
|
17951
|
-
hasCursorHookScript: (0,
|
|
18375
|
+
hasPluginJson: (0, import_node_fs20.existsSync)(pluginJson),
|
|
18376
|
+
hasHooksJson: (0, import_node_fs20.existsSync)(hooksJson),
|
|
18377
|
+
hasCliBundle: (0, import_node_fs20.existsSync)(cliBundle),
|
|
18378
|
+
hasCursorHookScript: (0, import_node_fs20.existsSync)(cursorHook),
|
|
17952
18379
|
isEmpty,
|
|
17953
18380
|
version
|
|
17954
18381
|
};
|
|
@@ -17958,19 +18385,19 @@ function cursorPluginCachePinSnapshots() {
|
|
|
17958
18385
|
}
|
|
17959
18386
|
}
|
|
17960
18387
|
function hubCheckoutForCursorSeed() {
|
|
17961
|
-
const manifest = (0,
|
|
17962
|
-
return (0,
|
|
18388
|
+
const manifest = (0, import_node_path20.join)(process.cwd(), "plugins", "mmi", ".cursor-plugin", "plugin.json");
|
|
18389
|
+
return (0, import_node_fs20.existsSync)(manifest) ? process.cwd() : void 0;
|
|
17963
18390
|
}
|
|
17964
18391
|
function mmiPluginCacheRootSnapshots() {
|
|
17965
18392
|
const roots = [
|
|
17966
|
-
{ surface: "claude", root: (0,
|
|
17967
|
-
{ surface: "codex", root: (0,
|
|
18393
|
+
{ surface: "claude", root: (0, import_node_path20.join)((0, import_node_os7.homedir)(), ".claude", "plugins", "cache", "mutmutco", "mmi") },
|
|
18394
|
+
{ surface: "codex", root: (0, import_node_path20.join)((0, import_node_os7.homedir)(), ".codex", "plugins", "cache", "mutmutco", "mmi") }
|
|
17968
18395
|
];
|
|
17969
18396
|
return roots.flatMap(({ surface, root }) => {
|
|
17970
18397
|
try {
|
|
17971
|
-
const entries = (0,
|
|
18398
|
+
const entries = (0, import_node_fs20.readdirSync)(root, { withFileTypes: true }).map((entry) => ({
|
|
17972
18399
|
name: entry.name,
|
|
17973
|
-
path: (0,
|
|
18400
|
+
path: (0, import_node_path20.join)(root, entry.name),
|
|
17974
18401
|
isDirectory: entry.isDirectory()
|
|
17975
18402
|
}));
|
|
17976
18403
|
return [{ surface, root, entries }];
|
|
@@ -17981,7 +18408,7 @@ function mmiPluginCacheRootSnapshots() {
|
|
|
17981
18408
|
}
|
|
17982
18409
|
function hasNestedMmiChild(versionDir) {
|
|
17983
18410
|
try {
|
|
17984
|
-
return (0,
|
|
18411
|
+
return (0, import_node_fs20.statSync)((0, import_node_path20.join)(versionDir, "mmi")).isDirectory();
|
|
17985
18412
|
} catch {
|
|
17986
18413
|
return false;
|
|
17987
18414
|
}
|
|
@@ -17992,10 +18419,10 @@ function nestedPluginTreeSnapshot() {
|
|
|
17992
18419
|
);
|
|
17993
18420
|
}
|
|
17994
18421
|
function uniqueQuarantineTarget(path2) {
|
|
17995
|
-
if (!(0,
|
|
18422
|
+
if (!(0, import_node_fs20.existsSync)(path2)) return path2;
|
|
17996
18423
|
for (let i = 1; i < 100; i += 1) {
|
|
17997
18424
|
const candidate = `${path2}-${i}`;
|
|
17998
|
-
if (!(0,
|
|
18425
|
+
if (!(0, import_node_fs20.existsSync)(candidate)) return candidate;
|
|
17999
18426
|
}
|
|
18000
18427
|
return `${path2}-${Date.now()}`;
|
|
18001
18428
|
}
|
|
@@ -18004,10 +18431,10 @@ function quarantinePluginCacheDirs(plan) {
|
|
|
18004
18431
|
const failed = [];
|
|
18005
18432
|
for (const move of plan) {
|
|
18006
18433
|
try {
|
|
18007
|
-
if (!(0,
|
|
18434
|
+
if (!(0, import_node_fs20.existsSync)(move.from)) continue;
|
|
18008
18435
|
const target = uniqueQuarantineTarget(move.to);
|
|
18009
|
-
(0,
|
|
18010
|
-
(0,
|
|
18436
|
+
(0, import_node_fs20.mkdirSync)((0, import_node_path20.dirname)(target), { recursive: true });
|
|
18437
|
+
(0, import_node_fs20.renameSync)(move.from, target);
|
|
18011
18438
|
moved += 1;
|
|
18012
18439
|
} catch {
|
|
18013
18440
|
failed.push(move);
|
|
@@ -18026,23 +18453,23 @@ async function robocopyMirrorEmpty(emptyDir, target) {
|
|
|
18026
18453
|
}
|
|
18027
18454
|
async function clearNestedPluginTreeDir(targetPath) {
|
|
18028
18455
|
try {
|
|
18029
|
-
if (!(0,
|
|
18456
|
+
if (!(0, import_node_fs20.existsSync)(targetPath)) return true;
|
|
18030
18457
|
if (isWin) {
|
|
18031
|
-
const emptyDir = (0,
|
|
18032
|
-
(0,
|
|
18458
|
+
const emptyDir = (0, import_node_path20.join)((0, import_node_os7.tmpdir)(), `mmi-empty-${Date.now()}`);
|
|
18459
|
+
(0, import_node_fs20.mkdirSync)(emptyDir, { recursive: true });
|
|
18033
18460
|
try {
|
|
18034
18461
|
await robocopyMirrorEmpty(emptyDir, targetPath);
|
|
18035
|
-
(0,
|
|
18462
|
+
(0, import_node_fs20.rmSync)(targetPath, { recursive: true, force: true });
|
|
18036
18463
|
} finally {
|
|
18037
18464
|
try {
|
|
18038
|
-
(0,
|
|
18465
|
+
(0, import_node_fs20.rmSync)(emptyDir, { recursive: true, force: true });
|
|
18039
18466
|
} catch {
|
|
18040
18467
|
}
|
|
18041
18468
|
}
|
|
18042
|
-
return !(0,
|
|
18469
|
+
return !(0, import_node_fs20.existsSync)(targetPath);
|
|
18043
18470
|
}
|
|
18044
|
-
(0,
|
|
18045
|
-
return !(0,
|
|
18471
|
+
(0, import_node_fs20.rmSync)(targetPath, { recursive: true, force: true });
|
|
18472
|
+
return !(0, import_node_fs20.existsSync)(targetPath);
|
|
18046
18473
|
} catch {
|
|
18047
18474
|
return false;
|
|
18048
18475
|
}
|
|
@@ -18055,18 +18482,18 @@ async function applyNestedPluginTreeCleanup(paths, log) {
|
|
|
18055
18482
|
}
|
|
18056
18483
|
return true;
|
|
18057
18484
|
}
|
|
18058
|
-
var gitignorePath = () => (0,
|
|
18485
|
+
var gitignorePath = () => (0, import_node_path20.join)(process.cwd(), ".gitignore");
|
|
18059
18486
|
function readTextFile(path2) {
|
|
18060
18487
|
try {
|
|
18061
|
-
if (!(0,
|
|
18062
|
-
return (0,
|
|
18488
|
+
if (!(0, import_node_fs20.existsSync)(path2)) return null;
|
|
18489
|
+
return (0, import_node_fs20.readFileSync)(path2, "utf8");
|
|
18063
18490
|
} catch {
|
|
18064
18491
|
return null;
|
|
18065
18492
|
}
|
|
18066
18493
|
}
|
|
18067
18494
|
function mcpDirExists(path2) {
|
|
18068
18495
|
try {
|
|
18069
|
-
return (0,
|
|
18496
|
+
return (0, import_node_fs20.existsSync)(path2);
|
|
18070
18497
|
} catch {
|
|
18071
18498
|
return false;
|
|
18072
18499
|
}
|
|
@@ -18074,60 +18501,60 @@ function mcpDirExists(path2) {
|
|
|
18074
18501
|
function mcpConfigTargets() {
|
|
18075
18502
|
const cwd = process.cwd();
|
|
18076
18503
|
const home = (0, import_node_os7.homedir)();
|
|
18077
|
-
const cursorProjectDir = (0,
|
|
18078
|
-
const cursorUserDir = (0,
|
|
18079
|
-
const codexDir = (0,
|
|
18504
|
+
const cursorProjectDir = (0, import_node_path20.join)(cwd, ".cursor");
|
|
18505
|
+
const cursorUserDir = (0, import_node_path20.join)(home, ".cursor");
|
|
18506
|
+
const codexDir = (0, import_node_path20.join)(home, ".codex");
|
|
18080
18507
|
return [
|
|
18081
18508
|
// Claude Code project MCP — reconciled if present, never conjured (org seeds .cursor/mcp.json, not this).
|
|
18082
18509
|
{
|
|
18083
18510
|
host: "claude-code",
|
|
18084
18511
|
label: "Claude Code project MCP",
|
|
18085
|
-
path: (0,
|
|
18512
|
+
path: (0, import_node_path20.join)(cwd, ".mcp.json"),
|
|
18086
18513
|
format: "json",
|
|
18087
18514
|
present: true,
|
|
18088
18515
|
// parent is the repo root (cwd); the caller gates the whole reconcile on isOrgRepo
|
|
18089
18516
|
isRepoFile: true,
|
|
18090
18517
|
createWhenFileAbsent: false,
|
|
18091
|
-
content: readTextFile((0,
|
|
18518
|
+
content: readTextFile((0, import_node_path20.join)(cwd, ".mcp.json"))
|
|
18092
18519
|
},
|
|
18093
18520
|
// Cursor project MCP — the bootstrap-seeded surface; restored if its .cursor dir exists but the file is gone.
|
|
18094
18521
|
{
|
|
18095
18522
|
host: "cursor-project",
|
|
18096
18523
|
label: "Cursor project MCP",
|
|
18097
|
-
path: (0,
|
|
18524
|
+
path: (0, import_node_path20.join)(cursorProjectDir, "mcp.json"),
|
|
18098
18525
|
format: "json",
|
|
18099
18526
|
present: mcpDirExists(cursorProjectDir),
|
|
18100
18527
|
isRepoFile: true,
|
|
18101
18528
|
createWhenFileAbsent: true,
|
|
18102
|
-
content: readTextFile((0,
|
|
18529
|
+
content: readTextFile((0, import_node_path20.join)(cursorProjectDir, "mcp.json"))
|
|
18103
18530
|
},
|
|
18104
18531
|
// Cursor user MCP — global; written only when Cursor is installed (~/.cursor exists).
|
|
18105
18532
|
{
|
|
18106
18533
|
host: "cursor-user",
|
|
18107
18534
|
label: "Cursor user MCP",
|
|
18108
|
-
path: (0,
|
|
18535
|
+
path: (0, import_node_path20.join)(cursorUserDir, "mcp.json"),
|
|
18109
18536
|
format: "json",
|
|
18110
18537
|
present: mcpDirExists(cursorUserDir),
|
|
18111
18538
|
isRepoFile: false,
|
|
18112
18539
|
createWhenFileAbsent: true,
|
|
18113
|
-
content: readTextFile((0,
|
|
18540
|
+
content: readTextFile((0, import_node_path20.join)(cursorUserDir, "mcp.json"))
|
|
18114
18541
|
},
|
|
18115
18542
|
// Codex user config (TOML) — global; written only when Codex is installed (~/.codex exists).
|
|
18116
18543
|
{
|
|
18117
18544
|
host: "codex",
|
|
18118
18545
|
label: "Codex user config",
|
|
18119
|
-
path: (0,
|
|
18546
|
+
path: (0, import_node_path20.join)(codexDir, "config.toml"),
|
|
18120
18547
|
format: "toml",
|
|
18121
18548
|
present: mcpDirExists(codexDir),
|
|
18122
18549
|
isRepoFile: false,
|
|
18123
18550
|
createWhenFileAbsent: true,
|
|
18124
|
-
content: readTextFile((0,
|
|
18551
|
+
content: readTextFile((0, import_node_path20.join)(codexDir, "config.toml"))
|
|
18125
18552
|
}
|
|
18126
18553
|
];
|
|
18127
18554
|
}
|
|
18128
18555
|
function writeMcpConfigFile(path2, content) {
|
|
18129
18556
|
try {
|
|
18130
|
-
(0,
|
|
18557
|
+
(0, import_node_fs20.writeFileSync)(path2, content, "utf8");
|
|
18131
18558
|
return true;
|
|
18132
18559
|
} catch {
|
|
18133
18560
|
return false;
|
|
@@ -18207,7 +18634,7 @@ function strayBrowserArtifactPaths() {
|
|
|
18207
18634
|
const cwd = process.cwd();
|
|
18208
18635
|
return STRAY_BROWSER_ARTIFACT_DIRS.filter((rel) => {
|
|
18209
18636
|
try {
|
|
18210
|
-
return (0,
|
|
18637
|
+
return (0, import_node_fs20.existsSync)((0, import_node_path20.join)(cwd, rel));
|
|
18211
18638
|
} catch {
|
|
18212
18639
|
return false;
|
|
18213
18640
|
}
|
|
@@ -18215,37 +18642,119 @@ function strayBrowserArtifactPaths() {
|
|
|
18215
18642
|
}
|
|
18216
18643
|
function readGitignore() {
|
|
18217
18644
|
try {
|
|
18218
|
-
return (0,
|
|
18645
|
+
return (0, import_node_fs20.readFileSync)(gitignorePath(), "utf8");
|
|
18219
18646
|
} catch {
|
|
18220
18647
|
return null;
|
|
18221
18648
|
}
|
|
18222
18649
|
}
|
|
18223
18650
|
function writeGitignore(content) {
|
|
18224
18651
|
try {
|
|
18225
|
-
(0,
|
|
18652
|
+
(0, import_node_fs20.writeFileSync)(gitignorePath(), content, "utf8");
|
|
18226
18653
|
return true;
|
|
18227
18654
|
} catch {
|
|
18228
18655
|
return false;
|
|
18229
18656
|
}
|
|
18230
18657
|
}
|
|
18658
|
+
function readRepoJson(rel) {
|
|
18659
|
+
try {
|
|
18660
|
+
return JSON.parse((0, import_node_fs20.readFileSync)((0, import_node_path20.join)(process.cwd(), rel), "utf8"));
|
|
18661
|
+
} catch {
|
|
18662
|
+
return null;
|
|
18663
|
+
}
|
|
18664
|
+
}
|
|
18665
|
+
var ENCODING_SCAN_ROOT_FILES = ["AGENTS.md", "README.md", "CLAUDE.md", "package.json", "opencode.json"];
|
|
18666
|
+
var ENCODING_SCAN_DIRS = [".claude", "hooks", "scripts"];
|
|
18667
|
+
var ENCODING_SCAN_EXTS = /* @__PURE__ */ new Set([".md", ".json", ".jsonc", ".ts", ".js", ".cjs", ".mjs", ".sh", ".ps1", ".py", ".txt"]);
|
|
18668
|
+
var ENCODING_SCAN_MAX_FILES = 400;
|
|
18669
|
+
function listTextFilesUnder(dirRel, out, budget) {
|
|
18670
|
+
let entries;
|
|
18671
|
+
try {
|
|
18672
|
+
entries = (0, import_node_fs20.readdirSync)((0, import_node_path20.join)(process.cwd(), dirRel), { withFileTypes: true });
|
|
18673
|
+
} catch {
|
|
18674
|
+
return;
|
|
18675
|
+
}
|
|
18676
|
+
for (const ent of entries) {
|
|
18677
|
+
if (budget.count >= ENCODING_SCAN_MAX_FILES) return;
|
|
18678
|
+
const rel = `${dirRel}/${ent.name}`;
|
|
18679
|
+
if (ent.isDirectory()) {
|
|
18680
|
+
if (ent.name === "node_modules" || ent.name === "dist" || ent.name === ".git" || ent.name.startsWith(".")) continue;
|
|
18681
|
+
listTextFilesUnder(rel, out, budget);
|
|
18682
|
+
} else if (ent.isFile()) {
|
|
18683
|
+
const ext = ent.name.slice(ent.name.lastIndexOf("."));
|
|
18684
|
+
if (ENCODING_SCAN_EXTS.has(ext)) {
|
|
18685
|
+
out.push(rel);
|
|
18686
|
+
budget.count++;
|
|
18687
|
+
}
|
|
18688
|
+
}
|
|
18689
|
+
}
|
|
18690
|
+
}
|
|
18691
|
+
function managedFileEncodingFindings() {
|
|
18692
|
+
const cwd = process.cwd();
|
|
18693
|
+
const paths = [];
|
|
18694
|
+
for (const f of ENCODING_SCAN_ROOT_FILES) {
|
|
18695
|
+
try {
|
|
18696
|
+
if ((0, import_node_fs20.existsSync)((0, import_node_path20.join)(cwd, f))) paths.push(f);
|
|
18697
|
+
} catch {
|
|
18698
|
+
}
|
|
18699
|
+
}
|
|
18700
|
+
const budget = { count: 0 };
|
|
18701
|
+
for (const d of ENCODING_SCAN_DIRS) listTextFilesUnder(d, paths, budget);
|
|
18702
|
+
const findings = [];
|
|
18703
|
+
for (const rel of paths) {
|
|
18704
|
+
try {
|
|
18705
|
+
const probe = inspectFileEncoding((0, import_node_fs20.readFileSync)((0, import_node_path20.join)(cwd, rel)));
|
|
18706
|
+
if (probe.bom || probe.invalidUtf8 || probe.replacementChars) findings.push({ path: rel, ...probe });
|
|
18707
|
+
} catch {
|
|
18708
|
+
}
|
|
18709
|
+
}
|
|
18710
|
+
return findings;
|
|
18711
|
+
}
|
|
18712
|
+
async function branchAheadBehind(branch) {
|
|
18713
|
+
const spec = `origin/${branch}...${branch}`;
|
|
18714
|
+
try {
|
|
18715
|
+
const { stdout } = await execFileP2("git", ["rev-list", "--left-right", "--count", spec], { timeout: GIT_TIMEOUT_MS });
|
|
18716
|
+
const [behind, ahead] = stdout.trim().split(/\s+/).map((n) => Number.parseInt(n, 10));
|
|
18717
|
+
if (Number.isNaN(behind) || Number.isNaN(ahead)) return null;
|
|
18718
|
+
return { behind, ahead };
|
|
18719
|
+
} catch {
|
|
18720
|
+
return null;
|
|
18721
|
+
}
|
|
18722
|
+
}
|
|
18723
|
+
async function localReleaseBranchSnapshots() {
|
|
18724
|
+
let local;
|
|
18725
|
+
try {
|
|
18726
|
+
const { stdout } = await execFileP2("git", ["for-each-ref", "--format=%(refname:short)", "refs/heads"], { timeout: GIT_TIMEOUT_MS });
|
|
18727
|
+
local = stdout.split(/\r?\n/).map((b) => b.trim()).filter(Boolean);
|
|
18728
|
+
} catch {
|
|
18729
|
+
return [];
|
|
18730
|
+
}
|
|
18731
|
+
const releaseBranches = local.filter((b) => b === "development" || b === "main" || b === "master" || b === "rc" || b.startsWith("release/"));
|
|
18732
|
+
const rows = [];
|
|
18733
|
+
for (const branch of releaseBranches) {
|
|
18734
|
+
const ab = await branchAheadBehind(branch);
|
|
18735
|
+
if (ab) rows.push({ name: branch, ...ab });
|
|
18736
|
+
}
|
|
18737
|
+
return rows;
|
|
18738
|
+
}
|
|
18231
18739
|
async function runDoctor(opts, io = consoleIo, readOrigin) {
|
|
18232
18740
|
if (opts.guide) {
|
|
18233
18741
|
if (opts.json) io.log(JSON.stringify({ resources: [MMI_AGENTIC_ONBOARDING_GUIDE] }, null, 2));
|
|
18234
18742
|
else io.log(MMI_AGENTIC_ONBOARDING_GUIDE.url);
|
|
18235
18743
|
return;
|
|
18236
18744
|
}
|
|
18237
|
-
const repairLocal = !opts.json || Boolean(opts.apply) || Boolean(opts.preflight);
|
|
18745
|
+
const repairLocal = !opts.fast && (!opts.json || Boolean(opts.apply) || Boolean(opts.preflight));
|
|
18238
18746
|
const repoWritesAllowed = !opts.noRepoWrites;
|
|
18239
|
-
const runExtended = Boolean(opts.verbose) || Boolean(opts.json);
|
|
18747
|
+
const runExtended = !opts.fast && (Boolean(opts.verbose) || Boolean(opts.json));
|
|
18748
|
+
const slowSkippedCount = opts.fast ? DOCTOR_EXTENDED_CHECK_LABELS.size : 0;
|
|
18240
18749
|
const checks = [];
|
|
18241
18750
|
const REWRITE_KEY = "url.https://github.com/.insteadOf";
|
|
18242
18751
|
const CLONE_FIX = 'run: git config --global url."https://github.com/".insteadOf "git@github.com:"';
|
|
18243
18752
|
const [login, pathProbe, releasedVersionResolution, cfg, callerArn, cloneProbe, isOrgRepo] = await Promise.all([
|
|
18244
|
-
githubLogin(),
|
|
18753
|
+
opts.fast ? Promise.resolve(void 0) : githubLogin(),
|
|
18245
18754
|
execFileP2(isWin ? "where" : "which", ["mmi-cli"]).then(() => true).catch(() => false),
|
|
18246
|
-
fetchReleasedVersion(),
|
|
18755
|
+
opts.fast ? Promise.resolve({ version: void 0, source: "unavailable" }) : fetchReleasedVersion(),
|
|
18247
18756
|
loadConfig(),
|
|
18248
|
-
awsCallerArn(),
|
|
18757
|
+
opts.fast ? Promise.resolve(void 0) : awsCallerArn(),
|
|
18249
18758
|
execFileP2("git", ["config", "--global", "--get-all", REWRITE_KEY]).then(({ stdout }) => stdout.split("\n").some((l) => l.trim() === "git@github.com:")).catch(() => false),
|
|
18250
18759
|
// unset → repair below
|
|
18251
18760
|
// hub-v3 step-back: org-membership gates the repo-local checks/repairs by the git `origin` remote,
|
|
@@ -18264,7 +18773,7 @@ async function runDoctor(opts, io = consoleIo, readOrigin) {
|
|
|
18264
18773
|
const semverPrefix = /^\d+\.\d+\.\d+/;
|
|
18265
18774
|
const isBehind = (installed2, released) => Boolean(installed2 && released && semverPrefix.test(installed2) && semverPrefix.test(released) && compareVersions(installed2, released) < 0);
|
|
18266
18775
|
const opencodeAdapterStale = isBehind(opencodeInstalledVersionForDoctor(), releasedVersion);
|
|
18267
|
-
const cursorCacheStale = (0,
|
|
18776
|
+
const cursorCacheStale = (0, import_node_fs20.existsSync)(cursorPluginCacheRoot()) && (cursorPluginCachePinSnapshots() ?? []).some((p) => isBehind(p.version, releasedVersion));
|
|
18268
18777
|
const healPlan = doctorHealPlan({
|
|
18269
18778
|
isOrgRepo,
|
|
18270
18779
|
surface,
|
|
@@ -18286,20 +18795,22 @@ async function runDoctor(opts, io = consoleIo, readOrigin) {
|
|
|
18286
18795
|
if (opts.preflight && !healPlan.needsEagerHeal) return;
|
|
18287
18796
|
const eagerHeal = Boolean(opts.preflight) || Boolean(opts.banner) && healPlan.needsEagerHeal;
|
|
18288
18797
|
if (eagerHeal && healPlan.needsEagerHeal) io.err(DOCTOR_PREFLIGHT_NOTICE);
|
|
18289
|
-
const repairFull = !opts.json && !opts.banner || Boolean(opts.apply) || Boolean(opts.preflight) || Boolean(opts.banner) && healPlan.needsEagerHeal;
|
|
18290
|
-
|
|
18291
|
-
|
|
18292
|
-
|
|
18293
|
-
|
|
18294
|
-
|
|
18295
|
-
|
|
18798
|
+
const repairFull = !opts.fast && (!opts.json && !opts.banner || Boolean(opts.apply) || Boolean(opts.preflight) || Boolean(opts.banner) && healPlan.needsEagerHeal);
|
|
18799
|
+
if (!opts.fast) {
|
|
18800
|
+
let ghInstalled = true;
|
|
18801
|
+
if (!login) {
|
|
18802
|
+
try {
|
|
18803
|
+
await execFileP2("gh", ["--version"]);
|
|
18804
|
+
} catch {
|
|
18805
|
+
ghInstalled = false;
|
|
18806
|
+
}
|
|
18296
18807
|
}
|
|
18808
|
+
checks.push(buildGithubAuthCheck({ login, ghInstalled }));
|
|
18297
18809
|
}
|
|
18298
|
-
checks.push(buildGithubAuthCheck({ login, ghInstalled }));
|
|
18299
18810
|
let onPath = pathProbe;
|
|
18300
18811
|
if (!onPath) {
|
|
18301
18812
|
const root = process.env.CLAUDE_PLUGIN_ROOT;
|
|
18302
|
-
if (root && (0,
|
|
18813
|
+
if (root && (0, import_node_fs20.existsSync)(`${root}/bin/mmi-cli${isWin ? ".cmd" : ""}`)) onPath = true;
|
|
18303
18814
|
}
|
|
18304
18815
|
checks.push({ ok: onPath, label: "mmi-cli on PATH", fix: "auto-provisioned at session start \u2014 reopen the session, or install the MMI plugin" });
|
|
18305
18816
|
const reloadHint = reloadAction(surface);
|
|
@@ -18342,7 +18853,7 @@ async function runDoctor(opts, io = consoleIo, readOrigin) {
|
|
|
18342
18853
|
return;
|
|
18343
18854
|
}
|
|
18344
18855
|
checks.push({ ok: Boolean(cfg.sagaApiUrl), label: "Hub API URL configured", fix: "set MMI_HUB_URL or use a current MMI CLI/plugin build" });
|
|
18345
|
-
const hubVersionInfo = await fetchHubVersionInfo(cfg.sagaApiUrl);
|
|
18856
|
+
const hubVersionInfo = opts.fast ? null : await fetchHubVersionInfo(cfg.sagaApiUrl);
|
|
18346
18857
|
const installedVersion = resolveClientVersion();
|
|
18347
18858
|
checks.push(
|
|
18348
18859
|
buildHubCompatCheck({
|
|
@@ -18360,6 +18871,12 @@ async function runDoctor(opts, io = consoleIo, readOrigin) {
|
|
|
18360
18871
|
releasedVersion
|
|
18361
18872
|
})
|
|
18362
18873
|
);
|
|
18874
|
+
checks.push(
|
|
18875
|
+
buildCliVersionDriftCheck({
|
|
18876
|
+
currentVersion: installedVersion,
|
|
18877
|
+
releasedVersion
|
|
18878
|
+
})
|
|
18879
|
+
);
|
|
18363
18880
|
}
|
|
18364
18881
|
if (runExtended) {
|
|
18365
18882
|
checks.push(buildAwsCrossAccountCheck({ callerArn }));
|
|
@@ -18446,8 +18963,19 @@ async function runDoctor(opts, io = consoleIo, readOrigin) {
|
|
|
18446
18963
|
checks.push(gitignoreCheck);
|
|
18447
18964
|
checks.push(buildRepoLocalWorktreeCheck({
|
|
18448
18965
|
isOrgRepo,
|
|
18449
|
-
hasRepoLocalWorktrees: (0,
|
|
18966
|
+
hasRepoLocalWorktrees: (0, import_node_fs20.existsSync)((0, import_node_path20.join)(process.cwd(), ".worktrees"))
|
|
18450
18967
|
}));
|
|
18968
|
+
if (isOrgRepo) {
|
|
18969
|
+
const pkg = readRepoJson("package.json");
|
|
18970
|
+
const lock = readRepoJson("package-lock.json");
|
|
18971
|
+
checks.push(buildLockfileOptionalsCheck({
|
|
18972
|
+
isOrgRepo,
|
|
18973
|
+
packageJsonOptionalDeps: pkg?.optionalDependencies ?? null,
|
|
18974
|
+
lockfilePackages: lock?.packages ?? null
|
|
18975
|
+
}));
|
|
18976
|
+
} else {
|
|
18977
|
+
checks.push(buildLockfileOptionalsCheck({ isOrgRepo, packageJsonOptionalDeps: null, lockfilePackages: null }));
|
|
18978
|
+
}
|
|
18451
18979
|
let driftCheck = buildPluginConfigDriftCheck({ isOrgRepo, installed, surface });
|
|
18452
18980
|
if (!driftCheck.ok && driftCheck.recordsToWrite && repairLocal) {
|
|
18453
18981
|
if (backupAndWriteInstalledPlugins(driftCheck.recordsToWrite, driftCheck.pluginId)) {
|
|
@@ -18632,7 +19160,7 @@ async function runDoctor(opts, io = consoleIo, readOrigin) {
|
|
|
18632
19160
|
return versions.sort((a, b) => compareVersions(b, a))[0];
|
|
18633
19161
|
};
|
|
18634
19162
|
const codexCacheVersions = () => mmiPluginCacheRootSnapshots().filter((r) => r.surface === "codex").flatMap((r) => r.entries.filter((e) => e.isDirectory).map((e) => e.name));
|
|
18635
|
-
const codexActiveVersion = () => isOrgRepo && releasedVersion ? codexEnabledMmiVersion() : Promise.resolve(void 0);
|
|
19163
|
+
const codexActiveVersion = () => !opts.fast && isOrgRepo && releasedVersion ? codexEnabledMmiVersion() : Promise.resolve(void 0);
|
|
18636
19164
|
let cacheCleanupCheck = buildMmiPluginCacheCleanupCheck({
|
|
18637
19165
|
isOrgRepo,
|
|
18638
19166
|
roots: mmiPluginCacheRootSnapshots(),
|
|
@@ -18735,7 +19263,7 @@ async function runDoctor(opts, io = consoleIo, readOrigin) {
|
|
|
18735
19263
|
}
|
|
18736
19264
|
checks.push(nestedPluginTreeCheck);
|
|
18737
19265
|
const cursorCacheRoot = cursorPluginCacheRoot();
|
|
18738
|
-
const cursorCacheRootExists = (0,
|
|
19266
|
+
const cursorCacheRootExists = (0, import_node_fs20.existsSync)(cursorCacheRoot);
|
|
18739
19267
|
let cursorPins = cursorPluginCachePinSnapshots() ?? [];
|
|
18740
19268
|
checks.push(
|
|
18741
19269
|
buildCursorPluginCacheCleanupCheck({
|
|
@@ -18763,7 +19291,7 @@ async function runDoctor(opts, io = consoleIo, readOrigin) {
|
|
|
18763
19291
|
cacheRootExists: cursorCacheRootExists,
|
|
18764
19292
|
hubCheckout: hubCheckoutForCursorSeed(),
|
|
18765
19293
|
execFileP: execFileP2,
|
|
18766
|
-
mkdtemp: (prefix) => (0, import_promises2.mkdtemp)((0,
|
|
19294
|
+
mkdtemp: (prefix) => (0, import_promises2.mkdtemp)((0, import_node_path20.join)((0, import_node_os7.tmpdir)(), prefix)),
|
|
18767
19295
|
log: (m) => io.err(m)
|
|
18768
19296
|
});
|
|
18769
19297
|
if (seeded) {
|
|
@@ -18840,6 +19368,23 @@ async function runDoctor(opts, io = consoleIo, readOrigin) {
|
|
|
18840
19368
|
cacheVersions: sessionCacheVersions
|
|
18841
19369
|
})
|
|
18842
19370
|
);
|
|
19371
|
+
{
|
|
19372
|
+
let redactorFailureLines = [];
|
|
19373
|
+
try {
|
|
19374
|
+
redactorFailureLines = (0, import_node_fs20.readFileSync)(activityLogPath(process.cwd()), "utf8").split("\n");
|
|
19375
|
+
} catch {
|
|
19376
|
+
redactorFailureLines = [];
|
|
19377
|
+
}
|
|
19378
|
+
const redactorSummary = summarizeRedactorFailures(redactorFailureLines);
|
|
19379
|
+
checks.push(
|
|
19380
|
+
buildRedactorLivenessCheck({
|
|
19381
|
+
isOrgRepo,
|
|
19382
|
+
failedCount: redactorSummary.failedCount,
|
|
19383
|
+
lastFailedTs: redactorSummary.lastFailedTs,
|
|
19384
|
+
lastFailedAction: redactorSummary.lastFailedAction
|
|
19385
|
+
})
|
|
19386
|
+
);
|
|
19387
|
+
}
|
|
18843
19388
|
if (runExtended) {
|
|
18844
19389
|
checks.push(
|
|
18845
19390
|
buildBrowserArtifactsCheck({
|
|
@@ -18864,6 +19409,14 @@ async function runDoctor(opts, io = consoleIo, readOrigin) {
|
|
|
18864
19409
|
checks.push(buildGitGcCheck(await gcPlan("origin", 200)));
|
|
18865
19410
|
} catch {
|
|
18866
19411
|
}
|
|
19412
|
+
try {
|
|
19413
|
+
checks.push(buildManagedFileEncodingCheck({ isOrgRepo, findings: managedFileEncodingFindings() }));
|
|
19414
|
+
} catch {
|
|
19415
|
+
}
|
|
19416
|
+
try {
|
|
19417
|
+
checks.push(buildStaleLocalBranchCheck({ isOrgRepo, branches: await localReleaseBranchSnapshots() }));
|
|
19418
|
+
} catch {
|
|
19419
|
+
}
|
|
18867
19420
|
} else if (opts.apply && repoWritesAllowed) {
|
|
18868
19421
|
const scratchRun = executeScratchGc(repoRoot, { apply: true });
|
|
18869
19422
|
if (scratchRun.applied?.pruned.length) {
|
|
@@ -18884,6 +19437,7 @@ async function runDoctor(opts, io = consoleIo, readOrigin) {
|
|
|
18884
19437
|
healedCount,
|
|
18885
19438
|
pluginReloadRequired,
|
|
18886
19439
|
reloadHint,
|
|
19440
|
+
slowSkippedCount,
|
|
18887
19441
|
// Surface-aware update report (#865): the per-surface version snapshot + copy-paste update recipes, so
|
|
18888
19442
|
// an agent told "make sure the CLI and plugin are up to date" can run the right command per surface and
|
|
18889
19443
|
// echo back an unambiguous version line (CLI / Claude plugin / Codex marketplace / Codex active cache).
|
|
@@ -18909,6 +19463,7 @@ function emitDoctorReport(opts, io, ctx) {
|
|
|
18909
19463
|
const { checks, surface, healedCount, pluginReloadRequired, reloadHint } = ctx;
|
|
18910
19464
|
const gaps = checks.filter((c) => !c.ok);
|
|
18911
19465
|
if (opts.preflight) {
|
|
19466
|
+
process.exitCode = doctorExitCode(checks);
|
|
18912
19467
|
const outcome = preflightOutcome({ gaps, needsEagerHeal: ctx.needsEagerHeal, surface });
|
|
18913
19468
|
if (outcome.line) io.err(outcome.line);
|
|
18914
19469
|
if (pluginReloadRequired) io.err(pluginAutonomousHaltLine(reloadHint));
|
|
@@ -18935,7 +19490,8 @@ function emitDoctorReport(opts, io, ctx) {
|
|
|
18935
19490
|
healedCount,
|
|
18936
19491
|
pluginReloadRequired,
|
|
18937
19492
|
reloadHint,
|
|
18938
|
-
verbose: Boolean(opts.verbose)
|
|
19493
|
+
verbose: Boolean(opts.verbose),
|
|
19494
|
+
skippedSlowChecks: ctx.slowSkippedCount
|
|
18939
19495
|
})) io.log(line);
|
|
18940
19496
|
for (const r of resources) io.log(`Resource: ${r.label} \u2014 ${r.url}`);
|
|
18941
19497
|
}
|
|
@@ -18958,23 +19514,23 @@ function mergeGuardHook(settings) {
|
|
|
18958
19514
|
next.hooks = hooks;
|
|
18959
19515
|
return next;
|
|
18960
19516
|
}
|
|
18961
|
-
var userScopeSettingsPath = (surface = detectSurface(process.env)) => (0,
|
|
19517
|
+
var userScopeSettingsPath = (surface = detectSurface(process.env)) => (0, import_node_path20.join)((0, import_node_os7.homedir)(), surface === "codex" ? ".codex" : ".claude", "settings.json");
|
|
18962
19518
|
function ensureUserScopeGuardHook(opts = {}) {
|
|
18963
19519
|
const path2 = opts.settingsPath ?? userScopeSettingsPath();
|
|
18964
19520
|
try {
|
|
18965
19521
|
let current = null;
|
|
18966
|
-
if ((0,
|
|
19522
|
+
if ((0, import_node_fs20.existsSync)(path2)) {
|
|
18967
19523
|
try {
|
|
18968
|
-
current = JSON.parse((0,
|
|
19524
|
+
current = JSON.parse((0, import_node_fs20.readFileSync)(path2, "utf8"));
|
|
18969
19525
|
} catch {
|
|
18970
19526
|
return "failed";
|
|
18971
19527
|
}
|
|
18972
19528
|
}
|
|
18973
19529
|
if (settingsHasGuardHook(current)) return "already";
|
|
18974
19530
|
const merged = mergeGuardHook(current);
|
|
18975
|
-
(0,
|
|
18976
|
-
if ((0,
|
|
18977
|
-
(0,
|
|
19531
|
+
(0, import_node_fs20.mkdirSync)((0, import_node_path20.dirname)(path2), { recursive: true });
|
|
19532
|
+
if ((0, import_node_fs20.existsSync)(path2)) (0, import_node_fs20.copyFileSync)(path2, `${path2}.bak`);
|
|
19533
|
+
(0, import_node_fs20.writeFileSync)(path2, `${JSON.stringify(merged, null, 2)}
|
|
18978
19534
|
`, "utf8");
|
|
18979
19535
|
return "written";
|
|
18980
19536
|
} catch {
|
|
@@ -19111,7 +19667,7 @@ async function applyGcPlan(plan, remote) {
|
|
|
19111
19667
|
cleanupBranch: (branch, expectedHeadOid) => cleanupPrMergeLocalBranch(branch.branch, {
|
|
19112
19668
|
beforeWorktrees,
|
|
19113
19669
|
startingPath: branch.worktreePath,
|
|
19114
|
-
pathExists: (p) => (0,
|
|
19670
|
+
pathExists: (p) => (0, import_node_fs21.existsSync)(p),
|
|
19115
19671
|
execGit: async (args) => (await execFileP2("git", args, { timeout: GIT_TIMEOUT_MS })).stdout,
|
|
19116
19672
|
teardownWorktreeStage,
|
|
19117
19673
|
deferredStore,
|
|
@@ -19134,7 +19690,7 @@ async function applyGcPlan(plan, remote) {
|
|
|
19134
19690
|
for (const wt of plan.worktreeDirs) {
|
|
19135
19691
|
try {
|
|
19136
19692
|
const cleanupTarget = resolveSafeSiblingWorktreeCleanupTarget(wt.path, siblingRoot, {
|
|
19137
|
-
realpath: (path2) => (0,
|
|
19693
|
+
realpath: (path2) => (0, import_node_fs21.realpathSync)(path2)
|
|
19138
19694
|
});
|
|
19139
19695
|
if (!cleanupTarget.ok) {
|
|
19140
19696
|
result.failed.push(`${wt.path}: ${cleanupTarget.reason}`);
|
|
@@ -19174,16 +19730,21 @@ async function requireFreshTrainCli(commandName) {
|
|
|
19174
19730
|
throw new Error(staleTrainCliMessage(report, commandName));
|
|
19175
19731
|
}
|
|
19176
19732
|
var program2 = new Command();
|
|
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)");
|
|
19733
|
+
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). If you expected this command to exist, your installed mmi-cli may be stale \u2014 update it (see: mmi-cli doctor)");
|
|
19178
19734
|
var rules = program2.command("rules").description("org-managed .gitignore delivery");
|
|
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) => {
|
|
19180
|
-
const path2 = (0,
|
|
19181
|
-
const current = (0,
|
|
19735
|
+
rules.command("gitignore").option("--write", "upsert the managed block into .gitignore (default: check only, non-zero exit on drift)").option("--json", "machine-readable output").description("verify (or --write) this repo's org-managed .gitignore block matches the SSOT").action((opts) => {
|
|
19736
|
+
const path2 = (0, import_node_path21.join)(process.cwd(), ".gitignore");
|
|
19737
|
+
const current = (0, import_node_fs21.existsSync)(path2) ? (0, import_node_fs21.readFileSync)(path2, "utf8") : null;
|
|
19182
19738
|
const plan = planManagedGitignore(current);
|
|
19183
19739
|
const drift = [...plan.added.map((l) => `+${l}`), ...plan.removed.map((l) => `-${l}`)].join(", ") || "block normalize";
|
|
19740
|
+
if (opts.json) {
|
|
19741
|
+
console.log(JSON.stringify(plan, null, 2));
|
|
19742
|
+
if (!opts.write && plan.changed) process.exitCode = 1;
|
|
19743
|
+
return;
|
|
19744
|
+
}
|
|
19184
19745
|
if (opts.write) {
|
|
19185
19746
|
if (plan.changed) {
|
|
19186
|
-
(0,
|
|
19747
|
+
(0, import_node_fs21.writeFileSync)(path2, plan.content, "utf8");
|
|
19187
19748
|
console.log(`mmi-cli rules gitignore: updated .gitignore (${drift})`);
|
|
19188
19749
|
} else {
|
|
19189
19750
|
console.log("mmi-cli rules gitignore: up to date");
|
|
@@ -19348,7 +19909,7 @@ function runWorktreeInstall(command, cwd, quiet) {
|
|
|
19348
19909
|
async function primaryCheckoutRoot(worktreeRoot) {
|
|
19349
19910
|
try {
|
|
19350
19911
|
const out = (await execFileP2("git", ["-C", worktreeRoot, "rev-parse", "--path-format=absolute", "--git-common-dir"], { timeout: GIT_TIMEOUT_MS })).stdout.trim();
|
|
19351
|
-
return out ? (0,
|
|
19912
|
+
return out ? (0, import_node_path21.dirname)(out) : void 0;
|
|
19352
19913
|
} catch {
|
|
19353
19914
|
return void 0;
|
|
19354
19915
|
}
|
|
@@ -19363,26 +19924,26 @@ function makeProvisionDeps(worktreeRoot, quiet, log) {
|
|
|
19363
19924
|
function acquireWorktreeSetupLock(worktreeRoot) {
|
|
19364
19925
|
const lockPath = repoRuntimeStatePath(worktreeRoot, "worktree-setup.lock");
|
|
19365
19926
|
const take = () => {
|
|
19366
|
-
const fd = (0,
|
|
19927
|
+
const fd = (0, import_node_fs21.openSync)(lockPath, "wx");
|
|
19367
19928
|
try {
|
|
19368
|
-
(0,
|
|
19929
|
+
(0, import_node_fs21.writeSync)(fd, String(Date.now()));
|
|
19369
19930
|
} finally {
|
|
19370
|
-
(0,
|
|
19931
|
+
(0, import_node_fs21.closeSync)(fd);
|
|
19371
19932
|
}
|
|
19372
19933
|
return () => {
|
|
19373
19934
|
try {
|
|
19374
|
-
(0,
|
|
19935
|
+
(0, import_node_fs21.rmSync)(lockPath, { force: true });
|
|
19375
19936
|
} catch {
|
|
19376
19937
|
}
|
|
19377
19938
|
};
|
|
19378
19939
|
};
|
|
19379
19940
|
try {
|
|
19380
|
-
(0,
|
|
19941
|
+
(0, import_node_fs21.mkdirSync)((0, import_node_path21.dirname)(lockPath), { recursive: true });
|
|
19381
19942
|
return take();
|
|
19382
19943
|
} catch {
|
|
19383
19944
|
try {
|
|
19384
|
-
if (Date.now() - (0,
|
|
19385
|
-
(0,
|
|
19945
|
+
if (Date.now() - (0, import_node_fs21.statSync)(lockPath).mtimeMs > WORKTREE_SETUP_LOCK_TTL_MS) {
|
|
19946
|
+
(0, import_node_fs21.rmSync)(lockPath, { force: true });
|
|
19386
19947
|
return take();
|
|
19387
19948
|
}
|
|
19388
19949
|
} catch {
|
|
@@ -19400,7 +19961,21 @@ worktree.command("create <branch>").description("create a worktree from a base r
|
|
|
19400
19961
|
const fetchErr = await execFileP2("git", ["fetch", o.remote, fetchBranch], { timeout: GH_MUTATION_TIMEOUT_MS }).then(() => void 0).catch((e) => (e instanceof Error ? e.message : String(e)).split("\n")[0]);
|
|
19401
19962
|
if (fetchErr) console.error(` warning: could not fetch ${o.remote}/${fetchBranch} (${fetchErr}); base ${base} may be stale`);
|
|
19402
19963
|
}
|
|
19403
|
-
await
|
|
19964
|
+
await addWorktreeRobust(wtPath, branch, base, {
|
|
19965
|
+
git: async (args) => (await execFileP2("git", args, { timeout: GH_MUTATION_TIMEOUT_MS })).stdout,
|
|
19966
|
+
revParse: async (ref) => {
|
|
19967
|
+
try {
|
|
19968
|
+
return (await execFileP2("git", ["rev-parse", "--verify", ref], { timeout: GIT_TIMEOUT_MS })).stdout.trim() || void 0;
|
|
19969
|
+
} catch {
|
|
19970
|
+
return void 0;
|
|
19971
|
+
}
|
|
19972
|
+
},
|
|
19973
|
+
deleteBranch: (b) => execFileP2("git", ["branch", "-D", b], { timeout: GIT_TIMEOUT_MS }).then(() => void 0),
|
|
19974
|
+
sleep: (ms) => new Promise((resolve5) => setTimeout(resolve5, ms)),
|
|
19975
|
+
log: (m) => {
|
|
19976
|
+
if (!o.json) console.error(` ${m}`);
|
|
19977
|
+
}
|
|
19978
|
+
});
|
|
19404
19979
|
const report = await provisionWorktree(wtPath, makeProvisionDeps(wtPath, Boolean(o.json), (m) => {
|
|
19405
19980
|
if (!o.json) console.error(` ${m}`);
|
|
19406
19981
|
}));
|
|
@@ -19519,7 +20094,7 @@ async function attachToProject(issueNumber, repo, priority) {
|
|
|
19519
20094
|
var ghRunner = async (args, timeoutMs) => (await execFileP2("gh", args, { timeout: timeoutMs })).stdout;
|
|
19520
20095
|
function scheduleRelatedDiscovery(o) {
|
|
19521
20096
|
try {
|
|
19522
|
-
const args = ["issue", "discover-related", "--number", String(o.number), "--title", o.title, "--body", o.body];
|
|
20097
|
+
const args = ["issue", "discover-related", "--number", String(o.number), "--title", o.title, "--body", o.body, "--fail-soft"];
|
|
19523
20098
|
if (o.repo) args.push("--repo", o.repo);
|
|
19524
20099
|
spawnDetachedSelf(args, { spawn: import_node_child_process12.spawn, execPath: process.execPath, scriptPath: process.argv[1] }, { cwd: process.cwd() });
|
|
19525
20100
|
} catch {
|
|
@@ -19872,7 +20447,7 @@ project.command("set [owner/repo]").description("upsert project META (idempotent
|
|
|
19872
20447
|
const vars = rawValues("--var");
|
|
19873
20448
|
if (o.secretsFile) {
|
|
19874
20449
|
try {
|
|
19875
|
-
vars.push(`secrets=${(0,
|
|
20450
|
+
vars.push(`secrets=${(0, import_node_fs21.readFileSync)(o.secretsFile, "utf8")}`);
|
|
19876
20451
|
} catch (e) {
|
|
19877
20452
|
return fail(`project set: cannot read --secrets-file ${o.secretsFile}: ${e.message}`);
|
|
19878
20453
|
}
|
|
@@ -20176,7 +20751,7 @@ issue.command("view <number>").description("read an issue as structured JSON \u2
|
|
|
20176
20751
|
return fail(`issue view: ${raw}${argvNote}`);
|
|
20177
20752
|
}
|
|
20178
20753
|
});
|
|
20179
|
-
issue.command("discover-related").description("find related issues for an existing issue and post only high-confidence links").requiredOption("--number <number>", "created issue number").requiredOption("--title <title>", "created issue title").requiredOption("--body <body>", "created issue body").option("--repo <owner/repo>", "target repo (defaults to the current repo)").option("--json", "print candidates instead of posting").action(async (o) => {
|
|
20754
|
+
issue.command("discover-related").description("find related issues for an existing issue and post only high-confidence links").requiredOption("--number <number>", "created issue number").requiredOption("--title <title>", "created issue title").requiredOption("--body <body>", "created issue body").option("--repo <owner/repo>", "target repo (defaults to the current repo)").option("--json", "print candidates instead of posting").option("--fail-soft", "suppress errors (used by the background auto-caller; direct runs fail loudly)").action(async (o) => {
|
|
20180
20755
|
const number = Number(o.number);
|
|
20181
20756
|
if (!Number.isInteger(number) || number <= 0) return fail("issue discover-related: --number must be a positive integer");
|
|
20182
20757
|
const repo = await resolveRepo(o.repo);
|
|
@@ -20208,7 +20783,10 @@ issue.command("discover-related").description("find related issues for an existi
|
|
|
20208
20783
|
]);
|
|
20209
20784
|
if (viewed.comments.some((comment) => comment.body.includes(relatedMarker(number)))) return;
|
|
20210
20785
|
await execFileP2("gh", ["issue", "comment", String(number), "--repo", repo, "--body", buildRelatedComment(number, candidates)], { timeout: GH_MUTATION_TIMEOUT_MS });
|
|
20211
|
-
} catch {
|
|
20786
|
+
} catch (e) {
|
|
20787
|
+
if (o.failSoft) return;
|
|
20788
|
+
const err = e;
|
|
20789
|
+
return fail(`issue discover-related: ${(err.stderr || err.message || String(e)).trim()}`);
|
|
20212
20790
|
}
|
|
20213
20791
|
});
|
|
20214
20792
|
issue.command("link-child <parent> <child>").description("link an existing issue as a native sub-issue of a parent and print {parentNumber,subIssueNumber,totalCount} JSON").option("--repo <owner/repo>", "repo for bare refs on either side (defaults to the current repo)").action(async (parentRef, childRef, o) => {
|
|
@@ -20415,7 +20993,7 @@ program2.command("skill-lesson").description("file a skill-lesson on the Hub boa
|
|
|
20415
20993
|
console.log(JSON.stringify({ ...created, deduped: false, label: SKILL_LESSON_LABEL, skill, priority, projectItemId, onBoard }));
|
|
20416
20994
|
});
|
|
20417
20995
|
var pr = program2.command("pr").description("pull requests \u2014 reliable create with structured output");
|
|
20418
|
-
pr.command("create").description("create a PR and print {number,url} JSON").option("--title <title>", "PR title").option("--title-file <path|->", "read the PR title from a UTF-8 file, or from stdin with -").option("--body <body>", "PR body (markdown)").option("--body-file <path|->", "read PR body from a UTF-8 file, or from stdin with -").option("--base <branch>", "base branch (defaults to the repo default)").option("--head <branch>", "head branch (defaults to the current branch)").option("--repo <owner/repo>", "target repo (defaults to the current repo)").action(async (o) => {
|
|
20996
|
+
pr.command("create").description("create a PR and print {number,url} JSON").option("--title <title>", "PR title").option("--title-file <path|->", "read the PR title from a UTF-8 file, or from stdin with -").option("--body <body>", "PR body (markdown)").option("--body-file <path|->", "read PR body from a UTF-8 file, or from stdin with -").option("--base <branch>", "base branch (defaults to the repo default)").option("--head <branch>", "head branch (defaults to the current branch)").option("--repo <owner/repo>", "target repo (defaults to the current repo)").option("--draft", "open the PR in draft state (#2667)").action(async (o) => {
|
|
20419
20997
|
let body;
|
|
20420
20998
|
let title;
|
|
20421
20999
|
try {
|
|
@@ -20424,17 +21002,19 @@ pr.command("create").description("create a PR and print {number,url} JSON").opti
|
|
|
20424
21002
|
} catch (e) {
|
|
20425
21003
|
return fail(`pr create: ${e.message}`);
|
|
20426
21004
|
}
|
|
20427
|
-
const created = await ghCreate(buildPrArgs({ title, body, base: o.base, head: o.head, repo: o.repo }));
|
|
21005
|
+
const created = await ghCreate(buildPrArgs({ title, body, base: o.base, head: o.head, repo: o.repo, draft: o.draft }));
|
|
20428
21006
|
console.log(JSON.stringify(created));
|
|
20429
21007
|
});
|
|
20430
|
-
pr.command("view <number>").description("read a PR as structured JSON (merged state, head/base, URL, merge commit) \u2014 the mmi-cli read path (#2347)").option("--repo <owner/repo>", "target repo (defaults to the current repo)").option("--json
|
|
21008
|
+
pr.command("view <number>").description("read a PR as structured JSON (merged state, head/base, URL, merge commit) \u2014 the mmi-cli read path (#2347)").option("--repo <owner/repo>", "target repo (defaults to the current repo)").option("--json [fields...]", 'gh --json field list (overrides the default field set). Accepts commas, spaces, or repeated --json flags \u2014 in PowerShell an unquoted comma list is an array literal, so QUOTE it: --json "state,baseRefName,mergeCommit"').action(async (number, o) => {
|
|
20431
21009
|
const n = Number(number);
|
|
20432
21010
|
if (!Number.isInteger(n) || n <= 0) return fail("pr view: <number> must be a positive integer");
|
|
20433
21011
|
const repo = await resolveRepo(o.repo);
|
|
20434
21012
|
if (!repo) return fail("pr view: could not resolve repo (pass --repo <owner/repo>)");
|
|
20435
|
-
const fields =
|
|
21013
|
+
const fields = normalizeIssueViewJsonFields(o.json);
|
|
21014
|
+
const defaultPrFields = "number,title,state,url,isDraft,mergeable,mergedAt,mergeCommit,headRefName,baseRefName,author,labels";
|
|
21015
|
+
const effective = fields === normalizeIssueViewJsonFields(void 0) ? defaultPrFields : fields;
|
|
20436
21016
|
try {
|
|
20437
|
-
const data = await ghJson(["pr", "view", String(n), "--repo", repo, "--json",
|
|
21017
|
+
const data = await ghJson(["pr", "view", String(n), "--repo", repo, "--json", effective]);
|
|
20438
21018
|
console.log(JSON.stringify(data));
|
|
20439
21019
|
} catch (e) {
|
|
20440
21020
|
const err = e;
|
|
@@ -20442,9 +21022,9 @@ pr.command("view <number>").description("read a PR as structured JSON (merged st
|
|
|
20442
21022
|
}
|
|
20443
21023
|
});
|
|
20444
21024
|
async function listCiWorkflowPaths(cwd = process.cwd()) {
|
|
20445
|
-
const wfDir = (0,
|
|
20446
|
-
if (!(0,
|
|
20447
|
-
return (0,
|
|
21025
|
+
const wfDir = (0, import_node_path21.join)(cwd, ".github", "workflows");
|
|
21026
|
+
if (!(0, import_node_fs21.existsSync)(wfDir)) return [];
|
|
21027
|
+
return (0, import_node_fs21.readdirSync)(wfDir).filter((name) => /\.(ya?ml)$/i.test(name)).map((name) => `.github/workflows/${name}`);
|
|
20448
21028
|
}
|
|
20449
21029
|
async function resolveMergeCiPolicyForCheckout(repoOpt) {
|
|
20450
21030
|
const repo = repoOpt ?? await resolveRepo();
|
|
@@ -20456,16 +21036,29 @@ async function resolveMergeCiPolicyForCheckout(repoOpt) {
|
|
|
20456
21036
|
}
|
|
20457
21037
|
function ciAuditDeps() {
|
|
20458
21038
|
const cfgPromise = loadConfig();
|
|
21039
|
+
const root = hubRoot();
|
|
20459
21040
|
return {
|
|
20460
21041
|
client: defaultGitHubClient(),
|
|
20461
21042
|
listProjects: async () => fetchProjectsList(registryClientDeps(await cfgPromise)),
|
|
20462
21043
|
getProjectMeta: async (slug) => fetchProjectBySlug(slug, registryClientDeps(await cfgPromise)),
|
|
20463
|
-
// Continuous CI delivery (#1550): the gate re-seed renders from the Hub's on-disk seed templates.
|
|
20464
|
-
//
|
|
20465
|
-
//
|
|
20466
|
-
|
|
21044
|
+
// Continuous CI delivery (#1550): the gate re-seed renders from the Hub's on-disk seed templates.
|
|
21045
|
+
// Resolve from the installed package root so the reconcile works from any org repo checkout; when the
|
|
21046
|
+
// Hub root is not locatable (e.g. a global npm install without a local checkout), return null so the
|
|
21047
|
+
// gate re-seed step is skipped gracefully rather than failing mid-run.
|
|
21048
|
+
readSeedFile: (path2) => {
|
|
21049
|
+
if (!root) return null;
|
|
21050
|
+
const fullPath = (0, import_node_path21.join)(root, path2);
|
|
21051
|
+
return (0, import_node_fs21.existsSync)(fullPath) ? (0, import_node_fs21.readFileSync)(fullPath, "utf8") : null;
|
|
21052
|
+
}
|
|
20467
21053
|
};
|
|
20468
21054
|
}
|
|
21055
|
+
function hubRoot() {
|
|
21056
|
+
const fromPkg = (0, import_node_path21.join)(__dirname, "..", "..");
|
|
21057
|
+
const marker = "skills/bootstrap/seeds/manifest.json";
|
|
21058
|
+
if ((0, import_node_fs21.existsSync)((0, import_node_path21.join)(fromPkg, marker))) return fromPkg;
|
|
21059
|
+
if ((0, import_node_fs21.existsSync)((0, import_node_path21.join)(process.cwd(), marker))) return process.cwd();
|
|
21060
|
+
return null;
|
|
21061
|
+
}
|
|
20469
21062
|
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) => {
|
|
20470
21063
|
const result = await resolveMergeCiPolicyForCheckout(o.repo);
|
|
20471
21064
|
if (o.json) return printLine(JSON.stringify(result));
|
|
@@ -20539,6 +21132,12 @@ pr.command("land <number>").description("agent merge path (#1440): train probe \
|
|
|
20539
21132
|
checksPassing: checks === "success" || checks === "no-checks-reported"
|
|
20540
21133
|
};
|
|
20541
21134
|
},
|
|
21135
|
+
readPrState: async (prNumber, repo) => {
|
|
21136
|
+
const args = repo ? ["--repo", repo] : [];
|
|
21137
|
+
const result2 = await readGhPrStateWithRetry(async () => (await execFileP2("gh", ["pr", "view", prNumber, ...args, "--json", "mergeStateStatus,mergeable,state"], { timeout: GC_GH_TIMEOUT_MS2 })).stdout);
|
|
21138
|
+
if (!result2.ok) throw new Error(`could not read PR state: ${result2.error}`);
|
|
21139
|
+
return JSON.parse(result2.state);
|
|
21140
|
+
},
|
|
20542
21141
|
mergeAuto: async (prNumber, repo) => {
|
|
20543
21142
|
const args = repo ? ["--repo", repo] : [];
|
|
20544
21143
|
const readMergeState = () => readGhPrStateWithRetry(async () => (await execFileP2("gh", ["pr", "view", prNumber, ...args, "--json", "state", "--jq", ".state"], { timeout: GC_GH_TIMEOUT_MS2 })).stdout);
|
|
@@ -20633,7 +21232,7 @@ async function createDeferredWorktreeStore() {
|
|
|
20633
21232
|
},
|
|
20634
21233
|
write: async (entries) => {
|
|
20635
21234
|
try {
|
|
20636
|
-
await (0, import_promises3.mkdir)((0,
|
|
21235
|
+
await (0, import_promises3.mkdir)((0, import_node_path21.dirname)(registryPath), { recursive: true });
|
|
20637
21236
|
await (0, import_promises3.writeFile)(registryPath, serializeDeferredWorktrees(entries), "utf8");
|
|
20638
21237
|
} catch {
|
|
20639
21238
|
}
|
|
@@ -20647,13 +21246,13 @@ var realWorktreeDirRemover = {
|
|
|
20647
21246
|
probe: (p) => {
|
|
20648
21247
|
let st;
|
|
20649
21248
|
try {
|
|
20650
|
-
st = (0,
|
|
21249
|
+
st = (0, import_node_fs21.lstatSync)(p);
|
|
20651
21250
|
} catch {
|
|
20652
21251
|
return null;
|
|
20653
21252
|
}
|
|
20654
21253
|
if (st.isSymbolicLink()) return "link";
|
|
20655
21254
|
try {
|
|
20656
|
-
(0,
|
|
21255
|
+
(0, import_node_fs21.readlinkSync)(p);
|
|
20657
21256
|
return "link";
|
|
20658
21257
|
} catch {
|
|
20659
21258
|
}
|
|
@@ -20661,7 +21260,7 @@ var realWorktreeDirRemover = {
|
|
|
20661
21260
|
},
|
|
20662
21261
|
readdir: (p) => {
|
|
20663
21262
|
try {
|
|
20664
|
-
return (0,
|
|
21263
|
+
return (0, import_node_fs21.readdirSync)(p);
|
|
20665
21264
|
} catch {
|
|
20666
21265
|
return [];
|
|
20667
21266
|
}
|
|
@@ -20670,9 +21269,9 @@ var realWorktreeDirRemover = {
|
|
|
20670
21269
|
// leaving the target); a file symlink with unlink. rmdir first, fall back to unlink.
|
|
20671
21270
|
detachLink: (p) => {
|
|
20672
21271
|
try {
|
|
20673
|
-
(0,
|
|
21272
|
+
(0, import_node_fs21.rmdirSync)(p);
|
|
20674
21273
|
} catch {
|
|
20675
|
-
(0,
|
|
21274
|
+
(0, import_node_fs21.unlinkSync)(p);
|
|
20676
21275
|
}
|
|
20677
21276
|
},
|
|
20678
21277
|
removeTree: (p) => (0, import_promises3.rm)(p, { recursive: true, force: true, maxRetries: 5, retryDelay: 200 })
|
|
@@ -20702,9 +21301,9 @@ async function worktreeHasStageState(worktreePath) {
|
|
|
20702
21301
|
}
|
|
20703
21302
|
}
|
|
20704
21303
|
function stageStateFileBelongsToWorktree(statePath, worktreePath) {
|
|
20705
|
-
if (!(0,
|
|
21304
|
+
if (!(0, import_node_fs21.existsSync)(statePath)) return false;
|
|
20706
21305
|
try {
|
|
20707
|
-
const state = JSON.parse((0,
|
|
21306
|
+
const state = JSON.parse((0, import_node_fs21.readFileSync)(statePath, "utf8"));
|
|
20708
21307
|
const recordedCwd = typeof state.identity?.cwd === "string" ? state.identity.cwd : typeof state.cwd === "string" ? state.cwd : "";
|
|
20709
21308
|
return Boolean(recordedCwd && isPathUnderDirectory(recordedCwd, worktreePath));
|
|
20710
21309
|
} catch {
|
|
@@ -20776,7 +21375,7 @@ pr.command("merge <number>").description("merge a PR (squash by default) and cle
|
|
|
20776
21375
|
localCleanup = await cleanupPrMergeLocalBranch(headRef, {
|
|
20777
21376
|
beforeWorktrees,
|
|
20778
21377
|
startingPath,
|
|
20779
|
-
pathExists: (p) => (0,
|
|
21378
|
+
pathExists: (p) => (0, import_node_fs21.existsSync)(p),
|
|
20780
21379
|
execGit: async (args) => (await execFileP2("git", args, { timeout: GIT_TIMEOUT_MS })).stdout,
|
|
20781
21380
|
teardownWorktreeStage,
|
|
20782
21381
|
deferredStore,
|
|
@@ -20997,7 +21596,7 @@ function stageScopedRunOpts(o) {
|
|
|
20997
21596
|
};
|
|
20998
21597
|
}
|
|
20999
21598
|
function printLine(value) {
|
|
21000
|
-
(0,
|
|
21599
|
+
(0, import_node_fs21.writeSync)(1, `${value}
|
|
21001
21600
|
`);
|
|
21002
21601
|
}
|
|
21003
21602
|
function stageKeepAlive() {
|
|
@@ -21011,8 +21610,8 @@ async function resolveStage() {
|
|
|
21011
21610
|
const portRange = portRangeMeta && typeof portRangeMeta.start === "number" && typeof portRangeMeta.end === "number" ? [portRangeMeta.start, portRangeMeta.end] : void 0;
|
|
21012
21611
|
return decideStage({
|
|
21013
21612
|
registry: { deployModel: project2?.deployModel, portRange, error: read.ok ? void 0 : read.error },
|
|
21014
|
-
hasCompose: (0,
|
|
21015
|
-
hasEnvExample: (0,
|
|
21613
|
+
hasCompose: (0, import_node_fs21.existsSync)((0, import_node_path21.join)(process.cwd(), "docker-compose.yml")),
|
|
21614
|
+
hasEnvExample: (0, import_node_fs21.existsSync)((0, import_node_path21.join)(process.cwd(), ".env.example"))
|
|
21016
21615
|
});
|
|
21017
21616
|
}
|
|
21018
21617
|
async function fetchStageVaultEnvMerge() {
|
|
@@ -21053,10 +21652,10 @@ program2.command("port-range <repo>").description("assign (idempotently) + print
|
|
|
21053
21652
|
printLine(o.json ? JSON.stringify({ repo, portRange: [start2, end2], source: "meta" }) : `${repo}: stage.portRange [${start2}, ${end2}]`);
|
|
21054
21653
|
return;
|
|
21055
21654
|
}
|
|
21056
|
-
const infra = resolvePortRangeInfra(process.cwd());
|
|
21655
|
+
const infra = resolvePortRangeInfra(process.cwd(), __dirname);
|
|
21057
21656
|
if (!infra) {
|
|
21058
21657
|
return failGraceful(
|
|
21059
|
-
`port-range: no MMI-Hub allocator files found
|
|
21658
|
+
`port-range: no MMI-Hub allocator files found (checked cwd ${process.cwd()}, sibling MMI-Hub dirs, and the installed package location); ensure the Hub's infra/port-ranges.json and infra/port-ddb.mjs are reachable`
|
|
21060
21659
|
);
|
|
21061
21660
|
}
|
|
21062
21661
|
const path2 = infra.registryPath;
|
|
@@ -21450,7 +22049,7 @@ ${r.repo}: applied=[${r.applied.join("; ")}] skipped=[${r.skipped.join("; ")}]${
|
|
|
21450
22049
|
if (!audit.ok) process.exitCode = 1;
|
|
21451
22050
|
});
|
|
21452
22051
|
var wiki = program2.command("wiki").description("release wiki lane \u2014 publish generated pages via a short-lived, repo-scoped minted token");
|
|
21453
|
-
wiki.command("publish <pages-dir>").description("mint a 1h repo-scoped contents:write token (master-gated) and publish the generated .md pages to <repo>.wiki.git; the token stays in memory (never printed/committed). Runs from any org repo checkout \u2014 the publisher ships inside mmi-cli, no repo-local script required. Fails loud on the credential gap \u2014 never a silent skip").option("--repo <owner/repo>", "target repo (defaults to the cwd repo)").action(async (pagesDir, o) => {
|
|
22052
|
+
wiki.command("publish <pages-dir>").description("mint a 1h repo-scoped contents:write token (master-gated) and publish the generated .md pages to <repo>.wiki.git; the token stays in memory (never printed/committed). Runs from any org repo checkout \u2014 the publisher ships inside mmi-cli, no repo-local script required. Fails loud on the credential gap \u2014 never a silent skip").option("--repo <owner/repo>", "target repo (defaults to the cwd repo)").option("--json", "machine-readable output").action(async (pagesDir, o) => {
|
|
21454
22053
|
const repo = await resolveRepo(o.repo);
|
|
21455
22054
|
if (!repo) return fail("wiki publish: could not determine the target repo (pass --repo owner/name)");
|
|
21456
22055
|
const cfg = await loadConfig();
|
|
@@ -21461,7 +22060,7 @@ wiki.command("publish <pages-dir>").description("mint a 1h repo-scoped contents:
|
|
|
21461
22060
|
log: (msg) => console.log(msg),
|
|
21462
22061
|
err: (msg) => console.error(msg)
|
|
21463
22062
|
},
|
|
21464
|
-
{ repo, pagesDir }
|
|
22063
|
+
{ repo, pagesDir, json: o.json }
|
|
21465
22064
|
);
|
|
21466
22065
|
if (!ok) process.exitCode = 1;
|
|
21467
22066
|
});
|
|
@@ -21486,9 +22085,8 @@ wiki.command("plan").description("compute the release wiki plan for the cwd repo
|
|
|
21486
22085
|
);
|
|
21487
22086
|
if (!ok) process.exitCode = 1;
|
|
21488
22087
|
});
|
|
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").
|
|
22088
|
+
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").action((o) => {
|
|
21490
22089
|
if (!o.repo) return fail("bootstrap: required option --repo <owner/repo> not specified");
|
|
21491
|
-
if (o.apply) return fail("bootstrap: execution is not implemented yet; use the dry-run plan and the existing /bootstrap skill");
|
|
21492
22090
|
if (o.class !== "deployable" && o.class !== "content") return fail("bootstrap: --class must be deployable or content");
|
|
21493
22091
|
const steps = bootstrapPlan(o.repo, o.class);
|
|
21494
22092
|
console.log(o.json ? JSON.stringify({ command: "bootstrap", repo: o.repo, class: o.class, steps }, null, 2) : renderSteps(`mmi-cli bootstrap: dry-run plan for ${o.repo}`, steps));
|
|
@@ -21505,7 +22103,7 @@ bootstrap.command("verify <repo>").description("audit whether an existing repo i
|
|
|
21505
22103
|
client: defaultGitHubClient(),
|
|
21506
22104
|
projectMeta: meta,
|
|
21507
22105
|
deployModel: typeof meta?.deployModel === "string" ? meta.deployModel : void 0,
|
|
21508
|
-
readLocalFile: (path2) => path2 === "projects.json" && apiProjects != null ? apiProjects : (0,
|
|
22106
|
+
readLocalFile: (path2) => path2 === "projects.json" && apiProjects != null ? apiProjects : (0, import_node_fs21.existsSync)(path2) ? (0, import_node_fs21.readFileSync)(path2, "utf8") : null,
|
|
21509
22107
|
// requiredGcpApis is stored as an array by a JSON write, but `project set --var KEY=VALUE` stores a raw
|
|
21510
22108
|
// comma-string — accept either so the seeded value verifies regardless of how it was written.
|
|
21511
22109
|
requiredGcpApis: (() => {
|
|
@@ -21538,7 +22136,7 @@ bootstrap.command("org-ruleset").description("drift-check the codified org no-ag
|
|
|
21538
22136
|
else console.log(renderOrgRulesetDriftReport(plan));
|
|
21539
22137
|
if (plan.action !== "noop") process.exitCode = 1;
|
|
21540
22138
|
});
|
|
21541
|
-
bootstrap.command("apply <repo>").description("idempotent seed apply from skills/bootstrap/seeds/manifest.json; dry-run unless --execute (live, master-gated)").option("--class <class>", "deployable | content", "deployable").option("--project-type <type>", `${PROJECT_TYPES.join(" | ")} (v2 capability shape)`).option("--deploy-model <model>", `${DEPLOY_MODELS.join(" | ")} (release/deploy path)`).option("--release-track <track>", `${RELEASE_TRACKS.join(" | ")} (branch topology; direct skips rc)`).option("--execute", "LIVE apply via gh (master-gated) \u2014 stamps seed files + labels into the repo").option("--var <KEY=VALUE...>", "placeholder values for repo-owned templates (repeatable)").option("--json", "machine-readable output").action(async (repo) => {
|
|
22139
|
+
bootstrap.command("apply <repo>").description("run from the MMI-Hub repo root: idempotent seed apply from skills/bootstrap/seeds/manifest.json; dry-run unless --execute (live, master-gated)").option("--class <class>", "deployable | content", "deployable").option("--project-type <type>", `${PROJECT_TYPES.join(" | ")} (v2 capability shape)`).option("--deploy-model <model>", `${DEPLOY_MODELS.join(" | ")} (release/deploy path)`).option("--release-track <track>", `${RELEASE_TRACKS.join(" | ")} (branch topology; direct skips rc)`).option("--execute", "LIVE apply via gh (master-gated) \u2014 stamps seed files + labels into the repo").option("--var <KEY=VALUE...>", "placeholder values for repo-owned templates (repeatable)").option("--json", "machine-readable output").action(async (repo) => {
|
|
21542
22140
|
const o = {
|
|
21543
22141
|
class: rawValue("--class", "deployable"),
|
|
21544
22142
|
projectType: rawValue("--project-type", ""),
|
|
@@ -21556,12 +22154,12 @@ bootstrap.command("apply <repo>").description("idempotent seed apply from skills
|
|
|
21556
22154
|
return fail(`bootstrap apply: ${e.message}`);
|
|
21557
22155
|
}
|
|
21558
22156
|
const manifestPath = "skills/bootstrap/seeds/manifest.json";
|
|
21559
|
-
if (!(0,
|
|
21560
|
-
const manifest = loadBootstrapSeeds((0,
|
|
22157
|
+
if (!(0, import_node_fs21.existsSync)(manifestPath)) return fail(`bootstrap apply: ${manifestPath} not found; bootstrap runs from the MMI-Hub repo root by design \u2014 it stamps org-level resources (Project, Ruleset, secrets, access) through the GitHub App, which is only authorized from the Hub checkout`);
|
|
22158
|
+
const manifest = loadBootstrapSeeds((0, import_node_fs21.readFileSync)(manifestPath, "utf8"));
|
|
21561
22159
|
const baseBranch = o.class === "content" ? "main" : "development";
|
|
21562
22160
|
const slug = parsedRepo.slug;
|
|
21563
22161
|
const gh = async (args) => execFileP2("gh", args, { timeout: 2e4 });
|
|
21564
|
-
const readFile2 = (p) => (0,
|
|
22162
|
+
const readFile2 = (p) => (0, import_node_fs21.existsSync)(p) ? (0, import_node_fs21.readFileSync)(p, "utf8") : null;
|
|
21565
22163
|
const enc = (p) => p.split("/").map(encodeURIComponent).join("/");
|
|
21566
22164
|
const rawVars = {};
|
|
21567
22165
|
for (const value of rawValues("--var")) {
|
|
@@ -21858,16 +22456,16 @@ access.command("audit").description("audit collaborator roles + train-branch pus
|
|
|
21858
22456
|
const repoClass = o.class;
|
|
21859
22457
|
targets = [{ repo: o.repo, class: repoClass, releaseTrack: repoClass === "content" ? "trunk" : resolveReleaseTrack(meta, void 0, o.repo) }];
|
|
21860
22458
|
} else {
|
|
21861
|
-
const projectsJson = registryProjects ? JSON.stringify({ projects: registryProjects }) : (0,
|
|
22459
|
+
const projectsJson = registryProjects ? JSON.stringify({ projects: registryProjects }) : (0, import_node_fs21.existsSync)("projects.json") ? (0, import_node_fs21.readFileSync)("projects.json", "utf8") : null;
|
|
21862
22460
|
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>");
|
|
21863
|
-
const fanoutJson = (0,
|
|
22461
|
+
const fanoutJson = (0, import_node_fs21.existsSync)(".github/fanout-targets.json") ? (0, import_node_fs21.readFileSync)(".github/fanout-targets.json", "utf8") : null;
|
|
21864
22462
|
targets = loadAccessTargets(projectsJson, fanoutJson);
|
|
21865
22463
|
}
|
|
21866
22464
|
const derivedMatrix = registryProjects ? accessMatrixFromProjects(registryProjects) : {};
|
|
21867
|
-
const fileMatrix = (0,
|
|
22465
|
+
const fileMatrix = (0, import_node_fs21.existsSync)("access-matrix.json") ? loadAccessMatrix((0, import_node_fs21.readFileSync)("access-matrix.json", "utf8")) : {};
|
|
21868
22466
|
const matrix = mergeAccessMatrix(fileMatrix, derivedMatrix);
|
|
21869
22467
|
const derivedContracts = registryProjects ? dataAccessContractsFromProjects(registryProjects) : { consumers: {} };
|
|
21870
|
-
const fileContracts = (0,
|
|
22468
|
+
const fileContracts = (0, import_node_fs21.existsSync)("data-access-contracts.json") ? loadDataAccessContracts((0, import_node_fs21.readFileSync)("data-access-contracts.json", "utf8")) : { consumers: {} };
|
|
21871
22469
|
const dataAccess = mergeDataAccessContracts(fileContracts, derivedContracts);
|
|
21872
22470
|
const report = await auditOrgAccess(targets, deps, matrix, dataAccess);
|
|
21873
22471
|
console.log(o.json ? JSON.stringify(report, null, 2) : renderAccessReport(report));
|
|
@@ -21875,7 +22473,7 @@ access.command("audit").description("audit collaborator roles + train-branch pus
|
|
|
21875
22473
|
});
|
|
21876
22474
|
access.command("capabilities").description("enumerate your effective vault reach \u2014 every credential NAME + tier + scope you can read/use across project + org/master tiers (names only, no values) (#1615)").option("--repo <owner/repo>", "target repo (defaults to the current repo)").option("--json", "machine-readable output").action((o) => withSecrets((d) => secretsCapabilities(d, o)));
|
|
21877
22475
|
var isWin2 = process.platform === "win32";
|
|
21878
|
-
program2.command("doctor").description("check onboarding gates and auto-heal CLI/plugin wiring; use --verbose for the full audit checklist").option("--banner", "one-line resume summary; silent when all gates pass").option("--preflight", "eager version/plugin heal with upfront notice when stale; silent when healthy (#1871)").option("--verbose", "run extended audit checks and print the full checklist + version report (#1989)").option("--guide", "print the MMI Agentic Onboarding guide URL").option("--json", "machine-readable output (read-only inspection \u2014 performs no repairs)").option("--apply", "perform the same auto-repairs as the interactive run (combine with --json for a machine-readable repair run)").option("--no-repo-writes", "env/plugin repairs only \u2014 never mutate the repo working tree; report pending managed .gitignore repairs with the follow-up command (for train preflights)").action((opts) => (
|
|
22476
|
+
program2.command("doctor").description("check onboarding gates and auto-heal CLI/plugin wiring; use --verbose for the full audit checklist").option("--banner", "one-line resume summary; silent when all gates pass").option("--fast", "offline-safe fast lane; skips slow checks and network probes").option("--preflight", "eager version/plugin heal with upfront notice when stale; silent when healthy (#1871)").option("--verbose", "run extended audit checks and print the full checklist + version report (#1989)").option("--ci", "stable CI output contract; no color, exit 0 for clear/advisory-only, 1 for hard gaps, 2 for doctor/tool failure").option("--guide", "print the MMI Agentic Onboarding guide URL").option("--json", "machine-readable output (read-only inspection \u2014 performs no repairs)").option("--apply", "perform the same auto-repairs as the interactive run (combine with --json for a machine-readable repair run)").option("--no-repo-writes", "env/plugin repairs only \u2014 never mutate the repo working tree; report pending managed .gitignore repairs with the follow-up command (for train preflights)").addHelpText("after", "\nExit codes:\n 0 no hard gaps remain; advisory gaps may exist\n 1 one or more included hard checks failed\n 2 doctor/tool execution failed before a normal verdict\n\n--banner keeps the legacy SessionStart contract and returns 0 unless the process crashes.\n").action((opts) => (
|
|
21879
22477
|
// Commander maps `--no-repo-writes` to `repoWrites: false`; translate to the explicit `noRepoWrites` flag.
|
|
21880
22478
|
runDoctor({ ...opts, noRepoWrites: opts.repoWrites === false })
|
|
21881
22479
|
));
|
|
@@ -21884,9 +22482,13 @@ program2.command("plugin-heal").description("reinstall + re-enable the MMI plugi
|
|
|
21884
22482
|
program2.command("session-start").description("run the SessionStart verbs (whoami, board slice, doctor) in one process").action(async () => {
|
|
21885
22483
|
if (isInsideRepoSubdir(process.cwd())) {
|
|
21886
22484
|
console.error("[mmi-hook] session-start: cwd is a repository SUBDIRECTORY \u2014 skipping the SessionStart hook (spine/docs/plan/saga delivery); run it from the repo root.");
|
|
22485
|
+
appendHookActivity(process.cwd(), { event: "SessionStart", script: "session-start", outcome: "ran", action: "skip (repo subdirectory)" });
|
|
22486
|
+
return;
|
|
22487
|
+
}
|
|
22488
|
+
if (!await isOrgRepoRoot()) {
|
|
22489
|
+
appendHookActivity(process.cwd(), { event: "SessionStart", script: "session-start", outcome: "ran", action: "skip (non-org repo)" });
|
|
21887
22490
|
return;
|
|
21888
22491
|
}
|
|
21889
|
-
if (!await isOrgRepoRoot()) return;
|
|
21890
22492
|
spawnDeferredGcSweep();
|
|
21891
22493
|
const { parallel, sequential } = buildSessionStartPlan({
|
|
21892
22494
|
// whoami (#879): surface the resolved human so agents act --for them without asking. Silent
|
|
@@ -21906,6 +22508,12 @@ program2.command("session-start").description("run the SessionStart verbs (whoam
|
|
|
21906
22508
|
// never costs banner time and next session's glance renders instantly within budget.
|
|
21907
22509
|
scheduleRefresh: () => spawnDetachedSelf(["board", "slice-refresh", "--quiet"], { spawn: import_node_child_process12.spawn, execPath: process.execPath, scriptPath: process.argv[1] })
|
|
21908
22510
|
}),
|
|
22511
|
+
// command-ladder hint (#2609): compact gh→mmi-cli cheat sheet so agents reach for mmi-cli first,
|
|
22512
|
+
// not after a denied gh call. <1KB, pure in-memory — no network, no fs, fail-soft.
|
|
22513
|
+
commandLadderHint: async (io) => {
|
|
22514
|
+
const hint = commandLadderHint();
|
|
22515
|
+
if (hint) io.log(hint);
|
|
22516
|
+
},
|
|
21909
22517
|
// local train sync (#2582): ff local development/main/rc to origin so this checkout never lags a
|
|
21910
22518
|
// release pushed to origin (incl. the deferred main -> development alignment merge). Bounded git,
|
|
21911
22519
|
// fail-soft, silent unless a branch moved.
|
|
@@ -21924,6 +22532,7 @@ program2.command("session-start").description("run the SessionStart verbs (whoam
|
|
|
21924
22532
|
spawnDetachedSelf(["worktree", "setup", "--quiet"], { spawn: import_node_child_process12.spawn, execPath: process.execPath, scriptPath: process.argv[1] });
|
|
21925
22533
|
consoleIo.log(worktreeBanner);
|
|
21926
22534
|
}
|
|
22535
|
+
appendHookActivity(process.cwd(), { event: "SessionStart", script: "session-start", outcome: "ran", action: "session-start hook completed" });
|
|
21927
22536
|
});
|
|
21928
22537
|
installProcessBackstop();
|
|
21929
22538
|
program2.parseAsync().catch((e) => failGraceful(e.message));
|