@juspay/yama 1.1.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.
@@ -0,0 +1,457 @@
1
+ "use strict";
2
+ /**
3
+ * Yama - Unified orchestrator class
4
+ * The main class that coordinates all operations using shared context
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.guardian = exports.Guardian = void 0;
8
+ exports.createGuardian = createGuardian;
9
+ const types_1 = require("../types");
10
+ const BitbucketProvider_1 = require("./providers/BitbucketProvider");
11
+ const ContextGatherer_1 = require("./ContextGatherer");
12
+ const CodeReviewer_1 = require("../features/CodeReviewer");
13
+ const DescriptionEnhancer_1 = require("../features/DescriptionEnhancer");
14
+ const Logger_1 = require("../utils/Logger");
15
+ const ConfigManager_1 = require("../utils/ConfigManager");
16
+ const Cache_1 = require("../utils/Cache");
17
+ class Guardian {
18
+ constructor(config) {
19
+ this.initialized = false;
20
+ this.config = {};
21
+ if (config) {
22
+ this.config = { ...this.config, ...config };
23
+ }
24
+ }
25
+ /**
26
+ * Initialize Guardian with configuration
27
+ */
28
+ async initialize(configPath) {
29
+ if (this.initialized) {
30
+ return;
31
+ }
32
+ try {
33
+ Logger_1.logger.badge();
34
+ Logger_1.logger.phase('🚀 Initializing Yama...');
35
+ // Load configuration
36
+ this.config = await ConfigManager_1.configManager.loadConfig(configPath);
37
+ // Initialize providers
38
+ this.bitbucketProvider = new BitbucketProvider_1.BitbucketProvider(this.config.providers.git.credentials);
39
+ await this.bitbucketProvider.initialize();
40
+ // Initialize NeuroLink with eval-based dynamic import to bypass TypeScript compilation
41
+ const dynamicImport = eval('(specifier) => import(specifier)');
42
+ const { NeuroLink } = await dynamicImport('@juspay/neurolink');
43
+ this.neurolink = new NeuroLink();
44
+ // Initialize core components
45
+ this.contextGatherer = new ContextGatherer_1.ContextGatherer(this.bitbucketProvider, this.config.providers.ai);
46
+ this.codeReviewer = new CodeReviewer_1.CodeReviewer(this.bitbucketProvider, this.config.providers.ai, this.config.features.codeReview);
47
+ this.descriptionEnhancer = new DescriptionEnhancer_1.DescriptionEnhancer(this.bitbucketProvider, this.config.providers.ai);
48
+ this.initialized = true;
49
+ Logger_1.logger.success('✅ Yama initialized successfully');
50
+ }
51
+ catch (error) {
52
+ Logger_1.logger.error(`Failed to initialize Yama: ${error.message}`);
53
+ throw new types_1.GuardianError('INITIALIZATION_ERROR', `Initialization failed: ${error.message}`);
54
+ }
55
+ }
56
+ /**
57
+ * Main method: Process PR with multiple operations using unified context
58
+ */
59
+ async processPR(options) {
60
+ await this.ensureInitialized();
61
+ const startTime = Date.now();
62
+ const operations = [];
63
+ try {
64
+ Logger_1.logger.operation('PR Processing', 'started');
65
+ Logger_1.logger.info(`Target: ${options.workspace}/${options.repository}`);
66
+ Logger_1.logger.info(`Operations: ${options.operations.join(', ')}`);
67
+ Logger_1.logger.info(`Mode: ${options.dryRun ? 'DRY RUN' : 'LIVE'}`);
68
+ // Step 1: Gather unified context ONCE for all operations
69
+ Logger_1.logger.phase('📋 Gathering unified context...');
70
+ const context = await this.gatherUnifiedContext(options);
71
+ Logger_1.logger.success(`Context ready: PR #${context.pr.id} - "${context.pr.title}"`);
72
+ Logger_1.logger.info(`Files: ${context.diffStrategy.fileCount}, Strategy: ${context.diffStrategy.strategy}`);
73
+ // Step 2: Execute requested operations using shared context
74
+ for (const operation of options.operations) {
75
+ if (operation === 'all') {
76
+ // Execute all available operations
77
+ operations.push(await this.executeOperation('review', context, options));
78
+ operations.push(await this.executeOperation('enhance-description', context, options));
79
+ }
80
+ else {
81
+ operations.push(await this.executeOperation(operation, context, options));
82
+ }
83
+ }
84
+ const duration = Date.now() - startTime;
85
+ const successCount = operations.filter(op => op.status === 'success').length;
86
+ const errorCount = operations.filter(op => op.status === 'error').length;
87
+ const skippedCount = operations.filter(op => op.status === 'skipped').length;
88
+ const result = {
89
+ pullRequest: context.pr,
90
+ operations,
91
+ summary: {
92
+ totalOperations: operations.length,
93
+ successCount,
94
+ errorCount,
95
+ skippedCount,
96
+ totalDuration: duration
97
+ }
98
+ };
99
+ Logger_1.logger.operation('PR Processing', 'completed');
100
+ Logger_1.logger.success(`✅ Processing completed in ${Math.round(duration / 1000)}s: ` +
101
+ `${successCount} success, ${errorCount} errors, ${skippedCount} skipped`);
102
+ return result;
103
+ }
104
+ catch (error) {
105
+ Logger_1.logger.operation('PR Processing', 'failed');
106
+ Logger_1.logger.error(`Processing failed: ${error.message}`);
107
+ throw error;
108
+ }
109
+ }
110
+ /**
111
+ * Streaming version of processPR for real-time updates
112
+ */
113
+ async *processPRStream(options, _streamOptions) {
114
+ await this.ensureInitialized();
115
+ const startTime = Date.now();
116
+ try {
117
+ // Initial update
118
+ yield {
119
+ operation: 'all',
120
+ status: 'started',
121
+ message: 'Yama processing initiated',
122
+ timestamp: new Date().toISOString()
123
+ };
124
+ // Context gathering phase
125
+ yield {
126
+ operation: 'all',
127
+ status: 'progress',
128
+ progress: 10,
129
+ message: 'Gathering unified context...',
130
+ timestamp: new Date().toISOString()
131
+ };
132
+ const context = await this.gatherUnifiedContext(options);
133
+ yield {
134
+ operation: 'all',
135
+ status: 'progress',
136
+ progress: 30,
137
+ message: `Context ready: PR #${context.pr.id}`,
138
+ data: { prId: context.pr.id, title: context.pr.title },
139
+ timestamp: new Date().toISOString()
140
+ };
141
+ // Execute operations with progress updates
142
+ const totalOps = options.operations.length;
143
+ let completedOps = 0;
144
+ for (const operation of options.operations) {
145
+ yield {
146
+ operation,
147
+ status: 'started',
148
+ message: `Starting ${operation}...`,
149
+ timestamp: new Date().toISOString()
150
+ };
151
+ try {
152
+ const result = await this.executeOperation(operation, context, options);
153
+ if (result.status === 'error') {
154
+ yield {
155
+ operation,
156
+ status: 'error',
157
+ message: `${operation} failed: ${result.error}`,
158
+ timestamp: new Date().toISOString()
159
+ };
160
+ }
161
+ else {
162
+ completedOps++;
163
+ yield {
164
+ operation,
165
+ status: 'completed',
166
+ progress: 30 + Math.round((completedOps / totalOps) * 60),
167
+ message: `${operation} completed`,
168
+ data: result,
169
+ timestamp: new Date().toISOString()
170
+ };
171
+ }
172
+ }
173
+ catch (error) {
174
+ // This catch is for unexpected errors that bypass executeOperation's own error handling
175
+ yield {
176
+ operation,
177
+ status: 'error',
178
+ message: `${operation} failed: ${error.message}`,
179
+ timestamp: new Date().toISOString()
180
+ };
181
+ }
182
+ }
183
+ // Final completion
184
+ const duration = Date.now() - startTime;
185
+ yield {
186
+ operation: 'all',
187
+ status: 'completed',
188
+ progress: 100,
189
+ message: `Processing completed in ${Math.round(duration / 1000)}s`,
190
+ timestamp: new Date().toISOString()
191
+ };
192
+ }
193
+ catch (error) {
194
+ yield {
195
+ operation: 'all',
196
+ status: 'error',
197
+ message: `Processing failed: ${error.message}`,
198
+ timestamp: new Date().toISOString()
199
+ };
200
+ }
201
+ }
202
+ /**
203
+ * Gather unified context (cached and reusable)
204
+ */
205
+ async gatherUnifiedContext(options) {
206
+ const identifier = {
207
+ workspace: options.workspace,
208
+ repository: options.repository,
209
+ branch: options.branch,
210
+ pullRequestId: options.pullRequestId
211
+ };
212
+ // Check if we have cached context first
213
+ const cachedContext = await this.contextGatherer.getCachedContext(identifier);
214
+ if (cachedContext && options.config?.cache?.enabled !== false) {
215
+ Logger_1.logger.debug('✓ Using cached context');
216
+ return cachedContext;
217
+ }
218
+ // Determine what operations need diff data
219
+ const needsDiff = options.operations.some(op => op === 'review' || op === 'security-scan' || op === 'all');
220
+ const contextOptions = {
221
+ excludePatterns: this.config.features.codeReview.excludePatterns,
222
+ contextLines: this.config.features.codeReview.contextLines,
223
+ forceRefresh: false,
224
+ includeDiff: needsDiff,
225
+ diffStrategyConfig: this.config.features.diffStrategy
226
+ };
227
+ return await this.contextGatherer.gatherContext(identifier, contextOptions);
228
+ }
229
+ /**
230
+ * Execute individual operation using shared context
231
+ */
232
+ async executeOperation(operation, context, options) {
233
+ const startTime = Date.now();
234
+ try {
235
+ let data;
236
+ switch (operation) {
237
+ case 'review':
238
+ data = await this.executeCodeReview(context, options);
239
+ break;
240
+ case 'enhance-description':
241
+ data = await this.executeDescriptionEnhancement(context, options);
242
+ break;
243
+ case 'security-scan':
244
+ // TODO: Implement in future phases
245
+ throw new Error('Security scan not implemented in Phase 1');
246
+ case 'analytics':
247
+ // TODO: Implement in future phases
248
+ throw new Error('Analytics not implemented in Phase 1');
249
+ default:
250
+ throw new Error(`Unknown operation: ${operation}`);
251
+ }
252
+ return {
253
+ operation,
254
+ status: 'success',
255
+ data,
256
+ duration: Date.now() - startTime,
257
+ timestamp: new Date().toISOString()
258
+ };
259
+ }
260
+ catch (error) {
261
+ Logger_1.logger.error(`Operation ${operation} failed: ${error.message}`);
262
+ return {
263
+ operation,
264
+ status: 'error',
265
+ error: error.message,
266
+ duration: Date.now() - startTime,
267
+ timestamp: new Date().toISOString()
268
+ };
269
+ }
270
+ }
271
+ /**
272
+ * Execute code review using shared context
273
+ */
274
+ async executeCodeReview(context, options) {
275
+ if (!this.config.features.codeReview.enabled) {
276
+ Logger_1.logger.info('Code review is disabled in configuration');
277
+ return { skipped: true, reason: 'disabled in config' };
278
+ }
279
+ Logger_1.logger.phase('🔍 Executing code review...');
280
+ const reviewOptions = {
281
+ workspace: context.identifier.workspace,
282
+ repository: context.identifier.repository,
283
+ pullRequestId: context.identifier.pullRequestId,
284
+ dryRun: options.dryRun,
285
+ verbose: Logger_1.logger.getConfig().verbose,
286
+ excludePatterns: this.config.features.codeReview.excludePatterns,
287
+ contextLines: this.config.features.codeReview.contextLines
288
+ };
289
+ // Use the already gathered context instead of gathering again
290
+ return await this.codeReviewer.reviewCodeWithContext(context, reviewOptions);
291
+ }
292
+ /**
293
+ * Execute description enhancement using shared context
294
+ */
295
+ async executeDescriptionEnhancement(context, options) {
296
+ if (!this.config.features.descriptionEnhancement.enabled) {
297
+ Logger_1.logger.info('Description enhancement is disabled in configuration');
298
+ return { skipped: true, reason: 'disabled in config' };
299
+ }
300
+ Logger_1.logger.phase('📝 Executing description enhancement...');
301
+ const enhancementOptions = {
302
+ workspace: context.identifier.workspace,
303
+ repository: context.identifier.repository,
304
+ pullRequestId: context.identifier.pullRequestId,
305
+ dryRun: options.dryRun,
306
+ verbose: Logger_1.logger.getConfig().verbose,
307
+ preserveContent: this.config.features.descriptionEnhancement.preserveContent,
308
+ ensureRequiredSections: true,
309
+ customSections: this.config.features.descriptionEnhancement.requiredSections
310
+ };
311
+ // Use the already gathered context instead of gathering again
312
+ return await this.descriptionEnhancer.enhanceWithContext(context, enhancementOptions);
313
+ }
314
+ /**
315
+ * Individual operation methods for backwards compatibility
316
+ */
317
+ /**
318
+ * Code review operation (standalone)
319
+ */
320
+ async reviewCode(options) {
321
+ await this.ensureInitialized();
322
+ const identifier = {
323
+ workspace: options.workspace,
324
+ repository: options.repository,
325
+ branch: options.branch,
326
+ pullRequestId: options.pullRequestId
327
+ };
328
+ Logger_1.logger.operation('Code Review', 'started');
329
+ try {
330
+ // Gather context specifically for code review
331
+ const context = await this.contextGatherer.gatherContext(identifier, {
332
+ excludePatterns: options.excludePatterns,
333
+ contextLines: options.contextLines,
334
+ includeDiff: true
335
+ });
336
+ const result = await this.codeReviewer.reviewCodeWithContext(context, options);
337
+ Logger_1.logger.operation('Code Review', 'completed');
338
+ return result;
339
+ }
340
+ catch (error) {
341
+ Logger_1.logger.operation('Code Review', 'failed');
342
+ throw error;
343
+ }
344
+ }
345
+ /**
346
+ * Description enhancement operation (standalone)
347
+ */
348
+ async enhanceDescription(options) {
349
+ await this.ensureInitialized();
350
+ const identifier = {
351
+ workspace: options.workspace,
352
+ repository: options.repository,
353
+ branch: options.branch,
354
+ pullRequestId: options.pullRequestId
355
+ };
356
+ Logger_1.logger.operation('Description Enhancement', 'started');
357
+ try {
358
+ // Gather context specifically for description enhancement
359
+ const context = await this.contextGatherer.gatherContext(identifier, {
360
+ includeDiff: true // Description enhancement may need to see changes
361
+ });
362
+ const result = await this.descriptionEnhancer.enhanceWithContext(context, options);
363
+ Logger_1.logger.operation('Description Enhancement', 'completed');
364
+ return result;
365
+ }
366
+ catch (error) {
367
+ Logger_1.logger.operation('Description Enhancement', 'failed');
368
+ throw error;
369
+ }
370
+ }
371
+ /**
372
+ * Health check for all components
373
+ */
374
+ async healthCheck() {
375
+ const components = {};
376
+ try {
377
+ // Check Bitbucket provider
378
+ components.bitbucket = await this.bitbucketProvider.healthCheck();
379
+ // Check cache
380
+ components.cache = {
381
+ healthy: true,
382
+ stats: Cache_1.cache.stats()
383
+ };
384
+ // Check NeuroLink (if initialized)
385
+ components.neurolink = {
386
+ healthy: true,
387
+ initialized: !!this.neurolink
388
+ };
389
+ const allHealthy = Object.values(components).every((comp) => comp.healthy);
390
+ return {
391
+ healthy: allHealthy,
392
+ components
393
+ };
394
+ }
395
+ catch (error) {
396
+ return {
397
+ healthy: false,
398
+ components: {
399
+ ...components,
400
+ error: error.message
401
+ }
402
+ };
403
+ }
404
+ }
405
+ /**
406
+ * Get comprehensive statistics
407
+ */
408
+ getStats() {
409
+ return {
410
+ initialized: this.initialized,
411
+ config: {
412
+ features: Object.keys(this.config.features || {}),
413
+ cacheEnabled: this.config.cache?.enabled
414
+ },
415
+ providers: {
416
+ bitbucket: this.bitbucketProvider?.getStats(),
417
+ context: this.contextGatherer?.getStats()
418
+ },
419
+ cache: Cache_1.cache.stats()
420
+ };
421
+ }
422
+ /**
423
+ * Clear all caches
424
+ */
425
+ clearCache() {
426
+ Cache_1.cache.clear();
427
+ this.bitbucketProvider?.clearCache();
428
+ Logger_1.logger.info('All caches cleared');
429
+ }
430
+ /**
431
+ * Ensure Guardian is initialized
432
+ */
433
+ async ensureInitialized() {
434
+ if (!this.initialized) {
435
+ await this.initialize();
436
+ }
437
+ }
438
+ /**
439
+ * Shutdown Guardian gracefully
440
+ */
441
+ async shutdown() {
442
+ Logger_1.logger.info('Shutting down Yama...');
443
+ // Clear caches
444
+ this.clearCache();
445
+ // Reset state
446
+ this.initialized = false;
447
+ Logger_1.logger.success('Yama shutdown complete');
448
+ }
449
+ }
450
+ exports.Guardian = Guardian;
451
+ // Export factory function
452
+ function createGuardian(config) {
453
+ return new Guardian(config);
454
+ }
455
+ // Export default instance
456
+ exports.guardian = new Guardian();
457
+ //# sourceMappingURL=Guardian.js.map
@@ -0,0 +1,105 @@
1
+ /**
2
+ * Enhanced Bitbucket Provider - Optimized from both pr-police.js and pr-describe.js
3
+ * Provides unified, cached, and optimized Bitbucket operations
4
+ */
5
+ import { PRIdentifier, PRInfo, PRDiff, GitCredentials } from '../../types';
6
+ export interface BitbucketMCPResponse {
7
+ content?: Array<{
8
+ text?: string;
9
+ }>;
10
+ message?: string;
11
+ error?: string;
12
+ }
13
+ export declare class BitbucketProvider {
14
+ private apiClient;
15
+ private branchHandlers;
16
+ private pullRequestHandlers;
17
+ private reviewHandlers;
18
+ private fileHandlers;
19
+ private initialized;
20
+ private baseUrl;
21
+ private credentials;
22
+ constructor(credentials: GitCredentials);
23
+ /**
24
+ * Initialize MCP handlers with lazy loading and connection reuse
25
+ */
26
+ initialize(): Promise<void>;
27
+ /**
28
+ * Parse MCP response - exactly matching the working pr-police.js implementation
29
+ */
30
+ private parseMCPResponse;
31
+ /**
32
+ * Find PR for branch with intelligent caching
33
+ */
34
+ findPRForBranch(identifier: PRIdentifier): Promise<PRInfo>;
35
+ /**
36
+ * Get PR details with enhanced caching
37
+ */
38
+ getPRDetails(identifier: PRIdentifier): Promise<PRInfo>;
39
+ /**
40
+ * Get PR diff with smart caching and filtering
41
+ */
42
+ getPRDiff(identifier: PRIdentifier, contextLines?: number, excludePatterns?: string[], includePatterns?: string[]): Promise<PRDiff>;
43
+ /**
44
+ * Get file content with caching
45
+ */
46
+ getFileContent(workspace: string, repository: string, filePath: string, branch: string): Promise<string>;
47
+ /**
48
+ * List directory content with caching
49
+ */
50
+ listDirectoryContent(workspace: string, repository: string, path: string, branch: string): Promise<any[]>;
51
+ /**
52
+ * Update PR description with reviewer preservation
53
+ */
54
+ updatePRDescription(identifier: PRIdentifier, description: string): Promise<{
55
+ success: boolean;
56
+ message: string;
57
+ }>;
58
+ /**
59
+ * Add comment to PR with smart positioning
60
+ */
61
+ addComment(identifier: PRIdentifier, commentText: string, options?: {
62
+ filePath?: string;
63
+ lineNumber?: number;
64
+ lineType?: 'ADDED' | 'REMOVED' | 'CONTEXT';
65
+ codeSnippet?: string;
66
+ searchContext?: {
67
+ before: string[];
68
+ after: string[];
69
+ };
70
+ matchStrategy?: 'exact' | 'best' | 'strict';
71
+ suggestion?: string;
72
+ }): Promise<{
73
+ success: boolean;
74
+ commentId?: number;
75
+ }>;
76
+ /**
77
+ * Batch operation support for multiple API calls
78
+ */
79
+ batchOperations<T>(operations: Array<() => Promise<T>>, options?: {
80
+ maxConcurrent?: number;
81
+ delayBetween?: number;
82
+ continueOnError?: boolean;
83
+ }): Promise<Array<{
84
+ success: boolean;
85
+ data?: T;
86
+ error?: string;
87
+ }>>;
88
+ /**
89
+ * Health check for the provider
90
+ */
91
+ healthCheck(): Promise<{
92
+ healthy: boolean;
93
+ details: any;
94
+ }>;
95
+ /**
96
+ * Get provider statistics and cache metrics
97
+ */
98
+ getStats(): any;
99
+ /**
100
+ * Clear provider-related cache entries
101
+ */
102
+ clearCache(): void;
103
+ }
104
+ export declare function createBitbucketProvider(credentials: GitCredentials): BitbucketProvider;
105
+ //# sourceMappingURL=BitbucketProvider.d.ts.map