@skyramp/mcp 0.3.0 → 0.3.1-ldw-beta

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 (112) hide show
  1. package/build/commands/commandLibrary.d.ts +1 -0
  2. package/build/commands/commandLibrary.js +19 -13
  3. package/build/commands/commandLibrary.test.d.ts +1 -0
  4. package/build/commands/commandLibrary.test.js +59 -0
  5. package/build/commands/localDevTestChangesCommand.d.ts +15 -0
  6. package/build/commands/localDevTestChangesCommand.js +201 -0
  7. package/build/index.js +80 -6
  8. package/build/playwright/traceRecordingPrompt.js +2 -0
  9. package/build/prompts/enhance-assertions/sharedAssertionRules.d.ts +1 -0
  10. package/build/prompts/enhance-assertions/sharedAssertionRules.js +17 -0
  11. package/build/prompts/initialize-workspace/initializeWorkspacePrompt.js +11 -9
  12. package/build/prompts/local-dev/local-dev-plan.d.ts +35 -0
  13. package/build/prompts/local-dev/local-dev-plan.js +466 -0
  14. package/build/prompts/local-dev/local-dev-prompts.d.ts +4 -0
  15. package/build/prompts/local-dev/local-dev-prompts.js +190 -0
  16. package/build/prompts/prompt-utils.d.ts +8 -0
  17. package/build/prompts/prompt-utils.js +33 -0
  18. package/build/prompts/test-recommendation/analysisOutputPrompt.js +21 -29
  19. package/build/prompts/test-recommendation/analysisOutputPrompt.test.js +0 -28
  20. package/build/prompts/test-recommendation/test-recommendation-prompt.js +41 -4
  21. package/build/prompts/test-recommendation/test-recommendation-prompt.test.js +16 -1
  22. package/build/prompts/testbot/testbot-prompts.d.ts +0 -5
  23. package/build/prompts/testbot/testbot-prompts.js +5 -34
  24. package/build/resources/testbotResource.js +2 -1
  25. package/build/services/AnalyticsService.d.ts +1 -1
  26. package/build/services/TestExecutionService.d.ts +2 -1
  27. package/build/services/TestExecutionService.js +8 -3
  28. package/build/services/TestExecutionService.test.js +83 -3
  29. package/build/services/TestGenerationService.d.ts +2 -2
  30. package/build/services/TestGenerationService.js +7 -21
  31. package/build/services/containerEnv.js +3 -1
  32. package/build/tool-phases.js +6 -0
  33. package/build/tools/code-refactor/enhanceAssertionsTool.js +47 -18
  34. package/build/tools/enrichTestWithMocksTool.d.ts +28 -0
  35. package/build/tools/enrichTestWithMocksTool.js +726 -0
  36. package/build/tools/enrichTestWithMocksTool.test.d.ts +1 -0
  37. package/build/tools/enrichTestWithMocksTool.test.js +266 -0
  38. package/build/tools/executeSkyrampTestTool.d.ts +11 -0
  39. package/build/tools/executeSkyrampTestTool.js +63 -22
  40. package/build/tools/executeSkyrampTestTool.test.d.ts +1 -0
  41. package/build/tools/executeSkyrampTestTool.test.js +36 -0
  42. package/build/tools/generate-tests/batchMockGenerationTool.d.ts +85 -0
  43. package/build/tools/generate-tests/batchMockGenerationTool.js +432 -0
  44. package/build/tools/generate-tests/generateBatchScenarioRestTool.js +6 -21
  45. package/build/tools/generate-tests/generateBatchScenarioRestTool.test.js +1 -0
  46. package/build/tools/generate-tests/generateContractRestTool.js +2 -2
  47. package/build/tools/generate-tests/generateMockRestTool.d.ts +129 -6
  48. package/build/tools/generate-tests/generateMockRestTool.js +234 -22
  49. package/build/tools/generate-tests/loadTestSchema.d.ts +1 -1
  50. package/build/tools/generateEnrichedIntegrationTestTool.d.ts +25 -0
  51. package/build/tools/generateEnrichedIntegrationTestTool.js +235 -0
  52. package/build/tools/generateEnrichedIntegrationTestTool.test.d.ts +1 -0
  53. package/build/tools/generateEnrichedIntegrationTestTool.test.js +44 -0
  54. package/build/tools/localDevWorkerComposeTool.d.ts +24 -0
  55. package/build/tools/localDevWorkerComposeTool.js +224 -0
  56. package/build/tools/localDevWorkerComposeTool.test.d.ts +1 -0
  57. package/build/tools/localDevWorkerComposeTool.test.js +129 -0
  58. package/build/tools/localDevWorkflowFixes.test.d.ts +1 -0
  59. package/build/tools/localDevWorkflowFixes.test.js +255 -0
  60. package/build/tools/one-click/oneClickTool.d.ts +13 -0
  61. package/build/tools/one-click/oneClickTool.js +195 -24
  62. package/build/tools/one-click/oneClickTool.test.d.ts +1 -0
  63. package/build/tools/one-click/oneClickTool.test.js +172 -0
  64. package/build/tools/preflightMockCheckTool.d.ts +2 -0
  65. package/build/tools/preflightMockCheckTool.js +96 -0
  66. package/build/tools/queryProxyMocksTool.d.ts +70 -0
  67. package/build/tools/queryProxyMocksTool.js +522 -0
  68. package/build/tools/queryProxyMocksTool.test.d.ts +1 -0
  69. package/build/tools/queryProxyMocksTool.test.js +164 -0
  70. package/build/tools/test-management/analyzeChangesTool.d.ts +2 -1
  71. package/build/tools/test-management/analyzeChangesTool.js +61 -38
  72. package/build/tools/trace/startTraceCollectionTool.js +3 -3
  73. package/build/types/OneClickCommands.d.ts +1 -1
  74. package/build/types/RepositoryAnalysis.d.ts +117 -0
  75. package/build/types/RepositoryAnalysis.js +16 -2
  76. package/build/types/TestExecution.d.ts +1 -0
  77. package/build/types/TestTypes.d.ts +25 -7
  78. package/build/types/TestTypes.js +19 -5
  79. package/build/utils/analyze-openapi.js +18 -1
  80. package/build/utils/analyze-openapi.test.d.ts +1 -0
  81. package/build/utils/analyze-openapi.test.js +19 -0
  82. package/build/utils/branchDiff.d.ts +17 -1
  83. package/build/utils/branchDiff.js +99 -14
  84. package/build/utils/branchDiff.test.d.ts +1 -0
  85. package/build/utils/branchDiff.test.js +109 -0
  86. package/build/utils/featureFlags.d.ts +16 -0
  87. package/build/utils/featureFlags.js +20 -0
  88. package/build/utils/featureFlags.test.js +57 -2
  89. package/build/utils/grpcMockValidation.d.ts +1 -0
  90. package/build/utils/grpcMockValidation.js +49 -0
  91. package/build/utils/grpcMockValidation.test.d.ts +1 -0
  92. package/build/utils/grpcMockValidation.test.js +41 -0
  93. package/build/utils/httpMethodValidation.d.ts +4 -0
  94. package/build/utils/httpMethodValidation.js +15 -0
  95. package/build/utils/mockCompatibility.d.ts +49 -0
  96. package/build/utils/mockCompatibility.js +82 -0
  97. package/build/utils/mockCompatibility.test.d.ts +1 -0
  98. package/build/utils/mockCompatibility.test.js +79 -0
  99. package/build/utils/progress.js +10 -5
  100. package/build/utils/versions.d.ts +1 -0
  101. package/build/utils/versions.js +2 -0
  102. package/build/utils/workspaceAuth.d.ts +9 -0
  103. package/build/utils/workspaceAuth.js +51 -1
  104. package/build/workspace/workspace.d.ts +28 -14
  105. package/build/workspace/workspace.js +3 -1
  106. package/node_modules/playwright/lib/mcp/browser/tools/screenshot.js +1 -1
  107. package/node_modules/playwright/lib/mcp/skyramp/resultCode.js +45 -0
  108. package/node_modules/playwright/lib/mcp/skyramp/traceRecordingBackend.js +6 -22
  109. package/node_modules/playwright/lib/mcp/test/resultCode.test.js +48 -0
  110. package/node_modules/playwright/node_modules/playwright-core/package.json +1 -1
  111. package/node_modules/playwright/package.json +1 -1
  112. package/package.json +2 -2
