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.
- package/package.json +1 -1
- package/src/content-retrieval.js +20 -16
- package/src/custom-documents.js +19 -19
- package/src/custom-documents.test.js +11 -9
- package/src/feedback-loader.js +31 -31
- package/src/index.js +99 -119
- package/src/llm.js +4 -3
- package/src/project-analyzer.js +32 -39
- package/src/project-analyzer.test.js +3 -5
- package/src/prompt-cache.js +1 -0
- package/src/rag-analyzer.js +199 -178
- package/src/rag-analyzer.test.js +57 -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/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) {
|
|
@@ -576,35 +575,37 @@ async function generateEmbeddings(options) {
|
|
|
576
575
|
|
|
577
576
|
// Start the progress update interval
|
|
578
577
|
const progressInterval = setInterval(updateSpinner, 100);
|
|
578
|
+
let results;
|
|
579
|
+
try {
|
|
580
|
+
results = await embeddingsSystem.processBatchEmbeddings(filesToProcess, {
|
|
581
|
+
concurrency,
|
|
582
|
+
verbose: options.verbose,
|
|
583
|
+
excludePatterns,
|
|
584
|
+
respectGitignore: options.gitignore !== false,
|
|
585
|
+
baseDir: baseDir,
|
|
586
|
+
batchSize: 100, // Set a reasonable batch size
|
|
587
|
+
maxLines: parseInt(options.maxLines || '1000', 10),
|
|
588
|
+
onProgress: (status) => {
|
|
589
|
+
// Update counters based on status
|
|
590
|
+
if (status === 'processed') {
|
|
591
|
+
processedCount++;
|
|
592
|
+
} else if (status === 'skipped') {
|
|
593
|
+
skippedCount++;
|
|
594
|
+
} else if (status === 'failed') {
|
|
595
|
+
failedCount++;
|
|
596
|
+
} else if (status === 'excluded') {
|
|
597
|
+
excludedCount++;
|
|
598
|
+
}
|
|
579
599
|
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
// Update counters based on status
|
|
590
|
-
if (status === 'processed') {
|
|
591
|
-
processedCount++;
|
|
592
|
-
} else if (status === 'skipped') {
|
|
593
|
-
skippedCount++;
|
|
594
|
-
} else if (status === 'failed') {
|
|
595
|
-
failedCount++;
|
|
596
|
-
} else if (status === 'excluded') {
|
|
597
|
-
excludedCount++;
|
|
598
|
-
}
|
|
599
|
-
|
|
600
|
-
// Update the spinner with new progress information
|
|
601
|
-
updateSpinner();
|
|
602
|
-
},
|
|
603
|
-
});
|
|
604
|
-
|
|
605
|
-
// Clean up the progress display
|
|
606
|
-
clearInterval(progressInterval);
|
|
607
|
-
spinner.stop(true);
|
|
600
|
+
// Update the spinner with new progress information
|
|
601
|
+
updateSpinner();
|
|
602
|
+
},
|
|
603
|
+
});
|
|
604
|
+
} finally {
|
|
605
|
+
// Clean up the progress display even if embedding generation fails.
|
|
606
|
+
clearInterval(progressInterval);
|
|
607
|
+
spinner.stop(true);
|
|
608
|
+
}
|
|
608
609
|
|
|
609
610
|
console.log(chalk.green(`\nEmbedding generation complete!`));
|
|
610
611
|
console.log(chalk.cyan(`Processed: ${results.processed} files`));
|
|
@@ -628,15 +629,14 @@ async function generateEmbeddings(options) {
|
|
|
628
629
|
await embeddingsSystem.storeProjectSummary(projectDir, projectSummary);
|
|
629
630
|
|
|
630
631
|
console.log(chalk.green('✅ Project analysis complete and stored'));
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
}
|
|
632
|
+
verboseLog(options, chalk.gray(` Project: ${projectSummary.projectName}`));
|
|
633
|
+
verboseLog(
|
|
634
|
+
options,
|
|
635
|
+
chalk.gray(
|
|
636
|
+
` Technologies: ${projectSummary.technologies.slice(0, 5).join(', ')}${projectSummary.technologies.length > 5 ? '...' : ''}`
|
|
637
|
+
)
|
|
638
|
+
);
|
|
639
|
+
verboseLog(options, chalk.gray(` Key patterns: ${projectSummary.keyPatterns.length}`));
|
|
640
640
|
} catch (error) {
|
|
641
641
|
console.error(chalk.red('⚠️ Project analysis failed but continuing:'), error.message);
|
|
642
642
|
}
|
|
@@ -754,10 +754,12 @@ async function showEmbeddingStats(options) {
|
|
|
754
754
|
*
|
|
755
755
|
* @param {string} directory - Directory to search
|
|
756
756
|
* @param {object} options - Options from generateEmbeddings command
|
|
757
|
+
* @param {boolean} [options.verbose=false] - Enable verbose glob and filtering logs
|
|
758
|
+
* @param {string} [options.filePattern] - Optional override pattern instead of the default supported-file patterns
|
|
759
|
+
* @param {string[]} [options.excludePatterns] - Additional glob exclusion patterns
|
|
757
760
|
* @returns {Promise<Array<string>>} Array of file paths
|
|
758
761
|
*/
|
|
759
762
|
async function findSupportedFiles(directory, options = {}) {
|
|
760
|
-
const verbose = options.verbose || false;
|
|
761
763
|
const baseDir = path.resolve(directory);
|
|
762
764
|
|
|
763
765
|
// Default patterns match common code files - adjust as needed
|
|
@@ -833,19 +835,15 @@ async function findSupportedFiles(directory, options = {}) {
|
|
|
833
835
|
// Instead, we rely on the shouldProcessFile check in embeddings.js which uses git check-ignore
|
|
834
836
|
globOptions.ignore = [...excludePatterns]; // Use only explicit excludes
|
|
835
837
|
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
console.log(chalk.gray(` Options:`), globOptions);
|
|
840
|
-
}
|
|
838
|
+
verboseLog(options, chalk.cyan('Using async glob to find files...'));
|
|
839
|
+
verboseLog(options, chalk.gray(` Patterns: ${patternsToUse.join(', ')}`));
|
|
840
|
+
verboseLog(options, chalk.gray(` Options:`), globOptions);
|
|
841
841
|
|
|
842
842
|
try {
|
|
843
843
|
// Use asynchronous glob
|
|
844
844
|
const files = await glob.glob(patternsToUse, globOptions);
|
|
845
845
|
|
|
846
|
-
|
|
847
|
-
console.log(chalk.green(`Glob found ${files.length} potential files.`));
|
|
848
|
-
}
|
|
846
|
+
verboseLog(options, chalk.green(`Glob found ${files.length} potential files.`));
|
|
849
847
|
|
|
850
848
|
// Filter results to ensure they are actual files (glob with stat should mostly handle this)
|
|
851
849
|
// And apply the final utilsShouldProcessFile check (e.g., for binary content if needed)
|
|
@@ -862,9 +860,7 @@ async function findSupportedFiles(directory, options = {}) {
|
|
|
862
860
|
// finalFiles.push(file);
|
|
863
861
|
// }
|
|
864
862
|
// } catch (statError) {
|
|
865
|
-
//
|
|
866
|
-
// console.warn(chalk.yellow(`Skipping file due to stat error ${path.relative(baseDir, file)}: ${statError.message}`));
|
|
867
|
-
// }
|
|
863
|
+
// console.warn(chalk.yellow(`Skipping file due to stat error ${path.relative(baseDir, file)}: ${statError.message}`));
|
|
868
864
|
// }
|
|
869
865
|
// }
|
|
870
866
|
|
|
@@ -872,9 +868,7 @@ async function findSupportedFiles(directory, options = {}) {
|
|
|
872
868
|
const finalFiles = files;
|
|
873
869
|
|
|
874
870
|
// Add log after the filtering loop (now just assignment)
|
|
875
|
-
|
|
876
|
-
console.log(chalk.green(`Finished filtering glob results. ${finalFiles.length} files remain.`));
|
|
877
|
-
}
|
|
871
|
+
verboseLog(options, chalk.green(`Finished filtering glob results. ${finalFiles.length} files remain.`));
|
|
878
872
|
return finalFiles;
|
|
879
873
|
} catch (err) {
|
|
880
874
|
if (err.name === 'AbortError') {
|
|
@@ -979,13 +973,13 @@ function getChangedFiles(branch, workingDir = process.cwd()) {
|
|
|
979
973
|
// REMOVED: checkBranchExists function - Moved to utils.js
|
|
980
974
|
|
|
981
975
|
// --- Output Formatting Functions --- //
|
|
982
|
-
// These
|
|
976
|
+
// These consume the normalized results returned by the RAG review pipeline
|
|
983
977
|
|
|
984
978
|
/**
|
|
985
979
|
* Output results in JSON format
|
|
986
980
|
*
|
|
987
|
-
* @param {Array<Object>} reviewResults - Array of individual file review results
|
|
988
|
-
* @param {Object}
|
|
981
|
+
* @param {Array<Object>} reviewResults - Array of individual file review results
|
|
982
|
+
* @param {Object} options - Command line options
|
|
989
983
|
*/
|
|
990
984
|
function outputJson(reviewResults, options) {
|
|
991
985
|
// Structure the output to be informative
|
|
@@ -1013,7 +1007,7 @@ function outputJson(reviewResults, options) {
|
|
|
1013
1007
|
filePath: r.filePath,
|
|
1014
1008
|
success: true,
|
|
1015
1009
|
language: r.language,
|
|
1016
|
-
review: r.results, // Contains summary
|
|
1010
|
+
review: r.results, // Contains summary and actionable issues (with optional codeSuggestion)
|
|
1017
1011
|
// Optionally include similar examples if needed
|
|
1018
1012
|
// similarExamplesUsed: r.similarExamples
|
|
1019
1013
|
};
|
|
@@ -1036,86 +1030,86 @@ function outputJson(reviewResults, options) {
|
|
|
1036
1030
|
* Output results in Markdown format
|
|
1037
1031
|
*
|
|
1038
1032
|
* @param {Array<Object>} reviewResults - Array of individual file review results
|
|
1039
|
-
* @param {Object}
|
|
1033
|
+
* @param {Object} options - Command line options
|
|
1040
1034
|
*/
|
|
1041
|
-
function outputMarkdown(reviewResults) {
|
|
1042
|
-
console.log('# AI Code Review Results (RAG Approach)\n');
|
|
1043
|
-
|
|
1035
|
+
function outputMarkdown(reviewResults, options) {
|
|
1044
1036
|
const totalFiles = reviewResults.length;
|
|
1045
1037
|
const filesWithIssues = reviewResults.filter((r) => r.success && !r.skipped && r.results?.issues?.length > 0).length;
|
|
1046
1038
|
const totalIssues = reviewResults.reduce((sum, r) => sum + (r.results?.issues?.length || 0), 0);
|
|
1047
1039
|
const skippedFiles = reviewResults.filter((r) => r.skipped).length;
|
|
1048
1040
|
const errorFiles = reviewResults.filter((r) => !r.success).length;
|
|
1049
1041
|
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1042
|
+
const lines = [
|
|
1043
|
+
'# AI Code Review Results (RAG Approach)',
|
|
1044
|
+
'',
|
|
1045
|
+
'## Summary',
|
|
1046
|
+
'',
|
|
1047
|
+
`- **Files Analyzed:** ${totalFiles}`,
|
|
1048
|
+
`- **Files with Issues:** ${filesWithIssues}`,
|
|
1049
|
+
`- **Total Issues Found:** ${totalIssues}`,
|
|
1050
|
+
];
|
|
1057
1051
|
|
|
1058
|
-
|
|
1052
|
+
if (skippedFiles > 0) lines.push(`- **Files Skipped:** ${skippedFiles}`);
|
|
1053
|
+
if (errorFiles > 0) lines.push(`- **Errors:** ${errorFiles}`);
|
|
1054
|
+
|
|
1055
|
+
lines.push('', '## Detailed Review per File', '');
|
|
1059
1056
|
|
|
1060
1057
|
reviewResults.forEach((fileResult) => {
|
|
1061
|
-
|
|
1058
|
+
lines.push(`### ${fileResult.filePath}`, '');
|
|
1062
1059
|
if (!fileResult.success) {
|
|
1063
|
-
|
|
1060
|
+
lines.push(`**Error:** ${fileResult.error}`, '');
|
|
1064
1061
|
return;
|
|
1065
1062
|
}
|
|
1066
1063
|
if (fileResult.skipped) {
|
|
1067
|
-
|
|
1064
|
+
lines.push('*Skipped (based on exclusion patterns or file type).*', '');
|
|
1068
1065
|
return;
|
|
1069
1066
|
}
|
|
1070
|
-
if (!fileResult.results ||
|
|
1071
|
-
|
|
1067
|
+
if (!fileResult.results || !fileResult.results.issues?.length) {
|
|
1068
|
+
lines.push('*No actionable issues reported.*', '');
|
|
1072
1069
|
if (fileResult.results?.summary) {
|
|
1073
|
-
|
|
1070
|
+
lines.push(`**Summary:** ${fileResult.results.summary}`, '');
|
|
1074
1071
|
}
|
|
1075
1072
|
return;
|
|
1076
1073
|
}
|
|
1077
1074
|
|
|
1078
1075
|
const review = fileResult.results;
|
|
1079
1076
|
if (review.summary) {
|
|
1080
|
-
|
|
1077
|
+
lines.push(`**Summary:** ${review.summary}`, '');
|
|
1081
1078
|
}
|
|
1082
1079
|
|
|
1083
1080
|
if (review.issues && review.issues.length > 0) {
|
|
1084
|
-
|
|
1081
|
+
lines.push(`**Issues Found (${review.issues.length}):**`, '');
|
|
1085
1082
|
review.issues.forEach((issue) => {
|
|
1086
1083
|
const severityEmoji = getSeverityEmoji(issue.severity);
|
|
1087
|
-
|
|
1084
|
+
lines.push(
|
|
1088
1085
|
`- **[${issue.severity.toUpperCase()}] ${severityEmoji} (Lines: ${issue.lineNumbers?.join(', ') || 'N/A'})**: ${
|
|
1089
1086
|
issue.description
|
|
1090
1087
|
}`
|
|
1091
1088
|
);
|
|
1092
1089
|
if (issue.suggestion) {
|
|
1093
|
-
|
|
1090
|
+
lines.push('', ` *Suggestion:* ${issue.suggestion}`, '');
|
|
1094
1091
|
}
|
|
1095
1092
|
// Include code suggestion if available
|
|
1096
1093
|
if (issue.codeSuggestion) {
|
|
1097
1094
|
const { startLine, endLine, newCode } = issue.codeSuggestion;
|
|
1098
1095
|
const lineRange = endLine ? `${startLine}-${endLine}` : `${startLine}`;
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
newCode
|
|
1103
|
-
.split('\n')
|
|
1104
|
-
.map((line) => ` ${line}`)
|
|
1105
|
-
.join('\n')
|
|
1106
|
-
);
|
|
1107
|
-
console.log(' ```\n');
|
|
1096
|
+
lines.push('', ` **Suggested change (lines ${lineRange}):**`, '', ' ```suggestion');
|
|
1097
|
+
lines.push(...newCode.split('\n').map((line) => ` ${line}`));
|
|
1098
|
+
lines.push(' ```', '');
|
|
1108
1099
|
}
|
|
1109
1100
|
});
|
|
1110
1101
|
}
|
|
1111
|
-
|
|
1112
|
-
if (review.positives && review.positives.length > 0) {
|
|
1113
|
-
console.log(`**Positives Found (${review.positives.length}):**\n`);
|
|
1114
|
-
review.positives.forEach((positive) => {
|
|
1115
|
-
console.log(` - ${positive}\n`);
|
|
1116
|
-
});
|
|
1117
|
-
}
|
|
1118
1102
|
});
|
|
1103
|
+
|
|
1104
|
+
const markdownOutput = `${lines.join('\n')}\n`;
|
|
1105
|
+
|
|
1106
|
+
if (options?.outputFile) {
|
|
1107
|
+
fs.writeFileSync(options.outputFile, markdownOutput, 'utf8');
|
|
1108
|
+
console.log(chalk.green(`Markdown output saved to: ${options.outputFile}`));
|
|
1109
|
+
return;
|
|
1110
|
+
}
|
|
1111
|
+
|
|
1112
|
+
process.stdout.write(markdownOutput);
|
|
1119
1113
|
}
|
|
1120
1114
|
|
|
1121
1115
|
/**
|
|
@@ -1147,17 +1141,13 @@ function outputText(reviewResults, cliOptions) {
|
|
|
1147
1141
|
return;
|
|
1148
1142
|
}
|
|
1149
1143
|
if (fileResult.skipped) {
|
|
1150
|
-
|
|
1151
|
-
console.log(chalk.yellow(`\nSkipped: ${fileResult.filePath}`));
|
|
1152
|
-
}
|
|
1144
|
+
verboseLog(cliOptions, chalk.yellow(`\nSkipped: ${fileResult.filePath}`));
|
|
1153
1145
|
return;
|
|
1154
1146
|
}
|
|
1155
|
-
if (!fileResult.results ||
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
console.log(chalk.green(` Summary: ${fileResult.results.summary}`));
|
|
1160
|
-
}
|
|
1147
|
+
if (!fileResult.results || !fileResult.results.issues?.length) {
|
|
1148
|
+
verboseLog(cliOptions, chalk.green(`\nNo findings for: ${fileResult.filePath}`));
|
|
1149
|
+
if (fileResult.results?.summary) {
|
|
1150
|
+
verboseLog(cliOptions, chalk.green(` Summary: ${fileResult.results.summary}`));
|
|
1161
1151
|
}
|
|
1162
1152
|
return;
|
|
1163
1153
|
}
|
|
@@ -1192,13 +1182,6 @@ function outputText(reviewResults, cliOptions) {
|
|
|
1192
1182
|
});
|
|
1193
1183
|
}
|
|
1194
1184
|
|
|
1195
|
-
if (review.positives && review.positives.length > 0) {
|
|
1196
|
-
console.log(chalk.bold.green('\nPositives:'));
|
|
1197
|
-
review.positives.forEach((positive) => {
|
|
1198
|
-
console.log(` - ${positive}`);
|
|
1199
|
-
});
|
|
1200
|
-
console.log('');
|
|
1201
|
-
}
|
|
1202
1185
|
console.log(chalk.gray(`========================================${'='.repeat(fileResult.filePath.length)}`));
|
|
1203
1186
|
});
|
|
1204
1187
|
}
|
|
@@ -1292,6 +1275,7 @@ async function analyzePRHistory(options) {
|
|
|
1292
1275
|
resume: options.resume,
|
|
1293
1276
|
clearExisting: options.clear,
|
|
1294
1277
|
projectPath,
|
|
1278
|
+
verbose: options.verbose,
|
|
1295
1279
|
onProgress: (progress) => displayProgress(progress, options.verbose),
|
|
1296
1280
|
};
|
|
1297
1281
|
|
|
@@ -1310,9 +1294,7 @@ async function analyzePRHistory(options) {
|
|
|
1310
1294
|
const endTime = Date.now();
|
|
1311
1295
|
const duration = ((endTime - startTime) / 1000).toFixed(2);
|
|
1312
1296
|
console.error(chalk.red(`\nError during PR history analysis (${duration}s):`), error.message);
|
|
1313
|
-
|
|
1314
|
-
console.error(error.stack);
|
|
1315
|
-
}
|
|
1297
|
+
verboseLog(options, error.stack);
|
|
1316
1298
|
process.exit(1);
|
|
1317
1299
|
}
|
|
1318
1300
|
}
|
|
@@ -1411,9 +1393,7 @@ async function clearPRHistory(options) {
|
|
|
1411
1393
|
}
|
|
1412
1394
|
} catch (error) {
|
|
1413
1395
|
console.error(chalk.red('Error clearing PR history data:'), error.message);
|
|
1414
|
-
|
|
1415
|
-
console.error(error.stack);
|
|
1416
|
-
}
|
|
1396
|
+
verboseLog(options, error.stack);
|
|
1417
1397
|
process.exit(1);
|
|
1418
1398
|
}
|
|
1419
1399
|
}
|
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) {
|
package/src/project-analyzer.js
CHANGED
|
@@ -13,6 +13,7 @@ import { getDefaultEmbeddingsSystem } from './embeddings/factory.js';
|
|
|
13
13
|
import * as llm from './llm.js';
|
|
14
14
|
import { FILE_SELECTION_SYSTEM_PROMPT, PROJECT_SUMMARY_SYSTEM_PROMPT } from './prompt-cache.js';
|
|
15
15
|
import { isDocumentationFile, isTestFile } from './utils/file-validation.js';
|
|
16
|
+
import { verboseLog } from './utils/logging.js';
|
|
16
17
|
|
|
17
18
|
// Consolidated file classification configuration
|
|
18
19
|
const FILE_PATTERNS = {
|
|
@@ -198,9 +199,7 @@ export class ProjectAnalyzer {
|
|
|
198
199
|
const { verbose = false, forceAnalysis = false } = options;
|
|
199
200
|
|
|
200
201
|
try {
|
|
201
|
-
|
|
202
|
-
console.log(chalk.cyan('🔍 Starting project architecture analysis...'));
|
|
203
|
-
}
|
|
202
|
+
verboseLog(verbose, chalk.cyan('🔍 Starting project architecture analysis...'));
|
|
204
203
|
|
|
205
204
|
// Initialize LLM client
|
|
206
205
|
if (!this.llm) {
|
|
@@ -212,16 +211,13 @@ export class ProjectAnalyzer {
|
|
|
212
211
|
if (existingSummary && !forceAnalysis) {
|
|
213
212
|
const currentHash = await this.calculateKeyFilesHash(existingSummary.keyFiles);
|
|
214
213
|
if (existingSummary.keyFilesHash === currentHash) {
|
|
215
|
-
|
|
216
|
-
console.log(chalk.green('✅ Project analysis up-to-date (no key file changes detected)'));
|
|
217
|
-
}
|
|
214
|
+
verboseLog(verbose, chalk.green('✅ Project analysis up-to-date (no key file changes detected)'));
|
|
218
215
|
return existingSummary;
|
|
219
216
|
}
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
console.log(
|
|
217
|
+
verboseLog(verbose, chalk.yellow('🔄 Key files changed, regenerating analysis...'));
|
|
218
|
+
} else {
|
|
219
|
+
verboseLog(
|
|
220
|
+
verbose,
|
|
225
221
|
chalk.cyan(
|
|
226
222
|
forceAnalysis
|
|
227
223
|
? '🔄 Force analysis requested - regenerating from scratch...'
|
|
@@ -235,10 +231,8 @@ export class ProjectAnalyzer {
|
|
|
235
231
|
? await this.validateAndUpdateKeyFiles(existingSummary.keyFiles, projectPath)
|
|
236
232
|
: await this.discoverKeyFilesWithLLM(projectPath);
|
|
237
233
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
console.log(chalk.cyan('🧠 Generating LLM-based project analysis...'));
|
|
241
|
-
}
|
|
234
|
+
verboseLog(verbose, chalk.gray(` Found ${keyFiles.length} key architectural files`));
|
|
235
|
+
verboseLog(verbose, chalk.cyan('🧠 Generating LLM-based project analysis...'));
|
|
242
236
|
|
|
243
237
|
// Generate summary
|
|
244
238
|
const projectSummary = await this.generateProjectSummary(keyFiles, projectPath);
|
|
@@ -254,12 +248,10 @@ export class ProjectAnalyzer {
|
|
|
254
248
|
this.keyFiles = keyFiles;
|
|
255
249
|
this.lastAnalysisHash = currentHash;
|
|
256
250
|
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
console.log(chalk.gray(` Key files tracked: ${keyFiles.length}`));
|
|
262
|
-
}
|
|
251
|
+
verboseLog(verbose, chalk.green('✅ Project analysis complete'));
|
|
252
|
+
verboseLog(verbose, chalk.gray(` Technologies: ${(projectSummary.technologies || []).join(', ')}`));
|
|
253
|
+
verboseLog(verbose, chalk.gray(` Key patterns: ${(projectSummary.keyPatterns || []).length} identified`));
|
|
254
|
+
verboseLog(verbose, chalk.gray(` Key files tracked: ${keyFiles.length}`));
|
|
263
255
|
|
|
264
256
|
return projectSummary;
|
|
265
257
|
} catch (error) {
|
|
@@ -300,7 +292,7 @@ export class ProjectAnalyzer {
|
|
|
300
292
|
try {
|
|
301
293
|
const embeddingsSystem = getDefaultEmbeddingsSystem();
|
|
302
294
|
await embeddingsSystem.storeProjectSummary(projectPath, projectSummary);
|
|
303
|
-
|
|
295
|
+
verboseLog({}, chalk.green('✅ Project analysis stored in database'));
|
|
304
296
|
} catch (error) {
|
|
305
297
|
console.error(chalk.yellow('Warning: Could not store analysis:'), error.message);
|
|
306
298
|
}
|
|
@@ -328,7 +320,7 @@ export class ProjectAnalyzer {
|
|
|
328
320
|
|
|
329
321
|
// If we lost more than 30% of key files, trigger fresh discovery
|
|
330
322
|
if (validatedFiles.length < existingKeyFiles.length * 0.7) {
|
|
331
|
-
|
|
323
|
+
verboseLog({}, chalk.yellow('⚠️ Many key files missing, performing fresh discovery...'));
|
|
332
324
|
return await this.discoverKeyFilesWithLLM(projectPath);
|
|
333
325
|
}
|
|
334
326
|
|
|
@@ -339,10 +331,10 @@ export class ProjectAnalyzer {
|
|
|
339
331
|
* Discover key architectural files using LanceDB hybrid search
|
|
340
332
|
*/
|
|
341
333
|
async discoverKeyFilesWithLLM(projectPath) {
|
|
342
|
-
|
|
334
|
+
verboseLog({}, chalk.cyan('🔍 Mining codebase embeddings with LanceDB hybrid search...'));
|
|
343
335
|
|
|
344
336
|
const keyFilesByCategory = await this.mineKeyFilesFromEmbeddings(projectPath);
|
|
345
|
-
|
|
337
|
+
verboseLog({}, chalk.cyan(`🧠 LLM analyzing ${keyFilesByCategory.length} candidates from embedding search...`));
|
|
346
338
|
|
|
347
339
|
const keyFiles = await this.selectFinalKeyFiles(keyFilesByCategory, projectPath);
|
|
348
340
|
return keyFiles;
|
|
@@ -362,7 +354,7 @@ export class ProjectAnalyzer {
|
|
|
362
354
|
await table.optimize();
|
|
363
355
|
} catch (optimizeError) {
|
|
364
356
|
if (optimizeError.message && optimizeError.message.includes('legacy format')) {
|
|
365
|
-
console.
|
|
357
|
+
console.warn(chalk.yellow(`Skipping optimization due to legacy index format - will be auto-upgraded during normal operations`));
|
|
366
358
|
} else {
|
|
367
359
|
console.warn(chalk.yellow(`Warning: Failed to optimize file embeddings table: ${optimizeError.message}`));
|
|
368
360
|
}
|
|
@@ -371,7 +363,7 @@ export class ProjectAnalyzer {
|
|
|
371
363
|
const keyFiles = new Map();
|
|
372
364
|
|
|
373
365
|
try {
|
|
374
|
-
|
|
366
|
+
verboseLog({}, chalk.gray(` 📊 Using LanceDB hybrid search for project: ${projectPath}`));
|
|
375
367
|
|
|
376
368
|
// Unified query function
|
|
377
369
|
const queryFiles = async (config) => {
|
|
@@ -414,17 +406,17 @@ export class ProjectAnalyzer {
|
|
|
414
406
|
|
|
415
407
|
return await query.limit(config.limit || 30).toArray();
|
|
416
408
|
} catch (error) {
|
|
417
|
-
|
|
409
|
+
verboseLog({}, chalk.yellow(` ⚠️ Query failed for ${config.category}: ${error.message}`));
|
|
418
410
|
return [];
|
|
419
411
|
}
|
|
420
412
|
};
|
|
421
413
|
|
|
422
414
|
// Execute all searches
|
|
423
415
|
for (const config of DB_SEARCH_CONFIGS) {
|
|
424
|
-
|
|
416
|
+
verboseLog({}, chalk.gray(` 🔍 Searching for ${config.category} files...`));
|
|
425
417
|
|
|
426
418
|
const results = await queryFiles(config);
|
|
427
|
-
|
|
419
|
+
verboseLog({}, chalk.gray(` 📦 Found ${results.length} ${config.category} file candidates`));
|
|
428
420
|
|
|
429
421
|
results.forEach((result) => {
|
|
430
422
|
if (this.matchesFileType(result.path, result.name, config.matcher)) {
|
|
@@ -438,7 +430,7 @@ export class ProjectAnalyzer {
|
|
|
438
430
|
}
|
|
439
431
|
|
|
440
432
|
const results = Array.from(keyFiles.values());
|
|
441
|
-
|
|
433
|
+
verboseLog({}, chalk.cyan(`🗃️ Found ${results.length} key files from embeddings database`));
|
|
442
434
|
return results;
|
|
443
435
|
}
|
|
444
436
|
|
|
@@ -475,11 +467,11 @@ export class ProjectAnalyzer {
|
|
|
475
467
|
*/
|
|
476
468
|
async selectFinalKeyFiles(candidates, projectPath) {
|
|
477
469
|
if (candidates.length === 0) {
|
|
478
|
-
|
|
470
|
+
verboseLog({}, chalk.yellow('⚠️ No candidates found from embeddings search'));
|
|
479
471
|
return [];
|
|
480
472
|
}
|
|
481
473
|
|
|
482
|
-
|
|
474
|
+
verboseLog({}, chalk.cyan(`🤖 LLM analyzing ${candidates.length} candidates...`));
|
|
483
475
|
|
|
484
476
|
const candidatesSummary = candidates
|
|
485
477
|
.map((file, index) => {
|
|
@@ -520,7 +512,7 @@ Select files following the criteria in the system instructions.`;
|
|
|
520
512
|
jsonSchema: fileSelectionSchema,
|
|
521
513
|
});
|
|
522
514
|
|
|
523
|
-
|
|
515
|
+
verboseLog({}, chalk.gray(' 📄 LLM Response preview:'), response.content.substring(0, 200));
|
|
524
516
|
|
|
525
517
|
const selectedPaths = response.json.selectedFiles;
|
|
526
518
|
|
|
@@ -546,14 +538,14 @@ Select files following the criteria in the system instructions.`;
|
|
|
546
538
|
})
|
|
547
539
|
.filter(Boolean);
|
|
548
540
|
|
|
549
|
-
|
|
541
|
+
verboseLog({}, chalk.cyan(`🎯 LLM selected ${keyFiles.length} final key files`));
|
|
550
542
|
return keyFiles;
|
|
551
543
|
} else {
|
|
552
544
|
throw new Error(`Failed to extract valid JSON array from LLM response`);
|
|
553
545
|
}
|
|
554
546
|
} catch (error) {
|
|
555
547
|
console.error(chalk.red('Error in LLM selection:'), error.message);
|
|
556
|
-
|
|
548
|
+
verboseLog({}, chalk.yellow(' 🔄 Falling back to automatic selection...'));
|
|
557
549
|
return this.fallbackFileSelection(candidates, projectPath);
|
|
558
550
|
}
|
|
559
551
|
}
|
|
@@ -588,7 +580,7 @@ Select files following the criteria in the system instructions.`;
|
|
|
588
580
|
}
|
|
589
581
|
}
|
|
590
582
|
|
|
591
|
-
|
|
583
|
+
verboseLog({}, chalk.yellow(`⚠️ Used fallback selection: ${fallbackFiles.length} files`));
|
|
592
584
|
return fallbackFiles;
|
|
593
585
|
}
|
|
594
586
|
|
|
@@ -793,7 +785,7 @@ Follow the analysis guidelines from the system instructions to identify custom i
|
|
|
793
785
|
} catch (error) {
|
|
794
786
|
console.error(chalk.red('Error generating project summary:'), error.message);
|
|
795
787
|
const fallback = this.createFallbackSummary(projectPath, keyFiles);
|
|
796
|
-
|
|
788
|
+
verboseLog({}, chalk.yellow('Using fallback summary with technologies:'), fallback.technologies);
|
|
797
789
|
return fallback;
|
|
798
790
|
}
|
|
799
791
|
}
|
|
@@ -857,7 +849,8 @@ Follow the analysis guidelines from the system instructions to identify custom i
|
|
|
857
849
|
};
|
|
858
850
|
}
|
|
859
851
|
|
|
860
|
-
|
|
852
|
+
verboseLog(
|
|
853
|
+
{},
|
|
861
854
|
chalk.cyan(
|
|
862
855
|
`✅ Project summary validated - Technologies: ${validatedSummary.technologies.length}, Frameworks: ${validatedSummary.mainFrameworks.length}`
|
|
863
856
|
)
|