mergestorm-mcp 0.2.20 → 0.2.22
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 +421 -129
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# mergestorm-mcp
|
|
2
2
|
|
|
3
|
-
Stdio MCP server for Mergestorm. Tools: `whoami`, `credits`, `review_list`, `review_submit`, `review_wait`, `review_wait_pr`, `review_dismiss`, `stack_adopt`, `stack_list`, `stack_set`, `stack_status`, `stack_wait`, `queue_status`, `settings_get`, `settings_set`.
|
|
3
|
+
Stdio MCP server for Mergestorm. Tools: `whoami`, `credits`, `review_list`, `review_submit`, `review_wait`, `review_wait_pr`, `review_dismiss`, `issue_create`, `stack_adopt`, `stack_list`, `stack_publish`, `stack_publish_status`, `stack_set`, `stack_status`, `stack_wait`, `queue_status`, `settings_get`, `settings_set`, `repo_settings_get`, `repo_settings_set`.
|
|
4
4
|
|
|
5
5
|
Every tool declares MCP `annotations` for read-only access, destructive changes, idempotence, and external systems. Reads are read-only; submission, adoption, dismissal, and policy changes are writes. Tools that can change existing policy, topology, or review gates are marked potentially destructive even when some inputs only preview or read. Annotations describe effects; the client still controls approval and authorization.
|
|
6
6
|
|
|
@@ -16,10 +16,14 @@ Every tool declares MCP `annotations` for read-only access, destructive changes,
|
|
|
16
16
|
- **Gates.** `gate.seam.cleared` is true only when every finding of that integration review is dismissed and it is still the member's current seam verdict. `gate.vortex.cleared` is true once every Vortex Blocker (error finding) at the live head is dismissed (every Core review merged into that head) and no Vortex review of the head is running or incomplete; with a warning still open the head reads `comment`, which Auto land accepts; otherwise `gate.vortex.reason` and `message` say what still holds it: that head's Vortex verdict becomes approve for Auto land, the stack watch, the dashboard and Cyclone. A partial dismissal clears nothing. CI and Auto land policy are unchanged (`gate.other_gates: "unchanged"`). For a stack PR the result carries `watch` with the next `stack_wait` call.
|
|
17
17
|
- **No resurfacing.** Vortex does not raise a dismissed finding again on the same diff: the same head, or a restack whose diff is unchanged.
|
|
18
18
|
|
|
19
|
-
The
|
|
19
|
+
`issue_create({ title, body?, labels?, owner?, repo?, cwd? })` opens a GitHub issue through Mergestorm with no GitHub credentials: the Surge App creates it and names the caller in the body. It defaults to the origin repository of `cwd`. Labels must already exist on the repository. The same title again returns the open issue it already made (`created: false`), so a retry does not duplicate it. Put `Fixes #<number>` in the commit or pull request body.
|
|
20
|
+
|
|
21
|
+
The stack tools expose reads, one scoped policy write, and publication of your own branch:
|
|
20
22
|
|
|
21
23
|
- `stack_adopt({ owner, repo, pr_number, auto_land?, auto_review?, auto_patch?, auto_resolve_conflicts?, auto_fix_ci? })` adopts an open PR chain with optional per-stack policy. Omitted policy does not force Cyclone off. `auto_review`, `auto_patch`, `auto_resolve_conflicts`, and `auto_fix_ci` accept `true`, `false`, or `null`; `null` clears the override. Read `stack_status` with the returned `result.stack.id` to verify policy and Cyclone ownership. Adoption needs the Mergestorm Surge GitHub App on the repository (Cyclone is the auto-patch App). A refusal keeps the code `cyclone_not_connected` or `cyclone_not_installed`; its message, and `error.reason` starting with `surge_` when Surge is the one to install, say which App is missing.
|
|
22
24
|
- `stack_list` returns the current API key owner's registered stacks.
|
|
25
|
+
- `stack_publish({ cwd?, owner?, repo?, branch?, base?, title?, body?, replace_head?, auto_land? })` publishes a local branch with no GitHub credentials. It reads the branch's commits from the repository at `cwd` and sends them to Mergestorm, which writes them and the branch on GitHub as the Surge App, opens a ready pull request, and registers the stack with Auto patch pinned off, Auto land on for a new stack, and Auto review left to the account. It never runs `gh`, `git push` or `git fetch`. Call it again for every later commit to that branch, and after a lost response: it resumes and does not duplicate the branch, commits, pull request or stack. It refuses with `head_changed` when the branch on GitHub holds work the local branch does not; `replace_head` names the exact GitHub head to replace. Limits: 20 commits, 500 changed paths, 2 MiB a file, 8 MiB in total; no submodules or merge commits. The result carries the verified head commit and tree, the pull request, the stack and its `watch`.
|
|
26
|
+
- `stack_publish_status({ cwd?, owner?, repo?, branch? })` reads how far a publication got (`absent`, `pushed`, `opened`, `registered`) with the head on GitHub, the pull request and the stack's Auto patch and Auto land state. It changes nothing.
|
|
23
27
|
- `stack_set({ stack_id, auto_land?, auto_review?, auto_patch?, auto_resolve_conflicts?, auto_fix_ci? })` sets per-stack policy on one owned stack. `auto_land` flips Auto land. `auto_review`, `auto_patch`, `auto_resolve_conflicts`, and `auto_fix_ci` pin Vortex auto-review, Cyclone auto-patch, Auto-resolve merge conflicts, or Auto-fix failing CI 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.
|
|
24
28
|
- `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.
|
|
25
29
|
- `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 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; act when a snapshot after the agents finish returns `attention`, re-reading the blocker then. For the three stuck-seam blockers, `repair` has 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. `repair` is included when another concrete repair hint is available. Blockers the agents can change keep `blocker` and `actAfter` null. If a hold looks stuck, refresh `stack_status` and tell the human.
|
package/dist/index.js
CHANGED
|
@@ -5,8 +5,8 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
|
|
|
5
5
|
|
|
6
6
|
// src/server.ts
|
|
7
7
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
8
|
-
import { z as
|
|
9
|
-
import { CommandError as
|
|
8
|
+
import { z as z5 } from "zod";
|
|
9
|
+
import { CommandError as CommandError14, BEARER_SETTINGS as BEARER_SETTINGS2 } from "mergestorm/client";
|
|
10
10
|
|
|
11
11
|
// src/brand-icons.ts
|
|
12
12
|
var MARK_RECTS = [
|
|
@@ -75,10 +75,59 @@ async function credits(cfg) {
|
|
|
75
75
|
};
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
+
// src/tools/issue-create.ts
|
|
79
|
+
import { CommandError as CommandError2, openStackIssue, parseGithubOriginRepo } from "mergestorm/client";
|
|
80
|
+
import { z } from "zod";
|
|
81
|
+
var issueCreateSchema = {
|
|
82
|
+
title: z.string().trim().min(1).max(256),
|
|
83
|
+
body: z.string().max(6e4).optional(),
|
|
84
|
+
labels: z.array(z.string().trim().min(1).max(50)).max(10).optional(),
|
|
85
|
+
owner: z.string().trim().regex(/^[A-Za-z0-9_.-]{1,100}$/).optional(),
|
|
86
|
+
repo: z.string().trim().regex(/^[A-Za-z0-9_.-]{1,100}$/).optional(),
|
|
87
|
+
cwd: z.string().trim().min(1).optional()
|
|
88
|
+
};
|
|
89
|
+
var inputSchema = z.object(issueCreateSchema);
|
|
90
|
+
async function issueCreate(input, cfg, deps = {}) {
|
|
91
|
+
const parsed = inputSchema.safeParse(input);
|
|
92
|
+
if (!parsed.success || Boolean(parsed.data.owner) !== Boolean(parsed.data.repo)) {
|
|
93
|
+
const message = "issue_create needs a one-line title of at most 256 characters, and owner and repo together";
|
|
94
|
+
return { summary: message, data: { error: { code: "invalid_input", message } }, isError: true };
|
|
95
|
+
}
|
|
96
|
+
const { title, body, labels, owner, repo, cwd } = parsed.data;
|
|
97
|
+
try {
|
|
98
|
+
const target2 = owner && repo ? { owner, repo } : (deps.parseGithubOriginRepo ?? parseGithubOriginRepo)(cwd);
|
|
99
|
+
if (!target2) {
|
|
100
|
+
const message = "Could not read a GitHub owner/repo from origin. Pass owner and repo.";
|
|
101
|
+
return { summary: message, data: { error: { code: "invalid_input", message } }, isError: true };
|
|
102
|
+
}
|
|
103
|
+
const issue = await (deps.openStackIssue ?? openStackIssue)(
|
|
104
|
+
{ owner: target2.owner, repo: target2.repo, title, body: body ?? "", labels: labels ?? [] },
|
|
105
|
+
cfg
|
|
106
|
+
);
|
|
107
|
+
return {
|
|
108
|
+
summary: `${issue.created ? "Opened" : "Already open:"} ${target2.owner}/${target2.repo}#${issue.number} ${issue.url}. Put "Fixes #${issue.number}" in the commit or pull request body.`,
|
|
109
|
+
data: { owner: target2.owner, repo: target2.repo, ...issue }
|
|
110
|
+
};
|
|
111
|
+
} catch (err) {
|
|
112
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
113
|
+
return {
|
|
114
|
+
summary: message,
|
|
115
|
+
data: {
|
|
116
|
+
error: {
|
|
117
|
+
code: err instanceof CommandError2 ? err.code ?? "issue_create_failed" : "issue_create_failed",
|
|
118
|
+
message,
|
|
119
|
+
...err instanceof CommandError2 && err.retryAfterSeconds !== void 0 ? { retry_after_seconds: err.retryAfterSeconds } : {}
|
|
120
|
+
}
|
|
121
|
+
},
|
|
122
|
+
isError: true
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
78
127
|
// src/tools/queue-status.ts
|
|
79
128
|
import {
|
|
80
129
|
apiFetch,
|
|
81
|
-
CommandError as
|
|
130
|
+
CommandError as CommandError3,
|
|
82
131
|
isCommandErrorCode,
|
|
83
132
|
loadConfig as loadConfig2,
|
|
84
133
|
rateLimitedMessage,
|
|
@@ -106,12 +155,12 @@ async function queueStatus(stackId, cfg) {
|
|
|
106
155
|
id ? `/api/v1/stacks/queue?stackId=${encodeURIComponent(id)}` : "/api/v1/stacks/queue"
|
|
107
156
|
);
|
|
108
157
|
if (status === 404) {
|
|
109
|
-
throw new
|
|
158
|
+
throw new CommandError3(
|
|
110
159
|
"Merge queue route returned 404. Deploy the API."
|
|
111
160
|
);
|
|
112
161
|
}
|
|
113
162
|
if (status === 429) {
|
|
114
|
-
throw new
|
|
163
|
+
throw new CommandError3(
|
|
115
164
|
rateLimitedMessage(retryAfterSeconds),
|
|
116
165
|
REVIEW_EXIT.rate_limited,
|
|
117
166
|
"rate_limited",
|
|
@@ -119,13 +168,13 @@ async function queueStatus(stackId, cfg) {
|
|
|
119
168
|
);
|
|
120
169
|
}
|
|
121
170
|
if (status !== 200) {
|
|
122
|
-
throw new
|
|
171
|
+
throw new CommandError3(
|
|
123
172
|
`Failed to get queue status (HTTP ${status}): ${JSON.stringify(body)}`
|
|
124
173
|
);
|
|
125
174
|
}
|
|
126
175
|
const entries = body.entries;
|
|
127
176
|
if (!Array.isArray(entries)) {
|
|
128
|
-
throw new
|
|
177
|
+
throw new CommandError3(
|
|
129
178
|
`Failed to get queue status (HTTP 200): ${JSON.stringify(body)}`
|
|
130
179
|
);
|
|
131
180
|
}
|
|
@@ -190,13 +239,13 @@ import {
|
|
|
190
239
|
stackWatchBackgroundCommand,
|
|
191
240
|
stackWatchObligation
|
|
192
241
|
} from "mergestorm/client";
|
|
193
|
-
import { z } from "zod";
|
|
242
|
+
import { z as z2 } from "zod";
|
|
194
243
|
var stackWaitSchema = {
|
|
195
|
-
stack_id:
|
|
196
|
-
timeout_s:
|
|
197
|
-
enrolled_head_sha:
|
|
198
|
-
after_finished_at:
|
|
199
|
-
bounce_id:
|
|
244
|
+
stack_id: z2.string().trim().min(1),
|
|
245
|
+
timeout_s: z2.number().finite().min(0).max(45).default(45),
|
|
246
|
+
enrolled_head_sha: z2.string().nullable().optional(),
|
|
247
|
+
after_finished_at: z2.string().nullable().optional(),
|
|
248
|
+
bounce_id: z2.string().nullable().optional()
|
|
200
249
|
};
|
|
201
250
|
function repairSummary(repair) {
|
|
202
251
|
if (!repair) return "";
|
|
@@ -233,7 +282,7 @@ function stackWaitStatusLine(envelope) {
|
|
|
233
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(", ")}`;
|
|
234
283
|
}
|
|
235
284
|
async function stackWait(input, cfg, deps = {}) {
|
|
236
|
-
const args =
|
|
285
|
+
const args = z2.object(stackWaitSchema).parse(input);
|
|
237
286
|
const cursor = args.enrolled_head_sha !== void 0 || args.after_finished_at !== void 0 || args.bounce_id !== void 0 ? {
|
|
238
287
|
stackId: args.stack_id,
|
|
239
288
|
enrolledHeadSha: args.enrolled_head_sha ?? null,
|
|
@@ -346,7 +395,7 @@ import { createHash } from "node:crypto";
|
|
|
346
395
|
import { realpath } from "node:fs/promises";
|
|
347
396
|
import { relative, resolve, sep } from "node:path";
|
|
348
397
|
import {
|
|
349
|
-
CommandError as
|
|
398
|
+
CommandError as CommandError4,
|
|
350
399
|
REVIEW_THREAD_SLUG_RE,
|
|
351
400
|
ReviewPollTimeoutError,
|
|
352
401
|
collectReviewInput,
|
|
@@ -401,7 +450,7 @@ function assertPinnableSpecialists(specialists) {
|
|
|
401
450
|
(id) => !PINNABLE_SPECIALISTS.includes(id)
|
|
402
451
|
);
|
|
403
452
|
if (unknown.length === 0) return;
|
|
404
|
-
throw new
|
|
453
|
+
throw new CommandError4(
|
|
405
454
|
`Unknown specialist: ${unknown.join(", ")}. Valid lanes: ${PINNABLE_SPECIALISTS.join(", ")}. governance and seam cannot be requested.`,
|
|
406
455
|
2,
|
|
407
456
|
"usage"
|
|
@@ -413,7 +462,7 @@ function mcpSandboxRoot() {
|
|
|
413
462
|
function waitTimeoutMs(timeout_s, defaultSeconds = MCP_PR_WAIT_DEFAULT_S) {
|
|
414
463
|
const seconds = timeout_s ?? defaultSeconds;
|
|
415
464
|
if (!Number.isFinite(seconds) || seconds <= 0) {
|
|
416
|
-
throw new
|
|
465
|
+
throw new CommandError4("timeout_s must be a positive number of seconds", 2, "usage");
|
|
417
466
|
}
|
|
418
467
|
return Math.max(1, Math.round(Math.min(seconds, MCP_WAIT_CEILING_S) * 1e3));
|
|
419
468
|
}
|
|
@@ -446,11 +495,11 @@ async function resolveInput(input) {
|
|
|
446
495
|
]);
|
|
447
496
|
const relToSandbox = relative(sandboxReal, cwdReal);
|
|
448
497
|
if (relToSandbox === ".." || relToSandbox.startsWith(`..${sep}`)) {
|
|
449
|
-
throw new
|
|
498
|
+
throw new CommandError4("cwd must be inside the configured sandbox root", 2, "usage");
|
|
450
499
|
}
|
|
451
500
|
} catch (err) {
|
|
452
|
-
if (err instanceof
|
|
453
|
-
throw new
|
|
501
|
+
if (err instanceof CommandError4) throw err;
|
|
502
|
+
throw new CommandError4("cwd must be inside the configured sandbox root", 2, "usage");
|
|
454
503
|
}
|
|
455
504
|
const base = input.base?.trim() || discoverTrunk(cwd);
|
|
456
505
|
return collectReviewInput(base, head, cwd, sandboxRoot);
|
|
@@ -461,10 +510,10 @@ async function reviewSubmit(input, cfg, opts = {}) {
|
|
|
461
510
|
const timeoutMs = waitTimeoutMs(input.timeout_s);
|
|
462
511
|
assertPinnableSpecialists(input.specialists);
|
|
463
512
|
if (input.thread && !REVIEW_THREAD_SLUG_RE.test(input.thread)) {
|
|
464
|
-
throw new
|
|
513
|
+
throw new CommandError4("thread must be 1\u2013120 chars [A-Za-z0-9._/-]", 2, "usage");
|
|
465
514
|
}
|
|
466
515
|
if (input.context === "-") {
|
|
467
|
-
throw new
|
|
516
|
+
throw new CommandError4(
|
|
468
517
|
'context "-" (stdin) is not available over MCP; pass the text directly',
|
|
469
518
|
2,
|
|
470
519
|
"usage"
|
|
@@ -514,7 +563,7 @@ async function reviewSubmit(input, cfg, opts = {}) {
|
|
|
514
563
|
};
|
|
515
564
|
}
|
|
516
565
|
if (!jobId) {
|
|
517
|
-
throw new
|
|
566
|
+
throw new CommandError4("Review API returned no job_id");
|
|
518
567
|
}
|
|
519
568
|
if (!wait) {
|
|
520
569
|
return payloadFromRow(submitted.row, { jobId, status: submitted.row.status ?? "queued" });
|
|
@@ -550,7 +599,7 @@ import {
|
|
|
550
599
|
|
|
551
600
|
// src/tools/review-get-pr.ts
|
|
552
601
|
import {
|
|
553
|
-
CommandError as
|
|
602
|
+
CommandError as CommandError5,
|
|
554
603
|
getPrVortexReview,
|
|
555
604
|
isCommandErrorCode as isCommandErrorCode3
|
|
556
605
|
} from "mergestorm/client";
|
|
@@ -558,7 +607,7 @@ function prReviewTarget(owner, repo, prNumber) {
|
|
|
558
607
|
const cleanOwner = owner.trim();
|
|
559
608
|
const cleanRepo = repo.trim();
|
|
560
609
|
if (!cleanOwner || !cleanRepo || !Number.isInteger(prNumber) || prNumber <= 0) {
|
|
561
|
-
throw new
|
|
610
|
+
throw new CommandError5(
|
|
562
611
|
"owner, repo, and a positive pr_number are required",
|
|
563
612
|
2,
|
|
564
613
|
"usage"
|
|
@@ -596,27 +645,27 @@ function payloadFromPrRateLimit(err, owner, repo, prNumber) {
|
|
|
596
645
|
};
|
|
597
646
|
}
|
|
598
647
|
async function reviewGetPr(owner, repo, prNumber, cfg, opts = {}) {
|
|
599
|
-
const
|
|
648
|
+
const target2 = prReviewTarget(owner, repo, prNumber);
|
|
600
649
|
try {
|
|
601
650
|
const envelope = await getPrVortexReview(
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
651
|
+
target2.owner,
|
|
652
|
+
target2.repo,
|
|
653
|
+
target2.prNumber,
|
|
605
654
|
cfg,
|
|
606
655
|
{ afterSha: opts.afterSha, pass: opts.pass, afterPass: opts.afterPass }
|
|
607
656
|
);
|
|
608
657
|
return payloadFromPrReview(
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
658
|
+
target2.owner,
|
|
659
|
+
target2.repo,
|
|
660
|
+
target2.prNumber,
|
|
612
661
|
envelope
|
|
613
662
|
);
|
|
614
663
|
} catch (err) {
|
|
615
664
|
const rateLimited = payloadFromPrRateLimit(
|
|
616
665
|
err,
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
666
|
+
target2.owner,
|
|
667
|
+
target2.repo,
|
|
668
|
+
target2.prNumber
|
|
620
669
|
);
|
|
621
670
|
if (rateLimited) return rateLimited;
|
|
622
671
|
throw err;
|
|
@@ -625,10 +674,10 @@ async function reviewGetPr(owner, repo, prNumber, cfg, opts = {}) {
|
|
|
625
674
|
|
|
626
675
|
// src/tools/review-wait-pr.ts
|
|
627
676
|
async function reviewWaitPr(owner, repo, prNumber, afterSha, timeout_s, cfg, opts = {}) {
|
|
628
|
-
const
|
|
677
|
+
const target2 = prReviewTarget(owner, repo, prNumber);
|
|
629
678
|
const resolved = cfg ?? await loadConfig5();
|
|
630
679
|
if (timeout_s === 0) {
|
|
631
|
-
return reviewGetPr(
|
|
680
|
+
return reviewGetPr(target2.owner, target2.repo, target2.prNumber, resolved, {
|
|
632
681
|
afterSha,
|
|
633
682
|
pass: opts.pass,
|
|
634
683
|
afterPass: opts.afterPass
|
|
@@ -637,9 +686,9 @@ async function reviewWaitPr(owner, repo, prNumber, afterSha, timeout_s, cfg, opt
|
|
|
637
686
|
try {
|
|
638
687
|
const envelope = await pollPrVortexReview(
|
|
639
688
|
resolved,
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
689
|
+
target2.owner,
|
|
690
|
+
target2.repo,
|
|
691
|
+
target2.prNumber,
|
|
643
692
|
{
|
|
644
693
|
timeoutMs: waitTimeoutMs(timeout_s, MCP_PR_WAIT_DEFAULT_S),
|
|
645
694
|
afterSha,
|
|
@@ -649,9 +698,9 @@ async function reviewWaitPr(owner, repo, prNumber, afterSha, timeout_s, cfg, opt
|
|
|
649
698
|
}
|
|
650
699
|
);
|
|
651
700
|
return payloadFromPrReview(
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
701
|
+
target2.owner,
|
|
702
|
+
target2.repo,
|
|
703
|
+
target2.prNumber,
|
|
655
704
|
envelope
|
|
656
705
|
);
|
|
657
706
|
} catch (err) {
|
|
@@ -663,14 +712,14 @@ async function reviewWaitPr(owner, repo, prNumber, afterSha, timeout_s, cfg, opt
|
|
|
663
712
|
observedSha && expectedSha.length >= 7 && (observedSha.startsWith(expectedSha) || expectedSha.startsWith(observedSha))
|
|
664
713
|
);
|
|
665
714
|
const findingCount = headShaMatches ? err.lastEnvelope?.finding_count ?? null : null;
|
|
666
|
-
const summary = prReviewSummary(
|
|
715
|
+
const summary = prReviewSummary(target2.owner, target2.repo, target2.prNumber, {
|
|
667
716
|
status,
|
|
668
717
|
head_sha: err.lastEnvelope?.head_sha ?? null,
|
|
669
718
|
finding_count: findingCount,
|
|
670
719
|
phase: err.lastEnvelope?.phase ?? null
|
|
671
720
|
});
|
|
672
721
|
return {
|
|
673
|
-
summary: err.reviewSeen ? summary : `${summary} \xB7 ${noPrReviewYetMessage(
|
|
722
|
+
summary: err.reviewSeen ? summary : `${summary} \xB7 ${noPrReviewYetMessage(target2.owner, target2.repo, target2.prNumber, { afterSha, pass: opts.pass, afterPass: opts.afterPass })}`,
|
|
674
723
|
data: {
|
|
675
724
|
status,
|
|
676
725
|
head_sha: err.lastEnvelope?.head_sha ?? null,
|
|
@@ -683,9 +732,9 @@ async function reviewWaitPr(owner, repo, prNumber, afterSha, timeout_s, cfg, opt
|
|
|
683
732
|
}
|
|
684
733
|
const rateLimited = payloadFromPrRateLimit(
|
|
685
734
|
err,
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
735
|
+
target2.owner,
|
|
736
|
+
target2.repo,
|
|
737
|
+
target2.prNumber
|
|
689
738
|
);
|
|
690
739
|
if (rateLimited) return rateLimited;
|
|
691
740
|
throw err;
|
|
@@ -694,7 +743,7 @@ async function reviewWaitPr(owner, repo, prNumber, afterSha, timeout_s, cfg, opt
|
|
|
694
743
|
|
|
695
744
|
// src/tools/review-wait.ts
|
|
696
745
|
import {
|
|
697
|
-
CommandError as
|
|
746
|
+
CommandError as CommandError7,
|
|
698
747
|
ReviewPollTimeoutError as ReviewPollTimeoutError2,
|
|
699
748
|
loadConfig as loadConfig6,
|
|
700
749
|
pollReview as pollReview2
|
|
@@ -702,7 +751,7 @@ import {
|
|
|
702
751
|
|
|
703
752
|
// src/tools/review-get.ts
|
|
704
753
|
import {
|
|
705
|
-
CommandError as
|
|
754
|
+
CommandError as CommandError6,
|
|
706
755
|
getReview,
|
|
707
756
|
toReviewJobEnvelope as toReviewJobEnvelope3
|
|
708
757
|
} from "mergestorm/client";
|
|
@@ -716,7 +765,7 @@ async function reviewGet(jobId, cfg) {
|
|
|
716
765
|
throw err;
|
|
717
766
|
}
|
|
718
767
|
if (!row || typeof row !== "object" || typeof row.status !== "string") {
|
|
719
|
-
throw new
|
|
768
|
+
throw new CommandError6(`Unexpected response for review ${jobId}.`);
|
|
720
769
|
}
|
|
721
770
|
const envelope = toReviewJobEnvelope3(row, { jobId });
|
|
722
771
|
const bits = [envelope.status];
|
|
@@ -732,7 +781,7 @@ async function reviewWait(jobId, timeout_s, cfg, opts = {}) {
|
|
|
732
781
|
const resolved = cfg ?? await loadConfig6();
|
|
733
782
|
const id = jobId.trim();
|
|
734
783
|
if (!id) {
|
|
735
|
-
throw new
|
|
784
|
+
throw new CommandError7("job_id is required", 2, "usage");
|
|
736
785
|
}
|
|
737
786
|
if (timeout_s === 0) return reviewGet(id, resolved);
|
|
738
787
|
try {
|
|
@@ -757,7 +806,7 @@ async function reviewWait(jobId, timeout_s, cfg, opts = {}) {
|
|
|
757
806
|
|
|
758
807
|
// src/tools/settings-get.ts
|
|
759
808
|
import {
|
|
760
|
-
CommandError as
|
|
809
|
+
CommandError as CommandError8,
|
|
761
810
|
getSettings
|
|
762
811
|
} from "mergestorm/client";
|
|
763
812
|
function settingsSummary(settings) {
|
|
@@ -775,7 +824,7 @@ function settingsSummary(settings) {
|
|
|
775
824
|
async function settingsGet(cfg) {
|
|
776
825
|
const settings = await getSettings(cfg);
|
|
777
826
|
if (!settings) {
|
|
778
|
-
throw new
|
|
827
|
+
throw new CommandError8(
|
|
779
828
|
"Settings are not available (offline or API too old). Try again or open https://mergestorm.ai/settings"
|
|
780
829
|
);
|
|
781
830
|
}
|
|
@@ -787,7 +836,7 @@ async function settingsGet(cfg) {
|
|
|
787
836
|
|
|
788
837
|
// src/tools/settings-set.ts
|
|
789
838
|
import {
|
|
790
|
-
CommandError as
|
|
839
|
+
CommandError as CommandError9,
|
|
791
840
|
BEARER_SETTINGS,
|
|
792
841
|
SETTINGS_WRITABLE_KEYS,
|
|
793
842
|
patchSettings
|
|
@@ -799,7 +848,7 @@ var SETTINGS_READ_ONLY_KEYS = [
|
|
|
799
848
|
function settingsPatchFromArgs(args) {
|
|
800
849
|
for (const key of SETTINGS_READ_ONLY_KEYS) {
|
|
801
850
|
if (args[key] !== void 0) {
|
|
802
|
-
throw new
|
|
851
|
+
throw new CommandError9(`${key} is read-only and cannot be set.`, 2, "usage");
|
|
803
852
|
}
|
|
804
853
|
}
|
|
805
854
|
const patch = {};
|
|
@@ -810,7 +859,7 @@ function settingsPatchFromArgs(args) {
|
|
|
810
859
|
if ("kind" in row && (row.kind === "seconds" || row.kind === "count")) {
|
|
811
860
|
if (typeof value !== "number" || !Number.isInteger(value) || value < row.min || value > row.max) {
|
|
812
861
|
const unit = row.kind === "seconds" ? " of seconds" : "";
|
|
813
|
-
throw new
|
|
862
|
+
throw new CommandError9(
|
|
814
863
|
`${key} takes a whole number${unit} from ${row.min} through ${row.max}.`,
|
|
815
864
|
2,
|
|
816
865
|
"usage"
|
|
@@ -821,17 +870,17 @@ function settingsPatchFromArgs(args) {
|
|
|
821
870
|
}
|
|
822
871
|
if ("kind" in row) {
|
|
823
872
|
const valid = row.kind === "logins" ? Array.isArray(value) && value.every((login) => typeof login === "string") : typeof value === "string" && row.values.includes(value);
|
|
824
|
-
if (!valid) throw new
|
|
873
|
+
if (!valid) throw new CommandError9(`Invalid value for ${key}.`, 2, "usage");
|
|
825
874
|
Object.assign(patch, { [key]: value });
|
|
826
875
|
continue;
|
|
827
876
|
}
|
|
828
877
|
if (typeof value !== "boolean") {
|
|
829
|
-
throw new
|
|
878
|
+
throw new CommandError9(`${key} takes a boolean.`, 2, "usage");
|
|
830
879
|
}
|
|
831
880
|
Object.assign(patch, { [key]: value });
|
|
832
881
|
}
|
|
833
882
|
if (Object.keys(patch).length === 0) {
|
|
834
|
-
throw new
|
|
883
|
+
throw new CommandError9(
|
|
835
884
|
`At least one settings key is required: ${SETTINGS_WRITABLE_KEYS.join(", ")}.`,
|
|
836
885
|
2,
|
|
837
886
|
"usage"
|
|
@@ -848,9 +897,71 @@ async function settingsSet(args, cfg) {
|
|
|
848
897
|
};
|
|
849
898
|
}
|
|
850
899
|
|
|
900
|
+
// src/tools/repo-settings.ts
|
|
901
|
+
import {
|
|
902
|
+
CommandError as CommandError10,
|
|
903
|
+
REPO_SETTINGS_KEYS,
|
|
904
|
+
listRepoSettings,
|
|
905
|
+
patchRepoSettings
|
|
906
|
+
} from "mergestorm/client";
|
|
907
|
+
var ARG_BY_KEY = {
|
|
908
|
+
auto_patch_enabled: "auto_patch",
|
|
909
|
+
auto_review_enabled: "auto_review",
|
|
910
|
+
auto_land_default: "auto_land"
|
|
911
|
+
};
|
|
912
|
+
function parseRepo(raw) {
|
|
913
|
+
const match = typeof raw === "string" ? /^([A-Za-z0-9_.-]+)\/([A-Za-z0-9_.-]+)$/.exec(raw.trim()) : null;
|
|
914
|
+
if (!match) throw new CommandError10("repo takes <owner>/<repo>.", 2, "usage");
|
|
915
|
+
return { owner: match[1], repo: match[2] };
|
|
916
|
+
}
|
|
917
|
+
function words(value) {
|
|
918
|
+
return value === null ? "inherit" : value ? "on" : "off";
|
|
919
|
+
}
|
|
920
|
+
function repoSettingsSummary(entry) {
|
|
921
|
+
return `${entry.owner}/${entry.repo}: ${REPO_SETTINGS_KEYS.map((key) => `${ARG_BY_KEY[key]} ${words(entry[key])}`).join(" \xB7 ")}`;
|
|
922
|
+
}
|
|
923
|
+
function repoSettingsPatchFromArgs(args) {
|
|
924
|
+
const patch = {};
|
|
925
|
+
for (const key of REPO_SETTINGS_KEYS) {
|
|
926
|
+
const value = args[ARG_BY_KEY[key]];
|
|
927
|
+
if (value === void 0) continue;
|
|
928
|
+
if (value !== null && typeof value !== "boolean") {
|
|
929
|
+
throw new CommandError10(`${ARG_BY_KEY[key]} takes true, false, or null.`, 2, "usage");
|
|
930
|
+
}
|
|
931
|
+
patch[key] = value;
|
|
932
|
+
}
|
|
933
|
+
if (Object.keys(patch).length === 0) {
|
|
934
|
+
throw new CommandError10(
|
|
935
|
+
`At least one of ${Object.values(ARG_BY_KEY).join(", ")} is required.`,
|
|
936
|
+
2,
|
|
937
|
+
"usage"
|
|
938
|
+
);
|
|
939
|
+
}
|
|
940
|
+
return patch;
|
|
941
|
+
}
|
|
942
|
+
async function repoSettingsGet(args, cfg) {
|
|
943
|
+
const wanted = args.repo === void 0 ? null : parseRepo(args.repo);
|
|
944
|
+
const all = await listRepoSettings(cfg);
|
|
945
|
+
if (!wanted) {
|
|
946
|
+
return {
|
|
947
|
+
summary: all.length === 0 ? "No repositories are connected to this account." : all.map(repoSettingsSummary).join("\n"),
|
|
948
|
+
data: { repos: all }
|
|
949
|
+
};
|
|
950
|
+
}
|
|
951
|
+
const slug = `${wanted.owner}/${wanted.repo}`.toLowerCase();
|
|
952
|
+
const entry = all.find((candidate) => `${candidate.owner}/${candidate.repo}`.toLowerCase() === slug);
|
|
953
|
+
if (!entry) throw new CommandError10(`${wanted.owner}/${wanted.repo} is not connected to this account.`);
|
|
954
|
+
return { summary: repoSettingsSummary(entry), data: { ...entry } };
|
|
955
|
+
}
|
|
956
|
+
async function repoSettingsSet(args, cfg) {
|
|
957
|
+
const { owner, repo } = parseRepo(args.repo);
|
|
958
|
+
const entry = await patchRepoSettings(owner, repo, repoSettingsPatchFromArgs(args), cfg);
|
|
959
|
+
return { summary: repoSettingsSummary(entry), data: { ...entry } };
|
|
960
|
+
}
|
|
961
|
+
|
|
851
962
|
// src/tools/stack-adopt.ts
|
|
852
|
-
import { adoptStack, CommandError as
|
|
853
|
-
import { z as
|
|
963
|
+
import { adoptStack, CommandError as CommandError11, stackWatchObligation as stackWatchObligation4 } from "mergestorm/client";
|
|
964
|
+
import { z as z3 } from "zod";
|
|
854
965
|
|
|
855
966
|
// src/tools/stack-set.ts
|
|
856
967
|
import { setStackPolicy, stackWatchObligation as stackWatchObligation3 } from "mergestorm/client";
|
|
@@ -918,18 +1029,18 @@ async function stackSet(stackId, input, cfg) {
|
|
|
918
1029
|
|
|
919
1030
|
// src/tools/stack-adopt.ts
|
|
920
1031
|
var stackAdoptSchema = {
|
|
921
|
-
owner:
|
|
922
|
-
repo:
|
|
923
|
-
pr_number:
|
|
924
|
-
auto_land:
|
|
925
|
-
auto_review:
|
|
926
|
-
auto_patch:
|
|
927
|
-
auto_resolve_conflicts:
|
|
928
|
-
auto_fix_ci:
|
|
1032
|
+
owner: z3.string().trim().min(1),
|
|
1033
|
+
repo: z3.string().trim().min(1),
|
|
1034
|
+
pr_number: z3.number().int().min(1),
|
|
1035
|
+
auto_land: z3.boolean().optional(),
|
|
1036
|
+
auto_review: z3.boolean().nullable().optional(),
|
|
1037
|
+
auto_patch: z3.boolean().nullable().optional(),
|
|
1038
|
+
auto_resolve_conflicts: z3.boolean().nullable().optional(),
|
|
1039
|
+
auto_fix_ci: z3.boolean().nullable().optional()
|
|
929
1040
|
};
|
|
930
|
-
var
|
|
1041
|
+
var inputSchema2 = z3.object(stackAdoptSchema);
|
|
931
1042
|
async function stackAdopt(input, cfg) {
|
|
932
|
-
const parsed =
|
|
1043
|
+
const parsed = inputSchema2.safeParse(input);
|
|
933
1044
|
if (!parsed.success) {
|
|
934
1045
|
const message = "stack_adopt requires owner, repo, a positive pr_number, and valid policy values";
|
|
935
1046
|
return { summary: message, data: { error: { code: "invalid_input", message } }, isError: true };
|
|
@@ -959,11 +1070,11 @@ async function stackAdopt(input, cfg) {
|
|
|
959
1070
|
summary: message,
|
|
960
1071
|
data: {
|
|
961
1072
|
error: {
|
|
962
|
-
code: err instanceof
|
|
1073
|
+
code: err instanceof CommandError11 ? err.code ?? "stack_adopt_failed" : "stack_adopt_failed",
|
|
963
1074
|
message,
|
|
964
|
-
...err instanceof
|
|
965
|
-
...err instanceof
|
|
966
|
-
...err instanceof
|
|
1075
|
+
...err instanceof CommandError11 ? { exit_code: err.exitCode } : {},
|
|
1076
|
+
...err instanceof CommandError11 && err.reason !== void 0 ? { reason: err.reason } : {},
|
|
1077
|
+
...err instanceof CommandError11 && err.retryAfterSeconds !== void 0 ? { retry_after_seconds: err.retryAfterSeconds } : {}
|
|
967
1078
|
}
|
|
968
1079
|
},
|
|
969
1080
|
isError: true
|
|
@@ -1032,6 +1143,102 @@ async function stackList(cfg) {
|
|
|
1032
1143
|
}
|
|
1033
1144
|
}
|
|
1034
1145
|
|
|
1146
|
+
// src/tools/stack-publish.ts
|
|
1147
|
+
import {
|
|
1148
|
+
CommandError as CommandError12,
|
|
1149
|
+
publishWorkingBranch,
|
|
1150
|
+
stackWatchObligation as stackWatchObligation5,
|
|
1151
|
+
workingBranchPublication
|
|
1152
|
+
} from "mergestorm/client";
|
|
1153
|
+
import { z as z4 } from "zod";
|
|
1154
|
+
var target = {
|
|
1155
|
+
cwd: z4.string().trim().min(1).optional(),
|
|
1156
|
+
owner: z4.string().trim().min(1).optional(),
|
|
1157
|
+
repo: z4.string().trim().min(1).optional(),
|
|
1158
|
+
branch: z4.string().trim().min(1).optional()
|
|
1159
|
+
};
|
|
1160
|
+
var stackPublishSchema = {
|
|
1161
|
+
...target,
|
|
1162
|
+
base: z4.string().trim().min(1).optional(),
|
|
1163
|
+
title: z4.string().trim().min(1).max(256).optional(),
|
|
1164
|
+
body: z4.string().max(6e4).optional(),
|
|
1165
|
+
replace_head: z4.string().regex(/^[0-9a-f]{40}$/).optional(),
|
|
1166
|
+
auto_land: z4.boolean().optional()
|
|
1167
|
+
};
|
|
1168
|
+
var stackPublishStatusSchema = target;
|
|
1169
|
+
var publishInput = z4.object(stackPublishSchema);
|
|
1170
|
+
var statusInput = z4.object(stackPublishStatusSchema);
|
|
1171
|
+
function failure(err, fallback) {
|
|
1172
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1173
|
+
return {
|
|
1174
|
+
summary: message,
|
|
1175
|
+
data: {
|
|
1176
|
+
error: {
|
|
1177
|
+
code: err instanceof CommandError12 ? err.code ?? fallback : fallback,
|
|
1178
|
+
message,
|
|
1179
|
+
...err instanceof CommandError12 && err.retryAfterSeconds !== void 0 ? { retry_after_seconds: err.retryAfterSeconds } : {}
|
|
1180
|
+
}
|
|
1181
|
+
},
|
|
1182
|
+
isError: true
|
|
1183
|
+
};
|
|
1184
|
+
}
|
|
1185
|
+
function targetOf(input, cfg) {
|
|
1186
|
+
return {
|
|
1187
|
+
...input.cwd ? { cwd: input.cwd } : {},
|
|
1188
|
+
...input.owner ? { owner: input.owner } : {},
|
|
1189
|
+
...input.repo ? { repo: input.repo } : {},
|
|
1190
|
+
...input.branch ? { branch: input.branch } : {},
|
|
1191
|
+
...cfg ? { cfg } : {}
|
|
1192
|
+
};
|
|
1193
|
+
}
|
|
1194
|
+
async function stackPublish(input, cfg, deps) {
|
|
1195
|
+
const parsed = publishInput.safeParse(input);
|
|
1196
|
+
if (!parsed.success || Boolean(parsed.data.owner) !== Boolean(parsed.data.repo)) {
|
|
1197
|
+
const message = "stack_publish takes owner and repo together, and replace_head as a full 40-character commit SHA";
|
|
1198
|
+
return { summary: message, data: { error: { code: "invalid_input", message } }, isError: true };
|
|
1199
|
+
}
|
|
1200
|
+
try {
|
|
1201
|
+
const result = await publishWorkingBranch(
|
|
1202
|
+
{
|
|
1203
|
+
...targetOf(parsed.data, cfg),
|
|
1204
|
+
...parsed.data.base ? { base: parsed.data.base } : {},
|
|
1205
|
+
...parsed.data.title ? { title: parsed.data.title } : {},
|
|
1206
|
+
...parsed.data.body !== void 0 ? { body: parsed.data.body } : {},
|
|
1207
|
+
...parsed.data.replace_head ? { replaceHead: parsed.data.replace_head } : {},
|
|
1208
|
+
...typeof parsed.data.auto_land === "boolean" ? { autoLand: parsed.data.auto_land } : {}
|
|
1209
|
+
},
|
|
1210
|
+
deps
|
|
1211
|
+
);
|
|
1212
|
+
const watch = stackWatchObligation5({ stackId: result.stack.id, terminal: null, unread: true, freshCursor: true });
|
|
1213
|
+
const moved = result.branchCreated ? "created" : result.branchUpdated ? "updated" : "already current";
|
|
1214
|
+
return {
|
|
1215
|
+
summary: withWatchText(
|
|
1216
|
+
watch,
|
|
1217
|
+
`Published ${result.owner}/${result.repo} ${result.branch} (${moved}) at ${result.head.sha}, PR #${result.pr.number}, stack ${result.stack.id} with Auto patch off`
|
|
1218
|
+
),
|
|
1219
|
+
data: { result, watch }
|
|
1220
|
+
};
|
|
1221
|
+
} catch (err) {
|
|
1222
|
+
return failure(err, "stack_publish_failed");
|
|
1223
|
+
}
|
|
1224
|
+
}
|
|
1225
|
+
async function stackPublishStatus(input, cfg, deps) {
|
|
1226
|
+
const parsed = statusInput.safeParse(input);
|
|
1227
|
+
if (!parsed.success || Boolean(parsed.data.owner) !== Boolean(parsed.data.repo)) {
|
|
1228
|
+
const message = "stack_publish_status takes owner and repo together";
|
|
1229
|
+
return { summary: message, data: { error: { code: "invalid_input", message } }, isError: true };
|
|
1230
|
+
}
|
|
1231
|
+
try {
|
|
1232
|
+
const status = await workingBranchPublication(targetOf(parsed.data, cfg), deps);
|
|
1233
|
+
return {
|
|
1234
|
+
summary: `${status.owner}/${status.repo} ${status.branch}: ${status.state}${status.head ? ` at ${status.head.sha}` : ""}${status.pr ? `, PR #${status.pr.number}` : ""}${status.stack ? `, stack ${status.stack.id}` : ""}`,
|
|
1235
|
+
data: { status }
|
|
1236
|
+
};
|
|
1237
|
+
} catch (err) {
|
|
1238
|
+
return failure(err, "stack_publish_status_failed");
|
|
1239
|
+
}
|
|
1240
|
+
}
|
|
1241
|
+
|
|
1035
1242
|
// src/tools/stack-status.ts
|
|
1036
1243
|
import {
|
|
1037
1244
|
getEnrichedStack,
|
|
@@ -1039,14 +1246,14 @@ import {
|
|
|
1039
1246
|
isCommandErrorCode as isCommandErrorCode5,
|
|
1040
1247
|
stackBlockers as stackBlockers2,
|
|
1041
1248
|
stackTerminalReason,
|
|
1042
|
-
stackWatchObligation as
|
|
1249
|
+
stackWatchObligation as stackWatchObligation6
|
|
1043
1250
|
} from "mergestorm/client";
|
|
1044
1251
|
async function stackStatus(stackId, cfg) {
|
|
1045
1252
|
try {
|
|
1046
1253
|
const stack = await getEnrichedStack(stackId, cfg);
|
|
1047
1254
|
if (!stack) {
|
|
1048
1255
|
const message = `Stack not found or not owned by the current user: ${stackId}`;
|
|
1049
|
-
const watch2 =
|
|
1256
|
+
const watch2 = stackWatchObligation6({ stackId, terminal: "not_found" });
|
|
1050
1257
|
return {
|
|
1051
1258
|
summary: withWatchText(watch2, message),
|
|
1052
1259
|
data: {
|
|
@@ -1062,7 +1269,7 @@ async function stackStatus(stackId, cfg) {
|
|
|
1062
1269
|
}
|
|
1063
1270
|
const entries = await listMergeQueueEntries2(cfg, { stackId: stack.id });
|
|
1064
1271
|
const { attention, held, issues, currentCandidate, busy, agents, repair, landGatePending, queueWait } = stackBlockers2(stack, entries);
|
|
1065
|
-
const watch =
|
|
1272
|
+
const watch = stackWatchObligation6({
|
|
1066
1273
|
stackId: stack.id,
|
|
1067
1274
|
terminal: stackTerminalReason(stack),
|
|
1068
1275
|
cursor: { enrolledHeadSha: currentCandidate?.headSha ?? null },
|
|
@@ -1075,7 +1282,7 @@ async function stackStatus(stackId, cfg) {
|
|
|
1075
1282
|
};
|
|
1076
1283
|
} catch (err) {
|
|
1077
1284
|
if (isCommandErrorCode5(err, "rate_limited")) {
|
|
1078
|
-
const watch =
|
|
1285
|
+
const watch = stackWatchObligation6({
|
|
1079
1286
|
stackId,
|
|
1080
1287
|
terminal: null,
|
|
1081
1288
|
status: "rate_limited",
|
|
@@ -1101,7 +1308,7 @@ async function stackStatus(stackId, cfg) {
|
|
|
1101
1308
|
|
|
1102
1309
|
// src/tools/whoami.ts
|
|
1103
1310
|
import {
|
|
1104
|
-
CommandError as
|
|
1311
|
+
CommandError as CommandError13,
|
|
1105
1312
|
apiBase,
|
|
1106
1313
|
configPath,
|
|
1107
1314
|
getMe as getMe2,
|
|
@@ -1112,7 +1319,7 @@ async function whoami(cfg, serverVersion) {
|
|
|
1112
1319
|
const resolved = cfg ?? await loadConfig7();
|
|
1113
1320
|
const key = resolveApiKey(resolved);
|
|
1114
1321
|
if (!key) {
|
|
1115
|
-
throw new
|
|
1322
|
+
throw new CommandError13(
|
|
1116
1323
|
"No API key. Run `mergestorm login` (agents: `mergestorm login --start`, then `--finish`) or set MERGESTORM_API_KEY.",
|
|
1117
1324
|
1,
|
|
1118
1325
|
"missing_api_key"
|
|
@@ -1120,7 +1327,7 @@ async function whoami(cfg, serverVersion) {
|
|
|
1120
1327
|
}
|
|
1121
1328
|
const me = await getMe2(resolved);
|
|
1122
1329
|
if (!me) {
|
|
1123
|
-
throw new
|
|
1330
|
+
throw new CommandError13("Live account details unavailable. Could not verify account with /me.");
|
|
1124
1331
|
}
|
|
1125
1332
|
const data = {
|
|
1126
1333
|
key_prefix: me.key.prefix,
|
|
@@ -1195,7 +1402,7 @@ var MCP_STACK_WATCH_INSTRUCTIONS = [
|
|
|
1195
1402
|
|
|
1196
1403
|
// src/server.ts
|
|
1197
1404
|
var MCP_SERVER_NAME = "mergestorm";
|
|
1198
|
-
var MCP_SERVER_VERSION = "0.2.
|
|
1405
|
+
var MCP_SERVER_VERSION = "0.2.22";
|
|
1199
1406
|
var MCP_SERVER_INSTRUCTIONS = [
|
|
1200
1407
|
MCP_STACK_DONE_INSTRUCTIONS,
|
|
1201
1408
|
MCP_PR_LOOP_INSTRUCTIONS,
|
|
@@ -1214,14 +1421,14 @@ function ok(payload) {
|
|
|
1214
1421
|
};
|
|
1215
1422
|
}
|
|
1216
1423
|
function fail(err) {
|
|
1217
|
-
const message = err instanceof
|
|
1424
|
+
const message = err instanceof CommandError14 ? err.code ? `Mergestorm request failed (${err.code}${err.reason ? `, reason ${err.reason}` : ""}): ${err.message}.` : err.message : err instanceof Error ? err.message : String(err);
|
|
1218
1425
|
return ok({
|
|
1219
1426
|
isError: true,
|
|
1220
1427
|
summary: message,
|
|
1221
1428
|
data: {
|
|
1222
1429
|
error: {
|
|
1223
1430
|
message: err instanceof Error ? err.message : String(err),
|
|
1224
|
-
...err instanceof
|
|
1431
|
+
...err instanceof CommandError14 ? {
|
|
1225
1432
|
exit_code: err.exitCode,
|
|
1226
1433
|
...err.code !== void 0 ? { code: err.code } : {},
|
|
1227
1434
|
...err.reason !== void 0 ? { reason: err.reason } : {},
|
|
@@ -1279,7 +1486,7 @@ function createMergestormMcpServer() {
|
|
|
1279
1486
|
title: "List reviews",
|
|
1280
1487
|
description: "List recent Mergestorm review jobs as job envelopes.",
|
|
1281
1488
|
inputSchema: {
|
|
1282
|
-
limit:
|
|
1489
|
+
limit: z5.number().int().min(1).max(50).optional()
|
|
1283
1490
|
},
|
|
1284
1491
|
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true }
|
|
1285
1492
|
},
|
|
@@ -1297,18 +1504,18 @@ function createMergestormMcpServer() {
|
|
|
1297
1504
|
title: "Submit review",
|
|
1298
1505
|
description: "Submit a local git diff (or an explicit diff) as a Mergestorm review job. Does not wait by default; returns job_id immediately after submission. Explicit wait: true polls one 45s slice by default. Record job_id and loop review_wait until done. Do not pass 300; hosts drop long MCP calls. On rate_limited, wait retry_after_seconds and inspect or wait for an in-flight review before resubmitting. specialists may be security, performance, architecture, tests, data, api, frontend. governance and seam cannot be requested.",
|
|
1299
1506
|
inputSchema: {
|
|
1300
|
-
cwd:
|
|
1301
|
-
base:
|
|
1302
|
-
head:
|
|
1303
|
-
diff:
|
|
1304
|
-
router:
|
|
1305
|
-
specialists:
|
|
1306
|
-
context:
|
|
1307
|
-
context_files:
|
|
1308
|
-
thread:
|
|
1309
|
-
idempotency_key:
|
|
1310
|
-
wait:
|
|
1311
|
-
timeout_s:
|
|
1507
|
+
cwd: z5.string().optional(),
|
|
1508
|
+
base: z5.string().optional(),
|
|
1509
|
+
head: z5.string().optional(),
|
|
1510
|
+
diff: z5.string().optional(),
|
|
1511
|
+
router: z5.enum(["off", "standard", "max", "manual"]).optional(),
|
|
1512
|
+
specialists: z5.array(z5.string().min(1)).max(5).optional(),
|
|
1513
|
+
context: z5.string().optional(),
|
|
1514
|
+
context_files: z5.array(z5.string().min(1)).max(10).optional(),
|
|
1515
|
+
thread: z5.string().min(1).max(120).optional(),
|
|
1516
|
+
idempotency_key: z5.string().min(1).max(128).optional(),
|
|
1517
|
+
wait: z5.boolean().optional(),
|
|
1518
|
+
timeout_s: z5.number().positive().max(300).optional()
|
|
1312
1519
|
},
|
|
1313
1520
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true }
|
|
1314
1521
|
},
|
|
@@ -1326,8 +1533,8 @@ function createMergestormMcpServer() {
|
|
|
1326
1533
|
title: "Wait for review",
|
|
1327
1534
|
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.",
|
|
1328
1535
|
inputSchema: {
|
|
1329
|
-
job_id:
|
|
1330
|
-
timeout_s:
|
|
1536
|
+
job_id: z5.string().min(1),
|
|
1537
|
+
timeout_s: z5.number().min(0).max(300).optional()
|
|
1331
1538
|
},
|
|
1332
1539
|
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true }
|
|
1333
1540
|
},
|
|
@@ -1345,13 +1552,13 @@ function createMergestormMcpServer() {
|
|
|
1345
1552
|
title: "Wait for GitHub PR Vortex review",
|
|
1346
1553
|
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, or none when no review exists for that PR and head yet (call once more; if it is none again, Vortex is not reviewing that head: comment @mergestorm-vortex review on the PR, run mg review, or push a new commit). 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.",
|
|
1347
1554
|
inputSchema: {
|
|
1348
|
-
owner:
|
|
1349
|
-
repo:
|
|
1350
|
-
pr_number:
|
|
1351
|
-
after_sha:
|
|
1352
|
-
pass:
|
|
1353
|
-
after_pass:
|
|
1354
|
-
timeout_s:
|
|
1555
|
+
owner: z5.string().min(1),
|
|
1556
|
+
repo: z5.string().min(1),
|
|
1557
|
+
pr_number: z5.number().int().min(1),
|
|
1558
|
+
after_sha: z5.string().optional(),
|
|
1559
|
+
pass: z5.number().int().min(1).optional(),
|
|
1560
|
+
after_pass: z5.number().int().min(1).optional(),
|
|
1561
|
+
timeout_s: z5.number().min(0).max(300).optional()
|
|
1355
1562
|
},
|
|
1356
1563
|
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true }
|
|
1357
1564
|
},
|
|
@@ -1379,16 +1586,16 @@ function createMergestormMcpServer() {
|
|
|
1379
1586
|
title: "Dismiss Vortex findings",
|
|
1380
1587
|
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.` + STACK_WATCH_RESULT_NOTE,
|
|
1381
1588
|
inputSchema: {
|
|
1382
|
-
owner:
|
|
1383
|
-
repo:
|
|
1384
|
-
pr_number:
|
|
1385
|
-
head_sha:
|
|
1386
|
-
review_id:
|
|
1387
|
-
finding_ids:
|
|
1388
|
-
scope:
|
|
1389
|
-
reason:
|
|
1390
|
-
evidence_url:
|
|
1391
|
-
preview:
|
|
1589
|
+
owner: z5.string().min(1),
|
|
1590
|
+
repo: z5.string().min(1),
|
|
1591
|
+
pr_number: z5.number().int().min(1),
|
|
1592
|
+
head_sha: z5.string().regex(/^[0-9a-fA-F]{40}$/),
|
|
1593
|
+
review_id: z5.number().int().min(1),
|
|
1594
|
+
finding_ids: z5.array(z5.string().min(1)).max(50).optional(),
|
|
1595
|
+
scope: z5.enum(["findings", "review"]).optional(),
|
|
1596
|
+
reason: z5.string().optional(),
|
|
1597
|
+
evidence_url: z5.string().url().optional(),
|
|
1598
|
+
preview: z5.boolean().optional()
|
|
1392
1599
|
},
|
|
1393
1600
|
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: true }
|
|
1394
1601
|
},
|
|
@@ -1400,6 +1607,22 @@ function createMergestormMcpServer() {
|
|
|
1400
1607
|
}
|
|
1401
1608
|
}
|
|
1402
1609
|
);
|
|
1610
|
+
addTool(
|
|
1611
|
+
"issue_create",
|
|
1612
|
+
{
|
|
1613
|
+
title: "Open GitHub issue",
|
|
1614
|
+
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.",
|
|
1615
|
+
inputSchema: issueCreateSchema,
|
|
1616
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true }
|
|
1617
|
+
},
|
|
1618
|
+
async (args) => {
|
|
1619
|
+
try {
|
|
1620
|
+
return ok(await issueCreate(args));
|
|
1621
|
+
} catch (err) {
|
|
1622
|
+
return fail(err);
|
|
1623
|
+
}
|
|
1624
|
+
}
|
|
1625
|
+
);
|
|
1403
1626
|
addTool(
|
|
1404
1627
|
"stack_adopt",
|
|
1405
1628
|
{
|
|
@@ -1431,18 +1654,50 @@ function createMergestormMcpServer() {
|
|
|
1431
1654
|
}
|
|
1432
1655
|
}
|
|
1433
1656
|
);
|
|
1657
|
+
addTool(
|
|
1658
|
+
"stack_publish",
|
|
1659
|
+
{
|
|
1660
|
+
title: "Publish branch through Mergestorm",
|
|
1661
|
+
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 patch pinned off, Auto land on for a new stack, and Auto review left to the account. These are your own changes; Cyclone does not write or patch them. 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. 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,
|
|
1662
|
+
inputSchema: stackPublishSchema,
|
|
1663
|
+
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: true }
|
|
1664
|
+
},
|
|
1665
|
+
async (args) => {
|
|
1666
|
+
try {
|
|
1667
|
+
return ok(await stackPublish(args));
|
|
1668
|
+
} catch (err) {
|
|
1669
|
+
return fail(err);
|
|
1670
|
+
}
|
|
1671
|
+
}
|
|
1672
|
+
);
|
|
1673
|
+
addTool(
|
|
1674
|
+
"stack_publish_status",
|
|
1675
|
+
{
|
|
1676
|
+
title: "Read branch publication",
|
|
1677
|
+
description: "Read how far a branch published with stack_publish got: absent, pushed (branch on GitHub, no pull request), opened (pull request open, stack not registered) or registered, with the head commit and tree on GitHub, the pull request and the stack's Auto patch and Auto land state. Changes nothing. Call it after a lost stack_publish response, then call stack_publish again to resume.",
|
|
1678
|
+
inputSchema: stackPublishStatusSchema,
|
|
1679
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true }
|
|
1680
|
+
},
|
|
1681
|
+
async (args) => {
|
|
1682
|
+
try {
|
|
1683
|
+
return ok(await stackPublishStatus(args));
|
|
1684
|
+
} catch (err) {
|
|
1685
|
+
return fail(err);
|
|
1686
|
+
}
|
|
1687
|
+
}
|
|
1688
|
+
);
|
|
1434
1689
|
addTool(
|
|
1435
1690
|
"stack_set",
|
|
1436
1691
|
{
|
|
1437
1692
|
title: "Set stack policy",
|
|
1438
1693
|
description: "Set per-stack automation on one owned Mergestorm stack: auto_land arms or disarms Auto land; auto_review, auto_patch, auto_resolve_conflicts, and auto_fix_ci pin Vortex auto-review, Cyclone auto-patch, Auto-resolve merge conflicts, or Auto-fix failing CI 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,
|
|
1439
1694
|
inputSchema: {
|
|
1440
|
-
stack_id:
|
|
1441
|
-
auto_land:
|
|
1442
|
-
auto_review:
|
|
1443
|
-
auto_patch:
|
|
1444
|
-
auto_resolve_conflicts:
|
|
1445
|
-
auto_fix_ci:
|
|
1695
|
+
stack_id: z5.string().min(1),
|
|
1696
|
+
auto_land: z5.boolean().optional(),
|
|
1697
|
+
auto_review: z5.boolean().nullable().optional(),
|
|
1698
|
+
auto_patch: z5.boolean().nullable().optional(),
|
|
1699
|
+
auto_resolve_conflicts: z5.boolean().nullable().optional(),
|
|
1700
|
+
auto_fix_ci: z5.boolean().nullable().optional()
|
|
1446
1701
|
},
|
|
1447
1702
|
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: true }
|
|
1448
1703
|
},
|
|
@@ -1468,7 +1723,7 @@ function createMergestormMcpServer() {
|
|
|
1468
1723
|
title: "Get stack status",
|
|
1469
1724
|
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. 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,
|
|
1470
1725
|
inputSchema: {
|
|
1471
|
-
stack_id:
|
|
1726
|
+
stack_id: z5.string().min(1)
|
|
1472
1727
|
},
|
|
1473
1728
|
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true }
|
|
1474
1729
|
},
|
|
@@ -1503,7 +1758,7 @@ function createMergestormMcpServer() {
|
|
|
1503
1758
|
title: "Get merge queue status",
|
|
1504
1759
|
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.",
|
|
1505
1760
|
inputSchema: {
|
|
1506
|
-
stack_id:
|
|
1761
|
+
stack_id: z5.string().min(1).optional()
|
|
1507
1762
|
},
|
|
1508
1763
|
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true }
|
|
1509
1764
|
},
|
|
@@ -1515,6 +1770,43 @@ function createMergestormMcpServer() {
|
|
|
1515
1770
|
}
|
|
1516
1771
|
}
|
|
1517
1772
|
);
|
|
1773
|
+
addTool(
|
|
1774
|
+
"repo_settings_get",
|
|
1775
|
+
{
|
|
1776
|
+
title: "Get repository settings",
|
|
1777
|
+
description: "Read the repository-level Auto patch, Auto review and Auto land values for the repositories connected to this account. Each value is true, false, or null; null means not set, so the account setting applies. Auto patch and Auto review precedence is the stack override (stack_set), then the repository value, then the account setting (settings_get). The Auto land repository value only seeds new stacks at adoption; it does not override an existing stack. Pass repo as owner/repo for one repository, or omit it for all.",
|
|
1778
|
+
inputSchema: { repo: z5.string().min(3).optional() },
|
|
1779
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true }
|
|
1780
|
+
},
|
|
1781
|
+
async (args) => {
|
|
1782
|
+
try {
|
|
1783
|
+
return ok(await repoSettingsGet(args ?? {}));
|
|
1784
|
+
} catch (err) {
|
|
1785
|
+
return fail(err);
|
|
1786
|
+
}
|
|
1787
|
+
}
|
|
1788
|
+
);
|
|
1789
|
+
addTool(
|
|
1790
|
+
"repo_settings_set",
|
|
1791
|
+
{
|
|
1792
|
+
title: "Update repository settings",
|
|
1793
|
+
description: "Set Auto patch (Cyclone), Auto review (Vortex) or the Auto land default for every stack and pull request in one repository connected to this account, and return the stored result. true or false sets the repository value; null clears it so the account setting applies again. A stack override (stack_set) still wins for that stack. auto_patch and auto_review apply to open stacks without an override from their next decision; nothing is cancelled or re-queued. auto_land only seeds new stacks. Only the account that connected the repository can change it. Never changes account settings.",
|
|
1794
|
+
inputSchema: {
|
|
1795
|
+
repo: z5.string().min(3),
|
|
1796
|
+
auto_patch: z5.boolean().nullable().optional(),
|
|
1797
|
+
auto_review: z5.boolean().nullable().optional(),
|
|
1798
|
+
auto_land: z5.boolean().nullable().optional()
|
|
1799
|
+
},
|
|
1800
|
+
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: true }
|
|
1801
|
+
},
|
|
1802
|
+
async (args) => {
|
|
1803
|
+
try {
|
|
1804
|
+
return ok(await repoSettingsSet(args ?? {}));
|
|
1805
|
+
} catch (err) {
|
|
1806
|
+
return fail(err);
|
|
1807
|
+
}
|
|
1808
|
+
}
|
|
1809
|
+
);
|
|
1518
1810
|
addTool(
|
|
1519
1811
|
"settings_get",
|
|
1520
1812
|
{
|
|
@@ -1539,12 +1831,12 @@ function createMergestormMcpServer() {
|
|
|
1539
1831
|
...Object.fromEntries(
|
|
1540
1832
|
BEARER_SETTINGS2.map((row) => [
|
|
1541
1833
|
row.key,
|
|
1542
|
-
("kind" in row ? row.kind === "logins" ?
|
|
1834
|
+
("kind" in row ? row.kind === "logins" ? z5.array(z5.string()) : row.kind === "seconds" || row.kind === "count" ? z5.number().int().min(row.min).max(row.max) : z5.enum(row.values) : z5.boolean()).optional()
|
|
1543
1835
|
])
|
|
1544
1836
|
),
|
|
1545
1837
|
// Preserve these args so the handler rejects them even alongside a writable key.
|
|
1546
|
-
cyclone_connected:
|
|
1547
|
-
github_connected:
|
|
1838
|
+
cyclone_connected: z5.unknown().optional(),
|
|
1839
|
+
github_connected: z5.unknown().optional()
|
|
1548
1840
|
},
|
|
1549
1841
|
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: true }
|
|
1550
1842
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mergestorm-mcp",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.22",
|
|
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.56",
|
|
33
33
|
"zod": "^3.24.2"
|
|
34
34
|
},
|
|
35
35
|
"devDependencies": {
|