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.
Files changed (76) hide show
  1. package/README.md +67 -24
  2. package/dist/api.d.ts +106 -40
  3. package/dist/api.js +245 -9
  4. package/dist/client/index.d.ts +5 -0
  5. package/dist/client/index.js +5 -0
  6. package/dist/commands/branches.d.ts +6 -2
  7. package/dist/commands/branches.js +29 -14
  8. package/dist/commands/browse.d.ts +39 -0
  9. package/dist/commands/browse.js +259 -0
  10. package/dist/commands/credits.js +30 -12
  11. package/dist/commands/help.d.ts +5 -0
  12. package/dist/commands/help.js +93 -0
  13. package/dist/commands/jobs.d.ts +11 -2
  14. package/dist/commands/jobs.js +64 -15
  15. package/dist/commands/login.js +32 -10
  16. package/dist/commands/logout.js +2 -1
  17. package/dist/commands/pr.d.ts +22 -0
  18. package/dist/commands/pr.js +127 -0
  19. package/dist/commands/queue.d.ts +12 -0
  20. package/dist/commands/queue.js +160 -0
  21. package/dist/commands/registry.d.ts +1 -1
  22. package/dist/commands/registry.js +115 -33
  23. package/dist/commands/review-client.d.ts +106 -0
  24. package/dist/commands/review-client.js +399 -0
  25. package/dist/commands/review.d.ts +36 -6
  26. package/dist/commands/review.js +395 -145
  27. package/dist/commands/settings.d.ts +17 -0
  28. package/dist/commands/settings.js +89 -0
  29. package/dist/commands/skill.d.ts +20 -0
  30. package/dist/commands/skill.js +145 -0
  31. package/dist/commands/stack.d.ts +36 -2
  32. package/dist/commands/stack.js +341 -113
  33. package/dist/commands/status.d.ts +18 -2
  34. package/dist/commands/status.js +236 -21
  35. package/dist/commands/whoami.d.ts +3 -1
  36. package/dist/commands/whoami.js +21 -7
  37. package/dist/errors.d.ts +18 -2
  38. package/dist/errors.js +22 -1
  39. package/dist/git-stack.d.ts +2 -0
  40. package/dist/git-stack.js +4 -0
  41. package/dist/git.d.ts +12 -1
  42. package/dist/git.js +52 -7
  43. package/dist/shell.d.ts +1 -1
  44. package/dist/shell.js +39 -28
  45. package/dist/stack-dto.d.ts +266 -0
  46. package/dist/stack-dto.js +7 -0
  47. package/dist/submit-pr-base.d.ts +31 -0
  48. package/dist/submit-pr-base.js +69 -0
  49. package/dist/ui/ansi.d.ts +2 -0
  50. package/dist/ui/ansi.js +2 -0
  51. package/dist/ui/banner.d.ts +34 -2
  52. package/dist/ui/banner.js +170 -79
  53. package/dist/ui/box.d.ts +13 -9
  54. package/dist/ui/box.js +28 -44
  55. package/dist/ui/findings.d.ts +4 -1
  56. package/dist/ui/findings.js +14 -7
  57. package/dist/ui/job-envelope.d.ts +68 -0
  58. package/dist/ui/job-envelope.js +35 -0
  59. package/dist/ui/line-tabs.d.ts +27 -0
  60. package/dist/ui/line-tabs.js +170 -0
  61. package/dist/ui/logo.d.ts +10 -2
  62. package/dist/ui/logo.js +13 -9
  63. package/dist/ui/present.d.ts +10 -0
  64. package/dist/ui/present.js +13 -0
  65. package/dist/ui/prompt.d.ts +148 -0
  66. package/dist/ui/prompt.js +433 -71
  67. package/dist/ui/select.js +1 -1
  68. package/dist/ui/tabs.d.ts +125 -0
  69. package/dist/ui/tabs.js +480 -0
  70. package/dist/ui/usage.d.ts +29 -1
  71. package/dist/ui/usage.js +71 -2
  72. package/dist/ui/width.d.ts +26 -0
  73. package/dist/ui/width.js +262 -0
  74. package/package.json +6 -1
  75. package/skill/SKILL.md +98 -0
  76. package/skill/mergestorm-pr-loop/SKILL.md +76 -0
