@j0hanz/code-review-analyst-mcp 1.6.0 → 1.6.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/diff-cleaner.js +20 -16
- package/dist/lib/diff-store.js +13 -1
- package/dist/lib/tool-factory.js +44 -9
- package/dist/lib/tool-response.d.ts +3 -1
- package/dist/lib/tool-response.js +4 -1
- package/dist/prompts/index.js +7 -7
- package/dist/resources/instructions.js +5 -0
- package/dist/tools/analyze-complexity.js +2 -2
- package/dist/tools/analyze-pr-impact.js +2 -1
- package/dist/tools/detect-api-breaking.js +3 -3
- package/dist/tools/generate-review-summary.js +2 -1
- package/dist/tools/generate-test-plan.js +4 -3
- package/dist/tools/inspect-code-quality.js +1 -1
- package/dist/tools/suggest-search-replace.js +3 -2
- package/package.json +1 -1
package/dist/lib/diff-cleaner.js
CHANGED
|
@@ -33,6 +33,14 @@ function shouldKeepSection(section) {
|
|
|
33
33
|
}
|
|
34
34
|
return true;
|
|
35
35
|
}
|
|
36
|
+
function processSection(raw, start, end, sections) {
|
|
37
|
+
if (end > start) {
|
|
38
|
+
const section = raw.slice(start, end);
|
|
39
|
+
if (shouldKeepSection(section)) {
|
|
40
|
+
sections.push(section);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
36
44
|
/**
|
|
37
45
|
* Split raw unified diff into per-file sections and strip:
|
|
38
46
|
* - Binary file sections ("Binary files a/... and b/... differ")
|
|
@@ -46,25 +54,21 @@ export function cleanDiff(raw) {
|
|
|
46
54
|
if (!raw)
|
|
47
55
|
return '';
|
|
48
56
|
const sections = [];
|
|
49
|
-
const delimiter = /^diff --git /gm;
|
|
50
57
|
let lastIndex = 0;
|
|
51
|
-
let
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
lastIndex = match.index;
|
|
58
|
+
let nextIndex = raw.startsWith('diff --git ')
|
|
59
|
+
? 0
|
|
60
|
+
: raw.indexOf('\ndiff --git ');
|
|
61
|
+
if (nextIndex === -1) {
|
|
62
|
+
processSection(raw, 0, raw.length, sections);
|
|
63
|
+
return sections.join('').trim();
|
|
60
64
|
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
}
|
|
65
|
+
while (nextIndex !== -1) {
|
|
66
|
+
const matchIndex = nextIndex === 0 ? 0 : nextIndex + 1; // +1 to skip \n
|
|
67
|
+
processSection(raw, lastIndex, matchIndex, sections);
|
|
68
|
+
lastIndex = matchIndex;
|
|
69
|
+
nextIndex = raw.indexOf('\ndiff --git ', lastIndex);
|
|
67
70
|
}
|
|
71
|
+
processSection(raw, lastIndex, raw.length, sections);
|
|
68
72
|
return sections.join('').trim();
|
|
69
73
|
}
|
|
70
74
|
export function isEmptyDiff(diff) {
|
package/dist/lib/diff-store.js
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
|
+
import { createCachedEnvInt } from './env-config.js';
|
|
1
2
|
import { createErrorToolResponse } from './tool-response.js';
|
|
2
3
|
export const DIFF_RESOURCE_URI = 'diff://current';
|
|
4
|
+
const diffCacheTtlMs = createCachedEnvInt('DIFF_CACHE_TTL_MS', 60 * 60 * 1_000 // 1 hour default
|
|
5
|
+
);
|
|
3
6
|
const diffSlots = new Map();
|
|
4
7
|
let sendResourceUpdated;
|
|
5
8
|
function setDiffSlot(key, data) {
|
|
@@ -29,7 +32,16 @@ export function storeDiff(data, key = process.cwd()) {
|
|
|
29
32
|
notifyDiffUpdated();
|
|
30
33
|
}
|
|
31
34
|
export function getDiff(key = process.cwd()) {
|
|
32
|
-
|
|
35
|
+
const slot = diffSlots.get(key);
|
|
36
|
+
if (!slot) {
|
|
37
|
+
return undefined;
|
|
38
|
+
}
|
|
39
|
+
const age = Date.now() - new Date(slot.generatedAt).getTime();
|
|
40
|
+
if (age > diffCacheTtlMs.get()) {
|
|
41
|
+
diffSlots.delete(key);
|
|
42
|
+
return undefined;
|
|
43
|
+
}
|
|
44
|
+
return slot;
|
|
33
45
|
}
|
|
34
46
|
export function hasDiff(key = process.cwd()) {
|
|
35
47
|
return diffSlots.has(key);
|
package/dist/lib/tool-factory.js
CHANGED
|
@@ -8,7 +8,14 @@ import { stripJsonSchemaConstraints } from './gemini-schema.js';
|
|
|
8
8
|
import { generateStructuredJson, getCurrentRequestId } from './gemini.js';
|
|
9
9
|
import { createErrorToolResponse, createToolResponse, } from './tool-response.js';
|
|
10
10
|
const DEFAULT_TASK_TTL_MS = 30 * 60 * 1_000;
|
|
11
|
-
|
|
11
|
+
// Named progress step indices for 7-step progress (0–6).
|
|
12
|
+
const STEP_STARTING = 0;
|
|
13
|
+
const STEP_VALIDATING = 1;
|
|
14
|
+
const STEP_BUILDING_PROMPT = 2;
|
|
15
|
+
const STEP_CALLING_MODEL = 3;
|
|
16
|
+
const STEP_VALIDATING_RESPONSE = 4;
|
|
17
|
+
const STEP_FINALIZING = 5;
|
|
18
|
+
const TASK_PROGRESS_TOTAL = STEP_FINALIZING + 1;
|
|
12
19
|
const INPUT_VALIDATION_FAILED = 'Input validation failed';
|
|
13
20
|
const DEFAULT_PROGRESS_CONTEXT = 'request';
|
|
14
21
|
const CANCELLED_ERROR_PATTERN = /cancelled|canceled/i;
|
|
@@ -240,7 +247,7 @@ async function reportProgressCompletionUpdate(reportProgress, toolName, context,
|
|
|
240
247
|
}
|
|
241
248
|
async function reportSchemaRetryProgressBestEffort(reportProgress, toolName, context, retryCount, maxRetries) {
|
|
242
249
|
try {
|
|
243
|
-
await reportProgressStepUpdate(reportProgress, toolName, context,
|
|
250
|
+
await reportProgressStepUpdate(reportProgress, toolName, context, STEP_VALIDATING_RESPONSE, `schema repair ${retryCount}/${maxRetries}`);
|
|
244
251
|
}
|
|
245
252
|
catch {
|
|
246
253
|
// Progress updates are best-effort and must not interrupt retries.
|
|
@@ -281,7 +288,15 @@ function createGeminiLogger(server, taskId) {
|
|
|
281
288
|
});
|
|
282
289
|
}
|
|
283
290
|
catch {
|
|
284
|
-
|
|
291
|
+
try {
|
|
292
|
+
const timestamp = new Date().toISOString();
|
|
293
|
+
const payload = JSON.stringify(asObjectRecord(data));
|
|
294
|
+
console.error(`[${timestamp}] [gemini:${level}] ${taskId} - ${payload}`);
|
|
295
|
+
}
|
|
296
|
+
catch {
|
|
297
|
+
// Safe fallback if JSON stringify fails
|
|
298
|
+
console.error(`[gemini:${level}] ${taskId} - (logging failed)`);
|
|
299
|
+
}
|
|
285
300
|
}
|
|
286
301
|
};
|
|
287
302
|
}
|
|
@@ -387,8 +402,19 @@ export class ToolTaskRunner {
|
|
|
387
402
|
async executeValidation(inputRecord, ctx) {
|
|
388
403
|
const validationError = await validateRequest(this.config, inputRecord, ctx);
|
|
389
404
|
if (validationError) {
|
|
390
|
-
|
|
391
|
-
|
|
405
|
+
let validationMessage = INPUT_VALIDATION_FAILED;
|
|
406
|
+
try {
|
|
407
|
+
const text = validationError.content[0]?.text;
|
|
408
|
+
if (text) {
|
|
409
|
+
const parsed = JSON.parse(text);
|
|
410
|
+
if (parsed.error?.message) {
|
|
411
|
+
validationMessage = parsed.error.message;
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
catch {
|
|
416
|
+
// fallback to default
|
|
417
|
+
}
|
|
392
418
|
await this.updateStatusMessage(validationMessage);
|
|
393
419
|
await reportProgressCompletionUpdate(this.reportProgress, this.config.name, this.progressContext, 'rejected');
|
|
394
420
|
await this.storeResultSafely('completed', validationError);
|
|
@@ -404,7 +430,8 @@ export class ToolTaskRunner {
|
|
|
404
430
|
try {
|
|
405
431
|
const raw = await generateStructuredJson(createGenerationRequest(this.config, { systemInstruction, prompt: retryPrompt }, this.responseSchema, this.onLog, this.extra.signal));
|
|
406
432
|
if (attempt === 0) {
|
|
407
|
-
await
|
|
433
|
+
await this.updateStatusMessage('validating response');
|
|
434
|
+
await reportProgressStepUpdate(this.reportProgress, this.config.name, this.progressContext, STEP_VALIDATING_RESPONSE, 'validating response');
|
|
408
435
|
}
|
|
409
436
|
parsed = this.config.resultSchema.parse(raw);
|
|
410
437
|
break;
|
|
@@ -441,16 +468,23 @@ export class ToolTaskRunner {
|
|
|
441
468
|
const ctx = {
|
|
442
469
|
diffSlot: this.hasSnapshot ? this.diffSlotSnapshot : getDiff(),
|
|
443
470
|
};
|
|
444
|
-
await reportProgressStepUpdate(this.reportProgress, this.config.name, this.progressContext,
|
|
471
|
+
await reportProgressStepUpdate(this.reportProgress, this.config.name, this.progressContext, STEP_STARTING, 'starting');
|
|
472
|
+
await this.updateStatusMessage('starting');
|
|
473
|
+
await reportProgressStepUpdate(this.reportProgress, this.config.name, this.progressContext, STEP_VALIDATING, 'validating input');
|
|
474
|
+
await this.updateStatusMessage('validating input');
|
|
445
475
|
if (!(await this.executeValidation(inputRecord, ctx))) {
|
|
446
476
|
return;
|
|
447
477
|
}
|
|
448
|
-
await reportProgressStepUpdate(this.reportProgress, this.config.name, this.progressContext,
|
|
478
|
+
await reportProgressStepUpdate(this.reportProgress, this.config.name, this.progressContext, STEP_BUILDING_PROMPT, 'building prompt');
|
|
479
|
+
await this.updateStatusMessage('building prompt');
|
|
449
480
|
const promptParts = this.config.buildPrompt(inputRecord, ctx);
|
|
450
481
|
const { prompt, systemInstruction } = promptParts;
|
|
451
482
|
const modelLabel = friendlyModelName(this.config.model);
|
|
452
|
-
await reportProgressStepUpdate(this.reportProgress, this.config.name, this.progressContext,
|
|
483
|
+
await reportProgressStepUpdate(this.reportProgress, this.config.name, this.progressContext, STEP_CALLING_MODEL, modelLabel);
|
|
484
|
+
await this.updateStatusMessage(modelLabel);
|
|
453
485
|
const parsed = await this.executeModelCall(systemInstruction, prompt);
|
|
486
|
+
await reportProgressStepUpdate(this.reportProgress, this.config.name, this.progressContext, STEP_FINALIZING, 'finalizing');
|
|
487
|
+
await this.updateStatusMessage('finalizing');
|
|
454
488
|
const finalResult = (this.config.transformResult
|
|
455
489
|
? this.config.transformResult(inputRecord, parsed, ctx)
|
|
456
490
|
: parsed);
|
|
@@ -459,6 +493,7 @@ export class ToolTaskRunner {
|
|
|
459
493
|
: undefined;
|
|
460
494
|
const outcome = this.config.formatOutcome?.(finalResult) ?? 'completed';
|
|
461
495
|
await reportProgressCompletionUpdate(this.reportProgress, this.config.name, this.progressContext, outcome);
|
|
496
|
+
await this.updateStatusMessage(`completed: ${outcome}`);
|
|
462
497
|
await this.storeResultSafely('completed', createToolResponse({
|
|
463
498
|
ok: true,
|
|
464
499
|
result: finalResult,
|
|
@@ -24,7 +24,9 @@ interface ToolResponse<TStructuredContent extends ToolStructuredContent> {
|
|
|
24
24
|
content: ToolTextContent[];
|
|
25
25
|
structuredContent: TStructuredContent;
|
|
26
26
|
}
|
|
27
|
-
interface ErrorToolResponse
|
|
27
|
+
interface ErrorToolResponse {
|
|
28
|
+
[key: string]: unknown;
|
|
29
|
+
content: ToolTextContent[];
|
|
28
30
|
isError: true;
|
|
29
31
|
}
|
|
30
32
|
export declare function createToolResponse<TStructuredContent extends ToolStructuredContent>(structured: TStructuredContent, textContent?: string): ToolResponse<TStructuredContent>;
|
|
@@ -29,5 +29,8 @@ export function createToolResponse(structured, textContent) {
|
|
|
29
29
|
}
|
|
30
30
|
export function createErrorToolResponse(code, message, result, meta) {
|
|
31
31
|
const structured = createErrorStructuredContent(code, message, result, meta);
|
|
32
|
-
return {
|
|
32
|
+
return {
|
|
33
|
+
content: toTextContent(structured),
|
|
34
|
+
isError: true,
|
|
35
|
+
};
|
|
33
36
|
}
|
package/dist/prompts/index.js
CHANGED
|
@@ -17,13 +17,13 @@ const TOOLS = getToolContractNames();
|
|
|
17
17
|
const TOOL_DESCRIPTION_TEXT = 'Select tool for review guide.';
|
|
18
18
|
const FOCUS_DESCRIPTION_TEXT = 'Select focus area.';
|
|
19
19
|
const FOCUS_AREA_GUIDES = {
|
|
20
|
-
security: 'Focus: Injection, auth, crypto, OWASP.',
|
|
21
|
-
correctness: 'Focus: Logic, edge cases,
|
|
22
|
-
performance: 'Focus:
|
|
23
|
-
regressions: 'Focus: Behavior changes, guards,
|
|
24
|
-
tests: 'Focus:
|
|
25
|
-
maintainability: 'Focus:
|
|
26
|
-
concurrency: 'Focus: Race conditions, deadlocks,
|
|
20
|
+
security: 'Focus: Injection (SQL/XSS), auth, crypto, OWASP Top 10.',
|
|
21
|
+
correctness: 'Focus: Logic errors, edge cases, algorithm validity, type safety.',
|
|
22
|
+
performance: 'Focus: Big-O complexity, memory allocations, I/O latency, N+1 queries.',
|
|
23
|
+
regressions: 'Focus: Behavior changes, missing guards, breaking API changes.',
|
|
24
|
+
tests: 'Focus: Missing coverage, flaky tests, error paths.',
|
|
25
|
+
maintainability: 'Focus: Code complexity, readability, DRY violations, patterns.',
|
|
26
|
+
concurrency: 'Focus: Race conditions, deadlocks, lack of atomicity.',
|
|
27
27
|
};
|
|
28
28
|
function isFocusArea(value) {
|
|
29
29
|
return INSPECTION_FOCUS_AREAS.includes(value);
|
|
@@ -60,6 +60,11 @@ ${toolSections.join('\n\n')}
|
|
|
60
60
|
|
|
61
61
|
## CONSTRAINTS
|
|
62
62
|
${constraintLines.join('\n')}
|
|
63
|
+
|
|
64
|
+
## TASK LIFECYCLE
|
|
65
|
+
- Progress steps (0–6): starting → validating input → building prompt → calling model → validating response → finalizing → done.
|
|
66
|
+
- Status messages update at each phase for task introspection.
|
|
67
|
+
- Schema repair: on validation failure, retries with error feedback (configurable via \`GEMINI_SCHEMA_RETRIES\`).
|
|
63
68
|
- Task terminal states: \`completed\` and \`failed\`; cancellations are surfaced as \`failed\` with \`error.kind=cancelled\`.
|
|
64
69
|
`;
|
|
65
70
|
}
|
|
@@ -6,7 +6,7 @@ const SYSTEM_INSTRUCTION = `
|
|
|
6
6
|
Algorithm Complexity Analyst.
|
|
7
7
|
Analyze Big-O time/space complexity for changes.
|
|
8
8
|
Detect performance degradation vs original.
|
|
9
|
-
Identify bottlenecks
|
|
9
|
+
Identify bottlenecks: nested loops, unbounded recursion, heavy allocations.
|
|
10
10
|
Return strict JSON.
|
|
11
11
|
`;
|
|
12
12
|
const TOOL_CONTRACT = requireToolContract('analyze_time_space_complexity');
|
|
@@ -35,7 +35,7 @@ export function registerAnalyzeComplexityTool(server) {
|
|
|
35
35
|
: '';
|
|
36
36
|
return {
|
|
37
37
|
systemInstruction: SYSTEM_INSTRUCTION,
|
|
38
|
-
prompt: `${languageLine}
|
|
38
|
+
prompt: `${languageLine}\nDiff:\n${diff}\n\nBased on the diff above, analyze the Big-O time and space complexity.`.trimStart(),
|
|
39
39
|
};
|
|
40
40
|
},
|
|
41
41
|
});
|
|
@@ -5,7 +5,7 @@ import { AnalyzePrImpactInputSchema } from '../schemas/inputs.js';
|
|
|
5
5
|
import { PrImpactResultSchema } from '../schemas/outputs.js';
|
|
6
6
|
const SYSTEM_INSTRUCTION = `
|
|
7
7
|
Technical Change Analyst.
|
|
8
|
-
|
|
8
|
+
Assess objective impact: severity, risk categories, breaking changes, rollback cost.
|
|
9
9
|
Strictly diff-based; no inference.
|
|
10
10
|
Return strict JSON.
|
|
11
11
|
`;
|
|
@@ -27,6 +27,7 @@ export function registerAnalyzePrImpactTool(server) {
|
|
|
27
27
|
maxOutputTokens: TOOL_CONTRACT.maxOutputTokens,
|
|
28
28
|
...buildStructuredToolRuntimeOptions(TOOL_CONTRACT),
|
|
29
29
|
requiresDiff: true,
|
|
30
|
+
progressContext: (input) => input.repository,
|
|
30
31
|
formatOutcome: (result) => `severity: ${result.severity}`,
|
|
31
32
|
formatOutput: (result) => `[${result.severity}] ${result.summary}`,
|
|
32
33
|
buildPrompt: (input, ctx) => {
|
|
@@ -5,8 +5,8 @@ import { DetectApiBreakingResultSchema } from '../schemas/outputs.js';
|
|
|
5
5
|
const SYSTEM_INSTRUCTION = `
|
|
6
6
|
API Compatibility Analyst.
|
|
7
7
|
Detect breaking changes to public APIs/contracts/interfaces.
|
|
8
|
-
Breaking = consumer
|
|
9
|
-
|
|
8
|
+
Definition: Breaking change = requires consumer code modification.
|
|
9
|
+
Output: element, nature, impact, mitigation.
|
|
10
10
|
Return strict JSON.
|
|
11
11
|
`;
|
|
12
12
|
const TOOL_CONTRACT = requireToolContract('detect_api_breaking_changes');
|
|
@@ -35,7 +35,7 @@ export function registerDetectApiBreakingTool(server) {
|
|
|
35
35
|
: '';
|
|
36
36
|
return {
|
|
37
37
|
systemInstruction: SYSTEM_INSTRUCTION,
|
|
38
|
-
prompt: `${languageLine}
|
|
38
|
+
prompt: `${languageLine}\nDiff:\n${diff}\n\nBased on the diff above, detect any breaking API changes.`.trimStart(),
|
|
39
39
|
};
|
|
40
40
|
},
|
|
41
41
|
});
|
|
@@ -9,7 +9,7 @@ const TOOL_CONTRACT = requireToolContract('generate_review_summary');
|
|
|
9
9
|
const SYSTEM_INSTRUCTION = `
|
|
10
10
|
Senior Code Reviewer.
|
|
11
11
|
Summarize PR: risk, key changes, merge recommendation (merge/squash/block).
|
|
12
|
-
|
|
12
|
+
Focus: Logic/behavior changes. Ignore: formatting/style/typos.
|
|
13
13
|
Return strict JSON.
|
|
14
14
|
`;
|
|
15
15
|
function formatLanguageSegment(language) {
|
|
@@ -41,6 +41,7 @@ export function registerGenerateReviewSummaryTool(server) {
|
|
|
41
41
|
maxOutputTokens: TOOL_CONTRACT.maxOutputTokens,
|
|
42
42
|
...buildStructuredToolRuntimeOptions(TOOL_CONTRACT),
|
|
43
43
|
requiresDiff: true,
|
|
44
|
+
progressContext: (input) => input.repository,
|
|
44
45
|
formatOutcome: (result) => `risk: ${result.overallRisk}`,
|
|
45
46
|
transformResult: (_input, result, ctx) => {
|
|
46
47
|
const { files, added, deleted } = getDiffStats(ctx);
|
|
@@ -5,9 +5,9 @@ import { GenerateTestPlanInputSchema } from '../schemas/inputs.js';
|
|
|
5
5
|
import { TestPlanResultSchema } from '../schemas/outputs.js';
|
|
6
6
|
const SYSTEM_INSTRUCTION = `
|
|
7
7
|
QA Automation Architect.
|
|
8
|
-
|
|
9
|
-
Prioritize: negative, edge,
|
|
10
|
-
|
|
8
|
+
Generate test plan for diff.
|
|
9
|
+
Prioritize: negative cases, edge cases, branch coverage, integration points.
|
|
10
|
+
Focus: observable behavior only.
|
|
11
11
|
Return strict JSON.
|
|
12
12
|
`;
|
|
13
13
|
const TOOL_CONTRACT = requireToolContract('generate_test_plan');
|
|
@@ -28,6 +28,7 @@ export function registerGenerateTestPlanTool(server) {
|
|
|
28
28
|
maxOutputTokens: TOOL_CONTRACT.maxOutputTokens,
|
|
29
29
|
...buildStructuredToolRuntimeOptions(TOOL_CONTRACT),
|
|
30
30
|
requiresDiff: true,
|
|
31
|
+
progressContext: (input) => input.repository,
|
|
31
32
|
formatOutcome: (result) => `${result.testCases.length} test cases`,
|
|
32
33
|
formatOutput: (result) => `${result.summary}\n${result.testCases.length} test cases.`,
|
|
33
34
|
transformResult: (input, result) => {
|
|
@@ -18,7 +18,7 @@ const SYSTEM_INSTRUCTION = `
|
|
|
18
18
|
Principal Engineer Code Review.
|
|
19
19
|
Source: Unified diff (primary), File excerpts (supplementary context).
|
|
20
20
|
Goal: Identify bugs, security, performance, maintainability.
|
|
21
|
-
Ignore style. Prioritize correctness/failure modes.
|
|
21
|
+
Constraint: Ignore style/formatting. Prioritize correctness/failure modes.
|
|
22
22
|
Return strict JSON.
|
|
23
23
|
`;
|
|
24
24
|
const TOOL_CONTRACT = requireToolContract('inspect_code_quality');
|
|
@@ -6,8 +6,8 @@ import { SearchReplaceResultSchema } from '../schemas/outputs.js';
|
|
|
6
6
|
const SYSTEM_INSTRUCTION = `
|
|
7
7
|
Code Remediation Expert.
|
|
8
8
|
Generate minimal search/replace blocks for described issue.
|
|
9
|
-
|
|
10
|
-
No
|
|
9
|
+
Constraint: 'search' must be verbatim (exact whitespace/indentation).
|
|
10
|
+
Constraint: No context drift. Omit patch if exact match uncertain.
|
|
11
11
|
Return strict JSON.
|
|
12
12
|
`;
|
|
13
13
|
const TOOL_CONTRACT = requireToolContract('suggest_search_replace');
|
|
@@ -28,6 +28,7 @@ export function registerSuggestSearchReplaceTool(server) {
|
|
|
28
28
|
maxOutputTokens: TOOL_CONTRACT.maxOutputTokens,
|
|
29
29
|
...buildStructuredToolRuntimeOptions(TOOL_CONTRACT),
|
|
30
30
|
requiresDiff: true,
|
|
31
|
+
progressContext: (input) => input.findingTitle,
|
|
31
32
|
formatOutcome: (result) => formatPatchCount(result.blocks.length),
|
|
32
33
|
formatOutput: (result) => {
|
|
33
34
|
const count = result.blocks.length;
|