@juspay/yama 2.3.0 → 2.4.1
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/CHANGELOG.md +14 -0
- package/dist/v2/config/ConfigLoader.js +38 -4
- package/dist/v2/config/DefaultConfig.js +9 -0
- package/dist/v2/core/SessionManager.d.ts +3 -1
- package/dist/v2/core/SessionManager.js +30 -0
- package/dist/v2/core/YamaV2Orchestrator.d.ts +14 -0
- package/dist/v2/core/YamaV2Orchestrator.js +181 -15
- package/dist/v2/exploration/ContextExplorerService.d.ts +39 -0
- package/dist/v2/exploration/ContextExplorerService.js +451 -0
- package/dist/v2/exploration/ExplorerPromptBuilder.d.ts +5 -0
- package/dist/v2/exploration/ExplorerPromptBuilder.js +67 -0
- package/dist/v2/exploration/RulesContextLoader.d.ts +12 -0
- package/dist/v2/exploration/RulesContextLoader.js +106 -0
- package/dist/v2/exploration/types.d.ts +26 -0
- package/dist/v2/exploration/types.js +2 -0
- package/dist/v2/memory/MemoryManager.d.ts +8 -0
- package/dist/v2/memory/MemoryManager.js +22 -0
- package/dist/v2/prompts/LangfusePromptManager.js +6 -0
- package/dist/v2/prompts/PromptBuilder.d.ts +18 -1
- package/dist/v2/prompts/PromptBuilder.js +131 -29
- package/dist/v2/prompts/ReviewSystemPrompt.d.ts +9 -4
- package/dist/v2/prompts/ReviewSystemPrompt.js +46 -239
- package/dist/v2/types/config.types.d.ts +19 -1
- package/dist/v2/types/v2.types.d.ts +28 -0
- package/package.json +4 -3
- package/yama.config.example.yaml +10 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
## [2.4.1](https://github.com/juspay/yama/compare/v2.4.0...v2.4.1) (2026-04-16)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* **deps:** pin undici to 5.x for Node 20 compatibility ([c8f92de](https://github.com/juspay/yama/commit/c8f92de3009f20f451d1848a568912d4c752a024))
|
|
7
|
+
|
|
8
|
+
# [2.4.0](https://github.com/juspay/yama/compare/v2.3.0...v2.4.0) (2026-04-15)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Features
|
|
12
|
+
|
|
13
|
+
* **v2:** bootstrap repo standards from recent PRs before review ([8c5b1cd](https://github.com/juspay/yama/commit/8c5b1cdd38b684397e7445e04b4db4bc45b5f2a8))
|
|
14
|
+
|
|
1
15
|
# [2.3.0](https://github.com/juspay/yama/compare/v2.2.2...v2.3.0) (2026-04-01)
|
|
2
16
|
|
|
3
17
|
|
|
@@ -180,13 +180,19 @@ export class ConfigLoader {
|
|
|
180
180
|
if (process.env.AI_MODEL) {
|
|
181
181
|
config.ai.model = process.env.AI_MODEL;
|
|
182
182
|
}
|
|
183
|
-
// Override temperature if env var set
|
|
183
|
+
// Override temperature if env var set (ignore non-finite values)
|
|
184
184
|
if (process.env.AI_TEMPERATURE) {
|
|
185
|
-
|
|
185
|
+
const temperature = Number(process.env.AI_TEMPERATURE);
|
|
186
|
+
if (Number.isFinite(temperature)) {
|
|
187
|
+
config.ai.temperature = temperature;
|
|
188
|
+
}
|
|
186
189
|
}
|
|
187
|
-
// Override max tokens if env var set
|
|
190
|
+
// Override max tokens if env var set (ignore non-integer / non-positive values)
|
|
188
191
|
if (process.env.AI_MAX_TOKENS) {
|
|
189
|
-
|
|
192
|
+
const maxTokens = Number.parseInt(process.env.AI_MAX_TOKENS, 10);
|
|
193
|
+
if (Number.isInteger(maxTokens) && maxTokens > 0) {
|
|
194
|
+
config.ai.maxTokens = maxTokens;
|
|
195
|
+
}
|
|
190
196
|
}
|
|
191
197
|
if (process.env.AI_ENABLE_TOOL_FILTERING) {
|
|
192
198
|
config.ai.enableToolFiltering =
|
|
@@ -198,6 +204,34 @@ export class ConfigLoader {
|
|
|
198
204
|
config.ai.toolFilteringMode = mode;
|
|
199
205
|
}
|
|
200
206
|
}
|
|
207
|
+
if (process.env.AI_EXPLORE_ENABLED) {
|
|
208
|
+
config.ai.explore.enabled = process.env.AI_EXPLORE_ENABLED === "true";
|
|
209
|
+
}
|
|
210
|
+
if (process.env.AI_EXPLORE_PROVIDER) {
|
|
211
|
+
config.ai.explore.provider = process.env.AI_EXPLORE_PROVIDER;
|
|
212
|
+
}
|
|
213
|
+
if (process.env.AI_EXPLORE_MODEL) {
|
|
214
|
+
config.ai.explore.model = process.env.AI_EXPLORE_MODEL;
|
|
215
|
+
}
|
|
216
|
+
if (process.env.AI_EXPLORE_TEMPERATURE) {
|
|
217
|
+
const temperature = Number(process.env.AI_EXPLORE_TEMPERATURE);
|
|
218
|
+
if (Number.isFinite(temperature)) {
|
|
219
|
+
config.ai.explore.temperature = temperature;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
if (process.env.AI_EXPLORE_MAX_TOKENS) {
|
|
223
|
+
const maxTokens = Number.parseInt(process.env.AI_EXPLORE_MAX_TOKENS, 10);
|
|
224
|
+
if (Number.isInteger(maxTokens) && maxTokens > 0) {
|
|
225
|
+
config.ai.explore.maxTokens = maxTokens;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
if (process.env.AI_EXPLORE_TIMEOUT) {
|
|
229
|
+
config.ai.explore.timeout = process.env.AI_EXPLORE_TIMEOUT;
|
|
230
|
+
}
|
|
231
|
+
if (process.env.AI_EXPLORE_CACHE_RESULTS) {
|
|
232
|
+
config.ai.explore.cacheResults =
|
|
233
|
+
process.env.AI_EXPLORE_CACHE_RESULTS === "true";
|
|
234
|
+
}
|
|
201
235
|
return config;
|
|
202
236
|
}
|
|
203
237
|
/**
|
|
@@ -31,6 +31,15 @@ export class DefaultConfig {
|
|
|
31
31
|
maxTurnsPerSession: 300,
|
|
32
32
|
enableSummarization: false,
|
|
33
33
|
},
|
|
34
|
+
explore: {
|
|
35
|
+
enabled: true,
|
|
36
|
+
provider: "auto",
|
|
37
|
+
model: "gemini-2.5-flash",
|
|
38
|
+
temperature: 0.1,
|
|
39
|
+
maxTokens: 32000,
|
|
40
|
+
timeout: "5m",
|
|
41
|
+
cacheResults: true,
|
|
42
|
+
},
|
|
34
43
|
},
|
|
35
44
|
mcpServers: {
|
|
36
45
|
bitbucket: {
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Session Manager for Yama
|
|
3
3
|
* Tracks review sessions, tool calls, and maintains state
|
|
4
4
|
*/
|
|
5
|
-
import { ReviewSession, ReviewRequest, ReviewResult, SessionMetadata } from "../types/v2.types.js";
|
|
5
|
+
import { ReviewSession, ReviewRequest, ReviewResult, SessionMetadata, ExplorationResult } from "../types/v2.types.js";
|
|
6
6
|
export declare class SessionManager {
|
|
7
7
|
private sessions;
|
|
8
8
|
private maxSessions;
|
|
@@ -22,6 +22,8 @@ export declare class SessionManager {
|
|
|
22
22
|
* Update session metadata
|
|
23
23
|
*/
|
|
24
24
|
updateMetadata(sessionId: string, updates: Partial<SessionMetadata>): void;
|
|
25
|
+
recordExploration(sessionId: string, cacheKey: string, task: string, focus: string[], result: ExplorationResult, cached?: boolean): void;
|
|
26
|
+
findExploration(sessionId: string, cacheKey: string): ExplorationResult | null;
|
|
25
27
|
/**
|
|
26
28
|
* Mark session as completed
|
|
27
29
|
*/
|
|
@@ -25,6 +25,7 @@ export class SessionManager {
|
|
|
25
25
|
totalCost: 0,
|
|
26
26
|
cacheHitRatio: 0,
|
|
27
27
|
},
|
|
28
|
+
explorations: [],
|
|
28
29
|
};
|
|
29
30
|
this.sessions.set(sessionId, session);
|
|
30
31
|
// Clean up old sessions if we exceed max
|
|
@@ -68,6 +69,26 @@ export class SessionManager {
|
|
|
68
69
|
...updates,
|
|
69
70
|
};
|
|
70
71
|
}
|
|
72
|
+
recordExploration(sessionId, cacheKey, task, focus, result, cached = false) {
|
|
73
|
+
const session = this.getSession(sessionId);
|
|
74
|
+
session.explorations = session.explorations || [];
|
|
75
|
+
session.explorations.push({
|
|
76
|
+
task,
|
|
77
|
+
cacheKey,
|
|
78
|
+
focus,
|
|
79
|
+
result,
|
|
80
|
+
createdAt: new Date(),
|
|
81
|
+
cached,
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
findExploration(sessionId, cacheKey) {
|
|
85
|
+
const session = this.getSession(sessionId);
|
|
86
|
+
const match = (session.explorations || [])
|
|
87
|
+
.slice()
|
|
88
|
+
.reverse()
|
|
89
|
+
.find((exploration) => exploration.cacheKey === cacheKey);
|
|
90
|
+
return match?.result || null;
|
|
91
|
+
}
|
|
71
92
|
/**
|
|
72
93
|
* Mark session as completed
|
|
73
94
|
*/
|
|
@@ -166,6 +187,15 @@ export class SessionManager {
|
|
|
166
187
|
// Don't include full result in export (can be very large)
|
|
167
188
|
resultSummary: this.summarizeToolResult(tc.result),
|
|
168
189
|
})),
|
|
190
|
+
explorations: (session.explorations || []).map((exploration) => ({
|
|
191
|
+
task: exploration.task,
|
|
192
|
+
cacheKey: exploration.cacheKey,
|
|
193
|
+
focus: exploration.focus,
|
|
194
|
+
createdAt: exploration.createdAt.toISOString(),
|
|
195
|
+
cached: exploration.cached,
|
|
196
|
+
// Don't include full result in export (can be very large / may contain sensitive data)
|
|
197
|
+
resultSummary: this.summarizeToolResult(exploration.result),
|
|
198
|
+
})),
|
|
169
199
|
};
|
|
170
200
|
}
|
|
171
201
|
/**
|
|
@@ -6,6 +6,7 @@ import { LocalReviewRequest, LocalReviewResult, ReviewRequest, ReviewResult, Rev
|
|
|
6
6
|
import { YamaInitOptions } from "../types/config.types.js";
|
|
7
7
|
export declare class YamaOrchestrator {
|
|
8
8
|
private neurolink;
|
|
9
|
+
private explorer;
|
|
9
10
|
private mcpManager;
|
|
10
11
|
private configLoader;
|
|
11
12
|
private promptBuilder;
|
|
@@ -16,7 +17,10 @@ export declare class YamaOrchestrator {
|
|
|
16
17
|
private initialized;
|
|
17
18
|
private mcpInitialized;
|
|
18
19
|
private localGitMcpInitialized;
|
|
20
|
+
private exploreToolRegistered;
|
|
21
|
+
private currentToolContext;
|
|
19
22
|
private initOptions;
|
|
23
|
+
private bootstrapStandardsCache;
|
|
20
24
|
constructor(options?: YamaInitOptions);
|
|
21
25
|
/**
|
|
22
26
|
* Initialize Yama with configuration and MCP servers
|
|
@@ -67,6 +71,8 @@ export declare class YamaOrchestrator {
|
|
|
67
71
|
exportSession(sessionId: string): any;
|
|
68
72
|
private getUserId;
|
|
69
73
|
private createToolContext;
|
|
74
|
+
private createLocalToolContext;
|
|
75
|
+
private setToolContext;
|
|
70
76
|
/**
|
|
71
77
|
* Parse AI response into structured review result
|
|
72
78
|
*/
|
|
@@ -120,6 +126,14 @@ export declare class YamaOrchestrator {
|
|
|
120
126
|
* Initialize NeuroLink with observability configuration
|
|
121
127
|
*/
|
|
122
128
|
private initializeNeurolink;
|
|
129
|
+
private registerExploreTool;
|
|
130
|
+
/**
|
|
131
|
+
* Bootstrap repo-level standards by delegating to explore_context.
|
|
132
|
+
* Runs once per (workspace/repository) per process lifetime.
|
|
133
|
+
* Output is injected into the review prompt as <bootstrapped-standards>.
|
|
134
|
+
* Graceful: any failure returns empty and the review proceeds without it.
|
|
135
|
+
*/
|
|
136
|
+
private getBootstrappedStandards;
|
|
123
137
|
/**
|
|
124
138
|
* Ensure orchestrator is initialized
|
|
125
139
|
*/
|
|
@@ -9,9 +9,11 @@ import { PromptBuilder } from "../prompts/PromptBuilder.js";
|
|
|
9
9
|
import { SessionManager } from "./SessionManager.js";
|
|
10
10
|
import { MemoryManager } from "../memory/MemoryManager.js";
|
|
11
11
|
import { LocalDiffSource } from "./LocalDiffSource.js";
|
|
12
|
+
import { ContextExplorerService } from "../exploration/ContextExplorerService.js";
|
|
12
13
|
import { buildObservabilityConfigFromEnv, validateObservabilityConfig, } from "../utils/ObservabilityConfig.js";
|
|
13
14
|
export class YamaOrchestrator {
|
|
14
15
|
neurolink;
|
|
16
|
+
explorer = null;
|
|
15
17
|
mcpManager;
|
|
16
18
|
configLoader;
|
|
17
19
|
promptBuilder;
|
|
@@ -22,7 +24,10 @@ export class YamaOrchestrator {
|
|
|
22
24
|
initialized = false;
|
|
23
25
|
mcpInitialized = false;
|
|
24
26
|
localGitMcpInitialized = false;
|
|
27
|
+
exploreToolRegistered = false;
|
|
28
|
+
currentToolContext = null;
|
|
25
29
|
initOptions;
|
|
30
|
+
bootstrapStandardsCache = new Map();
|
|
26
31
|
constructor(options = {}) {
|
|
27
32
|
this.initOptions = options;
|
|
28
33
|
this.configLoader = new ConfigLoader();
|
|
@@ -50,16 +55,24 @@ export class YamaOrchestrator {
|
|
|
50
55
|
// Step 3: Initialize NeuroLink with memory config injected
|
|
51
56
|
console.log("🧠 Initializing NeuroLink AI engine...");
|
|
52
57
|
this.neurolink = this.initializeNeurolink();
|
|
58
|
+
this.explorer = new ContextExplorerService(this.config, this.sessionManager, this.memoryManager);
|
|
59
|
+
this.registerExploreTool();
|
|
53
60
|
console.log("✅ NeuroLink initialized\n");
|
|
54
61
|
this.initialized = true;
|
|
55
62
|
}
|
|
56
63
|
// Step 4: Mode-specific setup
|
|
57
64
|
if (mode === "pr" && !this.mcpInitialized) {
|
|
58
65
|
await this.mcpManager.setupMCPServers(this.neurolink, this.config.mcpServers);
|
|
66
|
+
if (this.explorer && this.config.ai.explore.enabled) {
|
|
67
|
+
await this.explorer.initialize("pr");
|
|
68
|
+
}
|
|
59
69
|
this.mcpInitialized = true;
|
|
60
70
|
}
|
|
61
71
|
else if (mode === "local" && !this.localGitMcpInitialized) {
|
|
62
72
|
await this.mcpManager.setupLocalGitMCPServer(this.neurolink);
|
|
73
|
+
if (this.explorer && this.config.ai.explore.enabled) {
|
|
74
|
+
await this.explorer.initialize("local");
|
|
75
|
+
}
|
|
63
76
|
this.localGitMcpInitialized = true;
|
|
64
77
|
}
|
|
65
78
|
// Step 5: Mode-specific validation
|
|
@@ -81,8 +94,9 @@ export class YamaOrchestrator {
|
|
|
81
94
|
const sessionId = this.sessionManager.createSession(request);
|
|
82
95
|
this.logReviewStart(request, sessionId);
|
|
83
96
|
try {
|
|
97
|
+
const bootstrapStandards = await this.getBootstrappedStandards(request, sessionId);
|
|
84
98
|
// Build comprehensive AI instructions
|
|
85
|
-
const instructions = await this.promptBuilder.buildReviewInstructions(request, this.config);
|
|
99
|
+
const instructions = await this.promptBuilder.buildReviewInstructions(request, this.config, bootstrapStandards);
|
|
86
100
|
if (this.config.display.verboseToolCalls) {
|
|
87
101
|
console.log("\n📝 AI Instructions built:");
|
|
88
102
|
console.log(` Instruction length: ${instructions.length} characters\n`);
|
|
@@ -90,7 +104,7 @@ export class YamaOrchestrator {
|
|
|
90
104
|
// Create tool context for AI
|
|
91
105
|
const toolContext = this.createToolContext(sessionId, request);
|
|
92
106
|
// Set tool context in NeuroLink (using type assertion as setToolContext is documented but may not be in type definitions)
|
|
93
|
-
this.
|
|
107
|
+
this.setToolContext(toolContext);
|
|
94
108
|
// Update session metadata
|
|
95
109
|
this.sessionManager.updateMetadata(sessionId, {
|
|
96
110
|
aiProvider: this.config.ai.provider,
|
|
@@ -161,19 +175,14 @@ export class YamaOrchestrator {
|
|
|
161
175
|
try {
|
|
162
176
|
const diffContext = this.localDiffSource.getDiffContext(request);
|
|
163
177
|
const instructions = await this.promptBuilder.buildLocalReviewInstructions(request, this.config, diffContext);
|
|
178
|
+
const toolContext = this.createLocalToolContext(sessionId, pseudoRequest, diffContext);
|
|
179
|
+
this.setToolContext(toolContext);
|
|
164
180
|
const aiResponse = await this.neurolink.generate({
|
|
165
181
|
input: { text: instructions },
|
|
166
182
|
provider: this.config.ai.provider,
|
|
167
183
|
model: this.config.ai.model,
|
|
168
184
|
temperature: this.config.ai.temperature,
|
|
169
185
|
maxTokens: Math.min(this.config.ai.maxTokens, 16_000),
|
|
170
|
-
maxSteps: 100,
|
|
171
|
-
prepareStep: async ({ stepNumber }) => {
|
|
172
|
-
if (stepNumber >= 5) {
|
|
173
|
-
return { toolChoice: "none" };
|
|
174
|
-
}
|
|
175
|
-
return undefined;
|
|
176
|
-
},
|
|
177
186
|
timeout: this.config.ai.timeout,
|
|
178
187
|
enableAnalytics: this.config.ai.enableAnalytics,
|
|
179
188
|
enableEvaluation: this.config.ai.enableEvaluation,
|
|
@@ -184,7 +193,7 @@ export class YamaOrchestrator {
|
|
|
184
193
|
...this.getLocalToolFilteringOptions(),
|
|
185
194
|
context: {
|
|
186
195
|
sessionId,
|
|
187
|
-
userId:
|
|
196
|
+
userId: this.getUserId(pseudoRequest),
|
|
188
197
|
operation: "local-review",
|
|
189
198
|
metadata: {
|
|
190
199
|
repoPath: diffContext.repoPath,
|
|
@@ -212,11 +221,12 @@ export class YamaOrchestrator {
|
|
|
212
221
|
await this.ensureInitialized("pr", request.configPath);
|
|
213
222
|
const sessionId = this.sessionManager.createSession(request);
|
|
214
223
|
try {
|
|
224
|
+
const bootstrapStandards = await this.getBootstrappedStandards(request, sessionId);
|
|
215
225
|
// Build instructions
|
|
216
|
-
const instructions = await this.promptBuilder.buildReviewInstructions(request, this.config);
|
|
226
|
+
const instructions = await this.promptBuilder.buildReviewInstructions(request, this.config, bootstrapStandards);
|
|
217
227
|
// Create tool context
|
|
218
228
|
const toolContext = this.createToolContext(sessionId, request);
|
|
219
|
-
this.
|
|
229
|
+
this.setToolContext(toolContext);
|
|
220
230
|
// Stream AI execution
|
|
221
231
|
yield {
|
|
222
232
|
type: "progress",
|
|
@@ -272,15 +282,16 @@ export class YamaOrchestrator {
|
|
|
272
282
|
// ========================================================================
|
|
273
283
|
// PHASE 1: Code Review
|
|
274
284
|
// ========================================================================
|
|
285
|
+
const bootstrapStandards = await this.getBootstrappedStandards(request, sessionId);
|
|
275
286
|
// Build review instructions
|
|
276
|
-
const reviewInstructions = await this.promptBuilder.buildReviewInstructions(request, this.config);
|
|
287
|
+
const reviewInstructions = await this.promptBuilder.buildReviewInstructions(request, this.config, bootstrapStandards);
|
|
277
288
|
if (this.config.display.verboseToolCalls) {
|
|
278
289
|
console.log("\n📝 Review instructions built:");
|
|
279
290
|
console.log(` Instruction length: ${reviewInstructions.length} characters\n`);
|
|
280
291
|
}
|
|
281
292
|
// Create tool context
|
|
282
293
|
const toolContext = this.createToolContext(sessionId, request);
|
|
283
|
-
this.
|
|
294
|
+
this.setToolContext(toolContext);
|
|
284
295
|
// Update session metadata
|
|
285
296
|
this.sessionManager.updateMetadata(sessionId, {
|
|
286
297
|
aiProvider: this.config.ai.provider,
|
|
@@ -371,7 +382,7 @@ export class YamaOrchestrator {
|
|
|
371
382
|
console.log("\n📝 Enhancing PR description...\n");
|
|
372
383
|
const instructions = await this.promptBuilder.buildDescriptionEnhancementInstructions(request, this.config);
|
|
373
384
|
const toolContext = this.createToolContext(sessionId, request);
|
|
374
|
-
this.
|
|
385
|
+
this.setToolContext(toolContext);
|
|
375
386
|
const aiResponse = await this.neurolink.generate({
|
|
376
387
|
input: { text: instructions },
|
|
377
388
|
provider: this.config.ai.provider,
|
|
@@ -423,6 +434,7 @@ export class YamaOrchestrator {
|
|
|
423
434
|
createToolContext(sessionId, request) {
|
|
424
435
|
return {
|
|
425
436
|
sessionId,
|
|
437
|
+
mode: "pr",
|
|
426
438
|
workspace: request.workspace,
|
|
427
439
|
repository: request.repository,
|
|
428
440
|
pullRequestId: request.pullRequestId,
|
|
@@ -434,6 +446,27 @@ export class YamaOrchestrator {
|
|
|
434
446
|
},
|
|
435
447
|
};
|
|
436
448
|
}
|
|
449
|
+
createLocalToolContext(sessionId, request, diffContext) {
|
|
450
|
+
return {
|
|
451
|
+
sessionId,
|
|
452
|
+
mode: "local",
|
|
453
|
+
workspace: request.workspace,
|
|
454
|
+
repository: request.repository,
|
|
455
|
+
dryRun: request.dryRun || false,
|
|
456
|
+
metadata: {
|
|
457
|
+
yamaVersion: "2.2.1",
|
|
458
|
+
startTime: new Date().toISOString(),
|
|
459
|
+
repoPath: diffContext.repoPath,
|
|
460
|
+
diffSource: diffContext.diffSource,
|
|
461
|
+
baseRef: diffContext.baseRef,
|
|
462
|
+
headRef: diffContext.headRef,
|
|
463
|
+
},
|
|
464
|
+
};
|
|
465
|
+
}
|
|
466
|
+
setToolContext(context) {
|
|
467
|
+
this.currentToolContext = context;
|
|
468
|
+
this.neurolink.setToolContext(context);
|
|
469
|
+
}
|
|
437
470
|
/**
|
|
438
471
|
* Parse AI response into structured review result
|
|
439
472
|
*/
|
|
@@ -946,6 +979,139 @@ export class YamaOrchestrator {
|
|
|
946
979
|
throw new Error(`NeuroLink initialization failed: ${error}`);
|
|
947
980
|
}
|
|
948
981
|
}
|
|
982
|
+
registerExploreTool() {
|
|
983
|
+
if (this.exploreToolRegistered || !this.config.ai.explore.enabled) {
|
|
984
|
+
return;
|
|
985
|
+
}
|
|
986
|
+
this.neurolink.registerTool("explore_context", {
|
|
987
|
+
name: "explore_context",
|
|
988
|
+
description: "Delegate non-trivial research to an isolated Explore worker. Use it for function definitions, project-wide search, logic tracing, commit history, rules/context lookup, or any analysis that would otherwise require broad manual scanning.",
|
|
989
|
+
inputSchema: {
|
|
990
|
+
type: "object",
|
|
991
|
+
properties: {
|
|
992
|
+
task: {
|
|
993
|
+
type: "string",
|
|
994
|
+
description: "The exact research task for Explore to investigate and answer.",
|
|
995
|
+
},
|
|
996
|
+
focus: {
|
|
997
|
+
type: "array",
|
|
998
|
+
description: "Optional list of files, symbols, commits, tickets, or context areas Explore should prioritize.",
|
|
999
|
+
items: {
|
|
1000
|
+
type: "string",
|
|
1001
|
+
},
|
|
1002
|
+
},
|
|
1003
|
+
},
|
|
1004
|
+
required: ["task"],
|
|
1005
|
+
},
|
|
1006
|
+
execute: async (params, context) => {
|
|
1007
|
+
if (!this.explorer) {
|
|
1008
|
+
throw new Error("Explore service is not initialized");
|
|
1009
|
+
}
|
|
1010
|
+
const rawParams = (params || {});
|
|
1011
|
+
const mergedContext = {
|
|
1012
|
+
...(this.currentToolContext || {}),
|
|
1013
|
+
...(context && typeof context === "object"
|
|
1014
|
+
? context
|
|
1015
|
+
: {}),
|
|
1016
|
+
};
|
|
1017
|
+
const runtimeMode = mergedContext.mode === "local" ? "local" : "pr";
|
|
1018
|
+
const runtimeContext = {
|
|
1019
|
+
sessionId: String(mergedContext.sessionId || this.currentToolContext?.sessionId || ""),
|
|
1020
|
+
mode: runtimeMode,
|
|
1021
|
+
workspace: String(mergedContext.workspace || "local"),
|
|
1022
|
+
repository: String(mergedContext.repository || "repository"),
|
|
1023
|
+
pullRequestId: typeof mergedContext.pullRequestId === "number"
|
|
1024
|
+
? mergedContext.pullRequestId
|
|
1025
|
+
: undefined,
|
|
1026
|
+
branch: typeof mergedContext.branch === "string"
|
|
1027
|
+
? mergedContext.branch
|
|
1028
|
+
: undefined,
|
|
1029
|
+
dryRun: typeof mergedContext.dryRun === "boolean"
|
|
1030
|
+
? mergedContext.dryRun
|
|
1031
|
+
: false,
|
|
1032
|
+
metadata: mergedContext.metadata &&
|
|
1033
|
+
typeof mergedContext.metadata === "object" &&
|
|
1034
|
+
!Array.isArray(mergedContext.metadata)
|
|
1035
|
+
? mergedContext.metadata
|
|
1036
|
+
: {},
|
|
1037
|
+
};
|
|
1038
|
+
const focus = Array.isArray(rawParams.focus)
|
|
1039
|
+
? rawParams.focus.filter((value) => typeof value === "string")
|
|
1040
|
+
: undefined;
|
|
1041
|
+
const { result, cached } = await this.explorer.explore({
|
|
1042
|
+
task: String(rawParams.task || "").trim(),
|
|
1043
|
+
focus,
|
|
1044
|
+
}, runtimeContext);
|
|
1045
|
+
return {
|
|
1046
|
+
success: true,
|
|
1047
|
+
data: {
|
|
1048
|
+
...result,
|
|
1049
|
+
cached,
|
|
1050
|
+
},
|
|
1051
|
+
};
|
|
1052
|
+
},
|
|
1053
|
+
});
|
|
1054
|
+
this.exploreToolRegistered = true;
|
|
1055
|
+
}
|
|
1056
|
+
/**
|
|
1057
|
+
* Bootstrap repo-level standards by delegating to explore_context.
|
|
1058
|
+
* Runs once per (workspace/repository) per process lifetime.
|
|
1059
|
+
* Output is injected into the review prompt as <bootstrapped-standards>.
|
|
1060
|
+
* Graceful: any failure returns empty and the review proceeds without it.
|
|
1061
|
+
*/
|
|
1062
|
+
async getBootstrappedStandards(request, sessionId) {
|
|
1063
|
+
if (!this.config.ai.explore.enabled || !this.explorer) {
|
|
1064
|
+
return "";
|
|
1065
|
+
}
|
|
1066
|
+
const cacheKey = `${request.workspace}/${request.repository}`.toLowerCase();
|
|
1067
|
+
const cached = this.bootstrapStandardsCache.get(cacheKey);
|
|
1068
|
+
if (cached !== undefined) {
|
|
1069
|
+
return cached;
|
|
1070
|
+
}
|
|
1071
|
+
const task = `Bootstrap repo review standards for ${request.workspace}/${request.repository}. Inspect the last 15 merged pull requests on this repository. For each, collect inline and summary comments left by HUMAN reviewers (ignore bot authors like "Yama", "yama-bot", "yama-review", "euler.bot"). Focus on comments that asked for code changes, flagged bugs, pushed back on an approach, or cited a convention. Distill the recurring patterns and anti-patterns the human reviewers care about — especially things that a static config rule set would miss (naming, error-handling idioms, module boundaries, test expectations, migration rules, review etiquette). Return a concise bullet list of 10-20 patterns. Each bullet: one sentence stating the pattern, optionally one sentence of rationale. Do NOT include PR numbers, author names, or raw quotes — just distilled patterns.`;
|
|
1072
|
+
try {
|
|
1073
|
+
console.log(` 🧭 Bootstrapping repo standards from recent PRs (one-time per process)...`);
|
|
1074
|
+
const { result } = await this.explorer.explore({ task, focus: [request.repository] }, {
|
|
1075
|
+
sessionId,
|
|
1076
|
+
mode: "pr",
|
|
1077
|
+
workspace: request.workspace,
|
|
1078
|
+
repository: request.repository,
|
|
1079
|
+
pullRequestId: request.pullRequestId,
|
|
1080
|
+
branch: request.branch,
|
|
1081
|
+
dryRun: request.dryRun || false,
|
|
1082
|
+
metadata: { bootstrap: true },
|
|
1083
|
+
});
|
|
1084
|
+
const parts = [];
|
|
1085
|
+
if (result.summary && result.summary.trim().length > 0) {
|
|
1086
|
+
parts.push(result.summary.trim());
|
|
1087
|
+
}
|
|
1088
|
+
if (result.findings && result.findings.length > 0) {
|
|
1089
|
+
const bullets = result.findings.map((f) => `- ${f.claim}`).join("\n");
|
|
1090
|
+
parts.push(bullets);
|
|
1091
|
+
}
|
|
1092
|
+
const combined = parts.join("\n\n").trim();
|
|
1093
|
+
if (combined.length > 0) {
|
|
1094
|
+
console.log(` ✅ Bootstrap standards ready (${combined.length} chars)`);
|
|
1095
|
+
if (this.config.display.verboseToolCalls) {
|
|
1096
|
+
console.log(" ───────── bootstrapped-standards ─────────");
|
|
1097
|
+
for (const line of combined.split("\n")) {
|
|
1098
|
+
console.log(` ${line}`);
|
|
1099
|
+
}
|
|
1100
|
+
console.log(" ──────────────────────────────────────────");
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
1103
|
+
else {
|
|
1104
|
+
console.log(` ⚠️ Bootstrap returned no standards — proceeding without them`);
|
|
1105
|
+
}
|
|
1106
|
+
this.bootstrapStandardsCache.set(cacheKey, combined);
|
|
1107
|
+
return combined;
|
|
1108
|
+
}
|
|
1109
|
+
catch (error) {
|
|
1110
|
+
console.warn(` ⚠️ Bootstrap standards failed, proceeding without: ${error.message}`);
|
|
1111
|
+
this.bootstrapStandardsCache.set(cacheKey, "");
|
|
1112
|
+
return "";
|
|
1113
|
+
}
|
|
1114
|
+
}
|
|
949
1115
|
/**
|
|
950
1116
|
* Ensure orchestrator is initialized
|
|
951
1117
|
*/
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { YamaConfig } from "../types/config.types.js";
|
|
2
|
+
import { SessionManager } from "../core/SessionManager.js";
|
|
3
|
+
import { MemoryManager } from "../memory/MemoryManager.js";
|
|
4
|
+
import { ExploreContextInput, ExploreExecutionResult, ExploreRuntimeContext } from "./types.js";
|
|
5
|
+
export declare class ContextExplorerService {
|
|
6
|
+
private readonly config;
|
|
7
|
+
private readonly sessionManager;
|
|
8
|
+
private readonly memoryManager;
|
|
9
|
+
private readonly projectRoot;
|
|
10
|
+
private readonly promptBuilder;
|
|
11
|
+
private readonly rulesContextLoader;
|
|
12
|
+
private readonly mcpManager;
|
|
13
|
+
private neurolink;
|
|
14
|
+
private prMcpInitialized;
|
|
15
|
+
private localMcpInitialized;
|
|
16
|
+
constructor(config: YamaConfig, sessionManager: SessionManager, memoryManager: MemoryManager | null, projectRoot?: string);
|
|
17
|
+
initialize(mode: "pr" | "local"): Promise<void>;
|
|
18
|
+
explore(input: ExploreContextInput, runtimeContext: ExploreRuntimeContext): Promise<ExploreExecutionResult>;
|
|
19
|
+
private normalizeInput;
|
|
20
|
+
private buildCacheKey;
|
|
21
|
+
private getToolFilteringOptions;
|
|
22
|
+
private shouldExcludeTool;
|
|
23
|
+
private normalizeToolName;
|
|
24
|
+
private normalizeResult;
|
|
25
|
+
private buildExtractionPrompt;
|
|
26
|
+
private truncateForExtraction;
|
|
27
|
+
private shouldCacheResult;
|
|
28
|
+
private normalizeFindings;
|
|
29
|
+
private normalizeEvidence;
|
|
30
|
+
private normalizeConfidence;
|
|
31
|
+
private normalizeSourceType;
|
|
32
|
+
private normalizeNextStep;
|
|
33
|
+
private extractJsonPayload;
|
|
34
|
+
private loadProjectStandards;
|
|
35
|
+
private loadKnowledgeBase;
|
|
36
|
+
private loadRepositoryMemory;
|
|
37
|
+
private initializeNeurolink;
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=ContextExplorerService.d.ts.map
|