codecritique 1.2.2 → 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 +17 -17
- package/src/feedback-loader.js +31 -31
- package/src/index.js +69 -91
- package/src/llm.js +4 -3
- package/src/project-analyzer.js +32 -39
- package/src/project-analyzer.test.js +3 -5
- package/src/rag-analyzer.js +189 -169
- package/src/rag-analyzer.test.js +46 -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) {
|
|
@@ -630,15 +629,14 @@ async function generateEmbeddings(options) {
|
|
|
630
629
|
await embeddingsSystem.storeProjectSummary(projectDir, projectSummary);
|
|
631
630
|
|
|
632
631
|
console.log(chalk.green('✅ Project analysis complete and stored'));
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
}
|
|
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}`));
|
|
642
640
|
} catch (error) {
|
|
643
641
|
console.error(chalk.red('⚠️ Project analysis failed but continuing:'), error.message);
|
|
644
642
|
}
|
|
@@ -756,10 +754,12 @@ async function showEmbeddingStats(options) {
|
|
|
756
754
|
*
|
|
757
755
|
* @param {string} directory - Directory to search
|
|
758
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
|
|
759
760
|
* @returns {Promise<Array<string>>} Array of file paths
|
|
760
761
|
*/
|
|
761
762
|
async function findSupportedFiles(directory, options = {}) {
|
|
762
|
-
const verbose = options.verbose || false;
|
|
763
763
|
const baseDir = path.resolve(directory);
|
|
764
764
|
|
|
765
765
|
// Default patterns match common code files - adjust as needed
|
|
@@ -835,19 +835,15 @@ async function findSupportedFiles(directory, options = {}) {
|
|
|
835
835
|
// Instead, we rely on the shouldProcessFile check in embeddings.js which uses git check-ignore
|
|
836
836
|
globOptions.ignore = [...excludePatterns]; // Use only explicit excludes
|
|
837
837
|
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
console.log(chalk.gray(` Options:`), globOptions);
|
|
842
|
-
}
|
|
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);
|
|
843
841
|
|
|
844
842
|
try {
|
|
845
843
|
// Use asynchronous glob
|
|
846
844
|
const files = await glob.glob(patternsToUse, globOptions);
|
|
847
845
|
|
|
848
|
-
|
|
849
|
-
console.log(chalk.green(`Glob found ${files.length} potential files.`));
|
|
850
|
-
}
|
|
846
|
+
verboseLog(options, chalk.green(`Glob found ${files.length} potential files.`));
|
|
851
847
|
|
|
852
848
|
// Filter results to ensure they are actual files (glob with stat should mostly handle this)
|
|
853
849
|
// And apply the final utilsShouldProcessFile check (e.g., for binary content if needed)
|
|
@@ -864,9 +860,7 @@ async function findSupportedFiles(directory, options = {}) {
|
|
|
864
860
|
// finalFiles.push(file);
|
|
865
861
|
// }
|
|
866
862
|
// } catch (statError) {
|
|
867
|
-
//
|
|
868
|
-
// console.warn(chalk.yellow(`Skipping file due to stat error ${path.relative(baseDir, file)}: ${statError.message}`));
|
|
869
|
-
// }
|
|
863
|
+
// console.warn(chalk.yellow(`Skipping file due to stat error ${path.relative(baseDir, file)}: ${statError.message}`));
|
|
870
864
|
// }
|
|
871
865
|
// }
|
|
872
866
|
|
|
@@ -874,9 +868,7 @@ async function findSupportedFiles(directory, options = {}) {
|
|
|
874
868
|
const finalFiles = files;
|
|
875
869
|
|
|
876
870
|
// Add log after the filtering loop (now just assignment)
|
|
877
|
-
|
|
878
|
-
console.log(chalk.green(`Finished filtering glob results. ${finalFiles.length} files remain.`));
|
|
879
|
-
}
|
|
871
|
+
verboseLog(options, chalk.green(`Finished filtering glob results. ${finalFiles.length} files remain.`));
|
|
880
872
|
return finalFiles;
|
|
881
873
|
} catch (err) {
|
|
882
874
|
if (err.name === 'AbortError') {
|
|
@@ -981,13 +973,13 @@ function getChangedFiles(branch, workingDir = process.cwd()) {
|
|
|
981
973
|
// REMOVED: checkBranchExists function - Moved to utils.js
|
|
982
974
|
|
|
983
975
|
// --- Output Formatting Functions --- //
|
|
984
|
-
// These
|
|
976
|
+
// These consume the normalized results returned by the RAG review pipeline
|
|
985
977
|
|
|
986
978
|
/**
|
|
987
979
|
* Output results in JSON format
|
|
988
980
|
*
|
|
989
|
-
* @param {Array<Object>} reviewResults - Array of individual file review results
|
|
990
|
-
* @param {Object}
|
|
981
|
+
* @param {Array<Object>} reviewResults - Array of individual file review results
|
|
982
|
+
* @param {Object} options - Command line options
|
|
991
983
|
*/
|
|
992
984
|
function outputJson(reviewResults, options) {
|
|
993
985
|
// Structure the output to be informative
|
|
@@ -1015,7 +1007,7 @@ function outputJson(reviewResults, options) {
|
|
|
1015
1007
|
filePath: r.filePath,
|
|
1016
1008
|
success: true,
|
|
1017
1009
|
language: r.language,
|
|
1018
|
-
review: r.results, // Contains summary
|
|
1010
|
+
review: r.results, // Contains summary and actionable issues (with optional codeSuggestion)
|
|
1019
1011
|
// Optionally include similar examples if needed
|
|
1020
1012
|
// similarExamplesUsed: r.similarExamples
|
|
1021
1013
|
};
|
|
@@ -1038,86 +1030,86 @@ function outputJson(reviewResults, options) {
|
|
|
1038
1030
|
* Output results in Markdown format
|
|
1039
1031
|
*
|
|
1040
1032
|
* @param {Array<Object>} reviewResults - Array of individual file review results
|
|
1041
|
-
* @param {Object}
|
|
1033
|
+
* @param {Object} options - Command line options
|
|
1042
1034
|
*/
|
|
1043
|
-
function outputMarkdown(reviewResults) {
|
|
1044
|
-
console.log('# AI Code Review Results (RAG Approach)\n');
|
|
1045
|
-
|
|
1035
|
+
function outputMarkdown(reviewResults, options) {
|
|
1046
1036
|
const totalFiles = reviewResults.length;
|
|
1047
1037
|
const filesWithIssues = reviewResults.filter((r) => r.success && !r.skipped && r.results?.issues?.length > 0).length;
|
|
1048
1038
|
const totalIssues = reviewResults.reduce((sum, r) => sum + (r.results?.issues?.length || 0), 0);
|
|
1049
1039
|
const skippedFiles = reviewResults.filter((r) => r.skipped).length;
|
|
1050
1040
|
const errorFiles = reviewResults.filter((r) => !r.success).length;
|
|
1051
1041
|
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
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
|
+
];
|
|
1051
|
+
|
|
1052
|
+
if (skippedFiles > 0) lines.push(`- **Files Skipped:** ${skippedFiles}`);
|
|
1053
|
+
if (errorFiles > 0) lines.push(`- **Errors:** ${errorFiles}`);
|
|
1059
1054
|
|
|
1060
|
-
|
|
1055
|
+
lines.push('', '## Detailed Review per File', '');
|
|
1061
1056
|
|
|
1062
1057
|
reviewResults.forEach((fileResult) => {
|
|
1063
|
-
|
|
1058
|
+
lines.push(`### ${fileResult.filePath}`, '');
|
|
1064
1059
|
if (!fileResult.success) {
|
|
1065
|
-
|
|
1060
|
+
lines.push(`**Error:** ${fileResult.error}`, '');
|
|
1066
1061
|
return;
|
|
1067
1062
|
}
|
|
1068
1063
|
if (fileResult.skipped) {
|
|
1069
|
-
|
|
1064
|
+
lines.push('*Skipped (based on exclusion patterns or file type).*', '');
|
|
1070
1065
|
return;
|
|
1071
1066
|
}
|
|
1072
|
-
if (!fileResult.results ||
|
|
1073
|
-
|
|
1067
|
+
if (!fileResult.results || !fileResult.results.issues?.length) {
|
|
1068
|
+
lines.push('*No actionable issues reported.*', '');
|
|
1074
1069
|
if (fileResult.results?.summary) {
|
|
1075
|
-
|
|
1070
|
+
lines.push(`**Summary:** ${fileResult.results.summary}`, '');
|
|
1076
1071
|
}
|
|
1077
1072
|
return;
|
|
1078
1073
|
}
|
|
1079
1074
|
|
|
1080
1075
|
const review = fileResult.results;
|
|
1081
1076
|
if (review.summary) {
|
|
1082
|
-
|
|
1077
|
+
lines.push(`**Summary:** ${review.summary}`, '');
|
|
1083
1078
|
}
|
|
1084
1079
|
|
|
1085
1080
|
if (review.issues && review.issues.length > 0) {
|
|
1086
|
-
|
|
1081
|
+
lines.push(`**Issues Found (${review.issues.length}):**`, '');
|
|
1087
1082
|
review.issues.forEach((issue) => {
|
|
1088
1083
|
const severityEmoji = getSeverityEmoji(issue.severity);
|
|
1089
|
-
|
|
1084
|
+
lines.push(
|
|
1090
1085
|
`- **[${issue.severity.toUpperCase()}] ${severityEmoji} (Lines: ${issue.lineNumbers?.join(', ') || 'N/A'})**: ${
|
|
1091
1086
|
issue.description
|
|
1092
1087
|
}`
|
|
1093
1088
|
);
|
|
1094
1089
|
if (issue.suggestion) {
|
|
1095
|
-
|
|
1090
|
+
lines.push('', ` *Suggestion:* ${issue.suggestion}`, '');
|
|
1096
1091
|
}
|
|
1097
1092
|
// Include code suggestion if available
|
|
1098
1093
|
if (issue.codeSuggestion) {
|
|
1099
1094
|
const { startLine, endLine, newCode } = issue.codeSuggestion;
|
|
1100
1095
|
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');
|
|
1096
|
+
lines.push('', ` **Suggested change (lines ${lineRange}):**`, '', ' ```suggestion');
|
|
1097
|
+
lines.push(...newCode.split('\n').map((line) => ` ${line}`));
|
|
1098
|
+
lines.push(' ```', '');
|
|
1110
1099
|
}
|
|
1111
1100
|
});
|
|
1112
1101
|
}
|
|
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
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);
|
|
1121
1113
|
}
|
|
1122
1114
|
|
|
1123
1115
|
/**
|
|
@@ -1149,17 +1141,13 @@ function outputText(reviewResults, cliOptions) {
|
|
|
1149
1141
|
return;
|
|
1150
1142
|
}
|
|
1151
1143
|
if (fileResult.skipped) {
|
|
1152
|
-
|
|
1153
|
-
console.log(chalk.yellow(`\nSkipped: ${fileResult.filePath}`));
|
|
1154
|
-
}
|
|
1144
|
+
verboseLog(cliOptions, chalk.yellow(`\nSkipped: ${fileResult.filePath}`));
|
|
1155
1145
|
return;
|
|
1156
1146
|
}
|
|
1157
|
-
if (!fileResult.results ||
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
console.log(chalk.green(` Summary: ${fileResult.results.summary}`));
|
|
1162
|
-
}
|
|
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}`));
|
|
1163
1151
|
}
|
|
1164
1152
|
return;
|
|
1165
1153
|
}
|
|
@@ -1194,13 +1182,6 @@ function outputText(reviewResults, cliOptions) {
|
|
|
1194
1182
|
});
|
|
1195
1183
|
}
|
|
1196
1184
|
|
|
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
1185
|
console.log(chalk.gray(`========================================${'='.repeat(fileResult.filePath.length)}`));
|
|
1205
1186
|
});
|
|
1206
1187
|
}
|
|
@@ -1294,6 +1275,7 @@ async function analyzePRHistory(options) {
|
|
|
1294
1275
|
resume: options.resume,
|
|
1295
1276
|
clearExisting: options.clear,
|
|
1296
1277
|
projectPath,
|
|
1278
|
+
verbose: options.verbose,
|
|
1297
1279
|
onProgress: (progress) => displayProgress(progress, options.verbose),
|
|
1298
1280
|
};
|
|
1299
1281
|
|
|
@@ -1312,9 +1294,7 @@ async function analyzePRHistory(options) {
|
|
|
1312
1294
|
const endTime = Date.now();
|
|
1313
1295
|
const duration = ((endTime - startTime) / 1000).toFixed(2);
|
|
1314
1296
|
console.error(chalk.red(`\nError during PR history analysis (${duration}s):`), error.message);
|
|
1315
|
-
|
|
1316
|
-
console.error(error.stack);
|
|
1317
|
-
}
|
|
1297
|
+
verboseLog(options, error.stack);
|
|
1318
1298
|
process.exit(1);
|
|
1319
1299
|
}
|
|
1320
1300
|
}
|
|
@@ -1413,9 +1393,7 @@ async function clearPRHistory(options) {
|
|
|
1413
1393
|
}
|
|
1414
1394
|
} catch (error) {
|
|
1415
1395
|
console.error(chalk.red('Error clearing PR history data:'), error.message);
|
|
1416
|
-
|
|
1417
|
-
console.error(error.stack);
|
|
1418
|
-
}
|
|
1396
|
+
verboseLog(options, error.stack);
|
|
1419
1397
|
process.exit(1);
|
|
1420
1398
|
}
|
|
1421
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
|
)
|
|
@@ -272,7 +272,6 @@ describe('ProjectAnalyzer', () => {
|
|
|
272
272
|
await analyzer.storeAnalysis(mockProjectPath, summary);
|
|
273
273
|
|
|
274
274
|
expect(mockEmbeddingsSystem.storeProjectSummary).toHaveBeenCalledWith(mockProjectPath, summary);
|
|
275
|
-
expect(console.log).toHaveBeenCalledWith(expect.stringContaining('Project analysis stored'));
|
|
276
275
|
});
|
|
277
276
|
|
|
278
277
|
it('should handle storage errors gracefully', async () => {
|
|
@@ -319,7 +318,7 @@ describe('ProjectAnalyzer', () => {
|
|
|
319
318
|
await analyzer.validateAndUpdateKeyFiles(existingFiles, mockProjectPath);
|
|
320
319
|
|
|
321
320
|
// With 1 of 3 files found (33%), it should trigger fresh discovery
|
|
322
|
-
expect(
|
|
321
|
+
expect(mockEmbeddingsSystem.initialize).toHaveBeenCalled();
|
|
323
322
|
});
|
|
324
323
|
|
|
325
324
|
it('should filter out missing files and keep existing ones', async () => {
|
|
@@ -366,7 +365,7 @@ describe('ProjectAnalyzer', () => {
|
|
|
366
365
|
await analyzer.validateAndUpdateKeyFiles(existingFiles, mockProjectPath);
|
|
367
366
|
|
|
368
367
|
// Should trigger discoverKeyFilesWithLLM
|
|
369
|
-
expect(
|
|
368
|
+
expect(mockEmbeddingsSystem.initialize).toHaveBeenCalled();
|
|
370
369
|
});
|
|
371
370
|
});
|
|
372
371
|
|
|
@@ -410,7 +409,7 @@ describe('ProjectAnalyzer', () => {
|
|
|
410
409
|
const result = await analyzer.mineKeyFilesFromEmbeddings(mockProjectPath);
|
|
411
410
|
|
|
412
411
|
expect(result).toEqual([]);
|
|
413
|
-
expect(console.
|
|
412
|
+
expect(console.warn).toHaveBeenCalledWith(expect.stringContaining('legacy index format'));
|
|
414
413
|
});
|
|
415
414
|
|
|
416
415
|
it('should return empty array on query error', async () => {
|
|
@@ -496,7 +495,6 @@ describe('ProjectAnalyzer', () => {
|
|
|
496
495
|
await analyzer.selectFinalKeyFiles(candidates, mockProjectPath);
|
|
497
496
|
|
|
498
497
|
expect(console.error).toHaveBeenCalled();
|
|
499
|
-
expect(console.log).toHaveBeenCalledWith(expect.stringContaining('Falling back to automatic selection'));
|
|
500
498
|
});
|
|
501
499
|
|
|
502
500
|
it('should fallback if LLM returns invalid response', async () => {
|