@juspay/yama 1.1.0 → 1.2.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.
@@ -1,15 +1,16 @@
1
- "use strict";
2
1
  /**
3
2
  * Enhanced Code Reviewer - Optimized to work with Unified Context
4
3
  * Preserves all original functionality from pr-police.js but optimized
5
4
  */
6
- Object.defineProperty(exports, "__esModule", { value: true });
7
- exports.CodeReviewer = void 0;
8
- exports.createCodeReviewer = createCodeReviewer;
9
5
  // NeuroLink will be dynamically imported
10
- const types_1 = require("../types");
11
- const Logger_1 = require("../utils/Logger");
12
- class CodeReviewer {
6
+ import { ProviderError, } from "../types/index.js";
7
+ import { logger } from "../utils/Logger.js";
8
+ import { getProviderTokenLimit } from "../utils/ProviderLimits.js";
9
+ export class CodeReviewer {
10
+ neurolink;
11
+ bitbucketProvider;
12
+ aiConfig;
13
+ reviewConfig;
13
14
  constructor(bitbucketProvider, aiConfig, reviewConfig) {
14
15
  this.bitbucketProvider = bitbucketProvider;
15
16
  this.aiConfig = aiConfig;
@@ -21,8 +22,8 @@ class CodeReviewer {
21
22
  async reviewCodeWithContext(context, options) {
22
23
  const startTime = Date.now();
23
24
  try {
24
- Logger_1.logger.phase('🧪 Conducting AI-powered code analysis...');
25
- Logger_1.logger.info(`Analyzing ${context.diffStrategy.fileCount} files using ${context.diffStrategy.strategy} strategy`);
25
+ logger.phase("🧪 Conducting AI-powered code analysis...");
26
+ logger.info(`Analyzing ${context.diffStrategy.fileCount} files using ${context.diffStrategy.strategy} strategy`);
26
27
  const analysisPrompt = this.buildAnalysisPrompt(context, options);
27
28
  const violations = await this.analyzeWithAI(analysisPrompt, context);
28
29
  const validatedViolations = this.validateViolations(violations, context);
@@ -31,12 +32,12 @@ class CodeReviewer {
31
32
  }
32
33
  const duration = Math.round((Date.now() - startTime) / 1000);
33
34
  const result = this.generateReviewResult(validatedViolations, duration, context);
34
- Logger_1.logger.success(`Code review completed in ${duration}s: ${validatedViolations.length} violations found`);
35
+ logger.success(`Code review completed in ${duration}s: ${validatedViolations.length} violations found`);
35
36
  return result;
36
37
  }
37
38
  catch (error) {
38
- Logger_1.logger.error(`Code review failed: ${error.message}`);
39
- throw new types_1.ProviderError(`Code review failed: ${error.message}`);
39
+ logger.error(`Code review failed: ${error.message}`);
40
+ throw new ProviderError(`Code review failed: ${error.message}`);
40
41
  }
41
42
  }
42
43
  /**
@@ -46,7 +47,9 @@ class CodeReviewer {
46
47
  const validatedViolations = [];
47
48
  const diffContent = this.extractDiffContent(context);
48
49
  for (const violation of violations) {
49
- if (violation.type === 'inline' && violation.code_snippet && violation.file) {
50
+ if (violation.type === "inline" &&
51
+ violation.code_snippet &&
52
+ violation.file) {
50
53
  // Check if the code snippet exists in the diff
51
54
  if (diffContent.includes(violation.code_snippet)) {
52
55
  validatedViolations.push(violation);
@@ -58,8 +61,8 @@ class CodeReviewer {
58
61
  validatedViolations.push(fixedViolation);
59
62
  }
60
63
  else {
61
- Logger_1.logger.debug(`⚠️ Skipping violation - snippet not found in diff: ${violation.file}`);
62
- Logger_1.logger.debug(` Original snippet: "${violation.code_snippet}"`);
64
+ logger.debug(`⚠️ Skipping violation - snippet not found in diff: ${violation.file}`);
65
+ logger.debug(` Original snippet: "${violation.code_snippet}"`);
63
66
  }
64
67
  }
65
68
  }
@@ -68,50 +71,56 @@ class CodeReviewer {
68
71
  validatedViolations.push(violation);
69
72
  }
70
73
  }
71
- Logger_1.logger.debug(`Validated ${validatedViolations.length} out of ${violations.length} violations`);
74
+ logger.debug(`Validated ${validatedViolations.length} out of ${violations.length} violations`);
72
75
  return validatedViolations;
73
76
  }
74
77
  /**
75
78
  * Try to fix code snippet by finding it in the actual diff
76
79
  */
77
80
  tryFixCodeSnippet(violation, context) {
78
- if (!violation.file || !violation.code_snippet)
81
+ if (!violation.file || !violation.code_snippet) {
79
82
  return null;
83
+ }
80
84
  try {
81
85
  // Get the diff for this specific file
82
86
  let fileDiff;
83
- if (context.diffStrategy.strategy === 'whole' && context.prDiff) {
87
+ if (context.diffStrategy.strategy === "whole" && context.prDiff) {
84
88
  // Extract file diff from whole diff - handle different path formats
85
- const diffLines = context.prDiff.diff.split('\n');
89
+ const diffLines = context.prDiff.diff.split("\n");
86
90
  let fileStartIndex = -1;
87
91
  // Generate all possible path variations
88
92
  const pathVariations = this.generatePathVariations(violation.file);
89
93
  // Try to find the file in the diff with various path formats
90
94
  for (let i = 0; i < diffLines.length; i++) {
91
95
  const line = diffLines[i];
92
- if (line.startsWith('diff --git') || line.startsWith('Index:')) {
96
+ if (line.startsWith("diff --git") || line.startsWith("Index:")) {
93
97
  for (const pathVariation of pathVariations) {
94
98
  if (line.includes(pathVariation)) {
95
99
  fileStartIndex = i;
96
100
  break;
97
101
  }
98
102
  }
99
- if (fileStartIndex >= 0)
103
+ if (fileStartIndex >= 0) {
100
104
  break;
105
+ }
101
106
  }
102
107
  }
103
108
  if (fileStartIndex >= 0) {
104
- const nextFileIndex = diffLines.findIndex((line, idx) => idx > fileStartIndex && (line.startsWith('diff --git') || line.startsWith('Index:')));
105
- fileDiff = diffLines.slice(fileStartIndex, nextFileIndex > 0 ? nextFileIndex : diffLines.length).join('\n');
109
+ const nextFileIndex = diffLines.findIndex((line, idx) => idx > fileStartIndex &&
110
+ (line.startsWith("diff --git") || line.startsWith("Index:")));
111
+ fileDiff = diffLines
112
+ .slice(fileStartIndex, nextFileIndex > 0 ? nextFileIndex : diffLines.length)
113
+ .join("\n");
106
114
  }
107
115
  }
108
- else if (context.diffStrategy.strategy === 'file-by-file' && context.fileDiffs) {
116
+ else if (context.diffStrategy.strategy === "file-by-file" &&
117
+ context.fileDiffs) {
109
118
  // Try all path variations
110
119
  const pathVariations = this.generatePathVariations(violation.file);
111
120
  for (const path of pathVariations) {
112
121
  fileDiff = context.fileDiffs.get(path);
113
122
  if (fileDiff) {
114
- Logger_1.logger.debug(`Found diff for ${violation.file} using variation: ${path}`);
123
+ logger.debug(`Found diff for ${violation.file} using variation: ${path}`);
115
124
  break;
116
125
  }
117
126
  }
@@ -120,14 +129,14 @@ class CodeReviewer {
120
129
  for (const [key, value] of context.fileDiffs.entries()) {
121
130
  if (key.endsWith(violation.file) || violation.file.endsWith(key)) {
122
131
  fileDiff = value;
123
- Logger_1.logger.debug(`Found diff for ${violation.file} using partial match: ${key}`);
132
+ logger.debug(`Found diff for ${violation.file} using partial match: ${key}`);
124
133
  break;
125
134
  }
126
135
  }
127
136
  }
128
137
  }
129
138
  if (!fileDiff) {
130
- Logger_1.logger.debug(`❌ Could not find diff for file: ${violation.file}`);
139
+ logger.debug(`❌ Could not find diff for file: ${violation.file}`);
131
140
  return null;
132
141
  }
133
142
  // First, try to find the exact line with line number extraction
@@ -136,27 +145,28 @@ class CodeReviewer {
136
145
  const fixedViolation = { ...violation };
137
146
  fixedViolation.line_type = lineInfo.lineType;
138
147
  // Extract search context from the diff
139
- const diffLines = fileDiff.split('\n');
140
- const snippetIndex = diffLines.findIndex(line => line === violation.code_snippet);
148
+ const diffLines = fileDiff.split("\n");
149
+ const snippetIndex = diffLines.findIndex((line) => line === violation.code_snippet);
141
150
  if (snippetIndex > 0 && snippetIndex < diffLines.length - 1) {
142
151
  fixedViolation.search_context = {
143
152
  before: [diffLines[snippetIndex - 1]],
144
- after: [diffLines[snippetIndex + 1]]
153
+ after: [diffLines[snippetIndex + 1]],
145
154
  };
146
155
  }
147
- Logger_1.logger.debug(`✅ Found exact match with line number for ${violation.file}`);
156
+ logger.debug(`✅ Found exact match with line number for ${violation.file}`);
148
157
  return fixedViolation;
149
158
  }
150
159
  // Fallback: Clean the snippet and try fuzzy matching
151
160
  const cleanSnippet = violation.code_snippet
152
161
  .trim()
153
- .replace(/^[+\-\s]/, ''); // Remove diff prefix for searching
162
+ .replace(/^[+\-\s]/, ""); // Remove diff prefix for searching
154
163
  // Look for the clean snippet in the diff
155
- const diffLines = fileDiff.split('\n');
164
+ const diffLines = fileDiff.split("\n");
156
165
  for (let i = 0; i < diffLines.length; i++) {
157
166
  const line = diffLines[i];
158
- const cleanLine = line.replace(/^[+\-\s]/, '').trim();
159
- if (cleanLine.includes(cleanSnippet) || cleanSnippet.includes(cleanLine)) {
167
+ const cleanLine = line.replace(/^[+\-\s]/, "").trim();
168
+ if (cleanLine.includes(cleanSnippet) ||
169
+ cleanSnippet.includes(cleanLine)) {
160
170
  // Found a match! Update the violation with the correct snippet
161
171
  const fixedViolation = { ...violation };
162
172
  fixedViolation.code_snippet = line; // Use the full line with diff prefix
@@ -164,18 +174,18 @@ class CodeReviewer {
164
174
  if (i > 0 && i < diffLines.length - 1) {
165
175
  fixedViolation.search_context = {
166
176
  before: [diffLines[i - 1]],
167
- after: [diffLines[i + 1]]
177
+ after: [diffLines[i + 1]],
168
178
  };
169
179
  }
170
- Logger_1.logger.debug(`✅ Fixed code snippet for ${violation.file} using fuzzy match`);
180
+ logger.debug(`✅ Fixed code snippet for ${violation.file} using fuzzy match`);
171
181
  return fixedViolation;
172
182
  }
173
183
  }
174
- Logger_1.logger.debug(`❌ Could not find snippet in diff for ${violation.file}`);
175
- Logger_1.logger.debug(` Looking for: "${violation.code_snippet}"`);
184
+ logger.debug(`❌ Could not find snippet in diff for ${violation.file}`);
185
+ logger.debug(` Looking for: "${violation.code_snippet}"`);
176
186
  }
177
187
  catch (error) {
178
- Logger_1.logger.debug(`Error fixing code snippet: ${error.message}`);
188
+ logger.debug(`Error fixing code snippet: ${error.message}`);
179
189
  }
180
190
  return null;
181
191
  }
@@ -183,7 +193,7 @@ class CodeReviewer {
183
193
  * Get system prompt for security-focused code review
184
194
  */
185
195
  getSecurityReviewSystemPrompt() {
186
- return this.reviewConfig.systemPrompt ||
196
+ return (this.reviewConfig.systemPrompt ||
187
197
  `You are an Expert Security Code Reviewer for enterprise applications. Your role is to:
188
198
 
189
199
  🔒 SECURITY FIRST: Prioritize security vulnerabilities and data protection
@@ -194,14 +204,17 @@ class CodeReviewer {
194
204
  You provide actionable, educational feedback with specific examples and solutions.
195
205
  Focus on critical issues that could impact production systems.
196
206
 
197
- CRITICAL INSTRUCTION: When identifying issues, you MUST copy the EXACT line from the diff, including the diff prefix (+, -, or space). Do not modify or clean the line in any way.`;
207
+ CRITICAL INSTRUCTION: When identifying issues, you MUST copy the EXACT line from the diff, including the diff prefix (+, -, or space). Do not modify or clean the line in any way.`);
198
208
  }
199
209
  /**
200
210
  * Get analysis requirements from config or defaults
201
211
  */
202
212
  getAnalysisRequirements() {
203
- if (this.reviewConfig.focusAreas && this.reviewConfig.focusAreas.length > 0) {
204
- return this.reviewConfig.focusAreas.map(area => `### ${area}`).join('\n\n');
213
+ if (this.reviewConfig.focusAreas &&
214
+ this.reviewConfig.focusAreas.length > 0) {
215
+ return this.reviewConfig.focusAreas
216
+ .map((area) => `### ${area}`)
217
+ .join("\n\n");
205
218
  }
206
219
  // Default analysis requirements
207
220
  return `### 🔒 Security Analysis (CRITICAL PRIORITY)
@@ -228,7 +241,7 @@ CRITICAL INSTRUCTION: When identifying issues, you MUST copy the EXACT line from
228
241
  */
229
242
  buildCoreAnalysisPrompt(context) {
230
243
  const diffContent = this.extractDiffContent(context);
231
- return `Conduct a comprehensive security and quality analysis of this ${context.diffStrategy.strategy === 'whole' ? 'pull request' : 'code changeset'}.
244
+ return `Conduct a comprehensive security and quality analysis of this ${context.diffStrategy.strategy === "whole" ? "pull request" : "code changeset"}.
232
245
 
233
246
  ## COMPLETE PR CONTEXT:
234
247
  **Title**: ${context.pr.title}
@@ -248,7 +261,7 @@ CRITICAL INSTRUCTION: When identifying issues, you MUST copy the EXACT line from
248
261
  ${context.projectContext.memoryBank.projectContext || context.projectContext.memoryBank.summary}
249
262
 
250
263
  ## PROJECT RULES & STANDARDS:
251
- ${context.projectContext.clinerules || 'No specific rules defined'}
264
+ ${context.projectContext.clinerules || "No specific rules defined"}
252
265
 
253
266
  ## COMPLETE CODE CHANGES (NO TRUNCATION):
254
267
  ${diffContent}
@@ -310,17 +323,18 @@ Return ONLY valid JSON:
310
323
  * Extract diff content based on strategy
311
324
  */
312
325
  extractDiffContent(context) {
313
- if (context.diffStrategy.strategy === 'whole' && context.prDiff) {
326
+ if (context.diffStrategy.strategy === "whole" && context.prDiff) {
314
327
  return context.prDiff.diff || JSON.stringify(context.prDiff, null, 2);
315
328
  }
316
- else if (context.diffStrategy.strategy === 'file-by-file' && context.fileDiffs) {
329
+ else if (context.diffStrategy.strategy === "file-by-file" &&
330
+ context.fileDiffs) {
317
331
  const fileDiffArray = Array.from(context.fileDiffs.entries()).map(([file, diff]) => ({
318
332
  file,
319
- diff
333
+ diff,
320
334
  }));
321
335
  return JSON.stringify(fileDiffArray, null, 2);
322
336
  }
323
- return 'No diff content available';
337
+ return "No diff content available";
324
338
  }
325
339
  /**
326
340
  * Detect project type for better context
@@ -329,42 +343,53 @@ Return ONLY valid JSON:
329
343
  const fileExtensions = new Set();
330
344
  // Extract file extensions from changes
331
345
  if (context.pr.fileChanges) {
332
- context.pr.fileChanges.forEach(file => {
333
- const ext = file.split('.').pop()?.toLowerCase();
334
- if (ext)
346
+ context.pr.fileChanges.forEach((file) => {
347
+ const ext = file.split(".").pop()?.toLowerCase();
348
+ if (ext) {
335
349
  fileExtensions.add(ext);
350
+ }
336
351
  });
337
352
  }
338
- if (fileExtensions.has('rs') || fileExtensions.has('res'))
339
- return 'rescript';
340
- if (fileExtensions.has('ts') || fileExtensions.has('tsx'))
341
- return 'typescript';
342
- if (fileExtensions.has('js') || fileExtensions.has('jsx'))
343
- return 'javascript';
344
- if (fileExtensions.has('py'))
345
- return 'python';
346
- if (fileExtensions.has('go'))
347
- return 'golang';
348
- if (fileExtensions.has('java'))
349
- return 'java';
350
- if (fileExtensions.has('cpp') || fileExtensions.has('c'))
351
- return 'cpp';
352
- return 'mixed';
353
+ if (fileExtensions.has("rs") || fileExtensions.has("res")) {
354
+ return "rescript";
355
+ }
356
+ if (fileExtensions.has("ts") || fileExtensions.has("tsx")) {
357
+ return "typescript";
358
+ }
359
+ if (fileExtensions.has("js") || fileExtensions.has("jsx")) {
360
+ return "javascript";
361
+ }
362
+ if (fileExtensions.has("py")) {
363
+ return "python";
364
+ }
365
+ if (fileExtensions.has("go")) {
366
+ return "golang";
367
+ }
368
+ if (fileExtensions.has("java")) {
369
+ return "java";
370
+ }
371
+ if (fileExtensions.has("cpp") || fileExtensions.has("c")) {
372
+ return "cpp";
373
+ }
374
+ return "mixed";
353
375
  }
354
376
  /**
355
377
  * Assess complexity level for better AI context
356
378
  */
357
379
  assessComplexity(context) {
358
380
  const fileCount = context.diffStrategy.fileCount;
359
- const hasLargeFiles = context.diffStrategy.estimatedSize.includes('Large');
381
+ const hasLargeFiles = context.diffStrategy.estimatedSize.includes("Large");
360
382
  const hasComments = (context.pr.comments?.length || 0) > 0;
361
- if (fileCount > 50)
362
- return 'very-high';
363
- if (fileCount > 20 || hasLargeFiles)
364
- return 'high';
365
- if (fileCount > 10 || hasComments)
366
- return 'medium';
367
- return 'low';
383
+ if (fileCount > 50) {
384
+ return "very-high";
385
+ }
386
+ if (fileCount > 20 || hasLargeFiles) {
387
+ return "high";
388
+ }
389
+ if (fileCount > 10 || hasComments) {
390
+ return "medium";
391
+ }
392
+ return "low";
368
393
  }
369
394
  /**
370
395
  * Legacy method - kept for compatibility but simplified
@@ -373,21 +398,37 @@ Return ONLY valid JSON:
373
398
  // Legacy method - now delegates to new structure
374
399
  return this.buildCoreAnalysisPrompt(context);
375
400
  }
401
+ /**
402
+ * Get safe token limit based on AI provider using shared utility
403
+ */
404
+ getSafeTokenLimit() {
405
+ const provider = this.aiConfig.provider || "auto";
406
+ const configuredTokens = this.aiConfig.maxTokens;
407
+ // Use conservative limits for CodeReviewer (safer for large diffs)
408
+ const providerLimit = getProviderTokenLimit(provider, true);
409
+ // Use the smaller of configured tokens or provider limit
410
+ if (configuredTokens && configuredTokens > 0) {
411
+ const safeLimit = Math.min(configuredTokens, providerLimit);
412
+ logger.debug(`Token limit: configured=${configuredTokens}, provider=${providerLimit}, using=${safeLimit}`);
413
+ return safeLimit;
414
+ }
415
+ logger.debug(`Token limit: using provider default=${providerLimit} for ${provider}`);
416
+ return providerLimit;
417
+ }
376
418
  /**
377
419
  * Analyze code with AI using the enhanced prompt
378
420
  */
379
421
  async analyzeWithAI(prompt, context) {
380
422
  try {
381
- Logger_1.logger.debug('Starting AI analysis...');
423
+ logger.debug("Starting AI analysis...");
382
424
  // Initialize NeuroLink with eval-based dynamic import
383
425
  if (!this.neurolink) {
384
- const dynamicImport = eval('(specifier) => import(specifier)');
385
- const { NeuroLink } = await dynamicImport('@juspay/neurolink');
426
+ const { NeuroLink } = await import("@juspay/neurolink");
386
427
  this.neurolink = new NeuroLink();
387
428
  }
388
429
  // Extract context from unified context for better AI understanding
389
430
  const aiContext = {
390
- operation: 'code-review',
431
+ operation: "code-review",
391
432
  repository: `${context.identifier.workspace}/${context.identifier.repository}`,
392
433
  branch: context.identifier.branch,
393
434
  prId: context.identifier.pullRequestId,
@@ -395,52 +436,59 @@ Return ONLY valid JSON:
395
436
  prAuthor: context.pr.author,
396
437
  fileCount: context.diffStrategy.fileCount,
397
438
  diffStrategy: context.diffStrategy.strategy,
398
- analysisType: context.diffStrategy.strategy === 'whole' ? 'comprehensive' : 'file-by-file',
439
+ analysisType: context.diffStrategy.strategy === "whole"
440
+ ? "comprehensive"
441
+ : "file-by-file",
399
442
  projectType: this.detectProjectType(context),
400
443
  hasExistingComments: (context.pr.comments?.length || 0) > 0,
401
- complexity: this.assessComplexity(context)
444
+ complexity: this.assessComplexity(context),
402
445
  };
403
446
  // Simplified, focused prompt without context pollution
404
447
  const corePrompt = this.buildCoreAnalysisPrompt(context);
448
+ // Get safe token limit based on provider
449
+ const safeMaxTokens = this.getSafeTokenLimit();
450
+ logger.debug(`Using AI provider: ${this.aiConfig.provider || "auto"}`);
451
+ logger.debug(`Configured maxTokens: ${this.aiConfig.maxTokens}`);
452
+ logger.debug(`Safe maxTokens limit: ${safeMaxTokens}`);
405
453
  const result = await this.neurolink.generate({
406
454
  input: { text: corePrompt },
407
455
  systemPrompt: this.getSecurityReviewSystemPrompt(),
408
- provider: this.aiConfig.provider || 'auto', // Auto-select best provider
409
- model: this.aiConfig.model || 'best', // Use most capable model
456
+ provider: this.aiConfig.provider || "auto", // Auto-select best provider
457
+ model: this.aiConfig.model || "best", // Use most capable model
410
458
  temperature: this.aiConfig.temperature || 0.3, // Lower for more focused analysis
411
- maxTokens: Math.max(this.aiConfig.maxTokens || 0, 2000000), // Quality first - always use higher limit
412
- timeout: '15m', // Allow plenty of time for thorough analysis
459
+ maxTokens: safeMaxTokens, // Use provider-aware safe token limit
460
+ timeout: "15m", // Allow plenty of time for thorough analysis
413
461
  context: aiContext,
414
462
  enableAnalytics: this.aiConfig.enableAnalytics || true,
415
- enableEvaluation: false // Disabled to prevent evaluation warnings
463
+ enableEvaluation: false, // Disabled to prevent evaluation warnings
416
464
  });
417
465
  // Log analytics if available
418
466
  if (result.analytics) {
419
- Logger_1.logger.debug(`AI Analytics - Provider: ${result.provider}, Response Time: ${result.responseTime}ms, Quality Score: ${result.evaluation?.overallScore}`);
467
+ logger.debug(`AI Analytics - Provider: ${result.provider}, Response Time: ${result.responseTime}ms, Quality Score: ${result.evaluation?.overallScore}`);
420
468
  }
421
- Logger_1.logger.debug('AI analysis completed, parsing response...');
469
+ logger.debug("AI analysis completed, parsing response...");
422
470
  // Modern NeuroLink returns { content: string }
423
471
  const analysisData = this.parseAIResponse(result);
424
472
  // Display AI response for debugging
425
- if (Logger_1.logger.getConfig().verbose) {
426
- Logger_1.logger.debug('AI Analysis Response:');
427
- Logger_1.logger.debug(''.repeat(80));
428
- Logger_1.logger.debug(JSON.stringify(analysisData, null, 2));
429
- Logger_1.logger.debug(''.repeat(80));
473
+ if (logger.getConfig().verbose) {
474
+ logger.debug("AI Analysis Response:");
475
+ logger.debug("".repeat(80));
476
+ logger.debug(JSON.stringify(analysisData, null, 2));
477
+ logger.debug("".repeat(80));
430
478
  }
431
479
  if (!analysisData.violations || !Array.isArray(analysisData.violations)) {
432
- Logger_1.logger.debug('No violations array found in AI response');
480
+ logger.debug("No violations array found in AI response");
433
481
  return [];
434
482
  }
435
- Logger_1.logger.debug(`AI analysis found ${analysisData.violations.length} violations`);
483
+ logger.debug(`AI analysis found ${analysisData.violations.length} violations`);
436
484
  return analysisData.violations;
437
485
  }
438
486
  catch (error) {
439
- if (error.message?.includes('timeout')) {
440
- Logger_1.logger.error('⏰ AI analysis timed out after 15 minutes');
441
- throw new Error('Analysis timeout - try reducing diff size or adjusting timeout');
487
+ if (error.message?.includes("timeout")) {
488
+ logger.error("⏰ AI analysis timed out after 15 minutes");
489
+ throw new Error("Analysis timeout - try reducing diff size or adjusting timeout");
442
490
  }
443
- Logger_1.logger.error(`AI analysis failed: ${error.message}`);
491
+ logger.error(`AI analysis failed: ${error.message}`);
444
492
  throw error;
445
493
  }
446
494
  }
@@ -448,12 +496,12 @@ Return ONLY valid JSON:
448
496
  * Post comments to PR using unified context - matching pr-police.js exactly
449
497
  */
450
498
  async postComments(context, violations, _options) {
451
- Logger_1.logger.phase('📝 Posting review comments...');
499
+ logger.phase("📝 Posting review comments...");
452
500
  let commentsPosted = 0;
453
501
  let commentsFailed = 0;
454
502
  const failedComments = [];
455
503
  // Post inline comments
456
- const inlineViolations = violations.filter(v => v.type === 'inline' && v.file && v.code_snippet);
504
+ const inlineViolations = violations.filter((v) => v.type === "inline" && v.file && v.code_snippet);
457
505
  for (const violation of inlineViolations) {
458
506
  try {
459
507
  // Clean file path - remove protocol prefixes ONLY (keep a/ and b/ prefixes)
@@ -467,19 +515,19 @@ Return ONLY valid JSON:
467
515
  // Clean code snippet and fix search context - EXACTLY like pr-police.js
468
516
  const processedViolation = this.cleanCodeSnippet(violation);
469
517
  if (!processedViolation) {
470
- Logger_1.logger.debug(`⚠️ Skipping invalid violation for ${cleanFilePath}`);
518
+ logger.debug(`⚠️ Skipping invalid violation for ${cleanFilePath}`);
471
519
  continue;
472
520
  }
473
521
  const formattedComment = this.formatInlineComment(processedViolation);
474
522
  // Debug logging
475
- Logger_1.logger.debug(`🔍 Posting inline comment:`);
476
- Logger_1.logger.debug(` File: ${cleanFilePath}`);
477
- Logger_1.logger.debug(` Issue: ${processedViolation.issue}`);
478
- Logger_1.logger.debug(` Original snippet: ${violation.code_snippet}`);
479
- Logger_1.logger.debug(` Processed snippet: ${processedViolation.code_snippet}`);
523
+ logger.debug(`🔍 Posting inline comment:`);
524
+ logger.debug(` File: ${cleanFilePath}`);
525
+ logger.debug(` Issue: ${processedViolation.issue}`);
526
+ logger.debug(` Original snippet: ${violation.code_snippet}`);
527
+ logger.debug(` Processed snippet: ${processedViolation.code_snippet}`);
480
528
  if (processedViolation.search_context) {
481
- Logger_1.logger.debug(` Search context before: ${JSON.stringify(processedViolation.search_context.before)}`);
482
- Logger_1.logger.debug(` Search context after: ${JSON.stringify(processedViolation.search_context.after)}`);
529
+ logger.debug(` Search context before: ${JSON.stringify(processedViolation.search_context.before)}`);
530
+ logger.debug(` Search context after: ${JSON.stringify(processedViolation.search_context.after)}`);
483
531
  }
484
532
  // Use new code snippet approach - EXACTLY like pr-police.js
485
533
  await this.bitbucketProvider.addComment(context.identifier, formattedComment, {
@@ -489,21 +537,21 @@ Return ONLY valid JSON:
489
537
  codeSnippet: processedViolation.code_snippet,
490
538
  searchContext: processedViolation.search_context,
491
539
  matchStrategy: "best", // Use best match strategy instead of strict for flexibility
492
- suggestion: processedViolation.suggestion // Pass the suggestion for inline code suggestions
540
+ suggestion: processedViolation.suggestion, // Pass the suggestion for inline code suggestions
493
541
  });
494
542
  commentsPosted++;
495
- Logger_1.logger.debug(`✅ Posted inline comment: ${cleanFilePath} (${processedViolation.issue})`);
543
+ logger.debug(`✅ Posted inline comment: ${cleanFilePath} (${processedViolation.issue})`);
496
544
  }
497
545
  catch (error) {
498
546
  commentsFailed++;
499
547
  const errorMsg = error.message;
500
- Logger_1.logger.debug(`❌ Failed to post inline comment: ${errorMsg}`);
501
- Logger_1.logger.debug(` File: ${violation.file}, Issue: ${violation.issue}`);
502
- Logger_1.logger.debug(` Code snippet: ${violation.code_snippet}`);
548
+ logger.debug(`❌ Failed to post inline comment: ${errorMsg}`);
549
+ logger.debug(` File: ${violation.file}, Issue: ${violation.issue}`);
550
+ logger.debug(` Code snippet: ${violation.code_snippet}`);
503
551
  failedComments.push({
504
552
  file: violation.file,
505
553
  issue: violation.issue,
506
- error: errorMsg
554
+ error: errorMsg,
507
555
  });
508
556
  }
509
557
  }
@@ -513,15 +561,15 @@ Return ONLY valid JSON:
513
561
  const summaryComment = this.generateSummaryComment(violations, context, failedComments);
514
562
  await this.bitbucketProvider.addComment(context.identifier, summaryComment);
515
563
  commentsPosted++;
516
- Logger_1.logger.debug('✅ Posted summary comment');
564
+ logger.debug("✅ Posted summary comment");
517
565
  }
518
566
  catch (error) {
519
- Logger_1.logger.debug(`❌ Failed to post summary comment: ${error.message}`);
567
+ logger.debug(`❌ Failed to post summary comment: ${error.message}`);
520
568
  }
521
569
  }
522
- Logger_1.logger.success(`✅ Posted ${commentsPosted} comments successfully`);
570
+ logger.success(`✅ Posted ${commentsPosted} comments successfully`);
523
571
  if (commentsFailed > 0) {
524
- Logger_1.logger.warn(`⚠️ Failed to post ${commentsFailed} inline comments`);
572
+ logger.warn(`⚠️ Failed to post ${commentsFailed} inline comments`);
525
573
  }
526
574
  }
527
575
  /**
@@ -529,14 +577,23 @@ Return ONLY valid JSON:
529
577
  */
530
578
  formatInlineComment(violation) {
531
579
  const severityConfig = {
532
- CRITICAL: { emoji: '🚨', badge: '**🚨 CRITICAL SECURITY ISSUE**', color: 'red' },
533
- MAJOR: { emoji: '⚠️', badge: '**⚠️ MAJOR ISSUE**', color: 'orange' },
534
- MINOR: { emoji: '📝', badge: '**📝 MINOR IMPROVEMENT**', color: 'blue' },
535
- SUGGESTION: { emoji: '💡', badge: '**💡 SUGGESTION**', color: 'green' }
580
+ CRITICAL: {
581
+ emoji: "🚨",
582
+ badge: "**🚨 CRITICAL SECURITY ISSUE**",
583
+ color: "red",
584
+ },
585
+ MAJOR: { emoji: "⚠️", badge: "**⚠️ MAJOR ISSUE**", color: "orange" },
586
+ MINOR: { emoji: "📝", badge: "**📝 MINOR IMPROVEMENT**", color: "blue" },
587
+ SUGGESTION: { emoji: "💡", badge: "**💡 SUGGESTION**", color: "green" },
536
588
  };
537
589
  const categoryIcons = {
538
- security: '🔒', performance: '⚡', maintainability: '🏗️',
539
- functionality: '⚙️', error_handling: '🛡️', testing: '🧪', general: '📋'
590
+ security: "🔒",
591
+ performance: "⚡",
592
+ maintainability: "🏗️",
593
+ functionality: "⚙️",
594
+ error_handling: "🛡️",
595
+ testing: "🧪",
596
+ general: "📋",
540
597
  };
541
598
  const config = severityConfig[violation.severity] || severityConfig.MINOR;
542
599
  const categoryIcon = categoryIcons[violation.category] || categoryIcons.general;
@@ -544,20 +601,30 @@ Return ONLY valid JSON:
544
601
 
545
602
  **${categoryIcon} ${violation.issue}**
546
603
 
547
- **Category**: ${violation.category.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase())}
604
+ **Category**: ${violation.category.replace(/_/g, " ").replace(/\b\w/g, (l) => l.toUpperCase())}
548
605
 
549
606
  **Issue**: ${violation.message}`;
550
607
  if (violation.impact) {
551
608
  comment += `\n\n**Impact**: ${violation.impact}`;
552
609
  }
553
610
  if (violation.suggestion) {
554
- const fileExt = violation.file?.split('.').pop() || 'text';
611
+ const fileExt = violation.file?.split(".").pop() || "text";
555
612
  const langMap = {
556
- js: 'javascript', jsx: 'javascript', ts: 'typescript', tsx: 'typescript',
557
- res: 'rescript', resi: 'rescript', py: 'python', java: 'java',
558
- go: 'go', rb: 'ruby', php: 'php', sql: 'sql', json: 'json'
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",
559
626
  };
560
- const language = langMap[fileExt] || 'text';
627
+ const language = langMap[fileExt] || "text";
561
628
  // Use the escape method for code blocks
562
629
  const escapedCodeBlock = this.escapeMarkdownCodeBlock(violation.suggestion, language);
563
630
  comment += `\n\n**💡 Suggested Fix**:\n${escapedCodeBlock}`;
@@ -570,13 +637,20 @@ Return ONLY valid JSON:
570
637
  */
571
638
  generateSummaryComment(violations, context, failedComments = []) {
572
639
  const stats = this.calculateStats(violations);
573
- const statusEmoji = stats.criticalCount > 0 ? '🚨' :
574
- stats.majorCount > 0 ? '⚠️ ' :
575
- stats.minorCount > 0 ? '📝' : '✅';
576
- const statusText = stats.criticalCount > 0 ? 'CRITICAL ISSUES FOUND' :
577
- stats.majorCount > 0 ? 'ISSUES DETECTED' :
578
- stats.minorCount > 0 ? 'IMPROVEMENTS SUGGESTED' :
579
- 'CODE QUALITY APPROVED';
640
+ const statusEmoji = stats.criticalCount > 0
641
+ ? "🚨"
642
+ : stats.majorCount > 0
643
+ ? "⚠️ "
644
+ : stats.minorCount > 0
645
+ ? "📝"
646
+ : "✅";
647
+ const statusText = stats.criticalCount > 0
648
+ ? "CRITICAL ISSUES FOUND"
649
+ : stats.majorCount > 0
650
+ ? "ISSUES DETECTED"
651
+ : stats.minorCount > 0
652
+ ? "IMPROVEMENTS SUGGESTED"
653
+ : "CODE QUALITY APPROVED";
580
654
  let comment = `
581
655
  ╭─────────────────────────────────────────────────────────────╮
582
656
  │ ⚔️ **YAMA REVIEW REPORT** ⚔️ │
@@ -587,10 +661,10 @@ Return ONLY valid JSON:
587
661
  ### 📊 **Security & Quality Analysis**
588
662
  | **Severity** | **Count** | **Status** |
589
663
  |--------------|-----------|------------|
590
- | 🚨 Critical | ${stats.criticalCount} | ${stats.criticalCount > 0 ? '⛔ Must Fix' : '✅ Clear'} |
591
- | ⚠️ Major | ${stats.majorCount} | ${stats.majorCount > 0 ? '⚠️ Should Fix' : '✅ Clear'} |
592
- | 📝 Minor | ${stats.minorCount} | ${stats.minorCount > 0 ? '📝 Consider Fixing' : '✅ Clear'} |
593
- | 💡 Suggestions | ${stats.suggestionCount} | ${stats.suggestionCount > 0 ? '💡 Optional' : '✅ Clear'} |
664
+ | 🚨 Critical | ${stats.criticalCount} | ${stats.criticalCount > 0 ? "⛔ Must Fix" : "✅ Clear"} |
665
+ | ⚠️ Major | ${stats.majorCount} | ${stats.majorCount > 0 ? "⚠️ Should Fix" : "✅ Clear"} |
666
+ | 📝 Minor | ${stats.minorCount} | ${stats.minorCount > 0 ? "📝 Consider Fixing" : "✅ Clear"} |
667
+ | 💡 Suggestions | ${stats.suggestionCount} | ${stats.suggestionCount > 0 ? "💡 Optional" : "✅ Clear"} |
594
668
 
595
669
  ### 🔍 **Analysis Summary**
596
670
  - **📁 Files Analyzed**: ${context.diffStrategy.fileCount}
@@ -603,12 +677,19 @@ Return ONLY valid JSON:
603
677
  comment += `\n\n### 📍 **Issues by Category**\n`;
604
678
  for (const [category, categoryViolations] of Object.entries(violationsByCategory)) {
605
679
  const categoryIcons = {
606
- security: '🔒', performance: '⚡', maintainability: '🏗️',
607
- functionality: '⚙️', error_handling: '🛡️', testing: '🧪', general: '📋'
680
+ security: "🔒",
681
+ performance: "⚡",
682
+ maintainability: "🏗️",
683
+ functionality: "⚙️",
684
+ error_handling: "🛡️",
685
+ testing: "🧪",
686
+ general: "📋",
608
687
  };
609
- const icon = categoryIcons[category] || '📋';
610
- const name = category.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase());
611
- comment += `**${icon} ${name}**: ${categoryViolations.length} issue${categoryViolations.length !== 1 ? 's' : ''}\n`;
688
+ const icon = categoryIcons[category] || "📋";
689
+ const name = category
690
+ .replace(/_/g, " ")
691
+ .replace(/\b\w/g, (l) => l.toUpperCase());
692
+ comment += `**${icon} ${name}**: ${categoryViolations.length} issue${categoryViolations.length !== 1 ? "s" : ""}\n`;
612
693
  }
613
694
  }
614
695
  // Add failed comments section if any
@@ -617,17 +698,17 @@ Return ONLY valid JSON:
617
698
  comment += `Some inline comments could not be posted due to code matching issues. `;
618
699
  comment += `Please review the following issues manually:\n\n`;
619
700
  for (const failed of failedComments) {
620
- comment += `- **${failed.issue}** in \`${failed.file || 'unknown file'}\`\n`;
701
+ comment += `- **${failed.issue}** in \`${failed.file || "unknown file"}\`\n`;
621
702
  }
622
703
  }
623
704
  // Add recommendation
624
705
  const recommendation = stats.criticalCount > 0
625
- ? '🚨 **URGENT**: Critical security issues must be resolved before merge'
706
+ ? "🚨 **URGENT**: Critical security issues must be resolved before merge"
626
707
  : stats.majorCount > 0
627
- ? '⚠️ **RECOMMENDED**: Address major issues before merge'
708
+ ? "⚠️ **RECOMMENDED**: Address major issues before merge"
628
709
  : stats.minorCount > 0
629
- ? '📝 **OPTIONAL**: Consider addressing minor improvements'
630
- : '✅ **APPROVED**: Code meets security and quality standards';
710
+ ? "📝 **OPTIONAL**: Consider addressing minor improvements"
711
+ : "✅ **APPROVED**: Code meets security and quality standards";
631
712
  comment += `\n\n### 💡 **Recommendation**
632
713
  ${recommendation}
633
714
 
@@ -642,11 +723,10 @@ ${recommendation}
642
723
  cleanFilePath(filePath) {
643
724
  // Clean the file path but preserve the structure - EXACTLY like pr-police.js
644
725
  // Only clean src:// and dst:// prefixes, keep a/ and b/ prefixes
645
- const cleaned = filePath
646
- .replace(/^(src|dst):\/\//, '');
726
+ const cleaned = filePath.replace(/^(src|dst):\/\//, "");
647
727
  // Log the cleaning for debugging
648
728
  if (cleaned !== filePath) {
649
- Logger_1.logger.debug(`Cleaned file path: ${filePath} -> ${cleaned}`);
729
+ logger.debug(`Cleaned file path: ${filePath} -> ${cleaned}`);
650
730
  }
651
731
  return cleaned;
652
732
  }
@@ -654,12 +734,13 @@ ${recommendation}
654
734
  * Extract exact file path from diff
655
735
  */
656
736
  extractFilePathFromDiff(diff, fileName) {
657
- const lines = diff.split('\n');
737
+ const lines = diff.split("\n");
658
738
  for (const line of lines) {
659
- if (line.startsWith('diff --git')) {
739
+ if (line.startsWith("diff --git")) {
660
740
  // Extract both paths: a/path/to/file b/path/to/file
661
741
  const match = line.match(/diff --git a\/(.*?) b\/(.*?)$/);
662
- if (match && (match[1].includes(fileName) || match[2].includes(fileName))) {
742
+ if (match &&
743
+ (match[1].includes(fileName) || match[2].includes(fileName))) {
663
744
  return match[2]; // Return the 'b/' path (destination)
664
745
  }
665
746
  }
@@ -670,12 +751,12 @@ ${recommendation}
670
751
  * Extract line number from diff for a specific code snippet
671
752
  */
672
753
  extractLineNumberFromDiff(fileDiff, codeSnippet) {
673
- const lines = fileDiff.split('\n');
754
+ const lines = fileDiff.split("\n");
674
755
  let currentNewLine = 0;
675
756
  let currentOldLine = 0;
676
757
  let inHunk = false;
677
758
  // Debug logging
678
- Logger_1.logger.debug(`Looking for snippet: "${codeSnippet}"`);
759
+ logger.debug(`Looking for snippet: "${codeSnippet}"`);
679
760
  for (let i = 0; i < lines.length; i++) {
680
761
  const line = lines[i];
681
762
  // Parse hunk headers (e.g., @@ -10,6 +10,8 @@)
@@ -685,48 +766,51 @@ ${recommendation}
685
766
  currentOldLine = parseInt(hunkMatch[1]);
686
767
  currentNewLine = parseInt(hunkMatch[2]);
687
768
  inHunk = true;
688
- Logger_1.logger.debug(`Found hunk header: old=${currentOldLine}, new=${currentNewLine}`);
769
+ logger.debug(`Found hunk header: old=${currentOldLine}, new=${currentNewLine}`);
689
770
  continue;
690
771
  }
691
772
  // Skip lines that aren't part of the diff content
692
- if (!inHunk || (!line.startsWith('+') && !line.startsWith('-') && !line.startsWith(' '))) {
773
+ if (!inHunk ||
774
+ (!line.startsWith("+") &&
775
+ !line.startsWith("-") &&
776
+ !line.startsWith(" "))) {
693
777
  continue;
694
778
  }
695
779
  // Check if this line matches our snippet
696
780
  if (line === codeSnippet) {
697
781
  let resultLine;
698
782
  let lineType;
699
- if (line.startsWith('+')) {
783
+ if (line.startsWith("+")) {
700
784
  resultLine = currentNewLine;
701
- lineType = 'ADDED';
785
+ lineType = "ADDED";
702
786
  }
703
- else if (line.startsWith('-')) {
787
+ else if (line.startsWith("-")) {
704
788
  resultLine = currentOldLine;
705
- lineType = 'REMOVED';
789
+ lineType = "REMOVED";
706
790
  }
707
791
  else {
708
792
  resultLine = currentNewLine;
709
- lineType = 'CONTEXT';
793
+ lineType = "CONTEXT";
710
794
  }
711
- Logger_1.logger.debug(`Found match at line ${resultLine} (${lineType})`);
795
+ logger.debug(`Found match at line ${resultLine} (${lineType})`);
712
796
  return { lineNumber: resultLine, lineType };
713
797
  }
714
798
  // Update line counters AFTER checking for match
715
799
  // For added lines: only increment new line counter
716
800
  // For removed lines: only increment old line counter
717
801
  // For context lines: increment both counters
718
- if (line.startsWith('+')) {
802
+ if (line.startsWith("+")) {
719
803
  currentNewLine++;
720
804
  }
721
- else if (line.startsWith('-')) {
805
+ else if (line.startsWith("-")) {
722
806
  currentOldLine++;
723
807
  }
724
- else if (line.startsWith(' ')) {
808
+ else if (line.startsWith(" ")) {
725
809
  currentNewLine++;
726
810
  currentOldLine++;
727
811
  }
728
812
  }
729
- Logger_1.logger.debug(`Snippet not found in diff`);
813
+ logger.debug(`Snippet not found in diff`);
730
814
  return null;
731
815
  }
732
816
  /**
@@ -734,7 +818,7 @@ ${recommendation}
734
818
  */
735
819
  escapeMarkdownCodeBlock(code, language) {
736
820
  // If code contains triple backticks, use quadruple backticks
737
- if (code.includes('```')) {
821
+ if (code.includes("```")) {
738
822
  return `\`\`\`\`${language}\n${code}\n\`\`\`\``;
739
823
  }
740
824
  return `\`\`\`${language}\n${code}\n\`\`\``;
@@ -745,49 +829,51 @@ ${recommendation}
745
829
  const fixed = JSON.parse(JSON.stringify(violation));
746
830
  // Fix search_context arrays if they contain embedded newlines
747
831
  if (fixed.search_context) {
748
- if (fixed.search_context.before && Array.isArray(fixed.search_context.before)) {
832
+ if (fixed.search_context.before &&
833
+ Array.isArray(fixed.search_context.before)) {
749
834
  fixed.search_context.before = this.splitArrayLines(fixed.search_context.before);
750
835
  }
751
- if (fixed.search_context.after && Array.isArray(fixed.search_context.after)) {
836
+ if (fixed.search_context.after &&
837
+ Array.isArray(fixed.search_context.after)) {
752
838
  fixed.search_context.after = this.splitArrayLines(fixed.search_context.after);
753
839
  }
754
840
  }
755
841
  // Ensure line_type is set based on code snippet prefix BEFORE cleaning
756
842
  if (!fixed.line_type && fixed.code_snippet) {
757
- if (fixed.code_snippet.startsWith('+')) {
758
- fixed.line_type = 'ADDED';
843
+ if (fixed.code_snippet.startsWith("+")) {
844
+ fixed.line_type = "ADDED";
759
845
  }
760
- else if (fixed.code_snippet.startsWith('-')) {
761
- fixed.line_type = 'REMOVED';
846
+ else if (fixed.code_snippet.startsWith("-")) {
847
+ fixed.line_type = "REMOVED";
762
848
  }
763
849
  else {
764
- fixed.line_type = 'CONTEXT';
850
+ fixed.line_type = "CONTEXT";
765
851
  }
766
852
  }
767
853
  // Clean the code_snippet field to remove diff symbols - EXACTLY like pr-police.js
768
854
  if (fixed.code_snippet) {
769
- fixed.code_snippet = fixed.code_snippet.replace(/^[+\-\s]/, '').trim();
855
+ fixed.code_snippet = fixed.code_snippet.replace(/^[+\-\s]/, "").trim();
770
856
  }
771
857
  // Clean the suggestion field to remove any diff symbols
772
858
  if (fixed.suggestion) {
773
859
  fixed.suggestion = fixed.suggestion
774
- .split('\n')
775
- .map((line) => line.replace(/^[+\-\s]/, '')) // Remove diff symbols at start of each line
776
- .join('\n')
860
+ .split("\n")
861
+ .map((line) => line.replace(/^[+\-\s]/, "")) // Remove diff symbols at start of each line
862
+ .join("\n")
777
863
  .trim();
778
864
  }
779
865
  return fixed;
780
866
  }
781
867
  catch (error) {
782
- Logger_1.logger.debug(`❌ Error cleaning code snippet: ${error.message}`);
868
+ logger.debug(`❌ Error cleaning code snippet: ${error.message}`);
783
869
  return null;
784
870
  }
785
871
  }
786
872
  splitArrayLines(arr) {
787
873
  const result = [];
788
874
  for (const item of arr) {
789
- if (typeof item === 'string' && item.includes('\n')) {
790
- result.push(...item.split('\n').filter(line => line.length > 0));
875
+ if (typeof item === "string" && item.includes("\n")) {
876
+ result.push(...item.split("\n").filter((line) => line.length > 0));
791
877
  }
792
878
  else {
793
879
  result.push(item);
@@ -797,22 +883,24 @@ ${recommendation}
797
883
  }
798
884
  groupViolationsByCategory(violations) {
799
885
  const grouped = {};
800
- violations.forEach(v => {
801
- const category = v.category || 'general';
802
- if (!grouped[category])
886
+ violations.forEach((v) => {
887
+ const category = v.category || "general";
888
+ if (!grouped[category]) {
803
889
  grouped[category] = [];
890
+ }
804
891
  grouped[category].push(v);
805
892
  });
806
893
  return grouped;
807
894
  }
808
895
  calculateStats(violations) {
809
896
  return {
810
- criticalCount: violations.filter(v => v.severity === 'CRITICAL').length,
811
- majorCount: violations.filter(v => v.severity === 'MAJOR').length,
812
- minorCount: violations.filter(v => v.severity === 'MINOR').length,
813
- suggestionCount: violations.filter(v => v.severity === 'SUGGESTION').length,
897
+ criticalCount: violations.filter((v) => v.severity === "CRITICAL").length,
898
+ majorCount: violations.filter((v) => v.severity === "MAJOR").length,
899
+ minorCount: violations.filter((v) => v.severity === "MINOR").length,
900
+ suggestionCount: violations.filter((v) => v.severity === "SUGGESTION")
901
+ .length,
814
902
  totalIssues: violations.length,
815
- filesReviewed: new Set(violations.filter(v => v.file).map(v => v.file)).size || 1
903
+ filesReviewed: new Set(violations.filter((v) => v.file).map((v) => v.file)).size || 1,
816
904
  };
817
905
  }
818
906
  generateReviewResult(violations, _duration, _context) {
@@ -826,9 +914,9 @@ ${recommendation}
826
914
  criticalCount: stats.criticalCount,
827
915
  majorCount: stats.majorCount,
828
916
  minorCount: stats.minorCount,
829
- suggestionCount: stats.suggestionCount
917
+ suggestionCount: stats.suggestionCount,
830
918
  },
831
- positiveObservations: [] // Could be extracted from AI response
919
+ positiveObservations: [], // Could be extracted from AI response
832
920
  };
833
921
  }
834
922
  /**
@@ -836,7 +924,7 @@ ${recommendation}
836
924
  */
837
925
  parseAIResponse(result) {
838
926
  try {
839
- const responseText = result.content || result.text || result.response || '';
927
+ const responseText = result.content || result.text || result.response || "";
840
928
  if (!responseText) {
841
929
  return { violations: [] };
842
930
  }
@@ -848,7 +936,7 @@ ${recommendation}
848
936
  return { violations: [] };
849
937
  }
850
938
  catch (error) {
851
- Logger_1.logger.debug(`Failed to parse AI response: ${error.message}`);
939
+ logger.debug(`Failed to parse AI response: ${error.message}`);
852
940
  return { violations: [] };
853
941
  }
854
942
  }
@@ -856,20 +944,21 @@ ${recommendation}
856
944
  * Extract line information for comment from context
857
945
  */
858
946
  extractLineInfoForComment(violation, context) {
859
- if (!violation.file || !violation.code_snippet)
947
+ if (!violation.file || !violation.code_snippet) {
860
948
  return null;
949
+ }
861
950
  try {
862
951
  // Get the diff for this specific file
863
952
  let fileDiff;
864
- if (context.diffStrategy.strategy === 'whole' && context.prDiff) {
953
+ if (context.diffStrategy.strategy === "whole" && context.prDiff) {
865
954
  // Extract file diff from whole diff
866
- const diffLines = context.prDiff.diff.split('\n');
955
+ const diffLines = context.prDiff.diff.split("\n");
867
956
  let fileStartIndex = -1;
868
957
  // Create all possible path variations for matching
869
958
  const filePathVariations = this.generatePathVariations(violation.file);
870
959
  for (let i = 0; i < diffLines.length; i++) {
871
960
  const line = diffLines[i];
872
- if (line.startsWith('diff --git')) {
961
+ if (line.startsWith("diff --git")) {
873
962
  // Check if any variation matches
874
963
  for (const pathVariation of filePathVariations) {
875
964
  if (line.includes(pathVariation)) {
@@ -877,22 +966,26 @@ ${recommendation}
877
966
  break;
878
967
  }
879
968
  }
880
- if (fileStartIndex >= 0)
969
+ if (fileStartIndex >= 0) {
881
970
  break;
971
+ }
882
972
  }
883
973
  }
884
974
  if (fileStartIndex >= 0) {
885
- const nextFileIndex = diffLines.findIndex((line, idx) => idx > fileStartIndex && line.startsWith('diff --git'));
886
- fileDiff = diffLines.slice(fileStartIndex, nextFileIndex > 0 ? nextFileIndex : diffLines.length).join('\n');
975
+ const nextFileIndex = diffLines.findIndex((line, idx) => idx > fileStartIndex && line.startsWith("diff --git"));
976
+ fileDiff = diffLines
977
+ .slice(fileStartIndex, nextFileIndex > 0 ? nextFileIndex : diffLines.length)
978
+ .join("\n");
887
979
  }
888
980
  }
889
- else if (context.diffStrategy.strategy === 'file-by-file' && context.fileDiffs) {
981
+ else if (context.diffStrategy.strategy === "file-by-file" &&
982
+ context.fileDiffs) {
890
983
  // Try all possible path variations
891
984
  const pathVariations = this.generatePathVariations(violation.file);
892
985
  for (const path of pathVariations) {
893
986
  fileDiff = context.fileDiffs.get(path);
894
987
  if (fileDiff) {
895
- Logger_1.logger.debug(`Found diff for ${violation.file} using variation: ${path}`);
988
+ logger.debug(`Found diff for ${violation.file} using variation: ${path}`);
896
989
  break;
897
990
  }
898
991
  }
@@ -901,7 +994,7 @@ ${recommendation}
901
994
  for (const [key, value] of context.fileDiffs.entries()) {
902
995
  if (key.endsWith(violation.file) || violation.file.endsWith(key)) {
903
996
  fileDiff = value;
904
- Logger_1.logger.debug(`Found diff for ${violation.file} using partial match: ${key}`);
997
+ logger.debug(`Found diff for ${violation.file} using partial match: ${key}`);
905
998
  break;
906
999
  }
907
1000
  }
@@ -910,16 +1003,16 @@ ${recommendation}
910
1003
  if (fileDiff) {
911
1004
  const lineInfo = this.extractLineNumberFromDiff(fileDiff, violation.code_snippet);
912
1005
  if (lineInfo) {
913
- Logger_1.logger.debug(`Extracted line info for ${violation.file}: line ${lineInfo.lineNumber}, type ${lineInfo.lineType}`);
1006
+ logger.debug(`Extracted line info for ${violation.file}: line ${lineInfo.lineNumber}, type ${lineInfo.lineType}`);
914
1007
  }
915
1008
  return lineInfo;
916
1009
  }
917
1010
  else {
918
- Logger_1.logger.debug(`No diff found for file: ${violation.file}`);
1011
+ logger.debug(`No diff found for file: ${violation.file}`);
919
1012
  }
920
1013
  }
921
1014
  catch (error) {
922
- Logger_1.logger.debug(`Error extracting line info: ${error.message}`);
1015
+ logger.debug(`Error extracting line info: ${error.message}`);
923
1016
  }
924
1017
  return null;
925
1018
  }
@@ -933,29 +1026,29 @@ ${recommendation}
933
1026
  // Add with a/ and b/ prefixes
934
1027
  variations.add(`a/${filePath}`);
935
1028
  variations.add(`b/${filePath}`);
936
- // Handle nested paths
937
- if (filePath.includes('/')) {
938
- const parts = filePath.split('/');
1029
+ // Handle nested paths
1030
+ if (filePath.includes("/")) {
1031
+ const parts = filePath.split("/");
939
1032
  // Try removing first directory
940
1033
  if (parts.length > 1) {
941
- variations.add(parts.slice(1).join('/'));
1034
+ variations.add(parts.slice(1).join("/"));
942
1035
  }
943
1036
  // Try removing first two directories
944
1037
  if (parts.length > 2) {
945
- variations.add(parts.slice(2).join('/'));
1038
+ variations.add(parts.slice(2).join("/"));
946
1039
  }
947
1040
  // Try with just the filename
948
1041
  variations.add(parts[parts.length - 1]);
949
1042
  }
950
1043
  // Remove app/ prefix variations
951
- if (filePath.startsWith('app/')) {
1044
+ if (filePath.startsWith("app/")) {
952
1045
  const withoutApp = filePath.substring(4);
953
1046
  variations.add(withoutApp);
954
1047
  variations.add(`a/${withoutApp}`);
955
1048
  variations.add(`b/${withoutApp}`);
956
1049
  }
957
1050
  // Add app/ prefix variations
958
- if (!filePath.startsWith('app/')) {
1051
+ if (!filePath.startsWith("app/")) {
959
1052
  variations.add(`app/${filePath}`);
960
1053
  variations.add(`a/app/${filePath}`);
961
1054
  variations.add(`b/app/${filePath}`);
@@ -963,8 +1056,7 @@ ${recommendation}
963
1056
  return Array.from(variations);
964
1057
  }
965
1058
  }
966
- exports.CodeReviewer = CodeReviewer;
967
- function createCodeReviewer(bitbucketProvider, aiConfig, reviewConfig) {
1059
+ export function createCodeReviewer(bitbucketProvider, aiConfig, reviewConfig) {
968
1060
  return new CodeReviewer(bitbucketProvider, aiConfig, reviewConfig);
969
1061
  }
970
1062
  //# sourceMappingURL=CodeReviewer.js.map