mergestorm-mcp 0.2.26 → 0.2.28
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 +16 -2
- package/dist/index.js +828 -223
- package/dist/lib/brand-icons.d.ts +16 -0
- package/dist/lib/brand-icons.js +23 -0
- package/dist/lib/hosted.d.ts +7 -0
- package/dist/lib/hosted.js +21 -0
- package/dist/lib/index.d.ts +1 -0
- package/dist/lib/index.js +5 -0
- package/dist/lib/pr-loop-instructions.d.ts +14 -0
- package/dist/lib/pr-loop-instructions.js +73 -0
- package/dist/lib/server.d.ts +24 -0
- package/dist/lib/server.js +644 -0
- package/dist/lib/tools/agent-request.d.ts +6 -0
- package/dist/lib/tools/agent-request.js +25 -0
- package/dist/lib/tools/chat-task.d.ts +18 -0
- package/dist/lib/tools/chat-task.js +28 -0
- package/dist/lib/tools/credits.d.ts +3 -0
- package/dist/lib/tools/credits.js +38 -0
- package/dist/lib/tools/cyclone-job.d.ts +28 -0
- package/dist/lib/tools/cyclone-job.js +71 -0
- package/dist/lib/tools/envelope.d.ts +5 -0
- package/dist/lib/tools/envelope.js +30 -0
- package/dist/lib/tools/issue-create.d.ts +47 -0
- package/dist/lib/tools/issue-create.js +53 -0
- package/dist/lib/tools/memory.d.ts +68 -0
- package/dist/lib/tools/memory.js +74 -0
- package/dist/lib/tools/queue-status.d.ts +3 -0
- package/dist/lib/tools/queue-status.js +79 -0
- package/dist/lib/tools/repo-settings.d.ts +6 -0
- package/dist/lib/tools/repo-settings.js +78 -0
- package/dist/lib/tools/review-dismiss.d.ts +16 -0
- package/dist/lib/tools/review-dismiss.js +68 -0
- package/dist/lib/tools/review-get-pr.d.ts +22 -0
- package/dist/lib/tools/review-get-pr.js +52 -0
- package/dist/lib/tools/review-get.d.ts +3 -0
- package/dist/lib/tools/review-get.js +27 -0
- package/dist/lib/tools/review-list.d.ts +3 -0
- package/dist/lib/tools/review-list.js +10 -0
- package/dist/lib/tools/review-start.d.ts +11 -0
- package/dist/lib/tools/review-start.js +22 -0
- package/dist/lib/tools/review-submit.d.ts +36 -0
- package/dist/lib/tools/review-submit.js +172 -0
- package/dist/lib/tools/review-wait-pr.d.ts +11 -0
- package/dist/lib/tools/review-wait-pr.js +66 -0
- package/dist/lib/tools/review-wait.d.ts +6 -0
- package/dist/lib/tools/review-wait.js +33 -0
- package/dist/lib/tools/settings-get.d.ts +8 -0
- package/dist/lib/tools/settings-get.js +33 -0
- package/dist/lib/tools/settings-set.d.ts +11 -0
- package/dist/lib/tools/settings-set.js +62 -0
- package/dist/lib/tools/stack-actions.d.ts +4 -0
- package/dist/lib/tools/stack-actions.js +22 -0
- package/dist/lib/tools/stack-adopt.d.ts +44 -0
- package/dist/lib/tools/stack-adopt.js +55 -0
- package/dist/lib/tools/stack-list.d.ts +3 -0
- package/dist/lib/tools/stack-list.js +30 -0
- package/dist/lib/tools/stack-publish.d.ts +78 -0
- package/dist/lib/tools/stack-publish.js +89 -0
- package/dist/lib/tools/stack-set.d.ts +16 -0
- package/dist/lib/tools/stack-set.js +69 -0
- package/dist/lib/tools/stack-status.d.ts +3 -0
- package/dist/lib/tools/stack-status.js +57 -0
- package/dist/lib/tools/stack-summary.d.ts +2 -0
- package/dist/lib/tools/stack-summary.js +29 -0
- package/dist/lib/tools/stack-wait.d.ts +44 -0
- package/dist/lib/tools/stack-wait.js +146 -0
- package/dist/lib/tools/surge-setup.d.ts +6 -0
- package/dist/lib/tools/surge-setup.js +4 -0
- package/dist/lib/tools/types.d.ts +5 -0
- package/dist/lib/tools/types.js +1 -0
- package/dist/lib/tools/whoami.d.ts +3 -0
- package/dist/lib/tools/whoami.js +27 -0
- package/package.json +13 -3
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { CommandError, getSettings, } from "mergestorm/client";
|
|
2
|
+
/**
|
|
3
|
+
* One line an agent can act on without opening the data: is Cyclone allowed
|
|
4
|
+
* to patch, and is it even connected.
|
|
5
|
+
*/
|
|
6
|
+
export function settingsSummary(settings) {
|
|
7
|
+
const autoPatch = settings.auto_patch_enabled ? "on" : "off";
|
|
8
|
+
const automations = [
|
|
9
|
+
settings.auto_resolve_conflicts_enabled ? "auto_resolve_conflicts on" : null,
|
|
10
|
+
settings.auto_fix_ci_enabled ? "auto_fix_ci on" : null,
|
|
11
|
+
].filter((label) => label !== null);
|
|
12
|
+
const autoPatchLabel = [`auto_patch ${autoPatch}`, ...automations].join(" · ");
|
|
13
|
+
const cyclone = settings.cyclone_connected
|
|
14
|
+
? "Cyclone connected"
|
|
15
|
+
: "Cyclone not connected";
|
|
16
|
+
if (typeof settings.stacks_ready !== "boolean")
|
|
17
|
+
return `${autoPatchLabel} · ${cyclone}`;
|
|
18
|
+
const stacks = settings.stacks_ready
|
|
19
|
+
? "stacks, merge queue and Auto land ready"
|
|
20
|
+
: "stacks, merge queue and Auto land not ready (install Mergestorm Surge)";
|
|
21
|
+
return `${autoPatchLabel} · ${cyclone} (auto patch) · ${stacks}`;
|
|
22
|
+
}
|
|
23
|
+
export async function settingsGet(cfg) {
|
|
24
|
+
const settings = await getSettings(cfg);
|
|
25
|
+
if (!settings) {
|
|
26
|
+
throw new CommandError("Settings are not available (offline or API too old). " +
|
|
27
|
+
"Try again or open https://mergestorm.ai/settings");
|
|
28
|
+
}
|
|
29
|
+
return {
|
|
30
|
+
summary: settingsSummary(settings),
|
|
31
|
+
data: { ...settings },
|
|
32
|
+
};
|
|
33
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { type Config, type SettingsPatch } from "mergestorm/client";
|
|
2
|
+
import type { ToolPayload } from "./types.js";
|
|
3
|
+
/** Read-only on `/api/v1/settings`; the API owns them, agents never write them. */
|
|
4
|
+
export declare const SETTINGS_READ_ONLY_KEYS: readonly ["cyclone_connected", "github_connected"];
|
|
5
|
+
/**
|
|
6
|
+
* Build a PATCH body from raw tool args. Only the Bearer-writable allowlist
|
|
7
|
+
* passes through; read-only keys and empty patches are usage errors so the
|
|
8
|
+
* failure is the caller's wording, never a silent no-op.
|
|
9
|
+
*/
|
|
10
|
+
export declare function settingsPatchFromArgs(args: Record<string, unknown>): SettingsPatch;
|
|
11
|
+
export declare function settingsSet(args: Record<string, unknown>, cfg?: Config): Promise<ToolPayload>;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { CommandError, BEARER_SETTINGS, SETTINGS_WRITABLE_KEYS, patchSettings, } from "mergestorm/client";
|
|
2
|
+
import { settingsSummary } from "./settings-get.js";
|
|
3
|
+
/** Read-only on `/api/v1/settings`; the API owns them, agents never write them. */
|
|
4
|
+
export const SETTINGS_READ_ONLY_KEYS = [
|
|
5
|
+
"cyclone_connected",
|
|
6
|
+
"github_connected",
|
|
7
|
+
];
|
|
8
|
+
/**
|
|
9
|
+
* Build a PATCH body from raw tool args. Only the Bearer-writable allowlist
|
|
10
|
+
* passes through; read-only keys and empty patches are usage errors so the
|
|
11
|
+
* failure is the caller's wording, never a silent no-op.
|
|
12
|
+
*/
|
|
13
|
+
export function settingsPatchFromArgs(args) {
|
|
14
|
+
for (const key of SETTINGS_READ_ONLY_KEYS) {
|
|
15
|
+
if (args[key] !== undefined) {
|
|
16
|
+
throw new CommandError(`${key} is read-only and cannot be set.`, 2, "usage");
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
const patch = {};
|
|
20
|
+
for (const key of SETTINGS_WRITABLE_KEYS) {
|
|
21
|
+
const value = args[key];
|
|
22
|
+
if (value === undefined)
|
|
23
|
+
continue;
|
|
24
|
+
const row = BEARER_SETTINGS.find((row) => row.key === key);
|
|
25
|
+
if ("kind" in row && (row.kind === "seconds" || row.kind === "count")) {
|
|
26
|
+
if (typeof value !== "number" ||
|
|
27
|
+
!Number.isInteger(value) ||
|
|
28
|
+
value < row.min ||
|
|
29
|
+
value > row.max) {
|
|
30
|
+
const unit = row.kind === "seconds" ? " of seconds" : "";
|
|
31
|
+
throw new CommandError(`${key} takes a whole number${unit} from ${row.min} through ${row.max}.`, 2, "usage");
|
|
32
|
+
}
|
|
33
|
+
Object.assign(patch, { [key]: value });
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
if ("kind" in row) {
|
|
37
|
+
const valid = row.kind === "logins" || row.kind === "paths"
|
|
38
|
+
? Array.isArray(value) && value.every((entry) => typeof entry === "string")
|
|
39
|
+
: typeof value === "string" && row.values.includes(value);
|
|
40
|
+
if (!valid)
|
|
41
|
+
throw new CommandError(`Invalid value for ${key}.`, 2, "usage");
|
|
42
|
+
Object.assign(patch, { [key]: value });
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
if (typeof value !== "boolean") {
|
|
46
|
+
throw new CommandError(`${key} takes a boolean.`, 2, "usage");
|
|
47
|
+
}
|
|
48
|
+
Object.assign(patch, { [key]: value });
|
|
49
|
+
}
|
|
50
|
+
if (Object.keys(patch).length === 0) {
|
|
51
|
+
throw new CommandError(`At least one settings key is required: ${SETTINGS_WRITABLE_KEYS.join(", ")}.`, 2, "usage");
|
|
52
|
+
}
|
|
53
|
+
return patch;
|
|
54
|
+
}
|
|
55
|
+
export async function settingsSet(args, cfg) {
|
|
56
|
+
const patch = settingsPatchFromArgs(args);
|
|
57
|
+
const settings = await patchSettings(patch, cfg);
|
|
58
|
+
return {
|
|
59
|
+
summary: settingsSummary(settings),
|
|
60
|
+
data: { ...settings },
|
|
61
|
+
};
|
|
62
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { type Config } from "mergestorm/client";
|
|
2
|
+
import type { ToolPayload } from "./types.js";
|
|
3
|
+
export declare function stackRestack(stackId: string, cfg?: Config): Promise<ToolPayload>;
|
|
4
|
+
export declare function stackEnqueue(stackId: string, cfg?: Config, chatId?: string | null): Promise<ToolPayload>;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { enqueueStack, loadConfig, restackStack, stackWatchObligation } from "mergestorm/client";
|
|
2
|
+
import { chatTaskNote, trackInChat } from "./chat-task.js";
|
|
3
|
+
import { withWatchText } from "./stack-wait.js";
|
|
4
|
+
export async function stackRestack(stackId, cfg) {
|
|
5
|
+
const result = await restackStack(stackId, cfg);
|
|
6
|
+
const watch = stackWatchObligation({ stackId, terminal: null, unread: true, freshCursor: true });
|
|
7
|
+
return {
|
|
8
|
+
summary: withWatchText(watch, `Restacked stack ${stackId}: its layer branches were rebased onto their parents and pushed`),
|
|
9
|
+
data: { stack_id: stackId, result, watch },
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
export async function stackEnqueue(stackId, cfg, chatId) {
|
|
13
|
+
const resolved = cfg ?? (await loadConfig());
|
|
14
|
+
const result = await enqueueStack(stackId, resolved);
|
|
15
|
+
const watch = stackWatchObligation({ stackId, terminal: null, unread: true, freshCursor: true });
|
|
16
|
+
const task = chatId ? await trackInChat(resolved, chatId, { kind: "stack", stackId }) : null;
|
|
17
|
+
const summary = withWatchText(watch, `Sent stack ${stackId} to the Mergestorm merge queue`);
|
|
18
|
+
return {
|
|
19
|
+
summary: task ? `${summary} ${chatTaskNote(task)}` : summary,
|
|
20
|
+
data: { stack_id: stackId, result, watch, ...(task ? { background_task: task } : {}) },
|
|
21
|
+
};
|
|
22
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { type Config } from "mergestorm/client";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import type { ToolPayload } from "./types.js";
|
|
4
|
+
export declare const stackAdoptSchema: {
|
|
5
|
+
owner: z.ZodString;
|
|
6
|
+
repo: z.ZodString;
|
|
7
|
+
pr_number: z.ZodNumber;
|
|
8
|
+
auto_land: z.ZodOptional<z.ZodBoolean>;
|
|
9
|
+
auto_review: z.ZodOptional<z.ZodNullable<z.ZodBoolean>>;
|
|
10
|
+
auto_patch: z.ZodOptional<z.ZodNullable<z.ZodBoolean>>;
|
|
11
|
+
auto_resolve_conflicts: z.ZodOptional<z.ZodNullable<z.ZodBoolean>>;
|
|
12
|
+
auto_fix_ci: z.ZodOptional<z.ZodNullable<z.ZodBoolean>>;
|
|
13
|
+
};
|
|
14
|
+
declare const inputSchema: z.ZodObject<{
|
|
15
|
+
owner: z.ZodString;
|
|
16
|
+
repo: z.ZodString;
|
|
17
|
+
pr_number: z.ZodNumber;
|
|
18
|
+
auto_land: z.ZodOptional<z.ZodBoolean>;
|
|
19
|
+
auto_review: z.ZodOptional<z.ZodNullable<z.ZodBoolean>>;
|
|
20
|
+
auto_patch: z.ZodOptional<z.ZodNullable<z.ZodBoolean>>;
|
|
21
|
+
auto_resolve_conflicts: z.ZodOptional<z.ZodNullable<z.ZodBoolean>>;
|
|
22
|
+
auto_fix_ci: z.ZodOptional<z.ZodNullable<z.ZodBoolean>>;
|
|
23
|
+
}, "strip", z.ZodTypeAny, {
|
|
24
|
+
owner: string;
|
|
25
|
+
repo: string;
|
|
26
|
+
pr_number: number;
|
|
27
|
+
auto_patch?: boolean | null | undefined;
|
|
28
|
+
auto_review?: boolean | null | undefined;
|
|
29
|
+
auto_land?: boolean | undefined;
|
|
30
|
+
auto_resolve_conflicts?: boolean | null | undefined;
|
|
31
|
+
auto_fix_ci?: boolean | null | undefined;
|
|
32
|
+
}, {
|
|
33
|
+
owner: string;
|
|
34
|
+
repo: string;
|
|
35
|
+
pr_number: number;
|
|
36
|
+
auto_patch?: boolean | null | undefined;
|
|
37
|
+
auto_review?: boolean | null | undefined;
|
|
38
|
+
auto_land?: boolean | undefined;
|
|
39
|
+
auto_resolve_conflicts?: boolean | null | undefined;
|
|
40
|
+
auto_fix_ci?: boolean | null | undefined;
|
|
41
|
+
}>;
|
|
42
|
+
export type StackAdoptInput = z.infer<typeof inputSchema>;
|
|
43
|
+
export declare function stackAdopt(input: StackAdoptInput, cfg?: Config): Promise<ToolPayload>;
|
|
44
|
+
export {};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { adoptStack, CommandError, stackWatchObligation } from "mergestorm/client";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { stackSetPatch } from "./stack-set.js";
|
|
4
|
+
import { withWatchText } from "./stack-wait.js";
|
|
5
|
+
export const stackAdoptSchema = {
|
|
6
|
+
owner: z.string().trim().min(1),
|
|
7
|
+
repo: z.string().trim().min(1),
|
|
8
|
+
pr_number: z.number().int().min(1),
|
|
9
|
+
auto_land: z.boolean().optional(),
|
|
10
|
+
auto_review: z.boolean().nullable().optional(),
|
|
11
|
+
auto_patch: z.boolean().nullable().optional(),
|
|
12
|
+
auto_resolve_conflicts: z.boolean().nullable().optional(),
|
|
13
|
+
auto_fix_ci: z.boolean().nullable().optional(),
|
|
14
|
+
};
|
|
15
|
+
const inputSchema = z.object(stackAdoptSchema);
|
|
16
|
+
export async function stackAdopt(input, cfg) {
|
|
17
|
+
const parsed = inputSchema.safeParse(input);
|
|
18
|
+
if (!parsed.success) {
|
|
19
|
+
const message = "stack_adopt requires owner, repo, a positive pr_number, and valid policy values";
|
|
20
|
+
return { summary: message, data: { error: { code: "invalid_input", message } }, isError: true };
|
|
21
|
+
}
|
|
22
|
+
const { owner, repo, pr_number } = parsed.data;
|
|
23
|
+
try {
|
|
24
|
+
const result = await adoptStack(owner, repo, pr_number, cfg, stackSetPatch(parsed.data));
|
|
25
|
+
const summary = `Adopted stack for ${owner}/${repo}#${pr_number}`;
|
|
26
|
+
const stackId = typeof result === "object" && result !== null
|
|
27
|
+
? result.stack?.id
|
|
28
|
+
: undefined;
|
|
29
|
+
if (typeof stackId !== "string" || !stackId) {
|
|
30
|
+
return { summary, data: { owner, repo, pr_number, result } };
|
|
31
|
+
}
|
|
32
|
+
const watch = stackWatchObligation({ stackId, terminal: null, unread: true, freshCursor: true });
|
|
33
|
+
return {
|
|
34
|
+
summary: withWatchText(watch, summary),
|
|
35
|
+
data: { owner, repo, pr_number, result, watch },
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
catch (err) {
|
|
39
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
40
|
+
return {
|
|
41
|
+
summary: message,
|
|
42
|
+
data: {
|
|
43
|
+
error: {
|
|
44
|
+
code: err instanceof CommandError ? err.code ?? "stack_adopt_failed" : "stack_adopt_failed",
|
|
45
|
+
message,
|
|
46
|
+
...(err instanceof CommandError ? { exit_code: err.exitCode } : {}),
|
|
47
|
+
...(err instanceof CommandError && err.reason !== undefined ? { reason: err.reason } : {}),
|
|
48
|
+
...(err instanceof CommandError && err.retryAfterSeconds !== undefined
|
|
49
|
+
? { retry_after_seconds: err.retryAfterSeconds } : {}),
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
isError: true,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { isCommandErrorCode, listMergeQueueEntries, listStacks, } from "mergestorm/client";
|
|
2
|
+
import { stackSummary } from "./stack-summary.js";
|
|
3
|
+
export async function stackList(cfg) {
|
|
4
|
+
try {
|
|
5
|
+
const stacks = await listStacks(cfg);
|
|
6
|
+
const entries = await listMergeQueueEntries(cfg);
|
|
7
|
+
return {
|
|
8
|
+
summary: stacks.length === 0
|
|
9
|
+
? "No stacks"
|
|
10
|
+
: stacks.map((stack) => stackSummary(stack, entries)).join("\n"),
|
|
11
|
+
data: { stacks },
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
catch (err) {
|
|
15
|
+
if (isCommandErrorCode(err, "rate_limited")) {
|
|
16
|
+
return {
|
|
17
|
+
summary: err.message,
|
|
18
|
+
data: {
|
|
19
|
+
error: {
|
|
20
|
+
code: "rate_limited",
|
|
21
|
+
message: err.message,
|
|
22
|
+
retry_after_seconds: err.retryAfterSeconds,
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
isError: true,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
throw err;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { publishWorkingBranch, workingBranchPublication, type Config } from "mergestorm/client";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import type { ToolPayload } from "./types.js";
|
|
4
|
+
export declare const stackPublishSchema: {
|
|
5
|
+
base: z.ZodOptional<z.ZodString>;
|
|
6
|
+
extend: z.ZodOptional<z.ZodBoolean>;
|
|
7
|
+
title: z.ZodOptional<z.ZodString>;
|
|
8
|
+
body: z.ZodOptional<z.ZodString>;
|
|
9
|
+
replace_head: z.ZodOptional<z.ZodString>;
|
|
10
|
+
auto_land: z.ZodOptional<z.ZodBoolean>;
|
|
11
|
+
cwd: z.ZodOptional<z.ZodString>;
|
|
12
|
+
owner: z.ZodOptional<z.ZodString>;
|
|
13
|
+
repo: z.ZodOptional<z.ZodString>;
|
|
14
|
+
branch: z.ZodOptional<z.ZodString>;
|
|
15
|
+
};
|
|
16
|
+
export declare const stackPublishStatusSchema: {
|
|
17
|
+
cwd: z.ZodOptional<z.ZodString>;
|
|
18
|
+
owner: z.ZodOptional<z.ZodString>;
|
|
19
|
+
repo: z.ZodOptional<z.ZodString>;
|
|
20
|
+
branch: z.ZodOptional<z.ZodString>;
|
|
21
|
+
};
|
|
22
|
+
declare const publishInput: z.ZodObject<{
|
|
23
|
+
base: z.ZodOptional<z.ZodString>;
|
|
24
|
+
extend: z.ZodOptional<z.ZodBoolean>;
|
|
25
|
+
title: z.ZodOptional<z.ZodString>;
|
|
26
|
+
body: z.ZodOptional<z.ZodString>;
|
|
27
|
+
replace_head: z.ZodOptional<z.ZodString>;
|
|
28
|
+
auto_land: z.ZodOptional<z.ZodBoolean>;
|
|
29
|
+
cwd: z.ZodOptional<z.ZodString>;
|
|
30
|
+
owner: z.ZodOptional<z.ZodString>;
|
|
31
|
+
repo: z.ZodOptional<z.ZodString>;
|
|
32
|
+
branch: z.ZodOptional<z.ZodString>;
|
|
33
|
+
}, "strip", z.ZodTypeAny, {
|
|
34
|
+
title?: string | undefined;
|
|
35
|
+
owner?: string | undefined;
|
|
36
|
+
body?: string | undefined;
|
|
37
|
+
repo?: string | undefined;
|
|
38
|
+
cwd?: string | undefined;
|
|
39
|
+
branch?: string | undefined;
|
|
40
|
+
auto_land?: boolean | undefined;
|
|
41
|
+
base?: string | undefined;
|
|
42
|
+
extend?: boolean | undefined;
|
|
43
|
+
replace_head?: string | undefined;
|
|
44
|
+
}, {
|
|
45
|
+
title?: string | undefined;
|
|
46
|
+
owner?: string | undefined;
|
|
47
|
+
body?: string | undefined;
|
|
48
|
+
repo?: string | undefined;
|
|
49
|
+
cwd?: string | undefined;
|
|
50
|
+
branch?: string | undefined;
|
|
51
|
+
auto_land?: boolean | undefined;
|
|
52
|
+
base?: string | undefined;
|
|
53
|
+
extend?: boolean | undefined;
|
|
54
|
+
replace_head?: string | undefined;
|
|
55
|
+
}>;
|
|
56
|
+
declare const statusInput: z.ZodObject<{
|
|
57
|
+
cwd: z.ZodOptional<z.ZodString>;
|
|
58
|
+
owner: z.ZodOptional<z.ZodString>;
|
|
59
|
+
repo: z.ZodOptional<z.ZodString>;
|
|
60
|
+
branch: z.ZodOptional<z.ZodString>;
|
|
61
|
+
}, "strip", z.ZodTypeAny, {
|
|
62
|
+
owner?: string | undefined;
|
|
63
|
+
repo?: string | undefined;
|
|
64
|
+
cwd?: string | undefined;
|
|
65
|
+
branch?: string | undefined;
|
|
66
|
+
}, {
|
|
67
|
+
owner?: string | undefined;
|
|
68
|
+
repo?: string | undefined;
|
|
69
|
+
cwd?: string | undefined;
|
|
70
|
+
branch?: string | undefined;
|
|
71
|
+
}>;
|
|
72
|
+
export type StackPublishInput = z.infer<typeof publishInput>;
|
|
73
|
+
export type StackPublishStatusInput = z.infer<typeof statusInput>;
|
|
74
|
+
type PublishDeps = Parameters<typeof publishWorkingBranch>[1];
|
|
75
|
+
type StatusDeps = Parameters<typeof workingBranchPublication>[1];
|
|
76
|
+
export declare function stackPublish(input: StackPublishInput, cfg?: Config, deps?: PublishDeps): Promise<ToolPayload>;
|
|
77
|
+
export declare function stackPublishStatus(input: StackPublishStatusInput, cfg?: Config, deps?: StatusDeps): Promise<ToolPayload>;
|
|
78
|
+
export {};
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { CommandError, publishWorkingBranch, stackWatchObligation, workingBranchPublication, } from "mergestorm/client";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { withWatchText } from "./stack-wait.js";
|
|
4
|
+
const target = {
|
|
5
|
+
cwd: z.string().trim().min(1).optional(),
|
|
6
|
+
owner: z.string().trim().min(1).optional(),
|
|
7
|
+
repo: z.string().trim().min(1).optional(),
|
|
8
|
+
branch: z.string().trim().min(1).optional(),
|
|
9
|
+
};
|
|
10
|
+
export const stackPublishSchema = {
|
|
11
|
+
...target,
|
|
12
|
+
base: z.string().trim().min(1).optional(),
|
|
13
|
+
extend: z.boolean().optional(),
|
|
14
|
+
title: z.string().trim().min(1).max(256).optional(),
|
|
15
|
+
body: z.string().max(60_000).optional(),
|
|
16
|
+
replace_head: z.string().regex(/^[0-9a-f]{40}$/).optional(),
|
|
17
|
+
auto_land: z.boolean().optional(),
|
|
18
|
+
};
|
|
19
|
+
export const stackPublishStatusSchema = target;
|
|
20
|
+
const publishInput = z.object(stackPublishSchema);
|
|
21
|
+
const statusInput = z.object(stackPublishStatusSchema);
|
|
22
|
+
function failure(err, fallback) {
|
|
23
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
24
|
+
return {
|
|
25
|
+
summary: message,
|
|
26
|
+
data: {
|
|
27
|
+
error: {
|
|
28
|
+
code: err instanceof CommandError ? err.code ?? fallback : fallback,
|
|
29
|
+
message,
|
|
30
|
+
...(err instanceof CommandError && err.retryAfterSeconds !== undefined
|
|
31
|
+
? { retry_after_seconds: err.retryAfterSeconds } : {}),
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
isError: true,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
function targetOf(input, cfg) {
|
|
38
|
+
return {
|
|
39
|
+
...(input.cwd ? { cwd: input.cwd } : {}),
|
|
40
|
+
...(input.owner ? { owner: input.owner } : {}),
|
|
41
|
+
...(input.repo ? { repo: input.repo } : {}),
|
|
42
|
+
...(input.branch ? { branch: input.branch } : {}),
|
|
43
|
+
...(cfg ? { cfg } : {}),
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
export async function stackPublish(input, cfg, deps) {
|
|
47
|
+
const parsed = publishInput.safeParse(input);
|
|
48
|
+
if (!parsed.success || Boolean(parsed.data.owner) !== Boolean(parsed.data.repo)) {
|
|
49
|
+
const message = "stack_publish takes owner and repo together, and replace_head as a full 40-character commit SHA";
|
|
50
|
+
return { summary: message, data: { error: { code: "invalid_input", message } }, isError: true };
|
|
51
|
+
}
|
|
52
|
+
try {
|
|
53
|
+
const result = await publishWorkingBranch({
|
|
54
|
+
...targetOf(parsed.data, cfg),
|
|
55
|
+
...(parsed.data.base ? { base: parsed.data.base } : {}),
|
|
56
|
+
...(parsed.data.extend ? { extend: true } : {}),
|
|
57
|
+
...(parsed.data.title ? { title: parsed.data.title } : {}),
|
|
58
|
+
...(parsed.data.body !== undefined ? { body: parsed.data.body } : {}),
|
|
59
|
+
...(parsed.data.replace_head ? { replaceHead: parsed.data.replace_head } : {}),
|
|
60
|
+
...(typeof parsed.data.auto_land === "boolean" ? { autoLand: parsed.data.auto_land } : {}),
|
|
61
|
+
}, deps);
|
|
62
|
+
const watch = stackWatchObligation({ stackId: result.stack.id, terminal: null, unread: true, freshCursor: true });
|
|
63
|
+
const moved = result.branchCreated ? "created" : result.branchUpdated ? "updated" : "already current";
|
|
64
|
+
return {
|
|
65
|
+
summary: withWatchText(watch, `Published ${result.owner}/${result.repo} ${result.branch} (${moved}) at ${result.head.sha}, PR #${result.pr.number} (${result.stacked ? `a layer stacked on ${result.baseBranch}` : `a single PR onto ${result.baseBranch}`}), stack ${result.stack.id}`),
|
|
66
|
+
data: { result, watch },
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
catch (err) {
|
|
70
|
+
return failure(err, "stack_publish_failed");
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
export async function stackPublishStatus(input, cfg, deps) {
|
|
74
|
+
const parsed = statusInput.safeParse(input);
|
|
75
|
+
if (!parsed.success || Boolean(parsed.data.owner) !== Boolean(parsed.data.repo)) {
|
|
76
|
+
const message = "stack_publish_status takes owner and repo together";
|
|
77
|
+
return { summary: message, data: { error: { code: "invalid_input", message } }, isError: true };
|
|
78
|
+
}
|
|
79
|
+
try {
|
|
80
|
+
const status = await workingBranchPublication(targetOf(parsed.data, cfg), deps);
|
|
81
|
+
return {
|
|
82
|
+
summary: `${status.owner}/${status.repo} ${status.branch}: ${status.state}${status.head ? ` at ${status.head.sha}` : ""}${status.pr ? `, PR #${status.pr.number}` : ""}${status.stack ? `, stack ${status.stack.id}` : ""}`,
|
|
83
|
+
data: { status },
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
catch (err) {
|
|
87
|
+
return failure(err, "stack_publish_status_failed");
|
|
88
|
+
}
|
|
89
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { type Config, type StackPolicyPatch } from "mergestorm/client";
|
|
2
|
+
import type { ToolPayload } from "./types.js";
|
|
3
|
+
/**
|
|
4
|
+
* `stack_set` input. Every key is optional but at least one must be present.
|
|
5
|
+
* `auto_land` is boolean; `auto_review` / `auto_patch` are tri-state where
|
|
6
|
+
* `null` clears the per-stack override back to the account setting.
|
|
7
|
+
*/
|
|
8
|
+
export type StackSetInput = {
|
|
9
|
+
auto_land?: boolean;
|
|
10
|
+
auto_review?: boolean | null;
|
|
11
|
+
auto_patch?: boolean | null;
|
|
12
|
+
auto_resolve_conflicts?: boolean | null;
|
|
13
|
+
auto_fix_ci?: boolean | null;
|
|
14
|
+
};
|
|
15
|
+
export declare function stackSetPatch(input: StackSetInput): StackPolicyPatch;
|
|
16
|
+
export declare function stackSet(stackId: string, input: StackSetInput, cfg?: Config): Promise<ToolPayload>;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { setStackPolicy, stackWatchObligation } from "mergestorm/client";
|
|
2
|
+
import { withWatchText } from "./stack-wait.js";
|
|
3
|
+
function triWord(value) {
|
|
4
|
+
return value === null ? "default" : value ? "on" : "off";
|
|
5
|
+
}
|
|
6
|
+
export function stackSetPatch(input) {
|
|
7
|
+
const patch = {};
|
|
8
|
+
if (typeof input.auto_land === "boolean")
|
|
9
|
+
patch.autoEnqueueWhenReady = input.auto_land;
|
|
10
|
+
if (input.auto_review !== undefined)
|
|
11
|
+
patch.autoReviewOverride = input.auto_review;
|
|
12
|
+
if (input.auto_patch !== undefined)
|
|
13
|
+
patch.autoPatchOverride = input.auto_patch;
|
|
14
|
+
if (input.auto_resolve_conflicts !== undefined) {
|
|
15
|
+
patch.autoResolveConflictsOverride = input.auto_resolve_conflicts;
|
|
16
|
+
}
|
|
17
|
+
if (input.auto_fix_ci !== undefined)
|
|
18
|
+
patch.autoFixCiOverride = input.auto_fix_ci;
|
|
19
|
+
return patch;
|
|
20
|
+
}
|
|
21
|
+
export async function stackSet(stackId, input, cfg) {
|
|
22
|
+
const patch = stackSetPatch(input);
|
|
23
|
+
if (Object.keys(patch).length === 0) {
|
|
24
|
+
return {
|
|
25
|
+
summary: "stack_set needs at least one of auto_land, auto_review, auto_patch, auto_resolve_conflicts, auto_fix_ci",
|
|
26
|
+
isError: true,
|
|
27
|
+
data: {
|
|
28
|
+
error: {
|
|
29
|
+
code: "invalid_input",
|
|
30
|
+
message: "Pass at least one of auto_land, auto_review, auto_patch, auto_resolve_conflicts, auto_fix_ci.",
|
|
31
|
+
stack_id: stackId,
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
const result = await setStackPolicy(stackId, patch, cfg);
|
|
37
|
+
const parts = [];
|
|
38
|
+
if (typeof input.auto_land === "boolean") {
|
|
39
|
+
parts.push(`Auto land ${input.auto_land ? "on" : "off"}`);
|
|
40
|
+
}
|
|
41
|
+
if (input.auto_review !== undefined) {
|
|
42
|
+
parts.push(`auto-review ${triWord(input.auto_review)}`);
|
|
43
|
+
}
|
|
44
|
+
if (input.auto_patch !== undefined) {
|
|
45
|
+
parts.push(`auto-patch ${triWord(input.auto_patch)}`);
|
|
46
|
+
}
|
|
47
|
+
if (input.auto_resolve_conflicts !== undefined) {
|
|
48
|
+
parts.push(`auto-resolve-conflicts ${triWord(input.auto_resolve_conflicts)}`);
|
|
49
|
+
}
|
|
50
|
+
if (input.auto_fix_ci !== undefined) {
|
|
51
|
+
parts.push(`auto-fix-ci ${triWord(input.auto_fix_ci)}`);
|
|
52
|
+
}
|
|
53
|
+
const watch = stackWatchObligation({ stackId, terminal: null, unread: true, freshCursor: true });
|
|
54
|
+
return {
|
|
55
|
+
summary: withWatchText(watch, `${parts.join(", ")} for stack ${stackId}`),
|
|
56
|
+
data: {
|
|
57
|
+
stack_id: stackId,
|
|
58
|
+
...(typeof input.auto_land === "boolean" ? { auto_land: input.auto_land } : {}),
|
|
59
|
+
...(input.auto_review !== undefined ? { auto_review: input.auto_review } : {}),
|
|
60
|
+
...(input.auto_patch !== undefined ? { auto_patch: input.auto_patch } : {}),
|
|
61
|
+
...(input.auto_resolve_conflicts !== undefined
|
|
62
|
+
? { auto_resolve_conflicts: input.auto_resolve_conflicts }
|
|
63
|
+
: {}),
|
|
64
|
+
...(input.auto_fix_ci !== undefined ? { auto_fix_ci: input.auto_fix_ci } : {}),
|
|
65
|
+
result,
|
|
66
|
+
watch,
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { getEnrichedStack, listMergeQueueEntries, isCommandErrorCode, stackBlockers, stackTerminalReason, stackWatchObligation, } from "mergestorm/client";
|
|
2
|
+
import { repairSummary, withWatchText } from "./stack-wait.js";
|
|
3
|
+
import { stackSummary } from "./stack-summary.js";
|
|
4
|
+
export async function stackStatus(stackId, cfg) {
|
|
5
|
+
try {
|
|
6
|
+
const stack = await getEnrichedStack(stackId, cfg);
|
|
7
|
+
if (!stack) {
|
|
8
|
+
const message = `Stack not found or not owned by the current user: ${stackId}`;
|
|
9
|
+
const watch = stackWatchObligation({ stackId, terminal: "not_found" });
|
|
10
|
+
return {
|
|
11
|
+
summary: withWatchText(watch, message),
|
|
12
|
+
data: {
|
|
13
|
+
error: {
|
|
14
|
+
code: "stack_not_found",
|
|
15
|
+
message,
|
|
16
|
+
stack_id: stackId,
|
|
17
|
+
},
|
|
18
|
+
watch,
|
|
19
|
+
},
|
|
20
|
+
isError: true,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
const entries = await listMergeQueueEntries(cfg, { stackId: stack.id });
|
|
24
|
+
const { attention, held, issues, currentCandidate, busy, agents, repair, landGatePending, queueWait } = stackBlockers(stack, entries);
|
|
25
|
+
const watch = stackWatchObligation({
|
|
26
|
+
stackId: stack.id,
|
|
27
|
+
terminal: stackTerminalReason(stack),
|
|
28
|
+
cursor: { enrolledHeadSha: currentCandidate?.headSha ?? null },
|
|
29
|
+
attention: attention ? { prNumber: attention.prNumber, blocker: attention.blocker } : null,
|
|
30
|
+
freshCursor: true,
|
|
31
|
+
});
|
|
32
|
+
return {
|
|
33
|
+
summary: withWatchText(watch, `${stackSummary(stack, entries)}${repairSummary(repair)}`),
|
|
34
|
+
data: { stack, attention, held, issues, currentCandidate, busy, agents, repair, landGatePending, queueWait, watch },
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
catch (err) {
|
|
38
|
+
if (isCommandErrorCode(err, "rate_limited")) {
|
|
39
|
+
const watch = stackWatchObligation({
|
|
40
|
+
stackId, terminal: null, status: "rate_limited", retryAfterSeconds: err.retryAfterSeconds, freshCursor: true,
|
|
41
|
+
});
|
|
42
|
+
return {
|
|
43
|
+
summary: withWatchText(watch, err.message),
|
|
44
|
+
data: {
|
|
45
|
+
error: {
|
|
46
|
+
code: "rate_limited",
|
|
47
|
+
message: err.message,
|
|
48
|
+
retry_after_seconds: err.retryAfterSeconds,
|
|
49
|
+
},
|
|
50
|
+
watch,
|
|
51
|
+
},
|
|
52
|
+
isError: true,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
throw err;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { stackBlockers, stackBlockersSummary } from "mergestorm/client";
|
|
2
|
+
export function stackSummary(stack, entries = []) {
|
|
3
|
+
const count = stack.layers.length;
|
|
4
|
+
const layerLabel = `${count} layer${count === 1 ? "" : "s"}`;
|
|
5
|
+
const states = [...new Set(stack.layers.map((layer) => layer.state))];
|
|
6
|
+
const currentState = states.length > 0
|
|
7
|
+
? `restack: ${states.join(", ")}`
|
|
8
|
+
: stack.unit
|
|
9
|
+
? `unit: ${stack.unit.state}`
|
|
10
|
+
: "no open layers";
|
|
11
|
+
const overrides = [];
|
|
12
|
+
if (typeof stack.autoReviewOverride === "boolean") {
|
|
13
|
+
overrides.push(`auto-review ${stack.autoReviewOverride ? "on" : "off"}`);
|
|
14
|
+
}
|
|
15
|
+
if (typeof stack.autoPatchOverride === "boolean") {
|
|
16
|
+
overrides.push(`auto-patch ${stack.autoPatchOverride ? "on" : "off"}`);
|
|
17
|
+
}
|
|
18
|
+
if (typeof stack.autoResolveConflictsOverride === "boolean") {
|
|
19
|
+
overrides.push(`auto-resolve-conflicts ${stack.autoResolveConflictsOverride ? "on" : "off"}`);
|
|
20
|
+
}
|
|
21
|
+
if (typeof stack.autoFixCiOverride === "boolean") {
|
|
22
|
+
overrides.push(`auto-fix-ci ${stack.autoFixCiOverride ? "on" : "off"}`);
|
|
23
|
+
}
|
|
24
|
+
if (stack.cycloneOwnerMatch) {
|
|
25
|
+
overrides.push(`cyclone-owner ${stack.cycloneOwnerMatch}`);
|
|
26
|
+
}
|
|
27
|
+
const { attention, held, issues, busy, landGatePending, queueWait } = stackBlockers(stack, entries);
|
|
28
|
+
return `${stack.owner}/${stack.repo} · ${layerLabel} · ${currentState} · auto-land ${stack.autoEnqueueWhenReady ? "on" : "off"}${overrides.map((label) => ` · ${label}`).join("")}${stackBlockersSummary(attention ?? held, issues, busy, landGatePending, attention ? null : held?.actAfter ?? null, queueWait)}`;
|
|
29
|
+
}
|