mergestorm-mcp 0.1.6 → 0.1.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/index.js +35 -65
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@ The stack tools expose reads plus one scoped policy write:
|
|
|
8
8
|
- `stack_list` returns the current API key owner's registered stacks.
|
|
9
9
|
- `stack_set({ stack_id, auto_land?, auto_review?, auto_patch? })` sets per-stack policy on one owned stack. `auto_land` flips Auto land. `auto_review` and `auto_patch` pin Vortex auto-review or Cyclone auto-patch for that stack in either direction (`true` or `false`); `null` clears the pin so the stack follows the account setting. Pass at least one key. Account settings are never changed here.
|
|
10
10
|
- `stack_status({ stack_id })` returns one owned stack with enriched checks and agent state, or a structured `stack_not_found` error.
|
|
11
|
-
- `stack_wait({ stack_id, timeout_s?, enrolled_head_sha?, after_finished_at?, bounce_id? })` waits for a stack or queue state change and returns the latest snapshots
|
|
11
|
+
- `stack_wait({ stack_id, timeout_s?, enrolled_head_sha?, after_finished_at?, bounce_id? })` waits for a stack or queue state change and returns the latest snapshots. `timeout_s: 0` returns one snapshot; a positive timeout after a snapshot with no attention returns the current `waiting` or `in_progress` status with selectors to use for the next call. An unread deadline returns `failed`.
|
|
12
12
|
|
|
13
13
|
Auth is `MERGESTORM_API_KEY`, then `~/.mergestorm/config.json` (same as `mg`).
|
|
14
14
|
|
package/dist/index.js
CHANGED
|
@@ -479,17 +479,26 @@ async function reviewWaitPr(owner, repo, prNumber, afterSha, timeout_s, cfg, opt
|
|
|
479
479
|
);
|
|
480
480
|
} catch (err) {
|
|
481
481
|
if (err instanceof PrReviewPollTimeoutError) {
|
|
482
|
-
const status =
|
|
482
|
+
const status = "in_progress";
|
|
483
|
+
const expectedSha = afterSha?.trim().toLowerCase() ?? "";
|
|
484
|
+
const observedSha = err.lastEnvelope?.head_sha?.trim().toLowerCase() ?? "";
|
|
485
|
+
const headShaMatches = Boolean(
|
|
486
|
+
observedSha && expectedSha.length >= 7 && (observedSha.startsWith(expectedSha) || expectedSha.startsWith(observedSha))
|
|
487
|
+
);
|
|
488
|
+
const findingCount = headShaMatches ? err.lastEnvelope?.finding_count ?? null : null;
|
|
483
489
|
return {
|
|
484
490
|
summary: prReviewSummary(target.owner, target.repo, target.prNumber, {
|
|
485
491
|
status,
|
|
486
492
|
head_sha: err.lastEnvelope?.head_sha ?? null,
|
|
487
|
-
finding_count:
|
|
493
|
+
finding_count: findingCount,
|
|
488
494
|
phase: err.lastEnvelope?.phase ?? null
|
|
489
495
|
}),
|
|
490
496
|
data: {
|
|
491
|
-
...err.lastEnvelope ?? {},
|
|
492
497
|
status,
|
|
498
|
+
head_sha: err.lastEnvelope?.head_sha ?? null,
|
|
499
|
+
pass: headShaMatches ? err.lastEnvelope?.pass ?? null : null,
|
|
500
|
+
finding_count: findingCount,
|
|
501
|
+
phase: err.lastEnvelope?.phase ?? null,
|
|
493
502
|
error: err.message
|
|
494
503
|
}
|
|
495
504
|
};
|
|
@@ -719,64 +728,12 @@ import {
|
|
|
719
728
|
} from "mergestorm/client";
|
|
720
729
|
|
|
721
730
|
// src/tools/stack-summary.ts
|
|
722
|
-
import {
|
|
723
|
-
BOUNCE_KIND_LABELS,
|
|
724
|
-
mergeQueueBounceLabel
|
|
725
|
-
} from "mergestorm/client";
|
|
726
|
-
function sameHead(a, b) {
|
|
727
|
-
const left = a?.trim().toLowerCase();
|
|
728
|
-
const right = b?.trim().toLowerCase();
|
|
729
|
-
return !!left && !!right && (left.length === right.length ? left === right : left.startsWith(right) || right.startsWith(left));
|
|
730
|
-
}
|
|
731
|
-
function blockerLabel(stack, entries) {
|
|
732
|
-
if (stack.archivedAt) return null;
|
|
733
|
-
const queue = entries.filter((entry) => entry.stackId === stack.id);
|
|
734
|
-
if (queue.some((entry) => ["queued", "running", "waiting"].includes(entry.state))) return null;
|
|
735
|
-
const open = stack.layers.filter(
|
|
736
|
-
(layer2) => layer2.prNumber > 0 && layer2.state !== "merged" && layer2.state !== "closed"
|
|
737
|
-
);
|
|
738
|
-
const promote = open.filter(
|
|
739
|
-
(layer2) => layer2.prNumber !== stack.unit?.landPrNumber && !stack.unit?.members?.some(
|
|
740
|
-
(member) => member.prNumber === layer2.prNumber && member.promotedHeadSha
|
|
741
|
-
)
|
|
742
|
-
).sort((a, b) => a.position - b.position);
|
|
743
|
-
const layer = promote[0] ?? open.find((layer2) => layer2.prNumber === stack.unit?.landPrNumber) ?? stack.unit?.landPr;
|
|
744
|
-
const bounce = queue.filter((entry) => entry.state === "bounced").sort((a, b) => (Date.parse(b.finishedAt ?? "") || 0) - (Date.parse(a.finishedAt ?? "") || 0))[0];
|
|
745
|
-
const bounceKind = bounce?.bounceDetail?.kind;
|
|
746
|
-
const recoverableBounceSha = (bounceKind === "head_moved" ? bounce?.bounceDetail?.headSha ?? bounce?.verifyHeadSha : bounce?.verifyHeadSha ?? bounce?.bounceDetail?.headSha)?.trim().toLowerCase();
|
|
747
|
-
const currentRecoverableBounce = !!recoverableBounceSha && (bounceKind === "head_moved" || bounceKind === "must_consolidate") && !!layer && layer.state !== "merged" && layer.state !== "closed" && sameHead(layer.headSha, recoverableBounceSha);
|
|
748
|
-
if (layer && layer.state !== "merged" && layer.state !== "closed") {
|
|
749
|
-
if (layer.restackError && layer.state !== "restacking") {
|
|
750
|
-
return layer.restackError.kind === "rebase_conflict" ? "Conflict" : "Restack failed";
|
|
751
|
-
}
|
|
752
|
-
if (layer.state === "conflict") return "Conflict";
|
|
753
|
-
if (layer.draft) return "Draft PR";
|
|
754
|
-
if ((!layer.headSha || layer.mergeableHeadSha === layer.headSha) && (layer.mergeable === false || layer.mergeableState?.toLowerCase() === "dirty")) return "Merge conflicts";
|
|
755
|
-
if (!currentRecoverableBounce && (layer.ciStatus === "failure" || (layer.checks?.failure ?? 0) > 0)) {
|
|
756
|
-
const name = layer.checks?.failingName?.trim();
|
|
757
|
-
return BOUNCE_KIND_LABELS.ci_failure + (name ? ` \u2014 ${name}` : "");
|
|
758
|
-
}
|
|
759
|
-
const tempest = layer.agentRuns?.find(
|
|
760
|
-
(run) => run.agent === "tempest" && ["findings", "failed"].includes(run.status) && sameHead(run.sha, layer.headSha)
|
|
761
|
-
);
|
|
762
|
-
if (tempest) return `Tempest ${tempest.status}`;
|
|
763
|
-
if (layer.vortexStatus === "failed") return "Review failed";
|
|
764
|
-
if (stack.unit?.landPrNumber === layer.prNumber) {
|
|
765
|
-
if (stack.unit.landingBlockReason?.trim()) return stack.unit.landingBlockReason.trim();
|
|
766
|
-
if (stack.unit.tempestLandStatus?.toLowerCase() === "failed") return "Tempest failed";
|
|
767
|
-
}
|
|
768
|
-
}
|
|
769
|
-
const kind = bounceKind;
|
|
770
|
-
if (!kind || kind === "head_moved" || kind === "must_consolidate") return null;
|
|
771
|
-
const sha = (bounce.bounceDetail?.headSha ?? bounce.verifyHeadSha)?.trim().toLowerCase();
|
|
772
|
-
const current = sha && layer && layer.prNumber > 0 && layer.state !== "merged" && layer.state !== "closed" && (bounce.bounceDetail?.prNumber == null || layer.prNumber === bounce.bounceDetail.prNumber) && sameHead(layer.headSha, sha);
|
|
773
|
-
return current ? mergeQueueBounceLabel(bounce) : null;
|
|
774
|
-
}
|
|
731
|
+
import { stackBlockers, stackBlockersSummary } from "mergestorm/client";
|
|
775
732
|
function stackSummary(stack, entries = []) {
|
|
776
733
|
const count = stack.layers.length;
|
|
777
734
|
const layerLabel = `${count} layer${count === 1 ? "" : "s"}`;
|
|
778
735
|
const states = [...new Set(stack.layers.map((layer) => layer.state))];
|
|
779
|
-
const currentState = states.length > 0 ? `
|
|
736
|
+
const currentState = states.length > 0 ? `restack: ${states.join(", ")}` : stack.unit ? `unit: ${stack.unit.state}` : "no open layers";
|
|
780
737
|
const overrides = [];
|
|
781
738
|
if (typeof stack.autoReviewOverride === "boolean") {
|
|
782
739
|
overrides.push(`auto-review ${stack.autoReviewOverride ? "on" : "off"}`);
|
|
@@ -787,8 +744,8 @@ function stackSummary(stack, entries = []) {
|
|
|
787
744
|
if (stack.cycloneOwnerMatch) {
|
|
788
745
|
overrides.push(`cyclone-owner ${stack.cycloneOwnerMatch}`);
|
|
789
746
|
}
|
|
790
|
-
const
|
|
791
|
-
return `${stack.owner}/${stack.repo} \xB7 ${layerLabel} \xB7 ${currentState} \xB7 auto-land ${stack.autoEnqueueWhenReady ? "on" : "off"}${overrides.map((label) => ` \xB7 ${label}`).join("")}${
|
|
747
|
+
const { attention, issues } = stackBlockers(stack, entries);
|
|
748
|
+
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)}`;
|
|
792
749
|
}
|
|
793
750
|
|
|
794
751
|
// src/tools/stack-list.ts
|
|
@@ -821,6 +778,7 @@ async function stackList(cfg) {
|
|
|
821
778
|
// src/tools/stack-wait.ts
|
|
822
779
|
import {
|
|
823
780
|
loadConfig as loadConfig6,
|
|
781
|
+
stackBlockersSummary as stackBlockersSummary2,
|
|
824
782
|
pollStackWatch,
|
|
825
783
|
StackWatchError,
|
|
826
784
|
StackWatchTimeoutError
|
|
@@ -851,7 +809,7 @@ async function stackWait(input, cfg, deps = {}) {
|
|
|
851
809
|
} catch (err) {
|
|
852
810
|
if (err instanceof StackWatchError) {
|
|
853
811
|
return {
|
|
854
|
-
summary: `Stack ${err.lastEnvelope.stackId} \xB7 ${err.lastEnvelope.status}`,
|
|
812
|
+
summary: `Stack ${err.lastEnvelope.stackId} \xB7 ${err.lastEnvelope.status}${stackBlockersSummary2(err.lastEnvelope.blocker && err.lastEnvelope.prNumber != null ? { prNumber: err.lastEnvelope.prNumber, blocker: err.lastEnvelope.blocker } : null, err.lastEnvelope.issues)}${err.lastEnvelope.assessment === "unavailable" ? " \xB7 assessment unavailable" : ""}`,
|
|
855
813
|
data: {
|
|
856
814
|
...err.lastEnvelope,
|
|
857
815
|
...err.retryAfterSeconds !== void 0 ? { retry_after_seconds: err.retryAfterSeconds } : {}
|
|
@@ -860,10 +818,10 @@ async function stackWait(input, cfg, deps = {}) {
|
|
|
860
818
|
};
|
|
861
819
|
}
|
|
862
820
|
if (!(err instanceof StackWatchTimeoutError)) throw err;
|
|
863
|
-
envelope =
|
|
821
|
+
envelope = err.lastEnvelope;
|
|
864
822
|
}
|
|
865
823
|
return {
|
|
866
|
-
summary: `Stack ${envelope.stackId} \xB7 ${envelope.status}${envelope.blocker ?
|
|
824
|
+
summary: `Stack ${envelope.stackId} \xB7 ${envelope.status}${stackBlockersSummary2(envelope.blocker && envelope.prNumber != null ? { prNumber: envelope.prNumber, blocker: envelope.blocker } : null, envelope.issues)}${envelope.assessment === "unavailable" ? " \xB7 assessment unavailable" : ""}`,
|
|
867
825
|
data: envelope
|
|
868
826
|
};
|
|
869
827
|
}
|
|
@@ -963,10 +921,20 @@ var MCP_PR_LOOP_INSTRUCTIONS = [
|
|
|
963
921
|
`- 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).`,
|
|
964
922
|
"- Do not prefix that comment with cyclone-outcome: (reserved)."
|
|
965
923
|
].join("\n");
|
|
924
|
+
var MCP_STACK_WATCH_INSTRUCTIONS = [
|
|
925
|
+
"Stack-watch facts (mergestorm.stack_watch/v1):",
|
|
926
|
+
"- waiting with nonempty issues[] is not idle; in_progress can also carry issues[].",
|
|
927
|
+
"- assessment unavailable does not mean issue-free. timeout_s: 0 reads one enrich and one queue snapshot.",
|
|
928
|
+
"- Restack clean does not mean mergeable or CI-green.",
|
|
929
|
+
"- attention names the blocked PR via prNumber/headSha; currentCandidate is the promote candidate. Attention is not a mutation or authorization to change policy.",
|
|
930
|
+
"- verifyHeadSha is a queue verification SHA, not the live PR head.",
|
|
931
|
+
"- mg-park-* is a frozen GitHub base, not a repair target: never merge or rebase onto mg-park-*.",
|
|
932
|
+
"- Use the mergestorm-bounce-watch skill for the fix-then-watch policy."
|
|
933
|
+
].join("\n");
|
|
966
934
|
|
|
967
935
|
// src/server.ts
|
|
968
936
|
var MCP_SERVER_NAME = "mergestorm";
|
|
969
|
-
var MCP_SERVER_VERSION = "0.1.
|
|
937
|
+
var MCP_SERVER_VERSION = "0.1.7";
|
|
970
938
|
function stripNulls(value) {
|
|
971
939
|
if (Array.isArray(value)) {
|
|
972
940
|
return value.map((item) => stripNulls(item));
|
|
@@ -1000,7 +968,9 @@ function createMergestormMcpServer() {
|
|
|
1000
968
|
name: MCP_SERVER_NAME,
|
|
1001
969
|
version: MCP_SERVER_VERSION
|
|
1002
970
|
},
|
|
1003
|
-
{ instructions: MCP_PR_LOOP_INSTRUCTIONS
|
|
971
|
+
{ instructions: `${MCP_PR_LOOP_INSTRUCTIONS}
|
|
972
|
+
|
|
973
|
+
${MCP_STACK_WATCH_INSTRUCTIONS}` }
|
|
1004
974
|
);
|
|
1005
975
|
const addTool = server2.registerTool.bind(server2);
|
|
1006
976
|
addTool(
|
|
@@ -1248,7 +1218,7 @@ function createMergestormMcpServer() {
|
|
|
1248
1218
|
"stack_wait",
|
|
1249
1219
|
{
|
|
1250
1220
|
title: "Wait for stack attention",
|
|
1251
|
-
description: "Wait for a stack to need attention. Returns a mergestorm.stack_watch/v1 envelope;
|
|
1221
|
+
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. A timeout of failed means no assessment was produced. This tool is read-only.",
|
|
1252
1222
|
inputSchema: stackWaitSchema
|
|
1253
1223
|
},
|
|
1254
1224
|
async (args, extra) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mergestorm-mcp",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.7",
|
|
4
4
|
"description": "Mergestorm MCP server — identity, review, and read-only stack tools over stdio",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
},
|
|
30
30
|
"dependencies": {
|
|
31
31
|
"@modelcontextprotocol/sdk": "^1.30.0",
|
|
32
|
-
"mergestorm": "0.3.
|
|
32
|
+
"mergestorm": "0.3.20",
|
|
33
33
|
"zod": "^3.24.2"
|
|
34
34
|
},
|
|
35
35
|
"devDependencies": {
|