@j0hanz/code-review-analyst-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.
@@ -0,0 +1,248 @@
1
+ import { AsyncLocalStorage } from 'node:async_hooks';
2
+ import { randomInt, randomUUID } from 'node:crypto';
3
+ import { EventEmitter } from 'node:events';
4
+ import { performance } from 'node:perf_hooks';
5
+ import { setTimeout as sleep } from 'node:timers/promises';
6
+ import { GoogleGenAI, HarmBlockThreshold, HarmCategory } from '@google/genai';
7
+ import { getErrorMessage } from './errors.js';
8
+ function getDefaultModel() {
9
+ return process.env.GEMINI_MODEL ?? 'gemini-2.5-flash';
10
+ }
11
+ const DEFAULT_MAX_RETRIES = 1;
12
+ const DEFAULT_TIMEOUT_MS = 15_000;
13
+ const DEFAULT_MAX_OUTPUT_TOKENS = 16_384;
14
+ const RETRY_DELAY_BASE_MS = 300;
15
+ const RETRY_DELAY_MAX_MS = 5_000;
16
+ const RETRY_JITTER_RATIO = 0.2;
17
+ const DEFAULT_SAFETY_THRESHOLD = HarmBlockThreshold.BLOCK_NONE;
18
+ const numberFormatter = new Intl.NumberFormat('en-US');
19
+ function formatNumber(value) {
20
+ return numberFormatter.format(value);
21
+ }
22
+ const SAFETY_THRESHOLD_BY_NAME = {
23
+ BLOCK_NONE: HarmBlockThreshold.BLOCK_NONE,
24
+ BLOCK_ONLY_HIGH: HarmBlockThreshold.BLOCK_ONLY_HIGH,
25
+ BLOCK_MEDIUM_AND_ABOVE: HarmBlockThreshold.BLOCK_MEDIUM_AND_ABOVE,
26
+ BLOCK_LOW_AND_ABOVE: HarmBlockThreshold.BLOCK_LOW_AND_ABOVE,
27
+ };
28
+ let cachedClient;
29
+ export const geminiEvents = new EventEmitter();
30
+ geminiEvents.on('log', (payload) => {
31
+ console.error(JSON.stringify(payload));
32
+ });
33
+ const geminiContext = new AsyncLocalStorage({
34
+ name: 'gemini_request',
35
+ });
36
+ function getApiKey() {
37
+ const apiKey = process.env.GEMINI_API_KEY ?? process.env.GOOGLE_API_KEY;
38
+ if (!apiKey) {
39
+ throw new Error('Missing GEMINI_API_KEY or GOOGLE_API_KEY.');
40
+ }
41
+ return apiKey;
42
+ }
43
+ function getClient() {
44
+ cachedClient ??= new GoogleGenAI({ apiKey: getApiKey() });
45
+ return cachedClient;
46
+ }
47
+ export function setClientForTesting(client) {
48
+ cachedClient = client;
49
+ }
50
+ function nextRequestId() {
51
+ return randomUUID();
52
+ }
53
+ function logEvent(event, details) {
54
+ const context = geminiContext.getStore();
55
+ geminiEvents.emit('log', {
56
+ event,
57
+ requestId: context?.requestId ?? null,
58
+ model: context?.model ?? null,
59
+ ...details,
60
+ });
61
+ }
62
+ function getNestedError(error) {
63
+ if (!error || typeof error !== 'object') {
64
+ return undefined;
65
+ }
66
+ const record = error;
67
+ const nested = record.error;
68
+ if (!nested || typeof nested !== 'object') {
69
+ return record;
70
+ }
71
+ return nested;
72
+ }
73
+ function getNumericErrorCode(error) {
74
+ const record = getNestedError(error);
75
+ if (!record) {
76
+ return undefined;
77
+ }
78
+ const candidates = [record.status, record.statusCode, record.code];
79
+ for (const candidate of candidates) {
80
+ if (typeof candidate === 'number' && Number.isFinite(candidate)) {
81
+ return candidate;
82
+ }
83
+ if (typeof candidate === 'string' && /^\d+$/.test(candidate)) {
84
+ return Number.parseInt(candidate, 10);
85
+ }
86
+ }
87
+ return undefined;
88
+ }
89
+ function getTransientErrorCode(error) {
90
+ const record = getNestedError(error);
91
+ if (!record) {
92
+ return undefined;
93
+ }
94
+ const candidates = [record.code, record.status, record.statusText];
95
+ for (const candidate of candidates) {
96
+ if (typeof candidate === 'string' && candidate.trim().length > 0) {
97
+ return candidate.trim().toUpperCase();
98
+ }
99
+ }
100
+ return undefined;
101
+ }
102
+ function shouldRetry(error) {
103
+ const numericCode = getNumericErrorCode(error);
104
+ if (numericCode === 429 ||
105
+ numericCode === 500 ||
106
+ numericCode === 502 ||
107
+ numericCode === 503 ||
108
+ numericCode === 504) {
109
+ return true;
110
+ }
111
+ const transientCode = getTransientErrorCode(error);
112
+ if (transientCode === 'RESOURCE_EXHAUSTED' ||
113
+ transientCode === 'UNAVAILABLE' ||
114
+ transientCode === 'DEADLINE_EXCEEDED' ||
115
+ transientCode === 'INTERNAL' ||
116
+ transientCode === 'ABORTED') {
117
+ return true;
118
+ }
119
+ const message = getErrorMessage(error);
120
+ return /(429|500|502|503|504|rate limit|unavailable|timeout|invalid json)/i.test(message);
121
+ }
122
+ function getRetryDelayMs(attempt) {
123
+ const exponentialDelay = RETRY_DELAY_BASE_MS * 2 ** attempt;
124
+ const boundedDelay = Math.min(RETRY_DELAY_MAX_MS, exponentialDelay);
125
+ const jitterWindow = Math.max(1, Math.floor(boundedDelay * RETRY_JITTER_RATIO));
126
+ const jitter = randomInt(0, jitterWindow);
127
+ return Math.min(RETRY_DELAY_MAX_MS, boundedDelay + jitter);
128
+ }
129
+ function getSafetyThreshold() {
130
+ const threshold = process.env.GEMINI_HARM_BLOCK_THRESHOLD;
131
+ if (!threshold) {
132
+ return DEFAULT_SAFETY_THRESHOLD;
133
+ }
134
+ const normalizedThreshold = threshold.trim().toUpperCase();
135
+ if (normalizedThreshold in SAFETY_THRESHOLD_BY_NAME) {
136
+ return SAFETY_THRESHOLD_BY_NAME[normalizedThreshold];
137
+ }
138
+ return DEFAULT_SAFETY_THRESHOLD;
139
+ }
140
+ function buildGenerationConfig(request, abortSignal) {
141
+ const safetyThreshold = getSafetyThreshold();
142
+ return {
143
+ temperature: request.temperature ?? 0.2,
144
+ maxOutputTokens: request.maxOutputTokens ?? DEFAULT_MAX_OUTPUT_TOKENS,
145
+ responseMimeType: 'application/json',
146
+ responseSchema: request.responseSchema,
147
+ ...(request.systemInstruction
148
+ ? { systemInstruction: request.systemInstruction }
149
+ : {}),
150
+ safetySettings: [
151
+ {
152
+ category: HarmCategory.HARM_CATEGORY_HATE_SPEECH,
153
+ threshold: safetyThreshold,
154
+ },
155
+ {
156
+ category: HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT,
157
+ threshold: safetyThreshold,
158
+ },
159
+ {
160
+ category: HarmCategory.HARM_CATEGORY_HARASSMENT,
161
+ threshold: safetyThreshold,
162
+ },
163
+ {
164
+ category: HarmCategory.HARM_CATEGORY_SEXUALLY_EXPLICIT,
165
+ threshold: safetyThreshold,
166
+ },
167
+ ],
168
+ abortSignal,
169
+ };
170
+ }
171
+ async function generateContentWithTimeout(request, model, timeoutMs) {
172
+ const controller = new AbortController();
173
+ const timeout = setTimeout(() => {
174
+ controller.abort();
175
+ }, timeoutMs);
176
+ timeout.unref();
177
+ const signal = request.signal
178
+ ? AbortSignal.any([controller.signal, request.signal])
179
+ : controller.signal;
180
+ try {
181
+ return await getClient().models.generateContent({
182
+ model,
183
+ contents: request.prompt,
184
+ config: buildGenerationConfig(request, signal),
185
+ });
186
+ }
187
+ catch (error) {
188
+ if (request.signal?.aborted) {
189
+ throw new Error('Gemini request was cancelled.');
190
+ }
191
+ if (controller.signal.aborted) {
192
+ throw new Error(`Gemini request timed out after ${formatNumber(timeoutMs)}ms.`);
193
+ }
194
+ throw error;
195
+ }
196
+ finally {
197
+ clearTimeout(timeout);
198
+ }
199
+ }
200
+ export async function generateStructuredJson(request) {
201
+ const model = request.model ?? getDefaultModel();
202
+ const timeoutMs = request.timeoutMs ?? DEFAULT_TIMEOUT_MS;
203
+ const maxRetries = request.maxRetries ?? DEFAULT_MAX_RETRIES;
204
+ return geminiContext.run({ requestId: nextRequestId(), model }, async () => {
205
+ let lastError;
206
+ for (let attempt = 0; attempt <= maxRetries; attempt += 1) {
207
+ const startedAt = performance.now();
208
+ try {
209
+ const response = await generateContentWithTimeout(request, model, timeoutMs);
210
+ logEvent('gemini_call', {
211
+ attempt,
212
+ latencyMs: Math.round(performance.now() - startedAt),
213
+ usageMetadata: response.usageMetadata ?? null,
214
+ });
215
+ if (!response.text) {
216
+ throw new Error('Gemini returned an empty response body.');
217
+ }
218
+ let parsed;
219
+ try {
220
+ parsed = JSON.parse(response.text);
221
+ }
222
+ catch (error) {
223
+ throw new Error(`Model produced invalid JSON: ${getErrorMessage(error)}`);
224
+ }
225
+ return parsed;
226
+ }
227
+ catch (error) {
228
+ lastError = error;
229
+ const retryable = shouldRetry(error);
230
+ if (attempt >= maxRetries || !retryable) {
231
+ break;
232
+ }
233
+ const delayMs = getRetryDelayMs(attempt);
234
+ logEvent('gemini_retry', {
235
+ attempt,
236
+ delayMs,
237
+ reason: getErrorMessage(error),
238
+ });
239
+ await sleep(delayMs, undefined, { ref: false });
240
+ }
241
+ }
242
+ logEvent('gemini_failure', {
243
+ error: getErrorMessage(lastError),
244
+ attempts: maxRetries + 1,
245
+ });
246
+ throw new Error(`Gemini request failed after ${maxRetries + 1} attempts: ${getErrorMessage(lastError)}`);
247
+ });
248
+ }
@@ -0,0 +1,31 @@
1
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import type { ZodRawShapeCompat } from '@modelcontextprotocol/sdk/server/zod-compat.js';
3
+ import { z } from 'zod';
4
+ import { createErrorToolResponse } from './tool-response.js';
5
+ export interface PromptParts {
6
+ systemInstruction: string;
7
+ prompt: string;
8
+ }
9
+ export interface StructuredToolTaskConfig<TInput extends object = Record<string, unknown>> {
10
+ /** Tool name registered with the MCP server (e.g. 'review_diff'). */
11
+ name: string;
12
+ /** Human-readable title shown to clients. */
13
+ title: string;
14
+ /** Short description of the tool's purpose. */
15
+ description: string;
16
+ /** Zod shape object (e.g. `MySchema.shape`) used as the MCP input schema. */
17
+ inputSchema: ZodRawShapeCompat;
18
+ /** Full Zod schema for runtime input re-validation (rejects unknown fields). */
19
+ fullInputSchema?: z.ZodType;
20
+ /** Zod schema for parsing and validating the Gemini structured response. */
21
+ resultSchema: z.ZodType;
22
+ /** Optional Zod schema used specifically for Gemini response validation. */
23
+ geminiSchema?: z.ZodType;
24
+ /** Stable error code returned on failure (e.g. 'E_REVIEW_DIFF'). */
25
+ errorCode: string;
26
+ /** Optional validation hook for input parameters. */
27
+ validateInput?: (input: TInput) => Promise<ReturnType<typeof createErrorToolResponse> | undefined> | ReturnType<typeof createErrorToolResponse> | undefined;
28
+ /** Builds the system instruction and user prompt from parsed tool input. */
29
+ buildPrompt: (input: TInput) => PromptParts;
30
+ }
31
+ export declare function registerStructuredToolTask<TInput extends object>(server: McpServer, config: StructuredToolTaskConfig<TInput>): void;
@@ -0,0 +1,82 @@
1
+ import { z } from 'zod';
2
+ import { DefaultOutputSchema } from '../schemas/outputs.js';
3
+ import { getErrorMessage } from './errors.js';
4
+ import { stripJsonSchemaConstraints } from './gemini-schema.js';
5
+ import { generateStructuredJson } from './gemini.js';
6
+ import { createErrorToolResponse, createToolResponse, } from './tool-response.js';
7
+ export function registerStructuredToolTask(server, config) {
8
+ const responseSchema = config.geminiSchema
9
+ ? z.toJSONSchema(config.geminiSchema)
10
+ : stripJsonSchemaConstraints(z.toJSONSchema(config.resultSchema));
11
+ server.experimental.tasks.registerToolTask(config.name, {
12
+ title: config.title,
13
+ description: config.description,
14
+ inputSchema: config.inputSchema,
15
+ outputSchema: DefaultOutputSchema,
16
+ annotations: {
17
+ readOnlyHint: true,
18
+ openWorldHint: true,
19
+ },
20
+ execution: {
21
+ taskSupport: 'optional',
22
+ },
23
+ }, {
24
+ createTask: async (input, extra) => {
25
+ const task = await extra.taskStore.createTask({
26
+ ttl: extra.taskRequestedTtl ?? null,
27
+ });
28
+ const progressToken = extra._meta?.progressToken;
29
+ const sendProgress = async (progress, total) => {
30
+ if (progressToken == null)
31
+ return;
32
+ try {
33
+ await extra.sendNotification({
34
+ method: 'notifications/progress',
35
+ params: { progressToken, progress, total },
36
+ });
37
+ }
38
+ catch {
39
+ // Progress is best-effort; never fail the tool call.
40
+ }
41
+ };
42
+ try {
43
+ const inputRecord = config.fullInputSchema
44
+ ? config.fullInputSchema.parse(input)
45
+ : input;
46
+ if (config.validateInput) {
47
+ const validationError = await config.validateInput(inputRecord);
48
+ if (validationError) {
49
+ await extra.taskStore.storeTaskResult(task.taskId, 'failed', validationError);
50
+ return { task };
51
+ }
52
+ }
53
+ await sendProgress(1, 4);
54
+ const { systemInstruction, prompt } = config.buildPrompt(inputRecord);
55
+ await sendProgress(2, 4);
56
+ const raw = await generateStructuredJson({
57
+ systemInstruction,
58
+ prompt,
59
+ responseSchema,
60
+ signal: extra.signal,
61
+ });
62
+ await sendProgress(3, 4);
63
+ const parsed = config.resultSchema.parse(raw);
64
+ await extra.taskStore.storeTaskResult(task.taskId, 'completed', createToolResponse({
65
+ ok: true,
66
+ result: parsed,
67
+ }));
68
+ await sendProgress(4, 4);
69
+ }
70
+ catch (error) {
71
+ await extra.taskStore.storeTaskResult(task.taskId, 'failed', createErrorToolResponse(config.errorCode, getErrorMessage(error)));
72
+ }
73
+ return { task };
74
+ },
75
+ getTask: async (_input, extra) => {
76
+ return await extra.taskStore.getTask(extra.taskId);
77
+ },
78
+ getTaskResult: async (_input, extra) => {
79
+ return (await extra.taskStore.getTaskResult(extra.taskId));
80
+ },
81
+ });
82
+ }
@@ -0,0 +1,25 @@
1
+ interface ToolError {
2
+ code: string;
3
+ message: string;
4
+ }
5
+ interface ToolTextContent {
6
+ type: 'text';
7
+ text: string;
8
+ }
9
+ interface ToolStructuredContent {
10
+ [key: string]: unknown;
11
+ ok: boolean;
12
+ result?: unknown;
13
+ error?: ToolError;
14
+ }
15
+ interface ToolResponse<TStructuredContent extends ToolStructuredContent> {
16
+ [key: string]: unknown;
17
+ content: ToolTextContent[];
18
+ structuredContent: TStructuredContent;
19
+ }
20
+ interface ErrorToolResponse extends ToolResponse<ToolStructuredContent> {
21
+ isError: true;
22
+ }
23
+ export declare function createToolResponse<TStructuredContent extends ToolStructuredContent>(structured: TStructuredContent): ToolResponse<TStructuredContent>;
24
+ export declare function createErrorToolResponse(code: string, message: string, result?: unknown): ErrorToolResponse;
25
+ export {};
@@ -0,0 +1,21 @@
1
+ function toTextContent(structured) {
2
+ return [{ type: 'text', text: JSON.stringify(structured) }];
3
+ }
4
+ export function createToolResponse(structured) {
5
+ return {
6
+ content: toTextContent(structured),
7
+ structuredContent: structured,
8
+ };
9
+ }
10
+ export function createErrorToolResponse(code, message, result) {
11
+ const structured = {
12
+ ok: false,
13
+ error: { code, message },
14
+ ...(result === undefined ? {} : { result }),
15
+ };
16
+ return {
17
+ content: toTextContent(structured),
18
+ structuredContent: structured,
19
+ isError: true,
20
+ };
21
+ }
@@ -0,0 +1,15 @@
1
+ export type JsonObject = Record<string, unknown>;
2
+ interface GeminiStructuredRequestOptions {
3
+ model?: string;
4
+ maxRetries?: number;
5
+ timeoutMs?: number;
6
+ temperature?: number;
7
+ maxOutputTokens?: number;
8
+ signal?: AbortSignal;
9
+ }
10
+ export interface GeminiStructuredRequest extends GeminiStructuredRequestOptions {
11
+ systemInstruction?: string;
12
+ prompt: string;
13
+ responseSchema: JsonObject;
14
+ }
15
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,2 @@
1
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ export declare function registerAllPrompts(server: McpServer, instructions: string): void;
@@ -0,0 +1,17 @@
1
+ export function registerAllPrompts(server, instructions) {
2
+ server.registerPrompt('get-help', {
3
+ title: 'Get Help',
4
+ description: 'Return the server usage instructions.',
5
+ }, () => ({
6
+ description: 'Server usage instructions',
7
+ messages: [
8
+ {
9
+ role: 'user',
10
+ content: {
11
+ type: 'text',
12
+ text: instructions,
13
+ },
14
+ },
15
+ ],
16
+ }));
17
+ }
@@ -0,0 +1,2 @@
1
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ export declare function registerAllResources(server: McpServer, instructions: string): void;
@@ -0,0 +1,19 @@
1
+ export function registerAllResources(server, instructions) {
2
+ server.registerResource('server-instructions', 'internal://instructions', {
3
+ title: 'Server Instructions',
4
+ description: 'Guidance for using the MCP tools effectively.',
5
+ mimeType: 'text/markdown',
6
+ annotations: {
7
+ audience: ['assistant'],
8
+ priority: 0.8,
9
+ },
10
+ }, (uri) => ({
11
+ contents: [
12
+ {
13
+ uri: uri.href,
14
+ mimeType: 'text/markdown',
15
+ text: instructions,
16
+ },
17
+ ],
18
+ }));
19
+ }
@@ -0,0 +1,26 @@
1
+ import { z } from 'zod';
2
+ export declare const ReviewDiffInputSchema: z.ZodObject<{
3
+ diff: z.ZodString;
4
+ repository: z.ZodString;
5
+ language: z.ZodOptional<z.ZodString>;
6
+ focusAreas: z.ZodOptional<z.ZodArray<z.ZodString>>;
7
+ maxFindings: z.ZodOptional<z.ZodNumber>;
8
+ }, z.core.$strict>;
9
+ export declare const RiskScoreInputSchema: z.ZodObject<{
10
+ diff: z.ZodString;
11
+ deploymentCriticality: z.ZodOptional<z.ZodEnum<{
12
+ low: "low";
13
+ medium: "medium";
14
+ high: "high";
15
+ }>>;
16
+ }, z.core.$strict>;
17
+ export declare const SuggestPatchInputSchema: z.ZodObject<{
18
+ diff: z.ZodString;
19
+ findingTitle: z.ZodString;
20
+ findingDetails: z.ZodString;
21
+ patchStyle: z.ZodOptional<z.ZodEnum<{
22
+ minimal: "minimal";
23
+ balanced: "balanced";
24
+ defensive: "defensive";
25
+ }>>;
26
+ }, z.core.$strict>;
@@ -0,0 +1,72 @@
1
+ import { z } from 'zod';
2
+ const INPUT_LIMITS = {
3
+ diff: { min: 10, max: 400_000 },
4
+ repository: { min: 1, max: 200 },
5
+ language: { min: 2, max: 32 },
6
+ focusArea: { min: 2, max: 80, maxItems: 12 },
7
+ maxFindings: { min: 1, max: 25 },
8
+ findingTitle: { min: 3, max: 160 },
9
+ findingDetails: { min: 10, max: 3_000 },
10
+ };
11
+ function createDiffSchema(description) {
12
+ return z
13
+ .string()
14
+ .min(INPUT_LIMITS.diff.min)
15
+ .max(INPUT_LIMITS.diff.max)
16
+ .describe(description);
17
+ }
18
+ export const ReviewDiffInputSchema = z.strictObject({
19
+ diff: createDiffSchema('Unified diff text for one PR or commit.'),
20
+ repository: z
21
+ .string()
22
+ .min(INPUT_LIMITS.repository.min)
23
+ .max(INPUT_LIMITS.repository.max)
24
+ .describe('Repository identifier, for example org/repo.'),
25
+ language: z
26
+ .string()
27
+ .min(INPUT_LIMITS.language.min)
28
+ .max(INPUT_LIMITS.language.max)
29
+ .optional()
30
+ .describe('Primary implementation language to bias review depth.'),
31
+ focusAreas: z
32
+ .array(z
33
+ .string()
34
+ .min(INPUT_LIMITS.focusArea.min)
35
+ .max(INPUT_LIMITS.focusArea.max)
36
+ .describe('Specific area to inspect, for example security or tests.'))
37
+ .min(1)
38
+ .max(INPUT_LIMITS.focusArea.maxItems)
39
+ .optional()
40
+ .describe('Optional list of priorities for this review pass.'),
41
+ maxFindings: z
42
+ .number()
43
+ .int()
44
+ .min(INPUT_LIMITS.maxFindings.min)
45
+ .max(INPUT_LIMITS.maxFindings.max)
46
+ .optional()
47
+ .describe('Maximum number of findings to return.'),
48
+ });
49
+ export const RiskScoreInputSchema = z.strictObject({
50
+ diff: createDiffSchema('Unified diff text to analyze for release risk.'),
51
+ deploymentCriticality: z
52
+ .enum(['low', 'medium', 'high'])
53
+ .optional()
54
+ .describe('How sensitive the target system is to regressions.'),
55
+ });
56
+ export const SuggestPatchInputSchema = z.strictObject({
57
+ diff: createDiffSchema('Unified diff text that contains the issue to patch.'),
58
+ findingTitle: z
59
+ .string()
60
+ .min(INPUT_LIMITS.findingTitle.min)
61
+ .max(INPUT_LIMITS.findingTitle.max)
62
+ .describe('Short title of the finding that needs a patch.'),
63
+ findingDetails: z
64
+ .string()
65
+ .min(INPUT_LIMITS.findingDetails.min)
66
+ .max(INPUT_LIMITS.findingDetails.max)
67
+ .describe('Detailed explanation of the bug or risk.'),
68
+ patchStyle: z
69
+ .enum(['minimal', 'balanced', 'defensive'])
70
+ .optional()
71
+ .describe('How broad the patch should be.'),
72
+ });
@@ -0,0 +1,59 @@
1
+ import { z } from 'zod';
2
+ export declare const DefaultOutputSchema: z.ZodObject<{
3
+ ok: z.ZodBoolean;
4
+ result: z.ZodOptional<z.ZodUnknown>;
5
+ error: z.ZodOptional<z.ZodObject<{
6
+ code: z.ZodString;
7
+ message: z.ZodString;
8
+ }, z.core.$strict>>;
9
+ }, z.core.$strict>;
10
+ export declare const ReviewFindingSchema: z.ZodObject<{
11
+ severity: z.ZodEnum<{
12
+ low: "low";
13
+ medium: "medium";
14
+ high: "high";
15
+ critical: "critical";
16
+ }>;
17
+ file: z.ZodString;
18
+ line: z.ZodNullable<z.ZodNumber>;
19
+ title: z.ZodString;
20
+ explanation: z.ZodString;
21
+ recommendation: z.ZodString;
22
+ }, z.core.$strict>;
23
+ export declare const ReviewDiffResultSchema: z.ZodObject<{
24
+ summary: z.ZodString;
25
+ overallRisk: z.ZodEnum<{
26
+ low: "low";
27
+ medium: "medium";
28
+ high: "high";
29
+ }>;
30
+ findings: z.ZodArray<z.ZodObject<{
31
+ severity: z.ZodEnum<{
32
+ low: "low";
33
+ medium: "medium";
34
+ high: "high";
35
+ critical: "critical";
36
+ }>;
37
+ file: z.ZodString;
38
+ line: z.ZodNullable<z.ZodNumber>;
39
+ title: z.ZodString;
40
+ explanation: z.ZodString;
41
+ recommendation: z.ZodString;
42
+ }, z.core.$strict>>;
43
+ testsNeeded: z.ZodArray<z.ZodString>;
44
+ }, z.core.$strict>;
45
+ export declare const RiskScoreResultSchema: z.ZodObject<{
46
+ score: z.ZodNumber;
47
+ bucket: z.ZodEnum<{
48
+ low: "low";
49
+ medium: "medium";
50
+ high: "high";
51
+ critical: "critical";
52
+ }>;
53
+ rationale: z.ZodArray<z.ZodString>;
54
+ }, z.core.$strict>;
55
+ export declare const PatchSuggestionResultSchema: z.ZodObject<{
56
+ summary: z.ZodString;
57
+ patch: z.ZodString;
58
+ validationChecklist: z.ZodArray<z.ZodString>;
59
+ }, z.core.$strict>;