codecritique 2.0.7 → 2.0.8

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codecritique",
3
- "version": "2.0.7",
3
+ "version": "2.0.8",
4
4
  "description": "AI-powered code review tool for any programming language",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
@@ -405,8 +405,25 @@ export class ProjectAnalyzer {
405
405
  return depthA - depthB;
406
406
  });
407
407
 
408
- // Take only the first 500 after sorting to ensure we have shallow files
409
- const sortedFiles = allFiles.slice(0, TERM_SEARCH_CONTENT_CANDIDATE_LIMIT);
408
+ // Dedupe by path before capping: transient duplicate rows can exist for
409
+ // the same file (different content_hash) when a partial run hasn't pruned
410
+ // stale embeddings, and they would otherwise consume candidate slots and
411
+ // bloat the content query. Keep the shallowest occurrence (allFiles is
412
+ // already depth-sorted), then take the first 500 distinct shallow files.
413
+ const seenPaths = new Set();
414
+ const sortedFiles = [];
415
+ for (const file of allFiles) {
416
+ if (file.path) {
417
+ if (seenPaths.has(file.path)) {
418
+ continue;
419
+ }
420
+ seenPaths.add(file.path);
421
+ }
422
+ sortedFiles.push(file);
423
+ if (sortedFiles.length >= TERM_SEARCH_CONTENT_CANDIDATE_LIMIT) {
424
+ break;
425
+ }
426
+ }
410
427
  if (sortedFiles.length === 0) {
411
428
  return [];
412
429
  }
@@ -415,14 +432,20 @@ export class ProjectAnalyzer {
415
432
  const contentByPath = new Map();
416
433
  if (candidatePaths.length > 0) {
417
434
  const pathList = candidatePaths.map((candidatePath) => `'${escapeSqlString(candidatePath)}'`).join(', ');
435
+ // No row limit here: the result set is already bounded by the IN list of
436
+ // distinct candidate paths. A limit could truncate rows when duplicate
437
+ // rows exist for a path, silently dropping content for matched files.
418
438
  const contentResults = await table
419
439
  .query()
420
440
  .select(['path', 'content'])
421
441
  .where(`${projectPathFilter} AND path IN (${pathList})`)
422
- .limit(TERM_SEARCH_CONTENT_CANDIDATE_LIMIT)
423
442
  .toArray();
424
443
 
425
444
  for (const result of contentResults) {
445
+ // Prefer non-empty content when duplicate rows exist for a path.
446
+ if (contentByPath.get(result.path)) {
447
+ continue;
448
+ }
426
449
  contentByPath.set(result.path, result.content || '');
427
450
  }
428
451
  }
@@ -471,8 +471,10 @@ async function runAnalysis(filePath, options = {}) {
471
471
  let fullFileContent;
472
472
  if (options.diffOnly && options.diffContent) {
473
473
  content = options.diffContent;
474
- // For PR reviews, always read the full file content for context awareness
475
- fullFileContent = await readTextFile(filePath);
474
+ // For PR reviews, always read the full file content for context awareness.
475
+ // Tolerate a missing/unreadable file (e.g. deleted in the window after the
476
+ // existence check) by degrading to null; downstream falls back to file.content.
477
+ fullFileContent = await readTextFile(filePath).catch(() => null);
476
478
  verboseLog(options, chalk.blue(`Analyzing diff only for ${path.basename(filePath)}`));
477
479
  }
478
480
  else {
@@ -873,6 +875,22 @@ ${projectArchitectureSection}`);
873
875
  return blocks;
874
876
  }
875
877
 
878
+ /**
879
+ * Build the user-prompt sentence that points the model at the cached system blocks.
880
+ * Returns an empty string when no cached blocks were emitted, so the prompt never
881
+ * instructs the model to use context that was not sent.
882
+ *
883
+ * @param {string[]} cachedSystemBlocks - Cached blocks produced for this request
884
+ * @param {string} subject - What is being evaluated (e.g. 'this file')
885
+ * @returns {string} Instruction sentence, or '' when there is no cached context
886
+ */
887
+ function buildCachedContextInstruction(cachedSystemBlocks, subject) {
888
+ if (!cachedSystemBlocks || cachedSystemBlocks.length === 0) {
889
+ return '';
890
+ }
891
+ return `Use the cached project context, project architecture, and custom instructions when evaluating ${subject}.`;
892
+ }
893
+
876
894
  // LLM call function - sends prompt with cached system content for cost optimization
877
895
  async function sendPromptToLLM(promptConfig, llmOptions) {
878
896
  try {
@@ -1150,7 +1168,7 @@ ${reviewInstructions}
1150
1168
 
1151
1169
  ${fileSection}
1152
1170
 
1153
- Use the cached project context, project architecture, and custom instructions when evaluating this file.
1171
+ ${buildCachedContextInstruction(cachedSystemBlocks, 'this file')}
1154
1172
 
1155
1173
  CONTEXT A: EXPLICIT GUIDELINES FROM DOCUMENTATION
1156
1174
  ${formattedGuidelines}
@@ -1270,7 +1288,7 @@ ${reviewInstructions}
1270
1288
  ${fileSection}
1271
1289
 
1272
1290
  ## ANALYSIS CONTEXT
1273
- Use the cached project context, project architecture, and custom instructions when evaluating this test file.
1291
+ ${buildCachedContextInstruction(cachedSystemBlocks, 'this test file')}
1274
1292
 
1275
1293
  CONTEXT A: TESTING GUIDELINES AND BEST PRACTICES
1276
1294
  ${formattedGuidelines}
@@ -1427,7 +1445,7 @@ For each file in this PR, you have access to:
1427
1445
  - **Test Files**: ${prFiles.filter((f) => f.isTest).length}
1428
1446
 
1429
1447
  ## UNIFIED CONTEXT FROM PROJECT
1430
- Use the cached project context, project architecture, and custom instructions when evaluating this pull request.
1448
+ ${buildCachedContextInstruction(cachedSystemBlocks, 'this pull request')}
1431
1449
 
1432
1450
  ### PROJECT CODE EXAMPLES
1433
1451
  ${formattedCodeExamples}