@juspay/yama 2.2.2 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,3 +1,10 @@
1
+ # [2.3.0](https://github.com/juspay/yama/compare/v2.2.2...v2.3.0) (2026-04-01)
2
+
3
+
4
+ ### Features
5
+
6
+ * **memory:** add per-repo memory management and configuration ([0b1fd5d](https://github.com/juspay/yama/commit/0b1fd5de8f0e46402f3c54766a5e0c7bd937cca2))
7
+
1
8
  ## [2.2.2](https://github.com/juspay/yama/compare/v2.2.1...v2.2.2) (2026-03-26)
2
9
 
3
10
 
package/README.md CHANGED
@@ -28,6 +28,8 @@
28
28
  ```
29
29
  YamaOrchestrator
30
30
 
31
+ MemoryManager (per-repo condensed memory)
32
+
31
33
  NeuroLink AI Agent (Autonomous)
32
34
 
33
35
  MCP Tools (Bitbucket + Jira)
@@ -38,6 +40,7 @@ Pull Request Operations
38
40
  ### AI Autonomous Workflow
39
41
 
40
42
  1. **Context Gathering** (AI-driven)
43
+ - Reads per-repo memory (past review learnings)
41
44
  - Reads PR details
42
45
  - Finds and reads Jira ticket
43
46
  - Loads project standards from memory-bank
@@ -311,6 +314,17 @@ AI uses tools to understand code:
311
314
  - `get_file_content()` - Read related files
312
315
  - `list_directory_content()` - Explore structure
313
316
 
317
+ ### Per-Repo Memory
318
+
319
+ AI learns from past reviews and remembers across PRs:
320
+
321
+ - Reads condensed memory before each review for context
322
+ - Writes learnings after PR merge (false positives, missed issues, team conventions)
323
+ - LLM-powered condensation keeps memory within a configurable word limit
324
+ - Per-repo isolation — each repository gets independent memory keyed by `workspace-repository`
325
+ - Storage as `.md` files at configurable path (e.g., `memory-bank/yama/memory/`)
326
+ - Environment variable overrides for all settings (`YAMA_MEMORY_ENABLED`, `YAMA_MEMORY_MAX_WORDS`, etc.)
327
+
314
328
  ## Blocking Criteria
315
329
 
316
330
  AI applies these criteria automatically:
package/dist/index.d.ts CHANGED
@@ -3,12 +3,14 @@
3
3
  * Main export file
4
4
  */
5
5
  export { YamaOrchestrator, createYama, YamaOrchestrator as YamaV2Orchestrator, createYama as createYamaV2, } from "./v2/core/YamaV2Orchestrator.js";
6
+ export { LearningOrchestrator, createLearningOrchestrator, } from "./v2/core/LearningOrchestrator.js";
6
7
  export { ConfigLoader } from "./v2/config/ConfigLoader.js";
7
8
  export { MCPServerManager } from "./v2/core/MCPServerManager.js";
8
9
  export { SessionManager } from "./v2/core/SessionManager.js";
9
10
  export { PromptBuilder } from "./v2/prompts/PromptBuilder.js";
11
+ export { MemoryManager } from "./v2/memory/MemoryManager.js";
10
12
  export type { LocalReviewFinding, LocalReviewRequest, LocalReviewResult, ReviewRequest, ReviewMode, ReviewResult, ReviewUpdate, ReviewSession, ReviewStatistics, IssuesBySeverity, TokenUsage, UnifiedReviewRequest, } from "./v2/types/v2.types.js";
11
- export type { YamaConfig, YamaInitOptions, YamaConfig as YamaV2Config, AIConfig, MCPServersConfig, ReviewConfig, DescriptionEnhancementConfig, } from "./v2/types/config.types.js";
13
+ export type { YamaConfig, YamaInitOptions, YamaConfig as YamaV2Config, AIConfig, MCPServersConfig, ReviewConfig, DescriptionEnhancementConfig, MemoryConfig, } from "./v2/types/config.types.js";
12
14
  export type { GetPullRequestResponse, GetPullRequestDiffResponse, GetIssueResponse, SearchCodeResponse, } from "./v2/types/mcp.types.js";
13
15
  export declare const VERSION = "2.2.1";
14
16
  export { createYama as default } from "./v2/core/YamaV2Orchestrator.js";
package/dist/index.js CHANGED
@@ -6,10 +6,12 @@
6
6
  // Core Exports
7
7
  // ============================================================================
8
8
  export { YamaOrchestrator, createYama, YamaOrchestrator as YamaV2Orchestrator, createYama as createYamaV2, } from "./v2/core/YamaV2Orchestrator.js";
9
+ export { LearningOrchestrator, createLearningOrchestrator, } from "./v2/core/LearningOrchestrator.js";
9
10
  export { ConfigLoader } from "./v2/config/ConfigLoader.js";
10
11
  export { MCPServerManager } from "./v2/core/MCPServerManager.js";
11
12
  export { SessionManager } from "./v2/core/SessionManager.js";
12
13
  export { PromptBuilder } from "./v2/prompts/PromptBuilder.js";
14
+ export { MemoryManager } from "./v2/memory/MemoryManager.js";
13
15
  // ============================================================================
14
16
  // Version Information
15
17
  // ============================================================================
@@ -42,6 +42,21 @@ export declare class ConfigLoader {
42
42
  * Apply environment variable overrides
43
43
  */
44
44
  private applyEnvironmentOverrides;
45
+ /**
46
+ * Apply memory-related environment variable overrides.
47
+ *
48
+ * Env vars (YAMA_MEMORY_*) take precedence over yaml config.
49
+ * If YAMA_MEMORY_ENABLED is set but no memory config exists in yaml,
50
+ * a default config is created so the other overrides have a target.
51
+ *
52
+ * Supported env vars:
53
+ * YAMA_MEMORY_ENABLED — "true" / "false"
54
+ * YAMA_MEMORY_STORAGE_PATH — e.g. "memory-bank/yama/memory"
55
+ * YAMA_MEMORY_MAX_WORDS — e.g. "200"
56
+ * YAMA_MEMORY_AUTO_COMMIT — "true" / "false"
57
+ * YAMA_MEMORY_PROMPT — custom condensation prompt
58
+ */
59
+ private applyMemoryOverrides;
45
60
  /**
46
61
  * Basic configuration validation
47
62
  */
@@ -32,6 +32,7 @@ export class ConfigLoader {
32
32
  else {
33
33
  console.log(" Using default configuration (no config file found)");
34
34
  }
35
+ config.memory = this.applyMemoryOverrides(config.memory);
35
36
  // Layer 4: Apply SDK instance overrides (highest priority)
36
37
  if (instanceOverrides) {
37
38
  config = this.mergeConfigs(config, instanceOverrides);
@@ -199,6 +200,39 @@ export class ConfigLoader {
199
200
  }
200
201
  return config;
201
202
  }
203
+ /**
204
+ * Apply memory-related environment variable overrides.
205
+ *
206
+ * Env vars (YAMA_MEMORY_*) take precedence over yaml config.
207
+ * If YAMA_MEMORY_ENABLED is set but no memory config exists in yaml,
208
+ * a default config is created so the other overrides have a target.
209
+ *
210
+ * Supported env vars:
211
+ * YAMA_MEMORY_ENABLED — "true" / "false"
212
+ * YAMA_MEMORY_STORAGE_PATH — e.g. "memory-bank/yama/memory"
213
+ * YAMA_MEMORY_MAX_WORDS — e.g. "200"
214
+ * YAMA_MEMORY_AUTO_COMMIT — "true" / "false"
215
+ * YAMA_MEMORY_PROMPT — custom condensation prompt
216
+ */
217
+ applyMemoryOverrides(memory) {
218
+ const env = process.env;
219
+ if (env.YAMA_MEMORY_ENABLED) {
220
+ memory.enabled = env.YAMA_MEMORY_ENABLED === "true";
221
+ }
222
+ if (env.YAMA_MEMORY_STORAGE_PATH) {
223
+ memory.storagePath = env.YAMA_MEMORY_STORAGE_PATH;
224
+ }
225
+ if (env.YAMA_MEMORY_MAX_WORDS) {
226
+ memory.maxWords = parseInt(env.YAMA_MEMORY_MAX_WORDS, 10);
227
+ }
228
+ if (env.YAMA_MEMORY_AUTO_COMMIT) {
229
+ memory.autoCommit = env.YAMA_MEMORY_AUTO_COMMIT === "true";
230
+ }
231
+ if (env.YAMA_MEMORY_PROMPT) {
232
+ memory.prompt = env.YAMA_MEMORY_PROMPT;
233
+ }
234
+ return memory;
235
+ }
202
236
  /**
203
237
  * Basic configuration validation
204
238
  */
@@ -158,6 +158,11 @@ export class DefaultConfig {
158
158
  summaryRetentionCount: 20,
159
159
  autoCommit: false,
160
160
  },
161
+ memory: {
162
+ enabled: false,
163
+ storagePath: ".yama/memory",
164
+ maxWords: 200,
165
+ },
161
166
  projectStandards: {
162
167
  customPromptsPath: "config/prompts/",
163
168
  additionalFocusAreas: [],
@@ -9,6 +9,7 @@ export declare class LearningOrchestrator {
9
9
  private mcpManager;
10
10
  private configLoader;
11
11
  private promptManager;
12
+ private memoryManager;
12
13
  private config;
13
14
  private initialized;
14
15
  constructor();
@@ -8,12 +8,14 @@ import { MCPServerManager } from "./MCPServerManager.js";
8
8
  import { ConfigLoader } from "../config/ConfigLoader.js";
9
9
  import { LangfusePromptManager } from "../prompts/LangfusePromptManager.js";
10
10
  import { KnowledgeBaseManager } from "../learning/KnowledgeBaseManager.js";
11
+ import { MemoryManager } from "../memory/MemoryManager.js";
11
12
  import { buildObservabilityConfigFromEnv, validateObservabilityConfig, } from "../utils/ObservabilityConfig.js";
12
13
  export class LearningOrchestrator {
13
14
  neurolink;
14
15
  mcpManager;
15
16
  configLoader;
16
17
  promptManager;
18
+ memoryManager = null;
17
19
  config;
18
20
  initialized = false;
19
21
  constructor() {
@@ -32,7 +34,12 @@ export class LearningOrchestrator {
32
34
  try {
33
35
  // Load configuration
34
36
  this.config = await this.configLoader.loadConfig(configPath);
35
- // Initialize NeuroLink
37
+ // Initialize Memory Manager (if enabled) — must happen before NeuroLink
38
+ if (this.config.memory?.enabled) {
39
+ this.memoryManager = new MemoryManager(this.config.memory, this.config.ai.provider, this.config.ai.model);
40
+ console.log(" 🧠 Per-repo memory enabled\n");
41
+ }
42
+ // Initialize NeuroLink with memory config injected
36
43
  console.log(" 🔧 Initializing NeuroLink AI engine...");
37
44
  this.neurolink = this.initializeNeurolink();
38
45
  console.log(" ✅ NeuroLink initialized\n");
@@ -126,6 +133,52 @@ export class LearningOrchestrator {
126
133
  if (duplicateCount > 0) {
127
134
  console.log(` ⏭️ Skipped ${duplicateCount} duplicates`);
128
135
  }
136
+ // Resolve commit mode: commitMode takes precedence, fall back to legacy commit flag
137
+ const commitMode = request.commitMode ||
138
+ (request.commit || this.config.knowledgeBase.autoCommit
139
+ ? "kb"
140
+ : undefined);
141
+ const shouldCommitMemory = commitMode === "memory" || commitMode === "all";
142
+ const shouldCommitKb = commitMode === "kb" || commitMode === "all";
143
+ // Store learnings in per-repo memory via NeuroLink
144
+ // read: false (we don't need to read memory here)
145
+ // write: true (condense learnings into memory file)
146
+ if (this.memoryManager && addedCount > 0 && shouldCommitMemory) {
147
+ try {
148
+ const byCategory = new Map();
149
+ for (const l of learnings) {
150
+ const cat = l.category.replace(/_/g, " ");
151
+ if (!byCategory.has(cat)) {
152
+ byCategory.set(cat, []);
153
+ }
154
+ byCategory.get(cat).push(l.learning);
155
+ }
156
+ const parts = [];
157
+ for (const [category, items] of byCategory) {
158
+ parts.push(`${category}: ${items.join("; ")}`);
159
+ }
160
+ const ownerId = MemoryManager.buildOwnerId(request.workspace, request.repository);
161
+ await this.neurolink.generate({
162
+ input: { text: parts.join(". ") },
163
+ provider: this.config.ai.provider,
164
+ model: this.config.ai.model,
165
+ temperature: 0.1,
166
+ maxTokens: 100,
167
+ disableTools: true,
168
+ context: {
169
+ userId: ownerId,
170
+ operation: "memory-store-learnings",
171
+ },
172
+ memory: { read: false, write: true },
173
+ });
174
+ console.log(` 🧠 Learning memory stored for ${ownerId}`);
175
+ // Auto-commit and push memory files to repo
176
+ await this.memoryManager.commitMemoryChanges();
177
+ }
178
+ catch (error) {
179
+ console.warn(` ⚠️ Failed to store learning memory: ${error.message}`);
180
+ }
181
+ }
129
182
  // Check if summarization is needed
130
183
  let summarized = false;
131
184
  if (request.summarize || (await kbManager.needsSummarization())) {
@@ -134,9 +187,9 @@ export class LearningOrchestrator {
134
187
  summarized = true;
135
188
  console.log(" ✅ Summarization complete");
136
189
  }
137
- // Commit if requested
190
+ // Commit knowledge base if requested
138
191
  let committed = false;
139
- if (request.commit || this.config.knowledgeBase.autoCommit) {
192
+ if (shouldCommitKb) {
140
193
  console.log("\n📤 Committing knowledge base...");
141
194
  await kbManager.commit(request.pullRequestId, addedCount);
142
195
  committed = true;
@@ -352,8 +405,15 @@ Analyze the PR data above and extract learnings.
352
405
  */
353
406
  initializeNeurolink() {
354
407
  const observabilityConfig = buildObservabilityConfigFromEnv();
408
+ const conversationMemory = {
409
+ ...this.config.ai.conversationMemory,
410
+ };
411
+ if (this.memoryManager) {
412
+ conversationMemory.memory =
413
+ this.memoryManager.buildNeuroLinkMemoryConfig();
414
+ }
355
415
  const neurolinkConfig = {
356
- conversationMemory: this.config.ai.conversationMemory,
416
+ conversationMemory,
357
417
  };
358
418
  if (observabilityConfig &&
359
419
  validateObservabilityConfig(observabilityConfig)) {
@@ -10,6 +10,7 @@ export declare class YamaOrchestrator {
10
10
  private configLoader;
11
11
  private promptBuilder;
12
12
  private sessionManager;
13
+ private memoryManager;
13
14
  private localDiffSource;
14
15
  private config;
15
16
  private initialized;
@@ -64,9 +65,7 @@ export declare class YamaOrchestrator {
64
65
  * Export session data
65
66
  */
66
67
  exportSession(sessionId: string): any;
67
- /**
68
- * Create tool context for AI
69
- */
68
+ private getUserId;
70
69
  private createToolContext;
71
70
  /**
72
71
  * Parse AI response into structured review result
@@ -105,10 +104,6 @@ export declare class YamaOrchestrator {
105
104
  */
106
105
  private calculateCost;
107
106
  private toSafeNumber;
108
- /**
109
- * Generate userId for NeuroLink context from repository and branch/PR
110
- */
111
- private generateUserId;
112
107
  private isLocalReviewRequest;
113
108
  /**
114
109
  * Query-level tool filtering for PR mode.
@@ -7,6 +7,7 @@ import { MCPServerManager } from "./MCPServerManager.js";
7
7
  import { ConfigLoader } from "../config/ConfigLoader.js";
8
8
  import { PromptBuilder } from "../prompts/PromptBuilder.js";
9
9
  import { SessionManager } from "./SessionManager.js";
10
+ import { MemoryManager } from "../memory/MemoryManager.js";
10
11
  import { LocalDiffSource } from "./LocalDiffSource.js";
11
12
  import { buildObservabilityConfigFromEnv, validateObservabilityConfig, } from "../utils/ObservabilityConfig.js";
12
13
  export class YamaOrchestrator {
@@ -15,6 +16,7 @@ export class YamaOrchestrator {
15
16
  configLoader;
16
17
  promptBuilder;
17
18
  sessionManager;
19
+ memoryManager = null;
18
20
  localDiffSource;
19
21
  config;
20
22
  initialized = false;
@@ -40,13 +42,18 @@ export class YamaOrchestrator {
40
42
  const resolvedConfigPath = configPath || this.initOptions.configPath;
41
43
  this.config = await this.configLoader.loadConfig(resolvedConfigPath, this.initOptions.configOverrides);
42
44
  this.showBanner();
43
- // Step 2: Initialize NeuroLink
45
+ // Step 2: Initialize
46
+ if (this.config.memory?.enabled) {
47
+ this.memoryManager = new MemoryManager(this.config.memory, this.config.ai.provider, this.config.ai.model);
48
+ console.log(" 🧠 Per-repo memory enabled\n");
49
+ }
50
+ // Step 3: Initialize NeuroLink with memory config injected
44
51
  console.log("🧠 Initializing NeuroLink AI engine...");
45
52
  this.neurolink = this.initializeNeurolink();
46
53
  console.log("✅ NeuroLink initialized\n");
47
54
  this.initialized = true;
48
55
  }
49
- // Step 3: Mode-specific setup
56
+ // Step 4: Mode-specific setup
50
57
  if (mode === "pr" && !this.mcpInitialized) {
51
58
  await this.mcpManager.setupMCPServers(this.neurolink, this.config.mcpServers);
52
59
  this.mcpInitialized = true;
@@ -55,7 +62,7 @@ export class YamaOrchestrator {
55
62
  await this.mcpManager.setupLocalGitMCPServer(this.neurolink);
56
63
  this.localGitMcpInitialized = true;
57
64
  }
58
- // Step 4: Mode-specific validation
65
+ // Step 5: Mode-specific validation
59
66
  await this.configLoader.validate(mode);
60
67
  console.log("✅ Yama initialized successfully\n");
61
68
  console.log("═".repeat(60) + "\n");
@@ -92,6 +99,7 @@ export class YamaOrchestrator {
92
99
  // Execute autonomous AI review
93
100
  console.log("🤖 Starting autonomous AI review...");
94
101
  console.log(" AI will now make decisions and execute actions autonomously\n");
102
+ // Review call: RETRIEVE memory (for context), but DON'T STORE
95
103
  const aiResponse = await this.neurolink.generate({
96
104
  input: { text: instructions },
97
105
  provider: this.config.ai.provider,
@@ -103,10 +111,11 @@ export class YamaOrchestrator {
103
111
  ...this.getPRToolFilteringOptions(instructions),
104
112
  context: {
105
113
  sessionId,
106
- userId: this.generateUserId(request),
114
+ userId: this.getUserId(request),
107
115
  operation: "code-review",
108
116
  metadata: toolContext.metadata,
109
117
  },
118
+ memory: { read: true, write: false },
110
119
  enableAnalytics: this.config.ai.enableAnalytics,
111
120
  enableEvaluation: this.config.ai.enableEvaluation,
112
121
  });
@@ -229,8 +238,9 @@ export class YamaOrchestrator {
229
238
  ...this.getPRToolFilteringOptions(instructions),
230
239
  context: {
231
240
  sessionId,
232
- userId: this.generateUserId(request),
241
+ userId: this.getUserId(request),
233
242
  },
243
+ memory: { enabled: false },
234
244
  enableAnalytics: true,
235
245
  });
236
246
  yield {
@@ -290,10 +300,11 @@ export class YamaOrchestrator {
290
300
  ...this.getPRToolFilteringOptions(reviewInstructions),
291
301
  context: {
292
302
  sessionId,
293
- userId: this.generateUserId(request),
303
+ userId: this.getUserId(request),
294
304
  operation: "code-review",
295
305
  metadata: toolContext.metadata,
296
306
  },
307
+ memory: { read: true, write: false },
297
308
  enableAnalytics: this.config.ai.enableAnalytics,
298
309
  enableEvaluation: this.config.ai.enableEvaluation,
299
310
  });
@@ -322,10 +333,11 @@ export class YamaOrchestrator {
322
333
  ...this.getPRToolFilteringOptions(enhanceInstructions),
323
334
  context: {
324
335
  sessionId, // SAME sessionId = AI remembers review context
325
- userId: this.generateUserId(request),
336
+ userId: this.getUserId(request),
326
337
  operation: "description-enhancement",
327
338
  metadata: toolContext.metadata,
328
339
  },
340
+ memory: { enabled: false },
329
341
  enableAnalytics: this.config.ai.enableAnalytics,
330
342
  enableEvaluation: this.config.ai.enableEvaluation,
331
343
  });
@@ -368,9 +380,10 @@ export class YamaOrchestrator {
368
380
  ...this.getPRToolFilteringOptions(instructions),
369
381
  context: {
370
382
  sessionId,
371
- userId: this.generateUserId(request),
383
+ userId: this.getUserId(request),
372
384
  operation: "description-enhancement",
373
385
  },
386
+ memory: { enabled: false },
374
387
  enableAnalytics: true,
375
388
  });
376
389
  this.recordToolCallsFromResponse(sessionId, aiResponse);
@@ -404,9 +417,9 @@ export class YamaOrchestrator {
404
417
  exportSession(sessionId) {
405
418
  return this.sessionManager.exportSession(sessionId);
406
419
  }
407
- /**
408
- * Create tool context for AI
409
- */
420
+ getUserId(request) {
421
+ return `${request.workspace}-${request.repository}`.toLowerCase();
422
+ }
410
423
  createToolContext(sessionId, request) {
411
424
  return {
412
425
  sessionId,
@@ -825,14 +838,6 @@ export class YamaOrchestrator {
825
838
  const parsed = Number(value);
826
839
  return Number.isFinite(parsed) ? parsed : 0;
827
840
  }
828
- /**
829
- * Generate userId for NeuroLink context from repository and branch/PR
830
- */
831
- generateUserId(request) {
832
- const repo = request.repository;
833
- const identifier = request.branch || `pr-${request.pullRequestId}`;
834
- return `${repo}-${identifier}`;
835
- }
836
841
  isLocalReviewRequest(request) {
837
842
  return (request.mode === "local" ||
838
843
  (!("workspace" in request) && !("repository" in request)));
@@ -912,8 +917,15 @@ export class YamaOrchestrator {
912
917
  initializeNeurolink() {
913
918
  try {
914
919
  const observabilityConfig = buildObservabilityConfigFromEnv();
920
+ const conversationMemory = {
921
+ ...this.config.ai.conversationMemory,
922
+ };
923
+ if (this.memoryManager) {
924
+ conversationMemory.memory =
925
+ this.memoryManager.buildNeuroLinkMemoryConfig();
926
+ }
915
927
  const neurolinkConfig = {
916
- conversationMemory: this.config.ai.conversationMemory,
928
+ conversationMemory,
917
929
  };
918
930
  if (observabilityConfig) {
919
931
  // Validate observability config
@@ -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,57 @@
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
+ * Commit memory files to the repository if autoCommit is enabled.
45
+ *
46
+ * Checks git status for changes in the storagePath directory,
47
+ * then stages, commits, and pushes. Uses [skip ci] to prevent
48
+ * infinite CI loops. Never throws — failures are logged and ignored
49
+ * so they never block the review result.
50
+ */
51
+ commitMemoryChanges(): Promise<boolean>;
52
+ /**
53
+ * Map an ownerId to a safe file path.
54
+ */
55
+ private ownerIdToFilePath;
56
+ }
57
+ //# sourceMappingURL=MemoryManager.d.ts.map
@@ -0,0 +1,185 @@
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
+ * Commit memory files to the repository if autoCommit is enabled.
134
+ *
135
+ * Checks git status for changes in the storagePath directory,
136
+ * then stages, commits, and pushes. Uses [skip ci] to prevent
137
+ * infinite CI loops. Never throws — failures are logged and ignored
138
+ * so they never block the review result.
139
+ */
140
+ async commitMemoryChanges() {
141
+ if (!this.config.autoCommit) {
142
+ return false;
143
+ }
144
+ const storageDir = this.resolveStorageDir();
145
+ if (!existsSync(storageDir)) {
146
+ return false;
147
+ }
148
+ try {
149
+ // Check if there are any changes to commit
150
+ const { stdout: statusOutput } = await execFileAsync("git", ["status", "--porcelain", storageDir], { cwd: this.projectRoot });
151
+ if (!statusOutput.trim()) {
152
+ console.log(" 🧠 No memory changes to commit");
153
+ return false;
154
+ }
155
+ const commitMessage = this.config.commitMessage ||
156
+ "chore: update yama review memory [skip ci]";
157
+ // Stage memory files
158
+ await execFileAsync("git", ["add", storageDir], {
159
+ cwd: this.projectRoot,
160
+ });
161
+ // Commit with [skip ci] to prevent infinite loops
162
+ await execFileAsync("git", ["commit", "-m", commitMessage], {
163
+ cwd: this.projectRoot,
164
+ });
165
+ // Push to the current branch
166
+ await execFileAsync("git", ["push"], {
167
+ cwd: this.projectRoot,
168
+ });
169
+ console.log(" 🧠 Memory changes committed and pushed");
170
+ return true;
171
+ }
172
+ catch (error) {
173
+ console.warn(` ⚠️ Memory auto-commit failed: ${error.message}`);
174
+ return false;
175
+ }
176
+ }
177
+ /**
178
+ * Map an ownerId to a safe file path.
179
+ */
180
+ ownerIdToFilePath(storageDir, ownerId) {
181
+ const safeId = ownerId.replace(/[^a-zA-Z0-9-]/g, "-");
182
+ return join(storageDir, `${safeId}.md`);
183
+ }
184
+ }
185
+ //# sourceMappingURL=MemoryManager.js.map
@@ -12,6 +12,7 @@ export interface YamaConfig {
12
12
  descriptionEnhancement: DescriptionEnhancementConfig;
13
13
  memoryBank: MemoryBankConfig;
14
14
  knowledgeBase: KnowledgeBaseConfig;
15
+ memory: MemoryConfig;
15
16
  projectStandards?: ProjectStandardsConfig;
16
17
  monitoring: MonitoringConfig;
17
18
  performance: PerformanceConfig;
@@ -114,6 +115,56 @@ export interface KnowledgeBaseConfig {
114
115
  /** Auto-commit knowledge base changes (default for --commit flag) */
115
116
  autoCommit: boolean;
116
117
  }
118
+ /** Storage backend configuration for Hippocampus memory */
119
+ export type MemoryStorageConfig = {
120
+ type: "sqlite";
121
+ path?: string;
122
+ } | {
123
+ type: "redis";
124
+ host?: string;
125
+ port?: number;
126
+ password?: string;
127
+ db?: number;
128
+ keyPrefix?: string;
129
+ ttl?: number;
130
+ } | {
131
+ type: "s3";
132
+ bucket: string;
133
+ prefix?: string;
134
+ } | {
135
+ type: "custom";
136
+ onGet: (ownerId: string) => Promise<string | null>;
137
+ onSet: (ownerId: string, memory: string) => Promise<void>;
138
+ onDelete: (ownerId: string) => Promise<void>;
139
+ onClose?: () => Promise<void>;
140
+ };
141
+ /**
142
+ * Yama-specific memory configuration.
143
+ * Mirrors NeuroLink's Memory type (HippocampusConfig & { enabled }) with
144
+ * Yama-specific fields for file-based storage.
145
+ */
146
+ export interface MemoryConfig {
147
+ /** Enable per-repo condensed memory feature */
148
+ enabled: boolean;
149
+ /** Directory for file-based memory storage (relative to project root) */
150
+ storagePath: string;
151
+ /** Maximum word count for condensed memory per repository (default: 50) */
152
+ maxWords?: number;
153
+ /** Custom condensation prompt (overrides default review-specific prompt) */
154
+ prompt?: string;
155
+ /** Storage backend configuration (default: file-based custom storage managed by Yama) */
156
+ storage?: MemoryStorageConfig;
157
+ /** AI provider/model for memory condensation (defaults to main AI provider) */
158
+ neurolink?: {
159
+ provider?: string;
160
+ model?: string;
161
+ temperature?: number;
162
+ };
163
+ /** Auto-commit memory files to the repo after review (default: false) */
164
+ autoCommit?: boolean;
165
+ /** Git commit message for memory auto-commits (default: "chore: update yama review memory [skip ci]") */
166
+ commitMessage?: string;
167
+ }
117
168
  export interface ProjectStandardsConfig {
118
169
  customPromptsPath: string;
119
170
  additionalFocusAreas: FocusArea[];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@juspay/yama",
3
- "version": "2.2.2",
3
+ "version": "2.3.0",
4
4
  "description": "Enterprise-grade Pull Request automation toolkit with AI-powered code review and description enhancement",
5
5
  "keywords": [
6
6
  "pr",
@@ -89,7 +89,7 @@
89
89
  "check:all": "npm run lint && npm run format --check && npm run validate && npm run validate:commit"
90
90
  },
91
91
  "dependencies": {
92
- "@juspay/neurolink": "^9.18.0",
92
+ "@juspay/neurolink": "^9.42.0",
93
93
  "langfuse": "^3.35.0",
94
94
  "@nexus2520/bitbucket-mcp-server": "2.0.1",
95
95
  "@nexus2520/jira-mcp-server": "^1.1.1",
@@ -231,6 +231,21 @@ knowledgeBase:
231
231
  # Automatically commit knowledge base changes (with --commit flag)
232
232
  autoCommit: false
233
233
 
234
+ # Per-repo condensed memory
235
+ # Memory accumulates durable facts about each repository across reviews
236
+ memory:
237
+ enabled: true
238
+ storagePath: ".yama/memory"
239
+ # Maximum word count for condensed memory per repository (default: 50)
240
+ maxWords: 200
241
+ # prompt: "Your custom condensation prompt here..."
242
+ # neurolink:
243
+ # provider: google-ai
244
+ # model: gemini-2.0-flash
245
+ # temperature: 0.1
246
+ autoCommit: true # Auto-commit memory files to repo after each review (persists across CI builds)
247
+ # commitMessage: "chore: update yama review memory [skip ci]"
248
+
234
249
  # ============================================================================
235
250
  # Project-Specific Standards (Override in your repository)
236
251
  # ============================================================================