ralph-mcp 1.0.0

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.
@@ -0,0 +1,384 @@
1
+ import { z } from "zod";
2
+ import { removeWorktree, abortMerge, } from "../utils/worktree.js";
3
+ import { generateMergeAgentPrompt, startMergeAgent, } from "../utils/agent.js";
4
+ import { syncMainToBranch, runQualityChecks, generateCommitMessage, updateTodoDoc, updateProjectStatus, handleSchemaConflict, } from "../utils/merge-helpers.js";
5
+ import { execSync } from "child_process";
6
+ import { deleteMergeQueueByExecutionId, findExecutionByBranch, findExecutionById, insertMergeQueueItem, listExecutions, listMergeQueue, listUserStoriesByExecutionId, updateExecution, updateMergeQueueItem, } from "../store/state.js";
7
+ export const mergeInputSchema = z.object({
8
+ branch: z.string().describe("Branch name to merge (e.g., ralph/task1-agent)"),
9
+ force: z.boolean().default(false).describe("Skip verification checks"),
10
+ skipQualityChecks: z.boolean().default(false).describe("Skip type check and build"),
11
+ onConflict: z
12
+ .enum(["auto_theirs", "auto_ours", "notify", "agent"])
13
+ .optional()
14
+ .describe("Override conflict resolution strategy"),
15
+ });
16
+ export async function merge(input) {
17
+ // Find execution
18
+ const exec = await findExecutionByBranch(input.branch);
19
+ if (!exec) {
20
+ throw new Error(`No execution found for branch: ${input.branch}`);
21
+ }
22
+ // Get completed stories for commit message
23
+ const stories = await listUserStoriesByExecutionId(exec.id);
24
+ const completedStories = stories
25
+ .filter((s) => s.passes)
26
+ .map((s) => ({ id: s.storyId, title: s.title }));
27
+ // Check if all stories are complete (unless force)
28
+ if (!input.force) {
29
+ const { get } = await import("./get.js");
30
+ const status = await get({ branch: input.branch });
31
+ if (status.progress.completed < status.progress.total) {
32
+ throw new Error(`Cannot merge: ${status.progress.completed}/${status.progress.total} stories complete. Use force=true to override.`);
33
+ }
34
+ }
35
+ // Update status to merging
36
+ await updateExecution(exec.id, { status: "merging", updatedAt: new Date() });
37
+ const onConflict = input.onConflict || exec.onConflict || "agent";
38
+ try {
39
+ // Step 1: Sync main to feature branch (in worktree)
40
+ if (exec.worktreePath) {
41
+ console.log(">>> Syncing main to feature branch...");
42
+ const syncResult = await syncMainToBranch(exec.worktreePath, exec.branch);
43
+ if (!syncResult.success) {
44
+ if (syncResult.hasConflicts && syncResult.conflictFiles) {
45
+ // Try to handle schema conflicts automatically
46
+ const hasSchemaConflict = syncResult.conflictFiles.some((f) => f.includes("schema.prisma"));
47
+ if (hasSchemaConflict) {
48
+ console.log(">>> Attempting to resolve schema.prisma conflict...");
49
+ const schemaResolved = await handleSchemaConflict(exec.worktreePath);
50
+ if (!schemaResolved) {
51
+ throw new Error(`Failed to resolve schema.prisma conflict during sync`);
52
+ }
53
+ // Continue with remaining conflicts if any
54
+ const remainingConflicts = syncResult.conflictFiles.filter((f) => !f.includes("schema.prisma"));
55
+ if (remainingConflicts.length > 0) {
56
+ throw new Error(`Sync conflicts in: ${remainingConflicts.join(", ")}`);
57
+ }
58
+ }
59
+ else {
60
+ throw new Error(syncResult.message);
61
+ }
62
+ }
63
+ else {
64
+ throw new Error(syncResult.message);
65
+ }
66
+ }
67
+ }
68
+ // Step 2: Run quality checks (unless skipped)
69
+ if (!input.skipQualityChecks && exec.worktreePath) {
70
+ console.log(">>> Running quality checks...");
71
+ const qualityResult = await runQualityChecks(exec.worktreePath);
72
+ if (!qualityResult.success) {
73
+ const failedChecks = [];
74
+ if (!qualityResult.typeCheck.success)
75
+ failedChecks.push("typeCheck");
76
+ if (!qualityResult.build.success)
77
+ failedChecks.push("build");
78
+ await updateExecution(exec.id, { status: "failed", updatedAt: new Date() });
79
+ return {
80
+ success: false,
81
+ branch: input.branch,
82
+ cleanedUp: false,
83
+ qualityChecks: {
84
+ typeCheck: qualityResult.typeCheck.success,
85
+ build: qualityResult.build.success,
86
+ },
87
+ message: `Quality checks failed: ${failedChecks.join(", ")}. Fix issues before merging.`,
88
+ };
89
+ }
90
+ }
91
+ // Step 3: Generate commit message
92
+ const commitMessage = generateCommitMessage(exec.branch, exec.description, completedStories);
93
+ // Step 4: Attempt merge to main
94
+ console.log(">>> Merging to main...");
95
+ const mergeResult = await mergeBranchWithMessage(exec.projectRoot, exec.branch, commitMessage, onConflict);
96
+ if (mergeResult.success) {
97
+ // Step 5: Update docs
98
+ const docsUpdated = [];
99
+ if (updateTodoDoc(exec.projectRoot, exec.branch, exec.description)) {
100
+ docsUpdated.push("docs/TODO.md");
101
+ }
102
+ if (mergeResult.commitHash &&
103
+ updateProjectStatus(exec.projectRoot, exec.branch, exec.description, mergeResult.commitHash)) {
104
+ docsUpdated.push("docs/PROJECT-STATUS.md");
105
+ }
106
+ // Commit doc updates if any
107
+ if (docsUpdated.length > 0) {
108
+ try {
109
+ execSync(`git add ${docsUpdated.join(" ")} && git commit --amend --no-edit`, {
110
+ cwd: exec.projectRoot,
111
+ });
112
+ }
113
+ catch {
114
+ // Ignore if no changes or amend fails
115
+ }
116
+ }
117
+ // Step 6: Clean up worktree
118
+ let cleanedUp = false;
119
+ if (exec.worktreePath) {
120
+ try {
121
+ await removeWorktree(exec.projectRoot, exec.worktreePath);
122
+ cleanedUp = true;
123
+ }
124
+ catch (e) {
125
+ console.error("Failed to remove worktree:", e);
126
+ }
127
+ }
128
+ // Update status
129
+ await updateExecution(exec.id, { status: "completed", updatedAt: new Date() });
130
+ return {
131
+ success: true,
132
+ branch: input.branch,
133
+ commitHash: mergeResult.commitHash,
134
+ cleanedUp,
135
+ conflictResolution: "auto",
136
+ qualityChecks: input.skipQualityChecks
137
+ ? undefined
138
+ : { typeCheck: true, build: true },
139
+ docsUpdated: docsUpdated.length > 0 ? docsUpdated : undefined,
140
+ mergedStories: completedStories.map((s) => s.id),
141
+ message: `Successfully merged ${input.branch} to main`,
142
+ };
143
+ }
144
+ // Handle conflicts
145
+ if (mergeResult.hasConflicts && mergeResult.conflictFiles) {
146
+ // Try schema conflict resolution first
147
+ const hasSchemaConflict = mergeResult.conflictFiles.some((f) => f.includes("schema.prisma"));
148
+ if (hasSchemaConflict) {
149
+ console.log(">>> Attempting to resolve schema.prisma conflict...");
150
+ const schemaResolved = await handleSchemaConflict(exec.projectRoot);
151
+ if (schemaResolved) {
152
+ // Check if there are remaining conflicts
153
+ const remainingConflicts = mergeResult.conflictFiles.filter((f) => !f.includes("schema.prisma"));
154
+ if (remainingConflicts.length === 0) {
155
+ // Complete the merge
156
+ try {
157
+ execSync(`git commit -m "${commitMessage.replace(/"/g, '\\"')}"`, {
158
+ cwd: exec.projectRoot,
159
+ });
160
+ const commitHash = execSync("git rev-parse HEAD", {
161
+ cwd: exec.projectRoot,
162
+ encoding: "utf-8",
163
+ }).trim();
164
+ // Clean up
165
+ let cleanedUp = false;
166
+ if (exec.worktreePath) {
167
+ try {
168
+ await removeWorktree(exec.projectRoot, exec.worktreePath);
169
+ cleanedUp = true;
170
+ }
171
+ catch (e) {
172
+ console.error("Failed to remove worktree:", e);
173
+ }
174
+ }
175
+ await updateExecution(exec.id, { status: "completed", updatedAt: new Date() });
176
+ return {
177
+ success: true,
178
+ branch: input.branch,
179
+ commitHash,
180
+ cleanedUp,
181
+ conflictResolution: "auto",
182
+ mergedStories: completedStories.map((s) => s.id),
183
+ message: `Successfully merged ${input.branch} (schema conflict auto-resolved)`,
184
+ };
185
+ }
186
+ catch {
187
+ // Fall through to agent resolution
188
+ }
189
+ }
190
+ }
191
+ }
192
+ if (onConflict === "notify") {
193
+ await updateExecution(exec.id, { status: "failed", updatedAt: new Date() });
194
+ return {
195
+ success: false,
196
+ branch: input.branch,
197
+ cleanedUp: false,
198
+ conflictResolution: "pending",
199
+ message: `Merge conflicts detected in: ${mergeResult.conflictFiles.join(", ")}. Manual resolution required.`,
200
+ };
201
+ }
202
+ if (onConflict === "agent") {
203
+ const prompt = generateMergeAgentPrompt(exec.projectRoot, exec.branch, exec.description, mergeResult.conflictFiles, exec.prdPath);
204
+ const agentResult = await startMergeAgent(exec.projectRoot, prompt);
205
+ if (agentResult.success) {
206
+ let cleanedUp = false;
207
+ if (exec.worktreePath) {
208
+ try {
209
+ await removeWorktree(exec.projectRoot, exec.worktreePath);
210
+ cleanedUp = true;
211
+ }
212
+ catch (e) {
213
+ console.error("Failed to remove worktree:", e);
214
+ }
215
+ }
216
+ const commitHash = execSync("git rev-parse HEAD", {
217
+ cwd: exec.projectRoot,
218
+ encoding: "utf-8",
219
+ }).trim();
220
+ await updateExecution(exec.id, { status: "completed", updatedAt: new Date() });
221
+ return {
222
+ success: true,
223
+ branch: input.branch,
224
+ commitHash,
225
+ cleanedUp,
226
+ conflictResolution: "agent",
227
+ mergedStories: completedStories.map((s) => s.id),
228
+ message: `Merge conflicts resolved by agent for ${input.branch}`,
229
+ };
230
+ }
231
+ else {
232
+ await abortMerge(exec.projectRoot);
233
+ await updateExecution(exec.id, { status: "failed", updatedAt: new Date() });
234
+ return {
235
+ success: false,
236
+ branch: input.branch,
237
+ cleanedUp: false,
238
+ conflictResolution: "pending",
239
+ message: `Merge agent failed: ${agentResult.output}`,
240
+ };
241
+ }
242
+ }
243
+ }
244
+ throw new Error("Unexpected merge state");
245
+ }
246
+ catch (error) {
247
+ await updateExecution(exec.id, { status: "failed", updatedAt: new Date() });
248
+ throw error;
249
+ }
250
+ }
251
+ /**
252
+ * Merge branch with custom commit message
253
+ */
254
+ async function mergeBranchWithMessage(projectRoot, branch, commitMessage, onConflict) {
255
+ const { exec: execAsync } = await import("child_process");
256
+ const { promisify } = await import("util");
257
+ const execPromise = promisify(execAsync);
258
+ // Checkout main and pull
259
+ await execPromise("git checkout main && git pull", { cwd: projectRoot });
260
+ // Build merge strategy
261
+ let mergeStrategy = "";
262
+ if (onConflict === "auto_theirs") {
263
+ mergeStrategy = "-X theirs";
264
+ }
265
+ else if (onConflict === "auto_ours") {
266
+ mergeStrategy = "-X ours";
267
+ }
268
+ try {
269
+ // Use heredoc-style commit message to handle special characters
270
+ const escapedMessage = commitMessage.replace(/'/g, "'\\''");
271
+ await execPromise(`git merge --no-ff ${mergeStrategy} "${branch}" -m '${escapedMessage}'`, { cwd: projectRoot });
272
+ const { stdout: hash } = await execPromise("git rev-parse HEAD", {
273
+ cwd: projectRoot,
274
+ });
275
+ return {
276
+ success: true,
277
+ commitHash: hash.trim(),
278
+ hasConflicts: false,
279
+ };
280
+ }
281
+ catch {
282
+ // Check for conflicts
283
+ const { stdout: status } = await execPromise("git status --porcelain", {
284
+ cwd: projectRoot,
285
+ });
286
+ const conflictFiles = status
287
+ .split("\n")
288
+ .filter((line) => line.startsWith("UU ") || line.startsWith("AA "))
289
+ .map((line) => line.slice(3));
290
+ if (conflictFiles.length > 0) {
291
+ return {
292
+ success: false,
293
+ hasConflicts: true,
294
+ conflictFiles,
295
+ };
296
+ }
297
+ throw new Error("Merge failed for unknown reason");
298
+ }
299
+ }
300
+ // Merge queue management
301
+ export const mergeQueueInputSchema = z.object({
302
+ action: z
303
+ .enum(["list", "add", "remove", "process"])
304
+ .default("list")
305
+ .describe("Queue action"),
306
+ branch: z.string().optional().describe("Branch for add/remove actions"),
307
+ });
308
+ export async function mergeQueueAction(input) {
309
+ const queue = await listMergeQueue();
310
+ const current = queue.find((q) => q.status === "merging");
311
+ if (input.action === "list") {
312
+ const execs = await listExecutions();
313
+ const execById = new Map(execs.map((e) => [e.id, e]));
314
+ return {
315
+ queue: queue.map((q) => {
316
+ const exec = execById.get(q.executionId);
317
+ return exec?.branch || q.executionId;
318
+ }),
319
+ current: current
320
+ ? execById.get(current.executionId)?.branch
321
+ : undefined,
322
+ message: `${queue.length} items in merge queue`,
323
+ };
324
+ }
325
+ if (input.action === "add" && input.branch) {
326
+ const exec = await findExecutionByBranch(input.branch);
327
+ if (!exec) {
328
+ throw new Error(`No execution found for branch: ${input.branch}`);
329
+ }
330
+ const maxPosition = queue.length > 0
331
+ ? Math.max(...queue.map((q) => q.position))
332
+ : 0;
333
+ await insertMergeQueueItem({
334
+ executionId: exec.id,
335
+ position: maxPosition + 1,
336
+ status: "pending",
337
+ createdAt: new Date(),
338
+ });
339
+ return {
340
+ queue: [...queue.map((q) => q.executionId), exec.id],
341
+ message: `Added ${input.branch} to merge queue at position ${maxPosition + 1}`,
342
+ };
343
+ }
344
+ if (input.action === "remove" && input.branch) {
345
+ const exec = await findExecutionByBranch(input.branch);
346
+ if (exec) {
347
+ await deleteMergeQueueByExecutionId(exec.id);
348
+ }
349
+ return {
350
+ queue: queue
351
+ .filter((q) => q.executionId !== exec?.id)
352
+ .map((q) => q.executionId),
353
+ message: `Removed ${input.branch} from merge queue`,
354
+ };
355
+ }
356
+ if (input.action === "process") {
357
+ // Process next item in queue
358
+ const next = queue.find((q) => q.status === "pending");
359
+ if (!next) {
360
+ return {
361
+ queue: [],
362
+ message: "No pending items in merge queue",
363
+ };
364
+ }
365
+ const exec = await findExecutionById(next.executionId);
366
+ if (exec) {
367
+ // Update queue status
368
+ await updateMergeQueueItem(next.id, { status: "merging" });
369
+ // Perform merge
370
+ const result = await merge({ branch: exec.branch, force: false, skipQualityChecks: false });
371
+ // Update queue status
372
+ await updateMergeQueueItem(next.id, { status: result.success ? "completed" : "failed" });
373
+ return {
374
+ queue: queue.slice(1).map((q) => q.executionId),
375
+ current: exec.branch,
376
+ message: result.message,
377
+ };
378
+ }
379
+ }
380
+ return {
381
+ queue: queue.map((q) => q.executionId),
382
+ message: "Unknown action",
383
+ };
384
+ }
@@ -0,0 +1,18 @@
1
+ import { z } from "zod";
2
+ export declare const setAgentIdInputSchema: z.ZodObject<{
3
+ branch: z.ZodString;
4
+ agentTaskId: z.ZodString;
5
+ }, "strip", z.ZodTypeAny, {
6
+ branch: string;
7
+ agentTaskId: string;
8
+ }, {
9
+ branch: string;
10
+ agentTaskId: string;
11
+ }>;
12
+ export type SetAgentIdInput = z.infer<typeof setAgentIdInputSchema>;
13
+ export interface SetAgentIdResult {
14
+ success: boolean;
15
+ branch: string;
16
+ agentTaskId: string;
17
+ }
18
+ export declare function setAgentId(input: SetAgentIdInput): Promise<SetAgentIdResult>;
@@ -0,0 +1,24 @@
1
+ import { z } from "zod";
2
+ import { findExecutionByBranch, updateExecution } from "../store/state.js";
3
+ export const setAgentIdInputSchema = z.object({
4
+ branch: z.string().describe("Branch name"),
5
+ agentTaskId: z.string().describe("Claude Task agent ID"),
6
+ });
7
+ export async function setAgentId(input) {
8
+ // Find execution
9
+ const exec = await findExecutionByBranch(input.branch);
10
+ if (!exec) {
11
+ throw new Error(`No execution found for branch: ${input.branch}`);
12
+ }
13
+ // Update agent task ID and status
14
+ await updateExecution(exec.id, {
15
+ agentTaskId: input.agentTaskId,
16
+ status: "running",
17
+ updatedAt: new Date(),
18
+ });
19
+ return {
20
+ success: true,
21
+ branch: input.branch,
22
+ agentTaskId: input.agentTaskId,
23
+ };
24
+ }
@@ -0,0 +1,42 @@
1
+ import { z } from "zod";
2
+ export declare const startInputSchema: z.ZodObject<{
3
+ prdPath: z.ZodString;
4
+ projectRoot: z.ZodOptional<z.ZodString>;
5
+ worktree: z.ZodDefault<z.ZodBoolean>;
6
+ autoStart: z.ZodDefault<z.ZodBoolean>;
7
+ autoMerge: z.ZodDefault<z.ZodBoolean>;
8
+ notifyOnComplete: z.ZodDefault<z.ZodBoolean>;
9
+ onConflict: z.ZodDefault<z.ZodEnum<["auto_theirs", "auto_ours", "notify", "agent"]>>;
10
+ }, "strip", z.ZodTypeAny, {
11
+ prdPath: string;
12
+ onConflict: "auto_theirs" | "auto_ours" | "notify" | "agent";
13
+ autoMerge: boolean;
14
+ notifyOnComplete: boolean;
15
+ worktree: boolean;
16
+ autoStart: boolean;
17
+ projectRoot?: string | undefined;
18
+ }, {
19
+ prdPath: string;
20
+ projectRoot?: string | undefined;
21
+ onConflict?: "auto_theirs" | "auto_ours" | "notify" | "agent" | undefined;
22
+ autoMerge?: boolean | undefined;
23
+ notifyOnComplete?: boolean | undefined;
24
+ worktree?: boolean | undefined;
25
+ autoStart?: boolean | undefined;
26
+ }>;
27
+ export type StartInput = z.infer<typeof startInputSchema>;
28
+ export interface StartResult {
29
+ executionId: string;
30
+ branch: string;
31
+ worktreePath: string | null;
32
+ agentPrompt: string | null;
33
+ stories: Array<{
34
+ storyId: string;
35
+ title: string;
36
+ description: string;
37
+ acceptanceCriteria: string[];
38
+ priority: number;
39
+ passes: boolean;
40
+ }>;
41
+ }
42
+ export declare function start(input: StartInput): Promise<StartResult>;
@@ -0,0 +1,96 @@
1
+ import { z } from "zod";
2
+ import { randomUUID } from "crypto";
3
+ import { parsePrdFile } from "../utils/prd-parser.js";
4
+ import { createWorktree } from "../utils/worktree.js";
5
+ import { generateAgentPrompt } from "../utils/agent.js";
6
+ import { resolve, basename } from "path";
7
+ import { findExecutionByBranch, insertExecution, insertUserStories, } from "../store/state.js";
8
+ export const startInputSchema = z.object({
9
+ prdPath: z.string().describe("Path to the PRD markdown file"),
10
+ projectRoot: z.string().optional().describe("Project root directory (defaults to cwd)"),
11
+ worktree: z.boolean().default(true).describe("Create a worktree for isolation"),
12
+ autoStart: z.boolean().default(true).describe("Generate agent prompt for auto-start"),
13
+ autoMerge: z.boolean().default(false).describe("Auto add to merge queue when all stories pass"),
14
+ notifyOnComplete: z.boolean().default(true).describe("Show Windows notification when all stories complete"),
15
+ onConflict: z
16
+ .enum(["auto_theirs", "auto_ours", "notify", "agent"])
17
+ .default("agent")
18
+ .describe("Conflict resolution strategy for merge"),
19
+ });
20
+ export async function start(input) {
21
+ const projectRoot = input.projectRoot || process.cwd();
22
+ const prdPath = resolve(projectRoot, input.prdPath);
23
+ // Parse PRD file
24
+ const prd = parsePrdFile(prdPath);
25
+ // Check if execution already exists for this branch
26
+ const existing = await findExecutionByBranch(prd.branchName);
27
+ if (existing) {
28
+ throw new Error(`Execution already exists for branch ${prd.branchName}. Use ralph_get to check status or ralph_stop to stop it.`);
29
+ }
30
+ // Create worktree if requested
31
+ let worktreePath = null;
32
+ if (input.worktree) {
33
+ worktreePath = await createWorktree(projectRoot, prd.branchName);
34
+ }
35
+ // Create execution record
36
+ const executionId = randomUUID();
37
+ const now = new Date();
38
+ const projectName = basename(projectRoot);
39
+ await insertExecution({
40
+ id: executionId,
41
+ project: projectName,
42
+ branch: prd.branchName,
43
+ description: prd.description,
44
+ prdPath: prdPath,
45
+ projectRoot: projectRoot,
46
+ worktreePath: worktreePath,
47
+ status: "pending",
48
+ agentTaskId: null,
49
+ onConflict: input.onConflict,
50
+ autoMerge: input.autoMerge,
51
+ notifyOnComplete: input.notifyOnComplete,
52
+ createdAt: now,
53
+ updatedAt: now,
54
+ });
55
+ // Create user story records
56
+ const storyRecords = prd.userStories.map((story) => ({
57
+ id: `${executionId}:${story.id}`,
58
+ executionId: executionId,
59
+ storyId: story.id,
60
+ title: story.title,
61
+ description: story.description,
62
+ acceptanceCriteria: story.acceptanceCriteria,
63
+ priority: story.priority,
64
+ passes: false,
65
+ notes: "",
66
+ }));
67
+ if (storyRecords.length > 0) {
68
+ await insertUserStories(storyRecords);
69
+ }
70
+ // Generate agent prompt if auto-start
71
+ let agentPrompt = null;
72
+ if (input.autoStart) {
73
+ agentPrompt = generateAgentPrompt(prd.branchName, prd.description, worktreePath || projectRoot, storyRecords.map((s) => ({
74
+ storyId: s.storyId,
75
+ title: s.title,
76
+ description: s.description,
77
+ acceptanceCriteria: s.acceptanceCriteria,
78
+ priority: s.priority,
79
+ passes: s.passes,
80
+ })));
81
+ }
82
+ return {
83
+ executionId,
84
+ branch: prd.branchName,
85
+ worktreePath,
86
+ agentPrompt,
87
+ stories: storyRecords.map((s) => ({
88
+ storyId: s.storyId,
89
+ title: s.title,
90
+ description: s.description,
91
+ acceptanceCriteria: s.acceptanceCriteria,
92
+ priority: s.priority,
93
+ passes: s.passes,
94
+ })),
95
+ };
96
+ }
@@ -0,0 +1,35 @@
1
+ import { z } from "zod";
2
+ export declare const statusInputSchema: z.ZodObject<{
3
+ project: z.ZodOptional<z.ZodString>;
4
+ status: z.ZodOptional<z.ZodEnum<["pending", "running", "completed", "failed", "stopped", "merging"]>>;
5
+ }, "strip", z.ZodTypeAny, {
6
+ project?: string | undefined;
7
+ status?: "pending" | "running" | "completed" | "failed" | "stopped" | "merging" | undefined;
8
+ }, {
9
+ project?: string | undefined;
10
+ status?: "pending" | "running" | "completed" | "failed" | "stopped" | "merging" | undefined;
11
+ }>;
12
+ export type StatusInput = z.infer<typeof statusInputSchema>;
13
+ export interface ExecutionStatus {
14
+ branch: string;
15
+ description: string;
16
+ status: string;
17
+ progress: string;
18
+ completedStories: number;
19
+ totalStories: number;
20
+ worktreePath: string | null;
21
+ agentTaskId: string | null;
22
+ lastActivity: string;
23
+ createdAt: string;
24
+ }
25
+ export interface StatusResult {
26
+ executions: ExecutionStatus[];
27
+ summary: {
28
+ total: number;
29
+ running: number;
30
+ completed: number;
31
+ failed: number;
32
+ pending: number;
33
+ };
34
+ }
35
+ export declare function status(input: StatusInput): Promise<StatusResult>;
@@ -0,0 +1,53 @@
1
+ import { z } from "zod";
2
+ import { listExecutions, listUserStoriesByExecutionId } from "../store/state.js";
3
+ export const statusInputSchema = z.object({
4
+ project: z.string().optional().describe("Filter by project name"),
5
+ status: z
6
+ .enum(["pending", "running", "completed", "failed", "stopped", "merging"])
7
+ .optional()
8
+ .describe("Filter by status"),
9
+ });
10
+ export async function status(input) {
11
+ const allExecutions = await listExecutions();
12
+ // Filter in memory (simpler than building dynamic where clauses)
13
+ let filtered = allExecutions;
14
+ if (input.project) {
15
+ filtered = filtered.filter((e) => e.project === input.project);
16
+ }
17
+ if (input.status) {
18
+ filtered = filtered.filter((e) => e.status === input.status);
19
+ }
20
+ // Get story counts for each execution
21
+ const executionStatuses = [];
22
+ for (const exec of filtered) {
23
+ const stories = await listUserStoriesByExecutionId(exec.id);
24
+ const completedStories = stories.filter((s) => s.passes).length;
25
+ const totalStories = stories.length;
26
+ executionStatuses.push({
27
+ branch: exec.branch,
28
+ description: exec.description,
29
+ status: exec.status,
30
+ progress: `${completedStories}/${totalStories} US`,
31
+ completedStories,
32
+ totalStories,
33
+ worktreePath: exec.worktreePath,
34
+ agentTaskId: exec.agentTaskId,
35
+ lastActivity: exec.updatedAt.toISOString(),
36
+ createdAt: exec.createdAt.toISOString(),
37
+ });
38
+ }
39
+ // Sort by last activity (most recent first)
40
+ executionStatuses.sort((a, b) => new Date(b.lastActivity).getTime() - new Date(a.lastActivity).getTime());
41
+ // Calculate summary
42
+ const summary = {
43
+ total: executionStatuses.length,
44
+ running: executionStatuses.filter((e) => e.status === "running").length,
45
+ completed: executionStatuses.filter((e) => e.status === "completed").length,
46
+ failed: executionStatuses.filter((e) => e.status === "failed").length,
47
+ pending: executionStatuses.filter((e) => e.status === "pending").length,
48
+ };
49
+ return {
50
+ executions: executionStatuses,
51
+ summary,
52
+ };
53
+ }
@@ -0,0 +1,24 @@
1
+ import { z } from "zod";
2
+ export declare const stopInputSchema: z.ZodObject<{
3
+ branch: z.ZodString;
4
+ cleanup: z.ZodDefault<z.ZodBoolean>;
5
+ deleteRecord: z.ZodDefault<z.ZodBoolean>;
6
+ }, "strip", z.ZodTypeAny, {
7
+ branch: string;
8
+ cleanup: boolean;
9
+ deleteRecord: boolean;
10
+ }, {
11
+ branch: string;
12
+ cleanup?: boolean | undefined;
13
+ deleteRecord?: boolean | undefined;
14
+ }>;
15
+ export type StopInput = z.infer<typeof stopInputSchema>;
16
+ export interface StopResult {
17
+ success: boolean;
18
+ branch: string;
19
+ previousStatus: string;
20
+ cleanedUp: boolean;
21
+ deleted: boolean;
22
+ message: string;
23
+ }
24
+ export declare function stop(input: StopInput): Promise<StopResult>;