codecritique 2.0.8 → 2.1.0
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/rag-analyzer.js +12 -10
- package/src/rag-analyzer.test.js +64 -0
- package/src/rag-review.js +10 -3
- package/src/rag-review.test.js +3 -2
package/package.json
CHANGED
package/src/rag-analyzer.js
CHANGED
|
@@ -27,6 +27,7 @@ import { isGenericDocument, getGenericDocumentContext } from './utils/document-d
|
|
|
27
27
|
import { isTestFile, shouldProcessFile } from './utils/file-validation.js';
|
|
28
28
|
import { detectFileType, detectLanguageFromExtension } from './utils/language-detection.js';
|
|
29
29
|
import { debug, verboseLog } from './utils/logging.js';
|
|
30
|
+
import { formatPlannedHolisticFileContext, mergeHolisticFileContextPlans } from './utils/pr-file-context.js';
|
|
30
31
|
import { addLineNumbers } from './utils/string-utils.js';
|
|
31
32
|
|
|
32
33
|
// Constants for content processing
|
|
@@ -1376,23 +1377,24 @@ ${g.content}
|
|
|
1376
1377
|
|
|
1377
1378
|
// Format PR files with their diffs
|
|
1378
1379
|
const prFiles = file.prFiles || [];
|
|
1380
|
+
const holisticContextPlans = mergeHolisticFileContextPlans(prFiles, context.options || {});
|
|
1379
1381
|
const formattedPRFiles = prFiles
|
|
1380
1382
|
.map((prFile, idx) => {
|
|
1383
|
+
const fileContextPlan = holisticContextPlans[idx];
|
|
1384
|
+
const fileContext = formatPlannedHolisticFileContext(prFile, fileContextPlan, context.options || {});
|
|
1381
1385
|
return `
|
|
1382
1386
|
## FILE ${idx + 1}: ${prFile.path}
|
|
1383
1387
|
**Language**: ${prFile.language}
|
|
1384
1388
|
**Type**: ${prFile.isTest ? 'Test' : 'Source'} file
|
|
1385
1389
|
**Summary**: ${prFile.summary}
|
|
1390
|
+
**Context Mode**: ${fileContextPlan.mode === 'full' ? 'Full file content' : 'Focused changed-line context'}
|
|
1386
1391
|
|
|
1387
1392
|
### Changes (Git Diff):
|
|
1388
1393
|
\`\`\`diff
|
|
1389
1394
|
${prFile.diff}
|
|
1390
1395
|
\`\`\`
|
|
1391
1396
|
|
|
1392
|
-
|
|
1393
|
-
\`\`\`${prFile.language}
|
|
1394
|
-
${addLineNumbers(prFile.fullContent)}
|
|
1395
|
-
\`\`\`
|
|
1397
|
+
${fileContext}
|
|
1396
1398
|
`;
|
|
1397
1399
|
})
|
|
1398
1400
|
.join('\n');
|
|
@@ -1406,17 +1408,17 @@ ${addLineNumbers(prFile.fullContent)}
|
|
|
1406
1408
|
**CRITICAL CONTEXT AWARENESS INSTRUCTIONS:**
|
|
1407
1409
|
|
|
1408
1410
|
For each file in this PR, you have access to:
|
|
1409
|
-
1. **
|
|
1411
|
+
1. **FILE CONTEXT** - Complete file content when it fits the holistic context budget, or focused changed-line windows for very large files/PRs
|
|
1410
1412
|
2. **GIT DIFF** - Only the changes to review
|
|
1411
1413
|
|
|
1412
1414
|
**Review Rules:**
|
|
1413
1415
|
- ONLY critique the CHANGED lines shown in each file's diff (lines with + or -)
|
|
1414
|
-
- USE the
|
|
1416
|
+
- USE the file context to understand dependencies, nearby behavior, and existing implementations
|
|
1415
1417
|
- DO NOT suggest adding code that already exists in the unchanged portions of any file
|
|
1416
|
-
- DO NOT flag issues about missing code
|
|
1417
|
-
- Before flagging cross-file issues, verify the code doesn't already exist in
|
|
1418
|
-
- Do NOT flag functions/variables as missing if they exist elsewhere in the
|
|
1419
|
-
- The unchanged
|
|
1418
|
+
- DO NOT flag issues about missing code when the provided context already shows the code exists
|
|
1419
|
+
- Before flagging cross-file issues, verify the code doesn't already exist in the provided context
|
|
1420
|
+
- Do NOT flag functions/variables as missing if they exist elsewhere in the provided context
|
|
1421
|
+
- The unchanged context is part of each file - always check it before making assumptions`;
|
|
1420
1422
|
|
|
1421
1423
|
let roleDefinition = buildRoleDefinition(baseRole, customDocs, 'pr');
|
|
1422
1424
|
roleDefinition += '\nAnalyze ALL files together to identify cross-file issues, consistency problems, and overall code quality.';
|
package/src/rag-analyzer.test.js
CHANGED
|
@@ -578,6 +578,70 @@ describe('rag-analyzer', () => {
|
|
|
578
578
|
);
|
|
579
579
|
expect(result.success).toBe(true);
|
|
580
580
|
});
|
|
581
|
+
|
|
582
|
+
it('should use focused context for oversized holistic PR files', async () => {
|
|
583
|
+
const hugeContent = Array.from({ length: 5000 }, (_, index) => `huge line ${index + 1}`).join('\n');
|
|
584
|
+
llm.sendPromptToClaude.mockResolvedValue(createMockHolisticReviewResponse());
|
|
585
|
+
|
|
586
|
+
const result = await runAnalysis(
|
|
587
|
+
'PR_HOLISTIC_REVIEW',
|
|
588
|
+
setupHolisticReviewOptions({
|
|
589
|
+
prFiles: [
|
|
590
|
+
createMockPRFile({
|
|
591
|
+
path: 'src/small.js',
|
|
592
|
+
fullContent: 'const small = true;',
|
|
593
|
+
diff: '@@ -1,1 +1,1 @@\n-const small = false;\n+const small = true;',
|
|
594
|
+
}),
|
|
595
|
+
createMockPRFile({
|
|
596
|
+
path: 'src/huge.js',
|
|
597
|
+
fullContent: hugeContent,
|
|
598
|
+
diff: '@@ -2500,1 +2500,1 @@\n-huge line 2500\n+huge line 2500 changed',
|
|
599
|
+
}),
|
|
600
|
+
],
|
|
601
|
+
prContext: { totalFiles: 2 },
|
|
602
|
+
maxTotalFullContentTokens: 1000,
|
|
603
|
+
})
|
|
604
|
+
);
|
|
605
|
+
|
|
606
|
+
const prompt = llm.sendPromptToClaude.mock.calls.at(-1)[0];
|
|
607
|
+
expect(result.success).toBe(true);
|
|
608
|
+
expect(prompt).toContain('**Context Mode**: Full file content');
|
|
609
|
+
expect(prompt).toContain('**Context Mode**: Focused changed-line context');
|
|
610
|
+
expect(prompt).toContain('const small = true;');
|
|
611
|
+
expect(prompt).toContain('Focused File Context');
|
|
612
|
+
expect(prompt).toContain('2500 | huge line 2500');
|
|
613
|
+
expect(prompt).not.toContain('5000 | huge line 5000');
|
|
614
|
+
});
|
|
615
|
+
|
|
616
|
+
it('should preserve carried holistic context plans for chunked reviews', async () => {
|
|
617
|
+
llm.sendPromptToClaude.mockResolvedValue(createMockHolisticReviewResponse());
|
|
618
|
+
|
|
619
|
+
await runAnalysis(
|
|
620
|
+
'PR_HOLISTIC_REVIEW',
|
|
621
|
+
setupHolisticReviewOptions({
|
|
622
|
+
prFiles: [
|
|
623
|
+
createMockPRFile({
|
|
624
|
+
path: 'src/chunked.js',
|
|
625
|
+
fullContent: 'const wouldFit = true;',
|
|
626
|
+
diff: '@@ -1,1 +1,1 @@\n-const wouldFit = false;\n+const wouldFit = true;',
|
|
627
|
+
holisticContextPlan: {
|
|
628
|
+
mode: 'focused',
|
|
629
|
+
reason: 'global budget assigned focused context before chunking',
|
|
630
|
+
contextTokens: 5,
|
|
631
|
+
totalTokens: 20,
|
|
632
|
+
contextLineRadius: 0,
|
|
633
|
+
},
|
|
634
|
+
}),
|
|
635
|
+
],
|
|
636
|
+
prContext: { totalFiles: 1 },
|
|
637
|
+
})
|
|
638
|
+
);
|
|
639
|
+
|
|
640
|
+
const prompt = llm.sendPromptToClaude.mock.calls.at(-1)[0];
|
|
641
|
+
expect(prompt).toContain('**Context Mode**: Focused changed-line context');
|
|
642
|
+
expect(prompt).toContain('global budget assigned focused context before chunking');
|
|
643
|
+
expect(prompt).not.toContain('### Full File Content');
|
|
644
|
+
});
|
|
581
645
|
});
|
|
582
646
|
|
|
583
647
|
// ==========================================================================
|
package/src/rag-review.js
CHANGED
|
@@ -14,6 +14,7 @@ import { ensureBranchExists, findParentBranch, getChangedLinesInfo, getFileConte
|
|
|
14
14
|
import { detectFileType, detectLanguageFromExtension } from './utils/language-detection.js';
|
|
15
15
|
import { verboseLog } from './utils/logging.js';
|
|
16
16
|
import { shouldChunkPR, chunkPRFiles, combineChunkResults } from './utils/pr-chunking.js';
|
|
17
|
+
import { mergeHolisticFileContextPlans } from './utils/pr-file-context.js';
|
|
17
18
|
|
|
18
19
|
/**
|
|
19
20
|
* Review a single file using RAG approach
|
|
@@ -308,8 +309,10 @@ async function reviewPullRequestWithCrossFileContext(filesToReview, options = {}
|
|
|
308
309
|
}
|
|
309
310
|
|
|
310
311
|
// Check if PR should be chunked based on size and complexity (skip if this is already a chunk)
|
|
312
|
+
let holisticContextPlans = options.holisticContextPlans;
|
|
311
313
|
if (!options.skipChunking) {
|
|
312
314
|
const chunkingDecision = shouldChunkPR(prFiles, options);
|
|
315
|
+
holisticContextPlans = chunkingDecision.holisticContextPlans;
|
|
313
316
|
verboseLog(verbose, chalk.blue(`PR size assessment: ${chunkingDecision.estimatedTokens} tokens, ${prFiles.length} files`));
|
|
314
317
|
verboseLog(
|
|
315
318
|
verbose && chunkingDecision.shouldChunk,
|
|
@@ -319,10 +322,12 @@ async function reviewPullRequestWithCrossFileContext(filesToReview, options = {}
|
|
|
319
322
|
// If PR is too large, use chunked processing
|
|
320
323
|
if (chunkingDecision.shouldChunk) {
|
|
321
324
|
verboseLog(options, chalk.blue(`🔄 Using chunked processing for large PR (${chunkingDecision.estimatedTokens} tokens)`));
|
|
322
|
-
return await reviewLargePRInChunks(prFiles, options);
|
|
325
|
+
return await reviewLargePRInChunks(prFiles, { ...options, holisticContextPlans });
|
|
323
326
|
}
|
|
324
327
|
}
|
|
325
328
|
|
|
329
|
+
holisticContextPlans = mergeHolisticFileContextPlans(prFiles, options, holisticContextPlans);
|
|
330
|
+
|
|
326
331
|
// Step 2: Gather unified context for the entire PR (for regular-sized PRs)
|
|
327
332
|
verboseLog(verbose, chalk.blue(`Performing unified context retrieval for ${prFiles.length} PR files...`));
|
|
328
333
|
const {
|
|
@@ -361,7 +366,7 @@ async function reviewPullRequestWithCrossFileContext(filesToReview, options = {}
|
|
|
361
366
|
try {
|
|
362
367
|
// Create a comprehensive review context with all files and their diffs
|
|
363
368
|
const comprehensiveContext = {
|
|
364
|
-
prFiles: prFiles.map((file) => ({
|
|
369
|
+
prFiles: prFiles.map((file, index) => ({
|
|
365
370
|
path: path.relative(workingDir, file.filePath),
|
|
366
371
|
language: file.language,
|
|
367
372
|
isTest: file.isTest,
|
|
@@ -369,6 +374,8 @@ async function reviewPullRequestWithCrossFileContext(filesToReview, options = {}
|
|
|
369
374
|
summary: file.summary,
|
|
370
375
|
fullContent: file.content, // Add full file content for context
|
|
371
376
|
diff: file.diffContent,
|
|
377
|
+
diffInfo: file.diffInfo,
|
|
378
|
+
holisticContextPlan: file.holisticContextPlan || holisticContextPlans[index],
|
|
372
379
|
baseBranch: file.baseBranch,
|
|
373
380
|
targetBranch: file.targetBranch,
|
|
374
381
|
})),
|
|
@@ -557,7 +564,7 @@ async function reviewLargePRInChunks(prFiles, options) {
|
|
|
557
564
|
|
|
558
565
|
// Step 2: Split PR into manageable chunks
|
|
559
566
|
// Each chunk includes both diff AND full file content, plus ~25k context overhead
|
|
560
|
-
const chunks = chunkPRFiles(prFiles, 35000); // Conservative limit accounting for context overhead
|
|
567
|
+
const chunks = chunkPRFiles(prFiles, 35000, options); // Conservative limit accounting for context overhead
|
|
561
568
|
verboseLog(options, chalk.green(`✂️ Split PR into ${chunks.length} chunks`));
|
|
562
569
|
|
|
563
570
|
chunks.forEach((chunk, i) => {
|
package/src/rag-review.test.js
CHANGED
|
@@ -270,7 +270,8 @@ describe('rag-review', () => {
|
|
|
270
270
|
});
|
|
271
271
|
|
|
272
272
|
// Setup: shouldChunkPR returns true to trigger chunked processing
|
|
273
|
-
|
|
273
|
+
const holisticContextPlans = [{ mode: 'focused', contextTokens: 10, totalTokens: 20 }];
|
|
274
|
+
shouldChunkPR.mockReturnValue({ shouldChunk: true, estimatedTokens: 100000, recommendedChunks: 2, holisticContextPlans });
|
|
274
275
|
|
|
275
276
|
// Setup chunks that will be processed
|
|
276
277
|
chunkPRFiles.mockReturnValue([
|
|
@@ -298,7 +299,7 @@ describe('rag-review', () => {
|
|
|
298
299
|
|
|
299
300
|
// Verify chunking flow was triggered
|
|
300
301
|
expect(shouldChunkPR).toHaveBeenCalled();
|
|
301
|
-
expect(chunkPRFiles).
|
|
302
|
+
expect(chunkPRFiles).toHaveBeenCalledWith(expect.any(Array), 35000, expect.objectContaining({ holisticContextPlans }));
|
|
302
303
|
expect(combineChunkResults).toHaveBeenCalled();
|
|
303
304
|
expect(result.success).toBe(true);
|
|
304
305
|
});
|