mergestorm-mcp 0.2.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +159 -96
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -672,11 +672,100 @@ async function settingsSet(args, cfg) {
672
672
  }
673
673
 
674
674
  // src/tools/stack-adopt.ts
675
- import { adoptStack, CommandError as CommandError9 } from "mergestorm/client";
675
+ import { adoptStack, CommandError as CommandError9, stackWatchObligation as stackWatchObligation3 } from "mergestorm/client";
676
+ import { z as z2 } from "zod";
677
+
678
+ // src/tools/stack-set.ts
679
+ import { setStackPolicy, stackWatchObligation as stackWatchObligation2 } from "mergestorm/client";
680
+
681
+ // src/tools/stack-wait.ts
682
+ import {
683
+ loadConfig as loadConfig6,
684
+ stackBlockersSummary,
685
+ pollStackWatch,
686
+ StackWatchError,
687
+ StackWatchTimeoutError,
688
+ stackWatchObligation
689
+ } from "mergestorm/client";
676
690
  import { z } from "zod";
691
+ var stackWaitSchema = {
692
+ stack_id: z.string().trim().min(1),
693
+ timeout_s: z.number().finite().min(0).max(45).default(45),
694
+ enrolled_head_sha: z.string().nullable().optional(),
695
+ after_finished_at: z.string().nullable().optional(),
696
+ bounce_id: z.string().nullable().optional()
697
+ };
698
+ function repairSummary(repair) {
699
+ if (!repair) return "";
700
+ const files = "files" in repair && repair.files.length ? ` Files: ${repair.files.join(", ")}.` : "";
701
+ return `
702
+ Repair #${repair.prNumber} (${repair.kind}).${files} ${repair.steps}`;
703
+ }
704
+ function withWatchText(watch, body) {
705
+ return `${watch.message}
706
+
707
+ ${body}`;
708
+ }
709
+ function envelopeWatch(envelope) {
710
+ return envelope.watch ?? stackWatchObligation({
711
+ stackId: envelope.stackId,
712
+ terminal: null,
713
+ cursor: envelope.cursor,
714
+ status: envelope.status
715
+ });
716
+ }
717
+ function stackWaitSummary(envelope) {
718
+ return withWatchText(envelopeWatch(envelope), `${stackWaitStatusLine(envelope)}${repairSummary(envelope.repair)}`);
719
+ }
720
+ function stackWaitStatusLine(envelope) {
721
+ const base = `Stack ${envelope.stackId} \xB7 ${envelope.status}${stackBlockersSummary(envelope.blocker && envelope.prNumber != null ? { prNumber: envelope.prNumber, blocker: envelope.blocker } : null, envelope.issues, envelope.busy, envelope.landGatePending)}${envelope.assessment === "unavailable" ? " \xB7 assessment unavailable" : ""}`;
722
+ if (envelopeWatch(envelope).done || envelope.status !== "waiting" && envelope.status !== "in_progress") return base;
723
+ const selectors = [
724
+ `stack_id ${JSON.stringify(envelope.cursor.stackId)}`,
725
+ `enrolled_head_sha ${JSON.stringify(envelope.cursor.enrolledHeadSha)}`,
726
+ ...envelope.cursor.afterFinishedAt !== void 0 ? [`after_finished_at ${JSON.stringify(envelope.cursor.afterFinishedAt)}`] : [],
727
+ ...envelope.cursor.bounceId !== void 0 ? [`bounce_id ${JSON.stringify(envelope.cursor.bounceId)}`] : []
728
+ ];
729
+ return `${base} \xB7 watch not finished: call stack_wait again with timeout_s 45 and the same cursor: ${selectors.join(", ")}`;
730
+ }
731
+ async function stackWait(input, cfg, deps = {}) {
732
+ const args = z.object(stackWaitSchema).parse(input);
733
+ const cursor = args.enrolled_head_sha !== void 0 || args.after_finished_at !== void 0 || args.bounce_id !== void 0 ? {
734
+ stackId: args.stack_id,
735
+ enrolledHeadSha: args.enrolled_head_sha ?? null,
736
+ ...args.after_finished_at !== void 0 ? { afterFinishedAt: args.after_finished_at } : {},
737
+ ...args.bounce_id !== void 0 ? { bounceId: args.bounce_id } : {}
738
+ } : void 0;
739
+ let envelope;
740
+ try {
741
+ envelope = await (deps.pollStackWatch ?? pollStackWatch)(cfg ?? await loadConfig6(), args.stack_id, {
742
+ timeoutMs: args.timeout_s * 1e3,
743
+ cursor,
744
+ signal: deps.signal
745
+ });
746
+ } catch (err) {
747
+ if (err instanceof StackWatchError) {
748
+ const envelope2 = { ...err.lastEnvelope, watch: envelopeWatch(err.lastEnvelope) };
749
+ return {
750
+ summary: stackWaitSummary(envelope2),
751
+ data: {
752
+ ...envelope2,
753
+ ...err.retryAfterSeconds !== void 0 ? { retry_after_seconds: err.retryAfterSeconds } : {}
754
+ },
755
+ isError: !envelope2.watch.done
756
+ };
757
+ }
758
+ if (!(err instanceof StackWatchTimeoutError)) throw err;
759
+ envelope = err.lastEnvelope;
760
+ }
761
+ const sealed = { ...envelope, watch: envelopeWatch(envelope) };
762
+ return {
763
+ summary: stackWaitSummary(sealed),
764
+ data: sealed
765
+ };
766
+ }
677
767
 
