@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.
Files changed (35) hide show
  1. package/build/index.js +32 -5
  2. package/build/prompts/test-recommendation/analysisOutputPrompt.js +98 -0
  3. package/build/prompts/test-recommendation/recommendationSections.js +226 -0
  4. package/build/prompts/test-recommendation/registerRecommendTestsPrompt.js +71 -0
  5. package/build/prompts/test-recommendation/test-recommendation-prompt.js +166 -104
  6. package/build/prompts/testGenerationPrompt.js +2 -3
  7. package/build/prompts/testbot/testbot-prompts.js +96 -93
  8. package/build/resources/analysisResources.js +254 -0
  9. package/build/services/ScenarioGenerationService.js +70 -26
  10. package/build/tools/generate-tests/generateIntegrationRestTool.js +54 -1
  11. package/build/tools/generate-tests/generateScenarioRestTool.js +8 -5
  12. package/build/tools/submitReportTool.js +28 -0
  13. package/build/tools/test-maintenance/stateCleanupTool.js +8 -0
  14. package/build/tools/test-recommendation/analyzeRepositoryTool.js +349 -217
  15. package/build/tools/test-recommendation/recommendTestsTool.js +163 -159
  16. package/build/tools/workspace/initializeWorkspaceTool.js +1 -1
  17. package/build/types/RepositoryAnalysis.js +99 -12
  18. package/build/utils/AnalysisStateManager.js +40 -23
  19. package/build/utils/branchDiff.js +47 -0
  20. package/build/utils/pr-comment-parser.js +124 -0
  21. package/build/utils/projectMetadata.js +188 -0
  22. package/build/utils/projectMetadata.test.js +81 -0
  23. package/build/utils/repoScanner.js +378 -0
  24. package/build/utils/routeParsers.js +213 -0
  25. package/build/utils/routeParsers.test.js +87 -0
  26. package/build/utils/scenarioDrafting.js +119 -0
  27. package/build/utils/scenarioDrafting.test.js +66 -0
  28. package/build/utils/trace-parser.js +166 -0
  29. package/build/utils/workspaceAuth.js +16 -0
  30. package/package.json +1 -1
  31. package/build/prompts/test-recommendation/repository-analysis-prompt.js +0 -326
  32. package/build/prompts/test-recommendation/test-mapping-prompt.js +0 -266
  33. package/build/tools/test-recommendation/mapTestsTool.js +0 -243
  34. package/build/types/TestMapping.js +0 -173
  35. 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
- });