mergestorm 0.3.17 → 0.3.19

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 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.17 │
48
+ │ ▟██▛ mergestorm v0.3.19 │
49
49
  │ ▜▙ ● msk_live_… · maelstrom │
50
50
  │ [████████░░░░░░░░░░░░░░░░░░░░░░] 25% used · Resets Sep 5, 10:53am (UTC)│
51
51
  │ │
52
52
  │ review a diff · usage for tabs · /help for all commands │
53
- │ New in v0.3.17: nginx 413 names the upload limit instead of Failed: {} │
53
+ │ New in v0.3.19: stack wait and held GET waits │
54
54
  ╰──────────────────────────────────────────────────────────────────────────────╯
55
55
  mergestorm
56
56
  ╭──────────────────────────────────────────────────────────────────────────────╮
@@ -86,7 +86,7 @@ mergestorm jobs [n] [--json] Recent review jobs (default 10, max 50)
86
86
  mergestorm branches [n] Recently reviewed branches (arrow-pick on TTY)
87
87
  mergestorm chain [slug] Branch review-chain timeline (default: current branch)
88
88
  mergestorm whoami [--json] Key prefix, plan, API base, config path
89
- mergestorm skill install --claude|--cursor Copy the mergestorm-review and mergestorm-pr-loop skills into this repo
89
+ mergestorm skill install --claude|--cursor|--agents Copy the Mergestorm skills into this repo
90
90
  mergestorm thread <slug> Jobs in a review thread
91
91
  mergestorm stack create [name] New local stack layer (optional `--onto` / `--trunk` / `--extend`)
92
92
  mergestorm stack submit Push layers, open PRs (`gh`), register via adopt (optional `--extend`)
@@ -94,6 +94,7 @@ mergestorm stack list [--json] List registered stacks
94
94
  mergestorm stack adopt <owner/repo>#<pr> Import an existing open PR chain
95
95
  mergestorm stack restack <stack-id> Restack descendants
96
96
  mergestorm stack land <stack-id> Land / promote (into review unit when present)
97
+ mergestorm stack wait <stack-id> Wait for stack attention (default 45s, max 300s)
97
98
  mergestorm stack reset --force Clear local authoring state (not branches/PRs)
