@juspay/yama 2.0.0 → 2.2.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 +19 -0
- package/dist/cli/v2.cli.js +69 -0
- package/dist/v2/config/DefaultConfig.js +13 -17
- package/dist/v2/core/LearningOrchestrator.d.ts +65 -0
- package/dist/v2/core/LearningOrchestrator.js +499 -0
- package/dist/v2/core/MCPServerManager.js +12 -4
- package/dist/v2/learning/FeedbackExtractor.d.ts +46 -0
- package/dist/v2/learning/FeedbackExtractor.js +237 -0
- package/dist/v2/learning/KnowledgeBaseManager.d.ts +91 -0
- package/dist/v2/learning/KnowledgeBaseManager.js +475 -0
- package/dist/v2/learning/types.d.ts +121 -0
- package/dist/v2/learning/types.js +15 -0
- package/dist/v2/prompts/LangfusePromptManager.d.ts +48 -0
- package/dist/v2/prompts/LangfusePromptManager.js +144 -0
- package/dist/v2/prompts/LearningSystemPrompt.d.ts +11 -0
- package/dist/v2/prompts/LearningSystemPrompt.js +180 -0
- package/dist/v2/prompts/PromptBuilder.d.ts +7 -0
- package/dist/v2/prompts/PromptBuilder.js +37 -8
- package/dist/v2/prompts/ReviewSystemPrompt.d.ts +1 -1
- package/dist/v2/prompts/ReviewSystemPrompt.js +33 -24
- package/dist/v2/types/config.types.d.ts +22 -1
- package/dist/v2/types/mcp.types.d.ts +29 -8
- package/package.json +8 -4
- package/yama.config.example.yaml +58 -13
|
@@ -0,0 +1,475 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Knowledge Base Manager
|
|
3
|
+
* Handles reading, writing, and parsing the knowledge base markdown file
|
|
4
|
+
*/
|
|
5
|
+
import { readFile, writeFile, mkdir } from "fs/promises";
|
|
6
|
+
import { existsSync } from "fs";
|
|
7
|
+
import { dirname, join } from "path";
|
|
8
|
+
import { createHash } from "crypto";
|
|
9
|
+
import { execFile } from "child_process";
|
|
10
|
+
import { promisify } from "util";
|
|
11
|
+
const execFileAsync = promisify(execFile);
|
|
12
|
+
import { CATEGORY_SECTION_NAMES, } from "./types.js";
|
|
13
|
+
/**
|
|
14
|
+
* Template for a new knowledge base file
|
|
15
|
+
*/
|
|
16
|
+
const KNOWLEDGE_BASE_TEMPLATE = `# Project Knowledge Base
|
|
17
|
+
> Learned patterns, preferences, and guidelines from team feedback
|
|
18
|
+
|
|
19
|
+
## Metadata
|
|
20
|
+
- Last Updated: {{TIMESTAMP}}
|
|
21
|
+
- Total Learnings: 0
|
|
22
|
+
- Last Summarization: N/A
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## False Positives (Don't Flag These)
|
|
27
|
+
|
|
28
|
+
Things AI incorrectly flagged as issues. Avoid repeating these mistakes.
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## Style Preferences (Team Conventions)
|
|
33
|
+
|
|
34
|
+
Project-specific coding conventions that differ from general best practices.
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## Missed Issues (Should Have Flagged)
|
|
39
|
+
|
|
40
|
+
Patterns AI missed that should be caught in future reviews.
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## Context & Domain Knowledge
|
|
45
|
+
|
|
46
|
+
Project-specific context AI needs for accurate reviews.
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## Enhancement Guidelines
|
|
51
|
+
|
|
52
|
+
How AI should provide suggestions for this project.
|
|
53
|
+
|
|
54
|
+
`;
|
|
55
|
+
export class KnowledgeBaseManager {
|
|
56
|
+
config;
|
|
57
|
+
projectRoot;
|
|
58
|
+
constructor(config, projectRoot) {
|
|
59
|
+
this.config = config;
|
|
60
|
+
this.projectRoot = projectRoot || process.cwd();
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Get the full path to the knowledge base file
|
|
64
|
+
*/
|
|
65
|
+
getFilePath() {
|
|
66
|
+
return join(this.projectRoot, this.config.path);
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Check if knowledge base file exists
|
|
70
|
+
*/
|
|
71
|
+
exists() {
|
|
72
|
+
return existsSync(this.getFilePath());
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Load and parse the knowledge base file
|
|
76
|
+
*/
|
|
77
|
+
async load() {
|
|
78
|
+
if (!this.exists()) {
|
|
79
|
+
return this.createEmptyKnowledgeBase();
|
|
80
|
+
}
|
|
81
|
+
const content = await readFile(this.getFilePath(), "utf-8");
|
|
82
|
+
return this.parseMarkdown(content);
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Append new learnings to the knowledge base
|
|
86
|
+
* Returns count of learnings actually added (excludes duplicates)
|
|
87
|
+
*/
|
|
88
|
+
async append(learnings) {
|
|
89
|
+
const kb = await this.load();
|
|
90
|
+
let addedCount = 0;
|
|
91
|
+
for (const learning of learnings) {
|
|
92
|
+
// Check for duplicates
|
|
93
|
+
if (this.isDuplicate(kb, learning)) {
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
// Get or create section
|
|
97
|
+
let section = kb.sections.get(learning.category);
|
|
98
|
+
if (!section) {
|
|
99
|
+
section = {
|
|
100
|
+
category: learning.category,
|
|
101
|
+
subcategories: new Map(),
|
|
102
|
+
};
|
|
103
|
+
kb.sections.set(learning.category, section);
|
|
104
|
+
}
|
|
105
|
+
// Get or create subcategory
|
|
106
|
+
const subcatKey = learning.subcategory || "General";
|
|
107
|
+
let learningsList = section.subcategories.get(subcatKey);
|
|
108
|
+
if (!learningsList) {
|
|
109
|
+
learningsList = [];
|
|
110
|
+
section.subcategories.set(subcatKey, learningsList);
|
|
111
|
+
}
|
|
112
|
+
// Add the learning
|
|
113
|
+
learningsList.push(learning.learning);
|
|
114
|
+
addedCount++;
|
|
115
|
+
}
|
|
116
|
+
// Update metadata
|
|
117
|
+
kb.metadata.lastUpdated = new Date().toISOString();
|
|
118
|
+
kb.metadata.totalLearnings += addedCount;
|
|
119
|
+
// Write back
|
|
120
|
+
await this.write(kb);
|
|
121
|
+
return addedCount;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Write the knowledge base back to file
|
|
125
|
+
*/
|
|
126
|
+
async write(kb) {
|
|
127
|
+
const content = this.toMarkdown(kb);
|
|
128
|
+
const filePath = this.getFilePath();
|
|
129
|
+
const dir = dirname(filePath);
|
|
130
|
+
// Ensure directory exists
|
|
131
|
+
if (!existsSync(dir)) {
|
|
132
|
+
await mkdir(dir, { recursive: true });
|
|
133
|
+
}
|
|
134
|
+
await writeFile(filePath, content, "utf-8");
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Write raw markdown content directly to file
|
|
138
|
+
* Used by summarization to write AI-generated consolidated content
|
|
139
|
+
*/
|
|
140
|
+
async writeRaw(content) {
|
|
141
|
+
const filePath = this.getFilePath();
|
|
142
|
+
const dir = dirname(filePath);
|
|
143
|
+
// Ensure directory exists
|
|
144
|
+
if (!existsSync(dir)) {
|
|
145
|
+
await mkdir(dir, { recursive: true });
|
|
146
|
+
}
|
|
147
|
+
await writeFile(filePath, content, "utf-8");
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Create a new knowledge base file from template
|
|
151
|
+
*/
|
|
152
|
+
async create() {
|
|
153
|
+
const content = KNOWLEDGE_BASE_TEMPLATE.replace("{{TIMESTAMP}}", new Date().toISOString());
|
|
154
|
+
const filePath = this.getFilePath();
|
|
155
|
+
const dir = dirname(filePath);
|
|
156
|
+
if (!existsSync(dir)) {
|
|
157
|
+
await mkdir(dir, { recursive: true });
|
|
158
|
+
}
|
|
159
|
+
await writeFile(filePath, content, "utf-8");
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Get knowledge base content formatted for AI prompt injection
|
|
163
|
+
*/
|
|
164
|
+
async getForPrompt() {
|
|
165
|
+
if (!this.config.enabled || !this.exists()) {
|
|
166
|
+
return null;
|
|
167
|
+
}
|
|
168
|
+
try {
|
|
169
|
+
const content = await readFile(this.getFilePath(), "utf-8");
|
|
170
|
+
// Remove metadata section for cleaner prompt
|
|
171
|
+
const lines = content.split("\n");
|
|
172
|
+
const filteredLines = [];
|
|
173
|
+
let inMetadata = false;
|
|
174
|
+
for (const line of lines) {
|
|
175
|
+
if (line.startsWith("## Metadata")) {
|
|
176
|
+
inMetadata = true;
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
if (inMetadata && line.startsWith("---")) {
|
|
180
|
+
inMetadata = false;
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
if (!inMetadata) {
|
|
184
|
+
filteredLines.push(line);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
return filteredLines.join("\n").trim();
|
|
188
|
+
}
|
|
189
|
+
catch {
|
|
190
|
+
return null;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Get count of learnings in the knowledge base
|
|
195
|
+
*/
|
|
196
|
+
async getLearningCount() {
|
|
197
|
+
const kb = await this.load();
|
|
198
|
+
return kb.metadata.totalLearnings;
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Check if summarization is needed based on entry count
|
|
202
|
+
*/
|
|
203
|
+
async needsSummarization() {
|
|
204
|
+
const count = await this.getLearningCount();
|
|
205
|
+
return count >= this.config.maxEntriesBeforeSummarization;
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Commit the knowledge base file to git
|
|
209
|
+
* Uses execFile with argument arrays to prevent command injection
|
|
210
|
+
*/
|
|
211
|
+
async commit(prId, learningsAdded) {
|
|
212
|
+
const filePath = this.config.path; // Relative path for git
|
|
213
|
+
// Validate inputs to prevent injection
|
|
214
|
+
const safePrId = Math.floor(Number(prId));
|
|
215
|
+
const safeLearningsAdded = Math.floor(Number(learningsAdded));
|
|
216
|
+
if (!Number.isFinite(safePrId) || safePrId < 0) {
|
|
217
|
+
throw new Error("Invalid PR ID");
|
|
218
|
+
}
|
|
219
|
+
try {
|
|
220
|
+
// Stage the file using execFile with args array (safe from injection)
|
|
221
|
+
await execFileAsync("git", ["add", filePath], { cwd: this.projectRoot });
|
|
222
|
+
// Create commit message
|
|
223
|
+
const commitMessage = `chore(yama): update knowledge base from PR #${safePrId}
|
|
224
|
+
|
|
225
|
+
Added ${safeLearningsAdded} new learning${safeLearningsAdded !== 1 ? "s" : ""}.
|
|
226
|
+
|
|
227
|
+
🤖 Generated with Yama`;
|
|
228
|
+
// Commit using execFile with args array (safe from injection)
|
|
229
|
+
await execFileAsync("git", ["commit", "-m", commitMessage], {
|
|
230
|
+
cwd: this.projectRoot,
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
catch (error) {
|
|
234
|
+
throw new Error(`Failed to commit knowledge base: ${error instanceof Error ? error.message : String(error)}`);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Generate a hash for deduplication
|
|
239
|
+
*/
|
|
240
|
+
generateLearningId(learning) {
|
|
241
|
+
return createHash("md5")
|
|
242
|
+
.update(learning.toLowerCase().trim())
|
|
243
|
+
.digest("hex")
|
|
244
|
+
.substring(0, 12);
|
|
245
|
+
}
|
|
246
|
+
// ============================================================================
|
|
247
|
+
// Private Methods
|
|
248
|
+
// ============================================================================
|
|
249
|
+
/**
|
|
250
|
+
* Create an empty knowledge base structure
|
|
251
|
+
*/
|
|
252
|
+
createEmptyKnowledgeBase() {
|
|
253
|
+
return {
|
|
254
|
+
metadata: {
|
|
255
|
+
lastUpdated: new Date().toISOString(),
|
|
256
|
+
totalLearnings: 0,
|
|
257
|
+
},
|
|
258
|
+
sections: new Map(),
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* Check if a learning already exists in the knowledge base
|
|
263
|
+
*/
|
|
264
|
+
isDuplicate(kb, learning) {
|
|
265
|
+
const section = kb.sections.get(learning.category);
|
|
266
|
+
if (!section) {
|
|
267
|
+
return false;
|
|
268
|
+
}
|
|
269
|
+
const normalizedNew = learning.learning.toLowerCase().trim();
|
|
270
|
+
for (const [, learnings] of section.subcategories) {
|
|
271
|
+
for (const existing of learnings) {
|
|
272
|
+
const normalizedExisting = existing.toLowerCase().trim();
|
|
273
|
+
// Check for exact match or high similarity
|
|
274
|
+
if (normalizedExisting === normalizedNew ||
|
|
275
|
+
this.isSimilar(normalizedExisting, normalizedNew)) {
|
|
276
|
+
return true;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
return false;
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* Check if two learnings are similar (simple similarity check)
|
|
284
|
+
*/
|
|
285
|
+
isSimilar(a, b) {
|
|
286
|
+
// Remove common words and check overlap
|
|
287
|
+
const wordsA = new Set(a.split(/\s+/).filter((w) => w.length > 3));
|
|
288
|
+
const wordsB = new Set(b.split(/\s+/).filter((w) => w.length > 3));
|
|
289
|
+
if (wordsA.size === 0 || wordsB.size === 0) {
|
|
290
|
+
return false;
|
|
291
|
+
}
|
|
292
|
+
let overlap = 0;
|
|
293
|
+
for (const word of wordsA) {
|
|
294
|
+
if (wordsB.has(word)) {
|
|
295
|
+
overlap++;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
const similarity = overlap / Math.max(wordsA.size, wordsB.size);
|
|
299
|
+
return similarity > 0.7; // 70% word overlap = similar
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* Parse markdown content into structured knowledge base
|
|
303
|
+
*/
|
|
304
|
+
parseMarkdown(content) {
|
|
305
|
+
const kb = this.createEmptyKnowledgeBase();
|
|
306
|
+
const lines = content.split("\n");
|
|
307
|
+
let currentCategory = null;
|
|
308
|
+
let currentSubcategory = "General";
|
|
309
|
+
let inMetadata = false;
|
|
310
|
+
for (const line of lines) {
|
|
311
|
+
const trimmed = line.trim();
|
|
312
|
+
// Parse metadata
|
|
313
|
+
if (trimmed.startsWith("## Metadata")) {
|
|
314
|
+
inMetadata = true;
|
|
315
|
+
continue;
|
|
316
|
+
}
|
|
317
|
+
if (inMetadata) {
|
|
318
|
+
if (trimmed.startsWith("---")) {
|
|
319
|
+
inMetadata = false;
|
|
320
|
+
continue;
|
|
321
|
+
}
|
|
322
|
+
if (trimmed.startsWith("- Last Updated:")) {
|
|
323
|
+
kb.metadata.lastUpdated = trimmed
|
|
324
|
+
.replace("- Last Updated:", "")
|
|
325
|
+
.trim();
|
|
326
|
+
}
|
|
327
|
+
else if (trimmed.startsWith("- Total Learnings:")) {
|
|
328
|
+
kb.metadata.totalLearnings =
|
|
329
|
+
parseInt(trimmed.replace("- Total Learnings:", "").trim(), 10) || 0;
|
|
330
|
+
}
|
|
331
|
+
else if (trimmed.startsWith("- Last Summarization:")) {
|
|
332
|
+
const value = trimmed.replace("- Last Summarization:", "").trim();
|
|
333
|
+
if (value !== "N/A") {
|
|
334
|
+
kb.metadata.lastSummarization = value;
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
continue;
|
|
338
|
+
}
|
|
339
|
+
// Parse category headers (## level)
|
|
340
|
+
if (trimmed.startsWith("## ")) {
|
|
341
|
+
const sectionName = trimmed.substring(3);
|
|
342
|
+
currentCategory = this.categoryFromSectionName(sectionName);
|
|
343
|
+
currentSubcategory = "General";
|
|
344
|
+
if (currentCategory) {
|
|
345
|
+
kb.sections.set(currentCategory, {
|
|
346
|
+
category: currentCategory,
|
|
347
|
+
subcategories: new Map(),
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
continue;
|
|
351
|
+
}
|
|
352
|
+
// Parse subcategory headers (### level)
|
|
353
|
+
if (trimmed.startsWith("### ")) {
|
|
354
|
+
currentSubcategory = trimmed.substring(4);
|
|
355
|
+
continue;
|
|
356
|
+
}
|
|
357
|
+
// Parse learning entries (- bullet points)
|
|
358
|
+
if (trimmed.startsWith("- ") && currentCategory) {
|
|
359
|
+
const learning = trimmed.substring(2);
|
|
360
|
+
const section = kb.sections.get(currentCategory);
|
|
361
|
+
if (section) {
|
|
362
|
+
let learnings = section.subcategories.get(currentSubcategory);
|
|
363
|
+
if (!learnings) {
|
|
364
|
+
learnings = [];
|
|
365
|
+
section.subcategories.set(currentSubcategory, learnings);
|
|
366
|
+
}
|
|
367
|
+
learnings.push(learning);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
return kb;
|
|
372
|
+
}
|
|
373
|
+
/**
|
|
374
|
+
* Convert category section name back to category enum
|
|
375
|
+
*/
|
|
376
|
+
categoryFromSectionName(name) {
|
|
377
|
+
for (const [category, sectionName] of Object.entries(CATEGORY_SECTION_NAMES)) {
|
|
378
|
+
if (name.includes(sectionName) || sectionName.includes(name)) {
|
|
379
|
+
return category;
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
// Fallback matching
|
|
383
|
+
const lowerName = name.toLowerCase();
|
|
384
|
+
if (lowerName.includes("false positive") ||
|
|
385
|
+
lowerName.includes("don't flag")) {
|
|
386
|
+
return "false_positive";
|
|
387
|
+
}
|
|
388
|
+
if (lowerName.includes("missed") || lowerName.includes("should have")) {
|
|
389
|
+
return "missed_issue";
|
|
390
|
+
}
|
|
391
|
+
if (lowerName.includes("style") || lowerName.includes("convention")) {
|
|
392
|
+
return "style_preference";
|
|
393
|
+
}
|
|
394
|
+
if (lowerName.includes("context") || lowerName.includes("domain")) {
|
|
395
|
+
return "domain_context";
|
|
396
|
+
}
|
|
397
|
+
if (lowerName.includes("enhancement") || lowerName.includes("guideline")) {
|
|
398
|
+
return "enhancement_guideline";
|
|
399
|
+
}
|
|
400
|
+
return null;
|
|
401
|
+
}
|
|
402
|
+
/**
|
|
403
|
+
* Convert knowledge base structure to markdown
|
|
404
|
+
*/
|
|
405
|
+
toMarkdown(kb) {
|
|
406
|
+
const lines = [];
|
|
407
|
+
// Header
|
|
408
|
+
lines.push("# Project Knowledge Base");
|
|
409
|
+
lines.push("> Learned patterns, preferences, and guidelines from team feedback");
|
|
410
|
+
lines.push("");
|
|
411
|
+
// Metadata
|
|
412
|
+
lines.push("## Metadata");
|
|
413
|
+
lines.push(`- Last Updated: ${kb.metadata.lastUpdated}`);
|
|
414
|
+
lines.push(`- Total Learnings: ${kb.metadata.totalLearnings}`);
|
|
415
|
+
lines.push(`- Last Summarization: ${kb.metadata.lastSummarization || "N/A"}`);
|
|
416
|
+
lines.push("");
|
|
417
|
+
lines.push("---");
|
|
418
|
+
lines.push("");
|
|
419
|
+
// Sections in order
|
|
420
|
+
const categoryOrder = [
|
|
421
|
+
"false_positive",
|
|
422
|
+
"missed_issue",
|
|
423
|
+
"style_preference",
|
|
424
|
+
"domain_context",
|
|
425
|
+
"enhancement_guideline",
|
|
426
|
+
];
|
|
427
|
+
for (const category of categoryOrder) {
|
|
428
|
+
const sectionName = CATEGORY_SECTION_NAMES[category];
|
|
429
|
+
lines.push(`## ${sectionName}`);
|
|
430
|
+
lines.push("");
|
|
431
|
+
const section = kb.sections.get(category);
|
|
432
|
+
if (section && section.subcategories.size > 0) {
|
|
433
|
+
// Sort subcategories
|
|
434
|
+
const sortedSubcats = Array.from(section.subcategories.entries()).sort(([a], [b]) => a.localeCompare(b));
|
|
435
|
+
for (const [subcategory, learnings] of sortedSubcats) {
|
|
436
|
+
if (subcategory !== "General") {
|
|
437
|
+
lines.push(`### ${subcategory}`);
|
|
438
|
+
}
|
|
439
|
+
for (const learning of learnings) {
|
|
440
|
+
lines.push(`- ${learning}`);
|
|
441
|
+
}
|
|
442
|
+
lines.push("");
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
else {
|
|
446
|
+
// Add description placeholder for empty sections
|
|
447
|
+
lines.push(this.getSectionDescription(category));
|
|
448
|
+
lines.push("");
|
|
449
|
+
}
|
|
450
|
+
lines.push("---");
|
|
451
|
+
lines.push("");
|
|
452
|
+
}
|
|
453
|
+
return lines.join("\n");
|
|
454
|
+
}
|
|
455
|
+
/**
|
|
456
|
+
* Get description text for empty sections
|
|
457
|
+
*/
|
|
458
|
+
getSectionDescription(category) {
|
|
459
|
+
switch (category) {
|
|
460
|
+
case "false_positive":
|
|
461
|
+
return "Things AI incorrectly flagged as issues. Avoid repeating these mistakes.";
|
|
462
|
+
case "missed_issue":
|
|
463
|
+
return "Patterns AI missed that should be caught in future reviews.";
|
|
464
|
+
case "style_preference":
|
|
465
|
+
return "Project-specific coding conventions that differ from general best practices.";
|
|
466
|
+
case "domain_context":
|
|
467
|
+
return "Project-specific context AI needs for accurate reviews.";
|
|
468
|
+
case "enhancement_guideline":
|
|
469
|
+
return "How AI should provide suggestions for this project.";
|
|
470
|
+
default:
|
|
471
|
+
return "";
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
//# sourceMappingURL=KnowledgeBaseManager.js.map
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Learning Types
|
|
3
|
+
* Type definitions for the knowledge base and learning extraction system
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Categories for extracted learnings
|
|
7
|
+
* Maps to sections in the knowledge base file
|
|
8
|
+
*/
|
|
9
|
+
export type LearningCategory = "false_positive" | "missed_issue" | "style_preference" | "domain_context" | "enhancement_guideline";
|
|
10
|
+
/**
|
|
11
|
+
* Human-readable category names for knowledge base sections
|
|
12
|
+
*/
|
|
13
|
+
export declare const CATEGORY_SECTION_NAMES: Record<LearningCategory, string>;
|
|
14
|
+
/**
|
|
15
|
+
* A single learning extracted from PR feedback
|
|
16
|
+
*/
|
|
17
|
+
export interface ExtractedLearning {
|
|
18
|
+
/** Unique hash for deduplication */
|
|
19
|
+
id: string;
|
|
20
|
+
/** Category of the learning */
|
|
21
|
+
category: LearningCategory;
|
|
22
|
+
/** Sub-category within the section (e.g., "Async Patterns", "Security") */
|
|
23
|
+
subcategory?: string;
|
|
24
|
+
/** The actionable, project-level guideline */
|
|
25
|
+
learning: string;
|
|
26
|
+
/** File patterns where this applies (e.g., ["services/*.ts"]) */
|
|
27
|
+
filePatterns?: string[];
|
|
28
|
+
/** Severity for missed_issue learnings */
|
|
29
|
+
severity?: string;
|
|
30
|
+
/** Source info for traceability (not displayed in KB) */
|
|
31
|
+
sourceInfo?: {
|
|
32
|
+
prId: number;
|
|
33
|
+
timestamp: string;
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Metadata section of the knowledge base
|
|
38
|
+
*/
|
|
39
|
+
export interface KnowledgeBaseMetadata {
|
|
40
|
+
lastUpdated: string;
|
|
41
|
+
totalLearnings: number;
|
|
42
|
+
lastSummarization?: string;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* A section in the knowledge base (maps to a category)
|
|
46
|
+
*/
|
|
47
|
+
export interface KnowledgeBaseSection {
|
|
48
|
+
category: LearningCategory;
|
|
49
|
+
subcategories: Map<string, string[]>;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Full parsed knowledge base structure
|
|
53
|
+
*/
|
|
54
|
+
export interface KnowledgeBase {
|
|
55
|
+
metadata: KnowledgeBaseMetadata;
|
|
56
|
+
sections: Map<LearningCategory, KnowledgeBaseSection>;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Request for the learn command
|
|
60
|
+
*/
|
|
61
|
+
export interface LearnRequest {
|
|
62
|
+
workspace: string;
|
|
63
|
+
repository: string;
|
|
64
|
+
pullRequestId: number;
|
|
65
|
+
dryRun?: boolean;
|
|
66
|
+
commit?: boolean;
|
|
67
|
+
summarize?: boolean;
|
|
68
|
+
outputPath?: string;
|
|
69
|
+
outputFormat?: "md" | "json";
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Result from the learn command
|
|
73
|
+
*/
|
|
74
|
+
export interface LearnResult {
|
|
75
|
+
success: boolean;
|
|
76
|
+
prId: number;
|
|
77
|
+
learningsFound: number;
|
|
78
|
+
learningsAdded: number;
|
|
79
|
+
learningsDuplicate: number;
|
|
80
|
+
learnings: ExtractedLearning[];
|
|
81
|
+
knowledgeBasePath?: string;
|
|
82
|
+
committed?: boolean;
|
|
83
|
+
summarized?: boolean;
|
|
84
|
+
error?: string;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* A comment from a PR
|
|
88
|
+
*/
|
|
89
|
+
export interface PRComment {
|
|
90
|
+
id: number;
|
|
91
|
+
text: string;
|
|
92
|
+
author: {
|
|
93
|
+
name: string;
|
|
94
|
+
displayName?: string;
|
|
95
|
+
email?: string;
|
|
96
|
+
};
|
|
97
|
+
createdAt: string;
|
|
98
|
+
filePath?: string;
|
|
99
|
+
lineNumber?: number;
|
|
100
|
+
parentId?: number;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* A pair of AI comment and developer reply
|
|
104
|
+
*/
|
|
105
|
+
export interface CommentPair {
|
|
106
|
+
aiComment: PRComment;
|
|
107
|
+
developerReply: PRComment;
|
|
108
|
+
filePath?: string;
|
|
109
|
+
codeContext?: string;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Output format from AI learning extraction
|
|
113
|
+
*/
|
|
114
|
+
export interface AIExtractionOutput {
|
|
115
|
+
category: LearningCategory;
|
|
116
|
+
subcategory?: string;
|
|
117
|
+
learning: string;
|
|
118
|
+
filePatterns?: string[];
|
|
119
|
+
reasoning: string;
|
|
120
|
+
}
|
|
121
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Learning Types
|
|
3
|
+
* Type definitions for the knowledge base and learning extraction system
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Human-readable category names for knowledge base sections
|
|
7
|
+
*/
|
|
8
|
+
export const CATEGORY_SECTION_NAMES = {
|
|
9
|
+
false_positive: "False Positives (Don't Flag These)",
|
|
10
|
+
missed_issue: "Missed Issues (Should Have Flagged)",
|
|
11
|
+
style_preference: "Style Preferences (Team Conventions)",
|
|
12
|
+
domain_context: "Context & Domain Knowledge",
|
|
13
|
+
enhancement_guideline: "Enhancement Guidelines",
|
|
14
|
+
};
|
|
15
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Langfuse Prompt Manager
|
|
3
|
+
* Fetches prompts from Langfuse Prompt Management with local fallbacks
|
|
4
|
+
*
|
|
5
|
+
* Prompt Names in Langfuse:
|
|
6
|
+
* - yama-review: Review system prompt
|
|
7
|
+
* - yama-enhancement: Enhancement system prompt
|
|
8
|
+
*/
|
|
9
|
+
export declare class LangfusePromptManager {
|
|
10
|
+
private client;
|
|
11
|
+
private initialized;
|
|
12
|
+
constructor();
|
|
13
|
+
/**
|
|
14
|
+
* Initialize Langfuse client if credentials are available
|
|
15
|
+
*/
|
|
16
|
+
private initializeClient;
|
|
17
|
+
/**
|
|
18
|
+
* Get the review system prompt
|
|
19
|
+
* Fetches from Langfuse if available, otherwise returns local fallback
|
|
20
|
+
*/
|
|
21
|
+
getReviewPrompt(): Promise<string>;
|
|
22
|
+
/**
|
|
23
|
+
* Get the enhancement system prompt
|
|
24
|
+
* Fetches from Langfuse if available, otherwise returns local fallback
|
|
25
|
+
*/
|
|
26
|
+
getEnhancementPrompt(): Promise<string>;
|
|
27
|
+
/**
|
|
28
|
+
* Get the learning extraction prompt
|
|
29
|
+
* Fetches from Langfuse if available, otherwise returns local fallback
|
|
30
|
+
* Langfuse prompt name: "yama-learning"
|
|
31
|
+
*/
|
|
32
|
+
getLearningPrompt(): Promise<string>;
|
|
33
|
+
/**
|
|
34
|
+
* Get the summarization prompt
|
|
35
|
+
* Fetches from Langfuse if available, otherwise returns local fallback
|
|
36
|
+
* Langfuse prompt name: "yama-summarization"
|
|
37
|
+
*/
|
|
38
|
+
getSummarizationPrompt(): Promise<string>;
|
|
39
|
+
/**
|
|
40
|
+
* Check if Langfuse is enabled
|
|
41
|
+
*/
|
|
42
|
+
isEnabled(): boolean;
|
|
43
|
+
/**
|
|
44
|
+
* Shutdown Langfuse client gracefully
|
|
45
|
+
*/
|
|
46
|
+
shutdown(): Promise<void>;
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=LangfusePromptManager.d.ts.map
|