@j0hanz/code-review-analyst-mcp 1.2.0 → 1.2.1

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.
@@ -125,7 +125,10 @@ function getApiKey() {
125
125
  return apiKey;
126
126
  }
127
127
  function getClient() {
128
- cachedClient ??= new GoogleGenAI({ apiKey: getApiKey() });
128
+ cachedClient ??= new GoogleGenAI({
129
+ apiKey: getApiKey(),
130
+ apiVersion: 'v1beta',
131
+ });
129
132
  return cachedClient;
130
133
  }
131
134
  export function setClientForTesting(client) {
@@ -37,6 +37,8 @@ export interface StructuredToolTaskConfig<TInput extends object = Record<string,
37
37
  formatOutput?: (result: TFinal) => string;
38
38
  /** Optional context text used in progress messages. */
39
39
  progressContext?: (input: TInput) => string;
40
+ /** Optional short outcome suffix for the completion progress message (e.g., "3 findings"). */
41
+ formatOutcome?: (result: TFinal) => string;
40
42
  /** Builds the system instruction and user prompt from parsed tool input. */
41
43
  buildPrompt: (input: TInput) => PromptParts;
42
44
  }
@@ -143,8 +143,17 @@ function formatProgressStep(toolName, context, metadata) {
143
143
  const prefix = metadata === 'start' ? '▸' : '◆';
144
144
  return `${prefix} ${toolName}: ${context} [${metadata}]`;
145
145
  }
