@skyramp/mcp 0.0.44 → 0.0.45

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 (40) hide show
  1. package/build/index.js +15 -0
  2. package/build/prompts/code-reuse.js +1 -1
  3. package/build/prompts/driftAnalysisPrompt.js +159 -0
  4. package/build/prompts/modularization/ui-test-modularization.js +2 -0
  5. package/build/prompts/testGenerationPrompt.js +2 -2
  6. package/build/prompts/testHealthPrompt.js +82 -0
  7. package/build/services/DriftAnalysisService.js +924 -0
  8. package/build/services/ModularizationService.js +16 -1
  9. package/build/services/TestDiscoveryService.js +237 -0
  10. package/build/services/TestExecutionService.js +311 -0
  11. package/build/services/TestGenerationService.js +16 -2
  12. package/build/services/TestHealthService.js +653 -0
  13. package/build/tools/auth/loginTool.js +1 -1
  14. package/build/tools/auth/logoutTool.js +1 -1
  15. package/build/tools/code-refactor/codeReuseTool.js +5 -3
  16. package/build/tools/code-refactor/modularizationTool.js +8 -2
  17. package/build/tools/executeSkyrampTestTool.js +12 -122
  18. package/build/tools/fixErrorTool.js +1 -1
  19. package/build/tools/generate-tests/generateE2ERestTool.js +1 -1
  20. package/build/tools/generate-tests/generateFuzzRestTool.js +1 -1
  21. package/build/tools/generate-tests/generateLoadRestTool.js +1 -1
  22. package/build/tools/generate-tests/generateSmokeRestTool.js +1 -1
  23. package/build/tools/generate-tests/generateUIRestTool.js +1 -1
  24. package/build/tools/test-maintenance/actionsTool.js +202 -0
  25. package/build/tools/test-maintenance/analyzeTestDriftTool.js +188 -0
  26. package/build/tools/test-maintenance/calculateHealthScoresTool.js +248 -0
  27. package/build/tools/test-maintenance/discoverTestsTool.js +135 -0
  28. package/build/tools/test-maintenance/executeBatchTestsTool.js +188 -0
  29. package/build/tools/test-maintenance/stateCleanupTool.js +145 -0
  30. package/build/tools/test-recommendation/analyzeRepositoryTool.js +16 -1
  31. package/build/tools/test-recommendation/recommendTestsTool.js +6 -2
  32. package/build/tools/trace/startTraceCollectionTool.js +1 -1
  33. package/build/tools/trace/stopTraceCollectionTool.js +1 -1
  34. package/build/types/TestAnalysis.js +1 -0
  35. package/build/types/TestDriftAnalysis.js +1 -0
  36. package/build/types/TestExecution.js +6 -0
  37. package/build/types/TestHealth.js +4 -0
  38. package/build/utils/AnalysisStateManager.js +238 -0
  39. package/build/utils/utils.test.js +25 -9
  40. package/package.json +6 -3
