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.
@@ -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
- if (verbose) {
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 currentHash = await this.calculateKeyFilesHash(existingSummary.keyFiles);
214
- if (existingSummary.keyFilesHash === currentHash) {
215
- if (verbose) {
216
- console.log(chalk.green('✅ Project analysis up-to-date (no key file changes detected)'));
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
- return existingSummary;
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 if (verbose) {
224
- console.log(
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
- if (verbose) {
239
- console.log(chalk.gray(` Found ${keyFiles.length} key architectural files`));
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
- if (verbose) {
258
- console.log(chalk.green('✅ Project analysis complete'));
259
- console.log(chalk.gray(` Technologies: ${(projectSummary.technologies || []).join(', ')}`));
260
- console.log(chalk.gray(` Key patterns: ${(projectSummary.keyPatterns || []).length} identified`));
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
- console.log(chalk.green('✅ Project analysis stored in database'));
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
- console.log(chalk.yellow('⚠️ Many key files missing, performing fresh discovery...'));
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
- console.log(chalk.cyan('🔍 Mining codebase embeddings with LanceDB hybrid search...'));
340
+ verboseLog({}, chalk.cyan('🔍 Mining codebase embeddings with LanceDB hybrid search...'));
343
341
 
344
342
  const keyFilesByCategory = await this.mineKeyFilesFromEmbeddings(projectPath);
345
- console.log(chalk.cyan(`🧠 LLM analyzing ${keyFilesByCategory.length} candidates from embedding search...`));
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.log(chalk.yellow(`Skipping optimization due to legacy index format - will be auto-upgraded during normal operations`));
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
- console.log(chalk.gray(` 📊 Using LanceDB hybrid search for project: ${projectPath}`));
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
- console.log(chalk.yellow(` ⚠️ Query failed for ${config.category}: ${error.message}`));
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
- console.log(chalk.gray(` 🔍 Searching for ${config.category} files...`));
422
+ verboseLog({}, chalk.gray(` 🔍 Searching for ${config.category} files...`));
425
423
 
426
424
  const results = await queryFiles(config);
427
- console.log(chalk.gray(` 📦 Found ${results.length} ${config.category} file candidates`));
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
- console.log(chalk.cyan(`🗃️ Found ${results.length} key files from embeddings database`));
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
- console.log(chalk.yellow('⚠️ No candidates found from embeddings search'));
476
+ verboseLog({}, chalk.yellow('⚠️ No candidates found from embeddings search'));
479
477
  return [];
480
478
  }
481
479
 
482
- console.log(chalk.cyan(`🤖 LLM analyzing ${candidates.length} candidates...`));
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
- console.log(chalk.gray(' 📄 LLM Response preview:'), response.content.substring(0, 200));
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
- console.log(chalk.cyan(`🎯 LLM selected ${keyFiles.length} final key files`));
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
- console.log(chalk.yellow(' 🔄 Falling back to automatic selection...'));
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
- console.log(chalk.yellow(`⚠️ Used fallback selection: ${fallbackFiles.length} files`));
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
- console.log(chalk.yellow('Using fallback summary with technologies:'), fallback.technologies);
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
- console.log(
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(console.log).toHaveBeenCalledWith(expect.stringContaining('Many key files missing'));
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(console.log).toHaveBeenCalledWith(expect.stringContaining('Many key files missing'));
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.log).toHaveBeenCalledWith(expect.stringContaining('Skipping optimization'));
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 () => {