popeye-cli 1.0.0 → 1.1.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 (171) hide show
  1. package/README.md +521 -125
  2. package/dist/adapters/claude.d.ts +16 -4
  3. package/dist/adapters/claude.d.ts.map +1 -1
  4. package/dist/adapters/claude.js +679 -33
  5. package/dist/adapters/claude.js.map +1 -1
  6. package/dist/adapters/gemini.d.ts +55 -0
  7. package/dist/adapters/gemini.d.ts.map +1 -0
  8. package/dist/adapters/gemini.js +318 -0
  9. package/dist/adapters/gemini.js.map +1 -0
  10. package/dist/adapters/openai.d.ts.map +1 -1
  11. package/dist/adapters/openai.js +41 -7
  12. package/dist/adapters/openai.js.map +1 -1
  13. package/dist/auth/claude.d.ts +11 -9
  14. package/dist/auth/claude.d.ts.map +1 -1
  15. package/dist/auth/claude.js +107 -71
  16. package/dist/auth/claude.js.map +1 -1
  17. package/dist/auth/gemini.d.ts +58 -0
  18. package/dist/auth/gemini.d.ts.map +1 -0
  19. package/dist/auth/gemini.js +172 -0
  20. package/dist/auth/gemini.js.map +1 -0
  21. package/dist/auth/index.d.ts +11 -7
  22. package/dist/auth/index.d.ts.map +1 -1
  23. package/dist/auth/index.js +23 -5
  24. package/dist/auth/index.js.map +1 -1
  25. package/dist/auth/keychain.d.ts +20 -7
  26. package/dist/auth/keychain.d.ts.map +1 -1
  27. package/dist/auth/keychain.js +85 -29
  28. package/dist/auth/keychain.js.map +1 -1
  29. package/dist/auth/openai.d.ts +2 -2
  30. package/dist/auth/openai.d.ts.map +1 -1
  31. package/dist/auth/openai.js +30 -32
  32. package/dist/auth/openai.js.map +1 -1
  33. package/dist/cli/index.d.ts.map +1 -1
  34. package/dist/cli/index.js +4 -7
  35. package/dist/cli/index.js.map +1 -1
  36. package/dist/cli/interactive.d.ts +2 -2
  37. package/dist/cli/interactive.d.ts.map +1 -1
  38. package/dist/cli/interactive.js +1380 -183
  39. package/dist/cli/interactive.js.map +1 -1
  40. package/dist/config/defaults.d.ts +6 -1
  41. package/dist/config/defaults.d.ts.map +1 -1
  42. package/dist/config/defaults.js +10 -2
  43. package/dist/config/defaults.js.map +1 -1
  44. package/dist/config/index.d.ts +10 -0
  45. package/dist/config/index.d.ts.map +1 -1
  46. package/dist/config/index.js +19 -0
  47. package/dist/config/index.js.map +1 -1
  48. package/dist/config/schema.d.ts +20 -0
  49. package/dist/config/schema.d.ts.map +1 -1
  50. package/dist/config/schema.js +7 -0
  51. package/dist/config/schema.js.map +1 -1
  52. package/dist/generators/python.d.ts.map +1 -1
  53. package/dist/generators/python.js +1 -0
  54. package/dist/generators/python.js.map +1 -1
  55. package/dist/generators/typescript.d.ts.map +1 -1
  56. package/dist/generators/typescript.js +1 -0
  57. package/dist/generators/typescript.js.map +1 -1
  58. package/dist/state/index.d.ts +108 -0
  59. package/dist/state/index.d.ts.map +1 -1
  60. package/dist/state/index.js +551 -4
  61. package/dist/state/index.js.map +1 -1
  62. package/dist/state/registry.d.ts +52 -0
  63. package/dist/state/registry.d.ts.map +1 -0
  64. package/dist/state/registry.js +215 -0
  65. package/dist/state/registry.js.map +1 -0
  66. package/dist/types/cli.d.ts +4 -0
  67. package/dist/types/cli.d.ts.map +1 -1
  68. package/dist/types/cli.js.map +1 -1
  69. package/dist/types/consensus.d.ts +69 -4
  70. package/dist/types/consensus.d.ts.map +1 -1
  71. package/dist/types/consensus.js +24 -3
  72. package/dist/types/consensus.js.map +1 -1
  73. package/dist/types/workflow.d.ts +55 -0
  74. package/dist/types/workflow.d.ts.map +1 -1
  75. package/dist/types/workflow.js +16 -0
  76. package/dist/types/workflow.js.map +1 -1
  77. package/dist/workflow/auto-fix.d.ts +45 -0
  78. package/dist/workflow/auto-fix.d.ts.map +1 -0
  79. package/dist/workflow/auto-fix.js +274 -0
  80. package/dist/workflow/auto-fix.js.map +1 -0
  81. package/dist/workflow/consensus.d.ts +44 -2
  82. package/dist/workflow/consensus.d.ts.map +1 -1
  83. package/dist/workflow/consensus.js +565 -17
  84. package/dist/workflow/consensus.js.map +1 -1
  85. package/dist/workflow/execution-mode.d.ts +10 -4
  86. package/dist/workflow/execution-mode.d.ts.map +1 -1
  87. package/dist/workflow/execution-mode.js +547 -58
  88. package/dist/workflow/execution-mode.js.map +1 -1
  89. package/dist/workflow/index.d.ts +14 -2
  90. package/dist/workflow/index.d.ts.map +1 -1
  91. package/dist/workflow/index.js +69 -6
  92. package/dist/workflow/index.js.map +1 -1
  93. package/dist/workflow/milestone-workflow.d.ts +34 -0
  94. package/dist/workflow/milestone-workflow.d.ts.map +1 -0
  95. package/dist/workflow/milestone-workflow.js +414 -0
  96. package/dist/workflow/milestone-workflow.js.map +1 -0
  97. package/dist/workflow/plan-mode.d.ts +14 -1
  98. package/dist/workflow/plan-mode.d.ts.map +1 -1
  99. package/dist/workflow/plan-mode.js +589 -47
  100. package/dist/workflow/plan-mode.js.map +1 -1
  101. package/dist/workflow/plan-storage.d.ts +142 -0
  102. package/dist/workflow/plan-storage.d.ts.map +1 -0
  103. package/dist/workflow/plan-storage.js +331 -0
  104. package/dist/workflow/plan-storage.js.map +1 -0
  105. package/dist/workflow/project-verification.d.ts +37 -0
  106. package/dist/workflow/project-verification.d.ts.map +1 -0
  107. package/dist/workflow/project-verification.js +381 -0
  108. package/dist/workflow/project-verification.js.map +1 -0
  109. package/dist/workflow/task-workflow.d.ts +37 -0
  110. package/dist/workflow/task-workflow.d.ts.map +1 -0
  111. package/dist/workflow/task-workflow.js +383 -0
  112. package/dist/workflow/task-workflow.js.map +1 -0
  113. package/dist/workflow/test-runner.d.ts +1 -0
  114. package/dist/workflow/test-runner.d.ts.map +1 -1
  115. package/dist/workflow/test-runner.js +9 -5
  116. package/dist/workflow/test-runner.js.map +1 -1
  117. package/dist/workflow/ui-designer.d.ts +82 -0
  118. package/dist/workflow/ui-designer.d.ts.map +1 -0
  119. package/dist/workflow/ui-designer.js +234 -0
  120. package/dist/workflow/ui-designer.js.map +1 -0
  121. package/dist/workflow/ui-setup.d.ts +58 -0
  122. package/dist/workflow/ui-setup.d.ts.map +1 -0
  123. package/dist/workflow/ui-setup.js +685 -0
  124. package/dist/workflow/ui-setup.js.map +1 -0
  125. package/dist/workflow/ui-verification.d.ts +114 -0
  126. package/dist/workflow/ui-verification.d.ts.map +1 -0
  127. package/dist/workflow/ui-verification.js +258 -0
  128. package/dist/workflow/ui-verification.js.map +1 -0
  129. package/dist/workflow/workflow-logger.d.ts +110 -0
  130. package/dist/workflow/workflow-logger.d.ts.map +1 -0
  131. package/dist/workflow/workflow-logger.js +267 -0
  132. package/dist/workflow/workflow-logger.js.map +1 -0
  133. package/package.json +2 -2
  134. package/src/adapters/claude.ts +815 -34
  135. package/src/adapters/gemini.ts +373 -0
  136. package/src/adapters/openai.ts +40 -7
  137. package/src/auth/claude.ts +120 -78
  138. package/src/auth/gemini.ts +207 -0
  139. package/src/auth/index.ts +28 -8
  140. package/src/auth/keychain.ts +95 -28
  141. package/src/auth/openai.ts +29 -36
  142. package/src/cli/index.ts +4 -7
  143. package/src/cli/interactive.ts +1641 -216
  144. package/src/config/defaults.ts +10 -2
  145. package/src/config/index.ts +21 -0
  146. package/src/config/schema.ts +7 -0
  147. package/src/generators/python.ts +1 -0
  148. package/src/generators/typescript.ts +1 -0
  149. package/src/state/index.ts +713 -4
  150. package/src/state/registry.ts +278 -0
  151. package/src/types/cli.ts +4 -0
  152. package/src/types/consensus.ts +65 -6
  153. package/src/types/workflow.ts +35 -0
  154. package/src/workflow/auto-fix.ts +340 -0
  155. package/src/workflow/consensus.ts +750 -16
  156. package/src/workflow/execution-mode.ts +673 -74
  157. package/src/workflow/index.ts +95 -6
  158. package/src/workflow/milestone-workflow.ts +576 -0
  159. package/src/workflow/plan-mode.ts +696 -50
  160. package/src/workflow/plan-storage.ts +482 -0
  161. package/src/workflow/project-verification.ts +471 -0
  162. package/src/workflow/task-workflow.ts +525 -0
  163. package/src/workflow/test-runner.ts +10 -5
  164. package/src/workflow/ui-designer.ts +337 -0
  165. package/src/workflow/ui-setup.ts +797 -0
  166. package/src/workflow/ui-verification.ts +357 -0
  167. package/src/workflow/workflow-logger.ts +353 -0
  168. package/tests/config/config.test.ts +1 -1
  169. package/tests/types/consensus.test.ts +3 -3
  170. package/tests/workflow/plan-mode.test.ts +213 -0
  171. package/tests/workflow/test-runner.test.ts +5 -3