@@ -0,0 +1,653 @@
1
+ import * as fs from "fs";
2
+ import { logger } from "../utils/logger.js";
3
+ export class TestHealthService {
4
+ /**
5
+ * Generate comprehensive health report for tests
6
+ */
7
+ async generateHealthReport(tests, driftResults) {
8
+ logger.info(`Generating health report for ${tests.length} tests`);
9
+ const healthAnalyses = [];
10
+ const recommendations = [];
11
+ // Analyze each test
12
+ for (const test of tests) {
13
+ const driftData = driftResults?.find((d) => d.testFile === test.testFile);
14
+ const analysis = await this.analyzeTestHealth(test.testFile, test.execution, driftData, test.apiSchema);
15
+ healthAnalyses.push(analysis);
16
+ recommendations.push(analysis.recommendation);
17
+ }
18
+ // Calculate summary statistics
19
+ const summary = {
20
+ totalTests: tests.length,
21
+ healthy: healthAnalyses.filter((a) => a.healthScore.status === "healthy")
22
+ .length,
23
+ atRisk: healthAnalyses.filter((a) => a.healthScore.status === "at_risk")
24
+ .length,
25
+ broken: healthAnalyses.filter((a) => a.healthScore.status === "broken")
26
+ .length,
27
+ unknown: healthAnalyses.filter((a) => a.healthScore.status === "unknown")
28
+ .length,
29
+ averageHealthScore: healthAnalyses.length > 0
30
+ ? Math.round(healthAnalyses.reduce((sum, a) => sum + a.healthScore.overall, 0) / healthAnalyses.length)
31
+ : 0,
32
+ };
33
+ // Analyze coverage if API schemas are available
34
+ let coverage;
35
+ const apiSchemas = tests
36
+ .map((t) => t.apiSchema)
37
+ .filter((s) => s);
38
+ if (apiSchemas.length > 0) {
39
+ try {
40
+ coverage = await this.analyzeCoverage(tests, apiSchemas[0]);
41
+ }
42
+ catch (error) {
43
+ logger.error(`Failed to analyze coverage: ${error.message}`);
44
+ }
45
+ }
46
+ return {
47
+ summary,
48
+ tests: healthAnalyses,
49
+ recommendations: this.prioritizeRecommendations(recommendations),
50
+ coverage,
51
+ generatedAt: new Date().toISOString(),
52
+ };
53
+ }
54
+ /**
55
+ * Analyze health of a single test
56
+ */
57
+ async analyzeTestHealth(testFile, execution, drift, apiSchema) {
58
+ // Calculate execution score
59
+ const executionScore = execution
60
+ ? this.calculateExecutionScore(execution)
61
+ : undefined;
62
+ // Calculate health score
63
+ const healthScore = this.calculateHealthScore(executionScore?.score, drift?.driftScore);
64
+ // Identify issues
65
+ const issues = this.identifyIssues(execution, drift);
66
+ // Extract API endpoint info from test
67
+ const apiEndpoint = apiSchema
68
+ ? await this.extractEndpointFromTest(testFile, apiSchema)
69
+ : undefined;
70
+ // Generate recommendation
71
+ const recommendation = this.generateRecommendation(testFile, healthScore, drift?.driftScore, execution, issues, apiEndpoint);
72
+ return {
73
+ testFile,
74
+ healthScore,
75
+ issues,
76
+ recommendation,
77
+ executionData: execution
78
+ ? {
79
+ passed: execution.passed,
80
+ duration: execution.duration,
81
+ errors: execution.errors,
82
+ warnings: execution.warnings,
83
+ }
84
+ : undefined,
85
+ driftData: drift
86
+ ? {
87
+ driftScore: drift.driftScore,
88
+ changes: drift.changes?.length || 0,
89
+ affectedFiles: drift.affectedFiles?.files?.length || 0,
90
+ }
91
+ : undefined,
92
+ apiEndpoint,
93
+ };
94
+ }
95
+ /**
96
+ * Calculate execution score (0-100)
97
+ */
98
+ calculateExecutionScore(execution) {
99
+ let score;
100
+ let status;
101
+ if (execution.crashed) {
102
+ score = 0;
103
+ status = "crashed";
104
+ }
105
+ else if (execution.passed) {
106
+ if (execution.warnings.length > 0) {
107
+ // Passed with warnings: 50-100 based on warning count
108
+ score = Math.max(50, 100 - execution.warnings.length * 10);
109
+ status = "passed_with_warnings";
110
+ }
111
+ else {
112
+ score = 100;
113
+ status = "passed";
114
+ }
115
+ }
116
+ else if (execution.errors.length > 0) {
117
+ // Failed with errors: 30-50 based on error count
118
+ score = Math.max(30, 50 - execution.errors.length * 5);
119
+ status = "failed";
120
+ }
121
+ else if (execution.duration > 280000) {
122
+ // Near timeout (5 min = 300000ms)
123
+ score = 20;
124
+ status = "timeout";
125
+ }
126
+ else {
127
+ score = 30;
128
+ status = "failed";
129
+ }
130
+ return {
131
+ score,
132
+ status,
133
+ hasWarnings: execution.warnings.length > 0,
134
+ hasErrors: execution.errors.length > 0,
135
+ crashed: execution.crashed,
136
+ };
137
+ }
138
+ /**
139
+ * Calculate overall health score
140
+ *
141
+ * Health status is primarily drift-based with optional execution refinement:
142
+ * - Healthy: drift < 20 (and execution >= 80 if available)
143
+ * - At Risk: drift 20-40 (and execution >= 60 if available)
144
+ * - Broken: drift >= 40 (or execution < 60 if available)
145
+ */
146
+ calculateHealthScore(executionScore, driftScore) {
147
+ let overall;
148
+ let calculationMethod;
149
+ if (executionScore !== undefined && driftScore !== undefined) {
150
+ // Combined: 60% execution, 40% drift (inverted)
151
+ overall = Math.round(0.6 * executionScore + 0.4 * (100 - driftScore));
152
+ calculationMethod = "combined";
153
+ }
154
+ else if (executionScore !== undefined) {
155
+ overall = executionScore;
156
+ calculationMethod = "execution_only";
157
+ }
158
+ else if (driftScore !== undefined) {
159
+ overall = 100 - driftScore;
160
+ calculationMethod = "drift_only";
161
+ }
162
+ else {
163
+ overall = 0;
164
+ calculationMethod = "drift_only";
165
+ }
166
+ // Determine health status (primarily drift-based, execution is optional refinement)
167
+ // User requirements interpretation:
168
+ // - Healthy: drift < 20 AND (no execution OR execution >= 80)
169
+ // - At Risk: drift 20-40 AND (no execution OR execution >= 60)
170
+ // - Broken: drift >= 40 OR execution < 60
171
+ let status;
172
+ const drift = driftScore !== undefined ? driftScore : -1; // -1 means no drift data
173
+ const exec = executionScore !== undefined ? executionScore : -1; // -1 means no execution data
174
+ if (drift === -1 && exec === -1) {
175
+ // No data available
176
+ status = "unknown";
177
+ }
178
+ else if (drift !== -1) {
179
+ // Drift-based status (primary)
180
+ if (drift >= 40) {
181
+ // High drift (>= 40): Broken regardless of execution
182
+ status = "broken";
183
+ }
184
+ else if (drift >= 20 && drift < 40) {
185
+ // Medium drift (20-40): At risk, unless execution is very poor
186
+ status = exec !== -1 && exec < 60 ? "broken" : "at_risk";
187
+ }
188
+ else {
189
+ // Low drift (< 20): Healthy, but execution can downgrade to at_risk
190
+ // Since code hasn't changed much, even failing tests are "at_risk" not "broken"
191
+ if (exec !== -1 && exec < 80) {
192
+ status = "at_risk"; // Any execution issues with low drift → at_risk
193
+ }
194
+ else {
195
+ status = "healthy";
196
+ }
197
+ }
198
+ }
199
+ else if (exec !== -1) {
200
+ // Only execution data available (no drift)
201
+ if (exec >= 80) {
202
+ status = "healthy";
203
+ }
204
+ else if (exec >= 60) {
205
+ status = "at_risk";
206
+ }
207
+ else {
208
+ status = "broken";
209
+ }
210
+ }
211
+ else {
212
+ status = "unknown";
213
+ }
214
+ return {
215
+ overall,
216
+ executionScore,
217
+ driftScore,
218
+ status,
219
+ calculationMethod,
220
+ };
221
+ }
222
+ /**
223
+ * Identify specific issues with a test
224
+ */
225
+ identifyIssues(execution, drift) {
226
+ const issues = [];
227
+ // Check execution issues
228
+ if (execution) {
229
+ if (execution.crashed) {
230
+ issues.push({
231
+ type: "crash",
232
+ severity: "critical",
233
+ description: "Test crashed during execution",
234
+ details: execution.errors.join("; "),
235
+ });
236
+ }
237
+ else if (execution.errors.length > 0) {
238
+ issues.push({
239
+ type: "test_failures",
240
+ severity: "high",
241
+ description: `Test failed with ${execution.errors.length} error(s)`,
242
+ details: execution.errors.slice(0, 3).join("; "),
243
+ });
244
+ }
245
+ if (execution.duration > 280000) {
246
+ issues.push({
247
+ type: "timeout",
248
+ severity: "medium",
249
+ description: "Test approaching timeout threshold",
250
+ details: `Duration: ${(execution.duration / 1000).toFixed(1)}s`,
251
+ });
252
+ }
253
+ }
254
+ // Check drift issues
255
+ if (drift &&
256
+ drift.changes &&
257
+ Array.isArray(drift.changes) &&
258
+ drift.changes.length > 0) {
259
+ const hasCodeChanges = drift.changes.some((c) => ["code_change", "function_changed", "class_changed"].includes(c.type));
260
+ if (hasCodeChanges) {
261
+ issues.push({
262
+ type: "code_changes",
263
+ severity: "medium",
264
+ description: "Code changes detected in dependencies",
265
+ details: `${drift.affectedFiles?.files.length || 0} file(s) changed`,
266
+ });
267
+ }
268
+ const endpointsRemoved = drift.changes.filter((c) => c.type === "endpoint_removed");
269
+ if (endpointsRemoved.length > 0) {
270
+ issues.push({
271
+ type: "endpoints_removed",
272
+ severity: "high",
273
+ description: `${endpointsRemoved.length} API endpoint(s) removed`,
274
+ details: endpointsRemoved.map((c) => c.description).join("; "),
275
+ });
276
+ }
277
+ const schemaChanges = drift.changes.filter((c) => ["endpoint_modified", "authentication_changed"].includes(c.type));
278
+ if (schemaChanges.length > 0) {
279
+ issues.push({
280
+ type: "schema_changes",
281
+ severity: "high",
282
+ description: "API schema changes detected",
283
+ details: schemaChanges.map((c) => c.description).join("; "),
284
+ });
285
+ }
286
+ const authChanged = drift.changes.some((c) => c.type === "authentication_changed");
287
+ if (authChanged) {
288
+ issues.push({
289
+ type: "authentication_changed",
290
+ severity: "critical",
291
+ description: "Authentication mechanism changed",
292
+ });
293
+ }
294
+ }
295
+ return issues;
296
+ }
297
+ /**
298
+ * Generate recommendation for a test
299
+ *
300
+ * Recommendation logic (primarily drift-based):
301
+ * 1. IF drift > 70: REGENERATE (HIGH priority)
302
+ * 2. ELSE IF endpoint missing: DELETE (HIGH priority)
303
+ * 3. ELSE IF 30 < drift <= 70: UPDATE (MEDIUM priority)
304
+ * 4. ELSE IF drift > 10: VERIFY (LOW priority)
305
+ * 5. ELSE: VERIFY (LOW priority, healthy test)
306
+ *
307
+ * Execution failures enhance rationale but don't change primary action
308
+ */
309
+ generateRecommendation(testFile, healthScore, driftScore, execution, issues, apiEndpoint) {
310
+ const drift = driftScore !== undefined ? driftScore : -1; // -1 means no drift data
311
+ let action;
312
+ let priority;
313
+ let rationale;
314
+ let estimatedWork = "SMALL";
315
+ // Handle missing drift data first
316
+ if (drift === -1) {
317
+ // No drift data available - base recommendation on health status and execution
318
+ if (healthScore.status === "unknown") {
319
+ action = "VERIFY";
320
+ priority = "MEDIUM";
321
+ rationale = "Unable to analyze drift - manual verification recommended";
322
+ estimatedWork = "SMALL";
323
+ // If execution data shows failure, escalate
324
+ if (execution && !execution.passed) {
325
+ priority = "HIGH";
326
+ rationale = "Drift analysis unavailable and test is failing - investigate immediately";
327
+ estimatedWork = "MEDIUM";
328
+ }
329
+ }
330
+ else if (execution && !execution.passed) {
331
+ // No drift data but test is failing
332
+ action = "UPDATE";
333
+ priority = "HIGH";
334
+ rationale = "Test is failing but drift analysis unavailable - review test logic and dependencies";
335
+ estimatedWork = "MEDIUM";
336
+ }
337
+ else if (execution && execution.passed) {
338
+ // No drift data but test is passing
339
+ action = "VERIFY";
340
+ priority = "LOW";
341
+ rationale = "Drift analysis unavailable but test is passing - periodic verification recommended";
342
+ estimatedWork = "SMALL";
343
+ }
344
+ else {
345
+ // No drift data, no execution data
346
+ action = "VERIFY";
347
+ priority = "MEDIUM";
348
+ rationale = "No drift or execution data available - analysis needed";
349
+ estimatedWork = "SMALL";
350
+ }
351
+ }
352
+ else if (drift > 70) {
353
+ // High drift -> REGENERATE
354
+ action = "REGENERATE";
355
+ priority = "HIGH";
356
+ rationale =
357
+ "High drift detected - significant code changes since test creation";
358
+ estimatedWork = "MEDIUM";
359
+ // Enhance rationale with test failures if present
360
+ if (execution && !execution.passed) {
361
+ rationale += ". Test is also failing";
362
+ }
363
+ // Add specific issues
364
+ if (issues && issues.length > 0) {
365
+ const criticalIssues = issues.filter((i) => ["critical", "high"].includes(i.severity));
366
+ if (criticalIssues.length > 0) {
367
+ rationale += `. Critical issues: ${criticalIssues
368
+ .map((i) => i.description)
369
+ .join(", ")}`;
370
+ }
371
+ }
372
+ }
373
+ else if (apiEndpoint?.exists === false) {
374
+ // Endpoint removed from schema -> DELETE
375
+ action = "DELETE";
376
+ priority = "HIGH";
377
+ rationale = "Endpoint no longer exists in API schema";
378
+ estimatedWork = "SMALL";
379
+ }
380
+ else if (drift > 30 && drift <= 70) {
381
+ // Moderate drift -> UPDATE
382
+ action = "UPDATE";
383
+ priority = "MEDIUM";
384
+ rationale =
385
+ "Moderate drift detected - related code changes may affect test";
386
+ estimatedWork = "SMALL";
387
+ // Enhance with schema changes
388
+ const schemaChanges = issues?.filter((i) => [
389
+ "schema_changes",
390
+ "endpoints_removed",
391
+ "authentication_changed",
392
+ ].includes(i.type));
393
+ if (schemaChanges && schemaChanges.length > 0) {
394
+ rationale += `. Schema changes: ${schemaChanges
395
+ .map((i) => i.description)
396
+ .join(", ")}`;
397
+ estimatedWork = "MEDIUM";
398
+ }
399
+ // Note execution failures
400
+ if (execution && !execution.passed) {
401
+ rationale += ". Test is also failing - requires immediate attention";
402
+ priority = "HIGH"; // Escalate priority for failing tests
403
+ }
404
+ }
405
+ else if (drift > 10) {
406
+ // Low drift -> VERIFY
407
+ action = "VERIFY";
408
+ priority = "LOW";
409
+ rationale = "Minor changes detected - test likely still valid";
410
+ estimatedWork = "SMALL";
411
+ // Note execution status for context
412
+ if (execution) {
413
+ if (!execution.passed) {
414
+ rationale += ". However, test is failing - review needed";
415
+ priority = "MEDIUM"; // Escalate for failing tests
416
+ }
417
+ else if (execution.warnings.length > 0) {
418
+ rationale += ". Test passed with warnings";
419
+ }
420
+ }
421
+ }
422
+ else {
423
+ // Minimal/no drift -> VERIFY
424
+ action = "VERIFY";
425
+ priority = "LOW";
426
+ rationale = "Test appears healthy - periodic verification recommended";
427
+ estimatedWork = "SMALL";
428
+ // Handle edge case: low drift but test is failing
429
+ if (execution && !execution.passed) {
430
+ action = "UPDATE";
431
+ priority = "HIGH";
432
+ rationale =
433
+ "Test is failing despite low drift - may indicate environmental issues or test flakiness";
434
+ estimatedWork = "MEDIUM";
435
+ }
436
+ }
437
+ // Determine endpoint status
438
+ let endpointStatus;
439
+ if (apiEndpoint === undefined) {
440
+ endpointStatus = undefined;
441
+ }
442
+ else if (apiEndpoint.exists) {
443
+ endpointStatus = "exists";
444
+ }
445
+ else {
446
+ endpointStatus = "missing";
447
+ }
448
+ return {
449
+ testFile,
450
+ action,
451
+ priority,
452
+ rationale,
453
+ estimatedWork,
454
+ issues,
455
+ details: {
456
+ driftScore: drift,
457
+ executionPassed: execution?.passed,
458
+ endpointStatus,
459
+ },
460
+ };
461
+ }
462
+ /**
463
+ * Parse OpenAPI schema and extract endpoints
464
+ */
465
+ async parseApiSchema(schemaPath) {
466
+ let schema;
467
+ try {
468
+ if (schemaPath.startsWith("http://") ||
469
+ schemaPath.startsWith("https://")) {
470
+ // Fetch from URL
471
+ const response = await fetch(schemaPath);
472
+ if (!response.ok) {
473
+ throw new Error(`HTTP error! status: ${response.status}`);
474
+ }
475
+ schema = await response.json();
476
+ }
477
+ else {
478
+ // Read from file
479
+ const content = fs.readFileSync(schemaPath, "utf-8");
480
+ schema = JSON.parse(content);
481
+ }
482
+ }
483
+ catch (error) {
484
+ logger.error(`Failed to parse API schema: ${error.message}`);
485
+ throw new Error(`Could not parse API schema at ${schemaPath}`);
486
+ }
487
+ const endpoints = [];
488
+ const paths = schema.paths || {};
489
+ for (const [pathStr, pathItem] of Object.entries(paths)) {
490
+ for (const [method, operation] of Object.entries(pathItem)) {
491
+ if (["get", "post", "put", "delete", "patch"].includes(method)) {
492
+ const op = operation;
493
+ const authRequired = this.checkAuthRequired(op, schema);
494
+ endpoints.push({
495
+ path: pathStr,
496
+ method: method.toUpperCase(),
497
+ operationId: op.operationId,
498
+ authRequired,
499
+ parameters: op.parameters || [],
500
+ requestBody: op.requestBody,
501
+ responses: op.responses,
502
+ });
503
+ }
504
+ }
505
+ }
506
+ return endpoints;
507
+ }
508
+ /**
509
+ * Check if endpoint requires authentication
510
+ */
511
+ checkAuthRequired(operation, schema) {
512
+ // Check security at operation level
513
+ if (operation.security && operation.security.length > 0) {
514
+ return true;
515
+ }
516
+ // Check security at schema level
517
+ if (schema.security && schema.security.length > 0) {
518
+ return true;
519
+ }
520
+ return false;
521
+ }
522
+ /**
523
+ * Analyze test coverage
524
+ */
525
+ async analyzeCoverage(tests, apiSchema) {
526
+ const endpoints = await this.parseApiSchema(apiSchema);
527
+ const coverage = [];
528
+ for (const endpoint of endpoints) {
529
+ const endpointKey = `${endpoint.method} ${endpoint.path}`;
530
+ const coveredBy = tests.filter((t) => this.testCoversEndpoint(t.testFile, endpoint));
531
+ coverage.push({
532
+ endpoint: endpointKey,
533
+ method: endpoint.method,
534
+ covered: coveredBy.length > 0,
535
+ testFiles: coveredBy.map((t) => t.testFile),
536
+ endpointInfo: endpoint,
537
+ });
538
+ }
539
+ const coveredEndpoints = coverage.filter((c) => c.covered).length;
540
+ const uncoveredEndpoints = endpoints.filter((ep) => !coverage.find((c) => c.endpoint === `${ep.method} ${ep.path}`)
541
+ ?.covered);
542
+ return {
543
+ totalEndpoints: endpoints.length,
544
+ coveredEndpoints,
545
+ coveragePercentage: endpoints.length > 0
546
+ ? Math.round((coveredEndpoints / endpoints.length) * 100)
547
+ : 0,
548
+ uncoveredEndpoints,
549
+ coverage,
550
+ };
551
+ }
552
+ /**
553
+ * Check if test covers endpoint
554
+ */
555
+ testCoversEndpoint(testFile, endpoint) {
556
+ try {
557
+ const content = fs.readFileSync(testFile, "utf-8");
558
+ // Convert OpenAPI-style path to regex, e.g. /users/{id} -> /users/[^/]+
559
+ // Replace path parameters with regex pattern, escaping the path parts but not the regex itself
560
+ const pathRegexString = endpoint.path
561
+ .split(/(\{[^}]+\})/) // Split on path parameters, keeping them in the result
562
+ .map((part, index) => {
563
+ if (part.match(/^\{[^}]+\}$/)) {
564
+ // This is a path parameter like {id}, replace with regex pattern
565
+ return "[^/]+";
566
+ }
567
+ else {
568
+ // This is a literal path part, escape special regex chars
569
+ return part.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
570
+ }
571
+ })
572
+ .join('');
573
+ // pathRegexString is already escaped, so we can use it directly
574
+ const pathRegex = new RegExp(pathRegexString);
575
+ const method = endpoint.method.toUpperCase();
576
+ const methodLower = endpoint.method.toLowerCase();
577
+ // Check if path matches
578
+ if (!pathRegex.test(content)) {
579
+ return false;
580
+ }
581
+ // More precise method detection patterns
582
+ const methodPatterns = [
583
+ // Python: method="PUT" or method='PUT'
584
+ new RegExp(`method\\s*=\\s*["']${method}["']`, "i"),
585
+ // JavaScript/TypeScript: method: 'PUT' or .put( or .PUT(
586
+ new RegExp(`method\\s*:\\s*["']${method}["']`, "i"),
587
+ new RegExp(`\\.${methodLower}\\s*\\(`, "i"),
588
+ // HTTP request patterns: PUT /path
589
+ new RegExp(`${method}\\s+${pathRegexString}`, "i"),
590
+ // Axios/fetch: { method: 'PUT' }
591
+ new RegExp(`["']method["']\\s*:\\s*["']${method}["']`, "i"),
592
+ // RestAssured/Java: .put()
593
+ new RegExp(`\\.${methodLower}\\(`, "i"),
594
+ // Go: http.MethodPut or "PUT"
595
+ new RegExp(`http\\.Method${method.charAt(0) + methodLower.slice(1)}`, "i"),
596
+ new RegExp(`["']${method}["']`, "i"),
597
+ ];
598
+ // Check if any pattern matches
599
+ return methodPatterns.some((pattern) => pattern.test(content));
600
+ }
601
+ catch (error) {
602
+ return false;
603
+ }
604
+ }
605
+ /**
606
+ * Extract endpoint information from test file
607
+ */
608
+ async extractEndpointFromTest(testFile, apiSchema) {
609
+ try {
610
+ const content = fs.readFileSync(testFile, "utf-8");
611
+ const endpoints = await this.parseApiSchema(apiSchema);
612
+ // Find matching endpoint
613
+ for (const endpoint of endpoints) {
614
+ if (this.testCoversEndpoint(testFile, endpoint)) {
615
+ return {
616
+ path: endpoint.path,
617
+ method: endpoint.method,
618
+ exists: true,
619
+ };
620
+ }
621
+ }
622
+ return { path: "unknown", method: "unknown", exists: false };
623
+ }
624
+ catch (error) {
625
+ return undefined;
626
+ }
627
+ }
628
+ /**
629
+ * Prioritize and sort recommendations
630
+ */
631
+ prioritizeRecommendations(recommendations) {
632
+ const priorityOrder = {
633
+ CRITICAL: 0,
634
+ HIGH: 1,
635
+ MEDIUM: 2,
636
+ LOW: 3,
637
+ };
638
+ return recommendations.sort((a, b) => {
639
+ const priorityDiff = priorityOrder[a.priority] - priorityOrder[b.priority];
640
+ if (priorityDiff !== 0)
641
+ return priorityDiff;
642
+ // Within same priority, sort by action
643
+ const actionOrder = {
644
+ REGENERATE: 0,
645
+ DELETE: 1,
646
+ UPDATE: 2,
647
+ ADD: 3,
648
+ VERIFY: 4,
649
+ };
650
+ return actionOrder[a.action] - actionOrder[b.action];
651
+ });
652
+ }
653
+ }
@@ -12,7 +12,7 @@ Logging into Skyramp provides access to additional platform features and service
12
12
  .string()
