codecritique 1.2.3 → 2.0.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 +3 -3
- package/package.json +22 -20
- package/src/content-retrieval.js +109 -161
- package/src/content-retrieval.test.js +49 -9
- package/src/custom-documents.js +29 -14
- package/src/feedback-loader.js +19 -8
- package/src/index.js +97 -48
- package/src/llm.js +7 -3
- package/src/project-analyzer.js +92 -26
- package/src/rag-analyzer.js +70 -34
- package/src/rag-analyzer.test.js +12 -1
- package/src/rag-review.js +14 -7
- package/src/zero-shot-classifier-open.js +16 -7
package/src/custom-documents.js
CHANGED
|
@@ -70,7 +70,8 @@ export class CustomDocumentProcessor {
|
|
|
70
70
|
const headerMatch = content.match(/^#\s+(.+)$/m);
|
|
71
71
|
if (headerMatch) {
|
|
72
72
|
documentTitle = headerMatch[1].trim();
|
|
73
|
-
}
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
74
75
|
// If no header found, try to extract filename from title like "instruction:./FILENAME.md"
|
|
75
76
|
const filePathMatch = title.match(/:\.\/([^/]+)\.([a-zA-Z]+)$/);
|
|
76
77
|
if (filePathMatch) {
|
|
@@ -88,7 +89,9 @@ export class CustomDocumentProcessor {
|
|
|
88
89
|
|
|
89
90
|
for (let i = 0; i < sections.length; i++) {
|
|
90
91
|
const section = sections[i].trim();
|
|
91
|
-
if (!section)
|
|
92
|
+
if (!section) {
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
92
95
|
|
|
93
96
|
// Check if adding this section would exceed max chunk size
|
|
94
97
|
if (currentChunk.length + section.length > maxChunkSize && currentChunk.length > minChunkSize) {
|
|
@@ -108,7 +111,8 @@ export class CustomDocumentProcessor {
|
|
|
108
111
|
|
|
109
112
|
chunkIndex++;
|
|
110
113
|
currentChunk = section;
|
|
111
|
-
}
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
112
116
|
// Add section to current chunk
|
|
113
117
|
currentChunk += (currentChunk ? '\n\n' : '') + section;
|
|
114
118
|
}
|
|
@@ -142,7 +146,8 @@ export class CustomDocumentProcessor {
|
|
|
142
146
|
|
|
143
147
|
verboseLog({}, chalk.gray(` Chunked document "${documentTitle}" into ${chunks.length} chunks`));
|
|
144
148
|
return chunks;
|
|
145
|
-
}
|
|
149
|
+
}
|
|
150
|
+
catch (error) {
|
|
146
151
|
console.error(chalk.red(`Error chunking document: ${error.message}`));
|
|
147
152
|
throw new EmbeddingError(`Document chunking failed: ${error.message}`);
|
|
148
153
|
}
|
|
@@ -195,7 +200,8 @@ export class CustomDocumentProcessor {
|
|
|
195
200
|
project_path: path.resolve(projectPath),
|
|
196
201
|
created_at: new Date().toISOString(),
|
|
197
202
|
};
|
|
198
|
-
}
|
|
203
|
+
}
|
|
204
|
+
else {
|
|
199
205
|
console.error(chalk.red(`Error generating embedding for chunk ${chunk.id}: batch processing failed`));
|
|
200
206
|
return null;
|
|
201
207
|
}
|
|
@@ -207,7 +213,8 @@ export class CustomDocumentProcessor {
|
|
|
207
213
|
|
|
208
214
|
verboseLog({}, chalk.gray(` Generated embeddings for ${validChunks.length}/${chunks.length} chunks`));
|
|
209
215
|
this.performanceMetrics.embeddingsCalculated += validChunks.length;
|
|
210
|
-
}
|
|
216
|
+
}
|
|
217
|
+
catch (error) {
|
|
211
218
|
console.error(chalk.red(`Error in batch embedding generation for document ${doc.title}: ${error.message}`));
|
|
212
219
|
// Fallback to individual processing for this document
|
|
213
220
|
verboseLog({}, chalk.yellow(` Falling back to individual processing for ${doc.title}`));
|
|
@@ -225,7 +232,8 @@ export class CustomDocumentProcessor {
|
|
|
225
232
|
project_path: path.resolve(projectPath),
|
|
226
233
|
created_at: new Date().toISOString(),
|
|
227
234
|
};
|
|
228
|
-
}
|
|
235
|
+
}
|
|
236
|
+
catch (error) {
|
|
229
237
|
console.error(chalk.red(`Error generating embedding for chunk ${chunk.id}: ${error.message}`));
|
|
230
238
|
return null;
|
|
231
239
|
}
|
|
@@ -254,7 +262,8 @@ export class CustomDocumentProcessor {
|
|
|
254
262
|
|
|
255
263
|
verboseLog({}, chalk.green(`Successfully processed ${allChunks.length} custom document chunks (${Date.now() - startTime}ms)`));
|
|
256
264
|
return allChunks;
|
|
257
|
-
}
|
|
265
|
+
}
|
|
266
|
+
catch (error) {
|
|
258
267
|
console.error(chalk.red(`Error processing custom documents: ${error.message}`));
|
|
259
268
|
throw new EmbeddingError(`Custom document processing failed: ${error.message}`);
|
|
260
269
|
}
|
|
@@ -327,7 +336,8 @@ export class CustomDocumentProcessor {
|
|
|
327
336
|
}
|
|
328
337
|
|
|
329
338
|
return filteredResults;
|
|
330
|
-
}
|
|
339
|
+
}
|
|
340
|
+
catch (error) {
|
|
331
341
|
console.error(chalk.red(`Error searching custom document chunks: ${error.message}`));
|
|
332
342
|
throw new EmbeddingError(`Custom document search failed: ${error.message}`);
|
|
333
343
|
}
|
|
@@ -360,7 +370,8 @@ export class CustomDocumentProcessor {
|
|
|
360
370
|
|
|
361
371
|
debug(`[getExistingChunks] No existing chunks found for project: ${resolvedProjectPath}`);
|
|
362
372
|
return [];
|
|
363
|
-
}
|
|
373
|
+
}
|
|
374
|
+
catch (error) {
|
|
364
375
|
debug(`[getExistingChunks] Error checking existing chunks: ${error.message}`);
|
|
365
376
|
return [];
|
|
366
377
|
}
|
|
@@ -419,7 +430,8 @@ export class CustomDocumentProcessor {
|
|
|
419
430
|
contextMatchBonus += MODERATE_BOOST_TECH_MATCH;
|
|
420
431
|
}
|
|
421
432
|
}
|
|
422
|
-
}
|
|
433
|
+
}
|
|
434
|
+
else if (queryArea !== 'GeneralJS_TS') {
|
|
423
435
|
contextMatchBonus += HEAVY_PENALTY_AREA_MISMATCH;
|
|
424
436
|
}
|
|
425
437
|
}
|
|
@@ -505,7 +517,8 @@ export class CustomDocumentProcessor {
|
|
|
505
517
|
this.h1EmbeddingCache.set(docTitlesToCalculate[i], titleEmbeddings[i]);
|
|
506
518
|
}
|
|
507
519
|
}
|
|
508
|
-
}
|
|
520
|
+
}
|
|
521
|
+
catch (error) {
|
|
509
522
|
debug(`[OPTIMIZATION] Error in batch title embedding calculation: ${error.message}`);
|
|
510
523
|
// Continue without title embeddings
|
|
511
524
|
}
|
|
@@ -522,7 +535,8 @@ export class CustomDocumentProcessor {
|
|
|
522
535
|
this.customDocumentChunks.delete(resolvedProjectPath);
|
|
523
536
|
this.cacheManager.customDocumentChunks.delete(resolvedProjectPath);
|
|
524
537
|
verboseLog({}, chalk.green(`Cleared custom document chunks for project: ${resolvedProjectPath}`));
|
|
525
|
-
}
|
|
538
|
+
}
|
|
539
|
+
catch (error) {
|
|
526
540
|
console.error(chalk.red(`Error clearing project chunks: ${error.message}`));
|
|
527
541
|
}
|
|
528
542
|
}
|
|
@@ -590,7 +604,8 @@ export class CustomDocumentProcessor {
|
|
|
590
604
|
};
|
|
591
605
|
|
|
592
606
|
verboseLog({}, chalk.green('CustomDocumentProcessor cleanup complete'));
|
|
593
|
-
}
|
|
607
|
+
}
|
|
608
|
+
finally {
|
|
594
609
|
this.cleaningUp = false;
|
|
595
610
|
}
|
|
596
611
|
}
|
package/src/feedback-loader.js
CHANGED
|
@@ -68,7 +68,8 @@ export async function loadFeedbackData(feedbackPath, options = {}) {
|
|
|
68
68
|
totalItems += itemCount;
|
|
69
69
|
verboseLog(verbose, chalk.cyan(`📋 Loaded feedback from ${file}: ${itemCount} items`));
|
|
70
70
|
}
|
|
71
|
-
}
|
|
71
|
+
}
|
|
72
|
+
catch (parseError) {
|
|
72
73
|
console.warn(chalk.yellow(`⚠️ Error parsing feedback file ${file}: ${parseError.message}`));
|
|
73
74
|
}
|
|
74
75
|
}
|
|
@@ -79,7 +80,8 @@ export async function loadFeedbackData(feedbackPath, options = {}) {
|
|
|
79
80
|
}
|
|
80
81
|
|
|
81
82
|
return {};
|
|
82
|
-
}
|
|
83
|
+
}
|
|
84
|
+
catch (error) {
|
|
83
85
|
console.error(chalk.red(`❌ Error loading feedback data: ${error.message}`));
|
|
84
86
|
return {};
|
|
85
87
|
}
|
|
@@ -112,7 +114,8 @@ export async function initializeSemanticSimilarity() {
|
|
|
112
114
|
semanticSimilarityInitialized = true;
|
|
113
115
|
semanticSimilarityAvailable = true;
|
|
114
116
|
verboseLog({}, chalk.green('[FeedbackLoader] Semantic similarity initialized using embeddings system'));
|
|
115
|
-
}
|
|
117
|
+
}
|
|
118
|
+
catch (error) {
|
|
116
119
|
console.warn(chalk.yellow(`[FeedbackLoader] Semantic similarity initialization failed: ${error.message}`));
|
|
117
120
|
semanticSimilarityAvailable = false;
|
|
118
121
|
}
|
|
@@ -154,7 +157,8 @@ async function calculateSemanticSimilarity(text1, text2) {
|
|
|
154
157
|
const similarity = calculateCosineSimilarity(embedding1, embedding2);
|
|
155
158
|
// Cosine similarity ranges from -1 to 1, normalize to 0-1
|
|
156
159
|
return (similarity + 1) / 2;
|
|
157
|
-
}
|
|
160
|
+
}
|
|
161
|
+
catch (error) {
|
|
158
162
|
console.warn(chalk.yellow(`[FeedbackLoader] Semantic similarity calculation failed: ${error.message}`));
|
|
159
163
|
return null;
|
|
160
164
|
}
|
|
@@ -207,7 +211,9 @@ export async function shouldSkipSimilarIssue(issueDescription, feedbackData, opt
|
|
|
207
211
|
|
|
208
212
|
// Check similarity with dismissed issues
|
|
209
213
|
for (const dismissed of dismissedIssues) {
|
|
210
|
-
if (!dismissed.originalIssue)
|
|
214
|
+
if (!dismissed.originalIssue) {
|
|
215
|
+
continue;
|
|
216
|
+
}
|
|
211
217
|
|
|
212
218
|
let similarity;
|
|
213
219
|
let similarityMethod;
|
|
@@ -222,7 +228,8 @@ export async function shouldSkipSimilarIssue(issueDescription, feedbackData, opt
|
|
|
222
228
|
similarity = calculateWordSimilarity(issueDescription, dismissed.originalIssue);
|
|
223
229
|
similarityMethod = 'word-based';
|
|
224
230
|
}
|
|
225
|
-
}
|
|
231
|
+
}
|
|
232
|
+
else {
|
|
226
233
|
// Use word-based similarity
|
|
227
234
|
similarity = calculateWordSimilarity(issueDescription, dismissed.originalIssue);
|
|
228
235
|
similarityMethod = 'word-based';
|
|
@@ -297,7 +304,9 @@ export async function calculateIssueSimilarity(text1, text2, options = {}) {
|
|
|
297
304
|
* @returns {number} Similarity score (0-1)
|
|
298
305
|
*/
|
|
299
306
|
export function calculateWordSimilarity(text1, text2) {
|
|
300
|
-
if (!text1 || !text2)
|
|
307
|
+
if (!text1 || !text2) {
|
|
308
|
+
return 0;
|
|
309
|
+
}
|
|
301
310
|
|
|
302
311
|
// Normalize and tokenize
|
|
303
312
|
const normalize = (text) =>
|
|
@@ -310,7 +319,9 @@ export function calculateWordSimilarity(text1, text2) {
|
|
|
310
319
|
const words1 = new Set(normalize(text1));
|
|
311
320
|
const words2 = new Set(normalize(text2));
|
|
312
321
|
|
|
313
|
-
if (words1.size === 0 || words2.size === 0)
|
|
322
|
+
if (words1.size === 0 || words2.size === 0) {
|
|
323
|
+
return 0;
|
|
324
|
+
}
|
|
314
325
|
|
|
315
326
|
// Calculate Jaccard similarity (intersection over union)
|
|
316
327
|
const intersection = [...words1].filter((word) => words2.has(word)).length;
|
package/src/index.js
CHANGED
|
@@ -103,11 +103,13 @@ program
|
|
|
103
103
|
await embeddingsSystem.clearAllEmbeddings();
|
|
104
104
|
console.log(chalk.green('All embeddings have been cleared.'));
|
|
105
105
|
await embeddingsSystem.databaseManager.cleanup();
|
|
106
|
-
}
|
|
106
|
+
}
|
|
107
|
+
catch (err) {
|
|
107
108
|
console.error(chalk.red('Error clearing all embeddings:'), err.message);
|
|
108
109
|
try {
|
|
109
110
|
await embeddingsSystem.databaseManager.cleanup();
|
|
110
|
-
}
|
|
111
|
+
}
|
|
112
|
+
catch (cleanupErr) {
|
|
111
113
|
console.error(chalk.red('Error during cleanup:'), cleanupErr.message);
|
|
112
114
|
}
|
|
113
115
|
process.exit(1);
|
|
@@ -203,7 +205,8 @@ const hasCommand = process.argv
|
|
|
203
205
|
if (!hasCommand && process.argv.length > 2) {
|
|
204
206
|
// If no command is specified but there are arguments, default to 'analyze'
|
|
205
207
|
program.parse(['node', 'index.js', 'analyze', ...process.argv.slice(2)]);
|
|
206
|
-
}
|
|
208
|
+
}
|
|
209
|
+
else {
|
|
207
210
|
program.parse();
|
|
208
211
|
}
|
|
209
212
|
|
|
@@ -223,7 +226,8 @@ process.on('SIGINT', async () => {
|
|
|
223
226
|
clearTimeout(forceExitTimeout); // Cleanup finished, clear the timeout
|
|
224
227
|
console.log(chalk.cyan('SIGINT handler: Exiting normally (code 0).'));
|
|
225
228
|
process.exit(0); // Exit normally
|
|
226
|
-
}
|
|
229
|
+
}
|
|
230
|
+
catch (err) {
|
|
227
231
|
console.error(chalk.red('Error during embeddingsSystem.cleanup():'), err.message);
|
|
228
232
|
clearTimeout(forceExitTimeout);
|
|
229
233
|
console.log(chalk.cyan('SIGINT handler: Exiting after error (code 1).'));
|
|
@@ -246,7 +250,8 @@ process.on('SIGTERM', async () => {
|
|
|
246
250
|
clearTimeout(forceExitTimeout); // Cleanup finished, clear the timeout
|
|
247
251
|
console.log(chalk.cyan('SIGTERM handler: Exiting normally (code 0).'));
|
|
248
252
|
process.exit(0); // Exit normally
|
|
249
|
-
}
|
|
253
|
+
}
|
|
254
|
+
catch (err) {
|
|
250
255
|
console.error(chalk.red('Error during embeddingsSystem.cleanup():'), err.message);
|
|
251
256
|
clearTimeout(forceExitTimeout);
|
|
252
257
|
console.log(chalk.cyan('SIGTERM handler: Exiting after error (code 1).'));
|
|
@@ -366,13 +371,15 @@ async function runCodeReview(options) {
|
|
|
366
371
|
diffWith: options.diffWith,
|
|
367
372
|
};
|
|
368
373
|
reviewTask = reviewPullRequest(changedFiles, enhancedReviewOptions);
|
|
369
|
-
}
|
|
374
|
+
}
|
|
375
|
+
else if (options.file) {
|
|
370
376
|
operationDescription = `single file: ${options.file}`;
|
|
371
377
|
if (!fs.existsSync(options.file)) {
|
|
372
378
|
throw new Error(`File not found: ${options.file}`);
|
|
373
379
|
}
|
|
374
380
|
reviewTask = reviewFile(options.file, reviewOptions);
|
|
375
|
-
}
|
|
381
|
+
}
|
|
382
|
+
else if (options.files && options.files.length > 0) {
|
|
376
383
|
const filesToAnalyze = await expandFilePatterns(options.files);
|
|
377
384
|
if (filesToAnalyze.length === 0) {
|
|
378
385
|
console.log(chalk.yellow('No files found matching the specified patterns. Exiting.'));
|
|
@@ -380,7 +387,8 @@ async function runCodeReview(options) {
|
|
|
380
387
|
}
|
|
381
388
|
operationDescription = `${filesToAnalyze.length} specific files/patterns`;
|
|
382
389
|
reviewTask = reviewFiles(filesToAnalyze, reviewOptions);
|
|
383
|
-
}
|
|
390
|
+
}
|
|
391
|
+
else {
|
|
384
392
|
// No valid options provided - show error and exit
|
|
385
393
|
console.error(chalk.red('Error: You must specify one of the following:'));
|
|
386
394
|
console.error(chalk.yellow(' --file <file> Analyze a single file'));
|
|
@@ -418,7 +426,8 @@ async function runCodeReview(options) {
|
|
|
418
426
|
// Pass the detailed results array to the output function
|
|
419
427
|
outputFn(reviewResult.results, options);
|
|
420
428
|
console.log(chalk.bold.green(`\nAnalysis complete for ${operationDescription}! (${duration}s)`));
|
|
421
|
-
}
|
|
429
|
+
}
|
|
430
|
+
else {
|
|
422
431
|
// No results to display (e.g., all files were excluded/skipped)
|
|
423
432
|
const message = reviewResult.message || 'All files were excluded from review (e.g., config files, lock files).';
|
|
424
433
|
console.log(chalk.yellow(message));
|
|
@@ -432,7 +441,8 @@ async function runCodeReview(options) {
|
|
|
432
441
|
|
|
433
442
|
console.log(chalk.bold.yellow(`\nReview complete for ${operationDescription} - no reviewable files found (${duration}s)`));
|
|
434
443
|
}
|
|
435
|
-
}
|
|
444
|
+
}
|
|
445
|
+
else {
|
|
436
446
|
console.error(chalk.red('\nCode review process failed.'));
|
|
437
447
|
if (reviewResult && reviewResult.error) {
|
|
438
448
|
console.error(chalk.red(`Error: ${reviewResult.error}`));
|
|
@@ -445,11 +455,13 @@ async function runCodeReview(options) {
|
|
|
445
455
|
await embeddingsSystem.cleanup();
|
|
446
456
|
await cleanupClassifier();
|
|
447
457
|
console.log(chalk.green('All resources cleaned up successfully'));
|
|
448
|
-
}
|
|
458
|
+
}
|
|
459
|
+
catch (cleanupErr) {
|
|
449
460
|
console.error(chalk.yellow('Error during cleanup:'), cleanupErr.message);
|
|
450
461
|
process.exit(1);
|
|
451
462
|
}
|
|
452
|
-
}
|
|
463
|
+
}
|
|
464
|
+
catch (err) {
|
|
453
465
|
console.error(chalk.red(`\nError during code review (${operationDescription}):`), err.message);
|
|
454
466
|
console.error(err.stack);
|
|
455
467
|
// Clean up resources even on error
|
|
@@ -457,7 +469,8 @@ async function runCodeReview(options) {
|
|
|
457
469
|
await embeddingsSystem.cleanup();
|
|
458
470
|
await cleanupClassifier();
|
|
459
471
|
console.log(chalk.green('All resources cleaned up successfully'));
|
|
460
|
-
}
|
|
472
|
+
}
|
|
473
|
+
catch (cleanupErr) {
|
|
461
474
|
console.error(chalk.red('Error during cleanup:'), cleanupErr.message);
|
|
462
475
|
}
|
|
463
476
|
process.exit(1);
|
|
@@ -500,7 +513,8 @@ async function generateEmbeddings(options) {
|
|
|
500
513
|
.map((line) => line.trim())
|
|
501
514
|
.filter((line) => line && !line.startsWith('#'));
|
|
502
515
|
excludePatterns = [...excludePatterns, ...filePatterns];
|
|
503
|
-
}
|
|
516
|
+
}
|
|
517
|
+
else {
|
|
504
518
|
console.warn(chalk.yellow(`Exclude file not found: ${excludeFilePath}`));
|
|
505
519
|
}
|
|
506
520
|
}
|
|
@@ -512,19 +526,22 @@ async function generateEmbeddings(options) {
|
|
|
512
526
|
// Log gitignore status
|
|
513
527
|
if (options.gitignore === false) {
|
|
514
528
|
console.log(chalk.yellow('Automatic .gitignore exclusion is disabled.'));
|
|
515
|
-
}
|
|
529
|
+
}
|
|
530
|
+
else {
|
|
516
531
|
console.log(chalk.cyan('Respecting .gitignore patterns (if present).'));
|
|
517
532
|
}
|
|
518
533
|
console.log(chalk.green('Exclusion pattern processing complete.'));
|
|
519
534
|
|
|
520
535
|
// Get files to process
|
|
521
536
|
let filesToProcess = [];
|
|
537
|
+
const runMode = options.files && options.files.length > 0 ? 'partial' : 'full';
|
|
522
538
|
|
|
523
539
|
if (options.files && options.files.length > 0) {
|
|
524
540
|
console.log(chalk.cyan('Processing specified files/patterns...'));
|
|
525
541
|
filesToProcess = await expandFilePatterns(options.files, baseDir);
|
|
526
542
|
console.log(chalk.green(`Expanded specified files/patterns to ${filesToProcess.length} files.`));
|
|
527
|
-
}
|
|
543
|
+
}
|
|
544
|
+
else {
|
|
528
545
|
console.log(chalk.cyan(`Scanning directory for supported files: ${baseDir}`));
|
|
529
546
|
// Show spinner during file discovery
|
|
530
547
|
const scanSpinner = new Spinner('Scanning files... %s');
|
|
@@ -585,15 +602,19 @@ async function generateEmbeddings(options) {
|
|
|
585
602
|
baseDir: baseDir,
|
|
586
603
|
batchSize: 100, // Set a reasonable batch size
|
|
587
604
|
maxLines: parseInt(options.maxLines || '1000', 10),
|
|
605
|
+
runMode,
|
|
588
606
|
onProgress: (status) => {
|
|
589
607
|
// Update counters based on status
|
|
590
608
|
if (status === 'processed') {
|
|
591
609
|
processedCount++;
|
|
592
|
-
}
|
|
610
|
+
}
|
|
611
|
+
else if (status === 'skipped') {
|
|
593
612
|
skippedCount++;
|
|
594
|
-
}
|
|
613
|
+
}
|
|
614
|
+
else if (status === 'failed') {
|
|
595
615
|
failedCount++;
|
|
596
|
-
}
|
|
616
|
+
}
|
|
617
|
+
else if (status === 'excluded') {
|
|
597
618
|
excludedCount++;
|
|
598
619
|
}
|
|
599
620
|
|
|
@@ -601,7 +622,8 @@ async function generateEmbeddings(options) {
|
|
|
601
622
|
updateSpinner();
|
|
602
623
|
},
|
|
603
624
|
});
|
|
604
|
-
}
|
|
625
|
+
}
|
|
626
|
+
finally {
|
|
605
627
|
// Clean up the progress display even if embedding generation fails.
|
|
606
628
|
clearInterval(progressInterval);
|
|
607
629
|
spinner.stop(true);
|
|
@@ -625,9 +647,6 @@ async function generateEmbeddings(options) {
|
|
|
625
647
|
forceAnalysis: options.forceAnalysis,
|
|
626
648
|
});
|
|
627
649
|
|
|
628
|
-
// Store project summary in embeddings system for later use
|
|
629
|
-
await embeddingsSystem.storeProjectSummary(projectDir, projectSummary);
|
|
630
|
-
|
|
631
650
|
console.log(chalk.green('✅ Project analysis complete and stored'));
|
|
632
651
|
verboseLog(options, chalk.gray(` Project: ${projectSummary.projectName}`));
|
|
633
652
|
verboseLog(
|
|
@@ -637,7 +656,8 @@ async function generateEmbeddings(options) {
|
|
|
637
656
|
)
|
|
638
657
|
);
|
|
639
658
|
verboseLog(options, chalk.gray(` Key patterns: ${projectSummary.keyPatterns.length}`));
|
|
640
|
-
}
|
|
659
|
+
}
|
|
660
|
+
catch (error) {
|
|
641
661
|
console.error(chalk.red('⚠️ Project analysis failed but continuing:'), error.message);
|
|
642
662
|
}
|
|
643
663
|
|
|
@@ -645,7 +665,8 @@ async function generateEmbeddings(options) {
|
|
|
645
665
|
console.log(chalk.cyan('Cleaning up resources...'));
|
|
646
666
|
await embeddingsSystem.cleanup();
|
|
647
667
|
console.log(chalk.green('Cleanup successful.'));
|
|
648
|
-
}
|
|
668
|
+
}
|
|
669
|
+
catch (err) {
|
|
649
670
|
console.error(chalk.red('Error generating embeddings:'), err.message);
|
|
650
671
|
console.error(err.stack);
|
|
651
672
|
// Clean up resources even on error
|
|
@@ -653,7 +674,8 @@ async function generateEmbeddings(options) {
|
|
|
653
674
|
console.log(chalk.cyan('Cleaning up resources after error...'));
|
|
654
675
|
await embeddingsSystem.cleanup();
|
|
655
676
|
console.log(chalk.green('Cleanup successful.'));
|
|
656
|
-
}
|
|
677
|
+
}
|
|
678
|
+
catch (cleanupErr) {
|
|
657
679
|
console.error(chalk.red('Error during cleanup:'), cleanupErr.message);
|
|
658
680
|
}
|
|
659
681
|
process.exit(1);
|
|
@@ -679,13 +701,15 @@ async function clearEmbeddings(options) {
|
|
|
679
701
|
// Clean up resources (only database connection since we skipped full initialization)
|
|
680
702
|
console.log(chalk.cyan('Cleaning up resources...'));
|
|
681
703
|
await embeddingsSystem.databaseManager.cleanup();
|
|
682
|
-
}
|
|
704
|
+
}
|
|
705
|
+
catch (err) {
|
|
683
706
|
console.error(chalk.red('Error clearing embeddings:'), err.message);
|
|
684
707
|
console.error(err.stack);
|
|
685
708
|
// Clean up resources even on error (only database connection)
|
|
686
709
|
try {
|
|
687
710
|
await embeddingsSystem.databaseManager.cleanup();
|
|
688
|
-
}
|
|
711
|
+
}
|
|
712
|
+
catch (cleanupErr) {
|
|
689
713
|
console.error(chalk.red('Error during cleanup:'), cleanupErr.message);
|
|
690
714
|
}
|
|
691
715
|
process.exit(1);
|
|
@@ -704,7 +728,8 @@ async function showEmbeddingStats(options) {
|
|
|
704
728
|
|
|
705
729
|
if (options.directory) {
|
|
706
730
|
console.log(chalk.cyan(`Fetching embedding statistics for project: ${projectDir}`));
|
|
707
|
-
}
|
|
731
|
+
}
|
|
732
|
+
else {
|
|
708
733
|
console.log(chalk.cyan('Fetching embedding statistics for all projects...'));
|
|
709
734
|
}
|
|
710
735
|
|
|
@@ -715,7 +740,8 @@ async function showEmbeddingStats(options) {
|
|
|
715
740
|
|
|
716
741
|
if (!stats || Object.keys(stats).length === 0 || stats.totalCount === 0) {
|
|
717
742
|
console.log(chalk.yellow('No embeddings found or database is empty.'));
|
|
718
|
-
}
|
|
743
|
+
}
|
|
744
|
+
else {
|
|
719
745
|
console.log(` ${chalk.cyan('Total Embeddings:')} ${chalk.green(stats.totalCount)}`);
|
|
720
746
|
if (stats.dimensions) {
|
|
721
747
|
console.log(` ${chalk.cyan('Vector Dimensions:')} ${chalk.green(stats.dimensions)}`);
|
|
@@ -734,7 +760,8 @@ async function showEmbeddingStats(options) {
|
|
|
734
760
|
// Clean up resources
|
|
735
761
|
// console.log(chalk.cyan('Cleaning up resources...'));
|
|
736
762
|
// await embeddingsSystem.cleanup();
|
|
737
|
-
}
|
|
763
|
+
}
|
|
764
|
+
catch (err) {
|
|
738
765
|
console.error(chalk.red('Error fetching embedding statistics:'), err.message);
|
|
739
766
|
console.error(err.stack);
|
|
740
767
|
// Clean up resources even on error
|
|
@@ -870,10 +897,12 @@ async function findSupportedFiles(directory, options = {}) {
|
|
|
870
897
|
// Add log after the filtering loop (now just assignment)
|
|
871
898
|
verboseLog(options, chalk.green(`Finished filtering glob results. ${finalFiles.length} files remain.`));
|
|
872
899
|
return finalFiles;
|
|
873
|
-
}
|
|
900
|
+
}
|
|
901
|
+
catch (err) {
|
|
874
902
|
if (err.name === 'AbortError') {
|
|
875
903
|
console.error(chalk.red('Glob operation timed out. The directory might be too large or complex.'));
|
|
876
|
-
}
|
|
904
|
+
}
|
|
905
|
+
else {
|
|
877
906
|
console.error(chalk.red(`Error during glob file search: ${err.message}`));
|
|
878
907
|
}
|
|
879
908
|
console.error(err.stack); // Log stack for debugging
|
|
@@ -898,7 +927,8 @@ async function expandFilePatterns(patterns, baseDir = process.cwd()) {
|
|
|
898
927
|
// Check if it's a direct file path first
|
|
899
928
|
if (fs.existsSync(absolutePattern) && fs.statSync(absolutePattern).isFile()) {
|
|
900
929
|
files.add(absolutePattern);
|
|
901
|
-
}
|
|
930
|
+
}
|
|
931
|
+
else {
|
|
902
932
|
// Treat as a glob pattern
|
|
903
933
|
// Use the original pattern with baseDir as cwd for correct globbing
|
|
904
934
|
const matchedFiles = await glob.glob(pattern, { cwd: baseDir, absolute: true, nodir: true });
|
|
@@ -911,7 +941,8 @@ async function expandFilePatterns(patterns, baseDir = process.cwd()) {
|
|
|
911
941
|
}
|
|
912
942
|
}
|
|
913
943
|
return Array.from(files);
|
|
914
|
-
}
|
|
944
|
+
}
|
|
945
|
+
catch (err) {
|
|
915
946
|
console.error(chalk.red('Error expanding file patterns:'), err.message);
|
|
916
947
|
return [];
|
|
917
948
|
}
|
|
@@ -940,7 +971,8 @@ function getChangedFiles(branch, workingDir = process.cwd()) {
|
|
|
940
971
|
// Ensure the base branch exists locally as well (crucial for diff operations)
|
|
941
972
|
try {
|
|
942
973
|
ensureBranchExists(baseBranch, workingDir);
|
|
943
|
-
}
|
|
974
|
+
}
|
|
975
|
+
catch (error) {
|
|
944
976
|
console.warn(chalk.yellow(`Warning: Could not ensure base branch '${baseBranch}' exists locally: ${error.message}`));
|
|
945
977
|
// Continue with the original baseBranch name, it might work with remote refs
|
|
946
978
|
}
|
|
@@ -963,7 +995,8 @@ function getChangedFiles(branch, workingDir = process.cwd()) {
|
|
|
963
995
|
}
|
|
964
996
|
|
|
965
997
|
return changedFiles;
|
|
966
|
-
}
|
|
998
|
+
}
|
|
999
|
+
catch (err) {
|
|
967
1000
|
console.error(chalk.red('Error getting git diff:'), err.message);
|
|
968
1001
|
return [];
|
|
969
1002
|
}
|
|
@@ -989,7 +1022,9 @@ function outputJson(reviewResults, options) {
|
|
|
989
1022
|
filesWithIssues: reviewResults.filter((r) => r.success && !r.skipped && r.results?.issues?.length > 0).length,
|
|
990
1023
|
totalIssues: reviewResults.reduce((sum, r) => sum + (r.results?.issues?.length || 0), 0),
|
|
991
1024
|
issuesWithCodeSuggestions: reviewResults.reduce((sum, r) => {
|
|
992
|
-
if (!r.success || r.skipped || !r.results?.issues)
|
|
1025
|
+
if (!r.success || r.skipped || !r.results?.issues) {
|
|
1026
|
+
return sum;
|
|
1027
|
+
}
|
|
993
1028
|
return sum + r.results.issues.filter((issue) => issue.codeSuggestion).length;
|
|
994
1029
|
}, 0),
|
|
995
1030
|
skippedFiles: reviewResults.filter((r) => r.skipped).length,
|
|
@@ -1020,7 +1055,8 @@ function outputJson(reviewResults, options) {
|
|
|
1020
1055
|
if (options && options.outputFile) {
|
|
1021
1056
|
fs.writeFileSync(options.outputFile, jsonOutput, 'utf8');
|
|
1022
1057
|
console.log(chalk.green(`JSON output saved to: ${options.outputFile}`));
|
|
1023
|
-
}
|
|
1058
|
+
}
|
|
1059
|
+
else {
|
|
1024
1060
|
// Write JSON output to stdout (process.stdout is not buffered)
|
|
1025
1061
|
process.stdout.write(jsonOutput);
|
|
1026
1062
|
}
|
|
@@ -1049,8 +1085,12 @@ function outputMarkdown(reviewResults, options) {
|
|
|
1049
1085
|
`- **Total Issues Found:** ${totalIssues}`,
|
|
1050
1086
|
];
|
|
1051
1087
|
|
|
1052
|
-
if (skippedFiles > 0)
|
|
1053
|
-
|
|
1088
|
+
if (skippedFiles > 0) {
|
|
1089
|
+
lines.push(`- **Files Skipped:** ${skippedFiles}`);
|
|
1090
|
+
}
|
|
1091
|
+
if (errorFiles > 0) {
|
|
1092
|
+
lines.push(`- **Errors:** ${errorFiles}`);
|
|
1093
|
+
}
|
|
1054
1094
|
|
|
1055
1095
|
lines.push('', '## Detailed Review per File', '');
|
|
1056
1096
|
|
|
@@ -1129,8 +1169,12 @@ function outputText(reviewResults, cliOptions) {
|
|
|
1129
1169
|
console.log(`Files Analyzed: ${chalk.bold(totalFiles)}`);
|
|
1130
1170
|
console.log(`Files with Issues: ${chalk.bold(filesWithIssues)}`);
|
|
1131
1171
|
console.log(`Total Issues Found: ${chalk.bold(totalIssues)}`);
|
|
1132
|
-
if (skippedFiles > 0)
|
|
1133
|
-
|
|
1172
|
+
if (skippedFiles > 0) {
|
|
1173
|
+
console.log(`Files Skipped: ${chalk.yellow(skippedFiles)}`);
|
|
1174
|
+
}
|
|
1175
|
+
if (errorFiles > 0) {
|
|
1176
|
+
console.log(`Errors: ${chalk.red(errorFiles)}`);
|
|
1177
|
+
}
|
|
1134
1178
|
console.log(chalk.bold.blue('================================================'));
|
|
1135
1179
|
|
|
1136
1180
|
reviewResults.forEach((fileResult) => {
|
|
@@ -1290,7 +1334,8 @@ async function analyzePRHistory(options) {
|
|
|
1290
1334
|
// Display results using utility function
|
|
1291
1335
|
displayAnalysisResults(results, duration);
|
|
1292
1336
|
console.log(chalk.bold.green(`\nPR history analysis complete for ${repository}!`));
|
|
1293
|
-
}
|
|
1337
|
+
}
|
|
1338
|
+
catch (error) {
|
|
1294
1339
|
const endTime = Date.now();
|
|
1295
1340
|
const duration = ((endTime - startTime) / 1000).toFixed(2);
|
|
1296
1341
|
console.error(chalk.red(`\nError during PR history analysis (${duration}s):`), error.message);
|
|
@@ -1324,10 +1369,12 @@ async function getPRHistoryStatus(options) {
|
|
|
1324
1369
|
if (hasComments) {
|
|
1325
1370
|
const stats = await getPRCommentsStats(repository, projectPath);
|
|
1326
1371
|
displayDatabaseStats(stats, hasComments);
|
|
1327
|
-
}
|
|
1372
|
+
}
|
|
1373
|
+
else {
|
|
1328
1374
|
displayDatabaseStats(null, hasComments);
|
|
1329
1375
|
}
|
|
1330
|
-
}
|
|
1376
|
+
}
|
|
1377
|
+
catch (error) {
|
|
1331
1378
|
console.error(chalk.red('Error getting PR history status:'), error.message);
|
|
1332
1379
|
process.exit(1);
|
|
1333
1380
|
}
|
|
@@ -1388,10 +1435,12 @@ async function clearPRHistory(options) {
|
|
|
1388
1435
|
|
|
1389
1436
|
if (cleared) {
|
|
1390
1437
|
console.log(chalk.bold.green(`\nPR analysis data cleared successfully for ${repository}`));
|
|
1391
|
-
}
|
|
1438
|
+
}
|
|
1439
|
+
else {
|
|
1392
1440
|
console.log(chalk.yellow('No data was found to clear.'));
|
|
1393
1441
|
}
|
|
1394
|
-
}
|
|
1442
|
+
}
|
|
1443
|
+
catch (error) {
|
|
1395
1444
|
console.error(chalk.red('Error clearing PR history data:'), error.message);
|
|
1396
1445
|
verboseLog(options, error.stack);
|
|
1397
1446
|
process.exit(1);
|
package/src/llm.js
CHANGED
|
@@ -27,7 +27,9 @@ let anthropic = null;
|
|
|
27
27
|
* @returns {Anthropic} The Anthropic client
|
|
28
28
|
*/
|
|
29
29
|
function getAnthropicClient() {
|
|
30
|
-
if (anthropic)
|
|
30
|
+
if (anthropic) {
|
|
31
|
+
return anthropic;
|
|
32
|
+
}
|
|
31
33
|
const apiKey = process.env.ANTHROPIC_API_KEY;
|
|
32
34
|
if (!apiKey) {
|
|
33
35
|
throw new Error('ANTHROPIC_API_KEY is required for analysis. Set it in env or .env before running analyze.');
|
|
@@ -127,14 +129,16 @@ async function sendPromptToClaude(prompt, options = {}) {
|
|
|
127
129
|
usage: response.usage,
|
|
128
130
|
json: toolUse.input,
|
|
129
131
|
};
|
|
130
|
-
}
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
131
134
|
return {
|
|
132
135
|
content: response.content[0]?.text || '',
|
|
133
136
|
model: response.model,
|
|
134
137
|
usage: response.usage,
|
|
135
138
|
};
|
|
136
139
|
}
|
|
137
|
-
}
|
|
140
|
+
}
|
|
141
|
+
catch (error) {
|
|
138
142
|
console.error(chalk.red(`Error sending prompt to Claude: ${error.message}`));
|
|
139
143
|
throw error;
|
|
140
144
|
}
|