mergestorm 0.3.62 → 0.3.64

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.62 │
48
+ │ ▀▀▀▀▀▀▀▀▀▀▘ mergestorm v0.3.64 │
49
49
  │ ▀▀▀▀▀▀▀▀▀ ● msk_live_… · maelstrom │
50
50
  │ ▀▀▀▀▀▀ [████████░░░░░░░░░░░░░░░░░░░░░░] 25% used │
51
51
  │ ▝▀▀▀▀▀ local reviews + stacked PRs · mergestorm.ai │
52
52
  │ ▝▀▀▘ review a diff · usage for tabs · /help for all commands │
53
- │ ▀ New in v0.3.62: Watch wakes for a blocker on any layer │
53
+ │ ▀ New in v0.3.64: Memory commands and Overdrive merge batches │
54
54
  ╰──────────────────────────────────────────────────────────────────────────────╯
55
55
  mergestorm
56
56
  ╭──────────────────────────────────────────────────────────────────────────────╮
@@ -93,7 +93,7 @@ mergestorm thread <slug> Jobs in a review thread
93
93
  mergestorm issue create --title <t> Open a GitHub issue through Mergestorm, no `gh` (`--body`, `--body-file`, `--label`, `--repo`)
94
94
  mergestorm stack create [name] New local stack layer (optional `--onto` / `--trunk` / `--extend`)
95
95
  mergestorm stack submit Push layers, open PRs (`gh`), register via adopt (optional `--extend`, `--server-side`)
96
- mergestorm stack publish Publish the current branch through Mergestorm with no GitHub credentials (`--status` to read it)
96
+ mergestorm stack publish Publish the current branch through Mergestorm with no GitHub credentials (`--base <branch> --extend` to stack, `--status` to read it)
97
97
  mergestorm stack list [--json] List registered stacks
98
98
  mergestorm stack adopt <owner/repo>#<pr> Import an existing open PR chain
99
99
  mergestorm stack restack <stack-id> Restack descendants
@@ -113,7 +113,8 @@ mergestorm dismiss <owner/repo>#<n> --head <sha> --review <id> --finding <id> --
113
113
  - It prints nothing while the stack is `waiting` or `in_progress`, including while Cyclone or Vortex holds the blocked PR (`busy[]`, `actAfter: "agents_idle"`).
114
114
  - On attention it prints `MS-WATCH ATTENTION pr=<n> head=<sha> blocker="<blocker>" repair=<kind>` and exits 3. Attention names the lowest layer with a blocker an agent must fix, on any layer of the stack, not only the current candidate.
115
115
  - 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.
116
- - 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.
116
+ - A permanent read error is retried once; if the retry fails too, it prints `MS-WATCH ATTENTION failed stack=<id> error="<message>"` and exits 3. Timeouts, 5xx and rate limits are retried, but after 15 minutes with no stack read it prints the same line and exits 3.
117
+ - A watch that cannot start (bad flags, a config it cannot read, a crash) prints `MS-WATCH ATTENTION failed` too, so the command never exits without an `MS-WATCH` line unless you stop it (Ctrl+C or an abort, exit 130). Pass each flag as its own word: in zsh an unquoted `$args` holding several flags stays one word and the command refuses it.
117
118
  - `--head <sha>` enrolls the SHA you just pushed. After fixing a blocker, restart the watcher with it.
118
119
  - `--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.
119
120
  - `--until landed` keeps running after an attention, printing each new attention once, and exits only when the watch is done or reads keep failing.
@@ -138,9 +139,14 @@ Use `mergestorm dismiss` when a Vortex finding is wrong and you have checked it
138
139
 
139
140
  `mg` is a short alias for `mergestorm` (same binary), e.g. `mg stack create`. Happy path: **create → commit → submit → restack → land**. On review-unit stacks, `stack land` **promotes** the tip into the unit (same gates as the dashboard); otherwise it lands the bottom open PR. `stack adopt` is for importing a chain that already exists on GitHub (legacy / Graphite). Restack/land use the same login key (`/api/v1/stacks`). Unattended landing is `mg queue add`.
140
141
 
141
- `stack create` checks out a new branch from the current tip (or `--onto <branch>`), discovers trunk (`main` / `master` / `origin/HEAD`, overridable with `--trunk`), and records `{ branch, parentBranch }` in CLI-managed state under **`~/.mergestorm/stacks/<repo-id>/stack.json`**. The CLI updates this automatically; never edit it. Each worktree has its own authoring state; independent clones stay isolated too. Creating onto trunk with a non-empty active stack starts a new one. `--trunk` only sets trunk metadata — it does **not** change the parent; use `--onto main` (etc.) for a fresh stack from trunk.
142
+ `stack create` always starts a new single PR from trunk unless you pass **`--extend`**, even when the worktree is still on an earlier PR's branch: it checks out the new branch from `origin/<trunk>` (or local trunk; from `HEAD` when trunk is checked out). It discovers trunk (`main` / `master` / `origin/HEAD`, overridable with `--trunk`) and records `{ branch, parentBranch }` in CLI-managed state under **`~/.mergestorm/stacks/<repo-id>/stack.json`**. The CLI updates this automatically; never edit it. Each worktree has its own authoring state; independent clones stay isolated too. Creating onto trunk with a non-empty active stack starts a new one.
142
143
 
143
- 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`.
144
+ Both paths are one command:
145
+
146
+ - New stack from trunk: `mg stack create <name>` (from any branch), then `mg stack submit`.
147
+ - Grow a stack: `mg stack create <name> --extend` on the branch to stack onto (or `--onto <branch> --extend`), then `mg stack submit --extend` when the parent is a layer of a registered stack. `mg stack publish --base <branch> --extend` does the same for publish.
148
+
149
+ `--onto` or `--base` with a branch other than trunk and no `--extend` is refused, so nothing is added to a stack by accident. Every create, submit and publish prints which one it made: a new single PR onto trunk, or a layer stacked on its parent.
144
150
 
