@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,380 +0,0 @@
|
|
|
1
|
-
import { BASE_SCORES, CONTEXT_RULES, } from "../types/TestMapping.js";
|
|
2
|
-
import { TestType } from "../types/TestTypes.js";
|
|
3
|
-
/**
|
|
4
|
-
* Scoring Engine
|
|
5
|
-
* Calculates priority scores for test types based on repository analysis
|
|
6
|
-
*/
|
|
7
|
-
export class ScoringEngine {
|
|
8
|
-
/**
|
|
9
|
-
* Calculate priority score for a specific test type
|
|
10
|
-
*/
|
|
11
|
-
static calculateTestScore(testType, analysis, analysisScope) {
|
|
12
|
-
const _baseScore = BASE_SCORES[testType];
|
|
13
|
-
const contextMultiplier = this.calculateContextMultiplier(testType, analysis);
|
|
14
|
-
const diffMultiplier = analysisScope === "current_branch_diff" && analysis.branchDiffContext
|
|
15
|
-
? this.calculateDiffMultiplier(testType, analysis.branchDiffContext)
|
|
16
|
-
: 1.0;
|
|
17
|
-
const combinedMultiplier = contextMultiplier * diffMultiplier;
|
|
18
|
-
const _finalScore = _baseScore * combinedMultiplier;
|
|
19
|
-
const feasibility = this.assessFeasibility(testType, analysis);
|
|
20
|
-
const requiredArtifacts = this.identifyRequiredArtifacts(testType, analysis);
|
|
21
|
-
const reasoning = this.generateReasoning(testType, _baseScore, combinedMultiplier, _finalScore, analysis, analysisScope);
|
|
22
|
-
return {
|
|
23
|
-
testType,
|
|
24
|
-
_baseScore,
|
|
25
|
-
// Note: this field stores the combined multiplier (contextMultiplier * diffMultiplier)
|
|
26
|
-
contextMultiplier: Math.round(combinedMultiplier * 100) / 100,
|
|
27
|
-
_finalScore: Math.round(_finalScore * 10) / 10,
|
|
28
|
-
feasibility,
|
|
29
|
-
requiredArtifacts,
|
|
30
|
-
reasoning,
|
|
31
|
-
};
|
|
32
|
-
}
|
|
33
|
-
/**
|
|
34
|
-
* Calculate a score multiplier based on what changed in the branch diff.
|
|
35
|
-
* Boosts test types that are most relevant to the specific code changes.
|
|
36
|
-
*/
|
|
37
|
-
static calculateDiffMultiplier(testType, diff) {
|
|
38
|
-
const hasNewEndpoints = diff.newEndpoints.length > 0;
|
|
39
|
-
const hasModifiedEndpoints = diff.modifiedEndpoints.length > 0;
|
|
40
|
-
const hasEndpointChanges = hasNewEndpoints || hasModifiedEndpoints;
|
|
41
|
-
if (!hasEndpointChanges) {
|
|
42
|
-
if (diff.changedFiles.length === 0) {
|
|
43
|
-
// Truly empty diff — lower priority across the board.
|
|
44
|
-
return 0.5;
|
|
45
|
-
}
|
|
46
|
-
// Check whether any changed file looks like an API-affecting resource
|
|
47
|
-
// (model, schema, validator, serializer, DTO) even though no route
|
|
48
|
-
// decorator lines were touched. These changes affect API behaviour just
|
|
49
|
-
// as much as a route change (e.g. tightening a Pydantic field validator).
|
|
50
|
-
const hasApiRelatedFiles = diff.changedFiles.some((f) => /(model|schema|validator|serializer|dto)/i.test(f));
|
|
51
|
-
if (hasApiRelatedFiles) {
|
|
52
|
-
// Boost contract and fuzz — they're the most relevant for
|
|
53
|
-
// validation/schema changes. Give smoke a small nudge too.
|
|
54
|
-
switch (testType) {
|
|
55
|
-
case TestType.CONTRACT:
|
|
56
|
-
return 1.3;
|
|
57
|
-
case TestType.FUZZ:
|
|
58
|
-
return 1.4; // validation rule changes are exactly what fuzz catches
|
|
59
|
-
case TestType.SMOKE:
|
|
60
|
-
return 1.1;
|
|
61
|
-
default:
|
|
62
|
-
return 1.0;
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
// Files changed but not API-related — neutral.
|
|
66
|
-
return 1.0;
|
|
67
|
-
}
|
|
68
|
-
switch (testType) {
|
|
69
|
-
case TestType.CONTRACT:
|
|
70
|
-
if (hasModifiedEndpoints)
|
|
71
|
-
return 1.4;
|
|
72
|
-
if (hasNewEndpoints)
|
|
73
|
-
return 1.3;
|
|
74
|
-
return 1.0;
|
|
75
|
-
case TestType.FUZZ:
|
|
76
|
-
if (hasModifiedEndpoints)
|
|
77
|
-
return 1.4;
|
|
78
|
-
if (hasNewEndpoints)
|
|
79
|
-
return 1.2;
|
|
80
|
-
return 1.0;
|
|
81
|
-
case TestType.SMOKE:
|
|
82
|
-
if (hasNewEndpoints)
|
|
83
|
-
return 1.3;
|
|
84
|
-
if (hasModifiedEndpoints)
|
|
85
|
-
return 1.2;
|
|
86
|
-
return 1.0;
|
|
87
|
-
case TestType.INTEGRATION:
|
|
88
|
-
if (hasNewEndpoints)
|
|
89
|
-
return 1.2;
|
|
90
|
-
if (hasModifiedEndpoints)
|
|
91
|
-
return 1.1;
|
|
92
|
-
return 1.0;
|
|
93
|
-
case TestType.LOAD:
|
|
94
|
-
if (hasNewEndpoints)
|
|
95
|
-
return 1.1;
|
|
96
|
-
if (hasModifiedEndpoints)
|
|
97
|
-
return 1.0;
|
|
98
|
-
return 1.0;
|
|
99
|
-
case TestType.E2E:
|
|
100
|
-
if (hasNewEndpoints)
|
|
101
|
-
return 1.1;
|
|
102
|
-
if (hasModifiedEndpoints)
|
|
103
|
-
return 1.0;
|
|
104
|
-
return 1.0;
|
|
105
|
-
case TestType.UI:
|
|
106
|
-
return 0.5;
|
|
107
|
-
default:
|
|
108
|
-
return 1.0;
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
/**
|
|
112
|
-
* Calculate context multiplier based on repository characteristics
|
|
113
|
-
*/
|
|
114
|
-
static calculateContextMultiplier(testType, analysis) {
|
|
115
|
-
const rules = CONTEXT_RULES[testType];
|
|
116
|
-
let multiplier = 1.0;
|
|
117
|
-
for (const rule of rules) {
|
|
118
|
-
if (this.evaluateCondition(rule.condition, analysis)) {
|
|
119
|
-
multiplier = rule.multiplier;
|
|
120
|
-
break; // Use first matching rule
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
return multiplier;
|
|
124
|
-
}
|
|
125
|
-
/**
|
|
126
|
-
* Evaluate a context condition against repository analysis
|
|
127
|
-
*/
|
|
128
|
-
static evaluateCondition(condition, analysis) {
|
|
129
|
-
const { projectClassification, infrastructure, existingTests, artifacts } = analysis;
|
|
130
|
-
switch (condition) {
|
|
131
|
-
// Project type conditions
|
|
132
|
-
case "full-stack":
|
|
133
|
-
return projectClassification.projectType === "full-stack";
|
|
134
|
-
case "backend-only":
|
|
135
|
-
return projectClassification.projectType === "rest-api";
|
|
136
|
-
case "frontend-spa":
|
|
137
|
-
return projectClassification.projectType === "frontend";
|
|
138
|
-
case "library":
|
|
139
|
-
return projectClassification.projectType === "library";
|
|
140
|
-
case "cli-or-library":
|
|
141
|
-
return ["library", "cli"].includes(projectClassification.projectType);
|
|
142
|
-
case "microservices":
|
|
143
|
-
return (projectClassification.projectType === "microservices" ||
|
|
144
|
-
projectClassification.deploymentPattern === "microservices");
|
|
145
|
-
case "monolith":
|
|
146
|
-
return (projectClassification.deploymentPattern === "containerized-monolith");
|
|
147
|
-
// Infrastructure conditions
|
|
148
|
-
case "hasKubernetes":
|
|
149
|
-
return infrastructure.hasKubernetes;
|
|
150
|
-
case "hasDockerCompose":
|
|
151
|
-
return infrastructure.hasDockerCompose;
|
|
152
|
-
case "has-cicd":
|
|
153
|
-
return infrastructure.hasCiCd;
|
|
154
|
-
// Testing conditions
|
|
155
|
-
case "no-existing-tests":
|
|
156
|
-
return Object.values(existingTests.coverage).every((v) => v === 0);
|
|
157
|
-
case "has-unit-missing-integration":
|
|
158
|
-
return (existingTests.coverage.unit > 0 &&
|
|
159
|
-
existingTests.coverage.integration === 0);
|
|
160
|
-
case "has-integration-tests":
|
|
161
|
-
return existingTests.coverage.integration > 0;
|
|
162
|
-
case "comprehensive-tests":
|
|
163
|
-
return (Object.values(existingTests.coverage).reduce((a, b) => a + b, 0) > 20);
|
|
164
|
-
// Artifact conditions
|
|
165
|
-
case "frontend-spa-or-fullstack":
|
|
166
|
-
return ["frontend", "full-stack"].includes(projectClassification.projectType);
|
|
167
|
-
case "multiple-services":
|
|
168
|
-
return projectClassification.deploymentPattern === "microservices";
|
|
169
|
-
// Security conditions (inferred)
|
|
170
|
-
case "handles-payments":
|
|
171
|
-
return (analysis.businessContext.mainPurpose
|
|
172
|
-
.toLowerCase()
|
|
173
|
-
.includes("payment") ||
|
|
174
|
-
analysis.businessContext.mainPurpose
|
|
175
|
-
.toLowerCase()
|
|
176
|
-
.includes("commerce"));
|
|
177
|
-
case "handles-pii":
|
|
178
|
-
return (analysis.businessContext.mainPurpose.toLowerCase().includes("user") ||
|
|
179
|
-
analysis.businessContext.mainPurpose.toLowerCase().includes("profile"));
|
|
180
|
-
case "oauth2":
|
|
181
|
-
return analysis.authentication.method === "oauth2";
|
|
182
|
-
case "internal-service":
|
|
183
|
-
case "internal-tool":
|
|
184
|
-
return analysis.businessContext.mainPurpose
|
|
185
|
-
.toLowerCase()
|
|
186
|
-
.includes("internal");
|
|
187
|
-
default:
|
|
188
|
-
return false;
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
/**
|
|
192
|
-
* Assess feasibility of generating a test type
|
|
193
|
-
*/
|
|
194
|
-
static assessFeasibility(testType, analysis) {
|
|
195
|
-
const { artifacts, projectClassification } = analysis;
|
|
196
|
-
// Check for N/A cases first
|
|
197
|
-
if (testType === TestType.UI &&
|
|
198
|
-
projectClassification.projectType === "rest-api") {
|
|
199
|
-
return "not-applicable";
|
|
200
|
-
}
|
|
201
|
-
if (testType === TestType.E2E &&
|
|
202
|
-
projectClassification.projectType === "library") {
|
|
203
|
-
return "not-applicable";
|
|
204
|
-
}
|
|
205
|
-
// Check artifact requirements
|
|
206
|
-
const requiredArtifacts = this.identifyRequiredArtifacts(testType, analysis);
|
|
207
|
-
const missingCount = requiredArtifacts.missing.length;
|
|
208
|
-
if (missingCount === 0) {
|
|
209
|
-
return "high";
|
|
210
|
-
}
|
|
211
|
-
else if (missingCount === 1) {
|
|
212
|
-
return "medium";
|
|
213
|
-
}
|
|
214
|
-
else {
|
|
215
|
-
return "low";
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
/**
|
|
219
|
-
* Identify required artifacts and their availability
|
|
220
|
-
*/
|
|
221
|
-
static identifyRequiredArtifacts(testType, analysis) {
|
|
222
|
-
const available = [];
|
|
223
|
-
const missing = [];
|
|
224
|
-
const { artifacts } = analysis;
|
|
225
|
-
// Check OpenAPI spec
|
|
226
|
-
if (artifacts.openApiSpecs.length > 0) {
|
|
227
|
-
available.push("openApiSpec");
|
|
228
|
-
}
|
|
229
|
-
else {
|
|
230
|
-
if ([
|
|
231
|
-
TestType.SMOKE,
|
|
232
|
-
TestType.CONTRACT,
|
|
233
|
-
TestType.FUZZ,
|
|
234
|
-
TestType.INTEGRATION,
|
|
235
|
-
TestType.LOAD,
|
|
236
|
-
].includes(testType)) {
|
|
237
|
-
missing.push("openApiSpec");
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
// Check Playwright recordings
|
|
241
|
-
if (artifacts.playwrightRecordings.length > 0) {
|
|
242
|
-
available.push("playwrightRecording");
|
|
243
|
-
}
|
|
244
|
-
else {
|
|
245
|
-
if ([TestType.UI, TestType.E2E].includes(testType)) {
|
|
246
|
-
missing.push("playwrightRecording");
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
// Check trace files
|
|
250
|
-
if (artifacts.traceFiles.length > 0) {
|
|
251
|
-
available.push("traceFile");
|
|
252
|
-
}
|
|
253
|
-
else {
|
|
254
|
-
if ([TestType.E2E, TestType.INTEGRATION].includes(testType)) {
|
|
255
|
-
// Trace files are helpful but not required if OpenAPI is available
|
|
256
|
-
if (artifacts.openApiSpecs.length === 0) {
|
|
257
|
-
missing.push("traceFile");
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
return { available, missing };
|
|
262
|
-
}
|
|
263
|
-
/**
|
|
264
|
-
* Generate reasoning explanation for the score
|
|
265
|
-
*/
|
|
266
|
-
static generateReasoning(testType, _baseScore, multiplier, _finalScore, analysis, analysisScope) {
|
|
267
|
-
const { projectClassification, existingTests, artifacts, infrastructure } = analysis;
|
|
268
|
-
let reasoning = "";
|
|
269
|
-
if (analysisScope === "current_branch_diff" &&
|
|
270
|
-
analysis.branchDiffContext) {
|
|
271
|
-
const diff = analysis.branchDiffContext;
|
|
272
|
-
const hasNew = diff.newEndpoints.length > 0;
|
|
273
|
-
const hasMod = diff.modifiedEndpoints.length > 0;
|
|
274
|
-
const endpoints = [
|
|
275
|
-
...diff.newEndpoints.map((e) => `${e.method} ${e.path} (new)`),
|
|
276
|
-
...diff.modifiedEndpoints.map((e) => `${e.method} ${e.path} (modified)`),
|
|
277
|
-
];
|
|
278
|
-
if (!hasNew && !hasMod) {
|
|
279
|
-
reasoning += "No endpoint changes in branch diff — lower priority. ";
|
|
280
|
-
}
|
|
281
|
-
else {
|
|
282
|
-
const affectedLabel = endpoints.join(", ");
|
|
283
|
-
switch (testType) {
|
|
284
|
-
case TestType.CONTRACT:
|
|
285
|
-
reasoning += `Modified endpoints change API contract — contract tests critical. Affected: ${affectedLabel}. `;
|
|
286
|
-
break;
|
|
287
|
-
case TestType.FUZZ:
|
|
288
|
-
reasoning += `Validation rules changed — fuzz testing needed to verify boundary handling. Affected: ${affectedLabel}. `;
|
|
289
|
-
break;
|
|
290
|
-
case TestType.SMOKE:
|
|
291
|
-
reasoning += `Endpoint behavior changed — smoke tests verify basic functionality. Affected: ${affectedLabel}. `;
|
|
292
|
-
break;
|
|
293
|
-
case TestType.INTEGRATION:
|
|
294
|
-
reasoning += `Endpoint changes may affect multi-step flows. Affected: ${affectedLabel}. `;
|
|
295
|
-
break;
|
|
296
|
-
default:
|
|
297
|
-
if (hasNew || hasMod) {
|
|
298
|
-
reasoning += `Branch changes affect: ${affectedLabel}. `;
|
|
299
|
-
}
|
|
300
|
-
break;
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
// Base reasoning by test type — use += to preserve any diff reasoning above
|
|
305
|
-
switch (testType) {
|
|
306
|
-
case TestType.INTEGRATION:
|
|
307
|
-
if (existingTests.coverage.integration === 0) {
|
|
308
|
-
reasoning += `No integration tests exist for ${analysis.apiEndpoints.totalCount} endpoints. `;
|
|
309
|
-
}
|
|
310
|
-
else {
|
|
311
|
-
reasoning += `${existingTests.coverage.integration} integration tests exist. `;
|
|
312
|
-
}
|
|
313
|
-
if (projectClassification.deploymentPattern === "microservices") {
|
|
314
|
-
reasoning +=
|
|
315
|
-
"Microservices architecture makes integration testing critical. ";
|
|
316
|
-
}
|
|
317
|
-
break;
|
|
318
|
-
case TestType.E2E:
|
|
319
|
-
if (projectClassification.projectType === "full-stack") {
|
|
320
|
-
reasoning +=
|
|
321
|
-
"Full-stack application - E2E tests validate complete user journeys. ";
|
|
322
|
-
}
|
|
323
|
-
else {
|
|
324
|
-
reasoning +=
|
|
325
|
-
"E2E tests have limited applicability for this project type. ";
|
|
326
|
-
}
|
|
327
|
-
break;
|
|
328
|
-
case TestType.UI:
|
|
329
|
-
if (["frontend", "full-stack"].includes(projectClassification.projectType)) {
|
|
330
|
-
reasoning += "UI testing essential for frontend validation. ";
|
|
331
|
-
}
|
|
332
|
-
else {
|
|
333
|
-
reasoning += "No UI components detected. ";
|
|
334
|
-
}
|
|
335
|
-
break;
|
|
336
|
-
case TestType.SMOKE:
|
|
337
|
-
if (Object.values(existingTests.coverage).every((v) => v === 0)) {
|
|
338
|
-
reasoning += "No existing tests - smoke tests provide quick wins. ";
|
|
339
|
-
}
|
|
340
|
-
if (infrastructure.hasCiCd) {
|
|
341
|
-
reasoning += "CI/CD pipeline benefits from smoke test validation. ";
|
|
342
|
-
}
|
|
343
|
-
break;
|
|
344
|
-
case TestType.LOAD:
|
|
345
|
-
if (infrastructure.hasKubernetes) {
|
|
346
|
-
reasoning +=
|
|
347
|
-
"Kubernetes deployment suggests high-traffic expectations. ";
|
|
348
|
-
}
|
|
349
|
-
else {
|
|
350
|
-
reasoning += "Load testing validates performance and scalability. ";
|
|
351
|
-
}
|
|
352
|
-
break;
|
|
353
|
-
case TestType.FUZZ:
|
|
354
|
-
if (analysis.authentication.method !== "none") {
|
|
355
|
-
reasoning +=
|
|
356
|
-
"API handles authentication - security testing important. ";
|
|
357
|
-
}
|
|
358
|
-
reasoning += "Fuzz testing discovers input validation issues. ";
|
|
359
|
-
break;
|
|
360
|
-
case TestType.CONTRACT:
|
|
361
|
-
if (artifacts.openApiSpecs.length > 0) {
|
|
362
|
-
reasoning +=
|
|
363
|
-
"OpenAPI spec available - contract validation straightforward. ";
|
|
364
|
-
}
|
|
365
|
-
else {
|
|
366
|
-
reasoning += "No OpenAPI spec - contract tests harder to generate. ";
|
|
367
|
-
}
|
|
368
|
-
break;
|
|
369
|
-
}
|
|
370
|
-
// Add artifact status
|
|
371
|
-
const requiredArtifacts = this.identifyRequiredArtifacts(testType, analysis);
|
|
372
|
-
if (requiredArtifacts.missing.length > 0) {
|
|
373
|
-
reasoning += `Missing: ${requiredArtifacts.missing.join(", ")}. `;
|
|
374
|
-
}
|
|
375
|
-
else if (requiredArtifacts.available.length > 0) {
|
|
376
|
-
reasoning += `All required artifacts available. `;
|
|
377
|
-
}
|
|
378
|
-
return reasoning.trim();
|
|
379
|
-
}
|
|
380
|
-
}
|