@juspay/yama 2.2.2 → 2.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/README.md +14 -0
  3. package/dist/index.d.ts +3 -1
  4. package/dist/index.js +2 -0
  5. package/dist/v2/config/ConfigLoader.d.ts +15 -0
  6. package/dist/v2/config/ConfigLoader.js +72 -4
  7. package/dist/v2/config/DefaultConfig.js +14 -0
  8. package/dist/v2/core/LearningOrchestrator.d.ts +1 -0
  9. package/dist/v2/core/LearningOrchestrator.js +64 -4
  10. package/dist/v2/core/SessionManager.d.ts +3 -1
  11. package/dist/v2/core/SessionManager.js +30 -0
  12. package/dist/v2/core/YamaV2Orchestrator.d.ts +16 -7
  13. package/dist/v2/core/YamaV2Orchestrator.js +213 -35
  14. package/dist/v2/exploration/ContextExplorerService.d.ts +39 -0
  15. package/dist/v2/exploration/ContextExplorerService.js +451 -0
  16. package/dist/v2/exploration/ExplorerPromptBuilder.d.ts +5 -0
  17. package/dist/v2/exploration/ExplorerPromptBuilder.js +67 -0
  18. package/dist/v2/exploration/RulesContextLoader.d.ts +12 -0
  19. package/dist/v2/exploration/RulesContextLoader.js +106 -0
  20. package/dist/v2/exploration/types.d.ts +26 -0
  21. package/dist/v2/exploration/types.js +2 -0
  22. package/dist/v2/learning/types.d.ts +10 -0
  23. package/dist/v2/memory/MemoryManager.d.ts +65 -0
  24. package/dist/v2/memory/MemoryManager.js +207 -0
  25. package/dist/v2/prompts/LangfusePromptManager.js +6 -0
  26. package/dist/v2/prompts/PromptBuilder.d.ts +18 -1
  27. package/dist/v2/prompts/PromptBuilder.js +131 -29
  28. package/dist/v2/prompts/ReviewSystemPrompt.d.ts +9 -4
  29. package/dist/v2/prompts/ReviewSystemPrompt.js +46 -239
  30. package/dist/v2/types/config.types.d.ts +70 -1
  31. package/dist/v2/types/v2.types.d.ts +28 -0
  32. package/package.json +3 -3
  33. package/yama.config.example.yaml +25 -0
