@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.
@@ -2,8 +2,8 @@
2
2
  * Unified Context Gatherer - The foundation for all Yama operations
3
3
  * Gathers all necessary context once and reuses it across all operations
4
4
  */
5
- import { PRIdentifier, PRInfo, PRDiff, AIProviderConfig, DiffStrategyConfig } from '../types';
6
- import { BitbucketProvider } from './providers/BitbucketProvider';
5
+ import { PRIdentifier, PRInfo, PRDiff, AIProviderConfig, DiffStrategyConfig, MemoryBankConfig } from "../types/index.js";
6
+ import { BitbucketProvider } from "./providers/BitbucketProvider.js";
7
7
  export interface ProjectContext {
8
8
  memoryBank: {
9
9
  summary: string;
@@ -15,7 +15,7 @@ export interface ProjectContext {
15
15
  filesProcessed: number;
16
16
  }
17
17
  export interface DiffStrategy {
18
- strategy: 'whole' | 'file-by-file';
18
+ strategy: "whole" | "file-by-file";
19
19
  reason: string;
20
20
  fileCount: number;
21
21
  estimatedSize: string;
@@ -36,8 +36,9 @@ export declare class ContextGatherer {
36
36
  private neurolink;
37
37
  private bitbucketProvider;
38
38
  private aiConfig;
39
+ private memoryBankManager;
39
40
  private startTime;
40
- constructor(bitbucketProvider: BitbucketProvider, aiConfig: AIProviderConfig);
41
+ constructor(bitbucketProvider: BitbucketProvider, aiConfig: AIProviderConfig, memoryBankConfig: MemoryBankConfig);
41
42
  /**
42
43
  * Main context gathering method - used by all operations
43
44
  */
@@ -56,6 +57,10 @@ export declare class ContextGatherer {
56
57
  * Step 2: Gather project context (memory bank + clinerules)
57
58
  */
58
59
  private gatherProjectContext;
60
+ /**
61
+ * Get safe token limit based on AI provider using shared utility
62
+ */
63
+ private getSafeTokenLimit;
59
64
  /**
60
65
  * Parse project context with AI
61
66
  */
@@ -101,5 +106,5 @@ export declare class ContextGatherer {
101
106
  */
102
107
  getStats(): any;
103
108
  }
104
- export declare function createContextGatherer(bitbucketProvider: BitbucketProvider, aiConfig: AIProviderConfig): ContextGatherer;
109
+ export declare function createContextGatherer(bitbucketProvider: BitbucketProvider, aiConfig: AIProviderConfig, memoryBankConfig: MemoryBankConfig): ContextGatherer;
105
110
  //# sourceMappingURL=ContextGatherer.d.ts.map
@@ -1,20 +1,23 @@
1
- "use strict";
2
1
  /**
3
2
  * Unified Context Gatherer - The foundation for all Yama operations
4
3
  * Gathers all necessary context once and reuses it across all operations
5
4
  */
6
- Object.defineProperty(exports, "__esModule", { value: true });
7
- exports.ContextGatherer = void 0;
8
- exports.createContextGatherer = createContextGatherer;
9
5
  // NeuroLink will be dynamically imported
10
- const types_1 = require("../types");
11
- const Logger_1 = require("../utils/Logger");
12
- const Cache_1 = require("../utils/Cache");
13
- class ContextGatherer {
14
- constructor(bitbucketProvider, aiConfig) {
15
- this.startTime = 0;
6
+ import { ProviderError, } from "../types/index.js";
7
+ import { logger } from "../utils/Logger.js";
8
+ import { cache, Cache } from "../utils/Cache.js";
9
+ import { createMemoryBankManager } from "../utils/MemoryBankManager.js";
10
+ import { getProviderTokenLimit } from "../utils/ProviderLimits.js";
11
+ export class ContextGatherer {
12
+ neurolink;
13
+ bitbucketProvider;
14
+ aiConfig;
15
+ memoryBankManager;
16
+ startTime = 0;
17
+ constructor(bitbucketProvider, aiConfig, memoryBankConfig) {
16
18
  this.bitbucketProvider = bitbucketProvider;
17
19
  this.aiConfig = aiConfig;
20
+ this.memoryBankManager = createMemoryBankManager(memoryBankConfig, bitbucketProvider);
18
21
  }
19
22
  /**
20
23
  * Main context gathering method - used by all operations
@@ -23,29 +26,29 @@ class ContextGatherer {
23
26
  this.startTime = Date.now();
24
27
  const contextId = this.generateContextId(identifier);
25
28
  const cacheHits = [];
26
- Logger_1.logger.phase('🔍 Gathering unified context...');
27
- Logger_1.logger.info(`Target: ${identifier.workspace}/${identifier.repository}`);
29
+ logger.phase("🔍 Gathering unified context...");
30
+ logger.info(`Target: ${identifier.workspace}/${identifier.repository}`);
28
31
  try {
29
32
  // Step 1: Find and get PR information
30
33
  const pr = await this.findAndGetPR(identifier, cacheHits, options.forceRefresh);
31
34
  const completeIdentifier = {
32
35
  ...identifier,
33
- pullRequestId: pr.id
36
+ pullRequestId: pr.id,
34
37
  };
35
38
  // Step 2: Gather project context (memory bank + clinerules)
36
39
  const projectContext = await this.gatherProjectContext(completeIdentifier, cacheHits, options.forceRefresh);
37
40
  // Step 3: Determine diff strategy based on file count and config
38
41
  const diffStrategy = this.determineDiffStrategy(pr.fileChanges || [], options.diffStrategyConfig);
39
- Logger_1.logger.info(`Diff strategy: ${diffStrategy.strategy} (${diffStrategy.reason})`);
42
+ logger.info(`Diff strategy: ${diffStrategy.strategy} (${diffStrategy.reason})`);
40
43
  // Step 4: Get diff data based on strategy (if requested)
41
44
  let prDiff;
42
45
  let fileDiffs;
43
46
  if (options.includeDiff !== false) {
44
- if (diffStrategy.strategy === 'whole') {
45
- prDiff = await this.getPRDiff(completeIdentifier, options.contextLines || 3, options.excludePatterns || ['*.lock', '*.svg'], cacheHits, options.forceRefresh);
47
+ if (diffStrategy.strategy === "whole") {
48
+ prDiff = await this.getPRDiff(completeIdentifier, options.contextLines || 3, options.excludePatterns || ["*.lock", "*.svg"], cacheHits, options.forceRefresh);
46
49
  }
47
50
  else {
48
- fileDiffs = await this.getFileByFileDiffs(completeIdentifier, pr.fileChanges || [], options.contextLines || 3, options.excludePatterns || ['*.lock', '*.svg'], cacheHits, options.forceRefresh);
51
+ fileDiffs = await this.getFileByFileDiffs(completeIdentifier, pr.fileChanges || [], options.contextLines || 3, options.excludePatterns || ["*.lock", "*.svg"], cacheHits, options.forceRefresh);
49
52
  }
50
53
  }
51
54
  const gatheringDuration = Date.now() - this.startTime;
@@ -59,108 +62,99 @@ class ContextGatherer {
59
62
  contextId,
60
63
  gatheredAt: new Date().toISOString(),
61
64
  cacheHits,
62
- gatheringDuration
65
+ gatheringDuration,
63
66
  };
64
- Logger_1.logger.success(`Context gathered in ${Math.round(gatheringDuration / 1000)}s ` +
67
+ logger.success(`Context gathered in ${Math.round(gatheringDuration / 1000)}s ` +
65
68
  `(${cacheHits.length} cache hits, ${diffStrategy.fileCount} files, ${diffStrategy.estimatedSize})`);
66
69
  // Cache the complete context for reuse
67
70
  this.cacheContext(context);
68
71
  return context;
69
72
  }
70
73
  catch (error) {
71
- Logger_1.logger.error(`Context gathering failed: ${error.message}`);
72
- throw new types_1.ProviderError(`Failed to gather context: ${error.message}`);
74
+ logger.error(`Context gathering failed: ${error.message}`);
75
+ throw new ProviderError(`Failed to gather context: ${error.message}`);
73
76
  }
74
77
  }
75
78
  /**
76
79
  * Step 1: Find PR and get detailed information
77
80
  */
78
81
  async findAndGetPR(identifier, cacheHits, forceRefresh = false) {
79
- Logger_1.logger.debug('Step 1: Finding and getting PR information...');
82
+ logger.debug("Step 1: Finding and getting PR information...");
80
83
  // If PR ID is provided, get details directly
81
84
  if (identifier.pullRequestId) {
82
- const cacheKey = Cache_1.Cache.keys.prInfo(identifier.workspace, identifier.repository, identifier.pullRequestId);
83
- if (!forceRefresh && Cache_1.cache.has(cacheKey)) {
84
- cacheHits.push('pr-details');
85
+ const cacheKey = Cache.keys.prInfo(identifier.workspace, identifier.repository, identifier.pullRequestId);
86
+ if (!forceRefresh && cache.has(cacheKey)) {
87
+ cacheHits.push("pr-details");
85
88
  }
86
- return Cache_1.cache.getOrSet(cacheKey, async () => {
87
- Logger_1.logger.debug(`Getting PR details: ${identifier.workspace}/${identifier.repository}#${identifier.pullRequestId}`);
89
+ return cache.getOrSet(cacheKey, async () => {
90
+ logger.debug(`Getting PR details: ${identifier.workspace}/${identifier.repository}#${identifier.pullRequestId}`);
88
91
  return await this.bitbucketProvider.getPRDetails(identifier);
89
- }, 1800 // 30 minutes
90
- );
92
+ }, 1800);
91
93
  }
92
94
  // If branch is provided, find PR first
93
95
  if (identifier.branch) {
94
- const branchCacheKey = Cache_1.Cache.keys.branchInfo(identifier.workspace, identifier.repository, identifier.branch);
95
- if (!forceRefresh && Cache_1.cache.has(branchCacheKey)) {
96
- cacheHits.push('branch-pr-lookup');
96
+ const branchCacheKey = Cache.keys.branchInfo(identifier.workspace, identifier.repository, identifier.branch);
97
+ if (!forceRefresh && cache.has(branchCacheKey)) {
98
+ cacheHits.push("branch-pr-lookup");
97
99
  }
98
- const prInfo = await Cache_1.cache.getOrSet(branchCacheKey, async () => {
99
- Logger_1.logger.debug(`Finding PR for branch: ${identifier.workspace}/${identifier.repository}@${identifier.branch}`);
100
+ const prInfo = await cache.getOrSet(branchCacheKey, async () => {
101
+ logger.debug(`Finding PR for branch: ${identifier.workspace}/${identifier.repository}@${identifier.branch}`);
100
102
  return await this.bitbucketProvider.findPRForBranch(identifier);
101
- }, 3600 // 1 hour
102
- );
103
+ }, 3600);
103
104
  // Now get full PR details
104
- const detailsCacheKey = Cache_1.Cache.keys.prInfo(identifier.workspace, identifier.repository, prInfo.id);
105
- if (!forceRefresh && Cache_1.cache.has(detailsCacheKey)) {
106
- cacheHits.push('pr-details-from-branch');
105
+ const detailsCacheKey = Cache.keys.prInfo(identifier.workspace, identifier.repository, prInfo.id);
106
+ if (!forceRefresh && cache.has(detailsCacheKey)) {
107
+ cacheHits.push("pr-details-from-branch");
107
108
  }
108
- return Cache_1.cache.getOrSet(detailsCacheKey, async () => {
109
+ return cache.getOrSet(detailsCacheKey, async () => {
109
110
  return await this.bitbucketProvider.getPRDetails({
110
111
  ...identifier,
111
- pullRequestId: prInfo.id
112
+ pullRequestId: prInfo.id,
112
113
  });
113
- }, 1800 // 30 minutes
114
- );
114
+ }, 1800);
115
115
  }
116
- throw new types_1.ProviderError('Either pullRequestId or branch must be provided');
116
+ throw new ProviderError("Either pullRequestId or branch must be provided");
117
117
  }
118
118
  /**
119
119
  * Step 2: Gather project context (memory bank + clinerules)
120
120
  */
121
121
  async gatherProjectContext(identifier, cacheHits, forceRefresh = false) {
122
- Logger_1.logger.debug('Step 2: Gathering project context...');
123
- const cacheKey = Cache_1.Cache.keys.projectContext(identifier.workspace, identifier.repository, identifier.branch || 'main');
124
- if (!forceRefresh && Cache_1.cache.has(cacheKey)) {
125
- cacheHits.push('project-context');
122
+ logger.debug("Step 2: Gathering project context...");
123
+ const cacheKey = Cache.keys.projectContext(identifier.workspace, identifier.repository, identifier.branch || "main");
124
+ if (!forceRefresh && cache.has(cacheKey)) {
125
+ cacheHits.push("project-context");
126
126
  }
127
- return Cache_1.cache.getOrSet(cacheKey, async () => {
127
+ return cache.getOrSet(cacheKey, async () => {
128
128
  try {
129
- // Get memory-bank directory listing
130
- const memoryBankFiles = await this.bitbucketProvider.listDirectoryContent(identifier.workspace, identifier.repository, 'memory-bank', identifier.branch || 'main');
131
- if (!memoryBankFiles.length) {
132
- Logger_1.logger.debug('No memory-bank directory found');
129
+ // Use MemoryBankManager to get memory bank files
130
+ const memoryBankResult = await this.memoryBankManager.getMemoryBankFiles(identifier, forceRefresh);
131
+ if (memoryBankResult.files.length === 0) {
132
+ logger.debug("No memory bank files found");
133
133
  return {
134
134
  memoryBank: {
135
- summary: 'No project context available',
136
- projectContext: 'None',
137
- patterns: 'None',
138
- standards: 'None'
135
+ summary: "No project context available",
136
+ projectContext: "None",
137
+ patterns: "None",
138
+ standards: "None",
139
139
  },
140
- clinerules: '',
141
- filesProcessed: 0
140
+ clinerules: "",
141
+ filesProcessed: 0,
142
142
  };
143
143
  }
144
- // Get content of each memory bank file
144
+ // Convert MemoryBankFile[] to Record<string, string> for AI processing
145
145
  const fileContents = {};
146
- const files = memoryBankFiles.filter(f => f.type === 'file');
147
- for (const file of files) {
148
- try {
149
- fileContents[file.name] = await this.bitbucketProvider.getFileContent(identifier.workspace, identifier.repository, `memory-bank/${file.name}`, identifier.branch || 'main');
150
- Logger_1.logger.debug(`✓ Got content for: ${file.name}`);
151
- }
152
- catch (error) {
153
- Logger_1.logger.debug(`Could not read file ${file.name}: ${error.message}`);
154
- }
155
- }
146
+ memoryBankResult.files.forEach((file) => {
147
+ fileContents[file.name] = file.content;
148
+ });
149
+ logger.debug(`✓ Loaded ${memoryBankResult.files.length} memory bank files from ${memoryBankResult.resolvedPath}${memoryBankResult.fallbackUsed ? " (fallback)" : ""}`);
156
150
  // Get .clinerules file
157
- let clinerules = '';
151
+ let clinerules = "";
158
152
  try {
159
- clinerules = await this.bitbucketProvider.getFileContent(identifier.workspace, identifier.repository, '.clinerules', identifier.branch || 'main');
160
- Logger_1.logger.debug('✓ Got .clinerules content');
153
+ clinerules = await this.bitbucketProvider.getFileContent(identifier.workspace, identifier.repository, ".clinerules", identifier.branch || "main");
154
+ logger.debug("✓ Got .clinerules content");
161
155
  }
162
156
  catch (error) {
163
- Logger_1.logger.debug(`Could not read .clinerules: ${error.message}`);
157
+ logger.debug(`Could not read .clinerules: ${error.message}`);
164
158
  }
165
159
  // Parse and summarize with AI
166
160
  const contextData = await this.parseProjectContextWithAI(fileContents, clinerules);
@@ -171,27 +165,43 @@ Patterns: ${contextData.patterns}
171
165
  Standards: ${contextData.standards}`,
172
166
  projectContext: contextData.projectContext,
173
167
  patterns: contextData.patterns,
174
- standards: contextData.standards
168
+ standards: contextData.standards,
175
169
  },
176
170
  clinerules,
177
- filesProcessed: Object.keys(fileContents).length
171
+ filesProcessed: memoryBankResult.filesProcessed,
178
172
  };
179
173
  }
180
174
  catch (error) {
181
- Logger_1.logger.debug(`Failed to gather project context: ${error.message}`);
175
+ logger.debug(`Failed to gather project context: ${error.message}`);
182
176
  return {
183
177
  memoryBank: {
184
- summary: 'Context gathering failed',
185
- projectContext: 'Failed to load',
186
- patterns: 'Failed to load',
187
- standards: 'Failed to load'
178
+ summary: "Context gathering failed",
179
+ projectContext: "Failed to load",
180
+ patterns: "Failed to load",
181
+ standards: "Failed to load",
188
182
  },
189
- clinerules: '',
190
- filesProcessed: 0
183
+ clinerules: "",
184
+ filesProcessed: 0,
191
185
  };
192
186
  }
193
- }, 7200 // 2 hours - project context changes less frequently
194
- );
187
+ }, 7200);
188
+ }
189
+ /**
190
+ * Get safe token limit based on AI provider using shared utility
191
+ */
192
+ getSafeTokenLimit() {
193
+ const provider = this.aiConfig.provider || "auto";
194
+ const configuredTokens = this.aiConfig.maxTokens;
195
+ // Use conservative limits for ContextGatherer (safer for large context processing)
196
+ const providerLimit = getProviderTokenLimit(provider, true);
197
+ // Use the smaller of configured tokens or provider limit
198
+ if (configuredTokens && configuredTokens > 0) {
199
+ const safeLimit = Math.min(configuredTokens, providerLimit);
200
+ logger.debug(`Token limit: configured=${configuredTokens}, provider=${providerLimit}, using=${safeLimit}`);
201
+ return safeLimit;
202
+ }
203
+ logger.debug(`Token limit: using provider default=${providerLimit} for ${provider}`);
204
+ return providerLimit;
195
205
  }
196
206
  /**
197
207
  * Parse project context with AI
@@ -213,50 +223,54 @@ Extract and summarize the content and return ONLY this JSON format:
213
223
  try {
214
224
  // Initialize NeuroLink with eval-based dynamic import
215
225
  if (!this.neurolink) {
216
- const dynamicImport = eval('(specifier) => import(specifier)');
217
- const { NeuroLink } = await dynamicImport('@juspay/neurolink');
226
+ const { NeuroLink } = await import("@juspay/neurolink");
218
227
  this.neurolink = new NeuroLink();
219
228
  }
220
229
  // Context for project analysis
221
230
  const aiContext = {
222
- operation: 'project-context-analysis',
231
+ operation: "project-context-analysis",
223
232
  fileCount: Object.keys(fileContents).length,
224
233
  hasClinerules: !!clinerules,
225
- analysisType: 'memory-bank-synthesis'
234
+ analysisType: "memory-bank-synthesis",
226
235
  };
236
+ // Get safe token limit based on provider
237
+ const safeMaxTokens = this.getSafeTokenLimit();
238
+ logger.debug(`Using AI provider: ${this.aiConfig.provider || "auto"}`);
239
+ logger.debug(`Configured maxTokens: ${this.aiConfig.maxTokens}`);
240
+ logger.debug(`Safe maxTokens limit: ${safeMaxTokens}`);
227
241
  const result = await this.neurolink.generate({
228
242
  input: { text: prompt },
229
- systemPrompt: 'You are an Expert Project Analyst. Synthesize project context from documentation and configuration files to help AI understand the codebase architecture, patterns, and business domain.',
243
+ systemPrompt: "You are an Expert Project Analyst. Synthesize project context from documentation and configuration files to help AI understand the codebase architecture, patterns, and business domain.",
230
244
  provider: this.aiConfig.provider,
231
245
  model: this.aiConfig.model,
232
246
  temperature: 0.3,
233
- maxTokens: Math.max(this.aiConfig.maxTokens || 0, 500000), // Quality first - always use higher limit
234
- timeout: '10m', // Allow longer processing for quality
247
+ maxTokens: safeMaxTokens, // Use provider-aware safe token limit
248
+ timeout: "10m", // Allow longer processing for quality
235
249
  context: aiContext,
236
250
  enableAnalytics: this.aiConfig.enableAnalytics || true,
237
- enableEvaluation: false // Not needed for context synthesis
251
+ enableEvaluation: false, // Not needed for context synthesis
238
252
  });
239
253
  // Log context analysis
240
254
  if (result.analytics) {
241
- Logger_1.logger.debug(`Context Analysis - Files: ${Object.keys(fileContents).length}, Provider: ${result.provider}`);
255
+ logger.debug(`Context Analysis - Files: ${Object.keys(fileContents).length}, Provider: ${result.provider}`);
242
256
  }
243
257
  // Modern NeuroLink returns { content: string }
244
258
  const response = this.parseAIResponse(result);
245
259
  if (response.success) {
246
260
  return {
247
- projectContext: response.projectContext || 'None',
248
- patterns: response.patterns || 'None',
249
- standards: response.standards || 'None'
261
+ projectContext: response.projectContext || "None",
262
+ patterns: response.patterns || "None",
263
+ standards: response.standards || "None",
250
264
  };
251
265
  }
252
- throw new Error('AI parsing failed');
266
+ throw new Error("AI parsing failed");
253
267
  }
254
268
  catch (error) {
255
- Logger_1.logger.warn(`AI context parsing failed, using fallback: ${error.message}`);
269
+ logger.warn(`AI context parsing failed, using fallback: ${error.message}`);
256
270
  return {
257
- projectContext: 'AI parsing unavailable',
258
- patterns: 'Standard patterns assumed',
259
- standards: 'Standard quality requirements'
271
+ projectContext: "AI parsing unavailable",
272
+ patterns: "Standard patterns assumed",
273
+ standards: "Standard quality requirements",
260
274
  };
261
275
  }
262
276
  }
@@ -267,94 +281,96 @@ Extract and summarize the content and return ONLY this JSON format:
267
281
  const fileCount = fileChanges.length;
268
282
  // Get threshold values from config or use defaults
269
283
  const wholeDiffMaxFiles = config?.thresholds?.wholeDiffMaxFiles ?? 2;
270
- // Note: fileByFileMinFiles is currently same as wholeDiffMaxFiles + 1
284
+ // Note: fileByFileMinFiles is currently same as wholeDiffMaxFiles + 1
271
285
  // but kept separate for future flexibility
272
286
  // Check if force strategy is configured
273
- if (config?.forceStrategy && config.forceStrategy !== 'auto') {
287
+ if (config?.forceStrategy && config.forceStrategy !== "auto") {
274
288
  return {
275
289
  strategy: config.forceStrategy,
276
290
  reason: `Forced by configuration`,
277
291
  fileCount,
278
- estimatedSize: this.estimateDiffSize(fileCount)
292
+ estimatedSize: this.estimateDiffSize(fileCount),
279
293
  };
280
294
  }
281
295
  // Determine strategy based on thresholds
282
- let strategy = 'whole';
283
- let reason = '';
296
+ let strategy = "whole";
297
+ let reason = "";
284
298
  if (fileCount === 0) {
285
- strategy = 'whole';
286
- reason = 'No files to analyze';
299
+ strategy = "whole";
300
+ reason = "No files to analyze";
287
301
  }
288
302
  else if (fileCount <= wholeDiffMaxFiles) {
289
- strategy = 'whole';
303
+ strategy = "whole";
290
304
  reason = `${fileCount} file(s) ≤ ${wholeDiffMaxFiles} (threshold), using whole diff`;
291
305
  }
292
306
  else {
293
- strategy = 'file-by-file';
307
+ strategy = "file-by-file";
294
308
  reason = `${fileCount} file(s) > ${wholeDiffMaxFiles} (threshold), using file-by-file`;
295
309
  }
296
310
  return {
297
311
  strategy,
298
312
  reason,
299
313
  fileCount,
300
- estimatedSize: this.estimateDiffSize(fileCount)
314
+ estimatedSize: this.estimateDiffSize(fileCount),
301
315
  };
302
316
  }
303
317
  /**
304
318
  * Estimate diff size based on file count
305
319
  */
306
320
  estimateDiffSize(fileCount) {
307
- if (fileCount === 0)
308
- return '0 KB';
309
- if (fileCount <= 2)
310
- return 'Small (~5-20 KB)';
311
- if (fileCount <= 5)
312
- return 'Small (~10-50 KB)';
313
- if (fileCount <= 20)
314
- return 'Medium (~50-200 KB)';
315
- if (fileCount <= 50)
316
- return 'Large (~200-500 KB)';
317
- return 'Very Large (>500 KB)';
321
+ if (fileCount === 0) {
322
+ return "0 KB";
323
+ }
324
+ if (fileCount <= 2) {
325
+ return "Small (~5-20 KB)";
326
+ }
327
+ if (fileCount <= 5) {
328
+ return "Small (~10-50 KB)";
329
+ }
330
+ if (fileCount <= 20) {
331
+ return "Medium (~50-200 KB)";
332
+ }
333
+ if (fileCount <= 50) {
334
+ return "Large (~200-500 KB)";
335
+ }
336
+ return "Very Large (>500 KB)";
318
337
  }
319
338
  /**
320
339
  * Get whole PR diff
321
340
  */
322
341
  async getPRDiff(identifier, contextLines, excludePatterns, cacheHits, forceRefresh = false) {
323
- Logger_1.logger.debug('Getting whole PR diff...');
324
- const cacheKey = Cache_1.Cache.keys.prDiff(identifier.workspace, identifier.repository, identifier.pullRequestId);
325
- if (!forceRefresh && Cache_1.cache.has(cacheKey)) {
326
- cacheHits.push('pr-diff');
342
+ logger.debug("Getting whole PR diff...");
343
+ const cacheKey = Cache.keys.prDiff(identifier.workspace, identifier.repository, identifier.pullRequestId);
344
+ if (!forceRefresh && cache.has(cacheKey)) {
345
+ cacheHits.push("pr-diff");
327
346
  }
328
- return Cache_1.cache.getOrSet(cacheKey, async () => {
347
+ return cache.getOrSet(cacheKey, async () => {
329
348
  return await this.bitbucketProvider.getPRDiff(identifier, contextLines, excludePatterns);
330
- }, 1800 // 30 minutes
331
- );
349
+ }, 1800);
332
350
  }
333
351
  /**
334
352
  * Get file-by-file diffs for large changesets
335
353
  */
336
354
  async getFileByFileDiffs(identifier, fileChanges, contextLines, excludePatterns, cacheHits, forceRefresh = false) {
337
- Logger_1.logger.debug(`Getting file-by-file diffs for ${fileChanges.length} files...`);
355
+ logger.debug(`Getting file-by-file diffs for ${fileChanges.length} files...`);
338
356
  const fileDiffs = new Map();
339
357
  // Filter out excluded files
340
- const filteredFiles = fileChanges.filter(file => !excludePatterns.some(pattern => new RegExp(pattern.replace(/\*/g, '.*')).test(file)));
341
- Logger_1.logger.debug(`Processing ${filteredFiles.length} files after exclusions`);
358
+ const filteredFiles = fileChanges.filter((file) => !excludePatterns.some((pattern) => new RegExp(pattern.replace(/\*/g, ".*")).test(file)));
359
+ logger.debug(`Processing ${filteredFiles.length} files after exclusions`);
342
360
  // Process files in batches for better performance
343
361
  const batchSize = 5;
344
362
  for (let i = 0; i < filteredFiles.length; i += batchSize) {
345
363
  const batch = filteredFiles.slice(i, i + batchSize);
346
364
  const batchPromises = batch.map(async (file) => {
347
365
  const fileCacheKey = `file-diff:${identifier.workspace}:${identifier.repository}:${identifier.pullRequestId}:${file}`;
348
- if (!forceRefresh && Cache_1.cache.has(fileCacheKey)) {
366
+ if (!forceRefresh && cache.has(fileCacheKey)) {
349
367
  cacheHits.push(`file-diff-${file}`);
350
368
  }
351
- return Cache_1.cache.getOrSet(fileCacheKey, async () => {
369
+ return cache.getOrSet(fileCacheKey, async () => {
352
370
  // Use include_patterns to get diff for just this file
353
- const fileDiff = await this.bitbucketProvider.getPRDiff(identifier, contextLines, excludePatterns, [file] // Include patterns with single file
354
- );
371
+ const fileDiff = await this.bitbucketProvider.getPRDiff(identifier, contextLines, excludePatterns, [file]);
355
372
  return fileDiff.diff;
356
- }, 1800 // 30 minutes
357
- );
373
+ }, 1800);
358
374
  });
359
375
  const batchResults = await Promise.all(batchPromises);
360
376
  batch.forEach((file, index) => {
@@ -362,10 +378,10 @@ Extract and summarize the content and return ONLY this JSON format:
362
378
  });
363
379
  // Small delay between batches to avoid overwhelming the API
364
380
  if (i + batchSize < filteredFiles.length) {
365
- await new Promise(resolve => setTimeout(resolve, 500));
381
+ await new Promise((resolve) => setTimeout(resolve, 500));
366
382
  }
367
383
  }
368
- Logger_1.logger.debug(`✓ Got diffs for ${fileDiffs.size} files`);
384
+ logger.debug(`✓ Got diffs for ${fileDiffs.size} files`);
369
385
  return fileDiffs;
370
386
  }
371
387
  /**
@@ -373,12 +389,12 @@ Extract and summarize the content and return ONLY this JSON format:
373
389
  */
374
390
  cacheContext(context) {
375
391
  const contextCacheKey = `context:${context.contextId}`;
376
- Cache_1.cache.set(contextCacheKey, context, 1800); // 30 minutes
392
+ cache.set(contextCacheKey, context, 1800); // 30 minutes
377
393
  // Tag it for easy invalidation
378
- Cache_1.cache.setWithTags(contextCacheKey, context, [
394
+ cache.setWithTags(contextCacheKey, context, [
379
395
  `workspace:${context.identifier.workspace}`,
380
396
  `repository:${context.identifier.repository}`,
381
- `pr:${context.identifier.pullRequestId}`
397
+ `pr:${context.identifier.pullRequestId}`,
382
398
  ], 1800);
383
399
  }
384
400
  /**
@@ -387,9 +403,9 @@ Extract and summarize the content and return ONLY this JSON format:
387
403
  async getCachedContext(identifier) {
388
404
  const contextId = this.generateContextId(identifier);
389
405
  const contextCacheKey = `context:${contextId}`;
390
- const cached = Cache_1.cache.get(contextCacheKey);
406
+ const cached = cache.get(contextCacheKey);
391
407
  if (cached) {
392
- Logger_1.logger.debug(`✓ Using cached context: ${contextId}`);
408
+ logger.debug(`✓ Using cached context: ${contextId}`);
393
409
  return cached;
394
410
  }
395
411
  return null;
@@ -398,9 +414,9 @@ Extract and summarize the content and return ONLY this JSON format:
398
414
  * Invalidate context cache for a specific PR
399
415
  */
400
416
  invalidateContext(identifier) {
401
- Cache_1.cache.invalidateTag(`pr:${identifier.pullRequestId}`);
402
- Cache_1.cache.invalidateTag(`workspace:${identifier.workspace}`);
403
- Logger_1.logger.debug(`Context cache invalidated for PR ${identifier.pullRequestId}`);
417
+ cache.invalidateTag(`pr:${identifier.pullRequestId}`);
418
+ cache.invalidateTag(`workspace:${identifier.workspace}`);
419
+ logger.debug(`Context cache invalidated for PR ${identifier.pullRequestId}`);
404
420
  }
405
421
  /**
406
422
  * Generate unique context ID
@@ -409,11 +425,11 @@ Extract and summarize the content and return ONLY this JSON format:
409
425
  const parts = [
410
426
  identifier.workspace,
411
427
  identifier.repository,
412
- identifier.pullRequestId || identifier.branch || 'unknown'
428
+ identifier.pullRequestId || identifier.branch || "unknown",
413
429
  ];
414
- return Buffer.from(parts.join(':'))
415
- .toString('base64')
416
- .replace(/[+/=]/g, '')
430
+ return Buffer.from(parts.join(":"))
431
+ .toString("base64")
432
+ .replace(/[+/=]/g, "")
417
433
  .substring(0, 16);
418
434
  }
419
435
  /**
@@ -421,16 +437,16 @@ Extract and summarize the content and return ONLY this JSON format:
421
437
  */
422
438
  parseAIResponse(result) {
423
439
  try {
424
- const responseText = result.content || result.text || result.response || '';
440
+ const responseText = result.content || result.text || result.response || "";
425
441
  if (!responseText) {
426
- return { success: false, error: 'Empty response' };
442
+ return { success: false, error: "Empty response" };
427
443
  }
428
444
  // Find JSON in response
429
445
  const jsonMatch = responseText.match(/\{[\s\S]*\}/);
430
446
  if (jsonMatch) {
431
447
  return JSON.parse(jsonMatch[0]);
432
448
  }
433
- return { success: false, error: 'No JSON found' };
449
+ return { success: false, error: "No JSON found" };
434
450
  }
435
451
  catch (error) {
436
452
  return { success: false, error: error.message };
@@ -442,14 +458,13 @@ Extract and summarize the content and return ONLY this JSON format:
442
458
  getStats() {
443
459
  return {
444
460
  lastGatheringDuration: this.startTime ? Date.now() - this.startTime : 0,
445
- cacheStats: Cache_1.cache.stats(),
446
- cacheHitRatio: Cache_1.cache.getHitRatio()
461
+ cacheStats: cache.stats(),
462
+ cacheHitRatio: cache.getHitRatio(),
447
463
  };
448
464
  }
449
465
  }
450
- exports.ContextGatherer = ContextGatherer;
451
466
  // Export factory function
452
- function createContextGatherer(bitbucketProvider, aiConfig) {
453
- return new ContextGatherer(bitbucketProvider, aiConfig);
467
+ export function createContextGatherer(bitbucketProvider, aiConfig, memoryBankConfig) {
468
+ return new ContextGatherer(bitbucketProvider, aiConfig, memoryBankConfig);
454
469
  }
455
470
  //# sourceMappingURL=ContextGatherer.js.map