rvue-mcp 0.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/dist/bin/rvue-mcp.d.ts +1 -0
- package/dist/bin/rvue-mcp.js +10 -0
- package/dist/chunk-TNWUYJWK.js +752 -0
- package/dist/src/server.d.ts +3 -0
- package/dist/src/server.js +6 -0
- package/package.json +47 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
@@ -0,0 +1,752 @@
|
|
|
1
|
+
// src/server.ts
|
|
2
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
|
+
import { z as z3 } from "zod";
|
|
5
|
+
|
|
6
|
+
// src/intelligence/reader.ts
|
|
7
|
+
import { readFileSync, existsSync, watchFile, unwatchFile } from "fs";
|
|
8
|
+
import { join } from "path";
|
|
9
|
+
|
|
10
|
+
// ../shared/src/schemas/intelligence.ts
|
|
11
|
+
import { z } from "zod";
|
|
12
|
+
var intelligenceSourceSchema = z.enum([
|
|
13
|
+
"code_analysis",
|
|
14
|
+
"config_import",
|
|
15
|
+
"stack_baseline",
|
|
16
|
+
"git_history",
|
|
17
|
+
"pr_analysis",
|
|
18
|
+
"rejection_analysis",
|
|
19
|
+
"comment_analysis"
|
|
20
|
+
]);
|
|
21
|
+
var maturityLevelSchema = z.union([
|
|
22
|
+
z.literal(1),
|
|
23
|
+
z.literal(2),
|
|
24
|
+
z.literal(3),
|
|
25
|
+
z.literal(4)
|
|
26
|
+
]);
|
|
27
|
+
var maturityLabelSchema = z.enum([
|
|
28
|
+
"baseline",
|
|
29
|
+
"growing",
|
|
30
|
+
"established",
|
|
31
|
+
"mature"
|
|
32
|
+
]);
|
|
33
|
+
var conventionCategorySchema = z.enum([
|
|
34
|
+
"imports",
|
|
35
|
+
"testing",
|
|
36
|
+
"naming",
|
|
37
|
+
"async",
|
|
38
|
+
"structure",
|
|
39
|
+
"error-handling",
|
|
40
|
+
"api",
|
|
41
|
+
"documentation",
|
|
42
|
+
"security",
|
|
43
|
+
"performance",
|
|
44
|
+
"type-safety",
|
|
45
|
+
"other"
|
|
46
|
+
]);
|
|
47
|
+
var antiPatternSeveritySchema = z.enum([
|
|
48
|
+
"critical",
|
|
49
|
+
"high",
|
|
50
|
+
"medium",
|
|
51
|
+
"low"
|
|
52
|
+
]);
|
|
53
|
+
var techStackSchema = z.object({
|
|
54
|
+
language: z.string(),
|
|
55
|
+
languages: z.array(z.string()).optional(),
|
|
56
|
+
framework: z.string().optional(),
|
|
57
|
+
framework_version: z.string().optional(),
|
|
58
|
+
test_framework: z.string().optional(),
|
|
59
|
+
linter: z.string().optional(),
|
|
60
|
+
formatter: z.string().optional(),
|
|
61
|
+
build_tool: z.string().optional(),
|
|
62
|
+
package_manager: z.string().optional()
|
|
63
|
+
});
|
|
64
|
+
var conventionExampleSchema = z.object({
|
|
65
|
+
pr_number: z.number().optional(),
|
|
66
|
+
file_path: z.string(),
|
|
67
|
+
snippet: z.string().optional()
|
|
68
|
+
});
|
|
69
|
+
var conventionSchema = z.object({
|
|
70
|
+
id: z.string(),
|
|
71
|
+
category: conventionCategorySchema,
|
|
72
|
+
rule: z.string(),
|
|
73
|
+
confidence: z.number().min(0).max(1),
|
|
74
|
+
source: intelligenceSourceSchema,
|
|
75
|
+
evidence_count: z.number(),
|
|
76
|
+
first_seen: z.string(),
|
|
77
|
+
examples: z.array(conventionExampleSchema),
|
|
78
|
+
metadata: z.record(z.string(), z.unknown()).optional()
|
|
79
|
+
});
|
|
80
|
+
var antiPatternEvidenceSchema = z.object({
|
|
81
|
+
pr_number: z.number(),
|
|
82
|
+
type: z.enum(["rejected_pr", "review_comment", "bug_fix"]),
|
|
83
|
+
excerpt: z.string().optional()
|
|
84
|
+
});
|
|
85
|
+
var antiPatternSchema = z.object({
|
|
86
|
+
id: z.string(),
|
|
87
|
+
pattern: z.string(),
|
|
88
|
+
severity: antiPatternSeveritySchema,
|
|
89
|
+
reason: z.string(),
|
|
90
|
+
correct_approach: z.string(),
|
|
91
|
+
evidence: z.array(antiPatternEvidenceSchema)
|
|
92
|
+
});
|
|
93
|
+
var knowledgeEntrySchema = z.object({
|
|
94
|
+
value: z.string(),
|
|
95
|
+
confidence: z.number().min(0).max(1),
|
|
96
|
+
source_prs: z.array(z.number())
|
|
97
|
+
});
|
|
98
|
+
var knowledgeGraphSchema = z.record(
|
|
99
|
+
z.string(),
|
|
100
|
+
z.record(z.string(), knowledgeEntrySchema)
|
|
101
|
+
);
|
|
102
|
+
var reviewerExpertiseSchema = z.object({
|
|
103
|
+
area: z.string(),
|
|
104
|
+
confidence: z.number().min(0).max(1),
|
|
105
|
+
review_count: z.number()
|
|
106
|
+
});
|
|
107
|
+
var reviewerStatsSchema = z.object({
|
|
108
|
+
total_reviews: z.number(),
|
|
109
|
+
avg_review_time_hours: z.number(),
|
|
110
|
+
approval_rate: z.number().min(0).max(1)
|
|
111
|
+
});
|
|
112
|
+
var reviewerProfileSchema = z.object({
|
|
113
|
+
github_login: z.string(),
|
|
114
|
+
expertise: z.array(reviewerExpertiseSchema),
|
|
115
|
+
stats: reviewerStatsSchema
|
|
116
|
+
});
|
|
117
|
+
var riskFactorSchema = z.object({
|
|
118
|
+
type: z.enum(["file", "size", "timing", "complexity"]),
|
|
119
|
+
description: z.string(),
|
|
120
|
+
value: z.string(),
|
|
121
|
+
risk_multiplier: z.number(),
|
|
122
|
+
evidence: z.object({
|
|
123
|
+
total_prs: z.number(),
|
|
124
|
+
incidents: z.number()
|
|
125
|
+
})
|
|
126
|
+
});
|
|
127
|
+
var maturitySchema = z.object({
|
|
128
|
+
level: maturityLevelSchema,
|
|
129
|
+
label: maturityLabelSchema,
|
|
130
|
+
prs_analyzed: z.number(),
|
|
131
|
+
next_level_at: z.number(),
|
|
132
|
+
sources: z.array(intelligenceSourceSchema)
|
|
133
|
+
});
|
|
134
|
+
var repositoryInfoSchema = z.object({
|
|
135
|
+
owner: z.string(),
|
|
136
|
+
name: z.string(),
|
|
137
|
+
full_name: z.string(),
|
|
138
|
+
analyzed_prs: z.number(),
|
|
139
|
+
analyzed_period: z.object({
|
|
140
|
+
from: z.string(),
|
|
141
|
+
to: z.string()
|
|
142
|
+
}),
|
|
143
|
+
tech_stack: techStackSchema,
|
|
144
|
+
rvue_version: z.string()
|
|
145
|
+
});
|
|
146
|
+
var intelligenceFileSchema = z.object({
|
|
147
|
+
version: z.string(),
|
|
148
|
+
generated_at: z.string(),
|
|
149
|
+
last_synced_at: z.string(),
|
|
150
|
+
maturity: maturitySchema,
|
|
151
|
+
repository: repositoryInfoSchema,
|
|
152
|
+
conventions: z.array(conventionSchema),
|
|
153
|
+
anti_patterns: z.array(antiPatternSchema),
|
|
154
|
+
knowledge_graph: knowledgeGraphSchema,
|
|
155
|
+
reviewers: z.array(reviewerProfileSchema),
|
|
156
|
+
risk_factors: z.array(riskFactorSchema)
|
|
157
|
+
});
|
|
158
|
+
var conventionsFileSchema = z.array(conventionSchema);
|
|
159
|
+
var antiPatternsFileSchema = z.array(antiPatternSchema);
|
|
160
|
+
var knowledgeFileSchema = knowledgeGraphSchema;
|
|
161
|
+
var reviewersFileSchema = z.array(reviewerProfileSchema);
|
|
162
|
+
var intelligenceMetaSchema = z.object({
|
|
163
|
+
version: z.string(),
|
|
164
|
+
generated_at: z.string(),
|
|
165
|
+
last_synced_at: z.string(),
|
|
166
|
+
maturity: maturitySchema,
|
|
167
|
+
repository: repositoryInfoSchema,
|
|
168
|
+
risk_factors: z.array(riskFactorSchema),
|
|
169
|
+
counts: z.object({
|
|
170
|
+
conventions: z.number(),
|
|
171
|
+
anti_patterns: z.number(),
|
|
172
|
+
reviewers: z.number(),
|
|
173
|
+
knowledge_topics: z.number()
|
|
174
|
+
})
|
|
175
|
+
});
|
|
176
|
+
var proposalSchema = z.object({
|
|
177
|
+
id: z.string(),
|
|
178
|
+
type: z.enum(["new_convention", "update_convention", "new_exception", "deprecate"]),
|
|
179
|
+
targetId: z.string().optional(),
|
|
180
|
+
data: conventionSchema.partial(),
|
|
181
|
+
reason: z.string(),
|
|
182
|
+
filePath: z.string().optional(),
|
|
183
|
+
createdAt: z.string(),
|
|
184
|
+
status: z.enum(["pending", "approved", "rejected"]),
|
|
185
|
+
approvedBy: z.string().optional()
|
|
186
|
+
});
|
|
187
|
+
var proposalsFileSchema = z.array(proposalSchema);
|
|
188
|
+
|
|
189
|
+
// ../shared/src/schemas/config.ts
|
|
190
|
+
import { z as z2 } from "zod";
|
|
191
|
+
var customRuleSchema = z2.object({
|
|
192
|
+
name: z2.string(),
|
|
193
|
+
severity: z2.enum(["error", "warning", "info"]),
|
|
194
|
+
description: z2.string()
|
|
195
|
+
});
|
|
196
|
+
var RvueConfigSchema = z2.object({
|
|
197
|
+
version: z2.string(),
|
|
198
|
+
analysis: z2.object({
|
|
199
|
+
lookback_months: z2.number().default(12),
|
|
200
|
+
min_prs_for_pattern: z2.number().default(10),
|
|
201
|
+
min_confidence: z2.number().min(0).max(1).default(0.7),
|
|
202
|
+
include_closed_prs: z2.boolean().default(true)
|
|
203
|
+
}),
|
|
204
|
+
ignore: z2.object({
|
|
205
|
+
files: z2.array(z2.string()).default([]),
|
|
206
|
+
authors: z2.array(z2.string()).default(["dependabot", "renovate"]),
|
|
207
|
+
labels: z2.array(z2.string()).default(["wip", "draft"])
|
|
208
|
+
}),
|
|
209
|
+
sync: z2.object({
|
|
210
|
+
auto_commit: z2.boolean().default(false)
|
|
211
|
+
}),
|
|
212
|
+
custom_rules: z2.array(customRuleSchema).default([])
|
|
213
|
+
});
|
|
214
|
+
var userConfigSchema = z2.object({
|
|
215
|
+
auth: z2.object({
|
|
216
|
+
token: z2.string(),
|
|
217
|
+
github_token: z2.string(),
|
|
218
|
+
user: z2.object({
|
|
219
|
+
id: z2.string(),
|
|
220
|
+
login: z2.string(),
|
|
221
|
+
email: z2.string()
|
|
222
|
+
})
|
|
223
|
+
}).optional(),
|
|
224
|
+
defaults: z2.object({
|
|
225
|
+
ai_model: z2.string().default("deepseek")
|
|
226
|
+
}).optional()
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
// ../shared/src/constants.ts
|
|
230
|
+
var PATHS = {
|
|
231
|
+
/** @deprecated Use individual file paths instead */
|
|
232
|
+
INTELLIGENCE_FILE: ".rvue/intelligence.json",
|
|
233
|
+
CONVENTIONS_FILE: ".rvue/conventions.json",
|
|
234
|
+
ANTI_PATTERNS_FILE: ".rvue/anti-patterns.json",
|
|
235
|
+
KNOWLEDGE_FILE: ".rvue/knowledge.json",
|
|
236
|
+
REVIEWERS_FILE: ".rvue/reviewers.json",
|
|
237
|
+
META_FILE: ".rvue/meta.json",
|
|
238
|
+
PROPOSALS_FILE: ".rvue/proposals.json",
|
|
239
|
+
README_FILE: ".rvue/README.md",
|
|
240
|
+
RVUE_GITIGNORE: ".rvue/.gitignore",
|
|
241
|
+
CONFIG_FILE: ".rvue/config.json",
|
|
242
|
+
SKILL_DIR: ".cursor/skills/rvue-team-intel",
|
|
243
|
+
SKILL_FILE: ".cursor/skills/rvue-team-intel/SKILL.md",
|
|
244
|
+
SKILL_REFERENCES: ".cursor/skills/rvue-team-intel/references",
|
|
245
|
+
SKILL_SCRIPTS: ".cursor/skills/rvue-team-intel/scripts",
|
|
246
|
+
USER_CONFIG: ".rvue/config.json"
|
|
247
|
+
};
|
|
248
|
+
var AI_MODELS = {
|
|
249
|
+
FREE: "deepseek/deepseek-v3.2",
|
|
250
|
+
PAID: "anthropic/claude-sonnet-4-20250514"
|
|
251
|
+
};
|
|
252
|
+
var PLAN_LIMITS = {
|
|
253
|
+
free: {
|
|
254
|
+
private_repos: 1,
|
|
255
|
+
pr_lookback: 100,
|
|
256
|
+
ai_model: AI_MODELS.FREE
|
|
257
|
+
},
|
|
258
|
+
team: {
|
|
259
|
+
private_repos: Infinity,
|
|
260
|
+
pr_lookback: Infinity,
|
|
261
|
+
ai_model: AI_MODELS.PAID
|
|
262
|
+
},
|
|
263
|
+
enterprise: {
|
|
264
|
+
private_repos: Infinity,
|
|
265
|
+
pr_lookback: Infinity,
|
|
266
|
+
ai_model: AI_MODELS.PAID
|
|
267
|
+
}
|
|
268
|
+
};
|
|
269
|
+
|
|
270
|
+
// src/intelligence/reader.ts
|
|
271
|
+
var SPLIT_FILES = [
|
|
272
|
+
PATHS.META_FILE,
|
|
273
|
+
PATHS.CONVENTIONS_FILE,
|
|
274
|
+
PATHS.ANTI_PATTERNS_FILE,
|
|
275
|
+
PATHS.KNOWLEDGE_FILE,
|
|
276
|
+
PATHS.REVIEWERS_FILE
|
|
277
|
+
];
|
|
278
|
+
function readIntelligenceFile(cwd) {
|
|
279
|
+
const metaPath = join(cwd, PATHS.META_FILE);
|
|
280
|
+
if (existsSync(metaPath)) {
|
|
281
|
+
return readSplitFiles(cwd);
|
|
282
|
+
}
|
|
283
|
+
return readLegacyFile(cwd);
|
|
284
|
+
}
|
|
285
|
+
function watchIntelligenceFile(cwd, onChange) {
|
|
286
|
+
const reload = () => {
|
|
287
|
+
onChange(readIntelligenceFile(cwd));
|
|
288
|
+
};
|
|
289
|
+
const allPaths = [
|
|
290
|
+
join(cwd, PATHS.INTELLIGENCE_FILE),
|
|
291
|
+
...SPLIT_FILES.map((rel) => join(cwd, rel))
|
|
292
|
+
];
|
|
293
|
+
for (const abs of allPaths) {
|
|
294
|
+
watchFile(abs, { interval: 2e3 }, reload);
|
|
295
|
+
}
|
|
296
|
+
return () => {
|
|
297
|
+
for (const abs of allPaths) {
|
|
298
|
+
unwatchFile(abs, reload);
|
|
299
|
+
}
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
function readSplitFiles(cwd) {
|
|
303
|
+
try {
|
|
304
|
+
const meta = readJson(join(cwd, PATHS.META_FILE), intelligenceMetaSchema);
|
|
305
|
+
if (!meta) return null;
|
|
306
|
+
const conventions = readJson(join(cwd, PATHS.CONVENTIONS_FILE), conventionsFileSchema) ?? [];
|
|
307
|
+
const antiPatterns = readJson(join(cwd, PATHS.ANTI_PATTERNS_FILE), antiPatternsFileSchema) ?? [];
|
|
308
|
+
const knowledgeGraph = readJson(join(cwd, PATHS.KNOWLEDGE_FILE), knowledgeFileSchema) ?? {};
|
|
309
|
+
const reviewers = readJson(join(cwd, PATHS.REVIEWERS_FILE), reviewersFileSchema) ?? [];
|
|
310
|
+
return {
|
|
311
|
+
version: meta.version,
|
|
312
|
+
generated_at: meta.generated_at,
|
|
313
|
+
last_synced_at: meta.last_synced_at,
|
|
314
|
+
maturity: meta.maturity,
|
|
315
|
+
repository: meta.repository,
|
|
316
|
+
conventions,
|
|
317
|
+
anti_patterns: antiPatterns,
|
|
318
|
+
knowledge_graph: knowledgeGraph,
|
|
319
|
+
reviewers,
|
|
320
|
+
risk_factors: meta.risk_factors
|
|
321
|
+
};
|
|
322
|
+
} catch (error) {
|
|
323
|
+
console.error("Failed to read split intelligence files:", error);
|
|
324
|
+
return null;
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
function readLegacyFile(cwd) {
|
|
328
|
+
return readJson(
|
|
329
|
+
join(cwd, PATHS.INTELLIGENCE_FILE),
|
|
330
|
+
intelligenceFileSchema
|
|
331
|
+
);
|
|
332
|
+
}
|
|
333
|
+
function readJson(filePath, schema) {
|
|
334
|
+
if (!existsSync(filePath)) return null;
|
|
335
|
+
try {
|
|
336
|
+
const raw = readFileSync(filePath, "utf-8");
|
|
337
|
+
const parsed = JSON.parse(raw);
|
|
338
|
+
return schema.parse(parsed);
|
|
339
|
+
} catch (error) {
|
|
340
|
+
console.error(`Failed to read ${filePath}:`, error);
|
|
341
|
+
return null;
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// src/tools/get-team-context.ts
|
|
346
|
+
function handleGetTeamContext(intelligence2, input) {
|
|
347
|
+
const scope = input.scope || "all";
|
|
348
|
+
const sections = [];
|
|
349
|
+
sections.push(`# Team Intelligence: ${intelligence2.repository.full_name}`);
|
|
350
|
+
sections.push(
|
|
351
|
+
`Maturity: ${intelligence2.maturity.label} | PRs analyzed: ${intelligence2.maturity.prs_analyzed}`
|
|
352
|
+
);
|
|
353
|
+
sections.push(
|
|
354
|
+
`Tech stack: ${intelligence2.repository.tech_stack.language}${intelligence2.repository.tech_stack.framework ? ` / ${intelligence2.repository.tech_stack.framework}` : ""}`
|
|
355
|
+
);
|
|
356
|
+
sections.push("");
|
|
357
|
+
let conventions;
|
|
358
|
+
if (scope === "all") {
|
|
359
|
+
conventions = intelligence2.conventions.slice(0, 20);
|
|
360
|
+
} else {
|
|
361
|
+
conventions = intelligence2.conventions.filter((c) => {
|
|
362
|
+
if (c.category === scope) return true;
|
|
363
|
+
if (input.file_path) {
|
|
364
|
+
return isConventionRelevantToFile(c, input.file_path);
|
|
365
|
+
}
|
|
366
|
+
return false;
|
|
367
|
+
});
|
|
368
|
+
if (conventions.length < 3) {
|
|
369
|
+
conventions = [
|
|
370
|
+
...conventions,
|
|
371
|
+
...intelligence2.conventions.filter((c) => c.category === scope).slice(0, 5)
|
|
372
|
+
];
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
if (conventions.length > 0) {
|
|
376
|
+
sections.push("## Conventions");
|
|
377
|
+
sections.push("");
|
|
378
|
+
for (const conv of conventions) {
|
|
379
|
+
sections.push(
|
|
380
|
+
`- **${conv.rule}** (${conv.category}, ${Math.round(conv.confidence * 100)}% confidence)`
|
|
381
|
+
);
|
|
382
|
+
}
|
|
383
|
+
sections.push("");
|
|
384
|
+
}
|
|
385
|
+
let antiPatterns;
|
|
386
|
+
if (scope === "all") {
|
|
387
|
+
antiPatterns = intelligence2.anti_patterns.slice(0, 10);
|
|
388
|
+
} else {
|
|
389
|
+
antiPatterns = intelligence2.anti_patterns.filter((ap) => {
|
|
390
|
+
const patternLower = ap.pattern.toLowerCase();
|
|
391
|
+
const scopeLower = scope.toLowerCase();
|
|
392
|
+
return patternLower.includes(scopeLower);
|
|
393
|
+
});
|
|
394
|
+
if (antiPatterns.length === 0) {
|
|
395
|
+
antiPatterns = intelligence2.anti_patterns.slice(0, 5);
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
if (antiPatterns.length > 0) {
|
|
399
|
+
sections.push("## Anti-Patterns (Avoid These)");
|
|
400
|
+
sections.push("");
|
|
401
|
+
for (const ap of antiPatterns) {
|
|
402
|
+
sections.push(
|
|
403
|
+
`- **${ap.pattern}** [${ap.severity}] - ${ap.correct_approach}`
|
|
404
|
+
);
|
|
405
|
+
}
|
|
406
|
+
sections.push("");
|
|
407
|
+
}
|
|
408
|
+
if (scope !== "all") {
|
|
409
|
+
const relevantTopics = Object.entries(intelligence2.knowledge_graph).filter(
|
|
410
|
+
([topic]) => topic.toLowerCase().includes(scope.toLowerCase())
|
|
411
|
+
);
|
|
412
|
+
if (relevantTopics.length > 0) {
|
|
413
|
+
sections.push("## Domain Knowledge");
|
|
414
|
+
sections.push("");
|
|
415
|
+
for (const [topic, entries] of relevantTopics) {
|
|
416
|
+
sections.push(`### ${topic}`);
|
|
417
|
+
for (const [key, entry] of Object.entries(entries)) {
|
|
418
|
+
sections.push(`- ${key}: ${entry.value}`);
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
sections.push("");
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
if (input.file_path) {
|
|
425
|
+
const fileReviewers = intelligence2.reviewers.filter(
|
|
426
|
+
(r) => r.expertise.some(
|
|
427
|
+
(e) => e.area.split("/").some((part) => input.file_path.includes(part))
|
|
428
|
+
)
|
|
429
|
+
).slice(0, 3);
|
|
430
|
+
if (fileReviewers.length > 0) {
|
|
431
|
+
sections.push("## Suggested Reviewers for This File");
|
|
432
|
+
sections.push("");
|
|
433
|
+
for (const r of fileReviewers) {
|
|
434
|
+
sections.push(
|
|
435
|
+
`- @${r.github_login} (${r.stats.total_reviews} reviews)`
|
|
436
|
+
);
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
return sections.join("\n");
|
|
441
|
+
}
|
|
442
|
+
function isConventionRelevantToFile(conv, filePath) {
|
|
443
|
+
const fp = filePath.toLowerCase();
|
|
444
|
+
if (fp.includes(".test.") || fp.includes(".spec.") || fp.includes("__tests__")) {
|
|
445
|
+
return conv.category === "testing";
|
|
446
|
+
}
|
|
447
|
+
if (fp.includes("/api/") || fp.includes("route.ts") || fp.includes("handler")) {
|
|
448
|
+
return conv.category === "api" || conv.category === "error-handling";
|
|
449
|
+
}
|
|
450
|
+
if (fp.includes("component") || fp.endsWith(".tsx")) {
|
|
451
|
+
return conv.category === "naming" || conv.category === "structure";
|
|
452
|
+
}
|
|
453
|
+
return false;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
// src/tools/check-code.ts
|
|
457
|
+
function handleCheckCode(intelligence2, input) {
|
|
458
|
+
const violations = [];
|
|
459
|
+
const code = input.code;
|
|
460
|
+
const filePath = input.file_path || "";
|
|
461
|
+
for (const conv of intelligence2.conventions) {
|
|
462
|
+
if (conv.confidence < 0.7) continue;
|
|
463
|
+
const violation = checkConventionViolation(code, filePath, conv);
|
|
464
|
+
if (violation) {
|
|
465
|
+
violations.push({
|
|
466
|
+
convention_id: conv.id,
|
|
467
|
+
rule: conv.rule,
|
|
468
|
+
severity: violation.severity,
|
|
469
|
+
suggestion: violation.suggestion
|
|
470
|
+
});
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
for (const ap of intelligence2.anti_patterns) {
|
|
474
|
+
const violation = checkAntiPatternViolation(code, filePath, ap);
|
|
475
|
+
if (violation) {
|
|
476
|
+
violations.push({
|
|
477
|
+
convention_id: ap.id,
|
|
478
|
+
rule: ap.pattern,
|
|
479
|
+
severity: violation.severity,
|
|
480
|
+
suggestion: violation.suggestion
|
|
481
|
+
});
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
return {
|
|
485
|
+
violations,
|
|
486
|
+
passed: violations.filter((v) => v.severity === "error").length === 0
|
|
487
|
+
};
|
|
488
|
+
}
|
|
489
|
+
function checkConventionViolation(code, filePath, conv) {
|
|
490
|
+
const rule = conv.rule.toLowerCase();
|
|
491
|
+
if (rule.includes("named export") && conv.category === "imports") {
|
|
492
|
+
if (code.includes("export default ") && !filePath.includes("page.") && !filePath.includes("layout.")) {
|
|
493
|
+
return {
|
|
494
|
+
severity: "warning",
|
|
495
|
+
suggestion: "Use named exports. Replace `export default X` with `export { X }` or `export const/function X`."
|
|
496
|
+
};
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
if (rule.includes("path alias") && conv.category === "imports") {
|
|
500
|
+
if (code.match(/from\s+['"]\.\.\/\.\.\/\.\.\//)) {
|
|
501
|
+
return {
|
|
502
|
+
severity: "info",
|
|
503
|
+
suggestion: "Use path aliases (e.g., @/) instead of deep relative imports."
|
|
504
|
+
};
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
if (rule.includes("async/await") || rule.includes("async_await")) {
|
|
508
|
+
const thenCount = (code.match(/\.then\s*\(/g) || []).length;
|
|
509
|
+
if (thenCount > 0) {
|
|
510
|
+
return {
|
|
511
|
+
severity: "info",
|
|
512
|
+
suggestion: "Use async/await instead of .then() chains."
|
|
513
|
+
};
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
if (rule.includes("explicit return type") || rule.includes("return types")) {
|
|
517
|
+
const functionsWithoutReturnType = code.match(/(?:export\s+)?(?:async\s+)?function\s+\w+\s*\([^)]*\)\s*\{/g) || [];
|
|
518
|
+
if (functionsWithoutReturnType.length > 0) {
|
|
519
|
+
return {
|
|
520
|
+
severity: "info",
|
|
521
|
+
suggestion: "Add explicit return type annotations to functions."
|
|
522
|
+
};
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
return null;
|
|
526
|
+
}
|
|
527
|
+
function checkAntiPatternViolation(code, filePath, ap) {
|
|
528
|
+
const pattern = ap.pattern.toLowerCase();
|
|
529
|
+
if (pattern.includes("console.log") || pattern.includes("console statement")) {
|
|
530
|
+
const matches = code.match(/console\.(log|debug|info)\s*\(/g) || [];
|
|
531
|
+
if (matches.length > 0 && !filePath.includes(".test.") && !filePath.includes(".spec.")) {
|
|
532
|
+
return {
|
|
533
|
+
severity: ap.severity === "critical" ? "error" : "warning",
|
|
534
|
+
suggestion: ap.correct_approach || "Remove console statements or use a proper logger."
|
|
535
|
+
};
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
if (pattern.includes("any type") || pattern.includes("typescript any")) {
|
|
539
|
+
if (code.match(/:\s*any\b/) && !code.includes("eslint-disable")) {
|
|
540
|
+
return {
|
|
541
|
+
severity: "warning",
|
|
542
|
+
suggestion: ap.correct_approach || "Use specific types instead of `any`."
|
|
543
|
+
};
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
if (pattern.includes("todo") && pattern.includes("comment")) {
|
|
547
|
+
if (code.match(/\/\/\s*TODO/i)) {
|
|
548
|
+
return {
|
|
549
|
+
severity: "info",
|
|
550
|
+
suggestion: ap.correct_approach || "Resolve TODO comments before submitting."
|
|
551
|
+
};
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
return null;
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
// src/tools/get-knowledge.ts
|
|
558
|
+
function handleGetKnowledge(intelligence2, input) {
|
|
559
|
+
const topic = input.topic.toLowerCase();
|
|
560
|
+
const exactMatch = Object.entries(intelligence2.knowledge_graph).find(([key]) => key.toLowerCase() === topic);
|
|
561
|
+
if (exactMatch) {
|
|
562
|
+
return formatKnowledge(exactMatch[0], exactMatch[1]);
|
|
563
|
+
}
|
|
564
|
+
const fuzzyMatches = Object.entries(intelligence2.knowledge_graph).filter(
|
|
565
|
+
([key]) => key.toLowerCase().includes(topic) || topic.includes(key.toLowerCase())
|
|
566
|
+
);
|
|
567
|
+
if (fuzzyMatches.length > 0) {
|
|
568
|
+
const sections = fuzzyMatches.map(([key, entries]) => formatKnowledge(key, entries));
|
|
569
|
+
return sections.join("\n\n");
|
|
570
|
+
}
|
|
571
|
+
const availableTopics = Object.keys(intelligence2.knowledge_graph);
|
|
572
|
+
if (availableTopics.length === 0) {
|
|
573
|
+
return "No domain knowledge available yet. Run `rvue sync` to analyze PRs and extract knowledge.";
|
|
574
|
+
}
|
|
575
|
+
return `No knowledge found for topic: "${input.topic}"
|
|
576
|
+
|
|
577
|
+
Available topics: ${availableTopics.join(", ")}`;
|
|
578
|
+
}
|
|
579
|
+
function formatKnowledge(topic, entries) {
|
|
580
|
+
const lines = [`## ${topic}`, ""];
|
|
581
|
+
for (const [key, entry] of Object.entries(entries)) {
|
|
582
|
+
lines.push(`### ${key}`);
|
|
583
|
+
lines.push(entry.value);
|
|
584
|
+
lines.push(`(confidence: ${Math.round(entry.confidence * 100)}%, sources: ${entry.source_prs.length} PRs)`);
|
|
585
|
+
lines.push("");
|
|
586
|
+
}
|
|
587
|
+
return lines.join("\n");
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
// src/tools/suggest-reviewers.ts
|
|
591
|
+
function handleSuggestReviewers(intelligence2, input) {
|
|
592
|
+
if (intelligence2.reviewers.length === 0) {
|
|
593
|
+
return [];
|
|
594
|
+
}
|
|
595
|
+
const changedDirs = /* @__PURE__ */ new Set();
|
|
596
|
+
for (const file of input.files) {
|
|
597
|
+
const parts = file.split("/");
|
|
598
|
+
if (parts.length > 1) {
|
|
599
|
+
changedDirs.add(parts.slice(0, 2).join("/"));
|
|
600
|
+
changedDirs.add(parts[0]);
|
|
601
|
+
} else {
|
|
602
|
+
changedDirs.add(parts[0]);
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
const scored = [];
|
|
606
|
+
for (const reviewer of intelligence2.reviewers) {
|
|
607
|
+
let relevance = 0;
|
|
608
|
+
const matchedAreas = [];
|
|
609
|
+
for (const expertise of reviewer.expertise) {
|
|
610
|
+
const area = expertise.area.toLowerCase();
|
|
611
|
+
for (const dir of changedDirs) {
|
|
612
|
+
const dirLower = dir.toLowerCase();
|
|
613
|
+
if (area.includes(dirLower) || dirLower.includes(area)) {
|
|
614
|
+
relevance += expertise.confidence * expertise.review_count;
|
|
615
|
+
matchedAreas.push(expertise.area);
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
for (const file of input.files) {
|
|
619
|
+
const ext = file.split(".").pop()?.toLowerCase() || "";
|
|
620
|
+
if (area.includes(ext) || area.includes(file.split("/").pop() || "")) {
|
|
621
|
+
relevance += expertise.confidence * 0.5;
|
|
622
|
+
if (!matchedAreas.includes(expertise.area)) {
|
|
623
|
+
matchedAreas.push(expertise.area);
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
if (relevance > 0) {
|
|
629
|
+
scored.push({
|
|
630
|
+
login: reviewer.github_login,
|
|
631
|
+
relevance: Math.round(relevance * 100) / 100,
|
|
632
|
+
areas: [...new Set(matchedAreas)]
|
|
633
|
+
});
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
return scored.sort((a, b) => b.relevance - a.relevance).slice(0, 5);
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
// src/server.ts
|
|
640
|
+
var intelligence = null;
|
|
641
|
+
async function startServer() {
|
|
642
|
+
const cwd = process.cwd();
|
|
643
|
+
intelligence = readIntelligenceFile(cwd);
|
|
644
|
+
if (!intelligence) {
|
|
645
|
+
console.error("Warning: No .rvue/ intelligence files found in current directory.");
|
|
646
|
+
console.error("Run `rvue enable` to generate intelligence first.");
|
|
647
|
+
}
|
|
648
|
+
const unwatch = watchIntelligenceFile(cwd, (updated) => {
|
|
649
|
+
intelligence = updated;
|
|
650
|
+
if (updated) {
|
|
651
|
+
console.error("Intelligence file updated, reloaded.");
|
|
652
|
+
}
|
|
653
|
+
});
|
|
654
|
+
const server = new McpServer({
|
|
655
|
+
name: "rvue",
|
|
656
|
+
version: "0.1.0"
|
|
657
|
+
});
|
|
658
|
+
server.tool(
|
|
659
|
+
"get_team_context",
|
|
660
|
+
"Get team coding conventions, anti-patterns, and domain knowledge. Use this before writing or reviewing code to understand how the team works.",
|
|
661
|
+
{
|
|
662
|
+
scope: z3.enum(["all", "imports", "testing", "naming", "async", "api", "auth", "database", "structure", "error-handling", "documentation", "security", "performance", "type-safety"]).optional().describe("Filter context to a specific area"),
|
|
663
|
+
file_path: z3.string().optional().describe("File path to get context specific to that file type")
|
|
664
|
+
},
|
|
665
|
+
async (params) => {
|
|
666
|
+
if (!intelligence) {
|
|
667
|
+
return {
|
|
668
|
+
content: [{ type: "text", text: "No intelligence data available. Run `rvue enable` first." }]
|
|
669
|
+
};
|
|
670
|
+
}
|
|
671
|
+
const result = handleGetTeamContext(intelligence, {
|
|
672
|
+
scope: params.scope,
|
|
673
|
+
file_path: params.file_path
|
|
674
|
+
});
|
|
675
|
+
return {
|
|
676
|
+
content: [{ type: "text", text: result }]
|
|
677
|
+
};
|
|
678
|
+
}
|
|
679
|
+
);
|
|
680
|
+
server.tool(
|
|
681
|
+
"check_code",
|
|
682
|
+
"Validate a code snippet against team conventions and anti-patterns. Returns violations with suggestions.",
|
|
683
|
+
{
|
|
684
|
+
code: z3.string().describe("The code snippet to check"),
|
|
685
|
+
file_path: z3.string().optional().describe("File path for context-aware checking")
|
|
686
|
+
},
|
|
687
|
+
async (params) => {
|
|
688
|
+
if (!intelligence) {
|
|
689
|
+
return {
|
|
690
|
+
content: [{ type: "text", text: "No intelligence data available. Run `rvue enable` first." }]
|
|
691
|
+
};
|
|
692
|
+
}
|
|
693
|
+
const result = handleCheckCode(intelligence, {
|
|
694
|
+
code: params.code,
|
|
695
|
+
file_path: params.file_path
|
|
696
|
+
});
|
|
697
|
+
return {
|
|
698
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
699
|
+
};
|
|
700
|
+
}
|
|
701
|
+
);
|
|
702
|
+
server.tool(
|
|
703
|
+
"get_knowledge",
|
|
704
|
+
"Get domain-specific knowledge about a topic (e.g., authentication, database, deployment)",
|
|
705
|
+
{
|
|
706
|
+
topic: z3.string().describe('The topic to look up (e.g., "authentication", "database", "api")')
|
|
707
|
+
},
|
|
708
|
+
async (params) => {
|
|
709
|
+
if (!intelligence) {
|
|
710
|
+
return {
|
|
711
|
+
content: [{ type: "text", text: "No intelligence data available. Run `rvue enable` first." }]
|
|
712
|
+
};
|
|
713
|
+
}
|
|
714
|
+
const result = handleGetKnowledge(intelligence, { topic: params.topic });
|
|
715
|
+
return {
|
|
716
|
+
content: [{ type: "text", text: result }]
|
|
717
|
+
};
|
|
718
|
+
}
|
|
719
|
+
);
|
|
720
|
+
server.tool(
|
|
721
|
+
"suggest_reviewers",
|
|
722
|
+
"Suggest the best reviewers for a set of changed files based on team expertise",
|
|
723
|
+
{
|
|
724
|
+
files: z3.array(z3.string()).describe("List of file paths that were changed")
|
|
725
|
+
},
|
|
726
|
+
async (params) => {
|
|
727
|
+
if (!intelligence) {
|
|
728
|
+
return {
|
|
729
|
+
content: [{ type: "text", text: "No intelligence data available. Run `rvue enable` first." }]
|
|
730
|
+
};
|
|
731
|
+
}
|
|
732
|
+
const result = handleSuggestReviewers(intelligence, { files: params.files });
|
|
733
|
+
return {
|
|
734
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
735
|
+
};
|
|
736
|
+
}
|
|
737
|
+
);
|
|
738
|
+
const transport = new StdioServerTransport();
|
|
739
|
+
await server.connect(transport);
|
|
740
|
+
process.on("SIGINT", () => {
|
|
741
|
+
unwatch();
|
|
742
|
+
process.exit(0);
|
|
743
|
+
});
|
|
744
|
+
process.on("SIGTERM", () => {
|
|
745
|
+
unwatch();
|
|
746
|
+
process.exit(0);
|
|
747
|
+
});
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
export {
|
|
751
|
+
startServer
|
|
752
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "rvue-mcp",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "MCP server for rvue team intelligence. Serves team conventions, anti-patterns, and domain knowledge to AI coding agents via the Model Context Protocol.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"homepage": "https://rvue.dev",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "https://github.com/sriramfullstack/rvue-web-app",
|
|
11
|
+
"directory": "packages/mcp-server"
|
|
12
|
+
},
|
|
13
|
+
"keywords": [
|
|
14
|
+
"mcp",
|
|
15
|
+
"model-context-protocol",
|
|
16
|
+
"ai",
|
|
17
|
+
"cursor",
|
|
18
|
+
"conventions",
|
|
19
|
+
"code-review",
|
|
20
|
+
"team-intelligence"
|
|
21
|
+
],
|
|
22
|
+
"engines": {
|
|
23
|
+
"node": ">=20"
|
|
24
|
+
},
|
|
25
|
+
"bin": {
|
|
26
|
+
"rvue-mcp": "./dist/bin/rvue-mcp.js"
|
|
27
|
+
},
|
|
28
|
+
"files": [
|
|
29
|
+
"dist"
|
|
30
|
+
],
|
|
31
|
+
"scripts": {
|
|
32
|
+
"build": "tsup",
|
|
33
|
+
"dev": "tsup --watch",
|
|
34
|
+
"type-check": "tsc --noEmit",
|
|
35
|
+
"clean": "rm -rf dist"
|
|
36
|
+
},
|
|
37
|
+
"dependencies": {
|
|
38
|
+
"@modelcontextprotocol/sdk": "latest",
|
|
39
|
+
"zod": "^4.3.6"
|
|
40
|
+
},
|
|
41
|
+
"devDependencies": {
|
|
42
|
+
"@rvue/shared": "workspace:*",
|
|
43
|
+
"tsup": "latest",
|
|
44
|
+
"typescript": "^5",
|
|
45
|
+
"@types/node": "^22"
|
|
46
|
+
}
|
|
47
|
+
}
|