ai-first-cli 1.3.6 → 1.3.8

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 (84) hide show
  1. package/CHANGELOG.md +123 -0
  2. package/README.es.md +14 -1
  3. package/README.md +14 -1
  4. package/ai/graph/knowledge-graph.json +1 -1
  5. package/ai-context/index-state.json +86 -2
  6. package/dist/analyzers/techStack.d.ts.map +1 -1
  7. package/dist/analyzers/techStack.js +43 -0
  8. package/dist/analyzers/techStack.js.map +1 -1
  9. package/dist/commands/ai-first.d.ts.map +1 -1
  10. package/dist/commands/ai-first.js +78 -4
  11. package/dist/commands/ai-first.js.map +1 -1
  12. package/dist/config/configLoader.d.ts +6 -0
  13. package/dist/config/configLoader.d.ts.map +1 -0
  14. package/dist/config/configLoader.js +232 -0
  15. package/dist/config/configLoader.js.map +1 -0
  16. package/dist/config/index.d.ts +3 -0
  17. package/dist/config/index.d.ts.map +1 -0
  18. package/dist/config/index.js +2 -0
  19. package/dist/config/index.js.map +1 -0
  20. package/dist/config/types.d.ts +101 -0
  21. package/dist/config/types.d.ts.map +1 -0
  22. package/dist/config/types.js +2 -0
  23. package/dist/config/types.js.map +1 -0
  24. package/dist/core/content/contentProcessor.d.ts +4 -0
  25. package/dist/core/content/contentProcessor.d.ts.map +1 -0
  26. package/dist/core/content/contentProcessor.js +235 -0
  27. package/dist/core/content/contentProcessor.js.map +1 -0
  28. package/dist/core/content/index.d.ts +3 -0
  29. package/dist/core/content/index.d.ts.map +1 -0
  30. package/dist/core/content/index.js +2 -0
  31. package/dist/core/content/index.js.map +1 -0
  32. package/dist/core/content/types.d.ts +32 -0
  33. package/dist/core/content/types.d.ts.map +1 -0
  34. package/dist/core/content/types.js +2 -0
  35. package/dist/core/content/types.js.map +1 -0
  36. package/dist/core/gitAnalyzer.d.ts +14 -0
  37. package/dist/core/gitAnalyzer.d.ts.map +1 -1
  38. package/dist/core/gitAnalyzer.js +98 -0
  39. package/dist/core/gitAnalyzer.js.map +1 -1
  40. package/dist/core/multiRepo/index.d.ts +3 -0
  41. package/dist/core/multiRepo/index.d.ts.map +1 -0
  42. package/dist/core/multiRepo/index.js +2 -0
  43. package/dist/core/multiRepo/index.js.map +1 -0
  44. package/dist/core/multiRepo/multiRepoScanner.d.ts +18 -0
  45. package/dist/core/multiRepo/multiRepoScanner.d.ts.map +1 -0
  46. package/dist/core/multiRepo/multiRepoScanner.js +131 -0
  47. package/dist/core/multiRepo/multiRepoScanner.js.map +1 -0
  48. package/dist/core/rag/index.d.ts +3 -0
  49. package/dist/core/rag/index.d.ts.map +1 -0
  50. package/dist/core/rag/index.js +2 -0
  51. package/dist/core/rag/index.js.map +1 -0
  52. package/dist/core/rag/vectorIndex.d.ts +28 -0
  53. package/dist/core/rag/vectorIndex.d.ts.map +1 -0
  54. package/dist/core/rag/vectorIndex.js +71 -0
  55. package/dist/core/rag/vectorIndex.js.map +1 -0
  56. package/dist/mcp/index.d.ts +2 -0
  57. package/dist/mcp/index.d.ts.map +1 -0
  58. package/dist/mcp/index.js +2 -0
  59. package/dist/mcp/index.js.map +1 -0
  60. package/dist/mcp/server.d.ts +7 -0
  61. package/dist/mcp/server.d.ts.map +1 -0
  62. package/dist/mcp/server.js +154 -0
  63. package/dist/mcp/server.js.map +1 -0
  64. package/docs/planning/evaluator-v1.0.0/README.md +112 -0
  65. package/docs/planning/evaluator-v1.0.0/improvements_plan_2026-03-28.md +237 -0
  66. package/package.json +13 -3
  67. package/src/analyzers/techStack.ts +47 -1
  68. package/src/commands/ai-first.ts +83 -4
  69. package/src/config/configLoader.ts +274 -0
  70. package/src/config/index.ts +27 -0
  71. package/src/config/types.ts +117 -0
  72. package/src/core/content/contentProcessor.ts +292 -0
  73. package/src/core/content/index.ts +9 -0
  74. package/src/core/content/types.ts +35 -0
  75. package/src/core/gitAnalyzer.ts +130 -0
  76. package/src/core/multiRepo/index.ts +2 -0
  77. package/src/core/multiRepo/multiRepoScanner.ts +177 -0
  78. package/src/core/rag/index.ts +2 -0
  79. package/src/core/rag/vectorIndex.ts +105 -0
  80. package/src/mcp/index.ts +1 -0
  81. package/src/mcp/server.ts +179 -0
  82. package/tests/v1.3.8-integration.test.ts +361 -0
  83. package/ai-context-evaluation-report-1774223059505.md +0 -206
  84. package/scripts/ai-context-evaluator.ts +0 -440
