@mutmutco/cli 3.10.0 → 3.11.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 +977 -398
- 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
|
|
@@ -5838,9 +5840,31 @@ async function refreshBoardSliceCache(deps) {
|
|
|
5838
5840
|
}
|
|
5839
5841
|
}
|
|
5840
5842
|
|
|
5841
|
-
// src/
|
|
5843
|
+
// src/hook-activity.ts
|
|
5842
5844
|
var import_node_fs9 = require("node:fs");
|
|
5843
5845
|
var import_node_path9 = require("node:path");
|
|
5846
|
+
var DEFAULT_SURFACE = "claude";
|
|
5847
|
+
function activityLogPath(cwd) {
|
|
5848
|
+
return repoRuntimeStatePath(cwd, "hooks", "activity.jsonl");
|
|
5849
|
+
}
|
|
5850
|
+
function appendHookActivity(cwd, entry) {
|
|
5851
|
+
try {
|
|
5852
|
+
const path2 = activityLogPath(cwd);
|
|
5853
|
+
const line = {
|
|
5854
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5855
|
+
surface: DEFAULT_SURFACE,
|
|
5856
|
+
...entry
|
|
5857
|
+
};
|
|
5858
|
+
(0, import_node_fs9.mkdirSync)((0, import_node_path9.dirname)(path2), { recursive: true });
|
|
5859
|
+
(0, import_node_fs9.appendFileSync)(path2, `${JSON.stringify(line)}
|
|
5860
|
+
`, "utf8");
|
|
5861
|
+
} catch {
|
|
5862
|
+
}
|
|
5863
|
+
}
|
|
5864
|
+
|
|
5865
|
+
// src/worktree.ts
|
|
5866
|
+
var import_node_fs10 = require("node:fs");
|
|
5867
|
+
var import_node_path10 = require("node:path");
|
|
5844
5868
|
var LOCAL_ONLY_FILES = [".claude/settings.local.json"];
|
|
5845
5869
|
var PKG = "package.json";
|
|
5846
5870
|
var LOCKFILE = "package-lock.json";
|
|
@@ -5848,21 +5872,21 @@ var NODE_MODULES = "node_modules";
|
|
|
5848
5872
|
var realFsProbe = {
|
|
5849
5873
|
isDir: (p) => {
|
|
5850
5874
|
try {
|
|
5851
|
-
return (0,
|
|
5875
|
+
return (0, import_node_fs10.statSync)(p).isDirectory();
|
|
5852
5876
|
} catch {
|
|
5853
5877
|
return false;
|
|
5854
5878
|
}
|
|
5855
5879
|
},
|
|
5856
5880
|
isFile: (p) => {
|
|
5857
5881
|
try {
|
|
5858
|
-
return (0,
|
|
5882
|
+
return (0, import_node_fs10.statSync)(p).isFile();
|
|
5859
5883
|
} catch {
|
|
5860
5884
|
return false;
|
|
5861
5885
|
}
|
|
5862
5886
|
},
|
|
5863
5887
|
listDirs: (p) => {
|
|
5864
5888
|
try {
|
|
5865
|
-
return (0,
|
|
5889
|
+
return (0, import_node_fs10.readdirSync)(p, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
|
|
5866
5890
|
} catch {
|
|
5867
5891
|
return [];
|
|
5868
5892
|
}
|
|
@@ -5870,12 +5894,12 @@ var realFsProbe = {
|
|
|
5870
5894
|
};
|
|
5871
5895
|
function scanInstallDirs(root, fs2 = realFsProbe) {
|
|
5872
5896
|
const factsFor = (dir) => {
|
|
5873
|
-
const abs = dir ? (0,
|
|
5897
|
+
const abs = dir ? (0, import_node_path10.join)(root, dir) : root;
|
|
5874
5898
|
return {
|
|
5875
5899
|
dir,
|
|
5876
|
-
hasPackageJson: fs2.isFile((0,
|
|
5877
|
-
hasLockfile: fs2.isFile((0,
|
|
5878
|
-
hasNodeModules: fs2.isDir((0,
|
|
5900
|
+
hasPackageJson: fs2.isFile((0, import_node_path10.join)(abs, PKG)),
|
|
5901
|
+
hasLockfile: fs2.isFile((0, import_node_path10.join)(abs, LOCKFILE)),
|
|
5902
|
+
hasNodeModules: fs2.isDir((0, import_node_path10.join)(abs, NODE_MODULES))
|
|
5879
5903
|
};
|
|
5880
5904
|
};
|
|
5881
5905
|
const children = fs2.listDirs(root).filter((name) => name !== NODE_MODULES && name !== ".git");
|
|
@@ -5885,7 +5909,7 @@ function npmInstallTargets(dirs) {
|
|
|
5885
5909
|
return dirs.filter((d) => d.hasPackageJson && !d.hasNodeModules).map((d) => ({ dir: d.dir, command: d.hasLockfile ? "npm ci" : "npm install" }));
|
|
5886
5910
|
}
|
|
5887
5911
|
function isLinkedWorktree(root, fs2 = realFsProbe) {
|
|
5888
|
-
return fs2.isFile((0,
|
|
5912
|
+
return fs2.isFile((0, import_node_path10.join)(root, ".git"));
|
|
5889
5913
|
}
|
|
5890
5914
|
function worktreeAutoProvisionBanner(root, fs2 = realFsProbe) {
|
|
5891
5915
|
if (!isLinkedWorktree(root, fs2)) return null;
|
|
@@ -5895,8 +5919,8 @@ function worktreeAutoProvisionBanner(root, fs2 = realFsProbe) {
|
|
|
5895
5919
|
return `[worktree] provisioning tooling in the background (deps in ${where} + local config) \u2014 \`mmi-cli worktree setup\` to redo`;
|
|
5896
5920
|
}
|
|
5897
5921
|
function defaultCopyFile(from, to) {
|
|
5898
|
-
(0,
|
|
5899
|
-
(0,
|
|
5922
|
+
(0, import_node_fs10.mkdirSync)((0, import_node_path10.dirname)(to), { recursive: true });
|
|
5923
|
+
(0, import_node_fs10.copyFileSync)(from, to);
|
|
5900
5924
|
}
|
|
5901
5925
|
async function provisionWorktree(worktreeRoot, deps) {
|
|
5902
5926
|
const fs2 = deps.fs ?? realFsProbe;
|
|
@@ -5908,7 +5932,7 @@ async function provisionWorktree(worktreeRoot, deps) {
|
|
|
5908
5932
|
const skippedInstall = allDirs.filter((d) => d.hasPackageJson && d.hasNodeModules).map((d) => d.dir);
|
|
5909
5933
|
const installed = [];
|
|
5910
5934
|
for (const target of targets) {
|
|
5911
|
-
const cwd = target.dir ? (0,
|
|
5935
|
+
const cwd = target.dir ? (0, import_node_path10.join)(worktreeRoot, target.dir) : worktreeRoot;
|
|
5912
5936
|
log(`installing deps: ${target.command} in ${target.dir || "."}`);
|
|
5913
5937
|
await deps.runInstall(target.command, cwd);
|
|
5914
5938
|
installed.push(target);
|
|
@@ -5917,7 +5941,7 @@ async function provisionWorktree(worktreeRoot, deps) {
|
|
|
5917
5941
|
const copySkipped = [];
|
|
5918
5942
|
const primary = await deps.primaryCheckout();
|
|
5919
5943
|
for (const rel of LOCAL_ONLY_FILES) {
|
|
5920
|
-
const dest = (0,
|
|
5944
|
+
const dest = (0, import_node_path10.join)(worktreeRoot, rel);
|
|
5921
5945
|
if (fs2.isFile(dest)) {
|
|
5922
5946
|
copySkipped.push({ file: rel, reason: "already-present" });
|
|
5923
5947
|
continue;
|
|
@@ -5926,11 +5950,11 @@ async function provisionWorktree(worktreeRoot, deps) {
|
|
|
5926
5950
|
copySkipped.push({ file: rel, reason: "no-primary" });
|
|
5927
5951
|
continue;
|
|
5928
5952
|
}
|
|
5929
|
-
if (!fs2.isFile((0,
|
|
5953
|
+
if (!fs2.isFile((0, import_node_path10.join)(primary, rel))) {
|
|
5930
5954
|
copySkipped.push({ file: rel, reason: "absent-in-primary" });
|
|
5931
5955
|
continue;
|
|
5932
5956
|
}
|
|
5933
|
-
copyFile((0,
|
|
5957
|
+
copyFile((0, import_node_path10.join)(primary, rel), dest);
|
|
5934
5958
|
copied.push(rel);
|
|
5935
5959
|
log(`copied local config: ${rel}`);
|
|
5936
5960
|
}
|
|
@@ -5938,13 +5962,50 @@ async function provisionWorktree(worktreeRoot, deps) {
|
|
|
5938
5962
|
}
|
|
5939
5963
|
function defaultWorktreePath(repoRoot, branch) {
|
|
5940
5964
|
const safe = branch.replace(/[/\\]+/g, "-");
|
|
5941
|
-
return (0,
|
|
5965
|
+
return (0, import_node_path10.join)((0, import_node_path10.dirname)(repoRoot), "mmi-worktrees", safe);
|
|
5942
5966
|
}
|
|
5943
5967
|
function resolveWorktreeBase(from, remote) {
|
|
5944
5968
|
const remotePrefix = `${remote}/`;
|
|
5945
5969
|
const fetchBranch = from.startsWith(remotePrefix) ? from.slice(remotePrefix.length) : void 0;
|
|
5946
5970
|
return { base: from, fetchBranch };
|
|
5947
5971
|
}
|
|
5972
|
+
var GIT_CONFIG_LOCK_RE = /could not lock config file|unable to write upstream branch configuration/i;
|
|
5973
|
+
function isGitConfigLockError(error) {
|
|
5974
|
+
return GIT_CONFIG_LOCK_RE.test(error instanceof Error ? error.message : String(error));
|
|
5975
|
+
}
|
|
5976
|
+
var ADD_WORKTREE_BACKOFF_MS = [100, 250, 500, 750, 1e3];
|
|
5977
|
+
var ADD_WORKTREE_MAX_ATTEMPTS = 6;
|
|
5978
|
+
async function cleanupOrphanBranch(branch, base, preExistingOid, deps) {
|
|
5979
|
+
if (preExistingOid !== void 0) return;
|
|
5980
|
+
const currentOid = await deps.revParse(`refs/heads/${branch}`);
|
|
5981
|
+
if (currentOid === void 0) return;
|
|
5982
|
+
const baseOid = await deps.revParse(base);
|
|
5983
|
+
if (baseOid !== void 0 && currentOid !== baseOid) return;
|
|
5984
|
+
await deps.deleteBranch(branch);
|
|
5985
|
+
}
|
|
5986
|
+
async function addWorktreeRobust(wtPath, branch, base, deps) {
|
|
5987
|
+
const maxAttempts = deps.maxAttempts ?? ADD_WORKTREE_MAX_ATTEMPTS;
|
|
5988
|
+
const backoff = deps.backoffMs ?? ADD_WORKTREE_BACKOFF_MS;
|
|
5989
|
+
const log = deps.log ?? (() => {
|
|
5990
|
+
});
|
|
5991
|
+
const preExistingOid = await deps.revParse(`refs/heads/${branch}`);
|
|
5992
|
+
for (let i = 0; i < maxAttempts; i++) {
|
|
5993
|
+
try {
|
|
5994
|
+
await deps.git(["worktree", "add", wtPath, "-b", branch, base]);
|
|
5995
|
+
return;
|
|
5996
|
+
} catch (e) {
|
|
5997
|
+
await cleanupOrphanBranch(branch, base, preExistingOid, deps).catch(() => {
|
|
5998
|
+
});
|
|
5999
|
+
const retriesLeft = i < maxAttempts - 1;
|
|
6000
|
+
if (retriesLeft && isGitConfigLockError(e)) {
|
|
6001
|
+
log(`worktree add: .git/config lock busy, retrying (${i + 1}/${maxAttempts - 1})`);
|
|
6002
|
+
await deps.sleep(backoff[Math.min(i, backoff.length - 1)]);
|
|
6003
|
+
continue;
|
|
6004
|
+
}
|
|
6005
|
+
throw e;
|
|
6006
|
+
}
|
|
6007
|
+
}
|
|
6008
|
+
}
|
|
5948
6009
|
|
|
5949
6010
|
// src/whoami.ts
|
|
5950
6011
|
async function resolveWhoami(deps) {
|
|
@@ -5969,8 +6030,39 @@ function whoamiLine(report) {
|
|
|
5969
6030
|
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
6031
|
}
|
|
5971
6032
|
|
|
6033
|
+
// src/command-ladder-hint.ts
|
|
6034
|
+
var COMMAND_LADDER_WRITES = [
|
|
6035
|
+
{ gh: "gh issue create", replacement: "mmi-cli issue create" },
|
|
6036
|
+
{ gh: "gh issue comment", replacement: "mmi-cli issue comment" },
|
|
6037
|
+
{ gh: "gh pr create", replacement: "mmi-cli pr create" },
|
|
6038
|
+
{ gh: "gh pr merge", replacement: "mmi-cli pr merge (or mmi-cli pr land)" }
|
|
6039
|
+
];
|
|
6040
|
+
var COMMAND_LADDER_READS = [
|
|
6041
|
+
{ gh: "gh issue view <N>", replacement: "mmi-cli issue view <N>" },
|
|
6042
|
+
{ gh: "gh pr view <N>", replacement: "mmi-cli pr view <N>" },
|
|
6043
|
+
{ gh: "gh pr checks <N>", replacement: "mmi-cli pr checks-wait <N>" }
|
|
6044
|
+
];
|
|
6045
|
+
var COMMAND_LADDER_BOARD = [
|
|
6046
|
+
{ gh: "gh issue list / project", replacement: "mmi-cli board read" }
|
|
6047
|
+
];
|
|
6048
|
+
function commandLadderHint() {
|
|
6049
|
+
const pad = 28;
|
|
6050
|
+
const lines = [];
|
|
6051
|
+
lines.push("[mmi] Command ladder (#2609): reach for mmi-cli first");
|
|
6052
|
+
for (const e of COMMAND_LADDER_WRITES) {
|
|
6053
|
+
lines.push(`${e.gh.padEnd(pad)} \u2192 ${e.replacement}`);
|
|
6054
|
+
}
|
|
6055
|
+
for (const e of COMMAND_LADDER_READS) {
|
|
6056
|
+
lines.push(`${e.gh.padEnd(pad)} \u2192 ${e.replacement}`);
|
|
6057
|
+
}
|
|
6058
|
+
for (const e of COMMAND_LADDER_BOARD) {
|
|
6059
|
+
lines.push(`${e.gh.padEnd(pad)} \u2192 ${e.replacement}`);
|
|
6060
|
+
}
|
|
6061
|
+
return lines.join("\n");
|
|
6062
|
+
}
|
|
6063
|
+
|
|
5972
6064
|
// src/index.ts
|
|
5973
|
-
var
|
|
6065
|
+
var import_node_path21 = require("node:path");
|
|
5974
6066
|
|
|
5975
6067
|
// src/merge-ci-policy.ts
|
|
5976
6068
|
function resolveMergeCiPolicy(input) {
|
|
@@ -6912,7 +7004,7 @@ async function auditRepoCi(repo, deps) {
|
|
|
6912
7004
|
ok: hasRequiredChecks,
|
|
6913
7005
|
label: "product required status checks active",
|
|
6914
7006
|
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
|
|
7007
|
+
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
7008
|
});
|
|
6917
7009
|
if (hasGateWorkflow) {
|
|
6918
7010
|
const emitted = registryRequiredContexts(meta) ?? await getEmittedPrContexts();
|
|
@@ -7221,6 +7313,13 @@ async function runPrLand(prNumber, options, deps) {
|
|
|
7221
7313
|
error: `checks-wait ${checksWait.status}${checksWait.detail ? `: ${checksWait.detail}` : ""}`
|
|
7222
7314
|
};
|
|
7223
7315
|
}
|
|
7316
|
+
const prState = await deps.readPrState(prNumber, repo);
|
|
7317
|
+
if (prState.mergeStateStatus === "DIRTY" || prState.mergeable === "CONFLICTING") {
|
|
7318
|
+
return {
|
|
7319
|
+
...base,
|
|
7320
|
+
error: `pr land #${prNumber}: branch conflicts with base \u2014 rebase/merge development, rebuild any checked-in artifacts, then re-push before landing`
|
|
7321
|
+
};
|
|
7322
|
+
}
|
|
7224
7323
|
const merge = await mergeAutoWithTransientRetry(prNumber, repo, deps);
|
|
7225
7324
|
base.mergeStatus = merge.mergeStatus;
|
|
7226
7325
|
if (merge.mergeStatus === "failed") {
|
|
@@ -7241,10 +7340,10 @@ async function runPrLand(prNumber, options, deps) {
|
|
|
7241
7340
|
}
|
|
7242
7341
|
|
|
7243
7342
|
// src/wave-status.ts
|
|
7244
|
-
var
|
|
7343
|
+
var import_node_fs12 = require("node:fs");
|
|
7245
7344
|
|
|
7246
7345
|
// src/gc.ts
|
|
7247
|
-
var
|
|
7346
|
+
var import_node_path11 = require("node:path");
|
|
7248
7347
|
var DEFERRED_SWEEP_COMMAND = "mmi-cli gc sweep-deferred";
|
|
7249
7348
|
var DEFERRED_NOTE = "Worktree cleanup queued (IDE lock). Detached sweep will retry automatically \u2014 no human action required.";
|
|
7250
7349
|
var PRESERVED_WORKTREE_CONFIG = "mmi.preservedWorktreeBranch";
|
|
@@ -7515,8 +7614,8 @@ function resolveSafeSiblingWorktreeCleanupTarget(worktreePath, siblingRoot, deps
|
|
|
7515
7614
|
return { ok: true, path: worktreePath };
|
|
7516
7615
|
}
|
|
7517
7616
|
function siblingMmiWorktreesRoot(repoRoot) {
|
|
7518
|
-
const parent = (0,
|
|
7519
|
-
return (0,
|
|
7617
|
+
const parent = (0, import_node_path11.dirname)(repoRoot);
|
|
7618
|
+
return (0, import_node_path11.basename)(parent).toLowerCase() === "mmi-worktrees" ? parent : (0, import_node_path11.join)(parent, "mmi-worktrees");
|
|
7520
7619
|
}
|
|
7521
7620
|
function classifySiblingWorktreeDir(entry) {
|
|
7522
7621
|
if (!entry.ownedByCurrentRepo) {
|
|
@@ -8177,8 +8276,8 @@ function formatGcPlan(plan, apply) {
|
|
|
8177
8276
|
|
|
8178
8277
|
// src/stage-runner.ts
|
|
8179
8278
|
var import_node_child_process6 = require("node:child_process");
|
|
8180
|
-
var
|
|
8181
|
-
var
|
|
8279
|
+
var import_node_fs11 = require("node:fs");
|
|
8280
|
+
var import_node_path12 = require("node:path");
|
|
8182
8281
|
var import_node_net = require("node:net");
|
|
8183
8282
|
var import_node_util6 = require("node:util");
|
|
8184
8283
|
var execFileP4 = (0, import_node_util6.promisify)(import_node_child_process6.execFile);
|
|
@@ -8295,11 +8394,11 @@ function appendForceRecreate(up) {
|
|
|
8295
8394
|
return `${up.trimEnd()} --force-recreate`;
|
|
8296
8395
|
}
|
|
8297
8396
|
function stageStatePath(cwd = process.cwd()) {
|
|
8298
|
-
return (0,
|
|
8397
|
+
return (0, import_node_path12.join)(cwd, "tmp", "stage", "state.json");
|
|
8299
8398
|
}
|
|
8300
8399
|
function stageGlobalStatePath(cwd = process.cwd(), gitCommonDir = ".git") {
|
|
8301
|
-
const dir = (0,
|
|
8302
|
-
return (0,
|
|
8400
|
+
const dir = (0, import_node_path12.isAbsolute)(gitCommonDir) ? gitCommonDir : (0, import_node_path12.resolve)(cwd, gitCommonDir);
|
|
8401
|
+
return (0, import_node_path12.join)(dir, "mmi", "stage", "state.json");
|
|
8303
8402
|
}
|
|
8304
8403
|
function normPath2(path2) {
|
|
8305
8404
|
return path2.replace(/\\/g, "/").replace(/\/+$/, "").toLowerCase();
|
|
@@ -8523,14 +8622,14 @@ function stageProcessEnv(stagePort, extraEnv) {
|
|
|
8523
8622
|
}
|
|
8524
8623
|
async function ensureStageRuntimeEnv(config, opts, cwd) {
|
|
8525
8624
|
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,
|
|
8625
|
+
const target = (0, import_node_path12.join)(cwd, config.ensureEnv.target);
|
|
8626
|
+
const example = (0, import_node_path12.join)(cwd, config.ensureEnv.example);
|
|
8627
|
+
if (!(0, import_node_fs11.existsSync)(target) && (0, import_node_fs11.existsSync)(example)) {
|
|
8628
|
+
(0, import_node_fs11.copyFileSync)(example, target);
|
|
8629
|
+
} else if ((0, import_node_fs11.existsSync)(target) && (0, import_node_fs11.existsSync)(example)) {
|
|
8630
|
+
const stale = detectStaleEnvFile((0, import_node_fs11.readFileSync)(example, "utf8"), (0, import_node_fs11.readFileSync)(target, "utf8"), {
|
|
8631
|
+
exampleMtimeMs: (0, import_node_fs11.statSync)(example).mtimeMs,
|
|
8632
|
+
targetMtimeMs: (0, import_node_fs11.statSync)(target).mtimeMs
|
|
8534
8633
|
});
|
|
8535
8634
|
if (stale) {
|
|
8536
8635
|
const msg = `stale ${config.ensureEnv.target} (${stale}) \u2014 delete it or refresh from ${config.ensureEnv.example} before re-running /stage`;
|
|
@@ -8538,8 +8637,8 @@ async function ensureStageRuntimeEnv(config, opts, cwd) {
|
|
|
8538
8637
|
console.error(`mmi-cli stage: ${msg} (allowed via --allow-stale-env)`);
|
|
8539
8638
|
}
|
|
8540
8639
|
}
|
|
8541
|
-
if (opts.vaultEnvMerge && Object.keys(opts.vaultEnvMerge).length && (0,
|
|
8542
|
-
(0,
|
|
8640
|
+
if (opts.vaultEnvMerge && Object.keys(opts.vaultEnvMerge).length && (0, import_node_fs11.existsSync)(target)) {
|
|
8641
|
+
(0, import_node_fs11.writeFileSync)(target, mergeEnvSecretsIntoFile((0, import_node_fs11.readFileSync)(target, "utf8"), opts.vaultEnvMerge), "utf8");
|
|
8543
8642
|
}
|
|
8544
8643
|
}
|
|
8545
8644
|
async function gitText(cwd, args) {
|
|
@@ -8569,20 +8668,20 @@ async function resolveGlobalStatePath(cwd, explicit) {
|
|
|
8569
8668
|
return void 0;
|
|
8570
8669
|
}
|
|
8571
8670
|
function readState(path2) {
|
|
8572
|
-
if (!(0,
|
|
8671
|
+
if (!(0, import_node_fs11.existsSync)(path2)) return null;
|
|
8573
8672
|
try {
|
|
8574
|
-
return JSON.parse((0,
|
|
8673
|
+
return JSON.parse((0, import_node_fs11.readFileSync)(path2, "utf8"));
|
|
8575
8674
|
} catch {
|
|
8576
8675
|
return null;
|
|
8577
8676
|
}
|
|
8578
8677
|
}
|
|
8579
8678
|
function mkdirFor(path2) {
|
|
8580
8679
|
const dir = path2.slice(0, Math.max(path2.lastIndexOf("/"), path2.lastIndexOf("\\")));
|
|
8581
|
-
(0,
|
|
8680
|
+
(0, import_node_fs11.mkdirSync)(dir, { recursive: true });
|
|
8582
8681
|
}
|
|
8583
8682
|
function writeState(path2, state) {
|
|
8584
8683
|
mkdirFor(path2);
|
|
8585
|
-
(0,
|
|
8684
|
+
(0, import_node_fs11.writeFileSync)(path2, JSON.stringify(state, null, 2), "utf8");
|
|
8586
8685
|
}
|
|
8587
8686
|
function writeStagePortReservation(port, cwd, statePath, globalStatePath, now) {
|
|
8588
8687
|
const reservation = {
|
|
@@ -8602,7 +8701,7 @@ async function cleanupStageState(state, paths, timeoutMs, fallbackCwd) {
|
|
|
8602
8701
|
await shell(state.teardown.command.trim(), state.teardown.cwd || state.cwd || fallbackCwd, timeoutMs);
|
|
8603
8702
|
}
|
|
8604
8703
|
for (const path2 of [...new Set(paths.filter((p) => Boolean(p)))]) {
|
|
8605
|
-
(0,
|
|
8704
|
+
(0, import_node_fs11.rmSync)(path2, { force: true });
|
|
8606
8705
|
}
|
|
8607
8706
|
}
|
|
8608
8707
|
async function killTree(pid) {
|
|
@@ -8758,8 +8857,8 @@ async function runStage(config = {}, opts = {}) {
|
|
|
8758
8857
|
await ensureStageRuntimeEnv(config, opts, cwd);
|
|
8759
8858
|
if (build) await shell(sub(build), cwd, timeoutMs, stageProcessEnv(stagePort, extraEnv));
|
|
8760
8859
|
} catch (e) {
|
|
8761
|
-
(0,
|
|
8762
|
-
if (globalStatePath && globalStatePath !== statePath) (0,
|
|
8860
|
+
(0, import_node_fs11.rmSync)(statePath, { force: true });
|
|
8861
|
+
if (globalStatePath && globalStatePath !== statePath) (0, import_node_fs11.rmSync)(globalStatePath, { force: true });
|
|
8763
8862
|
throw e;
|
|
8764
8863
|
}
|
|
8765
8864
|
const started = await startStage(config, {
|
|
@@ -8784,9 +8883,9 @@ function parseNextFromHead(headText) {
|
|
|
8784
8883
|
}
|
|
8785
8884
|
function readStageSummary(worktreePath) {
|
|
8786
8885
|
const statePath = stageStatePath(worktreePath);
|
|
8787
|
-
if (!(0,
|
|
8886
|
+
if (!(0, import_node_fs12.existsSync)(statePath)) return void 0;
|
|
8788
8887
|
try {
|
|
8789
|
-
const state = JSON.parse((0,
|
|
8888
|
+
const state = JSON.parse((0, import_node_fs12.readFileSync)(statePath, "utf8"));
|
|
8790
8889
|
const port = typeof state.port === "number" ? state.port : void 0;
|
|
8791
8890
|
if (port == null || !Number.isInteger(port) || port <= 0) return void 0;
|
|
8792
8891
|
return { port, url: typeof state.url === "string" ? state.url : void 0 };
|
|
@@ -8902,7 +9001,7 @@ async function resolveAutoAddBoardAttach(client, cfg, selector, priority, warn =
|
|
|
8902
9001
|
// src/gh-create.ts
|
|
8903
9002
|
var import_promises = require("node:fs/promises");
|
|
8904
9003
|
var import_node_os3 = require("node:os");
|
|
8905
|
-
var
|
|
9004
|
+
var import_node_path13 = require("node:path");
|
|
8906
9005
|
var import_node_crypto3 = require("node:crypto");
|
|
8907
9006
|
var ISSUE_TYPES = ["bug", "feature", "task"];
|
|
8908
9007
|
var GH_MUTATION_TIMEOUT_MS = 12e4;
|
|
@@ -8945,8 +9044,8 @@ async function bodyArgsViaFile(args, deps = {}) {
|
|
|
8945
9044
|
const remove = deps.remove ?? import_promises.unlink;
|
|
8946
9045
|
const ensureDir = deps.ensureDir ?? import_promises.mkdir;
|
|
8947
9046
|
const dir = deps.dir ?? (0, import_node_os3.tmpdir)();
|
|
8948
|
-
const file = (0,
|
|
8949
|
-
await ensureDir((0,
|
|
9047
|
+
const file = (0, import_node_path13.join)(dir, `mmi-gh-body-${process.pid}-${(0, import_node_crypto3.randomBytes)(4).toString("hex")}.md`);
|
|
9048
|
+
await ensureDir((0, import_node_path13.dirname)(file), { recursive: true }).catch(() => {
|
|
8950
9049
|
});
|
|
8951
9050
|
await write(file, args[i + 1], "utf8");
|
|
8952
9051
|
return {
|
|
@@ -12000,8 +12099,8 @@ async function announceRelease(deps, args) {
|
|
|
12000
12099
|
}
|
|
12001
12100
|
|
|
12002
12101
|
// src/port-registry.ts
|
|
12003
|
-
var
|
|
12004
|
-
var
|
|
12102
|
+
var import_node_fs13 = require("node:fs");
|
|
12103
|
+
var import_node_path14 = require("node:path");
|
|
12005
12104
|
|
|
12006
12105
|
// ../infra/port-geometry.mjs
|
|
12007
12106
|
var PORT_BLOCK = 100;
|
|
@@ -12015,8 +12114,8 @@ function nextPortBlock(registry2) {
|
|
|
12015
12114
|
return [base, base + PORT_SPAN];
|
|
12016
12115
|
}
|
|
12017
12116
|
function loadPortRegistry(path2) {
|
|
12018
|
-
if (!(0,
|
|
12019
|
-
const raw = JSON.parse((0,
|
|
12117
|
+
if (!(0, import_node_fs13.existsSync)(path2)) return {};
|
|
12118
|
+
const raw = JSON.parse((0, import_node_fs13.readFileSync)(path2, "utf8"));
|
|
12020
12119
|
const out = {};
|
|
12021
12120
|
for (const [key, value] of Object.entries(raw)) {
|
|
12022
12121
|
if (Array.isArray(value) && value.length === 2 && value.every((n) => typeof n === "number")) {
|
|
@@ -12030,9 +12129,9 @@ function ensurePortRange(repo, path2) {
|
|
|
12030
12129
|
const existing = registry2[repo];
|
|
12031
12130
|
if (existing) return existing;
|
|
12032
12131
|
const range = nextPortBlock(registry2);
|
|
12033
|
-
const raw = (0,
|
|
12132
|
+
const raw = (0, import_node_fs13.existsSync)(path2) ? JSON.parse((0, import_node_fs13.readFileSync)(path2, "utf8")) : {};
|
|
12034
12133
|
raw[repo] = range;
|
|
12035
|
-
(0,
|
|
12134
|
+
(0, import_node_fs13.writeFileSync)(path2, JSON.stringify(raw, null, 2) + "\n", "utf8");
|
|
12036
12135
|
return range;
|
|
12037
12136
|
}
|
|
12038
12137
|
function portCursorSeed(registry2) {
|
|
@@ -12054,20 +12153,26 @@ function existingPortRange(repo, registry2) {
|
|
|
12054
12153
|
return registry2[repo] ?? null;
|
|
12055
12154
|
}
|
|
12056
12155
|
function portRangeInfraAt(root, source) {
|
|
12057
|
-
const registryPath = (0,
|
|
12058
|
-
const ddbScriptPath = (0,
|
|
12059
|
-
if (!(0,
|
|
12156
|
+
const registryPath = (0, import_node_path14.join)(root, "infra", "port-ranges.json");
|
|
12157
|
+
const ddbScriptPath = (0, import_node_path14.join)(root, "infra", "port-ddb.mjs");
|
|
12158
|
+
if (!(0, import_node_fs13.existsSync)(registryPath) || !(0, import_node_fs13.existsSync)(ddbScriptPath)) return null;
|
|
12060
12159
|
return { root, source, registryPath, ddbScriptPath };
|
|
12061
12160
|
}
|
|
12062
|
-
function resolvePortRangeInfra(cwd) {
|
|
12161
|
+
function resolvePortRangeInfra(cwd, packageDir) {
|
|
12063
12162
|
const direct = portRangeInfraAt(cwd, "cwd");
|
|
12064
12163
|
if (direct) return direct;
|
|
12065
|
-
for (let dir = cwd; ; dir = (0,
|
|
12066
|
-
const sibling = portRangeInfraAt((0,
|
|
12164
|
+
for (let dir = cwd; ; dir = (0, import_node_path14.dirname)(dir)) {
|
|
12165
|
+
const sibling = portRangeInfraAt((0, import_node_path14.join)(dir, "MMI-Hub"), "sibling-hub");
|
|
12067
12166
|
if (sibling) return sibling;
|
|
12068
|
-
const parent = (0,
|
|
12069
|
-
if (parent === dir)
|
|
12167
|
+
const parent = (0, import_node_path14.dirname)(dir);
|
|
12168
|
+
if (parent === dir) break;
|
|
12070
12169
|
}
|
|
12170
|
+
if (packageDir) {
|
|
12171
|
+
const pkgRoot = (0, import_node_path14.join)(packageDir, "..", "..");
|
|
12172
|
+
const pkgFrom = portRangeInfraAt(pkgRoot, "pkg-root");
|
|
12173
|
+
if (pkgFrom) return pkgFrom;
|
|
12174
|
+
}
|
|
12175
|
+
return null;
|
|
12071
12176
|
}
|
|
12072
12177
|
async function ensurePortRangeAtomic(repo, path2, allocate, opts = {}) {
|
|
12073
12178
|
const registry2 = loadPortRegistry(path2);
|
|
@@ -12954,27 +13059,39 @@ async function tenantDeploy(payload, deps) {
|
|
|
12954
13059
|
// src/wiki-publish-command.ts
|
|
12955
13060
|
async function wikiPublish(deps, opts) {
|
|
12956
13061
|
if (!opts.pagesDir) {
|
|
12957
|
-
|
|
13062
|
+
if (opts.json) {
|
|
13063
|
+
deps.log(JSON.stringify({ ok: false, error: "pages-dir is required" }, null, 2));
|
|
13064
|
+
} else {
|
|
13065
|
+
deps.err("wiki publish: <pages-dir> is required");
|
|
13066
|
+
}
|
|
12958
13067
|
return false;
|
|
12959
13068
|
}
|
|
12960
13069
|
const minted = await deps.mint(opts.repo);
|
|
12961
13070
|
if (!minted.ok) {
|
|
12962
|
-
|
|
13071
|
+
if (opts.json) {
|
|
13072
|
+
deps.log(JSON.stringify({ ok: false, error: minted.error }, null, 2));
|
|
13073
|
+
} else {
|
|
13074
|
+
deps.err(`wiki publish failed: could not mint a scoped wiki token for ${opts.repo}: ${minted.error}`);
|
|
13075
|
+
}
|
|
12963
13076
|
return false;
|
|
12964
13077
|
}
|
|
12965
|
-
const ok = deps.publish({ repo: opts.repo, token: minted.mint.token, pagesDir: opts.pagesDir });
|
|
13078
|
+
const ok = deps.publish({ repo: opts.repo, token: minted.mint.token, pagesDir: opts.pagesDir, json: opts.json });
|
|
12966
13079
|
if (!ok) {
|
|
12967
13080
|
return false;
|
|
12968
13081
|
}
|
|
12969
|
-
|
|
13082
|
+
if (opts.json) {
|
|
13083
|
+
deps.log(JSON.stringify({ ok: true, repo: opts.repo }, null, 2));
|
|
13084
|
+
} else {
|
|
13085
|
+
deps.log(`wiki published for ${opts.repo} (scoped token, expired/discarded)`);
|
|
13086
|
+
}
|
|
12970
13087
|
return true;
|
|
12971
13088
|
}
|
|
12972
13089
|
|
|
12973
13090
|
// src/wiki-publish-git.ts
|
|
12974
13091
|
var import_node_child_process8 = require("node:child_process");
|
|
12975
|
-
var
|
|
13092
|
+
var import_node_fs14 = require("node:fs");
|
|
12976
13093
|
var import_node_os5 = require("node:os");
|
|
12977
|
-
var
|
|
13094
|
+
var import_node_path15 = require("node:path");
|
|
12978
13095
|
var WIKI_STAMP_FILE = ".wiki-state.json";
|
|
12979
13096
|
function redactWikiSecrets(msg) {
|
|
12980
13097
|
return msg.replace(/x-access-token:[^@\s]+@/gi, "x-access-token:***@").replace(/(AUTHORIZATION:\s*basic\s+)[A-Za-z0-9+/=]+/gi, "$1***");
|
|
@@ -12986,6 +13103,7 @@ function publishWikiViaGit(deps, opts) {
|
|
|
12986
13103
|
const dir = deps.mkdtemp();
|
|
12987
13104
|
const url = `https://github.com/${opts.repo}.wiki.git`;
|
|
12988
13105
|
const authArgs = wikiAuthArgs(opts.token);
|
|
13106
|
+
const json = opts.json ?? false;
|
|
12989
13107
|
try {
|
|
12990
13108
|
deps.gitRun(["init", "-q"], dir);
|
|
12991
13109
|
deps.gitRun(["remote", "add", "origin", url], dir);
|
|
@@ -12995,20 +13113,20 @@ function publishWikiViaGit(deps, opts) {
|
|
|
12995
13113
|
let copied = 0;
|
|
12996
13114
|
for (const f of deps.readdir(opts.pagesDir)) {
|
|
12997
13115
|
if (f.endsWith(".md")) {
|
|
12998
|
-
deps.copyFile((0,
|
|
13116
|
+
deps.copyFile((0, import_node_path15.join)(opts.pagesDir, f), (0, import_node_path15.join)(dir, f));
|
|
12999
13117
|
copied++;
|
|
13000
13118
|
}
|
|
13001
13119
|
}
|
|
13002
13120
|
if (!copied) {
|
|
13003
|
-
deps.err(`wiki publish: no .md pages found in ${opts.pagesDir}`);
|
|
13121
|
+
if (!json) deps.err(`wiki publish: no .md pages found in ${opts.pagesDir}`);
|
|
13004
13122
|
return false;
|
|
13005
13123
|
}
|
|
13006
|
-
if (deps.fileExists((0,
|
|
13007
|
-
deps.copyFile((0,
|
|
13124
|
+
if (deps.fileExists((0, import_node_path15.join)(opts.pagesDir, WIKI_STAMP_FILE))) {
|
|
13125
|
+
deps.copyFile((0, import_node_path15.join)(opts.pagesDir, WIKI_STAMP_FILE), (0, import_node_path15.join)(dir, WIKI_STAMP_FILE));
|
|
13008
13126
|
}
|
|
13009
13127
|
deps.gitRun(["add", "-A"], dir);
|
|
13010
13128
|
if (deps.gitProbe(["diff", "--cached", "--quiet"], dir)) {
|
|
13011
|
-
deps.log("wiki publish: already up to date");
|
|
13129
|
+
if (!json) deps.log("wiki publish: already up to date");
|
|
13012
13130
|
return true;
|
|
13013
13131
|
}
|
|
13014
13132
|
deps.gitRun(
|
|
@@ -13026,15 +13144,15 @@ function publishWikiViaGit(deps, opts) {
|
|
|
13026
13144
|
try {
|
|
13027
13145
|
deps.gitRun([...authArgs, "push", "origin", "HEAD:master"], dir);
|
|
13028
13146
|
} catch (e) {
|
|
13029
|
-
deps.err(
|
|
13147
|
+
if (!json) deps.err(
|
|
13030
13148
|
`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
13149
|
);
|
|
13032
13150
|
return false;
|
|
13033
13151
|
}
|
|
13034
|
-
deps.log(`wiki publish: published ${copied} page(s) to ${opts.repo} wiki`);
|
|
13152
|
+
if (!json) deps.log(`wiki publish: published ${copied} page(s) to ${opts.repo} wiki`);
|
|
13035
13153
|
return true;
|
|
13036
13154
|
} catch (e) {
|
|
13037
|
-
deps.err(`wiki publish failed: ${redactWikiSecrets(e.message)}`);
|
|
13155
|
+
if (!json) deps.err(`wiki publish failed: ${redactWikiSecrets(e.message)}`);
|
|
13038
13156
|
return false;
|
|
13039
13157
|
} finally {
|
|
13040
13158
|
deps.rm(dir);
|
|
@@ -13054,7 +13172,7 @@ function readWikiStampViaGit(deps, opts) {
|
|
|
13054
13172
|
return { ok: true, stamp: {} };
|
|
13055
13173
|
}
|
|
13056
13174
|
deps.gitRun(["reset", "--hard", "FETCH_HEAD"], dir);
|
|
13057
|
-
const stampPath = (0,
|
|
13175
|
+
const stampPath = (0, import_node_path15.join)(dir, WIKI_STAMP_FILE);
|
|
13058
13176
|
if (!deps.fileExists(stampPath)) return { ok: true, stamp: {} };
|
|
13059
13177
|
const parsed = JSON.parse(deps.readFile(stampPath));
|
|
13060
13178
|
return { ok: true, stamp: parsed && typeof parsed === "object" ? parsed : {} };
|
|
@@ -13066,12 +13184,12 @@ function readWikiStampViaGit(deps, opts) {
|
|
|
13066
13184
|
}
|
|
13067
13185
|
function realWikiGitPublishDeps() {
|
|
13068
13186
|
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,
|
|
13187
|
+
mkdtemp: () => (0, import_node_fs14.mkdtempSync)((0, import_node_path15.join)((0, import_node_os5.tmpdir)(), "wiki-")),
|
|
13188
|
+
readdir: (dir) => (0, import_node_fs14.readdirSync)(dir),
|
|
13189
|
+
copyFile: (src, dest) => (0, import_node_fs14.copyFileSync)(src, dest),
|
|
13190
|
+
fileExists: (p) => (0, import_node_fs14.existsSync)(p),
|
|
13191
|
+
readFile: (p) => (0, import_node_fs14.readFileSync)(p, "utf8"),
|
|
13192
|
+
rm: (p) => (0, import_node_fs14.rmSync)(p, { recursive: true, force: true }),
|
|
13075
13193
|
gitProbe: (args, cwd) => {
|
|
13076
13194
|
try {
|
|
13077
13195
|
(0, import_node_child_process8.execFileSync)("git", args, { cwd, stdio: "pipe" });
|
|
@@ -13090,8 +13208,8 @@ function realWikiGitPublishDeps() {
|
|
|
13090
13208
|
|
|
13091
13209
|
// src/wiki-pages.ts
|
|
13092
13210
|
var import_node_crypto4 = require("node:crypto");
|
|
13093
|
-
var
|
|
13094
|
-
var
|
|
13211
|
+
var import_node_fs15 = require("node:fs");
|
|
13212
|
+
var import_node_path16 = require("node:path");
|
|
13095
13213
|
var MARKER_RE = /<!--\s*wiki:page\s+([^>]*?)-->/g;
|
|
13096
13214
|
var attr = (raw, key) => {
|
|
13097
13215
|
const m = new RegExp(`${key}\\s*=\\s*"([^"]*)"`).exec(raw);
|
|
@@ -13117,15 +13235,15 @@ var DURABLE_GLOBS = [
|
|
|
13117
13235
|
"docs/org-readme.md"
|
|
13118
13236
|
];
|
|
13119
13237
|
var walkMarkdown = (root, rel) => {
|
|
13120
|
-
const abs = (0,
|
|
13238
|
+
const abs = (0, import_node_path16.join)(root, rel);
|
|
13121
13239
|
let st;
|
|
13122
13240
|
try {
|
|
13123
|
-
st = (0,
|
|
13241
|
+
st = (0, import_node_fs15.statSync)(abs);
|
|
13124
13242
|
} catch {
|
|
13125
13243
|
return [];
|
|
13126
13244
|
}
|
|
13127
13245
|
if (st.isFile()) return rel.endsWith(".md") ? [rel] : [];
|
|
13128
|
-
return (0,
|
|
13246
|
+
return (0, import_node_fs15.readdirSync)(abs).flatMap((name) => walkMarkdown(root, (0, import_node_path16.join)(rel, name)));
|
|
13129
13247
|
};
|
|
13130
13248
|
function collectFeaturePages({ listDocs, readDoc }) {
|
|
13131
13249
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -13144,7 +13262,7 @@ function fsReaders(rootDir) {
|
|
|
13144
13262
|
listDocs: () => DURABLE_GLOBS.flatMap((g) => walkMarkdown(rootDir, g)),
|
|
13145
13263
|
readDoc: (p) => {
|
|
13146
13264
|
try {
|
|
13147
|
-
return (0,
|
|
13265
|
+
return (0, import_node_fs15.readFileSync)((0, import_node_path16.join)(rootDir, p), "utf8");
|
|
13148
13266
|
} catch {
|
|
13149
13267
|
return "";
|
|
13150
13268
|
}
|
|
@@ -14473,7 +14591,12 @@ async function targetRepo(deps, opts) {
|
|
|
14473
14591
|
return opts.repo ?? repoOf(await vaultSlug(deps, opts));
|
|
14474
14592
|
}
|
|
14475
14593
|
async function secretsWhere(deps, opts) {
|
|
14476
|
-
|
|
14594
|
+
const p = vaultPointer(await vaultSlug(deps, opts));
|
|
14595
|
+
if (opts.json) {
|
|
14596
|
+
deps.log(JSON.stringify(p, null, 2));
|
|
14597
|
+
return;
|
|
14598
|
+
}
|
|
14599
|
+
deps.log(formatVaultPointer(p));
|
|
14477
14600
|
}
|
|
14478
14601
|
async function readErr(res) {
|
|
14479
14602
|
try {
|
|
@@ -14536,6 +14659,10 @@ async function secretsList(deps, opts) {
|
|
|
14536
14659
|
return;
|
|
14537
14660
|
}
|
|
14538
14661
|
const { secrets } = await res.json();
|
|
14662
|
+
if (opts.json) {
|
|
14663
|
+
deps.log(JSON.stringify({ secrets: secrets ?? [] }, null, 2));
|
|
14664
|
+
return;
|
|
14665
|
+
}
|
|
14539
14666
|
deps.log(formatSecretList(secrets ?? []));
|
|
14540
14667
|
}
|
|
14541
14668
|
var CAPABILITIES_TIMEOUT_MS = 2e4;
|
|
@@ -14751,6 +14878,13 @@ function requiredRuntimeSecretNames(stage2, contract, opts = {}) {
|
|
|
14751
14878
|
function stageKey2(stage2, key) {
|
|
14752
14879
|
return key.includes("/") ? key : `${stage2}/${key}`;
|
|
14753
14880
|
}
|
|
14881
|
+
function keyDeclaredPerStage(declaredSecrets, key) {
|
|
14882
|
+
if (!declaredSecrets) return void 0;
|
|
14883
|
+
const entry = declaredSecrets[key];
|
|
14884
|
+
if (!entry) return void 0;
|
|
14885
|
+
if (!Array.isArray(entry.stages) || entry.stages.length === 0) return false;
|
|
14886
|
+
return true;
|
|
14887
|
+
}
|
|
14754
14888
|
async function secretsPreflight(deps, opts) {
|
|
14755
14889
|
const repo = await targetRepo(deps, opts);
|
|
14756
14890
|
const qs = new URLSearchParams({ repo }).toString();
|
|
@@ -14771,9 +14905,23 @@ async function secretsPreflight(deps, opts) {
|
|
|
14771
14905
|
}
|
|
14772
14906
|
const { secrets } = await res.json();
|
|
14773
14907
|
const present = new Set((secrets ?? []).map((s) => s.key));
|
|
14774
|
-
const missing = opts.required.filter(
|
|
14775
|
-
|
|
14776
|
-
|
|
14908
|
+
const missing = opts.required.filter((key) => {
|
|
14909
|
+
const perStage = keyDeclaredPerStage(opts.declaredSecrets, key);
|
|
14910
|
+
if (key.includes("/")) {
|
|
14911
|
+
return !present.has(key);
|
|
14912
|
+
}
|
|
14913
|
+
if (perStage === false) {
|
|
14914
|
+
return !present.has(key);
|
|
14915
|
+
}
|
|
14916
|
+
if (perStage === true) {
|
|
14917
|
+
return !present.has(stageKey2(opts.stage, key));
|
|
14918
|
+
}
|
|
14919
|
+
return !present.has(stageKey2(opts.stage, key)) && !present.has(key);
|
|
14920
|
+
});
|
|
14921
|
+
if (opts.json) {
|
|
14922
|
+
deps.log(JSON.stringify({ ok: missing.length === 0, stage: opts.stage, required: opts.required, missing, present: Array.from(present) }, null, 2));
|
|
14923
|
+
return missing.length === 0;
|
|
14924
|
+
}
|
|
14777
14925
|
if (missing.length) {
|
|
14778
14926
|
deps.log(`missing ${missing.map((key) => key.includes("/") ? key : `${stageKey2(opts.stage, key)} (and no stageless ${key})`).join(", ")}`);
|
|
14779
14927
|
return false;
|
|
@@ -14998,9 +15146,6 @@ async function secretsImportRailsCredentials(deps, opts) {
|
|
|
14998
15146
|
}
|
|
14999
15147
|
return true;
|
|
15000
15148
|
}
|
|
15001
|
-
async function secretsEdit(deps, key, opts) {
|
|
15002
|
-
return secretsSet(deps, key, opts);
|
|
15003
|
-
}
|
|
15004
15149
|
async function secretsRemove(deps, key, opts) {
|
|
15005
15150
|
if (!isValidSecretKey(key)) return deps.err(`invalid secret key ${JSON.stringify(key)}`);
|
|
15006
15151
|
const repo = await targetRepo(deps, opts);
|
|
@@ -15096,8 +15241,12 @@ async function secretsCopy(deps, opts) {
|
|
|
15096
15241
|
}
|
|
15097
15242
|
return true;
|
|
15098
15243
|
}
|
|
15244
|
+
function resolveSpawnTarget(command, args, platform2 = process.platform) {
|
|
15245
|
+
return platform2 === "win32" ? { cmd: "cmd.exe", args: ["/d", "/s", "/c", command, ...args] } : { cmd: command, args };
|
|
15246
|
+
}
|
|
15099
15247
|
function defaultSpawn(command, args, env) {
|
|
15100
|
-
const
|
|
15248
|
+
const target = resolveSpawnTarget(command, args);
|
|
15249
|
+
const r = (0, import_node_child_process9.spawnSync)(target.cmd, target.args, { stdio: "inherit", env });
|
|
15101
15250
|
if (r.error) throw r.error;
|
|
15102
15251
|
return r.status ?? 1;
|
|
15103
15252
|
}
|
|
@@ -15157,8 +15306,8 @@ async function secretsUse(deps, key, opts) {
|
|
|
15157
15306
|
}
|
|
15158
15307
|
|
|
15159
15308
|
// src/secrets-commands.ts
|
|
15160
|
-
var
|
|
15161
|
-
var
|
|
15309
|
+
var import_node_fs16 = require("node:fs");
|
|
15310
|
+
var import_node_path17 = require("node:path");
|
|
15162
15311
|
var RAILS_CREDENTIALS_DECRYPT_TIMEOUT_MS = 3e4;
|
|
15163
15312
|
var DEFAULT_RAILS_CREDENTIALS_FILE = "config/credentials.yml.enc";
|
|
15164
15313
|
var DEFAULT_RAILS_MASTER_KEY_FILE = "config/master.key";
|
|
@@ -15166,18 +15315,18 @@ function collectMap(value, previous = []) {
|
|
|
15166
15315
|
return [...previous, value];
|
|
15167
15316
|
}
|
|
15168
15317
|
async function decryptRailsCredentials(input) {
|
|
15169
|
-
const appDir = (0,
|
|
15318
|
+
const appDir = (0, import_node_path17.resolve)(input.appDir ?? process.cwd());
|
|
15170
15319
|
const credentialsFile = input.credentialsFile ?? DEFAULT_RAILS_CREDENTIALS_FILE;
|
|
15171
15320
|
const masterKeyFile = input.masterKeyFile ?? DEFAULT_RAILS_MASTER_KEY_FILE;
|
|
15172
|
-
const credentialsPath = (0,
|
|
15173
|
-
const masterKeyPath = (0,
|
|
15321
|
+
const credentialsPath = (0, import_node_path17.resolve)(appDir, credentialsFile);
|
|
15322
|
+
const masterKeyPath = (0, import_node_path17.resolve)(appDir, masterKeyFile);
|
|
15174
15323
|
const env = {
|
|
15175
15324
|
...process.env,
|
|
15176
15325
|
MMI_RAILS_CREDENTIALS_FILE: credentialsPath,
|
|
15177
15326
|
MMI_RAILS_MASTER_KEY_FILE: masterKeyPath
|
|
15178
15327
|
};
|
|
15179
|
-
if ((0,
|
|
15180
|
-
env.RAILS_MASTER_KEY = (0,
|
|
15328
|
+
if ((0, import_node_fs16.existsSync)(masterKeyPath)) {
|
|
15329
|
+
env.RAILS_MASTER_KEY = (0, import_node_fs16.readFileSync)(masterKeyPath, "utf8").trim();
|
|
15181
15330
|
}
|
|
15182
15331
|
const script = [
|
|
15183
15332
|
'require "json"',
|
|
@@ -15231,8 +15380,8 @@ async function withSecrets(run) {
|
|
|
15231
15380
|
}
|
|
15232
15381
|
function registerSecretsCommands(program3) {
|
|
15233
15382
|
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)));
|
|
15383
|
+
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)));
|
|
15384
|
+
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
15385
|
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
15386
|
if (!await secretsFind(d, intent, o)) process.exitCode = 1;
|
|
15238
15387
|
}));
|
|
@@ -15243,13 +15392,13 @@ function registerSecretsCommands(program3) {
|
|
|
15243
15392
|
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
15393
|
let body;
|
|
15245
15394
|
try {
|
|
15246
|
-
body = (0,
|
|
15395
|
+
body = (0, import_node_fs16.readFileSync)((0, import_node_path17.resolve)(o.file), "utf8");
|
|
15247
15396
|
} catch (e) {
|
|
15248
15397
|
return fail(`secrets org-catalog: cannot read --file ${o.file}: ${e.message}`);
|
|
15249
15398
|
}
|
|
15250
15399
|
if (!await secretsOrgCatalogSet(d, body)) process.exitCode = 1;
|
|
15251
15400
|
}));
|
|
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) => {
|
|
15401
|
+
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
15402
|
if (!["dev", "rc", "main"].includes(o.stage)) {
|
|
15254
15403
|
return fail("secrets preflight: --stage must be dev, rc, or main");
|
|
15255
15404
|
}
|
|
@@ -15270,7 +15419,7 @@ function registerSecretsCommands(program3) {
|
|
|
15270
15419
|
if (!o.required?.length && centralContainer && meta && !hasRuntimeSecretContract(meta.requiredRuntimeSecrets)) {
|
|
15271
15420
|
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
15421
|
}
|
|
15273
|
-
const ok = await secretsPreflight(d, { repo: o.repo, stage: o.stage, required });
|
|
15422
|
+
const ok = await secretsPreflight(d, { repo: o.repo, stage: o.stage, required, json: o.json, declaredSecrets: meta?.secrets });
|
|
15274
15423
|
if (!ok) process.exitCode = 1;
|
|
15275
15424
|
});
|
|
15276
15425
|
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 +15434,12 @@ function registerSecretsCommands(program3) {
|
|
|
15285
15434
|
const ok = await secretsVerify(d, key, o);
|
|
15286
15435
|
if (!ok) process.exitCode = 1;
|
|
15287
15436
|
}));
|
|
15288
|
-
|
|
15437
|
+
const setHandler = (key, o) => withSecrets(async (d) => {
|
|
15289
15438
|
const ok = await secretsSet(d, key, o);
|
|
15290
15439
|
if (!ok) process.exitCode = 1;
|
|
15291
|
-
})
|
|
15292
|
-
secrets.command("
|
|
15293
|
-
|
|
15294
|
-
if (!ok) process.exitCode = 1;
|
|
15295
|
-
}));
|
|
15440
|
+
});
|
|
15441
|
+
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);
|
|
15442
|
+
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
15443
|
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
15444
|
const stages = ["dev", "rc", "main"];
|
|
15298
15445
|
if (!stages.includes(o.from) || !stages.includes(o.to)) {
|
|
@@ -15318,7 +15465,7 @@ function registerSecretsCommands(program3) {
|
|
|
15318
15465
|
{
|
|
15319
15466
|
...d,
|
|
15320
15467
|
decryptRailsCredentials,
|
|
15321
|
-
removeFile: (path2) => (0,
|
|
15468
|
+
removeFile: (path2) => (0, import_node_fs16.unlinkSync)((0, import_node_path17.resolve)(o.appDir ?? process.cwd(), path2))
|
|
15322
15469
|
},
|
|
15323
15470
|
{
|
|
15324
15471
|
repo: o.repo,
|
|
@@ -15407,10 +15554,10 @@ function registerEdgeCommands(program3) {
|
|
|
15407
15554
|
}
|
|
15408
15555
|
|
|
15409
15556
|
// src/doctor-run.ts
|
|
15410
|
-
var
|
|
15557
|
+
var import_node_fs20 = require("node:fs");
|
|
15411
15558
|
var import_node_child_process11 = require("node:child_process");
|
|
15412
15559
|
var import_promises2 = require("node:fs/promises");
|
|
15413
|
-
var
|
|
15560
|
+
var import_node_path20 = require("node:path");
|
|
15414
15561
|
var import_node_os7 = require("node:os");
|
|
15415
15562
|
|
|
15416
15563
|
// src/plugin-guard.ts
|
|
@@ -15433,13 +15580,14 @@ function buildGuardSessionStartLine(state, opts = {}) {
|
|
|
15433
15580
|
|
|
15434
15581
|
// src/cursor-plugin-seed.ts
|
|
15435
15582
|
var import_node_child_process10 = require("node:child_process");
|
|
15436
|
-
var
|
|
15583
|
+
var import_node_fs17 = require("node:fs");
|
|
15437
15584
|
var import_node_os6 = require("node:os");
|
|
15438
|
-
var
|
|
15585
|
+
var import_node_path18 = require("node:path");
|
|
15439
15586
|
var import_node_util7 = require("node:util");
|
|
15440
15587
|
|
|
15441
15588
|
// src/doctor.ts
|
|
15442
15589
|
var GH_PROJECT_LOGIN_FIX = 'run: gh auth login --hostname github.com --git-protocol https --web --scopes "project"';
|
|
15590
|
+
var GITHUB_AUTH_LABEL = "GitHub auth identity (gh ops)";
|
|
15443
15591
|
var AWS_CROSS_ACCOUNT_LABEL = "AWS cross-account identity (master-agent audits)";
|
|
15444
15592
|
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
15593
|
var MMI_AGENTIC_ONBOARDING_GUIDE = {
|
|
@@ -15453,7 +15601,7 @@ function buildGithubAuthCheck(input) {
|
|
|
15453
15601
|
const ok = Boolean(input.login?.trim());
|
|
15454
15602
|
return {
|
|
15455
15603
|
ok,
|
|
15456
|
-
label:
|
|
15604
|
+
label: GITHUB_AUTH_LABEL,
|
|
15457
15605
|
fix: input.ghInstalled ? GH_PROJECT_LOGIN_FIX : `install GitHub CLI (https://cli.github.com), then: ${GH_PROJECT_LOGIN_FIX.replace(/^run: /, "")}`
|
|
15458
15606
|
};
|
|
15459
15607
|
}
|
|
@@ -15631,6 +15779,136 @@ function buildRepoLocalWorktreeCheck(input) {
|
|
|
15631
15779
|
fix: REPO_LOCAL_WORKTREE_FIX
|
|
15632
15780
|
};
|
|
15633
15781
|
}
|
|
15782
|
+
var LOCKFILE_OPTIONALS_LABEL = "npm lockfile keeps optionalDependencies across platforms (Windows lockfile drop #2594)";
|
|
15783
|
+
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)";
|
|
15784
|
+
function buildLockfileOptionalsCheck(input) {
|
|
15785
|
+
const base = {
|
|
15786
|
+
ok: true,
|
|
15787
|
+
label: LOCKFILE_OPTIONALS_LABEL,
|
|
15788
|
+
fix: LOCKFILE_OPTIONALS_FIX,
|
|
15789
|
+
missingOptionals: []
|
|
15790
|
+
};
|
|
15791
|
+
if (!input.isOrgRepo) return base;
|
|
15792
|
+
const optionalDeps = input.packageJsonOptionalDeps ?? {};
|
|
15793
|
+
const packages = input.lockfilePackages ?? {};
|
|
15794
|
+
const missingOptionals = Object.keys(optionalDeps).filter((name) => !(`node_modules/${name}` in packages));
|
|
15795
|
+
if (missingOptionals.length === 0) return base;
|
|
15796
|
+
return {
|
|
15797
|
+
...base,
|
|
15798
|
+
ok: false,
|
|
15799
|
+
missingOptionals,
|
|
15800
|
+
detail: `${missingOptionals.length} optionalDependency entr${missingOptionals.length === 1 ? "y" : "ies"} missing from the lockfile packages map: ${missingOptionals.join(", ")}`
|
|
15801
|
+
};
|
|
15802
|
+
}
|
|
15803
|
+
var FILE_ENCODING_LABEL = "managed file encoding (UTF-8, no BOM, no mojibake)";
|
|
15804
|
+
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';
|
|
15805
|
+
function inspectFileEncoding(bytes) {
|
|
15806
|
+
const bom = bytes.length >= 3 && bytes[0] === 239 && bytes[1] === 187 && bytes[2] === 191;
|
|
15807
|
+
let invalidUtf8 = false;
|
|
15808
|
+
let replacementChars = false;
|
|
15809
|
+
try {
|
|
15810
|
+
const text = new TextDecoder("utf-8", { fatal: true }).decode(bytes);
|
|
15811
|
+
replacementChars = text.includes("\uFFFD");
|
|
15812
|
+
} catch {
|
|
15813
|
+
invalidUtf8 = true;
|
|
15814
|
+
replacementChars = new TextDecoder("utf-8", { fatal: false }).decode(bytes).includes("\uFFFD");
|
|
15815
|
+
}
|
|
15816
|
+
return { bom, invalidUtf8, replacementChars };
|
|
15817
|
+
}
|
|
15818
|
+
function buildManagedFileEncodingCheck(input) {
|
|
15819
|
+
const base = {
|
|
15820
|
+
ok: true,
|
|
15821
|
+
label: FILE_ENCODING_LABEL,
|
|
15822
|
+
fix: FILE_ENCODING_FIX,
|
|
15823
|
+
findings: []
|
|
15824
|
+
};
|
|
15825
|
+
if (!input.isOrgRepo) return base;
|
|
15826
|
+
const defective = input.findings.filter((f) => f.bom || f.invalidUtf8 || f.replacementChars);
|
|
15827
|
+
if (defective.length === 0) return base;
|
|
15828
|
+
return {
|
|
15829
|
+
...base,
|
|
15830
|
+
ok: false,
|
|
15831
|
+
severityOverride: "advisory",
|
|
15832
|
+
findings: defective,
|
|
15833
|
+
detail: `${defective.length} managed file(s) not clean UTF-8: ${defective.map((f) => f.path).join(", ")}`
|
|
15834
|
+
};
|
|
15835
|
+
}
|
|
15836
|
+
var LOCAL_BEHIND_ORIGIN_LABEL = "local release branches current with origin (development/main/rc)";
|
|
15837
|
+
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)";
|
|
15838
|
+
var RELEASE_TRACKING_BRANCHES = /* @__PURE__ */ new Set(["development", "main", "master", "rc", "release"]);
|
|
15839
|
+
function isReleaseTrackingBranch(name) {
|
|
15840
|
+
if (!name) return false;
|
|
15841
|
+
const base = name.split("/").pop() ?? name;
|
|
15842
|
+
if (RELEASE_TRACKING_BRANCHES.has(base)) return true;
|
|
15843
|
+
return name.split("/")[0] === "release";
|
|
15844
|
+
}
|
|
15845
|
+
function buildStaleLocalBranchCheck(input) {
|
|
15846
|
+
const base = {
|
|
15847
|
+
ok: true,
|
|
15848
|
+
label: LOCAL_BEHIND_ORIGIN_LABEL,
|
|
15849
|
+
fix: LOCAL_BEHIND_ORIGIN_FIX,
|
|
15850
|
+
stale: []
|
|
15851
|
+
};
|
|
15852
|
+
if (!input.isOrgRepo) return base;
|
|
15853
|
+
const stale = input.branches.filter((b) => isReleaseTrackingBranch(b.name) && b.behind > 0 && b.ahead === 0).map((b) => ({ name: b.name, behind: b.behind }));
|
|
15854
|
+
if (stale.length === 0) return base;
|
|
15855
|
+
return {
|
|
15856
|
+
...base,
|
|
15857
|
+
ok: false,
|
|
15858
|
+
severityOverride: "advisory",
|
|
15859
|
+
stale,
|
|
15860
|
+
detail: `${stale.length} release branch(es) behind origin: ${stale.map((s) => `${s.name} (-${s.behind})`).join(", ")}`
|
|
15861
|
+
};
|
|
15862
|
+
}
|
|
15863
|
+
var REDACTOR_LIVENESS_LABEL = "secret-redact hook liveness (PostToolUse redactor crashes)";
|
|
15864
|
+
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';
|
|
15865
|
+
function summarizeRedactorFailures(lines) {
|
|
15866
|
+
const parsed = [];
|
|
15867
|
+
for (const raw of lines) {
|
|
15868
|
+
const trimmed = raw.trim();
|
|
15869
|
+
if (!trimmed) continue;
|
|
15870
|
+
try {
|
|
15871
|
+
parsed.push(JSON.parse(trimmed));
|
|
15872
|
+
} catch {
|
|
15873
|
+
}
|
|
15874
|
+
}
|
|
15875
|
+
let sessionStartIdx = -1;
|
|
15876
|
+
for (let i = 0; i < parsed.length; i += 1) {
|
|
15877
|
+
if (parsed[i].event === "SessionStart") sessionStartIdx = i;
|
|
15878
|
+
}
|
|
15879
|
+
const start = sessionStartIdx >= 0 ? sessionStartIdx : 0;
|
|
15880
|
+
let failedCount = 0;
|
|
15881
|
+
let lastFailedTs;
|
|
15882
|
+
let lastFailedAction;
|
|
15883
|
+
for (let i = start; i < parsed.length; i += 1) {
|
|
15884
|
+
const e = parsed[i];
|
|
15885
|
+
if (e.script === "secret-redact" && e.outcome === "failed") {
|
|
15886
|
+
failedCount += 1;
|
|
15887
|
+
lastFailedTs = e.ts;
|
|
15888
|
+
lastFailedAction = e.action;
|
|
15889
|
+
}
|
|
15890
|
+
}
|
|
15891
|
+
return { failedCount, lastFailedTs, lastFailedAction };
|
|
15892
|
+
}
|
|
15893
|
+
function buildRedactorLivenessCheck(input) {
|
|
15894
|
+
const base = {
|
|
15895
|
+
ok: true,
|
|
15896
|
+
label: REDACTOR_LIVENESS_LABEL,
|
|
15897
|
+
fix: REDACTOR_LIVENESS_FIX,
|
|
15898
|
+
failedCount: 0
|
|
15899
|
+
};
|
|
15900
|
+
if (!input.isOrgRepo) return base;
|
|
15901
|
+
if (input.failedCount === 0) return base;
|
|
15902
|
+
const when = input.lastFailedTs ? ` (most recent ${input.lastFailedTs})` : "";
|
|
15903
|
+
const reason = input.lastFailedAction ? `: ${input.lastFailedAction}` : "";
|
|
15904
|
+
return {
|
|
15905
|
+
ok: false,
|
|
15906
|
+
label: REDACTOR_LIVENESS_LABEL,
|
|
15907
|
+
fix: `secret-redact crashed ${input.failedCount} time(s) this session${when}${reason}. ${REDACTOR_LIVENESS_FIX}`,
|
|
15908
|
+
failedCount: input.failedCount,
|
|
15909
|
+
lastFailedTs: input.lastFailedTs
|
|
15910
|
+
};
|
|
15911
|
+
}
|
|
15634
15912
|
var SCRATCH_GC_LABEL = "scratch housekeeping (tmp/, plans/, browser artifacts)";
|
|
15635
15913
|
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
15914
|
function buildScratchGcCheck(plan) {
|
|
@@ -16089,7 +16367,16 @@ function buildDoctorJsonPayload(input) {
|
|
|
16089
16367
|
return {
|
|
16090
16368
|
ok: doctorExitCode(input.checks) === 0,
|
|
16091
16369
|
hasAdvisories: input.checks.some((c) => !c.ok && isAdvisoryDoctorCheck(c)),
|
|
16092
|
-
checks: input.checks
|
|
16370
|
+
checks: input.checks.map((check) => {
|
|
16371
|
+
const metadata = doctorMetadataForCheck(check);
|
|
16372
|
+
return {
|
|
16373
|
+
...check,
|
|
16374
|
+
...metadata,
|
|
16375
|
+
label: check.label,
|
|
16376
|
+
metadata,
|
|
16377
|
+
durationMs: check.durationMs ?? 0
|
|
16378
|
+
};
|
|
16379
|
+
}),
|
|
16093
16380
|
updateReport: input.updateReport,
|
|
16094
16381
|
...input.resources.length ? { resources: input.resources } : {}
|
|
16095
16382
|
};
|
|
@@ -16148,6 +16435,25 @@ function buildInstalledPluginVersionCheck(input) {
|
|
|
16148
16435
|
staleSurfaces: stale
|
|
16149
16436
|
};
|
|
16150
16437
|
}
|
|
16438
|
+
var CLI_VERSION_DRIFT_LABEL = "mmi-cli version drift (installed vs latest published)";
|
|
16439
|
+
var CLI_VERSION_DRIFT_FIX = "run: mmi-cli doctor --apply --no-repo-writes (or npm install -g @mutmutco/cli@latest)";
|
|
16440
|
+
function buildCliVersionDriftCheck(input) {
|
|
16441
|
+
const base = {
|
|
16442
|
+
ok: true,
|
|
16443
|
+
label: CLI_VERSION_DRIFT_LABEL,
|
|
16444
|
+
fix: CLI_VERSION_DRIFT_FIX,
|
|
16445
|
+
currentVersion: input.currentVersion,
|
|
16446
|
+
releasedVersion: input.releasedVersion
|
|
16447
|
+
};
|
|
16448
|
+
if (!input.releasedVersion || !isSemverVersion(input.releasedVersion) || !isSemverVersion(input.currentVersion)) return base;
|
|
16449
|
+
if (compareVersions(input.currentVersion, input.releasedVersion) >= 0) return base;
|
|
16450
|
+
return {
|
|
16451
|
+
...base,
|
|
16452
|
+
ok: false,
|
|
16453
|
+
label: `${CLI_VERSION_DRIFT_LABEL}: ${input.currentVersion} < ${input.releasedVersion}`,
|
|
16454
|
+
fix: `${CLI_VERSION_DRIFT_FIX} (installed ${input.currentVersion} is behind published ${input.releasedVersion})`
|
|
16455
|
+
};
|
|
16456
|
+
}
|
|
16151
16457
|
var OPENCODE_VERSION_LABEL = "installed OpenCode MMI adapter version (vs latest release)";
|
|
16152
16458
|
function buildOpencodeVersionCheck(input) {
|
|
16153
16459
|
const fix = pluginRecoveryFix("opencode");
|
|
@@ -16445,13 +16751,13 @@ function buildCursorHookCliCheck(input) {
|
|
|
16445
16751
|
return base;
|
|
16446
16752
|
}
|
|
16447
16753
|
var HUB_COMPAT_FIX = "update mmi-cli (npm i -g @mutmutco/cli) / refresh the MMI plugin, then rerun doctor";
|
|
16754
|
+
var HUB_COMPAT_LABEL = "Hub compatibility (client version vs Hub minimum)";
|
|
16448
16755
|
function buildHubCompatCheck(input) {
|
|
16449
|
-
const label = "Hub compatibility (client version vs Hub minimum)";
|
|
16450
16756
|
const min = input.versionInfo?.minClientVersion;
|
|
16451
16757
|
if (!input.isOrgRepo || !min || !parseSemver(min) || !parseSemver(input.installedVersion)) {
|
|
16452
|
-
return { ok: true, label, fix: HUB_COMPAT_FIX };
|
|
16758
|
+
return { ok: true, label: HUB_COMPAT_LABEL, fix: HUB_COMPAT_FIX };
|
|
16453
16759
|
}
|
|
16454
|
-
return { ok: versionAtLeast(input.installedVersion, min), label: `${
|
|
16760
|
+
return { ok: versionAtLeast(input.installedVersion, min), label: `${HUB_COMPAT_LABEL}: requires >= ${min}`, fix: HUB_COMPAT_FIX };
|
|
16455
16761
|
}
|
|
16456
16762
|
var HUB_DEPLOY_FRESHNESS_LABEL = "Hub deploy freshness (x-hub-version vs CLI / release)";
|
|
16457
16763
|
var HUB_DEPLOY_FRESHNESS_FIX = "prod Hub updates on /release (MMI-Hub is direct-track); lag vs development is expected between releases";
|
|
@@ -16486,8 +16792,6 @@ function buildHubDeployFreshnessCheck(input) {
|
|
|
16486
16792
|
staleAgainst
|
|
16487
16793
|
};
|
|
16488
16794
|
}
|
|
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
16795
|
function textHasPlaywrightMcp(content) {
|
|
16492
16796
|
const normalized = content.replace(/\r\n/g, "\n");
|
|
16493
16797
|
return /@playwright\/mcp/.test(normalized) || /mcp_servers\.playwright/.test(normalized) || /"playwright"\s*:\s*\{/.test(normalized) || /\bmcpServers\b/.test(normalized);
|
|
@@ -16545,7 +16849,7 @@ function buildSelfUpdateHaltPayload(input) {
|
|
|
16545
16849
|
checks: input.checks
|
|
16546
16850
|
};
|
|
16547
16851
|
}
|
|
16548
|
-
var DOCTOR_SELF_UPDATE_HALT_EXIT_CODE =
|
|
16852
|
+
var DOCTOR_SELF_UPDATE_HALT_EXIT_CODE = 2;
|
|
16549
16853
|
var DOCTOR_SELF_UPDATE_HALT_SENTINEL = "MMI_DOCTOR_SELF_UPDATE_HALT";
|
|
16550
16854
|
function selfUpdateHaltSentinelLine(input) {
|
|
16551
16855
|
const to = input.report.releasedVersion ?? "latest";
|
|
@@ -16555,8 +16859,11 @@ var DOCTOR_POST_SELF_UPDATE_ENV = "MMI_DOCTOR_POST_SELF_UPDATE";
|
|
|
16555
16859
|
function buildSelfUpdateReexecArgs(opts) {
|
|
16556
16860
|
const args = ["doctor"];
|
|
16557
16861
|
if (opts.banner) args.push("--banner");
|
|
16862
|
+
if (opts.fast) args.push("--fast");
|
|
16558
16863
|
if (opts.preflight) args.push("--preflight");
|
|
16559
16864
|
if (opts.verbose) args.push("--verbose");
|
|
16865
|
+
if (opts.ci) args.push("--ci");
|
|
16866
|
+
if (opts.strictAdvisory) args.push("--strict-advisory");
|
|
16560
16867
|
if (opts.guide) args.push("--guide");
|
|
16561
16868
|
if (opts.json) args.push("--json");
|
|
16562
16869
|
if (opts.apply) args.push("--apply");
|
|
@@ -16577,28 +16884,12 @@ function preflightOutcome(input) {
|
|
|
16577
16884
|
function pluginAutonomousHaltLine(reloadHint) {
|
|
16578
16885
|
return `\u26A0 PLUGIN RELOAD REQUIRED \u2014 mmi:* skills and agent types are unavailable until you ${reloadHint}. Halt autonomous /grind and /build until then.`;
|
|
16579
16886
|
}
|
|
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
16887
|
function isAdvisoryDoctorCheck(check) {
|
|
16598
16888
|
if (check.severityOverride) return check.severityOverride === "advisory";
|
|
16599
|
-
return
|
|
16889
|
+
return (check.metadata ?? doctorMetadataForLabel(check.label)).severity === "advisory";
|
|
16600
16890
|
}
|
|
16601
|
-
function doctorExitCode(checks) {
|
|
16891
|
+
function doctorExitCode(checks, opts = {}) {
|
|
16892
|
+
if (opts.doctorError) return 2;
|
|
16602
16893
|
return checks.some((c) => !c.ok && !isAdvisoryDoctorCheck(c)) ? 1 : 0;
|
|
16603
16894
|
}
|
|
16604
16895
|
function renderPluginUpdateReportStaleOnly(report, allChecksPass = false) {
|
|
@@ -16624,6 +16915,7 @@ function doctorVerdictLine(input) {
|
|
|
16624
16915
|
const gaps = input.checks.filter((c) => !c.ok);
|
|
16625
16916
|
const hardGaps = gaps.filter((c) => !isAdvisoryDoctorCheck(c));
|
|
16626
16917
|
const softGaps = gaps.filter((c) => isAdvisoryDoctorCheck(c));
|
|
16918
|
+
const skipSuffix = input.skippedSlowChecks ? ` \xB7 skipped ${input.skippedSlowChecks} slow checks` : "";
|
|
16627
16919
|
if (input.shouldApply) {
|
|
16628
16920
|
if (hardGaps.length > 0) {
|
|
16629
16921
|
return `\u2717 ${hardGaps.length} repair${hardGaps.length === 1 ? "" : "s"} failed \u2014 see the items above.`;
|
|
@@ -16637,19 +16929,30 @@ function doctorVerdictLine(input) {
|
|
|
16637
16929
|
}
|
|
16638
16930
|
const attention = gaps.length;
|
|
16639
16931
|
if (attention > 0) {
|
|
16640
|
-
return `\u26A0 ${attention} item${attention === 1 ? " needs" : "s need"} attention \u2014 run \`mmi-cli doctor --apply\` to heal.`;
|
|
16932
|
+
return `\u26A0 ${attention} item${attention === 1 ? " needs" : "s need"} attention \u2014 run \`mmi-cli doctor --apply\` to heal${skipSuffix}.`;
|
|
16641
16933
|
}
|
|
16642
|
-
return
|
|
16934
|
+
return `\u2713 healthy \xB7 ${input.checks.length} checks \xB7 0 gaps${skipSuffix}`;
|
|
16935
|
+
}
|
|
16936
|
+
function doctorCheckSummaryLine(check) {
|
|
16937
|
+
const meta = doctorMetadataForCheck(check);
|
|
16938
|
+
return ` ${doctorCheckGlyph(check)} ${meta.id} \u2014 ${meta.protects.join("; ")} \u2014 ${check.fix}`;
|
|
16643
16939
|
}
|
|
16644
16940
|
function doctorHumanLines(input) {
|
|
16645
16941
|
const gaps = input.checks.filter((c) => !c.ok);
|
|
16942
|
+
const healthyCount = input.checks.length - gaps.length;
|
|
16646
16943
|
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}`)
|
|
16944
|
+
doctorVerdictLine({ checks: input.checks, shouldApply: input.shouldApply, healedCount: input.healedCount, skippedSlowChecks: input.skippedSlowChecks })
|
|
16651
16945
|
];
|
|
16652
|
-
if (gaps.length > 0) {
|
|
16946
|
+
if (gaps.length > 0 || input.verbose) {
|
|
16947
|
+
lines.push("", "Checks");
|
|
16948
|
+
if (input.verbose) {
|
|
16949
|
+
lines.push(...input.checks.map((c) => ` ${doctorCheckGlyph(c)} ${doctorMetadataForCheck(c).id} \u2014 ${c.label}`));
|
|
16950
|
+
} else {
|
|
16951
|
+
lines.push(...gaps.map(doctorCheckSummaryLine));
|
|
16952
|
+
if (healthyCount > 0) lines.push(` \u2713 ${healthyCount} healthy check${healthyCount === 1 ? "" : "s"} collapsed`);
|
|
16953
|
+
}
|
|
16954
|
+
}
|
|
16955
|
+
if (gaps.length > 0 && input.verbose) {
|
|
16653
16956
|
lines.push("", input.shouldApply ? "Repairs" : "Will fix on --apply");
|
|
16654
16957
|
for (const c of gaps) {
|
|
16655
16958
|
const mark = input.shouldApply ? isAdvisoryDoctorCheck(c) ? "\u26A0" : "\u2717" : "\u2192";
|
|
@@ -16659,10 +16962,10 @@ function doctorHumanLines(input) {
|
|
|
16659
16962
|
if (input.shouldApply && input.pluginReloadRequired) {
|
|
16660
16963
|
lines.push("", `\u21BB ${input.reloadHint ?? "reload"} so the healed plugin/MCP installs load.`);
|
|
16661
16964
|
}
|
|
16662
|
-
const stale = renderPluginUpdateReportStaleOnly(input.updateReport, gaps.length === 0);
|
|
16965
|
+
const stale = input.verbose || gaps.length > 0 ? renderPluginUpdateReportStaleOnly(input.updateReport, gaps.length === 0) : [];
|
|
16663
16966
|
if (stale.length) {
|
|
16664
16967
|
lines.push("", ...stale);
|
|
16665
|
-
} else if (!input.verbose) {
|
|
16968
|
+
} else if (!input.verbose && gaps.length > 0) {
|
|
16666
16969
|
lines.push("", DOCTOR_VERBOSE_HINT);
|
|
16667
16970
|
}
|
|
16668
16971
|
if (!input.verbose) return lines;
|
|
@@ -16710,6 +17013,100 @@ function doctorAuditLines(input) {
|
|
|
16710
17013
|
return lines;
|
|
16711
17014
|
}
|
|
16712
17015
|
var PLUGIN_RESOLVABILITY_LABEL = "MMI plugin resolvability (marketplace + cache present)";
|
|
17016
|
+
var MMI_CLI_ON_PATH_LABEL = "mmi-cli on PATH";
|
|
17017
|
+
var PLUGIN_CLONE_LABEL = "plugin git clone (SSH\u2192HTTPS rewrite)";
|
|
17018
|
+
var MCP_RECONCILE_LABEL = "MCP server registrations (org-managed Playwright)";
|
|
17019
|
+
var CURSOR_ENABLED_PIN_LABEL = "Cursor Team Marketplace enabled plugin pin";
|
|
17020
|
+
var DOCTOR_CHECK_METADATA = [
|
|
17021
|
+
{ 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"] },
|
|
17022
|
+
{ 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"] },
|
|
17023
|
+
{ id: "hub.compat", category: "hub", severity: "hard", cost: "fast", heal: "auto", label: HUB_COMPAT_LABEL, protects: ["Client stays above the Hub minimum version floor"] },
|
|
17024
|
+
{ 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"] },
|
|
17025
|
+
{ 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)"] },
|
|
17026
|
+
{ 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"] },
|
|
17027
|
+
{ 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"] },
|
|
17028
|
+
{ 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"] },
|
|
17029
|
+
{ 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"] },
|
|
17030
|
+
{ 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"] },
|
|
17031
|
+
{ 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"] },
|
|
17032
|
+
{ 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"] },
|
|
17033
|
+
{ 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"] },
|
|
17034
|
+
{ 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"] },
|
|
17035
|
+
{ 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"] },
|
|
17036
|
+
{ 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"] },
|
|
17037
|
+
{ 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"] },
|
|
17038
|
+
{ 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"] },
|
|
17039
|
+
{ 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"] },
|
|
17040
|
+
{ 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"] },
|
|
17041
|
+
{ 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"] },
|
|
17042
|
+
{ id: "browser.artifacts", category: "browser", severity: "advisory", cost: "slow", heal: "none", label: BROWSER_ARTIFACTS_LABEL, protects: ["Playwright/browser output stays under tmp/playwright-mcp"] },
|
|
17043
|
+
{ 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"] },
|
|
17044
|
+
{ id: "opencode.version", category: "opencode", severity: "hard", cost: "fast", heal: "surface", label: OPENCODE_VERSION_LABEL, protects: ["OpenCode loads the released MMI adapter"] },
|
|
17045
|
+
{ 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"] },
|
|
17046
|
+
{ 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"] },
|
|
17047
|
+
{ id: "opencode.surface-assets", category: "opencode", severity: "hard", cost: "fast", heal: "auto", label: OPENCODE_SURFACE_ASSETS_LABEL, protects: ["OpenCode commands and skills are materialized"] },
|
|
17048
|
+
{ 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"] },
|
|
17049
|
+
{ 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"] },
|
|
17050
|
+
{ 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"] },
|
|
17051
|
+
{ 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"] },
|
|
17052
|
+
{ 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"] },
|
|
17053
|
+
{ 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"] },
|
|
17054
|
+
{ 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"] },
|
|
17055
|
+
// Incident coverage (#2594) — three real incident classes that previously had zero doctor coverage.
|
|
17056
|
+
{ 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"] },
|
|
17057
|
+
{ 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"] },
|
|
17058
|
+
{ 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"] }
|
|
17059
|
+
];
|
|
17060
|
+
var DOCTOR_CHECK_METADATA_BY_LABEL = Object.fromEntries(
|
|
17061
|
+
DOCTOR_CHECK_METADATA.map((meta) => [meta.label, meta])
|
|
17062
|
+
);
|
|
17063
|
+
var DOCTOR_EXTENDED_CHECK_LABELS = new Set(
|
|
17064
|
+
DOCTOR_CHECK_METADATA.filter((meta) => meta.cost === "slow").map((meta) => meta.label)
|
|
17065
|
+
);
|
|
17066
|
+
function derivedDoctorCheckMetadata(label) {
|
|
17067
|
+
if (label.startsWith(HUB_COMPAT_LABEL)) return { ...DOCTOR_CHECK_METADATA_BY_LABEL[HUB_COMPAT_LABEL], label };
|
|
17068
|
+
if (label.startsWith(HUB_DEPLOY_FRESHNESS_LABEL)) return { ...DOCTOR_CHECK_METADATA_BY_LABEL[HUB_DEPLOY_FRESHNESS_LABEL], label };
|
|
17069
|
+
if (label.startsWith("@mutmutco design-system")) {
|
|
17070
|
+
return {
|
|
17071
|
+
id: "registry.design-system-components",
|
|
17072
|
+
category: "hub",
|
|
17073
|
+
severity: "advisory",
|
|
17074
|
+
cost: "slow",
|
|
17075
|
+
heal: "none",
|
|
17076
|
+
label,
|
|
17077
|
+
protects: ["Design-system component cache can be compared against the live registry"]
|
|
17078
|
+
};
|
|
17079
|
+
}
|
|
17080
|
+
if (label.startsWith("@mutmutco registry components")) {
|
|
17081
|
+
return {
|
|
17082
|
+
id: "registry.components",
|
|
17083
|
+
category: "hub",
|
|
17084
|
+
severity: "advisory",
|
|
17085
|
+
cost: "slow",
|
|
17086
|
+
heal: "none",
|
|
17087
|
+
label,
|
|
17088
|
+
protects: ["Repo component cache can be compared against the live registry"]
|
|
17089
|
+
};
|
|
17090
|
+
}
|
|
17091
|
+
return void 0;
|
|
17092
|
+
}
|
|
17093
|
+
function fallbackDoctorCheckMetadata(label) {
|
|
17094
|
+
return {
|
|
17095
|
+
id: `legacy.${label.toLowerCase().replace(/[^a-z0-9]+/g, ".").replace(/(^\.|\.$)/g, "") || "unknown"}`,
|
|
17096
|
+
category: "repo",
|
|
17097
|
+
severity: "hard",
|
|
17098
|
+
cost: "fast",
|
|
17099
|
+
heal: "none",
|
|
17100
|
+
label,
|
|
17101
|
+
protects: ["Legacy check without catalog metadata"]
|
|
17102
|
+
};
|
|
17103
|
+
}
|
|
17104
|
+
function doctorMetadataForLabel(label) {
|
|
17105
|
+
return DOCTOR_CHECK_METADATA_BY_LABEL[label] ?? derivedDoctorCheckMetadata(label) ?? fallbackDoctorCheckMetadata(label);
|
|
17106
|
+
}
|
|
17107
|
+
function doctorMetadataForCheck(check) {
|
|
17108
|
+
return check.metadata ?? doctorMetadataForLabel(check.label);
|
|
17109
|
+
}
|
|
16713
17110
|
function buildPluginResolvabilityCheck(input) {
|
|
16714
17111
|
const { state } = buildPluginGuardDecision(input);
|
|
16715
17112
|
const surface = input.surface ?? "shell";
|
|
@@ -16755,17 +17152,17 @@ function ghReleaseTarballApiArgs(tag) {
|
|
|
16755
17152
|
}
|
|
16756
17153
|
function cursorUserGlobalStatePath() {
|
|
16757
17154
|
if (process.platform === "win32") {
|
|
16758
|
-
const base = process.env.APPDATA || (0,
|
|
16759
|
-
return (0,
|
|
17155
|
+
const base = process.env.APPDATA || (0, import_node_path18.join)((0, import_node_os6.homedir)(), "AppData", "Roaming");
|
|
17156
|
+
return (0, import_node_path18.join)(base, "Cursor", "User", "globalStorage", "state.vscdb");
|
|
16760
17157
|
}
|
|
16761
17158
|
if (process.platform === "darwin") {
|
|
16762
|
-
return (0,
|
|
17159
|
+
return (0, import_node_path18.join)((0, import_node_os6.homedir)(), "Library", "Application Support", "Cursor", "User", "globalStorage", "state.vscdb");
|
|
16763
17160
|
}
|
|
16764
|
-
return (0,
|
|
17161
|
+
return (0, import_node_path18.join)((0, import_node_os6.homedir)(), ".config", "Cursor", "User", "globalStorage", "state.vscdb");
|
|
16765
17162
|
}
|
|
16766
17163
|
async function readCursorThirdPartyExtensibilityEnabled(execFileP5) {
|
|
16767
17164
|
const dbPath = cursorUserGlobalStatePath();
|
|
16768
|
-
if (!(0,
|
|
17165
|
+
if (!(0, import_node_fs17.existsSync)(dbPath)) return void 0;
|
|
16769
17166
|
try {
|
|
16770
17167
|
const { stdout } = await execFileP5("sqlite3", [dbPath, `SELECT value FROM ItemTable WHERE key = '${CURSOR_THIRD_PARTY_STATE_KEY}';`], {
|
|
16771
17168
|
timeout: 5e3
|
|
@@ -16779,57 +17176,57 @@ async function readCursorThirdPartyExtensibilityEnabled(execFileP5) {
|
|
|
16779
17176
|
}
|
|
16780
17177
|
}
|
|
16781
17178
|
function syncDirContents(src, dest) {
|
|
16782
|
-
(0,
|
|
16783
|
-
for (const name of (0,
|
|
16784
|
-
(0,
|
|
17179
|
+
(0, import_node_fs17.mkdirSync)(dest, { recursive: true });
|
|
17180
|
+
for (const name of (0, import_node_fs17.readdirSync)(dest)) {
|
|
17181
|
+
(0, import_node_fs17.rmSync)((0, import_node_path18.join)(dest, name), { recursive: true, force: true });
|
|
16785
17182
|
}
|
|
16786
|
-
(0,
|
|
17183
|
+
(0, import_node_fs17.cpSync)(src, dest, { recursive: true });
|
|
16787
17184
|
}
|
|
16788
17185
|
function releaseTag(releasedVersion) {
|
|
16789
17186
|
return releasedVersion.startsWith("v") ? releasedVersion : `v${releasedVersion}`;
|
|
16790
17187
|
}
|
|
16791
17188
|
async function extractPluginMmiFromHubCheckout(hubCheckout, tag, tmpRoot, execFileP5) {
|
|
16792
|
-
const tarFile = (0,
|
|
17189
|
+
const tarFile = (0, import_node_path18.join)(tmpRoot, "archive.tar");
|
|
16793
17190
|
try {
|
|
16794
17191
|
await execFileP5("git", gitFetchReleaseTagArgs(hubCheckout, tag), { timeout: 6e4 });
|
|
16795
17192
|
await execFileP5("git", ["-C", hubCheckout, "archive", "--format=tar", `--output=${tarFile}`, tag, "plugins/mmi"], {
|
|
16796
17193
|
timeout: 6e4
|
|
16797
17194
|
});
|
|
16798
17195
|
await execFileP5("tar", ["-xf", tarFile, "-C", tmpRoot], { timeout: 6e4 });
|
|
16799
|
-
const pluginMmi = (0,
|
|
16800
|
-
return (0,
|
|
17196
|
+
const pluginMmi = (0, import_node_path18.join)(tmpRoot, "plugins", "mmi");
|
|
17197
|
+
return (0, import_node_fs17.existsSync)((0, import_node_path18.join)(pluginMmi, PLUGIN_JSON_REL)) ? pluginMmi : void 0;
|
|
16801
17198
|
} catch {
|
|
16802
17199
|
return void 0;
|
|
16803
17200
|
}
|
|
16804
17201
|
}
|
|
16805
17202
|
async function downloadPluginMmiViaGh(tag, tmpRoot) {
|
|
16806
|
-
const tarPath = (0,
|
|
17203
|
+
const tarPath = (0, import_node_path18.join)(tmpRoot, "repo.tgz");
|
|
16807
17204
|
try {
|
|
16808
|
-
(0,
|
|
17205
|
+
(0, import_node_fs17.mkdirSync)(tmpRoot, { recursive: true });
|
|
16809
17206
|
const { stdout } = await execFileBuffer("gh", ghReleaseTarballApiArgs(tag), {
|
|
16810
17207
|
timeout: 12e4,
|
|
16811
17208
|
maxBuffer: 100 * 1024 * 1024,
|
|
16812
17209
|
encoding: "buffer",
|
|
16813
17210
|
windowsHide: true
|
|
16814
17211
|
});
|
|
16815
|
-
(0,
|
|
17212
|
+
(0, import_node_fs17.writeFileSync)(tarPath, stdout);
|
|
16816
17213
|
await execFileBuffer("tar", ["-xzf", tarPath, "-C", tmpRoot], { timeout: 12e4, windowsHide: true });
|
|
16817
|
-
const top = (0,
|
|
17214
|
+
const top = (0, import_node_fs17.readdirSync)(tmpRoot).find((entry) => entry !== "repo.tgz");
|
|
16818
17215
|
if (!top) return void 0;
|
|
16819
|
-
const pluginMmi = (0,
|
|
16820
|
-
return (0,
|
|
17216
|
+
const pluginMmi = (0, import_node_path18.join)(tmpRoot, top, "plugins", "mmi");
|
|
17217
|
+
return (0, import_node_fs17.existsSync)((0, import_node_path18.join)(pluginMmi, PLUGIN_JSON_REL)) ? pluginMmi : void 0;
|
|
16821
17218
|
} catch {
|
|
16822
17219
|
return void 0;
|
|
16823
17220
|
}
|
|
16824
17221
|
}
|
|
16825
17222
|
async function resolvePluginMmiSource(releasedVersion, hubCheckout, tmpRoot, execFileP5) {
|
|
16826
|
-
(0,
|
|
17223
|
+
(0, import_node_fs17.mkdirSync)(tmpRoot, { recursive: true });
|
|
16827
17224
|
const tag = releaseTag(releasedVersion);
|
|
16828
17225
|
if (hubCheckout) {
|
|
16829
17226
|
const fromHub = await extractPluginMmiFromHubCheckout(hubCheckout, tag, tmpRoot, execFileP5);
|
|
16830
17227
|
if (fromHub) return fromHub;
|
|
16831
17228
|
}
|
|
16832
|
-
return downloadPluginMmiViaGh(tag, (0,
|
|
17229
|
+
return downloadPluginMmiViaGh(tag, (0, import_node_path18.join)(tmpRoot, "gh"));
|
|
16833
17230
|
}
|
|
16834
17231
|
function cursorPluginPinsNeedingSeed(pins, releasedVersion) {
|
|
16835
17232
|
if (!isSemverVersion2(releasedVersion)) return pins.filter((pin) => !pin.hasPluginJson || !pin.hasHooksJson || pin.isEmpty);
|
|
@@ -16843,7 +17240,7 @@ function cursorPluginCacheSeedTargets(pins, releasedVersion, cacheRoot, cacheRoo
|
|
|
16843
17240
|
const needing = cursorPluginPinsNeedingSeed(pins, releasedVersion);
|
|
16844
17241
|
if (needing.length > 0) return needing.map((pin) => pin.path);
|
|
16845
17242
|
if (pins.length === 0 && cacheRoot && cacheRootExists && isSemverVersion2(releasedVersion)) {
|
|
16846
|
-
return [(0,
|
|
17243
|
+
return [(0, import_node_path18.join)(cacheRoot, `v${releasedVersion.replace(/^v/, "")}`)];
|
|
16847
17244
|
}
|
|
16848
17245
|
return [];
|
|
16849
17246
|
}
|
|
@@ -16858,10 +17255,10 @@ async function applyCursorPluginCacheSeed(input) {
|
|
|
16858
17255
|
for (const dest of targets) {
|
|
16859
17256
|
syncDirContents(source, dest);
|
|
16860
17257
|
}
|
|
16861
|
-
(0,
|
|
17258
|
+
(0, import_node_fs17.rmSync)(tmpRoot, { recursive: true, force: true });
|
|
16862
17259
|
return true;
|
|
16863
17260
|
}
|
|
16864
|
-
var
|
|
17261
|
+
var CURSOR_ENABLED_PIN_LABEL2 = "Cursor Team Marketplace enabled plugin pin";
|
|
16865
17262
|
function cursorSplitBrainCapabilities() {
|
|
16866
17263
|
const skills = OPENCODE_WORKFLOW_COMMANDS.map((c) => `/${c}`).join(", ");
|
|
16867
17264
|
return `org skills (${skills}) and SessionStart hooks`;
|
|
@@ -16881,7 +17278,7 @@ function parseCursorPluginLog(text) {
|
|
|
16881
17278
|
return { enabledPin, loadSucceeded, loadFailed };
|
|
16882
17279
|
}
|
|
16883
17280
|
function buildCursorEnabledPinCheck(input) {
|
|
16884
|
-
const label =
|
|
17281
|
+
const label = CURSOR_ENABLED_PIN_LABEL2;
|
|
16885
17282
|
const healthy = { ok: true, label, fix: "" };
|
|
16886
17283
|
if (!input.isOrgRepo) return healthy;
|
|
16887
17284
|
if (!input.enabledPin && !input.loadFailed) return healthy;
|
|
@@ -16916,7 +17313,7 @@ var PLAYWRIGHT_MCP_SPEC = {
|
|
|
16916
17313
|
args: ["-y", "@playwright/mcp@latest", "--output-dir", "tmp/playwright-mcp"],
|
|
16917
17314
|
legacyNames: []
|
|
16918
17315
|
};
|
|
16919
|
-
var
|
|
17316
|
+
var MCP_RECONCILE_LABEL2 = "MCP server registrations (org-managed Playwright)";
|
|
16920
17317
|
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
17318
|
function extractServerBlock(content, format, spec) {
|
|
16922
17319
|
return format === "json" ? extractJsonServer(content, spec) : extractTomlServer(content, spec);
|
|
@@ -17030,7 +17427,7 @@ function planClaudeCliMcpHeal(item, spec) {
|
|
|
17030
17427
|
function buildMcpReconcileCheck(plan) {
|
|
17031
17428
|
const base = {
|
|
17032
17429
|
ok: true,
|
|
17033
|
-
label:
|
|
17430
|
+
label: MCP_RECONCILE_LABEL2,
|
|
17034
17431
|
fix: MCP_RECONCILE_FIX,
|
|
17035
17432
|
severityOverride: "advisory"
|
|
17036
17433
|
};
|
|
@@ -17217,9 +17614,9 @@ ${block}
|
|
|
17217
17614
|
}
|
|
17218
17615
|
|
|
17219
17616
|
// src/cli-doctor-shared.ts
|
|
17220
|
-
var import_node_fs17 = require("node:fs");
|
|
17221
|
-
var import_node_path18 = require("node:path");
|
|
17222
17617
|
var import_node_fs18 = require("node:fs");
|
|
17618
|
+
var import_node_path19 = require("node:path");
|
|
17619
|
+
var import_node_fs19 = require("node:fs");
|
|
17223
17620
|
var GC_GH_TIMEOUT_MS = 2e4;
|
|
17224
17621
|
async function awsCallerArn() {
|
|
17225
17622
|
try {
|
|
@@ -17265,7 +17662,7 @@ async function localBranchHeads() {
|
|
|
17265
17662
|
}
|
|
17266
17663
|
async function currentRepoWorktreeGitRoot(repoRoot) {
|
|
17267
17664
|
const gitCommonDir = (await execFileP2("git", ["rev-parse", "--git-common-dir"], { timeout: GIT_TIMEOUT_MS }).catch(() => ({ stdout: "" }))).stdout.trim();
|
|
17268
|
-
return gitCommonDir ? (0,
|
|
17665
|
+
return gitCommonDir ? (0, import_node_path19.resolve)(repoRoot, gitCommonDir, "worktrees") : "";
|
|
17269
17666
|
}
|
|
17270
17667
|
async function worktreeBranches() {
|
|
17271
17668
|
const { stdout } = await execFileP2("git", ["worktree", "list", "--porcelain"], { timeout: GIT_TIMEOUT_MS });
|
|
@@ -17285,18 +17682,18 @@ function resolveGitdirForWorktreeFile(worktreePath, content) {
|
|
|
17285
17682
|
const match = /^gitdir:\s*(.+)\s*$/im.exec(content);
|
|
17286
17683
|
if (!match?.[1]) return void 0;
|
|
17287
17684
|
const raw = match[1].trim();
|
|
17288
|
-
return (0,
|
|
17685
|
+
return (0, import_node_path19.isAbsolute)(raw) ? raw : (0, import_node_path19.resolve)(worktreePath, raw);
|
|
17289
17686
|
}
|
|
17290
17687
|
function metadataOwnsMissingWorktreeDir(worktreePath, worktreeGitRoot) {
|
|
17291
17688
|
if (!worktreeGitRoot) return false;
|
|
17292
17689
|
try {
|
|
17293
|
-
const entries = (0,
|
|
17690
|
+
const entries = (0, import_node_fs19.readdirSync)(worktreeGitRoot, { withFileTypes: true });
|
|
17294
17691
|
for (const ent of entries) {
|
|
17295
17692
|
if (!ent.isDirectory()) continue;
|
|
17296
17693
|
try {
|
|
17297
|
-
const gitdirPath = (0,
|
|
17298
|
-
const resolvedGitdir = (0,
|
|
17299
|
-
if (sameWorktreeMetadataPath((0,
|
|
17694
|
+
const gitdirPath = (0, import_node_fs18.readFileSync)((0, import_node_path19.join)(worktreeGitRoot, ent.name, "gitdir"), "utf8").trim();
|
|
17695
|
+
const resolvedGitdir = (0, import_node_path19.isAbsolute)(gitdirPath) ? gitdirPath : (0, import_node_path19.resolve)(worktreeGitRoot, ent.name, gitdirPath);
|
|
17696
|
+
if (sameWorktreeMetadataPath((0, import_node_path19.dirname)(resolvedGitdir), worktreePath)) return true;
|
|
17300
17697
|
} catch {
|
|
17301
17698
|
}
|
|
17302
17699
|
}
|
|
@@ -17306,7 +17703,7 @@ function metadataOwnsMissingWorktreeDir(worktreePath, worktreeGitRoot) {
|
|
|
17306
17703
|
}
|
|
17307
17704
|
function pathExistsKnown(path2) {
|
|
17308
17705
|
try {
|
|
17309
|
-
(0,
|
|
17706
|
+
(0, import_node_fs19.statSync)(path2);
|
|
17310
17707
|
return true;
|
|
17311
17708
|
} catch (e) {
|
|
17312
17709
|
const code = typeof e === "object" && e && "code" in e ? String(e.code ?? "") : "";
|
|
@@ -17315,10 +17712,10 @@ function pathExistsKnown(path2) {
|
|
|
17315
17712
|
}
|
|
17316
17713
|
}
|
|
17317
17714
|
function inspectSiblingWorktreeDir(path2, worktreeGitRoot) {
|
|
17318
|
-
const gitPath = (0,
|
|
17715
|
+
const gitPath = (0, import_node_path19.join)(path2, ".git");
|
|
17319
17716
|
let st;
|
|
17320
17717
|
try {
|
|
17321
|
-
st = (0,
|
|
17718
|
+
st = (0, import_node_fs19.lstatSync)(gitPath);
|
|
17322
17719
|
} catch (e) {
|
|
17323
17720
|
const code = typeof e === "object" && e && "code" in e ? String(e.code ?? "") : "";
|
|
17324
17721
|
if (code === "ENOENT" || code === "ENOTDIR") {
|
|
@@ -17335,7 +17732,7 @@ function inspectSiblingWorktreeDir(path2, worktreeGitRoot) {
|
|
|
17335
17732
|
if (st.isDirectory()) return { path: path2, gitType: "dir" };
|
|
17336
17733
|
if (!st.isFile()) return { path: path2, gitType: "other" };
|
|
17337
17734
|
try {
|
|
17338
|
-
const gitFileContent = (0,
|
|
17735
|
+
const gitFileContent = (0, import_node_fs18.readFileSync)(gitPath, "utf8");
|
|
17339
17736
|
const gitdir = resolveGitdirForWorktreeFile(path2, gitFileContent);
|
|
17340
17737
|
const gitDirExists = gitdir ? pathExistsKnown(gitdir) : false;
|
|
17341
17738
|
return {
|
|
@@ -17352,7 +17749,7 @@ function inspectSiblingWorktreeDir(path2, worktreeGitRoot) {
|
|
|
17352
17749
|
}
|
|
17353
17750
|
function inspectDeadWorktreeDirContent(path2) {
|
|
17354
17751
|
try {
|
|
17355
|
-
return { entries: (0,
|
|
17752
|
+
return { entries: (0, import_node_fs19.readdirSync)(path2) };
|
|
17356
17753
|
} catch (e) {
|
|
17357
17754
|
const code = typeof e === "object" && e && "code" in e ? String(e.code ?? "") : "";
|
|
17358
17755
|
return { error: code ? `unable to inspect directory contents (${code})` : "unable to inspect directory contents" };
|
|
@@ -17371,8 +17768,8 @@ async function siblingWorktreeDirs() {
|
|
|
17371
17768
|
const worktreeGitRoot = await currentRepoWorktreeGitRoot(repoRoot);
|
|
17372
17769
|
const siblingRoot = siblingMmiWorktreesRoot(repoRoot);
|
|
17373
17770
|
try {
|
|
17374
|
-
const entries = (0,
|
|
17375
|
-
return entries.filter((ent) => ent.isDirectory()).map((ent) => inspectSiblingWorktreeDir((0,
|
|
17771
|
+
const entries = (0, import_node_fs19.readdirSync)(siblingRoot, { withFileTypes: true });
|
|
17772
|
+
return entries.filter((ent) => ent.isDirectory()).map((ent) => inspectSiblingWorktreeDir((0, import_node_path19.join)(siblingRoot, ent.name), worktreeGitRoot)).filter((entry) => Boolean(entry));
|
|
17376
17773
|
} catch {
|
|
17377
17774
|
return [];
|
|
17378
17775
|
}
|
|
@@ -17422,7 +17819,7 @@ async function fetchHubVersionInfo(baseUrl) {
|
|
|
17422
17819
|
}
|
|
17423
17820
|
function readRepoVersion() {
|
|
17424
17821
|
try {
|
|
17425
|
-
return JSON.parse((0,
|
|
17822
|
+
return JSON.parse((0, import_node_fs20.readFileSync)((0, import_node_path20.join)(process.cwd(), ".claude-plugin", "plugin.json"), "utf8")).version || void 0;
|
|
17426
17823
|
} catch {
|
|
17427
17824
|
return void 0;
|
|
17428
17825
|
}
|
|
@@ -17574,11 +17971,11 @@ async function applyPluginHeal(token, surface, log, opts) {
|
|
|
17574
17971
|
}
|
|
17575
17972
|
var installedPluginsPath = (surface = detectSurface(process.env)) => {
|
|
17576
17973
|
const homeDir = surface === "codex" ? ".codex" : ".claude";
|
|
17577
|
-
return (0,
|
|
17974
|
+
return (0, import_node_path20.join)((0, import_node_os7.homedir)(), homeDir, "plugins", "installed_plugins.json");
|
|
17578
17975
|
};
|
|
17579
17976
|
function readInstalledPlugins(surface = detectSurface(process.env)) {
|
|
17580
17977
|
try {
|
|
17581
|
-
return JSON.parse((0,
|
|
17978
|
+
return JSON.parse((0, import_node_fs20.readFileSync)(installedPluginsPath(surface), "utf8"));
|
|
17582
17979
|
} catch {
|
|
17583
17980
|
return null;
|
|
17584
17981
|
}
|
|
@@ -17586,13 +17983,13 @@ function readInstalledPlugins(surface = detectSurface(process.env)) {
|
|
|
17586
17983
|
function marketplaceCloneCandidates(surface, home) {
|
|
17587
17984
|
if (surface === "codex") {
|
|
17588
17985
|
return [
|
|
17589
|
-
(0,
|
|
17590
|
-
(0,
|
|
17986
|
+
(0, import_node_path20.join)(home, ".codex", ".tmp", "marketplaces", "mutmutco"),
|
|
17987
|
+
(0, import_node_path20.join)(home, ".codex", "plugins", "marketplaces", "mutmutco")
|
|
17591
17988
|
];
|
|
17592
17989
|
}
|
|
17593
|
-
return [(0,
|
|
17990
|
+
return [(0, import_node_path20.join)(home, ".claude", "plugins", "marketplaces", "mutmutco")];
|
|
17594
17991
|
}
|
|
17595
|
-
function marketplaceClonePresent(surface, home, exists =
|
|
17992
|
+
function marketplaceClonePresent(surface, home, exists = import_node_fs20.existsSync) {
|
|
17596
17993
|
return marketplaceCloneCandidates(surface, home).some(exists);
|
|
17597
17994
|
}
|
|
17598
17995
|
function snapshotPluginGuardInput(surface = detectSurface(process.env), isOrgRepo = false) {
|
|
@@ -17602,14 +17999,14 @@ function snapshotPluginGuardInput(surface = detectSurface(process.env), isOrgRep
|
|
|
17602
17999
|
isOrgRepo,
|
|
17603
18000
|
installRecordPresent: hasUserInstallRecord(installed, MMI_PLUGIN_ID) || hasProjectInstallRecord(installed, MMI_PLUGIN_ID, process.cwd()),
|
|
17604
18001
|
marketplaceClonePresent: marketplaceClonePresent(surface, (0, import_node_os7.homedir)()),
|
|
17605
|
-
pluginCachePresent: (0,
|
|
18002
|
+
pluginCachePresent: (0, import_node_fs20.existsSync)((0, import_node_path20.join)((0, import_node_os7.homedir)(), homeDir, "plugins", "cache", "mutmutco", "mmi"))
|
|
17606
18003
|
};
|
|
17607
18004
|
}
|
|
17608
18005
|
function installedPluginSources() {
|
|
17609
18006
|
return ["claude", "codex"].map((surface) => {
|
|
17610
|
-
const recordPath = (0,
|
|
18007
|
+
const recordPath = (0, import_node_path20.join)((0, import_node_os7.homedir)(), `.${surface}`, "plugins", "installed_plugins.json");
|
|
17611
18008
|
try {
|
|
17612
|
-
return { surface, installed: JSON.parse((0,
|
|
18009
|
+
return { surface, installed: JSON.parse((0, import_node_fs20.readFileSync)(recordPath, "utf8")), recordPath };
|
|
17613
18010
|
} catch {
|
|
17614
18011
|
return { surface, installed: null, recordPath };
|
|
17615
18012
|
}
|
|
@@ -17617,7 +18014,7 @@ function installedPluginSources() {
|
|
|
17617
18014
|
}
|
|
17618
18015
|
function readClaudeSettings() {
|
|
17619
18016
|
try {
|
|
17620
|
-
return JSON.parse((0,
|
|
18017
|
+
return JSON.parse((0, import_node_fs20.readFileSync)((0, import_node_path20.join)(process.cwd(), ".claude", "settings.json"), "utf8"));
|
|
17621
18018
|
} catch {
|
|
17622
18019
|
return null;
|
|
17623
18020
|
}
|
|
@@ -17639,7 +18036,7 @@ function writeProjectInstallRecord(record) {
|
|
|
17639
18036
|
const list = file.plugins[MMI_PLUGIN_ID] ?? [];
|
|
17640
18037
|
list.push(record);
|
|
17641
18038
|
file.plugins[MMI_PLUGIN_ID] = list;
|
|
17642
|
-
(0,
|
|
18039
|
+
(0, import_node_fs20.writeFileSync)(installedPluginsPath(), `${JSON.stringify(file, null, 2)}
|
|
17643
18040
|
`, "utf8");
|
|
17644
18041
|
return true;
|
|
17645
18042
|
} catch {
|
|
@@ -17652,9 +18049,9 @@ function backupAndWriteInstalledPlugins(records, pluginId) {
|
|
|
17652
18049
|
if (!file) return false;
|
|
17653
18050
|
if (!file.plugins) file.plugins = {};
|
|
17654
18051
|
const path2 = installedPluginsPath();
|
|
17655
|
-
(0,
|
|
18052
|
+
(0, import_node_fs20.copyFileSync)(path2, `${path2}.bak`);
|
|
17656
18053
|
file.plugins[pluginId] = records;
|
|
17657
|
-
(0,
|
|
18054
|
+
(0, import_node_fs20.writeFileSync)(path2, `${JSON.stringify(file, null, 2)}
|
|
17658
18055
|
`, "utf8");
|
|
17659
18056
|
return true;
|
|
17660
18057
|
} catch {
|
|
@@ -17662,22 +18059,22 @@ function backupAndWriteInstalledPlugins(records, pluginId) {
|
|
|
17662
18059
|
}
|
|
17663
18060
|
}
|
|
17664
18061
|
function opencodeConfigDir() {
|
|
17665
|
-
return (0,
|
|
18062
|
+
return (0, import_node_path20.join)((0, import_node_os7.homedir)(), ".config", "opencode");
|
|
17666
18063
|
}
|
|
17667
18064
|
function opencodeConfigPath() {
|
|
17668
|
-
return (0,
|
|
18065
|
+
return (0, import_node_path20.join)(opencodeConfigDir(), "opencode.jsonc");
|
|
17669
18066
|
}
|
|
17670
18067
|
function opencodeCommandsDir() {
|
|
17671
|
-
return (0,
|
|
18068
|
+
return (0, import_node_path20.join)(opencodeConfigDir(), "commands");
|
|
17672
18069
|
}
|
|
17673
18070
|
function opencodeSkillsPath() {
|
|
17674
|
-
return (0,
|
|
18071
|
+
return (0, import_node_path20.join)(opencodeConfigDir(), "node_modules", "@mutmutco", "opencode-mmi", "skills");
|
|
17675
18072
|
}
|
|
17676
18073
|
function opencodeConfigSnapshot() {
|
|
17677
18074
|
const path2 = opencodeConfigPath();
|
|
17678
|
-
if (!(0,
|
|
18075
|
+
if (!(0, import_node_fs20.existsSync)(path2)) return { path: path2, hasConfig: false, hasPluginField: false, parseOk: true };
|
|
17679
18076
|
try {
|
|
17680
|
-
const raw = (0,
|
|
18077
|
+
const raw = (0, import_node_fs20.readFileSync)(path2, "utf8");
|
|
17681
18078
|
const parsed = JSON.parse(stripJsonc(raw));
|
|
17682
18079
|
const hasPluginField = Object.prototype.hasOwnProperty.call(parsed, "plugin");
|
|
17683
18080
|
const skillsPaths = Array.isArray(parsed.skills?.paths) ? parsed.skills.paths.filter((p) => typeof p === "string") : void 0;
|
|
@@ -17700,9 +18097,9 @@ function writeOpencodeConfigPlugin(snapshot) {
|
|
|
17700
18097
|
const plan = planOpencodeConfigWrite(snapshot.hasConfig ? snapshot.raw : void 0);
|
|
17701
18098
|
if (plan.action === "already") return true;
|
|
17702
18099
|
if (!plan.text || plan.action === "unsafe") return false;
|
|
17703
|
-
(0,
|
|
17704
|
-
if (snapshot.hasConfig) (0,
|
|
17705
|
-
(0,
|
|
18100
|
+
(0, import_node_fs20.mkdirSync)((0, import_node_path20.dirname)(path2), { recursive: true });
|
|
18101
|
+
if (snapshot.hasConfig) (0, import_node_fs20.copyFileSync)(path2, `${path2}.bak`);
|
|
18102
|
+
(0, import_node_fs20.writeFileSync)(path2, plan.text, "utf8");
|
|
17706
18103
|
return true;
|
|
17707
18104
|
} catch {
|
|
17708
18105
|
return false;
|
|
@@ -17717,9 +18114,9 @@ function writeOpencodeSkillsPath(snapshot, skillsPath) {
|
|
|
17717
18114
|
const normalized = skillsPath.replace(/\\/g, "/");
|
|
17718
18115
|
if (!paths.some((p) => p.replace(/\\/g, "/") === normalized)) paths.push(skillsPath.replace(/\\/g, "/"));
|
|
17719
18116
|
parsed.skills = { ...skills, paths };
|
|
17720
|
-
(0,
|
|
17721
|
-
if (snapshot.hasConfig && (0,
|
|
17722
|
-
(0,
|
|
18117
|
+
(0, import_node_fs20.mkdirSync)((0, import_node_path20.dirname)(snapshot.path), { recursive: true });
|
|
18118
|
+
if (snapshot.hasConfig && (0, import_node_fs20.existsSync)(snapshot.path)) (0, import_node_fs20.copyFileSync)(snapshot.path, `${snapshot.path}.bak`);
|
|
18119
|
+
(0, import_node_fs20.writeFileSync)(snapshot.path, `${JSON.stringify(parsed, null, 2)}
|
|
17723
18120
|
`, "utf8");
|
|
17724
18121
|
return true;
|
|
17725
18122
|
} catch {
|
|
@@ -17728,7 +18125,7 @@ function writeOpencodeSkillsPath(snapshot, skillsPath) {
|
|
|
17728
18125
|
}
|
|
17729
18126
|
function opencodeExistingCommands() {
|
|
17730
18127
|
try {
|
|
17731
|
-
return (0,
|
|
18128
|
+
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
18129
|
} catch {
|
|
17733
18130
|
return [];
|
|
17734
18131
|
}
|
|
@@ -17736,9 +18133,9 @@ function opencodeExistingCommands() {
|
|
|
17736
18133
|
function writeOpencodeCommandFiles() {
|
|
17737
18134
|
try {
|
|
17738
18135
|
const dir = opencodeCommandsDir();
|
|
17739
|
-
(0,
|
|
18136
|
+
(0, import_node_fs20.mkdirSync)(dir, { recursive: true });
|
|
17740
18137
|
for (const command of OPENCODE_WORKFLOW_COMMANDS) {
|
|
17741
|
-
(0,
|
|
18138
|
+
(0, import_node_fs20.writeFileSync)((0, import_node_path20.join)(dir, `${command}.md`), opencodeCommandMarkdown(command), "utf8");
|
|
17742
18139
|
}
|
|
17743
18140
|
return true;
|
|
17744
18141
|
} catch {
|
|
@@ -17747,12 +18144,12 @@ function writeOpencodeCommandFiles() {
|
|
|
17747
18144
|
}
|
|
17748
18145
|
function readOpencodeAdapterDiskVersion() {
|
|
17749
18146
|
const candidates = [
|
|
17750
|
-
(0,
|
|
17751
|
-
(0,
|
|
18147
|
+
(0, import_node_path20.join)(opencodeConfigDir(), "node_modules", "@mutmutco", "opencode-mmi", "package.json"),
|
|
18148
|
+
(0, import_node_path20.join)((0, import_node_os7.homedir)(), ".cache", "opencode", "node_modules", "@mutmutco", "opencode-mmi", "package.json")
|
|
17752
18149
|
];
|
|
17753
18150
|
for (const path2 of candidates) {
|
|
17754
18151
|
try {
|
|
17755
|
-
const parsed = JSON.parse((0,
|
|
18152
|
+
const parsed = JSON.parse((0, import_node_fs20.readFileSync)(path2, "utf8"));
|
|
17756
18153
|
if (typeof parsed.version === "string" && parsed.version.trim()) return parsed.version.trim();
|
|
17757
18154
|
} catch {
|
|
17758
18155
|
continue;
|
|
@@ -17768,7 +18165,7 @@ async function forceInstallOpencodeMmiPlugins(snapshot, log) {
|
|
|
17768
18165
|
try {
|
|
17769
18166
|
const specs = opencodeMmiPluginSpecs(snapshot);
|
|
17770
18167
|
log(` \u21BB force-refreshing OpenCode MMI npm plugin(s): ${specs.join(", ")}\u2026`);
|
|
17771
|
-
(0,
|
|
18168
|
+
(0, import_node_fs20.mkdirSync)(opencodeConfigDir(), { recursive: true });
|
|
17772
18169
|
await runHostBin("npm", ["install", "--prefix", opencodeConfigDir(), "--force", ...specs], { timeout: NPM_UPDATE_TIMEOUT_MS });
|
|
17773
18170
|
return true;
|
|
17774
18171
|
} catch {
|
|
@@ -17776,12 +18173,12 @@ async function forceInstallOpencodeMmiPlugins(snapshot, log) {
|
|
|
17776
18173
|
}
|
|
17777
18174
|
}
|
|
17778
18175
|
function opencodePackagesRoot() {
|
|
17779
|
-
return (0,
|
|
18176
|
+
return (0, import_node_path20.join)((0, import_node_os7.homedir)(), ".cache", "opencode", "packages", "@mutmutco");
|
|
17780
18177
|
}
|
|
17781
18178
|
function opencodePluginCacheDirs() {
|
|
17782
18179
|
const root = opencodePackagesRoot();
|
|
17783
18180
|
try {
|
|
17784
|
-
return (0,
|
|
18181
|
+
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
18182
|
} catch {
|
|
17786
18183
|
return [];
|
|
17787
18184
|
}
|
|
@@ -17789,7 +18186,7 @@ function opencodePluginCacheDirs() {
|
|
|
17789
18186
|
function readOpencodeCacheDirVersion(cacheDir) {
|
|
17790
18187
|
try {
|
|
17791
18188
|
const parsed = JSON.parse(
|
|
17792
|
-
(0,
|
|
18189
|
+
(0, import_node_fs20.readFileSync)((0, import_node_path20.join)(cacheDir, "node_modules", "@mutmutco", "opencode-mmi", "package.json"), "utf8")
|
|
17793
18190
|
);
|
|
17794
18191
|
return typeof parsed.version === "string" && parsed.version.trim() ? parsed.version.trim() : void 0;
|
|
17795
18192
|
} catch {
|
|
@@ -17812,9 +18209,9 @@ function quarantineStaleOpencodePluginCaches(releasedVersion, log) {
|
|
|
17812
18209
|
});
|
|
17813
18210
|
if (!plan.quarantine) continue;
|
|
17814
18211
|
try {
|
|
17815
|
-
const quarantineRoot = (0,
|
|
17816
|
-
(0,
|
|
17817
|
-
(0,
|
|
18212
|
+
const quarantineRoot = (0, import_node_path20.join)(opencodePackagesRoot(), ".mmi-quarantine", stamp);
|
|
18213
|
+
(0, import_node_fs20.mkdirSync)(quarantineRoot, { recursive: true });
|
|
18214
|
+
(0, import_node_fs20.renameSync)(cacheDir, (0, import_node_path20.join)(quarantineRoot, (0, import_node_path20.basename)(cacheDir)));
|
|
17818
18215
|
log(` \u21BB quarantined stale OpenCode plugin cache ${plan.cacheVersion} (< ${plan.releasedVersion}) \u2014 OpenCode reinstalls the current adapter on next start`);
|
|
17819
18216
|
moved = true;
|
|
17820
18217
|
} catch {
|
|
@@ -17840,30 +18237,30 @@ function opencodePluginVersionsForReport() {
|
|
|
17840
18237
|
}
|
|
17841
18238
|
function opencodeDesktopLogsRoot() {
|
|
17842
18239
|
if (process.platform === "win32") {
|
|
17843
|
-
const base = process.env.APPDATA || (0,
|
|
17844
|
-
return (0,
|
|
18240
|
+
const base = process.env.APPDATA || (0, import_node_path20.join)((0, import_node_os7.homedir)(), "AppData", "Roaming");
|
|
18241
|
+
return (0, import_node_path20.join)(base, "ai.opencode.desktop", "logs");
|
|
17845
18242
|
}
|
|
17846
18243
|
if (process.platform === "darwin") {
|
|
17847
|
-
return (0,
|
|
18244
|
+
return (0, import_node_path20.join)((0, import_node_os7.homedir)(), "Library", "Application Support", "ai.opencode.desktop", "logs");
|
|
17848
18245
|
}
|
|
17849
|
-
return (0,
|
|
18246
|
+
return (0, import_node_path20.join)((0, import_node_os7.homedir)(), ".config", "ai.opencode.desktop", "logs");
|
|
17850
18247
|
}
|
|
17851
18248
|
function opencodeDesktopBootstrapSnapshot() {
|
|
17852
18249
|
const root = opencodeDesktopLogsRoot();
|
|
17853
18250
|
try {
|
|
17854
|
-
const sessionDirs = (0,
|
|
18251
|
+
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
18252
|
const newest = sessionDirs[0];
|
|
17856
18253
|
if (!newest) return [];
|
|
17857
|
-
const logPath = (0,
|
|
17858
|
-
const text = (0,
|
|
17859
|
-
return opencodeAgentDirectoriesFromLog(text).filter((directory) => !(0,
|
|
18254
|
+
const logPath = (0, import_node_path20.join)(newest, "renderer.log");
|
|
18255
|
+
const text = (0, import_node_fs20.readFileSync)(logPath, "utf8");
|
|
18256
|
+
return opencodeAgentDirectoriesFromLog(text).filter((directory) => !(0, import_node_fs20.existsSync)(directory)).map((directory) => ({ directory, logPath }));
|
|
17860
18257
|
} catch {
|
|
17861
18258
|
return [];
|
|
17862
18259
|
}
|
|
17863
18260
|
}
|
|
17864
18261
|
function opencodeLegacyConfigSnapshot() {
|
|
17865
|
-
const legacyPath = (0,
|
|
17866
|
-
if (!(0,
|
|
18262
|
+
const legacyPath = (0, import_node_path20.join)((0, import_node_os7.homedir)(), ".opencode", "opencode.json");
|
|
18263
|
+
if (!(0, import_node_fs20.existsSync)(legacyPath)) return {};
|
|
17867
18264
|
const content = readTextFile(legacyPath);
|
|
17868
18265
|
if (content == null) return {};
|
|
17869
18266
|
const plugins = parseOpencodeLegacyConfigPlugins(content);
|
|
@@ -17875,24 +18272,24 @@ function opencodeLegacyConfigSnapshot() {
|
|
|
17875
18272
|
function quarantineOpencodeLegacyConfig(legacyPath) {
|
|
17876
18273
|
try {
|
|
17877
18274
|
const backupPath = `${legacyPath}.bak`;
|
|
17878
|
-
if ((0,
|
|
17879
|
-
(0,
|
|
18275
|
+
if ((0, import_node_fs20.existsSync)(backupPath)) return false;
|
|
18276
|
+
(0, import_node_fs20.renameSync)(legacyPath, backupPath);
|
|
17880
18277
|
return true;
|
|
17881
18278
|
} catch {
|
|
17882
18279
|
return false;
|
|
17883
18280
|
}
|
|
17884
18281
|
}
|
|
17885
18282
|
function cursorPluginCacheRoot() {
|
|
17886
|
-
return (0,
|
|
18283
|
+
return (0, import_node_path20.join)((0, import_node_os7.homedir)(), ".cursor", "plugins", "cache", "mutmutco", "mmi");
|
|
17887
18284
|
}
|
|
17888
18285
|
function cursorLogsRoot() {
|
|
17889
|
-
if (process.platform === "win32") return (0,
|
|
17890
|
-
if (process.platform === "darwin") return (0,
|
|
17891
|
-
return (0,
|
|
18286
|
+
if (process.platform === "win32") return (0, import_node_path20.join)((0, import_node_os7.homedir)(), "AppData", "Roaming", "Cursor", "logs");
|
|
18287
|
+
if (process.platform === "darwin") return (0, import_node_path20.join)((0, import_node_os7.homedir)(), "Library", "Application Support", "Cursor", "logs");
|
|
18288
|
+
return (0, import_node_path20.join)((0, import_node_os7.homedir)(), ".config", "Cursor", "logs");
|
|
17892
18289
|
}
|
|
17893
18290
|
function safeReaddirNames(dir) {
|
|
17894
18291
|
try {
|
|
17895
|
-
return (0,
|
|
18292
|
+
return (0, import_node_fs20.readdirSync)(dir, { withFileTypes: true }).map((e) => e.name);
|
|
17896
18293
|
} catch {
|
|
17897
18294
|
return [];
|
|
17898
18295
|
}
|
|
@@ -17901,15 +18298,15 @@ function readCursorPluginLogText() {
|
|
|
17901
18298
|
const root = cursorLogsRoot();
|
|
17902
18299
|
const sessions = safeReaddirNames(root).sort().reverse();
|
|
17903
18300
|
for (const session of sessions.slice(0, 5)) {
|
|
17904
|
-
const sessionDir = (0,
|
|
18301
|
+
const sessionDir = (0, import_node_path20.join)(root, session);
|
|
17905
18302
|
const texts = [];
|
|
17906
18303
|
for (const win of safeReaddirNames(sessionDir).filter((n) => n.startsWith("window"))) {
|
|
17907
|
-
const dir = (0,
|
|
18304
|
+
const dir = (0, import_node_path20.join)(sessionDir, win, "exthost", "anysphere.cursor-agent-exec");
|
|
17908
18305
|
for (const file of safeReaddirNames(dir)) {
|
|
17909
18306
|
if (!/^Cursor Plugins.*\.log$/i.test(file)) continue;
|
|
17910
|
-
const path2 = (0,
|
|
18307
|
+
const path2 = (0, import_node_path20.join)(dir, file);
|
|
17911
18308
|
try {
|
|
17912
|
-
texts.push({ text: (0,
|
|
18309
|
+
texts.push({ text: (0, import_node_fs20.readFileSync)(path2, "utf8"), mtime: (0, import_node_fs20.statSync)(path2).mtimeMs });
|
|
17913
18310
|
} catch {
|
|
17914
18311
|
}
|
|
17915
18312
|
}
|
|
@@ -17923,32 +18320,32 @@ function readCursorPluginLogText() {
|
|
|
17923
18320
|
function cursorPluginCachePinSnapshots() {
|
|
17924
18321
|
const root = cursorPluginCacheRoot();
|
|
17925
18322
|
try {
|
|
17926
|
-
return (0,
|
|
17927
|
-
const path2 = (0,
|
|
17928
|
-
const pluginJson = (0,
|
|
17929
|
-
const hooksJson = (0,
|
|
17930
|
-
const cliBundle = (0,
|
|
17931
|
-
const cursorHook = (0,
|
|
18323
|
+
return (0, import_node_fs20.readdirSync)(root, { withFileTypes: true }).filter((entry) => entry.isDirectory() && !entry.name.startsWith(".")).map((entry) => {
|
|
18324
|
+
const path2 = (0, import_node_path20.join)(root, entry.name);
|
|
18325
|
+
const pluginJson = (0, import_node_path20.join)(path2, ".cursor-plugin", "plugin.json");
|
|
18326
|
+
const hooksJson = (0, import_node_path20.join)(path2, "hooks", "hooks.json");
|
|
18327
|
+
const cliBundle = (0, import_node_path20.join)(path2, "cli", "dist", "index.cjs");
|
|
18328
|
+
const cursorHook = (0, import_node_path20.join)(path2, "scripts", "cursor-hook.mjs");
|
|
17932
18329
|
let version;
|
|
17933
18330
|
try {
|
|
17934
|
-
const raw = JSON.parse((0,
|
|
18331
|
+
const raw = JSON.parse((0, import_node_fs20.readFileSync)(pluginJson, "utf8"));
|
|
17935
18332
|
version = typeof raw.version === "string" ? raw.version : void 0;
|
|
17936
18333
|
} catch {
|
|
17937
18334
|
version = void 0;
|
|
17938
18335
|
}
|
|
17939
18336
|
let isEmpty = true;
|
|
17940
18337
|
try {
|
|
17941
|
-
isEmpty = (0,
|
|
18338
|
+
isEmpty = (0, import_node_fs20.readdirSync)(path2).length === 0;
|
|
17942
18339
|
} catch {
|
|
17943
18340
|
isEmpty = true;
|
|
17944
18341
|
}
|
|
17945
18342
|
return {
|
|
17946
18343
|
name: entry.name,
|
|
17947
18344
|
path: path2,
|
|
17948
|
-
hasPluginJson: (0,
|
|
17949
|
-
hasHooksJson: (0,
|
|
17950
|
-
hasCliBundle: (0,
|
|
17951
|
-
hasCursorHookScript: (0,
|
|
18345
|
+
hasPluginJson: (0, import_node_fs20.existsSync)(pluginJson),
|
|
18346
|
+
hasHooksJson: (0, import_node_fs20.existsSync)(hooksJson),
|
|
18347
|
+
hasCliBundle: (0, import_node_fs20.existsSync)(cliBundle),
|
|
18348
|
+
hasCursorHookScript: (0, import_node_fs20.existsSync)(cursorHook),
|
|
17952
18349
|
isEmpty,
|
|
17953
18350
|
version
|
|
17954
18351
|
};
|
|
@@ -17958,19 +18355,19 @@ function cursorPluginCachePinSnapshots() {
|
|
|
17958
18355
|
}
|
|
17959
18356
|
}
|
|
17960
18357
|
function hubCheckoutForCursorSeed() {
|
|
17961
|
-
const manifest = (0,
|
|
17962
|
-
return (0,
|
|
18358
|
+
const manifest = (0, import_node_path20.join)(process.cwd(), "plugins", "mmi", ".cursor-plugin", "plugin.json");
|
|
18359
|
+
return (0, import_node_fs20.existsSync)(manifest) ? process.cwd() : void 0;
|
|
17963
18360
|
}
|
|
17964
18361
|
function mmiPluginCacheRootSnapshots() {
|
|
17965
18362
|
const roots = [
|
|
17966
|
-
{ surface: "claude", root: (0,
|
|
17967
|
-
{ surface: "codex", root: (0,
|
|
18363
|
+
{ surface: "claude", root: (0, import_node_path20.join)((0, import_node_os7.homedir)(), ".claude", "plugins", "cache", "mutmutco", "mmi") },
|
|
18364
|
+
{ surface: "codex", root: (0, import_node_path20.join)((0, import_node_os7.homedir)(), ".codex", "plugins", "cache", "mutmutco", "mmi") }
|
|
17968
18365
|
];
|
|
17969
18366
|
return roots.flatMap(({ surface, root }) => {
|
|
17970
18367
|
try {
|
|
17971
|
-
const entries = (0,
|
|
18368
|
+
const entries = (0, import_node_fs20.readdirSync)(root, { withFileTypes: true }).map((entry) => ({
|
|
17972
18369
|
name: entry.name,
|
|
17973
|
-
path: (0,
|
|
18370
|
+
path: (0, import_node_path20.join)(root, entry.name),
|
|
17974
18371
|
isDirectory: entry.isDirectory()
|
|
17975
18372
|
}));
|
|
17976
18373
|
return [{ surface, root, entries }];
|
|
@@ -17981,7 +18378,7 @@ function mmiPluginCacheRootSnapshots() {
|
|
|
17981
18378
|
}
|
|
17982
18379
|
function hasNestedMmiChild(versionDir) {
|
|
17983
18380
|
try {
|
|
17984
|
-
return (0,
|
|
18381
|
+
return (0, import_node_fs20.statSync)((0, import_node_path20.join)(versionDir, "mmi")).isDirectory();
|
|
17985
18382
|
} catch {
|
|
17986
18383
|
return false;
|
|
17987
18384
|
}
|
|
@@ -17992,10 +18389,10 @@ function nestedPluginTreeSnapshot() {
|
|
|
17992
18389
|
);
|
|
17993
18390
|
}
|
|
17994
18391
|
function uniqueQuarantineTarget(path2) {
|
|
17995
|
-
if (!(0,
|
|
18392
|
+
if (!(0, import_node_fs20.existsSync)(path2)) return path2;
|
|
17996
18393
|
for (let i = 1; i < 100; i += 1) {
|
|
17997
18394
|
const candidate = `${path2}-${i}`;
|
|
17998
|
-
if (!(0,
|
|
18395
|
+
if (!(0, import_node_fs20.existsSync)(candidate)) return candidate;
|
|
17999
18396
|
}
|
|
18000
18397
|
return `${path2}-${Date.now()}`;
|
|
18001
18398
|
}
|
|
@@ -18004,10 +18401,10 @@ function quarantinePluginCacheDirs(plan) {
|
|
|
18004
18401
|
const failed = [];
|
|
18005
18402
|
for (const move of plan) {
|
|
18006
18403
|
try {
|
|
18007
|
-
if (!(0,
|
|
18404
|
+
if (!(0, import_node_fs20.existsSync)(move.from)) continue;
|
|
18008
18405
|
const target = uniqueQuarantineTarget(move.to);
|
|
18009
|
-
(0,
|
|
18010
|
-
(0,
|
|
18406
|
+
(0, import_node_fs20.mkdirSync)((0, import_node_path20.dirname)(target), { recursive: true });
|
|
18407
|
+
(0, import_node_fs20.renameSync)(move.from, target);
|
|
18011
18408
|
moved += 1;
|
|
18012
18409
|
} catch {
|
|
18013
18410
|
failed.push(move);
|
|
@@ -18026,23 +18423,23 @@ async function robocopyMirrorEmpty(emptyDir, target) {
|
|
|
18026
18423
|
}
|
|
18027
18424
|
async function clearNestedPluginTreeDir(targetPath) {
|
|
18028
18425
|
try {
|
|
18029
|
-
if (!(0,
|
|
18426
|
+
if (!(0, import_node_fs20.existsSync)(targetPath)) return true;
|
|
18030
18427
|
if (isWin) {
|
|
18031
|
-
const emptyDir = (0,
|
|
18032
|
-
(0,
|
|
18428
|
+
const emptyDir = (0, import_node_path20.join)((0, import_node_os7.tmpdir)(), `mmi-empty-${Date.now()}`);
|
|
18429
|
+
(0, import_node_fs20.mkdirSync)(emptyDir, { recursive: true });
|
|
18033
18430
|
try {
|
|
18034
18431
|
await robocopyMirrorEmpty(emptyDir, targetPath);
|
|
18035
|
-
(0,
|
|
18432
|
+
(0, import_node_fs20.rmSync)(targetPath, { recursive: true, force: true });
|
|
18036
18433
|
} finally {
|
|
18037
18434
|
try {
|
|
18038
|
-
(0,
|
|
18435
|
+
(0, import_node_fs20.rmSync)(emptyDir, { recursive: true, force: true });
|
|
18039
18436
|
} catch {
|
|
18040
18437
|
}
|
|
18041
18438
|
}
|
|
18042
|
-
return !(0,
|
|
18439
|
+
return !(0, import_node_fs20.existsSync)(targetPath);
|
|
18043
18440
|
}
|
|
18044
|
-
(0,
|
|
18045
|
-
return !(0,
|
|
18441
|
+
(0, import_node_fs20.rmSync)(targetPath, { recursive: true, force: true });
|
|
18442
|
+
return !(0, import_node_fs20.existsSync)(targetPath);
|
|
18046
18443
|
} catch {
|
|
18047
18444
|
return false;
|
|
18048
18445
|
}
|
|
@@ -18055,18 +18452,18 @@ async function applyNestedPluginTreeCleanup(paths, log) {
|
|
|
18055
18452
|
}
|
|
18056
18453
|
return true;
|
|
18057
18454
|
}
|
|
18058
|
-
var gitignorePath = () => (0,
|
|
18455
|
+
var gitignorePath = () => (0, import_node_path20.join)(process.cwd(), ".gitignore");
|
|
18059
18456
|
function readTextFile(path2) {
|
|
18060
18457
|
try {
|
|
18061
|
-
if (!(0,
|
|
18062
|
-
return (0,
|
|
18458
|
+
if (!(0, import_node_fs20.existsSync)(path2)) return null;
|
|
18459
|
+
return (0, import_node_fs20.readFileSync)(path2, "utf8");
|
|
18063
18460
|
} catch {
|
|
18064
18461
|
return null;
|
|
18065
18462
|
}
|
|
18066
18463
|
}
|
|
18067
18464
|
function mcpDirExists(path2) {
|
|
18068
18465
|
try {
|
|
18069
|
-
return (0,
|
|
18466
|
+
return (0, import_node_fs20.existsSync)(path2);
|
|
18070
18467
|
} catch {
|
|
18071
18468
|
return false;
|
|
18072
18469
|
}
|
|
@@ -18074,60 +18471,60 @@ function mcpDirExists(path2) {
|
|
|
18074
18471
|
function mcpConfigTargets() {
|
|
18075
18472
|
const cwd = process.cwd();
|
|
18076
18473
|
const home = (0, import_node_os7.homedir)();
|
|
18077
|
-
const cursorProjectDir = (0,
|
|
18078
|
-
const cursorUserDir = (0,
|
|
18079
|
-
const codexDir = (0,
|
|
18474
|
+
const cursorProjectDir = (0, import_node_path20.join)(cwd, ".cursor");
|
|
18475
|
+
const cursorUserDir = (0, import_node_path20.join)(home, ".cursor");
|
|
18476
|
+
const codexDir = (0, import_node_path20.join)(home, ".codex");
|
|
18080
18477
|
return [
|
|
18081
18478
|
// Claude Code project MCP — reconciled if present, never conjured (org seeds .cursor/mcp.json, not this).
|
|
18082
18479
|
{
|
|
18083
18480
|
host: "claude-code",
|
|
18084
18481
|
label: "Claude Code project MCP",
|
|
18085
|
-
path: (0,
|
|
18482
|
+
path: (0, import_node_path20.join)(cwd, ".mcp.json"),
|
|
18086
18483
|
format: "json",
|
|
18087
18484
|
present: true,
|
|
18088
18485
|
// parent is the repo root (cwd); the caller gates the whole reconcile on isOrgRepo
|
|
18089
18486
|
isRepoFile: true,
|
|
18090
18487
|
createWhenFileAbsent: false,
|
|
18091
|
-
content: readTextFile((0,
|
|
18488
|
+
content: readTextFile((0, import_node_path20.join)(cwd, ".mcp.json"))
|
|
18092
18489
|
},
|
|
18093
18490
|
// Cursor project MCP — the bootstrap-seeded surface; restored if its .cursor dir exists but the file is gone.
|
|
18094
18491
|
{
|
|
18095
18492
|
host: "cursor-project",
|
|
18096
18493
|
label: "Cursor project MCP",
|
|
18097
|
-
path: (0,
|
|
18494
|
+
path: (0, import_node_path20.join)(cursorProjectDir, "mcp.json"),
|
|
18098
18495
|
format: "json",
|
|
18099
18496
|
present: mcpDirExists(cursorProjectDir),
|
|
18100
18497
|
isRepoFile: true,
|
|
18101
18498
|
createWhenFileAbsent: true,
|
|
18102
|
-
content: readTextFile((0,
|
|
18499
|
+
content: readTextFile((0, import_node_path20.join)(cursorProjectDir, "mcp.json"))
|
|
18103
18500
|
},
|
|
18104
18501
|
// Cursor user MCP — global; written only when Cursor is installed (~/.cursor exists).
|
|
18105
18502
|
{
|
|
18106
18503
|
host: "cursor-user",
|
|
18107
18504
|
label: "Cursor user MCP",
|
|
18108
|
-
path: (0,
|
|
18505
|
+
path: (0, import_node_path20.join)(cursorUserDir, "mcp.json"),
|
|
18109
18506
|
format: "json",
|
|
18110
18507
|
present: mcpDirExists(cursorUserDir),
|
|
18111
18508
|
isRepoFile: false,
|
|
18112
18509
|
createWhenFileAbsent: true,
|
|
18113
|
-
content: readTextFile((0,
|
|
18510
|
+
content: readTextFile((0, import_node_path20.join)(cursorUserDir, "mcp.json"))
|
|
18114
18511
|
},
|
|
18115
18512
|
// Codex user config (TOML) — global; written only when Codex is installed (~/.codex exists).
|
|
18116
18513
|
{
|
|
18117
18514
|
host: "codex",
|
|
18118
18515
|
label: "Codex user config",
|
|
18119
|
-
path: (0,
|
|
18516
|
+
path: (0, import_node_path20.join)(codexDir, "config.toml"),
|
|
18120
18517
|
format: "toml",
|
|
18121
18518
|
present: mcpDirExists(codexDir),
|
|
18122
18519
|
isRepoFile: false,
|
|
18123
18520
|
createWhenFileAbsent: true,
|
|
18124
|
-
content: readTextFile((0,
|
|
18521
|
+
content: readTextFile((0, import_node_path20.join)(codexDir, "config.toml"))
|
|
18125
18522
|
}
|
|
18126
18523
|
];
|
|
18127
18524
|
}
|
|
18128
18525
|
function writeMcpConfigFile(path2, content) {
|
|
18129
18526
|
try {
|
|
18130
|
-
(0,
|
|
18527
|
+
(0, import_node_fs20.writeFileSync)(path2, content, "utf8");
|
|
18131
18528
|
return true;
|
|
18132
18529
|
} catch {
|
|
18133
18530
|
return false;
|
|
@@ -18207,7 +18604,7 @@ function strayBrowserArtifactPaths() {
|
|
|
18207
18604
|
const cwd = process.cwd();
|
|
18208
18605
|
return STRAY_BROWSER_ARTIFACT_DIRS.filter((rel) => {
|
|
18209
18606
|
try {
|
|
18210
|
-
return (0,
|
|
18607
|
+
return (0, import_node_fs20.existsSync)((0, import_node_path20.join)(cwd, rel));
|
|
18211
18608
|
} catch {
|
|
18212
18609
|
return false;
|
|
18213
18610
|
}
|
|
@@ -18215,37 +18612,119 @@ function strayBrowserArtifactPaths() {
|
|
|
18215
18612
|
}
|
|
18216
18613
|
function readGitignore() {
|
|
18217
18614
|
try {
|
|
18218
|
-
return (0,
|
|
18615
|
+
return (0, import_node_fs20.readFileSync)(gitignorePath(), "utf8");
|
|
18219
18616
|
} catch {
|
|
18220
18617
|
return null;
|
|
18221
18618
|
}
|
|
18222
18619
|
}
|
|
18223
18620
|
function writeGitignore(content) {
|
|
18224
18621
|
try {
|
|
18225
|
-
(0,
|
|
18622
|
+
(0, import_node_fs20.writeFileSync)(gitignorePath(), content, "utf8");
|
|
18226
18623
|
return true;
|
|
18227
18624
|
} catch {
|
|
18228
18625
|
return false;
|
|
18229
18626
|
}
|
|
18230
18627
|
}
|
|
18628
|
+
function readRepoJson(rel) {
|
|
18629
|
+
try {
|
|
18630
|
+
return JSON.parse((0, import_node_fs20.readFileSync)((0, import_node_path20.join)(process.cwd(), rel), "utf8"));
|
|
18631
|
+
} catch {
|
|
18632
|
+
return null;
|
|
18633
|
+
}
|
|
18634
|
+
}
|
|
18635
|
+
var ENCODING_SCAN_ROOT_FILES = ["AGENTS.md", "README.md", "CLAUDE.md", "package.json", "opencode.json"];
|
|
18636
|
+
var ENCODING_SCAN_DIRS = [".claude", "hooks", "scripts"];
|
|
18637
|
+
var ENCODING_SCAN_EXTS = /* @__PURE__ */ new Set([".md", ".json", ".jsonc", ".ts", ".js", ".cjs", ".mjs", ".sh", ".ps1", ".py", ".txt"]);
|
|
18638
|
+
var ENCODING_SCAN_MAX_FILES = 400;
|
|
18639
|
+
function listTextFilesUnder(dirRel, out, budget) {
|
|
18640
|
+
let entries;
|
|
18641
|
+
try {
|
|
18642
|
+
entries = (0, import_node_fs20.readdirSync)((0, import_node_path20.join)(process.cwd(), dirRel), { withFileTypes: true });
|
|
18643
|
+
} catch {
|
|
18644
|
+
return;
|
|
18645
|
+
}
|
|
18646
|
+
for (const ent of entries) {
|
|
18647
|
+
if (budget.count >= ENCODING_SCAN_MAX_FILES) return;
|
|
18648
|
+
const rel = `${dirRel}/${ent.name}`;
|
|
18649
|
+
if (ent.isDirectory()) {
|
|
18650
|
+
if (ent.name === "node_modules" || ent.name === "dist" || ent.name === ".git" || ent.name.startsWith(".")) continue;
|
|
18651
|
+
listTextFilesUnder(rel, out, budget);
|
|
18652
|
+
} else if (ent.isFile()) {
|
|
18653
|
+
const ext = ent.name.slice(ent.name.lastIndexOf("."));
|
|
18654
|
+
if (ENCODING_SCAN_EXTS.has(ext)) {
|
|
18655
|
+
out.push(rel);
|
|
18656
|
+
budget.count++;
|
|
18657
|
+
}
|
|
18658
|
+
}
|
|
18659
|
+
}
|
|
18660
|
+
}
|
|
18661
|
+
function managedFileEncodingFindings() {
|
|
18662
|
+
const cwd = process.cwd();
|
|
18663
|
+
const paths = [];
|
|
18664
|
+
for (const f of ENCODING_SCAN_ROOT_FILES) {
|
|
18665
|
+
try {
|
|
18666
|
+
if ((0, import_node_fs20.existsSync)((0, import_node_path20.join)(cwd, f))) paths.push(f);
|
|
18667
|
+
} catch {
|
|
18668
|
+
}
|
|
18669
|
+
}
|
|
18670
|
+
const budget = { count: 0 };
|
|
18671
|
+
for (const d of ENCODING_SCAN_DIRS) listTextFilesUnder(d, paths, budget);
|
|
18672
|
+
const findings = [];
|
|
18673
|
+
for (const rel of paths) {
|
|
18674
|
+
try {
|
|
18675
|
+
const probe = inspectFileEncoding((0, import_node_fs20.readFileSync)((0, import_node_path20.join)(cwd, rel)));
|
|
18676
|
+
if (probe.bom || probe.invalidUtf8 || probe.replacementChars) findings.push({ path: rel, ...probe });
|
|
18677
|
+
} catch {
|
|
18678
|
+
}
|
|
18679
|
+
}
|
|
18680
|
+
return findings;
|
|
18681
|
+
}
|
|
18682
|
+
async function branchAheadBehind(branch) {
|
|
18683
|
+
const spec = `origin/${branch}...${branch}`;
|
|
18684
|
+
try {
|
|
18685
|
+
const { stdout } = await execFileP2("git", ["rev-list", "--left-right", "--count", spec], { timeout: GIT_TIMEOUT_MS });
|
|
18686
|
+
const [behind, ahead] = stdout.trim().split(/\s+/).map((n) => Number.parseInt(n, 10));
|
|
18687
|
+
if (Number.isNaN(behind) || Number.isNaN(ahead)) return null;
|
|
18688
|
+
return { behind, ahead };
|
|
18689
|
+
} catch {
|
|
18690
|
+
return null;
|
|
18691
|
+
}
|
|
18692
|
+
}
|
|
18693
|
+
async function localReleaseBranchSnapshots() {
|
|
18694
|
+
let local;
|
|
18695
|
+
try {
|
|
18696
|
+
const { stdout } = await execFileP2("git", ["for-each-ref", "--format=%(refname:short)", "refs/heads"], { timeout: GIT_TIMEOUT_MS });
|
|
18697
|
+
local = stdout.split(/\r?\n/).map((b) => b.trim()).filter(Boolean);
|
|
18698
|
+
} catch {
|
|
18699
|
+
return [];
|
|
18700
|
+
}
|
|
18701
|
+
const releaseBranches = local.filter((b) => b === "development" || b === "main" || b === "master" || b === "rc" || b.startsWith("release/"));
|
|
18702
|
+
const rows = [];
|
|
18703
|
+
for (const branch of releaseBranches) {
|
|
18704
|
+
const ab = await branchAheadBehind(branch);
|
|
18705
|
+
if (ab) rows.push({ name: branch, ...ab });
|
|
18706
|
+
}
|
|
18707
|
+
return rows;
|
|
18708
|
+
}
|
|
18231
18709
|
async function runDoctor(opts, io = consoleIo, readOrigin) {
|
|
18232
18710
|
if (opts.guide) {
|
|
18233
18711
|
if (opts.json) io.log(JSON.stringify({ resources: [MMI_AGENTIC_ONBOARDING_GUIDE] }, null, 2));
|
|
18234
18712
|
else io.log(MMI_AGENTIC_ONBOARDING_GUIDE.url);
|
|
18235
18713
|
return;
|
|
18236
18714
|
}
|
|
18237
|
-
const repairLocal = !opts.json || Boolean(opts.apply) || Boolean(opts.preflight);
|
|
18715
|
+
const repairLocal = !opts.fast && (!opts.json || Boolean(opts.apply) || Boolean(opts.preflight));
|
|
18238
18716
|
const repoWritesAllowed = !opts.noRepoWrites;
|
|
18239
|
-
const runExtended = Boolean(opts.verbose) || Boolean(opts.json);
|
|
18717
|
+
const runExtended = !opts.fast && (Boolean(opts.verbose) || Boolean(opts.json));
|
|
18718
|
+
const slowSkippedCount = opts.fast ? DOCTOR_EXTENDED_CHECK_LABELS.size : 0;
|
|
18240
18719
|
const checks = [];
|
|
18241
18720
|
const REWRITE_KEY = "url.https://github.com/.insteadOf";
|
|
18242
18721
|
const CLONE_FIX = 'run: git config --global url."https://github.com/".insteadOf "git@github.com:"';
|
|
18243
18722
|
const [login, pathProbe, releasedVersionResolution, cfg, callerArn, cloneProbe, isOrgRepo] = await Promise.all([
|
|
18244
|
-
githubLogin(),
|
|
18723
|
+
opts.fast ? Promise.resolve(void 0) : githubLogin(),
|
|
18245
18724
|
execFileP2(isWin ? "where" : "which", ["mmi-cli"]).then(() => true).catch(() => false),
|
|
18246
|
-
fetchReleasedVersion(),
|
|
18725
|
+
opts.fast ? Promise.resolve({ version: void 0, source: "unavailable" }) : fetchReleasedVersion(),
|
|
18247
18726
|
loadConfig(),
|
|
18248
|
-
awsCallerArn(),
|
|
18727
|
+
opts.fast ? Promise.resolve(void 0) : awsCallerArn(),
|
|
18249
18728
|
execFileP2("git", ["config", "--global", "--get-all", REWRITE_KEY]).then(({ stdout }) => stdout.split("\n").some((l) => l.trim() === "git@github.com:")).catch(() => false),
|
|
18250
18729
|
// unset → repair below
|
|
18251
18730
|
// hub-v3 step-back: org-membership gates the repo-local checks/repairs by the git `origin` remote,
|
|
@@ -18264,7 +18743,7 @@ async function runDoctor(opts, io = consoleIo, readOrigin) {
|
|
|
18264
18743
|
const semverPrefix = /^\d+\.\d+\.\d+/;
|
|
18265
18744
|
const isBehind = (installed2, released) => Boolean(installed2 && released && semverPrefix.test(installed2) && semverPrefix.test(released) && compareVersions(installed2, released) < 0);
|
|
18266
18745
|
const opencodeAdapterStale = isBehind(opencodeInstalledVersionForDoctor(), releasedVersion);
|
|
18267
|
-
const cursorCacheStale = (0,
|
|
18746
|
+
const cursorCacheStale = (0, import_node_fs20.existsSync)(cursorPluginCacheRoot()) && (cursorPluginCachePinSnapshots() ?? []).some((p) => isBehind(p.version, releasedVersion));
|
|
18268
18747
|
const healPlan = doctorHealPlan({
|
|
18269
18748
|
isOrgRepo,
|
|
18270
18749
|
surface,
|
|
@@ -18286,20 +18765,22 @@ async function runDoctor(opts, io = consoleIo, readOrigin) {
|
|
|
18286
18765
|
if (opts.preflight && !healPlan.needsEagerHeal) return;
|
|
18287
18766
|
const eagerHeal = Boolean(opts.preflight) || Boolean(opts.banner) && healPlan.needsEagerHeal;
|
|
18288
18767
|
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
|
-
|
|
18768
|
+
const repairFull = !opts.fast && (!opts.json && !opts.banner || Boolean(opts.apply) || Boolean(opts.preflight) || Boolean(opts.banner) && healPlan.needsEagerHeal);
|
|
18769
|
+
if (!opts.fast) {
|
|
18770
|
+
let ghInstalled = true;
|
|
18771
|
+
if (!login) {
|
|
18772
|
+
try {
|
|
18773
|
+
await execFileP2("gh", ["--version"]);
|
|
18774
|
+
} catch {
|
|
18775
|
+
ghInstalled = false;
|
|
18776
|
+
}
|
|
18296
18777
|
}
|
|
18778
|
+
checks.push(buildGithubAuthCheck({ login, ghInstalled }));
|
|
18297
18779
|
}
|
|
18298
|
-
checks.push(buildGithubAuthCheck({ login, ghInstalled }));
|
|
18299
18780
|
let onPath = pathProbe;
|
|
18300
18781
|
if (!onPath) {
|
|
18301
18782
|
const root = process.env.CLAUDE_PLUGIN_ROOT;
|
|
18302
|
-
if (root && (0,
|
|
18783
|
+
if (root && (0, import_node_fs20.existsSync)(`${root}/bin/mmi-cli${isWin ? ".cmd" : ""}`)) onPath = true;
|
|
18303
18784
|
}
|
|
18304
18785
|
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
18786
|
const reloadHint = reloadAction(surface);
|
|
@@ -18342,7 +18823,7 @@ async function runDoctor(opts, io = consoleIo, readOrigin) {
|
|
|
18342
18823
|
return;
|
|
18343
18824
|
}
|
|
18344
18825
|
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);
|
|
18826
|
+
const hubVersionInfo = opts.fast ? null : await fetchHubVersionInfo(cfg.sagaApiUrl);
|
|
18346
18827
|
const installedVersion = resolveClientVersion();
|
|
18347
18828
|
checks.push(
|
|
18348
18829
|
buildHubCompatCheck({
|
|
@@ -18360,6 +18841,12 @@ async function runDoctor(opts, io = consoleIo, readOrigin) {
|
|
|
18360
18841
|
releasedVersion
|
|
18361
18842
|
})
|
|
18362
18843
|
);
|
|
18844
|
+
checks.push(
|
|
18845
|
+
buildCliVersionDriftCheck({
|
|
18846
|
+
currentVersion: installedVersion,
|
|
18847
|
+
releasedVersion
|
|
18848
|
+
})
|
|
18849
|
+
);
|
|
18363
18850
|
}
|
|
18364
18851
|
if (runExtended) {
|
|
18365
18852
|
checks.push(buildAwsCrossAccountCheck({ callerArn }));
|
|
@@ -18446,8 +18933,19 @@ async function runDoctor(opts, io = consoleIo, readOrigin) {
|
|
|
18446
18933
|
checks.push(gitignoreCheck);
|
|
18447
18934
|
checks.push(buildRepoLocalWorktreeCheck({
|
|
18448
18935
|
isOrgRepo,
|
|
18449
|
-
hasRepoLocalWorktrees: (0,
|
|
18936
|
+
hasRepoLocalWorktrees: (0, import_node_fs20.existsSync)((0, import_node_path20.join)(process.cwd(), ".worktrees"))
|
|
18450
18937
|
}));
|
|
18938
|
+
if (isOrgRepo) {
|
|
18939
|
+
const pkg = readRepoJson("package.json");
|
|
18940
|
+
const lock = readRepoJson("package-lock.json");
|
|
18941
|
+
checks.push(buildLockfileOptionalsCheck({
|
|
18942
|
+
isOrgRepo,
|
|
18943
|
+
packageJsonOptionalDeps: pkg?.optionalDependencies ?? null,
|
|
18944
|
+
lockfilePackages: lock?.packages ?? null
|
|
18945
|
+
}));
|
|
18946
|
+
} else {
|
|
18947
|
+
checks.push(buildLockfileOptionalsCheck({ isOrgRepo, packageJsonOptionalDeps: null, lockfilePackages: null }));
|
|
18948
|
+
}
|
|
18451
18949
|
let driftCheck = buildPluginConfigDriftCheck({ isOrgRepo, installed, surface });
|
|
18452
18950
|
if (!driftCheck.ok && driftCheck.recordsToWrite && repairLocal) {
|
|
18453
18951
|
if (backupAndWriteInstalledPlugins(driftCheck.recordsToWrite, driftCheck.pluginId)) {
|
|
@@ -18632,7 +19130,7 @@ async function runDoctor(opts, io = consoleIo, readOrigin) {
|
|
|
18632
19130
|
return versions.sort((a, b) => compareVersions(b, a))[0];
|
|
18633
19131
|
};
|
|
18634
19132
|
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);
|
|
19133
|
+
const codexActiveVersion = () => !opts.fast && isOrgRepo && releasedVersion ? codexEnabledMmiVersion() : Promise.resolve(void 0);
|
|
18636
19134
|
let cacheCleanupCheck = buildMmiPluginCacheCleanupCheck({
|
|
18637
19135
|
isOrgRepo,
|
|
18638
19136
|
roots: mmiPluginCacheRootSnapshots(),
|
|
@@ -18735,7 +19233,7 @@ async function runDoctor(opts, io = consoleIo, readOrigin) {
|
|
|
18735
19233
|
}
|
|
18736
19234
|
checks.push(nestedPluginTreeCheck);
|
|
18737
19235
|
const cursorCacheRoot = cursorPluginCacheRoot();
|
|
18738
|
-
const cursorCacheRootExists = (0,
|
|
19236
|
+
const cursorCacheRootExists = (0, import_node_fs20.existsSync)(cursorCacheRoot);
|
|
18739
19237
|
let cursorPins = cursorPluginCachePinSnapshots() ?? [];
|
|
18740
19238
|
checks.push(
|
|
18741
19239
|
buildCursorPluginCacheCleanupCheck({
|
|
@@ -18763,7 +19261,7 @@ async function runDoctor(opts, io = consoleIo, readOrigin) {
|
|
|
18763
19261
|
cacheRootExists: cursorCacheRootExists,
|
|
18764
19262
|
hubCheckout: hubCheckoutForCursorSeed(),
|
|
18765
19263
|
execFileP: execFileP2,
|
|
18766
|
-
mkdtemp: (prefix) => (0, import_promises2.mkdtemp)((0,
|
|
19264
|
+
mkdtemp: (prefix) => (0, import_promises2.mkdtemp)((0, import_node_path20.join)((0, import_node_os7.tmpdir)(), prefix)),
|
|
18767
19265
|
log: (m) => io.err(m)
|
|
18768
19266
|
});
|
|
18769
19267
|
if (seeded) {
|
|
@@ -18840,6 +19338,23 @@ async function runDoctor(opts, io = consoleIo, readOrigin) {
|
|
|
18840
19338
|
cacheVersions: sessionCacheVersions
|
|
18841
19339
|
})
|
|
18842
19340
|
);
|
|
19341
|
+
{
|
|
19342
|
+
let redactorFailureLines = [];
|
|
19343
|
+
try {
|
|
19344
|
+
redactorFailureLines = (0, import_node_fs20.readFileSync)(activityLogPath(process.cwd()), "utf8").split("\n");
|
|
19345
|
+
} catch {
|
|
19346
|
+
redactorFailureLines = [];
|
|
19347
|
+
}
|
|
19348
|
+
const redactorSummary = summarizeRedactorFailures(redactorFailureLines);
|
|
19349
|
+
checks.push(
|
|
19350
|
+
buildRedactorLivenessCheck({
|
|
19351
|
+
isOrgRepo,
|
|
19352
|
+
failedCount: redactorSummary.failedCount,
|
|
19353
|
+
lastFailedTs: redactorSummary.lastFailedTs,
|
|
19354
|
+
lastFailedAction: redactorSummary.lastFailedAction
|
|
19355
|
+
})
|
|
19356
|
+
);
|
|
19357
|
+
}
|
|
18843
19358
|
if (runExtended) {
|
|
18844
19359
|
checks.push(
|
|
18845
19360
|
buildBrowserArtifactsCheck({
|
|
@@ -18864,6 +19379,14 @@ async function runDoctor(opts, io = consoleIo, readOrigin) {
|
|
|
18864
19379
|
checks.push(buildGitGcCheck(await gcPlan("origin", 200)));
|
|
18865
19380
|
} catch {
|
|
18866
19381
|
}
|
|
19382
|
+
try {
|
|
19383
|
+
checks.push(buildManagedFileEncodingCheck({ isOrgRepo, findings: managedFileEncodingFindings() }));
|
|
19384
|
+
} catch {
|
|
19385
|
+
}
|
|
19386
|
+
try {
|
|
19387
|
+
checks.push(buildStaleLocalBranchCheck({ isOrgRepo, branches: await localReleaseBranchSnapshots() }));
|
|
19388
|
+
} catch {
|
|
19389
|
+
}
|
|
18867
19390
|
} else if (opts.apply && repoWritesAllowed) {
|
|
18868
19391
|
const scratchRun = executeScratchGc(repoRoot, { apply: true });
|
|
18869
19392
|
if (scratchRun.applied?.pruned.length) {
|
|
@@ -18884,6 +19407,7 @@ async function runDoctor(opts, io = consoleIo, readOrigin) {
|
|
|
18884
19407
|
healedCount,
|
|
18885
19408
|
pluginReloadRequired,
|
|
18886
19409
|
reloadHint,
|
|
19410
|
+
slowSkippedCount,
|
|
18887
19411
|
// Surface-aware update report (#865): the per-surface version snapshot + copy-paste update recipes, so
|
|
18888
19412
|
// an agent told "make sure the CLI and plugin are up to date" can run the right command per surface and
|
|
18889
19413
|
// echo back an unambiguous version line (CLI / Claude plugin / Codex marketplace / Codex active cache).
|
|
@@ -18909,6 +19433,7 @@ function emitDoctorReport(opts, io, ctx) {
|
|
|
18909
19433
|
const { checks, surface, healedCount, pluginReloadRequired, reloadHint } = ctx;
|
|
18910
19434
|
const gaps = checks.filter((c) => !c.ok);
|
|
18911
19435
|
if (opts.preflight) {
|
|
19436
|
+
process.exitCode = doctorExitCode(checks);
|
|
18912
19437
|
const outcome = preflightOutcome({ gaps, needsEagerHeal: ctx.needsEagerHeal, surface });
|
|
18913
19438
|
if (outcome.line) io.err(outcome.line);
|
|
18914
19439
|
if (pluginReloadRequired) io.err(pluginAutonomousHaltLine(reloadHint));
|
|
@@ -18935,7 +19460,8 @@ function emitDoctorReport(opts, io, ctx) {
|
|
|
18935
19460
|
healedCount,
|
|
18936
19461
|
pluginReloadRequired,
|
|
18937
19462
|
reloadHint,
|
|
18938
|
-
verbose: Boolean(opts.verbose)
|
|
19463
|
+
verbose: Boolean(opts.verbose),
|
|
19464
|
+
skippedSlowChecks: ctx.slowSkippedCount
|
|
18939
19465
|
})) io.log(line);
|
|
18940
19466
|
for (const r of resources) io.log(`Resource: ${r.label} \u2014 ${r.url}`);
|
|
18941
19467
|
}
|
|
@@ -18958,23 +19484,23 @@ function mergeGuardHook(settings) {
|
|
|
18958
19484
|
next.hooks = hooks;
|
|
18959
19485
|
return next;
|
|
18960
19486
|
}
|
|
18961
|
-
var userScopeSettingsPath = (surface = detectSurface(process.env)) => (0,
|
|
19487
|
+
var userScopeSettingsPath = (surface = detectSurface(process.env)) => (0, import_node_path20.join)((0, import_node_os7.homedir)(), surface === "codex" ? ".codex" : ".claude", "settings.json");
|
|
18962
19488
|
function ensureUserScopeGuardHook(opts = {}) {
|
|
18963
19489
|
const path2 = opts.settingsPath ?? userScopeSettingsPath();
|
|
18964
19490
|
try {
|
|
18965
19491
|
let current = null;
|
|
18966
|
-
if ((0,
|
|
19492
|
+
if ((0, import_node_fs20.existsSync)(path2)) {
|
|
18967
19493
|
try {
|
|
18968
|
-
current = JSON.parse((0,
|
|
19494
|
+
current = JSON.parse((0, import_node_fs20.readFileSync)(path2, "utf8"));
|
|
18969
19495
|
} catch {
|
|
18970
19496
|
return "failed";
|
|
18971
19497
|
}
|
|
18972
19498
|
}
|
|
18973
19499
|
if (settingsHasGuardHook(current)) return "already";
|
|
18974
19500
|
const merged = mergeGuardHook(current);
|
|
18975
|
-
(0,
|
|
18976
|
-
if ((0,
|
|
18977
|
-
(0,
|
|
19501
|
+
(0, import_node_fs20.mkdirSync)((0, import_node_path20.dirname)(path2), { recursive: true });
|
|
19502
|
+
if ((0, import_node_fs20.existsSync)(path2)) (0, import_node_fs20.copyFileSync)(path2, `${path2}.bak`);
|
|
19503
|
+
(0, import_node_fs20.writeFileSync)(path2, `${JSON.stringify(merged, null, 2)}
|
|
18978
19504
|
`, "utf8");
|
|
18979
19505
|
return "written";
|
|
18980
19506
|
} catch {
|
|
@@ -19111,7 +19637,7 @@ async function applyGcPlan(plan, remote) {
|
|
|
19111
19637
|
cleanupBranch: (branch, expectedHeadOid) => cleanupPrMergeLocalBranch(branch.branch, {
|
|
19112
19638
|
beforeWorktrees,
|
|
19113
19639
|
startingPath: branch.worktreePath,
|
|
19114
|
-
pathExists: (p) => (0,
|
|
19640
|
+
pathExists: (p) => (0, import_node_fs21.existsSync)(p),
|
|
19115
19641
|
execGit: async (args) => (await execFileP2("git", args, { timeout: GIT_TIMEOUT_MS })).stdout,
|
|
19116
19642
|
teardownWorktreeStage,
|
|
19117
19643
|
deferredStore,
|
|
@@ -19134,7 +19660,7 @@ async function applyGcPlan(plan, remote) {
|
|
|
19134
19660
|
for (const wt of plan.worktreeDirs) {
|
|
19135
19661
|
try {
|
|
19136
19662
|
const cleanupTarget = resolveSafeSiblingWorktreeCleanupTarget(wt.path, siblingRoot, {
|
|
19137
|
-
realpath: (path2) => (0,
|
|
19663
|
+
realpath: (path2) => (0, import_node_fs21.realpathSync)(path2)
|
|
19138
19664
|
});
|
|
19139
19665
|
if (!cleanupTarget.ok) {
|
|
19140
19666
|
result.failed.push(`${wt.path}: ${cleanupTarget.reason}`);
|
|
@@ -19174,16 +19700,21 @@ async function requireFreshTrainCli(commandName) {
|
|
|
19174
19700
|
throw new Error(staleTrainCliMessage(report, commandName));
|
|
19175
19701
|
}
|
|
19176
19702
|
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)");
|
|
19703
|
+
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
19704
|
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,
|
|
19705
|
+
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) => {
|
|
19706
|
+
const path2 = (0, import_node_path21.join)(process.cwd(), ".gitignore");
|
|
19707
|
+
const current = (0, import_node_fs21.existsSync)(path2) ? (0, import_node_fs21.readFileSync)(path2, "utf8") : null;
|
|
19182
19708
|
const plan = planManagedGitignore(current);
|
|
19183
19709
|
const drift = [...plan.added.map((l) => `+${l}`), ...plan.removed.map((l) => `-${l}`)].join(", ") || "block normalize";
|
|
19710
|
+
if (opts.json) {
|
|
19711
|
+
console.log(JSON.stringify(plan, null, 2));
|
|
19712
|
+
if (!opts.write && plan.changed) process.exitCode = 1;
|
|
19713
|
+
return;
|
|
19714
|
+
}
|
|
19184
19715
|
if (opts.write) {
|
|
19185
19716
|
if (plan.changed) {
|
|
19186
|
-
(0,
|
|
19717
|
+
(0, import_node_fs21.writeFileSync)(path2, plan.content, "utf8");
|
|
19187
19718
|
console.log(`mmi-cli rules gitignore: updated .gitignore (${drift})`);
|
|
19188
19719
|
} else {
|
|
19189
19720
|
console.log("mmi-cli rules gitignore: up to date");
|
|
@@ -19348,7 +19879,7 @@ function runWorktreeInstall(command, cwd, quiet) {
|
|
|
19348
19879
|
async function primaryCheckoutRoot(worktreeRoot) {
|
|
19349
19880
|
try {
|
|
19350
19881
|
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,
|
|
19882
|
+
return out ? (0, import_node_path21.dirname)(out) : void 0;
|
|
19352
19883
|
} catch {
|
|
19353
19884
|
return void 0;
|
|
19354
19885
|
}
|
|
@@ -19363,26 +19894,26 @@ function makeProvisionDeps(worktreeRoot, quiet, log) {
|
|
|
19363
19894
|
function acquireWorktreeSetupLock(worktreeRoot) {
|
|
19364
19895
|
const lockPath = repoRuntimeStatePath(worktreeRoot, "worktree-setup.lock");
|
|
19365
19896
|
const take = () => {
|
|
19366
|
-
const fd = (0,
|
|
19897
|
+
const fd = (0, import_node_fs21.openSync)(lockPath, "wx");
|
|
19367
19898
|
try {
|
|
19368
|
-
(0,
|
|
19899
|
+
(0, import_node_fs21.writeSync)(fd, String(Date.now()));
|
|
19369
19900
|
} finally {
|
|
19370
|
-
(0,
|
|
19901
|
+
(0, import_node_fs21.closeSync)(fd);
|
|
19371
19902
|
}
|
|
19372
19903
|
return () => {
|
|
19373
19904
|
try {
|
|
19374
|
-
(0,
|
|
19905
|
+
(0, import_node_fs21.rmSync)(lockPath, { force: true });
|
|
19375
19906
|
} catch {
|
|
19376
19907
|
}
|
|
19377
19908
|
};
|
|
19378
19909
|
};
|
|
19379
19910
|
try {
|
|
19380
|
-
(0,
|
|
19911
|
+
(0, import_node_fs21.mkdirSync)((0, import_node_path21.dirname)(lockPath), { recursive: true });
|
|
19381
19912
|
return take();
|
|
19382
19913
|
} catch {
|
|
19383
19914
|
try {
|
|
19384
|
-
if (Date.now() - (0,
|
|
19385
|
-
(0,
|
|
19915
|
+
if (Date.now() - (0, import_node_fs21.statSync)(lockPath).mtimeMs > WORKTREE_SETUP_LOCK_TTL_MS) {
|
|
19916
|
+
(0, import_node_fs21.rmSync)(lockPath, { force: true });
|
|
19386
19917
|
return take();
|
|
19387
19918
|
}
|
|
19388
19919
|
} catch {
|
|
@@ -19400,7 +19931,21 @@ worktree.command("create <branch>").description("create a worktree from a base r
|
|
|
19400
19931
|
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
19932
|
if (fetchErr) console.error(` warning: could not fetch ${o.remote}/${fetchBranch} (${fetchErr}); base ${base} may be stale`);
|
|
19402
19933
|
}
|
|
19403
|
-
await
|
|
19934
|
+
await addWorktreeRobust(wtPath, branch, base, {
|
|
19935
|
+
git: async (args) => (await execFileP2("git", args, { timeout: GH_MUTATION_TIMEOUT_MS })).stdout,
|
|
19936
|
+
revParse: async (ref) => {
|
|
19937
|
+
try {
|
|
19938
|
+
return (await execFileP2("git", ["rev-parse", "--verify", ref], { timeout: GIT_TIMEOUT_MS })).stdout.trim() || void 0;
|
|
19939
|
+
} catch {
|
|
19940
|
+
return void 0;
|
|
19941
|
+
}
|
|
19942
|
+
},
|
|
19943
|
+
deleteBranch: (b) => execFileP2("git", ["branch", "-D", b], { timeout: GIT_TIMEOUT_MS }).then(() => void 0),
|
|
19944
|
+
sleep: (ms) => new Promise((resolve5) => setTimeout(resolve5, ms)),
|
|
19945
|
+
log: (m) => {
|
|
19946
|
+
if (!o.json) console.error(` ${m}`);
|
|
19947
|
+
}
|
|
19948
|
+
});
|
|
19404
19949
|
const report = await provisionWorktree(wtPath, makeProvisionDeps(wtPath, Boolean(o.json), (m) => {
|
|
19405
19950
|
if (!o.json) console.error(` ${m}`);
|
|
19406
19951
|
}));
|
|
@@ -19519,7 +20064,7 @@ async function attachToProject(issueNumber, repo, priority) {
|
|
|
19519
20064
|
var ghRunner = async (args, timeoutMs) => (await execFileP2("gh", args, { timeout: timeoutMs })).stdout;
|
|
19520
20065
|
function scheduleRelatedDiscovery(o) {
|
|
19521
20066
|
try {
|
|
19522
|
-
const args = ["issue", "discover-related", "--number", String(o.number), "--title", o.title, "--body", o.body];
|
|
20067
|
+
const args = ["issue", "discover-related", "--number", String(o.number), "--title", o.title, "--body", o.body, "--fail-soft"];
|
|
19523
20068
|
if (o.repo) args.push("--repo", o.repo);
|
|
19524
20069
|
spawnDetachedSelf(args, { spawn: import_node_child_process12.spawn, execPath: process.execPath, scriptPath: process.argv[1] }, { cwd: process.cwd() });
|
|
19525
20070
|
} catch {
|
|
@@ -19872,7 +20417,7 @@ project.command("set [owner/repo]").description("upsert project META (idempotent
|
|
|
19872
20417
|
const vars = rawValues("--var");
|
|
19873
20418
|
if (o.secretsFile) {
|
|
19874
20419
|
try {
|
|
19875
|
-
vars.push(`secrets=${(0,
|
|
20420
|
+
vars.push(`secrets=${(0, import_node_fs21.readFileSync)(o.secretsFile, "utf8")}`);
|
|
19876
20421
|
} catch (e) {
|
|
19877
20422
|
return fail(`project set: cannot read --secrets-file ${o.secretsFile}: ${e.message}`);
|
|
19878
20423
|
}
|
|
@@ -20176,7 +20721,7 @@ issue.command("view <number>").description("read an issue as structured JSON \u2
|
|
|
20176
20721
|
return fail(`issue view: ${raw}${argvNote}`);
|
|
20177
20722
|
}
|
|
20178
20723
|
});
|
|
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) => {
|
|
20724
|
+
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
20725
|
const number = Number(o.number);
|
|
20181
20726
|
if (!Number.isInteger(number) || number <= 0) return fail("issue discover-related: --number must be a positive integer");
|
|
20182
20727
|
const repo = await resolveRepo(o.repo);
|
|
@@ -20208,7 +20753,10 @@ issue.command("discover-related").description("find related issues for an existi
|
|
|
20208
20753
|
]);
|
|
20209
20754
|
if (viewed.comments.some((comment) => comment.body.includes(relatedMarker(number)))) return;
|
|
20210
20755
|
await execFileP2("gh", ["issue", "comment", String(number), "--repo", repo, "--body", buildRelatedComment(number, candidates)], { timeout: GH_MUTATION_TIMEOUT_MS });
|
|
20211
|
-
} catch {
|
|
20756
|
+
} catch (e) {
|
|
20757
|
+
if (o.failSoft) return;
|
|
20758
|
+
const err = e;
|
|
20759
|
+
return fail(`issue discover-related: ${(err.stderr || err.message || String(e)).trim()}`);
|
|
20212
20760
|
}
|
|
20213
20761
|
});
|
|
20214
20762
|
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) => {
|
|
@@ -20427,14 +20975,16 @@ pr.command("create").description("create a PR and print {number,url} JSON").opti
|
|
|
20427
20975
|
const created = await ghCreate(buildPrArgs({ title, body, base: o.base, head: o.head, repo: o.repo }));
|
|
20428
20976
|
console.log(JSON.stringify(created));
|
|
20429
20977
|
});
|
|
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
|
|
20978
|
+
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
20979
|
const n = Number(number);
|
|
20432
20980
|
if (!Number.isInteger(n) || n <= 0) return fail("pr view: <number> must be a positive integer");
|
|
20433
20981
|
const repo = await resolveRepo(o.repo);
|
|
20434
20982
|
if (!repo) return fail("pr view: could not resolve repo (pass --repo <owner/repo>)");
|
|
20435
|
-
const fields =
|
|
20983
|
+
const fields = normalizeIssueViewJsonFields(o.json);
|
|
20984
|
+
const defaultPrFields = "number,title,state,url,isDraft,mergeable,mergedAt,mergeCommit,headRefName,baseRefName,author,labels";
|
|
20985
|
+
const effective = fields === normalizeIssueViewJsonFields(void 0) ? defaultPrFields : fields;
|
|
20436
20986
|
try {
|
|
20437
|
-
const data = await ghJson(["pr", "view", String(n), "--repo", repo, "--json",
|
|
20987
|
+
const data = await ghJson(["pr", "view", String(n), "--repo", repo, "--json", effective]);
|
|
20438
20988
|
console.log(JSON.stringify(data));
|
|
20439
20989
|
} catch (e) {
|
|
20440
20990
|
const err = e;
|
|
@@ -20442,9 +20992,9 @@ pr.command("view <number>").description("read a PR as structured JSON (merged st
|
|
|
20442
20992
|
}
|
|
20443
20993
|
});
|
|
20444
20994
|
async function listCiWorkflowPaths(cwd = process.cwd()) {
|
|
20445
|
-
const wfDir = (0,
|
|
20446
|
-
if (!(0,
|
|
20447
|
-
return (0,
|
|
20995
|
+
const wfDir = (0, import_node_path21.join)(cwd, ".github", "workflows");
|
|
20996
|
+
if (!(0, import_node_fs21.existsSync)(wfDir)) return [];
|
|
20997
|
+
return (0, import_node_fs21.readdirSync)(wfDir).filter((name) => /\.(ya?ml)$/i.test(name)).map((name) => `.github/workflows/${name}`);
|
|
20448
20998
|
}
|
|
20449
20999
|
async function resolveMergeCiPolicyForCheckout(repoOpt) {
|
|
20450
21000
|
const repo = repoOpt ?? await resolveRepo();
|
|
@@ -20456,16 +21006,29 @@ async function resolveMergeCiPolicyForCheckout(repoOpt) {
|
|
|
20456
21006
|
}
|
|
20457
21007
|
function ciAuditDeps() {
|
|
20458
21008
|
const cfgPromise = loadConfig();
|
|
21009
|
+
const root = hubRoot();
|
|
20459
21010
|
return {
|
|
20460
21011
|
client: defaultGitHubClient(),
|
|
20461
21012
|
listProjects: async () => fetchProjectsList(registryClientDeps(await cfgPromise)),
|
|
20462
21013
|
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
|
-
|
|
21014
|
+
// Continuous CI delivery (#1550): the gate re-seed renders from the Hub's on-disk seed templates.
|
|
21015
|
+
// Resolve from the installed package root so the reconcile works from any org repo checkout; when the
|
|
21016
|
+
// Hub root is not locatable (e.g. a global npm install without a local checkout), return null so the
|
|
21017
|
+
// gate re-seed step is skipped gracefully rather than failing mid-run.
|
|
21018
|
+
readSeedFile: (path2) => {
|
|
21019
|
+
if (!root) return null;
|
|
21020
|
+
const fullPath = (0, import_node_path21.join)(root, path2);
|
|
21021
|
+
return (0, import_node_fs21.existsSync)(fullPath) ? (0, import_node_fs21.readFileSync)(fullPath, "utf8") : null;
|
|
21022
|
+
}
|
|
20467
21023
|
};
|
|
20468
21024
|
}
|
|
21025
|
+
function hubRoot() {
|
|
21026
|
+
const fromPkg = (0, import_node_path21.join)(__dirname, "..", "..");
|
|
21027
|
+
const marker = "skills/bootstrap/seeds/manifest.json";
|
|
21028
|
+
if ((0, import_node_fs21.existsSync)((0, import_node_path21.join)(fromPkg, marker))) return fromPkg;
|
|
21029
|
+
if ((0, import_node_fs21.existsSync)((0, import_node_path21.join)(process.cwd(), marker))) return process.cwd();
|
|
21030
|
+
return null;
|
|
21031
|
+
}
|
|
20469
21032
|
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
21033
|
const result = await resolveMergeCiPolicyForCheckout(o.repo);
|
|
20471
21034
|
if (o.json) return printLine(JSON.stringify(result));
|
|
@@ -20539,6 +21102,12 @@ pr.command("land <number>").description("agent merge path (#1440): train probe \
|
|
|
20539
21102
|
checksPassing: checks === "success" || checks === "no-checks-reported"
|
|
20540
21103
|
};
|
|
20541
21104
|
},
|
|
21105
|
+
readPrState: async (prNumber, repo) => {
|
|
21106
|
+
const args = repo ? ["--repo", repo] : [];
|
|
21107
|
+
const result2 = await readGhPrStateWithRetry(async () => (await execFileP2("gh", ["pr", "view", prNumber, ...args, "--json", "mergeStateStatus,mergeable,state"], { timeout: GC_GH_TIMEOUT_MS2 })).stdout);
|
|
21108
|
+
if (!result2.ok) throw new Error(`could not read PR state: ${result2.error}`);
|
|
21109
|
+
return JSON.parse(result2.state);
|
|
21110
|
+
},
|
|
20542
21111
|
mergeAuto: async (prNumber, repo) => {
|
|
20543
21112
|
const args = repo ? ["--repo", repo] : [];
|
|
20544
21113
|
const readMergeState = () => readGhPrStateWithRetry(async () => (await execFileP2("gh", ["pr", "view", prNumber, ...args, "--json", "state", "--jq", ".state"], { timeout: GC_GH_TIMEOUT_MS2 })).stdout);
|
|
@@ -20633,7 +21202,7 @@ async function createDeferredWorktreeStore() {
|
|
|
20633
21202
|
},
|
|
20634
21203
|
write: async (entries) => {
|
|
20635
21204
|
try {
|
|
20636
|
-
await (0, import_promises3.mkdir)((0,
|
|
21205
|
+
await (0, import_promises3.mkdir)((0, import_node_path21.dirname)(registryPath), { recursive: true });
|
|
20637
21206
|
await (0, import_promises3.writeFile)(registryPath, serializeDeferredWorktrees(entries), "utf8");
|
|
20638
21207
|
} catch {
|
|
20639
21208
|
}
|
|
@@ -20647,13 +21216,13 @@ var realWorktreeDirRemover = {
|
|
|
20647
21216
|
probe: (p) => {
|
|
20648
21217
|
let st;
|
|
20649
21218
|
try {
|
|
20650
|
-
st = (0,
|
|
21219
|
+
st = (0, import_node_fs21.lstatSync)(p);
|
|
20651
21220
|
} catch {
|
|
20652
21221
|
return null;
|
|
20653
21222
|
}
|
|
20654
21223
|
if (st.isSymbolicLink()) return "link";
|
|
20655
21224
|
try {
|
|
20656
|
-
(0,
|
|
21225
|
+
(0, import_node_fs21.readlinkSync)(p);
|
|
20657
21226
|
return "link";
|
|
20658
21227
|
} catch {
|
|
20659
21228
|
}
|
|
@@ -20661,7 +21230,7 @@ var realWorktreeDirRemover = {
|
|
|
20661
21230
|
},
|
|
20662
21231
|
readdir: (p) => {
|
|
20663
21232
|
try {
|
|
20664
|
-
return (0,
|
|
21233
|
+
return (0, import_node_fs21.readdirSync)(p);
|
|
20665
21234
|
} catch {
|
|
20666
21235
|
return [];
|
|
20667
21236
|
}
|
|
@@ -20670,9 +21239,9 @@ var realWorktreeDirRemover = {
|
|
|
20670
21239
|
// leaving the target); a file symlink with unlink. rmdir first, fall back to unlink.
|
|
20671
21240
|
detachLink: (p) => {
|
|
20672
21241
|
try {
|
|
20673
|
-
(0,
|
|
21242
|
+
(0, import_node_fs21.rmdirSync)(p);
|
|
20674
21243
|
} catch {
|
|
20675
|
-
(0,
|
|
21244
|
+
(0, import_node_fs21.unlinkSync)(p);
|
|
20676
21245
|
}
|
|
20677
21246
|
},
|
|
20678
21247
|
removeTree: (p) => (0, import_promises3.rm)(p, { recursive: true, force: true, maxRetries: 5, retryDelay: 200 })
|
|
@@ -20702,9 +21271,9 @@ async function worktreeHasStageState(worktreePath) {
|
|
|
20702
21271
|
}
|
|
20703
21272
|
}
|
|
20704
21273
|
function stageStateFileBelongsToWorktree(statePath, worktreePath) {
|
|
20705
|
-
if (!(0,
|
|
21274
|
+
if (!(0, import_node_fs21.existsSync)(statePath)) return false;
|
|
20706
21275
|
try {
|
|
20707
|
-
const state = JSON.parse((0,
|
|
21276
|
+
const state = JSON.parse((0, import_node_fs21.readFileSync)(statePath, "utf8"));
|
|
20708
21277
|
const recordedCwd = typeof state.identity?.cwd === "string" ? state.identity.cwd : typeof state.cwd === "string" ? state.cwd : "";
|
|
20709
21278
|
return Boolean(recordedCwd && isPathUnderDirectory(recordedCwd, worktreePath));
|
|
20710
21279
|
} catch {
|
|
@@ -20776,7 +21345,7 @@ pr.command("merge <number>").description("merge a PR (squash by default) and cle
|
|
|
20776
21345
|
localCleanup = await cleanupPrMergeLocalBranch(headRef, {
|
|
20777
21346
|
beforeWorktrees,
|
|
20778
21347
|
startingPath,
|
|
20779
|
-
pathExists: (p) => (0,
|
|
21348
|
+
pathExists: (p) => (0, import_node_fs21.existsSync)(p),
|
|
20780
21349
|
execGit: async (args) => (await execFileP2("git", args, { timeout: GIT_TIMEOUT_MS })).stdout,
|
|
20781
21350
|
teardownWorktreeStage,
|
|
20782
21351
|
deferredStore,
|
|
@@ -20997,7 +21566,7 @@ function stageScopedRunOpts(o) {
|
|
|
20997
21566
|
};
|
|
20998
21567
|
}
|
|
20999
21568
|
function printLine(value) {
|
|
21000
|
-
(0,
|
|
21569
|
+
(0, import_node_fs21.writeSync)(1, `${value}
|
|
21001
21570
|
`);
|
|
21002
21571
|
}
|
|
21003
21572
|
function stageKeepAlive() {
|
|
@@ -21011,8 +21580,8 @@ async function resolveStage() {
|
|
|
21011
21580
|
const portRange = portRangeMeta && typeof portRangeMeta.start === "number" && typeof portRangeMeta.end === "number" ? [portRangeMeta.start, portRangeMeta.end] : void 0;
|
|
21012
21581
|
return decideStage({
|
|
21013
21582
|
registry: { deployModel: project2?.deployModel, portRange, error: read.ok ? void 0 : read.error },
|
|
21014
|
-
hasCompose: (0,
|
|
21015
|
-
hasEnvExample: (0,
|
|
21583
|
+
hasCompose: (0, import_node_fs21.existsSync)((0, import_node_path21.join)(process.cwd(), "docker-compose.yml")),
|
|
21584
|
+
hasEnvExample: (0, import_node_fs21.existsSync)((0, import_node_path21.join)(process.cwd(), ".env.example"))
|
|
21016
21585
|
});
|
|
21017
21586
|
}
|
|
21018
21587
|
async function fetchStageVaultEnvMerge() {
|
|
@@ -21053,10 +21622,10 @@ program2.command("port-range <repo>").description("assign (idempotently) + print
|
|
|
21053
21622
|
printLine(o.json ? JSON.stringify({ repo, portRange: [start2, end2], source: "meta" }) : `${repo}: stage.portRange [${start2}, ${end2}]`);
|
|
21054
21623
|
return;
|
|
21055
21624
|
}
|
|
21056
|
-
const infra = resolvePortRangeInfra(process.cwd());
|
|
21625
|
+
const infra = resolvePortRangeInfra(process.cwd(), __dirname);
|
|
21057
21626
|
if (!infra) {
|
|
21058
21627
|
return failGraceful(
|
|
21059
|
-
`port-range: no MMI-Hub allocator files found
|
|
21628
|
+
`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
21629
|
);
|
|
21061
21630
|
}
|
|
21062
21631
|
const path2 = infra.registryPath;
|
|
@@ -21450,7 +22019,7 @@ ${r.repo}: applied=[${r.applied.join("; ")}] skipped=[${r.skipped.join("; ")}]${
|
|
|
21450
22019
|
if (!audit.ok) process.exitCode = 1;
|
|
21451
22020
|
});
|
|
21452
22021
|
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) => {
|
|
22022
|
+
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
22023
|
const repo = await resolveRepo(o.repo);
|
|
21455
22024
|
if (!repo) return fail("wiki publish: could not determine the target repo (pass --repo owner/name)");
|
|
21456
22025
|
const cfg = await loadConfig();
|
|
@@ -21461,7 +22030,7 @@ wiki.command("publish <pages-dir>").description("mint a 1h repo-scoped contents:
|
|
|
21461
22030
|
log: (msg) => console.log(msg),
|
|
21462
22031
|
err: (msg) => console.error(msg)
|
|
21463
22032
|
},
|
|
21464
|
-
{ repo, pagesDir }
|
|
22033
|
+
{ repo, pagesDir, json: o.json }
|
|
21465
22034
|
);
|
|
21466
22035
|
if (!ok) process.exitCode = 1;
|
|
21467
22036
|
});
|
|
@@ -21486,9 +22055,8 @@ wiki.command("plan").description("compute the release wiki plan for the cwd repo
|
|
|
21486
22055
|
);
|
|
21487
22056
|
if (!ok) process.exitCode = 1;
|
|
21488
22057
|
});
|
|
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").
|
|
22058
|
+
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
22059
|
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
22060
|
if (o.class !== "deployable" && o.class !== "content") return fail("bootstrap: --class must be deployable or content");
|
|
21493
22061
|
const steps = bootstrapPlan(o.repo, o.class);
|
|
21494
22062
|
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 +22073,7 @@ bootstrap.command("verify <repo>").description("audit whether an existing repo i
|
|
|
21505
22073
|
client: defaultGitHubClient(),
|
|
21506
22074
|
projectMeta: meta,
|
|
21507
22075
|
deployModel: typeof meta?.deployModel === "string" ? meta.deployModel : void 0,
|
|
21508
|
-
readLocalFile: (path2) => path2 === "projects.json" && apiProjects != null ? apiProjects : (0,
|
|
22076
|
+
readLocalFile: (path2) => path2 === "projects.json" && apiProjects != null ? apiProjects : (0, import_node_fs21.existsSync)(path2) ? (0, import_node_fs21.readFileSync)(path2, "utf8") : null,
|
|
21509
22077
|
// requiredGcpApis is stored as an array by a JSON write, but `project set --var KEY=VALUE` stores a raw
|
|
21510
22078
|
// comma-string — accept either so the seeded value verifies regardless of how it was written.
|
|
21511
22079
|
requiredGcpApis: (() => {
|
|
@@ -21538,7 +22106,7 @@ bootstrap.command("org-ruleset").description("drift-check the codified org no-ag
|
|
|
21538
22106
|
else console.log(renderOrgRulesetDriftReport(plan));
|
|
21539
22107
|
if (plan.action !== "noop") process.exitCode = 1;
|
|
21540
22108
|
});
|
|
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) => {
|
|
22109
|
+
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
22110
|
const o = {
|
|
21543
22111
|
class: rawValue("--class", "deployable"),
|
|
21544
22112
|
projectType: rawValue("--project-type", ""),
|
|
@@ -21556,12 +22124,12 @@ bootstrap.command("apply <repo>").description("idempotent seed apply from skills
|
|
|
21556
22124
|
return fail(`bootstrap apply: ${e.message}`);
|
|
21557
22125
|
}
|
|
21558
22126
|
const manifestPath = "skills/bootstrap/seeds/manifest.json";
|
|
21559
|
-
if (!(0,
|
|
21560
|
-
const manifest = loadBootstrapSeeds((0,
|
|
22127
|
+
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`);
|
|
22128
|
+
const manifest = loadBootstrapSeeds((0, import_node_fs21.readFileSync)(manifestPath, "utf8"));
|
|
21561
22129
|
const baseBranch = o.class === "content" ? "main" : "development";
|
|
21562
22130
|
const slug = parsedRepo.slug;
|
|
21563
22131
|
const gh = async (args) => execFileP2("gh", args, { timeout: 2e4 });
|
|
21564
|
-
const readFile2 = (p) => (0,
|
|
22132
|
+
const readFile2 = (p) => (0, import_node_fs21.existsSync)(p) ? (0, import_node_fs21.readFileSync)(p, "utf8") : null;
|
|
21565
22133
|
const enc = (p) => p.split("/").map(encodeURIComponent).join("/");
|
|
21566
22134
|
const rawVars = {};
|
|
21567
22135
|
for (const value of rawValues("--var")) {
|
|
@@ -21858,16 +22426,16 @@ access.command("audit").description("audit collaborator roles + train-branch pus
|
|
|
21858
22426
|
const repoClass = o.class;
|
|
21859
22427
|
targets = [{ repo: o.repo, class: repoClass, releaseTrack: repoClass === "content" ? "trunk" : resolveReleaseTrack(meta, void 0, o.repo) }];
|
|
21860
22428
|
} else {
|
|
21861
|
-
const projectsJson = registryProjects ? JSON.stringify({ projects: registryProjects }) : (0,
|
|
22429
|
+
const projectsJson = registryProjects ? JSON.stringify({ projects: registryProjects }) : (0, import_node_fs21.existsSync)("projects.json") ? (0, import_node_fs21.readFileSync)("projects.json", "utf8") : null;
|
|
21862
22430
|
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,
|
|
22431
|
+
const fanoutJson = (0, import_node_fs21.existsSync)(".github/fanout-targets.json") ? (0, import_node_fs21.readFileSync)(".github/fanout-targets.json", "utf8") : null;
|
|
21864
22432
|
targets = loadAccessTargets(projectsJson, fanoutJson);
|
|
21865
22433
|
}
|
|
21866
22434
|
const derivedMatrix = registryProjects ? accessMatrixFromProjects(registryProjects) : {};
|
|
21867
|
-
const fileMatrix = (0,
|
|
22435
|
+
const fileMatrix = (0, import_node_fs21.existsSync)("access-matrix.json") ? loadAccessMatrix((0, import_node_fs21.readFileSync)("access-matrix.json", "utf8")) : {};
|
|
21868
22436
|
const matrix = mergeAccessMatrix(fileMatrix, derivedMatrix);
|
|
21869
22437
|
const derivedContracts = registryProjects ? dataAccessContractsFromProjects(registryProjects) : { consumers: {} };
|
|
21870
|
-
const fileContracts = (0,
|
|
22438
|
+
const fileContracts = (0, import_node_fs21.existsSync)("data-access-contracts.json") ? loadDataAccessContracts((0, import_node_fs21.readFileSync)("data-access-contracts.json", "utf8")) : { consumers: {} };
|
|
21871
22439
|
const dataAccess = mergeDataAccessContracts(fileContracts, derivedContracts);
|
|
21872
22440
|
const report = await auditOrgAccess(targets, deps, matrix, dataAccess);
|
|
21873
22441
|
console.log(o.json ? JSON.stringify(report, null, 2) : renderAccessReport(report));
|
|
@@ -21875,7 +22443,7 @@ access.command("audit").description("audit collaborator roles + train-branch pus
|
|
|
21875
22443
|
});
|
|
21876
22444
|
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
22445
|
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) => (
|
|
22446
|
+
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
22447
|
// Commander maps `--no-repo-writes` to `repoWrites: false`; translate to the explicit `noRepoWrites` flag.
|
|
21880
22448
|
runDoctor({ ...opts, noRepoWrites: opts.repoWrites === false })
|
|
21881
22449
|
));
|
|
@@ -21884,9 +22452,13 @@ program2.command("plugin-heal").description("reinstall + re-enable the MMI plugi
|
|
|
21884
22452
|
program2.command("session-start").description("run the SessionStart verbs (whoami, board slice, doctor) in one process").action(async () => {
|
|
21885
22453
|
if (isInsideRepoSubdir(process.cwd())) {
|
|
21886
22454
|
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.");
|
|
22455
|
+
appendHookActivity(process.cwd(), { event: "SessionStart", script: "session-start", outcome: "ran", action: "skip (repo subdirectory)" });
|
|
22456
|
+
return;
|
|
22457
|
+
}
|
|
22458
|
+
if (!await isOrgRepoRoot()) {
|
|
22459
|
+
appendHookActivity(process.cwd(), { event: "SessionStart", script: "session-start", outcome: "ran", action: "skip (non-org repo)" });
|
|
21887
22460
|
return;
|
|
21888
22461
|
}
|
|
21889
|
-
if (!await isOrgRepoRoot()) return;
|
|
21890
22462
|
spawnDeferredGcSweep();
|
|
21891
22463
|
const { parallel, sequential } = buildSessionStartPlan({
|
|
21892
22464
|
// whoami (#879): surface the resolved human so agents act --for them without asking. Silent
|
|
@@ -21906,6 +22478,12 @@ program2.command("session-start").description("run the SessionStart verbs (whoam
|
|
|
21906
22478
|
// never costs banner time and next session's glance renders instantly within budget.
|
|
21907
22479
|
scheduleRefresh: () => spawnDetachedSelf(["board", "slice-refresh", "--quiet"], { spawn: import_node_child_process12.spawn, execPath: process.execPath, scriptPath: process.argv[1] })
|
|
21908
22480
|
}),
|
|
22481
|
+
// command-ladder hint (#2609): compact gh→mmi-cli cheat sheet so agents reach for mmi-cli first,
|
|
22482
|
+
// not after a denied gh call. <1KB, pure in-memory — no network, no fs, fail-soft.
|
|
22483
|
+
commandLadderHint: async (io) => {
|
|
22484
|
+
const hint = commandLadderHint();
|
|
22485
|
+
if (hint) io.log(hint);
|
|
22486
|
+
},
|
|
21909
22487
|
// local train sync (#2582): ff local development/main/rc to origin so this checkout never lags a
|
|
21910
22488
|
// release pushed to origin (incl. the deferred main -> development alignment merge). Bounded git,
|
|
21911
22489
|
// fail-soft, silent unless a branch moved.
|
|
@@ -21924,6 +22502,7 @@ program2.command("session-start").description("run the SessionStart verbs (whoam
|
|
|
21924
22502
|
spawnDetachedSelf(["worktree", "setup", "--quiet"], { spawn: import_node_child_process12.spawn, execPath: process.execPath, scriptPath: process.argv[1] });
|
|
21925
22503
|
consoleIo.log(worktreeBanner);
|
|
21926
22504
|
}
|
|
22505
|
+
appendHookActivity(process.cwd(), { event: "SessionStart", script: "session-start", outcome: "ran", action: "session-start hook completed" });
|
|
21927
22506
|
});
|
|
21928
22507
|
installProcessBackstop();
|
|
21929
22508
|
program2.parseAsync().catch((e) => failGraceful(e.message));
|