codecritique 1.2.4 → 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 +38 -26
- package/src/custom-documents.js +29 -14
- package/src/feedback-loader.js +19 -8
- package/src/index.js +95 -45
- package/src/llm.js +7 -3
- package/src/project-analyzer.js +50 -23
- package/src/rag-analyzer.js +70 -34
- package/src/rag-analyzer.test.js +3 -1
- package/src/rag-review.js +14 -7
- package/src/zero-shot-classifier-open.js +16 -7
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,7 +526,8 @@ 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.'));
|
|
@@ -525,7 +540,8 @@ async function generateEmbeddings(options) {
|
|
|
525
540
|
console.log(chalk.cyan('Processing specified files/patterns...'));
|
|
526
541
|
filesToProcess = await expandFilePatterns(options.files, baseDir);
|
|
527
542
|
console.log(chalk.green(`Expanded specified files/patterns to ${filesToProcess.length} files.`));
|
|
528
|
-
}
|
|
543
|
+
}
|
|
544
|
+
else {
|
|
529
545
|
console.log(chalk.cyan(`Scanning directory for supported files: ${baseDir}`));
|
|
530
546
|
// Show spinner during file discovery
|
|
531
547
|
const scanSpinner = new Spinner('Scanning files... %s');
|
|
@@ -591,11 +607,14 @@ async function generateEmbeddings(options) {
|
|
|
591
607
|
// Update counters based on status
|
|
592
608
|
if (status === 'processed') {
|
|
593
609
|
processedCount++;
|
|
594
|
-
}
|
|
610
|
+
}
|
|
611
|
+
else if (status === 'skipped') {
|
|
595
612
|
skippedCount++;
|
|
596
|
-
}
|
|
613
|
+
}
|
|
614
|
+
else if (status === 'failed') {
|
|
597
615
|
failedCount++;
|
|
598
|
-
}
|
|
616
|
+
}
|
|
617
|
+
else if (status === 'excluded') {
|
|
599
618
|
excludedCount++;
|
|
600
619
|
}
|
|
601
620
|
|
|
@@ -603,7 +622,8 @@ async function generateEmbeddings(options) {
|
|
|
603
622
|
updateSpinner();
|
|
604
623
|
},
|
|
605
624
|
});
|
|
606
|
-
}
|
|
625
|
+
}
|
|
626
|
+
finally {
|
|
607
627
|
// Clean up the progress display even if embedding generation fails.
|
|
608
628
|
clearInterval(progressInterval);
|
|
609
629
|
spinner.stop(true);
|
|
@@ -636,7 +656,8 @@ async function generateEmbeddings(options) {
|
|
|
636
656
|
)
|
|
637
657
|
);
|
|
638
658
|
verboseLog(options, chalk.gray(` Key patterns: ${projectSummary.keyPatterns.length}`));
|
|
639
|
-
}
|
|
659
|
+
}
|
|
660
|
+
catch (error) {
|
|
640
661
|
console.error(chalk.red('⚠️ Project analysis failed but continuing:'), error.message);
|
|
641
662
|
}
|
|
642
663
|
|
|
@@ -644,7 +665,8 @@ async function generateEmbeddings(options) {
|
|
|
644
665
|
console.log(chalk.cyan('Cleaning up resources...'));
|
|
645
666
|
await embeddingsSystem.cleanup();
|
|
646
667
|
console.log(chalk.green('Cleanup successful.'));
|
|
647
|
-
}
|
|
668
|
+
}
|
|
669
|
+
catch (err) {
|
|
648
670
|
console.error(chalk.red('Error generating embeddings:'), err.message);
|
|
649
671
|
console.error(err.stack);
|
|
650
672
|
// Clean up resources even on error
|
|
@@ -652,7 +674,8 @@ async function generateEmbeddings(options) {
|
|
|
652
674
|
console.log(chalk.cyan('Cleaning up resources after error...'));
|
|
653
675
|
await embeddingsSystem.cleanup();
|
|
654
676
|
console.log(chalk.green('Cleanup successful.'));
|
|
655
|
-
}
|
|
677
|
+
}
|
|
678
|
+
catch (cleanupErr) {
|
|
656
679
|
console.error(chalk.red('Error during cleanup:'), cleanupErr.message);
|
|
657
680
|
}
|
|
658
681
|
process.exit(1);
|
|
@@ -678,13 +701,15 @@ async function clearEmbeddings(options) {
|
|
|
678
701
|
// Clean up resources (only database connection since we skipped full initialization)
|
|
679
702
|
console.log(chalk.cyan('Cleaning up resources...'));
|
|
680
703
|
await embeddingsSystem.databaseManager.cleanup();
|
|
681
|
-
}
|
|
704
|
+
}
|
|
705
|
+
catch (err) {
|
|
682
706
|
console.error(chalk.red('Error clearing embeddings:'), err.message);
|
|
683
707
|
console.error(err.stack);
|
|
684
708
|
// Clean up resources even on error (only database connection)
|
|
685
709
|
try {
|
|
686
710
|
await embeddingsSystem.databaseManager.cleanup();
|
|
687
|
-
}
|
|
711
|
+
}
|
|
712
|
+
catch (cleanupErr) {
|
|
688
713
|
console.error(chalk.red('Error during cleanup:'), cleanupErr.message);
|
|
689
714
|
}
|
|
690
715
|
process.exit(1);
|
|
@@ -703,7 +728,8 @@ async function showEmbeddingStats(options) {
|
|
|
703
728
|
|
|
704
729
|
if (options.directory) {
|
|
705
730
|
console.log(chalk.cyan(`Fetching embedding statistics for project: ${projectDir}`));
|
|
706
|
-
}
|
|
731
|
+
}
|
|
732
|
+
else {
|
|
707
733
|
console.log(chalk.cyan('Fetching embedding statistics for all projects...'));
|
|
708
734
|
}
|
|
709
735
|
|
|
@@ -714,7 +740,8 @@ async function showEmbeddingStats(options) {
|
|
|
714
740
|
|
|
715
741
|
if (!stats || Object.keys(stats).length === 0 || stats.totalCount === 0) {
|
|
716
742
|
console.log(chalk.yellow('No embeddings found or database is empty.'));
|
|
717
|
-
}
|
|
743
|
+
}
|
|
744
|
+
else {
|
|
718
745
|
console.log(` ${chalk.cyan('Total Embeddings:')} ${chalk.green(stats.totalCount)}`);
|
|
719
746
|
if (stats.dimensions) {
|
|
720
747
|
console.log(` ${chalk.cyan('Vector Dimensions:')} ${chalk.green(stats.dimensions)}`);
|
|
@@ -733,7 +760,8 @@ async function showEmbeddingStats(options) {
|
|
|
733
760
|
// Clean up resources
|
|
734
761
|
// console.log(chalk.cyan('Cleaning up resources...'));
|
|
735
762
|
// await embeddingsSystem.cleanup();
|
|
736
|
-
}
|
|
763
|
+
}
|
|
764
|
+
catch (err) {
|
|
737
765
|
console.error(chalk.red('Error fetching embedding statistics:'), err.message);
|
|
738
766
|
console.error(err.stack);
|
|
739
767
|
// Clean up resources even on error
|
|
@@ -869,10 +897,12 @@ async function findSupportedFiles(directory, options = {}) {
|
|
|
869
897
|
// Add log after the filtering loop (now just assignment)
|
|
870
898
|
verboseLog(options, chalk.green(`Finished filtering glob results. ${finalFiles.length} files remain.`));
|
|
871
899
|
return finalFiles;
|
|
872
|
-
}
|
|
900
|
+
}
|
|
901
|
+
catch (err) {
|
|
873
902
|
if (err.name === 'AbortError') {
|
|
874
903
|
console.error(chalk.red('Glob operation timed out. The directory might be too large or complex.'));
|
|
875
|
-
}
|
|
904
|
+
}
|
|
905
|
+
else {
|
|
876
906
|
console.error(chalk.red(`Error during glob file search: ${err.message}`));
|
|
877
907
|
}
|
|
878
908
|
console.error(err.stack); // Log stack for debugging
|
|
@@ -897,7 +927,8 @@ async function expandFilePatterns(patterns, baseDir = process.cwd()) {
|
|
|
897
927
|
// Check if it's a direct file path first
|
|
898
928
|
if (fs.existsSync(absolutePattern) && fs.statSync(absolutePattern).isFile()) {
|
|
899
929
|
files.add(absolutePattern);
|
|
900
|
-
}
|
|
930
|
+
}
|
|
931
|
+
else {
|
|
901
932
|
// Treat as a glob pattern
|
|
902
933
|
// Use the original pattern with baseDir as cwd for correct globbing
|
|
903
934
|
const matchedFiles = await glob.glob(pattern, { cwd: baseDir, absolute: true, nodir: true });
|
|
@@ -910,7 +941,8 @@ async function expandFilePatterns(patterns, baseDir = process.cwd()) {
|
|
|
910
941
|
}
|
|
911
942
|
}
|
|
912
943
|
return Array.from(files);
|
|
913
|
-
}
|
|
944
|
+
}
|
|
945
|
+
catch (err) {
|
|
914
946
|
console.error(chalk.red('Error expanding file patterns:'), err.message);
|
|
915
947
|
return [];
|
|
916
948
|
}
|
|
@@ -939,7 +971,8 @@ function getChangedFiles(branch, workingDir = process.cwd()) {
|
|
|
939
971
|
// Ensure the base branch exists locally as well (crucial for diff operations)
|
|
940
972
|
try {
|
|
941
973
|
ensureBranchExists(baseBranch, workingDir);
|
|
942
|
-
}
|
|
974
|
+
}
|
|
975
|
+
catch (error) {
|
|
943
976
|
console.warn(chalk.yellow(`Warning: Could not ensure base branch '${baseBranch}' exists locally: ${error.message}`));
|
|
944
977
|
// Continue with the original baseBranch name, it might work with remote refs
|
|
945
978
|
}
|
|
@@ -962,7 +995,8 @@ function getChangedFiles(branch, workingDir = process.cwd()) {
|
|
|
962
995
|
}
|
|
963
996
|
|
|
964
997
|
return changedFiles;
|
|
965
|
-
}
|
|
998
|
+
}
|
|
999
|
+
catch (err) {
|
|
966
1000
|
console.error(chalk.red('Error getting git diff:'), err.message);
|
|
967
1001
|
return [];
|
|
968
1002
|
}
|
|
@@ -988,7 +1022,9 @@ function outputJson(reviewResults, options) {
|
|
|
988
1022
|
filesWithIssues: reviewResults.filter((r) => r.success && !r.skipped && r.results?.issues?.length > 0).length,
|
|
989
1023
|
totalIssues: reviewResults.reduce((sum, r) => sum + (r.results?.issues?.length || 0), 0),
|
|
990
1024
|
issuesWithCodeSuggestions: reviewResults.reduce((sum, r) => {
|
|
991
|
-
if (!r.success || r.skipped || !r.results?.issues)
|
|
1025
|
+
if (!r.success || r.skipped || !r.results?.issues) {
|
|
1026
|
+
return sum;
|
|
1027
|
+
}
|
|
992
1028
|
return sum + r.results.issues.filter((issue) => issue.codeSuggestion).length;
|
|
993
1029
|
}, 0),
|
|
994
1030
|
skippedFiles: reviewResults.filter((r) => r.skipped).length,
|
|
@@ -1019,7 +1055,8 @@ function outputJson(reviewResults, options) {
|
|
|
1019
1055
|
if (options && options.outputFile) {
|
|
1020
1056
|
fs.writeFileSync(options.outputFile, jsonOutput, 'utf8');
|
|
1021
1057
|
console.log(chalk.green(`JSON output saved to: ${options.outputFile}`));
|
|
1022
|
-
}
|
|
1058
|
+
}
|
|
1059
|
+
else {
|
|
1023
1060
|
// Write JSON output to stdout (process.stdout is not buffered)
|
|
1024
1061
|
process.stdout.write(jsonOutput);
|
|
1025
1062
|
}
|
|
@@ -1048,8 +1085,12 @@ function outputMarkdown(reviewResults, options) {
|
|
|
1048
1085
|
`- **Total Issues Found:** ${totalIssues}`,
|
|
1049
1086
|
];
|
|
1050
1087
|
|
|
1051
|
-
if (skippedFiles > 0)
|
|
1052
|
-
|
|
1088
|
+
if (skippedFiles > 0) {
|
|
1089
|
+
lines.push(`- **Files Skipped:** ${skippedFiles}`);
|
|
1090
|
+
}
|
|
1091
|
+
if (errorFiles > 0) {
|
|
1092
|
+
lines.push(`- **Errors:** ${errorFiles}`);
|
|
1093
|
+
}
|
|
1053
1094
|
|
|
1054
1095
|
lines.push('', '## Detailed Review per File', '');
|
|
1055
1096
|
|
|
@@ -1128,8 +1169,12 @@ function outputText(reviewResults, cliOptions) {
|
|
|
1128
1169
|
console.log(`Files Analyzed: ${chalk.bold(totalFiles)}`);
|
|
1129
1170
|
console.log(`Files with Issues: ${chalk.bold(filesWithIssues)}`);
|
|
1130
1171
|
console.log(`Total Issues Found: ${chalk.bold(totalIssues)}`);
|
|
1131
|
-
if (skippedFiles > 0)
|
|
1132
|
-
|
|
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
|
+
}
|
|
1133
1178
|
console.log(chalk.bold.blue('================================================'));
|
|
1134
1179
|
|
|
1135
1180
|
reviewResults.forEach((fileResult) => {
|
|
@@ -1289,7 +1334,8 @@ async function analyzePRHistory(options) {
|
|
|
1289
1334
|
// Display results using utility function
|
|
1290
1335
|
displayAnalysisResults(results, duration);
|
|
1291
1336
|
console.log(chalk.bold.green(`\nPR history analysis complete for ${repository}!`));
|
|
1292
|
-
}
|
|
1337
|
+
}
|
|
1338
|
+
catch (error) {
|
|
1293
1339
|
const endTime = Date.now();
|
|
1294
1340
|
const duration = ((endTime - startTime) / 1000).toFixed(2);
|
|
1295
1341
|
console.error(chalk.red(`\nError during PR history analysis (${duration}s):`), error.message);
|
|
@@ -1323,10 +1369,12 @@ async function getPRHistoryStatus(options) {
|
|
|
1323
1369
|
if (hasComments) {
|
|
1324
1370
|
const stats = await getPRCommentsStats(repository, projectPath);
|
|
1325
1371
|
displayDatabaseStats(stats, hasComments);
|
|
1326
|
-
}
|
|
1372
|
+
}
|
|
1373
|
+
else {
|
|
1327
1374
|
displayDatabaseStats(null, hasComments);
|
|
1328
1375
|
}
|
|
1329
|
-
}
|
|
1376
|
+
}
|
|
1377
|
+
catch (error) {
|
|
1330
1378
|
console.error(chalk.red('Error getting PR history status:'), error.message);
|
|
1331
1379
|
process.exit(1);
|
|
1332
1380
|
}
|
|
@@ -1387,10 +1435,12 @@ async function clearPRHistory(options) {
|
|
|
1387
1435
|
|
|
1388
1436
|
if (cleared) {
|
|
1389
1437
|
console.log(chalk.bold.green(`\nPR analysis data cleared successfully for ${repository}`));
|
|
1390
|
-
}
|
|
1438
|
+
}
|
|
1439
|
+
else {
|
|
1391
1440
|
console.log(chalk.yellow('No data was found to clear.'));
|
|
1392
1441
|
}
|
|
1393
|
-
}
|
|
1442
|
+
}
|
|
1443
|
+
catch (error) {
|
|
1394
1444
|
console.error(chalk.red('Error clearing PR history data:'), error.message);
|
|
1395
1445
|
verboseLog(options, error.stack);
|
|
1396
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
|
}
|
package/src/project-analyzer.js
CHANGED
|
@@ -212,7 +212,8 @@ export class ProjectAnalyzer {
|
|
|
212
212
|
const currentEmbeddingInventoryHash = await this.calculateEmbeddingInventoryHash(projectPath);
|
|
213
213
|
if (existingSummary.embeddingInventoryHash !== currentEmbeddingInventoryHash) {
|
|
214
214
|
verboseLog(verbose, chalk.yellow('🔄 Embedding inventory changed, regenerating analysis...'));
|
|
215
|
-
}
|
|
215
|
+
}
|
|
216
|
+
else {
|
|
216
217
|
const currentHash = await this.calculateKeyFilesHash(existingSummary.keyFiles);
|
|
217
218
|
if (existingSummary.keyFilesHash === currentHash) {
|
|
218
219
|
verboseLog(verbose, chalk.green('✅ Project analysis up-to-date (no key file changes detected)'));
|
|
@@ -220,7 +221,8 @@ export class ProjectAnalyzer {
|
|
|
220
221
|
}
|
|
221
222
|
verboseLog(verbose, chalk.yellow('🔄 Key files changed, regenerating analysis...'));
|
|
222
223
|
}
|
|
223
|
-
}
|
|
224
|
+
}
|
|
225
|
+
else {
|
|
224
226
|
verboseLog(
|
|
225
227
|
verbose,
|
|
226
228
|
chalk.cyan(
|
|
@@ -260,7 +262,8 @@ export class ProjectAnalyzer {
|
|
|
260
262
|
verboseLog(verbose, chalk.gray(` Key files tracked: ${keyFiles.length}`));
|
|
261
263
|
|
|
262
264
|
return projectSummary;
|
|
263
|
-
}
|
|
265
|
+
}
|
|
266
|
+
catch (error) {
|
|
264
267
|
console.error(chalk.red('Error analyzing project:'), error.message);
|
|
265
268
|
return this.createFallbackSummary(projectPath);
|
|
266
269
|
}
|
|
@@ -285,7 +288,8 @@ export class ProjectAnalyzer {
|
|
|
285
288
|
return { ...summary, keyFiles };
|
|
286
289
|
}
|
|
287
290
|
return null;
|
|
288
|
-
}
|
|
291
|
+
}
|
|
292
|
+
catch (error) {
|
|
289
293
|
console.error(chalk.yellow('Warning: Could not load existing analysis:'), error.message);
|
|
290
294
|
return null;
|
|
291
295
|
}
|
|
@@ -299,7 +303,8 @@ export class ProjectAnalyzer {
|
|
|
299
303
|
const embeddingsSystem = getDefaultEmbeddingsSystem();
|
|
300
304
|
await embeddingsSystem.storeProjectSummary(projectPath, projectSummary);
|
|
301
305
|
verboseLog({}, chalk.green('✅ Project analysis stored in database'));
|
|
302
|
-
}
|
|
306
|
+
}
|
|
307
|
+
catch (error) {
|
|
303
308
|
console.error(chalk.yellow('Warning: Could not store analysis:'), error.message);
|
|
304
309
|
}
|
|
305
310
|
}
|
|
@@ -358,10 +363,12 @@ export class ProjectAnalyzer {
|
|
|
358
363
|
// Optimize table to sync indices with data and prevent TakeExec panics
|
|
359
364
|
try {
|
|
360
365
|
await table.optimize();
|
|
361
|
-
}
|
|
366
|
+
}
|
|
367
|
+
catch (optimizeError) {
|
|
362
368
|
if (optimizeError.message && optimizeError.message.includes('legacy format')) {
|
|
363
369
|
console.warn(chalk.yellow(`Skipping optimization due to legacy index format - will be auto-upgraded during normal operations`));
|
|
364
|
-
}
|
|
370
|
+
}
|
|
371
|
+
else {
|
|
365
372
|
console.warn(chalk.yellow(`Warning: Failed to optimize file embeddings table: ${optimizeError.message}`));
|
|
366
373
|
}
|
|
367
374
|
}
|
|
@@ -378,7 +385,8 @@ export class ProjectAnalyzer {
|
|
|
378
385
|
|
|
379
386
|
if (config.whereClause) {
|
|
380
387
|
query = query.where(`project_path = '${projectPath}' AND (${config.whereClause})`);
|
|
381
|
-
}
|
|
388
|
+
}
|
|
389
|
+
else if (config.terms) {
|
|
382
390
|
// For term-based searches, query ALL files and sort by depth to prioritize shallow config files
|
|
383
391
|
const allFiles = await table
|
|
384
392
|
.query()
|
|
@@ -406,12 +414,14 @@ export class ProjectAnalyzer {
|
|
|
406
414
|
|
|
407
415
|
return matches;
|
|
408
416
|
});
|
|
409
|
-
}
|
|
417
|
+
}
|
|
418
|
+
else {
|
|
410
419
|
query = query.where(`project_path = '${projectPath}'`);
|
|
411
420
|
}
|
|
412
421
|
|
|
413
422
|
return await query.limit(config.limit || 30).toArray();
|
|
414
|
-
}
|
|
423
|
+
}
|
|
424
|
+
catch (error) {
|
|
415
425
|
verboseLog({}, chalk.yellow(` ⚠️ Query failed for ${config.category}: ${error.message}`));
|
|
416
426
|
return [];
|
|
417
427
|
}
|
|
@@ -430,7 +440,8 @@ export class ProjectAnalyzer {
|
|
|
430
440
|
}
|
|
431
441
|
});
|
|
432
442
|
}
|
|
433
|
-
}
|
|
443
|
+
}
|
|
444
|
+
catch (error) {
|
|
434
445
|
console.error(chalk.red('Error mining embeddings:'), error.message);
|
|
435
446
|
return [];
|
|
436
447
|
}
|
|
@@ -444,11 +455,17 @@ export class ProjectAnalyzer {
|
|
|
444
455
|
* Unified file type matching using consolidated patterns
|
|
445
456
|
*/
|
|
446
457
|
matchesFileType(filePath, fileName, type) {
|
|
447
|
-
if (type === 'docs')
|
|
448
|
-
|
|
458
|
+
if (type === 'docs') {
|
|
459
|
+
return isDocumentationFile(filePath);
|
|
460
|
+
}
|
|
461
|
+
if (type === 'tests') {
|
|
462
|
+
return isTestFile(filePath);
|
|
463
|
+
}
|
|
449
464
|
|
|
450
465
|
const config = FILE_PATTERNS[type];
|
|
451
|
-
if (!config)
|
|
466
|
+
if (!config) {
|
|
467
|
+
return false;
|
|
468
|
+
}
|
|
452
469
|
|
|
453
470
|
const fileNameLower = fileName.toLowerCase();
|
|
454
471
|
const filePathLower = filePath.toLowerCase();
|
|
@@ -546,10 +563,12 @@ Select files following the criteria in the system instructions.`;
|
|
|
546
563
|
|
|
547
564
|
verboseLog({}, chalk.cyan(`🎯 LLM selected ${keyFiles.length} final key files`));
|
|
548
565
|
return keyFiles;
|
|
549
|
-
}
|
|
566
|
+
}
|
|
567
|
+
else {
|
|
550
568
|
throw new Error(`Failed to extract valid JSON array from LLM response`);
|
|
551
569
|
}
|
|
552
|
-
}
|
|
570
|
+
}
|
|
571
|
+
catch (error) {
|
|
553
572
|
console.error(chalk.red('Error in LLM selection:'), error.message);
|
|
554
573
|
verboseLog({}, chalk.yellow(' 🔄 Falling back to automatic selection...'));
|
|
555
574
|
return this.fallbackFileSelection(candidates, projectPath);
|
|
@@ -611,7 +630,8 @@ Select files following the criteria in the system instructions.`;
|
|
|
611
630
|
const content = fs.readFileSync(fullPath, 'utf8');
|
|
612
631
|
hash.update(content.substring(0, 1000));
|
|
613
632
|
}
|
|
614
|
-
}
|
|
633
|
+
}
|
|
634
|
+
catch {
|
|
615
635
|
hash.update(file.relativePath || file.path || '');
|
|
616
636
|
}
|
|
617
637
|
}
|
|
@@ -646,7 +666,8 @@ Select files following the criteria in the system instructions.`;
|
|
|
646
666
|
}
|
|
647
667
|
|
|
648
668
|
return hash.digest('hex');
|
|
649
|
-
}
|
|
669
|
+
}
|
|
670
|
+
catch (error) {
|
|
650
671
|
verboseLog({}, chalk.yellow(`Warning: Could not calculate embedding inventory hash: ${error.message}`));
|
|
651
672
|
return 'embedding-inventory-unavailable';
|
|
652
673
|
}
|
|
@@ -816,12 +837,14 @@ Follow the analysis guidelines from the system instructions to identify custom i
|
|
|
816
837
|
validatedSummary.projectPath = projectPath;
|
|
817
838
|
validatedSummary.keyFilesCount = keyFiles.length;
|
|
818
839
|
return validatedSummary;
|
|
819
|
-
}
|
|
840
|
+
}
|
|
841
|
+
else {
|
|
820
842
|
console.error(chalk.red('Failed to parse LLM response as JSON'));
|
|
821
843
|
console.error(chalk.gray('Response content preview:'), response.content.substring(0, 500));
|
|
822
844
|
throw new Error('Failed to parse LLM response as JSON');
|
|
823
845
|
}
|
|
824
|
-
}
|
|
846
|
+
}
|
|
847
|
+
catch (error) {
|
|
825
848
|
console.error(chalk.red('Error generating project summary:'), error.message);
|
|
826
849
|
const fallback = this.createFallbackSummary(projectPath, keyFiles);
|
|
827
850
|
verboseLog({}, chalk.yellow('Using fallback summary with technologies:'), fallback.technologies);
|
|
@@ -839,7 +862,9 @@ Follow the analysis guidelines from the system instructions to identify custom i
|
|
|
839
862
|
|
|
840
863
|
for (const file of keyFiles.slice(0, 25)) {
|
|
841
864
|
// Max 25 files
|
|
842
|
-
if (totalSize >= maxTotalSize)
|
|
865
|
+
if (totalSize >= maxTotalSize) {
|
|
866
|
+
break;
|
|
867
|
+
}
|
|
843
868
|
|
|
844
869
|
try {
|
|
845
870
|
const fileContent = fs.readFileSync(file.fullPath, 'utf8');
|
|
@@ -848,7 +873,8 @@ Follow the analysis guidelines from the system instructions to identify custom i
|
|
|
848
873
|
|
|
849
874
|
content += `\n\n=== ${file.relativePath} (${file.category}) ===\n${contentToAdd}`;
|
|
850
875
|
totalSize += contentToAdd.length;
|
|
851
|
-
}
|
|
876
|
+
}
|
|
877
|
+
catch (error) {
|
|
852
878
|
content += `\n\n=== ${file.relativePath} (${file.category}) ===\n[Could not read file: ${error.message}]`;
|
|
853
879
|
}
|
|
854
880
|
}
|
|
@@ -912,7 +938,8 @@ Follow the analysis guidelines from the system instructions to identify custom i
|
|
|
912
938
|
projectName = packageJson.name || projectName;
|
|
913
939
|
const deps = { ...packageJson.dependencies, ...packageJson.devDependencies };
|
|
914
940
|
technologies = Object.keys(deps).slice(0, 10);
|
|
915
|
-
}
|
|
941
|
+
}
|
|
942
|
+
catch {
|
|
916
943
|
// Continue with defaults
|
|
917
944
|
}
|
|
918
945
|
}
|