package/dist/shell.js CHANGED
@@ -5,18 +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 { openHelpBrowser } from "./commands/help.js";
9
+ import { showLinePanel } from "./ui/line-tabs.js";
8
10
  import { askLine, PromptClosedError } from "./ui/prompt.js";
9
- /** Shell autocomplete + `/help` — derived from the command registry (STRUCT-01). */
10
- export const COMMANDS = shellCommandSpecs();
11
- function printHelp() {
12
- const rows = COMMANDS.map((c) => ` ${ansi.green("/" + c.name.padEnd(10))} ${c.summary}`).join("\n");
13
- console.log(`
14
- ${ansi.bold("Commands")}
15
- ${rows}
16
-
17
- Dashboard: https://mergestorm.ai/settings#api
18
- `);
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")}`]);
19
14
  }
15
+ /** Shell autocomplete + `/help` � derived from the command registry (STRUCT-01). */
16
+ export const COMMANDS = shellCommandSpecs();
20
17
  /** Strips an optional leading "/" so both "review" and "/review" work. */
21
18
  export function parseLine(line) {
22
19
  let trimmed = line.trim();
@@ -31,10 +28,22 @@ export function parseLine(line) {
31
28
  return { cmd, args: parts.slice(1) };
32
29
  }
33
30
  export async function runShell() {
34
- try {
35
- await printBannerHeader();
36
- }
37
- catch { /* banner is best-effort */ }
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();
38
47
  const history = [];
39
48
  let busyAbort = null;
40
49
  const idleCtrlC = new CtrlCExitGate();
@@ -68,6 +77,11 @@ export async function runShell() {
68
77
  prompt: shellPrompt(loggedIn),
69
78
  history,
70
79
  commands: COMMANDS,
80
+ ...(banner
81
+ ? {
82
+ header: (columns, variant) => banner.rows(columns, variant),
83
+ }
84
+ : {}),
71
85
  });
72
86
  }
73
87
  catch (err) {
@@ -87,15 +101,12 @@ export async function runShell() {
87
101
  if (cmd === "exit" || cmd === "quit")
88
102
  break;
89
103
  if (cmd === "help" || cmd === "?") {
90
- printHelp();
104
+ await openHelpBrowser();
91
105
  continue;
92
106
  }
93
107
  if (cmd === "clear") {
94
108
  output.write("");
95
- try {
96
- await printBannerHeader();
97
- }
98
- catch { /* banner is best-effort */ }
109
+ await showBanner();
99
110
  continue;
100
111
  }
101
112
  busyAbort = new AbortController();
@@ -104,33 +115,33 @@ export async function runShell() {
104
115
  mode: "shell",
105
116
  signal: busyAbort.signal,
106
117
  afterAuth: async () => {
107
- try {
108
- await printBannerHeader();
109
- }
110
- catch { /* banner is best-effort */ }
118
+ await showBanner();
111
119
  },
112
120
  });
113
121
  if (!ok) {
114
- console.error(ansi.dim(`unknown command "${cmd}" -- type help`));
122
+ await showShellNotice("Error", `unknown command "${cmd}"\ntype help for the list`);
115
123
  }
116
124
  }
117
125
  catch (err) {
118
126
  if (err instanceof DetachedError) {
119
- console.log(`\n${ansi.yellow(err.message)}`);
127
+ await showShellNotice("Detached", err.message);
120
128
  }
121
129
  else if (err instanceof CommandError) {
122
- console.error(ansi.red(err.message));
130
+ await showShellNotice("Error", err.message);
123
131
  }
124
132
  else if (err instanceof Error && err.name === "AbortError") {
125
- console.log(ansi.dim("\naborted"));
133
+ await showShellNotice("Aborted", "aborted");
126
134
  }
127
135
  else {
128
- console.error(ansi.red(err instanceof Error ? err.message : String(err)));
136
+ await showShellNotice("Error", err instanceof Error ? err.message : String(err));
129
137
  }
130
138
  }
