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/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) {
|
|
@@ -210,18 +209,20 @@ export class ProjectAnalyzer {
|
|
|
210
209
|
// Check for existing analysis
|
|
211
210
|
const existingSummary = forceAnalysis ? null : await this.loadExistingAnalysis(projectPath);
|
|
212
211
|
if (existingSummary && !forceAnalysis) {
|
|
213
|
-
const
|
|
214
|
-
if (existingSummary.
|
|
215
|
-
|
|
216
|
-
|
|
212
|
+
const currentEmbeddingInventoryHash = await this.calculateEmbeddingInventoryHash(projectPath);
|
|
213
|
+
if (existingSummary.embeddingInventoryHash !== currentEmbeddingInventoryHash) {
|
|
214
|
+
verboseLog(verbose, chalk.yellow('🔄 Embedding inventory changed, regenerating analysis...'));
|
|
215
|
+
} else {
|
|
216
|
+
const currentHash = await this.calculateKeyFilesHash(existingSummary.keyFiles);
|
|
217
|
+
if (existingSummary.keyFilesHash === currentHash) {
|
|
218
|
+
verboseLog(verbose, chalk.green('✅ Project analysis up-to-date (no key file changes detected)'));
|
|
219
|
+
return existingSummary;
|
|
217
220
|
}
|
|
218
|
-
|
|
219
|
-
}
|
|
220
|
-
if (verbose) {
|
|
221
|
-
console.log(chalk.yellow('🔄 Key files changed, regenerating analysis...'));
|
|
221
|
+
verboseLog(verbose, chalk.yellow('🔄 Key files changed, regenerating analysis...'));
|
|
222
222
|
}
|
|
223
|
-
} else
|
|
224
|
-
|
|
223
|
+
} else {
|
|
224
|
+
verboseLog(
|
|
225
|
+
verbose,
|
|
225
226
|
chalk.cyan(
|
|
226
227
|
forceAnalysis
|
|
227
228
|
? '🔄 Force analysis requested - regenerating from scratch...'
|
|
@@ -235,10 +236,8 @@ export class ProjectAnalyzer {
|
|
|
235
236
|
? await this.validateAndUpdateKeyFiles(existingSummary.keyFiles, projectPath)
|
|
236
237
|
: await this.discoverKeyFilesWithLLM(projectPath);
|
|
237
238
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
console.log(chalk.cyan('🧠 Generating LLM-based project analysis...'));
|
|
241
|
-
}
|
|
239
|
+
verboseLog(verbose, chalk.gray(` Found ${keyFiles.length} key architectural files`));
|
|
240
|
+
verboseLog(verbose, chalk.cyan('🧠 Generating LLM-based project analysis...'));
|
|
242
241
|
|
|
243
242
|
// Generate summary
|
|
244
243
|
const projectSummary = await this.generateProjectSummary(keyFiles, projectPath);
|
|
@@ -247,6 +246,7 @@ export class ProjectAnalyzer {
|
|
|
247
246
|
const currentHash = await this.calculateKeyFilesHash(keyFiles);
|
|
248
247
|
projectSummary.keyFiles = keyFiles;
|
|
249
248
|
projectSummary.keyFilesHash = currentHash;
|
|
249
|
+
projectSummary.embeddingInventoryHash = await this.calculateEmbeddingInventoryHash(projectPath);
|
|
250
250
|
|
|
251
251
|
await this.storeAnalysis(projectPath, projectSummary);
|
|
252
252
|
|
|
@@ -254,12 +254,10 @@ export class ProjectAnalyzer {
|
|
|
254
254
|
this.keyFiles = keyFiles;
|
|
255
255
|
this.lastAnalysisHash = currentHash;
|
|
256
256
|
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
console.log(chalk.gray(` Key files tracked: ${keyFiles.length}`));
|
|
262
|
-
}
|
|
257
|
+
verboseLog(verbose, chalk.green('✅ Project analysis complete'));
|
|
258
|
+
verboseLog(verbose, chalk.gray(` Technologies: ${(projectSummary.technologies || []).join(', ')}`));
|
|
259
|
+
verboseLog(verbose, chalk.gray(` Key patterns: ${(projectSummary.keyPatterns || []).length} identified`));
|
|
260
|
+
verboseLog(verbose, chalk.gray(` Key files tracked: ${keyFiles.length}`));
|
|
263
261
|
|
|
264
262
|
return projectSummary;
|
|
265
263
|
} catch (error) {
|
|
@@ -300,7 +298,7 @@ export class ProjectAnalyzer {
|
|
|
300
298
|
try {
|
|
301
299
|
const embeddingsSystem = getDefaultEmbeddingsSystem();
|
|
302
300
|
await embeddingsSystem.storeProjectSummary(projectPath, projectSummary);
|
|
303
|
-
|
|
301
|
+
verboseLog({}, chalk.green('✅ Project analysis stored in database'));
|
|
304
302
|
} catch (error) {
|
|
305
303
|
console.error(chalk.yellow('Warning: Could not store analysis:'), error.message);
|
|
306
304
|
}
|
|
@@ -328,7 +326,7 @@ export class ProjectAnalyzer {
|
|
|
328
326
|
|
|
329
327
|
// If we lost more than 30% of key files, trigger fresh discovery
|
|
330
328
|
if (validatedFiles.length < existingKeyFiles.length * 0.7) {
|
|
331
|
-
|
|
329
|
+
verboseLog({}, chalk.yellow('⚠️ Many key files missing, performing fresh discovery...'));
|
|
332
330
|
return await this.discoverKeyFilesWithLLM(projectPath);
|
|
333
331
|
}
|
|
334
332
|
|
|
@@ -339,10 +337,10 @@ export class ProjectAnalyzer {
|
|
|
339
337
|
* Discover key architectural files using LanceDB hybrid search
|
|
340
338
|
*/
|
|
341
339
|
async discoverKeyFilesWithLLM(projectPath) {
|
|
342
|
-
|
|
340
|
+
verboseLog({}, chalk.cyan('🔍 Mining codebase embeddings with LanceDB hybrid search...'));
|
|
343
341
|
|
|
344
342
|
const keyFilesByCategory = await this.mineKeyFilesFromEmbeddings(projectPath);
|
|
345
|
-
|
|
343
|
+
verboseLog({}, chalk.cyan(`🧠 LLM analyzing ${keyFilesByCategory.length} candidates from embedding search...`));
|
|
346
344
|
|
|
347
345
|
const keyFiles = await this.selectFinalKeyFiles(keyFilesByCategory, projectPath);
|
|
348
346
|
return keyFiles;
|
|
@@ -362,7 +360,7 @@ export class ProjectAnalyzer {
|
|
|
362
360
|
await table.optimize();
|
|
363
361
|
} catch (optimizeError) {
|
|
364
362
|
if (optimizeError.message && optimizeError.message.includes('legacy format')) {
|
|
365
|
-
console.
|
|
363
|
+
console.warn(chalk.yellow(`Skipping optimization due to legacy index format - will be auto-upgraded during normal operations`));
|
|
366
364
|
} else {
|
|
367
365
|
console.warn(chalk.yellow(`Warning: Failed to optimize file embeddings table: ${optimizeError.message}`));
|
|
368
366
|
}
|
|
@@ -371,7 +369,7 @@ export class ProjectAnalyzer {
|
|
|
371
369
|
const keyFiles = new Map();
|
|
372
370
|
|
|
373
371
|
try {
|
|
374
|
-
|
|
372
|
+
verboseLog({}, chalk.gray(` 📊 Using LanceDB hybrid search for project: ${projectPath}`));
|
|
375
373
|
|
|
376
374
|
// Unified query function
|
|
377
375
|
const queryFiles = async (config) => {
|
|
@@ -414,17 +412,17 @@ export class ProjectAnalyzer {
|
|
|
414
412
|
|
|
415
413
|
return await query.limit(config.limit || 30).toArray();
|
|
416
414
|
} catch (error) {
|
|
417
|
-
|
|
415
|
+
verboseLog({}, chalk.yellow(` ⚠️ Query failed for ${config.category}: ${error.message}`));
|
|
418
416
|
return [];
|
|
419
417
|
}
|
|
420
418
|
};
|
|
421
419
|
|
|
422
420
|
// Execute all searches
|
|
423
421
|
for (const config of DB_SEARCH_CONFIGS) {
|
|
424
|
-
|
|
422
|
+
verboseLog({}, chalk.gray(` 🔍 Searching for ${config.category} files...`));
|
|
425
423
|
|
|
426
424
|
const results = await queryFiles(config);
|
|
427
|
-
|
|
425
|
+
verboseLog({}, chalk.gray(` 📦 Found ${results.length} ${config.category} file candidates`));
|
|
428
426
|
|
|
429
427
|
results.forEach((result) => {
|
|
430
428
|
if (this.matchesFileType(result.path, result.name, config.matcher)) {
|
|
@@ -438,7 +436,7 @@ export class ProjectAnalyzer {
|
|
|
438
436
|
}
|
|
439
437
|
|
|
440
438
|
const results = Array.from(keyFiles.values());
|
|
441
|
-
|
|
439
|
+
verboseLog({}, chalk.cyan(`🗃️ Found ${results.length} key files from embeddings database`));
|
|
442
440
|
return results;
|
|
443
441
|
}
|
|
444
442
|
|
|
@@ -475,11 +473,11 @@ export class ProjectAnalyzer {
|
|
|
475
473
|
*/
|
|
476
474
|
async selectFinalKeyFiles(candidates, projectPath) {
|
|
477
475
|
if (candidates.length === 0) {
|
|
478
|
-
|
|
476
|
+
verboseLog({}, chalk.yellow('⚠️ No candidates found from embeddings search'));
|
|
479
477
|
return [];
|
|
480
478
|
}
|
|
481
479
|
|
|
482
|
-
|
|
480
|
+
verboseLog({}, chalk.cyan(`🤖 LLM analyzing ${candidates.length} candidates...`));
|
|
483
481
|
|
|
484
482
|
const candidatesSummary = candidates
|
|
485
483
|
.map((file, index) => {
|
|
@@ -520,7 +518,7 @@ Select files following the criteria in the system instructions.`;
|
|
|
520
518
|
jsonSchema: fileSelectionSchema,
|
|
521
519
|
});
|
|
522
520
|
|
|
523
|
-
|
|
521
|
+
verboseLog({}, chalk.gray(' 📄 LLM Response preview:'), response.content.substring(0, 200));
|
|
524
522
|
|
|
525
523
|
const selectedPaths = response.json.selectedFiles;
|
|
526
524
|
|
|
@@ -546,14 +544,14 @@ Select files following the criteria in the system instructions.`;
|
|
|
546
544
|
})
|
|
547
545
|
.filter(Boolean);
|
|
548
546
|
|
|
549
|
-
|
|
547
|
+
verboseLog({}, chalk.cyan(`🎯 LLM selected ${keyFiles.length} final key files`));
|
|
550
548
|
return keyFiles;
|
|
551
549
|
} else {
|
|
552
550
|
throw new Error(`Failed to extract valid JSON array from LLM response`);
|
|
553
551
|
}
|
|
554
552
|
} catch (error) {
|
|
555
553
|
console.error(chalk.red('Error in LLM selection:'), error.message);
|
|
556
|
-
|
|
554
|
+
verboseLog({}, chalk.yellow(' 🔄 Falling back to automatic selection...'));
|
|
557
555
|
return this.fallbackFileSelection(candidates, projectPath);
|
|
558
556
|
}
|
|
559
557
|
}
|
|
@@ -588,7 +586,7 @@ Select files following the criteria in the system instructions.`;
|
|
|
588
586
|
}
|
|
589
587
|
}
|
|
590
588
|
|
|
591
|
-
|
|
589
|
+
verboseLog({}, chalk.yellow(`⚠️ Used fallback selection: ${fallbackFiles.length} files`));
|
|
592
590
|
return fallbackFiles;
|
|
593
591
|
}
|
|
594
592
|
|
|
@@ -621,6 +619,39 @@ Select files following the criteria in the system instructions.`;
|
|
|
621
619
|
return hash.digest('hex');
|
|
622
620
|
}
|
|
623
621
|
|
|
622
|
+
/**
|
|
623
|
+
* Calculate a project-scoped hash of the current embedding inventory.
|
|
624
|
+
*/
|
|
625
|
+
async calculateEmbeddingInventoryHash(projectPath) {
|
|
626
|
+
try {
|
|
627
|
+
const embeddingsSystem = getDefaultEmbeddingsSystem();
|
|
628
|
+
await embeddingsSystem.initialize();
|
|
629
|
+
const table = await embeddingsSystem.databaseManager.getTable(embeddingsSystem.databaseManager.fileEmbeddingsTable);
|
|
630
|
+
|
|
631
|
+
if (!table) {
|
|
632
|
+
return 'no-file-embeddings-table';
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
const records = await table
|
|
636
|
+
.query()
|
|
637
|
+
.select(['type', 'path', 'content_hash', 'project_path'])
|
|
638
|
+
.where(`project_path = '${projectPath.replace(/'/g, "''")}'`)
|
|
639
|
+
.toArray();
|
|
640
|
+
|
|
641
|
+
const hash = crypto.createHash('sha256');
|
|
642
|
+
const normalizedRows = records.map((record) => `${record.type || 'file'}:${record.path || ''}:${record.content_hash || ''}`).sort();
|
|
643
|
+
|
|
644
|
+
for (const row of normalizedRows) {
|
|
645
|
+
hash.update(row);
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
return hash.digest('hex');
|
|
649
|
+
} catch (error) {
|
|
650
|
+
verboseLog({}, chalk.yellow(`Warning: Could not calculate embedding inventory hash: ${error.message}`));
|
|
651
|
+
return 'embedding-inventory-unavailable';
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
|
|
624
655
|
/**
|
|
625
656
|
* Generate comprehensive project summary using LLM analysis (SINGLE CALL)
|
|
626
657
|
*/
|
|
@@ -793,7 +824,7 @@ Follow the analysis guidelines from the system instructions to identify custom i
|
|
|
793
824
|
} catch (error) {
|
|
794
825
|
console.error(chalk.red('Error generating project summary:'), error.message);
|
|
795
826
|
const fallback = this.createFallbackSummary(projectPath, keyFiles);
|
|
796
|
-
|
|
827
|
+
verboseLog({}, chalk.yellow('Using fallback summary with technologies:'), fallback.technologies);
|
|
797
828
|
return fallback;
|
|
798
829
|
}
|
|
799
830
|
}
|
|
@@ -857,7 +888,8 @@ Follow the analysis guidelines from the system instructions to identify custom i
|
|
|
857
888
|
};
|
|
858
889
|
}
|
|
859
890
|
|
|
860
|
-
|
|
891
|
+
verboseLog(
|
|
892
|
+
{},
|
|
861
893
|
chalk.cyan(
|
|
862
894
|
`✅ Project summary validated - Technologies: ${validatedSummary.technologies.length}, Frameworks: ${validatedSummary.mainFrameworks.length}`
|
|
863
895
|
)
|
|
@@ -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 () => {
|