popeye-cli 1.4.1 → 1.4.2

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 (56) hide show
  1. package/dist/adapters/claude.d.ts.map +1 -1
  2. package/dist/adapters/claude.js +6 -0
  3. package/dist/adapters/claude.js.map +1 -1
  4. package/dist/cli/interactive.d.ts.map +1 -1
  5. package/dist/cli/interactive.js +82 -5
  6. package/dist/cli/interactive.js.map +1 -1
  7. package/dist/types/workflow.d.ts +12 -0
  8. package/dist/types/workflow.d.ts.map +1 -1
  9. package/dist/types/workflow.js +4 -0
  10. package/dist/types/workflow.js.map +1 -1
  11. package/dist/workflow/auto-fix.d.ts +17 -1
  12. package/dist/workflow/auto-fix.d.ts.map +1 -1
  13. package/dist/workflow/auto-fix.js +97 -16
  14. package/dist/workflow/auto-fix.js.map +1 -1
  15. package/dist/workflow/execution-mode.d.ts +24 -0
  16. package/dist/workflow/execution-mode.d.ts.map +1 -1
  17. package/dist/workflow/execution-mode.js +292 -19
  18. package/dist/workflow/execution-mode.js.map +1 -1
  19. package/dist/workflow/index.d.ts +1 -0
  20. package/dist/workflow/index.d.ts.map +1 -1
  21. package/dist/workflow/index.js +1 -0
  22. package/dist/workflow/index.js.map +1 -1
  23. package/dist/workflow/milestone-workflow.d.ts.map +1 -1
  24. package/dist/workflow/milestone-workflow.js +63 -3
  25. package/dist/workflow/milestone-workflow.js.map +1 -1
  26. package/dist/workflow/project-structure.d.ts +29 -0
  27. package/dist/workflow/project-structure.d.ts.map +1 -0
  28. package/dist/workflow/project-structure.js +193 -0
  29. package/dist/workflow/project-structure.js.map +1 -0
  30. package/dist/workflow/project-verification.d.ts +24 -1
  31. package/dist/workflow/project-verification.d.ts.map +1 -1
  32. package/dist/workflow/project-verification.js +105 -22
  33. package/dist/workflow/project-verification.js.map +1 -1
  34. package/dist/workflow/remediation.d.ts +124 -0
  35. package/dist/workflow/remediation.d.ts.map +1 -0
  36. package/dist/workflow/remediation.js +510 -0
  37. package/dist/workflow/remediation.js.map +1 -0
  38. package/dist/workflow/task-workflow.d.ts.map +1 -1
  39. package/dist/workflow/task-workflow.js +202 -4
  40. package/dist/workflow/task-workflow.js.map +1 -1
  41. package/package.json +1 -1
  42. package/src/adapters/claude.ts +6 -0
  43. package/src/cli/interactive.ts +85 -5
  44. package/src/types/workflow.ts +9 -0
  45. package/src/workflow/auto-fix.ts +123 -18
  46. package/src/workflow/execution-mode.ts +364 -20
  47. package/src/workflow/index.ts +1 -0
  48. package/src/workflow/milestone-workflow.ts +80 -3
  49. package/src/workflow/project-structure.ts +233 -0
  50. package/src/workflow/project-verification.ts +128 -21
  51. package/src/workflow/remediation.ts +711 -0
  52. package/src/workflow/task-workflow.ts +237 -4
  53. package/tests/workflow/auto-fix-enhanced.test.ts +351 -0
  54. package/tests/workflow/project-structure.test.ts +131 -0
  55. package/tests/workflow/project-verification.test.ts +130 -0
  56. package/tests/workflow/remediation.test.ts +238 -0
