mergestorm 0.3.9 → 0.3.15
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 +67 -24
- package/dist/api.d.ts +106 -40
- package/dist/api.js +245 -9
- package/dist/client/index.d.ts +5 -0
- package/dist/client/index.js +5 -0
- package/dist/commands/branches.d.ts +6 -2
- package/dist/commands/branches.js +29 -14
- package/dist/commands/browse.d.ts +39 -0
- package/dist/commands/browse.js +259 -0
- package/dist/commands/credits.js +30 -12
- package/dist/commands/help.d.ts +5 -0
- package/dist/commands/help.js +93 -0
- package/dist/commands/jobs.d.ts +11 -2
- package/dist/commands/jobs.js +64 -15
- package/dist/commands/login.js +32 -10
- package/dist/commands/logout.js +2 -1
- package/dist/commands/pr.d.ts +22 -0
- package/dist/commands/pr.js +127 -0
- package/dist/commands/queue.d.ts +12 -0
- package/dist/commands/queue.js +160 -0
- package/dist/commands/registry.d.ts +1 -1
- package/dist/commands/registry.js +115 -33
- package/dist/commands/review-client.d.ts +106 -0
- package/dist/commands/review-client.js +399 -0
- package/dist/commands/review.d.ts +36 -6
- package/dist/commands/review.js +395 -145
- package/dist/commands/settings.d.ts +17 -0
- package/dist/commands/settings.js +89 -0
- package/dist/commands/skill.d.ts +20 -0
- package/dist/commands/skill.js +145 -0
- package/dist/commands/stack.d.ts +36 -2
- package/dist/commands/stack.js +341 -113
- package/dist/commands/status.d.ts +18 -2
- package/dist/commands/status.js +236 -21
- package/dist/commands/whoami.d.ts +3 -1
- package/dist/commands/whoami.js +21 -7
- package/dist/errors.d.ts +18 -2
- package/dist/errors.js +22 -1
- package/dist/git-stack.d.ts +2 -0
- package/dist/git-stack.js +4 -0
- package/dist/git.d.ts +12 -1
- package/dist/git.js +52 -7
- package/dist/shell.d.ts +1 -1
- package/dist/shell.js +39 -28
- package/dist/stack-dto.d.ts +266 -0
- package/dist/stack-dto.js +7 -0
- package/dist/submit-pr-base.d.ts +31 -0
- package/dist/submit-pr-base.js +69 -0
- package/dist/ui/ansi.d.ts +2 -0
- package/dist/ui/ansi.js +2 -0
- package/dist/ui/banner.d.ts +34 -2
- package/dist/ui/banner.js +170 -79
- package/dist/ui/box.d.ts +13 -9
- package/dist/ui/box.js +28 -44
- package/dist/ui/findings.d.ts +4 -1
- package/dist/ui/findings.js +14 -7
- package/dist/ui/job-envelope.d.ts +68 -0
- package/dist/ui/job-envelope.js +35 -0
- package/dist/ui/line-tabs.d.ts +27 -0
- package/dist/ui/line-tabs.js +170 -0
- package/dist/ui/logo.d.ts +10 -2
- package/dist/ui/logo.js +13 -9
- package/dist/ui/present.d.ts +10 -0
- package/dist/ui/present.js +13 -0
- package/dist/ui/prompt.d.ts +148 -0
- package/dist/ui/prompt.js +433 -71
- package/dist/ui/select.js +1 -1
- package/dist/ui/tabs.d.ts +125 -0
- package/dist/ui/tabs.js +480 -0
- package/dist/ui/usage.d.ts +29 -1
- package/dist/ui/usage.js +71 -2
- package/dist/ui/width.d.ts +26 -0
- package/dist/ui/width.js +262 -0
- package/package.json +6 -1
- package/skill/SKILL.md +98 -0
- package/skill/mergestorm-pr-loop/SKILL.md +76 -0
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { type PollPrVortexReviewOptions } from "./review-client.js";
|
|
2
|
+
export type ParsedPrArgs = {
|
|
3
|
+
owner: string;
|
|
4
|
+
repo: string;
|
|
5
|
+
prNumber: number;
|
|
6
|
+
format: "json" | "pretty";
|
|
7
|
+
wait: boolean;
|
|
8
|
+
afterSha?: string;
|
|
9
|
+
timeoutMs: number;
|
|
10
|
+
};
|
|
11
|
+
export type PrOptions = {
|
|
12
|
+
defaultFormat?: "json" | "pretty";
|
|
13
|
+
signal?: AbortSignal;
|
|
14
|
+
pollIntervalMs?: number;
|
|
15
|
+
/** Test seams for deterministic polling. */
|
|
16
|
+
poll?: Pick<PollPrVortexReviewOptions, "sleep" | "now" | "random">;
|
|
17
|
+
mode?: "oneshot" | "shell";
|
|
18
|
+
};
|
|
19
|
+
export declare function parsePrArgs(args: string[], defaults?: {
|
|
20
|
+
format: "json" | "pretty";
|
|
21
|
+
}): ParsedPrArgs;
|
|
22
|
+
export declare function cmdPr(args: string[], opts?: PrOptions): Promise<void>;
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { getPrVortexReview } from "../api.js";
|
|
2
|
+
import { loadConfig } from "../config.js";
|
|
3
|
+
import { CommandError, REVIEW_EXIT } from "../errors.js";
|
|
4
|
+
import { ansi } from "../ui/ansi.js";
|
|
5
|
+
import { renderFindings } from "../ui/findings.js";
|
|
6
|
+
import { PrReviewPollTimeoutError, REVIEW_POLL_DEFAULT_TIMEOUT_MS, pollPrVortexReview, } from "./review-client.js";
|
|
7
|
+
import { parseAdoptTarget } from "./stack.js";
|
|
8
|
+
const PR_USAGE = "usage: mergestorm pr <owner/repo>#<n> [--json] [--wait] [--after-sha <sha>] [--timeout <s>]";
|
|
9
|
+
function usageError(message = PR_USAGE) {
|
|
10
|
+
return new CommandError(message, REVIEW_EXIT.usage, "usage");
|
|
11
|
+
}
|
|
12
|
+
export function parsePrArgs(args, defaults = { format: "json" }) {
|
|
13
|
+
const target = [];
|
|
14
|
+
let format = defaults.format;
|
|
15
|
+
let wait = false;
|
|
16
|
+
let afterSha;
|
|
17
|
+
let timeoutMs = REVIEW_POLL_DEFAULT_TIMEOUT_MS;
|
|
18
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
19
|
+
const arg = args[i];
|
|
20
|
+
if (arg === "--json") {
|
|
21
|
+
format = "json";
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
if (arg === "--pretty") {
|
|
25
|
+
format = "pretty";
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
if (arg === "--wait") {
|
|
29
|
+
wait = true;
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
if (arg === "--after-sha" || arg.startsWith("--after-sha=")) {
|
|
33
|
+
const value = arg === "--after-sha" ? args[++i] : arg.slice("--after-sha=".length);
|
|
34
|
+
if (!value?.trim())
|
|
35
|
+
throw usageError("--after-sha requires a non-empty SHA");
|
|
36
|
+
afterSha = value.trim();
|
|
37
|
+
if (afterSha.length < 7) {
|
|
38
|
+
throw usageError("--after-sha must be at least 7 characters");
|
|
39
|
+
}
|
|
40
|
+
if (!/^[0-9a-f]+$/i.test(afterSha)) {
|
|
41
|
+
throw usageError("--after-sha must contain only hexadecimal characters");
|
|
42
|
+
}
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
if (arg === "--timeout" || arg.startsWith("--timeout=")) {
|
|
46
|
+
const value = arg === "--timeout" ? args[++i] : arg.slice("--timeout=".length);
|
|
47
|
+
const seconds = Number(value);
|
|
48
|
+
if (!value || !Number.isFinite(seconds) || seconds <= 0) {
|
|
49
|
+
throw usageError("--timeout must be a positive number of seconds");
|
|
50
|
+
}
|
|
51
|
+
timeoutMs = Math.round(seconds * 1_000);
|
|
52
|
+
wait = true;
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
if (arg.startsWith("-"))
|
|
56
|
+
throw usageError(`unknown flag: ${arg}`);
|
|
57
|
+
target.push(arg);
|
|
58
|
+
}
|
|
59
|
+
if (afterSha && !wait)
|
|
60
|
+
throw usageError("--after-sha requires --wait");
|
|
61
|
+
if (target.length < 1 || target.length > 2)
|
|
62
|
+
throw usageError();
|
|
63
|
+
let parsed;
|
|
64
|
+
try {
|
|
65
|
+
parsed = parseAdoptTarget(target);
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
throw usageError();
|
|
69
|
+
}
|
|
70
|
+
return { ...parsed, format, wait, afterSha, timeoutMs };
|
|
71
|
+
}
|
|
72
|
+
function printPretty(envelope) {
|
|
73
|
+
console.log(`Status: ${envelope.status}`);
|
|
74
|
+
if (envelope.verdict)
|
|
75
|
+
console.log(`Verdict: ${ansi.bold(envelope.verdict)}`);
|
|
76
|
+
if (envelope.head_sha)
|
|
77
|
+
console.log(`Head: ${envelope.head_sha}`);
|
|
78
|
+
console.log(`Finding count: ${envelope.finding_count}`);
|
|
79
|
+
if (envelope.skip_reason)
|
|
80
|
+
console.log(`Skip reason: ${envelope.skip_reason}`);
|
|
81
|
+
if (envelope.summary)
|
|
82
|
+
console.log(envelope.summary);
|
|
83
|
+
renderFindings(envelope.findings, {
|
|
84
|
+
emptyMessage: envelope.status === "completed",
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
export async function cmdPr(args, opts = {}) {
|
|
88
|
+
const parsed = parsePrArgs(args, {
|
|
89
|
+
format: opts.defaultFormat ?? "json",
|
|
90
|
+
});
|
|
91
|
+
const cfg = await loadConfig();
|
|
92
|
+
let envelope;
|
|
93
|
+
if (parsed.wait) {
|
|
94
|
+
try {
|
|
95
|
+
envelope = await pollPrVortexReview(cfg, parsed.owner, parsed.repo, parsed.prNumber, {
|
|
96
|
+
timeoutMs: parsed.timeoutMs,
|
|
97
|
+
intervalMs: opts.pollIntervalMs,
|
|
98
|
+
afterSha: parsed.afterSha,
|
|
99
|
+
signal: opts.signal,
|
|
100
|
+
...opts.poll,
|
|
101
|
+
onTick(kind) {
|
|
102
|
+
if (parsed.format === "json")
|
|
103
|
+
console.error(kind === "retry" ? "?" : ".");
|
|
104
|
+
else
|
|
105
|
+
process.stdout.write(kind === "retry" ? "?" : ".");
|
|
106
|
+
},
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
catch (err) {
|
|
110
|
+
if (err instanceof PrReviewPollTimeoutError) {
|
|
111
|
+
throw new CommandError(err.message, REVIEW_EXIT.timeout, "review_timeout");
|
|
112
|
+
}
|
|
113
|
+
throw err;
|
|
114
|
+
}
|
|
115
|
+
if (parsed.format === "pretty")
|
|
116
|
+
console.log("");
|
|
117
|
+
}
|
|
118
|
+
else {
|
|
119
|
+
envelope = await getPrVortexReview(parsed.owner, parsed.repo, parsed.prNumber, cfg, { signal: opts.signal });
|
|
120
|
+
}
|
|
121
|
+
if (parsed.format === "json") {
|
|
122
|
+
console.log(JSON.stringify(envelope, null, opts.mode === "shell" ? 2 : undefined));
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
125
|
+
printPretty(envelope);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { type MergeQueueEntryDto } from "../api.js";
|
|
2
|
+
import { runLineTabsBrowser } from "../ui/line-tabs.js";
|
|
3
|
+
/** Injectable seams for unit tests. Production callers omit deps. */
|
|
4
|
+
export type QueueCmdDeps = {
|
|
5
|
+
canBrowse?: () => boolean;
|
|
6
|
+
runLineTabsBrowser?: typeof runLineTabsBrowser;
|
|
7
|
+
};
|
|
8
|
+
export declare const QUEUE_USAGE = "usage:\n mergestorm queue [list] [--json]\n mergestorm queue add <stack-id> [--json]\n mergestorm queue rm <entry-id|stack-id> [--json]\n\n queue lists your live merge-queue entries. rm prefers an entry id; a live\n stack id is resolved to its queue entry before cancellation.";
|
|
9
|
+
/** Entry ids win; otherwise a live stack id resolves to its entry id. */
|
|
10
|
+
export declare function resolveQueueEntryId(idOrStackId: string, entries: readonly MergeQueueEntryDto[]): string;
|
|
11
|
+
export declare function buildQueueListLines(entries: readonly MergeQueueEntryDto[], fullStackId?: boolean): string[];
|
|
12
|
+
export declare function cmdQueue(args: string[], deps?: QueueCmdDeps): Promise<void>;
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { cancelMergeQueueEntry, enqueueStack, listMergeQueueEntries, } from "../api.js";
|
|
2
|
+
import { loadConfig } from "../config.js";
|
|
3
|
+
import { CommandError } from "../errors.js";
|
|
4
|
+
import { ansi } from "../ui/ansi.js";
|
|
5
|
+
import { runLineTabsBrowser } from "../ui/line-tabs.js";
|
|
6
|
+
import { present } from "../ui/present.js";
|
|
7
|
+
import { visibleWidth } from "../ui/width.js";
|
|
8
|
+
import { canBrowse } from "./browse.js";
|
|
9
|
+
const QUEUE_STATE_GLYPHS = {
|
|
10
|
+
queued: "○",
|
|
11
|
+
running: "●",
|
|
12
|
+
waiting: "◐",
|
|
13
|
+
bounced: "×",
|
|
14
|
+
landed: "✓",
|
|
15
|
+
cancelled: "·",
|
|
16
|
+
};
|
|
17
|
+
function queueStateGlyph(state) {
|
|
18
|
+
return QUEUE_STATE_GLYPHS[state] ?? "·";
|
|
19
|
+
}
|
|
20
|
+
function colorQueueGlyph(state, glyph) {
|
|
21
|
+
if (state === "running")
|
|
22
|
+
return ansi.brightGreen(glyph);
|
|
23
|
+
if (state === "waiting")
|
|
24
|
+
return ansi.yellow(glyph);
|
|
25
|
+
if (state === "bounced")
|
|
26
|
+
return ansi.red(glyph);
|
|
27
|
+
return ansi.dim(glyph);
|
|
28
|
+
}
|
|
29
|
+
export const QUEUE_USAGE = `usage:
|
|
30
|
+
mergestorm queue [list] [--json]
|
|
31
|
+
mergestorm queue add <stack-id> [--json]
|
|
32
|
+
mergestorm queue rm <entry-id|stack-id> [--json]
|
|
33
|
+
|
|
34
|
+
queue lists your live merge-queue entries. rm prefers an entry id; a live
|
|
35
|
+
stack id is resolved to its queue entry before cancellation.`;
|
|
36
|
+
function positionalArgs(args) {
|
|
37
|
+
const unknown = args.filter((arg) => arg.startsWith("-") &&
|
|
38
|
+
arg !== "--json" &&
|
|
39
|
+
arg !== "-h" &&
|
|
40
|
+
arg !== "--help");
|
|
41
|
+
if (unknown.length > 0) {
|
|
42
|
+
throw new CommandError(`unknown queue option: ${unknown[0]}`, 2, "usage");
|
|
43
|
+
}
|
|
44
|
+
return args.filter((arg) => arg !== "--json");
|
|
45
|
+
}
|
|
46
|
+
function requireIdentifier(value, usage) {
|
|
47
|
+
const id = value?.trim();
|
|
48
|
+
if (!id)
|
|
49
|
+
throw new CommandError(usage, 2, "usage");
|
|
50
|
+
return id;
|
|
51
|
+
}
|
|
52
|
+
/** Entry ids win; otherwise a live stack id resolves to its entry id. */
|
|
53
|
+
export function resolveQueueEntryId(idOrStackId, entries) {
|
|
54
|
+
const entry = entries.find((candidate) => candidate.id === idOrStackId);
|
|
55
|
+
if (entry)
|
|
56
|
+
return entry.id;
|
|
57
|
+
const stackEntry = entries.find((candidate) => candidate.stackId === idOrStackId);
|
|
58
|
+
return stackEntry?.id ?? idOrStackId;
|
|
59
|
+
}
|
|
60
|
+
export function buildQueueListLines(entries, fullStackId = false) {
|
|
61
|
+
if (entries.length === 0) {
|
|
62
|
+
return [
|
|
63
|
+
` ${ansi.bold("Nothing queued")}`,
|
|
64
|
+
"",
|
|
65
|
+
` ${ansi.brightGreen("queue add <stack-id>")} send a stack for verified landing`,
|
|
66
|
+
];
|
|
67
|
+
}
|
|
68
|
+
return entries.map((entry) => {
|
|
69
|
+
const reason = entry.waitReason ?? entry.bounceReason;
|
|
70
|
+
const glyph = colorQueueGlyph(entry.state, queueStateGlyph(entry.state));
|
|
71
|
+
const id = fullStackId ? entry.stackId : entry.stackId.slice(0, 8);
|
|
72
|
+
const core = ` ${String(entry.position).padStart(2)} ` +
|
|
73
|
+
`${glyph} ${entry.state.padEnd(8)} ${entry.owner}/${entry.repo} ${id}` +
|
|
74
|
+
(reason ? ` ${reason}` : "");
|
|
75
|
+
const who = entry.enqueuedBy === "human" || entry.enqueuedBy === "agent"
|
|
76
|
+
? ` by ${entry.enqueuedBy}`
|
|
77
|
+
: "";
|
|
78
|
+
const withWho = `${core}${who}`;
|
|
79
|
+
return visibleWidth(withWho) <= 75 ? withWho : core;
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
async function listQueue(asJson, deps = {}) {
|
|
83
|
+
const cfg = await loadConfig();
|
|
84
|
+
const entries = await listMergeQueueEntries(cfg);
|
|
85
|
+
if (asJson) {
|
|
86
|
+
console.log(JSON.stringify({ entries }, null, 2));
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
const browse = deps.canBrowse ?? canBrowse;
|
|
90
|
+
const browseable = browse();
|
|
91
|
+
const lines = buildQueueListLines(entries, !browseable);
|
|
92
|
+
if (browseable) {
|
|
93
|
+
const runBrowser = deps.runLineTabsBrowser ?? runLineTabsBrowser;
|
|
94
|
+
await runBrowser({
|
|
95
|
+
tabs: [{ id: "queue", label: "Queue", lines }],
|
|
96
|
+
});
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
await present("Merge queue", lines);
|
|
100
|
+
}
|
|
101
|
+
async function addQueue(args, asJson) {
|
|
102
|
+
const id = requireIdentifier(args[0], "usage: mergestorm queue add <stack-id> [--json]");
|
|
103
|
+
if (args.length > 1) {
|
|
104
|
+
throw new CommandError("usage: mergestorm queue add <stack-id> [--json]", 2, "usage");
|
|
105
|
+
}
|
|
106
|
+
const cfg = await loadConfig();
|
|
107
|
+
const body = await enqueueStack(id, cfg);
|
|
108
|
+
if (asJson) {
|
|
109
|
+
console.log(JSON.stringify(body, null, 2));
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
const entry = body?.entry;
|
|
113
|
+
const duplicate = body?.duplicate === true;
|
|
114
|
+
await present("Merge queue", [
|
|
115
|
+
ansi.brightGreen(` ${duplicate ? "Already queued" : "Queued"} ${id}` +
|
|
116
|
+
(entry?.position ? ` at position ${entry.position}` : "")),
|
|
117
|
+
]);
|
|
118
|
+
}
|
|
119
|
+
async function removeQueue(args, asJson) {
|
|
120
|
+
const requested = requireIdentifier(args[0], "usage: mergestorm queue rm <entry-id|stack-id> [--json]");
|
|
121
|
+
if (args.length > 1) {
|
|
122
|
+
throw new CommandError("usage: mergestorm queue rm <entry-id|stack-id> [--json]", 2, "usage");
|
|
123
|
+
}
|
|
124
|
+
const cfg = await loadConfig();
|
|
125
|
+
const entries = await listMergeQueueEntries(cfg);
|
|
126
|
+
const entryId = resolveQueueEntryId(requested, entries);
|
|
127
|
+
const body = await cancelMergeQueueEntry(entryId, cfg);
|
|
128
|
+
if (asJson) {
|
|
129
|
+
console.log(JSON.stringify(body, null, 2));
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
await present("Merge queue", [
|
|
133
|
+
ansi.brightGreen(` Removed ${entryId} from the merge queue`),
|
|
134
|
+
]);
|
|
135
|
+
}
|
|
136
|
+
export async function cmdQueue(args, deps = {}) {
|
|
137
|
+
const asJson = args.includes("--json");
|
|
138
|
+
const positional = positionalArgs(args);
|
|
139
|
+
const sub = positional[0]?.toLowerCase();
|
|
140
|
+
if (sub === "-h" || sub === "--help" || sub === "help") {
|
|
141
|
+
console.log(QUEUE_USAGE);
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
if (!sub || sub === "list" || sub === "ls") {
|
|
145
|
+
if (positional.length > (sub ? 1 : 0)) {
|
|
146
|
+
throw new CommandError(QUEUE_USAGE, 2, "usage");
|
|
147
|
+
}
|
|
148
|
+
await listQueue(asJson, deps);
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
if (sub === "add") {
|
|
152
|
+
await addQueue(positional.slice(1), asJson);
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
if (sub === "rm" || sub === "remove") {
|
|
156
|
+
await removeQueue(positional.slice(1), asJson);
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
throw new CommandError(`${QUEUE_USAGE}\nunknown queue subcommand: ${sub}`);
|
|
160
|
+
}
|
|
@@ -22,7 +22,7 @@ export type RegistryCommand = {
|
|
|
22
22
|
*/
|
|
23
23
|
export declare const COMMAND_REGISTRY: RegistryCommand[];
|
|
24
24
|
export declare function findCommand(name: string): RegistryCommand | undefined;
|
|
25
|
-
/** Autocomplete / `/help` list for the interactive shell. */
|
|
25
|
+
/** Autocomplete / `/help` list for the interactive shell. Aliases fold onto the primary name. */
|
|
26
26
|
export declare function shellCommandSpecs(): CommandSpec[];
|
|
27
27
|
export declare function formatUsageBody(): string;
|
|
28
28
|
/**
|
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
import { DEFAULT_API } from "../config.js";
|
|
2
2
|
import { CommandError } from "../errors.js";
|
|
3
3
|
import { cmdBranches, cmdChain } from "./branches.js";
|
|
4
|
+
import { canBrowse, openTabsBrowser } from "./browse.js";
|
|
4
5
|
import { cmdCredits } from "./credits.js";
|
|
5
6
|
import { cmdJobs, cmdThread } from "./jobs.js";
|
|
6
7
|
import { cmdLogin } from "./login.js";
|
|
7
8
|
import { cmdLogout } from "./logout.js";
|
|
9
|
+
import { cmdQueue } from "./queue.js";
|
|
10
|
+
import { cmdPr } from "./pr.js";
|
|
8
11
|
import { cmdReview } from "./review.js";
|
|
12
|
+
import { cmdSettings } from "./settings.js";
|
|
13
|
+
import { cmdSkill } from "./skill.js";
|
|
9
14
|
import { cmdStack } from "./stack.js";
|
|
10
15
|
import { cmdStatus } from "./status.js";
|
|
11
16
|
import { cmdWhoami } from "./whoami.js";
|
|
@@ -41,7 +46,12 @@ export const COMMAND_REGISTRY = [
|
|
|
41
46
|
name: "review",
|
|
42
47
|
summary: "Review git diff (default origin/HEAD or main or master)",
|
|
43
48
|
usage: [
|
|
44
|
-
"mergestorm review [base] [head] Review git diff base...head
|
|
49
|
+
"mergestorm review [base] [head] Review git diff base...head",
|
|
50
|
+
" [--json] [--wait|--no-wait] [--timeout seconds]",
|
|
51
|
+
" [--router off|standard|max|manual] [--specialists id,id]",
|
|
52
|
+
" [--context text] [--context-file path] [--thread slug]",
|
|
53
|
+
" [--idempotency-key k] [--webhook-url https://…]",
|
|
54
|
+
" HTTP 429 exits 7 (rate_limited) with retry_after_seconds",
|
|
45
55
|
],
|
|
46
56
|
async run(args, ctx) {
|
|
47
57
|
if (ctx.mode === "shell") {
|
|
@@ -51,30 +61,56 @@ export const COMMAND_REGISTRY = [
|
|
|
51
61
|
await cmdReview(args);
|
|
52
62
|
},
|
|
53
63
|
},
|
|
64
|
+
{
|
|
65
|
+
name: "pr",
|
|
66
|
+
summary: "Latest Vortex review for a GitHub PR",
|
|
67
|
+
usage: [
|
|
68
|
+
"mergestorm pr <owner/repo>#<n> Fetch the latest Vortex PR review",
|
|
69
|
+
" [--json] [--wait] [--after-sha <sha>] [--timeout <s>]",
|
|
70
|
+
" Also accepts: mergestorm pr <owner/repo> <n>",
|
|
71
|
+
],
|
|
72
|
+
async run(args, ctx) {
|
|
73
|
+
await cmdPr(args, {
|
|
74
|
+
defaultFormat: ctx.mode === "shell" ? "pretty" : "json",
|
|
75
|
+
signal: ctx.signal,
|
|
76
|
+
mode: ctx.mode,
|
|
77
|
+
});
|
|
78
|
+
},
|
|
79
|
+
},
|
|
54
80
|
{
|
|
55
81
|
name: "status",
|
|
56
|
-
summary: "
|
|
82
|
+
summary: "Job panel (id) or Status / Usage / Jobs tabs",
|
|
57
83
|
usage: [
|
|
58
|
-
"mergestorm status <job_id> Fetch a review job
|
|
84
|
+
"mergestorm status <job_id> Fetch a review job (same envelope as review --json)",
|
|
85
|
+
" [--json] [--wait] [--timeout seconds]",
|
|
86
|
+
" HTTP 429 exits 7 (rate_limited) with retry_after_seconds",
|
|
87
|
+
" With no job id on a TTY: tabbed Status / Usage / Jobs / Config browser",
|
|
59
88
|
],
|
|
60
89
|
async run(args, ctx) {
|
|
61
|
-
if (
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
: "usage: mergestorm status <job_id>");
|
|
90
|
+
if (args.length === 0 && canBrowse()) {
|
|
91
|
+
await openTabsBrowser("status");
|
|
92
|
+
return;
|
|
65
93
|
}
|
|
66
94
|
if (ctx.mode === "shell") {
|
|
67
|
-
await cmdStatus(args
|
|
95
|
+
await cmdStatus(args, {
|
|
96
|
+
defaultFormat: "pretty",
|
|
97
|
+
signal: ctx.signal,
|
|
98
|
+
interactive: true,
|
|
99
|
+
mode: "shell",
|
|
100
|
+
});
|
|
68
101
|
return;
|
|
69
102
|
}
|
|
70
|
-
await cmdStatus(args
|
|
103
|
+
await cmdStatus(args, { defaultFormat: "json" });
|
|
71
104
|
},
|
|
72
105
|
},
|
|
73
106
|
{
|
|
74
107
|
name: "credits",
|
|
75
108
|
aliases: ["usage"],
|
|
76
|
-
summary: "
|
|
77
|
-
usage: [
|
|
109
|
+
summary: "Usage: tabbed browser (TTY) or static panel",
|
|
110
|
+
usage: [
|
|
111
|
+
"mergestorm credits [--json] Usage panel (bar + last 5 jobs)",
|
|
112
|
+
" On a TTY: tabbed Status / Usage / Jobs / Config browser (Usage selected)",
|
|
113
|
+
],
|
|
78
114
|
async run(args) {
|
|
79
115
|
await cmdCredits(args);
|
|
80
116
|
},
|
|
@@ -83,8 +119,14 @@ export const COMMAND_REGISTRY = [
|
|
|
83
119
|
name: "jobs",
|
|
84
120
|
summary: "Recent review jobs (default 10)",
|
|
85
121
|
usage: ["mergestorm jobs [n] [--json] Recent review jobs"],
|
|
86
|
-
async run(args) {
|
|
87
|
-
|
|
122
|
+
async run(args, ctx) {
|
|
123
|
+
if (args.every((a) => a !== "--json") &&
|
|
124
|
+
!args.some((a) => /^\d+$/.test(a)) &&
|
|
125
|
+
canBrowse()) {
|
|
126
|
+
await openTabsBrowser("jobs");
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
await cmdJobs(args, { mode: ctx.mode });
|
|
88
130
|
},
|
|
89
131
|
},
|
|
90
132
|
{
|
|
@@ -92,26 +134,51 @@ export const COMMAND_REGISTRY = [
|
|
|
92
134
|
aliases: ["chains"],
|
|
93
135
|
summary: "Pick a recently reviewed branch",
|
|
94
136
|
usage: ["mergestorm branches [n] Recently reviewed branches (pick on TTY)"],
|
|
95
|
-
async run(args) {
|
|
96
|
-
await cmdBranches(args);
|
|
137
|
+
async run(args, ctx) {
|
|
138
|
+
await cmdBranches(args, { mode: ctx.mode });
|
|
97
139
|
},
|
|
98
140
|
},
|
|
99
141
|
{
|
|
100
142
|
name: "chain",
|
|
101
|
-
summary: "
|
|
143
|
+
summary: "Branch review-chain timeline (holds until q)",
|
|
102
144
|
usage: [
|
|
103
145
|
"mergestorm chain [slug] Branch review-chain timeline (default: current)",
|
|
104
146
|
],
|
|
105
|
-
async run(args) {
|
|
106
|
-
await cmdChain(args);
|
|
147
|
+
async run(args, ctx) {
|
|
148
|
+
await cmdChain(args, { mode: ctx.mode });
|
|
107
149
|
},
|
|
108
150
|
},
|
|
109
151
|
{
|
|
110
152
|
name: "whoami",
|
|
111
|
-
summary: "Key + plan
|
|
153
|
+
summary: "Key + plan (Status tab on a TTY)",
|
|
112
154
|
usage: ["mergestorm whoami [--json] Key + plan + API base"],
|
|
155
|
+
async run(args, ctx) {
|
|
156
|
+
await cmdWhoami(args, { mode: ctx.mode });
|
|
157
|
+
},
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
name: "settings",
|
|
161
|
+
summary: "Automation toggles (Config tab on a TTY)",
|
|
162
|
+
usage: [
|
|
163
|
+
"mergestorm settings [--json] Read the automation toggles (Config tab on a TTY)",
|
|
164
|
+
" [--auto-review on|off] [--auto-patch on|off] [--vortex-thinking on|off]",
|
|
165
|
+
" [--repo-overview on|off] [--review-unit-land on|off]",
|
|
166
|
+
" [--cyclone-review-unit-land on|off] [--vortex-seam on|off]",
|
|
167
|
+
" With flags: PATCH those settings and print the stored result",
|
|
168
|
+
],
|
|
169
|
+
async run(args, ctx) {
|
|
170
|
+
await cmdSettings(args, { mode: ctx.mode });
|
|
171
|
+
},
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
name: "skill",
|
|
175
|
+
summary: "Install mergestorm-review and mergestorm-pr-loop for Claude or Cursor",
|
|
176
|
+
usage: [
|
|
177
|
+
"mergestorm skill install --claude|--cursor Copy the mergestorm-review and mergestorm-pr-loop skills into this repo",
|
|
178
|
+
" [--json]",
|
|
179
|
+
],
|
|
113
180
|
async run(args) {
|
|
114
|
-
await
|
|
181
|
+
await cmdSkill(args);
|
|
115
182
|
},
|
|
116
183
|
},
|
|
117
184
|
{
|
|
@@ -124,21 +191,32 @@ export const COMMAND_REGISTRY = [
|
|
|
124
191
|
? "usage: thread <slug>"
|
|
125
192
|
: "usage: mergestorm thread <slug>");
|
|
126
193
|
}
|
|
127
|
-
await cmdThread(args[0], args.slice(1));
|
|
194
|
+
await cmdThread(args[0], args.slice(1), { mode: ctx.mode });
|
|
195
|
+
},
|
|
196
|
+
},
|
|
197
|
+
{
|
|
198
|
+
name: "queue",
|
|
199
|
+
summary: "Merge queue: list, add a stack, or remove an entry",
|
|
200
|
+
usage: [
|
|
201
|
+
"mergestorm queue [list] [--json] List live merge-queue entries",
|
|
202
|
+
"mergestorm queue add <stack-id> [--json] Queue a stack for verified landing",
|
|
203
|
+
"mergestorm queue rm <entry-id|stack-id> [--json] Remove a live queue entry",
|
|
204
|
+
],
|
|
205
|
+
async run(args) {
|
|
206
|
+
await cmdQueue(args);
|
|
128
207
|
},
|
|
129
208
|
},
|
|
130
209
|
{
|
|
131
210
|
name: "stack",
|
|
132
|
-
summary: "Stacks:
|
|
211
|
+
summary: "Stacks: /stack how-to, list yours, create → submit",
|
|
133
212
|
usage: [
|
|
134
|
-
"mergestorm stack create [name] New local stack layer (
|
|
135
|
-
"mergestorm stack submit Push active local stack, open PRs
|
|
213
|
+
"mergestorm stack create [name] New local stack layer ([--onto] [--trunk] [--extend])",
|
|
214
|
+
"mergestorm stack submit Push active local stack, open PRs ([--extend])",
|
|
136
215
|
"mergestorm stack reset --force Clear this repo's pre-submit authoring state",
|
|
137
216
|
"mergestorm stack list [--json] List registered stacks",
|
|
138
217
|
"mergestorm stack adopt <owner/repo>#<pr> Import an existing open PR chain",
|
|
139
218
|
"mergestorm stack restack <stack-id> Restack stack descendants",
|
|
140
219
|
"mergestorm stack land <stack-id> Land bottom PR (or promote into unit)",
|
|
141
|
-
"mergestorm stack auto-promote on|off <stack-id> Toggle auto-promote when green",
|
|
142
220
|
],
|
|
143
221
|
async run(args) {
|
|
144
222
|
await cmdStack(args);
|
|
@@ -157,7 +235,7 @@ export const COMMAND_REGISTRY = [
|
|
|
157
235
|
];
|
|
158
236
|
/** Shell meta commands (not dispatched via {@link COMMAND_REGISTRY}). */
|
|
159
237
|
const SHELL_META = [
|
|
160
|
-
{ name: "help", summary: "
|
|
238
|
+
{ name: "help", summary: "Tabbed help (Start / Commands / Stacks)" },
|
|
161
239
|
{ name: "clear", summary: "Clear screen and reprint banner" },
|
|
162
240
|
{ name: "exit", summary: "Leave the shell" },
|
|
163
241
|
{ name: "quit", summary: "Leave the shell" },
|
|
@@ -168,18 +246,22 @@ export function findCommand(name) {
|
|
|
168
246
|
const key = name.toLowerCase();
|
|
169
247
|
return COMMAND_REGISTRY.find((c) => c.name === key || (c.aliases?.includes(key) ?? false));
|
|
170
248
|
}
|
|
171
|
-
/** Autocomplete / `/help` list for the interactive shell. */
|
|
249
|
+
/** Autocomplete / `/help` list for the interactive shell. Aliases fold onto the primary name. */
|
|
172
250
|
export function shellCommandSpecs() {
|
|
173
|
-
const specs = [
|
|
251
|
+
const specs = [
|
|
252
|
+
{ name: "help", summary: SHELL_META[0].summary },
|
|
253
|
+
{ name: "exit", summary: SHELL_META[2].summary, aliases: ["quit"] },
|
|
254
|
+
];
|
|
174
255
|
for (const c of COMMAND_REGISTRY) {
|
|
175
256
|
if (SHELL_OMIT.has(c.name))
|
|
176
257
|
continue;
|
|
177
|
-
specs.push({
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
258
|
+
specs.push({
|
|
259
|
+
name: c.name,
|
|
260
|
+
summary: c.summary,
|
|
261
|
+
...(c.aliases?.length ? { aliases: c.aliases } : {}),
|
|
262
|
+
});
|
|
181
263
|
}
|
|
182
|
-
specs.push({ name: "clear", summary: SHELL_META[1].summary }
|
|
264
|
+
specs.push({ name: "clear", summary: SHELL_META[1].summary });
|
|
183
265
|
return specs;
|
|
184
266
|
}
|
|
185
267
|
export function formatUsageBody() {
|