mergestorm 0.3.51 → 0.3.53
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 +2 -2
- package/dist/api.js +2 -2
- package/dist/commands/browse.js +1 -1
- package/dist/commands/login.js +3 -0
- package/dist/commands/whoami.js +1 -1
- package/dist/stack-blockers.d.ts +1 -0
- package/dist/stack-blockers.js +23 -6
- package/dist/stack-dto.d.ts +7 -0
- package/dist/ui/banner.js +6 -0
- package/package.json +1 -1
- package/skill/mergestorm-pr-loop/SKILL.md +1 -1
package/README.md
CHANGED
|
@@ -45,12 +45,12 @@ On a TTY, bare `mergestorm` (or `mergestorm shell`) opens a branded REPL: a comp
|
|
|
45
45
|
|
|
46
46
|
```
|
|
47
47
|
╭──────────────────────────────────────────────────────────────────────────────╮
|
|
48
|
-
│ ▀▀▀▀▀▀▀▀▀▀▘ mergestorm v0.3.
|
|
48
|
+
│ ▀▀▀▀▀▀▀▀▀▀▘ mergestorm v0.3.53 │
|
|
49
49
|
│ ▀▀▀▀▀▀▀▀▀ ● msk_live_… · maelstrom │
|
|
50
50
|
│ ▀▀▀▀▀▀ [████████░░░░░░░░░░░░░░░░░░░░░░] 25% used │
|
|
51
51
|
│ ▝▀▀▀▀▀ local reviews + stacked PRs · mergestorm.ai │
|
|
52
52
|
│ ▝▀▀▘ review a diff · usage for tabs · /help for all commands │
|
|
53
|
-
│ ▀ New:
|
|
53
|
+
│ ▀ New in v0.3.53: Busy queue no longer reads as stuck │
|
|
54
54
|
╰──────────────────────────────────────────────────────────────────────────────╯
|
|
55
55
|
mergestorm
|
|
56
56
|
╭──────────────────────────────────────────────────────────────────────────────╮
|
package/dist/api.js
CHANGED
|
@@ -81,7 +81,7 @@ function rethrowFetchAbort(err, timeoutMs) {
|
|
|
81
81
|
export async function apiFetch(cfg, route, init = {}) {
|
|
82
82
|
const key = resolveApiKey(cfg);
|
|
83
83
|
if (!key) {
|
|
84
|
-
throw new CommandError("No API key. Run `mergestorm login` or set MERGESTORM_API_KEY.", 1, "missing_api_key");
|
|
84
|
+
throw new CommandError("No API key. Run `mergestorm login` (agents: `mergestorm login --start`, then `--finish`) or set MERGESTORM_API_KEY.", 1, "missing_api_key");
|
|
85
85
|
}
|
|
86
86
|
const { json, timeoutMs = DEFAULT_API_TIMEOUT_MS, signal: userSignal, ...rest } = init;
|
|
87
87
|
const headers = {
|
|
@@ -114,7 +114,7 @@ export async function apiFetch(cfg, route, init = {}) {
|
|
|
114
114
|
// keep text
|
|
115
115
|
}
|
|
116
116
|
if (res.status === 401) {
|
|
117
|
-
throw new CommandError("API key invalid or revoked. Run `mergestorm login
|
|
117
|
+
throw new CommandError("API key invalid or revoked. Run `mergestorm login` (agents: `mergestorm login --start`, then `--finish`).", 1, "auth_invalid");
|
|
118
118
|
}
|
|
119
119
|
const retryAfterSeconds = parseRetryAfterSeconds({
|
|
120
120
|
header: res.headers.get("retry-after"),
|
package/dist/commands/browse.js
CHANGED
|
@@ -183,7 +183,7 @@ export async function openTabsBrowser(initial) {
|
|
|
183
183
|
const cfg = await loadConfig();
|
|
184
184
|
const key = resolveApiKey(cfg);
|
|
185
185
|
if (!key) {
|
|
186
|
-
throw new CommandError("Not logged in. Run `mergestorm login
|
|
186
|
+
throw new CommandError("Not logged in. Run `mergestorm login` (agents: `mergestorm login --start`, then `--finish`).");
|
|
187
187
|
}
|
|
188
188
|
let me = null;
|
|
189
189
|
let keyInvalid = false;
|
package/dist/commands/login.js
CHANGED
|
@@ -220,6 +220,9 @@ export async function cmdLogin(args) {
|
|
|
220
220
|
console.log(` ${ansi.brightGreen(pending.openUrl)}`);
|
|
221
221
|
console.log(`\n Code: ${ansi.bold(pending.userCode)}\n`);
|
|
222
222
|
openBrowser(pending.openUrl);
|
|
223
|
+
if (!process.stdout.isTTY) {
|
|
224
|
+
console.log("No terminal here. If this command may be cut off before the approval, use `mergestorm login --start`, then `mergestorm login --finish`.");
|
|
225
|
+
}
|
|
223
226
|
console.log("Waiting for approval…");
|
|
224
227
|
await pollDeviceLogin(pending, "mergestorm login", "Run `mergestorm login` again.");
|
|
225
228
|
}
|
package/dist/commands/whoami.js
CHANGED
|
@@ -9,7 +9,7 @@ export async function cmdWhoami(args, opts = {}) {
|
|
|
9
9
|
const cfg = await loadConfig();
|
|
10
10
|
const key = resolveApiKey(cfg);
|
|
11
11
|
if (!key) {
|
|
12
|
-
throw new CommandError("Not logged in. Run `mergestorm login
|
|
12
|
+
throw new CommandError("Not logged in. Run `mergestorm login` (agents: `mergestorm login --start`, then `--finish`).");
|
|
13
13
|
}
|
|
14
14
|
if (!asJson && canBrowse()) {
|
|
15
15
|
await openTabsBrowser("status");
|
package/dist/stack-blockers.d.ts
CHANGED
|
@@ -199,6 +199,7 @@ export type MergeQueueWait = {
|
|
|
199
199
|
waitReason: string | null;
|
|
200
200
|
aheadStackId: string | null;
|
|
201
201
|
aheadOther: boolean;
|
|
202
|
+
idleSince?: string | null;
|
|
202
203
|
};
|
|
203
204
|
export declare function mergeQueueWait(queue: readonly MergeQueueEntryDto[], nowMs: number): MergeQueueWait | null;
|
|
204
205
|
export declare function conflictLiveParent(stack: StackDto, layer: StackLayerDto): string | null;
|
package/dist/stack-blockers.js
CHANGED
|
@@ -78,6 +78,7 @@ function cycloneHandoffAtHead(stack, layer) {
|
|
|
78
78
|
return stack.cyclonePatchOff ?? (stack.autoPatchOverride === false ? "auto_patch_off" : null);
|
|
79
79
|
}
|
|
80
80
|
const CYCLONE_HANDOFF_WORDS = {
|
|
81
|
+
failed: "Cyclone's patch failed, so it pushed nothing",
|
|
81
82
|
auto_patch_off: "auto-patch is off for this stack",
|
|
82
83
|
stack_auto_patch_off: "auto-patch is off for this stack",
|
|
83
84
|
account_auto_patch_off: "auto-patch is off for the account and this stack does not turn it on",
|
|
@@ -101,16 +102,21 @@ const CYCLONE_HANDOFF_WORDS = {
|
|
|
101
102
|
ci_red_on_base: "the same check also fails on the base branch",
|
|
102
103
|
ci_rerun_exhausted: "Cyclone already had Surge re-run this check once at this head",
|
|
103
104
|
ci_rerun_unavailable: "the failure looked flaky and Surge could not re-run it",
|
|
104
|
-
ci_unverified: "Cyclone
|
|
105
|
+
ci_unverified: "Cyclone's static checks did not cover its fix, so it pushed nothing",
|
|
105
106
|
ci_unreadable: "Cyclone could not read the failing checks",
|
|
106
107
|
ci_needs_actions_read: "Cyclone's GitHub App cannot read the Actions logs on this repo",
|
|
107
108
|
ci_unresolved: "Cyclone's CI fix failed",
|
|
108
109
|
stack_auto_fix_ci_off: "auto-fix CI is off for this stack",
|
|
109
110
|
account_auto_fix_ci_off: "auto-fix CI is off for the account and this stack does not turn it on",
|
|
110
111
|
};
|
|
111
|
-
function cycloneHandoffWords(reason) {
|
|
112
|
+
function cycloneHandoffWords(reason, detail) {
|
|
113
|
+
if (reason === "failed" && detail?.trim())
|
|
114
|
+
return `Cyclone wrote a fix and held it, so it pushed nothing: ${detail.trim()}`;
|
|
112
115
|
return CYCLONE_HANDOFF_WORDS[reason] ?? reason.replaceAll("_", " ");
|
|
113
116
|
}
|
|
117
|
+
function cycloneHandoffDetailAtHead(layer) {
|
|
118
|
+
return sameHead(layer.cycloneHandoff?.headSha, layer.headSha) ? layer.cycloneHandoff.detail ?? null : null;
|
|
119
|
+
}
|
|
114
120
|
function isLandPrLayer(stack, layer) {
|
|
115
121
|
return layer.prNumber === stack.unit?.landPrNumber || (!!stack.unit?.branch && layer.branch === stack.unit.branch);
|
|
116
122
|
}
|
|
@@ -514,11 +520,22 @@ export function mergeQueueWait(queue, nowMs) {
|
|
|
514
520
|
const queuedFor = minutesOf(queuedMs);
|
|
515
521
|
const ahead = entry.aheadInRepo;
|
|
516
522
|
if (!ahead) {
|
|
523
|
+
const progressAt = entry.repoQueue?.progressAt ?? null;
|
|
524
|
+
const progressMs = progressAt ? waitedMs(progressAt, nowMs) : null;
|
|
525
|
+
const idleMs = queuedMs != null && progressMs != null ? Math.min(queuedMs, progressMs) : queuedMs;
|
|
526
|
+
const queuedAhead = entry.repoQueue?.queuedAhead ?? 0;
|
|
527
|
+
const turn = queuedAhead > 0
|
|
528
|
+
? `behind ${queuedAhead} older queued ${queuedAhead === 1 ? "entry" : "entries"}`
|
|
529
|
+
: "not picked up yet";
|
|
530
|
+
const moved = progressMs != null && queuedMs != null && progressMs < queuedMs
|
|
531
|
+
? `; the queue last moved ${minutesOf(progressMs)}m ago`
|
|
532
|
+
: "";
|
|
517
533
|
return {
|
|
518
|
-
text: `Queued ${queuedFor}m in the merge queue,
|
|
519
|
-
stalled:
|
|
534
|
+
text: `Queued ${queuedFor}m in the merge queue, ${turn}${moved}${reason ? ` (last note: ${reason})` : ""}`,
|
|
535
|
+
stalled: idleMs != null && idleMs >= MERGE_QUEUE_STALL_ATTENTION_MS,
|
|
520
536
|
notPickedUp: true,
|
|
521
537
|
since: entry.enqueuedAt, waitReason: reason, aheadStackId: null, aheadOther: false,
|
|
538
|
+
idleSince: moved ? progressAt : null,
|
|
522
539
|
};
|
|
523
540
|
}
|
|
524
541
|
const aheadMs = ahead.claimedAt ? waitedMs(ahead.claimedAt, nowMs) : null;
|
|
@@ -548,7 +565,7 @@ function queueStalledSteps(stack, layer, wait) {
|
|
|
548
565
|
return `#${layer.prNumber} is waiting in the merge queue behind another entry in ${stack.owner}/${stack.repo} that has made no progress since ${wait.since ?? "it was claimed"}. Nothing on ${layer.branch} needs a change. ${ahead} If it stays stuck, tell the human: the queue for this repo is held by that entry.`;
|
|
549
566
|
}
|
|
550
567
|
if (wait.notPickedUp) {
|
|
551
|
-
return `#${layer.prNumber} has been queued in the merge queue for ${stack.owner}/${stack.repo} since ${wait.since ?? "it was enqueued"} and nothing is working on it or on an entry ahead of it${wait.waitReason ? `; its last note is: ${wait.waitReason}` : ""}. Nothing on ${layer.branch} needs a change: do not patch or push for this. If the note says the base branch keeps moving, the queue is waiting for a quiet moment on it and continues by itself once pushes to that branch pause. Otherwise tell the human: the merge queue is not picking this entry up; they can take the stack out of the queue and enqueue it again (mg queue rm ${stack.id}, then mg queue add ${stack.id}). Do not retarget the PR base.`;
|
|
568
|
+
return `#${layer.prNumber} has been queued in the merge queue for ${stack.owner}/${stack.repo} since ${wait.since ?? "it was enqueued"} and nothing is working on it or on an entry ahead of it${wait.idleSince ? `: the queue last claimed or finished an entry at ${wait.idleSince}` : ""}${wait.waitReason ? `; its last note is: ${wait.waitReason}` : ""}. Nothing on ${layer.branch} needs a change: do not patch or push for this. If the note says the base branch keeps moving, the queue is waiting for a quiet moment on it and continues by itself once pushes to that branch pause. Otherwise tell the human: the merge queue is not picking this entry up; they can take the stack out of the queue and enqueue it again (mg queue rm ${stack.id}, then mg queue add ${stack.id}). Do not retarget the PR base.`;
|
|
552
569
|
}
|
|
553
570
|
return `The merge queue has held #${layer.prNumber} at ${layer.headSha ?? "its head"} since ${wait.since ?? "it was claimed"} without landing or bouncing it${wait.waitReason ? `; its last note is: ${wait.waitReason}` : ", and it recorded no reason"}. If the note names a failing check or a conflict on ${layer.branch}, fix that with an ordinary push. If it waits on CI, list the head's checks (gh run list --branch ${layer.branch}) and re-run the one that never finished (gh run rerun <run-id>). If nothing moves after that, tell the human: they can take the stack out of the queue and enqueue it again (mg queue rm ${stack.id}, then mg queue add ${stack.id}). Do not retarget the PR base.`;
|
|
554
571
|
}
|
|
@@ -794,7 +811,7 @@ export function stackRepair(stack, attention, layer, bounce, queueWait = null) {
|
|
|
794
811
|
branch: layer.branch,
|
|
795
812
|
handoff,
|
|
796
813
|
findings,
|
|
797
|
-
steps: `Vortex ${vortexImportantFindingsHeldAtHead(layer) ? "left Important findings" : "requested changes"} on ${layer.branch} at ${layer.headSha ?? "its live head"} and Cyclone left them for a person${handoff ? ` (${cycloneHandoffWords(handoff)})` : ""}, so nothing will patch them and Auto land waits. Read the newest Vortex review and its inline comments (${findings}), verify each finding against ${layer.branch} at its live remote head, fix the real ones with the smallest patch, run the tests, confirm the remote head is still ${layer.headSha ?? "the head you started from"}, then an ordinary git push (no force). If a finding is wrong, tell the human; mg dismiss ${stack.owner}/${stack.repo}#${layer.prNumber} --head <sha> --review <id> dismisses that review. Do not retarget the PR base.`,
|
|
814
|
+
steps: `Vortex ${vortexImportantFindingsHeldAtHead(layer) ? "left Important findings" : "requested changes"} on ${layer.branch} at ${layer.headSha ?? "its live head"} and Cyclone left them for a person${handoff ? ` (${cycloneHandoffWords(handoff, cycloneHandoffDetailAtHead(layer))})` : ""}, so nothing will patch them and Auto land waits. Read the newest Vortex review and its inline comments (${findings}), verify each finding against ${layer.branch} at its live remote head, fix the real ones with the smallest patch, run the tests, confirm the remote head is still ${layer.headSha ?? "the head you started from"}, then an ordinary git push (no force). If a finding is wrong, tell the human; mg dismiss ${stack.owner}/${stack.repo}#${layer.prNumber} --head <sha> --review <id> dismisses that review. Do not retarget the PR base.`,
|
|
798
815
|
};
|
|
799
816
|
}
|
|
800
817
|
if (reason === "ci_failed" || attention.bounceKind === "ci_failure") {
|
package/dist/stack-dto.d.ts
CHANGED
|
@@ -69,6 +69,7 @@ export type StackCycloneHandoff = {
|
|
|
69
69
|
headSha: string;
|
|
70
70
|
reason: string;
|
|
71
71
|
at: string | null;
|
|
72
|
+
detail?: string | null;
|
|
72
73
|
};
|
|
73
74
|
export type StackAgentName = "vortex" | "cyclone";
|
|
74
75
|
/**
|
|
@@ -108,6 +109,7 @@ export type StackAgentRun = {
|
|
|
108
109
|
reviewed: number;
|
|
109
110
|
total: number;
|
|
110
111
|
} | null;
|
|
112
|
+
detail?: string | null;
|
|
111
113
|
};
|
|
112
114
|
export type StackAgentsBusy = {
|
|
113
115
|
vortex: boolean;
|
|
@@ -455,6 +457,10 @@ export type MergeQueueAheadDto = {
|
|
|
455
457
|
claimedAt: string | null;
|
|
456
458
|
waitReason: string | null;
|
|
457
459
|
};
|
|
460
|
+
export type MergeQueueRepoQueueDto = {
|
|
461
|
+
queuedAhead: number;
|
|
462
|
+
progressAt: string | null;
|
|
463
|
+
};
|
|
458
464
|
export type MergeQueueEntryDto = {
|
|
459
465
|
id: string;
|
|
460
466
|
stackId: string;
|
|
@@ -480,4 +486,5 @@ export type MergeQueueEntryDto = {
|
|
|
480
486
|
finishedAt: string | null;
|
|
481
487
|
claimedAt?: string | null;
|
|
482
488
|
aheadInRepo?: MergeQueueAheadDto | null;
|
|
489
|
+
repoQueue?: MergeQueueRepoQueueDto | null;
|
|
483
490
|
};
|
package/dist/ui/banner.js
CHANGED
|
@@ -191,6 +191,12 @@ const WHATS_NEW = {
|
|
|
191
191
|
"0.3.51": [
|
|
192
192
|
"Watch names findings held on a throttled head",
|
|
193
193
|
],
|
|
194
|
+
"0.3.52": [
|
|
195
|
+
"Sign-in errors name the agent login",
|
|
196
|
+
],
|
|
197
|
+
"0.3.53": [
|
|
198
|
+
"Busy queue no longer reads as stuck",
|
|
199
|
+
],
|
|
194
200
|
};
|
|
195
201
|
function densityFromVariant(variant) {
|
|
196
202
|
if (variant === "mini" || variant === "nano")
|
package/package.json
CHANGED
|
@@ -22,7 +22,7 @@ mg skill install --claude --cursor --agents
|
|
|
22
22
|
1. There is an already-open GitHub PR on a Vortex-monitored repo. No PR, wrong skill.
|
|
23
23
|
2. Auto-patch (Cyclone) must be off for this PR so you are the only patcher. Do not take anyone's word for it: the settings API and the PR's stack are the source of truth, and step 1 of the loop verifies them before anything else runs. Cyclone is off for the PR when its adopted stack has `autoPatchOverride` `false`, or when that override is unset and the account `auto_patch_enabled` is `false`.
|
|
24
24
|
3. The Bearer key and the Cyclone GitHub App install must be the same Mergestorm account. `stack_list` / `stack_status` expose `keyUserId`, `cycloneInstallUserId`, and `cycloneOwnerMatch`. Cyclone apply always uses the install account, not the key. If those identities differ, your `settings_get` mute reading is about the wrong person.
|
|
25
|
-
4. `whoami` works on the `mergestorm` MCP server (or `mg whoami --json` exits 0). Otherwise stop and ask for install
|
|
25
|
+
4. `whoami` works on the `mergestorm` MCP server (or `mg whoami --json` exits 0). Otherwise stop and ask for install, or sign in yourself: run `mg login --start`, send the person the link it prints, and after they approve it run `mg login --finish`.
|
|
26
26
|
|
|
27
27
|
## Loop (in order)
|
|
28
28
|
|