@juspay/yama 1.2.0 → 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.
package/CHANGELOG.md CHANGED
@@ -1,31 +1,33 @@
1
- # [1.2.0](https://github.com/juspay/yama/compare/v1.1.1...v1.2.0) (2025-08-08)
1
+ # [1.3.0](https://github.com/juspay/yama/compare/v1.2.0...v1.3.0) (2025-09-01)
2
+
3
+ ### Features
4
+
5
+ - **github:** implement comprehensive automation with proper Yama branding ([a03cb7f](https://github.com/juspay/yama/commit/a03cb7f499ea7793d626686beebde907551035d0))
2
6
 
7
+ # [1.2.0](https://github.com/juspay/yama/compare/v1.1.1...v1.2.0) (2025-08-08)
3
8
 
4
9
  ### Features
5
10
 
6
- * **Memory:** support memory bank path and maxToken from config file ([1bc69d5](https://github.com/juspay/yama/commit/1bc69d5bda3ac5868d7537b881007beaf6916476))
11
+ - **Memory:** support memory bank path and maxToken from config file ([1bc69d5](https://github.com/juspay/yama/commit/1bc69d5bda3ac5868d7537b881007beaf6916476))
7
12
 
8
13
  ## [1.1.1](https://github.com/juspay/yama/compare/v1.1.0...v1.1.1) (2025-07-28)
9
14
 
10
-
11
15
  ### Bug Fixes
12
16
 
13
- * bump version to 1.2.1 ([8964645](https://github.com/juspay/yama/commit/89646450a7dec6ffcc3ad7fb745e1414fc751d4f))
17
+ - bump version to 1.2.1 ([8964645](https://github.com/juspay/yama/commit/89646450a7dec6ffcc3ad7fb745e1414fc751d4f))
14
18
 
15
19
  # [1.1.0](https://github.com/juspay/yama/compare/v1.0.0...v1.1.0) (2025-07-28)
16
20
 
17
-
18
21
  ### Features
19
22
 
20
- * migrate from CommonJS to ESM modules ([b45559f](https://github.com/juspay/yama/commit/b45559f86d37ab3516079becfa56a9f73ff8f062))
23
+ - migrate from CommonJS to ESM modules ([b45559f](https://github.com/juspay/yama/commit/b45559f86d37ab3516079becfa56a9f73ff8f062))
21
24
 
22
25
  # 1.0.0 (2025-07-25)
23
26
 
24
-
25
27
  ### Features
26
28
 
27
- * add enterprise-grade CI/CD pipeline and release automation ([e385d69](https://github.com/juspay/yama/commit/e385d69d135bee72f51ac4462adcfc9a4a4be17b))
28
- * v1.1.0 - Enhanced AI configuration and performance improvements ([e763e93](https://github.com/juspay/yama/commit/e763e9341c2869433097b7a6bcc9080028934e1b))
29
+ - add enterprise-grade CI/CD pipeline and release automation ([e385d69](https://github.com/juspay/yama/commit/e385d69d135bee72f51ac4462adcfc9a4a4be17b))
30
+ - v1.1.0 - Enhanced AI configuration and performance improvements ([e763e93](https://github.com/juspay/yama/commit/e763e9341c2869433097b7a6bcc9080028934e1b))
29
31
 
30
32
  # Changelog
31
33
 
@@ -27,7 +27,7 @@ export class BitbucketProvider {
27
27
  }
28
28
  try {
29
29
  logger.debug("Initializing Bitbucket MCP handlers...");
30
- const dynamicImport = eval("(specifier) => import(specifier)");
30
+ const dynamicImport = new Function("specifier", "return import(specifier)");
31
31
  const [{ BitbucketApiClient }, { BranchHandlers }, { PullRequestHandlers }, { ReviewHandlers }, { FileHandlers },] = await Promise.all([
32
32
  dynamicImport("@nexus2520/bitbucket-mcp-server/build/utils/api-client.js"),
33
33
  dynamicImport("@nexus2520/bitbucket-mcp-server/build/handlers/branch-handlers.js"),
@@ -12,7 +12,7 @@ export declare class CodeReviewer {
12
12
  private reviewConfig;
13
13
  constructor(bitbucketProvider: BitbucketProvider, aiConfig: AIProviderConfig, reviewConfig: CodeReviewConfig);
14
14
  /**
15
- * Review code using pre-gathered unified context (OPTIMIZED)
15
+ * Review code using pre-gathered unified context (OPTIMIZED with Batch Processing)
16
16
  */
17
17
  reviewCodeWithContext(context: UnifiedContext, options: ReviewOptions): Promise<ReviewResult>;
18
18
  /**
@@ -92,6 +92,46 @@ export declare class CodeReviewer {
92
92
  private groupViolationsByCategory;
93
93
  private calculateStats;
94
94
  private generateReviewResult;
95
+ /**
96
+ * Get batch processing configuration with defaults
97
+ */
98
+ private getBatchProcessingConfig;
99
+ /**
100
+ * Determine if batch processing should be used
101
+ */
102
+ private shouldUseBatchProcessing;
103
+ /**
104
+ * Main batch processing method
105
+ */
106
+ private reviewWithBatchProcessing;
107
+ /**
108
+ * Prioritize files based on security importance and file type
109
+ */
110
+ private prioritizeFiles;
111
+ /**
112
+ * Calculate file priority based on path and content
113
+ */
114
+ private calculateFilePriority;
115
+ /**
116
+ * Estimate token count for a file
117
+ */
118
+ private estimateFileTokens;
119
+ /**
120
+ * Create batches from prioritized files
121
+ */
122
+ private createBatches;
123
+ /**
124
+ * Process a single batch of files
125
+ */
126
+ private processBatch;
127
+ /**
128
+ * Create context for a specific batch
129
+ */
130
+ private createBatchContext;
131
+ /**
132
+ * Build analysis prompt for a specific batch
133
+ */
134
+ private buildBatchAnalysisPrompt;
95
135
  /**
96
136
  * Utility methods
97
137
  */
@@ -100,6 +140,10 @@ export declare class CodeReviewer {
100
140
  * Extract line information for comment from context
101
141
  */
102
142
  private extractLineInfoForComment;
143
+ /**
144
+ * Detect programming language from file extension
145
+ */
146
+ private detectLanguageFromFile;
103
147
  /**
104
148
  * Generate all possible path variations for a file
105
149
  */
@@ -17,22 +17,37 @@ export class CodeReviewer {
17
17
  this.reviewConfig = reviewConfig;
18
18
  }
19
19
  /**
20
- * Review code using pre-gathered unified context (OPTIMIZED)
20
+ * Review code using pre-gathered unified context (OPTIMIZED with Batch Processing)
21
21
  */
22
22
  async reviewCodeWithContext(context, options) {
23
23
  const startTime = Date.now();
24
24
  try {
25
25
  logger.phase("🧪 Conducting AI-powered code analysis...");
26
26
  logger.info(`Analyzing ${context.diffStrategy.fileCount} files using ${context.diffStrategy.strategy} strategy`);
27
- const analysisPrompt = this.buildAnalysisPrompt(context, options);
28
- 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
+ }
29
44
  const validatedViolations = this.validateViolations(violations, context);
30
45
  if (!options.dryRun && validatedViolations.length > 0) {
31
46
  await this.postComments(context, validatedViolations, options);
32
47
  }
33
48
  const duration = Math.round((Date.now() - startTime) / 1000);
34
- const result = this.generateReviewResult(validatedViolations, duration, context);
35
- 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})`);
36
51
  return result;
37
52
  }
38
53
  catch (error) {
@@ -607,27 +622,14 @@ Return ONLY valid JSON:
607
622
  if (violation.impact) {
608
623
  comment += `\n\n**Impact**: ${violation.impact}`;
609
624
  }
625
+ // Add suggested fix section if suggestion is provided
610
626
  if (violation.suggestion) {
611
- const fileExt = violation.file?.split(".").pop() || "text";
612
- const langMap = {
613
- js: "javascript",
614
- jsx: "javascript",
615
- ts: "typescript",
616
- tsx: "typescript",
617
- res: "rescript",
618
- resi: "rescript",
619
- py: "python",
620
- java: "java",
621
- go: "go",
622
- rb: "ruby",
623
- php: "php",
624
- sql: "sql",
625
- json: "json",
626
- };
627
- const language = langMap[fileExt] || "text";
628
- // 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
629
631
  const escapedCodeBlock = this.escapeMarkdownCodeBlock(violation.suggestion, language);
630
- comment += `\n\n**💡 Suggested Fix**:\n${escapedCodeBlock}`;
632
+ comment += escapedCodeBlock;
631
633
  }
632
634
  comment += `\n\n---\n*🛡️ Automated review by **Yama** • Powered by AI*`;
633
635
  return comment;
@@ -903,7 +905,7 @@ ${recommendation}
903
905
  filesReviewed: new Set(violations.filter((v) => v.file).map((v) => v.file)).size || 1,
904
906
  };
905
907
  }
906
- generateReviewResult(violations, _duration, _context) {
908
+ generateReviewResult(violations, _duration, _context, processingStrategy) {
907
909
  const stats = this.calculateStats(violations);
908
910
  return {
909
911
  violations,
@@ -915,10 +917,374 @@ ${recommendation}
915
917
  majorCount: stats.majorCount,
916
918
  minorCount: stats.minorCount,
917
919
  suggestionCount: stats.suggestionCount,
920
+ processingStrategy,
918
921
  },
919
922
  positiveObservations: [], // Could be extracted from AI response
920
923
  };
921
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
+ }
922
1288
  /**
923
1289
  * Utility methods
924
1290
  */
@@ -1016,6 +1382,43 @@ ${recommendation}
1016
1382
  }
1017
1383
  return null;
1018
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
+ }
1019
1422
  /**
1020
1423
  * Generate all possible path variations for a file
1021
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;
@@ -250,6 +279,15 @@ export interface CodeReviewConfig {
250
279
  systemPrompt?: string;
251
280
  analysisTemplate?: string;
252
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;
253
291
  }
254
292
  export interface DescriptionEnhancementConfig {
255
293
  enabled: boolean;
@@ -229,7 +229,7 @@ export class Cache {
229
229
  }
230
230
  // Clean up tag associations for deleted keys
231
231
  this.tags.forEach((keys, tag) => {
232
- 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)));
233
233
  if (validKeys.size !== keys.size) {
234
234
  this.tags.set(tag, validKeys);
235
235
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@juspay/yama",
3
- "version": "1.2.0",
3
+ "version": "1.3.0",
4
4
  "description": "Enterprise-grade Pull Request automation toolkit with AI-powered code review and description enhancement",
5
5
  "keywords": [
6
6
  "pr",
@@ -54,12 +54,12 @@
54
54
  "test": "jest",
55
55
  "lint": "eslint .",
56
56
  "lint:fix": "eslint . --fix",
57
- "type-check": "tsc --noEmit",
57
+ "type-check": "tsc --noEmit --skipLibCheck",
58
58
  "format": "prettier --write .",
59
59
  "format:check": "prettier --check .",
60
60
  "docs": "typedoc src",
61
61
  "clean": "rimraf dist",
62
- "prepare": "npm run build",
62
+ "prepare": "husky install",
63
63
  "prepack": "npm run build && npm run test",
64
64
  "changeset": "changeset",
65
65
  "changeset:version": "changeset version && git add --all",
@@ -67,7 +67,20 @@
67
67
  "release:dry": "npm publish --dry-run",
68
68
  "release:github": "npm publish --registry https://npm.pkg.github.com",
69
69
  "version:check": "npm version --no-git-tag-version",
70
- "pack:verify": "npm pack && tar -tzf *.tgz | head -20"
70
+ "pack:verify": "npm pack && tar -tzf *.tgz | head -20",
71
+ "validate": "npm run validate:env && npm run validate:security",
72
+ "validate:all": "npm run lint && npm run type-check && npm run validate",
73
+ "validate:env": "node scripts/validate-env.cjs",
74
+ "validate:security": "node scripts/validate-security.cjs",
75
+ "validate:commit": "node scripts/commit-validation.cjs \"$(git log -1 --pretty=format:'%s')\"",
76
+ "validate:build": "node scripts/build-validations.cjs",
77
+ "commit:validate": "node scripts/commit-validation.cjs",
78
+ "pre-commit": "lint-staged",
79
+ "pre-push": "npm run validate && npm run test",
80
+ "quality:all": "npm run lint && npm run format && npm run test",
81
+ "quality:metrics": "node scripts/quality-metrics.cjs",
82
+ "quality:report": "npm run quality:metrics && echo 'Quality metrics saved to quality-metrics.json'",
83
+ "check:all": "npm run lint && npm run format --check && npm run validate && npm run validate:commit"
71
84
  },
72
85
  "dependencies": {
73
86
  "@juspay/neurolink": "^5.1.0",
@@ -111,7 +124,9 @@
111
124
  "@semantic-release/release-notes-generator": "^14.0.1",
112
125
  "semantic-release": "^24.0.0",
113
126
  "prettier": "^3.0.0",
114
- "publint": "^0.3.0"
127
+ "publint": "^0.3.0",
128
+ "husky": "^9.0.0",
129
+ "lint-staged": "^15.0.0"
115
130
  },
116
131
  "peerDependencies": {
117
132
  "typescript": ">=4.5.0"
@@ -134,5 +149,15 @@
134
149
  "onlyBuiltDependencies": [
135
150
  "esbuild"
136
151
  ]
152
+ },
153
+ "lint-staged": {
154
+ "*.{ts,tsx,js,jsx}": [
155
+ "eslint --fix",
156
+ "prettier --write"
157
+ ],
158
+ "*.{json,md,yml,yaml}": [
159
+ "prettier --write"
160
+ ],
161
+ "*.ts": []
137
162
  }
138
163
  }
@@ -51,6 +51,15 @@ features:
51
51
  - "Performance bottlenecks"
52
52
  - "Error handling"
53
53
  - "Code quality"
54
+
55
+ # NEW: Batch Processing Configuration
56
+ batchProcessing:
57
+ enabled: true # Enable batch processing for large PRs
58
+ maxFilesPerBatch: 3 # Maximum files to process in each batch
59
+ prioritizeSecurityFiles: true # Process security-sensitive files first
60
+ parallelBatches: false # Process batches sequentially for reliability
61
+ batchDelayMs: 1000 # Delay between batches in milliseconds
62
+ singleRequestThreshold: 5 # Use single request for PRs with ≤5 files
54
63
 
55
64
  # Description Enhancement Configuration
56
65
  descriptionEnhancement:
@@ -149,4 +158,4 @@ memoryBank:
149
158
  fallbackPaths: # Optional fallback paths if primary doesn't exist
150
159
  - "docs/memory-bank"
151
160
  - ".memory-bank"
152
- - "project-docs/context"
161
+ - "project-docs/context"