@klitchevo/code-council 0.0.1 → 0.0.3
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/README.md +65 -28
- package/dist/index.d.ts +0 -6
- package/dist/index.js +627 -57
- package/package.json +4 -3
- package/dist/config.d.ts +0 -48
- package/dist/config.d.ts.map +0 -1
- package/dist/config.js +0 -61
- package/dist/constants.d.ts +0 -33
- package/dist/constants.d.ts.map +0 -1
- package/dist/constants.js +0 -36
- package/dist/errors.d.ts +0 -53
- package/dist/errors.d.ts.map +0 -1
- package/dist/errors.js +0 -92
- package/dist/index.d.ts.map +0 -1
- package/dist/logger.d.ts +0 -22
- package/dist/logger.d.ts.map +0 -1
- package/dist/logger.js +0 -62
- package/dist/prompts/backend-review.d.ts +0 -6
- package/dist/prompts/backend-review.d.ts.map +0 -1
- package/dist/prompts/backend-review.js +0 -28
- package/dist/prompts/code-review.d.ts +0 -6
- package/dist/prompts/code-review.d.ts.map +0 -1
- package/dist/prompts/code-review.js +0 -18
- package/dist/prompts/frontend-review.d.ts +0 -6
- package/dist/prompts/frontend-review.d.ts.map +0 -1
- package/dist/prompts/frontend-review.js +0 -28
- package/dist/prompts/plan-review.d.ts +0 -6
- package/dist/prompts/plan-review.d.ts.map +0 -1
- package/dist/prompts/plan-review.js +0 -29
- package/dist/review-client.d.ts +0 -75
- package/dist/review-client.d.ts.map +0 -1
- package/dist/review-client.js +0 -116
- package/dist/schemas.d.ts +0 -60
- package/dist/schemas.d.ts.map +0 -1
- package/dist/schemas.js +0 -46
- package/dist/tools/factory.d.ts +0 -20
- package/dist/tools/factory.d.ts.map +0 -1
- package/dist/tools/factory.js +0 -55
- package/dist/tools/list-config.d.ts +0 -9
- package/dist/tools/list-config.d.ts.map +0 -1
- package/dist/tools/list-config.js +0 -31
- package/dist/tools/review-backend.d.ts +0 -22
- package/dist/tools/review-backend.d.ts.map +0 -1
- package/dist/tools/review-backend.js +0 -38
- package/dist/tools/review-code.d.ts +0 -15
- package/dist/tools/review-code.d.ts.map +0 -1
- package/dist/tools/review-code.js +0 -29
- package/dist/tools/review-frontend.d.ts +0 -22
- package/dist/tools/review-frontend.d.ts.map +0 -1
- package/dist/tools/review-frontend.js +0 -38
- package/dist/tools/review-plan.d.ts +0 -22
- package/dist/tools/review-plan.d.ts.map +0 -1
- package/dist/tools/review-plan.js +0 -35
- package/dist/utils/parallel-executor.d.ts +0 -10
- package/dist/utils/parallel-executor.d.ts.map +0 -1
- package/dist/utils/parallel-executor.js +0 -21
package/dist/index.js
CHANGED
|
@@ -1,79 +1,649 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
* Multi-model AI code review server using OpenRouter API
|
|
5
|
-
*/
|
|
2
|
+
|
|
3
|
+
// src/index.ts
|
|
6
4
|
import "dotenv/config";
|
|
7
5
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
8
6
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
7
|
+
|
|
8
|
+
// src/constants.ts
|
|
9
|
+
var LLM_CONFIG = {
|
|
10
|
+
/** Default temperature for model responses (can override with TEMPERATURE env var) */
|
|
11
|
+
DEFAULT_TEMPERATURE: Number(process.env.TEMPERATURE) || 0.3,
|
|
12
|
+
/** Default max tokens for responses (can override with MAX_TOKENS env var) */
|
|
13
|
+
DEFAULT_MAX_TOKENS: Number(process.env.MAX_TOKENS) || 16384
|
|
14
|
+
};
|
|
15
|
+
var DEFAULT_MODELS = [
|
|
16
|
+
"minimax/minimax-m2.1",
|
|
17
|
+
"x-ai/grok-code-fast-1"
|
|
18
|
+
];
|
|
19
|
+
|
|
20
|
+
// src/config.ts
|
|
21
|
+
function parseModels(envVar, defaults) {
|
|
22
|
+
if (envVar === void 0 || envVar === null) {
|
|
23
|
+
return defaults;
|
|
24
|
+
}
|
|
25
|
+
if (Array.isArray(envVar)) {
|
|
26
|
+
const filtered = envVar.filter((m) => m && m.trim().length > 0);
|
|
27
|
+
return filtered.length > 0 ? filtered : defaults;
|
|
28
|
+
}
|
|
29
|
+
throw new Error(
|
|
30
|
+
`Model configuration must be an array of strings, got: ${typeof envVar}. Example: ["anthropic/claude-sonnet-4.5", "openai/gpt-4o"]`
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
var CODE_REVIEW_MODELS = parseModels(
|
|
34
|
+
process.env.CODE_REVIEW_MODELS,
|
|
35
|
+
DEFAULT_MODELS
|
|
36
|
+
);
|
|
37
|
+
var FRONTEND_REVIEW_MODELS = parseModels(
|
|
38
|
+
process.env.FRONTEND_REVIEW_MODELS,
|
|
39
|
+
DEFAULT_MODELS
|
|
40
|
+
);
|
|
41
|
+
var BACKEND_REVIEW_MODELS = parseModels(
|
|
42
|
+
process.env.BACKEND_REVIEW_MODELS,
|
|
43
|
+
DEFAULT_MODELS
|
|
44
|
+
);
|
|
45
|
+
var PLAN_REVIEW_MODELS = parseModels(
|
|
46
|
+
process.env.PLAN_REVIEW_MODELS,
|
|
47
|
+
DEFAULT_MODELS
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
// src/logger.ts
|
|
51
|
+
var Logger = class {
|
|
52
|
+
isDevelopment = process.env.NODE_ENV === "development";
|
|
53
|
+
debugEnabled = process.env.DEBUG === "true";
|
|
54
|
+
log(level, message, context) {
|
|
55
|
+
const logEntry = {
|
|
56
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
57
|
+
level,
|
|
58
|
+
message,
|
|
59
|
+
...context
|
|
60
|
+
};
|
|
61
|
+
if (this.isDevelopment) {
|
|
62
|
+
const emoji = {
|
|
63
|
+
debug: "\u{1F50D}",
|
|
64
|
+
info: "\u2139\uFE0F",
|
|
65
|
+
warn: "\u26A0\uFE0F",
|
|
66
|
+
error: "\u274C"
|
|
67
|
+
}[level];
|
|
68
|
+
console.error(
|
|
69
|
+
`${emoji} [${level.toUpperCase()}] ${message}`,
|
|
70
|
+
context ? JSON.stringify(context, null, 2) : ""
|
|
71
|
+
);
|
|
72
|
+
} else {
|
|
73
|
+
console.error(JSON.stringify(logEntry));
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
debug(message, context) {
|
|
77
|
+
if (this.debugEnabled) {
|
|
78
|
+
this.log("debug", message, context);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
info(message, context) {
|
|
82
|
+
this.log("info", message, context);
|
|
83
|
+
}
|
|
84
|
+
warn(message, context) {
|
|
85
|
+
this.log("warn", message, context);
|
|
86
|
+
}
|
|
87
|
+
error(message, error, context) {
|
|
88
|
+
const errorContext = {
|
|
89
|
+
...context
|
|
90
|
+
};
|
|
91
|
+
if (error instanceof Error) {
|
|
92
|
+
errorContext.error = {
|
|
93
|
+
name: error.name,
|
|
94
|
+
message: error.message,
|
|
95
|
+
stack: error.stack
|
|
96
|
+
};
|
|
97
|
+
} else if (error) {
|
|
98
|
+
errorContext.error = error;
|
|
99
|
+
}
|
|
100
|
+
this.log("error", message, errorContext);
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
var logger = new Logger();
|
|
104
|
+
|
|
105
|
+
// src/review-client.ts
|
|
106
|
+
import { OpenRouter } from "@openrouter/sdk";
|
|
107
|
+
|
|
108
|
+
// src/errors.ts
|
|
109
|
+
var AppError = class extends Error {
|
|
110
|
+
constructor(message, code, userMessage) {
|
|
111
|
+
super(message);
|
|
112
|
+
this.code = code;
|
|
113
|
+
this.userMessage = userMessage;
|
|
114
|
+
this.name = this.constructor.name;
|
|
115
|
+
Error.captureStackTrace(this, this.constructor);
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
var OpenRouterError = class extends AppError {
|
|
119
|
+
constructor(message, statusCode, retryable = false) {
|
|
120
|
+
super(
|
|
121
|
+
message,
|
|
122
|
+
"OPENROUTER_ERROR",
|
|
123
|
+
retryable ? "The AI service is temporarily unavailable. Please try again in a moment." : "Unable to complete the review. Please check your API key and try again."
|
|
124
|
+
);
|
|
125
|
+
this.statusCode = statusCode;
|
|
126
|
+
this.retryable = retryable;
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
function formatErrorMessage(error) {
|
|
130
|
+
if (error instanceof AppError) {
|
|
131
|
+
return error.userMessage || error.message;
|
|
132
|
+
}
|
|
133
|
+
if (error instanceof Error) {
|
|
134
|
+
const sanitized = error.message.replace(/sk-or-v1-[a-zA-Z0-9]+/g, "[REDACTED]").replace(/Bearer [a-zA-Z0-9-_]+/g, "Bearer [REDACTED]");
|
|
135
|
+
if (sanitized.includes("401") || sanitized.includes("Unauthorized")) {
|
|
136
|
+
return "API authentication failed. Please check your OPENROUTER_API_KEY environment variable.";
|
|
137
|
+
}
|
|
138
|
+
if (sanitized.includes("429") || sanitized.includes("rate limit")) {
|
|
139
|
+
return "Rate limit exceeded. Please wait a moment and try again.";
|
|
140
|
+
}
|
|
141
|
+
if (sanitized.includes("timeout") || sanitized.includes("ETIMEDOUT")) {
|
|
142
|
+
return "Request timed out. The AI service may be slow. Please try again.";
|
|
143
|
+
}
|
|
144
|
+
return sanitized;
|
|
145
|
+
}
|
|
146
|
+
return "An unexpected error occurred. Please try again.";
|
|
147
|
+
}
|
|
148
|
+
function formatError(error) {
|
|
149
|
+
return {
|
|
150
|
+
content: [
|
|
151
|
+
{
|
|
152
|
+
type: "text",
|
|
153
|
+
text: `Error: ${formatErrorMessage(error)}`
|
|
154
|
+
}
|
|
155
|
+
],
|
|
156
|
+
isError: true
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// src/prompts/backend-review.ts
|
|
161
|
+
var SYSTEM_PROMPT = `You are an expert backend developer and security specialist. Review backend code for security, performance, and architecture.`;
|
|
162
|
+
function buildUserMessage(code, reviewType = "full", language, context) {
|
|
163
|
+
const focusArea = getFocusArea(reviewType);
|
|
164
|
+
const languageContext = language ? `Language/Framework: ${language}
|
|
165
|
+
` : "";
|
|
166
|
+
const additionalContext = context ? `${context}
|
|
167
|
+
` : "";
|
|
168
|
+
return `${languageContext}${additionalContext}${focusArea}
|
|
169
|
+
|
|
170
|
+
Code to review:
|
|
171
|
+
\`\`\`
|
|
172
|
+
${code}
|
|
173
|
+
\`\`\``;
|
|
174
|
+
}
|
|
175
|
+
function getFocusArea(reviewType) {
|
|
176
|
+
switch (reviewType) {
|
|
177
|
+
case "security":
|
|
178
|
+
return "Focus specifically on security (authentication, authorization, input validation, SQL injection, XSS, CSRF, secrets management).";
|
|
179
|
+
case "performance":
|
|
180
|
+
return "Focus specifically on backend performance (database queries, caching, async operations, resource usage, scalability).";
|
|
181
|
+
case "architecture":
|
|
182
|
+
return "Focus specifically on architecture (design patterns, separation of concerns, modularity, maintainability, scalability).";
|
|
183
|
+
default:
|
|
184
|
+
return "Provide a comprehensive backend review covering security, performance, and architecture.";
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// src/prompts/code-review.ts
|
|
189
|
+
var SYSTEM_PROMPT2 = `You are an expert code reviewer. Analyze the code for:
|
|
190
|
+
- Code quality and best practices
|
|
191
|
+
- Potential bugs and edge cases
|
|
192
|
+
- Performance issues
|
|
193
|
+
- Security vulnerabilities
|
|
194
|
+
- Maintainability concerns
|
|
195
|
+
|
|
196
|
+
Provide specific, actionable feedback.`;
|
|
197
|
+
function buildUserMessage2(code, context) {
|
|
198
|
+
if (context) {
|
|
199
|
+
return `${context}
|
|
200
|
+
|
|
201
|
+
Code to review:
|
|
202
|
+
\`\`\`
|
|
203
|
+
${code}
|
|
204
|
+
\`\`\``;
|
|
205
|
+
}
|
|
206
|
+
return `Code to review:
|
|
207
|
+
\`\`\`
|
|
208
|
+
${code}
|
|
209
|
+
\`\`\``;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// src/prompts/frontend-review.ts
|
|
213
|
+
var SYSTEM_PROMPT3 = `You are an expert frontend developer and UX specialist. Review frontend code for best practices.`;
|
|
214
|
+
function buildUserMessage3(code, reviewType = "full", framework, context) {
|
|
215
|
+
const focusArea = getFocusArea2(reviewType);
|
|
216
|
+
const frameworkContext = framework ? `Framework: ${framework}
|
|
217
|
+
` : "";
|
|
218
|
+
const additionalContext = context ? `${context}
|
|
219
|
+
` : "";
|
|
220
|
+
return `${frameworkContext}${additionalContext}${focusArea}
|
|
221
|
+
|
|
222
|
+
Code to review:
|
|
223
|
+
\`\`\`
|
|
224
|
+
${code}
|
|
225
|
+
\`\`\``;
|
|
226
|
+
}
|
|
227
|
+
function getFocusArea2(reviewType) {
|
|
228
|
+
switch (reviewType) {
|
|
229
|
+
case "accessibility":
|
|
230
|
+
return "Focus specifically on accessibility (WCAG compliance, ARIA labels, keyboard navigation, screen reader support).";
|
|
231
|
+
case "performance":
|
|
232
|
+
return "Focus specifically on frontend performance (bundle size, render optimization, lazy loading, Core Web Vitals).";
|
|
233
|
+
case "ux":
|
|
234
|
+
return "Focus specifically on user experience (intuitive design, error handling, loading states, responsive design).";
|
|
235
|
+
default:
|
|
236
|
+
return "Provide a comprehensive frontend review covering accessibility, performance, and user experience.";
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// src/prompts/plan-review.ts
|
|
241
|
+
var SYSTEM_PROMPT4 = `You are an expert software architect and project planner. Review implementation plans before code is written to catch issues early.`;
|
|
242
|
+
function buildUserMessage4(plan, reviewType = "full", context) {
|
|
243
|
+
const focusArea = getFocusArea3(reviewType);
|
|
244
|
+
const additionalContext = context ? `${context}
|
|
245
|
+
` : "";
|
|
246
|
+
return `${additionalContext}${focusArea}
|
|
247
|
+
|
|
248
|
+
Implementation plan to review:
|
|
249
|
+
\`\`\`
|
|
250
|
+
${plan}
|
|
251
|
+
\`\`\``;
|
|
252
|
+
}
|
|
253
|
+
function getFocusArea3(reviewType) {
|
|
254
|
+
switch (reviewType) {
|
|
255
|
+
case "feasibility":
|
|
256
|
+
return "Focus specifically on feasibility (technical complexity, resource requirements, potential blockers, dependencies).";
|
|
257
|
+
case "completeness":
|
|
258
|
+
return "Focus specifically on completeness (missing requirements, edge cases, error handling, testing strategy).";
|
|
259
|
+
case "risks":
|
|
260
|
+
return "Focus specifically on risks (technical risks, security concerns, scalability issues, maintenance burden).";
|
|
261
|
+
case "timeline":
|
|
262
|
+
return "Focus specifically on timeline (realistic estimates, task breakdown, critical path, potential delays).";
|
|
263
|
+
default:
|
|
264
|
+
return "Provide a comprehensive plan review covering feasibility, completeness, risks, and timeline.";
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// src/utils/parallel-executor.ts
|
|
269
|
+
async function executeInParallel(models, reviewFn) {
|
|
270
|
+
const promises = models.map(async (model) => {
|
|
271
|
+
try {
|
|
272
|
+
const review = await reviewFn(model);
|
|
273
|
+
return { model, review };
|
|
274
|
+
} catch (error) {
|
|
275
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
276
|
+
return { model, review: "", error: errorMessage };
|
|
277
|
+
}
|
|
278
|
+
});
|
|
279
|
+
return Promise.all(promises);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// src/review-client.ts
|
|
283
|
+
var ReviewClient = class {
|
|
284
|
+
client;
|
|
285
|
+
constructor(apiKey) {
|
|
286
|
+
this.client = new OpenRouter({
|
|
287
|
+
apiKey
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* Send a chat request to OpenRouter API
|
|
292
|
+
* @param model - Model identifier (e.g., "anthropic/claude-3.5-sonnet")
|
|
293
|
+
* @param systemPrompt - System prompt for the model
|
|
294
|
+
* @param userMessage - User message/question
|
|
295
|
+
* @returns The model's response content
|
|
296
|
+
* @throws {OpenRouterError} If the API call fails
|
|
297
|
+
*/
|
|
298
|
+
async chat(model, systemPrompt, userMessage) {
|
|
299
|
+
try {
|
|
300
|
+
logger.debug("Sending chat request", {
|
|
301
|
+
model,
|
|
302
|
+
messageLength: userMessage.length
|
|
303
|
+
});
|
|
304
|
+
const response = await this.client.chat.send({
|
|
305
|
+
model,
|
|
306
|
+
messages: [
|
|
307
|
+
{ role: "system", content: systemPrompt },
|
|
308
|
+
{ role: "user", content: userMessage }
|
|
309
|
+
],
|
|
310
|
+
temperature: LLM_CONFIG.DEFAULT_TEMPERATURE,
|
|
311
|
+
maxTokens: LLM_CONFIG.DEFAULT_MAX_TOKENS
|
|
312
|
+
});
|
|
313
|
+
const content = response.choices?.[0]?.message?.content;
|
|
314
|
+
if (typeof content === "string") {
|
|
315
|
+
logger.debug("Received response", { model, length: content.length });
|
|
316
|
+
return content;
|
|
317
|
+
}
|
|
318
|
+
if (Array.isArray(content)) {
|
|
319
|
+
const text = content.filter((item) => item.type === "text").map((item) => item.text).join("\n");
|
|
320
|
+
logger.debug("Received array response", { model, length: text.length });
|
|
321
|
+
return text;
|
|
322
|
+
}
|
|
323
|
+
throw new OpenRouterError("No response content from model", 500);
|
|
324
|
+
} catch (error) {
|
|
325
|
+
if (error instanceof OpenRouterError) {
|
|
326
|
+
throw error;
|
|
327
|
+
}
|
|
328
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
329
|
+
logger.error("Chat request failed", error, { model });
|
|
330
|
+
const isRetryable = message.includes("429") || message.includes("rate limit");
|
|
331
|
+
throw new OpenRouterError(message, void 0, isRetryable);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
/**
|
|
335
|
+
* Review code for quality, bugs, performance, and security
|
|
336
|
+
* @param code - Code to review
|
|
337
|
+
* @param models - Array of model identifiers to use
|
|
338
|
+
* @param context - Optional context (language, description, etc.)
|
|
339
|
+
* @returns Array of review results from each model
|
|
340
|
+
*/
|
|
341
|
+
async reviewCode(code, models, context) {
|
|
342
|
+
const userMessage = buildUserMessage2(code, context);
|
|
343
|
+
return executeInParallel(
|
|
344
|
+
models,
|
|
345
|
+
(model) => this.chat(model, SYSTEM_PROMPT2, userMessage)
|
|
346
|
+
);
|
|
347
|
+
}
|
|
348
|
+
/**
|
|
349
|
+
* Review frontend code for accessibility, performance, and UX
|
|
350
|
+
*/
|
|
351
|
+
async reviewFrontend(code, models, options) {
|
|
352
|
+
const userMessage = buildUserMessage3(
|
|
353
|
+
code,
|
|
354
|
+
options?.reviewType || "full",
|
|
355
|
+
options?.framework,
|
|
356
|
+
options?.context
|
|
357
|
+
);
|
|
358
|
+
return executeInParallel(
|
|
359
|
+
models,
|
|
360
|
+
(model) => this.chat(model, SYSTEM_PROMPT3, userMessage)
|
|
361
|
+
);
|
|
362
|
+
}
|
|
363
|
+
/**
|
|
364
|
+
* Review backend code for security, performance, and architecture
|
|
365
|
+
*/
|
|
366
|
+
async reviewBackend(code, models, options) {
|
|
367
|
+
const userMessage = buildUserMessage(
|
|
368
|
+
code,
|
|
369
|
+
options?.reviewType || "full",
|
|
370
|
+
options?.language,
|
|
371
|
+
options?.context
|
|
372
|
+
);
|
|
373
|
+
return executeInParallel(
|
|
374
|
+
models,
|
|
375
|
+
(model) => this.chat(model, SYSTEM_PROMPT, userMessage)
|
|
376
|
+
);
|
|
377
|
+
}
|
|
378
|
+
/**
|
|
379
|
+
* Review implementation plans before code is written
|
|
380
|
+
*/
|
|
381
|
+
async reviewPlan(plan, models, options) {
|
|
382
|
+
const userMessage = buildUserMessage4(
|
|
383
|
+
plan,
|
|
384
|
+
options?.reviewType || "full",
|
|
385
|
+
options?.context
|
|
386
|
+
);
|
|
387
|
+
return executeInParallel(
|
|
388
|
+
models,
|
|
389
|
+
(model) => this.chat(model, SYSTEM_PROMPT4, userMessage)
|
|
390
|
+
);
|
|
391
|
+
}
|
|
392
|
+
};
|
|
393
|
+
|
|
394
|
+
// src/tools/factory.ts
|
|
395
|
+
function formatResults(results) {
|
|
396
|
+
return results.map((r) => {
|
|
397
|
+
if (r.error) {
|
|
398
|
+
return `## Review from \`${r.model}\`
|
|
399
|
+
|
|
400
|
+
**Error:** ${r.error}`;
|
|
401
|
+
}
|
|
402
|
+
return `## Review from \`${r.model}\`
|
|
403
|
+
|
|
404
|
+
${r.review}`;
|
|
405
|
+
}).join("\n\n---\n\n");
|
|
406
|
+
}
|
|
407
|
+
function createReviewTool(server2, config) {
|
|
408
|
+
server2.registerTool(
|
|
409
|
+
config.name,
|
|
410
|
+
{
|
|
411
|
+
description: config.description,
|
|
412
|
+
inputSchema: config.inputSchema
|
|
413
|
+
},
|
|
414
|
+
async (input) => {
|
|
415
|
+
try {
|
|
416
|
+
logger.debug(`Starting ${config.name}`, {
|
|
417
|
+
inputKeys: Object.keys(input)
|
|
418
|
+
});
|
|
419
|
+
const { results, models, reviewType } = await config.handler(input);
|
|
420
|
+
logger.info(`Completed ${config.name}`, {
|
|
421
|
+
modelCount: models.length,
|
|
422
|
+
successCount: results.filter((r) => !r.error).length,
|
|
423
|
+
errorCount: results.filter((r) => r.error).length
|
|
424
|
+
});
|
|
425
|
+
const title = reviewType ? `# ${config.name.replace("review_", "").replace("_", " ")} Review - ${reviewType} (${models.length} models)` : `# ${config.name.replace("review_", "").replace("_", " ")} Review Results (${models.length} models)`;
|
|
426
|
+
return {
|
|
427
|
+
content: [
|
|
428
|
+
{
|
|
429
|
+
type: "text",
|
|
430
|
+
text: `${title}
|
|
431
|
+
|
|
432
|
+
${formatResults(results)}`
|
|
433
|
+
}
|
|
434
|
+
]
|
|
435
|
+
};
|
|
436
|
+
} catch (error) {
|
|
437
|
+
logger.error(
|
|
438
|
+
`Error in ${config.name}`,
|
|
439
|
+
error instanceof Error ? error : new Error(String(error))
|
|
440
|
+
);
|
|
441
|
+
return formatError(error);
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
);
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
// src/tools/list-config.ts
|
|
448
|
+
async function handleListConfig() {
|
|
449
|
+
const text = `## Current Configuration
|
|
450
|
+
|
|
451
|
+
**Code Review Models:**
|
|
452
|
+
${CODE_REVIEW_MODELS.map((m) => `- \`${m}\``).join("\n")}
|
|
453
|
+
|
|
454
|
+
**Frontend Review Models:**
|
|
455
|
+
${FRONTEND_REVIEW_MODELS.map((m) => `- \`${m}\``).join("\n")}
|
|
456
|
+
|
|
457
|
+
**Backend Review Models:**
|
|
458
|
+
${BACKEND_REVIEW_MODELS.map((m) => `- \`${m}\``).join("\n")}
|
|
459
|
+
|
|
460
|
+
**Plan Review Models:**
|
|
461
|
+
${PLAN_REVIEW_MODELS.map((m) => `- \`${m}\``).join("\n")}
|
|
462
|
+
|
|
463
|
+
To customize models, set environment variables in your MCP config:
|
|
464
|
+
- CODE_REVIEW_MODELS
|
|
465
|
+
- FRONTEND_REVIEW_MODELS
|
|
466
|
+
- BACKEND_REVIEW_MODELS
|
|
467
|
+
- PLAN_REVIEW_MODELS`;
|
|
468
|
+
return {
|
|
469
|
+
results: [],
|
|
470
|
+
models: [],
|
|
471
|
+
text
|
|
472
|
+
};
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
// src/tools/review-backend.ts
|
|
476
|
+
import { z } from "zod";
|
|
477
|
+
var backendReviewSchema = {
|
|
478
|
+
code: z.string().describe("The backend code to review"),
|
|
479
|
+
language: z.string().optional().describe("Programming language/framework (e.g., node, python, go, rust)"),
|
|
480
|
+
review_type: z.enum(["security", "performance", "architecture", "full"]).optional().describe("Type of review to perform (default: full)"),
|
|
481
|
+
context: z.string().optional().describe("Additional context")
|
|
482
|
+
};
|
|
483
|
+
async function handleBackendReview(client2, input) {
|
|
484
|
+
const { code, language, review_type, context } = input;
|
|
485
|
+
logger.info("Running backend review", {
|
|
486
|
+
modelCount: BACKEND_REVIEW_MODELS.length,
|
|
487
|
+
models: BACKEND_REVIEW_MODELS,
|
|
488
|
+
language,
|
|
489
|
+
reviewType: review_type || "full"
|
|
490
|
+
});
|
|
491
|
+
const results = await client2.reviewBackend(code, BACKEND_REVIEW_MODELS, {
|
|
492
|
+
language,
|
|
493
|
+
reviewType: review_type,
|
|
494
|
+
context
|
|
495
|
+
});
|
|
496
|
+
return {
|
|
497
|
+
results,
|
|
498
|
+
models: BACKEND_REVIEW_MODELS,
|
|
499
|
+
reviewType: review_type || "full"
|
|
500
|
+
};
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
// src/tools/review-code.ts
|
|
504
|
+
import { z as z2 } from "zod";
|
|
505
|
+
var codeReviewSchema = {
|
|
506
|
+
code: z2.string().describe("The code to review"),
|
|
507
|
+
language: z2.string().optional().describe("Programming language of the code"),
|
|
508
|
+
context: z2.string().optional().describe("Additional context about the code")
|
|
509
|
+
};
|
|
510
|
+
async function handleCodeReview(client2, input) {
|
|
511
|
+
const { code, language, context } = input;
|
|
512
|
+
const fullContext = language ? `Language: ${language}${context ? `
|
|
513
|
+
${context}` : ""}` : context;
|
|
514
|
+
logger.info("Running code review", {
|
|
515
|
+
modelCount: CODE_REVIEW_MODELS.length,
|
|
516
|
+
models: CODE_REVIEW_MODELS,
|
|
517
|
+
hasLanguage: !!language,
|
|
518
|
+
hasContext: !!context
|
|
519
|
+
});
|
|
520
|
+
const results = await client2.reviewCode(
|
|
521
|
+
code,
|
|
522
|
+
CODE_REVIEW_MODELS,
|
|
523
|
+
fullContext
|
|
524
|
+
);
|
|
525
|
+
return {
|
|
526
|
+
results,
|
|
527
|
+
models: CODE_REVIEW_MODELS
|
|
528
|
+
};
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
// src/tools/review-frontend.ts
|
|
532
|
+
import { z as z3 } from "zod";
|
|
533
|
+
var frontendReviewSchema = {
|
|
534
|
+
code: z3.string().describe("The frontend code to review"),
|
|
535
|
+
framework: z3.string().optional().describe("Frontend framework (e.g., react, vue, svelte)"),
|
|
536
|
+
review_type: z3.enum(["accessibility", "performance", "ux", "full"]).optional().describe("Type of review to perform (default: full)"),
|
|
537
|
+
context: z3.string().optional().describe("Additional context")
|
|
538
|
+
};
|
|
539
|
+
async function handleFrontendReview(client2, input) {
|
|
540
|
+
const { code, framework, review_type, context } = input;
|
|
541
|
+
logger.info("Running frontend review", {
|
|
542
|
+
modelCount: FRONTEND_REVIEW_MODELS.length,
|
|
543
|
+
models: FRONTEND_REVIEW_MODELS,
|
|
544
|
+
framework,
|
|
545
|
+
reviewType: review_type || "full"
|
|
546
|
+
});
|
|
547
|
+
const results = await client2.reviewFrontend(code, FRONTEND_REVIEW_MODELS, {
|
|
548
|
+
framework,
|
|
549
|
+
reviewType: review_type,
|
|
550
|
+
context
|
|
551
|
+
});
|
|
552
|
+
return {
|
|
553
|
+
results,
|
|
554
|
+
models: FRONTEND_REVIEW_MODELS,
|
|
555
|
+
reviewType: review_type || "full"
|
|
556
|
+
};
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
// src/tools/review-plan.ts
|
|
560
|
+
import { z as z4 } from "zod";
|
|
561
|
+
var planReviewSchema = {
|
|
562
|
+
plan: z4.string().describe("The implementation plan to review"),
|
|
563
|
+
review_type: z4.enum(["feasibility", "completeness", "risks", "timeline", "full"]).optional().describe("Type of review to perform (default: full)"),
|
|
564
|
+
context: z4.string().optional().describe("Additional context about the project or constraints")
|
|
565
|
+
};
|
|
566
|
+
async function handlePlanReview(client2, input) {
|
|
567
|
+
const { plan, review_type, context } = input;
|
|
568
|
+
logger.info("Running plan review", {
|
|
569
|
+
modelCount: PLAN_REVIEW_MODELS.length,
|
|
570
|
+
models: PLAN_REVIEW_MODELS,
|
|
571
|
+
reviewType: review_type || "full"
|
|
572
|
+
});
|
|
573
|
+
const results = await client2.reviewPlan(plan, PLAN_REVIEW_MODELS, {
|
|
574
|
+
reviewType: review_type,
|
|
575
|
+
context
|
|
576
|
+
});
|
|
577
|
+
return {
|
|
578
|
+
results,
|
|
579
|
+
models: PLAN_REVIEW_MODELS,
|
|
580
|
+
reviewType: review_type || "full"
|
|
581
|
+
};
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
// src/index.ts
|
|
585
|
+
var OPENROUTER_API_KEY = process.env.OPENROUTER_API_KEY;
|
|
20
586
|
if (!OPENROUTER_API_KEY) {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
587
|
+
console.error("Error: OPENROUTER_API_KEY environment variable is required");
|
|
588
|
+
console.error(
|
|
589
|
+
"For MCP clients, add it to the 'env' section of your server config."
|
|
590
|
+
);
|
|
591
|
+
console.error(
|
|
592
|
+
"For local development, create a .env file with: OPENROUTER_API_KEY=your-key"
|
|
593
|
+
);
|
|
594
|
+
process.exit(1);
|
|
595
|
+
}
|
|
596
|
+
var client = new ReviewClient(OPENROUTER_API_KEY);
|
|
597
|
+
var server = new McpServer({
|
|
598
|
+
name: "code-council",
|
|
599
|
+
version: "1.0.0"
|
|
31
600
|
});
|
|
32
|
-
// Register review tools
|
|
33
601
|
createReviewTool(server, {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
602
|
+
name: "review_code",
|
|
603
|
+
description: "Review code for quality, bugs, performance, and security issues using multiple AI models in parallel",
|
|
604
|
+
inputSchema: codeReviewSchema,
|
|
605
|
+
handler: (input) => handleCodeReview(client, input)
|
|
38
606
|
});
|
|
39
607
|
createReviewTool(server, {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
608
|
+
name: "review_frontend",
|
|
609
|
+
description: "Review frontend code for accessibility, performance, UX, and best practices using multiple AI models in parallel",
|
|
610
|
+
inputSchema: frontendReviewSchema,
|
|
611
|
+
handler: (input) => handleFrontendReview(client, input)
|
|
44
612
|
});
|
|
45
613
|
createReviewTool(server, {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
614
|
+
name: "review_backend",
|
|
615
|
+
description: "Review backend code for security, performance, architecture, and best practices using multiple AI models in parallel",
|
|
616
|
+
inputSchema: backendReviewSchema,
|
|
617
|
+
handler: (input) => handleBackendReview(client, input)
|
|
50
618
|
});
|
|
51
619
|
createReviewTool(server, {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
620
|
+
name: "review_plan",
|
|
621
|
+
description: "Review implementation plans BEFORE coding to catch issues early using multiple AI models in parallel",
|
|
622
|
+
inputSchema: planReviewSchema,
|
|
623
|
+
handler: (input) => handlePlanReview(client, input)
|
|
56
624
|
});
|
|
57
|
-
|
|
58
|
-
|
|
625
|
+
server.registerTool(
|
|
626
|
+
"list_review_config",
|
|
627
|
+
{ description: "Show current model configuration" },
|
|
628
|
+
async () => {
|
|
59
629
|
const { text } = await handleListConfig();
|
|
60
630
|
return {
|
|
61
|
-
|
|
631
|
+
content: [{ type: "text", text }]
|
|
62
632
|
};
|
|
63
|
-
}
|
|
64
|
-
|
|
633
|
+
}
|
|
634
|
+
);
|
|
65
635
|
async function main() {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
636
|
+
const transport = new StdioServerTransport();
|
|
637
|
+
await server.connect(transport);
|
|
638
|
+
logger.info("Code Council MCP server started", {
|
|
639
|
+
codeReviewModels: CODE_REVIEW_MODELS,
|
|
640
|
+
frontendReviewModels: FRONTEND_REVIEW_MODELS,
|
|
641
|
+
backendReviewModels: BACKEND_REVIEW_MODELS,
|
|
642
|
+
planReviewModels: PLAN_REVIEW_MODELS
|
|
643
|
+
});
|
|
74
644
|
}
|
|
75
645
|
main().catch((error) => {
|
|
76
|
-
|
|
77
|
-
|
|
646
|
+
logger.error("Fatal error during server startup", error);
|
|
647
|
+
process.exit(1);
|
|
78
648
|
});
|
|
79
649
|
//# sourceMappingURL=index.js.map
|