98
99
  ```
99
100
 
package/dist/api.d.ts CHANGED
@@ -134,6 +134,11 @@ export type PrVortexReview = {
134
134
  finding_count: number;
135
135
  findings: PrReviewFindings | null;
136
136
  patch_policy: unknown | null;
137
+ /**
138
+ * MS-01: True when the returned row is a resting completion but a newer
139
+ * attempt is in flight (active lease). The CLI must continue polling.
140
+ */
141
+ newer_pass_pending?: boolean;
137
142
  };
138
143
  /**
139
144
  * Pass selection on the DB-only PR review read (#2027). `pass` names one row;
@@ -154,9 +159,9 @@ export declare function applyPrReviewQuery(query: URLSearchParams, opts: PrRevie
154
159
  */
155
160
  export { SETTINGS_WRITABLE_KEYS };
156
161
  export type { SettingsWritableKey };
157
- export type SettingsPatch = Partial<Record<SettingsWritableKey, boolean>>;
162
+ export type SettingsPatch = Partial<import("./automation-catalog.js").BearerSettingsValues>;
158
163
  /** GET and PATCH `/api/v1/settings` both answer with this shape. */
159
- export type SettingsResponse = Record<SettingsWritableKey, boolean> & {
164
+ export type SettingsResponse = import("./automation-catalog.js").BearerSettingsValues & {
160
165
  cyclone_connected: boolean;
161
166
  github_connected: boolean;
162
167
  };
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, } 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. */
@@ -382,7 +382,43 @@ export async function adoptStack(owner, repo, prNumber, cfg, policy) {
382
382
  throw new CommandError(rateLimitedMessage(retryAfterSeconds), REVIEW_EXIT.rate_limited, "rate_limited", { retryAfterSeconds });
383
383
  }
384
384
  if (status !== 200) {
385
- throw new CommandError(`Failed to import stack (HTTP ${status}): ${JSON.stringify(body)}`);
385
+ const error = body && typeof body === "object" && "error" in body && typeof body.error === "string"
386
+ ? body.error
387
+ : undefined;
388
+ const message = body && typeof body === "object" && "message" in body && typeof body.message === "string"
389
+ ? body.message
390
+ : undefined;
391
+ const code = error === "auth_invalid" ||
392
+ error === "api_timeout" ||
393
+ error === "not_a_repo" ||
394
+ error === "not_found" ||
395
+ error === "missing_api_key" ||
396
+ error === "registered_parent" ||
397
+ error === "usage" ||
398
+ error === "review_quota" ||
399
+ error === "review_failed" ||
400
+ error === "review_timeout" ||
401
+ error === "rate_limited" ||
402
+ error === "cyclone_not_installed" ||
403
+ error === "pr_merged" ||
404
+ error === "pr_closed" ||
405
+ error === "not_stacked" ||
406
+ error === "draft" ||
407
+ error === "reserved_branch" ||
408
+ error === "supabase_unconfigured" ||
409
+ error === "cyclone_not_connected" ||
410
+ error === "busy" ||
411
+ error === "adopt_failed"
412
+ ? error
413
+ : undefined;
414
+ const fallbackMessage = code === "not_stacked"
415
+ ? "This pull request is not part of a stack."
416
+ : code === "supabase_unconfigured"
417
+ ? "Stacks are not configured on this server."
418
+ : undefined;
419
+ throw new CommandError(message?.trim()
420
+ ? message
421
+ : fallbackMessage ?? `Failed to import stack (HTTP ${status}): ${JSON.stringify(body)}`, 1, code);
386
422
  }
387
423
  return body;
388
424
  }
@@ -414,6 +450,10 @@ async function stackMutation(stackId, pathSuffix, method, json, failLabel, cfg)
414
450
  throw new CommandError("Stacks API is not available on this server yet. Deploy the API update or use the dashboard.");
415
451
  }
416
452
  if (status !== 200) {
453
+ const rejection = body;
454
+ if (method === "PATCH" && rejection?.error === "cyclone_not_connected") {
455
+ throw new CommandError(typeof rejection.message === "string" ? rejection.message : rejection.error, 1, "cyclone_not_connected");
456
+ }
417
457
  throw new CommandError(`${failLabel} (HTTP ${status}): ${JSON.stringify(body)}`);
418
458
  }
419
459
  return body;
@@ -22,6 +22,22 @@ export declare const BEARER_SETTINGS: readonly [{
22
22
  readonly key: "cyclone_review_unit_land_prs_enabled";
23
23
  readonly flag: "--cyclone-review-unit-land";
24
24
  readonly label: "Cyclone Review Unit land PRs";
25
+ }, {
26
+ readonly key: "cyclone_skip_ci_enabled";
27
+ readonly flag: "--cyclone-skip-ci";
28
+ readonly label: "Skip CI on Cyclone commits";
29
+ }, {
30
+ readonly key: "cyclone_patch_unverified_languages";
31
+ readonly flag: "--cyclone-patch-unverified";
32
+ readonly label: "Patch languages we cannot typecheck";
33
+ }, {
34
+ readonly key: "vortex_auto_overflow_enabled";
35
+ readonly flag: "--vortex-auto-overflow";
36
+ readonly label: "Vortex auto overflow";
37
+ }, {
38
+ readonly key: "vortex_skip_all_clear_comments";
39
+ readonly flag: "--vortex-skip-all-clear";
40
+ readonly label: "Vortex skip All-clear comments";
25
41
  }, {
26
42
  readonly key: "vortex_seam_specialist_enabled";
27
43
  readonly flag: "--vortex-seam";
@@ -30,8 +46,40 @@ export declare const BEARER_SETTINGS: readonly [{
30
46
  readonly key: "auto_land_default";
31
47
  readonly flag: "--auto-land";
32
48
  readonly label: "Auto land (new stacks)";
49
+ }, {
50
+ readonly key: "ignored_bot_logins";
51
+ readonly flag: "--ignore-bot";
52
+ readonly label: "Ignored bot logins";
53
+ readonly kind: "logins";
54
+ }, {
55
+ readonly key: "vortex_bot_skip_check";
56
+ readonly flag: "--vortex-skip-check";
57
+ readonly label: "Vortex bot skip check";
58
+ readonly kind: "enum";
59
+ readonly values: readonly ["none", "neutral"];
60
+ }, {
61
+ readonly key: "vortex_findings_check";
62
+ readonly flag: "--vortex-findings";
63
+ readonly label: "Vortex findings check";
64
+ readonly kind: "enum";
65
+ readonly values: readonly ["failure", "neutral", "success"];
66
+ }, {
67
+ readonly key: "cyclone_patch_failure_check";
68
+ readonly flag: "--cyclone-fail-check";
69
+ readonly label: "Cyclone patch failure check";
70
+ readonly kind: "enum";
71
+ readonly values: readonly ["failure", "neutral"];
33
72
  }];
34
73
  export type BearerSettingsKey = (typeof BEARER_SETTINGS)[number]["key"];
35
74
  export declare const BEARER_SETTINGS_KEYS: readonly BearerSettingsKey[];
36
75
  export declare const BEARER_SETTINGS_FLAGS: Record<string, BearerSettingsKey>;
37
76
  export declare const BEARER_SETTINGS_LABELS: Record<BearerSettingsKey, string>;
77
+ /** Omitted kind means boolean for the original toggle rows. */
78
+ export type BearerSettingsValues = {
79
+ [Row in (typeof BEARER_SETTINGS)[number] as Row["key"]]: Row extends {
80
+ kind: "logins";
81
+ } ? string[] : Row extends {
82
+ values: readonly (infer Value)[];
83
+ } ? Value : boolean;
84
+ };
85
+ export declare const BEARER_BOOLEAN_SETTINGS_KEYS: ("auto_review_enabled" | "auto_patch_enabled" | "vortex_show_thinking_traces" | "repo_overview_enabled" | "review_unit_land_prs_enabled" | "cyclone_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" | "ignored_bot_logins" | "vortex_bot_skip_check" | "vortex_findings_check" | "cyclone_patch_failure_check")[];
@@ -5,9 +5,20 @@ export const BEARER_SETTINGS = [
5
5
  { key: "repo_overview_enabled", flag: "--repo-overview", label: "Repo overview" },
6
6
  { key: "review_unit_land_prs_enabled", flag: "--review-unit-land", label: "Review Unit land PRs" },
7
7
  { key: "cyclone_review_unit_land_prs_enabled", flag: "--cyclone-review-unit-land", label: "Cyclone Review Unit land PRs" },
8
+ { key: "cyclone_skip_ci_enabled", flag: "--cyclone-skip-ci", label: "Skip CI on Cyclone commits" },
9
+ { key: "cyclone_patch_unverified_languages", flag: "--cyclone-patch-unverified", label: "Patch languages we cannot typecheck" },
10
+ { key: "vortex_auto_overflow_enabled", flag: "--vortex-auto-overflow", label: "Vortex auto overflow" },
11
+ { key: "vortex_skip_all_clear_comments", flag: "--vortex-skip-all-clear", label: "Vortex skip All-clear comments" },
8
12
  { key: "vortex_seam_specialist_enabled", flag: "--vortex-seam", label: "Vortex seam specialist" },
9
13
  { key: "auto_land_default", flag: "--auto-land", label: "Auto land (new stacks)" },
14
+ { key: "ignored_bot_logins", flag: "--ignore-bot", label: "Ignored bot logins", kind: "logins" },
15
+ { key: "vortex_bot_skip_check", flag: "--vortex-skip-check", label: "Vortex bot skip check", kind: "enum", values: ["none", "neutral"] },
16
+ { key: "vortex_findings_check", flag: "--vortex-findings", label: "Vortex findings check", kind: "enum", values: ["failure", "neutral", "success"] },
17
+ { key: "cyclone_patch_failure_check", flag: "--cyclone-fail-check", label: "Cyclone patch failure check", kind: "enum", values: ["failure", "neutral"] },
10
18
  ];
11
19
  export const BEARER_SETTINGS_KEYS = BEARER_SETTINGS.map((r) => r.key);
12
20
  export const BEARER_SETTINGS_FLAGS = Object.fromEntries(BEARER_SETTINGS.map((r) => [r.flag, r.key]));
13
21
  export const BEARER_SETTINGS_LABELS = Object.fromEntries(BEARER_SETTINGS.map((r) => [r.key, r.label]));
22
+ export const BEARER_BOOLEAN_SETTINGS_KEYS = BEARER_SETTINGS
23
+ .filter((row) => !("kind" in row))
24
+ .map((row) => row.key);
@@ -1,7 +1,9 @@
1
1
  export { DEFAULT_API, apiBase, clearConfig, configPath, keyDisplay, loadConfig, mergestormHome, resolveApiKey, saveConfig, type Config, } from "../config.js";
2
- export { BEARER_SETTINGS, BEARER_SETTINGS_FLAGS, BEARER_SETTINGS_KEYS, BEARER_SETTINGS_LABELS, type BearerSettingsKey, } from "../automation-catalog.js";
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, 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 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
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";
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
+ export { BOUNCE_KIND_LABELS, mergeQueueBounceLabel } from "../stack-dto.js";
9
+ export { STACK_WATCH_SCHEMA, STACK_WATCH_DEFAULT_TIMEOUT_MS, StackWatchError, StackWatchTimeoutError, pollStackWatch, type PollStackWatchOptions, type StackWatchCursor, type StackWatchEnvelope, } from "../stack-watch.js";
@@ -1,7 +1,9 @@
1
1
  export { DEFAULT_API, apiBase, clearConfig, configPath, keyDisplay, loadConfig, mergestormHome, resolveApiKey, saveConfig, } from "../config.js";
2
- export { BEARER_SETTINGS, BEARER_SETTINGS_FLAGS, BEARER_SETTINGS_KEYS, BEARER_SETTINGS_LABELS, } from "../automation-catalog.js";
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, 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
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";
6
6
  export { REVIEW_JOB_ENVELOPE_SCHEMA, toReviewJobEnvelope, } from "../ui/job-envelope.js";
7
7
  export { discoverTrunk } from "../git-stack.js";
8
+ export { BOUNCE_KIND_LABELS, mergeQueueBounceLabel } from "../stack-dto.js";
9
+ export { STACK_WATCH_SCHEMA, STACK_WATCH_DEFAULT_TIMEOUT_MS, StackWatchError, StackWatchTimeoutError, pollStackWatch, } from "../stack-watch.js";
@@ -27,7 +27,7 @@ export declare function buildStatusTabLines(input: StatusTabInput): string[];
27
27
  /** In-place job detail for the Jobs tab (same envelope as `status <id>`). */
28
28
  export declare function formatJobDetailLines(row: ReviewJobRow): string[];
29
29
  /** Human labels for the Config tab and the static `settings` printout. */
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_review_unit_land_prs_enabled" | "vortex_seam_specialist_enabled" | "auto_land_default", string>;
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_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" | "ignored_bot_logins" | "vortex_bot_skip_check" | "vortex_findings_check" | "cyclone_patch_failure_check", string>;
31
31
  /** Config rows in a stable order: writable toggles, then connected flags. */
32
32
  export declare function buildConfigRows(settings: SettingsResponse): TabsConfigRow[];
33
33
  /**
@@ -1,5 +1,5 @@
1
- import { getMe, getReview, getSettings, listJobs, patchSettings, SETTINGS_WRITABLE_KEYS, } from "../api.js";
2
- import { BEARER_SETTINGS_LABELS } from "../automation-catalog.js";
1
+ import { getMe, getReview, getSettings, listJobs, patchSettings, } from "../api.js";
2
+ import { BEARER_BOOLEAN_SETTINGS_KEYS, BEARER_SETTINGS_LABELS } from "../automation-catalog.js";
3
3
  import { apiBase, keyDisplay, loadConfig, resolveApiKey } from "../config.js";
4
4
  import { CommandError } from "../errors.js";
5
5
  import { ansi } from "../ui/ansi.js";
@@ -124,7 +124,7 @@ export const SETTINGS_LABELS = BEARER_SETTINGS_LABELS;
124
124
  /** Config rows in a stable order: writable toggles, then connected flags. */
125
125
  export function buildConfigRows(settings) {
126
126
  return [
127
- ...SETTINGS_WRITABLE_KEYS.map((key) => ({
127
+ ...BEARER_BOOLEAN_SETTINGS_KEYS.map((key) => ({
128
128
  key,
129
129
  label: SETTINGS_LABELS[key],
130
130
  value: settings[key] === true,
@@ -58,7 +58,7 @@ export function buildHelpTabs() {
58
58
  "",
59
59
  rule("Single stack (from trunk)"),
60
60
  cmd("stack create --onto main", "fresh stack, not on an existing one"),
61
- cmd("stack list", "what's registered for this repo"),
61
+ cmd("stack list · wait <id>", "registered stacks · wait for attention"),
62
62
  cmd("queue [add <stack-id>]", "list entries · queue verified landing"),
63
63
  cmd("stack land <id>", "land now · skips queue verification"),
64
64
  "",
@@ -5,7 +5,7 @@ export type QueueCmdDeps = {
5
5
  canBrowse?: () => boolean;
6
6
  runLineTabsBrowser?: typeof runLineTabsBrowser;
7
7
  };
8
- export declare const QUEUE_USAGE = "usage:\n mergestorm queue [list] [--json]\n mergestorm queue add <stack-id> [--json]\n mergestorm queue rm <entry-id|stack-id> [--json]\n\n queue lists your live merge-queue entries. rm prefers an entry id; a live\n stack id is resolved to its queue entry before cancellation.";
8
+ export declare const QUEUE_USAGE = "usage:\n mergestorm queue [list] [--json]\n mergestorm queue add <stack-id> [--json]\n mergestorm queue rm <entry-id|stack-id> [--json]\n\n queue lists live and recent bounced merge-queue entries. rm prefers an entry id; a live\n stack id is resolved to its queue entry before cancellation.";
9
9
  /** Entry ids win; otherwise a live stack id resolves to its entry id. */
10
10
  export declare function resolveQueueEntryId(idOrStackId: string, entries: readonly MergeQueueEntryDto[]): string;
11
11
  export declare function buildQueueListLines(entries: readonly MergeQueueEntryDto[], fullStackId?: boolean): string[];
@@ -1,6 +1,7 @@
1
1
  import { cancelMergeQueueEntry, enqueueStack, listMergeQueueEntries, } from "../api.js";
2
2
  import { loadConfig } from "../config.js";
3
3
  import { CommandError } from "../errors.js";
4
+ import { mergeQueueBounceLabel } from "../stack-dto.js";
4
5
  import { ansi } from "../ui/ansi.js";
5
6
  import { runLineTabsBrowser } from "../ui/line-tabs.js";
6
7
  import { present } from "../ui/present.js";
@@ -31,7 +32,7 @@ export const QUEUE_USAGE = `usage:
31
32
  mergestorm queue add <stack-id> [--json]
