@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.
- package/dist/lib/gemini.js +4 -1
- package/dist/lib/tool-factory.d.ts +2 -0
- package/dist/lib/tool-factory.js +57 -19
- package/dist/tools/analyze-pr-impact.js +1 -1
- package/dist/tools/generate-review-summary.js +1 -1
- package/dist/tools/generate-test-plan.js +1 -1
- package/dist/tools/inspect-code-quality.js +5 -1
- package/dist/tools/suggest-search-replace.js +4 -1
- package/package.json +1 -1
package/dist/lib/gemini.js
CHANGED
|
@@ -125,7 +125,10 @@ function getApiKey() {
|
|
|
125
125
|
return apiKey;
|
|
126
126
|
}
|
|
127
127
|
function getClient() {
|
|
128
|
-
cachedClient ??= new GoogleGenAI({
|
|
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
|
}
|
package/dist/lib/tool-factory.js
CHANGED
|
@@ -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
|
|
147
|
-
|
|
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, '
|
|
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, '
|
|
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, '
|
|
263
|
+
message: formatProgressStep(config.name, progressContext, 'preparing'),
|
|
255
264
|
});
|
|
256
|
-
const
|
|
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,
|
|
272
|
+
message: formatProgressStep(config.name, progressContext, `awaiting ${modelLabel}`),
|
|
261
273
|
});
|
|
262
|
-
const
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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) =>
|
|
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
|
-
|
|
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
|
},
|