popeye-cli 1.1.0 → 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.
Files changed (137) hide show
  1. package/.env.example +24 -1
  2. package/CONTRIBUTING.md +275 -0
  3. package/OPEN_SOURCE_MANIFESTO.md +172 -0
  4. package/README.md +340 -27
  5. package/dist/adapters/claude.d.ts +5 -2
  6. package/dist/adapters/claude.d.ts.map +1 -1
  7. package/dist/adapters/claude.js +239 -19
  8. package/dist/adapters/claude.js.map +1 -1
  9. package/dist/adapters/grok.d.ts +73 -0
  10. package/dist/adapters/grok.d.ts.map +1 -0
  11. package/dist/adapters/grok.js +430 -0
  12. package/dist/adapters/grok.js.map +1 -0
  13. package/dist/adapters/openai.d.ts +1 -1
  14. package/dist/adapters/openai.d.ts.map +1 -1
  15. package/dist/adapters/openai.js +6 -1
  16. package/dist/adapters/openai.js.map +1 -1
  17. package/dist/auth/grok.d.ts +73 -0
  18. package/dist/auth/grok.d.ts.map +1 -0
  19. package/dist/auth/grok.js +211 -0
  20. package/dist/auth/grok.js.map +1 -0
  21. package/dist/auth/index.d.ts +9 -6
  22. package/dist/auth/index.d.ts.map +1 -1
  23. package/dist/auth/index.js +23 -6
  24. package/dist/auth/index.js.map +1 -1
  25. package/dist/cli/commands/auth.d.ts +1 -1
  26. package/dist/cli/commands/auth.d.ts.map +1 -1
  27. package/dist/cli/commands/auth.js +79 -8
  28. package/dist/cli/commands/auth.js.map +1 -1
  29. package/dist/cli/commands/create.d.ts.map +1 -1
  30. package/dist/cli/commands/create.js +15 -4
  31. package/dist/cli/commands/create.js.map +1 -1
  32. package/dist/cli/interactive.d.ts.map +1 -1
  33. package/dist/cli/interactive.js +374 -35
  34. package/dist/cli/interactive.js.map +1 -1
  35. package/dist/config/defaults.d.ts +3 -0
  36. package/dist/config/defaults.d.ts.map +1 -1
  37. package/dist/config/defaults.js +9 -0
  38. package/dist/config/defaults.js.map +1 -1
  39. package/dist/config/index.d.ts +9 -0
  40. package/dist/config/index.d.ts.map +1 -1
  41. package/dist/config/index.js +16 -3
  42. package/dist/config/index.js.map +1 -1
  43. package/dist/config/schema.d.ts +27 -0
  44. package/dist/config/schema.d.ts.map +1 -1
  45. package/dist/config/schema.js +24 -3
  46. package/dist/config/schema.js.map +1 -1
  47. package/dist/generators/fullstack.d.ts +32 -0
  48. package/dist/generators/fullstack.d.ts.map +1 -0
  49. package/dist/generators/fullstack.js +497 -0
  50. package/dist/generators/fullstack.js.map +1 -0
  51. package/dist/generators/index.d.ts +4 -3
  52. package/dist/generators/index.d.ts.map +1 -1
  53. package/dist/generators/index.js +15 -1
  54. package/dist/generators/index.js.map +1 -1
  55. package/dist/generators/python.d.ts +17 -1
  56. package/dist/generators/python.d.ts.map +1 -1
  57. package/dist/generators/python.js +34 -21
  58. package/dist/generators/python.js.map +1 -1
  59. package/dist/generators/templates/fullstack.d.ts +113 -0
  60. package/dist/generators/templates/fullstack.d.ts.map +1 -0
  61. package/dist/generators/templates/fullstack.js +1004 -0
  62. package/dist/generators/templates/fullstack.js.map +1 -0
  63. package/dist/generators/typescript.d.ts +19 -1
  64. package/dist/generators/typescript.d.ts.map +1 -1
  65. package/dist/generators/typescript.js +37 -21
  66. package/dist/generators/typescript.js.map +1 -1
  67. package/dist/types/cli.d.ts +4 -0
  68. package/dist/types/cli.d.ts.map +1 -1
  69. package/dist/types/cli.js.map +1 -1
  70. package/dist/types/consensus.d.ts +119 -2
  71. package/dist/types/consensus.d.ts.map +1 -1
  72. package/dist/types/consensus.js +12 -1
  73. package/dist/types/consensus.js.map +1 -1
  74. package/dist/types/project.d.ts +76 -0
  75. package/dist/types/project.d.ts.map +1 -1
  76. package/dist/types/project.js +1 -1
  77. package/dist/types/project.js.map +1 -1
  78. package/dist/types/workflow.d.ts +162 -16
  79. package/dist/types/workflow.d.ts.map +1 -1
  80. package/dist/types/workflow.js +24 -1
  81. package/dist/types/workflow.js.map +1 -1
  82. package/dist/workflow/consensus.d.ts +29 -3
  83. package/dist/workflow/consensus.d.ts.map +1 -1
  84. package/dist/workflow/consensus.js +334 -27
  85. package/dist/workflow/consensus.js.map +1 -1
  86. package/dist/workflow/milestone-workflow.js +2 -2
  87. package/dist/workflow/milestone-workflow.js.map +1 -1
  88. package/dist/workflow/plan-mode.d.ts +66 -2
  89. package/dist/workflow/plan-mode.d.ts.map +1 -1
  90. package/dist/workflow/plan-mode.js +187 -11
  91. package/dist/workflow/plan-mode.js.map +1 -1
  92. package/dist/workflow/plan-storage.d.ts +252 -8
  93. package/dist/workflow/plan-storage.d.ts.map +1 -1
  94. package/dist/workflow/plan-storage.js +580 -33
  95. package/dist/workflow/plan-storage.js.map +1 -1
  96. package/dist/workflow/project-verification.js +1 -1
  97. package/dist/workflow/project-verification.js.map +1 -1
  98. package/dist/workflow/task-workflow.d.ts.map +1 -1
  99. package/dist/workflow/task-workflow.js +4 -1
  100. package/dist/workflow/task-workflow.js.map +1 -1
  101. package/dist/workflow/test-runner.d.ts +8 -0
  102. package/dist/workflow/test-runner.d.ts.map +1 -1
  103. package/dist/workflow/test-runner.js +92 -0
  104. package/dist/workflow/test-runner.js.map +1 -1
  105. package/dist/workflow/workspace-manager.d.ts +342 -0
  106. package/dist/workflow/workspace-manager.d.ts.map +1 -0
  107. package/dist/workflow/workspace-manager.js +733 -0
  108. package/dist/workflow/workspace-manager.js.map +1 -0
  109. package/package.json +1 -1
  110. package/src/adapters/claude.ts +263 -24
  111. package/src/adapters/grok.ts +492 -0
  112. package/src/adapters/openai.ts +8 -2
  113. package/src/auth/grok.ts +255 -0
  114. package/src/auth/index.ts +27 -9
  115. package/src/cli/commands/auth.ts +89 -10
  116. package/src/cli/commands/create.ts +13 -4
  117. package/src/cli/interactive.ts +424 -34
  118. package/src/config/defaults.ts +9 -0
  119. package/src/config/index.ts +17 -3
  120. package/src/config/schema.ts +25 -3
  121. package/src/generators/fullstack.ts +551 -0
  122. package/src/generators/index.ts +25 -1
  123. package/src/generators/python.ts +65 -21
  124. package/src/generators/templates/fullstack.ts +1047 -0
  125. package/src/generators/typescript.ts +69 -21
  126. package/src/types/cli.ts +4 -0
  127. package/src/types/consensus.ts +135 -3
  128. package/src/types/project.ts +82 -1
  129. package/src/types/workflow.ts +56 -2
  130. package/src/workflow/consensus.ts +461 -31
  131. package/src/workflow/milestone-workflow.ts +2 -2
  132. package/src/workflow/plan-mode.ts +238 -10
  133. package/src/workflow/plan-storage.ts +835 -35
  134. package/src/workflow/project-verification.ts +1 -1
  135. package/src/workflow/task-workflow.ts +4 -1
  136. package/src/workflow/test-runner.ts +110 -0
  137. package/src/workflow/workspace-manager.ts +912 -0
