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/stack.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { adoptStack, ensureUpperPark, landNextStack, listStacks, restackStack,
|
|
1
|
+
import { adoptStack, ensureUpperPark, landNextStack, listStacks, restackStack, setStackPolicy, } from "../api.js";
|
|
2
2
|
import { loadConfig } from "../config.js";
|
|
3
3
|
import { CommandError } from "../errors.js";
|
|
4
4
|
import { createPr, findOpenPrNumber, requireGh } from "../gh.js";
|
|
@@ -6,19 +6,26 @@ import { parseGithubOriginRepo } from "../git.js";
|
|
|
6
6
|
import { branchExists, commitsAheadOf, createBranchFromHead, currentBranch, defaultLayerBranchName, deleteBranch, discoverTrunk, fetchRemoteBranch, pushBranch, tipCommitMessage, tipCommitSubject, worktreeDirty, } from "../git-stack.js";
|
|
7
7
|
import { buildPrBodyFromCommit } from "../pr-body.js";
|
|
8
8
|
import { gitTopLevel } from "../repo-identity.js";
|
|
9
|
-
import { activeLayers, appendLayer, emptyStackMeta, loadStackMeta, removeActiveStack, resetStackMeta, saveStackMeta, selectStackForCreate, } from "../stack-meta.js";
|
|
9
|
+
import { activeLayers, appendLayer, emptyStackMeta, loadStackMeta, removeActiveStack, resetStackMeta, saveStackMeta, selectStackForCreate, setActivePolicy, } from "../stack-meta.js";
|
|
10
10
|
import { isMgParkBranch, planSubmitLayerBases } from "../submit-pr-base.js";
|
|
11
11
|
import { ansi } from "../ui/ansi.js";
|
|
12
|
+
import { runLineTabsBrowser } from "../ui/line-tabs.js";
|
|
13
|
+
import { present } from "../ui/present.js";
|
|
14
|
+
import { canBrowse } from "./browse.js";
|
|
15
|
+
import { openHelpBrowser } from "./help.js";
|
|
12
16
|
const STACK_USAGE = `usage:
|
|
13
|
-
mergestorm stack create [name] [--onto <branch>] [--trunk <branch>] [--extend] [--json]
|
|
14
|
-
mergestorm stack submit [--extend] [--json]
|
|
17
|
+
mergestorm stack create [name] [--onto <branch>] [--trunk <branch>] [--extend] [--auto-land on|off] [--auto-review on|off] [--auto-patch on|off] [--json]
|
|
18
|
+
mergestorm stack submit [--extend] [--auto-land on|off] [--auto-review on|off] [--auto-patch on|off] [--json]
|
|
15
19
|
mergestorm stack reset --force
|
|
16
20
|
mergestorm stack list [--json]
|
|
17
|
-
mergestorm stack
|
|
21
|
+
mergestorm stack set <stack-id> [--auto-land on|off] [--auto-review on|off|default] [--auto-patch on|off|default] [--json]
|
|
22
|
+
mergestorm stack adopt <owner/repo>#<pr> [--auto-land on|off] [--auto-review on|off] [--auto-patch on|off] [--json]
|
|
18
23
|
mergestorm stack restack <stack-id> [--json]
|
|
19
24
|
mergestorm stack land <stack-id> [--json]
|
|
20
|
-
mergestorm stack auto-promote on|off <stack-id> [--json]
|
|
21
25
|
|
|
26
|
+
--auto-review / --auto-patch pin Vortex auto-review / Cyclone auto-patch for
|
|
27
|
+
one stack in either direction. Absent, or \`default\` on stack set, follows
|
|
28
|
+
the account setting.
|
|
22
29
|
stack submit opens PRs with a body generated from each layer tip commit.
|
|
23
30
|
Layers 1–2 use the git parent as GitHub base. Layer 3+ opens onto the
|
|
24
31
|
review-unit park freeze (mg-park-*) so adopt does not retarget after open.
|
|
@@ -97,10 +104,51 @@ export function parseAdoptTarget(args) {
|
|
|
97
104
|
}
|
|
98
105
|
throw new CommandError(`usage: mergestorm stack adopt <owner/repo>#<pr>`);
|
|
99
106
|
}
|
|
107
|
+
const stackCmd = (name, blurb) => ` ${ansi.brightGreen(name.padEnd(26))}${blurb}`;
|
|
108
|
+
/** Empty + populated bodies for the TTY `stack list` panel. */
|
|
109
|
+
export function buildStackListLines(stacks) {
|
|
110
|
+
if (stacks.length === 0) {
|
|
111
|
+
return [
|
|
112
|
+
` ${ansi.bold("None yet")}`,
|
|
113
|
+
" No registered stacks for this key.",
|
|
114
|
+
"",
|
|
115
|
+
` ${ansi.bold("Author one")}`,
|
|
116
|
+
stackCmd("stack create", "new layer on this branch"),
|
|
117
|
+
" commit, then stack submit",
|
|
118
|
+
"",
|
|
119
|
+
` ${ansi.bold("Or import")}`,
|
|
120
|
+
stackCmd("stack adopt org/repo#12", "open GitHub chain"),
|
|
121
|
+
"",
|
|
122
|
+
ansi.dim(" Dashboard: https://mergestorm.ai/dashboard"),
|
|
123
|
+
];
|
|
124
|
+
}
|
|
125
|
+
const lines = [];
|
|
126
|
+
for (const s of stacks) {
|
|
127
|
+
lines.push(...formatStackHuman(s));
|
|
128
|
+
lines.push("");
|
|
129
|
+
}
|
|
130
|
+
lines.push(ansi.dim(" create · submit · land <id> · q to close"));
|
|
131
|
+
return lines;
|
|
132
|
+
}
|
|
133
|
+
/** Short per-stack policy suffixes: Auto land when on, overrides when pinned. */
|
|
134
|
+
export function stackPolicyLabels(s) {
|
|
135
|
+
const labels = [];
|
|
136
|
+
if (s.autoEnqueueWhenReady)
|
|
137
|
+
labels.push("auto-land on");
|
|
138
|
+
if (typeof s.autoReviewOverride === "boolean") {
|
|
139
|
+
labels.push(`auto-review ${s.autoReviewOverride ? "on" : "off"}`);
|
|
140
|
+
}
|
|
141
|
+
if (typeof s.autoPatchOverride === "boolean") {
|
|
142
|
+
labels.push(`auto-patch ${s.autoPatchOverride ? "on" : "off"}`);
|
|
143
|
+
}
|
|
144
|
+
return labels;
|
|
145
|
+
}
|
|
100
146
|
function formatStackHuman(s) {
|
|
101
147
|
const lines = [];
|
|
102
|
-
|
|
103
|
-
|
|
148
|
+
lines.push(` ${ansi.bold(`${s.owner}/${s.repo}`)} trunk=${s.trunkBranch}` +
|
|
149
|
+
stackPolicyLabels(s)
|
|
150
|
+
.map((label) => ` ${label}`)
|
|
151
|
+
.join(""));
|
|
104
152
|
lines.push(` ${ansi.dim(s.id)}`);
|
|
105
153
|
if (s.layers.length === 0) {
|
|
106
154
|
lines.push(ansi.dim(" (no layers)"));
|
|
@@ -113,12 +161,45 @@ function formatStackHuman(s) {
|
|
|
113
161
|
}
|
|
114
162
|
return lines;
|
|
115
163
|
}
|
|
164
|
+
const OPEN_POLICY_FLAGS = [
|
|
165
|
+
{ flag: "--auto-land", key: "autoLand" },
|
|
166
|
+
{ flag: "--auto-review", key: "autoReview" },
|
|
167
|
+
{ flag: "--auto-patch", key: "autoPatch" },
|
|
168
|
+
];
|
|
169
|
+
/**
|
|
170
|
+
* Consume one `--auto-* on|off` flag at `args[i]` (space or `=` form). Returns
|
|
171
|
+
* the next index to scan, or null when `args[i]` is not a policy flag.
|
|
172
|
+
*/
|
|
173
|
+
function takeOpenPolicyFlag(args, i, into, verb) {
|
|
174
|
+
const a = args[i];
|
|
175
|
+
for (const { flag, key } of OPEN_POLICY_FLAGS) {
|
|
176
|
+
if (a === flag) {
|
|
177
|
+
into[key] = parseOnOff(args[i + 1], flag, verb);
|
|
178
|
+
return i + 2;
|
|
179
|
+
}
|
|
180
|
+
if (a.startsWith(`${flag}=`)) {
|
|
181
|
+
into[key] = parseOnOff(a.slice(flag.length + 1), flag, verb);
|
|
182
|
+
return i + 1;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
return null;
|
|
186
|
+
}
|
|
187
|
+
/** Wire policy for adopt from the parsed `on|off` flags; absent keys stay off the body. */
|
|
188
|
+
export function openPolicyPatch(flags) {
|
|
189
|
+
const patch = {
|
|
190
|
+
...(typeof flags.autoLand === "boolean" ? { autoEnqueueWhenReady: flags.autoLand } : {}),
|
|
191
|
+
...(typeof flags.autoReview === "boolean" ? { autoReviewOverride: flags.autoReview } : {}),
|
|
192
|
+
...(typeof flags.autoPatch === "boolean" ? { autoPatchOverride: flags.autoPatch } : {}),
|
|
193
|
+
};
|
|
194
|
+
return Object.keys(patch).length > 0 ? patch : undefined;
|
|
195
|
+
}
|
|
116
196
|
/** Parse `stack create` argv into options. Exported for tests. */
|
|
117
197
|
export function parseStackCreateArgs(args) {
|
|
118
198
|
let name;
|
|
119
199
|
let onto;
|
|
120
200
|
let trunk;
|
|
121
201
|
let extend = false;
|
|
202
|
+
const policy = {};
|
|
122
203
|
let asJson = false;
|
|
123
204
|
for (let i = 0; i < args.length; i++) {
|
|
124
205
|
const a = args[i];
|
|
@@ -130,6 +211,11 @@ export function parseStackCreateArgs(args) {
|
|
|
130
211
|
extend = true;
|
|
131
212
|
continue;
|
|
132
213
|
}
|
|
214
|
+
const next = takeOpenPolicyFlag(args, i, policy, "stack create");
|
|
215
|
+
if (next !== null) {
|
|
216
|
+
i = next - 1;
|
|
217
|
+
continue;
|
|
218
|
+
}
|
|
133
219
|
if (a === "--onto") {
|
|
134
220
|
const v = args[++i];
|
|
135
221
|
if (!v) {
|
|
@@ -162,13 +248,24 @@ export function parseStackCreateArgs(args) {
|
|
|
162
248
|
}
|
|
163
249
|
name = a;
|
|
164
250
|
}
|
|
165
|
-
return {
|
|
251
|
+
return {
|
|
252
|
+
name,
|
|
253
|
+
onto,
|
|
254
|
+
trunk,
|
|
255
|
+
extend,
|
|
256
|
+
autoLand: policy.autoLand,
|
|
257
|
+
autoReview: policy.autoReview,
|
|
258
|
+
autoPatch: policy.autoPatch,
|
|
259
|
+
asJson,
|
|
260
|
+
};
|
|
166
261
|
}
|
|
167
262
|
/** Parse `stack submit` argv. Exported for tests. */
|
|
168
263
|
export function parseStackSubmitArgs(args) {
|
|
169
264
|
let extend = false;
|
|
265
|
+
const policy = {};
|
|
170
266
|
let asJson = false;
|
|
171
|
-
for (
|
|
267
|
+
for (let i = 0; i < args.length; i++) {
|
|
268
|
+
const a = args[i];
|
|
172
269
|
if (a === "--json") {
|
|
173
270
|
asJson = true;
|
|
174
271
|
continue;
|
|
@@ -177,43 +274,159 @@ export function parseStackSubmitArgs(args) {
|
|
|
177
274
|
extend = true;
|
|
178
275
|
continue;
|
|
179
276
|
}
|
|
180
|
-
|
|
277
|
+
const next = takeOpenPolicyFlag(args, i, policy, "stack submit");
|
|
278
|
+
if (next !== null) {
|
|
279
|
+
i = next - 1;
|
|
280
|
+
continue;
|
|
281
|
+
}
|
|
282
|
+
throw new CommandError("usage: mergestorm stack submit [--extend] [--auto-land on|off] [--auto-review on|off] [--auto-patch on|off] [--json]");
|
|
283
|
+
}
|
|
284
|
+
return {
|
|
285
|
+
extend,
|
|
286
|
+
autoLand: policy.autoLand,
|
|
287
|
+
autoReview: policy.autoReview,
|
|
288
|
+
autoPatch: policy.autoPatch,
|
|
289
|
+
asJson,
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
function parseOnOff(raw, flag, verb) {
|
|
293
|
+
if (raw === "on")
|
|
294
|
+
return true;
|
|
295
|
+
if (raw === "off")
|
|
296
|
+
return false;
|
|
297
|
+
throw new CommandError(`usage: mergestorm ${verb} [${flag} on|off]`);
|
|
298
|
+
}
|
|
299
|
+
/** `stack set` overrides are tri-state: `default` writes null (follow the account flag). */
|
|
300
|
+
function parseOnOffDefault(raw, flag, verb) {
|
|
301
|
+
if (raw === "on")
|
|
302
|
+
return true;
|
|
303
|
+
if (raw === "off")
|
|
304
|
+
return false;
|
|
305
|
+
if (raw === "default")
|
|
306
|
+
return null;
|
|
307
|
+
throw new CommandError(`usage: mergestorm ${verb} [${flag} on|off|default]`);
|
|
308
|
+
}
|
|
309
|
+
export function parseStackAdoptArgs(args) {
|
|
310
|
+
const targetArgs = [];
|
|
311
|
+
const policy = {};
|
|
312
|
+
let asJson = false;
|
|
313
|
+
for (let i = 0; i < args.length; i++) {
|
|
314
|
+
const a = args[i];
|
|
315
|
+
if (a === "--json") {
|
|
316
|
+
asJson = true;
|
|
317
|
+
continue;
|
|
318
|
+
}
|
|
319
|
+
const next = takeOpenPolicyFlag(args, i, policy, "stack adopt <owner/repo>#<pr>");
|
|
320
|
+
if (next !== null) {
|
|
321
|
+
i = next - 1;
|
|
322
|
+
continue;
|
|
323
|
+
}
|
|
324
|
+
if (a.startsWith("-")) {
|
|
325
|
+
throw new CommandError(`${STACK_USAGE}\nunknown flag: ${a}`);
|
|
326
|
+
}
|
|
327
|
+
targetArgs.push(a);
|
|
328
|
+
}
|
|
329
|
+
const target = parseAdoptTarget(targetArgs);
|
|
330
|
+
return {
|
|
331
|
+
...target,
|
|
332
|
+
autoLand: policy.autoLand,
|
|
333
|
+
autoReview: policy.autoReview,
|
|
334
|
+
autoPatch: policy.autoPatch,
|
|
335
|
+
asJson,
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
export function parseStackSetArgs(args) {
|
|
339
|
+
const usage = "usage: mergestorm stack set <stack-id> [--auto-land on|off] [--auto-review on|off|default] [--auto-patch on|off|default] [--json]";
|
|
340
|
+
const verb = "stack set <stack-id>";
|
|
341
|
+
let stackId;
|
|
342
|
+
let autoLand;
|
|
343
|
+
let autoReview;
|
|
344
|
+
let autoPatch;
|
|
345
|
+
let asJson = false;
|
|
346
|
+
for (let i = 0; i < args.length; i++) {
|
|
347
|
+
const arg = args[i];
|
|
348
|
+
if (arg === "--json") {
|
|
349
|
+
asJson = true;
|
|
350
|
+
}
|
|
351
|
+
else if (arg === "--auto-land") {
|
|
352
|
+
autoLand = parseOnOff(args[++i], "--auto-land", verb);
|
|
353
|
+
}
|
|
354
|
+
else if (arg.startsWith("--auto-land=")) {
|
|
355
|
+
autoLand = parseOnOff(arg.slice("--auto-land=".length), "--auto-land", verb);
|
|
356
|
+
}
|
|
357
|
+
else if (arg === "--auto-review") {
|
|
358
|
+
autoReview = parseOnOffDefault(args[++i], "--auto-review", verb);
|
|
359
|
+
}
|
|
360
|
+
else if (arg.startsWith("--auto-review=")) {
|
|
361
|
+
autoReview = parseOnOffDefault(arg.slice("--auto-review=".length), "--auto-review", verb);
|
|
362
|
+
}
|
|
363
|
+
else if (arg === "--auto-patch") {
|
|
364
|
+
autoPatch = parseOnOffDefault(args[++i], "--auto-patch", verb);
|
|
365
|
+
}
|
|
366
|
+
else if (arg.startsWith("--auto-patch=")) {
|
|
367
|
+
autoPatch = parseOnOffDefault(arg.slice("--auto-patch=".length), "--auto-patch", verb);
|
|
368
|
+
}
|
|
369
|
+
else if (arg.startsWith("-") || stackId !== undefined) {
|
|
370
|
+
throw new CommandError(usage);
|
|
371
|
+
}
|
|
372
|
+
else {
|
|
373
|
+
stackId = arg;
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
if (autoLand === undefined && autoReview === undefined && autoPatch === undefined) {
|
|
377
|
+
throw new CommandError(usage);
|
|
181
378
|
}
|
|
182
|
-
return {
|
|
379
|
+
return {
|
|
380
|
+
stackId: requireStackId(stackId, usage),
|
|
381
|
+
...(autoLand !== undefined ? { autoLand } : {}),
|
|
382
|
+
...(autoReview !== undefined ? { autoReview } : {}),
|
|
383
|
+
...(autoPatch !== undefined ? { autoPatch } : {}),
|
|
384
|
+
asJson,
|
|
385
|
+
};
|
|
183
386
|
}
|
|
184
387
|
export function parseStackResetArgs(args) {
|
|
185
388
|
if (args.length !== 1 || args[0] !== "--force") {
|
|
186
389
|
throw new CommandError("usage: mergestorm stack reset --force\nClears only CLI-managed pre-submit state; branches, PRs, and registered stacks are unchanged.");
|
|
187
390
|
}
|
|
188
391
|
}
|
|
189
|
-
async function cmdStackCreate(args, deps = {}) {
|
|
190
|
-
const { name, onto, trunk: trunkFlag, extend, asJson } = parseStackCreateArgs(args);
|
|
392
|
+
export async function cmdStackCreate(args, deps = {}) {
|
|
393
|
+
const { name, onto, trunk: trunkFlag, extend, autoLand, autoReview, autoPatch, asJson, } = parseStackCreateArgs(args);
|
|
191
394
|
const cwd = deps.cwd ?? process.cwd();
|
|
395
|
+
const gitTopLevelFn = deps.gitTopLevel ?? gitTopLevel;
|
|
396
|
+
const worktreeDirtyFn = deps.worktreeDirty ?? worktreeDirty;
|
|
397
|
+
const currentBranchFn = deps.currentBranch ?? currentBranch;
|
|
398
|
+
const branchExistsFn = deps.branchExists ?? branchExists;
|
|
399
|
+
const createBranchFromHeadFn = deps.createBranchFromHead ?? createBranchFromHead;
|
|
400
|
+
const deleteBranchFn = deps.deleteBranch ?? deleteBranch;
|
|
401
|
+
const discoverTrunkFn = deps.discoverTrunk ?? discoverTrunk;
|
|
402
|
+
const defaultLayerBranchNameFn = deps.defaultLayerBranchName ?? defaultLayerBranchName;
|
|
403
|
+
const loadStackMetaFn = deps.loadStackMeta ?? loadStackMeta;
|
|
404
|
+
const saveStackMetaFn = deps.saveStackMeta ?? saveStackMeta;
|
|
192
405
|
const listStacksFn = deps.listStacks ?? listStacks;
|
|
193
406
|
const loadConfigFn = deps.loadConfig ?? loadConfig;
|
|
194
407
|
const parseOriginFn = deps.parseGithubOriginRepo ?? parseGithubOriginRepo;
|
|
195
408
|
try {
|
|
196
|
-
|
|
409
|
+
gitTopLevelFn(cwd);
|
|
197
410
|
}
|
|
198
411
|
catch {
|
|
199
412
|
throw new CommandError("Not in a git repository. Run `mg stack create` from inside the repository whose stack you want to author.");
|
|
200
413
|
}
|
|
201
|
-
if (
|
|
414
|
+
if (worktreeDirtyFn(cwd)) {
|
|
202
415
|
throw new CommandError("Working tree is dirty. Commit or stash changes before `stack create`.");
|
|
203
416
|
}
|
|
204
|
-
const parentBranch = onto?.trim() ||
|
|
417
|
+
const parentBranch = onto?.trim() || currentBranchFn(cwd);
|
|
205
418
|
if (!parentBranch) {
|
|
206
419
|
throw new CommandError("Detached HEAD. Check out a branch (or pass --onto <branch>) before `stack create`.");
|
|
207
420
|
}
|
|
208
|
-
if (!
|
|
421
|
+
if (!branchExistsFn(parentBranch, cwd)) {
|
|
209
422
|
throw new CommandError(`Parent branch not found: ${parentBranch}`);
|
|
210
423
|
}
|
|
211
|
-
const branch =
|
|
212
|
-
if (
|
|
424
|
+
const branch = defaultLayerBranchNameFn(name);
|
|
425
|
+
if (branchExistsFn(branch, cwd)) {
|
|
213
426
|
throw new CommandError(`Branch already exists: ${branch}`);
|
|
214
427
|
}
|
|
215
|
-
let meta = await
|
|
216
|
-
const trunk = trunkFlag?.trim() || meta?.trunk ||
|
|
428
|
+
let meta = await loadStackMetaFn(cwd);
|
|
429
|
+
const trunk = trunkFlag?.trim() || meta?.trunk || discoverTrunkFn(cwd);
|
|
217
430
|
if (!meta) {
|
|
218
431
|
meta = emptyStackMeta(trunk);
|
|
219
432
|
}
|
|
@@ -253,23 +466,28 @@ async function cmdStackCreate(args, deps = {}) {
|
|
|
253
466
|
}
|
|
254
467
|
try {
|
|
255
468
|
meta = selectStackForCreate(meta, parentBranch);
|
|
469
|
+
meta = setActivePolicy(meta, {
|
|
470
|
+
autoEnqueueWhenReady: autoLand,
|
|
471
|
+
autoReviewOverride: autoReview,
|
|
472
|
+
autoPatchOverride: autoPatch,
|
|
473
|
+
});
|
|
256
474
|
}
|
|
257
475
|
catch (err) {
|
|
258
476
|
throw new CommandError(err instanceof Error ? err.message : String(err));
|
|
259
477
|
}
|
|
260
478
|
try {
|
|
261
|
-
|
|
479
|
+
createBranchFromHeadFn(branch, cwd);
|
|
262
480
|
}
|
|
263
481
|
catch (err) {
|
|
264
482
|
throw new CommandError(err instanceof Error ? err.message : String(err));
|
|
265
483
|
}
|
|
266
484
|
try {
|
|
267
485
|
meta = appendLayer(meta, { branch, parentBranch });
|
|
268
|
-
await
|
|
486
|
+
await saveStackMetaFn(meta, cwd);
|
|
269
487
|
}
|
|
270
488
|
catch (err) {
|
|
271
489
|
try {
|
|
272
|
-
|
|
490
|
+
deleteBranchFn(branch, cwd);
|
|
273
491
|
}
|
|
274
492
|
catch { /* rollback best-effort */ }
|
|
275
493
|
throw new CommandError(err instanceof Error ? err.message : String(err));
|
|
@@ -288,16 +506,19 @@ async function cmdStackCreate(args, deps = {}) {
|
|
|
288
506
|
}, null, 2));
|
|
289
507
|
return;
|
|
290
508
|
}
|
|
291
|
-
|
|
509
|
+
const created = [
|
|
510
|
+
ansi.brightGreen(` Created ${branch} (onto ${parentBranch})`),
|
|
511
|
+
];
|
|
292
512
|
if (registeredHit) {
|
|
293
|
-
|
|
513
|
+
created.push(ansi.dim(` extending registered stack ${registeredHit.stackId}` +
|
|
294
514
|
(registeredHit.prNumber > 0 ? ` (from #${registeredHit.prNumber})` : "")));
|
|
295
515
|
}
|
|
296
|
-
|
|
297
|
-
|
|
516
|
+
created.push(ansi.dim(` trunk: ${meta.trunk} · local stacks: ${meta.stacks.length} · active layers: ${layers.length}`));
|
|
517
|
+
created.push(ansi.dim(" authoring state: managed automatically outside the repository"));
|
|
518
|
+
await present("Stack", created);
|
|
298
519
|
}
|
|
299
520
|
export async function cmdStackSubmit(args, deps = {}) {
|
|
300
|
-
const { extend, asJson } = parseStackSubmitArgs(args);
|
|
521
|
+
const { extend, autoLand, autoReview, autoPatch, asJson } = parseStackSubmitArgs(args);
|
|
301
522
|
const cwd = deps.cwd ?? process.cwd();
|
|
302
523
|
const gitTopLevelFn = deps.gitTopLevel ?? gitTopLevel;
|
|
303
524
|
const loadStackMetaFn = deps.loadStackMeta ?? loadStackMeta;
|
|
@@ -368,6 +589,21 @@ export async function cmdStackSubmit(args, deps = {}) {
|
|
|
368
589
|
owner,
|
|
369
590
|
repo,
|
|
370
591
|
});
|
|
592
|
+
// Explicit submit flags win; otherwise a fresh (unregistered) stack carries
|
|
593
|
+
// the choices remembered at `stack create`. Joining a registered stack
|
|
594
|
+
// never replays stored create-time flags onto it.
|
|
595
|
+
const stored = meta.stacks[meta.active];
|
|
596
|
+
const freshStack = plans.every((plan) => plan.stackId === null);
|
|
597
|
+
const pick = (flag, remembered) => typeof flag === "boolean"
|
|
598
|
+
? flag
|
|
599
|
+
: freshStack && typeof remembered === "boolean"
|
|
600
|
+
? remembered
|
|
601
|
+
: undefined;
|
|
602
|
+
const policy = openPolicyPatch({
|
|
603
|
+
autoLand: pick(autoLand, stored?.autoEnqueueWhenReady),
|
|
604
|
+
autoReview: pick(autoReview, stored?.autoReviewOverride),
|
|
605
|
+
autoPatch: pick(autoPatch, stored?.autoPatchOverride),
|
|
606
|
+
});
|
|
371
607
|
for (const layer of layers) {
|
|
372
608
|
if (!branchExistsFn(layer.branch, cwd)) {
|
|
373
609
|
throw new CommandError(`Stack layer branch missing locally: ${layer.branch}`);
|
|
@@ -444,7 +680,7 @@ export async function cmdStackSubmit(args, deps = {}) {
|
|
|
444
680
|
let stackId = plans.find((plan) => plan.stackId)?.stackId ?? null;
|
|
445
681
|
const registerPr = async (prNumber) => {
|
|
446
682
|
console.log(ansi.dim(` Registering stack via import (${owner}/${repo}#${prNumber}) …`));
|
|
447
|
-
const body = await adoptStackFn(owner, repo, prNumber, cfg);
|
|
683
|
+
const body = await adoptStackFn(owner, repo, prNumber, cfg, policy);
|
|
448
684
|
if (typeof body !== "object" || body === null) {
|
|
449
685
|
throw new CommandError("Unexpected response shape from adopt API");
|
|
450
686
|
}
|
|
@@ -495,22 +731,26 @@ export async function cmdStackSubmit(args, deps = {}) {
|
|
|
495
731
|
}, null, 2));
|
|
496
732
|
return;
|
|
497
733
|
}
|
|
734
|
+
const summary = [];
|
|
498
735
|
for (const row of opened) {
|
|
499
736
|
const verb = row.created ? "opened" : "exists";
|
|
500
|
-
|
|
737
|
+
summary.push(ansi.brightGreen(` #${row.prNumber} ${row.branch} → ${row.base} (${verb})`));
|
|
501
738
|
}
|
|
502
739
|
if (stackId) {
|
|
503
|
-
|
|
740
|
+
summary.push(ansi.brightGreen(` Registered stack ${stackId}`));
|
|
504
741
|
}
|
|
505
742
|
else {
|
|
506
|
-
|
|
743
|
+
summary.push(ansi.dim(" Stack registration returned no stack ID"));
|
|
507
744
|
}
|
|
508
|
-
|
|
745
|
+
summary.push(ansi.dim(" Dashboard: https://mergestorm.ai/dashboard"));
|
|
746
|
+
await present("Stack", summary);
|
|
509
747
|
}
|
|
510
748
|
async function cmdStackReset(args) {
|
|
511
749
|
parseStackResetArgs(args);
|
|
512
750
|
await resetStackMeta(process.cwd());
|
|
513
|
-
|
|
751
|
+
await present("Stack", [
|
|
752
|
+
ansi.brightGreen(" Cleared CLI-managed pre-submit stack state. Branches, PRs, and registered stacks were not changed."),
|
|
753
|
+
]);
|
|
514
754
|
}
|
|
515
755
|
async function cmdStackList(args) {
|
|
516
756
|
const asJson = args.includes("--json");
|
|
@@ -520,28 +760,60 @@ async function cmdStackList(args) {
|
|
|
520
760
|
console.log(JSON.stringify({ stacks }, null, 2));
|
|
521
761
|
return;
|
|
522
762
|
}
|
|
763
|
+
if (canBrowse()) {
|
|
764
|
+
await runLineTabsBrowser({
|
|
765
|
+
tabs: [{ id: "yours", label: "Stacks", lines: buildStackListLines(stacks) }],
|
|
766
|
+
});
|
|
767
|
+
return;
|
|
768
|
+
}
|
|
523
769
|
if (stacks.length === 0) {
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
770
|
+
await present("Stacks", [
|
|
771
|
+
ansi.dim(" No stacks yet. Author with `stack create` → commit → `stack submit`."),
|
|
772
|
+
ansi.dim(" Import an existing chain only: `stack adopt owner/repo#pr`"),
|
|
773
|
+
ansi.dim(" Dashboard: https://mergestorm.ai/dashboard"),
|
|
774
|
+
]);
|
|
527
775
|
return;
|
|
528
776
|
}
|
|
777
|
+
const listed = [];
|
|
529
778
|
for (const s of stacks) {
|
|
530
|
-
|
|
531
|
-
console.log(line);
|
|
532
|
-
console.log("");
|
|
779
|
+
listed.push(...formatStackHuman(s), "");
|
|
533
780
|
}
|
|
534
|
-
|
|
781
|
+
listed.push(ansi.dim(" Dashboard: https://mergestorm.ai/dashboard"));
|
|
782
|
+
await present("Stacks", listed);
|
|
535
783
|
}
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
const
|
|
539
|
-
|
|
540
|
-
|
|
784
|
+
/** Human summary lines for a `stack set` write, one per flag. */
|
|
785
|
+
export function stackSetSummary(parsed) {
|
|
786
|
+
const tri = (value) => value === null ? "default (account setting)" : value ? "on" : "off";
|
|
787
|
+
const lines = [];
|
|
788
|
+
if (parsed.autoLand !== undefined) {
|
|
789
|
+
lines.push(`Auto land ${parsed.autoLand ? "on" : "off"}`);
|
|
541
790
|
}
|
|
542
|
-
|
|
791
|
+
if (parsed.autoReview !== undefined) {
|
|
792
|
+
lines.push(`Auto-review ${tri(parsed.autoReview)}`);
|
|
793
|
+
}
|
|
794
|
+
if (parsed.autoPatch !== undefined) {
|
|
795
|
+
lines.push(`Auto-patch ${tri(parsed.autoPatch)}`);
|
|
796
|
+
}
|
|
797
|
+
return lines;
|
|
798
|
+
}
|
|
799
|
+
export async function cmdStackSet(args, deps = {}) {
|
|
800
|
+
const parsed = parseStackSetArgs(args);
|
|
801
|
+
const cfg = await (deps.loadConfig ?? loadConfig)();
|
|
802
|
+
const body = await (deps.setStackPolicy ?? setStackPolicy)(parsed.stackId, {
|
|
803
|
+
...(parsed.autoLand !== undefined ? { autoEnqueueWhenReady: parsed.autoLand } : {}),
|
|
804
|
+
...(parsed.autoReview !== undefined ? { autoReviewOverride: parsed.autoReview } : {}),
|
|
805
|
+
...(parsed.autoPatch !== undefined ? { autoPatchOverride: parsed.autoPatch } : {}),
|
|
806
|
+
}, cfg);
|
|
807
|
+
if (parsed.asJson) {
|
|
808
|
+
console.log(JSON.stringify(body, null, 2));
|
|
809
|
+
return;
|
|
810
|
+
}
|
|
811
|
+
await present("Stack", stackSetSummary(parsed).map((line) => ansi.brightGreen(` ${line} for ${parsed.stackId}`)));
|
|
812
|
+
}
|
|
813
|
+
async function cmdStackAdopt(args) {
|
|
814
|
+
const { owner, repo, prNumber, autoLand, autoReview, autoPatch, asJson } = parseStackAdoptArgs(args);
|
|
543
815
|
const cfg = await loadConfig();
|
|
544
|
-
const body = await adoptStack(owner, repo, prNumber, cfg);
|
|
816
|
+
const body = await adoptStack(owner, repo, prNumber, cfg, openPolicyPatch({ autoLand, autoReview, autoPatch }));
|
|
545
817
|
if (asJson) {
|
|
546
818
|
console.log(JSON.stringify(body, null, 2));
|
|
547
819
|
return;
|
|
@@ -552,13 +824,16 @@ async function cmdStackAdopt(args) {
|
|
|
552
824
|
const data = body;
|
|
553
825
|
const stack = data.stack;
|
|
554
826
|
const chain = data.chain;
|
|
555
|
-
|
|
556
|
-
(
|
|
557
|
-
|
|
827
|
+
const imported = [
|
|
828
|
+
ansi.brightGreen(` Imported ${owner}/${repo}#${prNumber}` +
|
|
829
|
+
(stack?.id ? ` → ${stack.id}` : "") +
|
|
830
|
+
(Array.isArray(chain) ? ` (${chain.length} layer(s))` : "")),
|
|
831
|
+
];
|
|
558
832
|
if (stack?.trunkBranch) {
|
|
559
|
-
|
|
833
|
+
imported.push(ansi.dim(` trunk: ${stack.trunkBranch}`));
|
|
560
834
|
}
|
|
561
|
-
|
|
835
|
+
imported.push(ansi.dim(" Dashboard: https://mergestorm.ai/dashboard"));
|
|
836
|
+
await present("Stack", imported);
|
|
562
837
|
}
|
|
563
838
|
async function cmdStackRestack(args) {
|
|
564
839
|
const asJson = args.includes("--json");
|
|
@@ -570,7 +845,9 @@ async function cmdStackRestack(args) {
|
|
|
570
845
|
return;
|
|
571
846
|
}
|
|
572
847
|
const fromBranch = body.fromBranch;
|
|
573
|
-
|
|
848
|
+
await present("Stack", [
|
|
849
|
+
ansi.brightGreen(` Restack started` + (fromBranch ? ` from ${fromBranch}` : "") + ` (${id})`),
|
|
850
|
+
]);
|
|
574
851
|
}
|
|
575
852
|
async function cmdStackLand(args) {
|
|
576
853
|
const asJson = args.includes("--json");
|
|
@@ -586,39 +863,31 @@ async function cmdStackLand(args) {
|
|
|
586
863
|
const branch = body.mergedBranch;
|
|
587
864
|
const uNumber = body.uNumber;
|
|
588
865
|
if (typeof uNumber === "number" && uNumber > 0) {
|
|
589
|
-
|
|
590
|
-
(
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
866
|
+
await present("Stack", [
|
|
867
|
+
ansi.brightGreen(` Promoted` +
|
|
868
|
+
(pr != null ? ` #${pr}` : "") +
|
|
869
|
+
` into U${uNumber}` +
|
|
870
|
+
(branch ? ` (${branch})` : "") +
|
|
871
|
+
` on ${id}`),
|
|
872
|
+
]);
|
|
594
873
|
return;
|
|
595
874
|
}
|
|
596
|
-
|
|
597
|
-
(
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
}
|
|
601
|
-
|
|
602
|
-
const asJson = args.includes("--json");
|
|
603
|
-
const filtered = args.filter((a) => a !== "--json");
|
|
604
|
-
const mode = filtered[0]?.toLowerCase();
|
|
605
|
-
const id = requireStackId(filtered[1], "usage: mergestorm stack auto-promote on|off <stack-id>");
|
|
606
|
-
if (mode !== "on" && mode !== "off") {
|
|
607
|
-
throw new CommandError("usage: mergestorm stack auto-promote on|off <stack-id>");
|
|
608
|
-
}
|
|
609
|
-
const cfg = await loadConfig();
|
|
610
|
-
const body = await setStackAutoPromote(id, mode === "on", cfg);
|
|
611
|
-
if (asJson) {
|
|
612
|
-
console.log(JSON.stringify(body, null, 2));
|
|
613
|
-
return;
|
|
614
|
-
}
|
|
615
|
-
console.log(ansi.green(` Auto-promote ${mode} for ${id}`));
|
|
875
|
+
await present("Stack", [
|
|
876
|
+
ansi.brightGreen(` Landed` +
|
|
877
|
+
(pr != null ? ` #${pr}` : "") +
|
|
878
|
+
(branch ? ` (${branch})` : "") +
|
|
879
|
+
` on ${id}`),
|
|
880
|
+
]);
|
|
616
881
|
}
|
|
617
882
|
export async function cmdStack(args) {
|
|
618
883
|
const sub = args[0]?.toLowerCase();
|
|
619
884
|
const rest = args.slice(1);
|
|
620
885
|
// Help must exit 0 — print usage, do not throw CommandError.
|
|
621
886
|
if (!sub || sub === "-h" || sub === "--help" || sub === "help") {
|
|
887
|
+
if (canBrowse()) {
|
|
888
|
+
await openHelpBrowser("stacks");
|
|
889
|
+
return;
|
|
890
|
+
}
|
|
622
891
|
console.log(STACK_USAGE);
|
|
623
892
|
return;
|
|
624
893
|
}
|
|
@@ -630,17 +899,13 @@ export async function cmdStack(args) {
|
|
|
630
899
|
return cmdStackReset(rest);
|
|
631
900
|
if (sub === "list" || sub === "ls")
|
|
632
901
|
return cmdStackList(rest);
|
|
902
|
+
if (sub === "set")
|
|
903
|
+
return cmdStackSet(rest);
|
|
633
904
|
if (sub === "adopt")
|
|
634
905
|
return cmdStackAdopt(rest);
|
|
635
906
|
if (sub === "restack")
|
|
636
907
|
return cmdStackRestack(rest);
|
|
637
908
|
if (sub === "land" || sub === "land-next")
|
|
638
909
|
return cmdStackLand(rest);
|
|
639
|
-
if (sub === "auto-promote" ||
|
|
640
|
-
sub === "autopromote" ||
|
|
641
|
-
sub === "auto-land" ||
|
|
642
|
-
sub === "autoland") {
|
|
643
|
-
return cmdStackAutoLand(rest);
|
|
644
|
-
}
|
|
645
910
|
throw new CommandError(`${STACK_USAGE}\nunknown stack subcommand: ${sub}`);
|
|
646
911
|
}
|
|
@@ -12,6 +12,8 @@ export type StatusOptions = {
|
|
|
12
12
|
interactive?: boolean;
|
|
13
13
|
/** Test seam; production uses the two-second poll interval. */
|
|
14
14
|
pollIntervalMs?: number;
|
|
15
|
+
/** Shell: route --json through the hold-to-read panel instead of console.log. */
|
|
16
|
+
mode?: "oneshot" | "shell";
|
|
15
17
|
};
|
|
16
18
|
export declare function parseStatusArgs(args: string[], defaults?: {
|
|
17
19
|
format: "json" | "pretty";
|