@@ -9,3 +9,4 @@ export declare function getCommandIds(): OneClickCommandId[];
9
9
  export declare function lookupCommand(id: string): OneClickCommandDef;
10
10
  export { TEST_GIVEN_ENDPOINT_COMPREHENSIVELY_COMMAND } from "./testThisEndpointCommand.js";
11
11
  export { FULLREPO_RECOMMEND_GENERATE_EXECUTE_TOPN_TESTS_COMMAND } from "./recommendTestsAndExecuteCommand.js";
12
+ export { LOCAL_DEV_TEST_CHANGES_COMMAND } from "./localDevTestChangesCommand.js";
@@ -1,23 +1,28 @@
1
+ import { isLocalDevEnabled, isOneClickEnabled } from "../utils/featureFlags.js";
1
2
  import { TEST_GIVEN_ENDPOINT_COMPREHENSIVELY_COMMAND } from "./testThisEndpointCommand.js";
2
3
  import { FULLREPO_RECOMMEND_GENERATE_EXECUTE_TOPN_TESTS_COMMAND } from "./recommendTestsAndExecuteCommand.js";
3
- /**
4
- * To add a new predefined workflow:
5
- * 1. Add a new id to OneClickCommandId in types/OneClickCommands.ts
6
- * 2. Create a new file in commands/ (e.g. myWorkflowCommand.ts) with steps and export the command
7
- * 3. Import the command here and add it to COMMAND_LIBRARY
8
- * 4. The generic buildWorkflowFromCommand() will render detailed instructions from your steps
9
- * (no schema changes needed in oneClickTool.ts — the workflow param description is generated dynamically)
10
- */
11
- /** All predefined one-click commands */
12
- const COMMAND_LIBRARY = {
4
+ import { LOCAL_DEV_TEST_CHANGES_COMMAND } from "./localDevTestChangesCommand.js";
5
+ /** Base predefined one-click commands */
6
+ const BASE_COMMAND_LIBRARY = {
13
7
  test_given_endpoint_comprehensively: TEST_GIVEN_ENDPOINT_COMPREHENSIVELY_COMMAND,
14
8
  full_repo_scan_recommend_generate_and_execute_top_n_tests: FULLREPO_RECOMMEND_GENERATE_EXECUTE_TOPN_TESTS_COMMAND,
15
9
  };
10
+ /** All enabled predefined one-click commands */
11
+ function getCommandLibrary() {
12
+ const commands = {};
13
+ if (isOneClickEnabled()) {
14
+ Object.assign(commands, BASE_COMMAND_LIBRARY);
15
+ }
16
+ if (isLocalDevEnabled()) {
17
+ commands.local_dev_test_changes = LOCAL_DEV_TEST_CHANGES_COMMAND;
18
+ }
19
+ return commands;
20
+ }
16
21
  export function getAllCommands() {
17
- return Object.values(COMMAND_LIBRARY);
22
+ return Object.values(getCommandLibrary()).filter((cmd) => Boolean(cmd));
18
23
  }
19
24
  export function getCommandIds() {
20
- return Object.keys(COMMAND_LIBRARY);
25
+ return getAllCommands().map((cmd) => cmd.id);
21
26
  }
22
27
  /**
23
28
  * Look up a command by its string ID. Accepts any string so the tool handler
@@ -25,7 +30,7 @@ export function getCommandIds() {
25
30
  * the ID is not found.
26
31
  */
27
32
  export function lookupCommand(id) {
28
- const cmd = COMMAND_LIBRARY[id];
33
+ const cmd = getCommandLibrary()[id];
29
34
  if (!cmd) {
30
35
  const available = getCommandIds().join(", ");
31
36
  throw new Error(`Unknown workflow: "${id}". Valid workflow IDs: ${available}.`);
@@ -35,3 +40,4 @@ export function lookupCommand(id) {
35
40
  // Re-export commands for consumers that import from commandLibrary
36
41
  export { TEST_GIVEN_ENDPOINT_COMPREHENSIVELY_COMMAND } from "./testThisEndpointCommand.js";
37
42
  export { FULLREPO_RECOMMEND_GENERATE_EXECUTE_TOPN_TESTS_COMMAND } from "./recommendTestsAndExecuteCommand.js";
43
+ export { LOCAL_DEV_TEST_CHANGES_COMMAND } from "./localDevTestChangesCommand.js";
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,59 @@
1
+ import { afterEach, describe, expect, it } from "@jest/globals";
2
+ import { getCommandIds, lookupCommand } from "./commandLibrary.js";
3
+ const originalOneClick = process.env.SKYRAMP_FEATURE_ONE_CLICK;
4
+ const originalLocalDev = process.env.SKYRAMP_FEATURE_LOCAL_DEV;
5
+ function setFlags(oneClick, localDev) {
6
+ if (oneClick === undefined) {
7
+ delete process.env.SKYRAMP_FEATURE_ONE_CLICK;
8
+ }
9
+ else {
10
+ process.env.SKYRAMP_FEATURE_ONE_CLICK = oneClick;
11
+ }
12
+ if (localDev === undefined) {
13
+ delete process.env.SKYRAMP_FEATURE_LOCAL_DEV;
14
+ }
15
+ else {
16
+ process.env.SKYRAMP_FEATURE_LOCAL_DEV = localDev;
17
+ }
18
+ }
19
+ afterEach(() => {
20
+ if (originalOneClick === undefined) {
21
+ delete process.env.SKYRAMP_FEATURE_ONE_CLICK;
22
+ }
23
+ else {
24
+ process.env.SKYRAMP_FEATURE_ONE_CLICK = originalOneClick;
25
+ }
26
+ if (originalLocalDev === undefined) {
27
+ delete process.env.SKYRAMP_FEATURE_LOCAL_DEV;
28
+ }
29
+ else {
30
+ process.env.SKYRAMP_FEATURE_LOCAL_DEV = originalLocalDev;
31
+ }
32
+ });
33
+ describe("command library feature gates", () => {
34
+ it("exposes no one-click workflows when both one-click and local-dev are disabled", () => {
35
+ setFlags(undefined, undefined);
36
+ expect(getCommandIds()).toEqual([]);
37
+ });
38
+ it("exposes only base one-click workflows when one-click is enabled", () => {
39
+ setFlags("1", undefined);
40
+ expect(getCommandIds().sort()).toEqual([
41
+ "full_repo_scan_recommend_generate_and_execute_top_n_tests",
42
+ "test_given_endpoint_comprehensively",
43
+ ]);
44
+ });
45
+ it("exposes only local-dev when local-dev is enabled without general one-click", () => {
46
+ setFlags(undefined, "1");
47
+ expect(getCommandIds()).toEqual(["local_dev_test_changes"]);
48
+ expect(lookupCommand("local_dev_test_changes").id).toBe("local_dev_test_changes");
49
+ expect(() => lookupCommand("test_given_endpoint_comprehensively")).toThrow(/Unknown workflow/);
50
+ });
51
+ it("exposes base and local-dev workflows when both flags are enabled", () => {
52
+ setFlags("1", "1");
53
+ expect(getCommandIds().sort()).toEqual([
54
+ "full_repo_scan_recommend_generate_and_execute_top_n_tests",
55
+ "local_dev_test_changes",
56
+ "test_given_endpoint_comprehensively",
57
+ ]);
58
+ });
59
+ });
@@ -0,0 +1,15 @@
1
+ import type { OneClickCommandDef } from "../types/OneClickCommands.js";
2
+ /**
3
+ * Predefined One-Click Command: Local Dev — Test Changes
4
+ *
5
+ * This is the BOOTSTRAPPER / ENTRY POINT for local-dev testing.
6
+ * It handles first-time setup (skills + workspace), then delegates
7
+ * the actual test workflow to the `skyramp_local_dev` MCP prompt.
8
+ *
9
+ * Flow:
10
+ * Step 0: Install IDE skill into repo for the calling agent
11
+ * Step 1: Ensure .skyramp/workspace.yml exists
12
+ * Step 2: Invoke skyramp_local_dev prompt (which runs the full workflow)
13
+ */
14
+ export declare const LOCAL_DEV_SKILL_CONTENT = "---\nname: local-dev\ndescription: Run the Skyramp MCP local-dev workflow to test local code changes against a base branch. Use when the user asks to test changes, mock downstream services, validate a feature branch, run local-dev, or verify a PR before merge.\nuser_invocable: true\n---\n\n# local-dev\n\nUse the Skyramp MCP local-dev workflow as the source of truth. Do not recreate the workflow steps from this skill.\n\n## Usage\n\n`/local-dev` \u2014 diff against main (default)\n`/local-dev main --mock stripe,billing --real postgres,redis`\n\n## Invocation\n\nCall the `skyramp_local_dev` MCP prompt with the user's intent preserved:\n- `baseCommit`: branch/SHA to diff against (default: main)\n- `repositoryPath`: absolute path to repo\n- `servicesToMock`: comma-separated services the user explicitly wants mocked\n- `realServices`: comma-separated services that should receive real traffic\n- `mockProtocols`: comma-separated protocols the user explicitly wants mocked (for example, `rest`)\n\nBy default, mock only true third-party downstream services. Keep the service under test, infrastructure, and local first-party services real unless the user explicitly asks to mock them.\n";
15
+ export declare const LOCAL_DEV_TEST_CHANGES_COMMAND: OneClickCommandDef;
@@ -0,0 +1,201 @@
1
+ import { localDevPlan, } from "../prompts/local-dev/local-dev-plan.js";
2
+ /**
3
+ * Predefined One-Click Command: Local Dev — Test Changes
4
+ *
5
+ * This is the BOOTSTRAPPER / ENTRY POINT for local-dev testing.
6
+ * It handles first-time setup (skills + workspace), then delegates
7
+ * the actual test workflow to the `skyramp_local_dev` MCP prompt.
8
+ *
9
+ * Flow:
10
+ * Step 0: Install IDE skill into repo for the calling agent
11
+ * Step 1: Ensure .skyramp/workspace.yml exists
12
+ * Step 2: Invoke skyramp_local_dev prompt (which runs the full workflow)
13
+ */
14
+ // ── Skill content (single source of truth) ──────────────────────────────────
15
+ // These get written to user repos by Step 0. Edit here, not in separate files.
16
+ export const LOCAL_DEV_SKILL_CONTENT = `---
17
+ name: local-dev
18
+ description: Run the Skyramp MCP local-dev workflow to test local code changes against a base branch. Use when the user asks to test changes, mock downstream services, validate a feature branch, run local-dev, or verify a PR before merge.
19
+ user_invocable: true
20
+ ---
21
+
22
+ # local-dev
23
+
24
+ Use the Skyramp MCP local-dev workflow as the source of truth. Do not recreate the workflow steps from this skill.
25
+
26
+ ## Usage
27
+
28
+ \`/local-dev\` — diff against main (default)
29
+ \`/local-dev main --mock stripe,billing --real postgres,redis\`
30
+
31
+ ## Invocation
32
+
33
+ Call the \`skyramp_local_dev\` MCP prompt with the user's intent preserved:
34
+ - \`baseCommit\`: branch/SHA to diff against (default: main)
35
+ - \`repositoryPath\`: absolute path to repo
36
+ - \`servicesToMock\`: comma-separated services the user explicitly wants mocked
37
+ - \`realServices\`: comma-separated services that should receive real traffic
38
+ - \`mockProtocols\`: comma-separated protocols the user explicitly wants mocked (for example, \`rest\`)
39
+
40
+ By default, mock only true third-party downstream services. Keep the service under test, infrastructure, and local first-party services real unless the user explicitly asks to mock them.
41
+ `;
42
+ const localDevTestChangesSteps = [
43
+ {
44
+ stepIndex: 0,
45
+ title: "Install local-dev skill into the repository",
46
+ description: `The \`skyramp_one_click_tool\` installs the local-dev skill file for the calling agent automatically when this workflow is invoked. Verify the matching file exists before continuing.
47
+
48
+ File to check/create only if the tool did not already install it:
49
+ - Cursor: \`.cursor/skills/local-dev/SKILL.md\`
50
+ - Claude Code: \`.claude/skills/local-dev/SKILL.md\`
51
+ Agents without repo-local skill support skip skill creation. Also ensure the MCP config has the required env vars set (SKYRAMP_FEATURE_LOCAL_DEV=1).`,
52
+ conditionalGuidance: `Create the skill file for the calling agent ONLY if it doesn't already exist — never overwrite.
53
+
54
+ **Skill path for the calling agent only:**
55
+ - Cursor: \`.cursor/skills/local-dev/SKILL.md\`
56
+ - Claude Code: \`.claude/skills/local-dev/SKILL.md\`
57
+ Agents without repo-local skill support should skip skill creation. Write this exact content to the matching Cursor or Claude Code file:
58
+ ${LOCAL_DEV_SKILL_CONTENT}
59
+
60
+ **MCP config env vars** — also ensure the following env vars are set in the Skyramp MCP server config:
61
+ - For Cursor: \`~/.cursor/mcp.json\` → add to the skyramp server's \`env\` block:
62
+ \`"SKYRAMP_FEATURE_LOCAL_DEV": "1"\`
63
+ - For Claude Code: \`~/.claude/settings.json\` → add to the skyramp server's \`env\` block:
64
+ \`"SKYRAMP_FEATURE_LOCAL_DEV": "1"\`
65
+ - For VS Code: add to the Skyramp MCP server's VS Code MCP config env block:
66
+ \`"SKYRAMP_FEATURE_LOCAL_DEV": "1"\`
67
+
68
+ After updating the config, the user must restart the IDE or reload MCP servers for the change to take effect.
69
+
70
+ After verifying or creating files, inform the user what was installed.`,
71
+ },
72
+ {
73
+ stepIndex: 1,
74
+ title: "Ensure Skyramp workspace is initialized",
75
+ description: "Check whether .skyramp/workspace.yml exists at repositoryPath. If it exists, read it to confirm it has services and dependencies configured. If it does not exist and .git exists, call skyramp_init_scan with workspacePath to discover services, then call skyramp_init_workspace with the scan results to create the workspace file.",
76
+ toolCall: {
77
+ toolName: "skyramp_init_scan",
78
+ description: "Scan repository for services if workspace not initialized",
79
+ inputs: {
80
+ workspacePath: { source: "user", paramKey: "repositoryPath" },
81
+ },
82
+ },
83
+ conditionalGuidance: `Only call skyramp_init_scan + skyramp_init_workspace if .skyramp/workspace.yml does NOT exist. If it already exists, skip this step entirely. The workspace.yml is required by the skyramp_local_dev prompt — it reads service URLs, dependencies, runtime config, and network settings from it.
84
+
85
+ Turn efficiency — after skyramp_init_scan returns its instructions, follow them to build the services array and call skyramp_init_workspace. Do NOT manually explore the codebase, run git diff, or spawn sub-agents to read source files before calling skyramp_init_workspace — the analyze_changes tool in the next step handles diff analysis automatically.`,
86
+ },
87
+ {
88
+ stepIndex: 2,
89
+ title: "Invoke the skyramp_local_dev prompt",
90
+ description: "Call the `skyramp_local_dev` MCP prompt to run the full local-dev testing workflow.\n\nPass these parameters:\n- `baseCommit`: the one-click `baseBranch` value (default `main`)\n- `repositoryPath`: absolute path to the repo\n- `servicesToMock`: comma-separated list only when the user explicitly named services to mock\n- `realServices`: comma-separated list only when the user explicitly named services that should receive real traffic\n- `mockProtocols`: comma-separated list only when the user explicitly constrained mocked protocols (for example, `rest`)\n- `sandboxWorkerUrl`: the one-click `proxyUrl` value, only when provided — a Skyramp worker proxy URL (host:port or ingress route) to query for real recorded traffic\n- `proxyKubeconfig` / `proxyNamespace`: only when provided — reach the worker proxy in-cluster via kubectl port-forward instead of a URL\n\nWhen any proxy access is provided, the workflow calls `skyramp_query_proxy_mocks` during dependency discovery to derive mock request/response shapes from real recorded traffic instead of inferring them.\n\nThe one-click tool exposes `baseBranch` because that matches other workflow inputs; the `skyramp_local_dev` prompt argument is named `baseCommit`. Always map `baseBranch` → `baseCommit` when invoking the prompt.\n\nThe workflow generates the highest-value tests regardless of type. For integration scenarios with selected downstream mocks: skyramp_mock_generation → skyramp_integration_test_generation → skyramp_enrich_test_with_mocks. For provider contract scenarios: skyramp_contract_test_generation only; no mocks needed; do not enrich contract tests. Integration enrich is MANDATORY when the selected scenario has downstream mocks — it embeds MOCK_SERVICES into the test file.\n\nFollow the returned prompt instructions EXACTLY — do not skip steps or freelance.",
91
+ conditionalGuidance: renderFallbackGuidance(),
92
+ },
93
+ ];
94
+ /**
95
+ * Render the fallback guidance from the canonical localDevPlan.
96
+ * This ensures the one-click command fallback stays in sync with the plan
97
+ * without maintaining a separate handwritten copy.
98
+ */
99
+ function renderFallbackGuidance() {
100
+ const preamble = `The skyramp_local_dev prompt returns a detailed multi-step workflow. Follow it completely.
101
+
102
+ If the prompt is NOT available as a callable prompt in this IDE, fall back to calling the individual tools in sequence. Replace <REPOSITORY_PATH> with the actual repository path and <BASE_BRANCH> with the user's one-click \`baseBranch\` value (default "main"). When the plan says \`baseCommit\`, use that same baseBranch value.
103
+
104
+ Fallback service routing inputs from the one-click invocation:
105
+ <SERVICE_ROUTING>
106
+ <MOCK_SERVICES><servicesToMock from one-click params, if provided; use all when the user requested mock all></MOCK_SERVICES>
107
+ <REAL_SERVICES><realServices from one-click params, if provided></REAL_SERVICES>
108
+ <MOCK_PROTOCOLS><mockProtocols from one-click params, if provided></MOCK_PROTOCOLS>
109
+ </SERVICE_ROUTING>
110
+
111
+ Three rules apply in fallback mode at all times:
112
+ 1. **No hand-written tests or mocks**: do NOT use Write, Edit, or any file-writing tool to create test or mock files manually. ALL test and mock files MUST be produced by Skyramp MCP generation tools (\`skyramp_batch_scenario_test_generation\`, \`skyramp_integration_test_generation\`, \`skyramp_generate_enriched_integration_test\`, \`skyramp_contract_test_generation\`, \`skyramp_mock_generation\`, \`skyramp_batch_mock_generation\`). If a generation tool fails, fix the inputs and retry — do not fall back to writing tests by hand.
113
+ 2. **Mock scope**: if the user said "mock all downstream services", "mock all", "mock everything", or "mock all except X", treat all discovered downstream dependencies as selected for mocking (same as passing \`servicesToMock: "all"\`). Do not silently default to the third-party-only policy when the user expressed broad mock intent.
114
+ 3. **No pytest fallback**: if \`skyramp_execute_test\` becomes unavailable at any point (tool not found, MCP disconnected, context compaction), stop immediately and ask the user to reconnect the Skyramp MCP server. Do not fall back to \`pytest\`, \`python3\`, or any direct runtime — doing so bypasses mock deployment and produces results that cannot be trusted as Skyramp-validated.
115
+
116
+ ---
117
+
118
+ `;
119
+ const ctx = {
120
+ repositoryPath: "<REPOSITORY_PATH>",
121
+ baseCommit: "<BASE_BRANCH from one-click baseBranch, default main>",
122
+ maxRecommendations: 8,
123
+ maxGenerate: 5,
124
+ schemaSourceGuidance: "Use any OpenAPI spec found in the repository as the authoritative schema source. If no spec is available, infer request/response shapes from the implementation code.",
125
+ serviceRoutingBlock: `<SERVICE_ROUTING>
126
+ <MOCK_SERVICES><servicesToMock from one-click params, if provided; use all when the user requested mock all></MOCK_SERVICES>
127
+ <REAL_SERVICES><realServices from one-click params, if provided></REAL_SERVICES>
128
+ <MOCK_PROTOCOLS><mockProtocols from one-click params, if provided></MOCK_PROTOCOLS>
129
+ </SERVICE_ROUTING>`,
130
+ serviceContext: `<SERVICE_CONTEXT>
131
+ Read <REPOSITORY_PATH>/.skyramp/workspace.yml before classifying dependencies. Use its services, dependencies, runtime, ports, and docker network values as the local service inventory.
132
+ </SERVICE_CONTEXT>`,
133
+ };
134
+ const planBody = localDevPlan.render(ctx);
135
+ const principles = `
136
+ ---
137
+
138
+ ## Key Principles
139
+ - ALL output paths absolute inside repo. NEVER /tmp.
140
+ - For **integration tests**: call \`skyramp_enrich_test_with_mocks\` after generating each test that uses selected downstream mocks. Each integration test with mocked dependencies MUST have MOCK_SERVICES embedded for third-party mocks or user-requested mocks.
141
+ - For **provider contract tests**: no mocks needed — test hits the real endpoint directly. Do NOT call \`skyramp_enrich_test_with_mocks\` for contract tests.
142
+ - Prioritize tests that catch real bugs and edge cases; test type is secondary to coverage value.
143
+ - Present results directly to the user (no skyramp_submit_report).
144
+ - **Prefer batch operations**: Use \`skyramp_batch_mock_generation\` over individual mock calls.
145
+ - **Prefer combined tools**: Use \`skyramp_generate_enriched_integration_test\` over separate generate+enrich.
146
+ - **Pre-flight first**: Call \`skyramp_preflight_mock_check\` before mock generation to catch protocol conflicts.
147
+ - **Compact assertions**: Use \`skyramp_enhance_assertions\` with \`autoApply: true\` for shorter instructions.`;
148
+ return preamble + planBody + principles;
149
+ }
150
+ export const LOCAL_DEV_TEST_CHANGES_COMMAND = {
151
+ id: "local_dev_test_changes",
152
+ name: "Local Dev — Test Changes",
153
+ description: "Bootstrap and run the local-dev testing workflow: install the active agent's IDE skill into the repo, initialize workspace, then invoke skyramp_local_dev to diff local changes, generate mocks and exhaustive tests, execute, and report. First run sets up the repo for future `/local-dev` usage.",
154
+ intent: {
155
+ contextIndicators: [
156
+ "Use when the user wants to test their LOCAL code changes (committed or uncommitted) against a base branch",
157
+ "Use when the user asks to set up, initialize, or bootstrap Skyramp local-dev testing in their repo",
158
+ "Use when the user mentions mocking downstream services for local testing",
159
+ "Use when the user asks to validate a feature branch, generate tests for their diff, or run a local-dev workflow",
160
+ "First invocation installs the active agent's skill so future runs can use /local-dev directly",
161
+ "Do NOT use for testing a deployed/remote service — this is for LOCAL changes only",
162
+ "Do NOT use for a single test type request like 'generate a smoke test' — use the specific generation tool",
163
+ ],
164
+ purpose: "Bootstrap local-dev testing: install the active agent's IDE skill into repo, initialize workspace, then delegate to skyramp_local_dev prompt for the full test workflow (diff → mock → generate → execute → report).",
165
+ workflowSummary: "Install active agent skill → Init workspace → Invoke skyramp_local_dev prompt (which runs: analyze diff → recommend scenarios → generate mocks + tests → verify → deploy SUT + mocks → execute → report)",
166
+ examples: {
167
+ use: [
168
+ "test my local changes against main",
169
+ "setup skyramp local dev",
170
+ "initialize local dev testing",
171
+ "install skyramp skills",
172
+ "generate tests for my feature branch",
173
+ "run local-dev workflow",
174
+ "mock downstream services and test my diff",
175
+ "validate my changes before pushing",
176
+ "bootstrap skyramp testing in this repo",
177
+ ],
178
+ doNotUse: [
179
+ "test the products endpoint comprehensively → use test_given_endpoint_comprehensively instead",
180
+ "generate a smoke test for /api/users → use skyramp_smoke_test_generation directly",
181
+ "scan the full repo and run tests → use full_repo_scan_recommend_generate_and_execute_top_n_tests",
182
+ "run my existing tests → use skyramp_execute_test directly",
183
+ ],
184
+ },
185
+ },
186
+ steps: localDevTestChangesSteps,
187
+ inputParams: {
188
+ required: ["repositoryPath"],
189
+ optional: [
190
+ "baseBranch",
191
+ "servicesToMock",
192
+ "realServices",
193
+ "mockProtocols",
194
+ "endpointURL",
195
+ "token",
196
+ "proxyUrl",
197
+ "proxyKubeconfig",
198
+ "proxyNamespace",
199
+ ],
200
+ },
201
+ };
package/build/index.js CHANGED
@@ -24,24 +24,47 @@ import { registerCodeReuseTool } from "./tools/code-refactor/codeReuseTool.js";
24
24
  import { registerEnhanceAssertionsTool } from "./tools/code-refactor/enhanceAssertionsTool.js";
25
25
  import { registerBatchScenarioTestTool } from "./tools/generate-tests/generateBatchScenarioRestTool.js";
26
26
  import { registerMockTool } from "./tools/generate-tests/generateMockRestTool.js";
27
+ import { registerBatchMockTool } from "./tools/generate-tests/batchMockGenerationTool.js";
27
28
  import { registerAnalyzeChangesTool, registerAnalyzeTestHealthTool, registerActionsTool, } from "./tools/test-management/index.js";
28
29
  import { registerTestbotPrompt } from "./prompts/testbot/testbot-prompts.js";
29
30
  import { registerSutSetupResource } from "./resources/sutSetupResource.js";
30
31
  import { registerTestbotResource } from "./resources/testbotResource.js";
32
+ import { registerLocalDevPrompt } from "./prompts/local-dev/local-dev-prompts.js";
31
33
  import { registerSubmitReportTool } from "./tools/submitReportTool.js";
32
34
  import { registerInitializeWorkspaceTool } from "./tools/workspace/initializeWorkspaceTool.js";
33
35
  import { registerInitScanWorkspaceTool } from "./tools/workspace/initScanWorkspaceTool.js";
34
36
  import { registerOneClickTool } from "./tools/one-click/oneClickTool.js";
37
+ import { registerEnrichTestWithMocksTool } from "./tools/enrichTestWithMocksTool.js";
38
+ import { registerPreflightMockCheckTool } from "./tools/preflightMockCheckTool.js";
39
+ import { registerGenerateEnrichedIntegrationTestTool } from "./tools/generateEnrichedIntegrationTestTool.js";
40
+ import { registerLocalDevWorkerComposeTool } from "./tools/localDevWorkerComposeTool.js";
41
+ import { registerQueryProxyMocksTool } from "./tools/queryProxyMocksTool.js";
35
42
  import { registerAnalysisResources } from "./resources/analysisResources.js";
36
43
  import { registerProgressResource } from "./resources/progressResource.js";
37
44
  import { AnalyticsService } from "./services/AnalyticsService.js";
38
45
  import { registerInitTriggerOnMCPInitialized } from "./utils/initAgent.js";
39
- import { isTestbotEnabled } from "./utils/featureFlags.js";
46
+ import { isTestbotEnabled, isLocalDevEnabled } from "./utils/featureFlags.js";
40
47
  import { registerPlaywrightTools, registerTraceRecordingPrompt, getPlaywrightTraceService, } from "./playwright/index.js";
48
+ // One-click routing instructions — injected into MCP server instructions so agents
49
+ // know to delegate to the one-click tool rather than self-orchestrating.
50
+ // Suppressed in testbot mode where the testbot prompt drives orchestration.
41
51
  const oneClickEnabled = process.env.SKYRAMP_FEATURE_ONE_CLICK === "1";
42
- const oneClickInstructions = oneClickEnabled
43
- ? `\n- When the user asks to comprehensively, thoroughly, or deeply test a specific endpoint: MUST call \`skyramp_one_click_tool\` with workflow \`test_given_endpoint_comprehensively\` first. Do NOT self-orchestrate the steps manually.\n- When the user asks to scan the full repo, recommend, generate, and execute top N tests: MUST call \`skyramp_one_click_tool\` with workflow \`full_repo_scan_recommend_generate_and_execute_top_n_tests\`.`
44
- : "";
52
+ const localDevEnabled = isLocalDevEnabled();
53
+ const localDevInstructions = `\n- When the user asks to test local changes, validate a feature branch, mock services and test, or run local-dev: MUST call \`skyramp_one_click_tool\` with workflow \`local_dev_test_changes\`, \`callingAgent\` set to the current MCP client (\`cursor\` or \`claude\` for skill installation; VS Code does not support repo-local Agent Skills), \`repositoryPath\`, and \`baseBranch\` (default "main"). Do NOT self-orchestrate the one-click tool handles skill installation, workspace init, and prompt invocation.
54
+ - **Proxy / K8s worker**: When the user mentions a Skyramp proxy, K8s worker, kubeconfig, namespace, or provides proxy setup instructions (helm deploy worker, kubectl port-forward, proxy intercepts, seed traffic), do NOT execute those steps manually with bash commands. Instead, pass \`proxyKubeconfig\` and \`proxyNamespace\` (or \`proxyUrl\`) to \`skyramp_one_click_tool\`. The MCP tools handle proxy access internally — \`skyramp_query_proxy_mocks\` runs kubectl port-forward and queries captured traffic automatically. Manual kubectl, helm, or curl commands for proxy setup are unnecessary and waste agent turns.`;
55
+ const fullOneClickInstructions = `\n- When the user asks to comprehensively, thoroughly, or deeply test a specific endpoint: MUST call \`skyramp_one_click_tool\` with workflow \`test_given_endpoint_comprehensively\` first. Do NOT self-orchestrate the steps manually.\n- When the user asks to scan the full repo, recommend, generate, and execute top N tests: MUST call \`skyramp_one_click_tool\` with workflow \`full_repo_scan_recommend_generate_and_execute_top_n_tests\`.${localDevEnabled ? localDevInstructions : ""}`;
56
+ // Testbot suppresses one-click workflows (it has its own orchestration via testbot prompt),
57
+ // but local-dev routing is always active when the flag is set — they serve different use cases.
58
+ let oneClickInstructions = "";
59
+ if (isTestbotEnabled()) {
60
+ oneClickInstructions = localDevEnabled ? localDevInstructions : "";
61
+ }
62
+ else if (oneClickEnabled) {
63
+ oneClickInstructions = fullOneClickInstructions;
64
+ }
65
+ else if (localDevEnabled) {
66
+ oneClickInstructions = localDevInstructions;
67
+ }
45
68
  const server = new McpServer({
46
69
  name: "Skyramp MCP Server",
47
70
  version: "1.0.0",
@@ -121,6 +144,10 @@ if (isTestbotEnabled()) {
121
144
  registerSutSetupResource(server);
122
145
  logger.info("Testbot SUT setup resource enabled via SKYRAMP_FEATURE_TESTBOT");
123
146
  }
147
+ if (isLocalDevEnabled()) {
148
+ prompts.push(registerLocalDevPrompt);
149
+ logger.info("Local-dev prompt enabled via SKYRAMP_FEATURE_LOCAL_DEV");
150
+ }
124
151
  prompts.forEach((registerPrompt) => registerPrompt(server));
125
152
  logger.info("All prompts registered successfully");
126
153
  // Register test generation tools
@@ -134,6 +161,7 @@ const testGenerationTools = [
134
161
  registerUITestTool,
135
162
  registerBatchScenarioTestTool,
136
163
  registerMockTool,
164
+ registerBatchMockTool,
137
165
  ];
138
166
  testGenerationTools.forEach((registerTool) => registerTool(server));
139
167
  // Register modularization and code quality tools
@@ -155,9 +183,14 @@ registerActionsTool(server);
155
183
  registerInitScanWorkspaceTool(server);
156
184
  registerInitializeWorkspaceTool(server);
157
185
  // Register one-click orchestrated workflows
158
- if (oneClickEnabled) {
186
+ if (oneClickEnabled || localDevEnabled) {
159
187
  registerOneClickTool(server);
160
- logger.info("One-click tools enabled via SKYRAMP_FEATURE_ONE_CLICK");
188
+ logger.info("One-click tools enabled", {
189
+ enabledBy: {
190
+ SKYRAMP_FEATURE_ONE_CLICK: oneClickEnabled,
191
+ SKYRAMP_FEATURE_LOCAL_DEV: localDevEnabled,
192
+ },
193
+ });
161
194
  }
162
195
  // Register other Skyramp tools
163
196
  const infrastructureTools = [
@@ -167,6 +200,10 @@ const infrastructureTools = [
167
200
  registerTraceTool,
168
201
  registerTraceStopTool,
169
202
  ];
203
+ if (isLocalDevEnabled()) {
204
+ infrastructureTools.push(registerEnrichTestWithMocksTool, registerPreflightMockCheckTool, registerGenerateEnrichedIntegrationTestTool, registerLocalDevWorkerComposeTool, registerQueryProxyMocksTool);
205
+ logger.info("Local-dev tools enabled via SKYRAMP_FEATURE_LOCAL_DEV");
206
+ }
170
207
  if (isTestbotEnabled()) {
171
208
  infrastructureTools.push(registerSubmitReportTool);
172
209
  logger.info("Testbot tools enabled via SKYRAMP_FEATURE_TESTBOT");
@@ -195,6 +232,38 @@ process.on("uncaughtException", async (error) => {
195
232
  }
196
233
  process.exit(1);
197
234
  });
235
+ // Handle unhandled promise rejections — without this handler Node.js 15+
236
+ // terminates the process on any unhandled rejection, which drops the MCP
237
+ // connection if e.g. a progress notification write fails on a broken transport.
238
+ process.on("unhandledRejection", async (reason) => {
239
+ const error = reason instanceof Error ? reason : new Error(String(reason));
240
+ // EPIPE means the client already disconnected — exit cleanly
241
+ if ("code" in error && error.code === "EPIPE") {
242
+ process.exit(0);
243
+ }
244
+ // gRPC/Docker connectivity errors — log and survive.
245
+ // These occur when Docker containers/networks are destroyed while the MCP
246
+ // server has pending gRPC calls to the Skyramp worker. The server should
247
+ // stay alive so non-Docker tools remain available.
248
+ const msg = error.message || "";
249
+ const isDockerDisconnect = /UNAVAILABLE|DEADLINE_EXCEEDED|ECONNREFUSED|ECONNRESET|connection reset|socket hang up|stream removed/i.test(msg);
250
+ if (isDockerDisconnect) {
251
+ logger.info("gRPC/Docker connection lost (container or network likely destroyed)", {
252
+ error: msg,
253
+ });
254
+ return;
255
+ }
256
+ logger.error("Unhandled promise rejection", {
257
+ error: error.message,
258
+ stack: error.stack,
259
+ });
260
+ try {
261
+ await AnalyticsService.pushServerCrashEvent("unhandledRejection", error.message, error.stack);
262
+ }
263
+ catch {
264
+ // Best effort — don't let telemetry failure cascade
265
+ }
266
+ });
198
267
  // Start MCP server
199
268
  async function main() {
200
269
  const transport = new StdioServerTransport();
@@ -230,6 +299,11 @@ async function main() {
230
299
  logger.error("STDIN error, parent likely disconnected", { error: err });
231
300
  handleParentDisconnect("stdin error");
232
301
  });
302
+ // Handle stdout errors (EPIPE = client stopped reading, broken pipe)
303
+ process.stdout.on("error", (err) => {
304
+ logger.error("STDOUT error, client likely disconnected", { error: err });
305
+ handleParentDisconnect("stdout error");
306
+ });
233
307
  // Handle process termination signals
234
308
  process.on("SIGTERM", () => handleParentDisconnect("SIGTERM"));
235
309
  process.on("SIGINT", () => handleParentDisconnect("SIGINT"));
@@ -54,6 +54,8 @@ Call \`browser_assert\` when assertions are needed. Always provide the \`expecte
54
54
  - \`type: "text"\` — verify an element contains expected text
55
55
  - \`type: "value"\` — verify an input field has an expected value
56
56
 
57
+ **Visual / screenshot assertions.** When the instruction asks to take a screenshot, capture a baseline, or verify how a page/element/region *looks* (as opposed to its text or value), call \`browser_visual_snapshot\` — it records a \`toHaveScreenshot()\` assertion so the generated test pixel-compares against a baseline image on every run. Do NOT use \`browser_take_screenshot\` for this: it only captures a throwaway image, is dropped at export (it produces no assertion), and will NOT appear in the generated test. Use \`browser_take_screenshot\` only to view the page yourself while recording — never to record a visual check. \`filename\` is optional: if the instruction names a baseline file (e.g. \`page-001.png\`) pass it as \`filename\`, otherwise omit it and \`browser_visual_snapshot\` auto-generates one per \`snapshotType\` (\`page-NNN.png\` for a page, \`el-NNN.png\` for an element, \`region-NNN.png\` for a region).
58
+
57
59
  ### Tips
58
60
  - **Custom dropdowns (Radix, MUI, etc.)**: click the combobox trigger → \`browser_snapshot\` → click the option. Do NOT use \`browser_select_option\` — it only works on native \`<select>\` elements.
59
61
 
@@ -12,4 +12,5 @@ export interface AssertionEnrichmentRule {
12
12
  examples: AssertionEnrichmentExample[];
13
13
  }
14
14
  export declare function renderRule(index: number, rule: AssertionEnrichmentRule): string;
15
+ export declare function renderSharedAssertionRules(): string;
15
16
  export declare function getAssertionsPrompt(specificRules: AssertionEnrichmentRule[], scope: string, testFile: string, enhanceType: EnhanceType): string;
@@ -122,6 +122,20 @@ expect(getResponseValue(productsPostResponse, "created_at")).toMatch(/^\\d{4}-\\
122
122
  },
123
123
  ],
124
124
  },
125
+ {
126
+ title: "Use exact recorded JSON paths",
127
+ description: "Do not add object-wrapper prefixes or nested paths that are not present in the actual response body. Use the exact JSON path from the recorded response body.",
128
+ subPoints: [
129
+ `If the body is flat (\`{"id": "acc_...", "tax_exempt": true}\`), use \`"tax_exempt"\`, not \`"account.tax_exempt"\`.`,
130
+ "Only use dot notation when the recorded response genuinely nests the field under that key.",
131
+ ],
132
+ examples: [
133
+ {
134
+ language: "javascript",
135
+ code: `expect(getResponseValue(accountResponse, "tax_exempt")).toBe(true);`,
136
+ },
137
+ ],
138
+ },
125
139
  {
126
140
  title: "Computed response fields, derived using dynamic formulas",
127
141
  description: "When the response contains a field whose value is derived from a calculation, assert the result using a formula built from prior response values and request inputs, not a hardcoded literal.",
@@ -158,6 +172,9 @@ ${ex.code}
158
172
  function renderRules(rules) {
159
173
  return rules.map((rule, i) => renderRule(i + 1, rule)).join("\n\n");
160
174
  }
175
+ export function renderSharedAssertionRules() {
176
+ return renderRules(SHARED_RULES);
177
+ }
161
178
  export function getAssertionsPrompt(specificRules, scope, testFile, enhanceType) {
162
179
  const allRules = [...SHARED_RULES, ...specificRules];
163
180
  const ruleChecklistKeys = allRules
@@ -13,7 +13,7 @@ After scanning the workspace, before calling the skyramp_init_workspace tool, yo
13
13
  "framework": "<framework>",
14
14
  "testDirectory": "<path>",
15
15
  "api": { "schemaPath": "<path-or-url>", "baseUrl": "<url>", "authType": "<type>", "authHeader": "<header>" },
16
- "runtimeDetails": { "runtime": "<runtime>", "serverStartCommand": "<command>", "dockerNetwork": "<network>" }
16
+ "runtimeDetails": { "runtime": "<runtime>", "serverStartCommand": "<command>", "serverStopCommand": "<docker stop command>", "dockerNetwork": "<network>" }
17
17
  }
18
18
  ]
19
19
  \`\`\`
@@ -68,7 +68,7 @@ API schemas (look inside the service directory and check known framework default
68
68
  <runtime_config>
69
69
  Inspect the repo root (and subdirectories like .devcontainer/) for shared runtime configuration:
70
70
  1. CLAUDE.md or AGENTS.md: if either file exists at the repo root, check it for dev/test/CI setup instructions including how to start services, required environment variables, and runtime configuration.
71
- 2. Docker Compose files: scan for ALL compose files including docker-compose.yml, docker-compose.yaml, docker-compose.*.yml (such as docker-compose.testbot.yml or docker-compose.dev.yml), compose.yml, and compose.*.yaml in the repo root and subdirectories. Distinguish between application compose files and infrastructure-only compose files. Infrastructure compose files contain only supporting services like databases, Redis, Minio, mail servers, or message queues and do NOT run the application itself. Application compose files contain the actual application services that build from the repo source code (look for "build:" directives pointing to the repo, or services that map to discovered application directories). Only use application compose files for determining runtime and serverStartCommand. When a non-default compose file is found, the serverStartCommand must reference it explicitly (such as "docker compose -f docker-compose.testbot.yml up -d <service-name>"). Docker Compose ALWAYS prefixes the network name with the project name. If compose has "networks: { my-net: ... }", the actual network name is "<project-name>_my-net". If there is no explicit networks section, the default network is "<project-name>_default". The project name is the basename of the working directory where docker compose runs.
71
+ 2. Docker Compose files: scan for ALL compose files including docker-compose.yml, docker-compose.yaml, docker-compose.*.yml (such as docker-compose.testbot.yml or docker-compose.dev.yml), compose.yml, and compose.*.yaml in the repo root and subdirectories. Distinguish between application compose files and infrastructure-only compose files. Infrastructure compose files contain only supporting services like databases, Redis, Minio, mail servers, or message queues and do NOT run the application itself. Application compose files contain the actual application services that build from the repo source code (look for "build:" directives pointing to the repo, or services that map to discovered application directories). Only use application compose files for determining runtime, serverStartCommand, and serverStopCommand. When a non-default compose file is found, the serverStartCommand and serverStopCommand must reference it explicitly (such as "docker compose -f docker-compose.testbot.yml up -d --build <service-name>" and "docker compose -f docker-compose.testbot.yml stop <service-name>"). Docker Compose ALWAYS prefixes the network name with the project name. If compose has "networks: { my-net: ... }", the actual network name is "<project-name>_my-net". If there is no explicit networks section, the default network is "<project-name>_default". The project name is the basename of the working directory where docker compose runs.
72
72
  3. Makefile: extract start and dev targets.
73
73
  4. Root package.json scripts: extract workspace-level commands.
74
74
  </runtime_config>
@@ -135,18 +135,20 @@ Create one service entry per deployable unit. You MUST include every backend/API
135
135
  <runtime_fields>
136
136
  1. runtimeDetails.runtime (required): One of local, docker, or k8s. Detect per service:
137
137
  - If the service is listed in an application docker compose file (one that builds or runs the application, not infrastructure-only files containing only databases, caches, or queues) or has a Dockerfile as its intended run method, use "docker".
138
- - If k8s manifests exist (charts/, k8s/, deploy/), use "k8s".
138
+ - If k8s manifests exist (charts/, k8s/, deploy/) AND the service is intended to run in K8s for local development, use "k8s".
139
139
  - If the service has no Docker or k8s configuration and is run directly via a language runtime (npm, python, java, etc.), use "local".
140
140
  A repo may have MIXED runtimes. A backend in docker-compose.yml uses "docker" while a frontend run with pnpm or npm locally uses "local". Include ALL services regardless of runtime.
141
141
  If the frontend is bundled inside the API Docker container (no separate frontend service in the compose file), both frontend and backend services should use runtime "docker", share the same baseUrl (the container's exposed URL), and use the same serverStartCommand since they run in the same container.
142
+ IMPORTANT — proxy parameters are NOT runtime indicators: If proxyKubeconfig, proxyNamespace, or proxyUrl appear in the conversation context, they are for a Skyramp proxy worker that collects traffic samples for mock generation. They do NOT indicate the repo's runtime. Do not use proxy K8s params to set runtime, k8sNamespace, or k8sContext. Determine the runtime solely from the repo's own files (docker-compose.yml, Dockerfile, k8s manifests that are part of the repo's own local dev workflow). Note that many repos have K8s deployment manifests for staging/production but use Docker Compose for local development — check which setup the repo actually uses for local dev.
142
143
  2. runtimeDetails.serverStartCommand: The command to start or deploy the service. It MUST match the runtime:
143
- - For "docker" runtime: Use a Docker command such as "docker compose up -d <service-name>" for the default docker-compose.yml, or "docker compose -f <compose-file> up -d <service-name>" when using a non-default compose file. This is always derivable from the application docker compose file and service name.
144
+ - For "docker" runtime: Use a Docker command that rebuilds application services from the working tree before starting them, such as "docker compose up -d --build <service-name>" for the default docker-compose.yml, or "docker compose -f <compose-file> up -d --build <service-name>" when using a non-default compose file. This is always derivable from the application docker compose file and service name. If the compose service has no build context and only references an image, note that the command cannot rebuild local code.
144
145
  - For "k8s" runtime: Use a deploy command such as "kubectl apply -f deploy/", "helm install myrelease .", or "skaffold run". This is always derivable from the manifests or charts present in the repo.
145
146
  - For "local" runtime: Use an application command such as "uvicorn main:app", "npm run dev", or "java -jar app.jar". Derive from Makefile, package.json scripts, or README. If no start command is discoverable, omit this field entirely.
146
147
  NEVER mix runtime types with incompatible commands (for example, using "uvicorn" with runtime "docker" will cause errors). For "local" runtime, NEVER fabricate a command. Only use commands found in Makefile, package.json scripts, or README.
147
- 3. runtimeDetails.dockerNetwork: Docker network name. ONLY set when runtime is "docker". NEVER set for "local" or "k8s".
148
- 4. runtimeDetails.k8sNamespace: Kubernetes namespace. ONLY set when runtime is "k8s". NEVER set for "local" or "docker".
149
- 5. runtimeDetails.k8sContext: Kubernetes context. ONLY set when runtime is "k8s". NEVER set for "local" or "docker".
148
+ 3. runtimeDetails.serverStopCommand: Optional command to stop the service after test execution. ONLY set when runtime is "docker" and serverStartCommand starts persistent Docker infrastructure. For docker compose starts, prefer matching stop/down commands from the same compose file. Omit this field for "local" and "k8s" runtimes, or when teardown is unsafe or not discoverable.
149
+ 4. runtimeDetails.dockerNetwork: Docker network name. ONLY set when runtime is "docker". NEVER set for "local" or "k8s".
150
+ 5. runtimeDetails.k8sNamespace: Kubernetes namespace. ONLY set when runtime is "k8s". NEVER set for "local" or "docker".
151
+ 6. runtimeDetails.k8sContext: Kubernetes context. ONLY set when runtime is "k8s". NEVER set for "local" or "docker".
150
152
  </runtime_fields>
151
153
 
152
154
  ### Verification
@@ -159,8 +161,8 @@ Before calling skyramp_init_workspace, confirm all of the following:
159
161
  5. Every service with authType apiKey has authHeader explicitly set to the actual custom header name (such as "X-API-Key" or "X-Admin-Key"). If you cannot find the header name in the source code, env vars, or README, do NOT use authType apiKey. Use authType none instead and add a YAML comment explaining auth is unresolved.
160
162
  6. framework matches language (python uses pytest or robot, typescript or javascript uses playwright, java uses junit).
161
163
  7. testDirectory follows the stable resolution rules above: framework config file when present (Playwright testDir in playwright.config.ts, pytest testpaths in pytest.ini or pyproject.toml, JUnit test source dir in pom.xml or build.gradle); otherwise the deterministic default (tests/skyramp for a single service, tests/skyramp/<serviceDirName> for multiple services).
162
- 8. If serverStartCommand is provided, it matches the runtime.
163
- 9. For services in docker-compose.yml: runtime MUST be "docker" and the command MUST be a docker command such as "docker compose up -d <service-name>". Always include it since it is derivable from the service name.
164
+ 8. If serverStartCommand is provided, it matches the runtime. If serverStopCommand is provided, runtime is "docker" and the command is a Docker command.
165
+ 9. For services in docker-compose.yml: runtime MUST be "docker" and the command MUST be a docker command such as "docker compose up -d --build <service-name>" when the service has a build context. Always include it since it is derivable from the service name.
164
166
  10. NEVER use application-level commands (uvicorn, npm, node, python, java, etc.) with runtime "docker".
165
167
  11. For "local" runtime: if no start command is discoverable from Makefile, package.json scripts, or README, omit serverStartCommand rather than guessing.
166
168
  12. dockerNetwork is set only when runtime is "docker".