mergestorm-mcp 0.2.29 → 0.2.31

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 CHANGED
@@ -18,7 +18,7 @@ Every tool declares MCP `annotations` for read-only access, destructive changes,
18
18
 
19
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. Then call `issue_review_wait` and open the pull request, with `Fixes #<number>` in the commit or pull request body, only once the issue is approved.
20
20
 
21
- `issue_review_wait({ owner, repo, number, timeout_s? })` reads the Vortex review of an issue: `state` (`approved`, `needs_answers`, `pending`, `unreviewed`, `capped` or `off`), the round, the verdict, up to 8 points, the review comment link and `next`, one sentence saying what to do. `timeout_s: 0` reads it once; otherwise it waits while the review is pending, for up to 45s (the default), and returns `pending` when the slice ends. On `needs_answers` or `unreviewed`, answer the points in a comment on the issue and call it again; Vortex reviews each reply. On `capped`, a person approves with an issue comment whose first line is `mergestorm-issue: approve`. `stack_adopt` and `stack_publish` refuse a pull request whose linked issue is not approved with `issue_not_approved`, naming each issue's state and points.
21
+ `issue_review_wait({ owner, repo, number, timeout_s? })` reads the Vortex review of an issue: `state` (`approved`, `needs_answers`, `pending`, `unreviewed`, `capped` or `off`), the round, the verdict, up to 8 points, the review comment link and `next`, one sentence saying what to do. `timeout_s: 0` reads it once; otherwise it waits while the review is pending, for up to 45s (the default), and returns `pending` when the slice ends. On `needs_answers`, reply once in a comment on the issue, accepting each blocking point's suggested fix or saying why not (notes do not block; the PR review checks them against the code), and call it again; Vortex re-checks only the blocking points, once, and approves the issue. On `unreviewed`, comment on the issue to start a review. A person can approve early with an issue comment whose first line is `mergestorm-issue: approve`. `stack_publish` refuses with `issue_not_approved`, naming each issue's state and points, when it would open a pull request, or register one no stack tracks, whose linked issue is not approved. `stack_adopt` does not check issues.
22
22
 
23
23
  The agent tools start work on the user's own pull requests and repositories (`POST /api/v1/agents/...`):
24
24
 
package/dist/index.js CHANGED
@@ -138,7 +138,7 @@ async function issueCreate(input, cfg, deps = {}) {
138
138
  cfg
139
139
  );
140
140
  return {
141
- summary: `${issue.created ? "Opened" : "Already open:"} ${target2.owner}/${target2.repo}#${issue.number} ${issue.url}. Call issue_review_wait for #${issue.number} and open the pull request only once it is approved, with "Fixes #${issue.number}" in the commit or pull request body.`,
141
+ summary: `${issue.created ? "Opened" : "Already open:"} ${target2.owner}/${target2.repo}#${issue.number} ${issue.url}. Call issue_review_wait for #${issue.number} and open the pull request once it says you may, with "Fixes #${issue.number}" in the commit or pull request body.`,
142
142
  data: { owner: target2.owner, repo: target2.repo, ...issue }
143
143
  };
