@j0hanz/code-review-analyst-mcp 1.4.2 → 1.4.4

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.
@@ -4,7 +4,7 @@ import { EventEmitter } from 'node:events';
4
4
  import { performance } from 'node:perf_hooks';
5
5
  import { setTimeout as sleep } from 'node:timers/promises';
6
6
  import { debuglog } from 'node:util';
7
- import { GoogleGenAI, HarmBlockThreshold, HarmCategory } from '@google/genai';
7
+ import { FinishReason, GoogleGenAI, HarmBlockThreshold, HarmCategory, } from '@google/genai';
8
8
  import { createCachedEnvInt } from './env-config.js';
9
9
  import { getErrorMessage, RETRYABLE_UPSTREAM_ERROR_PATTERN } from './errors.js';
10
10
  // Lazy-cached: first call happens after parseCommandLineArgs() sets GEMINI_MODEL.
@@ -36,8 +36,8 @@ const DIGITS_ONLY_PATTERN = /^\d+$/;
36
36
  const SLEEP_UNREF_OPTIONS = { ref: false };
37
37
  const maxConcurrentCallsConfig = createCachedEnvInt('MAX_CONCURRENT_CALLS', 10);
38
38
  const concurrencyWaitMsConfig = createCachedEnvInt('MAX_CONCURRENT_CALLS_WAIT_MS', 2_000);
39
- const concurrencyPollMsConfig = createCachedEnvInt('MAX_CONCURRENT_CALLS_POLL_MS', 25);
40
39
  let activeCalls = 0;
