mergestorm 0.3.64 → 0.3.66
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 +28 -9
- package/dist/api.d.ts +63 -11
- package/dist/api.js +195 -6
- package/dist/automation-catalog.d.ts +5 -1
- package/dist/automation-catalog.js +1 -0
- package/dist/cli.js +21 -19
- package/dist/client/index.d.ts +3 -2
- package/dist/client/index.js +3 -2
- package/dist/commands/account.d.ts +3 -0
- package/dist/commands/account.js +75 -0
- package/dist/commands/browse.d.ts +1 -9
- package/dist/commands/browse.js +10 -8
- package/dist/commands/dismiss.d.ts +0 -2
- package/dist/commands/dismiss.js +2 -2
- package/dist/commands/help.js +3 -2
- package/dist/commands/issue.d.ts +24 -6
- package/dist/commands/issue.js +153 -31
- package/dist/commands/login.d.ts +2 -1
- package/dist/commands/login.js +50 -14
- package/dist/commands/logout.js +6 -7
- package/dist/commands/memory.d.ts +2 -3
- package/dist/commands/memory.js +8 -6
- package/dist/commands/registry.d.ts +1 -0
- package/dist/commands/registry.js +35 -11
- package/dist/commands/review-client.d.ts +0 -3
- package/dist/commands/review-client.js +3 -3
- package/dist/commands/review.d.ts +1 -1
- package/dist/commands/review.js +1 -1
- package/dist/commands/settings.d.ts +3 -5
- package/dist/commands/settings.js +4 -3
- package/dist/commands/stack.d.ts +19 -5
- package/dist/commands/stack.js +78 -4
- package/dist/commands/whoami.js +14 -10
- package/dist/config.d.ts +55 -2
- package/dist/config.js +264 -13
- package/dist/errors.d.ts +1 -1
- package/dist/gh.d.ts +0 -4
- package/dist/gh.js +4 -4
- package/dist/git.d.ts +1 -1
- package/dist/git.js +2 -2
- package/dist/memory-api.d.ts +1 -0
- package/dist/memory-api.js +2 -2
- package/dist/node-version.d.ts +0 -2
- package/dist/node-version.js +1 -1
- package/dist/pr-body.d.ts +4 -0
- package/dist/pr-body.js +15 -0
- package/dist/repo-identity.d.ts +2 -1
- package/dist/repo-identity.js +8 -4
- package/dist/server-publish.d.ts +1 -24
- package/dist/server-publish.js +9 -9
- package/dist/stack-blockers.d.ts +12 -38
- package/dist/stack-blockers.js +55 -33
- package/dist/stack-dto.d.ts +3 -0
- package/dist/stack-dto.js +15 -2
- package/dist/stack-meta.d.ts +2 -2
- package/dist/stack-meta.js +1 -1
- package/dist/stack-watch-loop.d.ts +2 -13
- package/dist/stack-watch-loop.js +12 -12
- package/dist/submit-pr-base.d.ts +0 -1
- package/dist/submit-pr-base.js +1 -1
- package/dist/ui/ansi.d.ts +0 -10
- package/dist/ui/ansi.js +2 -2
- package/dist/ui/banner.d.ts +3 -1
- package/dist/ui/banner.js +10 -3
- package/dist/ui/box.d.ts +0 -2
- package/dist/ui/box.js +1 -1
- package/dist/ui/prompt.d.ts +0 -11
- package/dist/ui/prompt.js +7 -7
- package/dist/ui/tabs.d.ts +2 -2
- package/dist/ui/tabs.js +1 -1
- package/dist/ui/width.d.ts +0 -9
- package/dist/ui/width.js +3 -3
- package/dist/web-api.d.ts +17 -0
- package/dist/web-api.js +30 -0
- package/package.json +1 -1
- package/skill/SKILL.md +4 -0
- package/skill/mergestorm-bounce-watch/SKILL.md +1 -1
package/dist/stack-blockers.d.ts
CHANGED
|
@@ -6,8 +6,8 @@ export type StackBlocker = {
|
|
|
6
6
|
blocker: string;
|
|
7
7
|
bounceKind: string | null;
|
|
8
8
|
};
|
|
9
|
-
export type StackBlockerReason = "restack_conflict" | "restack_failed" | "draft_pr" | "merge_conflict" | "ci_failed" | "vortex_failed" | "vortex_out_of_quota" | "vortex_skipped" | "vortex_findings_need_person" | "seam_failed" | "seam_findings" | "seam_not_running" | "seam_review_stuck" | "seam_rereview_not_running" | "seam_verdict_stale" | "unit_abandoned" | "land_gate" | "bounce" | "auto_land_off" | "auto_land_switched_off" | "auto_land_ci_pending" | "auto_land_members_remaining" | "auto_land_vortex_wait" | "auto_land_promote_failed" | "auto_land_ci_unreadable" | "auto_land_waited" | "queue_stalled" | "land_pr_missing";
|
|
10
|
-
|
|
9
|
+
export type StackBlockerReason = "restack_conflict" | "restack_failed" | "draft_pr" | "merge_conflict" | "ci_failed" | "vortex_failed" | "vortex_out_of_quota" | "vortex_skipped" | "vortex_findings_need_person" | "seam_failed" | "seam_findings" | "seam_not_running" | "seam_review_stuck" | "seam_rereview_not_running" | "seam_verdict_stale" | "unit_abandoned" | "land_gate" | "bounce" | "auto_land_off" | "auto_land_switched_off" | "auto_land_ci_pending" | "auto_land_members_remaining" | "auto_land_vortex_wait" | "auto_land_promote_failed" | "auto_land_ci_unreadable" | "auto_land_waited" | "queue_stalled" | "cyclone_stalled" | "land_pr_missing";
|
|
10
|
+
type ClassifiedStackBlocker = StackBlocker & {
|
|
11
11
|
reason: StackBlockerReason;
|
|
12
12
|
};
|
|
13
13
|
export type StackAgentBusy = {
|
|
@@ -113,6 +113,14 @@ export type StackRepairHint = {
|
|
|
113
113
|
since: string;
|
|
114
114
|
reason: string;
|
|
115
115
|
steps: string;
|
|
116
|
+
} | {
|
|
117
|
+
kind: "cyclone_stalled";
|
|
118
|
+
prNumber: number;
|
|
119
|
+
headSha: string | null;
|
|
120
|
+
branch: string;
|
|
121
|
+
runSha: string | null;
|
|
122
|
+
since: string | null;
|
|
123
|
+
steps: string;
|
|
116
124
|
} | {
|
|
117
125
|
kind: "queue_stalled";
|
|
118
126
|
prNumber: number;
|
|
@@ -159,42 +167,14 @@ export type StackLandGatePending = {
|
|
|
159
167
|
export declare function landGateIsPending(reason: string | null | undefined): boolean;
|
|
160
168
|
export declare function layerAgentsBusy(layer: StackLayerDto): StackAgentsBusy;
|
|
161
169
|
export declare function layerAgents(layer: StackLayerDto): StackLayerAgents;
|
|
162
|
-
export declare function isMgParkBase(branch: string | null | undefined): boolean;
|
|
163
|
-
/** GitHub DIRTY vs an mg-park-* freeze is not a merge conflict, verified or not. */
|
|
164
|
-
export declare function boundDirty(layer: StackLayerDto): boolean;
|
|
165
|
-
export declare function mergeabilityPending(layer: StackLayerDto): boolean;
|
|
166
170
|
export declare function sameHead(a: string | null | undefined, b: string | null | undefined): boolean;
|
|
167
171
|
export declare function currentLayer(stack: StackDto): StackLayerDto | undefined;
|
|
168
|
-
export declare const RESTACK_FAILED = "Restack failed";
|
|
169
|
-
export declare const SEAM_REVIEW_NOT_RUNNING = "Seam review pending, no review running";
|
|
170
|
-
export declare const VORTEX_REVIEW_OUT_OF_QUOTA = "Review skipped, out of review quota";
|
|
171
|
-
export declare const SEAM_REVIEW_STUCK = "Seam review stuck in reviewing, no review running";
|
|
172
|
-
export declare const SEAM_REREVIEW_NOT_RUNNING = "Seam re-review pending, no review running";
|
|
173
|
-
export declare const SEAM_VERDICT_STALE = "Seam verdict is for an older head, no review running";
|
|
174
172
|
export declare const VORTEX_FINDINGS_NEED_PERSON = "Vortex findings need a person";
|
|
175
|
-
export declare const CYCLONE_NOT_RUN_ATTENTION_MS: number;
|
|
176
|
-
export declare const CYCLONE_NOT_RUN_REASON = "not_run";
|
|
177
173
|
export declare function cycloneStatusWords(layer: Pick<StackLayerDto, "cycloneStatus" | "cycloneHandoff" | "headSha">): string | null;
|
|
178
174
|
export declare function autoResolveActive(stack: StackDto, layer: StackLayerDto): boolean;
|
|
179
175
|
export declare function autoFixCiActive(stack: StackDto, layer: StackLayerDto): boolean;
|
|
180
176
|
export declare const CI_DID_NOT_FINISH_BLOCKER = "CI did not finish \u2014 left for a person";
|
|
181
|
-
export declare
|
|
182
|
-
export declare const VORTEX_AUTO_REVIEW_OFF = "Vortex auto-review is off, so this head was not reviewed";
|
|
183
|
-
export declare const VORTEX_BILLING_SKIPPED = "Vortex skipped this head: it could not confirm billing";
|
|
184
|
-
export declare const VORTEX_REVIEW_INCOMPLETE = "Vortex reviewed only part of this PR";
|
|
185
|
-
export declare const VORTEX_IGNORED_AUTHOR = "Vortex skipped this head: the PR author is on the ignored authors list";
|
|
186
|
-
export declare const VORTEX_REPO_DISABLED = "Vortex skipped this head: reviews are turned off for this repository";
|
|
187
|
-
export declare const VORTEX_RETRY_SCHEDULED = "Vortex hit a temporary error on this head and will retry by itself";
|
|
188
|
-
export declare const VORTEX_REVIEW_FAILED = "Review failed";
|
|
189
|
-
export declare function vortexRetryScheduled(layer: StackLayerDto): boolean;
|
|
190
|
-
export declare const AUTO_LAND_CI_PENDING_ATTENTION_MS: number;
|
|
191
|
-
export declare const AUTO_LAND_VORTEX_WAIT_ATTENTION_MS: number;
|
|
192
|
-
export declare const AUTO_LAND_PROMOTE_FAILED_ATTEMPTS = 3;
|
|
193
|
-
export declare const AUTO_LAND_CI_FAILED_BLOCKER = "CI failed \u2014 Auto land reads a failing check on GitHub at this head";
|
|
194
|
-
export declare const AUTO_LAND_WAIT_ATTENTION_MS: number;
|
|
195
|
-
export declare const AUTO_LAND_CI_UNREADABLE_ATTENTION_MS: number;
|
|
196
|
-
export declare function autoLandWaitLayer(stack: StackDto): StackLayerDto | undefined;
|
|
197
|
-
export declare const MERGE_QUEUE_STALL_ATTENTION_MS: number;
|
|
177
|
+
export declare const CYCLONE_STALLED_PREFIX = "Cyclone stalled: ";
|
|
198
178
|
export type StackQueueWait = {
|
|
199
179
|
prNumber: number;
|
|
200
180
|
headSha: string | null;
|
|
@@ -216,15 +196,8 @@ export declare function mergeQueueWait(queue: readonly MergeQueueEntryDto[], now
|
|
|
216
196
|
export declare function conflictLiveParent(stack: StackDto, layer: StackLayerDto): string | null;
|
|
217
197
|
export declare function conflictRepairSteps(branch: string, liveParent: string | null): string;
|
|
218
198
|
export declare const BREEZE_BRANCH_PREFIX = "breeze/";
|
|
219
|
-
export declare function isBreezeBranch(branch: string | null | undefined): boolean;
|
|
220
|
-
export declare function breezeConflictRepairSteps(branch: string, liveParent: string | null, prNumber: number): string;
|
|
221
|
-
export declare function restackFailedRepairSteps(branch: string, liveParent: string | null, failure: {
|
|
222
|
-
kind: string;
|
|
223
|
-
detail?: string | null;
|
|
224
|
-
}, prNumber?: number): string;
|
|
225
199
|
export declare function agentsCannotClear(reason: StackBlockerReason, stack?: StackDto, layer?: StackLayerDto): boolean;
|
|
226
200
|
export declare function stackRepair(stack: StackDto, attention: ClassifiedStackBlocker | null, layer: StackLayerDto | undefined, bounce: MergeQueueEntryDto | undefined, queueWait?: MergeQueueWait | null, nowMs?: number): StackRepairHint | null;
|
|
227
|
-
export declare const LAND_PR_MISSING_PREFIX = "No open land PR for ";
|
|
228
201
|
/** Pair-gate attention plus live hard blocks on every other open layer. */
|
|
229
202
|
export declare function stackBlockers(stack: StackDto, entries?: MergeQueueEntryDto[], cursor?: Pick<StackWatchCursor, "bounceId" | "afterFinishedAt">, nowMs?: number): {
|
|
230
203
|
attention: StackBlocker | null;
|
|
@@ -243,3 +216,4 @@ export declare function stackBlockers(stack: StackDto, entries?: MergeQueueEntry
|
|
|
243
216
|
};
|
|
244
217
|
/** Compact contract text shared by status and wait summaries. */
|
|
245
218
|
export declare function stackBlockersSummary(attention: Pick<StackBlocker, "prNumber" | "blocker"> | null, issues: StackBlocker[], busy?: readonly StackAgentBusy[], landGatePending?: Pick<StackLandGatePending, "prNumber" | "reason"> | null, actAfter?: StackHeldBlocker["actAfter"] | null, queueWait?: Pick<StackQueueWait, "prNumber" | "text"> | null): string;
|
|
219
|
+
export {};
|
package/dist/stack-blockers.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { CYCLONE_CI_FIX_HELD_REASON, CYCLONE_FIX_HELD_REASON, deriveStackAgentsBusy, mergeQueueBounceLabel, restackRetryPending, } from "./stack-dto.js";
|
|
1
|
+
import { CYCLONE_CI_FIX_HELD_REASON, CYCLONE_FIX_HELD_REASON, deriveStackAgentsBusy, mergeQueueBounceLabel, restackRetryPending, STACK_AGENT_PATCHING_STALE_MS, stalledCycloneRun, } from "./stack-dto.js";
|
|
2
2
|
export function landGateIsPending(reason) {
|
|
3
3
|
const token = reason?.trim().replace(/^landing blocked:\s*/i, "").match(/^[a-z_]+/i)?.[0] ?? "";
|
|
4
4
|
return /_(?:pending|running)$/i.test(token);
|
|
@@ -18,11 +18,11 @@ export function layerAgents(layer) {
|
|
|
18
18
|
busy: { vortex: busy.vortex, cyclone: busy.cyclone },
|
|
19
19
|
};
|
|
20
20
|
}
|
|
21
|
-
|
|
21
|
+
function isMgParkBase(branch) {
|
|
22
22
|
return (branch ?? "").trim().toLowerCase().startsWith("mg-park-");
|
|
23
23
|
}
|
|
24
24
|
/** GitHub DIRTY vs an mg-park-* freeze is not a merge conflict, verified or not. */
|
|
25
|
-
|
|
25
|
+
function boundDirty(layer) {
|
|
26
26
|
if (isMgParkBase(layer.parentBranch))
|
|
27
27
|
return false;
|
|
28
28
|
if (layer.mergeable == null)
|
|
@@ -30,7 +30,7 @@ export function boundDirty(layer) {
|
|
|
30
30
|
return (!layer.headSha || layer.mergeableHeadSha === layer.headSha) &&
|
|
31
31
|
(layer.mergeable === false || layer.mergeableState?.toLowerCase() === "dirty");
|
|
32
32
|
}
|
|
33
|
-
|
|
33
|
+
function mergeabilityPending(layer) {
|
|
34
34
|
if (isMgParkBase(layer.parentBranch))
|
|
35
35
|
return false;
|
|
36
36
|
return layer.mergeable == null && !!layer.headSha && layer.mergeableHeadSha === layer.headSha &&
|
|
@@ -51,12 +51,12 @@ export function currentLayer(stack) {
|
|
|
51
51
|
return promote[0] ?? open.find((layer) => layer.prNumber === stack.unit?.landPrNumber) ??
|
|
52
52
|
(land && land.prNumber > 0 && land.state !== "merged" && land.state !== "closed" ? land : undefined);
|
|
53
53
|
}
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
54
|
+
const RESTACK_FAILED = "Restack failed";
|
|
55
|
+
const SEAM_REVIEW_NOT_RUNNING = "Seam review pending, no review running";
|
|
56
|
+
const VORTEX_REVIEW_OUT_OF_QUOTA = "Review skipped, out of review quota";
|
|
57
|
+
const SEAM_REVIEW_STUCK = "Seam review stuck in reviewing, no review running";
|
|
58
|
+
const SEAM_REREVIEW_NOT_RUNNING = "Seam re-review pending, no review running";
|
|
59
|
+
const SEAM_VERDICT_STALE = "Seam verdict is for an older head, no review running";
|
|
60
60
|
const STUCK_SEAM_REASONS = ["seam_review_stuck", "seam_rereview_not_running", "seam_verdict_stale"];
|
|
61
61
|
function coreReviewDoneAtHead(layer) {
|
|
62
62
|
const review = layer.vortexReview;
|
|
@@ -72,8 +72,8 @@ function vortexFindingsRestAtHead(layer) {
|
|
|
72
72
|
function vortexChangesRequestedAtHead(layer) {
|
|
73
73
|
return vortexFindingsRestAtHead(layer) && layer.reviewStatus === "changes_requested";
|
|
74
74
|
}
|
|
75
|
-
|
|
76
|
-
|
|
75
|
+
const CYCLONE_NOT_RUN_ATTENTION_MS = 30 * 60_000;
|
|
76
|
+
const CYCLONE_NOT_RUN_REASON = "not_run";
|
|
77
77
|
function cycloneNotRunAtHead(layer, nowMs) {
|
|
78
78
|
const review = layer.vortexReview;
|
|
79
79
|
if (layer.cycloneFindingsTakenAtHead !== false || layer.vortexStatus !== "findings")
|
|
@@ -109,6 +109,7 @@ const CYCLONE_HANDOFF_WORDS = {
|
|
|
109
109
|
unsupported_language: "the findings are in a language Cyclone does not patch",
|
|
110
110
|
policy_owner_changed: "the stack's owner changed while the patch was queued",
|
|
111
111
|
patch_job_lost: "Cyclone's patch job was lost before it finished",
|
|
112
|
+
run_deadline: "Cyclone's patch ran past its time limit and stopped without pushing",
|
|
112
113
|
pr_state_unavailable: "Cyclone could not read the PR's state, so it pushed nothing",
|
|
113
114
|
left_for_human: "Cyclone did not patch these findings and pushed nothing",
|
|
114
115
|
hold: "the review told Cyclone to hold, so it did not patch these findings",
|
|
@@ -175,7 +176,7 @@ export function autoFixCiActive(stack, layer) {
|
|
|
175
176
|
!sameHead(layer.cycloneCiHandoff?.headSha, layer.headSha);
|
|
176
177
|
}
|
|
177
178
|
export const CI_DID_NOT_FINISH_BLOCKER = "CI did not finish — left for a person";
|
|
178
|
-
|
|
179
|
+
function pendingCiLeftForPerson(stack, layer) {
|
|
179
180
|
return layer.ciStatus === "pending" && layer.checks?.cancelledOnly === true &&
|
|
180
181
|
sameHead(layer.checks.namedRunsCompleteHeadSha, layer.headSha) && !isLandPrLayer(stack, layer) &&
|
|
181
182
|
sameHead(layer.cycloneCiHandoff?.headSha, layer.headSha);
|
|
@@ -200,6 +201,7 @@ const CYCLONE_HELD_FINDINGS_REASONS = [
|
|
|
200
201
|
"pr_state_unavailable",
|
|
201
202
|
"vortex_patch_cap",
|
|
202
203
|
"patch_job_lost",
|
|
204
|
+
"run_deadline",
|
|
203
205
|
"unsupported_language",
|
|
204
206
|
"credits_exhausted",
|
|
205
207
|
"no_billing",
|
|
@@ -227,13 +229,13 @@ function quotaSkippedReview(layer) {
|
|
|
227
229
|
return review?.status === "skipped" && review.skip_reason === "quota_exceeded" &&
|
|
228
230
|
sameHead(review.head_sha, layer.headSha);
|
|
229
231
|
}
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
232
|
+
const VORTEX_AUTO_REVIEW_OFF = "Vortex auto-review is off, so this head was not reviewed";
|
|
233
|
+
const VORTEX_BILLING_SKIPPED = "Vortex skipped this head: it could not confirm billing";
|
|
234
|
+
const VORTEX_REVIEW_INCOMPLETE = "Vortex reviewed only part of this PR";
|
|
235
|
+
const VORTEX_IGNORED_AUTHOR = "Vortex skipped this head: the PR author is on the ignored authors list";
|
|
236
|
+
const VORTEX_REPO_DISABLED = "Vortex skipped this head: reviews are turned off for this repository";
|
|
237
|
+
const VORTEX_RETRY_SCHEDULED = "Vortex hit a temporary error on this head and will retry by itself";
|
|
238
|
+
const VORTEX_REVIEW_FAILED = "Review failed";
|
|
237
239
|
const VORTEX_SKIP_BLOCKERS = {
|
|
238
240
|
auto_review_off: VORTEX_AUTO_REVIEW_OFF,
|
|
239
241
|
billing_blocked: VORTEX_BILLING_SKIPPED,
|
|
@@ -241,7 +243,7 @@ const VORTEX_SKIP_BLOCKERS = {
|
|
|
241
243
|
ignored_author: VORTEX_IGNORED_AUTHOR,
|
|
242
244
|
repo_disabled: VORTEX_REPO_DISABLED,
|
|
243
245
|
};
|
|
244
|
-
|
|
246
|
+
function vortexRetryScheduled(layer) {
|
|
245
247
|
const review = layer.vortexReview;
|
|
246
248
|
const reason = review?.skip_reason?.trim();
|
|
247
249
|
return (reason === "retry_scheduled" || reason === "post_delayed") && sameHead(review?.head_sha, layer.headSha);
|
|
@@ -388,6 +390,9 @@ function layerAttention(stack, layer, queue, cursor, nowMs) {
|
|
|
388
390
|
if (landing && !landGateIsPending(landing))
|
|
389
391
|
return hardBlock(landing, "land_gate");
|
|
390
392
|
}
|
|
393
|
+
const stalled = stalledCycloneRun(layer, nowMs);
|
|
394
|
+
if (stalled)
|
|
395
|
+
return hardBlock(cycloneStalledBlocker(stalled, nowMs), "cyclone_stalled");
|
|
391
396
|
if (vortexFindingsLeftForPerson(stack, layer, nowMs))
|
|
392
397
|
return hardBlock(VORTEX_FINDINGS_NEED_PERSON, "vortex_findings_need_person");
|
|
393
398
|
}
|
|
@@ -408,6 +413,11 @@ function layerAttention(stack, layer, queue, cursor, nowMs) {
|
|
|
408
413
|
return { blocker: label, reason: "bounce", bounceKind: kind, bounce };
|
|
409
414
|
}
|
|
410
415
|
const UNIT_ABANDONED_BLOCKER_PREFIX = "Review unit abandoned: based on ";
|
|
416
|
+
export const CYCLONE_STALLED_PREFIX = "Cyclone stalled: ";
|
|
417
|
+
function cycloneStalledBlocker(run, nowMs) {
|
|
418
|
+
const minutes = Math.floor((nowMs - Date.parse(run.startedAt ?? "")) / 60_000);
|
|
419
|
+
return `${CYCLONE_STALLED_PREFIX}patching ${run.sha?.slice(0, 7) ?? "this PR"} for ${minutes}m with no push or result`;
|
|
420
|
+
}
|
|
411
421
|
function unitAbandonedUnder(stack, layer) {
|
|
412
422
|
if (stack.unit?.state !== "abandoned")
|
|
413
423
|
return false;
|
|
@@ -447,15 +457,15 @@ function switchedOffByAnotherActor(stack) {
|
|
|
447
457
|
const reason = stack.autoLandOff?.reason;
|
|
448
458
|
return reason && AUTO_LAND_SWITCHED_OFF_BY[reason] ? reason : undefined;
|
|
449
459
|
}
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
460
|
+
const AUTO_LAND_CI_PENDING_ATTENTION_MS = 45 * 60_000;
|
|
461
|
+
const AUTO_LAND_VORTEX_WAIT_ATTENTION_MS = 30 * 60_000;
|
|
462
|
+
const AUTO_LAND_PROMOTE_FAILED_ATTEMPTS = 3;
|
|
463
|
+
const AUTO_LAND_CI_FAILED_BLOCKER = "CI failed — Auto land reads a failing check on GitHub at this head";
|
|
454
464
|
const AUTO_LAND_CI_PENDING_PREFIX = "CI pending for ";
|
|
455
465
|
const AUTO_LAND_VORTEX_WAIT_PREFIX = "No green Vortex review for ";
|
|
456
466
|
const AUTO_LAND_PROMOTE_FAILED_PREFIX = "Promote failed ";
|
|
457
|
-
|
|
458
|
-
|
|
467
|
+
const AUTO_LAND_WAIT_ATTENTION_MS = 45 * 60_000;
|
|
468
|
+
const AUTO_LAND_CI_UNREADABLE_ATTENTION_MS = 10 * 60_000;
|
|
459
469
|
const AUTO_LAND_CI_UNREADABLE_PREFIX = "Mergestorm cannot read ";
|
|
460
470
|
const AUTO_LAND_WAITED_PREFIX = "Auto land has waited ";
|
|
461
471
|
const AUTO_LAND_MEMBERS_REMAINING_PREFIX = "The land PR cannot be queued: ";
|
|
@@ -510,7 +520,7 @@ function lastPrLayer(stack) {
|
|
|
510
520
|
return land;
|
|
511
521
|
return stack.layers.filter((layer) => layer.prNumber > 0).sort((a, b) => b.position - a.position)[0];
|
|
512
522
|
}
|
|
513
|
-
|
|
523
|
+
function autoLandWaitLayer(stack) {
|
|
514
524
|
const wait = armedAutoLandWait(stack);
|
|
515
525
|
if (!wait)
|
|
516
526
|
return undefined;
|
|
@@ -578,7 +588,7 @@ function autoLandWaitNamed(stack, layer, nowMs) {
|
|
|
578
588
|
? { blocker: `${AUTO_LAND_WAITED_PREFIX}${minutes}m: ${autoLandWaitWords(wait.reason)}`, reason: "auto_land_waited" }
|
|
579
589
|
: null;
|
|
580
590
|
}
|
|
581
|
-
|
|
591
|
+
const MERGE_QUEUE_STALL_ATTENTION_MS = 45 * 60_000;
|
|
582
592
|
const MERGE_QUEUE_STALLED_PREFIX = "Merge queue stuck: ";
|
|
583
593
|
const LIVE_QUEUE_STATES = ["queued", "running", "waiting"];
|
|
584
594
|
function minutesOf(ms) {
|
|
@@ -680,15 +690,15 @@ export function conflictRepairSteps(branch, liveParent) {
|
|
|
680
690
|
return `Merge ${liveParent} into ${branch}: git fetch origin, check out ${branch} at its live remote head, git merge origin/${liveParent}, resolve the conflicts, run the tests, then an ordinary git push (no force). Do not rebase onto trunk, main, or mg-park-*, and do not retarget the PR base.`;
|
|
681
691
|
}
|
|
682
692
|
export const BREEZE_BRANCH_PREFIX = "breeze/";
|
|
683
|
-
|
|
693
|
+
function isBreezeBranch(branch) {
|
|
684
694
|
return typeof branch === "string" && branch.startsWith(BREEZE_BRANCH_PREFIX);
|
|
685
695
|
}
|
|
686
|
-
|
|
696
|
+
function breezeConflictRepairSteps(branch, liveParent, prNumber) {
|
|
687
697
|
if (!liveParent)
|
|
688
698
|
return conflictRepairSteps(branch, liveParent);
|
|
689
699
|
return `${branch} is a Breeze branch: Breeze updates it only with open_pull_request, which publishes single-parent commits, so it cannot publish a merge of ${liveParent}. Rebase it in the Breeze chat that opened #${prNumber}: fetch ${liveParent} with fetch_repository, git rebase origin/${liveParent}, resolve the conflicts, run the tests, then call open_pull_request again. Or have Cyclone resolve it: cyclone_start with task resolve_conflicts and pr_number ${prNumber}. Do not retarget the PR base.`;
|
|
690
700
|
}
|
|
691
|
-
|
|
701
|
+
function restackFailedRepairSteps(branch, liveParent, failure, prNumber) {
|
|
692
702
|
const why = `Mergestorm could not restack ${branch}${liveParent ? ` onto ${liveParent}` : ""} (${failure.kind}${failure.detail?.trim() ? `: ${failure.detail.trim()}` : ""}) and will not try this head again.`;
|
|
693
703
|
if (!liveParent) {
|
|
694
704
|
return `${why} The live parent of ${branch} is unknown (it is never an mg-park-* freeze). Do not merge or rebase anything; stop and tell the human.`;
|
|
@@ -815,6 +825,18 @@ export function stackRepair(stack, attention, layer, bounce, queueWait = null, n
|
|
|
815
825
|
steps: `Auto land is off for this stack (autoLandOff reason ${offReason}: ${AUTO_LAND_SWITCHED_OFF_BY[offReason] ?? offReason}), so nothing will enqueue it and it will not land by itself. No bounce did this and nothing in the layers needs a change: do not patch or push for it. A person or another agent session may have switched it off on purpose, so do not turn Auto land back on yourself. Tell the human that Auto land is off and why, and turn it back on (mg stack set ${stack.id} --auto-land on) only when they say so.`,
|
|
816
826
|
};
|
|
817
827
|
}
|
|
828
|
+
if (reason === "cyclone_stalled") {
|
|
829
|
+
const run = stalledCycloneRun(layer, nowMs);
|
|
830
|
+
return {
|
|
831
|
+
kind: "cyclone_stalled",
|
|
832
|
+
prNumber: layer.prNumber,
|
|
833
|
+
headSha: layer.headSha ?? null,
|
|
834
|
+
branch: layer.branch,
|
|
835
|
+
runSha: run?.sha ?? null,
|
|
836
|
+
since: run?.startedAt ?? null,
|
|
837
|
+
steps: `Cyclone has been patching #${layer.prNumber}${run?.sha ? ` (a run for ${run.sha.slice(0, 7)})` : ""} since ${run?.startedAt ?? "an unknown time"} without pushing or finishing, past the ${STACK_AGENT_PATCHING_STALE_MS / 60_000}-minute limit, so the watch no longer waits for it. Patch the open Vortex findings at ${layer.headSha ?? "the head"} yourself: verify each one against a checkout of ${layer.branch}, fix the real ones with the smallest patch and an ordinary push, and dismiss the ones you judge wrong with mg dismiss. If ${layer.branch} moves before you push, Cyclone pushed after all: re-read stack_status instead of pushing. Tell the human Cyclone stalled on this PR.`,
|
|
838
|
+
};
|
|
839
|
+
}
|
|
818
840
|
if (reason === "vortex_failed") {
|
|
819
841
|
return {
|
|
820
842
|
kind: "vortex_failed",
|
|
@@ -933,7 +955,7 @@ export function stackRepair(stack, attention, layer, bounce, queueWait = null, n
|
|
|
933
955
|
}
|
|
934
956
|
return null;
|
|
935
957
|
}
|
|
936
|
-
|
|
958
|
+
const LAND_PR_MISSING_PREFIX = "No open land PR for ";
|
|
937
959
|
function landPrMissing(stack, candidate) {
|
|
938
960
|
const unit = stack.unit;
|
|
939
961
|
if (!unit || candidate || stack.archivedAt || unit.state === "landed" || unit.state === "abandoned")
|
package/dist/stack-dto.d.ts
CHANGED
|
@@ -120,7 +120,9 @@ export type StackAgentsBusy = {
|
|
|
120
120
|
headSha: string | null;
|
|
121
121
|
};
|
|
122
122
|
export declare const STACK_AGENT_QUEUED_STALE_MS: number;
|
|
123
|
+
export declare const STACK_AGENT_PATCHING_STALE_MS: number;
|
|
123
124
|
export declare const AUTO_RESOLVE_ENQUEUE_GRACE_MS: number;
|
|
125
|
+
export declare function stalledCycloneRun(layer: Pick<StackLayerDto, "agentRuns">, nowMs?: number): StackAgentRun | null;
|
|
124
126
|
export declare function deriveStackAgentsBusy(layer: Pick<StackLayerDto, "headSha" | "vortexStatus" | "cycloneStatus" | "agentRuns" | "vortexReview" | "cycloneConflictPendingSince" | "cycloneCiPendingSince">, opts?: {
|
|
125
127
|
cycloneClaimed?: boolean;
|
|
126
128
|
nowMs?: number;
|
|
@@ -238,6 +240,7 @@ export type StackLayerDto = {
|
|
|
238
240
|
headSha?: string | null;
|
|
239
241
|
/** Head SHA that produced mergeable / mergeableState. */
|
|
240
242
|
mergeableHeadSha?: string | null;
|
|
243
|
+
staleBaseMergeable?: boolean;
|
|
241
244
|
/** GitHub PR draft flag from enrich; the auto-land watcher never enqueues a draft. */
|
|
242
245
|
draft?: boolean | null;
|
|
243
246
|
/**
|
package/dist/stack-dto.js
CHANGED
|
@@ -26,6 +26,7 @@ export const AUTO_LAND_OFF_REASONS = ["user", "archive", "watcher", "cancelled",
|
|
|
26
26
|
export const CYCLONE_FIX_HELD_REASON = "fix_held";
|
|
27
27
|
export const CYCLONE_CI_FIX_HELD_REASON = "ci_fix_held";
|
|
28
28
|
export const STACK_AGENT_QUEUED_STALE_MS = 15 * 60 * 1000;
|
|
29
|
+
export const STACK_AGENT_PATCHING_STALE_MS = 60 * 60 * 1000;
|
|
29
30
|
export const AUTO_RESOLVE_ENQUEUE_GRACE_MS = 2 * 60_000;
|
|
30
31
|
function agentShaMayBeHead(sha, head) {
|
|
31
32
|
const left = sha?.trim().toLowerCase();
|
|
@@ -34,6 +35,17 @@ function agentShaMayBeHead(sha, head) {
|
|
|
34
35
|
return false;
|
|
35
36
|
return left.startsWith(right) || right.startsWith(left);
|
|
36
37
|
}
|
|
38
|
+
export function stalledCycloneRun(layer, nowMs = Date.now()) {
|
|
39
|
+
const patching = (layer.agentRuns ?? [])
|
|
40
|
+
.filter((run) => !run.finishedAt && run.agent === "cyclone" && run.status === "patching")
|
|
41
|
+
.map((run) => ({ run, startedMs: Date.parse(run.startedAt ?? "") }));
|
|
42
|
+
if (!patching.length)
|
|
43
|
+
return null;
|
|
44
|
+
if (!patching.every(({ startedMs }) => Number.isFinite(startedMs) && nowMs - startedMs > STACK_AGENT_PATCHING_STALE_MS)) {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
return patching.reduce((newest, entry) => (entry.startedMs > newest.startedMs ? entry : newest)).run;
|
|
48
|
+
}
|
|
37
49
|
export function deriveStackAgentsBusy(layer, opts = {}) {
|
|
38
50
|
const headSha = layer.headSha?.trim() || null;
|
|
39
51
|
const nowMs = opts.nowMs ?? Date.now();
|
|
@@ -54,8 +66,9 @@ export function deriveStackAgentsBusy(layer, opts = {}) {
|
|
|
54
66
|
const sinceMs = Date.parse(since ?? "");
|
|
55
67
|
return Number.isFinite(sinceMs) && nowMs - sinceMs <= AUTO_RESOLVE_ENQUEUE_GRACE_MS;
|
|
56
68
|
};
|
|
57
|
-
const
|
|
58
|
-
|
|
69
|
+
const stalled = stalledCycloneRun(layer, nowMs) !== null;
|
|
70
|
+
const cyclone = (!stalled && (opts.cycloneClaimed === true || layer.cycloneStatus === "patching" ||
|
|
71
|
+
runs.some((run) => run.agent === "cyclone" && run.status === "patching"))) ||
|
|
59
72
|
runs.some((run) => run.agent === "cyclone" && agentShaMayBeHead(run.sha, headSha) && queuedFresh(run)) ||
|
|
60
73
|
withinGrace(layer.cycloneConflictPendingSince) ||
|
|
61
74
|
withinGrace(layer.cycloneCiPendingSince);
|
package/dist/stack-meta.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ export type StackLayerMeta = {
|
|
|
3
3
|
/** Parent branch name (trunk for the bottom layer). */
|
|
4
4
|
parentBranch: string;
|
|
5
5
|
};
|
|
6
|
-
|
|
6
|
+
type LocalStack = {
|
|
7
7
|
layers: StackLayerMeta[];
|
|
8
8
|
autoEnqueueWhenReady?: boolean;
|
|
9
9
|
/** Per-open override carried from `stack create` to `stack submit`; absent = account flag. */
|
|
@@ -22,7 +22,6 @@ export type StackMeta = {
|
|
|
22
22
|
export declare function stackMetaPath(cwd?: string, stacksRoot?: string): string;
|
|
23
23
|
export declare function loadStackMeta(cwd?: string, stacksRoot?: string): Promise<StackMeta | null>;
|
|
24
24
|
export declare function emptyStackMeta(trunk: string): StackMeta;
|
|
25
|
-
export declare function activeStack(meta: StackMeta): LocalStack;
|
|
26
25
|
export declare function activeLayers(meta: StackMeta): StackLayerMeta[];
|
|
27
26
|
export declare function setActiveAutoEnqueue(meta: StackMeta, enabled: boolean | undefined): StackMeta;
|
|
28
27
|
/** Per-open automation flags remembered on the active local stack until submit. */
|
|
@@ -45,3 +44,4 @@ export declare function appendLayer(meta: StackMeta, layer: StackLayerMeta): Sta
|
|
|
45
44
|
export declare function selectStackForCreate(meta: StackMeta, parentBranch: string): StackMeta;
|
|
46
45
|
/** Drop the active stack after a successful submit. */
|
|
47
46
|
export declare function removeActiveStack(meta: StackMeta): StackMeta;
|
|
47
|
+
export {};
|
package/dist/stack-meta.js
CHANGED
|
@@ -102,7 +102,7 @@ export function emptyStackMeta(trunk) {
|
|
|
102
102
|
active: 0,
|
|
103
103
|
};
|
|
104
104
|
}
|
|
105
|
-
|
|
105
|
+
function activeStack(meta) {
|
|
106
106
|
const stack = meta.stacks[meta.active];
|
|
107
107
|
if (!stack) {
|
|
108
108
|
throw new Error("Stack authoring state has an out-of-range active stack; run `mg stack reset --force` to clear it.");
|
|
@@ -1,21 +1,14 @@
|
|
|
1
1
|
import type { Config } from "./config.js";
|
|
2
2
|
import { readLandedQueueEntry } from "./api.js";
|
|
3
3
|
import { pollStackWatch, type StackWatchCursor, type StackWatchEnvelope } from "./stack-watch.js";
|
|
4
|
-
export declare const STACK_WATCH_MARKER = "MS-WATCH";
|
|
5
4
|
export declare const STACK_WATCH_EXIT: {
|
|
6
5
|
readonly landed: 0;
|
|
7
6
|
readonly attention: 3;
|
|
8
7
|
readonly timeout: 5;
|
|
9
8
|
};
|
|
10
9
|
export declare const STACK_WATCH_SLICE_MS = 300000;
|
|
11
|
-
export declare const STACK_WATCH_RECHECK_MS = 30000;
|
|
12
|
-
export declare const STACK_WATCH_FAILED_RETRY_MS = 15000;
|
|
13
|
-
export declare const STACK_WATCH_RATE_LIMIT_WAIT_MS = 30000;
|
|
14
|
-
export declare const STACK_WATCH_TRANSIENT_WINDOW_MS: number;
|
|
15
|
-
export declare const STACK_WATCH_TRANSIENT_MAX_WAIT_MS = 60000;
|
|
16
|
-
export declare function isTransientWatchFailure(err: unknown): boolean;
|
|
17
10
|
export type StackWatchUntil = "attention" | "landed";
|
|
18
|
-
|
|
11
|
+
type StackWatchOutcome = "attention" | "landed" | "failed" | "timeout" | "aborted";
|
|
19
12
|
export type RunStackWatchOptions = {
|
|
20
13
|
until?: StackWatchUntil;
|
|
21
14
|
ignore?: readonly string[];
|
|
@@ -35,10 +28,6 @@ export type StackWatchLoopResult = {
|
|
|
35
28
|
exitCode: number;
|
|
36
29
|
envelope: StackWatchEnvelope | null;
|
|
37
30
|
};
|
|
38
|
-
export declare function blockerWithoutElapsedMinutes(blocker: string | null | undefined): string;
|
|
39
|
-
export declare function stackWatchAttentionKey(envelope: Pick<StackWatchEnvelope, "prNumber" | "headSha" | "blocker">): string;
|
|
40
|
-
export declare function stackWatchAttentionLine(envelope: StackWatchEnvelope): string;
|
|
41
|
-
export declare function stackWatchLandedLine(envelope: StackWatchEnvelope): string;
|
|
42
31
|
export declare function stackWatchFailedLine(stackId: string, message: string): string;
|
|
43
|
-
export declare function stackWatchTimeoutLine(stackId: string, maxMs: number): string;
|
|
44
32
|
export declare function runStackWatch(cfg: Config, stackId: string, opts?: RunStackWatchOptions): Promise<StackWatchLoopResult>;
|
|
33
|
+
export {};
|
package/dist/stack-watch-loop.js
CHANGED
|
@@ -3,16 +3,16 @@ import { API_TIMEOUT_PREFIX, DEFAULT_API_TIMEOUT_MS, LandedQueueReadError, readL
|
|
|
3
3
|
import { isTransientReviewPollError } from "./commands/review-client.js";
|
|
4
4
|
import { stackWatchBackgroundCommand } from "./stack-watch-obligation.js";
|
|
5
5
|
import { pollStackWatch, StackWatchError, StackWatchTimeoutError, } from "./stack-watch.js";
|
|
6
|
-
|
|
6
|
+
const STACK_WATCH_MARKER = "MS-WATCH";
|
|
7
7
|
export const STACK_WATCH_EXIT = { landed: 0, attention: 3, timeout: 5 };
|
|
8
8
|
export const STACK_WATCH_SLICE_MS = 300_000;
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
9
|
+
const STACK_WATCH_RECHECK_MS = 30_000;
|
|
10
|
+
const STACK_WATCH_FAILED_RETRY_MS = 15_000;
|
|
11
|
+
const STACK_WATCH_RATE_LIMIT_WAIT_MS = 30_000;
|
|
12
|
+
const STACK_WATCH_TRANSIENT_WINDOW_MS = 15 * 60_000;
|
|
13
|
+
const STACK_WATCH_TRANSIENT_MAX_WAIT_MS = 60_000;
|
|
14
14
|
const TRANSIENT_WATCH_FAILURE = /HTTP (?:408|429|5\d\d)|snapshot missing|fetch failed|ECONNRESET|ETIMEDOUT|ENOTFOUND|EAI_AGAIN|socket hang up|network|connection/i;
|
|
15
|
-
|
|
15
|
+
function isTransientWatchFailure(err) {
|
|
16
16
|
if (!(err instanceof Error))
|
|
17
17
|
return false;
|
|
18
18
|
if (err instanceof LandedQueueReadError)
|
|
@@ -24,24 +24,24 @@ export function isTransientWatchFailure(err) {
|
|
|
24
24
|
return err.message.startsWith(API_TIMEOUT_PREFIX) || TRANSIENT_WATCH_FAILURE.test(err.message);
|
|
25
25
|
}
|
|
26
26
|
const ELAPSED_MINUTES = /\b\d+m\b/g;
|
|
27
|
-
|
|
27
|
+
function blockerWithoutElapsedMinutes(blocker) {
|
|
28
28
|
return (blocker ?? "").replace(ELAPSED_MINUTES, "Nm");
|
|
29
29
|
}
|
|
30
|
-
|
|
30
|
+
function stackWatchAttentionKey(envelope) {
|
|
31
31
|
return `${envelope.prNumber ?? ""}|${envelope.headSha ?? ""}|${blockerWithoutElapsedMinutes(envelope.blocker)}`;
|
|
32
32
|
}
|
|
33
|
-
|
|
33
|
+
function stackWatchAttentionLine(envelope) {
|
|
34
34
|
const restart = envelope.cursor.bounceId
|
|
35
35
|
? ` restart=${JSON.stringify(stackWatchBackgroundCommand(envelope.stackId, envelope.cursor))}` : "";
|
|
36
36
|
return `${STACK_WATCH_MARKER} ATTENTION pr=${envelope.prNumber ?? "none"} head=${envelope.headSha ?? "none"} blocker=${JSON.stringify(envelope.blocker ?? "")} repair=${envelope.repair?.kind ?? "none"}${restart}`;
|
|
37
37
|
}
|
|
38
|
-
|
|
38
|
+
function stackWatchLandedLine(envelope) {
|
|
39
39
|
return `${STACK_WATCH_MARKER} LANDED stack=${envelope.stackId} reason=${envelope.watch.reason}`;
|
|
40
40
|
}
|
|
41
41
|
export function stackWatchFailedLine(stackId, message) {
|
|
42
42
|
return `${STACK_WATCH_MARKER} ATTENTION failed stack=${stackId} error=${JSON.stringify(message)}`;
|
|
43
43
|
}
|
|
44
|
-
|
|
44
|
+
function stackWatchTimeoutLine(stackId, maxMs) {
|
|
45
45
|
return `${STACK_WATCH_MARKER} TIMEOUT stack=${stackId} after=${Math.round(maxMs / 60_000)}m`;
|
|
46
46
|
}
|
|
47
47
|
function isAbort(err, signal) {
|
package/dist/submit-pr-base.d.ts
CHANGED
|
@@ -6,7 +6,6 @@
|
|
|
6
6
|
* `opened` and double-wake Vortex.
|
|
7
7
|
*/
|
|
8
8
|
import type { StackDto } from "./api.js";
|
|
9
|
-
export declare const MG_PARK_PREFIX = "mg-park-";
|
|
10
9
|
export declare function isMgParkBranch(branch: string): boolean;
|
|
11
10
|
/** Live freeze already used as a GitHub base on this stack, if any. */
|
|
12
11
|
export declare function parkFreezeBranchFromStack(stack: StackDto): string | null;
|
package/dist/submit-pr-base.js
CHANGED
|
@@ -13,7 +13,7 @@ function registeredParentHit(stacks, parentBranch, owner, repo) {
|
|
|
13
13
|
}
|
|
14
14
|
return null;
|
|
15
15
|
}
|
|
16
|
-
|
|
16
|
+
const MG_PARK_PREFIX = "mg-park-";
|
|
17
17
|
export function isMgParkBranch(branch) {
|
|
18
18
|
return branch.trim().toLowerCase().startsWith(MG_PARK_PREFIX);
|
|
19
19
|
}
|
package/dist/ui/ansi.d.ts
CHANGED
|
@@ -1,14 +1,4 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Brand ink. xterm-256 77 (#5FAF5F) � muted green that Terminal.app
|
|
3
|
-
* actually paints. 24-bit `38;2;61;�` was read as palette 61 (purple).
|
|
4
|
-
*/
|
|
5
|
-
export declare const BRAND_GREEN: {
|
|
6
|
-
readonly hex: "#5FAF5F";
|
|
7
|
-
readonly index: 77;
|
|
8
|
-
};
|
|
9
1
|
export declare const BRAND_GREEN_SGR: string;
|
|
10
|
-
/** Full-cell fill. Glyphs like ? leave a hairline; a space with this bg does not. */
|
|
11
|
-
export declare const BRAND_FILL_SGR: string;
|
|
12
2
|
export declare const ansi: {
|
|
13
3
|
readonly enabled: boolean;
|
|
14
4
|
green: (t: string) => string;
|
package/dist/ui/ansi.js
CHANGED
|
@@ -15,10 +15,10 @@ function wrap(code, text) {
|
|
|
15
15
|
* Brand ink. xterm-256 77 (#5FAF5F) � muted green that Terminal.app
|
|
16
16
|
* actually paints. 24-bit `38;2;61;�` was read as palette 61 (purple).
|
|
17
17
|
*/
|
|
18
|
-
|
|
18
|
+
const BRAND_GREEN = { hex: "#5FAF5F", index: 77 };
|
|
19
19
|
export const BRAND_GREEN_SGR = `38;5;${BRAND_GREEN.index}`;
|
|
20
20
|
/** Full-cell fill. Glyphs like ? leave a hairline; a space with this bg does not. */
|
|
21
|
-
|
|
21
|
+
const BRAND_FILL_SGR = `${BRAND_GREEN_SGR};48;5;${BRAND_GREEN.index}`;
|
|
22
22
|
function brandGreen(text, bold = false) {
|
|
23
23
|
if (!computeEnabled())
|
|
24
24
|
return text;
|
package/dist/ui/banner.d.ts
CHANGED
|
@@ -7,11 +7,12 @@ export type BannerState = {
|
|
|
7
7
|
me: MeResponse | null;
|
|
8
8
|
key: string | undefined;
|
|
9
9
|
keyInvalid: boolean;
|
|
10
|
+
account?: string | null;
|
|
10
11
|
};
|
|
11
12
|
/** Full welcome, mid pane, or Claude-style short chrome that still shows the mark. */
|
|
12
13
|
export type BannerDensity = "full" | "compact" | "mini" | "nano";
|
|
13
14
|
/** `true` = compact. Strings pick the short-pane densities. */
|
|
14
|
-
|
|
15
|
+
type BannerVariant = boolean | "mini" | "nano";
|
|
15
16
|
/** Handle returned by {@link printBannerHeader}; `rows` reflows for a resize. */
|
|
16
17
|
export type BannerHandle = {
|
|
17
18
|
rows(columns?: number, variant?: BannerVariant, frame?: number): string[];
|
|
@@ -35,3 +36,4 @@ export declare function printBannerHeader(opts?: {
|
|
|
35
36
|
paint?: boolean;
|
|
36
37
|
}): Promise<BannerHandle>;
|
|
37
38
|
export declare function shellPrompt(_loggedIn: boolean): string;
|
|
39
|
+
export {};
|
package/dist/ui/banner.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { getMe } from "../api.js";
|
|
2
2
|
import { CommandError } from "../errors.js";
|
|
3
|
-
import { keyDisplay, loadConfig, resolveApiKey } from "../config.js";
|
|
3
|
+
import { keyDisplay, loadConfig, resolveApiKey, settleAccountName } from "../config.js";
|
|
4
4
|
import { cliVersion } from "../version.js";
|
|
5
5
|
import { ansi } from "./ansi.js";
|
|
6
6
|
import { frameWidth, padVisible, preferBoxedUi, roundedBox, sideBySide, terminalColumns, visibleWidth, } from "./box.js";
|
|
@@ -230,6 +230,12 @@ const WHATS_NEW = {
|
|
|
230
230
|
"0.3.64": [
|
|
231
231
|
"Memory commands and Overdrive merge batches",
|
|
232
232
|
],
|
|
233
|
+
"0.3.65": [
|
|
234
|
+
"Up to 3 accounts: `account use`, `--account <name>`",
|
|
235
|
+
],
|
|
236
|
+
"0.3.66": [
|
|
237
|
+
"`issue wait`; submit needs an approved issue",
|
|
238
|
+
],
|
|
233
239
|
};
|
|
234
240
|
function densityFromVariant(variant) {
|
|
235
241
|
if (variant === "mini" || variant === "nano")
|
|
@@ -247,7 +253,7 @@ function statusLine(state) {
|
|
|
247
253
|
if (state.keyInvalid) {
|
|
248
254
|
return `${ansi.dim("○")} API key invalid or revoked · type ${ansi.brightGreen("login")} to sign in again`;
|
|
249
255
|
}
|
|
250
|
-
const display = state.me?.key.prefix ?? keyDisplay(state.key) ?? "msk_live_…";
|
|
256
|
+
const display = state.account ?? state.me?.key.prefix ?? keyDisplay(state.key) ?? "msk_live_…";
|
|
251
257
|
if (!state.me) {
|
|
252
258
|
return `${ansi.brightGreen("●")} ${ansi.bold(display)} ${ansi.dim("· account details unavailable")}`;
|
|
253
259
|
}
|
|
@@ -373,7 +379,8 @@ export async function printBannerHeader(opts) {
|
|
|
373
379
|
}
|
|
374
380
|
}
|
|
375
381
|
}
|
|
376
|
-
const
|
|
382
|
+
const account = me ? await settleAccountName(cfg, me) : cfg.account;
|
|
383
|
+
const state = { version: cliVersion(), me, key, keyInvalid, account: account ?? null };
|
|
377
384
|
if (opts?.paint !== false) {
|
|
378
385
|
for (const line of buildBannerRows(state)) {
|
|
379
386
|
console.log(line);
|
package/dist/ui/box.d.ts
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
import { padVisible, sliceVisible, visibleWidth } from "./width.js";
|
|
2
2
|
export { padVisible, sliceVisible, visibleWidth };
|
|
3
|
-
/** Below this width, boxed chrome wraps on real TTYs — prefer plain output. */
|
|
4
|
-
export declare const MIN_BOXED_COLUMNS = 48;
|
|
5
3
|
/**
|
|
6
4
|
* Usable terminal columns (TTY), with a sensible fallback for pipes/tests.
|
|
7
5
|
* Pass `columns` to measure a stream other than process.stdout (or a
|
package/dist/ui/box.js
CHANGED
|
@@ -5,7 +5,7 @@ import { padVisible, sliceVisible, visibleWidth } from "./width.js";
|
|
|
5
5
|
// importers (banner, prompt) keep working.
|
|
6
6
|
export { padVisible, sliceVisible, visibleWidth };
|
|
7
7
|
/** Below this width, boxed chrome wraps on real TTYs — prefer plain output. */
|
|
8
|
-
|
|
8
|
+
const MIN_BOXED_COLUMNS = 48;
|
|
9
9
|
/**
|
|
10
10
|
* Usable terminal columns (TTY), with a sensible fallback for pipes/tests.
|
|
11
11
|
* Pass `columns` to measure a stream other than process.stdout (or a
|