678
768
  // src/tools/stack-set.ts
679
- import { setStackPolicy } from "mergestorm/client";
680
769
  function triWord(value) {
681
770
  return value === null ? "default" : value ? "on" : "off";
682
771
  }
@@ -713,28 +802,30 @@ async function stackSet(stackId, input, cfg) {
713
802
  if (input.auto_patch !== void 0) {
714
803
  parts.push(`auto-patch ${triWord(input.auto_patch)}`);
715
804
  }
805
+ const watch = stackWatchObligation2({ stackId, terminal: null, unread: true, freshCursor: true });
716
806
  return {
717
- summary: `${parts.join(", ")} for stack ${stackId}`,
807
+ summary: withWatchText(watch, `${parts.join(", ")} for stack ${stackId}`),
718
808
  data: {
719
809
  stack_id: stackId,
720
810
  ...typeof input.auto_land === "boolean" ? { auto_land: input.auto_land } : {},
721
811
  ...input.auto_review !== void 0 ? { auto_review: input.auto_review } : {},
722
812
  ...input.auto_patch !== void 0 ? { auto_patch: input.auto_patch } : {},
723
- result
813
+ result,
814
+ watch
724
815
  }
725
816
  };
726
817
  }
727
818
 
728
819
  // src/tools/stack-adopt.ts
729
820
  var stackAdoptSchema = {
730
- owner: z.string().trim().min(1),
731
- repo: z.string().trim().min(1),
732
- pr_number: z.number().int().min(1),
733
- auto_land: z.boolean().optional(),
734
- auto_review: z.boolean().nullable().optional(),
735
- auto_patch: z.boolean().nullable().optional()
821
+ owner: z2.string().trim().min(1),
822
+ repo: z2.string().trim().min(1),
823
+ pr_number: z2.number().int().min(1),
824
+ auto_land: z2.boolean().optional(),
825
+ auto_review: z2.boolean().nullable().optional(),
826
+ auto_patch: z2.boolean().nullable().optional()
736
827
  };
