@skyramp/mcp 0.0.54 โ 0.0.55
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
CHANGED
|
@@ -30,6 +30,8 @@ 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 { registerTestbotPrompt } from "./prompts/testbot/testbot-prompts.js";
|
|
34
|
+
import { registerInitTestbotTool } from "./tools/initTestbotTool.js";
|
|
33
35
|
import { AnalyticsService } from "./services/AnalyticsService.js";
|
|
34
36
|
const server = new McpServer({
|
|
35
37
|
name: "Skyramp MCP Server",
|
|
@@ -51,6 +53,10 @@ const prompts = [
|
|
|
51
53
|
registerStartTraceCollectionPrompt,
|
|
52
54
|
registerTestHealthPrompt,
|
|
53
55
|
];
|
|
56
|
+
if (process.env.SKYRAMP_FEATURE_TESTBOT === "1") {
|
|
57
|
+
prompts.push(registerTestbotPrompt);
|
|
58
|
+
logger.info("TestBot prompt enabled via SKYRAMP_ENABLE_TESTBOT");
|
|
59
|
+
}
|
|
54
60
|
prompts.forEach((registerPrompt) => registerPrompt(server));
|
|
55
61
|
logger.info("All prompts registered successfully");
|
|
56
62
|
// Register test generation tools
|
|
@@ -91,6 +97,10 @@ const infrastructureTools = [
|
|
|
91
97
|
registerTraceTool,
|
|
92
98
|
registerTraceStopTool,
|
|
93
99
|
];
|
|
100
|
+
if (process.env.SKYRAMP_FEATURE_TESTBOT === "1") {
|
|
101
|
+
infrastructureTools.push(registerInitTestbotTool);
|
|
102
|
+
logger.info("TestBot init tool enabled via SKYRAMP_FEATURE_TESTBOT");
|
|
103
|
+
}
|
|
94
104
|
infrastructureTools.forEach((registerTool) => registerTool(server));
|
|
95
105
|
// Global error handlers for crash telemetry
|
|
96
106
|
process.on("uncaughtException", async (error) => {
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { logger } from "../../utils/logger.js";
|
|
3
|
+
function getTestbotRecommendationPrompt(prTitle, prDescription, summaryOutputFile) {
|
|
4
|
+
return `<TITLE>${prTitle}</TITLE>
|
|
5
|
+
<DESCRIPTION>${prDescription}</DESCRIPTION>
|
|
6
|
+
|
|
7
|
+
Use the MCP tools offered by Skyramp MCP server to analyze the pull request title and description to come up with a business justification
|
|
8
|
+
case for this code change. Based on your analysis of the business case, perform
|
|
9
|
+
|
|
10
|
+
1. Get recommendations for new contract, e2e, smoke, load tests that can be generated by Skyramp MCP tools
|
|
11
|
+
2. Use Skyramp MCP to execute the tests and validate the results.
|
|
12
|
+
3. Generate a CONCISE summary to '${summaryOutputFile}' following this format:
|
|
13
|
+
|
|
14
|
+
## ๐ค Skyramp Test Bot Recommendations
|
|
15
|
+
|
|
16
|
+
### ๐ก Suggested Test Coverage
|
|
17
|
+
- Brief list of new test types added and for which endpoint.
|
|
18
|
+
|
|
19
|
+
### ๐งช Test Results
|
|
20
|
+
- List each test executed with pass/fail status and brief details
|
|
21
|
+
|
|
22
|
+
### โ ๏ธ Issues Found
|
|
23
|
+
- List any test failures or bugs discovered (if none, write 'None')
|
|
24
|
+
|
|
25
|
+
Keep the summary brief and scannable. Avoid repeating code diff details that are already visible in the PR.
|
|
26
|
+
|
|
27
|
+
Use the skyramp-mcp tools available to you for test analysis, generation, and execution.`;
|
|
28
|
+
}
|
|
29
|
+
function getTestbotMaintenancePrompt(diffFile, testDirectory, summaryOutputFile) {
|
|
30
|
+
return `Based on the git diff in ${diffFile} file, perform the following test maintenance tasks:
|
|
31
|
+
|
|
32
|
+
1. Check if there are any relevant Skyramp tests in the '${testDirectory}' directory that correspond to the code changes.
|
|
33
|
+
2. If there are relevant tests, use Skyramp MCP tools to analyze and modify them to ensure they pass with the new changes.
|
|
34
|
+
3. If there are no tests that can validate the changes, use Skyramp MCP to generate appropriate Skyramp tests for the new or modified code.
|
|
35
|
+
4. Use Skyramp MCP to execute the tests and validate the results.
|
|
36
|
+
5. Generate a CONCISE summary to '${summaryOutputFile}' following this format:
|
|
37
|
+
|
|
38
|
+
## ๐ค Skyramp Test Bot Summary
|
|
39
|
+
|
|
40
|
+
**Tests Modified:** [number] | **Tests Created:** [number] | **Tests Executed:** [number] | **Passed:** [number] | **Failed:** [number]
|
|
41
|
+
|
|
42
|
+
### โ
Maintenance Summary
|
|
43
|
+
- Brief one-line summary of test changes (details are in the commit)
|
|
44
|
+
|
|
45
|
+
### ๐งช Test Results
|
|
46
|
+
- List each test executed with pass/fail status and brief details
|
|
47
|
+
|
|
48
|
+
### โ ๏ธ Issues Found
|
|
49
|
+
- List any test failures or bugs discovered (if none, write 'None')
|
|
50
|
+
|
|
51
|
+
Keep the summary brief and scannable. Avoid repeating code diff details that are already visible in the PR.
|
|
52
|
+
|
|
53
|
+
Use the skyramp-mcp tools available to you for test analysis, generation, and execution.`;
|
|
54
|
+
}
|
|
55
|
+
export function registerTestbotPrompt(server) {
|
|
56
|
+
logger.info("Registering testbot prompt");
|
|
57
|
+
server.registerPrompt("skyramp_testbot", {
|
|
58
|
+
description: "Run Skyramp TestBot to generate test recommendations and perform test maintenance for a pull request.",
|
|
59
|
+
argsSchema: {
|
|
60
|
+
prTitle: z
|
|
61
|
+
.string()
|
|
62
|
+
.describe("Pull request title"),
|
|
63
|
+
prDescription: z
|
|
64
|
+
.string()
|
|
65
|
+
.describe("Pull request description/body"),
|
|
66
|
+
diffFile: z
|
|
67
|
+
.string()
|
|
68
|
+
.describe("Path to the git diff file"),
|
|
69
|
+
testDirectory: z
|
|
70
|
+
.string()
|
|
71
|
+
.default("tests")
|
|
72
|
+
.describe("Directory containing Skyramp tests"),
|
|
73
|
+
recommendationSummaryOutputFile: z
|
|
74
|
+
.string()
|
|
75
|
+
.describe("File path where the agent should write the recommendation summary"),
|
|
76
|
+
maintenanceSummaryOutputFile: z
|
|
77
|
+
.string()
|
|
78
|
+
.describe("File path where the agent should write the maintenance summary"),
|
|
79
|
+
},
|
|
80
|
+
}, (args) => {
|
|
81
|
+
const recommendationPrompt = getTestbotRecommendationPrompt(args.prTitle, args.prDescription, args.recommendationSummaryOutputFile);
|
|
82
|
+
const maintenancePrompt = getTestbotMaintenancePrompt(args.diffFile, args.testDirectory, args.maintenanceSummaryOutputFile);
|
|
83
|
+
return {
|
|
84
|
+
messages: [
|
|
85
|
+
{
|
|
86
|
+
role: "user",
|
|
87
|
+
content: {
|
|
88
|
+
type: "text",
|
|
89
|
+
text: `# Skyramp TestBot Workflow
|
|
90
|
+
|
|
91
|
+
Follow these two phases in order.
|
|
92
|
+
|
|
93
|
+
## Phase 1: Test Recommendations
|
|
94
|
+
|
|
95
|
+
${recommendationPrompt}
|
|
96
|
+
|
|
97
|
+
## Phase 2: Test Maintenance
|
|
98
|
+
|
|
99
|
+
${maintenancePrompt}`,
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
],
|
|
103
|
+
};
|
|
104
|
+
});
|
|
105
|
+
}
|
|
@@ -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.3.
|
|
9
|
+
const EXECUTOR_DOCKER_IMAGE = "skyramp/executor:v1.3.8";
|
|
10
10
|
const DOCKER_PLATFORM = "linux/amd64";
|
|
11
11
|
const EXECUTION_PROGRESS_INTERVAL = 10000; // 10 seconds between progress updates during execution
|
|
12
12
|
// Files and directories to exclude when mounting workspace to Docker container
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { simpleGit } from "simple-git";
|
|
3
|
+
import * as fs from "fs/promises";
|
|
4
|
+
import * as path from "path";
|
|
5
|
+
import { logger } from "../utils/logger.js";
|
|
6
|
+
import { AnalyticsService } from "../services/AnalyticsService.js";
|
|
7
|
+
const TOOL_NAME = "skyramp_init_testbot";
|
|
8
|
+
const WORKFLOW_FILENAME = "skyramp-test-bot.yml";
|
|
9
|
+
function generateWorkflow(targetBranches, agentType) {
|
|
10
|
+
const apiKeyLine = agentType === "copilot"
|
|
11
|
+
? " copilot_api_key: ${{ secrets.COPILOT_API_KEY }}"
|
|
12
|
+
: " cursor_api_key: ${{ secrets.CURSOR_API_KEY }}";
|
|
13
|
+
const branchesBlock = targetBranches && targetBranches.length > 0
|
|
14
|
+
? `\n branches:\n${targetBranches.map((b) => ` - '${b}'`).join("\n")}`
|
|
15
|
+
: "";
|
|
16
|
+
return `name: Skyramp Test Automation
|
|
17
|
+
on:
|
|
18
|
+
pull_request:${branchesBlock}
|
|
19
|
+
|
|
20
|
+
jobs:
|
|
21
|
+
test-maintenance:
|
|
22
|
+
runs-on: ubuntu-latest
|
|
23
|
+
permissions:
|
|
24
|
+
contents: write
|
|
25
|
+
pull-requests: write
|
|
26
|
+
|
|
27
|
+
steps:
|
|
28
|
+
- name: Checkout code
|
|
29
|
+
uses: actions/checkout@v4
|
|
30
|
+
with:
|
|
31
|
+
fetch-depth: 0
|
|
32
|
+
|
|
33
|
+
- name: Run Skyramp Test Bot
|
|
34
|
+
uses: skyramp/test-bot@v1
|
|
35
|
+
with:
|
|
36
|
+
skyramp_license_file: \${{ secrets.SKYRAMP_LICENSE }}
|
|
37
|
+
${apiKeyLine}
|
|
38
|
+
`;
|
|
39
|
+
}
|
|
40
|
+
export function registerInitTestbotTool(server) {
|
|
41
|
+
server.registerTool(TOOL_NAME, {
|
|
42
|
+
description: `Initialize Skyramp Test Bot for a GitHub repository
|
|
43
|
+
|
|
44
|
+
Sets up a GitHub Actions workflow that automatically generates and maintains tests on pull requests using Skyramp Test Bot. This tool validates the repository, creates the workflow file, and provides setup instructions for required secrets.`,
|
|
45
|
+
inputSchema: {
|
|
46
|
+
repository_path: z
|
|
47
|
+
.string()
|
|
48
|
+
.describe("Absolute path to the user's GitHub repository"),
|
|
49
|
+
agent_type: z
|
|
50
|
+
.enum(["cursor", "copilot"])
|
|
51
|
+
.default("cursor")
|
|
52
|
+
.describe("Which AI agent the user uses (cursor or copilot). Defaults to cursor."),
|
|
53
|
+
target_branches: z
|
|
54
|
+
.array(z.string())
|
|
55
|
+
.optional()
|
|
56
|
+
.describe("Branches to trigger the test bot on pull requests. If not specified, the workflow triggers on all pull requests."),
|
|
57
|
+
},
|
|
58
|
+
_meta: {
|
|
59
|
+
keywords: [
|
|
60
|
+
"init",
|
|
61
|
+
"testbot",
|
|
62
|
+
"test-bot",
|
|
63
|
+
"github actions",
|
|
64
|
+
"workflow",
|
|
65
|
+
"setup",
|
|
66
|
+
"ci",
|
|
67
|
+
],
|
|
68
|
+
},
|
|
69
|
+
}, async (params) => {
|
|
70
|
+
let errorResult;
|
|
71
|
+
const repoPath = params.repository_path;
|
|
72
|
+
const agentType = params.agent_type;
|
|
73
|
+
const targetBranches = params.target_branches;
|
|
74
|
+
logger.info("Initializing Skyramp Test Bot", {
|
|
75
|
+
repository_path: repoPath,
|
|
76
|
+
agent_type: agentType,
|
|
77
|
+
target_branches: targetBranches,
|
|
78
|
+
});
|
|
79
|
+
try {
|
|
80
|
+
// Validate it's a git repo
|
|
81
|
+
const git = simpleGit(repoPath);
|
|
82
|
+
const isRepo = await git.checkIsRepo();
|
|
83
|
+
if (!isRepo) {
|
|
84
|
+
errorResult = {
|
|
85
|
+
content: [
|
|
86
|
+
{
|
|
87
|
+
type: "text",
|
|
88
|
+
text: `Error: "${repoPath}" is not a Git repository. Please run this tool from a valid Git repository.`,
|
|
89
|
+
},
|
|
90
|
+
],
|
|
91
|
+
isError: true,
|
|
92
|
+
};
|
|
93
|
+
return errorResult;
|
|
94
|
+
}
|
|
95
|
+
// Validate it has a GitHub remote
|
|
96
|
+
const remotes = await git.getRemotes(true);
|
|
97
|
+
const hasGitHubRemote = remotes.some((remote) => remote.refs.fetch?.includes("github.com") ||
|
|
98
|
+
remote.refs.push?.includes("github.com"));
|
|
99
|
+
if (!hasGitHubRemote) {
|
|
100
|
+
errorResult = {
|
|
101
|
+
content: [
|
|
102
|
+
{
|
|
103
|
+
type: "text",
|
|
104
|
+
text: `Error: No GitHub remote found in "${repoPath}". Skyramp Test Bot requires a GitHub repository. Please add a GitHub remote and try again.`,
|
|
105
|
+
},
|
|
106
|
+
],
|
|
107
|
+
isError: true,
|
|
108
|
+
};
|
|
109
|
+
return errorResult;
|
|
110
|
+
}
|
|
111
|
+
// Create the workflow directory if needed and write the workflow file atomically
|
|
112
|
+
const workflowDir = path.join(repoPath, ".github", "workflows");
|
|
113
|
+
await fs.mkdir(workflowDir, { recursive: true });
|
|
114
|
+
const workflowPath = path.join(workflowDir, WORKFLOW_FILENAME);
|
|
115
|
+
const workflowContent = generateWorkflow(targetBranches, agentType);
|
|
116
|
+
try {
|
|
117
|
+
await fs.writeFile(workflowPath, workflowContent, {
|
|
118
|
+
encoding: "utf-8",
|
|
119
|
+
flag: "wx",
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
catch (err) {
|
|
123
|
+
if (err.code === "EEXIST") {
|
|
124
|
+
return {
|
|
125
|
+
content: [
|
|
126
|
+
{
|
|
127
|
+
type: "text",
|
|
128
|
+
text: `Skyramp Test Bot workflow already exists at ".github/workflows/${WORKFLOW_FILENAME}". No changes were made. If you want to reconfigure it, delete the existing file and run this tool again.`,
|
|
129
|
+
},
|
|
130
|
+
],
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
throw err;
|
|
134
|
+
}
|
|
135
|
+
// Build success message with setup instructions
|
|
136
|
+
const secretName = agentType === "copilot" ? "COPILOT_API_KEY" : "CURSOR_API_KEY";
|
|
137
|
+
const agentLabel = agentType === "copilot" ? "Copilot" : "Cursor";
|
|
138
|
+
const successMessage = `Skyramp Test Bot workflow created successfully at ".github/workflows/${WORKFLOW_FILENAME}".
|
|
139
|
+
|
|
140
|
+
To complete the setup, configure the following GitHub Secrets in your repository (Settings > Secrets and variables > Actions):
|
|
141
|
+
|
|
142
|
+
1. **SKYRAMP_LICENSE** - The contents of your Skyramp license file (paste the full license file text)
|
|
143
|
+
2. **${secretName}** - Your ${agentLabel} API key
|
|
144
|
+
|
|
145
|
+
The workflow is configured to run on ${targetBranches && targetBranches.length > 0 ? `pull requests targeting: ${targetBranches.join(", ")}` : "all pull requests"}
|
|
146
|
+
|
|
147
|
+
Required repository permissions (already configured in the workflow):
|
|
148
|
+
- \`contents: write\` - To commit generated tests
|
|
149
|
+
- \`pull-requests: write\` - To post comments on PRs
|
|
150
|
+
|
|
151
|
+
Next steps:
|
|
152
|
+
1. Add the required secrets to your GitHub repository
|
|
153
|
+
2. Commit and push the new workflow file
|
|
154
|
+
3. Open a pull request to see Skyramp Test Bot in action`;
|
|
155
|
+
return {
|
|
156
|
+
content: [
|
|
157
|
+
{
|
|
158
|
+
type: "text",
|
|
159
|
+
text: successMessage,
|
|
160
|
+
},
|
|
161
|
+
],
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
catch (error) {
|
|
165
|
+
const errorMessage = `Failed to initialize Skyramp Test Bot: ${error.message}`;
|
|
166
|
+
logger.error(errorMessage, { error });
|
|
167
|
+
errorResult = {
|
|
168
|
+
content: [
|
|
169
|
+
{
|
|
170
|
+
type: "text",
|
|
171
|
+
text: errorMessage,
|
|
172
|
+
},
|
|
173
|
+
],
|
|
174
|
+
isError: true,
|
|
175
|
+
};
|
|
176
|
+
return errorResult;
|
|
177
|
+
}
|
|
178
|
+
finally {
|
|
179
|
+
const recordParams = {
|
|
180
|
+
repository_path: repoPath,
|
|
181
|
+
agent_type: agentType,
|
|
182
|
+
target_branches: targetBranches ? targetBranches.join(",") : "",
|
|
183
|
+
};
|
|
184
|
+
AnalyticsService.pushMCPToolEvent(TOOL_NAME, errorResult, recordParams);
|
|
185
|
+
}
|
|
186
|
+
});
|
|
187
|
+
}
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
// @ts-ignore
|
|
2
|
+
import { registerInitTestbotTool } from "./initTestbotTool.js";
|
|
3
|
+
import { simpleGit } from "simple-git";
|
|
4
|
+
import * as fs from "fs/promises";
|
|
5
|
+
import * as fsSync from "fs";
|
|
6
|
+
import * as path from "path";
|
|
7
|
+
import * as os from "os";
|
|
8
|
+
// Mock logger and AnalyticsService to avoid side effects
|
|
9
|
+
jest.mock("../utils/logger.js", () => ({
|
|
10
|
+
logger: { info: jest.fn(), error: jest.fn() },
|
|
11
|
+
}));
|
|
12
|
+
jest.mock("../services/AnalyticsService.js", () => ({
|
|
13
|
+
AnalyticsService: { pushMCPToolEvent: jest.fn() },
|
|
14
|
+
}));
|
|
15
|
+
/**
|
|
16
|
+
* Captures the tool handler callback from registerInitTestbotTool
|
|
17
|
+
* by providing a fake McpServer with a mock registerTool method.
|
|
18
|
+
*/
|
|
19
|
+
function captureToolHandler() {
|
|
20
|
+
let handler;
|
|
21
|
+
const fakeServer = {
|
|
22
|
+
registerTool: (_name, _opts, fn) => {
|
|
23
|
+
handler = fn;
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
registerInitTestbotTool(fakeServer);
|
|
27
|
+
return handler;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Creates a temp directory, initializes a git repo, and optionally
|
|
31
|
+
* adds a GitHub remote.
|
|
32
|
+
*/
|
|
33
|
+
async function createTempRepo(options) {
|
|
34
|
+
const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "init-testbot-test-"));
|
|
35
|
+
const git = simpleGit(tmpDir);
|
|
36
|
+
await git.init();
|
|
37
|
+
if (options?.addGithubRemote !== false) {
|
|
38
|
+
await git.addRemote("origin", "https://github.com/test-org/test-repo.git");
|
|
39
|
+
}
|
|
40
|
+
return tmpDir;
|
|
41
|
+
}
|
|
42
|
+
describe("registerInitTestbotTool", () => {
|
|
43
|
+
let handler;
|
|
44
|
+
let tmpDirs = [];
|
|
45
|
+
beforeAll(() => {
|
|
46
|
+
handler = captureToolHandler();
|
|
47
|
+
});
|
|
48
|
+
afterEach(async () => {
|
|
49
|
+
for (const dir of tmpDirs) {
|
|
50
|
+
await fs.rm(dir, { recursive: true, force: true });
|
|
51
|
+
}
|
|
52
|
+
tmpDirs = [];
|
|
53
|
+
});
|
|
54
|
+
it("should register the tool on the server", () => {
|
|
55
|
+
const registerToolMock = jest.fn();
|
|
56
|
+
const fakeServer = { registerTool: registerToolMock };
|
|
57
|
+
registerInitTestbotTool(fakeServer);
|
|
58
|
+
expect(registerToolMock).toHaveBeenCalledWith("skyramp_init_testbot", expect.objectContaining({
|
|
59
|
+
description: expect.any(String),
|
|
60
|
+
inputSchema: expect.any(Object),
|
|
61
|
+
}), expect.any(Function));
|
|
62
|
+
});
|
|
63
|
+
it("should return error for non-git directory", async () => {
|
|
64
|
+
const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "init-testbot-test-"));
|
|
65
|
+
tmpDirs.push(tmpDir);
|
|
66
|
+
const result = await handler({
|
|
67
|
+
repository_path: tmpDir,
|
|
68
|
+
agent_type: "cursor",
|
|
69
|
+
});
|
|
70
|
+
expect(result.isError).toBe(true);
|
|
71
|
+
expect(result.content[0].text).toContain("is not a Git repository");
|
|
72
|
+
});
|
|
73
|
+
it("should return error for git repo without GitHub remote", async () => {
|
|
74
|
+
const tmpDir = await createTempRepo({ addGithubRemote: false });
|
|
75
|
+
tmpDirs.push(tmpDir);
|
|
76
|
+
const result = await handler({
|
|
77
|
+
repository_path: tmpDir,
|
|
78
|
+
agent_type: "cursor",
|
|
79
|
+
});
|
|
80
|
+
expect(result.isError).toBe(true);
|
|
81
|
+
expect(result.content[0].text).toContain("No GitHub remote found");
|
|
82
|
+
});
|
|
83
|
+
it("should create workflow file on success", async () => {
|
|
84
|
+
const tmpDir = await createTempRepo();
|
|
85
|
+
tmpDirs.push(tmpDir);
|
|
86
|
+
const result = await handler({
|
|
87
|
+
repository_path: tmpDir,
|
|
88
|
+
agent_type: "cursor",
|
|
89
|
+
});
|
|
90
|
+
expect(result.isError).toBeUndefined();
|
|
91
|
+
expect(result.content[0].text).toContain("workflow created successfully");
|
|
92
|
+
const workflowPath = path.join(tmpDir, ".github", "workflows", "skyramp-test-bot.yml");
|
|
93
|
+
expect(fsSync.existsSync(workflowPath)).toBe(true);
|
|
94
|
+
});
|
|
95
|
+
it("should return already-exists message when workflow file exists", async () => {
|
|
96
|
+
const tmpDir = await createTempRepo();
|
|
97
|
+
tmpDirs.push(tmpDir);
|
|
98
|
+
// Create the workflow file first
|
|
99
|
+
const workflowDir = path.join(tmpDir, ".github", "workflows");
|
|
100
|
+
await fs.mkdir(workflowDir, { recursive: true });
|
|
101
|
+
await fs.writeFile(path.join(workflowDir, "skyramp-test-bot.yml"), "existing");
|
|
102
|
+
const result = await handler({
|
|
103
|
+
repository_path: tmpDir,
|
|
104
|
+
agent_type: "cursor",
|
|
105
|
+
});
|
|
106
|
+
expect(result.isError).toBeUndefined();
|
|
107
|
+
expect(result.content[0].text).toContain("already exists");
|
|
108
|
+
// Verify original file was not overwritten
|
|
109
|
+
const content = await fs.readFile(path.join(workflowDir, "skyramp-test-bot.yml"), "utf-8");
|
|
110
|
+
expect(content).toBe("existing");
|
|
111
|
+
});
|
|
112
|
+
it("should generate workflow with cursor API key by default", async () => {
|
|
113
|
+
const tmpDir = await createTempRepo();
|
|
114
|
+
tmpDirs.push(tmpDir);
|
|
115
|
+
await handler({
|
|
116
|
+
repository_path: tmpDir,
|
|
117
|
+
agent_type: "cursor",
|
|
118
|
+
});
|
|
119
|
+
const workflowContent = await fs.readFile(path.join(tmpDir, ".github", "workflows", "skyramp-test-bot.yml"), "utf-8");
|
|
120
|
+
expect(workflowContent).toContain("cursor_api_key");
|
|
121
|
+
expect(workflowContent).not.toContain("copilot_api_key");
|
|
122
|
+
});
|
|
123
|
+
it("should generate workflow with copilot API key when agent_type is copilot", async () => {
|
|
124
|
+
const tmpDir = await createTempRepo();
|
|
125
|
+
tmpDirs.push(tmpDir);
|
|
126
|
+
await handler({
|
|
127
|
+
repository_path: tmpDir,
|
|
128
|
+
agent_type: "copilot",
|
|
129
|
+
});
|
|
130
|
+
const workflowContent = await fs.readFile(path.join(tmpDir, ".github", "workflows", "skyramp-test-bot.yml"), "utf-8");
|
|
131
|
+
expect(workflowContent).toContain("copilot_api_key");
|
|
132
|
+
expect(workflowContent).not.toContain("cursor_api_key");
|
|
133
|
+
});
|
|
134
|
+
it("should generate workflow without branches key when target_branches is not provided", async () => {
|
|
135
|
+
const tmpDir = await createTempRepo();
|
|
136
|
+
tmpDirs.push(tmpDir);
|
|
137
|
+
await handler({
|
|
138
|
+
repository_path: tmpDir,
|
|
139
|
+
agent_type: "cursor",
|
|
140
|
+
});
|
|
141
|
+
const workflowContent = await fs.readFile(path.join(tmpDir, ".github", "workflows", "skyramp-test-bot.yml"), "utf-8");
|
|
142
|
+
expect(workflowContent).toContain("pull_request:");
|
|
143
|
+
expect(workflowContent).not.toContain("branches:");
|
|
144
|
+
});
|
|
145
|
+
it("should generate workflow with branches key when target_branches is provided", async () => {
|
|
146
|
+
const tmpDir = await createTempRepo();
|
|
147
|
+
tmpDirs.push(tmpDir);
|
|
148
|
+
await handler({
|
|
149
|
+
repository_path: tmpDir,
|
|
150
|
+
agent_type: "cursor",
|
|
151
|
+
target_branches: ["main", "develop"],
|
|
152
|
+
});
|
|
153
|
+
const workflowContent = await fs.readFile(path.join(tmpDir, ".github", "workflows", "skyramp-test-bot.yml"), "utf-8");
|
|
154
|
+
expect(workflowContent).toContain("branches:");
|
|
155
|
+
expect(workflowContent).toContain("- 'main'");
|
|
156
|
+
expect(workflowContent).toContain("- 'develop'");
|
|
157
|
+
});
|
|
158
|
+
it("should include setup instructions with correct secret names for cursor", async () => {
|
|
159
|
+
const tmpDir = await createTempRepo();
|
|
160
|
+
tmpDirs.push(tmpDir);
|
|
161
|
+
const result = await handler({
|
|
162
|
+
repository_path: tmpDir,
|
|
163
|
+
agent_type: "cursor",
|
|
164
|
+
});
|
|
165
|
+
expect(result.content[0].text).toContain("CURSOR_API_KEY");
|
|
166
|
+
expect(result.content[0].text).toContain("SKYRAMP_LICENSE");
|
|
167
|
+
expect(result.content[0].text).toContain("all pull requests");
|
|
168
|
+
});
|
|
169
|
+
it("should include setup instructions with correct secret names for copilot", async () => {
|
|
170
|
+
const tmpDir = await createTempRepo();
|
|
171
|
+
tmpDirs.push(tmpDir);
|
|
172
|
+
const result = await handler({
|
|
173
|
+
repository_path: tmpDir,
|
|
174
|
+
agent_type: "copilot",
|
|
175
|
+
target_branches: ["main"],
|
|
176
|
+
});
|
|
177
|
+
expect(result.content[0].text).toContain("COPILOT_API_KEY");
|
|
178
|
+
expect(result.content[0].text).toContain("pull requests targeting: main");
|
|
179
|
+
});
|
|
180
|
+
it("should generate valid YAML workflow structure", async () => {
|
|
181
|
+
const tmpDir = await createTempRepo();
|
|
182
|
+
tmpDirs.push(tmpDir);
|
|
183
|
+
await handler({
|
|
184
|
+
repository_path: tmpDir,
|
|
185
|
+
agent_type: "cursor",
|
|
186
|
+
});
|
|
187
|
+
const workflowContent = await fs.readFile(path.join(tmpDir, ".github", "workflows", "skyramp-test-bot.yml"), "utf-8");
|
|
188
|
+
expect(workflowContent).toContain("name: Skyramp Test Automation");
|
|
189
|
+
expect(workflowContent).toContain("runs-on: ubuntu-latest");
|
|
190
|
+
expect(workflowContent).toContain("uses: actions/checkout@v4");
|
|
191
|
+
expect(workflowContent).toContain("uses: skyramp/test-bot@v1");
|
|
192
|
+
expect(workflowContent).toContain("skyramp_license_file:");
|
|
193
|
+
});
|
|
194
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@skyramp/mcp",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.55",
|
|
4
4
|
"main": "build/index.js",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -45,8 +45,8 @@
|
|
|
45
45
|
},
|
|
46
46
|
"dependencies": {
|
|
47
47
|
"@modelcontextprotocol/sdk": "^1.24.3",
|
|
48
|
-
"@skyramp/skyramp": "1.3.6",
|
|
49
48
|
"@playwright/test": "^1.55.0",
|
|
49
|
+
"@skyramp/skyramp": "1.3.8",
|
|
50
50
|
"dockerode": "^4.0.6",
|
|
51
51
|
"fast-glob": "^3.3.3",
|
|
52
52
|
"simple-git": "^3.30.0",
|