@j0hanz/code-review-analyst-mcp 1.1.0 → 1.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.
Files changed (50) hide show
  1. package/README.md +203 -193
  2. package/dist/index.js +18 -15
  3. package/dist/instructions.md +83 -58
  4. package/dist/lib/context-budget.d.ts +8 -0
  5. package/dist/lib/context-budget.js +30 -0
  6. package/dist/lib/diff-budget.d.ts +3 -1
  7. package/dist/lib/diff-budget.js +16 -19
  8. package/dist/lib/diff-parser.d.ts +34 -0
  9. package/dist/lib/diff-parser.js +114 -0
  10. package/dist/lib/env-config.d.ts +5 -0
  11. package/dist/lib/env-config.js +24 -0
  12. package/dist/lib/errors.d.ts +1 -0
  13. package/dist/lib/errors.js +9 -6
  14. package/dist/lib/gemini-schema.d.ts +3 -1
  15. package/dist/lib/gemini-schema.js +18 -17
  16. package/dist/lib/gemini.d.ts +1 -0
  17. package/dist/lib/gemini.js +216 -111
  18. package/dist/lib/model-config.d.ts +17 -0
  19. package/dist/lib/model-config.js +19 -0
  20. package/dist/lib/tool-factory.d.ts +20 -8
  21. package/dist/lib/tool-factory.js +264 -67
  22. package/dist/lib/tool-response.d.ts +9 -2
  23. package/dist/lib/tool-response.js +29 -14
  24. package/dist/lib/types.d.ts +8 -3
  25. package/dist/prompts/index.js +35 -15
  26. package/dist/resources/index.js +10 -9
  27. package/dist/schemas/inputs.d.ts +27 -15
  28. package/dist/schemas/inputs.js +59 -21
  29. package/dist/schemas/outputs.d.ts +130 -7
  30. package/dist/schemas/outputs.js +170 -40
  31. package/dist/server.d.ts +5 -1
  32. package/dist/server.js +32 -24
  33. package/dist/tools/analyze-pr-impact.d.ts +2 -0
  34. package/dist/tools/analyze-pr-impact.js +46 -0
  35. package/dist/tools/generate-review-summary.d.ts +2 -0
  36. package/dist/tools/generate-review-summary.js +67 -0
  37. package/dist/tools/generate-test-plan.d.ts +2 -0
  38. package/dist/tools/generate-test-plan.js +56 -0
  39. package/dist/tools/index.js +10 -6
  40. package/dist/tools/inspect-code-quality.d.ts +4 -0
  41. package/dist/tools/inspect-code-quality.js +107 -0
  42. package/dist/tools/suggest-search-replace.d.ts +2 -0
  43. package/dist/tools/suggest-search-replace.js +46 -0
  44. package/package.json +3 -2
  45. package/dist/tools/review-diff.d.ts +0 -2
  46. package/dist/tools/review-diff.js +0 -42
  47. package/dist/tools/risk-score.d.ts +0 -2
  48. package/dist/tools/risk-score.js +0 -34
  49. package/dist/tools/suggest-patch.d.ts +0 -2
  50. package/dist/tools/suggest-patch.js +0 -35
@@ -1,9 +1,17 @@
1
1
  import { z } from 'zod';
2
2
  import { DefaultOutputSchema } from '../schemas/outputs.js';
3
- import { getErrorMessage } from './errors.js';
3
+ import { getErrorMessage, RETRYABLE_UPSTREAM_ERROR_PATTERN } from './errors.js';
4
4
  import { stripJsonSchemaConstraints } from './gemini-schema.js';
5
- import { generateStructuredJson } from './gemini.js';
5
+ import { generateStructuredJson, getCurrentRequestId } from './gemini.js';
6
6
  import { createErrorToolResponse, createToolResponse, } from './tool-response.js';
