codecritique 1.2.1 → 1.2.3

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