mergestorm 0.3.25 → 0.3.27
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 +19 -6
- package/dist/api.d.ts +59 -0
- package/dist/api.js +40 -0
- package/dist/client/index.d.ts +3 -2
- package/dist/client/index.js +3 -2
- package/dist/commands/dismiss.d.ts +15 -0
- package/dist/commands/dismiss.js +145 -0
- package/dist/commands/registry.js +17 -1
- package/dist/commands/stack.d.ts +9 -0
- package/dist/commands/stack.js +74 -7
- package/dist/shell.js +1 -1
- package/dist/stack-blockers.d.ts +49 -1
- package/dist/stack-blockers.js +124 -11
- package/dist/stack-dto.d.ts +14 -0
- package/dist/stack-watch-obligation.d.ts +43 -0
- package/dist/stack-watch-obligation.js +79 -0
- package/dist/stack-watch.d.ts +7 -1
- package/dist/stack-watch.js +39 -20
- package/dist/ui/ansi.d.ts +14 -1
- package/dist/ui/ansi.js +25 -5
- package/dist/ui/banner.d.ts +2 -1
- package/dist/ui/banner.js +26 -47
- package/dist/ui/logo.d.ts +13 -8
- package/dist/ui/logo.js +25 -13
- package/dist/ui/prompt.d.ts +11 -1
- package/dist/ui/prompt.js +92 -9
- package/package.json +1 -1
- package/skill/mergestorm-bounce-watch/SKILL.md +9 -5
- package/skill/mergestorm-pr-loop/SKILL.md +7 -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
|
-
│
|
|
49
|
-
│
|
|
50
|
-
│ [████████░░░░░░░░░░░░░░░░░░░░░░] 25% used
|
|
51
|
-
│
|
|
52
|
-
│
|
|
53
|
-
│ New
|
|
48
|
+
│ ▀▀▀▀▀▀▀▀▀▀▘ mergestorm v0.3.27 │
|
|
49
|
+
│ ▀▀▀▀▀▀▀▀▀ ● msk_live_… · maelstrom │
|
|
50
|
+
│ ▀▀▀▀▀▀ [████████░░░░░░░░░░░░░░░░░░░░░░] 25% used │
|
|
51
|
+
│ ▝▀▀▀▀▀ local reviews + stacked PRs · mergestorm.ai │
|
|
52
|
+
│ ▝▀▀▘ review a diff · usage for tabs · /help for all commands │
|
|
53
|
+
│ ▀ New: mg dismiss records an audited finding dismissal │
|
|
54
54
|
╰──────────────────────────────────────────────────────────────────────────────╯
|
|
55
55
|
mergestorm
|
|
56
56
|
╭──────────────────────────────────────────────────────────────────────────────╮
|
|
@@ -96,8 +96,21 @@ mergestorm stack restack <stack-id> Restack descendants
|
|
|
96
96
|
mergestorm stack land <stack-id> Land / promote (into review unit when present)
|
|
97
97
|
mergestorm stack wait <stack-id> [--timeout <s>] Wait for stack attention (default 45s, max 300s; 0 = one snapshot, no wait)
|
|
98
98
|
mergestorm stack reset --force Clear local authoring state (not branches/PRs)
|
|
99
|
+
mergestorm dismiss <owner/repo>#<n> --head <sha> --review <id> --preview List a Vortex review's finding ids and the seam gate
|
|
100
|
+
mergestorm dismiss <owner/repo>#<n> --head <sha> --review <id> --finding <id> --reason <text> Dismiss verified-wrong findings (`--all` for the whole review, `--evidence <url>`, `--json`)
|
|
99
101
|
```
|
|
100
102
|
|
|
103
|
+
### Dismissing a finding
|
|
104
|
+
|
|
105
|
+
Use `mergestorm dismiss` when a Vortex finding is wrong and you have checked it against the code. It is recorded server side with your GitHub login, the reason, the evidence URL, the scope and the time. Rules:
|
|
106
|
+
|
|
107
|
+
- You need write access to the repository on GitHub, and the repository must be monitored by your Mergestorm account or the PR must be in one of your stacks.
|
|
108
|
+
- `--head` must be the PR's live head and `--review` a Vortex review made at that head. A moved head, another review, or a finding id that is not in that review is refused, and nothing is written.
|
|
109
|
+
- Retrying the same dismissal is safe; it reports the earlier record.
|
|
110
|
+
- The seam gate clears only when every finding of that integration review is dismissed and the review is still the current seam verdict. CI, other reviews and Auto land policy still apply.
|
|
111
|
+
- Vortex does not raise a dismissed finding again on the same diff (the same head, or a restack with an unchanged diff).
|
|
112
|
+
- With `--json`, the result carries `gate` and, for a stack PR, `watch.next` (the next `stack_wait` call).
|
|
113
|
+
|
|
101
114
|
`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`.
|
|
102
115
|
|
|
103
116
|
`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.
|
package/dist/api.d.ts
CHANGED
|
@@ -267,3 +267,62 @@ export declare function enqueueStack(stackId: string, cfg?: Config): Promise<unk
|
|
|
267
267
|
export declare function cancelMergeQueueEntry(entryId: string, cfg?: Config): Promise<unknown>;
|
|
268
268
|
/** Recently reviewed branches/threads (Bearer GET /api/v1/threads). */
|
|
269
269
|
export declare function listThreads(limit?: number, cfg?: Config): Promise<ThreadListItem[]>;
|
|
270
|
+
export type PrFindingDismissInput = {
|
|
271
|
+
owner: string;
|
|
272
|
+
repo: string;
|
|
273
|
+
prNumber: number;
|
|
274
|
+
headSha: string;
|
|
275
|
+
reviewId: number;
|
|
276
|
+
findingIds?: string[];
|
|
277
|
+
scope?: "findings" | "review";
|
|
278
|
+
reason?: string;
|
|
279
|
+
evidenceUrl?: string;
|
|
280
|
+
preview?: boolean;
|
|
281
|
+
};
|
|
282
|
+
export type PrDismissedFinding = {
|
|
283
|
+
finding_id: string;
|
|
284
|
+
finding_key: string;
|
|
285
|
+
path: string | null;
|
|
286
|
+
line: number | null;
|
|
287
|
+
title: string;
|
|
288
|
+
};
|
|
289
|
+
export type PrFindingDismissResult = {
|
|
290
|
+
status: "dismissed" | "preview";
|
|
291
|
+
owner: string;
|
|
292
|
+
repo: string;
|
|
293
|
+
pr_number: number;
|
|
294
|
+
head_sha: string;
|
|
295
|
+
review_id: number;
|
|
296
|
+
review_kind: "seam" | "core";
|
|
297
|
+
scope: "findings" | "review";
|
|
298
|
+
dismissed: PrDismissedFinding[];
|
|
299
|
+
already_dismissed: Array<PrDismissedFinding & {
|
|
300
|
+
actor_login: string;
|
|
301
|
+
dismissed_at: string;
|
|
302
|
+
}>;
|
|
303
|
+
remaining: PrDismissedFinding[];
|
|
304
|
+
all_dismissed: boolean;
|
|
305
|
+
gate: {
|
|
306
|
+
seam: {
|
|
307
|
+
state: string;
|
|
308
|
+
reviewed_sha: string | null;
|
|
309
|
+
review_id: number | null;
|
|
310
|
+
cleared: boolean;
|
|
311
|
+
blocking: boolean;
|
|
312
|
+
} | null;
|
|
313
|
+
other_gates: "unchanged";
|
|
314
|
+
};
|
|
315
|
+
stack_id: string | null;
|
|
316
|
+
};
|
|
317
|
+
export type PrFindingDismissOutcome = {
|
|
318
|
+
ok: true;
|
|
319
|
+
result: PrFindingDismissResult;
|
|
320
|
+
} | {
|
|
321
|
+
ok: false;
|
|
322
|
+
status: number;
|
|
323
|
+
error: string;
|
|
324
|
+
message: string;
|
|
325
|
+
};
|
|
326
|
+
export declare function dismissPrFindings(input: PrFindingDismissInput, cfg?: Config, opts?: {
|
|
327
|
+
signal?: AbortSignal;
|
|
328
|
+
}): Promise<PrFindingDismissOutcome>;
|
package/dist/api.js
CHANGED
|
@@ -597,3 +597,43 @@ export async function listThreads(limit = 20, cfg) {
|
|
|
597
597
|
const items = body.items;
|
|
598
598
|
return Array.isArray(items) ? items : [];
|
|
599
599
|
}
|
|
600
|
+
export async function dismissPrFindings(input, cfg, opts) {
|
|
601
|
+
const resolved = cfg ?? (await loadConfig());
|
|
602
|
+
const json = {
|
|
603
|
+
owner: input.owner.trim(),
|
|
604
|
+
repo: input.repo.trim(),
|
|
605
|
+
pr_number: input.prNumber,
|
|
606
|
+
head_sha: input.headSha.trim(),
|
|
607
|
+
review_id: input.reviewId,
|
|
608
|
+
...(input.scope ? { scope: input.scope } : {}),
|
|
609
|
+
...(input.findingIds && input.findingIds.length > 0 ? { finding_ids: input.findingIds } : {}),
|
|
610
|
+
...(input.reason !== undefined ? { reason: input.reason } : {}),
|
|
611
|
+
...(input.evidenceUrl ? { evidence_url: input.evidenceUrl } : {}),
|
|
612
|
+
...(input.preview ? { preview: true } : {}),
|
|
613
|
+
};
|
|
614
|
+
const { status, body, retryAfterSeconds } = await apiFetch(resolved, "/api/v1/stacks/pr-review/dismiss", {
|
|
615
|
+
method: "POST",
|
|
616
|
+
json,
|
|
617
|
+
signal: opts?.signal,
|
|
618
|
+
});
|
|
619
|
+
if (status === 429) {
|
|
620
|
+
throw new CommandError(rateLimitedMessage(retryAfterSeconds), REVIEW_EXIT.rate_limited, "rate_limited", {
|
|
621
|
+
retryAfterSeconds,
|
|
622
|
+
});
|
|
623
|
+
}
|
|
624
|
+
if (status === 200)
|
|
625
|
+
return { ok: true, result: body };
|
|
626
|
+
const rejection = (body ?? {});
|
|
627
|
+
if (status === 404 && typeof rejection.error !== "string") {
|
|
628
|
+
throw new CommandError("Finding dismissal is not available on this server yet. Deploy the API update.", REVIEW_EXIT.failed, "not_found");
|
|
629
|
+
}
|
|
630
|
+
if (typeof rejection.error === "string") {
|
|
631
|
+
return {
|
|
632
|
+
ok: false,
|
|
633
|
+
status,
|
|
634
|
+
error: rejection.error,
|
|
635
|
+
message: typeof rejection.message === "string" ? rejection.message : rejection.error,
|
|
636
|
+
};
|
|
637
|
+
}
|
|
638
|
+
throw new CommandError(`Failed to dismiss findings (HTTP ${status}): ${JSON.stringify(body)}`, REVIEW_EXIT.failed, "review_failed");
|
|
639
|
+
}
|
package/dist/client/index.d.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
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, enqueueStack, ensureUpperPark, getEnrichedStack, getMe, getPrVortexReview, getReview, getSettings, landNextStack, listJobs, listMergeQueueEntries, listStacks, listThreads, parseRetryAfterSeconds, patchSettings, restackStack, setStackAutoLand, setStackPolicy, stackPolicyBody, type ApiFetchInit, type ApiFetchResult, type JobListItem, type MeResponse, type MergeQueueBounceDetail, type MergeQueueBounceKind, type MergeQueueEnqueuedBy, type MergeQueueEnqueuedVia, type MergeQueueEntryDto, type MergeQueueEntryState, type PrReviewFinding, type PrReviewFindings, type PrVortexReview, type SettingsPatch, type SettingsResponse, type SettingsWritableKey, type StackDto, type StackPolicyPatch, type ThreadDetail, type ThreadListItem, } from "../api.js";
|
|
3
|
+
export { API_TIMEOUT_PREFIX, DEFAULT_API_TIMEOUT_MS, SETTINGS_WRITABLE_KEYS, adoptStack, apiFetch, cancelMergeQueueEntry, devicePost, dismissPrFindings, enqueueStack, ensureUpperPark, getEnrichedStack, getMe, getPrVortexReview, getReview, getSettings, landNextStack, listJobs, listMergeQueueEntries, listStacks, listThreads, parseRetryAfterSeconds, patchSettings, restackStack, setStackAutoLand, setStackPolicy, stackPolicyBody, type ApiFetchInit, type ApiFetchResult, type JobListItem, type MeResponse, type MergeQueueBounceDetail, type MergeQueueBounceKind, type MergeQueueEnqueuedBy, type MergeQueueEnqueuedVia, type MergeQueueEntryDto, type MergeQueueEntryState, type PrDismissedFinding, type PrFindingDismissInput, type PrFindingDismissOutcome, type PrFindingDismissResult, type PrReviewFinding, type PrReviewFindings, type PrVortexReview, type SettingsPatch, type SettingsResponse, type SettingsWritableKey, type StackDto, type StackPolicyPatch, type ThreadDetail, type ThreadListItem, } from "../api.js";
|
|
4
4
|
export { CommandError, DetachedError, RATE_LIMITED_HINT, REVIEW_EXIT, isCommandErrorCode, rateLimitedMessage, type CommandErrorCode, type CommandErrorExtras, } from "../errors.js";
|
|
5
5
|
export { REVIEW_POLL_BACKOFF_MS, REVIEW_POLL_DEFAULT_TIMEOUT_MS, REVIEW_POLL_INTERVAL_MS, REVIEW_POLL_MAX_INTERVAL_MS, REVIEW_POLL_MAX_TRANSIENT_RETRIES, REVIEW_POLL_STRETCH_AFTER_MS, REVIEW_POLL_STRETCH_SPAN_MS, REVIEW_THREAD_SLUG_RE, PrReviewPollTimeoutError, ReviewPollTimeoutError, collectReviewInput, formatReviewSubmitError, isTransientReviewPollError, isTransientReviewPollStatus, loadReviewContext, pollPrVortexReview, pollReview, stretchedPollIntervalMs, submitReview, transientRetryWaitMs, type PollReviewOptions, type PollPrVortexReviewOptions, type ReviewInput, type SubmitReviewOptions, } from "../commands/review-client.js";
|
|
6
6
|
export { REVIEW_JOB_ENVELOPE_SCHEMA, toReviewJobEnvelope, type ReviewEnvelopeFallbacks, type ReviewJobEnvelope, type ReviewJobRow, } from "../ui/job-envelope.js";
|
|
7
7
|
export { discoverTrunk } from "../git-stack.js";
|
|
8
8
|
export { BOUNCE_KIND_LABELS, deriveStackAgentsBusy, mergeQueueBounceLabel, type StackAgentsBusy } from "../stack-dto.js";
|
|
9
9
|
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";
|
|
10
|
-
export { stackBlockers, stackBlockersSummary, sameHead, layerAgents, layerAgentsBusy, type StackAgentBusy, type StackBlocker, type StackLayerAgents, } from "../stack-blockers.js";
|
|
10
|
+
export { stackBlockers, stackBlockersSummary, sameHead, layerAgents, layerAgentsBusy, conflictLiveParent, stackRepair, agentsCannotClear, type StackAgentBusy, type StackBlocker, type StackBusyAgent, type StackHeldBlocker, type StackLandGatePending, type StackLayerAgents, type StackRepairHint, } from "../stack-blockers.js";
|
|
11
|
+
export { STACK_WATCH_NOT_DONE_SENTENCE, stackTerminalReason, stackWatchNextArgs, stackWatchObligation, type StackWatchDoneReason, type StackWatchNext, type StackWatchNextArgs, type StackWatchObligation, type StackWatchObligationInput, type StackWatchReason, } from "../stack-watch-obligation.js";
|
package/dist/client/index.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
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, enqueueStack, ensureUpperPark, getEnrichedStack, getMe, getPrVortexReview, getReview, getSettings, landNextStack, listJobs, listMergeQueueEntries, listStacks, listThreads, parseRetryAfterSeconds, patchSettings, restackStack, setStackAutoLand, setStackPolicy, stackPolicyBody, } from "../api.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, landNextStack, listJobs, listMergeQueueEntries, listStacks, listThreads, parseRetryAfterSeconds, patchSettings, restackStack, setStackAutoLand, setStackPolicy, stackPolicyBody, } from "../api.js";
|
|
4
4
|
export { CommandError, DetachedError, RATE_LIMITED_HINT, REVIEW_EXIT, isCommandErrorCode, rateLimitedMessage, } from "../errors.js";
|
|
5
5
|
export { REVIEW_POLL_BACKOFF_MS, REVIEW_POLL_DEFAULT_TIMEOUT_MS, REVIEW_POLL_INTERVAL_MS, REVIEW_POLL_MAX_INTERVAL_MS, REVIEW_POLL_MAX_TRANSIENT_RETRIES, REVIEW_POLL_STRETCH_AFTER_MS, REVIEW_POLL_STRETCH_SPAN_MS, REVIEW_THREAD_SLUG_RE, PrReviewPollTimeoutError, ReviewPollTimeoutError, collectReviewInput, formatReviewSubmitError, isTransientReviewPollError, isTransientReviewPollStatus, loadReviewContext, pollPrVortexReview, pollReview, stretchedPollIntervalMs, submitReview, transientRetryWaitMs, } from "../commands/review-client.js";
|
|
6
6
|
export { REVIEW_JOB_ENVELOPE_SCHEMA, toReviewJobEnvelope, } from "../ui/job-envelope.js";
|
|
7
7
|
export { discoverTrunk } from "../git-stack.js";
|
|
8
8
|
export { BOUNCE_KIND_LABELS, deriveStackAgentsBusy, mergeQueueBounceLabel } from "../stack-dto.js";
|
|
9
9
|
export { STACK_WATCH_SCHEMA, STACK_WATCH_DEFAULT_TIMEOUT_MS, STACK_WATCH_BUSY_RECHECK_S, StackWatchError, StackWatchTimeoutError, pollStackWatch, } from "../stack-watch.js";
|
|
10
|
-
export { stackBlockers, stackBlockersSummary, sameHead, layerAgents, layerAgentsBusy, } from "../stack-blockers.js";
|
|
10
|
+
export { stackBlockers, stackBlockersSummary, sameHead, layerAgents, layerAgentsBusy, conflictLiveParent, stackRepair, agentsCannotClear, } from "../stack-blockers.js";
|
|
11
|
+
export { STACK_WATCH_NOT_DONE_SENTENCE, stackTerminalReason, stackWatchNextArgs, stackWatchObligation, } from "../stack-watch-obligation.js";
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { type PrFindingDismissInput, type PrFindingDismissOutcome, type PrFindingDismissResult } from "../api.js";
|
|
2
|
+
import { type Config } from "../config.js";
|
|
3
|
+
import { type StackWatchObligation } from "../stack-watch-obligation.js";
|
|
4
|
+
export declare const DISMISS_USAGE: string;
|
|
5
|
+
export type ParsedDismissArgs = PrFindingDismissInput & {
|
|
6
|
+
json: boolean;
|
|
7
|
+
};
|
|
8
|
+
export declare function parseDismissArgs(args: string[]): ParsedDismissArgs;
|
|
9
|
+
export declare function dismissWatch(result: PrFindingDismissResult): StackWatchObligation | null;
|
|
10
|
+
export declare function formatDismissResult(result: PrFindingDismissResult, watch: StackWatchObligation | null): string;
|
|
11
|
+
export type DismissDeps = {
|
|
12
|
+
loadConfig?: () => Promise<Config>;
|
|
13
|
+
dismiss?: (input: PrFindingDismissInput, cfg: Config) => Promise<PrFindingDismissOutcome>;
|
|
14
|
+
};
|
|
15
|
+
export declare function cmdDismiss(args: string[], deps?: DismissDeps): Promise<void>;
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { dismissPrFindings, } from "../api.js";
|
|
2
|
+
import { loadConfig } from "../config.js";
|
|
3
|
+
import { CommandError, REVIEW_EXIT } from "../errors.js";
|
|
4
|
+
import { stackWatchObligation } from "../stack-watch-obligation.js";
|
|
5
|
+
import { parseAdoptTarget, withWatch } from "./stack.js";
|
|
6
|
+
export const DISMISS_USAGE = [
|
|
7
|
+
"usage: mergestorm dismiss <owner/repo>#<n> --head <sha> --review <id>",
|
|
8
|
+
" (--finding <id> [--finding <id>…] | --all) --reason <text> [--evidence <url>] [--json]",
|
|
9
|
+
" mergestorm dismiss <owner/repo>#<n> --head <sha> --review <id> --preview [--json]",
|
|
10
|
+
].join("\n");
|
|
11
|
+
function usage(message) {
|
|
12
|
+
return new CommandError(message ? `${message}\n${DISMISS_USAGE}` : DISMISS_USAGE, REVIEW_EXIT.usage, "usage");
|
|
13
|
+
}
|
|
14
|
+
export function parseDismissArgs(args) {
|
|
15
|
+
const target = [];
|
|
16
|
+
const findingIds = [];
|
|
17
|
+
let headSha = "";
|
|
18
|
+
let reviewId;
|
|
19
|
+
let reason;
|
|
20
|
+
let evidenceUrl;
|
|
21
|
+
let all = false;
|
|
22
|
+
let preview = false;
|
|
23
|
+
let json = false;
|
|
24
|
+
const value = (flag, raw) => {
|
|
25
|
+
if (raw === undefined || raw.startsWith("--"))
|
|
26
|
+
throw usage(`${flag} needs a value`);
|
|
27
|
+
return raw;
|
|
28
|
+
};
|
|
29
|
+
for (let i = 0; i < args.length; i++) {
|
|
30
|
+
const arg = args[i];
|
|
31
|
+
if (arg === "--head")
|
|
32
|
+
headSha = value(arg, args[++i]).trim().toLowerCase();
|
|
33
|
+
else if (arg === "--review") {
|
|
34
|
+
const raw = value(arg, args[++i]).trim();
|
|
35
|
+
if (!/^[1-9]\d*$/.test(raw) || !Number.isSafeInteger(Number(raw)))
|
|
36
|
+
throw usage("--review needs the numeric GitHub review id");
|
|
37
|
+
reviewId = Number(raw);
|
|
38
|
+
}
|
|
39
|
+
else if (arg === "--finding")
|
|
40
|
+
findingIds.push(...value(arg, args[++i]).split(",").map((id) => id.trim()).filter(Boolean));
|
|
41
|
+
else if (arg === "--all")
|
|
42
|
+
all = true;
|
|
43
|
+
else if (arg === "--reason")
|
|
44
|
+
reason = value(arg, args[++i]);
|
|
45
|
+
else if (arg === "--evidence")
|
|
46
|
+
evidenceUrl = value(arg, args[++i]).trim();
|
|
47
|
+
else if (arg === "--preview")
|
|
48
|
+
preview = true;
|
|
49
|
+
else if (arg === "--json")
|
|
50
|
+
json = true;
|
|
51
|
+
else if (arg.startsWith("--"))
|
|
52
|
+
throw usage(`unknown flag ${arg}`);
|
|
53
|
+
else
|
|
54
|
+
target.push(arg);
|
|
55
|
+
}
|
|
56
|
+
let parsedTarget;
|
|
57
|
+
try {
|
|
58
|
+
parsedTarget = parseAdoptTarget(target);
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
throw usage("name the PR as <owner/repo>#<n>");
|
|
62
|
+
}
|
|
63
|
+
if (!/^[0-9a-f]{40}$/.test(headSha))
|
|
64
|
+
throw usage("--head needs the full 40-character PR head SHA");
|
|
65
|
+
if (reviewId === undefined)
|
|
66
|
+
throw usage("--review is required");
|
|
67
|
+
if (!preview) {
|
|
68
|
+
if (all && findingIds.length > 0)
|
|
69
|
+
throw usage("pass --finding or --all, not both");
|
|
70
|
+
if (!all && findingIds.length === 0)
|
|
71
|
+
throw usage("pass --finding <id> for each finding, or --all for the whole review");
|
|
72
|
+
if (!reason?.trim())
|
|
73
|
+
throw usage("--reason is required: say why the finding is wrong or not actionable");
|
|
74
|
+
}
|
|
75
|
+
return {
|
|
76
|
+
...parsedTarget,
|
|
77
|
+
headSha,
|
|
78
|
+
reviewId,
|
|
79
|
+
findingIds: all ? [] : findingIds,
|
|
80
|
+
scope: all ? "review" : "findings",
|
|
81
|
+
...(reason !== undefined ? { reason } : {}),
|
|
82
|
+
...(evidenceUrl ? { evidenceUrl } : {}),
|
|
83
|
+
preview,
|
|
84
|
+
json,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
export function dismissWatch(result) {
|
|
88
|
+
if (!result.stack_id)
|
|
89
|
+
return null;
|
|
90
|
+
return stackWatchObligation({ stackId: result.stack_id, terminal: null, unread: true, freshCursor: true });
|
|
91
|
+
}
|
|
92
|
+
function findingLine(finding) {
|
|
93
|
+
const where = finding.path ? `${finding.path}${finding.line ? `:${finding.line}` : ""}` : "review body";
|
|
94
|
+
return ` ${finding.finding_id} ${where} ${finding.title}`;
|
|
95
|
+
}
|
|
96
|
+
export function formatDismissResult(result, watch) {
|
|
97
|
+
const lines = [];
|
|
98
|
+
const pr = `${result.owner}/${result.repo}#${result.pr_number}`;
|
|
99
|
+
const kind = result.review_kind === "seam" ? "seam review" : "Vortex review";
|
|
100
|
+
if (result.status === "preview") {
|
|
101
|
+
lines.push(`${pr} ${kind} ${result.review_id} at ${result.head_sha.slice(0, 7)}: ${result.remaining.length} open finding(s)`);
|
|
102
|
+
lines.push(...result.remaining.map(findingLine));
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
lines.push(`${pr} ${kind} ${result.review_id} at ${result.head_sha.slice(0, 7)}`);
|
|
106
|
+
if (result.dismissed.length)
|
|
107
|
+
lines.push("Dismissed:", ...result.dismissed.map(findingLine));
|
|
108
|
+
if (result.already_dismissed.length) {
|
|
109
|
+
lines.push("Already dismissed:", ...result.already_dismissed.map((f) => `${findingLine(f)} (by @${f.actor_login})`));
|
|
110
|
+
}
|
|
111
|
+
if (result.remaining.length)
|
|
112
|
+
lines.push("Still open:", ...result.remaining.map(findingLine));
|
|
113
|
+
}
|
|
114
|
+
const seam = result.gate.seam;
|
|
115
|
+
if (seam) {
|
|
116
|
+
lines.push(seam.cleared
|
|
117
|
+
? "Seam gate: cleared (approved at this head). CI, other reviews and Auto land policy still apply."
|
|
118
|
+
: seam.blocking
|
|
119
|
+
? `Seam gate: still blocking (seam_state=${seam.state}).`
|
|
120
|
+
: `Seam gate: not blocking (seam_state=${seam.state}).`);
|
|
121
|
+
}
|
|
122
|
+
if (watch?.next)
|
|
123
|
+
lines.push(`Next: ${watch.next.command}`);
|
|
124
|
+
return lines.join("\n");
|
|
125
|
+
}
|
|
126
|
+
export async function cmdDismiss(args, deps = {}) {
|
|
127
|
+
const parsed = parseDismissArgs(args);
|
|
128
|
+
const cfg = await (deps.loadConfig ?? loadConfig)();
|
|
129
|
+
const { json, ...input } = parsed;
|
|
130
|
+
const outcome = await (deps.dismiss ?? ((i, c) => dismissPrFindings(i, c)))(input, cfg);
|
|
131
|
+
if (!outcome.ok) {
|
|
132
|
+
if (json) {
|
|
133
|
+
console.log(JSON.stringify({ error: { code: outcome.error, message: outcome.message } }, null, 2));
|
|
134
|
+
process.exitCode = REVIEW_EXIT.failed;
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
throw new CommandError(`Dismissal refused (${outcome.error}): ${outcome.message}`, REVIEW_EXIT.failed, "review_failed");
|
|
138
|
+
}
|
|
139
|
+
const watch = dismissWatch(outcome.result);
|
|
140
|
+
if (json) {
|
|
141
|
+
console.log(JSON.stringify(withWatch(outcome.result, watch), null, 2));
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
console.log(formatDismissResult(outcome.result, watch));
|
|
145
|
+
}
|
|
@@ -3,6 +3,7 @@ import { CommandError } from "../errors.js";
|
|
|
3
3
|
import { cmdBranches, cmdChain } from "./branches.js";
|
|
4
4
|
import { canBrowse, openTabsBrowser } from "./browse.js";
|
|
5
5
|
import { cmdCredits } from "./credits.js";
|
|
6
|
+
import { cmdDismiss } from "./dismiss.js";
|
|
6
7
|
import { cmdJobs, cmdThread } from "./jobs.js";
|
|
7
8
|
import { cmdLogin } from "./login.js";
|
|
8
9
|
import { cmdLogout } from "./logout.js";
|
|
@@ -79,6 +80,21 @@ export const COMMAND_REGISTRY = [
|
|
|
79
80
|
});
|
|
80
81
|
},
|
|
81
82
|
},
|
|
83
|
+
{
|
|
84
|
+
name: "dismiss",
|
|
85
|
+
summary: "Dismiss Vortex findings you verified are wrong (audited)",
|
|
86
|
+
usage: [
|
|
87
|
+
"mergestorm dismiss <owner/repo>#<n> --head <sha> --review <id> --preview",
|
|
88
|
+
" Lists the review's findings (ids) and the seam gate; writes nothing",
|
|
89
|
+
"mergestorm dismiss <owner/repo>#<n> --head <sha> --review <id> --finding <id> --reason <text>",
|
|
90
|
+
" [--finding <id>]… | --all (every finding of that review) [--evidence <url>] [--json]",
|
|
91
|
+
" Needs write access on GitHub; refuses a moved head, another review or an unknown id",
|
|
92
|
+
" Clears the seam gate only when every finding of that seam review is dismissed",
|
|
93
|
+
],
|
|
94
|
+
async run(args) {
|
|
95
|
+
await cmdDismiss(args);
|
|
96
|
+
},
|
|
97
|
+
},
|
|
82
98
|
{
|
|
83
99
|
name: "status",
|
|
84
100
|
summary: "Job panel (id) or Status / Usage / Jobs tabs",
|
|
@@ -224,7 +240,7 @@ export const COMMAND_REGISTRY = [
|
|
|
224
240
|
"mergestorm stack reset --force Clear this repo's pre-submit authoring state",
|
|
225
241
|
"mergestorm stack list [--json] List registered stacks",
|
|
226
242
|
"mergestorm stack status <stack-id> [--json] Enriched stack status",
|
|
227
|
-
"mergestorm stack wait <stack-id> [--json] [--timeout <s>] Wait for stack attention (default 45s, max 300s; 0 = one snapshot, no wait)",
|
|
243
|
+
"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",
|
|
228
244
|
"mergestorm stack set <stack-id> [--auto-land on|off] [--auto-review on|off|default] [--auto-patch on|off|default] [--json] Per-stack policy",
|
|
229
245
|
"mergestorm stack adopt <owner/repo>#<pr> [--auto-land on|off] [--auto-review on|off] [--auto-patch on|off] Import an open PR chain",
|
|
230
246
|
"mergestorm stack restack <stack-id> Restack stack descendants",
|
package/dist/commands/stack.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { type StackHeldBlocker, type StackRepairHint } from "../stack-blockers.js";
|
|
2
|
+
import { type StackWatchObligation } from "../stack-watch-obligation.js";
|
|
1
3
|
import { adoptStack, ensureUpperPark, findStackPull, getEnrichedStack, listStacks, openStackPull, setStackPolicy, type StackDto, type StackPolicyPatch } from "../api.js";
|
|
2
4
|
import { loadConfig } from "../config.js";
|
|
3
5
|
import { createPrs, findOpenPrNumber } from "../gh.js";
|
|
@@ -7,6 +9,12 @@ import { buildPrBodyFromCommit } from "../pr-body.js";
|
|
|
7
9
|
import { gitTopLevel } from "../repo-identity.js";
|
|
8
10
|
import { loadStackMeta, saveStackMeta } from "../stack-meta.js";
|
|
9
11
|
import { pollStackWatch } from "../stack-watch.js";
|
|
12
|
+
export declare function stackHeldLine(held: StackHeldBlocker | null | undefined): string[];
|
|
13
|
+
export declare function stackRepairLine(repair: StackRepairHint | null | undefined): string[];
|
|
14
|
+
export declare function stackWatchTextLines(watch: StackWatchObligation | null | undefined): string[];
|
|
15
|
+
export declare function openStackWatch(stackId: string, opts?: {
|
|
16
|
+
unread?: boolean;
|
|
17
|
+
}): StackWatchObligation;
|
|
10
18
|
/** A registered stack layer that would become the parent of a new layer. */
|
|
11
19
|
export type RegisteredParentHit = {
|
|
12
20
|
stackId: string;
|
|
@@ -140,6 +148,7 @@ export type StackSetDeps = {
|
|
|
140
148
|
};
|
|
141
149
|
/** Human summary lines for a `stack set` write, one per flag. */
|
|
142
150
|
export declare function stackSetSummary(parsed: StackSetArgs): string[];
|
|
151
|
+
export declare function withWatch(body: unknown, watch: StackWatchObligation | null): unknown;
|
|
143
152
|
export declare function cmdStackSet(args: string[], deps?: StackSetDeps): Promise<void>;
|
|
144
153
|
export declare function cmdStack(args: string[], options?: {
|
|
145
154
|
signal?: AbortSignal;
|
package/dist/commands/stack.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { stackBlockersSummary } from "../stack-blockers.js";
|
|
1
|
+
import { stackBlockers, stackBlockersSummary } from "../stack-blockers.js";
|
|
2
|
+
import { STACK_WATCH_NOT_DONE_SENTENCE, stackTerminalReason, stackWatchObligation, } from "../stack-watch-obligation.js";
|
|
2
3
|
import { adoptStack, ensureUpperPark, findStackPull, getEnrichedStack, landNextStack, listStacks, openStackPull, restackStack, setStackPolicy, } from "../api.js";
|
|
3
4
|
import { loadConfig } from "../config.js";
|
|
4
5
|
import { rateLimitedMessage, REVIEW_EXIT, CommandError } from "../errors.js";
|
|
@@ -15,6 +16,32 @@ import { runLineTabsBrowser } from "../ui/line-tabs.js";
|
|
|
15
16
|
import { present } from "../ui/present.js";
|
|
16
17
|
import { canBrowse } from "./browse.js";
|
|
17
18
|
import { openHelpBrowser } from "./help.js";
|
|
19
|
+
export function stackHeldLine(held) {
|
|
20
|
+
if (!held)
|
|
21
|
+
return [];
|
|
22
|
+
const agents = held.waitingOn.map((agent) => agent === "cyclone" ? "Cyclone" : "Vortex").join(" and ");
|
|
23
|
+
return [`Held: #${held.prNumber} ${held.blocker}. Plan the fix; act once ${agents} ${held.waitingOn.length > 1 ? "are" : "is"} idle and the watch returns attention.`];
|
|
24
|
+
}
|
|
25
|
+
export function stackRepairLine(repair) {
|
|
26
|
+
if (!repair)
|
|
27
|
+
return [];
|
|
28
|
+
const files = "files" in repair && repair.files.length ? ` (files: ${repair.files.join(", ")})` : "";
|
|
29
|
+
return [`Repair #${repair.prNumber} ${repair.kind}${files}: ${repair.steps}`];
|
|
30
|
+
}
|
|
31
|
+
export function stackWatchTextLines(watch) {
|
|
32
|
+
if (!watch)
|
|
33
|
+
return [];
|
|
34
|
+
if (watch.done)
|
|
35
|
+
return [watch.message];
|
|
36
|
+
const next = watch.next;
|
|
37
|
+
return [
|
|
38
|
+
...(next ? [`Next: ${next.command} (MCP: stack_wait ${JSON.stringify(next.args)})`] : []),
|
|
39
|
+
STACK_WATCH_NOT_DONE_SENTENCE,
|
|
40
|
+
];
|
|
41
|
+
}
|
|
42
|
+
export function openStackWatch(stackId, opts = {}) {
|
|
43
|
+
return stackWatchObligation({ stackId, terminal: null, unread: opts.unread, freshCursor: true });
|
|
44
|
+
}
|
|
18
45
|
const STACK_USAGE = `usage:
|
|
19
46
|
mergestorm stack create [name] [--onto <branch>] [--trunk <branch>] [--extend] [--auto-land on|off] [--auto-review on|off] [--auto-patch on|off] [--json]
|
|
20
47
|
mergestorm stack submit [--extend] [--auto-land on|off] [--auto-review on|off] [--auto-patch on|off] [--rest] [--json]
|
|
@@ -22,6 +49,8 @@ const STACK_USAGE = `usage:
|
|
|
22
49
|
mergestorm stack list [--json]
|
|
23
50
|
mergestorm stack status <stack-id> [--json]
|
|
24
51
|
mergestorm stack wait <stack-id> [--json] [--timeout <s>]
|
|
52
|
+
--json writes exactly one JSON document to stdout. The timeout or error notice goes to stderr
|
|
53
|
+
(exit 5 on timeout), so do not merge the streams (2>&1) before parsing stdout.
|
|
25
54
|
mergestorm stack set <stack-id> [--auto-land on|off] [--auto-review on|off|default] [--auto-patch on|off|default] [--json]
|
|
26
55
|
mergestorm stack adopt <owner/repo>#<pr> [--auto-land on|off] [--auto-review on|off] [--auto-patch on|off] [--json]
|
|
27
56
|
mergestorm stack restack <stack-id> [--json]
|
|
@@ -780,6 +809,7 @@ export async function cmdStackSubmit(args, deps = {}) {
|
|
|
780
809
|
stackId: stackId ?? null,
|
|
781
810
|
trunk: data.stack?.trunkBranch ?? meta.trunk,
|
|
782
811
|
chain: data.chain ?? null,
|
|
812
|
+
watch: stackId ? openStackWatch(stackId) : null,
|
|
783
813
|
}, null, 2));
|
|
784
814
|
return;
|
|
785
815
|
}
|
|
@@ -799,6 +829,8 @@ export async function cmdStackSubmit(args, deps = {}) {
|
|
|
799
829
|
summary.push(ansi.dim(" Stack registration returned no stack ID"));
|
|
800
830
|
}
|
|
801
831
|
summary.push(ansi.dim(" Dashboard: https://mergestorm.ai/dashboard"));
|
|
832
|
+
if (stackId)
|
|
833
|
+
summary.push(...stackWatchTextLines(openStackWatch(stackId)).map((line) => ` ${line}`));
|
|
802
834
|
await present("Stack", summary);
|
|
803
835
|
}
|
|
804
836
|
async function cmdStackReset(args) {
|
|
@@ -868,7 +900,11 @@ export async function cmdStackWait(args, deps = {}) {
|
|
|
868
900
|
...envelope,
|
|
869
901
|
...(retryAfterSeconds !== undefined ? { retry_after_seconds: retryAfterSeconds } : {}),
|
|
870
902
|
}, null, 2)
|
|
871
|
-
:
|
|
903
|
+
: [
|
|
904
|
+
`Stack ${envelope.stackId} · ${envelope.status}${stackBlockersSummary(envelope.blocker && envelope.prNumber != null ? { prNumber: envelope.prNumber, blocker: envelope.blocker } : null, envelope.issues, envelope.busy, envelope.landGatePending ?? null, envelope.actAfter ?? null)}${envelope.assessment === "unavailable" ? " · assessment unavailable" : ""}`,
|
|
905
|
+
...stackRepairLine(envelope.repair),
|
|
906
|
+
...stackWatchTextLines(envelope.watch),
|
|
907
|
+
].join("\n"));
|
|
872
908
|
try {
|
|
873
909
|
print(await (deps.pollStackWatch ?? pollStackWatch)(cfg, stackId, {
|
|
874
910
|
timeoutMs: timeoutS * 1000,
|
|
@@ -892,6 +928,7 @@ export async function cmdStackWait(args, deps = {}) {
|
|
|
892
928
|
}
|
|
893
929
|
export async function cmdStackStatus(args, deps = {}) {
|
|
894
930
|
const usage = "usage: mergestorm stack status <stack-id> [--json]";
|
|
931
|
+
const asJson = args.includes("--json");
|
|
895
932
|
const positional = args.filter((arg) => arg !== "--json");
|
|
896
933
|
if (positional.length !== 1)
|
|
897
934
|
throw new CommandError(usage, 2, "usage");
|
|
@@ -899,10 +936,25 @@ export async function cmdStackStatus(args, deps = {}) {
|
|
|
899
936
|
const cfg = await (deps.loadConfig ?? loadConfig)();
|
|
900
937
|
const stack = await (deps.getEnrichedStack ?? getEnrichedStack)(stackId, cfg);
|
|
901
938
|
if (!stack) {
|
|
939
|
+
const watch = stackWatchObligation({ stackId, terminal: "not_found" });
|
|
940
|
+
if (asJson) {
|
|
941
|
+
console.log(JSON.stringify({ error: { code: "not_found", stack_id: stackId }, watch }, null, 2));
|
|
942
|
+
process.exitCode = 1;
|
|
943
|
+
return;
|
|
944
|
+
}
|
|
945
|
+
console.log(watch.message);
|
|
902
946
|
throw new CommandError(`Stack not found or not owned by the current user: ${stackId}`, 1, "not_found");
|
|
903
947
|
}
|
|
904
|
-
|
|
905
|
-
|
|
948
|
+
const { attention, held, currentCandidate, repair } = stackBlockers(stack);
|
|
949
|
+
const watch = stackWatchObligation({
|
|
950
|
+
stackId: stack.id,
|
|
951
|
+
terminal: stackTerminalReason(stack),
|
|
952
|
+
cursor: { enrolledHeadSha: currentCandidate?.headSha ?? null },
|
|
953
|
+
attention: attention ? { prNumber: attention.prNumber, blocker: attention.blocker } : null,
|
|
954
|
+
freshCursor: true,
|
|
955
|
+
});
|
|
956
|
+
if (asJson) {
|
|
957
|
+
console.log(JSON.stringify({ ...stack, held, repair, watch }, null, 2));
|
|
906
958
|
return;
|
|
907
959
|
}
|
|
908
960
|
const lines = formatStackHuman(stack, false);
|
|
@@ -915,6 +967,7 @@ export async function cmdStackStatus(args, deps = {}) {
|
|
|
915
967
|
(layer.cycloneStatus ? ` Cyclone: ${layer.cycloneStatus}` : "") +
|
|
916
968
|
(layer.tempestStatus ? ` Tempest: ${layer.tempestStatus}` : ""));
|
|
917
969
|
}
|
|
970
|
+
lines.push(...[...stackHeldLine(held), ...stackRepairLine(repair), ...stackWatchTextLines(watch)].map((line) => ` ${line}`));
|
|
918
971
|
await present("Stack status", lines);
|
|
919
972
|
}
|
|
920
973
|
/** Human summary lines for a `stack set` write, one per flag. */
|
|
@@ -932,6 +985,11 @@ export function stackSetSummary(parsed) {
|
|
|
932
985
|
}
|
|
933
986
|
return lines;
|
|
934
987
|
}
|
|
988
|
+
export function withWatch(body, watch) {
|
|
989
|
+
if (!watch || typeof body !== "object" || body === null || Array.isArray(body))
|
|
990
|
+
return body;
|
|
991
|
+
return { ...body, watch };
|
|
992
|
+
}
|
|
935
993
|
export async function cmdStackSet(args, deps = {}) {
|
|
936
994
|
const parsed = parseStackSetArgs(args);
|
|
937
995
|
const cfg = await (deps.loadConfig ?? loadConfig)();
|
|
@@ -940,18 +998,26 @@ export async function cmdStackSet(args, deps = {}) {
|
|
|
940
998
|
...(parsed.autoReview !== undefined ? { autoReviewOverride: parsed.autoReview } : {}),
|
|
941
999
|
...(parsed.autoPatch !== undefined ? { autoPatchOverride: parsed.autoPatch } : {}),
|
|
942
1000
|
}, cfg);
|
|
1001
|
+
const watch = openStackWatch(parsed.stackId, { unread: true });
|
|
943
1002
|
if (parsed.asJson) {
|
|
944
|
-
console.log(JSON.stringify(body, null, 2));
|
|
1003
|
+
console.log(JSON.stringify(withWatch(body, watch), null, 2));
|
|
945
1004
|
return;
|
|
946
1005
|
}
|
|
947
|
-
await present("Stack",
|
|
1006
|
+
await present("Stack", [
|
|
1007
|
+
...stackSetSummary(parsed).map((line) => ansi.brightGreen(` ${line} for ${parsed.stackId}`)),
|
|
1008
|
+
...stackWatchTextLines(watch).map((line) => ` ${line}`),
|
|
1009
|
+
]);
|
|
948
1010
|
}
|
|
949
1011
|
async function cmdStackAdopt(args) {
|
|
950
1012
|
const { owner, repo, prNumber, autoLand, autoReview, autoPatch, asJson } = parseStackAdoptArgs(args);
|
|
951
1013
|
const cfg = await loadConfig();
|
|
952
1014
|
const body = await adoptStack(owner, repo, prNumber, cfg, openPolicyPatch({ autoLand, autoReview, autoPatch }));
|
|
1015
|
+
const adoptedId = typeof body === "object" && body !== null
|
|
1016
|
+
? body.stack?.id
|
|
1017
|
+
: undefined;
|
|
1018
|
+
const watch = typeof adoptedId === "string" && adoptedId ? openStackWatch(adoptedId, { unread: true }) : null;
|
|
953
1019
|
if (asJson) {
|
|
954
|
-
console.log(JSON.stringify(body, null, 2));
|
|
1020
|
+
console.log(JSON.stringify(withWatch(body, watch), null, 2));
|
|
955
1021
|
return;
|
|
956
1022
|
}
|
|
957
1023
|
if (typeof body !== "object" || body === null) {
|
|
@@ -969,6 +1035,7 @@ async function cmdStackAdopt(args) {
|
|
|
969
1035
|
imported.push(ansi.dim(` trunk: ${stack.trunkBranch}`));
|
|
970
1036
|
}
|
|
971
1037
|
imported.push(ansi.dim(" Dashboard: https://mergestorm.ai/dashboard"));
|
|
1038
|
+
imported.push(...stackWatchTextLines(watch).map((line) => ` ${line}`));
|
|
972
1039
|
await present("Stack", imported);
|
|
973
1040
|
}
|
|
974
1041
|
async function cmdStackRestack(args) {
|
package/dist/shell.js
CHANGED