7
+ const DEFAULT_TASK_TTL_MS = 30 * 60 * 1_000;
8
+ const TASK_PROGRESS_TOTAL = 4;
9
+ const INPUT_VALIDATION_FAILED = 'Input validation failed';
10
+ const DEFAULT_PROGRESS_CONTEXT = 'request';
11
+ const CANCELLED_ERROR_PATTERN = /cancelled|canceled/i;
12
+ const TIMEOUT_ERROR_PATTERN = /timed out|timeout/i;
13
+ const BUDGET_ERROR_PATTERN = /exceeds limit|max allowed size|input too large/i;
14
+ const BUSY_ERROR_PATTERN = /too many concurrent/i;
7
15
  function createGeminiResponseSchema(config) {
8
16
  const sourceSchema = config.geminiSchema ?? config.resultSchema;
9
17
  return stripJsonSchemaConstraints(z.toJSONSchema(sourceSchema));
@@ -11,6 +19,173 @@ function createGeminiResponseSchema(config) {
11
19
  function parseToolInput(input, fullInputSchema) {
12
20
  return fullInputSchema.parse(input);
13
21
  }
22
+ function createGenerationRequest(config, promptParts, responseSchema, onLog, signal) {
23
+ const request = {
24
+ systemInstruction: promptParts.systemInstruction,
25
+ prompt: promptParts.prompt,
26
+ responseSchema,
27
+ onLog,
28
+ };
29
+ if (config.model !== undefined) {
30
+ request.model = config.model;
31
+ }
32
+ if (config.thinkingBudget !== undefined) {
33
+ request.thinkingBudget = config.thinkingBudget;
34
+ }
35
+ if (config.timeoutMs !== undefined) {
36
+ request.timeoutMs = config.timeoutMs;
37
+ }
38
+ if (signal !== undefined) {
39
+ request.signal = signal;
40
+ }
41
+ return request;
42
+ }
43
+ function classifyErrorMeta(error, message) {
44
+ if (error instanceof z.ZodError || /validation/i.test(message)) {
45
+ return {
46
+ kind: 'validation',
47
+ retryable: false,
48
+ };
49
+ }
50
+ if (CANCELLED_ERROR_PATTERN.test(message)) {
51
+ return {
52
+ kind: 'cancelled',
53
+ retryable: false,
54
+ };
55
+ }
56
+ if (TIMEOUT_ERROR_PATTERN.test(message)) {
57
+ return {
58
+ kind: 'timeout',
59
+ retryable: true,
60
+ };
61
+ }
62
+ if (BUDGET_ERROR_PATTERN.test(message)) {
63
+ return {
64
+ kind: 'budget',
65
+ retryable: false,
66
+ };
67
+ }
68
+ if (RETRYABLE_UPSTREAM_ERROR_PATTERN.test(message)) {
69
+ return {
70
+ kind: 'upstream',
71
+ retryable: true,
72
+ };
73
+ }
74
+ if (BUSY_ERROR_PATTERN.test(message)) {
75
+ return {
76
+ kind: 'upstream',
77
+ retryable: true,
78
+ };
79
+ }
80
+ return {
81
+ kind: 'internal',
82
+ retryable: false,
83
+ };
84
+ }
85
+ async function sendTaskProgress(extra, payload) {
86
+ const progressToken = extra._meta?.progressToken;
87
+ if (typeof progressToken !== 'string' && typeof progressToken !== 'number') {
88
+ return;
89
+ }
90
+ try {
91
+ const params = {
92
+ progressToken,
93
+ progress: payload.current,
94
+ };
95
+ if (payload.total !== undefined) {
96
+ params.total = payload.total;
97
+ }
98
+ if (payload.message !== undefined) {
99
+ params.message = payload.message;
100
+ }
101
+ await extra.sendNotification({
102
+ method: 'notifications/progress',
103
+ params,
104
+ });
105
+ }
106
+ catch {
107
+ // Progress is best-effort; never fail the tool call.
108
+ }
109
+ }
110
+ function createProgressReporter(extra) {
111
+ let lastCurrent = 0;
112
+ let didSendTerminal = false;
113
+ return async (payload) => {
114
+ if (didSendTerminal) {
115
+ return;
116
+ }
117
+ const current = Math.max(payload.current, lastCurrent);
118
+ const total = payload.total !== undefined
119
+ ? Math.max(payload.total, current)
120
+ : undefined;
121
+ await sendTaskProgress(extra, {
122
+ current,
123
+ ...(total !== undefined ? { total } : {}),
124
+ ...(payload.message !== undefined ? { message: payload.message } : {}),
125
+ });
126
+ lastCurrent = current;
127
+ if (total !== undefined && total === current) {
128
+ didSendTerminal = true;
129
+ }
130
+ };
131
+ }
132
+ function normalizeProgressContext(context) {
133
+ const compact = context?.replace(/\s+/g, ' ').trim();
134
+ if (!compact) {
135
+ return DEFAULT_PROGRESS_CONTEXT;
136
+ }
137
+ if (compact.length <= 80) {
138
+ return compact;
139
+ }
140
+ return `${compact.slice(0, 77)}...`;
141
+ }
142
+ function formatProgressStep(toolName, context, metadata) {
143
+ const prefix = metadata === 'start' ? '▸' : '◆';
144
+ return `${prefix} ${toolName}: ${context} [${metadata}]`;
145
+ }
146
+ function formatProgressCompletion(toolName, context, outcome) {
147
+ const prefix = outcome === 'completed' ? '◈' : '‣';
148
+ return `${prefix} ${toolName}: ${context} • ${outcome}`;
149
+ }
150
+ function toLoggingLevel(level) {
151
+ switch (level) {
152
+ case 'debug':
153
+ case 'info':
154
+ case 'notice':
155
+ case 'warning':
156
+ case 'error':
157
+ case 'critical':
158
+ case 'alert':
159
+ case 'emergency':
160
+ return level;
161
+ default:
162
+ return 'error';
163
+ }
164
+ }
165
+ function asObjectRecord(value) {
166
+ if (typeof value === 'object' && value !== null) {
167
+ return value;
168
+ }
169
+ return { payload: value };
170
+ }
171
+ function createGeminiLogger(server, taskId) {
172
+ return async (level, data) => {
173
+ try {
174
+ await server.sendLoggingMessage({
175
+ level: toLoggingLevel(level),
176
+ logger: 'gemini',
177
+ data: {
178
+ requestId: getCurrentRequestId(),
179
+ taskId,
180
+ ...asObjectRecord(data),
181
+ },
182
+ });
183
+ }
184
+ catch {
185
+ // Logging is best-effort; never fail the tool call.
186
+ }
187
+ };
188
+ }
14
189
  export function registerStructuredToolTask(server, config) {
15
190
  const responseSchema = createGeminiResponseSchema({
16
191
  geminiSchema: config.geminiSchema,
@@ -25,83 +200,105 @@ export function registerStructuredToolTask(server, config) {
25
200
  readOnlyHint: true,
26
201
  openWorldHint: true,
27
202
  },
28
- execution: {
29
- taskSupport: 'optional',
30
- },
31
203
  }, {
32
204
  createTask: async (input, extra) => {
33
205
  const task = await extra.taskStore.createTask({
34
- ttl: extra.taskRequestedTtl ?? null,
206
+ ttl: extra.taskRequestedTtl ?? DEFAULT_TASK_TTL_MS,
35
207
  });
36
- const progressToken = extra._meta?.progressToken;
37
- const sendProgress = async (progress, total) => {
38
- if (progressToken == null)
39
- return;
40
- try {
41
- await extra.sendNotification({
42
- method: 'notifications/progress',
43
- params: { progressToken, progress, total },
44
- });
45
- }
46
- catch {
47
- // Progress is best-effort; never fail the tool call.
48
- }
49
- };
50
- const updateStatusMessage = async (message) => {
51
- try {
52
- await extra.taskStore.updateTaskStatus(task.taskId, 'working', message);
53
- }
54
- catch {
55
- // statusMessage is best-effort; task may already be terminal.
56
- }
57
- };
58
- try {
59
- const onLog = async (level, data) => {
208
+ const runTask = async () => {
209
+ const reportProgress = createProgressReporter(extra);
210
+ let progressContext = DEFAULT_PROGRESS_CONTEXT;
211
+ const updateStatusMessage = async (message) => {
60
212
  try {
61
- await server.sendLoggingMessage({
62
- level: level,
63
- logger: 'gemini',
64
- data,
65
- });
213
+ await extra.taskStore.updateTaskStatus(task.taskId, 'working', message);
66
214
  }
67
215
  catch {
68
- // Logging is best-effort; never fail the tool call.
216
+ // statusMessage is best-effort; task may already be terminal.
69
217
  }
70
218
  };
71
- const inputRecord = parseToolInput(input, config.fullInputSchema);
72
- if (config.validateInput) {
73
- const validationError = await config.validateInput(inputRecord);
74
- if (validationError) {
75
- const validationMessage = validationError.structuredContent.error?.message ??
76
- 'Input validation failed';
77
- await updateStatusMessage(validationMessage);
78
- await extra.taskStore.storeTaskResult(task.taskId, 'failed', validationError);
79
- return { task };
219
+ const storeResultSafely = async (status, result) => {
220
+ try {
221
+ await extra.taskStore.storeTaskResult(task.taskId, status, result);
222
+ }
223
+ catch {
224
+ // storing the result failed, possibly because the task is already marked as failed due to an uncaught error. There's not much we can do at this point, so we swallow the error to avoid unhandled rejections.
225
+ }
226
+ };
227
+ try {
228
+ await reportProgress({
229
+ current: 0,
230
+ total: TASK_PROGRESS_TOTAL,
231
+ message: formatProgressStep(config.name, progressContext, 'start'),
232
+ });
233
+ const onLog = createGeminiLogger(server, task.taskId);
234
+ const inputRecord = parseToolInput(input, config.fullInputSchema);
235
+ progressContext = normalizeProgressContext(config.progressContext?.(inputRecord));
236
+ if (config.validateInput) {
237
+ const validationError = await config.validateInput(inputRecord);
238
+ if (validationError) {
239
+ const validationMessage = validationError.structuredContent.error?.message ??
240
+ INPUT_VALIDATION_FAILED;
241
+ await updateStatusMessage(validationMessage);
242
+ await reportProgress({
243
+ current: TASK_PROGRESS_TOTAL,
244
+ total: TASK_PROGRESS_TOTAL,
245
+ message: formatProgressCompletion(config.name, progressContext, 'failed'),
246
+ });
247
+ await storeResultSafely('completed', validationError);
248
+ return;
249
+ }
80
250
  }
251
+ await reportProgress({
252
+ current: 1,
253
+ total: TASK_PROGRESS_TOTAL,
254
+ message: formatProgressStep(config.name, progressContext, 'input validated'),
255
+ });
256
+ const { systemInstruction, prompt } = config.buildPrompt(inputRecord);
257
+ await reportProgress({
258
+ current: 2,
259
+ total: TASK_PROGRESS_TOTAL,
260
+ message: formatProgressStep(config.name, progressContext, 'prompt prepared'),
261
+ });
262
+ const raw = await generateStructuredJson(createGenerationRequest(config, { systemInstruction, prompt }, responseSchema, onLog, extra.signal));
263
+ await reportProgress({
264
+ current: 3,
265
+ total: TASK_PROGRESS_TOTAL,
266
+ message: formatProgressStep(config.name, progressContext, 'model response received'),
267
+ });
268
+ const parsed = config.resultSchema.parse(raw);
269
+ const finalResult = (config.transformResult
270
+ ? config.transformResult(inputRecord, parsed)
271
+ : parsed);
272
+ const textContent = config.formatOutput
273
+ ? config.formatOutput(finalResult)
274
+ : undefined;
275
+ await reportProgress({
276
+ current: TASK_PROGRESS_TOTAL,
277
+ total: TASK_PROGRESS_TOTAL,
278
+ message: formatProgressCompletion(config.name, progressContext, 'completed'),
279
+ });
280
+ await storeResultSafely('completed', createToolResponse({
281
+ ok: true,
282
+ result: finalResult,
283
+ }, textContent));
81
284
  }
82
- await sendProgress(1, 4);
83
- const { systemInstruction, prompt } = config.buildPrompt(inputRecord);
84
- await sendProgress(2, 4);
85
- const raw = await generateStructuredJson({
86
- systemInstruction,
87
- prompt,
88
- responseSchema,
89
- signal: extra.signal,
90
- onLog,
285
+ catch (error) {
286
+ const errorMessage = getErrorMessage(error);
287
+ const errorMeta = classifyErrorMeta(error, errorMessage);
288
+ await reportProgress({
289
+ current: TASK_PROGRESS_TOTAL,
290
+ total: TASK_PROGRESS_TOTAL,
291
+ message: formatProgressCompletion(config.name, progressContext, 'failed'),
292
+ });
293
+ await updateStatusMessage(errorMessage);
294
+ await storeResultSafely('failed', createErrorToolResponse(config.errorCode, errorMessage, undefined, errorMeta));
295
+ }
296
+ };
297
+ queueMicrotask(() => {
298
+ void runTask().catch((error) => {
299
+ console.error(`[task-runner:${config.name}] ${getErrorMessage(error)}`);
91
300
  });
92
- await sendProgress(3, 4);
93
- const parsed = config.resultSchema.parse(raw);
94
- await extra.taskStore.storeTaskResult(task.taskId, 'completed', createToolResponse({
95
- ok: true,
96
- result: parsed,
97
- }));
98
- await sendProgress(4, 4);
99
- }
100
- catch (error) {
101
- const errorMessage = getErrorMessage(error);
102
- await updateStatusMessage(errorMessage);
103
- await extra.taskStore.storeTaskResult(task.taskId, 'failed', createErrorToolResponse(config.errorCode, errorMessage));
104
- }
301
+ });
105
302
  return { task };
106
303
  },
107
304
  getTask: async (_input, extra) => {
@@ -1,6 +1,13 @@
1
+ export type ErrorKind = 'validation' | 'budget' | 'upstream' | 'timeout' | 'cancelled' | 'internal';
2
+ export interface ErrorMeta {
3
+ retryable?: boolean;
4
+ kind?: ErrorKind;
5
+ }
1
6
  interface ToolError {
2
7
  code: string;
3
8
  message: string;
9
+ retryable?: boolean;
10
+ kind?: ErrorKind;
4
11
  }
5
12
  interface ToolTextContent {
6
13
  type: 'text';
@@ -20,6 +27,6 @@ interface ToolResponse<TStructuredContent extends ToolStructuredContent> {
20
27
  interface ErrorToolResponse extends ToolResponse<ToolStructuredContent> {
21
28
  isError: true;
22
29
  }
23
- export declare function createToolResponse<TStructuredContent extends ToolStructuredContent>(structured: TStructuredContent): ToolResponse<TStructuredContent>;
24
- export declare function createErrorToolResponse(code: string, message: string, result?: unknown): ErrorToolResponse;
30
+ export declare function createToolResponse<TStructuredContent extends ToolStructuredContent>(structured: TStructuredContent, textContent?: string): ToolResponse<TStructuredContent>;
31
+ export declare function createErrorToolResponse(code: string, message: string, result?: unknown, meta?: ErrorMeta): ErrorToolResponse;
25
32
  export {};
@@ -1,23 +1,38 @@
1
- function toTextContent(structured) {
2
- return [{ type: 'text', text: JSON.stringify(structured) }];
1
+ function toTextContent(structured, textContent) {
2
+ const text = textContent ?? JSON.stringify(structured);
3
+ return [{ type: 'text', text }];
3
4
  }
4
- function createErrorStructuredContent(code, message, result) {
5
- if (result === undefined) {
6
- return { ok: false, error: { code, message } };
7
- }
8
- return { ok: false, error: { code, message }, result };
9
- }
10
- export function createToolResponse(structured) {
5
+ function buildToolResponse(structured, textContent) {
11
6
  return {
12
- content: toTextContent(structured),
7
+ content: toTextContent(structured, textContent),
13
8
  structuredContent: structured,
14
9
  };
15
10
  }
16
- export function createErrorToolResponse(code, message, result) {
17
- const structured = createErrorStructuredContent(code, message, result);
11
+ function createErrorStructuredContent(code, message, result, meta) {
12
+ const error = {
13
+ code,
14
+ message,
15
+ };
16
+ if (meta?.retryable !== undefined) {
17
+ error.retryable = meta.retryable;
18
+ }
19
+ if (meta?.kind !== undefined) {
20
+ error.kind = meta.kind;
21
+ }
22
+ if (result === undefined) {
23
+ return { ok: false, error };
24
+ }
25
+ return { ok: false, error, result };
26
+ }
27
+ export function createToolResponse(structured, textContent) {
28
+ return buildToolResponse(structured, textContent);
29
+ }
30
+ export function createErrorToolResponse(code, message, result, meta) {
31
+ const structured = createErrorStructuredContent(code, message, result, meta);
32
+ const base = buildToolResponse(structured);
18
33
  return {
19
- content: toTextContent(structured),
20
- structuredContent: structured,
34
+ content: base.content,
35
+ structuredContent: base.structuredContent,
21
36
  isError: true,
22
37
  };
23
38
  }
@@ -1,15 +1,20 @@
1
1
  export type JsonObject = Record<string, unknown>;
2
- export interface GeminiStructuredRequestOptions {
3
- model?: string;
2
+ export type GeminiLogHandler = (level: string, data: unknown) => Promise<void>;
3
+ interface GeminiRequestExecutionOptions {
4
4
  maxRetries?: number;
5
5
  timeoutMs?: number;
6
6
  temperature?: number;
7
7
  maxOutputTokens?: number;
8
+ thinkingBudget?: number;
8
9
  signal?: AbortSignal;
9
- onLog?: (level: string, data: unknown) => Promise<void>;
10
+ onLog?: GeminiLogHandler;
11
+ }
12
+ export interface GeminiStructuredRequestOptions extends GeminiRequestExecutionOptions {
13
+ model?: string;
10
14
  }
11
15
  export interface GeminiStructuredRequest extends GeminiStructuredRequestOptions {
12
16
  systemInstruction?: string;
13
17
  prompt: string;
14
18
  responseSchema: JsonObject;
15
19
  }
20
+ export {};
@@ -1,10 +1,18 @@
1
1
  import { completable } from '@modelcontextprotocol/sdk/server/completable.js';
2
2
  import { z } from 'zod';
3
3
  const HELP_PROMPT_NAME = 'get-help';
4
+ const HELP_PROMPT_TITLE = 'Get Help';
4
5
  const HELP_PROMPT_DESCRIPTION = 'Return the server usage instructions.';
5
6
  const REVIEW_GUIDE_PROMPT_NAME = 'review-guide';
7
+ const REVIEW_GUIDE_PROMPT_TITLE = 'Review Guide';
6
8
  const REVIEW_GUIDE_PROMPT_DESCRIPTION = 'Guided workflow instructions for a specific code review tool and focus area.';
7
- const TOOLS = ['review_diff', 'risk_score', 'suggest_patch'];
9
+ const TOOLS = [
10
+ 'analyze_pr_impact',
11
+ 'generate_review_summary',
12
+ 'inspect_code_quality',
13
+ 'suggest_search_replace',
14
+ 'generate_test_plan',
15
+ ];
8
16
  const FOCUS_AREAS = [
9
17
  'security',
10
18
  'correctness',
@@ -13,14 +21,13 @@ const FOCUS_AREAS = [
13
21
  'tests',
14
22
  ];
15
23
  const TOOL_GUIDES = {
16
- review_diff: 'Call `review_diff` with `diff` (unified diff text) and `repository` (org/repo). ' +
17
- 'Optional: `focusAreas` array and `maxFindings` cap. ' +
18
- 'Returns structured findings, overallRisk, and test recommendations.',
19
- risk_score: 'Call `risk_score` with `diff`. Optional: `deploymentCriticality` (low, medium, high). ' +
20
- 'Returns a 0–100 score, bucket, and rationale for release gating.',
21
- suggest_patch: 'First call `review_diff` to get findings. Then call `suggest_patch` with `diff`, ' +
22
- '`findingTitle`, and `findingDetails` from one finding. ' +
23
- 'Optional: `patchStyle` (minimal, balanced, defensive). One finding per call.',
24
+ analyze_pr_impact: 'Call `analyze_pr_impact` with `diff` and `repository`. ' +
25
+ 'Get severity rating and categorization.',
26
+ generate_review_summary: 'Call `generate_review_summary` for a concise digest and merge recommendation.',
27
+ inspect_code_quality: 'Call `inspect_code_quality` for deep review with optional file context. ' +
28
+ 'Uses thinking model for complex reasoning.',
29
+ suggest_search_replace: 'Call `suggest_search_replace` to generate verbatim search/replace fixes.',
30
+ generate_test_plan: 'Call `generate_test_plan` to create a verification strategy.',
24
31
  };
25
32
  const FOCUS_AREA_GUIDES = {
26
33
  security: 'Audit for injection vulnerabilities, insecure data handling, broken authentication, ' +
@@ -34,15 +41,28 @@ const FOCUS_AREA_GUIDES = {
34
41
  tests: 'Assess test coverage gaps, missing edge case tests, flaky test patterns, ' +
35
42
  'and untested error paths.',
36
43
  };
44
+ function completeByPrefix(values, prefix) {
45
+ const matches = [];
46
+ for (const value of values) {
47
+ if (value.startsWith(prefix)) {
48
+ matches.push(value);
49
+ }
50
+ }
51
+ return matches;
52
+ }
53
+ function getGuide(guides, value, fallback) {
54
+ const guide = guides[value];
55
+ return guide ?? fallback(value);
56
+ }
37
57
  function getToolGuide(tool) {
38
- return TOOL_GUIDES[tool] ?? `Use \`${tool}\` to analyze your code changes.`;
58
+ return getGuide(TOOL_GUIDES, tool, (toolName) => `Use \`${toolName}\` to analyze your code changes.`);
39
59
  }
40
60
  function getFocusAreaGuide(focusArea) {
41
- return FOCUS_AREA_GUIDES[focusArea] ?? `Focus on ${focusArea} concerns.`;
61
+ return getGuide(FOCUS_AREA_GUIDES, focusArea, (area) => `Focus on ${area} concerns.`);
42
62
  }
43
63
  export function registerAllPrompts(server, instructions) {
44
64
  server.registerPrompt(HELP_PROMPT_NAME, {
45
- title: 'Get Help',
65
+ title: HELP_PROMPT_TITLE,
46
66
  description: 'Return the server usage instructions.',
47
67
  }, () => ({
48
68
  description: HELP_PROMPT_DESCRIPTION,
@@ -57,15 +77,15 @@ export function registerAllPrompts(server, instructions) {
57
77
  ],
58
78
  }));
59
79
  server.registerPrompt(REVIEW_GUIDE_PROMPT_NAME, {
60
- title: 'Review Guide',
80
+ title: REVIEW_GUIDE_PROMPT_TITLE,
61
81
  description: REVIEW_GUIDE_PROMPT_DESCRIPTION,
62
82
  argsSchema: {
63
83
  tool: completable(z
64
84
  .string()
65
- .describe('Which review tool to use: review_diff, risk_score, or suggest_patch'), (value) => TOOLS.filter((t) => t.startsWith(value))),
85
+ .describe('Which review tool to use: analyze_pr_impact, generate_review_summary, etc.'), (value) => completeByPrefix(TOOLS, value)),
66
86
  focusArea: completable(z
67
87
  .string()
68
- .describe('Focus area: security, correctness, performance, regressions, or tests'), (value) => FOCUS_AREAS.filter((f) => f.startsWith(value))),
88
+ .describe('Focus area: security, correctness, performance, regressions, or tests'), (value) => completeByPrefix(FOCUS_AREAS, value)),
69
89
  },
70
90
  }, ({ tool, focusArea }) => ({
71
91
  description: `Code review guide: ${tool} / ${focusArea}`,
@@ -1,16 +1,17 @@
1
1
  const RESOURCE_ID = 'server-instructions';
2
2
  const RESOURCE_URI = 'internal://instructions';
3
3
  const RESOURCE_MIME_TYPE = 'text/markdown';
4
+ const RESOURCE_METADATA = {
5
+ title: 'Server Instructions',
6
+ description: 'Guidance for using the MCP tools effectively.',
7
+ mimeType: RESOURCE_MIME_TYPE,
8
+ annotations: {
9
+ audience: ['assistant'],
10
+ priority: 0.8,
11
+ },
12
+ };
4
13
  export function registerAllResources(server, instructions) {
5
- server.registerResource(RESOURCE_ID, RESOURCE_URI, {
6
- title: 'Server Instructions',
7
- description: 'Guidance for using the MCP tools effectively.',
8
- mimeType: RESOURCE_MIME_TYPE,
9
- annotations: {
10
- audience: ['assistant'],
11
- priority: 0.8,
12
- },
13
- }, (uri) => ({
14
+ server.registerResource(RESOURCE_ID, RESOURCE_URI, RESOURCE_METADATA, (uri) => ({
14
15
  contents: [
15
16
  {
16
17
  uri: uri.href,
@@ -1,26 +1,38 @@
1
1
  import { z } from 'zod';
2
- export declare const ReviewDiffInputSchema: z.ZodObject<{
2
+ export declare const FileContextSchema: z.ZodObject<{
3
+ path: z.ZodString;
4
+ content: z.ZodString;
5
+ }, z.core.$strict>;
6
+ export declare const AnalyzePrImpactInputSchema: z.ZodObject<{
3
7
  diff: z.ZodString;
4
8
  repository: z.ZodString;
5
9
  language: z.ZodOptional<z.ZodString>;
6
- focusAreas: z.ZodOptional<z.ZodArray<z.ZodString>>;
7
- maxFindings: z.ZodOptional<z.ZodNumber>;
8
10
  }, z.core.$strict>;
9
- export declare const RiskScoreInputSchema: z.ZodObject<{
11
+ export declare const GenerateReviewSummaryInputSchema: z.ZodObject<{
10
12
  diff: z.ZodString;
11
- deploymentCriticality: z.ZodOptional<z.ZodEnum<{
12
- low: "low";
13
- medium: "medium";
14
- high: "high";
15
- }>>;
13
+ repository: z.ZodString;
14
+ language: z.ZodOptional<z.ZodString>;
16
15
  }, z.core.$strict>;
17
- export declare const SuggestPatchInputSchema: z.ZodObject<{
16
+ export declare const InspectCodeQualityInputSchema: z.ZodObject<{
17
+ diff: z.ZodString;
18
+ repository: z.ZodString;
19
+ language: z.ZodOptional<z.ZodString>;
20
+ focusAreas: z.ZodOptional<z.ZodArray<z.ZodString>>;
21
+ maxFindings: z.ZodOptional<z.ZodNumber>;
22
+ files: z.ZodOptional<z.ZodArray<z.ZodObject<{
23
+ path: z.ZodString;
24
+ content: z.ZodString;
25
+ }, z.core.$strict>>>;
26
+ }, z.core.$strict>;
27
+ export declare const SuggestSearchReplaceInputSchema: z.ZodObject<{
18
28
  diff: z.ZodString;
19
29
  findingTitle: z.ZodString;
20
30
  findingDetails: z.ZodString;
21
- patchStyle: z.ZodOptional<z.ZodEnum<{
22
- minimal: "minimal";
23
- balanced: "balanced";
24
- defensive: "defensive";
25
- }>>;
31
+ }, z.core.$strict>;
32
+ export declare const GenerateTestPlanInputSchema: z.ZodObject<{
33
+ diff: z.ZodString;
34
+ repository: z.ZodString;
35
+ language: z.ZodOptional<z.ZodString>;
36
+ testFramework: z.ZodOptional<z.ZodString>;
37
+ maxTestCases: z.ZodOptional<z.ZodNumber>;
26
38
  }, z.core.$strict>;