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.
- package/package.json +1 -1
- package/src/content-retrieval.js +93 -153
- package/src/content-retrieval.test.js +49 -9
- package/src/custom-documents.js +17 -17
- package/src/feedback-loader.js +31 -31
- package/src/index.js +71 -94
- package/src/llm.js +4 -3
- package/src/project-analyzer.js +73 -41
- package/src/project-analyzer.test.js +3 -5
- package/src/rag-analyzer.js +189 -169
- package/src/rag-analyzer.test.js +55 -0
- package/src/rag-review.js +105 -74
- package/src/rag-review.test.js +115 -3
- package/src/zero-shot-classifier-open.js +3 -2
package/src/rag-analyzer.js
CHANGED
|
@@ -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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
500
|
+
verboseLog(options, chalk.magenta('--- Guidelines Sent to LLM ---'));
|
|
501
501
|
if (formattedGuidelines.length > 0) {
|
|
502
502
|
formattedGuidelines.forEach((g, i) => {
|
|
503
|
-
|
|
504
|
-
|
|
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
|
-
|
|
507
|
+
verboseLog(options, chalk.magenta(' (None)'));
|
|
508
508
|
}
|
|
509
509
|
|
|
510
|
-
|
|
510
|
+
verboseLog(options, chalk.magenta('--- Code Examples Sent to LLM ---'));
|
|
511
511
|
if (finalCodeExamples.length > 0) {
|
|
512
512
|
finalCodeExamples.forEach((ex, i) => {
|
|
513
|
-
|
|
514
|
-
|
|
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
|
-
|
|
517
|
+
verboseLog(options, chalk.magenta(' (None)'));
|
|
518
518
|
}
|
|
519
519
|
|
|
520
|
-
|
|
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
|
-
|
|
524
|
-
|
|
525
|
-
|
|
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
|
-
|
|
528
|
+
verboseLog(options, chalk.magenta(' (None)'));
|
|
529
529
|
}
|
|
530
|
-
|
|
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
|
-
|
|
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
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
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
|
-
|
|
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
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
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
|
-
|
|
988
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1015
|
+
verboseLog(context.options, chalk.blue(`PR History section preview: ${prHistorySection.substring(0, 200)}...`));
|
|
1017
1016
|
} else {
|
|
1018
|
-
|
|
1017
|
+
verboseLog(context.options, chalk.yellow(`❌ No PR comments section found in context`));
|
|
1019
1018
|
}
|
|
1020
1019
|
} else {
|
|
1021
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1530
|
+
verboseLog(options, chalk.green(`✅ Hybrid search returned ${relevantComments.length} comments`));
|
|
1533
1531
|
if (relevantComments.length > 0) {
|
|
1534
|
-
|
|
1532
|
+
verboseLog(options, chalk.blue(`Top comment similarities:`));
|
|
1535
1533
|
relevantComments.slice(0, 3).forEach((comment, idx) => {
|
|
1536
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
1676
|
+
verboseLog(options, chalk.blue(`🔍 Performing holistic analysis of ${prFiles.length} files with unified context...`));
|
|
1678
1677
|
|
|
1679
1678
|
// Retrieve project architecture summary
|
|
1680
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1738
|
+
verboseLog(options, chalk.gray(` Content: ${g.content.substring(0, 100).replace(/\n/g, ' ')}...`));
|
|
1739
1739
|
});
|
|
1740
1740
|
} else {
|
|
1741
|
-
|
|
1741
|
+
verboseLog(options, chalk.magenta(' (None)'));
|
|
1742
1742
|
}
|
|
1743
1743
|
|
|
1744
|
-
|
|
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
|
-
|
|
1748
|
-
|
|
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
|
-
|
|
1751
|
+
verboseLog(options, chalk.magenta(' (None)'));
|
|
1752
1752
|
}
|
|
1753
1753
|
|
|
1754
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1763
|
-
|
|
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
|
-
|
|
1767
|
+
verboseLog(options, chalk.magenta(' (None)'));
|
|
1767
1768
|
}
|
|
1768
1769
|
|
|
1769
|
-
|
|
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
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
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
|
-
|
|
1778
|
+
verboseLog(options, chalk.magenta(' (None)'));
|
|
1778
1779
|
}
|
|
1779
|
-
|
|
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
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1909
|
+
verboseLog(options, chalk.cyan('📋 Custom Document Chunks Selected:'));
|
|
1906
1910
|
relevantChunks.forEach((chunk, i) => {
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1954
|
+
verboseLog(options, chalk.cyan('📋 Custom Document Chunks Selected:'));
|
|
1951
1955
|
relevantChunks.forEach((chunk, i) => {
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2209
|
+
verboseLog(options, chalk.green(`📄 Reusing ${processedChunks.length} already processed custom document chunks`));
|
|
2188
2210
|
}
|
|
2189
2211
|
|
|
2190
2212
|
globalCustomDocChunks = processedChunks;
|
|
2191
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
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
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
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
|
-
|
|
2360
|
-
|
|
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
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2416
|
-
|
|
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,
|