@skyramp/mcp 0.0.59 → 0.0.60-rc.2
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 +32 -5
- package/build/prompts/test-recommendation/analysisOutputPrompt.js +98 -0
- package/build/prompts/test-recommendation/recommendationSections.js +226 -0
- package/build/prompts/test-recommendation/registerRecommendTestsPrompt.js +71 -0
- package/build/prompts/test-recommendation/test-recommendation-prompt.js +166 -104
- package/build/prompts/testGenerationPrompt.js +2 -3
- package/build/prompts/testbot/testbot-prompts.js +96 -93
- package/build/resources/analysisResources.js +254 -0
- package/build/services/ScenarioGenerationService.js +70 -26
- package/build/tools/generate-tests/generateIntegrationRestTool.js +54 -1
- package/build/tools/generate-tests/generateScenarioRestTool.js +8 -5
- package/build/tools/submitReportTool.js +28 -0
- package/build/tools/test-maintenance/stateCleanupTool.js +8 -0
- package/build/tools/test-recommendation/analyzeRepositoryTool.js +349 -217
- package/build/tools/test-recommendation/recommendTestsTool.js +163 -159
- package/build/tools/workspace/initializeWorkspaceTool.js +1 -1
- package/build/types/RepositoryAnalysis.js +99 -12
- package/build/utils/AnalysisStateManager.js +40 -23
- package/build/utils/branchDiff.js +47 -0
- package/build/utils/pr-comment-parser.js +124 -0
- package/build/utils/projectMetadata.js +188 -0
- package/build/utils/projectMetadata.test.js +81 -0
- package/build/utils/repoScanner.js +378 -0
- package/build/utils/routeParsers.js +213 -0
- package/build/utils/routeParsers.test.js +87 -0
- package/build/utils/scenarioDrafting.js +119 -0
- package/build/utils/scenarioDrafting.test.js +66 -0
- package/build/utils/trace-parser.js +166 -0
- package/build/utils/workspaceAuth.js +16 -0
- package/package.json +1 -1
- package/build/prompts/test-recommendation/repository-analysis-prompt.js +0 -326
- package/build/prompts/test-recommendation/test-mapping-prompt.js +0 -266
- package/build/tools/test-recommendation/mapTestsTool.js +0 -243
- package/build/types/TestMapping.js +0 -173
- package/build/utils/scoring-engine.js +0 -380
|
@@ -1,243 +0,0 @@
|
|
|
1
|
-
import { z } from "zod";
|
|
2
|
-
import { readFile } from "fs/promises";
|
|
3
|
-
import { repositoryAnalysisSchema } from "../../types/RepositoryAnalysis.js";
|
|
4
|
-
import { TestType } from "../../types/TestTypes.js";
|
|
5
|
-
import { ScoringEngine } from "../../utils/scoring-engine.js";
|
|
6
|
-
import { StateManager, } from "../../utils/AnalysisStateManager.js";
|
|
7
|
-
import { logger } from "../../utils/logger.js";
|
|
8
|
-
import { AnalyticsService } from "../../services/AnalyticsService.js";
|
|
9
|
-
/**
|
|
10
|
-
* Map Tests Tool
|
|
11
|
-
* MCP tool for calculating test priority scores
|
|
12
|
-
*/
|
|
13
|
-
const mapTestsSchema = z.object({
|
|
14
|
-
stateFile: z
|
|
15
|
-
.string()
|
|
16
|
-
.describe("Path to a state file containing the RepositoryAnalysis JSON (created by skyramp_analyze_repository)."),
|
|
17
|
-
analysisScope: z
|
|
18
|
-
.enum(["full_repo", "current_branch_diff"])
|
|
19
|
-
.default("full_repo")
|
|
20
|
-
.describe("Scope of the analysis that produced the report. Must match the scope used in skyramp_analyze_repository. 'current_branch_diff' scopes recommendations to only the endpoints/code changed in the current branch."),
|
|
21
|
-
customWeights: z
|
|
22
|
-
.record(z.number())
|
|
23
|
-
.optional()
|
|
24
|
-
.describe("Optional: Custom weight multipliers for specific test types (e.g., {'load': 1.5, 'fuzz': 1.3})"),
|
|
25
|
-
focusTestTypes: z
|
|
26
|
-
.array(z.nativeEnum(TestType))
|
|
27
|
-
.optional()
|
|
28
|
-
.describe("Optional: Only evaluate specific test types (e.g., ['integration', 'smoke'])"),
|
|
29
|
-
});
|
|
30
|
-
const TOOL_NAME = "skyramp_map_tests";
|
|
31
|
-
export function registerMapTestsTool(server) {
|
|
32
|
-
server.registerTool(TOOL_NAME, {
|
|
33
|
-
description: `Calculate priority scores for Skyramp test types based on repository analysis.
|
|
34
|
-
|
|
35
|
-
This tool evaluates all test types (E2E, UI, Integration, Load, Fuzz, Contract, Smoke) and calculates priority scores using:
|
|
36
|
-
- Base impact scores (E2E: 100, UI: 95, Integration: 85, etc.)
|
|
37
|
-
- Context multipliers based on repository characteristics
|
|
38
|
-
- Feasibility assessment based on available artifacts
|
|
39
|
-
|
|
40
|
-
The scoring algorithm considers:
|
|
41
|
-
- Project type (full-stack, microservices, REST API, etc.)
|
|
42
|
-
- Infrastructure (Kubernetes, Docker Compose, CI/CD)
|
|
43
|
-
- Existing test coverage gaps
|
|
44
|
-
- Available artifacts (OpenAPI specs, Playwright recordings)
|
|
45
|
-
- Security requirements (authentication, sensitive data)
|
|
46
|
-
|
|
47
|
-
Example usage:
|
|
48
|
-
\`\`\`
|
|
49
|
-
{
|
|
50
|
-
"stateFile": "/tmp/skyramp-recommendation-123456.json",
|
|
51
|
-
"customWeights": {
|
|
52
|
-
"load": 1.5,
|
|
53
|
-
"fuzz": 1.3
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
\`\`\`
|
|
57
|
-
|
|
58
|
-
**OUTPUT**:
|
|
59
|
-
Returns the state file path to pass to skyramp_recommend_tests
|
|
60
|
-
|
|
61
|
-
Output: TestMappingResult with priority scores, and state file path for next step.`,
|
|
62
|
-
inputSchema: mapTestsSchema.shape,
|
|
63
|
-
}, async (params) => {
|
|
64
|
-
let errorResult;
|
|
65
|
-
try {
|
|
66
|
-
logger.info("Map tests tool invoked", {
|
|
67
|
-
hasStateFile: !!params.stateFile,
|
|
68
|
-
});
|
|
69
|
-
if (!params.stateFile) {
|
|
70
|
-
throw new Error("stateFile is required. Run skyramp_analyze_repository first to create one.");
|
|
71
|
-
}
|
|
72
|
-
let analysis;
|
|
73
|
-
let repositoryPath;
|
|
74
|
-
logger.info("Reading analysis from state file", {
|
|
75
|
-
stateFile: params.stateFile,
|
|
76
|
-
});
|
|
77
|
-
try {
|
|
78
|
-
const raw = await readFile(params.stateFile, "utf-8");
|
|
79
|
-
const parsed = JSON.parse(raw);
|
|
80
|
-
if (parsed.metadata?.stateType && parsed.analysis) {
|
|
81
|
-
analysis = parsed.analysis;
|
|
82
|
-
if (parsed.repositoryPath) {
|
|
83
|
-
repositoryPath = parsed.repositoryPath;
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
else {
|
|
87
|
-
analysis = parsed;
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
catch (error) {
|
|
91
|
-
const msg = error instanceof Error ? error.message : String(error);
|
|
92
|
-
throw new Error(`Failed to read analysis from stateFile "${params.stateFile}": ${msg}`);
|
|
93
|
-
}
|
|
94
|
-
// Validate the analysis object against the schema
|
|
95
|
-
const validationResult = repositoryAnalysisSchema.safeParse(analysis);
|
|
96
|
-
if (!validationResult.success) {
|
|
97
|
-
const errors = validationResult.error.errors
|
|
98
|
-
.map((e) => `${e.path.join(".")}: ${e.message}`)
|
|
99
|
-
.join("; ");
|
|
100
|
-
throw new Error(`Analysis validation failed: ${errors}`);
|
|
101
|
-
}
|
|
102
|
-
analysis = validationResult.data;
|
|
103
|
-
// Determine which test types to evaluate
|
|
104
|
-
const testTypesToEvaluate = params.focusTestTypes ||
|
|
105
|
-
Object.values(TestType).filter((t) => typeof t === "string");
|
|
106
|
-
// Calculate scores for each test type
|
|
107
|
-
const analysisScope = params.analysisScope || "full_repo";
|
|
108
|
-
const priorityScores = [];
|
|
109
|
-
for (const testType of testTypesToEvaluate) {
|
|
110
|
-
const score = ScoringEngine.calculateTestScore(testType, analysis, analysisScope);
|
|
111
|
-
// Apply custom weights if provided
|
|
112
|
-
if (params.customWeights && params.customWeights[testType]) {
|
|
113
|
-
score._finalScore *= params.customWeights[testType];
|
|
114
|
-
score.contextMultiplier *= params.customWeights[testType];
|
|
115
|
-
score.reasoning += ` (custom weight: ×${params.customWeights[testType]})`;
|
|
116
|
-
}
|
|
117
|
-
priorityScores.push(score);
|
|
118
|
-
}
|
|
119
|
-
// Sort by final score (descending)
|
|
120
|
-
priorityScores.sort((a, b) => b._finalScore - a._finalScore);
|
|
121
|
-
// Create summary
|
|
122
|
-
const highPriority = [];
|
|
123
|
-
const mediumPriority = [];
|
|
124
|
-
const lowPriority = [];
|
|
125
|
-
for (const score of priorityScores) {
|
|
126
|
-
if (score.feasibility === "not-applicable")
|
|
127
|
-
continue;
|
|
128
|
-
if (score._finalScore >= 80) {
|
|
129
|
-
highPriority.push(score.testType);
|
|
130
|
-
}
|
|
131
|
-
else if (score._finalScore >= 60) {
|
|
132
|
-
mediumPriority.push(score.testType);
|
|
133
|
-
}
|
|
134
|
-
else {
|
|
135
|
-
lowPriority.push(score.testType);
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
// Extract context factors
|
|
139
|
-
const contextFactors = { applied: [] };
|
|
140
|
-
if (analysis.infrastructure.hasKubernetes) {
|
|
141
|
-
contextFactors.applied.push({
|
|
142
|
-
factor: "hasKubernetes",
|
|
143
|
-
impact: "Increases load and integration test importance",
|
|
144
|
-
multiplier: 1.2,
|
|
145
|
-
});
|
|
146
|
-
}
|
|
147
|
-
if (analysis.infrastructure.hasDockerCompose) {
|
|
148
|
-
contextFactors.applied.push({
|
|
149
|
-
factor: "hasDockerCompose",
|
|
150
|
-
impact: "Suggests scaled infrastructure",
|
|
151
|
-
multiplier: 1.1,
|
|
152
|
-
});
|
|
153
|
-
}
|
|
154
|
-
if (analysis.infrastructure.hasCiCd) {
|
|
155
|
-
contextFactors.applied.push({
|
|
156
|
-
factor: "hasCiCd",
|
|
157
|
-
impact: "Increases smoke test importance",
|
|
158
|
-
multiplier: 1.1,
|
|
159
|
-
});
|
|
160
|
-
}
|
|
161
|
-
const mapping = {
|
|
162
|
-
priorityScores,
|
|
163
|
-
contextFactors,
|
|
164
|
-
summary: { highPriority, mediumPriority, lowPriority },
|
|
165
|
-
};
|
|
166
|
-
// Save results using StateManager — reuse session from input stateFile if provided
|
|
167
|
-
const stateManager = params.stateFile
|
|
168
|
-
? StateManager.fromStatePath(params.stateFile)
|
|
169
|
-
: new StateManager("recommendation");
|
|
170
|
-
const stateData = {
|
|
171
|
-
repositoryPath: repositoryPath ?? "",
|
|
172
|
-
analysisScope,
|
|
173
|
-
analysis,
|
|
174
|
-
mapping,
|
|
175
|
-
};
|
|
176
|
-
await stateManager.writeData(stateData, {
|
|
177
|
-
repositoryPath,
|
|
178
|
-
step: "map-tests",
|
|
179
|
-
});
|
|
180
|
-
const stateFilePath = stateManager.getStatePath();
|
|
181
|
-
const sessionId = stateManager.getSessionId();
|
|
182
|
-
const stateSize = await stateManager.getSizeFormatted();
|
|
183
|
-
logger.info(`Saved test mapping to: ${stateFilePath} (${stateSize}), repositoryPath: ${repositoryPath ?? "unknown"}`);
|
|
184
|
-
// Format output
|
|
185
|
-
const output = `# Test Priority Mapping
|
|
186
|
-
|
|
187
|
-
## Summary
|
|
188
|
-
- **High Priority**: ${mapping.summary.highPriority.join(", ") || "None"}
|
|
189
|
-
- **Medium Priority**: ${mapping.summary.mediumPriority.join(", ") || "None"}
|
|
190
|
-
- **Low Priority**: ${mapping.summary.lowPriority.join(", ") || "None"}
|
|
191
|
-
|
|
192
|
-
## Test Type Priorities (Ordered by Score)
|
|
193
|
-
|
|
194
|
-
${mapping.priorityScores
|
|
195
|
-
.map((score) => `### ${score.testType.toUpperCase()}
|
|
196
|
-
- **Feasibility**: ${score.feasibility}
|
|
197
|
-
- **Available Artifacts**: ${score.requiredArtifacts.available.join(", ") || "None"}
|
|
198
|
-
- **Missing Artifacts**: ${score.requiredArtifacts.missing.join(", ") || "None"}
|
|
199
|
-
- **Reasoning**: ${score.reasoning}
|
|
200
|
-
`)
|
|
201
|
-
.join("\n")}
|
|
202
|
-
|
|
203
|
-
## Context Factors Applied
|
|
204
|
-
|
|
205
|
-
${mapping.contextFactors.applied
|
|
206
|
-
.map((factor) => `- **${factor.factor}**: ${factor.impact} (×${factor.multiplier})`)
|
|
207
|
-
.join("\n")}
|
|
208
|
-
|
|
209
|
-
## Results Saved
|
|
210
|
-
|
|
211
|
-
**State File**: \`${stateFilePath}\`
|
|
212
|
-
**Session ID**: \`${sessionId}\`
|
|
213
|
-
|
|
214
|
-
**Next Step**: Call \`skyramp_recommend_tests\` with stateFile: \`${stateFilePath}\``;
|
|
215
|
-
return {
|
|
216
|
-
content: [
|
|
217
|
-
{
|
|
218
|
-
type: "text",
|
|
219
|
-
text: output,
|
|
220
|
-
},
|
|
221
|
-
],
|
|
222
|
-
isError: false,
|
|
223
|
-
};
|
|
224
|
-
}
|
|
225
|
-
catch (error) {
|
|
226
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
227
|
-
logger.error("Map tests tool failed", { error: errorMessage });
|
|
228
|
-
errorResult = {
|
|
229
|
-
content: [
|
|
230
|
-
{
|
|
231
|
-
type: "text",
|
|
232
|
-
text: `Error mapping tests: ${errorMessage}`,
|
|
233
|
-
},
|
|
234
|
-
],
|
|
235
|
-
isError: true,
|
|
236
|
-
};
|
|
237
|
-
return errorResult;
|
|
238
|
-
}
|
|
239
|
-
finally {
|
|
240
|
-
AnalyticsService.pushMCPToolEvent(TOOL_NAME, errorResult, {});
|
|
241
|
-
}
|
|
242
|
-
});
|
|
243
|
-
}
|
|
@@ -1,173 +0,0 @@
|
|
|
1
|
-
import { z } from "zod";
|
|
2
|
-
import { TestType } from "./TestTypes.js";
|
|
3
|
-
// Re-export TestType for convenience
|
|
4
|
-
export { TestType };
|
|
5
|
-
// Base impact scores for each test type
|
|
6
|
-
export const BASE_SCORES = {
|
|
7
|
-
[TestType.E2E]: 100,
|
|
8
|
-
[TestType.UI]: 95,
|
|
9
|
-
[TestType.INTEGRATION]: 90,
|
|
10
|
-
[TestType.LOAD]: 60,
|
|
11
|
-
[TestType.FUZZ]: 50,
|
|
12
|
-
[TestType.CONTRACT]: 80,
|
|
13
|
-
[TestType.SMOKE]: 70,
|
|
14
|
-
};
|
|
15
|
-
export const CONTEXT_RULES = {
|
|
16
|
-
[TestType.E2E]: [
|
|
17
|
-
{
|
|
18
|
-
condition: "full-stack",
|
|
19
|
-
multiplier: 1.2,
|
|
20
|
-
description: "Full-stack apps benefit most from E2E tests",
|
|
21
|
-
},
|
|
22
|
-
{
|
|
23
|
-
condition: "backend-only",
|
|
24
|
-
multiplier: 0.7,
|
|
25
|
-
description: "Limited E2E applicability for backend-only",
|
|
26
|
-
},
|
|
27
|
-
{
|
|
28
|
-
condition: "frontend-spa",
|
|
29
|
-
multiplier: 0.9,
|
|
30
|
-
description: "Slightly reduced without backend",
|
|
31
|
-
},
|
|
32
|
-
{
|
|
33
|
-
condition: "library",
|
|
34
|
-
multiplier: 0,
|
|
35
|
-
description: "Not applicable to libraries",
|
|
36
|
-
},
|
|
37
|
-
],
|
|
38
|
-
[TestType.LOAD]: [
|
|
39
|
-
{
|
|
40
|
-
condition: "hasKubernetes",
|
|
41
|
-
multiplier: 1.2,
|
|
42
|
-
description: "K8s suggests high-traffic expectations",
|
|
43
|
-
},
|
|
44
|
-
{
|
|
45
|
-
condition: "hasDockerCompose",
|
|
46
|
-
multiplier: 1.15,
|
|
47
|
-
description: "Scaled infrastructure indicates traffic needs",
|
|
48
|
-
},
|
|
49
|
-
{
|
|
50
|
-
condition: "cli-or-library",
|
|
51
|
-
multiplier: 0.4,
|
|
52
|
-
description: "Low traffic expected",
|
|
53
|
-
},
|
|
54
|
-
{
|
|
55
|
-
condition: "internal-tool",
|
|
56
|
-
multiplier: 0.4,
|
|
57
|
-
description: "Minimal load",
|
|
58
|
-
},
|
|
59
|
-
],
|
|
60
|
-
[TestType.FUZZ]: [
|
|
61
|
-
{
|
|
62
|
-
condition: "handles-payments",
|
|
63
|
-
multiplier: 1.2,
|
|
64
|
-
description: "Security critical for payments",
|
|
65
|
-
},
|
|
66
|
-
{
|
|
67
|
-
condition: "handles-pii",
|
|
68
|
-
multiplier: 1.2,
|
|
69
|
-
description: "Security critical for PII",
|
|
70
|
-
},
|
|
71
|
-
{
|
|
72
|
-
condition: "oauth2",
|
|
73
|
-
multiplier: 1.15,
|
|
74
|
-
description: "Public API security needs",
|
|
75
|
-
},
|
|
76
|
-
{
|
|
77
|
-
condition: "internal-service",
|
|
78
|
-
multiplier: 0.9,
|
|
79
|
-
description: "Lower security priority",
|
|
80
|
-
},
|
|
81
|
-
],
|
|
82
|
-
[TestType.CONTRACT]: [
|
|
83
|
-
{
|
|
84
|
-
condition: "microservices",
|
|
85
|
-
multiplier: 1.2,
|
|
86
|
-
description: "Critical for service contracts",
|
|
87
|
-
},
|
|
88
|
-
{
|
|
89
|
-
condition: "multiple-services",
|
|
90
|
-
multiplier: 1.15,
|
|
91
|
-
description: "Service interactions important",
|
|
92
|
-
},
|
|
93
|
-
{
|
|
94
|
-
condition: "monolith",
|
|
95
|
-
multiplier: 0.9,
|
|
96
|
-
description: "Less critical",
|
|
97
|
-
},
|
|
98
|
-
],
|
|
99
|
-
[TestType.INTEGRATION]: [
|
|
100
|
-
{
|
|
101
|
-
condition: "microservices",
|
|
102
|
-
multiplier: 1.1,
|
|
103
|
-
description: "Service communication important",
|
|
104
|
-
},
|
|
105
|
-
{
|
|
106
|
-
condition: "has-unit-missing-integration",
|
|
107
|
-
multiplier: 1.2,
|
|
108
|
-
description: "Fill coverage gap",
|
|
109
|
-
},
|
|
110
|
-
{
|
|
111
|
-
condition: "has-integration-tests",
|
|
112
|
-
multiplier: 0.9,
|
|
113
|
-
description: "Less urgent",
|
|
114
|
-
},
|
|
115
|
-
],
|
|
116
|
-
[TestType.UI]: [
|
|
117
|
-
{
|
|
118
|
-
condition: "frontend-spa-or-fullstack",
|
|
119
|
-
multiplier: 1.2,
|
|
120
|
-
description: "Full value for UI apps",
|
|
121
|
-
},
|
|
122
|
-
{
|
|
123
|
-
condition: "backend-only",
|
|
124
|
-
multiplier: 0,
|
|
125
|
-
description: "Not applicable",
|
|
126
|
-
},
|
|
127
|
-
],
|
|
128
|
-
[TestType.SMOKE]: [
|
|
129
|
-
{
|
|
130
|
-
condition: "no-existing-tests",
|
|
131
|
-
multiplier: 1.2,
|
|
132
|
-
description: "Quick wins needed",
|
|
133
|
-
},
|
|
134
|
-
{
|
|
135
|
-
condition: "has-cicd",
|
|
136
|
-
multiplier: 1.1,
|
|
137
|
-
description: "Deployment validation",
|
|
138
|
-
},
|
|
139
|
-
{
|
|
140
|
-
condition: "comprehensive-tests",
|
|
141
|
-
multiplier: 0.8,
|
|
142
|
-
description: "Less valuable",
|
|
143
|
-
},
|
|
144
|
-
],
|
|
145
|
-
};
|
|
146
|
-
// Zod schemas for validation
|
|
147
|
-
export const testPriorityScoreSchema = z.object({
|
|
148
|
-
testType: z.nativeEnum(TestType),
|
|
149
|
-
_baseScore: z.number().min(0).max(120),
|
|
150
|
-
contextMultiplier: z.number().min(0).max(2),
|
|
151
|
-
_finalScore: z.number().min(0).max(150),
|
|
152
|
-
feasibility: z.enum(["high", "medium", "low", "not-applicable"]),
|
|
153
|
-
requiredArtifacts: z.object({
|
|
154
|
-
available: z.array(z.string()),
|
|
155
|
-
missing: z.array(z.string()),
|
|
156
|
-
}),
|
|
157
|
-
reasoning: z.string(),
|
|
158
|
-
});
|
|
159
|
-
export const testMappingResultSchema = z.object({
|
|
160
|
-
priorityScores: z.array(testPriorityScoreSchema),
|
|
161
|
-
contextFactors: z.object({
|
|
162
|
-
applied: z.array(z.object({
|
|
163
|
-
factor: z.string(),
|
|
164
|
-
impact: z.string(),
|
|
165
|
-
multiplier: z.number(),
|
|
166
|
-
})),
|
|
167
|
-
}),
|
|
168
|
-
summary: z.object({
|
|
169
|
-
highPriority: z.array(z.nativeEnum(TestType)),
|
|
170
|
-
mediumPriority: z.array(z.nativeEnum(TestType)),
|
|
171
|
-
lowPriority: z.array(z.nativeEnum(TestType)),
|
|
172
|
-
}),
|
|
173
|
-
});
|