mergestorm-mcp 0.2.30 → 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/dist/index.js CHANGED
@@ -163,6 +163,8 @@ import {
163
163
  IssueReviewTimeoutError,
164
164
  NO_LINKED_ISSUE,
165
165
  getIssueReview,
166
+ issueReviewStallAdvice,
167
+ issueReviewWaitAfter,
166
168
  loadConfig as loadConfig2,
167
169
  pollIssueReview
168
170
  } from "mergestorm/client";
@@ -172,7 +174,9 @@ var issueReviewWaitSchema = {
172
174
  owner: z2.string().trim().regex(/^[A-Za-z0-9_.-]{1,100}$/),
173
175
  repo: z2.string().trim().regex(/^[A-Za-z0-9_.-]{1,100}$/),
174
176
  number: z2.number().int().min(1),
175
- 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()
176
180
  };
177
181
  var inputSchema2 = z2.object(issueReviewWaitSchema);
178
182
  function blockingCount(status) {
@@ -181,14 +185,36 @@ function blockingCount(status) {
181
185
  function issueReviewSummary(status) {
182
186
  const ref = `${status.owner}/${status.repo}#${status.number}`;
183
187
  const round = status.round ? ` (round ${status.round} of ${status.maxRounds})` : "";
184
- 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.` : `${ref}: ${status.state}${round}, ${blockingCount(status)} point${blockingCount(status) === 1 ? "" : "s"} to answer.${status.mode === "auto" ? " 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.`;
185
203
  return status.next ? `${head} ${status.next}` : head;
186
204
  }
187
- 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 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.";
188
206
  function withIssueReviewHint(err, message) {
189
207
  return err instanceof CommandError3 && err.code === "issue_not_approved" && err.reason !== NO_LINKED_ISSUE ? `${message}
190
208
  ${ISSUE_NOT_APPROVED_HINT}` : message;
191
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
+ }
192
218
  function failure(code, message, extra = {}) {
193
219
  return { summary: message, data: { error: { code, message, ...extra } }, isError: true };
194
220
  }
@@ -197,12 +223,22 @@ async function issueReviewWait(input, cfg, deps = {}) {
197
223
  if (!parsed.success) {
198
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}`);
199
225
  }
200
- const { owner, repo: repo2, number, timeout_s } = parsed.data;
226
+ const { owner, repo: repo2, number, timeout_s, after_round, waiting_since } = parsed.data;
201
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
+ };
202
235
  try {
203
236
  const resolved = cfg ?? await loadConfig2();
204
- 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, {
205
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,
206
242
  ...deps.pollIntervalMs !== void 0 ? { intervalMs: deps.pollIntervalMs } : {}
207
243
  });
208
244
  if (!status) {
@@ -211,11 +247,9 @@ async function issueReviewWait(input, cfg, deps = {}) {
211
247
  "This Mergestorm server does not review issues yet. Open the pull request as before."
212
248
  );
213
249
  }
214
- return { summary: issueReviewSummary(status), data: { ...status } };
250
+ return withNext(status);
215
251
  } catch (err) {
216
- if (err instanceof IssueReviewTimeoutError) {
217
- return { summary: issueReviewSummary(err.lastStatus), data: { ...err.lastStatus } };
218
- }
252
+ if (err instanceof IssueReviewTimeoutError) return withNext(err.lastStatus);
219
253
  const message = err instanceof Error ? err.message : String(err);
220
254
  return failure(
221
255
  err instanceof CommandError3 ? err.code ?? "issue_review_wait_failed" : "issue_review_wait_failed",
@@ -1873,7 +1907,7 @@ var MCP_HOSTED_SERVER_INSTRUCTIONS = [
1873
1907
  // src/server.ts
1874
1908
  var BATCH_SIZE_ROW = BEARER_SETTINGS2.find((row) => row.key === "merge_queue_batch_size");
1875
1909
  var MCP_SERVER_NAME = "mergestorm";
1876
- var MCP_SERVER_VERSION = "0.2.30";
1910
+ var MCP_SERVER_VERSION = "0.2.31";
1877
1911
  var MCP_TOOL_NAMES = [
1878
1912
  "whoami",
1879
1913
  "credits",
@@ -2232,7 +2266,7 @@ function createMergestormMcpServer(options = {}) {
2232
2266
  "issue_review_wait",
2233
2267
  {
2234
2268
  title: "Wait for issue review",
2235
- 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, reply once in a comment on the issue: for each blocking point, accept its suggested fix or say why not (notes do not block; the PR review checks them against the code). Vortex then re-checks only the blocking points and approves the issue. On unreviewed, comment on the issue tagging @mergestorm-vortex to start a review, unless mode is mention, where an unreviewed issue needs no approval. Then call again. A person can also approve 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.",
2236
2270
  inputSchema: issueReviewWaitSchema,
2237
2271
  annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true }
2238
2272
  },
@@ -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.30";
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.30";
35
+ export const MCP_SERVER_VERSION = "0.2.31";
36
36
  export const MCP_TOOL_NAMES = [
37
37
  "whoami",
38
38
  "credits",
@@ -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, reply once in a comment on the issue: for each blocking point, accept its suggested fix or say why not (notes do not block; the PR review checks them against the code). Vortex then re-checks only the blocking points and approves the issue. On unreviewed, comment on the issue tagging @mergestorm-vortex to start a review, unless mode is mention, where an unreviewed issue needs no approval. Then call again. A person can also approve 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) => {
@@ -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: 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 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, NO_LINKED_ISSUE, 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,6 +6,8 @@ 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);
11
13
  function blockingCount(status) {
@@ -14,6 +16,20 @@ function blockingCount(status) {
14
16
  export function issueReviewSummary(status) {
15
17
  const ref = `${status.owner}/${status.repo}#${status.number}`;
16
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
+ }
17
33
  const head = status.state === "approved"
18
34
  ? status.approvedBy === null
19
35
  ? `The Vortex review of ${ref} failed, so it does not hold the pull request back; you may open it.`
@@ -23,14 +39,24 @@ export function issueReviewSummary(status) {
23
39
  : status.mode === "mention" && status.state === "unreviewed"
24
40
  ? `Issue reviews for ${status.owner}/${status.repo} run on mention; ${ref} needs no approval, so you may open the pull request.`
25
41
  : status.state === "pending"
26
- ? `${ref}: Vortex is still reviewing${round}; call issue_review_wait again.`
27
- : `${ref}: ${status.state}${round}, ${blockingCount(status)} point${blockingCount(status) === 1 ? "" : "s"} to answer.${status.mode === "auto" ? " Do not open the pull request yet." : ""}`;
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.`;
28
44
  return status.next ? `${head} ${status.next}` : head;
29
45
  }
30
- 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 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.";
31
47
  export function withIssueReviewHint(err, message) {
32
48
  return err instanceof CommandError && err.code === "issue_not_approved" && err.reason !== NO_LINKED_ISSUE ? `${message}\n${ISSUE_NOT_APPROVED_HINT}` : message;
33
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 };
59
+ }
34
60
  function failure(code, message, extra = {}) {
35
61
  return { summary: message, data: { error: { code, message, ...extra } }, isError: true };
36
62
  }
@@ -39,25 +65,34 @@ export async function issueReviewWait(input, cfg, deps = {}) {
39
65
  if (!parsed.success) {
40
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}`);
41
67
  }
42
- const { owner, repo, number, timeout_s } = parsed.data;
68
+ const { owner, repo, number, timeout_s, after_round, waiting_since } = parsed.data;
43
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
+ };
44
77
  try {
45
78
  const resolved = cfg ?? (await loadConfig());
79
+ const read = timeout_s === 0 ? await (deps.getIssueReview ?? getIssueReview)(resolved, target) : null;
46
80
  const status = timeout_s === 0
47
- ? await (deps.getIssueReview ?? getIssueReview)(resolved, target)
81
+ ? read && { ...read, wait: issueReviewWaitAfter(read, { afterRound: after_round, waitedMs: now() - since, nowMs: now() }) }
48
82
  : await (deps.pollIssueReview ?? pollIssueReview)(resolved, target, {
49
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,
50
86
  ...(deps.pollIntervalMs !== undefined ? { intervalMs: deps.pollIntervalMs } : {}),
51
87
  });
52
88
  if (!status) {
53
89
  return failure("issue_review_unavailable", "This Mergestorm server does not review issues yet. Open the pull request as before.");
54
90
  }
55
- return { summary: issueReviewSummary(status), data: { ...status } };
91
+ return withNext(status);
56
92
  }
57
93
  catch (err) {
58
- if (err instanceof IssueReviewTimeoutError) {
59
- return { summary: issueReviewSummary(err.lastStatus), data: { ...err.lastStatus } };
60
- }
94
+ if (err instanceof IssueReviewTimeoutError)
95
+ return withNext(err.lastStatus);
61
96
  const message = err instanceof Error ? err.message : String(err);
62
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 } : {});
63
98
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mergestorm-mcp",
3
- "version": "0.2.30",
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.67",
42
+ "mergestorm": "0.3.68",
43
43
  "zod": "^3.24.2"
44
44
  },
45
45
  "devDependencies": {