@@ -1,440 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- /**
4
- * ai-context-evaluator.ts
5
- *
6
- * Evalúa la calidad de ai-context/ usando 3 modelos de AI (Kimi, GLM, MiniMax)
7
- * en paralelo por proyecto, procesando secuencialmente para evitar timeouts.
8
- *
9
- * Proyectos evaluados:
10
- * - PRIORIDAD: salesforce-cli (especial interés)
11
- * - Soportados: ai-first, express-api, nestjs-backend, python-cli, spring-boot-app
12
- * - No soportados: android-kotlin-app, ios-swift-app, go-microservice, rust-cli, php-vanilla
13
- */
14
-
15
- import { readFileSync, readdirSync, statSync, existsSync, writeFileSync } from 'fs';
16
- import { join, basename } from 'path';
17
- import { exec } from 'child_process';
18
- import { promisify } from 'util';
19
-
20
- const execAsync = promisify(exec);
21
-
22
- // Configuration
23
- const CONFIG = {
24
- opencodeApiKey: process.env.OPENCODE_API_KEY || '',
25
- minimaxApiKey: process.env.MINIMAX_API_KEY || '',
26
- timeout: 120000, // 2 minutes per call
27
- maxRetries: 3,
28
- retryDelay: 2000,
29
- };
30
-
31
- // Projects to evaluate (mix of supported and unsupported)
32
- const PROJECTS = [
33
- // PRIORITY: Salesforce (special interest)
34
- { path: 'test-projects/salesforce-cli', name: 'salesforce-cli', type: 'priority' },
35
- // Supported
36
- { path: '.', name: 'ai-first-cli', type: 'supported' },
37
- { path: 'test-projects/express-api', name: 'express-api', type: 'supported' },
38
- { path: 'test-projects/nestjs-backend', name: 'nestjs-backend', type: 'supported' },
39
- { path: 'test-projects/python-cli', name: 'python-cli', type: 'supported' },
40
- { path: 'test-projects/spring-boot-app', name: 'spring-boot-app', type: 'supported' },
41
- // Unsupported
42
- { path: 'test-projects/android-kotlin-app', name: 'android-kotlin-app', type: 'unsupported' },
43
- { path: 'test-projects/ios-swift-app', name: 'ios-swift-app', type: 'unsupported' },
44
- { path: 'test-projects/go-microservice', name: 'go-microservice', type: 'unsupported' },
45
- { path: 'test-projects/rust-cli', name: 'rust-cli', type: 'unsupported' },
46
- { path: 'test-projects/php-vanilla', name: 'php-vanilla', type: 'unsupported' },
47
- ];
48
-
49
- interface EvaluationResult {
50
- model: string;
51
- perspective: string;
52
- score: number;
53
- feedback: string;
54
- improvements: string[];
55
- }
56
-
57
- interface ProjectEvaluation {
58
- projectName: string;
59
- projectType: 'priority' | 'supported' | 'unsupported';
60
- aiContextPath: string;
61
- hasIndexDb: boolean;
62
- results: EvaluationResult[];
63
- summary: string;
64
- synthesizedImprovements: string[];
65
- }
66
-
67
- /**
68
- * Read ai-context files and create evaluation prompt
69
- */
70
- function createEvaluationPrompt(projectPath: string, projectName: string): string {
71
- const aiContextPath = join(projectPath, 'ai-context');
72
-
73
- if (!existsSync(aiContextPath)) {
74
- return `ERROR: No ai-context directory found for ${projectName}`;
75
- }
76
-
77
- // Read key files
78
- const files: Record<string, string> = {};
79
- const keyFiles = [
80
- 'summary.md',
81
- 'architecture.md',
82
- 'tech_stack.md',
83
- 'repo_map.md',
84
- 'ai_context.md',
85
- 'entrypoints.md',
86
- ];
87
-
88
- for (const file of keyFiles) {
89
- const filePath = join(aiContextPath, file);
90
- if (existsSync(filePath)) {
91
- try {
92
- files[file] = readFileSync(filePath, 'utf8').substring(0, 5000); // Limit size
93
- } catch (e) {
94
- files[file] = `[Error reading ${file}: ${e}]`;
95
- }
96
- }
97
- }
98
-
99
- // Check for index.db
100
- const hasIndexDb = existsSync(join(aiContextPath, 'index.db'));
101
- const hasFeatures = existsSync(join(aiContextPath, 'context', 'features'));
102
- const hasFlows = existsSync(join(aiContextPath, 'context', 'flows'));
103
-
104
- return `
105
- EVALUATE THIS AI-CONTEXT DIRECTORY:
106
-
107
- PROJECT: ${projectName}
108
- INDEX DB: ${hasIndexDb ? 'YES' : 'NO'}
109
- FEATURES: ${hasFeatures ? 'YES' : 'NO'}
110
- FLOWS: ${hasFlows ? 'YES' : 'NO'}
111
-
112
- KEY FILES:
113
- ${Object.entries(files).map(([name, content]) => `=== ${name} ===\n${content}`).join('\n')}
114
-
115
- TASK: Evaluate from 1-5 (5=excellent) on:
116
- 1. CLARITY - Clear and non-redundant?
117
- 2. COMPLETENESS - Has everything needed?
118
- 3. STRUCTURE - Easy for LLM to parse?
119
- 4. ACTIONABILITY - Can AI use it to code/decide?
120
- 5. IMPROVEMENTS - What changes would help?
121
-
122
- REQUIRED RESPONSE:
123
- - ONLY return valid JSON, no other text
124
- - Start with { and end with }
125
- - Include all fields
126
-
127
- EXAMPLE (follow this exact format):
128
- {"clarity":{"score":4,"feedback":"clear","improvements":["item1"]},"completeness":{"score":3,"feedback":"missing X","improvements":["item2"]},"structure":{"score":4,"feedback":"good","improvements":[]},"actionability":{"score":3,"feedback":"needs Y","improvements":["item3"]},"overall_score":3.5,"overall_feedback":"overall good","top_3_improvements":["fix A","add B","remove C"]}
129
- `.trim();
130
- }
131
-
132
- /**
133
- * Call OpenCode API (Kimi or GLM)
134
- */
135
- async function callOpenCode(model: string, prompt: string): Promise<any> {
136
- const data = {
137
- model: model,
138
- messages: [{ role: 'user', content: prompt }],
139
- temperature: 0.3,
140
- max_tokens: 8000,
141
- };
142
-
143
- const curlCommand = `curl -s --max-time ${CONFIG.timeout / 1000} -X POST "https://opencode.ai/zen/go/v1/chat/completions" \
144
- -H "Content-Type: application/json" \
145
- -H "Authorization: Bearer ${CONFIG.opencodeApiKey}" \
146
- --data-raw '${JSON.stringify(data).replace(/'/g, "'\\''")}'`;
147
-
148
- for (let attempt = 1; attempt <= CONFIG.maxRetries; attempt++) {
149
- try {
150
- const { stdout } = await execAsync(curlCommand, { timeout: CONFIG.timeout });
151
- const response = JSON.parse(stdout);
152
-
153
- if (response.choices && response.choices[0]?.message?.content) {
154
- const content = response.choices[0].message.content;
155
- // Try to parse JSON from response
156
- const jsonMatch = content.match(/\{[\s\S]*\}/);
157
- if (jsonMatch) {
158
- return JSON.parse(jsonMatch[0]);
159
- }
160
- return { raw_response: content };
161
- }
162
- throw new Error('Invalid response format');
163
- } catch (error) {
164
- if (attempt === CONFIG.maxRetries) {
165
- console.error(`OpenCode ${model} failed after ${CONFIG.maxRetries} attempts:`, error);
166
- return { error: error.message };
167
- }
168
- await new Promise(resolve => setTimeout(resolve, CONFIG.retryDelay * attempt));
169
- }
170
- }
171
- }
172
-
173
- /**
174
- * Call MiniMax API
175
- */
176
- async function callMiniMax(prompt: string): Promise<any> {
177
- const data = {
178
- model: 'minimax-coding-plan/MiniMax-M2.7',
179
- max_tokens: 8000,
180
- temperature: 0.3,
181
- messages: [{ role: 'user', content: prompt }],
182
- };
183
-
184
- const curlCommand = `curl -s --max-time ${CONFIG.timeout / 1000} -X POST "https://api.minimax.io/anthropic/v1/messages" \
185
- -H "Content-Type: application/json" \
186
- -H "Authorization: Bearer ${CONFIG.minimaxApiKey}" \
187
- --data-raw '${JSON.stringify(data).replace(/'/g, "'\\''")}'`;
188
-
189
- for (let attempt = 1; attempt <= CONFIG.maxRetries; attempt++) {
190
- try {
191
- const { stdout } = await execAsync(curlCommand, { timeout: CONFIG.timeout });
192
- const response = JSON.parse(stdout);
193
-
194
- if (response.content && Array.isArray(response.content)) {
195
- const textObj = response.content.find((c: any) => c.type === 'text');
196
- const content = textObj?.text || response.content[0]?.text || '';
197
- const jsonMatch = content.match(/\{[\s\S]*\}/);
198
- if (jsonMatch) {
199
- return JSON.parse(jsonMatch[0]);
200
- }
201
- return { raw_response: content };
202
- }
203
- throw new Error('Invalid response format');
204
- } catch (error) {
205
- if (attempt === CONFIG.maxRetries) {
206
- console.error('MiniMax failed after retries:', error);
207
- return { error: error.message };
208
- }
209
- await new Promise(resolve => setTimeout(resolve, CONFIG.retryDelay * attempt));
210
- }
211
- }
212
- }
213
-
214
- /**
215
- * Evaluate single project with all 3 models in parallel
216
- */
217
- async function evaluateProject(project: typeof PROJECTS[0]): Promise<ProjectEvaluation> {
218
- console.log(`\n📁 Evaluating: ${project.name} (${project.type})`);
219
-
220
- const aiContextPath = join(project.path, 'ai-context');
221
- const hasIndexDb = existsSync(join(aiContextPath, 'index.db'));
222
-
223
- // Skip if no ai-context
224
- if (!existsSync(aiContextPath)) {
225
- console.log(` ⚠️ No ai-context directory found`);
226
- return {
227
- projectName: project.name,
228
- projectType: project.type as 'priority' | 'supported' | 'unsupported',
229
- aiContextPath,
230
- hasIndexDb,
231
- results: [],
232
- summary: 'No ai-context directory found',
233
- synthesizedImprovements: [],
234
- };
235
- }
236
-
237
- const prompt = createEvaluationPrompt(project.path, project.name);
238
-
239
- // Call all 3 models in parallel
240
- console.log(' 🔄 Calling models in parallel...');
241
- const startTime = Date.now();
242
-
243
- const [kimiResult, glmResult, minimaxResult] = await Promise.all([
244
- callOpenCode('kimi-k2.5', prompt),
245
- callOpenCode('glm-5', prompt),
246
- callMiniMax(prompt),
247
- ]);
248
-
249
- const duration = ((Date.now() - startTime) / 1000).toFixed(1);
250
- console.log(` ✅ Completed in ${duration}s`);
251
-
252
- // Process results
253
- const results: EvaluationResult[] = [
254
- { model: 'Kimi K2.5', perspective: 'overall', score: kimiResult.overall_score || 0, feedback: kimiResult.overall_feedback || '', improvements: kimiResult.top_3_improvements || [] },
255
- { model: 'GLM 5', perspective: 'overall', score: glmResult.overall_score || 0, feedback: glmResult.overall_feedback || '', improvements: glmResult.top_3_improvements || [] },
256
- { model: 'MiniMax 2.7', perspective: 'overall', score: minimaxResult.overall_score || 0, feedback: minimaxResult.overall_feedback || '', improvements: minimaxResult.top_3_improvements || [] },
257
- ];
258
-
259
- // Synthesize improvements
260
- const allImprovements = [
261
- ...(kimiResult.top_3_improvements || []),
262
- ...(glmResult.top_3_improvements || []),
263
- ...(minimaxResult.top_3_improvements || []),
264
- ];
265
-
266
- // Simple deduplication (in real implementation, use LLM to synthesize)
267
- const synthesizedImprovements = [...new Set(allImprovements)].slice(0, 5);
268
-
269
- return {
270
- projectName: project.name,
271
- projectType: project.type as 'priority' | 'supported' | 'unsupported',
272
- aiContextPath,
273
- hasIndexDb,
274
- results,
275
- summary: `Average score: ${(results.reduce((a, b) => a + b.score, 0) / results.length).toFixed(1)}/5`,
276
- synthesizedImprovements,
277
- };
278
- }
279
-
280
- /**
281
- * Generate final report
282
- */
283
- function generateReport(evaluations: ProjectEvaluation[]): string {
284
- let report = `# AI-Context Evaluation Report\n\n`;
285
- report += `Generated: ${new Date().toISOString()}\n\n`;
286
-
287
- // Summary table
288
- report += `## Summary\n\n`;
289
- report += `| Project | Type | Index DB | Avg Score | Key Issue |\n`;
290
- report += `|---------|------|----------|-----------|-----------|\n`;
291
-
292
- for (const eval_ of evaluations) {
293
- const avgScore = eval_.results.length > 0
294
- ? (eval_.results.reduce((a, b) => a + b.score, 0) / eval_.results.length).toFixed(1)
295
- : 'N/A';
296
- const keyIssue = eval_.synthesizedImprovements[0] || 'None identified';
297
- report += `| ${eval_.projectName} | ${eval_.projectType} | ${eval_.hasIndexDb ? '✅' : '❌'} | ${avgScore} | ${keyIssue.substring(0, 40)}... |\n`;
298
- }
299
-
300
- // Detailed findings
301
- report += `\n## Detailed Findings\n\n`;
302
-
303
- for (const eval_ of evaluations) {
304
- report += `### ${eval_.projectName}\n\n`;
305
- report += `- **Type:** ${eval_.projectType}\n`;
306
- report += `- **Index DB:** ${eval_.hasIndexDb ? 'Yes' : 'No'}\n`;
307
- report += `- **Model Scores:**\n`;
308
-
309
- for (const result of eval_.results) {
310
- report += ` - ${result.model}: ${result.score}/5\n`;
311
- }
312
-
313
- if (eval_.synthesizedImprovements.length > 0) {
314
- report += `- **Top Improvements:**\n`;
315
- for (const improvement of eval_.synthesizedImprovements) {
316
- report += ` - ${improvement}\n`;
317
- }
318
- }
319
-
320
- report += `\n`;
321
- }
322
-
323
- // Cross-project patterns
324
- const priority = evaluations.filter(e => e.projectType === 'priority');
325
- const unsupported = evaluations.filter(e => e.projectType === 'unsupported');
326
- const supported = evaluations.filter(e => e.projectType === 'supported');
327
-
328
- report += `## Cross-Project Analysis\n\n`;
329
-
330
- report += `### 🎯 Priority Projects (Salesforce)\n`;
331
- report += `- Average score: ${calculateAverageScore(priority)}\n`;
332
- report += `- Key findings: ${extractCommonIssues(priority).join(', ') || 'See detailed section above'}\n`;
333
- report += `- Salesforce-specific insights: ${priority.length > 0 ? 'Apex classes, triggers, and SObject metadata handling' : 'N/A'}\n\n`;
334
-
335
- report += `### Supported Projects\n`;
336
- report += `- Average score: ${calculateAverageScore(supported)}\n`;
337
- report += `- Common issues: ${extractCommonIssues(supported).join(', ') || 'None'}\n\n`;
338
-
339
- report += `### Unsupported Projects\n`;
340
- report += `- Average score: ${calculateAverageScore(unsupported)}\n`;
341
- report += `- Common issues: ${extractCommonIssues(unsupported).join(', ') || 'None'}\n\n`;
342
-
343
- report += `### Key Insights\n`;
344
- report += `1. **Salesforce Priority**: Detailed analysis of Apex, triggers, and metadata\n`;
345
- report += `2. Index DB impact on quality: ${evaluations.filter(e => e.hasIndexDb).length > 0 ? 'Projects with Index DB show...' : 'Mixed results'}\n`;
346
- report += `3. Unsupported projects: ${unsupported.length > 0 ? 'Generic analysis provides value but lacks framework-specific insights' : 'N/A'}\n`;
347
-
348
- return report;
349
- }
350
-
351
- function calculateAverageScore(evaluations: ProjectEvaluation[]): string {
352
- const scores = evaluations
353
- .flatMap(e => e.results)
354
- .map(r => r.score)
355
- .filter(s => s > 0);
356
-
357
- if (scores.length === 0) return 'N/A';
358
- return (scores.reduce((a, b) => a + b, 0) / scores.length).toFixed(1);
359
- }
360
-
361
- function extractCommonIssues(evaluations: ProjectEvaluation[]): string[] {
362
- const allIssues = evaluations.flatMap(e => e.synthesizedImprovements);
363
- const issueCounts: Record<string, number> = {};
364
-
365
- for (const issue of allIssues) {
366
- const normalized = issue.toLowerCase().replace(/[^a-z0-9\s]/g, '');
367
- issueCounts[normalized] = (issueCounts[normalized] || 0) + 1;
368
- }
369
-
370
- return Object.entries(issueCounts)
371
- .filter(([_, count]) => count > 1)
372
- .map(([issue, _]) => issue)
373
- .slice(0, 3);
374
- }
375
-
376
- /**
377
- * Main execution
378
- */
379
- async function main() {
380
- console.log('🚀 AI-Context Evaluator\n');
381
- console.log('Configuration:');
382
- console.log(` - Timeout: ${CONFIG.timeout}ms per call`);
383
- console.log(` - Max retries: ${CONFIG.maxRetries}`);
384
- console.log(` - Projects: ${PROJECTS.length}`);
385
- console.log(` - Models: Kimi K2.5, GLM 5, MiniMax 2.7\n`);
386
-
387
- // Validate API keys
388
- if (!CONFIG.opencodeApiKey) {
389
- console.error('❌ Error: OPENCODE_API_KEY not set');
390
- process.exit(1);
391
- }
392
- if (!CONFIG.minimaxApiKey) {
393
- console.error('❌ Error: MINIMAX_API_KEY not set');
394
- process.exit(1);
395
- }
396
-
397
- const evaluations: ProjectEvaluation[] = [];
398
-
399
- // Evaluate each project sequentially
400
- for (const project of PROJECTS) {
401
- try {
402
- const evaluation = await evaluateProject(project);
403
- evaluations.push(evaluation);
404
-
405
- // Save intermediate results
406
- const intermediateFile = `evaluation-${project.name}.json`;
407
- // In real implementation, write to file
408
- console.log(` 💾 Saved intermediate results\n`);
409
-
410
- } catch (error) {
411
- console.error(` ❌ Failed to evaluate ${project.name}:`, error);
412
- evaluations.push({
413
- projectName: project.name,
414
- projectType: project.type as 'priority' | 'supported' | 'unsupported',
415
- aiContextPath: '',
416
- hasIndexDb: false,
417
- results: [],
418
- summary: `Error: ${error}`,
419
- synthesizedImprovements: [],
420
- });
421
- }
422
- }
423
-
424
- // Generate final report
425
- console.log('\n📊 Generating final report...\n');
426
- const report = generateReport(evaluations);
427
-
428
- // Save report
429
- const reportPath = `ai-context-evaluation-report-${Date.now()}.md`;
430
- writeFileSync(reportPath, report);
431
- console.log(`✅ Report saved to: ${reportPath}`);
432
- console.log('\n📋 Summary:');
433
- console.log(report.split('\n').slice(0, 20).join('\n'));
434
- console.log('\n... (truncated)');
435
- }
436
-
437
- // Run if called directly
438
- main().catch(console.error);
439
-
440
- export { evaluateProject, createEvaluationPrompt, generateReport };