40
+ const slotWaiters = [];
41
41
  const RETRYABLE_TRANSIENT_CODES = new Set([
42
42
  'RESOURCE_EXHAUSTED',
43
43
  'UNAVAILABLE',
@@ -370,14 +370,20 @@ async function executeAttempt(request, model, timeoutMs, attempt, onLog) {
370
370
  const startedAt = performance.now();
371
371
  const response = await generateContentWithTimeout(request, model, timeoutMs);
372
372
  const latencyMs = Math.round(performance.now() - startedAt);
373
+ const finishReason = response.candidates?.[0]?.finishReason;
373
374
  await emitGeminiLog(onLog, 'info', {
374
375
  event: 'gemini_call',
375
376
  details: {
376
377
  attempt,
377
378
  latencyMs,
379
+ finishReason: finishReason ?? null,
378
380
  usageMetadata: response.usageMetadata ?? null,
379
381
  },
380
382
  });
383
+ if (finishReason === FinishReason.MAX_TOKENS) {
384
+ const limit = request.maxOutputTokens ?? DEFAULT_MAX_OUTPUT_TOKENS;
385
+ throw new Error(`Response truncated: model output exceeds limit (maxOutputTokens=${formatNumber(limit)}). Increase maxOutputTokens or reduce prompt complexity.`);
386
+ }
381
387
  return parseStructuredResponse(response.text);
382
388
  }
383
389
  async function waitBeforeRetry(attempt, error, onLog, requestSignal) {
@@ -437,19 +443,62 @@ async function runWithRetries(request, model, timeoutMs, maxRetries, onLog) {
437
443
  function canRetryAttempt(attempt, maxRetries, error) {
438
444
  return attempt < maxRetries && shouldRetry(error);
439
445
  }
446
+ function tryWakeNextWaiter() {
447
+ const next = slotWaiters.shift();
448
+ if (next !== undefined) {
449
+ next();
450
+ }
451
+ }
440
452
  async function waitForConcurrencySlot(limit, requestSignal) {
453
+ if (activeCalls < limit) {
454
+ return;
455
+ }
456
+ if (requestSignal?.aborted) {
457
+ throw new Error('Gemini request was cancelled.');
458
+ }
441
459
  const waitLimitMs = concurrencyWaitMsConfig.get();
442
- const pollMs = concurrencyPollMsConfig.get();
443
- const deadline = performance.now() + waitLimitMs;
444
- while (activeCalls >= limit) {
445
- if (requestSignal?.aborted) {
446
- throw new Error('Gemini request was cancelled.');
447
- }
448
- if (performance.now() >= deadline) {
449
- throw new Error(formatConcurrencyLimitErrorMessage(limit, waitLimitMs));
460
+ return new Promise((resolve, reject) => {
461
+ let settled = false;
462
+ const waiter = () => {
463
+ if (settled)
464
+ return;
465
+ settled = true;
466
+ clearTimeout(deadlineTimer);
467
+ if (requestSignal) {
468
+ requestSignal.removeEventListener('abort', onAbort);
469
+ }
470
+ resolve();
471
+ };
472
+ slotWaiters.push(waiter);
473
+ const deadlineTimer = setTimeout(() => {
474
+ if (settled)
475
+ return;
476
+ settled = true;
477
+ const idx = slotWaiters.indexOf(waiter);
478
+ if (idx !== -1) {
479
+ slotWaiters.splice(idx, 1);
480
+ }
481
+ if (requestSignal) {
482
+ requestSignal.removeEventListener('abort', onAbort);
483
+ }
484
+ reject(new Error(formatConcurrencyLimitErrorMessage(limit, waitLimitMs)));
485
+ }, waitLimitMs);
486
+ deadlineTimer.unref();
487
+ const onAbort = () => {
488
+ if (settled)
489
+ return;
490
+ settled = true;
491
+ const idx = slotWaiters.indexOf(waiter);
492
+ if (idx !== -1) {
493
+ slotWaiters.splice(idx, 1);
494
+ }
495
+ clearTimeout(deadlineTimer);
496
+ reject(new Error('Gemini request was cancelled.'));
497
+ };
498
+ if (requestSignal) {
499
+ requestSignal.addEventListener('abort', onAbort, { once: true });
450
500
  }
451
- await sleep(pollMs, undefined, SLEEP_UNREF_OPTIONS);
452
- }
501
+ });
453
502
  }
454
503
  export async function generateStructuredJson(request) {
455
504
  const model = request.model ?? getDefaultModel();
@@ -464,5 +513,6 @@ export async function generateStructuredJson(request) {
464
513
  }
465
514
  finally {
466
515
  activeCalls -= 1;
516
+ tryWakeNextWaiter();
467
517
  }
468
518
  }
@@ -2,24 +2,39 @@
2
2
  export declare const FLASH_MODEL = "gemini-2.5-flash";
3
3
  /** High-capability model for deep reasoning, quality inspection, and reliable code generation. */
4
4
  export declare const PRO_MODEL = "gemini-2.5-pro";
5
- /** Thinking budget (tokens) for Flash model thinking tasks (test plans, search/replace). */
6
- export declare const FLASH_THINKING_BUDGET: 8192;
7
- /** Thinking budget (tokens) for Pro model deep-analysis tasks (code quality inspection). */
8
- export declare const PRO_THINKING_BUDGET: 16384;
5
+ /**
6
+ * Thinking budget (tokens) for Flash triage tools (impact, summary, API-breaking).
7
+ * Explicitly disabled (0) these are classification/extraction tasks that do not
8
+ * benefit from a reasoning chain. Avoids default dynamic-thinking overhead.
9
+ * Flash 2.5 range: 0–24_576.
10
+ */
11
+ export declare const FLASH_TRIAGE_THINKING_BUDGET: 0;
12
+ /** Thinking budget (tokens) for Flash analysis tasks (test plans, complexity). */
13
+ export declare const FLASH_THINKING_BUDGET: 16384;
14
+ /** Thinking budget (tokens) for Pro model deep-analysis tasks (quality, patches). */
15
+ export declare const PRO_THINKING_BUDGET: 24576;
9
16
  /** Output cap for Flash triage tools (impact, summary). */
10
- export declare const FLASH_TRIAGE_MAX_OUTPUT_TOKENS: 2048;
17
+ export declare const FLASH_TRIAGE_MAX_OUTPUT_TOKENS: 4096;
11
18
  /** Output cap for API breaking-change detection (migration guidance needs room). */
12
19
  export declare const FLASH_API_BREAKING_MAX_OUTPUT_TOKENS: 4096;
13
20
  /** Output cap for test-plan generation (includes pseudocode snippets). */
14
- export declare const FLASH_TEST_PLAN_MAX_OUTPUT_TOKENS: 4096;
21
+ export declare const FLASH_TEST_PLAN_MAX_OUTPUT_TOKENS: 8192;
15
22
  /** Output cap for Pro deep review findings. */
16
- export declare const PRO_REVIEW_MAX_OUTPUT_TOKENS: 8192;
23
+ export declare const PRO_REVIEW_MAX_OUTPUT_TOKENS: 12288;
17
24
  /** Output cap for Pro search/replace remediation blocks. */
18
- export declare const PRO_PATCH_MAX_OUTPUT_TOKENS: 4096;
25
+ export declare const PRO_PATCH_MAX_OUTPUT_TOKENS: 8192;
19
26
  /** Output cap for Flash complexity analysis reports. */
20
27
  export declare const FLASH_COMPLEXITY_MAX_OUTPUT_TOKENS: 2048;
21
28
  /** Extended timeout for Pro model calls (ms). Pro thinks longer than Flash. */
22
29
  export declare const DEFAULT_TIMEOUT_PRO_MS = 120000;
30
+ /** Temperature for triage/classification tools (deterministic structured extraction). */
31
+ export declare const TRIAGE_TEMPERATURE: 0.1;
32
+ /** Temperature for analytical tools (consistent algorithmic reasoning). */
33
+ export declare const ANALYSIS_TEMPERATURE: 0.1;
34
+ /** Temperature for code patch generation (maximum precision for search blocks). */
35
+ export declare const PATCH_TEMPERATURE: 0;
36
+ /** Temperature for creative synthesis tools (test plan generation). */
37
+ export declare const CREATIVE_TEMPERATURE: 0.2;
23
38
  export declare const MODEL_TIMEOUT_MS: {
24
39
  readonly defaultPro: 120000;
25
40
  };
@@ -3,21 +3,71 @@ export const FLASH_MODEL = 'gemini-2.5-flash';
3
3
  /** High-capability model for deep reasoning, quality inspection, and reliable code generation. */
4
4
  export const PRO_MODEL = 'gemini-2.5-pro';
5
5
  const THINKING_BUDGET_TOKENS = {
6
- flash: 8_192,
7
- pro: 16_384,
6
+ /**
7
+ * Disabled (0): triage/classification tasks need no reasoning chain.
8
+ * Flash 2.5 range: 0–24_576; 0 explicitly disables thinking.
9
+ */
10
+ flashTriage: 0,
11
+ /**
12
+ * Raised from 8_192 → half of Flash max (24_576).
13
+ * Used for analysis tasks (test plans, complexity) that benefit from
14
+ * multi-step reasoning but not from unbounded thinking tokens.
15
+ */
16
+ flash: 16_384,
17
+ /**
18
+ * Raised from 16_384 → 75 % of Pro max (32_768).
19
+ * Gives deep-review and patch-generation tools genuine headroom for
20
+ * complex multi-file diffs without switching to cost-unpredictable dynamic.
21
+ */
22
+ pro: 24_576,
8
23
  };
9
24
  const OUTPUT_TOKEN_BUDGET = {
10
- flashTriage: 2_048,
11
- flashTestPlan: 4_096,
25
+ flashTriage: 4_096,
26
+ /**
27
+ * Raised from 4_096: 15 test cases × pseudoCode@2_000 chars ≈ 7_500 tokens;
28
+ * staying at 4_096 risked MAX_TOKENS truncation on moderate test plans.
29
+ */
30
+ flashTestPlan: 8_192,
12
31
  flashApiBreaking: 4_096,
13
32
  flashComplexity: 2_048,
14
- proReview: 8_192,
15
- proPatch: 4_096,
33
+ /**
34
+ * Raised from 8_192: 25 findings × (title+explanation+recommendation) can
35
+ * exceed 8_192 tokens for rich, high-finding-count reviews.
36
+ */
37
+ proReview: 12_288,
38
+ /**
39
+ * Raised from 4_096: 10 search/replace blocks with multi-line code context
40
+ * can exceed the previous cap and cause MAX_TOKENS truncation.
41
+ */
42
+ proPatch: 8_192,
43
+ };
44
+ /**
45
+ * Per-task temperature presets for structured JSON generation.
46
+ * These are intentionally low: the model is already heavily constrained by
47
+ * the responseSchema, so lower temperatures improve schema-validation
48
+ * pass-through rates and reduce hallucinated field values.
49
+ */
50
+ const TOOL_TEMPERATURE = {
51
+ /** Triage/classification tasks — deterministic structured extraction. */
52
+ triage: 0.1,
53
+ /** Analytical reasoning — consistent algorithmic analysis. */
54
+ analysis: 0.1,
55
+ /** Code patch generation — maximum precision for exact-match search blocks. */
56
+ patch: 0.0,
57
+ /** Test plan generation — allow modest diversity in test-case synthesis. */
58
+ creative: 0.2,
16
59
  };
17
60
  const DEFAULT_DETECT_HINT = 'detect';
18
- /** Thinking budget (tokens) for Flash model thinking tasks (test plans, search/replace). */
61
+ /**
62
+ * Thinking budget (tokens) for Flash triage tools (impact, summary, API-breaking).
63
+ * Explicitly disabled (0) — these are classification/extraction tasks that do not
64
+ * benefit from a reasoning chain. Avoids default dynamic-thinking overhead.
65
+ * Flash 2.5 range: 0–24_576.
66
+ */
67
+ export const FLASH_TRIAGE_THINKING_BUDGET = THINKING_BUDGET_TOKENS.flashTriage;
68
+ /** Thinking budget (tokens) for Flash analysis tasks (test plans, complexity). */
19
69
  export const FLASH_THINKING_BUDGET = THINKING_BUDGET_TOKENS.flash;
20
- /** Thinking budget (tokens) for Pro model deep-analysis tasks (code quality inspection). */
70
+ /** Thinking budget (tokens) for Pro model deep-analysis tasks (quality, patches). */
21
71
  export const PRO_THINKING_BUDGET = THINKING_BUDGET_TOKENS.pro;
22
72
  /** Output cap for Flash triage tools (impact, summary). */
23
73
  export const FLASH_TRIAGE_MAX_OUTPUT_TOKENS = OUTPUT_TOKEN_BUDGET.flashTriage;
@@ -33,6 +83,17 @@ export const PRO_PATCH_MAX_OUTPUT_TOKENS = OUTPUT_TOKEN_BUDGET.proPatch;
33
83
  export const FLASH_COMPLEXITY_MAX_OUTPUT_TOKENS = OUTPUT_TOKEN_BUDGET.flashComplexity;
34
84
  /** Extended timeout for Pro model calls (ms). Pro thinks longer than Flash. */
35
85
  export const DEFAULT_TIMEOUT_PRO_MS = 120_000;
86
+ // ---------------------------------------------------------------------------
87
+ // Temperature presets — see TOOL_TEMPERATURE constant for rationale.
88
+ // ---------------------------------------------------------------------------
89
+ /** Temperature for triage/classification tools (deterministic structured extraction). */
90
+ export const TRIAGE_TEMPERATURE = TOOL_TEMPERATURE.triage;
91
+ /** Temperature for analytical tools (consistent algorithmic reasoning). */
92
+ export const ANALYSIS_TEMPERATURE = TOOL_TEMPERATURE.analysis;
93
+ /** Temperature for code patch generation (maximum precision for search blocks). */
94
+ export const PATCH_TEMPERATURE = TOOL_TEMPERATURE.patch;
95
+ /** Temperature for creative synthesis tools (test plan generation). */
96
+ export const CREATIVE_TEMPERATURE = TOOL_TEMPERATURE.creative;
36
97
  export const MODEL_TIMEOUT_MS = {
37
98
  defaultPro: DEFAULT_TIMEOUT_PRO_MS,
38
99
  };
@@ -1,3 +1,4 @@
1
+ export declare const INSPECTION_FOCUS_AREAS: readonly ["security", "correctness", "performance", "regressions", "tests", "maintainability", "concurrency"];
1
2
  export interface ToolParameterContract {
2
3
  name: string;
3
4
  type: string;
@@ -15,6 +16,13 @@ export interface ToolContract {
15
16
  thinkingBudget?: number;
16
17
  /** Set to 0 for synchronous (non-Gemini) tools. */
17
18
  maxOutputTokens: number;
19
+ /**
20
+ * Sampling temperature for the Gemini call.
21
+ * Lower values (0.0–0.1) favour deterministic structured output;
22
+ * higher values (0.2) add diversity for creative synthesis tasks.
23
+ * Omit to use the global default (0.2).
24
+ */
25
+ temperature?: number;
18
26
  params: readonly ToolParameterContract[];
19
27
  outputShape: string;
20
28
  gotchas: readonly string[];
@@ -42,7 +50,9 @@ export declare const TOOL_CONTRACTS: readonly [{
42
50
  readonly purpose: "Assess severity, categories, breaking changes, and rollback complexity.";
43
51
  readonly model: "gemini-2.5-flash";
44
52
  readonly timeoutMs: 90000;
45
- readonly maxOutputTokens: 2048;
53
+ readonly thinkingBudget: 0;
54
+ readonly maxOutputTokens: 4096;
55
+ readonly temperature: 0.1;
46
56
  readonly params: readonly [{
47
57
  readonly name: "repository";
48
58
  readonly type: "string";
@@ -64,7 +74,9 @@ export declare const TOOL_CONTRACTS: readonly [{
64
74
  readonly purpose: "Produce PR summary, risk rating, and merge recommendation.";
65
75
  readonly model: "gemini-2.5-flash";
66
76
  readonly timeoutMs: 90000;
67
- readonly maxOutputTokens: 2048;
77
+ readonly thinkingBudget: 0;
78
+ readonly maxOutputTokens: 4096;
79
+ readonly temperature: 0.1;
68
80
  readonly params: readonly [{
69
81
  readonly name: "repository";
70
82
  readonly type: "string";
@@ -86,8 +98,9 @@ export declare const TOOL_CONTRACTS: readonly [{
86
98
  readonly purpose: "Deep code review with optional full-file context.";
87
99
  readonly model: "gemini-2.5-pro";
88
100
  readonly timeoutMs: 120000;
89
- readonly thinkingBudget: 16384;
90
- readonly maxOutputTokens: 8192;
101
+ readonly thinkingBudget: 24576;
102
+ readonly maxOutputTokens: 12288;
103
+ readonly temperature: 0.1;
91
104
  readonly params: readonly [{
92
105
  readonly name: "repository";
93
106
  readonly type: "string";
@@ -105,7 +118,7 @@ export declare const TOOL_CONTRACTS: readonly [{
105
118
  readonly type: "string[]";
106
119
  readonly required: false;
107
120
  readonly constraints: "1-12 items, 2-80 chars each";
108
- readonly description: "Focused inspection categories.";
121
+ readonly description: `Focused inspection categories (e.g. ${string}).`;
109
122
  }, {
110
123
  readonly name: "maxFindings";
111
124
  readonly type: "number";
@@ -128,8 +141,9 @@ export declare const TOOL_CONTRACTS: readonly [{
128
141
  readonly purpose: "Generate verbatim search/replace fix blocks for one finding.";
129
142
  readonly model: "gemini-2.5-pro";
130
143
  readonly timeoutMs: 120000;
131
- readonly thinkingBudget: 16384;
132
- readonly maxOutputTokens: 4096;
144
+ readonly thinkingBudget: 24576;
145
+ readonly maxOutputTokens: 8192;
146
+ readonly temperature: 0;
133
147
  readonly params: readonly [{
134
148
  readonly name: "findingTitle";
135
149
  readonly type: "string";
@@ -152,8 +166,9 @@ export declare const TOOL_CONTRACTS: readonly [{
152
166
  readonly purpose: "Generate prioritized test cases and coverage guidance.";
153
167
  readonly model: "gemini-2.5-flash";
154
168
  readonly timeoutMs: 90000;
155
- readonly thinkingBudget: 8192;
156
- readonly maxOutputTokens: 4096;
169
+ readonly thinkingBudget: 16384;
170
+ readonly maxOutputTokens: 8192;
171
+ readonly temperature: 0.2;
157
172
  readonly params: readonly [{
158
173
  readonly name: "repository";
159
174
  readonly type: "string";
@@ -187,8 +202,9 @@ export declare const TOOL_CONTRACTS: readonly [{
187
202
  readonly purpose: "Analyze Big-O complexity and detect degradations in changed code.";
188
203
  readonly model: "gemini-2.5-flash";
189
204
  readonly timeoutMs: 90000;
190
- readonly thinkingBudget: 8192;
205
+ readonly thinkingBudget: 16384;
191
206
  readonly maxOutputTokens: 2048;
207
+ readonly temperature: 0.1;
192
208
  readonly params: readonly [{
193
209
  readonly name: "language";
194
210
  readonly type: "string";
@@ -204,7 +220,9 @@ export declare const TOOL_CONTRACTS: readonly [{
204
220
  readonly purpose: "Detect breaking API/interface changes in a diff.";
205
221
  readonly model: "gemini-2.5-flash";
206
222
  readonly timeoutMs: 90000;
223
+ readonly thinkingBudget: 0;
207
224
  readonly maxOutputTokens: 4096;
225
+ readonly temperature: 0.1;
208
226
  readonly params: readonly [{
209
227
  readonly name: "language";
210
228
  readonly type: "string";
@@ -1,5 +1,14 @@
1
- import { DEFAULT_TIMEOUT_PRO_MS, FLASH_API_BREAKING_MAX_OUTPUT_TOKENS, FLASH_COMPLEXITY_MAX_OUTPUT_TOKENS, FLASH_MODEL, FLASH_TEST_PLAN_MAX_OUTPUT_TOKENS, FLASH_THINKING_BUDGET, FLASH_TRIAGE_MAX_OUTPUT_TOKENS, PRO_MODEL, PRO_PATCH_MAX_OUTPUT_TOKENS, PRO_REVIEW_MAX_OUTPUT_TOKENS, PRO_THINKING_BUDGET, } from './model-config.js';
1
+ import { ANALYSIS_TEMPERATURE, CREATIVE_TEMPERATURE, DEFAULT_TIMEOUT_PRO_MS, FLASH_API_BREAKING_MAX_OUTPUT_TOKENS, FLASH_COMPLEXITY_MAX_OUTPUT_TOKENS, FLASH_MODEL, FLASH_TEST_PLAN_MAX_OUTPUT_TOKENS, FLASH_THINKING_BUDGET, FLASH_TRIAGE_MAX_OUTPUT_TOKENS, FLASH_TRIAGE_THINKING_BUDGET, PATCH_TEMPERATURE, PRO_MODEL, PRO_PATCH_MAX_OUTPUT_TOKENS, PRO_REVIEW_MAX_OUTPUT_TOKENS, PRO_THINKING_BUDGET, TRIAGE_TEMPERATURE, } from './model-config.js';
2
2
  const DEFAULT_TIMEOUT_FLASH_MS = 90_000;
3
+ export const INSPECTION_FOCUS_AREAS = [
4
+ 'security',
5
+ 'correctness',
6
+ 'performance',
7
+ 'regressions',
8
+ 'tests',
9
+ 'maintainability',
10
+ 'concurrency',
11
+ ];
3
12
  export const TOOL_CONTRACTS = [
4
13
  {
5
14
  name: 'generate_diff',
@@ -31,7 +40,9 @@ export const TOOL_CONTRACTS = [
31
40
  purpose: 'Assess severity, categories, breaking changes, and rollback complexity.',
32
41
  model: FLASH_MODEL,
33
42
  timeoutMs: DEFAULT_TIMEOUT_FLASH_MS,
43
+ thinkingBudget: FLASH_TRIAGE_THINKING_BUDGET,
34
44
  maxOutputTokens: FLASH_TRIAGE_MAX_OUTPUT_TOKENS,
45
+ temperature: TRIAGE_TEMPERATURE,
35
46
  params: [
36
47
  {
37
48
  name: 'repository',
@@ -62,7 +73,9 @@ export const TOOL_CONTRACTS = [
62
73
  purpose: 'Produce PR summary, risk rating, and merge recommendation.',
63
74
  model: FLASH_MODEL,
64
75
  timeoutMs: DEFAULT_TIMEOUT_FLASH_MS,
76
+ thinkingBudget: FLASH_TRIAGE_THINKING_BUDGET,
65
77
  maxOutputTokens: FLASH_TRIAGE_MAX_OUTPUT_TOKENS,
78
+ temperature: TRIAGE_TEMPERATURE,
66
79
  params: [
67
80
  {
68
81
  name: 'repository',
@@ -95,6 +108,7 @@ export const TOOL_CONTRACTS = [
95
108
  timeoutMs: DEFAULT_TIMEOUT_PRO_MS,
96
109
  thinkingBudget: PRO_THINKING_BUDGET,
97
110
  maxOutputTokens: PRO_REVIEW_MAX_OUTPUT_TOKENS,
111
+ temperature: ANALYSIS_TEMPERATURE,
98
112
  params: [
99
113
  {
100
114
  name: 'repository',
@@ -115,7 +129,7 @@ export const TOOL_CONTRACTS = [
115
129
  type: 'string[]',
116
130
  required: false,
117
131
  constraints: '1-12 items, 2-80 chars each',
118
- description: 'Focused inspection categories.',
132
+ description: `Focused inspection categories (e.g. ${INSPECTION_FOCUS_AREAS.join(', ')}).`,
119
133
  },
120
134
  {
121
135
  name: 'maxFindings',
@@ -151,6 +165,7 @@ export const TOOL_CONTRACTS = [
151
165
  timeoutMs: DEFAULT_TIMEOUT_PRO_MS,
152
166
  thinkingBudget: PRO_THINKING_BUDGET,
153
167
  maxOutputTokens: PRO_PATCH_MAX_OUTPUT_TOKENS,
168
+ temperature: PATCH_TEMPERATURE,
154
169
  params: [
155
170
  {
156
171
  name: 'findingTitle',
@@ -185,6 +200,7 @@ export const TOOL_CONTRACTS = [
185
200
  timeoutMs: DEFAULT_TIMEOUT_FLASH_MS,
186
201
  thinkingBudget: FLASH_THINKING_BUDGET,
187
202
  maxOutputTokens: FLASH_TEST_PLAN_MAX_OUTPUT_TOKENS,
203
+ temperature: CREATIVE_TEMPERATURE,
188
204
  params: [
189
205
  {
190
206
  name: 'repository',
@@ -231,6 +247,7 @@ export const TOOL_CONTRACTS = [
231
247
  timeoutMs: DEFAULT_TIMEOUT_FLASH_MS,
232
248
  thinkingBudget: FLASH_THINKING_BUDGET,
233
249
  maxOutputTokens: FLASH_COMPLEXITY_MAX_OUTPUT_TOKENS,
250
+ temperature: ANALYSIS_TEMPERATURE,
234
251
  params: [
235
252
  {
236
253
  name: 'language',
@@ -252,7 +269,9 @@ export const TOOL_CONTRACTS = [
252
269
  purpose: 'Detect breaking API/interface changes in a diff.',
253
270
  model: FLASH_MODEL,
254
271
  timeoutMs: DEFAULT_TIMEOUT_FLASH_MS,
272
+ thinkingBudget: FLASH_TRIAGE_THINKING_BUDGET,
255
273
  maxOutputTokens: FLASH_API_BREAKING_MAX_OUTPUT_TOKENS,
274
+ temperature: TRIAGE_TEMPERATURE,
256
275
  params: [
257
276
  {
258
277
  name: 'language',
@@ -63,6 +63,13 @@ export interface StructuredToolTaskConfig<TInput extends object = Record<string,
63
63
  timeoutMs?: number;
64
64
  /** Optional max output tokens for Gemini. */
65
65
  maxOutputTokens?: number;
66
+ /**
67
+ * Optional sampling temperature for this tool's Gemini call.
68
+ * Lower values (0.0–0.1) favour determinism for structured extraction;
69
+ * higher values (0.2) add useful diversity for creative synthesis tasks.
70
+ * Falls back to the global default (0.2) when omitted.
71
+ */
72
+ temperature?: number;
66
73
  /** Optional opt-in to Gemini thought output. Defaults to false. */
67
74
  includeThoughts?: boolean;
68
75
  /** Optional formatter for human-readable text output. */
@@ -40,6 +40,9 @@ function createGenerationRequest(config, promptParts, responseSchema, onLog, sig
40
40
  if (config.maxOutputTokens !== undefined) {
41
41
  request.maxOutputTokens = config.maxOutputTokens;
42
42
  }
43
+ if (config.temperature !== undefined) {
44
+ request.temperature = config.temperature;
45
+ }
43
46
  if (config.includeThoughts !== undefined) {
44
47
  request.includeThoughts = config.includeThoughts;
45
48
  }
@@ -1,2 +1,11 @@
1
1
  import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ export declare const PROMPT_DEFINITIONS: readonly [{
3
+ readonly name: "get-help";
4
+ readonly title: "Get Help";
5
+ readonly description: "Server instructions.";
6
+ }, {
7
+ readonly name: "review-guide";
8
+ readonly title: "Review Guide";
9
+ readonly description: "Workflow guide for tool/focus area.";
10
+ }];
2
11
  export declare function registerAllPrompts(server: McpServer, instructions: string): void;
@@ -1,20 +1,19 @@
1
1
  import { completable } from '@modelcontextprotocol/sdk/server/completable.js';
2
2
  import { z } from 'zod';
3
- import { getToolContract, getToolContractNames, } from '../lib/tool-contracts.js';
4
- const HELP_PROMPT_NAME = 'get-help';
5
- const HELP_PROMPT_TITLE = 'Get Help';
6
- const HELP_PROMPT_DESCRIPTION = 'Server instructions.';
7
- const REVIEW_GUIDE_PROMPT_NAME = 'review-guide';
8
- const REVIEW_GUIDE_PROMPT_TITLE = 'Review Guide';
9
- const REVIEW_GUIDE_PROMPT_DESCRIPTION = 'Workflow guide for tool/focus area.';
10
- const TOOLS = getToolContractNames();
11
- const FOCUS_AREAS = [
12
- 'security',
13
- 'correctness',
14
- 'performance',
15
- 'regressions',
16
- 'tests',
3
+ import { getToolContract, getToolContractNames, INSPECTION_FOCUS_AREAS, } from '../lib/tool-contracts.js';
4
+ export const PROMPT_DEFINITIONS = [
5
+ {
6
+ name: 'get-help',
7
+ title: 'Get Help',
8
+ description: 'Server instructions.',
9
+ },
10
+ {
11
+ name: 'review-guide',
12
+ title: 'Review Guide',
13
+ description: 'Workflow guide for tool/focus area.',
14
+ },
17
15
  ];
16
+ const TOOLS = getToolContractNames();
18
17
  const TOOL_DESCRIPTION_TEXT = 'Select tool for review guide.';
19
18
  const FOCUS_DESCRIPTION_TEXT = 'Select focus area.';
20
19
  const FOCUS_AREA_GUIDES = {
@@ -23,6 +22,8 @@ const FOCUS_AREA_GUIDES = {
23
22
  performance: 'Focus: Complexity, allocations, I/O, queries.',
24
23
  regressions: 'Focus: Behavior changes, guards, types, breaks.',
25
24
  tests: 'Focus: Coverage, edge cases, flakes, error paths.',
25
+ maintainability: 'Focus: Complexity, readability, structure, patterns.',
26
+ concurrency: 'Focus: Race conditions, deadlocks, shared state.',
26
27
  };
27
28
  function completeByPrefix(values, prefix) {
28
29
  const matches = [];
@@ -52,11 +53,12 @@ function getFocusAreaGuide(focusArea) {
52
53
  return getGuide(FOCUS_AREA_GUIDES, focusArea, (area) => `Focus on ${area} concerns.`);
53
54
  }
54
55
  function registerHelpPrompt(server, instructions) {
55
- server.registerPrompt(HELP_PROMPT_NAME, {
56
- title: HELP_PROMPT_TITLE,
57
- description: HELP_PROMPT_DESCRIPTION,
56
+ const def = PROMPT_DEFINITIONS[0];
57
+ server.registerPrompt(def.name, {
58
+ title: def.title,
59
+ description: def.description,
58
60
  }, () => ({
59
- description: HELP_PROMPT_DESCRIPTION,
61
+ description: def.description,
60
62
  messages: [
61
63
  {
62
64
  role: 'user',
@@ -85,12 +87,13 @@ function buildReviewGuideText(tool, focusArea) {
85
87
  `> Tip: Run \`get-help\` for full server documentation.`);
86
88
  }
87
89
  function registerReviewGuidePrompt(server) {
88
- server.registerPrompt(REVIEW_GUIDE_PROMPT_NAME, {
89
- title: REVIEW_GUIDE_PROMPT_TITLE,
90
- description: REVIEW_GUIDE_PROMPT_DESCRIPTION,
90
+ const def = PROMPT_DEFINITIONS[1];
91
+ server.registerPrompt(def.name, {
92
+ title: def.title,
93
+ description: def.description,
91
94
  argsSchema: {
92
95
  tool: completable(z.string().describe(TOOL_DESCRIPTION_TEXT), (value) => completeByPrefix(TOOLS, value)),
93
- focusArea: completable(z.string().describe(FOCUS_DESCRIPTION_TEXT), (value) => completeByPrefix(FOCUS_AREAS, value)),
96
+ focusArea: completable(z.string().describe(FOCUS_DESCRIPTION_TEXT), (value) => completeByPrefix(INSPECTION_FOCUS_AREAS, value)),
94
97
  },
95
98
  }, ({ tool, focusArea }) => ({
96
99
  description: `Code review guide: ${tool} / ${focusArea}`,
@@ -1,2 +1,12 @@
1
1
  import { type McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ export interface StaticResourceDef {
3
+ id: string;
4
+ uri: string;
5
+ title: string;
6
+ description: string;
7
+ priority: number;
8
+ content: () => string;
9
+ }
10
+ export declare const STATIC_RESOURCES: readonly StaticResourceDef[];
11
+ export declare const DIFF_RESOURCE_DESCRIPTION = "The most recently generated diff, cached by generate_diff. Read by all review tools automatically.";
2
12
  export declare function registerAllResources(server: McpServer, instructions: string): void;
@@ -9,7 +9,7 @@ const RESOURCE_AUDIENCE = ['assistant'];
9
9
  function createMarkdownContent(uri, text) {
10
10
  return { uri: uri.href, mimeType: RESOURCE_MIME_TYPE, text };
11
11
  }
12
- const STATIC_RESOURCES = [
12
+ export const STATIC_RESOURCES = [
13
13
  {
14
14
  id: 'server-instructions',
15
15
  uri: 'internal://instructions',
@@ -77,10 +77,11 @@ function registerToolInfoResources(server) {
77
77
  return { contents: [createMarkdownContent(uri, text)] };
78
78
  });
79
79
  }
80
+ export const DIFF_RESOURCE_DESCRIPTION = 'The most recently generated diff, cached by generate_diff. Read by all review tools automatically.';
80
81
  function registerDiffResource(server) {
81
82
  server.registerResource('diff-current', new ResourceTemplate(DIFF_RESOURCE_URI, { list: undefined }), {
82
83
  title: 'Current Diff',
83
- description: 'The most recently generated diff, cached by generate_diff. Read by all review tools automatically.',
84
+ description: DIFF_RESOURCE_DESCRIPTION,
84
85
  mimeType: 'text/x-patch',
85
86
  annotations: {
86
87
  audience: ['assistant'],
@@ -1,16 +1,12 @@
1
1
  import { getToolContracts } from '../lib/tool-contracts.js';
2
+ import { PROMPT_DEFINITIONS } from '../prompts/index.js';
3
+ import { DIFF_RESOURCE_DESCRIPTION, STATIC_RESOURCES } from './index.js';
2
4
  import { getSharedConstraints } from './tool-info.js';
3
- const PROMPT_LIST = [
4
- '- `get-help`: Returns these server instructions.',
5
- '- `review-guide`: Workflow guide for a selected tool and focus area.',
6
- ];
5
+ const PROMPT_LIST = PROMPT_DEFINITIONS.map((def) => `- \`${def.name}\`: ${def.description}`);
7
6
  const RESOURCE_LIST = [
8
- '- `internal://instructions`: This document.',
9
- '- `internal://tool-catalog`: Tool matrix and cross-tool data flow.',
10
- '- `internal://workflows`: Recommended multi-step tool workflows.',
11
- '- `internal://server-config`: Runtime limits and model configuration.',
7
+ ...STATIC_RESOURCES.map((def) => `- \`${def.uri}\`: ${def.description}`),
12
8
  '- `internal://tool-info/{toolName}`: Per-tool contract details.',
13
- '- `diff://current`: Cached diff from the most recent generate_diff run.',
9
+ `- \`diff://current\`: ${DIFF_RESOURCE_DESCRIPTION}`,
14
10
  ];
15
11
  function formatParameterLine(parameter) {
16
12
  const required = parameter.required ? 'required' : 'optional';
@@ -51,7 +51,7 @@ export const InspectCodeQualityInputSchema = z.strictObject({
51
51
  .min(1)
52
52
  .max(INPUT_LIMITS.focusArea.maxItems)
53
53
  .optional()
54
- .describe('Review focus areas. Standard tags: security, performance, correctness, maintainability, concurrency. Omit for general review.'),
54
+ .describe('Review focus areas. Standard tags: security, correctness, performance, regressions, tests, maintainability, concurrency. Omit for general review.'),
55
55
  maxFindings: createOptionalBoundedInteger(INPUT_LIMITS.maxFindings.min, INPUT_LIMITS.maxFindings.max, 'Max findings (1-25). Default: 10.'),
56
56
  files: z
57
57
  .array(FileContextSchema)
@@ -26,6 +26,9 @@ export function registerAnalyzeComplexityTool(server) {
26
26
  ...(TOOL_CONTRACT.thinkingBudget !== undefined
27
27
  ? { thinkingBudget: TOOL_CONTRACT.thinkingBudget }
28
28
  : undefined),
29
+ ...(TOOL_CONTRACT.temperature !== undefined
30
+ ? { temperature: TOOL_CONTRACT.temperature }
31
+ : undefined),
29
32
  validateInput: (_input, ctx) => {
30
33
  const slot = ctx.diffSlot;
31
34
  if (!slot)
@@ -27,6 +27,12 @@ export function registerAnalyzePrImpactTool(server) {
27
27
  model: TOOL_CONTRACT.model,
28
28
  timeoutMs: TOOL_CONTRACT.timeoutMs,
29
29
  maxOutputTokens: TOOL_CONTRACT.maxOutputTokens,
30
+ ...(TOOL_CONTRACT.thinkingBudget !== undefined
31
+ ? { thinkingBudget: TOOL_CONTRACT.thinkingBudget }
32
+ : undefined),
33
+ ...(TOOL_CONTRACT.temperature !== undefined
34
+ ? { temperature: TOOL_CONTRACT.temperature }
35
+ : undefined),
30
36
  validateInput: (_input, ctx) => {
31
37
  const slot = ctx.diffSlot;
32
38
  if (!slot)
@@ -23,6 +23,12 @@ export function registerDetectApiBreakingTool(server) {
23
23
  model: TOOL_CONTRACT.model,
24
24
  timeoutMs: TOOL_CONTRACT.timeoutMs,
25
25
  maxOutputTokens: TOOL_CONTRACT.maxOutputTokens,
26
+ ...(TOOL_CONTRACT.thinkingBudget !== undefined
27
+ ? { thinkingBudget: TOOL_CONTRACT.thinkingBudget }
28
+ : undefined),
29
+ ...(TOOL_CONTRACT.temperature !== undefined
30
+ ? { temperature: TOOL_CONTRACT.temperature }
31
+ : undefined),
26
32
  validateInput: (_input, ctx) => {
27
33
  const slot = ctx.diffSlot;
28
34
  if (!slot)
@@ -34,6 +34,12 @@ export function registerGenerateReviewSummaryTool(server) {
34
34
  model: TOOL_CONTRACT.model,
35
35
  timeoutMs: TOOL_CONTRACT.timeoutMs,
36
36
  maxOutputTokens: TOOL_CONTRACT.maxOutputTokens,
37
+ ...(TOOL_CONTRACT.thinkingBudget !== undefined
38
+ ? { thinkingBudget: TOOL_CONTRACT.thinkingBudget }
39
+ : undefined),
40
+ ...(TOOL_CONTRACT.temperature !== undefined
41
+ ? { temperature: TOOL_CONTRACT.temperature }
42
+ : undefined),
37
43
  validateInput: (_input, ctx) => {
38
44
  const slot = ctx.diffSlot;
39
45
  if (!slot)
@@ -30,6 +30,9 @@ export function registerGenerateTestPlanTool(server) {
30
30
  ...(TOOL_CONTRACT.thinkingBudget !== undefined
31
31
  ? { thinkingBudget: TOOL_CONTRACT.thinkingBudget }
32
32
  : undefined),
33
+ ...(TOOL_CONTRACT.temperature !== undefined
34
+ ? { temperature: TOOL_CONTRACT.temperature }
35
+ : undefined),
33
36
  validateInput: (_input, ctx) => {
34
37
  const slot = ctx.diffSlot;
35
38
  if (!slot)
@@ -66,6 +66,9 @@ export function registerInspectCodeQualityTool(server) {
66
66
  ...(TOOL_CONTRACT.thinkingBudget !== undefined
67
67
  ? { thinkingBudget: TOOL_CONTRACT.thinkingBudget }
68
68
  : undefined),
69
+ ...(TOOL_CONTRACT.temperature !== undefined
70
+ ? { temperature: TOOL_CONTRACT.temperature }
71
+ : undefined),
69
72
  progressContext: (input) => {
70
73
  const fileCount = input.files?.length;
71
74
  return fileCount ? `+${fileCount} files` : '';
@@ -30,6 +30,9 @@ export function registerSuggestSearchReplaceTool(server) {
30
30
  ...(TOOL_CONTRACT.thinkingBudget !== undefined
31
31
  ? { thinkingBudget: TOOL_CONTRACT.thinkingBudget }
32
32
  : undefined),
33
+ ...(TOOL_CONTRACT.temperature !== undefined
34
+ ? { temperature: TOOL_CONTRACT.temperature }
35
+ : undefined),
33
36
  validateInput: (_input, ctx) => {
34
37
  const slot = ctx.diffSlot;
35
38
  if (!slot)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@j0hanz/code-review-analyst-mcp",
3
- "version": "1.4.2",
3
+ "version": "1.4.4",
4
4
  "mcpName": "io.github.j0hanz/code-review-analyst",
5
5
  "description": "Gemini-powered MCP server for code review analysis.",
6
6
  "type": "module",