32
33
  mergestorm queue rm <entry-id|stack-id> [--json]
33
34
 
34
- queue lists your live merge-queue entries. rm prefers an entry id; a live
35
+ queue lists live and recent bounced merge-queue entries. rm prefers an entry id; a live
35
36
  stack id is resolved to its queue entry before cancellation.`;
36
37
  function positionalArgs(args) {
37
38
  const unknown = args.filter((arg) => arg.startsWith("-") &&
@@ -66,17 +67,31 @@ export function buildQueueListLines(entries, fullStackId = false) {
66
67
  ];
67
68
  }
68
69
  return entries.map((entry) => {
69
- const reason = entry.waitReason ?? entry.bounceReason;
70
+ const reason = entry.bounceDetail
71
+ ? mergeQueueBounceLabel(entry)
72
+ : entry.waitReason ?? (entry.bounceReason ? mergeQueueBounceLabel(entry) : null);
73
+ const prNumber = entry.bounceDetail?.prNumber != null && entry.bounceDetail.prNumber > 0
74
+ ? entry.bounceDetail.prNumber
75
+ : entry.waitReason?.match(/#([1-9]\d*)\b/)?.[1];
76
+ const headSha = entry.bounceDetail?.headSha ?? entry.verifyHeadSha;
77
+ const pr = prNumber != null
78
+ ? ` #${prNumber}${headSha ? `@${headSha.slice(0, 7)}` : ""}`
79
+ : "";
70
80
  const glyph = colorQueueGlyph(entry.state, queueStateGlyph(entry.state));
