mergestorm 0.3.13 → 0.3.16
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 +16 -17
- package/dist/api.d.ts +103 -6
- package/dist/api.js +195 -9
- package/dist/automation-catalog.d.ts +37 -0
- package/dist/automation-catalog.js +13 -0
- package/dist/client/index.d.ts +3 -2
- package/dist/client/index.js +3 -2
- 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 +252 -0
- package/dist/commands/credits.js +7 -10
- 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 +4 -3
- 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.js +98 -21
- package/dist/commands/review-client.d.ts +13 -0
- package/dist/commands/review-client.js +110 -0
- package/dist/commands/review.d.ts +6 -0
- package/dist/commands/review.js +18 -7
- package/dist/commands/settings.d.ts +17 -0
- package/dist/commands/settings.js +82 -0
- package/dist/commands/skill.d.ts +20 -0
- package/dist/commands/skill.js +145 -0
- package/dist/commands/stack.d.ts +57 -2
- package/dist/commands/stack.js +352 -87
- package/dist/commands/status.d.ts +2 -0
- package/dist/commands/status.js +30 -15
- package/dist/commands/whoami.d.ts +3 -1
- package/dist/commands/whoami.js +21 -7
- package/dist/errors.d.ts +1 -1
- package/dist/repo-identity.d.ts +1 -1
- package/dist/repo-identity.js +13 -2
- package/dist/shell.js +39 -31
- package/dist/stack-dto.d.ts +92 -1
- package/dist/stack-meta.d.ts +12 -0
- package/dist/stack-meta.js +32 -3
- 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 +166 -79
- package/dist/ui/findings.d.ts +2 -1
- package/dist/ui/findings.js +13 -7
- 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 +58 -1
- package/dist/ui/prompt.js +225 -42
- 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 +4 -0
- package/dist/ui/usage.js +22 -2
- package/package.json +2 -1
- package/skill/SKILL.md +98 -0
- package/skill/mergestorm-pr-loop/SKILL.md +84 -0
package/dist/commands/status.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { getReview } from "../api.js";
|
|
2
2
|
import { loadConfig } from "../config.js";
|
|
3
|
+
import { showLinePanel } from "../ui/line-tabs.js";
|
|
4
|
+
import { canBrowse, formatJobDetailLines } from "./browse.js";
|
|
3
5
|
import { CommandError, DetachedError, REVIEW_EXIT, isCommandErrorCode, } from "../errors.js";
|
|
4
6
|
import { ansi } from "../ui/ansi.js";
|
|
5
7
|
import { renderFindings } from "../ui/findings.js";
|
|
@@ -77,6 +79,13 @@ function printPretty(row) {
|
|
|
77
79
|
emptyMessage: envelope.status === "completed",
|
|
78
80
|
});
|
|
79
81
|
}
|
|
82
|
+
async function showPretty(row) {
|
|
83
|
+
if (canBrowse()) {
|
|
84
|
+
await showLinePanel("Job", formatJobDetailLines(row));
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
printPretty(row);
|
|
88
|
+
}
|
|
80
89
|
export async function cmdStatus(args, opts = {}) {
|
|
81
90
|
const parsed = parseStatusArgs(args, {
|
|
82
91
|
format: opts.defaultFormat ?? "json",
|
|
@@ -85,8 +94,14 @@ export async function cmdStatus(args, opts = {}) {
|
|
|
85
94
|
const interactive = Boolean(opts.interactive && !json);
|
|
86
95
|
const fallback = { jobId: parsed.jobId };
|
|
87
96
|
let jsonPrinted = false;
|
|
88
|
-
const printJson = (row, extra = {}) => {
|
|
89
|
-
|
|
97
|
+
const printJson = async (row, extra = {}) => {
|
|
98
|
+
const text = JSON.stringify(toReviewJobEnvelope(row, { ...fallback, ...extra }), null, opts.mode === "shell" ? 2 : undefined);
|
|
99
|
+
if (opts.mode === "shell") {
|
|
100
|
+
await showLinePanel("Job", text.split("\n"));
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
console.log(text);
|
|
104
|
+
}
|
|
90
105
|
jsonPrinted = true;
|
|
91
106
|
};
|
|
92
107
|
try {
|
|
@@ -105,12 +120,12 @@ export async function cmdStatus(args, opts = {}) {
|
|
|
105
120
|
throw err;
|
|
106
121
|
if (isCommandErrorCode(err, "api_timeout")) {
|
|
107
122
|
if (json)
|
|
108
|
-
printJson(null, { status: "in_progress", error: err.message });
|
|
123
|
+
await printJson(null, { status: "in_progress", error: err.message });
|
|
109
124
|
throw new CommandError(err.message, REVIEW_EXIT.timeout, "review_timeout");
|
|
110
125
|
}
|
|
111
126
|
if (isCommandErrorCode(err, "rate_limited")) {
|
|
112
127
|
if (json) {
|
|
113
|
-
printJson(null, {
|
|
128
|
+
await printJson(null, {
|
|
114
129
|
status: "rate_limited",
|
|
115
130
|
error: err.message,
|
|
116
131
|
retryAfterSeconds: err.retryAfterSeconds,
|
|
@@ -129,15 +144,15 @@ export async function cmdStatus(args, opts = {}) {
|
|
|
129
144
|
: { job_id: parsed.jobId, status: "failed", error: "Invalid review response" };
|
|
130
145
|
if (row.status === "failed" || row.status === "quota_exceeded") {
|
|
131
146
|
if (json)
|
|
132
|
-
printJson(row);
|
|
147
|
+
await printJson(row);
|
|
133
148
|
else
|
|
134
|
-
|
|
149
|
+
await showPretty(row);
|
|
135
150
|
throw new CommandError(`Review ${row.status}: ${row.error ?? ""}`, row.status === "quota_exceeded" ? REVIEW_EXIT.quota : REVIEW_EXIT.failed, row.status === "quota_exceeded" ? "review_quota" : "review_failed");
|
|
136
151
|
}
|
|
137
152
|
if (json)
|
|
138
|
-
printJson(row);
|
|
153
|
+
await printJson(row);
|
|
139
154
|
else
|
|
140
|
-
|
|
155
|
+
await showPretty(row);
|
|
141
156
|
return;
|
|
142
157
|
}
|
|
143
158
|
if (!json) {
|
|
@@ -171,7 +186,7 @@ export async function cmdStatus(args, opts = {}) {
|
|
|
171
186
|
if (err instanceof ReviewPollTimeoutError) {
|
|
172
187
|
const message = `Timed out waiting for review ${parsed.jobId}`;
|
|
173
188
|
if (json) {
|
|
174
|
-
printJson(err.lastRow, {
|
|
189
|
+
await printJson(err.lastRow, {
|
|
175
190
|
jobId: parsed.jobId,
|
|
176
191
|
status: err.lastRow.status ?? "in_progress",
|
|
177
192
|
error: "Timed out waiting for review.",
|
|
@@ -181,7 +196,7 @@ export async function cmdStatus(args, opts = {}) {
|
|
|
181
196
|
}
|
|
182
197
|
if (isCommandErrorCode(err, "rate_limited")) {
|
|
183
198
|
if (json) {
|
|
184
|
-
printJson(null, {
|
|
199
|
+
await printJson(null, {
|
|
185
200
|
status: "rate_limited",
|
|
186
201
|
error: err.message,
|
|
187
202
|
retryAfterSeconds: err.retryAfterSeconds,
|
|
@@ -200,15 +215,15 @@ export async function cmdStatus(args, opts = {}) {
|
|
|
200
215
|
if (row.status === "failed" || row.status === "quota_exceeded") {
|
|
201
216
|
const message = `Review ${row.status}: ${row.error ?? ""}`;
|
|
202
217
|
if (json)
|
|
203
|
-
printJson(row);
|
|
218
|
+
await printJson(row);
|
|
204
219
|
else
|
|
205
|
-
|
|
220
|
+
await showPretty(row);
|
|
206
221
|
throw new CommandError(message, row.status === "quota_exceeded" ? REVIEW_EXIT.quota : REVIEW_EXIT.failed, row.status === "quota_exceeded" ? "review_quota" : "review_failed");
|
|
207
222
|
}
|
|
208
223
|
if (json)
|
|
209
|
-
printJson(row);
|
|
224
|
+
await printJson(row);
|
|
210
225
|
else
|
|
211
|
-
|
|
226
|
+
await showPretty(row);
|
|
212
227
|
}
|
|
213
228
|
catch (err) {
|
|
214
229
|
if (err instanceof DetachedError)
|
|
@@ -217,7 +232,7 @@ export async function cmdStatus(args, opts = {}) {
|
|
|
217
232
|
!jsonPrinted &&
|
|
218
233
|
!(err instanceof Error && err.name === "AbortError")) {
|
|
219
234
|
const message = err instanceof Error ? err.message : String(err);
|
|
220
|
-
printJson(null, { status: "failed", error: message });
|
|
235
|
+
await printJson(null, { status: "failed", error: message });
|
|
221
236
|
}
|
|
222
237
|
if (err instanceof CommandError &&
|
|
223
238
|
(err.code === "auth_invalid" || err.code === "missing_api_key")) {
|
package/dist/commands/whoami.js
CHANGED
|
@@ -2,13 +2,19 @@ import { getMe } from "../api.js";
|
|
|
2
2
|
import { apiBase, configPath, loadConfig, resolveApiKey, keyDisplay } from "../config.js";
|
|
3
3
|
import { CommandError } from "../errors.js";
|
|
4
4
|
import { ansi } from "../ui/ansi.js";
|
|
5
|
-
|
|
5
|
+
import { present } from "../ui/present.js";
|
|
6
|
+
import { canBrowse, openTabsBrowser } from "./browse.js";
|
|
7
|
+
export async function cmdWhoami(args, opts = {}) {
|
|
6
8
|
const asJson = args.includes("--json");
|
|
7
9
|
const cfg = await loadConfig();
|
|
8
10
|
const key = resolveApiKey(cfg);
|
|
9
11
|
if (!key) {
|
|
10
12
|
throw new CommandError("Not logged in. Run `mergestorm login`.");
|
|
11
13
|
}
|
|
14
|
+
if (!asJson && canBrowse()) {
|
|
15
|
+
await openTabsBrowser("status");
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
12
18
|
const me = await getMe(cfg);
|
|
13
19
|
const payload = {
|
|
14
20
|
key_prefix: me?.key.prefix ?? keyDisplay(key),
|
|
@@ -20,16 +26,24 @@ export async function cmdWhoami(args) {
|
|
|
20
26
|
usage: me?.usage ?? null,
|
|
21
27
|
};
|
|
22
28
|
if (asJson) {
|
|
23
|
-
|
|
29
|
+
const text = JSON.stringify(payload, null, 2);
|
|
30
|
+
if (opts.mode === "shell") {
|
|
31
|
+
await present("Account", text.split("\n"));
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
console.log(text);
|
|
24
35
|
return;
|
|
25
36
|
}
|
|
26
|
-
|
|
37
|
+
const lines = [
|
|
38
|
+
` Key ${ansi.bold(String(payload.key_prefix))}${payload.key_name ? ` (${payload.key_name})` : ""}`,
|
|
39
|
+
];
|
|
27
40
|
if (payload.plan_key) {
|
|
28
|
-
|
|
41
|
+
lines.push(` Plan ${payload.plan_label_key ?? payload.plan_key}`);
|
|
29
42
|
}
|
|
30
|
-
|
|
31
|
-
|
|
43
|
+
lines.push(` API ${payload.api_base}`);
|
|
44
|
+
lines.push(` Config ${payload.config_path}`);
|
|
32
45
|
if (!me) {
|
|
33
|
-
|
|
46
|
+
lines.push(ansi.dim(" (live account details unavailable — older API or offline)"));
|
|
34
47
|
}
|
|
48
|
+
await present("Account", lines);
|
|
35
49
|
}
|
package/dist/errors.d.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Stable machine codes for CommandError control flow.
|
|
3
3
|
* UX copy in `message` may change freely; match on `code`, never on message text.
|
|
4
4
|
*/
|
|
5
|
-
export type CommandErrorCode = "auth_invalid" | "api_timeout" | "not_a_repo" | "missing_api_key" | "registered_parent" | "usage" | "review_quota" | "review_failed" | "review_timeout" | "rate_limited";
|
|
5
|
+
export type CommandErrorCode = "auth_invalid" | "api_timeout" | "not_a_repo" | "not_found" | "missing_api_key" | "registered_parent" | "usage" | "review_quota" | "review_failed" | "review_timeout" | "rate_limited";
|
|
6
6
|
/** Stable one-shot exit codes for machine callers of `mergestorm review`. */
|
|
7
7
|
export declare const REVIEW_EXIT: {
|
|
8
8
|
readonly success: 0;
|
package/dist/repo-identity.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ export declare const LEGACY_STACK_META_REL: string;
|
|
|
2
2
|
export declare function gitTopLevel(cwd?: string): string;
|
|
3
3
|
export declare function gitCommonDir(cwd?: string): string;
|
|
4
4
|
/**
|
|
5
|
-
*
|
|
5
|
+
* Each worktree has its own authoring state.
|
|
6
6
|
* Independent clones remain isolated even when they point at the same remote.
|
|
7
7
|
*/
|
|
8
8
|
export declare function stableRepoId(cwd?: string): string;
|
package/dist/repo-identity.js
CHANGED
|
@@ -41,11 +41,22 @@ export function gitCommonDir(cwd = process.cwd()) {
|
|
|
41
41
|
}
|
|
42
42
|
}
|
|
43
43
|
/**
|
|
44
|
-
*
|
|
44
|
+
* Each worktree has its own authoring state.
|
|
45
45
|
* Independent clones remain isolated even when they point at the same remote.
|
|
46
46
|
*/
|
|
47
47
|
export function stableRepoId(cwd = process.cwd()) {
|
|
48
|
-
|
|
48
|
+
let raw;
|
|
49
|
+
try {
|
|
50
|
+
raw = git(["rev-parse", "--git-dir"], cwd).trim();
|
|
51
|
+
}
|
|
52
|
+
catch (err) {
|
|
53
|
+
rethrowIfNotARepo(err);
|
|
54
|
+
}
|
|
55
|
+
const absolute = path.isAbsolute(raw) ? raw : path.resolve(cwd, raw);
|
|
56
|
+
return createHash("sha256")
|
|
57
|
+
.update(realpathSync(absolute))
|
|
58
|
+
.digest("hex")
|
|
59
|
+
.slice(0, 20);
|
|
49
60
|
}
|
|
50
61
|
export function cliStacksRoot() {
|
|
51
62
|
return path.join(mergestormHome(), "stacks");
|
package/dist/shell.js
CHANGED
|
@@ -5,21 +5,15 @@ import { dispatchCommand, shellCommandSpecs, } from "./commands/registry.js";
|
|
|
5
5
|
import { ansi } from "./ui/ansi.js";
|
|
6
6
|
import { printBannerHeader, shellPrompt } from "./ui/banner.js";
|
|
7
7
|
import { CTRL_C_EXIT_HINT, CtrlCExitGate } from "./ui/ctrl-c-exit.js";
|
|
8
|
-
import {
|
|
8
|
+
import { openHelpBrowser } from "./commands/help.js";
|
|
9
|
+
import { showLinePanel } from "./ui/line-tabs.js";
|
|
10
|
+
import { askLine, PromptClosedError } from "./ui/prompt.js";
|
|
11
|
+
async function showShellNotice(title, message) {
|
|
12
|
+
const lines = message.split("\n").flatMap((line) => (line ? [` ${line}`] : [""]));
|
|
13
|
+
await showLinePanel(title, [...lines, "", ` ${ansi.dim("q to return")}`]);
|
|
14
|
+
}
|
|
9
15
|
/** Shell autocomplete + `/help` � derived from the command registry (STRUCT-01). */
|
|
10
16
|
export const COMMANDS = shellCommandSpecs();
|
|
11
|
-
function printHelp() {
|
|
12
|
-
const rows = COMMANDS.map((c) => {
|
|
13
|
-
const label = formatSlashLabel(c).slice(1);
|
|
14
|
-
return ` ${ansi.green("/" + label.padEnd(20))} ${c.summary}`;
|
|
15
|
-
}).join("\n");
|
|
16
|
-
console.log(`
|
|
17
|
-
${ansi.bold("Commands")}
|
|
18
|
-
${rows}
|
|
19
|
-
|
|
20
|
-
Dashboard: https://mergestorm.ai/settings#api
|
|
21
|
-
`);
|
|
22
|
-
}
|
|
23
17
|
/** Strips an optional leading "/" so both "review" and "/review" work. */
|
|
24
18
|
export function parseLine(line) {
|
|
25
19
|
let trimmed = line.trim();
|
|
@@ -34,10 +28,22 @@ export function parseLine(line) {
|
|
|
34
28
|
return { cmd, args: parts.slice(1) };
|
|
35
29
|
}
|
|
36
30
|
export async function runShell() {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
31
|
+
// Idle is always the home screen: header + docked prompt. Command TUIs
|
|
32
|
+
// (usage / credits / status) and printed output become scrollback when
|
|
33
|
+
// the next askLine homes + ED 0. Do not retire the header after the
|
|
34
|
+
// first command � that is what floated a bare prompt at the top.
|
|
35
|
+
let banner = null;
|
|
36
|
+
const showBanner = async () => {
|
|
37
|
+
try {
|
|
38
|
+
banner = await printBannerHeader({ paint: false });
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
// Keep the previous chrome on a transient refresh failure rather than
|
|
42
|
+
// blanking the always-on home header.
|
|
43
|
+
}
|
|
44
|
+
return banner;
|
|
45
|
+
};
|
|
46
|
+
banner = await showBanner();
|
|
41
47
|
const history = [];
|
|
42
48
|
let busyAbort = null;
|
|
43
49
|
const idleCtrlC = new CtrlCExitGate();
|
|
@@ -71,6 +77,11 @@ export async function runShell() {
|
|
|
71
77
|
prompt: shellPrompt(loggedIn),
|
|
72
78
|
history,
|
|
73
79
|
commands: COMMANDS,
|
|
80
|
+
...(banner
|
|
81
|
+
? {
|
|
82
|
+
header: (columns, variant) => banner.rows(columns, variant),
|
|
83
|
+
}
|
|
84
|
+
: {}),
|
|
74
85
|
});
|
|
75
86
|
}
|
|
76
87
|
catch (err) {
|
|
@@ -90,15 +101,12 @@ export async function runShell() {
|
|
|
90
101
|
if (cmd === "exit" || cmd === "quit")
|
|
91
102
|
break;
|
|
92
103
|
if (cmd === "help" || cmd === "?") {
|
|
93
|
-
|
|
104
|
+
await openHelpBrowser();
|
|
94
105
|
continue;
|
|
95
106
|
}
|
|
96
107
|
if (cmd === "clear") {
|
|
97
108
|
output.write("[2J[H");
|
|
98
|
-
|
|
99
|
-
await printBannerHeader();
|
|
100
|
-
}
|
|
101
|
-
catch { /* banner is best-effort */ }
|
|
109
|
+
await showBanner();
|
|
102
110
|
continue;
|
|
103
111
|
}
|
|
104
112
|
busyAbort = new AbortController();
|
|
@@ -107,33 +115,33 @@ export async function runShell() {
|
|
|
107
115
|
mode: "shell",
|
|
108
116
|
signal: busyAbort.signal,
|
|
109
117
|
afterAuth: async () => {
|
|
110
|
-
|
|
111
|
-
await printBannerHeader();
|
|
112
|
-
}
|
|
113
|
-
catch { /* banner is best-effort */ }
|
|
118
|
+
await showBanner();
|
|
114
119
|
},
|
|
115
120
|
});
|
|
116
121
|
if (!ok) {
|
|
117
|
-
|
|
122
|
+
await showShellNotice("Error", `unknown command "${cmd}"\ntype help for the list`);
|
|
118
123
|
}
|
|
119
124
|
}
|
|
120
125
|
catch (err) {
|
|
121
126
|
if (err instanceof DetachedError) {
|
|
122
|
-
|
|
127
|
+
await showShellNotice("Detached", err.message);
|
|
123
128
|
}
|
|
124
129
|
else if (err instanceof CommandError) {
|
|
125
|
-
|
|
130
|
+
await showShellNotice("Error", err.message);
|
|
126
131
|
}
|
|
127
132
|
else if (err instanceof Error && err.name === "AbortError") {
|
|
128
|
-
|
|
133
|
+
await showShellNotice("Aborted", "aborted");
|
|
129
134
|
}
|
|
130
135
|
else {
|
|
131
|
-
|
|
136
|
+
await showShellNotice("Error", err instanceof Error ? err.message : String(err));
|
|
132
137
|
}
|
|
133
138
|
}
|
|
134
139
|
finally {
|
|
135
140
|
busyAbort = null;
|
|
136
141
|
}
|
|
142
|
+
// Re-capture the home header so usage/credits shown above the prompt
|
|
143
|
+
// reflect what the command just consumed (e.g. review spending a credit).
|
|
144
|
+
await showBanner();
|
|
137
145
|
}
|
|
138
146
|
}
|
|
139
147
|
finally {
|
package/dist/stack-dto.d.ts
CHANGED
|
@@ -45,6 +45,25 @@ export type StackLayerChecks = {
|
|
|
45
45
|
pending: number;
|
|
46
46
|
failure: number;
|
|
47
47
|
failingName: string | null;
|
|
48
|
+
/**
|
|
49
|
+
* True when the persisted `ci_checks` carries a `namedRuns` seed (enrich
|
|
50
|
+
* writes it so CI webhook deliveries fold into the snapshot). Pre-seeding
|
|
51
|
+
* rows are false — the webhook cannot fold into them, so the interval poll
|
|
52
|
+
* must keep enriching until the first enrich seeds one (#1602). Status-only
|
|
53
|
+
* layers (empty `namedRuns` over a non-empty rollup) are also false — the
|
|
54
|
+
* webhook refuses to fold into an empty map, so the poll keeps enriching
|
|
55
|
+
* them (#1602).
|
|
56
|
+
*/
|
|
57
|
+
namedRunsSeeded?: boolean;
|
|
58
|
+
/**
|
|
59
|
+
* True when the persisted `namedRuns` carries the reserved synthetic
|
|
60
|
+
* "commit status" run at a pending/failure verdict. Webhook deliveries
|
|
61
|
+
* re-key by check/context name and can never advance that seed, so the
|
|
62
|
+
* interval poll must keep enriching until the next enrich re-seeds it green
|
|
63
|
+
* (or drops it). Absent (false) when the seed is green — folds keep that
|
|
64
|
+
* layer fresh (#1602).
|
|
65
|
+
*/
|
|
66
|
+
combinedStatusSeeded?: boolean;
|
|
48
67
|
};
|
|
49
68
|
/** Closing / Development-sidebar issue. Cap 3 on the wire. */
|
|
50
69
|
export type StackLinkedIssue = {
|
|
@@ -70,6 +89,14 @@ export type StackLayerDto = {
|
|
|
70
89
|
branch: string;
|
|
71
90
|
parentBranch: string | null;
|
|
72
91
|
prNumber: number;
|
|
92
|
+
/** Persisted PR timing and diff snapshots from pr_stats. */
|
|
93
|
+
openedAt: string | null;
|
|
94
|
+
mergedAt: string | null;
|
|
95
|
+
closedAt: string | null;
|
|
96
|
+
additions: number | null;
|
|
97
|
+
deletions: number | null;
|
|
98
|
+
openAdditions: number | null;
|
|
99
|
+
openDeletions: number | null;
|
|
73
100
|
position: number;
|
|
74
101
|
state: StackBranchState;
|
|
75
102
|
title: string | null;
|
|
@@ -96,6 +123,12 @@ export type StackLayerDto = {
|
|
|
96
123
|
mergeable: boolean | null;
|
|
97
124
|
/** GitHub REST mergeable_state; null when unknown. */
|
|
98
125
|
mergeableState: string | null;
|
|
126
|
+
/** Most recently observed PR head SHA. */
|
|
127
|
+
headSha?: string | null;
|
|
128
|
+
/** Head SHA that produced mergeable / mergeableState. */
|
|
129
|
+
mergeableHeadSha?: string | null;
|
|
130
|
+
/** GitHub PR draft flag from enrich; the auto-land watcher never enqueues a draft. */
|
|
131
|
+
draft?: boolean | null;
|
|
99
132
|
/**
|
|
100
133
|
* Vortex-style linked issues (keywords + Development sidebar), cap 3.
|
|
101
134
|
* null until first enrich finds any; kept on merged unit members (unlike CI).
|
|
@@ -121,6 +154,14 @@ export type StackLayerDto = {
|
|
|
121
154
|
};
|
|
122
155
|
export type StackUnitMemberDto = {
|
|
123
156
|
prNumber: number;
|
|
157
|
+
/** Persisted PR timing and diff snapshots from pr_stats. */
|
|
158
|
+
openedAt: string | null;
|
|
159
|
+
mergedAt: string | null;
|
|
160
|
+
closedAt: string | null;
|
|
161
|
+
additions: number | null;
|
|
162
|
+
deletions: number | null;
|
|
163
|
+
openAdditions: number | null;
|
|
164
|
+
openDeletions: number | null;
|
|
124
165
|
branch: string;
|
|
125
166
|
position: number;
|
|
126
167
|
seamState: string;
|
|
@@ -173,13 +214,63 @@ export type StackDto = {
|
|
|
173
214
|
trunkBranch: string;
|
|
174
215
|
/** Real trunk the review unit eventually lands into. */
|
|
175
216
|
landTarget: string;
|
|
176
|
-
autoPromoteWhenGreen: boolean;
|
|
177
217
|
/**
|
|
178
218
|
* Dashboard-only archive (#963): set hides the stack from the active Work
|
|
179
219
|
* list; GitHub PRs stay open. Only PATCH /stacks/:id toggles it.
|
|
180
220
|
*/
|
|
181
221
|
archivedAt: string | null;
|
|
222
|
+
/**
|
|
223
|
+
* Auto land (#1511): enqueue this unit-less single-PR stack when eligible.
|
|
224
|
+
* Missing on older payloads is off.
|
|
225
|
+
*/
|
|
226
|
+
autoEnqueueWhenReady?: boolean;
|
|
227
|
+
/**
|
|
228
|
+
* Per-stack Vortex auto-review override. `null` (or missing) follows the
|
|
229
|
+
* account `auto_review_enabled` flag; a boolean wins in both directions.
|
|
230
|
+
*/
|
|
231
|
+
autoReviewOverride?: boolean | null;
|
|
232
|
+
/**
|
|
233
|
+
* Per-stack Cyclone auto-patch override. `null` (or missing) follows the
|
|
234
|
+
* account `auto_patch_enabled` flag; a boolean wins in both directions.
|
|
235
|
+
*/
|
|
236
|
+
autoPatchOverride?: boolean | null;
|
|
182
237
|
layers: StackLayerDto[];
|
|
183
238
|
/** Present when a review-unit row exists for this stack. */
|
|
184
239
|
unit?: StackUnitDto;
|
|
185
240
|
};
|
|
241
|
+
export type MergeQueueEntryState = "queued" | "running" | "waiting" | "landed" | "bounced" | "cancelled";
|
|
242
|
+
export type MergeQueueEnqueuedBy = "human" | "agent";
|
|
243
|
+
export type MergeQueueEnqueuedVia = "dashboard" | "cli" | "chat" | "api" | "mcp";
|
|
244
|
+
export type MergeQueueBounceKind = "ci_failure" | "ci_timeout" | "head_moved" | "tempest_findings" | "seam_findings" | "restack_conflict" | "pr_draft" | "merge_failed" | "gh_error";
|
|
245
|
+
/** Structured bounce handoff — enough for the bar chip and the chat quote. */
|
|
246
|
+
export type MergeQueueBounceDetail = {
|
|
247
|
+
kind: MergeQueueBounceKind;
|
|
248
|
+
prNumber?: number;
|
|
249
|
+
headSha?: string;
|
|
250
|
+
failingCheck?: string;
|
|
251
|
+
conflictBranch?: string;
|
|
252
|
+
conflictDetail?: string;
|
|
253
|
+
message?: string;
|
|
254
|
+
};
|
|
255
|
+
export type MergeQueueEntryDto = {
|
|
256
|
+
id: string;
|
|
257
|
+
stackId: string;
|
|
258
|
+
owner: string;
|
|
259
|
+
repo: string;
|
|
260
|
+
state: MergeQueueEntryState;
|
|
261
|
+
/** 1-based FIFO among this user's live entries; `0` on bounced history rows (MQ-7). */
|
|
262
|
+
position: number;
|
|
263
|
+
waitReason: string | null;
|
|
264
|
+
bounceReason: string | null;
|
|
265
|
+
bounceDetail: MergeQueueBounceDetail | null;
|
|
266
|
+
enqueuedBy: MergeQueueEnqueuedBy;
|
|
267
|
+
enqueuedVia: MergeQueueEnqueuedVia;
|
|
268
|
+
enqueuedAt: string;
|
|
269
|
+
attempts: number;
|
|
270
|
+
landedPrNumbers: number[];
|
|
271
|
+
/** Combined PR head the queue verified. Null until the worker records it. */
|
|
272
|
+
verifyHeadSha: string | null;
|
|
273
|
+
/** Target tip included in the verified PR head. Null until recorded. */
|
|
274
|
+
verifyBaseSha: string | null;
|
|
275
|
+
finishedAt: string | null;
|
|
276
|
+
};
|
package/dist/stack-meta.d.ts
CHANGED
|
@@ -5,6 +5,10 @@ export type StackLayerMeta = {
|
|
|
5
5
|
};
|
|
6
6
|
export type LocalStack = {
|
|
7
7
|
layers: StackLayerMeta[];
|
|
8
|
+
autoEnqueueWhenReady?: boolean;
|
|
9
|
+
/** Per-open override carried from `stack create` to `stack submit`; absent = account flag. */
|
|
10
|
+
autoReviewOverride?: boolean;
|
|
11
|
+
autoPatchOverride?: boolean;
|
|
8
12
|
};
|
|
9
13
|
export type StackMeta = {
|
|
10
14
|
version: 2;
|
|
@@ -20,6 +24,14 @@ export declare function loadStackMeta(cwd?: string, stacksRoot?: string): Promis
|
|
|
20
24
|
export declare function emptyStackMeta(trunk: string): StackMeta;
|
|
21
25
|
export declare function activeStack(meta: StackMeta): LocalStack;
|
|
22
26
|
export declare function activeLayers(meta: StackMeta): StackLayerMeta[];
|
|
27
|
+
export declare function setActiveAutoEnqueue(meta: StackMeta, enabled: boolean | undefined): StackMeta;
|
|
28
|
+
/** Per-open automation flags remembered on the active local stack until submit. */
|
|
29
|
+
export type LocalStackPolicy = {
|
|
30
|
+
autoEnqueueWhenReady?: boolean;
|
|
31
|
+
autoReviewOverride?: boolean;
|
|
32
|
+
autoPatchOverride?: boolean;
|
|
33
|
+
};
|
|
34
|
+
export declare function setActivePolicy(meta: StackMeta, policy: LocalStackPolicy): StackMeta;
|
|
23
35
|
export declare function saveStackMeta(meta: StackMeta, cwd?: string, stacksRoot?: string): Promise<void>;
|
|
24
36
|
/** Explicit recovery. Does not touch branches, PRs, or registered server stacks. */
|
|
25
37
|
export declare function resetStackMeta(cwd?: string, stacksRoot?: string): Promise<void>;
|
package/dist/stack-meta.js
CHANGED
|
@@ -8,6 +8,11 @@ import path from "node:path";
|
|
|
8
8
|
import { CommandError, isCommandErrorCode } from "./errors.js";
|
|
9
9
|
import { git } from "./git.js";
|
|
10
10
|
import { cliStacksRoot, gitTopLevel, LEGACY_STACK_META_REL, legacyStackMetaPath, repoStateDir, stackMetaPath as resolveStackMetaPath, } from "./repo-identity.js";
|
|
11
|
+
const LOCAL_STACK_FLAGS = [
|
|
12
|
+
"autoEnqueueWhenReady",
|
|
13
|
+
"autoReviewOverride",
|
|
14
|
+
"autoPatchOverride",
|
|
15
|
+
];
|
|
11
16
|
/** Exported only for legacy migration/tests. New state never uses this path. */
|
|
12
17
|
export const STACK_META_REL = LEGACY_STACK_META_REL;
|
|
13
18
|
export function stackMetaPath(cwd = process.cwd(), stacksRoot = cliStacksRoot()) {
|
|
@@ -35,10 +40,18 @@ function normalizeMeta(raw) {
|
|
|
35
40
|
if (!stack ||
|
|
36
41
|
typeof stack !== "object" ||
|
|
37
42
|
!Array.isArray(stack.layers) ||
|
|
38
|
-
!stack.layers.every(isLayer)
|
|
43
|
+
!stack.layers.every(isLayer) ||
|
|
44
|
+
LOCAL_STACK_FLAGS.some((flag) => stack[flag] !== undefined &&
|
|
45
|
+
typeof stack[flag] !== "boolean")) {
|
|
39
46
|
throw new Error("Stack authoring state has a malformed stack entry; run `mg stack reset --force` to clear it.");
|
|
40
47
|
}
|
|
41
|
-
|
|
48
|
+
const entry = { layers: stack.layers };
|
|
49
|
+
for (const flag of LOCAL_STACK_FLAGS) {
|
|
50
|
+
const value = stack[flag];
|
|
51
|
+
if (typeof value === "boolean")
|
|
52
|
+
entry[flag] = value;
|
|
53
|
+
}
|
|
54
|
+
stacks.push(entry);
|
|
42
55
|
}
|
|
43
56
|
const active = obj.active;
|
|
44
57
|
if (typeof active !== "number" ||
|
|
@@ -165,6 +178,22 @@ export function activeStack(meta) {
|
|
|
165
178
|
export function activeLayers(meta) {
|
|
166
179
|
return activeStack(meta).layers;
|
|
167
180
|
}
|
|
181
|
+
export function setActiveAutoEnqueue(meta, enabled) {
|
|
182
|
+
return setActiveStackFlag(meta, "autoEnqueueWhenReady", enabled);
|
|
183
|
+
}
|
|
184
|
+
export function setActivePolicy(meta, policy) {
|
|
185
|
+
let next = meta;
|
|
186
|
+
for (const flag of LOCAL_STACK_FLAGS) {
|
|
187
|
+
next = setActiveStackFlag(next, flag, policy[flag]);
|
|
188
|
+
}
|
|
189
|
+
return next;
|
|
190
|
+
}
|
|
191
|
+
function setActiveStackFlag(meta, flag, value) {
|
|
192
|
+
if (value === undefined)
|
|
193
|
+
return meta;
|
|
194
|
+
const stacks = meta.stacks.map((stack, index) => index === meta.active ? { ...stack, [flag]: value } : stack);
|
|
195
|
+
return { ...meta, stacks };
|
|
196
|
+
}
|
|
168
197
|
export async function saveStackMeta(meta, cwd = process.cwd(), stacksRoot = cliStacksRoot()) {
|
|
169
198
|
const file = stackMetaPath(cwd, stacksRoot);
|
|
170
199
|
const dir = path.dirname(file);
|
|
@@ -229,7 +258,7 @@ export function appendLayer(meta, layer) {
|
|
|
229
258
|
}
|
|
230
259
|
}
|
|
231
260
|
const stacks = meta.stacks.map((stack, index) => index === meta.active
|
|
232
|
-
? { layers: [...stack.layers, layer] }
|
|
261
|
+
? { ...stack, layers: [...stack.layers, layer] }
|
|
233
262
|
: stack);
|
|
234
263
|
return { ...meta, stacks };
|
|
235
264
|
}
|
package/dist/ui/ansi.d.ts
CHANGED
package/dist/ui/ansi.js
CHANGED
|
@@ -24,4 +24,6 @@ export const ansi = {
|
|
|
24
24
|
brightGreen: (t) => wrap("92", t),
|
|
25
25
|
invert: (t) => wrap("7", t),
|
|
26
26
|
boldGreen: (t) => (computeEnabled() ? `[1;32m${t}[0m` : t),
|
|
27
|
+
/** Prompt / mark ink. Never 32 — that reads as sick dark green. */
|
|
28
|
+
boldBrightGreen: (t) => (computeEnabled() ? `[1;92m${t}[0m` : t),
|
|
27
29
|
};
|
package/dist/ui/banner.d.ts
CHANGED
|
@@ -1,4 +1,36 @@
|
|
|
1
|
+
import { type MeResponse } from "../api.js";
|
|
1
2
|
/** Used by the non-interactive `usage()` path in cli.ts — the boxed banner is shell-only. */
|
|
2
3
|
export declare function printWordmark(): void;
|
|
3
|
-
|
|
4
|
-
export
|
|
4
|
+
/** Everything the welcome panel needs, captured once so redraws stay sync. */
|
|
5
|
+
export type BannerState = {
|
|
6
|
+
version: string;
|
|
7
|
+
me: MeResponse | null;
|
|
8
|
+
key: string | undefined;
|
|
9
|
+
keyInvalid: boolean;
|
|
10
|
+
};
|
|
11
|
+
/** Full welcome, mid pane, or Claude-style short chrome that still shows the mark. */
|
|
12
|
+
export type BannerDensity = "full" | "compact" | "mini" | "nano";
|
|
13
|
+
/** `true` = compact. Strings pick the short-pane densities. */
|
|
14
|
+
export type BannerVariant = boolean | "mini" | "nano";
|
|
15
|
+
/** Handle returned by {@link printBannerHeader}; `rows` reflows for a resize. */
|
|
16
|
+
export type BannerHandle = {
|
|
17
|
+
rows(columns?: number, variant?: BannerVariant): string[];
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* The whole welcome panel as rows, reflowed to `columns`. Every row fits in
|
|
21
|
+
* `columns - 1` cells so a redraw's cursor math stays honest; below
|
|
22
|
+
* MIN_BOXED_COLUMNS the frame drops away instead of wrapping.
|
|
23
|
+
*/
|
|
24
|
+
export declare function buildBannerRows(state: BannerState, columns?: number, opts?: {
|
|
25
|
+
compact?: boolean;
|
|
26
|
+
density?: BannerDensity;
|
|
27
|
+
}): string[];
|
|
28
|
+
/**
|
|
29
|
+
* Print the welcome panel and return a handle whose `rows()` reflows the
|
|
30
|
+
* same data for a new width (used by the prompt's resize redraw).
|
|
31
|
+
*/
|
|
32
|
+
export declare function printBannerHeader(opts?: {
|
|
33
|
+
/** When false, only build the handle; the prompt paints the rows. */
|
|
34
|
+
paint?: boolean;
|
|
35
|
+
}): Promise<BannerHandle>;
|
|
36
|
+
export declare function shellPrompt(_loggedIn: boolean): string;
|