@@ -0,0 +1,492 @@
1
+ /**
2
+ * xAI Grok API adapter
3
+ * Handles consensus reviews and arbitration using Grok models
4
+ *
5
+ * Uses OpenAI SDK with custom baseURL since Grok API is OpenAI-compatible
6
+ */
7
+
8
+ import OpenAI from 'openai';
9
+ import type { ConsensusResult, ArbitrationResult } from '../types/consensus.js';
10
+ import { getGrokToken, GROK_API_URL } from '../auth/grok.js';
11
+ import { DEFAULT_GROK_MODEL } from '../types/consensus.js';
12
+
13
+ /**
14
+ * Default Grok configuration
15
+ */
16
+ export const DEFAULT_GROK_CONFIG = {
17
+ model: DEFAULT_GROK_MODEL,
18
+ temperature: 0.3,
19
+ maxTokens: 4096,
20
+ };
21
+
22
+ /**
23
+ * Create a Grok client using OpenAI SDK with custom baseURL
24
+ */
25
+ export async function createClient(): Promise<OpenAI> {
26
+ const apiKey = await getGrokToken();
27
+
28
+ if (!apiKey) {
29
+ throw new Error('Grok API key not found. Run: popeye-cli auth grok');
30
+ }
31
+
32
+ return new OpenAI({
33
+ apiKey,
34
+ baseURL: GROK_API_URL,
35
+ });
36
+ }
37
+
38
+ /**
39
+ * Request consensus review from Grok
40
+ *
41
+ * @param plan - The development plan to review
42
+ * @param context - Project context
43
+ * @param config - Configuration options
44
+ * @returns Consensus result
45
+ */
46
+ export async function requestConsensus(
47
+ plan: string,
48
+ context: string,
49
+ config: { model?: string; temperature?: number; maxTokens?: number } = {}
50
+ ): Promise<ConsensusResult> {
51
+ const {
52
+ model = DEFAULT_GROK_CONFIG.model,
53
+ temperature = DEFAULT_GROK_CONFIG.temperature,
54
+ maxTokens = DEFAULT_GROK_CONFIG.maxTokens,
55
+ } = config;
56
+
57
+ const client = await createClient();
58
+ const prompt = buildConsensusPrompt(plan, context);
59
+
60
+ try {
61
+ const completion = await client.chat.completions.create({
62
+ model,
63
+ messages: [{ role: 'user', content: prompt }],
64
+ temperature,
65
+ max_tokens: maxTokens,
66
+ });
67
+
68
+ const response = completion.choices[0]?.message?.content || '';
69
+ return parseConsensusResponse(response);
70
+ } catch (error) {
71
+ if (error instanceof OpenAI.RateLimitError) {
72
+ // Implement exponential backoff retry
73
+ await sleep(5000);
74
+ return requestConsensus(plan, context, config);
75
+ }
76
+ throw error;
77
+ }
78
+ }
79
+
80
+ /**
81
+ * Request arbitration from Grok when consensus is stuck
82
+ *
83
+ * @param plan - The best plan achieved
84
+ * @param reviewerFeedback - Feedback from the reviewer
85
+ * @param claudeFeedback - Claude's perspective on the plan
86
+ * @param iterations - Number of iterations attempted
87
+ * @param scores - Score history
88
+ * @returns Arbitration decision
89
+ */
90
+ export async function requestArbitration(
91
+ plan: string,
92
+ reviewerFeedback: string,
93
+ claudeFeedback: string,
94
+ iterations: number,
95
+ scores: number[]
96
+ ): Promise<ArbitrationResult> {
97
+ const client = await createClient();
98
+ const prompt = buildArbitrationPrompt(plan, reviewerFeedback, claudeFeedback, iterations, scores);
99
+
100
+ try {
101
+ const completion = await client.chat.completions.create({
102
+ model: DEFAULT_GROK_CONFIG.model,
103
+ messages: [{ role: 'user', content: prompt }],
104
+ temperature: 0.2,
105
+ max_tokens: 4096,
106
+ });
107
+
108
+ const response = completion.choices[0]?.message?.content || '';
109
+ return parseArbitrationResponse(response);
110
+ } catch (error) {
111
+ const errorMsg = error instanceof Error ? error.message : 'Unknown error';
112
+ throw new Error(`Grok arbitration error: ${errorMsg}`);
113
+ }
114
+ }
115
+
116
+ /**
117
+ * Build the consensus review prompt
118
+ */
119
+ function buildConsensusPrompt(plan: string, context: string): string {
120
+ return `You are a senior software architect reviewing a development plan.
121
+ Analyze the following plan for completeness, correctness, and feasibility.
122
+
123
+ PROJECT CONTEXT:
124
+ ${context}
125
+
126
+ PROPOSED PLAN:
127
+ ${plan}
128
+
129
+ Please provide your response in EXACTLY this format (use these exact headers):
130
+
131
+ ANALYSIS:
132
+ [Your detailed analysis here]
133
+
134
+ STRENGTHS:
135
+ - [Strength 1]
136
+ - [Strength 2]
137
+ - [etc.]
138
+
139
+ CONCERNS:
140
+ - [Concern 1]
141
+ - [Concern 2]
142
+ - [etc.]
143
+
144
+ RECOMMENDATIONS:
145
+ - [Recommendation 1]
146
+ - [Recommendation 2]
147
+ - [etc.]
148
+
149
+ CONSENSUS: [X]%
150
+
151
+ Scoring guide:
152
+ - 95-100%: Ready for execution, no changes needed
153
+ - 85-94%: Minor revisions needed, mostly good
154
+ - 70-84%: Significant revisions needed
155
+ - Below 70%: Major rework required
156
+
157
+ Be thorough but constructive. Focus on actionable feedback.`;
158
+ }
159
+
160
+ /**
161
+ * Build the arbitration prompt
162
+ */
163
+ function buildArbitrationPrompt(
164
+ plan: string,
165
+ reviewerFeedback: string,
166
+ claudeFeedback: string,
167
+ iterations: number,
168
+ scores: number[]
169
+ ): string {
170
+ const scoreHistory = scores.map((s, i) => `Iteration ${i + 1}: ${s}%`).join(', ');
171
+
172
+ return `You are an impartial arbitrator resolving a disagreement between two AI systems about a development plan.
173
+
174
+ SITUATION:
175
+ - Claude (code generator) created a plan
176
+ - A reviewer has been reviewing and providing feedback
177
+ - They have gone through ${iterations} iterations without reaching 95% consensus
178
+ - Score history: ${scoreHistory}
179
+
180
+ THE PLAN:
181
+ ${plan}
182
+
183
+ REVIEWER'S LATEST FEEDBACK:
184
+ ${reviewerFeedback}
185
+
186
+ CLAUDE'S PERSPECTIVE:
187
+ ${claudeFeedback}
188
+
189
+ As the arbitrator, you must:
190
+ 1. Analyze both perspectives objectively
191
+ 2. Determine if the remaining concerns are:
192
+ - CRITICAL: Must be addressed before proceeding
193
+ - MINOR: Can be addressed during implementation
194
+ - SUBJECTIVE: Matters of preference, not correctness
195
+ 3. Make a final decision
196
+
197
+ Respond in EXACTLY this format:
198
+
199
+ ANALYSIS:
200
+ [Your analysis of the disagreement]
201
+
202
+ CRITICAL_CONCERNS:
203
+ - [List any truly critical issues, or "None" if none exist]
204
+
205
+ MINOR_CONCERNS:
206
+ - [List minor issues that can be addressed during implementation]
207
+
208
+ SUBJECTIVE_CONCERNS:
209
+ - [List preference-based concerns that don't affect correctness]
210
+
211
+ DECISION: [APPROVE or REVISE]
212
+
213
+ FINAL_SCORE: [X]%
214
+
215
+ REASONING:
216
+ [Explain your decision]
217
+
218
+ SUGGESTED_CHANGES:
219
+ - [If REVISE, list specific changes needed]
220
+ - [If APPROVE, write "None - plan is acceptable"]`;
221
+ }
222
+
223
+ /**
224
+ * Parse the consensus response from Grok
225
+ * Uses same format as OpenAI/Gemini for consistency
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
+ * Helper sleep function for rate limiting
363
+ */
364
+ function sleep(ms: number): Promise<void> {
365
+ return new Promise((resolve) => setTimeout(resolve, ms));
366
+ }
367
+
368
+ /**
369
+ * Validate that the Grok API key is working
370
+ */
371
+ export async function validateApiKey(): Promise<boolean> {
372
+ try {
373
+ const client = await createClient();
374
+ await client.chat.completions.create({
375
+ model: DEFAULT_GROK_CONFIG.model,
376
+ messages: [{ role: 'user', content: 'Say "OK" if you can hear me.' }],
377
+ max_tokens: 5,
378
+ });
379
+ return true;
380
+ } catch {
381
+ return false;
382
+ }
383
+ }
384
+
385
+ /**
386
+ * List available Grok models (if API supports it)
387
+ */
388
+ export async function listAvailableModels(): Promise<string[]> {
389
+ try {
390
+ const client = await createClient();
391
+ const models = await client.models.list();
392
+
393
+ return models.data
394
+ .filter((m) => m.id.includes('grok'))
395
+ .map((m) => m.id)
396
+ .sort();
397
+ } catch {
398
+ // Return known models if listing fails
399
+ return ['grok-3', 'grok-4'];
400
+ }
401
+ }
402
+
403
+ /**
404
+ * Expand a brief idea into a full specification using Grok
405
+ *
406
+ * @param idea - The brief project idea
407
+ * @param language - Target programming language
408
+ * @returns Expanded specification
409
+ */
410
+ export async function expandIdea(
411
+ idea: string,
412
+ language: 'python' | 'typescript' | 'fullstack'
413
+ ): Promise<string> {
414
+ const client = await createClient();
415
+
416
+ const languageDesc = language === 'fullstack'
417
+ ? 'React (TypeScript) frontend with FastAPI (Python) backend'
418
+ : language === 'python'
419
+ ? 'Python'
420
+ : 'TypeScript';
421
+
422
+ const prompt = `You are a senior software architect. A user wants to build a project with the following idea:
423
+
424
+ "${idea}"
425
+
426
+ The project will be implemented in ${languageDesc}.
427
+
428
+ Expand this into a complete software specification including:
429
+
430
+ 1. **Project Overview**: A clear description of what will be built
431
+ 2. **Core Features**: List of main features and functionality
432
+ 3. **Technical Requirements**:
433
+ - Language and framework choices
434
+ - Database requirements (if any)
435
+ - External APIs or services needed
436
+ - Authentication requirements (if any)
437
+ 4. **Architecture Overview**: High-level system design
438
+ 5. **API Specification** (if applicable): Key endpoints and their purposes
439
+ 6. **Data Models**: Key entities and their relationships
440
+ 7. **Non-Functional Requirements**: Performance, security, scalability considerations
441
+ 8. **Deployment**: Docker configuration and deployment approach
442
+
443
+ Be specific and actionable. The specification should be detailed enough that a developer could implement it without further clarification.`;
444
+
445
+ const completion = await client.chat.completions.create({
446
+ model: DEFAULT_GROK_CONFIG.model,
447
+ messages: [{ role: 'user', content: prompt }],
448
+ temperature: 0.7,
449
+ max_tokens: 4096,
450
+ });
451
+
452
+ return completion.choices[0]?.message?.content || idea;
453
+ }
454
+
455
+ /**
456
+ * Get feedback on generated code using Grok
457
+ *
458
+ * @param code - The code to review
459
+ * @param context - Context about what the code should do
460
+ */
461
+ export async function reviewCode(code: string, context: string): Promise<ConsensusResult> {
462
+ const client = await createClient();
463
+
464
+ const prompt = `You are a senior software engineer reviewing code. Review the following code:
465
+
466
+ CONTEXT:
467
+ ${context}
468
+
469
+ CODE:
470
+ \`\`\`
471
+ ${code}
472
+ \`\`\`
473
+
474
+ Provide:
475
+ 1. ANALYSIS: Overall code quality assessment
476
+ 2. STRENGTHS: What's done well
477
+ 3. CONCERNS: Issues, bugs, or improvements needed
478
+ 4. RECOMMENDATIONS: Specific fixes or enhancements
479
+ 5. CONSENSUS SCORE: A percentage (0-100%) of how production-ready this code is
480
+
481
+ Format your score as: CONSENSUS: [X]%`;
482
+
483
+ const completion = await client.chat.completions.create({
484
+ model: DEFAULT_GROK_CONFIG.model,
485
+ messages: [{ role: 'user', content: prompt }],
486
+ temperature: 0.3,
487
+ max_tokens: 2048,
488
+ });
489
+
490
+ const response = completion.choices[0]?.message?.content || '';
491
+ return parseConsensusResponse(response);
492
+ }
@@ -256,15 +256,21 @@ export async function listAvailableModels(): Promise<string[]> {
256
256
  */
257
257
  export async function expandIdea(
258
258
  idea: string,
259
- language: 'python' | 'typescript'
259
+ language: 'python' | 'typescript' | 'fullstack'
260
260
  ): Promise<string> {
261
261
  const client = await createClient();
262
262
 
263
+ const languageDesc = language === 'fullstack'
264
+ ? 'React (TypeScript) frontend with FastAPI (Python) backend'
265
+ : language === 'python'
266
+ ? 'Python'
267
+ : 'TypeScript';
268
+
263
269
  const prompt = `You are a senior software architect. A user wants to build a project with the following idea:
264
270
 
265
271
  "${idea}"
266
272
 
267
- The project will be implemented in ${language === 'python' ? 'Python' : 'TypeScript'}.
273
+ The project will be implemented in ${languageDesc}.
268
274
 
269
275
  Expand this into a complete software specification including:
270
276