mergestorm 0.3.29 → 0.3.30

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 CHANGED
@@ -45,7 +45,7 @@ On a TTY, bare `mergestorm` (or `mergestorm shell`) opens a branded REPL: a comp
45
45
 
46
46
  ```
47
47
  ╭──────────────────────────────────────────────────────────────────────────────╮
48
- │ ▀▀▀▀▀▀▀▀▀▀▘ mergestorm v0.3.29 │
48
+ │ ▀▀▀▀▀▀▀▀▀▀▘ mergestorm v0.3.30 │
49
49
  │ ▀▀▀▀▀▀▀▀▀ ● msk_live_… · maelstrom │
50
50
  │ ▀▀▀▀▀▀ [████████░░░░░░░░░░░░░░░░░░░░░░] 25% used │
51
51
  │ ▝▀▀▀▀▀ local reviews + stacked PRs · mergestorm.ai │
@@ -114,7 +114,7 @@ mergestorm dismiss <owner/repo>#<n> --head <sha> --review <id> --finding <id> --
114
114
  - `--ignore <text>` keeps it silent on the first attention whose blocker contains that text (case-insensitive), until the blocker, its PR or its head changes. Use it for a blocker a human has taken over. Repeat the flag for more than one.
115
115
  - `--until landed` keeps running after an attention, printing each new attention once, and exits only when the watch is done or reads keep failing.
116
116
  - `--max <minutes>` caps the whole watch; when it runs out it prints `MS-WATCH TIMEOUT` and exits 5. The stack is not landed then.
117
- - `--json` adds the full `mergestorm.stack_watch/v1` envelope as one JSON line after each marker line.
117
+ - `--json` writes exactly one JSON document to stdout: the final `mergestorm.stack_watch/v1` envelope, or `{ stackId, outcome }` when there is none (a repeated failed read, `--max` timeout, or abort). The MS-WATCH marker lines go to stderr, so do not merge the streams (2>&1) before parsing stdout.
118
118
 
119
119
  ```bash
120
120
  mg stack watch 11111111-1111-4111-8111-111111111111 --head "$(git rev-parse HEAD)"
@@ -56,6 +56,7 @@ const STACK_USAGE = `usage:
56
56
  Loops stack wait with the cursor and stays silent while the stack is waiting or in progress.
57
57
  Prints one MS-WATCH ATTENTION line and exits 3 on attention or a repeated failed read; prints
58
58
  MS-WATCH LANDED and exits 0 once the watch is done. Run it as a background command.
59
+ --json writes exactly one JSON document to stdout and sends the MS-WATCH lines to stderr.
59
60
  mergestorm stack set <stack-id> [--auto-land on|off] [--auto-review on|off|default] [--auto-patch on|off|default] [--json]
60
61
  mergestorm stack adopt <owner/repo>#<pr> [--auto-land on|off] [--auto-review on|off] [--auto-patch on|off] [--json]
61
62
  mergestorm stack restack <stack-id> [--json]
@@ -392,6 +392,11 @@ export type MergeQueueBounceDetail = {
392
392
  conflictPaths?: string[];
393
393
  message?: string;
394
394
  warning?: string;
395
+ batch?: MergeQueueBounceBatch;
396
+ };
397
+ export type MergeQueueBounceBatch = {
398
+ id: string;
399
+ withPrNumbers: number[];
395
400
  };
396
401
  export type MergeQueueBatchState = "verifying" | "landing" | "landed" | "stopped" | "bounced" | "abandoned";
397
402
  export type MergeQueueEntryBatchDto = {
package/dist/stack-dto.js CHANGED
@@ -84,7 +84,11 @@ export function mergeQueueBounceLabel(entry) {
84
84
  (detail?.conflictPaths?.length ? detail.conflictPaths.join(", ") : null) ??
85
85
  detail?.message ??
86
86
  (detail?.prNumber != null ? `PR #${detail.prNumber}` : null);
87
- return specifics ? `${kindLabel} — ${specifics}` : kindLabel;
87
+ const withPrNumbers = detail?.batch?.withPrNumbers ?? [];
88
+ const batch = withPrNumbers.length
89
+ ? ` in a red batch with ${withPrNumbers.map((prNumber) => `#${prNumber}`).join(", ")}`
90
+ : "";
91
+ return specifics ? `${kindLabel} — ${specifics}${batch}` : `${kindLabel}${batch}`;
88
92
  }
89
93
  return entry.bounceReason?.trim() || "bounced";
90
94
  }
