mergestorm 0.3.63 → 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 +30 -10
- package/dist/api.d.ts +68 -11
- package/dist/api.js +206 -8
- package/dist/automation-catalog.d.ts +17 -3
- package/dist/automation-catalog.js +4 -1
- package/dist/cli.js +21 -19
- package/dist/client/index.d.ts +6 -4
- package/dist/client/index.js +6 -4
- 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 +29 -0
- package/dist/commands/memory.js +154 -0
- package/dist/commands/registry.d.ts +1 -0
- package/dist/commands/registry.js +52 -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 -4
- package/dist/commands/settings.js +51 -5
- package/dist/commands/stack.d.ts +20 -5
- package/dist/commands/stack.js +106 -7
- package/dist/commands/whoami.js +14 -10
- package/dist/config.d.ts +56 -2
- package/dist/config.js +271 -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 +74 -0
- package/dist/memory-api.js +69 -0
- 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 +5 -26
- package/dist/server-publish.js +47 -40
- package/dist/stack-blockers.d.ts +15 -36
- package/dist/stack-blockers.js +69 -33
- package/dist/stack-dto.d.ts +4 -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 +35 -14
- package/dist/stack-watch.d.ts +2 -1
- package/dist/stack-watch.js +6 -2
- 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 +13 -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 +3 -1
- package/skill/SKILL.md +4 -0
- package/skill/mergestorm-bounce-watch/SKILL.md +1 -1
package/dist/commands/stack.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { type
|
|
1
|
+
import { type StackRepairHint } from "../stack-blockers.js";
|
|
2
2
|
import { type StackWatchObligation } from "../stack-watch-obligation.js";
|
|
3
|
-
import { adoptStack, ensureUpperPark, findStackPull, getEnrichedStack, getStackPublication, listMergeQueueEntries, listStacks, openStackPull, claimStackPulls, setStackPolicy, type StackDto, type StackPolicyPatch } from "../api.js";
|
|
3
|
+
import { adoptStack, ensureUpperPark, findStackPull, getEnrichedStack, getIssueReview, getStackPublication, listMergeQueueEntries, listStacks, openStackPull, claimStackPulls, setStackPolicy, type StackDto, type StackPolicyPatch } from "../api.js";
|
|
4
4
|
import { loadConfig } from "../config.js";
|
|
5
5
|
import { createPrs, findOpenPrNumber } from "../gh.js";
|
|
6
6
|
import { parseGithubOriginRepo } from "../git.js";
|
|
@@ -11,9 +11,7 @@ import { gitTopLevel } from "../repo-identity.js";
|
|
|
11
11
|
import { loadStackMeta, saveStackMeta } from "../stack-meta.js";
|
|
12
12
|
import { pollStackWatch } from "../stack-watch.js";
|
|
13
13
|
import { runStackWatch } from "../stack-watch-loop.js";
|
|
14
|
-
export declare function stackHeldLine(held: StackHeldBlocker | null | undefined): string[];
|
|
15
14
|
export declare function stackRepairLine(repair: StackRepairHint | null | undefined): string[];
|
|
16
|
-
export declare function stackWatchTextLines(watch: StackWatchObligation | null | undefined): string[];
|
|
17
15
|
export declare function openStackWatch(stackId: string, opts?: {
|
|
18
16
|
unread?: boolean;
|
|
19
17
|
}): StackWatchObligation;
|
|
@@ -128,6 +126,22 @@ export type StackCreateDeps = {
|
|
|
128
126
|
};
|
|
129
127
|
export declare function cmdStackCreate(args: string[], deps?: StackCreateDeps): Promise<void>;
|
|
130
128
|
/** Injectable seams for unit tests (STRUCT-04). Production callers omit deps. */
|
|
129
|
+
export declare function issueReviewsEnabledFor(cfg: Awaited<ReturnType<typeof loadConfig>>, owner: string, repo: string): Promise<boolean>;
|
|
130
|
+
export type IssueGateLayer = {
|
|
131
|
+
branch: string;
|
|
132
|
+
texts: string[];
|
|
133
|
+
};
|
|
134
|
+
export type IssueGateDeps = {
|
|
135
|
+
getIssueReview?: typeof getIssueReview;
|
|
136
|
+
issueReviewsEnabled?: typeof issueReviewsEnabledFor;
|
|
137
|
+
};
|
|
138
|
+
export declare function assertLinkedIssuesApproved(input: {
|
|
139
|
+
cfg: Awaited<ReturnType<typeof loadConfig>>;
|
|
140
|
+
owner: string;
|
|
141
|
+
repo: string;
|
|
142
|
+
layers: readonly IssueGateLayer[];
|
|
143
|
+
warn: (line: string) => void;
|
|
144
|
+
}, deps?: IssueGateDeps): Promise<void>;
|
|
131
145
|
export type StackSubmitDeps = {
|
|
132
146
|
cwd?: string;
|
|
133
147
|
gitTopLevel?: (cwd?: string) => string;
|
|
@@ -155,7 +169,7 @@ export type StackSubmitDeps = {
|
|
|
155
169
|
adoptStack?: typeof adoptStack;
|
|
156
170
|
ensureUpperPark?: typeof ensureUpperPark;
|
|
157
171
|
env?: NodeJS.ProcessEnv;
|
|
158
|
-
};
|
|
172
|
+
} & IssueGateDeps;
|
|
159
173
|
export declare function cmdStackSubmit(args: string[], deps?: StackSubmitDeps): Promise<void>;
|
|
160
174
|
export declare function parseStackPublishArgs(args: string[]): {
|
|
161
175
|
branch?: string;
|
|
@@ -180,6 +194,7 @@ export declare function cmdStackWatch(args: string[], deps?: {
|
|
|
180
194
|
loadConfig?: typeof loadConfig;
|
|
181
195
|
runStackWatch?: typeof runStackWatch;
|
|
182
196
|
signal?: AbortSignal;
|
|
197
|
+
writeMarker?: (line: string) => void;
|
|
183
198
|
}): Promise<void>;
|
|
184
199
|
export declare function cmdStackStatus(args: string[], deps?: {
|
|
185
200
|
loadConfig?: typeof loadConfig;
|
package/dist/commands/stack.js
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
import { cycloneStatusWords, stackBlockers, stackBlockersSummary } from "../stack-blockers.js";
|
|
2
2
|
import { STACK_WATCH_NOT_DONE_SENTENCE, STACK_WATCH_NOTIFY_PATTERN, stackTerminalReason, stackWatchObligation, } from "../stack-watch-obligation.js";
|
|
3
|
-
import { adoptStack, ensureUpperPark, findStackPull, getEnrichedStack, getStackPublication, landNextStack, listMergeQueueEntries, listStacks, openStackPull, claimStackPulls, restackStack, setStackPolicy, } from "../api.js";
|
|
3
|
+
import { adoptStack, ensureUpperPark, findStackPull, getEnrichedStack, getIssueReview, getSettings, getStackPublication, issueNotApprovedError, landNextStack, listRepoSettings, listMergeQueueEntries, listStacks, openStackPull, claimStackPulls, restackStack, setStackPolicy, } from "../api.js";
|
|
4
4
|
import { loadConfig } from "../config.js";
|
|
5
5
|
import { rateLimitedMessage, REVIEW_EXIT, CommandError } from "../errors.js";
|
|
6
6
|
import { createPrs, findOpenPrNumber, ghReady, githubApiFromEnv, } from "../gh.js";
|
|
7
7
|
import { parseGithubOriginRepo } from "../git.js";
|
|
8
8
|
import { branchExists, commitsAheadOf, createBranchFrom, createBranchFromHead, trunkStartRef, currentBranch, defaultLayerBranchName, deleteBranch, discoverTrunk, fetchRemoteBranch, pushBranch, tipCommitMessage, tipCommitSubject, worktreeDirty, } from "../git-stack.js";
|
|
9
|
-
import { buildPrBodyFromCommit } from "../pr-body.js";
|
|
9
|
+
import { buildPrBodyFromCommit, linkedIssueNumbers } from "../pr-body.js";
|
|
10
10
|
import { publishBranchServerSide, assertExplicitStacking, publishWorkingBranch, workingBranchPublication, } from "../server-publish.js";
|
|
11
11
|
import { gitTopLevel } from "../repo-identity.js";
|
|
12
12
|
import { activeLayers, appendLayer, emptyStackMeta, loadStackMeta, removeActiveStack, resetStackMeta, saveStackMeta, selectStackForCreate, setActivePolicy, } from "../stack-meta.js";
|
|
13
13
|
import { pollStackWatch, StackWatchError, StackWatchTimeoutError, } from "../stack-watch.js";
|
|
14
|
-
import { runStackWatch, STACK_WATCH_EXIT } from "../stack-watch-loop.js";
|
|
14
|
+
import { runStackWatch, STACK_WATCH_EXIT, stackWatchFailedLine } from "../stack-watch-loop.js";
|
|
15
15
|
import { isMgParkBranch, planSubmitLayerBases } from "../submit-pr-base.js";
|
|
16
16
|
import { ansi } from "../ui/ansi.js";
|
|
17
17
|
import { runLineTabsBrowser } from "../ui/line-tabs.js";
|
|
@@ -19,7 +19,7 @@ import { present } from "../ui/present.js";
|
|
|
19
19
|
import { canBrowse } from "./browse.js";
|
|
20
20
|
import { isTransientReviewPollError } from "./review-client.js";
|
|
21
21
|
import { openHelpBrowser } from "./help.js";
|
|
22
|
-
|
|
22
|
+
function stackHeldLine(held) {
|
|
23
23
|
if (!held)
|
|
24
24
|
return [];
|
|
25
25
|
const agents = held.waitingOn.map((agent) => agent === "cyclone" ? "Cyclone" : "Vortex").join(" and ");
|
|
@@ -32,7 +32,7 @@ export function stackRepairLine(repair) {
|
|
|
32
32
|
const note = "note" in repair && repair.note ? ` ${repair.note}` : "";
|
|
33
33
|
return [`Repair #${repair.prNumber} ${repair.kind}${files}: ${repair.steps}${note}`];
|
|
34
34
|
}
|
|
35
|
-
|
|
35
|
+
function stackWatchTextLines(watch) {
|
|
36
36
|
if (!watch)
|
|
37
37
|
return [];
|
|
38
38
|
if (watch.done)
|
|
@@ -62,7 +62,8 @@ const STACK_USAGE = `usage:
|
|
|
62
62
|
mergestorm stack watch <stack-id> [--until attention|landed] [--ignore <blocker-substring>] [--max <minutes>] [--head <sha>] [--bounce-id <id>] [--after-finished-at <iso>] [--json]
|
|
63
63
|
Loops stack wait with the cursor and stays silent while the stack is waiting or in progress.
|
|
64
64
|
Prints one MS-WATCH ATTENTION line and exits 3 on attention or on reads that keep failing (a permanent
|
|
65
|
-
error twice, or 15 minutes
|
|
65
|
+
error twice, or 15 minutes with no stack read: timeouts, 5xx and rate limits, which it retries with backoff).
|
|
66
|
+
It also prints MS-WATCH ATTENTION failed when it cannot start (bad flags, no config), so it never exits without an MS-WATCH line unless you stop it; prints MS-WATCH LANDED
|
|
66
67
|
and exits 0 only for a confirmed landing, including a stack Mergestorm deleted after its queue entry landed. Other terminal reasons exit 3 with ATTENTION. Run it as a background command.
|
|
67
68
|
An ATTENTION line for a merge queue bounce ends with restart="mg stack watch <id> --bounce-id <id> --after-finished-at <iso>":
|
|
68
69
|
restart with that command (plus --head <sha> after a push) so a bounce you already handled is not reported again.
|
|
@@ -679,6 +680,65 @@ export async function cmdStackCreate(args, deps = {}) {
|
|
|
679
680
|
created.push(ansi.dim(" authoring state: managed automatically outside the repository"));
|
|
680
681
|
await present("Stack", created);
|
|
681
682
|
}
|
|
683
|
+
/** Injectable seams for unit tests (STRUCT-04). Production callers omit deps. */
|
|
684
|
+
export async function issueReviewsEnabledFor(cfg, owner, repo) {
|
|
685
|
+
const repos = await listRepoSettings(cfg, { timeoutMs: STACK_GUARD_TIMEOUT_MS });
|
|
686
|
+
const row = repos.find((r) => r.owner.toLowerCase() === owner.toLowerCase() && r.repo.toLowerCase() === repo.toLowerCase());
|
|
687
|
+
if (typeof row?.issue_review_enabled === "boolean")
|
|
688
|
+
return row.issue_review_enabled;
|
|
689
|
+
const settings = await getSettings(cfg, { timeoutMs: STACK_GUARD_TIMEOUT_MS });
|
|
690
|
+
if (!settings)
|
|
691
|
+
throw new CommandError("Could not read the account settings");
|
|
692
|
+
return settings.issue_review_enabled === true;
|
|
693
|
+
}
|
|
694
|
+
export async function assertLinkedIssuesApproved(input, deps = {}) {
|
|
695
|
+
const { cfg, owner, repo, layers, warn } = input;
|
|
696
|
+
if (layers.length === 0)
|
|
697
|
+
return;
|
|
698
|
+
const getIssueReviewFn = deps.getIssueReview ?? getIssueReview;
|
|
699
|
+
const unverified = (err) => {
|
|
700
|
+
if (err instanceof CommandError && (err.code === "auth_invalid" || err.code === "missing_api_key" || err.code === "rate_limited"))
|
|
701
|
+
return err;
|
|
702
|
+
return new CommandError(`Could not verify the Vortex reviews of the linked issues (${err instanceof Error ? err.message : String(err)}); ` +
|
|
703
|
+
"refusing to submit. Retry when the API is reachable.");
|
|
704
|
+
};
|
|
705
|
+
const linked = layers.map((layer) => ({ branch: layer.branch, numbers: linkedIssueNumbers({ owner, repo }, ...layer.texts) }));
|
|
706
|
+
const statuses = [];
|
|
707
|
+
for (const number of [...new Set(linked.flatMap((layer) => layer.numbers))].sort((a, b) => a - b)) {
|
|
708
|
+
let status;
|
|
709
|
+
try {
|
|
710
|
+
status = await getIssueReviewFn(cfg, { owner, repo, number }, { timeoutMs: STACK_GUARD_TIMEOUT_MS });
|
|
711
|
+
}
|
|
712
|
+
catch (err) {
|
|
713
|
+
throw unverified(err);
|
|
714
|
+
}
|
|
715
|
+
if (!status) {
|
|
716
|
+
warn(ansi.dim(" This Mergestorm server does not check issue reviews yet; submitting without the issue gate."));
|
|
717
|
+
continue;
|
|
718
|
+
}
|
|
719
|
+
statuses.push(status);
|
|
720
|
+
}
|
|
721
|
+
const unlinked = linked.filter((layer) => layer.numbers.length === 0);
|
|
722
|
+
if (unlinked.length > 0) {
|
|
723
|
+
let enabled;
|
|
724
|
+
try {
|
|
725
|
+
enabled = await (deps.issueReviewsEnabled ?? issueReviewsEnabledFor)(cfg, owner, repo);
|
|
726
|
+
}
|
|
727
|
+
catch (err) {
|
|
728
|
+
throw unverified(err);
|
|
729
|
+
}
|
|
730
|
+
if (enabled) {
|
|
731
|
+
throw new CommandError([
|
|
732
|
+
`Issue reviews are on for ${owner}/${repo}, and ${unlinked.map((layer) => layer.branch).join(", ")} links no issue.`,
|
|
733
|
+
"Open one with `mergestorm issue create --title <problem> --wait`, answer the Vortex review until it is approved,",
|
|
734
|
+
"then add `Fixes #N` to the tip commit message and submit again.",
|
|
735
|
+
].join("\n"), REVIEW_EXIT.failed, "issue_not_approved");
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
const blocking = statuses.filter((status) => status.state !== "approved" && status.state !== "off");
|
|
739
|
+
if (blocking.length > 0)
|
|
740
|
+
throw issueNotApprovedError(blocking);
|
|
741
|
+
}
|
|
682
742
|
export async function cmdStackSubmit(args, deps = {}) {
|
|
683
743
|
const { extend, autoLand, autoReview, autoPatch, autoResolveConflicts, autoFixCi, rest, serverSide, asJson } = parseStackSubmitArgs(args);
|
|
684
744
|
const api = rest ? "rest" : githubApiFromEnv(deps.env ?? process.env);
|
|
@@ -802,6 +862,21 @@ export async function cmdStackSubmit(args, deps = {}) {
|
|
|
802
862
|
for (const plan of plans) {
|
|
803
863
|
existingPrs.set(plan.branch, await opener.findOpen(plan.branch));
|
|
804
864
|
}
|
|
865
|
+
await assertLinkedIssuesApproved({
|
|
866
|
+
cfg: await loadConfigFn(),
|
|
867
|
+
owner,
|
|
868
|
+
repo,
|
|
869
|
+
layers: plans
|
|
870
|
+
.filter((plan) => existingPrs.get(plan.branch) == null)
|
|
871
|
+
.map((plan) => {
|
|
872
|
+
if (!branchExistsFn(plan.branch, cwd)) {
|
|
873
|
+
throw new CommandError(`Stack layer branch missing locally: ${plan.branch}`);
|
|
874
|
+
}
|
|
875
|
+
const message = tipCommitMessageFn(plan.branch, cwd);
|
|
876
|
+
return { branch: plan.branch, texts: [message, buildPrBodyFn(message)] };
|
|
877
|
+
}),
|
|
878
|
+
warn: progress,
|
|
879
|
+
}, deps);
|
|
805
880
|
const newHeads = plans
|
|
806
881
|
.filter((plan) => existingPrs.get(plan.branch) == null)
|
|
807
882
|
.map((plan) => plan.branch);
|
|
@@ -1213,7 +1288,7 @@ export async function cmdStackWait(args, deps = {}) {
|
|
|
1213
1288
|
throw new CommandError(err.message, REVIEW_EXIT.timeout, "review_timeout");
|
|
1214
1289
|
}
|
|
1215
1290
|
}
|
|
1216
|
-
|
|
1291
|
+
async function startStackWatch(args, deps) {
|
|
1217
1292
|
const usage = "usage: mergestorm stack watch <stack-id> [--until attention|landed] [--ignore <blocker-substring>] [--max <minutes>] [--head <sha>] [--bounce-id <id>] [--after-finished-at <iso>] [--json]";
|
|
1218
1293
|
const fail = (detail) => new CommandError(detail ? `${usage}\n${detail}` : usage, REVIEW_EXIT.usage, "usage");
|
|
1219
1294
|
const positional = [];
|
|
@@ -1286,6 +1361,30 @@ export async function cmdStackWatch(args, deps = {}) {
|
|
|
1286
1361
|
...(afterFinishedAt ? { afterFinishedAt } : {}),
|
|
1287
1362
|
} } : {}),
|
|
1288
1363
|
});
|
|
1364
|
+
return { stackId, result };
|
|
1365
|
+
}
|
|
1366
|
+
export async function cmdStackWatch(args, deps = {}) {
|
|
1367
|
+
let started;
|
|
1368
|
+
try {
|
|
1369
|
+
started = await startStackWatch(args, deps);
|
|
1370
|
+
}
|
|
1371
|
+
catch (err) {
|
|
1372
|
+
const valueFlags = new Set(["--until", "--ignore", "--max", "--head", "--bounce-id", "--after-finished-at"]);
|
|
1373
|
+
let json = false;
|
|
1374
|
+
for (let i = 0; i < args.length; i++) {
|
|
1375
|
+
if (args[i] === "--json")
|
|
1376
|
+
json = true;
|
|
1377
|
+
else if (valueFlags.has(args[i]))
|
|
1378
|
+
i++;
|
|
1379
|
+
}
|
|
1380
|
+
const marker = deps.writeMarker ?? (json ? console.error : console.log);
|
|
1381
|
+
const stackId = args.find((arg, i) => UUID_RE.test(arg.trim()) && !valueFlags.has(args[i - 1]))?.trim() ?? "none";
|
|
1382
|
+
marker(stackWatchFailedLine(stackId, err instanceof Error ? err.message : String(err)));
|
|
1383
|
+
if (json)
|
|
1384
|
+
console.log(JSON.stringify({ stackId, outcome: "failed" }));
|
|
1385
|
+
throw err;
|
|
1386
|
+
}
|
|
1387
|
+
const { stackId, result } = started;
|
|
1289
1388
|
if (result.exitCode === STACK_WATCH_EXIT.landed)
|
|
1290
1389
|
return;
|
|
1291
1390
|
const reason = result.outcome === "timeout"
|
package/dist/commands/whoami.js
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import { getMe } from "../api.js";
|
|
2
|
-
import { apiBase, configPath, loadConfig, resolveApiKey } from "../config.js";
|
|
1
|
+
import { accountLabel, getMe } from "../api.js";
|
|
2
|
+
import { apiBase, configPath, listAccounts, loadConfig, resolveApiKey, settleAccountName } from "../config.js";
|
|
3
3
|
import { CommandError } from "../errors.js";
|
|
4
4
|
import { ansi } from "../ui/ansi.js";
|
|
5
5
|
import { present } from "../ui/present.js";
|
|
6
|
-
import { canBrowse, openTabsBrowser } from "./browse.js";
|
|
7
6
|
export async function cmdWhoami(args, opts = {}) {
|
|
8
7
|
const asJson = args.includes("--json");
|
|
9
8
|
const cfg = await loadConfig();
|
|
@@ -11,15 +10,17 @@ export async function cmdWhoami(args, opts = {}) {
|
|
|
11
10
|
if (!key) {
|
|
12
11
|
throw new CommandError("Not logged in. Run `mergestorm login` (agents: `mergestorm login --start`, then `--finish`).");
|
|
13
12
|
}
|
|
14
|
-
if (!asJson && canBrowse()) {
|
|
15
|
-
await openTabsBrowser("status");
|
|
16
|
-
return;
|
|
17
|
-
}
|
|
18
13
|
const me = await getMe(cfg);
|
|
19
14
|
if (!me) {
|
|
20
15
|
throw new CommandError("Live account details unavailable. Could not verify account with /me.");
|
|
21
16
|
}
|
|
17
|
+
const account = (await settleAccountName(cfg, me)) ?? accountLabel(me.account);
|
|
18
|
+
const accounts = (await listAccounts()).map((a) => a.name);
|
|
22
19
|
const payload = {
|
|
20
|
+
account,
|
|
21
|
+
accounts,
|
|
22
|
+
github_login: me.account?.github_login ?? null,
|
|
23
|
+
email: me.account?.email ?? null,
|
|
23
24
|
key_prefix: me.key.prefix,
|
|
24
25
|
key_name: me.key.name ?? null,
|
|
25
26
|
plan_key: me.plan_key ?? null,
|
|
@@ -37,9 +38,12 @@ export async function cmdWhoami(args, opts = {}) {
|
|
|
37
38
|
console.log(text);
|
|
38
39
|
return;
|
|
39
40
|
}
|
|
40
|
-
const lines = [
|
|
41
|
-
|
|
42
|
-
|
|
41
|
+
const lines = [` Account ${account ? ansi.bold(account) : "unknown (API too old to say)"}`];
|
|
42
|
+
lines.push(` Key ${ansi.bold(String(payload.key_prefix))}${payload.key_name ? ` (${payload.key_name})` : ""}`);
|
|
43
|
+
const others = accounts.filter((name) => name !== account);
|
|
44
|
+
if (others.length > 0) {
|
|
45
|
+
lines.push(` Others ${others.join(", ")} ${ansi.dim("· mergestorm account use <name>")}`);
|
|
46
|
+
}
|
|
43
47
|
if (payload.plan_key) {
|
|
44
48
|
lines.push(` Plan ${payload.plan_label_key ?? payload.plan_key}`);
|
|
45
49
|
}
|
package/dist/config.d.ts
CHANGED
|
@@ -1,15 +1,69 @@
|
|
|
1
1
|
export declare const DEFAULT_API = "https://api.mergestorm.ai";
|
|
2
|
+
export declare const ACCOUNT_LIMIT = 3;
|
|
2
3
|
export type Config = {
|
|
3
4
|
apiKey?: string;
|
|
4
5
|
apiBase?: string;
|
|
5
6
|
ignoreEnv?: boolean;
|
|
7
|
+
account?: string;
|
|
8
|
+
};
|
|
9
|
+
export type StoredAccount = {
|
|
10
|
+
apiKey: string;
|
|
11
|
+
apiBase?: string;
|
|
12
|
+
};
|
|
13
|
+
export type AccountStore = {
|
|
14
|
+
accounts: Record<string, StoredAccount>;
|
|
15
|
+
activeAccount?: string;
|
|
16
|
+
apiBase?: string;
|
|
17
|
+
ignoreEnv?: boolean;
|
|
18
|
+
};
|
|
19
|
+
type StoreFile = AccountStore & {
|
|
20
|
+
apiKey?: string;
|
|
21
|
+
};
|
|
22
|
+
export type AccountSummary = {
|
|
23
|
+
name: string;
|
|
24
|
+
active: boolean;
|
|
25
|
+
keyPrefix: string;
|
|
26
|
+
apiBase: string;
|
|
27
|
+
};
|
|
28
|
+
export type NamedMe = {
|
|
29
|
+
account?: {
|
|
30
|
+
github_login?: string | null;
|
|
31
|
+
email?: string | null;
|
|
32
|
+
} | null;
|
|
6
33
|
};
|
|
7
34
|
export declare function mergestormHome(env?: NodeJS.ProcessEnv): string;
|
|
8
35
|
export declare function configPath(): string;
|
|
36
|
+
export declare function provisionalAccountName(apiKey: string): string;
|
|
37
|
+
export declare function accountNameFromMe(me: NamedMe | null | undefined): string | null;
|
|
38
|
+
export declare function serializeStore(store: AccountStore): StoreFile;
|
|
39
|
+
export declare function migrateStore(raw: unknown): {
|
|
40
|
+
store: AccountStore;
|
|
41
|
+
migrated: boolean;
|
|
42
|
+
};
|
|
43
|
+
export declare function writeStore(store: AccountStore): Promise<void>;
|
|
44
|
+
export declare function readStore(): Promise<AccountStore>;
|
|
45
|
+
export declare function withAccount<T>(name: string | undefined, fn: () => Promise<T>): Promise<T>;
|
|
46
|
+
export declare function requestedAccount(store: AccountStore, env?: NodeJS.ProcessEnv): string | undefined;
|
|
47
|
+
export declare function selectAccountName(store: AccountStore, requested: string | undefined): string | undefined;
|
|
48
|
+
export declare function configFor(store: AccountStore, name: string | undefined): Config;
|
|
9
49
|
export declare function loadConfig(): Promise<Config>;
|
|
10
|
-
export declare function
|
|
11
|
-
export declare function
|
|
50
|
+
export declare function accountLimitMessage(store: AccountStore): string;
|
|
51
|
+
export declare function saveAccount(name: string, account: StoredAccount, opts?: {
|
|
52
|
+
activate?: boolean;
|
|
53
|
+
}): Promise<AccountStore>;
|
|
54
|
+
export declare function useAccount(name: string): Promise<AccountStore>;
|
|
55
|
+
export declare function removeAccount(name: string): Promise<AccountStore>;
|
|
56
|
+
export declare function renameAccount(from: string, to: string): Promise<AccountStore>;
|
|
57
|
+
export declare function summarizeAccounts(store: AccountStore): AccountSummary[];
|
|
58
|
+
export declare function listAccounts(): Promise<AccountSummary[]>;
|
|
59
|
+
export declare function settleAccountName(cfg: Config, me: NamedMe | null | undefined): Promise<string | undefined>;
|
|
60
|
+
export declare function splitAccountFlag(args: readonly string[]): {
|
|
61
|
+
account?: string;
|
|
62
|
+
args: string[];
|
|
63
|
+
};
|
|
12
64
|
export declare function apiBase(cfg: Config): string;
|
|
65
|
+
export declare function resolveChatId(cfg: Config): string | undefined;
|
|
13
66
|
export declare function resolveApiKey(cfg: Config): string | undefined;
|
|
14
67
|
/** Short display form of a stored key (prefix only). */
|
|
15
68
|
export declare function keyDisplay(key: string | undefined): string | null;
|
|
69
|
+
export {};
|
package/dist/config.js
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
|
-
import { mkdir, readFile, writeFile
|
|
1
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
3
|
import path from "node:path";
|
|
4
|
+
import { CommandError } from "./errors.js";
|
|
4
5
|
export const DEFAULT_API = "https://api.mergestorm.ai";
|
|
6
|
+
export const ACCOUNT_LIMIT = 3;
|
|
7
|
+
const KEY_PREFIX_LENGTH = "msk_live_".length + 4;
|
|
5
8
|
export function mergestormHome(env = process.env) {
|
|
6
9
|
const override = env.MERGESTORM_HOME?.trim();
|
|
7
10
|
return override ? path.resolve(override) : path.join(homedir(), ".mergestorm");
|
|
@@ -9,30 +12,285 @@ export function mergestormHome(env = process.env) {
|
|
|
9
12
|
export function configPath() {
|
|
10
13
|
return path.join(mergestormHome(), "config.json");
|
|
11
14
|
}
|
|
12
|
-
export
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
15
|
+
export function provisionalAccountName(apiKey) {
|
|
16
|
+
return apiKey.slice(0, KEY_PREFIX_LENGTH);
|
|
17
|
+
}
|
|
18
|
+
export function accountNameFromMe(me) {
|
|
19
|
+
const login = me?.account?.github_login?.trim();
|
|
20
|
+
if (login)
|
|
21
|
+
return login;
|
|
22
|
+
const email = me?.account?.email?.trim();
|
|
23
|
+
return email || null;
|
|
24
|
+
}
|
|
25
|
+
function cleanString(value) {
|
|
26
|
+
return typeof value === "string" && value.trim() ? value.trim() : undefined;
|
|
27
|
+
}
|
|
28
|
+
function compact(store) {
|
|
29
|
+
const out = { accounts: store.accounts };
|
|
30
|
+
if (store.activeAccount && store.accounts[store.activeAccount])
|
|
31
|
+
out.activeAccount = store.activeAccount;
|
|
32
|
+
if (store.apiBase)
|
|
33
|
+
out.apiBase = store.apiBase;
|
|
34
|
+
if (store.ignoreEnv)
|
|
35
|
+
out.ignoreEnv = true;
|
|
36
|
+
return out;
|
|
37
|
+
}
|
|
38
|
+
function storedAccount(apiKey, apiBase) {
|
|
39
|
+
return apiBase ? { apiKey, apiBase } : { apiKey };
|
|
40
|
+
}
|
|
41
|
+
export function serializeStore(store) {
|
|
42
|
+
const out = compact(store);
|
|
43
|
+
const active = out.activeAccount ? out.accounts[out.activeAccount] : undefined;
|
|
44
|
+
if (!active)
|
|
45
|
+
return out;
|
|
46
|
+
out.apiKey = active.apiKey;
|
|
47
|
+
if (active.apiBase)
|
|
48
|
+
out.apiBase = active.apiBase;
|
|
49
|
+
else
|
|
50
|
+
delete out.apiBase;
|
|
51
|
+
return out;
|
|
52
|
+
}
|
|
53
|
+
export function migrateStore(raw) {
|
|
54
|
+
const obj = raw && typeof raw === "object" ? raw : {};
|
|
55
|
+
const ignoreEnv = obj.ignoreEnv === true ? true : undefined;
|
|
56
|
+
const apiBase = cleanString(obj.apiBase);
|
|
57
|
+
const apiKey = cleanString(obj.apiKey);
|
|
58
|
+
if (obj.accounts && typeof obj.accounts === "object") {
|
|
59
|
+
const accounts = {};
|
|
60
|
+
for (const [name, value] of Object.entries(obj.accounts)) {
|
|
61
|
+
const entry = value && typeof value === "object" ? value : {};
|
|
62
|
+
const key = cleanString(entry.apiKey);
|
|
63
|
+
if (!key)
|
|
64
|
+
continue;
|
|
65
|
+
accounts[name] = storedAccount(key, cleanString(entry.apiBase));
|
|
66
|
+
}
|
|
67
|
+
const requested = cleanString(obj.activeAccount);
|
|
68
|
+
let activeAccount = requested && Object.hasOwn(accounts, requested) ? requested : Object.keys(accounts)[0];
|
|
69
|
+
let migrated = false;
|
|
70
|
+
if (apiKey) {
|
|
71
|
+
const owner = Object.keys(accounts).find((name) => accounts[name].apiKey === apiKey);
|
|
72
|
+
if (!owner) {
|
|
73
|
+
if (Object.keys(accounts).length < ACCOUNT_LIMIT) {
|
|
74
|
+
const name = provisionalAccountName(apiKey);
|
|
75
|
+
accounts[name] = storedAccount(apiKey, apiBase);
|
|
76
|
+
activeAccount = name;
|
|
77
|
+
migrated = true;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
else if (owner !== activeAccount) {
|
|
81
|
+
activeAccount = owner;
|
|
82
|
+
migrated = true;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
else if (activeAccount) {
|
|
86
|
+
migrated = true;
|
|
87
|
+
}
|
|
88
|
+
const store = compact({ accounts, activeAccount, apiBase: activeAccount ? undefined : apiBase, ignoreEnv });
|
|
89
|
+
return { store, migrated };
|
|
16
90
|
}
|
|
17
|
-
|
|
18
|
-
return {};
|
|
91
|
+
if (!apiKey) {
|
|
92
|
+
return { store: compact({ accounts: {}, apiBase, ignoreEnv }), migrated: Object.keys(obj).length > 0 };
|
|
19
93
|
}
|
|
94
|
+
const name = provisionalAccountName(apiKey);
|
|
95
|
+
return {
|
|
96
|
+
store: compact({ accounts: { [name]: storedAccount(apiKey, apiBase) }, activeAccount: name, ignoreEnv }),
|
|
97
|
+
migrated: true,
|
|
98
|
+
};
|
|
20
99
|
}
|
|
21
|
-
export async function
|
|
22
|
-
const
|
|
23
|
-
await mkdir(
|
|
24
|
-
await writeFile(
|
|
100
|
+
export async function writeStore(store) {
|
|
101
|
+
const file = configPath();
|
|
102
|
+
await mkdir(path.dirname(file), { recursive: true });
|
|
103
|
+
await writeFile(file, JSON.stringify(serializeStore(store), null, 2) + "\n", {
|
|
25
104
|
encoding: "utf8",
|
|
26
105
|
mode: 0o600,
|
|
27
106
|
});
|
|
28
107
|
}
|
|
29
|
-
export async function
|
|
30
|
-
|
|
108
|
+
export async function readStore() {
|
|
109
|
+
let raw;
|
|
110
|
+
try {
|
|
111
|
+
raw = JSON.parse(await readFile(configPath(), "utf8"));
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
return { accounts: {} };
|
|
115
|
+
}
|
|
116
|
+
const { store, migrated } = migrateStore(raw);
|
|
117
|
+
if (migrated) {
|
|
118
|
+
try {
|
|
119
|
+
await writeStore(store);
|
|
120
|
+
}
|
|
121
|
+
catch {
|
|
122
|
+
return store;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return store;
|
|
126
|
+
}
|
|
127
|
+
function accountNames(store) {
|
|
128
|
+
return Object.keys(store.accounts);
|
|
129
|
+
}
|
|
130
|
+
function unknownAccountMessage(name, store) {
|
|
131
|
+
const names = accountNames(store);
|
|
132
|
+
if (names.length === 0) {
|
|
133
|
+
return `No account named "${name}". Nobody is logged in; run \`mergestorm login\`.`;
|
|
134
|
+
}
|
|
135
|
+
return `No account named "${name}". Accounts: ${names.join(", ")}.`;
|
|
136
|
+
}
|
|
137
|
+
let accountOverride;
|
|
138
|
+
export async function withAccount(name, fn) {
|
|
139
|
+
if (!name)
|
|
140
|
+
return fn();
|
|
141
|
+
const previous = accountOverride;
|
|
142
|
+
accountOverride = name;
|
|
143
|
+
try {
|
|
144
|
+
return await fn();
|
|
145
|
+
}
|
|
146
|
+
finally {
|
|
147
|
+
accountOverride = previous;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
export function requestedAccount(store, env = process.env) {
|
|
151
|
+
if (accountOverride)
|
|
152
|
+
return accountOverride;
|
|
153
|
+
if (store.ignoreEnv)
|
|
154
|
+
return undefined;
|
|
155
|
+
return env.MERGESTORM_ACCOUNT?.trim() || undefined;
|
|
156
|
+
}
|
|
157
|
+
export function selectAccountName(store, requested) {
|
|
158
|
+
if (!requested)
|
|
159
|
+
return store.activeAccount;
|
|
160
|
+
if (Object.hasOwn(store.accounts, requested))
|
|
161
|
+
return requested;
|
|
162
|
+
throw new CommandError(unknownAccountMessage(requested, store));
|
|
163
|
+
}
|
|
164
|
+
export function configFor(store, name) {
|
|
165
|
+
const account = name ? store.accounts[name] : undefined;
|
|
166
|
+
const cfg = {};
|
|
167
|
+
if (account && name) {
|
|
168
|
+
cfg.apiKey = account.apiKey;
|
|
169
|
+
cfg.account = name;
|
|
170
|
+
}
|
|
171
|
+
const base = account?.apiBase ?? store.apiBase;
|
|
172
|
+
if (base)
|
|
173
|
+
cfg.apiBase = base;
|
|
174
|
+
if (store.ignoreEnv)
|
|
175
|
+
cfg.ignoreEnv = true;
|
|
176
|
+
return cfg;
|
|
177
|
+
}
|
|
178
|
+
export async function loadConfig() {
|
|
179
|
+
const store = await readStore();
|
|
180
|
+
return configFor(store, selectAccountName(store, requestedAccount(store)));
|
|
181
|
+
}
|
|
182
|
+
export function accountLimitMessage(store) {
|
|
183
|
+
return `Already ${ACCOUNT_LIMIT} accounts: ${accountNames(store).join(", ")}. Remove one with \`mergestorm account remove <name>\` first.`;
|
|
184
|
+
}
|
|
185
|
+
export async function saveAccount(name, account, opts = {}) {
|
|
186
|
+
const store = await readStore();
|
|
187
|
+
if (!Object.hasOwn(store.accounts, name) && accountNames(store).length >= ACCOUNT_LIMIT) {
|
|
188
|
+
throw new CommandError(accountLimitMessage(store), 1, "account_limit");
|
|
189
|
+
}
|
|
190
|
+
store.accounts[name] = storedAccount(account.apiKey, account.apiBase);
|
|
191
|
+
if (opts.activate || !store.activeAccount)
|
|
192
|
+
store.activeAccount = name;
|
|
193
|
+
await writeStore(store);
|
|
194
|
+
return store;
|
|
195
|
+
}
|
|
196
|
+
export async function useAccount(name) {
|
|
197
|
+
const store = await readStore();
|
|
198
|
+
if (!Object.hasOwn(store.accounts, name))
|
|
199
|
+
throw new CommandError(unknownAccountMessage(name, store));
|
|
200
|
+
store.activeAccount = name;
|
|
201
|
+
await writeStore(store);
|
|
202
|
+
return store;
|
|
203
|
+
}
|
|
204
|
+
export async function removeAccount(name) {
|
|
205
|
+
const store = await readStore();
|
|
206
|
+
if (!Object.hasOwn(store.accounts, name))
|
|
207
|
+
throw new CommandError(unknownAccountMessage(name, store));
|
|
208
|
+
delete store.accounts[name];
|
|
209
|
+
if (store.activeAccount === name)
|
|
210
|
+
store.activeAccount = accountNames(store)[0];
|
|
211
|
+
await writeStore(store);
|
|
212
|
+
return store;
|
|
213
|
+
}
|
|
214
|
+
export async function renameAccount(from, to) {
|
|
215
|
+
const store = await readStore();
|
|
216
|
+
const account = store.accounts[from];
|
|
217
|
+
if (!account || from === to)
|
|
218
|
+
return store;
|
|
219
|
+
if (Object.hasOwn(store.accounts, to)) {
|
|
220
|
+
throw new CommandError(`An account named "${to}" already exists, so ${from} keeps its name.`);
|
|
221
|
+
}
|
|
222
|
+
delete store.accounts[from];
|
|
223
|
+
store.accounts[to] = account;
|
|
224
|
+
if (store.activeAccount === from)
|
|
225
|
+
store.activeAccount = to;
|
|
226
|
+
await writeStore(store);
|
|
227
|
+
return store;
|
|
228
|
+
}
|
|
229
|
+
export function summarizeAccounts(store) {
|
|
230
|
+
return Object.entries(store.accounts).map(([name, account]) => ({
|
|
231
|
+
name,
|
|
232
|
+
active: name === store.activeAccount,
|
|
233
|
+
keyPrefix: provisionalAccountName(account.apiKey),
|
|
234
|
+
apiBase: (account.apiBase ?? store.apiBase ?? DEFAULT_API).replace(/\/$/, ""),
|
|
235
|
+
}));
|
|
236
|
+
}
|
|
237
|
+
export async function listAccounts() {
|
|
238
|
+
return summarizeAccounts(await readStore());
|
|
239
|
+
}
|
|
240
|
+
export async function settleAccountName(cfg, me) {
|
|
241
|
+
if (!cfg.account || !cfg.apiKey || resolveApiKey(cfg) !== cfg.apiKey)
|
|
242
|
+
return cfg.account;
|
|
243
|
+
if (cfg.account !== provisionalAccountName(cfg.apiKey))
|
|
244
|
+
return cfg.account;
|
|
245
|
+
const name = accountNameFromMe(me);
|
|
246
|
+
if (!name || name === cfg.account)
|
|
247
|
+
return cfg.account;
|
|
248
|
+
try {
|
|
249
|
+
await renameAccount(cfg.account, name);
|
|
250
|
+
}
|
|
251
|
+
catch {
|
|
252
|
+
return cfg.account;
|
|
253
|
+
}
|
|
254
|
+
return name;
|
|
255
|
+
}
|
|
256
|
+
export function splitAccountFlag(args) {
|
|
257
|
+
const rest = [];
|
|
258
|
+
let account;
|
|
259
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
260
|
+
const arg = args[i];
|
|
261
|
+
let value;
|
|
262
|
+
if (arg === "--account") {
|
|
263
|
+
value = args[i + 1];
|
|
264
|
+
if (value === undefined || value.startsWith("-"))
|
|
265
|
+
value = "";
|
|
266
|
+
else
|
|
267
|
+
i += 1;
|
|
268
|
+
}
|
|
269
|
+
else if (arg.startsWith("--account=")) {
|
|
270
|
+
value = arg.slice("--account=".length);
|
|
271
|
+
}
|
|
272
|
+
else {
|
|
273
|
+
rest.push(arg);
|
|
274
|
+
continue;
|
|
275
|
+
}
|
|
276
|
+
if (!value.trim()) {
|
|
277
|
+
throw new CommandError("--account needs an account name. `mergestorm account list` shows them.", 1, "usage");
|
|
278
|
+
}
|
|
279
|
+
account = value.trim();
|
|
280
|
+
}
|
|
281
|
+
return account ? { account, args: rest } : { args: rest };
|
|
31
282
|
}
|
|
32
283
|
export function apiBase(cfg) {
|
|
33
284
|
const fromEnv = cfg.ignoreEnv ? undefined : process.env.MERGESTORM_API_URL?.trim();
|
|
34
285
|
return (fromEnv || cfg.apiBase || DEFAULT_API).replace(/\/$/, "");
|
|
35
286
|
}
|
|
287
|
+
const CHAT_ID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
288
|
+
export function resolveChatId(cfg) {
|
|
289
|
+
if (cfg.ignoreEnv)
|
|
290
|
+
return undefined;
|
|
291
|
+
const chatId = process.env.MERGESTORM_CHAT_ID?.trim() ?? "";
|
|
292
|
+
return CHAT_ID.test(chatId) ? chatId.toLowerCase() : undefined;
|
|
293
|
+
}
|
|
36
294
|
export function resolveApiKey(cfg) {
|
|
37
295
|
if (cfg.ignoreEnv)
|
|
38
296
|
return cfg.apiKey;
|
package/dist/errors.d.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Stable machine codes for CommandError control flow.
|
|
3
3
|
* UX copy in `message` may change freely; match on `code`, never on message text.
|
|
4
4
|
*/
|
|
5
|
-
export type CommandErrorCode = "auth_invalid" | "api_timeout" | "not_a_repo" | "not_found" | "missing_api_key" | "registered_parent" | "implicit_stack" | "usage" | "review_quota" | "review_failed" | "review_timeout" | "rate_limited" | "cyclone_not_installed" | "pr_merged" | "pr_closed" | "not_stacked" | "draft" | "reserved_branch" | "supabase_unconfigured" | "cyclone_not_connected" | "cyclone_permission_missing" | "busy" | "adopt_failed" | "github_unreadable" | "stack_attention" | "head_changed" | "base_changed" | "tree_mismatch" | "protected_branch" | "payload_too_large" | "unsupported_publication" | "publication_failed" | "unknown_label";
|
|
5
|
+
export type CommandErrorCode = "auth_invalid" | "api_timeout" | "not_a_repo" | "not_found" | "missing_api_key" | "registered_parent" | "implicit_stack" | "usage" | "review_quota" | "review_failed" | "review_timeout" | "rate_limited" | "cyclone_not_installed" | "pr_merged" | "pr_closed" | "not_stacked" | "draft" | "reserved_branch" | "supabase_unconfigured" | "cyclone_not_connected" | "account_limit" | "surge_other_account" | "cyclone_permission_missing" | "busy" | "adopt_failed" | "github_unreadable" | "stack_attention" | "head_changed" | "base_changed" | "tree_mismatch" | "protected_branch" | "payload_too_large" | "unsupported_publication" | "publication_failed" | "git_failed" | "unknown_label" | "invalid_input" | "memory_off" | "memory_full" | "memory_similar" | "memory_unavailable" | "web_unavailable" | "issue_not_approved" | "issue_review_unavailable";
|
|
6
6
|
/** Stable one-shot exit codes for machine callers of `mergestorm review`. */
|
|
7
7
|
export declare const REVIEW_EXIT: {
|
|
8
8
|
readonly success: 0;
|