@mutmutco/cli 2.41.0 → 2.42.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/main.cjs +374 -227
- package/package.json +1 -1
package/dist/main.cjs
CHANGED
|
@@ -3392,7 +3392,7 @@ var program = new Command();
|
|
|
3392
3392
|
|
|
3393
3393
|
// src/index.ts
|
|
3394
3394
|
var import_promises7 = require("node:fs/promises");
|
|
3395
|
-
var
|
|
3395
|
+
var import_node_fs23 = require("node:fs");
|
|
3396
3396
|
|
|
3397
3397
|
// src/rules-sync.ts
|
|
3398
3398
|
function normalizeEol(s) {
|
|
@@ -5168,11 +5168,11 @@ function parseHandoffText(text) {
|
|
|
5168
5168
|
const sourceSessionId = clean(raw.sourceSessionId);
|
|
5169
5169
|
const createdAt = clean(raw.createdAt);
|
|
5170
5170
|
if (state !== "open" && state !== "claimed" && state !== "cancelled") return null;
|
|
5171
|
-
if (!key || !
|
|
5171
|
+
if (!key || !summary || !sourceSessionId || !createdAt) return null;
|
|
5172
5172
|
return {
|
|
5173
5173
|
state,
|
|
5174
5174
|
key,
|
|
5175
|
-
northStarSlug,
|
|
5175
|
+
northStarSlug: northStarSlug || void 0,
|
|
5176
5176
|
summary,
|
|
5177
5177
|
sourceSessionId,
|
|
5178
5178
|
sourceBranch: clean(raw.sourceBranch) || void 0,
|
|
@@ -5190,20 +5190,19 @@ function listHandoffs(head, opts = {}) {
|
|
|
5190
5190
|
}
|
|
5191
5191
|
function findOpenHandoff(head, key) {
|
|
5192
5192
|
const normalized = normalizeHandoffKey(key).toLowerCase();
|
|
5193
|
-
return listHandoffs(head).find((item) => item.record.key.toLowerCase() === normalized || item.record.northStarSlug
|
|
5193
|
+
return listHandoffs(head).find((item) => item.record.key.toLowerCase() === normalized || item.record.northStarSlug?.toLowerCase() === normalized);
|
|
5194
5194
|
}
|
|
5195
5195
|
function planOpenHandoff(input) {
|
|
5196
5196
|
const key = normalizeHandoffKey(input.key);
|
|
5197
5197
|
const northStarSlug = clean(input.northStarSlug);
|
|
5198
5198
|
const summary = clean(input.summary);
|
|
5199
5199
|
const sourceSessionId = clean(input.sourceSessionId);
|
|
5200
|
-
if (!northStarSlug) throw new Error("north star slug is required");
|
|
5201
5200
|
if (!summary) throw new Error("handoff summary is required");
|
|
5202
5201
|
if (!sourceSessionId) throw new Error("source session id is required");
|
|
5203
5202
|
return {
|
|
5204
5203
|
state: "open",
|
|
5205
5204
|
key,
|
|
5206
|
-
northStarSlug,
|
|
5205
|
+
northStarSlug: northStarSlug || void 0,
|
|
5207
5206
|
summary,
|
|
5208
5207
|
sourceSessionId,
|
|
5209
5208
|
sourceBranch: clean(input.sourceBranch) || void 0,
|
|
@@ -5222,7 +5221,7 @@ function formatHandoffLine(item) {
|
|
|
5222
5221
|
const r = item.record;
|
|
5223
5222
|
const branch = r.sourceBranch ? ` branch:${r.sourceBranch}` : "";
|
|
5224
5223
|
const suffix = r.state === "claimed" && r.claimedBySessionId ? ` -> ${r.claimedBySessionId}` : "";
|
|
5225
|
-
return `${r.key} [${r.state}] northstar:${r.northStarSlug}${branch} source:${r.sourceSessionId}${suffix} - ${r.summary}`;
|
|
5224
|
+
return `${r.key} [${r.state}] northstar:${r.northStarSlug ?? "-"}${branch} source:${r.sourceSessionId}${suffix} - ${r.summary}`;
|
|
5226
5225
|
}
|
|
5227
5226
|
|
|
5228
5227
|
// src/handoff-commands.ts
|
|
@@ -5298,7 +5297,7 @@ async function locateClaimedHandoff(key, claimedBySessionId, retry = FOREGROUND_
|
|
|
5298
5297
|
const normalized = key.toLowerCase();
|
|
5299
5298
|
return handoffs.find((item) => {
|
|
5300
5299
|
const r = item.record;
|
|
5301
|
-
return r.state === "claimed" && r.claimedBySessionId === claimedBySessionId && (r.key.toLowerCase() === normalized || r.northStarSlug
|
|
5300
|
+
return r.state === "claimed" && r.claimedBySessionId === claimedBySessionId && (r.key.toLowerCase() === normalized || r.northStarSlug?.toLowerCase() === normalized);
|
|
5302
5301
|
}) ?? null;
|
|
5303
5302
|
}
|
|
5304
5303
|
async function postKeyedNote(key, summary, options) {
|
|
@@ -5312,9 +5311,12 @@ async function postKeyedNote(key, summary, options) {
|
|
|
5312
5311
|
function deriveOpenFields(summary, opts, head, key) {
|
|
5313
5312
|
const northStarSlug = opts.northStarSlug ?? head.anchor?.slug;
|
|
5314
5313
|
const handoffKey = opts.key ?? northStarSlug;
|
|
5314
|
+
if (!handoffKey?.trim()) {
|
|
5315
|
+
throw new Error("a handoff needs a key: pass --key <slug|#issue> (or --next), or open it on a session with a North Star anchor");
|
|
5316
|
+
}
|
|
5315
5317
|
const text = summary?.trim() || head.next?.trim() || head.anchor?.intent?.trim();
|
|
5316
5318
|
return planOpenHandoff({
|
|
5317
|
-
key: handoffKey
|
|
5319
|
+
key: handoffKey,
|
|
5318
5320
|
northStarSlug: northStarSlug ?? "",
|
|
5319
5321
|
summary: text ?? "",
|
|
5320
5322
|
sourceSessionId: key.sessionId,
|
|
@@ -5390,7 +5392,7 @@ async function closeSourceHandoff(key, state, opts = {}, io = consoleIo) {
|
|
|
5390
5392
|
anchor: located.item.record.summary,
|
|
5391
5393
|
anchorSlug: located.item.record.northStarSlug,
|
|
5392
5394
|
anchorForce: true,
|
|
5393
|
-
decision: `accepted handoff ${located.item.record.key} from session ${located.item.record.sourceSessionId}; northstar ${located.item.record.northStarSlug}`,
|
|
5395
|
+
decision: `accepted handoff ${located.item.record.key} from session ${located.item.record.sourceSessionId}; northstar ${located.item.record.northStarSlug ?? "(none)"}`,
|
|
5394
5396
|
verified: true
|
|
5395
5397
|
});
|
|
5396
5398
|
}
|
|
@@ -5412,7 +5414,7 @@ async function runHandoffDecline(key, opts = {}, io = consoleIo) {
|
|
|
5412
5414
|
}
|
|
5413
5415
|
function registerHandoffCommands(program3) {
|
|
5414
5416
|
const handoff = program3.command("handoff").description("explicit saga + North Star handoff lifecycle");
|
|
5415
|
-
handoff.command("open [summary]").description("open a handoff bound to
|
|
5417
|
+
handoff.command("open [summary]").description("open a handoff bound to a North Star slug or a board issue (--key/--next)").option("--key <slug|#issue>", "handoff key: a North Star slug or a board issue (defaults to the current North Star slug)").option("--next <slug|#issue>", "alias for --key (mirrors `saga note --next`)").option("--north-star-slug <slug>", "North Star slug to bind (optional; omit for an issue-bound handoff)").option("--message-file <path|->", "read the handoff summary from a UTF-8 file, or from stdin with - (avoids cmd.exe quoting)").option("--json", "machine-readable output").action((summary, opts) => runHandoffOpen(summary, { ...opts, key: opts.key ?? opts.next }));
|
|
5416
5418
|
handoff.command("list").description("list open handoffs for the current repo (any branch)").option("--all", "include claimed/cancelled records").option("--json", "machine-readable output").action(async (opts) => {
|
|
5417
5419
|
await runHandoffList(opts);
|
|
5418
5420
|
});
|
|
@@ -7105,6 +7107,9 @@ function northstarPointer(injected = false) {
|
|
|
7105
7107
|
}
|
|
7106
7108
|
return "North Stars: run `mmi-cli northstar relevant` to load plans relevant to your task (`northstar list` for all).";
|
|
7107
7109
|
}
|
|
7110
|
+
function kbPointer() {
|
|
7111
|
+
return "MM-KB (org knowledge): start at `mmi-cli kb get kb/INDEX.md`, then `kb get <path>` \u2014 consult before inventing conventions or storing long-term docs.";
|
|
7112
|
+
}
|
|
7108
7113
|
|
|
7109
7114
|
// src/board.ts
|
|
7110
7115
|
var import_node_child_process7 = require("node:child_process");
|
|
@@ -8023,9 +8028,47 @@ function ghError(e) {
|
|
|
8023
8028
|
return (err.stderr || err.message || String(e)).trim();
|
|
8024
8029
|
}
|
|
8025
8030
|
|
|
8031
|
+
// src/board-slice-cache.ts
|
|
8032
|
+
var import_node_fs14 = require("node:fs");
|
|
8033
|
+
var import_node_path12 = require("node:path");
|
|
8034
|
+
var BOARD_SLICE_CACHE_FILE = (0, import_node_path12.join)(".mmi", "board-slice.json");
|
|
8035
|
+
var BOARD_SLICE_CACHE_TTL_MS = 10 * 60 * 1e3;
|
|
8036
|
+
function boardSliceCachePath(cwd) {
|
|
8037
|
+
return (0, import_node_path12.join)(cwd, BOARD_SLICE_CACHE_FILE);
|
|
8038
|
+
}
|
|
8039
|
+
function readCachedBoardSlice(cwd) {
|
|
8040
|
+
try {
|
|
8041
|
+
const parsed = JSON.parse((0, import_node_fs14.readFileSync)(boardSliceCachePath(cwd), "utf8"));
|
|
8042
|
+
if (typeof parsed.ts !== "number") return null;
|
|
8043
|
+
return { block: typeof parsed.block === "string" ? parsed.block : null, ts: parsed.ts };
|
|
8044
|
+
} catch {
|
|
8045
|
+
return null;
|
|
8046
|
+
}
|
|
8047
|
+
}
|
|
8048
|
+
function writeCachedBoardSlice(cwd, slice) {
|
|
8049
|
+
const path2 = boardSliceCachePath(cwd);
|
|
8050
|
+
const tmp = `${path2}.${process.pid}.tmp`;
|
|
8051
|
+
try {
|
|
8052
|
+
(0, import_node_fs14.mkdirSync)((0, import_node_path12.dirname)(path2), { recursive: true });
|
|
8053
|
+
(0, import_node_fs14.writeFileSync)(tmp, JSON.stringify(slice));
|
|
8054
|
+
(0, import_node_fs14.renameSync)(tmp, path2);
|
|
8055
|
+
} catch {
|
|
8056
|
+
try {
|
|
8057
|
+
(0, import_node_fs14.rmSync)(tmp, { force: true });
|
|
8058
|
+
} catch {
|
|
8059
|
+
}
|
|
8060
|
+
}
|
|
8061
|
+
}
|
|
8062
|
+
function boardSliceCacheStale(cached, now, ttlMs = BOARD_SLICE_CACHE_TTL_MS) {
|
|
8063
|
+
return !cached || now - cached.ts >= ttlMs;
|
|
8064
|
+
}
|
|
8065
|
+
|
|
8026
8066
|
// src/board-slice.ts
|
|
8027
8067
|
var SESSION_START_BOARD_TIMEOUT_MS = 3e3;
|
|
8068
|
+
var BOARD_SLICE_REFRESH_TIMEOUT_MS = 3e4;
|
|
8028
8069
|
var BOARD_SLICE_FRAMING = "BOARD SLICE \u2014 reconcile with saga HEAD; board Status is authoritative for work lifecycle.";
|
|
8070
|
+
var BOARD_SLICE_EMPTY = "Board: nothing assigned or claimable right now \u2014 `mmi-cli board read` (or `/mmi`) for the full board.";
|
|
8071
|
+
var BOARD_SLICE_PENDING = "Board: loading your slice in the background \u2014 run `/mmi` now; it renders here next session.";
|
|
8029
8072
|
var BOARD_SLICE_MAX_LINES = 5;
|
|
8030
8073
|
var ACTIVE_STATUS_ORDER = {
|
|
8031
8074
|
"In Progress": 0,
|
|
@@ -8086,12 +8129,25 @@ async function withTimeout(promise, ms) {
|
|
|
8086
8129
|
}
|
|
8087
8130
|
}
|
|
8088
8131
|
async function runBoardSlice(io, deps, opts) {
|
|
8132
|
+
const cwd = deps.cwd ?? process.cwd();
|
|
8133
|
+
const now = deps.now ?? Date.now();
|
|
8134
|
+
let cfg;
|
|
8135
|
+
try {
|
|
8136
|
+
cfg = await deps.loadConfig();
|
|
8137
|
+
} catch {
|
|
8138
|
+
return;
|
|
8139
|
+
}
|
|
8140
|
+
if (!boardMetaConfigured(cfg)) return;
|
|
8141
|
+
const cached = readCachedBoardSlice(cwd);
|
|
8142
|
+
if (boardSliceCacheStale(cached, now)) deps.scheduleRefresh?.();
|
|
8143
|
+
if (cached) {
|
|
8144
|
+
io.log(cached.block ?? BOARD_SLICE_EMPTY);
|
|
8145
|
+
return;
|
|
8146
|
+
}
|
|
8089
8147
|
const budget = deps.timeoutMs ?? SESSION_START_BOARD_TIMEOUT_MS;
|
|
8090
8148
|
const controller = new AbortController();
|
|
8091
8149
|
const deadline = setTimeout(() => controller.abort(), budget);
|
|
8092
8150
|
try {
|
|
8093
|
-
const cfg = await deps.loadConfig();
|
|
8094
|
-
if (!boardMetaConfigured(cfg)) return;
|
|
8095
8151
|
const client = deps.client ?? createGitHubClient({ defaultTimeoutMs: budget, signal: controller.signal });
|
|
8096
8152
|
const report = await withTimeout(
|
|
8097
8153
|
deps.readBoard({ config: cfg, allowPartial: true }, { client }),
|
|
@@ -8099,7 +8155,26 @@ async function runBoardSlice(io, deps, opts) {
|
|
|
8099
8155
|
);
|
|
8100
8156
|
if (opts?.expectedLogin && report.viewer && opts.expectedLogin !== report.viewer) return;
|
|
8101
8157
|
const block = renderBoardSlice(report);
|
|
8102
|
-
|
|
8158
|
+
writeCachedBoardSlice(cwd, { block, ts: now });
|
|
8159
|
+
io.log(block ?? BOARD_SLICE_EMPTY);
|
|
8160
|
+
} catch {
|
|
8161
|
+
io.log(BOARD_SLICE_PENDING);
|
|
8162
|
+
} finally {
|
|
8163
|
+
clearTimeout(deadline);
|
|
8164
|
+
}
|
|
8165
|
+
}
|
|
8166
|
+
async function refreshBoardSliceCache(deps) {
|
|
8167
|
+
const cwd = deps.cwd ?? process.cwd();
|
|
8168
|
+
const now = deps.now ?? Date.now();
|
|
8169
|
+
const budget = deps.timeoutMs ?? BOARD_SLICE_REFRESH_TIMEOUT_MS;
|
|
8170
|
+
const controller = new AbortController();
|
|
8171
|
+
const deadline = setTimeout(() => controller.abort(), budget);
|
|
8172
|
+
try {
|
|
8173
|
+
const cfg = await deps.loadConfig();
|
|
8174
|
+
if (!boardMetaConfigured(cfg)) return;
|
|
8175
|
+
const client = deps.client ?? createGitHubClient({ defaultTimeoutMs: budget, signal: controller.signal });
|
|
8176
|
+
const report = await withTimeout(deps.readBoard({ config: cfg, allowPartial: true }, { client }), budget);
|
|
8177
|
+
writeCachedBoardSlice(cwd, { block: renderBoardSlice(report), ts: now });
|
|
8103
8178
|
} catch {
|
|
8104
8179
|
} finally {
|
|
8105
8180
|
clearTimeout(deadline);
|
|
@@ -8107,8 +8182,8 @@ async function runBoardSlice(io, deps, opts) {
|
|
|
8107
8182
|
}
|
|
8108
8183
|
|
|
8109
8184
|
// src/worktree.ts
|
|
8110
|
-
var
|
|
8111
|
-
var
|
|
8185
|
+
var import_node_fs15 = require("node:fs");
|
|
8186
|
+
var import_node_path13 = require("node:path");
|
|
8112
8187
|
var LOCAL_ONLY_FILES = [".claude/settings.local.json"];
|
|
8113
8188
|
var PKG = "package.json";
|
|
8114
8189
|
var LOCKFILE = "package-lock.json";
|
|
@@ -8116,21 +8191,21 @@ var NODE_MODULES = "node_modules";
|
|
|
8116
8191
|
var realFsProbe = {
|
|
8117
8192
|
isDir: (p) => {
|
|
8118
8193
|
try {
|
|
8119
|
-
return (0,
|
|
8194
|
+
return (0, import_node_fs15.statSync)(p).isDirectory();
|
|
8120
8195
|
} catch {
|
|
8121
8196
|
return false;
|
|
8122
8197
|
}
|
|
8123
8198
|
},
|
|
8124
8199
|
isFile: (p) => {
|
|
8125
8200
|
try {
|
|
8126
|
-
return (0,
|
|
8201
|
+
return (0, import_node_fs15.statSync)(p).isFile();
|
|
8127
8202
|
} catch {
|
|
8128
8203
|
return false;
|
|
8129
8204
|
}
|
|
8130
8205
|
},
|
|
8131
8206
|
listDirs: (p) => {
|
|
8132
8207
|
try {
|
|
8133
|
-
return (0,
|
|
8208
|
+
return (0, import_node_fs15.readdirSync)(p, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
|
|
8134
8209
|
} catch {
|
|
8135
8210
|
return [];
|
|
8136
8211
|
}
|
|
@@ -8138,12 +8213,12 @@ var realFsProbe = {
|
|
|
8138
8213
|
};
|
|
8139
8214
|
function scanInstallDirs(root, fs2 = realFsProbe) {
|
|
8140
8215
|
const factsFor = (dir) => {
|
|
8141
|
-
const abs = dir ? (0,
|
|
8216
|
+
const abs = dir ? (0, import_node_path13.join)(root, dir) : root;
|
|
8142
8217
|
return {
|
|
8143
8218
|
dir,
|
|
8144
|
-
hasPackageJson: fs2.isFile((0,
|
|
8145
|
-
hasLockfile: fs2.isFile((0,
|
|
8146
|
-
hasNodeModules: fs2.isDir((0,
|
|
8219
|
+
hasPackageJson: fs2.isFile((0, import_node_path13.join)(abs, PKG)),
|
|
8220
|
+
hasLockfile: fs2.isFile((0, import_node_path13.join)(abs, LOCKFILE)),
|
|
8221
|
+
hasNodeModules: fs2.isDir((0, import_node_path13.join)(abs, NODE_MODULES))
|
|
8147
8222
|
};
|
|
8148
8223
|
};
|
|
8149
8224
|
const children = fs2.listDirs(root).filter((name) => name !== NODE_MODULES && name !== ".git");
|
|
@@ -8153,7 +8228,7 @@ function npmInstallTargets(dirs) {
|
|
|
8153
8228
|
return dirs.filter((d) => d.hasPackageJson && !d.hasNodeModules).map((d) => ({ dir: d.dir, command: d.hasLockfile ? "npm ci" : "npm install" }));
|
|
8154
8229
|
}
|
|
8155
8230
|
function isLinkedWorktree(root, fs2 = realFsProbe) {
|
|
8156
|
-
return fs2.isFile((0,
|
|
8231
|
+
return fs2.isFile((0, import_node_path13.join)(root, ".git"));
|
|
8157
8232
|
}
|
|
8158
8233
|
function worktreeAutoProvisionBanner(root, fs2 = realFsProbe) {
|
|
8159
8234
|
if (!isLinkedWorktree(root, fs2)) return null;
|
|
@@ -8163,8 +8238,8 @@ function worktreeAutoProvisionBanner(root, fs2 = realFsProbe) {
|
|
|
8163
8238
|
return `[worktree] provisioning tooling in the background (deps in ${where} + local config) \u2014 \`mmi-cli worktree setup\` to redo`;
|
|
8164
8239
|
}
|
|
8165
8240
|
function defaultCopyFile(from, to) {
|
|
8166
|
-
(0,
|
|
8167
|
-
(0,
|
|
8241
|
+
(0, import_node_fs15.mkdirSync)((0, import_node_path13.dirname)(to), { recursive: true });
|
|
8242
|
+
(0, import_node_fs15.copyFileSync)(from, to);
|
|
8168
8243
|
}
|
|
8169
8244
|
async function provisionWorktree(worktreeRoot, deps) {
|
|
8170
8245
|
const fs2 = deps.fs ?? realFsProbe;
|
|
@@ -8176,7 +8251,7 @@ async function provisionWorktree(worktreeRoot, deps) {
|
|
|
8176
8251
|
const skippedInstall = allDirs.filter((d) => d.hasPackageJson && d.hasNodeModules).map((d) => d.dir);
|
|
8177
8252
|
const installed = [];
|
|
8178
8253
|
for (const target of targets) {
|
|
8179
|
-
const cwd = target.dir ? (0,
|
|
8254
|
+
const cwd = target.dir ? (0, import_node_path13.join)(worktreeRoot, target.dir) : worktreeRoot;
|
|
8180
8255
|
log(`installing deps: ${target.command} in ${target.dir || "."}`);
|
|
8181
8256
|
await deps.runInstall(target.command, cwd);
|
|
8182
8257
|
installed.push(target);
|
|
@@ -8185,7 +8260,7 @@ async function provisionWorktree(worktreeRoot, deps) {
|
|
|
8185
8260
|
const copySkipped = [];
|
|
8186
8261
|
const primary = await deps.primaryCheckout();
|
|
8187
8262
|
for (const rel of LOCAL_ONLY_FILES) {
|
|
8188
|
-
const dest = (0,
|
|
8263
|
+
const dest = (0, import_node_path13.join)(worktreeRoot, rel);
|
|
8189
8264
|
if (fs2.isFile(dest)) {
|
|
8190
8265
|
copySkipped.push({ file: rel, reason: "already-present" });
|
|
8191
8266
|
continue;
|
|
@@ -8194,11 +8269,11 @@ async function provisionWorktree(worktreeRoot, deps) {
|
|
|
8194
8269
|
copySkipped.push({ file: rel, reason: "no-primary" });
|
|
8195
8270
|
continue;
|
|
8196
8271
|
}
|
|
8197
|
-
if (!fs2.isFile((0,
|
|
8272
|
+
if (!fs2.isFile((0, import_node_path13.join)(primary, rel))) {
|
|
8198
8273
|
copySkipped.push({ file: rel, reason: "absent-in-primary" });
|
|
8199
8274
|
continue;
|
|
8200
8275
|
}
|
|
8201
|
-
copyFile((0,
|
|
8276
|
+
copyFile((0, import_node_path13.join)(primary, rel), dest);
|
|
8202
8277
|
copied.push(rel);
|
|
8203
8278
|
log(`copied local config: ${rel}`);
|
|
8204
8279
|
}
|
|
@@ -8206,7 +8281,7 @@ async function provisionWorktree(worktreeRoot, deps) {
|
|
|
8206
8281
|
}
|
|
8207
8282
|
function defaultWorktreePath(repoRoot, branch) {
|
|
8208
8283
|
const safe = branch.replace(/[/\\]+/g, "-");
|
|
8209
|
-
return (0,
|
|
8284
|
+
return (0, import_node_path13.join)((0, import_node_path13.dirname)(repoRoot), "mmi-worktrees", safe);
|
|
8210
8285
|
}
|
|
8211
8286
|
|
|
8212
8287
|
// src/frontmatter.ts
|
|
@@ -8404,7 +8479,7 @@ async function runNorthstarContext(io, deps) {
|
|
|
8404
8479
|
}
|
|
8405
8480
|
|
|
8406
8481
|
// src/index.ts
|
|
8407
|
-
var
|
|
8482
|
+
var import_node_path20 = require("node:path");
|
|
8408
8483
|
|
|
8409
8484
|
// src/merge-ci-policy.ts
|
|
8410
8485
|
function resolveMergeCiPolicy(input) {
|
|
@@ -8705,6 +8780,11 @@ function diffManagedGitignoreBlock(current) {
|
|
|
8705
8780
|
seeded: false
|
|
8706
8781
|
};
|
|
8707
8782
|
}
|
|
8783
|
+
function planManagedGitignore(current) {
|
|
8784
|
+
const { content, changed } = upsertManagedGitignoreBlock(current);
|
|
8785
|
+
const { added, removed } = diffManagedGitignoreBlock(current);
|
|
8786
|
+
return { changed, content, added, removed };
|
|
8787
|
+
}
|
|
8708
8788
|
|
|
8709
8789
|
// src/project-model.ts
|
|
8710
8790
|
var PROJECT_TYPES = ["web-app", "hub-service", "content", "desktop-game", "non-deployable", "cli-tool", "worker"];
|
|
@@ -9603,7 +9683,7 @@ var import_node_os6 = require("node:os");
|
|
|
9603
9683
|
// src/gh-create.ts
|
|
9604
9684
|
var import_promises5 = require("node:fs/promises");
|
|
9605
9685
|
var import_node_os4 = require("node:os");
|
|
9606
|
-
var
|
|
9686
|
+
var import_node_path14 = require("node:path");
|
|
9607
9687
|
var import_node_crypto6 = require("node:crypto");
|
|
9608
9688
|
var ISSUE_TYPES = ["bug", "feature", "task"];
|
|
9609
9689
|
var GH_MUTATION_TIMEOUT_MS = 12e4;
|
|
@@ -9644,7 +9724,7 @@ async function bodyArgsViaFile(args, deps = {}) {
|
|
|
9644
9724
|
} };
|
|
9645
9725
|
const write = deps.write ?? import_promises5.writeFile;
|
|
9646
9726
|
const remove = deps.remove ?? import_promises5.unlink;
|
|
9647
|
-
const file = (0,
|
|
9727
|
+
const file = (0, import_node_path14.join)(deps.dir ?? (0, import_node_os4.tmpdir)(), `mmi-gh-body-${process.pid}-${(0, import_node_crypto6.randomBytes)(4).toString("hex")}.md`);
|
|
9648
9728
|
await write(file, args[i + 1], "utf8");
|
|
9649
9729
|
return {
|
|
9650
9730
|
args: [...args.slice(0, i), "--body-file", file, ...args.slice(i + 2)],
|
|
@@ -11245,9 +11325,9 @@ function decideStage(inputs) {
|
|
|
11245
11325
|
|
|
11246
11326
|
// src/cursor-plugin-seed.ts
|
|
11247
11327
|
var import_node_child_process8 = require("node:child_process");
|
|
11248
|
-
var
|
|
11328
|
+
var import_node_fs16 = require("node:fs");
|
|
11249
11329
|
var import_node_os5 = require("node:os");
|
|
11250
|
-
var
|
|
11330
|
+
var import_node_path15 = require("node:path");
|
|
11251
11331
|
var import_node_util6 = require("node:util");
|
|
11252
11332
|
function isSemverVersion(v) {
|
|
11253
11333
|
return typeof v === "string" && /^v?\d+\.\d+\.\d+/.test(v.trim());
|
|
@@ -11264,17 +11344,17 @@ function ghReleaseTarballApiArgs(tag) {
|
|
|
11264
11344
|
}
|
|
11265
11345
|
function cursorUserGlobalStatePath() {
|
|
11266
11346
|
if (process.platform === "win32") {
|
|
11267
|
-
const base2 = process.env.APPDATA || (0,
|
|
11268
|
-
return (0,
|
|
11347
|
+
const base2 = process.env.APPDATA || (0, import_node_path15.join)((0, import_node_os5.homedir)(), "AppData", "Roaming");
|
|
11348
|
+
return (0, import_node_path15.join)(base2, "Cursor", "User", "globalStorage", "state.vscdb");
|
|
11269
11349
|
}
|
|
11270
11350
|
if (process.platform === "darwin") {
|
|
11271
|
-
return (0,
|
|
11351
|
+
return (0, import_node_path15.join)((0, import_node_os5.homedir)(), "Library", "Application Support", "Cursor", "User", "globalStorage", "state.vscdb");
|
|
11272
11352
|
}
|
|
11273
|
-
return (0,
|
|
11353
|
+
return (0, import_node_path15.join)((0, import_node_os5.homedir)(), ".config", "Cursor", "User", "globalStorage", "state.vscdb");
|
|
11274
11354
|
}
|
|
11275
11355
|
async function readCursorThirdPartyExtensibilityEnabled(execFileP5) {
|
|
11276
11356
|
const dbPath = cursorUserGlobalStatePath();
|
|
11277
|
-
if (!(0,
|
|
11357
|
+
if (!(0, import_node_fs16.existsSync)(dbPath)) return void 0;
|
|
11278
11358
|
try {
|
|
11279
11359
|
const { stdout } = await execFileP5("sqlite3", [dbPath, `SELECT value FROM ItemTable WHERE key = '${CURSOR_THIRD_PARTY_STATE_KEY}';`], {
|
|
11280
11360
|
timeout: 5e3
|
|
@@ -11288,57 +11368,57 @@ async function readCursorThirdPartyExtensibilityEnabled(execFileP5) {
|
|
|
11288
11368
|
}
|
|
11289
11369
|
}
|
|
11290
11370
|
function syncDirContents(src, dest) {
|
|
11291
|
-
(0,
|
|
11292
|
-
for (const name of (0,
|
|
11293
|
-
(0,
|
|
11371
|
+
(0, import_node_fs16.mkdirSync)(dest, { recursive: true });
|
|
11372
|
+
for (const name of (0, import_node_fs16.readdirSync)(dest)) {
|
|
11373
|
+
(0, import_node_fs16.rmSync)((0, import_node_path15.join)(dest, name), { recursive: true, force: true });
|
|
11294
11374
|
}
|
|
11295
|
-
(0,
|
|
11375
|
+
(0, import_node_fs16.cpSync)(src, dest, { recursive: true });
|
|
11296
11376
|
}
|
|
11297
11377
|
function releaseTag(releasedVersion) {
|
|
11298
11378
|
return releasedVersion.startsWith("v") ? releasedVersion : `v${releasedVersion}`;
|
|
11299
11379
|
}
|
|
11300
11380
|
async function extractPluginMmiFromHubCheckout(hubCheckout, tag, tmpRoot, execFileP5) {
|
|
11301
|
-
const tarFile = (0,
|
|
11381
|
+
const tarFile = (0, import_node_path15.join)(tmpRoot, "archive.tar");
|
|
11302
11382
|
try {
|
|
11303
11383
|
await execFileP5("git", gitFetchReleaseTagArgs(hubCheckout, tag), { timeout: 6e4 });
|
|
11304
11384
|
await execFileP5("git", ["-C", hubCheckout, "archive", "--format=tar", `--output=${tarFile}`, tag, "plugins/mmi"], {
|
|
11305
11385
|
timeout: 6e4
|
|
11306
11386
|
});
|
|
11307
11387
|
await execFileP5("tar", ["-xf", tarFile, "-C", tmpRoot], { timeout: 6e4 });
|
|
11308
|
-
const pluginMmi = (0,
|
|
11309
|
-
return (0,
|
|
11388
|
+
const pluginMmi = (0, import_node_path15.join)(tmpRoot, "plugins", "mmi");
|
|
11389
|
+
return (0, import_node_fs16.existsSync)((0, import_node_path15.join)(pluginMmi, PLUGIN_JSON_REL)) ? pluginMmi : void 0;
|
|
11310
11390
|
} catch {
|
|
11311
11391
|
return void 0;
|
|
11312
11392
|
}
|
|
11313
11393
|
}
|
|
11314
11394
|
async function downloadPluginMmiViaGh(tag, tmpRoot) {
|
|
11315
|
-
const tarPath = (0,
|
|
11395
|
+
const tarPath = (0, import_node_path15.join)(tmpRoot, "repo.tgz");
|
|
11316
11396
|
try {
|
|
11317
|
-
(0,
|
|
11397
|
+
(0, import_node_fs16.mkdirSync)(tmpRoot, { recursive: true });
|
|
11318
11398
|
const { stdout } = await execFileBuffer("gh", ghReleaseTarballApiArgs(tag), {
|
|
11319
11399
|
timeout: 12e4,
|
|
11320
11400
|
maxBuffer: 100 * 1024 * 1024,
|
|
11321
11401
|
encoding: "buffer",
|
|
11322
11402
|
windowsHide: true
|
|
11323
11403
|
});
|
|
11324
|
-
(0,
|
|
11404
|
+
(0, import_node_fs16.writeFileSync)(tarPath, stdout);
|
|
11325
11405
|
await execFileBuffer("tar", ["-xzf", tarPath, "-C", tmpRoot], { timeout: 12e4, windowsHide: true });
|
|
11326
|
-
const top = (0,
|
|
11406
|
+
const top = (0, import_node_fs16.readdirSync)(tmpRoot).find((entry) => entry !== "repo.tgz");
|
|
11327
11407
|
if (!top) return void 0;
|
|
11328
|
-
const pluginMmi = (0,
|
|
11329
|
-
return (0,
|
|
11408
|
+
const pluginMmi = (0, import_node_path15.join)(tmpRoot, top, "plugins", "mmi");
|
|
11409
|
+
return (0, import_node_fs16.existsSync)((0, import_node_path15.join)(pluginMmi, PLUGIN_JSON_REL)) ? pluginMmi : void 0;
|
|
11330
11410
|
} catch {
|
|
11331
11411
|
return void 0;
|
|
11332
11412
|
}
|
|
11333
11413
|
}
|
|
11334
11414
|
async function resolvePluginMmiSource(releasedVersion, hubCheckout, tmpRoot, execFileP5) {
|
|
11335
|
-
(0,
|
|
11415
|
+
(0, import_node_fs16.mkdirSync)(tmpRoot, { recursive: true });
|
|
11336
11416
|
const tag = releaseTag(releasedVersion);
|
|
11337
11417
|
if (hubCheckout) {
|
|
11338
11418
|
const fromHub = await extractPluginMmiFromHubCheckout(hubCheckout, tag, tmpRoot, execFileP5);
|
|
11339
11419
|
if (fromHub) return fromHub;
|
|
11340
11420
|
}
|
|
11341
|
-
return downloadPluginMmiViaGh(tag, (0,
|
|
11421
|
+
return downloadPluginMmiViaGh(tag, (0, import_node_path15.join)(tmpRoot, "gh"));
|
|
11342
11422
|
}
|
|
11343
11423
|
function cursorPluginPinsNeedingSeed(pins, releasedVersion) {
|
|
11344
11424
|
if (!isSemverVersion(releasedVersion)) return pins.filter((pin) => !pin.hasPluginJson || !pin.hasHooksJson || pin.isEmpty);
|
|
@@ -11359,7 +11439,7 @@ async function applyCursorPluginCacheSeed(input) {
|
|
|
11359
11439
|
for (const pin of pinsToSeed) {
|
|
11360
11440
|
syncDirContents(source, pin.path);
|
|
11361
11441
|
}
|
|
11362
|
-
(0,
|
|
11442
|
+
(0, import_node_fs16.rmSync)(tmpRoot, { recursive: true, force: true });
|
|
11363
11443
|
return true;
|
|
11364
11444
|
}
|
|
11365
11445
|
|
|
@@ -11400,6 +11480,17 @@ function pluginInstallManualFix(projectPath, surface = "claude-cli") {
|
|
|
11400
11480
|
function isMmiPluginEnabled(settings) {
|
|
11401
11481
|
return Boolean(settings?.enabledPlugins?.[MMI_PLUGIN_ID]);
|
|
11402
11482
|
}
|
|
11483
|
+
function buildSettingsPluginDriftCheck(input) {
|
|
11484
|
+
const base2 = {
|
|
11485
|
+
ok: true,
|
|
11486
|
+
label: "org plugin wiring in .claude/settings.json (mmi@mutmutco)",
|
|
11487
|
+
fix: "the Claude Code app pruned mmi@mutmutco from the tracked .claude/settings.json (it does this at session start when the mutmutco marketplace source does not resolve, #1805); restore it with `git checkout -- .claude/settings.json` before committing, or the whole org skill set is disabled for the branch"
|
|
11488
|
+
};
|
|
11489
|
+
const enabled = input.settings?.enabledPlugins;
|
|
11490
|
+
if (!input.isOrgRepo || !enabled || Object.keys(enabled).length === 0) return base2;
|
|
11491
|
+
if (!enabled[MMI_PLUGIN_ID]) return { ...base2, ok: false };
|
|
11492
|
+
return base2;
|
|
11493
|
+
}
|
|
11403
11494
|
function hasProjectInstallRecord(file, pluginId, projectPath) {
|
|
11404
11495
|
const records = file?.plugins?.[pluginId];
|
|
11405
11496
|
if (!Array.isArray(records)) return false;
|
|
@@ -12016,8 +12107,8 @@ async function runStageLiveDown(deps, t) {
|
|
|
12016
12107
|
}
|
|
12017
12108
|
|
|
12018
12109
|
// src/design-system.ts
|
|
12019
|
-
var
|
|
12020
|
-
var
|
|
12110
|
+
var import_node_fs17 = require("node:fs");
|
|
12111
|
+
var import_node_path16 = require("node:path");
|
|
12021
12112
|
var UI_PACKAGE_CANDIDATES = ["@mutmutco/ui-dashboard", "@mutmutco/ui", "@mutmutco/theme"];
|
|
12022
12113
|
var DESIGN_SYSTEM_VERSION_LABEL = "@mutmutco design-system npm package (vs @latest)";
|
|
12023
12114
|
function dashboardConsumerRegistryFix(error) {
|
|
@@ -12066,17 +12157,17 @@ function buildDesignSystemVersionCheck(input) {
|
|
|
12066
12157
|
}
|
|
12067
12158
|
function readJsonFile(path2) {
|
|
12068
12159
|
try {
|
|
12069
|
-
return JSON.parse((0,
|
|
12160
|
+
return JSON.parse((0, import_node_fs17.readFileSync)(path2, "utf8"));
|
|
12070
12161
|
} catch {
|
|
12071
12162
|
return void 0;
|
|
12072
12163
|
}
|
|
12073
12164
|
}
|
|
12074
12165
|
function isUiFactoryCheckout(root) {
|
|
12075
|
-
const pkg = readJsonFile((0,
|
|
12166
|
+
const pkg = readJsonFile((0, import_node_path16.join)(root, "package.json"));
|
|
12076
12167
|
return pkg?.name === "mmd-ui" && pkg?.private === true;
|
|
12077
12168
|
}
|
|
12078
12169
|
function resolveDeclaredUiPackage(root) {
|
|
12079
|
-
const pkg = readJsonFile((0,
|
|
12170
|
+
const pkg = readJsonFile((0, import_node_path16.join)(root, "package.json"));
|
|
12080
12171
|
if (!pkg) return void 0;
|
|
12081
12172
|
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
12082
12173
|
for (const name of UI_PACKAGE_CANDIDATES) {
|
|
@@ -12086,8 +12177,8 @@ function resolveDeclaredUiPackage(root) {
|
|
|
12086
12177
|
return void 0;
|
|
12087
12178
|
}
|
|
12088
12179
|
function readLockfileInstalledVersion(root, packageName) {
|
|
12089
|
-
const lockPath = (0,
|
|
12090
|
-
if (!(0,
|
|
12180
|
+
const lockPath = (0, import_node_path16.join)(root, "package-lock.json");
|
|
12181
|
+
if (!(0, import_node_fs17.existsSync)(lockPath)) return void 0;
|
|
12091
12182
|
const lock = readJsonFile(lockPath);
|
|
12092
12183
|
const node = lock?.packages?.[`node_modules/${packageName}`];
|
|
12093
12184
|
const version = node?.version?.trim();
|
|
@@ -12116,15 +12207,15 @@ function designSystemSnapshot(root) {
|
|
|
12116
12207
|
|
|
12117
12208
|
// src/design-system-registry.ts
|
|
12118
12209
|
var import_node_crypto7 = require("node:crypto");
|
|
12119
|
-
var
|
|
12120
|
-
var
|
|
12210
|
+
var import_node_fs19 = require("node:fs");
|
|
12211
|
+
var import_node_path17 = require("node:path");
|
|
12121
12212
|
|
|
12122
12213
|
// src/atomic-write.ts
|
|
12123
|
-
var
|
|
12214
|
+
var import_node_fs18 = require("node:fs");
|
|
12124
12215
|
function atomicWriteFileSync(path2, content) {
|
|
12125
12216
|
const tmp = `${path2}.${process.pid}.tmp`;
|
|
12126
|
-
(0,
|
|
12127
|
-
(0,
|
|
12217
|
+
(0, import_node_fs18.writeFileSync)(tmp, content, "utf8");
|
|
12218
|
+
(0, import_node_fs18.renameSync)(tmp, path2);
|
|
12128
12219
|
}
|
|
12129
12220
|
|
|
12130
12221
|
// src/design-system-registry.ts
|
|
@@ -12135,13 +12226,13 @@ var REGISTRY_FIX = "run `mmi-cli doctor --apply` to pull registry components int
|
|
|
12135
12226
|
var REGISTRY_UNREACHABLE_FIX = "live @mutmutco registry unreachable \u2014 verify `components.json` `@mutmutco` registry URL and network, then retry `mmi-cli doctor`";
|
|
12136
12227
|
function readJsonFile2(path2) {
|
|
12137
12228
|
try {
|
|
12138
|
-
return JSON.parse((0,
|
|
12229
|
+
return JSON.parse((0, import_node_fs19.readFileSync)(path2, "utf8"));
|
|
12139
12230
|
} catch {
|
|
12140
12231
|
return void 0;
|
|
12141
12232
|
}
|
|
12142
12233
|
}
|
|
12143
12234
|
function readComponentsJson(root) {
|
|
12144
|
-
return readJsonFile2((0,
|
|
12235
|
+
return readJsonFile2((0, import_node_path17.join)(root, "components.json"));
|
|
12145
12236
|
}
|
|
12146
12237
|
function hasMutmutcoRegistry(root) {
|
|
12147
12238
|
const url = readComponentsJson(root)?.registries?.["@mutmutco"];
|
|
@@ -12149,7 +12240,7 @@ function hasMutmutcoRegistry(root) {
|
|
|
12149
12240
|
}
|
|
12150
12241
|
function resolveCacheDir(root) {
|
|
12151
12242
|
const custom = readComponentsJson(root)?.mmi?.cacheDir;
|
|
12152
|
-
return (0,
|
|
12243
|
+
return (0, import_node_path17.join)(root, custom ?? DESIGN_SYSTEM_CACHE_DIR);
|
|
12153
12244
|
}
|
|
12154
12245
|
function resolveRegistryUrlTemplate(root) {
|
|
12155
12246
|
return readComponentsJson(root)?.registries?.["@mutmutco"];
|
|
@@ -12158,7 +12249,7 @@ function registryItemUrl(template, name) {
|
|
|
12158
12249
|
return template.replace("{name}", name);
|
|
12159
12250
|
}
|
|
12160
12251
|
function readDesignSystemManifest(root) {
|
|
12161
|
-
const raw = readJsonFile2((0,
|
|
12252
|
+
const raw = readJsonFile2((0, import_node_path17.join)(root, DESIGN_SYSTEM_MANIFEST_PATH));
|
|
12162
12253
|
if (!raw || !Array.isArray(raw.components)) return void 0;
|
|
12163
12254
|
return raw;
|
|
12164
12255
|
}
|
|
@@ -12170,11 +12261,11 @@ function listInstalledRegistryComponents(root) {
|
|
|
12170
12261
|
return scanCachedComponentNames(resolveCacheDir(root));
|
|
12171
12262
|
}
|
|
12172
12263
|
function scanCachedComponentNames(cacheDir) {
|
|
12173
|
-
if (!(0,
|
|
12264
|
+
if (!(0, import_node_fs19.existsSync)(cacheDir)) return [];
|
|
12174
12265
|
const names = /* @__PURE__ */ new Set();
|
|
12175
12266
|
const walk = (dir) => {
|
|
12176
|
-
for (const ent of (0,
|
|
12177
|
-
const p = (0,
|
|
12267
|
+
for (const ent of (0, import_node_fs19.readdirSync)(dir, { withFileTypes: true })) {
|
|
12268
|
+
const p = (0, import_node_path17.join)(dir, ent.name);
|
|
12178
12269
|
if (ent.isDirectory()) walk(p);
|
|
12179
12270
|
else if (ent.isFile() && /\.(tsx?|jsx?)$/.test(ent.name)) {
|
|
12180
12271
|
names.add(ent.name.replace(/\.(tsx|ts|jsx|js)$/, ""));
|
|
@@ -12263,13 +12354,13 @@ async function gatherRegistryComponentsState(root, targetVersion, deps) {
|
|
|
12263
12354
|
let componentStale = false;
|
|
12264
12355
|
for (const file of item.files) {
|
|
12265
12356
|
if (!file.target || file.content == null) continue;
|
|
12266
|
-
const cachePath = (0,
|
|
12267
|
-
if (!(0,
|
|
12357
|
+
const cachePath = (0, import_node_path17.join)(cacheDir, cacheRelativePath(file.target));
|
|
12358
|
+
if (!(0, import_node_fs19.existsSync)(cachePath)) {
|
|
12268
12359
|
componentStale = true;
|
|
12269
12360
|
break;
|
|
12270
12361
|
}
|
|
12271
12362
|
try {
|
|
12272
|
-
if (contentHash((0,
|
|
12363
|
+
if (contentHash((0, import_node_fs19.readFileSync)(cachePath, "utf8")) !== contentHash(file.content)) {
|
|
12273
12364
|
componentStale = true;
|
|
12274
12365
|
break;
|
|
12275
12366
|
}
|
|
@@ -12279,7 +12370,7 @@ async function gatherRegistryComponentsState(root, targetVersion, deps) {
|
|
|
12279
12370
|
}
|
|
12280
12371
|
}
|
|
12281
12372
|
if (componentStale) {
|
|
12282
|
-
if ((0,
|
|
12373
|
+
if ((0, import_node_fs19.existsSync)((0, import_node_path17.join)(cacheDir, "ui", `${name}.tsx`)) || (0, import_node_fs19.existsSync)((0, import_node_path17.join)(cacheDir, `${name}.tsx`))) {
|
|
12283
12374
|
stale.push(name);
|
|
12284
12375
|
} else {
|
|
12285
12376
|
missing.push(name);
|
|
@@ -12307,15 +12398,15 @@ async function applyRegistryComponentsSync(root, components, targetVersion, log,
|
|
|
12307
12398
|
if (!item) return { ok: false };
|
|
12308
12399
|
for (const file of item.files) {
|
|
12309
12400
|
if (!file.target || file.content == null) continue;
|
|
12310
|
-
const outPath = (0,
|
|
12311
|
-
deps.mkdir((0,
|
|
12401
|
+
const outPath = (0, import_node_path17.join)(cacheDir, cacheRelativePath(file.target));
|
|
12402
|
+
deps.mkdir((0, import_node_path17.dirname)(outPath));
|
|
12312
12403
|
const body = file.content.endsWith("\n") ? file.content : `${file.content}
|
|
12313
12404
|
`;
|
|
12314
12405
|
deps.writeFile(outPath, body);
|
|
12315
12406
|
}
|
|
12316
12407
|
}
|
|
12317
|
-
const manifestPath = (0,
|
|
12318
|
-
deps.mkdir((0,
|
|
12408
|
+
const manifestPath = (0, import_node_path17.join)(root, DESIGN_SYSTEM_MANIFEST_PATH);
|
|
12409
|
+
deps.mkdir((0, import_node_path17.dirname)(manifestPath));
|
|
12319
12410
|
const manifest = {
|
|
12320
12411
|
version: targetVersion,
|
|
12321
12412
|
syncedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -12330,14 +12421,14 @@ function defaultRegistrySyncDeps() {
|
|
|
12330
12421
|
return {
|
|
12331
12422
|
fetch,
|
|
12332
12423
|
writeFile: (path2, content) => atomicWriteFileSync(path2, content),
|
|
12333
|
-
mkdir: (path2) => (0,
|
|
12424
|
+
mkdir: (path2) => (0, import_node_fs19.mkdirSync)(path2, { recursive: true })
|
|
12334
12425
|
};
|
|
12335
12426
|
}
|
|
12336
12427
|
|
|
12337
12428
|
// src/stage-runner.ts
|
|
12338
12429
|
var import_node_child_process9 = require("node:child_process");
|
|
12339
|
-
var
|
|
12340
|
-
var
|
|
12430
|
+
var import_node_fs20 = require("node:fs");
|
|
12431
|
+
var import_node_path18 = require("node:path");
|
|
12341
12432
|
var import_node_net2 = require("node:net");
|
|
12342
12433
|
var import_node_util7 = require("node:util");
|
|
12343
12434
|
var execFileP4 = (0, import_node_util7.promisify)(import_node_child_process9.execFile);
|
|
@@ -12382,7 +12473,7 @@ function detectStaleEnvFile(exampleContent, targetContent, mtimes) {
|
|
|
12382
12473
|
return void 0;
|
|
12383
12474
|
}
|
|
12384
12475
|
function stageStatePath(cwd = process.cwd()) {
|
|
12385
|
-
return (0,
|
|
12476
|
+
return (0, import_node_path18.join)(cwd, "tmp", "stage", "state.json");
|
|
12386
12477
|
}
|
|
12387
12478
|
function mergeEnvSecretsIntoFile(content, secrets) {
|
|
12388
12479
|
const lines = content.split(/\r?\n/);
|
|
@@ -12465,9 +12556,9 @@ async function shell(command, cwd, timeoutMs) {
|
|
|
12465
12556
|
});
|
|
12466
12557
|
}
|
|
12467
12558
|
function readState(path2) {
|
|
12468
|
-
if (!(0,
|
|
12559
|
+
if (!(0, import_node_fs20.existsSync)(path2)) return null;
|
|
12469
12560
|
try {
|
|
12470
|
-
return JSON.parse((0,
|
|
12561
|
+
return JSON.parse((0, import_node_fs20.readFileSync)(path2, "utf8"));
|
|
12471
12562
|
} catch {
|
|
12472
12563
|
return null;
|
|
12473
12564
|
}
|
|
@@ -12519,7 +12610,7 @@ async function stopStage(opts = {}) {
|
|
|
12519
12610
|
return { ok: true, action: "stop", statePath, message: "no previous stage state found" };
|
|
12520
12611
|
}
|
|
12521
12612
|
await killTree(state.pid);
|
|
12522
|
-
(0,
|
|
12613
|
+
(0, import_node_fs20.rmSync)(statePath, { force: true });
|
|
12523
12614
|
return { ok: true, action: "stop", statePath, pid: state.pid, message: `stopped previous stage pid ${state.pid}` };
|
|
12524
12615
|
}
|
|
12525
12616
|
async function startStage(config = {}, opts = {}) {
|
|
@@ -12528,7 +12619,7 @@ async function startStage(config = {}, opts = {}) {
|
|
|
12528
12619
|
const cwd = opts.cwd ?? process.cwd();
|
|
12529
12620
|
const statePath = opts.statePath ?? stageStatePath(cwd);
|
|
12530
12621
|
const dir = statePath.slice(0, Math.max(statePath.lastIndexOf("/"), statePath.lastIndexOf("\\")));
|
|
12531
|
-
(0,
|
|
12622
|
+
(0, import_node_fs20.mkdirSync)(dir, { recursive: true });
|
|
12532
12623
|
let stagePort;
|
|
12533
12624
|
if (config.portRange) {
|
|
12534
12625
|
const [s, e] = config.portRange;
|
|
@@ -12538,14 +12629,14 @@ async function startStage(config = {}, opts = {}) {
|
|
|
12538
12629
|
}
|
|
12539
12630
|
const sub = (s) => s != null && stagePort != null ? s.replace(/\$\{?STAGE_PORT\}?/g, String(stagePort)) : s;
|
|
12540
12631
|
if (config.ensureEnv) {
|
|
12541
|
-
const target = (0,
|
|
12542
|
-
const example = (0,
|
|
12543
|
-
if (!(0,
|
|
12544
|
-
(0,
|
|
12545
|
-
} else if ((0,
|
|
12546
|
-
const stale = detectStaleEnvFile((0,
|
|
12547
|
-
exampleMtimeMs: (0,
|
|
12548
|
-
targetMtimeMs: (0,
|
|
12632
|
+
const target = (0, import_node_path18.join)(cwd, config.ensureEnv.target);
|
|
12633
|
+
const example = (0, import_node_path18.join)(cwd, config.ensureEnv.example);
|
|
12634
|
+
if (!(0, import_node_fs20.existsSync)(target) && (0, import_node_fs20.existsSync)(example)) {
|
|
12635
|
+
(0, import_node_fs20.copyFileSync)(example, target);
|
|
12636
|
+
} else if ((0, import_node_fs20.existsSync)(target) && (0, import_node_fs20.existsSync)(example)) {
|
|
12637
|
+
const stale = detectStaleEnvFile((0, import_node_fs20.readFileSync)(example, "utf8"), (0, import_node_fs20.readFileSync)(target, "utf8"), {
|
|
12638
|
+
exampleMtimeMs: (0, import_node_fs20.statSync)(example).mtimeMs,
|
|
12639
|
+
targetMtimeMs: (0, import_node_fs20.statSync)(target).mtimeMs
|
|
12549
12640
|
});
|
|
12550
12641
|
if (stale) {
|
|
12551
12642
|
const msg = `stale ${config.ensureEnv.target} (${stale}) \u2014 delete it or refresh from ${config.ensureEnv.example} before re-running /stage`;
|
|
@@ -12553,8 +12644,8 @@ async function startStage(config = {}, opts = {}) {
|
|
|
12553
12644
|
console.error(`mmi-cli stage: ${msg} (allowed via --allow-stale-env)`);
|
|
12554
12645
|
}
|
|
12555
12646
|
}
|
|
12556
|
-
if (opts.vaultEnvMerge && Object.keys(opts.vaultEnvMerge).length && (0,
|
|
12557
|
-
(0,
|
|
12647
|
+
if (opts.vaultEnvMerge && Object.keys(opts.vaultEnvMerge).length && (0, import_node_fs20.existsSync)(target)) {
|
|
12648
|
+
(0, import_node_fs20.writeFileSync)(target, mergeEnvSecretsIntoFile((0, import_node_fs20.readFileSync)(target, "utf8"), opts.vaultEnvMerge), "utf8");
|
|
12558
12649
|
}
|
|
12559
12650
|
}
|
|
12560
12651
|
const extraEnv = {};
|
|
@@ -12579,13 +12670,13 @@ async function startStage(config = {}, opts = {}) {
|
|
|
12579
12670
|
healthUrl: sub(config.healthUrl?.trim()) || void 0,
|
|
12580
12671
|
port: stagePort
|
|
12581
12672
|
};
|
|
12582
|
-
(0,
|
|
12673
|
+
(0, import_node_fs20.writeFileSync)(statePath, JSON.stringify(state, null, 2), "utf8");
|
|
12583
12674
|
try {
|
|
12584
12675
|
if (state.healthUrl) await waitForHealth(state.healthUrl, opts.timeoutMs ?? 6e4, config.healthAnyStatus);
|
|
12585
12676
|
else await waitForProcessStability(child);
|
|
12586
12677
|
} catch (e) {
|
|
12587
12678
|
await killTree(state.pid);
|
|
12588
|
-
(0,
|
|
12679
|
+
(0, import_node_fs20.rmSync)(statePath, { force: true });
|
|
12589
12680
|
throw e;
|
|
12590
12681
|
}
|
|
12591
12682
|
const result = { ok: true, action: "start", statePath, pid: state.pid, port: stagePort, message: `started stage pid ${state.pid}${stagePort != null ? ` on port ${stagePort}` : ""}` };
|
|
@@ -13043,18 +13134,25 @@ async function rollDevelopmentForward(deps, ctx, tag) {
|
|
|
13043
13134
|
}
|
|
13044
13135
|
function resolveContextState(context, checkRuns, statuses) {
|
|
13045
13136
|
let sawFailure = false;
|
|
13137
|
+
let sawSuccess = false;
|
|
13138
|
+
let sawPending = false;
|
|
13046
13139
|
for (const r of checkRuns) {
|
|
13047
13140
|
if (r.name !== context) continue;
|
|
13048
13141
|
if (r.status === "completed") {
|
|
13049
|
-
if (r.conclusion === "success")
|
|
13050
|
-
if (r.conclusion !== "skipped" && r.conclusion !== "neutral") sawFailure = true;
|
|
13142
|
+
if (r.conclusion === "success") sawSuccess = true;
|
|
13143
|
+
else if (r.conclusion !== "skipped" && r.conclusion !== "neutral") sawFailure = true;
|
|
13144
|
+
} else {
|
|
13145
|
+
sawPending = true;
|
|
13051
13146
|
}
|
|
13052
13147
|
}
|
|
13053
13148
|
for (const s of statuses) {
|
|
13054
13149
|
if (s.context !== context) continue;
|
|
13055
|
-
if (s.state === "success")
|
|
13056
|
-
if (s.state === "failure" || s.state === "error") sawFailure = true;
|
|
13150
|
+
if (s.state === "success") sawSuccess = true;
|
|
13151
|
+
else if (s.state === "failure" || s.state === "error") sawFailure = true;
|
|
13152
|
+
else sawPending = true;
|
|
13057
13153
|
}
|
|
13154
|
+
if (sawPending) return "pending";
|
|
13155
|
+
if (sawSuccess) return "success";
|
|
13058
13156
|
return sawFailure ? "failed" : "pending";
|
|
13059
13157
|
}
|
|
13060
13158
|
async function waitForRequiredTrainChecks(deps, ctx, sha, required) {
|
|
@@ -14403,7 +14501,7 @@ async function announceRelease(deps, args) {
|
|
|
14403
14501
|
}
|
|
14404
14502
|
|
|
14405
14503
|
// src/port-registry.ts
|
|
14406
|
-
var
|
|
14504
|
+
var import_node_fs21 = require("node:fs");
|
|
14407
14505
|
|
|
14408
14506
|
// ../infra/port-geometry.mjs
|
|
14409
14507
|
var PORT_BLOCK = 100;
|
|
@@ -14417,8 +14515,8 @@ function nextPortBlock(registry2) {
|
|
|
14417
14515
|
return [base2, base2 + PORT_SPAN];
|
|
14418
14516
|
}
|
|
14419
14517
|
function loadPortRegistry(path2) {
|
|
14420
|
-
if (!(0,
|
|
14421
|
-
const raw = JSON.parse((0,
|
|
14518
|
+
if (!(0, import_node_fs21.existsSync)(path2)) return {};
|
|
14519
|
+
const raw = JSON.parse((0, import_node_fs21.readFileSync)(path2, "utf8"));
|
|
14422
14520
|
const out = {};
|
|
14423
14521
|
for (const [key, value] of Object.entries(raw)) {
|
|
14424
14522
|
if (Array.isArray(value) && value.length === 2 && value.every((n) => typeof n === "number")) {
|
|
@@ -14432,9 +14530,9 @@ function ensurePortRange(repo, path2) {
|
|
|
14432
14530
|
const existing = registry2[repo];
|
|
14433
14531
|
if (existing) return existing;
|
|
14434
14532
|
const range = nextPortBlock(registry2);
|
|
14435
|
-
const raw = (0,
|
|
14533
|
+
const raw = (0, import_node_fs21.existsSync)(path2) ? JSON.parse((0, import_node_fs21.readFileSync)(path2, "utf8")) : {};
|
|
14436
14534
|
raw[repo] = range;
|
|
14437
|
-
(0,
|
|
14535
|
+
(0, import_node_fs21.writeFileSync)(path2, JSON.stringify(raw, null, 2) + "\n", "utf8");
|
|
14438
14536
|
return range;
|
|
14439
14537
|
}
|
|
14440
14538
|
function portCursorSeed(registry2) {
|
|
@@ -15814,7 +15912,7 @@ function parseOauthVar(raw) {
|
|
|
15814
15912
|
throw new Error('project set: oauth must be JSON, e.g. {"subdomains":["app"],"domains":["example.co"],"callbackPath":"/auth/callback"}');
|
|
15815
15913
|
}
|
|
15816
15914
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
15817
|
-
throw new Error("project set: oauth must be a {subdomains,domains,callbackPath} object");
|
|
15915
|
+
throw new Error("project set: oauth must be a {subdomains,domains,callbackPath,fofuSubdomain} object");
|
|
15818
15916
|
}
|
|
15819
15917
|
const map = parsed;
|
|
15820
15918
|
const out = {};
|
|
@@ -15827,8 +15925,11 @@ function parseOauthVar(raw) {
|
|
|
15827
15925
|
} else if (key === "callbackPath") {
|
|
15828
15926
|
if (typeof value !== "string" || !value.trim()) throw new Error("project set: oauth.callbackPath must be a non-empty string");
|
|
15829
15927
|
out.callbackPath = value.trim();
|
|
15928
|
+
} else if (key === "fofuSubdomain") {
|
|
15929
|
+
if (typeof value !== "string") throw new Error('project set: oauth.fofuSubdomain must be a string ("" selects the apex fofu.ai)');
|
|
15930
|
+
out.fofuSubdomain = value.trim();
|
|
15830
15931
|
} else {
|
|
15831
|
-
throw new Error(`project set: oauth key "${key}" \u2014 expected only subdomains/domains/callbackPath`);
|
|
15932
|
+
throw new Error(`project set: oauth key "${key}" \u2014 expected only subdomains/domains/callbackPath/fofuSubdomain`);
|
|
15832
15933
|
}
|
|
15833
15934
|
}
|
|
15834
15935
|
return out;
|
|
@@ -15943,7 +16044,7 @@ var SETTABLE_VAR_HINTS = {
|
|
|
15943
16044
|
dashboard: "true|false",
|
|
15944
16045
|
fofuEnabled: "true|false",
|
|
15945
16046
|
repos: 'JSON array, e.g. ["mutmutco/mm-foo"]',
|
|
15946
|
-
oauth: "JSON {subdomains,domains,callbackPath}",
|
|
16047
|
+
oauth: "JSON {subdomains,domains,callbackPath,fofuSubdomain}",
|
|
15947
16048
|
requiredGcpApis: "comma-string",
|
|
15948
16049
|
requiredRuntimeSecrets: 'JSON stage map, e.g. {"dev":["KEY"],"rc":["KEY"],"main":["KEY"]}',
|
|
15949
16050
|
edgeDomains: "JSON {dev,rc,main} domain map",
|
|
@@ -16134,15 +16235,15 @@ function parseKbTree(stdout, prefix) {
|
|
|
16134
16235
|
}
|
|
16135
16236
|
|
|
16136
16237
|
// src/northstar-commands.ts
|
|
16137
|
-
var
|
|
16238
|
+
var import_node_fs22 = require("node:fs");
|
|
16138
16239
|
var import_node_child_process11 = require("node:child_process");
|
|
16139
16240
|
var import_promises6 = require("node:fs/promises");
|
|
16140
16241
|
|
|
16141
16242
|
// src/plan.ts
|
|
16142
|
-
var
|
|
16243
|
+
var import_node_path19 = require("node:path");
|
|
16143
16244
|
var PLANS_DIR = "plans";
|
|
16144
|
-
var META_FILE = (0,
|
|
16145
|
-
var planPath = (slug) => (0,
|
|
16245
|
+
var META_FILE = (0, import_node_path19.join)(PLANS_DIR, ".plan-meta.json");
|
|
16246
|
+
var planPath = (slug) => (0, import_node_path19.join)(PLANS_DIR, `${slug}.md`);
|
|
16146
16247
|
var metaKey = (project2, slug) => `${project2}/${slug}`;
|
|
16147
16248
|
function parseMeta(raw) {
|
|
16148
16249
|
if (!raw) return {};
|
|
@@ -16167,7 +16268,7 @@ function hashContent(s) {
|
|
|
16167
16268
|
function staleHint(slug) {
|
|
16168
16269
|
return `remote "${slug}" is newer \u2014 run \`mmi-cli northstar pull ${slug}\` first (your local is based on an older version), or re-push with \`--force\` to overwrite`;
|
|
16169
16270
|
}
|
|
16170
|
-
var INDEX_FILE = (0,
|
|
16271
|
+
var INDEX_FILE = (0, import_node_path19.join)(PLANS_DIR, ".index.json");
|
|
16171
16272
|
var INDEX_TTL_MS = 6e4;
|
|
16172
16273
|
function parseIndex(raw) {
|
|
16173
16274
|
if (!raw) return null;
|
|
@@ -16196,7 +16297,7 @@ function mergeIndex(idx, scope, plans, now) {
|
|
|
16196
16297
|
const mergedScope = idx.scope === null ? null : [.../* @__PURE__ */ new Set([...idx.scope, ...scope])];
|
|
16197
16298
|
return { fetchedAt: now, scope: mergedScope, plans: [...kept, ...plans] };
|
|
16198
16299
|
}
|
|
16199
|
-
var QUEUE_FILE = (0,
|
|
16300
|
+
var QUEUE_FILE = (0, import_node_path19.join)(PLANS_DIR, ".sync-queue.json");
|
|
16200
16301
|
var QUEUE_MAX_ATTEMPTS = 10;
|
|
16201
16302
|
function isValidQueueEntry(e) {
|
|
16202
16303
|
if (!e || typeof e !== "object") return false;
|
|
@@ -16671,7 +16772,7 @@ function detachPlanSync() {
|
|
|
16671
16772
|
}
|
|
16672
16773
|
}
|
|
16673
16774
|
function makePlanDeps(cfg, io = consoleIo) {
|
|
16674
|
-
const ensureDir = () => (0,
|
|
16775
|
+
const ensureDir = () => (0, import_node_fs22.mkdirSync)(PLANS_DIR, { recursive: true });
|
|
16675
16776
|
return {
|
|
16676
16777
|
apiUrl: cfg.sagaApiUrl,
|
|
16677
16778
|
fetch: (url, init = {}) => fetch(url, { ...init, signal: init.signal ?? AbortSignal.timeout(1e4) }),
|
|
@@ -16679,24 +16780,24 @@ function makePlanDeps(cfg, io = consoleIo) {
|
|
|
16679
16780
|
project: async () => (await sagaKey(cfg)).project,
|
|
16680
16781
|
readLocal: (slug) => {
|
|
16681
16782
|
try {
|
|
16682
|
-
return (0,
|
|
16783
|
+
return (0, import_node_fs22.readFileSync)(planPath(slug), "utf8");
|
|
16683
16784
|
} catch {
|
|
16684
16785
|
return null;
|
|
16685
16786
|
}
|
|
16686
16787
|
},
|
|
16687
16788
|
writeLocal: (slug, content) => {
|
|
16688
16789
|
ensureDir();
|
|
16689
|
-
(0,
|
|
16790
|
+
(0, import_node_fs22.writeFileSync)(planPath(slug), content, "utf8");
|
|
16690
16791
|
},
|
|
16691
16792
|
removeLocal: (slug) => {
|
|
16692
16793
|
try {
|
|
16693
|
-
(0,
|
|
16794
|
+
(0, import_node_fs22.rmSync)(planPath(slug));
|
|
16694
16795
|
} catch {
|
|
16695
16796
|
}
|
|
16696
16797
|
},
|
|
16697
16798
|
readMetaRaw: () => {
|
|
16698
16799
|
try {
|
|
16699
|
-
return (0,
|
|
16800
|
+
return (0, import_node_fs22.readFileSync)(META_FILE, "utf8");
|
|
16700
16801
|
} catch {
|
|
16701
16802
|
return null;
|
|
16702
16803
|
}
|
|
@@ -16707,7 +16808,7 @@ function makePlanDeps(cfg, io = consoleIo) {
|
|
|
16707
16808
|
},
|
|
16708
16809
|
readIndexRaw: () => {
|
|
16709
16810
|
try {
|
|
16710
|
-
return (0,
|
|
16811
|
+
return (0, import_node_fs22.readFileSync)(INDEX_FILE, "utf8");
|
|
16711
16812
|
} catch {
|
|
16712
16813
|
return null;
|
|
16713
16814
|
}
|
|
@@ -16718,7 +16819,7 @@ function makePlanDeps(cfg, io = consoleIo) {
|
|
|
16718
16819
|
},
|
|
16719
16820
|
readQueueRaw: () => {
|
|
16720
16821
|
try {
|
|
16721
|
-
return (0,
|
|
16822
|
+
return (0, import_node_fs22.readFileSync)(QUEUE_FILE, "utf8");
|
|
16722
16823
|
} catch {
|
|
16723
16824
|
return null;
|
|
16724
16825
|
}
|
|
@@ -16753,7 +16854,7 @@ async function withPlan(quiet, run, io = consoleIo) {
|
|
|
16753
16854
|
}
|
|
16754
16855
|
await run(makePlanDeps(cfg, io));
|
|
16755
16856
|
}
|
|
16756
|
-
async function gatherRelevanceSignals() {
|
|
16857
|
+
async function gatherRelevanceSignals(opts = {}) {
|
|
16757
16858
|
const branch = await gitOut(["rev-parse", "--abbrev-ref", "HEAD"]).catch(() => "") || void 0;
|
|
16758
16859
|
const changed = (await gitOut(["diff", "--name-only", "HEAD"]).catch(() => "")).split("\n").map((s) => s.trim()).filter(Boolean);
|
|
16759
16860
|
const signals = { branch, changedFiles: changed.length ? changed : void 0 };
|
|
@@ -16771,6 +16872,10 @@ async function gatherRelevanceSignals() {
|
|
|
16771
16872
|
} catch {
|
|
16772
16873
|
}
|
|
16773
16874
|
}
|
|
16875
|
+
if (opts.anchorSlug) {
|
|
16876
|
+
const slug = (await opts.anchorSlug().catch(() => void 0))?.trim();
|
|
16877
|
+
if (slug) signals.anchorSlug = slug;
|
|
16878
|
+
}
|
|
16774
16879
|
return signals;
|
|
16775
16880
|
}
|
|
16776
16881
|
function registerNorthStarCommands(cmd) {
|
|
@@ -16891,9 +16996,7 @@ function registerSecretsCommands(program3) {
|
|
|
16891
16996
|
});
|
|
16892
16997
|
const centralContainer = meta?.deployModel === "tenant-container" || meta?.deployModel === "solo-container";
|
|
16893
16998
|
if (!o.required?.length && centralContainer && meta && !hasRuntimeSecretContract(meta.requiredRuntimeSecrets)) {
|
|
16894
|
-
d.err(
|
|
16895
|
-
process.exitCode = 1;
|
|
16896
|
-
return;
|
|
16999
|
+
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.');
|
|
16897
17000
|
}
|
|
16898
17001
|
const ok = await secretsPreflight(d, { repo: o.repo, stage: o.stage, required });
|
|
16899
17002
|
if (!ok) process.exitCode = 1;
|
|
@@ -16958,6 +17061,9 @@ function expectedHosts(cfg) {
|
|
|
16958
17061
|
for (const env of ENV_PREFIXES) out.push(env ? `${env}.${base2}` : base2);
|
|
16959
17062
|
}
|
|
16960
17063
|
}
|
|
17064
|
+
if (cfg.fofuSubdomain !== void 0) {
|
|
17065
|
+
out.push(cfg.fofuSubdomain ? `${cfg.fofuSubdomain}.fofu.ai` : "fofu.ai");
|
|
17066
|
+
}
|
|
16961
17067
|
return uniq(out);
|
|
16962
17068
|
}
|
|
16963
17069
|
function expectedJsOrigins(cfg) {
|
|
@@ -17002,7 +17108,10 @@ function parseOauthConfig(mmiConfig, slug) {
|
|
|
17002
17108
|
if (!callbackPath.startsWith("/")) {
|
|
17003
17109
|
throw new Error(`oauth.callbackPath must start with "/" (got ${JSON.stringify(callbackPath)})`);
|
|
17004
17110
|
}
|
|
17005
|
-
|
|
17111
|
+
const meta = mmiConfig ?? {};
|
|
17112
|
+
const rawFofuSub = raw.fofuSubdomain;
|
|
17113
|
+
const fofuSubdomain = meta.fofuEnabled === true ? typeof rawFofuSub === "string" ? rawFofuSub : defaultSubdomain2(slug) : void 0;
|
|
17114
|
+
return { subdomains, domains, callbackPath, fofuSubdomain };
|
|
17006
17115
|
}
|
|
17007
17116
|
function probeRedirectUri(callbackPath, port = 9123) {
|
|
17008
17117
|
return `http://localhost:${port}${callbackPath}`;
|
|
@@ -17166,7 +17275,7 @@ async function fetchHubVersionInfo(baseUrl) {
|
|
|
17166
17275
|
}
|
|
17167
17276
|
function readRepoVersion() {
|
|
17168
17277
|
try {
|
|
17169
|
-
return JSON.parse((0,
|
|
17278
|
+
return JSON.parse((0, import_node_fs23.readFileSync)((0, import_node_path20.join)(process.cwd(), ".claude-plugin", "plugin.json"), "utf8")).version || void 0;
|
|
17170
17279
|
} catch {
|
|
17171
17280
|
return void 0;
|
|
17172
17281
|
}
|
|
@@ -17341,10 +17450,10 @@ async function runRulesSync(opts, io = consoleIo) {
|
|
|
17341
17450
|
for (const entry of fetched) {
|
|
17342
17451
|
if ("error" in entry) continue;
|
|
17343
17452
|
const { file, source } = entry;
|
|
17344
|
-
const current = (0,
|
|
17453
|
+
const current = (0, import_node_fs23.existsSync)(file) ? await (0, import_promises7.readFile)(file, "utf8") : null;
|
|
17345
17454
|
if (needsUpdate(source, current)) {
|
|
17346
17455
|
const slash = file.lastIndexOf("/");
|
|
17347
|
-
if (slash > 0) (0,
|
|
17456
|
+
if (slash > 0) (0, import_node_fs23.mkdirSync)(file.slice(0, slash), { recursive: true });
|
|
17348
17457
|
await (0, import_promises7.writeFile)(file, normalizeEol(source), "utf8");
|
|
17349
17458
|
changed++;
|
|
17350
17459
|
if (!opts.quiet) io.log(`mmi-cli rules: updated ${file}`);
|
|
@@ -17357,6 +17466,27 @@ var rules = program2.command("rules").description("org rules delivery");
|
|
|
17357
17466
|
rules.command("sync").option("--quiet", "stay silent unless something changed or errored").description("fetch the org-delivered files (AGENTS.md / CLAUDE.md / .claude/settings.json / output style / Cursor rule) from MMI-Hub and write them verbatim (org-owned, whole-file)").action(async (opts) => {
|
|
17358
17467
|
if (!await runRulesSync(opts)) process.exitCode = 1;
|
|
17359
17468
|
});
|
|
17469
|
+
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) => {
|
|
17470
|
+
const path2 = (0, import_node_path20.join)(process.cwd(), ".gitignore");
|
|
17471
|
+
const current = (0, import_node_fs23.existsSync)(path2) ? (0, import_node_fs23.readFileSync)(path2, "utf8") : null;
|
|
17472
|
+
const plan2 = planManagedGitignore(current);
|
|
17473
|
+
const drift = [...plan2.added.map((l) => `+${l}`), ...plan2.removed.map((l) => `-${l}`)].join(", ") || "block normalize";
|
|
17474
|
+
if (opts.write) {
|
|
17475
|
+
if (plan2.changed) {
|
|
17476
|
+
(0, import_node_fs23.writeFileSync)(path2, plan2.content, "utf8");
|
|
17477
|
+
console.log(`mmi-cli rules gitignore: updated .gitignore (${drift})`);
|
|
17478
|
+
} else {
|
|
17479
|
+
console.log("mmi-cli rules gitignore: up to date");
|
|
17480
|
+
}
|
|
17481
|
+
return;
|
|
17482
|
+
}
|
|
17483
|
+
if (plan2.changed) {
|
|
17484
|
+
console.error(`mmi-cli rules gitignore: managed block drift (${drift}) \u2014 run \`mmi-cli rules gitignore --write\` and commit`);
|
|
17485
|
+
process.exitCode = 1;
|
|
17486
|
+
} else {
|
|
17487
|
+
console.log("mmi-cli rules gitignore: up to date");
|
|
17488
|
+
}
|
|
17489
|
+
});
|
|
17360
17490
|
async function runDocsSync(opts, io = consoleIo) {
|
|
17361
17491
|
const ref = await gitOut(["symbolic-ref", "--quiet", "--short", "refs/remotes/origin/HEAD"]);
|
|
17362
17492
|
const def = (ref.startsWith("origin/") ? ref.slice("origin/".length) : ref) || "development";
|
|
@@ -17370,7 +17500,7 @@ async function runDocsSync(opts, io = consoleIo) {
|
|
|
17370
17500
|
return null;
|
|
17371
17501
|
}
|
|
17372
17502
|
},
|
|
17373
|
-
localContent: async (f) => (0,
|
|
17503
|
+
localContent: async (f) => (0, import_node_fs23.existsSync)(f) ? await (0, import_promises7.readFile)(f, "utf8") : null,
|
|
17374
17504
|
writeDoc: async (f, c) => {
|
|
17375
17505
|
await (0, import_promises7.writeFile)(f, c, "utf8");
|
|
17376
17506
|
}
|
|
@@ -17469,7 +17599,7 @@ function runWorktreeInstall(command, cwd, quiet) {
|
|
|
17469
17599
|
async function primaryCheckoutRoot(worktreeRoot) {
|
|
17470
17600
|
try {
|
|
17471
17601
|
const out = (await execFileP2("git", ["-C", worktreeRoot, "rev-parse", "--path-format=absolute", "--git-common-dir"], { timeout: GIT_TIMEOUT_MS })).stdout.trim();
|
|
17472
|
-
return out ? (0,
|
|
17602
|
+
return out ? (0, import_node_path20.dirname)(out) : void 0;
|
|
17473
17603
|
} catch {
|
|
17474
17604
|
return void 0;
|
|
17475
17605
|
}
|
|
@@ -17482,28 +17612,28 @@ function makeProvisionDeps(worktreeRoot, quiet, log) {
|
|
|
17482
17612
|
};
|
|
17483
17613
|
}
|
|
17484
17614
|
function acquireWorktreeSetupLock(worktreeRoot) {
|
|
17485
|
-
const lockPath = (0,
|
|
17615
|
+
const lockPath = (0, import_node_path20.join)(worktreeRoot, ".mmi", "worktree-setup.lock");
|
|
17486
17616
|
const take = () => {
|
|
17487
|
-
const fd = (0,
|
|
17617
|
+
const fd = (0, import_node_fs23.openSync)(lockPath, "wx");
|
|
17488
17618
|
try {
|
|
17489
|
-
(0,
|
|
17619
|
+
(0, import_node_fs23.writeSync)(fd, String(Date.now()));
|
|
17490
17620
|
} finally {
|
|
17491
|
-
(0,
|
|
17621
|
+
(0, import_node_fs23.closeSync)(fd);
|
|
17492
17622
|
}
|
|
17493
17623
|
return () => {
|
|
17494
17624
|
try {
|
|
17495
|
-
(0,
|
|
17625
|
+
(0, import_node_fs23.rmSync)(lockPath, { force: true });
|
|
17496
17626
|
} catch {
|
|
17497
17627
|
}
|
|
17498
17628
|
};
|
|
17499
17629
|
};
|
|
17500
17630
|
try {
|
|
17501
|
-
(0,
|
|
17631
|
+
(0, import_node_fs23.mkdirSync)((0, import_node_path20.dirname)(lockPath), { recursive: true });
|
|
17502
17632
|
return take();
|
|
17503
17633
|
} catch {
|
|
17504
17634
|
try {
|
|
17505
|
-
if (Date.now() - (0,
|
|
17506
|
-
(0,
|
|
17635
|
+
if (Date.now() - (0, import_node_fs23.statSync)(lockPath).mtimeMs > WORKTREE_SETUP_LOCK_TTL_MS) {
|
|
17636
|
+
(0, import_node_fs23.rmSync)(lockPath, { force: true });
|
|
17507
17637
|
return take();
|
|
17508
17638
|
}
|
|
17509
17639
|
} catch {
|
|
@@ -18466,9 +18596,9 @@ pr.command("create").description("create a PR and print {number,url} JSON").opti
|
|
|
18466
18596
|
console.log(JSON.stringify(created));
|
|
18467
18597
|
});
|
|
18468
18598
|
async function listCiWorkflowPaths(cwd = process.cwd()) {
|
|
18469
|
-
const wfDir = (0,
|
|
18470
|
-
if (!(0,
|
|
18471
|
-
return (0,
|
|
18599
|
+
const wfDir = (0, import_node_path20.join)(cwd, ".github", "workflows");
|
|
18600
|
+
if (!(0, import_node_fs23.existsSync)(wfDir)) return [];
|
|
18601
|
+
return (0, import_node_fs23.readdirSync)(wfDir).filter((name) => /\.(ya?ml)$/i.test(name)).map((name) => `.github/workflows/${name}`);
|
|
18472
18602
|
}
|
|
18473
18603
|
async function resolveMergeCiPolicyForCheckout(repoOpt) {
|
|
18474
18604
|
const repo = repoOpt ?? await resolveRepo();
|
|
@@ -18487,7 +18617,7 @@ function ciAuditDeps() {
|
|
|
18487
18617
|
// Continuous CI delivery (#1550): the gate re-seed renders from the Hub's on-disk seed templates. The
|
|
18488
18618
|
// reconcile runs IN the Hub checkout, so this is local-file I/O (no network fetch). Path is relative to
|
|
18489
18619
|
// the repo root (e.g. skills/bootstrap/seeds/gate.template.yml).
|
|
18490
|
-
readSeedFile: (path2) => (0,
|
|
18620
|
+
readSeedFile: (path2) => (0, import_node_fs23.existsSync)(path2) ? (0, import_node_fs23.readFileSync)(path2, "utf8") : null
|
|
18491
18621
|
};
|
|
18492
18622
|
}
|
|
18493
18623
|
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) => {
|
|
@@ -18623,7 +18753,7 @@ async function createDeferredWorktreeStore() {
|
|
|
18623
18753
|
},
|
|
18624
18754
|
write: async (entries) => {
|
|
18625
18755
|
try {
|
|
18626
|
-
await (0, import_promises7.mkdir)((0,
|
|
18756
|
+
await (0, import_promises7.mkdir)((0, import_node_path20.dirname)(registryPath), { recursive: true });
|
|
18627
18757
|
await (0, import_promises7.writeFile)(registryPath, serializeDeferredWorktrees(entries), "utf8");
|
|
18628
18758
|
} catch {
|
|
18629
18759
|
}
|
|
@@ -18637,13 +18767,13 @@ var realWorktreeDirRemover = {
|
|
|
18637
18767
|
probe: (p) => {
|
|
18638
18768
|
let st;
|
|
18639
18769
|
try {
|
|
18640
|
-
st = (0,
|
|
18770
|
+
st = (0, import_node_fs23.lstatSync)(p);
|
|
18641
18771
|
} catch {
|
|
18642
18772
|
return null;
|
|
18643
18773
|
}
|
|
18644
18774
|
if (st.isSymbolicLink()) return "link";
|
|
18645
18775
|
try {
|
|
18646
|
-
(0,
|
|
18776
|
+
(0, import_node_fs23.readlinkSync)(p);
|
|
18647
18777
|
return "link";
|
|
18648
18778
|
} catch {
|
|
18649
18779
|
}
|
|
@@ -18651,7 +18781,7 @@ var realWorktreeDirRemover = {
|
|
|
18651
18781
|
},
|
|
18652
18782
|
readdir: (p) => {
|
|
18653
18783
|
try {
|
|
18654
|
-
return (0,
|
|
18784
|
+
return (0, import_node_fs23.readdirSync)(p);
|
|
18655
18785
|
} catch {
|
|
18656
18786
|
return [];
|
|
18657
18787
|
}
|
|
@@ -18660,9 +18790,9 @@ var realWorktreeDirRemover = {
|
|
|
18660
18790
|
// leaving the target); a file symlink with unlink. rmdir first, fall back to unlink.
|
|
18661
18791
|
detachLink: (p) => {
|
|
18662
18792
|
try {
|
|
18663
|
-
(0,
|
|
18793
|
+
(0, import_node_fs23.rmdirSync)(p);
|
|
18664
18794
|
} catch {
|
|
18665
|
-
(0,
|
|
18795
|
+
(0, import_node_fs23.unlinkSync)(p);
|
|
18666
18796
|
}
|
|
18667
18797
|
},
|
|
18668
18798
|
removeTree: (p) => (0, import_promises7.rm)(p, { recursive: true, force: true, maxRetries: 5, retryDelay: 200 })
|
|
@@ -18683,7 +18813,7 @@ function worktreeRemoveDeps(execGit) {
|
|
|
18683
18813
|
}
|
|
18684
18814
|
function teardownWorktreeStage(worktreePath) {
|
|
18685
18815
|
return runWorktreeStageTeardown(worktreePath, {
|
|
18686
|
-
hasStageState: (wt) => (0,
|
|
18816
|
+
hasStageState: (wt) => (0, import_node_fs23.existsSync)(stageStatePath(wt)),
|
|
18687
18817
|
stopRecordedStage: async (wt) => (await stopStage({ cwd: wt })).pid,
|
|
18688
18818
|
listComposeProjects: async () => {
|
|
18689
18819
|
const { stdout } = await execFileP2("docker", ["compose", "ls", "--all", "--format", "json"], { timeout: GC_GH_TIMEOUT_MS });
|
|
@@ -18746,7 +18876,7 @@ pr.command("merge <number>").description("merge a PR (squash by default) and cle
|
|
|
18746
18876
|
} : await cleanupPrMergeLocalBranch(headRef, {
|
|
18747
18877
|
beforeWorktrees,
|
|
18748
18878
|
startingPath,
|
|
18749
|
-
pathExists: (p) => (0,
|
|
18879
|
+
pathExists: (p) => (0, import_node_fs23.existsSync)(p),
|
|
18750
18880
|
execGit: async (args) => (await execFileP2("git", args, { timeout: GIT_TIMEOUT_MS })).stdout,
|
|
18751
18881
|
teardownWorktreeStage,
|
|
18752
18882
|
deferredStore,
|
|
@@ -18789,6 +18919,9 @@ async function runBoardRead(o) {
|
|
|
18789
18919
|
}
|
|
18790
18920
|
var board = program2.command("board").description("read, claim, show, and move Project v2 work items for the current repo");
|
|
18791
18921
|
board.command("read", { isDefault: true }).description("read the board and print user-owned, claimable, and taken items").option("--json", "machine-readable output").option("--repo <owner/repo>", "current repo (defaults to git origin)").option("--bundle-details", "fetch body/comments only for user-owned and claimable issues").option("--allow-partial", "return partial board results when later page/detail reads fail").action((o) => runBoardRead(o));
|
|
18922
|
+
board.command("slice-refresh", { hidden: true }).description("internal: refresh the cached SessionStart board slice (background worker)").option("--quiet", "silent (background worker)").action(async () => {
|
|
18923
|
+
await refreshBoardSliceCache({ loadConfig: () => loadConfigForRepo(), readBoard });
|
|
18924
|
+
});
|
|
18792
18925
|
board.command("claim <issues...>").description("assign Todo issues and move their Project v2 Status to In Progress (one or more refs)").option("--json", "machine-readable output").option("--repo <owner/repo>", "current repo for local issue numbers (defaults to git origin)").option("--for <login>", "assign to this login instead of @me \u2014 agent claims on behalf of the master").option("--allow-partial", "return success JSON if assignment succeeds but the status move fails").action(async (issueRefs, o) => {
|
|
18793
18926
|
if (issueRefs.length === 1) {
|
|
18794
18927
|
const issueRef = issueRefs[0];
|
|
@@ -18921,7 +19054,7 @@ function rawValues(flag) {
|
|
|
18921
19054
|
return out;
|
|
18922
19055
|
}
|
|
18923
19056
|
function printLine(value) {
|
|
18924
|
-
(0,
|
|
19057
|
+
(0, import_node_fs23.writeSync)(1, `${value}
|
|
18925
19058
|
`);
|
|
18926
19059
|
}
|
|
18927
19060
|
function stageKeepAlive() {
|
|
@@ -18938,8 +19071,8 @@ async function resolveStage() {
|
|
|
18938
19071
|
local,
|
|
18939
19072
|
shell: shellFor(),
|
|
18940
19073
|
registry: { deployModel: project2?.deployModel, portRange, error: read.ok ? void 0 : read.error },
|
|
18941
|
-
hasCompose: (0,
|
|
18942
|
-
hasEnvExample: (0,
|
|
19074
|
+
hasCompose: (0, import_node_fs23.existsSync)((0, import_node_path20.join)(process.cwd(), "docker-compose.yml")),
|
|
19075
|
+
hasEnvExample: (0, import_node_fs23.existsSync)((0, import_node_path20.join)(process.cwd(), ".env.example"))
|
|
18943
19076
|
});
|
|
18944
19077
|
}
|
|
18945
19078
|
async function fetchStageVaultEnvMerge() {
|
|
@@ -18991,9 +19124,9 @@ program2.command("port-range <repo>").description("assign (idempotently) + print
|
|
|
18991
19124
|
printLine(o.json ? JSON.stringify({ repo, portRange: [start2, end2], source: "meta" }) : `${repo}: stage.portRange [${start2}, ${end2}]`);
|
|
18992
19125
|
return;
|
|
18993
19126
|
}
|
|
18994
|
-
const path2 = (0,
|
|
19127
|
+
const path2 = (0, import_node_path20.join)(process.cwd(), "infra", "port-ranges.json");
|
|
18995
19128
|
const allocate = async (seed) => {
|
|
18996
|
-
const { stdout } = await execFileP2("node", [(0,
|
|
19129
|
+
const { stdout } = await execFileP2("node", [(0, import_node_path20.join)(process.cwd(), "infra", "port-ddb.mjs"), String(seed)], { timeout: 15e3 });
|
|
18997
19130
|
const parsed = JSON.parse(stdout);
|
|
18998
19131
|
if (!Array.isArray(parsed.range) || parsed.range.length !== 2) throw new Error("port-ddb: no range in output");
|
|
18999
19132
|
return parsed.range;
|
|
@@ -19388,7 +19521,7 @@ bootstrap.command("verify <repo>").description("audit whether an existing repo i
|
|
|
19388
19521
|
client: defaultGitHubClient(),
|
|
19389
19522
|
projectMeta: meta,
|
|
19390
19523
|
deployModel: typeof meta?.deployModel === "string" ? meta.deployModel : void 0,
|
|
19391
|
-
readLocalFile: (path2) => path2 === "projects.json" && apiProjects != null ? apiProjects : (0,
|
|
19524
|
+
readLocalFile: (path2) => path2 === "projects.json" && apiProjects != null ? apiProjects : (0, import_node_fs23.existsSync)(path2) ? (0, import_node_fs23.readFileSync)(path2, "utf8") : null,
|
|
19392
19525
|
// requiredGcpApis is stored as an array by a JSON write, but `project set --var KEY=VALUE` stores a raw
|
|
19393
19526
|
// comma-string — accept either so the seeded value verifies regardless of how it was written.
|
|
19394
19527
|
requiredGcpApis: (() => {
|
|
@@ -19432,12 +19565,12 @@ bootstrap.command("apply <repo>").description("idempotent seed apply from skills
|
|
|
19432
19565
|
return fail(`bootstrap apply: ${e.message}`);
|
|
19433
19566
|
}
|
|
19434
19567
|
const manifestPath = "skills/bootstrap/seeds/manifest.json";
|
|
19435
|
-
if (!(0,
|
|
19436
|
-
const manifest = loadBootstrapSeeds((0,
|
|
19568
|
+
if (!(0, import_node_fs23.existsSync)(manifestPath)) return fail(`bootstrap apply: ${manifestPath} not found; run from the MMI-Hub repo root`);
|
|
19569
|
+
const manifest = loadBootstrapSeeds((0, import_node_fs23.readFileSync)(manifestPath, "utf8"));
|
|
19437
19570
|
const baseBranch = o.class === "content" ? "main" : "development";
|
|
19438
19571
|
const slug = parsedRepo.slug;
|
|
19439
19572
|
const gh = async (args) => execFileP2("gh", args, { timeout: 2e4 });
|
|
19440
|
-
const readFile7 = (p) => (0,
|
|
19573
|
+
const readFile7 = (p) => (0, import_node_fs23.existsSync)(p) ? (0, import_node_fs23.readFileSync)(p, "utf8") : null;
|
|
19441
19574
|
const enc2 = (p) => p.split("/").map(encodeURIComponent).join("/");
|
|
19442
19575
|
const rawVars = {};
|
|
19443
19576
|
for (const value of rawValues("--var")) {
|
|
@@ -19688,16 +19821,16 @@ access.command("audit").description("audit collaborator roles + train-branch pus
|
|
|
19688
19821
|
if (o.class !== "deployable" && o.class !== "content") return failGraceful("access audit: --class must be deployable or content");
|
|
19689
19822
|
targets = [{ repo: o.repo, class: o.class }];
|
|
19690
19823
|
} else {
|
|
19691
|
-
const projectsJson = registryProjects ? JSON.stringify({ projects: registryProjects }) : (0,
|
|
19824
|
+
const projectsJson = registryProjects ? JSON.stringify({ projects: registryProjects }) : (0, import_node_fs23.existsSync)("projects.json") ? (0, import_node_fs23.readFileSync)("projects.json", "utf8") : null;
|
|
19692
19825
|
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>");
|
|
19693
|
-
const fanoutJson = (0,
|
|
19826
|
+
const fanoutJson = (0, import_node_fs23.existsSync)(".github/fanout-targets.json") ? (0, import_node_fs23.readFileSync)(".github/fanout-targets.json", "utf8") : null;
|
|
19694
19827
|
targets = loadAccessTargets(projectsJson, fanoutJson);
|
|
19695
19828
|
}
|
|
19696
19829
|
const derivedMatrix = registryProjects ? accessMatrixFromProjects(registryProjects) : {};
|
|
19697
|
-
const fileMatrix = (0,
|
|
19830
|
+
const fileMatrix = (0, import_node_fs23.existsSync)("access-matrix.json") ? loadAccessMatrix((0, import_node_fs23.readFileSync)("access-matrix.json", "utf8")) : {};
|
|
19698
19831
|
const matrix = mergeAccessMatrix(fileMatrix, derivedMatrix);
|
|
19699
19832
|
const derivedContracts = registryProjects ? dataAccessContractsFromProjects(registryProjects) : { consumers: {} };
|
|
19700
|
-
const fileContracts = (0,
|
|
19833
|
+
const fileContracts = (0, import_node_fs23.existsSync)("data-access-contracts.json") ? loadDataAccessContracts((0, import_node_fs23.readFileSync)("data-access-contracts.json", "utf8")) : { consumers: {} };
|
|
19701
19834
|
const dataAccess = mergeDataAccessContracts(fileContracts, derivedContracts);
|
|
19702
19835
|
const report = await auditOrgAccess(targets, deps, matrix, dataAccess);
|
|
19703
19836
|
console.log(o.json ? JSON.stringify(report, null, 2) : renderAccessReport(report));
|
|
@@ -19707,20 +19840,20 @@ access.command("capabilities").description("enumerate your effective vault reach
|
|
|
19707
19840
|
var isWin = process.platform === "win32";
|
|
19708
19841
|
var installedPluginsPath = (surface = detectSurface(process.env)) => {
|
|
19709
19842
|
const homeDir = surface === "codex" ? ".codex" : ".claude";
|
|
19710
|
-
return (0,
|
|
19843
|
+
return (0, import_node_path20.join)((0, import_node_os6.homedir)(), homeDir, "plugins", "installed_plugins.json");
|
|
19711
19844
|
};
|
|
19712
19845
|
function readInstalledPlugins() {
|
|
19713
19846
|
try {
|
|
19714
|
-
return JSON.parse((0,
|
|
19847
|
+
return JSON.parse((0, import_node_fs23.readFileSync)(installedPluginsPath(), "utf8"));
|
|
19715
19848
|
} catch {
|
|
19716
19849
|
return null;
|
|
19717
19850
|
}
|
|
19718
19851
|
}
|
|
19719
19852
|
function installedPluginSources() {
|
|
19720
19853
|
return ["claude", "codex"].map((surface) => {
|
|
19721
|
-
const recordPath = (0,
|
|
19854
|
+
const recordPath = (0, import_node_path20.join)((0, import_node_os6.homedir)(), `.${surface}`, "plugins", "installed_plugins.json");
|
|
19722
19855
|
try {
|
|
19723
|
-
return { surface, installed: JSON.parse((0,
|
|
19856
|
+
return { surface, installed: JSON.parse((0, import_node_fs23.readFileSync)(recordPath, "utf8")), recordPath };
|
|
19724
19857
|
} catch {
|
|
19725
19858
|
return { surface, installed: null, recordPath };
|
|
19726
19859
|
}
|
|
@@ -19728,7 +19861,7 @@ function installedPluginSources() {
|
|
|
19728
19861
|
}
|
|
19729
19862
|
function readClaudeSettings() {
|
|
19730
19863
|
try {
|
|
19731
|
-
return JSON.parse((0,
|
|
19864
|
+
return JSON.parse((0, import_node_fs23.readFileSync)((0, import_node_path20.join)(process.cwd(), ".claude", "settings.json"), "utf8"));
|
|
19732
19865
|
} catch {
|
|
19733
19866
|
return null;
|
|
19734
19867
|
}
|
|
@@ -19750,7 +19883,7 @@ function writeProjectInstallRecord(record) {
|
|
|
19750
19883
|
const list = file.plugins[MMI_PLUGIN_ID] ?? [];
|
|
19751
19884
|
list.push(record);
|
|
19752
19885
|
file.plugins[MMI_PLUGIN_ID] = list;
|
|
19753
|
-
(0,
|
|
19886
|
+
(0, import_node_fs23.writeFileSync)(installedPluginsPath(), `${JSON.stringify(file, null, 2)}
|
|
19754
19887
|
`, "utf8");
|
|
19755
19888
|
return true;
|
|
19756
19889
|
} catch {
|
|
@@ -19763,9 +19896,9 @@ function backupAndWriteInstalledPlugins(records, pluginId) {
|
|
|
19763
19896
|
if (!file) return false;
|
|
19764
19897
|
if (!file.plugins) file.plugins = {};
|
|
19765
19898
|
const path2 = installedPluginsPath();
|
|
19766
|
-
(0,
|
|
19899
|
+
(0, import_node_fs23.copyFileSync)(path2, `${path2}.bak`);
|
|
19767
19900
|
file.plugins[pluginId] = records;
|
|
19768
|
-
(0,
|
|
19901
|
+
(0, import_node_fs23.writeFileSync)(path2, `${JSON.stringify(file, null, 2)}
|
|
19769
19902
|
`, "utf8");
|
|
19770
19903
|
return true;
|
|
19771
19904
|
} catch {
|
|
@@ -19773,35 +19906,35 @@ function backupAndWriteInstalledPlugins(records, pluginId) {
|
|
|
19773
19906
|
}
|
|
19774
19907
|
}
|
|
19775
19908
|
function cursorPluginCacheRoot() {
|
|
19776
|
-
return (0,
|
|
19909
|
+
return (0, import_node_path20.join)((0, import_node_os6.homedir)(), ".cursor", "plugins", "cache", "mutmutco", "mmi");
|
|
19777
19910
|
}
|
|
19778
19911
|
function cursorPluginCachePinSnapshots() {
|
|
19779
19912
|
const root = cursorPluginCacheRoot();
|
|
19780
19913
|
try {
|
|
19781
|
-
return (0,
|
|
19782
|
-
const path2 = (0,
|
|
19783
|
-
const pluginJson = (0,
|
|
19784
|
-
const hooksJson = (0,
|
|
19785
|
-
const cliBundle = (0,
|
|
19914
|
+
return (0, import_node_fs23.readdirSync)(root, { withFileTypes: true }).filter((entry) => entry.isDirectory() && !entry.name.startsWith(".")).map((entry) => {
|
|
19915
|
+
const path2 = (0, import_node_path20.join)(root, entry.name);
|
|
19916
|
+
const pluginJson = (0, import_node_path20.join)(path2, ".cursor-plugin", "plugin.json");
|
|
19917
|
+
const hooksJson = (0, import_node_path20.join)(path2, "hooks", "hooks.json");
|
|
19918
|
+
const cliBundle = (0, import_node_path20.join)(path2, "cli", "dist", "index.cjs");
|
|
19786
19919
|
let version;
|
|
19787
19920
|
try {
|
|
19788
|
-
const raw = JSON.parse((0,
|
|
19921
|
+
const raw = JSON.parse((0, import_node_fs23.readFileSync)(pluginJson, "utf8"));
|
|
19789
19922
|
version = typeof raw.version === "string" ? raw.version : void 0;
|
|
19790
19923
|
} catch {
|
|
19791
19924
|
version = void 0;
|
|
19792
19925
|
}
|
|
19793
19926
|
let isEmpty = true;
|
|
19794
19927
|
try {
|
|
19795
|
-
isEmpty = (0,
|
|
19928
|
+
isEmpty = (0, import_node_fs23.readdirSync)(path2).length === 0;
|
|
19796
19929
|
} catch {
|
|
19797
19930
|
isEmpty = true;
|
|
19798
19931
|
}
|
|
19799
19932
|
return {
|
|
19800
19933
|
name: entry.name,
|
|
19801
19934
|
path: path2,
|
|
19802
|
-
hasPluginJson: (0,
|
|
19803
|
-
hasHooksJson: (0,
|
|
19804
|
-
hasCliBundle: (0,
|
|
19935
|
+
hasPluginJson: (0, import_node_fs23.existsSync)(pluginJson),
|
|
19936
|
+
hasHooksJson: (0, import_node_fs23.existsSync)(hooksJson),
|
|
19937
|
+
hasCliBundle: (0, import_node_fs23.existsSync)(cliBundle),
|
|
19805
19938
|
isEmpty,
|
|
19806
19939
|
version
|
|
19807
19940
|
};
|
|
@@ -19811,19 +19944,19 @@ function cursorPluginCachePinSnapshots() {
|
|
|
19811
19944
|
}
|
|
19812
19945
|
}
|
|
19813
19946
|
function hubCheckoutForCursorSeed() {
|
|
19814
|
-
const manifest = (0,
|
|
19815
|
-
return (0,
|
|
19947
|
+
const manifest = (0, import_node_path20.join)(process.cwd(), "plugins", "mmi", ".cursor-plugin", "plugin.json");
|
|
19948
|
+
return (0, import_node_fs23.existsSync)(manifest) ? process.cwd() : void 0;
|
|
19816
19949
|
}
|
|
19817
19950
|
function mmiPluginCacheRootSnapshots() {
|
|
19818
19951
|
const roots = [
|
|
19819
|
-
{ surface: "claude", root: (0,
|
|
19820
|
-
{ surface: "codex", root: (0,
|
|
19952
|
+
{ surface: "claude", root: (0, import_node_path20.join)((0, import_node_os6.homedir)(), ".claude", "plugins", "cache", "mutmutco", "mmi") },
|
|
19953
|
+
{ surface: "codex", root: (0, import_node_path20.join)((0, import_node_os6.homedir)(), ".codex", "plugins", "cache", "mutmutco", "mmi") }
|
|
19821
19954
|
];
|
|
19822
19955
|
return roots.flatMap(({ surface, root }) => {
|
|
19823
19956
|
try {
|
|
19824
|
-
const entries = (0,
|
|
19957
|
+
const entries = (0, import_node_fs23.readdirSync)(root, { withFileTypes: true }).map((entry) => ({
|
|
19825
19958
|
name: entry.name,
|
|
19826
|
-
path: (0,
|
|
19959
|
+
path: (0, import_node_path20.join)(root, entry.name),
|
|
19827
19960
|
isDirectory: entry.isDirectory()
|
|
19828
19961
|
}));
|
|
19829
19962
|
return [{ surface, root, entries }];
|
|
@@ -19834,7 +19967,7 @@ function mmiPluginCacheRootSnapshots() {
|
|
|
19834
19967
|
}
|
|
19835
19968
|
function hasNestedMmiChild(versionDir) {
|
|
19836
19969
|
try {
|
|
19837
|
-
return (0,
|
|
19970
|
+
return (0, import_node_fs23.statSync)((0, import_node_path20.join)(versionDir, "mmi")).isDirectory();
|
|
19838
19971
|
} catch {
|
|
19839
19972
|
return false;
|
|
19840
19973
|
}
|
|
@@ -19845,10 +19978,10 @@ function nestedPluginTreeSnapshot() {
|
|
|
19845
19978
|
);
|
|
19846
19979
|
}
|
|
19847
19980
|
function uniqueQuarantineTarget(path2) {
|
|
19848
|
-
if (!(0,
|
|
19981
|
+
if (!(0, import_node_fs23.existsSync)(path2)) return path2;
|
|
19849
19982
|
for (let i = 1; i < 100; i += 1) {
|
|
19850
19983
|
const candidate = `${path2}-${i}`;
|
|
19851
|
-
if (!(0,
|
|
19984
|
+
if (!(0, import_node_fs23.existsSync)(candidate)) return candidate;
|
|
19852
19985
|
}
|
|
19853
19986
|
return `${path2}-${Date.now()}`;
|
|
19854
19987
|
}
|
|
@@ -19857,10 +19990,10 @@ function quarantinePluginCacheDirs(plan2) {
|
|
|
19857
19990
|
const failed = [];
|
|
19858
19991
|
for (const move of plan2) {
|
|
19859
19992
|
try {
|
|
19860
|
-
if (!(0,
|
|
19993
|
+
if (!(0, import_node_fs23.existsSync)(move.from)) continue;
|
|
19861
19994
|
const target = uniqueQuarantineTarget(move.to);
|
|
19862
|
-
(0,
|
|
19863
|
-
(0,
|
|
19995
|
+
(0, import_node_fs23.mkdirSync)((0, import_node_path20.dirname)(target), { recursive: true });
|
|
19996
|
+
(0, import_node_fs23.renameSync)(move.from, target);
|
|
19864
19997
|
moved += 1;
|
|
19865
19998
|
} catch {
|
|
19866
19999
|
failed.push(move);
|
|
@@ -19879,23 +20012,23 @@ async function robocopyMirrorEmpty(emptyDir, target) {
|
|
|
19879
20012
|
}
|
|
19880
20013
|
async function clearNestedPluginTreeDir(targetPath) {
|
|
19881
20014
|
try {
|
|
19882
|
-
if (!(0,
|
|
20015
|
+
if (!(0, import_node_fs23.existsSync)(targetPath)) return true;
|
|
19883
20016
|
if (isWin) {
|
|
19884
|
-
const emptyDir = (0,
|
|
19885
|
-
(0,
|
|
20017
|
+
const emptyDir = (0, import_node_path20.join)((0, import_node_os6.tmpdir)(), `mmi-empty-${Date.now()}`);
|
|
20018
|
+
(0, import_node_fs23.mkdirSync)(emptyDir, { recursive: true });
|
|
19886
20019
|
try {
|
|
19887
20020
|
await robocopyMirrorEmpty(emptyDir, targetPath);
|
|
19888
|
-
(0,
|
|
20021
|
+
(0, import_node_fs23.rmSync)(targetPath, { recursive: true, force: true });
|
|
19889
20022
|
} finally {
|
|
19890
20023
|
try {
|
|
19891
|
-
(0,
|
|
20024
|
+
(0, import_node_fs23.rmSync)(emptyDir, { recursive: true, force: true });
|
|
19892
20025
|
} catch {
|
|
19893
20026
|
}
|
|
19894
20027
|
}
|
|
19895
|
-
return !(0,
|
|
20028
|
+
return !(0, import_node_fs23.existsSync)(targetPath);
|
|
19896
20029
|
}
|
|
19897
|
-
(0,
|
|
19898
|
-
return !(0,
|
|
20030
|
+
(0, import_node_fs23.rmSync)(targetPath, { recursive: true, force: true });
|
|
20031
|
+
return !(0, import_node_fs23.existsSync)(targetPath);
|
|
19899
20032
|
} catch {
|
|
19900
20033
|
return false;
|
|
19901
20034
|
}
|
|
@@ -19908,11 +20041,11 @@ async function applyNestedPluginTreeCleanup(paths, log) {
|
|
|
19908
20041
|
}
|
|
19909
20042
|
return true;
|
|
19910
20043
|
}
|
|
19911
|
-
var gitignorePath = () => (0,
|
|
20044
|
+
var gitignorePath = () => (0, import_node_path20.join)(process.cwd(), ".gitignore");
|
|
19912
20045
|
function readTextFile(path2) {
|
|
19913
20046
|
try {
|
|
19914
|
-
if (!(0,
|
|
19915
|
-
return (0,
|
|
20047
|
+
if (!(0, import_node_fs23.existsSync)(path2)) return null;
|
|
20048
|
+
return (0, import_node_fs23.readFileSync)(path2, "utf8");
|
|
19916
20049
|
} catch {
|
|
19917
20050
|
return null;
|
|
19918
20051
|
}
|
|
@@ -19921,9 +20054,9 @@ function playwrightMcpConfigSnapshots() {
|
|
|
19921
20054
|
const cwd = process.cwd();
|
|
19922
20055
|
const home = (0, import_node_os6.homedir)();
|
|
19923
20056
|
const candidates = [
|
|
19924
|
-
(0,
|
|
19925
|
-
(0,
|
|
19926
|
-
(0,
|
|
20057
|
+
(0, import_node_path20.join)(cwd, ".cursor", "mcp.json"),
|
|
20058
|
+
(0, import_node_path20.join)(home, ".cursor", "mcp.json"),
|
|
20059
|
+
(0, import_node_path20.join)(home, ".codex", "config.toml")
|
|
19927
20060
|
];
|
|
19928
20061
|
const out = [];
|
|
19929
20062
|
for (const path2 of candidates) {
|
|
@@ -19936,7 +20069,7 @@ function strayBrowserArtifactPaths() {
|
|
|
19936
20069
|
const cwd = process.cwd();
|
|
19937
20070
|
return STRAY_BROWSER_ARTIFACT_DIRS.filter((rel) => {
|
|
19938
20071
|
try {
|
|
19939
|
-
return (0,
|
|
20072
|
+
return (0, import_node_fs23.existsSync)((0, import_node_path20.join)(cwd, rel));
|
|
19940
20073
|
} catch {
|
|
19941
20074
|
return false;
|
|
19942
20075
|
}
|
|
@@ -19944,14 +20077,14 @@ function strayBrowserArtifactPaths() {
|
|
|
19944
20077
|
}
|
|
19945
20078
|
function readGitignore() {
|
|
19946
20079
|
try {
|
|
19947
|
-
return (0,
|
|
20080
|
+
return (0, import_node_fs23.readFileSync)(gitignorePath(), "utf8");
|
|
19948
20081
|
} catch {
|
|
19949
20082
|
return null;
|
|
19950
20083
|
}
|
|
19951
20084
|
}
|
|
19952
20085
|
function writeGitignore(content) {
|
|
19953
20086
|
try {
|
|
19954
|
-
(0,
|
|
20087
|
+
(0, import_node_fs23.writeFileSync)(gitignorePath(), content, "utf8");
|
|
19955
20088
|
return true;
|
|
19956
20089
|
} catch {
|
|
19957
20090
|
return false;
|
|
@@ -19990,7 +20123,7 @@ async function runDoctor(opts, io = consoleIo) {
|
|
|
19990
20123
|
let onPath = pathProbe;
|
|
19991
20124
|
if (!onPath) {
|
|
19992
20125
|
const root = process.env.CLAUDE_PLUGIN_ROOT;
|
|
19993
|
-
if (root && (0,
|
|
20126
|
+
if (root && (0, import_node_fs23.existsSync)(`${root}/bin/mmi-cli${isWin ? ".cmd" : ""}`)) onPath = true;
|
|
19994
20127
|
}
|
|
19995
20128
|
checks.push({ ok: onPath, label: "mmi-cli on PATH", fix: "auto-provisioned at session start \u2014 reopen the session, or install the MMI plugin" });
|
|
19996
20129
|
const surface = detectSurface(process.env);
|
|
@@ -20023,9 +20156,10 @@ async function runDoctor(opts, io = consoleIo) {
|
|
|
20023
20156
|
}
|
|
20024
20157
|
checks.push({ ok: cloneOk, label: "plugin git clone (SSH\u2192HTTPS rewrite)", fix: CLONE_FIX });
|
|
20025
20158
|
const installed = readInstalledPlugins();
|
|
20159
|
+
const claudeSettings = readClaudeSettings();
|
|
20026
20160
|
let pluginCheck = buildPluginInstallRecordCheck({
|
|
20027
20161
|
isOrgRepo: Boolean(cfg.sagaApiUrl),
|
|
20028
|
-
settings:
|
|
20162
|
+
settings: claudeSettings,
|
|
20029
20163
|
installed,
|
|
20030
20164
|
projectPath: process.cwd(),
|
|
20031
20165
|
mirrorFrom: existingMirrorRecord(installed),
|
|
@@ -20038,6 +20172,7 @@ async function runDoctor(opts, io = consoleIo) {
|
|
|
20038
20172
|
}
|
|
20039
20173
|
}
|
|
20040
20174
|
checks.push(pluginCheck);
|
|
20175
|
+
checks.push(buildSettingsPluginDriftCheck({ isOrgRepo: Boolean(cfg.sagaApiUrl), settings: claudeSettings }));
|
|
20041
20176
|
let legacyPluginCheck = buildLegacyPluginInstallCheck({
|
|
20042
20177
|
isOrgRepo: Boolean(cfg.sagaApiUrl),
|
|
20043
20178
|
sources: installedPluginSources(),
|
|
@@ -20192,7 +20327,7 @@ async function runDoctor(opts, io = consoleIo) {
|
|
|
20192
20327
|
isOrgRepo: Boolean(cfg.sagaApiUrl),
|
|
20193
20328
|
surface,
|
|
20194
20329
|
cacheRoot: cursorCacheRoot,
|
|
20195
|
-
cacheRootExists: (0,
|
|
20330
|
+
cacheRootExists: (0, import_node_fs23.existsSync)(cursorCacheRoot),
|
|
20196
20331
|
pins: cursorPins,
|
|
20197
20332
|
hubCheckout: hubCheckoutForCursorSeed(),
|
|
20198
20333
|
releasedVersion
|
|
@@ -20203,7 +20338,7 @@ async function runDoctor(opts, io = consoleIo) {
|
|
|
20203
20338
|
releasedVersion,
|
|
20204
20339
|
hubCheckout: hubCheckoutForCursorSeed(),
|
|
20205
20340
|
execFileP: execFileP2,
|
|
20206
|
-
mkdtemp: (prefix) => (0, import_promises7.mkdtemp)((0,
|
|
20341
|
+
mkdtemp: (prefix) => (0, import_promises7.mkdtemp)((0, import_node_path20.join)((0, import_node_os6.tmpdir)(), prefix)),
|
|
20207
20342
|
log: (m) => io.err(m)
|
|
20208
20343
|
});
|
|
20209
20344
|
if (seeded) {
|
|
@@ -20212,7 +20347,7 @@ async function runDoctor(opts, io = consoleIo) {
|
|
|
20212
20347
|
isOrgRepo: Boolean(cfg.sagaApiUrl),
|
|
20213
20348
|
surface,
|
|
20214
20349
|
cacheRoot: cursorCacheRoot,
|
|
20215
|
-
cacheRootExists: (0,
|
|
20350
|
+
cacheRootExists: (0, import_node_fs23.existsSync)(cursorCacheRoot),
|
|
20216
20351
|
pins: cursorPins,
|
|
20217
20352
|
hubCheckout: hubCheckoutForCursorSeed(),
|
|
20218
20353
|
releasedVersion
|
|
@@ -20417,7 +20552,15 @@ program2.command("session-start").description("run the SessionStart verbs (rules
|
|
|
20417
20552
|
northstarInjected = await runNorthstarContext(io, {
|
|
20418
20553
|
loadPlans: () => scopedPlanList(planDeps),
|
|
20419
20554
|
readLocal: (slug) => planDeps.readLocal(slug),
|
|
20420
|
-
|
|
20555
|
+
// #1812: thread the saga HEAD's North Star anchor (its NEXT slug) into the relevance gate so
|
|
20556
|
+
// the plan the agent is actively on is force-injected even on a generic branch with no token
|
|
20557
|
+
// overlap. fetchSagaHead errors are swallowed via a silent io — a missing/failed HEAD just
|
|
20558
|
+
// falls back to token-overlap scoring, never noises or blocks the banner.
|
|
20559
|
+
gatherSignals: () => gatherRelevanceSignals({
|
|
20560
|
+
anchorSlug: () => fetchSagaHead({ log: () => {
|
|
20561
|
+
}, err: () => {
|
|
20562
|
+
} }).then((h) => h?.anchor?.slug ?? void 0)
|
|
20563
|
+
})
|
|
20421
20564
|
});
|
|
20422
20565
|
},
|
|
20423
20566
|
sagaHealth: (io) => runSagaHealth({ banner: true, quiet: true }, io),
|
|
@@ -20433,12 +20576,16 @@ program2.command("session-start").description("run the SessionStart verbs (rules
|
|
|
20433
20576
|
},
|
|
20434
20577
|
boardSlice: (io) => runBoardSlice(io, {
|
|
20435
20578
|
loadConfig: () => loadConfigForRepo(),
|
|
20436
|
-
readBoard
|
|
20579
|
+
readBoard,
|
|
20580
|
+
// #1813: warm the slice cache out-of-band (detached, like docs sync) so the ~20s live read
|
|
20581
|
+
// never costs banner time and next session's glance renders instantly within budget.
|
|
20582
|
+
scheduleRefresh: () => spawnDetachedSelf(["board", "slice-refresh", "--quiet"], { spawn: import_node_child_process12.spawn, execPath: process.execPath, scriptPath: process.argv[1] })
|
|
20437
20583
|
}),
|
|
20438
20584
|
doctor: (io) => runDoctor({ banner: true }, io)
|
|
20439
20585
|
});
|
|
20440
20586
|
await runSessionStart(parallel, sequential, consoleIo);
|
|
20441
20587
|
consoleIo.log(northstarPointer(northstarInjected));
|
|
20588
|
+
consoleIo.log(kbPointer());
|
|
20442
20589
|
for (const line of planStoreLines(process.cwd())) consoleIo.log(line);
|
|
20443
20590
|
for (const line of scratchGcLines(process.cwd())) consoleIo.log(line);
|
|
20444
20591
|
const worktreeBanner = worktreeAutoProvisionBanner(process.cwd());
|