mergestorm 0.3.61 → 0.3.63
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 +13 -8
- package/dist/api.d.ts +16 -8
- package/dist/api.js +16 -23
- package/dist/client/index.d.ts +1 -1
- package/dist/client/index.js +1 -1
- package/dist/commands/dismiss.js +4 -1
- package/dist/commands/login.d.ts +4 -1
- package/dist/commands/login.js +5 -3
- package/dist/commands/registry.js +1 -1
- package/dist/commands/review-client.d.ts +1 -2
- package/dist/commands/review-client.js +24 -3
- package/dist/commands/stack.d.ts +9 -1
- package/dist/commands/stack.js +99 -42
- package/dist/config.d.ts +1 -0
- package/dist/config.js +4 -1
- package/dist/errors.d.ts +1 -1
- package/dist/git-stack.d.ts +2 -0
- package/dist/git-stack.js +15 -0
- package/dist/repo-identity.d.ts +0 -3
- package/dist/repo-identity.js +0 -23
- package/dist/server-publish.d.ts +14 -1
- package/dist/server-publish.js +37 -9
- package/dist/stack-blockers.d.ts +4 -17
- package/dist/stack-blockers.js +93 -26
- package/dist/stack-dto.d.ts +2 -0
- package/dist/stack-meta.d.ts +0 -2
- package/dist/stack-meta.js +3 -81
- package/dist/stack-watch-loop.js +4 -1
- package/dist/stack-watch-obligation.d.ts +4 -1
- package/dist/stack-watch-obligation.js +7 -4
- package/dist/stack-watch.js +2 -1
- package/dist/ui/banner.js +6 -0
- package/package.json +9 -2
- package/skill/SKILL.md +1 -1
- package/skill/mergestorm-bounce-watch/SKILL.md +11 -5
- package/skill/mergestorm-pr-loop/SKILL.md +7 -3
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.63 │
|
|
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.63: Stack create starts a new stack unless --extend│
|
|
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
|
|
@@ -111,7 +111,7 @@ mergestorm dismiss <owner/repo>#<n> --head <sha> --review <id> --finding <id> --
|
|
|
111
111
|
|
|
112
112
|
- It calls `stack wait` in slices of at most 300s and carries the cursor (`enrolled_head_sha`, `after_finished_at`, `bounce_id`) from one slice to the next.
|
|
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
|
-
- On attention it prints `MS-WATCH ATTENTION pr=<n> head=<sha> blocker="<blocker>" repair=<kind>` and exits 3.
|
|
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
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.
|
|
117
117
|
- `--head <sha>` enrolls the SHA you just pushed. After fixing a blocker, restart the watcher with it.
|
|
@@ -138,9 +138,14 @@ Use `mergestorm dismiss` when a Vortex finding is wrong and you have checked it
|
|
|
138
138
|
|
|
139
139
|
`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
140
|
|
|
141
|
-
`stack create`
|
|
141
|
+
`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
142
|
|
|
143
|
-
|
|
143
|
+
Both paths are one command:
|
|
144
|
+
|
|
145
|
+
- New stack from trunk: `mg stack create <name>` (from any branch), then `mg stack submit`.
|
|
146
|
+
- 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.
|
|
147
|
+
|
|
148
|
+
`--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
149
|
|
|
145
150
|
`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
151
|
|
|
@@ -148,7 +153,7 @@ If the parent branch is already a layer of a **registered** (submitted) stack, `
|
|
|
148
153
|
|
|
149
154
|
`mg stack submit --server-side` and `mg stack publish` are for a machine that can reach Mergestorm but cannot authenticate to GitHub, such as a cloud coding sandbox. They never run `gh`, `git push` or `git fetch`. `mg` reads your commits from the local repository and sends them to Mergestorm with your API key; Mergestorm writes those exact commits and the branch on GitHub as the Surge App, opens a ready pull request, and registers the stack. Surge must be installed on the repository and your linked GitHub account needs write access to it.
|
|
150
155
|
|
|
151
|
-
|
|
156
|
+
The stack is registered with Auto land on for a new stack (`--auto-land off` to keep it off). Auto patch and Auto review follow the stack, repository and account settings like any other stack; `--auto-patch` and `--auto-review` pin them on the stack after it is registered. Send later commits with `mg stack publish`.
|
|
152
157
|
|
|
153
158
|
```bash
|
|
154
159
|
mg issue create --title "The thing is broken" --body-file issue.md --label bug --json # no gh
|
|
@@ -167,7 +172,7 @@ mg stack publish --status # absent | pushed | opened | registered
|
|
|
167
172
|
- **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`.
|
|
168
173
|
- **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.
|
|
169
174
|
|
|
170
|
-
|
|
175
|
+
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.
|
|
171
176
|
|
|
172
177
|
### `login`
|
|
173
178
|
|
package/dist/api.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { type Config } from "./config.js";
|
|
2
|
-
import { BEARER_SETTINGS_KEYS as SETTINGS_WRITABLE_KEYS
|
|
2
|
+
import { BEARER_SETTINGS_KEYS as SETTINGS_WRITABLE_KEYS } from "./automation-catalog.js";
|
|
3
3
|
import { CommandError } from "./errors.js";
|
|
4
4
|
import type { MergeQueueEntryDto, StackDto } from "./stack-dto.js";
|
|
5
|
-
export type { MergeQueueBounceDetail, MergeQueueBounceKind, MergeQueueEnqueuedBy, MergeQueueEnqueuedVia, MergeQueueEntryDto, MergeQueueEntryState,
|
|
5
|
+
export type { MergeQueueBounceDetail, MergeQueueBounceKind, MergeQueueEnqueuedBy, MergeQueueEnqueuedVia, MergeQueueEntryDto, MergeQueueEntryState, StackDto, StackLayerDto, } from "./stack-dto.js";
|
|
6
6
|
/** Default per-request timeout so a hung API cannot stall the shell/CI forever. */
|
|
7
7
|
export declare const DEFAULT_API_TIMEOUT_MS = 30000;
|
|
8
8
|
/** Message prefix of the CommandError thrown when a request exceeds its timeout. */
|
|
@@ -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
|
|
@@ -167,7 +175,6 @@ export declare function applyPrReviewQuery(query: URLSearchParams, opts: PrRevie
|
|
|
167
175
|
* The connected flags are read-only there and deliberately absent here.
|
|
168
176
|
*/
|
|
169
177
|
export { SETTINGS_WRITABLE_KEYS };
|
|
170
|
-
export type { SettingsWritableKey };
|
|
171
178
|
export type SettingsPatch = Partial<import("./automation-catalog.js").BearerSettingsValues>;
|
|
172
179
|
/** GET and PATCH `/api/v1/settings` both answer with this shape. */
|
|
173
180
|
export type SettingsResponse = import("./automation-catalog.js").BearerSettingsValues & {
|
|
@@ -389,8 +396,6 @@ export declare function openStackIssue(input: {
|
|
|
389
396
|
url: string;
|
|
390
397
|
created: boolean;
|
|
391
398
|
}>;
|
|
392
|
-
/** Arm or disarm Auto land on one owned stack. */
|
|
393
|
-
export declare function setStackAutoLand(stackId: string, enabled: boolean, cfg?: Config): Promise<unknown>;
|
|
394
399
|
/**
|
|
395
400
|
* Patch any subset of one owned stack's automation policy
|
|
396
401
|
* (Bearer PATCH /api/v1/stacks/:id). `null` clears an override.
|
|
@@ -399,9 +404,7 @@ export declare function setStackPolicy(stackId: string, policy: StackPolicyPatch
|
|
|
399
404
|
/** Restack descendants (Bearer POST /api/v1/stacks/:id/restack). */
|
|
400
405
|
export declare function restackStack(stackId: string, cfg?: Config): Promise<unknown>;
|
|
401
406
|
/** Land the bottom open layer (Bearer POST /api/v1/stacks/:id/land-next). */
|
|
402
|
-
export declare function landNextStack(stackId: string, cfg?: Config
|
|
403
|
-
prNumber?: number;
|
|
404
|
-
}): Promise<unknown>;
|
|
407
|
+
export declare function landNextStack(stackId: string, cfg?: Config): Promise<unknown>;
|
|
405
408
|
/** List the authenticated user's live merge-queue entries. */
|
|
406
409
|
export declare function listMergeQueueEntries(cfg?: Config, opts?: {
|
|
407
410
|
stackId?: string;
|
|
@@ -463,7 +466,12 @@ export type PrFindingDismissResult = {
|
|
|
463
466
|
other_gates: "unchanged";
|
|
464
467
|
};
|
|
465
468
|
stack_id: string | null;
|
|
469
|
+
threads?: Array<{
|
|
470
|
+
finding_id: string;
|
|
471
|
+
state: "resolved" | "already_resolved" | "not_found" | "failed";
|
|
472
|
+
}>;
|
|
466
473
|
};
|
|
474
|
+
export declare function dismissThreadsLine(result: Pick<PrFindingDismissResult, "threads">): string | null;
|
|
467
475
|
export type PrFindingDismissOutcome = {
|
|
468
476
|
ok: true;
|
|
469
477
|
result: PrFindingDismissResult;
|
package/dist/api.js
CHANGED
|
@@ -269,8 +269,6 @@ export async function getMe(cfg, opts) {
|
|
|
269
269
|
const { status, body } = await apiFetch(resolved, "/api/v1/me", {
|
|
270
270
|
timeoutMs: opts?.timeoutMs,
|
|
271
271
|
});
|
|
272
|
-
if (status === 404)
|
|
273
|
-
return null;
|
|
274
272
|
if (status !== 200)
|
|
275
273
|
return null;
|
|
276
274
|
return body;
|
|
@@ -432,18 +430,7 @@ export async function adoptStack(owner, repo, prNumber, cfg, policy) {
|
|
|
432
430
|
const message = body && typeof body === "object" && "message" in body && typeof body.message === "string"
|
|
433
431
|
? body.message
|
|
434
432
|
: undefined;
|
|
435
|
-
const code = error === "
|
|
436
|
-
error === "api_timeout" ||
|
|
437
|
-
error === "not_a_repo" ||
|
|
438
|
-
error === "not_found" ||
|
|
439
|
-
error === "missing_api_key" ||
|
|
440
|
-
error === "registered_parent" ||
|
|
441
|
-
error === "usage" ||
|
|
442
|
-
error === "review_quota" ||
|
|
443
|
-
error === "review_failed" ||
|
|
444
|
-
error === "review_timeout" ||
|
|
445
|
-
error === "rate_limited" ||
|
|
446
|
-
error === "cyclone_not_installed" ||
|
|
433
|
+
const code = error === "cyclone_not_installed" ||
|
|
447
434
|
error === "pr_merged" ||
|
|
448
435
|
error === "pr_closed" ||
|
|
449
436
|
error === "not_stacked" ||
|
|
@@ -689,10 +676,6 @@ async function stackMutation(stackId, pathSuffix, method, json, failLabel, cfg)
|
|
|
689
676
|
}
|
|
690
677
|
return body;
|
|
691
678
|
}
|
|
692
|
-
/** Arm or disarm Auto land on one owned stack. */
|
|
693
|
-
export async function setStackAutoLand(stackId, enabled, cfg) {
|
|
694
|
-
return setStackPolicy(stackId, { autoEnqueueWhenReady: enabled }, cfg);
|
|
695
|
-
}
|
|
696
679
|
/**
|
|
697
680
|
* Patch any subset of one owned stack's automation policy
|
|
698
681
|
* (Bearer PATCH /api/v1/stacks/:id). `null` clears an override.
|
|
@@ -709,11 +692,8 @@ export async function restackStack(stackId, cfg) {
|
|
|
709
692
|
return stackMutation(stackId, "/restack", "POST", {}, "Failed to restack", cfg);
|
|
710
693
|
}
|
|
711
694
|
/** Land the bottom open layer (Bearer POST /api/v1/stacks/:id/land-next). */
|
|
712
|
-
export async function landNextStack(stackId, cfg
|
|
713
|
-
|
|
714
|
-
? { prNumber: opts.prNumber }
|
|
715
|
-
: {};
|
|
716
|
-
return stackMutation(stackId, "/land-next", "POST", json, "Failed to land next", cfg);
|
|
695
|
+
export async function landNextStack(stackId, cfg) {
|
|
696
|
+
return stackMutation(stackId, "/land-next", "POST", {}, "Failed to land next", cfg);
|
|
717
697
|
}
|
|
718
698
|
/** List the authenticated user's live merge-queue entries. */
|
|
719
699
|
export async function listMergeQueueEntries(cfg, opts) {
|
|
@@ -793,6 +773,19 @@ export async function listThreads(limit = 20, cfg) {
|
|
|
793
773
|
const items = body.items;
|
|
794
774
|
return Array.isArray(items) ? items : [];
|
|
795
775
|
}
|
|
776
|
+
export function dismissThreadsLine(result) {
|
|
777
|
+
const threads = result.threads ?? [];
|
|
778
|
+
if (threads.length === 0)
|
|
779
|
+
return null;
|
|
780
|
+
const closed = threads.filter((thread) => thread.state === "resolved" || thread.state === "already_resolved").length;
|
|
781
|
+
const failed = threads.filter((thread) => thread.state === "failed").map((thread) => thread.finding_id);
|
|
782
|
+
const missing = threads.filter((thread) => thread.state === "not_found").map((thread) => thread.finding_id);
|
|
783
|
+
return [
|
|
784
|
+
`GitHub threads: ${closed} of ${threads.length} resolved.`,
|
|
785
|
+
...(failed.length ? [`Could not resolve ${failed.join(", ")}; run the same dismissal again to retry.`] : []),
|
|
786
|
+
...(missing.length ? [`No thread found for ${missing.join(", ")}.`] : []),
|
|
787
|
+
].join(" ");
|
|
788
|
+
}
|
|
796
789
|
export async function dismissPrFindings(input, cfg, opts) {
|
|
797
790
|
const resolved = cfg ?? (await loadConfig());
|
|
798
791
|
const json = {
|
package/dist/client/index.d.ts
CHANGED
|
@@ -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,
|
|
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";
|
package/dist/client/index.js
CHANGED
|
@@ -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,
|
|
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";
|
package/dist/commands/dismiss.js
CHANGED
|
@@ -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) {
|
package/dist/commands/login.d.ts
CHANGED
|
@@ -6,4 +6,7 @@ import { type MeResponse } from "../api.js";
|
|
|
6
6
|
*/
|
|
7
7
|
export declare function validateApiKey(cfg: Config): Promise<MeResponse>;
|
|
8
8
|
export declare function pendingLoginPath(): string;
|
|
9
|
-
export
|
|
9
|
+
export interface LoginDeps {
|
|
10
|
+
openBrowser: (url: string) => void;
|
|
11
|
+
}
|
|
12
|
+
export declare function cmdLogin(args: string[], deps?: LoginDeps): Promise<void>;
|
package/dist/commands/login.js
CHANGED
|
@@ -200,7 +200,7 @@ async function loginFinish() {
|
|
|
200
200
|
console.log(`Waiting for approval of code ${pending.userCode} at ${pending.openUrl}`);
|
|
201
201
|
await pollDeviceLogin(pending, "mergestorm login --finish", "Run `mergestorm login --start`, then `mergestorm login --finish`.");
|
|
202
202
|
}
|
|
203
|
-
export async function cmdLogin(args) {
|
|
203
|
+
export async function cmdLogin(args, deps = { openBrowser }) {
|
|
204
204
|
if (args.includes("--key")) {
|
|
205
205
|
await loginWithKey();
|
|
206
206
|
return;
|
|
@@ -219,8 +219,10 @@ export async function cmdLogin(args) {
|
|
|
219
219
|
console.log("To sign in, open this URL and confirm the code:\n");
|
|
220
220
|
console.log(` ${ansi.brightGreen(pending.openUrl)}`);
|
|
221
221
|
console.log(`\n Code: ${ansi.bold(pending.userCode)}\n`);
|
|
222
|
-
|
|
223
|
-
|
|
222
|
+
if (process.stdout.isTTY) {
|
|
223
|
+
deps.openBrowser(pending.openUrl);
|
|
224
|
+
}
|
|
225
|
+
else {
|
|
224
226
|
console.log("No terminal here. If this command may be cut off before the approval, use `mergestorm login --start`, then `mergestorm login --finish`.");
|
|
225
227
|
}
|
|
226
228
|
console.log("Waiting for approval…");
|
|
@@ -264,7 +264,7 @@ export const COMMAND_REGISTRY = [
|
|
|
264
264
|
"mergestorm stack list [--json] List registered stacks",
|
|
265
265
|
"mergestorm stack status <stack-id> [--json] Enriched stack status",
|
|
266
266
|
"mergestorm stack wait <stack-id> [--json] [--timeout <s>] Wait for stack attention (default 45s, max 300s; 0 = one snapshot, no wait). --json: one document on stdout; timeout notice on stderr, exit 5",
|
|
267
|
-
"mergestorm stack watch <stack-id> [--until attention|landed] [--ignore <blocker-substring>] [--max <minutes>] [--head <sha>] [--json] Background watcher: silent while waiting; MS-WATCH ATTENTION exits 3, MS-WATCH LANDED exits 0",
|
|
267
|
+
"mergestorm stack watch <stack-id> [--until attention|landed] [--ignore <blocker-substring>] [--max <minutes>] [--head <sha>] [--bounce-id <id>] [--after-finished-at <iso>] [--json] Background watcher: silent while waiting; MS-WATCH ATTENTION exits 3, MS-WATCH LANDED exits 0",
|
|
268
268
|
"mergestorm stack set <stack-id> [--auto-land on|off] [--auto-review on|off|default] [--auto-patch on|off|default] [--auto-resolve-conflicts on|off|default] [--auto-fix-ci on|off|default] [--json] Per-stack policy",
|
|
269
269
|
"mergestorm stack adopt <owner/repo>#<pr> [--auto-land on|off] [--auto-review on|off] [--auto-patch on|off] [--auto-resolve-conflicts on|off] [--auto-fix-ci on|off] Import an open PR chain",
|
|
270
270
|
"mergestorm stack restack <stack-id> Restack stack descendants",
|
|
@@ -10,8 +10,6 @@ export declare const REVIEW_POLL_STRETCH_AFTER_MS = 60000;
|
|
|
10
10
|
export declare const REVIEW_POLL_STRETCH_SPAN_MS = 60000;
|
|
11
11
|
/** Same thread slug contract as POST /api/v1/reviews. */
|
|
12
12
|
export declare const REVIEW_THREAD_SLUG_RE: RegExp;
|
|
13
|
-
/** Same product limit as `api/src/v1/reviews.ts` `MAX_DIFF_BYTES`. */
|
|
14
|
-
export declare const MAX_REVIEW_DIFF_BYTES = 1000000;
|
|
15
13
|
export declare const MAX_CONTEXT_TEXT_CHARS = 60000;
|
|
16
14
|
export declare const MAX_CONTEXT_FILES = 10;
|
|
17
15
|
export declare const MAX_CONTEXT_FILE_CHARS = 16000;
|
|
@@ -59,6 +57,7 @@ export type LoadReviewContextOptions = {
|
|
|
59
57
|
};
|
|
60
58
|
/** Load --context / --context-file and enforce the API caps client-side. */
|
|
61
59
|
export declare function loadReviewContext(opts: LoadReviewContextOptions): Promise<ReviewContextPayload>;
|
|
60
|
+
export declare function reviewIdempotencyKey(input: ReviewInput, opts?: SubmitReviewOptions, afterJobId?: string | null): string;
|
|
62
61
|
export declare function submitReview(cfg: Config, input: ReviewInput, opts?: SubmitReviewOptions): Promise<{
|
|
63
62
|
status: number;
|
|
64
63
|
row: ReviewJobRow;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
1
2
|
import { readFile, realpath } from "node:fs/promises";
|
|
2
3
|
import { join, relative, resolve, sep } from "node:path";
|
|
3
4
|
import { apiFetch, applyPrReviewQuery, isPositiveInteger, } from "../api.js";
|
|
@@ -12,8 +13,6 @@ export const REVIEW_POLL_STRETCH_AFTER_MS = 60_000;
|
|
|
12
13
|
export const REVIEW_POLL_STRETCH_SPAN_MS = 60_000;
|
|
13
14
|
/** Same thread slug contract as POST /api/v1/reviews. */
|
|
14
15
|
export const REVIEW_THREAD_SLUG_RE = /^[a-zA-Z0-9][a-zA-Z0-9._/-]{0,119}$/;
|
|
15
|
-
/** Same product limit as `api/src/v1/reviews.ts` `MAX_DIFF_BYTES`. */
|
|
16
|
-
export const MAX_REVIEW_DIFF_BYTES = 1_000_000;
|
|
17
16
|
export const MAX_CONTEXT_TEXT_CHARS = 60_000;
|
|
18
17
|
export const MAX_CONTEXT_FILES = 10;
|
|
19
18
|
export const MAX_CONTEXT_FILE_CHARS = 16_000;
|
|
@@ -128,7 +127,29 @@ export async function loadReviewContext(opts) {
|
|
|
128
127
|
...(contextFiles.length > 0 ? { contextFiles } : {}),
|
|
129
128
|
};
|
|
130
129
|
}
|
|
130
|
+
const REPLAYED_JOB_RESUBMIT_STATUSES = ["failed", "quota_exceeded"];
|
|
131
|
+
const MAX_REPLAYED_JOB_RESUBMITS = 5;
|
|
132
|
+
export function reviewIdempotencyKey(input, opts = {}, afterJobId = null) {
|
|
133
|
+
const request = [
|
|
134
|
+
input.thread, input.branch, input.repo ?? null, input.baseLabel, input.headLabel, input.diff, input.files,
|
|
135
|
+
opts.routerMode ?? null, [...new Set(opts.specialists ?? [])].sort(), opts.context ?? null, opts.contextFiles ?? [], opts.webhookUrl ?? null,
|
|
136
|
+
afterJobId,
|
|
137
|
+
];
|
|
138
|
+
return `mg-auto-${createHash("sha256").update(JSON.stringify(request)).digest("hex")}`;
|
|
139
|
+
}
|
|
131
140
|
export async function submitReview(cfg, input, opts = {}) {
|
|
141
|
+
const explicitKey = opts.idempotencyKey?.trim() || undefined;
|
|
142
|
+
let idempotencyKey = explicitKey ?? reviewIdempotencyKey(input, opts);
|
|
143
|
+
for (let resubmits = 0;; resubmits++) {
|
|
144
|
+
const submitted = await postReview(cfg, input, opts, idempotencyKey);
|
|
145
|
+
const replayedJobId = submitted.row.job_id ?? submitted.row.id ?? null;
|
|
146
|
+
if (explicitKey || submitted.status !== 200 || !replayedJobId || resubmits >= MAX_REPLAYED_JOB_RESUBMITS ||
|
|
147
|
+
!REPLAYED_JOB_RESUBMIT_STATUSES.includes(submitted.row.status ?? ""))
|
|
148
|
+
return submitted;
|
|
149
|
+
idempotencyKey = reviewIdempotencyKey(input, opts, replayedJobId);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
async function postReview(cfg, input, opts, idempotencyKey) {
|
|
132
153
|
const { status, body, retryAfterSeconds } = await apiFetch(cfg, "/api/v1/reviews", {
|
|
133
154
|
method: "POST",
|
|
134
155
|
json: {
|
|
@@ -143,7 +164,7 @@ export async function submitReview(cfg, input, opts = {}) {
|
|
|
143
164
|
...(opts.specialists?.length ? { specialists: opts.specialists } : {}),
|
|
144
165
|
...(opts.context ? { context: opts.context } : {}),
|
|
145
166
|
...(opts.contextFiles?.length ? { context_files: opts.contextFiles } : {}),
|
|
146
|
-
|
|
167
|
+
idempotency_key: idempotencyKey,
|
|
147
168
|
...(opts.webhookUrl ? { webhook_url: opts.webhookUrl } : {}),
|
|
148
169
|
},
|
|
149
170
|
signal: opts.signal,
|
package/dist/commands/stack.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ import { adoptStack, ensureUpperPark, findStackPull, getEnrichedStack, getStackP
|
|
|
4
4
|
import { loadConfig } from "../config.js";
|
|
5
5
|
import { createPrs, findOpenPrNumber } from "../gh.js";
|
|
6
6
|
import { parseGithubOriginRepo } from "../git.js";
|
|
7
|
-
import { branchExists, commitsAheadOf, createBranchFromHead, currentBranch, defaultLayerBranchName, deleteBranch, discoverTrunk, fetchRemoteBranch, pushBranch, tipCommitMessage, tipCommitSubject, worktreeDirty } from "../git-stack.js";
|
|
7
|
+
import { branchExists, commitsAheadOf, createBranchFrom, createBranchFromHead, trunkStartRef, currentBranch, defaultLayerBranchName, deleteBranch, discoverTrunk, fetchRemoteBranch, pushBranch, tipCommitMessage, tipCommitSubject, worktreeDirty } from "../git-stack.js";
|
|
8
8
|
import { buildPrBodyFromCommit } from "../pr-body.js";
|
|
9
9
|
import { publishBranchServerSide, type WorkingBranchDeps } from "../server-publish.js";
|
|
10
10
|
import { gitTopLevel } from "../repo-identity.js";
|
|
@@ -33,6 +33,11 @@ export declare function findRegisteredParent(stacks: StackDto[], parentBranch: s
|
|
|
33
33
|
* Local unsubmitted parents are fine; only API-listed layers trip this.
|
|
34
34
|
*/
|
|
35
35
|
export declare function assertMayParentOntoRegistered(parentBranch: string, stacks: StackDto[], extend: boolean, verb: "create" | "submit", owner?: string, repo?: string): RegisteredParentHit | null;
|
|
36
|
+
export declare function stackShapeLine(input: {
|
|
37
|
+
layers: number;
|
|
38
|
+
base: string;
|
|
39
|
+
extendedStack: boolean;
|
|
40
|
+
}): string;
|
|
36
41
|
export declare function requireStackId(raw: string | undefined, usage: string): string;
|
|
37
42
|
/** Parse `owner/repo#12` or `owner/repo 12`. */
|
|
38
43
|
export declare function parseAdoptTarget(args: string[]): {
|
|
@@ -110,6 +115,8 @@ export type StackCreateDeps = {
|
|
|
110
115
|
currentBranch?: typeof currentBranch;
|
|
111
116
|
branchExists?: typeof branchExists;
|
|
112
117
|
createBranchFromHead?: typeof createBranchFromHead;
|
|
118
|
+
createBranchFrom?: typeof createBranchFrom;
|
|
119
|
+
trunkStartRef?: typeof trunkStartRef;
|
|
113
120
|
deleteBranch?: typeof deleteBranch;
|
|
114
121
|
discoverTrunk?: typeof discoverTrunk;
|
|
115
122
|
defaultLayerBranchName?: typeof defaultLayerBranchName;
|
|
@@ -156,6 +163,7 @@ export declare function parseStackPublishArgs(args: string[]): {
|
|
|
156
163
|
title?: string;
|
|
157
164
|
replaceHead?: string;
|
|
158
165
|
autoLand?: boolean;
|
|
166
|
+
extend: boolean;
|
|
159
167
|
status: boolean;
|
|
160
168
|
asJson: boolean;
|
|
161
169
|
};
|