145
151
  `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.
146
152
 
package/dist/api.d.ts CHANGED
@@ -142,6 +142,14 @@ export type PrVortexReview = {
142
142
  reviewed_at: string | null;
143
143
  finding_count: number;
144
144
  findings: PrReviewFindings | null;
145
+ dismissed_findings?: Array<{
146
+ path: string;
147
+ line: number;
148
+ title: string;
149
+ severity: string;
150
+ finding_key: string;
151
+ github_comment_id: number | null;
152
+ }>;
145
153
  patch_policy: unknown | null;
146
154
  /**
147
155
  * MS-01: True when the returned row is a resting completion but a newer
@@ -174,13 +182,18 @@ export type SettingsResponse = import("./automation-catalog.js").BearerSettingsV
174
182
  github_connected: boolean;
175
183
  stacks_ready?: boolean;
176
184
  };
177
- export declare const REPO_SETTINGS_KEYS: readonly ["auto_patch_enabled", "auto_review_enabled", "auto_land_default"];
185
+ export declare const REPO_SETTINGS_SWITCH_KEYS: readonly ["auto_patch_enabled", "auto_review_enabled", "auto_land_default", "merge_queue_batch_enabled", "merge_queue_overdrive"];
186
+ export declare const REPO_SETTINGS_KEYS: readonly ["auto_patch_enabled", "auto_review_enabled", "auto_land_default", "merge_queue_batch_enabled", "merge_queue_overdrive", "merge_queue_batch_size", "merge_queue_lands_alone_paths"];
178
187
  export type RepoSettingsKey = (typeof REPO_SETTINGS_KEYS)[number];
188
+ export type RepoSettingsValues = Record<(typeof REPO_SETTINGS_SWITCH_KEYS)[number], boolean | null> & {
189
+ merge_queue_batch_size: number | null;
190
+ merge_queue_lands_alone_paths: string[] | null;
191
+ };
179
192
  export type RepoSettings = {
180
193
  owner: string;
181
194
  repo: string;
182
- } & Record<RepoSettingsKey, boolean | null>;
183
- export type RepoSettingsPatch = Partial<Record<RepoSettingsKey, boolean | null>>;
195
+ } & RepoSettingsValues;
196
+ export type RepoSettingsPatch = Partial<RepoSettingsValues>;
184
197
  export declare function listRepoSettings(cfg?: Config, opts?: {
185
198
  timeoutMs?: number;
186
199
  }): Promise<RepoSettings[]>;
@@ -458,7 +471,12 @@ export type PrFindingDismissResult = {
458
471
  other_gates: "unchanged";
459
472
  };
460
473
  stack_id: string | null;
474
+ threads?: Array<{
475
+ finding_id: string;
476
+ state: "resolved" | "already_resolved" | "not_found" | "failed";
477
+ }>;
461
478
  };
479
+ export declare function dismissThreadsLine(result: Pick<PrFindingDismissResult, "threads">): string | null;
462
480
  export type PrFindingDismissOutcome = {
463
481
  ok: true;
464
482
  result: PrFindingDismissResult;
package/dist/api.js CHANGED
@@ -1,4 +1,4 @@
1
- import { apiBase, loadConfig, resolveApiKey } from "./config.js";
1
+ import { apiBase, loadConfig, resolveApiKey, resolveChatId } from "./config.js";
2
2
  import { BEARER_SETTINGS_KEYS as SETTINGS_WRITABLE_KEYS, } from "./automation-catalog.js";
3
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. */
@@ -84,8 +84,10 @@ export async function apiFetch(cfg, route, init = {}) {
84
84
  throw new CommandError("No API key. Run `mergestorm login` (agents: `mergestorm login --start`, then `--finish`) or set MERGESTORM_API_KEY.", 1, "missing_api_key");
85
85
  }
86
86
  const { json, timeoutMs = DEFAULT_API_TIMEOUT_MS, signal: userSignal, ...rest } = init;
87
+ const chatId = resolveChatId(cfg);
87
88
  const headers = {
88
89
  Authorization: `Bearer ${key}`,
90
+ ...(chatId ? { "x-mergestorm-chat-id": chatId } : {}),
89
91
  ...rest.headers,
90
92
  };
91
93
  let body = rest.body;
@@ -172,7 +174,14 @@ export function applyPrReviewQuery(query, opts) {
172
174
  * The connected flags are read-only there and deliberately absent here.
173
175
  */
174
176
  export { SETTINGS_WRITABLE_KEYS };
175
- export const REPO_SETTINGS_KEYS = ["auto_patch_enabled", "auto_review_enabled", "auto_land_default"];
177
+ export const REPO_SETTINGS_SWITCH_KEYS = [
178
+ "auto_patch_enabled",
179
+ "auto_review_enabled",
180
+ "auto_land_default",
181
+ "merge_queue_batch_enabled",
182
+ "merge_queue_overdrive",
183
+ ];
184
+ export const REPO_SETTINGS_KEYS = [...REPO_SETTINGS_SWITCH_KEYS, "merge_queue_batch_size", "merge_queue_lands_alone_paths"];
176
185
  function repoSettingsFailure(status, body, retryAfterSeconds, action) {
177
186
  if (status === 429) {
178
187
  return new CommandError(rateLimitedMessage(retryAfterSeconds), REVIEW_EXIT.rate_limited, "rate_limited", {
@@ -773,6 +782,19 @@ export async function listThreads(limit = 20, cfg) {
773
782
  const items = body.items;
774
783
  return Array.isArray(items) ? items : [];
775
784
  }
785
+ export function dismissThreadsLine(result) {
786
+ const threads = result.threads ?? [];
787
+ if (threads.length === 0)
788
+ return null;
789
+ const closed = threads.filter((thread) => thread.state === "resolved" || thread.state === "already_resolved").length;
790
+ const failed = threads.filter((thread) => thread.state === "failed").map((thread) => thread.finding_id);
791
+ const missing = threads.filter((thread) => thread.state === "not_found").map((thread) => thread.finding_id);
792
+ return [
793
+ `GitHub threads: ${closed} of ${threads.length} resolved.`,
794
+ ...(failed.length ? [`Could not resolve ${failed.join(", ")}; run the same dismissal again to retry.`] : []),
795
+ ...(missing.length ? [`No thread found for ${missing.join(", ")}.`] : []),
796
+ ].join(" ");
797
+ }
776
798
  export async function dismissPrFindings(input, cfg, opts) {
777
799
  const resolved = cfg ?? (await loadConfig());
778
800
  const json = {
@@ -63,7 +63,17 @@ export declare const BEARER_SETTINGS: readonly [{
63
63
  readonly label: "Merge queue batch size (pull requests)";
64
64
  readonly kind: "count";
65
65
  readonly min: 2;
66
- readonly max: 8;
66
+ readonly max: 10;
67
+ }, {
68
+ readonly key: "merge_queue_lands_alone_paths";
69
+ readonly flag: "--merge-queue-lands-alone";
70
+ readonly label: "Merge queue lands-alone paths";
71
+ readonly kind: "paths";
72
+ readonly defaults: readonly ["**/migrations/**", "db/migrate/**", ".github/workflows/**"];
73
+ }, {
74
+ readonly key: "merge_queue_overdrive";
75
+ readonly flag: "--merge-queue-overdrive";
76
+ readonly label: "Merge queue overdrive (up to 10 per batch)";
67
77
  }, {
68
78
  readonly key: "ignored_bot_logins";
69
79
  readonly flag: "--ignore-bot";
@@ -95,11 +105,11 @@ export declare const BEARER_SETTINGS_LABELS: Record<BearerSettingsKey, string>;
95
105
  /** Omitted kind means boolean for the original toggle rows. */
96
106
  export type BearerSettingsValues = {
97
107
  [Row in (typeof BEARER_SETTINGS)[number] as Row["key"]]: Row extends {
98
- kind: "logins";
108
+ kind: "logins" | "paths";
99
109
  } ? string[] : Row extends {
100
110
  kind: "seconds" | "count";
101
111
  } ? number : Row extends {
102
112
  values: readonly (infer Value)[];
103
113
  } ? Value : boolean;
104
114
  };
105
- export declare const BEARER_BOOLEAN_SETTINGS_KEYS: ("auto_review_enabled" | "auto_patch_enabled" | "auto_resolve_conflicts_enabled" | "auto_fix_ci_enabled" | "vortex_show_thinking_traces" | "repo_overview_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")[];
115
+ export declare const BEARER_BOOLEAN_SETTINGS_KEYS: ("auto_review_enabled" | "auto_patch_enabled" | "auto_resolve_conflicts_enabled" | "auto_fix_ci_enabled" | "vortex_show_thinking_traces" | "repo_overview_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" | "merge_queue_lands_alone_paths" | "merge_queue_overdrive" | "ignored_bot_logins" | "vortex_bot_skip_check" | "vortex_findings_check" | "cyclone_patch_failure_check")[];
@@ -13,7 +13,9 @@ export const BEARER_SETTINGS = [
13
13
  { key: "auto_land_default", flag: "--auto-land", label: "Auto land (new stacks)" },
14
14
  { key: "auto_land_settle_seconds", flag: "--auto-land-settle", label: "Auto land settle (seconds)", kind: "seconds", min: 15, max: 300 },
15
15
  { key: "merge_queue_batch_enabled", flag: "--merge-queue-batch", label: "Merge queue batching" },
16
- { key: "merge_queue_batch_size", flag: "--merge-queue-batch-size", label: "Merge queue batch size (pull requests)", kind: "count", min: 2, max: 8 },
16
+ { key: "merge_queue_batch_size", flag: "--merge-queue-batch-size", label: "Merge queue batch size (pull requests)", kind: "count", min: 2, max: 10 },
17
+ { key: "merge_queue_lands_alone_paths", flag: "--merge-queue-lands-alone", label: "Merge queue lands-alone paths", kind: "paths", defaults: ["**/migrations/**", "db/migrate/**", ".github/workflows/**"] },
18
+ { key: "merge_queue_overdrive", flag: "--merge-queue-overdrive", label: "Merge queue overdrive (up to 10 per batch)" },
17
19
  { key: "ignored_bot_logins", flag: "--ignore-bot", label: "Ignored bot logins", kind: "logins" },
18
20
  { key: "vortex_bot_skip_check", flag: "--vortex-skip-check", label: "Vortex bot skip check", kind: "enum", values: ["none", "neutral"] },
19
21
  { key: "vortex_findings_check", flag: "--vortex-findings", label: "Vortex findings check", kind: "enum", values: ["failure", "neutral", "success"] },
@@ -1,6 +1,6 @@
1
1
  export { DEFAULT_API, apiBase, clearConfig, configPath, keyDisplay, loadConfig, mergestormHome, resolveApiKey, saveConfig, type Config, } from "../config.js";
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
- export { API_TIMEOUT_PREFIX, DEFAULT_API_TIMEOUT_MS, SETTINGS_WRITABLE_KEYS, adoptStack, apiFetch, cancelMergeQueueEntry, devicePost, dismissPrFindings, enqueueStack, ensureUpperPark, getEnrichedStack, getMe, getPrVortexReview, getReview, getSettings, listRepoSettings, patchRepoSettings, REPO_SETTINGS_KEYS, getStackPublication, landNextStack, listJobs, listMergeQueueEntries, listStacks, listThreads, openStackIssue, parseRetryAfterSeconds, patchSettings, restackStack, setStackPolicy, stackPolicyBody, submitStackPublication, 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 RepoSettings, type RepoSettingsKey, type RepoSettingsPatch, type SettingsResponse, type StackDto, type StackPolicyPatch, type StackPublication, type StackPublicationRequest, type StackPublicationStatus, type ThreadDetail, type ThreadListItem, } from "../api.js";
3
+ export { API_TIMEOUT_PREFIX, DEFAULT_API_TIMEOUT_MS, SETTINGS_WRITABLE_KEYS, adoptStack, apiFetch, cancelMergeQueueEntry, devicePost, dismissPrFindings, dismissThreadsLine, enqueueStack, ensureUpperPark, getEnrichedStack, getMe, getPrVortexReview, getReview, getSettings, listRepoSettings, patchRepoSettings, REPO_SETTINGS_KEYS, getStackPublication, landNextStack, listJobs, listMergeQueueEntries, listStacks, listThreads, openStackIssue, parseRetryAfterSeconds, patchSettings, restackStack, setStackPolicy, stackPolicyBody, submitStackPublication, 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 RepoSettings, type RepoSettingsKey, type RepoSettingsPatch, type SettingsResponse, type StackDto, type StackPolicyPatch, type StackPublication, type StackPublicationRequest, type StackPublicationStatus, 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, 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";
@@ -8,6 +8,7 @@ export { discoverTrunk } from "../git-stack.js";
8
8
  export { parseGithubOriginRepo } from "../git.js";
9
9
  export { BOUNCE_KIND_LABELS, deriveStackAgentsBusy, mergeQueueBounceLabel, type StackAgentsBusy } from "../stack-dto.js";
10
10
  export { STACK_WATCH_SCHEMA, STACK_WATCH_DEFAULT_TIMEOUT_MS, STACK_WATCH_BUSY_RECHECK_S, StackWatchError, StackWatchTimeoutError, pollStackWatch, type PollStackWatchOptions, type StackWatchCursor, type StackWatchEnvelope, } from "../stack-watch.js";
11
- export { stackBlockers, stackBlockersSummary, sameHead, layerAgents, layerAgentsBusy, conflictLiveParent, stackRepair, agentsCannotClear, autoFixCiActive, autoResolveActive, type StackAgentBusy, type StackBlocker, type StackBusyAgent, type StackHeldBlocker, type StackLandGatePending, type StackLayerAgents, type StackRepairHint, } from "../stack-blockers.js";
11
+ export { BREEZE_BRANCH_PREFIX, stackBlockers, stackBlockersSummary, sameHead, layerAgents, layerAgentsBusy, conflictLiveParent, stackRepair, agentsCannotClear, autoFixCiActive, autoResolveActive, type StackAgentBusy, type StackBlocker, type StackBusyAgent, type StackHeldBlocker, type StackLandGatePending, type StackLayerAgents, type StackRepairHint, } from "../stack-blockers.js";
12
12
  export { STACK_WATCH_NOT_DONE_SENTENCE, STACK_WATCH_NOTIFY_PATTERN, stackWatchBackgroundCommand, stackTerminalReason, stackWatchNextArgs, stackWatchObligation, type StackWatchDoneReason, type StackWatchNext, type StackWatchNextArgs, type StackWatchObligation, type StackWatchObligationInput, type StackWatchReason, } from "../stack-watch-obligation.js";
13
- export { publishBranchServerSide, publishWorkingBranch, workingBranchPublication, type ServerPublishResult, type WorkingBranchInput, } from "../server-publish.js";
13
+ export { READ_ONLY_GIT, publishBranchServerSide, publishWorkingBranch, workingBranchPublication, type GitRead, type ServerPublishDeps, type ServerPublishInput, type ServerPublishResult, type WorkingBranchInput, } from "../server-publish.js";
14
+ export { MEMORY_KINDS, MEMORY_MODES, MEMORY_SCOPES, MEMORY_SEARCH_LIMIT_MAX, deleteMemory, getMemoryMode, listMemories, memoryChatId, saveMemory, searchMemories, setMemoryMode, updateMemory, type MemoryItem, type MemoryKind, type MemoryList, type MemoryMode, type MemoryScope, type MemorySearch, } from "../memory-api.js";
@@ -1,6 +1,6 @@
1
1
  export { DEFAULT_API, apiBase, clearConfig, configPath, keyDisplay, loadConfig, mergestormHome, resolveApiKey, saveConfig, } from "../config.js";
2
2
  export { BEARER_SETTINGS, BEARER_BOOLEAN_SETTINGS_KEYS, BEARER_SETTINGS_FLAGS, BEARER_SETTINGS_KEYS, BEARER_SETTINGS_LABELS, } from "../automation-catalog.js";
3
- export { API_TIMEOUT_PREFIX, DEFAULT_API_TIMEOUT_MS, SETTINGS_WRITABLE_KEYS, adoptStack, apiFetch, cancelMergeQueueEntry, devicePost, dismissPrFindings, enqueueStack, ensureUpperPark, getEnrichedStack, getMe, getPrVortexReview, getReview, getSettings, listRepoSettings, patchRepoSettings, REPO_SETTINGS_KEYS, getStackPublication, landNextStack, listJobs, listMergeQueueEntries, listStacks, listThreads, openStackIssue, parseRetryAfterSeconds, patchSettings, restackStack, setStackPolicy, stackPolicyBody, submitStackPublication, } from "../api.js";
3
+ export { API_TIMEOUT_PREFIX, DEFAULT_API_TIMEOUT_MS, SETTINGS_WRITABLE_KEYS, adoptStack, apiFetch, cancelMergeQueueEntry, devicePost, dismissPrFindings, dismissThreadsLine, enqueueStack, ensureUpperPark, getEnrichedStack, getMe, getPrVortexReview, getReview, getSettings, listRepoSettings, patchRepoSettings, REPO_SETTINGS_KEYS, getStackPublication, landNextStack, listJobs, listMergeQueueEntries, listStacks, listThreads, openStackIssue, parseRetryAfterSeconds, patchSettings, restackStack, setStackPolicy, stackPolicyBody, submitStackPublication, } 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, 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";
@@ -8,6 +8,7 @@ export { discoverTrunk } from "../git-stack.js";
8
8
  export { parseGithubOriginRepo } from "../git.js";
9
9
  export { BOUNCE_KIND_LABELS, deriveStackAgentsBusy, mergeQueueBounceLabel } from "../stack-dto.js";
10
10
  export { STACK_WATCH_SCHEMA, STACK_WATCH_DEFAULT_TIMEOUT_MS, STACK_WATCH_BUSY_RECHECK_S, StackWatchError, StackWatchTimeoutError, pollStackWatch, } from "../stack-watch.js";
11
- export { stackBlockers, stackBlockersSummary, sameHead, layerAgents, layerAgentsBusy, conflictLiveParent, stackRepair, agentsCannotClear, autoFixCiActive, autoResolveActive, } from "../stack-blockers.js";
11
+ export { BREEZE_BRANCH_PREFIX, stackBlockers, stackBlockersSummary, sameHead, layerAgents, layerAgentsBusy, conflictLiveParent, stackRepair, agentsCannotClear, autoFixCiActive, autoResolveActive, } from "../stack-blockers.js";
12
12
  export { STACK_WATCH_NOT_DONE_SENTENCE, STACK_WATCH_NOTIFY_PATTERN, stackWatchBackgroundCommand, stackTerminalReason, stackWatchNextArgs, stackWatchObligation, } from "../stack-watch-obligation.js";
13
- export { publishBranchServerSide, publishWorkingBranch, workingBranchPublication, } from "../server-publish.js";
13
+ export { READ_ONLY_GIT, publishBranchServerSide, publishWorkingBranch, workingBranchPublication, } from "../server-publish.js";
14
+ export { MEMORY_KINDS, MEMORY_MODES, MEMORY_SCOPES, MEMORY_SEARCH_LIMIT_MAX, deleteMemory, getMemoryMode, listMemories, memoryChatId, saveMemory, searchMemories, setMemoryMode, updateMemory, } from "../memory-api.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" | "auto_resolve_conflicts_enabled" | "auto_fix_ci_enabled" | "vortex_show_thinking_traces" | "repo_overview_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>;
30
+ export declare const SETTINGS_LABELS: Record<"auto_review_enabled" | "auto_patch_enabled" | "auto_resolve_conflicts_enabled" | "auto_fix_ci_enabled" | "vortex_show_thinking_traces" | "repo_overview_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" | "merge_queue_lands_alone_paths" | "merge_queue_overdrive" | "ignored_bot_logins" | "vortex_bot_skip_check" | "vortex_findings_check" | "cyclone_patch_failure_check", string>;
31
31
  export declare const STACKS_READY_LABEL = "Stacks, merge queue, Auto land";
32
32
  export declare const STACKS_READY_WORDS: {
33
33
  readonly on: "ready";
@@ -1,4 +1,4 @@
1
- import { dismissPrFindings, } from "../api.js";
1
+ import { dismissPrFindings, dismissThreadsLine, } from "../api.js";
2
2
  import { loadConfig } from "../config.js";
3
3
  import { CommandError, REVIEW_EXIT } from "../errors.js";
4
4
  import { stackWatchObligation } from "../stack-watch-obligation.js";
@@ -110,6 +110,9 @@ export function formatDismissResult(result, watch) {
110
110
  }
111
111
  if (result.remaining.length)
112
112
  lines.push("Still open:", ...result.remaining.map(findingLine));
113
+ const threads = dismissThreadsLine(result);
114
+ if (threads)
115
+ lines.push(threads);
113
116
  }
114
117
  const seam = result.gate.seam;
115
118
  if (seam) {
@@ -0,0 +1,30 @@
1
+ import { type Config } from "../config.js";
2
+ import { deleteMemory, getMemoryMode, listMemories, saveMemory, searchMemories, setMemoryMode, updateMemory, type MemoryItem, type MemoryKind, type MemoryScope } from "../memory-api.js";
3
+ export declare const MEMORY_USAGE: string;
4
+ export declare const MEMORY_SUBCOMMANDS: readonly ["list", "search", "add", "update", "delete", "mode"];
5
+ type Flags = {
6
+ positional: string[];
7
+ json: boolean;
8
+ repo?: string;
9
+ kind?: MemoryKind;
10
+ scope?: MemoryScope;
11
+ limit?: number;
12
+ };
13
+ export declare function parseMemoryFlags(args: string[], allowed: ReadonlyArray<"repo" | "kind" | "scope" | "limit">): Flags;
14
+ export declare function formatMemoryLine(memory: MemoryItem): string;
15
+ export type MemoryDeps = {
16
+ loadConfig?: () => Promise<Config>;
17
+ chatId?: string | null;
18
+ print?: (line: string) => void;
19
+ api?: Partial<{
20
+ listMemories: typeof listMemories;
21
+ searchMemories: typeof searchMemories;
22
+ saveMemory: typeof saveMemory;
23
+ updateMemory: typeof updateMemory;
24
+ deleteMemory: typeof deleteMemory;
25
+ getMemoryMode: typeof getMemoryMode;
26
+ setMemoryMode: typeof setMemoryMode;
27
+ }>;
28
+ };
29
+ export declare function cmdMemory(args: string[], deps?: MemoryDeps): Promise<void>;
30
+ export {};
@@ -0,0 +1,152 @@
1
+ import { loadConfig } from "../config.js";
2
+ import { CommandError, REVIEW_EXIT } from "../errors.js";
3
+ import { MEMORY_KINDS, MEMORY_SEARCH_LIMIT_MAX, deleteMemory, getMemoryMode, isMemoryKind, isMemoryMode, isMemoryScope, listMemories, memoryChatId, saveMemory, searchMemories, setMemoryMode, updateMemory, } from "../memory-api.js";
4
+ export const MEMORY_USAGE = [
5
+ "usage: mergestorm memory list [--scope user|repo] [--repo <owner/repo>] [--kind <kind>] [--json]",
6
+ " mergestorm memory search <query> [--repo <owner/repo>] [--limit <1-20>] [--json]",
7
+ " mergestorm memory add <text> --kind <kind> [--repo <owner/repo>] [--json]",
8
+ " mergestorm memory update <id> <text> [--kind <kind>] [--json]",
9
+ " mergestorm memory delete <id> [--json]",
10
+ " mergestorm memory mode [off|on_request|all] [--json]",
11
+ ` kind is one of ${MEMORY_KINDS.join(", ")}. With --repo a memory is about that repository, otherwise about you.`,
12
+ " search covers your memories, and with --repo that repository's memories and standing rules too.",
13
+ " Inside a Breeze chat (MERGESTORM_CHAT_ID), add and search follow that chat's memory setting.",
14
+ ].join("\n");
15
+ export const MEMORY_SUBCOMMANDS = ["list", "search", "add", "update", "delete", "mode"];
16
+ const REPO = /^[A-Za-z0-9](?:[A-Za-z0-9-]{0,38})\/[A-Za-z0-9._-]{1,100}$/;
17
+ function usage(message) {
18
+ return new CommandError(message ? `${message}\n${MEMORY_USAGE}` : MEMORY_USAGE, REVIEW_EXIT.usage, "usage");
19
+ }
20
+ export function parseMemoryFlags(args, allowed) {
21
+ const flags = { positional: [], json: false };
22
+ const value = (flag, raw) => {
23
+ if (raw === undefined || raw.startsWith("--"))
24
+ throw usage(`${flag} needs a value`);
25
+ return raw.trim();
26
+ };
27
+ for (let i = 0; i < args.length; i++) {
28
+ const arg = args[i];
29
+ const name = arg.startsWith("--") ? arg.slice(2) : null;
30
+ if (arg === "--json")
31
+ flags.json = true;
32
+ else if (name === "repo" && allowed.includes("repo")) {
33
+ const repo = value(arg, args[++i]).replace(/^https?:\/\/github\.com\//i, "").replace(/\.git$/i, "");
34
+ if (!REPO.test(repo))
35
+ throw usage("--repo needs <owner/repo>");
36
+ flags.repo = repo.toLowerCase();
37
+ }
38
+ else if (name === "kind" && allowed.includes("kind")) {
39
+ const kind = value(arg, args[++i]);
40
+ if (!isMemoryKind(kind))
41
+ throw usage(`--kind is one of ${MEMORY_KINDS.join(", ")}`);
42
+ flags.kind = kind;
43
+ }
44
+ else if (name === "scope" && allowed.includes("scope")) {
45
+ const scope = value(arg, args[++i]);
46
+ if (!isMemoryScope(scope))
47
+ throw usage("--scope is user or repo");
48
+ flags.scope = scope;
49
+ }
50
+ else if (name === "limit" && allowed.includes("limit")) {
51
+ const limit = Number(value(arg, args[++i]));
52
+ if (!Number.isInteger(limit) || limit < 1 || limit > MEMORY_SEARCH_LIMIT_MAX)
53
+ throw usage(`--limit is a whole number from 1 to ${MEMORY_SEARCH_LIMIT_MAX}`);
54
+ flags.limit = limit;
55
+ }
56
+ else if (arg.startsWith("--"))
57
+ throw usage(`unknown argument ${arg}`);
58
+ else
59
+ flags.positional.push(arg);
60
+ }
61
+ return flags;
62
+ }
63
+ export function formatMemoryLine(memory) {
64
+ const where = memory.repo ? `${memory.kind}, ${memory.repo}` : memory.kind;
65
+ return `${memory.id} (${where}) ${memory.text}`;
66
+ }
67
+ export async function cmdMemory(args, deps = {}) {
68
+ const print = deps.print ?? ((line) => console.log(line));
69
+ const api = { listMemories, searchMemories, saveMemory, updateMemory, deleteMemory, getMemoryMode, setMemoryMode, ...deps.api };
70
+ const sub = args[0]?.toLowerCase();
71
+ const rest = args.slice(1);
72
+ if (!sub || sub === "-h" || sub === "--help" || sub === "help" || rest.some((arg) => arg === "-h" || arg === "--help")) {
73
+ print(MEMORY_USAGE);
74
+ return;
75
+ }
76
+ const config = () => (deps.loadConfig ?? loadConfig)();
77
+ const chatId = deps.chatId === undefined ? memoryChatId() : deps.chatId;
78
+ const emit = (json, data, lines) => {
79
+ if (json)
80
+ print(JSON.stringify(data, null, 2));
81
+ else
82
+ for (const line of lines())
83
+ print(line);
84
+ };
85
+ if (sub === "list" || sub === "ls") {
86
+ const flags = parseMemoryFlags(rest, ["repo", "kind", "scope"]);
87
+ if (flags.positional.length > 0)
88
+ throw usage(`unexpected argument ${flags.positional[0]}`);
89
+ const listed = await api.listMemories({ ...(flags.scope ? { scope: flags.scope } : {}), ...(flags.repo ? { repo: flags.repo } : {}), ...(flags.kind ? { kind: flags.kind } : {}) }, await config());
90
+ emit(flags.json, listed, () => [
91
+ `Memory is ${listed.memory_mode}. ${listed.items.length === 1 ? "1 memory" : `${listed.items.length} memories`}.`,
92
+ ...listed.items.map(formatMemoryLine),
93
+ ]);
94
+ return;
95
+ }
96
+ if (sub === "search") {
97
+ const flags = parseMemoryFlags(rest, ["repo", "limit"]);
98
+ const query = flags.positional.join(" ").trim();
99
+ if (!query)
100
+ throw usage("search needs a query");
101
+ const found = await api.searchMemories({ query, ...(flags.repo ? { repo: flags.repo } : {}), ...(flags.limit ? { limit: flags.limit } : {}), chatId }, await config());
102
+ emit(flags.json, found, () => {
103
+ if (found.memory_mode === "off")
104
+ return ["Memory is off, so nothing was searched."];
105
+ const all = [...found.rules, ...found.items];
106
+ return all.length === 0 ? ["Nothing in memory matches."] : all.map(formatMemoryLine);
107
+ });
108
+ return;
109
+ }
110
+ if (sub === "add" || sub === "save" || sub === "remember") {
111
+ const flags = parseMemoryFlags(rest, ["repo", "kind"]);
112
+ const text = flags.positional.join(" ").trim();
113
+ if (!text)
114
+ throw usage("add needs the text to remember");
115
+ if (!flags.kind)
116
+ throw usage(`add needs --kind (${MEMORY_KINDS.join(", ")})`);
117
+ const saved = await api.saveMemory({ text, kind: flags.kind, ...(flags.repo ? { repo: flags.repo } : {}), chatId }, await config());
118
+ emit(flags.json, saved, () => [saved.merged ? `That matched memory ${saved.id}, which now says this.` : `Saved to memory as ${saved.id}.`]);
119
+ return;
120
+ }
121
+ if (sub === "update" || sub === "edit") {
122
+ const flags = parseMemoryFlags(rest, ["kind"]);
123
+ const [id, ...words] = flags.positional;
124
+ const text = words.join(" ").trim();
125
+ if (!id || !text)
126
+ throw usage("update needs a memory id and the new text");
127
+ const updated = await api.updateMemory(id, { text, ...(flags.kind ? { kind: flags.kind } : {}) }, await config());
128
+ emit(flags.json, updated, () => [`Memory ${updated.id} updated.`]);
129
+ return;
130
+ }
131
+ if (sub === "delete" || sub === "rm" || sub === "forget") {
132
+ const flags = parseMemoryFlags(rest, []);
133
+ const [id, extra] = flags.positional;
134
+ if (!id || extra !== undefined)
135
+ throw usage("delete needs one memory id");
136
+ const deleted = await api.deleteMemory(id, await config());
137
+ emit(flags.json, deleted, () => [`Memory ${deleted.id} deleted.`]);
138
+ return;
139
+ }
140
+ if (sub === "mode") {
141
+ const flags = parseMemoryFlags(rest, []);
142
+ const [mode, extra] = flags.positional;
143
+ if (extra !== undefined)
144
+ throw usage(`unexpected argument ${extra}`);
145
+ if (mode !== undefined && !isMemoryMode(mode))
146
+ throw usage("mode is off, on_request or all");
147
+ const result = mode === undefined ? await api.getMemoryMode(await config()) : await api.setMemoryMode(mode, await config());
148
+ emit(flags.json, result, () => [`Memory is ${result.memory_mode}.`]);
149
+ return;
150
+ }
151
+ throw usage(`unknown memory subcommand: ${sub}`);
152
+ }
@@ -8,6 +8,7 @@ import { cmdIssue } from "./issue.js";
8
8
  import { cmdJobs, cmdThread } from "./jobs.js";
9
9
  import { cmdLogin } from "./login.js";
10
10
  import { cmdLogout } from "./logout.js";
11
+ import { cmdMemory } from "./memory.js";
11
12
  import { cmdQueue } from "./queue.js";
12
13
  import { cmdPr } from "./pr.js";
13
14
  import { cmdReview } from "./review.js";
@@ -203,7 +204,8 @@ export const COMMAND_REGISTRY = [
203
204
  " [--auto-land on|off] Auto land default for new stacks",
204
205
  " [--auto-land-settle <seconds>] How long Auto land waits before queueing (15-300, default 60)",
205
206
  " [--merge-queue-batch on|off] Test several queued pull requests on one CI run",
206
- " [--merge-queue-batch-size <n>] Most pull requests per batch (2-8, default 4)",
207
+ " [--merge-queue-batch-size <n>] Most pull requests per batch (2-10, default 4)",
208
+ " [--merge-queue-overdrive on|off] Account default: batch up to 10 queued pull requests; --repo overrides it per repository",
207
209
  " [--ignore-bot add <login>|remove <login>|clear]",
208
210
  " [--vortex-skip-check none|neutral] [--vortex-findings failure|neutral|success]",
209
211
  " [--cyclone-fail-check failure|neutral]",
@@ -217,6 +219,21 @@ export const COMMAND_REGISTRY = [
217
219
  await cmdSettings(args, { mode: ctx.mode });
218
220
  },
219
221
  },
222
+ {
223
+ name: "memory",
224
+ summary: "Your memories: list, search, add, update, delete, mode",
225
+ usage: [
226
+ "mergestorm memory list [--scope user|repo] [--repo <owner/repo>] [--kind <kind>] [--json]",
227
+ "mergestorm memory search <query> [--repo <owner/repo>] [--limit <1-20>] [--json]",
228
+ "mergestorm memory add <text> --kind rule|style|preference|fact|project [--repo <owner/repo>] [--json]",
229
+ "mergestorm memory update <id> <text> [--kind <kind>] [--json]",
230
+ "mergestorm memory delete <id> [--json]",
231
+ "mergestorm memory mode [off|on_request|all] [--json] Read or set whether Breeze remembers",
232
+ ],
233
+ async run(args) {
234
+ await cmdMemory(args);
235
+ },
236
+ },
220
237
  {
221
238
  name: "skill",
222
239
  summary: "Install Mergestorm skills for Claude, Cursor, or agents",
@@ -3,7 +3,7 @@ import { type RepoSettings, type RepoSettingsPatch, type SettingsPatch, type Set
3
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" | "auto_resolve_conflicts_enabled" | "auto_fix_ci_enabled" | "vortex_show_thinking_traces" | "repo_overview_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">;
6
+ export declare const SETTINGS_FLAGS: Record<string, "auto_review_enabled" | "auto_patch_enabled" | "auto_resolve_conflicts_enabled" | "auto_fix_ci_enabled" | "vortex_show_thinking_traces" | "repo_overview_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" | "merge_queue_lands_alone_paths" | "merge_queue_overdrive" | "ignored_bot_logins" | "vortex_bot_skip_check" | "vortex_findings_check" | "cyclone_patch_failure_check">;
7
7
  type IgnoreBotOperation = {
8
8
  action: "add" | "remove";
9
9
  login: string;
@@ -22,6 +22,7 @@ export type SettingsArgs = {
22
22
  repo?: RepoSettingsScope;
23
23
  listRepos?: boolean;
24
24
  };
25
+ export declare function parsePathsFlagValue(flag: string, value: string | undefined, defaults: readonly string[]): string[];
25
26
  export declare function formatRepoSettingsLines(repos: readonly RepoSettings[]): string[];
26
27
  /** Parse boolean and enum flags plus ordered ignore-list operations. */
27
28
  export declare function parseSettingsArgs(rawArgs: string[]): SettingsArgs;
@@ -14,7 +14,25 @@ const REPO_FLAG_LABELS = {
14
14
  auto_patch_enabled: "Auto patch",
15
15
  auto_review_enabled: "Auto review",
16
16
  auto_land_default: "Auto land (new stacks)",
17
+ merge_queue_batch_enabled: "Merge queue batching",
18
+ merge_queue_batch_size: "Merge queue batch size (pull requests)",
19
+ merge_queue_lands_alone_paths: "Merge queue lands-alone paths",
20
+ merge_queue_overdrive: "Merge queue overdrive",
17
21
  };
22
+ export function parsePathsFlagValue(flag, value, defaults) {
23
+ if (value === undefined || value === "" || value.startsWith("--")) {
24
+ throw new CommandError(`${flag} takes a comma-separated list of path patterns, default, or none.`, 2, "usage");
25
+ }
26
+ if (value === "default")
27
+ return [...defaults];
28
+ if (value === "none")
29
+ return [];
30
+ const paths = [...new Set(value.split(",").map((entry) => entry.trim()).filter((entry) => entry !== ""))];
31
+ if (paths.length === 0 || paths.some((entry) => /\s/.test(entry))) {
32
+ throw new CommandError(`${flag} takes a comma-separated list of path patterns, default, or none.`, 2, "usage");
33
+ }
34
+ return paths;
35
+ }
18
36
  function parseRepoSlug(raw) {
19
37
  const match = /^([A-Za-z0-9_.-]+)\/([A-Za-z0-9_.-]+)$/.exec(raw ?? "");
20
38
  if (!match)
@@ -61,18 +79,41 @@ function parseRepoSettingsArgs(slug, args) {
61
79
  throw new CommandError(`Unknown settings flag: ${flag}. See \`mergestorm --help\`.`, 2, "usage");