131
139
  finally {
132
140
  busyAbort = null;
133
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();
134
145
  }
135
146
  }
136
147
  finally {
@@ -0,0 +1,266 @@
1
+ /**
2
+ * Wire types returned by the stack list/enrich HTTP endpoints.
3
+ *
4
+ * `api/src/stacks.ts#loadStackDtos` is the producer. Keep this file
5
+ * dependency-free and byte-for-byte aligned with the canonical wire contract.
6
+ */
7
+ export type StackBranchState = "clean" | "needs_restack" | "restacking" | "conflict" | "merged" | "closed";
8
+ export type StackCiStatus = "success" | "pending" | "failure" | "neutral" | "none" | "unknown";
9
+ export type StackReviewStatus = "approved" | "changes_requested" | "reviewed" | "failed" | "none" | "unknown";
10
+ /** `failed` is the newest Vortex `pr_reviews` row. Work paints Review failed. */
11
+ export type StackVortexStatus = "reviewing" | "seam_pending" | "all_clear" | "findings" | "throttled" | "failed";
12
+ export type StackCycloneStatus = "patching" | "awaiting_fix";
13
+ export type StackTempestStatus = "reviewing" | "findings" | "clear" | "failed" | "stale";
14
+ export type StackAgentName = "vortex" | "cyclone" | "tempest";
15
+ /**
16
+ * One live-or-resting agent run for a PR (#1119): a dumb projection of one
17
+ * ledger row (webhook job / lease / review-state / pr_reviews). Two live
18
+ * Vortex jobs on two SHAs → two runs → two chips. Carries only safe facts —
19
+ * never job ids, holders, or error strings.
20
+ */
21
+ export type StackAgentRun = {
22
+ agent: StackAgentName;
23
+ /** Agent-specific status, e.g. "reviewing" / "patching" / "all_clear". */
24
+ status: string;
25
+ /** Full SHA when known. UI shortens to 7. */
26
+ sha: string | null;
27
+ startedAt?: string | null;
28
+ finishedAt?: string | null;
29
+ /**
30
+ * Vortex in-flight sub-state from pr_reviews.phase (#1189): "fanout" while
31
+ * Core + specialists run in parallel, "synthesizing" during consolidation.
32
+ * Only on live `reviewing` runs; unknown values fall back to plain Reviewing.
33
+ */
34
+ phase?: string | null;
35
+ /**
36
+ * Actionable finding count from the latest `pr_reviews.findings` payload
37
+ * (#1248). Info-only notes (Continue, Decision required) are excluded.
38
+ * Only on resting Vortex `findings` runs.
39
+ */
40
+ findingCount?: number | null;
41
+ };
42
+ export type StackLayerChecks = {
43
+ total: number;
44
+ success: number;
45
+ pending: number;
46
+ failure: number;
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;
67
+ };
68
+ /** Closing / Development-sidebar issue. Cap 3 on the wire. */
69
+ export type StackLinkedIssue = {
70
+ number: number;
71
+ title: string | null;
72
+ };
73
+ /** Cyclone merge-conflict specialist resolution for this PR (#973). */
74
+ export type StackLayerConflictResolution = {
75
+ resolvedAt: string;
76
+ /** Branch the conflicts were resolved against (stacks parent or GitHub base). */
77
+ baseBranch: string | null;
78
+ /** Conflicted paths the specialist resolved; the writer caps the list. */
79
+ files: string[];
80
+ /** Head commit of the pushed resolution, when known. */
81
+ headSha: string | null;
82
+ /**
83
+ * Chat dispatch that triggered the run (#973). Traces a mark to the job that
84
+ * pushed it; null when the row predates dispatch tracing (or had no job).
85
+ */
86
+ dispatchId: string | null;
87
+ };
88
+ export type StackLayerDto = {
89
+ branch: string;
90
+ parentBranch: string | null;
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;
100
+ position: number;
101
+ state: StackBranchState;
102
+ title: string | null;
103
+ htmlUrl: string | null;
104
+ ciStatus: StackCiStatus;
105
+ reviewStatus: StackReviewStatus;
106
+ checks: StackLayerChecks | null;
107
+ /** null on the fast list or when no Vortex/seam signal exists. */
108
+ vortexStatus: StackVortexStatus | null;
109
+ /** null on the fast list or when no Cyclone signal exists. */
110
+ cycloneStatus: StackCycloneStatus | null;
111
+ /** null when no Tempest signal exists. Fast list and enrich both paint this from pr_reviews (#876). */
112
+ tempestStatus: StackTempestStatus | null;
113
+ /**
114
+ * Ledger-backed agent runs, one per live row (#1119). Fast list and enrich
115
+ * both paint this from our tables only — never GitHub. The per-agent status
116
+ * fields above stay as a rollup of the newest run for older clients.
117
+ * Undefined = producer predates runs (clients fall back to the rollup).
118
+ */
119
+ agentRuns?: StackAgentRun[];
120
+ conflictDetail: string | null;
121
+ lastRestackedSha: string | null;
122
+ /** GitHub PR mergeability from enrich; null when unknown. */
123
+ mergeable: boolean | null;
124
+ /** GitHub REST mergeable_state; null when unknown. */
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;
132
+ /**
133
+ * Vortex-style linked issues (keywords + Development sidebar), cap 3.
134
+ * null until first enrich finds any; kept on merged unit members (unlike CI).
135
+ */
136
+ linkedIssues?: StackLinkedIssue[] | null;
137
+ /**
138
+ * Specialist lanes Vortex launched on this PR (smart router / Manual).
139
+ * Fast list and enrich both fill from vortex_pr_review_state (#896 / #915).
140
+ * Durable after merge — terminal layers keep the last run so Work can grey
141
+ * the marks on a done-growing unit member instead of dropping them (#1021).
142
+ */
143
+ specialistsRun?: string[] | null;
144
+ /** Published invocation count per specialist lane. Frontend paint follows separately. */
145
+ specialistRunCounts?: Record<string, number> | null;
146
+ /** Number of published reviews Core participated in for this PR. */
147
+ coreRuns?: number | null;
148
+ /**
149
+ * Cyclone's merge-conflict specialist resolved this PR (#973). Fast list and
150
+ * enrich both paint from cyclone_conflict_resolutions; null on terminal
151
+ * layers and when the specialist never ran.
152
+ */
153
+ conflictResolution?: StackLayerConflictResolution | null;
154
+ };
155
+ export type StackUnitMemberDto = {
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;
165
+ branch: string;
166
+ position: number;
167
+ seamState: string;
168
+ seamReviewedSha: string | null;
169
+ promotedHeadSha: string | null;
170
+ promotedAt: string;
171
+ /**
172
+ * GitHub PR title snapshotted at promote / enrich (#965).
173
+ * Survives stack_branches dropping the open-chain row. null until first write.
174
+ */
175
+ title?: string | null;
176
+ /**
177
+ * Linked issues snapshotted onto the member at promote / enrich (#936).
178
+ * Survives stack_branches dropping the open-chain row. null until first write.
179
+ */
180
+ linkedIssues?: StackLinkedIssue[] | null;
181
+ /**
182
+ * Vortex specialist lanes that ran on this PR (#1021). Painted from
183
+ * vortex_pr_review_state on the fast list — the open layer is gone after
184
+ * promote, so the member must carry the marks for the greyed-out row.
185
+ */
186
+ specialistsRun?: string[] | null;
187
+ /** Published invocation count per specialist lane. */
188
+ specialistRunCounts?: Record<string, number> | null;
189
+ /** Number of published reviews Core participated in for this PR. */
190
+ coreRuns?: number | null;
191
+ };
192
+ export type StackUnitDto = {
193
+ id: string;
194
+ uNumber: number;
195
+ state: string;
196
+ branch: string;
197
+ landTarget: string;
198
+ members: StackUnitMemberDto[];
199
+ landPrNumber: number | null;
200
+ tempestLandStatus: string | null;
201
+ landingBlockReason: string | null;
202
+ /**
203
+ * Land-PR chrome. Fast list hydrates last-known CI/title/mergeable/issues from
204
+ * `review_units` (#878 / #892) and may attach a tempest-only stub (#876). Enrich
205
+ * refreshes GitHub and writes back. Null when the land PR is an open stack
206
+ * layer (chips live on `layers`) or the unit has no land PR.
207
+ */
208
+ landPr: StackLayerDto | null;
209
+ };
210
+ export type StackDto = {
211
+ id: string;
212
+ owner: string;
213
+ repo: string;
214
+ trunkBranch: string;
215
+ /** Real trunk the review unit eventually lands into. */
216
+ landTarget: string;
217
+ /**
218
+ * Dashboard-only archive (#963): set hides the stack from the active Work
219
+ * list; GitHub PRs stay open. Only PATCH /stacks/:id toggles it.
220
+ */
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
+ layers: StackLayerDto[];
228
+ /** Present when a review-unit row exists for this stack. */
229
+ unit?: StackUnitDto;
230
+ };
231
+ export type MergeQueueEntryState = "queued" | "running" | "waiting" | "landed" | "bounced" | "cancelled";
232
+ export type MergeQueueEnqueuedBy = "human" | "agent";
233
+ export type MergeQueueEnqueuedVia = "dashboard" | "cli" | "chat" | "api" | "mcp";
234
+ export type MergeQueueBounceKind = "ci_failure" | "ci_timeout" | "head_moved" | "tempest_findings" | "seam_findings" | "restack_conflict" | "pr_draft" | "merge_failed" | "gh_error";
235
+ /** Structured bounce handoff — enough for the bar chip and the chat quote. */
236
+ export type MergeQueueBounceDetail = {
237
+ kind: MergeQueueBounceKind;
238
+ prNumber?: number;
239
+ headSha?: string;
240
+ failingCheck?: string;
241
+ conflictBranch?: string;
242
+ conflictDetail?: string;
243
+ message?: string;
244
+ };
245
+ export type MergeQueueEntryDto = {
246
+ id: string;
247
+ stackId: string;
248
+ owner: string;
249
+ repo: string;
250
+ state: MergeQueueEntryState;
251
+ /** 1-based FIFO among this user's live entries; `0` on bounced history rows (MQ-7). */
252
+ position: number;
253
+ waitReason: string | null;
254
+ bounceReason: string | null;
255
+ bounceDetail: MergeQueueBounceDetail | null;
256
+ enqueuedBy: MergeQueueEnqueuedBy;
257
+ enqueuedVia: MergeQueueEnqueuedVia;
258
+ enqueuedAt: string;
259
+ attempts: number;
260
+ landedPrNumbers: number[];
261
+ /** Combined PR head the queue verified. Null until the worker records it. */
262
+ verifyHeadSha: string | null;
263
+ /** Target tip included in the verified PR head. Null until recorded. */
264
+ verifyBaseSha: string | null;
265
+ finishedAt: string | null;
266
+ };
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Wire types returned by the stack list/enrich HTTP endpoints.
3
+ *
4
+ * `api/src/stacks.ts#loadStackDtos` is the producer. Keep this file
5
+ * dependency-free and byte-for-byte aligned with the canonical wire contract.
6
+ */
7
+ export {};
@@ -0,0 +1,31 @@
1
+ /**
2
+ * GitHub PR base for `stack submit` (#1104).
3
+ *
4
+ * Git parent stays the previous layer (local rebase / `--onto`). Position 3+
5
+ * must open onto the review-unit park freeze so adopt does not retarget after
6
+ * `opened` and double-wake Vortex.
7
+ */
8
+ import type { StackDto } from "./api.js";
9
+ export declare const MG_PARK_PREFIX = "mg-park-";
10
+ export declare function isMgParkBranch(branch: string): boolean;
11
+ /** Live freeze already used as a GitHub base on this stack, if any. */
12
+ export declare function parkFreezeBranchFromStack(stack: StackDto): string | null;
13
+ export type SubmitLayerPlan = {
14
+ branch: string;
15
+ gitParent: string;
16
+ position: number;
17
+ /** GitHub `--base` when known. `null` means mint the freeze first. */
18
+ githubBase: string | null;
19
+ needsPark: boolean;
20
+ stackId: string | null;
21
+ };
22
+ export declare function planSubmitLayerBases(input: {
23
+ layers: readonly {
24
+ branch: string;
25
+ parentBranch: string;
26
+ }[];
27
+ trunk: string;
28
+ registered: readonly StackDto[];
29
+ owner: string;
30
+ repo: string;
31
+ }): SubmitLayerPlan[];
@@ -0,0 +1,69 @@
1
+ function registeredParentHit(stacks, parentBranch, owner, repo) {
2
+ const want = parentBranch.trim();
3
+ if (!want)
4
+ return null;
5
+ for (const stack of stacks) {
6
+ if (stack.owner !== owner || stack.repo !== repo)
7
+ continue;
8
+ for (const layer of stack.layers) {
9
+ if (layer.branch === want) {
10
+ return { stackId: stack.id, position: layer.position };
11
+ }
12
+ }
13
+ }
14
+ return null;
15
+ }
16
+ export const MG_PARK_PREFIX = "mg-park-";
17
+ export function isMgParkBranch(branch) {
18
+ return branch.trim().toLowerCase().startsWith(MG_PARK_PREFIX);
19
+ }
20
+ /** Live freeze already used as a GitHub base on this stack, if any. */
21
+ export function parkFreezeBranchFromStack(stack) {
22
+ for (const layer of stack.layers) {
23
+ const parent = layer.parentBranch?.trim() ?? "";
24
+ if (isMgParkBranch(parent))
25
+ return parent;
26
+ }
27
+ return null;
28
+ }
29
+ export function planSubmitLayerBases(input) {
30
+ const { layers, trunk, registered, owner, repo } = input;
31
+ const localIndex = new Map(layers.map((layer, i) => [layer.branch, i]));
32
+ const plans = [];
33
+ for (let i = 0; i < layers.length; i++) {
34
+ const layer = layers[i];
35
+ const gitParent = layer.parentBranch.trim() || trunk;
36
+ const priorLocal = localIndex.get(gitParent);
37
+ let position;
38
+ let stackId = null;
39
+ if (priorLocal !== undefined && plans[priorLocal]) {
40
+ position = plans[priorLocal].position + 1;
41
+ stackId = plans[priorLocal].stackId;
42
+ }
43
+ else {
44
+ const hit = registeredParentHit(registered, gitParent, owner, repo);
45
+ if (hit) {
46
+ position = hit.position + 1;
47
+ stackId = hit.stackId;
48
+ }
49
+ else {
50
+ position = 1;
51
+ }
52
+ }
53
+ const stack = stackId
54
+ ? registered.find((row) => row.id === stackId) ?? null
55
+ : null;
56
+ const existingPark = stack ? parkFreezeBranchFromStack(stack) : null;
57
+ const needsPark = position >= 3;
58
+ const githubBase = needsPark ? existingPark : gitParent;
59
+ plans.push({
60
+ branch: layer.branch,
61
+ gitParent,
62
+ position,
63
+ githubBase,
64
+ needsPark,
65
+ stackId,
66
+ });
67
+ }
68
+ return plans;
69
+ }
package/dist/ui/ansi.d.ts CHANGED
@@ -9,4 +9,6 @@ export declare const ansi: {
9
9
  brightGreen: (t: string) => string;
10
10
  invert: (t: string) => string;
11
11
  boldGreen: (t: string) => string;
12
+ /** Prompt / mark ink. Never 32 — that reads as sick dark green. */
13
+ boldBrightGreen: (t: string) => string;
12
14
  };
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() ? `${t}` : t),
27
+ /** Prompt / mark ink. Never 32 — that reads as sick dark green. */
28
+ boldBrightGreen: (t) => (computeEnabled() ? `${t}` : t),
27
29
  };
@@ -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
- export declare function printBannerHeader(): Promise<void>;
4
- export declare function shellPrompt(loggedIn: boolean): string;
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;