@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.
@@ -0,0 +1,451 @@
1
+ import { readFile } from "fs/promises";
2
+ import { existsSync } from "fs";
3
+ import { join } from "path";
4
+ import { NeuroLink } from "@juspay/neurolink";
5
+ import { MCPServerManager } from "../core/MCPServerManager.js";
6
+ import { KnowledgeBaseManager } from "../learning/KnowledgeBaseManager.js";
7
+ import { buildObservabilityConfigFromEnv, validateObservabilityConfig, } from "../utils/ObservabilityConfig.js";
8
+ import { ExplorerPromptBuilder } from "./ExplorerPromptBuilder.js";
9
+ import { RulesContextLoader } from "./RulesContextLoader.js";
10
+ export class ContextExplorerService {
11
+ config;
12
+ sessionManager;
13
+ memoryManager;
14
+ projectRoot;
15
+ promptBuilder = new ExplorerPromptBuilder();
16
+ rulesContextLoader;
17
+ mcpManager = new MCPServerManager();
18
+ neurolink;
19
+ prMcpInitialized = false;
20
+ localMcpInitialized = false;
21
+ constructor(config, sessionManager, memoryManager, projectRoot = process.cwd()) {
22
+ this.config = config;
23
+ this.sessionManager = sessionManager;
24
+ this.memoryManager = memoryManager;
25
+ this.projectRoot = projectRoot;
26
+ this.rulesContextLoader = new RulesContextLoader(config.memoryBank, config.projectStandards, projectRoot);
27
+ this.neurolink = this.initializeNeurolink();
28
+ }
29
+ async initialize(mode) {
30
+ if (mode === "pr" && !this.prMcpInitialized) {
31
+ await this.mcpManager.setupMCPServers(this.neurolink, this.config.mcpServers);
32
+ this.prMcpInitialized = true;
33
+ return;
34
+ }
35
+ if (mode === "local" && !this.localMcpInitialized) {
36
+ await this.mcpManager.setupLocalGitMCPServer(this.neurolink);
37
+ this.localMcpInitialized = true;
38
+ }
39
+ }
40
+ async explore(input, runtimeContext) {
41
+ await this.initialize(runtimeContext.mode);
42
+ const normalizedInput = this.normalizeInput(input);
43
+ const cacheKey = this.buildCacheKey(normalizedInput, runtimeContext);
44
+ if (this.config.ai.explore.cacheResults) {
45
+ const cached = this.sessionManager.findExploration(runtimeContext.sessionId, cacheKey);
46
+ if (cached) {
47
+ return { result: cached, cached: true };
48
+ }
49
+ }
50
+ const [projectRules, projectStandards, knowledgeBase, repositoryMemory] = await Promise.all([
51
+ this.rulesContextLoader.load(),
52
+ this.loadProjectStandards(),
53
+ this.loadKnowledgeBase(),
54
+ this.loadRepositoryMemory(runtimeContext),
55
+ ]);
56
+ const prompt = this.promptBuilder.buildPrompt(normalizedInput, runtimeContext, {
57
+ projectRules,
58
+ projectStandards,
59
+ knowledgeBase,
60
+ repositoryMemory,
61
+ });
62
+ this.neurolink.setToolContext(runtimeContext);
63
+ console.log(` 🔎 Explore: ${normalizedInput.task}`);
64
+ const researchResponse = await this.neurolink.generate({
65
+ input: { text: prompt },
66
+ provider: this.config.ai.explore.provider || this.config.ai.provider,
67
+ model: this.config.ai.explore.model || this.config.ai.model,
68
+ temperature: this.config.ai.explore.temperature ?? this.config.ai.temperature,
69
+ maxTokens: this.config.ai.explore.maxTokens || this.config.ai.maxTokens,
70
+ timeout: this.config.ai.explore.timeout || this.config.ai.timeout,
71
+ skipToolPromptInjection: true,
72
+ ...this.getToolFilteringOptions(runtimeContext.mode),
73
+ context: {
74
+ sessionId: runtimeContext.sessionId,
75
+ userId: `${runtimeContext.workspace}-${runtimeContext.repository}`.toLowerCase(),
76
+ operation: "explore-context-research",
77
+ metadata: runtimeContext.metadata || {},
78
+ },
79
+ memory: { enabled: false },
80
+ enableAnalytics: this.config.ai.enableAnalytics,
81
+ enableEvaluation: false,
82
+ });
83
+ const extractionPrompt = this.buildExtractionPrompt(normalizedInput.task, researchResponse);
84
+ const extractionResponse = await this.neurolink.generate({
85
+ input: { text: extractionPrompt },
86
+ provider: this.config.ai.explore.provider || this.config.ai.provider,
87
+ model: this.config.ai.explore.model || this.config.ai.model,
88
+ temperature: 0.1,
89
+ maxTokens: Math.min(this.config.ai.explore.maxTokens || this.config.ai.maxTokens, 12_000),
90
+ timeout: "2m",
91
+ disableTools: true,
92
+ context: {
93
+ sessionId: runtimeContext.sessionId,
94
+ userId: `${runtimeContext.workspace}-${runtimeContext.repository}`.toLowerCase(),
95
+ operation: "explore-context-extraction",
96
+ metadata: runtimeContext.metadata || {},
97
+ },
98
+ memory: { enabled: false },
99
+ enableAnalytics: this.config.ai.enableAnalytics,
100
+ enableEvaluation: false,
101
+ });
102
+ const result = this.normalizeResult(normalizedInput.task, extractionResponse?.content);
103
+ if (this.shouldCacheResult(result)) {
104
+ this.sessionManager.recordExploration(runtimeContext.sessionId, cacheKey, normalizedInput.task, normalizedInput.focus || [], result);
105
+ }
106
+ return { result, cached: false };
107
+ }
108
+ normalizeInput(input) {
109
+ const task = typeof input.task === "string" ? input.task.trim() : "";
110
+ if (!task) {
111
+ throw new Error("explore_context requires a non-empty task");
112
+ }
113
+ const focus = Array.isArray(input.focus)
114
+ ? input.focus
115
+ .filter((value) => typeof value === "string")
116
+ .map((value) => value.trim())
117
+ .filter(Boolean)
118
+ : undefined;
119
+ return {
120
+ task,
121
+ focus,
122
+ };
123
+ }
124
+ buildCacheKey(input, runtimeContext) {
125
+ return JSON.stringify({
126
+ mode: runtimeContext.mode,
127
+ workspace: runtimeContext.workspace,
128
+ repository: runtimeContext.repository,
129
+ pullRequestId: runtimeContext.pullRequestId || null,
130
+ branch: runtimeContext.branch || null,
131
+ task: input.task.toLowerCase(),
132
+ focus: (input.focus || []).map((item) => item.toLowerCase()),
133
+ });
134
+ }
135
+ getToolFilteringOptions(mode) {
136
+ try {
137
+ const externalTools = this.neurolink.getExternalMCPTools?.();
138
+ if (!Array.isArray(externalTools)) {
139
+ return {};
140
+ }
141
+ const excludeTools = externalTools
142
+ .map((tool) => tool?.name)
143
+ .filter((name) => typeof name === "string")
144
+ .filter((name) => this.shouldExcludeTool(mode, name));
145
+ return excludeTools.length > 0 ? { excludeTools } : {};
146
+ }
147
+ catch {
148
+ return {};
149
+ }
150
+ }
151
+ shouldExcludeTool(mode, toolName) {
152
+ const normalized = this.normalizeToolName(toolName);
153
+ if (/^(add_comment|set_pr_approval|set_review_status|approve_pull_request|unapprove_pull_request|request_changes|remove_requested_changes|update_pull_request|merge_pull_request|delete_branch)$/i.test(normalized)) {
154
+ return true;
155
+ }
156
+ if (mode === "local") {
157
+ return /^git_(commit|push|add|checkout|create_branch|merge|rebase|cherry_pick|reset|revert|tag|rm|clean|stash|apply)\b/i.test(normalized);
158
+ }
159
+ return false;
160
+ }
161
+ normalizeToolName(name) {
162
+ return name.split(/[.:/]/).pop() || name;
163
+ }
164
+ normalizeResult(task, content) {
165
+ const rawText = typeof content === "string" ? content : JSON.stringify(content || {});
166
+ const parsed = this.extractJsonPayload(rawText);
167
+ const findings = this.normalizeFindings(parsed?.findings);
168
+ const evidence = this.normalizeEvidence(parsed?.evidence);
169
+ const openQuestions = Array.isArray(parsed?.openQuestions)
170
+ ? parsed.openQuestions
171
+ .filter((value) => typeof value === "string")
172
+ .map((value) => value.trim())
173
+ .filter(Boolean)
174
+ : [];
175
+ return {
176
+ task,
177
+ summary: (typeof parsed?.summary === "string" && parsed.summary.trim()) ||
178
+ "Explore returned unstructured output and could not produce verified findings.",
179
+ findings,
180
+ evidence,
181
+ openQuestions: openQuestions.length > 0
182
+ ? openQuestions
183
+ : parsed
184
+ ? []
185
+ : [
186
+ "Explore did not return structured JSON. Retry with a narrower task or inspect the raw investigation path.",
187
+ ],
188
+ recommendedNextStep: parsed
189
+ ? this.normalizeNextStep(parsed?.recommendedNextStep)
190
+ : "explore_more",
191
+ completedAt: new Date().toISOString(),
192
+ };
193
+ }
194
+ buildExtractionPrompt(task, researchResponse) {
195
+ const researchContent = this.truncateForExtraction(typeof researchResponse?.content === "string"
196
+ ? researchResponse.content
197
+ : JSON.stringify(researchResponse?.content || {}), 16_000);
198
+ const toolCalls = Array.isArray(researchResponse?.toolCalls)
199
+ ? researchResponse.toolCalls.map((call) => ({
200
+ toolName: call?.toolName || call?.name || call?.tool || "unknown_tool",
201
+ parameters: call?.parameters || call?.args || {},
202
+ }))
203
+ : [];
204
+ const toolResults = Array.isArray(researchResponse?.toolResults)
205
+ ? researchResponse.toolResults.map((result) => ({
206
+ toolName: result?.toolName || "unknown_tool",
207
+ error: result?.error,
208
+ result: this.truncateForExtraction(JSON.stringify(result?.result || result?.content || result || {}), 8_000),
209
+ }))
210
+ : [];
211
+ return `
212
+ You are converting an Explore research run into strict JSON.
213
+
214
+ Original task:
215
+ ${task}
216
+
217
+ Research response text:
218
+ ${researchContent || "(empty)"}
219
+
220
+ Tool calls:
221
+ ${JSON.stringify(toolCalls, null, 2)}
222
+
223
+ Tool results:
224
+ ${JSON.stringify(toolResults, null, 2)}
225
+
226
+ Return ONLY valid JSON with this exact shape:
227
+ {
228
+ "summary": "string",
229
+ "findings": [
230
+ {
231
+ "claim": "string",
232
+ "confidence": "high|medium|low"
233
+ }
234
+ ],
235
+ "evidence": [
236
+ {
237
+ "sourceType": "file|commit|diff|jira|memory|rules|kb",
238
+ "ref": "string",
239
+ "snippet": "string",
240
+ "reason": "string"
241
+ }
242
+ ],
243
+ "openQuestions": ["string"],
244
+ "recommendedNextStep": "continue_review|explore_more|avoid_commenting"
245
+ }
246
+
247
+ Rules:
248
+ - Use only information present in the research response and tool results.
249
+ - If evidence is insufficient, keep findings empty and set recommendedNextStep to "explore_more".
250
+ - Prefer precise file paths, commit SHAs, or ticket IDs in evidence.ref.
251
+ - Do not include markdown fences or prose outside JSON.
252
+ `.trim();
253
+ }
254
+ truncateForExtraction(value, maxChars) {
255
+ if (value.length <= maxChars) {
256
+ return value;
257
+ }
258
+ return `${value.slice(0, maxChars)}\n... [truncated]`;
259
+ }
260
+ shouldCacheResult(result) {
261
+ if (result.findings.length > 0 || result.evidence.length > 0) {
262
+ return true;
263
+ }
264
+ return (result.summary !==
265
+ "Explore returned unstructured output and could not produce verified findings." &&
266
+ result.openQuestions.length === 0);
267
+ }
268
+ normalizeFindings(input) {
269
+ if (!Array.isArray(input)) {
270
+ return [];
271
+ }
272
+ return input
273
+ .map((entry) => {
274
+ const value = (entry || {});
275
+ const claim = typeof value.claim === "string" ? value.claim.trim() : "";
276
+ if (!claim) {
277
+ return null;
278
+ }
279
+ return {
280
+ claim,
281
+ confidence: this.normalizeConfidence(value.confidence),
282
+ };
283
+ })
284
+ .filter((entry) => entry !== null);
285
+ }
286
+ normalizeEvidence(input) {
287
+ if (!Array.isArray(input)) {
288
+ return [];
289
+ }
290
+ return input
291
+ .map((entry) => {
292
+ const value = (entry || {});
293
+ const sourceType = this.normalizeSourceType(value.sourceType);
294
+ const ref = typeof value.ref === "string" ? value.ref.trim() : "";
295
+ const reason = typeof value.reason === "string" ? value.reason.trim() : "";
296
+ if (!ref || !reason) {
297
+ return null;
298
+ }
299
+ const result = {
300
+ sourceType,
301
+ ref,
302
+ reason,
303
+ };
304
+ if (typeof value.snippet === "string" && value.snippet.trim()) {
305
+ result.snippet = value.snippet.trim();
306
+ }
307
+ return result;
308
+ })
309
+ .filter((entry) => entry !== null);
310
+ }
311
+ normalizeConfidence(value) {
312
+ if (typeof value !== "string") {
313
+ return "medium";
314
+ }
315
+ const normalized = value.toLowerCase();
316
+ return normalized === "high" ||
317
+ normalized === "medium" ||
318
+ normalized === "low"
319
+ ? normalized
320
+ : "medium";
321
+ }
322
+ normalizeSourceType(value) {
323
+ if (typeof value !== "string") {
324
+ return "file";
325
+ }
326
+ const normalized = value.toLowerCase();
327
+ if (normalized === "file" ||
328
+ normalized === "commit" ||
329
+ normalized === "diff" ||
330
+ normalized === "jira" ||
331
+ normalized === "memory" ||
332
+ normalized === "rules" ||
333
+ normalized === "kb") {
334
+ return normalized;
335
+ }
336
+ return "file";
337
+ }
338
+ normalizeNextStep(value) {
339
+ if (typeof value !== "string") {
340
+ return "continue_review";
341
+ }
342
+ const normalized = value.toLowerCase();
343
+ if (normalized === "continue_review" ||
344
+ normalized === "explore_more" ||
345
+ normalized === "avoid_commenting") {
346
+ return normalized;
347
+ }
348
+ return "continue_review";
349
+ }
350
+ extractJsonPayload(content) {
351
+ if (!content || typeof content !== "string") {
352
+ return null;
353
+ }
354
+ const trimmed = content.trim();
355
+ try {
356
+ return JSON.parse(trimmed);
357
+ }
358
+ catch {
359
+ // Fall through to extraction strategies
360
+ }
361
+ const fenceOpen = trimmed.toLowerCase().indexOf("```json");
362
+ if (fenceOpen !== -1) {
363
+ let contentStart = fenceOpen + 7;
364
+ while (contentStart < trimmed.length &&
365
+ (trimmed[contentStart] === " " ||
366
+ trimmed[contentStart] === "\n" ||
367
+ trimmed[contentStart] === "\r")) {
368
+ contentStart++;
369
+ }
370
+ const fenceClose = trimmed.indexOf("```", contentStart);
371
+ if (fenceClose !== -1) {
372
+ try {
373
+ return JSON.parse(trimmed.slice(contentStart, fenceClose).trim());
374
+ }
375
+ catch {
376
+ return null;
377
+ }
378
+ }
379
+ }
380
+ const start = trimmed.indexOf("{");
381
+ const end = trimmed.lastIndexOf("}");
382
+ if (start >= 0 && end > start) {
383
+ try {
384
+ return JSON.parse(trimmed.slice(start, end + 1));
385
+ }
386
+ catch {
387
+ return null;
388
+ }
389
+ }
390
+ return null;
391
+ }
392
+ async loadProjectStandards() {
393
+ if (!this.config.projectStandards?.customPromptsPath) {
394
+ return null;
395
+ }
396
+ const promptDir = join(this.projectRoot, this.config.projectStandards.customPromptsPath);
397
+ const promptFiles = [
398
+ "review-standards.md",
399
+ "security-guidelines.md",
400
+ "coding-conventions.md",
401
+ ];
402
+ const sections = [];
403
+ for (const file of promptFiles) {
404
+ const absolutePath = join(promptDir, file);
405
+ if (!existsSync(absolutePath)) {
406
+ continue;
407
+ }
408
+ try {
409
+ const content = await readFile(absolutePath, "utf-8");
410
+ sections.push(`## ${file}\n\n${content}`);
411
+ }
412
+ catch {
413
+ continue;
414
+ }
415
+ }
416
+ return sections.length > 0 ? sections.join("\n\n---\n\n") : null;
417
+ }
418
+ async loadKnowledgeBase() {
419
+ if (!this.config.knowledgeBase?.enabled) {
420
+ return null;
421
+ }
422
+ try {
423
+ return await new KnowledgeBaseManager(this.config.knowledgeBase, this.projectRoot).getForPrompt();
424
+ }
425
+ catch {
426
+ return null;
427
+ }
428
+ }
429
+ async loadRepositoryMemory(runtimeContext) {
430
+ if (!this.memoryManager) {
431
+ return null;
432
+ }
433
+ return this.memoryManager.readRepositoryMemory(runtimeContext.workspace, runtimeContext.repository);
434
+ }
435
+ initializeNeurolink() {
436
+ const observabilityConfig = buildObservabilityConfigFromEnv();
437
+ const neurolinkConfig = {
438
+ conversationMemory: {
439
+ enabled: false,
440
+ },
441
+ };
442
+ if (observabilityConfig) {
443
+ if (!validateObservabilityConfig(observabilityConfig)) {
444
+ throw new Error("Invalid observability configuration");
445
+ }
446
+ neurolinkConfig.observability = observabilityConfig;
447
+ }
448
+ return new NeuroLink(neurolinkConfig);
449
+ }
450
+ }
451
+ //# sourceMappingURL=ContextExplorerService.js.map
@@ -0,0 +1,5 @@
1
+ import { ExplorerSupportingContext, ExploreContextInput, ExploreRuntimeContext } from "./types.js";
2
+ export declare class ExplorerPromptBuilder {
3
+ buildPrompt(input: ExploreContextInput, runtimeContext: ExploreRuntimeContext, supportingContext: ExplorerSupportingContext): string;
4
+ }
5
+ //# sourceMappingURL=ExplorerPromptBuilder.d.ts.map
@@ -0,0 +1,67 @@
1
+ export class ExplorerPromptBuilder {
2
+ buildPrompt(input, runtimeContext, supportingContext) {
3
+ const focus = input.focus && input.focus.length > 0
4
+ ? input.focus
5
+ : ["No explicit focus provided"];
6
+ const contextSections = [];
7
+ if (supportingContext.projectRules) {
8
+ contextSections.push(`<project-rules>\n${supportingContext.projectRules}\n</project-rules>`);
9
+ }
10
+ if (supportingContext.projectStandards) {
11
+ contextSections.push(`<project-standards>\n${supportingContext.projectStandards}\n</project-standards>`);
12
+ }
13
+ if (supportingContext.knowledgeBase) {
14
+ contextSections.push(`<knowledge-base>\n${supportingContext.knowledgeBase}\n</knowledge-base>`);
15
+ }
16
+ if (supportingContext.repositoryMemory) {
17
+ contextSections.push(`<repository-memory>\n${supportingContext.repositoryMemory}\n</repository-memory>`);
18
+ }
19
+ return `
20
+ You are Explore, a generic research worker for Yama.
21
+ Your job is to investigate the assigned task using available tools and return only the required JSON object.
22
+
23
+ Rules:
24
+ 1. You are not the reviewer. Do not approve, block, comment on the PR, or mutate repository state.
25
+ 2. Use tools as many times as needed until you can answer the task with evidence.
26
+ 3. Prefer the smallest sufficient evidence. Do not dump raw tool output.
27
+ 4. If you are uncertain, say so explicitly in findings or openQuestions.
28
+ 5. Return valid JSON only. No markdown fences or commentary outside JSON.
29
+
30
+ Research task:
31
+ - Task: ${input.task}
32
+ - Review mode: ${runtimeContext.mode}
33
+ - Workspace: ${runtimeContext.workspace}
34
+ - Repository: ${runtimeContext.repository}
35
+ - Pull request: ${runtimeContext.pullRequestId ?? "N/A"}
36
+ - Branch: ${runtimeContext.branch || "N/A"}
37
+
38
+ Focus areas:
39
+ ${focus.map((item) => `- ${item}`).join("\n")}
40
+
41
+ ${contextSections.join("\n\n")}
42
+
43
+ Return this exact JSON shape:
44
+ {
45
+ "task": "string",
46
+ "summary": "string",
47
+ "findings": [
48
+ {
49
+ "claim": "string",
50
+ "confidence": "high|medium|low"
51
+ }
52
+ ],
53
+ "evidence": [
54
+ {
55
+ "sourceType": "file|commit|diff|jira|memory|rules|kb",
56
+ "ref": "string",
57
+ "snippet": "string",
58
+ "reason": "string"
59
+ }
60
+ ],
61
+ "openQuestions": ["string"],
62
+ "recommendedNextStep": "continue_review|explore_more|avoid_commenting"
63
+ }
64
+ `.trim();
65
+ }
66
+ }
67
+ //# sourceMappingURL=ExplorerPromptBuilder.js.map
@@ -0,0 +1,12 @@
1
+ import { MemoryBankConfig, ProjectStandardsConfig } from "../types/config.types.js";
2
+ export declare class RulesContextLoader {
3
+ private readonly memoryBank;
4
+ private readonly projectStandards?;
5
+ private readonly projectRoot;
6
+ constructor(memoryBank: MemoryBankConfig, projectStandards?: ProjectStandardsConfig | undefined, projectRoot?: string);
7
+ load(): Promise<string | null>;
8
+ private resolveMemoryBankDir;
9
+ private tryReadDirectory;
10
+ private tryReadFile;
11
+ }
12
+ //# sourceMappingURL=RulesContextLoader.d.ts.map
@@ -0,0 +1,106 @@
1
+ import { readdir, readFile } from "fs/promises";
2
+ import { existsSync } from "fs";
3
+ import { join, resolve } from "path";
4
+ const ROOT_RULE_FILES = ["CLAUDE.md", ".clinerules", "CONTRIBUTING.md"];
5
+ const CONTEXT_EXTENSIONS = new Set([".md", ".txt", ".yml", ".yaml"]);
6
+ export class RulesContextLoader {
7
+ memoryBank;
8
+ projectStandards;
9
+ projectRoot;
10
+ constructor(memoryBank, projectStandards, projectRoot = process.cwd()) {
11
+ this.memoryBank = memoryBank;
12
+ this.projectStandards = projectStandards;
13
+ this.projectRoot = projectRoot;
14
+ }
15
+ async load() {
16
+ const sections = [];
17
+ for (const file of ROOT_RULE_FILES) {
18
+ const absolutePath = resolve(this.projectRoot, file);
19
+ const content = await this.tryReadFile(absolutePath);
20
+ if (content) {
21
+ sections.push(`## ${file}\n\n${content}`);
22
+ }
23
+ }
24
+ const memoryBankDir = this.resolveMemoryBankDir();
25
+ if (memoryBankDir) {
26
+ const standardFiles = this.memoryBank.standardFiles || [];
27
+ for (const file of standardFiles) {
28
+ const absolutePath = join(memoryBankDir, file);
29
+ const content = await this.tryReadFile(absolutePath);
30
+ if (content) {
31
+ sections.push(`## memory-bank/${file}\n\n${content}`);
32
+ }
33
+ }
34
+ const directoryEntries = await this.tryReadDirectory(memoryBankDir);
35
+ for (const entry of directoryEntries) {
36
+ if (standardFiles.includes(entry)) {
37
+ continue;
38
+ }
39
+ const extension = entry.slice(entry.lastIndexOf("."));
40
+ if (!CONTEXT_EXTENSIONS.has(extension)) {
41
+ continue;
42
+ }
43
+ const absolutePath = join(memoryBankDir, entry);
44
+ const content = await this.tryReadFile(absolutePath);
45
+ if (content) {
46
+ sections.push(`## memory-bank/${entry}\n\n${content}`);
47
+ }
48
+ }
49
+ }
50
+ if (this.projectStandards?.customPromptsPath) {
51
+ const promptDir = resolve(this.projectRoot, this.projectStandards.customPromptsPath);
52
+ const promptFiles = [
53
+ "review-standards.md",
54
+ "security-guidelines.md",
55
+ "coding-conventions.md",
56
+ ];
57
+ for (const file of promptFiles) {
58
+ const content = await this.tryReadFile(join(promptDir, file));
59
+ if (content) {
60
+ sections.push(`## prompts/${file}\n\n${content}`);
61
+ }
62
+ }
63
+ }
64
+ if (sections.length === 0) {
65
+ return null;
66
+ }
67
+ return sections.join("\n\n---\n\n");
68
+ }
69
+ resolveMemoryBankDir() {
70
+ if (!this.memoryBank.enabled) {
71
+ return null;
72
+ }
73
+ const candidates = [this.memoryBank.path, ...this.memoryBank.fallbackPaths];
74
+ for (const candidate of candidates) {
75
+ const absolutePath = resolve(this.projectRoot, candidate);
76
+ if (existsSync(absolutePath)) {
77
+ return absolutePath;
78
+ }
79
+ }
80
+ return null;
81
+ }
82
+ async tryReadDirectory(path) {
83
+ try {
84
+ const entries = await readdir(path, { withFileTypes: true });
85
+ return entries
86
+ .filter((entry) => entry.isFile())
87
+ .map((entry) => entry.name)
88
+ .sort((left, right) => left.localeCompare(right));
89
+ }
90
+ catch {
91
+ return [];
92
+ }
93
+ }
94
+ async tryReadFile(path) {
95
+ if (!existsSync(path)) {
96
+ return null;
97
+ }
98
+ try {
99
+ return await readFile(path, "utf-8");
100
+ }
101
+ catch {
102
+ return null;
103
+ }
104
+ }
105
+ }
106
+ //# sourceMappingURL=RulesContextLoader.js.map
@@ -0,0 +1,26 @@
1
+ import { ExplorationResult } from "../types/v2.types.js";
2
+ export interface ExploreContextInput {
3
+ task: string;
4
+ focus?: string[];
5
+ }
6
+ export interface ExploreRuntimeContext {
7
+ sessionId: string;
8
+ mode: "pr" | "local";
9
+ workspace: string;
10
+ repository: string;
11
+ pullRequestId?: number;
12
+ branch?: string;
13
+ dryRun?: boolean;
14
+ metadata?: Record<string, unknown>;
15
+ }
16
+ export interface ExplorerSupportingContext {
17
+ projectRules: string | null;
18
+ projectStandards: string | null;
19
+ knowledgeBase: string | null;
20
+ repositoryMemory: string | null;
21
+ }
22
+ export interface ExploreExecutionResult {
23
+ result: ExplorationResult;
24
+ cached: boolean;
25
+ }
26
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -40,6 +40,14 @@ export declare class MemoryManager {
40
40
  * This value is passed as `context.userId` in generate() calls.
41
41
  */
42
42
  static buildOwnerId(workspace: string, repository: string): string;
43
+ /**
44
+ * Read persisted condensed memory for a repository owner ID.
45
+ */
46
+ readMemory(ownerId: string): Promise<string | null>;
47
+ /**
48
+ * Read persisted condensed memory for a workspace/repository pair.
49
+ */
50
+ readRepositoryMemory(workspace: string, repository: string): Promise<string | null>;
43
51
  /**
44
52
  * Commit memory files to the repository if autoCommit is enabled.
45
53
  *