@juspay/yama 1.6.0 → 2.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.
Files changed (79) hide show
  1. package/.mcp-config.example.json +26 -0
  2. package/CHANGELOG.md +46 -0
  3. package/README.md +311 -685
  4. package/dist/cli/v2.cli.d.ts +13 -0
  5. package/dist/cli/v2.cli.js +359 -0
  6. package/dist/index.d.ts +12 -13
  7. package/dist/index.js +18 -19
  8. package/dist/v2/config/ConfigLoader.d.ts +50 -0
  9. package/dist/v2/config/ConfigLoader.js +205 -0
  10. package/dist/v2/config/DefaultConfig.d.ts +9 -0
  11. package/dist/v2/config/DefaultConfig.js +187 -0
  12. package/dist/v2/core/LearningOrchestrator.d.ts +65 -0
  13. package/dist/v2/core/LearningOrchestrator.js +499 -0
  14. package/dist/v2/core/MCPServerManager.d.ts +22 -0
  15. package/dist/v2/core/MCPServerManager.js +100 -0
  16. package/dist/v2/core/SessionManager.d.ts +72 -0
  17. package/dist/v2/core/SessionManager.js +200 -0
  18. package/dist/v2/core/YamaV2Orchestrator.d.ts +112 -0
  19. package/dist/v2/core/YamaV2Orchestrator.js +549 -0
  20. package/dist/v2/learning/FeedbackExtractor.d.ts +46 -0
  21. package/dist/v2/learning/FeedbackExtractor.js +237 -0
  22. package/dist/v2/learning/KnowledgeBaseManager.d.ts +91 -0
  23. package/dist/v2/learning/KnowledgeBaseManager.js +475 -0
  24. package/dist/v2/learning/types.d.ts +121 -0
  25. package/dist/v2/learning/types.js +15 -0
  26. package/dist/v2/prompts/EnhancementSystemPrompt.d.ts +8 -0
  27. package/dist/v2/prompts/EnhancementSystemPrompt.js +216 -0
  28. package/dist/v2/prompts/LangfusePromptManager.d.ts +48 -0
  29. package/dist/v2/prompts/LangfusePromptManager.js +144 -0
  30. package/dist/v2/prompts/LearningSystemPrompt.d.ts +11 -0
  31. package/dist/v2/prompts/LearningSystemPrompt.js +180 -0
  32. package/dist/v2/prompts/PromptBuilder.d.ts +45 -0
  33. package/dist/v2/prompts/PromptBuilder.js +257 -0
  34. package/dist/v2/prompts/ReviewSystemPrompt.d.ts +8 -0
  35. package/dist/v2/prompts/ReviewSystemPrompt.js +270 -0
  36. package/dist/v2/types/config.types.d.ts +141 -0
  37. package/dist/v2/types/config.types.js +5 -0
  38. package/dist/v2/types/mcp.types.d.ts +191 -0
  39. package/dist/v2/types/mcp.types.js +6 -0
  40. package/dist/v2/types/v2.types.d.ts +182 -0
  41. package/dist/v2/types/v2.types.js +42 -0
  42. package/dist/v2/utils/ObservabilityConfig.d.ts +22 -0
  43. package/dist/v2/utils/ObservabilityConfig.js +48 -0
  44. package/package.json +16 -10
  45. package/yama.config.example.yaml +259 -204
  46. package/dist/cli/index.d.ts +0 -12
  47. package/dist/cli/index.js +0 -538
  48. package/dist/core/ContextGatherer.d.ts +0 -110
  49. package/dist/core/ContextGatherer.js +0 -470
  50. package/dist/core/Guardian.d.ts +0 -81
  51. package/dist/core/Guardian.js +0 -480
  52. package/dist/core/providers/BitbucketProvider.d.ts +0 -105
  53. package/dist/core/providers/BitbucketProvider.js +0 -489
  54. package/dist/features/CodeReviewer.d.ts +0 -173
  55. package/dist/features/CodeReviewer.js +0 -1707
  56. package/dist/features/DescriptionEnhancer.d.ts +0 -70
  57. package/dist/features/DescriptionEnhancer.js +0 -511
  58. package/dist/features/MultiInstanceProcessor.d.ts +0 -74
  59. package/dist/features/MultiInstanceProcessor.js +0 -360
  60. package/dist/types/index.d.ts +0 -624
  61. package/dist/types/index.js +0 -104
  62. package/dist/utils/Cache.d.ts +0 -103
  63. package/dist/utils/Cache.js +0 -444
  64. package/dist/utils/ConfigManager.d.ts +0 -88
  65. package/dist/utils/ConfigManager.js +0 -602
  66. package/dist/utils/ContentSimilarityService.d.ts +0 -74
  67. package/dist/utils/ContentSimilarityService.js +0 -215
  68. package/dist/utils/ExactDuplicateRemover.d.ts +0 -77
  69. package/dist/utils/ExactDuplicateRemover.js +0 -361
  70. package/dist/utils/Logger.d.ts +0 -31
  71. package/dist/utils/Logger.js +0 -214
  72. package/dist/utils/MemoryBankManager.d.ts +0 -73
  73. package/dist/utils/MemoryBankManager.js +0 -310
  74. package/dist/utils/ParallelProcessing.d.ts +0 -140
  75. package/dist/utils/ParallelProcessing.js +0 -333
  76. package/dist/utils/ProviderLimits.d.ts +0 -58
  77. package/dist/utils/ProviderLimits.js +0 -143
  78. package/dist/utils/RetryManager.d.ts +0 -78
  79. package/dist/utils/RetryManager.js +0 -205
