codecritique 1.0.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.
Files changed (40) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +1145 -0
  3. package/package.json +98 -0
  4. package/src/content-retrieval.js +747 -0
  5. package/src/custom-documents.js +597 -0
  6. package/src/embeddings/cache-manager.js +364 -0
  7. package/src/embeddings/constants.js +40 -0
  8. package/src/embeddings/database.js +921 -0
  9. package/src/embeddings/errors.js +208 -0
  10. package/src/embeddings/factory.js +447 -0
  11. package/src/embeddings/file-processor.js +851 -0
  12. package/src/embeddings/model-manager.js +337 -0
  13. package/src/embeddings/similarity-calculator.js +97 -0
  14. package/src/embeddings/types.js +113 -0
  15. package/src/feedback-loader.js +384 -0
  16. package/src/index.js +1418 -0
  17. package/src/llm.js +123 -0
  18. package/src/pr-history/analyzer.js +579 -0
  19. package/src/pr-history/bot-detector.js +123 -0
  20. package/src/pr-history/cli-utils.js +204 -0
  21. package/src/pr-history/comment-processor.js +549 -0
  22. package/src/pr-history/database.js +819 -0
  23. package/src/pr-history/github-client.js +629 -0
  24. package/src/project-analyzer.js +955 -0
  25. package/src/rag-analyzer.js +2764 -0
  26. package/src/rag-review.js +566 -0
  27. package/src/technology-keywords.json +753 -0
  28. package/src/utils/command.js +48 -0
  29. package/src/utils/constants.js +263 -0
  30. package/src/utils/context-inference.js +364 -0
  31. package/src/utils/document-detection.js +105 -0
  32. package/src/utils/file-validation.js +271 -0
  33. package/src/utils/git.js +232 -0
  34. package/src/utils/language-detection.js +170 -0
  35. package/src/utils/logging.js +24 -0
  36. package/src/utils/markdown.js +132 -0
  37. package/src/utils/mobilebert-tokenizer.js +141 -0
  38. package/src/utils/pr-chunking.js +276 -0
  39. package/src/utils/string-utils.js +28 -0
  40. package/src/zero-shot-classifier-open.js +392 -0
