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/feedback-loader.js
CHANGED
|
@@ -15,39 +15,41 @@ import path from 'path';
|
|
|
15
15
|
import chalk from 'chalk';
|
|
16
16
|
import { getDefaultEmbeddingsSystem } from './embeddings/factory.js';
|
|
17
17
|
import { calculateCosineSimilarity } from './embeddings/similarity-calculator.js';
|
|
18
|
+
import { verboseLog } from './utils/logging.js';
|
|
18
19
|
|
|
19
20
|
/**
|
|
20
21
|
* Load feedback data from artifacts directory
|
|
21
22
|
*
|
|
22
23
|
* @param {string} feedbackPath - Path to feedback artifacts directory
|
|
23
24
|
* @param {Object} options - Loading options
|
|
25
|
+
* @param {boolean} [options.verbose=false] - Enable verbose progress logging
|
|
24
26
|
* @returns {Promise<Object>} Loaded feedback data
|
|
25
27
|
*/
|
|
26
28
|
export async function loadFeedbackData(feedbackPath, options = {}) {
|
|
27
29
|
const { verbose = false } = options;
|
|
28
30
|
|
|
29
31
|
if (!feedbackPath) {
|
|
30
|
-
|
|
32
|
+
verboseLog(verbose, chalk.gray('No feedback path provided'));
|
|
31
33
|
return {};
|
|
32
34
|
}
|
|
33
35
|
|
|
34
36
|
try {
|
|
35
37
|
if (!fs.existsSync(feedbackPath)) {
|
|
36
|
-
|
|
38
|
+
verboseLog(verbose, chalk.gray(`Feedback directory not found: ${feedbackPath}`));
|
|
37
39
|
return {};
|
|
38
40
|
}
|
|
39
41
|
|
|
40
|
-
|
|
42
|
+
verboseLog(verbose, chalk.cyan(`📁 Loading feedback from: ${feedbackPath}`));
|
|
41
43
|
|
|
42
44
|
// Look for feedback files in the directory
|
|
43
45
|
const feedbackFiles = fs.readdirSync(feedbackPath).filter((file) => file.startsWith('feedback-') && file.endsWith('.json'));
|
|
44
46
|
|
|
45
47
|
if (feedbackFiles.length === 0) {
|
|
46
|
-
|
|
48
|
+
verboseLog(verbose, chalk.gray('No feedback files found'));
|
|
47
49
|
return {};
|
|
48
50
|
}
|
|
49
51
|
|
|
50
|
-
|
|
52
|
+
verboseLog(verbose, chalk.cyan(`📥 Found ${feedbackFiles.length} feedback file(s)`));
|
|
51
53
|
|
|
52
54
|
// Load and merge all feedback files
|
|
53
55
|
const allFeedback = {};
|
|
@@ -64,25 +66,21 @@ export async function loadFeedbackData(feedbackPath, options = {}) {
|
|
|
64
66
|
Object.assign(allFeedback, feedbackData.feedback);
|
|
65
67
|
const itemCount = Object.keys(feedbackData.feedback).length;
|
|
66
68
|
totalItems += itemCount;
|
|
67
|
-
|
|
68
|
-
console.log(chalk.cyan(`📋 Loaded feedback from ${file}: ${itemCount} items`));
|
|
69
|
-
}
|
|
69
|
+
verboseLog(verbose, chalk.cyan(`📋 Loaded feedback from ${file}: ${itemCount} items`));
|
|
70
70
|
}
|
|
71
71
|
} catch (parseError) {
|
|
72
|
-
console.
|
|
72
|
+
console.warn(chalk.yellow(`⚠️ Error parsing feedback file ${file}: ${parseError.message}`));
|
|
73
73
|
}
|
|
74
74
|
}
|
|
75
75
|
|
|
76
76
|
if (totalItems > 0) {
|
|
77
|
-
|
|
78
|
-
console.log(chalk.green(`✅ Successfully loaded ${totalItems} feedback items total`));
|
|
79
|
-
}
|
|
77
|
+
verboseLog(verbose, chalk.green(`✅ Successfully loaded ${totalItems} feedback items total`));
|
|
80
78
|
return allFeedback;
|
|
81
79
|
}
|
|
82
80
|
|
|
83
81
|
return {};
|
|
84
82
|
} catch (error) {
|
|
85
|
-
console.
|
|
83
|
+
console.error(chalk.red(`❌ Error loading feedback data: ${error.message}`));
|
|
86
84
|
return {};
|
|
87
85
|
}
|
|
88
86
|
}
|
|
@@ -113,9 +111,9 @@ export async function initializeSemanticSimilarity() {
|
|
|
113
111
|
await embeddingsSystem.initialize();
|
|
114
112
|
semanticSimilarityInitialized = true;
|
|
115
113
|
semanticSimilarityAvailable = true;
|
|
116
|
-
|
|
114
|
+
verboseLog({}, chalk.green('[FeedbackLoader] Semantic similarity initialized using embeddings system'));
|
|
117
115
|
} catch (error) {
|
|
118
|
-
console.
|
|
116
|
+
console.warn(chalk.yellow(`[FeedbackLoader] Semantic similarity initialization failed: ${error.message}`));
|
|
119
117
|
semanticSimilarityAvailable = false;
|
|
120
118
|
}
|
|
121
119
|
}
|
|
@@ -157,7 +155,7 @@ async function calculateSemanticSimilarity(text1, text2) {
|
|
|
157
155
|
// Cosine similarity ranges from -1 to 1, normalize to 0-1
|
|
158
156
|
return (similarity + 1) / 2;
|
|
159
157
|
} catch (error) {
|
|
160
|
-
console.
|
|
158
|
+
console.warn(chalk.yellow(`[FeedbackLoader] Semantic similarity calculation failed: ${error.message}`));
|
|
161
159
|
return null;
|
|
162
160
|
}
|
|
163
161
|
}
|
|
@@ -173,9 +171,9 @@ async function calculateSemanticSimilarity(text1, text2) {
|
|
|
173
171
|
* @param {string} issueDescription - Description of the current issue
|
|
174
172
|
* @param {Object} feedbackData - Loaded feedback data
|
|
175
173
|
* @param {Object} options - Filtering options
|
|
176
|
-
* @param {number} options.similarityThreshold - Threshold for considering issues similar
|
|
177
|
-
* @param {boolean} options.verbose - Enable verbose logging
|
|
178
|
-
* @param {boolean} options.useSemanticSimilarity - Use semantic similarity when available
|
|
174
|
+
* @param {number} [options.similarityThreshold=0.7] - Threshold for considering issues similar
|
|
175
|
+
* @param {boolean} [options.verbose=false] - Enable verbose progress logging
|
|
176
|
+
* @param {boolean} [options.useSemanticSimilarity=true] - Use semantic similarity when available
|
|
179
177
|
* @returns {Promise<boolean>} True if issue should be skipped
|
|
180
178
|
*/
|
|
181
179
|
export async function shouldSkipSimilarIssue(issueDescription, feedbackData, options = {}) {
|
|
@@ -205,9 +203,7 @@ export async function shouldSkipSimilarIssue(issueDescription, feedbackData, opt
|
|
|
205
203
|
// Determine if we should use semantic similarity
|
|
206
204
|
const canUseSemanticSimilarity = useSemanticSimilarity && isSemanticSimilarityAvailable();
|
|
207
205
|
|
|
208
|
-
|
|
209
|
-
console.log(chalk.cyan('🔍 Using semantic similarity for issue comparison'));
|
|
210
|
-
}
|
|
206
|
+
verboseLog(verbose && canUseSemanticSimilarity, chalk.cyan('🔍 Using semantic similarity for issue comparison'));
|
|
211
207
|
|
|
212
208
|
// Check similarity with dismissed issues
|
|
213
209
|
for (const dismissed of dismissedIssues) {
|
|
@@ -233,11 +229,12 @@ export async function shouldSkipSimilarIssue(issueDescription, feedbackData, opt
|
|
|
233
229
|
}
|
|
234
230
|
|
|
235
231
|
if (similarity > similarityThreshold) {
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
}
|
|
232
|
+
verboseLog(
|
|
233
|
+
verbose,
|
|
234
|
+
chalk.yellow(`⏭️ Skipping similar dismissed issue (${(similarity * 100).toFixed(1)}% ${similarityMethod} similarity)`)
|
|
235
|
+
);
|
|
236
|
+
verboseLog(verbose, chalk.gray(` Current: ${issueDescription.substring(0, 80)}...`));
|
|
237
|
+
verboseLog(verbose, chalk.gray(` Previous: ${dismissed.originalIssue.substring(0, 80)}...`));
|
|
241
238
|
return true;
|
|
242
239
|
}
|
|
243
240
|
}
|
|
@@ -252,7 +249,7 @@ export async function shouldSkipSimilarIssue(issueDescription, feedbackData, opt
|
|
|
252
249
|
* @param {string} text1 - First text
|
|
253
250
|
* @param {string} text2 - Second text
|
|
254
251
|
* @param {Object} options - Options
|
|
255
|
-
* @param {boolean} options.useSemanticSimilarity - Use semantic similarity when available
|
|
252
|
+
* @param {boolean} [options.useSemanticSimilarity=true] - Use semantic similarity when available
|
|
256
253
|
* @returns {Promise<{similarity: number, method: string}>} Similarity result with method used
|
|
257
254
|
*/
|
|
258
255
|
export async function calculateIssueSimilarity(text1, text2, options = {}) {
|
|
@@ -327,6 +324,8 @@ export function calculateWordSimilarity(text1, text2) {
|
|
|
327
324
|
*
|
|
328
325
|
* @param {Object} feedbackData - Loaded feedback data
|
|
329
326
|
* @param {Object} options - Extraction options
|
|
327
|
+
* @param {number} [options.maxPatterns=10] - Maximum number of dismissed patterns to include
|
|
328
|
+
* @param {boolean} [options.verbose=false] - Enable verbose progress logging
|
|
330
329
|
* @returns {Array} Array of dismissed issue patterns
|
|
331
330
|
*/
|
|
332
331
|
export function extractDismissedPatterns(feedbackData, options = {}) {
|
|
@@ -355,9 +354,10 @@ export function extractDismissedPatterns(feedbackData, options = {}) {
|
|
|
355
354
|
}))
|
|
356
355
|
.slice(0, maxPatterns);
|
|
357
356
|
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
357
|
+
verboseLog(
|
|
358
|
+
verbose && dismissedIssues.length > 0,
|
|
359
|
+
chalk.cyan(`📋 Extracted ${dismissedIssues.length} dismissed issue patterns for LLM context`)
|
|
360
|
+
);
|
|
361
361
|
|
|
362
362
|
return dismissedIssues;
|
|
363
363
|
}
|
package/src/index.js
CHANGED
|
@@ -30,6 +30,7 @@ import { ProjectAnalyzer } from './project-analyzer.js';
|
|
|
30
30
|
import { reviewFile, reviewFiles, reviewPullRequest } from './rag-review.js';
|
|
31
31
|
import { execGitSafe } from './utils/command.js';
|
|
32
32
|
import { ensureBranchExists, findBaseBranch } from './utils/git.js';
|
|
33
|
+
import { verboseLog } from './utils/logging.js';
|
|
33
34
|
|
|
34
35
|
// Create a default embeddings system instance
|
|
35
36
|
const embeddingsSystem = getDefaultEmbeddingsSystem();
|
|
@@ -406,9 +407,7 @@ async function runCodeReview(options) {
|
|
|
406
407
|
const endTime = Date.now();
|
|
407
408
|
const duration = ((endTime - startTime) / 1000).toFixed(2);
|
|
408
409
|
|
|
409
|
-
|
|
410
|
-
console.log(chalk.blue(`Review process took ${duration} seconds.`));
|
|
411
|
-
}
|
|
410
|
+
verboseLog(options, chalk.blue(`Review process took ${duration} seconds.`));
|
|
412
411
|
|
|
413
412
|
// Process and output results
|
|
414
413
|
if (reviewResult && reviewResult.success) {
|
|
@@ -520,6 +519,7 @@ async function generateEmbeddings(options) {
|
|
|
520
519
|
|
|
521
520
|
// Get files to process
|
|
522
521
|
let filesToProcess = [];
|
|
522
|
+
const runMode = options.files && options.files.length > 0 ? 'partial' : 'full';
|
|
523
523
|
|
|
524
524
|
if (options.files && options.files.length > 0) {
|
|
525
525
|
console.log(chalk.cyan('Processing specified files/patterns...'));
|
|
@@ -586,6 +586,7 @@ async function generateEmbeddings(options) {
|
|
|
586
586
|
baseDir: baseDir,
|
|
587
587
|
batchSize: 100, // Set a reasonable batch size
|
|
588
588
|
maxLines: parseInt(options.maxLines || '1000', 10),
|
|
589
|
+
runMode,
|
|
589
590
|
onProgress: (status) => {
|
|
590
591
|
// Update counters based on status
|
|
591
592
|
if (status === 'processed') {
|
|
@@ -626,19 +627,15 @@ async function generateEmbeddings(options) {
|
|
|
626
627
|
forceAnalysis: options.forceAnalysis,
|
|
627
628
|
});
|
|
628
629
|
|
|
629
|
-
// Store project summary in embeddings system for later use
|
|
630
|
-
await embeddingsSystem.storeProjectSummary(projectDir, projectSummary);
|
|
631
|
-
|
|
632
630
|
console.log(chalk.green('✅ Project analysis complete and stored'));
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
}
|
|
631
|
+
verboseLog(options, chalk.gray(` Project: ${projectSummary.projectName}`));
|
|
632
|
+
verboseLog(
|
|
633
|
+
options,
|
|
634
|
+
chalk.gray(
|
|
635
|
+
` Technologies: ${projectSummary.technologies.slice(0, 5).join(', ')}${projectSummary.technologies.length > 5 ? '...' : ''}`
|
|
636
|
+
)
|
|
637
|
+
);
|
|
638
|
+
verboseLog(options, chalk.gray(` Key patterns: ${projectSummary.keyPatterns.length}`));
|
|
642
639
|
} catch (error) {
|
|
643
640
|
console.error(chalk.red('⚠️ Project analysis failed but continuing:'), error.message);
|
|
644
641
|
}
|
|
@@ -756,10 +753,12 @@ async function showEmbeddingStats(options) {
|
|
|
756
753
|
*
|
|
757
754
|
* @param {string} directory - Directory to search
|
|
758
755
|
* @param {object} options - Options from generateEmbeddings command
|
|
756
|
+
* @param {boolean} [options.verbose=false] - Enable verbose glob and filtering logs
|
|
757
|
+
* @param {string} [options.filePattern] - Optional override pattern instead of the default supported-file patterns
|
|
758
|
+
* @param {string[]} [options.excludePatterns] - Additional glob exclusion patterns
|
|
759
759
|
* @returns {Promise<Array<string>>} Array of file paths
|
|
760
760
|
*/
|
|
761
761
|
async function findSupportedFiles(directory, options = {}) {
|
|
762
|
-
const verbose = options.verbose || false;
|
|
763
762
|
const baseDir = path.resolve(directory);
|
|
764
763
|
|
|
765
764
|
// Default patterns match common code files - adjust as needed
|
|
@@ -835,19 +834,15 @@ async function findSupportedFiles(directory, options = {}) {
|
|
|
835
834
|
// Instead, we rely on the shouldProcessFile check in embeddings.js which uses git check-ignore
|
|
836
835
|
globOptions.ignore = [...excludePatterns]; // Use only explicit excludes
|
|
837
836
|
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
console.log(chalk.gray(` Options:`), globOptions);
|
|
842
|
-
}
|
|
837
|
+
verboseLog(options, chalk.cyan('Using async glob to find files...'));
|
|
838
|
+
verboseLog(options, chalk.gray(` Patterns: ${patternsToUse.join(', ')}`));
|
|
839
|
+
verboseLog(options, chalk.gray(` Options:`), globOptions);
|
|
843
840
|
|
|
844
841
|
try {
|
|
845
842
|
// Use asynchronous glob
|
|
846
843
|
const files = await glob.glob(patternsToUse, globOptions);
|
|
847
844
|
|
|
848
|
-
|
|
849
|
-
console.log(chalk.green(`Glob found ${files.length} potential files.`));
|
|
850
|
-
}
|
|
845
|
+
verboseLog(options, chalk.green(`Glob found ${files.length} potential files.`));
|
|
851
846
|
|
|
852
847
|
// Filter results to ensure they are actual files (glob with stat should mostly handle this)
|
|
853
848
|
// And apply the final utilsShouldProcessFile check (e.g., for binary content if needed)
|
|
@@ -864,9 +859,7 @@ async function findSupportedFiles(directory, options = {}) {
|
|
|
864
859
|
// finalFiles.push(file);
|
|
865
860
|
// }
|
|
866
861
|
// } catch (statError) {
|
|
867
|
-
//
|
|
868
|
-
// console.warn(chalk.yellow(`Skipping file due to stat error ${path.relative(baseDir, file)}: ${statError.message}`));
|
|
869
|
-
// }
|
|
862
|
+
// console.warn(chalk.yellow(`Skipping file due to stat error ${path.relative(baseDir, file)}: ${statError.message}`));
|
|
870
863
|
// }
|
|
871
864
|
// }
|
|
872
865
|
|
|
@@ -874,9 +867,7 @@ async function findSupportedFiles(directory, options = {}) {
|
|
|
874
867
|
const finalFiles = files;
|
|
875
868
|
|
|
876
869
|
// Add log after the filtering loop (now just assignment)
|
|
877
|
-
|
|
878
|
-
console.log(chalk.green(`Finished filtering glob results. ${finalFiles.length} files remain.`));
|
|
879
|
-
}
|
|
870
|
+
verboseLog(options, chalk.green(`Finished filtering glob results. ${finalFiles.length} files remain.`));
|
|
880
871
|
return finalFiles;
|
|
881
872
|
} catch (err) {
|
|
882
873
|
if (err.name === 'AbortError') {
|
|
@@ -981,13 +972,13 @@ function getChangedFiles(branch, workingDir = process.cwd()) {
|
|
|
981
972
|
// REMOVED: checkBranchExists function - Moved to utils.js
|
|
982
973
|
|
|
983
974
|
// --- Output Formatting Functions --- //
|
|
984
|
-
// These
|
|
975
|
+
// These consume the normalized results returned by the RAG review pipeline
|
|
985
976
|
|
|
986
977
|
/**
|
|
987
978
|
* Output results in JSON format
|
|
988
979
|
*
|
|
989
|
-
* @param {Array<Object>} reviewResults - Array of individual file review results
|
|
990
|
-
* @param {Object}
|
|
980
|
+
* @param {Array<Object>} reviewResults - Array of individual file review results
|
|
981
|
+
* @param {Object} options - Command line options
|
|
991
982
|
*/
|
|
992
983
|
function outputJson(reviewResults, options) {
|
|
993
984
|
// Structure the output to be informative
|
|
@@ -1015,7 +1006,7 @@ function outputJson(reviewResults, options) {
|
|
|
1015
1006
|
filePath: r.filePath,
|
|
1016
1007
|
success: true,
|
|
1017
1008
|
language: r.language,
|
|
1018
|
-
review: r.results, // Contains summary
|
|
1009
|
+
review: r.results, // Contains summary and actionable issues (with optional codeSuggestion)
|
|
1019
1010
|
// Optionally include similar examples if needed
|
|
1020
1011
|
// similarExamplesUsed: r.similarExamples
|
|
1021
1012
|
};
|
|
@@ -1038,86 +1029,86 @@ function outputJson(reviewResults, options) {
|
|
|
1038
1029
|
* Output results in Markdown format
|
|
1039
1030
|
*
|
|
1040
1031
|
* @param {Array<Object>} reviewResults - Array of individual file review results
|
|
1041
|
-
* @param {Object}
|
|
1032
|
+
* @param {Object} options - Command line options
|
|
1042
1033
|
*/
|
|
1043
|
-
function outputMarkdown(reviewResults) {
|
|
1044
|
-
console.log('# AI Code Review Results (RAG Approach)\n');
|
|
1045
|
-
|
|
1034
|
+
function outputMarkdown(reviewResults, options) {
|
|
1046
1035
|
const totalFiles = reviewResults.length;
|
|
1047
1036
|
const filesWithIssues = reviewResults.filter((r) => r.success && !r.skipped && r.results?.issues?.length > 0).length;
|
|
1048
1037
|
const totalIssues = reviewResults.reduce((sum, r) => sum + (r.results?.issues?.length || 0), 0);
|
|
1049
1038
|
const skippedFiles = reviewResults.filter((r) => r.skipped).length;
|
|
1050
1039
|
const errorFiles = reviewResults.filter((r) => !r.success).length;
|
|
1051
1040
|
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1041
|
+
const lines = [
|
|
1042
|
+
'# AI Code Review Results (RAG Approach)',
|
|
1043
|
+
'',
|
|
1044
|
+
'## Summary',
|
|
1045
|
+
'',
|
|
1046
|
+
`- **Files Analyzed:** ${totalFiles}`,
|
|
1047
|
+
`- **Files with Issues:** ${filesWithIssues}`,
|
|
1048
|
+
`- **Total Issues Found:** ${totalIssues}`,
|
|
1049
|
+
];
|
|
1050
|
+
|
|
1051
|
+
if (skippedFiles > 0) lines.push(`- **Files Skipped:** ${skippedFiles}`);
|
|
1052
|
+
if (errorFiles > 0) lines.push(`- **Errors:** ${errorFiles}`);
|
|
1059
1053
|
|
|
1060
|
-
|
|
1054
|
+
lines.push('', '## Detailed Review per File', '');
|
|
1061
1055
|
|
|
1062
1056
|
reviewResults.forEach((fileResult) => {
|
|
1063
|
-
|
|
1057
|
+
lines.push(`### ${fileResult.filePath}`, '');
|
|
1064
1058
|
if (!fileResult.success) {
|
|
1065
|
-
|
|
1059
|
+
lines.push(`**Error:** ${fileResult.error}`, '');
|
|
1066
1060
|
return;
|
|
1067
1061
|
}
|
|
1068
1062
|
if (fileResult.skipped) {
|
|
1069
|
-
|
|
1063
|
+
lines.push('*Skipped (based on exclusion patterns or file type).*', '');
|
|
1070
1064
|
return;
|
|
1071
1065
|
}
|
|
1072
|
-
if (!fileResult.results ||
|
|
1073
|
-
|
|
1066
|
+
if (!fileResult.results || !fileResult.results.issues?.length) {
|
|
1067
|
+
lines.push('*No actionable issues reported.*', '');
|
|
1074
1068
|
if (fileResult.results?.summary) {
|
|
1075
|
-
|
|
1069
|
+
lines.push(`**Summary:** ${fileResult.results.summary}`, '');
|
|
1076
1070
|
}
|
|
1077
1071
|
return;
|
|
1078
1072
|
}
|
|
1079
1073
|
|
|
1080
1074
|
const review = fileResult.results;
|
|
1081
1075
|
if (review.summary) {
|
|
1082
|
-
|
|
1076
|
+
lines.push(`**Summary:** ${review.summary}`, '');
|
|
1083
1077
|
}
|
|
1084
1078
|
|
|
1085
1079
|
if (review.issues && review.issues.length > 0) {
|
|
1086
|
-
|
|
1080
|
+
lines.push(`**Issues Found (${review.issues.length}):**`, '');
|
|
1087
1081
|
review.issues.forEach((issue) => {
|
|
1088
1082
|
const severityEmoji = getSeverityEmoji(issue.severity);
|
|
1089
|
-
|
|
1083
|
+
lines.push(
|
|
1090
1084
|
`- **[${issue.severity.toUpperCase()}] ${severityEmoji} (Lines: ${issue.lineNumbers?.join(', ') || 'N/A'})**: ${
|
|
1091
1085
|
issue.description
|
|
1092
1086
|
}`
|
|
1093
1087
|
);
|
|
1094
1088
|
if (issue.suggestion) {
|
|
1095
|
-
|
|
1089
|
+
lines.push('', ` *Suggestion:* ${issue.suggestion}`, '');
|
|
1096
1090
|
}
|
|
1097
1091
|
// Include code suggestion if available
|
|
1098
1092
|
if (issue.codeSuggestion) {
|
|
1099
1093
|
const { startLine, endLine, newCode } = issue.codeSuggestion;
|
|
1100
1094
|
const lineRange = endLine ? `${startLine}-${endLine}` : `${startLine}`;
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
newCode
|
|
1105
|
-
.split('\n')
|
|
1106
|
-
.map((line) => ` ${line}`)
|
|
1107
|
-
.join('\n')
|
|
1108
|
-
);
|
|
1109
|
-
console.log(' ```\n');
|
|
1095
|
+
lines.push('', ` **Suggested change (lines ${lineRange}):**`, '', ' ```suggestion');
|
|
1096
|
+
lines.push(...newCode.split('\n').map((line) => ` ${line}`));
|
|
1097
|
+
lines.push(' ```', '');
|
|
1110
1098
|
}
|
|
1111
1099
|
});
|
|
1112
1100
|
}
|
|
1113
|
-
|
|
1114
|
-
if (review.positives && review.positives.length > 0) {
|
|
1115
|
-
console.log(`**Positives Found (${review.positives.length}):**\n`);
|
|
1116
|
-
review.positives.forEach((positive) => {
|
|
1117
|
-
console.log(` - ${positive}\n`);
|
|
1118
|
-
});
|
|
1119
|
-
}
|
|
1120
1101
|
});
|
|
1102
|
+
|
|
1103
|
+
const markdownOutput = `${lines.join('\n')}\n`;
|
|
1104
|
+
|
|
1105
|
+
if (options?.outputFile) {
|
|
1106
|
+
fs.writeFileSync(options.outputFile, markdownOutput, 'utf8');
|
|
1107
|
+
console.log(chalk.green(`Markdown output saved to: ${options.outputFile}`));
|
|
1108
|
+
return;
|
|
1109
|
+
}
|
|
1110
|
+
|
|
1111
|
+
process.stdout.write(markdownOutput);
|
|
1121
1112
|
}
|
|
1122
1113
|
|
|
1123
1114
|
/**
|
|
@@ -1149,17 +1140,13 @@ function outputText(reviewResults, cliOptions) {
|
|
|
1149
1140
|
return;
|
|
1150
1141
|
}
|
|
1151
1142
|
if (fileResult.skipped) {
|
|
1152
|
-
|
|
1153
|
-
console.log(chalk.yellow(`\nSkipped: ${fileResult.filePath}`));
|
|
1154
|
-
}
|
|
1143
|
+
verboseLog(cliOptions, chalk.yellow(`\nSkipped: ${fileResult.filePath}`));
|
|
1155
1144
|
return;
|
|
1156
1145
|
}
|
|
1157
|
-
if (!fileResult.results ||
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
console.log(chalk.green(` Summary: ${fileResult.results.summary}`));
|
|
1162
|
-
}
|
|
1146
|
+
if (!fileResult.results || !fileResult.results.issues?.length) {
|
|
1147
|
+
verboseLog(cliOptions, chalk.green(`\nNo findings for: ${fileResult.filePath}`));
|
|
1148
|
+
if (fileResult.results?.summary) {
|
|
1149
|
+
verboseLog(cliOptions, chalk.green(` Summary: ${fileResult.results.summary}`));
|
|
1163
1150
|
}
|
|
1164
1151
|
return;
|
|
1165
1152
|
}
|
|
@@ -1194,13 +1181,6 @@ function outputText(reviewResults, cliOptions) {
|
|
|
1194
1181
|
});
|
|
1195
1182
|
}
|
|
1196
1183
|
|
|
1197
|
-
if (review.positives && review.positives.length > 0) {
|
|
1198
|
-
console.log(chalk.bold.green('\nPositives:'));
|
|
1199
|
-
review.positives.forEach((positive) => {
|
|
1200
|
-
console.log(` - ${positive}`);
|
|
1201
|
-
});
|
|
1202
|
-
console.log('');
|
|
1203
|
-
}
|
|
1204
1184
|
console.log(chalk.gray(`========================================${'='.repeat(fileResult.filePath.length)}`));
|
|
1205
1185
|
});
|
|
1206
1186
|
}
|
|
@@ -1294,6 +1274,7 @@ async function analyzePRHistory(options) {
|
|
|
1294
1274
|
resume: options.resume,
|
|
1295
1275
|
clearExisting: options.clear,
|
|
1296
1276
|
projectPath,
|
|
1277
|
+
verbose: options.verbose,
|
|
1297
1278
|
onProgress: (progress) => displayProgress(progress, options.verbose),
|
|
1298
1279
|
};
|
|
1299
1280
|
|
|
@@ -1312,9 +1293,7 @@ async function analyzePRHistory(options) {
|
|
|
1312
1293
|
const endTime = Date.now();
|
|
1313
1294
|
const duration = ((endTime - startTime) / 1000).toFixed(2);
|
|
1314
1295
|
console.error(chalk.red(`\nError during PR history analysis (${duration}s):`), error.message);
|
|
1315
|
-
|
|
1316
|
-
console.error(error.stack);
|
|
1317
|
-
}
|
|
1296
|
+
verboseLog(options, error.stack);
|
|
1318
1297
|
process.exit(1);
|
|
1319
1298
|
}
|
|
1320
1299
|
}
|
|
@@ -1413,9 +1392,7 @@ async function clearPRHistory(options) {
|
|
|
1413
1392
|
}
|
|
1414
1393
|
} catch (error) {
|
|
1415
1394
|
console.error(chalk.red('Error clearing PR history data:'), error.message);
|
|
1416
|
-
|
|
1417
|
-
console.error(error.stack);
|
|
1418
|
-
}
|
|
1395
|
+
verboseLog(options, error.stack);
|
|
1419
1396
|
process.exit(1);
|
|
1420
1397
|
}
|
|
1421
1398
|
}
|
package/src/llm.js
CHANGED
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
import { Anthropic } from '@anthropic-ai/sdk';
|
|
16
16
|
import chalk from 'chalk';
|
|
17
17
|
import dotenv from 'dotenv';
|
|
18
|
+
import { verboseLog } from './utils/logging.js';
|
|
18
19
|
|
|
19
20
|
// Load env variables if present; do not enforce key at import time
|
|
20
21
|
dotenv.config();
|
|
@@ -56,7 +57,7 @@ async function sendPromptToClaude(prompt, options = {}) {
|
|
|
56
57
|
const { model = DEFAULT_MODEL, maxTokens = MAX_TOKENS, temperature = 0.7, system = '', jsonSchema = null, cacheTtl = '5m' } = options;
|
|
57
58
|
|
|
58
59
|
try {
|
|
59
|
-
|
|
60
|
+
verboseLog(options, chalk.cyan('Sending prompt to Claude...'));
|
|
60
61
|
|
|
61
62
|
const client = getAnthropicClient();
|
|
62
63
|
|
|
@@ -104,8 +105,8 @@ async function sendPromptToClaude(prompt, options = {}) {
|
|
|
104
105
|
const response = await client.messages.create(requestParams);
|
|
105
106
|
|
|
106
107
|
// Log response structure for debugging
|
|
107
|
-
|
|
108
|
-
|
|
108
|
+
verboseLog(options, chalk.gray(` Response stop_reason: ${response.stop_reason}`));
|
|
109
|
+
verboseLog(options, chalk.gray(` Response content blocks: ${response.content?.length || 0}`));
|
|
109
110
|
|
|
110
111
|
// Process response based on whether we used tool calling
|
|
111
112
|
if (jsonSchema) {
|