@j0hanz/code-assistant 0.9.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 +437 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +72 -0
- package/dist/lib/concurrency.d.ts +13 -0
- package/dist/lib/concurrency.js +77 -0
- package/dist/lib/config.d.ts +43 -0
- package/dist/lib/config.js +87 -0
- package/dist/lib/diff.d.ts +49 -0
- package/dist/lib/diff.js +241 -0
- package/dist/lib/errors.d.ts +8 -0
- package/dist/lib/errors.js +69 -0
- package/dist/lib/format.d.ts +14 -0
- package/dist/lib/format.js +33 -0
- package/dist/lib/gemini.d.ts +45 -0
- package/dist/lib/gemini.js +833 -0
- package/dist/lib/progress.d.ts +72 -0
- package/dist/lib/progress.js +204 -0
- package/dist/lib/tools.d.ts +274 -0
- package/dist/lib/tools.js +646 -0
- package/dist/prompts/index.d.ts +11 -0
- package/dist/prompts/index.js +96 -0
- package/dist/resources/index.d.ts +12 -0
- package/dist/resources/index.js +115 -0
- package/dist/resources/instructions.d.ts +1 -0
- package/dist/resources/instructions.js +71 -0
- package/dist/resources/server-config.d.ts +1 -0
- package/dist/resources/server-config.js +75 -0
- package/dist/resources/tool-catalog.d.ts +1 -0
- package/dist/resources/tool-catalog.js +30 -0
- package/dist/resources/tool-info.d.ts +5 -0
- package/dist/resources/tool-info.js +105 -0
- package/dist/resources/workflows.d.ts +1 -0
- package/dist/resources/workflows.js +59 -0
- package/dist/schemas/inputs.d.ts +21 -0
- package/dist/schemas/inputs.js +46 -0
- package/dist/schemas/outputs.d.ts +121 -0
- package/dist/schemas/outputs.js +162 -0
- package/dist/server.d.ts +6 -0
- package/dist/server.js +88 -0
- package/dist/tools/analyze-complexity.d.ts +2 -0
- package/dist/tools/analyze-complexity.js +50 -0
- package/dist/tools/analyze-pr-impact.d.ts +2 -0
- package/dist/tools/analyze-pr-impact.js +62 -0
- package/dist/tools/detect-api-breaking.d.ts +2 -0
- package/dist/tools/detect-api-breaking.js +49 -0
- package/dist/tools/generate-diff.d.ts +2 -0
- package/dist/tools/generate-diff.js +140 -0
- package/dist/tools/generate-review-summary.d.ts +2 -0
- package/dist/tools/generate-review-summary.js +71 -0
- package/dist/tools/generate-test-plan.d.ts +2 -0
- package/dist/tools/generate-test-plan.js +67 -0
- package/dist/tools/index.d.ts +2 -0
- package/dist/tools/index.js +19 -0
- package/package.json +79 -0
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { completable } from '@modelcontextprotocol/sdk/server/completable.js';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { toInlineCode } from '../lib/format.js';
|
|
4
|
+
import { getToolContract, getToolContractNames, INSPECTION_FOCUS_AREAS, } from '../lib/tools.js';
|
|
5
|
+
export const PROMPT_DEFINITIONS = [
|
|
6
|
+
{
|
|
7
|
+
name: 'get-help',
|
|
8
|
+
title: 'Get Help',
|
|
9
|
+
description: 'Server instructions.',
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
name: 'review-guide',
|
|
13
|
+
title: 'Review Guide',
|
|
14
|
+
description: 'Workflow guide for tool/focus area.',
|
|
15
|
+
},
|
|
16
|
+
];
|
|
17
|
+
const TOOLS = getToolContractNames();
|
|
18
|
+
const TOOL_DESCRIPTION_TEXT = 'Select tool for review guide.';
|
|
19
|
+
const FOCUS_DESCRIPTION_TEXT = 'Select focus area.';
|
|
20
|
+
const FOCUS_AREA_GUIDES = {
|
|
21
|
+
security: 'Focus: Injection, Auth, Crypto, OWASP.',
|
|
22
|
+
correctness: 'Focus: Logic, Edge Cases, Types.',
|
|
23
|
+
performance: 'Focus: Complexity, Memory, Latency.',
|
|
24
|
+
regressions: 'Focus: Behavior Changes, Breaking APIs.',
|
|
25
|
+
tests: 'Focus: Coverage, Error Paths.',
|
|
26
|
+
maintainability: 'Focus: Complexity, Readability, Patterns.',
|
|
27
|
+
concurrency: 'Focus: Races, Deadlocks, Atomicity.',
|
|
28
|
+
};
|
|
29
|
+
function isFocusArea(value) {
|
|
30
|
+
return INSPECTION_FOCUS_AREAS.includes(value);
|
|
31
|
+
}
|
|
32
|
+
function completeByPrefix(values, prefix) {
|
|
33
|
+
return values.filter((value) => value.startsWith(prefix));
|
|
34
|
+
}
|
|
35
|
+
function getToolGuide(tool) {
|
|
36
|
+
const contract = getToolContract(tool);
|
|
37
|
+
if (!contract) {
|
|
38
|
+
return `Use ${toInlineCode(tool)} to analyze your code changes.`;
|
|
39
|
+
}
|
|
40
|
+
const modelLine = buildToolModelLine(contract);
|
|
41
|
+
return `Tool: ${contract.name}\n${modelLine}\nOutput: ${contract.outputShape}\nUse: ${contract.purpose}`;
|
|
42
|
+
}
|
|
43
|
+
function buildToolModelLine(contract) {
|
|
44
|
+
if (contract.thinkingLevel !== undefined) {
|
|
45
|
+
return `Model: ${contract.model} (thinking level ${contract.thinkingLevel}, output cap ${contract.maxOutputTokens}).`;
|
|
46
|
+
}
|
|
47
|
+
return `Model: ${contract.model} (output cap ${contract.maxOutputTokens}).`;
|
|
48
|
+
}
|
|
49
|
+
function createPromptResponse(description, text) {
|
|
50
|
+
return {
|
|
51
|
+
description,
|
|
52
|
+
messages: [
|
|
53
|
+
{
|
|
54
|
+
role: 'user',
|
|
55
|
+
content: { type: 'text', text },
|
|
56
|
+
},
|
|
57
|
+
],
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
function getFocusAreaGuide(focusArea) {
|
|
61
|
+
return isFocusArea(focusArea)
|
|
62
|
+
? FOCUS_AREA_GUIDES[focusArea]
|
|
63
|
+
: `Focus on ${focusArea} concerns.`;
|
|
64
|
+
}
|
|
65
|
+
function registerHelpPrompt(server, instructions) {
|
|
66
|
+
const def = PROMPT_DEFINITIONS[0];
|
|
67
|
+
server.registerPrompt(def.name, {
|
|
68
|
+
title: def.title,
|
|
69
|
+
description: def.description,
|
|
70
|
+
}, () => createPromptResponse(def.description, instructions));
|
|
71
|
+
}
|
|
72
|
+
function buildReviewGuideText(tool, focusArea) {
|
|
73
|
+
const toolCode = toInlineCode(tool);
|
|
74
|
+
return (`# Guide: ${tool} / ${focusArea}\n\n` +
|
|
75
|
+
`## Tool: ${toolCode}\n${getToolGuide(tool)}\n\n` +
|
|
76
|
+
`## Focus: ${focusArea}\n${getFocusAreaGuide(focusArea)}`);
|
|
77
|
+
}
|
|
78
|
+
function registerReviewGuidePrompt(server) {
|
|
79
|
+
const def = PROMPT_DEFINITIONS[1];
|
|
80
|
+
server.registerPrompt(def.name, {
|
|
81
|
+
title: def.title,
|
|
82
|
+
description: def.description,
|
|
83
|
+
argsSchema: {
|
|
84
|
+
tool: completable(z.string().optional().describe(TOOL_DESCRIPTION_TEXT), (value) => completeByPrefix(TOOLS, value ?? '')),
|
|
85
|
+
focusArea: completable(z.string().optional().describe(FOCUS_DESCRIPTION_TEXT), (value) => completeByPrefix(INSPECTION_FOCUS_AREAS, value ?? '')),
|
|
86
|
+
},
|
|
87
|
+
}, (args) => {
|
|
88
|
+
const selectedTool = args.tool ?? TOOLS[0] ?? 'analyze_pr_impact';
|
|
89
|
+
const selectedFocus = args.focusArea ?? INSPECTION_FOCUS_AREAS[0];
|
|
90
|
+
return createPromptResponse(`Code review guide: ${selectedTool} / ${selectedFocus}`, buildReviewGuideText(selectedTool, selectedFocus));
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
export function registerAllPrompts(server, instructions) {
|
|
94
|
+
registerHelpPrompt(server, instructions);
|
|
95
|
+
registerReviewGuidePrompt(server);
|
|
96
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
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.";
|
|
12
|
+
export declare function registerAllResources(server: McpServer, instructions: string): void;
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { ResourceTemplate, } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
import { DIFF_RESOURCE_URI, getDiff } from '../lib/diff.js';
|
|
3
|
+
import { buildServerConfig } from './server-config.js';
|
|
4
|
+
import { buildToolCatalog } from './tool-catalog.js';
|
|
5
|
+
import { getToolInfo, getToolInfoNames } from './tool-info.js';
|
|
6
|
+
import { buildWorkflowGuide } from './workflows.js';
|
|
7
|
+
const RESOURCE_MIME_TYPE = 'text/markdown';
|
|
8
|
+
const PATCH_MIME_TYPE = 'text/x-patch';
|
|
9
|
+
const RESOURCE_AUDIENCE = ['assistant'];
|
|
10
|
+
const TOOL_INFO_RESOURCE_URI = 'internal://tool-info/{toolName}';
|
|
11
|
+
function completeByPrefix(values, prefix) {
|
|
12
|
+
return values.filter((value) => value.startsWith(prefix));
|
|
13
|
+
}
|
|
14
|
+
function createMarkdownContent(uri, text) {
|
|
15
|
+
return { uri: uri.href, mimeType: RESOURCE_MIME_TYPE, text };
|
|
16
|
+
}
|
|
17
|
+
function createPatchContent(uri, text) {
|
|
18
|
+
return { uri: uri.href, mimeType: PATCH_MIME_TYPE, text };
|
|
19
|
+
}
|
|
20
|
+
function createResourceAnnotations(priority) {
|
|
21
|
+
return { audience: [...RESOURCE_AUDIENCE], priority };
|
|
22
|
+
}
|
|
23
|
+
function formatUnknownToolMessage(name) {
|
|
24
|
+
return `Unknown tool: ${name}`;
|
|
25
|
+
}
|
|
26
|
+
function formatDiffResourceText() {
|
|
27
|
+
const slot = getDiff();
|
|
28
|
+
if (!slot) {
|
|
29
|
+
return '# No diff cached. Call generate_diff first.';
|
|
30
|
+
}
|
|
31
|
+
return `# Diff — ${slot.mode} — ${slot.generatedAt}\n# ${slot.stats.files} file(s), +${slot.stats.added} -${slot.stats.deleted}\n\n${slot.diff}`;
|
|
32
|
+
}
|
|
33
|
+
export const STATIC_RESOURCES = [
|
|
34
|
+
{
|
|
35
|
+
id: 'server-instructions',
|
|
36
|
+
uri: 'internal://instructions',
|
|
37
|
+
title: 'Server Instructions',
|
|
38
|
+
description: 'Complete server usage instructions.',
|
|
39
|
+
priority: 0.8,
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
id: 'tool-catalog',
|
|
43
|
+
uri: 'internal://tool-catalog',
|
|
44
|
+
title: 'Tool Catalog',
|
|
45
|
+
description: 'Tool reference: models, params, outputs, data flow.',
|
|
46
|
+
priority: 1.0,
|
|
47
|
+
content: buildToolCatalog,
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
id: 'workflows',
|
|
51
|
+
uri: 'internal://workflows',
|
|
52
|
+
title: 'Workflow Reference',
|
|
53
|
+
description: 'Recommended workflows and tool sequences.',
|
|
54
|
+
priority: 0.9,
|
|
55
|
+
content: buildWorkflowGuide,
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
id: 'server-config',
|
|
59
|
+
uri: 'internal://server-config',
|
|
60
|
+
title: 'Server Configuration',
|
|
61
|
+
description: 'Runtime configuration and limits.',
|
|
62
|
+
priority: 0.7,
|
|
63
|
+
content: buildServerConfig,
|
|
64
|
+
},
|
|
65
|
+
];
|
|
66
|
+
function resolveStaticResourceContentOverride(resourceId, instructions) {
|
|
67
|
+
return resourceId === 'server-instructions' ? instructions : undefined;
|
|
68
|
+
}
|
|
69
|
+
function registerStaticResource(server, def, contentOverride) {
|
|
70
|
+
const content = contentOverride ?? def.content?.() ?? '';
|
|
71
|
+
server.registerResource(def.id, def.uri, {
|
|
72
|
+
title: def.title,
|
|
73
|
+
description: def.description,
|
|
74
|
+
mimeType: RESOURCE_MIME_TYPE,
|
|
75
|
+
annotations: createResourceAnnotations(def.priority),
|
|
76
|
+
}, (uri) => ({ contents: [createMarkdownContent(uri, content)] }));
|
|
77
|
+
}
|
|
78
|
+
function registerToolInfoResources(server) {
|
|
79
|
+
const toolNames = getToolInfoNames();
|
|
80
|
+
server.registerResource('tool-info', new ResourceTemplate(TOOL_INFO_RESOURCE_URI, {
|
|
81
|
+
list: undefined,
|
|
82
|
+
complete: {
|
|
83
|
+
toolName: (value) => completeByPrefix(toolNames, value),
|
|
84
|
+
},
|
|
85
|
+
}), {
|
|
86
|
+
title: 'Tool Info',
|
|
87
|
+
description: 'Per-tool reference: model, params, output, gotchas.',
|
|
88
|
+
mimeType: RESOURCE_MIME_TYPE,
|
|
89
|
+
annotations: createResourceAnnotations(0.6),
|
|
90
|
+
}, (uri, { toolName }) => {
|
|
91
|
+
const name = typeof toolName === 'string' ? toolName : '';
|
|
92
|
+
const info = getToolInfo(name);
|
|
93
|
+
const text = info ?? formatUnknownToolMessage(name);
|
|
94
|
+
return { contents: [createMarkdownContent(uri, text)] };
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
export const DIFF_RESOURCE_DESCRIPTION = 'The most recently generated diff, cached by generate_diff. Read by all review tools automatically.';
|
|
98
|
+
function registerDiffResource(server) {
|
|
99
|
+
server.registerResource('diff-current', DIFF_RESOURCE_URI, {
|
|
100
|
+
title: 'Current Diff',
|
|
101
|
+
description: DIFF_RESOURCE_DESCRIPTION,
|
|
102
|
+
mimeType: PATCH_MIME_TYPE,
|
|
103
|
+
annotations: createResourceAnnotations(1.0),
|
|
104
|
+
}, (uri) => ({
|
|
105
|
+
contents: [createPatchContent(uri, formatDiffResourceText())],
|
|
106
|
+
}));
|
|
107
|
+
}
|
|
108
|
+
export function registerAllResources(server, instructions) {
|
|
109
|
+
for (const def of STATIC_RESOURCES) {
|
|
110
|
+
const override = resolveStaticResourceContentOverride(def.id, instructions);
|
|
111
|
+
registerStaticResource(server, def, override);
|
|
112
|
+
}
|
|
113
|
+
registerToolInfoResources(server);
|
|
114
|
+
registerDiffResource(server);
|
|
115
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function buildServerInstructions(): string;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { toBulletedList, toInlineCode } from '../lib/format.js';
|
|
2
|
+
import { getToolContracts } from '../lib/tools.js';
|
|
3
|
+
import { PROMPT_DEFINITIONS } from '../prompts/index.js';
|
|
4
|
+
import { DIFF_RESOURCE_DESCRIPTION, STATIC_RESOURCES } from './index.js';
|
|
5
|
+
import { getSharedConstraints } from './tool-info.js';
|
|
6
|
+
const PROMPT_LIST = PROMPT_DEFINITIONS.map((def) => `${toInlineCode(def.name)}: ${def.description}`);
|
|
7
|
+
const RESOURCE_LIST = [
|
|
8
|
+
...STATIC_RESOURCES.map((def) => `${toInlineCode(def.uri)}: ${def.description}`),
|
|
9
|
+
`${toInlineCode('internal://tool-info/{toolName}')}: Per-tool contract details.`,
|
|
10
|
+
`${toInlineCode('diff://current')}: ${DIFF_RESOURCE_DESCRIPTION}`,
|
|
11
|
+
];
|
|
12
|
+
function formatParameterLine(parameter) {
|
|
13
|
+
const req = parameter.required ? 'req' : 'opt';
|
|
14
|
+
return ` - \`${parameter.name}\` (${parameter.type}, ${req}): ${parameter.constraints}`;
|
|
15
|
+
}
|
|
16
|
+
function formatToolSection(contract) {
|
|
17
|
+
const parameterLines = contract.params.map((parameter) => formatParameterLine(parameter));
|
|
18
|
+
if (contract.model === 'none') {
|
|
19
|
+
return `### \`${contract.name}\` (Sync)
|
|
20
|
+
${contract.purpose}
|
|
21
|
+
- **Params**:
|
|
22
|
+
${parameterLines.join('\n')}
|
|
23
|
+
- **Output**: \`${contract.outputShape}\``;
|
|
24
|
+
}
|
|
25
|
+
const modelInfo = [
|
|
26
|
+
'Flash',
|
|
27
|
+
contract.thinkingLevel ? `Thinking:${contract.thinkingLevel}` : '',
|
|
28
|
+
`${Math.round(contract.timeoutMs / 1_000)}s`,
|
|
29
|
+
`MaxTokens:${contract.maxOutputTokens}`,
|
|
30
|
+
]
|
|
31
|
+
.filter(Boolean)
|
|
32
|
+
.join(', ');
|
|
33
|
+
return `### \`${contract.name}\` (${modelInfo})
|
|
34
|
+
${contract.purpose}
|
|
35
|
+
- **Params**:
|
|
36
|
+
${parameterLines.join('\n')}
|
|
37
|
+
- **Output**: \`${contract.outputShape}\``;
|
|
38
|
+
}
|
|
39
|
+
export function buildServerInstructions() {
|
|
40
|
+
const contracts = getToolContracts();
|
|
41
|
+
const toolNames = contracts
|
|
42
|
+
.map((contract) => `\`${contract.name}\``)
|
|
43
|
+
.join(', ');
|
|
44
|
+
const toolSections = contracts.map((contract) => formatToolSection(contract));
|
|
45
|
+
const constraintLines = toBulletedList(getSharedConstraints());
|
|
46
|
+
return `# CODE ASSISTANT MCP
|
|
47
|
+
|
|
48
|
+
## CORE
|
|
49
|
+
- Domain: Gemini-powered code analysis.
|
|
50
|
+
- Capabilities: tools, resources (subscribe), prompts, logging, completions, tasks.
|
|
51
|
+
- Tools: ${toolNames}
|
|
52
|
+
|
|
53
|
+
## PROMPTS
|
|
54
|
+
${toBulletedList(PROMPT_LIST)}
|
|
55
|
+
|
|
56
|
+
## RESOURCES
|
|
57
|
+
${toBulletedList(RESOURCE_LIST)}
|
|
58
|
+
|
|
59
|
+
## TOOLS
|
|
60
|
+
${toolSections.join('\n\n')}
|
|
61
|
+
|
|
62
|
+
## CONSTRAINTS
|
|
63
|
+
${constraintLines}
|
|
64
|
+
|
|
65
|
+
## TASK LIFECYCLE
|
|
66
|
+
- Progress steps (0–6): starting → validating input → building prompt → calling model → validating response → finalizing → done.
|
|
67
|
+
- Status messages update at each phase for task introspection.
|
|
68
|
+
- Schema repair: on validation failure, retries with error feedback (configurable via \`GEMINI_SCHEMA_RETRIES\`).
|
|
69
|
+
- Task terminal states: \`completed\` and \`failed\`; cancellations are surfaced as \`failed\` with \`error.kind=cancelled\`.
|
|
70
|
+
`;
|
|
71
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function buildServerConfig(): string;
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { createCachedEnvInt } from '../lib/config.js';
|
|
2
|
+
import { FLASH_MODEL } from '../lib/config.js';
|
|
3
|
+
import { formatThinkingLevel, formatTimeoutSeconds, formatUsNumber, } from '../lib/format.js';
|
|
4
|
+
import { toInlineCode } from '../lib/format.js';
|
|
5
|
+
import { getToolContracts } from '../lib/tools.js';
|
|
6
|
+
const DEFAULT_MAX_DIFF_CHARS = 120_000;
|
|
7
|
+
const DEFAULT_MAX_CONCURRENT_CALLS = 10;
|
|
8
|
+
const DEFAULT_CONCURRENT_WAIT_MS = 2_000;
|
|
9
|
+
const DEFAULT_SAFETY_THRESHOLD = 'BLOCK_NONE';
|
|
10
|
+
const GEMINI_HARM_BLOCK_THRESHOLD_ENV_VAR = 'GEMINI_HARM_BLOCK_THRESHOLD';
|
|
11
|
+
const GEMINI_MODEL_ENV_VAR = 'GEMINI_MODEL';
|
|
12
|
+
const GEMINI_BATCH_MODE_ENV_VAR = 'GEMINI_BATCH_MODE';
|
|
13
|
+
const diffCharsConfig = createCachedEnvInt('MAX_DIFF_CHARS', DEFAULT_MAX_DIFF_CHARS);
|
|
14
|
+
const concurrentCallsConfig = createCachedEnvInt('MAX_CONCURRENT_CALLS', DEFAULT_MAX_CONCURRENT_CALLS);
|
|
15
|
+
const concurrentBatchCallsConfig = createCachedEnvInt('MAX_CONCURRENT_BATCH_CALLS', 2);
|
|
16
|
+
const concurrentWaitConfig = createCachedEnvInt('MAX_CONCURRENT_CALLS_WAIT_MS', DEFAULT_CONCURRENT_WAIT_MS);
|
|
17
|
+
function getModelOverride() {
|
|
18
|
+
return process.env[GEMINI_MODEL_ENV_VAR] ?? FLASH_MODEL;
|
|
19
|
+
}
|
|
20
|
+
function getBatchMode() {
|
|
21
|
+
return process.env[GEMINI_BATCH_MODE_ENV_VAR] ?? 'off';
|
|
22
|
+
}
|
|
23
|
+
function getSafetyThreshold() {
|
|
24
|
+
return (process.env[GEMINI_HARM_BLOCK_THRESHOLD_ENV_VAR] ?? DEFAULT_SAFETY_THRESHOLD);
|
|
25
|
+
}
|
|
26
|
+
export function buildServerConfig() {
|
|
27
|
+
const maxDiffChars = diffCharsConfig.get();
|
|
28
|
+
const maxConcurrent = concurrentCallsConfig.get();
|
|
29
|
+
const maxConcurrentBatch = concurrentBatchCallsConfig.get();
|
|
30
|
+
const concurrentWaitMs = concurrentWaitConfig.get();
|
|
31
|
+
const defaultModel = getModelOverride();
|
|
32
|
+
const batchMode = getBatchMode();
|
|
33
|
+
const safetyThreshold = getSafetyThreshold();
|
|
34
|
+
const toolRows = getToolContracts()
|
|
35
|
+
.filter((contract) => contract.model !== 'none')
|
|
36
|
+
.map((contract) => {
|
|
37
|
+
return `| ${toInlineCode(contract.name)} | ${toInlineCode(contract.model)} | ${formatThinkingLevel(contract.thinkingLevel, '—')} | ${formatTimeoutSeconds(contract.timeoutMs)} | ${formatUsNumber(contract.maxOutputTokens)} |`;
|
|
38
|
+
})
|
|
39
|
+
.join('\n');
|
|
40
|
+
return `# Server Configuration
|
|
41
|
+
|
|
42
|
+
## Input Limits
|
|
43
|
+
|
|
44
|
+
| Limit | Value | Env |
|
|
45
|
+
|-------|-------|-----|
|
|
46
|
+
| Diff limit | ${formatUsNumber(maxDiffChars)} chars | ${toInlineCode('MAX_DIFF_CHARS')} |
|
|
47
|
+
| Concurrency limit | ${maxConcurrent} | ${toInlineCode('MAX_CONCURRENT_CALLS')} |
|
|
48
|
+
| Batch concurrency limit | ${maxConcurrentBatch} | ${toInlineCode('MAX_CONCURRENT_BATCH_CALLS')} |
|
|
49
|
+
| Wait timeout | ${formatUsNumber(concurrentWaitMs)}ms | ${toInlineCode('MAX_CONCURRENT_CALLS_WAIT_MS')} |
|
|
50
|
+
| Batch mode | ${batchMode} | ${toInlineCode('GEMINI_BATCH_MODE')} |
|
|
51
|
+
|
|
52
|
+
## Model Assignments
|
|
53
|
+
|
|
54
|
+
Default model: ${toInlineCode(defaultModel)} (override with ${toInlineCode('GEMINI_MODEL')})
|
|
55
|
+
|
|
56
|
+
| Tool | Model | Thinking Level | Timeout | Max Output Tokens |
|
|
57
|
+
|------|-------|----------------|---------|-------------------|
|
|
58
|
+
${toolRows}
|
|
59
|
+
|
|
60
|
+
## Safety
|
|
61
|
+
|
|
62
|
+
- Harm block threshold: ${toInlineCode(safetyThreshold)}
|
|
63
|
+
- Override with ${toInlineCode('GEMINI_HARM_BLOCK_THRESHOLD')} (BLOCK_NONE, BLOCK_ONLY_HIGH, BLOCK_MEDIUM_AND_ABOVE, BLOCK_LOW_AND_ABOVE)
|
|
64
|
+
|
|
65
|
+
## API Keys
|
|
66
|
+
|
|
67
|
+
- Set ${toInlineCode('GEMINI_API_KEY')} or ${toInlineCode('GOOGLE_API_KEY')} environment variable (required)
|
|
68
|
+
|
|
69
|
+
## Batch Mode
|
|
70
|
+
|
|
71
|
+
- ${toInlineCode('GEMINI_BATCH_MODE')}: ${toInlineCode('off')} (default) or ${toInlineCode('inline')}
|
|
72
|
+
- ${toInlineCode('GEMINI_BATCH_POLL_INTERVAL_MS')}: poll cadence for batch status checks
|
|
73
|
+
- ${toInlineCode('GEMINI_BATCH_TIMEOUT_MS')}: max wait for batch completion
|
|
74
|
+
`;
|
|
75
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function buildToolCatalog(): string;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { toInlineCode } from '../lib/format.js';
|
|
2
|
+
import { buildCoreContextPack } from './tool-info.js';
|
|
3
|
+
const TOOL_CATALOG_CONTENT = `# Tool Catalog Details
|
|
4
|
+
|
|
5
|
+
## Optional Parameters
|
|
6
|
+
|
|
7
|
+
- ${toInlineCode('language')}: Primary language hint (auto-detects). All analysis tools.
|
|
8
|
+
- ${toInlineCode('testFramework')}: Framework hint. ${toInlineCode('generate_test_plan')} only.
|
|
9
|
+
- ${toInlineCode('maxTestCases')}: Output cap (1–30). ${toInlineCode('generate_test_plan')} only.
|
|
10
|
+
|
|
11
|
+
## Cross-Tool Data Flow
|
|
12
|
+
|
|
13
|
+
\`\`\`
|
|
14
|
+
analyze_pr_impact ──→ severity/categories ──→ triage decision
|
|
15
|
+
│
|
|
16
|
+
generate_review_summary ──→ overallRisk ──────┤
|
|
17
|
+
▼
|
|
18
|
+
diff ──→ generate_test_plan
|
|
19
|
+
\`\`\`
|
|
20
|
+
|
|
21
|
+
## When to Use Each Tool
|
|
22
|
+
|
|
23
|
+
- **Triage**: ${toInlineCode('analyze_pr_impact')}, ${toInlineCode('generate_review_summary')}.
|
|
24
|
+
- **Tests**: ${toInlineCode('generate_test_plan')}.
|
|
25
|
+
- **Complexity**: ${toInlineCode('analyze_time_space_complexity')}.
|
|
26
|
+
- **Breaking API**: ${toInlineCode('detect_api_breaking_changes')}.
|
|
27
|
+
`;
|
|
28
|
+
export function buildToolCatalog() {
|
|
29
|
+
return `${buildCoreContextPack()}\n\n${TOOL_CATALOG_CONTENT}`;
|
|
30
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export declare function buildCoreContextPack(): string;
|
|
2
|
+
export declare function getSharedConstraints(): readonly string[];
|
|
3
|
+
export declare function getToolInfoNames(): string[];
|
|
4
|
+
export declare function getToolInfo(toolName: string): string | undefined;
|
|
5
|
+
export declare function getToolPurpose(toolName: string): string | undefined;
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { formatThinkingLevel, formatTimeoutSeconds, formatUsNumber, } from '../lib/format.js';
|
|
2
|
+
import { toBulletedList, toInlineCode } from '../lib/format.js';
|
|
3
|
+
import { getToolContract, getToolContracts } from '../lib/tools.js';
|
|
4
|
+
const GLOBAL_CONSTRAINTS = [
|
|
5
|
+
'Diff budget: <= 120K chars.',
|
|
6
|
+
'Structured output: tools return both `structuredContent` and JSON text `content`.',
|
|
7
|
+
];
|
|
8
|
+
function formatParameterRow(entry) {
|
|
9
|
+
return `| ${entry.name} | ${entry.type} | ${entry.required ? 'Yes' : 'No'} | ${entry.constraints} | ${entry.description} |`;
|
|
10
|
+
}
|
|
11
|
+
function toToolInfoEntry(contract) {
|
|
12
|
+
const parameterRows = [
|
|
13
|
+
'| Param | Type | Required | Constraints | Description |',
|
|
14
|
+
'|-------|------|----------|-------------|-------------|',
|
|
15
|
+
...contract.params.map((parameter) => formatParameterRow(parameter)),
|
|
16
|
+
];
|
|
17
|
+
return {
|
|
18
|
+
name: contract.name,
|
|
19
|
+
purpose: contract.purpose,
|
|
20
|
+
model: contract.model,
|
|
21
|
+
thinkingLevel: formatThinkingLevel(contract.thinkingLevel),
|
|
22
|
+
timeout: formatTimeoutSeconds(contract.timeoutMs),
|
|
23
|
+
maxOutputTokens: formatUsNumber(contract.maxOutputTokens),
|
|
24
|
+
params: parameterRows.join('\n'),
|
|
25
|
+
outputShape: `\`${contract.outputShape}\``,
|
|
26
|
+
gotchas: contract.gotchas,
|
|
27
|
+
crossToolFlow: contract.crossToolFlow,
|
|
28
|
+
...(contract.constraints
|
|
29
|
+
? { constraints: contract.constraints }
|
|
30
|
+
: undefined),
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
const TOOL_INFO_ENTRIES = Object.fromEntries(getToolContracts().map((contract) => [
|
|
34
|
+
contract.name,
|
|
35
|
+
toToolInfoEntry(contract),
|
|
36
|
+
]));
|
|
37
|
+
const TOOL_NAMES = Object.keys(TOOL_INFO_ENTRIES).sort((a, b) => a.localeCompare(b));
|
|
38
|
+
function collectToolConstraints(entries) {
|
|
39
|
+
const constraints = new Set();
|
|
40
|
+
for (const [toolName, entry] of Object.entries(entries)) {
|
|
41
|
+
for (const constraint of entry.constraints ?? []) {
|
|
42
|
+
constraints.add(`\`${toolName}\`: ${constraint}`);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return Array.from(constraints).sort((a, b) => a.localeCompare(b));
|
|
46
|
+
}
|
|
47
|
+
function formatToolInfo(entry) {
|
|
48
|
+
const constraints = [...entry.gotchas, ...entry.crossToolFlow];
|
|
49
|
+
return `# ${entry.name}
|
|
50
|
+
${entry.purpose}
|
|
51
|
+
|
|
52
|
+
## Model
|
|
53
|
+
\`${entry.model}\` (Thinking: ${entry.thinkingLevel}, Timeout: ${entry.timeout}, Tokens: ${entry.maxOutputTokens})
|
|
54
|
+
|
|
55
|
+
## Parameters
|
|
56
|
+
${entry.params}
|
|
57
|
+
|
|
58
|
+
## Output
|
|
59
|
+
${entry.outputShape}
|
|
60
|
+
|
|
61
|
+
## Constraints
|
|
62
|
+
${constraints.map((item) => `- ${item}`).join('\n')}
|
|
63
|
+
`;
|
|
64
|
+
}
|
|
65
|
+
function formatCompactToolRow(entry) {
|
|
66
|
+
return `| ${toInlineCode(entry.name)} | ${entry.model} | ${entry.timeout} | ${entry.maxOutputTokens} | ${entry.purpose} |`;
|
|
67
|
+
}
|
|
68
|
+
export function buildCoreContextPack() {
|
|
69
|
+
const rows = TOOL_NAMES.flatMap((toolName) => {
|
|
70
|
+
const entry = TOOL_INFO_ENTRIES[toolName];
|
|
71
|
+
return entry ? [formatCompactToolRow(entry)] : [];
|
|
72
|
+
});
|
|
73
|
+
return `# Core Context Pack
|
|
74
|
+
|
|
75
|
+
## Server Essentials
|
|
76
|
+
- Domain: Gemini-powered MCP server for code analysis.
|
|
77
|
+
- Surface: 7 analysis tools + internal resources + guided prompts.
|
|
78
|
+
- Transport: stdio with task lifecycle support.
|
|
79
|
+
|
|
80
|
+
## Tool Matrix
|
|
81
|
+
| Tool | Model | Timeout | Max Output Tokens | Purpose |
|
|
82
|
+
|------|-------|---------|-------------------|---------|
|
|
83
|
+
${rows.join('\n')}
|
|
84
|
+
|
|
85
|
+
## Shared Constraints
|
|
86
|
+
${toBulletedList(getSharedConstraints())}
|
|
87
|
+
`;
|
|
88
|
+
}
|
|
89
|
+
export function getSharedConstraints() {
|
|
90
|
+
const toolConstraints = collectToolConstraints(TOOL_INFO_ENTRIES);
|
|
91
|
+
return [...GLOBAL_CONSTRAINTS, ...toolConstraints];
|
|
92
|
+
}
|
|
93
|
+
export function getToolInfoNames() {
|
|
94
|
+
return TOOL_NAMES;
|
|
95
|
+
}
|
|
96
|
+
export function getToolInfo(toolName) {
|
|
97
|
+
const entry = TOOL_INFO_ENTRIES[toolName];
|
|
98
|
+
if (!entry) {
|
|
99
|
+
return undefined;
|
|
100
|
+
}
|
|
101
|
+
return formatToolInfo(entry);
|
|
102
|
+
}
|
|
103
|
+
export function getToolPurpose(toolName) {
|
|
104
|
+
return getToolContract(toolName)?.purpose;
|
|
105
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function buildWorkflowGuide(): string;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { toBulletedList } from '../lib/format.js';
|
|
2
|
+
import { getToolContracts } from '../lib/tools.js';
|
|
3
|
+
import { getSharedConstraints } from './tool-info.js';
|
|
4
|
+
function buildWorkflowToolReference() {
|
|
5
|
+
const contracts = getToolContracts();
|
|
6
|
+
return contracts
|
|
7
|
+
.map((c) => `### \`${c.name}\`\n- **Purpose:** ${c.purpose}\n- **Model:** ${c.model}\n- **Output:** \`${c.outputShape}\``)
|
|
8
|
+
.join('\n\n');
|
|
9
|
+
}
|
|
10
|
+
export function buildWorkflowGuide() {
|
|
11
|
+
return `# Workflow Reference
|
|
12
|
+
|
|
13
|
+
## A: Full PR Review
|
|
14
|
+
|
|
15
|
+
1. \`generate_review_summary\` → \`{overallRisk, keyChanges[], recommendation, stats}\`
|
|
16
|
+
|
|
17
|
+
## B: Impact Assessment
|
|
18
|
+
|
|
19
|
+
1. \`analyze_pr_impact\` → \`{severity, categories[], breakingChanges[], rollbackComplexity}\`
|
|
20
|
+
2. \`generate_review_summary\` → complementary merge recommendation
|
|
21
|
+
|
|
22
|
+
> Use when categorization (breaking, api) or rollback assessment needed.
|
|
23
|
+
|
|
24
|
+
## C: Test Coverage
|
|
25
|
+
|
|
26
|
+
1. \`generate_test_plan\` → \`{testCases[], coverageSummary}\`
|
|
27
|
+
2. Review by priority: \`must_have\` → \`should_have\` → \`nice_to_have\`
|
|
28
|
+
|
|
29
|
+
> Combine with review tools.
|
|
30
|
+
|
|
31
|
+
## D: Complexity & Breaking Changes
|
|
32
|
+
|
|
33
|
+
1. \`analyze_time_space_complexity\` → \`{timeComplexity, spaceComplexity, isDegradation}\`
|
|
34
|
+
2. \`detect_api_breaking_changes\` → \`{hasBreakingChanges, breakingChanges[]}\`
|
|
35
|
+
|
|
36
|
+
> Use for algorithm or API changes. Diff-only input.
|
|
37
|
+
|
|
38
|
+
## Shared Constraints
|
|
39
|
+
${toBulletedList(getSharedConstraints())}
|
|
40
|
+
|
|
41
|
+
## Tool Reference
|
|
42
|
+
|
|
43
|
+
${buildWorkflowToolReference()}
|
|
44
|
+
|
|
45
|
+
## Output Shape Reference
|
|
46
|
+
|
|
47
|
+
### Finding
|
|
48
|
+
\`{severity, file, line, title, explanation, recommendation}\`
|
|
49
|
+
|
|
50
|
+
### Search/Replace Block
|
|
51
|
+
\`{file, search, replace, explanation}\`
|
|
52
|
+
|
|
53
|
+
### Test Case
|
|
54
|
+
\`{name, type, file, description, pseudoCode, priority}\`
|
|
55
|
+
|
|
56
|
+
### Breaking Change
|
|
57
|
+
\`{element, natureOfChange, consumerImpact, suggestedMitigation}\`
|
|
58
|
+
`;
|
|
59
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export declare const AnalyzePrImpactInputSchema: z.ZodObject<{
|
|
3
|
+
repository: z.ZodString;
|
|
4
|
+
language: z.ZodOptional<z.ZodString>;
|
|
5
|
+
}, z.core.$strict>;
|
|
6
|
+
export declare const GenerateReviewSummaryInputSchema: z.ZodObject<{
|
|
7
|
+
repository: z.ZodString;
|
|
8
|
+
language: z.ZodOptional<z.ZodString>;
|
|
9
|
+
}, z.core.$strict>;
|
|
10
|
+
export declare const GenerateTestPlanInputSchema: z.ZodObject<{
|
|
11
|
+
repository: z.ZodString;
|
|
12
|
+
language: z.ZodOptional<z.ZodString>;
|
|
13
|
+
testFramework: z.ZodOptional<z.ZodString>;
|
|
14
|
+
maxTestCases: z.ZodOptional<z.ZodNumber>;
|
|
15
|
+
}, z.core.$strict>;
|
|
16
|
+
export declare const AnalyzeComplexityInputSchema: z.ZodObject<{
|
|
17
|
+
language: z.ZodOptional<z.ZodString>;
|
|
18
|
+
}, z.core.$strict>;
|
|
19
|
+
export declare const DetectApiBreakingInputSchema: z.ZodObject<{
|
|
20
|
+
language: z.ZodOptional<z.ZodString>;
|
|
21
|
+
}, z.core.$strict>;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
const INPUT_LIMITS = {
|
|
3
|
+
repository: { min: 1, max: 200 },
|
|
4
|
+
language: { min: 2, max: 32 },
|
|
5
|
+
testFramework: { min: 1, max: 50 },
|
|
6
|
+
maxTestCases: { min: 1, max: 30 },
|
|
7
|
+
};
|
|
8
|
+
function createBoundedString(min, max, description) {
|
|
9
|
+
return z.string().min(min).max(max).describe(description);
|
|
10
|
+
}
|
|
11
|
+
function createOptionalBoundedString(min, max, description) {
|
|
12
|
+
return createBoundedString(min, max, description).optional();
|
|
13
|
+
}
|
|
14
|
+
const LANGUAGE_DESCRIPTION = 'Primary language (e.g. TypeScript). Auto-infer from files.';
|
|
15
|
+
const REPOSITORY_DESCRIPTION = 'Repo ID (owner/repo). Auto-infer from git/dir.';
|
|
16
|
+
function createLanguageSchema() {
|
|
17
|
+
return createOptionalBoundedString(INPUT_LIMITS.language.min, INPUT_LIMITS.language.max, LANGUAGE_DESCRIPTION);
|
|
18
|
+
}
|
|
19
|
+
function createRepositorySchema() {
|
|
20
|
+
return createBoundedString(INPUT_LIMITS.repository.min, INPUT_LIMITS.repository.max, REPOSITORY_DESCRIPTION);
|
|
21
|
+
}
|
|
22
|
+
function createOptionalBoundedInteger(min, max, description) {
|
|
23
|
+
return z.number().int().min(min).max(max).optional().describe(description);
|
|
24
|
+
}
|
|
25
|
+
const RepositorySchema = createRepositorySchema();
|
|
26
|
+
const LanguageSchema = createLanguageSchema();
|
|
27
|
+
export const AnalyzePrImpactInputSchema = z.strictObject({
|
|
28
|
+
repository: RepositorySchema,
|
|
29
|
+
language: LanguageSchema,
|
|
30
|
+
});
|
|
31
|
+
export const GenerateReviewSummaryInputSchema = z.strictObject({
|
|
32
|
+
repository: RepositorySchema,
|
|
33
|
+
language: LanguageSchema,
|
|
34
|
+
});
|
|
35
|
+
export const GenerateTestPlanInputSchema = z.strictObject({
|
|
36
|
+
repository: RepositorySchema,
|
|
37
|
+
language: LanguageSchema,
|
|
38
|
+
testFramework: createOptionalBoundedString(INPUT_LIMITS.testFramework.min, INPUT_LIMITS.testFramework.max, 'Test framework (jest, pytest, etc). Auto-infer.'),
|
|
39
|
+
maxTestCases: createOptionalBoundedInteger(INPUT_LIMITS.maxTestCases.min, INPUT_LIMITS.maxTestCases.max, 'Max test cases (1-30). Default: 15.'),
|
|
40
|
+
});
|
|
41
|
+
export const AnalyzeComplexityInputSchema = z.strictObject({
|
|
42
|
+
language: LanguageSchema,
|
|
43
|
+
});
|
|
44
|
+
export const DetectApiBreakingInputSchema = z.strictObject({
|
|
45
|
+
language: LanguageSchema,
|
|
46
|
+
});
|