737
- var inputSchema = z.object(stackAdoptSchema);
828
+ var inputSchema = z2.object(stackAdoptSchema);
738
829
  async function stackAdopt(input, cfg) {
739
830
  const parsed = inputSchema.safeParse(input);
740
831
  if (!parsed.success) {
@@ -750,9 +841,15 @@ async function stackAdopt(input, cfg) {
750
841
  cfg,
751
842
  stackSetPatch(parsed.data)
752
843
  );
844
+ const summary = `Adopted stack for ${owner}/${repo}#${pr_number}`;
845
+ const stackId = typeof result === "object" && result !== null ? result.stack?.id : void 0;
846
+ if (typeof stackId !== "string" || !stackId) {
847
+ return { summary, data: { owner, repo, pr_number, result } };
848
+ }
849
+ const watch = stackWatchObligation3({ stackId, terminal: null, unread: true, freshCursor: true });
753
850
  return {
754
- summary: `Adopted stack for ${owner}/${repo}#${pr_number}`,
755
- data: { owner, repo, pr_number, result }
851
+ summary: withWatchText(watch, summary),
852
+ data: { owner, repo, pr_number, result, watch }
756
853
  };
757
854
  } catch (err) {
758
855
  const message = err instanceof Error ? err.message : String(err);
@@ -778,7 +875,7 @@ import {
778
875
  } from "mergestorm/client";
779
876
 
780
877
  // src/tools/stack-summary.ts
781
- import { stackBlockers, stackBlockersSummary } from "mergestorm/client";
878
+ import { stackBlockers, stackBlockersSummary as stackBlockersSummary2 } from "mergestorm/client";
782
879
  function stackSummary(stack, entries = []) {
783
880
  const count = stack.layers.length;
784
881
  const layerLabel = `${count} layer${count === 1 ? "" : "s"}`;
@@ -794,8 +891,8 @@ function stackSummary(stack, entries = []) {
794
891
  if (stack.cycloneOwnerMatch) {
795
892
  overrides.push(`cyclone-owner ${stack.cycloneOwnerMatch}`);
796
893
  }
797
- const { attention, issues, busy } = stackBlockers(stack, entries);
798
- return `${stack.owner}/${stack.repo} \xB7 ${layerLabel} \xB7 ${currentState} \xB7 auto-land ${stack.autoEnqueueWhenReady ? "on" : "off"}${overrides.map((label) => ` \xB7 ${label}`).join("")}${stackBlockersSummary(attention, issues, busy)}`;
894
+ const { attention, issues, busy, landGatePending } = stackBlockers(stack, entries);
895
+ return `${stack.owner}/${stack.repo} \xB7 ${layerLabel} \xB7 ${currentState} \xB7 auto-land ${stack.autoEnqueueWhenReady ? "on" : "off"}${overrides.map((label) => ` \xB7 ${label}`).join("")}${stackBlockersSummary2(attention, issues, busy, landGatePending)}`;
799
896
  }
800
897
 
801
898
  // src/tools/stack-list.ts
@@ -825,108 +922,65 @@ async function stackList(cfg) {
825
922
  }
826
923
  }
827
924
 
828
- // src/tools/stack-wait.ts
829
- import {
830
- loadConfig as loadConfig6,
831
- stackBlockersSummary as stackBlockersSummary2,
832
- pollStackWatch,
833
- StackWatchError,
834
- StackWatchTimeoutError
835
- } from "mergestorm/client";
836
- import { z as z2 } from "zod";
837
- var stackWaitSchema = {
838
- stack_id: z2.string().trim().min(1),
839
- timeout_s: z2.number().finite().min(0).max(45).default(45),
840
- enrolled_head_sha: z2.string().nullable().optional(),
841
- after_finished_at: z2.string().nullable().optional(),
842
- bounce_id: z2.string().nullable().optional()
843
- };
844
- function stackWaitSummary(envelope) {
845
- const base = `Stack ${envelope.stackId} \xB7 ${envelope.status}${stackBlockersSummary2(envelope.blocker && envelope.prNumber != null ? { prNumber: envelope.prNumber, blocker: envelope.blocker } : null, envelope.issues, envelope.busy)}${envelope.assessment === "unavailable" ? " \xB7 assessment unavailable" : ""}`;
846
- if (envelope.status !== "waiting" && envelope.status !== "in_progress") return base;
847
- const selectors = [
848
- `stack_id ${JSON.stringify(envelope.cursor.stackId)}`,
849
- `enrolled_head_sha ${JSON.stringify(envelope.cursor.enrolledHeadSha)}`,
850
- ...envelope.cursor.afterFinishedAt !== void 0 ? [`after_finished_at ${JSON.stringify(envelope.cursor.afterFinishedAt)}`] : [],
851
- ...envelope.cursor.bounceId !== void 0 ? [`bounce_id ${JSON.stringify(envelope.cursor.bounceId)}`] : []
852
- ];
853
- return `${base} \xB7 watch not finished: call stack_wait again with timeout_s 45 and the same cursor: ${selectors.join(", ")}`;
854
- }
855
- async function stackWait(input, cfg, deps = {}) {
856
- const args = z2.object(stackWaitSchema).parse(input);
857
- const cursor = args.enrolled_head_sha !== void 0 || args.after_finished_at !== void 0 || args.bounce_id !== void 0 ? {
858
- stackId: args.stack_id,
859
- enrolledHeadSha: args.enrolled_head_sha ?? null,
860
- ...args.after_finished_at !== void 0 ? { afterFinishedAt: args.after_finished_at } : {},
861
- ...args.bounce_id !== void 0 ? { bounceId: args.bounce_id } : {}
862
- } : void 0;
863
- let envelope;
864
- try {
865
- envelope = await (deps.pollStackWatch ?? pollStackWatch)(cfg ?? await loadConfig6(), args.stack_id, {
866
- timeoutMs: args.timeout_s * 1e3,
867
- cursor,
868
- signal: deps.signal
869
- });
870
- } catch (err) {
871
- if (err instanceof StackWatchError) {
872
- return {
873
- summary: stackWaitSummary(err.lastEnvelope),
874
- data: {
875
- ...err.lastEnvelope,
876
- ...err.retryAfterSeconds !== void 0 ? { retry_after_seconds: err.retryAfterSeconds } : {}
877
- },
878
- isError: true
879
- };
880
- }
881
- if (!(err instanceof StackWatchTimeoutError)) throw err;
882
- envelope = err.lastEnvelope;
883
- }
884
- return {
885
- summary: stackWaitSummary(envelope),
886
- data: envelope
887
- };
888
- }
889
-
890
925
  // src/tools/stack-status.ts
891
926
  import {
892
927
  getEnrichedStack,
893
928
  listMergeQueueEntries as listMergeQueueEntries2,
894
929
  isCommandErrorCode as isCommandErrorCode5,
895
- stackBlockers as stackBlockers2
930
+ stackBlockers as stackBlockers2,
931
+ stackTerminalReason,
932
+ stackWatchObligation as stackWatchObligation4
896
933
  } from "mergestorm/client";
897
934
  async function stackStatus(stackId, cfg) {
898
935
  try {
899
936
  const stack = await getEnrichedStack(stackId, cfg);
900
937
  if (!stack) {
901
938
  const message = `Stack not found or not owned by the current user: ${stackId}`;
939
+ const watch2 = stackWatchObligation4({ stackId, terminal: "not_found" });
902
940
  return {
903
- summary: message,
941
+ summary: withWatchText(watch2, message),
904
942
  data: {
905
943
  error: {
906
944
  code: "stack_not_found",
907
945
  message,
908
946
  stack_id: stackId
909
- }
947
+ },
948
+ watch: watch2
910
949
  },
911
950
  isError: true
912
951
  };
913
952
  }
914
953
  const entries = await listMergeQueueEntries2(cfg, { stackId: stack.id });
915
- const { attention, issues, currentCandidate, busy, agents } = stackBlockers2(stack, entries);
954
+ const { attention, issues, currentCandidate, busy, agents, repair, landGatePending } = stackBlockers2(stack, entries);
955
+ const watch = stackWatchObligation4({
956
+ stackId: stack.id,
957
+ terminal: stackTerminalReason(stack),
958
+ cursor: { enrolledHeadSha: currentCandidate?.headSha ?? null },
959
+ attention: attention ? { prNumber: attention.prNumber, blocker: attention.blocker } : null,
960
+ freshCursor: true
961
+ });
916
962
  return {
917
- summary: stackSummary(stack, entries),
918
- data: { stack, attention, issues, currentCandidate, busy, agents }
963
+ summary: withWatchText(watch, `${stackSummary(stack, entries)}${repairSummary(repair)}`),
964
+ data: { stack, attention, issues, currentCandidate, busy, agents, repair, landGatePending, watch }
919
965
  };
920
966
  } catch (err) {
921
967
  if (isCommandErrorCode5(err, "rate_limited")) {
968
+ const watch = stackWatchObligation4({
969
+ stackId,
970
+ terminal: null,
971
+ status: "rate_limited",
972
+ retryAfterSeconds: err.retryAfterSeconds,
973
+ freshCursor: true
974
+ });
922
975
  return {
923
- summary: err.message,
976
+ summary: withWatchText(watch, err.message),
924
977
  data: {
925
978
  error: {
926
979
  code: "rate_limited",
927
980
  message: err.message,
928
981
  retry_after_seconds: err.retryAfterSeconds
929
- }
982
+ },
983
+ watch
930
984
  },
931
985
  isError: true
932
986
  };
@@ -984,6 +1038,12 @@ var MCP_PR_LOOP_INSTRUCTIONS = [
984
1038
  `- If you skip a finding (not reproducible, policy fork, or needs a human), post a public GitHub PR comment before you stop. First line must be: ${MCP_FINDING_DISMISS_MARKER}. Then one line per skipped finding (path, severity, why).`,
985
1039
  "- Do not prefix that comment with cyclone-outcome: (reserved)."
986
1040
  ].join("\n");
1041
+ var MCP_STACK_DONE_INSTRUCTIONS = [
1042
+ "When stack work is done:",
1043
+ "- Stack work is done only when the stack is landed, or when the human takes it back (archives it or tells you to stop). A clean push, a submit, an adopt, or one merged layer is not done: merging a lower layer can break a layer above it.",
1044
+ "- Every stack_wait, stack_status, stack_adopt, and stack_set result carries watch: {done, until, reason, next, message}. While watch.done is false, keep calling stack_wait with watch.next.args (the returned cursor), 45s at a time, and do not end your task. Stop only when watch.done is true.",
1045
+ "- attention means something for you to fix; repair, when present, names the concrete fix. A pending land gate (landGatePending: ci_pending, tempest_pending, tempest_running) is a wait, not attention."
1046
+ ].join("\n");
987
1047
  var MCP_STACK_BASE_INSTRUCTIONS = [
988
1048
  "Stack PR bases (Mergestorm sets these; leave them):",
989
1049
  "- Stack of 2+ PRs: after adopt (stack_adopt, mg stack adopt, or mg stack submit) the bottom PR's GitHub base is the owned trunk mg-stack-<n>, not main. That is correct. Layer 2 is based on layer 1; layer 3+ on the mg-park-* freeze; the git parent of layer 2+ is still the layer below. A 1-PR stack stays on main and gets no mg-stack-<n>.",
@@ -1007,7 +1067,14 @@ var MCP_STACK_WATCH_INSTRUCTIONS = [
1007
1067
 
1008
1068
  // src/server.ts
1009
1069
  var MCP_SERVER_NAME = "mergestorm";
1010
- var MCP_SERVER_VERSION = "0.2.0";
1070
+ var MCP_SERVER_VERSION = "0.2.1";
1071
+ var MCP_SERVER_INSTRUCTIONS = [
1072
+ MCP_STACK_DONE_INSTRUCTIONS,
1073
+ MCP_PR_LOOP_INSTRUCTIONS,
1074
+ MCP_STACK_BASE_INSTRUCTIONS,
1075
+ MCP_STACK_WATCH_INSTRUCTIONS
1076
+ ].join("\n\n");
1077
+ var STACK_WATCH_RESULT_NOTE = ' The result carries watch {done, until: "landed", reason, next, message}: while watch.done is false the stack is not landed and your task is not done; call stack_wait with watch.next.args.';
1011
1078
  function stripNulls(value) {
1012
1079
  if (Array.isArray(value)) {
1013
1080
  return value.map((item) => stripNulls(item));
@@ -1044,11 +1111,7 @@ function createMergestormMcpServer() {
1044
1111
  websiteUrl: MCP_WEBSITE_URL,
1045
1112
  icons: MCP_SERVER_ICONS
1046
1113
  },
1047
- { instructions: `${MCP_PR_LOOP_INSTRUCTIONS}
1048
-
1049
- ${MCP_STACK_BASE_INSTRUCTIONS}
1050
-
1051
- ${MCP_STACK_WATCH_INSTRUCTIONS}` }
1114
+ { instructions: MCP_SERVER_INSTRUCTIONS }
1052
1115
  );
1053
1116
  const addTool = server2.registerTool.bind(server2);
1054
1117
  addTool(
@@ -1185,7 +1248,7 @@ ${MCP_STACK_WATCH_INSTRUCTIONS}` }
1185
1248
  "stack_adopt",
1186
1249
  {
1187
1250
  title: "Adopt PR stack",
1188
- description: "Adopt an open GitHub PR chain into a Mergestorm stack. auto_land is boolean; auto_review and auto_patch accept true, false, or null, where null clears the override. Omitted auto_land seeds from auto_land_default; omitted auto_review and auto_patch overrides are not seeded. Never changes account settings. Adoption requires a Cyclone GitHub App install; without it the API returns cyclone_not_connected \u2014 do not retry, tell the human. Read stack_status with result.stack.id afterward to verify policy and Cyclone ownership. With 2+ PRs, adopt moves the bottom PR's GitHub base to mg-stack-<n> and parks PR3+ on an mg-park-* freeze; a 1-PR stack stays on main. Leave those bases. Never retarget the bottom back to main.",
1251
+ description: "Adopt an open GitHub PR chain into a Mergestorm stack. auto_land is boolean; auto_review and auto_patch accept true, false, or null, where null clears the override. Omitted auto_land seeds from auto_land_default; omitted auto_review and auto_patch overrides are not seeded. Never changes account settings. Adoption requires a Cyclone GitHub App install; without it the API returns cyclone_not_connected \u2014 do not retry, tell the human. Read stack_status with result.stack.id afterward to verify policy and Cyclone ownership. With 2+ PRs, adopt moves the bottom PR's GitHub base to mg-stack-<n> and parks PR3+ on an mg-park-* freeze; a 1-PR stack stays on main. Leave those bases. Never retarget the bottom back to main." + STACK_WATCH_RESULT_NOTE,
1189
1252
  inputSchema: stackAdoptSchema,
1190
1253
  annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false }
1191
1254
  },
@@ -1216,7 +1279,7 @@ ${MCP_STACK_WATCH_INSTRUCTIONS}` }
1216
1279
  "stack_set",
1217
1280
  {
1218
1281
  title: "Set stack policy",
1219
- description: "Set per-stack automation on one owned Mergestorm stack: auto_land arms or disarms Auto land; auto_review and auto_patch pin Vortex auto-review or Cyclone auto-patch for this stack only (true or false), and null clears the pin so the stack follows the account setting again. Pass at least one key. Never changes account settings.",
1282
+ description: "Set per-stack automation on one owned Mergestorm stack: auto_land arms or disarms Auto land; auto_review and auto_patch pin Vortex auto-review or Cyclone auto-patch for this stack only (true or false), and null clears the pin so the stack follows the account setting again. Pass at least one key. Never changes account settings." + STACK_WATCH_RESULT_NOTE,
1220
1283
  inputSchema: {
1221
1284
  stack_id: z3.string().min(1),
1222
1285
  auto_land: z3.boolean().optional(),
@@ -1243,7 +1306,7 @@ ${MCP_STACK_WATCH_INSTRUCTIONS}` }
1243
1306
  "stack_status",
1244
1307
  {
1245
1308
  title: "Get stack status",
1246
- description: "Fetch one owned Mergestorm stack with enriched checks and agent state, plus attention, issues, and currentCandidate from the same blocker rules as stack_wait, read against that stack's own merge queue. trunkBranch is mg-stack-<n> for an adopted 2+ PR stack and matches the bottom open PR's GitHub base; an mg-park-* parentBranch is a freeze. Neither is drift to fix.",
1309
+ description: "Fetch one owned Mergestorm stack with enriched checks and agent state, plus attention, issues, and currentCandidate from the same blocker rules as stack_wait, read against that stack's own merge queue. trunkBranch is mg-stack-<n> for an adopted 2+ PR stack and matches the bottom open PR's GitHub base; an mg-park-* parentBranch is a freeze. Neither is drift to fix. repair names the concrete fix for attention; landGatePending is a wait, not attention." + STACK_WATCH_RESULT_NOTE,
1247
1310
  inputSchema: {
1248
1311
  stack_id: z3.string().min(1)
1249
1312
  },
@@ -1261,7 +1324,7 @@ ${MCP_STACK_WATCH_INSTRUCTIONS}` }
1261
1324
  "stack_wait",
1262
1325
  {
1263
1326
  title: "Wait for stack attention",
1264
- description: "Wait for a stack to need attention. Returns a mergestorm.stack_watch/v1 envelope; timeout_s: 0 returns one snapshot and timeout_s: 1-45 waits up to that many seconds, returning the current status (waiting or in_progress) if a snapshot was read and no attention is found. A timeout with either of those statuses is not a failure; waiting and in_progress end this slice, and the next call is stack_wait again with the same cursor. A timeout of failed means no assessment was produced. While Cyclone or Vortex is still working on the blocked PR it returns in_progress, not attention, with busy[] naming the PR, blocker, and agent; agents carries that PR's vortexStatus, cycloneStatus, vortexReview, and busy flags. Attention never calls for changing a PR's GitHub base.",
1327
+ description: "Wait for a stack to need attention. Returns a mergestorm.stack_watch/v1 envelope; timeout_s: 0 returns one snapshot and timeout_s: 1-45 waits up to that many seconds, returning the current status (waiting or in_progress) if a snapshot was read and no attention is found. A timeout with either of those statuses is not a failure; waiting and in_progress end this slice, and the next call is stack_wait again with the same cursor. A timeout of failed means no assessment was produced. A stack that no longer exists returns status failed with watch.done true (reason not_found) and is not an error. While Cyclone or Vortex is still working on the blocked PR it returns in_progress, not attention, with busy[] naming the PR, blocker, and agent; agents carries that PR's vortexStatus, cycloneStatus, vortexReview, and busy flags. Attention never calls for changing a PR's GitHub base. repair names the concrete fix for attention (restack_conflict and merge_conflict carry liveParent, never mg-park-*). A pending land gate (landGatePending) returns in_progress, not attention." + STACK_WATCH_RESULT_NOTE,
1265
1328
  inputSchema: stackWaitSchema,
1266
1329
  annotations: { readOnlyHint: true }
1267
1330
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mergestorm-mcp",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "Mergestorm MCP server — identity, review, and read-only stack tools over stdio",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -29,7 +29,7 @@
29
29
  },
30
30
  "dependencies": {
31
31
  "@modelcontextprotocol/sdk": "^1.30.0",
32
- "mergestorm": "0.3.25",
32
+ "mergestorm": "0.3.26",
33
33
  "zod": "^3.24.2"
34
34
  },
35
35
  "devDependencies": {