mergestorm-mcp 0.2.10 → 0.2.12
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 +1 -1
- package/dist/index.js +23 -16
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -18,7 +18,7 @@ Every tool declares MCP `annotations`. The reads carry `readOnlyHint: true`; `re
|
|
|
18
18
|
|
|
19
19
|
The stack tools expose reads plus one scoped policy write:
|
|
20
20
|
|
|
21
|
-
- `stack_adopt({ owner, repo, pr_number, auto_land?, auto_review?, auto_patch? })` adopts an open PR chain with optional per-stack policy. Omitted policy does not force Cyclone off. `auto_review` and `auto_patch` accept `true`, `false`, or `null`; `null` clears the override. Read `stack_status` with the returned `result.stack.id` to verify policy and Cyclone ownership.
|
|
21
|
+
- `stack_adopt({ owner, repo, pr_number, auto_land?, auto_review?, auto_patch? })` adopts an open PR chain with optional per-stack policy. Omitted policy does not force Cyclone off. `auto_review` and `auto_patch` 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 infrastructure GitHub App on the repository: Mergestorm Surge, or Cyclone on accounts not yet moved to Surge (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
22
|
- `stack_list` returns the current API key owner's registered stacks.
|
|
23
23
|
- `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.
|
|
24
24
|
- `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.
|
package/dist/index.js
CHANGED
|
@@ -537,6 +537,7 @@ async function reviewSubmit(input, cfg, opts = {}) {
|
|
|
537
537
|
import {
|
|
538
538
|
PrReviewPollTimeoutError,
|
|
539
539
|
loadConfig as loadConfig5,
|
|
540
|
+
noPrReviewYetMessage,
|
|
540
541
|
pollPrVortexReview
|
|
541
542
|
} from "mergestorm/client";
|
|
542
543
|
|
|
@@ -648,20 +649,21 @@ async function reviewWaitPr(owner, repo, prNumber, afterSha, timeout_s, cfg, opt
|
|
|
648
649
|
);
|
|
649
650
|
} catch (err) {
|
|
650
651
|
if (err instanceof PrReviewPollTimeoutError) {
|
|
651
|
-
const status = "in_progress";
|
|
652
|
+
const status = err.reviewSeen ? "in_progress" : "none";
|
|
652
653
|
const expectedSha = afterSha?.trim().toLowerCase() ?? "";
|
|
653
654
|
const observedSha = err.lastEnvelope?.head_sha?.trim().toLowerCase() ?? "";
|
|
654
655
|
const headShaMatches = Boolean(
|
|
655
656
|
observedSha && expectedSha.length >= 7 && (observedSha.startsWith(expectedSha) || expectedSha.startsWith(observedSha))
|
|
656
657
|
);
|
|
657
658
|
const findingCount = headShaMatches ? err.lastEnvelope?.finding_count ?? null : null;
|
|
659
|
+
const summary = prReviewSummary(target.owner, target.repo, target.prNumber, {
|
|
660
|
+
status,
|
|
661
|
+
head_sha: err.lastEnvelope?.head_sha ?? null,
|
|
662
|
+
finding_count: findingCount,
|
|
663
|
+
phase: err.lastEnvelope?.phase ?? null
|
|
664
|
+
});
|
|
658
665
|
return {
|
|
659
|
-
summary:
|
|
660
|
-
status,
|
|
661
|
-
head_sha: err.lastEnvelope?.head_sha ?? null,
|
|
662
|
-
finding_count: findingCount,
|
|
663
|
-
phase: err.lastEnvelope?.phase ?? null
|
|
664
|
-
}),
|
|
666
|
+
summary: err.reviewSeen ? summary : `${summary} \xB7 ${noPrReviewYetMessage(target.owner, target.repo, target.prNumber, { afterSha, pass: opts.pass, afterPass: opts.afterPass })}`,
|
|
665
667
|
data: {
|
|
666
668
|
status,
|
|
667
669
|
head_sha: err.lastEnvelope?.head_sha ?? null,
|
|
@@ -754,7 +756,9 @@ import {
|
|
|
754
756
|
function settingsSummary(settings) {
|
|
755
757
|
const autoPatch = settings.auto_patch_enabled ? "on" : "off";
|
|
756
758
|
const cyclone = settings.cyclone_connected ? "Cyclone connected" : "Cyclone not connected";
|
|
757
|
-
return `auto_patch ${autoPatch} \xB7 ${cyclone}`;
|
|
759
|
+
if (typeof settings.stacks_ready !== "boolean") return `auto_patch ${autoPatch} \xB7 ${cyclone}`;
|
|
760
|
+
const stacks = settings.stacks_ready ? "stacks, merge queue and Auto land ready" : "stacks, merge queue and Auto land not ready (install Mergestorm Surge)";
|
|
761
|
+
return `auto_patch ${autoPatch} \xB7 ${cyclone} (auto patch) \xB7 ${stacks}`;
|
|
758
762
|
}
|
|
759
763
|
async function settingsGet(cfg) {
|
|
760
764
|
const settings = await getSettings(cfg);
|
|
@@ -931,6 +935,7 @@ async function stackAdopt(input, cfg) {
|
|
|
931
935
|
error: {
|
|
932
936
|
code: err instanceof CommandError9 ? err.code ?? "stack_adopt_failed" : "stack_adopt_failed",
|
|
933
937
|
message,
|
|
938
|
+
...err instanceof CommandError9 && err.reason !== void 0 ? { reason: err.reason } : {},
|
|
934
939
|
...err instanceof CommandError9 && err.retryAfterSeconds !== void 0 ? { retry_after_seconds: err.retryAfterSeconds } : {}
|
|
935
940
|
}
|
|
936
941
|
},
|
|
@@ -1118,13 +1123,13 @@ var MCP_STACK_DONE_INSTRUCTIONS = [
|
|
|
1118
1123
|
"- Every stack_wait, stack_status, stack_adopt, and stack_set result carries watch: {done, until, reason, next, message}. While watch.done is false, keep calling stack_wait with watch.next.args (the returned cursor), 45s at a time, or hand the waiting to the background watcher below, and do not end your task. Stop only when watch.done is true.",
|
|
1119
1124
|
"- If your host cannot hold a long turn open (Cursor, Claude Code, Codex, and other turn-based agents), do not poll stack_wait inside your turn: run `mg stack watch <stack-id>` as a background command with a notification on output matching `MS-WATCH (ATTENTION|LANDED)`, then end your turn. The watcher runs stack_wait against the Mergestorm API in slices of at most 300s, carries the cursor, stays silent while the stack is waiting or in_progress (busy[] holds and actAfter included), and costs no model tokens. It exits on attention (`MS-WATCH ATTENTION pr=<n> head=<sha> blocker=... repair=<kind>`, exit 3), on a read that failed twice (`MS-WATCH ATTENTION failed`, exit 3; tell the human the stack is no longer watched), or when watch.done is true (`MS-WATCH LANDED stack=<id> reason=<reason>`, exit 0; only reason=landed is landed). On an attention wake, read stack_status once, fix the blocker per mergestorm-bounce-watch, push, and start `mg stack watch <stack-id> --head <pushed-sha>` again. Ending your turn while the watcher runs is not ending the task. Do not subscribe to GitHub events to wait on a stack.",
|
|
1120
1125
|
"- A layer PR shown as merged is merged into mg-stack-<n>, not main. The stack is landed only when the land PR (head mg-stack-<n>) merges into main or watch.done says landed. Never judge landing from GitHub PR state alone.",
|
|
1121
|
-
"- attention means something for you to fix; repair, when present, names the concrete fix. A pending land gate (landGatePending: ci_pending
|
|
1126
|
+
"- attention means something for you to fix; repair, when present, names the concrete fix. A pending land gate (landGatePending: ci_pending) is a wait, not attention."
|
|
1122
1127
|
].join("\n");
|
|
1123
1128
|
var MCP_STACK_BASE_INSTRUCTIONS = [
|
|
1124
1129
|
"Stack PR bases (Mergestorm sets these; leave them):",
|
|
1125
1130
|
"- 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>.",
|
|
1126
1131
|
"- Never retarget a stack PR's GitHub base (gh pr edit --base, a REST base change, or the UI). Moving the bottom to main makes Mergestorm abandon the stack's review unit; setting mg-stack-<n> again does not restore it. If a base was already moved, stop and tell the human; do not run stack land or re-adopt to repair it.",
|
|
1127
|
-
"- mg-stack-<n> is the bottom PR's live parent: merging it into the bottom branch is that PR's conflict repair (bounce-watch rules). Never push to mg-stack-<n> except as the land PR's own repair: once every layer is promoted, the land PR's head is mg-stack-<n> and its live parent is its base (main), so merging main into mg-stack-<n>, a CI fix on it,
|
|
1132
|
+
"- mg-stack-<n> is the bottom PR's live parent: merging it into the bottom branch is that PR's conflict repair (bounce-watch rules). Never push to mg-stack-<n> except as the land PR's own repair: once every layer is promoted, the land PR's head is mg-stack-<n> and its live parent is its base (main), so merging main into mg-stack-<n>, or a CI fix on it, with an ordinary push (no force) is that repair. Fix the land PR only when busy[] shows no Cyclone run on it. Never rebase or merge layer 2+ onto mg-stack-<n> or onto main. mg-park-* is never a merge or rebase target.",
|
|
1128
1133
|
"- mg stack submit prints the base each PR opened with; the bottom moves to mg-stack-<n> right after. stack_status trunkBranch is the current trunk."
|
|
1129
1134
|
].join("\n");
|
|
1130
1135
|
var MCP_STACK_WATCH_INSTRUCTIONS = [
|
|
@@ -1140,19 +1145,21 @@ var MCP_STACK_WATCH_INSTRUCTIONS = [
|
|
|
1140
1145
|
"- 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.",
|
|
1141
1146
|
"- mg-park-* is a frozen GitHub base, not a repair target: never merge or rebase onto mg-park-*.",
|
|
1142
1147
|
"- merge_failed, gh_error, and ci_timeout bounces turn Auto land off, and stack autoLandOff then reads reason bounced with that queue entry's id; any other autoLandOff reason (user, archive, watcher, cancelled, landed) is not a bounce to undo. The watching agent turns Auto land back on with stack_set auto_land: true once it has handled that bounce, at most once per bounce and 3 times per watch; mergestorm-bounce-watch has the conditions.",
|
|
1148
|
+
`- A stack whose layers are all merged into mg-stack-N but which has no open land PR (the branch was deleted, or the land PR cannot be opened) returns attention: blocker "No open land PR for mg-stack-N ...", repair kind land_pr_unavailable. Do not patch or push; tell the human to restore the branch from the closed land PR's head or close the stack.`,
|
|
1143
1149
|
'- While stack autoLandOff reads reason bounced, the watch returns attention: blocker "Auto land off after <kind> bounce", the bounce in issues[], repair kind auto_land_off.',
|
|
1144
1150
|
'- A stack whose review unit was abandoned (a human moved its bottom PR off mg-stack-N) returns attention: blocker "Review unit abandoned: based on <base>", repair kind unit_abandoned. Do not patch or retarget; ask the human whether to re-root it. If at least two PRs remain open, tell them that mg stack adopt owner/repo#<bottom PR> re-roots it on a new review unit. With only one open PR, re-adopting will not create a review unit, so tell them to close and resubmit it to create a fresh stack.',
|
|
1145
1151
|
`- Auto land records why it is waiting (stack autoLandWait: reason, prNumber, headSha, since). The watch returns attention on that PR when the wait cannot clear on its own: a failing check Auto land reads live on GitHub, even while the ledger shows CI green (blocker "CI failed \u2014 Auto land reads a failing check on GitHub at this head", repair kind ci_failure); a check pending for 45 minutes (repair kind ci_pending); no approving Vortex review for 30 minutes with no Vortex run working (repair kind vortex_not_green); and a promote GitHub refused 3 times with merge_failed, after which Auto land stops retrying that head (repair kind promote_failed). It also returns attention when GitHub refuses to let Mergestorm read the head's check runs or commit statuses (blocker "Mergestorm cannot read ... on this head", repair kind ci_unreadable: do not patch or push; tell the human to grant the Mergestorm GitHub App the permission the repair names), and when any other wait reason has held for 45 minutes, a land gate left pending or running on the land PR included (blocker "Auto land has waited <N>m: <reason in plain words>", repair kind auto_land_waiting: read stack_status, fix what a commit on the named branch can fix, otherwise tell the human what Auto land waits for and since when). A more specific blocker on that PR is named first. A new head clears the wait.`,
|
|
1146
1152
|
`- While the stack has a live merge queue entry, stack_wait carries queueWait (the entry's wait reason, how long it has been held, and the stack queued ahead of it) and the status line names it. The watch returns attention, blocker "Merge queue stuck: ..." and repair kind queue_stalled, when the entry (or the entry ahead of it) has been held for 45 minutes without landing or bouncing, or has sat queued for 45 minutes with nothing working on it or ahead of it.`,
|
|
1147
1153
|
'- When no Vortex run works on the head and its review was skipped because auto-review is off (blocker "Vortex auto-review is off, so this head was not reviewed") or because billing could not be confirmed ("Vortex skipped this head: it could not confirm billing"), or it covered only part of the PR ("Vortex reviewed only part of this PR"), the watch returns attention at once with repair kind vortex_skipped. Do not patch for it; the repair names the mention or setting that gets the head reviewed.',
|
|
1148
1154
|
'- A live-head Vortex request_changes handoff returns attention with blocker "Vortex findings need a person" and repair kind vortex_findings; verify the review and inline findings against the live head before repair.',
|
|
1149
|
-
"-
|
|
1155
|
+
"- Cyclone patches a land PR when auto-patch is on for that stack (its override, else the account setting); otherwise the watching agent fixes the land PR's blocker on mg-stack-<n>.",
|
|
1156
|
+
'- More states the watch names: a Vortex review skipped because the PR author is on the ignored authors list or because reviews are turned off for the repository is attention at once with repair kind vortex_skipped; a Vortex retry that is scheduled ("Vortex hit a temporary error on this head and will retry by itself") is a wait listed in issues[], never attention; "Review failed" carries repair kind vortex_failed (comment @mergestorm-vortex review or push a new commit; if it fails again tell the human); a PR open on mg-stack-N that is not a layer of the stack is attention at once, blocker "The land PR cannot be queued: ...", repair kind members_remaining (do not patch or push; close, retarget or adopt that PR).',
|
|
1150
1157
|
"- Use the mergestorm-bounce-watch skill for the fix-then-watch policy."
|
|
1151
1158
|
].join("\n");
|
|
1152
1159
|
|
|
1153
1160
|
// src/server.ts
|
|
1154
1161
|
var MCP_SERVER_NAME = "mergestorm";
|
|
1155
|
-
var MCP_SERVER_VERSION = "0.2.
|
|
1162
|
+
var MCP_SERVER_VERSION = "0.2.12";
|
|
1156
1163
|
var MCP_SERVER_INSTRUCTIONS = [
|
|
1157
1164
|
MCP_STACK_DONE_INSTRUCTIONS,
|
|
1158
1165
|
MCP_PR_LOOP_INSTRUCTIONS,
|
|
@@ -1181,7 +1188,7 @@ function ok(payload) {
|
|
|
1181
1188
|
};
|
|
1182
1189
|
}
|
|
1183
1190
|
function fail(err) {
|
|
1184
|
-
const message = err instanceof CommandError11 ? err.code ? `Mergestorm request failed (${err.code}): ${err.message}.` : err.message : err instanceof Error ? err.message : String(err);
|
|
1191
|
+
const message = err instanceof CommandError11 ? err.code ? `Mergestorm request failed (${err.code}${err.reason ? `, reason ${err.reason}` : ""}): ${err.message}.` : err.message : err instanceof Error ? err.message : String(err);
|
|
1185
1192
|
return {
|
|
1186
1193
|
isError: true,
|
|
1187
1194
|
content: [{ type: "text", text: message }]
|
|
@@ -1299,7 +1306,7 @@ function createMergestormMcpServer() {
|
|
|
1299
1306
|
"review_wait_pr",
|
|
1300
1307
|
{
|
|
1301
1308
|
title: "Wait for GitHub PR Vortex review",
|
|
1302
|
-
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.",
|
|
1309
|
+
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.",
|
|
1303
1310
|
inputSchema: {
|
|
1304
1311
|
owner: z3.string().min(1),
|
|
1305
1312
|
repo: z3.string().min(1),
|
|
@@ -1360,7 +1367,7 @@ function createMergestormMcpServer() {
|
|
|
1360
1367
|
"stack_adopt",
|
|
1361
1368
|
{
|
|
1362
1369
|
title: "Adopt PR stack",
|
|
1363
|
-
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
|
|
1370
|
+
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 needs the infrastructure GitHub App on the repository: Mergestorm Surge, or Cyclone on accounts not yet moved to Surge. Stacks, the merge queue and Auto land run through that App; Cyclone is the auto-patch App. Without it the API returns cyclone_not_connected or cyclone_not_installed; the message, and error.reason starting with surge_ when Surge is the one to install, say which App is missing \u2014 do not retry, tell the human what the message says. 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,
|
|
1364
1371
|
inputSchema: stackAdoptSchema,
|
|
1365
1372
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false }
|
|
1366
1373
|
},
|
|
@@ -1471,7 +1478,7 @@ function createMergestormMcpServer() {
|
|
|
1471
1478
|
"settings_get",
|
|
1472
1479
|
{
|
|
1473
1480
|
title: "Get settings",
|
|
1474
|
-
description: "Read the Bearer /api/v1/settings toggles, including auto_patch_enabled, auto_land_settle_seconds (how long Auto land waits before queueing), merge_queue_batch_enabled and merge_queue_batch_size (whether the merge queue tests several pull requests on one CI run, and how many), and
|
|
1481
|
+
description: "Read the Bearer /api/v1/settings toggles, including auto_patch_enabled, auto_land_settle_seconds (how long Auto land waits before queueing), merge_queue_batch_enabled and merge_queue_batch_size (whether the merge queue tests several pull requests on one CI run, and how many), cyclone_connected (the Cyclone auto-patch App), and, when the server sends it, stacks_ready (whether stacks, the merge queue and Auto land can run for this account, through Mergestorm Surge or, on accounts not yet moved, Cyclone).",
|
|
1475
1482
|
annotations: { readOnlyHint: true }
|
|
1476
1483
|
},
|
|
1477
1484
|
async () => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mergestorm-mcp",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.12",
|
|
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.40",
|
|
33
33
|
"zod": "^3.24.2"
|
|
34
34
|
},
|
|
35
35
|
"devDependencies": {
|