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/project-analyzer.js
CHANGED
|
@@ -209,13 +209,20 @@ export class ProjectAnalyzer {
|
|
|
209
209
|
// Check for existing analysis
|
|
210
210
|
const existingSummary = forceAnalysis ? null : await this.loadExistingAnalysis(projectPath);
|
|
211
211
|
if (existingSummary && !forceAnalysis) {
|
|
212
|
-
const
|
|
213
|
-
if (existingSummary.
|
|
214
|
-
verboseLog(verbose, chalk.
|
|
215
|
-
return existingSummary;
|
|
212
|
+
const currentEmbeddingInventoryHash = await this.calculateEmbeddingInventoryHash(projectPath);
|
|
213
|
+
if (existingSummary.embeddingInventoryHash !== currentEmbeddingInventoryHash) {
|
|
214
|
+
verboseLog(verbose, chalk.yellow('🔄 Embedding inventory changed, regenerating analysis...'));
|
|
216
215
|
}
|
|
217
|
-
|
|
218
|
-
|
|
216
|
+
else {
|
|
217
|
+
const currentHash = await this.calculateKeyFilesHash(existingSummary.keyFiles);
|
|
218
|
+
if (existingSummary.keyFilesHash === currentHash) {
|
|
219
|
+
verboseLog(verbose, chalk.green('✅ Project analysis up-to-date (no key file changes detected)'));
|
|
220
|
+
return existingSummary;
|
|
221
|
+
}
|
|
222
|
+
verboseLog(verbose, chalk.yellow('🔄 Key files changed, regenerating analysis...'));
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
else {
|
|
219
226
|
verboseLog(
|
|
220
227
|
verbose,
|
|
221
228
|
chalk.cyan(
|
|
@@ -241,6 +248,7 @@ export class ProjectAnalyzer {
|
|
|
241
248
|
const currentHash = await this.calculateKeyFilesHash(keyFiles);
|
|
242
249
|
projectSummary.keyFiles = keyFiles;
|
|
243
250
|
projectSummary.keyFilesHash = currentHash;
|
|
251
|
+
projectSummary.embeddingInventoryHash = await this.calculateEmbeddingInventoryHash(projectPath);
|
|
244
252
|
|
|
245
253
|
await this.storeAnalysis(projectPath, projectSummary);
|
|
246
254
|
|
|
@@ -254,7 +262,8 @@ export class ProjectAnalyzer {
|
|
|
254
262
|
verboseLog(verbose, chalk.gray(` Key files tracked: ${keyFiles.length}`));
|
|
255
263
|
|
|
256
264
|
return projectSummary;
|
|
257
|
-
}
|
|
265
|
+
}
|
|
266
|
+
catch (error) {
|
|
258
267
|
console.error(chalk.red('Error analyzing project:'), error.message);
|
|
259
268
|
return this.createFallbackSummary(projectPath);
|
|
260
269
|
}
|
|
@@ -279,7 +288,8 @@ export class ProjectAnalyzer {
|
|
|
279
288
|
return { ...summary, keyFiles };
|
|
280
289
|
}
|
|
281
290
|
return null;
|
|
282
|
-
}
|
|
291
|
+
}
|
|
292
|
+
catch (error) {
|
|
283
293
|
console.error(chalk.yellow('Warning: Could not load existing analysis:'), error.message);
|
|
284
294
|
return null;
|
|
285
295
|
}
|
|
@@ -293,7 +303,8 @@ export class ProjectAnalyzer {
|
|
|
293
303
|
const embeddingsSystem = getDefaultEmbeddingsSystem();
|
|
294
304
|
await embeddingsSystem.storeProjectSummary(projectPath, projectSummary);
|
|
295
305
|
verboseLog({}, chalk.green('✅ Project analysis stored in database'));
|
|
296
|
-
}
|
|
306
|
+
}
|
|
307
|
+
catch (error) {
|
|
297
308
|
console.error(chalk.yellow('Warning: Could not store analysis:'), error.message);
|
|
298
309
|
}
|
|
299
310
|
}
|
|
@@ -352,10 +363,12 @@ export class ProjectAnalyzer {
|
|
|
352
363
|
// Optimize table to sync indices with data and prevent TakeExec panics
|
|
353
364
|
try {
|
|
354
365
|
await table.optimize();
|
|
355
|
-
}
|
|
366
|
+
}
|
|
367
|
+
catch (optimizeError) {
|
|
356
368
|
if (optimizeError.message && optimizeError.message.includes('legacy format')) {
|
|
357
369
|
console.warn(chalk.yellow(`Skipping optimization due to legacy index format - will be auto-upgraded during normal operations`));
|
|
358
|
-
}
|
|
370
|
+
}
|
|
371
|
+
else {
|
|
359
372
|
console.warn(chalk.yellow(`Warning: Failed to optimize file embeddings table: ${optimizeError.message}`));
|
|
360
373
|
}
|
|
361
374
|
}
|
|
@@ -372,7 +385,8 @@ export class ProjectAnalyzer {
|
|
|
372
385
|
|
|
373
386
|
if (config.whereClause) {
|
|
374
387
|
query = query.where(`project_path = '${projectPath}' AND (${config.whereClause})`);
|
|
375
|
-
}
|
|
388
|
+
}
|
|
389
|
+
else if (config.terms) {
|
|
376
390
|
// For term-based searches, query ALL files and sort by depth to prioritize shallow config files
|
|
377
391
|
const allFiles = await table
|
|
378
392
|
.query()
|
|
@@ -400,12 +414,14 @@ export class ProjectAnalyzer {
|
|
|
400
414
|
|
|
401
415
|
return matches;
|
|
402
416
|
});
|
|
403
|
-
}
|
|
417
|
+
}
|
|
418
|
+
else {
|
|
404
419
|
query = query.where(`project_path = '${projectPath}'`);
|
|
405
420
|
}
|
|
406
421
|
|
|
407
422
|
return await query.limit(config.limit || 30).toArray();
|
|
408
|
-
}
|
|
423
|
+
}
|
|
424
|
+
catch (error) {
|
|
409
425
|
verboseLog({}, chalk.yellow(` ⚠️ Query failed for ${config.category}: ${error.message}`));
|
|
410
426
|
return [];
|
|
411
427
|
}
|
|
@@ -424,7 +440,8 @@ export class ProjectAnalyzer {
|
|
|
424
440
|
}
|
|
425
441
|
});
|
|
426
442
|
}
|
|
427
|
-
}
|
|
443
|
+
}
|
|
444
|
+
catch (error) {
|
|
428
445
|
console.error(chalk.red('Error mining embeddings:'), error.message);
|
|
429
446
|
return [];
|
|
430
447
|
}
|
|
@@ -438,11 +455,17 @@ export class ProjectAnalyzer {
|
|
|
438
455
|
* Unified file type matching using consolidated patterns
|
|
439
456
|
*/
|
|
440
457
|
matchesFileType(filePath, fileName, type) {
|
|
441
|
-
if (type === 'docs')
|
|
442
|
-
|
|
458
|
+
if (type === 'docs') {
|
|
459
|
+
return isDocumentationFile(filePath);
|
|
460
|
+
}
|
|
461
|
+
if (type === 'tests') {
|
|
462
|
+
return isTestFile(filePath);
|
|
463
|
+
}
|
|
443
464
|
|
|
444
465
|
const config = FILE_PATTERNS[type];
|
|
445
|
-
if (!config)
|
|
466
|
+
if (!config) {
|
|
467
|
+
return false;
|
|
468
|
+
}
|
|
446
469
|
|
|
447
470
|
const fileNameLower = fileName.toLowerCase();
|
|
448
471
|
const filePathLower = filePath.toLowerCase();
|
|
@@ -540,10 +563,12 @@ Select files following the criteria in the system instructions.`;
|
|
|
540
563
|
|
|
541
564
|
verboseLog({}, chalk.cyan(`🎯 LLM selected ${keyFiles.length} final key files`));
|
|
542
565
|
return keyFiles;
|
|
543
|
-
}
|
|
566
|
+
}
|
|
567
|
+
else {
|
|
544
568
|
throw new Error(`Failed to extract valid JSON array from LLM response`);
|
|
545
569
|
}
|
|
546
|
-
}
|
|
570
|
+
}
|
|
571
|
+
catch (error) {
|
|
547
572
|
console.error(chalk.red('Error in LLM selection:'), error.message);
|
|
548
573
|
verboseLog({}, chalk.yellow(' 🔄 Falling back to automatic selection...'));
|
|
549
574
|
return this.fallbackFileSelection(candidates, projectPath);
|
|
@@ -605,7 +630,8 @@ Select files following the criteria in the system instructions.`;
|
|
|
605
630
|
const content = fs.readFileSync(fullPath, 'utf8');
|
|
606
631
|
hash.update(content.substring(0, 1000));
|
|
607
632
|
}
|
|
608
|
-
}
|
|
633
|
+
}
|
|
634
|
+
catch {
|
|
609
635
|
hash.update(file.relativePath || file.path || '');
|
|
610
636
|
}
|
|
611
637
|
}
|
|
@@ -613,6 +639,40 @@ Select files following the criteria in the system instructions.`;
|
|
|
613
639
|
return hash.digest('hex');
|
|
614
640
|
}
|
|
615
641
|
|
|
642
|
+
/**
|
|
643
|
+
* Calculate a project-scoped hash of the current embedding inventory.
|
|
644
|
+
*/
|
|
645
|
+
async calculateEmbeddingInventoryHash(projectPath) {
|
|
646
|
+
try {
|
|
647
|
+
const embeddingsSystem = getDefaultEmbeddingsSystem();
|
|
648
|
+
await embeddingsSystem.initialize();
|
|
649
|
+
const table = await embeddingsSystem.databaseManager.getTable(embeddingsSystem.databaseManager.fileEmbeddingsTable);
|
|
650
|
+
|
|
651
|
+
if (!table) {
|
|
652
|
+
return 'no-file-embeddings-table';
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
const records = await table
|
|
656
|
+
.query()
|
|
657
|
+
.select(['type', 'path', 'content_hash', 'project_path'])
|
|
658
|
+
.where(`project_path = '${projectPath.replace(/'/g, "''")}'`)
|
|
659
|
+
.toArray();
|
|
660
|
+
|
|
661
|
+
const hash = crypto.createHash('sha256');
|
|
662
|
+
const normalizedRows = records.map((record) => `${record.type || 'file'}:${record.path || ''}:${record.content_hash || ''}`).sort();
|
|
663
|
+
|
|
664
|
+
for (const row of normalizedRows) {
|
|
665
|
+
hash.update(row);
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
return hash.digest('hex');
|
|
669
|
+
}
|
|
670
|
+
catch (error) {
|
|
671
|
+
verboseLog({}, chalk.yellow(`Warning: Could not calculate embedding inventory hash: ${error.message}`));
|
|
672
|
+
return 'embedding-inventory-unavailable';
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
|
|
616
676
|
/**
|
|
617
677
|
* Generate comprehensive project summary using LLM analysis (SINGLE CALL)
|
|
618
678
|
*/
|
|
@@ -777,12 +837,14 @@ Follow the analysis guidelines from the system instructions to identify custom i
|
|
|
777
837
|
validatedSummary.projectPath = projectPath;
|
|
778
838
|
validatedSummary.keyFilesCount = keyFiles.length;
|
|
779
839
|
return validatedSummary;
|
|
780
|
-
}
|
|
840
|
+
}
|
|
841
|
+
else {
|
|
781
842
|
console.error(chalk.red('Failed to parse LLM response as JSON'));
|
|
782
843
|
console.error(chalk.gray('Response content preview:'), response.content.substring(0, 500));
|
|
783
844
|
throw new Error('Failed to parse LLM response as JSON');
|
|
784
845
|
}
|
|
785
|
-
}
|
|
846
|
+
}
|
|
847
|
+
catch (error) {
|
|
786
848
|
console.error(chalk.red('Error generating project summary:'), error.message);
|
|
787
849
|
const fallback = this.createFallbackSummary(projectPath, keyFiles);
|
|
788
850
|
verboseLog({}, chalk.yellow('Using fallback summary with technologies:'), fallback.technologies);
|
|
@@ -800,7 +862,9 @@ Follow the analysis guidelines from the system instructions to identify custom i
|
|
|
800
862
|
|
|
801
863
|
for (const file of keyFiles.slice(0, 25)) {
|
|
802
864
|
// Max 25 files
|
|
803
|
-
if (totalSize >= maxTotalSize)
|
|
865
|
+
if (totalSize >= maxTotalSize) {
|
|
866
|
+
break;
|
|
867
|
+
}
|
|
804
868
|
|
|
805
869
|
try {
|
|
806
870
|
const fileContent = fs.readFileSync(file.fullPath, 'utf8');
|
|
@@ -809,7 +873,8 @@ Follow the analysis guidelines from the system instructions to identify custom i
|
|
|
809
873
|
|
|
810
874
|
content += `\n\n=== ${file.relativePath} (${file.category}) ===\n${contentToAdd}`;
|
|
811
875
|
totalSize += contentToAdd.length;
|
|
812
|
-
}
|
|
876
|
+
}
|
|
877
|
+
catch (error) {
|
|
813
878
|
content += `\n\n=== ${file.relativePath} (${file.category}) ===\n[Could not read file: ${error.message}]`;
|
|
814
879
|
}
|
|
815
880
|
}
|
|
@@ -873,7 +938,8 @@ Follow the analysis guidelines from the system instructions to identify custom i
|
|
|
873
938
|
projectName = packageJson.name || projectName;
|
|
874
939
|
const deps = { ...packageJson.dependencies, ...packageJson.devDependencies };
|
|
875
940
|
technologies = Object.keys(deps).slice(0, 10);
|
|
876
|
-
}
|
|
941
|
+
}
|
|
942
|
+
catch {
|
|
877
943
|
// Continue with defaults
|
|
878
944
|
}
|
|
879
945
|
}
|
package/src/rag-analyzer.js
CHANGED
|
@@ -55,7 +55,8 @@ async function ensureSemanticSimilarityInitialized() {
|
|
|
55
55
|
// Initialize semantic similarity using the shared embeddings system
|
|
56
56
|
await initializeSemanticSimilarity();
|
|
57
57
|
semanticSimilarityInitialized = true;
|
|
58
|
-
}
|
|
58
|
+
}
|
|
59
|
+
catch (error) {
|
|
59
60
|
console.warn(chalk.yellow(`⚠️ Could not initialize semantic similarity: ${error.message}`));
|
|
60
61
|
// Continue without semantic similarity - word-based fallback will be used
|
|
61
62
|
}
|
|
@@ -228,7 +229,8 @@ async function getProjectSummary(projectPath, options = {}) {
|
|
|
228
229
|
}
|
|
229
230
|
|
|
230
231
|
return summary;
|
|
231
|
-
}
|
|
232
|
+
}
|
|
233
|
+
catch (error) {
|
|
232
234
|
console.error(chalk.red(`Error retrieving project summary: ${error.message}`));
|
|
233
235
|
return null;
|
|
234
236
|
}
|
|
@@ -240,7 +242,9 @@ async function getProjectSummary(projectPath, options = {}) {
|
|
|
240
242
|
* @returns {string} Formatted context string
|
|
241
243
|
*/
|
|
242
244
|
function formatProjectSummaryForLLM(summary) {
|
|
243
|
-
if (!summary)
|
|
245
|
+
if (!summary) {
|
|
246
|
+
return '';
|
|
247
|
+
}
|
|
244
248
|
|
|
245
249
|
let context = `\n## PROJECT ARCHITECTURE CONTEXT\n\n`;
|
|
246
250
|
|
|
@@ -456,7 +460,8 @@ async function runAnalysis(filePath, options = {}) {
|
|
|
456
460
|
// For PR reviews, always read the full file content for context awareness
|
|
457
461
|
fullFileContent = fs.existsSync(filePath) ? fs.readFileSync(filePath, 'utf8') : null;
|
|
458
462
|
verboseLog(options, chalk.blue(`Analyzing diff only for ${path.basename(filePath)}`));
|
|
459
|
-
}
|
|
463
|
+
}
|
|
464
|
+
else {
|
|
460
465
|
content = fs.readFileSync(filePath, 'utf8');
|
|
461
466
|
fullFileContent = content;
|
|
462
467
|
verboseLog(options, chalk.blue(`Analyzing full file ${path.basename(filePath)}`));
|
|
@@ -503,7 +508,8 @@ async function runAnalysis(filePath, options = {}) {
|
|
|
503
508
|
verboseLog(options, chalk.magenta(` [${i + 1}] Path: ${g.path} ${g.headingText ? `(Heading: "${g.headingText}")` : ''}`));
|
|
504
509
|
verboseLog(options, chalk.gray(` Content: ${g.content.substring(0, 100).replace(/\\n/g, ' ')}...`));
|
|
505
510
|
});
|
|
506
|
-
}
|
|
511
|
+
}
|
|
512
|
+
else {
|
|
507
513
|
verboseLog(options, chalk.magenta(' (None)'));
|
|
508
514
|
}
|
|
509
515
|
|
|
@@ -513,7 +519,8 @@ async function runAnalysis(filePath, options = {}) {
|
|
|
513
519
|
verboseLog(options, chalk.magenta(` [${i + 1}] Path: ${ex.path} (Similarity: ${ex.similarity?.toFixed(3) || 'N/A'})`));
|
|
514
520
|
verboseLog(options, chalk.gray(` Content: ${ex.content.substring(0, 100).replace(/\\n/g, ' ')}...`));
|
|
515
521
|
});
|
|
516
|
-
}
|
|
522
|
+
}
|
|
523
|
+
else {
|
|
517
524
|
verboseLog(options, chalk.magenta(' (None)'));
|
|
518
525
|
}
|
|
519
526
|
|
|
@@ -524,7 +531,8 @@ async function runAnalysis(filePath, options = {}) {
|
|
|
524
531
|
verboseLog(options, chalk.magenta(` Similarity: ${chunk.similarity?.toFixed(3) || 'N/A'}`));
|
|
525
532
|
verboseLog(options, chalk.gray(` Content: ${chunk.content.substring(0, 100).replace(/\\n/g, ' ')}...`));
|
|
526
533
|
});
|
|
527
|
-
}
|
|
534
|
+
}
|
|
535
|
+
else {
|
|
528
536
|
verboseLog(options, chalk.magenta(' (None)'));
|
|
529
537
|
}
|
|
530
538
|
verboseLog(options, chalk.magenta('---------------------------------'));
|
|
@@ -593,7 +601,8 @@ async function runAnalysis(filePath, options = {}) {
|
|
|
593
601
|
...(filteredResults.metadata || {}),
|
|
594
602
|
},
|
|
595
603
|
};
|
|
596
|
-
}
|
|
604
|
+
}
|
|
605
|
+
catch (error) {
|
|
597
606
|
console.error(chalk.red(`Error analyzing file: ${error.message}`));
|
|
598
607
|
return {
|
|
599
608
|
success: false,
|
|
@@ -745,7 +754,8 @@ async function callLLMForAnalysis(context, options = {}) {
|
|
|
745
754
|
|
|
746
755
|
if (options.isHolisticPRReview) {
|
|
747
756
|
prompt = generateHolisticPRAnalysisPrompt(context);
|
|
748
|
-
}
|
|
757
|
+
}
|
|
758
|
+
else {
|
|
749
759
|
prompt = options.isTestFile ? generateTestFileAnalysisPrompt(context) : generateAnalysisPrompt(context);
|
|
750
760
|
}
|
|
751
761
|
|
|
@@ -781,7 +791,8 @@ async function callLLMForAnalysis(context, options = {}) {
|
|
|
781
791
|
|
|
782
792
|
verboseLog(options, chalk.green('Successfully parsed LLM response with expected structure'));
|
|
783
793
|
return analysisResponse;
|
|
784
|
-
}
|
|
794
|
+
}
|
|
795
|
+
catch (error) {
|
|
785
796
|
console.error(chalk.red(`Error calling LLM for analysis: ${error.message}`));
|
|
786
797
|
console.error(error.stack);
|
|
787
798
|
throw error;
|
|
@@ -945,7 +956,8 @@ async function sendPromptToLLM(promptConfig, llmOptions) {
|
|
|
945
956
|
});
|
|
946
957
|
|
|
947
958
|
return response;
|
|
948
|
-
}
|
|
959
|
+
}
|
|
960
|
+
catch (error) {
|
|
949
961
|
console.error(chalk.red(`Error in LLM call: ${error.message}`));
|
|
950
962
|
throw error;
|
|
951
963
|
}
|
|
@@ -1013,10 +1025,12 @@ Similar code patterns and issues identified by human reviewers in past PRs
|
|
|
1013
1025
|
prHistorySection += `Only report issues that EXACTLY match historical patterns with SPECIFIC code fixes.\n\n`;
|
|
1014
1026
|
|
|
1015
1027
|
verboseLog(context.options, chalk.blue(`PR History section preview: ${prHistorySection.substring(0, 200)}...`));
|
|
1016
|
-
}
|
|
1028
|
+
}
|
|
1029
|
+
else {
|
|
1017
1030
|
verboseLog(context.options, chalk.yellow(`❌ No PR comments section found in context`));
|
|
1018
1031
|
}
|
|
1019
|
-
}
|
|
1032
|
+
}
|
|
1033
|
+
else {
|
|
1020
1034
|
verboseLog(context.options, chalk.yellow(`❌ No context sections available for PR comments`));
|
|
1021
1035
|
}
|
|
1022
1036
|
|
|
@@ -1476,7 +1490,8 @@ async function getPRCommentContext(filePath, options = {}) {
|
|
|
1476
1490
|
fileContent = fs.readFileSync(filePath, 'utf8');
|
|
1477
1491
|
const maxEmbeddingLength = 8000; // Keep consistent with original truncation
|
|
1478
1492
|
contentForSearch = fileContent.length > maxEmbeddingLength ? fileContent.substring(0, maxEmbeddingLength) : fileContent;
|
|
1479
|
-
}
|
|
1493
|
+
}
|
|
1494
|
+
catch (readError) {
|
|
1480
1495
|
debug(`[getPRCommentContext] Could not read file ${filePath}: ${readError.message}`);
|
|
1481
1496
|
return {
|
|
1482
1497
|
success: false,
|
|
@@ -1486,11 +1501,13 @@ async function getPRCommentContext(filePath, options = {}) {
|
|
|
1486
1501
|
summary: 'Failed to read file for context analysis',
|
|
1487
1502
|
};
|
|
1488
1503
|
}
|
|
1489
|
-
}
|
|
1504
|
+
}
|
|
1505
|
+
else {
|
|
1490
1506
|
// Fallback to original behavior if no pre-computed embedding provided
|
|
1491
1507
|
try {
|
|
1492
1508
|
fileContent = fs.readFileSync(filePath, 'utf8');
|
|
1493
|
-
}
|
|
1509
|
+
}
|
|
1510
|
+
catch (readError) {
|
|
1494
1511
|
debug(`[getPRCommentContext] Could not read file ${filePath}: ${readError.message}`);
|
|
1495
1512
|
return {
|
|
1496
1513
|
success: false,
|
|
@@ -1537,7 +1554,8 @@ async function getPRCommentContext(filePath, options = {}) {
|
|
|
1537
1554
|
);
|
|
1538
1555
|
});
|
|
1539
1556
|
}
|
|
1540
|
-
}
|
|
1557
|
+
}
|
|
1558
|
+
catch (dbError) {
|
|
1541
1559
|
console.warn(chalk.yellow(`⚠️ Hybrid search failed: ${dbError.message}`));
|
|
1542
1560
|
debug(`[getPRCommentContext] Hybrid search failed: ${dbError.message}`);
|
|
1543
1561
|
// No fallback needed - if hybrid search fails, we just return empty results
|
|
@@ -1568,7 +1586,8 @@ async function getPRCommentContext(filePath, options = {}) {
|
|
|
1568
1586
|
relevantComments.length > 0 && relevantComments[0].similarity_score !== 0.5 ? 'semantic_embedding' : 'file_path_fallback',
|
|
1569
1587
|
},
|
|
1570
1588
|
};
|
|
1571
|
-
}
|
|
1589
|
+
}
|
|
1590
|
+
catch (error) {
|
|
1572
1591
|
debug(`[getPRCommentContext] Error getting PR comment context: ${error.message}`);
|
|
1573
1592
|
return {
|
|
1574
1593
|
success: false,
|
|
@@ -1737,7 +1756,8 @@ async function performHolisticPRAnalysis(options) {
|
|
|
1737
1756
|
);
|
|
1738
1757
|
verboseLog(options, chalk.gray(` Content: ${g.content.substring(0, 100).replace(/\n/g, ' ')}...`));
|
|
1739
1758
|
});
|
|
1740
|
-
}
|
|
1759
|
+
}
|
|
1760
|
+
else {
|
|
1741
1761
|
verboseLog(options, chalk.magenta(' (None)'));
|
|
1742
1762
|
}
|
|
1743
1763
|
|
|
@@ -1747,7 +1767,8 @@ async function performHolisticPRAnalysis(options) {
|
|
|
1747
1767
|
verboseLog(options, chalk.magenta(` [${i + 1}] Path: ${ex.path} (Similarity: ${ex.similarity?.toFixed(3) || 'N/A'})`));
|
|
1748
1768
|
verboseLog(options, chalk.gray(` Content: ${ex.content.substring(0, 100).replace(/\\n/g, ' ')}...`));
|
|
1749
1769
|
});
|
|
1750
|
-
}
|
|
1770
|
+
}
|
|
1771
|
+
else {
|
|
1751
1772
|
verboseLog(options, chalk.magenta(' (None)'));
|
|
1752
1773
|
}
|
|
1753
1774
|
|
|
@@ -1763,7 +1784,8 @@ async function performHolisticPRAnalysis(options) {
|
|
|
1763
1784
|
verboseLog(options, chalk.gray(` File: ${comment.filePath}`));
|
|
1764
1785
|
verboseLog(options, chalk.gray(` Comment: ${comment.body.substring(0, 100).replace(/\n/g, ' ')}...`));
|
|
1765
1786
|
});
|
|
1766
|
-
}
|
|
1787
|
+
}
|
|
1788
|
+
else {
|
|
1767
1789
|
verboseLog(options, chalk.magenta(' (None)'));
|
|
1768
1790
|
}
|
|
1769
1791
|
|
|
@@ -1774,7 +1796,8 @@ async function performHolisticPRAnalysis(options) {
|
|
|
1774
1796
|
verboseLog(options, chalk.gray(` Similarity: ${chunk.similarity?.toFixed(3) || 'N/A'}`));
|
|
1775
1797
|
verboseLog(options, chalk.gray(` Content: ${chunk.content.substring(0, 100).replace(/\n/g, ' ')}...`));
|
|
1776
1798
|
});
|
|
1777
|
-
}
|
|
1799
|
+
}
|
|
1800
|
+
else {
|
|
1778
1801
|
verboseLog(options, chalk.magenta(' (None)'));
|
|
1779
1802
|
}
|
|
1780
1803
|
verboseLog(options, chalk.magenta('--- Sending Holistic PR Analysis Prompt to LLM ---'));
|
|
@@ -1821,7 +1844,8 @@ async function performHolisticPRAnalysis(options) {
|
|
|
1821
1844
|
},
|
|
1822
1845
|
},
|
|
1823
1846
|
};
|
|
1824
|
-
}
|
|
1847
|
+
}
|
|
1848
|
+
catch (error) {
|
|
1825
1849
|
console.error(chalk.red(`Error in holistic PR analysis: ${error.message}`));
|
|
1826
1850
|
return {
|
|
1827
1851
|
success: false,
|
|
@@ -1852,7 +1876,8 @@ async function getContextForFile(filePath, content, options = {}) {
|
|
|
1852
1876
|
// Note: This may be called concurrently. `initializeTables` should be idempotent.
|
|
1853
1877
|
try {
|
|
1854
1878
|
await embeddingsSystem.initialize();
|
|
1855
|
-
}
|
|
1879
|
+
}
|
|
1880
|
+
catch (initError) {
|
|
1856
1881
|
console.warn(chalk.yellow(`Database initialization warning: ${initError.message}`));
|
|
1857
1882
|
}
|
|
1858
1883
|
|
|
@@ -1932,7 +1957,8 @@ async function getContextForFile(filePath, content, options = {}) {
|
|
|
1932
1957
|
verboseLog(options, chalk.cyan('📄 Custom documents not yet processed for this project, processing now...'));
|
|
1933
1958
|
// Process custom documents into chunks (only if not already processed)
|
|
1934
1959
|
processedChunks = await embeddingsSystem.processCustomDocumentsInMemory(options.customDocs, projectPath);
|
|
1935
|
-
}
|
|
1960
|
+
}
|
|
1961
|
+
else {
|
|
1936
1962
|
verboseLog(options, chalk.green(`📄 Reusing ${processedChunks.length} already processed custom document chunks`));
|
|
1937
1963
|
}
|
|
1938
1964
|
|
|
@@ -1961,7 +1987,8 @@ async function getContextForFile(filePath, content, options = {}) {
|
|
|
1961
1987
|
|
|
1962
1988
|
return relevantChunks;
|
|
1963
1989
|
}
|
|
1964
|
-
}
|
|
1990
|
+
}
|
|
1991
|
+
catch (error) {
|
|
1965
1992
|
console.error(chalk.red(`Error processing custom documents: ${error.message}`));
|
|
1966
1993
|
}
|
|
1967
1994
|
|
|
@@ -1973,7 +2000,8 @@ async function getContextForFile(filePath, content, options = {}) {
|
|
|
1973
2000
|
try {
|
|
1974
2001
|
// Use the statically imported function
|
|
1975
2002
|
return await embeddingsSystem.getExistingCustomDocumentChunks(projectPath);
|
|
1976
|
-
}
|
|
2003
|
+
}
|
|
2004
|
+
catch {
|
|
1977
2005
|
verboseLog(options, chalk.gray('No existing custom document chunks found, will process from scratch'));
|
|
1978
2006
|
return [];
|
|
1979
2007
|
}
|
|
@@ -1982,7 +2010,8 @@ async function getContextForFile(filePath, content, options = {}) {
|
|
|
1982
2010
|
const withContextFallback = async (promise, fallbackValue, label) => {
|
|
1983
2011
|
try {
|
|
1984
2012
|
return await promise;
|
|
1985
|
-
}
|
|
2013
|
+
}
|
|
2014
|
+
catch (error) {
|
|
1986
2015
|
console.warn(chalk.yellow(`${label} failed: ${error.message}`));
|
|
1987
2016
|
return fallbackValue;
|
|
1988
2017
|
}
|
|
@@ -2055,11 +2084,14 @@ async function getContextForFile(filePath, content, options = {}) {
|
|
|
2055
2084
|
if (isGenericDocument(docPath, docH1)) {
|
|
2056
2085
|
candidateDocFullContext = getGenericDocumentContext(docPath, docH1);
|
|
2057
2086
|
debug(`[FAST-PATH] Using pre-computed context for generic document in RAG: ${docPath}`);
|
|
2058
|
-
}
|
|
2087
|
+
}
|
|
2088
|
+
else {
|
|
2059
2089
|
candidateDocFullContext = await inferContextFromDocumentContent(docPath, docH1, docChunks, language);
|
|
2060
2090
|
}
|
|
2061
2091
|
const relevantChunksForDoc = docChunks.filter((c) => c.similarity >= RELEVANT_CHUNK_THRESHOLD);
|
|
2062
|
-
if (relevantChunksForDoc.length === 0)
|
|
2092
|
+
if (relevantChunksForDoc.length === 0) {
|
|
2093
|
+
continue;
|
|
2094
|
+
}
|
|
2063
2095
|
|
|
2064
2096
|
const maxChunkScoreInDoc = Math.max(...relevantChunksForDoc.map((c) => c.similarity));
|
|
2065
2097
|
const avgChunkScoreInDoc = relevantChunksForDoc.reduce((sum, c) => sum + c.similarity, 0) / relevantChunksForDoc.length;
|
|
@@ -2080,7 +2112,8 @@ async function getContextForFile(filePath, content, options = {}) {
|
|
|
2080
2112
|
break;
|
|
2081
2113
|
}
|
|
2082
2114
|
}
|
|
2083
|
-
}
|
|
2115
|
+
}
|
|
2116
|
+
else if (reviewedSnippetContext.area !== 'GeneralJS_TS') {
|
|
2084
2117
|
docLevelContextMatchScore -= 0.2;
|
|
2085
2118
|
}
|
|
2086
2119
|
}
|
|
@@ -2205,13 +2238,15 @@ async function gatherUnifiedContextForPR(prFiles, options = {}) {
|
|
|
2205
2238
|
if (!processedChunks || processedChunks.length === 0) {
|
|
2206
2239
|
verboseLog(options, chalk.cyan('📄 Custom documents not yet processed for this project, processing now...'));
|
|
2207
2240
|
processedChunks = await embeddingsSystem.processCustomDocumentsInMemory(options.customDocs, projectPath);
|
|
2208
|
-
}
|
|
2241
|
+
}
|
|
2242
|
+
else {
|
|
2209
2243
|
verboseLog(options, chalk.green(`📄 Reusing ${processedChunks.length} already processed custom document chunks`));
|
|
2210
2244
|
}
|
|
2211
2245
|
|
|
2212
2246
|
globalCustomDocChunks = processedChunks;
|
|
2213
2247
|
verboseLog(options, chalk.green(`📄 Custom documents processed: ${globalCustomDocChunks.length} chunks available for PR analysis`));
|
|
2214
|
-
}
|
|
2248
|
+
}
|
|
2249
|
+
catch (error) {
|
|
2215
2250
|
console.error(chalk.red(`Error processing custom documents for PR: ${error.message}`));
|
|
2216
2251
|
}
|
|
2217
2252
|
}
|
|
@@ -2231,7 +2266,8 @@ async function gatherUnifiedContextForPR(prFiles, options = {}) {
|
|
|
2231
2266
|
...context,
|
|
2232
2267
|
filePath,
|
|
2233
2268
|
};
|
|
2234
|
-
}
|
|
2269
|
+
}
|
|
2270
|
+
catch (error) {
|
|
2235
2271
|
console.error(chalk.red(`Error gathering context for file ${file.filePath}: ${error.message}`));
|
|
2236
2272
|
return null; // Return null on error for this file
|
|
2237
2273
|
}
|
package/src/rag-analyzer.test.js
CHANGED
|
@@ -910,6 +910,15 @@ describe('rag-analyzer', () => {
|
|
|
910
910
|
expect(context).toHaveProperty('codeExamples');
|
|
911
911
|
});
|
|
912
912
|
|
|
913
|
+
it('should degrade gracefully when retrieval returns no context for active files', async () => {
|
|
914
|
+
mockEmbeddingsSystem.findSimilarCode.mockResolvedValue([]);
|
|
915
|
+
mockEmbeddingsSystem.findRelevantDocs.mockResolvedValue([]);
|
|
916
|
+
const prFiles = [{ filePath: '/src/file.js', content: 'code', language: 'javascript' }];
|
|
917
|
+
const context = await gatherUnifiedContextForPR(prFiles, { projectPath: '/project' });
|
|
918
|
+
expect(context.codeExamples).toEqual([]);
|
|
919
|
+
expect(context.guidelines).toEqual([]);
|
|
920
|
+
});
|
|
921
|
+
|
|
913
922
|
it('should find custom document chunks', async () => {
|
|
914
923
|
mockEmbeddingsSystem.getExistingCustomDocumentChunks.mockResolvedValue([{ content: 'Custom doc', document_title: 'Guidelines' }]);
|
|
915
924
|
const prFiles = [{ filePath: '/src/file.js', content: 'code', language: 'javascript' }];
|
|
@@ -933,7 +942,9 @@ describe('rag-analyzer', () => {
|
|
|
933
942
|
describe('gatherUnifiedContextForPR error handling', () => {
|
|
934
943
|
it('should handle file context gathering errors', async () => {
|
|
935
944
|
fs.readFileSync.mockImplementation((path) => {
|
|
936
|
-
if (path.includes('error-file'))
|
|
945
|
+
if (path.includes('error-file')) {
|
|
946
|
+
throw new Error('Read error');
|
|
947
|
+
}
|
|
937
948
|
return 'const x = 1;';
|
|
938
949
|
});
|
|
939
950
|
const prFiles = [
|
package/src/rag-review.js
CHANGED
|
@@ -73,7 +73,8 @@ async function reviewFile(filePath, options = {}) {
|
|
|
73
73
|
|
|
74
74
|
// If analysis failed, return the error
|
|
75
75
|
return analyzeResult;
|
|
76
|
-
}
|
|
76
|
+
}
|
|
77
|
+
catch (error) {
|
|
77
78
|
console.error(chalk.red(`Error reviewing file ${filePath}:`), error.message);
|
|
78
79
|
return {
|
|
79
80
|
success: false,
|
|
@@ -143,7 +144,8 @@ async function reviewFiles(filePaths, options = {}) {
|
|
|
143
144
|
results: validResults, // Return array of individual file results
|
|
144
145
|
message: finalMessage,
|
|
145
146
|
};
|
|
146
|
-
}
|
|
147
|
+
}
|
|
148
|
+
catch (error) {
|
|
147
149
|
console.error(chalk.red(`Error reviewing multiple files: ${error.message}`));
|
|
148
150
|
console.error(error.stack);
|
|
149
151
|
return {
|
|
@@ -186,7 +188,8 @@ async function reviewPullRequest(changedFilePaths, options = {}) {
|
|
|
186
188
|
|
|
187
189
|
// Use enhanced PR review with cross-file context
|
|
188
190
|
return await reviewPullRequestWithCrossFileContext(filesToReview, options);
|
|
189
|
-
}
|
|
191
|
+
}
|
|
192
|
+
catch (error) {
|
|
190
193
|
console.error(chalk.red(`Error reviewing pull request files: ${error.message}`));
|
|
191
194
|
console.error(error.stack);
|
|
192
195
|
return {
|
|
@@ -263,7 +266,8 @@ async function reviewPullRequestWithCrossFileContext(filesToReview, options = {}
|
|
|
263
266
|
baseBranch,
|
|
264
267
|
targetBranch,
|
|
265
268
|
});
|
|
266
|
-
}
|
|
269
|
+
}
|
|
270
|
+
catch (error) {
|
|
267
271
|
console.warn(chalk.yellow(`Error processing file ${filePath}: ${error.message}`));
|
|
268
272
|
}
|
|
269
273
|
}
|
|
@@ -429,7 +433,8 @@ async function reviewPullRequestWithCrossFileContext(filesToReview, options = {}
|
|
|
429
433
|
},
|
|
430
434
|
},
|
|
431
435
|
};
|
|
432
|
-
}
|
|
436
|
+
}
|
|
437
|
+
catch (error) {
|
|
433
438
|
console.error(chalk.red(`Error in holistic PR review: ${error.message}`));
|
|
434
439
|
|
|
435
440
|
// Fallback to individual file review if holistic review fails
|
|
@@ -469,7 +474,8 @@ async function reviewPullRequestWithCrossFileContext(filesToReview, options = {}
|
|
|
469
474
|
|
|
470
475
|
const result = await runAnalysis(file.filePath, enhancedOptions);
|
|
471
476
|
return result;
|
|
472
|
-
}
|
|
477
|
+
}
|
|
478
|
+
catch (error) {
|
|
473
479
|
console.error(chalk.red(`Error reviewing ${file.filePath}: ${error.message}`));
|
|
474
480
|
return {
|
|
475
481
|
filePath: file.filePath,
|
|
@@ -496,7 +502,8 @@ async function reviewPullRequestWithCrossFileContext(filesToReview, options = {}
|
|
|
496
502
|
},
|
|
497
503
|
};
|
|
498
504
|
}
|
|
499
|
-
}
|
|
505
|
+
}
|
|
506
|
+
catch (error) {
|
|
500
507
|
console.error(chalk.red(`Error in enhanced PR review: ${error.message}`));
|
|
501
508
|
return {
|
|
502
509
|
success: false,
|