mergestorm 0.3.9 → 0.3.13

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.
Files changed (43) hide show
  1. package/README.md +57 -13
  2. package/dist/api.d.ts +27 -38
  3. package/dist/api.js +81 -4
  4. package/dist/client/index.d.ts +5 -0
  5. package/dist/client/index.js +5 -0
  6. package/dist/commands/credits.js +33 -12
  7. package/dist/commands/login.js +28 -7
  8. package/dist/commands/registry.d.ts +1 -1
  9. package/dist/commands/registry.js +26 -19
  10. package/dist/commands/review-client.d.ts +93 -0
  11. package/dist/commands/review-client.js +289 -0
  12. package/dist/commands/review.d.ts +30 -6
  13. package/dist/commands/review.js +385 -146
  14. package/dist/commands/stack.d.ts +34 -2
  15. package/dist/commands/stack.js +252 -58
  16. package/dist/commands/status.d.ts +16 -2
  17. package/dist/commands/status.js +221 -21
  18. package/dist/errors.d.ts +18 -2
  19. package/dist/errors.js +22 -1
  20. package/dist/git-stack.d.ts +2 -0
  21. package/dist/git-stack.js +4 -0
  22. package/dist/git.d.ts +12 -1
  23. package/dist/git.js +52 -7
  24. package/dist/shell.d.ts +1 -1
  25. package/dist/shell.js +6 -3
  26. package/dist/stack-dto.d.ts +185 -0
  27. package/dist/stack-dto.js +7 -0
  28. package/dist/submit-pr-base.d.ts +31 -0
  29. package/dist/submit-pr-base.js +69 -0
  30. package/dist/ui/banner.js +7 -0
  31. package/dist/ui/box.d.ts +13 -9
  32. package/dist/ui/box.js +28 -44
  33. package/dist/ui/findings.d.ts +2 -0
  34. package/dist/ui/findings.js +2 -1
  35. package/dist/ui/job-envelope.d.ts +68 -0
  36. package/dist/ui/job-envelope.js +35 -0
  37. package/dist/ui/prompt.d.ts +91 -0
  38. package/dist/ui/prompt.js +241 -62
  39. package/dist/ui/usage.d.ts +25 -1
  40. package/dist/ui/usage.js +50 -1
  41. package/dist/ui/width.d.ts +26 -0
  42. package/dist/ui/width.js +262 -0
  43. package/package.json +5 -1
