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.
- package/README.md +521 -125
- package/dist/adapters/claude.d.ts +16 -4
- package/dist/adapters/claude.d.ts.map +1 -1
- package/dist/adapters/claude.js +679 -33
- package/dist/adapters/claude.js.map +1 -1
- package/dist/adapters/gemini.d.ts +55 -0
- package/dist/adapters/gemini.d.ts.map +1 -0
- package/dist/adapters/gemini.js +318 -0
- package/dist/adapters/gemini.js.map +1 -0
- package/dist/adapters/openai.d.ts.map +1 -1
- package/dist/adapters/openai.js +41 -7
- package/dist/adapters/openai.js.map +1 -1
- package/dist/auth/claude.d.ts +11 -9
- package/dist/auth/claude.d.ts.map +1 -1
- package/dist/auth/claude.js +107 -71
- package/dist/auth/claude.js.map +1 -1
- package/dist/auth/gemini.d.ts +58 -0
- package/dist/auth/gemini.d.ts.map +1 -0
- package/dist/auth/gemini.js +172 -0
- package/dist/auth/gemini.js.map +1 -0
- package/dist/auth/index.d.ts +11 -7
- package/dist/auth/index.d.ts.map +1 -1
- package/dist/auth/index.js +23 -5
- package/dist/auth/index.js.map +1 -1
- package/dist/auth/keychain.d.ts +20 -7
- package/dist/auth/keychain.d.ts.map +1 -1
- package/dist/auth/keychain.js +85 -29
- package/dist/auth/keychain.js.map +1 -1
- package/dist/auth/openai.d.ts +2 -2
- package/dist/auth/openai.d.ts.map +1 -1
- package/dist/auth/openai.js +30 -32
- package/dist/auth/openai.js.map +1 -1
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +4 -7
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/interactive.d.ts +2 -2
- package/dist/cli/interactive.d.ts.map +1 -1
- package/dist/cli/interactive.js +1380 -183
- package/dist/cli/interactive.js.map +1 -1
- package/dist/config/defaults.d.ts +6 -1
- package/dist/config/defaults.d.ts.map +1 -1
- package/dist/config/defaults.js +10 -2
- package/dist/config/defaults.js.map +1 -1
- package/dist/config/index.d.ts +10 -0
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/index.js +19 -0
- package/dist/config/index.js.map +1 -1
- package/dist/config/schema.d.ts +20 -0
- package/dist/config/schema.d.ts.map +1 -1
- package/dist/config/schema.js +7 -0
- package/dist/config/schema.js.map +1 -1
- package/dist/generators/python.d.ts.map +1 -1
- package/dist/generators/python.js +1 -0
- package/dist/generators/python.js.map +1 -1
- package/dist/generators/typescript.d.ts.map +1 -1
- package/dist/generators/typescript.js +1 -0
- package/dist/generators/typescript.js.map +1 -1
- package/dist/state/index.d.ts +108 -0
- package/dist/state/index.d.ts.map +1 -1
- package/dist/state/index.js +551 -4
- package/dist/state/index.js.map +1 -1
- package/dist/state/registry.d.ts +52 -0
- package/dist/state/registry.d.ts.map +1 -0
- package/dist/state/registry.js +215 -0
- package/dist/state/registry.js.map +1 -0
- package/dist/types/cli.d.ts +4 -0
- package/dist/types/cli.d.ts.map +1 -1
- package/dist/types/cli.js.map +1 -1
- package/dist/types/consensus.d.ts +69 -4
- package/dist/types/consensus.d.ts.map +1 -1
- package/dist/types/consensus.js +24 -3
- package/dist/types/consensus.js.map +1 -1
- package/dist/types/workflow.d.ts +55 -0
- package/dist/types/workflow.d.ts.map +1 -1
- package/dist/types/workflow.js +16 -0
- package/dist/types/workflow.js.map +1 -1
- package/dist/workflow/auto-fix.d.ts +45 -0
- package/dist/workflow/auto-fix.d.ts.map +1 -0
- package/dist/workflow/auto-fix.js +274 -0
- package/dist/workflow/auto-fix.js.map +1 -0
- package/dist/workflow/consensus.d.ts +44 -2
- package/dist/workflow/consensus.d.ts.map +1 -1
- package/dist/workflow/consensus.js +565 -17
- package/dist/workflow/consensus.js.map +1 -1
- package/dist/workflow/execution-mode.d.ts +10 -4
- package/dist/workflow/execution-mode.d.ts.map +1 -1
- package/dist/workflow/execution-mode.js +547 -58
- package/dist/workflow/execution-mode.js.map +1 -1
- package/dist/workflow/index.d.ts +14 -2
- package/dist/workflow/index.d.ts.map +1 -1
- package/dist/workflow/index.js +69 -6
- package/dist/workflow/index.js.map +1 -1
- package/dist/workflow/milestone-workflow.d.ts +34 -0
- package/dist/workflow/milestone-workflow.d.ts.map +1 -0
- package/dist/workflow/milestone-workflow.js +414 -0
- package/dist/workflow/milestone-workflow.js.map +1 -0
- package/dist/workflow/plan-mode.d.ts +14 -1
- package/dist/workflow/plan-mode.d.ts.map +1 -1
- package/dist/workflow/plan-mode.js +589 -47
- package/dist/workflow/plan-mode.js.map +1 -1
- package/dist/workflow/plan-storage.d.ts +142 -0
- package/dist/workflow/plan-storage.d.ts.map +1 -0
- package/dist/workflow/plan-storage.js +331 -0
- package/dist/workflow/plan-storage.js.map +1 -0
- package/dist/workflow/project-verification.d.ts +37 -0
- package/dist/workflow/project-verification.d.ts.map +1 -0
- package/dist/workflow/project-verification.js +381 -0
- package/dist/workflow/project-verification.js.map +1 -0
- package/dist/workflow/task-workflow.d.ts +37 -0
- package/dist/workflow/task-workflow.d.ts.map +1 -0
- package/dist/workflow/task-workflow.js +383 -0
- package/dist/workflow/task-workflow.js.map +1 -0
- package/dist/workflow/test-runner.d.ts +1 -0
- package/dist/workflow/test-runner.d.ts.map +1 -1
- package/dist/workflow/test-runner.js +9 -5
- package/dist/workflow/test-runner.js.map +1 -1
- package/dist/workflow/ui-designer.d.ts +82 -0
- package/dist/workflow/ui-designer.d.ts.map +1 -0
- package/dist/workflow/ui-designer.js +234 -0
- package/dist/workflow/ui-designer.js.map +1 -0
- package/dist/workflow/ui-setup.d.ts +58 -0
- package/dist/workflow/ui-setup.d.ts.map +1 -0
- package/dist/workflow/ui-setup.js +685 -0
- package/dist/workflow/ui-setup.js.map +1 -0
- package/dist/workflow/ui-verification.d.ts +114 -0
- package/dist/workflow/ui-verification.d.ts.map +1 -0
- package/dist/workflow/ui-verification.js +258 -0
- package/dist/workflow/ui-verification.js.map +1 -0
- package/dist/workflow/workflow-logger.d.ts +110 -0
- package/dist/workflow/workflow-logger.d.ts.map +1 -0
- package/dist/workflow/workflow-logger.js +267 -0
- package/dist/workflow/workflow-logger.js.map +1 -0
- package/package.json +2 -2
- package/src/adapters/claude.ts +815 -34
- package/src/adapters/gemini.ts +373 -0
- package/src/adapters/openai.ts +40 -7
- package/src/auth/claude.ts +120 -78
- package/src/auth/gemini.ts +207 -0
- package/src/auth/index.ts +28 -8
- package/src/auth/keychain.ts +95 -28
- package/src/auth/openai.ts +29 -36
- package/src/cli/index.ts +4 -7
- package/src/cli/interactive.ts +1641 -216
- package/src/config/defaults.ts +10 -2
- package/src/config/index.ts +21 -0
- package/src/config/schema.ts +7 -0
- package/src/generators/python.ts +1 -0
- package/src/generators/typescript.ts +1 -0
- package/src/state/index.ts +713 -4
- package/src/state/registry.ts +278 -0
- package/src/types/cli.ts +4 -0
- package/src/types/consensus.ts +65 -6
- package/src/types/workflow.ts +35 -0
- package/src/workflow/auto-fix.ts +340 -0
- package/src/workflow/consensus.ts +750 -16
- package/src/workflow/execution-mode.ts +673 -74
- package/src/workflow/index.ts +95 -6
- package/src/workflow/milestone-workflow.ts +576 -0
- package/src/workflow/plan-mode.ts +696 -50
- package/src/workflow/plan-storage.ts +482 -0
- package/src/workflow/project-verification.ts +471 -0
- package/src/workflow/task-workflow.ts +525 -0
- package/src/workflow/test-runner.ts +10 -5
- package/src/workflow/ui-designer.ts +337 -0
- package/src/workflow/ui-setup.ts +797 -0
- package/src/workflow/ui-verification.ts +357 -0
- package/src/workflow/workflow-logger.ts +353 -0
- package/tests/config/config.test.ts +1 -1
- package/tests/types/consensus.test.ts +3 -3
- package/tests/workflow/plan-mode.test.ts +213 -0
- 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
|
-
|
|
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)`;
|