@@ -0,0 +1,566 @@
1
+ /**
2
+ * RAG Review Module
3
+ *
4
+ * This module serves as the main entry point for the dynamic, context-augmented
5
+ * code review process. It coordinates file discovery and analysis,
6
+ * relying on dynamic context retrieval via embeddings.
7
+ */
8
+
9
+ import path from 'path';
10
+ import chalk from 'chalk';
11
+ import { runAnalysis, gatherUnifiedContextForPR } from './rag-analyzer.js';
12
+ import { shouldProcessFile } from './utils/file-validation.js';
13
+ import { findBaseBranch, getChangedLinesInfo, getFileContentFromGit } from './utils/git.js';
14
+ import { detectFileType, detectLanguageFromExtension } from './utils/language-detection.js';
15
+ import { shouldChunkPR, chunkPRFiles, combineChunkResults } from './utils/pr-chunking.js';
16
+
17
+ /**
18
+ * Review a single file using RAG approach
19
+ *
20
+ * @param {string} filePath - Path to the file to review
21
+ * @param {object} options - Review options
22
+ * @returns {Promise<object>} Review result object
23
+ */
24
+ async function reviewFile(filePath, options = {}) {
25
+ try {
26
+ console.log(chalk.blue(`Reviewing file: ${filePath}`));
27
+
28
+ // Analyze the file using the RAG analyzer
29
+ const analyzeResult = await runAnalysis(filePath, options);
30
+
31
+ // If analysis successful, return the result
32
+ if (analyzeResult.success) {
33
+ // Convert object results to array format expected by the output functions
34
+ if (analyzeResult.results && !Array.isArray(analyzeResult.results)) {
35
+ console.log(chalk.blue('Converting results object to array format'));
36
+
37
+ // Create a new array with one entry containing the object results
38
+ const resultArray = [
39
+ {
40
+ filePath: analyzeResult.filePath,
41
+ language: analyzeResult.language,
42
+ success: true,
43
+ results: analyzeResult.results,
44
+ },
45
+ ];
46
+
47
+ return {
48
+ success: true,
49
+ results: resultArray,
50
+ };
51
+ }
52
+
53
+ return analyzeResult;
54
+ }
55
+
56
+ // If analysis failed, return the error
57
+ return analyzeResult;
58
+ } catch (error) {
59
+ console.error(chalk.red(`Error reviewing file ${filePath}:`), error.message);
60
+ return {
61
+ success: false,
62
+ error: error.message,
63
+ filePath,
64
+ };
65
+ }
66
+ }
67
+
68
+ /**
69
+ * Review multiple files using dynamic context retrieval.
70
+ *
71
+ * @param {Array<string>} filePaths - Paths to the files to review
72
+ * @param {Object} options - Review options (passed to each reviewFile call)
73
+ * @returns {Promise<Object>} Aggregated review results { success: boolean, results: Array<Object>, message: string, error?: string }
74
+ */
75
+ async function reviewFiles(filePaths, options = {}) {
76
+ try {
77
+ const verbose = options.verbose || false;
78
+ if (verbose) {
79
+ console.log(chalk.blue(`Reviewing ${filePaths.length} files...`));
80
+ }
81
+
82
+ // Review files concurrently
83
+ const results = [];
84
+ const concurrency = options.concurrency || 3; // Limit concurrency for API calls/CPU usage
85
+
86
+ // Process files in batches to limit concurrency
87
+ for (let i = 0; i < filePaths.length; i += concurrency) {
88
+ const batch = filePaths.slice(i, i + concurrency);
89
+
90
+ if (verbose) {
91
+ console.log(
92
+ chalk.blue(
93
+ `Processing review batch ${Math.floor(i / concurrency) + 1}/${Math.ceil(filePaths.length / concurrency)} (${
94
+ batch.length
95
+ } files)`
96
+ )
97
+ );
98
+ }
99
+
100
+ // Pass options down to reviewFile
101
+ const batchPromises = batch.map((filePath) => reviewFile(filePath, options));
102
+ const batchResults = await Promise.all(batchPromises);
103
+
104
+ results.push(...batchResults);
105
+ }
106
+
107
+ // Filter out potential null results if any step could return null/undefined (though analyzeFile should always return an object)
108
+ const validResults = results.filter((r) => r != null);
109
+ const successCount = validResults.filter((r) => r.success && !r.skipped).length;
110
+ const skippedCount = validResults.filter((r) => r.skipped).length;
111
+ const errorCount = validResults.filter((r) => !r.success).length;
112
+
113
+ let finalMessage = `Review completed for ${filePaths.length} files. `;
114
+ finalMessage += `Success: ${successCount}, Skipped: ${skippedCount}, Errors: ${errorCount}.`;
115
+
116
+ console.log(chalk.green(finalMessage));
117
+
118
+ return {
119
+ success: errorCount === 0,
120
+ results: validResults, // Return array of individual file results
121
+ message: finalMessage,
122
+ };
123
+ } catch (error) {
124
+ console.error(chalk.red(`Error reviewing multiple files: ${error.message}`));
125
+ console.error(error.stack);
126
+ return {
127
+ success: false,
128
+ error: error.message,
129
+ results: [],
130
+ message: 'Failed to review files due to an unexpected error',
131
+ };
132
+ }
133
+ }
134
+
135
+ /**
136
+ * Review files changed in a pull request (requires changed file paths).
137
+ *
138
+ * @param {Array<string>} changedFilePaths - Array of file paths changed in the PR.
139
+ * @param {Object} options - Review options (passed to reviewFiles).
140
+ * @returns {Promise<Object>} Aggregated review results.
141
+ */
142
+ async function reviewPullRequest(changedFilePaths, options = {}) {
143
+ try {
144
+ const verbose = options.verbose || false;
145
+ if (verbose) {
146
+ console.log(chalk.blue(`Reviewing ${changedFilePaths.length} changed files from PR...`));
147
+ }
148
+
149
+ // No longer filter files here, as new files in a different branch won't exist locally.
150
+ // The downstream functions are responsible for fetching content from git.
151
+ const filesToReview = changedFilePaths;
152
+
153
+ if (filesToReview.length === 0) {
154
+ const message = 'No processable files found among the changed files provided for PR review.';
155
+ console.log(chalk.yellow(message));
156
+ return {
157
+ success: true,
158
+ message: message,
159
+ results: [],
160
+ };
161
+ }
162
+
163
+ if (verbose) {
164
+ console.log(chalk.green(`Reviewing ${filesToReview.length} existing and processable changed files`));
165
+ }
166
+
167
+ // Use enhanced PR review with cross-file context
168
+ return await reviewPullRequestWithCrossFileContext(filesToReview, options);
169
+ } catch (error) {
170
+ console.error(chalk.red(`Error reviewing pull request files: ${error.message}`));
171
+ console.error(error.stack);
172
+ return {
173
+ success: false,
174
+ error: error.message,
175
+ message: 'Failed to review pull request files',
176
+ results: [],
177
+ };
178
+ }
179
+ }
180
+
181
+ /**
182
+ * Enhanced PR review with cross-file context and de-duplicated resources
183
+ *
184
+ * @param {Array<string>} filesToReview - Array of file paths to review
185
+ * @param {Object} options - Review options
186
+ * @returns {Promise<Object>} Aggregated review results
187
+ */
188
+ async function reviewPullRequestWithCrossFileContext(filesToReview, options = {}) {
189
+ try {
190
+ const verbose = options.verbose || false;
191
+ if (verbose) {
192
+ console.log(chalk.blue(`Starting enhanced PR review with cross-file context for ${filesToReview.length} files...`));
193
+ }
194
+
195
+ // Step 1: Get the base branch and collect diff info for all files in the PR
196
+ const workingDir = options.directory ? path.resolve(options.directory) : process.cwd();
197
+ const baseBranch = findBaseBranch(workingDir);
198
+ const targetBranch = options.diffWith || 'HEAD'; // The feature branch being reviewed
199
+
200
+ // Get the actual branch name from options passed from index.js
201
+ const actualTargetBranch = options.actualBranch || targetBranch;
202
+
203
+ if (verbose) {
204
+ console.log(chalk.gray(`Base branch: ${baseBranch}, Target branch: ${targetBranch}`));
205
+ }
206
+
207
+ const prFiles = [];
208
+ for (const filePath of filesToReview) {
209
+ try {
210
+ // Check if the file should be processed before fetching its content from git
211
+ if (!shouldProcessFile(filePath, '', options)) {
212
+ if (verbose) {
213
+ console.log(chalk.yellow(`Skipping file due to exclusion rules: ${path.basename(filePath)}`));
214
+ }
215
+ continue;
216
+ }
217
+
218
+ const content = getFileContentFromGit(filePath, actualTargetBranch, workingDir);
219
+ const language = detectLanguageFromExtension(path.extname(filePath));
220
+ const fileType = detectFileType(filePath, content);
221
+
222
+ // Get the git diff for this file
223
+ const diffInfo = getChangedLinesInfo(filePath, baseBranch, actualTargetBranch, workingDir);
224
+
225
+ if (!diffInfo.hasChanges) {
226
+ if (verbose) {
227
+ console.log(chalk.yellow(`No changes detected in ${path.basename(filePath)}, skipping`));
228
+ }
229
+ continue;
230
+ }
231
+
232
+ // Create a summary of changes for context
233
+ const changesSummary = `${diffInfo.addedLines.length} lines added, ${diffInfo.removedLines.length} lines removed`;
234
+
235
+ prFiles.push({
236
+ filePath,
237
+ content, // Keep full content for context gathering
238
+ diffContent: diffInfo.fullDiff, // The actual diff to review
239
+ diffInfo: diffInfo, // Parsed diff info
240
+ language,
241
+ fileType,
242
+ isTest: fileType.isTest,
243
+ isComponent: content.includes('export default') || content.includes('export const') || content.includes('export function'),
244
+ summary: `${language} ${fileType.isTest ? 'test' : 'source'} file: ${path.basename(filePath)} (${changesSummary})`,
245
+ baseBranch,
246
+ targetBranch,
247
+ });
248
+ } catch (error) {
249
+ console.warn(chalk.yellow(`Error processing file ${filePath}: ${error.message}`));
250
+ }
251
+ }
252
+
253
+ if (prFiles.length === 0) {
254
+ console.log(chalk.yellow('No files with changes found for review'));
255
+ return {
256
+ success: true,
257
+ results: [],
258
+ prContext: { message: 'No changes to review' },
259
+ };
260
+ }
261
+
262
+ // Check if PR should be chunked based on size and complexity (skip if this is already a chunk)
263
+ if (!options.skipChunking) {
264
+ const chunkingDecision = shouldChunkPR(prFiles);
265
+ if (verbose) {
266
+ console.log(chalk.blue(`PR size assessment: ${chunkingDecision.estimatedTokens} tokens, ${prFiles.length} files`));
267
+ if (chunkingDecision.shouldChunk) {
268
+ console.log(chalk.yellow(`Large PR detected - will chunk into ~${chunkingDecision.recommendedChunks} chunks`));
269
+ }
270
+ }
271
+
272
+ // If PR is too large, use chunked processing
273
+ if (chunkingDecision.shouldChunk) {
274
+ console.log(chalk.blue(`🔄 Using chunked processing for large PR (${chunkingDecision.estimatedTokens} tokens)`));
275
+ return await reviewLargePRInChunks(prFiles, options);
276
+ }
277
+ }
278
+
279
+ // Step 2: Gather unified context for the entire PR (for regular-sized PRs)
280
+ if (verbose) {
281
+ console.log(chalk.blue(`Performing unified context retrieval for ${prFiles.length} PR files...`));
282
+ }
283
+ const {
284
+ codeExamples: deduplicatedCodeExamples,
285
+ guidelines: deduplicatedGuidelines,
286
+ prComments: deduplicatedPRComments,
287
+ customDocChunks: deduplicatedCustomDocChunks,
288
+ } = await gatherUnifiedContextForPR(prFiles, options);
289
+
290
+ if (verbose) {
291
+ console.log(
292
+ chalk.green(
293
+ `De-duplicated context: ${deduplicatedCodeExamples.length} code examples, ${deduplicatedGuidelines.length} guidelines, ${deduplicatedPRComments.length} PR comments, ${deduplicatedCustomDocChunks.length} custom doc chunks`
294
+ )
295
+ );
296
+ }
297
+
298
+ // Step 3: Create PR context summary for LLM
299
+ const prContext = {
300
+ allFiles: prFiles.map((f) => ({
301
+ path: path.relative(process.cwd(), f.filePath),
302
+ language: f.language,
303
+ isTest: f.isTest,
304
+ isComponent: f.isComponent,
305
+ summary: f.summary,
306
+ })),
307
+ totalFiles: prFiles.length,
308
+ testFiles: prFiles.filter((f) => f.isTest).length,
309
+ sourceFiles: prFiles.filter((f) => !f.isTest).length,
310
+ };
311
+
312
+ // Step 4: Perform holistic PR review with all files and unified context
313
+ if (verbose) {
314
+ console.log(chalk.blue(`Performing holistic PR review for all ${prFiles.length} files...`));
315
+ }
316
+
317
+ try {
318
+ // Create a comprehensive review context with all files and their diffs
319
+ const comprehensiveContext = {
320
+ prFiles: prFiles.map((file) => ({
321
+ path: path.relative(workingDir, file.filePath),
322
+ language: file.language,
323
+ isTest: file.isTest,
324
+ isComponent: file.isComponent,
325
+ summary: file.summary,
326
+ fullContent: file.content, // Add full file content for context
327
+ diff: file.diffContent,
328
+ baseBranch: file.baseBranch,
329
+ targetBranch: file.targetBranch,
330
+ })),
331
+ unifiedContext: {
332
+ codeExamples: deduplicatedCodeExamples,
333
+ guidelines: deduplicatedGuidelines,
334
+ prComments: deduplicatedPRComments,
335
+ customDocChunks: deduplicatedCustomDocChunks,
336
+ },
337
+ prContext: prContext,
338
+ };
339
+
340
+ // Use the existing analyzeFile function with holistic PR context
341
+ const holisticOptions = {
342
+ ...options,
343
+ isHolisticPRReview: true,
344
+ prFiles: comprehensiveContext.prFiles,
345
+ unifiedContext: comprehensiveContext.unifiedContext,
346
+ prContext: comprehensiveContext.prContext,
347
+ };
348
+
349
+ // Create a synthetic "file" path for holistic analysis
350
+ const holisticResult = await runAnalysis('PR_HOLISTIC_REVIEW', holisticOptions);
351
+
352
+ // Convert holistic result to individual file results format for compatibility
353
+ const results = prFiles.map((file) => {
354
+ const relativePath = path.relative(workingDir, file.filePath);
355
+ const baseName = path.basename(file.filePath);
356
+
357
+ // Try multiple path formats to find file-specific issues
358
+ let fileIssues = [];
359
+ const possibleKeys = [
360
+ relativePath, // Full relative path
361
+ baseName, // Just filename
362
+ file.filePath, // Absolute path
363
+ path.posix.normalize(relativePath), // Normalized posix path
364
+ ];
365
+
366
+ // Find issues using any of the possible key formats
367
+ for (const key of possibleKeys) {
368
+ if (holisticResult?.results?.fileSpecificIssues?.[key]) {
369
+ fileIssues = holisticResult.results.fileSpecificIssues[key];
370
+ console.log(chalk.green(`✅ Found ${fileIssues.length} issues for ${baseName} using key: "${key}"`));
371
+ break;
372
+ }
373
+ }
374
+
375
+ console.log(chalk.gray(`🔍 Mapping issues for ${file.filePath}:`));
376
+ console.log(chalk.gray(` - Relative path: "${relativePath}"`));
377
+ console.log(chalk.gray(` - Tried keys: ${possibleKeys.map((k) => `"${k}"`).join(', ')}`));
378
+ console.log(chalk.gray(` - Final issues: ${fileIssues.length}`));
379
+
380
+ return {
381
+ success: true,
382
+ filePath: file.filePath,
383
+ language: file.language,
384
+ results: {
385
+ summary: `Part of holistic PR review covering ${prFiles.length} files`,
386
+ issues: fileIssues,
387
+ },
388
+ context: {
389
+ codeExamples: deduplicatedCodeExamples.length,
390
+ guidelines: deduplicatedGuidelines.length,
391
+ prComments: deduplicatedPRComments.length,
392
+ customDocChunks: deduplicatedCustomDocChunks.length,
393
+ },
394
+ };
395
+ });
396
+
397
+ // Add holistic analysis to the first result
398
+ if (results.length > 0 && holisticResult?.results) {
399
+ results[0].holisticAnalysis = {
400
+ crossFileIssues: holisticResult.results.crossFileIssues || [],
401
+ overallSummary: holisticResult.results.summary,
402
+ recommendations: holisticResult.results.recommendations || [],
403
+ };
404
+ }
405
+
406
+ return {
407
+ success: true,
408
+ results: results,
409
+ prContext: {
410
+ ...prContext,
411
+ holisticAnalysis: holisticResult,
412
+ contextSummary: {
413
+ codeExamples: deduplicatedCodeExamples.length,
414
+ guidelines: deduplicatedGuidelines.length,
415
+ prComments: deduplicatedPRComments.length,
416
+ customDocChunks: deduplicatedCustomDocChunks.length,
417
+ },
418
+ },
419
+ };
420
+ } catch (error) {
421
+ console.error(chalk.red(`Error in holistic PR review: ${error.message}`));
422
+
423
+ // Fallback to individual file review if holistic review fails
424
+ if (verbose) {
425
+ console.log(chalk.yellow(`Falling back to individual file reviews...`));
426
+ }
427
+
428
+ const results = [];
429
+ const concurrency = options.concurrency || 3;
430
+
431
+ for (let i = 0; i < prFiles.length; i += concurrency) {
432
+ const batch = prFiles.slice(i, i + concurrency);
433
+
434
+ const batchPromises = batch.map(async (file) => {
435
+ try {
436
+ // Enhance options with shared context and diff-only analysis
437
+ const enhancedOptions = {
438
+ ...options,
439
+ // Add PR context for cross-file awareness
440
+ prContext: prContext,
441
+ // Override context gathering to use shared/pre-gathered resources
442
+ preGatheredContext: {
443
+ codeExamples: deduplicatedCodeExamples,
444
+ guidelines: deduplicatedGuidelines,
445
+ prComments: deduplicatedPRComments,
446
+ },
447
+ // Flag to indicate this is part of a PR review
448
+ isPRReview: true,
449
+ // Add diff-specific options
450
+ diffOnly: true,
451
+ diffContent: file.diffContent,
452
+ fullFileContent: file.content, // Pass full file content for context awareness
453
+ diffInfo: file.diffInfo,
454
+ baseBranch: file.baseBranch,
455
+ targetBranch: file.targetBranch,
456
+ // Add context about all files in the PR
457
+ allPRFiles: prContext.allFiles,
458
+ };
459
+
460
+ const result = await runAnalysis(file.filePath, enhancedOptions);
461
+ return result;
462
+ } catch (error) {
463
+ console.error(chalk.red(`Error reviewing ${file.filePath}: ${error.message}`));
464
+ return {
465
+ filePath: file.filePath,
466
+ success: false,
467
+ error: error.message,
468
+ };
469
+ }
470
+ });
471
+
472
+ const batchResults = await Promise.all(batchPromises);
473
+ results.push(...batchResults);
474
+ }
475
+
476
+ // Return fallback results
477
+ return {
478
+ success: true,
479
+ results: results,
480
+ prContext: prContext,
481
+ sharedContextStats: {
482
+ codeExamples: deduplicatedCodeExamples.length,
483
+ guidelines: deduplicatedGuidelines.length,
484
+ prComments: deduplicatedPRComments.length,
485
+ customDocChunks: deduplicatedCustomDocChunks.length,
486
+ },
487
+ };
488
+ }
489
+ } catch (error) {
490
+ console.error(chalk.red(`Error in enhanced PR review: ${error.message}`));
491
+ return {
492
+ success: false,
493
+ error: error.message,
494
+ results: [],
495
+ };
496
+ }
497
+ }
498
+
499
+ /**
500
+ * Reviews a large PR by splitting it into manageable chunks and processing them in parallel
501
+ * @param {Array} prFiles - Array of PR files with diff content
502
+ * @param {Object} options - Review options
503
+ * @returns {Promise<Object>} Combined review results
504
+ */
505
+ async function reviewLargePRInChunks(prFiles, options) {
506
+ console.log(chalk.blue(`🔄 Large PR detected: ${prFiles.length} files. Splitting into chunks...`));
507
+
508
+ // Step 1: Gather shared context once for all chunks
509
+ console.log(chalk.cyan('📚 Gathering shared context for entire PR...'));
510
+ const sharedContext = await gatherUnifiedContextForPR(prFiles, options);
511
+
512
+ // Step 2: Split PR into manageable chunks
513
+ // Each chunk includes both diff AND full file content, plus ~25k context overhead
514
+ const chunks = chunkPRFiles(prFiles, 35000); // Conservative limit accounting for context overhead
515
+ console.log(chalk.green(`✂️ Split PR into ${chunks.length} chunks`));
516
+
517
+ chunks.forEach((chunk, i) => {
518
+ console.log(chalk.gray(` Chunk ${i + 1}: ${chunk.files.length} files (~${chunk.totalTokens} tokens)`));
519
+ });
520
+
521
+ // Step 3: Process chunks in parallel
522
+ console.log(chalk.blue('🔄 Processing chunks in parallel...'));
523
+ const chunkResults = await Promise.all(
524
+ chunks.map((chunk, index) => reviewPRChunk(chunk, sharedContext, options, index + 1, chunks.length))
525
+ );
526
+
527
+ // Step 4: Combine results
528
+ console.log(chalk.blue('🔗 Combining chunk results...'));
529
+ return combineChunkResults(chunkResults, prFiles.length);
530
+ }
531
+
532
+ /**
533
+ * Reviews a single chunk of files from a large PR
534
+ * @param {Object} chunk - Chunk object with files array
535
+ * @param {Object} sharedContext - Pre-gathered shared context
536
+ * @param {Object} options - Review options
537
+ * @param {number} chunkNumber - Current chunk number
538
+ * @param {number} totalChunks - Total number of chunks
539
+ * @returns {Promise<Object>} Chunk review results
540
+ */
541
+ async function reviewPRChunk(chunk, sharedContext, options, chunkNumber, totalChunks) {
542
+ console.log(chalk.cyan(`📝 Reviewing chunk ${chunkNumber}/${totalChunks} (${chunk.files.length} files)...`));
543
+
544
+ // Create chunk-specific options
545
+ const chunkOptions = {
546
+ ...options,
547
+ isChunkedReview: true,
548
+ chunkNumber: chunkNumber,
549
+ totalChunks: totalChunks,
550
+ preGatheredContext: sharedContext, // Use shared context
551
+ // Reduce context per chunk since we have multiple parallel reviews
552
+ maxExamples: Math.max(3, Math.floor((options.maxExamples || 40) / totalChunks)),
553
+ };
554
+
555
+ // Review this chunk as a smaller PR - call the main function recursively but with chunked flag
556
+ // to prevent infinite recursion
557
+ const chunkFilePaths = chunk.files.map((f) => f.filePath);
558
+
559
+ // Skip chunking decision for chunk reviews to prevent infinite recursion
560
+ const skipChunkingOptions = { ...chunkOptions, skipChunking: true };
561
+
562
+ return await reviewPullRequestWithCrossFileContext(chunkFilePaths, skipChunkingOptions);
563
+ }
564
+
565
+ // Export the core review functions
566
+ export { reviewFile, reviewFiles, reviewPullRequest };