@juspay/yama 1.6.0 → 2.1.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/.mcp-config.example.json +26 -0
- package/CHANGELOG.md +46 -0
- package/README.md +311 -685
- package/dist/cli/v2.cli.d.ts +13 -0
- package/dist/cli/v2.cli.js +359 -0
- package/dist/index.d.ts +12 -13
- package/dist/index.js +18 -19
- package/dist/v2/config/ConfigLoader.d.ts +50 -0
- package/dist/v2/config/ConfigLoader.js +205 -0
- package/dist/v2/config/DefaultConfig.d.ts +9 -0
- package/dist/v2/config/DefaultConfig.js +187 -0
- package/dist/v2/core/LearningOrchestrator.d.ts +65 -0
- package/dist/v2/core/LearningOrchestrator.js +499 -0
- package/dist/v2/core/MCPServerManager.d.ts +22 -0
- package/dist/v2/core/MCPServerManager.js +100 -0
- package/dist/v2/core/SessionManager.d.ts +72 -0
- package/dist/v2/core/SessionManager.js +200 -0
- package/dist/v2/core/YamaV2Orchestrator.d.ts +112 -0
- package/dist/v2/core/YamaV2Orchestrator.js +549 -0
- 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/EnhancementSystemPrompt.d.ts +8 -0
- package/dist/v2/prompts/EnhancementSystemPrompt.js +216 -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 +45 -0
- package/dist/v2/prompts/PromptBuilder.js +257 -0
- package/dist/v2/prompts/ReviewSystemPrompt.d.ts +8 -0
- package/dist/v2/prompts/ReviewSystemPrompt.js +270 -0
- package/dist/v2/types/config.types.d.ts +141 -0
- package/dist/v2/types/config.types.js +5 -0
- package/dist/v2/types/mcp.types.d.ts +191 -0
- package/dist/v2/types/mcp.types.js +6 -0
- package/dist/v2/types/v2.types.d.ts +182 -0
- package/dist/v2/types/v2.types.js +42 -0
- package/dist/v2/utils/ObservabilityConfig.d.ts +22 -0
- package/dist/v2/utils/ObservabilityConfig.js +48 -0
- package/package.json +16 -10
- package/yama.config.example.yaml +259 -204
- package/dist/cli/index.d.ts +0 -12
- package/dist/cli/index.js +0 -538
- package/dist/core/ContextGatherer.d.ts +0 -110
- package/dist/core/ContextGatherer.js +0 -470
- package/dist/core/Guardian.d.ts +0 -81
- package/dist/core/Guardian.js +0 -480
- package/dist/core/providers/BitbucketProvider.d.ts +0 -105
- package/dist/core/providers/BitbucketProvider.js +0 -489
- package/dist/features/CodeReviewer.d.ts +0 -173
- package/dist/features/CodeReviewer.js +0 -1707
- package/dist/features/DescriptionEnhancer.d.ts +0 -70
- package/dist/features/DescriptionEnhancer.js +0 -511
- package/dist/features/MultiInstanceProcessor.d.ts +0 -74
- package/dist/features/MultiInstanceProcessor.js +0 -360
- package/dist/types/index.d.ts +0 -624
- package/dist/types/index.js +0 -104
- package/dist/utils/Cache.d.ts +0 -103
- package/dist/utils/Cache.js +0 -444
- package/dist/utils/ConfigManager.d.ts +0 -88
- package/dist/utils/ConfigManager.js +0 -602
- package/dist/utils/ContentSimilarityService.d.ts +0 -74
- package/dist/utils/ContentSimilarityService.js +0 -215
- package/dist/utils/ExactDuplicateRemover.d.ts +0 -77
- package/dist/utils/ExactDuplicateRemover.js +0 -361
- package/dist/utils/Logger.d.ts +0 -31
- package/dist/utils/Logger.js +0 -214
- package/dist/utils/MemoryBankManager.d.ts +0 -73
- package/dist/utils/MemoryBankManager.js +0 -310
- package/dist/utils/ParallelProcessing.d.ts +0 -140
- package/dist/utils/ParallelProcessing.js +0 -333
- package/dist/utils/ProviderLimits.d.ts +0 -58
- package/dist/utils/ProviderLimits.js +0 -143
- package/dist/utils/RetryManager.d.ts +0 -78
- package/dist/utils/RetryManager.js +0 -205
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Feedback Extractor
|
|
3
|
+
* Identifies AI comments and developer replies from PR comments
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Text patterns that identify AI-generated comments
|
|
7
|
+
*/
|
|
8
|
+
const AI_TEXT_PATTERNS = [
|
|
9
|
+
"🔒 CRITICAL",
|
|
10
|
+
"⚠️ MAJOR",
|
|
11
|
+
"💡 MINOR",
|
|
12
|
+
"💬 SUGGESTION",
|
|
13
|
+
"**Issue**:",
|
|
14
|
+
"**Impact**:",
|
|
15
|
+
"**Fix**:",
|
|
16
|
+
"Yama Review",
|
|
17
|
+
"_Review powered by Yama_",
|
|
18
|
+
"🔒 SECURITY",
|
|
19
|
+
"⚡ PERFORMANCE",
|
|
20
|
+
];
|
|
21
|
+
export class FeedbackExtractor {
|
|
22
|
+
config;
|
|
23
|
+
constructor(config) {
|
|
24
|
+
this.config = config;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Check if a comment is from AI based on author and text patterns
|
|
28
|
+
*/
|
|
29
|
+
isAIComment(comment) {
|
|
30
|
+
// Check author name patterns
|
|
31
|
+
const authorMatch = this.config.aiAuthorPatterns.some((pattern) => comment.author.name.toLowerCase().includes(pattern.toLowerCase()));
|
|
32
|
+
if (authorMatch) {
|
|
33
|
+
return true;
|
|
34
|
+
}
|
|
35
|
+
// Check text patterns
|
|
36
|
+
const textMatch = AI_TEXT_PATTERNS.some((pattern) => comment.text.includes(pattern));
|
|
37
|
+
return textMatch;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Check if a comment is a developer reply (not from AI)
|
|
41
|
+
*/
|
|
42
|
+
isDeveloperComment(comment) {
|
|
43
|
+
return !this.isAIComment(comment);
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Extract AI comment + developer reply pairs from a list of comments
|
|
47
|
+
*/
|
|
48
|
+
extractCommentPairs(comments) {
|
|
49
|
+
const pairs = [];
|
|
50
|
+
// Group comments by parent (for threaded comments)
|
|
51
|
+
const commentsByParent = new Map();
|
|
52
|
+
const commentById = new Map();
|
|
53
|
+
for (const comment of comments) {
|
|
54
|
+
commentById.set(comment.id, comment);
|
|
55
|
+
const parentKey = comment.parentId;
|
|
56
|
+
if (!commentsByParent.has(parentKey)) {
|
|
57
|
+
commentsByParent.set(parentKey, []);
|
|
58
|
+
}
|
|
59
|
+
commentsByParent.get(parentKey).push(comment);
|
|
60
|
+
}
|
|
61
|
+
// Find AI comments with developer replies
|
|
62
|
+
for (const comment of comments) {
|
|
63
|
+
if (this.isAIComment(comment)) {
|
|
64
|
+
// Look for developer replies to this AI comment
|
|
65
|
+
const replies = commentsByParent.get(comment.id) || [];
|
|
66
|
+
const developerReplies = replies.filter((r) => this.isDeveloperComment(r));
|
|
67
|
+
for (const reply of developerReplies) {
|
|
68
|
+
pairs.push({
|
|
69
|
+
aiComment: comment,
|
|
70
|
+
developerReply: reply,
|
|
71
|
+
filePath: comment.filePath || reply.filePath,
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
// Also check for inline comment threads (adjacent comments on same file/line)
|
|
77
|
+
const inlinePairs = this.findInlineCommentPairs(comments);
|
|
78
|
+
pairs.push(...inlinePairs);
|
|
79
|
+
return pairs;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Find comment pairs based on file/line proximity (for inline comments)
|
|
83
|
+
*/
|
|
84
|
+
findInlineCommentPairs(comments) {
|
|
85
|
+
const pairs = [];
|
|
86
|
+
const processedIds = new Set();
|
|
87
|
+
// Group by file path and line
|
|
88
|
+
const byLocation = new Map();
|
|
89
|
+
for (const comment of comments) {
|
|
90
|
+
if (comment.filePath && comment.lineNumber) {
|
|
91
|
+
const key = `${comment.filePath}:${comment.lineNumber}`;
|
|
92
|
+
if (!byLocation.has(key)) {
|
|
93
|
+
byLocation.set(key, []);
|
|
94
|
+
}
|
|
95
|
+
byLocation.get(key).push(comment);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
// Find AI + developer pairs at same location
|
|
99
|
+
for (const [, locationComments] of byLocation) {
|
|
100
|
+
// Sort by timestamp
|
|
101
|
+
locationComments.sort((a, b) => new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime());
|
|
102
|
+
for (let i = 0; i < locationComments.length; i++) {
|
|
103
|
+
const aiComment = locationComments[i];
|
|
104
|
+
if (!this.isAIComment(aiComment) || processedIds.has(aiComment.id)) {
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
// Look for developer reply after this AI comment
|
|
108
|
+
for (let j = i + 1; j < locationComments.length; j++) {
|
|
109
|
+
const reply = locationComments[j];
|
|
110
|
+
if (this.isDeveloperComment(reply) && !processedIds.has(reply.id)) {
|
|
111
|
+
pairs.push({
|
|
112
|
+
aiComment,
|
|
113
|
+
developerReply: reply,
|
|
114
|
+
filePath: aiComment.filePath,
|
|
115
|
+
});
|
|
116
|
+
processedIds.add(aiComment.id);
|
|
117
|
+
processedIds.add(reply.id);
|
|
118
|
+
break;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return pairs;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Convert raw Bitbucket API comment to PRComment
|
|
127
|
+
*/
|
|
128
|
+
convertBitbucketComment(rawComment) {
|
|
129
|
+
const author = rawComment.author;
|
|
130
|
+
const user = rawComment.user;
|
|
131
|
+
const authorData = author || user;
|
|
132
|
+
// Handle text extraction - Bitbucket Cloud uses content.raw
|
|
133
|
+
let text = rawComment.text;
|
|
134
|
+
if (!text) {
|
|
135
|
+
const content = rawComment.content;
|
|
136
|
+
if (typeof content === "string") {
|
|
137
|
+
text = content;
|
|
138
|
+
}
|
|
139
|
+
else if (content && typeof content === "object" && "raw" in content) {
|
|
140
|
+
text = content.raw;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return {
|
|
144
|
+
id: rawComment.id ||
|
|
145
|
+
parseInt(String(rawComment.id), 10) ||
|
|
146
|
+
Date.now(),
|
|
147
|
+
text: text || "",
|
|
148
|
+
author: {
|
|
149
|
+
name: authorData?.name ||
|
|
150
|
+
authorData?.username ||
|
|
151
|
+
authorData?.slug ||
|
|
152
|
+
"Unknown",
|
|
153
|
+
displayName: authorData?.displayName,
|
|
154
|
+
email: authorData?.emailAddress,
|
|
155
|
+
},
|
|
156
|
+
createdAt: rawComment.createdDate ||
|
|
157
|
+
rawComment.created_on ||
|
|
158
|
+
new Date().toISOString(),
|
|
159
|
+
filePath: rawComment.path ||
|
|
160
|
+
rawComment.inline?.path,
|
|
161
|
+
lineNumber: rawComment.line ||
|
|
162
|
+
rawComment.inline?.to,
|
|
163
|
+
parentId: rawComment.parent?.id || undefined,
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Extract meaningful feedback from a comment pair
|
|
168
|
+
* Determines if the developer reply contains actionable feedback
|
|
169
|
+
*/
|
|
170
|
+
hasActionableFeedback(pair) {
|
|
171
|
+
const reply = pair.developerReply.text.toLowerCase();
|
|
172
|
+
// Skip very short replies (likely just acknowledgments like "ok" or "done")
|
|
173
|
+
if (reply.length < 20) {
|
|
174
|
+
return false;
|
|
175
|
+
}
|
|
176
|
+
// Skip pure acknowledgments
|
|
177
|
+
const acknowledgments = [
|
|
178
|
+
"fixed",
|
|
179
|
+
"done",
|
|
180
|
+
"ok",
|
|
181
|
+
"thanks",
|
|
182
|
+
"thank you",
|
|
183
|
+
"will do",
|
|
184
|
+
"good catch",
|
|
185
|
+
"nice catch",
|
|
186
|
+
"👍",
|
|
187
|
+
"✅",
|
|
188
|
+
];
|
|
189
|
+
const isJustAcknowledgment = acknowledgments.some((ack) => reply.trim() === ack || reply.trim() === ack + ".");
|
|
190
|
+
if (isJustAcknowledgment) {
|
|
191
|
+
return false;
|
|
192
|
+
}
|
|
193
|
+
// Look for indicators of substantive feedback
|
|
194
|
+
const feedbackIndicators = [
|
|
195
|
+
"actually",
|
|
196
|
+
"but",
|
|
197
|
+
"however",
|
|
198
|
+
"not necessary",
|
|
199
|
+
"don't need",
|
|
200
|
+
"intentional",
|
|
201
|
+
"by design",
|
|
202
|
+
"we prefer",
|
|
203
|
+
"our convention",
|
|
204
|
+
"team decision",
|
|
205
|
+
"won't fix",
|
|
206
|
+
"false positive",
|
|
207
|
+
"not an issue",
|
|
208
|
+
"this is fine",
|
|
209
|
+
"this is okay",
|
|
210
|
+
"that's expected",
|
|
211
|
+
"that's intentional",
|
|
212
|
+
"on purpose",
|
|
213
|
+
"should also",
|
|
214
|
+
"you missed",
|
|
215
|
+
"also check",
|
|
216
|
+
"what about",
|
|
217
|
+
"consider",
|
|
218
|
+
];
|
|
219
|
+
return feedbackIndicators.some((indicator) => reply.includes(indicator));
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* Get statistics about comment analysis
|
|
223
|
+
*/
|
|
224
|
+
getCommentStats(comments) {
|
|
225
|
+
const pairs = this.extractCommentPairs(comments);
|
|
226
|
+
const actionablePairs = pairs.filter((p) => this.hasActionableFeedback(p));
|
|
227
|
+
return {
|
|
228
|
+
total: comments.length,
|
|
229
|
+
aiComments: comments.filter((c) => this.isAIComment(c)).length,
|
|
230
|
+
developerComments: comments.filter((c) => this.isDeveloperComment(c))
|
|
231
|
+
.length,
|
|
232
|
+
pairs: pairs.length,
|
|
233
|
+
actionablePairs: actionablePairs.length,
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
//# sourceMappingURL=FeedbackExtractor.js.map
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Knowledge Base Manager
|
|
3
|
+
* Handles reading, writing, and parsing the knowledge base markdown file
|
|
4
|
+
*/
|
|
5
|
+
import { KnowledgeBaseConfig } from "../types/config.types.js";
|
|
6
|
+
import { KnowledgeBase, ExtractedLearning } from "./types.js";
|
|
7
|
+
export declare class KnowledgeBaseManager {
|
|
8
|
+
private config;
|
|
9
|
+
private projectRoot;
|
|
10
|
+
constructor(config: KnowledgeBaseConfig, projectRoot?: string);
|
|
11
|
+
/**
|
|
12
|
+
* Get the full path to the knowledge base file
|
|
13
|
+
*/
|
|
14
|
+
getFilePath(): string;
|
|
15
|
+
/**
|
|
16
|
+
* Check if knowledge base file exists
|
|
17
|
+
*/
|
|
18
|
+
exists(): boolean;
|
|
19
|
+
/**
|
|
20
|
+
* Load and parse the knowledge base file
|
|
21
|
+
*/
|
|
22
|
+
load(): Promise<KnowledgeBase>;
|
|
23
|
+
/**
|
|
24
|
+
* Append new learnings to the knowledge base
|
|
25
|
+
* Returns count of learnings actually added (excludes duplicates)
|
|
26
|
+
*/
|
|
27
|
+
append(learnings: ExtractedLearning[]): Promise<number>;
|
|
28
|
+
/**
|
|
29
|
+
* Write the knowledge base back to file
|
|
30
|
+
*/
|
|
31
|
+
write(kb: KnowledgeBase): Promise<void>;
|
|
32
|
+
/**
|
|
33
|
+
* Write raw markdown content directly to file
|
|
34
|
+
* Used by summarization to write AI-generated consolidated content
|
|
35
|
+
*/
|
|
36
|
+
writeRaw(content: string): Promise<void>;
|
|
37
|
+
/**
|
|
38
|
+
* Create a new knowledge base file from template
|
|
39
|
+
*/
|
|
40
|
+
create(): Promise<void>;
|
|
41
|
+
/**
|
|
42
|
+
* Get knowledge base content formatted for AI prompt injection
|
|
43
|
+
*/
|
|
44
|
+
getForPrompt(): Promise<string | null>;
|
|
45
|
+
/**
|
|
46
|
+
* Get count of learnings in the knowledge base
|
|
47
|
+
*/
|
|
48
|
+
getLearningCount(): Promise<number>;
|
|
49
|
+
/**
|
|
50
|
+
* Check if summarization is needed based on entry count
|
|
51
|
+
*/
|
|
52
|
+
needsSummarization(): Promise<boolean>;
|
|
53
|
+
/**
|
|
54
|
+
* Commit the knowledge base file to git
|
|
55
|
+
* Uses execFile with argument arrays to prevent command injection
|
|
56
|
+
*/
|
|
57
|
+
commit(prId: number, learningsAdded: number): Promise<void>;
|
|
58
|
+
/**
|
|
59
|
+
* Generate a hash for deduplication
|
|
60
|
+
*/
|
|
61
|
+
generateLearningId(learning: string): string;
|
|
62
|
+
/**
|
|
63
|
+
* Create an empty knowledge base structure
|
|
64
|
+
*/
|
|
65
|
+
private createEmptyKnowledgeBase;
|
|
66
|
+
/**
|
|
67
|
+
* Check if a learning already exists in the knowledge base
|
|
68
|
+
*/
|
|
69
|
+
private isDuplicate;
|
|
70
|
+
/**
|
|
71
|
+
* Check if two learnings are similar (simple similarity check)
|
|
72
|
+
*/
|
|
73
|
+
private isSimilar;
|
|
74
|
+
/**
|
|
75
|
+
* Parse markdown content into structured knowledge base
|
|
76
|
+
*/
|
|
77
|
+
private parseMarkdown;
|
|
78
|
+
/**
|
|
79
|
+
* Convert category section name back to category enum
|
|
80
|
+
*/
|
|
81
|
+
private categoryFromSectionName;
|
|
82
|
+
/**
|
|
83
|
+
* Convert knowledge base structure to markdown
|
|
84
|
+
*/
|
|
85
|
+
private toMarkdown;
|
|
86
|
+
/**
|
|
87
|
+
* Get description text for empty sections
|
|
88
|
+
*/
|
|
89
|
+
private getSectionDescription;
|
|
90
|
+
}
|
|
91
|
+
//# sourceMappingURL=KnowledgeBaseManager.d.ts.map
|