ai-validation-mcp 0.1.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.
@@ -0,0 +1,9 @@
1
+ export type CacheMode = 'cached' | 'live';
2
+ export declare function computeCacheKey(toolName: string, inputs: Record<string, string>): string;
3
+ export declare function getFixturePath(cacheKey: string): string;
4
+ export declare function getCachedResponse(cacheKey: string): string | null;
5
+ export declare function saveCachedResponse(cacheKey: string, response: string, meta: {
6
+ toolName: string;
7
+ inputs: Record<string, string>;
8
+ }): void;
9
+ //# sourceMappingURL=cache.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../src/cache.ts"],"names":[],"mappings":"AAQA,MAAM,MAAM,SAAS,GAAG,QAAQ,GAAG,MAAM,CAAC;AAE1C,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,MAAM,CAOxF;AAED,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAEvD;AAED,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CASjE;AAED,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAAE,GACzD,IAAI,CAiBN"}
package/dist/cache.js ADDED
@@ -0,0 +1,40 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { readFileSync, writeFileSync, mkdirSync, existsSync } from 'node:fs';
3
+ import { join, dirname } from 'node:path';
4
+ import { fileURLToPath } from 'node:url';
5
+ const __dirname = dirname(fileURLToPath(import.meta.url));
6
+ const FIXTURES_DIR = join(__dirname, '..', 'fixtures');
7
+ export function computeCacheKey(toolName, inputs) {
8
+ const sorted = Object.keys(inputs)
9
+ .sort()
10
+ .map((k) => `${k}:${inputs[k]}`)
11
+ .join('|');
12
+ const hash = createHash('sha256').update(`${toolName}|${sorted}`).digest('hex').substring(0, 16);
13
+ return `${toolName}-${hash}`;
14
+ }
15
+ export function getFixturePath(cacheKey) {
16
+ return join(FIXTURES_DIR, `${cacheKey}.json`);
17
+ }
18
+ export function getCachedResponse(cacheKey) {
19
+ const path = getFixturePath(cacheKey);
20
+ if (!existsSync(path))
21
+ return null;
22
+ try {
23
+ const data = JSON.parse(readFileSync(path, 'utf-8'));
24
+ return data.response;
25
+ }
26
+ catch {
27
+ return null;
28
+ }
29
+ }
30
+ export function saveCachedResponse(cacheKey, response, meta) {
31
+ mkdirSync(FIXTURES_DIR, { recursive: true });
32
+ const path = getFixturePath(cacheKey);
33
+ writeFileSync(path, JSON.stringify({
34
+ toolName: meta.toolName,
35
+ inputs: meta.inputs,
36
+ response,
37
+ cachedAt: new Date().toISOString(),
38
+ }, null, 2), 'utf-8');
39
+ }
40
+ //# sourceMappingURL=cache.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cache.js","sourceRoot":"","sources":["../src/cache.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAC7E,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,MAAM,SAAS,GAAG,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AAC1D,MAAM,YAAY,GAAG,IAAI,CAAC,SAAS,EAAE,IAAI,EAAE,UAAU,CAAC,CAAC;AAIvD,MAAM,UAAU,eAAe,CAAC,QAAgB,EAAE,MAA8B;IAC9E,MAAM,MAAM,GAAG,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC;SAC/B,IAAI,EAAE;SACN,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,IAAI,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC;SAC/B,IAAI,CAAC,GAAG,CAAC,CAAC;IACb,MAAM,IAAI,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,GAAG,QAAQ,IAAI,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,SAAS,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IACjG,OAAO,GAAG,QAAQ,IAAI,IAAI,EAAE,CAAC;AAC/B,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,QAAgB;IAC7C,OAAO,IAAI,CAAC,YAAY,EAAE,GAAG,QAAQ,OAAO,CAAC,CAAC;AAChD,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,QAAgB;IAChD,MAAM,IAAI,GAAG,cAAc,CAAC,QAAQ,CAAC,CAAC;IACtC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC;IACnC,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC;QACrD,OAAO,IAAI,CAAC,QAAQ,CAAC;IACvB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,MAAM,UAAU,kBAAkB,CAChC,QAAgB,EAChB,QAAgB,EAChB,IAA0D;IAE1D,SAAS,CAAC,YAAY,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC7C,MAAM,IAAI,GAAG,cAAc,CAAC,QAAQ,CAAC,CAAC;IACtC,aAAa,CACX,IAAI,EACJ,IAAI,CAAC,SAAS,CACZ;QACE,QAAQ,EAAE,IAAI,CAAC,QAAQ;QACvB,MAAM,EAAE,IAAI,CAAC,MAAM;QACnB,QAAQ;QACR,QAAQ,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;KACnC,EACD,IAAI,EACJ,CAAC,CACF,EACD,OAAO,CACR,CAAC;AACJ,CAAC"}
@@ -0,0 +1,3 @@
1
+ import { type CacheMode } from './cache.js';
2
+ export declare function analyzeWithClaude(toolName: string, prompt: string, inputs: Record<string, string>, cacheMode: CacheMode): Promise<string>;
3
+ //# sourceMappingURL=claude-client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"claude-client.d.ts","sourceRoot":"","sources":["../src/claude-client.ts"],"names":[],"mappings":"AACA,OAAO,EAA0D,KAAK,SAAS,EAAE,MAAM,YAAY,CAAC;AAWpG,wBAAsB,iBAAiB,CACrC,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC9B,SAAS,EAAE,SAAS,GACnB,OAAO,CAAC,MAAM,CAAC,CAgCjB"}
@@ -0,0 +1,36 @@
1
+ import Anthropic from '@anthropic-ai/sdk';
2
+ import { getCachedResponse, saveCachedResponse, computeCacheKey } from './cache.js';
3
+ let client = null;
4
+ function getClient() {
5
+ if (!client) {
6
+ client = new Anthropic();
7
+ }
8
+ return client;
9
+ }
10
+ export async function analyzeWithClaude(toolName, prompt, inputs, cacheMode) {
11
+ const cacheKey = computeCacheKey(toolName, inputs);
12
+ if (cacheMode === 'cached') {
13
+ const cached = getCachedResponse(cacheKey);
14
+ if (cached)
15
+ return cached;
16
+ throw new Error(`No cached response for "${toolName}" with these inputs. ` +
17
+ `Run with --live to generate a fixture, or add a fixture at fixtures/${cacheKey}.json`);
18
+ }
19
+ // Live mode: call Claude API
20
+ const anthropic = getClient();
21
+ const message = await anthropic.messages.create({
22
+ model: 'claude-sonnet-4-20250514',
23
+ max_tokens: 4096,
24
+ messages: [
25
+ {
26
+ role: 'user',
27
+ content: prompt,
28
+ },
29
+ ],
30
+ });
31
+ const response = message.content[0].type === 'text' ? message.content[0].text : JSON.stringify(message.content);
32
+ // Save to cache for future use
33
+ saveCachedResponse(cacheKey, response, { toolName, inputs });
34
+ return response;
35
+ }
36
+ //# sourceMappingURL=claude-client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"claude-client.js","sourceRoot":"","sources":["../src/claude-client.ts"],"names":[],"mappings":"AAAA,OAAO,SAAS,MAAM,mBAAmB,CAAC;AAC1C,OAAO,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,eAAe,EAAkB,MAAM,YAAY,CAAC;AAEpG,IAAI,MAAM,GAAqB,IAAI,CAAC;AAEpC,SAAS,SAAS;IAChB,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,MAAM,GAAG,IAAI,SAAS,EAAE,CAAC;IAC3B,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,QAAgB,EAChB,MAAc,EACd,MAA8B,EAC9B,SAAoB;IAEpB,MAAM,QAAQ,GAAG,eAAe,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;IAEnD,IAAI,SAAS,KAAK,QAAQ,EAAE,CAAC;QAC3B,MAAM,MAAM,GAAG,iBAAiB,CAAC,QAAQ,CAAC,CAAC;QAC3C,IAAI,MAAM;YAAE,OAAO,MAAM,CAAC;QAC1B,MAAM,IAAI,KAAK,CACb,2BAA2B,QAAQ,uBAAuB;YACxD,uEAAuE,QAAQ,OAAO,CACzF,CAAC;IACJ,CAAC;IAED,6BAA6B;IAC7B,MAAM,SAAS,GAAG,SAAS,EAAE,CAAC;IAC9B,MAAM,OAAO,GAAG,MAAM,SAAS,CAAC,QAAQ,CAAC,MAAM,CAAC;QAC9C,KAAK,EAAE,0BAA0B;QACjC,UAAU,EAAE,IAAI;QAChB,QAAQ,EAAE;YACR;gBACE,IAAI,EAAE,MAAM;gBACZ,OAAO,EAAE,MAAM;aAChB;SACF;KACF,CAAC,CAAC;IAEH,MAAM,QAAQ,GACZ,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;IAEjG,+BAA+B;IAC/B,kBAAkB,CAAC,QAAQ,EAAE,QAAQ,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,CAAC;IAE7D,OAAO,QAAQ,CAAC;AAClB,CAAC"}
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ import 'dotenv/config';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA,OAAO,eAAe,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,132 @@
1
+ #!/usr/bin/env node
2
+ import 'dotenv/config';
3
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
4
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
5
+ import { CallToolRequestSchema, ListToolsRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
6
+ import { handleValidateTests } from './tools/validate-tests.js';
7
+ import { handleSuggestEdgeCases } from './tools/suggest-edge-cases.js';
8
+ import { handleCoverageGapAnalysis } from './tools/coverage-gap-analysis.js';
9
+ const cacheMode = process.argv.includes('--live') ? 'live' : 'cached';
10
+ const server = new Server({ name: 'ai-validation-mcp', version: '0.1.0' }, { capabilities: { tools: {} } });
11
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({
12
+ tools: [
13
+ {
14
+ name: 'validate_tests',
15
+ description: 'Analyze existing tests for bias, missing edge cases, and quality issues. ' +
16
+ 'Returns bias warnings, missing edge case suggestions, and an overall quality score.',
17
+ inputSchema: {
18
+ type: 'object',
19
+ properties: {
20
+ sourceCode: {
21
+ type: 'string',
22
+ description: 'The source code to validate tests against',
23
+ },
24
+ testCode: {
25
+ type: 'string',
26
+ description: 'The existing test code to analyze',
27
+ },
28
+ language: {
29
+ type: 'string',
30
+ description: 'Programming language (default: typescript)',
31
+ },
32
+ },
33
+ required: ['sourceCode', 'testCode'],
34
+ },
35
+ },
36
+ {
37
+ name: 'suggest_edge_cases',
38
+ description: 'Suggest comprehensive edge cases for a given function/code. ' +
39
+ 'Returns categorized edge cases: happy path, error handling, boundary, type coercion, integration.',
40
+ inputSchema: {
41
+ type: 'object',
42
+ properties: {
43
+ sourceCode: {
44
+ type: 'string',
45
+ description: 'The source code to analyze for edge cases',
46
+ },
47
+ language: {
48
+ type: 'string',
49
+ description: 'Programming language (default: typescript)',
50
+ },
51
+ },
52
+ required: ['sourceCode'],
53
+ },
54
+ },
55
+ {
56
+ name: 'coverage_gap_analysis',
57
+ description: 'Identify untested code paths, branches, and logic gaps in existing tests. ' +
58
+ 'Returns coverage score, gap details, and suggested tests to close gaps.',
59
+ inputSchema: {
60
+ type: 'object',
61
+ properties: {
62
+ sourceCode: {
63
+ type: 'string',
64
+ description: 'The source code to analyze coverage for',
65
+ },
66
+ testCode: {
67
+ type: 'string',
68
+ description: 'The existing test code to evaluate',
69
+ },
70
+ language: {
71
+ type: 'string',
72
+ description: 'Programming language (default: typescript)',
73
+ },
74
+ },
75
+ required: ['sourceCode', 'testCode'],
76
+ },
77
+ },
78
+ ],
79
+ }));
80
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
81
+ const { name, arguments: args } = request.params;
82
+ try {
83
+ let result;
84
+ switch (name) {
85
+ case 'validate_tests':
86
+ result = await handleValidateTests({
87
+ sourceCode: args?.sourceCode,
88
+ testCode: args?.testCode,
89
+ language: args?.language,
90
+ }, cacheMode);
91
+ break;
92
+ case 'suggest_edge_cases':
93
+ result = await handleSuggestEdgeCases({
94
+ sourceCode: args?.sourceCode,
95
+ language: args?.language,
96
+ }, cacheMode);
97
+ break;
98
+ case 'coverage_gap_analysis':
99
+ result = await handleCoverageGapAnalysis({
100
+ sourceCode: args?.sourceCode,
101
+ testCode: args?.testCode,
102
+ language: args?.language,
103
+ }, cacheMode);
104
+ break;
105
+ default:
106
+ return {
107
+ content: [{ type: 'text', text: `Unknown tool: ${name}` }],
108
+ isError: true,
109
+ };
110
+ }
111
+ return {
112
+ content: [{ type: 'text', text: result }],
113
+ };
114
+ }
115
+ catch (error) {
116
+ const message = error instanceof Error ? error.message : String(error);
117
+ return {
118
+ content: [{ type: 'text', text: `Error: ${message}` }],
119
+ isError: true,
120
+ };
121
+ }
122
+ });
123
+ async function main() {
124
+ const transport = new StdioServerTransport();
125
+ await server.connect(transport);
126
+ console.error(`ai-validation-mcp server running (cache mode: ${cacheMode})`);
127
+ }
128
+ main().catch((error) => {
129
+ console.error('Fatal error:', error);
130
+ process.exit(1);
131
+ });
132
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ import type { CacheMode } from '../cache.js';
2
+ export interface CoverageGapInput {
3
+ sourceCode: string;
4
+ testCode: string;
5
+ language?: string;
6
+ }
7
+ export declare function handleCoverageGapAnalysis(input: CoverageGapInput, cacheMode: CacheMode): Promise<string>;
8
+ //# sourceMappingURL=coverage-gap-analysis.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"coverage-gap-analysis.d.ts","sourceRoot":"","sources":["../../src/tools/coverage-gap-analysis.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAE7C,MAAM,WAAW,gBAAgB;IAC/B,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,wBAAsB,yBAAyB,CAC7C,KAAK,EAAE,gBAAgB,EACvB,SAAS,EAAE,SAAS,GACnB,OAAO,CAAC,MAAM,CAAC,CAUjB"}
@@ -0,0 +1,13 @@
1
+ import { analyzeWithClaude } from '../claude-client.js';
2
+ import { buildCoverageGapPrompt } from './prompts.js';
3
+ export async function handleCoverageGapAnalysis(input, cacheMode) {
4
+ const language = input.language ?? 'typescript';
5
+ const prompt = buildCoverageGapPrompt(input.sourceCode, input.testCode, language);
6
+ const inputs = {
7
+ sourceCode: input.sourceCode,
8
+ testCode: input.testCode,
9
+ language,
10
+ };
11
+ return analyzeWithClaude('coverage_gap_analysis', prompt, inputs, cacheMode);
12
+ }
13
+ //# sourceMappingURL=coverage-gap-analysis.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"coverage-gap-analysis.js","sourceRoot":"","sources":["../../src/tools/coverage-gap-analysis.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,EAAE,sBAAsB,EAAE,MAAM,cAAc,CAAC;AAStD,MAAM,CAAC,KAAK,UAAU,yBAAyB,CAC7C,KAAuB,EACvB,SAAoB;IAEpB,MAAM,QAAQ,GAAG,KAAK,CAAC,QAAQ,IAAI,YAAY,CAAC;IAChD,MAAM,MAAM,GAAG,sBAAsB,CAAC,KAAK,CAAC,UAAU,EAAE,KAAK,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;IAClF,MAAM,MAAM,GAAG;QACb,UAAU,EAAE,KAAK,CAAC,UAAU;QAC5B,QAAQ,EAAE,KAAK,CAAC,QAAQ;QACxB,QAAQ;KACT,CAAC;IAEF,OAAO,iBAAiB,CAAC,uBAAuB,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,CAAC,CAAC;AAC/E,CAAC"}
@@ -0,0 +1,5 @@
1
+ export declare const EDGE_CASE_TAXONOMY = "\n## Edge Case Taxonomy\n\nCategorize all findings into these categories:\n\n1. **HAPPY_PATH**: Normal, expected inputs and standard flows\n2. **ERROR_HANDLING**: Exceptions, invalid inputs, failure modes, error propagation\n3. **BOUNDARY**: Off-by-one errors, empty collections, max/min values, zero, negative numbers, overflow\n4. **TYPE_COERCION**: Unexpected types, null/undefined, NaN, Infinity, type casting edge cases\n5. **INTEGRATION**: State dependencies, race conditions, side effects, interaction between components\n";
2
+ export declare function buildValidateTestsPrompt(sourceCode: string, testCode: string, language: string): string;
3
+ export declare function buildSuggestEdgeCasesPrompt(sourceCode: string, language: string): string;
4
+ export declare function buildCoverageGapPrompt(sourceCode: string, testCode: string, language: string): string;
5
+ //# sourceMappingURL=prompts.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prompts.d.ts","sourceRoot":"","sources":["../../src/tools/prompts.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,kBAAkB,shBAU9B,CAAC;AAEF,wBAAgB,wBAAwB,CACtC,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,GACf,MAAM,CAuCR;AAED,wBAAgB,2BAA2B,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAkCxF;AAED,wBAAgB,sBAAsB,CACpC,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,GACf,MAAM,CAkCR"}
@@ -0,0 +1,122 @@
1
+ export const EDGE_CASE_TAXONOMY = `
2
+ ## Edge Case Taxonomy
3
+
4
+ Categorize all findings into these categories:
5
+
6
+ 1. **HAPPY_PATH**: Normal, expected inputs and standard flows
7
+ 2. **ERROR_HANDLING**: Exceptions, invalid inputs, failure modes, error propagation
8
+ 3. **BOUNDARY**: Off-by-one errors, empty collections, max/min values, zero, negative numbers, overflow
9
+ 4. **TYPE_COERCION**: Unexpected types, null/undefined, NaN, Infinity, type casting edge cases
10
+ 5. **INTEGRATION**: State dependencies, race conditions, side effects, interaction between components
11
+ `;
12
+ export function buildValidateTestsPrompt(sourceCode, testCode, language) {
13
+ return `You are an expert test quality analyst. Analyze the following source code and its tests for bias, missing edge cases, and quality issues.
14
+
15
+ ## Source Code (${language})
16
+ \`\`\`${language}
17
+ ${sourceCode}
18
+ \`\`\`
19
+
20
+ ## Existing Tests
21
+ \`\`\`${language}
22
+ ${testCode}
23
+ \`\`\`
24
+
25
+ ${EDGE_CASE_TAXONOMY}
26
+
27
+ ## Task
28
+
29
+ Analyze the tests and provide a JSON response with this exact structure:
30
+ {
31
+ "overallScore": <number 1-10>,
32
+ "biasWarnings": [
33
+ {
34
+ "type": "happy_path_bias" | "missing_error_handling" | "no_boundary_tests" | "type_assumptions" | "missing_integration",
35
+ "severity": "high" | "medium" | "low",
36
+ "description": "<what is biased or missing>",
37
+ "suggestion": "<what test to add>"
38
+ }
39
+ ],
40
+ "missingEdgeCases": [
41
+ {
42
+ "category": "HAPPY_PATH" | "ERROR_HANDLING" | "BOUNDARY" | "TYPE_COERCION" | "INTEGRATION",
43
+ "description": "<what is missing>",
44
+ "testSuggestion": "<concrete test code>"
45
+ }
46
+ ],
47
+ "summary": "<1-2 sentence summary of test quality>"
48
+ }
49
+
50
+ Respond ONLY with valid JSON. No markdown, no explanation outside JSON.`;
51
+ }
52
+ export function buildSuggestEdgeCasesPrompt(sourceCode, language) {
53
+ return `You are an expert at finding edge cases in code. Analyze the following source code and suggest comprehensive edge cases for testing.
54
+
55
+ ## Source Code (${language})
56
+ \`\`\`${language}
57
+ ${sourceCode}
58
+ \`\`\`
59
+
60
+ ${EDGE_CASE_TAXONOMY}
61
+
62
+ ## Task
63
+
64
+ Suggest edge cases for thorough testing. Provide a JSON response with this exact structure:
65
+ {
66
+ "functionName": "<detected function name>",
67
+ "edgeCases": [
68
+ {
69
+ "category": "HAPPY_PATH" | "ERROR_HANDLING" | "BOUNDARY" | "TYPE_COERCION" | "INTEGRATION",
70
+ "description": "<what the edge case tests>",
71
+ "input": "<example input>",
72
+ "expectedBehavior": "<what should happen>",
73
+ "priority": "critical" | "high" | "medium" | "low"
74
+ }
75
+ ],
76
+ "totalByCategory": {
77
+ "HAPPY_PATH": <count>,
78
+ "ERROR_HANDLING": <count>,
79
+ "BOUNDARY": <count>,
80
+ "TYPE_COERCION": <count>,
81
+ "INTEGRATION": <count>
82
+ }
83
+ }
84
+
85
+ Respond ONLY with valid JSON. No markdown, no explanation outside JSON.`;
86
+ }
87
+ export function buildCoverageGapPrompt(sourceCode, testCode, language) {
88
+ return `You are an expert code coverage analyst. Analyze the source code and existing tests to identify untested code paths, branches, and logic gaps.
89
+
90
+ ## Source Code (${language})
91
+ \`\`\`${language}
92
+ ${sourceCode}
93
+ \`\`\`
94
+
95
+ ## Existing Tests
96
+ \`\`\`${language}
97
+ ${testCode}
98
+ \`\`\`
99
+
100
+ ${EDGE_CASE_TAXONOMY}
101
+
102
+ ## Task
103
+
104
+ Identify coverage gaps — code paths, branches, and conditions not exercised by the existing tests. Provide a JSON response with this exact structure:
105
+ {
106
+ "coverageScore": <number 1-10, where 10 is fully covered>,
107
+ "gaps": [
108
+ {
109
+ "type": "branch" | "path" | "condition" | "exception" | "return_value",
110
+ "location": "<line or code section description>",
111
+ "description": "<what is not tested>",
112
+ "severity": "critical" | "high" | "medium" | "low",
113
+ "suggestedTest": "<concrete test code to close this gap>"
114
+ }
115
+ ],
116
+ "testedPaths": ["<list of paths that ARE covered>"],
117
+ "summary": "<1-2 sentence coverage assessment>"
118
+ }
119
+
120
+ Respond ONLY with valid JSON. No markdown, no explanation outside JSON.`;
121
+ }
122
+ //# sourceMappingURL=prompts.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prompts.js","sourceRoot":"","sources":["../../src/tools/prompts.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,kBAAkB,GAAG;;;;;;;;;;CAUjC,CAAC;AAEF,MAAM,UAAU,wBAAwB,CACtC,UAAkB,EAClB,QAAgB,EAChB,QAAgB;IAEhB,OAAO;;kBAES,QAAQ;QAClB,QAAQ;EACd,UAAU;;;;QAIJ,QAAQ;EACd,QAAQ;;;EAGR,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;wEAyBoD,CAAC;AACzE,CAAC;AAED,MAAM,UAAU,2BAA2B,CAAC,UAAkB,EAAE,QAAgB;IAC9E,OAAO;;kBAES,QAAQ;QAClB,QAAQ;EACd,UAAU;;;EAGV,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;wEAyBoD,CAAC;AACzE,CAAC;AAED,MAAM,UAAU,sBAAsB,CACpC,UAAkB,EAClB,QAAgB,EAChB,QAAgB;IAEhB,OAAO;;kBAES,QAAQ;QAClB,QAAQ;EACd,UAAU;;;;QAIJ,QAAQ;EACd,QAAQ;;;EAGR,kBAAkB;;;;;;;;;;;;;;;;;;;;wEAoBoD,CAAC;AACzE,CAAC"}
@@ -0,0 +1,7 @@
1
+ import type { CacheMode } from '../cache.js';
2
+ export interface SuggestEdgeCasesInput {
3
+ sourceCode: string;
4
+ language?: string;
5
+ }
6
+ export declare function handleSuggestEdgeCases(input: SuggestEdgeCasesInput, cacheMode: CacheMode): Promise<string>;
7
+ //# sourceMappingURL=suggest-edge-cases.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"suggest-edge-cases.d.ts","sourceRoot":"","sources":["../../src/tools/suggest-edge-cases.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAE7C,MAAM,WAAW,qBAAqB;IACpC,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,wBAAsB,sBAAsB,CAC1C,KAAK,EAAE,qBAAqB,EAC5B,SAAS,EAAE,SAAS,GACnB,OAAO,CAAC,MAAM,CAAC,CASjB"}
@@ -0,0 +1,12 @@
1
+ import { analyzeWithClaude } from '../claude-client.js';
2
+ import { buildSuggestEdgeCasesPrompt } from './prompts.js';
3
+ export async function handleSuggestEdgeCases(input, cacheMode) {
4
+ const language = input.language ?? 'typescript';
5
+ const prompt = buildSuggestEdgeCasesPrompt(input.sourceCode, language);
6
+ const inputs = {
7
+ sourceCode: input.sourceCode,
8
+ language,
9
+ };
10
+ return analyzeWithClaude('suggest_edge_cases', prompt, inputs, cacheMode);
11
+ }
12
+ //# sourceMappingURL=suggest-edge-cases.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"suggest-edge-cases.js","sourceRoot":"","sources":["../../src/tools/suggest-edge-cases.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,EAAE,2BAA2B,EAAE,MAAM,cAAc,CAAC;AAQ3D,MAAM,CAAC,KAAK,UAAU,sBAAsB,CAC1C,KAA4B,EAC5B,SAAoB;IAEpB,MAAM,QAAQ,GAAG,KAAK,CAAC,QAAQ,IAAI,YAAY,CAAC;IAChD,MAAM,MAAM,GAAG,2BAA2B,CAAC,KAAK,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC;IACvE,MAAM,MAAM,GAAG;QACb,UAAU,EAAE,KAAK,CAAC,UAAU;QAC5B,QAAQ;KACT,CAAC;IAEF,OAAO,iBAAiB,CAAC,oBAAoB,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,CAAC,CAAC;AAC5E,CAAC"}
@@ -0,0 +1,8 @@
1
+ import type { CacheMode } from '../cache.js';
2
+ export interface ValidateTestsInput {
3
+ sourceCode: string;
4
+ testCode: string;
5
+ language?: string;
6
+ }
7
+ export declare function handleValidateTests(input: ValidateTestsInput, cacheMode: CacheMode): Promise<string>;
8
+ //# sourceMappingURL=validate-tests.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validate-tests.d.ts","sourceRoot":"","sources":["../../src/tools/validate-tests.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAE7C,MAAM,WAAW,kBAAkB;IACjC,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,wBAAsB,mBAAmB,CACvC,KAAK,EAAE,kBAAkB,EACzB,SAAS,EAAE,SAAS,GACnB,OAAO,CAAC,MAAM,CAAC,CAUjB"}
@@ -0,0 +1,13 @@
1
+ import { analyzeWithClaude } from '../claude-client.js';
2
+ import { buildValidateTestsPrompt } from './prompts.js';
3
+ export async function handleValidateTests(input, cacheMode) {
4
+ const language = input.language ?? 'typescript';
5
+ const prompt = buildValidateTestsPrompt(input.sourceCode, input.testCode, language);
6
+ const inputs = {
7
+ sourceCode: input.sourceCode,
8
+ testCode: input.testCode,
9
+ language,
10
+ };
11
+ return analyzeWithClaude('validate_tests', prompt, inputs, cacheMode);
12
+ }
13
+ //# sourceMappingURL=validate-tests.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validate-tests.js","sourceRoot":"","sources":["../../src/tools/validate-tests.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,EAAE,wBAAwB,EAAE,MAAM,cAAc,CAAC;AASxD,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACvC,KAAyB,EACzB,SAAoB;IAEpB,MAAM,QAAQ,GAAG,KAAK,CAAC,QAAQ,IAAI,YAAY,CAAC;IAChD,MAAM,MAAM,GAAG,wBAAwB,CAAC,KAAK,CAAC,UAAU,EAAE,KAAK,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;IACpF,MAAM,MAAM,GAAG;QACb,UAAU,EAAE,KAAK,CAAC,UAAU;QAC5B,QAAQ,EAAE,KAAK,CAAC,QAAQ;QACxB,QAAQ;KACT,CAAC;IAEF,OAAO,iBAAiB,CAAC,gBAAgB,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,CAAC,CAAC;AACxE,CAAC"}
@@ -0,0 +1,10 @@
1
+ {
2
+ "toolName": "coverage_gap_analysis",
3
+ "inputs": {
4
+ "sourceCode": "function divide(a: number, b: number): number {\n return a / b;\n}",
5
+ "testCode": "test('divides two numbers', () => {\n expect(divide(10, 2)).toBe(5);\n});",
6
+ "language": "typescript"
7
+ },
8
+ "response": "{\n \"coverageScore\": 2,\n \"gaps\": [\n {\n \"type\": \"condition\",\n \"location\": \"divide(a, b) — b parameter value space\",\n \"description\": \"Division by zero (b=0) is never tested. In JavaScript, this returns Infinity rather than throwing, which may mask bugs in calling code.\",\n \"severity\": \"critical\",\n \"suggestedTest\": \"test('division by zero returns Infinity', () => {\\n expect(divide(5, 0)).toBe(Infinity);\\n expect(divide(-5, 0)).toBe(-Infinity);\\n});\"\n },\n {\n \"type\": \"condition\",\n \"location\": \"divide(a, b) — a parameter value space\",\n \"description\": \"Zero as dividend (a=0) is never tested. divide(0, n) should always return 0 (except 0/0 = NaN).\",\n \"severity\": \"high\",\n \"suggestedTest\": \"test('zero dividend', () => {\\n expect(divide(0, 5)).toBe(0);\\n expect(divide(0, -3)).toBe(-0);\\n});\"\n },\n {\n \"type\": \"path\",\n \"location\": \"divide(a, b) — negative number paths\",\n \"description\": \"No tests exercise negative inputs. Sign handling has 4 combinations: (+,+), (+,-), (-,+), (-,-) — only (+,+) is tested.\",\n \"severity\": \"high\",\n \"suggestedTest\": \"test('negative number combinations', () => {\\n expect(divide(-10, 2)).toBe(-5);\\n expect(divide(10, -2)).toBe(-5);\\n expect(divide(-10, -2)).toBe(5);\\n});\"\n },\n {\n \"type\": \"return_value\",\n \"location\": \"divide(a, b) — non-integer return values\",\n \"description\": \"Only integer results are tested. Floating-point division (e.g., 7/2=3.5) and repeating decimals (1/3) are not exercised.\",\n \"severity\": \"medium\",\n \"suggestedTest\": \"test('floating point results', () => {\\n expect(divide(7, 2)).toBe(3.5);\\n expect(divide(1, 3)).toBeCloseTo(0.3333, 4);\\n});\"\n },\n {\n \"type\": \"exception\",\n \"location\": \"divide(a, b) — special IEEE 754 values\",\n \"description\": \"NaN and Infinity inputs are never tested. NaN propagates (NaN/x = NaN), and Infinity has special rules (Inf/Inf = NaN).\",\n \"severity\": \"medium\",\n \"suggestedTest\": \"test('special values', () => {\\n expect(divide(NaN, 5)).toBeNaN();\\n expect(divide(Infinity, 2)).toBe(Infinity);\\n expect(divide(Infinity, Infinity)).toBeNaN();\\n});\"\n }\n ],\n \"testedPaths\": [\n \"Basic positive integer division with exact integer result (10 / 2 = 5)\"\n ],\n \"summary\": \"Only 1 of approximately 8-10 meaningful code paths is tested. Critical gaps include division by zero, negative numbers, and special IEEE 754 values. The single test creates a false sense of confidence.\"\n}",
9
+ "cachedAt": "2026-03-20T14:30:00.000Z"
10
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "toolName": "suggest_edge_cases",
3
+ "inputs": {
4
+ "sourceCode": "function divide(a: number, b: number): number {\n return a / b;\n}",
5
+ "language": "typescript"
6
+ },
7
+ "response": "{\n \"functionName\": \"divide\",\n \"edgeCases\": [\n {\n \"category\": \"HAPPY_PATH\",\n \"description\": \"Basic integer division with exact result\",\n \"input\": \"divide(10, 2)\",\n \"expectedBehavior\": \"Returns 5\",\n \"priority\": \"high\"\n },\n {\n \"category\": \"HAPPY_PATH\",\n \"description\": \"Division resulting in a decimal\",\n \"input\": \"divide(7, 2)\",\n \"expectedBehavior\": \"Returns 3.5\",\n \"priority\": \"high\"\n },\n {\n \"category\": \"ERROR_HANDLING\",\n \"description\": \"Division by zero — JavaScript returns Infinity, not an error\",\n \"input\": \"divide(5, 0)\",\n \"expectedBehavior\": \"Returns Infinity. Consider whether the function should throw instead.\",\n \"priority\": \"critical\"\n },\n {\n \"category\": \"ERROR_HANDLING\",\n \"description\": \"Zero divided by zero — undefined mathematical operation\",\n \"input\": \"divide(0, 0)\",\n \"expectedBehavior\": \"Returns NaN\",\n \"priority\": \"critical\"\n },\n {\n \"category\": \"BOUNDARY\",\n \"description\": \"Negative dividend\",\n \"input\": \"divide(-10, 2)\",\n \"expectedBehavior\": \"Returns -5\",\n \"priority\": \"high\"\n },\n {\n \"category\": \"BOUNDARY\",\n \"description\": \"Negative divisor\",\n \"input\": \"divide(10, -2)\",\n \"expectedBehavior\": \"Returns -5\",\n \"priority\": \"high\"\n },\n {\n \"category\": \"BOUNDARY\",\n \"description\": \"Both negative\",\n \"input\": \"divide(-10, -2)\",\n \"expectedBehavior\": \"Returns 5 (negative signs cancel)\",\n \"priority\": \"medium\"\n },\n {\n \"category\": \"BOUNDARY\",\n \"description\": \"Zero as dividend\",\n \"input\": \"divide(0, 5)\",\n \"expectedBehavior\": \"Returns 0\",\n \"priority\": \"high\"\n },\n {\n \"category\": \"BOUNDARY\",\n \"description\": \"Very large numbers\",\n \"input\": \"divide(Number.MAX_SAFE_INTEGER, 1)\",\n \"expectedBehavior\": \"Returns Number.MAX_SAFE_INTEGER without precision loss\",\n \"priority\": \"medium\"\n },\n {\n \"category\": \"BOUNDARY\",\n \"description\": \"Very small divisor approaching zero\",\n \"input\": \"divide(1, Number.MIN_VALUE)\",\n \"expectedBehavior\": \"Returns Infinity (number too large to represent)\",\n \"priority\": \"medium\"\n },\n {\n \"category\": \"TYPE_COERCION\",\n \"description\": \"NaN as input\",\n \"input\": \"divide(NaN, 5)\",\n \"expectedBehavior\": \"Returns NaN (NaN propagates through arithmetic)\",\n \"priority\": \"high\"\n },\n {\n \"category\": \"TYPE_COERCION\",\n \"description\": \"Infinity as dividend\",\n \"input\": \"divide(Infinity, 2)\",\n \"expectedBehavior\": \"Returns Infinity\",\n \"priority\": \"medium\"\n },\n {\n \"category\": \"TYPE_COERCION\",\n \"description\": \"Infinity divided by Infinity\",\n \"input\": \"divide(Infinity, Infinity)\",\n \"expectedBehavior\": \"Returns NaN\",\n \"priority\": \"medium\"\n },\n {\n \"category\": \"TYPE_COERCION\",\n \"description\": \"Negative zero\",\n \"input\": \"divide(-0, 1)\",\n \"expectedBehavior\": \"Returns -0 (Object.is(result, -0) should be true)\",\n \"priority\": \"low\"\n },\n {\n \"category\": \"BOUNDARY\",\n \"description\": \"Floating point precision loss\",\n \"input\": \"divide(0.1, 0.3)\",\n \"expectedBehavior\": \"Returns approximately 0.3333... (may have floating point imprecision)\",\n \"priority\": \"medium\"\n }\n ],\n \"totalByCategory\": {\n \"HAPPY_PATH\": 2,\n \"ERROR_HANDLING\": 2,\n \"BOUNDARY\": 6,\n \"TYPE_COERCION\": 4,\n \"INTEGRATION\": 0\n }\n}",
8
+ "cachedAt": "2026-03-20T14:30:00.000Z"
9
+ }
@@ -0,0 +1,10 @@
1
+ {
2
+ "toolName": "validate_tests",
3
+ "inputs": {
4
+ "sourceCode": "function divide(a: number, b: number): number {\n return a / b;\n}",
5
+ "testCode": "test('divides two numbers', () => {\n expect(divide(10, 2)).toBe(5);\n});",
6
+ "language": "typescript"
7
+ },
8
+ "response": "{\n \"overallScore\": 2,\n \"biasWarnings\": [\n {\n \"type\": \"happy_path_bias\",\n \"severity\": \"high\",\n \"description\": \"Tests exclusively cover the happy path with two positive integers producing an exact integer result. No negative, zero, floating-point, or extreme value inputs are tested.\",\n \"suggestion\": \"Add tests with negative numbers, floating-point inputs, very large numbers, and zero values.\"\n },\n {\n \"type\": \"missing_error_handling\",\n \"severity\": \"high\",\n \"description\": \"Division by zero is a critical operation that is completely untested. JavaScript returns Infinity for division by zero rather than throwing, which may be unexpected.\",\n \"suggestion\": \"Add test: expect(divide(5, 0)).toBe(Infinity) and decide if this behavior is acceptable or if the function should throw.\"\n },\n {\n \"type\": \"no_boundary_tests\",\n \"severity\": \"medium\",\n \"description\": \"No tests for boundary values like Number.MAX_SAFE_INTEGER, Number.MIN_SAFE_INTEGER, Number.EPSILON, or very small denominators.\",\n \"suggestion\": \"Add tests for extreme values: divide(Number.MAX_SAFE_INTEGER, 1), divide(1, Number.MIN_VALUE)\"\n },\n {\n \"type\": \"type_assumptions\",\n \"severity\": \"medium\",\n \"description\": \"TypeScript types enforce number inputs at compile time, but runtime behavior with NaN, Infinity, or -0 is untested.\",\n \"suggestion\": \"Add tests: divide(NaN, 2), divide(Infinity, 2), divide(-0, 1)\"\n }\n ],\n \"missingEdgeCases\": [\n {\n \"category\": \"ERROR_HANDLING\",\n \"description\": \"Division by zero returns Infinity instead of throwing\",\n \"testSuggestion\": \"test('division by zero returns Infinity', () => {\\n expect(divide(5, 0)).toBe(Infinity);\\n expect(divide(-5, 0)).toBe(-Infinity);\\n});\"\n },\n {\n \"category\": \"BOUNDARY\",\n \"description\": \"Negative number division\",\n \"testSuggestion\": \"test('handles negative numbers', () => {\\n expect(divide(-10, 2)).toBe(-5);\\n expect(divide(10, -2)).toBe(-5);\\n expect(divide(-10, -2)).toBe(5);\\n});\"\n },\n {\n \"category\": \"BOUNDARY\",\n \"description\": \"Floating point precision\",\n \"testSuggestion\": \"test('floating point division', () => {\\n expect(divide(1, 3)).toBeCloseTo(0.3333, 4);\\n expect(divide(0.1, 0.3)).toBeCloseTo(0.3333, 4);\\n});\"\n },\n {\n \"category\": \"TYPE_COERCION\",\n \"description\": \"NaN propagation\",\n \"testSuggestion\": \"test('NaN inputs return NaN', () => {\\n expect(divide(NaN, 5)).toBeNaN();\\n expect(divide(5, NaN)).toBeNaN();\\n});\"\n },\n {\n \"category\": \"TYPE_COERCION\",\n \"description\": \"Infinity arithmetic\",\n \"testSuggestion\": \"test('Infinity inputs', () => {\\n expect(divide(Infinity, 2)).toBe(Infinity);\\n expect(divide(Infinity, Infinity)).toBeNaN();\\n});\"\n },\n {\n \"category\": \"BOUNDARY\",\n \"description\": \"Zero divided by zero\",\n \"testSuggestion\": \"test('0/0 returns NaN', () => {\\n expect(divide(0, 0)).toBeNaN();\\n});\"\n }\n ],\n \"summary\": \"Tests are critically inadequate — only a single happy-path case is tested. Division by zero, negative numbers, floating-point precision, NaN/Infinity propagation, and boundary values are all untested. The test suite provides a false sense of security.\"\n}",
9
+ "cachedAt": "2026-03-20T14:30:00.000Z"
10
+ }
package/package.json ADDED
@@ -0,0 +1,53 @@
1
+ {
2
+ "name": "ai-validation-mcp",
3
+ "version": "0.1.0",
4
+ "description": "MCP Server for independent AI code validation - analyzes tests for bias, missing edge cases, and coverage gaps",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "bin": {
8
+ "ai-validation-mcp": "dist/index.js"
9
+ },
10
+ "files": [
11
+ "dist",
12
+ "fixtures"
13
+ ],
14
+ "scripts": {
15
+ "build": "tsc",
16
+ "start": "node dist/index.js",
17
+ "start:live": "node dist/index.js --live",
18
+ "dev": "tsx src/index.ts",
19
+ "test": "vitest run",
20
+ "lint": "eslint src/ tests/",
21
+ "format": "prettier --write 'src/**/*.ts' 'tests/**/*.ts'",
22
+ "prepublishOnly": "npm run build"
23
+ },
24
+ "keywords": [
25
+ "mcp",
26
+ "model-context-protocol",
27
+ "ai-validation",
28
+ "code-review",
29
+ "test-validation",
30
+ "edge-cases",
31
+ "coverage-gaps",
32
+ "ai-tools"
33
+ ],
34
+ "author": "Friedrich Wagner",
35
+ "license": "MIT",
36
+ "dependencies": {
37
+ "@anthropic-ai/sdk": "^0.39.0",
38
+ "@modelcontextprotocol/sdk": "^1.7.0",
39
+ "dotenv": "^16.4.0"
40
+ },
41
+ "devDependencies": {
42
+ "@typescript-eslint/eslint-plugin": "^8.0.0",
43
+ "@typescript-eslint/parser": "^8.0.0",
44
+ "eslint": "^8.57.0",
45
+ "prettier": "^3.4.0",
46
+ "tsx": "^4.19.0",
47
+ "typescript": "^5.7.0",
48
+ "vitest": "^3.0.0"
49
+ },
50
+ "engines": {
51
+ "node": ">=18"
52
+ }
53
+ }