@@ -20,6 +20,7 @@ export type RunStackWatchOptions = {
20
20
  cursor?: StackWatchCursor;
21
21
  signal?: AbortSignal;
22
22
  write?: (line: string) => void;
23
+ writeMarker?: (line: string) => void;
23
24
  poll?: typeof pollStackWatch;
24
25
  sleep?: (ms: number, signal?: AbortSignal) => Promise<void>;
25
26
  now?: () => number;
@@ -29,14 +29,15 @@ export async function runStackWatch(cfg, stackId, opts = {}) {
29
29
  const until = opts.until ?? "attention";
30
30
  const ignore = opts.ignore ?? [];
31
31
  const write = opts.write ?? ((line) => console.log(line));
32
+ const writeMarker = opts.json ? opts.writeMarker ?? ((line) => console.error(line)) : write;
32
33
  const poll = opts.poll ?? pollStackWatch;
33
34
  const now = opts.now ?? Date.now;
34
35
  const wait = opts.sleep ?? ((ms, signal) => sleep(ms, undefined, { signal }));
35
36
  const deadline = opts.maxMs !== undefined ? now() + opts.maxMs : Number.POSITIVE_INFINITY;
36
- const emit = (line, envelope) => {
37
- write(line);
38
- if (opts.json && envelope)
39
- write(JSON.stringify(envelope));
37
+ const emit = (line, envelope, outcome) => {
38
+ writeMarker(line);
39
+ if (opts.json && outcome)
40
+ write(JSON.stringify(envelope ?? { stackId: id, outcome }));
40
41
  };
41
42
  const pause = async (ms) => {
42
43
  const remaining = Math.min(ms, Math.max(0, deadline - now()));
@@ -48,7 +49,7 @@ export async function runStackWatch(cfg, stackId, opts = {}) {
48
49
  const ignoredKeys = new Map();
49
50
  let reportedKey = null;
50
51
  const landed = (envelope) => {
51
- emit(stackWatchLandedLine(envelope), envelope);
52
+ emit(stackWatchLandedLine(envelope), envelope, "landed");
52
53
  if (!opts.json)
53
54
  write(envelope.watch.message);
54
55
  return { outcome: "landed", exitCode: STACK_WATCH_EXIT.landed, envelope };
@@ -56,7 +57,7 @@ export async function runStackWatch(cfg, stackId, opts = {}) {
56
57
  const failed = async (message, envelope) => {
57
58
  failures += 1;
58
59
  if (failures >= 2) {
59
- emit(stackWatchFailedLine(id, message), envelope);
60
+ emit(stackWatchFailedLine(id, message), envelope, "failed");
60
61
  return { outcome: "failed", exitCode: STACK_WATCH_EXIT.attention, envelope };
61
62
  }
62
63
  await pause(STACK_WATCH_FAILED_RETRY_MS);
@@ -106,7 +107,7 @@ export async function runStackWatch(cfg, stackId, opts = {}) {
106
107
  }
107
108
  const silent = [...ignoredKeys.values()].includes(key) || key === reportedKey;
108
109
  if (!silent) {
109
- emit(stackWatchAttentionLine(envelope), envelope);
110
+ emit(stackWatchAttentionLine(envelope), envelope, until === "attention" ? "attention" : null);
110
111
  if (until === "attention")
111
112
  return { outcome: "attention", exitCode: STACK_WATCH_EXIT.attention, envelope };
112
113
  reportedKey = key;
@@ -116,10 +117,13 @@ export async function runStackWatch(cfg, stackId, opts = {}) {
116
117
  }
117
118
  }
118
119
  catch (err) {
119
- if (isAbort(err, opts.signal))
120
+ if (isAbort(err, opts.signal)) {
121
+ if (opts.json)
122
+ write(JSON.stringify({ stackId: id, outcome: "aborted" }));
120
123
  return { outcome: "aborted", exitCode: 130, envelope: null };
124
+ }
121
125
  throw err;
122
126
  }
123
- emit(stackWatchTimeoutLine(id, opts.maxMs ?? 0), null);
127
+ emit(stackWatchTimeoutLine(id, opts.maxMs ?? 0), null, "timeout");
124
128
  return { outcome: "timeout", exitCode: STACK_WATCH_EXIT.timeout, envelope: null };
125
129
  }
package/dist/ui/banner.js CHANGED
@@ -120,6 +120,9 @@ const WHATS_NEW = {
120
120
  "0.3.29": [
121
121
  "mg stack watch waits in the background",
122
122
  ],
123
+ "0.3.30": [
124
+ "stack watch --json prints one JSON document",
125
+ ],
123
126
  };
124
127
  function densityFromVariant(variant) {
125
128
  if (variant === "mini" || variant === "nano")
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mergestorm",
3
- "version": "0.3.29",
3
+ "version": "0.3.30",
4
4
  "description": "Mergestorm CLI (mg) — local reviews & stacked PRs. MIT; build from source: https://github.com/marginsystems/mergestorm-cli",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -20,7 +20,7 @@ If your host cannot hold a long turn open (Cursor, Claude Code, Codex, and other
20
20
  - `MS-WATCH ATTENTION pr=<n> head=<sha> blocker="<blocker>" repair=<kind>` (exit 3): the watch returned `attention`.
21
21
  - `MS-WATCH ATTENTION failed stack=<id> error="<message>"` (exit 3): a read failed and its one retry failed too.
22
22
  - `MS-WATCH LANDED stack=<id> reason=<reason>` (exit 0): `watch.done` is `true`. Only `reason=landed` is a landed stack; for `closed`, `archived`, or `not_found`, follow "Not done until landed".
23
- With `--max <minutes>` it can also stop with `MS-WATCH TIMEOUT` (exit 5). The stack is not landed then; start the watcher again. `--json` adds the full envelope as one JSON line after the marker line.
23
+ With `--max <minutes>` it can also stop with `MS-WATCH TIMEOUT` (exit 5). The stack is not landed then; start the watcher again. `--json` writes exactly one JSON document to stdout: the final `mergestorm.stack_watch/v1` envelope, or `{ stackId, outcome }` when there is no envelope (a repeated failed read, timeout, or abort). MS-WATCH marker lines go to stderr; do not merge the streams before parsing stdout.
24
24
  3. When it wakes you on `MS-WATCH ATTENTION pr=`, call `stack_status` once for that stack, then fix the blocker as steps 3 to 7 of "Watch and patch" say, push, and start the watcher again with `--head` set to the SHA you pushed. On `MS-WATCH ATTENTION failed`, the watcher already retried once: tell the human the stack is not landed and is no longer being watched.
25
25
  4. When the human has taken one blocker over and asked you to keep watching the rest, pass `--ignore <blocker-substring>`. The watcher stays silent on the first attention whose blocker contains that text until the blocker, its PR, or its head changes; any other attention still wakes you.
26
26
  5. `--until landed` keeps the watcher running after an attention: it prints each new attention line once and exits only when the watch is done or a read fails twice. Use it only when your host streams output lines to you while the command runs.