@@ -0,0 +1,525 @@
1
+ /**
2
+ * Task-level workflow
3
+ * Handles the per-task consensus cycle: Plan → Consensus → Implement → Test
4
+ */
5
+
6
+ import { promises as fs } from 'node:fs';
7
+ import path from 'node:path';
8
+ import type { ProjectState, Task, Milestone } from '../types/workflow.js';
9
+ import type { ConsensusConfig } from '../types/consensus.js';
10
+ import { createPlan as claudeCreatePlan } from '../adapters/claude.js';
11
+ import {
12
+ loadProject,
13
+ updateState,
14
+ } from '../state/index.js';
15
+ import { iterateUntilConsensus, runOptimizedConsensusProcess, type ConsensusProcessResult } from './consensus.js';
16
+ import { executeTask as executeTaskCode } from './execution-mode.js';
17
+ import { runTests, testsExist, getTestSummary, type TestResult } from './test-runner.js';
18
+
19
+ /**
20
+ * Options for task workflow
21
+ */
22
+ export interface TaskWorkflowOptions {
23
+ projectDir: string;
24
+ consensusConfig?: Partial<ConsensusConfig>;
25
+ maxRetries?: number;
26
+ onProgress?: (phase: string, message: string) => void;
27
+ }
28
+
29
+ /**
30
+ * Result of task workflow
31
+ */
32
+ export interface TaskWorkflowResult {
33
+ success: boolean;
34
+ task: Task;
35
+ consensusResult?: ConsensusProcessResult;
36
+ testResult?: TestResult;
37
+ error?: string;
38
+ }
39
+
40
+ /**
41
+ * Create a detailed implementation plan for a specific task
42
+ *
43
+ * @param task - The task to plan
44
+ * @param milestone - The parent milestone
45
+ * @param state - Current project state
46
+ * @param onProgress - Progress callback
47
+ * @returns The task implementation plan
48
+ */
49
+ async function createTaskPlan(
50
+ task: Task,
51
+ milestone: Milestone,
52
+ state: ProjectState,
53
+ onProgress?: (message: string) => void
54
+ ): Promise<string> {
55
+ onProgress?.('Creating detailed task plan...');
56
+
57
+ const context = `
58
+ ## Project Context
59
+ Project: ${state.name}
60
+ Language: ${state.language}
61
+
62
+ ## Milestone: ${milestone.name}
63
+ ${milestone.description}
64
+
65
+ ## Overall Project Plan
66
+ ${state.plan?.slice(0, 2000) || 'No overall plan available'}
67
+
68
+ ## Completed Tasks in This Milestone
69
+ ${milestone.tasks
70
+ .filter(t => t.status === 'complete')
71
+ .map(t => `- ${t.name}`)
72
+ .join('\n') || 'None yet'}
73
+ `.trim();
74
+
75
+ const prompt = `
76
+ Create a detailed implementation plan for the following task:
77
+
78
+ ## Task: ${task.name}
79
+ ${task.description}
80
+
81
+ ${task.testPlan ? `## Test Requirements\n${task.testPlan}` : ''}
82
+
83
+ Please provide:
84
+ 1. **Implementation Steps**: Specific code changes needed
85
+ 2. **Files to Create/Modify**: List all files that will be touched
86
+ 3. **Dependencies**: Any packages or modules needed
87
+ 4. **Acceptance Criteria**: How to verify the task is complete
88
+ 5. **Test Plan**: Specific tests to write
89
+
90
+ Be specific and actionable. This plan will be reviewed for consensus before implementation.
91
+ `.trim();
92
+
93
+ const result = await claudeCreatePlan(prompt, context, onProgress);
94
+
95
+ if (!result.success) {
96
+ throw new Error(`Failed to create task plan: ${result.error}`);
97
+ }
98
+
99
+ return result.response;
100
+ }
101
+
102
+ /**
103
+ * Document a task plan to the docs folder
104
+ *
105
+ * @param projectDir - Project directory
106
+ * @param milestone - Parent milestone
107
+ * @param task - The task
108
+ * @param plan - The plan content
109
+ * @param consensusResult - The consensus result
110
+ * @returns Path to the document
111
+ */
112
+ async function documentTaskPlan(
113
+ projectDir: string,
114
+ milestone: Milestone,
115
+ task: Task,
116
+ plan: string,
117
+ consensusResult: ConsensusProcessResult
118
+ ): Promise<string> {
119
+ const docsDir = path.join(projectDir, 'docs', 'tasks');
120
+ await fs.mkdir(docsDir, { recursive: true });
121
+
122
+ const milestoneNum = milestone.id.replace('milestone-', '');
123
+ const taskNum = task.id.split('-task-')[1] || '1';
124
+ const filename = `milestone_${milestoneNum}_task_${taskNum}_plan.md`;
125
+ const docPath = path.join(docsDir, filename);
126
+
127
+ const content = `# Task Plan: ${task.name}
128
+
129
+ ## Metadata
130
+ - **Milestone**: ${milestone.name}
131
+ - **Task ID**: ${task.id}
132
+ - **Consensus Score**: ${consensusResult.finalScore}%
133
+ - **Iterations**: ${consensusResult.totalIterations}
134
+ - **Status**: ${consensusResult.approved ? 'APPROVED' : 'NOT APPROVED'}
135
+ - **Generated**: ${new Date().toISOString()}
136
+
137
+ ## Task Description
138
+ ${task.description}
139
+
140
+ ## Implementation Plan
141
+ ${plan}
142
+
143
+ ${consensusResult.finalConcerns.length > 0 ? `
144
+ ## Notes from Review
145
+ ${consensusResult.finalConcerns.map(c => `- ${c}`).join('\n')}
146
+ ` : ''}
147
+ `;
148
+
149
+ await fs.writeFile(docPath, content, 'utf-8');
150
+ return `docs/tasks/${filename}`;
151
+ }
152
+
153
+ /**
154
+ * Document task test results
155
+ *
156
+ * @param projectDir - Project directory
157
+ * @param milestone - Parent milestone
158
+ * @param task - The task
159
+ * @param testResult - Test results
160
+ * @returns Path to the document
161
+ */
162
+ async function documentTestResults(
163
+ projectDir: string,
164
+ milestone: Milestone,
165
+ task: Task,
166
+ testResult: TestResult
167
+ ): Promise<string> {
168
+ const docsDir = path.join(projectDir, 'docs', 'tests');
169
+ await fs.mkdir(docsDir, { recursive: true });
170
+
171
+ const milestoneNum = milestone.id.replace('milestone-', '');
172
+ const taskNum = task.id.split('-task-')[1] || '1';
173
+ const filename = `milestone_${milestoneNum}_task_${taskNum}_tests.md`;
174
+ const docPath = path.join(docsDir, filename);
175
+
176
+ const content = `# Test Results: ${task.name}
177
+
178
+ ## Summary
179
+ - **Status**: ${testResult.success ? 'PASSED' : 'FAILED'}
180
+ - **Total Tests**: ${testResult.total}
181
+ - **Passed**: ${testResult.passed}
182
+ - **Failed**: ${testResult.failed}
183
+ - **Execution Time**: ${new Date().toISOString()}
184
+
185
+ ## Task Details
186
+ - **Milestone**: ${milestone.name}
187
+ - **Task ID**: ${task.id}
188
+
189
+ ## Test Output
190
+ \`\`\`
191
+ ${testResult.output.slice(0, 5000)}
192
+ \`\`\`
193
+
194
+ ${testResult.failedTests && testResult.failedTests.length > 0 ? `
195
+ ## Failed Tests
196
+ ${testResult.failedTests.map(t => `- ${t}`).join('\n')}
197
+ ` : ''}
198
+ `;
199
+
200
+ await fs.writeFile(docPath, content, 'utf-8');
201
+ return `docs/tests/${filename}`;
202
+ }
203
+
204
+ /**
205
+ * Update task in state with new data
206
+ */
207
+ async function updateTaskInState(
208
+ projectDir: string,
209
+ taskId: string,
210
+ updates: Partial<Task>
211
+ ): Promise<ProjectState> {
212
+ const state = await loadProject(projectDir);
213
+
214
+ const updatedMilestones = state.milestones.map(milestone => ({
215
+ ...milestone,
216
+ tasks: milestone.tasks.map(task =>
217
+ task.id === taskId ? { ...task, ...updates } : task
218
+ ),
219
+ }));
220
+
221
+ return updateState(projectDir, { milestones: updatedMilestones });
222
+ }
223
+
224
+ /**
225
+ * Run the complete task workflow: Plan → Consensus → Implement → Test
226
+ *
227
+ * @param milestone - The parent milestone
228
+ * @param task - The task to execute
229
+ * @param options - Workflow options
230
+ * @returns Task workflow result
231
+ */
232
+ export async function runTaskWorkflow(
233
+ milestone: Milestone,
234
+ task: Task,
235
+ options: TaskWorkflowOptions
236
+ ): Promise<TaskWorkflowResult> {
237
+ const {
238
+ projectDir,
239
+ consensusConfig,
240
+ maxRetries = 3,
241
+ onProgress,
242
+ } = options;
243
+
244
+ try {
245
+ let state = await loadProject(projectDir);
246
+
247
+ // Reload task from state to get latest data (for resume scenarios)
248
+ const currentMilestone = state.milestones.find(m => m.id === milestone.id);
249
+ const currentTask = currentMilestone?.tasks.find(t => t.id === task.id);
250
+ if (currentTask) {
251
+ task = currentTask;
252
+ }
253
+
254
+ // Check if we're resuming from a previous attempt
255
+ const hasApprovedPlan = task.consensusApproved && task.plan;
256
+ const hasCompletedImplementation = task.implementationComplete;
257
+
258
+ // Mark task as in-progress
259
+ state = await updateTaskInState(projectDir, task.id, {
260
+ status: 'in-progress',
261
+ error: undefined, // Clear previous error
262
+ });
263
+
264
+ let consensusResult: ConsensusProcessResult;
265
+
266
+ // ============================================
267
+ // PHASE 1-2: Plan and Consensus (skip if already approved)
268
+ // ============================================
269
+ if (hasApprovedPlan) {
270
+ onProgress?.('task-plan', `Using existing approved plan for: ${task.name} (Score: ${task.consensusScore}%)`);
271
+
272
+ // Create mock consensus result from saved data
273
+ consensusResult = {
274
+ approved: true,
275
+ finalPlan: task.plan!,
276
+ finalScore: task.consensusScore || 95,
277
+ bestPlan: task.plan!,
278
+ bestScore: task.consensusScore || 95,
279
+ bestIteration: task.consensusIterations || 1,
280
+ totalIterations: task.consensusIterations || 1,
281
+ iterations: [],
282
+ finalConcerns: [],
283
+ finalRecommendations: [],
284
+ arbitrated: false,
285
+ };
286
+ } else {
287
+ // ============================================
288
+ // PHASE 1: Create Task Plan
289
+ // ============================================
290
+ onProgress?.('task-plan', `Planning task: ${task.name}`);
291
+
292
+ const taskPlan = await createTaskPlan(
293
+ task,
294
+ milestone,
295
+ state,
296
+ (msg) => onProgress?.('task-plan', msg)
297
+ );
298
+
299
+ // Store plan in task
300
+ state = await updateTaskInState(projectDir, task.id, {
301
+ plan: taskPlan,
302
+ });
303
+
304
+ // ============================================
305
+ // PHASE 2: Get Consensus on Task Plan
306
+ // ============================================
307
+ onProgress?.('task-consensus', `Getting consensus for task: ${task.name}`);
308
+
309
+ const context = `
310
+ Project: ${state.name}
311
+ Language: ${state.language}
312
+ Milestone: ${milestone.name}
313
+ Task: ${task.name}
314
+ `.trim();
315
+
316
+ // Use optimized consensus with batched feedback and plan storage
317
+ const useOptimized = consensusConfig?.useOptimizedConsensus !== false;
318
+
319
+ if (useOptimized) {
320
+ onProgress?.('task-consensus', `Using optimized consensus (batched feedback, file-based tracking)`);
321
+ consensusResult = await runOptimizedConsensusProcess(
322
+ taskPlan,
323
+ context,
324
+ {
325
+ projectDir,
326
+ config: consensusConfig,
327
+ milestoneId: milestone.id,
328
+ milestoneName: milestone.name,
329
+ taskId: task.id,
330
+ taskName: task.name,
331
+ parallelReviews: true,
332
+ onIteration: (iteration, result) => {
333
+ onProgress?.('task-consensus', `Iteration ${iteration}: ${result.score}%`);
334
+ },
335
+ onProgress,
336
+ }
337
+ );
338
+ } else {
339
+ // Fallback to original consensus
340
+ consensusResult = await iterateUntilConsensus(
341
+ taskPlan,
342
+ context,
343
+ {
344
+ projectDir,
345
+ config: consensusConfig,
346
+ onIteration: (iteration, result) => {
347
+ onProgress?.('task-consensus', `Iteration ${iteration}: ${result.score}%`);
348
+ },
349
+ onProgress,
350
+ }
351
+ );
352
+ }
353
+
354
+ // Document the task plan
355
+ const planDocPath = await documentTaskPlan(
356
+ projectDir,
357
+ milestone,
358
+ task,
359
+ consensusResult.bestPlan,
360
+ consensusResult
361
+ );
362
+
363
+ // Update task with consensus results
364
+ state = await updateTaskInState(projectDir, task.id, {
365
+ plan: consensusResult.bestPlan,
366
+ consensusScore: consensusResult.finalScore,
367
+ consensusIterations: consensusResult.totalIterations,
368
+ consensusApproved: consensusResult.approved,
369
+ planDoc: planDocPath,
370
+ });
371
+
372
+ // Check if consensus was achieved
373
+ if (!consensusResult.approved) {
374
+ onProgress?.('task-consensus', `Consensus not reached for task: ${task.name} (${consensusResult.finalScore}%)`);
375
+
376
+ state = await updateTaskInState(projectDir, task.id, {
377
+ status: 'failed',
378
+ error: `Consensus not reached: ${consensusResult.finalScore}%`,
379
+ });
380
+
381
+ return {
382
+ success: false,
383
+ task: { ...task, status: 'failed' },
384
+ consensusResult,
385
+ error: `Task plan not approved. Score: ${consensusResult.finalScore}%`,
386
+ };
387
+ }
388
+
389
+ onProgress?.('task-consensus', `Task plan approved with ${consensusResult.finalScore}% consensus`);
390
+ }
391
+
392
+ // ============================================
393
+ // PHASE 3: Implement the Task (skip if already complete)
394
+ // ============================================
395
+ if (hasCompletedImplementation) {
396
+ onProgress?.('task-implement', `Implementation already complete for: ${task.name}, skipping to tests...`);
397
+ } else {
398
+ onProgress?.('task-implement', `Implementing task: ${task.name}`);
399
+
400
+ const implementResult = await executeTaskCode(
401
+ task,
402
+ consensusResult.bestPlan, // Use the approved plan as context
403
+ projectDir,
404
+ (msg) => onProgress?.('task-implement', msg)
405
+ );
406
+
407
+ if (!implementResult.success) {
408
+ state = await updateTaskInState(projectDir, task.id, {
409
+ status: 'failed',
410
+ error: implementResult.error,
411
+ });
412
+
413
+ return {
414
+ success: false,
415
+ task: { ...task, status: 'failed' },
416
+ consensusResult,
417
+ error: `Implementation failed: ${implementResult.error}`,
418
+ };
419
+ }
420
+
421
+ // Mark implementation as complete (for resume purposes)
422
+ state = await updateTaskInState(projectDir, task.id, {
423
+ implementationComplete: true,
424
+ });
425
+
426
+ onProgress?.('task-implement', `Implementation complete for: ${task.name}`);
427
+ }
428
+
429
+ // ============================================
430
+ // PHASE 4: Run Tests
431
+ // ============================================
432
+ const hasTests = await testsExist(projectDir, state.language);
433
+
434
+ if (hasTests) {
435
+ onProgress?.('task-test', `Running tests for: ${task.name}`);
436
+
437
+ let retries = 0;
438
+ let testResult: TestResult | undefined;
439
+
440
+ while (retries <= maxRetries) {
441
+ testResult = await runTests(projectDir, state.language);
442
+
443
+ // Document test results
444
+ const testDocPath = await documentTestResults(
445
+ projectDir,
446
+ milestone,
447
+ task,
448
+ testResult
449
+ );
450
+
451
+ state = await updateTaskInState(projectDir, task.id, {
452
+ testResultsDoc: testDocPath,
453
+ });
454
+
455
+ if (testResult.success) {
456
+ onProgress?.('task-test', `Tests passed: ${getTestSummary(testResult)}`);
457
+ break;
458
+ }
459
+
460
+ retries++;
461
+ if (retries <= maxRetries) {
462
+ onProgress?.('task-test', `Tests failed, retry ${retries}/${maxRetries}...`);
463
+ // Could add fix attempt here
464
+ }
465
+ }
466
+
467
+ if (testResult && !testResult.success) {
468
+ state = await updateTaskInState(projectDir, task.id, {
469
+ status: 'failed',
470
+ testsPassed: false,
471
+ error: `Tests failed after ${retries} retries`,
472
+ });
473
+
474
+ return {
475
+ success: false,
476
+ task: { ...task, status: 'failed', testsPassed: false },
477
+ consensusResult,
478
+ testResult,
479
+ error: `Tests failed: ${getTestSummary(testResult)}`,
480
+ };
481
+ }
482
+
483
+ // Mark task as complete
484
+ state = await updateTaskInState(projectDir, task.id, {
485
+ status: 'complete',
486
+ testsPassed: true,
487
+ });
488
+
489
+ return {
490
+ success: true,
491
+ task: { ...task, status: 'complete', testsPassed: true },
492
+ consensusResult,
493
+ testResult,
494
+ };
495
+ }
496
+
497
+ // No tests - mark as complete
498
+ state = await updateTaskInState(projectDir, task.id, {
499
+ status: 'complete',
500
+ });
501
+
502
+ onProgress?.('task-complete', `Task complete: ${task.name}`);
503
+
504
+ return {
505
+ success: true,
506
+ task: { ...task, status: 'complete' },
507
+ consensusResult,
508
+ };
509
+
510
+ } catch (error) {
511
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
512
+ onProgress?.('task-error', errorMessage);
513
+
514
+ await updateTaskInState(projectDir, task.id, {
515
+ status: 'failed',
516
+ error: errorMessage,
517
+ });
518
+
519
+ return {
520
+ success: false,
521
+ task: { ...task, status: 'failed', error: errorMessage },
522
+ error: errorMessage,
523
+ };
524
+ }
525
+ }
@@ -17,6 +17,7 @@ export interface TestResult {
17
17
  output: string;
18
18
  failedTests?: string[];
19
19
  error?: string;
20
+ noTestsFound?: boolean; // True when no tests were found (still counts as success)
20
21
  }