@@ -58,12 +58,22 @@ export interface KnowledgeBase {
58
58
  /**
59
59
  * Request for the learn command
60
60
  */
61
+ /**
62
+ * Controls which storage systems are committed after learning extraction.
63
+ * - "kb" — commit only the knowledge base file (existing behavior)
64
+ * - "memory" — commit only per-repo memory files (via NeuroLink + git push)
65
+ * - "all" — commit both knowledge base and per-repo memory
66
+ */
67
+ export type LearnCommitMode = "kb" | "memory" | "all";
61
68
  export interface LearnRequest {
62
69
  workspace: string;
63
70
  repository: string;
64
71
  pullRequestId: number;
65
72
  dryRun?: boolean;
73
+ /** @deprecated Use commitMode instead */
66
74
  commit?: boolean;
75
+ /** Controls which storage systems are committed after extraction */
76
+ commitMode?: LearnCommitMode;
67
77
  summarize?: boolean;
68
78
  outputPath?: string;
69
79
  outputFormat?: "md" | "json";
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Memory Manager
3
+ *
4
+ * Provides per-repository condensed memory configuration for NeuroLink.
5
+ *
6
+ * This manager builds the NeuroLink-compatible memory SDK config with
7
+ * file-based custom storage, so that NeuroLink's generate()/stream() calls
8
+ * can retrieve and store memory using context.userId as the per-repo key.
9
+ *
10
+ * Callers control WHEN memory is read/written via per-call flags:
11
+ * memory: { enabled: true, read: true, write: false }
12
+ *
13
+ * This avoids noise from operational calls (e.g., fetching PR data)
14
+ * polluting the condensed memory.
15
+ *
16
+ * Storage: file-based (.yama/memory/{workspace}-{repository}.txt) (lowercased)
17
+ * Condensation: LLM-powered via NeuroLink's built-in Hippocampus
18
+ */
19
+ import { MemoryConfig } from "../types/config.types.js";
20
+ export declare class MemoryManager {
21
+ private readonly config;
22
+ private readonly projectRoot;
23
+ private readonly aiProvider;
24
+ private readonly aiModel;
25
+ constructor(config: MemoryConfig, aiProvider: string, aiModel: string, projectRoot?: string);
26
+ /**
27
+ * Resolve the storage directory path (handles both absolute and relative paths).
28
+ */
29
+ private resolveStorageDir;
30
+ /**
31
+ * Build the NeuroLink-compatible Memory config object.
32
+ *
33
+ * Passed to NeuroLink constructor as `conversationMemory.memory`.
34
+ * NeuroLink internally initializes Hippocampus with our file-based
35
+ * storage and review-specific condensation prompt.
36
+ */
37
+ buildNeuroLinkMemoryConfig(): Record<string, unknown>;
38
+ /**
39
+ * Build a deterministic owner ID from workspace and repository.
40
+ * This value is passed as `context.userId` in generate() calls.
41
+ */
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>;
51
+ /**
52
+ * Commit memory files to the repository if autoCommit is enabled.
53
+ *
54
+ * Checks git status for changes in the storagePath directory,
55
+ * then stages, commits, and pushes. Uses [skip ci] to prevent
56
+ * infinite CI loops. Never throws — failures are logged and ignored
57
+ * so they never block the review result.
58
+ */
59
+ commitMemoryChanges(): Promise<boolean>;
60
+ /**
61
+ * Map an ownerId to a safe file path.
62
+ */
63
+ private ownerIdToFilePath;
64
+ }
65
+ //# sourceMappingURL=MemoryManager.d.ts.map
@@ -0,0 +1,207 @@
1
+ /**
2
+ * Memory Manager
3
+ *
4
+ * Provides per-repository condensed memory configuration for NeuroLink.
5
+ *
6
+ * This manager builds the NeuroLink-compatible memory SDK config with
7
+ * file-based custom storage, so that NeuroLink's generate()/stream() calls
8
+ * can retrieve and store memory using context.userId as the per-repo key.
9
+ *
10
+ * Callers control WHEN memory is read/written via per-call flags:
11
+ * memory: { enabled: true, read: true, write: false }
12
+ *
13
+ * This avoids noise from operational calls (e.g., fetching PR data)
14
+ * polluting the condensed memory.
15
+ *
16
+ * Storage: file-based (.yama/memory/{workspace}-{repository}.txt) (lowercased)
17
+ * Condensation: LLM-powered via NeuroLink's built-in Hippocampus
18
+ */
19
+ import { readFile, writeFile, mkdir, unlink } from "fs/promises";
20
+ import { existsSync } from "fs";
21
+ import { join, dirname, isAbsolute } from "path";
22
+ import { execFile } from "child_process";
23
+ import { promisify } from "util";
24
+ const execFileAsync = promisify(execFile);
25
+ // ============================================================================
26
+ // Constants
27
+ // ============================================================================
28
+ /**
29
+ * Condensation prompt tailored for code review memory.
30
+ * Guides the LLM to retain review patterns, team conventions, and
31
+ * frequently flagged issues rather than individual PR details.
32
+ */
33
+ const REVIEW_MEMORY_CONDENSATION_PROMPT = `You are a memory condensation engine for an AI code reviewer called Yama.
34
+ You receive:
35
+ 1. OLD_MEMORY: the existing condensed memory for a specific repository (may be empty)
36
+ 2. NEW_CONTENT: new information from a recent code review or learning extraction
37
+
38
+ Your job: merge old memory with the new information into a single condensed summary.
39
+
40
+ Rules:
41
+ - Output ONLY the condensed memory text, nothing else
42
+ - Maximum {{MAX_WORDS}} words
43
+ - PRIORITIZE retaining (most important first):
44
+ 1. False positive patterns: things the team confirmed are NOT issues
45
+ 2. Team coding conventions and style preferences
46
+ 3. Recurring review themes and common issue categories
47
+ 4. Repository-specific domain knowledge and architecture decisions
48
+ 5. Patterns the AI missed that developers caught
49
+ 6. Review outcome trends (approval rate, common blocking reasons)
50
+ - DROP: individual PR numbers, timestamps, one-off issues, greeting text
51
+ - Keep learnings GENERIC and applicable to future reviews
52
+ - If NEW_CONTENT has nothing worth remembering, return OLD_MEMORY unchanged
53
+ - If both are empty, return empty string`;
54
+ // ============================================================================
55
+ // Manager
56
+ // ============================================================================
57
+ export class MemoryManager {
58
+ config;
59
+ projectRoot;
60
+ aiProvider;
61
+ aiModel;
62
+ constructor(config, aiProvider, aiModel, projectRoot) {
63
+ this.config = config;
64
+ this.aiProvider = aiProvider;
65
+ this.aiModel = aiModel;
66
+ this.projectRoot = projectRoot || process.cwd();
67
+ }
68
+ /**
69
+ * Resolve the storage directory path (handles both absolute and relative paths).
70
+ */
71
+ resolveStorageDir() {
72
+ return isAbsolute(this.config.storagePath)
73
+ ? this.config.storagePath
74
+ : join(this.projectRoot, this.config.storagePath);
75
+ }
76
+ /**
77
+ * Build the NeuroLink-compatible Memory config object.
78
+ *
79
+ * Passed to NeuroLink constructor as `conversationMemory.memory`.
80
+ * NeuroLink internally initializes Hippocampus with our file-based
81
+ * storage and review-specific condensation prompt.
82
+ */
83
+ buildNeuroLinkMemoryConfig() {
84
+ const storageDir = this.resolveStorageDir();
85
+ return {
86
+ enabled: true,
87
+ storage: this.config.storage || {
88
+ type: "custom",
89
+ onGet: async (ownerId) => {
90
+ const filePath = this.ownerIdToFilePath(storageDir, ownerId);
91
+ if (!existsSync(filePath)) {
92
+ return null;
93
+ }
94
+ try {
95
+ return await readFile(filePath, "utf-8");
96
+ }
97
+ catch {
98
+ return null;
99
+ }
100
+ },
101
+ onSet: async (ownerId, memory) => {
102
+ const filePath = this.ownerIdToFilePath(storageDir, ownerId);
103
+ const dir = dirname(filePath);
104
+ if (!existsSync(dir)) {
105
+ await mkdir(dir, { recursive: true });
106
+ }
107
+ await writeFile(filePath, memory, "utf-8");
108
+ },
109
+ onDelete: async (ownerId) => {
110
+ const filePath = this.ownerIdToFilePath(storageDir, ownerId);
111
+ if (existsSync(filePath)) {
112
+ await unlink(filePath);
113
+ }
114
+ },
115
+ },
116
+ neurolink: this.config.neurolink || {
117
+ provider: this.aiProvider,
118
+ model: this.aiModel,
119
+ temperature: 0.1,
120
+ },
121
+ maxWords: this.config.maxWords,
122
+ prompt: this.config.prompt || REVIEW_MEMORY_CONDENSATION_PROMPT,
123
+ };
124
+ }
125
+ /**
126
+ * Build a deterministic owner ID from workspace and repository.
127
+ * This value is passed as `context.userId` in generate() calls.
128
+ */
129
+ static buildOwnerId(workspace, repository) {
130
+ return `${workspace}-${repository}`.toLowerCase();
131
+ }
132
+ /**
133
+ * Read persisted condensed memory for a repository owner ID.
134
+ */
135
+ async readMemory(ownerId) {
136
+ const storageDir = this.resolveStorageDir();
137
+ const filePath = this.ownerIdToFilePath(storageDir, ownerId);
138
+ if (!existsSync(filePath)) {
139
+ return null;
140
+ }
141
+ try {
142
+ return await readFile(filePath, "utf-8");
143
+ }
144
+ catch {
145
+ return null;
146
+ }
147
+ }
148
+ /**
149
+ * Read persisted condensed memory for a workspace/repository pair.
150
+ */
151
+ async readRepositoryMemory(workspace, repository) {
152
+ return this.readMemory(MemoryManager.buildOwnerId(workspace, repository));
153
+ }
154
+ /**
155
+ * Commit memory files to the repository if autoCommit is enabled.
156
+ *
157
+ * Checks git status for changes in the storagePath directory,
158
+ * then stages, commits, and pushes. Uses [skip ci] to prevent
159
+ * infinite CI loops. Never throws — failures are logged and ignored
160
+ * so they never block the review result.
161
+ */
162
+ async commitMemoryChanges() {
163
+ if (!this.config.autoCommit) {
164
+ return false;
165
+ }
166
+ const storageDir = this.resolveStorageDir();
167
+ if (!existsSync(storageDir)) {
168
+ return false;
169
+ }
170
+ try {
171
+ // Check if there are any changes to commit
172
+ const { stdout: statusOutput } = await execFileAsync("git", ["status", "--porcelain", storageDir], { cwd: this.projectRoot });
173
+ if (!statusOutput.trim()) {
174
+ console.log(" 🧠 No memory changes to commit");
175
+ return false;
176
+ }
177
+ const commitMessage = this.config.commitMessage ||
178
+ "chore: update yama review memory [skip ci]";
179
+ // Stage memory files
180
+ await execFileAsync("git", ["add", storageDir], {
181
+ cwd: this.projectRoot,
182
+ });
183
+ // Commit with [skip ci] to prevent infinite loops
184
+ await execFileAsync("git", ["commit", "-m", commitMessage], {
185
+ cwd: this.projectRoot,
186
+ });
187
+ // Push to the current branch
188
+ await execFileAsync("git", ["push"], {
189
+ cwd: this.projectRoot,
190
+ });
191
+ console.log(" 🧠 Memory changes committed and pushed");
192
+ return true;
193
+ }
194
+ catch (error) {
195
+ console.warn(` ⚠️ Memory auto-commit failed: ${error.message}`);
196
+ return false;
197
+ }
198
+ }
199
+ /**
200
+ * Map an ownerId to a safe file path.
201
+ */
202
+ ownerIdToFilePath(storageDir, ownerId) {
203
+ const safeId = ownerId.replace(/[^a-zA-Z0-9-]/g, "-");
204
+ return join(storageDir, `${safeId}.md`);
205
+ }
206
+ }
207
+ //# sourceMappingURL=MemoryManager.js.map
@@ -20,6 +20,12 @@ export class LangfusePromptManager {
20
20
  * Initialize Langfuse client if credentials are available
21
21
  */
22
22
  initializeClient() {
23
+ const skipLangfusePrompts = process.env.YAMA_SKIP_LANGFUSE_PROMPTS === "true" ||
24
+ process.env.YAMA_SKIP_LANGFUSE_PROMPTS === "1";
25
+ if (skipLangfusePrompts) {
26
+ console.log(" ⏭️ Langfuse prompt fetch disabled (YAMA_SKIP_LANGFUSE_PROMPTS) — using local prompts");
27
+ return;
28
+ }
23
29
  const publicKey = process.env.LANGFUSE_PUBLIC_KEY;
24
30
  const secretKey = process.env.LANGFUSE_SECRET_KEY;
25
31
  const baseUrl = process.env.LANGFUSE_BASE_URL;
@@ -15,7 +15,24 @@ export declare class PromptBuilder {
15
15
  * Build complete review instructions for AI
16
16
  * Combines generic base prompt + project-specific config
17
17
  */
18
- buildReviewInstructions(request: ReviewRequest, config: YamaConfig): Promise<string>;
18
+ buildReviewInstructions(request: ReviewRequest, config: YamaConfig, bootstrapStandards?: string | null): Promise<string>;
19
+ /**
20
+ * Per-PR workflow block. Standards-first, file-by-file, explore-on-uncertainty.
21
+ * The agent stays autonomous; this just choreographs the order it should follow.
22
+ */
23
+ private buildReviewWorkflow;
24
+ /**
25
+ * Strip sections that depend on explore_context being enabled.
26
+ * Keeps the prompt single-source and avoids forking files for the disabled case.
27
+ *
28
+ * - <!-- EXPLORE_BEGIN -->...<!-- EXPLORE_END --> is removed when explore is OFF.
29
+ * - <!-- EXPLORE_DISABLED_BEGIN -->...<!-- EXPLORE_DISABLED_END --> is removed when explore is ON.
30
+ * - The marker comments themselves are always stripped.
31
+ *
32
+ * Implementation uses linear indexOf/slice instead of regex to avoid any
33
+ * polynomial-backtracking risk on adversarial input.
34
+ */
35
+ static stripDisabledSections(prompt: string, exploreEnabled: boolean): string;
19
36
  /**
20
37
  * Build project configuration in XML format
21
38
  * Injects project-specific rules into base system prompt
@@ -19,15 +19,31 @@ export class PromptBuilder {
19
19
  * Build complete review instructions for AI
20
20
  * Combines generic base prompt + project-specific config
21
21
  */
22
- async buildReviewInstructions(request, config) {
22
+ async buildReviewInstructions(request, config, bootstrapStandards) {
23
23
  // Base system prompt - fetched from Langfuse or local fallback
24
- const basePrompt = await this.langfuseManager.getReviewPrompt();
24
+ const basePromptRaw = await this.langfuseManager.getReviewPrompt();
25
25
  // Project-specific configuration in XML format
26
26
  const projectConfig = this.buildProjectConfigXML(config, request);
27
27
  // Project-specific standards (if available)
28
28
  const projectStandards = await this.loadProjectStandards(config);
29
29
  // Knowledge base learnings (reinforcement learning)
30
30
  const knowledgeBase = await this.loadKnowledgeBase(config);
31
+ const exploreEnabled = config.ai.explore.enabled;
32
+ // Strip explore_context references when the subagent is disabled.
33
+ const basePrompt = PromptBuilder.stripDisabledSections(basePromptRaw, exploreEnabled);
34
+ const workflowBlock = PromptBuilder.stripDisabledSections(this.buildReviewWorkflow(request), exploreEnabled);
35
+ const bootstrapBlock = bootstrapStandards && bootstrapStandards.trim().length > 0
36
+ ? `<bootstrapped-standards>
37
+ <!--
38
+ Recurring reviewer patterns observed in recent merged PRs on this repo.
39
+ These are runtime observations, not config rules. Treat them as guidance
40
+ that ranks BELOW <blocking-criteria> but ABOVE generic suggestions.
41
+ If they conflict with <project-standards> or <blocking-criteria>, the
42
+ config wins.
43
+ -->
44
+ ${bootstrapStandards.trim()}
45
+ </bootstrapped-standards>`
46
+ : "";
31
47
  // Combine all parts
32
48
  return `
33
49
  ${basePrompt}
@@ -38,6 +54,8 @@ ${projectConfig}
38
54
 
39
55
  ${projectStandards ? `<project-standards>\n${projectStandards}\n</project-standards>` : ""}
40
56
 
57
+ ${bootstrapBlock}
58
+
41
59
  ${knowledgeBase ? `<learned-knowledge>\n${knowledgeBase}\n</learned-knowledge>` : ""}
42
60
 
43
61
  <review-task>
@@ -47,23 +65,106 @@ ${knowledgeBase ? `<learned-knowledge>\n${knowledgeBase}\n</learned-knowledge>`
47
65
  <branch>${this.escapeXML(request.branch || "N/A")}</branch>
48
66
  <mode>${request.dryRun ? "dry-run" : "live"}</mode>
49
67
 
50
- <instructions>
51
- Begin your autonomous code review now.
52
-
53
- 1. Call get_pull_request() to read PR details and existing comments
54
- 2. Analyze files one by one using get_pull_request_diff()
55
- 3. Use search_code() BEFORE commenting on unfamiliar code
56
- 4. Post comments immediately with add_comment() using line_number and line_type from diff
57
- 5. Apply blocking criteria to make final decision
58
- 6. Call set_pr_approval(approved: true) or set_review_status(request_changes: true)
59
- 7. Post summary comment with statistics
60
-
61
- ${request.dryRun ? "DRY RUN MODE: Simulate actions only, do not post real comments." : "LIVE MODE: Post real comments and make real decisions."}
62
- ${request.prompt ? `ADDITIONAL INSTRUCTIONS: ${this.escapeXML(request.prompt)}` : ""}
63
- </instructions>
68
+ ${workflowBlock}
64
69
  </review-task>
65
70
  `.trim();
66
71
  }
72
+ /**
73
+ * Per-PR workflow block. Standards-first, file-by-file, explore-on-uncertainty.
74
+ * The agent stays autonomous; this just choreographs the order it should follow.
75
+ */
76
+ buildReviewWorkflow(request) {
77
+ const modeLine = request.dryRun
78
+ ? "DRY RUN MODE: simulate actions only, do not post real comments or change PR state."
79
+ : "LIVE MODE: post real comments and make real decisions.";
80
+ const additional = request.prompt
81
+ ? `\n ADDITIONAL INSTRUCTIONS: ${this.escapeXML(request.prompt)}`
82
+ : "";
83
+ return ` <instructions>
84
+ Begin your autonomous review. Follow this order.
85
+
86
+ STEP 1 — Read project standards
87
+ Read the <project-standards> block above carefully. Treat any reviewer-expectation
88
+ entry with severity=BLOCKING as a blocking criterion for this PR. If the block is
89
+ missing or empty, fall back to <focus-areas> and <blocking-criteria>.
90
+
91
+ STEP 2 — Read the PR shell
92
+ Call get_pull_request once to get changed files, branch info, and existing comments.
93
+ Build a mental map of which files exist and which already have comments.
94
+ Do NOT request the full PR diff.
95
+
96
+ STEP 3 — Walk files one at a time
97
+ For each changed file, in order:
98
+ a. Call get_pull_request_diff(file_path=&lt;this file&gt;).
99
+ b. Cross-check the diff against project-standards and existing comments on this file.
100
+ c. If anything is non-trivial — multi-file impact, unfamiliar pattern, unclear intent,
101
+ history-dependent behavior — <!-- EXPLORE_BEGIN -->call explore_context with a precise
102
+ task and wait for its evidence before commenting<!-- EXPLORE_END --><!-- EXPLORE_DISABLED_BEGIN -->use search_code or get_file_content to verify before commenting<!-- EXPLORE_DISABLED_END -->.
103
+ d. For every confirmed issue, call add_comment immediately with line_number and
104
+ line_type from the diff JSON. Include a real-code suggestion for CRITICAL/MAJOR.
105
+ e. Move to the next file. Never request another file's diff before finishing the
106
+ current one. Never request a multi-file diff.
107
+
108
+ STEP 4 — Decision
109
+ After the last file, count issues by severity, apply <blocking-criteria>, and call
110
+ set_pr_approval(approved=true) OR set_review_status(request_changes=true).
111
+
112
+ STEP 5 — Summary comment
113
+ Post one summary comment with file count, issue counts by severity, and next steps.
114
+
115
+ Budget guidance: roughly 10 tool calls per file in the main loop. If you exceed
116
+ that on a single file, <!-- EXPLORE_BEGIN -->delegate the rest to explore_context<!-- EXPLORE_END --><!-- EXPLORE_DISABLED_BEGIN -->stop investigating<!-- EXPLORE_DISABLED_END --> and move on.
117
+
118
+ ${modeLine}${additional}
119
+ </instructions>`;
120
+ }
121
+ /**
122
+ * Strip sections that depend on explore_context being enabled.
123
+ * Keeps the prompt single-source and avoids forking files for the disabled case.
124
+ *
125
+ * - <!-- EXPLORE_BEGIN -->...<!-- EXPLORE_END --> is removed when explore is OFF.
126
+ * - <!-- EXPLORE_DISABLED_BEGIN -->...<!-- EXPLORE_DISABLED_END --> is removed when explore is ON.
127
+ * - The marker comments themselves are always stripped.
128
+ *
129
+ * Implementation uses linear indexOf/slice instead of regex to avoid any
130
+ * polynomial-backtracking risk on adversarial input.
131
+ */
132
+ static stripDisabledSections(prompt, exploreEnabled) {
133
+ const EXPLORE_BEGIN = "<!-- EXPLORE_BEGIN -->";
134
+ const EXPLORE_END = "<!-- EXPLORE_END -->";
135
+ const EXPLORE_DISABLED_BEGIN = "<!-- EXPLORE_DISABLED_BEGIN -->";
136
+ const EXPLORE_DISABLED_END = "<!-- EXPLORE_DISABLED_END -->";
137
+ const stripBlock = (text, start, end) => {
138
+ let out = "";
139
+ let cursor = 0;
140
+ while (cursor <= text.length) {
141
+ const s = text.indexOf(start, cursor);
142
+ if (s === -1) {
143
+ out += text.slice(cursor);
144
+ break;
145
+ }
146
+ out += text.slice(cursor, s);
147
+ const e = text.indexOf(end, s + start.length);
148
+ if (e === -1) {
149
+ out += text.slice(s);
150
+ break;
151
+ }
152
+ cursor = e + end.length;
153
+ }
154
+ return out;
155
+ };
156
+ const removeAll = (text, marker) => text.split(marker).join("");
157
+ if (exploreEnabled) {
158
+ let result = stripBlock(prompt, EXPLORE_DISABLED_BEGIN, EXPLORE_DISABLED_END);
159
+ result = removeAll(result, EXPLORE_BEGIN);
160
+ result = removeAll(result, EXPLORE_END);
161
+ return result;
162
+ }
163
+ let result = stripBlock(prompt, EXPLORE_BEGIN, EXPLORE_END);
164
+ result = removeAll(result, EXPLORE_DISABLED_BEGIN);
165
+ result = removeAll(result, EXPLORE_DISABLED_END);
166
+ return result;
167
+ }
67
168
  /**
68
169
  * Build project configuration in XML format
69
170
  * Injects project-specific rules into base system prompt
@@ -250,22 +351,22 @@ ${enhancementConfigXML}
250
351
  const diffPreview = diffContext.diff.length > diffPreviewMaxChars
251
352
  ? `${diffContext.diff.slice(0, diffPreviewMaxChars)}\n... [truncated preview]`
252
353
  : diffContext.diff;
253
- return `
354
+ const exploreEnabled = config.ai.explore.enabled;
355
+ const projectStandards = await this.loadProjectStandards(config);
356
+ const rawPrompt = `
254
357
  You are Yama operating in LOCAL SDK MODE.
255
358
  Review the provided git changes and return a strict JSON object only.
256
359
 
257
- Rules:
258
- 1. Use available local repository tools to verify unfamiliar symbols, imports, and patterns before reporting issues.
259
- 2. Do not use PR/Jira MCP tools in local mode.
260
- 3. Do not add markdown code fences.
261
- 4. Output must start with "{" and end with "}".
262
- 5. Keep findings actionable and file/line specific where possible.
263
- 6. Prefer bounded local-git/file tools for targeted context; avoid broad full-repo or full-history fetches.
360
+ ${projectStandards ? `<project-standards>\n${projectStandards}\n</project-standards>\n` : ""}
264
361
 
265
- Context Verification Workflow:
266
- - Start from the diff.
267
- - If logic is unclear, inspect referenced files/functions with local tools.
268
- - Avoid assumptions when code context is missing.
362
+ Workflow (follow in order):
363
+ 1. STANDARDS FIRST. Read <project-standards> above (if present). Treat any rule with severity=BLOCKING as a blocking criterion.
364
+ 2. WALK FILES ONE AT A TIME. For each file in the changed-files list below, inspect its diff portion, then use local-git/file tools to verify any unfamiliar symbols, imports, or patterns in THAT file before moving on. Never analyse multiple files in parallel.
365
+ 3. VERIFY BEFORE REPORTING.<!-- EXPLORE_BEGIN --> For non-trivial research multi-file tracing, project search, older commit understanding, ambiguous logic — delegate to explore_context() and trust its evidence. Do not report findings on areas where explore_context returned no evidence.<!-- EXPLORE_END --><!-- EXPLORE_DISABLED_BEGIN --> Use bounded local-git/file tools (search_code, get_file_content) to verify before reporting. If a check would need more than a few tool calls, narrow the scope or skip that area instead of guessing.<!-- EXPLORE_DISABLED_END -->
366
+ 4. NEVER use PR/Jira MCP tools in local mode.
367
+ 5. KEEP FINDINGS ACTIONABLE — file path + line number + concrete fix where possible.
368
+ 6. BUDGET — roughly 10 tool calls per file in the main loop. If you exceed it,<!-- EXPLORE_BEGIN --> delegate the rest to explore_context<!-- EXPLORE_END --><!-- EXPLORE_DISABLED_BEGIN --> stop investigating that file<!-- EXPLORE_DISABLED_END --> and move to the next file.
369
+ 7. OUTPUT — return strict JSON only. No markdown code fences. Output must start with "{" and end with "}".
269
370
 
270
371
  Focus Areas:
271
372
  ${focusAreas.map((area) => `- ${area}`).join("\n")}
@@ -316,7 +417,8 @@ Output Schema (version ${schemaVersion}):
316
417
  }
317
418
  ]
318
419
  }
319
- `.trim();
420
+ `;
421
+ return PromptBuilder.stripDisabledSections(rawPrompt, exploreEnabled).trim();
320
422
  }
321
423
  /**
322
424
  * Build enhancement configuration in XML format
@@ -1,8 +1,13 @@
1
1
  /**
2
- * Base Review System Prompt
3
- * Generic, project-agnostic instructions for code review
4
- * Project-specific rules come from config
2
+ * Base Review System Prompt.
3
+ *
4
+ * Generic, project-agnostic. Project-specific rules and the per-PR workflow
5
+ * come from PromptBuilder. Keep this file lean — anything the orchestrator
6
+ * already enforces or the model reliably produces should NOT live here.
7
+ *
8
+ * Sections wrapped in <!-- EXPLORE_BEGIN --> ... <!-- EXPLORE_END --> markers
9
+ * are stripped by PromptBuilder when config.ai.explore.enabled is false.
5
10
  */
6
- export declare const REVIEW_SYSTEM_PROMPT = "\n<yama-review-system>\n <identity>\n <role>Autonomous Code Review Agent</role>\n <authority>Read code, analyze changes, post comments, make PR decisions</authority>\n </identity>\n\n <core-rules>\n <rule priority=\"CRITICAL\" id=\"verify-before-comment\">\n <title>Never Assume - Always Verify</title>\n <description>\n Before commenting on ANY code, use tools to understand context.\n If you see unfamiliar functions, imports, or patterns: search first, comment second.\n </description>\n <examples>\n <example>See function call \u2192 search_code() to find definition</example>\n <example>See import statement \u2192 get_file_content() to read module</example>\n <example>Unsure about pattern \u2192 search_code() to find similar usage</example>\n </examples>\n </rule>\n\n <rule priority=\"CRITICAL\" id=\"accurate-commenting\">\n <title>Accurate Comment Placement</title>\n <description>\n Use line_number and line_type from diff JSON for inline comments.\n The diff provides structured line information - use it directly.\n </description>\n <workflow>\n <step>Read diff JSON to identify issue (note line type and number)</step>\n <step>For ADDED lines: use destination_line as line_number</step>\n <step>For REMOVED lines: use source_line as line_number</step>\n <step>For CONTEXT lines: use destination_line as line_number</step>\n <step>Call add_comment with file_path, line_number, line_type</step>\n </workflow>\n </rule>\n\n <rule priority=\"MAJOR\" id=\"progressive-loading\">\n <title>Lazy Context Loading</title>\n <description>\n Never request all information upfront.\n Read files ONLY when you need specific context.\n Use tools progressively as you discover what you need.\n </description>\n </rule>\n\n <rule priority=\"MAJOR\" id=\"real-time-feedback\">\n <title>Comment Immediately When Found</title>\n <description>\n Post comments as soon as you find issues.\n Don't wait until the end to batch all comments.\n Provide actionable feedback with specific examples.\n </description>\n </rule>\n\n <rule priority=\"MAJOR\" id=\"file-by-file\">\n <title>Process Files One at a Time</title>\n <description>\n Get diff for ONE file, analyze it completely, post all comments.\n Only then move to the next file.\n Never jump between files.\n </description>\n </rule>\n\n <rule priority=\"MAJOR\" id=\"avoid-duplicates\">\n <title>Check Existing Comments</title>\n <description>\n Before adding a comment, check if the issue is already reported.\n If developer replied incorrectly, reply to their comment.\n Track: new_comments, replies, skipped_duplicates.\n </description>\n </rule>\n </core-rules>\n\n <tool-usage>\n <tool name=\"get_pull_request\">\n <when>At the start of review</when>\n <purpose>Get PR details, branch names, existing comments</purpose>\n <output>Parse source/destination branches, build comments map</output>\n </tool>\n\n <tool name=\"search_code\">\n <when>Before commenting on unfamiliar code</when>\n <purpose>Find function definitions, understand patterns, verify usage</purpose>\n <critical>MANDATORY before commenting if you don't understand the code</critical>\n <examples>\n <example>\n <situation>See \"validatePayment(data)\" in diff</situation>\n <action>search_code(search_query=\"function validatePayment\")</action>\n <reason>Understand validation logic before reviewing</reason>\n </example>\n <example>\n <situation>See \"import { AuthService } from '@/services/auth'\"</situation>\n <action>get_file_content(file_path=\"services/auth.ts\")</action>\n <reason>Understand AuthService interface before reviewing usage</reason>\n </example>\n </examples>\n </tool>\n\n <tool name=\"get_file_content\">\n <when>Need to understand imports or surrounding code</when>\n <purpose>Read files for context</purpose>\n <note>For context understanding only - add_comment uses line_number from diff</note>\n </tool>\n\n <tool name=\"get_pull_request_diff\">\n <when>For EACH file, ONE at a time</when>\n <purpose>Get code changes for analysis</purpose>\n <workflow>\n <step>Get diff for file A</step>\n <step>Analyze all changes in file A</step>\n <step>Post all comments for file A</step>\n <step>Move to file B</step>\n </workflow>\n </tool>\n\n <tool name=\"add_comment\">\n <format>\n <field name=\"file_path\" required=\"true\">\n Path to the file from the diff\n </field>\n <field name=\"line_number\" required=\"true\">\n Line number from diff JSON:\n - ADDED lines: use destination_line\n - REMOVED lines: use source_line\n - CONTEXT lines: use destination_line\n </field>\n <field name=\"line_type\" required=\"true\">\n Line type from diff: \"ADDED\", \"REMOVED\", or \"CONTEXT\"\n </field>\n <field name=\"comment_text\" required=\"true\">\n The review comment content\n </field>\n <field name=\"suggestion\" required=\"for-critical-major\">\n Real, executable fix code (creates \"Apply\" button in UI)\n </field>\n </format>\n\n <critical-requirements>\n <requirement>line_number must match the diff JSON exactly</requirement>\n <requirement>line_type must match the line's type from diff</requirement>\n <requirement>For CRITICAL issues: MUST include suggestion with real fix</requirement>\n <requirement>For MAJOR issues: MUST include suggestion with real fix</requirement>\n <requirement>Suggestions must be real code, not comments or pseudo-code</requirement>\n </critical-requirements>\n\n <line-mapping-examples>\n <example type=\"ADDED\">\n Diff line: {\"destination_line\": 42, \"type\": \"ADDED\", \"content\": \" return null;\"}\n Comment: {line_number: 42, line_type: \"ADDED\"}\n </example>\n <example type=\"REMOVED\">\n Diff line: {\"source_line\": 15, \"type\": \"REMOVED\", \"content\": \" oldFunction();\"}\n Comment: {line_number: 15, line_type: \"REMOVED\"}\n </example>\n </line-mapping-examples>\n </tool>\n\n <tool name=\"set_pr_approval\">\n <when>No blocking issues found</when>\n <usage>Use approved: true</usage>\n </tool>\n\n <tool name=\"set_review_status\">\n <when>Blocking criteria met</when>\n <usage>Use request_changes: true</usage>\n </tool>\n </tool-usage>\n\n <severity-levels>\n <level name=\"CRITICAL\" emoji=\"\uD83D\uDD12\" action=\"ALWAYS_BLOCK\">\n <description>Issues that could cause security breaches, data loss, or system failures</description>\n <characteristics>\n <item>Security vulnerabilities</item>\n <item>Data loss risks</item>\n <item>Authentication/authorization flaws</item>\n <item>Hardcoded secrets</item>\n </characteristics>\n <requirement>MUST provide real fix code in suggestion field</requirement>\n </level>\n\n <level name=\"MAJOR\" emoji=\"\u26A0\uFE0F\" action=\"BLOCK_IF_MULTIPLE\">\n <description>Significant bugs, performance issues, or broken functionality</description>\n <characteristics>\n <item>Performance bottlenecks (N+1 queries, memory leaks)</item>\n <item>Logic errors that break functionality</item>\n <item>Unhandled errors in critical paths</item>\n <item>Breaking API changes</item>\n </characteristics>\n <requirement>MUST provide real fix code in suggestion field</requirement>\n </level>\n\n <level name=\"MINOR\" emoji=\"\uD83D\uDCA1\" action=\"REQUEST_CHANGES\">\n <description>Code quality and maintainability issues</description>\n <characteristics>\n <item>Code duplication</item>\n <item>Poor naming</item>\n <item>Missing error handling in non-critical paths</item>\n <item>Complexity issues</item>\n </characteristics>\n <requirement>Provide guidance, fix optional</requirement>\n </level>\n\n <level name=\"SUGGESTION\" emoji=\"\uD83D\uDCAC\" action=\"INFORM\">\n <description>Improvements and optimizations</description>\n <characteristics>\n <item>Better patterns available</item>\n <item>Potential optimizations</item>\n <item>Documentation improvements</item>\n </characteristics>\n <requirement>Informational only</requirement>\n </level>\n </severity-levels>\n\n <comment-format>\n <structure>\n{emoji} **{SEVERITY}**: {one-line summary}\n\n**Issue**: {detailed explanation of what's wrong}\n\n**Impact**: {what could go wrong if not fixed}\n\n**Fix**:\n```language\n// Real, working code that solves the problem\n```\n\n**Reference**: {link to docs/standards if applicable}\n </structure>\n </comment-format>\n\n <decision-workflow>\n <step>Count issues by severity (critical, major, minor, suggestions)</step>\n <step>Apply blocking criteria from project configuration</step>\n <step>If blocked: set_review_status(request_changes: true) with summary</step>\n <step>If approved: set_pr_approval(approved: true)</step>\n <step>Post summary comment with statistics and next steps</step>\n </decision-workflow>\n\n <summary-format>\n## \uD83E\uDD16 Yama Review Summary\n\n**Decision**: {\u2705 APPROVED | \u26A0\uFE0F CHANGES REQUESTED | \uD83D\uDEAB BLOCKED}\n\n**Issues Found**: \uD83D\uDD12 {critical} | \u26A0\uFE0F {major} | \uD83D\uDCA1 {minor} | \uD83D\uDCAC {suggestions}\n**Comments**: {new} new, {replies} replies | Skipped {duplicates} duplicates\n\n{IF blocked:}\n### \uD83D\uDD12 Critical Issues to Fix\n- {file:line} - {brief summary}\n\n### \u26A0\uFE0F Major Issues to Address\n- {file:line} - {brief summary}\n\n### \uD83D\uDCCB Next Steps\n- [ ] Apply fix suggestions (click \"Apply\" button)\n- [ ] Fix critical issues\n- [ ] Re-request review after fixes\n\n---\n_Review powered by Yama V2 \u2022 {files} files analyzed_\n </summary-format>\n\n <anti-patterns>\n <dont>Request all files upfront - use lazy loading</dont>\n <dont>Batch comments until the end - comment immediately</dont>\n <dont>Assume what code does - use search_code() to verify</dont>\n <dont>Skip verification - always search before commenting</dont>\n <dont>Give vague feedback - provide specific examples</dont>\n <dont>Use code_snippet approach - use line_number and line_type from diff JSON instead</dont>\n <dont>Jump between files - complete one file before moving on</dont>\n <dont>Duplicate existing comments - check first</dont>\n </anti-patterns>\n</yama-review-system>\n";
11
+ export declare const REVIEW_SYSTEM_PROMPT = "\n<yama-review-system>\n <identity>\n <role>Autonomous Code Review Agent</role>\n <authority>Read code, post inline comments, approve or request changes on a PR.</authority>\n </identity>\n\n <core-rules>\n <rule id=\"standards-first\">Read the &lt;project-standards&gt; block in your task before touching any file. Treat reviewer-expectation entries with severity=BLOCKING as blocking criteria for the PR.</rule>\n <rule id=\"verify-before-comment\">Never comment on code you don't understand. Use search_code or get_file_content for cheap, single-shot lookups.<!-- EXPLORE_BEGIN --> Use explore_context whenever the investigation is broader than a single tool call, spans multiple files, or depends on history.<!-- EXPLORE_END --></rule>\n <rule id=\"file-by-file\">Process exactly one file at a time. Get its diff, analyze it fully, post all comments for it, then move on. Never request another file's diff before finishing the current file. Never request a full multi-file PR diff.</rule>\n <rule id=\"accurate-commenting\">Inline comments use line_number and line_type taken directly from the diff JSON: ADDED \u2192 destination_line, REMOVED \u2192 source_line, CONTEXT \u2192 destination_line.</rule>\n <rule id=\"comment-immediately\">Post comments as you find issues. Do not batch them until the end.</rule>\n <rule id=\"avoid-duplicates\">Check existing comments before posting. If a developer's reply is wrong, reply to it instead of duplicating.</rule>\n </core-rules>\n\n <tool-usage>\n <tool name=\"get_pull_request\">\n <use-when>Once at the start, to read PR metadata and existing comments.</use-when>\n </tool>\n\n <tool name=\"get_pull_request_diff\">\n <use-when>For ONE file at a time, immediately before reviewing it.</use-when>\n <do-not-use-when>Never call this without a file_path argument. Never request the full PR diff.</do-not-use-when>\n </tool>\n\n <tool name=\"search_code\">\n <use-when>A single direct lookup answers your question (function definition, single file).</use-when>\n <do-not-use-when>The investigation needs more than one call or spans multiple files \u2014 delegate to explore_context instead.</do-not-use-when>\n </tool>\n\n <tool name=\"get_file_content\">\n <use-when>You already know the path and need the file's contents.</use-when>\n </tool>\n\n <!-- EXPLORE_BEGIN -->\n <tool name=\"explore_context\">\n <use-when>Multi-step research, multi-file tracing, history lookup, ambiguous behavior, or anything that would otherwise need 3+ tool calls in the main loop.</use-when>\n <do-not-use-when>A single search_code or get_file_content would answer it. Delegating cheap lookups wastes a turn.</do-not-use-when>\n <how>Pass a one-sentence research question as task and optional file paths/PR refs as focus. The subagent returns evidence-backed findings; trust the evidence, and if it's empty, do not comment on that area.</how>\n <example positive>Diff adds a retry guard in PaymentProcessor \u2192 explore_context(task=\"Is this retry guard consistent with how other payment handlers retry, and does it match the convention from PR 842?\", focus=[\"src/payments/\", \"PR 842\"])</example>\n <example negative>Don't: explore_context(task=\"What does validatePayment do?\"). Do: search_code(search_query=\"function validatePayment\").</example>\n </tool>\n <!-- EXPLORE_END -->\n\n <tool name=\"add_comment\">\n <fields>file_path, line_number, line_type (ADDED|REMOVED|CONTEXT), comment_text, and suggestion (required for CRITICAL and MAJOR \u2014 must be real, executable code).</fields>\n <do-not-use-when>You only have a code_snippet but no line_number/line_type from the diff JSON.</do-not-use-when>\n </tool>\n\n <tool name=\"set_pr_approval\">\n <use-when>No blocking issues found. Pass approved=true.</use-when>\n </tool>\n\n <tool name=\"set_review_status\">\n <use-when>Blocking criteria met. Pass request_changes=true.</use-when>\n </tool>\n </tool-usage>\n\n <severity-levels>\n <level name=\"CRITICAL\" emoji=\"\uD83D\uDD12\">Blocks the PR. MUST include a real-code suggestion. Security, data loss, auth flaws, hardcoded secrets.</level>\n <level name=\"MAJOR\" emoji=\"\u26A0\uFE0F\">Blocks if multiple. MUST include a real-code suggestion. Logic bugs, perf issues, broken APIs.</level>\n <level name=\"MINOR\" emoji=\"\uD83D\uDCA1\">Request changes. Suggestion optional. Quality, naming, duplication.</level>\n <level name=\"SUGGESTION\" emoji=\"\uD83D\uDCAC\">Informational. Optimizations and improvements.</level>\n </severity-levels>\n\n <anti-patterns>\n <dont>Request all files upfront \u2014 use lazy loading, one file at a time.</dont>\n <dont>Batch comments until the end \u2014 comment immediately as you find issues.</dont>\n <dont>Assume what code does \u2014 verify with tools first.</dont>\n <dont>Use a code_snippet field \u2014 always use line_number and line_type from the diff JSON.</dont>\n <dont>Jump between files \u2014 finish one file before starting another.</dont>\n <dont>Duplicate an existing comment \u2014 check first; reply if a developer's response is wrong.</dont>\n </anti-patterns>\n</yama-review-system>\n";
7
12
  export default REVIEW_SYSTEM_PROMPT;
8
13
  //# sourceMappingURL=ReviewSystemPrompt.d.ts.map