popeye-cli 1.0.1 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +24 -1
- package/CONTRIBUTING.md +275 -0
- package/OPEN_SOURCE_MANIFESTO.md +172 -0
- package/README.md +832 -123
- package/dist/adapters/claude.d.ts +19 -4
- package/dist/adapters/claude.d.ts.map +1 -1
- package/dist/adapters/claude.js +908 -42
- 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/grok.d.ts +73 -0
- package/dist/adapters/grok.d.ts.map +1 -0
- package/dist/adapters/grok.js +430 -0
- package/dist/adapters/grok.js.map +1 -0
- package/dist/adapters/openai.d.ts +1 -1
- package/dist/adapters/openai.d.ts.map +1 -1
- package/dist/adapters/openai.js +47 -8
- 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/grok.d.ts +73 -0
- package/dist/auth/grok.d.ts.map +1 -0
- package/dist/auth/grok.js +211 -0
- package/dist/auth/grok.js.map +1 -0
- package/dist/auth/index.d.ts +14 -7
- package/dist/auth/index.d.ts.map +1 -1
- package/dist/auth/index.js +41 -6
- 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/commands/auth.d.ts +1 -1
- package/dist/cli/commands/auth.d.ts.map +1 -1
- package/dist/cli/commands/auth.js +79 -8
- package/dist/cli/commands/auth.js.map +1 -1
- package/dist/cli/commands/create.d.ts.map +1 -1
- package/dist/cli/commands/create.js +15 -4
- package/dist/cli/commands/create.js.map +1 -1
- package/dist/cli/interactive.d.ts.map +1 -1
- package/dist/cli/interactive.js +1494 -114
- package/dist/cli/interactive.js.map +1 -1
- package/dist/config/defaults.d.ts +9 -1
- package/dist/config/defaults.d.ts.map +1 -1
- package/dist/config/defaults.js +19 -2
- package/dist/config/defaults.js.map +1 -1
- package/dist/config/index.d.ts +19 -0
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/index.js +33 -1
- package/dist/config/index.js.map +1 -1
- package/dist/config/schema.d.ts +47 -0
- package/dist/config/schema.d.ts.map +1 -1
- package/dist/config/schema.js +29 -1
- package/dist/config/schema.js.map +1 -1
- package/dist/generators/fullstack.d.ts +32 -0
- package/dist/generators/fullstack.d.ts.map +1 -0
- package/dist/generators/fullstack.js +497 -0
- package/dist/generators/fullstack.js.map +1 -0
- package/dist/generators/index.d.ts +4 -3
- package/dist/generators/index.d.ts.map +1 -1
- package/dist/generators/index.js +15 -1
- package/dist/generators/index.js.map +1 -1
- package/dist/generators/python.d.ts +17 -1
- package/dist/generators/python.d.ts.map +1 -1
- package/dist/generators/python.js +34 -20
- package/dist/generators/python.js.map +1 -1
- package/dist/generators/templates/fullstack.d.ts +113 -0
- package/dist/generators/templates/fullstack.d.ts.map +1 -0
- package/dist/generators/templates/fullstack.js +1004 -0
- package/dist/generators/templates/fullstack.js.map +1 -0
- package/dist/generators/typescript.d.ts +19 -1
- package/dist/generators/typescript.d.ts.map +1 -1
- package/dist/generators/typescript.js +37 -20
- 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 +8 -0
- package/dist/types/cli.d.ts.map +1 -1
- package/dist/types/cli.js.map +1 -1
- package/dist/types/consensus.d.ts +186 -4
- package/dist/types/consensus.d.ts.map +1 -1
- package/dist/types/consensus.js +35 -3
- package/dist/types/consensus.js.map +1 -1
- package/dist/types/project.d.ts +76 -0
- package/dist/types/project.d.ts.map +1 -1
- package/dist/types/project.js +1 -1
- package/dist/types/project.js.map +1 -1
- package/dist/types/workflow.d.ts +217 -16
- package/dist/types/workflow.d.ts.map +1 -1
- package/dist/types/workflow.js +40 -1
- 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 +70 -2
- package/dist/workflow/consensus.d.ts.map +1 -1
- package/dist/workflow/consensus.js +872 -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 +80 -3
- package/dist/workflow/plan-mode.d.ts.map +1 -1
- package/dist/workflow/plan-mode.js +767 -49
- package/dist/workflow/plan-mode.js.map +1 -1
- package/dist/workflow/plan-storage.d.ts +386 -0
- package/dist/workflow/plan-storage.d.ts.map +1 -0
- package/dist/workflow/plan-storage.js +878 -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 +386 -0
- package/dist/workflow/task-workflow.js.map +1 -0
- package/dist/workflow/test-runner.d.ts +9 -0
- package/dist/workflow/test-runner.d.ts.map +1 -1
- package/dist/workflow/test-runner.js +101 -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/dist/workflow/workspace-manager.d.ts +342 -0
- package/dist/workflow/workspace-manager.d.ts.map +1 -0
- package/dist/workflow/workspace-manager.js +733 -0
- package/dist/workflow/workspace-manager.js.map +1 -0
- package/package.json +2 -2
- package/src/adapters/claude.ts +1067 -47
- package/src/adapters/gemini.ts +373 -0
- package/src/adapters/grok.ts +492 -0
- package/src/adapters/openai.ts +48 -9
- package/src/auth/claude.ts +120 -78
- package/src/auth/gemini.ts +207 -0
- package/src/auth/grok.ts +255 -0
- package/src/auth/index.ts +47 -9
- package/src/auth/keychain.ts +95 -28
- package/src/auth/openai.ts +29 -36
- package/src/cli/commands/auth.ts +89 -10
- package/src/cli/commands/create.ts +13 -4
- package/src/cli/interactive.ts +1774 -142
- package/src/config/defaults.ts +19 -2
- package/src/config/index.ts +36 -1
- package/src/config/schema.ts +30 -1
- package/src/generators/fullstack.ts +551 -0
- package/src/generators/index.ts +25 -1
- package/src/generators/python.ts +65 -20
- package/src/generators/templates/fullstack.ts +1047 -0
- package/src/generators/typescript.ts +69 -20
- package/src/state/index.ts +713 -4
- package/src/state/registry.ts +278 -0
- package/src/types/cli.ts +8 -0
- package/src/types/consensus.ts +197 -6
- package/src/types/project.ts +82 -1
- package/src/types/workflow.ts +90 -1
- package/src/workflow/auto-fix.ts +340 -0
- package/src/workflow/consensus.ts +1180 -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 +924 -50
- package/src/workflow/plan-storage.ts +1282 -0
- package/src/workflow/project-verification.ts +471 -0
- package/src/workflow/task-workflow.ts +528 -0
- package/src/workflow/test-runner.ts +120 -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/src/workflow/workspace-manager.ts +912 -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,373 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Google Gemini API adapter
|
|
3
|
+
* Handles consensus reviews and arbitration
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { GoogleGenerativeAI } from '@google/generative-ai';
|
|
7
|
+
import type { ConsensusResult, ArbitrationResult } from '../types/consensus.js';
|
|
8
|
+
import { getGeminiToken } from '../auth/index.js';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Supported Gemini models
|
|
12
|
+
*/
|
|
13
|
+
export type GeminiModel = 'gemini-2.0-flash' | 'gemini-1.5-pro' | 'gemini-1.5-flash';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Default Gemini configuration
|
|
17
|
+
*/
|
|
18
|
+
export const DEFAULT_GEMINI_CONFIG = {
|
|
19
|
+
model: 'gemini-2.0-flash' as GeminiModel,
|
|
20
|
+
temperature: 0.3,
|
|
21
|
+
maxTokens: 4096,
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Create a Gemini client with stored credentials
|
|
26
|
+
*/
|
|
27
|
+
export async function createClient(): Promise<GoogleGenerativeAI> {
|
|
28
|
+
const apiKey = await getGeminiToken();
|
|
29
|
+
|
|
30
|
+
if (!apiKey) {
|
|
31
|
+
throw new Error('Gemini API key not found. Run: popeye auth gemini');
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return new GoogleGenerativeAI(apiKey);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Request consensus review from Gemini
|
|
39
|
+
*
|
|
40
|
+
* @param plan - The development plan to review
|
|
41
|
+
* @param context - Project context
|
|
42
|
+
* @param config - Configuration options
|
|
43
|
+
* @returns Consensus result
|
|
44
|
+
*/
|
|
45
|
+
export async function requestConsensus(
|
|
46
|
+
plan: string,
|
|
47
|
+
context: string,
|
|
48
|
+
config: { model?: GeminiModel; temperature?: number; maxTokens?: number } = {}
|
|
49
|
+
): Promise<ConsensusResult> {
|
|
50
|
+
const {
|
|
51
|
+
model = DEFAULT_GEMINI_CONFIG.model,
|
|
52
|
+
temperature = DEFAULT_GEMINI_CONFIG.temperature,
|
|
53
|
+
maxTokens = DEFAULT_GEMINI_CONFIG.maxTokens,
|
|
54
|
+
} = config;
|
|
55
|
+
|
|
56
|
+
const client = await createClient();
|
|
57
|
+
const generativeModel = client.getGenerativeModel({ model });
|
|
58
|
+
|
|
59
|
+
const prompt = buildConsensusPrompt(plan, context);
|
|
60
|
+
|
|
61
|
+
try {
|
|
62
|
+
const result = await generativeModel.generateContent({
|
|
63
|
+
contents: [{ role: 'user', parts: [{ text: prompt }] }],
|
|
64
|
+
generationConfig: {
|
|
65
|
+
temperature,
|
|
66
|
+
maxOutputTokens: maxTokens,
|
|
67
|
+
},
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
const response = result.response.text();
|
|
71
|
+
return parseConsensusResponse(response);
|
|
72
|
+
} catch (error) {
|
|
73
|
+
const errorMsg = error instanceof Error ? error.message : 'Unknown error';
|
|
74
|
+
throw new Error(`Gemini API error: ${errorMsg}`);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Request arbitration from Gemini when consensus is stuck
|
|
80
|
+
*
|
|
81
|
+
* @param plan - The best plan achieved
|
|
82
|
+
* @param reviewerFeedback - Feedback from the reviewer
|
|
83
|
+
* @param claudeFeedback - Claude's perspective on the plan
|
|
84
|
+
* @param iterations - Number of iterations attempted
|
|
85
|
+
* @param scores - Score history
|
|
86
|
+
* @returns Arbitration decision
|
|
87
|
+
*/
|
|
88
|
+
export async function requestArbitration(
|
|
89
|
+
plan: string,
|
|
90
|
+
reviewerFeedback: string,
|
|
91
|
+
claudeFeedback: string,
|
|
92
|
+
iterations: number,
|
|
93
|
+
scores: number[]
|
|
94
|
+
): Promise<ArbitrationResult> {
|
|
95
|
+
const client = await createClient();
|
|
96
|
+
const generativeModel = client.getGenerativeModel({ model: 'gemini-2.0-flash' });
|
|
97
|
+
|
|
98
|
+
const prompt = buildArbitrationPrompt(plan, reviewerFeedback, claudeFeedback, iterations, scores);
|
|
99
|
+
|
|
100
|
+
try {
|
|
101
|
+
const result = await generativeModel.generateContent({
|
|
102
|
+
contents: [{ role: 'user', parts: [{ text: prompt }] }],
|
|
103
|
+
generationConfig: {
|
|
104
|
+
temperature: 0.2,
|
|
105
|
+
maxOutputTokens: 4096,
|
|
106
|
+
},
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
const response = result.response.text();
|
|
110
|
+
return parseArbitrationResponse(response);
|
|
111
|
+
} catch (error) {
|
|
112
|
+
const errorMsg = error instanceof Error ? error.message : 'Unknown error';
|
|
113
|
+
throw new Error(`Gemini arbitration error: ${errorMsg}`);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Build the consensus review prompt
|
|
119
|
+
*/
|
|
120
|
+
function buildConsensusPrompt(plan: string, context: string): string {
|
|
121
|
+
return `You are a senior software architect reviewing a development plan.
|
|
122
|
+
Analyze the following plan for completeness, correctness, and feasibility.
|
|
123
|
+
|
|
124
|
+
PROJECT CONTEXT:
|
|
125
|
+
${context}
|
|
126
|
+
|
|
127
|
+
PROPOSED PLAN:
|
|
128
|
+
${plan}
|
|
129
|
+
|
|
130
|
+
Please provide your response in EXACTLY this format (use these exact headers):
|
|
131
|
+
|
|
132
|
+
ANALYSIS:
|
|
133
|
+
[Your detailed analysis here]
|
|
134
|
+
|
|
135
|
+
STRENGTHS:
|
|
136
|
+
- [Strength 1]
|
|
137
|
+
- [Strength 2]
|
|
138
|
+
- [etc.]
|
|
139
|
+
|
|
140
|
+
CONCERNS:
|
|
141
|
+
- [Concern 1]
|
|
142
|
+
- [Concern 2]
|
|
143
|
+
- [etc.]
|
|
144
|
+
|
|
145
|
+
RECOMMENDATIONS:
|
|
146
|
+
- [Recommendation 1]
|
|
147
|
+
- [Recommendation 2]
|
|
148
|
+
- [etc.]
|
|
149
|
+
|
|
150
|
+
CONSENSUS: [X]%
|
|
151
|
+
|
|
152
|
+
Scoring guide:
|
|
153
|
+
- 95-100%: Ready for execution, no changes needed
|
|
154
|
+
- 85-94%: Minor revisions needed, mostly good
|
|
155
|
+
- 70-84%: Significant revisions needed
|
|
156
|
+
- Below 70%: Major rework required
|
|
157
|
+
|
|
158
|
+
Be thorough but constructive. Focus on actionable feedback.`;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Build the arbitration prompt
|
|
163
|
+
*/
|
|
164
|
+
function buildArbitrationPrompt(
|
|
165
|
+
plan: string,
|
|
166
|
+
reviewerFeedback: string,
|
|
167
|
+
claudeFeedback: string,
|
|
168
|
+
iterations: number,
|
|
169
|
+
scores: number[]
|
|
170
|
+
): string {
|
|
171
|
+
const scoreHistory = scores.map((s, i) => `Iteration ${i + 1}: ${s}%`).join(', ');
|
|
172
|
+
|
|
173
|
+
return `You are an impartial arbitrator resolving a disagreement between two AI systems about a development plan.
|
|
174
|
+
|
|
175
|
+
SITUATION:
|
|
176
|
+
- Claude (code generator) created a plan
|
|
177
|
+
- A reviewer (OpenAI/Gemini) has been reviewing and providing feedback
|
|
178
|
+
- They have gone through ${iterations} iterations without reaching 95% consensus
|
|
179
|
+
- Score history: ${scoreHistory}
|
|
180
|
+
|
|
181
|
+
THE PLAN:
|
|
182
|
+
${plan}
|
|
183
|
+
|
|
184
|
+
REVIEWER'S LATEST FEEDBACK:
|
|
185
|
+
${reviewerFeedback}
|
|
186
|
+
|
|
187
|
+
CLAUDE'S PERSPECTIVE:
|
|
188
|
+
${claudeFeedback}
|
|
189
|
+
|
|
190
|
+
As the arbitrator, you must:
|
|
191
|
+
1. Analyze both perspectives objectively
|
|
192
|
+
2. Determine if the remaining concerns are:
|
|
193
|
+
- CRITICAL: Must be addressed before proceeding
|
|
194
|
+
- MINOR: Can be addressed during implementation
|
|
195
|
+
- SUBJECTIVE: Matters of preference, not correctness
|
|
196
|
+
3. Make a final decision
|
|
197
|
+
|
|
198
|
+
Respond in EXACTLY this format:
|
|
199
|
+
|
|
200
|
+
ANALYSIS:
|
|
201
|
+
[Your analysis of the disagreement]
|
|
202
|
+
|
|
203
|
+
CRITICAL_CONCERNS:
|
|
204
|
+
- [List any truly critical issues, or "None" if none exist]
|
|
205
|
+
|
|
206
|
+
MINOR_CONCERNS:
|
|
207
|
+
- [List minor issues that can be addressed during implementation]
|
|
208
|
+
|
|
209
|
+
SUBJECTIVE_CONCERNS:
|
|
210
|
+
- [List preference-based concerns that don't affect correctness]
|
|
211
|
+
|
|
212
|
+
DECISION: [APPROVE or REVISE]
|
|
213
|
+
|
|
214
|
+
FINAL_SCORE: [X]%
|
|
215
|
+
|
|
216
|
+
REASONING:
|
|
217
|
+
[Explain your decision]
|
|
218
|
+
|
|
219
|
+
SUGGESTED_CHANGES:
|
|
220
|
+
- [If REVISE, list specific changes needed]
|
|
221
|
+
- [If APPROVE, write "None - plan is acceptable"]`;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Parse the consensus response from Gemini
|
|
226
|
+
*/
|
|
227
|
+
export function parseConsensusResponse(response: string): ConsensusResult {
|
|
228
|
+
// Extract consensus score - look for various formats
|
|
229
|
+
let score = 0;
|
|
230
|
+
const scorePatterns = [
|
|
231
|
+
/CONSENSUS:\s*(\d+)%/i,
|
|
232
|
+
/CONSENSUS\s*SCORE:\s*(\d+)%/i,
|
|
233
|
+
/(\d+)%\s*consensus/i,
|
|
234
|
+
/score[:\s]+(\d+)%/i,
|
|
235
|
+
];
|
|
236
|
+
|
|
237
|
+
for (const pattern of scorePatterns) {
|
|
238
|
+
const match = response.match(pattern);
|
|
239
|
+
if (match) {
|
|
240
|
+
score = parseInt(match[1], 10);
|
|
241
|
+
break;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Extract sections with better handling of markdown headers
|
|
246
|
+
const analysis = extractSection(response, ['ANALYSIS', '## Analysis', '### Analysis']);
|
|
247
|
+
const strengthsText = extractSection(response, ['STRENGTHS', '## Strengths', '### Strengths']);
|
|
248
|
+
const concernsText = extractSection(response, ['CONCERNS', '## Concerns', '### Concerns']);
|
|
249
|
+
const recommendationsText = extractSection(response, ['RECOMMENDATIONS', '## Recommendations', '### Recommendations']);
|
|
250
|
+
|
|
251
|
+
// Parse lists from sections
|
|
252
|
+
const strengths = parseList(strengthsText);
|
|
253
|
+
const concerns = parseList(concernsText);
|
|
254
|
+
const recommendations = parseList(recommendationsText);
|
|
255
|
+
|
|
256
|
+
return {
|
|
257
|
+
score,
|
|
258
|
+
analysis: analysis.trim(),
|
|
259
|
+
strengths,
|
|
260
|
+
concerns,
|
|
261
|
+
recommendations,
|
|
262
|
+
approved: score >= 95,
|
|
263
|
+
rawResponse: response,
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Parse the arbitration response
|
|
269
|
+
*/
|
|
270
|
+
function parseArbitrationResponse(response: string): ArbitrationResult {
|
|
271
|
+
// Extract score
|
|
272
|
+
const scoreMatch = response.match(/FINAL_SCORE:\s*(\d+)%/i);
|
|
273
|
+
const score = scoreMatch ? parseInt(scoreMatch[1], 10) : 0;
|
|
274
|
+
|
|
275
|
+
// Extract decision
|
|
276
|
+
const decisionMatch = response.match(/DECISION:\s*(APPROVE|REVISE)/i);
|
|
277
|
+
const approved = decisionMatch ? decisionMatch[1].toUpperCase() === 'APPROVE' : score >= 90;
|
|
278
|
+
|
|
279
|
+
// Extract sections
|
|
280
|
+
const analysis = extractSection(response, ['ANALYSIS']);
|
|
281
|
+
const criticalConcerns = parseList(extractSection(response, ['CRITICAL_CONCERNS']));
|
|
282
|
+
const minorConcerns = parseList(extractSection(response, ['MINOR_CONCERNS']));
|
|
283
|
+
const subjectiveConcerns = parseList(extractSection(response, ['SUBJECTIVE_CONCERNS']));
|
|
284
|
+
const reasoning = extractSection(response, ['REASONING']);
|
|
285
|
+
const suggestedChanges = parseList(extractSection(response, ['SUGGESTED_CHANGES']));
|
|
286
|
+
|
|
287
|
+
return {
|
|
288
|
+
approved,
|
|
289
|
+
score,
|
|
290
|
+
analysis,
|
|
291
|
+
criticalConcerns: criticalConcerns.filter(c => c.toLowerCase() !== 'none'),
|
|
292
|
+
minorConcerns: minorConcerns.filter(c => c.toLowerCase() !== 'none'),
|
|
293
|
+
subjectiveConcerns: subjectiveConcerns.filter(c => c.toLowerCase() !== 'none'),
|
|
294
|
+
reasoning,
|
|
295
|
+
suggestedChanges: suggestedChanges.filter(c => !c.toLowerCase().includes('none')),
|
|
296
|
+
rawResponse: response,
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Extract a section from the response with multiple possible headers
|
|
302
|
+
*/
|
|
303
|
+
function extractSection(response: string, headers: string[]): string {
|
|
304
|
+
// Build pattern to match any of the headers
|
|
305
|
+
const headerPattern = headers.map(h => h.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')).join('|');
|
|
306
|
+
const startPattern = new RegExp(`(${headerPattern})[:\\s]*\\n?`, 'i');
|
|
307
|
+
|
|
308
|
+
const startMatch = response.match(startPattern);
|
|
309
|
+
if (!startMatch || startMatch.index === undefined) return '';
|
|
310
|
+
|
|
311
|
+
const startIndex = startMatch.index + startMatch[0].length;
|
|
312
|
+
|
|
313
|
+
// Find the next section header (any capitalized word followed by colon or markdown header)
|
|
314
|
+
const endPattern = /\n(?:#{1,3}\s+)?[A-Z][A-Z_]+[:\s]/;
|
|
315
|
+
const remaining = response.slice(startIndex);
|
|
316
|
+
const endMatch = remaining.match(endPattern);
|
|
317
|
+
|
|
318
|
+
if (!endMatch || endMatch.index === undefined) {
|
|
319
|
+
return remaining.trim();
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
return remaining.slice(0, endMatch.index).trim();
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* Parse a bulleted or numbered list from text
|
|
327
|
+
*/
|
|
328
|
+
function parseList(text: string): string[] {
|
|
329
|
+
if (!text) return [];
|
|
330
|
+
|
|
331
|
+
const lines = text.split('\n');
|
|
332
|
+
const items: string[] = [];
|
|
333
|
+
|
|
334
|
+
for (const line of lines) {
|
|
335
|
+
const trimmed = line.trim();
|
|
336
|
+
|
|
337
|
+
// Skip empty lines and section headers
|
|
338
|
+
if (!trimmed) continue;
|
|
339
|
+
if (trimmed.match(/^#{1,3}\s/)) continue; // Skip markdown headers
|
|
340
|
+
if (trimmed.match(/^[A-Z][A-Z_]+:/)) continue; // Skip section headers
|
|
341
|
+
|
|
342
|
+
// Match bullets (-, *, +) or numbers (1., 2., etc.)
|
|
343
|
+
const bulletMatch = trimmed.match(/^[-*+]\s+(.+)$/);
|
|
344
|
+
const numberMatch = trimmed.match(/^\d+\.\s+(.+)$/);
|
|
345
|
+
|
|
346
|
+
if (bulletMatch) {
|
|
347
|
+
items.push(bulletMatch[1].trim());
|
|
348
|
+
} else if (numberMatch) {
|
|
349
|
+
items.push(numberMatch[1].trim());
|
|
350
|
+
} else if (trimmed && !trimmed.match(/^[A-Z]+:/i)) {
|
|
351
|
+
// Only add non-header lines that have substantial content
|
|
352
|
+
if (trimmed.length > 10 && !trimmed.startsWith('**') && !trimmed.endsWith(':')) {
|
|
353
|
+
items.push(trimmed);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
return items;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/**
|
|
362
|
+
* Validate that the Gemini API key is working
|
|
363
|
+
*/
|
|
364
|
+
export async function validateApiKey(): Promise<boolean> {
|
|
365
|
+
try {
|
|
366
|
+
const client = await createClient();
|
|
367
|
+
const model = client.getGenerativeModel({ model: 'gemini-2.0-flash' });
|
|
368
|
+
await model.generateContent('Say "OK" if you can hear me.');
|
|
369
|
+
return true;
|
|
370
|
+
} catch {
|
|
371
|
+
return false;
|
|
372
|
+
}
|
|
373
|
+
}
|