codecritique 1.2.2 → 1.2.4

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.
@@ -26,7 +26,7 @@ import { inferContextFromCodeContent, inferContextFromDocumentContent } from './
26
26
  import { isGenericDocument, getGenericDocumentContext } from './utils/document-detection.js';
27
27
  import { isTestFile, shouldProcessFile } from './utils/file-validation.js';
28
28
  import { detectFileType, detectLanguageFromExtension } from './utils/language-detection.js';
29
- import { debug } from './utils/logging.js';
29
+ import { debug, verboseLog } from './utils/logging.js';
30
30
  import { addLineNumbers } from './utils/string-utils.js';
31
31
 
32
32
  // Constants for content processing
@@ -56,7 +56,7 @@ async function ensureSemanticSimilarityInitialized() {
56
56
  await initializeSemanticSimilarity();
57
57
  semanticSimilarityInitialized = true;
58
58
  } catch (error) {
59
- console.log(chalk.yellow(`⚠️ Could not initialize semantic similarity: ${error.message}`));
59
+ console.warn(chalk.yellow(`⚠️ Could not initialize semantic similarity: ${error.message}`));
60
60
  // Continue without semantic similarity - word-based fallback will be used
61
61
  }
62
62
  }
@@ -216,7 +216,7 @@ ${ex.content}
216
216
  * @param {string} projectPath - Project path
217
217
  * @returns {Promise<Object|null>} Project summary or null
218
218
  */
