@juspay/yama 2.0.0 → 2.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.
@@ -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
@@ -14,10 +14,10 @@ export class MCPServerManager {
14
14
  async setupMCPServers(neurolink, config) {
15
15
  console.log("šŸ”Œ Setting up MCP servers...");
16
16
  // Setup Bitbucket MCP (always enabled)
17
- await this.setupBitbucketMCP(neurolink);
17
+ await this.setupBitbucketMCP(neurolink, config.bitbucket?.blockedTools);
18
18
  // Setup Jira MCP (optional)
19
19
  if (config.jira.enabled) {
20
- await this.setupJiraMCP(neurolink);
20
+ await this.setupJiraMCP(neurolink, config.jira.blockedTools);
21
21
  }
22
22
  else {
23
23
  console.log(" ā­ļø Jira MCP disabled in config");
@@ -28,7 +28,7 @@ export class MCPServerManager {
28
28
  /**
29
29
  * Setup Bitbucket MCP server (hardcoded, always enabled)
30
30
  */
31
- async setupBitbucketMCP(neurolink) {
31
+ async setupBitbucketMCP(neurolink, blockedTools) {
32
32
  try {
33
33
  console.log(" šŸ”§ Registering Bitbucket MCP server...");
34
34
  // Verify environment variables
@@ -47,8 +47,12 @@ export class MCPServerManager {
47
47
  BITBUCKET_TOKEN: process.env.BITBUCKET_TOKEN,
48
48
  BITBUCKET_BASE_URL: process.env.BITBUCKET_BASE_URL,
49
49
  },
50
+ blockedTools: blockedTools || [],
50
51
  });
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
+ }
52
56
  }
53
57
  catch (error) {
54
58
  throw new MCPServerError(`Failed to setup Bitbucket MCP server: ${error.message}`);
@@ -57,7 +61,7 @@ export class MCPServerManager {
57
61
  /**
58
62
  * Setup Jira MCP server (hardcoded, optionally enabled)
59
63
  */
60
- async setupJiraMCP(neurolink) {
64
+ async setupJiraMCP(neurolink, blockedTools) {
61
65
  try {
62
66
  console.log(" šŸ”§ Registering Jira MCP server...");
63
67
  // Validate required Jira environment variables
@@ -79,8 +83,12 @@ export class MCPServerManager {
79
83
  JIRA_API_TOKEN: process.env.JIRA_API_TOKEN,
80
84
  JIRA_BASE_URL: process.env.JIRA_BASE_URL,
81
85
  },
86
+ blockedTools: blockedTools || [],
82
87
  });
83
88
  console.log(" āœ… Jira MCP server registered and tools available");
89
+ if (blockedTools && blockedTools.length > 0) {
90
+ console.log(` 🚫 Blocked tools: ${blockedTools.join(", ")}`);
91
+ }
84
92
  }
85
93
  catch (error) {
86
94
  // Jira is optional, so we warn instead of throwing
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Feedback Extractor
3
+ * Identifies AI comments and developer replies from PR comments
4
+ */
5
+ import { KnowledgeBaseConfig } from "../types/config.types.js";
6
+ import { PRComment, CommentPair } from "./types.js";
7
+ export declare class FeedbackExtractor {
8
+ private config;
9
+ constructor(config: KnowledgeBaseConfig);
10
+ /**
11
+ * Check if a comment is from AI based on author and text patterns
12
+ */
13
+ isAIComment(comment: PRComment): boolean;
14
+ /**
15
+ * Check if a comment is a developer reply (not from AI)
16
+ */
17
+ isDeveloperComment(comment: PRComment): boolean;
18
+ /**
19
+ * Extract AI comment + developer reply pairs from a list of comments
20
+ */
21
+ extractCommentPairs(comments: PRComment[]): CommentPair[];
22
+ /**
23
+ * Find comment pairs based on file/line proximity (for inline comments)
24
+ */
25
+ private findInlineCommentPairs;
26
+ /**
27
+ * Convert raw Bitbucket API comment to PRComment
28
+ */
29
+ convertBitbucketComment(rawComment: Record<string, unknown>): PRComment;
30
+ /**
31
+ * Extract meaningful feedback from a comment pair
32
+ * Determines if the developer reply contains actionable feedback
33
+ */
34
+ hasActionableFeedback(pair: CommentPair): boolean;
35
+ /**
36
+ * Get statistics about comment analysis
37
+ */
38
+ getCommentStats(comments: PRComment[]): {
39
+ total: number;
40
+ aiComments: number;
41
+ developerComments: number;
42
+ pairs: number;
43
+ actionablePairs: number;
44
+ };
45
+ }
46
+ //# sourceMappingURL=FeedbackExtractor.d.ts.map