21
22
 
22
23
  /**
@@ -138,7 +139,9 @@ export function parseTestOutput(output: string, language: OutputLanguage): TestR
138
139
  }
139
140
  }
140
141
 
141
- const success = failed === 0 && total > 0;
142
+ // Success if no failures - treat "no tests found" as success (not a failure)
143
+ // If total === 0, there are no tests to fail, so it's technically a pass
144
+ const success = failed === 0;
142
145
 
143
146
  return {
144
147
  success,
@@ -147,6 +150,8 @@ export function parseTestOutput(output: string, language: OutputLanguage): TestR
147
150
  total,
148
151
  output,
149
152
  failedTests: failedTests.length > 0 ? failedTests : undefined,
153
+ // Flag to indicate no tests were found (for informational purposes)
154
+ noTestsFound: total === 0,
150
155
  };
151
156
  }
152
157
 
@@ -330,12 +335,12 @@ export function getTestSummary(result: TestResult): string {
330
335
  return `Tests failed to run: ${result.error}`;
331
336
  }
332
337
 
333
- if (result.total === 0) {
334
- return 'No tests found';
338
+ if (result.total === 0 || result.noTestsFound) {
339
+ return 'No tests found (OK)';
335
340
  }
336
341
 
337
- const status = result.success ? '' : '';
338
- let summary = `${status} ${result.passed}/${result.total} tests passed`;
342
+ const status = result.success ? 'PASS' : 'FAIL';
343
+ let summary = `${status}: ${result.passed}/${result.total} tests passed`;
339
344
 
340
345
  if (result.failed > 0) {
341
346
  summary += ` (${result.failed} failed)`;