mergestorm 0.3.15 → 0.3.16
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 +23 -3
- package/dist/api.js +38 -11
- package/dist/automation-catalog.d.ts +37 -0
- package/dist/automation-catalog.js +13 -0
- package/dist/client/index.d.ts +2 -1
- package/dist/client/index.js +2 -1
- package/dist/commands/browse.d.ts +2 -2
- package/dist/commands/browse.js +2 -9
- package/dist/commands/registry.js +5 -3
- 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 +263 -32
- package/dist/repo-identity.d.ts +1 -1
- package/dist/repo-identity.js +13 -2
- package/dist/stack-dto.d.ts +10 -0
- package/dist/stack-meta.d.ts +12 -0
- package/dist/stack-meta.js +32 -3
- package/dist/ui/banner.js +3 -0
- package/package.json +1 -1
- package/skill/mergestorm-pr-loop/SKILL.md +18 -10
package/README.md
CHANGED
|
@@ -45,12 +45,12 @@ On a TTY, bare `mergestorm` (or `mergestorm shell`) opens a branded REPL: a comp
|
|
|
45
45
|
|
|
46
46
|
```
|
|
47
47
|
╭──────────────────────────────────────────────────────────────────────────────╮
|
|
48
|
-
│ ▟██▛ mergestorm v0.3.
|
|
48
|
+
│ ▟██▛ mergestorm v0.3.16 │
|
|
49
49
|
│ ▜▙ ● msk_live_… · maelstrom │
|
|
50
50
|
│ [████████░░░░░░░░░░░░░░░░░░░░░░] 25% used · Resets Sep 5, 10:53am (UTC)│
|
|
51
51
|
│ │
|
|
52
52
|
│ review a diff · usage for tabs · /help for all commands │
|
|
53
|
-
│ New in v0.3.
|
|
53
|
+
│ New in v0.3.16: verify findings, small diff, public dismiss on skip │
|
|
54
54
|
╰──────────────────────────────────────────────────────────────────────────────╯
|
|
55
55
|
mergestorm
|
|
56
56
|
╭──────────────────────────────────────────────────────────────────────────────╮
|
|
@@ -99,7 +99,7 @@ mergestorm stack reset --force Clear local authoring state (not branches/PRs)
|
|
|
99
99
|
|
|
100
100
|
`mg` is a short alias for `mergestorm` (same binary), e.g. `mg stack create`. Happy path: **create → commit → submit → restack → land**. On review-unit stacks, `stack land` **promotes** the tip into the unit (same gates as the dashboard); otherwise it lands the bottom open PR. `stack adopt` is for importing a chain that already exists on GitHub (legacy / Graphite). Restack/land use the same login key (`/api/v1/stacks`). Unattended landing is `mg queue add`.
|
|
101
101
|
|
|
102
|
-
`stack create` checks out a new branch from the current tip (or `--onto <branch>`), discovers trunk (`main` / `master` / `origin/HEAD`, overridable with `--trunk`), and records `{ branch, parentBranch }` in CLI-managed state under **`~/.mergestorm/stacks/<repo-id>/stack.json`**. The CLI updates this automatically; never edit it.
|
|
102
|
+
`stack create` checks out a new branch from the current tip (or `--onto <branch>`), discovers trunk (`main` / `master` / `origin/HEAD`, overridable with `--trunk`), and records `{ branch, parentBranch }` in CLI-managed state under **`~/.mergestorm/stacks/<repo-id>/stack.json`**. The CLI updates this automatically; never edit it. Each worktree has its own authoring state; independent clones stay isolated too. Creating onto trunk with a non-empty active stack starts a new one. `--trunk` only sets trunk metadata — it does **not** change the parent; use `--onto main` (etc.) for a fresh stack from trunk.
|
|
103
103
|
|
|
104
104
|
If the parent branch is already a layer of a **registered** (submitted) stack, `create` and `submit` refuse unless you pass **`--extend`**. That blocks accidentally gluing an unrelated PR onto an open unit. Growing a registered stack on purpose: `mg stack create --onto <tip> --extend` → commit → `mg stack submit --extend`.
|
|
105
105
|
|
|
@@ -119,7 +119,7 @@ Removes `~/.mergestorm/config.json` so the stored key is no longer used.
|
|
|
119
119
|
|
|
120
120
|
### `review [base] [head]`
|
|
121
121
|
|
|
122
|
-
Collects `git diff base...head` (default: discovered trunk — `origin/HEAD`, `main`, or `master`), attaches the changed file contents (up to 40 files, skipping files > 400 KB), submits the job, and polls until it finishes (~8 min max — Core plus a Max fleet can exceed 3 minutes). The thread slug defaults to the current branch (`local/<branch>`); pass `--thread` to override so follow-ups chain on a chosen slug. The CLI does not scan
|
|
122
|
+
Collects `git diff base...head` (default: discovered trunk — `origin/HEAD`, `main`, or `master`), attaches the changed file contents (up to 40 files, skipping files > 400 KB), submits the job, and polls until it finishes (~8 min max — Core plus a Max fleet can exceed 3 minutes). The thread slug defaults to the current branch (`local/<branch>`); pass `--thread` to override so follow-ups chain on a chosen slug. The CLI still does not scan a repo folder for review context. GitHub Vortex will read house-rule files in a later PR (`AGENTS.md`, `CLAUDE.md`, alwaysApply Cursor rules). That ingest is not shipping yet.
|
|
123
123
|
|
|
124
124
|
```bash
|
|
125
125
|
mergestorm review # discovered trunk (origin/HEAD, main, or master)...HEAD
|
package/dist/api.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type Config } from "./config.js";
|
|
2
|
+
import { BEARER_SETTINGS_KEYS as SETTINGS_WRITABLE_KEYS, type BearerSettingsKey as SettingsWritableKey } from "./automation-catalog.js";
|
|
2
3
|
import type { MergeQueueEntryDto, StackDto } from "./stack-dto.js";
|
|
3
4
|
export type { MergeQueueBounceDetail, MergeQueueBounceKind, MergeQueueEnqueuedBy, MergeQueueEnqueuedVia, MergeQueueEntryDto, MergeQueueEntryState, StackBranchState, StackCiStatus, StackDto, StackLayerChecks, StackLayerDto, StackReviewStatus, StackTempestStatus, StackUnitDto, StackUnitMemberDto, } from "./stack-dto.js";
|
|
4
5
|
/** Default per-request timeout so a hung API cannot stall the shell/CI forever. */
|
|
@@ -132,8 +133,8 @@ export type PrVortexReview = {
|
|
|
132
133
|
* Bearer-writable settings keys, mirroring the `/api/v1/settings` allowlist.
|
|
133
134
|
* The connected flags are read-only there and deliberately absent here.
|
|
134
135
|
*/
|
|
135
|
-
export
|
|
136
|
-
export type SettingsWritableKey
|
|
136
|
+
export { SETTINGS_WRITABLE_KEYS };
|
|
137
|
+
export type { SettingsWritableKey };
|
|
137
138
|
export type SettingsPatch = Partial<Record<SettingsWritableKey, boolean>>;
|
|
138
139
|
/** GET and PATCH `/api/v1/settings` both answer with this shape. */
|
|
139
140
|
export type SettingsResponse = Record<SettingsWritableKey, boolean> & {
|
|
@@ -180,13 +181,32 @@ export declare function listStacks(cfg?: Config, opts?: {
|
|
|
180
181
|
export declare function getEnrichedStack(stackId: string, cfg?: Config, opts?: {
|
|
181
182
|
timeoutMs?: number;
|
|
182
183
|
}): Promise<StackDto | null>;
|
|
184
|
+
/**
|
|
185
|
+
* Per-stack automation policy. Auto land is a boolean; the review / patch
|
|
186
|
+
* overrides are tri-state (`null` clears back to the account flag). Absent
|
|
187
|
+
* keys are left untouched.
|
|
188
|
+
*/
|
|
189
|
+
export type StackPolicyPatch = {
|
|
190
|
+
autoEnqueueWhenReady?: boolean;
|
|
191
|
+
autoReviewOverride?: boolean | null;
|
|
192
|
+
autoPatchOverride?: boolean | null;
|
|
193
|
+
};
|
|
194
|
+
/** Drop undefined keys so the wire body only carries what was requested. */
|
|
195
|
+
export declare function stackPolicyBody(policy: StackPolicyPatch | undefined): StackPolicyPatch;
|
|
183
196
|
/** Adopt an open PR chain (Bearer POST /api/v1/stacks/adopt). */
|
|
184
|
-
export declare function adoptStack(owner: string, repo: string, prNumber: number, cfg?: Config): Promise<unknown>;
|
|
197
|
+
export declare function adoptStack(owner: string, repo: string, prNumber: number, cfg?: Config, policy?: StackPolicyPatch): Promise<unknown>;
|
|
185
198
|
/** Mint or reuse the PR3+ park freeze (Bearer POST /api/v1/stacks/:id/ensure-upper-park). */
|
|
186
199
|
export declare function ensureUpperPark(stackId: string, cfg?: Config): Promise<{
|
|
187
200
|
freezeBranch: string;
|
|
188
201
|
created: boolean;
|
|
189
202
|
}>;
|
|
203
|
+
/** Arm or disarm Auto land on one owned stack. */
|
|
204
|
+
export declare function setStackAutoLand(stackId: string, enabled: boolean, cfg?: Config): Promise<unknown>;
|
|
205
|
+
/**
|
|
206
|
+
* Patch any subset of one owned stack's automation policy
|
|
207
|
+
* (Bearer PATCH /api/v1/stacks/:id). `null` clears an override.
|
|
208
|
+
*/
|
|
209
|
+
export declare function setStackPolicy(stackId: string, policy: StackPolicyPatch, cfg?: Config): Promise<unknown>;
|
|
190
210
|
/** Restack descendants (Bearer POST /api/v1/stacks/:id/restack). */
|
|
191
211
|
export declare function restackStack(stackId: string, cfg?: Config): Promise<unknown>;
|
|
192
212
|
/** Land the bottom open layer (Bearer POST /api/v1/stacks/:id/land-next). */
|
package/dist/api.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { apiBase, loadConfig, resolveApiKey } from "./config.js";
|
|
2
|
+
import { BEARER_SETTINGS_KEYS as SETTINGS_WRITABLE_KEYS, } from "./automation-catalog.js";
|
|
2
3
|
import { CommandError, REVIEW_EXIT, rateLimitedMessage } from "./errors.js";
|
|
3
4
|
/** Default per-request timeout so a hung API cannot stall the shell/CI forever. */
|
|
4
5
|
export const DEFAULT_API_TIMEOUT_MS = 30_000;
|
|
@@ -158,15 +159,7 @@ export async function devicePost(base, route, json, opts = {}) {
|
|
|
158
159
|
* Bearer-writable settings keys, mirroring the `/api/v1/settings` allowlist.
|
|
159
160
|
* The connected flags are read-only there and deliberately absent here.
|
|
160
161
|
*/
|
|
161
|
-
export
|
|
162
|
-
"auto_review_enabled",
|
|
163
|
-
"auto_patch_enabled",
|
|
164
|
-
"vortex_show_thinking_traces",
|
|
165
|
-
"repo_overview_enabled",
|
|
166
|
-
"review_unit_land_prs_enabled",
|
|
167
|
-
"cyclone_review_unit_land_prs_enabled",
|
|
168
|
-
"vortex_seam_specialist_enabled",
|
|
169
|
-
];
|
|
162
|
+
export { SETTINGS_WRITABLE_KEYS };
|
|
170
163
|
/**
|
|
171
164
|
* Fetch Bearer /api/v1/settings; null on 404/network/timeouts so panels can
|
|
172
165
|
* degrade (same contract as {@link getMe}), while 401 still surfaces.
|
|
@@ -343,12 +336,31 @@ export async function getEnrichedStack(stackId, cfg, opts) {
|
|
|
343
336
|
}
|
|
344
337
|
return stacks.find((stack) => stack?.id === id) ?? null;
|
|
345
338
|
}
|
|
339
|
+
/** Drop undefined keys so the wire body only carries what was requested. */
|
|
340
|
+
export function stackPolicyBody(policy) {
|
|
341
|
+
const body = {};
|
|
342
|
+
if (typeof policy?.autoEnqueueWhenReady === "boolean") {
|
|
343
|
+
body.autoEnqueueWhenReady = policy.autoEnqueueWhenReady;
|
|
344
|
+
}
|
|
345
|
+
if (policy?.autoReviewOverride !== undefined) {
|
|
346
|
+
body.autoReviewOverride = policy.autoReviewOverride;
|
|
347
|
+
}
|
|
348
|
+
if (policy?.autoPatchOverride !== undefined) {
|
|
349
|
+
body.autoPatchOverride = policy.autoPatchOverride;
|
|
350
|
+
}
|
|
351
|
+
return body;
|
|
352
|
+
}
|
|
346
353
|
/** Adopt an open PR chain (Bearer POST /api/v1/stacks/adopt). */
|
|
347
|
-
export async function adoptStack(owner, repo, prNumber, cfg) {
|
|
354
|
+
export async function adoptStack(owner, repo, prNumber, cfg, policy) {
|
|
348
355
|
const resolved = cfg ?? (await loadConfig());
|
|
349
356
|
const { status, body } = await apiFetch(resolved, "/api/v1/stacks/adopt", {
|
|
350
357
|
method: "POST",
|
|
351
|
-
json: {
|
|
358
|
+
json: {
|
|
359
|
+
owner,
|
|
360
|
+
repo,
|
|
361
|
+
prNumber,
|
|
362
|
+
...stackPolicyBody(policy),
|
|
363
|
+
},
|
|
352
364
|
});
|
|
353
365
|
if (status === 404) {
|
|
354
366
|
throw new CommandError("Stacks API is not available on this server yet. Deploy the API update or use the dashboard.");
|
|
@@ -390,6 +402,21 @@ async function stackMutation(stackId, pathSuffix, method, json, failLabel, cfg)
|
|
|
390
402
|
}
|
|
391
403
|
return body;
|
|
392
404
|
}
|
|
405
|
+
/** Arm or disarm Auto land on one owned stack. */
|
|
406
|
+
export async function setStackAutoLand(stackId, enabled, cfg) {
|
|
407
|
+
return setStackPolicy(stackId, { autoEnqueueWhenReady: enabled }, cfg);
|
|
408
|
+
}
|
|
409
|
+
/**
|
|
410
|
+
* Patch any subset of one owned stack's automation policy
|
|
411
|
+
* (Bearer PATCH /api/v1/stacks/:id). `null` clears an override.
|
|
412
|
+
*/
|
|
413
|
+
export async function setStackPolicy(stackId, policy, cfg) {
|
|
414
|
+
const json = stackPolicyBody(policy);
|
|
415
|
+
if (Object.keys(json).length === 0) {
|
|
416
|
+
throw new CommandError("setStackPolicy needs at least one policy key");
|
|
417
|
+
}
|
|
418
|
+
return stackMutation(stackId, "", "PATCH", json, "Failed to set stack policy", cfg);
|
|
419
|
+
}
|
|
393
420
|
/** Restack descendants (Bearer POST /api/v1/stacks/:id/restack). */
|
|
394
421
|
export async function restackStack(stackId, cfg) {
|
|
395
422
|
return stackMutation(stackId, "/restack", "POST", {}, "Failed to restack", cfg);
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export declare const BEARER_SETTINGS: readonly [{
|
|
2
|
+
readonly key: "auto_review_enabled";
|
|
3
|
+
readonly flag: "--auto-review";
|
|
4
|
+
readonly label: "Auto review";
|
|
5
|
+
}, {
|
|
6
|
+
readonly key: "auto_patch_enabled";
|
|
7
|
+
readonly flag: "--auto-patch";
|
|
8
|
+
readonly label: "Auto patch";
|
|
9
|
+
}, {
|
|
10
|
+
readonly key: "vortex_show_thinking_traces";
|
|
11
|
+
readonly flag: "--vortex-thinking";
|
|
12
|
+
readonly label: "Vortex thinking traces";
|
|
13
|
+
}, {
|
|
14
|
+
readonly key: "repo_overview_enabled";
|
|
15
|
+
readonly flag: "--repo-overview";
|
|
16
|
+
readonly label: "Repo overview";
|
|
17
|
+
}, {
|
|
18
|
+
readonly key: "review_unit_land_prs_enabled";
|
|
19
|
+
readonly flag: "--review-unit-land";
|
|
20
|
+
readonly label: "Review Unit land PRs";
|
|
21
|
+
}, {
|
|
22
|
+
readonly key: "cyclone_review_unit_land_prs_enabled";
|
|
23
|
+
readonly flag: "--cyclone-review-unit-land";
|
|
24
|
+
readonly label: "Cyclone Review Unit land PRs";
|
|
25
|
+
}, {
|
|
26
|
+
readonly key: "vortex_seam_specialist_enabled";
|
|
27
|
+
readonly flag: "--vortex-seam";
|
|
28
|
+
readonly label: "Vortex seam specialist";
|
|
29
|
+
}, {
|
|
30
|
+
readonly key: "auto_land_default";
|
|
31
|
+
readonly flag: "--auto-land";
|
|
32
|
+
readonly label: "Auto land (new stacks)";
|
|
33
|
+
}];
|
|
34
|
+
export type BearerSettingsKey = (typeof BEARER_SETTINGS)[number]["key"];
|
|
35
|
+
export declare const BEARER_SETTINGS_KEYS: readonly BearerSettingsKey[];
|
|
36
|
+
export declare const BEARER_SETTINGS_FLAGS: Record<string, BearerSettingsKey>;
|
|
37
|
+
export declare const BEARER_SETTINGS_LABELS: Record<BearerSettingsKey, string>;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export const BEARER_SETTINGS = [
|
|
2
|
+
{ key: "auto_review_enabled", flag: "--auto-review", label: "Auto review" },
|
|
3
|
+
{ key: "auto_patch_enabled", flag: "--auto-patch", label: "Auto patch" },
|
|
4
|
+
{ key: "vortex_show_thinking_traces", flag: "--vortex-thinking", label: "Vortex thinking traces" },
|
|
5
|
+
{ key: "repo_overview_enabled", flag: "--repo-overview", label: "Repo overview" },
|
|
6
|
+
{ key: "review_unit_land_prs_enabled", flag: "--review-unit-land", label: "Review Unit land PRs" },
|
|
7
|
+
{ key: "cyclone_review_unit_land_prs_enabled", flag: "--cyclone-review-unit-land", label: "Cyclone Review Unit land PRs" },
|
|
8
|
+
{ key: "vortex_seam_specialist_enabled", flag: "--vortex-seam", label: "Vortex seam specialist" },
|
|
9
|
+
{ key: "auto_land_default", flag: "--auto-land", label: "Auto land (new stacks)" },
|
|
10
|
+
];
|
|
11
|
+
export const BEARER_SETTINGS_KEYS = BEARER_SETTINGS.map((r) => r.key);
|
|
12
|
+
export const BEARER_SETTINGS_FLAGS = Object.fromEntries(BEARER_SETTINGS.map((r) => [r.flag, r.key]));
|
|
13
|
+
export const BEARER_SETTINGS_LABELS = Object.fromEntries(BEARER_SETTINGS.map((r) => [r.key, r.label]));
|
package/dist/client/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export { DEFAULT_API, apiBase, clearConfig, configPath, keyDisplay, loadConfig, mergestormHome, resolveApiKey, saveConfig, type Config, } from "../config.js";
|
|
2
|
-
export {
|
|
2
|
+
export { BEARER_SETTINGS, BEARER_SETTINGS_FLAGS, BEARER_SETTINGS_KEYS, BEARER_SETTINGS_LABELS, type BearerSettingsKey, } from "../automation-catalog.js";
|
|
3
|
+
export { API_TIMEOUT_PREFIX, DEFAULT_API_TIMEOUT_MS, SETTINGS_WRITABLE_KEYS, adoptStack, apiFetch, cancelMergeQueueEntry, devicePost, enqueueStack, ensureUpperPark, getEnrichedStack, getMe, getPrVortexReview, getReview, getSettings, landNextStack, listJobs, listMergeQueueEntries, listStacks, listThreads, parseRetryAfterSeconds, patchSettings, restackStack, setStackAutoLand, setStackPolicy, stackPolicyBody, type ApiFetchInit, type ApiFetchResult, type JobListItem, type MeResponse, type MergeQueueBounceDetail, type MergeQueueBounceKind, type MergeQueueEnqueuedBy, type MergeQueueEnqueuedVia, type MergeQueueEntryDto, type MergeQueueEntryState, type PrReviewFinding, type PrReviewFindings, type PrVortexReview, type SettingsPatch, type SettingsResponse, type SettingsWritableKey, type StackDto, type StackPolicyPatch, type ThreadDetail, type ThreadListItem, } from "../api.js";
|
|
3
4
|
export { CommandError, DetachedError, RATE_LIMITED_HINT, REVIEW_EXIT, isCommandErrorCode, rateLimitedMessage, type CommandErrorCode, type CommandErrorExtras, } from "../errors.js";
|
|
4
5
|
export { REVIEW_POLL_BACKOFF_MS, REVIEW_POLL_DEFAULT_TIMEOUT_MS, REVIEW_POLL_INTERVAL_MS, REVIEW_POLL_MAX_INTERVAL_MS, REVIEW_POLL_MAX_TRANSIENT_RETRIES, REVIEW_POLL_STRETCH_AFTER_MS, REVIEW_POLL_STRETCH_SPAN_MS, REVIEW_THREAD_SLUG_RE, PrReviewPollTimeoutError, ReviewPollTimeoutError, collectReviewInput, isTransientReviewPollError, isTransientReviewPollStatus, loadReviewContext, pollPrVortexReview, pollReview, stretchedPollIntervalMs, submitReview, transientRetryWaitMs, type PollReviewOptions, type PollPrVortexReviewOptions, type ReviewInput, type SubmitReviewOptions, } from "../commands/review-client.js";
|
|
5
6
|
export { REVIEW_JOB_ENVELOPE_SCHEMA, toReviewJobEnvelope, type ReviewEnvelopeFallbacks, type ReviewJobEnvelope, type ReviewJobRow, } from "../ui/job-envelope.js";
|
package/dist/client/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export { DEFAULT_API, apiBase, clearConfig, configPath, keyDisplay, loadConfig, mergestormHome, resolveApiKey, saveConfig, } from "../config.js";
|
|
2
|
-
export {
|
|
2
|
+
export { BEARER_SETTINGS, BEARER_SETTINGS_FLAGS, BEARER_SETTINGS_KEYS, BEARER_SETTINGS_LABELS, } from "../automation-catalog.js";
|
|
3
|
+
export { API_TIMEOUT_PREFIX, DEFAULT_API_TIMEOUT_MS, SETTINGS_WRITABLE_KEYS, adoptStack, apiFetch, cancelMergeQueueEntry, devicePost, enqueueStack, ensureUpperPark, getEnrichedStack, getMe, getPrVortexReview, getReview, getSettings, landNextStack, listJobs, listMergeQueueEntries, listStacks, listThreads, parseRetryAfterSeconds, patchSettings, restackStack, setStackAutoLand, setStackPolicy, stackPolicyBody, } from "../api.js";
|
|
3
4
|
export { CommandError, DetachedError, RATE_LIMITED_HINT, REVIEW_EXIT, isCommandErrorCode, rateLimitedMessage, } from "../errors.js";
|
|
4
5
|
export { REVIEW_POLL_BACKOFF_MS, REVIEW_POLL_DEFAULT_TIMEOUT_MS, REVIEW_POLL_INTERVAL_MS, REVIEW_POLL_MAX_INTERVAL_MS, REVIEW_POLL_MAX_TRANSIENT_RETRIES, REVIEW_POLL_STRETCH_AFTER_MS, REVIEW_POLL_STRETCH_SPAN_MS, REVIEW_THREAD_SLUG_RE, PrReviewPollTimeoutError, ReviewPollTimeoutError, collectReviewInput, isTransientReviewPollError, isTransientReviewPollStatus, loadReviewContext, pollPrVortexReview, pollReview, stretchedPollIntervalMs, submitReview, transientRetryWaitMs, } from "../commands/review-client.js";
|
|
5
6
|
export { REVIEW_JOB_ENVELOPE_SCHEMA, toReviewJobEnvelope, } from "../ui/job-envelope.js";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type JobListItem, type SettingsResponse
|
|
1
|
+
import { type JobListItem, type SettingsResponse } from "../api.js";
|
|
2
2
|
import { type ReviewJobRow } from "../ui/job-envelope.js";
|
|
3
3
|
import { type TabId, type TabsConfigRow } from "../ui/tabs.js";
|
|
4
4
|
import { type UsagePanelJob } from "../ui/usage.js";
|
|
@@ -27,7 +27,7 @@ export declare function buildStatusTabLines(input: StatusTabInput): string[];
|
|
|
27
27
|
/** In-place job detail for the Jobs tab (same envelope as `status <id>`). */
|
|
28
28
|
export declare function formatJobDetailLines(row: ReviewJobRow): string[];
|
|
29
29
|
/** Human labels for the Config tab and the static `settings` printout. */
|
|
30
|
-
export declare const SETTINGS_LABELS: Record<
|
|
30
|
+
export declare const SETTINGS_LABELS: Record<"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", string>;
|
|
31
31
|
/** Config rows in a stable order: writable toggles, then connected flags. */
|
|
32
32
|
export declare function buildConfigRows(settings: SettingsResponse): TabsConfigRow[];
|
|
33
33
|
/**
|
package/dist/commands/browse.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { getMe, getReview, getSettings, listJobs, patchSettings, SETTINGS_WRITABLE_KEYS, } from "../api.js";
|
|
2
|
+
import { BEARER_SETTINGS_LABELS } from "../automation-catalog.js";
|
|
2
3
|
import { apiBase, keyDisplay, loadConfig, resolveApiKey } from "../config.js";
|
|
3
4
|
import { CommandError } from "../errors.js";
|
|
4
5
|
import { ansi } from "../ui/ansi.js";
|
|
@@ -117,15 +118,7 @@ export function formatJobDetailLines(row) {
|
|
|
117
118
|
return lines;
|
|
118
119
|
}
|
|
119
120
|
/** Human labels for the Config tab and the static `settings` printout. */
|
|
120
|
-
export const SETTINGS_LABELS =
|
|
121
|
-
auto_review_enabled: "Auto review",
|
|
122
|
-
auto_patch_enabled: "Auto patch",
|
|
123
|
-
vortex_show_thinking_traces: "Vortex thinking traces",
|
|
124
|
-
repo_overview_enabled: "Repo overview",
|
|
125
|
-
review_unit_land_prs_enabled: "Review Unit land PRs",
|
|
126
|
-
cyclone_review_unit_land_prs_enabled: "Cyclone Review Unit land PRs",
|
|
127
|
-
vortex_seam_specialist_enabled: "Vortex seam specialist",
|
|
128
|
-
};
|
|
121
|
+
export const SETTINGS_LABELS = BEARER_SETTINGS_LABELS;
|
|
129
122
|
/** Config rows in a stable order: writable toggles, then connected flags. */
|
|
130
123
|
export function buildConfigRows(settings) {
|
|
131
124
|
return [
|
|
@@ -164,6 +164,7 @@ export const COMMAND_REGISTRY = [
|
|
|
164
164
|
" [--auto-review on|off] [--auto-patch on|off] [--vortex-thinking on|off]",
|
|
165
165
|
" [--repo-overview on|off] [--review-unit-land on|off]",
|
|
166
166
|
" [--cyclone-review-unit-land on|off] [--vortex-seam on|off]",
|
|
167
|
+
" [--auto-land on|off] Auto land default for stacks opened after this",
|
|
167
168
|
" With flags: PATCH those settings and print the stored result",
|
|
168
169
|
],
|
|
169
170
|
async run(args, ctx) {
|
|
@@ -210,11 +211,12 @@ export const COMMAND_REGISTRY = [
|
|
|
210
211
|
name: "stack",
|
|
211
212
|
summary: "Stacks: /stack how-to, list yours, create → submit",
|
|
212
213
|
usage: [
|
|
213
|
-
"mergestorm stack create [name] New local
|
|
214
|
-
"mergestorm stack submit Push active
|
|
214
|
+
"mergestorm stack create [name] New local layer ([--onto] [--trunk] [--extend] [--auto-land on|off] [--auto-review on|off] [--auto-patch on|off])",
|
|
215
|
+
"mergestorm stack submit Push/open active stack ([--extend] [--auto-land on|off] [--auto-review on|off] [--auto-patch on|off])",
|
|
215
216
|
"mergestorm stack reset --force Clear this repo's pre-submit authoring state",
|
|
216
217
|
"mergestorm stack list [--json] List registered stacks",
|
|
217
|
-
"mergestorm stack
|
|
218
|
+
"mergestorm stack set <stack-id> [--auto-land on|off] [--auto-review on|off|default] [--auto-patch on|off|default] [--json] Per-stack policy",
|
|
219
|
+
"mergestorm stack adopt <owner/repo>#<pr> [--auto-land on|off] [--auto-review on|off] [--auto-patch on|off] Import an open PR chain",
|
|
218
220
|
"mergestorm stack restack <stack-id> Restack stack descendants",
|
|
219
221
|
"mergestorm stack land <stack-id> Land bottom PR (or promote into unit)",
|
|
220
222
|
],
|
|
@@ -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>;
|
package/dist/commands/stack.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { adoptStack, ensureUpperPark, landNextStack, listStacks, restackStack, } from "../api.js";
|
|
1
|
+
import { adoptStack, ensureUpperPark, landNextStack, listStacks, restackStack, setStackPolicy, } from "../api.js";
|
|
2
2
|
import { loadConfig } from "../config.js";
|
|
3
3
|
import { CommandError } from "../errors.js";
|
|
4
4
|
import { createPr, findOpenPrNumber, requireGh } from "../gh.js";
|
|
@@ -6,7 +6,7 @@ import { parseGithubOriginRepo } from "../git.js";
|
|
|
6
6
|
import { branchExists, commitsAheadOf, createBranchFromHead, currentBranch, defaultLayerBranchName, deleteBranch, discoverTrunk, fetchRemoteBranch, pushBranch, tipCommitMessage, tipCommitSubject, worktreeDirty, } from "../git-stack.js";
|
|
7
7
|
import { buildPrBodyFromCommit } from "../pr-body.js";
|
|
8
8
|
import { gitTopLevel } from "../repo-identity.js";
|
|
9
|
-
import { activeLayers, appendLayer, emptyStackMeta, loadStackMeta, removeActiveStack, resetStackMeta, saveStackMeta, selectStackForCreate, } from "../stack-meta.js";
|
|
9
|
+
import { activeLayers, appendLayer, emptyStackMeta, loadStackMeta, removeActiveStack, resetStackMeta, saveStackMeta, selectStackForCreate, setActivePolicy, } from "../stack-meta.js";
|
|
10
10
|
import { isMgParkBranch, planSubmitLayerBases } from "../submit-pr-base.js";
|
|
11
11
|
import { ansi } from "../ui/ansi.js";
|
|
12
12
|
import { runLineTabsBrowser } from "../ui/line-tabs.js";
|
|
@@ -14,14 +14,18 @@ import { present } from "../ui/present.js";
|
|
|
14
14
|
import { canBrowse } from "./browse.js";
|
|
15
15
|
import { openHelpBrowser } from "./help.js";
|
|
16
16
|
const STACK_USAGE = `usage:
|
|
17
|
-
mergestorm stack create [name] [--onto <branch>] [--trunk <branch>] [--extend] [--json]
|
|
18
|
-
mergestorm stack submit [--extend] [--json]
|
|
17
|
+
mergestorm stack create [name] [--onto <branch>] [--trunk <branch>] [--extend] [--auto-land on|off] [--auto-review on|off] [--auto-patch on|off] [--json]
|
|
18
|
+
mergestorm stack submit [--extend] [--auto-land on|off] [--auto-review on|off] [--auto-patch on|off] [--json]
|
|
19
19
|
mergestorm stack reset --force
|
|
20
20
|
mergestorm stack list [--json]
|
|
21
|
-
mergestorm stack
|
|
21
|
+
mergestorm stack set <stack-id> [--auto-land on|off] [--auto-review on|off|default] [--auto-patch on|off|default] [--json]
|
|
22
|
+
mergestorm stack adopt <owner/repo>#<pr> [--auto-land on|off] [--auto-review on|off] [--auto-patch on|off] [--json]
|
|
22
23
|
mergestorm stack restack <stack-id> [--json]
|
|
23
24
|
mergestorm stack land <stack-id> [--json]
|
|
24
25
|
|
|
26
|
+
--auto-review / --auto-patch pin Vortex auto-review / Cyclone auto-patch for
|
|
27
|
+
one stack in either direction. Absent, or \`default\` on stack set, follows
|
|
28
|
+
the account setting.
|
|
25
29
|
stack submit opens PRs with a body generated from each layer tip commit.
|
|
26
30
|
Layers 1–2 use the git parent as GitHub base. Layer 3+ opens onto the
|
|
27
31
|
review-unit park freeze (mg-park-*) so adopt does not retarget after open.
|
|
@@ -126,9 +130,25 @@ export function buildStackListLines(stacks) {
|
|
|
126
130
|
lines.push(ansi.dim(" create · submit · land <id> · q to close"));
|
|
127
131
|
return lines;
|
|
128
132
|
}
|
|
133
|
+
/** Short per-stack policy suffixes: Auto land when on, overrides when pinned. */
|
|
134
|
+
export function stackPolicyLabels(s) {
|
|
135
|
+
const labels = [];
|
|
136
|
+
if (s.autoEnqueueWhenReady)
|
|
137
|
+
labels.push("auto-land on");
|
|
138
|
+
if (typeof s.autoReviewOverride === "boolean") {
|
|
139
|
+
labels.push(`auto-review ${s.autoReviewOverride ? "on" : "off"}`);
|
|
140
|
+
}
|
|
141
|
+
if (typeof s.autoPatchOverride === "boolean") {
|
|
142
|
+
labels.push(`auto-patch ${s.autoPatchOverride ? "on" : "off"}`);
|
|
143
|
+
}
|
|
144
|
+
return labels;
|
|
145
|
+
}
|
|
129
146
|
function formatStackHuman(s) {
|
|
130
147
|
const lines = [];
|
|
131
|
-
lines.push(` ${ansi.bold(`${s.owner}/${s.repo}`)} trunk=${s.trunkBranch}`
|
|
148
|
+
lines.push(` ${ansi.bold(`${s.owner}/${s.repo}`)} trunk=${s.trunkBranch}` +
|
|
149
|
+
stackPolicyLabels(s)
|
|
150
|
+
.map((label) => ` ${label}`)
|
|
151
|
+
.join(""));
|
|
132
152
|
lines.push(` ${ansi.dim(s.id)}`);
|
|
133
153
|
if (s.layers.length === 0) {
|
|
134
154
|
lines.push(ansi.dim(" (no layers)"));
|
|
@@ -141,12 +161,45 @@ function formatStackHuman(s) {
|
|
|
141
161
|
}
|
|
142
162
|
return lines;
|
|
143
163
|
}
|
|
164
|
+
const OPEN_POLICY_FLAGS = [
|
|
165
|
+
{ flag: "--auto-land", key: "autoLand" },
|
|
166
|
+
{ flag: "--auto-review", key: "autoReview" },
|
|
167
|
+
{ flag: "--auto-patch", key: "autoPatch" },
|
|
168
|
+
];
|
|
169
|
+
/**
|
|
170
|
+
* Consume one `--auto-* on|off` flag at `args[i]` (space or `=` form). Returns
|
|
171
|
+
* the next index to scan, or null when `args[i]` is not a policy flag.
|
|
172
|
+
*/
|
|
173
|
+
function takeOpenPolicyFlag(args, i, into, verb) {
|
|
174
|
+
const a = args[i];
|
|
175
|
+
for (const { flag, key } of OPEN_POLICY_FLAGS) {
|
|
176
|
+
if (a === flag) {
|
|
177
|
+
into[key] = parseOnOff(args[i + 1], flag, verb);
|
|
178
|
+
return i + 2;
|
|
179
|
+
}
|
|
180
|
+
if (a.startsWith(`${flag}=`)) {
|
|
181
|
+
into[key] = parseOnOff(a.slice(flag.length + 1), flag, verb);
|
|
182
|
+
return i + 1;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
return null;
|
|
186
|
+
}
|
|
187
|
+
/** Wire policy for adopt from the parsed `on|off` flags; absent keys stay off the body. */
|
|
188
|
+
export function openPolicyPatch(flags) {
|
|
189
|
+
const patch = {
|
|
190
|
+
...(typeof flags.autoLand === "boolean" ? { autoEnqueueWhenReady: flags.autoLand } : {}),
|
|
191
|
+
...(typeof flags.autoReview === "boolean" ? { autoReviewOverride: flags.autoReview } : {}),
|
|
192
|
+
...(typeof flags.autoPatch === "boolean" ? { autoPatchOverride: flags.autoPatch } : {}),
|
|
193
|
+
};
|
|
194
|
+
return Object.keys(patch).length > 0 ? patch : undefined;
|
|
195
|
+
}
|
|
144
196
|
/** Parse `stack create` argv into options. Exported for tests. */
|
|
145
197
|
export function parseStackCreateArgs(args) {
|
|
146
198
|
let name;
|
|
147
199
|
let onto;
|
|
148
200
|
let trunk;
|
|
149
201
|
let extend = false;
|
|
202
|
+
const policy = {};
|
|
150
203
|
let asJson = false;
|
|
151
204
|
for (let i = 0; i < args.length; i++) {
|
|
152
205
|
const a = args[i];
|
|
@@ -158,6 +211,11 @@ export function parseStackCreateArgs(args) {
|
|
|
158
211
|
extend = true;
|
|
159
212
|
continue;
|
|
160
213
|
}
|
|
214
|
+
const next = takeOpenPolicyFlag(args, i, policy, "stack create");
|
|
215
|
+
if (next !== null) {
|
|
216
|
+
i = next - 1;
|
|
217
|
+
continue;
|
|
218
|
+
}
|
|
161
219
|
if (a === "--onto") {
|
|
162
220
|
const v = args[++i];
|
|
163
221
|
if (!v) {
|
|
@@ -190,13 +248,24 @@ export function parseStackCreateArgs(args) {
|
|
|
190
248
|
}
|
|
191
249
|
name = a;
|
|
192
250
|
}
|
|
193
|
-
return {
|
|
251
|
+
return {
|
|
252
|
+
name,
|
|
253
|
+
onto,
|
|
254
|
+
trunk,
|
|
255
|
+
extend,
|
|
256
|
+
autoLand: policy.autoLand,
|
|
257
|
+
autoReview: policy.autoReview,
|
|
258
|
+
autoPatch: policy.autoPatch,
|
|
259
|
+
asJson,
|
|
260
|
+
};
|
|
194
261
|
}
|
|
195
262
|
/** Parse `stack submit` argv. Exported for tests. */
|
|
196
263
|
export function parseStackSubmitArgs(args) {
|
|
197
264
|
let extend = false;
|
|
265
|
+
const policy = {};
|
|
198
266
|
let asJson = false;
|
|
199
|
-
for (
|
|
267
|
+
for (let i = 0; i < args.length; i++) {
|
|
268
|
+
const a = args[i];
|
|
200
269
|
if (a === "--json") {
|
|
201
270
|
asJson = true;
|
|
202
271
|
continue;
|
|
@@ -205,43 +274,159 @@ export function parseStackSubmitArgs(args) {
|
|
|
205
274
|
extend = true;
|
|
206
275
|
continue;
|
|
207
276
|
}
|
|
208
|
-
|
|
277
|
+
const next = takeOpenPolicyFlag(args, i, policy, "stack submit");
|
|
278
|
+
if (next !== null) {
|
|
279
|
+
i = next - 1;
|
|
280
|
+
continue;
|
|
281
|
+
}
|
|
282
|
+
throw new CommandError("usage: mergestorm stack submit [--extend] [--auto-land on|off] [--auto-review on|off] [--auto-patch on|off] [--json]");
|
|
283
|
+
}
|
|
284
|
+
return {
|
|
285
|
+
extend,
|
|
286
|
+
autoLand: policy.autoLand,
|
|
287
|
+
autoReview: policy.autoReview,
|
|
288
|
+
autoPatch: policy.autoPatch,
|
|
289
|
+
asJson,
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
function parseOnOff(raw, flag, verb) {
|
|
293
|
+
if (raw === "on")
|
|
294
|
+
return true;
|
|
295
|
+
if (raw === "off")
|
|
296
|
+
return false;
|
|
297
|
+
throw new CommandError(`usage: mergestorm ${verb} [${flag} on|off]`);
|
|
298
|
+
}
|
|
299
|
+
/** `stack set` overrides are tri-state: `default` writes null (follow the account flag). */
|
|
300
|
+
function parseOnOffDefault(raw, flag, verb) {
|
|
301
|
+
if (raw === "on")
|
|
302
|
+
return true;
|
|
303
|
+
if (raw === "off")
|
|
304
|
+
return false;
|
|
305
|
+
if (raw === "default")
|
|
306
|
+
return null;
|
|
307
|
+
throw new CommandError(`usage: mergestorm ${verb} [${flag} on|off|default]`);
|
|
308
|
+
}
|
|
309
|
+
export function parseStackAdoptArgs(args) {
|
|
310
|
+
const targetArgs = [];
|
|
311
|
+
const policy = {};
|
|
312
|
+
let asJson = false;
|
|
313
|
+
for (let i = 0; i < args.length; i++) {
|
|
314
|
+
const a = args[i];
|
|
315
|
+
if (a === "--json") {
|
|
316
|
+
asJson = true;
|
|
317
|
+
continue;
|
|
318
|
+
}
|
|
319
|
+
const next = takeOpenPolicyFlag(args, i, policy, "stack adopt <owner/repo>#<pr>");
|
|
320
|
+
if (next !== null) {
|
|
321
|
+
i = next - 1;
|
|
322
|
+
continue;
|
|
323
|
+
}
|
|
324
|
+
if (a.startsWith("-")) {
|
|
325
|
+
throw new CommandError(`${STACK_USAGE}\nunknown flag: ${a}`);
|
|
326
|
+
}
|
|
327
|
+
targetArgs.push(a);
|
|
328
|
+
}
|
|
329
|
+
const target = parseAdoptTarget(targetArgs);
|
|
330
|
+
return {
|
|
331
|
+
...target,
|
|
332
|
+
autoLand: policy.autoLand,
|
|
333
|
+
autoReview: policy.autoReview,
|
|
334
|
+
autoPatch: policy.autoPatch,
|
|
335
|
+
asJson,
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
export function parseStackSetArgs(args) {
|
|
339
|
+
const usage = "usage: mergestorm stack set <stack-id> [--auto-land on|off] [--auto-review on|off|default] [--auto-patch on|off|default] [--json]";
|
|
340
|
+
const verb = "stack set <stack-id>";
|
|
341
|
+
let stackId;
|
|
342
|
+
let autoLand;
|
|
343
|
+
let autoReview;
|
|
344
|
+
let autoPatch;
|
|
345
|
+
let asJson = false;
|
|
346
|
+
for (let i = 0; i < args.length; i++) {
|
|
347
|
+
const arg = args[i];
|
|
348
|
+
if (arg === "--json") {
|
|
349
|
+
asJson = true;
|
|
350
|
+
}
|
|
351
|
+
else if (arg === "--auto-land") {
|
|
352
|
+
autoLand = parseOnOff(args[++i], "--auto-land", verb);
|
|
353
|
+
}
|
|
354
|
+
else if (arg.startsWith("--auto-land=")) {
|
|
355
|
+
autoLand = parseOnOff(arg.slice("--auto-land=".length), "--auto-land", verb);
|
|
356
|
+
}
|
|
357
|
+
else if (arg === "--auto-review") {
|
|
358
|
+
autoReview = parseOnOffDefault(args[++i], "--auto-review", verb);
|
|
359
|
+
}
|
|
360
|
+
else if (arg.startsWith("--auto-review=")) {
|
|
361
|
+
autoReview = parseOnOffDefault(arg.slice("--auto-review=".length), "--auto-review", verb);
|
|
362
|
+
}
|
|
363
|
+
else if (arg === "--auto-patch") {
|
|
364
|
+
autoPatch = parseOnOffDefault(args[++i], "--auto-patch", verb);
|
|
365
|
+
}
|
|
366
|
+
else if (arg.startsWith("--auto-patch=")) {
|
|
367
|
+
autoPatch = parseOnOffDefault(arg.slice("--auto-patch=".length), "--auto-patch", verb);
|
|
368
|
+
}
|
|
369
|
+
else if (arg.startsWith("-") || stackId !== undefined) {
|
|
370
|
+
throw new CommandError(usage);
|
|
371
|
+
}
|
|
372
|
+
else {
|
|
373
|
+
stackId = arg;
|
|
374
|
+
}
|
|
209
375
|
}
|
|
210
|
-
|
|
376
|
+
if (autoLand === undefined && autoReview === undefined && autoPatch === undefined) {
|
|
377
|
+
throw new CommandError(usage);
|
|
378
|
+
}
|
|
379
|
+
return {
|
|
380
|
+
stackId: requireStackId(stackId, usage),
|
|
381
|
+
...(autoLand !== undefined ? { autoLand } : {}),
|
|
382
|
+
...(autoReview !== undefined ? { autoReview } : {}),
|
|
383
|
+
...(autoPatch !== undefined ? { autoPatch } : {}),
|
|
384
|
+
asJson,
|
|
385
|
+
};
|
|
211
386
|
}
|
|
212
387
|
export function parseStackResetArgs(args) {
|
|
213
388
|
if (args.length !== 1 || args[0] !== "--force") {
|
|
214
389
|
throw new CommandError("usage: mergestorm stack reset --force\nClears only CLI-managed pre-submit state; branches, PRs, and registered stacks are unchanged.");
|
|
215
390
|
}
|
|
216
391
|
}
|
|
217
|
-
async function cmdStackCreate(args, deps = {}) {
|
|
218
|
-
const { name, onto, trunk: trunkFlag, extend, asJson } = parseStackCreateArgs(args);
|
|
392
|
+
export async function cmdStackCreate(args, deps = {}) {
|
|
393
|
+
const { name, onto, trunk: trunkFlag, extend, autoLand, autoReview, autoPatch, asJson, } = parseStackCreateArgs(args);
|
|
219
394
|
const cwd = deps.cwd ?? process.cwd();
|
|
395
|
+
const gitTopLevelFn = deps.gitTopLevel ?? gitTopLevel;
|
|
396
|
+
const worktreeDirtyFn = deps.worktreeDirty ?? worktreeDirty;
|
|
397
|
+
const currentBranchFn = deps.currentBranch ?? currentBranch;
|
|
398
|
+
const branchExistsFn = deps.branchExists ?? branchExists;
|
|
399
|
+
const createBranchFromHeadFn = deps.createBranchFromHead ?? createBranchFromHead;
|
|
400
|
+
const deleteBranchFn = deps.deleteBranch ?? deleteBranch;
|
|
401
|
+
const discoverTrunkFn = deps.discoverTrunk ?? discoverTrunk;
|
|
402
|
+
const defaultLayerBranchNameFn = deps.defaultLayerBranchName ?? defaultLayerBranchName;
|
|
403
|
+
const loadStackMetaFn = deps.loadStackMeta ?? loadStackMeta;
|
|
404
|
+
const saveStackMetaFn = deps.saveStackMeta ?? saveStackMeta;
|
|
220
405
|
const listStacksFn = deps.listStacks ?? listStacks;
|
|
221
406
|
const loadConfigFn = deps.loadConfig ?? loadConfig;
|
|
222
407
|
const parseOriginFn = deps.parseGithubOriginRepo ?? parseGithubOriginRepo;
|
|
223
408
|
try {
|
|
224
|
-
|
|
409
|
+
gitTopLevelFn(cwd);
|
|
225
410
|
}
|
|
226
411
|
catch {
|
|
227
412
|
throw new CommandError("Not in a git repository. Run `mg stack create` from inside the repository whose stack you want to author.");
|
|
228
413
|
}
|
|
229
|
-
if (
|
|
414
|
+
if (worktreeDirtyFn(cwd)) {
|
|
230
415
|
throw new CommandError("Working tree is dirty. Commit or stash changes before `stack create`.");
|
|
231
416
|
}
|
|
232
|
-
const parentBranch = onto?.trim() ||
|
|
417
|
+
const parentBranch = onto?.trim() || currentBranchFn(cwd);
|
|
233
418
|
if (!parentBranch) {
|
|
234
419
|
throw new CommandError("Detached HEAD. Check out a branch (or pass --onto <branch>) before `stack create`.");
|
|
235
420
|
}
|
|
236
|
-
if (!
|
|
421
|
+
if (!branchExistsFn(parentBranch, cwd)) {
|
|
237
422
|
throw new CommandError(`Parent branch not found: ${parentBranch}`);
|
|
238
423
|
}
|
|
239
|
-
const branch =
|
|
240
|
-
if (
|
|
424
|
+
const branch = defaultLayerBranchNameFn(name);
|
|
425
|
+
if (branchExistsFn(branch, cwd)) {
|
|
241
426
|
throw new CommandError(`Branch already exists: ${branch}`);
|
|
242
427
|
}
|
|
243
|
-
let meta = await
|
|
244
|
-
const trunk = trunkFlag?.trim() || meta?.trunk ||
|
|
428
|
+
let meta = await loadStackMetaFn(cwd);
|
|
429
|
+
const trunk = trunkFlag?.trim() || meta?.trunk || discoverTrunkFn(cwd);
|
|
245
430
|
if (!meta) {
|
|
246
431
|
meta = emptyStackMeta(trunk);
|
|
247
432
|
}
|
|
@@ -281,23 +466,28 @@ async function cmdStackCreate(args, deps = {}) {
|
|
|
281
466
|
}
|
|
282
467
|
try {
|
|
283
468
|
meta = selectStackForCreate(meta, parentBranch);
|
|
469
|
+
meta = setActivePolicy(meta, {
|
|
470
|
+
autoEnqueueWhenReady: autoLand,
|
|
471
|
+
autoReviewOverride: autoReview,
|
|
472
|
+
autoPatchOverride: autoPatch,
|
|
473
|
+
});
|
|
284
474
|
}
|
|
285
475
|
catch (err) {
|
|
286
476
|
throw new CommandError(err instanceof Error ? err.message : String(err));
|
|
287
477
|
}
|
|
288
478
|
try {
|
|
289
|
-
|
|
479
|
+
createBranchFromHeadFn(branch, cwd);
|
|
290
480
|
}
|
|
291
481
|
catch (err) {
|
|
292
482
|
throw new CommandError(err instanceof Error ? err.message : String(err));
|
|
293
483
|
}
|
|
294
484
|
try {
|
|
295
485
|
meta = appendLayer(meta, { branch, parentBranch });
|
|
296
|
-
await
|
|
486
|
+
await saveStackMetaFn(meta, cwd);
|
|
297
487
|
}
|
|
298
488
|
catch (err) {
|
|
299
489
|
try {
|
|
300
|
-
|
|
490
|
+
deleteBranchFn(branch, cwd);
|
|
301
491
|
}
|
|
302
492
|
catch { /* rollback best-effort */ }
|
|
303
493
|
throw new CommandError(err instanceof Error ? err.message : String(err));
|
|
@@ -328,7 +518,7 @@ async function cmdStackCreate(args, deps = {}) {
|
|
|
328
518
|
await present("Stack", created);
|
|
329
519
|
}
|
|
330
520
|
export async function cmdStackSubmit(args, deps = {}) {
|
|
331
|
-
const { extend, asJson } = parseStackSubmitArgs(args);
|
|
521
|
+
const { extend, autoLand, autoReview, autoPatch, asJson } = parseStackSubmitArgs(args);
|
|
332
522
|
const cwd = deps.cwd ?? process.cwd();
|
|
333
523
|
const gitTopLevelFn = deps.gitTopLevel ?? gitTopLevel;
|
|
334
524
|
const loadStackMetaFn = deps.loadStackMeta ?? loadStackMeta;
|
|
@@ -399,6 +589,21 @@ export async function cmdStackSubmit(args, deps = {}) {
|
|
|
399
589
|
owner,
|
|
400
590
|
repo,
|
|
401
591
|
});
|
|
592
|
+
// Explicit submit flags win; otherwise a fresh (unregistered) stack carries
|
|
593
|
+
// the choices remembered at `stack create`. Joining a registered stack
|
|
594
|
+
// never replays stored create-time flags onto it.
|
|
595
|
+
const stored = meta.stacks[meta.active];
|
|
596
|
+
const freshStack = plans.every((plan) => plan.stackId === null);
|
|
597
|
+
const pick = (flag, remembered) => typeof flag === "boolean"
|
|
598
|
+
? flag
|
|
599
|
+
: freshStack && typeof remembered === "boolean"
|
|
600
|
+
? remembered
|
|
601
|
+
: undefined;
|
|
602
|
+
const policy = openPolicyPatch({
|
|
603
|
+
autoLand: pick(autoLand, stored?.autoEnqueueWhenReady),
|
|
604
|
+
autoReview: pick(autoReview, stored?.autoReviewOverride),
|
|
605
|
+
autoPatch: pick(autoPatch, stored?.autoPatchOverride),
|
|
606
|
+
});
|
|
402
607
|
for (const layer of layers) {
|
|
403
608
|
if (!branchExistsFn(layer.branch, cwd)) {
|
|
404
609
|
throw new CommandError(`Stack layer branch missing locally: ${layer.branch}`);
|
|
@@ -475,7 +680,7 @@ export async function cmdStackSubmit(args, deps = {}) {
|
|
|
475
680
|
let stackId = plans.find((plan) => plan.stackId)?.stackId ?? null;
|
|
476
681
|
const registerPr = async (prNumber) => {
|
|
477
682
|
console.log(ansi.dim(` Registering stack via import (${owner}/${repo}#${prNumber}) …`));
|
|
478
|
-
const body = await adoptStackFn(owner, repo, prNumber, cfg);
|
|
683
|
+
const body = await adoptStackFn(owner, repo, prNumber, cfg, policy);
|
|
479
684
|
if (typeof body !== "object" || body === null) {
|
|
480
685
|
throw new CommandError("Unexpected response shape from adopt API");
|
|
481
686
|
}
|
|
@@ -576,15 +781,39 @@ async function cmdStackList(args) {
|
|
|
576
781
|
listed.push(ansi.dim(" Dashboard: https://mergestorm.ai/dashboard"));
|
|
577
782
|
await present("Stacks", listed);
|
|
578
783
|
}
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
const
|
|
582
|
-
|
|
583
|
-
|
|
784
|
+
/** Human summary lines for a `stack set` write, one per flag. */
|
|
785
|
+
export function stackSetSummary(parsed) {
|
|
786
|
+
const tri = (value) => value === null ? "default (account setting)" : value ? "on" : "off";
|
|
787
|
+
const lines = [];
|
|
788
|
+
if (parsed.autoLand !== undefined) {
|
|
789
|
+
lines.push(`Auto land ${parsed.autoLand ? "on" : "off"}`);
|
|
584
790
|
}
|
|
585
|
-
|
|
791
|
+
if (parsed.autoReview !== undefined) {
|
|
792
|
+
lines.push(`Auto-review ${tri(parsed.autoReview)}`);
|
|
793
|
+
}
|
|
794
|
+
if (parsed.autoPatch !== undefined) {
|
|
795
|
+
lines.push(`Auto-patch ${tri(parsed.autoPatch)}`);
|
|
796
|
+
}
|
|
797
|
+
return lines;
|
|
798
|
+
}
|
|
799
|
+
export async function cmdStackSet(args, deps = {}) {
|
|
800
|
+
const parsed = parseStackSetArgs(args);
|
|
801
|
+
const cfg = await (deps.loadConfig ?? loadConfig)();
|
|
802
|
+
const body = await (deps.setStackPolicy ?? setStackPolicy)(parsed.stackId, {
|
|
803
|
+
...(parsed.autoLand !== undefined ? { autoEnqueueWhenReady: parsed.autoLand } : {}),
|
|
804
|
+
...(parsed.autoReview !== undefined ? { autoReviewOverride: parsed.autoReview } : {}),
|
|
805
|
+
...(parsed.autoPatch !== undefined ? { autoPatchOverride: parsed.autoPatch } : {}),
|
|
806
|
+
}, cfg);
|
|
807
|
+
if (parsed.asJson) {
|
|
808
|
+
console.log(JSON.stringify(body, null, 2));
|
|
809
|
+
return;
|
|
810
|
+
}
|
|
811
|
+
await present("Stack", stackSetSummary(parsed).map((line) => ansi.brightGreen(` ${line} for ${parsed.stackId}`)));
|
|
812
|
+
}
|
|
813
|
+
async function cmdStackAdopt(args) {
|
|
814
|
+
const { owner, repo, prNumber, autoLand, autoReview, autoPatch, asJson } = parseStackAdoptArgs(args);
|
|
586
815
|
const cfg = await loadConfig();
|
|
587
|
-
const body = await adoptStack(owner, repo, prNumber, cfg);
|
|
816
|
+
const body = await adoptStack(owner, repo, prNumber, cfg, openPolicyPatch({ autoLand, autoReview, autoPatch }));
|
|
588
817
|
if (asJson) {
|
|
589
818
|
console.log(JSON.stringify(body, null, 2));
|
|
590
819
|
return;
|
|
@@ -670,6 +899,8 @@ export async function cmdStack(args) {
|
|
|
670
899
|
return cmdStackReset(rest);
|
|
671
900
|
if (sub === "list" || sub === "ls")
|
|
672
901
|
return cmdStackList(rest);
|
|
902
|
+
if (sub === "set")
|
|
903
|
+
return cmdStackSet(rest);
|
|
673
904
|
if (sub === "adopt")
|
|
674
905
|
return cmdStackAdopt(rest);
|
|
675
906
|
if (sub === "restack")
|
package/dist/repo-identity.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ export declare const LEGACY_STACK_META_REL: string;
|
|
|
2
2
|
export declare function gitTopLevel(cwd?: string): string;
|
|
3
3
|
export declare function gitCommonDir(cwd?: string): string;
|
|
4
4
|
/**
|
|
5
|
-
*
|
|
5
|
+
* Each worktree has its own authoring state.
|
|
6
6
|
* Independent clones remain isolated even when they point at the same remote.
|
|
7
7
|
*/
|
|
8
8
|
export declare function stableRepoId(cwd?: string): string;
|
package/dist/repo-identity.js
CHANGED
|
@@ -41,11 +41,22 @@ export function gitCommonDir(cwd = process.cwd()) {
|
|
|
41
41
|
}
|
|
42
42
|
}
|
|
43
43
|
/**
|
|
44
|
-
*
|
|
44
|
+
* Each worktree has its own authoring state.
|
|
45
45
|
* Independent clones remain isolated even when they point at the same remote.
|
|
46
46
|
*/
|
|
47
47
|
export function stableRepoId(cwd = process.cwd()) {
|
|
48
|
-
|
|
48
|
+
let raw;
|
|
49
|
+
try {
|
|
50
|
+
raw = git(["rev-parse", "--git-dir"], cwd).trim();
|
|
51
|
+
}
|
|
52
|
+
catch (err) {
|
|
53
|
+
rethrowIfNotARepo(err);
|
|
54
|
+
}
|
|
55
|
+
const absolute = path.isAbsolute(raw) ? raw : path.resolve(cwd, raw);
|
|
56
|
+
return createHash("sha256")
|
|
57
|
+
.update(realpathSync(absolute))
|
|
58
|
+
.digest("hex")
|
|
59
|
+
.slice(0, 20);
|
|
49
60
|
}
|
|
50
61
|
export function cliStacksRoot() {
|
|
51
62
|
return path.join(mergestormHome(), "stacks");
|
package/dist/stack-dto.d.ts
CHANGED
|
@@ -224,6 +224,16 @@ export type StackDto = {
|
|
|
224
224
|
* Missing on older payloads is off.
|
|
225
225
|
*/
|
|
226
226
|
autoEnqueueWhenReady?: boolean;
|
|
227
|
+
/**
|
|
228
|
+
* Per-stack Vortex auto-review override. `null` (or missing) follows the
|
|
229
|
+
* account `auto_review_enabled` flag; a boolean wins in both directions.
|
|
230
|
+
*/
|
|
231
|
+
autoReviewOverride?: boolean | null;
|
|
232
|
+
/**
|
|
233
|
+
* Per-stack Cyclone auto-patch override. `null` (or missing) follows the
|
|
234
|
+
* account `auto_patch_enabled` flag; a boolean wins in both directions.
|
|
235
|
+
*/
|
|
236
|
+
autoPatchOverride?: boolean | null;
|
|
227
237
|
layers: StackLayerDto[];
|
|
228
238
|
/** Present when a review-unit row exists for this stack. */
|
|
229
239
|
unit?: StackUnitDto;
|
package/dist/stack-meta.d.ts
CHANGED
|
@@ -5,6 +5,10 @@ export type StackLayerMeta = {
|
|
|
5
5
|
};
|
|
6
6
|
export type LocalStack = {
|
|
7
7
|
layers: StackLayerMeta[];
|
|
8
|
+
autoEnqueueWhenReady?: boolean;
|
|
9
|
+
/** Per-open override carried from `stack create` to `stack submit`; absent = account flag. */
|
|
10
|
+
autoReviewOverride?: boolean;
|
|
11
|
+
autoPatchOverride?: boolean;
|
|
8
12
|
};
|
|
9
13
|
export type StackMeta = {
|
|
10
14
|
version: 2;
|
|
@@ -20,6 +24,14 @@ export declare function loadStackMeta(cwd?: string, stacksRoot?: string): Promis
|
|
|
20
24
|
export declare function emptyStackMeta(trunk: string): StackMeta;
|
|
21
25
|
export declare function activeStack(meta: StackMeta): LocalStack;
|
|
22
26
|
export declare function activeLayers(meta: StackMeta): StackLayerMeta[];
|
|
27
|
+
export declare function setActiveAutoEnqueue(meta: StackMeta, enabled: boolean | undefined): StackMeta;
|
|
28
|
+
/** Per-open automation flags remembered on the active local stack until submit. */
|
|
29
|
+
export type LocalStackPolicy = {
|
|
30
|
+
autoEnqueueWhenReady?: boolean;
|
|
31
|
+
autoReviewOverride?: boolean;
|
|
32
|
+
autoPatchOverride?: boolean;
|
|
33
|
+
};
|
|
34
|
+
export declare function setActivePolicy(meta: StackMeta, policy: LocalStackPolicy): StackMeta;
|
|
23
35
|
export declare function saveStackMeta(meta: StackMeta, cwd?: string, stacksRoot?: string): Promise<void>;
|
|
24
36
|
/** Explicit recovery. Does not touch branches, PRs, or registered server stacks. */
|
|
25
37
|
export declare function resetStackMeta(cwd?: string, stacksRoot?: string): Promise<void>;
|
package/dist/stack-meta.js
CHANGED
|
@@ -8,6 +8,11 @@ import path from "node:path";
|
|
|
8
8
|
import { CommandError, isCommandErrorCode } from "./errors.js";
|
|
9
9
|
import { git } from "./git.js";
|
|
10
10
|
import { cliStacksRoot, gitTopLevel, LEGACY_STACK_META_REL, legacyStackMetaPath, repoStateDir, stackMetaPath as resolveStackMetaPath, } from "./repo-identity.js";
|
|
11
|
+
const LOCAL_STACK_FLAGS = [
|
|
12
|
+
"autoEnqueueWhenReady",
|
|
13
|
+
"autoReviewOverride",
|
|
14
|
+
"autoPatchOverride",
|
|
15
|
+
];
|
|
11
16
|
/** Exported only for legacy migration/tests. New state never uses this path. */
|
|
12
17
|
export const STACK_META_REL = LEGACY_STACK_META_REL;
|
|
13
18
|
export function stackMetaPath(cwd = process.cwd(), stacksRoot = cliStacksRoot()) {
|
|
@@ -35,10 +40,18 @@ function normalizeMeta(raw) {
|
|
|
35
40
|
if (!stack ||
|
|
36
41
|
typeof stack !== "object" ||
|
|
37
42
|
!Array.isArray(stack.layers) ||
|
|
38
|
-
!stack.layers.every(isLayer)
|
|
43
|
+
!stack.layers.every(isLayer) ||
|
|
44
|
+
LOCAL_STACK_FLAGS.some((flag) => stack[flag] !== undefined &&
|
|
45
|
+
typeof stack[flag] !== "boolean")) {
|
|
39
46
|
throw new Error("Stack authoring state has a malformed stack entry; run `mg stack reset --force` to clear it.");
|
|
40
47
|
}
|
|
41
|
-
|
|
48
|
+
const entry = { layers: stack.layers };
|
|
49
|
+
for (const flag of LOCAL_STACK_FLAGS) {
|
|
50
|
+
const value = stack[flag];
|
|
51
|
+
if (typeof value === "boolean")
|
|
52
|
+
entry[flag] = value;
|
|
53
|
+
}
|
|
54
|
+
stacks.push(entry);
|
|
42
55
|
}
|
|
43
56
|
const active = obj.active;
|
|
44
57
|
if (typeof active !== "number" ||
|
|
@@ -165,6 +178,22 @@ export function activeStack(meta) {
|
|
|
165
178
|
export function activeLayers(meta) {
|
|
166
179
|
return activeStack(meta).layers;
|
|
167
180
|
}
|
|
181
|
+
export function setActiveAutoEnqueue(meta, enabled) {
|
|
182
|
+
return setActiveStackFlag(meta, "autoEnqueueWhenReady", enabled);
|
|
183
|
+
}
|
|
184
|
+
export function setActivePolicy(meta, policy) {
|
|
185
|
+
let next = meta;
|
|
186
|
+
for (const flag of LOCAL_STACK_FLAGS) {
|
|
187
|
+
next = setActiveStackFlag(next, flag, policy[flag]);
|
|
188
|
+
}
|
|
189
|
+
return next;
|
|
190
|
+
}
|
|
191
|
+
function setActiveStackFlag(meta, flag, value) {
|
|
192
|
+
if (value === undefined)
|
|
193
|
+
return meta;
|
|
194
|
+
const stacks = meta.stacks.map((stack, index) => index === meta.active ? { ...stack, [flag]: value } : stack);
|
|
195
|
+
return { ...meta, stacks };
|
|
196
|
+
}
|
|
168
197
|
export async function saveStackMeta(meta, cwd = process.cwd(), stacksRoot = cliStacksRoot()) {
|
|
169
198
|
const file = stackMetaPath(cwd, stacksRoot);
|
|
170
199
|
const dir = path.dirname(file);
|
|
@@ -229,7 +258,7 @@ export function appendLayer(meta, layer) {
|
|
|
229
258
|
}
|
|
230
259
|
}
|
|
231
260
|
const stacks = meta.stacks.map((stack, index) => index === meta.active
|
|
232
|
-
? { layers: [...stack.layers, layer] }
|
|
261
|
+
? { ...stack, layers: [...stack.layers, layer] }
|
|
233
262
|
: stack);
|
|
234
263
|
return { ...meta, stacks };
|
|
235
264
|
}
|
package/dist/ui/banner.js
CHANGED
|
@@ -81,6 +81,9 @@ const WHATS_NEW = {
|
|
|
81
81
|
"mg pr --wait --after-sha, mg settings, mergestorm-pr-loop skill on npm",
|
|
82
82
|
"mg skill install copies review + pr-loop from the packed tarball",
|
|
83
83
|
],
|
|
84
|
+
"0.3.16": [
|
|
85
|
+
"PR loop: verify each finding, prefer a small diff, post mergestorm-loop: dismiss on skip",
|
|
86
|
+
],
|
|
84
87
|
};
|
|
85
88
|
function densityFromVariant(variant) {
|
|
86
89
|
if (variant === "mini" || variant === "nano")
|
package/package.json
CHANGED
|
@@ -18,23 +18,29 @@ mg skill install --claude --cursor
|
|
|
18
18
|
## Preconditions (in order)
|
|
19
19
|
|
|
20
20
|
1. There is an already-open GitHub PR on a Vortex-monitored repo. No PR, wrong skill.
|
|
21
|
-
2. Auto-patch (Cyclone) must be off so you are the only patcher. Do not take anyone's word for it: the settings API
|
|
21
|
+
2. Auto-patch (Cyclone) must be off for this PR so you are the only patcher. Do not take anyone's word for it: the settings API and the PR's stack are the source of truth, and step 1 of the loop verifies them before anything else runs. Cyclone is off for the PR when its adopted stack has `autoPatchOverride` `false`, or when that override is unset and the account `auto_patch_enabled` is `false`.
|
|
22
22
|
3. `whoami` works on the `mergestorm` MCP server (or `mg whoami --json` exits 0). Otherwise stop and ask for install/login.
|
|
23
23
|
|
|
24
24
|
## Loop (in order)
|
|
25
25
|
|
|
26
26
|
1. Call `settings_get` and read `auto_patch_enabled`.
|
|
27
27
|
- If `settings_get` errors or its result has no `auto_patch_enabled`: refuse the loop. Do not wait, patch, or push; tell the human that Cyclone auto-patch could not be verified.
|
|
28
|
-
- If it is `false`: continue. You are the only patcher.
|
|
29
|
-
- If it is `true
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
28
|
+
- If it is `false`: find the PR's stack with `stack_list`. If its `autoPatchOverride` is `true`, refuse unless the human asked you to turn Cyclone off for this PR; if they did, use the adopted-stack and `stack_set { auto_patch: false }` path below. Otherwise continue. You are the only patcher.
|
|
29
|
+
- If it is `true`: find the PR's stack with `stack_list` (the stack whose `layers` contain this PR's `prNumber` for this owner/repo) and read its `autoPatchOverride`.
|
|
30
|
+
- If that stack's `autoPatchOverride` is `false`: continue. Cyclone is pinned off for this stack.
|
|
31
|
+
- If the PR has no stack, or the override is not `false`, and the human asked you to turn Cyclone off for this PR: the PR must be an adopted stack layer first (if it is not, stop and tell the human to adopt it, for example `mg stack adopt <owner/repo>#<n> --auto-patch off`). Then call `stack_set` with `{ stack_id, auto_patch: false }` on that stack, call `stack_status` with that `stack_id`, and confirm `autoPatchOverride` now reads `false`. Leave the account `auto_patch_enabled` unchanged; other PRs keep their own policy.
|
|
32
|
+
- Otherwise: refuse the loop. Do not wait, patch, or push; tell the human Cyclone auto-patch is still on for this PR and that `stack_set` with `auto_patch: false` on its stack turns it off for this PR only.
|
|
33
|
+
- Never set `auto_patch_enabled` with `settings_set` from this skill, in either direction, and never set `auto_patch: true` on a stack.
|
|
34
|
+
2. Call `review_wait_pr` with the PR's owner, repo, pr_number, and `timeout_s` 45 (`review_get_pr` works for a first look at the resting state). A Vortex pass often takes several minutes. If status is `in_progress`, call `review_wait_pr` again with the same args. Do not pass a 300s timeout; hosts drop long MCP calls.
|
|
33
35
|
3. Confirm the envelope's `head_sha` is the commit you have checked out as the PR head (compare its leading 7+ hex characters with `git rev-parse HEAD`). If it is not, someone else pushed after this pass and its findings are not yours to patch: do not edit. Fetch and check out the PR head, then go to step 7 with `after_sha` set to that head.
|
|
34
|
-
4. Read `findings.inline` and `findings.offDiff` (or `off_diff`) sorted by severity.
|
|
36
|
+
4. Read `findings.inline` and `findings.offDiff` (or `off_diff`) sorted by severity. For each finding:
|
|
37
|
+
- Verify it against the current checkout. Do not treat the finding text as proven.
|
|
38
|
+
- Prefer the smallest correct patch. Do not refactor around a finding.
|
|
39
|
+
- Patch concrete bugs. A chat-only explanation is not a dismiss.
|
|
40
|
+
- If you skip a finding (not reproducible, policy fork, or needs a human), post a public GitHub PR comment before you stop. First line: `mergestorm-loop: dismiss`. Then one line per skipped finding (path, severity, why). Do not use the `cyclone-outcome:` prefix.
|
|
35
41
|
5. Patch on the PR head branch only.
|
|
36
42
|
6. Push that head branch with an ordinary push. If the push is rejected because the remote branch moved, another patcher already pushed for this pass. Do not rebase, do not force, do not retry the push: stop and tell the human that a second patcher is active on this PR.
|
|
37
|
-
7. Call `review_wait_pr` again with `after_sha` set to the SHA you just pushed (7+ hex). An envelope whose `head_sha` is an older commit is a previous pass, not yours; keep waiting.
|
|
43
|
+
7. Call `review_wait_pr` again with `after_sha` set to the SHA you just pushed (7+ hex) and `timeout_s` 45. An envelope whose `head_sha` is an older commit is a previous pass, not yours; keep waiting.
|
|
38
44
|
8. Repeat from step 3 until a stop rule fires.
|
|
39
45
|
|
|
40
46
|
## Stop rules
|
|
@@ -52,12 +58,14 @@ Map these to `mergestorm.pr_review/v1` envelope fields; do not invent statuses.
|
|
|
52
58
|
|
|
53
59
|
- submit a local review job, or call the job-id tools `review_wait` / `review_get` here. Local jobs are `mergestorm-review`'s world: they cost credits and do not fire Vortex on the PR.
|
|
54
60
|
- land, merge, enqueue, or touch stack authoring or the merge queue
|
|
55
|
-
-
|
|
61
|
+
- change the account `auto_patch_enabled` from this skill, for any reason; Cyclone is muted per stack with `stack_set`, never per account
|
|
62
|
+
- set `auto_patch: true` on any stack
|
|
56
63
|
- push any branch other than the PR head
|
|
57
64
|
- force-push, or rebase and retry a push that was rejected because the remote moved
|
|
58
65
|
- patch a pass whose `head_sha` is not the commit you have checked out
|
|
59
66
|
- wrap the CLI in a shell escape hatch
|
|
60
67
|
- claim a finding is fixed without a diff that touches its path
|
|
68
|
+
- claim a finding is dismissed without posting a `mergestorm-loop: dismiss` comment on the PR
|
|
61
69
|
|
|
62
70
|
## Fallback (no MCP)
|
|
63
71
|
|
|
@@ -67,7 +75,7 @@ Verify auto-patch with the CLI instead of `settings_get`:
|
|
|
67
75
|
mg settings --json
|
|
68
76
|
```
|
|
69
77
|
|
|
70
|
-
Read `auto_patch_enabled` from stdout. If the command errors or the result has no `auto_patch_enabled`, refuse the loop and tell the human that Cyclone auto-patch could not be verified. If it is `true`
|
|
78
|
+
Read `auto_patch_enabled` from stdout. If the command errors or the result has no `auto_patch_enabled`, refuse the loop and tell the human that Cyclone auto-patch could not be verified. Run `mg stack list --json` and find the stack whose layers contain this PR. If its `autoPatchOverride` is `false`, continue. If it is unset and the account flag is `false`, continue. If it is `true`, or it is unset while the account flag is `true`, refuse unless the human asked you to turn Cyclone off for this PR; only then run `mg stack set <stack-id> --auto-patch off` (adopt first with `mg stack adopt <owner/repo>#<n> --auto-patch off` when the PR has no stack) and re-read with `mg stack list --json`. Do not run `mg settings --auto-patch off`: that changes the account, not this PR.
|
|
71
79
|
|
|
72
80
|
```bash
|
|
73
81
|
mg pr <owner/repo>#<n> --json --wait --after-sha <sha> --timeout <s>
|