popeye-cli 1.0.1 → 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/interactive.d.ts.map +1 -1
- package/dist/cli/interactive.js +1151 -110
- 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/interactive.ts +1357 -115
- 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
|
@@ -1,11 +1,67 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Consensus workflow module
|
|
3
|
-
* Handles the iterative consensus-building process between Claude and OpenAI
|
|
3
|
+
* Handles the iterative consensus-building process between Claude and OpenAI/Gemini
|
|
4
|
+
* with arbitration support when consensus cannot be reached
|
|
4
5
|
*/
|
|
5
6
|
import { DEFAULT_CONSENSUS_CONFIG } from '../types/consensus.js';
|
|
6
|
-
import { requestConsensus } from '../adapters/openai.js';
|
|
7
|
+
import { requestConsensus as requestOpenAIConsensus } from '../adapters/openai.js';
|
|
8
|
+
import { requestConsensus as requestGeminiConsensus, requestArbitration as requestGeminiArbitration } from '../adapters/gemini.js';
|
|
7
9
|
import { revisePlan } from '../adapters/claude.js';
|
|
8
10
|
import { recordConsensusIteration } from '../state/index.js';
|
|
11
|
+
import { createPlanStorage } from './plan-storage.js';
|
|
12
|
+
/**
|
|
13
|
+
* Request consensus from the configured reviewer (OpenAI or Gemini)
|
|
14
|
+
*/
|
|
15
|
+
async function requestReviewerConsensus(plan, context, reviewer, config) {
|
|
16
|
+
if (reviewer === 'gemini') {
|
|
17
|
+
return requestGeminiConsensus(plan, context, {
|
|
18
|
+
model: config.geminiModel,
|
|
19
|
+
temperature: config.temperature,
|
|
20
|
+
maxTokens: config.maxTokens,
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
return requestOpenAIConsensus(plan, context, config);
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Check if the consensus process is "stuck" (not improving)
|
|
27
|
+
* Detects both:
|
|
28
|
+
* 1. Stagnation: scores within 5% of each other
|
|
29
|
+
* 2. Oscillation: scores going up and down without progress
|
|
30
|
+
*/
|
|
31
|
+
function isStuck(scores, stuckIterations) {
|
|
32
|
+
if (scores.length < stuckIterations)
|
|
33
|
+
return false;
|
|
34
|
+
const recentScores = scores.slice(-stuckIterations);
|
|
35
|
+
const maxRecent = Math.max(...recentScores);
|
|
36
|
+
const minRecent = Math.min(...recentScores);
|
|
37
|
+
// Check 1: Stagnation - all recent scores are within 5% of each other
|
|
38
|
+
if ((maxRecent - minRecent) <= 5) {
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
// Check 2: Oscillation - detect if we're going up and down without making progress
|
|
42
|
+
// e.g., 70 -> 85 -> 75 -> 80 (oscillating around ~77.5)
|
|
43
|
+
if (recentScores.length >= 3) {
|
|
44
|
+
const avg = recentScores.reduce((a, b) => a + b, 0) / recentScores.length;
|
|
45
|
+
const deviations = recentScores.map(s => Math.abs(s - avg));
|
|
46
|
+
const avgDeviation = deviations.reduce((a, b) => a + b, 0) / deviations.length;
|
|
47
|
+
// If scores are oscillating around an average (avg deviation > 3% but range < 20%)
|
|
48
|
+
// and we're not trending upward, consider it stuck
|
|
49
|
+
if (avgDeviation > 3 && (maxRecent - minRecent) < 20) {
|
|
50
|
+
// Check if we're trending upward (last score should be close to max)
|
|
51
|
+
const lastScore = recentScores[recentScores.length - 1];
|
|
52
|
+
const firstScore = recentScores[0];
|
|
53
|
+
// Not improving if last score is not better than first
|
|
54
|
+
if (lastScore <= firstScore + 2) {
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Default consensus timeout (15 minutes total)
|
|
63
|
+
*/
|
|
64
|
+
const DEFAULT_CONSENSUS_TIMEOUT_MS = 15 * 60 * 1000;
|
|
9
65
|
/**
|
|
10
66
|
* Format a plan for consensus review
|
|
11
67
|
* Structures the plan in a way that's optimal for review
|
|
@@ -55,6 +111,7 @@ export function meetsThreshold(score, threshold = DEFAULT_CONSENSUS_CONFIG.thres
|
|
|
55
111
|
}
|
|
56
112
|
/**
|
|
57
113
|
* Iterate until consensus is reached
|
|
114
|
+
* Supports configurable reviewer and arbitration when stuck
|
|
58
115
|
*
|
|
59
116
|
* @param initialPlan - The initial plan to review
|
|
60
117
|
* @param context - Project context
|
|
@@ -62,15 +119,89 @@ export function meetsThreshold(score, threshold = DEFAULT_CONSENSUS_CONFIG.thres
|
|
|
62
119
|
* @returns The consensus process result
|
|
63
120
|
*/
|
|
64
121
|
export async function iterateUntilConsensus(initialPlan, context, options) {
|
|
65
|
-
const { projectDir, config = {}, onIteration, onRevision, } = options;
|
|
66
|
-
const { threshold = DEFAULT_CONSENSUS_CONFIG.threshold, maxIterations = DEFAULT_CONSENSUS_CONFIG.maxIterations, } = config;
|
|
122
|
+
const { projectDir, config = {}, onIteration, onRevision, onConcerns, onArbitration, onProgress, } = options;
|
|
123
|
+
const { threshold = DEFAULT_CONSENSUS_CONFIG.threshold, maxIterations = DEFAULT_CONSENSUS_CONFIG.maxIterations, reviewer = DEFAULT_CONSENSUS_CONFIG.reviewer, arbitrator = DEFAULT_CONSENSUS_CONFIG.arbitrator, enableArbitration = DEFAULT_CONSENSUS_CONFIG.enableArbitration, arbitrationThreshold = DEFAULT_CONSENSUS_CONFIG.arbitrationThreshold, stuckIterations = DEFAULT_CONSENSUS_CONFIG.stuckIterations, } = config;
|
|
67
124
|
const iterations = [];
|
|
125
|
+
const scores = [];
|
|
68
126
|
let currentPlan = initialPlan;
|
|
69
127
|
let iteration = 0;
|
|
128
|
+
// Track the best plan throughout the process
|
|
129
|
+
let bestPlan = initialPlan;
|
|
130
|
+
let bestScore = 0;
|
|
131
|
+
let bestIteration = 0;
|
|
132
|
+
let lastConcerns = [];
|
|
133
|
+
let lastRecommendations = [];
|
|
134
|
+
let lastAnalysis = '';
|
|
135
|
+
// Track arbitration attempts to prevent infinite loops
|
|
136
|
+
let arbitrationAttempts = 0;
|
|
137
|
+
// Track elapsed time to detect stuck processes
|
|
138
|
+
const startTime = Date.now();
|
|
139
|
+
const maxArbitrationAttempts = 2;
|
|
140
|
+
onProgress?.('consensus', `Using ${reviewer} as reviewer${enableArbitration ? `, ${arbitrator} as arbitrator` : ''}`);
|
|
70
141
|
while (iteration < maxIterations) {
|
|
71
142
|
iteration++;
|
|
72
|
-
//
|
|
73
|
-
const
|
|
143
|
+
// Check total elapsed time - if timing out, try arbitration before giving up
|
|
144
|
+
const totalElapsed = Date.now() - startTime;
|
|
145
|
+
if (totalElapsed > DEFAULT_CONSENSUS_TIMEOUT_MS && enableArbitration && arbitrationAttempts < maxArbitrationAttempts) {
|
|
146
|
+
onProgress?.('consensus', `Consensus timeout after ${Math.round(totalElapsed / 60000)} minutes - invoking arbitrator before accepting`);
|
|
147
|
+
try {
|
|
148
|
+
arbitrationAttempts++;
|
|
149
|
+
const arbitrationResult = await requestGeminiArbitration(bestPlan, lastAnalysis, `Consensus timed out after ${Math.round(totalElapsed / 60000)} minutes. Best score: ${bestScore}%. Main concerns: ${lastConcerns.slice(0, 3).join('; ')}`, iteration, scores);
|
|
150
|
+
if (onArbitration) {
|
|
151
|
+
onArbitration(arbitrationResult);
|
|
152
|
+
}
|
|
153
|
+
// Accept arbitration result (we're out of time)
|
|
154
|
+
onProgress?.('arbitration', `Arbitrator decision: ${arbitrationResult.approved ? 'APPROVED' : 'REVISE'} with ${arbitrationResult.score}%`);
|
|
155
|
+
return {
|
|
156
|
+
approved: arbitrationResult.approved || arbitrationResult.score >= 80,
|
|
157
|
+
finalPlan: bestPlan,
|
|
158
|
+
finalScore: arbitrationResult.score,
|
|
159
|
+
bestPlan,
|
|
160
|
+
bestScore: arbitrationResult.score,
|
|
161
|
+
bestIteration,
|
|
162
|
+
iterations,
|
|
163
|
+
totalIterations: iteration - 1,
|
|
164
|
+
finalConcerns: arbitrationResult.minorConcerns || lastConcerns,
|
|
165
|
+
finalRecommendations: arbitrationResult.suggestedChanges || lastRecommendations,
|
|
166
|
+
arbitrated: true,
|
|
167
|
+
arbitrationResult,
|
|
168
|
+
timedOut: true,
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
catch (arbError) {
|
|
172
|
+
onProgress?.('arbitration', `Arbitration failed on timeout: ${arbError instanceof Error ? arbError.message : 'Unknown error'}`);
|
|
173
|
+
// Fall through to accept best plan
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
// Hard timeout - no more arbitration attempts left
|
|
177
|
+
if (totalElapsed > DEFAULT_CONSENSUS_TIMEOUT_MS) {
|
|
178
|
+
onProgress?.('consensus', `Consensus timeout - accepting best plan with ${bestScore}%`);
|
|
179
|
+
return {
|
|
180
|
+
approved: bestScore >= arbitrationThreshold,
|
|
181
|
+
finalPlan: bestPlan,
|
|
182
|
+
finalScore: bestScore,
|
|
183
|
+
bestPlan,
|
|
184
|
+
bestScore,
|
|
185
|
+
bestIteration,
|
|
186
|
+
iterations,
|
|
187
|
+
totalIterations: iteration - 1,
|
|
188
|
+
finalConcerns: lastConcerns,
|
|
189
|
+
finalRecommendations: lastRecommendations,
|
|
190
|
+
arbitrated: false,
|
|
191
|
+
timedOut: true,
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
// Log iteration timing
|
|
195
|
+
const iterationStart = Date.now();
|
|
196
|
+
const elapsedMinutes = Math.round((iterationStart - startTime) / 60000);
|
|
197
|
+
onProgress?.('consensus', `Iteration ${iteration} starting (${elapsedMinutes}min elapsed)`);
|
|
198
|
+
// Request consensus review from configured reviewer
|
|
199
|
+
onProgress?.('consensus', `Requesting review from ${reviewer}...`);
|
|
200
|
+
const consensusResult = await requestReviewerConsensus(currentPlan, context, reviewer, config);
|
|
201
|
+
// Log iteration duration
|
|
202
|
+
const iterationDuration = Math.round((Date.now() - iterationStart) / 1000);
|
|
203
|
+
onProgress?.('consensus', `Review completed in ${iterationDuration}s - score: ${consensusResult.score}%`);
|
|
204
|
+
scores.push(consensusResult.score);
|
|
74
205
|
// Record the iteration
|
|
75
206
|
const iterationRecord = {
|
|
76
207
|
iteration,
|
|
@@ -81,45 +212,155 @@ export async function iterateUntilConsensus(initialPlan, context, options) {
|
|
|
81
212
|
iterations.push(iterationRecord);
|
|
82
213
|
// Save to project state
|
|
83
214
|
await recordConsensusIteration(projectDir, iterationRecord);
|
|
84
|
-
//
|
|
215
|
+
// Track best plan - only update if this score is better
|
|
216
|
+
if (consensusResult.score > bestScore) {
|
|
217
|
+
bestPlan = currentPlan;
|
|
218
|
+
bestScore = consensusResult.score;
|
|
219
|
+
bestIteration = iteration;
|
|
220
|
+
}
|
|
221
|
+
// Track concerns for output
|
|
222
|
+
lastConcerns = consensusResult.concerns || [];
|
|
223
|
+
lastRecommendations = consensusResult.recommendations || [];
|
|
224
|
+
lastAnalysis = consensusResult.analysis || '';
|
|
225
|
+
// Notify callbacks
|
|
85
226
|
if (onIteration) {
|
|
86
227
|
onIteration(iteration, consensusResult);
|
|
87
228
|
}
|
|
229
|
+
if (onConcerns && (lastConcerns.length > 0 || lastRecommendations.length > 0)) {
|
|
230
|
+
onConcerns(lastConcerns, lastRecommendations);
|
|
231
|
+
}
|
|
88
232
|
// Check if we've reached consensus
|
|
89
233
|
if (meetsThreshold(consensusResult.score, threshold)) {
|
|
90
234
|
return {
|
|
91
235
|
approved: true,
|
|
92
236
|
finalPlan: currentPlan,
|
|
93
237
|
finalScore: consensusResult.score,
|
|
238
|
+
bestPlan: currentPlan,
|
|
239
|
+
bestScore: consensusResult.score,
|
|
240
|
+
bestIteration: iteration,
|
|
94
241
|
iterations,
|
|
95
242
|
totalIterations: iteration,
|
|
243
|
+
finalConcerns: [],
|
|
244
|
+
finalRecommendations: [],
|
|
245
|
+
arbitrated: false,
|
|
96
246
|
};
|
|
97
247
|
}
|
|
248
|
+
// Check if we're stuck and should trigger arbitration
|
|
249
|
+
if (enableArbitration &&
|
|
250
|
+
bestScore >= arbitrationThreshold &&
|
|
251
|
+
isStuck(scores, stuckIterations) &&
|
|
252
|
+
arbitrationAttempts < maxArbitrationAttempts) {
|
|
253
|
+
arbitrationAttempts++;
|
|
254
|
+
onProgress?.('arbitration', `Consensus stuck at ${bestScore}%, invoking ${arbitrator} arbitrator (attempt ${arbitrationAttempts}/${maxArbitrationAttempts})...`);
|
|
255
|
+
try {
|
|
256
|
+
const arbitrationResult = await requestGeminiArbitration(bestPlan, lastAnalysis, `The plan has been revised ${iteration} times. Best score achieved: ${bestScore}%. The reviewer's main concerns are: ${lastConcerns.slice(0, 3).join('; ')}`, iteration, scores);
|
|
257
|
+
if (onArbitration) {
|
|
258
|
+
onArbitration(arbitrationResult);
|
|
259
|
+
}
|
|
260
|
+
// Accept if arbitrator approves OR if arbitrator gives a high score (>= 88%)
|
|
261
|
+
// This prevents infinite REVISE loops when the arbitrator is happy enough
|
|
262
|
+
const acceptArbitration = arbitrationResult.approved ||
|
|
263
|
+
arbitrationResult.score >= 88 ||
|
|
264
|
+
(arbitrationAttempts >= maxArbitrationAttempts && arbitrationResult.score >= 80);
|
|
265
|
+
if (acceptArbitration) {
|
|
266
|
+
const reason = arbitrationResult.approved
|
|
267
|
+
? `Arbitrator approved plan with ${arbitrationResult.score}% confidence`
|
|
268
|
+
: `Arbitrator score ${arbitrationResult.score}% is acceptable - proceeding with best plan`;
|
|
269
|
+
onProgress?.('arbitration', reason);
|
|
270
|
+
return {
|
|
271
|
+
approved: true,
|
|
272
|
+
finalPlan: bestPlan,
|
|
273
|
+
finalScore: arbitrationResult.score,
|
|
274
|
+
bestPlan,
|
|
275
|
+
bestScore: arbitrationResult.score,
|
|
276
|
+
bestIteration,
|
|
277
|
+
iterations,
|
|
278
|
+
totalIterations: iteration,
|
|
279
|
+
finalConcerns: arbitrationResult.minorConcerns || [],
|
|
280
|
+
finalRecommendations: arbitrationResult.suggestedChanges || [],
|
|
281
|
+
arbitrated: true,
|
|
282
|
+
arbitrationResult,
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
else {
|
|
286
|
+
onProgress?.('arbitration', `Arbitrator requests changes: ${arbitrationResult.suggestedChanges.slice(0, 2).join('; ')}`);
|
|
287
|
+
// Apply arbitrator's suggested changes
|
|
288
|
+
if (arbitrationResult.suggestedChanges.length > 0) {
|
|
289
|
+
onProgress?.('consensus', 'Applying arbitrator suggestions...');
|
|
290
|
+
const revisionResult = await revisePlan(bestPlan, arbitrationResult.reasoning, arbitrationResult.suggestedChanges);
|
|
291
|
+
if (revisionResult.success && revisionResult.response) {
|
|
292
|
+
currentPlan = revisionResult.response;
|
|
293
|
+
// Reset stuck detection after arbitration revision
|
|
294
|
+
scores.length = 0;
|
|
295
|
+
scores.push(arbitrationResult.score);
|
|
296
|
+
onProgress?.('consensus', 'Plan revised based on arbitrator feedback');
|
|
297
|
+
}
|
|
298
|
+
else {
|
|
299
|
+
onProgress?.('consensus', 'Revision failed, continuing with current plan');
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
catch (error) {
|
|
305
|
+
onProgress?.('arbitration', `Arbitration failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
306
|
+
// If we've tried arbitration and it failed, accept the best plan we have
|
|
307
|
+
if (arbitrationAttempts >= maxArbitrationAttempts && bestScore >= arbitrationThreshold) {
|
|
308
|
+
onProgress?.('arbitration', `Max arbitration attempts reached, accepting best plan with ${bestScore}%`);
|
|
309
|
+
return {
|
|
310
|
+
approved: true,
|
|
311
|
+
finalPlan: bestPlan,
|
|
312
|
+
finalScore: bestScore,
|
|
313
|
+
bestPlan,
|
|
314
|
+
bestScore,
|
|
315
|
+
bestIteration,
|
|
316
|
+
iterations,
|
|
317
|
+
totalIterations: iteration,
|
|
318
|
+
finalConcerns: lastConcerns,
|
|
319
|
+
finalRecommendations: lastRecommendations,
|
|
320
|
+
arbitrated: true,
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
}
|
|
98
325
|
// If not at max iterations, revise the plan
|
|
99
326
|
if (iteration < maxIterations) {
|
|
100
327
|
const concerns = extractConcerns(consensusResult);
|
|
328
|
+
onProgress?.('consensus', 'Revising plan based on feedback...');
|
|
329
|
+
// Create a progress handler for revision
|
|
330
|
+
const revisionProgress = onProgress
|
|
331
|
+
? (msg) => onProgress('consensus', `[revision] ${msg}`)
|
|
332
|
+
: undefined;
|
|
101
333
|
// Use Claude to revise the plan
|
|
102
|
-
const revisionResult = await revisePlan(currentPlan, consensusResult.analysis, concerns);
|
|
334
|
+
const revisionResult = await revisePlan(currentPlan, consensusResult.analysis, concerns, revisionProgress);
|
|
103
335
|
if (revisionResult.success && revisionResult.response) {
|
|
336
|
+
// Only use the revised plan for the next iteration
|
|
337
|
+
// The best plan tracking above will decide if it's actually better
|
|
104
338
|
currentPlan = revisionResult.response;
|
|
105
339
|
if (onRevision) {
|
|
106
340
|
onRevision(iteration, currentPlan);
|
|
107
341
|
}
|
|
108
342
|
}
|
|
109
343
|
else {
|
|
110
|
-
// If revision fails, try to continue with
|
|
344
|
+
// If revision fails, try to continue with best plan
|
|
111
345
|
console.warn(`Plan revision failed at iteration ${iteration}:`, revisionResult.error);
|
|
346
|
+
currentPlan = bestPlan;
|
|
112
347
|
}
|
|
113
348
|
}
|
|
114
349
|
}
|
|
115
350
|
// Max iterations reached without consensus
|
|
116
|
-
|
|
351
|
+
// Return the BEST plan we found, not the last one
|
|
117
352
|
return {
|
|
118
353
|
approved: false,
|
|
119
|
-
finalPlan:
|
|
120
|
-
finalScore:
|
|
354
|
+
finalPlan: bestPlan,
|
|
355
|
+
finalScore: bestScore,
|
|
356
|
+
bestPlan,
|
|
357
|
+
bestScore,
|
|
358
|
+
bestIteration,
|
|
121
359
|
iterations,
|
|
122
360
|
totalIterations: iteration,
|
|
361
|
+
finalConcerns: lastConcerns,
|
|
362
|
+
finalRecommendations: lastRecommendations,
|
|
363
|
+
arbitrated: false,
|
|
123
364
|
};
|
|
124
365
|
}
|
|
125
366
|
/**
|
|
@@ -132,27 +373,48 @@ export function summarizeConsensusProcess(result) {
|
|
|
132
373
|
const lines = [];
|
|
133
374
|
lines.push(`## Consensus Summary`);
|
|
134
375
|
lines.push('');
|
|
135
|
-
lines.push(`**Status:** ${result.approved ? 'APPROVED' : 'NOT APPROVED'}`);
|
|
376
|
+
lines.push(`**Status:** ${result.approved ? 'APPROVED' : 'NOT APPROVED'}${result.arbitrated ? ' (via arbitration)' : ''}`);
|
|
136
377
|
lines.push(`**Final Score:** ${result.finalScore}%`);
|
|
378
|
+
lines.push(`**Best Score:** ${result.bestScore}% (iteration ${result.bestIteration})`);
|
|
137
379
|
lines.push(`**Total Iterations:** ${result.totalIterations}`);
|
|
380
|
+
if (result.arbitrated && result.arbitrationResult) {
|
|
381
|
+
lines.push('');
|
|
382
|
+
lines.push(`### Arbitration Decision`);
|
|
383
|
+
lines.push(`- Decision: ${result.arbitrationResult.approved ? 'APPROVED' : 'REVISE'}`);
|
|
384
|
+
lines.push(`- Confidence: ${result.arbitrationResult.score}%`);
|
|
385
|
+
if (result.arbitrationResult.criticalConcerns.length > 0) {
|
|
386
|
+
lines.push(`- Critical Concerns: ${result.arbitrationResult.criticalConcerns.length}`);
|
|
387
|
+
}
|
|
388
|
+
if (result.arbitrationResult.minorConcerns.length > 0) {
|
|
389
|
+
lines.push(`- Minor Concerns: ${result.arbitrationResult.minorConcerns.length}`);
|
|
390
|
+
}
|
|
391
|
+
}
|
|
138
392
|
lines.push('');
|
|
139
393
|
lines.push(`### Iteration History`);
|
|
140
394
|
lines.push('');
|
|
141
395
|
for (const iteration of result.iterations) {
|
|
142
|
-
|
|
396
|
+
const isBest = iteration.iteration === result.bestIteration;
|
|
397
|
+
lines.push(`#### Iteration ${iteration.iteration}${isBest ? ' (BEST)' : ''}`);
|
|
143
398
|
lines.push(`- Score: ${iteration.result.score}%`);
|
|
144
399
|
lines.push(`- Strengths: ${iteration.result.strengths?.length || 0}`);
|
|
145
400
|
lines.push(`- Concerns: ${iteration.result.concerns?.length || 0}`);
|
|
146
401
|
lines.push('');
|
|
147
402
|
}
|
|
148
403
|
if (!result.approved) {
|
|
149
|
-
|
|
150
|
-
if (lastResult?.concerns && lastResult.concerns.length > 0) {
|
|
404
|
+
if (result.finalConcerns && result.finalConcerns.length > 0) {
|
|
151
405
|
lines.push(`### Remaining Concerns`);
|
|
152
406
|
lines.push('');
|
|
153
|
-
for (const concern of
|
|
407
|
+
for (const concern of result.finalConcerns) {
|
|
154
408
|
lines.push(`- ${concern}`);
|
|
155
409
|
}
|
|
410
|
+
lines.push('');
|
|
411
|
+
}
|
|
412
|
+
if (result.finalRecommendations && result.finalRecommendations.length > 0) {
|
|
413
|
+
lines.push(`### Recommendations`);
|
|
414
|
+
lines.push('');
|
|
415
|
+
for (const rec of result.finalRecommendations) {
|
|
416
|
+
lines.push(`- ${rec}`);
|
|
417
|
+
}
|
|
156
418
|
}
|
|
157
419
|
}
|
|
158
420
|
return lines.join('\n');
|
|
@@ -217,4 +479,290 @@ export function getScoreTrend(iterations) {
|
|
|
217
479
|
return 'declining';
|
|
218
480
|
return 'stable';
|
|
219
481
|
}
|
|
482
|
+
/**
|
|
483
|
+
* Collect feedback from a single reviewer
|
|
484
|
+
*/
|
|
485
|
+
async function collectReviewerFeedback(plan, context, reviewer, config, onProgress) {
|
|
486
|
+
onProgress?.('consensus', `Requesting review from ${reviewer}...`);
|
|
487
|
+
const startTime = Date.now();
|
|
488
|
+
const result = await requestReviewerConsensus(plan, context, reviewer, config);
|
|
489
|
+
const duration = Math.round((Date.now() - startTime) / 1000);
|
|
490
|
+
onProgress?.('consensus', `${reviewer} review completed in ${duration}s - score: ${result.score}%`);
|
|
491
|
+
return {
|
|
492
|
+
reviewer,
|
|
493
|
+
score: result.score,
|
|
494
|
+
timestamp: new Date().toISOString(),
|
|
495
|
+
concerns: result.concerns || [],
|
|
496
|
+
recommendations: result.recommendations || [],
|
|
497
|
+
analysis: result.analysis || '',
|
|
498
|
+
};
|
|
499
|
+
}
|
|
500
|
+
/**
|
|
501
|
+
* Collect feedback from multiple reviewers in parallel
|
|
502
|
+
*/
|
|
503
|
+
async function collectAllFeedback(plan, context, reviewers, config, onProgress) {
|
|
504
|
+
onProgress?.('consensus', `Collecting feedback from ${reviewers.length} reviewer(s) in parallel...`);
|
|
505
|
+
const feedbackPromises = reviewers.map(reviewer => collectReviewerFeedback(plan, context, reviewer, config, onProgress)
|
|
506
|
+
.catch(error => {
|
|
507
|
+
onProgress?.('consensus', `${reviewer} review failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
508
|
+
return null;
|
|
509
|
+
}));
|
|
510
|
+
const results = await Promise.all(feedbackPromises);
|
|
511
|
+
return results.filter((f) => f !== null);
|
|
512
|
+
}
|
|
513
|
+
/**
|
|
514
|
+
* Optimized consensus process that batches feedback and reduces API calls
|
|
515
|
+
*
|
|
516
|
+
* Key optimizations:
|
|
517
|
+
* 1. Plans stored in files, not regenerated from scratch
|
|
518
|
+
* 2. Collects ALL reviewer feedback before revision
|
|
519
|
+
* 3. Claude revises ONCE per round with combined feedback
|
|
520
|
+
* 4. Parallel reviews when multiple reviewers configured
|
|
521
|
+
*
|
|
522
|
+
* @param initialPlan - The initial plan to seek consensus on
|
|
523
|
+
* @param context - Project context for review
|
|
524
|
+
* @param options - Consensus options including tracking info
|
|
525
|
+
* @returns Consensus process result
|
|
526
|
+
*/
|
|
527
|
+
export async function runOptimizedConsensusProcess(initialPlan, context, options) {
|
|
528
|
+
const { projectDir, config = {}, onIteration, onRevision, onConcerns, onArbitration, onProgress, milestoneId, milestoneName, taskId, taskName, parallelReviews = true, additionalReviewers = [], } = options;
|
|
529
|
+
const { threshold = DEFAULT_CONSENSUS_CONFIG.threshold, maxIterations = DEFAULT_CONSENSUS_CONFIG.maxIterations, reviewer = DEFAULT_CONSENSUS_CONFIG.reviewer, arbitrator = DEFAULT_CONSENSUS_CONFIG.arbitrator, enableArbitration = DEFAULT_CONSENSUS_CONFIG.enableArbitration, arbitrationThreshold = DEFAULT_CONSENSUS_CONFIG.arbitrationThreshold, stuckIterations = DEFAULT_CONSENSUS_CONFIG.stuckIterations, } = config;
|
|
530
|
+
// Initialize plan storage
|
|
531
|
+
const planStorage = createPlanStorage(projectDir);
|
|
532
|
+
await planStorage.initialize();
|
|
533
|
+
// Determine all reviewers
|
|
534
|
+
const allReviewers = [reviewer, ...additionalReviewers.filter(r => r !== reviewer)];
|
|
535
|
+
const iterations = [];
|
|
536
|
+
const scores = [];
|
|
537
|
+
let currentPlan = initialPlan;
|
|
538
|
+
let iteration = 0;
|
|
539
|
+
// Track the best plan
|
|
540
|
+
let bestPlan = initialPlan;
|
|
541
|
+
let bestScore = 0;
|
|
542
|
+
let bestIteration = 0;
|
|
543
|
+
let lastConcerns = [];
|
|
544
|
+
let lastRecommendations = [];
|
|
545
|
+
let lastAnalysis = '';
|
|
546
|
+
const startTime = Date.now();
|
|
547
|
+
onProgress?.('consensus', `Using optimized consensus with ${allReviewers.join(', ')} as reviewer(s)`);
|
|
548
|
+
onProgress?.('consensus', `Plan tracking: milestone=${milestoneId}${taskId ? `, task=${taskId}` : ''}`);
|
|
549
|
+
// Save initial plan to storage
|
|
550
|
+
await planStorage.savePlan(currentPlan, taskId ? 'task' : 'milestone', {
|
|
551
|
+
milestoneId,
|
|
552
|
+
milestoneName,
|
|
553
|
+
taskId,
|
|
554
|
+
taskName,
|
|
555
|
+
});
|
|
556
|
+
while (iteration < maxIterations) {
|
|
557
|
+
iteration++;
|
|
558
|
+
// Check timeout
|
|
559
|
+
const totalElapsed = Date.now() - startTime;
|
|
560
|
+
if (totalElapsed > DEFAULT_CONSENSUS_TIMEOUT_MS) {
|
|
561
|
+
onProgress?.('consensus', `Consensus timeout after ${Math.round(totalElapsed / 60000)} minutes`);
|
|
562
|
+
if (enableArbitration) {
|
|
563
|
+
try {
|
|
564
|
+
const arbitrationResult = await requestGeminiArbitration(bestPlan, lastAnalysis, `Timeout. Best score: ${bestScore}%. Concerns: ${lastConcerns.slice(0, 3).join('; ')}`, iteration, scores);
|
|
565
|
+
if (onArbitration)
|
|
566
|
+
onArbitration(arbitrationResult);
|
|
567
|
+
return {
|
|
568
|
+
approved: arbitrationResult.approved || arbitrationResult.score >= 80,
|
|
569
|
+
finalPlan: bestPlan,
|
|
570
|
+
finalScore: arbitrationResult.score,
|
|
571
|
+
bestPlan,
|
|
572
|
+
bestScore: arbitrationResult.score,
|
|
573
|
+
bestIteration,
|
|
574
|
+
iterations,
|
|
575
|
+
totalIterations: iteration - 1,
|
|
576
|
+
finalConcerns: arbitrationResult.minorConcerns || lastConcerns,
|
|
577
|
+
finalRecommendations: arbitrationResult.suggestedChanges || lastRecommendations,
|
|
578
|
+
arbitrated: true,
|
|
579
|
+
arbitrationResult,
|
|
580
|
+
timedOut: true,
|
|
581
|
+
};
|
|
582
|
+
}
|
|
583
|
+
catch {
|
|
584
|
+
// Fall through to accept best plan
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
return {
|
|
588
|
+
approved: bestScore >= arbitrationThreshold,
|
|
589
|
+
finalPlan: bestPlan,
|
|
590
|
+
finalScore: bestScore,
|
|
591
|
+
bestPlan,
|
|
592
|
+
bestScore,
|
|
593
|
+
bestIteration,
|
|
594
|
+
iterations,
|
|
595
|
+
totalIterations: iteration - 1,
|
|
596
|
+
finalConcerns: lastConcerns,
|
|
597
|
+
finalRecommendations: lastRecommendations,
|
|
598
|
+
arbitrated: false,
|
|
599
|
+
timedOut: true,
|
|
600
|
+
};
|
|
601
|
+
}
|
|
602
|
+
const elapsedMinutes = Math.round((Date.now() - startTime) / 60000);
|
|
603
|
+
onProgress?.('consensus', `Iteration ${iteration} starting (${elapsedMinutes}min elapsed)`);
|
|
604
|
+
// Clear previous feedback for this round
|
|
605
|
+
await planStorage.clearFeedback(milestoneId, taskId);
|
|
606
|
+
// ============================================
|
|
607
|
+
// OPTIMIZATION: Collect ALL feedback in parallel
|
|
608
|
+
// ============================================
|
|
609
|
+
let allFeedback;
|
|
610
|
+
if (parallelReviews && allReviewers.length > 1) {
|
|
611
|
+
allFeedback = await collectAllFeedback(currentPlan, context, allReviewers, config, onProgress);
|
|
612
|
+
}
|
|
613
|
+
else {
|
|
614
|
+
// Sequential fallback
|
|
615
|
+
allFeedback = [];
|
|
616
|
+
for (const rev of allReviewers) {
|
|
617
|
+
const feedback = await collectReviewerFeedback(currentPlan, context, rev, config, onProgress);
|
|
618
|
+
allFeedback.push(feedback);
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
// Save all feedback
|
|
622
|
+
for (const feedback of allFeedback) {
|
|
623
|
+
await planStorage.saveFeedback(feedback, milestoneId, taskId);
|
|
624
|
+
}
|
|
625
|
+
// Calculate combined score (average of all reviewers)
|
|
626
|
+
const combinedScore = allFeedback.length > 0
|
|
627
|
+
? Math.round(allFeedback.reduce((sum, f) => sum + f.score, 0) / allFeedback.length)
|
|
628
|
+
: 0;
|
|
629
|
+
scores.push(combinedScore);
|
|
630
|
+
// Combine all concerns and recommendations
|
|
631
|
+
const allConcerns = [...new Set(allFeedback.flatMap(f => f.concerns))];
|
|
632
|
+
const allRecommendations = [...new Set(allFeedback.flatMap(f => f.recommendations))];
|
|
633
|
+
const combinedAnalysis = allFeedback.map(f => `[${f.reviewer}] ${f.analysis}`).join('\n\n');
|
|
634
|
+
lastConcerns = allConcerns;
|
|
635
|
+
lastRecommendations = allRecommendations;
|
|
636
|
+
lastAnalysis = combinedAnalysis;
|
|
637
|
+
// Create consensus result for tracking
|
|
638
|
+
const consensusResult = {
|
|
639
|
+
score: combinedScore,
|
|
640
|
+
analysis: combinedAnalysis,
|
|
641
|
+
concerns: allConcerns,
|
|
642
|
+
recommendations: allRecommendations,
|
|
643
|
+
approved: combinedScore >= threshold,
|
|
644
|
+
strengths: [],
|
|
645
|
+
rawResponse: combinedAnalysis,
|
|
646
|
+
};
|
|
647
|
+
// Record iteration
|
|
648
|
+
const iterationRecord = {
|
|
649
|
+
iteration,
|
|
650
|
+
plan: currentPlan,
|
|
651
|
+
timestamp: new Date().toISOString(),
|
|
652
|
+
result: consensusResult,
|
|
653
|
+
};
|
|
654
|
+
iterations.push(iterationRecord);
|
|
655
|
+
if (onIteration)
|
|
656
|
+
onIteration(iteration, consensusResult);
|
|
657
|
+
if (onConcerns)
|
|
658
|
+
onConcerns(allConcerns, allRecommendations);
|
|
659
|
+
// Update best plan tracking
|
|
660
|
+
if (combinedScore > bestScore) {
|
|
661
|
+
bestScore = combinedScore;
|
|
662
|
+
bestPlan = currentPlan;
|
|
663
|
+
bestIteration = iteration;
|
|
664
|
+
}
|
|
665
|
+
// Save plan with updated score
|
|
666
|
+
await planStorage.savePlan(currentPlan, taskId ? 'task' : 'milestone', {
|
|
667
|
+
milestoneId,
|
|
668
|
+
milestoneName,
|
|
669
|
+
taskId,
|
|
670
|
+
taskName,
|
|
671
|
+
score: combinedScore,
|
|
672
|
+
});
|
|
673
|
+
// Record in project state
|
|
674
|
+
await recordConsensusIteration(projectDir, iterationRecord);
|
|
675
|
+
onProgress?.('consensus', `Combined score: ${combinedScore}% (from ${allFeedback.length} reviewer(s))`);
|
|
676
|
+
// Check if consensus reached
|
|
677
|
+
if (combinedScore >= threshold) {
|
|
678
|
+
onProgress?.('consensus', `Consensus reached at ${combinedScore}%`);
|
|
679
|
+
await planStorage.updateStatus('approved', milestoneId, taskId);
|
|
680
|
+
return {
|
|
681
|
+
approved: true,
|
|
682
|
+
finalPlan: currentPlan,
|
|
683
|
+
finalScore: combinedScore,
|
|
684
|
+
bestPlan: currentPlan,
|
|
685
|
+
bestScore: combinedScore,
|
|
686
|
+
bestIteration: iteration,
|
|
687
|
+
iterations,
|
|
688
|
+
totalIterations: iteration,
|
|
689
|
+
finalConcerns: allConcerns,
|
|
690
|
+
finalRecommendations: allRecommendations,
|
|
691
|
+
arbitrated: false,
|
|
692
|
+
};
|
|
693
|
+
}
|
|
694
|
+
// Check if stuck
|
|
695
|
+
if (isStuck(scores, stuckIterations) && enableArbitration) {
|
|
696
|
+
onProgress?.('consensus', `Consensus stuck - invoking ${arbitrator} for arbitration`);
|
|
697
|
+
try {
|
|
698
|
+
const arbitrationResult = await requestGeminiArbitration(bestPlan, combinedAnalysis, `Stuck after ${iteration} iterations. Scores: ${scores.slice(-stuckIterations).join(', ')}`, iteration, scores);
|
|
699
|
+
if (onArbitration)
|
|
700
|
+
onArbitration(arbitrationResult);
|
|
701
|
+
if (arbitrationResult.approved || arbitrationResult.score >= arbitrationThreshold) {
|
|
702
|
+
onProgress?.('arbitration', `Arbitrator approved with ${arbitrationResult.score}%`);
|
|
703
|
+
await planStorage.updateStatus('approved', milestoneId, taskId);
|
|
704
|
+
return {
|
|
705
|
+
approved: true,
|
|
706
|
+
finalPlan: bestPlan,
|
|
707
|
+
finalScore: arbitrationResult.score,
|
|
708
|
+
bestPlan,
|
|
709
|
+
bestScore: arbitrationResult.score,
|
|
710
|
+
bestIteration,
|
|
711
|
+
iterations,
|
|
712
|
+
totalIterations: iteration,
|
|
713
|
+
finalConcerns: arbitrationResult.minorConcerns || allConcerns,
|
|
714
|
+
finalRecommendations: arbitrationResult.suggestedChanges || allRecommendations,
|
|
715
|
+
arbitrated: true,
|
|
716
|
+
arbitrationResult,
|
|
717
|
+
};
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
catch (arbError) {
|
|
721
|
+
onProgress?.('arbitration', `Arbitration failed: ${arbError instanceof Error ? arbError.message : 'Unknown error'}`);
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
// ============================================
|
|
725
|
+
// OPTIMIZATION: Single revision with ALL feedback
|
|
726
|
+
// ============================================
|
|
727
|
+
if (iteration < maxIterations) {
|
|
728
|
+
onProgress?.('consensus', `Revising plan with combined feedback from ${allFeedback.length} reviewer(s)...`);
|
|
729
|
+
const revisionProgress = onProgress
|
|
730
|
+
? (msg) => onProgress('consensus', `[revision] ${msg}`)
|
|
731
|
+
: undefined;
|
|
732
|
+
// Use Claude to revise with ALL combined feedback (single API call)
|
|
733
|
+
const revisionResult = await revisePlan(currentPlan, combinedAnalysis, allConcerns, revisionProgress);
|
|
734
|
+
if (revisionResult.success && revisionResult.response) {
|
|
735
|
+
currentPlan = revisionResult.response;
|
|
736
|
+
// Save revised plan
|
|
737
|
+
await planStorage.savePlan(currentPlan, taskId ? 'task' : 'milestone', {
|
|
738
|
+
milestoneId,
|
|
739
|
+
milestoneName,
|
|
740
|
+
taskId,
|
|
741
|
+
taskName,
|
|
742
|
+
});
|
|
743
|
+
if (onRevision)
|
|
744
|
+
onRevision(iteration, currentPlan);
|
|
745
|
+
}
|
|
746
|
+
else {
|
|
747
|
+
onProgress?.('consensus', `Revision failed, continuing with best plan`);
|
|
748
|
+
currentPlan = bestPlan;
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
// Max iterations reached
|
|
753
|
+
await planStorage.updateStatus('reviewing', milestoneId, taskId);
|
|
754
|
+
return {
|
|
755
|
+
approved: false,
|
|
756
|
+
finalPlan: bestPlan,
|
|
757
|
+
finalScore: bestScore,
|
|
758
|
+
bestPlan,
|
|
759
|
+
bestScore,
|
|
760
|
+
bestIteration,
|
|
761
|
+
iterations,
|
|
762
|
+
totalIterations: iteration,
|
|
763
|
+
finalConcerns: lastConcerns,
|
|
764
|
+
finalRecommendations: lastRecommendations,
|
|
765
|
+
arbitrated: false,
|
|
766
|
+
};
|
|
767
|
+
}
|
|
220
768
|
//# sourceMappingURL=consensus.js.map
|