popeye-cli 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.
Files changed (209) hide show
  1. package/.env.example +25 -0
  2. package/.prettierrc +8 -0
  3. package/README.md +320 -0
  4. package/dist/adapters/claude.d.ts +82 -0
  5. package/dist/adapters/claude.d.ts.map +1 -0
  6. package/dist/adapters/claude.js +230 -0
  7. package/dist/adapters/claude.js.map +1 -0
  8. package/dist/adapters/openai.d.ts +48 -0
  9. package/dist/adapters/openai.d.ts.map +1 -0
  10. package/dist/adapters/openai.js +257 -0
  11. package/dist/adapters/openai.js.map +1 -0
  12. package/dist/auth/claude.d.ts +44 -0
  13. package/dist/auth/claude.d.ts.map +1 -0
  14. package/dist/auth/claude.js +139 -0
  15. package/dist/auth/claude.js.map +1 -0
  16. package/dist/auth/index.d.ts +61 -0
  17. package/dist/auth/index.d.ts.map +1 -0
  18. package/dist/auth/index.js +141 -0
  19. package/dist/auth/index.js.map +1 -0
  20. package/dist/auth/keychain.d.ts +66 -0
  21. package/dist/auth/keychain.d.ts.map +1 -0
  22. package/dist/auth/keychain.js +125 -0
  23. package/dist/auth/keychain.js.map +1 -0
  24. package/dist/auth/openai-entry.d.ts +9 -0
  25. package/dist/auth/openai-entry.d.ts.map +1 -0
  26. package/dist/auth/openai-entry.js +410 -0
  27. package/dist/auth/openai-entry.js.map +1 -0
  28. package/dist/auth/openai.d.ts +71 -0
  29. package/dist/auth/openai.d.ts.map +1 -0
  30. package/dist/auth/openai.js +212 -0
  31. package/dist/auth/openai.js.map +1 -0
  32. package/dist/auth/server.d.ts +32 -0
  33. package/dist/auth/server.d.ts.map +1 -0
  34. package/dist/auth/server.js +213 -0
  35. package/dist/auth/server.js.map +1 -0
  36. package/dist/cli/commands/auth.d.ts +10 -0
  37. package/dist/cli/commands/auth.d.ts.map +1 -0
  38. package/dist/cli/commands/auth.js +162 -0
  39. package/dist/cli/commands/auth.js.map +1 -0
  40. package/dist/cli/commands/config.d.ts +10 -0
  41. package/dist/cli/commands/config.d.ts.map +1 -0
  42. package/dist/cli/commands/config.js +215 -0
  43. package/dist/cli/commands/config.js.map +1 -0
  44. package/dist/cli/commands/create.d.ts +10 -0
  45. package/dist/cli/commands/create.d.ts.map +1 -0
  46. package/dist/cli/commands/create.js +240 -0
  47. package/dist/cli/commands/create.js.map +1 -0
  48. package/dist/cli/commands/index.d.ts +10 -0
  49. package/dist/cli/commands/index.d.ts.map +1 -0
  50. package/dist/cli/commands/index.js +10 -0
  51. package/dist/cli/commands/index.js.map +1 -0
  52. package/dist/cli/commands/resume.d.ts +18 -0
  53. package/dist/cli/commands/resume.d.ts.map +1 -0
  54. package/dist/cli/commands/resume.js +241 -0
  55. package/dist/cli/commands/resume.js.map +1 -0
  56. package/dist/cli/commands/status.d.ts +18 -0
  57. package/dist/cli/commands/status.d.ts.map +1 -0
  58. package/dist/cli/commands/status.js +154 -0
  59. package/dist/cli/commands/status.js.map +1 -0
  60. package/dist/cli/index.d.ts +17 -0
  61. package/dist/cli/index.d.ts.map +1 -0
  62. package/dist/cli/index.js +71 -0
  63. package/dist/cli/index.js.map +1 -0
  64. package/dist/cli/interactive.d.ts +9 -0
  65. package/dist/cli/interactive.d.ts.map +1 -0
  66. package/dist/cli/interactive.js +330 -0
  67. package/dist/cli/interactive.js.map +1 -0
  68. package/dist/cli/output.d.ts +182 -0
  69. package/dist/cli/output.d.ts.map +1 -0
  70. package/dist/cli/output.js +355 -0
  71. package/dist/cli/output.js.map +1 -0
  72. package/dist/config/defaults.d.ts +57 -0
  73. package/dist/config/defaults.d.ts.map +1 -0
  74. package/dist/config/defaults.js +103 -0
  75. package/dist/config/defaults.js.map +1 -0
  76. package/dist/config/index.d.ts +138 -0
  77. package/dist/config/index.d.ts.map +1 -0
  78. package/dist/config/index.js +244 -0
  79. package/dist/config/index.js.map +1 -0
  80. package/dist/config/schema.d.ts +220 -0
  81. package/dist/config/schema.d.ts.map +1 -0
  82. package/dist/config/schema.js +141 -0
  83. package/dist/config/schema.js.map +1 -0
  84. package/dist/generators/index.d.ts +101 -0
  85. package/dist/generators/index.d.ts.map +1 -0
  86. package/dist/generators/index.js +200 -0
  87. package/dist/generators/index.js.map +1 -0
  88. package/dist/generators/python.d.ts +48 -0
  89. package/dist/generators/python.d.ts.map +1 -0
  90. package/dist/generators/python.js +262 -0
  91. package/dist/generators/python.js.map +1 -0
  92. package/dist/generators/templates/index.d.ts +6 -0
  93. package/dist/generators/templates/index.d.ts.map +1 -0
  94. package/dist/generators/templates/index.js +6 -0
  95. package/dist/generators/templates/index.js.map +1 -0
  96. package/dist/generators/templates/python.d.ts +53 -0
  97. package/dist/generators/templates/python.d.ts.map +1 -0
  98. package/dist/generators/templates/python.js +454 -0
  99. package/dist/generators/templates/python.js.map +1 -0
  100. package/dist/generators/templates/typescript.d.ts +53 -0
  101. package/dist/generators/templates/typescript.d.ts.map +1 -0
  102. package/dist/generators/templates/typescript.js +394 -0
  103. package/dist/generators/templates/typescript.js.map +1 -0
  104. package/dist/generators/typescript.d.ts +64 -0
  105. package/dist/generators/typescript.d.ts.map +1 -0
  106. package/dist/generators/typescript.js +271 -0
  107. package/dist/generators/typescript.js.map +1 -0
  108. package/dist/index.d.ts +7 -0
  109. package/dist/index.d.ts.map +1 -0
  110. package/dist/index.js +12 -0
  111. package/dist/index.js.map +1 -0
  112. package/dist/state/index.d.ts +168 -0
  113. package/dist/state/index.d.ts.map +1 -0
  114. package/dist/state/index.js +338 -0
  115. package/dist/state/index.js.map +1 -0
  116. package/dist/state/persistence.d.ts +91 -0
  117. package/dist/state/persistence.d.ts.map +1 -0
  118. package/dist/state/persistence.js +201 -0
  119. package/dist/state/persistence.js.map +1 -0
  120. package/dist/types/cli.d.ts +132 -0
  121. package/dist/types/cli.d.ts.map +1 -0
  122. package/dist/types/cli.js +17 -0
  123. package/dist/types/cli.js.map +1 -0
  124. package/dist/types/consensus.d.ts +111 -0
  125. package/dist/types/consensus.d.ts.map +1 -0
  126. package/dist/types/consensus.js +29 -0
  127. package/dist/types/consensus.js.map +1 -0
  128. package/dist/types/index.d.ts +9 -0
  129. package/dist/types/index.d.ts.map +1 -0
  130. package/dist/types/index.js +13 -0
  131. package/dist/types/index.js.map +1 -0
  132. package/dist/types/project.d.ts +73 -0
  133. package/dist/types/project.d.ts.map +1 -0
  134. package/dist/types/project.js +55 -0
  135. package/dist/types/project.js.map +1 -0
  136. package/dist/types/workflow.d.ts +236 -0
  137. package/dist/types/workflow.d.ts.map +1 -0
  138. package/dist/types/workflow.js +74 -0
  139. package/dist/types/workflow.js.map +1 -0
  140. package/dist/workflow/consensus.d.ts +89 -0
  141. package/dist/workflow/consensus.d.ts.map +1 -0
  142. package/dist/workflow/consensus.js +220 -0
  143. package/dist/workflow/consensus.js.map +1 -0
  144. package/dist/workflow/execution-mode.d.ts +82 -0
  145. package/dist/workflow/execution-mode.d.ts.map +1 -0
  146. package/dist/workflow/execution-mode.js +346 -0
  147. package/dist/workflow/execution-mode.js.map +1 -0
  148. package/dist/workflow/index.d.ts +110 -0
  149. package/dist/workflow/index.d.ts.map +1 -0
  150. package/dist/workflow/index.js +283 -0
  151. package/dist/workflow/index.js.map +1 -0
  152. package/dist/workflow/plan-mode.d.ts +83 -0
  153. package/dist/workflow/plan-mode.d.ts.map +1 -0
  154. package/dist/workflow/plan-mode.js +241 -0
  155. package/dist/workflow/plan-mode.js.map +1 -0
  156. package/dist/workflow/test-runner.d.ts +87 -0
  157. package/dist/workflow/test-runner.d.ts.map +1 -0
  158. package/dist/workflow/test-runner.js +273 -0
  159. package/dist/workflow/test-runner.js.map +1 -0
  160. package/eslint.config.js +25 -0
  161. package/package.json +66 -0
  162. package/src/adapters/claude.ts +298 -0
  163. package/src/adapters/openai.ts +300 -0
  164. package/src/auth/claude.ts +166 -0
  165. package/src/auth/index.ts +171 -0
  166. package/src/auth/keychain.ts +138 -0
  167. package/src/auth/openai-entry.ts +410 -0
  168. package/src/auth/openai.ts +260 -0
  169. package/src/auth/server.ts +252 -0
  170. package/src/cli/commands/auth.ts +194 -0
  171. package/src/cli/commands/config.ts +241 -0
  172. package/src/cli/commands/create.ts +308 -0
  173. package/src/cli/commands/index.ts +10 -0
  174. package/src/cli/commands/resume.ts +304 -0
  175. package/src/cli/commands/status.ts +189 -0
  176. package/src/cli/index.ts +90 -0
  177. package/src/cli/interactive.ts +418 -0
  178. package/src/cli/output.ts +410 -0
  179. package/src/config/defaults.ts +114 -0
  180. package/src/config/index.ts +315 -0
  181. package/src/config/schema.ts +164 -0
  182. package/src/generators/index.ts +251 -0
  183. package/src/generators/python.ts +318 -0
  184. package/src/generators/templates/index.ts +6 -0
  185. package/src/generators/templates/python.ts +465 -0
  186. package/src/generators/templates/typescript.ts +417 -0
  187. package/src/generators/typescript.ts +340 -0
  188. package/src/index.ts +13 -0
  189. package/src/state/index.ts +454 -0
  190. package/src/state/persistence.ts +230 -0
  191. package/src/types/cli.ts +146 -0
  192. package/src/types/consensus.ts +116 -0
  193. package/src/types/index.ts +64 -0
  194. package/src/types/project.ts +85 -0
  195. package/src/types/workflow.ts +149 -0
  196. package/src/workflow/consensus.ts +299 -0
  197. package/src/workflow/execution-mode.ts +517 -0
  198. package/src/workflow/index.ts +396 -0
  199. package/src/workflow/plan-mode.ts +356 -0
  200. package/src/workflow/test-runner.ts +345 -0
  201. package/tests/adapters/openai.test.ts +145 -0
  202. package/tests/config/config.test.ts +208 -0
  203. package/tests/generators/generators.test.ts +185 -0
  204. package/tests/types/consensus.test.ts +152 -0
  205. package/tests/types/project.test.ts +134 -0
  206. package/tests/workflow/consensus.test.ts +221 -0
  207. package/tests/workflow/test-runner.test.ts +214 -0
  208. package/tsconfig.json +25 -0
  209. package/vitest.config.ts +22 -0
