mergestorm 0.3.64 → 0.3.67
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 +28 -9
- package/dist/api.d.ts +77 -11
- package/dist/api.js +228 -6
- package/dist/automation-catalog.d.ts +9 -1
- package/dist/automation-catalog.js +2 -0
- package/dist/cli.js +21 -19
- package/dist/client/index.d.ts +3 -2
- package/dist/client/index.js +3 -2
- package/dist/commands/account.d.ts +3 -0
- package/dist/commands/account.js +75 -0
- package/dist/commands/browse.d.ts +1 -9
- package/dist/commands/browse.js +10 -8
- package/dist/commands/dismiss.d.ts +0 -2
- package/dist/commands/dismiss.js +2 -2
- package/dist/commands/help.js +3 -2
- package/dist/commands/issue.d.ts +24 -6
- package/dist/commands/issue.js +157 -31
- package/dist/commands/login.js +46 -14
- package/dist/commands/logout.js +6 -7
- package/dist/commands/memory.d.ts +2 -3
- package/dist/commands/memory.js +8 -6
- package/dist/commands/registry.d.ts +1 -0
- package/dist/commands/registry.js +35 -11
- package/dist/commands/review-client.d.ts +0 -3
- package/dist/commands/review-client.js +3 -3
- package/dist/commands/review.d.ts +1 -1
- package/dist/commands/review.js +1 -1
- package/dist/commands/settings.d.ts +3 -5
- package/dist/commands/settings.js +5 -3
- package/dist/commands/stack.d.ts +19 -5
- package/dist/commands/stack.js +88 -4
- package/dist/commands/whoami.js +14 -10
- package/dist/config.d.ts +55 -2
- package/dist/config.js +264 -13
- package/dist/errors.d.ts +1 -1
- package/dist/gh.d.ts +0 -4
- package/dist/gh.js +4 -4
- package/dist/git.d.ts +1 -1
- package/dist/git.js +2 -2
- package/dist/memory-api.d.ts +1 -0
- package/dist/memory-api.js +2 -2
- package/dist/node-version.d.ts +0 -2
- package/dist/node-version.js +1 -1
- package/dist/pr-body.d.ts +4 -0
- package/dist/pr-body.js +15 -0
- package/dist/repo-identity.d.ts +2 -1
- package/dist/repo-identity.js +8 -4
- package/dist/server-publish.d.ts +1 -24
- package/dist/server-publish.js +9 -9
- package/dist/stack-blockers.d.ts +12 -38
- package/dist/stack-blockers.js +57 -33
- package/dist/stack-dto.d.ts +3 -0
- package/dist/stack-dto.js +15 -2
- package/dist/stack-meta.d.ts +2 -2
- package/dist/stack-meta.js +1 -1
- package/dist/stack-watch-loop.d.ts +2 -13
- package/dist/stack-watch-loop.js +12 -12
- package/dist/submit-pr-base.d.ts +0 -1
- package/dist/submit-pr-base.js +1 -1
- package/dist/ui/ansi.d.ts +0 -10
- package/dist/ui/ansi.js +2 -2
- package/dist/ui/banner.d.ts +3 -1
- package/dist/ui/banner.js +13 -3
- package/dist/ui/box.d.ts +0 -2
- package/dist/ui/box.js +1 -1
- package/dist/ui/prompt.d.ts +0 -11
- package/dist/ui/prompt.js +7 -7
- package/dist/ui/tabs.d.ts +2 -2
- package/dist/ui/tabs.js +1 -1
- package/dist/ui/width.d.ts +0 -9
- package/dist/ui/width.js +3 -3
- package/dist/web-api.d.ts +17 -0
- package/dist/web-api.js +30 -0
- package/package.json +1 -1
- package/skill/SKILL.md +4 -0
- package/skill/mergestorm-bounce-watch/SKILL.md +1 -1
package/README.md
CHANGED
|
@@ -45,12 +45,12 @@ On a TTY, bare `mergestorm` (or `mergestorm shell`) opens a branded REPL: a comp
|
|
|
45
45
|
|
|
46
46
|
```
|
|
47
47
|
╭──────────────────────────────────────────────────────────────────────────────╮
|
|
48
|
-
│ ▀▀▀▀▀▀▀▀▀▀▘ mergestorm v0.3.
|
|
48
|
+
│ ▀▀▀▀▀▀▀▀▀▀▘ mergestorm v0.3.67 │
|
|
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.
|
|
53
|
+
│ ▀ New in v0.3.67: Issue review: reply once, then one re-check │
|
|
54
54
|
╰──────────────────────────────────────────────────────────────────────────────╯
|
|
55
55
|
mergestorm
|
|
56
56
|
╭──────────────────────────────────────────────────────────────────────────────╮
|
|
@@ -75,11 +75,14 @@ One-shot subcommands work the same inside the shell and from argv (scripts/CI):
|
|
|
75
75
|
```
|
|
76
76
|
mergestorm Interactive shell (TTY only)
|
|
77
77
|
mergestorm shell Explicit shell entry
|
|
78
|
-
mergestorm login Sign in via browser
|
|
78
|
+
mergestorm login Sign in via browser and add the account (up to 3 saved)
|
|
79
79
|
mergestorm login --start Print a sign-in URL and code, then exit (agents)
|
|
80
80
|
mergestorm login --finish Store the API key once the code from --start is approved
|
|
81
81
|
mergestorm login --key Paste an existing API key instead (headless/CI)
|
|
82
|
-
mergestorm logout
|
|
82
|
+
mergestorm logout Log out of the active account (or --account <name>) and revoke its key
|
|
83
|
+
mergestorm account list [--json] Saved accounts (up to 3), the active one marked
|
|
84
|
+
mergestorm account use <name> Make <name> the active account
|
|
85
|
+
mergestorm account remove <name> Log out of <name> and revoke its key
|
|
83
86
|
mergestorm review [base] [head] Review git diff base...head (default: origin/HEAD or main or master)
|
|
84
87
|
mergestorm status <job_id> Fetch a review job (envelope; --json --wait --timeout)
|
|
85
88
|
mergestorm status Tabbed Status / Usage / Jobs browser (TTY)
|
|
@@ -87,10 +90,11 @@ mergestorm credits [--json] Usage panel (bar + last 5 jobs); tabbed browser
|
|
|
87
90
|
mergestorm jobs [n] [--json] Recent review jobs (default 10, max 50)
|
|
88
91
|
mergestorm branches [n] Recently reviewed branches (arrow-pick on TTY)
|
|
89
92
|
mergestorm chain [slug] Branch review-chain timeline (default: current branch)
|
|
90
|
-
mergestorm whoami [--json]
|
|
93
|
+
mergestorm whoami [--json] Active account, key prefix, plan, API base, config path
|
|
91
94
|
mergestorm skill install --claude|--cursor|--agents Copy the Mergestorm skills into this repo
|
|
92
95
|
mergestorm thread <slug> Jobs in a review thread
|
|
93
|
-
mergestorm issue create --title <t> Open a GitHub issue through Mergestorm, no `gh` (`--body`, `--body-file`, `--label`, `--repo`)
|
|
96
|
+
mergestorm issue create --title <t> Open a GitHub issue through Mergestorm, no `gh` (`--body`, `--body-file`, `--label`, `--repo`, `--wait`)
|
|
97
|
+
mergestorm issue wait <n> Wait for the Vortex issue review; exits 0 once approved, 4 with points to answer
|
|
94
98
|
mergestorm stack create [name] New local stack layer (optional `--onto` / `--trunk` / `--extend`)
|
|
95
99
|
mergestorm stack submit Push layers, open PRs (`gh`), register via adopt (optional `--extend`, `--server-side`)
|
|
96
100
|
mergestorm stack publish Publish the current branch through Mergestorm with no GitHub credentials (`--base <branch> --extend` to stack, `--status` to read it)
|
|
@@ -167,25 +171,39 @@ mg stack publish --status # absent | pushed | opened | registered
|
|
|
167
171
|
```
|
|
168
172
|
|
|
169
173
|
- **Issues too.** `mg issue create` opens the issue a PR needs through the same Surge App, with existing labels only. Running it again with the same title returns the issue it already opened. Put `Fixes #<n>` in the commit message; `stack submit` carries it into the PR body.
|
|
174
|
+
- **Issue reviews first.** On a repository with Vortex issue reviews on, Vortex reviews each new issue before any PR. `mg issue wait <n>` (or `mg issue create --wait`) waits for that review: it exits 0 once the issue is approved, or exits 4 with the numbered points to answer. Reply once in a comment on the issue, accepting each blocking point's suggested fix or saying why not, and wait again; Vortex re-checks only the blocking points, once, and approves the issue. Notes never block; the PR review checks them against the code. A person can approve early with an issue comment whose first line is `mergestorm-issue: approve`. `stack submit` refuses to open a PR whose tip commit links an issue that is not approved yet, or links no issue at all while issue reviews are on.
|
|
170
175
|
- **Exact contents.** Every commit travels with its Git tree SHA and the whole content of each changed file, so binaries, deletions, renames, executable bits and symlinks arrive unchanged. Mergestorm moves the branch only when GitHub builds the same tree, and `mg` checks the returned tree against your local branch.
|
|
171
176
|
- **Never overwrites surprises.** `mg` looks at the branch on GitHub first. If its files match one of your local commits, only the commits after that one are appended. If they match none (someone else pushed, or you rebased), `mg` stops with `head_changed`; `--replace-head <sha>` replaces exactly that head and nothing else. Mergestorm refuses `main`, `master`, the default branch, protected branches and its own `mg-stack-*`, `mg-park-*` and `mq-batch-*` branches.
|
|
172
177
|
- **Safe to run again.** After a timeout or an error, run the same command again. It finds the branch, pull request and stack it already made and finishes the missing step; it does not create duplicates.
|
|
173
178
|
- **Read-only home directory.** `mg` keeps the saved login and the local stack state of `stack create` / `stack submit` under `~/.mergestorm`. Where the home directory cannot be written, set `MERGESTORM_HOME` to a writable directory (outside the repository) and every command uses that instead. `MERGESTORM_API_KEY` supplies the key without `mg login`.
|
|
179
|
+
- **Several accounts.** `mg login` keeps up to three accounts signed in; `mg account use <name>` picks the active one, and `--account <name>` or `MERGESTORM_ACCOUNT` runs one command as another.
|
|
174
180
|
- **Limits.** 20 commits, 500 changed paths, 2 MiB a file and 8 MiB in total per publication. Submodules and merge commits are refused with the path or the reason. `stack submit --server-side` publishes the first two layers of a stack; a third layer needs a park freeze, which this mode does not create, so add it once the first two are registered.
|
|
175
181
|
|
|
176
182
|
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.
|
|
177
183
|
|
|
178
184
|
### `login`
|
|
179
185
|
|
|
180
|
-
Runs a browser device-authorization flow: prints a short code, opens `https://mergestorm.ai/cli/auth`, and — once you approve — mints and
|
|
186
|
+
Runs a browser device-authorization flow: prints a short code, opens `https://mergestorm.ai/cli/auth`, and — once you approve — mints an API key and saves it as an account in `~/.mergestorm/config.json` (mode `600`), or in `$MERGESTORM_HOME/config.json` when `MERGESTORM_HOME` is set. No key copy/paste needed. The account is named by its GitHub login (or its email when there is no GitHub login), as `mg whoami` reports it, and becomes the active account.
|
|
187
|
+
|
|
188
|
+
Up to three accounts can stay logged in at once. `mg login` again adds another account, or replaces the key of an account that is already saved. A fourth login is refused with the three names; remove one first. A login saved by an older `mg` migrates on first read into an account named by its key prefix (`msk_live_abcd`), and takes its real name the next time a command such as `mg whoami` reaches the API. The file also keeps the active account's key at its top level, so an older `mg` or `mergestorm-mcp` on the same machine keeps signing in as the active account.
|
|
181
189
|
|
|
182
190
|
For headless or CI environments, use `mergestorm login --key` to paste an existing `msk_live_…` key (create one on the dashboard [Settings → API](https://mergestorm.ai/settings#api)). On a TTY the paste is hidden (not echoed); the CLI verifies the key with the API before saving.
|
|
183
191
|
|
|
184
192
|
For an agent, which cannot sit in a prompt while a person signs in, use two steps. `mergestorm login --start` prints the URL and the short code and exits. Give both to the account owner, who opens [mergestorm.ai/cli/auth](https://mergestorm.ai/cli/auth) on any device, signs in, and approves the code. Then `mergestorm login --finish` stores the API key. The code lasts 10 minutes, and `--finish` can be run again if it is interrupted before the approval.
|
|
185
193
|
|
|
194
|
+
### `account`
|
|
195
|
+
|
|
196
|
+
```bash
|
|
197
|
+
mg account list # saved accounts, the active one marked ● (--json for machines)
|
|
198
|
+
mg account use work # make "work" the active account for every later command
|
|
199
|
+
mg account remove work # log "work" out: revoke its API key and drop it
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
Every command runs as the active account. To run one command as another saved account without switching, pass `--account <name>` anywhere on the command line (`mg --account work stack submit`, `mg whoami --account work`), or set `MERGESTORM_ACCOUNT=<name>`; the flag wins over the variable, and both win over the active account. An unknown name stops the command and lists the saved accounts. `MERGESTORM_API_KEY` still overrides every saved account. The `mergestorm-mcp` server reads the same store, so `MERGESTORM_ACCOUNT` in its environment picks the account it serves.
|
|
203
|
+
|
|
186
204
|
### `logout`
|
|
187
205
|
|
|
188
|
-
|
|
206
|
+
Logs out of the active account (or the one named with `--account`): revokes that API key on the server when it can, then drops the account from the store. The next saved account, if any, becomes active. `mg account remove <name>` does the same for a named account.
|
|
189
207
|
|
|
190
208
|
### `review [base] [head]`
|
|
191
209
|
|
|
@@ -234,7 +252,8 @@ Require a logged-in key and API support for `GET /api/v1/me` and `GET /api/v1/re
|
|
|
234
252
|
|
|
235
253
|
| Variable | Purpose | Default |
|
|
236
254
|
|----------|---------|---------|
|
|
237
|
-
| `MERGESTORM_API_KEY` | API key (overrides
|
|
255
|
+
| `MERGESTORM_API_KEY` | API key (overrides every saved account) | — |
|
|
256
|
+
| `MERGESTORM_ACCOUNT` | Saved account to run as; `--account <name>` on a command wins over it | the active account |
|
|
238
257
|
| `MERGESTORM_API_URL` | API base URL | `https://api.mergestorm.ai` |
|
|
239
258
|
| `MERGESTORM_HOME` | Directory for the saved login and local stack state, for machines whose home directory is read-only | `~/.mergestorm` |
|
|
240
259
|
| `NO_COLOR` | Disable ANSI colors | — |
|
package/dist/api.d.ts
CHANGED
|
@@ -30,14 +30,20 @@ export declare function devicePost(base: string, route: string, json: unknown, o
|
|
|
30
30
|
status: number;
|
|
31
31
|
body: any;
|
|
32
32
|
}>;
|
|
33
|
-
|
|
33
|
+
type SurgeUsage = {
|
|
34
34
|
includedMinutes: number;
|
|
35
35
|
usedMinutes: number;
|
|
36
36
|
walletMinutes: number;
|
|
37
37
|
windowStart: string;
|
|
38
38
|
pricePerMinuteUsd: number;
|
|
39
39
|
};
|
|
40
|
+
export type AccountIdentity = {
|
|
41
|
+
github_login: string | null;
|
|
42
|
+
email: string | null;
|
|
43
|
+
};
|
|
44
|
+
export declare function accountLabel(account: AccountIdentity | null | undefined): string | null;
|
|
40
45
|
export type MeResponse = {
|
|
46
|
+
account?: AccountIdentity;
|
|
41
47
|
key: {
|
|
42
48
|
prefix: string;
|
|
43
49
|
name: string;
|
|
@@ -182,10 +188,10 @@ export type SettingsResponse = import("./automation-catalog.js").BearerSettingsV
|
|
|
182
188
|
github_connected: boolean;
|
|
183
189
|
stacks_ready?: boolean;
|
|
184
190
|
};
|
|
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"];
|
|
191
|
+
export declare const REPO_SETTINGS_SWITCH_KEYS: readonly ["auto_patch_enabled", "auto_review_enabled", "auto_land_default", "merge_queue_batch_enabled", "merge_queue_overdrive", "issue_review_enabled", "issue_auto_review_enabled"];
|
|
192
|
+
export declare const REPO_SETTINGS_KEYS: readonly ["auto_patch_enabled", "auto_review_enabled", "auto_land_default", "merge_queue_batch_enabled", "merge_queue_overdrive", "issue_review_enabled", "issue_auto_review_enabled", "merge_queue_batch_size", "merge_queue_lands_alone_paths"];
|
|
187
193
|
export type RepoSettingsKey = (typeof REPO_SETTINGS_KEYS)[number];
|
|
188
|
-
|
|
194
|
+
type RepoSettingsValues = Record<(typeof REPO_SETTINGS_SWITCH_KEYS)[number], boolean | null> & {
|
|
189
195
|
merge_queue_batch_size: number | null;
|
|
190
196
|
merge_queue_lands_alone_paths: string[] | null;
|
|
191
197
|
};
|
|
@@ -219,6 +225,9 @@ export declare function patchSettings(patch: SettingsPatch, cfg?: Config, opts?:
|
|
|
219
225
|
export declare function getMe(cfg?: Config, opts?: {
|
|
220
226
|
timeoutMs?: number;
|
|
221
227
|
}): Promise<MeResponse | null>;
|
|
228
|
+
export declare function revokeOwnKey(cfg: Config, opts?: {
|
|
229
|
+
timeoutMs?: number;
|
|
230
|
+
}): Promise<boolean>;
|
|
222
231
|
export declare function listJobs(limit?: number, cfg?: Config, opts?: {
|
|
223
232
|
timeoutMs?: number;
|
|
224
233
|
}): Promise<JobListItem[]>;
|
|
@@ -257,16 +266,11 @@ export type StackPolicyPatch = {
|
|
|
257
266
|
export declare function stackPolicyBody(policy: StackPolicyPatch | undefined): StackPolicyPatch;
|
|
258
267
|
/** Adopt an open PR chain (Bearer POST /api/v1/stacks/adopt). */
|
|
259
268
|
export declare function adoptStack(owner: string, repo: string, prNumber: number, cfg?: Config, policy?: StackPolicyPatch): Promise<unknown>;
|
|
260
|
-
export declare const SURGE_REFUSAL_FALLBACK = "Mergestorm Surge is not installed or not ready on this repository. Install it from Agents.";
|
|
261
|
-
export declare function surgeRefusal(body: unknown): CommandError | null;
|
|
262
|
-
export declare const GITHUB_UNREADABLE_MESSAGE = "GitHub could not be read just now; try again.";
|
|
263
269
|
/** Mint or reuse the PR3+ park freeze (Bearer POST /api/v1/stacks/:id/ensure-upper-park). */
|
|
264
270
|
export declare function ensureUpperPark(stackId: string, cfg?: Config): Promise<{
|
|
265
271
|
freezeBranch: string;
|
|
266
272
|
created: boolean;
|
|
267
273
|
}>;
|
|
268
|
-
export declare const SURGE_INSTALL_URL = "https://github.com/apps/mergestorm-surge/installations/new";
|
|
269
|
-
export declare function surgeNotInstalledMessage(owner: string, repo: string, installUrl?: string): string;
|
|
270
274
|
export declare class LandedQueueReadError extends CommandError {
|
|
271
275
|
readonly status: number;
|
|
272
276
|
readonly retryAfterSeconds?: number | undefined;
|
|
@@ -336,7 +340,7 @@ export type StackPublicationRequest = {
|
|
|
336
340
|
};
|
|
337
341
|
autoLand?: boolean;
|
|
338
342
|
};
|
|
339
|
-
|
|
343
|
+
type StackPublicationStack = {
|
|
340
344
|
id: string;
|
|
341
345
|
autoPatchOverride: boolean | null;
|
|
342
346
|
autoLand: boolean;
|
|
@@ -383,7 +387,6 @@ export type StackPublication = {
|
|
|
383
387
|
};
|
|
384
388
|
stack: StackPublicationStack;
|
|
385
389
|
};
|
|
386
|
-
export declare const STACK_PUBLICATION_TIMEOUT_MS = 180000;
|
|
387
390
|
export declare function getStackPublication(cfg: Config, input: {
|
|
388
391
|
owner: string;
|
|
389
392
|
repo: string;
|
|
@@ -401,6 +404,69 @@ export declare function openStackIssue(input: {
|
|
|
401
404
|
url: string;
|
|
402
405
|
created: boolean;
|
|
403
406
|
}>;
|
|
407
|
+
export declare const ISSUE_REVIEW_STATES: readonly ["approved", "needs_answers", "pending", "unreviewed", "capped", "off"];
|
|
408
|
+
export type IssueReviewState = (typeof ISSUE_REVIEW_STATES)[number];
|
|
409
|
+
export type IssueReviewPoint = {
|
|
410
|
+
kind: string;
|
|
411
|
+
text: string;
|
|
412
|
+
blocking?: boolean;
|
|
413
|
+
};
|
|
414
|
+
export declare const ISSUE_REVIEW_MODES: readonly ["off", "mention", "auto"];
|
|
415
|
+
export type IssueReviewMode = (typeof ISSUE_REVIEW_MODES)[number];
|
|
416
|
+
export declare const ISSUE_REVIEW_TAG = "@mergestorm-vortex";
|
|
417
|
+
export type IssueReviewStatus = {
|
|
418
|
+
owner: string;
|
|
419
|
+
repo: string;
|
|
420
|
+
number: number;
|
|
421
|
+
enabled: boolean;
|
|
422
|
+
mode: IssueReviewMode;
|
|
423
|
+
state: IssueReviewState;
|
|
424
|
+
approvedBy: "vortex" | "human" | null;
|
|
425
|
+
round: number | null;
|
|
426
|
+
maxRounds: number;
|
|
427
|
+
verdict: "ready" | "ready_with_notes" | "needs_answers" | null;
|
|
428
|
+
summary: string | null;
|
|
429
|
+
points: IssueReviewPoint[];
|
|
430
|
+
commentUrl: string | null;
|
|
431
|
+
next: string;
|
|
432
|
+
};
|
|
433
|
+
export type IssueReviewTarget = {
|
|
434
|
+
owner: string;
|
|
435
|
+
repo: string;
|
|
436
|
+
number: number;
|
|
437
|
+
};
|
|
438
|
+
export declare const ISSUE_REVIEW_POLL_INTERVAL_MS = 10000;
|
|
439
|
+
export declare const ISSUE_REVIEW_DEFAULT_TIMEOUT_MS = 900000;
|
|
440
|
+
export declare function parseIssueReviewStatus(body: unknown, target: IssueReviewTarget): IssueReviewStatus | null;
|
|
441
|
+
export declare function getIssueReview(cfg: Config, target: IssueReviewTarget, opts?: {
|
|
442
|
+
timeoutMs?: number;
|
|
443
|
+
signal?: AbortSignal;
|
|
444
|
+
}): Promise<IssueReviewStatus | null>;
|
|
445
|
+
export declare function getIssueReviewMode(cfg: Config, target: {
|
|
446
|
+
owner: string;
|
|
447
|
+
repo: string;
|
|
448
|
+
}, opts?: {
|
|
449
|
+
timeoutMs?: number;
|
|
450
|
+
}): Promise<IssueReviewMode | null>;
|
|
451
|
+
export declare function issueReviewSettled(status: IssueReviewStatus): boolean;
|
|
452
|
+
export declare function issueReviewBlocksPr(status: IssueReviewStatus): boolean;
|
|
453
|
+
export declare class IssueReviewTimeoutError extends CommandError {
|
|
454
|
+
readonly lastStatus: IssueReviewStatus;
|
|
455
|
+
constructor(lastStatus: IssueReviewStatus);
|
|
456
|
+
}
|
|
457
|
+
export type PollIssueReviewOptions = {
|
|
458
|
+
timeoutMs?: number;
|
|
459
|
+
intervalMs?: number;
|
|
460
|
+
now?: () => number;
|
|
461
|
+
sleep?: (ms: number, signal?: AbortSignal) => Promise<void>;
|
|
462
|
+
signal?: AbortSignal;
|
|
463
|
+
onTick?: (status: IssueReviewStatus | null) => void;
|
|
464
|
+
};
|
|
465
|
+
export declare function pollIssueReview(cfg: Config, target: IssueReviewTarget, opts?: PollIssueReviewOptions): Promise<IssueReviewStatus | null>;
|
|
466
|
+
export declare function issueReviewLines(status: IssueReviewStatus): string[];
|
|
467
|
+
export declare function issueNotApprovedError(issues: IssueReviewStatus[], message?: string | null): CommandError;
|
|
468
|
+
export declare const NO_LINKED_ISSUE = "no_linked_issue";
|
|
469
|
+
export declare function issueNotApprovedRefusal(status: number, body: unknown, owner: string, repo: string): CommandError | null;
|
|
404
470
|
/**
|
|
405
471
|
* Patch any subset of one owned stack's automation policy
|
|
406
472
|
* (Bearer PATCH /api/v1/stacks/:id). `null` clears an override.
|
package/dist/api.js
CHANGED
|
@@ -157,6 +157,13 @@ export async function devicePost(base, route, json, opts = {}) {
|
|
|
157
157
|
}
|
|
158
158
|
return { status: res.status, body: parsed };
|
|
159
159
|
}
|
|
160
|
+
export function accountLabel(account) {
|
|
161
|
+
const login = account?.github_login?.trim();
|
|
162
|
+
if (login)
|
|
163
|
+
return login;
|
|
164
|
+
const email = account?.email?.trim();
|
|
165
|
+
return email || null;
|
|
166
|
+
}
|
|
160
167
|
export function isPositiveInteger(value) {
|
|
161
168
|
return typeof value === "number" && Number.isInteger(value) && value >= 1;
|
|
162
169
|
}
|
|
@@ -180,6 +187,8 @@ export const REPO_SETTINGS_SWITCH_KEYS = [
|
|
|
180
187
|
"auto_land_default",
|
|
181
188
|
"merge_queue_batch_enabled",
|
|
182
189
|
"merge_queue_overdrive",
|
|
190
|
+
"issue_review_enabled",
|
|
191
|
+
"issue_auto_review_enabled",
|
|
183
192
|
];
|
|
184
193
|
export const REPO_SETTINGS_KEYS = [...REPO_SETTINGS_SWITCH_KEYS, "merge_queue_batch_size", "merge_queue_lands_alone_paths"];
|
|
185
194
|
function repoSettingsFailure(status, body, retryAfterSeconds, action) {
|
|
@@ -293,6 +302,20 @@ export async function getMe(cfg, opts) {
|
|
|
293
302
|
return null;
|
|
294
303
|
}
|
|
295
304
|
}
|
|
305
|
+
export async function revokeOwnKey(cfg, opts) {
|
|
306
|
+
try {
|
|
307
|
+
const { status } = await apiFetch(cfg, "/api/v1/me/key", {
|
|
308
|
+
method: "DELETE",
|
|
309
|
+
timeoutMs: opts?.timeoutMs,
|
|
310
|
+
});
|
|
311
|
+
return status === 200;
|
|
312
|
+
}
|
|
313
|
+
catch (err) {
|
|
314
|
+
if (err instanceof CommandError)
|
|
315
|
+
return err.code === "auth_invalid";
|
|
316
|
+
return false;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
296
319
|
export async function listJobs(limit = 10, cfg, opts) {
|
|
297
320
|
const resolved = cfg ?? (await loadConfig());
|
|
298
321
|
const capped = Math.min(Math.max(limit, 1), 50);
|
|
@@ -432,6 +455,9 @@ export async function adoptStack(owner, repo, prNumber, cfg, policy) {
|
|
|
432
455
|
if (status === 429) {
|
|
433
456
|
throw new CommandError(rateLimitedMessage(retryAfterSeconds), REVIEW_EXIT.rate_limited, "rate_limited", { retryAfterSeconds });
|
|
434
457
|
}
|
|
458
|
+
const issueRefusal = issueNotApprovedRefusal(status, body, owner, repo);
|
|
459
|
+
if (issueRefusal)
|
|
460
|
+
throw issueRefusal;
|
|
435
461
|
if (status !== 200) {
|
|
436
462
|
const error = body && typeof body === "object" && "error" in body && typeof body.error === "string"
|
|
437
463
|
? body.error
|
|
@@ -469,13 +495,14 @@ const INFRASTRUCTURE_REFUSAL_CODES = [
|
|
|
469
495
|
"cyclone_not_connected",
|
|
470
496
|
"cyclone_not_installed",
|
|
471
497
|
"cyclone_permission_missing",
|
|
498
|
+
"surge_other_account",
|
|
472
499
|
];
|
|
473
500
|
function refusalReason(body) {
|
|
474
501
|
const reason = (body && typeof body === "object" ? body.reason : undefined);
|
|
475
502
|
return typeof reason === "string" && plainServerText(reason).trim() ? plainServerText(reason).trim() : undefined;
|
|
476
503
|
}
|
|
477
|
-
|
|
478
|
-
|
|
504
|
+
const SURGE_REFUSAL_FALLBACK = "Mergestorm Surge is not installed or not ready on this repository. Install it from Agents.";
|
|
505
|
+
function surgeRefusal(body) {
|
|
479
506
|
if (!body || typeof body !== "object")
|
|
480
507
|
return null;
|
|
481
508
|
const refusal = body;
|
|
@@ -489,7 +516,7 @@ export function surgeRefusal(body) {
|
|
|
489
516
|
const installUrl = githubAppInstallUrl(refusal.installUrl) ?? "";
|
|
490
517
|
return new CommandError(installUrl && !message.includes(installUrl) ? `${message} (${installUrl})` : message, 1, error, { reason });
|
|
491
518
|
}
|
|
492
|
-
|
|
519
|
+
const GITHUB_UNREADABLE_MESSAGE = "GitHub could not be read just now; try again.";
|
|
493
520
|
function serverMessage(message) {
|
|
494
521
|
if (typeof message !== "string")
|
|
495
522
|
return null;
|
|
@@ -518,8 +545,8 @@ export async function ensureUpperPark(stackId, cfg) {
|
|
|
518
545
|
created: body.created === true,
|
|
519
546
|
};
|
|
520
547
|
}
|
|
521
|
-
|
|
522
|
-
|
|
548
|
+
const SURGE_INSTALL_URL = "https://github.com/apps/mergestorm-surge/installations/new";
|
|
549
|
+
function surgeNotInstalledMessage(owner, repo, installUrl = SURGE_INSTALL_URL) {
|
|
523
550
|
return (`Opening PRs needs either the GitHub CLI (gh auth login) or Mergestorm Surge installed on ` +
|
|
524
551
|
`${owner}/${repo} (${installUrl}).`);
|
|
525
552
|
}
|
|
@@ -533,6 +560,9 @@ function stackPullFailure(status, body, owner, repo, failLabel) {
|
|
|
533
560
|
const surge = surgeRefusal(body);
|
|
534
561
|
if (surge)
|
|
535
562
|
return surge;
|
|
563
|
+
const issueRefusal = issueNotApprovedRefusal(status, body, owner, repo);
|
|
564
|
+
if (issueRefusal)
|
|
565
|
+
return issueRefusal;
|
|
536
566
|
const text = `${String(rejection.error ?? "")} ${String(rejection.message ?? "")}`;
|
|
537
567
|
if (rejection.error === "cyclone_not_installed" ||
|
|
538
568
|
/not installed|installation[^\n]*not found/i.test(text)) {
|
|
@@ -599,7 +629,7 @@ export async function openStackPull(cfg, input) {
|
|
|
599
629
|
created: row.created === true,
|
|
600
630
|
};
|
|
601
631
|
}
|
|
602
|
-
|
|
632
|
+
const STACK_PUBLICATION_TIMEOUT_MS = 180_000;
|
|
603
633
|
const PUBLICATION_ERROR_CODES = [
|
|
604
634
|
"head_changed",
|
|
605
635
|
"base_changed",
|
|
@@ -661,6 +691,198 @@ export async function openStackIssue(input, cfg) {
|
|
|
661
691
|
}
|
|
662
692
|
return { number: row.number, url: typeof row.url === "string" ? row.url : "", created: row.created === true };
|
|
663
693
|
}
|
|
694
|
+
export const ISSUE_REVIEW_STATES = ["approved", "needs_answers", "pending", "unreviewed", "capped", "off"];
|
|
695
|
+
export const ISSUE_REVIEW_MODES = ["off", "mention", "auto"];
|
|
696
|
+
export const ISSUE_REVIEW_TAG = "@mergestorm-vortex";
|
|
697
|
+
export const ISSUE_REVIEW_POLL_INTERVAL_MS = 10_000;
|
|
698
|
+
export const ISSUE_REVIEW_DEFAULT_TIMEOUT_MS = 900_000;
|
|
699
|
+
const ISSUE_REVIEW_MAX_POINTS = 8;
|
|
700
|
+
const ISSUE_REVIEW_DEFAULT_MAX_ROUNDS = 2;
|
|
701
|
+
function issueReviewText(value) {
|
|
702
|
+
if (typeof value !== "string")
|
|
703
|
+
return null;
|
|
704
|
+
const plain = plainServerText(value).trim();
|
|
705
|
+
return plain ? plain : null;
|
|
706
|
+
}
|
|
707
|
+
export function parseIssueReviewStatus(body, target) {
|
|
708
|
+
if (!body || typeof body !== "object")
|
|
709
|
+
return null;
|
|
710
|
+
const row = body;
|
|
711
|
+
const state = ISSUE_REVIEW_STATES.find((known) => known === row.state);
|
|
712
|
+
if (!state)
|
|
713
|
+
return null;
|
|
714
|
+
const points = Array.isArray(row.points)
|
|
715
|
+
? row.points
|
|
716
|
+
.flatMap((point) => {
|
|
717
|
+
const text = issueReviewText(point?.text);
|
|
718
|
+
if (!text)
|
|
719
|
+
return [];
|
|
720
|
+
return [{ kind: issueReviewText(point.kind) ?? "note", text, blocking: point.blocking !== false }];
|
|
721
|
+
})
|
|
722
|
+
.slice(0, ISSUE_REVIEW_MAX_POINTS)
|
|
723
|
+
: [];
|
|
724
|
+
const verdict = row.verdict === "ready" || row.verdict === "ready_with_notes" || row.verdict === "needs_answers" ? row.verdict : null;
|
|
725
|
+
return {
|
|
726
|
+
owner: typeof row.owner === "string" && row.owner ? row.owner : target.owner,
|
|
727
|
+
repo: typeof row.repo === "string" && row.repo ? row.repo : target.repo,
|
|
728
|
+
number: isPositiveInteger(row.number) ? row.number : target.number,
|
|
729
|
+
enabled: row.enabled === true,
|
|
730
|
+
mode: ISSUE_REVIEW_MODES.find((known) => known === row.mode) ?? (row.enabled === true ? "auto" : "off"),
|
|
731
|
+
state,
|
|
732
|
+
approvedBy: row.approvedBy === "vortex" || row.approvedBy === "human" ? row.approvedBy : null,
|
|
733
|
+
round: isPositiveInteger(row.round) ? row.round : null,
|
|
734
|
+
maxRounds: isPositiveInteger(row.maxRounds) ? row.maxRounds : ISSUE_REVIEW_DEFAULT_MAX_ROUNDS,
|
|
735
|
+
verdict,
|
|
736
|
+
summary: issueReviewText(row.summary),
|
|
737
|
+
points,
|
|
738
|
+
commentUrl: issueReviewText(row.commentUrl),
|
|
739
|
+
next: issueReviewText(row.next) ?? "",
|
|
740
|
+
};
|
|
741
|
+
}
|
|
742
|
+
export async function getIssueReview(cfg, target, opts = {}) {
|
|
743
|
+
const query = new URLSearchParams({ owner: target.owner, repo: target.repo }).toString();
|
|
744
|
+
const { status, body, retryAfterSeconds } = await apiFetch(cfg, `/api/v1/stacks/issues/${encodeURIComponent(String(target.number))}/review?${query}`, opts);
|
|
745
|
+
if (status === 404) {
|
|
746
|
+
const message = serverMessage(body?.message);
|
|
747
|
+
if (!message)
|
|
748
|
+
return null;
|
|
749
|
+
throw new CommandError(`${message} Connect it in Mergestorm, then try again.`, 1, "not_found");
|
|
750
|
+
}
|
|
751
|
+
if (status === 429) {
|
|
752
|
+
throw new CommandError(rateLimitedMessage(retryAfterSeconds), REVIEW_EXIT.rate_limited, "rate_limited", retryAfterSeconds !== undefined ? { retryAfterSeconds } : undefined);
|
|
753
|
+
}
|
|
754
|
+
const parsed = status === 200 ? parseIssueReviewStatus(body, target) : null;
|
|
755
|
+
if (!parsed) {
|
|
756
|
+
const message = serverMessage(body?.message);
|
|
757
|
+
throw new CommandError(message ?? `Failed to read the issue review of ${target.owner}/${target.repo}#${target.number} (HTTP ${status})`, 1, status >= 500 ? "issue_review_unavailable" : undefined);
|
|
758
|
+
}
|
|
759
|
+
return parsed;
|
|
760
|
+
}
|
|
761
|
+
export async function getIssueReviewMode(cfg, target, opts = {}) {
|
|
762
|
+
const query = new URLSearchParams({ owner: target.owner, repo: target.repo }).toString();
|
|
763
|
+
const { status, body, retryAfterSeconds } = await apiFetch(cfg, `/api/v1/stacks/issues/review-mode?${query}`, opts);
|
|
764
|
+
if (status === 404)
|
|
765
|
+
return null;
|
|
766
|
+
if (status === 429) {
|
|
767
|
+
throw new CommandError(rateLimitedMessage(retryAfterSeconds), REVIEW_EXIT.rate_limited, "rate_limited", retryAfterSeconds !== undefined ? { retryAfterSeconds } : undefined);
|
|
768
|
+
}
|
|
769
|
+
const mode = status === 200 ? ISSUE_REVIEW_MODES.find((known) => known === body?.mode) : undefined;
|
|
770
|
+
if (!mode) {
|
|
771
|
+
const message = serverMessage(body?.message);
|
|
772
|
+
throw new CommandError(message ?? `Failed to read the issue review mode of ${target.owner}/${target.repo} (HTTP ${status})`);
|
|
773
|
+
}
|
|
774
|
+
return mode;
|
|
775
|
+
}
|
|
776
|
+
export function issueReviewSettled(status) {
|
|
777
|
+
if (status.state === "pending")
|
|
778
|
+
return false;
|
|
779
|
+
return !(status.state === "unreviewed" && status.mode === "auto");
|
|
780
|
+
}
|
|
781
|
+
export function issueReviewBlocksPr(status) {
|
|
782
|
+
return status.mode === "auto" && status.state !== "approved" && status.state !== "off";
|
|
783
|
+
}
|
|
784
|
+
export class IssueReviewTimeoutError extends CommandError {
|
|
785
|
+
lastStatus;
|
|
786
|
+
constructor(lastStatus) {
|
|
787
|
+
super(`Timed out waiting for the Vortex review of ${lastStatus.owner}/${lastStatus.repo}#${lastStatus.number}. Run \`mergestorm issue wait ${lastStatus.number}\` again.`, REVIEW_EXIT.timeout, "review_timeout");
|
|
788
|
+
this.name = "IssueReviewTimeoutError";
|
|
789
|
+
this.lastStatus = lastStatus;
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
function abortableSleep(ms, signal) {
|
|
793
|
+
return new Promise((resolve, reject) => {
|
|
794
|
+
const abortError = () => Object.assign(new Error("Aborted"), { name: "AbortError" });
|
|
795
|
+
if (signal?.aborted) {
|
|
796
|
+
reject(abortError());
|
|
797
|
+
return;
|
|
798
|
+
}
|
|
799
|
+
const onAbort = () => {
|
|
800
|
+
clearTimeout(timer);
|
|
801
|
+
reject(abortError());
|
|
802
|
+
};
|
|
803
|
+
const timer = setTimeout(() => {
|
|
804
|
+
signal?.removeEventListener("abort", onAbort);
|
|
805
|
+
resolve();
|
|
806
|
+
}, ms);
|
|
807
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
808
|
+
});
|
|
809
|
+
}
|
|
810
|
+
function isTransientIssueReviewError(err) {
|
|
811
|
+
if (!(err instanceof Error) || err.name === "AbortError")
|
|
812
|
+
return false;
|
|
813
|
+
if (err instanceof CommandError) {
|
|
814
|
+
return err.code === "api_timeout" || err.code === "issue_review_unavailable" || err.code === "rate_limited";
|
|
815
|
+
}
|
|
816
|
+
return /fetch failed|ECONNRESET|ETIMEDOUT|ENOTFOUND|EAI_AGAIN|socket hang up|network/i.test(err.message);
|
|
817
|
+
}
|
|
818
|
+
export async function pollIssueReview(cfg, target, opts = {}) {
|
|
819
|
+
const timeoutMs = opts.timeoutMs ?? ISSUE_REVIEW_DEFAULT_TIMEOUT_MS;
|
|
820
|
+
const intervalMs = opts.intervalMs ?? ISSUE_REVIEW_POLL_INTERVAL_MS;
|
|
821
|
+
const now = opts.now ?? Date.now;
|
|
822
|
+
const wait = opts.sleep ?? abortableSleep;
|
|
823
|
+
const deadline = now() + timeoutMs;
|
|
824
|
+
let last = null;
|
|
825
|
+
let failure = null;
|
|
826
|
+
for (;;) {
|
|
827
|
+
try {
|
|
828
|
+
const status = await getIssueReview(cfg, target, { signal: opts.signal });
|
|
829
|
+
if (!status || issueReviewSettled(status))
|
|
830
|
+
return status;
|
|
831
|
+
last = status;
|
|
832
|
+
opts.onTick?.(status);
|
|
833
|
+
}
|
|
834
|
+
catch (err) {
|
|
835
|
+
if (!isTransientIssueReviewError(err) || now() >= deadline)
|
|
836
|
+
throw err;
|
|
837
|
+
failure = err;
|
|
838
|
+
opts.onTick?.(null);
|
|
839
|
+
}
|
|
840
|
+
const remaining = deadline - now();
|
|
841
|
+
if (remaining <= 0) {
|
|
842
|
+
if (last)
|
|
843
|
+
throw new IssueReviewTimeoutError(last);
|
|
844
|
+
throw failure;
|
|
845
|
+
}
|
|
846
|
+
await wait(Math.min(intervalMs, remaining), opts.signal);
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
export function issueReviewLines(status) {
|
|
850
|
+
const round = status.round ? ` (round ${status.round} of ${status.maxRounds})` : "";
|
|
851
|
+
const lines = [`${status.owner}/${status.repo}#${status.number}: ${status.state}${round}`];
|
|
852
|
+
if (status.summary)
|
|
853
|
+
lines.push(` ${status.summary}`);
|
|
854
|
+
status.points.forEach((point, i) => lines.push(` ${i + 1}. [${point.blocking === false ? "note: " : ""}${point.kind}] ${point.text}`));
|
|
855
|
+
if (status.commentUrl)
|
|
856
|
+
lines.push(` Review: ${status.commentUrl}`);
|
|
857
|
+
if (status.next)
|
|
858
|
+
lines.push(` Next: ${status.next}`);
|
|
859
|
+
return lines;
|
|
860
|
+
}
|
|
861
|
+
export function issueNotApprovedError(issues, message) {
|
|
862
|
+
const lines = [message?.trim() || "Vortex has not approved the linked issue yet; refusing to open the pull request."];
|
|
863
|
+
for (const issue of issues) {
|
|
864
|
+
lines.push(...issueReviewLines(issue));
|
|
865
|
+
const reply = issue.state === "needs_answers" ? `Reply once in a comment on #${issue.number}, accepting each blocking point's suggested fix or saying why not; then run` : "Run";
|
|
866
|
+
lines.push(` ${reply} \`mergestorm issue wait ${issue.number}\`.`);
|
|
867
|
+
}
|
|
868
|
+
return new CommandError(lines.join("\n"), REVIEW_EXIT.failed, "issue_not_approved", issues.length === 0 ? { reason: NO_LINKED_ISSUE } : undefined);
|
|
869
|
+
}
|
|
870
|
+
export const NO_LINKED_ISSUE = "no_linked_issue";
|
|
871
|
+
export function issueNotApprovedRefusal(status, body, owner, repo) {
|
|
872
|
+
if (status !== 409 || !body || typeof body !== "object")
|
|
873
|
+
return null;
|
|
874
|
+
const refusal = body;
|
|
875
|
+
if (refusal.error !== "issue_not_approved")
|
|
876
|
+
return null;
|
|
877
|
+
const issues = Array.isArray(refusal.issues)
|
|
878
|
+
? refusal.issues.flatMap((issue) => {
|
|
879
|
+
const n = issue?.number;
|
|
880
|
+
const parsed = parseIssueReviewStatus(issue, { owner, repo, number: isPositiveInteger(n) ? n : 0 });
|
|
881
|
+
return parsed ? [parsed] : [];
|
|
882
|
+
})
|
|
883
|
+
: [];
|
|
884
|
+
return issueNotApprovedError(issues, serverMessage(refusal.message));
|
|
885
|
+
}
|
|
664
886
|
async function stackMutation(stackId, pathSuffix, method, json, failLabel, cfg) {
|
|
665
887
|
const resolved = cfg ?? (await loadConfig());
|
|
666
888
|
const { status, body } = await apiFetch(resolved, `/api/v1/stacks/${encodeURIComponent(stackId)}${pathSuffix}`, method === "PATCH"
|
|
@@ -42,6 +42,14 @@ export declare const BEARER_SETTINGS: readonly [{
|
|
|
42
42
|
readonly key: "vortex_seam_specialist_enabled";
|
|
43
43
|
readonly flag: "--vortex-seam";
|
|
44
44
|
readonly label: "Vortex seam specialist";
|
|
45
|
+
}, {
|
|
46
|
+
readonly key: "issue_review_enabled";
|
|
47
|
+
readonly flag: "--issue-review";
|
|
48
|
+
readonly label: "Vortex issue reviews";
|
|
49
|
+
}, {
|
|
50
|
+
readonly key: "issue_auto_review_enabled";
|
|
51
|
+
readonly flag: "--issue-auto-review";
|
|
52
|
+
readonly label: "Vortex issue auto review";
|
|
45
53
|
}, {
|
|
46
54
|
readonly key: "auto_land_default";
|
|
47
55
|
readonly flag: "--auto-land";
|
|
@@ -112,4 +120,4 @@ export type BearerSettingsValues = {
|
|
|
112
120
|
values: readonly (infer Value)[];
|
|
113
121
|
} ? Value : boolean;
|
|
114
122
|
};
|
|
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")[];
|
|
123
|
+
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" | "issue_review_enabled" | "issue_auto_review_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")[];
|
|
@@ -10,6 +10,8 @@ export const BEARER_SETTINGS = [
|
|
|
10
10
|
{ key: "vortex_auto_overflow_enabled", flag: "--vortex-auto-overflow", label: "Vortex auto overflow" },
|
|
11
11
|
{ key: "vortex_skip_all_clear_comments", flag: "--vortex-skip-all-clear", label: "Vortex skip All-clear comments" },
|
|
12
12
|
{ key: "vortex_seam_specialist_enabled", flag: "--vortex-seam", label: "Vortex seam specialist" },
|
|
13
|
+
{ key: "issue_review_enabled", flag: "--issue-review", label: "Vortex issue reviews" },
|
|
14
|
+
{ key: "issue_auto_review_enabled", flag: "--issue-auto-review", label: "Vortex issue auto review" },
|
|
13
15
|
{ key: "auto_land_default", flag: "--auto-land", label: "Auto land (new stacks)" },
|
|
14
16
|
{ key: "auto_land_settle_seconds", flag: "--auto-land-settle", label: "Auto land settle (seconds)", kind: "seconds", min: 15, max: 300 },
|
|
15
17
|
{ key: "merge_queue_batch_enabled", flag: "--merge-queue-batch", label: "Merge queue batching" },
|
package/dist/cli.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { stdin as input, stdout as output } from "node:process";
|
|
3
|
+
import { splitAccountFlag, withAccount } from "./config.js";
|
|
3
4
|
import { CommandError } from "./errors.js";
|
|
4
5
|
import { dispatchCommand, formatUsageBody, } from "./commands/registry.js";
|
|
5
6
|
import { isSupportedNode, unsupportedNodeMessage, } from "./node-version.js";
|
|
@@ -17,37 +18,38 @@ function usage() {
|
|
|
17
18
|
${formatUsageBody()}
|
|
18
19
|
`);
|
|
19
20
|
}
|
|
20
|
-
async function runCommand(cmd, args) {
|
|
21
|
+
async function runCommand(cmd, args, account) {
|
|
21
22
|
if (cmd === "help" || cmd === "?") {
|
|
22
23
|
usage();
|
|
23
24
|
return;
|
|
24
25
|
}
|
|
25
|
-
const ok = await dispatchCommand(cmd, args, { mode: "oneshot" });
|
|
26
|
+
const ok = await dispatchCommand(cmd, args, { mode: "oneshot", account });
|
|
26
27
|
if (!ok) {
|
|
27
28
|
usage();
|
|
28
29
|
throw new CommandError(`unknown command: ${cmd}`);
|
|
29
30
|
}
|
|
30
31
|
}
|
|
31
32
|
async function main() {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
33
|
+
try {
|
|
34
|
+
const { account, args: argv } = splitAccountFlag(process.argv.slice(2));
|
|
35
|
+
const [cmd, ...args] = argv;
|
|
36
|
+
if (!cmd) {
|
|
37
|
+
if (input.isTTY && output.isTTY) {
|
|
38
|
+
await withAccount(account, () => runShell());
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
usage();
|
|
36
42
|
return;
|
|
37
43
|
}
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
return;
|
|
48
|
-
}
|
|
49
|
-
try {
|
|
50
|
-
await runCommand(cmd, args);
|
|
44
|
+
if (cmd === "-h" || cmd === "--help" || cmd === "help" || cmd === "?") {
|
|
45
|
+
usage();
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
if (cmd === "-v" || cmd === "--version" || cmd === "version") {
|
|
49
|
+
console.log(cliVersion());
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
await runCommand(cmd, args, account);
|
|
51
53
|
}
|
|
52
54
|
catch (err) {
|
|
53
55
|
if (err instanceof CommandError) {
|