mergestorm-mcp 0.2.25 → 0.2.27
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/README.md +17 -4
- package/dist/index.js +456 -101
- package/dist/lib/brand-icons.d.ts +16 -0
- package/dist/lib/brand-icons.js +23 -0
- package/dist/lib/hosted.d.ts +7 -0
- package/dist/lib/hosted.js +21 -0
- package/dist/lib/index.d.ts +1 -0
- package/dist/lib/index.js +5 -0
- package/dist/lib/pr-loop-instructions.d.ts +13 -0
- package/dist/lib/pr-loop-instructions.js +62 -0
- package/dist/lib/server.d.ts +22 -0
- package/dist/lib/server.js +558 -0
- package/dist/lib/tools/agent-request.d.ts +6 -0
- package/dist/lib/tools/agent-request.js +25 -0
- package/dist/lib/tools/chat-wake.d.ts +21 -0
- package/dist/lib/tools/chat-wake.js +26 -0
- package/dist/lib/tools/credits.d.ts +3 -0
- package/dist/lib/tools/credits.js +38 -0
- package/dist/lib/tools/cyclone-job.d.ts +29 -0
- package/dist/lib/tools/cyclone-job.js +70 -0
- package/dist/lib/tools/envelope.d.ts +5 -0
- package/dist/lib/tools/envelope.js +30 -0
- package/dist/lib/tools/issue-create.d.ts +47 -0
- package/dist/lib/tools/issue-create.js +53 -0
- package/dist/lib/tools/queue-status.d.ts +3 -0
- package/dist/lib/tools/queue-status.js +79 -0
- package/dist/lib/tools/repo-settings.d.ts +6 -0
- package/dist/lib/tools/repo-settings.js +54 -0
- package/dist/lib/tools/review-dismiss.d.ts +16 -0
- package/dist/lib/tools/review-dismiss.js +68 -0
- package/dist/lib/tools/review-get-pr.d.ts +22 -0
- package/dist/lib/tools/review-get-pr.js +52 -0
- package/dist/lib/tools/review-get.d.ts +3 -0
- package/dist/lib/tools/review-get.js +27 -0
- package/dist/lib/tools/review-list.d.ts +3 -0
- package/dist/lib/tools/review-list.js +10 -0
- package/dist/lib/tools/review-start.d.ts +11 -0
- package/dist/lib/tools/review-start.js +21 -0
- package/dist/lib/tools/review-submit.d.ts +36 -0
- package/dist/lib/tools/review-submit.js +172 -0
- package/dist/lib/tools/review-wait-pr.d.ts +11 -0
- package/dist/lib/tools/review-wait-pr.js +74 -0
- package/dist/lib/tools/review-wait.d.ts +6 -0
- package/dist/lib/tools/review-wait.js +33 -0
- package/dist/lib/tools/settings-get.d.ts +8 -0
- package/dist/lib/tools/settings-get.js +33 -0
- package/dist/lib/tools/settings-set.d.ts +11 -0
- package/dist/lib/tools/settings-set.js +62 -0
- package/dist/lib/tools/stack-actions.d.ts +4 -0
- package/dist/lib/tools/stack-actions.js +22 -0
- package/dist/lib/tools/stack-adopt.d.ts +44 -0
- package/dist/lib/tools/stack-adopt.js +55 -0
- package/dist/lib/tools/stack-list.d.ts +3 -0
- package/dist/lib/tools/stack-list.js +30 -0
- package/dist/lib/tools/stack-publish.d.ts +78 -0
- package/dist/lib/tools/stack-publish.js +89 -0
- package/dist/lib/tools/stack-set.d.ts +16 -0
- package/dist/lib/tools/stack-set.js +69 -0
- package/dist/lib/tools/stack-status.d.ts +3 -0
- package/dist/lib/tools/stack-status.js +57 -0
- package/dist/lib/tools/stack-summary.d.ts +2 -0
- package/dist/lib/tools/stack-summary.js +29 -0
- package/dist/lib/tools/stack-wait.d.ts +24 -0
- package/dist/lib/tools/stack-wait.js +84 -0
- package/dist/lib/tools/surge-setup.d.ts +6 -0
- package/dist/lib/tools/surge-setup.js +4 -0
- package/dist/lib/tools/types.d.ts +5 -0
- package/dist/lib/tools/types.js +1 -0
- package/dist/lib/tools/whoami.d.ts +3 -0
- package/dist/lib/tools/whoami.js +27 -0
- package/package.json +11 -3
package/dist/index.js
CHANGED
|
@@ -3,10 +3,33 @@
|
|
|
3
3
|
// src/index.ts
|
|
4
4
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
5
5
|
|
|
6
|
+
// src/tools/chat-wake.ts
|
|
7
|
+
import { apiFetch } from "mergestorm/client";
|
|
8
|
+
var CHAT_ID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
9
|
+
function breezeChatId(value) {
|
|
10
|
+
const trimmed = value?.trim() ?? "";
|
|
11
|
+
return CHAT_ID.test(trimmed) ? trimmed.toLowerCase() : null;
|
|
12
|
+
}
|
|
13
|
+
var CHAT_WAKE_REVIEW_NOTE = "Keep waiting: call review_wait_pr again until the review finishes, then tell the user its verdict and findings. If your reply ends first, this chat is woken with the review's result.";
|
|
14
|
+
var REVIEW_START_WAIT_NOTE = "Wait for it now: call review_wait_pr for this pull request until the review finishes, then tell the user its verdict and findings. If your reply ends first, this chat is woken with the review's result.";
|
|
15
|
+
var CHAT_WAKE_STACK_NOTE = "If your reply ends before the stack is done, this chat is woken when the stack lands, is bounced out of the merge queue, or is archived.";
|
|
16
|
+
async function registerChatWake(cfg, chatId, target2, signal) {
|
|
17
|
+
const json = target2.kind === "vortex" ? { chat_id: chatId, kind: "vortex", owner: target2.owner, repo: target2.repo, pr_number: target2.prNumber, head_sha: target2.headSha, min_pass: target2.headSha ? target2.minPass : null } : { chat_id: chatId, kind: "stack", stack_id: target2.stackId };
|
|
18
|
+
try {
|
|
19
|
+
const response = await apiFetch(cfg, "/api/v1/stacks/chat-waits", { method: "POST", json, signal, timeoutMs: 1e4 });
|
|
20
|
+
const body = response.body;
|
|
21
|
+
if (response.status !== 200 || !body || typeof body.id !== "string") return null;
|
|
22
|
+
if (body.result !== "waiting" && body.result !== "existing" && body.result !== "finished") return null;
|
|
23
|
+
return { id: body.id, result: body.result };
|
|
24
|
+
} catch {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
6
29
|
// src/server.ts
|
|
7
30
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
8
31
|
import { z as z5 } from "zod";
|
|
9
|
-
import { CommandError as
|
|
32
|
+
import { CommandError as CommandError15, BEARER_SETTINGS as BEARER_SETTINGS2 } from "mergestorm/client";
|
|
10
33
|
|
|
11
34
|
// src/brand-icons.ts
|
|
12
35
|
var MARK_RECTS = [
|
|
@@ -86,6 +109,13 @@ var issueCreateSchema = {
|
|
|
86
109
|
repo: z.string().trim().regex(/^[A-Za-z0-9_.-]{1,100}$/).optional(),
|
|
87
110
|
cwd: z.string().trim().min(1).optional()
|
|
88
111
|
};
|
|
112
|
+
var hostedIssueCreateSchema = {
|
|
113
|
+
title: issueCreateSchema.title,
|
|
114
|
+
body: issueCreateSchema.body,
|
|
115
|
+
labels: issueCreateSchema.labels,
|
|
116
|
+
owner: z.string().trim().regex(/^[A-Za-z0-9_.-]{1,100}$/),
|
|
117
|
+
repo: z.string().trim().regex(/^[A-Za-z0-9_.-]{1,100}$/)
|
|
118
|
+
};
|
|
89
119
|
var inputSchema = z.object(issueCreateSchema);
|
|
90
120
|
async function issueCreate(input, cfg, deps = {}) {
|
|
91
121
|
const parsed = inputSchema.safeParse(input);
|
|
@@ -126,7 +156,7 @@ async function issueCreate(input, cfg, deps = {}) {
|
|
|
126
156
|
|
|
127
157
|
// src/tools/queue-status.ts
|
|
128
158
|
import {
|
|
129
|
-
apiFetch,
|
|
159
|
+
apiFetch as apiFetch2,
|
|
130
160
|
CommandError as CommandError3,
|
|
131
161
|
isCommandErrorCode,
|
|
132
162
|
loadConfig as loadConfig2,
|
|
@@ -150,7 +180,7 @@ async function queueStatus(stackId, cfg) {
|
|
|
150
180
|
try {
|
|
151
181
|
const resolved = cfg ?? await loadConfig2();
|
|
152
182
|
const id = stackId?.trim();
|
|
153
|
-
const { status, body, retryAfterSeconds } = await
|
|
183
|
+
const { status, body, retryAfterSeconds } = await apiFetch2(
|
|
154
184
|
resolved,
|
|
155
185
|
id ? `/api/v1/stacks/queue?stackId=${encodeURIComponent(id)}` : "/api/v1/stacks/queue"
|
|
156
186
|
);
|
|
@@ -226,6 +256,7 @@ async function queueStatus(stackId, cfg) {
|
|
|
226
256
|
// src/tools/review-dismiss.ts
|
|
227
257
|
import {
|
|
228
258
|
dismissPrFindings,
|
|
259
|
+
dismissThreadsLine,
|
|
229
260
|
stackWatchObligation as stackWatchObligation2
|
|
230
261
|
} from "mergestorm/client";
|
|
231
262
|
|
|
@@ -279,10 +310,11 @@ function stackWaitStatusLine(envelope) {
|
|
|
279
310
|
...envelope.cursor.afterFinishedAt !== void 0 ? [`after_finished_at ${JSON.stringify(envelope.cursor.afterFinishedAt)}`] : [],
|
|
280
311
|
...envelope.cursor.bounceId !== void 0 ? [`bounce_id ${JSON.stringify(envelope.cursor.bounceId)}`] : []
|
|
281
312
|
];
|
|
282
|
-
return `${base} \xB7 watch not finished: only with a confirmed host notification that resumes this task, run ${stackWatchBackgroundCommand(envelope.stackId)} as a background command and end your turn; otherwise call stack_wait again with timeout_s 45 and the same cursor: ${selectors.join(", ")}`;
|
|
313
|
+
return `${base} \xB7 watch not finished: only with a confirmed host notification that resumes this task, run ${stackWatchBackgroundCommand(envelope.stackId, envelope.cursor)} as a background command and end your turn; otherwise call stack_wait again with timeout_s 45 and the same cursor: ${selectors.join(", ")}`;
|
|
283
314
|
}
|
|
284
315
|
async function stackWait(input, cfg, deps = {}) {
|
|
285
316
|
const args = z2.object(stackWaitSchema).parse(input);
|
|
317
|
+
const config = cfg ?? await loadConfig3();
|
|
286
318
|
const cursor = args.enrolled_head_sha !== void 0 || args.after_finished_at !== void 0 || args.bounce_id !== void 0 ? {
|
|
287
319
|
stackId: args.stack_id,
|
|
288
320
|
enrolledHeadSha: args.enrolled_head_sha ?? null,
|
|
@@ -291,7 +323,7 @@ async function stackWait(input, cfg, deps = {}) {
|
|
|
291
323
|
} : void 0;
|
|
292
324
|
let envelope;
|
|
293
325
|
try {
|
|
294
|
-
envelope = await (deps.pollStackWatch ?? pollStackWatch)(
|
|
326
|
+
envelope = await (deps.pollStackWatch ?? pollStackWatch)(config, args.stack_id, {
|
|
295
327
|
timeoutMs: args.timeout_s * 1e3,
|
|
296
328
|
cursor,
|
|
297
329
|
signal: deps.signal
|
|
@@ -312,9 +344,12 @@ async function stackWait(input, cfg, deps = {}) {
|
|
|
312
344
|
envelope = err.lastEnvelope;
|
|
313
345
|
}
|
|
314
346
|
const sealed = { ...envelope, watch: envelopeWatch(envelope) };
|
|
347
|
+
const wake = deps.chatId && !sealed.watch.done ? await registerChatWake(config, deps.chatId, { kind: "stack", stackId: sealed.stackId }, deps.signal) : null;
|
|
315
348
|
return {
|
|
316
|
-
summary: stackWaitSummary(sealed)
|
|
317
|
-
|
|
349
|
+
summary: wake ? `${stackWaitSummary(sealed)}
|
|
350
|
+
|
|
351
|
+
${CHAT_WAKE_STACK_NOTE}` : stackWaitSummary(sealed),
|
|
352
|
+
data: wake ? { ...sealed, chat_wake: wake } : sealed
|
|
318
353
|
};
|
|
319
354
|
}
|
|
320
355
|
|
|
@@ -335,6 +370,8 @@ function reviewDismissSummary(result) {
|
|
|
335
370
|
`${pr} ${head}: dismissed ${result.dismissed.length}, already dismissed ${result.already_dismissed.length}, still open ${result.remaining.length}.`
|
|
336
371
|
);
|
|
337
372
|
for (const finding of result.remaining) lines.push(`- open: ${findingText(finding)}`);
|
|
373
|
+
const threads = dismissThreadsLine(result);
|
|
374
|
+
if (threads) lines.push(threads);
|
|
338
375
|
}
|
|
339
376
|
const seam = result.gate.seam;
|
|
340
377
|
if (seam) {
|
|
@@ -378,6 +415,144 @@ async function reviewDismiss(input, cfg) {
|
|
|
378
415
|
return { summary: withWatchText(watch, summary), data: { ...result, watch } };
|
|
379
416
|
}
|
|
380
417
|
|
|
418
|
+
// src/tools/agent-request.ts
|
|
419
|
+
import { apiFetch as apiFetch3, CommandError as CommandError4, loadConfig as loadConfig4 } from "mergestorm/client";
|
|
420
|
+
async function agentRequest(route, init, cfg) {
|
|
421
|
+
const resolved = cfg ?? await loadConfig4();
|
|
422
|
+
const { status, body, retryAfterSeconds } = await apiFetch3(resolved, route, { method: init.method, ...init.json ? { json: init.json } : {} });
|
|
423
|
+
const data = body && typeof body === "object" && !Array.isArray(body) ? body : { body };
|
|
424
|
+
if (status === 404 && data.error === void 0) {
|
|
425
|
+
throw new CommandError4("This Mergestorm server does not offer that action yet.", 1, "not_found");
|
|
426
|
+
}
|
|
427
|
+
const message = typeof data.message === "string" ? data.message : `Mergestorm answered ${status}.`;
|
|
428
|
+
if (status >= 400) {
|
|
429
|
+
return {
|
|
430
|
+
summary: message,
|
|
431
|
+
isError: true,
|
|
432
|
+
data: {
|
|
433
|
+
...data,
|
|
434
|
+
error: {
|
|
435
|
+
code: typeof data.error === "string" ? data.error : `http_${status}`,
|
|
436
|
+
message,
|
|
437
|
+
...retryAfterSeconds !== void 0 ? { retry_after_seconds: retryAfterSeconds } : {}
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
};
|
|
441
|
+
}
|
|
442
|
+
return { summary: message, data };
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
// src/tools/review-start.ts
|
|
446
|
+
async function reviewStartPr(input, cfg, chatId) {
|
|
447
|
+
const payload = await agentRequest("/api/v1/agents/vortex/review", {
|
|
448
|
+
method: "POST",
|
|
449
|
+
json: {
|
|
450
|
+
owner: input.owner,
|
|
451
|
+
repo: input.repo,
|
|
452
|
+
pr_number: input.pr_number,
|
|
453
|
+
...input.head_sha ? { head_sha: input.head_sha } : {},
|
|
454
|
+
...chatId ? { chat_id: chatId } : {}
|
|
455
|
+
}
|
|
456
|
+
}, cfg);
|
|
457
|
+
return payload.data.chat_wake ? { ...payload, summary: `${payload.summary} ${REVIEW_START_WAIT_NOTE}` } : payload;
|
|
458
|
+
}
|
|
459
|
+
async function reviewStopPr(input, cfg) {
|
|
460
|
+
return agentRequest("/api/v1/agents/vortex/stop", {
|
|
461
|
+
method: "POST",
|
|
462
|
+
json: { owner: input.owner, repo: input.repo, pr_number: input.pr_number }
|
|
463
|
+
}, cfg);
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
// src/tools/cyclone-job.ts
|
|
467
|
+
var CYCLONE_TASKS = ["fix_findings", "fix_ci", "resolve_conflicts", "build"];
|
|
468
|
+
var CYCLONE_JOB_WAIT_DEFAULT_S = 45;
|
|
469
|
+
var CYCLONE_JOB_WAIT_MAX_S = 100;
|
|
470
|
+
var CYCLONE_JOB_POLL_MS = 5e3;
|
|
471
|
+
var CYCLONE_JOB_TERMINAL_STATES = ["pushed", "completed", "failed"];
|
|
472
|
+
var CYCLONE_START_WAIT_NOTE = `Wait for it now: call cyclone_job with this dispatch_id and timeout_s ${CYCLONE_JOB_WAIT_DEFAULT_S}, again while it is queued or running, then tell the user what Cyclone pushed or why it failed.`;
|
|
473
|
+
var CHAT_WAKE_CYCLONE_NOTE = "If your reply ends before Cyclone finishes, this chat is woken with the job's result.";
|
|
474
|
+
var CYCLONE_JOB_STILL_RUNNING_NOTE = `Cyclone is not done. Call cyclone_job again with this dispatch_id and timeout_s ${CYCLONE_JOB_WAIT_DEFAULT_S}, and report the result once it is pushed, completed or failed.`;
|
|
475
|
+
async function cycloneStart(input, cfg, chatId) {
|
|
476
|
+
const payload = await agentRequest("/api/v1/agents/cyclone/jobs", {
|
|
477
|
+
method: "POST",
|
|
478
|
+
json: {
|
|
479
|
+
task: input.task,
|
|
480
|
+
owner: input.owner,
|
|
481
|
+
repo: input.repo,
|
|
482
|
+
...input.pr_number !== void 0 ? { pr_number: input.pr_number } : {},
|
|
483
|
+
...input.failing_check ? { failing_check: input.failing_check } : {},
|
|
484
|
+
...input.brief ? { brief: input.brief } : {},
|
|
485
|
+
...chatId ? { chat_id: chatId } : {}
|
|
486
|
+
}
|
|
487
|
+
}, cfg);
|
|
488
|
+
if (payload.isError || typeof payload.data.dispatch_id !== "string" || isCycloneJobTerminal(payload.data)) return payload;
|
|
489
|
+
return { ...payload, summary: [payload.summary, CYCLONE_START_WAIT_NOTE, ...payload.data.chat_wake ? [CHAT_WAKE_CYCLONE_NOTE] : []].join(" ") };
|
|
490
|
+
}
|
|
491
|
+
function isCycloneJobTerminal(job) {
|
|
492
|
+
return CYCLONE_JOB_TERMINAL_STATES.includes(job.state);
|
|
493
|
+
}
|
|
494
|
+
function cycloneJobSummary(job) {
|
|
495
|
+
const target2 = job.pr_number ? `${job.owner}/${job.repo}#${job.pr_number}` : `${job.owner}/${job.repo}`;
|
|
496
|
+
const parts = [`Cyclone ${job.task ?? "job"} on ${target2}: ${job.state}`];
|
|
497
|
+
if (typeof job.outcome === "string" && job.outcome) parts[0] += ` (${job.outcome})`;
|
|
498
|
+
if (typeof job.result_sha === "string" && job.result_sha) parts.push(`pushed ${job.result_sha.slice(0, 10)}`);
|
|
499
|
+
if (typeof job.detail === "string" && job.detail) parts.push(job.detail);
|
|
500
|
+
return parts.join(". ");
|
|
501
|
+
}
|
|
502
|
+
function sleepFor(ms, signal) {
|
|
503
|
+
return new Promise((resolve2) => {
|
|
504
|
+
if (signal?.aborted) return resolve2();
|
|
505
|
+
const timer = setTimeout(done, ms);
|
|
506
|
+
function done() {
|
|
507
|
+
clearTimeout(timer);
|
|
508
|
+
signal?.removeEventListener("abort", done);
|
|
509
|
+
resolve2();
|
|
510
|
+
}
|
|
511
|
+
signal?.addEventListener("abort", done, { once: true });
|
|
512
|
+
});
|
|
513
|
+
}
|
|
514
|
+
async function cycloneJob(dispatchId, cfg, timeoutS = 0, deps = {}) {
|
|
515
|
+
const now = deps.now ?? Date.now;
|
|
516
|
+
const sleep = deps.sleep ?? sleepFor;
|
|
517
|
+
const deadline = now() + Math.min(Math.max(timeoutS, 0), CYCLONE_JOB_WAIT_MAX_S) * 1e3;
|
|
518
|
+
const route = `/api/v1/agents/cyclone/jobs/${encodeURIComponent(dispatchId)}`;
|
|
519
|
+
for (; ; ) {
|
|
520
|
+
const payload = await agentRequest(route, { method: "GET" }, cfg);
|
|
521
|
+
if (payload.isError) return payload;
|
|
522
|
+
if (isCycloneJobTerminal(payload.data)) return { ...payload, summary: cycloneJobSummary(payload.data) };
|
|
523
|
+
const left = deadline - now();
|
|
524
|
+
if (left <= 0 || deps.signal?.aborted) return { ...payload, summary: `${cycloneJobSummary(payload.data)}. ${CYCLONE_JOB_STILL_RUNNING_NOTE}` };
|
|
525
|
+
await sleep(Math.min(deps.pollMs ?? CYCLONE_JOB_POLL_MS, left), deps.signal);
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
// src/tools/stack-actions.ts
|
|
530
|
+
import { enqueueStack, loadConfig as loadConfig5, restackStack, stackWatchObligation as stackWatchObligation3 } from "mergestorm/client";
|
|
531
|
+
async function stackRestack(stackId, cfg) {
|
|
532
|
+
const result = await restackStack(stackId, cfg);
|
|
533
|
+
const watch = stackWatchObligation3({ stackId, terminal: null, unread: true, freshCursor: true });
|
|
534
|
+
return {
|
|
535
|
+
summary: withWatchText(watch, `Restacked stack ${stackId}: its layer branches were rebased onto their parents and pushed`),
|
|
536
|
+
data: { stack_id: stackId, result, watch }
|
|
537
|
+
};
|
|
538
|
+
}
|
|
539
|
+
async function stackEnqueue(stackId, cfg, chatId) {
|
|
540
|
+
const resolved = cfg ?? await loadConfig5();
|
|
541
|
+
const result = await enqueueStack(stackId, resolved);
|
|
542
|
+
const watch = stackWatchObligation3({ stackId, terminal: null, unread: true, freshCursor: true });
|
|
543
|
+
const wake = chatId ? await registerChatWake(resolved, chatId, { kind: "stack", stackId }) : null;
|
|
544
|
+
const summary = withWatchText(watch, `Sent stack ${stackId} to the Mergestorm merge queue`);
|
|
545
|
+
return {
|
|
546
|
+
summary: wake ? `${summary} ${CHAT_WAKE_STACK_NOTE}` : summary,
|
|
547
|
+
data: { stack_id: stackId, result, watch, ...wake ? { chat_wake: wake } : {} }
|
|
548
|
+
};
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
// src/tools/surge-setup.ts
|
|
552
|
+
async function surgeSetup(input, cfg) {
|
|
553
|
+
return agentRequest("/api/v1/agents/surge/setup", { method: "POST", json: { owner: input.owner, repo: input.repo } }, cfg);
|
|
554
|
+
}
|
|
555
|
+
|
|
381
556
|
// src/tools/review-list.ts
|
|
382
557
|
import { listJobs, toReviewJobEnvelope } from "mergestorm/client";
|
|
383
558
|
async function reviewList(limit = 10, cfg) {
|
|
@@ -395,13 +570,13 @@ import { createHash } from "node:crypto";
|
|
|
395
570
|
import { realpath } from "node:fs/promises";
|
|
396
571
|
import { relative, resolve, sep } from "node:path";
|
|
397
572
|
import {
|
|
398
|
-
CommandError as
|
|
573
|
+
CommandError as CommandError5,
|
|
399
574
|
REVIEW_THREAD_SLUG_RE,
|
|
400
575
|
ReviewPollTimeoutError,
|
|
401
576
|
collectReviewInput,
|
|
402
577
|
discoverTrunk,
|
|
403
578
|
formatReviewSubmitError,
|
|
404
|
-
loadConfig as
|
|
579
|
+
loadConfig as loadConfig6,
|
|
405
580
|
loadReviewContext,
|
|
406
581
|
pollReview,
|
|
407
582
|
rateLimitedMessage as rateLimitedMessage2,
|
|
@@ -450,7 +625,7 @@ function assertPinnableSpecialists(specialists) {
|
|
|
450
625
|
(id) => !PINNABLE_SPECIALISTS.includes(id)
|
|
451
626
|
);
|
|
452
627
|
if (unknown.length === 0) return;
|
|
453
|
-
throw new
|
|
628
|
+
throw new CommandError5(
|
|
454
629
|
`Unknown specialist: ${unknown.join(", ")}. Valid lanes: ${PINNABLE_SPECIALISTS.join(", ")}. governance and seam cannot be requested.`,
|
|
455
630
|
2,
|
|
456
631
|
"usage"
|
|
@@ -462,7 +637,7 @@ function mcpSandboxRoot() {
|
|
|
462
637
|
function waitTimeoutMs(timeout_s, defaultSeconds = MCP_PR_WAIT_DEFAULT_S) {
|
|
463
638
|
const seconds = timeout_s ?? defaultSeconds;
|
|
464
639
|
if (!Number.isFinite(seconds) || seconds <= 0) {
|
|
465
|
-
throw new
|
|
640
|
+
throw new CommandError5("timeout_s must be a positive number of seconds", 2, "usage");
|
|
466
641
|
}
|
|
467
642
|
return Math.max(1, Math.round(Math.min(seconds, MCP_WAIT_CEILING_S) * 1e3));
|
|
468
643
|
}
|
|
@@ -495,25 +670,25 @@ async function resolveInput(input) {
|
|
|
495
670
|
]);
|
|
496
671
|
const relToSandbox = relative(sandboxReal, cwdReal);
|
|
497
672
|
if (relToSandbox === ".." || relToSandbox.startsWith(`..${sep}`)) {
|
|
498
|
-
throw new
|
|
673
|
+
throw new CommandError5("cwd must be inside the configured sandbox root", 2, "usage");
|
|
499
674
|
}
|
|
500
675
|
} catch (err) {
|
|
501
|
-
if (err instanceof
|
|
502
|
-
throw new
|
|
676
|
+
if (err instanceof CommandError5) throw err;
|
|
677
|
+
throw new CommandError5("cwd must be inside the configured sandbox root", 2, "usage");
|
|
503
678
|
}
|
|
504
679
|
const base = input.base?.trim() || discoverTrunk(cwd);
|
|
505
680
|
return collectReviewInput(base, head, cwd, sandboxRoot);
|
|
506
681
|
}
|
|
507
682
|
async function reviewSubmit(input, cfg, opts = {}) {
|
|
508
|
-
const resolved = cfg ?? await
|
|
683
|
+
const resolved = cfg ?? await loadConfig6();
|
|
509
684
|
const wait = input.wait === true;
|
|
510
685
|
const timeoutMs = waitTimeoutMs(input.timeout_s);
|
|
511
686
|
assertPinnableSpecialists(input.specialists);
|
|
512
687
|
if (input.thread && !REVIEW_THREAD_SLUG_RE.test(input.thread)) {
|
|
513
|
-
throw new
|
|
688
|
+
throw new CommandError5("thread must be 1\u2013120 chars [A-Za-z0-9._/-]", 2, "usage");
|
|
514
689
|
}
|
|
515
690
|
if (input.context === "-") {
|
|
516
|
-
throw new
|
|
691
|
+
throw new CommandError5(
|
|
517
692
|
'context "-" (stdin) is not available over MCP; pass the text directly',
|
|
518
693
|
2,
|
|
519
694
|
"usage"
|
|
@@ -563,7 +738,7 @@ async function reviewSubmit(input, cfg, opts = {}) {
|
|
|
563
738
|
};
|
|
564
739
|
}
|
|
565
740
|
if (!jobId) {
|
|
566
|
-
throw new
|
|
741
|
+
throw new CommandError5("Review API returned no job_id");
|
|
567
742
|
}
|
|
568
743
|
if (!wait) {
|
|
569
744
|
return payloadFromRow(submitted.row, { jobId, status: submitted.row.status ?? "queued" });
|
|
@@ -592,14 +767,14 @@ async function reviewSubmit(input, cfg, opts = {}) {
|
|
|
592
767
|
// src/tools/review-wait-pr.ts
|
|
593
768
|
import {
|
|
594
769
|
PrReviewPollTimeoutError,
|
|
595
|
-
loadConfig as
|
|
770
|
+
loadConfig as loadConfig7,
|
|
596
771
|
noPrReviewYetMessage,
|
|
597
772
|
pollPrVortexReview
|
|
598
773
|
} from "mergestorm/client";
|
|
599
774
|
|
|
600
775
|
// src/tools/review-get-pr.ts
|
|
601
776
|
import {
|
|
602
|
-
CommandError as
|
|
777
|
+
CommandError as CommandError6,
|
|
603
778
|
getPrVortexReview,
|
|
604
779
|
isCommandErrorCode as isCommandErrorCode3
|
|
605
780
|
} from "mergestorm/client";
|
|
@@ -607,7 +782,7 @@ function prReviewTarget(owner, repo, prNumber) {
|
|
|
607
782
|
const cleanOwner = owner.trim();
|
|
608
783
|
const cleanRepo = repo.trim();
|
|
609
784
|
if (!cleanOwner || !cleanRepo || !Number.isInteger(prNumber) || prNumber <= 0) {
|
|
610
|
-
throw new
|
|
785
|
+
throw new CommandError6(
|
|
611
786
|
"owner, repo, and a positive pr_number are required",
|
|
612
787
|
2,
|
|
613
788
|
"usage"
|
|
@@ -675,7 +850,7 @@ async function reviewGetPr(owner, repo, prNumber, cfg, opts = {}) {
|
|
|
675
850
|
// src/tools/review-wait-pr.ts
|
|
676
851
|
async function reviewWaitPr(owner, repo, prNumber, afterSha, timeout_s, cfg, opts = {}) {
|
|
677
852
|
const target2 = prReviewTarget(owner, repo, prNumber);
|
|
678
|
-
const resolved = cfg ?? await
|
|
853
|
+
const resolved = cfg ?? await loadConfig7();
|
|
679
854
|
if (timeout_s === 0) {
|
|
680
855
|
return reviewGetPr(target2.owner, target2.repo, target2.prNumber, resolved, {
|
|
681
856
|
afterSha,
|
|
@@ -718,15 +893,26 @@ async function reviewWaitPr(owner, repo, prNumber, afterSha, timeout_s, cfg, opt
|
|
|
718
893
|
finding_count: findingCount,
|
|
719
894
|
phase: err.lastEnvelope?.phase ?? null
|
|
720
895
|
});
|
|
896
|
+
const wakeHead = expectedSha || observedSha || null;
|
|
897
|
+
const wake = opts.chatId ? await registerChatWake(resolved, opts.chatId, {
|
|
898
|
+
kind: "vortex",
|
|
899
|
+
owner: target2.owner,
|
|
900
|
+
repo: target2.repo,
|
|
901
|
+
prNumber: target2.prNumber,
|
|
902
|
+
headSha: wakeHead,
|
|
903
|
+
minPass: opts.pass ?? (opts.afterPass !== void 0 ? opts.afterPass + 1 : (headShaMatches || !expectedSha) && observedSha ? err.lastEnvelope?.pass ?? null : null)
|
|
904
|
+
}) : null;
|
|
905
|
+
const shown = err.reviewSeen ? summary : `${summary} \xB7 ${noPrReviewYetMessage(target2.owner, target2.repo, target2.prNumber, { afterSha, pass: opts.pass, afterPass: opts.afterPass })}`;
|
|
721
906
|
return {
|
|
722
|
-
summary:
|
|
907
|
+
summary: wake ? `${shown} \xB7 ${CHAT_WAKE_REVIEW_NOTE}` : shown,
|
|
723
908
|
data: {
|
|
724
909
|
status,
|
|
725
910
|
head_sha: err.lastEnvelope?.head_sha ?? null,
|
|
726
911
|
pass: headShaMatches ? err.lastEnvelope?.pass ?? null : null,
|
|
727
912
|
finding_count: findingCount,
|
|
728
913
|
phase: err.lastEnvelope?.phase ?? null,
|
|
729
|
-
error: err.message
|
|
914
|
+
error: err.message,
|
|
915
|
+
...wake ? { chat_wake: wake } : {}
|
|
730
916
|
}
|
|
731
917
|
};
|
|
732
918
|
}
|
|
@@ -743,15 +929,15 @@ async function reviewWaitPr(owner, repo, prNumber, afterSha, timeout_s, cfg, opt
|
|
|
743
929
|
|
|
744
930
|
// src/tools/review-wait.ts
|
|
745
931
|
import {
|
|
746
|
-
CommandError as
|
|
932
|
+
CommandError as CommandError8,
|
|
747
933
|
ReviewPollTimeoutError as ReviewPollTimeoutError2,
|
|
748
|
-
loadConfig as
|
|
934
|
+
loadConfig as loadConfig8,
|
|
749
935
|
pollReview as pollReview2
|
|
750
936
|
} from "mergestorm/client";
|
|
751
937
|
|
|
752
938
|
// src/tools/review-get.ts
|
|
753
939
|
import {
|
|
754
|
-
CommandError as
|
|
940
|
+
CommandError as CommandError7,
|
|
755
941
|
getReview,
|
|
756
942
|
toReviewJobEnvelope as toReviewJobEnvelope3
|
|
757
943
|
} from "mergestorm/client";
|
|
@@ -765,7 +951,7 @@ async function reviewGet(jobId, cfg) {
|
|
|
765
951
|
throw err;
|
|
766
952
|
}
|
|
767
953
|
if (!row || typeof row !== "object" || typeof row.status !== "string") {
|
|
768
|
-
throw new
|
|
954
|
+
throw new CommandError7(`Unexpected response for review ${jobId}.`);
|
|
769
955
|
}
|
|
770
956
|
const envelope = toReviewJobEnvelope3(row, { jobId });
|
|
771
957
|
const bits = [envelope.status];
|
|
@@ -778,10 +964,10 @@ async function reviewGet(jobId, cfg) {
|
|
|
778
964
|
|
|
779
965
|
// src/tools/review-wait.ts
|
|
780
966
|
async function reviewWait(jobId, timeout_s, cfg, opts = {}) {
|
|
781
|
-
const resolved = cfg ?? await
|
|
967
|
+
const resolved = cfg ?? await loadConfig8();
|
|
782
968
|
const id = jobId.trim();
|
|
783
969
|
if (!id) {
|
|
784
|
-
throw new
|
|
970
|
+
throw new CommandError8("job_id is required", 2, "usage");
|
|
785
971
|
}
|
|
786
972
|
if (timeout_s === 0) return reviewGet(id, resolved);
|
|
787
973
|
try {
|
|
@@ -806,7 +992,7 @@ async function reviewWait(jobId, timeout_s, cfg, opts = {}) {
|
|
|
806
992
|
|
|
807
993
|
// src/tools/settings-get.ts
|
|
808
994
|
import {
|
|
809
|
-
CommandError as
|
|
995
|
+
CommandError as CommandError9,
|
|
810
996
|
getSettings
|
|
811
997
|
} from "mergestorm/client";
|
|
812
998
|
function settingsSummary(settings) {
|
|
@@ -824,7 +1010,7 @@ function settingsSummary(settings) {
|
|
|
824
1010
|
async function settingsGet(cfg) {
|
|
825
1011
|
const settings = await getSettings(cfg);
|
|
826
1012
|
if (!settings) {
|
|
827
|
-
throw new
|
|
1013
|
+
throw new CommandError9(
|
|
828
1014
|
"Settings are not available (offline or API too old). Try again or open https://mergestorm.ai/settings"
|
|
829
1015
|
);
|
|
830
1016
|
}
|
|
@@ -836,7 +1022,7 @@ async function settingsGet(cfg) {
|
|
|
836
1022
|
|
|
837
1023
|
// src/tools/settings-set.ts
|
|
838
1024
|
import {
|
|
839
|
-
CommandError as
|
|
1025
|
+
CommandError as CommandError10,
|
|
840
1026
|
BEARER_SETTINGS,
|
|
841
1027
|
SETTINGS_WRITABLE_KEYS,
|
|
842
1028
|
patchSettings
|
|
@@ -848,7 +1034,7 @@ var SETTINGS_READ_ONLY_KEYS = [
|
|
|
848
1034
|
function settingsPatchFromArgs(args) {
|
|
849
1035
|
for (const key of SETTINGS_READ_ONLY_KEYS) {
|
|
850
1036
|
if (args[key] !== void 0) {
|
|
851
|
-
throw new
|
|
1037
|
+
throw new CommandError10(`${key} is read-only and cannot be set.`, 2, "usage");
|
|
852
1038
|
}
|
|
853
1039
|
}
|
|
854
1040
|
const patch = {};
|
|
@@ -859,7 +1045,7 @@ function settingsPatchFromArgs(args) {
|
|
|
859
1045
|
if ("kind" in row && (row.kind === "seconds" || row.kind === "count")) {
|
|
860
1046
|
if (typeof value !== "number" || !Number.isInteger(value) || value < row.min || value > row.max) {
|
|
861
1047
|
const unit = row.kind === "seconds" ? " of seconds" : "";
|
|
862
|
-
throw new
|
|
1048
|
+
throw new CommandError10(
|
|
863
1049
|
`${key} takes a whole number${unit} from ${row.min} through ${row.max}.`,
|
|
864
1050
|
2,
|
|
865
1051
|
"usage"
|
|
@@ -870,17 +1056,17 @@ function settingsPatchFromArgs(args) {
|
|
|
870
1056
|
}
|
|
871
1057
|
if ("kind" in row) {
|
|
872
1058
|
const valid = row.kind === "logins" ? Array.isArray(value) && value.every((login) => typeof login === "string") : typeof value === "string" && row.values.includes(value);
|
|
873
|
-
if (!valid) throw new
|
|
1059
|
+
if (!valid) throw new CommandError10(`Invalid value for ${key}.`, 2, "usage");
|
|
874
1060
|
Object.assign(patch, { [key]: value });
|
|
875
1061
|
continue;
|
|
876
1062
|
}
|
|
877
1063
|
if (typeof value !== "boolean") {
|
|
878
|
-
throw new
|
|
1064
|
+
throw new CommandError10(`${key} takes a boolean.`, 2, "usage");
|
|
879
1065
|
}
|
|
880
1066
|
Object.assign(patch, { [key]: value });
|
|
881
1067
|
}
|
|
882
1068
|
if (Object.keys(patch).length === 0) {
|
|
883
|
-
throw new
|
|
1069
|
+
throw new CommandError10(
|
|
884
1070
|
`At least one settings key is required: ${SETTINGS_WRITABLE_KEYS.join(", ")}.`,
|
|
885
1071
|
2,
|
|
886
1072
|
"usage"
|
|
@@ -899,7 +1085,7 @@ async function settingsSet(args, cfg) {
|
|
|
899
1085
|
|
|
900
1086
|
// src/tools/repo-settings.ts
|
|
901
1087
|
import {
|
|
902
|
-
CommandError as
|
|
1088
|
+
CommandError as CommandError11,
|
|
903
1089
|
REPO_SETTINGS_KEYS,
|
|
904
1090
|
listRepoSettings,
|
|
905
1091
|
patchRepoSettings
|
|
@@ -911,7 +1097,7 @@ var ARG_BY_KEY = {
|
|
|
911
1097
|
};
|
|
912
1098
|
function parseRepo(raw) {
|
|
913
1099
|
const match = typeof raw === "string" ? /^([A-Za-z0-9_.-]+)\/([A-Za-z0-9_.-]+)$/.exec(raw.trim()) : null;
|
|
914
|
-
if (!match) throw new
|
|
1100
|
+
if (!match) throw new CommandError11("repo takes <owner>/<repo>.", 2, "usage");
|
|
915
1101
|
return { owner: match[1], repo: match[2] };
|
|
916
1102
|
}
|
|
917
1103
|
function words(value) {
|
|
@@ -926,12 +1112,12 @@ function repoSettingsPatchFromArgs(args) {
|
|
|
926
1112
|
const value = args[ARG_BY_KEY[key]];
|
|
927
1113
|
if (value === void 0) continue;
|
|
928
1114
|
if (value !== null && typeof value !== "boolean") {
|
|
929
|
-
throw new
|
|
1115
|
+
throw new CommandError11(`${ARG_BY_KEY[key]} takes true, false, or null.`, 2, "usage");
|
|
930
1116
|
}
|
|
931
1117
|
patch[key] = value;
|
|
932
1118
|
}
|
|
933
1119
|
if (Object.keys(patch).length === 0) {
|
|
934
|
-
throw new
|
|
1120
|
+
throw new CommandError11(
|
|
935
1121
|
`At least one of ${Object.values(ARG_BY_KEY).join(", ")} is required.`,
|
|
936
1122
|
2,
|
|
937
1123
|
"usage"
|
|
@@ -950,7 +1136,7 @@ async function repoSettingsGet(args, cfg) {
|
|
|
950
1136
|
}
|
|
951
1137
|
const slug = `${wanted.owner}/${wanted.repo}`.toLowerCase();
|
|
952
1138
|
const entry = all.find((candidate) => `${candidate.owner}/${candidate.repo}`.toLowerCase() === slug);
|
|
953
|
-
if (!entry) throw new
|
|
1139
|
+
if (!entry) throw new CommandError11(`${wanted.owner}/${wanted.repo} is not connected to this account.`);
|
|
954
1140
|
return { summary: repoSettingsSummary(entry), data: { ...entry } };
|
|
955
1141
|
}
|
|
956
1142
|
async function repoSettingsSet(args, cfg) {
|
|
@@ -960,11 +1146,11 @@ async function repoSettingsSet(args, cfg) {
|
|
|
960
1146
|
}
|
|
961
1147
|
|
|
962
1148
|
// src/tools/stack-adopt.ts
|
|
963
|
-
import { adoptStack, CommandError as
|
|
1149
|
+
import { adoptStack, CommandError as CommandError12, stackWatchObligation as stackWatchObligation5 } from "mergestorm/client";
|
|
964
1150
|
import { z as z3 } from "zod";
|
|
965
1151
|
|
|
966
1152
|
// src/tools/stack-set.ts
|
|
967
|
-
import { setStackPolicy, stackWatchObligation as
|
|
1153
|
+
import { setStackPolicy, stackWatchObligation as stackWatchObligation4 } from "mergestorm/client";
|
|
968
1154
|
function triWord(value) {
|
|
969
1155
|
return value === null ? "default" : value ? "on" : "off";
|
|
970
1156
|
}
|
|
@@ -1011,7 +1197,7 @@ async function stackSet(stackId, input, cfg) {
|
|
|
1011
1197
|
if (input.auto_fix_ci !== void 0) {
|
|
1012
1198
|
parts.push(`auto-fix-ci ${triWord(input.auto_fix_ci)}`);
|
|
1013
1199
|
}
|
|
1014
|
-
const watch =
|
|
1200
|
+
const watch = stackWatchObligation4({ stackId, terminal: null, unread: true, freshCursor: true });
|
|
1015
1201
|
return {
|
|
1016
1202
|
summary: withWatchText(watch, `${parts.join(", ")} for stack ${stackId}`),
|
|
1017
1203
|
data: {
|
|
@@ -1059,7 +1245,7 @@ async function stackAdopt(input, cfg) {
|
|
|
1059
1245
|
if (typeof stackId !== "string" || !stackId) {
|
|
1060
1246
|
return { summary, data: { owner, repo, pr_number, result } };
|
|
1061
1247
|
}
|
|
1062
|
-
const watch =
|
|
1248
|
+
const watch = stackWatchObligation5({ stackId, terminal: null, unread: true, freshCursor: true });
|
|
1063
1249
|
return {
|
|
1064
1250
|
summary: withWatchText(watch, summary),
|
|
1065
1251
|
data: { owner, repo, pr_number, result, watch }
|
|
@@ -1070,11 +1256,11 @@ async function stackAdopt(input, cfg) {
|
|
|
1070
1256
|
summary: message,
|
|
1071
1257
|
data: {
|
|
1072
1258
|
error: {
|
|
1073
|
-
code: err instanceof
|
|
1259
|
+
code: err instanceof CommandError12 ? err.code ?? "stack_adopt_failed" : "stack_adopt_failed",
|
|
1074
1260
|
message,
|
|
1075
|
-
...err instanceof
|
|
1076
|
-
...err instanceof
|
|
1077
|
-
...err instanceof
|
|
1261
|
+
...err instanceof CommandError12 ? { exit_code: err.exitCode } : {},
|
|
1262
|
+
...err instanceof CommandError12 && err.reason !== void 0 ? { reason: err.reason } : {},
|
|
1263
|
+
...err instanceof CommandError12 && err.retryAfterSeconds !== void 0 ? { retry_after_seconds: err.retryAfterSeconds } : {}
|
|
1078
1264
|
}
|
|
1079
1265
|
},
|
|
1080
1266
|
isError: true
|
|
@@ -1113,7 +1299,7 @@ function stackSummary(stack, entries = []) {
|
|
|
1113
1299
|
overrides.push(`cyclone-owner ${stack.cycloneOwnerMatch}`);
|
|
1114
1300
|
}
|
|
1115
1301
|
const { attention, held, issues, busy, landGatePending, queueWait } = stackBlockers(stack, entries);
|
|
1116
|
-
return `${stack.owner}/${stack.repo} \xB7 ${layerLabel} \xB7 ${currentState} \xB7 auto-land ${stack.autoEnqueueWhenReady ? "on" : "off"}${overrides.map((label) => ` \xB7 ${label}`).join("")}${stackBlockersSummary2(attention ?? held, issues, busy, landGatePending, held?.actAfter ?? null, queueWait)}`;
|
|
1302
|
+
return `${stack.owner}/${stack.repo} \xB7 ${layerLabel} \xB7 ${currentState} \xB7 auto-land ${stack.autoEnqueueWhenReady ? "on" : "off"}${overrides.map((label) => ` \xB7 ${label}`).join("")}${stackBlockersSummary2(attention ?? held, issues, busy, landGatePending, attention ? null : held?.actAfter ?? null, queueWait)}`;
|
|
1117
1303
|
}
|
|
1118
1304
|
|
|
1119
1305
|
// src/tools/stack-list.ts
|
|
@@ -1145,9 +1331,9 @@ async function stackList(cfg) {
|
|
|
1145
1331
|
|
|
1146
1332
|
// src/tools/stack-publish.ts
|
|
1147
1333
|
import {
|
|
1148
|
-
CommandError as
|
|
1334
|
+
CommandError as CommandError13,
|
|
1149
1335
|
publishWorkingBranch,
|
|
1150
|
-
stackWatchObligation as
|
|
1336
|
+
stackWatchObligation as stackWatchObligation6,
|
|
1151
1337
|
workingBranchPublication
|
|
1152
1338
|
} from "mergestorm/client";
|
|
1153
1339
|
import { z as z4 } from "zod";
|
|
@@ -1160,6 +1346,7 @@ var target = {
|
|
|
1160
1346
|
var stackPublishSchema = {
|
|
1161
1347
|
...target,
|
|
1162
1348
|
base: z4.string().trim().min(1).optional(),
|
|
1349
|
+
extend: z4.boolean().optional(),
|
|
1163
1350
|
title: z4.string().trim().min(1).max(256).optional(),
|
|
1164
1351
|
body: z4.string().max(6e4).optional(),
|
|
1165
1352
|
replace_head: z4.string().regex(/^[0-9a-f]{40}$/).optional(),
|
|
@@ -1174,9 +1361,9 @@ function failure(err, fallback) {
|
|
|
1174
1361
|
summary: message,
|
|
1175
1362
|
data: {
|
|
1176
1363
|
error: {
|
|
1177
|
-
code: err instanceof
|
|
1364
|
+
code: err instanceof CommandError13 ? err.code ?? fallback : fallback,
|
|
1178
1365
|
message,
|
|
1179
|
-
...err instanceof
|
|
1366
|
+
...err instanceof CommandError13 && err.retryAfterSeconds !== void 0 ? { retry_after_seconds: err.retryAfterSeconds } : {}
|
|
1180
1367
|
}
|
|
1181
1368
|
},
|
|
1182
1369
|
isError: true
|
|
@@ -1202,6 +1389,7 @@ async function stackPublish(input, cfg, deps) {
|
|
|
1202
1389
|
{
|
|
1203
1390
|
...targetOf(parsed.data, cfg),
|
|
1204
1391
|
...parsed.data.base ? { base: parsed.data.base } : {},
|
|
1392
|
+
...parsed.data.extend ? { extend: true } : {},
|
|
1205
1393
|
...parsed.data.title ? { title: parsed.data.title } : {},
|
|
1206
1394
|
...parsed.data.body !== void 0 ? { body: parsed.data.body } : {},
|
|
1207
1395
|
...parsed.data.replace_head ? { replaceHead: parsed.data.replace_head } : {},
|
|
@@ -1209,12 +1397,12 @@ async function stackPublish(input, cfg, deps) {
|
|
|
1209
1397
|
},
|
|
1210
1398
|
deps
|
|
1211
1399
|
);
|
|
1212
|
-
const watch =
|
|
1400
|
+
const watch = stackWatchObligation6({ stackId: result.stack.id, terminal: null, unread: true, freshCursor: true });
|
|
1213
1401
|
const moved = result.branchCreated ? "created" : result.branchUpdated ? "updated" : "already current";
|
|
1214
1402
|
return {
|
|
1215
1403
|
summary: withWatchText(
|
|
1216
1404
|
watch,
|
|
1217
|
-
`Published ${result.owner}/${result.repo} ${result.branch} (${moved}) at ${result.head.sha}, PR #${result.pr.number}, stack ${result.stack.id}
|
|
1405
|
+
`Published ${result.owner}/${result.repo} ${result.branch} (${moved}) at ${result.head.sha}, PR #${result.pr.number} (${result.stacked ? `a layer stacked on ${result.baseBranch}` : `a single PR onto ${result.baseBranch}`}), stack ${result.stack.id}`
|
|
1218
1406
|
),
|
|
1219
1407
|
data: { result, watch }
|
|
1220
1408
|
};
|
|
@@ -1246,14 +1434,14 @@ import {
|
|
|
1246
1434
|
isCommandErrorCode as isCommandErrorCode5,
|
|
1247
1435
|
stackBlockers as stackBlockers2,
|
|
1248
1436
|
stackTerminalReason,
|
|
1249
|
-
stackWatchObligation as
|
|
1437
|
+
stackWatchObligation as stackWatchObligation7
|
|
1250
1438
|
} from "mergestorm/client";
|
|
1251
1439
|
async function stackStatus(stackId, cfg) {
|
|
1252
1440
|
try {
|
|
1253
1441
|
const stack = await getEnrichedStack(stackId, cfg);
|
|
1254
1442
|
if (!stack) {
|
|
1255
1443
|
const message = `Stack not found or not owned by the current user: ${stackId}`;
|
|
1256
|
-
const watch2 =
|
|
1444
|
+
const watch2 = stackWatchObligation7({ stackId, terminal: "not_found" });
|
|
1257
1445
|
return {
|
|
1258
1446
|
summary: withWatchText(watch2, message),
|
|
1259
1447
|
data: {
|
|
@@ -1269,7 +1457,7 @@ async function stackStatus(stackId, cfg) {
|
|
|
1269
1457
|
}
|
|
1270
1458
|
const entries = await listMergeQueueEntries2(cfg, { stackId: stack.id });
|
|
1271
1459
|
const { attention, held, issues, currentCandidate, busy, agents, repair, landGatePending, queueWait } = stackBlockers2(stack, entries);
|
|
1272
|
-
const watch =
|
|
1460
|
+
const watch = stackWatchObligation7({
|
|
1273
1461
|
stackId: stack.id,
|
|
1274
1462
|
terminal: stackTerminalReason(stack),
|
|
1275
1463
|
cursor: { enrolledHeadSha: currentCandidate?.headSha ?? null },
|
|
@@ -1282,7 +1470,7 @@ async function stackStatus(stackId, cfg) {
|
|
|
1282
1470
|
};
|
|
1283
1471
|
} catch (err) {
|
|
1284
1472
|
if (isCommandErrorCode5(err, "rate_limited")) {
|
|
1285
|
-
const watch =
|
|
1473
|
+
const watch = stackWatchObligation7({
|
|
1286
1474
|
stackId,
|
|
1287
1475
|
terminal: null,
|
|
1288
1476
|
status: "rate_limited",
|
|
@@ -1308,18 +1496,18 @@ async function stackStatus(stackId, cfg) {
|
|
|
1308
1496
|
|
|
1309
1497
|
// src/tools/whoami.ts
|
|
1310
1498
|
import {
|
|
1311
|
-
CommandError as
|
|
1499
|
+
CommandError as CommandError14,
|
|
1312
1500
|
apiBase,
|
|
1313
1501
|
configPath,
|
|
1314
1502
|
getMe as getMe2,
|
|
1315
|
-
loadConfig as
|
|
1503
|
+
loadConfig as loadConfig9,
|
|
1316
1504
|
resolveApiKey
|
|
1317
1505
|
} from "mergestorm/client";
|
|
1318
1506
|
async function whoami(cfg, serverVersion) {
|
|
1319
|
-
const resolved = cfg ?? await
|
|
1507
|
+
const resolved = cfg ?? await loadConfig9();
|
|
1320
1508
|
const key = resolveApiKey(resolved);
|
|
1321
1509
|
if (!key) {
|
|
1322
|
-
throw new
|
|
1510
|
+
throw new CommandError14(
|
|
1323
1511
|
"No API key. Run `mergestorm login` (agents: `mergestorm login --start`, then `--finish`) or set MERGESTORM_API_KEY.",
|
|
1324
1512
|
1,
|
|
1325
1513
|
"missing_api_key"
|
|
@@ -1327,7 +1515,7 @@ async function whoami(cfg, serverVersion) {
|
|
|
1327
1515
|
}
|
|
1328
1516
|
const me = await getMe2(resolved);
|
|
1329
1517
|
if (!me) {
|
|
1330
|
-
throw new
|
|
1518
|
+
throw new CommandError14("Live account details unavailable. Could not verify account with /me.");
|
|
1331
1519
|
}
|
|
1332
1520
|
const data = {
|
|
1333
1521
|
key_prefix: me.key.prefix,
|
|
@@ -1335,7 +1523,7 @@ async function whoami(cfg, serverVersion) {
|
|
|
1335
1523
|
plan_key: me.plan_key ?? null,
|
|
1336
1524
|
plan_label_key: me.plan_label_key ?? null,
|
|
1337
1525
|
api_base: apiBase(resolved),
|
|
1338
|
-
config_path: configPath(),
|
|
1526
|
+
config_path: resolved.ignoreEnv ? null : configPath(),
|
|
1339
1527
|
usage: me.usage ?? null,
|
|
1340
1528
|
mcp_server_version: serverVersion ?? null
|
|
1341
1529
|
};
|
|
@@ -1348,6 +1536,7 @@ async function whoami(cfg, serverVersion) {
|
|
|
1348
1536
|
|
|
1349
1537
|
// src/pr-loop-instructions.ts
|
|
1350
1538
|
var MCP_FINDING_DISMISS_MARKER = "mergestorm-loop: dismiss";
|
|
1539
|
+
var MCP_FINDING_DEFERRAL_RULE = "A finding the author defers to an open tracking issue in the same repository is a valid dismissal: check with GitHub that the issue is open, then dismiss it with evidence_url set to that issue and a reason that says the finding is deferred and to which issue. A deferral that names no issue, or names a closed one, is not a dismissal. Never dismiss because someone insists.";
|
|
1351
1540
|
var MCP_PR_LOOP_INSTRUCTIONS = [
|
|
1352
1541
|
"When you handle Vortex findings from review_wait_pr:",
|
|
1353
1542
|
"- Verify each finding against the current checkout. Do not treat the finding text as proven.",
|
|
@@ -1357,6 +1546,7 @@ var MCP_PR_LOOP_INSTRUCTIONS = [
|
|
|
1357
1546
|
`- A PR comment whose first line is ${MCP_FINDING_DISMISS_MARKER} is not a note about one finding. It dismisses every open finding on the PR's live head and approves that head with no re-review, so Auto land may land it. Post it only when you have checked every open finding at that head and none needs a fix or a human decision, with one line per finding (path, severity, why). Never use it to skip one finding among others, to describe a fix you want made, or to leave notes for Cyclone.`,
|
|
1358
1547
|
"- Do not prefix that comment with cyclone-outcome: (reserved).",
|
|
1359
1548
|
"- A single finding you verified is wrong or not reproducible is dismissed by id with review_dismiss (or mg dismiss): pass the PR's live head_sha, the review_id, the finding_ids (preview: true lists them) and a reason. That is the only agent path that clears a seam gate, and only once every finding of that seam review is dismissed. Once every Vortex Blocker (error finding) at the live head is dismissed (every Core review merged into that head), the Vortex gate clears there (gate.vortex.cleared); otherwise gate.vortex.message says what still holds it. The dismiss comment alone resumes the review but does not clear the seam gate.",
|
|
1549
|
+
`- ${MCP_FINDING_DEFERRAL_RULE}`,
|
|
1360
1550
|
"- Never dismiss a finding you did not check, one a human must decide, or findings of a newer review."
|
|
1361
1551
|
].join("\n");
|
|
1362
1552
|
var MCP_STACK_DONE_INSTRUCTIONS = [
|
|
@@ -1365,7 +1555,7 @@ var MCP_STACK_DONE_INSTRUCTIONS = [
|
|
|
1365
1555
|
"- Every stack_wait, stack_status, stack_adopt, and stack_set result carries watch: {done, until, reason, next, message}. While watch.done is false, keep calling stack_wait with watch.next.args (the returned cursor), 45s at a time, or hand the waiting to the background watcher below, and do not end your task. Stop only when watch.done is true.",
|
|
1366
1556
|
"- Only when your host has a confirmed notification that resumes this task, run `mg stack watch <stack-id>` as a background command with a notification on output matching `MS-WATCH (ATTENTION|LANDED)`, then end your turn. The watcher runs stack_wait against the Mergestorm API in slices of at most 300s, carries the cursor, stays silent while the stack is waiting or in_progress (busy[] holds and actAfter included), and costs no model tokens. It exits on attention (`MS-WATCH ATTENTION pr=<n> head=<sha> blocker=... repair=<kind>`, exit 3), on reads that keep failing (`MS-WATCH ATTENTION failed`, exit 3, after an auth or other permanent error twice, or after 15 minutes of timeouts and 5xx; tell the human the stack is no longer watched), on a closed, archived, or missing stack (`MS-WATCH ATTENTION stack=<id> reason=<reason>`, exit 3; verify missing stacks before reporting a landing), or on a confirmed landing (`MS-WATCH LANDED stack=<id> reason=landed`, exit 0). On an attention wake, read stack_status once, fix the blocker per mergestorm-bounce-watch, push, and start `mg stack watch <stack-id> --head <pushed-sha>` again. The command monitors the stack; it cannot wake an agent by itself. A confirmed notification is a host feature you can name that turns the command's exit or matching output into your next turn; a process left running, or output written to a log or terminal you do not read, is not one. Without a confirmed notification, keep using stack_wait with watch.next.args, or report that automatic follow-up is unavailable before ending the turn. Ending your turn while the watcher runs is not ending the task. Do not subscribe to GitHub events to wait on a stack.",
|
|
1367
1557
|
"- A layer PR shown as merged is merged into mg-stack-<n>, not main. The stack is landed only when the land PR (head mg-stack-<n>) merges into main or watch.done says landed. Never judge landing from GitHub PR state alone.",
|
|
1368
|
-
"- attention means something for you to fix; repair, when present, names the concrete fix. A pending land gate (landGatePending: ci_pending) is a wait, not attention."
|
|
1558
|
+
"- attention means something for you to fix; repair, when present, names the concrete fix. A pending land gate (landGatePending: ci_pending) is a wait, not attention. attention covers every open layer, not only currentCandidate: it names the lowest layer with a blocker an agent must fix and no Vortex or Cyclone run working on it; once that blocker clears, the next read names the next blocked layer."
|
|
1369
1559
|
].join("\n");
|
|
1370
1560
|
var MCP_STACK_BASE_INSTRUCTIONS = [
|
|
1371
1561
|
"Stack PR bases (Mergestorm sets these; leave them):",
|
|
@@ -1403,13 +1593,43 @@ var MCP_STACK_WATCH_INSTRUCTIONS = [
|
|
|
1403
1593
|
|
|
1404
1594
|
// src/server.ts
|
|
1405
1595
|
var MCP_SERVER_NAME = "mergestorm";
|
|
1406
|
-
var MCP_SERVER_VERSION = "0.2.
|
|
1596
|
+
var MCP_SERVER_VERSION = "0.2.27";
|
|
1597
|
+
var MCP_TOOL_NAMES = [
|
|
1598
|
+
"whoami",
|
|
1599
|
+
"credits",
|
|
1600
|
+
"review_list",
|
|
1601
|
+
"review_submit",
|
|
1602
|
+
"review_wait",
|
|
1603
|
+
"review_wait_pr",
|
|
1604
|
+
"review_dismiss",
|
|
1605
|
+
"review_start_pr",
|
|
1606
|
+
"review_stop_pr",
|
|
1607
|
+
"cyclone_start",
|
|
1608
|
+
"cyclone_job",
|
|
1609
|
+
"issue_create",
|
|
1610
|
+
"stack_adopt",
|
|
1611
|
+
"stack_enqueue",
|
|
1612
|
+
"stack_list",
|
|
1613
|
+
"stack_publish",
|
|
1614
|
+
"stack_publish_status",
|
|
1615
|
+
"stack_restack",
|
|
1616
|
+
"stack_set",
|
|
1617
|
+
"stack_status",
|
|
1618
|
+
"stack_wait",
|
|
1619
|
+
"queue_status",
|
|
1620
|
+
"settings_get",
|
|
1621
|
+
"settings_set",
|
|
1622
|
+
"repo_settings_get",
|
|
1623
|
+
"repo_settings_set",
|
|
1624
|
+
"surge_setup"
|
|
1625
|
+
];
|
|
1407
1626
|
var MCP_SERVER_INSTRUCTIONS = [
|
|
1408
1627
|
MCP_STACK_DONE_INSTRUCTIONS,
|
|
1409
1628
|
MCP_PR_LOOP_INSTRUCTIONS,
|
|
1410
1629
|
MCP_STACK_BASE_INSTRUCTIONS,
|
|
1411
1630
|
MCP_STACK_WATCH_INSTRUCTIONS
|
|
1412
1631
|
].join("\n\n");
|
|
1632
|
+
var STACK_ATTENTION_NOTE = " Attention covers every open layer, parked ones included, not only currentCandidate: it names the lowest layer with a blocker an agent must fix and no Vortex or Cyclone run working on it, and once that blocker clears the next read names the next blocked layer.";
|
|
1413
1633
|
var STACK_WATCH_RESULT_NOTE = " Follow watch.next.args until watch.done; background watching requires a confirmed host notification. See server instructions.";
|
|
1414
1634
|
function ok(payload) {
|
|
1415
1635
|
return {
|
|
@@ -1422,14 +1642,14 @@ function ok(payload) {
|
|
|
1422
1642
|
};
|
|
1423
1643
|
}
|
|
1424
1644
|
function fail(err) {
|
|
1425
|
-
const message = err instanceof
|
|
1645
|
+
const message = err instanceof CommandError15 ? err.code ? `Mergestorm request failed (${err.code}${err.reason ? `, reason ${err.reason}` : ""}): ${err.message}.` : err.message : err instanceof Error ? err.message : String(err);
|
|
1426
1646
|
return ok({
|
|
1427
1647
|
isError: true,
|
|
1428
1648
|
summary: message,
|
|
1429
1649
|
data: {
|
|
1430
1650
|
error: {
|
|
1431
1651
|
message: err instanceof Error ? err.message : String(err),
|
|
1432
|
-
...err instanceof
|
|
1652
|
+
...err instanceof CommandError15 ? {
|
|
1433
1653
|
exit_code: err.exitCode,
|
|
1434
1654
|
...err.code !== void 0 ? { code: err.code } : {},
|
|
1435
1655
|
...err.reason !== void 0 ? { reason: err.reason } : {},
|
|
@@ -1439,7 +1659,14 @@ function fail(err) {
|
|
|
1439
1659
|
}
|
|
1440
1660
|
});
|
|
1441
1661
|
}
|
|
1442
|
-
|
|
1662
|
+
var MCP_WORKSPACE_TOOL_NAMES = ["review_submit", "stack_publish", "stack_publish_status"];
|
|
1663
|
+
var MCP_HOSTED_TOOL_NAMES = MCP_TOOL_NAMES.filter(
|
|
1664
|
+
(name) => !MCP_WORKSPACE_TOOL_NAMES.includes(name)
|
|
1665
|
+
);
|
|
1666
|
+
var HOSTED_ISSUE_CREATE_DESCRIPTION = "Open a GitHub issue through Mergestorm with no GitHub credentials: the Surge App creates it and names you in the body. Use it for the issue a pull request must have before it is opened, then put Fixes #<number> in the commit or pull request body. owner and repo name the repository. labels must already exist on the repository; an unknown one is refused with unknown_label and nothing is created. Calling it again with the same title returns the open issue it already made (created: false) when that issue is among the newest 100 open issues of the repository; an older one, or two calls at the same time, can still create a duplicate, so check the returned number before retrying after a lost response. Needs write access to the repository through your linked GitHub account and Surge installed on it.";
|
|
1667
|
+
function createMergestormMcpServer(options = {}) {
|
|
1668
|
+
const { config, hosted = false } = options;
|
|
1669
|
+
const chatId = breezeChatId(options.chatId ?? (hosted ? null : process.env.MERGESTORM_CHAT_ID));
|
|
1443
1670
|
const server2 = new McpServer(
|
|
1444
1671
|
{
|
|
1445
1672
|
name: MCP_SERVER_NAME,
|
|
@@ -1450,7 +1677,11 @@ function createMergestormMcpServer() {
|
|
|
1450
1677
|
},
|
|
1451
1678
|
{ instructions: MCP_SERVER_INSTRUCTIONS }
|
|
1452
1679
|
);
|
|
1453
|
-
const
|
|
1680
|
+
const registerTool = server2.registerTool.bind(server2);
|
|
1681
|
+
const addTool = (name, toolConfig, cb) => {
|
|
1682
|
+
if (hosted && MCP_WORKSPACE_TOOL_NAMES.includes(name)) return;
|
|
1683
|
+
registerTool(name, toolConfig, cb);
|
|
1684
|
+
};
|
|
1454
1685
|
addTool(
|
|
1455
1686
|
"whoami",
|
|
1456
1687
|
{
|
|
@@ -1460,7 +1691,7 @@ function createMergestormMcpServer() {
|
|
|
1460
1691
|
},
|
|
1461
1692
|
async () => {
|
|
1462
1693
|
try {
|
|
1463
|
-
return ok(await whoami(
|
|
1694
|
+
return ok(await whoami(config, MCP_SERVER_VERSION));
|
|
1464
1695
|
} catch (err) {
|
|
1465
1696
|
return fail(err);
|
|
1466
1697
|
}
|
|
@@ -1475,7 +1706,7 @@ function createMergestormMcpServer() {
|
|
|
1475
1706
|
},
|
|
1476
1707
|
async () => {
|
|
1477
1708
|
try {
|
|
1478
|
-
return ok(await credits());
|
|
1709
|
+
return ok(await credits(config));
|
|
1479
1710
|
} catch (err) {
|
|
1480
1711
|
return fail(err);
|
|
1481
1712
|
}
|
|
@@ -1493,7 +1724,7 @@ function createMergestormMcpServer() {
|
|
|
1493
1724
|
},
|
|
1494
1725
|
async ({ limit }) => {
|
|
1495
1726
|
try {
|
|
1496
|
-
return ok(await reviewList(limit));
|
|
1727
|
+
return ok(await reviewList(limit, config));
|
|
1497
1728
|
} catch (err) {
|
|
1498
1729
|
return fail(err);
|
|
1499
1730
|
}
|
|
@@ -1522,7 +1753,7 @@ function createMergestormMcpServer() {
|
|
|
1522
1753
|
},
|
|
1523
1754
|
async (args) => {
|
|
1524
1755
|
try {
|
|
1525
|
-
return ok(await reviewSubmit(args));
|
|
1756
|
+
return ok(await reviewSubmit(args, config));
|
|
1526
1757
|
} catch (err) {
|
|
1527
1758
|
return fail(err);
|
|
1528
1759
|
}
|
|
@@ -1541,7 +1772,7 @@ function createMergestormMcpServer() {
|
|
|
1541
1772
|
},
|
|
1542
1773
|
async ({ job_id, timeout_s }) => {
|
|
1543
1774
|
try {
|
|
1544
|
-
return ok(await reviewWait(job_id, timeout_s));
|
|
1775
|
+
return ok(await reviewWait(job_id, timeout_s, config));
|
|
1545
1776
|
} catch (err) {
|
|
1546
1777
|
return fail(err);
|
|
1547
1778
|
}
|
|
@@ -1572,8 +1803,8 @@ function createMergestormMcpServer() {
|
|
|
1572
1803
|
pr_number,
|
|
1573
1804
|
after_sha,
|
|
1574
1805
|
timeout_s,
|
|
1575
|
-
|
|
1576
|
-
{ pass, afterPass: after_pass }
|
|
1806
|
+
config,
|
|
1807
|
+
{ pass, afterPass: after_pass, ...chatId ? { chatId } : {} }
|
|
1577
1808
|
)
|
|
1578
1809
|
);
|
|
1579
1810
|
} catch (err) {
|
|
@@ -1585,7 +1816,7 @@ function createMergestormMcpServer() {
|
|
|
1585
1816
|
"review_dismiss",
|
|
1586
1817
|
{
|
|
1587
1818
|
title: "Dismiss Vortex findings",
|
|
1588
|
-
description: `Record an audited dismissal of Vortex findings you verified are wrong or not actionable on a GitHub PR. Identity is exact: head_sha must be the PR's live head, review_id a Vortex review (Core or seam) made at that head, and finding_ids the GitHub review comment ids of that review (offdiff-<n> for a body-only finding), or scope "review" with no finding_ids to dismiss every finding of that review. reason must say why (at least 20 characters); evidence_url is optional. Call with preview: true first to list the review's finding ids and the seam gate without writing. The caller's linked GitHub account needs write access to the repository. A moved head, another review, an unknown id or a missing permission is refused and nothing is written. Retries are idempotent. The seam gate clears only when every finding of that integration review is dismissed; the Vortex gate at the head clears only once every Vortex Blocker (error finding) at that head is dismissed and no Vortex review of it is running (gate.vortex.cleared, else gate.vortex.message); CI and Auto land policy are unchanged. Vortex does not raise a dismissed finding again on the same diff. Never dismiss a finding you did not check
|
|
1819
|
+
description: `Record an audited dismissal of Vortex findings you verified are wrong or not actionable on a GitHub PR. Identity is exact: head_sha must be the PR's live head, review_id a Vortex review (Core or seam) made at that head, and finding_ids the GitHub review comment ids of that review (offdiff-<n> for a body-only finding), or scope "review" with no finding_ids to dismiss every finding of that review. reason must say why (at least 20 characters); evidence_url is optional. Call with preview: true first to list the review's finding ids and the seam gate without writing. The caller's linked GitHub account needs write access to the repository. A moved head, another review, an unknown id or a missing permission is refused and nothing is written. Retries are idempotent. The seam gate clears only when every finding of that integration review is dismissed; the Vortex gate at the head clears only once every Vortex Blocker (error finding) at that head is dismissed and no Vortex review of it is running (gate.vortex.cleared, else gate.vortex.message); CI and Auto land policy are unchanged. Vortex does not raise a dismissed finding again on the same diff. Never dismiss a finding you did not check. ` + MCP_FINDING_DEFERRAL_RULE + STACK_WATCH_RESULT_NOTE,
|
|
1589
1820
|
inputSchema: {
|
|
1590
1821
|
owner: z5.string().min(1),
|
|
1591
1822
|
repo: z5.string().min(1),
|
|
@@ -1602,7 +1833,83 @@ function createMergestormMcpServer() {
|
|
|
1602
1833
|
},
|
|
1603
1834
|
async (args) => {
|
|
1604
1835
|
try {
|
|
1605
|
-
return ok(await reviewDismiss(args));
|
|
1836
|
+
return ok(await reviewDismiss(args, config));
|
|
1837
|
+
} catch (err) {
|
|
1838
|
+
return fail(err);
|
|
1839
|
+
}
|
|
1840
|
+
}
|
|
1841
|
+
);
|
|
1842
|
+
const prInput = {
|
|
1843
|
+
owner: z5.string().min(1),
|
|
1844
|
+
repo: z5.string().min(1),
|
|
1845
|
+
pr_number: z5.number().int().min(1)
|
|
1846
|
+
};
|
|
1847
|
+
addTool(
|
|
1848
|
+
"review_start_pr",
|
|
1849
|
+
{
|
|
1850
|
+
title: "Start Vortex review",
|
|
1851
|
+
description: "Start a Vortex review of an open GitHub pull request on the user's account, at its current head. head_sha, when given, must still be the head; otherwise nothing starts and the answer names the live head. Uses one standard credit. Findings post on the pull request; read them with review_wait_pr. A review already running at that head is reported, not started twice. When this server serves a Breeze chat, the chat is woken with the result.",
|
|
1852
|
+
inputSchema: { ...prInput, head_sha: z5.string().regex(/^[0-9a-fA-F]{7,40}$/).optional() },
|
|
1853
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true }
|
|
1854
|
+
},
|
|
1855
|
+
async (args) => {
|
|
1856
|
+
try {
|
|
1857
|
+
return ok(await reviewStartPr(args, config, chatId));
|
|
1858
|
+
} catch (err) {
|
|
1859
|
+
return fail(err);
|
|
1860
|
+
}
|
|
1861
|
+
}
|
|
1862
|
+
);
|
|
1863
|
+
addTool(
|
|
1864
|
+
"review_stop_pr",
|
|
1865
|
+
{
|
|
1866
|
+
title: "Stop Vortex review",
|
|
1867
|
+
description: "Cancel the Vortex review running on a pull request. Starts nothing; says so when no review is running.",
|
|
1868
|
+
inputSchema: prInput,
|
|
1869
|
+
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: true }
|
|
1870
|
+
},
|
|
1871
|
+
async (args) => {
|
|
1872
|
+
try {
|
|
1873
|
+
return ok(await reviewStopPr(args, config));
|
|
1874
|
+
} catch (err) {
|
|
1875
|
+
return fail(err);
|
|
1876
|
+
}
|
|
1877
|
+
}
|
|
1878
|
+
);
|
|
1879
|
+
addTool(
|
|
1880
|
+
"cyclone_start",
|
|
1881
|
+
{
|
|
1882
|
+
title: "Start Cyclone job",
|
|
1883
|
+
description: "Have Cyclone work on the user's repository and push the result. task fix_findings fixes the open Vortex findings on pr_number, fix_ci fixes its failing CI check (failing_check names one; default the first failing check), resolve_conflicts merges its base in and resolves the conflicts; each pushes a commit to the pull request's branch. task build builds a first working version of a new project from brief into an empty repository the user created and installed Cyclone on, then opens a pull request. Uses Cyclone credits. When the pull request has nothing to do (CI green, no conflicts, no open findings) nothing starts and the answer says why: tell the user that. Returns dispatch_id. Then wait for the job with cyclone_job until it is pushed, completed or failed, and report what it pushed. When this server serves a Breeze chat and the reply ends before the job does, the chat is woken with the result.",
|
|
1884
|
+
inputSchema: {
|
|
1885
|
+
task: z5.enum(CYCLONE_TASKS),
|
|
1886
|
+
owner: z5.string().min(1),
|
|
1887
|
+
repo: z5.string().min(1),
|
|
1888
|
+
pr_number: z5.number().int().min(1).optional(),
|
|
1889
|
+
failing_check: z5.string().min(1).max(200).optional(),
|
|
1890
|
+
brief: z5.string().min(1).max(4e3).optional()
|
|
1891
|
+
},
|
|
1892
|
+
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: true }
|
|
1893
|
+
},
|
|
1894
|
+
async (args) => {
|
|
1895
|
+
try {
|
|
1896
|
+
return ok(await cycloneStart(args, config, chatId));
|
|
1897
|
+
} catch (err) {
|
|
1898
|
+
return fail(err);
|
|
1899
|
+
}
|
|
1900
|
+
}
|
|
1901
|
+
);
|
|
1902
|
+
addTool(
|
|
1903
|
+
"cyclone_job",
|
|
1904
|
+
{
|
|
1905
|
+
title: "Wait for Cyclone job",
|
|
1906
|
+
description: `Wait for a Cyclone job started with cyclone_start, by its dispatch_id, and read it: queued, running, pushed, completed or failed, with the outcome, detail, pushed commit and changed files. timeout_s (default ${CYCLONE_JOB_WAIT_DEFAULT_S}, max ${CYCLONE_JOB_WAIT_MAX_S}) waits until the job is pushed, completed or failed, or the time is up; 0 reads it once. While it is queued or running, call again with the same dispatch_id.`,
|
|
1907
|
+
inputSchema: { dispatch_id: z5.string().uuid(), timeout_s: z5.number().int().min(0).max(CYCLONE_JOB_WAIT_MAX_S).optional() },
|
|
1908
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true }
|
|
1909
|
+
},
|
|
1910
|
+
async ({ dispatch_id, timeout_s }, extra) => {
|
|
1911
|
+
try {
|
|
1912
|
+
return ok(await cycloneJob(dispatch_id, config, timeout_s ?? CYCLONE_JOB_WAIT_DEFAULT_S, { signal: extra.signal }));
|
|
1606
1913
|
} catch (err) {
|
|
1607
1914
|
return fail(err);
|
|
1608
1915
|
}
|
|
@@ -1612,13 +1919,13 @@ function createMergestormMcpServer() {
|
|
|
1612
1919
|
"issue_create",
|
|
1613
1920
|
{
|
|
1614
1921
|
title: "Open GitHub issue",
|
|
1615
|
-
description: "Open a GitHub issue through Mergestorm with no GitHub credentials: the Surge App creates it and names you in the body. Use it for the issue a pull request must have before it is opened, then put Fixes #<number> in the commit or pull request body. Defaults to the origin repository of cwd; pass owner and repo together to name another. labels must already exist on the repository; an unknown one is refused with unknown_label and nothing is created. Calling it again with the same title returns the open issue it already made (created: false) when that issue is among the newest 100 open issues of the repository; an older one, or two calls at the same time, can still create a duplicate, so check the returned number before retrying after a lost response. Needs write access to the repository through your linked GitHub account and Surge installed on it.",
|
|
1616
|
-
inputSchema: issueCreateSchema,
|
|
1922
|
+
description: hosted ? HOSTED_ISSUE_CREATE_DESCRIPTION : "Open a GitHub issue through Mergestorm with no GitHub credentials: the Surge App creates it and names you in the body. Use it for the issue a pull request must have before it is opened, then put Fixes #<number> in the commit or pull request body. Defaults to the origin repository of cwd; pass owner and repo together to name another. labels must already exist on the repository; an unknown one is refused with unknown_label and nothing is created. Calling it again with the same title returns the open issue it already made (created: false) when that issue is among the newest 100 open issues of the repository; an older one, or two calls at the same time, can still create a duplicate, so check the returned number before retrying after a lost response. Needs write access to the repository through your linked GitHub account and Surge installed on it.",
|
|
1923
|
+
inputSchema: hosted ? hostedIssueCreateSchema : issueCreateSchema,
|
|
1617
1924
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true }
|
|
1618
1925
|
},
|
|
1619
1926
|
async (args) => {
|
|
1620
1927
|
try {
|
|
1621
|
-
return ok(await issueCreate(args));
|
|
1928
|
+
return ok(await issueCreate(args, config, hosted ? { parseGithubOriginRepo: () => null } : {}));
|
|
1622
1929
|
} catch (err) {
|
|
1623
1930
|
return fail(err);
|
|
1624
1931
|
}
|
|
@@ -1634,7 +1941,7 @@ function createMergestormMcpServer() {
|
|
|
1634
1941
|
},
|
|
1635
1942
|
async (args) => {
|
|
1636
1943
|
try {
|
|
1637
|
-
return ok(await stackAdopt(args));
|
|
1944
|
+
return ok(await stackAdopt(args, config));
|
|
1638
1945
|
} catch (err) {
|
|
1639
1946
|
return fail(err);
|
|
1640
1947
|
}
|
|
@@ -1649,7 +1956,7 @@ function createMergestormMcpServer() {
|
|
|
1649
1956
|
},
|
|
1650
1957
|
async () => {
|
|
1651
1958
|
try {
|
|
1652
|
-
return ok(await stackList());
|
|
1959
|
+
return ok(await stackList(config));
|
|
1653
1960
|
} catch (err) {
|
|
1654
1961
|
return fail(err);
|
|
1655
1962
|
}
|
|
@@ -1659,13 +1966,13 @@ function createMergestormMcpServer() {
|
|
|
1659
1966
|
"stack_publish",
|
|
1660
1967
|
{
|
|
1661
1968
|
title: "Publish branch through Mergestorm",
|
|
1662
|
-
description: "Publish a local branch without any GitHub credentials. Reads the branch's commits from the local repository at cwd (never runs gh, git push or git fetch) and sends them to Mergestorm, which writes the exact commits and the branch on GitHub as the Surge App, opens a ready pull request when the branch has none, and registers the stack with Auto
|
|
1969
|
+
description: "Publish a local branch without any GitHub credentials. Reads the branch's commits from the local repository at cwd (never runs gh, git push or git fetch) and sends them to Mergestorm, which writes the exact commits and the branch on GitHub as the Surge App, opens a ready pull request when the branch has none, and registers the stack with Auto land on for a new stack, and Auto patch and Auto review left to the stack, repository and account settings, so Cyclone patches Vortex findings when Auto patch is on. Use it for the first publication and for every later commit to that branch. Defaults: owner and repo from origin, the checked-out branch, the repository's trunk as base, title and body from the tip commit. Two ways to publish: with no base (or trunk as base) the branch opens as a single PR onto trunk; to stack it on another branch, pass that branch as base and extend: true. A base other than trunk without extend is refused with implicit_stack. The result says which one it made. Mergestorm verifies each commit's Git tree before it moves the branch and returns the verified head and tree. It refuses with head_changed when the branch on GitHub holds work your local branch does not, base_changed when the base commit left the base branch, protected_branch for trunk and Mergestorm-owned branches, and payload_too_large beyond 20 commits, 500 changed paths, 2 MiB a file or 8 MiB in total; submodules and merge commits are not carried. replace_head replaces that exact GitHub head with the local branch and discards its commits. Safe to call again after a lost response or a failure: it resumes and never duplicates the branch, commits, pull request or stack." + STACK_WATCH_RESULT_NOTE,
|
|
1663
1970
|
inputSchema: stackPublishSchema,
|
|
1664
1971
|
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: true }
|
|
1665
1972
|
},
|
|
1666
1973
|
async (args) => {
|
|
1667
1974
|
try {
|
|
1668
|
-
return ok(await stackPublish(args));
|
|
1975
|
+
return ok(await stackPublish(args, config));
|
|
1669
1976
|
} catch (err) {
|
|
1670
1977
|
return fail(err);
|
|
1671
1978
|
}
|
|
@@ -1681,7 +1988,7 @@ function createMergestormMcpServer() {
|
|
|
1681
1988
|
},
|
|
1682
1989
|
async (args) => {
|
|
1683
1990
|
try {
|
|
1684
|
-
return ok(await stackPublishStatus(args));
|
|
1991
|
+
return ok(await stackPublishStatus(args, config));
|
|
1685
1992
|
} catch (err) {
|
|
1686
1993
|
return fail(err);
|
|
1687
1994
|
}
|
|
@@ -1711,18 +2018,50 @@ function createMergestormMcpServer() {
|
|
|
1711
2018
|
...auto_patch !== void 0 ? { auto_patch } : {},
|
|
1712
2019
|
...auto_resolve_conflicts !== void 0 ? { auto_resolve_conflicts } : {},
|
|
1713
2020
|
...auto_fix_ci !== void 0 ? { auto_fix_ci } : {}
|
|
1714
|
-
})
|
|
2021
|
+
}, config)
|
|
1715
2022
|
);
|
|
1716
2023
|
} catch (err) {
|
|
1717
2024
|
return fail(err);
|
|
1718
2025
|
}
|
|
1719
2026
|
}
|
|
1720
2027
|
);
|
|
2028
|
+
addTool(
|
|
2029
|
+
"stack_restack",
|
|
2030
|
+
{
|
|
2031
|
+
title: "Restack stack",
|
|
2032
|
+
description: "Rebase one owned stack's layer branches onto their parents and push them, after the trunk or a lower layer moved. Does not merge or enqueue anything." + STACK_WATCH_RESULT_NOTE,
|
|
2033
|
+
inputSchema: { stack_id: z5.string().min(1) },
|
|
2034
|
+
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: true }
|
|
2035
|
+
},
|
|
2036
|
+
async ({ stack_id }) => {
|
|
2037
|
+
try {
|
|
2038
|
+
return ok(await stackRestack(stack_id, config));
|
|
2039
|
+
} catch (err) {
|
|
2040
|
+
return fail(err);
|
|
2041
|
+
}
|
|
2042
|
+
}
|
|
2043
|
+
);
|
|
2044
|
+
addTool(
|
|
2045
|
+
"stack_enqueue",
|
|
2046
|
+
{
|
|
2047
|
+
title: "Send stack to merge queue",
|
|
2048
|
+
description: "Send one owned stack to the Mergestorm merge queue, which tests it and lands it when its checks pass. To restart a failed or stuck land, call stack_restack first and then this. When this server serves a Breeze chat, the chat is woken when the stack lands, is bounced or is archived." + STACK_WATCH_RESULT_NOTE,
|
|
2049
|
+
inputSchema: { stack_id: z5.string().min(1) },
|
|
2050
|
+
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: true }
|
|
2051
|
+
},
|
|
2052
|
+
async ({ stack_id }) => {
|
|
2053
|
+
try {
|
|
2054
|
+
return ok(await stackEnqueue(stack_id, config, chatId));
|
|
2055
|
+
} catch (err) {
|
|
2056
|
+
return fail(err);
|
|
2057
|
+
}
|
|
2058
|
+
}
|
|
2059
|
+
);
|
|
1721
2060
|
addTool(
|
|
1722
2061
|
"stack_status",
|
|
1723
2062
|
{
|
|
1724
2063
|
title: "Get stack status",
|
|
1725
|
-
description: `Fetch one owned Mergestorm stack with enriched checks and agent state, plus attention, issues, and currentCandidate from the same blocker rules as stack_wait, read against that stack's own merge queue. trunkBranch is mg-stack-<n> for an adopted 2+ PR stack and matches the bottom open PR's GitHub base; an mg-park-* parentBranch is a freeze. Neither is drift to fix. repair names the concrete fix for attention; landGatePending is a wait, not attention. held names a blocker no agent run clears while Vortex or Cyclone is still busy on that PR (actAfter agents_idle, waitingOn); repair then describes the fix to plan, not to apply yet. Auto land off after a merge_failed, gh_error, or ci_timeout bounce (the stack's autoLandOff reads reason bounced) returns attention with the blocker "Auto land off after <kind> bounce", the bounce in issues[], and a repair of kind auto_land_off: fix the cause, then turn Auto land back on or enqueue. auto_land_switched_off (returned only when the PR has no live blocker of its own): Auto land was switched off without a bounce (queue entry cancelled, or nothing left to land); do not patch, push or turn it back on yourself, tell the human. A Vortex request_changes handoff at the live head returns attention with blocker "Vortex findings need a person" and repair kind vortex_findings; verify its findings against the live head.` + STACK_WATCH_RESULT_NOTE,
|
|
2064
|
+
description: `Fetch one owned Mergestorm stack with enriched checks and agent state, plus attention, issues, and currentCandidate from the same blocker rules as stack_wait, read against that stack's own merge queue. trunkBranch is mg-stack-<n> for an adopted 2+ PR stack and matches the bottom open PR's GitHub base; an mg-park-* parentBranch is a freeze. Neither is drift to fix. repair names the concrete fix for attention; landGatePending is a wait, not attention. held names a blocker no agent run clears while Vortex or Cyclone is still busy on that PR (actAfter agents_idle, waitingOn); repair then describes the fix to plan, not to apply yet. Auto land off after a merge_failed, gh_error, or ci_timeout bounce (the stack's autoLandOff reads reason bounced) returns attention with the blocker "Auto land off after <kind> bounce", the bounce in issues[], and a repair of kind auto_land_off: fix the cause, then turn Auto land back on or enqueue. auto_land_switched_off (returned only when the PR has no live blocker of its own): Auto land was switched off without a bounce (queue entry cancelled, or nothing left to land); do not patch, push or turn it back on yourself, tell the human. A Vortex request_changes handoff at the live head returns attention with blocker "Vortex findings need a person" and repair kind vortex_findings; verify its findings against the live head.` + STACK_ATTENTION_NOTE + STACK_WATCH_RESULT_NOTE,
|
|
1726
2065
|
inputSchema: {
|
|
1727
2066
|
stack_id: z5.string().min(1)
|
|
1728
2067
|
},
|
|
@@ -1730,7 +2069,7 @@ function createMergestormMcpServer() {
|
|
|
1730
2069
|
},
|
|
1731
2070
|
async ({ stack_id }) => {
|
|
1732
2071
|
try {
|
|
1733
|
-
return ok(await stackStatus(stack_id));
|
|
2072
|
+
return ok(await stackStatus(stack_id, config));
|
|
1734
2073
|
} catch (err) {
|
|
1735
2074
|
return fail(err);
|
|
1736
2075
|
}
|
|
@@ -1740,13 +2079,13 @@ function createMergestormMcpServer() {
|
|
|
1740
2079
|
"stack_wait",
|
|
1741
2080
|
{
|
|
1742
2081
|
title: "Wait for stack attention",
|
|
1743
|
-
description: `Wait for a stack to need attention. Returns a mergestorm.stack_watch/v1 envelope; timeout_s: 0 returns one snapshot and timeout_s: 1-45 waits up to that many seconds, returning the current status (waiting or in_progress) if a snapshot was read and no attention is found. A timeout with either of those statuses is not a failure; waiting and in_progress end this slice, and the next call is stack_wait again with the same cursor. A timeout of failed means no assessment was produced. A stack that no longer exists returns status failed with watch.done true (reason not_found) and is not an error. While Cyclone or Vortex is still working on the blocked PR it returns in_progress, not attention, with busy[] naming the PR, blocker, and agent; agents carries that PR's vortexStatus, cycloneStatus, vortexReview, and busy flags. A blocker no agent run clears (a merge conflict vs the live parent, a restack Conflict or Restack failed, a Draft PR, a seam review pending with no review running, "Seam review stuck in reviewing, no review running", "Seam re-review pending, no review running", "Seam verdict is for an older head, no review running", or a review skipped for review quota) is still named during that in_progress, in blocker, prNumber, headSha, and issues[], with actAfter: "agents_idle" and waitingOn listing the busy agents: plan the fix, and act when a snapshot after the agents finish returns attention, re-reading the blocker then. Blockers the agents can change keep blocker and actAfter null. Attention never calls for changing a PR's GitHub base. When present, repair names the concrete fix for attention (restack_conflict and merge_conflict carry liveParent, never mg-park-*). For the three stuck-seam blockers, repair kind seam_review_stuck: do not patch or push; tell the human to click Continue, which re-queues the seam review at the live head. A pending land gate (landGatePending) returns in_progress, not attention. Auto land off after a merge_failed, gh_error, or ci_timeout bounce (the stack's autoLandOff reads reason bounced) returns attention with the blocker "Auto land off after <kind> bounce", the bounce in issues[], and a repair of kind auto_land_off: fix the cause, then turn Auto land back on or enqueue. auto_land_switched_off (returned only when the PR has no live blocker of its own): Auto land was switched off without a bounce (queue entry cancelled, or nothing left to land); do not patch, push or turn it back on yourself, tell the human. A Vortex request_changes handoff at the live head returns attention with blocker "Vortex findings need a person" and repair kind vortex_findings; verify its findings against the live head.` + STACK_WATCH_RESULT_NOTE,
|
|
2082
|
+
description: `Wait for a stack to need attention. Returns a mergestorm.stack_watch/v1 envelope; timeout_s: 0 returns one snapshot and timeout_s: 1-45 waits up to that many seconds, returning the current status (waiting or in_progress) if a snapshot was read and no attention is found. A timeout with either of those statuses is not a failure; waiting and in_progress end this slice, and the next call is stack_wait again with the same cursor. A timeout of failed means no assessment was produced. A stack that no longer exists returns status failed with watch.done true (reason not_found) and is not an error. While Cyclone or Vortex is still working on the blocked PR it returns in_progress, not attention, with busy[] naming the PR, blocker, and agent; agents carries that PR's vortexStatus, cycloneStatus, vortexReview, and busy flags. When another layer has a blocker no agent works on, attention names that blocker instead and busy[] is empty. A blocker no agent run clears (a merge conflict vs the live parent, a restack Conflict or Restack failed, a Draft PR, a seam review pending with no review running, "Seam review stuck in reviewing, no review running", "Seam re-review pending, no review running", "Seam verdict is for an older head, no review running", or a review skipped for review quota) is still named during that in_progress, in blocker, prNumber, headSha, and issues[], with actAfter: "agents_idle" and waitingOn listing the busy agents: plan the fix, and act when a snapshot after the agents finish returns attention, re-reading the blocker then. Blockers the agents can change keep blocker and actAfter null. Attention never calls for changing a PR's GitHub base. When present, repair names the concrete fix for attention (restack_conflict and merge_conflict carry liveParent, never mg-park-*). For the three stuck-seam blockers, repair kind seam_review_stuck: do not patch or push; tell the human to click Continue, which re-queues the seam review at the live head. A pending land gate (landGatePending) returns in_progress, not attention. Auto land off after a merge_failed, gh_error, or ci_timeout bounce (the stack's autoLandOff reads reason bounced) returns attention with the blocker "Auto land off after <kind> bounce", the bounce in issues[], and a repair of kind auto_land_off: fix the cause, then turn Auto land back on or enqueue. auto_land_switched_off (returned only when the PR has no live blocker of its own): Auto land was switched off without a bounce (queue entry cancelled, or nothing left to land); do not patch, push or turn it back on yourself, tell the human. A Vortex request_changes handoff at the live head returns attention with blocker "Vortex findings need a person" and repair kind vortex_findings; verify its findings against the live head.` + STACK_ATTENTION_NOTE + STACK_WATCH_RESULT_NOTE,
|
|
1744
2083
|
inputSchema: stackWaitSchema,
|
|
1745
2084
|
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true }
|
|
1746
2085
|
},
|
|
1747
2086
|
async (args, extra) => {
|
|
1748
2087
|
try {
|
|
1749
|
-
const payload = await stackWait(args,
|
|
2088
|
+
const payload = await stackWait(args, config, { signal: extra.signal, ...chatId ? { chatId } : {} });
|
|
1750
2089
|
return ok(payload);
|
|
1751
2090
|
} catch (err) {
|
|
1752
2091
|
return fail(err);
|
|
@@ -1765,7 +2104,7 @@ function createMergestormMcpServer() {
|
|
|
1765
2104
|
},
|
|
1766
2105
|
async ({ stack_id }) => {
|
|
1767
2106
|
try {
|
|
1768
|
-
return ok(await queueStatus(stack_id));
|
|
2107
|
+
return ok(await queueStatus(stack_id, config));
|
|
1769
2108
|
} catch (err) {
|
|
1770
2109
|
return fail(err);
|
|
1771
2110
|
}
|
|
@@ -1781,7 +2120,7 @@ function createMergestormMcpServer() {
|
|
|
1781
2120
|
},
|
|
1782
2121
|
async (args) => {
|
|
1783
2122
|
try {
|
|
1784
|
-
return ok(await repoSettingsGet(args ?? {}));
|
|
2123
|
+
return ok(await repoSettingsGet(args ?? {}, config));
|
|
1785
2124
|
} catch (err) {
|
|
1786
2125
|
return fail(err);
|
|
1787
2126
|
}
|
|
@@ -1802,7 +2141,23 @@ function createMergestormMcpServer() {
|
|
|
1802
2141
|
},
|
|
1803
2142
|
async (args) => {
|
|
1804
2143
|
try {
|
|
1805
|
-
return ok(await repoSettingsSet(args ?? {}));
|
|
2144
|
+
return ok(await repoSettingsSet(args ?? {}, config));
|
|
2145
|
+
} catch (err) {
|
|
2146
|
+
return fail(err);
|
|
2147
|
+
}
|
|
2148
|
+
}
|
|
2149
|
+
);
|
|
2150
|
+
addTool(
|
|
2151
|
+
"surge_setup",
|
|
2152
|
+
{
|
|
2153
|
+
title: "Set up Surge CI",
|
|
2154
|
+
description: "Set up Surge CI on one repository of the user's account: if Surge does not serve it yet, serve it (it joins the repositories Surge serves and gets the account's Surge settings), then open a pull request that points its workflow jobs at Surge runners and adds the merge queue batch trigger. Nothing in the repository's files changes until that pull request is merged. The answer says what happened, with the pull request link.",
|
|
2155
|
+
inputSchema: { owner: z5.string().min(1), repo: z5.string().min(1) },
|
|
2156
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: true }
|
|
2157
|
+
},
|
|
2158
|
+
async (args) => {
|
|
2159
|
+
try {
|
|
2160
|
+
return ok(await surgeSetup(args, config));
|
|
1806
2161
|
} catch (err) {
|
|
1807
2162
|
return fail(err);
|
|
1808
2163
|
}
|
|
@@ -1817,7 +2172,7 @@ function createMergestormMcpServer() {
|
|
|
1817
2172
|
},
|
|
1818
2173
|
async () => {
|
|
1819
2174
|
try {
|
|
1820
|
-
return ok(await settingsGet());
|
|
2175
|
+
return ok(await settingsGet(config));
|
|
1821
2176
|
} catch (err) {
|
|
1822
2177
|
return fail(err);
|
|
1823
2178
|
}
|
|
@@ -1843,7 +2198,7 @@ function createMergestormMcpServer() {
|
|
|
1843
2198
|
},
|
|
1844
2199
|
async (args) => {
|
|
1845
2200
|
try {
|
|
1846
|
-
return ok(await settingsSet(args ?? {}));
|
|
2201
|
+
return ok(await settingsSet(args ?? {}, config));
|
|
1847
2202
|
} catch (err) {
|
|
1848
2203
|
return fail(err);
|
|
1849
2204
|
}
|