@juspay/yama 1.1.1 โ†’ 1.3.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.
@@ -5,6 +5,7 @@
5
5
  // NeuroLink will be dynamically imported
6
6
  import { ProviderError, } from "../types/index.js";
7
7
  import { logger } from "../utils/Logger.js";
8
+ import { getProviderTokenLimit } from "../utils/ProviderLimits.js";
8
9
  export class CodeReviewer {
9
10
  neurolink;
10
11
  bitbucketProvider;
@@ -16,22 +17,37 @@ export class CodeReviewer {
16
17
  this.reviewConfig = reviewConfig;
17
18
  }
18
19
  /**
19
- * Review code using pre-gathered unified context (OPTIMIZED)
20
+ * Review code using pre-gathered unified context (OPTIMIZED with Batch Processing)
20
21
  */
21
22
  async reviewCodeWithContext(context, options) {
22
23
  const startTime = Date.now();
23
24
  try {
24
25
  logger.phase("๐Ÿงช Conducting AI-powered code analysis...");
25
26
  logger.info(`Analyzing ${context.diffStrategy.fileCount} files using ${context.diffStrategy.strategy} strategy`);
26
- const analysisPrompt = this.buildAnalysisPrompt(context, options);
27
- const violations = await this.analyzeWithAI(analysisPrompt, context);
27
+ // Determine if we should use batch processing
28
+ const batchConfig = this.getBatchProcessingConfig();
29
+ const shouldUseBatchProcessing = this.shouldUseBatchProcessing(context, batchConfig);
30
+ let violations;
31
+ let processingStrategy;
32
+ if (shouldUseBatchProcessing) {
33
+ logger.info("๐Ÿ”„ Using batch processing for large PR analysis");
34
+ const batchResult = await this.reviewWithBatchProcessing(context, options, batchConfig);
35
+ violations = batchResult.violations;
36
+ processingStrategy = "batch-processing";
37
+ }
38
+ else {
39
+ logger.info("โšก Using single-request analysis for small PR");
40
+ const analysisPrompt = this.buildAnalysisPrompt(context, options);
41
+ violations = await this.analyzeWithAI(analysisPrompt, context);
42
+ processingStrategy = "single-request";
43
+ }
28
44
  const validatedViolations = this.validateViolations(violations, context);
29
45
  if (!options.dryRun && validatedViolations.length > 0) {
30
46
  await this.postComments(context, validatedViolations, options);
31
47
  }
32
48
  const duration = Math.round((Date.now() - startTime) / 1000);
33
- const result = this.generateReviewResult(validatedViolations, duration, context);
34
- logger.success(`Code review completed in ${duration}s: ${validatedViolations.length} violations found`);
49
+ const result = this.generateReviewResult(validatedViolations, duration, context, processingStrategy);
50
+ logger.success(`Code review completed in ${duration}s: ${validatedViolations.length} violations found (${processingStrategy})`);
35
51
  return result;
36
52
  }
37
53
  catch (error) {
@@ -397,6 +413,23 @@ Return ONLY valid JSON:
397
413
  // Legacy method - now delegates to new structure
398
414
  return this.buildCoreAnalysisPrompt(context);
399
415
  }
416
+ /**
417
+ * Get safe token limit based on AI provider using shared utility
418
+ */
419
+ getSafeTokenLimit() {
420
+ const provider = this.aiConfig.provider || "auto";
421
+ const configuredTokens = this.aiConfig.maxTokens;
422
+ // Use conservative limits for CodeReviewer (safer for large diffs)
423
+ const providerLimit = getProviderTokenLimit(provider, true);
424
+ // Use the smaller of configured tokens or provider limit
425
+ if (configuredTokens && configuredTokens > 0) {
426
+ const safeLimit = Math.min(configuredTokens, providerLimit);
427
+ logger.debug(`Token limit: configured=${configuredTokens}, provider=${providerLimit}, using=${safeLimit}`);
428
+ return safeLimit;
429
+ }
430
+ logger.debug(`Token limit: using provider default=${providerLimit} for ${provider}`);
431
+ return providerLimit;
432
+ }
400
433
  /**
401
434
  * Analyze code with AI using the enhanced prompt
402
435
  */
@@ -427,13 +460,18 @@ Return ONLY valid JSON:
427
460
  };
428
461
  // Simplified, focused prompt without context pollution
429
462
  const corePrompt = this.buildCoreAnalysisPrompt(context);
463
+ // Get safe token limit based on provider
464
+ const safeMaxTokens = this.getSafeTokenLimit();
465
+ logger.debug(`Using AI provider: ${this.aiConfig.provider || "auto"}`);
466
+ logger.debug(`Configured maxTokens: ${this.aiConfig.maxTokens}`);
467
+ logger.debug(`Safe maxTokens limit: ${safeMaxTokens}`);
430
468
  const result = await this.neurolink.generate({
431
469
  input: { text: corePrompt },
432
470
  systemPrompt: this.getSecurityReviewSystemPrompt(),
433
471
  provider: this.aiConfig.provider || "auto", // Auto-select best provider
434
472
  model: this.aiConfig.model || "best", // Use most capable model
435
473
  temperature: this.aiConfig.temperature || 0.3, // Lower for more focused analysis
436
- maxTokens: Math.max(this.aiConfig.maxTokens || 0, 2000000), // Quality first - always use higher limit
474
+ maxTokens: safeMaxTokens, // Use provider-aware safe token limit
437
475
  timeout: "15m", // Allow plenty of time for thorough analysis
438
476
  context: aiContext,
439
477
  enableAnalytics: this.aiConfig.enableAnalytics || true,
@@ -584,27 +622,14 @@ Return ONLY valid JSON:
584
622
  if (violation.impact) {
585
623
  comment += `\n\n**Impact**: ${violation.impact}`;
586
624
  }
625
+ // Add suggested fix section if suggestion is provided
587
626
  if (violation.suggestion) {
588
- const fileExt = violation.file?.split(".").pop() || "text";
589
- const langMap = {
590
- js: "javascript",
591
- jsx: "javascript",
592
- ts: "typescript",
593
- tsx: "typescript",
594
- res: "rescript",
595
- resi: "rescript",
596
- py: "python",
597
- java: "java",
598
- go: "go",
599
- rb: "ruby",
600
- php: "php",
601
- sql: "sql",
602
- json: "json",
603
- };
604
- const language = langMap[fileExt] || "text";
605
- // Use the escape method for code blocks
627
+ comment += `\n\n**Suggested Fix**:\n`;
628
+ // Detect the language for syntax highlighting
629
+ const language = this.detectLanguageFromFile(violation.file || "");
630
+ // Use proper markdown escaping for code blocks
606
631
  const escapedCodeBlock = this.escapeMarkdownCodeBlock(violation.suggestion, language);
607
- comment += `\n\n**๐Ÿ’ก Suggested Fix**:\n${escapedCodeBlock}`;
632
+ comment += escapedCodeBlock;
608
633
  }
609
634
  comment += `\n\n---\n*๐Ÿ›ก๏ธ Automated review by **Yama** โ€ข Powered by AI*`;
610
635
  return comment;
@@ -880,7 +905,7 @@ ${recommendation}
880
905
  filesReviewed: new Set(violations.filter((v) => v.file).map((v) => v.file)).size || 1,
881
906
  };
882
907
  }
883
- generateReviewResult(violations, _duration, _context) {
908
+ generateReviewResult(violations, _duration, _context, processingStrategy) {
884
909
  const stats = this.calculateStats(violations);
885
910
  return {
886
911
  violations,
@@ -892,10 +917,374 @@ ${recommendation}
892
917
  majorCount: stats.majorCount,
893
918
  minorCount: stats.minorCount,
894
919
  suggestionCount: stats.suggestionCount,
920
+ processingStrategy,
895
921
  },
896
922
  positiveObservations: [], // Could be extracted from AI response
897
923
  };
898
924
  }
925
+ // ============================================================================
926
+ // BATCH PROCESSING METHODS
927
+ // ============================================================================
928
+ /**
929
+ * Get batch processing configuration with defaults
930
+ */
931
+ getBatchProcessingConfig() {
932
+ const defaultConfig = {
933
+ enabled: true,
934
+ maxFilesPerBatch: 3,
935
+ prioritizeSecurityFiles: true,
936
+ parallelBatches: false, // Sequential for better reliability
937
+ batchDelayMs: 1000,
938
+ singleRequestThreshold: 5, // Use single request for โ‰ค5 files
939
+ };
940
+ return {
941
+ ...defaultConfig,
942
+ ...this.reviewConfig.batchProcessing,
943
+ };
944
+ }
945
+ /**
946
+ * Determine if batch processing should be used
947
+ */
948
+ shouldUseBatchProcessing(context, batchConfig) {
949
+ if (!batchConfig.enabled) {
950
+ logger.debug("Batch processing disabled in config");
951
+ return false;
952
+ }
953
+ const fileCount = context.diffStrategy.fileCount;
954
+ if (fileCount <= batchConfig.singleRequestThreshold) {
955
+ logger.debug(`File count (${fileCount}) โ‰ค threshold (${batchConfig.singleRequestThreshold}), using single request`);
956
+ return false;
957
+ }
958
+ // Force batch processing for file-by-file strategy with many files
959
+ if (context.diffStrategy.strategy === "file-by-file" && fileCount > 10) {
960
+ logger.debug(`File-by-file strategy with ${fileCount} files, forcing batch processing`);
961
+ return true;
962
+ }
963
+ logger.debug(`File count (${fileCount}) > threshold (${batchConfig.singleRequestThreshold}), using batch processing`);
964
+ return true;
965
+ }
966
+ /**
967
+ * Main batch processing method
968
+ */
969
+ async reviewWithBatchProcessing(context, options, batchConfig) {
970
+ const startTime = Date.now();
971
+ try {
972
+ // Step 1: Prioritize and organize files
973
+ const prioritizedFiles = await this.prioritizeFiles(context, batchConfig);
974
+ logger.info(`๐Ÿ“‹ Prioritized ${prioritizedFiles.length} files: ${prioritizedFiles.filter(f => f.priority === "high").length} high, ${prioritizedFiles.filter(f => f.priority === "medium").length} medium, ${prioritizedFiles.filter(f => f.priority === "low").length} low priority`);
975
+ // Step 2: Create batches
976
+ const batches = this.createBatches(prioritizedFiles, batchConfig);
977
+ logger.info(`๐Ÿ“ฆ Created ${batches.length} batches (max ${batchConfig.maxFilesPerBatch} files per batch)`);
978
+ // Step 3: Process batches
979
+ const batchResults = [];
980
+ const allViolations = [];
981
+ for (let i = 0; i < batches.length; i++) {
982
+ const batch = batches[i];
983
+ logger.info(`๐Ÿ”„ Processing batch ${i + 1}/${batches.length} (${batch.files.length} files, ${batch.priority} priority)`);
984
+ try {
985
+ const batchResult = await this.processBatch(batch, context, options);
986
+ batchResults.push(batchResult);
987
+ allViolations.push(...batchResult.violations);
988
+ logger.info(`โœ… Batch ${i + 1} completed: ${batchResult.violations.length} violations found in ${Math.round(batchResult.processingTime / 1000)}s`);
989
+ // Add delay between batches if configured
990
+ if (i < batches.length - 1 && batchConfig.batchDelayMs > 0) {
991
+ logger.debug(`โณ Waiting ${batchConfig.batchDelayMs}ms before next batch`);
992
+ await new Promise(resolve => setTimeout(resolve, batchConfig.batchDelayMs));
993
+ }
994
+ }
995
+ catch (error) {
996
+ logger.error(`โŒ Batch ${i + 1} failed: ${error.message}`);
997
+ // Record failed batch
998
+ batchResults.push({
999
+ batchIndex: i,
1000
+ files: batch.files,
1001
+ violations: [],
1002
+ processingTime: Date.now() - startTime,
1003
+ error: error.message,
1004
+ });
1005
+ }
1006
+ }
1007
+ const totalTime = Date.now() - startTime;
1008
+ const avgBatchSize = batches.reduce((sum, b) => sum + b.files.length, 0) / batches.length;
1009
+ logger.success(`๐ŸŽฏ Batch processing completed: ${allViolations.length} total violations from ${batches.length} batches in ${Math.round(totalTime / 1000)}s (avg ${avgBatchSize.toFixed(1)} files/batch)`);
1010
+ return { violations: allViolations, batchResults };
1011
+ }
1012
+ catch (error) {
1013
+ logger.error(`Batch processing failed: ${error.message}`);
1014
+ throw error;
1015
+ }
1016
+ }
1017
+ /**
1018
+ * Prioritize files based on security importance and file type
1019
+ */
1020
+ async prioritizeFiles(context, batchConfig) {
1021
+ const files = context.pr.fileChanges || [];
1022
+ const prioritizedFiles = [];
1023
+ for (const filePath of files) {
1024
+ const priority = this.calculateFilePriority(filePath, batchConfig);
1025
+ const estimatedTokens = await this.estimateFileTokens(filePath, context);
1026
+ prioritizedFiles.push({
1027
+ path: filePath,
1028
+ priority,
1029
+ estimatedTokens,
1030
+ diff: context.fileDiffs?.get(filePath),
1031
+ });
1032
+ }
1033
+ // Sort by priority (high -> medium -> low) then by estimated tokens (smaller first)
1034
+ prioritizedFiles.sort((a, b) => {
1035
+ const priorityOrder = { high: 0, medium: 1, low: 2 };
1036
+ const priorityDiff = priorityOrder[a.priority] - priorityOrder[b.priority];
1037
+ if (priorityDiff !== 0) {
1038
+ return priorityDiff;
1039
+ }
1040
+ return a.estimatedTokens - b.estimatedTokens;
1041
+ });
1042
+ return prioritizedFiles;
1043
+ }
1044
+ /**
1045
+ * Calculate file priority based on path and content
1046
+ */
1047
+ calculateFilePriority(filePath, batchConfig) {
1048
+ if (!batchConfig.prioritizeSecurityFiles) {
1049
+ return "medium"; // All files same priority if not prioritizing
1050
+ }
1051
+ const path = filePath.toLowerCase();
1052
+ // High priority: Security-sensitive files
1053
+ const highPriorityPatterns = [
1054
+ /auth/i, /login/i, /password/i, /token/i, /jwt/i, /oauth/i,
1055
+ /crypto/i, /encrypt/i, /decrypt/i, /hash/i, /security/i,
1056
+ /payment/i, /billing/i, /transaction/i, /money/i, /wallet/i,
1057
+ /admin/i, /privilege/i, /permission/i, /role/i, /access/i,
1058
+ /config/i, /env/i, /secret/i, /key/i, /credential/i,
1059
+ /api/i, /endpoint/i, /route/i, /controller/i, /middleware/i,
1060
+ ];
1061
+ if (highPriorityPatterns.some(pattern => pattern.test(path))) {
1062
+ return "high";
1063
+ }
1064
+ // Low priority: Documentation, tests, config files
1065
+ const lowPriorityPatterns = [
1066
+ /\.md$/i, /\.txt$/i, /readme/i, /changelog/i, /license/i,
1067
+ /test/i, /spec/i, /\.test\./i, /\.spec\./i, /__tests__/i,
1068
+ /\.json$/i, /\.yaml$/i, /\.yml$/i, /\.toml$/i, /\.ini$/i,
1069
+ /\.lock$/i, /package-lock/i, /yarn\.lock/i, /pnpm-lock/i,
1070
+ /\.gitignore/i, /\.eslint/i, /\.prettier/i, /tsconfig/i,
1071
+ /\.svg$/i, /\.png$/i, /\.jpg$/i, /\.jpeg$/i, /\.gif$/i,
1072
+ ];
1073
+ if (lowPriorityPatterns.some(pattern => pattern.test(path))) {
1074
+ return "low";
1075
+ }
1076
+ // Medium priority: Everything else
1077
+ return "medium";
1078
+ }
1079
+ /**
1080
+ * Estimate token count for a file
1081
+ */
1082
+ async estimateFileTokens(filePath, context) {
1083
+ try {
1084
+ let content = "";
1085
+ if (context.fileDiffs?.has(filePath)) {
1086
+ content = context.fileDiffs.get(filePath) || "";
1087
+ }
1088
+ else if (context.prDiff) {
1089
+ // Extract file content from whole diff
1090
+ const diffLines = context.prDiff.diff.split("\n");
1091
+ let inFile = false;
1092
+ for (const line of diffLines) {
1093
+ if (line.startsWith("diff --git") && line.includes(filePath)) {
1094
+ inFile = true;
1095
+ continue;
1096
+ }
1097
+ if (inFile && line.startsWith("diff --git")) {
1098
+ break;
1099
+ }
1100
+ if (inFile) {
1101
+ content += line + "\n";
1102
+ }
1103
+ }
1104
+ }
1105
+ // Rough estimation: ~4 characters per token
1106
+ const estimatedTokens = Math.ceil(content.length / 4);
1107
+ // Add base overhead for context and prompts
1108
+ const baseOverhead = 1000;
1109
+ return estimatedTokens + baseOverhead;
1110
+ }
1111
+ catch (error) {
1112
+ logger.debug(`Error estimating tokens for ${filePath}: ${error.message}`);
1113
+ return 2000; // Default estimate
1114
+ }
1115
+ }
1116
+ /**
1117
+ * Create batches from prioritized files
1118
+ */
1119
+ createBatches(prioritizedFiles, batchConfig) {
1120
+ const batches = [];
1121
+ const maxTokensPerBatch = this.getSafeTokenLimit() * 0.7; // Use 70% of limit for safety
1122
+ let currentBatch = {
1123
+ files: [],
1124
+ priority: "medium",
1125
+ estimatedTokens: 0,
1126
+ batchIndex: 0,
1127
+ };
1128
+ for (const file of prioritizedFiles) {
1129
+ const wouldExceedTokens = currentBatch.estimatedTokens + file.estimatedTokens > maxTokensPerBatch;
1130
+ const wouldExceedFileCount = currentBatch.files.length >= batchConfig.maxFilesPerBatch;
1131
+ if ((wouldExceedTokens || wouldExceedFileCount) && currentBatch.files.length > 0) {
1132
+ // Finalize current batch
1133
+ batches.push(currentBatch);
1134
+ // Start new batch
1135
+ currentBatch = {
1136
+ files: [],
1137
+ priority: file.priority,
1138
+ estimatedTokens: 0,
1139
+ batchIndex: batches.length,
1140
+ };
1141
+ }
1142
+ // Add file to current batch
1143
+ currentBatch.files.push(file.path);
1144
+ currentBatch.estimatedTokens += file.estimatedTokens;
1145
+ // Update batch priority to highest priority file in batch
1146
+ if (file.priority === "high" ||
1147
+ (file.priority === "medium" && currentBatch.priority === "low")) {
1148
+ currentBatch.priority = file.priority;
1149
+ }
1150
+ }
1151
+ // Add final batch if it has files
1152
+ if (currentBatch.files.length > 0) {
1153
+ batches.push(currentBatch);
1154
+ }
1155
+ return batches;
1156
+ }
1157
+ /**
1158
+ * Process a single batch of files
1159
+ */
1160
+ async processBatch(batch, context, options) {
1161
+ const startTime = Date.now();
1162
+ try {
1163
+ // Create batch-specific context
1164
+ const batchContext = this.createBatchContext(batch, context);
1165
+ // Build batch-specific prompt
1166
+ const batchPrompt = this.buildBatchAnalysisPrompt(batchContext, batch, options);
1167
+ // Analyze with AI
1168
+ const violations = await this.analyzeWithAI(batchPrompt, batchContext);
1169
+ const processingTime = Date.now() - startTime;
1170
+ return {
1171
+ batchIndex: batch.batchIndex,
1172
+ files: batch.files,
1173
+ violations,
1174
+ processingTime,
1175
+ };
1176
+ }
1177
+ catch (error) {
1178
+ const processingTime = Date.now() - startTime;
1179
+ return {
1180
+ batchIndex: batch.batchIndex,
1181
+ files: batch.files,
1182
+ violations: [],
1183
+ processingTime,
1184
+ error: error.message,
1185
+ };
1186
+ }
1187
+ }
1188
+ /**
1189
+ * Create context for a specific batch
1190
+ */
1191
+ createBatchContext(batch, originalContext) {
1192
+ // Create a filtered context containing only the files in this batch
1193
+ const batchFileDiffs = new Map();
1194
+ if (originalContext.fileDiffs) {
1195
+ for (const filePath of batch.files) {
1196
+ const diff = originalContext.fileDiffs.get(filePath);
1197
+ if (diff) {
1198
+ batchFileDiffs.set(filePath, diff);
1199
+ }
1200
+ }
1201
+ }
1202
+ return {
1203
+ ...originalContext,
1204
+ fileDiffs: batchFileDiffs,
1205
+ diffStrategy: {
1206
+ ...originalContext.diffStrategy,
1207
+ fileCount: batch.files.length,
1208
+ strategy: "file-by-file", // Always use file-by-file for batches
1209
+ reason: `Batch processing ${batch.files.length} files`,
1210
+ },
1211
+ pr: {
1212
+ ...originalContext.pr,
1213
+ fileChanges: batch.files,
1214
+ },
1215
+ };
1216
+ }
1217
+ /**
1218
+ * Build analysis prompt for a specific batch
1219
+ */
1220
+ buildBatchAnalysisPrompt(batchContext, batch, options) {
1221
+ const diffContent = this.extractDiffContent(batchContext);
1222
+ return `Conduct a focused security and quality analysis of this batch of ${batch.files.length} files (${batch.priority} priority).
1223
+
1224
+ ## BATCH CONTEXT:
1225
+ **Batch**: ${batch.batchIndex + 1}
1226
+ **Files**: ${batch.files.length}
1227
+ **Priority**: ${batch.priority}
1228
+ **Files in batch**: ${batch.files.join(", ")}
1229
+
1230
+ ## PR CONTEXT:
1231
+ **Title**: ${batchContext.pr.title}
1232
+ **Author**: ${batchContext.pr.author}
1233
+ **Repository**: ${batchContext.identifier.workspace}/${batchContext.identifier.repository}
1234
+
1235
+ ## PROJECT CONTEXT:
1236
+ ${batchContext.projectContext.memoryBank.projectContext || batchContext.projectContext.memoryBank.summary}
1237
+
1238
+ ## PROJECT RULES & STANDARDS:
1239
+ ${batchContext.projectContext.clinerules || "No specific rules defined"}
1240
+
1241
+ ## BATCH CODE CHANGES:
1242
+ ${diffContent}
1243
+
1244
+ ## CRITICAL INSTRUCTIONS FOR CODE SNIPPETS:
1245
+
1246
+ When you identify an issue in the code, you MUST:
1247
+ 1. Copy the EXACT line from the diff above, including the diff prefix (+, -, or space at the beginning)
1248
+ 2. Do NOT modify, clean, or reformat the line
1249
+ 3. Include the complete line as it appears in the diff
1250
+ 4. If the issue spans multiple lines, choose the most relevant single line
1251
+
1252
+ ## ANALYSIS REQUIREMENTS:
1253
+
1254
+ ${this.getAnalysisRequirements()}
1255
+
1256
+ ### ๐Ÿ“‹ OUTPUT FORMAT
1257
+ Return ONLY valid JSON:
1258
+ {
1259
+ "violations": [
1260
+ {
1261
+ "type": "inline",
1262
+ "file": "exact/file/path.ext",
1263
+ "code_snippet": "EXACT line from diff INCLUDING the +/- prefix",
1264
+ "search_context": {
1265
+ "before": ["line before from diff with prefix"],
1266
+ "after": ["line after from diff with prefix"]
1267
+ },
1268
+ "severity": "CRITICAL|MAJOR|MINOR|SUGGESTION",
1269
+ "category": "security|performance|maintainability|functionality",
1270
+ "issue": "Brief issue title",
1271
+ "message": "Detailed explanation",
1272
+ "impact": "Potential impact description",
1273
+ "suggestion": "Clean, executable code fix (no diff symbols)"
1274
+ }
1275
+ ],
1276
+ "summary": "Batch analysis summary",
1277
+ "positiveObservations": ["Good practices found"],
1278
+ "statistics": {
1279
+ "filesReviewed": ${batch.files.length},
1280
+ "totalIssues": 0,
1281
+ "criticalCount": 0,
1282
+ "majorCount": 0,
1283
+ "minorCount": 0,
1284
+ "suggestionCount": 0
1285
+ }
1286
+ }`;
1287
+ }
899
1288
  /**
900
1289
  * Utility methods
901
1290
  */
@@ -993,6 +1382,43 @@ ${recommendation}
993
1382
  }
