codecritique 2.0.0 → 2.0.2
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/project-analyzer.js +6 -8
- package/src/project-analyzer.test.js +8 -0
- package/src/rag-analyzer.js +7 -2
- package/src/rag-analyzer.test.js +13 -0
- package/src/rag-review.js +12 -1
package/package.json
CHANGED
package/src/project-analyzer.js
CHANGED
|
@@ -14,6 +14,7 @@ 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
16
|
import { verboseLog } from './utils/logging.js';
|
|
17
|
+
import { escapeSqlString } from './utils/string-utils.js';
|
|
17
18
|
|
|
18
19
|
// Consolidated file classification configuration
|
|
19
20
|
const FILE_PATTERNS = {
|
|
@@ -377,6 +378,7 @@ export class ProjectAnalyzer {
|
|
|
377
378
|
|
|
378
379
|
try {
|
|
379
380
|
verboseLog({}, chalk.gray(` 📊 Using LanceDB hybrid search for project: ${projectPath}`));
|
|
381
|
+
const projectPathFilter = `project_path = '${escapeSqlString(projectPath)}'`;
|
|
380
382
|
|
|
381
383
|
// Unified query function
|
|
382
384
|
const queryFiles = async (config) => {
|
|
@@ -384,15 +386,11 @@ export class ProjectAnalyzer {
|
|
|
384
386
|
let query = table.query().select(['path', 'name', 'content', 'type', 'language']);
|
|
385
387
|
|
|
386
388
|
if (config.whereClause) {
|
|
387
|
-
query = query.where(
|
|
389
|
+
query = query.where(`${projectPathFilter} AND (${config.whereClause})`);
|
|
388
390
|
}
|
|
389
391
|
else if (config.terms) {
|
|
390
392
|
// For term-based searches, query ALL files and sort by depth to prioritize shallow config files
|
|
391
|
-
const allFiles = await table
|
|
392
|
-
.query()
|
|
393
|
-
.select(['path', 'name', 'content', 'type', 'language'])
|
|
394
|
-
.where(`project_path = '${projectPath}'`)
|
|
395
|
-
.toArray(); // NO LIMIT - get all files
|
|
393
|
+
const allFiles = await table.query().select(['path', 'name', 'content', 'type', 'language']).where(projectPathFilter).toArray(); // NO LIMIT - get all files
|
|
396
394
|
|
|
397
395
|
// Sort by path depth (shorter paths first) to prioritize config files
|
|
398
396
|
allFiles.sort((a, b) => {
|
|
@@ -416,7 +414,7 @@ export class ProjectAnalyzer {
|
|
|
416
414
|
});
|
|
417
415
|
}
|
|
418
416
|
else {
|
|
419
|
-
query = query.where(
|
|
417
|
+
query = query.where(projectPathFilter);
|
|
420
418
|
}
|
|
421
419
|
|
|
422
420
|
return await query.limit(config.limit || 30).toArray();
|
|
@@ -655,7 +653,7 @@ Select files following the criteria in the system instructions.`;
|
|
|
655
653
|
const records = await table
|
|
656
654
|
.query()
|
|
657
655
|
.select(['type', 'path', 'content_hash', 'project_path'])
|
|
658
|
-
.where(`project_path = '${projectPath
|
|
656
|
+
.where(`project_path = '${escapeSqlString(projectPath)}'`)
|
|
659
657
|
.toArray();
|
|
660
658
|
|
|
661
659
|
const hash = crypto.createHash('sha256');
|
|
@@ -402,6 +402,14 @@ describe('ProjectAnalyzer', () => {
|
|
|
402
402
|
expect(mockTable.query).toHaveBeenCalled();
|
|
403
403
|
});
|
|
404
404
|
|
|
405
|
+
it('should escape project paths in key file queries', async () => {
|
|
406
|
+
const queryChain = mockTable.query();
|
|
407
|
+
|
|
408
|
+
await analyzer.mineKeyFilesFromEmbeddings("/mock/we're/project");
|
|
409
|
+
|
|
410
|
+
expect(queryChain.where).toHaveBeenCalledWith(expect.stringContaining("/mock/we''re/project"));
|
|
411
|
+
});
|
|
412
|
+
|
|
405
413
|
it('should handle table optimization errors gracefully', async () => {
|
|
406
414
|
mockTable.optimize.mockRejectedValue(new Error('legacy format'));
|
|
407
415
|
mockTable.query().toArray.mockResolvedValue([]);
|
package/src/rag-analyzer.js
CHANGED
|
@@ -722,7 +722,7 @@ function prepareContextForLLM(filePath, content, language, finalCodeExamples, fi
|
|
|
722
722
|
context: contextSections,
|
|
723
723
|
codeExamples,
|
|
724
724
|
guidelineSnippets,
|
|
725
|
-
customDocs: relevantCustomDocChunks
|
|
725
|
+
customDocs: relevantCustomDocChunks?.length > 0 ? relevantCustomDocChunks : customDocs, // Use relevant chunks if available, fallback to full docs
|
|
726
726
|
feedbackContext: generateFeedbackContext(dismissedPatterns), // Add feedback context for LLM
|
|
727
727
|
projectSummary: projectSummary, // Add project architecture summary
|
|
728
728
|
metadata: {
|
|
@@ -1729,7 +1729,12 @@ async function performHolisticPRAnalysis(options) {
|
|
|
1729
1729
|
items: unifiedContext.prComments.slice(0, 10),
|
|
1730
1730
|
},
|
|
1731
1731
|
],
|
|
1732
|
-
customDocs:
|
|
1732
|
+
customDocs:
|
|
1733
|
+
unifiedContext.customDocChunks?.length > 0
|
|
1734
|
+
? unifiedContext.customDocChunks
|
|
1735
|
+
: options.relevantCustomDocChunks?.length > 0
|
|
1736
|
+
? options.relevantCustomDocChunks
|
|
1737
|
+
: customDocs, // Use chunks only when available, then fall back to full docs
|
|
1733
1738
|
projectSummary: projectSummary, // Add project architecture summary
|
|
1734
1739
|
metadata: {
|
|
1735
1740
|
hasCodeExamples: unifiedContext.codeExamples.length > 0,
|
package/src/rag-analyzer.test.js
CHANGED
|
@@ -762,6 +762,19 @@ describe('rag-analyzer', () => {
|
|
|
762
762
|
expect(result.success).toBe(true);
|
|
763
763
|
});
|
|
764
764
|
|
|
765
|
+
it('should fall back to full custom docs when no relevant chunks are found', async () => {
|
|
766
|
+
mockEmbeddingsSystem.getExistingCustomDocumentChunks.mockResolvedValue([
|
|
767
|
+
{ id: 'existing', content: 'Existing indexed chunk', document_title: 'Indexed Doc', chunk_index: 0 },
|
|
768
|
+
]);
|
|
769
|
+
mockEmbeddingsSystem.findRelevantCustomDocChunks.mockResolvedValue([]);
|
|
770
|
+
|
|
771
|
+
await runAnalysis('/test/file.js', {
|
|
772
|
+
customDocs: [{ content: 'Mandatory internal review rule', document_title: 'Internal Standards' }],
|
|
773
|
+
});
|
|
774
|
+
|
|
775
|
+
expect(llm.sendPromptToClaude).toHaveBeenCalledWith(expect.stringContaining('Mandatory internal review rule'), expect.any(Object));
|
|
776
|
+
});
|
|
777
|
+
|
|
765
778
|
it('should log selected chunks when verbose', async () => {
|
|
766
779
|
mockEmbeddingsSystem.findRelevantCustomDocChunks.mockResolvedValue([
|
|
767
780
|
{ id: 'chunk1', content: 'Guidelines', document_title: 'Guide', chunk_index: 0, similarity: 0.85 },
|
package/src/rag-review.js
CHANGED
|
@@ -230,7 +230,17 @@ async function reviewPullRequestWithCrossFileContext(filesToReview, options = {}
|
|
|
230
230
|
verboseLog(verbose, chalk.gray(`Base branch: ${baseBranch}, Target branch: ${targetBranch}`));
|
|
231
231
|
|
|
232
232
|
const prFiles = [];
|
|
233
|
-
|
|
233
|
+
if (options.preloadedPRFiles?.length > 0) {
|
|
234
|
+
prFiles.push(
|
|
235
|
+
...options.preloadedPRFiles.map((file) => ({
|
|
236
|
+
...file,
|
|
237
|
+
baseBranch: file.baseBranch || baseBranch,
|
|
238
|
+
targetBranch: file.targetBranch || targetBranch,
|
|
239
|
+
}))
|
|
240
|
+
);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
for (const filePath of options.preloadedPRFiles?.length > 0 ? [] : filesToReview) {
|
|
234
244
|
try {
|
|
235
245
|
// Check if the file should be processed before fetching its content from git
|
|
236
246
|
if (!shouldProcessFile(filePath, '', options)) {
|
|
@@ -596,6 +606,7 @@ async function reviewPRChunk(chunk, sharedContext, options, chunkNumber, totalCh
|
|
|
596
606
|
|
|
597
607
|
// Skip chunking decision for chunk reviews to prevent infinite recursion
|
|
598
608
|
const skipChunkingOptions = { ...chunkOptions, skipChunking: true };
|
|
609
|
+
skipChunkingOptions.preloadedPRFiles = chunk.files;
|
|
599
610
|
|
|
600
611
|
return await reviewPullRequestWithCrossFileContext(chunkFilePaths, skipChunkingOptions);
|
|
601
612
|
}
|