@mutmutco/cli 2.31.0 → 2.32.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +1 -1
- package/dist/main.cjs +524 -92
- package/dist/saga.cjs +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -62,7 +62,7 @@ function setInjectedStdin(payload) {
|
|
|
62
62
|
}
|
|
63
63
|
async function readStdin() {
|
|
64
64
|
if (injectedStdin !== void 0) return injectedStdin;
|
|
65
|
-
if (process.stdin.isTTY) return "";
|
|
65
|
+
if (process.stdin.isTTY !== false) return "";
|
|
66
66
|
const chunks = [];
|
|
67
67
|
for await (const chunk of process.stdin) chunks.push(chunk);
|
|
68
68
|
return Buffer.concat(chunks).toString("utf8");
|
package/dist/main.cjs
CHANGED
|
@@ -4112,7 +4112,7 @@ async function hubAuthToken(deps) {
|
|
|
4112
4112
|
var injectedStdin;
|
|
4113
4113
|
async function readStdin() {
|
|
4114
4114
|
if (injectedStdin !== void 0) return injectedStdin;
|
|
4115
|
-
if (process.stdin.isTTY) return "";
|
|
4115
|
+
if (process.stdin.isTTY !== false) return "";
|
|
4116
4116
|
const chunks = [];
|
|
4117
4117
|
for await (const chunk of process.stdin) chunks.push(chunk);
|
|
4118
4118
|
return Buffer.concat(chunks).toString("utf8");
|
|
@@ -5257,6 +5257,15 @@ function buildIngestPayload(args) {
|
|
|
5257
5257
|
|
|
5258
5258
|
// src/honcho-client.ts
|
|
5259
5259
|
var HONCHO_ASSISTANT_PEER = "assistant";
|
|
5260
|
+
function parseHonchoQueueStatus(json) {
|
|
5261
|
+
const o = json && typeof json === "object" ? json : {};
|
|
5262
|
+
const pendingRaw = o.pending_work_units ?? o.depth ?? o.queue_depth ?? o.pending;
|
|
5263
|
+
const inProgressRaw = o.in_progress_work_units;
|
|
5264
|
+
const pending = typeof pendingRaw === "number" ? pendingRaw : null;
|
|
5265
|
+
const inProgress = typeof inProgressRaw === "number" ? inProgressRaw : null;
|
|
5266
|
+
const stalled = (pending ?? 0) > 0 && (inProgress ?? 0) === 0;
|
|
5267
|
+
return { pending, inProgress, stalled };
|
|
5268
|
+
}
|
|
5260
5269
|
var enc = encodeURIComponent;
|
|
5261
5270
|
var base = (apiUrl) => apiUrl.replace(/\/+$/, "");
|
|
5262
5271
|
var honchoRoutes = {
|
|
@@ -5323,20 +5332,28 @@ async function fetchPeerCard(cfg, peer, opts = {}) {
|
|
|
5323
5332
|
try {
|
|
5324
5333
|
const path2 = honchoRoutes.peerCard(cfg.workspace, peer, opts.project);
|
|
5325
5334
|
const res = await request(cfg, fetchImpl, "GET", path2, void 0, timeoutMs);
|
|
5326
|
-
if (!res.ok) return
|
|
5335
|
+
if (!res.ok) return { status: "error", httpStatus: res.status };
|
|
5327
5336
|
const json = await res.json();
|
|
5328
|
-
|
|
5329
|
-
|
|
5330
|
-
|
|
5337
|
+
const profile = formatPeerCardLines(json?.peer_card, maxChars);
|
|
5338
|
+
return profile ? { status: "ok", data: profile } : { status: "empty" };
|
|
5339
|
+
} catch (e) {
|
|
5340
|
+
return { status: "error", message: e.message };
|
|
5331
5341
|
}
|
|
5332
5342
|
}
|
|
5333
5343
|
async function fetchPeerCardWithFallback(cfg, peer, project2, opts = {}) {
|
|
5334
5344
|
if (project2) {
|
|
5335
5345
|
const scoped = await fetchPeerCard(cfg, peer, { ...opts, project: project2 });
|
|
5336
|
-
if (scoped) return { profile: scoped, scope: "project" };
|
|
5346
|
+
if (scoped.status === "ok") return { profile: scoped.data, scope: "project", status: "ok" };
|
|
5347
|
+
if (scoped.status === "error") {
|
|
5348
|
+
return { profile: null, scope: null, status: "error", httpStatus: scoped.httpStatus, message: scoped.message };
|
|
5349
|
+
}
|
|
5337
5350
|
}
|
|
5338
5351
|
const wide = await fetchPeerCard(cfg, peer, opts);
|
|
5339
|
-
return { profile: wide, scope:
|
|
5352
|
+
if (wide.status === "ok") return { profile: wide.data, scope: "peer", status: "ok" };
|
|
5353
|
+
if (wide.status === "error") {
|
|
5354
|
+
return { profile: null, scope: null, status: "error", httpStatus: wide.httpStatus, message: wide.message };
|
|
5355
|
+
}
|
|
5356
|
+
return { profile: null, scope: null, status: "empty" };
|
|
5340
5357
|
}
|
|
5341
5358
|
async function dialecticChat(cfg, peer, query, opts = {}, fetchImpl = fetch) {
|
|
5342
5359
|
try {
|
|
@@ -5346,25 +5363,44 @@ async function dialecticChat(cfg, peer, query, opts = {}, fetchImpl = fetch) {
|
|
|
5346
5363
|
...opts.target ? { target: opts.target } : {},
|
|
5347
5364
|
...opts.sessionId ? { session_id: opts.sessionId } : {}
|
|
5348
5365
|
}, opts.timeoutMs ?? 15e3);
|
|
5349
|
-
if (!res.ok) return
|
|
5366
|
+
if (!res.ok) return { status: "error", httpStatus: res.status };
|
|
5350
5367
|
const json = await res.json();
|
|
5351
|
-
|
|
5352
|
-
|
|
5353
|
-
|
|
5368
|
+
const answer = (json?.content ?? "").trim();
|
|
5369
|
+
return answer ? { status: "ok", data: answer } : { status: "empty" };
|
|
5370
|
+
} catch (e) {
|
|
5371
|
+
return { status: "error", message: e.message };
|
|
5354
5372
|
}
|
|
5355
5373
|
}
|
|
5356
5374
|
async function probeHoncho(cfg, fetchImpl = fetch, timeoutMs = 3e3, opts = {}) {
|
|
5375
|
+
const emptyQueue = { pending: null, inProgress: null, stalled: false };
|
|
5357
5376
|
try {
|
|
5358
5377
|
const healthRes = await request(cfg, fetchImpl, "GET", honchoRoutes.health(), void 0, timeoutMs);
|
|
5359
5378
|
if (!healthRes.ok) {
|
|
5360
|
-
return { reachable: false, status: healthRes.status, authOk: false, authStatus: void 0 };
|
|
5379
|
+
return { reachable: false, status: healthRes.status, authOk: false, authStatus: void 0, queue: emptyQueue };
|
|
5361
5380
|
}
|
|
5362
5381
|
const authPath = opts.peer ? honchoRoutes.peerCard(cfg.workspace, opts.peer) : honchoRoutes.queueStatus(cfg.workspace);
|
|
5363
5382
|
const authRes = await request(cfg, fetchImpl, "GET", authPath, void 0, timeoutMs);
|
|
5364
5383
|
const authOk = authRes.status !== 401 && authRes.status !== 403;
|
|
5365
|
-
|
|
5384
|
+
let queue = emptyQueue;
|
|
5385
|
+
if (authPath.includes("/queue/status") && authRes.ok) {
|
|
5386
|
+
try {
|
|
5387
|
+
queue = parseHonchoQueueStatus(await authRes.json());
|
|
5388
|
+
} catch {
|
|
5389
|
+
queue = emptyQueue;
|
|
5390
|
+
}
|
|
5391
|
+
} else {
|
|
5392
|
+
const queueRes = await request(cfg, fetchImpl, "GET", honchoRoutes.queueStatus(cfg.workspace), void 0, timeoutMs);
|
|
5393
|
+
if (queueRes.ok) {
|
|
5394
|
+
try {
|
|
5395
|
+
queue = parseHonchoQueueStatus(await queueRes.json());
|
|
5396
|
+
} catch {
|
|
5397
|
+
queue = emptyQueue;
|
|
5398
|
+
}
|
|
5399
|
+
}
|
|
5400
|
+
}
|
|
5401
|
+
return { reachable: true, status: healthRes.status, authOk, authStatus: authRes.status, queue };
|
|
5366
5402
|
} catch {
|
|
5367
|
-
return { reachable: false, authOk: false };
|
|
5403
|
+
return { reachable: false, authOk: false, queue: emptyQueue };
|
|
5368
5404
|
}
|
|
5369
5405
|
}
|
|
5370
5406
|
|
|
@@ -5552,8 +5588,21 @@ async function runHonchoContext(opts, io = consoleIo) {
|
|
|
5552
5588
|
return;
|
|
5553
5589
|
}
|
|
5554
5590
|
const key = await sagaKey(cfg);
|
|
5555
|
-
const { profile, scope } = await fetchPeerCardWithFallback(hc, peer, key.project);
|
|
5556
|
-
if (opts.json)
|
|
5591
|
+
const { profile, scope, status, httpStatus, message } = await fetchPeerCardWithFallback(hc, peer, key.project);
|
|
5592
|
+
if (opts.json) {
|
|
5593
|
+
return io.log(JSON.stringify({
|
|
5594
|
+
peer,
|
|
5595
|
+
project: key.project,
|
|
5596
|
+
cardScope: scope,
|
|
5597
|
+
profile,
|
|
5598
|
+
status,
|
|
5599
|
+
...status === "error" ? { error: { httpStatus, message } } : {}
|
|
5600
|
+
}));
|
|
5601
|
+
}
|
|
5602
|
+
if (status === "error") {
|
|
5603
|
+
io.err(`honcho context: peer card request failed${httpStatus ? ` (${httpStatus})` : ""}${message ? `: ${message}` : ""}`);
|
|
5604
|
+
return;
|
|
5605
|
+
}
|
|
5557
5606
|
if (!profile) return;
|
|
5558
5607
|
io.log(opts.banner ? profileBanner(profile, peer, scope === "project" ? key.project : void 0) : profile);
|
|
5559
5608
|
}
|
|
@@ -5577,9 +5626,21 @@ async function runHonchoChat(query, opts, io = consoleIo) {
|
|
|
5577
5626
|
io.err("honcho chat: query is empty after redaction");
|
|
5578
5627
|
return;
|
|
5579
5628
|
}
|
|
5580
|
-
const
|
|
5581
|
-
if (opts.json)
|
|
5582
|
-
|
|
5629
|
+
const result = await dialecticChat(hc, peer, safeQuery, { target: opts.target });
|
|
5630
|
+
if (opts.json) {
|
|
5631
|
+
return io.log(JSON.stringify({
|
|
5632
|
+
peer,
|
|
5633
|
+
query,
|
|
5634
|
+
answer: result.status === "ok" ? result.data : null,
|
|
5635
|
+
status: result.status,
|
|
5636
|
+
...result.status === "error" ? { error: { httpStatus: result.httpStatus, message: result.message } } : {}
|
|
5637
|
+
}));
|
|
5638
|
+
}
|
|
5639
|
+
if (result.status === "error") {
|
|
5640
|
+
io.err(`honcho chat: request failed${result.httpStatus ? ` (${result.httpStatus})` : ""}${result.message ? `: ${result.message}` : ""}`);
|
|
5641
|
+
return;
|
|
5642
|
+
}
|
|
5643
|
+
io.log(result.status === "ok" ? result.data : "(no answer \u2014 the profile may be empty or not yet built)");
|
|
5583
5644
|
}
|
|
5584
5645
|
async function runHonchoHealth(o, io = consoleIo) {
|
|
5585
5646
|
const cfg = await loadConfig();
|
|
@@ -5587,12 +5648,19 @@ async function runHonchoHealth(o, io = consoleIo) {
|
|
|
5587
5648
|
const apiKey = await honchoApiKey();
|
|
5588
5649
|
const hc = apiKey ? { apiUrl, apiKey, workspace } : null;
|
|
5589
5650
|
const peer = honchoPeerId(await honchoLogin(cfg), cfg);
|
|
5590
|
-
const liveness = hc ? await probeHoncho(hc, fetch, 3e3, { peer: peer ?? void 0 }) : {
|
|
5651
|
+
const liveness = hc ? await probeHoncho(hc, fetch, 3e3, { peer: peer ?? void 0 }) : {
|
|
5652
|
+
reachable: false,
|
|
5653
|
+
status: void 0,
|
|
5654
|
+
authOk: false,
|
|
5655
|
+
authStatus: void 0,
|
|
5656
|
+
queue: { pending: null, inProgress: null, stalled: false }
|
|
5657
|
+
};
|
|
5591
5658
|
const pending = readHonchoPending().length;
|
|
5592
5659
|
const ingestSkip = readIngestSkip();
|
|
5593
5660
|
const ingestSkipMessage = formatIngestSkip(ingestSkip);
|
|
5661
|
+
const deriverStalled = liveness.queue.stalled;
|
|
5594
5662
|
const report = {
|
|
5595
|
-
ok: !!hc && liveness.reachable && liveness.authOk,
|
|
5663
|
+
ok: !!hc && liveness.reachable && liveness.authOk && !deriverStalled,
|
|
5596
5664
|
configured: !!hc,
|
|
5597
5665
|
apiUrl,
|
|
5598
5666
|
apiKeyConfigured: !!apiKey,
|
|
@@ -5603,6 +5671,11 @@ async function runHonchoHealth(o, io = consoleIo) {
|
|
|
5603
5671
|
peer: peer ?? null,
|
|
5604
5672
|
workspace,
|
|
5605
5673
|
pending,
|
|
5674
|
+
deriverQueue: {
|
|
5675
|
+
pending: liveness.queue.pending,
|
|
5676
|
+
inProgress: liveness.queue.inProgress,
|
|
5677
|
+
stalled: deriverStalled
|
|
5678
|
+
},
|
|
5606
5679
|
ingestSkip: ingestSkip ?? null,
|
|
5607
5680
|
ingestSkipMessage: ingestSkipMessage ?? null
|
|
5608
5681
|
};
|
|
@@ -5610,6 +5683,7 @@ async function runHonchoHealth(o, io = consoleIo) {
|
|
|
5610
5683
|
if (o.banner) {
|
|
5611
5684
|
if (report.configured && !report.reachable) io.log(`honcho: configured but unreachable (${pending} pending)`);
|
|
5612
5685
|
if (report.configured && report.reachable && !report.authOk) io.log("honcho: configured but API key rejected (401/403)");
|
|
5686
|
+
if (deriverStalled) io.log(`honcho: deriver stalled (${liveness.queue.pending} pending, 0 in progress)`);
|
|
5613
5687
|
if (ingestSkipMessage) io.log(`honcho: ${ingestSkipMessage}`);
|
|
5614
5688
|
return;
|
|
5615
5689
|
}
|
|
@@ -5618,8 +5692,12 @@ async function runHonchoHealth(o, io = consoleIo) {
|
|
|
5618
5692
|
if (report.configured && !report.reachable) io.log(" - service unreachable");
|
|
5619
5693
|
if (report.configured && report.reachable && !report.authOk) io.log(" - API key rejected (401/403) \u2014 rotate or fix vault path");
|
|
5620
5694
|
if (!report.peer) io.log(" - no peer identity resolved (gh login) \u2014 ingest will be skipped");
|
|
5695
|
+
if (deriverStalled) io.log(` - deriver stalled (${liveness.queue.pending} pending, 0 in progress)`);
|
|
5621
5696
|
if (ingestSkipMessage) io.log(` - ${ingestSkipMessage}`);
|
|
5622
5697
|
if (pending > 0) io.log(` - ${pending} ingest(s) queued locally`);
|
|
5698
|
+
if (liveness.queue.pending != null && liveness.queue.pending > 0) {
|
|
5699
|
+
io.log(` - deriver queue: ${liveness.queue.pending} pending, ${liveness.queue.inProgress ?? 0} in progress`);
|
|
5700
|
+
}
|
|
5623
5701
|
}
|
|
5624
5702
|
async function runHonchoKey(o, io = consoleIo) {
|
|
5625
5703
|
const cfg = await loadConfig();
|
|
@@ -7151,7 +7229,7 @@ ${buildReportBody(body, sourceRepo)}`;
|
|
|
7151
7229
|
|
|
7152
7230
|
// src/skill-lesson.ts
|
|
7153
7231
|
var SKILL_LESSON_LABEL = "skill-lesson";
|
|
7154
|
-
var SKILL_NAMES = ["bootstrap", "grind", "hotfix", "mmi", "rcand", "release", "secrets", "stage"];
|
|
7232
|
+
var SKILL_NAMES = ["bootstrap", "browser-automation", "grind", "hotfix", "mmi", "rcand", "release", "secrets", "stage"];
|
|
7155
7233
|
function assertSkillName(name) {
|
|
7156
7234
|
const match = SKILL_NAMES.find((skill) => skill === name);
|
|
7157
7235
|
if (!match) throw new Error(`unknown skill "${name}" \u2014 expected one of: ${SKILL_NAMES.join(", ")}`);
|
|
@@ -7277,6 +7355,129 @@ function buildPanelPlan(input) {
|
|
|
7277
7355
|
};
|
|
7278
7356
|
}
|
|
7279
7357
|
|
|
7358
|
+
// src/grind-policy.ts
|
|
7359
|
+
var DEFAULT_SEARCH_DENY_DOMAINS = [
|
|
7360
|
+
"stackoverflow.com",
|
|
7361
|
+
"stackexchange.com",
|
|
7362
|
+
"github.com/issues",
|
|
7363
|
+
"reddit.com"
|
|
7364
|
+
];
|
|
7365
|
+
|
|
7366
|
+
// src/verify-fusion.ts
|
|
7367
|
+
var DEFAULT_MODELS = {
|
|
7368
|
+
builder: "builder-slot",
|
|
7369
|
+
verifier: "verifier-slot",
|
|
7370
|
+
third: "third-slot",
|
|
7371
|
+
synthesizer: "verifier-slot"
|
|
7372
|
+
};
|
|
7373
|
+
function resolveFusionProviderUrl(explicit) {
|
|
7374
|
+
if (explicit) return explicit;
|
|
7375
|
+
const env = process.env.MMI_FUSION_PROVIDER_URL?.trim();
|
|
7376
|
+
return env || null;
|
|
7377
|
+
}
|
|
7378
|
+
function buildFusionPlan(input) {
|
|
7379
|
+
const routing = input.routing;
|
|
7380
|
+
const lenses = input.lenses ?? [...GRIND_LENSES];
|
|
7381
|
+
const provider = resolveFusionProviderUrl(input.providerUrl ?? void 0);
|
|
7382
|
+
const models = {
|
|
7383
|
+
...DEFAULT_MODELS,
|
|
7384
|
+
...input.models,
|
|
7385
|
+
synthesizer: input.models?.synthesizer ?? input.models?.third ?? input.models?.verifier ?? DEFAULT_MODELS.synthesizer
|
|
7386
|
+
};
|
|
7387
|
+
if (models.verifier === models.builder) {
|
|
7388
|
+
throw new Error("fusion plan: verifier must not equal builder");
|
|
7389
|
+
}
|
|
7390
|
+
if (models.synthesizer === models.builder) {
|
|
7391
|
+
throw new Error("fusion plan: synthesizer must not equal builder");
|
|
7392
|
+
}
|
|
7393
|
+
const toolPolicy = {
|
|
7394
|
+
webSearch: Boolean(input.toolPolicy?.webSearch),
|
|
7395
|
+
maxQueriesPerLens: input.toolPolicy?.maxQueriesPerLens ?? 3,
|
|
7396
|
+
denyDomains: input.toolPolicy?.denyDomains ?? []
|
|
7397
|
+
};
|
|
7398
|
+
return {
|
|
7399
|
+
provider,
|
|
7400
|
+
routing,
|
|
7401
|
+
lenses,
|
|
7402
|
+
models,
|
|
7403
|
+
toolPolicy,
|
|
7404
|
+
criteria: input.criteria,
|
|
7405
|
+
diff: input.diff,
|
|
7406
|
+
fallback: "host-panel",
|
|
7407
|
+
instructions: "Hosted fusion when provider is configured; else spawn host lenses and pipe JSON to `mmi-cli verify synthesize`. Synthesizer slot must differ from builder."
|
|
7408
|
+
};
|
|
7409
|
+
}
|
|
7410
|
+
function adaptFusionResponse(raw) {
|
|
7411
|
+
if (raw.lenses) {
|
|
7412
|
+
const lenses = parseLensResults(raw.lenses);
|
|
7413
|
+
const base2 = synthesizePanelReport(lenses);
|
|
7414
|
+
return {
|
|
7415
|
+
...base2,
|
|
7416
|
+
consensus: raw.consensus?.length ? raw.consensus : base2.consensus,
|
|
7417
|
+
contradictions: raw.contradictions ?? base2.contradictions,
|
|
7418
|
+
partial_coverage: raw.partial_coverage ?? base2.partial_coverage,
|
|
7419
|
+
unique_insights: raw.unique_insights ?? base2.unique_insights,
|
|
7420
|
+
blind_spots: raw.blind_spots ?? base2.blind_spots,
|
|
7421
|
+
nits: raw.nits ?? base2.nits
|
|
7422
|
+
};
|
|
7423
|
+
}
|
|
7424
|
+
const blockers = (raw.blockers ?? []).map((b, i) => ({
|
|
7425
|
+
id: `fusion-${i}`,
|
|
7426
|
+
title: b.title,
|
|
7427
|
+
file: b.file,
|
|
7428
|
+
line: b.line,
|
|
7429
|
+
why: b.why,
|
|
7430
|
+
sources: b.sources ?? ["hosted-fusion"]
|
|
7431
|
+
}));
|
|
7432
|
+
return {
|
|
7433
|
+
consensus: raw.consensus ?? [],
|
|
7434
|
+
contradictions: raw.contradictions ?? [],
|
|
7435
|
+
partial_coverage: raw.partial_coverage ?? [],
|
|
7436
|
+
unique_insights: raw.unique_insights ?? [],
|
|
7437
|
+
blind_spots: raw.blind_spots ?? [],
|
|
7438
|
+
blockers,
|
|
7439
|
+
nits: raw.nits ?? []
|
|
7440
|
+
};
|
|
7441
|
+
}
|
|
7442
|
+
async function runFusionProvider(plan2, deps = {}) {
|
|
7443
|
+
const url = resolveFusionProviderUrl(deps.providerUrl ?? plan2.provider ?? void 0);
|
|
7444
|
+
if (!url) {
|
|
7445
|
+
return { ok: false, source: "fallback", error: "no fusion provider configured" };
|
|
7446
|
+
}
|
|
7447
|
+
const fetchImpl = deps.fetch ?? fetch;
|
|
7448
|
+
const apiKey = deps.apiKey ?? process.env.MMI_FUSION_API_KEY?.trim();
|
|
7449
|
+
const headers = { "content-type": "application/json" };
|
|
7450
|
+
if (apiKey) headers.authorization = `Bearer ${apiKey}`;
|
|
7451
|
+
try {
|
|
7452
|
+
const res = await fetchImpl(url, {
|
|
7453
|
+
method: "POST",
|
|
7454
|
+
headers,
|
|
7455
|
+
body: JSON.stringify({
|
|
7456
|
+
routing: plan2.routing,
|
|
7457
|
+
lenses: plan2.lenses,
|
|
7458
|
+
models: plan2.models,
|
|
7459
|
+
toolPolicy: plan2.toolPolicy,
|
|
7460
|
+
criteria: plan2.criteria,
|
|
7461
|
+
diff: plan2.diff
|
|
7462
|
+
}),
|
|
7463
|
+
signal: AbortSignal.timeout(3e4)
|
|
7464
|
+
});
|
|
7465
|
+
if (!res.ok) {
|
|
7466
|
+
return { ok: false, source: "fallback", error: `provider HTTP ${res.status}` };
|
|
7467
|
+
}
|
|
7468
|
+
const body = await res.json();
|
|
7469
|
+
return { ok: true, source: "hosted-fusion", report: adaptFusionResponse(body) };
|
|
7470
|
+
} catch (e) {
|
|
7471
|
+
return { ok: false, source: "fallback", error: e.message };
|
|
7472
|
+
}
|
|
7473
|
+
}
|
|
7474
|
+
function parseFusionLenses(raw) {
|
|
7475
|
+
return raw.split(",").map((s) => assertGrindLens(s.trim()));
|
|
7476
|
+
}
|
|
7477
|
+
function parseFusionRouting(raw) {
|
|
7478
|
+
return assertVerifyRouting(raw);
|
|
7479
|
+
}
|
|
7480
|
+
|
|
7280
7481
|
// src/gc.ts
|
|
7281
7482
|
var DEFERRED_SWEEP_COMMAND = "mmi-cli gc --apply";
|
|
7282
7483
|
var DEFERRED_NOTE = "Worktree cleanup deferred \u2014 close this folder in your editor (or run cleanup from a shell outside it), then rerun mmi-cli gc --apply.";
|
|
@@ -7626,9 +7827,10 @@ function selectPrMergeCleanupWorktree(branch, before, after, startingPath) {
|
|
|
7626
7827
|
if (startingPath && before.some((w) => w.branch === branch && samePath(w.path, startingPath))) return startingPath;
|
|
7627
7828
|
return void 0;
|
|
7628
7829
|
}
|
|
7629
|
-
function selectSafeWorktreeCwd(worktrees, targetPath) {
|
|
7830
|
+
function selectSafeWorktreeCwd(worktrees, targetPath, options) {
|
|
7630
7831
|
if (!targetPath) return void 0;
|
|
7631
|
-
|
|
7832
|
+
const exists = options?.pathExists ?? (() => true);
|
|
7833
|
+
return worktrees.find((w) => !samePath(w.path, targetPath) && exists(w.path))?.path;
|
|
7632
7834
|
}
|
|
7633
7835
|
function branchMissingFromList(branch, stdout) {
|
|
7634
7836
|
const names = stdout.split(/\r?\n/).map((line) => line.replace(/^\*\s*/, "").trim()).filter(Boolean);
|
|
@@ -7659,7 +7861,9 @@ async function cleanupPrMergeLocalBranch(branch, options) {
|
|
|
7659
7861
|
}
|
|
7660
7862
|
const beforeWorktrees = options.beforeWorktrees ?? [];
|
|
7661
7863
|
const wtPath = selectPrMergeCleanupWorktree(branch, beforeWorktrees, afterWorktrees, options.startingPath);
|
|
7662
|
-
const safeCwd = selectSafeWorktreeCwd([...afterWorktrees, ...beforeWorktrees], wtPath
|
|
7864
|
+
const safeCwd = selectSafeWorktreeCwd([...afterWorktrees, ...beforeWorktrees], wtPath, {
|
|
7865
|
+
pathExists: options.pathExists
|
|
7866
|
+
});
|
|
7663
7867
|
const git = (args) => safeCwd ? options.execGit(["-C", safeCwd, ...args]) : options.execGit(args);
|
|
7664
7868
|
const removeDeps = {
|
|
7665
7869
|
git,
|
|
@@ -7688,23 +7892,36 @@ async function cleanupPrMergeLocalBranch(branch, options) {
|
|
|
7688
7892
|
if (outcome.status === "removed") {
|
|
7689
7893
|
report.worktree = { path: wtPath, status: "removed", stageTeardown, recovery: outcome.recovery };
|
|
7690
7894
|
} else if (isPersistentWorktreeLockFailure(outcome) && options.deferredStore) {
|
|
7691
|
-
|
|
7692
|
-
|
|
7693
|
-
|
|
7694
|
-
|
|
7695
|
-
|
|
7696
|
-
|
|
7697
|
-
|
|
7698
|
-
|
|
7699
|
-
|
|
7700
|
-
|
|
7701
|
-
|
|
7702
|
-
|
|
7703
|
-
|
|
7704
|
-
|
|
7705
|
-
|
|
7706
|
-
|
|
7707
|
-
|
|
7895
|
+
try {
|
|
7896
|
+
const { newlyRegistered } = await registerDeferredWorktree(options.deferredStore, {
|
|
7897
|
+
path: wtPath,
|
|
7898
|
+
branch,
|
|
7899
|
+
reason: "lock-held"
|
|
7900
|
+
});
|
|
7901
|
+
report.worktree = {
|
|
7902
|
+
path: wtPath,
|
|
7903
|
+
status: "deferred",
|
|
7904
|
+
reason: "lock-held",
|
|
7905
|
+
error: outcome.error,
|
|
7906
|
+
deferredNote: DEFERRED_NOTE,
|
|
7907
|
+
deferredSweepCommand: DEFERRED_SWEEP_COMMAND,
|
|
7908
|
+
...newlyRegistered ? { safeCleanupCommand: safeWorktreeRemoveCommand(safeCwd, wtPath) } : {},
|
|
7909
|
+
stageTeardown
|
|
7910
|
+
};
|
|
7911
|
+
report.localBranch = { name: branch, status: "not-attempted", reason: "worktree-removal-deferred" };
|
|
7912
|
+
return report;
|
|
7913
|
+
} catch (e) {
|
|
7914
|
+
report.worktree = {
|
|
7915
|
+
path: wtPath,
|
|
7916
|
+
status: "failed",
|
|
7917
|
+
reason: "deferred-registry-unavailable",
|
|
7918
|
+
error: errorMessage(e),
|
|
7919
|
+
safeCleanupCommand: safeWorktreeRemoveCommand(safeCwd, wtPath),
|
|
7920
|
+
stageTeardown
|
|
7921
|
+
};
|
|
7922
|
+
report.localBranch = { name: branch, status: "not-attempted", reason: "worktree-removal-failed" };
|
|
7923
|
+
return report;
|
|
7924
|
+
}
|
|
7708
7925
|
} else {
|
|
7709
7926
|
report.worktree = {
|
|
7710
7927
|
path: wtPath,
|
|
@@ -8583,6 +8800,48 @@ function buildHubCompatCheck(input) {
|
|
|
8583
8800
|
}
|
|
8584
8801
|
return { ok: versionAtLeast(input.installedVersion, min), label: `${label}: requires >= ${min}`, fix: HUB_COMPAT_FIX };
|
|
8585
8802
|
}
|
|
8803
|
+
var PLAYWRIGHT_MCP_VISION_CAP_LABEL = "Playwright MCP vision caps (--caps=vision prohibited)";
|
|
8804
|
+
var PLAYWRIGHT_MCP_VISION_CAP_FIX = "remove --caps=vision (and vision-first defaults) from Playwright MCP args \u2014 use DOM-first tools; see skills/browser-automation/SKILL.md and bootstrap seed mcp-playwright.template.json";
|
|
8805
|
+
function textHasPlaywrightVisionCap(content) {
|
|
8806
|
+
const normalized = content.replace(/\r\n/g, "\n");
|
|
8807
|
+
if (/(?:^|[\s"'`=])--caps=(?:[^"\s\n]*,)?vision\b/m.test(normalized)) return true;
|
|
8808
|
+
if (/\b--caps\s*=\s*["']?vision\b/.test(normalized)) return true;
|
|
8809
|
+
if (/"--caps"\s*,\s*"vision"/.test(normalized)) return true;
|
|
8810
|
+
if (/"caps"\s*:\s*"vision"/.test(normalized)) return true;
|
|
8811
|
+
const playwrightMcp = /@playwright\/mcp/.test(normalized) || /mcp_servers\.playwright/.test(normalized) || /"playwright"\s*:\s*\{/.test(normalized) || /\bmcpServers\b/.test(normalized);
|
|
8812
|
+
if (!playwrightMcp) return false;
|
|
8813
|
+
if (/\bvision[-_]?(?:first|only|mode)\b/i.test(normalized)) return true;
|
|
8814
|
+
return false;
|
|
8815
|
+
}
|
|
8816
|
+
function buildPlaywrightMcpVisionCapCheck(input) {
|
|
8817
|
+
const base2 = {
|
|
8818
|
+
ok: true,
|
|
8819
|
+
label: PLAYWRIGHT_MCP_VISION_CAP_LABEL,
|
|
8820
|
+
fix: PLAYWRIGHT_MCP_VISION_CAP_FIX
|
|
8821
|
+
};
|
|
8822
|
+
if (!input.isOrgRepo) return base2;
|
|
8823
|
+
const offending = input.configs.filter((c) => textHasPlaywrightVisionCap(c.content)).map((c) => c.path);
|
|
8824
|
+
if (offending.length === 0) return base2;
|
|
8825
|
+
return {
|
|
8826
|
+
...base2,
|
|
8827
|
+
ok: false,
|
|
8828
|
+
offendingPaths: offending,
|
|
8829
|
+
fix: `${PLAYWRIGHT_MCP_VISION_CAP_FIX} \u2014 found in: ${offending.join(", ")}`
|
|
8830
|
+
};
|
|
8831
|
+
}
|
|
8832
|
+
var STRAY_BROWSER_ARTIFACT_DIRS = [".playwright-mcp", "playwright-report", "test-results"];
|
|
8833
|
+
var BROWSER_ARTIFACTS_LABEL = "browser MCP artifacts outside tmp/ (use tmp/playwright-mcp)";
|
|
8834
|
+
var BROWSER_ARTIFACTS_FIX = "move or delete stray Playwright output at repo root; re-run MCP with --output-dir tmp/playwright-mcp \u2014 see skills/browser-automation/SKILL.md";
|
|
8835
|
+
function buildBrowserArtifactsCheck(input) {
|
|
8836
|
+
const base2 = { ok: true, label: BROWSER_ARTIFACTS_LABEL, fix: BROWSER_ARTIFACTS_FIX };
|
|
8837
|
+
if (!input.isOrgRepo || input.strayPaths.length === 0) return base2;
|
|
8838
|
+
return {
|
|
8839
|
+
...base2,
|
|
8840
|
+
ok: false,
|
|
8841
|
+
strayPaths: [...input.strayPaths],
|
|
8842
|
+
fix: `${BROWSER_ARTIFACTS_FIX} \u2014 found: ${input.strayPaths.join(", ")}`
|
|
8843
|
+
};
|
|
8844
|
+
}
|
|
8586
8845
|
|
|
8587
8846
|
// src/stage-live.ts
|
|
8588
8847
|
var import_node_net = require("node:net");
|
|
@@ -8956,10 +9215,22 @@ function resolveDeployModel(meta, repo) {
|
|
|
8956
9215
|
function projectTypeClearsWebProfile(projectType, deployModel) {
|
|
8957
9216
|
return projectType === "content" || projectType === "desktop-game" || projectType === "non-deployable" || projectType === "cli-tool" || deployModel === "content" || deployModel === "none" || deployModel === "registry-publish";
|
|
8958
9217
|
}
|
|
8959
|
-
function
|
|
9218
|
+
function inferReleaseTrackFromBranches(hints) {
|
|
9219
|
+
if (hints.hasRcBranch) return void 0;
|
|
9220
|
+
if (hints.hasDevelopmentBranch && hints.hasMainBranch) return "direct";
|
|
9221
|
+
return void 0;
|
|
9222
|
+
}
|
|
9223
|
+
function resolveBootstrapReleaseTrack(cls, explicit) {
|
|
9224
|
+
if (isReleaseTrack(explicit)) return explicit;
|
|
9225
|
+
if (cls === "content") return "trunk";
|
|
9226
|
+
return "full";
|
|
9227
|
+
}
|
|
9228
|
+
function resolveReleaseTrack(meta, hints) {
|
|
8960
9229
|
const raw = typeof meta?.releaseTrack === "string" ? meta.releaseTrack : void 0;
|
|
8961
9230
|
if (isReleaseTrack(raw)) return raw;
|
|
8962
9231
|
if (meta?.class === "content" || meta?.deployModel === "content") return "trunk";
|
|
9232
|
+
const inferred = hints ? inferReleaseTrackFromBranches(hints) : void 0;
|
|
9233
|
+
if (inferred) return inferred;
|
|
8963
9234
|
return "full";
|
|
8964
9235
|
}
|
|
8965
9236
|
function branchesForTrack(track) {
|
|
@@ -9110,6 +9381,18 @@ function ensurePositiveCount(out, emptyMessage) {
|
|
|
9110
9381
|
if (!Number.isFinite(count)) throw new Error(`could not parse ahead count: ${out.trim() || "(empty)"}`);
|
|
9111
9382
|
if (count <= 0) throw new Error(emptyMessage);
|
|
9112
9383
|
}
|
|
9384
|
+
async function remoteBranchExists(deps, branch) {
|
|
9385
|
+
const out = clean(await deps.run("git", ["ls-remote", "origin", `refs/heads/${branch}`]));
|
|
9386
|
+
return out.length > 0;
|
|
9387
|
+
}
|
|
9388
|
+
async function loadReleaseTrackBranchHints(deps) {
|
|
9389
|
+
const [hasDevelopmentBranch, hasMainBranch, hasRcBranch] = await Promise.all([
|
|
9390
|
+
remoteBranchExists(deps, "development"),
|
|
9391
|
+
remoteBranchExists(deps, "main"),
|
|
9392
|
+
remoteBranchExists(deps, "rc")
|
|
9393
|
+
]);
|
|
9394
|
+
return { hasDevelopmentBranch, hasMainBranch, hasRcBranch };
|
|
9395
|
+
}
|
|
9113
9396
|
async function buildTrainApplyContext(deps) {
|
|
9114
9397
|
const repo = requireValue(clean(await deps.run("gh", ["repo", "view", "--json", "nameWithOwner", "--jq", ".nameWithOwner"])), "repo");
|
|
9115
9398
|
const [owner, name] = repo.split("/");
|
|
@@ -9543,7 +9826,8 @@ async function runTrainApply(command, deps, options = {}) {
|
|
|
9543
9826
|
await requireCleanTree(deps);
|
|
9544
9827
|
await deps.run("git", ["fetch", "origin"]);
|
|
9545
9828
|
const meta = requireProjectMetaForTrain(await loadProjectMeta(deps, ctx), ctx.repo);
|
|
9546
|
-
const
|
|
9829
|
+
const branchHints = await loadReleaseTrackBranchHints(deps);
|
|
9830
|
+
const directTrack = isHubControlRepo(ctx.repo) || resolveReleaseTrack(meta, branchHints) === "direct";
|
|
9547
9831
|
if (command === "rcand") {
|
|
9548
9832
|
await requireBranch(deps, "development");
|
|
9549
9833
|
if (directTrack) {
|
|
@@ -9635,13 +9919,16 @@ async function runTrainApply(command, deps, options = {}) {
|
|
|
9635
9919
|
if (command === "release" && options.dev) {
|
|
9636
9920
|
await requireBranch(deps, "development");
|
|
9637
9921
|
await ffOnlyPull(deps, "development");
|
|
9638
|
-
const
|
|
9639
|
-
|
|
9640
|
-
|
|
9641
|
-
|
|
9642
|
-
throw new Error(
|
|
9643
|
-
|
|
9644
|
-
|
|
9922
|
+
const hasRcBranch = branchHints.hasRcBranch ?? false;
|
|
9923
|
+
if (hasRcBranch) {
|
|
9924
|
+
const rcOnlyOut = (await deps.run("git", ["rev-list", "--count", "--right-only", "--cherry-pick", "--no-merges", "origin/development...origin/rc"])).trim();
|
|
9925
|
+
const rcOnly = Number.parseInt(rcOnlyOut, 10);
|
|
9926
|
+
if (!Number.isFinite(rcOnly)) throw new Error(`release --dev: could not count rc-only commits for the guard: ${rcOnlyOut || "(empty)"}`);
|
|
9927
|
+
if (rcOnly > 0) {
|
|
9928
|
+
throw new Error(
|
|
9929
|
+
`release --dev refused: origin/rc carries ${rcOnly} commit(s) not in origin/development \u2014 a development -> main release would drop that rc-only content. Land it on development first, or release the candidate via the default rc -> main path, then rerun.`
|
|
9930
|
+
);
|
|
9931
|
+
}
|
|
9645
9932
|
}
|
|
9646
9933
|
ensurePositiveCount(
|
|
9647
9934
|
await deps.run("git", ["rev-list", "--count", "origin/main..origin/development"]),
|
|
@@ -9649,7 +9936,7 @@ async function runTrainApply(command, deps, options = {}) {
|
|
|
9649
9936
|
);
|
|
9650
9937
|
const deployModel2 = await preflight(deps, ctx, "main", meta);
|
|
9651
9938
|
const tag2 = requireValue(clean(await deps.run("node", ["scripts/next-version.mjs", "cycle"])), "release tag");
|
|
9652
|
-
const rcShaAtRelease = clean(await deps.run("git", ["rev-parse", "origin/rc"]));
|
|
9939
|
+
const rcShaAtRelease = hasRcBranch ? clean(await deps.run("git", ["rev-parse", "origin/rc"])) : "";
|
|
9653
9940
|
const foldPaths2 = await resolveFoldPaths(deps, deployModel2);
|
|
9654
9941
|
const tolerated2 = [...foldPaths2, ...RELEASE_TOLERATED_PATHS];
|
|
9655
9942
|
const predicted2 = await predictMergeConflicts(deps, "origin/main", "origin/development");
|
|
@@ -9677,14 +9964,18 @@ async function runTrainApply(command, deps, options = {}) {
|
|
|
9677
9964
|
const announceNote2 = deps.announce ? (await deps.announce({ repo: ctx.repo, tag: tag2, summaryFile: options.announceSummaryFile })).note : void 0;
|
|
9678
9965
|
const autoRunSince2 = (deps.now ?? Date.now)();
|
|
9679
9966
|
const d2 = await dispatchDeploy(deps, ctx, "main", "main", deployModel2, watch, autoRunSince2, releaseSha2, "report");
|
|
9680
|
-
const retirement2 = await retireRcRuntime(deps, ctx, deployModel2, d2.deployStatus, rcShaAtRelease);
|
|
9967
|
+
const retirement2 = hasRcBranch ? await retireRcRuntime(deps, ctx, deployModel2, d2.deployStatus, rcShaAtRelease) : { status: "not-applicable", note: "no origin/rc branch \u2014 rc runtime retirement skipped" };
|
|
9681
9968
|
const devRollForward2 = await rollDevelopmentForward(deps, ctx, tag2);
|
|
9682
9969
|
let rcAlignment2;
|
|
9683
|
-
|
|
9684
|
-
|
|
9685
|
-
|
|
9686
|
-
|
|
9687
|
-
|
|
9970
|
+
if (hasRcBranch) {
|
|
9971
|
+
try {
|
|
9972
|
+
await deps.run("git", ["push", "origin", "main:rc"]);
|
|
9973
|
+
rcAlignment2 = "origin/rc aligned to the released main";
|
|
9974
|
+
} catch (e) {
|
|
9975
|
+
rcAlignment2 = `rc alignment push failed \u2014 align manually with \`git push origin main:rc\`: ${e instanceof Error ? e.message : String(e)}`;
|
|
9976
|
+
}
|
|
9977
|
+
} else {
|
|
9978
|
+
rcAlignment2 = "no origin/rc branch \u2014 rc alignment skipped";
|
|
9688
9979
|
}
|
|
9689
9980
|
const environments2 = await buildEnvironments(deps, ctx, deployModel2, d2.deployStatus, retirement2);
|
|
9690
9981
|
return {
|
|
@@ -11298,8 +11589,9 @@ function parseOwnerRepo(repo) {
|
|
|
11298
11589
|
}
|
|
11299
11590
|
var DEFAULT_INSTALL_CMD = "npm ci";
|
|
11300
11591
|
var DEFAULT_GATE_CMD = "npm run check";
|
|
11301
|
-
function gateSeedVars(cls) {
|
|
11302
|
-
|
|
11592
|
+
function gateSeedVars(cls, releaseTrack) {
|
|
11593
|
+
const track = releaseTrack ?? (cls === "content" ? "trunk" : "full");
|
|
11594
|
+
if (track === "trunk") {
|
|
11303
11595
|
return {
|
|
11304
11596
|
GATE_CMD: DEFAULT_GATE_CMD,
|
|
11305
11597
|
GATE_PUSH_BRANCHES_YAML: "[main]",
|
|
@@ -11307,6 +11599,14 @@ function gateSeedVars(cls) {
|
|
|
11307
11599
|
GATE_RULESET_BRANCH_REFS_JSON: '["refs/heads/main"]'
|
|
11308
11600
|
};
|
|
11309
11601
|
}
|
|
11602
|
+
if (track === "direct") {
|
|
11603
|
+
return {
|
|
11604
|
+
GATE_CMD: DEFAULT_GATE_CMD,
|
|
11605
|
+
GATE_PUSH_BRANCHES_YAML: "[development, main]",
|
|
11606
|
+
GATE_FULL_RUN_BRANCH: "development",
|
|
11607
|
+
GATE_RULESET_BRANCH_REFS_JSON: '["refs/heads/development", "refs/heads/main"]'
|
|
11608
|
+
};
|
|
11609
|
+
}
|
|
11310
11610
|
return {
|
|
11311
11611
|
GATE_CMD: DEFAULT_GATE_CMD,
|
|
11312
11612
|
GATE_PUSH_BRANCHES_YAML: "[development, rc, main]",
|
|
@@ -11314,13 +11614,14 @@ function gateSeedVars(cls) {
|
|
|
11314
11614
|
GATE_RULESET_BRANCH_REFS_JSON: '["refs/heads/development", "refs/heads/rc", "refs/heads/main"]'
|
|
11315
11615
|
};
|
|
11316
11616
|
}
|
|
11317
|
-
function withDerivedRepoVars(vars, parsed, cls) {
|
|
11617
|
+
function withDerivedRepoVars(vars, parsed, cls, releaseTrack) {
|
|
11318
11618
|
const out = { ...vars };
|
|
11319
11619
|
out.REPO_NAME ??= parsed.name;
|
|
11320
11620
|
out.REPO_SLUG ??= parsed.slug;
|
|
11321
11621
|
out.CLASS ??= cls;
|
|
11322
11622
|
out.INSTALL_CMD ??= DEFAULT_INSTALL_CMD;
|
|
11323
|
-
|
|
11623
|
+
const track = releaseTrack ?? resolveBootstrapReleaseTrack(cls);
|
|
11624
|
+
for (const [key, value] of Object.entries(gateSeedVars(cls, track))) {
|
|
11324
11625
|
out[key] ??= value;
|
|
11325
11626
|
}
|
|
11326
11627
|
return out;
|
|
@@ -11432,8 +11733,8 @@ function buildRegisterPayload(repo, cls, vars, options = {}) {
|
|
|
11432
11733
|
class: cls,
|
|
11433
11734
|
projectType,
|
|
11434
11735
|
deployModel,
|
|
11435
|
-
// #
|
|
11436
|
-
releaseTrack:
|
|
11736
|
+
// #1359: always persist an explicit track so release tooling never guesses from absence alone.
|
|
11737
|
+
releaseTrack: resolveBootstrapReleaseTrack(cls, options.releaseTrack),
|
|
11437
11738
|
// Board coords (from GraphQL at bootstrap, passed as --var by the skill).
|
|
11438
11739
|
projectOwner: vars.PROJECT_OWNER || void 0,
|
|
11439
11740
|
projectNumber: num(vars.PROJECT_NUMBER),
|
|
@@ -11447,6 +11748,11 @@ function buildRegisterPayload(repo, cls, vars, options = {}) {
|
|
|
11447
11748
|
kbPointer: `kb/projects/${slug}.md`
|
|
11448
11749
|
};
|
|
11449
11750
|
for (const k of Object.keys(payload)) if (payload[k] === void 0) delete payload[k];
|
|
11751
|
+
if (payload.projectId && payload.projectNumber == null) {
|
|
11752
|
+
throw new Error(
|
|
11753
|
+
"bootstrap apply: PROJECT_ID is set but PROJECT_NUMBER is missing \u2014 pass --var PROJECT_NUMBER=<N> or ensure the live board GraphQL query succeeds"
|
|
11754
|
+
);
|
|
11755
|
+
}
|
|
11450
11756
|
return payload;
|
|
11451
11757
|
}
|
|
11452
11758
|
var BOARD_FIELD_VAR_MAP = {
|
|
@@ -11459,9 +11765,18 @@ var BOARD_FIELD_VAR_MAP = {
|
|
|
11459
11765
|
options: { Urgent: "PRIORITY_URGENT", High: "PRIORITY_HIGH", Medium: "PRIORITY_MEDIUM", Low: "PRIORITY_LOW" }
|
|
11460
11766
|
}
|
|
11461
11767
|
};
|
|
11768
|
+
function projectV2BoardNode(fieldsJson) {
|
|
11769
|
+
if (Array.isArray(fieldsJson)) return { fields: { nodes: fieldsJson } };
|
|
11770
|
+
const wrapped = fieldsJson;
|
|
11771
|
+
return wrapped?.data?.node ?? wrapped?.node;
|
|
11772
|
+
}
|
|
11462
11773
|
function extractBoardFieldVars(fieldsJson) {
|
|
11463
11774
|
const out = {};
|
|
11464
|
-
const
|
|
11775
|
+
const projectNode = projectV2BoardNode(fieldsJson);
|
|
11776
|
+
if (typeof projectNode?.number === "number" && Number.isFinite(projectNode.number)) {
|
|
11777
|
+
out.PROJECT_NUMBER = String(projectNode.number);
|
|
11778
|
+
}
|
|
11779
|
+
const nodes = projectNode?.fields?.nodes;
|
|
11465
11780
|
if (!Array.isArray(nodes)) return out;
|
|
11466
11781
|
for (const node of nodes) {
|
|
11467
11782
|
const field = node;
|
|
@@ -11479,7 +11794,7 @@ function extractBoardFieldVars(fieldsJson) {
|
|
|
11479
11794
|
return out;
|
|
11480
11795
|
}
|
|
11481
11796
|
function boardFieldsQueryArgs(projectId) {
|
|
11482
|
-
const query = "query($id: ID!) { node(id: $id) { ... on ProjectV2 { fields(first: 50) { nodes { ... on ProjectV2SingleSelectField { id name options { id name } } } } } } }";
|
|
11797
|
+
const query = "query($id: ID!) { node(id: $id) { ... on ProjectV2 { number fields(first: 50) { nodes { ... on ProjectV2SingleSelectField { id name options { id name } } } } } } }";
|
|
11483
11798
|
return ["api", "graphql", "-f", `query=${query}`, "-f", `id=${projectId}`];
|
|
11484
11799
|
}
|
|
11485
11800
|
function serializeRegistry(obj) {
|
|
@@ -11724,6 +12039,14 @@ function dnsErrorToResolution(code) {
|
|
|
11724
12039
|
}
|
|
11725
12040
|
var STAGES = ["dev", "rc", "main"];
|
|
11726
12041
|
var DEFAULT_RUNTIME_SECRET_NAMES2 = ["GOOGLE_CLIENT_ID", "GOOGLE_CLIENT_SECRET"];
|
|
12042
|
+
function boardRegistryGaps(meta) {
|
|
12043
|
+
if (!meta?.projectId) return [];
|
|
12044
|
+
if (meta.projectNumber != null) return [];
|
|
12045
|
+
return ["projectNumber"];
|
|
12046
|
+
}
|
|
12047
|
+
function boardRegistryGapMessage(repo) {
|
|
12048
|
+
return `Board META incomplete for ${repo}: registry has projectId but no projectNumber \u2014 board claim and auto-add will fail until projectNumber is backfilled (re-run \`node infra/migrate/seed-registry.mjs\` or \`mmi-cli bootstrap apply --execute\` with board vars)`;
|
|
12049
|
+
}
|
|
11727
12050
|
function slugOfRepo(repoOrSlug) {
|
|
11728
12051
|
return (repoOrSlug.includes("/") ? repoOrSlug.split("/").pop() : repoOrSlug).toLowerCase();
|
|
11729
12052
|
}
|
|
@@ -11922,6 +12245,7 @@ function buildV2HealPatch(repoOrSlug, meta) {
|
|
|
11922
12245
|
}
|
|
11923
12246
|
}
|
|
11924
12247
|
const appOwnedGaps = confidentType ? appGapsFor(meta, model, slug, confidentType) : [`Project type is unset and not derivable \u2014 classify with \`mmi-cli project set ${repo} --project-type <web-app|hub-service|content|desktop-game|non-deployable|cli-tool|worker> --deploy-model <tenant-container|solo-container|hub-serverless|serverless|registry-publish|content|none>\` before heal completes the v2 fields (prevents defaulting a non-web repo to tenant-container).`];
|
|
12248
|
+
if (boardRegistryGaps(meta).length) appOwnedGaps.unshift(boardRegistryGapMessage(repo));
|
|
11925
12249
|
return { slug, patch, appOwnedGaps };
|
|
11926
12250
|
}
|
|
11927
12251
|
async function runV2Heal(repoOrSlug, opts, deps) {
|
|
@@ -12011,7 +12335,7 @@ async function buildV2Doctor(repoOrSlug, deps) {
|
|
|
12011
12335
|
const missing = required.filter((key) => !presentSecrets.has(key));
|
|
12012
12336
|
return [stage2, { required, present, missing }];
|
|
12013
12337
|
}));
|
|
12014
|
-
const metaMissing = ["class", "projectType", "deployModel", "vaultPath", "kbPointer"].filter((key) => meta[key] === void 0);
|
|
12338
|
+
const metaMissing = ["class", "projectType", "deployModel", "vaultPath", "kbPointer"].filter((key) => meta[key] === void 0).concat(boardRegistryGaps(meta));
|
|
12015
12339
|
const ok = !secretsError && metaMissing.length === 0 && Object.values(deployCoords).every((v) => v.ok) && Object.values(secrets2).every((v) => v.missing.length === 0);
|
|
12016
12340
|
const edgeDomainWarnings = deps.resolveDns ? await probeEdgeDomains(meta, deps.resolveDns) : [];
|
|
12017
12341
|
return {
|
|
@@ -12732,9 +13056,13 @@ async function relevantPlans(deps, signals, opts = {}) {
|
|
|
12732
13056
|
deps.err(`northstar relevant: ${e.message}`);
|
|
12733
13057
|
return;
|
|
12734
13058
|
}
|
|
12735
|
-
if (!plans.length)
|
|
13059
|
+
if (!plans.length) {
|
|
13060
|
+
if (opts.json) return deps.log(JSON.stringify({ ranked: [] }));
|
|
13061
|
+
return deps.log("no North Stars for this repo yet");
|
|
13062
|
+
}
|
|
12736
13063
|
const ranked = rankPlansByRelevance(plans, signals, { includeAll: opts.includeAll });
|
|
12737
13064
|
const top = (opts.includeAll ? ranked : ranked.filter((r) => r.score > 0)).slice(0, opts.limit ?? 5);
|
|
13065
|
+
if (opts.json) return deps.log(JSON.stringify({ ranked: top }));
|
|
12738
13066
|
if (!top.length) {
|
|
12739
13067
|
return deps.log(`no task-relevant North Stars among ${plans.length} for this repo \u2014 \`mmi-cli northstar relevant --all\` lists recent ones`);
|
|
12740
13068
|
}
|
|
@@ -12816,9 +13144,14 @@ async function planSync(deps, opts = {}) {
|
|
|
12816
13144
|
if (!opts.quiet) deps.err(`northstar sync: list refresh failed: ${e.message}`);
|
|
12817
13145
|
}
|
|
12818
13146
|
}
|
|
12819
|
-
async function planStatus(deps) {
|
|
13147
|
+
async function planStatus(deps, opts = {}) {
|
|
12820
13148
|
const queue = parseQueue(deps.readQueueRaw());
|
|
12821
13149
|
const idx = parseIndex(deps.readIndexRaw());
|
|
13150
|
+
if (opts.json) {
|
|
13151
|
+
deps.log(JSON.stringify({ queue, index: idx }));
|
|
13152
|
+
if (queue.some((e) => e.conflict || e.deadLettered)) process.exitCode = 1;
|
|
13153
|
+
return;
|
|
13154
|
+
}
|
|
12822
13155
|
for (const e of queue) {
|
|
12823
13156
|
if (e.conflict) deps.err(`${e.slug} \xB7 CONFLICT \u2014 ${e.conflict}`);
|
|
12824
13157
|
else if (e.deadLettered) deps.err(`${e.slug} \xB7 DEAD-LETTER \u2014 ${e.deadLettered}`);
|
|
@@ -13277,7 +13610,7 @@ async function runWhoami(io = consoleIo) {
|
|
|
13277
13610
|
io.log(JSON.stringify(report));
|
|
13278
13611
|
return report;
|
|
13279
13612
|
}
|
|
13280
|
-
program2.command("whoami").description('resolve the logged-in human: {login, source, sessionExpiresAt} JSON; source "unknown" (exit 0) when neither the Hub session nor gh can name them').action(async () => {
|
|
13613
|
+
program2.command("whoami").description('resolve the logged-in human: {login, source, sessionExpiresAt} JSON; source "unknown" (exit 0) when neither the Hub session nor gh can name them').option("--json", "machine-readable output (default)").action(async () => {
|
|
13281
13614
|
await runWhoami();
|
|
13282
13615
|
});
|
|
13283
13616
|
program2.command("gc").description("dry-run cleanup for merged/closed PR branches and stale tracking refs").option("--dry-run", "show what would be deleted (default)").option("--apply", "delete only the listed clean merged/closed PR branches and stale tracking refs").option("--json", "machine-readable output").option("--remote <name>", "remote name", "origin").option("--limit <n>", "PRs to inspect per state", "200").action(async (o) => {
|
|
@@ -13560,12 +13893,12 @@ function registerNorthStarCommands(cmd) {
|
|
|
13560
13893
|
if (!ok) process.exitCode = 1;
|
|
13561
13894
|
}));
|
|
13562
13895
|
cmd.command("list").description("list your North Star plans (cross-device)").option("--project <name>", "filter by project").option("--json", "machine-readable output").option("--quiet", "silent when unconfigured/empty/unreachable (SessionStart hook)").option("--fresh", "bypass the local cache and read from the server").action((o) => withPlan(o.quiet ?? false, (d) => planList(d, o)));
|
|
13563
|
-
cmd.command("relevant").description("list North Stars relevant to your current task (branch + claimed issue + changed files)").option("--project <name>", "override the project key").option("--all", "include superseded/graduated and recent non-matching plans").option("--limit <n>", "max results (default 5)").option("--fresh", "bypass the local cache and read from the server").action((o) => withPlan(false, async (d) => {
|
|
13896
|
+
cmd.command("relevant").description("list North Stars relevant to your current task (branch + claimed issue + changed files)").option("--project <name>", "override the project key").option("--all", "include superseded/graduated and recent non-matching plans").option("--limit <n>", "max results (default 5)").option("--fresh", "bypass the local cache and read from the server").option("--json", "machine-readable output").action((o) => withPlan(false, async (d) => {
|
|
13564
13897
|
const signals = await gatherRelevanceSignals();
|
|
13565
|
-
await relevantPlans(d, signals, { project: o.project, includeAll: o.all, limit: o.limit ? Number(o.limit) : void 0, fresh: o.fresh });
|
|
13898
|
+
await relevantPlans(d, signals, { project: o.project, includeAll: o.all, limit: o.limit ? Number(o.limit) : void 0, fresh: o.fresh, json: o.json });
|
|
13566
13899
|
}));
|
|
13567
13900
|
cmd.command("sync").description("drain queued background pushes and refresh the local plan index").option("--quiet", "silent (background worker)").action((o) => withPlan(o.quiet ?? false, (d) => planSync(d, o)));
|
|
13568
|
-
cmd.command("status").description("show pending/conflicted background pushes and the plan-cache age").action(() => withPlan(false, (d) => planStatus(d)));
|
|
13901
|
+
cmd.command("status").description("show pending/conflicted background pushes and the plan-cache age").option("--json", "machine-readable output").action((o) => withPlan(false, (d) => planStatus(d, o)));
|
|
13569
13902
|
cmd.command("reconcile").description("refresh stale local etags from the server without --force (recovers from an object-store re-stamp)").action(() => withPlan(false, (d) => planReconcile(d)));
|
|
13570
13903
|
cmd.command("open <slug>").description("pull if needed, then open plans/<slug>.md in $EDITOR").option("--project <name>", "override the project key").action(
|
|
13571
13904
|
(slug, o) => withPlan(false, async (d) => {
|
|
@@ -14204,6 +14537,41 @@ verify.command("synthesize").description("merge lens JSON array into a PanelRepo
|
|
|
14204
14537
|
return fail(`verify synthesize: ${e.message}`);
|
|
14205
14538
|
}
|
|
14206
14539
|
});
|
|
14540
|
+
var fusion = verify.command("fusion").description("optional hosted fusion provider for grind verify (#1377)");
|
|
14541
|
+
fusion.command("plan").description("plan a hosted fusion job \u2014 print FusionPlan JSON (falls back to host panel when provider unset)").requiredOption("--criteria-file <path>", "UTF-8 file with success criteria").requiredOption("--diff-file <path>", "UTF-8 file with git diff output").option("--routing <routing>", "Balanced | Budget | Paranoid", "Balanced").option("--lenses <list>", `comma-separated lens names (default: ${GRIND_LENSES.join(",")})`, GRIND_LENSES.join(",")).option("--provider-url <url>", "fusion provider base URL (else MMI_FUSION_PROVIDER_URL)").option("--web-search", "enable bounded web search in fusion tool policy").action(async (o) => {
|
|
14542
|
+
try {
|
|
14543
|
+
const routing = parseFusionRouting(o.routing);
|
|
14544
|
+
const lenses = parseFusionLenses(o.lenses);
|
|
14545
|
+
const criteria = await (0, import_promises5.readFile)(o.criteriaFile, "utf8");
|
|
14546
|
+
const diff = await (0, import_promises5.readFile)(o.diffFile, "utf8");
|
|
14547
|
+
const plan2 = buildFusionPlan({
|
|
14548
|
+
routing,
|
|
14549
|
+
lenses,
|
|
14550
|
+
criteria,
|
|
14551
|
+
diff,
|
|
14552
|
+
providerUrl: o.providerUrl ?? null,
|
|
14553
|
+
toolPolicy: {
|
|
14554
|
+
webSearch: Boolean(o.webSearch),
|
|
14555
|
+
maxQueriesPerLens: 3,
|
|
14556
|
+
denyDomains: [...DEFAULT_SEARCH_DENY_DOMAINS]
|
|
14557
|
+
}
|
|
14558
|
+
});
|
|
14559
|
+
console.log(JSON.stringify(plan2));
|
|
14560
|
+
} catch (e) {
|
|
14561
|
+
return fail(`verify fusion plan: ${e.message}`);
|
|
14562
|
+
}
|
|
14563
|
+
});
|
|
14564
|
+
fusion.command("run").description("execute hosted fusion from a FusionPlan JSON file; prints PanelReport or fallback envelope").requiredOption("--plan-file <path>", "UTF-8 FusionPlan JSON from verify fusion plan").option("--provider-url <url>", "override fusion provider URL (else plan.provider or MMI_FUSION_PROVIDER_URL)").action(async (o) => {
|
|
14565
|
+
try {
|
|
14566
|
+
const raw = await (0, import_promises5.readFile)(o.planFile, "utf8");
|
|
14567
|
+
const plan2 = JSON.parse(raw);
|
|
14568
|
+
const result = await runFusionProvider(plan2, { providerUrl: o.providerUrl ?? plan2.provider ?? null });
|
|
14569
|
+
console.log(JSON.stringify(result));
|
|
14570
|
+
if (!result.ok) process.exitCode = 1;
|
|
14571
|
+
} catch (e) {
|
|
14572
|
+
return fail(`verify fusion run: ${e.message}`);
|
|
14573
|
+
}
|
|
14574
|
+
});
|
|
14207
14575
|
program2.command("skill-lesson").description("file a skill-lesson on the Hub board (GitHub auth, dedups open lessons) and print {number,url} JSON").requiredOption("--skill <name>", `which skill misfired (${SKILL_NAMES.join(" | ")})`).option("--title <title>", "one-line summary of what misfired").option("--title-file <path|->", "read the one-line summary from a UTF-8 file, or from stdin with -").option("--body <body>", "lesson body: what misfired, the evidence, and the proposed amendment (markdown)").option("--body-file <path|->", "read the lesson body from a UTF-8 file, or from stdin with -").option("--priority <priority>", "urgent | high | medium | low (board Priority field when configured)", "medium").option("--repo <owner/repo>", `target repo (defaults to the org Hub: ${HUB_REPO})`).option("--force", "file a new issue even when an open lesson looks like a duplicate").option("--json", "machine-readable output (already the default \u2014 skill-lesson always prints JSON)").action(async (o) => {
|
|
14208
14576
|
const targetRepo2 = o.repo ?? HUB_REPO;
|
|
14209
14577
|
const sourceRepo = await resolveRepo(void 0);
|
|
@@ -14276,7 +14644,7 @@ pr.command("create").description("create a PR and print {number,url} JSON").opti
|
|
|
14276
14644
|
const created = await ghCreate(buildPrArgs({ title, body, base: o.base, head: o.head, repo: o.repo }));
|
|
14277
14645
|
console.log(JSON.stringify(created));
|
|
14278
14646
|
});
|
|
14279
|
-
async function
|
|
14647
|
+
async function remoteBranchExists2(branch, options = {}) {
|
|
14280
14648
|
return checkRemoteBranchExists(branch, {
|
|
14281
14649
|
execGit: async (args) => (await execFileP2("git", args, { timeout: GIT_TIMEOUT_MS })).stdout
|
|
14282
14650
|
}, options);
|
|
@@ -14295,7 +14663,11 @@ async function createDeferredWorktreeStore() {
|
|
|
14295
14663
|
}
|
|
14296
14664
|
},
|
|
14297
14665
|
write: async (entries) => {
|
|
14298
|
-
|
|
14666
|
+
try {
|
|
14667
|
+
await (0, import_promises5.mkdir)((0, import_node_path13.dirname)(registryPath), { recursive: true });
|
|
14668
|
+
await (0, import_promises5.writeFile)(registryPath, serializeDeferredWorktrees(entries), "utf8");
|
|
14669
|
+
} catch {
|
|
14670
|
+
}
|
|
14299
14671
|
}
|
|
14300
14672
|
};
|
|
14301
14673
|
} catch {
|
|
@@ -14330,7 +14702,7 @@ pr.command("merge <number>").description("merge a PR (squash by default) and cle
|
|
|
14330
14702
|
const beforeWorktrees = parseWorktreePorcelain(
|
|
14331
14703
|
(await execFileP2("git", ["worktree", "list", "--porcelain"], { timeout: GIT_TIMEOUT_MS }).catch(() => ({ stdout: "" }))).stdout
|
|
14332
14704
|
);
|
|
14333
|
-
const remoteBefore = repoArgs.length ? void 0 : await
|
|
14705
|
+
const remoteBefore = repoArgs.length ? void 0 : await remoteBranchExists2(headRef);
|
|
14334
14706
|
let remoteDeleteAttempted = false;
|
|
14335
14707
|
let remoteNotAttemptedReason = repoArgs.length ? "repo-option" : void 0;
|
|
14336
14708
|
await execFileP2("gh", buildPrMergeArgs({ number, repoArgs, method, auto: o.auto }), { timeout: GH_MUTATION_TIMEOUT_MS }).catch((e) => {
|
|
@@ -14358,25 +14730,39 @@ pr.command("merge <number>").description("merge a PR (squash by default) and cle
|
|
|
14358
14730
|
attempted: false,
|
|
14359
14731
|
reason: remoteNotAttemptedReason
|
|
14360
14732
|
}) : await buildPrMergeRemoteBranchCleanupReport(headRef, {
|
|
14361
|
-
exists:
|
|
14733
|
+
exists: remoteBranchExists2
|
|
14362
14734
|
}, {
|
|
14363
14735
|
attempted: remoteDeleteAttempted,
|
|
14364
14736
|
existedBefore: remoteBefore,
|
|
14365
14737
|
reason: remoteNotAttemptedReason
|
|
14366
14738
|
});
|
|
14367
14739
|
const deferredStore = await createDeferredWorktreeStore();
|
|
14368
|
-
|
|
14369
|
-
|
|
14370
|
-
|
|
14371
|
-
|
|
14372
|
-
|
|
14373
|
-
|
|
14374
|
-
|
|
14375
|
-
|
|
14376
|
-
|
|
14377
|
-
|
|
14378
|
-
|
|
14379
|
-
|
|
14740
|
+
let localCleanup;
|
|
14741
|
+
try {
|
|
14742
|
+
localCleanup = repoArgs.length ? {
|
|
14743
|
+
branch: headRef,
|
|
14744
|
+
localBranch: { name: headRef, status: "not-attempted", reason: "repo-option" },
|
|
14745
|
+
worktree: void 0
|
|
14746
|
+
} : await cleanupPrMergeLocalBranch(headRef, {
|
|
14747
|
+
beforeWorktrees,
|
|
14748
|
+
startingPath,
|
|
14749
|
+
pathExists: (p) => (0, import_node_fs14.existsSync)(p),
|
|
14750
|
+
execGit: async (args) => (await execFileP2("git", args, { timeout: GIT_TIMEOUT_MS })).stdout,
|
|
14751
|
+
teardownWorktreeStage,
|
|
14752
|
+
deferredStore,
|
|
14753
|
+
removeWorktreeDir: worktreeRemoveDeps(async (args) => (await execFileP2("git", args, { timeout: GIT_TIMEOUT_MS })).stdout).removeWorktreeDir
|
|
14754
|
+
});
|
|
14755
|
+
} catch (e) {
|
|
14756
|
+
localCleanup = {
|
|
14757
|
+
branch: headRef,
|
|
14758
|
+
localBranch: {
|
|
14759
|
+
name: headRef,
|
|
14760
|
+
status: "failed",
|
|
14761
|
+
reason: "cleanup-exception",
|
|
14762
|
+
error: e instanceof Error ? e.message : String(e)
|
|
14763
|
+
}
|
|
14764
|
+
};
|
|
14765
|
+
}
|
|
14380
14766
|
console.log(JSON.stringify(buildPrMergeResultPayload({
|
|
14381
14767
|
number,
|
|
14382
14768
|
branch: headRef,
|
|
@@ -14938,6 +15324,7 @@ bootstrap.command("apply <repo>").description("idempotent seed apply from skills
|
|
|
14938
15324
|
json: rawFlag("--json")
|
|
14939
15325
|
};
|
|
14940
15326
|
if (o.class !== "deployable" && o.class !== "content") return fail("bootstrap apply: --class must be deployable or content");
|
|
15327
|
+
const bootstrapReleaseTrack = resolveBootstrapReleaseTrack(o.class, o.releaseTrack || void 0);
|
|
14941
15328
|
let parsedRepo;
|
|
14942
15329
|
try {
|
|
14943
15330
|
parsedRepo = parseOwnerRepo(repo);
|
|
@@ -14957,7 +15344,7 @@ bootstrap.command("apply <repo>").description("idempotent seed apply from skills
|
|
|
14957
15344
|
const eq = value.indexOf("=");
|
|
14958
15345
|
if (eq > 0) rawVars[value.slice(0, eq)] = value.slice(eq + 1);
|
|
14959
15346
|
}
|
|
14960
|
-
const vars = withDerivedRepoVars(rawVars, parsedRepo, o.class);
|
|
15347
|
+
const vars = withDerivedRepoVars(rawVars, parsedRepo, o.class, bootstrapReleaseTrack);
|
|
14961
15348
|
if (vars.PROJECT_ID) {
|
|
14962
15349
|
try {
|
|
14963
15350
|
const r = await gh(boardFieldsQueryArgs(vars.PROJECT_ID));
|
|
@@ -15025,7 +15412,7 @@ bootstrap.command("apply <repo>").description("idempotent seed apply from skills
|
|
|
15025
15412
|
registerPayload = buildRegisterPayload(repo, o.class, vars, {
|
|
15026
15413
|
projectType: o.projectType || void 0,
|
|
15027
15414
|
deployModel: o.deployModel || void 0,
|
|
15028
|
-
releaseTrack:
|
|
15415
|
+
releaseTrack: bootstrapReleaseTrack
|
|
15029
15416
|
});
|
|
15030
15417
|
} catch (e) {
|
|
15031
15418
|
return fail(`bootstrap apply: ${e.message}`);
|
|
@@ -15329,6 +15716,39 @@ function quarantinePluginCacheDirs(plan2) {
|
|
|
15329
15716
|
return moved;
|
|
15330
15717
|
}
|
|
15331
15718
|
var gitignorePath = () => (0, import_node_path13.join)(process.cwd(), ".gitignore");
|
|
15719
|
+
function readTextFile(path2) {
|
|
15720
|
+
try {
|
|
15721
|
+
if (!(0, import_node_fs14.existsSync)(path2)) return null;
|
|
15722
|
+
return (0, import_node_fs14.readFileSync)(path2, "utf8");
|
|
15723
|
+
} catch {
|
|
15724
|
+
return null;
|
|
15725
|
+
}
|
|
15726
|
+
}
|
|
15727
|
+
function playwrightMcpConfigSnapshots() {
|
|
15728
|
+
const cwd = process.cwd();
|
|
15729
|
+
const home = (0, import_node_os4.homedir)();
|
|
15730
|
+
const candidates = [
|
|
15731
|
+
(0, import_node_path13.join)(cwd, ".cursor", "mcp.json"),
|
|
15732
|
+
(0, import_node_path13.join)(home, ".cursor", "mcp.json"),
|
|
15733
|
+
(0, import_node_path13.join)(home, ".codex", "config.toml")
|
|
15734
|
+
];
|
|
15735
|
+
const out = [];
|
|
15736
|
+
for (const path2 of candidates) {
|
|
15737
|
+
const content = readTextFile(path2);
|
|
15738
|
+
if (content != null) out.push({ path: path2, content });
|
|
15739
|
+
}
|
|
15740
|
+
return out;
|
|
15741
|
+
}
|
|
15742
|
+
function strayBrowserArtifactPaths() {
|
|
15743
|
+
const cwd = process.cwd();
|
|
15744
|
+
return STRAY_BROWSER_ARTIFACT_DIRS.filter((rel) => {
|
|
15745
|
+
try {
|
|
15746
|
+
return (0, import_node_fs14.existsSync)((0, import_node_path13.join)(cwd, rel));
|
|
15747
|
+
} catch {
|
|
15748
|
+
return false;
|
|
15749
|
+
}
|
|
15750
|
+
});
|
|
15751
|
+
}
|
|
15332
15752
|
function readGitignore() {
|
|
15333
15753
|
try {
|
|
15334
15754
|
return (0, import_node_fs14.readFileSync)(gitignorePath(), "utf8");
|
|
@@ -15525,6 +15945,18 @@ async function runDoctor(opts, io = consoleIo) {
|
|
|
15525
15945
|
mmiCliOnPath: onPath
|
|
15526
15946
|
})
|
|
15527
15947
|
);
|
|
15948
|
+
checks.push(
|
|
15949
|
+
buildPlaywrightMcpVisionCapCheck({
|
|
15950
|
+
isOrgRepo: Boolean(cfg.sagaApiUrl),
|
|
15951
|
+
configs: playwrightMcpConfigSnapshots()
|
|
15952
|
+
})
|
|
15953
|
+
);
|
|
15954
|
+
checks.push(
|
|
15955
|
+
buildBrowserArtifactsCheck({
|
|
15956
|
+
isOrgRepo: Boolean(cfg.sagaApiUrl),
|
|
15957
|
+
strayPaths: strayBrowserArtifactPaths()
|
|
15958
|
+
})
|
|
15959
|
+
);
|
|
15528
15960
|
const gaps = checks.filter((c) => !c.ok);
|
|
15529
15961
|
if (opts.banner) {
|
|
15530
15962
|
if (gaps.length) io.log(`\u26A0 MMI setup needed \u2014 ${gaps.map((g) => g.fix).join(" \xB7 ")} \xB7 guide: ${MMI_AGENTIC_ONBOARDING_GUIDE.url}`);
|
|
@@ -15553,7 +15985,7 @@ async function runDoctor(opts, io = consoleIo) {
|
|
|
15553
15985
|
io.log(gaps.length ? `
|
|
15554
15986
|
${gaps.length} item(s) need attention.` : "\nAll set \u2014 you are ready.");
|
|
15555
15987
|
}
|
|
15556
|
-
program2.command("doctor").description("check onboarding gates (GitHub auth, mmi-cli on PATH, Hub API default, plugin git clone, plugin install record, .gitignore managed block, plugin config drift, installed plugin version, Cursor Team Marketplace plugin install), print fixes, and report per-surface versions + update recipes").option("--banner", "one-line resume summary; silent when all gates pass").option("--guide", "print the MMI Agentic Onboarding guide URL").option("--json", "machine-readable output (read-only inspection \u2014 performs no repairs)").option("--apply", "perform the same auto-repairs as the interactive run (combine with --json for a machine-readable repair run)").option("--no-repo-writes", "env/plugin repairs only \u2014 never mutate the repo working tree; report pending managed .gitignore repairs with the follow-up command (for train preflights)").action((opts) => (
|
|
15988
|
+
program2.command("doctor").description("check onboarding gates (GitHub auth, mmi-cli on PATH, Hub API default, plugin git clone, plugin install record, .gitignore managed block, plugin config drift, installed plugin version, Cursor Team Marketplace plugin install, Playwright MCP vision caps, browser artifact hygiene), print fixes, and report per-surface versions + update recipes").option("--banner", "one-line resume summary; silent when all gates pass").option("--guide", "print the MMI Agentic Onboarding guide URL").option("--json", "machine-readable output (read-only inspection \u2014 performs no repairs)").option("--apply", "perform the same auto-repairs as the interactive run (combine with --json for a machine-readable repair run)").option("--no-repo-writes", "env/plugin repairs only \u2014 never mutate the repo working tree; report pending managed .gitignore repairs with the follow-up command (for train preflights)").action((opts) => (
|
|
15557
15989
|
// Commander maps `--no-repo-writes` to `repoWrites: false`; translate to the explicit `noRepoWrites` flag.
|
|
15558
15990
|
runDoctor({ ...opts, noRepoWrites: opts.repoWrites === false })
|
|
15559
15991
|
));
|
package/dist/saga.cjs
CHANGED
|
@@ -4058,7 +4058,7 @@ function setInjectedStdin(payload) {
|
|
|
4058
4058
|
}
|
|
4059
4059
|
async function readStdin() {
|
|
4060
4060
|
if (injectedStdin !== void 0) return injectedStdin;
|
|
4061
|
-
if (process.stdin.isTTY) return "";
|
|
4061
|
+
if (process.stdin.isTTY !== false) return "";
|
|
4062
4062
|
const chunks = [];
|
|
4063
4063
|
for await (const chunk of process.stdin) chunks.push(chunk);
|
|
4064
4064
|
return Buffer.concat(chunks).toString("utf8");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mutmutco/cli",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.32.1",
|
|
4
4
|
"description": "MMI Future CLI — delivers the org rules (whole-file), plus saga and KB access. The cross-IDE engine the plugin's SessionStart hook drives.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "UNLICENSED",
|