mergestorm 0.3.39 → 0.3.41
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 +4 -4
- package/dist/api.d.ts +8 -3
- package/dist/api.js +66 -10
- package/dist/commands/browse.d.ts +5 -0
- package/dist/commands/browse.js +18 -1
- package/dist/commands/review.d.ts +2 -1
- package/dist/commands/review.js +1 -0
- package/dist/commands/settings.js +2 -3
- package/dist/commands/stack.js +7 -6
- package/dist/errors.d.ts +4 -1
- package/dist/errors.js +9 -0
- package/dist/stack-blockers.d.ts +7 -17
- package/dist/stack-blockers.js +110 -140
- package/dist/stack-dto.d.ts +7 -8
- package/dist/stack-dto.js +0 -4
- package/dist/stack-watch-loop.js +8 -5
- package/dist/stack-watch-obligation.js +2 -2
- package/dist/ui/banner.js +9 -3
- package/dist/ui/tabs.d.ts +5 -0
- package/dist/ui/tabs.js +5 -3
- package/package.json +1 -1
- package/skill/mergestorm-bounce-watch/SKILL.md +6 -8
- package/skill/mergestorm-pr-loop/SKILL.md +1 -1
package/README.md
CHANGED
|
@@ -45,7 +45,7 @@ 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.41 │
|
|
49
49
|
│ ▀▀▀▀▀▀▀▀▀ ● msk_live_… · maelstrom │
|
|
50
50
|
│ ▀▀▀▀▀▀ [████████░░░░░░░░░░░░░░░░░░░░░░] 25% used │
|
|
51
51
|
│ ▝▀▀▀▀▀ local reviews + stacked PRs · mergestorm.ai │
|
|
@@ -103,12 +103,12 @@ mergestorm dismiss <owner/repo>#<n> --head <sha> --review <id> --finding <id> --
|
|
|
103
103
|
|
|
104
104
|
### Watching a stack in the background
|
|
105
105
|
|
|
106
|
-
`mg stack watch <stack-id>` is for agents that cannot hold a long turn open. Run it as a background command
|
|
106
|
+
`mg stack watch <stack-id>` is for agents that cannot hold a long turn open. Run it as a background command only after confirming that your host can resume the task on completion or output matching `MS-WATCH (ATTENTION|LANDED)`. The process monitors the stack; it does not itself resume a Codex, Claude Code, or Cursor conversation. Without a notification mechanism, keep using bounded `stack_wait` calls or explicitly report that automatic follow-up is unavailable. While it waits it spends no model tokens and talks only to the Mergestorm API, not GitHub.
|
|
107
107
|
|
|
108
108
|
- It calls `stack wait` in slices of at most 300s and carries the cursor (`enrolled_head_sha`, `after_finished_at`, `bounce_id`) from one slice to the next.
|
|
109
109
|
- It prints nothing while the stack is `waiting` or `in_progress`, including while Cyclone or Vortex holds the blocked PR (`busy[]`, `actAfter: "agents_idle"`).
|
|
110
110
|
- On attention it prints `MS-WATCH ATTENTION pr=<n> head=<sha> blocker="<blocker>" repair=<kind>` and exits 3.
|
|
111
|
-
-
|
|
111
|
+
- A confirmed landing prints `MS-WATCH LANDED stack=<id> reason=landed` and exits 0. `closed`, `archived` and `not_found` end the watch with `MS-WATCH ATTENTION stack=<id> reason=<reason>` and exit 3. A missing stack does not prove a landing; verify each PR before reporting it merged.
|
|
112
112
|
- A failed read is retried once. If the retry fails too, it prints `MS-WATCH ATTENTION failed stack=<id> error="<message>"` and exits 3. A rate limit is waited out.
|
|
113
113
|
- `--head <sha>` enrolls the SHA you just pushed. After fixing a blocker, restart the watcher with it.
|
|
114
114
|
- `--ignore <text>` keeps it silent on the first attention whose blocker contains that text (case-insensitive), until the blocker, its PR or its head changes. Use it for a blocker a human has taken over. Repeat the flag for more than one.
|
|
@@ -137,7 +137,7 @@ Use `mergestorm dismiss` when a Vortex finding is wrong and you have checked it
|
|
|
137
137
|
|
|
138
138
|
If the parent branch is already a layer of a **registered** (submitted) stack, `create` and `submit` refuse unless you pass **`--extend`**. That blocks accidentally gluing an unrelated PR onto an open unit. Growing a registered stack on purpose: `mg stack create --onto <tip> --extend` → commit → `mg stack submit --extend`.
|
|
139
139
|
|
|
140
|
-
`stack submit` walks the **active** local stack: `git push -u origin` each layer, opens a PR with `gh` (`--base` = parent or trunk; adopt then moves the bottom of a 2+ PR stack onto `mg-stack-<n>` — leave it there; skips heads that already have an open PR; body from the tip commit — Summary + Test plan, preserving Fixes/Closes/Resolves), registers the stack with `POST /api/v1/stacks/adopt` internally (Mergestorm API key), then drops that local stack entry so the next create onto trunk is clean. Push is always your local `git` credentials. PRs open as you when `gh` is installed and `gh api user` succeeds; otherwise `stack submit` asks the Mergestorm API (`POST /api/v1/stacks/pulls`) to open them as
|
|
140
|
+
`stack submit` walks the **active** local stack: `git push -u origin` each layer, opens a PR with `gh` (`--base` = parent or trunk; adopt then moves the bottom of a 2+ PR stack onto `mg-stack-<n>` — leave it there; skips heads that already have an open PR; body from the tip commit — Summary + Test plan, preserving Fixes/Closes/Resolves), registers the stack with `POST /api/v1/stacks/adopt` internally (Mergestorm API key), then drops that local stack entry so the next create onto trunk is clean. Push is always your local `git` credentials. PRs open as you when `gh` is installed and `gh api user` succeeds; otherwise `stack submit` asks the Mergestorm API (`POST /api/v1/stacks/pulls`) to open them as the Mergestorm Surge App on your behalf, after checking your GitHub account has write access, and assigns you. Stacks, the merge queue and Auto land run through Surge, and Cyclone is the auto-patch App. Without Surge, submit stops before pushing and prints the server's message, which names the App to install and links its install page. `mg settings` prints a `Stacks, merge queue, Auto land` line when the server reports whether that App is ready.
|
|
141
141
|
|
|
142
142
|
Legacy repo-local `.mergestorm/stack.json` state migrates automatically on the next stack command. If local authoring state is stale or malformed, use `mg stack reset --force`; this clears only pre-submit CLI state and never deletes branches, PRs, or registered stacks.
|
|
143
143
|
|
package/dist/api.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { type Config } from "./config.js";
|
|
2
2
|
import { BEARER_SETTINGS_KEYS as SETTINGS_WRITABLE_KEYS, type BearerSettingsKey as SettingsWritableKey } from "./automation-catalog.js";
|
|
3
|
+
import { CommandError } from "./errors.js";
|
|
3
4
|
import type { MergeQueueEntryDto, StackDto } from "./stack-dto.js";
|
|
4
|
-
export type { MergeQueueBounceDetail, MergeQueueBounceKind, MergeQueueEnqueuedBy, MergeQueueEnqueuedVia, MergeQueueEntryDto, MergeQueueEntryState, StackBranchState, StackCiStatus, StackDto, StackLayerChecks, StackLayerDto, StackReviewStatus,
|
|
5
|
+
export type { MergeQueueBounceDetail, MergeQueueBounceKind, MergeQueueEnqueuedBy, MergeQueueEnqueuedVia, MergeQueueEntryDto, MergeQueueEntryState, StackBranchState, StackCiStatus, StackDto, StackLayerChecks, StackLayerDto, StackReviewStatus, StackUnitDto, StackUnitMemberDto, } from "./stack-dto.js";
|
|
5
6
|
/** Default per-request timeout so a hung API cannot stall the shell/CI forever. */
|
|
6
7
|
export declare const DEFAULT_API_TIMEOUT_MS = 30000;
|
|
7
8
|
/** Message prefix of the CommandError thrown when a request exceeds its timeout. */
|
|
@@ -172,6 +173,7 @@ export type SettingsPatch = Partial<import("./automation-catalog.js").BearerSett
|
|
|
172
173
|
export type SettingsResponse = import("./automation-catalog.js").BearerSettingsValues & {
|
|
173
174
|
cyclone_connected: boolean;
|
|
174
175
|
github_connected: boolean;
|
|
176
|
+
stacks_ready?: boolean;
|
|
175
177
|
};
|
|
176
178
|
/**
|
|
177
179
|
* Fetch Bearer /api/v1/settings; null on 404/network/timeouts so panels can
|
|
@@ -228,13 +230,16 @@ export type StackPolicyPatch = {
|
|
|
228
230
|
export declare function stackPolicyBody(policy: StackPolicyPatch | undefined): StackPolicyPatch;
|
|
229
231
|
/** Adopt an open PR chain (Bearer POST /api/v1/stacks/adopt). */
|
|
230
232
|
export declare function adoptStack(owner: string, repo: string, prNumber: number, cfg?: Config, policy?: StackPolicyPatch): Promise<unknown>;
|
|
233
|
+
export declare const SURGE_REFUSAL_FALLBACK = "Mergestorm Surge is not installed or not ready on this repository. Install it from Agents.";
|
|
234
|
+
export declare function surgeRefusal(body: unknown): CommandError | null;
|
|
235
|
+
export declare const GITHUB_UNREADABLE_MESSAGE = "GitHub could not be read just now; try again.";
|
|
231
236
|
/** Mint or reuse the PR3+ park freeze (Bearer POST /api/v1/stacks/:id/ensure-upper-park). */
|
|
232
237
|
export declare function ensureUpperPark(stackId: string, cfg?: Config): Promise<{
|
|
233
238
|
freezeBranch: string;
|
|
234
239
|
created: boolean;
|
|
235
240
|
}>;
|
|
236
|
-
export declare const
|
|
237
|
-
export declare function
|
|
241
|
+
export declare const SURGE_INSTALL_URL = "https://github.com/apps/mergestorm-surge/installations/new";
|
|
242
|
+
export declare function surgeNotInstalledMessage(owner: string, repo: string, installUrl?: string): string;
|
|
238
243
|
export declare function findStackPull(cfg: Config, input: {
|
|
239
244
|
owner: string;
|
|
240
245
|
repo: string;
|
package/dist/api.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { apiBase, loadConfig, resolveApiKey } from "./config.js";
|
|
2
2
|
import { BEARER_SETTINGS_KEYS as SETTINGS_WRITABLE_KEYS, } from "./automation-catalog.js";
|
|
3
|
-
import { CommandError, REVIEW_EXIT, rateLimitedMessage, } from "./errors.js";
|
|
3
|
+
import { CommandError, REVIEW_EXIT, rateLimitedMessage, plainServerText, } from "./errors.js";
|
|
4
4
|
/** Default per-request timeout so a hung API cannot stall the shell/CI forever. */
|
|
5
5
|
export const DEFAULT_API_TIMEOUT_MS = 30_000;
|
|
6
6
|
/** Message prefix of the CommandError thrown when a request exceeds its timeout. */
|
|
@@ -416,12 +416,53 @@ export async function adoptStack(owner, repo, prNumber, cfg, policy) {
|
|
|
416
416
|
: code === "supabase_unconfigured"
|
|
417
417
|
? "Stacks are not configured on this server."
|
|
418
418
|
: undefined;
|
|
419
|
+
const surge = surgeRefusal(body);
|
|
420
|
+
if (surge)
|
|
421
|
+
throw surge;
|
|
419
422
|
throw new CommandError(message?.trim()
|
|
420
423
|
? message
|
|
421
424
|
: fallbackMessage ?? `Failed to import stack (HTTP ${status}): ${JSON.stringify(body)}`, 1, code);
|
|
422
425
|
}
|
|
423
426
|
return body;
|
|
424
427
|
}
|
|
428
|
+
const INFRASTRUCTURE_REFUSAL_CODES = [
|
|
429
|
+
"cyclone_not_connected",
|
|
430
|
+
"cyclone_not_installed",
|
|
431
|
+
"cyclone_permission_missing",
|
|
432
|
+
];
|
|
433
|
+
function refusalReason(body) {
|
|
434
|
+
const reason = (body && typeof body === "object" ? body.reason : undefined);
|
|
435
|
+
return typeof reason === "string" && plainServerText(reason).trim() ? plainServerText(reason).trim() : undefined;
|
|
436
|
+
}
|
|
437
|
+
export const SURGE_REFUSAL_FALLBACK = "Mergestorm Surge is not installed or not ready on this repository. Install it from Agents.";
|
|
438
|
+
export function surgeRefusal(body) {
|
|
439
|
+
if (!body || typeof body !== "object")
|
|
440
|
+
return null;
|
|
441
|
+
const refusal = body;
|
|
442
|
+
const reason = refusalReason(body);
|
|
443
|
+
if (!reason?.startsWith("surge_"))
|
|
444
|
+
return null;
|
|
445
|
+
const error = INFRASTRUCTURE_REFUSAL_CODES.find((code) => code === refusal.error);
|
|
446
|
+
if (!error)
|
|
447
|
+
return null;
|
|
448
|
+
const message = serverMessage(refusal.message) ?? SURGE_REFUSAL_FALLBACK;
|
|
449
|
+
const installUrl = githubAppInstallUrl(refusal.installUrl) ?? "";
|
|
450
|
+
return new CommandError(installUrl && !message.includes(installUrl) ? `${message} (${installUrl})` : message, 1, error, { reason });
|
|
451
|
+
}
|
|
452
|
+
export const GITHUB_UNREADABLE_MESSAGE = "GitHub could not be read just now; try again.";
|
|
453
|
+
function serverMessage(message) {
|
|
454
|
+
if (typeof message !== "string")
|
|
455
|
+
return null;
|
|
456
|
+
const plain = plainServerText(message).trim();
|
|
457
|
+
return plain ? plain : null;
|
|
458
|
+
}
|
|
459
|
+
const GITHUB_APP_INSTALL_URL = /https:\/\/github\.com\/apps\/[^\s)]+\/installations\/new/;
|
|
460
|
+
function githubAppInstallUrl(value) {
|
|
461
|
+
if (typeof value !== "string")
|
|
462
|
+
return null;
|
|
463
|
+
const match = GITHUB_APP_INSTALL_URL.exec(plainServerText(value).trim());
|
|
464
|
+
return match && match.index === 0 ? match[0] : null;
|
|
465
|
+
}
|
|
425
466
|
/** Mint or reuse the PR3+ park freeze (Bearer POST /api/v1/stacks/:id/ensure-upper-park). */
|
|
426
467
|
export async function ensureUpperPark(stackId, cfg) {
|
|
427
468
|
const body = await stackMutation(stackId, "/ensure-upper-park", "POST", {}, "Failed to ensure upper-park freeze", cfg);
|
|
@@ -437,22 +478,25 @@ export async function ensureUpperPark(stackId, cfg) {
|
|
|
437
478
|
created: body.created === true,
|
|
438
479
|
};
|
|
439
480
|
}
|
|
440
|
-
export const
|
|
441
|
-
export function
|
|
442
|
-
return (`Opening PRs needs either the GitHub CLI (gh auth login) or
|
|
481
|
+
export const SURGE_INSTALL_URL = "https://github.com/apps/mergestorm-surge/installations/new";
|
|
482
|
+
export function surgeNotInstalledMessage(owner, repo, installUrl = SURGE_INSTALL_URL) {
|
|
483
|
+
return (`Opening PRs needs either the GitHub CLI (gh auth login) or Mergestorm Surge installed on ` +
|
|
443
484
|
`${owner}/${repo} (${installUrl}).`);
|
|
444
485
|
}
|
|
445
486
|
function installUrlFrom(message) {
|
|
446
487
|
if (typeof message !== "string")
|
|
447
|
-
return
|
|
448
|
-
return message.match(
|
|
488
|
+
return SURGE_INSTALL_URL;
|
|
489
|
+
return message.match(GITHUB_APP_INSTALL_URL)?.[0] ?? SURGE_INSTALL_URL;
|
|
449
490
|
}
|
|
450
491
|
function stackPullFailure(status, body, owner, repo, failLabel) {
|
|
451
492
|
const rejection = (body && typeof body === "object" ? body : {});
|
|
493
|
+
const surge = surgeRefusal(body);
|
|
494
|
+
if (surge)
|
|
495
|
+
return surge;
|
|
452
496
|
const text = `${String(rejection.error ?? "")} ${String(rejection.message ?? "")}`;
|
|
453
497
|
if (rejection.error === "cyclone_not_installed" ||
|
|
454
498
|
/not installed|installation[^\n]*not found/i.test(text)) {
|
|
455
|
-
return new CommandError(
|
|
499
|
+
return new CommandError(surgeNotInstalledMessage(owner, repo, installUrlFrom(rejection.message)), 1, "cyclone_not_installed");
|
|
456
500
|
}
|
|
457
501
|
if (status === 404) {
|
|
458
502
|
return new CommandError("Stacks API cannot open PRs on this server yet. Install the GitHub CLI (gh auth login) and retry.");
|
|
@@ -500,6 +544,9 @@ async function stackMutation(stackId, pathSuffix, method, json, failLabel, cfg)
|
|
|
500
544
|
throw new CommandError("Stacks API is not available on this server yet. Deploy the API update or use the dashboard.");
|
|
501
545
|
}
|
|
502
546
|
if (status !== 200) {
|
|
547
|
+
const surge = surgeRefusal(body);
|
|
548
|
+
if (surge)
|
|
549
|
+
throw surge;
|
|
503
550
|
const rejection = body;
|
|
504
551
|
if (method === "PATCH" && rejection?.error === "cyclone_not_connected") {
|
|
505
552
|
throw new CommandError(typeof rejection.message === "string" ? rejection.message : rejection.error, 1, "cyclone_not_connected");
|
|
@@ -554,7 +601,7 @@ export async function listMergeQueueEntries(cfg, opts) {
|
|
|
554
601
|
}
|
|
555
602
|
return entries;
|
|
556
603
|
}
|
|
557
|
-
async function queueMutation(route, failLabel, cfg) {
|
|
604
|
+
async function queueMutation(route, failLabel, cfg, opts = {}) {
|
|
558
605
|
const resolved = cfg ?? (await loadConfig());
|
|
559
606
|
const { status, body } = await apiFetch(resolved, route, {
|
|
560
607
|
method: "POST",
|
|
@@ -572,18 +619,27 @@ async function queueMutation(route, failLabel, cfg) {
|
|
|
572
619
|
}
|
|
573
620
|
if (status === 409) {
|
|
574
621
|
const refusal = body;
|
|
575
|
-
if (refusal?.error === "conflict_unchanged"
|
|
622
|
+
if ((refusal?.error === "conflict_unchanged" || refusal?.error === "ci_failed") &&
|
|
623
|
+
typeof refusal.message === "string") {
|
|
576
624
|
throw new CommandError(refusal.message);
|
|
577
625
|
}
|
|
578
626
|
}
|
|
627
|
+
if (opts.githubUnreadableRefusal &&
|
|
628
|
+
status === 503 &&
|
|
629
|
+
body?.error === "github_unreadable") {
|
|
630
|
+
throw new CommandError(serverMessage(body.message) ?? GITHUB_UNREADABLE_MESSAGE, 1, "github_unreadable");
|
|
631
|
+
}
|
|
579
632
|
if (status < 200 || status >= 300) {
|
|
633
|
+
const surge = surgeRefusal(body);
|
|
634
|
+
if (surge)
|
|
635
|
+
throw surge;
|
|
580
636
|
throw new CommandError(`${failLabel} (HTTP ${status}): ${JSON.stringify(body)}`);
|
|
581
637
|
}
|
|
582
638
|
return body;
|
|
583
639
|
}
|
|
584
640
|
/** Enqueue a stack for verified landing. */
|
|
585
641
|
export async function enqueueStack(stackId, cfg) {
|
|
586
|
-
return queueMutation(`/api/v1/stacks/${encodeURIComponent(stackId)}/enqueue`, "Failed to add stack to merge queue", cfg);
|
|
642
|
+
return queueMutation(`/api/v1/stacks/${encodeURIComponent(stackId)}/enqueue`, "Failed to add stack to merge queue", cfg, { githubUnreadableRefusal: true });
|
|
587
643
|
}
|
|
588
644
|
/** Cancel a live merge-queue entry by entry id. */
|
|
589
645
|
export async function cancelMergeQueueEntry(entryId, cfg) {
|
|
@@ -28,6 +28,11 @@ export declare function buildStatusTabLines(input: StatusTabInput): string[];
|
|
|
28
28
|
export declare function formatJobDetailLines(row: ReviewJobRow): string[];
|
|
29
29
|
/** Human labels for the Config tab and the static `settings` printout. */
|
|
30
30
|
export declare const SETTINGS_LABELS: Record<"auto_review_enabled" | "auto_patch_enabled" | "vortex_show_thinking_traces" | "repo_overview_enabled" | "review_unit_land_prs_enabled" | "cyclone_skip_ci_enabled" | "cyclone_patch_unverified_languages" | "vortex_auto_overflow_enabled" | "vortex_skip_all_clear_comments" | "vortex_seam_specialist_enabled" | "auto_land_default" | "auto_land_settle_seconds" | "merge_queue_batch_enabled" | "merge_queue_batch_size" | "ignored_bot_logins" | "vortex_bot_skip_check" | "vortex_findings_check" | "cyclone_patch_failure_check", string>;
|
|
31
|
+
export declare const STACKS_READY_LABEL = "Stacks, merge queue, Auto land";
|
|
32
|
+
export declare const STACKS_READY_WORDS: {
|
|
33
|
+
readonly on: "ready";
|
|
34
|
+
readonly off: "not ready (install Mergestorm Surge)";
|
|
35
|
+
};
|
|
31
36
|
/** Config rows in a stable order: writable toggles, then connected flags. */
|
|
32
37
|
export declare function buildConfigRows(settings: SettingsResponse): TabsConfigRow[];
|
|
33
38
|
/**
|
package/dist/commands/browse.js
CHANGED
|
@@ -121,8 +121,14 @@ export function formatJobDetailLines(row) {
|
|
|
121
121
|
}
|
|
122
122
|
/** Human labels for the Config tab and the static `settings` printout. */
|
|
123
123
|
export const SETTINGS_LABELS = BEARER_SETTINGS_LABELS;
|
|
124
|
+
export const STACKS_READY_LABEL = "Stacks, merge queue, Auto land";
|
|
125
|
+
export const STACKS_READY_WORDS = {
|
|
126
|
+
on: "ready",
|
|
127
|
+
off: "not ready (install Mergestorm Surge)",
|
|
128
|
+
};
|
|
124
129
|
/** Config rows in a stable order: writable toggles, then connected flags. */
|
|
125
130
|
export function buildConfigRows(settings) {
|
|
131
|
+
const stacksReadyKnown = typeof settings.stacks_ready === "boolean";
|
|
126
132
|
return [
|
|
127
133
|
...BEARER_BOOLEAN_SETTINGS_KEYS.map((key) => ({
|
|
128
134
|
key,
|
|
@@ -130,9 +136,20 @@ export function buildConfigRows(settings) {
|
|
|
130
136
|
value: settings[key] === true,
|
|
131
137
|
writable: true,
|
|
132
138
|
})),
|
|
139
|
+
...(stacksReadyKnown
|
|
140
|
+
? [
|
|
141
|
+
{
|
|
142
|
+
key: "stacks_ready",
|
|
143
|
+
label: STACKS_READY_LABEL,
|
|
144
|
+
value: settings.stacks_ready === true,
|
|
145
|
+
writable: false,
|
|
146
|
+
readOnlyWords: STACKS_READY_WORDS,
|
|
147
|
+
},
|
|
148
|
+
]
|
|
149
|
+
: []),
|
|
133
150
|
{
|
|
134
151
|
key: "cyclone_connected",
|
|
135
|
-
label: "Cyclone",
|
|
152
|
+
label: stacksReadyKnown ? "Cyclone (auto patch)" : "Cyclone",
|
|
136
153
|
value: settings.cyclone_connected === true,
|
|
137
154
|
writable: false,
|
|
138
155
|
},
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { REVIEW_POLL_MAX_TRANSIENT_RETRIES, isTransientReviewPollError, isTransientReviewPollStatus } from "./review-client.js";
|
|
1
|
+
import { REVIEW_POLL_MAX_TRANSIENT_RETRIES, isTransientReviewPollError, isTransientReviewPollStatus, type PollReviewOptions } from "./review-client.js";
|
|
2
2
|
export { REVIEW_POLL_MAX_TRANSIENT_RETRIES, isTransientReviewPollError, isTransientReviewPollStatus, };
|
|
3
3
|
/** Same public specialist allowlist as the API contract and migration 117. */
|
|
4
4
|
export declare const VORTEX_ROUTER_MODES: readonly ["off", "standard", "max", "manual"];
|
|
@@ -43,6 +43,7 @@ export type ReviewOptions = {
|
|
|
43
43
|
interactive?: boolean;
|
|
44
44
|
/** Test seam; production uses the two-second poll interval. */
|
|
45
45
|
pollIntervalMs?: number;
|
|
46
|
+
pollClock?: Pick<PollReviewOptions, "sleep" | "now" | "random">;
|
|
46
47
|
};
|
|
47
48
|
export declare function reviewStatusHint(jobId: string): string;
|
|
48
49
|
/** Append recovery hint once a job id exists (idempotent). */
|
package/dist/commands/review.js
CHANGED
|
@@ -357,6 +357,7 @@ export async function cmdReview(args, opts = {}) {
|
|
|
357
357
|
row = await pollReview(cfg, jobId, {
|
|
358
358
|
timeoutMs: parsed.timeoutMs,
|
|
359
359
|
intervalMs: opts.pollIntervalMs,
|
|
360
|
+
...opts.pollClock,
|
|
360
361
|
signal: opts.signal,
|
|
361
362
|
propagateTimeout: interactive,
|
|
362
363
|
onTick(kind) {
|
|
@@ -3,6 +3,7 @@ import { BEARER_SETTINGS, BEARER_SETTINGS_FLAGS } from "../automation-catalog.js
|
|
|
3
3
|
import { loadConfig } from "../config.js";
|
|
4
4
|
import { CommandError } from "../errors.js";
|
|
5
5
|
import { present } from "../ui/present.js";
|
|
6
|
+
import { readOnlyRowWords } from "../ui/tabs.js";
|
|
6
7
|
import { buildConfigRows, canBrowse, openTabsBrowser } from "./browse.js";
|
|
7
8
|
/**
|
|
8
9
|
* CLI flag → Bearer settings key. Boolean flags take on|off. The connected flags are
|
|
@@ -91,9 +92,7 @@ export function formatSettingsLines(settings) {
|
|
|
91
92
|
? row.value
|
|
92
93
|
? "on"
|
|
93
94
|
: "off"
|
|
94
|
-
: row
|
|
95
|
-
? "connected"
|
|
96
|
-
: "not connected";
|
|
95
|
+
: readOnlyRowWords(row);
|
|
97
96
|
return ` ${row.label.padEnd(labelWidth + 2)}${value}`;
|
|
98
97
|
});
|
|
99
98
|
}
|
package/dist/commands/stack.js
CHANGED
|
@@ -37,7 +37,7 @@ export function stackWatchTextLines(watch) {
|
|
|
37
37
|
const next = watch.next;
|
|
38
38
|
return [
|
|
39
39
|
...(next ? [
|
|
40
|
-
`
|
|
40
|
+
`Only with a confirmed host notification that resumes this task: run ${next.background} as a background command (notify on ${STACK_WATCH_NOTIFY_PATTERN}), then end your turn`,
|
|
41
41
|
`Or poll: ${next.command} (MCP: stack_wait ${JSON.stringify(next.args)})`,
|
|
42
42
|
] : []),
|
|
43
43
|
STACK_WATCH_NOT_DONE_SENTENCE,
|
|
@@ -58,7 +58,7 @@ const STACK_USAGE = `usage:
|
|
|
58
58
|
mergestorm stack watch <stack-id> [--until attention|landed] [--ignore <blocker-substring>] [--max <minutes>] [--head <sha>] [--json]
|
|
59
59
|
Loops stack wait with the cursor and stays silent while the stack is waiting or in progress.
|
|
60
60
|
Prints one MS-WATCH ATTENTION line and exits 3 on attention or a repeated failed read; prints
|
|
61
|
-
MS-WATCH LANDED and exits 0
|
|
61
|
+
MS-WATCH LANDED and exits 0 only for a confirmed landing. Other terminal reasons exit 3 with ATTENTION. Run it as a background command.
|
|
62
62
|
--json writes exactly one JSON document to stdout and sends the MS-WATCH lines to stderr.
|
|
63
63
|
mergestorm stack set <stack-id> [--auto-land on|off] [--auto-review on|off|default] [--auto-patch on|off|default] [--json]
|
|
64
64
|
mergestorm stack adopt <owner/repo>#<pr> [--auto-land on|off] [--auto-review on|off] [--auto-patch on|off] [--json]
|
|
@@ -996,7 +996,9 @@ export async function cmdStackWatch(args, deps = {}) {
|
|
|
996
996
|
? `Stopped watching stack ${stackId} after --max; it is not landed.`
|
|
997
997
|
: result.outcome === "aborted"
|
|
998
998
|
? `Stopped watching stack ${stackId}; it is not landed.`
|
|
999
|
-
:
|
|
999
|
+
: result.envelope?.watch.reason === "not_found"
|
|
1000
|
+
? `Stack ${stackId} was not found; landing is unconfirmed. Verify each PR before reporting it landed.`
|
|
1001
|
+
: `Stack ${stackId} needs attention; it is not landed.`;
|
|
1000
1002
|
throw new CommandError(reason, result.exitCode, result.outcome === "timeout" ? "review_timeout" : "stack_attention");
|
|
1001
1003
|
}
|
|
1002
1004
|
export async function cmdStackStatus(args, deps = {}) {
|
|
@@ -1040,8 +1042,7 @@ export async function cmdStackStatus(args, deps = {}) {
|
|
|
1040
1042
|
lines.push(` ${String(layer.position).padStart(2)} ${pr}${head} ${layer.state.padEnd(14)} ${layer.branch}` +
|
|
1041
1043
|
` CI: ${layer.ciStatus ?? "unknown"} review: ${layer.reviewStatus ?? "unknown"}` +
|
|
1042
1044
|
(layer.vortexStatus ? ` Vortex: ${layer.vortexStatus}` : "") +
|
|
1043
|
-
(layer.cycloneStatus ? ` Cyclone: ${layer.cycloneStatus}` : "")
|
|
1044
|
-
(layer.tempestStatus ? ` Tempest: ${layer.tempestStatus}` : ""));
|
|
1045
|
+
(layer.cycloneStatus ? ` Cyclone: ${layer.cycloneStatus}` : ""));
|
|
1045
1046
|
}
|
|
1046
1047
|
if (queueWait && !attention)
|
|
1047
1048
|
lines.push(` Queue: #${queueWait.prNumber} ${queueWait.text}`);
|
|
@@ -1147,7 +1148,7 @@ async function cmdStackLand(args) {
|
|
|
1147
1148
|
await present("Stack", [
|
|
1148
1149
|
ansi.brightGreen(` Promoted` +
|
|
1149
1150
|
(pr != null ? ` #${pr}` : "") +
|
|
1150
|
-
` into
|
|
1151
|
+
` into IU ${uNumber}` +
|
|
1151
1152
|
(branch ? ` (${branch})` : "") +
|
|
1152
1153
|
` on ${id}`),
|
|
1153
1154
|
]);
|
package/dist/errors.d.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Stable machine codes for CommandError control flow.
|
|
3
3
|
* UX copy in `message` may change freely; match on `code`, never on message text.
|
|
4
4
|
*/
|
|
5
|
-
export type CommandErrorCode = "auth_invalid" | "api_timeout" | "not_a_repo" | "not_found" | "missing_api_key" | "registered_parent" | "usage" | "review_quota" | "review_failed" | "review_timeout" | "rate_limited" | "cyclone_not_installed" | "pr_merged" | "pr_closed" | "not_stacked" | "draft" | "reserved_branch" | "supabase_unconfigured" | "cyclone_not_connected" | "busy" | "adopt_failed" | "stack_attention";
|
|
5
|
+
export type CommandErrorCode = "auth_invalid" | "api_timeout" | "not_a_repo" | "not_found" | "missing_api_key" | "registered_parent" | "usage" | "review_quota" | "review_failed" | "review_timeout" | "rate_limited" | "cyclone_not_installed" | "pr_merged" | "pr_closed" | "not_stacked" | "draft" | "reserved_branch" | "supabase_unconfigured" | "cyclone_not_connected" | "cyclone_permission_missing" | "busy" | "adopt_failed" | "github_unreadable" | "stack_attention";
|
|
6
6
|
/** Stable one-shot exit codes for machine callers of `mergestorm review`. */
|
|
7
7
|
export declare const REVIEW_EXIT: {
|
|
8
8
|
readonly success: 0;
|
|
@@ -15,14 +15,17 @@ export declare const REVIEW_EXIT: {
|
|
|
15
15
|
};
|
|
16
16
|
export type CommandErrorExtras = {
|
|
17
17
|
retryAfterSeconds?: number;
|
|
18
|
+
reason?: string;
|
|
18
19
|
};
|
|
19
20
|
/** Thrown by command handlers; one-shot mode maps `exitCode` onto process.exitCode. */
|
|
20
21
|
export declare class CommandError extends Error {
|
|
21
22
|
readonly exitCode: number;
|
|
22
23
|
readonly code?: CommandErrorCode;
|
|
23
24
|
readonly retryAfterSeconds?: number;
|
|
25
|
+
readonly reason?: string;
|
|
24
26
|
constructor(message: string, exitCode?: number, code?: CommandErrorCode, extras?: CommandErrorExtras);
|
|
25
27
|
}
|
|
28
|
+
export declare function plainServerText(text: string): string;
|
|
26
29
|
export declare const RATE_LIMITED_HINT = "Rate limited. Wait for a running review or mergestorm jobs.";
|
|
27
30
|
export declare function rateLimitedMessage(retryAfterSeconds?: number): string;
|
|
28
31
|
export declare function isCommandErrorCode(err: unknown, code: CommandErrorCode): err is CommandError;
|
package/dist/errors.js
CHANGED
|
@@ -13,6 +13,7 @@ export class CommandError extends Error {
|
|
|
13
13
|
exitCode;
|
|
14
14
|
code;
|
|
15
15
|
retryAfterSeconds;
|
|
16
|
+
reason;
|
|
16
17
|
constructor(message, exitCode = 1, code, extras) {
|
|
17
18
|
super(message);
|
|
18
19
|
this.name = "CommandError";
|
|
@@ -21,8 +22,16 @@ export class CommandError extends Error {
|
|
|
21
22
|
if (extras?.retryAfterSeconds !== undefined) {
|
|
22
23
|
this.retryAfterSeconds = extras.retryAfterSeconds;
|
|
23
24
|
}
|
|
25
|
+
if (extras?.reason !== undefined) {
|
|
26
|
+
this.reason = extras.reason;
|
|
27
|
+
}
|
|
24
28
|
}
|
|
25
29
|
}
|
|
30
|
+
const ANSI_SEQUENCE = /\u001b\][^\u0007\u001b]*(?:\u0007|\u001b\\)|\u001b\[[0-?]*[ -/]*[@-~]/g;
|
|
31
|
+
const CONTROL_CHARACTER = /[\u0000-\u0008\u000b-\u001f\u007f-\u009f]/g;
|
|
32
|
+
export function plainServerText(text) {
|
|
33
|
+
return text.replace(ANSI_SEQUENCE, "").replace(CONTROL_CHARACTER, "");
|
|
34
|
+
}
|
|
26
35
|
export const RATE_LIMITED_HINT = "Rate limited. Wait for a running review or mergestorm jobs.";
|
|
27
36
|
export function rateLimitedMessage(retryAfterSeconds) {
|
|
28
37
|
if (retryAfterSeconds == null || !Number.isFinite(retryAfterSeconds)) {
|
package/dist/stack-blockers.d.ts
CHANGED
|
@@ -6,6 +6,10 @@ export type StackBlocker = {
|
|
|
6
6
|
blocker: string;
|
|
7
7
|
bounceKind: string | null;
|
|
8
8
|
};
|
|
9
|
+
export type StackBlockerReason = "restack_conflict" | "restack_failed" | "draft_pr" | "merge_conflict" | "ci_failed" | "vortex_failed" | "vortex_out_of_quota" | "vortex_skipped" | "vortex_findings_need_person" | "seam_failed" | "seam_findings" | "seam_not_running" | "seam_review_stuck" | "seam_rereview_not_running" | "seam_verdict_stale" | "unit_abandoned" | "land_gate" | "bounce" | "auto_land_off" | "auto_land_ci_pending" | "auto_land_members_remaining" | "auto_land_vortex_wait" | "auto_land_promote_failed" | "auto_land_ci_unreadable" | "auto_land_waited" | "queue_stalled" | "land_pr_missing";
|
|
10
|
+
export type ClassifiedStackBlocker = StackBlocker & {
|
|
11
|
+
reason: StackBlockerReason;
|
|
12
|
+
};
|
|
9
13
|
export type StackAgentBusy = {
|
|
10
14
|
prNumber: number;
|
|
11
15
|
headSha: string | null;
|
|
@@ -47,20 +51,6 @@ export type StackRepairHint = {
|
|
|
47
51
|
branch: string;
|
|
48
52
|
failingCheck: string | null;
|
|
49
53
|
steps: string;
|
|
50
|
-
} | {
|
|
51
|
-
kind: "tempest_findings";
|
|
52
|
-
prNumber: number;
|
|
53
|
-
headSha: string | null;
|
|
54
|
-
branch: string;
|
|
55
|
-
findings: string;
|
|
56
|
-
steps: string;
|
|
57
|
-
} | {
|
|
58
|
-
kind: "tempest_rerun";
|
|
59
|
-
prNumber: number;
|
|
60
|
-
headSha: string | null;
|
|
61
|
-
branch: string;
|
|
62
|
-
stoppedByPerson: boolean;
|
|
63
|
-
steps: string;
|
|
64
54
|
} | {
|
|
65
55
|
kind: "vortex_findings";
|
|
66
56
|
prNumber: number;
|
|
@@ -121,7 +111,7 @@ export type StackRepairHint = {
|
|
|
121
111
|
waitReason: string | null;
|
|
122
112
|
steps: string;
|
|
123
113
|
} | {
|
|
124
|
-
kind: "members_remaining" | "vortex_failed"
|
|
114
|
+
kind: "members_remaining" | "vortex_failed";
|
|
125
115
|
prNumber: number;
|
|
126
116
|
headSha: string | null;
|
|
127
117
|
branch: string;
|
|
@@ -211,8 +201,8 @@ export declare function restackFailedRepairSteps(branch: string, liveParent: str
|
|
|
211
201
|
kind: string;
|
|
212
202
|
detail?: string | null;
|
|
213
203
|
}): string;
|
|
214
|
-
export declare function agentsCannotClear(
|
|
215
|
-
export declare function stackRepair(stack: StackDto, attention:
|
|
204
|
+
export declare function agentsCannotClear(reason: StackBlockerReason): boolean;
|
|
205
|
+
export declare function stackRepair(stack: StackDto, attention: ClassifiedStackBlocker | null, layer: StackLayerDto | undefined, bounce: MergeQueueEntryDto | undefined, queueWait?: MergeQueueWait | null): StackRepairHint | null;
|
|
216
206
|
export declare const LAND_PR_MISSING_PREFIX = "No open land PR for ";
|
|
217
207
|
/** Pair-gate attention plus live hard blocks on every other open layer. */
|
|
218
208
|
export declare function stackBlockers(stack: StackDto, entries?: MergeQueueEntryDto[], cursor?: Pick<StackWatchCursor, "bounceId" | "afterFinishedAt">, nowMs?: number): {
|