@skyramp/mcp 0.0.37 → 0.0.39
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/build/index.js +14 -0
- package/build/prompts/test-recommendation/repository-analysis-prompt.js +287 -0
- package/build/prompts/test-recommendation/test-mapping-prompt.js +266 -0
- package/build/prompts/test-recommendation/test-recommendation-prompt.js +125 -0
- package/build/prompts/testGenerationPrompt.js +7 -0
- package/build/services/TestGenerationService.js +0 -6
- package/build/tools/executeSkyrampTestTool.js +7 -1
- package/build/tools/generate-tests/generateIntegrationRestTool.js +4 -3
- package/build/tools/test-recommendation/analyzeRepositoryTool.js +96 -0
- package/build/tools/test-recommendation/mapTestsTool.js +195 -0
- package/build/tools/test-recommendation/recommendTestsTool.js +131 -0
- package/build/types/RepositoryAnalysis.js +106 -0
- package/build/types/TestMapping.js +173 -0
- package/build/types/TestRecommendation.js +52 -0
- package/build/types/TestTypes.js +1 -1
- package/build/utils/scoring-engine.js +262 -0
- package/package.json +2 -2
|
@@ -18,6 +18,13 @@ export function registerTestGenerationPrompt(mcpServer) {
|
|
|
18
18
|
- CRITICAL: UI, INTEGRATION, E2E TESTS MUST BE MODULARIZED USING skyramp_modularization TOOL. ALWAYS ADD A TASK TO MODULARIZE THE TEST USING skyramp_modularization TOOL AFTER GENERATING THESE(UI, INTEGRATION, E2E) TESTS.
|
|
19
19
|
- **CRITICAL: skyramp_reuse_code TOOL MUST BE CALLED IF DURING THE TEST GENERATION THE CODE REUSE FLAG IS SET TO TRUE EXPLICITLY BY THE USER.**
|
|
20
20
|
|
|
21
|
+
**MANDATORY RULES**:
|
|
22
|
+
1. **Priority Scores Must Remain Unchanged**: When a test type is missing required inputs (e.g., Playwright recordings, traces), **DO NOT**:
|
|
23
|
+
- Mark the test as "blocked" in the output
|
|
24
|
+
- Adjust or reduce the priority score
|
|
25
|
+
- Exclude it from recommendations if it ranks in the top 7
|
|
26
|
+
2. DO NOT CREATE ANY .json or .md file during repository analysis, test mapping, or test recommendation.
|
|
27
|
+
|
|
21
28
|
**CONTRACT TEST:**
|
|
22
29
|
- Purpose: Ensures a service is properly communicating with another service
|
|
23
30
|
- Requirements: At least ONE of the following combinations:
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { SkyrampClient } from "@skyramp/skyramp";
|
|
2
2
|
import { analyzeOpenAPIWithGivenEndpoint } from "../utils/analyze-openapi.js";
|
|
3
|
-
import * as fs from "fs";
|
|
4
3
|
import { getPathParameterValidationError, OUTPUT_DIR_FIELD_NAME, PATH_PARAMS_FIELD_NAME, QUERY_PARAMS_FIELD_NAME, FORM_PARAMS_FIELD_NAME, validateParams, validatePath, validateRequestData, TELEMETRY_entrypoint_FIELD_NAME, } from "../utils/utils.js";
|
|
5
4
|
import { getLanguageSteps } from "../utils/language-helper.js";
|
|
6
5
|
import { logger } from "../utils/logger.js";
|
|
@@ -27,11 +26,6 @@ export class TestGenerationService {
|
|
|
27
26
|
if (apiAnalysisResult) {
|
|
28
27
|
return apiAnalysisResult;
|
|
29
28
|
}
|
|
30
|
-
if (params.scenarioFile) {
|
|
31
|
-
//read file and convert and pass to generateOptions as `rawTrace`
|
|
32
|
-
const scenarioFile = await fs.promises.readFile(params.scenarioFile, "utf8");
|
|
33
|
-
generateOptions.rawTrace = scenarioFile;
|
|
34
|
-
}
|
|
35
29
|
const result = await this.executeGeneration(generateOptions);
|
|
36
30
|
const testType = this.getTestType();
|
|
37
31
|
const languageSteps = getLanguageSteps({
|
|
@@ -5,7 +5,7 @@ import { Writable } from "stream";
|
|
|
5
5
|
import { logger } from "../utils/logger.js";
|
|
6
6
|
import { stripVTControlCharacters } from "util";
|
|
7
7
|
import fs from "fs";
|
|
8
|
-
const EXECUTOR_DOCKER_IMAGE = "skyramp/executor:v1.2.
|
|
8
|
+
const EXECUTOR_DOCKER_IMAGE = "skyramp/executor:v1.2.29";
|
|
9
9
|
const DOCKER_PLATFORM = "linux/amd64";
|
|
10
10
|
export function registerExecuteSkyrampTestTool(server) {
|
|
11
11
|
server.registerTool("skyramp_execute_test", {
|
|
@@ -105,6 +105,12 @@ For detailed documentation visit: https://www.skyramp.dev/docs/quickstart`,
|
|
|
105
105
|
"SKYRAMP_TEST_TOKEN=" + params.token,
|
|
106
106
|
"SKYRAMP_IN_DOCKER=true",
|
|
107
107
|
];
|
|
108
|
+
if (process.env.SKYRAMP_DEBUG) {
|
|
109
|
+
env.push("SKYRAMP_DEBUG=" + process.env.SKYRAMP_DEBUG);
|
|
110
|
+
}
|
|
111
|
+
if (process.env.API_KEY) {
|
|
112
|
+
env.push("API_KEY=" + process.env.API_KEY);
|
|
113
|
+
}
|
|
108
114
|
var output = "";
|
|
109
115
|
class DockerStream extends Writable {
|
|
110
116
|
_write(data, encode, cb) {
|
|
@@ -11,12 +11,13 @@ const integrationTestSchema = z
|
|
|
11
11
|
trace: baseTraceSchema.shape.trace.optional(),
|
|
12
12
|
include: baseTraceSchema.shape.include.optional(),
|
|
13
13
|
exclude: baseTraceSchema.shape.exclude.optional(),
|
|
14
|
-
endpointURL: baseTestSchema.endpointURL.default(""),
|
|
15
|
-
...codeRefactoringSchema.shape,
|
|
16
14
|
scenarioFile: z
|
|
17
15
|
.string()
|
|
18
16
|
.describe("Path to the scenario file to be used for test generation. This file is generated by the skyramp_scenario_test_generation tool.")
|
|
19
17
|
.optional(),
|
|
18
|
+
...codeRefactoringSchema.shape,
|
|
19
|
+
...baseTestSchema,
|
|
20
|
+
endpointURL: baseTestSchema.endpointURL.default(""),
|
|
20
21
|
})
|
|
21
22
|
.omit({ method: true }).shape;
|
|
22
23
|
export class IntegrationTestService extends TestGenerationService {
|
|
@@ -28,7 +29,7 @@ export class IntegrationTestService extends TestGenerationService {
|
|
|
28
29
|
...super.buildBaseGenerationOptions(params),
|
|
29
30
|
responseData: params.responseData,
|
|
30
31
|
playwrightInput: params.playwrightInput,
|
|
31
|
-
|
|
32
|
+
trace: params.scenarioFile,
|
|
32
33
|
};
|
|
33
34
|
}
|
|
34
35
|
async handleApiAnalysis(params, generateOptions) {
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { logger } from "../../utils/logger.js";
|
|
3
|
+
import { getRepositoryAnalysisPrompt } from "../../prompts/test-recommendation/repository-analysis-prompt.js";
|
|
4
|
+
/**
|
|
5
|
+
* Analyze Repository Tool
|
|
6
|
+
* MCP tool for comprehensive repository analysis
|
|
7
|
+
*/
|
|
8
|
+
const analyzeRepositorySchema = z.object({
|
|
9
|
+
repositoryPath: z
|
|
10
|
+
.string()
|
|
11
|
+
.describe("Absolute path to the repository to analyze (e.g., /path/to/my-repo)"),
|
|
12
|
+
scanDepth: z
|
|
13
|
+
.enum(["quick", "full"])
|
|
14
|
+
.default("full")
|
|
15
|
+
.describe("Analysis depth: 'quick' for basic info, 'full' for comprehensive analysis"),
|
|
16
|
+
focusAreas: z
|
|
17
|
+
.array(z.string())
|
|
18
|
+
.optional()
|
|
19
|
+
.describe("Optional: Specific areas to focus on (e.g., ['api', 'frontend', 'infrastructure'])"),
|
|
20
|
+
});
|
|
21
|
+
export function registerAnalyzeRepositoryTool(server) {
|
|
22
|
+
server.registerTool("skyramp_analyze_repository", {
|
|
23
|
+
description: `Analyze a code repository to understand its structure, technology stack, and testing readiness.
|
|
24
|
+
|
|
25
|
+
This tool performs comprehensive repository analysis including:
|
|
26
|
+
- Project type classification (REST API, Frontend, Full-stack, Microservices, etc.)
|
|
27
|
+
- Technology stack identification (languages, frameworks, dependencies)
|
|
28
|
+
- Artifact discovery (OpenAPI specs, Playwright recordings, trace files)
|
|
29
|
+
- API endpoint detection and cataloging
|
|
30
|
+
- Authentication mechanism analysis
|
|
31
|
+
- Infrastructure configuration (Docker, Kubernetes, CI/CD)
|
|
32
|
+
- Existing test coverage assessment
|
|
33
|
+
|
|
34
|
+
The analysis provides structured data that can be used by skyramp_map_tests to calculate test priorities.
|
|
35
|
+
|
|
36
|
+
Example usage:
|
|
37
|
+
\`\`\`
|
|
38
|
+
{
|
|
39
|
+
"repositoryPath": "/Users/dev/my-api",
|
|
40
|
+
"scanDepth": "full"
|
|
41
|
+
}
|
|
42
|
+
\`\`\`
|
|
43
|
+
|
|
44
|
+
**CRITICAL RULES**:
|
|
45
|
+
- DO NOT CREATE ANY .json or .md file during repository analysis.
|
|
46
|
+
|
|
47
|
+
Output: Detailed RepositoryAnalysis JSON object with all repository characteristics.`,
|
|
48
|
+
inputSchema: analyzeRepositorySchema.shape,
|
|
49
|
+
}, async (params) => {
|
|
50
|
+
try {
|
|
51
|
+
logger.info("Analyze repository tool invoked", {
|
|
52
|
+
repositoryPath: params.repositoryPath,
|
|
53
|
+
scanDepth: params.scanDepth,
|
|
54
|
+
});
|
|
55
|
+
// Return prompt for LLM to execute
|
|
56
|
+
const analysisPrompt = getRepositoryAnalysisPrompt(params.repositoryPath);
|
|
57
|
+
return {
|
|
58
|
+
content: [
|
|
59
|
+
{
|
|
60
|
+
type: "text",
|
|
61
|
+
text: `# Repository Analysis Request
|
|
62
|
+
|
|
63
|
+
Please analyze the repository at: \`${params.repositoryPath}\`
|
|
64
|
+
|
|
65
|
+
${params.focusAreas ? `Focus on: ${params.focusAreas.join(", ")}\n` : ""}
|
|
66
|
+
|
|
67
|
+
Use the following tools to gather information:
|
|
68
|
+
- \`codebase_search\` - to understand code patterns and structure
|
|
69
|
+
- \`grep\` - to find specific patterns (route definitions, dependencies, etc.)
|
|
70
|
+
- \`glob_file_search\` - to find files by pattern (OpenAPI specs, config files, trace files, etc.)
|
|
71
|
+
- \`read_file\` - to read specific files (package.json, requirements.txt, etc.)
|
|
72
|
+
- \`list_dir\` - to explore directory structure
|
|
73
|
+
|
|
74
|
+
${analysisPrompt}
|
|
75
|
+
|
|
76
|
+
After gathering all information, return a JSON object matching the RepositoryAnalysis structure shown in the prompt above.`,
|
|
77
|
+
},
|
|
78
|
+
],
|
|
79
|
+
isError: false,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
catch (error) {
|
|
83
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
84
|
+
logger.error("Analyze repository tool failed", { error: errorMessage });
|
|
85
|
+
return {
|
|
86
|
+
content: [
|
|
87
|
+
{
|
|
88
|
+
type: "text",
|
|
89
|
+
text: `Error analyzing repository: ${errorMessage}`,
|
|
90
|
+
},
|
|
91
|
+
],
|
|
92
|
+
isError: true,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
}
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { repositoryAnalysisSchema } from "../../types/RepositoryAnalysis.js";
|
|
3
|
+
import { TestType } from "../../types/TestTypes.js";
|
|
4
|
+
import { ScoringEngine } from "../../utils/scoring-engine.js";
|
|
5
|
+
import { logger } from "../../utils/logger.js";
|
|
6
|
+
/**
|
|
7
|
+
* Map Tests Tool
|
|
8
|
+
* MCP tool for calculating test priority scores
|
|
9
|
+
*/
|
|
10
|
+
const mapTestsSchema = z.object({
|
|
11
|
+
analysisReport: z
|
|
12
|
+
.union([z.string(), repositoryAnalysisSchema])
|
|
13
|
+
.describe("Repository analysis result (JSON string or object from skyramp_analyze_repository)"),
|
|
14
|
+
customWeights: z
|
|
15
|
+
.record(z.number())
|
|
16
|
+
.optional()
|
|
17
|
+
.describe("Optional: Custom weight multipliers for specific test types (e.g., {'load': 1.5, 'fuzz': 1.3})"),
|
|
18
|
+
focusTestTypes: z
|
|
19
|
+
.array(z.nativeEnum(TestType))
|
|
20
|
+
.optional()
|
|
21
|
+
.describe("Optional: Only evaluate specific test types (e.g., ['integration', 'smoke'])"),
|
|
22
|
+
});
|
|
23
|
+
export function registerMapTestsTool(server) {
|
|
24
|
+
server.registerTool("skyramp_map_tests", {
|
|
25
|
+
description: `Calculate priority scores for Skyramp test types based on repository analysis.
|
|
26
|
+
|
|
27
|
+
This tool evaluates all test types (E2E, UI, Integration, Load, Fuzz, Contract, Smoke) and calculates priority scores using:
|
|
28
|
+
- Base impact scores (E2E: 100, UI: 95, Integration: 85, etc.)
|
|
29
|
+
- Context multipliers based on repository characteristics
|
|
30
|
+
- Feasibility assessment based on available artifacts
|
|
31
|
+
|
|
32
|
+
The scoring algorithm considers:
|
|
33
|
+
- Project type (full-stack, microservices, REST API, etc.)
|
|
34
|
+
- Infrastructure (Kubernetes, Docker Compose, CI/CD)
|
|
35
|
+
- Existing test coverage gaps
|
|
36
|
+
- Available artifacts (OpenAPI specs, Playwright recordings)
|
|
37
|
+
- Security requirements (authentication, sensitive data)
|
|
38
|
+
|
|
39
|
+
Example usage:
|
|
40
|
+
\`\`\`
|
|
41
|
+
{
|
|
42
|
+
"analysisReport": "<RepositoryAnalysis JSON from skyramp_analyze_repository>",
|
|
43
|
+
"customWeights": {
|
|
44
|
+
"load": 1.5,
|
|
45
|
+
"fuzz": 1.3
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
\`\`\`
|
|
49
|
+
|
|
50
|
+
**CRITICAL RULES**:
|
|
51
|
+
- DO NOT CREATE ANY .json or .md file during test mapping.
|
|
52
|
+
|
|
53
|
+
Output: TestMappingResult JSON with priority scores, feasibility, and reasoning for each test type.`,
|
|
54
|
+
inputSchema: mapTestsSchema.shape,
|
|
55
|
+
}, async (params) => {
|
|
56
|
+
try {
|
|
57
|
+
logger.info("Map tests tool invoked");
|
|
58
|
+
// Parse and validate analysis report
|
|
59
|
+
let analysis = params.analysisReport;
|
|
60
|
+
if (typeof analysis === "string") {
|
|
61
|
+
try {
|
|
62
|
+
analysis = JSON.parse(analysis);
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
throw new Error("analysisReport must be valid JSON string or RepositoryAnalysis object. JSON parsing failed.");
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
// Validate the analysis object against the schema
|
|
69
|
+
const validationResult = repositoryAnalysisSchema.safeParse(analysis);
|
|
70
|
+
if (!validationResult.success) {
|
|
71
|
+
const errors = validationResult.error.errors
|
|
72
|
+
.map((e) => `${e.path.join(".")}: ${e.message}`)
|
|
73
|
+
.join("; ");
|
|
74
|
+
throw new Error(`analysisReport validation failed: ${errors}`);
|
|
75
|
+
}
|
|
76
|
+
analysis = validationResult.data;
|
|
77
|
+
// Determine which test types to evaluate
|
|
78
|
+
const testTypesToEvaluate = params.focusTestTypes ||
|
|
79
|
+
Object.values(TestType).filter((t) => typeof t === "string");
|
|
80
|
+
// Calculate scores for each test type
|
|
81
|
+
const priorityScores = [];
|
|
82
|
+
for (const testType of testTypesToEvaluate) {
|
|
83
|
+
const score = ScoringEngine.calculateTestScore(testType, analysis);
|
|
84
|
+
// Apply custom weights if provided
|
|
85
|
+
if (params.customWeights && params.customWeights[testType]) {
|
|
86
|
+
score._finalScore *= params.customWeights[testType];
|
|
87
|
+
score.contextMultiplier *= params.customWeights[testType];
|
|
88
|
+
score.reasoning += ` (custom weight: ×${params.customWeights[testType]})`;
|
|
89
|
+
}
|
|
90
|
+
priorityScores.push(score);
|
|
91
|
+
}
|
|
92
|
+
// Sort by final score (descending)
|
|
93
|
+
priorityScores.sort((a, b) => b._finalScore - a._finalScore);
|
|
94
|
+
// Create summary
|
|
95
|
+
const highPriority = [];
|
|
96
|
+
const mediumPriority = [];
|
|
97
|
+
const lowPriority = [];
|
|
98
|
+
for (const score of priorityScores) {
|
|
99
|
+
if (score.feasibility === "not-applicable")
|
|
100
|
+
continue;
|
|
101
|
+
if (score._finalScore >= 80) {
|
|
102
|
+
highPriority.push(score.testType);
|
|
103
|
+
}
|
|
104
|
+
else if (score._finalScore >= 60) {
|
|
105
|
+
mediumPriority.push(score.testType);
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
lowPriority.push(score.testType);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
// Extract context factors
|
|
112
|
+
const contextFactors = { applied: [] };
|
|
113
|
+
if (analysis.infrastructure.hasKubernetes) {
|
|
114
|
+
contextFactors.applied.push({
|
|
115
|
+
factor: "hasKubernetes",
|
|
116
|
+
impact: "Increases load and integration test importance",
|
|
117
|
+
multiplier: 1.2,
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
if (analysis.infrastructure.hasDockerCompose) {
|
|
121
|
+
contextFactors.applied.push({
|
|
122
|
+
factor: "hasDockerCompose",
|
|
123
|
+
impact: "Suggests scaled infrastructure",
|
|
124
|
+
multiplier: 1.1,
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
if (analysis.infrastructure.hasCiCd) {
|
|
128
|
+
contextFactors.applied.push({
|
|
129
|
+
factor: "hasCiCd",
|
|
130
|
+
impact: "Increases smoke test importance",
|
|
131
|
+
multiplier: 1.1,
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
const mapping = {
|
|
135
|
+
priorityScores,
|
|
136
|
+
contextFactors,
|
|
137
|
+
summary: { highPriority, mediumPriority, lowPriority },
|
|
138
|
+
};
|
|
139
|
+
// Format output
|
|
140
|
+
const output = `# Test Priority Mapping
|
|
141
|
+
|
|
142
|
+
## Summary
|
|
143
|
+
- **High Priority**: ${mapping.summary.highPriority.join(", ") || "None"}
|
|
144
|
+
- **Medium Priority**: ${mapping.summary.mediumPriority.join(", ") || "None"}
|
|
145
|
+
- **Low Priority**: ${mapping.summary.lowPriority.join(", ") || "None"}
|
|
146
|
+
|
|
147
|
+
## Test Type Priorities (Ordered by Score)
|
|
148
|
+
|
|
149
|
+
${mapping.priorityScores
|
|
150
|
+
.map((score) => `### ${score.testType.toUpperCase()}
|
|
151
|
+
- **Feasibility**: ${score.feasibility}
|
|
152
|
+
- **Available Artifacts**: ${score.requiredArtifacts.available.join(", ") || "None"}
|
|
153
|
+
- **Missing Artifacts**: ${score.requiredArtifacts.missing.join(", ") || "None"}
|
|
154
|
+
- **Reasoning**: ${score.reasoning}
|
|
155
|
+
`)
|
|
156
|
+
.join("\n")}
|
|
157
|
+
|
|
158
|
+
## Context Factors Applied
|
|
159
|
+
|
|
160
|
+
${mapping.contextFactors.applied
|
|
161
|
+
.map((factor) => `- **${factor.factor}**: ${factor.impact} (×${factor.multiplier})`)
|
|
162
|
+
.join("\n")}
|
|
163
|
+
|
|
164
|
+
## Complete Mapping Result (JSON)
|
|
165
|
+
|
|
166
|
+
\`\`\`json
|
|
167
|
+
${JSON.stringify(mapping, null, 2)}
|
|
168
|
+
\`\`\`
|
|
169
|
+
|
|
170
|
+
**Next Step**: Use \`skyramp_recommend_tests\` with this mapping result to get actionable test recommendations.`;
|
|
171
|
+
return {
|
|
172
|
+
content: [
|
|
173
|
+
{
|
|
174
|
+
type: "text",
|
|
175
|
+
text: output,
|
|
176
|
+
},
|
|
177
|
+
],
|
|
178
|
+
isError: false,
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
catch (error) {
|
|
182
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
183
|
+
logger.error("Map tests tool failed", { error: errorMessage });
|
|
184
|
+
return {
|
|
185
|
+
content: [
|
|
186
|
+
{
|
|
187
|
+
type: "text",
|
|
188
|
+
text: `Error mapping tests: ${errorMessage}`,
|
|
189
|
+
},
|
|
190
|
+
],
|
|
191
|
+
isError: true,
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
});
|
|
195
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { testMappingResultSchema } from "../../types/TestMapping.js";
|
|
3
|
+
import { repositoryAnalysisSchema } from "../../types/RepositoryAnalysis.js";
|
|
4
|
+
import { getTestRecommendationPrompt } from "../../prompts/test-recommendation/test-recommendation-prompt.js";
|
|
5
|
+
import { logger } from "../../utils/logger.js";
|
|
6
|
+
/**
|
|
7
|
+
* Recommend Tests Tool
|
|
8
|
+
* MCP tool for generating actionable test recommendations
|
|
9
|
+
*/
|
|
10
|
+
const recommendTestsSchema = z.object({
|
|
11
|
+
priorityScores: z
|
|
12
|
+
.union([z.string(), testMappingResultSchema])
|
|
13
|
+
.describe("Test mapping result (JSON string or object from skyramp_map_tests)"),
|
|
14
|
+
analysisReport: z
|
|
15
|
+
.union([z.string(), repositoryAnalysisSchema])
|
|
16
|
+
.describe("Repository analysis result (JSON string or object from skyramp_analyze_repository)"),
|
|
17
|
+
topN: z
|
|
18
|
+
.number()
|
|
19
|
+
.default(7)
|
|
20
|
+
.describe("Number of top test types to recommend (default: 5)"),
|
|
21
|
+
minScore: z
|
|
22
|
+
.number()
|
|
23
|
+
.default(60)
|
|
24
|
+
.describe("Minimum score threshold for recommendations (default: 60)"),
|
|
25
|
+
});
|
|
26
|
+
export function registerRecommendTestsTool(server) {
|
|
27
|
+
server.registerTool("skyramp_recommend_tests", {
|
|
28
|
+
description: `Generate actionable test recommendations with ready-to-use generation prompts.
|
|
29
|
+
|
|
30
|
+
This tool takes test priority and repository analysis to create:
|
|
31
|
+
- Ordered list of recommended test types (high/medium/low priority)
|
|
32
|
+
- Specific test scenarios based on actual repository endpoints and flows
|
|
33
|
+
- Artifact availability status and guidance for missing artifacts
|
|
34
|
+
- Step-by-step next actions
|
|
35
|
+
|
|
36
|
+
SAMPLE OUTPUT:
|
|
37
|
+
\`\`\`
|
|
38
|
+
Recommended Tests (Prioritized)
|
|
39
|
+
Priority: <PRIORITY HIGH, MEDIUM, OR LOW>
|
|
40
|
+
1. <TEST TYPE> Tests [**CRITICAL: NEVER MENTION THE SCORE HERE IN THE OUTPUT**]
|
|
41
|
+
Rationale:
|
|
42
|
+
Specific Tests to Create:
|
|
43
|
+
Test 1: <TEST NAME>
|
|
44
|
+
Description: <TEST DESCRIPTION>
|
|
45
|
+
Target Flow: <TARGET FLOW>
|
|
46
|
+
\`\`\`
|
|
47
|
+
|
|
48
|
+
For each recommended test type, you'll get:
|
|
49
|
+
- 2-3 specific tests you can create right now
|
|
50
|
+
- Required vs. available artifacts. THIS SHOULD NOT CHANGE PRIORITIZATION OF THE TESTS.
|
|
51
|
+
- Guidance for creating missing artifacts WITHOUT PROVIDING ANY CLI COMMANDS.
|
|
52
|
+
|
|
53
|
+
**CRITICAL RULES**:
|
|
54
|
+
- THE PRIORITY SHOULD ONLY BE DEFINED AS HIGH, MEDIUM, OR LOW NOTHING ELSE.
|
|
55
|
+
- DO NOT SHOW ANY PRIORITY BREAKDOWN IN THE OUTPUT.
|
|
56
|
+
- DON'T MARK ANY TEST BLOCKED EVEN IF REQUIRED ARTIFACTS ARE MISSING.
|
|
57
|
+
- DO NOT SHOW RESULTS IN .MD OR .JSON OR ANY OTHER FILE FORMAT.
|
|
58
|
+
|
|
59
|
+
Output: TestRecommendation JSON with prioritized, actionable test recommendations.`,
|
|
60
|
+
inputSchema: recommendTestsSchema.shape,
|
|
61
|
+
}, async (params) => {
|
|
62
|
+
try {
|
|
63
|
+
logger.info("Recommend tests tool invoked", {
|
|
64
|
+
topN: params.topN,
|
|
65
|
+
minScore: params.minScore,
|
|
66
|
+
});
|
|
67
|
+
// Parse inputs if they're strings
|
|
68
|
+
let mapping = params.priorityScores;
|
|
69
|
+
let analysis = params.analysisReport;
|
|
70
|
+
if (typeof mapping === "string") {
|
|
71
|
+
try {
|
|
72
|
+
mapping = JSON.parse(mapping);
|
|
73
|
+
}
|
|
74
|
+
catch (error) {
|
|
75
|
+
throw new Error("priorityScores must be valid JSON string or TestMappingResult object");
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
if (typeof analysis === "string") {
|
|
79
|
+
try {
|
|
80
|
+
analysis = JSON.parse(analysis);
|
|
81
|
+
}
|
|
82
|
+
catch (error) {
|
|
83
|
+
throw new Error("analysisReport must be valid JSON string or RepositoryAnalysis object");
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
// Generate the prompt for LLM to create dynamic recommendations
|
|
87
|
+
const prompt = getTestRecommendationPrompt(mapping, analysis, params.topN);
|
|
88
|
+
// Return prompt for LLM to execute
|
|
89
|
+
return {
|
|
90
|
+
content: [
|
|
91
|
+
{
|
|
92
|
+
type: "text",
|
|
93
|
+
text: `# Test Recommendation Request
|
|
94
|
+
|
|
95
|
+
Please generate actionable test recommendations based on the priority scores and repository analysis.
|
|
96
|
+
|
|
97
|
+
${prompt}
|
|
98
|
+
|
|
99
|
+
**Important Guidelines:**
|
|
100
|
+
1. **NO SCORES IN OUTPUT**: The numeric scores are for internal ranking only. DO NOT include "score" field in JSON and DO NOT mention numeric scores in any text (rationale, description, etc.). Use ONLY priority levels: "high", "medium", "low".
|
|
101
|
+
2. Include guidance for missing artifacts with specific Skyramp tool commands
|
|
102
|
+
3. Prioritize quick wins (tests with all artifacts available)
|
|
103
|
+
4. Use actual endpoint paths and file paths from the repository analysis
|
|
104
|
+
|
|
105
|
+
**MANDATORY RULES**:
|
|
106
|
+
- THE PRIORITY SHOULD ONLY BE DEFINED AS HIGH, MEDIUM, OR LOW NOTHING ELSE.
|
|
107
|
+
- DO NOT SHOW ANY PRIORITY BREAKDOWN IN THE OUTPUT.
|
|
108
|
+
- DON'T MARK ANY TEST BLOCKED EVEN IF REQUIRED ARTIFACTS ARE MISSING.
|
|
109
|
+
- DO NOT SHOW RESULTS IN .MD OR .JSON OR ANY OTHER FILE FORMAT.
|
|
110
|
+
|
|
111
|
+
After analyzing the data above, return the complete JSON response following the structure defined in the prompt.`,
|
|
112
|
+
},
|
|
113
|
+
],
|
|
114
|
+
isError: false,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
catch (error) {
|
|
118
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
119
|
+
logger.error("Recommend tests tool failed", { error: errorMessage });
|
|
120
|
+
return {
|
|
121
|
+
content: [
|
|
122
|
+
{
|
|
123
|
+
type: "text",
|
|
124
|
+
text: `Error generating recommendations: ${errorMessage}`,
|
|
125
|
+
},
|
|
126
|
+
],
|
|
127
|
+
isError: true,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
// Zod schemas for validation
|
|
3
|
+
export const analysisMetadataSchema = z.object({
|
|
4
|
+
repositoryName: z.string(),
|
|
5
|
+
analysisDate: z.string(),
|
|
6
|
+
scanDepth: z.enum(["quick", "full"]),
|
|
7
|
+
});
|
|
8
|
+
export const projectClassificationSchema = z.object({
|
|
9
|
+
projectType: z.enum([
|
|
10
|
+
"rest-api",
|
|
11
|
+
"frontend",
|
|
12
|
+
"full-stack",
|
|
13
|
+
"microservices",
|
|
14
|
+
"library",
|
|
15
|
+
"cli",
|
|
16
|
+
"other",
|
|
17
|
+
]),
|
|
18
|
+
primaryLanguage: z.string(),
|
|
19
|
+
primaryFramework: z.string(),
|
|
20
|
+
deploymentPattern: z.enum([
|
|
21
|
+
"microservices",
|
|
22
|
+
"full-stack",
|
|
23
|
+
"containerized-monolith",
|
|
24
|
+
"traditional",
|
|
25
|
+
"unknown",
|
|
26
|
+
]),
|
|
27
|
+
});
|
|
28
|
+
export const repositoryAnalysisSchema = z.object({
|
|
29
|
+
metadata: analysisMetadataSchema,
|
|
30
|
+
projectClassification: projectClassificationSchema,
|
|
31
|
+
technologyStack: z.object({
|
|
32
|
+
languages: z.array(z.string()),
|
|
33
|
+
frameworks: z.array(z.string()),
|
|
34
|
+
runtime: z.string(),
|
|
35
|
+
keyDependencies: z.array(z.object({
|
|
36
|
+
name: z.string(),
|
|
37
|
+
version: z.string(),
|
|
38
|
+
purpose: z.string(),
|
|
39
|
+
})),
|
|
40
|
+
}),
|
|
41
|
+
businessContext: z.object({
|
|
42
|
+
mainPurpose: z.string(),
|
|
43
|
+
userFlows: z.array(z.string()),
|
|
44
|
+
dataFlows: z.array(z.string()),
|
|
45
|
+
integrationPatterns: z.array(z.string()),
|
|
46
|
+
}),
|
|
47
|
+
artifacts: z.object({
|
|
48
|
+
openApiSpecs: z.array(z.object({
|
|
49
|
+
path: z.string(),
|
|
50
|
+
version: z.string(),
|
|
51
|
+
endpointCount: z.number(),
|
|
52
|
+
baseUrl: z.string(),
|
|
53
|
+
authType: z.string(),
|
|
54
|
+
})),
|
|
55
|
+
playwrightRecordings: z.array(z.object({
|
|
56
|
+
path: z.string(),
|
|
57
|
+
description: z.string(),
|
|
58
|
+
})),
|
|
59
|
+
traceFiles: z.array(z.object({
|
|
60
|
+
path: z.string(),
|
|
61
|
+
format: z.string(),
|
|
62
|
+
analyzed: z.boolean().optional(),
|
|
63
|
+
userFlows: z.array(z.string()).optional(),
|
|
64
|
+
})),
|
|
65
|
+
notFound: z.array(z.string()),
|
|
66
|
+
}),
|
|
67
|
+
apiEndpoints: z.object({
|
|
68
|
+
totalCount: z.number(),
|
|
69
|
+
baseUrl: z.string(),
|
|
70
|
+
endpoints: z.array(z.object({
|
|
71
|
+
path: z.string(),
|
|
72
|
+
method: z.string(),
|
|
73
|
+
resourceGroup: z.string(),
|
|
74
|
+
authRequired: z.boolean(),
|
|
75
|
+
sourceFile: z.string().optional(),
|
|
76
|
+
})),
|
|
77
|
+
}),
|
|
78
|
+
authentication: z.object({
|
|
79
|
+
method: z.enum(["bearer", "api-key", "oauth2", "basic", "jwt", "none"]),
|
|
80
|
+
configLocation: z.string(),
|
|
81
|
+
envVarsRequired: z.array(z.string()),
|
|
82
|
+
setupExample: z.string(),
|
|
83
|
+
}),
|
|
84
|
+
infrastructure: z.object({
|
|
85
|
+
isContainerized: z.boolean(),
|
|
86
|
+
hasDockerCompose: z.boolean(),
|
|
87
|
+
hasKubernetes: z.boolean(),
|
|
88
|
+
hasCiCd: z.boolean(),
|
|
89
|
+
ciCdPlatform: z.string().optional(),
|
|
90
|
+
}),
|
|
91
|
+
existingTests: z.object({
|
|
92
|
+
frameworks: z.array(z.string()),
|
|
93
|
+
coverage: z.object({
|
|
94
|
+
unit: z.number(),
|
|
95
|
+
integration: z.number(),
|
|
96
|
+
e2e: z.number(),
|
|
97
|
+
ui: z.number(),
|
|
98
|
+
load: z.number(),
|
|
99
|
+
contract: z.number(),
|
|
100
|
+
smoke: z.number(),
|
|
101
|
+
}),
|
|
102
|
+
testLocations: z.record(z.string()),
|
|
103
|
+
hasCoverageReports: z.boolean(),
|
|
104
|
+
estimatedCoverage: z.number().optional(),
|
|
105
|
+
}),
|
|
106
|
+
});
|