994
1383
  return null;
995
1384
  }
1385
+ /**
1386
+ * Detect programming language from file extension
1387
+ */
1388
+ detectLanguageFromFile(filePath) {
1389
+ const ext = filePath.split(".").pop()?.toLowerCase();
1390
+ const languageMap = {
1391
+ js: "javascript",
1392
+ jsx: "javascript",
1393
+ ts: "typescript",
1394
+ tsx: "typescript",
1395
+ py: "python",
1396
+ java: "java",
1397
+ cpp: "cpp",
1398
+ c: "c",
1399
+ cs: "csharp",
1400
+ php: "php",
1401
+ rb: "ruby",
1402
+ go: "go",
1403
+ rs: "rust",
1404
+ res: "rescript",
1405
+ kt: "kotlin",
1406
+ swift: "swift",
1407
+ scala: "scala",
1408
+ sh: "bash",
1409
+ sql: "sql",
1410
+ json: "json",
1411
+ yaml: "yaml",
1412
+ yml: "yaml",
1413
+ xml: "xml",
1414
+ html: "html",
1415
+ css: "css",
1416
+ scss: "scss",
1417
+ sass: "sass",
1418
+ md: "markdown",
1419
+ };
1420
+ return languageMap[ext || ""] || "text";
1421
+ }
996
1422
  /**
997
1423
  * Generate all possible path variations for a file
998
1424
  */