219
- async function getProjectSummary(projectPath) {
219
+ async function getProjectSummary(projectPath, options = {}) {
220
220
  const resolvedPath = path.resolve(projectPath);
221
221
 
222
222
  try {
@@ -224,7 +224,7 @@ async function getProjectSummary(projectPath) {
224
224
  const summary = await embeddingsSystem.getProjectSummary(resolvedPath);
225
225
 
226
226
  if (summary) {
227
- console.log(chalk.cyan(`📋 Retrieved project summary for: ${path.basename(resolvedPath)}`));
227
+ verboseLog(options, chalk.cyan(`📋 Retrieved project summary for: ${path.basename(resolvedPath)}`));
228
228
  }
229
229
 
230
230
  return summary;
@@ -430,16 +430,16 @@ async function runAnalysis(filePath, options = {}) {
430
430
  try {
431
431
  // Check if this is a holistic PR review
432
432
  if (options.isHolisticPRReview && filePath === 'PR_HOLISTIC_REVIEW') {
433
- console.log(chalk.blue(`Performing holistic PR review for ${options.prFiles?.length || 0} files`));
433
+ verboseLog(options, chalk.blue(`Performing holistic PR review for ${options.prFiles?.length || 0} files`));
434
434
  return await performHolisticPRAnalysis(options);
435
435
  }
436
436
 
437
- console.log(chalk.blue(`Analyzing file: ${filePath}`));
437
+ verboseLog(options, chalk.blue(`Analyzing file: ${filePath}`));
438
438
 
439
439
  // Load feedback data if feedback tracking is enabled
440
440
  let feedbackData = {};
441
441
  if (options.trackFeedback && options.feedbackPath) {
442
- console.log(chalk.cyan('--- Loading Feedback Data ---'));
442
+ verboseLog(options, chalk.cyan('--- Loading Feedback Data ---'));
443
443
  feedbackData = await loadFeedbackData(options.feedbackPath, { verbose: options.verbose });
444
444
  }
445
445
 
@@ -455,16 +455,16 @@ async function runAnalysis(filePath, options = {}) {
455
455
  content = options.diffContent;
456
456
  // For PR reviews, always read the full file content for context awareness
457
457
  fullFileContent = fs.existsSync(filePath) ? fs.readFileSync(filePath, 'utf8') : null;
458
- console.log(chalk.blue(`Analyzing diff only for ${path.basename(filePath)}`));
458
+ verboseLog(options, chalk.blue(`Analyzing diff only for ${path.basename(filePath)}`));
459
459
  } else {
460
460
  content = fs.readFileSync(filePath, 'utf8');
461
461
  fullFileContent = content;
462
- console.log(chalk.blue(`Analyzing full file ${path.basename(filePath)}`));
462
+ verboseLog(options, chalk.blue(`Analyzing full file ${path.basename(filePath)}`));
463
463
  }
464
464
 
465
465
  // Check if file should be processed
466
466
  if (!shouldProcessFile(filePath, content)) {
467
- console.log(chalk.yellow(`Skipping file based on exclusion patterns: ${filePath}`));
467
+ verboseLog(options, chalk.yellow(`Skipping file based on exclusion patterns: ${filePath}`));
468
468
  return {
469
469
  success: true,
470
470
  skipped: true,
@@ -473,7 +473,7 @@ async function runAnalysis(filePath, options = {}) {
473
473
  }
474
474
 
475
475
  // --- Stage 1: CONTEXT RETRIEVAL ---
476
- console.log(chalk.blue('--- Stage 1: Context Retrieval ---'));
476
+ verboseLog(options, chalk.blue('--- Stage 1: Context Retrieval ---'));
477
477
  const {
478
478
  language,
479
479
  isTestFile,
@@ -485,49 +485,49 @@ async function runAnalysis(filePath, options = {}) {
485
485
  } = await getContextForFile(filePath, content, options);
486
486
 
487
487
  // --- Stage 1.5: PROJECT ARCHITECTURE CONTEXT ---
488
- console.log(chalk.blue('--- Stage 1.5: Retrieving Project Architecture Context ---'));
488
+ verboseLog(options, chalk.blue('--- Stage 1.5: Retrieving Project Architecture Context ---'));
489
489
  const projectPath = options.projectPath || process.cwd();
490
- const projectSummary = await getProjectSummary(projectPath);
490
+ const projectSummary = await getProjectSummary(projectPath, options);
491
491
 
492
492
  // --- Stage 2: PREPARE CONTEXT FOR LLM ---
493
- console.log(chalk.blue('--- Stage 2: Preparing Context for LLM ---'));
493
+ verboseLog(options, chalk.blue('--- Stage 2: Preparing Context for LLM ---'));
494
494
 
495
495
  // Format the lists that will be passed
496
496
  const formattedCodeExamples = formatContextItems(finalCodeExamples, 'code');
497
497
  const formattedGuidelines = formatContextItems(finalGuidelineSnippets, 'guideline');
498
498
 
499
499
  // --- Log the context being sent to the LLM --- >
500
- console.log(chalk.magenta('--- Guidelines Sent to LLM ---'));
500
+ verboseLog(options, chalk.magenta('--- Guidelines Sent to LLM ---'));
501
501
  if (formattedGuidelines.length > 0) {
502
502
  formattedGuidelines.forEach((g, i) => {
503
- console.log(chalk.magenta(` [${i + 1}] Path: ${g.path} ${g.headingText ? `(Heading: "${g.headingText}")` : ''}`));
504
- console.log(chalk.gray(` Content: ${g.content.substring(0, 100).replace(/\\n/g, ' ')}...`));
503
+ verboseLog(options, chalk.magenta(` [${i + 1}] Path: ${g.path} ${g.headingText ? `(Heading: "${g.headingText}")` : ''}`));
504
+ verboseLog(options, chalk.gray(` Content: ${g.content.substring(0, 100).replace(/\\n/g, ' ')}...`));
505
505
  });
506
506
  } else {
507
- console.log(chalk.magenta(' (None)'));
507
+ verboseLog(options, chalk.magenta(' (None)'));
508
508
  }
509
509
 
510
- console.log(chalk.magenta('--- Code Examples Sent to LLM ---'));
510
+ verboseLog(options, chalk.magenta('--- Code Examples Sent to LLM ---'));
511
511
  if (finalCodeExamples.length > 0) {
512
512
  finalCodeExamples.forEach((ex, i) => {
513
- console.log(chalk.magenta(` [${i + 1}] Path: ${ex.path} (Similarity: ${ex.similarity?.toFixed(3) || 'N/A'})`));
514
- console.log(chalk.gray(` Content: ${ex.content.substring(0, 100).replace(/\\n/g, ' ')}...`));
513
+ verboseLog(options, chalk.magenta(` [${i + 1}] Path: ${ex.path} (Similarity: ${ex.similarity?.toFixed(3) || 'N/A'})`));
514
+ verboseLog(options, chalk.gray(` Content: ${ex.content.substring(0, 100).replace(/\\n/g, ' ')}...`));
515
515
  });
516
516
  } else {
517
- console.log(chalk.magenta(' (None)'));
517
+ verboseLog(options, chalk.magenta(' (None)'));
518
518
  }
519
519
 
520
- console.log(chalk.magenta('--- Custom Document Chunks Sent to LLM ---'));
520
+ verboseLog(options, chalk.magenta('--- Custom Document Chunks Sent to LLM ---'));
521
521
  if (relevantCustomDocChunks && relevantCustomDocChunks.length > 0) {
522
522
  relevantCustomDocChunks.forEach((chunk, i) => {
523
- console.log(chalk.magenta(` [${i + 1}] Document: "${chunk.document_title}" (Chunk ${chunk.chunk_index + 1})`));
524
- console.log(chalk.magenta(` Similarity: ${chunk.similarity?.toFixed(3) || 'N/A'}`));
525
- console.log(chalk.gray(` Content: ${chunk.content.substring(0, 100).replace(/\\n/g, ' ')}...`));
523
+ verboseLog(options, chalk.magenta(` [${i + 1}] Document: "${chunk.document_title}" (Chunk ${chunk.chunk_index + 1})`));
524
+ verboseLog(options, chalk.magenta(` Similarity: ${chunk.similarity?.toFixed(3) || 'N/A'}`));
525
+ verboseLog(options, chalk.gray(` Content: ${chunk.content.substring(0, 100).replace(/\\n/g, ' ')}...`));
526
526
  });
527
527
  } else {
528
- console.log(chalk.magenta(' (None)'));
528
+ verboseLog(options, chalk.magenta(' (None)'));
529
529
  }
530
- console.log(chalk.magenta('---------------------------------'));
530
+ verboseLog(options, chalk.magenta('---------------------------------'));
531
531
  // --- End Logging --->
532
532
 
533
533
  // Prepare context for LLM with the potentially reduced lists
@@ -553,7 +553,7 @@ async function runAnalysis(filePath, options = {}) {
553
553
  // Post-process results to filter dismissed issues
554
554
  let filteredResults = lowSeverityFiltered;
555
555
  if (options.trackFeedback && feedbackData && Object.keys(feedbackData).length > 0) {
556
- console.log(chalk.cyan('--- Filtering Results Based on Feedback ---'));
556
+ verboseLog(options, chalk.cyan('--- Filtering Results Based on Feedback ---'));
557
557
  filteredResults = await filterAnalysisResults(lowSeverityFiltered, feedbackData, {
558
558
  similarityThreshold: options.feedbackThreshold || 0.7,
559
559
  verbose: options.verbose,
@@ -759,11 +759,10 @@ async function callLLMForAnalysis(context, options = {}) {
759
759
  cacheTtl: options.cacheTtl || '5m', // Pass cache TTL option (default: 5m, no extra cost)
760
760
  });
761
761
 
762
- console.log(chalk.blue('Received LLM response, attempting to parse...'));
763
-
764
- console.log(chalk.gray(`Response type: ${typeof llmResponse}`));
765
- console.log(chalk.gray(`Response has json: ${!!llmResponse?.json}`));
766
- console.log(chalk.gray(`Response content length: ${llmResponse?.content?.length || 0} characters`));
762
+ verboseLog(options, chalk.blue('Received LLM response, attempting to parse...'));
763
+ verboseLog(options, chalk.gray(`Response type: ${typeof llmResponse}`));
764
+ verboseLog(options, chalk.gray(`Response has json: ${!!llmResponse?.json}`));
765
+ verboseLog(options, chalk.gray(`Response content length: ${llmResponse?.content?.length || 0} characters`));
767
766
 
768
767
  // Parse the raw LLM response
769
768
  const analysisResponse = parseAnalysisResponse(llmResponse);
@@ -780,7 +779,7 @@ async function callLLMForAnalysis(context, options = {}) {
780
779
  };
781
780
  }
782
781
 
783
- console.log(chalk.green('Successfully parsed LLM response with expected structure'));
782
+ verboseLog(options, chalk.green('Successfully parsed LLM response with expected structure'));
784
783
  return analysisResponse;
785
784
  } catch (error) {
786
785
  console.error(chalk.red(`Error calling LLM for analysis: ${error.message}`));
@@ -933,10 +932,10 @@ async function sendPromptToLLM(promptConfig, llmOptions) {
933
932
  };
934
933
 
935
934
  // Debug: Log prompt structure
936
- console.log(chalk.gray(` Prompt config has systemPrompt: ${!!promptConfig.systemPrompt}`));
937
- console.log(chalk.gray(` Prompt config has userPrompt: ${!!promptConfig.userPrompt}`));
938
- console.log(chalk.gray(` System prompt length: ${promptConfig.systemPrompt?.length || 0} chars`));
939
- console.log(chalk.gray(` User prompt length: ${promptConfig.userPrompt?.length || 0} chars`));
935
+ verboseLog(llmOptions, chalk.gray(` Prompt config has systemPrompt: ${!!promptConfig.systemPrompt}`));
936
+ verboseLog(llmOptions, chalk.gray(` Prompt config has userPrompt: ${!!promptConfig.userPrompt}`));
937
+ verboseLog(llmOptions, chalk.gray(` System prompt length: ${promptConfig.systemPrompt?.length || 0} chars`));
938
+ verboseLog(llmOptions, chalk.gray(` User prompt length: ${promptConfig.userPrompt?.length || 0} chars`));
940
939
 
941
940
  // Send prompt with system (cached) and user (dynamic) content
942
941
  const response = await llm.sendPromptToClaude(promptConfig.userPrompt, {
@@ -984,17 +983,17 @@ function generateAnalysisPrompt(context) {
984
983
  const { context: contextSections } = context;
985
984
  let prHistorySection = '';
986
985
 
987
- console.log(chalk.blue(`🔍 Checking for PR comments in prompt generation...`));
988
- console.log(chalk.gray(`Context sections available: ${contextSections ? contextSections.length : 0}`));
986
+ verboseLog(context.options, chalk.blue(`🔍 Checking for PR comments in prompt generation...`));
987
+ verboseLog(context.options, chalk.gray(`Context sections available: ${contextSections ? contextSections.length : 0}`));
989
988
 
990
989
  if (contextSections && contextSections.length > 0) {
991
990
  contextSections.forEach((section, idx) => {
992
- console.log(chalk.gray(` Section ${idx + 1}: ${section.title} (${section.items?.length || 0} items)`));
991
+ verboseLog(context.options, chalk.gray(` Section ${idx + 1}: ${section.title} (${section.items?.length || 0} items)`));
993
992
  });
994
993
 
995
994
  const prComments = contextSections.find((section) => section.title === 'Historical Review Comments');
996
995
  if (prComments && prComments.items.length > 0) {
997
- console.log(chalk.green(`✅ Adding ${prComments.items.length} PR comments to LLM prompt`));
996
+ verboseLog(context.options, chalk.green(`✅ Adding ${prComments.items.length} PR comments to LLM prompt`));
998
997
  prHistorySection += `
999
998
 
1000
999
  CONTEXT C: HISTORICAL REVIEW COMMENTS
@@ -1013,12 +1012,12 @@ Similar code patterns and issues identified by human reviewers in past PRs
1013
1012
  prHistorySection += `Use these historical patterns to identify DEFINITE issues in the current code. `;
1014
1013
  prHistorySection += `Only report issues that EXACTLY match historical patterns with SPECIFIC code fixes.\n\n`;
1015
1014
 
1016
- console.log(chalk.blue(`PR History section preview: ${prHistorySection.substring(0, 200)}...`));
1015
+ verboseLog(context.options, chalk.blue(`PR History section preview: ${prHistorySection.substring(0, 200)}...`));
1017
1016
  } else {
1018
- console.log(chalk.yellow(`❌ No PR comments section found in context`));
1017
+ verboseLog(context.options, chalk.yellow(`❌ No PR comments section found in context`));
1019
1018
  }
1020
1019
  } else {
1021
- console.log(chalk.yellow(`❌ No context sections available for PR comments`));
1020
+ verboseLog(context.options, chalk.yellow(`❌ No context sections available for PR comments`));
1022
1021
  }
1023
1022
 
1024
1023
  // Detect if this is a diff review
@@ -1471,7 +1470,7 @@ async function getPRCommentContext(filePath, options = {}) {
1471
1470
  let contentForSearch = '';
1472
1471
 
1473
1472
  if (precomputedQueryEmbedding) {
1474
- console.log(chalk.blue(`🔍 Using pre-computed query embedding for PR comment search`));
1473
+ verboseLog(options, chalk.blue(`🔍 Using pre-computed query embedding for PR comment search`));
1475
1474
  // We still need the file content for the search function, but not for embedding
1476
1475
  try {
1477
1476
  fileContent = fs.readFileSync(filePath, 'utf8');
@@ -1513,39 +1512,39 @@ async function getPRCommentContext(filePath, options = {}) {
1513
1512
  // Use semantic search to find similar PR comments
1514
1513
  let relevantComments = [];
1515
1514
 
1516
- console.log(chalk.blue(`🔍 Searching for PR comments with:`));
1517
-
1518
- console.log(chalk.gray(` Project Path: ${projectPath}`));
1519
- console.log(chalk.gray(` File: ${fileName}`));
1520
- console.log(chalk.gray(` Similarity Threshold: ${similarityThreshold}`));
1521
- console.log(chalk.gray(` Content Length: ${contentForSearch.length} chars`));
1522
- console.log(chalk.gray(` Using Pre-computed Embedding: ${precomputedQueryEmbedding ? 'Yes' : 'No'}`));
1515
+ verboseLog(options, chalk.blue(`🔍 Searching for PR comments with:`));
1516
+ verboseLog(options, chalk.gray(` Project Path: ${projectPath}`));
1517
+ verboseLog(options, chalk.gray(` File: ${fileName}`));
1518
+ verboseLog(options, chalk.gray(` Similarity Threshold: ${similarityThreshold}`));
1519
+ verboseLog(options, chalk.gray(` Content Length: ${contentForSearch.length} chars`));
1520
+ verboseLog(options, chalk.gray(` Using Pre-computed Embedding: ${precomputedQueryEmbedding ? 'Yes' : 'No'}`));
1523
1521
 
1524
1522
  try {
1525
- console.log(chalk.blue(`🔍 Attempting hybrid search with chunking...`));
1523
+ verboseLog(options, chalk.blue(`🔍 Attempting hybrid search with chunking...`));
1526
1524
  relevantComments = await findRelevantPRComments(contentForSearch, {
1527
1525
  projectPath,
1528
1526
  limit: maxComments,
1529
1527
  isTestFile: isTest, // Pass test file context for filtering
1530
1528
  precomputedQueryEmbedding: precomputedQueryEmbedding, // Pass pre-computed embedding if available
1531
1529
  });
1532
- console.log(chalk.green(`✅ Hybrid search returned ${relevantComments.length} comments`));
1530
+ verboseLog(options, chalk.green(`✅ Hybrid search returned ${relevantComments.length} comments`));
1533
1531
  if (relevantComments.length > 0) {
1534
- console.log(chalk.blue(`Top comment similarities:`));
1532
+ verboseLog(options, chalk.blue(`Top comment similarities:`));
1535
1533
  relevantComments.slice(0, 3).forEach((comment, idx) => {
1536
- console.log(
1534
+ verboseLog(
1535
+ options,
1537
1536
  chalk.gray(` ${idx + 1}. Score: ${comment.similarity_score?.toFixed(3)} - ${comment.comment_text?.substring(0, 80)}...`)
1538
1537
  );
1539
1538
  });
1540
1539
  }
1541
1540
  } catch (dbError) {
1542
- console.log(chalk.yellow(`⚠️ Hybrid search failed: ${dbError.message}`));
1541
+ console.warn(chalk.yellow(`⚠️ Hybrid search failed: ${dbError.message}`));
1543
1542
  debug(`[getPRCommentContext] Hybrid search failed: ${dbError.message}`);
1544
1543
  // No fallback needed - if hybrid search fails, we just return empty results
1545
1544
  relevantComments = [];
1546
1545
  }
1547
1546
 
1548
- console.log('Total relevant comments number:', relevantComments.length);
1547
+ verboseLog(options, 'Total relevant comments number:', relevantComments.length);
1549
1548
 
1550
1549
  // Extract patterns and insights
1551
1550
  const patterns = extractCommentPatterns(relevantComments);
@@ -1674,12 +1673,12 @@ async function performHolisticPRAnalysis(options) {
1674
1673
  try {
1675
1674
  const { prFiles, unifiedContext, customDocs } = options;
1676
1675
 
1677
- console.log(chalk.blue(`🔍 Performing holistic analysis of ${prFiles.length} files with unified context...`));
1676
+ verboseLog(options, chalk.blue(`🔍 Performing holistic analysis of ${prFiles.length} files with unified context...`));
1678
1677
 
1679
1678
  // Retrieve project architecture summary
1680
- console.log(chalk.blue('--- Retrieving Project Architecture Context for Holistic PR Review ---'));
1679
+ verboseLog(options, chalk.blue('--- Retrieving Project Architecture Context for Holistic PR Review ---'));
1681
1680
  const projectPath = options.projectPath || process.cwd();
1682
- const projectSummary = await getProjectSummary(projectPath);
1681
+ const projectSummary = await getProjectSummary(projectPath, options);
1683
1682
 
1684
1683
  // Create a synthetic file context for holistic analysis
1685
1684
  const holisticContext = {
@@ -1727,56 +1726,58 @@ async function performHolisticPRAnalysis(options) {
1727
1726
  };
1728
1727
 
1729
1728
  // Add verbose debug logging similar to individual file reviews
1730
- console.log(chalk.magenta('--- Holistic PR Review: Guidelines Sent to LLM ---'));
1729
+ verboseLog(options, chalk.magenta('--- Holistic PR Review: Guidelines Sent to LLM ---'));
1731
1730
  if (unifiedContext.guidelines.length > 0) {
1732
1731
  unifiedContext.guidelines.slice(0, 10).forEach((g, i) => {
1733
- console.log(
1732
+ verboseLog(
1733
+ options,
1734
1734
  chalk.magenta(
1735
1735
  ` [${i + 1}] Path: ${g.path} ${g.headingText || g.heading_text ? `(Heading: "${g.headingText || g.heading_text}")` : ''}`
1736
1736
  )
1737
1737
  );
1738
- console.log(chalk.gray(` Content: ${g.content.substring(0, 100).replace(/\n/g, ' ')}...`));
1738
+ verboseLog(options, chalk.gray(` Content: ${g.content.substring(0, 100).replace(/\n/g, ' ')}...`));
1739
1739
  });
1740
1740
  } else {
1741
- console.log(chalk.magenta(' (None)'));
1741
+ verboseLog(options, chalk.magenta(' (None)'));
1742
1742
  }
1743
1743
 
1744
- console.log(chalk.magenta('--- Holistic PR Review: Code Examples Sent to LLM ---'));
1744
+ verboseLog(options, chalk.magenta('--- Holistic PR Review: Code Examples Sent to LLM ---'));
1745
1745
  if (unifiedContext.codeExamples.length > 0) {
1746
1746
  unifiedContext.codeExamples.slice(0, 10).forEach((ex, i) => {
1747
- console.log(chalk.magenta(` [${i + 1}] Path: ${ex.path} (Similarity: ${ex.similarity?.toFixed(3) || 'N/A'})`));
1748
- console.log(chalk.gray(` Content: ${ex.content.substring(0, 100).replace(/\\n/g, ' ')}...`));
1747
+ verboseLog(options, chalk.magenta(` [${i + 1}] Path: ${ex.path} (Similarity: ${ex.similarity?.toFixed(3) || 'N/A'})`));
1748
+ verboseLog(options, chalk.gray(` Content: ${ex.content.substring(0, 100).replace(/\\n/g, ' ')}...`));
1749
1749
  });
1750
1750
  } else {
1751
- console.log(chalk.magenta(' (None)'));
1751
+ verboseLog(options, chalk.magenta(' (None)'));
1752
1752
  }
1753
1753
 
1754
- console.log(chalk.magenta('--- Holistic PR Review: Top Historic Comments Sent to LLM ---'));
1754
+ verboseLog(options, chalk.magenta('--- Holistic PR Review: Top Historic Comments Sent to LLM ---'));
1755
1755
  if (unifiedContext.prComments.length > 0) {
1756
1756
  unifiedContext.prComments.slice(0, 5).forEach((comment, i) => {
1757
- console.log(
1757
+ verboseLog(
1758
+ options,
1758
1759
  chalk.magenta(
1759
1760
  ` [${i + 1}] PR #${comment.prNumber} by ${comment.author} (Relevance: ${(comment.relevanceScore * 100).toFixed(1)}%)`
1760
1761
  )
1761
1762
  );
1762
- console.log(chalk.gray(` File: ${comment.filePath}`));
1763
- console.log(chalk.gray(` Comment: ${comment.body.substring(0, 100).replace(/\n/g, ' ')}...`));
1763
+ verboseLog(options, chalk.gray(` File: ${comment.filePath}`));
1764
+ verboseLog(options, chalk.gray(` Comment: ${comment.body.substring(0, 100).replace(/\n/g, ' ')}...`));
1764
1765
  });
1765
1766
  } else {
1766
- console.log(chalk.magenta(' (None)'));
1767
+ verboseLog(options, chalk.magenta(' (None)'));
1767
1768
  }
1768
1769
 
1769
- console.log(chalk.magenta('--- Holistic PR Review: Custom Document Chunks Sent to LLM ---'));
1770
+ verboseLog(options, chalk.magenta('--- Holistic PR Review: Custom Document Chunks Sent to LLM ---'));
1770
1771
  if (unifiedContext.customDocChunks && unifiedContext.customDocChunks.length > 0) {
1771
1772
  unifiedContext.customDocChunks.forEach((chunk, i) => {
1772
- console.log(chalk.magenta(` [${i + 1}] Document: "${chunk.document_title}" (Chunk ${chunk.chunk_index + 1})`));
1773
- console.log(chalk.gray(` Similarity: ${chunk.similarity?.toFixed(3) || 'N/A'}`));
1774
- console.log(chalk.gray(` Content: ${chunk.content.substring(0, 100).replace(/\n/g, ' ')}...`));
1773
+ verboseLog(options, chalk.magenta(` [${i + 1}] Document: "${chunk.document_title}" (Chunk ${chunk.chunk_index + 1})`));
1774
+ verboseLog(options, chalk.gray(` Similarity: ${chunk.similarity?.toFixed(3) || 'N/A'}`));
1775
+ verboseLog(options, chalk.gray(` Content: ${chunk.content.substring(0, 100).replace(/\n/g, ' ')}...`));
1775
1776
  });
1776
1777
  } else {
1777
- console.log(chalk.magenta(' (None)'));
1778
+ verboseLog(options, chalk.magenta(' (None)'));
1778
1779
  }
1779
- console.log(chalk.magenta('--- Sending Holistic PR Analysis Prompt to LLM ---'));
1780
+ verboseLog(options, chalk.magenta('--- Sending Holistic PR Analysis Prompt to LLM ---'));
1780
1781
 
1781
1782
  // Call the centralized analysis function
1782
1783
  const parsedResponse = await callLLMForAnalysis(holisticContext, {
@@ -1785,11 +1786,11 @@ async function performHolisticPRAnalysis(options) {
1785
1786
  });
1786
1787
 
1787
1788
  // Debug logging
1788
- console.log(chalk.blue(`🐛 Holistic analysis parsed response:`));
1789
- console.log(chalk.gray(`Summary: ${parsedResponse.summary?.substring(0, 100)}...`));
1790
- console.log(chalk.gray(`Cross-file issues: ${parsedResponse.crossFileIssues?.length || 0}`));
1791
- console.log(chalk.gray(`File-specific issues keys: ${Object.keys(parsedResponse.fileSpecificIssues || {}).join(', ')}`));
1792
- console.log(chalk.gray(`Recommendations: ${parsedResponse.recommendations?.length || 0}`));
1789
+ verboseLog(options, chalk.blue(`🐛 Holistic analysis parsed response:`));
1790
+ verboseLog(options, chalk.gray(`Summary: ${parsedResponse.summary?.substring(0, 100)}...`));
1791
+ verboseLog(options, chalk.gray(`Cross-file issues: ${parsedResponse.crossFileIssues?.length || 0}`));
1792
+ verboseLog(options, chalk.gray(`File-specific issues keys: ${Object.keys(parsedResponse.fileSpecificIssues || {}).join(', ')}`));
1793
+ verboseLog(options, chalk.gray(`Recommendations: ${parsedResponse.recommendations?.length || 0}`));
1793
1794
 
1794
1795
  // Filter out low severity issues (formatting/style concerns handled by linters)
1795
1796
  // Note: The LLM prompt instructs not to generate low severity issues, but this filter
@@ -1881,12 +1882,15 @@ async function getContextForFile(filePath, content, options = {}) {
1881
1882
  guidelineQueryEmbedding = await embeddingsSystem.calculateQueryEmbedding(guidelineQuery);
1882
1883
  }
1883
1884
 
1884
- console.log(chalk.blue('� Starting parallel context retrieval...'));
1885
+ verboseLog(options, chalk.blue('� Starting parallel context retrieval...'));
1885
1886
  // Helper function to process custom documents in parallel (with caching)
1886
1887
  const processCustomDocuments = async () => {
1887
1888
  // Check if preprocessed chunks are available (from PR-level processing)
1888
1889
  if (options.preprocessedCustomDocChunks && options.preprocessedCustomDocChunks.length > 0) {
1889
- console.log(chalk.blue(`📄 Using preprocessed custom document chunks (${options.preprocessedCustomDocChunks.length} available)`));
1890
+ verboseLog(
1891
+ options,
1892
+ chalk.blue(`📄 Using preprocessed custom document chunks (${options.preprocessedCustomDocChunks.length} available)`)
1893
+ );
1890
1894
 
1891
1895
  // Use the guideline query for finding relevant custom document chunks
1892
1896
  const relevantChunks = await embeddingsSystem.findRelevantCustomDocChunks(guidelineQuery, options.preprocessedCustomDocChunks, {
@@ -1898,15 +1902,15 @@ async function getContextForFile(filePath, content, options = {}) {
1898
1902
  queryFilePath: filePath,
1899
1903
  });
1900
1904
 
1901
- console.log(chalk.green(`📄 Found ${relevantChunks.length} relevant custom document chunks`));
1905
+ verboseLog(options, chalk.green(`📄 Found ${relevantChunks.length} relevant custom document chunks`));
1902
1906
 
1903
1907
  // Log which chunks made the cut
1904
1908
  if (relevantChunks.length > 0) {
1905
- console.log(chalk.cyan('📋 Custom Document Chunks Selected:'));
1909
+ verboseLog(options, chalk.cyan('📋 Custom Document Chunks Selected:'));
1906
1910
  relevantChunks.forEach((chunk, i) => {
1907
- console.log(chalk.cyan(` [${i + 1}] "${chunk.document_title}" (Chunk ${chunk.chunk_index + 1})`));
1908
- console.log(chalk.gray(` Similarity: ${chunk.similarity?.toFixed(3) || 'N/A'}`));
1909
- console.log(chalk.gray(` Content: ${chunk.content.substring(0, 80).replace(/\n/g, ' ')}...`));
1911
+ verboseLog(options, chalk.cyan(` [${i + 1}] "${chunk.document_title}" (Chunk ${chunk.chunk_index + 1})`));
1912
+ verboseLog(options, chalk.gray(` Similarity: ${chunk.similarity?.toFixed(3) || 'N/A'}`));
1913
+ verboseLog(options, chalk.gray(` Content: ${chunk.content.substring(0, 80).replace(/\n/g, ' ')}...`));
1910
1914
  });
1911
1915
  }
1912
1916
 
@@ -1919,17 +1923,17 @@ async function getContextForFile(filePath, content, options = {}) {
1919
1923
  }
1920
1924
 
1921
1925
  try {
1922
- console.log(chalk.blue('📄 Processing custom documents for context...'));
1926
+ verboseLog(options, chalk.blue('📄 Processing custom documents for context...'));
1923
1927
 
1924
1928
  // Check if custom documents are already processed for this project
1925
1929
  let processedChunks = await checkExistingCustomDocumentChunks(projectPath);
1926
1930
 
1927
1931
  if (!processedChunks || processedChunks.length === 0) {
1928
- console.log(chalk.cyan('📄 Custom documents not yet processed for this project, processing now...'));
1932
+ verboseLog(options, chalk.cyan('📄 Custom documents not yet processed for this project, processing now...'));
1929
1933
  // Process custom documents into chunks (only if not already processed)
1930
1934
  processedChunks = await embeddingsSystem.processCustomDocumentsInMemory(options.customDocs, projectPath);
1931
1935
  } else {
1932
- console.log(chalk.green(`📄 Reusing ${processedChunks.length} already processed custom document chunks`));
1936
+ verboseLog(options, chalk.green(`📄 Reusing ${processedChunks.length} already processed custom document chunks`));
1933
1937
  }
1934
1938
 
1935
1939
  if (processedChunks.length > 0) {
@@ -1943,15 +1947,15 @@ async function getContextForFile(filePath, content, options = {}) {
1943
1947
  queryFilePath: filePath,
1944
1948
  });
1945
1949
 
1946
- console.log(chalk.green(`📄 Found ${relevantChunks.length} relevant custom document chunks`));
1950
+ verboseLog(options, chalk.green(`📄 Found ${relevantChunks.length} relevant custom document chunks`));
1947
1951
 
1948
1952
  // Log which chunks made the cut
1949
1953
  if (relevantChunks.length > 0) {
1950
- console.log(chalk.cyan('📋 Custom Document Chunks Selected:'));
1954
+ verboseLog(options, chalk.cyan('📋 Custom Document Chunks Selected:'));
1951
1955
  relevantChunks.forEach((chunk, i) => {
1952
- console.log(chalk.cyan(` [${i + 1}] "${chunk.document_title}" (Chunk ${chunk.chunk_index + 1})`));
1953
- console.log(chalk.gray(` Similarity: ${chunk.similarity?.toFixed(3) || 'N/A'}`));
1954
- console.log(chalk.gray(` Content: ${chunk.content.substring(0, 80).replace(/\n/g, ' ')}...`));
1956
+ verboseLog(options, chalk.cyan(` [${i + 1}] "${chunk.document_title}" (Chunk ${chunk.chunk_index + 1})`));
1957
+ verboseLog(options, chalk.gray(` Similarity: ${chunk.similarity?.toFixed(3) || 'N/A'}`));
1958
+ verboseLog(options, chalk.gray(` Content: ${chunk.content.substring(0, 80).replace(/\n/g, ' ')}...`));
1955
1959
  });
1956
1960
  }
1957
1961
 
@@ -1970,49 +1974,67 @@ async function getContextForFile(filePath, content, options = {}) {
1970
1974
  // Use the statically imported function
1971
1975
  return await embeddingsSystem.getExistingCustomDocumentChunks(projectPath);
1972
1976
  } catch {
1973
- console.log(chalk.gray('No existing custom document chunks found, will process from scratch'));
1977
+ verboseLog(options, chalk.gray('No existing custom document chunks found, will process from scratch'));
1974
1978
  return [];
1975
1979
  }
1976
1980
  };
1977
1981
 
1982
+ const withContextFallback = async (promise, fallbackValue, label) => {
1983
+ try {
1984
+ return await promise;
1985
+ } catch (error) {
1986
+ console.warn(chalk.yellow(`${label} failed: ${error.message}`));
1987
+ return fallbackValue;
1988
+ }
1989
+ };
1990
+
1978
1991
  const [prContextResult, guidelineCandidates, codeExampleCandidates, relevantCustomDocChunks] = await Promise.all([
1979
- getPRCommentContext(filePath, {
1980
- ...options,
1981
- projectPath,
1982
- precomputedQueryEmbedding: fileContentQueryEmbedding,
1983
- maxComments: MAX_PR_COMMENTS_FOR_CONTEXT,
1984
- similarityThreshold: options.prSimilarityThreshold || 0.3,
1985
- timeout: options.prTimeout || 300000,
1986
- repository: options.repository || null,
1987
- }),
1988
- embeddingsSystem.findRelevantDocs(guidelineQuery, {
1989
- ...options,
1990
- projectPath,
1991
- precomputedQueryEmbedding: guidelineQueryEmbedding,
1992
- limit: GUIDELINE_CANDIDATE_LIMIT,
1993
- similarityThreshold: 0.05,
1994
- useReranking: true,
1995
- queryContextForReranking: reviewedSnippetContext,
1996
- }),
1997
- embeddingsSystem.findSimilarCode(isTestFile ? `${content}\\n// Looking for similar test files and testing patterns` : content, {
1998
- ...options,
1999
- projectPath,
2000
- isTestFile,
2001
- precomputedQueryEmbedding: fileContentQueryEmbedding,
2002
- limit: CODE_EXAMPLE_LIMIT,
2003
- similarityThreshold: 0.3,
2004
- queryFilePath: filePath,
2005
- includeProjectStructure: false,
2006
- }),
2007
- processCustomDocuments(), // Add custom document processing as 4th parallel operation
2008
- ]).catch((error) => {
2009
- console.warn(chalk.yellow(`Parallel context retrieval failed: ${error.message}`));
2010
- return [[], [], [], []];
2011
- });
1992
+ withContextFallback(
1993
+ getPRCommentContext(filePath, {
1994
+ ...options,
1995
+ projectPath,
1996
+ precomputedQueryEmbedding: fileContentQueryEmbedding,
1997
+ maxComments: MAX_PR_COMMENTS_FOR_CONTEXT,
1998
+ similarityThreshold: options.prSimilarityThreshold || 0.3,
1999
+ timeout: options.prTimeout || 300000,
2000
+ repository: options.repository || null,
2001
+ }),
2002
+ { comments: [] },
2003
+ 'PR comment context retrieval'
2004
+ ),
2005
+ withContextFallback(
2006
+ embeddingsSystem.findRelevantDocs(guidelineQuery, {
2007
+ ...options,
2008
+ projectPath,
2009
+ precomputedQueryEmbedding: guidelineQueryEmbedding,
2010
+ limit: GUIDELINE_CANDIDATE_LIMIT,
2011
+ similarityThreshold: 0.05,
2012
+ useReranking: true,
2013
+ queryContextForReranking: reviewedSnippetContext,
2014
+ }),
2015
+ [],
2016
+ 'Guideline retrieval'
2017
+ ),
2018
+ withContextFallback(
2019
+ embeddingsSystem.findSimilarCode(isTestFile ? `${content}\\n// Looking for similar test files and testing patterns` : content, {
2020
+ ...options,
2021
+ projectPath,
2022
+ isTestFile,
2023
+ precomputedQueryEmbedding: fileContentQueryEmbedding,
2024
+ limit: CODE_EXAMPLE_LIMIT,
2025
+ similarityThreshold: 0.3,
2026
+ queryFilePath: filePath,
2027
+ includeProjectStructure: false,
2028
+ }),
2029
+ [],
2030
+ 'Code example retrieval'
2031
+ ),
2032
+ withContextFallback(processCustomDocuments(), [], 'Custom document retrieval'),
2033
+ ]);
2012
2034
 
2013
2035
  const prCommentContext = prContextResult?.comments || [];
2014
2036
  const prContextAvailable = prCommentContext.length > 0;
2015
- console.log(chalk.green(`✅ Found ${prCommentContext.length} relevant PR comments`));
2037
+ verboseLog(options, chalk.green(`✅ Found ${prCommentContext.length} relevant PR comments`));
2016
2038
 
2017
2039
  const documentChunks = Array.isArray(guidelineCandidates) ? guidelineCandidates.filter((c) => c.type === 'documentation-chunk') : [];
2018
2040
  const chunksByDocument = new Map();
@@ -2174,21 +2196,21 @@ async function gatherUnifiedContextForPR(prFiles, options = {}) {
2174
2196
  let globalCustomDocChunks = [];
2175
2197
  if (options.customDocs && options.customDocs.length > 0) {
2176
2198
  const projectPath = options.projectPath || process.cwd();
2177
- console.log(chalk.blue('📄 Processing custom documents once for entire PR...'));
2199
+ verboseLog(options, chalk.blue('📄 Processing custom documents once for entire PR...'));
2178
2200
 
2179
2201
  try {
2180
2202
  // Check if custom documents are already processed for this project
2181
2203
  let processedChunks = await embeddingsSystem.getExistingCustomDocumentChunks(projectPath);
2182
2204
 
2183
2205
  if (!processedChunks || processedChunks.length === 0) {
2184
- console.log(chalk.cyan('📄 Custom documents not yet processed for this project, processing now...'));
2206
+ verboseLog(options, chalk.cyan('📄 Custom documents not yet processed for this project, processing now...'));
2185
2207
  processedChunks = await embeddingsSystem.processCustomDocumentsInMemory(options.customDocs, projectPath);
2186
2208
  } else {
2187
- console.log(chalk.green(`📄 Reusing ${processedChunks.length} already processed custom document chunks`));
2209
+ verboseLog(options, chalk.green(`📄 Reusing ${processedChunks.length} already processed custom document chunks`));
2188
2210
  }
2189
2211
 
2190
2212
  globalCustomDocChunks = processedChunks;
2191
- console.log(chalk.green(`📄 Custom documents processed: ${globalCustomDocChunks.length} chunks available for PR analysis`));
2213
+ verboseLog(options, chalk.green(`📄 Custom documents processed: ${globalCustomDocChunks.length} chunks available for PR analysis`));
2192
2214
  } catch (error) {
2193
2215
  console.error(chalk.red(`Error processing custom documents for PR: ${error.message}`));
2194
2216
  }
@@ -2288,6 +2310,7 @@ async function gatherUnifiedContextForPR(prFiles, options = {}) {
2288
2310
  *
2289
2311
  * @param {Object} analysisResults - Analysis results from LLM
2290
2312
  * @param {Object} options - Filtering options
2313
+ * @param {boolean} [options.verbose=false] - Enable verbose logging for filtered issues
2291
2314
  * @returns {Object} Filtered analysis results without low severity issues
2292
2315
  */
2293
2316
  function filterLowSeverityIssues(analysisResults, options = {}) {
@@ -2305,9 +2328,7 @@ function filterLowSeverityIssues(analysisResults, options = {}) {
2305
2328
  analysisResults.issues = analysisResults.issues.filter((issue) => {
2306
2329
  const severity = (issue.severity || '').toLowerCase();
2307
2330
  if (severity === 'low') {
2308
- if (verbose) {
2309
- console.log(chalk.yellow(` Filtering low severity issue: "${(issue.description || '').substring(0, 50)}..."`));
2310
- }
2331
+ verboseLog(verbose, chalk.yellow(` Filtering low severity issue: "${(issue.description || '').substring(0, 50)}..."`));
2311
2332
  return false;
2312
2333
  }
2313
2334
  return true;
@@ -2321,11 +2342,10 @@ function filterLowSeverityIssues(analysisResults, options = {}) {
2321
2342
  analysisResults.crossFileIssues = analysisResults.crossFileIssues.filter((issue) => {
2322
2343
  const severity = (issue.severity || '').toLowerCase();
2323
2344
  if (severity === 'low') {
2324
- if (verbose) {
2325
- console.log(
2326
- chalk.yellow(` Filtering low severity cross-file issue: "${(issue.message || issue.description || '').substring(0, 50)}..."`)
2327
- );
2328
- }
2345
+ verboseLog(
2346
+ verbose,
2347
+ chalk.yellow(` Filtering low severity cross-file issue: "${(issue.message || issue.description || '').substring(0, 50)}..."`)
2348
+ );
2329
2349
  return false;
2330
2350
  }
2331
2351
  return true;
@@ -2342,11 +2362,10 @@ function filterLowSeverityIssues(analysisResults, options = {}) {
2342
2362
  analysisResults.fileSpecificIssues[filePath] = issues.filter((issue) => {
2343
2363
  const severity = (issue.severity || '').toLowerCase();
2344
2364
  if (severity === 'low') {
2345
- if (verbose) {
2346
- console.log(
2347
- chalk.yellow(` Filtering low severity issue in ${filePath}: "${(issue.description || '').substring(0, 50)}..."`)
2348
- );
2349
- }
2365
+ verboseLog(
2366
+ verbose,
2367
+ chalk.yellow(` Filtering low severity issue in ${filePath}: "${(issue.description || '').substring(0, 50)}..."`)
2368
+ );
2350
2369
  return false;
2351
2370
  }
2352
2371
  return true;
@@ -2356,9 +2375,10 @@ function filterLowSeverityIssues(analysisResults, options = {}) {
2356
2375
  }
2357
2376
  }
2358
2377
 
2359
- if (filteredCount > 0) {
2360
- console.log(chalk.cyan(`🔇 Filtered ${filteredCount} low severity issue(s) (formatting/style concerns handled by linters)`));
2361
- }
2378
+ verboseLog(
2379
+ verbose && filteredCount > 0,
2380
+ chalk.cyan(`🔇 Filtered ${filteredCount} low severity issue(s) (formatting/style concerns handled by linters)`)
2381
+ );
2362
2382
 
2363
2383
  return analysisResults;
2364
2384
  }
@@ -2369,6 +2389,8 @@ function filterLowSeverityIssues(analysisResults, options = {}) {
2369
2389
  * @param {Object} analysisResults - Raw analysis results from LLM
2370
2390
  * @param {Object} feedbackData - Loaded feedback data
2371
2391
  * @param {Object} options - Filtering options
2392
+ * @param {number} [options.similarityThreshold=0.7] - Threshold for considering issues similar to dismissed feedback
2393
+ * @param {boolean} [options.verbose=false] - Enable verbose similarity and filtering logs
2372
2394
  * @returns {Promise<Object>} Filtered analysis results
2373
2395
  */
2374
2396
  async function filterAnalysisResults(analysisResults, feedbackData, options = {}) {
@@ -2384,12 +2406,11 @@ async function filterAnalysisResults(analysisResults, feedbackData, options = {}
2384
2406
  await ensureSemanticSimilarityInitialized();
2385
2407
 
2386
2408
  // Log whether semantic similarity is available
2387
- if (verbose) {
2388
- const usingSemanticSimilarity = isSemanticSimilarityAvailable();
2389
- console.log(
2390
- chalk.cyan(`🔍 Filtering issues using ${usingSemanticSimilarity ? 'semantic + word-based similarity' : 'word-based similarity only'}`)
2391
- );
2392
- }
2409
+ const usingSemanticSimilarity = isSemanticSimilarityAvailable();
2410
+ verboseLog(
2411
+ verbose,
2412
+ chalk.cyan(`🔍 Filtering issues using ${usingSemanticSimilarity ? 'semantic + word-based similarity' : 'word-based similarity only'}`)
2413
+ );
2393
2414
 
2394
2415
  // Filter issues based on feedback (now async due to semantic similarity)
2395
2416
  const filterResults = await Promise.all(
@@ -2400,9 +2421,7 @@ async function filterAnalysisResults(analysisResults, feedbackData, options = {}
2400
2421
  verbose,
2401
2422
  });
2402
2423
 
2403
- if (shouldSkip && verbose) {
2404
- console.log(chalk.yellow(` Filtered issue ${index + 1}: "${issueDescription.substring(0, 50)}..."`));
2405
- }
2424
+ verboseLog(shouldSkip && verbose, chalk.yellow(` Filtered issue ${index + 1}: "${issueDescription.substring(0, 50)}..."`));
2406
2425
 
2407
2426
  return { issue, shouldSkip };
2408
2427
  })
@@ -2412,9 +2431,10 @@ async function filterAnalysisResults(analysisResults, feedbackData, options = {}
2412
2431
 
2413
2432
  const filteredCount = originalCount - filteredIssues.length;
2414
2433
 
2415
- if (verbose && filteredCount > 0) {
2416
- console.log(chalk.green(`✅ Filtered ${filteredCount} dismissed issues, ${filteredIssues.length} remaining`));
2417
- }
2434
+ verboseLog(
2435
+ verbose && filteredCount > 0,
2436
+ chalk.green(`✅ Filtered ${filteredCount} dismissed issues, ${filteredIssues.length} remaining`)
2437
+ );
2418
2438
 
2419
2439
  return {
2420
2440
  ...analysisResults,