146
- function formatProgressCompletion(toolName, context, outcome) {
147
- const prefix = outcome === 'completed' ? '◈' : '‣';
146
+ function friendlyModelName(model) {
147
+ if (!model)
148
+ return 'model';
149
+ if (model.includes('pro'))
150
+ return 'Pro';
151
+ if (model.includes('flash'))
152
+ return 'Flash';
153
+ return 'model';
154
+ }
155
+ function formatProgressCompletion(toolName, context, outcome, success = true) {
156
+ const prefix = success ? '◈' : '‣';
148
157
  return `${prefix} ${toolName}: ${context} • ${outcome}`;
149
158
  }
150
159
  function toLoggingLevel(level) {
@@ -225,14 +234,14 @@ export function registerStructuredToolTask(server, config) {
225
234
  }
226
235
  };
227
236
  try {
237
+ const onLog = createGeminiLogger(server, task.taskId);
238
+ const inputRecord = parseToolInput(input, config.fullInputSchema);
239
+ progressContext = normalizeProgressContext(config.progressContext?.(inputRecord));
228
240
  await reportProgress({
229
241
  current: 0,
230
242
  total: TASK_PROGRESS_TOTAL,
231
- message: formatProgressStep(config.name, progressContext, 'start'),
243
+ message: formatProgressStep(config.name, progressContext, 'starting'),
232
244
  });
233
- const onLog = createGeminiLogger(server, task.taskId);
234
- const inputRecord = parseToolInput(input, config.fullInputSchema);
235
- progressContext = normalizeProgressContext(config.progressContext?.(inputRecord));
236
245
  if (config.validateInput) {
237
246
  const validationError = await config.validateInput(inputRecord);
238
247
  if (validationError) {
@@ -242,7 +251,7 @@ export function registerStructuredToolTask(server, config) {
242
251
  await reportProgress({
243
252
  current: TASK_PROGRESS_TOTAL,
244
253
  total: TASK_PROGRESS_TOTAL,
245
- message: formatProgressCompletion(config.name, progressContext, 'failed'),
254
+ message: formatProgressCompletion(config.name, progressContext, 'rejected', false),
246
255
  });
247
256
  await storeResultSafely('completed', validationError);
248
257
  return;
@@ -251,31 +260,60 @@ export function registerStructuredToolTask(server, config) {
251
260
  await reportProgress({
252
261
  current: 1,
253
262
  total: TASK_PROGRESS_TOTAL,
254
- message: formatProgressStep(config.name, progressContext, 'input validated'),
263
+ message: formatProgressStep(config.name, progressContext, 'preparing'),
255
264
  });
256
- const { systemInstruction, prompt } = config.buildPrompt(inputRecord);
265
+ const promptParts = config.buildPrompt(inputRecord);
266
+ let { prompt } = promptParts;
267
+ const { systemInstruction } = promptParts;
268
+ const modelLabel = friendlyModelName(config.model);
257
269
  await reportProgress({
258
270
  current: 2,
259
271
  total: TASK_PROGRESS_TOTAL,
260
- message: formatProgressStep(config.name, progressContext, 'prompt prepared'),
272
+ message: formatProgressStep(config.name, progressContext, `awaiting ${modelLabel}`),
261
273
  });
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);
274
+ const MAX_SCHEMA_RETRIES = 1;
275
+ let raw;
276
+ let parsed;
277
+ for (let attempt = 0; attempt <= MAX_SCHEMA_RETRIES; attempt += 1) {
278
+ try {
279
+ raw = await generateStructuredJson(createGenerationRequest(config, { systemInstruction, prompt }, responseSchema, onLog, extra.signal));
280
+ if (attempt === 0) {
281
+ await reportProgress({
282
+ current: 3,
283
+ total: TASK_PROGRESS_TOTAL,
284
+ message: formatProgressStep(config.name, progressContext, 'processing response'),
285
+ });
286
+ }
287
+ parsed = config.resultSchema.parse(raw);
288
+ break;
289
+ }
290
+ catch (error) {
291
+ if (attempt >= MAX_SCHEMA_RETRIES ||
292
+ !(error instanceof z.ZodError)) {
293
+ throw error;
294
+ }
295
+ const errorMessage = getErrorMessage(error);
296
+ await onLog('warning', {
297
+ event: 'schema_validation_failed',
298
+ details: { attempt, error: errorMessage },
299
+ });
300
+ prompt += `\n\nCRITICAL: The previous response was invalid JSON schema. Error: ${errorMessage}. Please fix and return valid JSON matching the schema.`;
301
+ }
302
+ }
303
+ if (!parsed) {
304
+ throw new Error('Unexpected state: parsed result is undefined');
305
+ }
269
306
  const finalResult = (config.transformResult
270
307
  ? config.transformResult(inputRecord, parsed)
271
308
  : parsed);
272
309
  const textContent = config.formatOutput
273
310
  ? config.formatOutput(finalResult)
274
311
  : undefined;
312
+ const outcome = config.formatOutcome?.(finalResult) ?? 'completed';
275
313
  await reportProgress({
276
314
  current: TASK_PROGRESS_TOTAL,
277
315
  total: TASK_PROGRESS_TOTAL,
278
- message: formatProgressCompletion(config.name, progressContext, 'completed'),
316
+ message: formatProgressCompletion(config.name, progressContext, outcome),
279
317
  });
280
318
  await storeResultSafely('completed', createToolResponse({
281
319
  ok: true,
@@ -288,7 +326,7 @@ export function registerStructuredToolTask(server, config) {
288
326
  await reportProgress({
289
327
  current: TASK_PROGRESS_TOTAL,
290
328
  total: TASK_PROGRESS_TOTAL,
291
- message: formatProgressCompletion(config.name, progressContext, 'failed'),
329
+ message: formatProgressCompletion(config.name, progressContext, 'failed', false),
292
330
  });
293
331
  await updateStatusMessage(errorMessage);
294
332
  await storeResultSafely('failed', createErrorToolResponse(config.errorCode, errorMessage, undefined, errorMeta));
@@ -22,7 +22,7 @@ export function registerAnalyzePrImpactTool(server) {
22
22
  errorCode: 'E_ANALYZE_IMPACT',
23
23
  model: FLASH_MODEL,
24
24
  validateInput: (input) => validateDiffBudget(input.diff),
25
- progressContext: (input) => `repo: ${input.repository}, lang: ${input.language ?? DEFAULT_LANGUAGE}`,
25
+ formatOutcome: (result) => `severity: ${result.severity}`,
26
26
  formatOutput: (result) => {
27
27
  return `Impact Analysis (${result.severity}): ${result.summary}`;
28
28
  },
@@ -35,7 +35,7 @@ export function registerGenerateReviewSummaryTool(server) {
35
35
  errorCode: 'E_REVIEW_SUMMARY',
36
36
  model: FLASH_MODEL,
37
37
  validateInput: (input) => validateDiffBudget(input.diff),
38
- progressContext: (input) => `repo: ${input.repository}, lang: ${input.language ?? DEFAULT_LANGUAGE}`,
38
+ formatOutcome: (result) => `risk: ${result.overallRisk}`,
39
39
  transformResult: (input, result) => {
40
40
  const stats = getCachedStats(input);
41
41
  statsCache.delete(input);
@@ -24,7 +24,7 @@ export function registerGenerateTestPlanTool(server) {
24
24
  model: FLASH_MODEL,
25
25
  thinkingBudget: FLASH_THINKING_BUDGET,
26
26
  validateInput: (input) => validateDiffBudget(input.diff),
27
- progressContext: (input) => `repo: ${input.repository}, framework: ${input.testFramework ?? DEFAULT_TEST_FRAMEWORK}, max-cases: ${input.maxTestCases ?? DEFAULT_MAX_TEST_CASES}`,
27
+ formatOutcome: (result) => `${result.testCases.length} test cases`,
28
28
  formatOutput: (result) => {
29
29
  return `Test Plan: ${result.summary}\n${result.testCases.length} cases proposed.`;
30
30
  },
@@ -65,7 +65,11 @@ export function registerInspectCodeQualityTool(server) {
65
65
  model: PRO_MODEL,
66
66
  thinkingBudget: PRO_THINKING_BUDGET,
67
67
  timeoutMs: DEFAULT_TIMEOUT_PRO_MS,
68
- progressContext: (input) => `repo: ${input.repository}, focus: ${input.focusAreas?.length ?? 0}, file-context: ${input.files?.length ?? 0}, max-findings: ${input.maxFindings ?? DEFAULT_MAX_FINDINGS}`,
68
+ progressContext: (input) => {
69
+ const fileCount = input.files?.length;
70
+ return fileCount ? `+${fileCount} files` : '';
71
+ },
72
+ formatOutcome: (result) => `${result.findings.length} findings, risk: ${result.overallRisk}`,
69
73
  validateInput: (input) => {
70
74
  const diffError = validateDiffBudget(input.diff);
71
75
  if (diffError)
@@ -25,7 +25,10 @@ export function registerSuggestSearchReplaceTool(server) {
25
25
  thinkingBudget: PRO_THINKING_BUDGET,
26
26
  timeoutMs: DEFAULT_TIMEOUT_PRO_MS,
27
27
  validateInput: (input) => validateDiffBudget(input.diff),
28
- progressContext: (input) => `finding: ${input.findingTitle}, details: ${input.findingDetails.length} chars`,
28
+ formatOutcome: (result) => {
29
+ const count = result.blocks.length;
30
+ return `${count} ${count === 1 ? 'patch' : 'patches'}`;
31
+ },
29
32
  formatOutput: (result) => {
30
33
  return `Search/Replace Suggestion: ${result.summary}`;
31
34
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@j0hanz/code-review-analyst-mcp",
3
- "version": "1.2.0",
3
+ "version": "1.2.1",
4
4
  "mcpName": "io.github.j0hanz/code-review-analyst",
5
5
  "description": "Gemini-powered MCP server for code review analysis.",
6
6
  "type": "module",