@mutmutco/cli 2.40.3 → 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 +577 -244
- 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
|
});
|
|
@@ -5678,7 +5680,16 @@ async function secretsCapabilities(deps, opts) {
|
|
|
5678
5680
|
return;
|
|
5679
5681
|
}
|
|
5680
5682
|
if (!res.ok) {
|
|
5681
|
-
|
|
5683
|
+
const upgrade = await upgradeMessage(res);
|
|
5684
|
+
if (upgrade) {
|
|
5685
|
+
deps.err(upgrade);
|
|
5686
|
+
} else if (res.status === 404) {
|
|
5687
|
+
deps.err(
|
|
5688
|
+
"access capabilities: the Hub API did not recognize /secrets/capabilities (HTTP 404). This endpoint ships in the Hub, so a 404 means the deployed Hub predates this command and should answer once a Hub release carrying this command is deployed \u2014 it is not an authorization or missing-credential error."
|
|
5689
|
+
);
|
|
5690
|
+
} else {
|
|
5691
|
+
deps.err(`access capabilities failed: HTTP ${res.status}${await readErr(res)}`);
|
|
5692
|
+
}
|
|
5682
5693
|
return;
|
|
5683
5694
|
}
|
|
5684
5695
|
const report = await res.json();
|
|
@@ -6152,7 +6163,6 @@ function buildIngestPayload(args) {
|
|
|
6152
6163
|
}
|
|
6153
6164
|
|
|
6154
6165
|
// src/honcho-client.ts
|
|
6155
|
-
var HONCHO_ASSISTANT_PEER = "assistant";
|
|
6156
6166
|
function parseHonchoQueueStatus(json) {
|
|
6157
6167
|
const o = json && typeof json === "object" ? json : {};
|
|
6158
6168
|
const pendingRaw = o.pending_work_units ?? o.depth ?? o.queue_depth ?? o.pending;
|
|
@@ -6205,9 +6215,9 @@ async function ingestPost(cfg, body, fetchImpl = fetch, timeoutMs = 1e4) {
|
|
|
6205
6215
|
const session = String(body.session ?? "");
|
|
6206
6216
|
const meta = body.meta && typeof body.meta === "object" ? body.meta : {};
|
|
6207
6217
|
const rawMessages = Array.isArray(body.messages) ? body.messages : [];
|
|
6208
|
-
const messages = rawMessages.filter((m) => m && typeof m.content === "string" && m.content.trim().length > 0).map((m) => ({
|
|
6218
|
+
const messages = rawMessages.filter((m) => m && m.role === "user" && typeof m.content === "string" && m.content.trim().length > 0).map((m) => ({
|
|
6209
6219
|
content: m.content,
|
|
6210
|
-
peer_id:
|
|
6220
|
+
peer_id: peer,
|
|
6211
6221
|
metadata: { role: m.role, ...meta }
|
|
6212
6222
|
}));
|
|
6213
6223
|
if (!peer || !session || !messages.length) return { ok: true, threw: false, status: 204 };
|
|
@@ -6216,7 +6226,8 @@ async function ingestPost(cfg, body, fetchImpl = fetch, timeoutMs = 1e4) {
|
|
|
6216
6226
|
if (res.status === 404) {
|
|
6217
6227
|
const ensured = await request(cfg, fetchImpl, "POST", honchoRoutes.sessions(cfg.workspace), {
|
|
6218
6228
|
id: session,
|
|
6219
|
-
peers: { [peer]: {}
|
|
6229
|
+
peers: { [peer]: {} }
|
|
6230
|
+
// only the human peer — assistant turns are no longer ingested (#1775)
|
|
6220
6231
|
}, timeoutMs);
|
|
6221
6232
|
if (!ensured.ok && ensured.status !== 409) {
|
|
6222
6233
|
if (ensured.status >= 500) return { ok: false, threw: true, message: `honcho session-ensure ${ensured.status}` };
|
|
@@ -6366,7 +6377,7 @@ function spawnHonchoFlush() {
|
|
|
6366
6377
|
} catch {
|
|
6367
6378
|
}
|
|
6368
6379
|
}
|
|
6369
|
-
var DEFAULT_INGEST_MIN_INTERVAL_SEC =
|
|
6380
|
+
var DEFAULT_INGEST_MIN_INTERVAL_SEC = 300;
|
|
6370
6381
|
function honchoThrottlePath(key) {
|
|
6371
6382
|
const safe = (s) => s.replace(/[^A-Za-z0-9._-]/g, "_");
|
|
6372
6383
|
return `.mmi/head-ts/honcho/${safe(key.project)}/${safe(key.branch)}`;
|
|
@@ -6398,7 +6409,12 @@ async function resolveSummaryText(summary, messageFile) {
|
|
|
6398
6409
|
}
|
|
6399
6410
|
return "";
|
|
6400
6411
|
}
|
|
6412
|
+
function isAutomatedSession(env = process.env) {
|
|
6413
|
+
const v = env.GITHUB_ACTIONS;
|
|
6414
|
+
return !!v && v !== "false" && v !== "0";
|
|
6415
|
+
}
|
|
6401
6416
|
async function runHonchoIngest(opts) {
|
|
6417
|
+
if (isAutomatedSession()) return;
|
|
6402
6418
|
const cfg = await loadConfig();
|
|
6403
6419
|
const peer = honchoPeerId(await honchoLogin(cfg), cfg);
|
|
6404
6420
|
if (!peer) return;
|
|
@@ -7091,6 +7107,9 @@ function northstarPointer(injected = false) {
|
|
|
7091
7107
|
}
|
|
7092
7108
|
return "North Stars: run `mmi-cli northstar relevant` to load plans relevant to your task (`northstar list` for all).";
|
|
7093
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
|
+
}
|
|
7094
7113
|
|
|
7095
7114
|
// src/board.ts
|
|
7096
7115
|
var import_node_child_process7 = require("node:child_process");
|
|
@@ -8009,9 +8028,47 @@ function ghError(e) {
|
|
|
8009
8028
|
return (err.stderr || err.message || String(e)).trim();
|
|
8010
8029
|
}
|
|
8011
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
|
+
|
|
8012
8066
|
// src/board-slice.ts
|
|
8013
8067
|
var SESSION_START_BOARD_TIMEOUT_MS = 3e3;
|
|
8068
|
+
var BOARD_SLICE_REFRESH_TIMEOUT_MS = 3e4;
|
|
8014
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.";
|
|
8015
8072
|
var BOARD_SLICE_MAX_LINES = 5;
|
|
8016
8073
|
var ACTIVE_STATUS_ORDER = {
|
|
8017
8074
|
"In Progress": 0,
|
|
@@ -8072,12 +8129,25 @@ async function withTimeout(promise, ms) {
|
|
|
8072
8129
|
}
|
|
8073
8130
|
}
|
|
8074
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
|
+
}
|
|
8075
8147
|
const budget = deps.timeoutMs ?? SESSION_START_BOARD_TIMEOUT_MS;
|
|
8076
8148
|
const controller = new AbortController();
|
|
8077
8149
|
const deadline = setTimeout(() => controller.abort(), budget);
|
|
8078
8150
|
try {
|
|
8079
|
-
const cfg = await deps.loadConfig();
|
|
8080
|
-
if (!boardMetaConfigured(cfg)) return;
|
|
8081
8151
|
const client = deps.client ?? createGitHubClient({ defaultTimeoutMs: budget, signal: controller.signal });
|
|
8082
8152
|
const report = await withTimeout(
|
|
8083
8153
|
deps.readBoard({ config: cfg, allowPartial: true }, { client }),
|
|
@@ -8085,7 +8155,26 @@ async function runBoardSlice(io, deps, opts) {
|
|
|
8085
8155
|
);
|
|
8086
8156
|
if (opts?.expectedLogin && report.viewer && opts.expectedLogin !== report.viewer) return;
|
|
8087
8157
|
const block = renderBoardSlice(report);
|
|
8088
|
-
|
|
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 });
|
|
8089
8178
|
} catch {
|
|
8090
8179
|
} finally {
|
|
8091
8180
|
clearTimeout(deadline);
|
|
@@ -8093,8 +8182,8 @@ async function runBoardSlice(io, deps, opts) {
|
|
|
8093
8182
|
}
|
|
8094
8183
|
|
|
8095
8184
|
// src/worktree.ts
|
|
8096
|
-
var
|
|
8097
|
-
var
|
|
8185
|
+
var import_node_fs15 = require("node:fs");
|
|
8186
|
+
var import_node_path13 = require("node:path");
|
|
8098
8187
|
var LOCAL_ONLY_FILES = [".claude/settings.local.json"];
|
|
8099
8188
|
var PKG = "package.json";
|
|
8100
8189
|
var LOCKFILE = "package-lock.json";
|
|
@@ -8102,21 +8191,21 @@ var NODE_MODULES = "node_modules";
|
|
|
8102
8191
|
var realFsProbe = {
|
|
8103
8192
|
isDir: (p) => {
|
|
8104
8193
|
try {
|
|
8105
|
-
return (0,
|
|
8194
|
+
return (0, import_node_fs15.statSync)(p).isDirectory();
|
|
8106
8195
|
} catch {
|
|
8107
8196
|
return false;
|
|
8108
8197
|
}
|
|
8109
8198
|
},
|
|
8110
8199
|
isFile: (p) => {
|
|
8111
8200
|
try {
|
|
8112
|
-
return (0,
|
|
8201
|
+
return (0, import_node_fs15.statSync)(p).isFile();
|
|
8113
8202
|
} catch {
|
|
8114
8203
|
return false;
|
|
8115
8204
|
}
|
|
8116
8205
|
},
|
|
8117
8206
|
listDirs: (p) => {
|
|
8118
8207
|
try {
|
|
8119
|
-
return (0,
|
|
8208
|
+
return (0, import_node_fs15.readdirSync)(p, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
|
|
8120
8209
|
} catch {
|
|
8121
8210
|
return [];
|
|
8122
8211
|
}
|
|
@@ -8124,12 +8213,12 @@ var realFsProbe = {
|
|
|
8124
8213
|
};
|
|
8125
8214
|
function scanInstallDirs(root, fs2 = realFsProbe) {
|
|
8126
8215
|
const factsFor = (dir) => {
|
|
8127
|
-
const abs = dir ? (0,
|
|
8216
|
+
const abs = dir ? (0, import_node_path13.join)(root, dir) : root;
|
|
8128
8217
|
return {
|
|
8129
8218
|
dir,
|
|
8130
|
-
hasPackageJson: fs2.isFile((0,
|
|
8131
|
-
hasLockfile: fs2.isFile((0,
|
|
8132
|
-
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))
|
|
8133
8222
|
};
|
|
8134
8223
|
};
|
|
8135
8224
|
const children = fs2.listDirs(root).filter((name) => name !== NODE_MODULES && name !== ".git");
|
|
@@ -8139,7 +8228,7 @@ function npmInstallTargets(dirs) {
|
|
|
8139
8228
|
return dirs.filter((d) => d.hasPackageJson && !d.hasNodeModules).map((d) => ({ dir: d.dir, command: d.hasLockfile ? "npm ci" : "npm install" }));
|
|
8140
8229
|
}
|
|
8141
8230
|
function isLinkedWorktree(root, fs2 = realFsProbe) {
|
|
8142
|
-
return fs2.isFile((0,
|
|
8231
|
+
return fs2.isFile((0, import_node_path13.join)(root, ".git"));
|
|
8143
8232
|
}
|
|
8144
8233
|
function worktreeAutoProvisionBanner(root, fs2 = realFsProbe) {
|
|
8145
8234
|
if (!isLinkedWorktree(root, fs2)) return null;
|
|
@@ -8149,8 +8238,8 @@ function worktreeAutoProvisionBanner(root, fs2 = realFsProbe) {
|
|
|
8149
8238
|
return `[worktree] provisioning tooling in the background (deps in ${where} + local config) \u2014 \`mmi-cli worktree setup\` to redo`;
|
|
8150
8239
|
}
|
|
8151
8240
|
function defaultCopyFile(from, to) {
|
|
8152
|
-
(0,
|
|
8153
|
-
(0,
|
|
8241
|
+
(0, import_node_fs15.mkdirSync)((0, import_node_path13.dirname)(to), { recursive: true });
|
|
8242
|
+
(0, import_node_fs15.copyFileSync)(from, to);
|
|
8154
8243
|
}
|
|
8155
8244
|
async function provisionWorktree(worktreeRoot, deps) {
|
|
8156
8245
|
const fs2 = deps.fs ?? realFsProbe;
|
|
@@ -8162,7 +8251,7 @@ async function provisionWorktree(worktreeRoot, deps) {
|
|
|
8162
8251
|
const skippedInstall = allDirs.filter((d) => d.hasPackageJson && d.hasNodeModules).map((d) => d.dir);
|
|
8163
8252
|
const installed = [];
|
|
8164
8253
|
for (const target of targets) {
|
|
8165
|
-
const cwd = target.dir ? (0,
|
|
8254
|
+
const cwd = target.dir ? (0, import_node_path13.join)(worktreeRoot, target.dir) : worktreeRoot;
|
|
8166
8255
|
log(`installing deps: ${target.command} in ${target.dir || "."}`);
|
|
8167
8256
|
await deps.runInstall(target.command, cwd);
|
|
8168
8257
|
installed.push(target);
|
|
@@ -8171,7 +8260,7 @@ async function provisionWorktree(worktreeRoot, deps) {
|
|
|
8171
8260
|
const copySkipped = [];
|
|
8172
8261
|
const primary = await deps.primaryCheckout();
|
|
8173
8262
|
for (const rel of LOCAL_ONLY_FILES) {
|
|
8174
|
-
const dest = (0,
|
|
8263
|
+
const dest = (0, import_node_path13.join)(worktreeRoot, rel);
|
|
8175
8264
|
if (fs2.isFile(dest)) {
|
|
8176
8265
|
copySkipped.push({ file: rel, reason: "already-present" });
|
|
8177
8266
|
continue;
|
|
@@ -8180,11 +8269,11 @@ async function provisionWorktree(worktreeRoot, deps) {
|
|
|
8180
8269
|
copySkipped.push({ file: rel, reason: "no-primary" });
|
|
8181
8270
|
continue;
|
|
8182
8271
|
}
|
|
8183
|
-
if (!fs2.isFile((0,
|
|
8272
|
+
if (!fs2.isFile((0, import_node_path13.join)(primary, rel))) {
|
|
8184
8273
|
copySkipped.push({ file: rel, reason: "absent-in-primary" });
|
|
8185
8274
|
continue;
|
|
8186
8275
|
}
|
|
8187
|
-
copyFile((0,
|
|
8276
|
+
copyFile((0, import_node_path13.join)(primary, rel), dest);
|
|
8188
8277
|
copied.push(rel);
|
|
8189
8278
|
log(`copied local config: ${rel}`);
|
|
8190
8279
|
}
|
|
@@ -8192,7 +8281,7 @@ async function provisionWorktree(worktreeRoot, deps) {
|
|
|
8192
8281
|
}
|
|
8193
8282
|
function defaultWorktreePath(repoRoot, branch) {
|
|
8194
8283
|
const safe = branch.replace(/[/\\]+/g, "-");
|
|
8195
|
-
return (0,
|
|
8284
|
+
return (0, import_node_path13.join)((0, import_node_path13.dirname)(repoRoot), "mmi-worktrees", safe);
|
|
8196
8285
|
}
|
|
8197
8286
|
|
|
8198
8287
|
// src/frontmatter.ts
|
|
@@ -8390,7 +8479,7 @@ async function runNorthstarContext(io, deps) {
|
|
|
8390
8479
|
}
|
|
8391
8480
|
|
|
8392
8481
|
// src/index.ts
|
|
8393
|
-
var
|
|
8482
|
+
var import_node_path20 = require("node:path");
|
|
8394
8483
|
|
|
8395
8484
|
// src/merge-ci-policy.ts
|
|
8396
8485
|
function resolveMergeCiPolicy(input) {
|
|
@@ -8691,6 +8780,11 @@ function diffManagedGitignoreBlock(current) {
|
|
|
8691
8780
|
seeded: false
|
|
8692
8781
|
};
|
|
8693
8782
|
}
|
|
8783
|
+
function planManagedGitignore(current) {
|
|
8784
|
+
const { content, changed } = upsertManagedGitignoreBlock(current);
|
|
8785
|
+
const { added, removed } = diffManagedGitignoreBlock(current);
|
|
8786
|
+
return { changed, content, added, removed };
|
|
8787
|
+
}
|
|
8694
8788
|
|
|
8695
8789
|
// src/project-model.ts
|
|
8696
8790
|
var PROJECT_TYPES = ["web-app", "hub-service", "content", "desktop-game", "non-deployable", "cli-tool", "worker"];
|
|
@@ -9589,7 +9683,7 @@ var import_node_os6 = require("node:os");
|
|
|
9589
9683
|
// src/gh-create.ts
|
|
9590
9684
|
var import_promises5 = require("node:fs/promises");
|
|
9591
9685
|
var import_node_os4 = require("node:os");
|
|
9592
|
-
var
|
|
9686
|
+
var import_node_path14 = require("node:path");
|
|
9593
9687
|
var import_node_crypto6 = require("node:crypto");
|
|
9594
9688
|
var ISSUE_TYPES = ["bug", "feature", "task"];
|
|
9595
9689
|
var GH_MUTATION_TIMEOUT_MS = 12e4;
|
|
@@ -9630,7 +9724,7 @@ async function bodyArgsViaFile(args, deps = {}) {
|
|
|
9630
9724
|
} };
|
|
9631
9725
|
const write = deps.write ?? import_promises5.writeFile;
|
|
9632
9726
|
const remove = deps.remove ?? import_promises5.unlink;
|
|
9633
|
-
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`);
|
|
9634
9728
|
await write(file, args[i + 1], "utf8");
|
|
9635
9729
|
return {
|
|
9636
9730
|
args: [...args.slice(0, i), "--body-file", file, ...args.slice(i + 2)],
|
|
@@ -9672,6 +9766,44 @@ function buildPrArgs({ title, body, base: base2, head, repo }) {
|
|
|
9672
9766
|
return args;
|
|
9673
9767
|
}
|
|
9674
9768
|
|
|
9769
|
+
// src/issue-check.ts
|
|
9770
|
+
var CHECKLIST_RE = /^([ \t]*[-*+] \[)([ xX])(\] )(.*)$/gm;
|
|
9771
|
+
function findChecklistItems(body) {
|
|
9772
|
+
const items = [];
|
|
9773
|
+
for (const m of body.matchAll(CHECKLIST_RE)) {
|
|
9774
|
+
const prefix = m[1];
|
|
9775
|
+
const marker = m[2];
|
|
9776
|
+
const text = m[4].replace(/\r$/, "");
|
|
9777
|
+
items.push({
|
|
9778
|
+
markerIndex: (m.index ?? 0) + prefix.length,
|
|
9779
|
+
checked: marker.toLowerCase() === "x",
|
|
9780
|
+
text
|
|
9781
|
+
});
|
|
9782
|
+
}
|
|
9783
|
+
return items;
|
|
9784
|
+
}
|
|
9785
|
+
function selectChecklistItem(items, query) {
|
|
9786
|
+
const q = query.trim();
|
|
9787
|
+
if (!q) return { ok: false, reason: "not-found" };
|
|
9788
|
+
const exact = items.filter((it) => it.text.trim() === q);
|
|
9789
|
+
if (exact.length === 1) return { ok: true, item: exact[0] };
|
|
9790
|
+
if (exact.length > 1) return { ok: false, reason: "ambiguous", matches: exact };
|
|
9791
|
+
const sub = items.filter((it) => it.text.includes(q));
|
|
9792
|
+
if (sub.length === 1) return { ok: true, item: sub[0] };
|
|
9793
|
+
if (sub.length === 0) return { ok: false, reason: "not-found" };
|
|
9794
|
+
return { ok: false, reason: "ambiguous", matches: sub };
|
|
9795
|
+
}
|
|
9796
|
+
function setChecklistMarker(body, item, checked) {
|
|
9797
|
+
if (item.checked === checked) return { body, changed: false };
|
|
9798
|
+
const target = checked ? "x" : " ";
|
|
9799
|
+
return { body: body.slice(0, item.markerIndex) + target + body.slice(item.markerIndex + 1), changed: true };
|
|
9800
|
+
}
|
|
9801
|
+
function applyChecklistCheck(body, query, checked) {
|
|
9802
|
+
const sel = selectChecklistItem(findChecklistItems(body), query);
|
|
9803
|
+
if (!sel.ok) return sel;
|
|
9804
|
+
return { ok: true, edit: setChecklistMarker(body, sel.item, checked), item: sel.item };
|
|
9805
|
+
}
|
|
9806
|
+
|
|
9675
9807
|
// src/command-manifest.ts
|
|
9676
9808
|
function buildArgument(arg) {
|
|
9677
9809
|
const out = { name: arg.name(), required: arg.required, variadic: arg.variadic };
|
|
@@ -11193,9 +11325,9 @@ function decideStage(inputs) {
|
|
|
11193
11325
|
|
|
11194
11326
|
// src/cursor-plugin-seed.ts
|
|
11195
11327
|
var import_node_child_process8 = require("node:child_process");
|
|
11196
|
-
var
|
|
11328
|
+
var import_node_fs16 = require("node:fs");
|
|
11197
11329
|
var import_node_os5 = require("node:os");
|
|
11198
|
-
var
|
|
11330
|
+
var import_node_path15 = require("node:path");
|
|
11199
11331
|
var import_node_util6 = require("node:util");
|
|
11200
11332
|
function isSemverVersion(v) {
|
|
11201
11333
|
return typeof v === "string" && /^v?\d+\.\d+\.\d+/.test(v.trim());
|
|
@@ -11212,17 +11344,17 @@ function ghReleaseTarballApiArgs(tag) {
|
|
|
11212
11344
|
}
|
|
11213
11345
|
function cursorUserGlobalStatePath() {
|
|
11214
11346
|
if (process.platform === "win32") {
|
|
11215
|
-
const base2 = process.env.APPDATA || (0,
|
|
11216
|
-
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");
|
|
11217
11349
|
}
|
|
11218
11350
|
if (process.platform === "darwin") {
|
|
11219
|
-
return (0,
|
|
11351
|
+
return (0, import_node_path15.join)((0, import_node_os5.homedir)(), "Library", "Application Support", "Cursor", "User", "globalStorage", "state.vscdb");
|
|
11220
11352
|
}
|
|
11221
|
-
return (0,
|
|
11353
|
+
return (0, import_node_path15.join)((0, import_node_os5.homedir)(), ".config", "Cursor", "User", "globalStorage", "state.vscdb");
|
|
11222
11354
|
}
|
|
11223
11355
|
async function readCursorThirdPartyExtensibilityEnabled(execFileP5) {
|
|
11224
11356
|
const dbPath = cursorUserGlobalStatePath();
|
|
11225
|
-
if (!(0,
|
|
11357
|
+
if (!(0, import_node_fs16.existsSync)(dbPath)) return void 0;
|
|
11226
11358
|
try {
|
|
11227
11359
|
const { stdout } = await execFileP5("sqlite3", [dbPath, `SELECT value FROM ItemTable WHERE key = '${CURSOR_THIRD_PARTY_STATE_KEY}';`], {
|
|
11228
11360
|
timeout: 5e3
|
|
@@ -11236,57 +11368,57 @@ async function readCursorThirdPartyExtensibilityEnabled(execFileP5) {
|
|
|
11236
11368
|
}
|
|
11237
11369
|
}
|
|
11238
11370
|
function syncDirContents(src, dest) {
|
|
11239
|
-
(0,
|
|
11240
|
-
for (const name of (0,
|
|
11241
|
-
(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 });
|
|
11242
11374
|
}
|
|
11243
|
-
(0,
|
|
11375
|
+
(0, import_node_fs16.cpSync)(src, dest, { recursive: true });
|
|
11244
11376
|
}
|
|
11245
11377
|
function releaseTag(releasedVersion) {
|
|
11246
11378
|
return releasedVersion.startsWith("v") ? releasedVersion : `v${releasedVersion}`;
|
|
11247
11379
|
}
|
|
11248
11380
|
async function extractPluginMmiFromHubCheckout(hubCheckout, tag, tmpRoot, execFileP5) {
|
|
11249
|
-
const tarFile = (0,
|
|
11381
|
+
const tarFile = (0, import_node_path15.join)(tmpRoot, "archive.tar");
|
|
11250
11382
|
try {
|
|
11251
11383
|
await execFileP5("git", gitFetchReleaseTagArgs(hubCheckout, tag), { timeout: 6e4 });
|
|
11252
11384
|
await execFileP5("git", ["-C", hubCheckout, "archive", "--format=tar", `--output=${tarFile}`, tag, "plugins/mmi"], {
|
|
11253
11385
|
timeout: 6e4
|
|
11254
11386
|
});
|
|
11255
11387
|
await execFileP5("tar", ["-xf", tarFile, "-C", tmpRoot], { timeout: 6e4 });
|
|
11256
|
-
const pluginMmi = (0,
|
|
11257
|
-
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;
|
|
11258
11390
|
} catch {
|
|
11259
11391
|
return void 0;
|
|
11260
11392
|
}
|
|
11261
11393
|
}
|
|
11262
11394
|
async function downloadPluginMmiViaGh(tag, tmpRoot) {
|
|
11263
|
-
const tarPath = (0,
|
|
11395
|
+
const tarPath = (0, import_node_path15.join)(tmpRoot, "repo.tgz");
|
|
11264
11396
|
try {
|
|
11265
|
-
(0,
|
|
11397
|
+
(0, import_node_fs16.mkdirSync)(tmpRoot, { recursive: true });
|
|
11266
11398
|
const { stdout } = await execFileBuffer("gh", ghReleaseTarballApiArgs(tag), {
|
|
11267
11399
|
timeout: 12e4,
|
|
11268
11400
|
maxBuffer: 100 * 1024 * 1024,
|
|
11269
11401
|
encoding: "buffer",
|
|
11270
11402
|
windowsHide: true
|
|
11271
11403
|
});
|
|
11272
|
-
(0,
|
|
11404
|
+
(0, import_node_fs16.writeFileSync)(tarPath, stdout);
|
|
11273
11405
|
await execFileBuffer("tar", ["-xzf", tarPath, "-C", tmpRoot], { timeout: 12e4, windowsHide: true });
|
|
11274
|
-
const top = (0,
|
|
11406
|
+
const top = (0, import_node_fs16.readdirSync)(tmpRoot).find((entry) => entry !== "repo.tgz");
|
|
11275
11407
|
if (!top) return void 0;
|
|
11276
|
-
const pluginMmi = (0,
|
|
11277
|
-
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;
|
|
11278
11410
|
} catch {
|
|
11279
11411
|
return void 0;
|
|
11280
11412
|
}
|
|
11281
11413
|
}
|
|
11282
11414
|
async function resolvePluginMmiSource(releasedVersion, hubCheckout, tmpRoot, execFileP5) {
|
|
11283
|
-
(0,
|
|
11415
|
+
(0, import_node_fs16.mkdirSync)(tmpRoot, { recursive: true });
|
|
11284
11416
|
const tag = releaseTag(releasedVersion);
|
|
11285
11417
|
if (hubCheckout) {
|
|
11286
11418
|
const fromHub = await extractPluginMmiFromHubCheckout(hubCheckout, tag, tmpRoot, execFileP5);
|
|
11287
11419
|
if (fromHub) return fromHub;
|
|
11288
11420
|
}
|
|
11289
|
-
return downloadPluginMmiViaGh(tag, (0,
|
|
11421
|
+
return downloadPluginMmiViaGh(tag, (0, import_node_path15.join)(tmpRoot, "gh"));
|
|
11290
11422
|
}
|
|
11291
11423
|
function cursorPluginPinsNeedingSeed(pins, releasedVersion) {
|
|
11292
11424
|
if (!isSemverVersion(releasedVersion)) return pins.filter((pin) => !pin.hasPluginJson || !pin.hasHooksJson || pin.isEmpty);
|
|
@@ -11307,7 +11439,7 @@ async function applyCursorPluginCacheSeed(input) {
|
|
|
11307
11439
|
for (const pin of pinsToSeed) {
|
|
11308
11440
|
syncDirContents(source, pin.path);
|
|
11309
11441
|
}
|
|
11310
|
-
(0,
|
|
11442
|
+
(0, import_node_fs16.rmSync)(tmpRoot, { recursive: true, force: true });
|
|
11311
11443
|
return true;
|
|
11312
11444
|
}
|
|
11313
11445
|
|
|
@@ -11348,6 +11480,17 @@ function pluginInstallManualFix(projectPath, surface = "claude-cli") {
|
|
|
11348
11480
|
function isMmiPluginEnabled(settings) {
|
|
11349
11481
|
return Boolean(settings?.enabledPlugins?.[MMI_PLUGIN_ID]);
|
|
11350
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
|
+
}
|
|
11351
11494
|
function hasProjectInstallRecord(file, pluginId, projectPath) {
|
|
11352
11495
|
const records = file?.plugins?.[pluginId];
|
|
11353
11496
|
if (!Array.isArray(records)) return false;
|
|
@@ -11913,8 +12056,8 @@ function stageLiveUpSteps(t) {
|
|
|
11913
12056
|
command: `gh ${ghDispatchArgs("tenant-deploy.yml", { slug: t.slug, repo: t.repo, ref: t.ref ?? "<branch>", stage: "dev" }).join(" ")}`
|
|
11914
12057
|
},
|
|
11915
12058
|
{
|
|
11916
|
-
label:
|
|
11917
|
-
command: `gh ${ghDispatchArgs("tenant-control.yml", { slug: t.slug, stage: "dev", action: "allow
|
|
12059
|
+
label: `gate ${t.host} to your IP at the Cloudflare edge (ephemeral firewall_custom skip rule)`,
|
|
12060
|
+
command: `gh ${ghDispatchArgs("tenant-control.yml", { slug: t.slug, stage: "dev", action: "cf-gate-allow", host: t.host, ip: "<your ip>" }).join(" ")}`
|
|
11918
12061
|
},
|
|
11919
12062
|
{ label: "tear down when done", command: "mmi-cli stage --live --down --apply" }
|
|
11920
12063
|
];
|
|
@@ -11926,8 +12069,8 @@ function stageLiveDownSteps(t) {
|
|
|
11926
12069
|
command: `gh ${ghDispatchArgs("tenant-control.yml", { slug: t.slug, stage: "dev", action: "stop" }).join(" ")}`
|
|
11927
12070
|
},
|
|
11928
12071
|
{
|
|
11929
|
-
label: "
|
|
11930
|
-
command: `gh ${ghDispatchArgs("tenant-control.yml", { slug: t.slug, stage: "dev", action: "
|
|
12072
|
+
label: "remove the Cloudflare edge gate (the stage goes dark even if restarted)",
|
|
12073
|
+
command: `gh ${ghDispatchArgs("tenant-control.yml", { slug: t.slug, stage: "dev", action: "cf-gate-clear", host: t.host }).join(" ")}`
|
|
11931
12074
|
}
|
|
11932
12075
|
];
|
|
11933
12076
|
}
|
|
@@ -11935,8 +12078,9 @@ async function runStageLiveUp(deps, t) {
|
|
|
11935
12078
|
if (!t.ref?.trim()) throw new Error("stage --live: cannot resolve the current branch to deploy");
|
|
11936
12079
|
const ip = (await deps.detectIp()).trim();
|
|
11937
12080
|
if (!validStageLiveIp(ip)) throw new Error(`stage --live: detected public IP is not a literal IPv4/IPv6 address: "${ip.slice(0, 80)}"`);
|
|
12081
|
+
if (!t.host?.trim()) throw new Error("stage --live: cannot resolve the dev edge host (registry edgeDomains.dev)");
|
|
11938
12082
|
await deps.run("gh", ghDispatchArgs("tenant-deploy.yml", { slug: t.slug, repo: t.repo, ref: t.ref, stage: "dev" }));
|
|
11939
|
-
await deps.run("gh", ghDispatchArgs("tenant-control.yml", { slug: t.slug, stage: "dev", action: "allow
|
|
12083
|
+
await deps.run("gh", ghDispatchArgs("tenant-control.yml", { slug: t.slug, stage: "dev", action: "cf-gate-allow", host: t.host, ip }));
|
|
11940
12084
|
return {
|
|
11941
12085
|
command: "stage --live",
|
|
11942
12086
|
mode: "up",
|
|
@@ -11945,25 +12089,26 @@ async function runStageLiveUp(deps, t) {
|
|
|
11945
12089
|
ref: t.ref,
|
|
11946
12090
|
ip,
|
|
11947
12091
|
dispatched: ["tenant-deploy.yml", "tenant-control.yml"],
|
|
11948
|
-
message: `dispatched the dev deploy of ${t.ref} and the
|
|
12092
|
+
message: `dispatched the dev deploy of ${t.ref} and the Cloudflare edge gate for ${t.host} \u2192 ${ip}; watch the runs in ${STAGE_LIVE_HUB_REPO} Actions \u2014 tear down with: mmi-cli stage --live --down --apply`
|
|
11949
12093
|
};
|
|
11950
12094
|
}
|
|
11951
12095
|
async function runStageLiveDown(deps, t) {
|
|
12096
|
+
if (!t.host?.trim()) throw new Error("stage --live: cannot resolve the dev edge host (registry edgeDomains.dev)");
|
|
11952
12097
|
await deps.run("gh", ghDispatchArgs("tenant-control.yml", { slug: t.slug, stage: "dev", action: "stop" }));
|
|
11953
|
-
await deps.run("gh", ghDispatchArgs("tenant-control.yml", { slug: t.slug, stage: "dev", action: "
|
|
12098
|
+
await deps.run("gh", ghDispatchArgs("tenant-control.yml", { slug: t.slug, stage: "dev", action: "cf-gate-clear", host: t.host }));
|
|
11954
12099
|
return {
|
|
11955
12100
|
command: "stage --live",
|
|
11956
12101
|
mode: "down",
|
|
11957
12102
|
slug: t.slug,
|
|
11958
12103
|
repo: t.repo,
|
|
11959
12104
|
dispatched: ["tenant-control.yml", "tenant-control.yml"],
|
|
11960
|
-
message: `dispatched the dev stop and
|
|
12105
|
+
message: `dispatched the dev stop and the Cloudflare edge gate clear for ${t.host}; the dev stage is dark until the next mmi-cli stage --live --apply`
|
|
11961
12106
|
};
|
|
11962
12107
|
}
|
|
11963
12108
|
|
|
11964
12109
|
// src/design-system.ts
|
|
11965
|
-
var
|
|
11966
|
-
var
|
|
12110
|
+
var import_node_fs17 = require("node:fs");
|
|
12111
|
+
var import_node_path16 = require("node:path");
|
|
11967
12112
|
var UI_PACKAGE_CANDIDATES = ["@mutmutco/ui-dashboard", "@mutmutco/ui", "@mutmutco/theme"];
|
|
11968
12113
|
var DESIGN_SYSTEM_VERSION_LABEL = "@mutmutco design-system npm package (vs @latest)";
|
|
11969
12114
|
function dashboardConsumerRegistryFix(error) {
|
|
@@ -12012,17 +12157,17 @@ function buildDesignSystemVersionCheck(input) {
|
|
|
12012
12157
|
}
|
|
12013
12158
|
function readJsonFile(path2) {
|
|
12014
12159
|
try {
|
|
12015
|
-
return JSON.parse((0,
|
|
12160
|
+
return JSON.parse((0, import_node_fs17.readFileSync)(path2, "utf8"));
|
|
12016
12161
|
} catch {
|
|
12017
12162
|
return void 0;
|
|
12018
12163
|
}
|
|
12019
12164
|
}
|
|
12020
12165
|
function isUiFactoryCheckout(root) {
|
|
12021
|
-
const pkg = readJsonFile((0,
|
|
12166
|
+
const pkg = readJsonFile((0, import_node_path16.join)(root, "package.json"));
|
|
12022
12167
|
return pkg?.name === "mmd-ui" && pkg?.private === true;
|
|
12023
12168
|
}
|
|
12024
12169
|
function resolveDeclaredUiPackage(root) {
|
|
12025
|
-
const pkg = readJsonFile((0,
|
|
12170
|
+
const pkg = readJsonFile((0, import_node_path16.join)(root, "package.json"));
|
|
12026
12171
|
if (!pkg) return void 0;
|
|
12027
12172
|
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
12028
12173
|
for (const name of UI_PACKAGE_CANDIDATES) {
|
|
@@ -12032,8 +12177,8 @@ function resolveDeclaredUiPackage(root) {
|
|
|
12032
12177
|
return void 0;
|
|
12033
12178
|
}
|
|
12034
12179
|
function readLockfileInstalledVersion(root, packageName) {
|
|
12035
|
-
const lockPath = (0,
|
|
12036
|
-
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;
|
|
12037
12182
|
const lock = readJsonFile(lockPath);
|
|
12038
12183
|
const node = lock?.packages?.[`node_modules/${packageName}`];
|
|
12039
12184
|
const version = node?.version?.trim();
|
|
@@ -12062,15 +12207,15 @@ function designSystemSnapshot(root) {
|
|
|
12062
12207
|
|
|
12063
12208
|
// src/design-system-registry.ts
|
|
12064
12209
|
var import_node_crypto7 = require("node:crypto");
|
|
12065
|
-
var
|
|
12066
|
-
var
|
|
12210
|
+
var import_node_fs19 = require("node:fs");
|
|
12211
|
+
var import_node_path17 = require("node:path");
|
|
12067
12212
|
|
|
12068
12213
|
// src/atomic-write.ts
|
|
12069
|
-
var
|
|
12214
|
+
var import_node_fs18 = require("node:fs");
|
|
12070
12215
|
function atomicWriteFileSync(path2, content) {
|
|
12071
12216
|
const tmp = `${path2}.${process.pid}.tmp`;
|
|
12072
|
-
(0,
|
|
12073
|
-
(0,
|
|
12217
|
+
(0, import_node_fs18.writeFileSync)(tmp, content, "utf8");
|
|
12218
|
+
(0, import_node_fs18.renameSync)(tmp, path2);
|
|
12074
12219
|
}
|
|
12075
12220
|
|
|
12076
12221
|
// src/design-system-registry.ts
|
|
@@ -12081,13 +12226,13 @@ var REGISTRY_FIX = "run `mmi-cli doctor --apply` to pull registry components int
|
|
|
12081
12226
|
var REGISTRY_UNREACHABLE_FIX = "live @mutmutco registry unreachable \u2014 verify `components.json` `@mutmutco` registry URL and network, then retry `mmi-cli doctor`";
|
|
12082
12227
|
function readJsonFile2(path2) {
|
|
12083
12228
|
try {
|
|
12084
|
-
return JSON.parse((0,
|
|
12229
|
+
return JSON.parse((0, import_node_fs19.readFileSync)(path2, "utf8"));
|
|
12085
12230
|
} catch {
|
|
12086
12231
|
return void 0;
|
|
12087
12232
|
}
|
|
12088
12233
|
}
|
|
12089
12234
|
function readComponentsJson(root) {
|
|
12090
|
-
return readJsonFile2((0,
|
|
12235
|
+
return readJsonFile2((0, import_node_path17.join)(root, "components.json"));
|
|
12091
12236
|
}
|
|
12092
12237
|
function hasMutmutcoRegistry(root) {
|
|
12093
12238
|
const url = readComponentsJson(root)?.registries?.["@mutmutco"];
|
|
@@ -12095,7 +12240,7 @@ function hasMutmutcoRegistry(root) {
|
|
|
12095
12240
|
}
|
|
12096
12241
|
function resolveCacheDir(root) {
|
|
12097
12242
|
const custom = readComponentsJson(root)?.mmi?.cacheDir;
|
|
12098
|
-
return (0,
|
|
12243
|
+
return (0, import_node_path17.join)(root, custom ?? DESIGN_SYSTEM_CACHE_DIR);
|
|
12099
12244
|
}
|
|
12100
12245
|
function resolveRegistryUrlTemplate(root) {
|
|
12101
12246
|
return readComponentsJson(root)?.registries?.["@mutmutco"];
|
|
@@ -12104,7 +12249,7 @@ function registryItemUrl(template, name) {
|
|
|
12104
12249
|
return template.replace("{name}", name);
|
|
12105
12250
|
}
|
|
12106
12251
|
function readDesignSystemManifest(root) {
|
|
12107
|
-
const raw = readJsonFile2((0,
|
|
12252
|
+
const raw = readJsonFile2((0, import_node_path17.join)(root, DESIGN_SYSTEM_MANIFEST_PATH));
|
|
12108
12253
|
if (!raw || !Array.isArray(raw.components)) return void 0;
|
|
12109
12254
|
return raw;
|
|
12110
12255
|
}
|
|
@@ -12116,11 +12261,11 @@ function listInstalledRegistryComponents(root) {
|
|
|
12116
12261
|
return scanCachedComponentNames(resolveCacheDir(root));
|
|
12117
12262
|
}
|
|
12118
12263
|
function scanCachedComponentNames(cacheDir) {
|
|
12119
|
-
if (!(0,
|
|
12264
|
+
if (!(0, import_node_fs19.existsSync)(cacheDir)) return [];
|
|
12120
12265
|
const names = /* @__PURE__ */ new Set();
|
|
12121
12266
|
const walk = (dir) => {
|
|
12122
|
-
for (const ent of (0,
|
|
12123
|
-
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);
|
|
12124
12269
|
if (ent.isDirectory()) walk(p);
|
|
12125
12270
|
else if (ent.isFile() && /\.(tsx?|jsx?)$/.test(ent.name)) {
|
|
12126
12271
|
names.add(ent.name.replace(/\.(tsx|ts|jsx|js)$/, ""));
|
|
@@ -12209,13 +12354,13 @@ async function gatherRegistryComponentsState(root, targetVersion, deps) {
|
|
|
12209
12354
|
let componentStale = false;
|
|
12210
12355
|
for (const file of item.files) {
|
|
12211
12356
|
if (!file.target || file.content == null) continue;
|
|
12212
|
-
const cachePath = (0,
|
|
12213
|
-
if (!(0,
|
|
12357
|
+
const cachePath = (0, import_node_path17.join)(cacheDir, cacheRelativePath(file.target));
|
|
12358
|
+
if (!(0, import_node_fs19.existsSync)(cachePath)) {
|
|
12214
12359
|
componentStale = true;
|
|
12215
12360
|
break;
|
|
12216
12361
|
}
|
|
12217
12362
|
try {
|
|
12218
|
-
if (contentHash((0,
|
|
12363
|
+
if (contentHash((0, import_node_fs19.readFileSync)(cachePath, "utf8")) !== contentHash(file.content)) {
|
|
12219
12364
|
componentStale = true;
|
|
12220
12365
|
break;
|
|
12221
12366
|
}
|
|
@@ -12225,7 +12370,7 @@ async function gatherRegistryComponentsState(root, targetVersion, deps) {
|
|
|
12225
12370
|
}
|
|
12226
12371
|
}
|
|
12227
12372
|
if (componentStale) {
|
|
12228
|
-
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`))) {
|
|
12229
12374
|
stale.push(name);
|
|
12230
12375
|
} else {
|
|
12231
12376
|
missing.push(name);
|
|
@@ -12253,15 +12398,15 @@ async function applyRegistryComponentsSync(root, components, targetVersion, log,
|
|
|
12253
12398
|
if (!item) return { ok: false };
|
|
12254
12399
|
for (const file of item.files) {
|
|
12255
12400
|
if (!file.target || file.content == null) continue;
|
|
12256
|
-
const outPath = (0,
|
|
12257
|
-
deps.mkdir((0,
|
|
12401
|
+
const outPath = (0, import_node_path17.join)(cacheDir, cacheRelativePath(file.target));
|
|
12402
|
+
deps.mkdir((0, import_node_path17.dirname)(outPath));
|
|
12258
12403
|
const body = file.content.endsWith("\n") ? file.content : `${file.content}
|
|
12259
12404
|
`;
|
|
12260
12405
|
deps.writeFile(outPath, body);
|
|
12261
12406
|
}
|
|
12262
12407
|
}
|
|
12263
|
-
const manifestPath = (0,
|
|
12264
|
-
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));
|
|
12265
12410
|
const manifest = {
|
|
12266
12411
|
version: targetVersion,
|
|
12267
12412
|
syncedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -12276,14 +12421,14 @@ function defaultRegistrySyncDeps() {
|
|
|
12276
12421
|
return {
|
|
12277
12422
|
fetch,
|
|
12278
12423
|
writeFile: (path2, content) => atomicWriteFileSync(path2, content),
|
|
12279
|
-
mkdir: (path2) => (0,
|
|
12424
|
+
mkdir: (path2) => (0, import_node_fs19.mkdirSync)(path2, { recursive: true })
|
|
12280
12425
|
};
|
|
12281
12426
|
}
|
|
12282
12427
|
|
|
12283
12428
|
// src/stage-runner.ts
|
|
12284
12429
|
var import_node_child_process9 = require("node:child_process");
|
|
12285
|
-
var
|
|
12286
|
-
var
|
|
12430
|
+
var import_node_fs20 = require("node:fs");
|
|
12431
|
+
var import_node_path18 = require("node:path");
|
|
12287
12432
|
var import_node_net2 = require("node:net");
|
|
12288
12433
|
var import_node_util7 = require("node:util");
|
|
12289
12434
|
var execFileP4 = (0, import_node_util7.promisify)(import_node_child_process9.execFile);
|
|
@@ -12328,7 +12473,7 @@ function detectStaleEnvFile(exampleContent, targetContent, mtimes) {
|
|
|
12328
12473
|
return void 0;
|
|
12329
12474
|
}
|
|
12330
12475
|
function stageStatePath(cwd = process.cwd()) {
|
|
12331
|
-
return (0,
|
|
12476
|
+
return (0, import_node_path18.join)(cwd, "tmp", "stage", "state.json");
|
|
12332
12477
|
}
|
|
12333
12478
|
function mergeEnvSecretsIntoFile(content, secrets) {
|
|
12334
12479
|
const lines = content.split(/\r?\n/);
|
|
@@ -12411,9 +12556,9 @@ async function shell(command, cwd, timeoutMs) {
|
|
|
12411
12556
|
});
|
|
12412
12557
|
}
|
|
12413
12558
|
function readState(path2) {
|
|
12414
|
-
if (!(0,
|
|
12559
|
+
if (!(0, import_node_fs20.existsSync)(path2)) return null;
|
|
12415
12560
|
try {
|
|
12416
|
-
return JSON.parse((0,
|
|
12561
|
+
return JSON.parse((0, import_node_fs20.readFileSync)(path2, "utf8"));
|
|
12417
12562
|
} catch {
|
|
12418
12563
|
return null;
|
|
12419
12564
|
}
|
|
@@ -12465,7 +12610,7 @@ async function stopStage(opts = {}) {
|
|
|
12465
12610
|
return { ok: true, action: "stop", statePath, message: "no previous stage state found" };
|
|
12466
12611
|
}
|
|
12467
12612
|
await killTree(state.pid);
|
|
12468
|
-
(0,
|
|
12613
|
+
(0, import_node_fs20.rmSync)(statePath, { force: true });
|
|
12469
12614
|
return { ok: true, action: "stop", statePath, pid: state.pid, message: `stopped previous stage pid ${state.pid}` };
|
|
12470
12615
|
}
|
|
12471
12616
|
async function startStage(config = {}, opts = {}) {
|
|
@@ -12474,7 +12619,7 @@ async function startStage(config = {}, opts = {}) {
|
|
|
12474
12619
|
const cwd = opts.cwd ?? process.cwd();
|
|
12475
12620
|
const statePath = opts.statePath ?? stageStatePath(cwd);
|
|
12476
12621
|
const dir = statePath.slice(0, Math.max(statePath.lastIndexOf("/"), statePath.lastIndexOf("\\")));
|
|
12477
|
-
(0,
|
|
12622
|
+
(0, import_node_fs20.mkdirSync)(dir, { recursive: true });
|
|
12478
12623
|
let stagePort;
|
|
12479
12624
|
if (config.portRange) {
|
|
12480
12625
|
const [s, e] = config.portRange;
|
|
@@ -12484,14 +12629,14 @@ async function startStage(config = {}, opts = {}) {
|
|
|
12484
12629
|
}
|
|
12485
12630
|
const sub = (s) => s != null && stagePort != null ? s.replace(/\$\{?STAGE_PORT\}?/g, String(stagePort)) : s;
|
|
12486
12631
|
if (config.ensureEnv) {
|
|
12487
|
-
const target = (0,
|
|
12488
|
-
const example = (0,
|
|
12489
|
-
if (!(0,
|
|
12490
|
-
(0,
|
|
12491
|
-
} else if ((0,
|
|
12492
|
-
const stale = detectStaleEnvFile((0,
|
|
12493
|
-
exampleMtimeMs: (0,
|
|
12494
|
-
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
|
|
12495
12640
|
});
|
|
12496
12641
|
if (stale) {
|
|
12497
12642
|
const msg = `stale ${config.ensureEnv.target} (${stale}) \u2014 delete it or refresh from ${config.ensureEnv.example} before re-running /stage`;
|
|
@@ -12499,8 +12644,8 @@ async function startStage(config = {}, opts = {}) {
|
|
|
12499
12644
|
console.error(`mmi-cli stage: ${msg} (allowed via --allow-stale-env)`);
|
|
12500
12645
|
}
|
|
12501
12646
|
}
|
|
12502
|
-
if (opts.vaultEnvMerge && Object.keys(opts.vaultEnvMerge).length && (0,
|
|
12503
|
-
(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");
|
|
12504
12649
|
}
|
|
12505
12650
|
}
|
|
12506
12651
|
const extraEnv = {};
|
|
@@ -12525,13 +12670,13 @@ async function startStage(config = {}, opts = {}) {
|
|
|
12525
12670
|
healthUrl: sub(config.healthUrl?.trim()) || void 0,
|
|
12526
12671
|
port: stagePort
|
|
12527
12672
|
};
|
|
12528
|
-
(0,
|
|
12673
|
+
(0, import_node_fs20.writeFileSync)(statePath, JSON.stringify(state, null, 2), "utf8");
|
|
12529
12674
|
try {
|
|
12530
12675
|
if (state.healthUrl) await waitForHealth(state.healthUrl, opts.timeoutMs ?? 6e4, config.healthAnyStatus);
|
|
12531
12676
|
else await waitForProcessStability(child);
|
|
12532
12677
|
} catch (e) {
|
|
12533
12678
|
await killTree(state.pid);
|
|
12534
|
-
(0,
|
|
12679
|
+
(0, import_node_fs20.rmSync)(statePath, { force: true });
|
|
12535
12680
|
throw e;
|
|
12536
12681
|
}
|
|
12537
12682
|
const result = { ok: true, action: "start", statePath, pid: state.pid, port: stagePort, message: `started stage pid ${state.pid}${stagePort != null ? ` on port ${stagePort}` : ""}` };
|
|
@@ -12989,18 +13134,25 @@ async function rollDevelopmentForward(deps, ctx, tag) {
|
|
|
12989
13134
|
}
|
|
12990
13135
|
function resolveContextState(context, checkRuns, statuses) {
|
|
12991
13136
|
let sawFailure = false;
|
|
13137
|
+
let sawSuccess = false;
|
|
13138
|
+
let sawPending = false;
|
|
12992
13139
|
for (const r of checkRuns) {
|
|
12993
13140
|
if (r.name !== context) continue;
|
|
12994
13141
|
if (r.status === "completed") {
|
|
12995
|
-
if (r.conclusion === "success")
|
|
12996
|
-
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;
|
|
12997
13146
|
}
|
|
12998
13147
|
}
|
|
12999
13148
|
for (const s of statuses) {
|
|
13000
13149
|
if (s.context !== context) continue;
|
|
13001
|
-
if (s.state === "success")
|
|
13002
|
-
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;
|
|
13003
13153
|
}
|
|
13154
|
+
if (sawPending) return "pending";
|
|
13155
|
+
if (sawSuccess) return "success";
|
|
13004
13156
|
return sawFailure ? "failed" : "pending";
|
|
13005
13157
|
}
|
|
13006
13158
|
async function waitForRequiredTrainChecks(deps, ctx, sha, required) {
|
|
@@ -14349,7 +14501,7 @@ async function announceRelease(deps, args) {
|
|
|
14349
14501
|
}
|
|
14350
14502
|
|
|
14351
14503
|
// src/port-registry.ts
|
|
14352
|
-
var
|
|
14504
|
+
var import_node_fs21 = require("node:fs");
|
|
14353
14505
|
|
|
14354
14506
|
// ../infra/port-geometry.mjs
|
|
14355
14507
|
var PORT_BLOCK = 100;
|
|
@@ -14363,8 +14515,8 @@ function nextPortBlock(registry2) {
|
|
|
14363
14515
|
return [base2, base2 + PORT_SPAN];
|
|
14364
14516
|
}
|
|
14365
14517
|
function loadPortRegistry(path2) {
|
|
14366
|
-
if (!(0,
|
|
14367
|
-
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"));
|
|
14368
14520
|
const out = {};
|
|
14369
14521
|
for (const [key, value] of Object.entries(raw)) {
|
|
14370
14522
|
if (Array.isArray(value) && value.length === 2 && value.every((n) => typeof n === "number")) {
|
|
@@ -14378,9 +14530,9 @@ function ensurePortRange(repo, path2) {
|
|
|
14378
14530
|
const existing = registry2[repo];
|
|
14379
14531
|
if (existing) return existing;
|
|
14380
14532
|
const range = nextPortBlock(registry2);
|
|
14381
|
-
const raw = (0,
|
|
14533
|
+
const raw = (0, import_node_fs21.existsSync)(path2) ? JSON.parse((0, import_node_fs21.readFileSync)(path2, "utf8")) : {};
|
|
14382
14534
|
raw[repo] = range;
|
|
14383
|
-
(0,
|
|
14535
|
+
(0, import_node_fs21.writeFileSync)(path2, JSON.stringify(raw, null, 2) + "\n", "utf8");
|
|
14384
14536
|
return range;
|
|
14385
14537
|
}
|
|
14386
14538
|
function portCursorSeed(registry2) {
|
|
@@ -15240,6 +15392,9 @@ async function upsertProject(slug, patch, deps) {
|
|
|
15240
15392
|
async function attestAppGaps(slug, repo, deps) {
|
|
15241
15393
|
return postJson(`/projects/${encodeURIComponent(slug)}/attest-app`, { repo }, deps);
|
|
15242
15394
|
}
|
|
15395
|
+
async function setDeployCoords(slug, payload, deps) {
|
|
15396
|
+
return postJson(`/projects/${encodeURIComponent(slug)}/deploy`, payload, deps);
|
|
15397
|
+
}
|
|
15243
15398
|
async function tenantControl(payload, deps) {
|
|
15244
15399
|
return postJson("/tenant-control", payload, deps, "POST", { noRetry: true });
|
|
15245
15400
|
}
|
|
@@ -15634,7 +15789,7 @@ ${section}`.trim();
|
|
|
15634
15789
|
}
|
|
15635
15790
|
|
|
15636
15791
|
// src/project-set.ts
|
|
15637
|
-
var UNSET_KEYS = ["oauth", "requiredRuntimeSecrets", "edgeDomains", "requiredGcpApis", "publishRequired", "publishDir", "dashboard", "ci", "requiredChecks", "gate"];
|
|
15792
|
+
var UNSET_KEYS = ["oauth", "requiredRuntimeSecrets", "edgeDomains", "requiredGcpApis", "publishRequired", "publishDir", "dashboard", "fofuEnabled", "ci", "requiredChecks", "gate"];
|
|
15638
15793
|
var UNSET_KEY_SET = new Set(UNSET_KEYS);
|
|
15639
15794
|
var RUNTIME_SECRET_STAGES = ["dev", "rc", "main"];
|
|
15640
15795
|
function parseRuntimeSecretsVar(raw) {
|
|
@@ -15757,7 +15912,7 @@ function parseOauthVar(raw) {
|
|
|
15757
15912
|
throw new Error('project set: oauth must be JSON, e.g. {"subdomains":["app"],"domains":["example.co"],"callbackPath":"/auth/callback"}');
|
|
15758
15913
|
}
|
|
15759
15914
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
15760
|
-
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");
|
|
15761
15916
|
}
|
|
15762
15917
|
const map = parsed;
|
|
15763
15918
|
const out = {};
|
|
@@ -15770,8 +15925,11 @@ function parseOauthVar(raw) {
|
|
|
15770
15925
|
} else if (key === "callbackPath") {
|
|
15771
15926
|
if (typeof value !== "string" || !value.trim()) throw new Error("project set: oauth.callbackPath must be a non-empty string");
|
|
15772
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();
|
|
15773
15931
|
} else {
|
|
15774
|
-
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`);
|
|
15775
15933
|
}
|
|
15776
15934
|
}
|
|
15777
15935
|
return out;
|
|
@@ -15798,6 +15956,11 @@ function parseDashboardVar(raw) {
|
|
|
15798
15956
|
if (raw === "false") return false;
|
|
15799
15957
|
throw new Error("project set: dashboard must be true or false");
|
|
15800
15958
|
}
|
|
15959
|
+
function parseFofuEnabledVar(raw) {
|
|
15960
|
+
if (raw === "true") return true;
|
|
15961
|
+
if (raw === "false") return false;
|
|
15962
|
+
throw new Error("project set: fofuEnabled must be true or false");
|
|
15963
|
+
}
|
|
15801
15964
|
function parsePublishDirVar(raw) {
|
|
15802
15965
|
const v = raw.trim();
|
|
15803
15966
|
if (v === "" || v === ".") {
|
|
@@ -15860,6 +16023,7 @@ var SETTABLE_VAR_KEYS = [
|
|
|
15860
16023
|
"publishRequired",
|
|
15861
16024
|
"publishDir",
|
|
15862
16025
|
"dashboard",
|
|
16026
|
+
"fofuEnabled",
|
|
15863
16027
|
"requiredGcpApis",
|
|
15864
16028
|
"requiredRuntimeSecrets",
|
|
15865
16029
|
"edgeDomains",
|
|
@@ -15878,8 +16042,9 @@ var SETTABLE_VAR_HINTS = {
|
|
|
15878
16042
|
publishRequired: "true|false",
|
|
15879
16043
|
publishDir: "relative subpath, e.g. packages/ui",
|
|
15880
16044
|
dashboard: "true|false",
|
|
16045
|
+
fofuEnabled: "true|false",
|
|
15881
16046
|
repos: 'JSON array, e.g. ["mutmutco/mm-foo"]',
|
|
15882
|
-
oauth: "JSON {subdomains,domains,callbackPath}",
|
|
16047
|
+
oauth: "JSON {subdomains,domains,callbackPath,fofuSubdomain}",
|
|
15883
16048
|
requiredGcpApis: "comma-string",
|
|
15884
16049
|
requiredRuntimeSecrets: 'JSON stage map, e.g. {"dev":["KEY"],"rc":["KEY"],"main":["KEY"]}',
|
|
15885
16050
|
edgeDomains: "JSON {dev,rc,main} domain map",
|
|
@@ -15953,6 +16118,8 @@ function buildProjectSetPatch(input) {
|
|
|
15953
16118
|
patch[key] = parsePublishRequiredVar(raw);
|
|
15954
16119
|
} else if (key === "dashboard") {
|
|
15955
16120
|
patch[key] = parseDashboardVar(raw);
|
|
16121
|
+
} else if (key === "fofuEnabled") {
|
|
16122
|
+
patch[key] = parseFofuEnabledVar(raw);
|
|
15956
16123
|
} else if (key === "publishDir") {
|
|
15957
16124
|
patch[key] = parsePublishDirVar(raw);
|
|
15958
16125
|
} else if (key === "ci") {
|
|
@@ -15981,6 +16148,54 @@ function buildProjectSetPatch(input) {
|
|
|
15981
16148
|
}
|
|
15982
16149
|
return patch;
|
|
15983
16150
|
}
|
|
16151
|
+
var DEPLOY_SUBSTRATES = ["hetzner-ssh"];
|
|
16152
|
+
var DEPLOY_STAGES = ["dev", "rc", "main"];
|
|
16153
|
+
var DEPLOY_DOMAIN_RE = /^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)+$/;
|
|
16154
|
+
var DEPLOY_SHELL_SAFE_RE = /^[A-Za-z0-9./:_?&=%-]*$/;
|
|
16155
|
+
function buildSetDeployPatch(slug, input) {
|
|
16156
|
+
const clean4 = (v) => typeof v === "string" && v.trim() !== "" ? v.trim() : void 0;
|
|
16157
|
+
const stage2 = clean4(input.stage);
|
|
16158
|
+
if (!stage2 || !DEPLOY_STAGES.includes(stage2)) {
|
|
16159
|
+
throw new Error(`project set-deploy: --stage must be one of: ${DEPLOY_STAGES.join(", ")}`);
|
|
16160
|
+
}
|
|
16161
|
+
const substrate = clean4(input.substrate) ?? "hetzner-ssh";
|
|
16162
|
+
if (!DEPLOY_SUBSTRATES.includes(substrate)) {
|
|
16163
|
+
throw new Error(`project set-deploy: --substrate must be one of: ${DEPLOY_SUBSTRATES.join(", ")}`);
|
|
16164
|
+
}
|
|
16165
|
+
const sshHost = clean4(input.sshHost);
|
|
16166
|
+
if (substrate === "hetzner-ssh" && !sshHost) {
|
|
16167
|
+
throw new Error("project set-deploy: hetzner-ssh requires --ssh-host");
|
|
16168
|
+
}
|
|
16169
|
+
const sshUser = clean4(input.sshUser) ?? "root";
|
|
16170
|
+
const deployPath = clean4(input.deployPath) ?? `/opt/mmi/${slug}/${stage2}`;
|
|
16171
|
+
const serviceName = clean4(input.service) ?? slug;
|
|
16172
|
+
for (const [label, v] of [["--ssh-host", sshHost], ["--ssh-user", sshUser], ["--deploy-path", deployPath], ["--service", serviceName]]) {
|
|
16173
|
+
if (v !== void 0 && !DEPLOY_SHELL_SAFE_RE.test(v)) throw new Error(`project set-deploy: ${label} contains unsafe characters`);
|
|
16174
|
+
}
|
|
16175
|
+
let port;
|
|
16176
|
+
if (input.port !== void 0 && input.port !== null && `${input.port}`.trim() !== "") {
|
|
16177
|
+
port = Number(input.port);
|
|
16178
|
+
if (!Number.isInteger(port) || port < 1 || port > 65535) throw new Error("project set-deploy: --port must be an integer 1..65535");
|
|
16179
|
+
}
|
|
16180
|
+
const domain = clean4(input.domain);
|
|
16181
|
+
if (domain !== void 0 && !DEPLOY_DOMAIN_RE.test(domain)) throw new Error(`project set-deploy: --domain must be a DNS hostname, got ${JSON.stringify(input.domain)}`);
|
|
16182
|
+
const aliases = (Array.isArray(input.aliases) ? input.aliases : []).map((a) => clean4(a)).filter((a) => a !== void 0);
|
|
16183
|
+
for (const a of aliases) {
|
|
16184
|
+
if (!DEPLOY_DOMAIN_RE.test(a)) throw new Error(`project set-deploy: --alias must be a DNS hostname, got ${JSON.stringify(a)}`);
|
|
16185
|
+
}
|
|
16186
|
+
const uniqueAliases = [...new Set(aliases)];
|
|
16187
|
+
return {
|
|
16188
|
+
stage: stage2,
|
|
16189
|
+
substrate,
|
|
16190
|
+
sshHost,
|
|
16191
|
+
sshUser,
|
|
16192
|
+
deployPath,
|
|
16193
|
+
serviceName,
|
|
16194
|
+
...domain !== void 0 ? { domain } : {},
|
|
16195
|
+
...port !== void 0 ? { port } : {},
|
|
16196
|
+
...uniqueAliases.length ? { aliases: uniqueAliases } : {}
|
|
16197
|
+
};
|
|
16198
|
+
}
|
|
15984
16199
|
function repoFromRemoteUrl(remoteUrl) {
|
|
15985
16200
|
const m = remoteUrl.trim().match(/^(?:[a-z][a-z0-9+.-]*:\/\/)?(?:[^@\s/]+@)?github\.com[:/]([^/\s:]+)\/([^/\s]+?)(?:\.git)?\/?$/i);
|
|
15986
16201
|
return m ? `${m[1]}/${m[2]}` : void 0;
|
|
@@ -16020,15 +16235,15 @@ function parseKbTree(stdout, prefix) {
|
|
|
16020
16235
|
}
|
|
16021
16236
|
|
|
16022
16237
|
// src/northstar-commands.ts
|
|
16023
|
-
var
|
|
16238
|
+
var import_node_fs22 = require("node:fs");
|
|
16024
16239
|
var import_node_child_process11 = require("node:child_process");
|
|
16025
16240
|
var import_promises6 = require("node:fs/promises");
|
|
16026
16241
|
|
|
16027
16242
|
// src/plan.ts
|
|
16028
|
-
var
|
|
16243
|
+
var import_node_path19 = require("node:path");
|
|
16029
16244
|
var PLANS_DIR = "plans";
|
|
16030
|
-
var META_FILE = (0,
|
|
16031
|
-
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`);
|
|
16032
16247
|
var metaKey = (project2, slug) => `${project2}/${slug}`;
|
|
16033
16248
|
function parseMeta(raw) {
|
|
16034
16249
|
if (!raw) return {};
|
|
@@ -16053,7 +16268,7 @@ function hashContent(s) {
|
|
|
16053
16268
|
function staleHint(slug) {
|
|
16054
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`;
|
|
16055
16270
|
}
|
|
16056
|
-
var INDEX_FILE = (0,
|
|
16271
|
+
var INDEX_FILE = (0, import_node_path19.join)(PLANS_DIR, ".index.json");
|
|
16057
16272
|
var INDEX_TTL_MS = 6e4;
|
|
16058
16273
|
function parseIndex(raw) {
|
|
16059
16274
|
if (!raw) return null;
|
|
@@ -16082,7 +16297,7 @@ function mergeIndex(idx, scope, plans, now) {
|
|
|
16082
16297
|
const mergedScope = idx.scope === null ? null : [.../* @__PURE__ */ new Set([...idx.scope, ...scope])];
|
|
16083
16298
|
return { fetchedAt: now, scope: mergedScope, plans: [...kept, ...plans] };
|
|
16084
16299
|
}
|
|
16085
|
-
var QUEUE_FILE = (0,
|
|
16300
|
+
var QUEUE_FILE = (0, import_node_path19.join)(PLANS_DIR, ".sync-queue.json");
|
|
16086
16301
|
var QUEUE_MAX_ATTEMPTS = 10;
|
|
16087
16302
|
function isValidQueueEntry(e) {
|
|
16088
16303
|
if (!e || typeof e !== "object") return false;
|
|
@@ -16557,7 +16772,7 @@ function detachPlanSync() {
|
|
|
16557
16772
|
}
|
|
16558
16773
|
}
|
|
16559
16774
|
function makePlanDeps(cfg, io = consoleIo) {
|
|
16560
|
-
const ensureDir = () => (0,
|
|
16775
|
+
const ensureDir = () => (0, import_node_fs22.mkdirSync)(PLANS_DIR, { recursive: true });
|
|
16561
16776
|
return {
|
|
16562
16777
|
apiUrl: cfg.sagaApiUrl,
|
|
16563
16778
|
fetch: (url, init = {}) => fetch(url, { ...init, signal: init.signal ?? AbortSignal.timeout(1e4) }),
|
|
@@ -16565,24 +16780,24 @@ function makePlanDeps(cfg, io = consoleIo) {
|
|
|
16565
16780
|
project: async () => (await sagaKey(cfg)).project,
|
|
16566
16781
|
readLocal: (slug) => {
|
|
16567
16782
|
try {
|
|
16568
|
-
return (0,
|
|
16783
|
+
return (0, import_node_fs22.readFileSync)(planPath(slug), "utf8");
|
|
16569
16784
|
} catch {
|
|
16570
16785
|
return null;
|
|
16571
16786
|
}
|
|
16572
16787
|
},
|
|
16573
16788
|
writeLocal: (slug, content) => {
|
|
16574
16789
|
ensureDir();
|
|
16575
|
-
(0,
|
|
16790
|
+
(0, import_node_fs22.writeFileSync)(planPath(slug), content, "utf8");
|
|
16576
16791
|
},
|
|
16577
16792
|
removeLocal: (slug) => {
|
|
16578
16793
|
try {
|
|
16579
|
-
(0,
|
|
16794
|
+
(0, import_node_fs22.rmSync)(planPath(slug));
|
|
16580
16795
|
} catch {
|
|
16581
16796
|
}
|
|
16582
16797
|
},
|
|
16583
16798
|
readMetaRaw: () => {
|
|
16584
16799
|
try {
|
|
16585
|
-
return (0,
|
|
16800
|
+
return (0, import_node_fs22.readFileSync)(META_FILE, "utf8");
|
|
16586
16801
|
} catch {
|
|
16587
16802
|
return null;
|
|
16588
16803
|
}
|
|
@@ -16593,7 +16808,7 @@ function makePlanDeps(cfg, io = consoleIo) {
|
|
|
16593
16808
|
},
|
|
16594
16809
|
readIndexRaw: () => {
|
|
16595
16810
|
try {
|
|
16596
|
-
return (0,
|
|
16811
|
+
return (0, import_node_fs22.readFileSync)(INDEX_FILE, "utf8");
|
|
16597
16812
|
} catch {
|
|
16598
16813
|
return null;
|
|
16599
16814
|
}
|
|
@@ -16604,7 +16819,7 @@ function makePlanDeps(cfg, io = consoleIo) {
|
|
|
16604
16819
|
},
|
|
16605
16820
|
readQueueRaw: () => {
|
|
16606
16821
|
try {
|
|
16607
|
-
return (0,
|
|
16822
|
+
return (0, import_node_fs22.readFileSync)(QUEUE_FILE, "utf8");
|
|
16608
16823
|
} catch {
|
|
16609
16824
|
return null;
|
|
16610
16825
|
}
|
|
@@ -16639,7 +16854,7 @@ async function withPlan(quiet, run, io = consoleIo) {
|
|
|
16639
16854
|
}
|
|
16640
16855
|
await run(makePlanDeps(cfg, io));
|
|
16641
16856
|
}
|
|
16642
|
-
async function gatherRelevanceSignals() {
|
|
16857
|
+
async function gatherRelevanceSignals(opts = {}) {
|
|
16643
16858
|
const branch = await gitOut(["rev-parse", "--abbrev-ref", "HEAD"]).catch(() => "") || void 0;
|
|
16644
16859
|
const changed = (await gitOut(["diff", "--name-only", "HEAD"]).catch(() => "")).split("\n").map((s) => s.trim()).filter(Boolean);
|
|
16645
16860
|
const signals = { branch, changedFiles: changed.length ? changed : void 0 };
|
|
@@ -16657,6 +16872,10 @@ async function gatherRelevanceSignals() {
|
|
|
16657
16872
|
} catch {
|
|
16658
16873
|
}
|
|
16659
16874
|
}
|
|
16875
|
+
if (opts.anchorSlug) {
|
|
16876
|
+
const slug = (await opts.anchorSlug().catch(() => void 0))?.trim();
|
|
16877
|
+
if (slug) signals.anchorSlug = slug;
|
|
16878
|
+
}
|
|
16660
16879
|
return signals;
|
|
16661
16880
|
}
|
|
16662
16881
|
function registerNorthStarCommands(cmd) {
|
|
@@ -16777,9 +16996,7 @@ function registerSecretsCommands(program3) {
|
|
|
16777
16996
|
});
|
|
16778
16997
|
const centralContainer = meta?.deployModel === "tenant-container" || meta?.deployModel === "solo-container";
|
|
16779
16998
|
if (!o.required?.length && centralContainer && meta && !hasRuntimeSecretContract(meta.requiredRuntimeSecrets)) {
|
|
16780
|
-
d.err(
|
|
16781
|
-
process.exitCode = 1;
|
|
16782
|
-
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.');
|
|
16783
17000
|
}
|
|
16784
17001
|
const ok = await secretsPreflight(d, { repo: o.repo, stage: o.stage, required });
|
|
16785
17002
|
if (!ok) process.exitCode = 1;
|
|
@@ -16844,6 +17061,9 @@ function expectedHosts(cfg) {
|
|
|
16844
17061
|
for (const env of ENV_PREFIXES) out.push(env ? `${env}.${base2}` : base2);
|
|
16845
17062
|
}
|
|
16846
17063
|
}
|
|
17064
|
+
if (cfg.fofuSubdomain !== void 0) {
|
|
17065
|
+
out.push(cfg.fofuSubdomain ? `${cfg.fofuSubdomain}.fofu.ai` : "fofu.ai");
|
|
17066
|
+
}
|
|
16847
17067
|
return uniq(out);
|
|
16848
17068
|
}
|
|
16849
17069
|
function expectedJsOrigins(cfg) {
|
|
@@ -16888,7 +17108,10 @@ function parseOauthConfig(mmiConfig, slug) {
|
|
|
16888
17108
|
if (!callbackPath.startsWith("/")) {
|
|
16889
17109
|
throw new Error(`oauth.callbackPath must start with "/" (got ${JSON.stringify(callbackPath)})`);
|
|
16890
17110
|
}
|
|
16891
|
-
|
|
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 };
|
|
16892
17115
|
}
|
|
16893
17116
|
function probeRedirectUri(callbackPath, port = 9123) {
|
|
16894
17117
|
return `http://localhost:${port}${callbackPath}`;
|
|
@@ -17052,7 +17275,7 @@ async function fetchHubVersionInfo(baseUrl) {
|
|
|
17052
17275
|
}
|
|
17053
17276
|
function readRepoVersion() {
|
|
17054
17277
|
try {
|
|
17055
|
-
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;
|
|
17056
17279
|
} catch {
|
|
17057
17280
|
return void 0;
|
|
17058
17281
|
}
|
|
@@ -17227,10 +17450,10 @@ async function runRulesSync(opts, io = consoleIo) {
|
|
|
17227
17450
|
for (const entry of fetched) {
|
|
17228
17451
|
if ("error" in entry) continue;
|
|
17229
17452
|
const { file, source } = entry;
|
|
17230
|
-
const current = (0,
|
|
17453
|
+
const current = (0, import_node_fs23.existsSync)(file) ? await (0, import_promises7.readFile)(file, "utf8") : null;
|
|
17231
17454
|
if (needsUpdate(source, current)) {
|
|
17232
17455
|
const slash = file.lastIndexOf("/");
|
|
17233
|
-
if (slash > 0) (0,
|
|
17456
|
+
if (slash > 0) (0, import_node_fs23.mkdirSync)(file.slice(0, slash), { recursive: true });
|
|
17234
17457
|
await (0, import_promises7.writeFile)(file, normalizeEol(source), "utf8");
|
|
17235
17458
|
changed++;
|
|
17236
17459
|
if (!opts.quiet) io.log(`mmi-cli rules: updated ${file}`);
|
|
@@ -17243,6 +17466,27 @@ var rules = program2.command("rules").description("org rules delivery");
|
|
|
17243
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) => {
|
|
17244
17467
|
if (!await runRulesSync(opts)) process.exitCode = 1;
|
|
17245
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
|
+
});
|
|
17246
17490
|
async function runDocsSync(opts, io = consoleIo) {
|
|
17247
17491
|
const ref = await gitOut(["symbolic-ref", "--quiet", "--short", "refs/remotes/origin/HEAD"]);
|
|
17248
17492
|
const def = (ref.startsWith("origin/") ? ref.slice("origin/".length) : ref) || "development";
|
|
@@ -17256,7 +17500,7 @@ async function runDocsSync(opts, io = consoleIo) {
|
|
|
17256
17500
|
return null;
|
|
17257
17501
|
}
|
|
17258
17502
|
},
|
|
17259
|
-
localContent: async (f) => (0,
|
|
17503
|
+
localContent: async (f) => (0, import_node_fs23.existsSync)(f) ? await (0, import_promises7.readFile)(f, "utf8") : null,
|
|
17260
17504
|
writeDoc: async (f, c) => {
|
|
17261
17505
|
await (0, import_promises7.writeFile)(f, c, "utf8");
|
|
17262
17506
|
}
|
|
@@ -17355,7 +17599,7 @@ function runWorktreeInstall(command, cwd, quiet) {
|
|
|
17355
17599
|
async function primaryCheckoutRoot(worktreeRoot) {
|
|
17356
17600
|
try {
|
|
17357
17601
|
const out = (await execFileP2("git", ["-C", worktreeRoot, "rev-parse", "--path-format=absolute", "--git-common-dir"], { timeout: GIT_TIMEOUT_MS })).stdout.trim();
|
|
17358
|
-
return out ? (0,
|
|
17602
|
+
return out ? (0, import_node_path20.dirname)(out) : void 0;
|
|
17359
17603
|
} catch {
|
|
17360
17604
|
return void 0;
|
|
17361
17605
|
}
|
|
@@ -17368,28 +17612,28 @@ function makeProvisionDeps(worktreeRoot, quiet, log) {
|
|
|
17368
17612
|
};
|
|
17369
17613
|
}
|
|
17370
17614
|
function acquireWorktreeSetupLock(worktreeRoot) {
|
|
17371
|
-
const lockPath = (0,
|
|
17615
|
+
const lockPath = (0, import_node_path20.join)(worktreeRoot, ".mmi", "worktree-setup.lock");
|
|
17372
17616
|
const take = () => {
|
|
17373
|
-
const fd = (0,
|
|
17617
|
+
const fd = (0, import_node_fs23.openSync)(lockPath, "wx");
|
|
17374
17618
|
try {
|
|
17375
|
-
(0,
|
|
17619
|
+
(0, import_node_fs23.writeSync)(fd, String(Date.now()));
|
|
17376
17620
|
} finally {
|
|
17377
|
-
(0,
|
|
17621
|
+
(0, import_node_fs23.closeSync)(fd);
|
|
17378
17622
|
}
|
|
17379
17623
|
return () => {
|
|
17380
17624
|
try {
|
|
17381
|
-
(0,
|
|
17625
|
+
(0, import_node_fs23.rmSync)(lockPath, { force: true });
|
|
17382
17626
|
} catch {
|
|
17383
17627
|
}
|
|
17384
17628
|
};
|
|
17385
17629
|
};
|
|
17386
17630
|
try {
|
|
17387
|
-
(0,
|
|
17631
|
+
(0, import_node_fs23.mkdirSync)((0, import_node_path20.dirname)(lockPath), { recursive: true });
|
|
17388
17632
|
return take();
|
|
17389
17633
|
} catch {
|
|
17390
17634
|
try {
|
|
17391
|
-
if (Date.now() - (0,
|
|
17392
|
-
(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 });
|
|
17393
17637
|
return take();
|
|
17394
17638
|
}
|
|
17395
17639
|
} catch {
|
|
@@ -17729,7 +17973,7 @@ deploys run centrally (tenant-deploy.yml); product repos carry no deploy files.
|
|
|
17729
17973
|
}
|
|
17730
17974
|
});
|
|
17731
17975
|
project.command("resolve <owner/repo>").description("deploy coords for a stage \u2014 for diagnosis. NOTE: /deploy-coords is OIDC-gated (a deploy job\u2019s id-token), so a gh-token CLI cannot read it from a dev machine").option("--stage <main|rc>", "deploy stage", "main").option("--json", "machine-readable output").action((_repoOrRepo, o) => {
|
|
17732
|
-
const msg = "project resolve: deploy coords are served only to a deploy workflow (GitHub OIDC id-token, repo-scoped). A gh-token CLI on a dev machine cannot read /deploy-coords; inspect the DEPLOY# item via
|
|
17976
|
+
const msg = "project resolve: deploy coords are served only to a deploy workflow (GitHub OIDC id-token, repo-scoped). A gh-token CLI on a dev machine cannot read /deploy-coords; inspect the DEPLOY# item via a master registry (DDB) read instead.";
|
|
17733
17977
|
if (o.json) {
|
|
17734
17978
|
console.log(JSON.stringify({ ok: false, stage: o.stage, error: msg }));
|
|
17735
17979
|
process.exitCode = 1;
|
|
@@ -17829,6 +18073,34 @@ project.command("set [owner/repo]").description("upsert project META (idempotent
|
|
|
17829
18073
|
const res = await upsertProject(slug, { ...patch, repo }, registryClientDeps(cfg));
|
|
17830
18074
|
return reportWrite("project set", res);
|
|
17831
18075
|
});
|
|
18076
|
+
project.command("set-deploy [owner/repo]").description("write the DEPLOY#<stage> Hetzner deploy coords for a tenant (master-only) \u2014 the explicit-coords path that seeds a freshly-bootstrapped tenant; defaults to the current repo").requiredOption("--stage <stage>", "dev | rc | main").option("--ssh-host <host>", "the box address the deploy ssh-es into (required for hetzner-ssh)").option("--ssh-user <user>", "ssh user (default root)").option("--port <port>", "loopback port the container binds / Caddy upstream (1..65535)").option("--substrate <substrate>", "hetzner-ssh (default)").option("--deploy-path <path>", "on-box per-stage release root (default /opt/mmi/<slug>/<stage>)").option("--service <name>", "systemd/compose service name (default the slug)").option("--domain <domain>", "canonical serving host (default the project edgeDomains[stage])").option("--alias <domain...>", "extra serving hostname the box Caddy answers (repeatable)").option("--json", "machine-readable output").action(async (repoOrSlug, o) => {
|
|
18077
|
+
const cfg = await loadConfig();
|
|
18078
|
+
let target;
|
|
18079
|
+
try {
|
|
18080
|
+
target = await projectTarget("project set-deploy", repoOrSlug);
|
|
18081
|
+
} catch (e) {
|
|
18082
|
+
return fail(e.message);
|
|
18083
|
+
}
|
|
18084
|
+
const slug = slugOf(target);
|
|
18085
|
+
let body;
|
|
18086
|
+
try {
|
|
18087
|
+
body = buildSetDeployPatch(slug, {
|
|
18088
|
+
stage: o.stage,
|
|
18089
|
+
sshHost: o.sshHost,
|
|
18090
|
+
sshUser: o.sshUser,
|
|
18091
|
+
port: o.port,
|
|
18092
|
+
substrate: o.substrate,
|
|
18093
|
+
deployPath: o.deployPath,
|
|
18094
|
+
service: o.service,
|
|
18095
|
+
domain: o.domain,
|
|
18096
|
+
aliases: o.alias
|
|
18097
|
+
});
|
|
18098
|
+
} catch (e) {
|
|
18099
|
+
return fail(e.message);
|
|
18100
|
+
}
|
|
18101
|
+
const res = await setDeployCoords(slug, body, registryClientDeps(cfg));
|
|
18102
|
+
return reportWrite("project set-deploy", res);
|
|
18103
|
+
});
|
|
17832
18104
|
var registry = program2.command("registry").description("the DDB org registry \u2014 org-level constants");
|
|
17833
18105
|
registry.command("org").description("the org config (account id, region, orgProjectId, sagaApiUrl)").option("--json", "machine-readable output").action(async (_o) => {
|
|
17834
18106
|
const cfg = await loadConfig();
|
|
@@ -18011,6 +18283,46 @@ issue.command("link-child <parent> <child>").description("link an existing issue
|
|
|
18011
18283
|
return fail(`issue link-child: ${(err.stderr || err.message || String(e)).trim()}${note ? ` (${note})` : ""}`);
|
|
18012
18284
|
}
|
|
18013
18285
|
});
|
|
18286
|
+
issue.command("check <ref>").description("tick (or with --off untick) a task-list checkbox in an issue/epic body by its item text and print {number,repo,item,checked,changed} JSON").requiredOption("--item <text>", "the checklist item to match \u2014 exact item text, else a unique substring").option("--off", "untick the item ([x] \u2192 [ ]) instead of ticking it").option("--repo <owner/repo>", "repo for a bare ref (defaults to the current repo)").action(async (ref, o) => {
|
|
18287
|
+
let parsed;
|
|
18288
|
+
try {
|
|
18289
|
+
parsed = parseIssueRef(ref);
|
|
18290
|
+
} catch (e) {
|
|
18291
|
+
return fail(`issue check: ${e.message}`);
|
|
18292
|
+
}
|
|
18293
|
+
const repo = await resolveRepo(parsed.repo ?? o.repo);
|
|
18294
|
+
if (!repo) return fail("issue check: could not resolve repo \u2014 pass --repo owner/repo");
|
|
18295
|
+
const checked = o.off !== true;
|
|
18296
|
+
let body;
|
|
18297
|
+
try {
|
|
18298
|
+
const viewed = await ghJson(["issue", "view", String(parsed.number), "--repo", repo, "--json", "body"]);
|
|
18299
|
+
body = viewed.body ?? "";
|
|
18300
|
+
} catch (e) {
|
|
18301
|
+
return fail(`issue check: could not read ${repo}#${parsed.number}: ${e.message}`);
|
|
18302
|
+
}
|
|
18303
|
+
const result = applyChecklistCheck(body, o.item, checked);
|
|
18304
|
+
if (!result.ok) {
|
|
18305
|
+
if (result.reason === "ambiguous") {
|
|
18306
|
+
const list = result.matches.map((m) => ` - ${m.text}`).join("\n");
|
|
18307
|
+
return fail(`issue check: "${o.item}" matches ${result.matches.length} checklist items in ${repo}#${parsed.number} \u2014 narrow the text:
|
|
18308
|
+
${list}`);
|
|
18309
|
+
}
|
|
18310
|
+
return fail(`issue check: no checklist item matching "${o.item}" in ${repo}#${parsed.number}`);
|
|
18311
|
+
}
|
|
18312
|
+
if (!result.edit.changed) {
|
|
18313
|
+
return console.log(JSON.stringify({ number: parsed.number, repo, item: result.item.text, checked, changed: false }));
|
|
18314
|
+
}
|
|
18315
|
+
const edit = await bodyArgsViaFile(["issue", "edit", String(parsed.number), "--repo", repo, "--body", result.edit.body]);
|
|
18316
|
+
try {
|
|
18317
|
+
await execFileP2("gh", edit.args, { timeout: GH_MUTATION_TIMEOUT_MS });
|
|
18318
|
+
} catch (e) {
|
|
18319
|
+
const note = timeoutKillNote(e, GH_MUTATION_TIMEOUT_MS);
|
|
18320
|
+
return fail(`issue check: edit failed for ${repo}#${parsed.number}: ${e.message}${note ? ` (${note})` : ""}`);
|
|
18321
|
+
} finally {
|
|
18322
|
+
await edit.cleanup();
|
|
18323
|
+
}
|
|
18324
|
+
console.log(JSON.stringify({ number: parsed.number, repo, item: result.item.text, checked, changed: true }));
|
|
18325
|
+
});
|
|
18014
18326
|
program2.command("report").description("file a friction report on the Hub board (GitHub auth, dedups open reports) and print {number,url} JSON").option("--title <title>", "one-line friction summary").option("--title-file <path|->", "read the friction summary from a UTF-8 file, or from stdin with -").option("--body <body>", "report body (markdown)").option("--body-file <path|->", "read report body from a UTF-8 file, or from stdin with -").option("--type <type>", "bug | feature | task (sets the matching label)", "task").option("--priority <priority>", "urgent | high | medium | low (sets the board Priority field only, #416)", "medium").option("--repo <owner/repo>", `target repo (defaults to the org Hub: ${HUB_REPO2})`).option("--force", "file a new issue even when an open report looks like a duplicate").option("--json", "machine-readable output (already the default \u2014 report always prints JSON; #682)").action(async (o) => {
|
|
18015
18327
|
let body;
|
|
18016
18328
|
let priority;
|
|
@@ -18284,9 +18596,9 @@ pr.command("create").description("create a PR and print {number,url} JSON").opti
|
|
|
18284
18596
|
console.log(JSON.stringify(created));
|
|
18285
18597
|
});
|
|
18286
18598
|
async function listCiWorkflowPaths(cwd = process.cwd()) {
|
|
18287
|
-
const wfDir = (0,
|
|
18288
|
-
if (!(0,
|
|
18289
|
-
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}`);
|
|
18290
18602
|
}
|
|
18291
18603
|
async function resolveMergeCiPolicyForCheckout(repoOpt) {
|
|
18292
18604
|
const repo = repoOpt ?? await resolveRepo();
|
|
@@ -18305,7 +18617,7 @@ function ciAuditDeps() {
|
|
|
18305
18617
|
// Continuous CI delivery (#1550): the gate re-seed renders from the Hub's on-disk seed templates. The
|
|
18306
18618
|
// reconcile runs IN the Hub checkout, so this is local-file I/O (no network fetch). Path is relative to
|
|
18307
18619
|
// the repo root (e.g. skills/bootstrap/seeds/gate.template.yml).
|
|
18308
|
-
readSeedFile: (path2) => (0,
|
|
18620
|
+
readSeedFile: (path2) => (0, import_node_fs23.existsSync)(path2) ? (0, import_node_fs23.readFileSync)(path2, "utf8") : null
|
|
18309
18621
|
};
|
|
18310
18622
|
}
|
|
18311
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) => {
|
|
@@ -18441,7 +18753,7 @@ async function createDeferredWorktreeStore() {
|
|
|
18441
18753
|
},
|
|
18442
18754
|
write: async (entries) => {
|
|
18443
18755
|
try {
|
|
18444
|
-
await (0, import_promises7.mkdir)((0,
|
|
18756
|
+
await (0, import_promises7.mkdir)((0, import_node_path20.dirname)(registryPath), { recursive: true });
|
|
18445
18757
|
await (0, import_promises7.writeFile)(registryPath, serializeDeferredWorktrees(entries), "utf8");
|
|
18446
18758
|
} catch {
|
|
18447
18759
|
}
|
|
@@ -18455,13 +18767,13 @@ var realWorktreeDirRemover = {
|
|
|
18455
18767
|
probe: (p) => {
|
|
18456
18768
|
let st;
|
|
18457
18769
|
try {
|
|
18458
|
-
st = (0,
|
|
18770
|
+
st = (0, import_node_fs23.lstatSync)(p);
|
|
18459
18771
|
} catch {
|
|
18460
18772
|
return null;
|
|
18461
18773
|
}
|
|
18462
18774
|
if (st.isSymbolicLink()) return "link";
|
|
18463
18775
|
try {
|
|
18464
|
-
(0,
|
|
18776
|
+
(0, import_node_fs23.readlinkSync)(p);
|
|
18465
18777
|
return "link";
|
|
18466
18778
|
} catch {
|
|
18467
18779
|
}
|
|
@@ -18469,7 +18781,7 @@ var realWorktreeDirRemover = {
|
|
|
18469
18781
|
},
|
|
18470
18782
|
readdir: (p) => {
|
|
18471
18783
|
try {
|
|
18472
|
-
return (0,
|
|
18784
|
+
return (0, import_node_fs23.readdirSync)(p);
|
|
18473
18785
|
} catch {
|
|
18474
18786
|
return [];
|
|
18475
18787
|
}
|
|
@@ -18478,9 +18790,9 @@ var realWorktreeDirRemover = {
|
|
|
18478
18790
|
// leaving the target); a file symlink with unlink. rmdir first, fall back to unlink.
|
|
18479
18791
|
detachLink: (p) => {
|
|
18480
18792
|
try {
|
|
18481
|
-
(0,
|
|
18793
|
+
(0, import_node_fs23.rmdirSync)(p);
|
|
18482
18794
|
} catch {
|
|
18483
|
-
(0,
|
|
18795
|
+
(0, import_node_fs23.unlinkSync)(p);
|
|
18484
18796
|
}
|
|
18485
18797
|
},
|
|
18486
18798
|
removeTree: (p) => (0, import_promises7.rm)(p, { recursive: true, force: true, maxRetries: 5, retryDelay: 200 })
|
|
@@ -18501,7 +18813,7 @@ function worktreeRemoveDeps(execGit) {
|
|
|
18501
18813
|
}
|
|
18502
18814
|
function teardownWorktreeStage(worktreePath) {
|
|
18503
18815
|
return runWorktreeStageTeardown(worktreePath, {
|
|
18504
|
-
hasStageState: (wt) => (0,
|
|
18816
|
+
hasStageState: (wt) => (0, import_node_fs23.existsSync)(stageStatePath(wt)),
|
|
18505
18817
|
stopRecordedStage: async (wt) => (await stopStage({ cwd: wt })).pid,
|
|
18506
18818
|
listComposeProjects: async () => {
|
|
18507
18819
|
const { stdout } = await execFileP2("docker", ["compose", "ls", "--all", "--format", "json"], { timeout: GC_GH_TIMEOUT_MS });
|
|
@@ -18564,7 +18876,7 @@ pr.command("merge <number>").description("merge a PR (squash by default) and cle
|
|
|
18564
18876
|
} : await cleanupPrMergeLocalBranch(headRef, {
|
|
18565
18877
|
beforeWorktrees,
|
|
18566
18878
|
startingPath,
|
|
18567
|
-
pathExists: (p) => (0,
|
|
18879
|
+
pathExists: (p) => (0, import_node_fs23.existsSync)(p),
|
|
18568
18880
|
execGit: async (args) => (await execFileP2("git", args, { timeout: GIT_TIMEOUT_MS })).stdout,
|
|
18569
18881
|
teardownWorktreeStage,
|
|
18570
18882
|
deferredStore,
|
|
@@ -18607,6 +18919,9 @@ async function runBoardRead(o) {
|
|
|
18607
18919
|
}
|
|
18608
18920
|
var board = program2.command("board").description("read, claim, show, and move Project v2 work items for the current repo");
|
|
18609
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
|
+
});
|
|
18610
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) => {
|
|
18611
18926
|
if (issueRefs.length === 1) {
|
|
18612
18927
|
const issueRef = issueRefs[0];
|
|
@@ -18739,7 +19054,7 @@ function rawValues(flag) {
|
|
|
18739
19054
|
return out;
|
|
18740
19055
|
}
|
|
18741
19056
|
function printLine(value) {
|
|
18742
|
-
(0,
|
|
19057
|
+
(0, import_node_fs23.writeSync)(1, `${value}
|
|
18743
19058
|
`);
|
|
18744
19059
|
}
|
|
18745
19060
|
function stageKeepAlive() {
|
|
@@ -18756,8 +19071,8 @@ async function resolveStage() {
|
|
|
18756
19071
|
local,
|
|
18757
19072
|
shell: shellFor(),
|
|
18758
19073
|
registry: { deployModel: project2?.deployModel, portRange, error: read.ok ? void 0 : read.error },
|
|
18759
|
-
hasCompose: (0,
|
|
18760
|
-
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"))
|
|
18761
19076
|
});
|
|
18762
19077
|
}
|
|
18763
19078
|
async function fetchStageVaultEnvMerge() {
|
|
@@ -18809,9 +19124,9 @@ program2.command("port-range <repo>").description("assign (idempotently) + print
|
|
|
18809
19124
|
printLine(o.json ? JSON.stringify({ repo, portRange: [start2, end2], source: "meta" }) : `${repo}: stage.portRange [${start2}, ${end2}]`);
|
|
18810
19125
|
return;
|
|
18811
19126
|
}
|
|
18812
|
-
const path2 = (0,
|
|
19127
|
+
const path2 = (0, import_node_path20.join)(process.cwd(), "infra", "port-ranges.json");
|
|
18813
19128
|
const allocate = async (seed) => {
|
|
18814
|
-
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 });
|
|
18815
19130
|
const parsed = JSON.parse(stdout);
|
|
18816
19131
|
if (!Array.isArray(parsed.range) || parsed.range.length !== 2) throw new Error("port-ddb: no range in output");
|
|
18817
19132
|
return parsed.range;
|
|
@@ -18831,7 +19146,11 @@ async function stageLiveTarget() {
|
|
|
18831
19146
|
const repo = await resolveRepo();
|
|
18832
19147
|
if (!repo) throw new Error("stage --live: cannot resolve the current repo (run inside a GitHub-remoted checkout)");
|
|
18833
19148
|
const ref = await gitOut(["rev-parse", "--abbrev-ref", "HEAD"]).catch(() => "") || void 0;
|
|
18834
|
-
|
|
19149
|
+
const slug = slugOf(repo);
|
|
19150
|
+
const meta = await fetchProjectBySlug(slug, registryClientDeps(await loadConfig())).catch(() => null);
|
|
19151
|
+
const host = edgeDomainsByStage(meta).dev ?? `dev.${defaultSubdomain(slug)}.mutatismutandis.co`;
|
|
19152
|
+
if (!host.trim()) throw new Error(`stage --live: no dev edge host for ${slug} (registry edgeDomains.dev)`);
|
|
19153
|
+
return { slug, repo, host, ref };
|
|
18835
19154
|
}
|
|
18836
19155
|
async function runStageLiveCommand(o) {
|
|
18837
19156
|
let target;
|
|
@@ -19202,7 +19521,7 @@ bootstrap.command("verify <repo>").description("audit whether an existing repo i
|
|
|
19202
19521
|
client: defaultGitHubClient(),
|
|
19203
19522
|
projectMeta: meta,
|
|
19204
19523
|
deployModel: typeof meta?.deployModel === "string" ? meta.deployModel : void 0,
|
|
19205
|
-
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,
|
|
19206
19525
|
// requiredGcpApis is stored as an array by a JSON write, but `project set --var KEY=VALUE` stores a raw
|
|
19207
19526
|
// comma-string — accept either so the seeded value verifies regardless of how it was written.
|
|
19208
19527
|
requiredGcpApis: (() => {
|
|
@@ -19246,12 +19565,12 @@ bootstrap.command("apply <repo>").description("idempotent seed apply from skills
|
|
|
19246
19565
|
return fail(`bootstrap apply: ${e.message}`);
|
|
19247
19566
|
}
|
|
19248
19567
|
const manifestPath = "skills/bootstrap/seeds/manifest.json";
|
|
19249
|
-
if (!(0,
|
|
19250
|
-
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"));
|
|
19251
19570
|
const baseBranch = o.class === "content" ? "main" : "development";
|
|
19252
19571
|
const slug = parsedRepo.slug;
|
|
19253
19572
|
const gh = async (args) => execFileP2("gh", args, { timeout: 2e4 });
|
|
19254
|
-
const readFile7 = (p) => (0,
|
|
19573
|
+
const readFile7 = (p) => (0, import_node_fs23.existsSync)(p) ? (0, import_node_fs23.readFileSync)(p, "utf8") : null;
|
|
19255
19574
|
const enc2 = (p) => p.split("/").map(encodeURIComponent).join("/");
|
|
19256
19575
|
const rawVars = {};
|
|
19257
19576
|
for (const value of rawValues("--var")) {
|
|
@@ -19502,16 +19821,16 @@ access.command("audit").description("audit collaborator roles + train-branch pus
|
|
|
19502
19821
|
if (o.class !== "deployable" && o.class !== "content") return failGraceful("access audit: --class must be deployable or content");
|
|
19503
19822
|
targets = [{ repo: o.repo, class: o.class }];
|
|
19504
19823
|
} else {
|
|
19505
|
-
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;
|
|
19506
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>");
|
|
19507
|
-
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;
|
|
19508
19827
|
targets = loadAccessTargets(projectsJson, fanoutJson);
|
|
19509
19828
|
}
|
|
19510
19829
|
const derivedMatrix = registryProjects ? accessMatrixFromProjects(registryProjects) : {};
|
|
19511
|
-
const fileMatrix = (0,
|
|
19830
|
+
const fileMatrix = (0, import_node_fs23.existsSync)("access-matrix.json") ? loadAccessMatrix((0, import_node_fs23.readFileSync)("access-matrix.json", "utf8")) : {};
|
|
19512
19831
|
const matrix = mergeAccessMatrix(fileMatrix, derivedMatrix);
|
|
19513
19832
|
const derivedContracts = registryProjects ? dataAccessContractsFromProjects(registryProjects) : { consumers: {} };
|
|
19514
|
-
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: {} };
|
|
19515
19834
|
const dataAccess = mergeDataAccessContracts(fileContracts, derivedContracts);
|
|
19516
19835
|
const report = await auditOrgAccess(targets, deps, matrix, dataAccess);
|
|
19517
19836
|
console.log(o.json ? JSON.stringify(report, null, 2) : renderAccessReport(report));
|
|
@@ -19521,20 +19840,20 @@ access.command("capabilities").description("enumerate your effective vault reach
|
|
|
19521
19840
|
var isWin = process.platform === "win32";
|
|
19522
19841
|
var installedPluginsPath = (surface = detectSurface(process.env)) => {
|
|
19523
19842
|
const homeDir = surface === "codex" ? ".codex" : ".claude";
|
|
19524
|
-
return (0,
|
|
19843
|
+
return (0, import_node_path20.join)((0, import_node_os6.homedir)(), homeDir, "plugins", "installed_plugins.json");
|
|
19525
19844
|
};
|
|
19526
19845
|
function readInstalledPlugins() {
|
|
19527
19846
|
try {
|
|
19528
|
-
return JSON.parse((0,
|
|
19847
|
+
return JSON.parse((0, import_node_fs23.readFileSync)(installedPluginsPath(), "utf8"));
|
|
19529
19848
|
} catch {
|
|
19530
19849
|
return null;
|
|
19531
19850
|
}
|
|
19532
19851
|
}
|
|
19533
19852
|
function installedPluginSources() {
|
|
19534
19853
|
return ["claude", "codex"].map((surface) => {
|
|
19535
|
-
const recordPath = (0,
|
|
19854
|
+
const recordPath = (0, import_node_path20.join)((0, import_node_os6.homedir)(), `.${surface}`, "plugins", "installed_plugins.json");
|
|
19536
19855
|
try {
|
|
19537
|
-
return { surface, installed: JSON.parse((0,
|
|
19856
|
+
return { surface, installed: JSON.parse((0, import_node_fs23.readFileSync)(recordPath, "utf8")), recordPath };
|
|
19538
19857
|
} catch {
|
|
19539
19858
|
return { surface, installed: null, recordPath };
|
|
19540
19859
|
}
|
|
@@ -19542,7 +19861,7 @@ function installedPluginSources() {
|
|
|
19542
19861
|
}
|
|
19543
19862
|
function readClaudeSettings() {
|
|
19544
19863
|
try {
|
|
19545
|
-
return JSON.parse((0,
|
|
19864
|
+
return JSON.parse((0, import_node_fs23.readFileSync)((0, import_node_path20.join)(process.cwd(), ".claude", "settings.json"), "utf8"));
|
|
19546
19865
|
} catch {
|
|
19547
19866
|
return null;
|
|
19548
19867
|
}
|
|
@@ -19564,7 +19883,7 @@ function writeProjectInstallRecord(record) {
|
|
|
19564
19883
|
const list = file.plugins[MMI_PLUGIN_ID] ?? [];
|
|
19565
19884
|
list.push(record);
|
|
19566
19885
|
file.plugins[MMI_PLUGIN_ID] = list;
|
|
19567
|
-
(0,
|
|
19886
|
+
(0, import_node_fs23.writeFileSync)(installedPluginsPath(), `${JSON.stringify(file, null, 2)}
|
|
19568
19887
|
`, "utf8");
|
|
19569
19888
|
return true;
|
|
19570
19889
|
} catch {
|
|
@@ -19577,9 +19896,9 @@ function backupAndWriteInstalledPlugins(records, pluginId) {
|
|
|
19577
19896
|
if (!file) return false;
|
|
19578
19897
|
if (!file.plugins) file.plugins = {};
|
|
19579
19898
|
const path2 = installedPluginsPath();
|
|
19580
|
-
(0,
|
|
19899
|
+
(0, import_node_fs23.copyFileSync)(path2, `${path2}.bak`);
|
|
19581
19900
|
file.plugins[pluginId] = records;
|
|
19582
|
-
(0,
|
|
19901
|
+
(0, import_node_fs23.writeFileSync)(path2, `${JSON.stringify(file, null, 2)}
|
|
19583
19902
|
`, "utf8");
|
|
19584
19903
|
return true;
|
|
19585
19904
|
} catch {
|
|
@@ -19587,35 +19906,35 @@ function backupAndWriteInstalledPlugins(records, pluginId) {
|
|
|
19587
19906
|
}
|
|
19588
19907
|
}
|
|
19589
19908
|
function cursorPluginCacheRoot() {
|
|
19590
|
-
return (0,
|
|
19909
|
+
return (0, import_node_path20.join)((0, import_node_os6.homedir)(), ".cursor", "plugins", "cache", "mutmutco", "mmi");
|
|
19591
19910
|
}
|
|
19592
19911
|
function cursorPluginCachePinSnapshots() {
|
|
19593
19912
|
const root = cursorPluginCacheRoot();
|
|
19594
19913
|
try {
|
|
19595
|
-
return (0,
|
|
19596
|
-
const path2 = (0,
|
|
19597
|
-
const pluginJson = (0,
|
|
19598
|
-
const hooksJson = (0,
|
|
19599
|
-
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");
|
|
19600
19919
|
let version;
|
|
19601
19920
|
try {
|
|
19602
|
-
const raw = JSON.parse((0,
|
|
19921
|
+
const raw = JSON.parse((0, import_node_fs23.readFileSync)(pluginJson, "utf8"));
|
|
19603
19922
|
version = typeof raw.version === "string" ? raw.version : void 0;
|
|
19604
19923
|
} catch {
|
|
19605
19924
|
version = void 0;
|
|
19606
19925
|
}
|
|
19607
19926
|
let isEmpty = true;
|
|
19608
19927
|
try {
|
|
19609
|
-
isEmpty = (0,
|
|
19928
|
+
isEmpty = (0, import_node_fs23.readdirSync)(path2).length === 0;
|
|
19610
19929
|
} catch {
|
|
19611
19930
|
isEmpty = true;
|
|
19612
19931
|
}
|
|
19613
19932
|
return {
|
|
19614
19933
|
name: entry.name,
|
|
19615
19934
|
path: path2,
|
|
19616
|
-
hasPluginJson: (0,
|
|
19617
|
-
hasHooksJson: (0,
|
|
19618
|
-
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),
|
|
19619
19938
|
isEmpty,
|
|
19620
19939
|
version
|
|
19621
19940
|
};
|
|
@@ -19625,19 +19944,19 @@ function cursorPluginCachePinSnapshots() {
|
|
|
19625
19944
|
}
|
|
19626
19945
|
}
|
|
19627
19946
|
function hubCheckoutForCursorSeed() {
|
|
19628
|
-
const manifest = (0,
|
|
19629
|
-
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;
|
|
19630
19949
|
}
|
|
19631
19950
|
function mmiPluginCacheRootSnapshots() {
|
|
19632
19951
|
const roots = [
|
|
19633
|
-
{ surface: "claude", root: (0,
|
|
19634
|
-
{ 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") }
|
|
19635
19954
|
];
|
|
19636
19955
|
return roots.flatMap(({ surface, root }) => {
|
|
19637
19956
|
try {
|
|
19638
|
-
const entries = (0,
|
|
19957
|
+
const entries = (0, import_node_fs23.readdirSync)(root, { withFileTypes: true }).map((entry) => ({
|
|
19639
19958
|
name: entry.name,
|
|
19640
|
-
path: (0,
|
|
19959
|
+
path: (0, import_node_path20.join)(root, entry.name),
|
|
19641
19960
|
isDirectory: entry.isDirectory()
|
|
19642
19961
|
}));
|
|
19643
19962
|
return [{ surface, root, entries }];
|
|
@@ -19648,7 +19967,7 @@ function mmiPluginCacheRootSnapshots() {
|
|
|
19648
19967
|
}
|
|
19649
19968
|
function hasNestedMmiChild(versionDir) {
|
|
19650
19969
|
try {
|
|
19651
|
-
return (0,
|
|
19970
|
+
return (0, import_node_fs23.statSync)((0, import_node_path20.join)(versionDir, "mmi")).isDirectory();
|
|
19652
19971
|
} catch {
|
|
19653
19972
|
return false;
|
|
19654
19973
|
}
|
|
@@ -19659,10 +19978,10 @@ function nestedPluginTreeSnapshot() {
|
|
|
19659
19978
|
);
|
|
19660
19979
|
}
|
|
19661
19980
|
function uniqueQuarantineTarget(path2) {
|
|
19662
|
-
if (!(0,
|
|
19981
|
+
if (!(0, import_node_fs23.existsSync)(path2)) return path2;
|
|
19663
19982
|
for (let i = 1; i < 100; i += 1) {
|
|
19664
19983
|
const candidate = `${path2}-${i}`;
|
|
19665
|
-
if (!(0,
|
|
19984
|
+
if (!(0, import_node_fs23.existsSync)(candidate)) return candidate;
|
|
19666
19985
|
}
|
|
19667
19986
|
return `${path2}-${Date.now()}`;
|
|
19668
19987
|
}
|
|
@@ -19671,10 +19990,10 @@ function quarantinePluginCacheDirs(plan2) {
|
|
|
19671
19990
|
const failed = [];
|
|
19672
19991
|
for (const move of plan2) {
|
|
19673
19992
|
try {
|
|
19674
|
-
if (!(0,
|
|
19993
|
+
if (!(0, import_node_fs23.existsSync)(move.from)) continue;
|
|
19675
19994
|
const target = uniqueQuarantineTarget(move.to);
|
|
19676
|
-
(0,
|
|
19677
|
-
(0,
|
|
19995
|
+
(0, import_node_fs23.mkdirSync)((0, import_node_path20.dirname)(target), { recursive: true });
|
|
19996
|
+
(0, import_node_fs23.renameSync)(move.from, target);
|
|
19678
19997
|
moved += 1;
|
|
19679
19998
|
} catch {
|
|
19680
19999
|
failed.push(move);
|
|
@@ -19693,23 +20012,23 @@ async function robocopyMirrorEmpty(emptyDir, target) {
|
|
|
19693
20012
|
}
|
|
19694
20013
|
async function clearNestedPluginTreeDir(targetPath) {
|
|
19695
20014
|
try {
|
|
19696
|
-
if (!(0,
|
|
20015
|
+
if (!(0, import_node_fs23.existsSync)(targetPath)) return true;
|
|
19697
20016
|
if (isWin) {
|
|
19698
|
-
const emptyDir = (0,
|
|
19699
|
-
(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 });
|
|
19700
20019
|
try {
|
|
19701
20020
|
await robocopyMirrorEmpty(emptyDir, targetPath);
|
|
19702
|
-
(0,
|
|
20021
|
+
(0, import_node_fs23.rmSync)(targetPath, { recursive: true, force: true });
|
|
19703
20022
|
} finally {
|
|
19704
20023
|
try {
|
|
19705
|
-
(0,
|
|
20024
|
+
(0, import_node_fs23.rmSync)(emptyDir, { recursive: true, force: true });
|
|
19706
20025
|
} catch {
|
|
19707
20026
|
}
|
|
19708
20027
|
}
|
|
19709
|
-
return !(0,
|
|
20028
|
+
return !(0, import_node_fs23.existsSync)(targetPath);
|
|
19710
20029
|
}
|
|
19711
|
-
(0,
|
|
19712
|
-
return !(0,
|
|
20030
|
+
(0, import_node_fs23.rmSync)(targetPath, { recursive: true, force: true });
|
|
20031
|
+
return !(0, import_node_fs23.existsSync)(targetPath);
|
|
19713
20032
|
} catch {
|
|
19714
20033
|
return false;
|
|
19715
20034
|
}
|
|
@@ -19722,11 +20041,11 @@ async function applyNestedPluginTreeCleanup(paths, log) {
|
|
|
19722
20041
|
}
|
|
19723
20042
|
return true;
|
|
19724
20043
|
}
|
|
19725
|
-
var gitignorePath = () => (0,
|
|
20044
|
+
var gitignorePath = () => (0, import_node_path20.join)(process.cwd(), ".gitignore");
|
|
19726
20045
|
function readTextFile(path2) {
|
|
19727
20046
|
try {
|
|
19728
|
-
if (!(0,
|
|
19729
|
-
return (0,
|
|
20047
|
+
if (!(0, import_node_fs23.existsSync)(path2)) return null;
|
|
20048
|
+
return (0, import_node_fs23.readFileSync)(path2, "utf8");
|
|
19730
20049
|
} catch {
|
|
19731
20050
|
return null;
|
|
19732
20051
|
}
|
|
@@ -19735,9 +20054,9 @@ function playwrightMcpConfigSnapshots() {
|
|
|
19735
20054
|
const cwd = process.cwd();
|
|
19736
20055
|
const home = (0, import_node_os6.homedir)();
|
|
19737
20056
|
const candidates = [
|
|
19738
|
-
(0,
|
|
19739
|
-
(0,
|
|
19740
|
-
(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")
|
|
19741
20060
|
];
|
|
19742
20061
|
const out = [];
|
|
19743
20062
|
for (const path2 of candidates) {
|
|
@@ -19750,7 +20069,7 @@ function strayBrowserArtifactPaths() {
|
|
|
19750
20069
|
const cwd = process.cwd();
|
|
19751
20070
|
return STRAY_BROWSER_ARTIFACT_DIRS.filter((rel) => {
|
|
19752
20071
|
try {
|
|
19753
|
-
return (0,
|
|
20072
|
+
return (0, import_node_fs23.existsSync)((0, import_node_path20.join)(cwd, rel));
|
|
19754
20073
|
} catch {
|
|
19755
20074
|
return false;
|
|
19756
20075
|
}
|
|
@@ -19758,14 +20077,14 @@ function strayBrowserArtifactPaths() {
|
|
|
19758
20077
|
}
|
|
19759
20078
|
function readGitignore() {
|
|
19760
20079
|
try {
|
|
19761
|
-
return (0,
|
|
20080
|
+
return (0, import_node_fs23.readFileSync)(gitignorePath(), "utf8");
|
|
19762
20081
|
} catch {
|
|
19763
20082
|
return null;
|
|
19764
20083
|
}
|
|
19765
20084
|
}
|
|
19766
20085
|
function writeGitignore(content) {
|
|
19767
20086
|
try {
|
|
19768
|
-
(0,
|
|
20087
|
+
(0, import_node_fs23.writeFileSync)(gitignorePath(), content, "utf8");
|
|
19769
20088
|
return true;
|
|
19770
20089
|
} catch {
|
|
19771
20090
|
return false;
|
|
@@ -19804,7 +20123,7 @@ async function runDoctor(opts, io = consoleIo) {
|
|
|
19804
20123
|
let onPath = pathProbe;
|
|
19805
20124
|
if (!onPath) {
|
|
19806
20125
|
const root = process.env.CLAUDE_PLUGIN_ROOT;
|
|
19807
|
-
if (root && (0,
|
|
20126
|
+
if (root && (0, import_node_fs23.existsSync)(`${root}/bin/mmi-cli${isWin ? ".cmd" : ""}`)) onPath = true;
|
|
19808
20127
|
}
|
|
19809
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" });
|
|
19810
20129
|
const surface = detectSurface(process.env);
|
|
@@ -19837,9 +20156,10 @@ async function runDoctor(opts, io = consoleIo) {
|
|
|
19837
20156
|
}
|
|
19838
20157
|
checks.push({ ok: cloneOk, label: "plugin git clone (SSH\u2192HTTPS rewrite)", fix: CLONE_FIX });
|
|
19839
20158
|
const installed = readInstalledPlugins();
|
|
20159
|
+
const claudeSettings = readClaudeSettings();
|
|
19840
20160
|
let pluginCheck = buildPluginInstallRecordCheck({
|
|
19841
20161
|
isOrgRepo: Boolean(cfg.sagaApiUrl),
|
|
19842
|
-
settings:
|
|
20162
|
+
settings: claudeSettings,
|
|
19843
20163
|
installed,
|
|
19844
20164
|
projectPath: process.cwd(),
|
|
19845
20165
|
mirrorFrom: existingMirrorRecord(installed),
|
|
@@ -19852,6 +20172,7 @@ async function runDoctor(opts, io = consoleIo) {
|
|
|
19852
20172
|
}
|
|
19853
20173
|
}
|
|
19854
20174
|
checks.push(pluginCheck);
|
|
20175
|
+
checks.push(buildSettingsPluginDriftCheck({ isOrgRepo: Boolean(cfg.sagaApiUrl), settings: claudeSettings }));
|
|
19855
20176
|
let legacyPluginCheck = buildLegacyPluginInstallCheck({
|
|
19856
20177
|
isOrgRepo: Boolean(cfg.sagaApiUrl),
|
|
19857
20178
|
sources: installedPluginSources(),
|
|
@@ -20006,7 +20327,7 @@ async function runDoctor(opts, io = consoleIo) {
|
|
|
20006
20327
|
isOrgRepo: Boolean(cfg.sagaApiUrl),
|
|
20007
20328
|
surface,
|
|
20008
20329
|
cacheRoot: cursorCacheRoot,
|
|
20009
|
-
cacheRootExists: (0,
|
|
20330
|
+
cacheRootExists: (0, import_node_fs23.existsSync)(cursorCacheRoot),
|
|
20010
20331
|
pins: cursorPins,
|
|
20011
20332
|
hubCheckout: hubCheckoutForCursorSeed(),
|
|
20012
20333
|
releasedVersion
|
|
@@ -20017,7 +20338,7 @@ async function runDoctor(opts, io = consoleIo) {
|
|
|
20017
20338
|
releasedVersion,
|
|
20018
20339
|
hubCheckout: hubCheckoutForCursorSeed(),
|
|
20019
20340
|
execFileP: execFileP2,
|
|
20020
|
-
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)),
|
|
20021
20342
|
log: (m) => io.err(m)
|
|
20022
20343
|
});
|
|
20023
20344
|
if (seeded) {
|
|
@@ -20026,7 +20347,7 @@ async function runDoctor(opts, io = consoleIo) {
|
|
|
20026
20347
|
isOrgRepo: Boolean(cfg.sagaApiUrl),
|
|
20027
20348
|
surface,
|
|
20028
20349
|
cacheRoot: cursorCacheRoot,
|
|
20029
|
-
cacheRootExists: (0,
|
|
20350
|
+
cacheRootExists: (0, import_node_fs23.existsSync)(cursorCacheRoot),
|
|
20030
20351
|
pins: cursorPins,
|
|
20031
20352
|
hubCheckout: hubCheckoutForCursorSeed(),
|
|
20032
20353
|
releasedVersion
|
|
@@ -20231,7 +20552,15 @@ program2.command("session-start").description("run the SessionStart verbs (rules
|
|
|
20231
20552
|
northstarInjected = await runNorthstarContext(io, {
|
|
20232
20553
|
loadPlans: () => scopedPlanList(planDeps),
|
|
20233
20554
|
readLocal: (slug) => planDeps.readLocal(slug),
|
|
20234
|
-
|
|
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
|
+
})
|
|
20235
20564
|
});
|
|
20236
20565
|
},
|
|
20237
20566
|
sagaHealth: (io) => runSagaHealth({ banner: true, quiet: true }, io),
|
|
@@ -20247,12 +20576,16 @@ program2.command("session-start").description("run the SessionStart verbs (rules
|
|
|
20247
20576
|
},
|
|
20248
20577
|
boardSlice: (io) => runBoardSlice(io, {
|
|
20249
20578
|
loadConfig: () => loadConfigForRepo(),
|
|
20250
|
-
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] })
|
|
20251
20583
|
}),
|
|
20252
20584
|
doctor: (io) => runDoctor({ banner: true }, io)
|
|
20253
20585
|
});
|
|
20254
20586
|
await runSessionStart(parallel, sequential, consoleIo);
|
|
20255
20587
|
consoleIo.log(northstarPointer(northstarInjected));
|
|
20588
|
+
consoleIo.log(kbPointer());
|
|
20256
20589
|
for (const line of planStoreLines(process.cwd())) consoleIo.log(line);
|
|
20257
20590
|
for (const line of scratchGcLines(process.cwd())) consoleIo.log(line);
|
|
20258
20591
|
const worktreeBanner = worktreeAutoProvisionBanner(process.cwd());
|