71
81
  const id = fullStackId ? entry.stackId : entry.stackId.slice(0, 8);
72
- const core = ` ${String(entry.position).padStart(2)} ` +
73
- `${glyph} ${entry.state.padEnd(8)} ${entry.owner}/${entry.repo} ${id}` +
74
- (reason ? ` ${reason}` : "");
82
+ const coreWithoutReason = ` ${String(entry.position).padStart(2)} ` +
83
+ `${glyph} ${entry.state.padEnd(8)} ${entry.owner}/${entry.repo} ${id}`;
84
+ const coreWithoutPr = coreWithoutReason + (reason ? ` ${reason}` : "");
85
+ const core = `${coreWithoutPr}${pr}`;
75
86
  const who = entry.enqueuedBy === "human" || entry.enqueuedBy === "agent"
76
87
  ? ` by ${entry.enqueuedBy}`
77
88
  : "";
78
89
  const withWho = `${core}${who}`;
79
- return visibleWidth(withWho) <= 75 ? withWho : core;
90
+ if (visibleWidth(withWho) <= 80)
91
+ return withWho;
92
+ if (visibleWidth(core) <= 80)
93
+ return core;
94
+ return pr ? core : coreWithoutPr;
80
95
  });
81
96
  }
82
97
  async function listQueue(asJson, deps = {}) {
@@ -52,6 +52,7 @@ export const COMMAND_REGISTRY = [
52
52
  " [--context text] [--context-file path] [--thread slug]",
53
53
  " [--idempotency-key k] [--webhook-url https://…]",
54
54
  " HTTP 429 exits 7 (rate_limited) with retry_after_seconds",
55
+ " Uploads over 1 MB / 1_000_000 are rejected with HTTP 413 and do not create a job",
55
56
  ],
56
57
  async run(args, ctx) {
57
58
  if (ctx.mode === "shell") {
@@ -164,8 +165,13 @@ export const COMMAND_REGISTRY = [
164
165
  "mergestorm settings [--json] Read the automation toggles (Config tab on a TTY)",
165
166
  " [--auto-review on|off] [--auto-patch on|off] [--vortex-thinking on|off]",
166
167
  " [--repo-overview on|off] [--review-unit-land on|off]",
167
- " [--cyclone-review-unit-land on|off] [--vortex-seam on|off]",
168
- " [--auto-land on|off] Auto land default for stacks opened after this",
168
+ " [--cyclone-review-unit-land on|off] [--cyclone-skip-ci on|off]",
169
+ " [--cyclone-patch-unverified on|off] Patch languages we cannot typecheck",
170
+ " [--vortex-seam on|off] [--vortex-skip-all-clear on|off]",
171
+ " [--auto-land on|off] Auto land default for new stacks",
172
+ " [--ignore-bot add <login>|remove <login>|clear]",
173
+ " [--vortex-skip-check none|neutral] [--vortex-findings failure|neutral|success]",
174
+ " [--cyclone-fail-check failure|neutral]",
169
175
  " With flags: PATCH those settings and print the stored result",
170
176
  ],
171
177
  async run(args, ctx) {
@@ -174,9 +180,9 @@ export const COMMAND_REGISTRY = [
174
180
  },
175
181
  {
176
182
  name: "skill",
177
- summary: "Install mergestorm-review and mergestorm-pr-loop for Claude or Cursor",
183
+ summary: "Install Mergestorm skills for Claude, Cursor, or agents",
178
184
  usage: [
179
- "mergestorm skill install --claude|--cursor Copy the mergestorm-review and mergestorm-pr-loop skills into this repo",
185
+ "mergestorm skill install --claude|--cursor|--agents Copy the Mergestorm skills into this repo",
180
186
  " [--json]",
181
187
  ],
182
188
  async run(args) {
@@ -200,7 +206,7 @@ export const COMMAND_REGISTRY = [
200
206
  name: "queue",
201
207
  summary: "Merge queue: list, add a stack, or remove an entry",
202
208
  usage: [
203
- "mergestorm queue [list] [--json] List live merge-queue entries",
209
+ "mergestorm queue [list] [--json] List live and recent bounced merge-queue entries",
204
210
  "mergestorm queue add <stack-id> [--json] Queue a stack for verified landing",
205
211
  "mergestorm queue rm <entry-id|stack-id> [--json] Remove a live queue entry",
206
212
  ],
@@ -216,13 +222,15 @@ export const COMMAND_REGISTRY = [
216
222
  "mergestorm stack submit Push/open active stack ([--extend] [--auto-land on|off] [--auto-review on|off] [--auto-patch on|off])",
217
223
  "mergestorm stack reset --force Clear this repo's pre-submit authoring state",
218
224
  "mergestorm stack list [--json] List registered stacks",
225
+ "mergestorm stack status <stack-id> [--json] Enriched stack status",
226
+ "mergestorm stack wait <stack-id> [--json] [--timeout <s>] Wait for stack attention (default 45s, max 300s)",
219
227
  "mergestorm stack set <stack-id> [--auto-land on|off] [--auto-review on|off|default] [--auto-patch on|off|default] [--json] Per-stack policy",
220
228
  "mergestorm stack adopt <owner/repo>#<pr> [--auto-land on|off] [--auto-review on|off] [--auto-patch on|off] Import an open PR chain",
221
229
  "mergestorm stack restack <stack-id> Restack stack descendants",
222
230
  "mergestorm stack land <stack-id> Land bottom PR (or promote into unit)",
223
231
  ],
224
- async run(args) {
225
- await cmdStack(args);
232
+ async run(args, ctx) {
233
+ await cmdStack(args, { signal: ctx.signal });
226
234
  },
227
235
  },
228
236
  {
@@ -271,7 +271,6 @@ function shaMatches(actual, expected) {
271
271
  /** Poll the DB-only PR review endpoint until the requested pass is resting. */
272
272
  export async function pollPrVortexReview(cfg, owner, repo, prNumber, opts = {}) {
273
273
  const timeoutMs = opts.timeoutMs ?? REVIEW_POLL_DEFAULT_TIMEOUT_MS;
274
- const intervalMs = opts.intervalMs ?? REVIEW_POLL_INTERVAL_MS;
275
274
  const now = opts.now ?? Date.now;
276
275
  const wait = opts.sleep ?? sleep;
277
276
  const startedAt = now();
@@ -296,17 +295,13 @@ export async function pollPrVortexReview(cfg, owner, repo, prNumber, opts = {})
296
295
  if (waitMs > 0)
297
296
  await wait(waitMs, opts.signal);
298
297
  };
299
- const sleepBeforePoll = async () => {
300
- const waitMs = Math.min(stretchedPollIntervalMs(now() - startedAt, intervalMs), Math.max(0, deadline - now()));
301
- if (waitMs > 0)
302
- await wait(waitMs, opts.signal);
303
- };
304
298
  while (now() < deadline) {
299
+ query.set("wait", String(Math.min(45, Math.max(1, Math.ceil((deadline - now()) / 1000)))));
305
300
  let poll;
306
301
  try {
307
302
  poll = await apiFetch(cfg, `/api/v1/stacks/pr-review?${query.toString()}`, {
308
303
  signal: opts.signal,
309
- timeoutMs: Math.max(1, Math.min(30_000, deadline - now())),
304
+ timeoutMs: Number(query.get("wait")) * 1000 + 5_000,
310
305
  });
311
306
  }
312
307
  catch (err) {
@@ -331,7 +326,6 @@ export async function pollPrVortexReview(cfg, owner, repo, prNumber, opts = {})
331
326
  if (poll.status === 404) {
332
327
  transientFailures = 0;
333
328
  opts.onTick?.("progress");
334
- await sleepBeforePoll();
335
329
  continue;
336
330
  }
337
331
  if (poll.status !== 200) {
@@ -358,16 +352,17 @@ export async function pollPrVortexReview(cfg, owner, repo, prNumber, opts = {})
358
352
  (!isPositiveInteger(envelope.pass) || (opts.pass !== undefined && envelope.pass !== opts.pass) ||
359
353
  (opts.afterPass !== undefined && envelope.pass <= opts.afterPass))) {
360
354
  opts.onTick?.("progress");
361
- await sleepBeforePoll();
362
355
  continue;
363
356
  }
364
357
  lastEnvelope = envelope;
365
358
  const resting = (lastEnvelope.raw_status ?? lastEnvelope.status) !== "in_progress";
366
359
  const matches = !opts.afterSha || shaMatches(lastEnvelope.head_sha, opts.afterSha);
367
- if (resting && matches)
360
+ // MS-01: A resting completion with newer_pass_pending indicates a newer
361
+ // attempt is in flight. Do not satisfy the wait from this stale result.
362
+ const stale = lastEnvelope.newer_pass_pending === true;
363
+ if (resting && matches && !stale)
368
364
  return lastEnvelope;
369
365
  opts.onTick?.("progress");
370
- await sleepBeforePoll();
371
366
  }
372
367
  throw new PrReviewPollTimeoutError(owner, repo, prNumber, lastEnvelope);
373
368
  }
@@ -40,7 +40,8 @@ function normalizeVortexSpecialistIds(value) {
40
40
  const selected = new Set(value.filter((id) => typeof id === "string" && VORTEX_SPECIALIST_IDS.includes(id)));
41
41
  return VORTEX_SPECIALIST_IDS.filter((id) => selected.has(id));
42
42
  }
43
- const REVIEW_USAGE = "usage: mergestorm review [base] [head] [--json] [--wait|--no-wait] [--timeout seconds] [--router off|standard|max|manual] [--specialists id,id] [--context text] [--context-file path] [--idempotency-key k] [--webhook-url https://…] [--thread slug]";
43
+ const REVIEW_USAGE = "usage: mergestorm review [base] [head] [--json] [--wait|--no-wait] [--timeout seconds] [--router off|standard|max|manual] [--specialists id,id] [--context text] [--context-file path] [--idempotency-key k] [--webhook-url https://…] [--thread slug]\n" +
44
+ "Uploads over 1 MB / 1_000_000 are rejected with HTTP 413 and do not create a job.";
44
45
  function usageError(message = REVIEW_USAGE) {
45
46
  return new CommandError(message, REVIEW_EXIT.usage, "usage");
46
47
  }
@@ -164,6 +165,9 @@ export function parseReviewArgs(args) {
164
165
  thread = slug;
165
166
  continue;
166
167
  }
168
+ if (a === "-h" || a === "--help") {
169
+ throw usageError();
170
+ }
167
171
  if (a.startsWith("-")) {
168
172
  throw usageError(`unknown flag: ${a}`);
169
173
  }
@@ -227,6 +231,10 @@ function asPollCommandError(err, jobId) {
227
231
  return new CommandError(withReviewJobRecovery(`Poll failed: ${detail}`, jobId));
228
232
  }
229
233
  export async function cmdReview(args, opts = {}) {
234
+ if (args.includes("-h") || args.includes("--help")) {
235
+ console.log(REVIEW_USAGE);
236
+ return;
237
+ }
230
238
  const parsed = parseReviewArgs(args);
231
239
  const base = resolveReviewBase(parsed.base);
232
240
  const head = parsed.head || "HEAD";
@@ -1,17 +1,25 @@
1
1
  import { type SettingsPatch, type SettingsResponse } from "../api.js";
2
2
  /**
3
- * CLI flag → Bearer settings key. on|off only. The connected flags are
3
+ * CLI flag → Bearer settings key. Boolean flags take on|off. The connected flags are
4
4
  * read-only on the API and deliberately have no flag here.
5
5
  */
6
- export declare const SETTINGS_FLAGS: Record<string, "auto_review_enabled" | "auto_patch_enabled" | "vortex_show_thinking_traces" | "repo_overview_enabled" | "review_unit_land_prs_enabled" | "cyclone_review_unit_land_prs_enabled" | "vortex_seam_specialist_enabled" | "auto_land_default">;
6
+ export declare const SETTINGS_FLAGS: Record<string, "auto_review_enabled" | "auto_patch_enabled" | "vortex_show_thinking_traces" | "repo_overview_enabled" | "review_unit_land_prs_enabled" | "cyclone_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" | "ignored_bot_logins" | "vortex_bot_skip_check" | "vortex_findings_check" | "cyclone_patch_failure_check">;
7
+ type IgnoreBotOperation = {
8
+ action: "add" | "remove";
9
+ login: string;
10
+ } | {
11
+ action: "clear";
12
+ };
7
13
  export type SettingsArgs = {
8
14
  json: boolean;
9
15
  patch: SettingsPatch;
16
+ ignoreBots?: IgnoreBotOperation[];
10
17
  };
11
- /** Parse `settings` argv. Accepts `--flag on|off` and `--flag=on|off`. */
18
+ /** Parse boolean and enum flags plus ordered ignore-list operations. */
12
19
  export declare function parseSettingsArgs(args: string[]): SettingsArgs;
13
- /** Static printout of the GET/PATCH response shape (rows match the Config tab). */
20
+ /** Static printout includes the Config toggles plus enum and login settings. */
14
21
  export declare function formatSettingsLines(settings: SettingsResponse): string[];
15
22
  export declare function cmdSettings(args: string[], opts?: {
16
23
  mode?: "oneshot" | "shell";
17
24
  }): Promise<void>;
25
+ export {};