@@ -162,6 +162,35 @@ export interface ReviewStatistics {
162
162
  majorCount: number;
163
163
  minorCount: number;
164
164
  suggestionCount: number;
165
+ batchCount?: number;
166
+ processingStrategy?: "single-request" | "batch-processing";
167
+ averageBatchSize?: number;
168
+ totalProcessingTime?: number;
169
+ }
170
+ export interface FileBatch {
171
+ files: string[];
172
+ priority: "high" | "medium" | "low";
173
+ estimatedTokens: number;
174
+ batchIndex: number;
175
+ }
176
+ export interface BatchResult {
177
+ batchIndex: number;
178
+ files: string[];
179
+ violations: Violation[];
180
+ processingTime: number;
181
+ tokenUsage?: {
182
+ input: number;
183
+ output: number;
184
+ total: number;
185
+ };
186
+ error?: string;
187
+ }
188
+ export type FilePriority = "high" | "medium" | "low";
189
+ export interface PrioritizedFile {
190
+ path: string;
191
+ priority: FilePriority;
192
+ estimatedTokens: number;
193
+ diff?: string;
165
194
  }
166
195
  export interface ReviewOptions {
167
196
  workspace: string;
@@ -233,6 +262,7 @@ export interface GuardianConfig {
233
262
  securityScan?: SecurityScanConfig;
234
263
  analytics?: AnalyticsConfig;
235
264
  };
265
+ memoryBank?: MemoryBankConfig;
236
266
  cache?: CacheConfig;
237
267
  performance?: PerformanceConfig;
238
268
  rules?: CustomRulesConfig;
@@ -249,6 +279,15 @@ export interface CodeReviewConfig {
249
279
  systemPrompt?: string;
250
280
  analysisTemplate?: string;
251
281
  focusAreas?: string[];
282
+ batchProcessing?: BatchProcessingConfig;
283
+ }
284
+ export interface BatchProcessingConfig {
285
+ enabled: boolean;
286
+ maxFilesPerBatch: number;
287
+ prioritizeSecurityFiles: boolean;
288
+ parallelBatches: boolean;
289
+ batchDelayMs: number;
290
+ singleRequestThreshold: number;
252
291
  }
253
292
  export interface DescriptionEnhancementConfig {
254
293
  enabled: boolean;
@@ -277,6 +316,11 @@ export interface AnalyticsConfig {
277
316
  trackMetrics: boolean;
278
317
  exportFormat: "json" | "csv" | "yaml";
279
318
  }
319
+ export interface MemoryBankConfig {
320
+ enabled: boolean;
321
+ path: string;
322
+ fallbackPaths?: string[];
323
+ }
280
324
  export interface CacheConfig {
281
325
  enabled: boolean;
282
326
  ttl: string;
@@ -57,6 +57,10 @@ export declare class Cache implements ICache {
57
57
  * Invalidate all keys with a specific tag
58
58
  */
59
59
  invalidateTag(tag: string): number;
60
+ /**
61
+ * Invalidate all keys matching a pattern
62
+ */
63
+ invalidatePattern(pattern: string): number;
60
64
  /**
61
65
  * Cache key generators for common patterns
62
66
  */
@@ -69,6 +73,7 @@ export declare class Cache implements ICache {
69
73
  aiResponse: (prompt: string, provider: string, model: string) => string;
70
74
  projectContext: (workspace: string, repository: string, branch: string) => string;
71
75
  reviewResult: (workspace: string, repository: string, prId: string | number, configHash: string) => string;
76
+ memoryBankFiles: (workspace: string, repository: string, branch: string, path: string) => string;
72
77
  };
73
78
  /**
74
79
  * Smart cache warming for common patterns
@@ -166,6 +166,21 @@ export class Cache {
166
166
  logger.debug(`Invalidated tag "${tag}": ${deleted} keys`);
167
167
  return deleted;
168
168
  }
169
+ /**
170
+ * Invalidate all keys matching a pattern
171
+ */
172
+ invalidatePattern(pattern) {
173
+ const regex = new RegExp(pattern.replace(/\*/g, ".*"));
174
+ const allKeys = this.keys();
175
+ let deleted = 0;
176
+ allKeys.forEach((key) => {
177
+ if (regex.test(key)) {
178
+ deleted += this.del(key);
179
+ }
180
+ });
181
+ logger.debug(`Invalidated pattern "${pattern}": ${deleted} keys`);
182
+ return deleted;
183
+ }
169
184
  /**
170
185
  * Cache key generators for common patterns
171
186
  */
@@ -182,6 +197,7 @@ export class Cache {
182
197
  },
183
198
  projectContext: (workspace, repository, branch) => `context:${workspace}:${repository}:${branch}`,
184
199
  reviewResult: (workspace, repository, prId, configHash) => `review:${workspace}:${repository}:${prId}:${configHash}`,
200
+ memoryBankFiles: (workspace, repository, branch, path) => `memory-bank:${workspace}:${repository}:${branch}:${path}`,
185
201
  };
186
202
  /**
187
203
  * Smart cache warming for common patterns
@@ -213,7 +229,7 @@ export class Cache {
213
229
  }
214
230
  // Clean up tag associations for deleted keys
215
231
  this.tags.forEach((keys, tag) => {
216
- const validKeys = new Set([...keys].filter((key) => this.cache.has(key)));
232
+ const validKeys = new Set(Array.from(keys).filter((key) => this.cache.has(key)));
217
233
  if (validKeys.size !== keys.size) {
218
234
  this.tags.set(tag, validKeys);
219
235
  }