mergestorm-mcp 0.1.10 → 0.2.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/README.md +6 -2
- package/dist/index.js +376 -299
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
# mergestorm-mcp
|
|
2
2
|
|
|
3
|
-
Stdio MCP server for Mergestorm. Tools: `whoami`, `credits`, `review_list`, `
|
|
3
|
+
Stdio MCP server for Mergestorm. Tools: `whoami`, `credits`, `review_list`, `review_submit`, `review_wait`, `review_wait_pr`, `stack_adopt`, `stack_list`, `stack_set`, `stack_status`, `stack_wait`, `queue_status`, `settings_get`, `settings_set`.
|
|
4
|
+
|
|
5
|
+
Every tool declares MCP `annotations`. The reads carry `readOnlyHint: true`; `review_submit`, `stack_adopt`, `stack_set`, and `settings_set` carry `readOnlyHint: false` and `destructiveHint: false`.
|
|
6
|
+
|
|
7
|
+
`review_wait({ job_id, timeout_s? })` reads a local review job by the `job_id` that `review_submit` returned. `review_wait_pr({ owner, repo, pr_number, after_sha?, pass?, after_pass?, timeout_s? })` reads the Vortex pass on a GitHub PR. Both poll one 45s slice by default; `timeout_s: 0` reads once without polling.
|
|
4
8
|
|
|
5
9
|
The stack tools expose reads plus one scoped policy write:
|
|
6
10
|
|
|
@@ -8,7 +12,7 @@ The stack tools expose reads plus one scoped policy write:
|
|
|
8
12
|
- `stack_list` returns the current API key owner's registered stacks.
|
|
9
13
|
- `stack_set({ stack_id, auto_land?, auto_review?, auto_patch? })` sets per-stack policy on one owned stack. `auto_land` flips Auto land. `auto_review` and `auto_patch` pin Vortex auto-review or Cyclone auto-patch for that stack in either direction (`true` or `false`); `null` clears the pin so the stack follows the account setting. Pass at least one key. Account settings are never changed here.
|
|
10
14
|
- `stack_status({ stack_id })` returns one owned 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. It returns a structured `stack_not_found` or `rate_limited` error.
|
|
11
|
-
- `stack_wait({ stack_id, timeout_s?, enrolled_head_sha?, after_finished_at?, bounce_id? })` waits for a stack or queue state change and returns the latest snapshots. `timeout_s: 0` returns one snapshot; a positive timeout after a snapshot with no attention returns the current `waiting` or `in_progress` status with selectors to use for the next call. An unread deadline returns `failed`.
|
|
15
|
+
- `stack_wait({ stack_id, timeout_s?, enrolled_head_sha?, after_finished_at?, bounce_id? })` waits for a stack or queue state change and returns the latest snapshots. `timeout_s: 0` returns one snapshot; a positive timeout after a snapshot with no attention returns the current `waiting` or `in_progress` status with selectors to use for the next call. An unread deadline returns `failed`. While Cyclone or Vortex is still working on the blocked PR, `stack_wait` and `stack_status` return `in_progress` instead of `attention`, with `busy[]` naming the PR, blocker, and agent, and `agents` carrying that PR's `vortexStatus`, `cycloneStatus`, `vortexReview`, and busy flags. A nonempty `busy[]` means wait. A queued Vortex run holds for at most 15 minutes, `seam_pending` alone does not hold, and reviewing or patching holds until the run finishes or its lease or check run is cleared. A hold can hide a blocker no agent clears (such as Draft); if it looks stuck, refresh `stack_status` and tell the human.
|
|
12
16
|
|
|
13
17
|
Auth is `MERGESTORM_API_KEY`, then `~/.mergestorm/config.json` (same as `mg`).
|
|
14
18
|
|
package/dist/index.js
CHANGED
|
@@ -8,6 +8,33 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
|
8
8
|
import { z as z3 } from "zod";
|
|
9
9
|
import { CommandError as CommandError11, BEARER_SETTINGS as BEARER_SETTINGS2 } from "mergestorm/client";
|
|
10
10
|
|
|
11
|
+
// src/brand-icons.ts
|
|
12
|
+
var MARK_RECTS = [
|
|
13
|
+
{ x: 103.685, y: 79.639, width: 336.635, height: 32.005 },
|
|
14
|
+
{ x: 71.68, y: 143.816, width: 288.545, height: 32.005 },
|
|
15
|
+
{ x: 103.685, y: 207.993, width: 192.363, height: 32.005 },
|
|
16
|
+
{ x: 151.775, y: 272.002, width: 176.277, height: 32.005 },
|
|
17
|
+
{ x: 215.952, y: 336.179, width: 96.182, height: 32.005 },
|
|
18
|
+
{ x: 264.043, y: 400.356, width: 32.005, height: 32.005 }
|
|
19
|
+
];
|
|
20
|
+
var SITE = "https://mergestorm.ai";
|
|
21
|
+
function markSvg(fill) {
|
|
22
|
+
const rects = MARK_RECTS.map(
|
|
23
|
+
(r) => `<rect x="${r.x}" y="${r.y}" width="${r.width}" height="${r.height}" fill="${fill}"/>`
|
|
24
|
+
).join("");
|
|
25
|
+
return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="64 64 384 384">${rects}</svg>`;
|
|
26
|
+
}
|
|
27
|
+
function svgDataUri(svg) {
|
|
28
|
+
return `data:image/svg+xml;base64,${Buffer.from(svg, "utf8").toString("base64")}`;
|
|
29
|
+
}
|
|
30
|
+
var MCP_SERVER_ICONS = [
|
|
31
|
+
{ src: svgDataUri(markSvg("#000000")), mimeType: "image/svg+xml", sizes: ["any"], theme: "light" },
|
|
32
|
+
{ src: svgDataUri(markSvg("#ffffff")), mimeType: "image/svg+xml", sizes: ["any"], theme: "dark" },
|
|
33
|
+
{ src: `${SITE}/images/android-chrome-192x192.png`, mimeType: "image/png", sizes: ["192x192"] },
|
|
34
|
+
{ src: `${SITE}/images/android-chrome-512x512.png`, mimeType: "image/png", sizes: ["512x512"] }
|
|
35
|
+
];
|
|
36
|
+
var MCP_WEBSITE_URL = SITE;
|
|
37
|
+
|
|
11
38
|
// src/tools/credits.ts
|
|
12
39
|
import { CommandError, getMe, loadConfig } from "mergestorm/client";
|
|
13
40
|
async function credits(cfg) {
|
|
@@ -135,95 +162,43 @@ async function queueStatus(stackId, cfg) {
|
|
|
135
162
|
}
|
|
136
163
|
}
|
|
137
164
|
|
|
138
|
-
// src/tools/review-
|
|
139
|
-
import {
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
function prReviewTarget(owner, repo, prNumber) {
|
|
145
|
-
const cleanOwner = owner.trim();
|
|
146
|
-
const cleanRepo = repo.trim();
|
|
147
|
-
if (!cleanOwner || !cleanRepo || !Number.isInteger(prNumber) || prNumber <= 0) {
|
|
148
|
-
throw new CommandError3(
|
|
149
|
-
"owner, repo, and a positive pr_number are required",
|
|
150
|
-
2,
|
|
151
|
-
"usage"
|
|
152
|
-
);
|
|
153
|
-
}
|
|
154
|
-
return { owner: cleanOwner, repo: cleanRepo, prNumber };
|
|
155
|
-
}
|
|
156
|
-
function prReviewSummary(owner, repo, prNumber, envelope) {
|
|
157
|
-
const bits = [`${owner}/${repo}#${prNumber}`, envelope.status];
|
|
158
|
-
const sha = envelope.head_sha?.trim();
|
|
159
|
-
if (sha) bits.push(`sha ${sha.slice(0, 7)}`);
|
|
160
|
-
if (typeof envelope.finding_count === "number") {
|
|
161
|
-
bits.push(
|
|
162
|
-
`${envelope.finding_count} finding${envelope.finding_count === 1 ? "" : "s"}`
|
|
163
|
-
);
|
|
164
|
-
}
|
|
165
|
-
if (envelope.phase) bits.push(String(envelope.phase));
|
|
166
|
-
return bits.join(" \xB7 ");
|
|
167
|
-
}
|
|
168
|
-
function payloadFromPrReview(owner, repo, prNumber, envelope) {
|
|
169
|
-
return {
|
|
170
|
-
summary: prReviewSummary(owner, repo, prNumber, envelope),
|
|
171
|
-
data: { ...envelope }
|
|
172
|
-
};
|
|
173
|
-
}
|
|
174
|
-
function payloadFromPrRateLimit(err, owner, repo, prNumber) {
|
|
175
|
-
if (!isCommandErrorCode2(err, "rate_limited")) return null;
|
|
165
|
+
// src/tools/review-list.ts
|
|
166
|
+
import { listJobs, toReviewJobEnvelope } from "mergestorm/client";
|
|
167
|
+
async function reviewList(limit = 10, cfg) {
|
|
168
|
+
const items = await listJobs(limit, cfg);
|
|
169
|
+
const envelopes = items.map((row) => toReviewJobEnvelope(row));
|
|
170
|
+
const n = envelopes.length;
|
|
176
171
|
return {
|
|
177
|
-
summary: `${
|
|
178
|
-
data: {
|
|
179
|
-
status: "rate_limited",
|
|
180
|
-
error: err.message,
|
|
181
|
-
retry_after_seconds: err.retryAfterSeconds
|
|
182
|
-
}
|
|
172
|
+
summary: n === 0 ? "No review jobs" : `${n} review job${n === 1 ? "" : "s"}`,
|
|
173
|
+
data: { items: envelopes }
|
|
183
174
|
};
|
|
184
175
|
}
|
|
185
|
-
async function reviewGetPr(owner, repo, prNumber, cfg, opts = {}) {
|
|
186
|
-
const target = prReviewTarget(owner, repo, prNumber);
|
|
187
|
-
try {
|
|
188
|
-
const envelope = await getPrVortexReview(
|
|
189
|
-
target.owner,
|
|
190
|
-
target.repo,
|
|
191
|
-
target.prNumber,
|
|
192
|
-
cfg,
|
|
193
|
-
{ afterSha: opts.afterSha, pass: opts.pass, afterPass: opts.afterPass }
|
|
194
|
-
);
|
|
195
|
-
return payloadFromPrReview(
|
|
196
|
-
target.owner,
|
|
197
|
-
target.repo,
|
|
198
|
-
target.prNumber,
|
|
199
|
-
envelope
|
|
200
|
-
);
|
|
201
|
-
} catch (err) {
|
|
202
|
-
const rateLimited = payloadFromPrRateLimit(
|
|
203
|
-
err,
|
|
204
|
-
target.owner,
|
|
205
|
-
target.repo,
|
|
206
|
-
target.prNumber
|
|
207
|
-
);
|
|
208
|
-
if (rateLimited) return rateLimited;
|
|
209
|
-
throw err;
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
176
|
|
|
213
|
-
// src/tools/review-
|
|
177
|
+
// src/tools/review-submit.ts
|
|
178
|
+
import { createHash } from "node:crypto";
|
|
179
|
+
import { realpath } from "node:fs/promises";
|
|
180
|
+
import { relative, resolve, sep } from "node:path";
|
|
214
181
|
import {
|
|
215
|
-
CommandError as
|
|
216
|
-
|
|
217
|
-
|
|
182
|
+
CommandError as CommandError3,
|
|
183
|
+
REVIEW_THREAD_SLUG_RE,
|
|
184
|
+
ReviewPollTimeoutError,
|
|
185
|
+
collectReviewInput,
|
|
186
|
+
discoverTrunk,
|
|
187
|
+
formatReviewSubmitError,
|
|
188
|
+
loadConfig as loadConfig3,
|
|
189
|
+
loadReviewContext,
|
|
190
|
+
pollReview,
|
|
191
|
+
rateLimitedMessage as rateLimitedMessage2,
|
|
192
|
+
submitReview
|
|
218
193
|
} from "mergestorm/client";
|
|
219
194
|
|
|
220
195
|
// src/tools/envelope.ts
|
|
221
196
|
import {
|
|
222
|
-
isCommandErrorCode as
|
|
223
|
-
toReviewJobEnvelope
|
|
197
|
+
isCommandErrorCode as isCommandErrorCode2,
|
|
198
|
+
toReviewJobEnvelope as toReviewJobEnvelope2
|
|
224
199
|
} from "mergestorm/client";
|
|
225
200
|
function payloadFromRow(row, fallback = {}) {
|
|
226
|
-
const envelope =
|
|
201
|
+
const envelope = toReviewJobEnvelope2(row, fallback);
|
|
227
202
|
const bits = [envelope.status];
|
|
228
203
|
if (envelope.verdict) bits.push(envelope.verdict);
|
|
229
204
|
return {
|
|
@@ -232,7 +207,7 @@ function payloadFromRow(row, fallback = {}) {
|
|
|
232
207
|
};
|
|
233
208
|
}
|
|
234
209
|
function payloadFromRateLimit(err, jobId) {
|
|
235
|
-
if (!
|
|
210
|
+
if (!isCommandErrorCode2(err, "rate_limited")) return null;
|
|
236
211
|
return payloadFromRow(null, {
|
|
237
212
|
jobId,
|
|
238
213
|
status: "rate_limited",
|
|
@@ -241,57 +216,7 @@ function payloadFromRateLimit(err, jobId) {
|
|
|
241
216
|
});
|
|
242
217
|
}
|
|
243
218
|
|
|
244
|
-
// src/tools/review-get.ts
|
|
245
|
-
async function reviewGet(jobId, cfg) {
|
|
246
|
-
let row;
|
|
247
|
-
try {
|
|
248
|
-
row = await getReview(jobId, cfg);
|
|
249
|
-
} catch (err) {
|
|
250
|
-
const rateLimited = payloadFromRateLimit(err, jobId);
|
|
251
|
-
if (rateLimited) return rateLimited;
|
|
252
|
-
throw err;
|
|
253
|
-
}
|
|
254
|
-
if (!row || typeof row !== "object" || typeof row.status !== "string") {
|
|
255
|
-
throw new CommandError4(`Unexpected response for review ${jobId}.`);
|
|
256
|
-
}
|
|
257
|
-
const envelope = toReviewJobEnvelope2(row, { jobId });
|
|
258
|
-
const bits = [envelope.status];
|
|
259
|
-
if (envelope.verdict) bits.push(envelope.verdict);
|
|
260
|
-
return {
|
|
261
|
-
summary: `${envelope.job_id ?? jobId} \xB7 ${bits.join(" \xB7 ")}`,
|
|
262
|
-
data: envelope
|
|
263
|
-
};
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
// src/tools/review-list.ts
|
|
267
|
-
import { listJobs, toReviewJobEnvelope as toReviewJobEnvelope3 } from "mergestorm/client";
|
|
268
|
-
async function reviewList(limit = 10, cfg) {
|
|
269
|
-
const items = await listJobs(limit, cfg);
|
|
270
|
-
const envelopes = items.map((row) => toReviewJobEnvelope3(row));
|
|
271
|
-
const n = envelopes.length;
|
|
272
|
-
return {
|
|
273
|
-
summary: n === 0 ? "No review jobs" : `${n} review job${n === 1 ? "" : "s"}`,
|
|
274
|
-
data: { items: envelopes }
|
|
275
|
-
};
|
|
276
|
-
}
|
|
277
|
-
|
|
278
219
|
// src/tools/review-submit.ts
|
|
279
|
-
import { createHash } from "node:crypto";
|
|
280
|
-
import { realpath } from "node:fs/promises";
|
|
281
|
-
import { relative, resolve, sep } from "node:path";
|
|
282
|
-
import {
|
|
283
|
-
CommandError as CommandError5,
|
|
284
|
-
REVIEW_THREAD_SLUG_RE,
|
|
285
|
-
ReviewPollTimeoutError,
|
|
286
|
-
collectReviewInput,
|
|
287
|
-
discoverTrunk,
|
|
288
|
-
formatReviewSubmitError,
|
|
289
|
-
loadConfig as loadConfig3,
|
|
290
|
-
loadReviewContext,
|
|
291
|
-
pollReview,
|
|
292
|
-
rateLimitedMessage as rateLimitedMessage2,
|
|
293
|
-
submitReview
|
|
294
|
-
} from "mergestorm/client";
|
|
295
220
|
var MCP_WAIT_CEILING_S = 300;
|
|
296
221
|
var MCP_PR_WAIT_DEFAULT_S = 45;
|
|
297
222
|
var PINNABLE_SPECIALISTS = [
|
|
@@ -309,7 +234,7 @@ function assertPinnableSpecialists(specialists) {
|
|
|
309
234
|
(id) => !PINNABLE_SPECIALISTS.includes(id)
|
|
310
235
|
);
|
|
311
236
|
if (unknown.length === 0) return;
|
|
312
|
-
throw new
|
|
237
|
+
throw new CommandError3(
|
|
313
238
|
`Unknown specialist: ${unknown.join(", ")}. Valid lanes: ${PINNABLE_SPECIALISTS.join(", ")}. governance and seam cannot be requested.`,
|
|
314
239
|
2,
|
|
315
240
|
"usage"
|
|
@@ -321,7 +246,7 @@ function mcpSandboxRoot() {
|
|
|
321
246
|
function waitTimeoutMs(timeout_s, defaultSeconds = MCP_PR_WAIT_DEFAULT_S) {
|
|
322
247
|
const seconds = timeout_s ?? defaultSeconds;
|
|
323
248
|
if (!Number.isFinite(seconds) || seconds <= 0) {
|
|
324
|
-
throw new
|
|
249
|
+
throw new CommandError3("timeout_s must be a positive number of seconds", 2, "usage");
|
|
325
250
|
}
|
|
326
251
|
return Math.max(1, Math.round(Math.min(seconds, MCP_WAIT_CEILING_S) * 1e3));
|
|
327
252
|
}
|
|
@@ -354,11 +279,11 @@ async function resolveInput(input) {
|
|
|
354
279
|
]);
|
|
355
280
|
const relToSandbox = relative(sandboxReal, cwdReal);
|
|
356
281
|
if (relToSandbox === ".." || relToSandbox.startsWith(`..${sep}`)) {
|
|
357
|
-
throw new
|
|
282
|
+
throw new CommandError3("cwd must be inside the configured sandbox root", 2, "usage");
|
|
358
283
|
}
|
|
359
284
|
} catch (err) {
|
|
360
|
-
if (err instanceof
|
|
361
|
-
throw new
|
|
285
|
+
if (err instanceof CommandError3) throw err;
|
|
286
|
+
throw new CommandError3("cwd must be inside the configured sandbox root", 2, "usage");
|
|
362
287
|
}
|
|
363
288
|
const base = input.base?.trim() || discoverTrunk(cwd);
|
|
364
289
|
return collectReviewInput(base, head, cwd, sandboxRoot);
|
|
@@ -369,10 +294,10 @@ async function reviewSubmit(input, cfg, opts = {}) {
|
|
|
369
294
|
const timeoutMs = waitTimeoutMs(input.timeout_s);
|
|
370
295
|
assertPinnableSpecialists(input.specialists);
|
|
371
296
|
if (input.thread && !REVIEW_THREAD_SLUG_RE.test(input.thread)) {
|
|
372
|
-
throw new
|
|
297
|
+
throw new CommandError3("thread must be 1\u2013120 chars [A-Za-z0-9._/-]", 2, "usage");
|
|
373
298
|
}
|
|
374
299
|
if (input.context === "-") {
|
|
375
|
-
throw new
|
|
300
|
+
throw new CommandError3(
|
|
376
301
|
'context "-" (stdin) is not available over MCP; pass the text directly',
|
|
377
302
|
2,
|
|
378
303
|
"usage"
|
|
@@ -422,7 +347,7 @@ async function reviewSubmit(input, cfg, opts = {}) {
|
|
|
422
347
|
};
|
|
423
348
|
}
|
|
424
349
|
if (!jobId) {
|
|
425
|
-
throw new
|
|
350
|
+
throw new CommandError3("Review API returned no job_id");
|
|
426
351
|
}
|
|
427
352
|
if (!wait) {
|
|
428
353
|
return payloadFromRow(submitted.row, { jobId, status: submitted.row.status ?? "queued" });
|
|
@@ -454,9 +379,93 @@ import {
|
|
|
454
379
|
loadConfig as loadConfig4,
|
|
455
380
|
pollPrVortexReview
|
|
456
381
|
} from "mergestorm/client";
|
|
382
|
+
|
|
383
|
+
// src/tools/review-get-pr.ts
|
|
384
|
+
import {
|
|
385
|
+
CommandError as CommandError4,
|
|
386
|
+
getPrVortexReview,
|
|
387
|
+
isCommandErrorCode as isCommandErrorCode3
|
|
388
|
+
} from "mergestorm/client";
|
|
389
|
+
function prReviewTarget(owner, repo, prNumber) {
|
|
390
|
+
const cleanOwner = owner.trim();
|
|
391
|
+
const cleanRepo = repo.trim();
|
|
392
|
+
if (!cleanOwner || !cleanRepo || !Number.isInteger(prNumber) || prNumber <= 0) {
|
|
393
|
+
throw new CommandError4(
|
|
394
|
+
"owner, repo, and a positive pr_number are required",
|
|
395
|
+
2,
|
|
396
|
+
"usage"
|
|
397
|
+
);
|
|
398
|
+
}
|
|
399
|
+
return { owner: cleanOwner, repo: cleanRepo, prNumber };
|
|
400
|
+
}
|
|
401
|
+
function prReviewSummary(owner, repo, prNumber, envelope) {
|
|
402
|
+
const bits = [`${owner}/${repo}#${prNumber}`, envelope.status];
|
|
403
|
+
const sha = envelope.head_sha?.trim();
|
|
404
|
+
if (sha) bits.push(`sha ${sha.slice(0, 7)}`);
|
|
405
|
+
if (typeof envelope.finding_count === "number") {
|
|
406
|
+
bits.push(
|
|
407
|
+
`${envelope.finding_count} finding${envelope.finding_count === 1 ? "" : "s"}`
|
|
408
|
+
);
|
|
409
|
+
}
|
|
410
|
+
if (envelope.phase) bits.push(String(envelope.phase));
|
|
411
|
+
return bits.join(" \xB7 ");
|
|
412
|
+
}
|
|
413
|
+
function payloadFromPrReview(owner, repo, prNumber, envelope) {
|
|
414
|
+
return {
|
|
415
|
+
summary: prReviewSummary(owner, repo, prNumber, envelope),
|
|
416
|
+
data: { ...envelope }
|
|
417
|
+
};
|
|
418
|
+
}
|
|
419
|
+
function payloadFromPrRateLimit(err, owner, repo, prNumber) {
|
|
420
|
+
if (!isCommandErrorCode3(err, "rate_limited")) return null;
|
|
421
|
+
return {
|
|
422
|
+
summary: `${owner}/${repo}#${prNumber} \xB7 rate_limited`,
|
|
423
|
+
data: {
|
|
424
|
+
status: "rate_limited",
|
|
425
|
+
error: err.message,
|
|
426
|
+
retry_after_seconds: err.retryAfterSeconds
|
|
427
|
+
}
|
|
428
|
+
};
|
|
429
|
+
}
|
|
430
|
+
async function reviewGetPr(owner, repo, prNumber, cfg, opts = {}) {
|
|
431
|
+
const target = prReviewTarget(owner, repo, prNumber);
|
|
432
|
+
try {
|
|
433
|
+
const envelope = await getPrVortexReview(
|
|
434
|
+
target.owner,
|
|
435
|
+
target.repo,
|
|
436
|
+
target.prNumber,
|
|
437
|
+
cfg,
|
|
438
|
+
{ afterSha: opts.afterSha, pass: opts.pass, afterPass: opts.afterPass }
|
|
439
|
+
);
|
|
440
|
+
return payloadFromPrReview(
|
|
441
|
+
target.owner,
|
|
442
|
+
target.repo,
|
|
443
|
+
target.prNumber,
|
|
444
|
+
envelope
|
|
445
|
+
);
|
|
446
|
+
} catch (err) {
|
|
447
|
+
const rateLimited = payloadFromPrRateLimit(
|
|
448
|
+
err,
|
|
449
|
+
target.owner,
|
|
450
|
+
target.repo,
|
|
451
|
+
target.prNumber
|
|
452
|
+
);
|
|
453
|
+
if (rateLimited) return rateLimited;
|
|
454
|
+
throw err;
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
// src/tools/review-wait-pr.ts
|
|
457
459
|
async function reviewWaitPr(owner, repo, prNumber, afterSha, timeout_s, cfg, opts = {}) {
|
|
458
460
|
const target = prReviewTarget(owner, repo, prNumber);
|
|
459
461
|
const resolved = cfg ?? await loadConfig4();
|
|
462
|
+
if (timeout_s === 0) {
|
|
463
|
+
return reviewGetPr(target.owner, target.repo, target.prNumber, resolved, {
|
|
464
|
+
afterSha,
|
|
465
|
+
pass: opts.pass,
|
|
466
|
+
afterPass: opts.afterPass
|
|
467
|
+
});
|
|
468
|
+
}
|
|
460
469
|
try {
|
|
461
470
|
const envelope = await pollPrVortexReview(
|
|
462
471
|
resolved,
|
|
@@ -521,12 +530,42 @@ import {
|
|
|
521
530
|
loadConfig as loadConfig5,
|
|
522
531
|
pollReview as pollReview2
|
|
523
532
|
} from "mergestorm/client";
|
|
533
|
+
|
|
534
|
+
// src/tools/review-get.ts
|
|
535
|
+
import {
|
|
536
|
+
CommandError as CommandError5,
|
|
537
|
+
getReview,
|
|
538
|
+
toReviewJobEnvelope as toReviewJobEnvelope3
|
|
539
|
+
} from "mergestorm/client";
|
|
540
|
+
async function reviewGet(jobId, cfg) {
|
|
541
|
+
let row;
|
|
542
|
+
try {
|
|
543
|
+
row = await getReview(jobId, cfg);
|
|
544
|
+
} catch (err) {
|
|
545
|
+
const rateLimited = payloadFromRateLimit(err, jobId);
|
|
546
|
+
if (rateLimited) return rateLimited;
|
|
547
|
+
throw err;
|
|
548
|
+
}
|
|
549
|
+
if (!row || typeof row !== "object" || typeof row.status !== "string") {
|
|
550
|
+
throw new CommandError5(`Unexpected response for review ${jobId}.`);
|
|
551
|
+
}
|
|
552
|
+
const envelope = toReviewJobEnvelope3(row, { jobId });
|
|
553
|
+
const bits = [envelope.status];
|
|
554
|
+
if (envelope.verdict) bits.push(envelope.verdict);
|
|
555
|
+
return {
|
|
556
|
+
summary: `${envelope.job_id ?? jobId} \xB7 ${bits.join(" \xB7 ")}`,
|
|
557
|
+
data: envelope
|
|
558
|
+
};
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
// src/tools/review-wait.ts
|
|
524
562
|
async function reviewWait(jobId, timeout_s, cfg, opts = {}) {
|
|
525
563
|
const resolved = cfg ?? await loadConfig5();
|
|
526
564
|
const id = jobId.trim();
|
|
527
565
|
if (!id) {
|
|
528
566
|
throw new CommandError6("job_id is required", 2, "usage");
|
|
529
567
|
}
|
|
568
|
+
if (timeout_s === 0) return reviewGet(id, resolved);
|
|
530
569
|
try {
|
|
531
570
|
const row = await pollReview2(resolved, id, {
|
|
532
571
|
timeoutMs: waitTimeoutMs(timeout_s),
|
|
@@ -633,11 +672,100 @@ async function settingsSet(args, cfg) {
|
|
|
633
672
|
}
|
|
634
673
|
|
|
635
674
|
// src/tools/stack-adopt.ts
|
|
636
|
-
import { adoptStack, CommandError as CommandError9 } from "mergestorm/client";
|
|
675
|
+
import { adoptStack, CommandError as CommandError9, stackWatchObligation as stackWatchObligation3 } from "mergestorm/client";
|
|
676
|
+
import { z as z2 } from "zod";
|
|
677
|
+
|
|
678
|
+
// src/tools/stack-set.ts
|
|
679
|
+
import { setStackPolicy, stackWatchObligation as stackWatchObligation2 } from "mergestorm/client";
|
|
680
|
+
|
|
681
|
+
// src/tools/stack-wait.ts
|
|
682
|
+
import {
|
|
683
|
+
loadConfig as loadConfig6,
|
|
684
|
+
stackBlockersSummary,
|
|
685
|
+
pollStackWatch,
|
|
686
|
+
StackWatchError,
|
|
687
|
+
StackWatchTimeoutError,
|
|
688
|
+
stackWatchObligation
|
|
689
|
+
} from "mergestorm/client";
|
|
637
690
|
import { z } from "zod";
|
|
691
|
+
var stackWaitSchema = {
|
|
692
|
+
stack_id: z.string().trim().min(1),
|
|
693
|
+
timeout_s: z.number().finite().min(0).max(45).default(45),
|
|
694
|
+
enrolled_head_sha: z.string().nullable().optional(),
|
|
695
|
+
after_finished_at: z.string().nullable().optional(),
|
|
696
|
+
bounce_id: z.string().nullable().optional()
|
|
697
|
+
};
|
|
698
|
+
function repairSummary(repair) {
|
|
699
|
+
if (!repair) return "";
|
|
700
|
+
const files = "files" in repair && repair.files.length ? ` Files: ${repair.files.join(", ")}.` : "";
|
|
701
|
+
return `
|
|
702
|
+
Repair #${repair.prNumber} (${repair.kind}).${files} ${repair.steps}`;
|
|
703
|
+
}
|
|
704
|
+
function withWatchText(watch, body) {
|
|
705
|
+
return `${watch.message}
|
|
706
|
+
|
|
707
|
+
${body}`;
|
|
708
|
+
}
|
|
709
|
+
function envelopeWatch(envelope) {
|
|
710
|
+
return envelope.watch ?? stackWatchObligation({
|
|
711
|
+
stackId: envelope.stackId,
|
|
712
|
+
terminal: null,
|
|
713
|
+
cursor: envelope.cursor,
|
|
714
|
+
status: envelope.status
|
|
715
|
+
});
|
|
716
|
+
}
|
|
717
|
+
function stackWaitSummary(envelope) {
|
|
718
|
+
return withWatchText(envelopeWatch(envelope), `${stackWaitStatusLine(envelope)}${repairSummary(envelope.repair)}`);
|
|
719
|
+
}
|
|
720
|
+
function stackWaitStatusLine(envelope) {
|
|
721
|
+
const base = `Stack ${envelope.stackId} \xB7 ${envelope.status}${stackBlockersSummary(envelope.blocker && envelope.prNumber != null ? { prNumber: envelope.prNumber, blocker: envelope.blocker } : null, envelope.issues, envelope.busy, envelope.landGatePending)}${envelope.assessment === "unavailable" ? " \xB7 assessment unavailable" : ""}`;
|
|
722
|
+
if (envelopeWatch(envelope).done || envelope.status !== "waiting" && envelope.status !== "in_progress") return base;
|
|
723
|
+
const selectors = [
|
|
724
|
+
`stack_id ${JSON.stringify(envelope.cursor.stackId)}`,
|
|
725
|
+
`enrolled_head_sha ${JSON.stringify(envelope.cursor.enrolledHeadSha)}`,
|
|
726
|
+
...envelope.cursor.afterFinishedAt !== void 0 ? [`after_finished_at ${JSON.stringify(envelope.cursor.afterFinishedAt)}`] : [],
|
|
727
|
+
...envelope.cursor.bounceId !== void 0 ? [`bounce_id ${JSON.stringify(envelope.cursor.bounceId)}`] : []
|
|
728
|
+
];
|
|
729
|
+
return `${base} \xB7 watch not finished: call stack_wait again with timeout_s 45 and the same cursor: ${selectors.join(", ")}`;
|
|
730
|
+
}
|
|
731
|
+
async function stackWait(input, cfg, deps = {}) {
|
|
732
|
+
const args = z.object(stackWaitSchema).parse(input);
|
|
733
|
+
const cursor = args.enrolled_head_sha !== void 0 || args.after_finished_at !== void 0 || args.bounce_id !== void 0 ? {
|
|
734
|
+
stackId: args.stack_id,
|
|
735
|
+
enrolledHeadSha: args.enrolled_head_sha ?? null,
|
|
736
|
+
...args.after_finished_at !== void 0 ? { afterFinishedAt: args.after_finished_at } : {},
|
|
737
|
+
...args.bounce_id !== void 0 ? { bounceId: args.bounce_id } : {}
|
|
738
|
+
} : void 0;
|
|
739
|
+
let envelope;
|
|
740
|
+
try {
|
|
741
|
+
envelope = await (deps.pollStackWatch ?? pollStackWatch)(cfg ?? await loadConfig6(), args.stack_id, {
|
|
742
|
+
timeoutMs: args.timeout_s * 1e3,
|
|
743
|
+
cursor,
|
|
744
|
+
signal: deps.signal
|
|
745
|
+
});
|
|
746
|
+
} catch (err) {
|
|
747
|
+
if (err instanceof StackWatchError) {
|
|
748
|
+
const envelope2 = { ...err.lastEnvelope, watch: envelopeWatch(err.lastEnvelope) };
|
|
749
|
+
return {
|
|
750
|
+
summary: stackWaitSummary(envelope2),
|
|
751
|
+
data: {
|
|
752
|
+
...envelope2,
|
|
753
|
+
...err.retryAfterSeconds !== void 0 ? { retry_after_seconds: err.retryAfterSeconds } : {}
|
|
754
|
+
},
|
|
755
|
+
isError: !envelope2.watch.done
|
|
756
|
+
};
|
|
757
|
+
}
|
|
758
|
+
if (!(err instanceof StackWatchTimeoutError)) throw err;
|
|
759
|
+
envelope = err.lastEnvelope;
|
|
760
|
+
}
|
|
761
|
+
const sealed = { ...envelope, watch: envelopeWatch(envelope) };
|
|
762
|
+
return {
|
|
763
|
+
summary: stackWaitSummary(sealed),
|
|
764
|
+
data: sealed
|
|
765
|
+
};
|
|
766
|
+
}
|
|
638
767
|
|
|
639
768
|
// src/tools/stack-set.ts
|
|
640
|
-
import { setStackPolicy } from "mergestorm/client";
|
|
641
769
|
function triWord(value) {
|
|
642
770
|
return value === null ? "default" : value ? "on" : "off";
|
|
643
771
|
}
|
|
@@ -674,28 +802,30 @@ async function stackSet(stackId, input, cfg) {
|
|
|
674
802
|
if (input.auto_patch !== void 0) {
|
|
675
803
|
parts.push(`auto-patch ${triWord(input.auto_patch)}`);
|
|
676
804
|
}
|
|
805
|
+
const watch = stackWatchObligation2({ stackId, terminal: null, unread: true, freshCursor: true });
|
|
677
806
|
return {
|
|
678
|
-
summary: `${parts.join(", ")} for stack ${stackId}
|
|
807
|
+
summary: withWatchText(watch, `${parts.join(", ")} for stack ${stackId}`),
|
|
679
808
|
data: {
|
|
680
809
|
stack_id: stackId,
|
|
681
810
|
...typeof input.auto_land === "boolean" ? { auto_land: input.auto_land } : {},
|
|
682
811
|
...input.auto_review !== void 0 ? { auto_review: input.auto_review } : {},
|
|
683
812
|
...input.auto_patch !== void 0 ? { auto_patch: input.auto_patch } : {},
|
|
684
|
-
result
|
|
813
|
+
result,
|
|
814
|
+
watch
|
|
685
815
|
}
|
|
686
816
|
};
|
|
687
817
|
}
|
|
688
818
|
|
|
689
819
|
// src/tools/stack-adopt.ts
|
|
690
820
|
var stackAdoptSchema = {
|
|
691
|
-
owner:
|
|
692
|
-
repo:
|
|
693
|
-
pr_number:
|
|
694
|
-
auto_land:
|
|
695
|
-
auto_review:
|
|
696
|
-
auto_patch:
|
|
821
|
+
owner: z2.string().trim().min(1),
|
|
822
|
+
repo: z2.string().trim().min(1),
|
|
823
|
+
pr_number: z2.number().int().min(1),
|
|
824
|
+
auto_land: z2.boolean().optional(),
|
|
825
|
+
auto_review: z2.boolean().nullable().optional(),
|
|
826
|
+
auto_patch: z2.boolean().nullable().optional()
|
|
697
827
|
};
|
|
698
|
-
var inputSchema =
|
|
828
|
+
var inputSchema = z2.object(stackAdoptSchema);
|
|
699
829
|
async function stackAdopt(input, cfg) {
|
|
700
830
|
const parsed = inputSchema.safeParse(input);
|
|
701
831
|
if (!parsed.success) {
|
|
@@ -711,9 +841,15 @@ async function stackAdopt(input, cfg) {
|
|
|
711
841
|
cfg,
|
|
712
842
|
stackSetPatch(parsed.data)
|
|
713
843
|
);
|
|
844
|
+
const summary = `Adopted stack for ${owner}/${repo}#${pr_number}`;
|
|
845
|
+
const stackId = typeof result === "object" && result !== null ? result.stack?.id : void 0;
|
|
846
|
+
if (typeof stackId !== "string" || !stackId) {
|
|
847
|
+
return { summary, data: { owner, repo, pr_number, result } };
|
|
848
|
+
}
|
|
849
|
+
const watch = stackWatchObligation3({ stackId, terminal: null, unread: true, freshCursor: true });
|
|
714
850
|
return {
|
|
715
|
-
summary:
|
|
716
|
-
data: { owner, repo, pr_number, result }
|
|
851
|
+
summary: withWatchText(watch, summary),
|
|
852
|
+
data: { owner, repo, pr_number, result, watch }
|
|
717
853
|
};
|
|
718
854
|
} catch (err) {
|
|
719
855
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -739,7 +875,7 @@ import {
|
|
|
739
875
|
} from "mergestorm/client";
|
|
740
876
|
|
|
741
877
|
// src/tools/stack-summary.ts
|
|
742
|
-
import { stackBlockers, stackBlockersSummary } from "mergestorm/client";
|
|
878
|
+
import { stackBlockers, stackBlockersSummary as stackBlockersSummary2 } from "mergestorm/client";
|
|
743
879
|
function stackSummary(stack, entries = []) {
|
|
744
880
|
const count = stack.layers.length;
|
|
745
881
|
const layerLabel = `${count} layer${count === 1 ? "" : "s"}`;
|
|
@@ -755,8 +891,8 @@ function stackSummary(stack, entries = []) {
|
|
|
755
891
|
if (stack.cycloneOwnerMatch) {
|
|
756
892
|
overrides.push(`cyclone-owner ${stack.cycloneOwnerMatch}`);
|
|
757
893
|
}
|
|
758
|
-
const { attention, issues } = stackBlockers(stack, entries);
|
|
759
|
-
return `${stack.owner}/${stack.repo} \xB7 ${layerLabel} \xB7 ${currentState} \xB7 auto-land ${stack.autoEnqueueWhenReady ? "on" : "off"}${overrides.map((label) => ` \xB7 ${label}`).join("")}${
|
|
894
|
+
const { attention, issues, busy, landGatePending } = stackBlockers(stack, entries);
|
|
895
|
+
return `${stack.owner}/${stack.repo} \xB7 ${layerLabel} \xB7 ${currentState} \xB7 auto-land ${stack.autoEnqueueWhenReady ? "on" : "off"}${overrides.map((label) => ` \xB7 ${label}`).join("")}${stackBlockersSummary2(attention, issues, busy, landGatePending)}`;
|
|
760
896
|
}
|
|
761
897
|
|
|
762
898
|
// src/tools/stack-list.ts
|
|
@@ -786,108 +922,65 @@ async function stackList(cfg) {
|
|
|
786
922
|
}
|
|
787
923
|
}
|
|
788
924
|
|
|
789
|
-
// src/tools/stack-wait.ts
|
|
790
|
-
import {
|
|
791
|
-
loadConfig as loadConfig6,
|
|
792
|
-
stackBlockersSummary as stackBlockersSummary2,
|
|
793
|
-
pollStackWatch,
|
|
794
|
-
StackWatchError,
|
|
795
|
-
StackWatchTimeoutError
|
|
796
|
-
} from "mergestorm/client";
|
|
797
|
-
import { z as z2 } from "zod";
|
|
798
|
-
var stackWaitSchema = {
|
|
799
|
-
stack_id: z2.string().trim().min(1),
|
|
800
|
-
timeout_s: z2.number().finite().min(0).max(45).default(45),
|
|
801
|
-
enrolled_head_sha: z2.string().nullable().optional(),
|
|
802
|
-
after_finished_at: z2.string().nullable().optional(),
|
|
803
|
-
bounce_id: z2.string().nullable().optional()
|
|
804
|
-
};
|
|
805
|
-
function stackWaitSummary(envelope) {
|
|
806
|
-
const base = `Stack ${envelope.stackId} \xB7 ${envelope.status}${stackBlockersSummary2(envelope.blocker && envelope.prNumber != null ? { prNumber: envelope.prNumber, blocker: envelope.blocker } : null, envelope.issues)}${envelope.assessment === "unavailable" ? " \xB7 assessment unavailable" : ""}`;
|
|
807
|
-
if (envelope.status !== "waiting" && envelope.status !== "in_progress") return base;
|
|
808
|
-
const selectors = [
|
|
809
|
-
`stack_id ${JSON.stringify(envelope.cursor.stackId)}`,
|
|
810
|
-
`enrolled_head_sha ${JSON.stringify(envelope.cursor.enrolledHeadSha)}`,
|
|
811
|
-
...envelope.cursor.afterFinishedAt !== void 0 ? [`after_finished_at ${JSON.stringify(envelope.cursor.afterFinishedAt)}`] : [],
|
|
812
|
-
...envelope.cursor.bounceId !== void 0 ? [`bounce_id ${JSON.stringify(envelope.cursor.bounceId)}`] : []
|
|
813
|
-
];
|
|
814
|
-
return `${base} \xB7 watch not finished: call stack_wait again with timeout_s 45 and the same cursor: ${selectors.join(", ")}`;
|
|
815
|
-
}
|
|
816
|
-
async function stackWait(input, cfg, deps = {}) {
|
|
817
|
-
const args = z2.object(stackWaitSchema).parse(input);
|
|
818
|
-
const cursor = args.enrolled_head_sha !== void 0 || args.after_finished_at !== void 0 || args.bounce_id !== void 0 ? {
|
|
819
|
-
stackId: args.stack_id,
|
|
820
|
-
enrolledHeadSha: args.enrolled_head_sha ?? null,
|
|
821
|
-
...args.after_finished_at !== void 0 ? { afterFinishedAt: args.after_finished_at } : {},
|
|
822
|
-
...args.bounce_id !== void 0 ? { bounceId: args.bounce_id } : {}
|
|
823
|
-
} : void 0;
|
|
824
|
-
let envelope;
|
|
825
|
-
try {
|
|
826
|
-
envelope = await (deps.pollStackWatch ?? pollStackWatch)(cfg ?? await loadConfig6(), args.stack_id, {
|
|
827
|
-
timeoutMs: args.timeout_s * 1e3,
|
|
828
|
-
cursor,
|
|
829
|
-
signal: deps.signal
|
|
830
|
-
});
|
|
831
|
-
} catch (err) {
|
|
832
|
-
if (err instanceof StackWatchError) {
|
|
833
|
-
return {
|
|
834
|
-
summary: stackWaitSummary(err.lastEnvelope),
|
|
835
|
-
data: {
|
|
836
|
-
...err.lastEnvelope,
|
|
837
|
-
...err.retryAfterSeconds !== void 0 ? { retry_after_seconds: err.retryAfterSeconds } : {}
|
|
838
|
-
},
|
|
839
|
-
isError: true
|
|
840
|
-
};
|
|
841
|
-
}
|
|
842
|
-
if (!(err instanceof StackWatchTimeoutError)) throw err;
|
|
843
|
-
envelope = err.lastEnvelope;
|
|
844
|
-
}
|
|
845
|
-
return {
|
|
846
|
-
summary: stackWaitSummary(envelope),
|
|
847
|
-
data: envelope
|
|
848
|
-
};
|
|
849
|
-
}
|
|
850
|
-
|
|
851
925
|
// src/tools/stack-status.ts
|
|
852
926
|
import {
|
|
853
927
|
getEnrichedStack,
|
|
854
928
|
listMergeQueueEntries as listMergeQueueEntries2,
|
|
855
929
|
isCommandErrorCode as isCommandErrorCode5,
|
|
856
|
-
stackBlockers as stackBlockers2
|
|
930
|
+
stackBlockers as stackBlockers2,
|
|
931
|
+
stackTerminalReason,
|
|
932
|
+
stackWatchObligation as stackWatchObligation4
|
|
857
933
|
} from "mergestorm/client";
|
|
858
934
|
async function stackStatus(stackId, cfg) {
|
|
859
935
|
try {
|
|
860
936
|
const stack = await getEnrichedStack(stackId, cfg);
|
|
861
937
|
if (!stack) {
|
|
862
938
|
const message = `Stack not found or not owned by the current user: ${stackId}`;
|
|
939
|
+
const watch2 = stackWatchObligation4({ stackId, terminal: "not_found" });
|
|
863
940
|
return {
|
|
864
|
-
summary: message,
|
|
941
|
+
summary: withWatchText(watch2, message),
|
|
865
942
|
data: {
|
|
866
943
|
error: {
|
|
867
944
|
code: "stack_not_found",
|
|
868
945
|
message,
|
|
869
946
|
stack_id: stackId
|
|
870
|
-
}
|
|
947
|
+
},
|
|
948
|
+
watch: watch2
|
|
871
949
|
},
|
|
872
950
|
isError: true
|
|
873
951
|
};
|
|
874
952
|
}
|
|
875
953
|
const entries = await listMergeQueueEntries2(cfg, { stackId: stack.id });
|
|
876
|
-
const { attention, issues, currentCandidate } = stackBlockers2(stack, entries);
|
|
954
|
+
const { attention, issues, currentCandidate, busy, agents, repair, landGatePending } = stackBlockers2(stack, entries);
|
|
955
|
+
const watch = stackWatchObligation4({
|
|
956
|
+
stackId: stack.id,
|
|
957
|
+
terminal: stackTerminalReason(stack),
|
|
958
|
+
cursor: { enrolledHeadSha: currentCandidate?.headSha ?? null },
|
|
959
|
+
attention: attention ? { prNumber: attention.prNumber, blocker: attention.blocker } : null,
|
|
960
|
+
freshCursor: true
|
|
961
|
+
});
|
|
877
962
|
return {
|
|
878
|
-
summary: stackSummary(stack, entries),
|
|
879
|
-
data: { stack, attention, issues, currentCandidate }
|
|
963
|
+
summary: withWatchText(watch, `${stackSummary(stack, entries)}${repairSummary(repair)}`),
|
|
964
|
+
data: { stack, attention, issues, currentCandidate, busy, agents, repair, landGatePending, watch }
|
|
880
965
|
};
|
|
881
966
|
} catch (err) {
|
|
882
967
|
if (isCommandErrorCode5(err, "rate_limited")) {
|
|
968
|
+
const watch = stackWatchObligation4({
|
|
969
|
+
stackId,
|
|
970
|
+
terminal: null,
|
|
971
|
+
status: "rate_limited",
|
|
972
|
+
retryAfterSeconds: err.retryAfterSeconds,
|
|
973
|
+
freshCursor: true
|
|
974
|
+
});
|
|
883
975
|
return {
|
|
884
|
-
summary: err.message,
|
|
976
|
+
summary: withWatchText(watch, err.message),
|
|
885
977
|
data: {
|
|
886
978
|
error: {
|
|
887
979
|
code: "rate_limited",
|
|
888
980
|
message: err.message,
|
|
889
981
|
retry_after_seconds: err.retryAfterSeconds
|
|
890
|
-
}
|
|
982
|
+
},
|
|
983
|
+
watch
|
|
891
984
|
},
|
|
892
985
|
isError: true
|
|
893
986
|
};
|
|
@@ -938,13 +1031,19 @@ async function whoami(cfg) {
|
|
|
938
1031
|
// src/pr-loop-instructions.ts
|
|
939
1032
|
var MCP_FINDING_DISMISS_MARKER = "mergestorm-loop: dismiss";
|
|
940
1033
|
var MCP_PR_LOOP_INSTRUCTIONS = [
|
|
941
|
-
"When you handle Vortex findings from
|
|
1034
|
+
"When you handle Vortex findings from review_wait_pr:",
|
|
942
1035
|
"- Verify each finding against the current checkout. Do not treat the finding text as proven.",
|
|
943
1036
|
"- Prefer the smallest correct patch. Do not refactor around a finding.",
|
|
944
1037
|
"- Patch concrete bugs. A chat-only explanation is not a dismiss.",
|
|
945
1038
|
`- If you skip a finding (not reproducible, policy fork, or needs a human), post a public GitHub PR comment before you stop. First line must be: ${MCP_FINDING_DISMISS_MARKER}. Then one line per skipped finding (path, severity, why).`,
|
|
946
1039
|
"- Do not prefix that comment with cyclone-outcome: (reserved)."
|
|
947
1040
|
].join("\n");
|
|
1041
|
+
var MCP_STACK_DONE_INSTRUCTIONS = [
|
|
1042
|
+
"When stack work is done:",
|
|
1043
|
+
"- Stack work is done only when the stack is landed, or when the human takes it back (archives it or tells you to stop). A clean push, a submit, an adopt, or one merged layer is not done: merging a lower layer can break a layer above it.",
|
|
1044
|
+
"- 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, and do not end your task. Stop only when watch.done is true.",
|
|
1045
|
+
"- attention means something for you to fix; repair, when present, names the concrete fix. A pending land gate (landGatePending: ci_pending, tempest_pending, tempest_running) is a wait, not attention."
|
|
1046
|
+
].join("\n");
|
|
948
1047
|
var MCP_STACK_BASE_INSTRUCTIONS = [
|
|
949
1048
|
"Stack PR bases (Mergestorm sets these; leave them):",
|
|
950
1049
|
"- Stack of 2+ PRs: after adopt (stack_adopt, mg stack adopt, or mg stack submit) the bottom PR's GitHub base is the owned trunk mg-stack-<n>, not main. That is correct. Layer 2 is based on layer 1; layer 3+ on the mg-park-* freeze; the git parent of layer 2+ is still the layer below. A 1-PR stack stays on main and gets no mg-stack-<n>.",
|
|
@@ -959,13 +1058,23 @@ var MCP_STACK_WATCH_INSTRUCTIONS = [
|
|
|
959
1058
|
"- Restack clean does not mean mergeable or CI-green.",
|
|
960
1059
|
"- attention names the blocked PR via prNumber/headSha; currentCandidate is the promote candidate. Attention is not a mutation or authorization to change policy.",
|
|
961
1060
|
"- verifyHeadSha is a queue verification SHA, not the live PR head.",
|
|
1061
|
+
"- Do not patch a bounced PR while Cyclone or Vortex is still working on it. stack_wait and stack_status enforce this: they return in_progress instead of attention, with busy[] naming the PR, blocker, and agent, and agents carrying that PR's vortexStatus, cycloneStatus, vortexReview, and busy flags. in_progress with a nonempty busy[] means wait. Recheck the live remote PR head right before pushing.",
|
|
1062
|
+
"- seam_pending alone, and a queued Vortex run older than 15 minutes, do not hold. A queued Vortex run holds for at most 15 minutes; reviewing and patching hold until the run finishes or its lease or check run is cleared. A hold can hide a blocker no agent clears, such as a Draft PR; if it looks stuck, refresh stack_status and tell the human.",
|
|
1063
|
+
"- stack.autoEnqueueSettle with action ready or promote means Auto land found Cyclone and Vortex idle on that PR's green head and started its settle clock. action mergeability is published earlier, on a head that may not be green or idle yet.",
|
|
962
1064
|
"- mg-park-* is a frozen GitHub base, not a repair target: never merge or rebase onto mg-park-*.",
|
|
963
1065
|
"- Use the mergestorm-bounce-watch skill for the fix-then-watch policy."
|
|
964
1066
|
].join("\n");
|
|
965
1067
|
|
|
966
1068
|
// src/server.ts
|
|
967
1069
|
var MCP_SERVER_NAME = "mergestorm";
|
|
968
|
-
var MCP_SERVER_VERSION = "0.1
|
|
1070
|
+
var MCP_SERVER_VERSION = "0.2.1";
|
|
1071
|
+
var MCP_SERVER_INSTRUCTIONS = [
|
|
1072
|
+
MCP_STACK_DONE_INSTRUCTIONS,
|
|
1073
|
+
MCP_PR_LOOP_INSTRUCTIONS,
|
|
1074
|
+
MCP_STACK_BASE_INSTRUCTIONS,
|
|
1075
|
+
MCP_STACK_WATCH_INSTRUCTIONS
|
|
1076
|
+
].join("\n\n");
|
|
1077
|
+
var STACK_WATCH_RESULT_NOTE = ' The result carries watch {done, until: "landed", reason, next, message}: while watch.done is false the stack is not landed and your task is not done; call stack_wait with watch.next.args.';
|
|
969
1078
|
function stripNulls(value) {
|
|
970
1079
|
if (Array.isArray(value)) {
|
|
971
1080
|
return value.map((item) => stripNulls(item));
|
|
@@ -997,20 +1106,20 @@ function createMergestormMcpServer() {
|
|
|
997
1106
|
const server2 = new McpServer(
|
|
998
1107
|
{
|
|
999
1108
|
name: MCP_SERVER_NAME,
|
|
1000
|
-
|
|
1109
|
+
title: "Mergestorm",
|
|
1110
|
+
version: MCP_SERVER_VERSION,
|
|
1111
|
+
websiteUrl: MCP_WEBSITE_URL,
|
|
1112
|
+
icons: MCP_SERVER_ICONS
|
|
1001
1113
|
},
|
|
1002
|
-
{ instructions:
|
|
1003
|
-
|
|
1004
|
-
${MCP_STACK_BASE_INSTRUCTIONS}
|
|
1005
|
-
|
|
1006
|
-
${MCP_STACK_WATCH_INSTRUCTIONS}` }
|
|
1114
|
+
{ instructions: MCP_SERVER_INSTRUCTIONS }
|
|
1007
1115
|
);
|
|
1008
1116
|
const addTool = server2.registerTool.bind(server2);
|
|
1009
1117
|
addTool(
|
|
1010
1118
|
"whoami",
|
|
1011
1119
|
{
|
|
1012
1120
|
title: "Who am I",
|
|
1013
|
-
description: "Current Mergestorm API key prefix, plan, and API base."
|
|
1121
|
+
description: "Current Mergestorm API key prefix, plan, and API base.",
|
|
1122
|
+
annotations: { readOnlyHint: true }
|
|
1014
1123
|
},
|
|
1015
1124
|
async () => {
|
|
1016
1125
|
try {
|
|
@@ -1024,7 +1133,8 @@ ${MCP_STACK_WATCH_INSTRUCTIONS}` }
|
|
|
1024
1133
|
"credits",
|
|
1025
1134
|
{
|
|
1026
1135
|
title: "Credits",
|
|
1027
|
-
description: "Current standard-credit usage and reset time."
|
|
1136
|
+
description: "Current standard-credit usage and reset time.",
|
|
1137
|
+
annotations: { readOnlyHint: true }
|
|
1028
1138
|
},
|
|
1029
1139
|
async () => {
|
|
1030
1140
|
try {
|
|
@@ -1041,7 +1151,8 @@ ${MCP_STACK_WATCH_INSTRUCTIONS}` }
|
|
|
1041
1151
|
description: "List recent Mergestorm review jobs as job envelopes.",
|
|
1042
1152
|
inputSchema: {
|
|
1043
1153
|
limit: z3.number().int().min(1).max(50).optional()
|
|
1044
|
-
}
|
|
1154
|
+
},
|
|
1155
|
+
annotations: { readOnlyHint: true }
|
|
1045
1156
|
},
|
|
1046
1157
|
async ({ limit }) => {
|
|
1047
1158
|
try {
|
|
@@ -1051,51 +1162,6 @@ ${MCP_STACK_WATCH_INSTRUCTIONS}` }
|
|
|
1051
1162
|
}
|
|
1052
1163
|
}
|
|
1053
1164
|
);
|
|
1054
|
-
addTool(
|
|
1055
|
-
"review_get",
|
|
1056
|
-
{
|
|
1057
|
-
title: "Get review",
|
|
1058
|
-
description: "Fetch one Mergestorm review job as a job envelope. On rate_limited, wait retry_after_seconds before trying again.",
|
|
1059
|
-
inputSchema: {
|
|
1060
|
-
job_id: z3.string().min(1)
|
|
1061
|
-
}
|
|
1062
|
-
},
|
|
1063
|
-
async ({ job_id }) => {
|
|
1064
|
-
try {
|
|
1065
|
-
return ok(await reviewGet(job_id));
|
|
1066
|
-
} catch (err) {
|
|
1067
|
-
return fail(err);
|
|
1068
|
-
}
|
|
1069
|
-
}
|
|
1070
|
-
);
|
|
1071
|
-
addTool(
|
|
1072
|
-
"review_get_pr",
|
|
1073
|
-
{
|
|
1074
|
-
title: "Get GitHub PR Vortex review",
|
|
1075
|
-
description: "Fetch the latest GitHub PR Vortex pass from the DB-only endpoint. Review identity is SHA + pass: the envelope carries pass (1 for the first review on a head; a re-review on the same head is the next pass). Pass after_sha to scope to a head, pass to read one exact attempt, or after_pass to read only a later attempt on that head. This tool is read-only. On rate_limited, wait retry_after_seconds before trying again. Verify each finding against the current code; prefer the smallest correct patch; a chat-only skip is not a dismiss (post a PR comment starting with mergestorm-loop: dismiss).",
|
|
1076
|
-
inputSchema: {
|
|
1077
|
-
owner: z3.string().min(1),
|
|
1078
|
-
repo: z3.string().min(1),
|
|
1079
|
-
pr_number: z3.number().int().min(1),
|
|
1080
|
-
after_sha: z3.string().optional(),
|
|
1081
|
-
pass: z3.number().int().min(1).optional(),
|
|
1082
|
-
after_pass: z3.number().int().min(1).optional()
|
|
1083
|
-
}
|
|
1084
|
-
},
|
|
1085
|
-
async ({ owner, repo, pr_number, after_sha, pass, after_pass }) => {
|
|
1086
|
-
try {
|
|
1087
|
-
return ok(
|
|
1088
|
-
await reviewGetPr(owner, repo, pr_number, void 0, {
|
|
1089
|
-
afterSha: after_sha,
|
|
1090
|
-
pass,
|
|
1091
|
-
afterPass: after_pass
|
|
1092
|
-
})
|
|
1093
|
-
);
|
|
1094
|
-
} catch (err) {
|
|
1095
|
-
return fail(err);
|
|
1096
|
-
}
|
|
1097
|
-
}
|
|
1098
|
-
);
|
|
1099
1165
|
addTool(
|
|
1100
1166
|
"review_submit",
|
|
1101
1167
|
{
|
|
@@ -1114,7 +1180,8 @@ ${MCP_STACK_WATCH_INSTRUCTIONS}` }
|
|
|
1114
1180
|
idempotency_key: z3.string().min(1).max(128).optional(),
|
|
1115
1181
|
wait: z3.boolean().optional(),
|
|
1116
1182
|
timeout_s: z3.number().positive().max(300).optional()
|
|
1117
|
-
}
|
|
1183
|
+
},
|
|
1184
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true }
|
|
1118
1185
|
},
|
|
1119
1186
|
async (args) => {
|
|
1120
1187
|
try {
|
|
@@ -1128,11 +1195,12 @@ ${MCP_STACK_WATCH_INSTRUCTIONS}` }
|
|
|
1128
1195
|
"review_wait",
|
|
1129
1196
|
{
|
|
1130
1197
|
title: "Wait for review",
|
|
1131
|
-
description: "
|
|
1198
|
+
description: "Read one local Mergestorm review job by the job_id that review_submit returned, as a job envelope. timeout_s: 0 reads it once without polling; otherwise it polls for one 45s slice by default (max 300s). On timeout, returns in_progress; call again with the same job_id until done. Do not pass 300; hosts drop long MCP calls. On rate_limited, wait retry_after_seconds before trying again.",
|
|
1132
1199
|
inputSchema: {
|
|
1133
1200
|
job_id: z3.string().min(1),
|
|
1134
|
-
timeout_s: z3.number().
|
|
1135
|
-
}
|
|
1201
|
+
timeout_s: z3.number().min(0).max(300).optional()
|
|
1202
|
+
},
|
|
1203
|
+
annotations: { readOnlyHint: true }
|
|
1136
1204
|
},
|
|
1137
1205
|
async ({ job_id, timeout_s }) => {
|
|
1138
1206
|
try {
|
|
@@ -1146,7 +1214,7 @@ ${MCP_STACK_WATCH_INSTRUCTIONS}` }
|
|
|
1146
1214
|
"review_wait_pr",
|
|
1147
1215
|
{
|
|
1148
1216
|
title: "Wait for GitHub PR Vortex review",
|
|
1149
|
-
description: "
|
|
1217
|
+
description: "Read the Vortex pass on a GitHub PR from the DB-only endpoint; review identity is head SHA + pass. timeout_s: 0 reads it once without polling; otherwise it polls for one 45s slice by default (max 300) and returns in_progress on timeout so you can call again. Pass after_sha for the head you pushed and pass to read one exact attempt; when you already hold a resting envelope for that head, also pass after_pass set to its pass and keep it unchanged across retries; never replace it with the pass of an in-progress envelope, and omit after_pass for a head you hold no envelope for. With timeout_s: 0, a pass that does not exist yet returns not_found, so after a push use a positive timeout. Do not pass 300; hosts drop long MCP calls. On rate_limited, wait retry_after_seconds before trying again. After a resting pass, verify each finding; prefer the smallest correct patch; if you skip, post a PR comment starting with mergestorm-loop: dismiss.",
|
|
1150
1218
|
inputSchema: {
|
|
1151
1219
|
owner: z3.string().min(1),
|
|
1152
1220
|
repo: z3.string().min(1),
|
|
@@ -1154,8 +1222,9 @@ ${MCP_STACK_WATCH_INSTRUCTIONS}` }
|
|
|
1154
1222
|
after_sha: z3.string().optional(),
|
|
1155
1223
|
pass: z3.number().int().min(1).optional(),
|
|
1156
1224
|
after_pass: z3.number().int().min(1).optional(),
|
|
1157
|
-
timeout_s: z3.number().
|
|
1158
|
-
}
|
|
1225
|
+
timeout_s: z3.number().min(0).max(300).optional()
|
|
1226
|
+
},
|
|
1227
|
+
annotations: { readOnlyHint: true }
|
|
1159
1228
|
},
|
|
1160
1229
|
async ({ owner, repo, pr_number, after_sha, pass, after_pass, timeout_s }) => {
|
|
1161
1230
|
try {
|
|
@@ -1179,8 +1248,9 @@ ${MCP_STACK_WATCH_INSTRUCTIONS}` }
|
|
|
1179
1248
|
"stack_adopt",
|
|
1180
1249
|
{
|
|
1181
1250
|
title: "Adopt PR stack",
|
|
1182
|
-
description: "Adopt an open GitHub PR chain into a Mergestorm stack. auto_land is boolean; auto_review and auto_patch accept true, false, or null, where null clears the override. Omitted auto_land seeds from auto_land_default; omitted auto_review and auto_patch overrides are not seeded. Never changes account settings. Adoption requires a Cyclone GitHub App install; without it the API returns cyclone_not_connected \u2014 do not retry, tell the human. Read stack_status with result.stack.id afterward to verify policy and Cyclone ownership. With 2+ PRs, adopt moves the bottom PR's GitHub base to mg-stack-<n> and parks PR3+ on an mg-park-* freeze; a 1-PR stack stays on main. Leave those bases. Never retarget the bottom back to main.",
|
|
1183
|
-
inputSchema: stackAdoptSchema
|
|
1251
|
+
description: "Adopt an open GitHub PR chain into a Mergestorm stack. auto_land is boolean; auto_review and auto_patch accept true, false, or null, where null clears the override. Omitted auto_land seeds from auto_land_default; omitted auto_review and auto_patch overrides are not seeded. Never changes account settings. Adoption requires a Cyclone GitHub App install; without it the API returns cyclone_not_connected \u2014 do not retry, tell the human. Read stack_status with result.stack.id afterward to verify policy and Cyclone ownership. With 2+ PRs, adopt moves the bottom PR's GitHub base to mg-stack-<n> and parks PR3+ on an mg-park-* freeze; a 1-PR stack stays on main. Leave those bases. Never retarget the bottom back to main." + STACK_WATCH_RESULT_NOTE,
|
|
1252
|
+
inputSchema: stackAdoptSchema,
|
|
1253
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false }
|
|
1184
1254
|
},
|
|
1185
1255
|
async (args) => {
|
|
1186
1256
|
try {
|
|
@@ -1194,7 +1264,8 @@ ${MCP_STACK_WATCH_INSTRUCTIONS}` }
|
|
|
1194
1264
|
"stack_list",
|
|
1195
1265
|
{
|
|
1196
1266
|
title: "List stacks",
|
|
1197
|
-
description: "List the current user's Mergestorm stacks.
|
|
1267
|
+
description: "List the current user's Mergestorm stacks.",
|
|
1268
|
+
annotations: { readOnlyHint: true }
|
|
1198
1269
|
},
|
|
1199
1270
|
async () => {
|
|
1200
1271
|
try {
|
|
@@ -1208,13 +1279,14 @@ ${MCP_STACK_WATCH_INSTRUCTIONS}` }
|
|
|
1208
1279
|
"stack_set",
|
|
1209
1280
|
{
|
|
1210
1281
|
title: "Set stack policy",
|
|
1211
|
-
description: "Set per-stack automation on one owned Mergestorm stack: auto_land arms or disarms Auto land; auto_review and auto_patch pin Vortex auto-review or Cyclone auto-patch for this stack only (true or false), and null clears the pin so the stack follows the account setting again. Pass at least one key. Never changes account settings.",
|
|
1282
|
+
description: "Set per-stack automation on one owned Mergestorm stack: auto_land arms or disarms Auto land; auto_review and auto_patch pin Vortex auto-review or Cyclone auto-patch for this stack only (true or false), and null clears the pin so the stack follows the account setting again. Pass at least one key. Never changes account settings." + STACK_WATCH_RESULT_NOTE,
|
|
1212
1283
|
inputSchema: {
|
|
1213
1284
|
stack_id: z3.string().min(1),
|
|
1214
1285
|
auto_land: z3.boolean().optional(),
|
|
1215
1286
|
auto_review: z3.boolean().nullable().optional(),
|
|
1216
1287
|
auto_patch: z3.boolean().nullable().optional()
|
|
1217
|
-
}
|
|
1288
|
+
},
|
|
1289
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true }
|
|
1218
1290
|
},
|
|
1219
1291
|
async ({ stack_id, auto_land, auto_review, auto_patch }) => {
|
|
1220
1292
|
try {
|
|
@@ -1234,10 +1306,11 @@ ${MCP_STACK_WATCH_INSTRUCTIONS}` }
|
|
|
1234
1306
|
"stack_status",
|
|
1235
1307
|
{
|
|
1236
1308
|
title: "Get stack status",
|
|
1237
|
-
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.
|
|
1309
|
+
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." + STACK_WATCH_RESULT_NOTE,
|
|
1238
1310
|
inputSchema: {
|
|
1239
1311
|
stack_id: z3.string().min(1)
|
|
1240
|
-
}
|
|
1312
|
+
},
|
|
1313
|
+
annotations: { readOnlyHint: true }
|
|
1241
1314
|
},
|
|
1242
1315
|
async ({ stack_id }) => {
|
|
1243
1316
|
try {
|
|
@@ -1251,8 +1324,9 @@ ${MCP_STACK_WATCH_INSTRUCTIONS}` }
|
|
|
1251
1324
|
"stack_wait",
|
|
1252
1325
|
{
|
|
1253
1326
|
title: "Wait for stack attention",
|
|
1254
|
-
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.
|
|
1255
|
-
inputSchema: stackWaitSchema
|
|
1327
|
+
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. Attention never calls for changing a PR's GitHub base. repair names the concrete fix for attention (restack_conflict and merge_conflict carry liveParent, never mg-park-*). A pending land gate (landGatePending) returns in_progress, not attention." + STACK_WATCH_RESULT_NOTE,
|
|
1328
|
+
inputSchema: stackWaitSchema,
|
|
1329
|
+
annotations: { readOnlyHint: true }
|
|
1256
1330
|
},
|
|
1257
1331
|
async (args, extra) => {
|
|
1258
1332
|
try {
|
|
@@ -1267,10 +1341,11 @@ ${MCP_STACK_WATCH_INSTRUCTIONS}` }
|
|
|
1267
1341
|
"queue_status",
|
|
1268
1342
|
{
|
|
1269
1343
|
title: "Get merge queue status",
|
|
1270
|
-
description: "List live merge queue entries (position >= 1) plus the newest bounced entries (position 0) with bounceDetail for the current user, optionally filtered by stack.
|
|
1344
|
+
description: "List live merge queue entries (position >= 1) plus the newest bounced entries (position 0) with bounceDetail for the current user, optionally filtered by stack.",
|
|
1271
1345
|
inputSchema: {
|
|
1272
1346
|
stack_id: z3.string().min(1).optional()
|
|
1273
|
-
}
|
|
1347
|
+
},
|
|
1348
|
+
annotations: { readOnlyHint: true }
|
|
1274
1349
|
},
|
|
1275
1350
|
async ({ stack_id }) => {
|
|
1276
1351
|
try {
|
|
@@ -1284,7 +1359,8 @@ ${MCP_STACK_WATCH_INSTRUCTIONS}` }
|
|
|
1284
1359
|
"settings_get",
|
|
1285
1360
|
{
|
|
1286
1361
|
title: "Get settings",
|
|
1287
|
-
description: "Read the Bearer /api/v1/settings toggles, including auto_patch_enabled, auto_land_settle_seconds (how long Auto land waits before queueing), and cyclone_connected.
|
|
1362
|
+
description: "Read the Bearer /api/v1/settings toggles, including auto_patch_enabled, auto_land_settle_seconds (how long Auto land waits before queueing), and cyclone_connected.",
|
|
1363
|
+
annotations: { readOnlyHint: true }
|
|
1288
1364
|
},
|
|
1289
1365
|
async () => {
|
|
1290
1366
|
try {
|
|
@@ -1309,7 +1385,8 @@ ${MCP_STACK_WATCH_INSTRUCTIONS}` }
|
|
|
1309
1385
|
// Preserve these args so the handler rejects them even alongside a writable key.
|
|
1310
1386
|
cyclone_connected: z3.unknown().optional(),
|
|
1311
1387
|
github_connected: z3.unknown().optional()
|
|
1312
|
-
}
|
|
1388
|
+
},
|
|
1389
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true }
|
|
1313
1390
|
},
|
|
1314
1391
|
async (args) => {
|
|
1315
1392
|
try {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mergestorm-mcp",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Mergestorm MCP server — identity, review, and read-only stack tools over stdio",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
},
|
|
30
30
|
"dependencies": {
|
|
31
31
|
"@modelcontextprotocol/sdk": "^1.30.0",
|
|
32
|
-
"mergestorm": "0.3.
|
|
32
|
+
"mergestorm": "0.3.26",
|
|
33
33
|
"zod": "^3.24.2"
|
|
34
34
|
},
|
|
35
35
|
"devDependencies": {
|