mergestorm 0.3.38 → 0.3.40
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.d.ts +6 -1
- package/dist/api.js +61 -5
- package/dist/client/index.d.ts +1 -1
- package/dist/client/index.js +1 -1
- package/dist/commands/browse.d.ts +5 -0
- package/dist/commands/browse.js +18 -1
- package/dist/commands/review-client.d.ts +8 -1
- package/dist/commands/review-client.js +18 -3
- 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 +1 -2
- package/dist/errors.d.ts +4 -1
- package/dist/errors.js +9 -0
- package/dist/stack-blockers.d.ts +26 -16
- package/dist/stack-blockers.js +197 -117
- package/dist/stack-dto.d.ts +7 -8
- package/dist/stack-dto.js +0 -2
- package/dist/ui/banner.js +7 -1
- 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 +4 -3
- package/skill/mergestorm-pr-loop/SKILL.md +2 -2
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.40 │
|
|
49
49
|
│ ▀▀▀▀▀▀▀▀▀ ● msk_live_… · maelstrom │
|
|
50
50
|
│ ▀▀▀▀▀▀ [████████░░░░░░░░░░░░░░░░░░░░░░] 25% used │
|
|
51
51
|
│ ▝▀▀▀▀▀ local reviews + stacked PRs · mergestorm.ai │
|
|
@@ -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 infrastructure App on your behalf, after checking your GitHub account has write access, and assigns you. That App is Mergestorm Surge, or Cyclone on accounts not yet moved to Surge; stacks, the merge queue and Auto land run through it, and Cyclone is the auto-patch App. Without either, 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,6 +230,9 @@ 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;
|
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);
|
|
@@ -445,10 +486,13 @@ export function cycloneNotInstalledMessage(owner, repo, installUrl = CYCLONE_INS
|
|
|
445
486
|
function installUrlFrom(message) {
|
|
446
487
|
if (typeof message !== "string")
|
|
447
488
|
return CYCLONE_INSTALL_URL;
|
|
448
|
-
return message.match(
|
|
489
|
+
return message.match(GITHUB_APP_INSTALL_URL)?.[0] ?? CYCLONE_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)) {
|
|
@@ -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) {
|
package/dist/client/index.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ export { DEFAULT_API, apiBase, clearConfig, configPath, keyDisplay, loadConfig,
|
|
|
2
2
|
export { BEARER_SETTINGS, BEARER_BOOLEAN_SETTINGS_KEYS, BEARER_SETTINGS_FLAGS, BEARER_SETTINGS_KEYS, BEARER_SETTINGS_LABELS, type BearerSettingsKey, } from "../automation-catalog.js";
|
|
3
3
|
export { API_TIMEOUT_PREFIX, DEFAULT_API_TIMEOUT_MS, SETTINGS_WRITABLE_KEYS, adoptStack, apiFetch, cancelMergeQueueEntry, devicePost, dismissPrFindings, enqueueStack, ensureUpperPark, getEnrichedStack, getMe, getPrVortexReview, getReview, getSettings, landNextStack, listJobs, listMergeQueueEntries, listStacks, listThreads, parseRetryAfterSeconds, patchSettings, restackStack, setStackAutoLand, setStackPolicy, stackPolicyBody, type ApiFetchInit, type ApiFetchResult, type JobListItem, type MeResponse, type MergeQueueBounceDetail, type MergeQueueBounceKind, type MergeQueueEnqueuedBy, type MergeQueueEnqueuedVia, type MergeQueueEntryDto, type MergeQueueEntryState, type PrDismissedFinding, type PrFindingDismissInput, type PrFindingDismissOutcome, type PrFindingDismissResult, type PrReviewFinding, type PrReviewFindings, type PrVortexReview, type SettingsPatch, type SettingsResponse, type SettingsWritableKey, type StackDto, type StackPolicyPatch, type ThreadDetail, type ThreadListItem, } from "../api.js";
|
|
4
4
|
export { CommandError, DetachedError, RATE_LIMITED_HINT, REVIEW_EXIT, isCommandErrorCode, rateLimitedMessage, type CommandErrorCode, type CommandErrorExtras, } from "../errors.js";
|
|
5
|
-
export { REVIEW_POLL_BACKOFF_MS, REVIEW_POLL_DEFAULT_TIMEOUT_MS, REVIEW_POLL_INTERVAL_MS, REVIEW_POLL_MAX_INTERVAL_MS, REVIEW_POLL_MAX_TRANSIENT_RETRIES, REVIEW_POLL_STRETCH_AFTER_MS, REVIEW_POLL_STRETCH_SPAN_MS, REVIEW_THREAD_SLUG_RE, PrReviewPollTimeoutError, ReviewPollTimeoutError, collectReviewInput, formatReviewSubmitError, isTransientReviewPollError, isTransientReviewPollStatus, loadReviewContext, pollPrVortexReview, pollReview, stretchedPollIntervalMs, submitReview, transientRetryWaitMs, type PollReviewOptions, type PollPrVortexReviewOptions, type ReviewInput, type SubmitReviewOptions, } from "../commands/review-client.js";
|
|
5
|
+
export { REVIEW_POLL_BACKOFF_MS, REVIEW_POLL_DEFAULT_TIMEOUT_MS, REVIEW_POLL_INTERVAL_MS, REVIEW_POLL_MAX_INTERVAL_MS, REVIEW_POLL_MAX_TRANSIENT_RETRIES, REVIEW_POLL_STRETCH_AFTER_MS, REVIEW_POLL_STRETCH_SPAN_MS, REVIEW_THREAD_SLUG_RE, PrReviewPollTimeoutError, ReviewPollTimeoutError, noPrReviewYetMessage, collectReviewInput, formatReviewSubmitError, isTransientReviewPollError, isTransientReviewPollStatus, loadReviewContext, pollPrVortexReview, pollReview, stretchedPollIntervalMs, submitReview, transientRetryWaitMs, type PollReviewOptions, type PollPrVortexReviewOptions, type ReviewInput, type SubmitReviewOptions, } from "../commands/review-client.js";
|
|
6
6
|
export { REVIEW_JOB_ENVELOPE_SCHEMA, toReviewJobEnvelope, type ReviewEnvelopeFallbacks, type ReviewJobEnvelope, type ReviewJobRow, } from "../ui/job-envelope.js";
|
|
7
7
|
export { discoverTrunk } from "../git-stack.js";
|
|
8
8
|
export { BOUNCE_KIND_LABELS, deriveStackAgentsBusy, mergeQueueBounceLabel, type StackAgentsBusy } from "../stack-dto.js";
|
package/dist/client/index.js
CHANGED
|
@@ -2,7 +2,7 @@ export { DEFAULT_API, apiBase, clearConfig, configPath, keyDisplay, loadConfig,
|
|
|
2
2
|
export { BEARER_SETTINGS, BEARER_BOOLEAN_SETTINGS_KEYS, BEARER_SETTINGS_FLAGS, BEARER_SETTINGS_KEYS, BEARER_SETTINGS_LABELS, } from "../automation-catalog.js";
|
|
3
3
|
export { API_TIMEOUT_PREFIX, DEFAULT_API_TIMEOUT_MS, SETTINGS_WRITABLE_KEYS, adoptStack, apiFetch, cancelMergeQueueEntry, devicePost, dismissPrFindings, enqueueStack, ensureUpperPark, getEnrichedStack, getMe, getPrVortexReview, getReview, getSettings, landNextStack, listJobs, listMergeQueueEntries, listStacks, listThreads, parseRetryAfterSeconds, patchSettings, restackStack, setStackAutoLand, setStackPolicy, stackPolicyBody, } from "../api.js";
|
|
4
4
|
export { CommandError, DetachedError, RATE_LIMITED_HINT, REVIEW_EXIT, isCommandErrorCode, rateLimitedMessage, } from "../errors.js";
|
|
5
|
-
export { REVIEW_POLL_BACKOFF_MS, REVIEW_POLL_DEFAULT_TIMEOUT_MS, REVIEW_POLL_INTERVAL_MS, REVIEW_POLL_MAX_INTERVAL_MS, REVIEW_POLL_MAX_TRANSIENT_RETRIES, REVIEW_POLL_STRETCH_AFTER_MS, REVIEW_POLL_STRETCH_SPAN_MS, REVIEW_THREAD_SLUG_RE, PrReviewPollTimeoutError, ReviewPollTimeoutError, collectReviewInput, formatReviewSubmitError, isTransientReviewPollError, isTransientReviewPollStatus, loadReviewContext, pollPrVortexReview, pollReview, stretchedPollIntervalMs, submitReview, transientRetryWaitMs, } from "../commands/review-client.js";
|
|
5
|
+
export { REVIEW_POLL_BACKOFF_MS, REVIEW_POLL_DEFAULT_TIMEOUT_MS, REVIEW_POLL_INTERVAL_MS, REVIEW_POLL_MAX_INTERVAL_MS, REVIEW_POLL_MAX_TRANSIENT_RETRIES, REVIEW_POLL_STRETCH_AFTER_MS, REVIEW_POLL_STRETCH_SPAN_MS, REVIEW_THREAD_SLUG_RE, PrReviewPollTimeoutError, ReviewPollTimeoutError, noPrReviewYetMessage, collectReviewInput, formatReviewSubmitError, isTransientReviewPollError, isTransientReviewPollStatus, loadReviewContext, pollPrVortexReview, pollReview, stretchedPollIntervalMs, submitReview, transientRetryWaitMs, } from "../commands/review-client.js";
|
|
6
6
|
export { REVIEW_JOB_ENVELOPE_SCHEMA, toReviewJobEnvelope, } from "../ui/job-envelope.js";
|
|
7
7
|
export { discoverTrunk } from "../git-stack.js";
|
|
8
8
|
export { BOUNCE_KIND_LABELS, deriveStackAgentsBusy, mergeQueueBounceLabel } from "../stack-dto.js";
|
|
@@ -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
|
},
|
|
@@ -84,12 +84,19 @@ export declare class ReviewPollTimeoutError extends Error {
|
|
|
84
84
|
readonly lastRow: ReviewJobRow;
|
|
85
85
|
constructor(jobId: string, lastRow: ReviewJobRow);
|
|
86
86
|
}
|
|
87
|
+
export type PrReviewWaitTarget = {
|
|
88
|
+
afterSha?: string;
|
|
89
|
+
pass?: number;
|
|
90
|
+
afterPass?: number;
|
|
91
|
+
};
|
|
92
|
+
export declare function noPrReviewYetMessage(owner: string, repo: string, prNumber: number, target?: PrReviewWaitTarget): string;
|
|
87
93
|
export declare class PrReviewPollTimeoutError extends Error {
|
|
88
94
|
readonly owner: string;
|
|
89
95
|
readonly repo: string;
|
|
90
96
|
readonly prNumber: number;
|
|
91
97
|
readonly lastEnvelope: PrVortexReview | null;
|
|
92
|
-
|
|
98
|
+
readonly reviewSeen: boolean;
|
|
99
|
+
constructor(owner: string, repo: string, prNumber: number, lastEnvelope: PrVortexReview | null, target?: PrReviewWaitTarget, reviewSeen?: boolean);
|
|
93
100
|
}
|
|
94
101
|
export type PollReviewOptions = {
|
|
95
102
|
timeoutMs?: number;
|
|
@@ -249,18 +249,31 @@ export class ReviewPollTimeoutError extends Error {
|
|
|
249
249
|
this.lastRow = lastRow;
|
|
250
250
|
}
|
|
251
251
|
}
|
|
252
|
+
export function noPrReviewYetMessage(owner, repo, prNumber, target = {}) {
|
|
253
|
+
const sha = target.afterSha?.trim();
|
|
254
|
+
const pass = target.pass !== undefined
|
|
255
|
+
? `Vortex pass ${target.pass}`
|
|
256
|
+
: target.afterPass !== undefined ? `Vortex pass after pass ${target.afterPass}` : "Vortex review";
|
|
257
|
+
return `No ${pass} exists for ${owner}/${repo}#${prNumber}${sha ? ` at ${sha.slice(0, 12)}` : ""} yet. ` +
|
|
258
|
+
"If the push was in the last few minutes, wait once more. If it is still missing after that, Vortex is not reviewing this head " +
|
|
259
|
+
"(auto-review may be off for the repo or the stack, or the push did not reach Mergestorm): " +
|
|
260
|
+
`comment "@mergestorm-vortex review" on the PR to run it, or review the branch locally with mg review; a new push also queues a review.`;
|
|
261
|
+
}
|
|
252
262
|
export class PrReviewPollTimeoutError extends Error {
|
|
253
263
|
owner;
|
|
254
264
|
repo;
|
|
255
265
|
prNumber;
|
|
256
266
|
lastEnvelope;
|
|
257
|
-
|
|
258
|
-
|
|
267
|
+
reviewSeen;
|
|
268
|
+
constructor(owner, repo, prNumber, lastEnvelope, target = {}, reviewSeen = lastEnvelope !== null) {
|
|
269
|
+
super(`Timed out waiting for PR review ${owner}/${repo}#${prNumber}` +
|
|
270
|
+
(reviewSeen ? "" : `. ${noPrReviewYetMessage(owner, repo, prNumber, target)}`));
|
|
259
271
|
this.name = "PrReviewPollTimeoutError";
|
|
260
272
|
this.owner = owner;
|
|
261
273
|
this.repo = repo;
|
|
262
274
|
this.prNumber = prNumber;
|
|
263
275
|
this.lastEnvelope = lastEnvelope;
|
|
276
|
+
this.reviewSeen = reviewSeen;
|
|
264
277
|
}
|
|
265
278
|
}
|
|
266
279
|
function shaMatches(actual, expected) {
|
|
@@ -286,6 +299,7 @@ export async function pollPrVortexReview(cfg, owner, repo, prNumber, opts = {})
|
|
|
286
299
|
applyPrReviewQuery(query, opts);
|
|
287
300
|
let transientFailures = 0;
|
|
288
301
|
let lastEnvelope = null;
|
|
302
|
+
let apiTimeoutSeen = false;
|
|
289
303
|
const sleepBeforeRetry = async (retryAfterSeconds) => {
|
|
290
304
|
const waitMs = Math.min(transientRetryWaitMs({
|
|
291
305
|
retryAfterSeconds,
|
|
@@ -315,6 +329,7 @@ export async function pollPrVortexReview(cfg, owner, repo, prNumber, opts = {})
|
|
|
315
329
|
if (isCommandErrorCode(err, "api_timeout")) {
|
|
316
330
|
if (opts.propagateTimeout)
|
|
317
331
|
throw err;
|
|
332
|
+
apiTimeoutSeen = true;
|
|
318
333
|
break;
|
|
319
334
|
}
|
|
320
335
|
if (isTransientReviewPollError(err)) {
|
|
@@ -364,7 +379,7 @@ export async function pollPrVortexReview(cfg, owner, repo, prNumber, opts = {})
|
|
|
364
379
|
return lastEnvelope;
|
|
365
380
|
opts.onTick?.("progress");
|
|
366
381
|
}
|
|
367
|
-
throw new PrReviewPollTimeoutError(owner, repo, prNumber, lastEnvelope);
|
|
382
|
+
throw new PrReviewPollTimeoutError(owner, repo, prNumber, lastEnvelope, opts, lastEnvelope !== null || apiTimeoutSeen);
|
|
368
383
|
}
|
|
369
384
|
export async function pollReview(cfg, jobId, opts = {}) {
|
|
370
385
|
const timeoutMs = opts.timeoutMs ?? REVIEW_POLL_DEFAULT_TIMEOUT_MS;
|
|
@@ -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
|
@@ -1040,8 +1040,7 @@ export async function cmdStackStatus(args, deps = {}) {
|
|
|
1040
1040
|
lines.push(` ${String(layer.position).padStart(2)} ${pr}${head} ${layer.state.padEnd(14)} ${layer.branch}` +
|
|
1041
1041
|
` CI: ${layer.ciStatus ?? "unknown"} review: ${layer.reviewStatus ?? "unknown"}` +
|
|
1042
1042
|
(layer.vortexStatus ? ` Vortex: ${layer.vortexStatus}` : "") +
|
|
1043
|
-
(layer.cycloneStatus ? ` Cyclone: ${layer.cycloneStatus}` : "")
|
|
1044
|
-
(layer.tempestStatus ? ` Tempest: ${layer.tempestStatus}` : ""));
|
|
1043
|
+
(layer.cycloneStatus ? ` Cyclone: ${layer.cycloneStatus}` : ""));
|
|
1045
1044
|
}
|
|
1046
1045
|
if (queueWait && !attention)
|
|
1047
1046
|
lines.push(` Queue: #${queueWait.prNumber} ${queueWait.text}`);
|
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;
|
|
@@ -120,6 +110,20 @@ export type StackRepairHint = {
|
|
|
120
110
|
since: string | null;
|
|
121
111
|
waitReason: string | null;
|
|
122
112
|
steps: string;
|
|
113
|
+
} | {
|
|
114
|
+
kind: "members_remaining" | "vortex_failed";
|
|
115
|
+
prNumber: number;
|
|
116
|
+
headSha: string | null;
|
|
117
|
+
branch: string;
|
|
118
|
+
detail: string | null;
|
|
119
|
+
steps: string;
|
|
120
|
+
} | {
|
|
121
|
+
kind: "land_pr_unavailable";
|
|
122
|
+
prNumber: number;
|
|
123
|
+
headSha: string | null;
|
|
124
|
+
branch: string;
|
|
125
|
+
detail: string | null;
|
|
126
|
+
steps: string;
|
|
123
127
|
} | {
|
|
124
128
|
kind: "vortex_skipped";
|
|
125
129
|
prNumber: number;
|
|
@@ -160,6 +164,11 @@ export declare const VORTEX_FINDINGS_NEED_PERSON = "Vortex findings need a perso
|
|
|
160
164
|
export declare const VORTEX_AUTO_REVIEW_OFF = "Vortex auto-review is off, so this head was not reviewed";
|
|
161
165
|
export declare const VORTEX_BILLING_SKIPPED = "Vortex skipped this head: it could not confirm billing";
|
|
162
166
|
export declare const VORTEX_REVIEW_INCOMPLETE = "Vortex reviewed only part of this PR";
|
|
167
|
+
export declare const VORTEX_IGNORED_AUTHOR = "Vortex skipped this head: the PR author is on the ignored authors list";
|
|
168
|
+
export declare const VORTEX_REPO_DISABLED = "Vortex skipped this head: reviews are turned off for this repository";
|
|
169
|
+
export declare const VORTEX_RETRY_SCHEDULED = "Vortex hit a temporary error on this head and will retry by itself";
|
|
170
|
+
export declare const VORTEX_REVIEW_FAILED = "Review failed";
|
|
171
|
+
export declare function vortexRetryScheduled(layer: StackLayerDto): boolean;
|
|
163
172
|
export declare const AUTO_LAND_CI_PENDING_ATTENTION_MS: number;
|
|
164
173
|
export declare const AUTO_LAND_VORTEX_WAIT_ATTENTION_MS: number;
|
|
165
174
|
export declare const AUTO_LAND_PROMOTE_FAILED_ATTEMPTS = 3;
|
|
@@ -192,8 +201,9 @@ export declare function restackFailedRepairSteps(branch: string, liveParent: str
|
|
|
192
201
|
kind: string;
|
|
193
202
|
detail?: string | null;
|
|
194
203
|
}): string;
|
|
195
|
-
export declare function agentsCannotClear(
|
|
196
|
-
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;
|
|
206
|
+
export declare const LAND_PR_MISSING_PREFIX = "No open land PR for ";
|
|
197
207
|
/** Pair-gate attention plus live hard blocks on every other open layer. */
|
|
198
208
|
export declare function stackBlockers(stack: StackDto, entries?: MergeQueueEntryDto[], cursor?: Pick<StackWatchCursor, "bounceId" | "afterFinishedAt">, nowMs?: number): {
|
|
199
209
|
attention: null;
|