@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.
package/README.md ADDED
@@ -0,0 +1,297 @@
1
+ # Code Review Analyst MCP Server
2
+
3
+ <!-- mcp-name: io.github.j0hanz/code-review-analyst -->
4
+
5
+ [![Node.js](https://img.shields.io/badge/Node.js-%3E%3D24-339933?style=flat-square&logo=nodedotjs&logoColor=white)](package.json) [![TypeScript](https://img.shields.io/badge/TypeScript-5.9%2B-3178C6?style=flat-square&logo=typescript&logoColor=white)](package.json) [![MCP SDK](https://img.shields.io/badge/MCP_SDK-1.26.0-6f42c1?style=flat-square)](package.json) [![License](https://img.shields.io/badge/License-MIT-blue?style=flat-square)](package.json)
6
+
7
+ [![Install in VS Code](https://img.shields.io/badge/VS_Code-Install_Server-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://insiders.vscode.dev/redirect/mcp/install?name=Code%20Review%20Analyst&config=%7B%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22code-review-analyst-mcp%40latest%22%5D%7D) [![Install in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install_Server-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://insiders.vscode.dev/redirect/mcp/install?name=Code%20Review%20Analyst&config=%7B%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22code-review-analyst-mcp%40latest%22%5D%7D&quality=insiders)
8
+
9
+ [![Install in Cursor](https://cursor.com/deeplink/mcp-install-dark.svg)](https://cursor.com/en/install-mcp?name=Code%20Review%20Analyst&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsImNvZGUtcmV2aWV3LWFuYWx5c3QtbWNwQGxhdGVzdCJdfQ==)
10
+
11
+ Gemini-powered MCP server for pull request analysis with structured outputs for findings, release risk, and focused patch suggestions.
12
+
13
+ ## Overview
14
+
15
+ This server runs over **stdio transport** and exposes three review-focused tools: `review_diff`, `risk_score`, and `suggest_patch`. It also publishes an `internal://instructions` resource and a `get-help` prompt for in-client guidance.
16
+
17
+ ## Key Features
18
+
19
+ - Structured review analysis with strict JSON output envelopes (`ok`, `result`, `error`).
20
+ - Three complementary workflows: full review, release risk scoring, and targeted patch generation.
21
+ - Runtime diff-size budget guard (`MAX_DIFF_CHARS`, default `120000`).
22
+ - Optional task execution support (`execution.taskSupport: "optional"`) with in-memory task store.
23
+ - Progress notifications when clients provide `_meta.progressToken`.
24
+ - Shared Gemini adapter with timeout, retries, safety thresholds, and structured observability logs to `stderr`.
25
+
26
+ ## Requirements
27
+
28
+ - Node.js `>=24`
29
+ - One API key: `GEMINI_API_KEY` or `GOOGLE_API_KEY`
30
+ - MCP client that supports stdio servers and tool calls
31
+
32
+ ## Quick Start
33
+
34
+ Standard config for most MCP clients:
35
+
36
+ ```json
37
+ {
38
+ "mcpServers": {
39
+ "code-review-analyst": {
40
+ "command": "npx",
41
+ "args": ["-y", "code-review-analyst-mcp@latest"],
42
+ "env": {
43
+ "GEMINI_API_KEY": "YOUR_API_KEY"
44
+ }
45
+ }
46
+ }
47
+ }
48
+ ```
49
+
50
+ > [!TIP]
51
+ > For local development, build and run directly via `node dist/index.js` after `npm run build`.
52
+
53
+ ## Client Configuration
54
+
55
+ <details>
56
+ <summary><b>Install in VS Code</b></summary>
57
+
58
+ [![Install in VS Code](https://img.shields.io/badge/VS_Code-Install_Server-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://insiders.vscode.dev/redirect/mcp/install?name=Code%20Review%20Analyst&config=%7B%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22code-review-analyst-mcp%40latest%22%5D%7D)
59
+ [![Install in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install_Server-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://insiders.vscode.dev/redirect/mcp/install?name=Code%20Review%20Analyst&config=%7B%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22code-review-analyst-mcp%40latest%22%5D%7D&quality=insiders)
60
+
61
+ `.vscode/mcp.json`
62
+
63
+ ```json
64
+ {
65
+ "servers": {
66
+ "code-review-analyst": {
67
+ "command": "npx",
68
+ "args": ["-y", "code-review-analyst-mcp@latest"],
69
+ "env": {
70
+ "GEMINI_API_KEY": "YOUR_API_KEY"
71
+ }
72
+ }
73
+ }
74
+ }
75
+ ```
76
+
77
+ CLI install:
78
+
79
+ ```bash
80
+ code --add-mcp '{"name":"code-review-analyst","command":"npx","args":["-y","code-review-analyst-mcp@latest"]}'
81
+ ```
82
+
83
+ </details>
84
+
85
+ <details>
86
+ <summary><b>Install in Cursor</b></summary>
87
+
88
+ [![Install in Cursor](https://cursor.com/deeplink/mcp-install-dark.svg)](https://cursor.com/en/install-mcp?name=Code%20Review%20Analyst&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsImNvZGUtcmV2aWV3LWFuYWx5c3QtbWNwQGxhdGVzdCJdfQ==)
89
+
90
+ `~/.cursor/mcp.json`
91
+
92
+ ```json
93
+ {
94
+ "mcpServers": {
95
+ "code-review-analyst": {
96
+ "command": "npx",
97
+ "args": ["-y", "code-review-analyst-mcp@latest"],
98
+ "env": {
99
+ "GEMINI_API_KEY": "YOUR_API_KEY"
100
+ }
101
+ }
102
+ }
103
+ }
104
+ ```
105
+
106
+ </details>
107
+
108
+ <details>
109
+ <summary><b>Install in Claude Desktop</b></summary>
110
+
111
+ `claude_desktop_config.json`
112
+
113
+ ```json
114
+ {
115
+ "mcpServers": {
116
+ "code-review-analyst": {
117
+ "command": "npx",
118
+ "args": ["-y", "code-review-analyst-mcp@latest"],
119
+ "env": {
120
+ "GEMINI_API_KEY": "YOUR_API_KEY"
121
+ }
122
+ }
123
+ }
124
+ }
125
+ ```
126
+
127
+ </details>
128
+
129
+ <details>
130
+ <summary><b>Install in Claude Code</b></summary>
131
+
132
+ ```bash
133
+ claude mcp add code-review-analyst -- npx -y code-review-analyst-mcp@latest
134
+ ```
135
+
136
+ </details>
137
+
138
+ ## MCP Surface
139
+
140
+ ### Tools
141
+
142
+ #### `review_diff`
143
+
144
+ Analyze a unified diff and return structured findings, overall merge risk, and test recommendations.
145
+
146
+ | Name | Type | Required | Default | Description |
147
+ | ------------- | ---------- | -------- | ------------------------------------------------- | ---------------------------------------------------- |
148
+ | `diff` | `string` | Yes | — | Unified diff text (`10..400000` chars schema limit). |
149
+ | `repository` | `string` | Yes | — | Repository identifier (example: `org/repo`). |
150
+ | `language` | `string` | No | `not specified` | Primary language hint for analysis. |
151
+ | `focusAreas` | `string[]` | No | `security, correctness, regressions, performance` | Optional review priorities (`1..12` items). |
152
+ | `maxFindings` | `integer` | No | `10` | Max findings returned (`1..25`). |
153
+
154
+ Returns (inside `result`):
155
+
156
+ - `summary`, `overallRisk` (`low|medium|high`), `findings[]`, `testsNeeded[]`
157
+
158
+ Example:
159
+
160
+ ```json
161
+ {
162
+ "ok": true,
163
+ "result": {
164
+ "summary": "One high-risk auth-path change without null guards.",
165
+ "overallRisk": "high",
166
+ "findings": [
167
+ {
168
+ "severity": "high",
169
+ "file": "src/auth.ts",
170
+ "line": 42,
171
+ "title": "Missing null check",
172
+ "explanation": "Null response can throw and break login.",
173
+ "recommendation": "Guard for null before property access."
174
+ }
175
+ ],
176
+ "testsNeeded": ["Add auth null-path regression test"]
177
+ }
178
+ }
179
+ ```
180
+
181
+ #### `risk_score`
182
+
183
+ Score deployment risk for a diff and explain the score drivers.
184
+
185
+ | Name | Type | Required | Default | Description |
186
+ | ----------------------- | ----------------------------- | -------- | -------- | ---------------------------------------------------- |
187
+ | `diff` | `string` | Yes | — | Unified diff text (`10..400000` chars schema limit). |
188
+ | `deploymentCriticality` | `"low" \| "medium" \| "high"` | No | `medium` | Sensitivity of target deployment. |
189
+
190
+ Returns (inside `result`):
191
+
192
+ - `score` (`0..100`), `bucket` (`low|medium|high|critical`), `rationale[]`
193
+
194
+ #### `suggest_patch`
195
+
196
+ Generate a focused unified-diff patch for one selected finding.
197
+
198
+ | Name | Type | Required | Default | Description |
199
+ | ---------------- | ---------------------------------------- | -------- | ---------- | ------------------------------------------------ |
200
+ | `diff` | `string` | Yes | — | Unified diff text containing the issue context. |
201
+ | `findingTitle` | `string` | Yes | — | Short finding title (`3..160` chars). |
202
+ | `findingDetails` | `string` | Yes | — | Detailed finding explanation (`10..3000` chars). |
203
+ | `patchStyle` | `"minimal" \| "balanced" \| "defensive"` | No | `balanced` | Desired patch breadth. |
204
+
205
+ Returns (inside `result`):
206
+
207
+ - `summary`, `patch` (unified diff text), `validationChecklist[]`
208
+
209
+ ### Resources
210
+
211
+ | URI | Name | MIME Type | Description |
212
+ | ------------------------- | --------------------- | --------------- | -------------------------------------------- |
213
+ | `internal://instructions` | `server-instructions` | `text/markdown` | In-repo usage guide for tools and workflows. |
214
+
215
+ ### Prompts
216
+
217
+ | Name | Description | Arguments |
218
+ | ---------- | ---------------------------------- | --------- |
219
+ | `get-help` | Returns server usage instructions. | None |
220
+
221
+ ### Tasks & Progress
222
+
223
+ - Server declares `capabilities.tasks` with tool-call task support.
224
+ - Each tool is registered with `execution.taskSupport: "optional"`.
225
+ - Progress updates are emitted via `notifications/progress` when `_meta.progressToken` is provided.
226
+ - Task storage uses in-memory task store (`InMemoryTaskStore`).
227
+
228
+ ## Configuration
229
+
230
+ ### Runtime Mode
231
+
232
+ | Mode | Supported | Notes |
233
+ | ------------------------ | --------- | -------------------------------------- |
234
+ | `stdio` | Yes | Active transport in `src/index.ts`. |
235
+ | HTTP/SSE/Streamable HTTP | No | Not implemented in current entrypoint. |
236
+
237
+ ### Environment Variables
238
+
239
+ | Variable | Description | Default | Required |
240
+ | ----------------------------- | --------------------------------------------------------------------------------------------------- | ------------------ | ------------------------------------------- |
241
+ | `GEMINI_API_KEY` | Gemini API key (preferred) | — | One of `GEMINI_API_KEY` or `GOOGLE_API_KEY` |
242
+ | `GOOGLE_API_KEY` | Alternate Gemini API key env | — | One of `GEMINI_API_KEY` or `GOOGLE_API_KEY` |
243
+ | `GEMINI_MODEL` | Gemini model id | `gemini-2.5-flash` | No |
244
+ | `GEMINI_HARM_BLOCK_THRESHOLD` | Safety threshold (`BLOCK_NONE`, `BLOCK_ONLY_HIGH`, `BLOCK_MEDIUM_AND_ABOVE`, `BLOCK_LOW_AND_ABOVE`) | `BLOCK_NONE` | No |
245
+ | `MAX_DIFF_CHARS` | Runtime diff-size budget | `120000` | No |
246
+ | `TASK_TIMEOUT_MS` | Task-runner timeout for build/test scripts | unset | No |
247
+
248
+ ## Security
249
+
250
+ - Stdio transport avoids HTTP exposure in the current runtime path.
251
+ - Runtime logs and warnings are written to `stderr`; avoid writing non-protocol output to `stdout` in stdio mode.
252
+ - Input and output contracts use strict Zod schemas (`z.strictObject`) with explicit bounds.
253
+ - Oversized diffs are rejected early with `E_INPUT_TOO_LARGE`.
254
+ - Tool metadata marks calls as `readOnlyHint: true` and `openWorldHint: true` (external model call, no local state mutation).
255
+
256
+ ## Development
257
+
258
+ Install and run locally:
259
+
260
+ ```bash
261
+ npm install
262
+ npm run build
263
+ npm start
264
+ ```
265
+
266
+ Useful scripts:
267
+
268
+ | Script | Command | Purpose |
269
+ | ------------ | ----------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------- |
270
+ | `build` | `node scripts/tasks.mjs build` | Clean, compile, validate instructions, copy assets, set executable bit. |
271
+ | `dev` | `tsc --watch --preserveWatchOutput` | TypeScript watch mode. |
272
+ | `dev:run` | `node --env-file=.env --watch dist/index.js` | Run built server with watch and `.env`. |
273
+ | `test` | `node scripts/tasks.mjs test` | Full build + Node test runner. |
274
+ | `test:fast` | `node --test --import tsx/esm ...` | Fast test path on TS sources. |
275
+ | `type-check` | `node scripts/tasks.mjs type-check` | TypeScript no-emit checks. |
276
+ | `lint` | `eslint .` | ESLint checks. |
277
+ | `format` | `prettier --write .` | Prettier formatting. |
278
+ | `inspector` | `npm run build && npx -y @modelcontextprotocol/inspector node dist/index.js ${workspaceFolder}` | MCP Inspector for stdio server. |
279
+
280
+ Inspector examples:
281
+
282
+ ```bash
283
+ # stdio
284
+ npx @modelcontextprotocol/inspector node dist/index.js
285
+ ```
286
+
287
+ ## Troubleshooting
288
+
289
+ - **`E_INPUT_TOO_LARGE`**: split diff into smaller chunks, then rerun.
290
+ - **`E_REVIEW_DIFF` / `E_RISK_SCORE` / `E_SUGGEST_PATCH`**: verify API key env vars and retry with narrower input.
291
+ - **`Gemini request timed out after ...ms.`**: reduce diff/prompt size or increase timeout in caller.
292
+ - **`Gemini returned an empty response body.`**: retry and check upstream model health.
293
+ - **Malformed model JSON response**: retry with same schema and inspect stderr logs.
294
+
295
+ ## Contributing & License
296
+
297
+ - License: **MIT** (from `package.json`).
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,54 @@
1
+ #!/usr/bin/env node
2
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
3
+ import { parseArgs } from 'node:util';
4
+ import { getErrorMessage } from './lib/errors.js';
5
+ import { createServer } from './server.js';
6
+ const SHUTDOWN_SIGNALS = ['SIGINT', 'SIGTERM'];
7
+ function parseCommandLineArgs() {
8
+ const { values } = parseArgs({
9
+ args: process.argv.slice(2),
10
+ options: {
11
+ model: {
12
+ type: 'string',
13
+ short: 'm',
14
+ },
15
+ 'max-diff-chars': {
16
+ type: 'string',
17
+ },
18
+ },
19
+ strict: false,
20
+ });
21
+ if (typeof values.model === 'string') {
22
+ process.env.GEMINI_MODEL = values.model;
23
+ }
24
+ if (typeof values['max-diff-chars'] === 'string') {
25
+ process.env.MAX_DIFF_CHARS = values['max-diff-chars'];
26
+ }
27
+ }
28
+ let shuttingDown = false;
29
+ async function shutdown(server) {
30
+ if (shuttingDown) {
31
+ return;
32
+ }
33
+ shuttingDown = true;
34
+ await server.close();
35
+ process.exit(0);
36
+ }
37
+ function registerShutdownHandlers(server) {
38
+ for (const signal of SHUTDOWN_SIGNALS) {
39
+ process.on(signal, () => {
40
+ void shutdown(server);
41
+ });
42
+ }
43
+ }
44
+ async function main() {
45
+ parseCommandLineArgs();
46
+ const server = createServer();
47
+ const transport = new StdioServerTransport();
48
+ registerShutdownHandlers(server);
49
+ await server.connect(transport);
50
+ }
51
+ main().catch((error) => {
52
+ console.error(`[fatal] ${getErrorMessage(error)}`);
53
+ process.exit(1);
54
+ });
@@ -0,0 +1,126 @@
1
+ # CODE REVIEW ANALYST MCP INSTRUCTIONS
2
+
3
+ These instructions are available as a resource (internal://instructions) or prompt (get-help). Load them when unsure about tool usage.
4
+
5
+ ---
6
+
7
+ ## CORE CAPABILITY
8
+
9
+ - Domain: Analyze pull request diffs with Gemini and return structured review findings, risk scores, and focused patch suggestions for automation clients.
10
+ - Primary Resources: Unified diff text, structured JSON review results, release-risk assessments, and unified-diff patch suggestions.
11
+ - Tools: READ: `review_diff`, `risk_score`, `suggest_patch`. WRITE: none.
12
+
13
+ ---
14
+
15
+ ## PROMPTS
16
+
17
+ - `get-help`: Returns these instructions for quick recall.
18
+
19
+ ---
20
+
21
+ ## RESOURCES & RESOURCE LINKS
22
+
23
+ - `internal://instructions`: This document.
24
+
25
+ ---
26
+
27
+ ## PROGRESS & TASKS
28
+
29
+ - Include `_meta.progressToken` in requests to receive `notifications/progress` updates during Gemini processing.
30
+ - Task-augmented tool calls are supported for `review_diff`, `risk_score`, and `suggest_patch`:
31
+ - These tools declare `execution.taskSupport: "optional"` — invoke normally or as a task.
32
+ - Send `tools/call` with `task` to get a task id.
33
+ - Poll `tasks/get` and fetch results via `tasks/result`.
34
+ - Use `tasks/cancel` to abort.
35
+ - Task data is stored in memory and cleared on restart.
36
+
37
+ ---
38
+
39
+ ## THE "GOLDEN PATH" WORKFLOWS (CRITICAL)
40
+
41
+ ### WORKFLOW A: FULL PR REVIEW
42
+
43
+ 1. Call `review_diff` with `diff` and `repository` to get structured findings and merge risk.
44
+ 2. Use `focusAreas` to bias analysis toward the highest-priority concerns.
45
+ 3. Use `maxFindings` to cap result volume when context windows are tight.
46
+ NOTE: Never pass oversized diffs. Pre-check against your own limits and handle `E_INPUT_TOO_LARGE`.
47
+
48
+ ### WORKFLOW B: RELEASE GATE RISK CHECK
49
+
50
+ 1. Call `risk_score` with `diff` to get a 0–100 score, bucket, and rationale.
51
+ 2. Set `deploymentCriticality` when evaluating sensitive systems.
52
+ 3. Use score and rationale to decide whether to block or require additional validation.
53
+ NOTE: Call `review_diff` first if you need file-level defect evidence.
54
+
55
+ ### WORKFLOW C: PATCH FROM A SELECTED FINDING
56
+
57
+ 1. Call `review_diff` to identify one concrete finding to fix.
58
+ 2. Call `suggest_patch` with the same `diff`, plus `findingTitle` and `findingDetails` from that finding.
59
+ 3. Use `patchStyle` (`minimal`, `balanced`, `defensive`) to control change breadth.
60
+ NOTE: Keep inputs scoped to one finding at a time to avoid mixed patch intent.
61
+
62
+ ---
63
+
64
+ ## TOOL NUANCES & GOTCHAS
65
+
66
+ `review_diff`
67
+
68
+ - Purpose: Generate structured review findings, overall risk, and test recommendations from a unified diff.
69
+ - Input: `maxFindings` defaults to 10; `focusAreas` defaults to security/correctness/regressions/performance when omitted.
70
+ - Output: `ok/result/error` envelope; successful payload follows `ReviewDiffResultSchema` and includes `summary`, `overallRisk`, `findings`, and `testsNeeded`.
71
+ - Gotcha: Schema allows `diff` up to 400,000 chars, but runtime rejects payloads above `MAX_DIFF_CHARS` (default 120,000) with `E_INPUT_TOO_LARGE`.
72
+ - Side effects: Calls external Gemini API (`openWorldHint: true`); does not mutate local state (`readOnlyHint: true`).
73
+
74
+ `risk_score`
75
+
76
+ - Purpose: Produce deployment risk score and rationale for release decisions.
77
+ - Input: `deploymentCriticality` defaults to `medium` when omitted.
78
+ - Output: `ok/result/error` envelope; successful payload includes `score`, `bucket`, and `rationale`.
79
+ - Gotcha: Uses the same runtime diff budget guard as other tools; oversized inputs fail before model execution.
80
+ - Side effects: External Gemini call only.
81
+
82
+ `suggest_patch`
83
+
84
+ - Purpose: Generate a focused unified diff patch for one selected review finding.
85
+ - Input: `patchStyle` defaults to `balanced`; requires both `findingTitle` and `findingDetails`.
86
+ - Output: `ok/result/error` envelope; successful payload includes `summary`, `patch`, and `validationChecklist`.
87
+ - Gotcha: Output is model-generated text and must be validated before application.
88
+ - Side effects: External Gemini call only.
89
+
90
+ ---
91
+
92
+ ## CROSS-FEATURE RELATIONSHIPS
93
+
94
+ - Use `review_diff` first to generate concrete finding metadata for `suggest_patch` inputs.
95
+ - Use `risk_score` after `review_diff` when you need both defect-level detail and a release gate score.
96
+ - All tools share the same Gemini adapter, retry policy, timeout policy, and diff-size guard.
97
+ - All tool responses include both `structuredContent` and JSON-string `content` for client compatibility.
98
+
99
+ ---
100
+
101
+ ## CONSTRAINTS & LIMITATIONS
102
+
103
+ - Transport: stdio only in current server entrypoint.
104
+ - API credentials: Require `GEMINI_API_KEY` or `GOOGLE_API_KEY`.
105
+ - Model selection: Uses `GEMINI_MODEL` if set; defaults to `gemini-2.5-flash`.
106
+ - Diff size: Runtime limit defaults to 120,000 chars (`MAX_DIFF_CHARS` env override). Input schema max is 400,000 chars.
107
+ - Timeout/retries: Per-call timeout defaults to 15,000 ms; retry count defaults to 1 with exponential backoff.
108
+ - Output tokens: `maxOutputTokens` defaults to 16,384 to prevent unbounded responses.
109
+ - Safety config: Gemini safety thresholds default to `BLOCK_NONE` for configured harm categories and can be overridden with `GEMINI_HARM_BLOCK_THRESHOLD` (`BLOCK_NONE`, `BLOCK_ONLY_HIGH`, `BLOCK_MEDIUM_AND_ABOVE`, `BLOCK_LOW_AND_ABOVE`).
110
+ - Resource scope: Only `internal://instructions` is registered as a resource; no dynamic resource templates are exposed.
111
+ - Prompt scope: Only `get-help` is registered.
112
+
113
+ ---
114
+
115
+ ## ERROR HANDLING STRATEGY
116
+
117
+ - `E_INPUT_TOO_LARGE`: Diff exceeded runtime budget. → Split the diff into smaller chunks or raise `MAX_DIFF_CHARS` safely.
118
+ - `E_REVIEW_DIFF`: Review generation failed. → Check API key env vars, reduce diff size, and retry; inspect stderr Gemini logs.
119
+ - `E_RISK_SCORE`: Risk scoring failed. → Check connectivity/model availability and retry with same diff.
120
+ - `E_SUGGEST_PATCH`: Patch generation failed. → Verify finding inputs are specific and retry with narrower details.
121
+ - Missing `GEMINI_API_KEY`/`GOOGLE_API_KEY` (wrapped by tool error codes): Credentials not configured. → Set one API key env var and rerun.
122
+ - Gemini timeout message (`Gemini request timed out after ...ms.`): Request exceeded timeout budget. → Reduce prompt/diff size or increase `timeoutMs` in caller.
123
+ - Empty model body (`Gemini returned an empty response body.`): Provider returned no text payload. → Retry and inspect model/service status.
124
+ - JSON parse failure from model output (wrapped by tool error codes): Output was not valid JSON. → Retry with same schema; inspect logs for malformed response text.
125
+
126
+ ---
@@ -0,0 +1,4 @@
1
+ import { createErrorToolResponse } from './tool-response.js';
2
+ export declare function exceedsDiffBudget(diff: string): boolean;
3
+ export declare function getDiffBudgetError(diffLength: number): string;
4
+ export declare function validateDiffBudget(diff: string): ReturnType<typeof createErrorToolResponse> | undefined;
@@ -0,0 +1,27 @@
1
+ import { createErrorToolResponse } from './tool-response.js';
2
+ const DEFAULT_MAX_DIFF_CHARS = 120_000;
3
+ const MAX_DIFF_CHARS_ENV_VAR = 'MAX_DIFF_CHARS';
4
+ const numberFormatter = new Intl.NumberFormat('en-US');
5
+ function formatNumber(value) {
6
+ return numberFormatter.format(value);
7
+ }
8
+ function getPositiveIntEnv(name) {
9
+ const parsed = Number.parseInt(process.env[name] ?? '', 10);
10
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : undefined;
11
+ }
12
+ function getMaxDiffChars() {
13
+ return getPositiveIntEnv(MAX_DIFF_CHARS_ENV_VAR) ?? DEFAULT_MAX_DIFF_CHARS;
14
+ }
15
+ export function exceedsDiffBudget(diff) {
16
+ return diff.length > getMaxDiffChars();
17
+ }
18
+ export function getDiffBudgetError(diffLength) {
19
+ const maxDiffChars = getMaxDiffChars();
20
+ return `diff exceeds max allowed size (${formatNumber(diffLength)} chars > ${formatNumber(maxDiffChars)} chars)`;
21
+ }
22
+ export function validateDiffBudget(diff) {
23
+ if (!exceedsDiffBudget(diff)) {
24
+ return undefined;
25
+ }
26
+ return createErrorToolResponse('E_INPUT_TOO_LARGE', getDiffBudgetError(diff.length));
27
+ }
@@ -0,0 +1 @@
1
+ export declare function getErrorMessage(error: unknown): string;
@@ -0,0 +1,16 @@
1
+ import { inspect } from 'node:util';
2
+ function isErrorWithMessage(error) {
3
+ return (typeof error === 'object' &&
4
+ error !== null &&
5
+ 'message' in error &&
6
+ typeof error.message === 'string');
7
+ }
8
+ export function getErrorMessage(error) {
9
+ if (isErrorWithMessage(error)) {
10
+ return error.message;
11
+ }
12
+ if (typeof error === 'string') {
13
+ return error;
14
+ }
15
+ return inspect(error, { depth: 3, breakLength: 120 });
16
+ }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Recursively strips value-range constraints (`min*`, `max*`, `multipleOf`)
3
+ * from a JSON Schema object and converts `"type": "integer"` to
4
+ * `"type": "number"`.
5
+ *
6
+ * Use this to derive a relaxed schema for Gemini structured output from the
7
+ * same Zod schema that validates tool results. The tool-level result schema
8
+ * enforces strict bounds *after* Gemini returns its response.
9
+ */
10
+ export declare function stripJsonSchemaConstraints(schema: Record<string, unknown>): Record<string, unknown>;
@@ -0,0 +1,51 @@
1
+ /**
2
+ * JSON Schema property keys that represent value-range or count constraints.
3
+ * These are stripped when generating relaxed schemas for Gemini structured
4
+ * output so the model is not over-constrained by bounds that the
5
+ * application-level result schema enforces after parsing.
6
+ */
7
+ const CONSTRAINT_KEYS = new Set([
8
+ 'minLength',
9
+ 'maxLength',
10
+ 'minimum',
11
+ 'maximum',
12
+ 'exclusiveMinimum',
13
+ 'exclusiveMaximum',
14
+ 'minItems',
15
+ 'maxItems',
16
+ 'multipleOf',
17
+ ]);
18
+ /**
19
+ * Recursively strips value-range constraints (`min*`, `max*`, `multipleOf`)
20
+ * from a JSON Schema object and converts `"type": "integer"` to
21
+ * `"type": "number"`.
22
+ *
23
+ * Use this to derive a relaxed schema for Gemini structured output from the
24
+ * same Zod schema that validates tool results. The tool-level result schema
25
+ * enforces strict bounds *after* Gemini returns its response.
26
+ */
27
+ export function stripJsonSchemaConstraints(schema) {
28
+ const result = {};
29
+ for (const [key, value] of Object.entries(schema)) {
30
+ if (CONSTRAINT_KEYS.has(key))
31
+ continue;
32
+ // Relax integer → number so Gemini is not forced into integer-only
33
+ // output; the stricter result schema still validates integrality.
34
+ if (key === 'type' && value === 'integer') {
35
+ result[key] = 'number';
36
+ continue;
37
+ }
38
+ if (Array.isArray(value)) {
39
+ result[key] = value.map((item) => typeof item === 'object' && item !== null && !Array.isArray(item)
40
+ ? stripJsonSchemaConstraints(item)
41
+ : item);
42
+ }
43
+ else if (typeof value === 'object' && value !== null) {
44
+ result[key] = stripJsonSchemaConstraints(value);
45
+ }
46
+ else {
47
+ result[key] = value;
48
+ }
49
+ }
50
+ return result;
51
+ }
@@ -0,0 +1,6 @@
1
+ import { EventEmitter } from 'node:events';
2
+ import { GoogleGenAI } from '@google/genai';
3
+ import type { GeminiStructuredRequest } from './types.js';
4
+ export declare const geminiEvents: EventEmitter<[never]>;
5
+ export declare function setClientForTesting(client: GoogleGenAI): void;
6
+ export declare function generateStructuredJson(request: GeminiStructuredRequest): Promise<unknown>;