@@ -0,0 +1,262 @@
1
+ /**
2
+ * Terminal cell arithmetic. Everything that frames, pads, or parks a cursor
3
+ * must count *cells*, not UTF-16 units: CJK is two cells, combining marks
4
+ * and ZWJ are zero, an astral emoji is one grapheme. Getting this wrong is
5
+ * why the right border of the prompt box drifted on non-Latin input.
6
+ */
7
+ const SGR_PATTERN = /\u001b\[[0-9;]*m/g;
8
+ const SGR_SPLIT = /(\u001b\[[0-9;]*m)/;
9
+ export function stripSgr(str) {
10
+ return str.replace(SGR_PATTERN, "");
11
+ }
12
+ /**
13
+ * East Asian Wide / Fullwidth ranges (plus emoji presentation blocks).
14
+ * Deliberately excludes Ambiguous-width ranges such as box drawing
15
+ * (U+2500..257F) and block elements (U+2580..259F, the TORNADO_LOGO
16
+ * glyphs), which every terminal we target renders in one cell.
17
+ */
18
+ const WIDE_RANGES = [
19
+ [0x1100, 0x115f], // Hangul Jamo
20
+ [0x231a, 0x231b],
21
+ [0x2329, 0x232a],
22
+ [0x23e9, 0x23ec],
23
+ [0x23f0, 0x23f0],
24
+ [0x23f3, 0x23f3],
25
+ [0x25fd, 0x25fe],
26
+ [0x2614, 0x2615],
27
+ [0x2648, 0x2653],
28
+ [0x267f, 0x267f],
29
+ [0x2693, 0x2693],
30
+ [0x26a1, 0x26a1],
31
+ [0x26aa, 0x26ab],
32
+ [0x26bd, 0x26be],
33
+ [0x26c4, 0x26c5],
34
+ [0x26ce, 0x26ce],
35
+ [0x26d4, 0x26d4],
36
+ [0x26ea, 0x26ea],
37
+ [0x26f2, 0x26f3],
38
+ [0x26f5, 0x26f5],
39
+ [0x26fa, 0x26fa],
40
+ [0x26fd, 0x26fd],
41
+ [0x2705, 0x2705],
42
+ [0x270a, 0x270b],
43
+ [0x2728, 0x2728],
44
+ [0x274c, 0x274c],
45
+ [0x274e, 0x274e],
46
+ [0x2753, 0x2755],
47
+ [0x2757, 0x2757],
48
+ [0x2795, 0x2797],
49
+ [0x27b0, 0x27b0],
50
+ [0x27bf, 0x27bf],
51
+ [0x2b1b, 0x2b1c],
52
+ [0x2b50, 0x2b50],
53
+ [0x2b55, 0x2b55],
54
+ [0x2e80, 0x303e], // CJK radicals, Kangxi, ideographic description, CJK symbols
55
+ [0x3041, 0x33ff], // Hiragana, Katakana, Bopomofo, Hangul compat, enclosed CJK
56
+ [0x3400, 0x4dbf], // CJK ext A
57
+ [0x4e00, 0x9fff], // CJK unified
58
+ [0xa000, 0xa4cf], // Yi
59
+ [0xa960, 0xa97f], // Hangul Jamo ext A
60
+ [0xac00, 0xd7a3], // Hangul syllables
61
+ [0xf900, 0xfaff], // CJK compat ideographs
62
+ [0xfe10, 0xfe19], // vertical forms
63
+ [0xfe30, 0xfe6f], // CJK compat forms, small forms
64
+ [0xff00, 0xff60], // fullwidth forms
65
+ [0xffe0, 0xffe6],
66
+ [0x16fe0, 0x16fe4],
67
+ [0x17000, 0x18aff], // Tangut, Khitan
68
+ [0x1b000, 0x1b2ff], // Kana supplement / ext
69
+ [0x1f004, 0x1f004],
70
+ [0x1f0cf, 0x1f0cf],
71
+ [0x1f18e, 0x1f18e],
72
+ [0x1f191, 0x1f19a],
73
+ [0x1f1e6, 0x1f1ff], // regional indicators (flags pair to one 2-cell cluster)
74
+ [0x1f200, 0x1f202],
75
+ [0x1f210, 0x1f23b],
76
+ [0x1f240, 0x1f248],
77
+ [0x1f250, 0x1f251],
78
+ [0x1f260, 0x1f265],
79
+ [0x1f300, 0x1f320],
80
+ [0x1f32d, 0x1f335],
81
+ [0x1f337, 0x1f37c],
82
+ [0x1f37e, 0x1f393],
83
+ [0x1f3a0, 0x1f3ca],
84
+ [0x1f3cf, 0x1f3d3],
85
+ [0x1f3e0, 0x1f3f0],
86
+ [0x1f3f4, 0x1f3f4],
87
+ [0x1f3f8, 0x1f43e],
88
+ [0x1f440, 0x1f440],
89
+ [0x1f442, 0x1f4fc],
90
+ [0x1f4ff, 0x1f53d],
91
+ [0x1f54b, 0x1f54e],
92
+ [0x1f550, 0x1f567],
93
+ [0x1f57a, 0x1f57a],
94
+ [0x1f595, 0x1f596],
95
+ [0x1f5a4, 0x1f5a4],
96
+ [0x1f5fb, 0x1f64f],
97
+ [0x1f680, 0x1f6c5],
98
+ [0x1f6cc, 0x1f6cc],
99
+ [0x1f6d0, 0x1f6d2],
100
+ [0x1f6d5, 0x1f6d7],
101
+ [0x1f6dc, 0x1f6df],
102
+ [0x1f6eb, 0x1f6ec],
103
+ [0x1f6f4, 0x1f6fc],
104
+ [0x1f7e0, 0x1f7eb],
105
+ [0x1f7f0, 0x1f7f0],
106
+ [0x1f90c, 0x1f93a],
107
+ [0x1f93c, 0x1f945],
108
+ [0x1f947, 0x1f9ff],
109
+ [0x1fa70, 0x1faff],
110
+ [0x20000, 0x2fffd], // CJK ext B..F
111
+ [0x30000, 0x3fffd], // CJK ext G..
112
+ ];
113
+ const ZERO_WIDTH_RE = /^[\p{M}\p{Cc}\p{Cf}\u200b-\u200f\u2028-\u202e\u2060-\u2064\ufeff]$/u;
114
+ function isWide(cp) {
115
+ // Binary search over the sorted range table.
116
+ let lo = 0;
117
+ let hi = WIDE_RANGES.length - 1;
118
+ while (lo <= hi) {
119
+ const mid = (lo + hi) >> 1;
120
+ const [start, end] = WIDE_RANGES[mid];
121
+ if (cp < start)
122
+ hi = mid - 1;
123
+ else if (cp > end)
124
+ lo = mid + 1;
125
+ else
126
+ return true;
127
+ }
128
+ return false;
129
+ }
130
+ /** Cells for one code point on its own (no cluster context). */
131
+ export function codePointWidth(cp) {
132
+ if (cp === 0xfe0f)
133
+ return 0; // VS16 handled at cluster level
134
+ const ch = String.fromCodePoint(cp);
135
+ if (ZERO_WIDTH_RE.test(ch))
136
+ return 0;
137
+ if (cp >= 0xfe00 && cp <= 0xfe0f)
138
+ return 0; // variation selectors
139
+ if (cp >= 0xe0100 && cp <= 0xe01ef)
140
+ return 0; // VS supplement
141
+ if (cp >= 0xe0020 && cp <= 0xe007f)
142
+ return 0; // emoji tag sequence
143
+ if (cp >= 0xd800 && cp <= 0xdfff)
144
+ return 1; // lone surrogate (broken slice)
145
+ return isWide(cp) ? 2 : 1;
146
+ }
147
+ let segmenter;
148
+ /**
149
+ * Grapheme clusters of `text`, yielded lazily so an early exit (e.g.
150
+ * `sliceVisible` cutting a cell window) never segments the whole tail of a
151
+ * long buffer. Falls back to code-point iteration without Intl.Segmenter.
152
+ */
153
+ function* graphemes(text) {
154
+ if (segmenter === undefined) {
155
+ try {
156
+ segmenter = new Intl.Segmenter(undefined, { granularity: "grapheme" });
157
+ }
158
+ catch {
159
+ segmenter = null;
160
+ }
161
+ }
162
+ if (segmenter) {
163
+ for (const seg of segmenter.segment(text))
164
+ yield seg.segment;
165
+ return;
166
+ }
167
+ for (const ch of text)
168
+ yield ch;
169
+ }
170
+ /**
171
+ * Cells one grapheme cluster occupies. A cluster is as wide as its widest
172
+ * base (a ZWJ family emoji is 2, not 6); VS16 promotes a text-style symbol
173
+ * to emoji presentation (2 cells); a cluster of nothing but marks is 0.
174
+ */
175
+ export function graphemeWidth(cluster) {
176
+ let width = 0;
177
+ let vs16 = false;
178
+ for (const ch of cluster) {
179
+ const cp = ch.codePointAt(0);
180
+ if (cp === 0xfe0f) {
181
+ vs16 = true;
182
+ continue;
183
+ }
184
+ width = Math.max(width, codePointWidth(cp));
185
+ }
186
+ if (vs16 && width === 1)
187
+ return 2;
188
+ return width;
189
+ }
190
+ /** Terminal cells a string occupies, ignoring SGR colour codes. */
191
+ export function visibleWidth(str) {
192
+ const plain = stripSgr(str);
193
+ if (!plain)
194
+ return 0;
195
+ let total = 0;
196
+ for (const cluster of graphemes(plain))
197
+ total += graphemeWidth(cluster);
198
+ return total;
199
+ }
200
+ /**
201
+ * Take the cells `[start, start + cells)` of `str`, keeping every SGR code
202
+ * (so colours survive) and substituting spaces for a wide glyph cut by
203
+ * either edge, so the result's geometry is exactly what the caller asked
204
+ * for even when a CJK character straddles the window.
205
+ */
206
+ export function sliceVisible(str, start, cells) {
207
+ if (cells <= 0)
208
+ return "";
209
+ const end = start + cells;
210
+ let out = "";
211
+ let offset = 0;
212
+ const parts = str.split(SGR_SPLIT);
213
+ for (let i = 0; i < parts.length; i++) {
214
+ const part = parts[i];
215
+ if (!part)
216
+ continue;
217
+ if (i % 2 === 1) {
218
+ out += part;
219
+ continue;
220
+ }
221
+ // Offsets only grow, so once we're past the window no later content can
222
+ // land in it; stop segmenting the tail of a long buffer.
223
+ if (offset > end)
224
+ continue;
225
+ for (const cluster of graphemes(part)) {
226
+ if (offset > end)
227
+ break;
228
+ const w = graphemeWidth(cluster);
229
+ const from = offset;
230
+ const to = offset + w;
231
+ offset = to;
232
+ if (w === 0) {
233
+ // Marks attach to the previous glyph; keep them only when that glyph
234
+ // was emitted.
235
+ if (from > start && from <= end)
236
+ out += cluster;
237
+ continue;
238
+ }
239
+ if (to <= start || from >= end)
240
+ continue;
241
+ if (from >= start && to <= end) {
242
+ out += cluster;
243
+ }
244
+ else {
245
+ out += " ".repeat(Math.min(to, end) - Math.max(from, start));
246
+ }
247
+ }
248
+ }
249
+ return out;
250
+ }
251
+ /** Pad or truncate to an exact cell width (ANSI-safe). */
252
+ export function padVisible(line, width) {
253
+ const target = Math.max(0, width);
254
+ const w = visibleWidth(line);
255
+ if (w === target)
256
+ return line;
257
+ if (w < target)
258
+ return `${line}${" ".repeat(target - w)}`;
259
+ const cut = sliceVisible(line, 0, target);
260
+ const cutWidth = visibleWidth(cut);
261
+ return cutWidth < target ? `${cut}${" ".repeat(target - cutWidth)}` : cut;
262
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mergestorm",
3
- "version": "0.3.9",
3
+ "version": "0.3.13",
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",
@@ -18,6 +18,10 @@
18
18
  "mergestorm": "./dist/cli.js",
19
19
  "mg": "./dist/cli.js"
20
20
  },
21
+ "exports": {
22
+ ".": "./dist/cli.js",
23
+ "./client": "./dist/client/index.js"
24
+ },
21
25
  "homepage": "https://mergestorm.ai/docs#review-jobs-api",
22
26
  "repository": {
23
27
  "type": "git",