@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.
- package/.mcp-config.example.json +26 -0
- package/CHANGELOG.md +46 -0
- package/README.md +311 -685
- package/dist/cli/v2.cli.d.ts +13 -0
- package/dist/cli/v2.cli.js +359 -0
- package/dist/index.d.ts +12 -13
- package/dist/index.js +18 -19
- package/dist/v2/config/ConfigLoader.d.ts +50 -0
- package/dist/v2/config/ConfigLoader.js +205 -0
- package/dist/v2/config/DefaultConfig.d.ts +9 -0
- package/dist/v2/config/DefaultConfig.js +187 -0
- package/dist/v2/core/LearningOrchestrator.d.ts +65 -0
- package/dist/v2/core/LearningOrchestrator.js +499 -0
- package/dist/v2/core/MCPServerManager.d.ts +22 -0
- package/dist/v2/core/MCPServerManager.js +100 -0
- package/dist/v2/core/SessionManager.d.ts +72 -0
- package/dist/v2/core/SessionManager.js +200 -0
- package/dist/v2/core/YamaV2Orchestrator.d.ts +112 -0
- package/dist/v2/core/YamaV2Orchestrator.js +549 -0
- package/dist/v2/learning/FeedbackExtractor.d.ts +46 -0
- package/dist/v2/learning/FeedbackExtractor.js +237 -0
- package/dist/v2/learning/KnowledgeBaseManager.d.ts +91 -0
- package/dist/v2/learning/KnowledgeBaseManager.js +475 -0
- package/dist/v2/learning/types.d.ts +121 -0
- package/dist/v2/learning/types.js +15 -0
- package/dist/v2/prompts/EnhancementSystemPrompt.d.ts +8 -0
- package/dist/v2/prompts/EnhancementSystemPrompt.js +216 -0
- package/dist/v2/prompts/LangfusePromptManager.d.ts +48 -0
- package/dist/v2/prompts/LangfusePromptManager.js +144 -0
- package/dist/v2/prompts/LearningSystemPrompt.d.ts +11 -0
- package/dist/v2/prompts/LearningSystemPrompt.js +180 -0
- package/dist/v2/prompts/PromptBuilder.d.ts +45 -0
- package/dist/v2/prompts/PromptBuilder.js +257 -0
- package/dist/v2/prompts/ReviewSystemPrompt.d.ts +8 -0
- package/dist/v2/prompts/ReviewSystemPrompt.js +270 -0
- package/dist/v2/types/config.types.d.ts +141 -0
- package/dist/v2/types/config.types.js +5 -0
- package/dist/v2/types/mcp.types.d.ts +191 -0
- package/dist/v2/types/mcp.types.js +6 -0
- package/dist/v2/types/v2.types.d.ts +182 -0
- package/dist/v2/types/v2.types.js +42 -0
- package/dist/v2/utils/ObservabilityConfig.d.ts +22 -0
- package/dist/v2/utils/ObservabilityConfig.js +48 -0
- package/package.json +16 -10
- package/yama.config.example.yaml +259 -204
- package/dist/cli/index.d.ts +0 -12
- package/dist/cli/index.js +0 -538
- package/dist/core/ContextGatherer.d.ts +0 -110
- package/dist/core/ContextGatherer.js +0 -470
- package/dist/core/Guardian.d.ts +0 -81
- package/dist/core/Guardian.js +0 -480
- package/dist/core/providers/BitbucketProvider.d.ts +0 -105
- package/dist/core/providers/BitbucketProvider.js +0 -489
- package/dist/features/CodeReviewer.d.ts +0 -173
- package/dist/features/CodeReviewer.js +0 -1707
- package/dist/features/DescriptionEnhancer.d.ts +0 -70
- package/dist/features/DescriptionEnhancer.js +0 -511
- package/dist/features/MultiInstanceProcessor.d.ts +0 -74
- package/dist/features/MultiInstanceProcessor.js +0 -360
- package/dist/types/index.d.ts +0 -624
- package/dist/types/index.js +0 -104
- package/dist/utils/Cache.d.ts +0 -103
- package/dist/utils/Cache.js +0 -444
- package/dist/utils/ConfigManager.d.ts +0 -88
- package/dist/utils/ConfigManager.js +0 -602
- package/dist/utils/ContentSimilarityService.d.ts +0 -74
- package/dist/utils/ContentSimilarityService.js +0 -215
- package/dist/utils/ExactDuplicateRemover.d.ts +0 -77
- package/dist/utils/ExactDuplicateRemover.js +0 -361
- package/dist/utils/Logger.d.ts +0 -31
- package/dist/utils/Logger.js +0 -214
- package/dist/utils/MemoryBankManager.d.ts +0 -73
- package/dist/utils/MemoryBankManager.js +0 -310
- package/dist/utils/ParallelProcessing.d.ts +0 -140
- package/dist/utils/ParallelProcessing.js +0 -333
- package/dist/utils/ProviderLimits.d.ts +0 -58
- package/dist/utils/ProviderLimits.js +0 -143
- package/dist/utils/RetryManager.d.ts +0 -78
- package/dist/utils/RetryManager.js +0 -205
|
@@ -0,0 +1,549 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Yama V2 Orchestrator
|
|
3
|
+
* Main entry point for AI-native autonomous code review
|
|
4
|
+
*/
|
|
5
|
+
import { NeuroLink } from "@juspay/neurolink";
|
|
6
|
+
import { MCPServerManager } from "./MCPServerManager.js";
|
|
7
|
+
import { ConfigLoader } from "../config/ConfigLoader.js";
|
|
8
|
+
import { PromptBuilder } from "../prompts/PromptBuilder.js";
|
|
9
|
+
import { SessionManager } from "./SessionManager.js";
|
|
10
|
+
import { buildObservabilityConfigFromEnv, validateObservabilityConfig, } from "../utils/ObservabilityConfig.js";
|
|
11
|
+
export class YamaV2Orchestrator {
|
|
12
|
+
neurolink;
|
|
13
|
+
mcpManager;
|
|
14
|
+
configLoader;
|
|
15
|
+
promptBuilder;
|
|
16
|
+
sessionManager;
|
|
17
|
+
config;
|
|
18
|
+
initialized = false;
|
|
19
|
+
constructor() {
|
|
20
|
+
this.configLoader = new ConfigLoader();
|
|
21
|
+
this.mcpManager = new MCPServerManager();
|
|
22
|
+
this.promptBuilder = new PromptBuilder();
|
|
23
|
+
this.sessionManager = new SessionManager();
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Initialize Yama V2 with configuration and MCP servers
|
|
27
|
+
*/
|
|
28
|
+
async initialize(configPath) {
|
|
29
|
+
if (this.initialized) {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
this.showBanner();
|
|
33
|
+
console.log("🚀 Initializing Yama V2...\n");
|
|
34
|
+
try {
|
|
35
|
+
// Step 1: Load configuration
|
|
36
|
+
this.config = await this.configLoader.loadConfig(configPath);
|
|
37
|
+
// Step 2: Initialize NeuroLink with observability
|
|
38
|
+
console.log("🧠 Initializing NeuroLink AI engine...");
|
|
39
|
+
this.neurolink = this.initializeNeurolink();
|
|
40
|
+
console.log("✅ NeuroLink initialized\n");
|
|
41
|
+
// Step 3: Setup MCP servers
|
|
42
|
+
await this.mcpManager.setupMCPServers(this.neurolink, this.config.mcpServers);
|
|
43
|
+
console.log("✅ MCP servers ready (tools managed by NeuroLink)\n");
|
|
44
|
+
// Step 4: Validate configuration
|
|
45
|
+
await this.configLoader.validate();
|
|
46
|
+
this.initialized = true;
|
|
47
|
+
console.log("✅ Yama V2 initialized successfully\n");
|
|
48
|
+
console.log("═".repeat(60) + "\n");
|
|
49
|
+
}
|
|
50
|
+
catch (error) {
|
|
51
|
+
console.error("\n❌ Initialization failed:", error.message);
|
|
52
|
+
throw error;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Start autonomous AI review
|
|
57
|
+
*/
|
|
58
|
+
async startReview(request) {
|
|
59
|
+
await this.ensureInitialized();
|
|
60
|
+
const startTime = Date.now();
|
|
61
|
+
const sessionId = this.sessionManager.createSession(request);
|
|
62
|
+
this.logReviewStart(request, sessionId);
|
|
63
|
+
try {
|
|
64
|
+
// Build comprehensive AI instructions
|
|
65
|
+
const instructions = await this.promptBuilder.buildReviewInstructions(request, this.config);
|
|
66
|
+
if (this.config.display.verboseToolCalls) {
|
|
67
|
+
console.log("\n📝 AI Instructions built:");
|
|
68
|
+
console.log(` Instruction length: ${instructions.length} characters\n`);
|
|
69
|
+
}
|
|
70
|
+
// Create tool context for AI
|
|
71
|
+
const toolContext = this.createToolContext(sessionId, request);
|
|
72
|
+
// Set tool context in NeuroLink (using type assertion as setToolContext is documented but may not be in type definitions)
|
|
73
|
+
this.neurolink.setToolContext(toolContext);
|
|
74
|
+
// Update session metadata
|
|
75
|
+
this.sessionManager.updateMetadata(sessionId, {
|
|
76
|
+
aiProvider: this.config.ai.provider,
|
|
77
|
+
aiModel: this.config.ai.model,
|
|
78
|
+
});
|
|
79
|
+
// Execute autonomous AI review
|
|
80
|
+
console.log("🤖 Starting autonomous AI review...");
|
|
81
|
+
console.log(" AI will now make decisions and execute actions autonomously\n");
|
|
82
|
+
const aiResponse = await this.neurolink.generate({
|
|
83
|
+
input: { text: instructions },
|
|
84
|
+
provider: this.config.ai.provider,
|
|
85
|
+
model: this.config.ai.model,
|
|
86
|
+
temperature: this.config.ai.temperature,
|
|
87
|
+
maxTokens: this.config.ai.maxTokens,
|
|
88
|
+
timeout: this.config.ai.timeout,
|
|
89
|
+
context: {
|
|
90
|
+
sessionId,
|
|
91
|
+
userId: this.generateUserId(request),
|
|
92
|
+
operation: "code-review",
|
|
93
|
+
metadata: toolContext.metadata,
|
|
94
|
+
},
|
|
95
|
+
enableAnalytics: this.config.ai.enableAnalytics,
|
|
96
|
+
enableEvaluation: this.config.ai.enableEvaluation,
|
|
97
|
+
});
|
|
98
|
+
// Extract and parse results
|
|
99
|
+
const result = this.parseReviewResult(aiResponse, startTime, sessionId);
|
|
100
|
+
// Update session with results
|
|
101
|
+
this.sessionManager.completeSession(sessionId, result);
|
|
102
|
+
this.logReviewComplete(result);
|
|
103
|
+
return result;
|
|
104
|
+
}
|
|
105
|
+
catch (error) {
|
|
106
|
+
this.sessionManager.failSession(sessionId, error);
|
|
107
|
+
console.error("\n❌ Review failed:", error.message);
|
|
108
|
+
throw error;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Stream review with real-time updates (for verbose mode)
|
|
113
|
+
*/
|
|
114
|
+
async *streamReview(request) {
|
|
115
|
+
await this.ensureInitialized();
|
|
116
|
+
const sessionId = this.sessionManager.createSession(request);
|
|
117
|
+
try {
|
|
118
|
+
// Build instructions
|
|
119
|
+
const instructions = await this.promptBuilder.buildReviewInstructions(request, this.config);
|
|
120
|
+
// Create tool context
|
|
121
|
+
const toolContext = this.createToolContext(sessionId, request);
|
|
122
|
+
this.neurolink.setToolContext(toolContext);
|
|
123
|
+
// Stream AI execution
|
|
124
|
+
yield {
|
|
125
|
+
type: "progress",
|
|
126
|
+
timestamp: new Date().toISOString(),
|
|
127
|
+
sessionId,
|
|
128
|
+
data: {
|
|
129
|
+
phase: "context_gathering",
|
|
130
|
+
progress: 0,
|
|
131
|
+
message: "Starting review...",
|
|
132
|
+
},
|
|
133
|
+
};
|
|
134
|
+
// Note: NeuroLink streaming implementation depends on version
|
|
135
|
+
// This is a placeholder for streaming functionality
|
|
136
|
+
const aiResponse = await this.neurolink.generate({
|
|
137
|
+
input: { text: instructions },
|
|
138
|
+
provider: this.config.ai.provider,
|
|
139
|
+
model: this.config.ai.model,
|
|
140
|
+
context: {
|
|
141
|
+
sessionId,
|
|
142
|
+
userId: this.generateUserId(request),
|
|
143
|
+
},
|
|
144
|
+
enableAnalytics: true,
|
|
145
|
+
});
|
|
146
|
+
yield {
|
|
147
|
+
type: "progress",
|
|
148
|
+
timestamp: new Date().toISOString(),
|
|
149
|
+
sessionId,
|
|
150
|
+
data: {
|
|
151
|
+
phase: "decision_making",
|
|
152
|
+
progress: 100,
|
|
153
|
+
message: "Review complete",
|
|
154
|
+
},
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
catch (error) {
|
|
158
|
+
this.sessionManager.failSession(sessionId, error);
|
|
159
|
+
throw error;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Start review and then enhance description in the same session
|
|
164
|
+
* This allows the AI to use knowledge gained during review to write better descriptions
|
|
165
|
+
*/
|
|
166
|
+
async startReviewAndEnhance(request) {
|
|
167
|
+
await this.ensureInitialized();
|
|
168
|
+
const startTime = Date.now();
|
|
169
|
+
const sessionId = this.sessionManager.createSession(request);
|
|
170
|
+
this.logReviewStart(request, sessionId);
|
|
171
|
+
try {
|
|
172
|
+
// ========================================================================
|
|
173
|
+
// PHASE 1: Code Review
|
|
174
|
+
// ========================================================================
|
|
175
|
+
// Build review instructions
|
|
176
|
+
const reviewInstructions = await this.promptBuilder.buildReviewInstructions(request, this.config);
|
|
177
|
+
if (this.config.display.verboseToolCalls) {
|
|
178
|
+
console.log("\n📝 Review instructions built:");
|
|
179
|
+
console.log(` Instruction length: ${reviewInstructions.length} characters\n`);
|
|
180
|
+
}
|
|
181
|
+
// Create tool context
|
|
182
|
+
const toolContext = this.createToolContext(sessionId, request);
|
|
183
|
+
this.neurolink.setToolContext(toolContext);
|
|
184
|
+
// Update session metadata
|
|
185
|
+
this.sessionManager.updateMetadata(sessionId, {
|
|
186
|
+
aiProvider: this.config.ai.provider,
|
|
187
|
+
aiModel: this.config.ai.model,
|
|
188
|
+
});
|
|
189
|
+
// Execute review
|
|
190
|
+
console.log("🤖 Phase 1: Starting autonomous AI code review...");
|
|
191
|
+
console.log(" AI will analyze files and post comments\n");
|
|
192
|
+
const reviewResponse = await this.neurolink.generate({
|
|
193
|
+
input: { text: reviewInstructions },
|
|
194
|
+
provider: this.config.ai.provider,
|
|
195
|
+
model: this.config.ai.model,
|
|
196
|
+
temperature: this.config.ai.temperature,
|
|
197
|
+
maxTokens: this.config.ai.maxTokens,
|
|
198
|
+
timeout: this.config.ai.timeout,
|
|
199
|
+
context: {
|
|
200
|
+
sessionId,
|
|
201
|
+
userId: this.generateUserId(request),
|
|
202
|
+
operation: "code-review",
|
|
203
|
+
metadata: toolContext.metadata,
|
|
204
|
+
},
|
|
205
|
+
enableAnalytics: this.config.ai.enableAnalytics,
|
|
206
|
+
enableEvaluation: this.config.ai.enableEvaluation,
|
|
207
|
+
});
|
|
208
|
+
// Parse review results
|
|
209
|
+
const reviewResult = this.parseReviewResult(reviewResponse, startTime, sessionId);
|
|
210
|
+
console.log("\n✅ Phase 1 complete: Code review finished");
|
|
211
|
+
console.log(` Decision: ${reviewResult.decision}`);
|
|
212
|
+
console.log(` Comments: ${reviewResult.statistics.totalComments}\n`);
|
|
213
|
+
// ========================================================================
|
|
214
|
+
// PHASE 2: Description Enhancement (using same session)
|
|
215
|
+
// ========================================================================
|
|
216
|
+
if (this.config.descriptionEnhancement.enabled) {
|
|
217
|
+
console.log("📝 Phase 2: Enhancing PR description...");
|
|
218
|
+
console.log(" AI will use review insights to write description\n");
|
|
219
|
+
const enhanceInstructions = await this.promptBuilder.buildDescriptionEnhancementInstructions(request, this.config);
|
|
220
|
+
// Continue the SAME session - AI remembers everything from review
|
|
221
|
+
const enhanceResponse = await this.neurolink.generate({
|
|
222
|
+
input: { text: enhanceInstructions },
|
|
223
|
+
provider: this.config.ai.provider,
|
|
224
|
+
model: this.config.ai.model,
|
|
225
|
+
temperature: this.config.ai.temperature,
|
|
226
|
+
maxTokens: this.config.ai.maxTokens,
|
|
227
|
+
timeout: this.config.ai.timeout,
|
|
228
|
+
context: {
|
|
229
|
+
sessionId, // SAME sessionId = AI remembers review context
|
|
230
|
+
userId: this.generateUserId(request),
|
|
231
|
+
operation: "description-enhancement",
|
|
232
|
+
metadata: toolContext.metadata,
|
|
233
|
+
},
|
|
234
|
+
enableAnalytics: this.config.ai.enableAnalytics,
|
|
235
|
+
enableEvaluation: this.config.ai.enableEvaluation,
|
|
236
|
+
});
|
|
237
|
+
console.log("✅ Phase 2 complete: Description enhanced\n");
|
|
238
|
+
// Add enhancement status to result
|
|
239
|
+
reviewResult.descriptionEnhanced = true;
|
|
240
|
+
}
|
|
241
|
+
else {
|
|
242
|
+
console.log("⏭️ Skipping description enhancement (disabled in config)\n");
|
|
243
|
+
reviewResult.descriptionEnhanced = false;
|
|
244
|
+
}
|
|
245
|
+
// Update session with final results
|
|
246
|
+
this.sessionManager.completeSession(sessionId, reviewResult);
|
|
247
|
+
this.logReviewComplete(reviewResult);
|
|
248
|
+
return reviewResult;
|
|
249
|
+
}
|
|
250
|
+
catch (error) {
|
|
251
|
+
this.sessionManager.failSession(sessionId, error);
|
|
252
|
+
console.error("\n❌ Review failed:", error.message);
|
|
253
|
+
throw error;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* Enhance PR description only (without full review)
|
|
258
|
+
*/
|
|
259
|
+
async enhanceDescription(request) {
|
|
260
|
+
await this.ensureInitialized();
|
|
261
|
+
const sessionId = this.sessionManager.createSession(request);
|
|
262
|
+
try {
|
|
263
|
+
console.log("\n📝 Enhancing PR description...\n");
|
|
264
|
+
const instructions = await this.promptBuilder.buildDescriptionEnhancementInstructions(request, this.config);
|
|
265
|
+
const toolContext = this.createToolContext(sessionId, request);
|
|
266
|
+
this.neurolink.setToolContext(toolContext);
|
|
267
|
+
const aiResponse = await this.neurolink.generate({
|
|
268
|
+
input: { text: instructions },
|
|
269
|
+
provider: this.config.ai.provider,
|
|
270
|
+
model: this.config.ai.model,
|
|
271
|
+
context: {
|
|
272
|
+
sessionId,
|
|
273
|
+
userId: this.generateUserId(request),
|
|
274
|
+
operation: "description-enhancement",
|
|
275
|
+
},
|
|
276
|
+
enableAnalytics: true,
|
|
277
|
+
});
|
|
278
|
+
console.log("✅ Description enhanced successfully\n");
|
|
279
|
+
return {
|
|
280
|
+
success: true,
|
|
281
|
+
enhanced: true,
|
|
282
|
+
sessionId,
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
catch (error) {
|
|
286
|
+
this.sessionManager.failSession(sessionId, error);
|
|
287
|
+
throw error;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* Get session information
|
|
292
|
+
*/
|
|
293
|
+
getSession(sessionId) {
|
|
294
|
+
return this.sessionManager.getSession(sessionId);
|
|
295
|
+
}
|
|
296
|
+
/**
|
|
297
|
+
* Get session statistics
|
|
298
|
+
*/
|
|
299
|
+
getSessionStats(sessionId) {
|
|
300
|
+
return this.sessionManager.getSessionStats(sessionId);
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* Export session data
|
|
304
|
+
*/
|
|
305
|
+
exportSession(sessionId) {
|
|
306
|
+
return this.sessionManager.exportSession(sessionId);
|
|
307
|
+
}
|
|
308
|
+
/**
|
|
309
|
+
* Create tool context for AI
|
|
310
|
+
*/
|
|
311
|
+
createToolContext(sessionId, request) {
|
|
312
|
+
return {
|
|
313
|
+
sessionId,
|
|
314
|
+
workspace: request.workspace,
|
|
315
|
+
repository: request.repository,
|
|
316
|
+
pullRequestId: request.pullRequestId,
|
|
317
|
+
branch: request.branch,
|
|
318
|
+
dryRun: request.dryRun || false,
|
|
319
|
+
metadata: {
|
|
320
|
+
yamaVersion: "2.0.0",
|
|
321
|
+
startTime: new Date().toISOString(),
|
|
322
|
+
},
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
/**
|
|
326
|
+
* Parse AI response into structured review result
|
|
327
|
+
*/
|
|
328
|
+
parseReviewResult(aiResponse, startTime, sessionId) {
|
|
329
|
+
const session = this.sessionManager.getSession(sessionId);
|
|
330
|
+
const duration = Math.round((Date.now() - startTime) / 1000);
|
|
331
|
+
// Extract decision from AI response or tool calls
|
|
332
|
+
const decision = this.extractDecision(aiResponse, session);
|
|
333
|
+
// Calculate statistics from session tool calls
|
|
334
|
+
const statistics = this.calculateStatistics(session);
|
|
335
|
+
return {
|
|
336
|
+
prId: session.request.pullRequestId || 0,
|
|
337
|
+
decision,
|
|
338
|
+
statistics,
|
|
339
|
+
summary: this.extractSummary(aiResponse),
|
|
340
|
+
duration,
|
|
341
|
+
tokenUsage: {
|
|
342
|
+
input: aiResponse.usage?.inputTokens || 0,
|
|
343
|
+
output: aiResponse.usage?.outputTokens || 0,
|
|
344
|
+
total: aiResponse.usage?.totalTokens || 0,
|
|
345
|
+
},
|
|
346
|
+
costEstimate: this.calculateCost(aiResponse.usage),
|
|
347
|
+
sessionId,
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
/**
|
|
351
|
+
* Extract decision from AI response
|
|
352
|
+
*/
|
|
353
|
+
extractDecision(aiResponse, session) {
|
|
354
|
+
// Check if AI called approve_pull_request or request_changes
|
|
355
|
+
const toolCalls = session.toolCalls || [];
|
|
356
|
+
const approveCall = toolCalls.find((tc) => tc.toolName === "approve_pull_request");
|
|
357
|
+
const requestChangesCall = toolCalls.find((tc) => tc.toolName === "request_changes");
|
|
358
|
+
if (approveCall) {
|
|
359
|
+
return "APPROVED";
|
|
360
|
+
}
|
|
361
|
+
if (requestChangesCall) {
|
|
362
|
+
return "BLOCKED";
|
|
363
|
+
}
|
|
364
|
+
// Default to changes requested if unclear
|
|
365
|
+
return "CHANGES_REQUESTED";
|
|
366
|
+
}
|
|
367
|
+
/**
|
|
368
|
+
* Calculate statistics from session
|
|
369
|
+
*/
|
|
370
|
+
calculateStatistics(session) {
|
|
371
|
+
const toolCalls = session.toolCalls || [];
|
|
372
|
+
// Count file diffs read
|
|
373
|
+
const filesReviewed = toolCalls.filter((tc) => tc.toolName === "get_pull_request_diff").length;
|
|
374
|
+
// Try to extract issue counts from comments
|
|
375
|
+
const commentCalls = toolCalls.filter((tc) => tc.toolName === "add_comment");
|
|
376
|
+
const issuesFound = this.extractIssueCountsFromComments(commentCalls);
|
|
377
|
+
return {
|
|
378
|
+
filesReviewed,
|
|
379
|
+
issuesFound,
|
|
380
|
+
requirementCoverage: 0, // Would need to parse from Jira comparison
|
|
381
|
+
codeQualityScore: 0, // Would need AI to provide this
|
|
382
|
+
toolCallsMade: toolCalls.length,
|
|
383
|
+
cacheHits: 0,
|
|
384
|
+
totalComments: commentCalls.length,
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
/**
|
|
388
|
+
* Extract issue counts from comment tool calls
|
|
389
|
+
*/
|
|
390
|
+
extractIssueCountsFromComments(commentCalls) {
|
|
391
|
+
const counts = {
|
|
392
|
+
critical: 0,
|
|
393
|
+
major: 0,
|
|
394
|
+
minor: 0,
|
|
395
|
+
suggestions: 0,
|
|
396
|
+
};
|
|
397
|
+
commentCalls.forEach((call) => {
|
|
398
|
+
const text = call.args?.comment_text || "";
|
|
399
|
+
if (text.includes("🔒 CRITICAL") || text.includes("CRITICAL:")) {
|
|
400
|
+
counts.critical++;
|
|
401
|
+
}
|
|
402
|
+
else if (text.includes("⚠️ MAJOR") || text.includes("MAJOR:")) {
|
|
403
|
+
counts.major++;
|
|
404
|
+
}
|
|
405
|
+
else if (text.includes("💡 MINOR") || text.includes("MINOR:")) {
|
|
406
|
+
counts.minor++;
|
|
407
|
+
}
|
|
408
|
+
else if (text.includes("💬 SUGGESTION") ||
|
|
409
|
+
text.includes("SUGGESTION:")) {
|
|
410
|
+
counts.suggestions++;
|
|
411
|
+
}
|
|
412
|
+
});
|
|
413
|
+
return counts;
|
|
414
|
+
}
|
|
415
|
+
/**
|
|
416
|
+
* Extract summary from AI response
|
|
417
|
+
*/
|
|
418
|
+
extractSummary(aiResponse) {
|
|
419
|
+
return aiResponse.content || aiResponse.text || "Review completed";
|
|
420
|
+
}
|
|
421
|
+
/**
|
|
422
|
+
* Calculate cost estimate from token usage
|
|
423
|
+
*/
|
|
424
|
+
calculateCost(usage) {
|
|
425
|
+
if (!usage) {
|
|
426
|
+
return 0;
|
|
427
|
+
}
|
|
428
|
+
// Rough estimates (update with actual pricing)
|
|
429
|
+
const inputCostPer1M = 0.25; // $0.25 per 1M input tokens (Gemini 2.0 Flash)
|
|
430
|
+
const outputCostPer1M = 1.0; // $1.00 per 1M output tokens
|
|
431
|
+
const inputCost = (usage.inputTokens / 1_000_000) * inputCostPer1M;
|
|
432
|
+
const outputCost = (usage.outputTokens / 1_000_000) * outputCostPer1M;
|
|
433
|
+
return Number((inputCost + outputCost).toFixed(4));
|
|
434
|
+
}
|
|
435
|
+
/**
|
|
436
|
+
* Generate userId for NeuroLink context from repository and branch/PR
|
|
437
|
+
*/
|
|
438
|
+
generateUserId(request) {
|
|
439
|
+
const repo = request.repository;
|
|
440
|
+
const identifier = request.branch || `pr-${request.pullRequestId}`;
|
|
441
|
+
return `${repo}-${identifier}`;
|
|
442
|
+
}
|
|
443
|
+
/**
|
|
444
|
+
* Initialize NeuroLink with observability configuration
|
|
445
|
+
*/
|
|
446
|
+
initializeNeurolink() {
|
|
447
|
+
try {
|
|
448
|
+
const observabilityConfig = buildObservabilityConfigFromEnv();
|
|
449
|
+
const neurolinkConfig = {
|
|
450
|
+
conversationMemory: this.config.ai.conversationMemory,
|
|
451
|
+
};
|
|
452
|
+
if (observabilityConfig) {
|
|
453
|
+
// Validate observability config
|
|
454
|
+
if (!validateObservabilityConfig(observabilityConfig)) {
|
|
455
|
+
throw new Error("Invalid observability configuration");
|
|
456
|
+
}
|
|
457
|
+
neurolinkConfig.observability = observabilityConfig;
|
|
458
|
+
console.log(" 📊 Observability enabled (Langfuse tracing active)");
|
|
459
|
+
}
|
|
460
|
+
else {
|
|
461
|
+
console.log(" 📊 Observability not configured (set LANGFUSE_* env vars to enable)");
|
|
462
|
+
}
|
|
463
|
+
const neurolink = new NeuroLink(neurolinkConfig);
|
|
464
|
+
return neurolink;
|
|
465
|
+
}
|
|
466
|
+
catch (error) {
|
|
467
|
+
console.error("❌ Failed to initialize NeuroLink:", error.message);
|
|
468
|
+
throw new Error(`NeuroLink initialization failed: ${error}`);
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
/**
|
|
472
|
+
* Ensure orchestrator is initialized
|
|
473
|
+
*/
|
|
474
|
+
async ensureInitialized() {
|
|
475
|
+
if (!this.initialized) {
|
|
476
|
+
await this.initialize();
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
/**
|
|
480
|
+
* Show Yama V2 banner
|
|
481
|
+
*/
|
|
482
|
+
showBanner() {
|
|
483
|
+
if (!this.config?.display?.showBanner) {
|
|
484
|
+
return;
|
|
485
|
+
}
|
|
486
|
+
console.log("\n" + "═".repeat(60));
|
|
487
|
+
console.log(`
|
|
488
|
+
⚔️ YAMA V2 - AI-Native Code Review Guardian
|
|
489
|
+
|
|
490
|
+
Version: 2.0.0
|
|
491
|
+
Mode: Autonomous AI-Powered Review
|
|
492
|
+
Powered by: NeuroLink + MCP Tools
|
|
493
|
+
`);
|
|
494
|
+
console.log("═".repeat(60) + "\n");
|
|
495
|
+
}
|
|
496
|
+
/**
|
|
497
|
+
* Log review start
|
|
498
|
+
*/
|
|
499
|
+
logReviewStart(request, sessionId) {
|
|
500
|
+
console.log("\n" + "─".repeat(60));
|
|
501
|
+
console.log(`📋 Review Session Started`);
|
|
502
|
+
console.log("─".repeat(60));
|
|
503
|
+
console.log(` Session ID: ${sessionId}`);
|
|
504
|
+
console.log(` Workspace: ${request.workspace}`);
|
|
505
|
+
console.log(` Repository: ${request.repository}`);
|
|
506
|
+
console.log(` PR: ${request.pullRequestId || request.branch}`);
|
|
507
|
+
console.log(` Mode: ${request.dryRun ? "🔵 DRY RUN" : "🔴 LIVE"}`);
|
|
508
|
+
console.log("─".repeat(60) + "\n");
|
|
509
|
+
}
|
|
510
|
+
/**
|
|
511
|
+
* Log review completion
|
|
512
|
+
*/
|
|
513
|
+
logReviewComplete(result) {
|
|
514
|
+
console.log("\n" + "═".repeat(60));
|
|
515
|
+
console.log(`✅ Review Completed Successfully`);
|
|
516
|
+
console.log("═".repeat(60));
|
|
517
|
+
console.log(` Decision: ${this.formatDecision(result.decision)}`);
|
|
518
|
+
console.log(` Duration: ${result.duration}s`);
|
|
519
|
+
console.log(` Files Reviewed: ${result.statistics.filesReviewed}`);
|
|
520
|
+
console.log(` Issues Found:`);
|
|
521
|
+
console.log(` 🔒 CRITICAL: ${result.statistics.issuesFound.critical}`);
|
|
522
|
+
console.log(` ⚠️ MAJOR: ${result.statistics.issuesFound.major}`);
|
|
523
|
+
console.log(` 💡 MINOR: ${result.statistics.issuesFound.minor}`);
|
|
524
|
+
console.log(` 💬 SUGGESTIONS: ${result.statistics.issuesFound.suggestions}`);
|
|
525
|
+
console.log(` Token Usage: ${result.tokenUsage.total.toLocaleString()}`);
|
|
526
|
+
console.log(` Cost Estimate: $${result.costEstimate.toFixed(4)}`);
|
|
527
|
+
console.log("═".repeat(60) + "\n");
|
|
528
|
+
}
|
|
529
|
+
/**
|
|
530
|
+
* Format decision for display
|
|
531
|
+
*/
|
|
532
|
+
formatDecision(decision) {
|
|
533
|
+
switch (decision) {
|
|
534
|
+
case "APPROVED":
|
|
535
|
+
return "✅ APPROVED";
|
|
536
|
+
case "BLOCKED":
|
|
537
|
+
return "🚫 BLOCKED";
|
|
538
|
+
case "CHANGES_REQUESTED":
|
|
539
|
+
return "⚠️ CHANGES REQUESTED";
|
|
540
|
+
default:
|
|
541
|
+
return decision;
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
// Export factory function
|
|
546
|
+
export function createYamaV2() {
|
|
547
|
+
return new YamaV2Orchestrator();
|
|
548
|
+
}
|
|
549
|
+
//# sourceMappingURL=YamaV2Orchestrator.js.map
|
|
@@ -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
|