62
80
  }
63
81
  if (!REPO_SETTINGS_KEYS.includes(key)) {
64
- throw new CommandError(`${flag} is an account setting. With --repo only --auto-patch, --auto-review and --auto-land can be set.`, 2, "usage");
82
+ throw new CommandError(`${flag} is an account setting. With --repo only --auto-patch, --auto-review, --auto-land, --merge-queue-batch, --merge-queue-batch-size, --merge-queue-lands-alone and --merge-queue-overdrive can be set.`, 2, "usage");
65
83
  }
66
84
  const value = eq >= 0 ? arg.slice(eq + 1) : args[++i];
67
- if (value !== "on" && value !== "off" && value !== "inherit") {
85
+ const row = BEARER_SETTINGS.find((row) => row.key === key);
86
+ if (value === "inherit") {
87
+ scope.patch[key] = null;
88
+ continue;
89
+ }
90
+ if ("kind" in row && row.kind === "count") {
91
+ const amount = value !== undefined && /^\d+$/.test(value) ? Number(value) : Number.NaN;
92
+ if (!Number.isInteger(amount) || amount < row.min || amount > row.max) {
93
+ throw new CommandError(`${flag} takes a whole number from ${row.min} through ${row.max}, or inherit, with --repo.`, 2, "usage");
94
+ }
95
+ scope.patch.merge_queue_batch_size = amount;
96
+ continue;
97
+ }
98
+ if ("kind" in row && row.kind === "paths") {
99
+ scope.patch.merge_queue_lands_alone_paths = parsePathsFlagValue(flag, value, row.defaults);
100
+ continue;
101
+ }
102
+ if (value !== "on" && value !== "off") {
68
103
  throw new CommandError(`${flag} takes on, off or inherit with --repo.`, 2, "usage");
69
104
  }
70
- scope.patch[key] = value === "inherit" ? null : value === "on";
105
+ scope.patch[key] = value === "on";
71
106
  }
72
107
  return { json, patch: {}, repo: scope };
73
108
  }
74
109
  function repoSettingWords(value) {
75
- return value === null ? "inherit (follows the account)" : value ? "on" : "off";
110
+ if (value === null)
111
+ return "inherit (follows the account)";
112
+ if (Array.isArray(value))
113
+ return value.length === 0 ? "(none)" : value.join(", ");
114
+ if (typeof value === "number")
115
+ return String(value);
116
+ return value ? "on" : "off";
76
117
  }
77
118
  export function formatRepoSettingsLines(repos) {
78
119
  if (repos.length === 0)
@@ -149,6 +190,10 @@ export function parseSettingsArgs(rawArgs) {
149
190
  }
150
191
  const value = eq >= 0 ? arg.slice(eq + 1) : args[++i];
151
192
  const row = BEARER_SETTINGS.find((row) => row.key === key);
193
+ if ("kind" in row && row.kind === "paths") {
194
+ Object.assign(patch, { [key]: parsePathsFlagValue(flag, value, row.defaults) });
195
+ continue;
196
+ }
152
197
  if ("kind" in row && row.kind === "logins") {
153
198
  if (value === "clear")
154
199
  ignoreBots.push({ action: "clear" });