13
13
  .describe("The prompt user provided to login to Skyramp"),
14
14
  },
15
- annotations: {
15
+ _meta: {
16
16
  keywords: ["login", "authenticate", "skyramp login"],
17
17
  },
18
18
  }, async (params) => {
@@ -11,7 +11,7 @@ Logout from Skyramp platform to end your authenticated session.`,
11
11
  .string()
12
12
  .describe("The prompt user provided to logout from Skyramp"),
13
13
  },
14
- annotations: {
14
+ _meta: {
15
15
  keywords: ["logout", "sign out", "skyramp logout"],
16
16
  },
17
17
  }, async (params) => {
@@ -37,16 +37,18 @@ export function registerCodeReuseTool(server) {
37
37
  3. **ONLY IF** reusable helpers are found in OTHER test files → move to utils and update imports
38
38
  4. **IF NO** existing utils or reusable helpers are found in test files then reuse is complete, without creating any files or changes to the test files
39
39
  5. Verify no duplicate code remains and tests work as expected
40
- 6. **MANDATORY**: After code reuse is complete, proceed to modularization by calling skyramp_modularization tool
40
+ 6. **MANDATORY**: After code reuse is complete, proceed to modularization by calling skyramp_modularization tool ONLY for UI, E2E, INTEGRATION or LOAD tests generated from traces.
41
41
 
42
42
  **WHEN TO SKIP (DO NOTHING):**
43
43
  - Other test files only have repetitive patterns, no helper functions
44
44
  - Other test files are essentially identical to current file
45
45
  - No actual function definitions found in other test files
46
-
46
+
47
+ **MANDATORY**: ONLY ALLOW CODE REUSE IF THE IS TRACE BASED FLAG IS SET TO TRUE ELSE DO NOT ALLOW CODE REUSE AND LEAVE THE TEST FILE AS IS.
48
+ **CRITICAL**: NON TRACE BASED TESTS ARE ALREADY MODULARIZED AND DO NOT NEED CODE REUSE.
47
49
  The tool will provide step-by-step instructions that MUST be followed exactly.`,
48
50
  inputSchema: codeReuseSchema.shape,
49
- annotations: {
51
+ _meta: {
50
52
  keywords: [
51
53
  "code reuse",
52
54
  "duplicate code",