144
144
  } catch (err) {
@@ -161,7 +161,10 @@ async function issueCreate(input, cfg, deps = {}) {
161
161
  import {
162
162
  CommandError as CommandError3,
163
163
  IssueReviewTimeoutError,
164
+ NO_LINKED_ISSUE,
164
165
  getIssueReview,
166
+ issueReviewStallAdvice,
167
+ issueReviewWaitAfter,
165
168
  loadConfig as loadConfig2,
166
169
  pollIssueReview
167
170
  } from "mergestorm/client";
@@ -171,20 +174,47 @@ var issueReviewWaitSchema = {
171
174
  owner: z2.string().trim().regex(/^[A-Za-z0-9_.-]{1,100}$/),
172
175
  repo: z2.string().trim().regex(/^[A-Za-z0-9_.-]{1,100}$/),
173
176
  number: z2.number().int().min(1),
174
- timeout_s: z2.number().min(0).max(ISSUE_REVIEW_WAIT_MAX_S).optional()
177
+ timeout_s: z2.number().min(0).max(ISSUE_REVIEW_WAIT_MAX_S).optional(),
178
+ after_round: z2.number().int().min(1).optional(),
179
+ waiting_since: z2.string().datetime({ offset: true }).optional()
175
180
  };
176
181
  var inputSchema2 = z2.object(issueReviewWaitSchema);
182
+ function blockingCount(status) {
183
+ return status.points.filter((point) => point.blocking !== false).length;
184
+ }
177
185
  function issueReviewSummary(status) {
178
186
  const ref = `${status.owner}/${status.repo}#${status.number}`;
179
187
  const round = status.round ? ` (round ${status.round} of ${status.maxRounds})` : "";
180
- const head = status.state === "approved" ? `${ref} is approved by ${status.approvedBy === "human" ? "a person" : "Vortex"}; you may open the pull request.` : status.state === "off" ? `Issue reviews are off for ${status.owner}/${status.repo}; ${ref} needs no approval.` : status.state === "pending" ? `${ref}: Vortex is still reviewing${round}; call issue_review_wait again.` : `${ref}: ${status.state}${round}, ${status.points.length} point${status.points.length === 1 ? "" : "s"} to answer. Do not open the pull request yet.`;
188
+ const wait = status.wait;
189
+ if (wait.status === "stalled") {
190
+ const since = wait.minutes !== null ? ` for ${wait.minutes} min` : "";
191
+ return `${ref}: stalled, no new Vortex round${since} (reason: ${wait.reason}). ${issueReviewStallAdvice(wait.reason, status.mode)} Tell the human once and stop; do not call issue_review_wait in a loop.`;
192
+ }
193
+ if (wait.status === "answered_waiting_for_round" && wait.reason === "answer_not_seen") {
194
+ return `${ref}: Vortex has not seen an answer after round ${wait.verdictRound ?? status.round} yet. If you have not posted your answer, post it now in one issue comment; then call issue_review_wait again with next_args. Do not poll GitHub.`;
195
+ }
196
+ if (wait.status === "answered_waiting_for_round") {
197
+ return `${ref}: Vortex has an answer after round ${wait.verdictRound ?? status.round}; the next round has not finished. Call issue_review_wait again with next_args; do not poll GitHub.`;
198
+ }
199
+ if (wait.status === "round_running" && status.state !== "pending") {
200
+ return `${ref}: Vortex is running a round${round}; call issue_review_wait again with next_args.`;
201
+ }
202
+ const head = status.state === "approved" ? status.approvedBy === null ? `The Vortex review of ${ref} failed, so it does not hold the pull request back; you may open it.` : `${ref} is approved by ${status.approvedBy === "human" ? "a person" : "Vortex"}; you may open the pull request.` : status.state === "off" ? `Issue reviews are off for ${status.owner}/${status.repo}; ${ref} needs no approval.` : status.mode === "mention" && status.state === "unreviewed" ? `Issue reviews for ${status.owner}/${status.repo} run on mention; ${ref} needs no approval, so you may open the pull request.` : status.state === "pending" ? `${ref}: Vortex is still reviewing${round}; call issue_review_wait again with next_args.` : `${ref}: ${status.state}${round}, ${blockingCount(status)} point${blockingCount(status) === 1 ? "" : "s"} to answer.${status.mode === "auto" ? " Do not open the pull request yet." : ""} After you answer in one issue comment, call issue_review_wait once with next_args.`;
181
203
  return status.next ? `${head} ${status.next}` : head;
182
204
  }
183
- var ISSUE_NOT_APPROVED_HINT = "Do not open the pull request yet: answer the points in a comment on each issue, call issue_review_wait until it returns approved, then try again.";
205
+ var ISSUE_NOT_APPROVED_HINT = "Do not open the pull request yet: reply once in a comment on each issue, accepting each blocking point's suggested fix or saying why not (notes do not block), call issue_review_wait with its next_args until it returns approved or stalled, then try again. Never poll GitHub.";
184
206
  function withIssueReviewHint(err, message) {
185
- return err instanceof CommandError3 && err.code === "issue_not_approved" ? `${message}
207
+ return err instanceof CommandError3 && err.code === "issue_not_approved" && err.reason !== NO_LINKED_ISSUE ? `${message}
186
208
  ${ISSUE_NOT_APPROVED_HINT}` : message;
187
209
  }
210
+ function nextArgs(status, input, waitingSince) {
211
+ const target2 = { owner: input.owner, repo: input.repo, number: input.number };
212
+ const wait = status.wait;
213
+ if (wait.status === "approved" || wait.status === "stalled") return null;
214
+ if (wait.status === "points") return wait.verdictRound ? { ...target2, after_round: wait.verdictRound } : target2;
215
+ const after = input.after_round ?? wait.verdictRound ?? void 0;
216
+ return { ...target2, ...after ? { after_round: after } : {}, waiting_since: waitingSince };
217
+ }
188
218
  function failure(code, message, extra = {}) {
189
219
  return { summary: message, data: { error: { code, message, ...extra } }, isError: true };
190
220
  }
@@ -193,12 +223,22 @@ async function issueReviewWait(input, cfg, deps = {}) {
193
223
  if (!parsed.success) {
194
224
  return failure("invalid_input", `issue_review_wait needs owner, repo, a positive issue number and timeout_s from 0 to ${ISSUE_REVIEW_WAIT_MAX_S}`);
195
225
  }
196
- const { owner, repo: repo2, number, timeout_s } = parsed.data;
226
+ const { owner, repo: repo2, number, timeout_s, after_round, waiting_since } = parsed.data;
197
227
  const target2 = { owner, repo: repo2, number };
228
+ const now = deps.now ?? Date.now;
229
+ const since = Math.min(waiting_since ? Date.parse(waiting_since) : now(), now());
230
+ const sinceIso = new Date(since).toISOString();
231
+ const withNext = (status) => {
232
+ const next = nextArgs(status, parsed.data, sinceIso);
233
+ return { summary: issueReviewSummary(status), data: { ...status, ...next ? { next_args: next } : {} } };
234
+ };
198
235
  try {
199
236
  const resolved = cfg ?? await loadConfig2();
200
- const status = timeout_s === 0 ? await (deps.getIssueReview ?? getIssueReview)(resolved, target2) : await (deps.pollIssueReview ?? pollIssueReview)(resolved, target2, {
237
+ const read = timeout_s === 0 ? await (deps.getIssueReview ?? getIssueReview)(resolved, target2) : null;
238
+ const status = timeout_s === 0 ? read && { ...read, wait: issueReviewWaitAfter(read, { afterRound: after_round, waitedMs: now() - since, nowMs: now() }) } : await (deps.pollIssueReview ?? pollIssueReview)(resolved, target2, {
201
239
  timeoutMs: Math.max(1, Math.round((timeout_s ?? ISSUE_REVIEW_WAIT_MAX_S) * 1e3)),
240
+ ...after_round !== void 0 ? { afterRound: after_round } : {},
241
+ waitingSince: since,
202
242
  ...deps.pollIntervalMs !== void 0 ? { intervalMs: deps.pollIntervalMs } : {}
203
243
  });
204
244
  if (!status) {
@@ -207,11 +247,9 @@ async function issueReviewWait(input, cfg, deps = {}) {
207
247
  "This Mergestorm server does not review issues yet. Open the pull request as before."
208
248
  );
209
249
  }
210
- return { summary: issueReviewSummary(status), data: { ...status } };
250
+ return withNext(status);
211
251
  } catch (err) {
212
- if (err instanceof IssueReviewTimeoutError) {
213
- return { summary: issueReviewSummary(err.lastStatus), data: { ...err.lastStatus } };
214
- }
252
+ if (err instanceof IssueReviewTimeoutError) return withNext(err.lastStatus);
215
253
  const message = err instanceof Error ? err.message : String(err);
216
254
  return failure(
217
255
  err instanceof CommandError3 ? err.code ?? "issue_review_wait_failed" : "issue_review_wait_failed",
@@ -1334,7 +1372,8 @@ var ARG_BY_KEY = {
1334
1372
  merge_queue_batch_size: "merge_queue_batch_size",
1335
1373
  merge_queue_lands_alone_paths: "merge_queue_lands_alone_paths",
1336
1374
  merge_queue_overdrive: "merge_queue_overdrive",
1337
- issue_review_enabled: "issue_review"
1375
+ issue_review_enabled: "issue_review",
1376
+ issue_auto_review_enabled: "issue_auto_review"
1338
1377
  };
1339
1378
  function parseRepo(raw) {
1340
1379
  const match = typeof raw === "string" ? /^([A-Za-z0-9_.-]+)\/([A-Za-z0-9_.-]+)$/.exec(raw.trim()) : null;
@@ -1868,7 +1907,7 @@ var MCP_HOSTED_SERVER_INSTRUCTIONS = [
1868
1907
  // src/server.ts
1869
1908
  var BATCH_SIZE_ROW = BEARER_SETTINGS2.find((row) => row.key === "merge_queue_batch_size");
1870
1909
  var MCP_SERVER_NAME = "mergestorm";
1871
- var MCP_SERVER_VERSION = "0.2.29";
1910
+ var MCP_SERVER_VERSION = "0.2.31";
1872
1911
  var MCP_TOOL_NAMES = [
1873
1912
  "whoami",
1874
1913
  "credits",
@@ -1953,7 +1992,7 @@ var HOSTED_REVIEW_WAIT_PR_DESCRIPTION = "Read the Vortex pass on a GitHub PR; re
1953
1992
  var HOSTED_REVIEW_DISMISS_DESCRIPTION = `Dismiss Vortex findings you checked and found wrong or not actionable, at the PR's live head: head_sha, review_id, finding_ids (the GitHub review comment ids, offdiff-<n> for a body-only finding; preview: true lists them without writing) or scope "review" to dismiss every finding of that review, and a reason of at least 20 characters. A deferral needs evidence_url set to an open tracking issue in the same repository. Refused, with nothing written, when the head moved, an id is unknown or the user's GitHub account lacks write access; retries are idempotent. Returns which gates cleared (gate.vortex.cleared, else gate.vortex.message) and the stack watch.`;
1954
1993
  var HOSTED_STACK_STATUS_DESCRIPTION = "Read one owned stack: its layers, checks and agent state, with the same attention, issues, repair and currentCandidate as stack_wait. Use it to see where a stack stands now; to wait for a change, use stack_wait.";
1955
1994
  var HOSTED_STACK_WAIT_DESCRIPTION = "Wait up to timeout_s (1-45) for a stack to need attention, or read it once with 0. Returns a mergestorm.stack_watch/v1 envelope: attention names the PR, blocker and repair; waiting or in_progress means call again with watch.next.args; watch.done true means stop (landed, closed, archived or gone).";
1956
- var HOSTED_ISSUE_CREATE_DESCRIPTION = "Open a GitHub issue through Mergestorm with no GitHub credentials: the Surge App creates it and names you in the body. Use it for the issue a pull request must have before it is opened, then put Fixes #<number> in the commit or pull request body. owner and repo name the repository. labels must already exist on the repository; an unknown one is refused with unknown_label and nothing is created. Calling it again with the same title returns the open issue it already made (created: false) when that issue is among the newest 100 open issues of the repository; an older one, or two calls at the same time, can still create a duplicate, so check the returned number before retrying after a lost response. Needs write access to the repository through your linked GitHub account and Surge installed on it. After creating it, call issue_review_wait with the returned number and do not open a pull request until the issue is approved: answer any points in an issue comment (Vortex reviews each reply again), then call issue_review_wait again.";
1995
+ var HOSTED_ISSUE_CREATE_DESCRIPTION = "Open a GitHub issue through Mergestorm with no GitHub credentials: the Surge App creates it and names you in the body. Use it for the issue a pull request must have before it is opened, then put Fixes #<number> in the commit or pull request body. owner and repo name the repository. labels must already exist on the repository; an unknown one is refused with unknown_label and nothing is created. Calling it again with the same title returns the open issue it already made (created: false) when that issue is among the newest 100 open issues of the repository; an older one, or two calls at the same time, can still create a duplicate, so check the returned number before retrying after a lost response. Needs write access to the repository through your linked GitHub account and Surge installed on it. After creating it, call issue_review_wait with the returned number and do not open a pull request until the issue is approved: reply once in an issue comment, accepting each blocking point's suggested fix or saying why not (notes do not block; Vortex re-checks the blocking points once and approves), then call issue_review_wait again.";
1957
1996
  function createMergestormMcpServer(options = {}) {
1958
1997
  const { config, hosted = false } = options;
1959
1998
  const chatId = breezeChatId(options.chatId ?? (hosted ? null : process.env.MERGESTORM_CHAT_ID));
@@ -2211,7 +2250,7 @@ function createMergestormMcpServer(options = {}) {
2211
2250
  "issue_create",
2212
2251
  {
2213
2252
  title: "Open GitHub issue",
2214
- description: hosted ? HOSTED_ISSUE_CREATE_DESCRIPTION : "Open a GitHub issue through Mergestorm with no GitHub credentials: the Surge App creates it and names you in the body. Use it for the issue a pull request must have before it is opened, then put Fixes #<number> in the commit or pull request body. Defaults to the origin repository of cwd; pass owner and repo together to name another. labels must already exist on the repository; an unknown one is refused with unknown_label and nothing is created. Calling it again with the same title returns the open issue it already made (created: false) when that issue is among the newest 100 open issues of the repository; an older one, or two calls at the same time, can still create a duplicate, so check the returned number before retrying after a lost response. Needs write access to the repository through your linked GitHub account and Surge installed on it. After creating it, call issue_review_wait with the returned number and do not open a pull request until the issue is approved: answer any points in an issue comment (Vortex reviews each reply again), then call issue_review_wait again.",
2253
+ description: hosted ? HOSTED_ISSUE_CREATE_DESCRIPTION : "Open a GitHub issue through Mergestorm with no GitHub credentials: the Surge App creates it and names you in the body. Use it for the issue a pull request must have before it is opened, then put Fixes #<number> in the commit or pull request body. Defaults to the origin repository of cwd; pass owner and repo together to name another. labels must already exist on the repository; an unknown one is refused with unknown_label and nothing is created. Calling it again with the same title returns the open issue it already made (created: false) when that issue is among the newest 100 open issues of the repository; an older one, or two calls at the same time, can still create a duplicate, so check the returned number before retrying after a lost response. Needs write access to the repository through your linked GitHub account and Surge installed on it. After creating it, call issue_review_wait with the returned number and do not open a pull request until the issue is approved: reply once in an issue comment, accepting each blocking point's suggested fix or saying why not (notes do not block; Vortex re-checks the blocking points once and approves), then call issue_review_wait again.",
2215
2254
  inputSchema: hosted ? hostedIssueCreateSchema : issueCreateSchema,
2216
2255
  annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true }
2217
2256
  },
@@ -2227,7 +2266,7 @@ function createMergestormMcpServer(options = {}) {
2227
2266
  "issue_review_wait",
2228
2267
  {
2229
2268
  title: "Wait for issue review",
2230
- description: "Read the Vortex review of a GitHub issue, which on repositories with issue reviews on must approve the issue before any pull request is opened for it. Returns the review state (approved, needs_answers, pending, unreviewed, capped or off), the round, the verdict, the summary, up to 8 points, the review comment link and next, one sentence saying what to do. timeout_s: 0 reads it once; otherwise it waits while the review is pending for one slice of up to 45s (the default) and returns pending when the slice ends, so call again. Open the pull request only on approved or off. On needs_answers or unreviewed, answer every point in a comment on the issue, then call again: Vortex reviews each reply, up to maxRounds rounds. On capped, a person approves with an issue comment whose first line is mergestorm-issue: approve.",
2269
+ description: "Wait for the next Vortex verdict on a GitHub issue. Returns the review state (approved, needs_answers, pending, unreviewed or off), the round, the verdict, the summary, up to 8 points, the review comment link, next (one sentence saying what to do), wait.status and, while there is more to wait for, next_args. wait.status is answered_waiting_for_round (Vortex has an answer after the last verdict and the next round has not finished; with wait.reason answer_not_seen Vortex has no answer after the verdict yet, so post yours), round_running, points (a verdict with points to answer), approved (approved, off, a failed review, the round cap, or unreviewed on a mention-mode repo) or stalled (no round started: about 10 minutes after a queued answer, at once when an answer failed or was skipped without starting a round, about 10 minutes into the wait when no review has started at all, or about 10 minutes after the verdict with no answer seen once the wait is at least 2 minutes old; wait.reason says why). timeout_s: 0 reads it once; otherwise it waits for one slice of up to 45s (the default). Call it again with exactly next_args: after_round is the round whose verdict you already saw, so the stale verdict is never returned again, and waiting_since carries the stall clock across slices. Open the pull request only on approved, or when the summary says the issue needs no approval (issue reviews off, or unreviewed on a mention-mode repo). On points, answer in ONE new comment on the issue as the author or a maintainer: for each blocking point, accept its suggested fix or say why not (notes do not block). On a mention-mode repo the comment must tag @mergestorm-vortex and the next round starts at once; on an auto-mode repo it starts about 90s later. Editing the body or an earlier comment starts nothing. Then call once with next_args. On stalled, follow the summary's advice for wait.reason and do not loop on this tool. Never poll GitHub or loop on this tool beyond next_args. A person can also approve with an issue comment whose first line is mergestorm-issue: approve.",
2231
2270
  inputSchema: issueReviewWaitSchema,
2232
2271
  annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true }
2233
2272
  },
@@ -2422,7 +2461,7 @@ function createMergestormMcpServer(options = {}) {
2422
2461
  "repo_settings_get",
2423
2462
  {
2424
2463
  title: "Get repository settings",
2425
- description: "Read the repository-level Auto patch, Auto review, Auto land, merge queue (merge_queue_batch_enabled, merge_queue_batch_size, merge_queue_lands_alone_paths) and merge queue overdrive values for the repositories connected to this account. auto_patch_enabled, auto_review_enabled, auto_land_default, merge_queue_batch_enabled and merge_queue_overdrive are true, false, or null; merge_queue_batch_size is a whole number or null; merge_queue_lands_alone_paths is a list of path patterns 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. merge_queue_overdrive is the repository value over the account default; while it resolves on, that repository batches up to 10 queued pull requests. issue_review_enabled is true, false, or null: the repository value for Vortex issue reviews over the account default (settings_get issue_review_enabled). Pass repo as owner/repo for one repository, or omit it for all.",
2464
+ description: "Read the repository-level Auto patch, Auto review, Auto land, merge queue (merge_queue_batch_enabled, merge_queue_batch_size, merge_queue_lands_alone_paths) and merge queue overdrive values for the repositories connected to this account. auto_patch_enabled, auto_review_enabled, auto_land_default, merge_queue_batch_enabled and merge_queue_overdrive are true, false, or null; merge_queue_batch_size is a whole number or null; merge_queue_lands_alone_paths is a list of path patterns 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. merge_queue_overdrive is the repository value over the account default; while it resolves on, that repository batches up to 10 queued pull requests. issue_review_enabled is true, false, or null: the repository value for Vortex issue reviews over the account default (settings_get issue_review_enabled). issue_auto_review_enabled is true, false, or null the same way: while issue reviews are on, true reviews every new issue and reply, and false reviews only when a comment tags @mergestorm-vortex. Pass repo as owner/repo for one repository, or omit it for all.",
2426
2465
  inputSchema: { repo: z8.string().min(3).optional() },
2427
2466
  annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true }
2428
2467
  },
@@ -2438,7 +2477,7 @@ function createMergestormMcpServer(options = {}) {
2438
2477
  "repo_settings_set",
2439
2478
  {
2440
2479
  title: "Update repository settings",
2441
- description: "Set Auto patch (Cyclone), Auto review (Vortex), the Auto land default, or the merge queue settings (merge_queue_batch, merge_queue_batch_size, merge_queue_lands_alone_paths, merge_queue_overdrive) for every stack and pull request in one repository connected to this account, and return the stored result. A value 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. merge_queue_lands_alone_paths is a list of folder prefixes or globs; a queued pull request that changes a matching file is never batched with others, and an empty list keeps only the built-in rule that a stack with more than one open pull request lands alone. merge_queue_overdrive true makes that repository batch up to 10 queued pull requests, even with batching off; false turns overdrive off for that repository whatever the account default is, and null clears the repository value so the account default (settings_get merge_queue_overdrive) applies again. issue_review true or false turns Vortex issue reviews on or off for that repository whatever the account default is; null clears the repository value so the account default (settings_get issue_review_enabled) applies again. Only the account that connected the repository can change it. Never changes account settings.",
2480
+ description: "Set Auto patch (Cyclone), Auto review (Vortex), the Auto land default, or the merge queue settings (merge_queue_batch, merge_queue_batch_size, merge_queue_lands_alone_paths, merge_queue_overdrive) for every stack and pull request in one repository connected to this account, and return the stored result. A value 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. merge_queue_lands_alone_paths is a list of folder prefixes or globs; a queued pull request that changes a matching file is never batched with others, and an empty list keeps only the built-in rule that a stack with more than one open pull request lands alone. merge_queue_overdrive true makes that repository batch up to 10 queued pull requests, even with batching off; false turns overdrive off for that repository whatever the account default is, and null clears the repository value so the account default (settings_get merge_queue_overdrive) applies again. issue_review true or false turns Vortex issue reviews on or off for that repository whatever the account default is; null clears the repository value so the account default (settings_get issue_review_enabled) applies again. issue_auto_review false makes those reviews run only when a comment tags @mergestorm-vortex (opening a PR then needs no issue approval), true reviews every new issue and reply, and null inherits the account default (settings_get issue_auto_review_enabled). Only the account that connected the repository can change it. Never changes account settings.",
2442
2481
  inputSchema: {
2443
2482
  repo: z8.string().min(3),
2444
2483
  auto_patch: z8.boolean().nullable().optional(),
@@ -2448,7 +2487,8 @@ function createMergestormMcpServer(options = {}) {
2448
2487
  merge_queue_batch_size: z8.number().int().min(BATCH_SIZE_ROW.min).max(BATCH_SIZE_ROW.max).nullable().optional(),
2449
2488
  merge_queue_lands_alone_paths: z8.array(z8.string()).nullable().optional(),
2450
2489
  merge_queue_overdrive: z8.boolean().nullable().optional(),
2451
- issue_review: z8.boolean().nullable().optional()
2490
+ issue_review: z8.boolean().nullable().optional(),
2491
+ issue_auto_review: z8.boolean().nullable().optional()
2452
2492
  },
2453
2493
  annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: true }
2454
2494
  },
@@ -2480,7 +2520,7 @@ function createMergestormMcpServer(options = {}) {
2480
2520
  "settings_get",
2481
2521
  {
2482
2522
  title: "Get settings",
2483
- 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), merge_queue_overdrive (the account default for overdrive: the merge queue batches up to 10 queued pull requests per repository; repo_settings_set overrides it per repository), issue_review_enabled (the account default for Vortex issue reviews; repo_settings_set overrides it per repository), cyclone_connected (the Cyclone auto-patch App), and stacks_ready (whether stacks, the merge queue and Auto land can run for this account, which needs Mergestorm Surge).",
2523
+ 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), merge_queue_overdrive (the account default for overdrive: the merge queue batches up to 10 queued pull requests per repository; repo_settings_set overrides it per repository), issue_review_enabled (the account default for Vortex issue reviews; repo_settings_set overrides it per repository), issue_auto_review_enabled (the account default for whether those reviews run on every issue or only when a comment tags @mergestorm-vortex), cyclone_connected (the Cyclone auto-patch App), and stacks_ready (whether stacks, the merge queue and Auto land can run for this account, which needs Mergestorm Surge).",
2484
2524
  annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true }
2485
2525
  },
2486
2526
  async () => {
@@ -2495,7 +2535,7 @@ function createMergestormMcpServer(options = {}) {
2495
2535
  "settings_set",
2496
2536
  {
2497
2537
  title: "Update settings",
2498
- description: "Update writable Bearer /api/v1/settings values and return the stored result. At least one key is required. auto_land_settle_seconds is a whole number of seconds from 15 through 300; it applies to settle clocks that start after the write. merge_queue_batch_enabled turns merge queue batching on or off for the account, merge_queue_batch_size is a whole number from 2 through 10, merge_queue_lands_alone_paths is the list of folder prefixes or globs whose changed files keep a pull request out of a batch, and merge_queue_overdrive is the account default for overdrive: while it resolves on, a repository batches up to 10 queued pull requests, even with batching off, without changing merge_queue_batch_size, and it stays on until turned off (repo_settings_set overrides each of these per repository). issue_review_enabled is the account default for Vortex issue reviews; repo_settings_set issue_review overrides it per repository. cyclone_connected and github_connected are read-only and cannot be set.",
2538
+ description: "Update writable Bearer /api/v1/settings values and return the stored result. At least one key is required. auto_land_settle_seconds is a whole number of seconds from 15 through 300; it applies to settle clocks that start after the write. merge_queue_batch_enabled turns merge queue batching on or off for the account, merge_queue_batch_size is a whole number from 2 through 10, merge_queue_lands_alone_paths is the list of folder prefixes or globs whose changed files keep a pull request out of a batch, and merge_queue_overdrive is the account default for overdrive: while it resolves on, a repository batches up to 10 queued pull requests, even with batching off, without changing merge_queue_batch_size, and it stays on until turned off (repo_settings_set overrides each of these per repository). issue_review_enabled is the account default for Vortex issue reviews; repo_settings_set issue_review overrides it per repository. issue_auto_review_enabled false makes them run only when a comment tags @mergestorm-vortex; repo_settings_set issue_auto_review overrides it per repository. cyclone_connected and github_connected are read-only and cannot be set.",
2499
2539
  inputSchema: {
2500
2540
  ...Object.fromEntries(
2501
2541
  BEARER_SETTINGS2.map((row) => [
@@ -3,7 +3,7 @@ import { type Config } from "mergestorm/client";
3
3
  import { MCP_HOSTED_SERVER_INSTRUCTIONS } from "./pr-loop-instructions.js";
4
4
  export { MCP_HOSTED_SERVER_INSTRUCTIONS };
5
5
  export declare const MCP_SERVER_NAME = "mergestorm";
6
- export declare const MCP_SERVER_VERSION = "0.2.29";
6
+ export declare const MCP_SERVER_VERSION = "0.2.31";
7
7
  export declare const MCP_TOOL_NAMES: readonly ["whoami", "credits", "review_list", "review_submit", "review_wait", "review_wait_pr", "review_dismiss", "review_start_pr", "review_stop_pr", "cyclone_start", "cyclone_job", "issue_create", "issue_review_wait", "stack_adopt", "stack_enqueue", "stack_list", "stack_publish", "stack_publish_status", "stack_restack", "stack_set", "stack_status", "stack_wait", "queue_status", "settings_get", "settings_set", "repo_settings_get", "repo_settings_set", "surge_setup", "list_memories", "search_memory", "remember", "update_memory", "forget_memory", "get_memory_mode", "set_memory_mode", "web_search", "read_web_page"];
8
8
  export declare const MCP_SERVER_INSTRUCTIONS: string;
9
9
  export declare function fail(err: unknown): {
@@ -32,7 +32,7 @@ import { whoami } from "./tools/whoami.js";
32
32
  import { MCP_FINDING_DEFERRAL_RULE, MCP_HOSTED_SERVER_INSTRUCTIONS, MCP_STACK_DONE_INSTRUCTIONS, MCP_PR_LOOP_INSTRUCTIONS, MCP_STACK_BASE_INSTRUCTIONS, MCP_STACK_WATCH_INSTRUCTIONS, } from "./pr-loop-instructions.js";
33
33
  export { MCP_HOSTED_SERVER_INSTRUCTIONS };
34
34
  export const MCP_SERVER_NAME = "mergestorm";
35
- export const MCP_SERVER_VERSION = "0.2.29";
35
+ export const MCP_SERVER_VERSION = "0.2.31";
36
36
  export const MCP_TOOL_NAMES = [
37
37
  "whoami",
38
38
  "credits",
@@ -121,7 +121,7 @@ const HOSTED_REVIEW_WAIT_PR_DESCRIPTION = "Read the Vortex pass on a GitHub PR;
121
121
  const HOSTED_REVIEW_DISMISS_DESCRIPTION = "Dismiss Vortex findings you checked and found wrong or not actionable, at the PR's live head: head_sha, review_id, finding_ids (the GitHub review comment ids, offdiff-<n> for a body-only finding; preview: true lists them without writing) or scope \"review\" to dismiss every finding of that review, and a reason of at least 20 characters. A deferral needs evidence_url set to an open tracking issue in the same repository. Refused, with nothing written, when the head moved, an id is unknown or the user's GitHub account lacks write access; retries are idempotent. Returns which gates cleared (gate.vortex.cleared, else gate.vortex.message) and the stack watch.";
122
122
  const HOSTED_STACK_STATUS_DESCRIPTION = "Read one owned stack: its layers, checks and agent state, with the same attention, issues, repair and currentCandidate as stack_wait. Use it to see where a stack stands now; to wait for a change, use stack_wait.";
123
123
  const HOSTED_STACK_WAIT_DESCRIPTION = "Wait up to timeout_s (1-45) for a stack to need attention, or read it once with 0. Returns a mergestorm.stack_watch/v1 envelope: attention names the PR, blocker and repair; waiting or in_progress means call again with watch.next.args; watch.done true means stop (landed, closed, archived or gone).";
124
- const HOSTED_ISSUE_CREATE_DESCRIPTION = "Open a GitHub issue through Mergestorm with no GitHub credentials: the Surge App creates it and names you in the body. Use it for the issue a pull request must have before it is opened, then put Fixes #<number> in the commit or pull request body. owner and repo name the repository. labels must already exist on the repository; an unknown one is refused with unknown_label and nothing is created. Calling it again with the same title returns the open issue it already made (created: false) when that issue is among the newest 100 open issues of the repository; an older one, or two calls at the same time, can still create a duplicate, so check the returned number before retrying after a lost response. Needs write access to the repository through your linked GitHub account and Surge installed on it. After creating it, call issue_review_wait with the returned number and do not open a pull request until the issue is approved: answer any points in an issue comment (Vortex reviews each reply again), then call issue_review_wait again.";
124
+ const HOSTED_ISSUE_CREATE_DESCRIPTION = "Open a GitHub issue through Mergestorm with no GitHub credentials: the Surge App creates it and names you in the body. Use it for the issue a pull request must have before it is opened, then put Fixes #<number> in the commit or pull request body. owner and repo name the repository. labels must already exist on the repository; an unknown one is refused with unknown_label and nothing is created. Calling it again with the same title returns the open issue it already made (created: false) when that issue is among the newest 100 open issues of the repository; an older one, or two calls at the same time, can still create a duplicate, so check the returned number before retrying after a lost response. Needs write access to the repository through your linked GitHub account and Surge installed on it. After creating it, call issue_review_wait with the returned number and do not open a pull request until the issue is approved: reply once in an issue comment, accepting each blocking point's suggested fix or saying why not (notes do not block; Vortex re-checks the blocking points once and approves), then call issue_review_wait again.";
125
125
  export function createMergestormMcpServer(options = {}) {
126
126
  const { config, hosted = false } = options;
127
127
  const chatId = breezeChatId(options.chatId ?? (hosted ? null : process.env.MERGESTORM_CHAT_ID));
@@ -338,7 +338,7 @@ export function createMergestormMcpServer(options = {}) {
338
338
  title: "Open GitHub issue",
339
339
  description: hosted
340
340
  ? HOSTED_ISSUE_CREATE_DESCRIPTION
341
- : "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. After creating it, call issue_review_wait with the returned number and do not open a pull request until the issue is approved: answer any points in an issue comment (Vortex reviews each reply again), then call issue_review_wait again.",
341
+ : "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. After creating it, call issue_review_wait with the returned number and do not open a pull request until the issue is approved: reply once in an issue comment, accepting each blocking point's suggested fix or saying why not (notes do not block; Vortex re-checks the blocking points once and approves), then call issue_review_wait again.",
342
342
  inputSchema: hosted ? hostedIssueCreateSchema : issueCreateSchema,
343
343
  annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },
344
344
  }, async (args) => {
@@ -351,7 +351,7 @@ export function createMergestormMcpServer(options = {}) {
351
351
  });
352
352
  addTool("issue_review_wait", {
353
353
  title: "Wait for issue review",
354
- description: "Read the Vortex review of a GitHub issue, which on repositories with issue reviews on must approve the issue before any pull request is opened for it. Returns the review state (approved, needs_answers, pending, unreviewed, capped or off), the round, the verdict, the summary, up to 8 points, the review comment link and next, one sentence saying what to do. timeout_s: 0 reads it once; otherwise it waits while the review is pending for one slice of up to 45s (the default) and returns pending when the slice ends, so call again. Open the pull request only on approved or off. On needs_answers or unreviewed, answer every point in a comment on the issue, then call again: Vortex reviews each reply, up to maxRounds rounds. On capped, a person approves with an issue comment whose first line is mergestorm-issue: approve.",
354
+ description: "Wait for the next Vortex verdict on a GitHub issue. Returns the review state (approved, needs_answers, pending, unreviewed or off), the round, the verdict, the summary, up to 8 points, the review comment link, next (one sentence saying what to do), wait.status and, while there is more to wait for, next_args. wait.status is answered_waiting_for_round (Vortex has an answer after the last verdict and the next round has not finished; with wait.reason answer_not_seen Vortex has no answer after the verdict yet, so post yours), round_running, points (a verdict with points to answer), approved (approved, off, a failed review, the round cap, or unreviewed on a mention-mode repo) or stalled (no round started: about 10 minutes after a queued answer, at once when an answer failed or was skipped without starting a round, about 10 minutes into the wait when no review has started at all, or about 10 minutes after the verdict with no answer seen once the wait is at least 2 minutes old; wait.reason says why). timeout_s: 0 reads it once; otherwise it waits for one slice of up to 45s (the default). Call it again with exactly next_args: after_round is the round whose verdict you already saw, so the stale verdict is never returned again, and waiting_since carries the stall clock across slices. Open the pull request only on approved, or when the summary says the issue needs no approval (issue reviews off, or unreviewed on a mention-mode repo). On points, answer in ONE new comment on the issue as the author or a maintainer: for each blocking point, accept its suggested fix or say why not (notes do not block). On a mention-mode repo the comment must tag @mergestorm-vortex and the next round starts at once; on an auto-mode repo it starts about 90s later. Editing the body or an earlier comment starts nothing. Then call once with next_args. On stalled, follow the summary's advice for wait.reason and do not loop on this tool. Never poll GitHub or loop on this tool beyond next_args. A person can also approve with an issue comment whose first line is mergestorm-issue: approve.",
355
355
  inputSchema: issueReviewWaitSchema,
356
356
  annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
357
357
  }, async (args) => {
@@ -513,7 +513,7 @@ export function createMergestormMcpServer(options = {}) {
513
513
  });
514
514
  addTool("repo_settings_get", {
515
515
  title: "Get repository settings",
516
- description: "Read the repository-level Auto patch, Auto review, Auto land, merge queue (merge_queue_batch_enabled, merge_queue_batch_size, merge_queue_lands_alone_paths) and merge queue overdrive values for the repositories connected to this account. auto_patch_enabled, auto_review_enabled, auto_land_default, merge_queue_batch_enabled and merge_queue_overdrive are true, false, or null; merge_queue_batch_size is a whole number or null; merge_queue_lands_alone_paths is a list of path patterns 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. merge_queue_overdrive is the repository value over the account default; while it resolves on, that repository batches up to 10 queued pull requests. issue_review_enabled is true, false, or null: the repository value for Vortex issue reviews over the account default (settings_get issue_review_enabled). Pass repo as owner/repo for one repository, or omit it for all.",
516
+ description: "Read the repository-level Auto patch, Auto review, Auto land, merge queue (merge_queue_batch_enabled, merge_queue_batch_size, merge_queue_lands_alone_paths) and merge queue overdrive values for the repositories connected to this account. auto_patch_enabled, auto_review_enabled, auto_land_default, merge_queue_batch_enabled and merge_queue_overdrive are true, false, or null; merge_queue_batch_size is a whole number or null; merge_queue_lands_alone_paths is a list of path patterns 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. merge_queue_overdrive is the repository value over the account default; while it resolves on, that repository batches up to 10 queued pull requests. issue_review_enabled is true, false, or null: the repository value for Vortex issue reviews over the account default (settings_get issue_review_enabled). issue_auto_review_enabled is true, false, or null the same way: while issue reviews are on, true reviews every new issue and reply, and false reviews only when a comment tags @mergestorm-vortex. Pass repo as owner/repo for one repository, or omit it for all.",
517
517
  inputSchema: { repo: z.string().min(3).optional() },
518
518
  annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
519
519
  }, async (args) => {
@@ -526,7 +526,7 @@ export function createMergestormMcpServer(options = {}) {
526
526
  });
527
527
  addTool("repo_settings_set", {
528
528
  title: "Update repository settings",
529
- description: "Set Auto patch (Cyclone), Auto review (Vortex), the Auto land default, or the merge queue settings (merge_queue_batch, merge_queue_batch_size, merge_queue_lands_alone_paths, merge_queue_overdrive) for every stack and pull request in one repository connected to this account, and return the stored result. A value 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. merge_queue_lands_alone_paths is a list of folder prefixes or globs; a queued pull request that changes a matching file is never batched with others, and an empty list keeps only the built-in rule that a stack with more than one open pull request lands alone. merge_queue_overdrive true makes that repository batch up to 10 queued pull requests, even with batching off; false turns overdrive off for that repository whatever the account default is, and null clears the repository value so the account default (settings_get merge_queue_overdrive) applies again. issue_review true or false turns Vortex issue reviews on or off for that repository whatever the account default is; null clears the repository value so the account default (settings_get issue_review_enabled) applies again. Only the account that connected the repository can change it. Never changes account settings.",
529
+ description: "Set Auto patch (Cyclone), Auto review (Vortex), the Auto land default, or the merge queue settings (merge_queue_batch, merge_queue_batch_size, merge_queue_lands_alone_paths, merge_queue_overdrive) for every stack and pull request in one repository connected to this account, and return the stored result. A value 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. merge_queue_lands_alone_paths is a list of folder prefixes or globs; a queued pull request that changes a matching file is never batched with others, and an empty list keeps only the built-in rule that a stack with more than one open pull request lands alone. merge_queue_overdrive true makes that repository batch up to 10 queued pull requests, even with batching off; false turns overdrive off for that repository whatever the account default is, and null clears the repository value so the account default (settings_get merge_queue_overdrive) applies again. issue_review true or false turns Vortex issue reviews on or off for that repository whatever the account default is; null clears the repository value so the account default (settings_get issue_review_enabled) applies again. issue_auto_review false makes those reviews run only when a comment tags @mergestorm-vortex (opening a PR then needs no issue approval), true reviews every new issue and reply, and null inherits the account default (settings_get issue_auto_review_enabled). Only the account that connected the repository can change it. Never changes account settings.",
530
530
  inputSchema: {
531
531
  repo: z.string().min(3),
532
532
  auto_patch: z.boolean().nullable().optional(),
@@ -537,6 +537,7 @@ export function createMergestormMcpServer(options = {}) {
537
537
  merge_queue_lands_alone_paths: z.array(z.string()).nullable().optional(),
538
538
  merge_queue_overdrive: z.boolean().nullable().optional(),
539
539
  issue_review: z.boolean().nullable().optional(),
540
+ issue_auto_review: z.boolean().nullable().optional(),
540
541
  },
541
542
  annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: true },
542
543
  }, async (args) => {
@@ -562,7 +563,7 @@ export function createMergestormMcpServer(options = {}) {
562
563
  });
563
564
  addTool("settings_get", {
564
565
  title: "Get settings",
565
- 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), merge_queue_overdrive (the account default for overdrive: the merge queue batches up to 10 queued pull requests per repository; repo_settings_set overrides it per repository), issue_review_enabled (the account default for Vortex issue reviews; repo_settings_set overrides it per repository), cyclone_connected (the Cyclone auto-patch App), and stacks_ready (whether stacks, the merge queue and Auto land can run for this account, which needs Mergestorm Surge).",
566
+ 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), merge_queue_overdrive (the account default for overdrive: the merge queue batches up to 10 queued pull requests per repository; repo_settings_set overrides it per repository), issue_review_enabled (the account default for Vortex issue reviews; repo_settings_set overrides it per repository), issue_auto_review_enabled (the account default for whether those reviews run on every issue or only when a comment tags @mergestorm-vortex), cyclone_connected (the Cyclone auto-patch App), and stacks_ready (whether stacks, the merge queue and Auto land can run for this account, which needs Mergestorm Surge).",
566
567
  annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
567
568
  }, async () => {
568
569
  try {
@@ -574,7 +575,7 @@ export function createMergestormMcpServer(options = {}) {
574
575
  });
575
576
  addTool("settings_set", {
576
577
  title: "Update settings",
577
- description: "Update writable Bearer /api/v1/settings values and return the stored result. At least one key is required. auto_land_settle_seconds is a whole number of seconds from 15 through 300; it applies to settle clocks that start after the write. merge_queue_batch_enabled turns merge queue batching on or off for the account, merge_queue_batch_size is a whole number from 2 through 10, merge_queue_lands_alone_paths is the list of folder prefixes or globs whose changed files keep a pull request out of a batch, and merge_queue_overdrive is the account default for overdrive: while it resolves on, a repository batches up to 10 queued pull requests, even with batching off, without changing merge_queue_batch_size, and it stays on until turned off (repo_settings_set overrides each of these per repository). issue_review_enabled is the account default for Vortex issue reviews; repo_settings_set issue_review overrides it per repository. cyclone_connected and github_connected are read-only and cannot be set.",
578
+ description: "Update writable Bearer /api/v1/settings values and return the stored result. At least one key is required. auto_land_settle_seconds is a whole number of seconds from 15 through 300; it applies to settle clocks that start after the write. merge_queue_batch_enabled turns merge queue batching on or off for the account, merge_queue_batch_size is a whole number from 2 through 10, merge_queue_lands_alone_paths is the list of folder prefixes or globs whose changed files keep a pull request out of a batch, and merge_queue_overdrive is the account default for overdrive: while it resolves on, a repository batches up to 10 queued pull requests, even with batching off, without changing merge_queue_batch_size, and it stays on until turned off (repo_settings_set overrides each of these per repository). issue_review_enabled is the account default for Vortex issue reviews; repo_settings_set issue_review overrides it per repository. issue_auto_review_enabled false makes them run only when a comment tags @mergestorm-vortex; repo_settings_set issue_auto_review overrides it per repository. cyclone_connected and github_connected are read-only and cannot be set.",
578
579
  inputSchema: {
579
580
  ...Object.fromEntries(BEARER_SETTINGS.map((row) => [row.key,
580
581
  ("kind" in row
@@ -31,7 +31,7 @@ export async function issueCreate(input, cfg, deps = {}) {
31
31
  }
32
32
  const issue = await (deps.openStackIssue ?? openStackIssue)({ owner: target.owner, repo: target.repo, title, body: body ?? "", labels: labels ?? [] }, cfg);
33
33
  return {
34
- summary: `${issue.created ? "Opened" : "Already open:"} ${target.owner}/${target.repo}#${issue.number} ${issue.url}. Call issue_review_wait for #${issue.number} and open the pull request only once it is approved, with "Fixes #${issue.number}" in the commit or pull request body.`,
34
+ summary: `${issue.created ? "Opened" : "Already open:"} ${target.owner}/${target.repo}#${issue.number} ${issue.url}. Call issue_review_wait for #${issue.number} and open the pull request once it says you may, with "Fixes #${issue.number}" in the commit or pull request body.`,
35
35
  data: { owner: target.owner, repo: target.repo, ...issue },
36
36
  };
37
37
  }
@@ -7,31 +7,47 @@ export declare const issueReviewWaitSchema: {
7
7
  repo: z.ZodString;
8
8
  number: z.ZodNumber;
9
9
  timeout_s: z.ZodOptional<z.ZodNumber>;
10
+ after_round: z.ZodOptional<z.ZodNumber>;
11
+ waiting_since: z.ZodOptional<z.ZodString>;
10
12
  };
11
13
  declare const inputSchema: z.ZodObject<{
12
14
  owner: z.ZodString;
13
15
  repo: z.ZodString;
14
16
  number: z.ZodNumber;
15
17
  timeout_s: z.ZodOptional<z.ZodNumber>;
18
+ after_round: z.ZodOptional<z.ZodNumber>;
19
+ waiting_since: z.ZodOptional<z.ZodString>;
16
20
  }, "strip", z.ZodTypeAny, {
17
21
  number: number;
18
22
  owner: string;
19
23
  repo: string;
20
24
  timeout_s?: number | undefined;
25
+ after_round?: number | undefined;
26
+ waiting_since?: string | undefined;
21
27
  }, {
22
28
  number: number;
23
29
  owner: string;
24
30
  repo: string;
25
31
  timeout_s?: number | undefined;
32
+ after_round?: number | undefined;
33
+ waiting_since?: string | undefined;
26
34
  }>;
27
35
  export type IssueReviewWaitInput = z.infer<typeof inputSchema>;
28
36
  export type IssueReviewWaitDeps = {
29
37
  getIssueReview?: typeof getIssueReview;
30
38
  pollIssueReview?: typeof pollIssueReview;
31
39
  pollIntervalMs?: number;
40
+ now?: () => number;
41
+ };
42
+ export type IssueReviewWaitNextArgs = {
43
+ owner: string;
44
+ repo: string;
45
+ number: number;
46
+ after_round?: number;
47
+ waiting_since?: string;
32
48
  };
33
49
  export declare function issueReviewSummary(status: IssueReviewStatus): string;
34
- export declare const ISSUE_NOT_APPROVED_HINT = "Do not open the pull request yet: answer the points in a comment on each issue, call issue_review_wait until it returns approved, then try again.";
50
+ export declare const ISSUE_NOT_APPROVED_HINT = "Do not open the pull request yet: reply once in a comment on each issue, accepting each blocking point's suggested fix or saying why not (notes do not block), call issue_review_wait with its next_args until it returns approved or stalled, then try again. Never poll GitHub.";
35
51
  export declare function withIssueReviewHint(err: unknown, message: string): string;
36
52
  export declare function issueReviewWait(input: IssueReviewWaitInput, cfg?: Config, deps?: IssueReviewWaitDeps): Promise<ToolPayload>;
37
53
  export {};
@@ -1,4 +1,4 @@
1
- import { CommandError, IssueReviewTimeoutError, getIssueReview, loadConfig, pollIssueReview, } from "mergestorm/client";
1
+ import { CommandError, IssueReviewTimeoutError, NO_LINKED_ISSUE, getIssueReview, issueReviewStallAdvice, issueReviewWaitAfter, loadConfig, pollIssueReview, } from "mergestorm/client";
2
2
  import { z } from "zod";
3
3
  export const ISSUE_REVIEW_WAIT_MAX_S = 45;
4
4
  export const issueReviewWaitSchema = {
@@ -6,23 +6,56 @@ export const issueReviewWaitSchema = {
6
6
  repo: z.string().trim().regex(/^[A-Za-z0-9_.-]{1,100}$/),
7
7
  number: z.number().int().min(1),
8
8
  timeout_s: z.number().min(0).max(ISSUE_REVIEW_WAIT_MAX_S).optional(),
9
+ after_round: z.number().int().min(1).optional(),
10
+ waiting_since: z.string().datetime({ offset: true }).optional(),
9
11
  };
10
12
  const inputSchema = z.object(issueReviewWaitSchema);
13
+ function blockingCount(status) {
14
+ return status.points.filter((point) => point.blocking !== false).length;
15
+ }
11
16
  export function issueReviewSummary(status) {
12
17
  const ref = `${status.owner}/${status.repo}#${status.number}`;
13
18
  const round = status.round ? ` (round ${status.round} of ${status.maxRounds})` : "";
19
+ const wait = status.wait;
20
+ if (wait.status === "stalled") {
21
+ const since = wait.minutes !== null ? ` for ${wait.minutes} min` : "";
22
+ return `${ref}: stalled, no new Vortex round${since} (reason: ${wait.reason}). ${issueReviewStallAdvice(wait.reason, status.mode)} Tell the human once and stop; do not call issue_review_wait in a loop.`;
23
+ }
24
+ if (wait.status === "answered_waiting_for_round" && wait.reason === "answer_not_seen") {
25
+ return `${ref}: Vortex has not seen an answer after round ${wait.verdictRound ?? status.round} yet. If you have not posted your answer, post it now in one issue comment; then call issue_review_wait again with next_args. Do not poll GitHub.`;
26
+ }
27
+ if (wait.status === "answered_waiting_for_round") {
28
+ return `${ref}: Vortex has an answer after round ${wait.verdictRound ?? status.round}; the next round has not finished. Call issue_review_wait again with next_args; do not poll GitHub.`;
29
+ }
30
+ if (wait.status === "round_running" && status.state !== "pending") {
31
+ return `${ref}: Vortex is running a round${round}; call issue_review_wait again with next_args.`;
32
+ }
14
33
  const head = status.state === "approved"
15
- ? `${ref} is approved by ${status.approvedBy === "human" ? "a person" : "Vortex"}; you may open the pull request.`
34
+ ? status.approvedBy === null
35
+ ? `The Vortex review of ${ref} failed, so it does not hold the pull request back; you may open it.`
36
+ : `${ref} is approved by ${status.approvedBy === "human" ? "a person" : "Vortex"}; you may open the pull request.`
16
37
  : status.state === "off"
17
38
  ? `Issue reviews are off for ${status.owner}/${status.repo}; ${ref} needs no approval.`
18
- : status.state === "pending"
19
- ? `${ref}: Vortex is still reviewing${round}; call issue_review_wait again.`
20
- : `${ref}: ${status.state}${round}, ${status.points.length} point${status.points.length === 1 ? "" : "s"} to answer. Do not open the pull request yet.`;
39
+ : status.mode === "mention" && status.state === "unreviewed"
40
+ ? `Issue reviews for ${status.owner}/${status.repo} run on mention; ${ref} needs no approval, so you may open the pull request.`
41
+ : status.state === "pending"
42
+ ? `${ref}: Vortex is still reviewing${round}; call issue_review_wait again with next_args.`
43
+ : `${ref}: ${status.state}${round}, ${blockingCount(status)} point${blockingCount(status) === 1 ? "" : "s"} to answer.${status.mode === "auto" ? " Do not open the pull request yet." : ""} After you answer in one issue comment, call issue_review_wait once with next_args.`;
21
44
  return status.next ? `${head} ${status.next}` : head;
22
45
  }
23
- export const ISSUE_NOT_APPROVED_HINT = "Do not open the pull request yet: answer the points in a comment on each issue, call issue_review_wait until it returns approved, then try again.";
46
+ export const ISSUE_NOT_APPROVED_HINT = "Do not open the pull request yet: reply once in a comment on each issue, accepting each blocking point's suggested fix or saying why not (notes do not block), call issue_review_wait with its next_args until it returns approved or stalled, then try again. Never poll GitHub.";
24
47
  export function withIssueReviewHint(err, message) {
25
- return err instanceof CommandError && err.code === "issue_not_approved" ? `${message}\n${ISSUE_NOT_APPROVED_HINT}` : message;
48
+ return err instanceof CommandError && err.code === "issue_not_approved" && err.reason !== NO_LINKED_ISSUE ? `${message}\n${ISSUE_NOT_APPROVED_HINT}` : message;
49
+ }
50
+ function nextArgs(status, input, waitingSince) {
51
+ const target = { owner: input.owner, repo: input.repo, number: input.number };
52
+ const wait = status.wait;
53
+ if (wait.status === "approved" || wait.status === "stalled")
54
+ return null;
55
+ if (wait.status === "points")
56
+ return wait.verdictRound ? { ...target, after_round: wait.verdictRound } : target;
57
+ const after = input.after_round ?? wait.verdictRound ?? undefined;
58
+ return { ...target, ...(after ? { after_round: after } : {}), waiting_since: waitingSince };
26
59
  }
27
60
  function failure(code, message, extra = {}) {
28
61
  return { summary: message, data: { error: { code, message, ...extra } }, isError: true };
@@ -32,25 +65,34 @@ export async function issueReviewWait(input, cfg, deps = {}) {
32
65
  if (!parsed.success) {
33
66
  return failure("invalid_input", `issue_review_wait needs owner, repo, a positive issue number and timeout_s from 0 to ${ISSUE_REVIEW_WAIT_MAX_S}`);
34
67
  }
35
- const { owner, repo, number, timeout_s } = parsed.data;
68
+ const { owner, repo, number, timeout_s, after_round, waiting_since } = parsed.data;
36
69
  const target = { owner, repo, number };
70
+ const now = deps.now ?? Date.now;
71
+ const since = Math.min(waiting_since ? Date.parse(waiting_since) : now(), now());
72
+ const sinceIso = new Date(since).toISOString();
73
+ const withNext = (status) => {
74
+ const next = nextArgs(status, parsed.data, sinceIso);
75
+ return { summary: issueReviewSummary(status), data: { ...status, ...(next ? { next_args: next } : {}) } };
76
+ };
37
77
  try {
38
78
  const resolved = cfg ?? (await loadConfig());
79
+ const read = timeout_s === 0 ? await (deps.getIssueReview ?? getIssueReview)(resolved, target) : null;
39
80
  const status = timeout_s === 0
40
- ? await (deps.getIssueReview ?? getIssueReview)(resolved, target)
81
+ ? read && { ...read, wait: issueReviewWaitAfter(read, { afterRound: after_round, waitedMs: now() - since, nowMs: now() }) }
41
82
  : await (deps.pollIssueReview ?? pollIssueReview)(resolved, target, {
42
83
  timeoutMs: Math.max(1, Math.round((timeout_s ?? ISSUE_REVIEW_WAIT_MAX_S) * 1000)),
84
+ ...(after_round !== undefined ? { afterRound: after_round } : {}),
85
+ waitingSince: since,
43
86
  ...(deps.pollIntervalMs !== undefined ? { intervalMs: deps.pollIntervalMs } : {}),
44
87
  });
45
88
  if (!status) {
46
89
  return failure("issue_review_unavailable", "This Mergestorm server does not review issues yet. Open the pull request as before.");
47
90
  }
48
- return { summary: issueReviewSummary(status), data: { ...status } };
91
+ return withNext(status);
49
92
  }
50
93
  catch (err) {
51
- if (err instanceof IssueReviewTimeoutError) {
52
- return { summary: issueReviewSummary(err.lastStatus), data: { ...err.lastStatus } };
53
- }
94
+ if (err instanceof IssueReviewTimeoutError)
95
+ return withNext(err.lastStatus);
54
96
  const message = err instanceof Error ? err.message : String(err);
55
97
  return failure(err instanceof CommandError ? err.code ?? "issue_review_wait_failed" : "issue_review_wait_failed", message, err instanceof CommandError && err.retryAfterSeconds !== undefined ? { retry_after_seconds: err.retryAfterSeconds } : {});
56
98
  }
@@ -8,6 +8,7 @@ const ARG_BY_KEY = {
8
8
  merge_queue_lands_alone_paths: "merge_queue_lands_alone_paths",
9
9
  merge_queue_overdrive: "merge_queue_overdrive",
10
10
  issue_review_enabled: "issue_review",
11
+ issue_auto_review_enabled: "issue_auto_review",
11
12
  };
12
13
  function parseRepo(raw) {
13
14
  const match = typeof raw === "string" ? /^([A-Za-z0-9_.-]+)\/([A-Za-z0-9_.-]+)$/.exec(raw.trim()) : null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mergestorm-mcp",
3
- "version": "0.2.29",
3
+ "version": "0.2.31",
4
4
  "description": "Mergestorm MCP server — identity, review, and read-only stack tools over stdio",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -39,7 +39,7 @@
39
39
  },
40
40
  "dependencies": {
41
41
  "@modelcontextprotocol/sdk": "^1.30.0",
42
- "mergestorm": "0.3.66",
42
+ "mergestorm": "0.3.68",
43
43
  "zod": "^3.24.2"
44
44
  },
45
45
  "devDependencies": {