@@ -0,0 +1,499 @@
1
+ /**
2
+ * Learning Orchestrator
3
+ * Main entry point for the knowledge base learning feature
4
+ * Orchestrates PR comment extraction, AI analysis, and knowledge base updates
5
+ */
6
+ import { NeuroLink } from "@juspay/neurolink";
7
+ import { MCPServerManager } from "./MCPServerManager.js";
8
+ import { ConfigLoader } from "../config/ConfigLoader.js";
9
+ import { LangfusePromptManager } from "../prompts/LangfusePromptManager.js";
10
+ import { KnowledgeBaseManager } from "../learning/KnowledgeBaseManager.js";
11
+ import { buildObservabilityConfigFromEnv, validateObservabilityConfig, } from "../utils/ObservabilityConfig.js";
12
+ export class LearningOrchestrator {
13
+ neurolink;
14
+ mcpManager;
15
+ configLoader;
16
+ promptManager;
17
+ config;
18
+ initialized = false;
19
+ constructor() {
20
+ this.configLoader = new ConfigLoader();
21
+ this.mcpManager = new MCPServerManager();
22
+ this.promptManager = new LangfusePromptManager();
23
+ }
24
+ /**
25
+ * Initialize the learning orchestrator
26
+ */
27
+ async initialize(configPath) {
28
+ if (this.initialized) {
29
+ return;
30
+ }
31
+ console.log("🧠 Initializing Learning Orchestrator...\n");
32
+ try {
33
+ // Load configuration
34
+ this.config = await this.configLoader.loadConfig(configPath);
35
+ // Initialize NeuroLink
36
+ console.log(" 🔧 Initializing NeuroLink AI engine...");
37
+ this.neurolink = this.initializeNeurolink();
38
+ console.log(" ✅ NeuroLink initialized\n");
39
+ // Setup MCP servers (need Bitbucket for PR access)
40
+ await this.mcpManager.setupMCPServers(this.neurolink, this.config.mcpServers);
41
+ console.log(" ✅ MCP servers ready\n");
42
+ this.initialized = true;
43
+ console.log("✅ Learning Orchestrator initialized\n");
44
+ }
45
+ catch (error) {
46
+ console.error("\n❌ Initialization failed:", error.message);
47
+ throw error;
48
+ }
49
+ }
50
+ /**
51
+ * Extract learnings from a merged PR
52
+ * Uses fully agentic AI approach - AI decides which tools to call
53
+ */
54
+ async extractLearnings(request) {
55
+ await this.ensureInitialized();
56
+ console.log("\n" + "─".repeat(60));
57
+ console.log(`📚 Extracting learnings from PR #${request.pullRequestId}`);
58
+ console.log("─".repeat(60));
59
+ console.log(` Workspace: ${request.workspace}`);
60
+ console.log(` Repository: ${request.repository}`);
61
+ console.log(` Mode: ${request.dryRun ? "🔵 DRY RUN" : "🔴 LIVE"}`);
62
+ console.log("─".repeat(60) + "\n");
63
+ try {
64
+ // STEP 1: Fetch PR comments using agentic approach (with tools)
65
+ console.log("📥 Step 1: Fetching PR comments via Bitbucket MCP...");
66
+ const fetchInstructions = this.buildFetchCommentsInstructions(request);
67
+ const fetchResponse = await this.neurolink.generate({
68
+ input: { text: fetchInstructions },
69
+ provider: this.config.ai.provider,
70
+ model: this.config.ai.model,
71
+ temperature: 0.1,
72
+ maxTokens: 50000,
73
+ timeout: this.config.ai.timeout,
74
+ context: {
75
+ operation: "learning-fetch-comments",
76
+ prId: request.pullRequestId,
77
+ },
78
+ });
79
+ const commentsData = fetchResponse.content || "";
80
+ console.log(" ✅ PR data fetched\n");
81
+ // STEP 2: Extract learnings with structured schema (no tools needed)
82
+ console.log("🤖 Step 2: Extracting learnings with structured output...");
83
+ const learningPrompt = await this.promptManager.getLearningPrompt();
84
+ const extractionInstructions = this.buildExtractionInstructions(request, learningPrompt, commentsData);
85
+ // Extract learnings - disableTools since we already have the data
86
+ const extractResponse = await this.neurolink.generate({
87
+ input: { text: extractionInstructions },
88
+ provider: this.config.ai.provider,
89
+ model: this.config.ai.model,
90
+ temperature: 0.2,
91
+ maxTokens: 10000,
92
+ timeout: "2m",
93
+ disableTools: true, // No tools needed for extraction phase
94
+ context: {
95
+ operation: "learning-extraction",
96
+ prId: request.pullRequestId,
97
+ },
98
+ });
99
+ // Parse structured response
100
+ const learnings = this.parseStructuredLearnings(extractResponse, request.pullRequestId);
101
+ console.log(`\n📊 AI extracted ${learnings.length} learnings\n`);
102
+ if (learnings.length === 0) {
103
+ // Log extraction response for debugging
104
+ console.log("🔍 Extraction Response (for debugging):");
105
+ console.log("─".repeat(60));
106
+ const responsePreview = JSON.stringify(extractResponse.content || extractResponse, null, 2).slice(0, 2000);
107
+ console.log(responsePreview);
108
+ console.log("─".repeat(60) + "\n");
109
+ return this.createEmptyResult(request, "No actionable learnings found from PR feedback");
110
+ }
111
+ // Handle dry run vs live mode
112
+ if (request.dryRun) {
113
+ return this.handleDryRun(request, learnings);
114
+ }
115
+ // Update knowledge base
116
+ console.log("📝 Updating knowledge base...");
117
+ const kbManager = new KnowledgeBaseManager(this.config.knowledgeBase, process.cwd());
118
+ // Create if doesn't exist
119
+ if (!kbManager.exists()) {
120
+ console.log(" Creating new knowledge base file...");
121
+ await kbManager.create();
122
+ }
123
+ const addedCount = await kbManager.append(learnings);
124
+ const duplicateCount = learnings.length - addedCount;
125
+ console.log(` ✅ Added ${addedCount} learnings`);
126
+ if (duplicateCount > 0) {
127
+ console.log(` ⏭️ Skipped ${duplicateCount} duplicates`);
128
+ }
129
+ // Check if summarization is needed
130
+ let summarized = false;
131
+ if (request.summarize || (await kbManager.needsSummarization())) {
132
+ console.log("\n🔄 Running summarization...");
133
+ await this.runSummarization(kbManager);
134
+ summarized = true;
135
+ console.log(" ✅ Summarization complete");
136
+ }
137
+ // Commit if requested
138
+ let committed = false;
139
+ if (request.commit || this.config.knowledgeBase.autoCommit) {
140
+ console.log("\n📤 Committing knowledge base...");
141
+ await kbManager.commit(request.pullRequestId, addedCount);
142
+ committed = true;
143
+ console.log(" ✅ Changes committed");
144
+ }
145
+ // Return result
146
+ const result = {
147
+ success: true,
148
+ prId: request.pullRequestId,
149
+ learningsFound: learnings.length,
150
+ learningsAdded: addedCount,
151
+ learningsDuplicate: duplicateCount,
152
+ learnings,
153
+ knowledgeBasePath: kbManager.getFilePath(),
154
+ committed,
155
+ summarized,
156
+ };
157
+ this.logResult(result);
158
+ return result;
159
+ }
160
+ catch (error) {
161
+ console.error("\n❌ Learning extraction failed:", error.message);
162
+ return {
163
+ success: false,
164
+ prId: request.pullRequestId,
165
+ learningsFound: 0,
166
+ learningsAdded: 0,
167
+ learningsDuplicate: 0,
168
+ learnings: [],
169
+ error: error.message,
170
+ };
171
+ }
172
+ }
173
+ /**
174
+ * Build instructions for Step 1: Fetch PR comments
175
+ */
176
+ buildFetchCommentsInstructions(request) {
177
+ const aiPatterns = this.config.knowledgeBase.aiAuthorPatterns.join(", ");
178
+ return `
179
+ <task>Fetch and analyze ALL PR comments for learning extraction</task>
180
+
181
+ <pr-details>
182
+ <workspace>${request.workspace}</workspace>
183
+ <repository>${request.repository}</repository>
184
+ <pull_request_id>${request.pullRequestId}</pull_request_id>
185
+ </pr-details>
186
+
187
+ <instructions>
188
+ 1. Use get_pull_request tool to fetch the PR details including all comments/activities
189
+ 2. Look through ALL comments on the PR (both active and resolved if available)
190
+ 3. Identify AI-generated comments by:
191
+ - Author patterns: ${aiPatterns}
192
+ - Text patterns: Comments starting with "🔒 CRITICAL:", "⚠️ MAJOR:", "💡 MINOR:", "💬 SUGGESTION:", or "Yama Review"
193
+ 4. For each AI comment, look for developer replies (threaded responses or comments at same location)
194
+ 5. IMPORTANT: Also identify DEVELOPER comments that are NOT replies to AI comments
195
+ - These represent issues that AI MISSED and should have caught
196
+ - Look for inline code comments from developers (not AI) that point out bugs, issues, or improvements
197
+ 6. Output a structured summary of what you found
198
+ </instructions>
199
+
200
+ <output-format>
201
+ Provide a detailed summary in this format:
202
+
203
+ ## PR Overview
204
+ - Title: [PR title]
205
+ - State: [merged/open/etc]
206
+ - Total comments found: [N]
207
+
208
+ ## AI Comments Found
209
+ For each AI comment, list:
210
+ - Author: [name]
211
+ - Severity: [CRITICAL/MAJOR/MINOR/SUGGESTION]
212
+ - Comment text: [the comment]
213
+ - Location: [file path and line if inline]
214
+
215
+ ## Developer Replies to AI Comments
216
+ For each developer reply to an AI comment:
217
+ - Original AI comment: [brief summary]
218
+ - Developer reply: [the full reply text]
219
+ - Developer name: [name]
220
+
221
+ ## Developer Comments (NOT replies to AI)
222
+ IMPORTANT: List ALL developer-authored inline comments that are NOT replies to AI.
223
+ These represent issues AI MISSED and should have caught.
224
+ For each:
225
+ - Developer name: [name]
226
+ - File: [file path]
227
+ - Line: [line number if available]
228
+ - Comment text: [the full comment]
229
+ - Type: [bug/issue/suggestion/question]
230
+
231
+ If there are no developer comments (other than replies), state: "NO INDEPENDENT DEVELOPER COMMENTS FOUND"
232
+ </output-format>
233
+
234
+ Fetch the PR data now.
235
+ `;
236
+ }
237
+ /**
238
+ * Build instructions for Step 2: Extract learnings from fetched data
239
+ */
240
+ buildExtractionInstructions(request, learningPrompt, commentsData) {
241
+ return `
242
+ <task>Extract project-level learnings from PR feedback</task>
243
+
244
+ <fetched-pr-data>
245
+ ${commentsData}
246
+ </fetched-pr-data>
247
+
248
+ ${learningPrompt}
249
+
250
+ <extraction-rules>
251
+ Extract learnings from TWO sources:
252
+
253
+ 1. DEVELOPER REPLIES TO AI COMMENTS:
254
+ - Developer explained why AI was wrong → false_positive
255
+ - Developer provided context AI should know → domain_context
256
+ - Developer indicated team preference → style_preference
257
+ - Developer suggested how AI should improve → enhancement_guideline
258
+
259
+ 2. INDEPENDENT DEVELOPER COMMENTS (not replies to AI):
260
+ - These are issues AI MISSED and should have caught → missed_issue
261
+ - If a developer added their own inline comment about a bug/issue, AI failed to spot it
262
+ - Extract what type of issue it was so AI can catch similar issues in future
263
+
264
+ IMPORTANT RULES:
265
+ - Make learnings GENERIC - remove PR-specific details (file names, line numbers, variable names)
266
+ - Focus on the underlying principle or pattern that can apply to future reviews
267
+ - If BOTH "NO DEVELOPER REPLIES" AND "NO INDEPENDENT DEVELOPER COMMENTS", return empty learnings
268
+
269
+ Categories:
270
+ - false_positive: Developer explained why AI was wrong
271
+ - missed_issue: Developer pointed out something AI should have caught (including independent comments)
272
+ - style_preference: Team conventions that differ from general practice
273
+ - domain_context: Project-specific knowledge AI needs
274
+ - enhancement_guideline: How AI should provide better suggestions
275
+ </extraction-rules>
276
+
277
+ <output-format>
278
+ Return a JSON object in this exact format (wrapped in a json code block):
279
+
280
+ \`\`\`json
281
+ {
282
+ "learnings": [
283
+ {
284
+ "category": "false_positive|missed_issue|style_preference|domain_context|enhancement_guideline",
285
+ "subcategory": "Optional grouping (e.g., 'Async Patterns', 'Error Handling')",
286
+ "learning": "The GENERIC, PROJECT-LEVEL guideline",
287
+ "filePatterns": ["Optional array of file patterns where this applies"],
288
+ "reasoning": "Why this learning was extracted"
289
+ }
290
+ ],
291
+ "summary": {
292
+ "totalAIComments": 0,
293
+ "totalDeveloperReplies": 0,
294
+ "totalIndependentDevComments": 0,
295
+ "actionablePairsFound": 0
296
+ }
297
+ }
298
+ \`\`\`
299
+
300
+ If no actionable feedback found, return empty learnings array with appropriate summary counts.
301
+ </output-format>
302
+
303
+ Analyze the PR data above and extract learnings.
304
+ `;
305
+ }
306
+ /**
307
+ * Parse structured response from AI output
308
+ */
309
+ parseStructuredLearnings(response, prId) {
310
+ try {
311
+ const content = (response.content || "");
312
+ // Find JSON in response (may be in code block)
313
+ const jsonMatch = content.match(/```json\s*([\s\S]*?)\s*```/) ||
314
+ content.match(/\{[\s\S]*\}/);
315
+ if (!jsonMatch) {
316
+ console.log(" ⚠️ No JSON found in extraction response");
317
+ return [];
318
+ }
319
+ const jsonStr = jsonMatch[1] || jsonMatch[0];
320
+ const parsed = JSON.parse(jsonStr);
321
+ // Log summary if available
322
+ if (parsed.summary) {
323
+ const devComments = parsed.summary.totalIndependentDevComments || 0;
324
+ console.log(` 📊 Summary: ${parsed.summary.totalAIComments} AI comments, ${parsed.summary.totalDeveloperReplies} dev replies, ${devComments} independent dev comments`);
325
+ }
326
+ if (!parsed.learnings || !Array.isArray(parsed.learnings)) {
327
+ return [];
328
+ }
329
+ const kbManager = new KnowledgeBaseManager(this.config.knowledgeBase, process.cwd());
330
+ return parsed.learnings.map((item) => ({
331
+ id: kbManager.generateLearningId(item.learning),
332
+ category: item.category,
333
+ subcategory: item.subcategory,
334
+ learning: item.learning,
335
+ filePatterns: item.filePatterns,
336
+ sourceInfo: {
337
+ prId,
338
+ timestamp: new Date().toISOString(),
339
+ },
340
+ }));
341
+ }
342
+ catch (error) {
343
+ console.warn(" ⚠️ Failed to parse structured output:", error.message);
344
+ return [];
345
+ }
346
+ }
347
+ // ============================================================================
348
+ // Private Methods
349
+ // ============================================================================
350
+ /**
351
+ * Initialize NeuroLink with observability
352
+ */
353
+ initializeNeurolink() {
354
+ const observabilityConfig = buildObservabilityConfigFromEnv();
355
+ const neurolinkConfig = {
356
+ conversationMemory: this.config.ai.conversationMemory,
357
+ };
358
+ if (observabilityConfig &&
359
+ validateObservabilityConfig(observabilityConfig)) {
360
+ neurolinkConfig.observability = observabilityConfig;
361
+ }
362
+ return new NeuroLink(neurolinkConfig);
363
+ }
364
+ /**
365
+ * Ensure orchestrator is initialized
366
+ */
367
+ async ensureInitialized() {
368
+ if (!this.initialized) {
369
+ await this.initialize();
370
+ }
371
+ }
372
+ /**
373
+ * Handle dry run mode
374
+ */
375
+ handleDryRun(request, learnings) {
376
+ console.log("\n📋 DRY RUN - Extracted learnings preview:\n");
377
+ if (request.outputFormat === "json") {
378
+ console.log(JSON.stringify({
379
+ prId: request.pullRequestId,
380
+ learningsFound: learnings.length,
381
+ learnings: learnings.map((l) => ({
382
+ category: l.category,
383
+ subcategory: l.subcategory,
384
+ learning: l.learning,
385
+ })),
386
+ }, null, 2));
387
+ }
388
+ else {
389
+ // Markdown format
390
+ const byCategory = new Map();
391
+ for (const learning of learnings) {
392
+ const cat = learning.category;
393
+ if (!byCategory.has(cat)) {
394
+ byCategory.set(cat, []);
395
+ }
396
+ byCategory.get(cat).push(learning);
397
+ }
398
+ for (const [category, items] of byCategory) {
399
+ console.log(`## ${category.replace(/_/g, " ").toUpperCase()}`);
400
+ for (const item of items) {
401
+ console.log(`- ${item.learning}`);
402
+ }
403
+ console.log("");
404
+ }
405
+ console.log("─".repeat(60));
406
+ console.log("Run without --dry-run to commit these learnings.");
407
+ }
408
+ return {
409
+ success: true,
410
+ prId: request.pullRequestId,
411
+ learningsFound: learnings.length,
412
+ learningsAdded: 0,
413
+ learningsDuplicate: 0,
414
+ learnings,
415
+ };
416
+ }
417
+ /**
418
+ * Run AI-powered summarization
419
+ */
420
+ async runSummarization(kbManager) {
421
+ const currentContent = await kbManager.getForPrompt();
422
+ if (!currentContent) {
423
+ return;
424
+ }
425
+ const systemPrompt = await this.promptManager.getSummarizationPrompt();
426
+ const prompt = `
427
+ ${systemPrompt}
428
+
429
+ <current-knowledge-base>
430
+ ${currentContent}
431
+ </current-knowledge-base>
432
+
433
+ Consolidate the learnings as instructed. Return the complete updated knowledge base in markdown format.
434
+ `;
435
+ const response = await this.neurolink.generate({
436
+ input: { text: prompt },
437
+ provider: this.config.ai.provider,
438
+ model: this.config.ai.model,
439
+ temperature: 0.2,
440
+ maxTokens: 30000,
441
+ });
442
+ // Parse and update the knowledge base
443
+ const newContent = (response.content || "");
444
+ // Extract markdown content (may be in code block)
445
+ let markdownContent = newContent;
446
+ const mdMatch = newContent.match(/```(?:markdown|md)?\s*([\s\S]*?)```/);
447
+ if (mdMatch) {
448
+ markdownContent = mdMatch[1].trim();
449
+ }
450
+ // Verify it looks like valid markdown KB
451
+ if (markdownContent.includes("# Project Knowledge Base")) {
452
+ // Write the summarized content directly to file
453
+ await kbManager.writeRaw(markdownContent);
454
+ }
455
+ }
456
+ /**
457
+ * Create empty result for cases with no learnings
458
+ */
459
+ createEmptyResult(request, reason) {
460
+ console.log(`\n⚠️ ${reason}\n`);
461
+ return {
462
+ success: true,
463
+ prId: request.pullRequestId,
464
+ learningsFound: 0,
465
+ learningsAdded: 0,
466
+ learningsDuplicate: 0,
467
+ learnings: [],
468
+ };
469
+ }
470
+ /**
471
+ * Log the final result
472
+ */
473
+ logResult(result) {
474
+ console.log("\n" + "═".repeat(60));
475
+ console.log("✅ Learning Extraction Complete");
476
+ console.log("═".repeat(60));
477
+ console.log(` PR: #${result.prId}`);
478
+ console.log(` Learnings found: ${result.learningsFound}`);
479
+ console.log(` Learnings added: ${result.learningsAdded}`);
480
+ console.log(` Duplicates skipped: ${result.learningsDuplicate}`);
481
+ if (result.knowledgeBasePath) {
482
+ console.log(` Knowledge base: ${result.knowledgeBasePath}`);
483
+ }
484
+ if (result.committed) {
485
+ console.log(` 📤 Changes committed`);
486
+ }
487
+ if (result.summarized) {
488
+ console.log(` 🔄 Knowledge base summarized`);
489
+ }
490
+ console.log("═".repeat(60) + "\n");
491
+ }
492
+ }
493
+ /**
494
+ * Factory function to create LearningOrchestrator
495
+ */
496
+ export function createLearningOrchestrator() {
497
+ return new LearningOrchestrator();
498
+ }
499
+ //# sourceMappingURL=LearningOrchestrator.js.map
@@ -0,0 +1,22 @@
1
+ /**
2
+ * MCP Server Manager for Yama V2
3
+ * Manages lifecycle and health of Bitbucket and Jira MCP servers
4
+ */
5
+ import { MCPServersConfig } from "../types/config.types.js";
6
+ export declare class MCPServerManager {
7
+ private initialized;
8
+ /**
9
+ * Setup all MCP servers in NeuroLink
10
+ * Bitbucket is always enabled, Jira is optional based on config
11
+ */
12
+ setupMCPServers(neurolink: any, config: MCPServersConfig): Promise<void>;
13
+ /**
14
+ * Setup Bitbucket MCP server (hardcoded, always enabled)
15
+ */
16
+ private setupBitbucketMCP;
17
+ /**
18
+ * Setup Jira MCP server (hardcoded, optionally enabled)
19
+ */
20
+ private setupJiraMCP;
21
+ }
22
+ //# sourceMappingURL=MCPServerManager.d.ts.map
@@ -0,0 +1,100 @@
1
+ /**
2
+ * MCP Server Manager for Yama V2
3
+ * Manages lifecycle and health of Bitbucket and Jira MCP servers
4
+ */
5
+ import { MCPServerError } from "../types/v2.types.js";
6
+ export class MCPServerManager {
7
+ // MCP servers are managed entirely by NeuroLink
8
+ // No need to track tools locally
9
+ initialized = false;
10
+ /**
11
+ * Setup all MCP servers in NeuroLink
12
+ * Bitbucket is always enabled, Jira is optional based on config
13
+ */
14
+ async setupMCPServers(neurolink, config) {
15
+ console.log("🔌 Setting up MCP servers...");
16
+ // Setup Bitbucket MCP (always enabled)
17
+ await this.setupBitbucketMCP(neurolink, config.bitbucket?.blockedTools);
18
+ // Setup Jira MCP (optional)
19
+ if (config.jira.enabled) {
20
+ await this.setupJiraMCP(neurolink, config.jira.blockedTools);
21
+ }
22
+ else {
23
+ console.log(" ⏭️ Jira MCP disabled in config");
24
+ }
25
+ this.initialized = true;
26
+ console.log("✅ MCP servers configured\n");
27
+ }
28
+ /**
29
+ * Setup Bitbucket MCP server (hardcoded, always enabled)
30
+ */
31
+ async setupBitbucketMCP(neurolink, blockedTools) {
32
+ try {
33
+ console.log(" 🔧 Registering Bitbucket MCP server...");
34
+ // Verify environment variables
35
+ if (!process.env.BITBUCKET_USERNAME ||
36
+ !process.env.BITBUCKET_TOKEN ||
37
+ !process.env.BITBUCKET_BASE_URL) {
38
+ throw new MCPServerError("Missing required environment variables: BITBUCKET_USERNAME, BITBUCKET_TOKEN, or BITBUCKET_BASE_URL");
39
+ }
40
+ // Hardcoded Bitbucket MCP configuration
41
+ await neurolink.addExternalMCPServer("bitbucket", {
42
+ command: "npx",
43
+ args: ["-y", "@nexus2520/bitbucket-mcp-server"],
44
+ transport: "stdio",
45
+ env: {
46
+ BITBUCKET_USERNAME: process.env.BITBUCKET_USERNAME,
47
+ BITBUCKET_TOKEN: process.env.BITBUCKET_TOKEN,
48
+ BITBUCKET_BASE_URL: process.env.BITBUCKET_BASE_URL,
49
+ },
50
+ blockedTools: blockedTools || [],
51
+ });
52
+ console.log(" ✅ Bitbucket MCP server registered and tools available");
53
+ if (blockedTools && blockedTools.length > 0) {
54
+ console.log(` 🚫 Blocked tools: ${blockedTools.join(", ")}`);
55
+ }
56
+ }
57
+ catch (error) {
58
+ throw new MCPServerError(`Failed to setup Bitbucket MCP server: ${error.message}`);
59
+ }
60
+ }
61
+ /**
62
+ * Setup Jira MCP server (hardcoded, optionally enabled)
63
+ */
64
+ async setupJiraMCP(neurolink, blockedTools) {
65
+ try {
66
+ console.log(" 🔧 Registering Jira MCP server...");
67
+ // Validate required Jira environment variables
68
+ const jiraEmail = process.env.JIRA_EMAIL;
69
+ const jiraToken = process.env.JIRA_API_TOKEN;
70
+ const jiraBaseUrl = process.env.JIRA_BASE_URL;
71
+ if (!jiraEmail || !jiraToken || !jiraBaseUrl) {
72
+ console.warn(" ⚠️ Missing Jira environment variables (JIRA_EMAIL, JIRA_API_TOKEN, or JIRA_BASE_URL)");
73
+ console.warn(" Skipping Jira integration...");
74
+ return;
75
+ }
76
+ // Hardcoded Jira MCP configuration
77
+ await neurolink.addExternalMCPServer("jira", {
78
+ command: "npx",
79
+ args: ["-y", "@nexus2520/jira-mcp-server"],
80
+ transport: "stdio",
81
+ env: {
82
+ JIRA_EMAIL: process.env.JIRA_EMAIL,
83
+ JIRA_API_TOKEN: process.env.JIRA_API_TOKEN,
84
+ JIRA_BASE_URL: process.env.JIRA_BASE_URL,
85
+ },
86
+ blockedTools: blockedTools || [],
87
+ });
88
+ console.log(" ✅ Jira MCP server registered and tools available");
89
+ if (blockedTools && blockedTools.length > 0) {
90
+ console.log(` 🚫 Blocked tools: ${blockedTools.join(", ")}`);
91
+ }
92
+ }
93
+ catch (error) {
94
+ // Jira is optional, so we warn instead of throwing
95
+ console.warn(` ⚠️ Failed to setup Jira MCP server: ${error.message}`);
96
+ console.warn(" Continuing without Jira integration...");
97
+ }
98
+ }
99
+ }
100
+ //# sourceMappingURL=MCPServerManager.js.map
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Session Manager for Yama V2
3
+ * Tracks review sessions, tool calls, and maintains state
4
+ */
5
+ import { ReviewSession, ReviewRequest, ReviewResult, SessionMetadata } from "../types/v2.types.js";
6
+ export declare class SessionManager {
7
+ private sessions;
8
+ private maxSessions;
9
+ /**
10
+ * Create a new review session
11
+ */
12
+ createSession(request: ReviewRequest): string;
13
+ /**
14
+ * Get session by ID
15
+ */
16
+ getSession(sessionId: string): ReviewSession;
17
+ /**
18
+ * Record a tool call in the session
19
+ */
20
+ recordToolCall(sessionId: string, toolName: string, args: any, result: any, duration: number, error?: string): void;
21
+ /**
22
+ * Update session metadata
23
+ */
24
+ updateMetadata(sessionId: string, updates: Partial<SessionMetadata>): void;
25
+ /**
26
+ * Mark session as completed
27
+ */
28
+ completeSession(sessionId: string, result: ReviewResult): void;
29
+ /**
30
+ * Mark session as failed
31
+ */
32
+ failSession(sessionId: string, error: Error): void;
33
+ /**
34
+ * Get all active sessions
35
+ */
36
+ getActiveSessions(): ReviewSession[];
37
+ /**
38
+ * Get session statistics
39
+ */
40
+ getSessionStats(sessionId: string): {
41
+ duration: number;
42
+ toolCallCount: number;
43
+ uniqueTools: number;
44
+ averageToolCallDuration: number;
45
+ toolCallsByName: Record<string, number>;
46
+ };
47
+ /**
48
+ * Generate unique session ID
49
+ */
50
+ private generateSessionId;
51
+ /**
52
+ * Clean up old sessions (keep most recent 100)
53
+ */
54
+ private cleanupOldSessions;
55
+ /**
56
+ * Export session data for debugging
57
+ */
58
+ exportSession(sessionId: string): any;
59
+ /**
60
+ * Summarize tool result for logging
61
+ */
62
+ private summarizeToolResult;
63
+ /**
64
+ * Clear all sessions
65
+ */
66
+ clearAll(): void;
67
+ /**
68
+ * Get session count
69
+ */
70
+ getSessionCount(): number;
71
+ }
72
+ //# sourceMappingURL=SessionManager.d.ts.map