@skyramp/mcp 0.0.45 → 0.0.46
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 +48 -18
- package/build/services/AnalyticsService.js +86 -0
- package/build/services/DriftAnalysisService.js +6 -2
- package/build/services/TestExecutionService.js +195 -2
- package/build/services/TestHealthService.js +8 -5
- package/build/tools/auth/loginTool.js +12 -2
- package/build/tools/auth/logoutTool.js +12 -2
- package/build/tools/code-refactor/codeReuseTool.js +41 -15
- package/build/tools/code-refactor/modularizationTool.js +36 -9
- package/build/tools/executeSkyrampTestTool.js +17 -3
- package/build/tools/fixErrorTool.js +37 -13
- package/build/tools/generate-tests/generateContractRestTool.js +8 -2
- package/build/tools/generate-tests/generateE2ERestTool.js +8 -2
- package/build/tools/generate-tests/generateFuzzRestTool.js +8 -2
- package/build/tools/generate-tests/generateIntegrationRestTool.js +8 -2
- package/build/tools/generate-tests/generateLoadRestTool.js +8 -2
- package/build/tools/generate-tests/generateScenarioRestTool.js +8 -2
- package/build/tools/generate-tests/generateSmokeRestTool.js +8 -2
- package/build/tools/generate-tests/generateUIRestTool.js +8 -2
- package/build/tools/test-maintenance/actionsTool.js +175 -147
- package/build/tools/test-maintenance/analyzeTestDriftTool.js +14 -5
- package/build/tools/test-maintenance/calculateHealthScoresTool.js +13 -4
- package/build/tools/test-maintenance/discoverTestsTool.js +10 -2
- package/build/tools/test-maintenance/executeBatchTestsTool.js +14 -4
- package/build/tools/test-maintenance/stateCleanupTool.js +11 -3
- package/build/tools/test-recommendation/analyzeRepositoryTool.js +11 -2
- package/build/tools/test-recommendation/mapTestsTool.js +9 -2
- package/build/tools/test-recommendation/recommendTestsTool.js +15 -3
- package/build/tools/trace/startTraceCollectionTool.js +17 -4
- package/build/tools/trace/stopTraceCollectionTool.js +27 -3
- package/build/utils/AnalysisStateManager.js +3 -1
- package/package.json +2 -2
package/build/index.js
CHANGED
|
@@ -30,6 +30,7 @@ import { registerExecuteBatchTestsTool } from "./tools/test-maintenance/executeB
|
|
|
30
30
|
import { registerCalculateHealthScoresTool } from "./tools/test-maintenance/calculateHealthScoresTool.js";
|
|
31
31
|
import { registerActionsTool } from "./tools/test-maintenance/actionsTool.js";
|
|
32
32
|
import { registerStateCleanupTool } from "./tools/test-maintenance/stateCleanupTool.js";
|
|
33
|
+
import { AnalyticsService } from "./services/AnalyticsService.js";
|
|
33
34
|
const server = new McpServer({
|
|
34
35
|
name: "Skyramp MCP Server",
|
|
35
36
|
version: "1.0.0",
|
|
@@ -71,23 +72,17 @@ const codeQualityTools = [
|
|
|
71
72
|
registerCodeReuseTool,
|
|
72
73
|
];
|
|
73
74
|
codeQualityTools.forEach((registerTool) => registerTool(server));
|
|
74
|
-
// Register
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
registerActionsTool(server);
|
|
86
|
-
registerStateCleanupTool(server);
|
|
87
|
-
}
|
|
88
|
-
else {
|
|
89
|
-
logger.info("Internal tools disabled. Set SKYRAMP_ENABLE_INTERNAL_TOOLS=true to enable.");
|
|
90
|
-
}
|
|
75
|
+
// Register test recommendation tools
|
|
76
|
+
registerAnalyzeRepositoryTool(server);
|
|
77
|
+
registerMapTestsTool(server);
|
|
78
|
+
registerRecommendTestsTool(server);
|
|
79
|
+
// Register test maintenance tools
|
|
80
|
+
registerDiscoverTestsTool(server);
|
|
81
|
+
registerAnalyzeTestDriftTool(server);
|
|
82
|
+
registerExecuteBatchTestsTool(server);
|
|
83
|
+
registerCalculateHealthScoresTool(server);
|
|
84
|
+
registerActionsTool(server);
|
|
85
|
+
registerStateCleanupTool(server);
|
|
91
86
|
// Register other Skyramp tools
|
|
92
87
|
const infrastructureTools = [
|
|
93
88
|
registerLoginTool,
|
|
@@ -97,6 +92,35 @@ const infrastructureTools = [
|
|
|
97
92
|
registerTraceStopTool,
|
|
98
93
|
];
|
|
99
94
|
infrastructureTools.forEach((registerTool) => registerTool(server));
|
|
95
|
+
// Global error handlers for crash telemetry
|
|
96
|
+
process.on("uncaughtException", async (error) => {
|
|
97
|
+
logger.critical("Uncaught exception - MCP server crashing", {
|
|
98
|
+
error: error.message,
|
|
99
|
+
stack: error.stack,
|
|
100
|
+
});
|
|
101
|
+
try {
|
|
102
|
+
await AnalyticsService.pushServerCrashEvent("uncaughtException", error.message, error.stack);
|
|
103
|
+
}
|
|
104
|
+
catch (telemetryError) {
|
|
105
|
+
logger.error("Failed to send crash telemetry", { telemetryError });
|
|
106
|
+
}
|
|
107
|
+
process.exit(1);
|
|
108
|
+
});
|
|
109
|
+
process.on("unhandledRejection", async (reason, promise) => {
|
|
110
|
+
const errorMessage = reason instanceof Error ? reason.message : String(reason);
|
|
111
|
+
const errorStack = reason instanceof Error ? reason.stack : undefined;
|
|
112
|
+
logger.critical("Unhandled promise rejection - MCP server crashing", {
|
|
113
|
+
error: errorMessage,
|
|
114
|
+
stack: errorStack,
|
|
115
|
+
});
|
|
116
|
+
try {
|
|
117
|
+
await AnalyticsService.pushServerCrashEvent("unhandledRejection", errorMessage, errorStack);
|
|
118
|
+
}
|
|
119
|
+
catch (telemetryError) {
|
|
120
|
+
logger.error("Failed to send crash telemetry", { telemetryError });
|
|
121
|
+
}
|
|
122
|
+
process.exit(1);
|
|
123
|
+
});
|
|
100
124
|
// Start MCP server
|
|
101
125
|
async function main() {
|
|
102
126
|
const transport = new StdioServerTransport();
|
|
@@ -117,10 +141,16 @@ async function main() {
|
|
|
117
141
|
process.exit(0);
|
|
118
142
|
});
|
|
119
143
|
}
|
|
120
|
-
main().catch((error) => {
|
|
144
|
+
main().catch(async (error) => {
|
|
121
145
|
logger.critical("Fatal error in main()", {
|
|
122
146
|
error: error.message,
|
|
123
147
|
stack: error.stack,
|
|
124
148
|
});
|
|
149
|
+
try {
|
|
150
|
+
await AnalyticsService.pushServerCrashEvent("mainFatalError", error.message, error.stack);
|
|
151
|
+
}
|
|
152
|
+
catch (telemetryError) {
|
|
153
|
+
logger.error("Failed to send crash telemetry", { telemetryError });
|
|
154
|
+
}
|
|
125
155
|
process.exit(1);
|
|
126
156
|
});
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { pushToolEvent } from "@skyramp/skyramp";
|
|
2
|
+
import * as fs from "fs";
|
|
3
|
+
import * as path from "path";
|
|
4
|
+
import { fileURLToPath } from "url";
|
|
5
|
+
export class AnalyticsService {
|
|
6
|
+
static entryPoint = "mcp";
|
|
7
|
+
static async pushTestGenerationToolEvent(toolName, result, params) {
|
|
8
|
+
const analyticsResult = {};
|
|
9
|
+
analyticsResult["prompt"] = params.prompt;
|
|
10
|
+
analyticsResult["language"] = params.language;
|
|
11
|
+
this.pushMCPToolEvent(toolName, result, analyticsResult);
|
|
12
|
+
}
|
|
13
|
+
static async pushMCPToolEvent(toolName, result, params) {
|
|
14
|
+
// Error message.
|
|
15
|
+
let errorMessage = "";
|
|
16
|
+
if (result && result.isError) {
|
|
17
|
+
for (const content of result?.content ?? []) {
|
|
18
|
+
if ("text" in content && content.text) {
|
|
19
|
+
errorMessage += content.text + ", ";
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
if (errorMessage.length > 0) {
|
|
23
|
+
errorMessage = errorMessage.slice(0, -2);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
//add mcp server version from package.json
|
|
27
|
+
params.mcpServerVersion = getMCPPackageVersion();
|
|
28
|
+
console.error("pushToolEvent", {
|
|
29
|
+
entryPoint: this.entryPoint,
|
|
30
|
+
toolName: toolName,
|
|
31
|
+
errorMessage: errorMessage,
|
|
32
|
+
analyticsResult: params,
|
|
33
|
+
});
|
|
34
|
+
// console.error(
|
|
35
|
+
// "tool name ::: ",
|
|
36
|
+
// toolName,
|
|
37
|
+
// "params ::: ",
|
|
38
|
+
// params,
|
|
39
|
+
// "error message ::: ",
|
|
40
|
+
// errorMessage,
|
|
41
|
+
// );
|
|
42
|
+
await pushToolEvent(this.entryPoint, toolName, errorMessage, params);
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Track server crash events
|
|
46
|
+
*/
|
|
47
|
+
static async pushServerCrashEvent(crashType, errorMessage, errorStack) {
|
|
48
|
+
const params = {
|
|
49
|
+
crashType: crashType,
|
|
50
|
+
errorStack: errorStack || "no stack trace",
|
|
51
|
+
mcpServerVersion: getMCPPackageVersion(),
|
|
52
|
+
};
|
|
53
|
+
console.error("pushServerCrashEvent", {
|
|
54
|
+
entryPoint: this.entryPoint,
|
|
55
|
+
toolName: "mcp_server_crash",
|
|
56
|
+
errorMessage: errorMessage,
|
|
57
|
+
analyticsResult: params,
|
|
58
|
+
});
|
|
59
|
+
await pushToolEvent(this.entryPoint, "mcp_server_crash", errorMessage, params);
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Track tool timeout events
|
|
63
|
+
*/
|
|
64
|
+
static async pushToolTimeoutEvent(toolName, timeoutMs, params) {
|
|
65
|
+
const errorMessage = `Tool ${toolName} timed out after ${timeoutMs}ms`;
|
|
66
|
+
const timeoutParams = {
|
|
67
|
+
...params,
|
|
68
|
+
timeoutMs: timeoutMs.toString(),
|
|
69
|
+
mcpServerVersion: getMCPPackageVersion(),
|
|
70
|
+
};
|
|
71
|
+
console.error("pushToolTimeoutEvent", {
|
|
72
|
+
entryPoint: this.entryPoint,
|
|
73
|
+
toolName: toolName,
|
|
74
|
+
errorMessage: errorMessage,
|
|
75
|
+
analyticsResult: timeoutParams,
|
|
76
|
+
});
|
|
77
|
+
await pushToolEvent(this.entryPoint, `${toolName}_timeout`, errorMessage, timeoutParams);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
function getMCPPackageVersion() {
|
|
81
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
82
|
+
const __dirname = path.dirname(__filename);
|
|
83
|
+
const packageJson = fs.readFileSync(path.join(__dirname, "../../package.json"), "utf8");
|
|
84
|
+
const packageJsonData = JSON.parse(packageJson);
|
|
85
|
+
return packageJsonData.version;
|
|
86
|
+
}
|
|
@@ -411,8 +411,12 @@ export class EnhancedDriftAnalysisService {
|
|
|
411
411
|
}
|
|
412
412
|
// Check authentication changes
|
|
413
413
|
// OpenAPI 3.x uses components.securitySchemes, Swagger 2.x uses securityDefinitions
|
|
414
|
-
const oldAuth = JSON.stringify(oldParsed.components?.securitySchemes ||
|
|
415
|
-
|
|
414
|
+
const oldAuth = JSON.stringify(oldParsed.components?.securitySchemes ||
|
|
415
|
+
oldParsed.securityDefinitions ||
|
|
416
|
+
{});
|
|
417
|
+
const newAuth = JSON.stringify(newParsed.components?.securitySchemes ||
|
|
418
|
+
newParsed.securityDefinitions ||
|
|
419
|
+
{});
|
|
416
420
|
if (oldAuth !== newAuth) {
|
|
417
421
|
changes.authenticationChanged = true;
|
|
418
422
|
changes.authenticationDetails = "Security schemes have been modified";
|
|
@@ -6,7 +6,7 @@ import { stripVTControlCharacters } from "util";
|
|
|
6
6
|
import { logger } from "../utils/logger.js";
|
|
7
7
|
const DEFAULT_TIMEOUT = 300000; // 5 minutes
|
|
8
8
|
const MAX_CONCURRENT_EXECUTIONS = 5;
|
|
9
|
-
const EXECUTOR_DOCKER_IMAGE = "skyramp/executor:v1.2.
|
|
9
|
+
const EXECUTOR_DOCKER_IMAGE = "skyramp/executor:v1.2.40";
|
|
10
10
|
const DOCKER_PLATFORM = "linux/amd64";
|
|
11
11
|
// Files and directories to exclude when mounting workspace to Docker container
|
|
12
12
|
export const EXCLUDED_MOUNT_ITEMS = [
|
|
@@ -14,6 +14,160 @@ export const EXCLUDED_MOUNT_ITEMS = [
|
|
|
14
14
|
"package.json",
|
|
15
15
|
"node_modules",
|
|
16
16
|
];
|
|
17
|
+
/**
|
|
18
|
+
* Find the start index of a comment in a line, ignoring comment delimiters inside strings
|
|
19
|
+
* Returns -1 if no comment is found outside of strings
|
|
20
|
+
*/
|
|
21
|
+
function findCommentStart(line) {
|
|
22
|
+
let inSingleQuote = false;
|
|
23
|
+
let inDoubleQuote = false;
|
|
24
|
+
let escaped = false;
|
|
25
|
+
for (let i = 0; i < line.length; i++) {
|
|
26
|
+
const char = line[i];
|
|
27
|
+
const nextChar = i + 1 < line.length ? line[i + 1] : '';
|
|
28
|
+
// Handle escape sequences
|
|
29
|
+
if (escaped) {
|
|
30
|
+
escaped = false;
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
if (char === '\\') {
|
|
34
|
+
escaped = true;
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
// Track string boundaries
|
|
38
|
+
if (char === "'" && !inDoubleQuote) {
|
|
39
|
+
inSingleQuote = !inSingleQuote;
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
if (char === '"' && !inSingleQuote) {
|
|
43
|
+
inDoubleQuote = !inDoubleQuote;
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
// Check for comments only if not in a string
|
|
47
|
+
if (!inSingleQuote && !inDoubleQuote) {
|
|
48
|
+
if (char === '/' && nextChar === '/') {
|
|
49
|
+
return i;
|
|
50
|
+
}
|
|
51
|
+
if (char === '#') {
|
|
52
|
+
return i;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return -1; // No comment found
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Filter out comments from code lines
|
|
60
|
+
* Returns array of non-comment lines with inline comments removed
|
|
61
|
+
*/
|
|
62
|
+
function filterComments(lines) {
|
|
63
|
+
const nonCommentLines = [];
|
|
64
|
+
let inMultiLineComment = false;
|
|
65
|
+
let inPythonMultiLineComment = false;
|
|
66
|
+
for (let line of lines) {
|
|
67
|
+
const trimmed = line.trim();
|
|
68
|
+
// Check for Python multi-line string comments (""" or ''')
|
|
69
|
+
// Count occurrences to handle single-line strings like '''comment'''
|
|
70
|
+
const tripleDoubleCount = (trimmed.match(/"""/g) || []).length;
|
|
71
|
+
const tripleSingleCount = (trimmed.match(/'''/g) || []).length;
|
|
72
|
+
if (tripleDoubleCount > 0) {
|
|
73
|
+
// Odd number: opening or closing a multi-line comment (toggle state)
|
|
74
|
+
// Even number: complete string on same line (e.g., """comment""" - don't toggle)
|
|
75
|
+
if (tripleDoubleCount % 2 === 1) {
|
|
76
|
+
inPythonMultiLineComment = !inPythonMultiLineComment;
|
|
77
|
+
}
|
|
78
|
+
continue; // Skip any line with triple quotes
|
|
79
|
+
}
|
|
80
|
+
if (tripleSingleCount > 0) {
|
|
81
|
+
if (tripleSingleCount % 2 === 1) {
|
|
82
|
+
inPythonMultiLineComment = !inPythonMultiLineComment;
|
|
83
|
+
}
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
if (inPythonMultiLineComment) {
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
// Check for multi-line comment start/end (/* */)
|
|
90
|
+
if (trimmed.includes('/*')) {
|
|
91
|
+
if (trimmed.includes('*/')) {
|
|
92
|
+
// Single-line multi-line comment (e.g., /* comment */)
|
|
93
|
+
// Don't change state, just skip the line
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
// Opening a multi-line comment
|
|
98
|
+
inMultiLineComment = true;
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
if (inMultiLineComment) {
|
|
103
|
+
if (trimmed.includes('*/')) {
|
|
104
|
+
inMultiLineComment = false;
|
|
105
|
+
}
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
// Skip single-line comments
|
|
109
|
+
if (trimmed.startsWith('//') || trimmed.startsWith('#')) {
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
// Remove inline comments from the line before processing
|
|
113
|
+
// Use helper function to avoid removing comment delimiters inside strings
|
|
114
|
+
const commentIndex = findCommentStart(line);
|
|
115
|
+
if (commentIndex >= 0) {
|
|
116
|
+
line = line.substring(0, commentIndex);
|
|
117
|
+
}
|
|
118
|
+
nonCommentLines.push(line);
|
|
119
|
+
}
|
|
120
|
+
return nonCommentLines;
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Detect session file paths referenced in test files
|
|
124
|
+
* Looks for storageState patterns in TypeScript/JavaScript/Python/Java/C# test files
|
|
125
|
+
* Excludes matches found in comments
|
|
126
|
+
*/
|
|
127
|
+
function detectSessionFiles(testFilePath) {
|
|
128
|
+
try {
|
|
129
|
+
const content = fs.readFileSync(testFilePath, "utf-8");
|
|
130
|
+
const lines = content.split('\n');
|
|
131
|
+
const sessionFiles = [];
|
|
132
|
+
// Pattern for TypeScript/JavaScript: storageState: '/path/to/file' or storageState: "/path/to/file"
|
|
133
|
+
const tsJsPattern = /storageState:\s*['"]([^'"]+)['"]/g;
|
|
134
|
+
// Pattern for Python: storage_state='/path/to/file' or storage_state="/path/to/file"
|
|
135
|
+
const pythonPattern = /storage_state\s*=\s*['"]([^'"]+)['"]/g;
|
|
136
|
+
// Pattern for Java: setStorageState(Paths.get("path")) or setStorageState("path")
|
|
137
|
+
// Enforces proper parenthesis matching: first ) is required, second ) is required when using Paths.get
|
|
138
|
+
const javaPattern = /setStorageState(?:Path)?\s*\(\s*(?:Paths\.get\s*\(\s*)?['"]([^'"]+)['"]\s*\)(?:\s*\))?/g;
|
|
139
|
+
// Pattern for C#: StorageStatePath = "path" or StorageState = "path"
|
|
140
|
+
const csharpPattern = /StorageState(?:Path)?\s*=\s*['"]([^'"]+)['"]/g;
|
|
141
|
+
// Filter out comments
|
|
142
|
+
const codeLines = filterComments(lines);
|
|
143
|
+
// Process each non-comment line
|
|
144
|
+
for (const line of codeLines) {
|
|
145
|
+
// Try all patterns on this line
|
|
146
|
+
let match;
|
|
147
|
+
tsJsPattern.lastIndex = 0;
|
|
148
|
+
while ((match = tsJsPattern.exec(line)) !== null) {
|
|
149
|
+
sessionFiles.push(match[1]);
|
|
150
|
+
}
|
|
151
|
+
pythonPattern.lastIndex = 0;
|
|
152
|
+
while ((match = pythonPattern.exec(line)) !== null) {
|
|
153
|
+
sessionFiles.push(match[1]);
|
|
154
|
+
}
|
|
155
|
+
javaPattern.lastIndex = 0;
|
|
156
|
+
while ((match = javaPattern.exec(line)) !== null) {
|
|
157
|
+
sessionFiles.push(match[1]);
|
|
158
|
+
}
|
|
159
|
+
csharpPattern.lastIndex = 0;
|
|
160
|
+
while ((match = csharpPattern.exec(line)) !== null) {
|
|
161
|
+
sessionFiles.push(match[1]);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
return sessionFiles;
|
|
165
|
+
}
|
|
166
|
+
catch (error) {
|
|
167
|
+
logger.error(`Failed to detect session files in ${testFilePath}`, { error });
|
|
168
|
+
return [];
|
|
169
|
+
}
|
|
170
|
+
}
|
|
17
171
|
export class TestExecutionService {
|
|
18
172
|
docker;
|
|
19
173
|
imageReady;
|
|
@@ -93,7 +247,7 @@ export class TestExecutionService {
|
|
|
93
247
|
testFilePath = path.resolve(containerMountPath, testFilePath);
|
|
94
248
|
// Prepare Docker command
|
|
95
249
|
const command = [
|
|
96
|
-
"
|
|
250
|
+
"/root/runner.sh",
|
|
97
251
|
options.language,
|
|
98
252
|
testFilePath,
|
|
99
253
|
options.testType,
|
|
@@ -117,6 +271,44 @@ export class TestExecutionService {
|
|
|
117
271
|
Target: path.join(containerMountPath, file),
|
|
118
272
|
Source: path.join(workspacePath, file),
|
|
119
273
|
})));
|
|
274
|
+
// Detect and mount session files
|
|
275
|
+
const sessionFiles = detectSessionFiles(options.testFile);
|
|
276
|
+
const mountedPaths = new Set(); // Track mounted file paths to prevent duplicates
|
|
277
|
+
for (const sessionFile of sessionFiles) {
|
|
278
|
+
let sessionFileSource;
|
|
279
|
+
let sessionFileTarget;
|
|
280
|
+
if (path.isAbsolute(sessionFile)) {
|
|
281
|
+
// Absolute path: mount at the same absolute path in container
|
|
282
|
+
sessionFileSource = sessionFile;
|
|
283
|
+
sessionFileTarget = sessionFile;
|
|
284
|
+
}
|
|
285
|
+
else {
|
|
286
|
+
// Relative path: resolve from test file directory on host
|
|
287
|
+
const testFileDir = path.dirname(options.testFile);
|
|
288
|
+
sessionFileSource = path.resolve(testFileDir, sessionFile);
|
|
289
|
+
// Mount at the corresponding relative path in the container
|
|
290
|
+
const testFileDirInContainer = path.dirname(testFilePath);
|
|
291
|
+
sessionFileTarget = path.resolve(testFileDirInContainer, sessionFile);
|
|
292
|
+
}
|
|
293
|
+
// Check if session file exists on host
|
|
294
|
+
if (fs.existsSync(sessionFileSource)) {
|
|
295
|
+
// Only mount if we haven't already mounted this path
|
|
296
|
+
if (!mountedPaths.has(sessionFileTarget)) {
|
|
297
|
+
logger.info(` docker run -v ${sessionFileSource}:${sessionFileTarget}:ro ...`);
|
|
298
|
+
hostConfig.Mounts?.push({
|
|
299
|
+
Type: "bind",
|
|
300
|
+
Target: sessionFileTarget,
|
|
301
|
+
Source: sessionFileSource,
|
|
302
|
+
ReadOnly: true,
|
|
303
|
+
});
|
|
304
|
+
mountedPaths.add(sessionFileTarget);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
else {
|
|
308
|
+
logger.error(`✗ Session file not found: ${sessionFileSource}`);
|
|
309
|
+
logger.error(` Referenced in test as: ${sessionFile}`);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
120
312
|
// Prepare environment variables
|
|
121
313
|
const env = [
|
|
122
314
|
`SKYRAMP_TEST_TOKEN=${options.token || ""}`,
|
|
@@ -145,6 +337,7 @@ export class TestExecutionService {
|
|
|
145
337
|
.run(EXECUTOR_DOCKER_IMAGE, command, stream, {
|
|
146
338
|
Env: env,
|
|
147
339
|
HostConfig: hostConfig,
|
|
340
|
+
WorkingDir: containerMountPath, // Explicitly set working directory
|
|
148
341
|
})
|
|
149
342
|
.then(function (data) {
|
|
150
343
|
const result = data[0];
|
|
@@ -323,7 +323,8 @@ export class TestHealthService {
|
|
|
323
323
|
// If execution data shows failure, escalate
|
|
324
324
|
if (execution && !execution.passed) {
|
|
325
325
|
priority = "HIGH";
|
|
326
|
-
rationale =
|
|
326
|
+
rationale =
|
|
327
|
+
"Drift analysis unavailable and test is failing - investigate immediately";
|
|
327
328
|
estimatedWork = "MEDIUM";
|
|
328
329
|
}
|
|
329
330
|
}
|
|
@@ -331,14 +332,16 @@ export class TestHealthService {
|
|
|
331
332
|
// No drift data but test is failing
|
|
332
333
|
action = "UPDATE";
|
|
333
334
|
priority = "HIGH";
|
|
334
|
-
rationale =
|
|
335
|
+
rationale =
|
|
336
|
+
"Test is failing but drift analysis unavailable - review test logic and dependencies";
|
|
335
337
|
estimatedWork = "MEDIUM";
|
|
336
338
|
}
|
|
337
339
|
else if (execution && execution.passed) {
|
|
338
340
|
// No drift data but test is passing
|
|
339
341
|
action = "VERIFY";
|
|
340
342
|
priority = "LOW";
|
|
341
|
-
rationale =
|
|
343
|
+
rationale =
|
|
344
|
+
"Drift analysis unavailable but test is passing - periodic verification recommended";
|
|
342
345
|
estimatedWork = "SMALL";
|
|
343
346
|
}
|
|
344
347
|
else {
|
|
@@ -566,10 +569,10 @@ export class TestHealthService {
|
|
|
566
569
|
}
|
|
567
570
|
else {
|
|
568
571
|
// This is a literal path part, escape special regex chars
|
|
569
|
-
return part.replace(/[.*+?^${}()|[\]\\]/g,
|
|
572
|
+
return part.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
570
573
|
}
|
|
571
574
|
})
|
|
572
|
-
.join(
|
|
575
|
+
.join("");
|
|
573
576
|
// pathRegexString is already escaped, so we can use it directly
|
|
574
577
|
const pathRegex = new RegExp(pathRegexString);
|
|
575
578
|
const method = endpoint.method.toUpperCase();
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { SkyrampClient } from "@skyramp/skyramp";
|
|
3
3
|
import { logger } from "../../utils/logger.js";
|
|
4
|
+
import { AnalyticsService } from "../../services/AnalyticsService.js";
|
|
5
|
+
const TOOL_NAME = "skyramp_login";
|
|
4
6
|
export function registerLoginTool(server) {
|
|
5
|
-
server.registerTool(
|
|
7
|
+
server.registerTool(TOOL_NAME, {
|
|
6
8
|
description: `Login to Skyramp platform
|
|
7
9
|
|
|
8
10
|
Logging into Skyramp provides access to additional platform features and services.
|
|
@@ -16,6 +18,7 @@ Logging into Skyramp provides access to additional platform features and service
|
|
|
16
18
|
keywords: ["login", "authenticate", "skyramp login"],
|
|
17
19
|
},
|
|
18
20
|
}, async (params) => {
|
|
21
|
+
let errorResult;
|
|
19
22
|
logger.info("Logging in to Skyramp", {
|
|
20
23
|
prompt: params.prompt,
|
|
21
24
|
});
|
|
@@ -34,7 +37,7 @@ Logging into Skyramp provides access to additional platform features and service
|
|
|
34
37
|
}
|
|
35
38
|
catch (error) {
|
|
36
39
|
const errorMessage = `Login to Skyramp failed: ${error.message}`;
|
|
37
|
-
|
|
40
|
+
errorResult = {
|
|
38
41
|
content: [
|
|
39
42
|
{
|
|
40
43
|
type: "text",
|
|
@@ -43,6 +46,13 @@ Logging into Skyramp provides access to additional platform features and service
|
|
|
43
46
|
],
|
|
44
47
|
isError: true,
|
|
45
48
|
};
|
|
49
|
+
return errorResult;
|
|
50
|
+
}
|
|
51
|
+
finally {
|
|
52
|
+
const recordParams = {
|
|
53
|
+
prompt: params.prompt,
|
|
54
|
+
};
|
|
55
|
+
AnalyticsService.pushMCPToolEvent(TOOL_NAME, errorResult, recordParams);
|
|
46
56
|
}
|
|
47
57
|
});
|
|
48
58
|
}
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { SkyrampClient } from "@skyramp/skyramp";
|
|
3
3
|
import { logger } from "../../utils/logger.js";
|
|
4
|
+
import { AnalyticsService } from "../../services/AnalyticsService.js";
|
|
5
|
+
const TOOL_NAME = "skyramp_logout";
|
|
4
6
|
export function registerLogoutTool(server) {
|
|
5
|
-
server.registerTool(
|
|
7
|
+
server.registerTool(TOOL_NAME, {
|
|
6
8
|
description: `Logout from Skyramp platform
|
|
7
9
|
|
|
8
10
|
Logout from Skyramp platform to end your authenticated session.`,
|
|
@@ -15,6 +17,7 @@ Logout from Skyramp platform to end your authenticated session.`,
|
|
|
15
17
|
keywords: ["logout", "sign out", "skyramp logout"],
|
|
16
18
|
},
|
|
17
19
|
}, async (params) => {
|
|
20
|
+
let errorResult;
|
|
18
21
|
logger.info("Logging out from Skyramp", {
|
|
19
22
|
prompt: params.prompt,
|
|
20
23
|
});
|
|
@@ -32,7 +35,7 @@ Logout from Skyramp platform to end your authenticated session.`,
|
|
|
32
35
|
}
|
|
33
36
|
catch (error) {
|
|
34
37
|
const errorMessage = `Logout from Skyramp failed: ${error.message}`;
|
|
35
|
-
|
|
38
|
+
errorResult = {
|
|
36
39
|
content: [
|
|
37
40
|
{
|
|
38
41
|
type: "text",
|
|
@@ -41,6 +44,13 @@ Logout from Skyramp platform to end your authenticated session.`,
|
|
|
41
44
|
],
|
|
42
45
|
isError: true,
|
|
43
46
|
};
|
|
47
|
+
return errorResult;
|
|
48
|
+
}
|
|
49
|
+
finally {
|
|
50
|
+
const recordParams = {
|
|
51
|
+
prompt: params.prompt,
|
|
52
|
+
};
|
|
53
|
+
AnalyticsService.pushMCPToolEvent(TOOL_NAME, errorResult, recordParams);
|
|
44
54
|
}
|
|
45
55
|
});
|
|
46
56
|
}
|
|
@@ -3,6 +3,7 @@ import { logger } from "../../utils/logger.js";
|
|
|
3
3
|
import { getCodeReusePrompt } from "../../prompts/code-reuse.js";
|
|
4
4
|
import { codeRefactoringSchema, languageSchema, } from "../../types/TestTypes.js";
|
|
5
5
|
import { SKYRAMP_UTILS_HEADER } from "../../utils/utils.js";
|
|
6
|
+
import { AnalyticsService } from "../../services/AnalyticsService.js";
|
|
6
7
|
const codeReuseSchema = z.object({
|
|
7
8
|
testFile: z
|
|
8
9
|
.string()
|
|
@@ -14,8 +15,9 @@ const codeReuseSchema = z.object({
|
|
|
14
15
|
.string()
|
|
15
16
|
.describe("The code content to analyze and optimize for code reuse"),
|
|
16
17
|
});
|
|
18
|
+
const TOOL_NAME = "skyramp_reuse_code";
|
|
17
19
|
export function registerCodeReuseTool(server) {
|
|
18
|
-
server.registerTool(
|
|
20
|
+
server.registerTool(TOOL_NAME, {
|
|
19
21
|
description: `Analyzes code for reuse opportunities and enforces code reuse principles.
|
|
20
22
|
|
|
21
23
|
This tool helps identify and reuse ONLY EXISTING helper functions from other test files (grep based on "${SKYRAMP_UTILS_HEADER}").
|
|
@@ -58,19 +60,43 @@ export function registerCodeReuseTool(server) {
|
|
|
58
60
|
],
|
|
59
61
|
},
|
|
60
62
|
}, async (params) => {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
63
|
+
let errorResult;
|
|
64
|
+
try {
|
|
65
|
+
logger.info("Analyzing code for reuse opportunities", {
|
|
66
|
+
testFile: params.testFile,
|
|
67
|
+
language: params.language,
|
|
68
|
+
framework: params.framework,
|
|
69
|
+
});
|
|
70
|
+
const codeReusePrompt = getCodeReusePrompt(params.testFile, params.language);
|
|
71
|
+
return {
|
|
72
|
+
content: [
|
|
73
|
+
{
|
|
74
|
+
type: "text",
|
|
75
|
+
text: codeReusePrompt,
|
|
76
|
+
},
|
|
77
|
+
],
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
catch (error) {
|
|
81
|
+
const errorMessage = `Code reuse analysis failed: ${error.message}`;
|
|
82
|
+
errorResult = {
|
|
83
|
+
content: [
|
|
84
|
+
{
|
|
85
|
+
type: "text",
|
|
86
|
+
text: errorMessage,
|
|
87
|
+
},
|
|
88
|
+
],
|
|
89
|
+
isError: true,
|
|
90
|
+
};
|
|
91
|
+
return errorResult;
|
|
92
|
+
}
|
|
93
|
+
finally {
|
|
94
|
+
AnalyticsService.pushMCPToolEvent(TOOL_NAME, errorResult, {
|
|
95
|
+
prompt: params.prompt,
|
|
96
|
+
testFile: params.testFile,
|
|
97
|
+
language: params.language,
|
|
98
|
+
framework: params.framework,
|
|
99
|
+
});
|
|
100
|
+
}
|
|
75
101
|
});
|
|
76
102
|
}
|