@@ -0,0 +1,711 @@
1
+ /**
2
+ * Remediation loop for consensus-driven failure recovery
3
+ * When a task fails during execution, this module:
4
+ * 1. Gathers full failure context
5
+ * 2. Analyzes root cause via Claude
6
+ * 3. Creates a fix plan
7
+ * 4. Gets consensus from reviewers (with full failure context)
8
+ * 5. Implements the approved fix
9
+ * 6. Retries the task
10
+ */
11
+
12
+ import { promises as fs } from 'node:fs';
13
+ import path from 'node:path';
14
+ import type { ProjectState, Task, Milestone } from '../types/workflow.js';
15
+ import type { ConsensusConfig } from '../types/consensus.js';
16
+ import { isWorkspace } from '../types/project.js';
17
+ import { createPlan as claudeCreatePlan, generateCode } from '../adapters/claude.js';
18
+ import { loadProject, updateState } from '../state/index.js';
19
+ import {
20
+ iterateUntilConsensus,
21
+ runOptimizedConsensusProcess,
22
+ type ConsensusProcessResult,
23
+ } from './consensus.js';
24
+ import { runTaskWorkflow, type TaskWorkflowResult } from './task-workflow.js';
25
+
26
+ /**
27
+ * Maximum number of remediation attempts per task
28
+ */
29
+ export const MAX_REMEDIATION_ATTEMPTS = 2;
30
+
31
+ /**
32
+ * Result of a remediation attempt
33
+ */
34
+ export interface RemediationResult {
35
+ success: boolean;
36
+ taskResult?: TaskWorkflowResult;
37
+ remediationPlan?: string;
38
+ consensusResult?: ConsensusProcessResult;
39
+ failureAnalysis?: string;
40
+ attempt: number;
41
+ maxAttempts: number;
42
+ error?: string;
43
+ rateLimitPaused?: boolean;
44
+ }
45
+
46
+ /**
47
+ * Options for remediation
48
+ */
49
+ export interface RemediationOptions {
50
+ projectDir: string;
51
+ consensusConfig?: Partial<ConsensusConfig>;
52
+ onProgress?: (phase: string, message: string) => void;
53
+ }
54
+
55
+ /**
56
+ * Update a task in state with new data
57
+ */
58
+ async function updateTaskInState(
59
+ projectDir: string,
60
+ taskId: string,
61
+ updates: Partial<Task>
62
+ ): Promise<ProjectState> {
63
+ const state = await loadProject(projectDir);
64
+
65
+ const updatedMilestones = state.milestones.map(milestone => ({
66
+ ...milestone,
67
+ tasks: milestone.tasks.map(task =>
68
+ task.id === taskId ? { ...task, ...updates } : task
69
+ ),
70
+ }));
71
+
72
+ return updateState(projectDir, { milestones: updatedMilestones });
73
+ }
74
+
75
+ /**
76
+ * Build comprehensive failure context for reviewers
77
+ * Gathers all available information about why a task failed
78
+ *
79
+ * @param task - The failed task
80
+ * @param milestone - The parent milestone
81
+ * @param taskResult - The task workflow result with failure details
82
+ * @param state - Current project state
83
+ * @param projectDir - Project directory for reading docs
84
+ * @returns Structured markdown string with full failure context
85
+ */
86
+ export async function buildFailureContext(
87
+ task: Task,
88
+ milestone: Milestone,
89
+ taskResult: TaskWorkflowResult,
90
+ state: ProjectState,
91
+ projectDir: string
92
+ ): Promise<string> {
93
+ const sections: string[] = [];
94
+
95
+ // Section 1: Task info
96
+ sections.push(`## Failed Task
97
+ - **Name**: ${task.name}
98
+ - **Description**: ${task.description}
99
+ - **Milestone**: ${milestone.name}
100
+ - **Task ID**: ${task.id}`);
101
+
102
+ if (task.plan) {
103
+ sections.push(`### Task Plan (that was executed)
104
+ ${task.plan.slice(0, 2000)}`);
105
+ }
106
+
107
+ // Section 2: Error details
108
+ sections.push(`## Error Details
109
+ ${taskResult.error || 'No error message available'}`);
110
+
111
+ // Section 3: Test results (if available)
112
+ if (taskResult.testResult) {
113
+ const tr = taskResult.testResult;
114
+ const isCrash = tr.passed === 0 && tr.failed > 20;
115
+
116
+ sections.push(`## Test Results
117
+ - **Status**: ${tr.success ? 'PASSED' : 'FAILED'}
118
+ - **Passed**: ${tr.passed}
119
+ - **Failed**: ${tr.failed}
120
+ - **Total**: ${tr.total}
121
+ ${isCrash ? '- **CRASH DETECTED**: 0 passed with many failures indicates a startup/import error' : ''}`);
122
+
123
+ if (tr.failedTests && tr.failedTests.length > 0) {
124
+ sections.push(`### Failed Tests
125
+ ${tr.failedTests.slice(0, 10).map(t => `- ${t}`).join('\n')}`);
126
+ }
127
+
128
+ if (tr.output) {
129
+ sections.push(`### Test Output (truncated)
130
+ \`\`\`
131
+ ${tr.output.slice(0, 3000)}
132
+ \`\`\``);
133
+ }
134
+ }
135
+
136
+ // Section 4: Consensus history (if available)
137
+ if (taskResult.consensusResult) {
138
+ const cr = taskResult.consensusResult;
139
+ if (cr.finalConcerns.length > 0 || cr.finalRecommendations.length > 0) {
140
+ sections.push(`## Previous Consensus Feedback
141
+ ### Concerns Raised by Reviewers
142
+ ${cr.finalConcerns.map(c => `- ${c}`).join('\n') || 'None'}
143
+
144
+ ### Recommendations from Reviewers
145
+ ${cr.finalRecommendations.map(r => `- ${r}`).join('\n') || 'None'}`);
146
+ }
147
+ }
148
+
149
+ // Section 5: Previous remediation attempts
150
+ if (task.remediationAttempts && task.remediationAttempts > 0) {
151
+ sections.push(`## Previous Remediation Attempts (${task.remediationAttempts})
152
+ ### Previous Root Cause Analysis
153
+ ${task.lastFailureAnalysis || 'Not available'}
154
+
155
+ ### Previous Fix Plan (did not resolve the issue)
156
+ ${task.lastRemediationPlan || 'Not available'}
157
+
158
+ **IMPORTANT**: The previous fix did not work. The new fix must take a DIFFERENT approach.`);
159
+ }
160
+
161
+ // Section 6: Read disk docs if they exist
162
+ if (task.planDoc) {
163
+ try {
164
+ const planDocPath = path.join(projectDir, task.planDoc);
165
+ const planContent = await fs.readFile(planDocPath, 'utf-8');
166
+ sections.push(`## Task Plan Document
167
+ ${planContent.slice(0, 1500)}`);
168
+ } catch {
169
+ // File may not exist, skip
170
+ }
171
+ }
172
+
173
+ if (task.testResultsDoc) {
174
+ try {
175
+ const testDocPath = path.join(projectDir, task.testResultsDoc);
176
+ const testContent = await fs.readFile(testDocPath, 'utf-8');
177
+ sections.push(`## Test Results Document
178
+ ${testContent.slice(0, 1500)}`);
179
+ } catch {
180
+ // File may not exist, skip
181
+ }
182
+ }
183
+
184
+ // Section 7: Milestone context
185
+ const completedTasks = milestone.tasks.filter(t => t.status === 'complete');
186
+ const remainingTasks = milestone.tasks.filter(
187
+ t => t.status !== 'complete' && t.id !== task.id
188
+ );
189
+
190
+ sections.push(`## Milestone Context
191
+ - **Project**: ${state.name}
192
+ - **Language**: ${state.language}
193
+ - **Completed Tasks**: ${completedTasks.map(t => t.name).join(', ') || 'None'}
194
+ - **Remaining Tasks**: ${remainingTasks.map(t => t.name).join(', ') || 'None'}`);
195
+
196
+ return sections.join('\n\n');
197
+ }
198
+
199
+ /**
200
+ * Use Claude to analyze the failure and create a remediation plan
201
+ *
202
+ * @param failureContext - Full failure context markdown
203
+ * @param task - The failed task
204
+ * @param state - Current project state
205
+ * @param onProgress - Progress callback
206
+ * @returns The remediation plan
207
+ */
208
+ export async function buildRemediationPlan(
209
+ failureContext: string,
210
+ task: Task,
211
+ state: ProjectState,
212
+ onProgress?: (message: string) => void
213
+ ): Promise<string> {
214
+ onProgress?.('Analyzing failure and creating remediation plan...');
215
+
216
+ const prompt = `
217
+ You are analyzing a task failure and must create a remediation plan.
218
+
219
+ ${failureContext}
220
+
221
+ Based on the failure context above, provide:
222
+
223
+ ### Root Cause Analysis
224
+ Identify the specific root cause of the failure. Be precise - don't just restate the error.
225
+
226
+ ### Fix Plan
227
+ Step-by-step plan to fix the issue:
228
+ 1. [Specific action]
229
+ 2. [Specific action]
230
+ ...
231
+
232
+ ### Files to Modify
233
+ List the exact files that need to be changed and what changes are needed.
234
+
235
+ ### Verification Steps
236
+ How to verify the fix works:
237
+ 1. [Verification step]
238
+ 2. [Verification step]
239
+
240
+ ${task.remediationAttempts && task.remediationAttempts > 0
241
+ ? '\nIMPORTANT: Previous remediation attempts failed. You MUST take a DIFFERENT approach this time.'
242
+ : ''}
243
+
244
+ Be specific and actionable. This plan will be reviewed before implementation.
245
+ `.trim();
246
+
247
+ const context = `
248
+ Project: ${state.name}
249
+ Language: ${state.language}
250
+ Phase: REMEDIATION
251
+ `.trim();
252
+
253
+ const result = await claudeCreatePlan(prompt, context, state.language, onProgress);
254
+
255
+ if (!result.success) {
256
+ // If Claude analysis fails (e.g., rate limit), return a basic plan
257
+ // based on the available failure context
258
+ onProgress?.('Analysis failed, creating basic remediation plan from available context');
259
+ return `### Root Cause Analysis
260
+ Based on error: ${task.error || 'Unknown error'}
261
+
262
+ ### Fix Plan
263
+ 1. Review the error output and identify the failing component
264
+ 2. Fix the identified issue
265
+ 3. Re-run tests to verify
266
+
267
+ ### Files to Modify
268
+ See task plan for relevant files.
269
+
270
+ ### Verification Steps
271
+ 1. Run tests and verify they pass
272
+ 2. Check that no regressions were introduced`;
273
+ }
274
+
275
+ return result.response;
276
+ }
277
+
278
+ /**
279
+ * Get consensus on the remediation plan with full failure context
280
+ *
281
+ * @param plan - The remediation plan
282
+ * @param failureContext - Full failure context for reviewers
283
+ * @param task - The failed task
284
+ * @param milestone - Parent milestone
285
+ * @param state - Current project state
286
+ * @param options - Remediation options
287
+ * @returns Consensus process result
288
+ */
289
+ export async function runRemediationConsensus(
290
+ plan: string,
291
+ failureContext: string,
292
+ task: Task,
293
+ milestone: Milestone,
294
+ state: ProjectState,
295
+ options: RemediationOptions
296
+ ): Promise<ConsensusProcessResult> {
297
+ const { projectDir, consensusConfig, onProgress } = options;
298
+
299
+ const attempt = (task.remediationAttempts || 0) + 1;
300
+
301
+ // Build context that includes failure information for reviewers
302
+ const context = `
303
+ Project: ${state.name}
304
+ Language: ${state.language}
305
+ Phase: REMEDIATION (attempt ${attempt}/${MAX_REMEDIATION_ATTEMPTS})
306
+ Milestone: ${milestone.name}
307
+ Task: ${task.name}
308
+
309
+ ## FAILURE CONTEXT (Why this task needs remediation)
310
+ ${failureContext.slice(0, 4000)}
311
+ `.trim();
312
+
313
+ const useOptimized = consensusConfig?.useOptimizedConsensus !== false;
314
+
315
+ if (useOptimized) {
316
+ onProgress?.('remediation-consensus', 'Getting consensus on remediation plan (optimized)...');
317
+ const result = await runOptimizedConsensusProcess(
318
+ plan,
319
+ context,
320
+ {
321
+ projectDir,
322
+ config: consensusConfig,
323
+ milestoneId: milestone.id,
324
+ milestoneName: milestone.name,
325
+ taskId: task.id,
326
+ taskName: `${task.name} - Remediation ${attempt}`,
327
+ parallelReviews: true,
328
+ isFullstack: isWorkspace(state.language),
329
+ onIteration: (iteration, result) => {
330
+ onProgress?.('remediation-consensus', `Remediation consensus iteration ${iteration}: ${result.score}%`);
331
+ },
332
+ onProgress,
333
+ }
334
+ );
335
+ // runOptimizedConsensusProcess returns ConsensusProcessResult or FullstackConsensusProcessResult
336
+ // Both satisfy ConsensusProcessResult
337
+ return result as ConsensusProcessResult;
338
+ }
339
+
340
+ onProgress?.('remediation-consensus', 'Getting consensus on remediation plan...');
341
+ return iterateUntilConsensus(
342
+ plan,
343
+ context,
344
+ {
345
+ projectDir,
346
+ config: consensusConfig,
347
+ isFullstack: isWorkspace(state.language),
348
+ language: state.language,
349
+ onIteration: (iteration, result) => {
350
+ onProgress?.('remediation-consensus', `Remediation consensus iteration ${iteration}: ${result.score}%`);
351
+ },
352
+ onProgress,
353
+ }
354
+ );
355
+ }
356
+
357
+ /**
358
+ * Implement the approved remediation fix
359
+ *
360
+ * @param task - The task to fix
361
+ * @param plan - The approved remediation plan
362
+ * @param projectDir - Project directory
363
+ * @param onProgress - Progress callback
364
+ * @returns Code generation result
365
+ */
366
+ export async function executeRemediation(
367
+ task: Task,
368
+ plan: string,
369
+ projectDir: string,
370
+ onProgress?: (message: string) => void
371
+ ): Promise<{ success: boolean; rateLimitPaused?: boolean; rateLimitInfo?: { message?: string }; error?: string }> {
372
+ onProgress?.('Implementing remediation fix...');
373
+
374
+ const prompt = `
375
+ ## Remediation Fix Required
376
+
377
+ ### Task: ${task.name}
378
+ ${task.description}
379
+
380
+ ### Approved Fix Plan
381
+ ${plan}
382
+
383
+ ### Instructions
384
+ 1. Implement the fix plan above
385
+ 2. Focus only on the changes specified in the plan
386
+ 3. Do NOT make unrelated changes
387
+ 4. Ensure the fix addresses the root cause
388
+ `.trim();
389
+
390
+ const context = `Remediation fix for task: ${task.name}`;
391
+
392
+ const result = await generateCode(prompt, context, {
393
+ cwd: projectDir,
394
+ onProgress,
395
+ });
396
+
397
+ if (result.rateLimitPaused) {
398
+ return {
399
+ success: false,
400
+ rateLimitPaused: true,
401
+ rateLimitInfo: result.rateLimitInfo,
402
+ };
403
+ }
404
+
405
+ return {
406
+ success: result.success,
407
+ error: result.error,
408
+ };
409
+ }
410
+
411
+ /**
412
+ * Document a remediation attempt to disk
413
+ *
414
+ * @param projectDir - Project directory
415
+ * @param milestone - Parent milestone
416
+ * @param task - The task
417
+ * @param attempt - Attempt number
418
+ * @param failureAnalysis - Root cause analysis
419
+ * @param remediationPlan - The fix plan
420
+ * @param consensusScore - Consensus score for the plan
421
+ * @param success - Whether the remediation succeeded
422
+ */
423
+ export async function documentRemediationAttempt(
424
+ projectDir: string,
425
+ milestone: Milestone,
426
+ task: Task,
427
+ attempt: number,
428
+ failureAnalysis: string,
429
+ remediationPlan: string,
430
+ consensusScore: number,
431
+ success: boolean
432
+ ): Promise<string> {
433
+ const docsDir = path.join(projectDir, 'docs', 'remediation');
434
+ await fs.mkdir(docsDir, { recursive: true });
435
+
436
+ const milestoneNum = milestone.id.replace('milestone-', '');
437
+ const taskNum = task.id.split('-task-')[1] || '1';
438
+ const filename = `milestone_${milestoneNum}_task_${taskNum}_remediation_${attempt}.md`;
439
+ const docPath = path.join(docsDir, filename);
440
+
441
+ const content = `# Remediation Attempt ${attempt}: ${task.name}
442
+
443
+ ## Metadata
444
+ - **Milestone**: ${milestone.name}
445
+ - **Task ID**: ${task.id}
446
+ - **Attempt**: ${attempt}/${MAX_REMEDIATION_ATTEMPTS}
447
+ - **Consensus Score**: ${consensusScore}%
448
+ - **Result**: ${success ? 'SUCCESS' : 'FAILED'}
449
+ - **Timestamp**: ${new Date().toISOString()}
450
+
451
+ ## Failure Analysis
452
+ ${failureAnalysis}
453
+
454
+ ## Remediation Plan
455
+ ${remediationPlan}
456
+ `;
457
+
458
+ await fs.writeFile(docPath, content, 'utf-8');
459
+ return `docs/remediation/${filename}`;
460
+ }
461
+
462
+ /**
463
+ * Orchestrate a complete remediation attempt for a failed task
464
+ *
465
+ * Flow:
466
+ * 1. Check attempt count < MAX
467
+ * 2. Build failure context
468
+ * 3. Create fix plan via Claude
469
+ * 4. Get consensus with full failure context
470
+ * 5. Document the attempt
471
+ * 6. Implement the fix
472
+ * 7. Reset task state and re-run task workflow
473
+ *
474
+ * @param milestone - Parent milestone
475
+ * @param task - The failed task
476
+ * @param taskResult - The failed task result
477
+ * @param options - Remediation options
478
+ * @returns Remediation result
479
+ */
480
+ export async function attemptRemediation(
481
+ milestone: Milestone,
482
+ task: Task,
483
+ taskResult: TaskWorkflowResult,
484
+ options: RemediationOptions
485
+ ): Promise<RemediationResult> {
486
+ const { projectDir, consensusConfig, onProgress } = options;
487
+
488
+ // Load fresh state
489
+ let state = await loadProject(projectDir);
490
+
491
+ // Get current task from state (may have been updated)
492
+ const currentMilestone = state.milestones.find(m => m.id === milestone.id);
493
+ const currentTask = currentMilestone?.tasks.find(t => t.id === task.id);
494
+ const attempts = currentTask?.remediationAttempts || 0;
495
+
496
+ // Check if max attempts reached
497
+ if (attempts >= MAX_REMEDIATION_ATTEMPTS) {
498
+ onProgress?.('remediation', `Max remediation attempts (${MAX_REMEDIATION_ATTEMPTS}) reached for: ${task.name}`);
499
+ return {
500
+ success: false,
501
+ attempt: attempts,
502
+ maxAttempts: MAX_REMEDIATION_ATTEMPTS,
503
+ error: `Max remediation attempts (${MAX_REMEDIATION_ATTEMPTS}) reached`,
504
+ };
505
+ }
506
+
507
+ const currentAttempt = attempts + 1;
508
+ onProgress?.('remediation-analysis', `Remediation attempt ${currentAttempt}/${MAX_REMEDIATION_ATTEMPTS} for: ${task.name}`);
509
+
510
+ // Increment attempt counter in state
511
+ state = await updateTaskInState(projectDir, task.id, {
512
+ remediationAttempts: currentAttempt,
513
+ });
514
+
515
+ // Step 1: Build failure context
516
+ onProgress?.('remediation-analysis', 'Gathering failure context...');
517
+ const failureContext = await buildFailureContext(
518
+ currentTask || task,
519
+ currentMilestone || milestone,
520
+ taskResult,
521
+ state,
522
+ projectDir
523
+ );
524
+
525
+ // Step 2: Create fix plan via Claude
526
+ onProgress?.('remediation-plan', 'Creating remediation plan...');
527
+ const remediationPlan = await buildRemediationPlan(
528
+ failureContext,
529
+ currentTask || task,
530
+ state,
531
+ (msg) => onProgress?.('remediation-plan', msg)
532
+ );
533
+
534
+ // Extract failure analysis from the plan (first section)
535
+ const analysisMatch = remediationPlan.match(/### Root Cause Analysis\n([\s\S]*?)(?=\n### |$)/);
536
+ const failureAnalysis = analysisMatch ? analysisMatch[1].trim() : remediationPlan.slice(0, 500);
537
+
538
+ // Save analysis to state
539
+ state = await updateTaskInState(projectDir, task.id, {
540
+ lastFailureAnalysis: failureAnalysis,
541
+ lastRemediationPlan: remediationPlan,
542
+ });
543
+
544
+ // Step 3: Get consensus on remediation plan
545
+ onProgress?.('remediation-consensus', 'Getting consensus on remediation plan...');
546
+ let consensusResult: ConsensusProcessResult;
547
+
548
+ try {
549
+ consensusResult = await runRemediationConsensus(
550
+ remediationPlan,
551
+ failureContext,
552
+ currentTask || task,
553
+ currentMilestone || milestone,
554
+ state,
555
+ options
556
+ );
557
+ } catch (consensusError) {
558
+ const errorMsg = consensusError instanceof Error ? consensusError.message : 'Unknown error';
559
+ onProgress?.('remediation-consensus', `Consensus failed: ${errorMsg}`);
560
+ return {
561
+ success: false,
562
+ remediationPlan,
563
+ failureAnalysis,
564
+ attempt: currentAttempt,
565
+ maxAttempts: MAX_REMEDIATION_ATTEMPTS,
566
+ error: `Consensus failed: ${errorMsg}`,
567
+ };
568
+ }
569
+
570
+ // Check if consensus was rejected
571
+ if (!consensusResult.approved) {
572
+ onProgress?.('remediation-consensus', `Remediation plan not approved (${consensusResult.finalScore}%)`);
573
+
574
+ // Document the rejected attempt
575
+ await documentRemediationAttempt(
576
+ projectDir,
577
+ currentMilestone || milestone,
578
+ currentTask || task,
579
+ currentAttempt,
580
+ failureAnalysis,
581
+ remediationPlan,
582
+ consensusResult.finalScore,
583
+ false
584
+ );
585
+
586
+ return {
587
+ success: false,
588
+ remediationPlan,
589
+ consensusResult,
590
+ failureAnalysis,
591
+ attempt: currentAttempt,
592
+ maxAttempts: MAX_REMEDIATION_ATTEMPTS,
593
+ error: `Remediation plan not approved. Score: ${consensusResult.finalScore}%`,
594
+ };
595
+ }
596
+
597
+ onProgress?.('remediation-consensus', `Remediation plan approved (${consensusResult.finalScore}%)`);
598
+
599
+ // Step 4: Document the attempt
600
+ onProgress?.('remediation-doc', 'Documenting remediation attempt...');
601
+ await documentRemediationAttempt(
602
+ projectDir,
603
+ currentMilestone || milestone,
604
+ currentTask || task,
605
+ currentAttempt,
606
+ failureAnalysis,
607
+ consensusResult.bestPlan,
608
+ consensusResult.finalScore,
609
+ false // Not yet known - will update if successful
610
+ );
611
+
612
+ // Step 5: Implement the fix
613
+ onProgress?.('remediation-fix', 'Implementing remediation fix...');
614
+ const fixResult = await executeRemediation(
615
+ currentTask || task,
616
+ consensusResult.bestPlan,
617
+ projectDir,
618
+ (msg) => onProgress?.('remediation-fix', msg)
619
+ );
620
+
621
+ // Handle rate limit pause
622
+ if (fixResult.rateLimitPaused) {
623
+ onProgress?.('remediation-fix', 'Rate limit during remediation - pausing gracefully');
624
+
625
+ // Save state for resume
626
+ await updateTaskInState(projectDir, task.id, {
627
+ status: 'paused',
628
+ error: `Rate limit during remediation. ${fixResult.rateLimitInfo?.message || ''}`,
629
+ });
630
+
631
+ return {
632
+ success: false,
633
+ remediationPlan: consensusResult.bestPlan,
634
+ consensusResult,
635
+ failureAnalysis,
636
+ attempt: currentAttempt,
637
+ maxAttempts: MAX_REMEDIATION_ATTEMPTS,
638
+ rateLimitPaused: true,
639
+ error: `Rate limit during remediation fix`,
640
+ };
641
+ }
642
+
643
+ // Handle fix failure
644
+ if (!fixResult.success) {
645
+ onProgress?.('remediation-fix', `Remediation fix failed: ${fixResult.error}`);
646
+ return {
647
+ success: false,
648
+ remediationPlan: consensusResult.bestPlan,
649
+ consensusResult,
650
+ failureAnalysis,
651
+ attempt: currentAttempt,
652
+ maxAttempts: MAX_REMEDIATION_ATTEMPTS,
653
+ error: `Remediation fix implementation failed: ${fixResult.error}`,
654
+ };
655
+ }
656
+
657
+ // Step 6: Reset task state and re-run task workflow
658
+ onProgress?.('remediation-retry', 'Retrying task after remediation fix...');
659
+
660
+ // Reset task for retry: keep consensus (plan is still valid), reset implementation
661
+ state = await updateTaskInState(projectDir, task.id, {
662
+ status: 'pending',
663
+ implementationComplete: false,
664
+ testsPassed: undefined,
665
+ error: undefined,
666
+ // Keep consensusApproved, plan, consensusScore (the original plan is still valid)
667
+ // Keep remediationAttempts, lastFailureAnalysis, lastRemediationPlan (for tracking)
668
+ });
669
+
670
+ // Re-run the task workflow (gets fresh 3 test retries)
671
+ const retryResult = await runTaskWorkflow(
672
+ currentMilestone || milestone,
673
+ currentTask || task,
674
+ {
675
+ projectDir,
676
+ consensusConfig,
677
+ onProgress,
678
+ }
679
+ );
680
+
681
+ // Update documentation if successful
682
+ if (retryResult.success) {
683
+ onProgress?.('remediation-retry', `Remediation successful - task "${task.name}" now passes`);
684
+
685
+ // Rewrite doc as successful
686
+ await documentRemediationAttempt(
687
+ projectDir,
688
+ currentMilestone || milestone,
689
+ currentTask || task,
690
+ currentAttempt,
691
+ failureAnalysis,
692
+ consensusResult.bestPlan,
693
+ consensusResult.finalScore,
694
+ true
695
+ );
696
+ } else {
697
+ onProgress?.('remediation-retry', `Task still failing after remediation attempt ${currentAttempt}`);
698
+ }
699
+
700
+ return {
701
+ success: retryResult.success,
702
+ taskResult: retryResult,
703
+ remediationPlan: consensusResult.bestPlan,
704
+ consensusResult,
705
+ failureAnalysis,
706
+ attempt: currentAttempt,
707
+ maxAttempts: MAX_REMEDIATION_ATTEMPTS,
708
+ error: retryResult.success ? undefined : retryResult.error,
709
+ rateLimitPaused: retryResult.rateLimitPaused,
710
+ };
711
+ }