@@ -0,0 +1,517 @@
1
+ /**
2
+ * Execution Mode workflow
3
+ * Handles task execution, testing, and error recovery
4
+ */
5
+
6
+ import type { ProjectState, Task, Milestone } from '../types/workflow.js';
7
+ import { generateCode, type ClaudeExecuteResult } from '../adapters/claude.js';
8
+ import {
9
+ loadProject,
10
+ updateState,
11
+ updateTaskStatus,
12
+ setCurrentMilestone,
13
+ setCurrentTask,
14
+ getNextTask,
15
+ getProgress,
16
+ completeProject,
17
+ failProject,
18
+ } from '../state/index.js';
19
+ import {
20
+ runTests,
21
+ testsExist,
22
+ getTestSummary,
23
+ type TestResult,
24
+ } from './test-runner.js';
25
+
26
+ /**
27
+ * Options for execution mode
28
+ */
29
+ export interface ExecutionModeOptions {
30
+ projectDir: string;
31
+ maxRetries?: number;
32
+ onProgress?: (phase: string, message: string) => void;
33
+ onTaskStart?: (milestone: Milestone, task: Task) => void;
34
+ onTaskComplete?: (milestone: Milestone, task: Task, success: boolean) => void;
35
+ onTestResult?: (result: TestResult) => void;
36
+ }
37
+
38
+ /**
39
+ * Result of task execution
40
+ */
41
+ export interface TaskExecutionResult {
42
+ success: boolean;
43
+ task: Task;
44
+ response?: string;
45
+ testResult?: TestResult;
46
+ error?: string;
47
+ retries: number;
48
+ }
49
+
50
+ /**
51
+ * Result of execution mode
52
+ */
53
+ export interface ExecutionModeResult {
54
+ success: boolean;
55
+ state: ProjectState;
56
+ completedTasks: number;
57
+ failedTasks: number;
58
+ error?: string;
59
+ }
60
+
61
+ /**
62
+ * Maximum number of retries for failed tests
63
+ */
64
+ const DEFAULT_MAX_RETRIES = 3;
65
+
66
+ /**
67
+ * Build the execution context for a task
68
+ *
69
+ * @param state - Current project state
70
+ * @param milestone - Current milestone
71
+ * @param task - Current task
72
+ * @returns Context string for code generation
73
+ */
74
+ function buildTaskContext(
75
+ state: ProjectState,
76
+ milestone: Milestone,
77
+ _task: Task
78
+ ): string {
79
+ const lines: string[] = [];
80
+
81
+ lines.push(`## Project: ${state.name}`);
82
+ lines.push(`Language: ${state.language}`);
83
+ lines.push('');
84
+
85
+ if (state.specification) {
86
+ lines.push('## Specification');
87
+ lines.push(state.specification.slice(0, 2000));
88
+ lines.push('');
89
+ }
90
+
91
+ if (state.plan) {
92
+ lines.push('## Development Plan');
93
+ lines.push(state.plan.slice(0, 2000));
94
+ lines.push('');
95
+ }
96
+
97
+ lines.push('## Current Milestone');
98
+ lines.push(`Name: ${milestone.name}`);
99
+ lines.push(`Description: ${milestone.description}`);
100
+ lines.push('');
101
+
102
+ // Add completed tasks for context
103
+ const completedTasks = milestone.tasks.filter((t) => t.status === 'complete');
104
+ if (completedTasks.length > 0) {
105
+ lines.push('## Completed Tasks in This Milestone');
106
+ for (const t of completedTasks) {
107
+ lines.push(`- ${t.name}`);
108
+ }
109
+ lines.push('');
110
+ }
111
+
112
+ return lines.join('\n');
113
+ }
114
+
115
+ /**
116
+ * Execute a single task
117
+ *
118
+ * @param task - The task to execute
119
+ * @param context - Execution context
120
+ * @param projectDir - Project directory
121
+ * @returns Execution result
122
+ */
123
+ export async function executeTask(
124
+ task: Task,
125
+ context: string,
126
+ projectDir: string
127
+ ): Promise<ClaudeExecuteResult> {
128
+ const prompt = `
129
+ ## Task
130
+ ${task.name}
131
+
132
+ ## Description
133
+ ${task.description || task.name}
134
+
135
+ ${task.testPlan ? `## Test Requirements\n${task.testPlan}\n` : ''}
136
+
137
+ Please implement this task completely. After implementing:
138
+ 1. Create appropriate tests if needed
139
+ 2. Ensure code follows best practices
140
+ 3. Document any complex logic
141
+ `.trim();
142
+
143
+ return generateCode(prompt, context, { cwd: projectDir });
144
+ }
145
+
146
+ /**
147
+ * Handle a test failure by attempting to fix the code
148
+ *
149
+ * @param task - The failed task
150
+ * @param testResult - The test result
151
+ * @param context - Execution context
152
+ * @param projectDir - Project directory
153
+ * @returns Fix attempt result
154
+ */
155
+ export async function handleTestFailure(
156
+ task: Task,
157
+ testResult: TestResult,
158
+ context: string,
159
+ projectDir: string
160
+ ): Promise<ClaudeExecuteResult> {
161
+ const prompt = `
162
+ ## Test Failure Fix Required
163
+
164
+ The tests for the following task have failed:
165
+
166
+ ### Task
167
+ ${task.name}
168
+
169
+ ### Test Output
170
+ \`\`\`
171
+ ${testResult.output.slice(0, 3000)}
172
+ \`\`\`
173
+
174
+ ### Failed Tests
175
+ ${testResult.failedTests?.map((t) => `- ${t}`).join('\n') || 'See output above'}
176
+
177
+ Please:
178
+ 1. Analyze the test failures
179
+ 2. Fix the code to make all tests pass
180
+ 3. Do NOT modify the tests unless they are incorrect
181
+ `.trim();
182
+
183
+ return generateCode(prompt, context, { cwd: projectDir });
184
+ }
185
+
186
+ /**
187
+ * Execute a task with retry logic for test failures
188
+ *
189
+ * @param milestone - The milestone containing the task
190
+ * @param task - The task to execute
191
+ * @param state - Current project state
192
+ * @param options - Execution options
193
+ * @returns Task execution result
194
+ */
195
+ async function executeTaskWithRetry(
196
+ milestone: Milestone,
197
+ task: Task,
198
+ state: ProjectState,
199
+ options: ExecutionModeOptions
200
+ ): Promise<TaskExecutionResult> {
201
+ const {
202
+ projectDir,
203
+ maxRetries = DEFAULT_MAX_RETRIES,
204
+ onTestResult,
205
+ } = options;
206
+
207
+ const context = buildTaskContext(state, milestone, task);
208
+ let retries = 0;
209
+
210
+ // Execute the task
211
+ const execResult = await executeTask(task, context, projectDir);
212
+
213
+ if (!execResult.success) {
214
+ return {
215
+ success: false,
216
+ task,
217
+ error: execResult.error || 'Task execution failed',
218
+ retries: 0,
219
+ };
220
+ }
221
+
222
+ // Check if tests exist
223
+ const hasTests = await testsExist(projectDir, state.language);
224
+
225
+ if (!hasTests) {
226
+ // No tests to run, mark as complete
227
+ return {
228
+ success: true,
229
+ task,
230
+ response: execResult.response,
231
+ retries: 0,
232
+ };
233
+ }
234
+
235
+ // Run tests with retry loop
236
+ while (retries <= maxRetries) {
237
+ const testResult = await runTests(projectDir, state.language);
238
+
239
+ if (onTestResult) {
240
+ onTestResult(testResult);
241
+ }
242
+
243
+ if (testResult.success) {
244
+ return {
245
+ success: true,
246
+ task,
247
+ response: execResult.response,
248
+ testResult,
249
+ retries,
250
+ };
251
+ }
252
+
253
+ // Tests failed
254
+ if (retries >= maxRetries) {
255
+ return {
256
+ success: false,
257
+ task,
258
+ response: execResult.response,
259
+ testResult,
260
+ error: `Tests failed after ${retries} retries: ${getTestSummary(testResult)}`,
261
+ retries,
262
+ };
263
+ }
264
+
265
+ // Attempt to fix
266
+ retries++;
267
+ const fixResult = await handleTestFailure(task, testResult, context, projectDir);
268
+
269
+ if (!fixResult.success) {
270
+ return {
271
+ success: false,
272
+ task,
273
+ testResult,
274
+ error: `Fix attempt failed: ${fixResult.error}`,
275
+ retries,
276
+ };
277
+ }
278
+ }
279
+
280
+ // Should not reach here
281
+ return {
282
+ success: false,
283
+ task,
284
+ error: 'Unexpected error in retry loop',
285
+ retries,
286
+ };
287
+ }
288
+
289
+ /**
290
+ * Run execution mode for a project
291
+ *
292
+ * @param options - Execution options
293
+ * @returns Execution mode result
294
+ */
295
+ export async function runExecutionMode(
296
+ options: ExecutionModeOptions
297
+ ): Promise<ExecutionModeResult> {
298
+ const {
299
+ projectDir,
300
+ onProgress,
301
+ onTaskStart,
302
+ onTaskComplete,
303
+ } = options;
304
+
305
+ let completedTasks = 0;
306
+ let failedTasks = 0;
307
+
308
+ try {
309
+ let state = await loadProject(projectDir);
310
+
311
+ // Ensure we're in execution phase
312
+ if (state.phase !== 'execution') {
313
+ return {
314
+ success: false,
315
+ state,
316
+ completedTasks: 0,
317
+ failedTasks: 0,
318
+ error: `Cannot run execution mode: project is in ${state.phase} phase`,
319
+ };
320
+ }
321
+
322
+ // Update status
323
+ state = await updateState(projectDir, { status: 'in-progress' });
324
+
325
+ onProgress?.('execution-start', 'Starting execution mode...');
326
+
327
+ // Process tasks
328
+ let nextTask = await getNextTask(projectDir);
329
+
330
+ while (nextTask) {
331
+ const { milestone, task } = nextTask;
332
+
333
+ // Set current milestone and task
334
+ state = await setCurrentMilestone(projectDir, milestone.id);
335
+ state = await setCurrentTask(projectDir, task.id);
336
+
337
+ // Update task to in-progress
338
+ state = await updateTaskStatus(projectDir, task.id, 'in-progress');
339
+
340
+ onProgress?.(
341
+ 'task-start',
342
+ `Executing: ${task.name} (Milestone: ${milestone.name})`
343
+ );
344
+
345
+ if (onTaskStart) {
346
+ onTaskStart(milestone, task);
347
+ }
348
+
349
+ // Execute the task
350
+ const result = await executeTaskWithRetry(
351
+ milestone,
352
+ task,
353
+ state,
354
+ options
355
+ );
356
+
357
+ if (result.success) {
358
+ state = await updateTaskStatus(projectDir, task.id, 'complete', {
359
+ testsPassed: result.testResult?.success,
360
+ });
361
+ completedTasks++;
362
+
363
+ onProgress?.(
364
+ 'task-complete',
365
+ `Completed: ${task.name}${result.testResult ? ` (${getTestSummary(result.testResult)})` : ''}`
366
+ );
367
+ } else {
368
+ state = await updateTaskStatus(projectDir, task.id, 'failed', {
369
+ testsPassed: false,
370
+ error: result.error,
371
+ });
372
+ failedTasks++;
373
+
374
+ onProgress?.(
375
+ 'task-failed',
376
+ `Failed: ${task.name} - ${result.error}`
377
+ );
378
+ }
379
+
380
+ if (onTaskComplete) {
381
+ onTaskComplete(milestone, task, result.success);
382
+ }
383
+
384
+ // Get next task
385
+ nextTask = await getNextTask(projectDir);
386
+ }
387
+
388
+ // Check final status
389
+ const progress = await getProgress(projectDir);
390
+
391
+ if (failedTasks > 0) {
392
+ state = await failProject(
393
+ projectDir,
394
+ `${failedTasks} tasks failed out of ${progress.totalTasks}`
395
+ );
396
+
397
+ return {
398
+ success: false,
399
+ state,
400
+ completedTasks,
401
+ failedTasks,
402
+ error: `Execution completed with ${failedTasks} failed tasks`,
403
+ };
404
+ }
405
+
406
+ // All tasks complete
407
+ state = await completeProject(projectDir);
408
+
409
+ onProgress?.(
410
+ 'execution-complete',
411
+ `Project complete! ${completedTasks} tasks executed successfully.`
412
+ );
413
+
414
+ return {
415
+ success: true,
416
+ state,
417
+ completedTasks,
418
+ failedTasks: 0,
419
+ };
420
+ } catch (error) {
421
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
422
+ onProgress?.('error', errorMessage);
423
+
424
+ return {
425
+ success: false,
426
+ state: await loadProject(projectDir).catch(() => ({} as ProjectState)),
427
+ completedTasks,
428
+ failedTasks,
429
+ error: errorMessage,
430
+ };
431
+ }
432
+ }
433
+
434
+ /**
435
+ * Resume execution mode from where it left off
436
+ *
437
+ * @param options - Execution options
438
+ * @returns Execution mode result
439
+ */
440
+ export async function resumeExecutionMode(
441
+ options: ExecutionModeOptions
442
+ ): Promise<ExecutionModeResult> {
443
+ const state = await loadProject(options.projectDir);
444
+
445
+ // Check current status
446
+ if (state.status === 'complete') {
447
+ return {
448
+ success: true,
449
+ state,
450
+ completedTasks: state.milestones.flatMap((m) => m.tasks).filter((t) => t.status === 'complete').length,
451
+ failedTasks: 0,
452
+ };
453
+ }
454
+
455
+ // Reset failed tasks to pending so they can be retried
456
+ if (state.status === 'failed') {
457
+ const updatedMilestones = state.milestones.map((m) => ({
458
+ ...m,
459
+ tasks: m.tasks.map((t) =>
460
+ t.status === 'failed' ? { ...t, status: 'pending' as const, error: undefined } : t
461
+ ),
462
+ }));
463
+
464
+ await updateState(options.projectDir, {
465
+ milestones: updatedMilestones,
466
+ status: 'pending',
467
+ error: undefined,
468
+ });
469
+ }
470
+
471
+ return runExecutionMode(options);
472
+ }
473
+
474
+ /**
475
+ * Execute a single task by ID
476
+ *
477
+ * @param projectDir - Project directory
478
+ * @param taskId - Task ID to execute
479
+ * @param options - Execution options
480
+ * @returns Task execution result
481
+ */
482
+ export async function executeSingleTask(
483
+ projectDir: string,
484
+ taskId: string,
485
+ options: Partial<ExecutionModeOptions> = {}
486
+ ): Promise<TaskExecutionResult> {
487
+ const state = await loadProject(projectDir);
488
+
489
+ // Find the task
490
+ let foundTask: Task | undefined;
491
+ let foundMilestone: Milestone | undefined;
492
+
493
+ for (const milestone of state.milestones) {
494
+ const task = milestone.tasks.find((t) => t.id === taskId);
495
+ if (task) {
496
+ foundTask = task;
497
+ foundMilestone = milestone;
498
+ break;
499
+ }
500
+ }
501
+
502
+ if (!foundTask || !foundMilestone) {
503
+ return {
504
+ success: false,
505
+ task: { id: taskId, name: 'Unknown', status: 'pending', description: '' },
506
+ error: `Task ${taskId} not found`,
507
+ retries: 0,
508
+ };
509
+ }
510
+
511
+ return executeTaskWithRetry(
512
+ foundMilestone,
513
+ foundTask,
514
+ state,
515
+ { projectDir, ...options }
516
+ );
517
+ }