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.
@@ -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 currentHash = await this.calculateKeyFilesHash(existingSummary.keyFiles);
213
- if (existingSummary.keyFilesHash === currentHash) {
214
- verboseLog(verbose, chalk.green(' Project analysis up-to-date (no key file changes detected)'));
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
- verboseLog(verbose, chalk.yellow('🔄 Key files changed, regenerating analysis...'));
218
- } else {
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
- } catch (error) {
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
- } catch (error) {
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
- } catch (error) {
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
- } catch (optimizeError) {
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
- } else {
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
- } else if (config.terms) {
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
- } else {
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
- } catch (error) {
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
- } catch (error) {
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') return isDocumentationFile(filePath);
442
- if (type === 'tests') return isTestFile(filePath);
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) return false;
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
- } else {
566
+ }
567
+ else {
544
568
  throw new Error(`Failed to extract valid JSON array from LLM response`);
545
569
  }
546
- } catch (error) {
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
- } catch {
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
- } else {
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
- } catch (error) {
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) break;
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
- } catch (error) {
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
- } catch {
941
+ }
942
+ catch {
877
943
  // Continue with defaults
878
944
  }
879
945
  }
@@ -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
- } catch (error) {
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
- } catch (error) {
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) return '';
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
- } else {
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
- } else {
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
- } else {
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
- } else {
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
- } catch (error) {
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
- } else {
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
- } catch (error) {
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
- } catch (error) {
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
- } else {
1028
+ }
1029
+ else {
1017
1030
  verboseLog(context.options, chalk.yellow(`❌ No PR comments section found in context`));
1018
1031
  }
1019
- } else {
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
- } catch (readError) {
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
- } else {
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
- } catch (readError) {
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
- } catch (dbError) {
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
- } catch (error) {
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
- } else {
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
- } else {
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
- } else {
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
- } else {
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
- } catch (error) {
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
- } catch (initError) {
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
- } else {
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
- } catch (error) {
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
- } catch {
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
- } catch (error) {
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
- } else {
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) continue;
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
- } else if (reviewedSnippetContext.area !== 'GeneralJS_TS') {
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
- } else {
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
- } catch (error) {
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
- } catch (error) {
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
  }
@@ -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')) throw new Error('Read error');
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
- } catch (error) {
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
- } catch (error) {
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
- } catch (error) {
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
- } catch (error) {
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
- } catch (error) {
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
- } catch (error) {
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
- } catch (error) {
505
+ }
506
+ catch (error) {
500
507
  console.error(chalk.red(`Error in enhanced PR review: ${error.message}`));
501
508
  return {
502
509
  success: false,