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.
- package/.env.example +25 -0
- package/.prettierrc +8 -0
- package/README.md +320 -0
- package/dist/adapters/claude.d.ts +82 -0
- package/dist/adapters/claude.d.ts.map +1 -0
- package/dist/adapters/claude.js +230 -0
- package/dist/adapters/claude.js.map +1 -0
- package/dist/adapters/openai.d.ts +48 -0
- package/dist/adapters/openai.d.ts.map +1 -0
- package/dist/adapters/openai.js +257 -0
- package/dist/adapters/openai.js.map +1 -0
- package/dist/auth/claude.d.ts +44 -0
- package/dist/auth/claude.d.ts.map +1 -0
- package/dist/auth/claude.js +139 -0
- package/dist/auth/claude.js.map +1 -0
- package/dist/auth/index.d.ts +61 -0
- package/dist/auth/index.d.ts.map +1 -0
- package/dist/auth/index.js +141 -0
- package/dist/auth/index.js.map +1 -0
- package/dist/auth/keychain.d.ts +66 -0
- package/dist/auth/keychain.d.ts.map +1 -0
- package/dist/auth/keychain.js +125 -0
- package/dist/auth/keychain.js.map +1 -0
- package/dist/auth/openai-entry.d.ts +9 -0
- package/dist/auth/openai-entry.d.ts.map +1 -0
- package/dist/auth/openai-entry.js +410 -0
- package/dist/auth/openai-entry.js.map +1 -0
- package/dist/auth/openai.d.ts +71 -0
- package/dist/auth/openai.d.ts.map +1 -0
- package/dist/auth/openai.js +212 -0
- package/dist/auth/openai.js.map +1 -0
- package/dist/auth/server.d.ts +32 -0
- package/dist/auth/server.d.ts.map +1 -0
- package/dist/auth/server.js +213 -0
- package/dist/auth/server.js.map +1 -0
- package/dist/cli/commands/auth.d.ts +10 -0
- package/dist/cli/commands/auth.d.ts.map +1 -0
- package/dist/cli/commands/auth.js +162 -0
- package/dist/cli/commands/auth.js.map +1 -0
- package/dist/cli/commands/config.d.ts +10 -0
- package/dist/cli/commands/config.d.ts.map +1 -0
- package/dist/cli/commands/config.js +215 -0
- package/dist/cli/commands/config.js.map +1 -0
- package/dist/cli/commands/create.d.ts +10 -0
- package/dist/cli/commands/create.d.ts.map +1 -0
- package/dist/cli/commands/create.js +240 -0
- package/dist/cli/commands/create.js.map +1 -0
- package/dist/cli/commands/index.d.ts +10 -0
- package/dist/cli/commands/index.d.ts.map +1 -0
- package/dist/cli/commands/index.js +10 -0
- package/dist/cli/commands/index.js.map +1 -0
- package/dist/cli/commands/resume.d.ts +18 -0
- package/dist/cli/commands/resume.d.ts.map +1 -0
- package/dist/cli/commands/resume.js +241 -0
- package/dist/cli/commands/resume.js.map +1 -0
- package/dist/cli/commands/status.d.ts +18 -0
- package/dist/cli/commands/status.d.ts.map +1 -0
- package/dist/cli/commands/status.js +154 -0
- package/dist/cli/commands/status.js.map +1 -0
- package/dist/cli/index.d.ts +17 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +71 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/interactive.d.ts +9 -0
- package/dist/cli/interactive.d.ts.map +1 -0
- package/dist/cli/interactive.js +330 -0
- package/dist/cli/interactive.js.map +1 -0
- package/dist/cli/output.d.ts +182 -0
- package/dist/cli/output.d.ts.map +1 -0
- package/dist/cli/output.js +355 -0
- package/dist/cli/output.js.map +1 -0
- package/dist/config/defaults.d.ts +57 -0
- package/dist/config/defaults.d.ts.map +1 -0
- package/dist/config/defaults.js +103 -0
- package/dist/config/defaults.js.map +1 -0
- package/dist/config/index.d.ts +138 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +244 -0
- package/dist/config/index.js.map +1 -0
- package/dist/config/schema.d.ts +220 -0
- package/dist/config/schema.d.ts.map +1 -0
- package/dist/config/schema.js +141 -0
- package/dist/config/schema.js.map +1 -0
- package/dist/generators/index.d.ts +101 -0
- package/dist/generators/index.d.ts.map +1 -0
- package/dist/generators/index.js +200 -0
- package/dist/generators/index.js.map +1 -0
- package/dist/generators/python.d.ts +48 -0
- package/dist/generators/python.d.ts.map +1 -0
- package/dist/generators/python.js +262 -0
- package/dist/generators/python.js.map +1 -0
- package/dist/generators/templates/index.d.ts +6 -0
- package/dist/generators/templates/index.d.ts.map +1 -0
- package/dist/generators/templates/index.js +6 -0
- package/dist/generators/templates/index.js.map +1 -0
- package/dist/generators/templates/python.d.ts +53 -0
- package/dist/generators/templates/python.d.ts.map +1 -0
- package/dist/generators/templates/python.js +454 -0
- package/dist/generators/templates/python.js.map +1 -0
- package/dist/generators/templates/typescript.d.ts +53 -0
- package/dist/generators/templates/typescript.d.ts.map +1 -0
- package/dist/generators/templates/typescript.js +394 -0
- package/dist/generators/templates/typescript.js.map +1 -0
- package/dist/generators/typescript.d.ts +64 -0
- package/dist/generators/typescript.d.ts.map +1 -0
- package/dist/generators/typescript.js +271 -0
- package/dist/generators/typescript.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +12 -0
- package/dist/index.js.map +1 -0
- package/dist/state/index.d.ts +168 -0
- package/dist/state/index.d.ts.map +1 -0
- package/dist/state/index.js +338 -0
- package/dist/state/index.js.map +1 -0
- package/dist/state/persistence.d.ts +91 -0
- package/dist/state/persistence.d.ts.map +1 -0
- package/dist/state/persistence.js +201 -0
- package/dist/state/persistence.js.map +1 -0
- package/dist/types/cli.d.ts +132 -0
- package/dist/types/cli.d.ts.map +1 -0
- package/dist/types/cli.js +17 -0
- package/dist/types/cli.js.map +1 -0
- package/dist/types/consensus.d.ts +111 -0
- package/dist/types/consensus.d.ts.map +1 -0
- package/dist/types/consensus.js +29 -0
- package/dist/types/consensus.js.map +1 -0
- package/dist/types/index.d.ts +9 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +13 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/project.d.ts +73 -0
- package/dist/types/project.d.ts.map +1 -0
- package/dist/types/project.js +55 -0
- package/dist/types/project.js.map +1 -0
- package/dist/types/workflow.d.ts +236 -0
- package/dist/types/workflow.d.ts.map +1 -0
- package/dist/types/workflow.js +74 -0
- package/dist/types/workflow.js.map +1 -0
- package/dist/workflow/consensus.d.ts +89 -0
- package/dist/workflow/consensus.d.ts.map +1 -0
- package/dist/workflow/consensus.js +220 -0
- package/dist/workflow/consensus.js.map +1 -0
- package/dist/workflow/execution-mode.d.ts +82 -0
- package/dist/workflow/execution-mode.d.ts.map +1 -0
- package/dist/workflow/execution-mode.js +346 -0
- package/dist/workflow/execution-mode.js.map +1 -0
- package/dist/workflow/index.d.ts +110 -0
- package/dist/workflow/index.d.ts.map +1 -0
- package/dist/workflow/index.js +283 -0
- package/dist/workflow/index.js.map +1 -0
- package/dist/workflow/plan-mode.d.ts +83 -0
- package/dist/workflow/plan-mode.d.ts.map +1 -0
- package/dist/workflow/plan-mode.js +241 -0
- package/dist/workflow/plan-mode.js.map +1 -0
- package/dist/workflow/test-runner.d.ts +87 -0
- package/dist/workflow/test-runner.d.ts.map +1 -0
- package/dist/workflow/test-runner.js +273 -0
- package/dist/workflow/test-runner.js.map +1 -0
- package/eslint.config.js +25 -0
- package/package.json +66 -0
- package/src/adapters/claude.ts +298 -0
- package/src/adapters/openai.ts +300 -0
- package/src/auth/claude.ts +166 -0
- package/src/auth/index.ts +171 -0
- package/src/auth/keychain.ts +138 -0
- package/src/auth/openai-entry.ts +410 -0
- package/src/auth/openai.ts +260 -0
- package/src/auth/server.ts +252 -0
- package/src/cli/commands/auth.ts +194 -0
- package/src/cli/commands/config.ts +241 -0
- package/src/cli/commands/create.ts +308 -0
- package/src/cli/commands/index.ts +10 -0
- package/src/cli/commands/resume.ts +304 -0
- package/src/cli/commands/status.ts +189 -0
- package/src/cli/index.ts +90 -0
- package/src/cli/interactive.ts +418 -0
- package/src/cli/output.ts +410 -0
- package/src/config/defaults.ts +114 -0
- package/src/config/index.ts +315 -0
- package/src/config/schema.ts +164 -0
- package/src/generators/index.ts +251 -0
- package/src/generators/python.ts +318 -0
- package/src/generators/templates/index.ts +6 -0
- package/src/generators/templates/python.ts +465 -0
- package/src/generators/templates/typescript.ts +417 -0
- package/src/generators/typescript.ts +340 -0
- package/src/index.ts +13 -0
- package/src/state/index.ts +454 -0
- package/src/state/persistence.ts +230 -0
- package/src/types/cli.ts +146 -0
- package/src/types/consensus.ts +116 -0
- package/src/types/index.ts +64 -0
- package/src/types/project.ts +85 -0
- package/src/types/workflow.ts +149 -0
- package/src/workflow/consensus.ts +299 -0
- package/src/workflow/execution-mode.ts +517 -0
- package/src/workflow/index.ts +396 -0
- package/src/workflow/plan-mode.ts +356 -0
- package/src/workflow/test-runner.ts +345 -0
- package/tests/adapters/openai.test.ts +145 -0
- package/tests/config/config.test.ts +208 -0
- package/tests/generators/generators.test.ts +185 -0
- package/tests/types/consensus.test.ts +152 -0
- package/tests/types/project.test.ts +134 -0
- package/tests/workflow/consensus.test.ts +221 -0
- package/tests/workflow/test-runner.test.ts +214 -0
- package/tsconfig.json +25 -0
- 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
|
+
}
|