mergestorm 0.3.15 → 0.3.17
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 +4 -4
- package/dist/api.d.ts +44 -4
- package/dist/api.js +55 -12
- package/dist/automation-catalog.d.ts +37 -0
- package/dist/automation-catalog.js +13 -0
- package/dist/client/index.d.ts +4 -2
- package/dist/client/index.js +4 -2
- package/dist/commands/browse.d.ts +2 -2
- package/dist/commands/browse.js +7 -11
- package/dist/commands/credits.js +1 -0
- package/dist/commands/pr.d.ts +4 -0
- package/dist/commands/pr.js +32 -5
- package/dist/commands/registry.js +7 -4
- package/dist/commands/review-client.d.ts +10 -3
- package/dist/commands/review-client.js +75 -24
- package/dist/commands/review.js +17 -14
- package/dist/commands/settings.d.ts +2 -2
- package/dist/commands/settings.js +2 -9
- package/dist/commands/stack.d.ts +55 -2
- package/dist/commands/stack.js +274 -37
- package/dist/commands/status.js +4 -2
- package/dist/commands/whoami.js +9 -9
- package/dist/git.js +1 -1
- package/dist/repo-identity.d.ts +1 -1
- package/dist/repo-identity.js +13 -2
- package/dist/stack-dto.d.ts +55 -2
- package/dist/stack-dto.js +0 -6
- package/dist/stack-meta.d.ts +12 -0
- package/dist/stack-meta.js +32 -3
- package/dist/ui/banner.js +6 -0
- package/dist/ui/human-summary.d.ts +2 -0
- package/dist/ui/human-summary.js +153 -0
- package/dist/ui/usage.d.ts +1 -2
- package/dist/ui/usage.js +3 -4
- package/package.json +1 -1
- package/skill/SKILL.md +3 -3
- package/skill/mergestorm-pr-loop/SKILL.md +36 -21
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { readFile, realpath } from "node:fs/promises";
|
|
2
2
|
import { join, relative, resolve, sep } from "node:path";
|
|
3
|
-
import { apiFetch } from "../api.js";
|
|
3
|
+
import { apiFetch, applyPrReviewQuery, isPositiveInteger, } from "../api.js";
|
|
4
4
|
import { CommandError, REVIEW_EXIT, isCommandErrorCode, rateLimitedMessage } from "../errors.js";
|
|
5
5
|
import { changedNamesInSandbox, collectChangedFiles, git, parseGithubOriginRepo, repoRoot, } from "../git.js";
|
|
6
6
|
export const REVIEW_POLL_INTERVAL_MS = 2_000;
|
|
@@ -12,6 +12,8 @@ export const REVIEW_POLL_STRETCH_AFTER_MS = 60_000;
|
|
|
12
12
|
export const REVIEW_POLL_STRETCH_SPAN_MS = 60_000;
|
|
13
13
|
/** Same thread slug contract as POST /api/v1/reviews. */
|
|
14
14
|
export const REVIEW_THREAD_SLUG_RE = /^[a-zA-Z0-9][a-zA-Z0-9._/-]{0,119}$/;
|
|
15
|
+
/** Same product limit as `api/src/v1/reviews.ts` `MAX_DIFF_BYTES`. */
|
|
16
|
+
export const MAX_REVIEW_DIFF_BYTES = 1_000_000;
|
|
15
17
|
export const MAX_CONTEXT_TEXT_CHARS = 60_000;
|
|
16
18
|
export const MAX_CONTEXT_FILES = 10;
|
|
17
19
|
export const MAX_CONTEXT_FILE_CHARS = 16_000;
|
|
@@ -25,9 +27,11 @@ export async function collectReviewInput(base, head, cwd = process.cwd(), sandbo
|
|
|
25
27
|
diff = "";
|
|
26
28
|
}
|
|
27
29
|
else {
|
|
28
|
-
const
|
|
29
|
-
const
|
|
30
|
-
|
|
30
|
+
const canonicalCwd = await realpath(cwd);
|
|
31
|
+
const root = repoRoot(canonicalCwd);
|
|
32
|
+
const specs = names.map((name) => `:(literal)${relative(canonicalCwd, join(root, name))}`);
|
|
33
|
+
// Collection must not execute configured diff or text conversion helpers.
|
|
34
|
+
diff = git(["diff", "--no-ext-diff", "--no-textconv", `${base}...${head}`, "--", ...specs], cwd);
|
|
31
35
|
}
|
|
32
36
|
}
|
|
33
37
|
catch (err) {
|
|
@@ -83,26 +87,28 @@ export async function loadReviewContext(opts) {
|
|
|
83
87
|
let total = 0;
|
|
84
88
|
for (const rawPath of opts.contextFiles) {
|
|
85
89
|
const abs = resolve(cwd, rawPath);
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
90
|
+
const boundaryLabel = opts.sandboxCwd ? "working directory" : "repo root";
|
|
91
|
+
let targetReal;
|
|
92
|
+
let sandboxReal;
|
|
93
|
+
try {
|
|
94
|
+
sandboxReal = opts.sandboxCwd ? await realpath(resolve(opts.sandboxCwd)) : repoRoot(cwd);
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
throw contextUsage(`--context-file ${rawPath} must be inside the ${boundaryLabel}`);
|
|
98
|
+
}
|
|
99
|
+
try {
|
|
100
|
+
targetReal = await realpath(abs);
|
|
101
|
+
}
|
|
102
|
+
catch (err) {
|
|
103
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
104
|
+
throw contextUsage(`could not read --context-file ${rawPath}: ${detail}`);
|
|
105
|
+
}
|
|
106
|
+
if (targetReal !== sandboxReal && !targetReal.startsWith(`${sandboxReal}${sep}`)) {
|
|
107
|
+
throw contextUsage(`--context-file ${rawPath} must be inside the ${boundaryLabel}`);
|
|
102
108
|
}
|
|
103
109
|
let content;
|
|
104
110
|
try {
|
|
105
|
-
content = await readFile(
|
|
111
|
+
content = await readFile(targetReal, "utf8");
|
|
106
112
|
}
|
|
107
113
|
catch (err) {
|
|
108
114
|
const detail = err instanceof Error ? err.message : String(err);
|
|
@@ -148,6 +154,39 @@ export async function submitReview(cfg, input, opts = {}) {
|
|
|
148
154
|
...(retryAfterSeconds !== undefined ? { retryAfterSeconds } : {}),
|
|
149
155
|
};
|
|
150
156
|
}
|
|
157
|
+
function reviewSubmitErrorText(body) {
|
|
158
|
+
if (body && typeof body === "object") {
|
|
159
|
+
const rec = body;
|
|
160
|
+
const message = typeof rec.message === "string" ? rec.message.trim() : "";
|
|
161
|
+
const error = typeof rec.error === "string" ? rec.error.trim() : "";
|
|
162
|
+
if (message)
|
|
163
|
+
return message;
|
|
164
|
+
if (error)
|
|
165
|
+
return error;
|
|
166
|
+
}
|
|
167
|
+
if (typeof body === "string") {
|
|
168
|
+
const trimmed = body.trim();
|
|
169
|
+
if (trimmed && !trimmed.startsWith("<"))
|
|
170
|
+
return trimmed.slice(0, 240);
|
|
171
|
+
}
|
|
172
|
+
return "";
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Human copy for a failed POST /api/v1/reviews.
|
|
176
|
+
* nginx 413 is HTML, so `submitReview` stores `{}` and used to print `Failed: {}`.
|
|
177
|
+
*/
|
|
178
|
+
export function formatReviewSubmitError(status, body) {
|
|
179
|
+
const detail = reviewSubmitErrorText(body);
|
|
180
|
+
if (status === 413) {
|
|
181
|
+
if (detail)
|
|
182
|
+
return `${detail} No review job was created and no credits were charged.`;
|
|
183
|
+
return ("Upload rejected (HTTP 413). nginx stopped the body before a review job was created, " +
|
|
184
|
+
"so no credits were charged. Max diff size is 1 MB / 1_000_000 bytes.");
|
|
185
|
+
}
|
|
186
|
+
if (detail)
|
|
187
|
+
return `Failed (HTTP ${status}): ${detail}`;
|
|
188
|
+
return `Failed (HTTP ${status}). The API did not return a JSON error body.`;
|
|
189
|
+
}
|
|
151
190
|
/** Exponential backoff with jitter in `[0.5, 1.0]` of the base delay. */
|
|
152
191
|
export function transientRetryWaitMs(input) {
|
|
153
192
|
const exp = REVIEW_POLL_BACKOFF_MS * 2 ** Math.max(0, input.failureCount - 1);
|
|
@@ -242,8 +281,10 @@ export async function pollPrVortexReview(cfg, owner, repo, prNumber, opts = {})
|
|
|
242
281
|
repo,
|
|
243
282
|
pr_number: String(prNumber),
|
|
244
283
|
});
|
|
245
|
-
|
|
246
|
-
|
|
284
|
+
// #2027: the pass selector is fixed for the whole wait. after_pass stays
|
|
285
|
+
// what the caller handed in on every poll and every retry; it is never
|
|
286
|
+
// replaced by the pass of an in-progress envelope seen along the way.
|
|
287
|
+
applyPrReviewQuery(query, opts);
|
|
247
288
|
let transientFailures = 0;
|
|
248
289
|
let lastEnvelope = null;
|
|
249
290
|
const sleepBeforeRetry = async (retryAfterSeconds) => {
|
|
@@ -310,7 +351,17 @@ export async function pollPrVortexReview(cfg, owner, repo, prNumber, opts = {})
|
|
|
310
351
|
if (!poll.body || typeof poll.body !== "object") {
|
|
311
352
|
throw new CommandError("Poll failed: invalid PR review response", REVIEW_EXIT.failed, "review_failed");
|
|
312
353
|
}
|
|
313
|
-
|
|
354
|
+
const envelope = poll.body;
|
|
355
|
+
// The server filters by pass; re-check here so a server that ignores the
|
|
356
|
+
// selector can never satisfy the wait with the wrong pass.
|
|
357
|
+
if ((opts.pass !== undefined || opts.afterPass !== undefined) &&
|
|
358
|
+
(!isPositiveInteger(envelope.pass) || (opts.pass !== undefined && envelope.pass !== opts.pass) ||
|
|
359
|
+
(opts.afterPass !== undefined && envelope.pass <= opts.afterPass))) {
|
|
360
|
+
opts.onTick?.("progress");
|
|
361
|
+
await sleepBeforePoll();
|
|
362
|
+
continue;
|
|
363
|
+
}
|
|
364
|
+
lastEnvelope = envelope;
|
|
314
365
|
const resting = (lastEnvelope.raw_status ?? lastEnvelope.status) !== "in_progress";
|
|
315
366
|
const matches = !opts.afterSha || shaMatches(lastEnvelope.head_sha, opts.afterSha);
|
|
316
367
|
if (resting && matches)
|
package/dist/commands/review.js
CHANGED
|
@@ -4,9 +4,10 @@ import { CommandError, DetachedError, REVIEW_EXIT, isCommandErrorCode, rateLimit
|
|
|
4
4
|
import { discoverTrunk } from "../git-stack.js";
|
|
5
5
|
import { ansi } from "../ui/ansi.js";
|
|
6
6
|
import { formatFindingsLines } from "../ui/findings.js";
|
|
7
|
+
import { humanSummary } from "../ui/human-summary.js";
|
|
7
8
|
import { present } from "../ui/present.js";
|
|
8
9
|
import { toReviewJobEnvelope, } from "../ui/job-envelope.js";
|
|
9
|
-
import { MAX_IDEMPOTENCY_KEY_CHARS, REVIEW_POLL_DEFAULT_TIMEOUT_MS, REVIEW_POLL_MAX_TRANSIENT_RETRIES, REVIEW_THREAD_SLUG_RE, ReviewPollTimeoutError, collectReviewInput, isTransientReviewPollError, isTransientReviewPollStatus, loadReviewContext, pollReview, submitReview, } from "./review-client.js";
|
|
10
|
+
import { MAX_IDEMPOTENCY_KEY_CHARS, REVIEW_POLL_DEFAULT_TIMEOUT_MS, REVIEW_POLL_MAX_TRANSIENT_RETRIES, REVIEW_THREAD_SLUG_RE, ReviewPollTimeoutError, collectReviewInput, isTransientReviewPollError, isTransientReviewPollStatus, loadReviewContext, pollReview, formatReviewSubmitError, submitReview, } from "./review-client.js";
|
|
10
11
|
export { REVIEW_POLL_MAX_TRANSIENT_RETRIES, isTransientReviewPollError, isTransientReviewPollStatus, };
|
|
11
12
|
/** Same public specialist allowlist as the API contract and migration 117. */
|
|
12
13
|
export const VORTEX_ROUTER_MODES = ["off", "standard", "max", "manual"];
|
|
@@ -255,7 +256,6 @@ export async function cmdReview(args, opts = {}) {
|
|
|
255
256
|
};
|
|
256
257
|
try {
|
|
257
258
|
const cfg = await loadConfig();
|
|
258
|
-
const meBefore = interactive ? await getMe(cfg) : null;
|
|
259
259
|
progress(`Collecting diff ${base}...${head} …`);
|
|
260
260
|
const input = await collectReviewInput(base, head);
|
|
261
261
|
if (!input) {
|
|
@@ -320,7 +320,7 @@ export async function cmdReview(args, opts = {}) {
|
|
|
320
320
|
});
|
|
321
321
|
}
|
|
322
322
|
if (submitted.status !== 202 && submitted.status !== 200) {
|
|
323
|
-
const message =
|
|
323
|
+
const message = formatReviewSubmitError(submitted.status, submitted.row);
|
|
324
324
|
if (json)
|
|
325
325
|
printJson(submitted.row, { status: "failed", error: message });
|
|
326
326
|
throw new CommandError(message, REVIEW_EXIT.failed, "review_failed");
|
|
@@ -407,21 +407,24 @@ export async function cmdReview(args, opts = {}) {
|
|
|
407
407
|
];
|
|
408
408
|
if (ran.length > 0)
|
|
409
409
|
result.push(ansi.dim(`Specialists: ${ran.join(", ")}`));
|
|
410
|
-
|
|
411
|
-
|
|
410
|
+
const summary = row.summary ? humanSummary(row.summary) : null;
|
|
411
|
+
if (summary)
|
|
412
|
+
result.push(summary);
|
|
412
413
|
result.push(...formatFindingsLines(row.findings, {
|
|
413
414
|
emptyMessage: true,
|
|
414
415
|
}));
|
|
415
|
-
if (interactive
|
|
416
|
-
const
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
result.push(ansi.dim(`${used} credit${used === 1 ? "" : "s"} used · ${afterLeft} remaining`));
|
|
423
|
-
}
|
|
416
|
+
if (interactive) {
|
|
417
|
+
const me = await getMe(cfg);
|
|
418
|
+
const used = row.credits?.standard;
|
|
419
|
+
const remaining = me?.usage.standard.remaining;
|
|
420
|
+
const credits = [];
|
|
421
|
+
if (used != null) {
|
|
422
|
+
credits.push(`${used} credit${used === 1 ? "" : "s"} used`);
|
|
424
423
|
}
|
|
424
|
+
if (remaining != null)
|
|
425
|
+
credits.push(`${remaining} remaining`);
|
|
426
|
+
if (credits.length > 0)
|
|
427
|
+
result.push(ansi.dim(credits.join(" · ")));
|
|
425
428
|
}
|
|
426
429
|
await present("Review", result);
|
|
427
430
|
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { type SettingsPatch, type SettingsResponse
|
|
1
|
+
import { type SettingsPatch, type SettingsResponse } from "../api.js";
|
|
2
2
|
/**
|
|
3
3
|
* CLI flag → Bearer settings key. on|off only. The connected flags are
|
|
4
4
|
* read-only on the API and deliberately have no flag here.
|
|
5
5
|
*/
|
|
6
|
-
export declare const SETTINGS_FLAGS: Record<string,
|
|
6
|
+
export declare const SETTINGS_FLAGS: Record<string, "auto_review_enabled" | "auto_patch_enabled" | "vortex_show_thinking_traces" | "repo_overview_enabled" | "review_unit_land_prs_enabled" | "cyclone_review_unit_land_prs_enabled" | "vortex_seam_specialist_enabled" | "auto_land_default">;
|
|
7
7
|
export type SettingsArgs = {
|
|
8
8
|
json: boolean;
|
|
9
9
|
patch: SettingsPatch;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { getSettings, patchSettings, } from "../api.js";
|
|
2
|
+
import { BEARER_SETTINGS_FLAGS } from "../automation-catalog.js";
|
|
2
3
|
import { loadConfig } from "../config.js";
|
|
3
4
|
import { CommandError } from "../errors.js";
|
|
4
5
|
import { present } from "../ui/present.js";
|
|
@@ -7,15 +8,7 @@ import { buildConfigRows, canBrowse, openTabsBrowser } from "./browse.js";
|
|
|
7
8
|
* CLI flag → Bearer settings key. on|off only. The connected flags are
|
|
8
9
|
* read-only on the API and deliberately have no flag here.
|
|
9
10
|
*/
|
|
10
|
-
export const SETTINGS_FLAGS =
|
|
11
|
-
"--auto-review": "auto_review_enabled",
|
|
12
|
-
"--auto-patch": "auto_patch_enabled",
|
|
13
|
-
"--vortex-thinking": "vortex_show_thinking_traces",
|
|
14
|
-
"--repo-overview": "repo_overview_enabled",
|
|
15
|
-
"--review-unit-land": "review_unit_land_prs_enabled",
|
|
16
|
-
"--cyclone-review-unit-land": "cyclone_review_unit_land_prs_enabled",
|
|
17
|
-
"--vortex-seam": "vortex_seam_specialist_enabled",
|
|
18
|
-
};
|
|
11
|
+
export const SETTINGS_FLAGS = BEARER_SETTINGS_FLAGS;
|
|
19
12
|
/** Parse `settings` argv. Accepts `--flag on|off` and `--flag=on|off`. */
|
|
20
13
|
export function parseSettingsArgs(args) {
|
|
21
14
|
let json = false;
|
package/dist/commands/stack.d.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { adoptStack, ensureUpperPark, listStacks, type StackDto } from "../api.js";
|
|
1
|
+
import { adoptStack, ensureUpperPark, listStacks, setStackPolicy, type StackDto, type StackPolicyPatch } from "../api.js";
|
|
2
2
|
import { loadConfig } from "../config.js";
|
|
3
3
|
import { createPr, findOpenPrNumber } from "../gh.js";
|
|
4
4
|
import { parseGithubOriginRepo } from "../git.js";
|
|
5
|
-
import { branchExists, commitsAheadOf, fetchRemoteBranch, pushBranch, tipCommitMessage, tipCommitSubject } from "../git-stack.js";
|
|
5
|
+
import { branchExists, commitsAheadOf, createBranchFromHead, currentBranch, defaultLayerBranchName, deleteBranch, discoverTrunk, fetchRemoteBranch, pushBranch, tipCommitMessage, tipCommitSubject, worktreeDirty } from "../git-stack.js";
|
|
6
6
|
import { buildPrBodyFromCommit } from "../pr-body.js";
|
|
7
|
+
import { gitTopLevel } from "../repo-identity.js";
|
|
7
8
|
import { loadStackMeta, saveStackMeta } from "../stack-meta.js";
|
|
8
9
|
/** A registered stack layer that would become the parent of a new layer. */
|
|
9
10
|
export type RegisteredParentHit = {
|
|
@@ -30,27 +31,72 @@ export declare function parseAdoptTarget(args: string[]): {
|
|
|
30
31
|
};
|
|
31
32
|
/** Empty + populated bodies for the TTY `stack list` panel. */
|
|
32
33
|
export declare function buildStackListLines(stacks: StackDto[]): string[];
|
|
34
|
+
/** Short per-stack policy suffixes: Auto land when on, overrides when pinned. */
|
|
35
|
+
export declare function stackPolicyLabels(s: Pick<StackDto, "autoEnqueueWhenReady" | "autoReviewOverride" | "autoPatchOverride">): string[];
|
|
36
|
+
/** Per-open policy flags shared by create, submit, and adopt (`on|off` only). */
|
|
37
|
+
export type StackOpenPolicyFlags = {
|
|
38
|
+
autoLand?: boolean;
|
|
39
|
+
autoReview?: boolean;
|
|
40
|
+
autoPatch?: boolean;
|
|
41
|
+
};
|
|
42
|
+
/** Wire policy for adopt from the parsed `on|off` flags; absent keys stay off the body. */
|
|
43
|
+
export declare function openPolicyPatch(flags: StackOpenPolicyFlags): StackPolicyPatch | undefined;
|
|
33
44
|
/** Parse `stack create` argv into options. Exported for tests. */
|
|
34
45
|
export declare function parseStackCreateArgs(args: string[]): {
|
|
35
46
|
name?: string;
|
|
36
47
|
onto?: string;
|
|
37
48
|
trunk?: string;
|
|
38
49
|
extend: boolean;
|
|
50
|
+
autoLand?: boolean;
|
|
51
|
+
autoReview?: boolean;
|
|
52
|
+
autoPatch?: boolean;
|
|
39
53
|
asJson: boolean;
|
|
40
54
|
};
|
|
41
55
|
/** Parse `stack submit` argv. Exported for tests. */
|
|
42
56
|
export declare function parseStackSubmitArgs(args: string[]): {
|
|
43
57
|
extend: boolean;
|
|
58
|
+
autoLand?: boolean;
|
|
59
|
+
autoReview?: boolean;
|
|
60
|
+
autoPatch?: boolean;
|
|
61
|
+
asJson: boolean;
|
|
62
|
+
};
|
|
63
|
+
export declare function parseStackAdoptArgs(args: string[]): {
|
|
64
|
+
owner: string;
|
|
65
|
+
repo: string;
|
|
66
|
+
prNumber: number;
|
|
67
|
+
autoLand?: boolean;
|
|
68
|
+
autoReview?: boolean;
|
|
69
|
+
autoPatch?: boolean;
|
|
70
|
+
asJson: boolean;
|
|
71
|
+
};
|
|
72
|
+
export type StackSetArgs = {
|
|
73
|
+
stackId: string;
|
|
74
|
+
/** At least one of these is present. */
|
|
75
|
+
autoLand?: boolean;
|
|
76
|
+
autoReview?: boolean | null;
|
|
77
|
+
autoPatch?: boolean | null;
|
|
44
78
|
asJson: boolean;
|
|
45
79
|
};
|
|
80
|
+
export declare function parseStackSetArgs(args: string[]): StackSetArgs;
|
|
46
81
|
export declare function parseStackResetArgs(args: string[]): void;
|
|
47
82
|
/** Injectable seams for unit tests. Production callers omit deps. */
|
|
48
83
|
export type StackCreateDeps = {
|
|
49
84
|
cwd?: string;
|
|
85
|
+
gitTopLevel?: typeof gitTopLevel;
|
|
86
|
+
worktreeDirty?: typeof worktreeDirty;
|
|
87
|
+
currentBranch?: typeof currentBranch;
|
|
88
|
+
branchExists?: typeof branchExists;
|
|
89
|
+
createBranchFromHead?: typeof createBranchFromHead;
|
|
90
|
+
deleteBranch?: typeof deleteBranch;
|
|
91
|
+
discoverTrunk?: typeof discoverTrunk;
|
|
92
|
+
defaultLayerBranchName?: typeof defaultLayerBranchName;
|
|
93
|
+
loadStackMeta?: typeof loadStackMeta;
|
|
94
|
+
saveStackMeta?: typeof saveStackMeta;
|
|
50
95
|
listStacks?: typeof listStacks;
|
|
51
96
|
loadConfig?: typeof loadConfig;
|
|
52
97
|
parseGithubOriginRepo?: typeof parseGithubOriginRepo;
|
|
53
98
|
};
|
|
99
|
+
export declare function cmdStackCreate(args: string[], deps?: StackCreateDeps): Promise<void>;
|
|
54
100
|
/** Injectable seams for unit tests (STRUCT-04). Production callers omit deps. */
|
|
55
101
|
export type StackSubmitDeps = {
|
|
56
102
|
cwd?: string;
|
|
@@ -74,4 +120,11 @@ export type StackSubmitDeps = {
|
|
|
74
120
|
ensureUpperPark?: typeof ensureUpperPark;
|
|
75
121
|
};
|
|
76
122
|
export declare function cmdStackSubmit(args: string[], deps?: StackSubmitDeps): Promise<void>;
|
|
123
|
+
export type StackSetDeps = {
|
|
124
|
+
loadConfig?: typeof loadConfig;
|
|
125
|
+
setStackPolicy?: typeof setStackPolicy;
|
|
126
|
+
};
|
|
127
|
+
/** Human summary lines for a `stack set` write, one per flag. */
|
|
128
|
+
export declare function stackSetSummary(parsed: StackSetArgs): string[];
|
|
129
|
+
export declare function cmdStackSet(args: string[], deps?: StackSetDeps): Promise<void>;
|
|
77
130
|
export declare function cmdStack(args: string[]): Promise<void>;
|