@skyramp/mcp 0.3.2-rc.pom-3 → 0.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (289) hide show
  1. package/build/commands/commandLibrary.d.ts +1 -0
  2. package/build/commands/commandLibrary.js +19 -13
  3. package/build/commands/localDevTestChangesCommand.d.ts +15 -0
  4. package/build/commands/localDevTestChangesCommand.js +201 -0
  5. package/build/index.js +80 -6
  6. package/build/prompts/code-reuse.js +3 -0
  7. package/build/prompts/enhance-assertions/sharedAssertionRules.d.ts +1 -0
  8. package/build/prompts/enhance-assertions/sharedAssertionRules.js +17 -0
  9. package/build/prompts/initialize-workspace/initializeWorkspacePrompt.js +11 -9
  10. package/build/prompts/local-dev/local-dev-plan.d.ts +35 -0
  11. package/build/prompts/local-dev/local-dev-plan.js +429 -0
  12. package/build/prompts/local-dev/local-dev-prompts.d.ts +4 -0
  13. package/build/prompts/local-dev/local-dev-prompts.js +190 -0
  14. package/build/prompts/pom-aware-code-reuse.js +12 -0
  15. package/build/prompts/prompt-utils.d.ts +8 -0
  16. package/build/prompts/prompt-utils.js +33 -0
  17. package/build/prompts/sut-setup/modes/adaptWorkflowPrompt.js +37 -14
  18. package/build/prompts/sut-setup/shared.js +16 -12
  19. package/build/prompts/test-recommendation/analysisOutputPrompt.js +21 -29
  20. package/build/prompts/test-recommendation/scopeAssessment.d.ts +24 -7
  21. package/build/prompts/test-recommendation/scopeAssessment.js +111 -12
  22. package/build/prompts/test-recommendation/test-recommendation-prompt.js +41 -4
  23. package/build/prompts/testbot/testbot-prompts.d.ts +0 -5
  24. package/build/prompts/testbot/testbot-prompts.js +32 -52
  25. package/build/recommendation/planRanker.d.ts +15 -2
  26. package/build/recommendation/planRanker.js +76 -5
  27. package/build/resources/testbotResource.js +2 -1
  28. package/build/services/AnalyticsService.d.ts +1 -1
  29. package/build/services/TestExecutionService.d.ts +2 -1
  30. package/build/services/TestExecutionService.js +8 -3
  31. package/build/services/TestGenerationService.d.ts +2 -2
  32. package/build/services/TestGenerationService.js +39 -21
  33. package/build/services/containerEnv.js +3 -1
  34. package/build/tool-phases.js +6 -0
  35. package/build/tools/code-refactor/codeReuseTool.js +43 -4
  36. package/build/tools/code-refactor/enhanceAssertionsTool.js +68 -18
  37. package/build/tools/code-refactor/reuse-outcome.d.ts +109 -0
  38. package/build/tools/code-refactor/reuse-outcome.js +158 -0
  39. package/build/tools/code-refactor/reuse-state.d.ts +45 -0
  40. package/build/tools/code-refactor/reuse-state.js +140 -0
  41. package/build/tools/enrichTestWithMocksTool.d.ts +28 -0
  42. package/build/tools/enrichTestWithMocksTool.js +726 -0
  43. package/build/tools/executeSkyrampTestTool.d.ts +11 -0
  44. package/build/tools/executeSkyrampTestTool.js +62 -21
  45. package/build/tools/generate-tests/batchMockGenerationTool.d.ts +106 -0
  46. package/build/tools/generate-tests/batchMockGenerationTool.js +545 -0
  47. package/build/tools/generate-tests/generateBatchScenarioRestTool.js +25 -23
  48. package/build/tools/generate-tests/generateContractRestTool.js +2 -2
  49. package/build/tools/generate-tests/generateE2ERestTool.d.ts +1 -1
  50. package/build/tools/generate-tests/generateE2ERestTool.js +1 -1
  51. package/build/tools/generate-tests/generateLoadRestTool.d.ts +1 -1
  52. package/build/tools/generate-tests/generateLoadRestTool.js +1 -1
  53. package/build/tools/generate-tests/generateMockRestTool.d.ts +179 -6
  54. package/build/tools/generate-tests/generateMockRestTool.js +391 -22
  55. package/build/tools/generate-tests/loadTestSchema.d.ts +1 -1
  56. package/build/tools/generate-tests/planGuard.js +2 -22
  57. package/build/tools/generateEnrichedIntegrationTestTool.d.ts +25 -0
  58. package/build/tools/generateEnrichedIntegrationTestTool.js +235 -0
  59. package/build/tools/localDevWorkerComposeTool.d.ts +24 -0
  60. package/build/tools/localDevWorkerComposeTool.js +264 -0
  61. package/build/tools/one-click/oneClickTool.d.ts +13 -0
  62. package/build/tools/one-click/oneClickTool.js +195 -24
  63. package/build/tools/preflightMockCheckTool.d.ts +2 -0
  64. package/build/tools/preflightMockCheckTool.js +96 -0
  65. package/build/tools/queryProxyMocksTool.d.ts +70 -0
  66. package/build/tools/queryProxyMocksTool.js +522 -0
  67. package/build/tools/runExistingTestsTool.d.ts +31 -0
  68. package/build/tools/runExistingTestsTool.js +214 -13
  69. package/build/tools/submitReportTool.js +38 -3
  70. package/build/tools/test-management/analyzeChangesTool.d.ts +2 -1
  71. package/build/tools/test-management/analyzeChangesTool.js +63 -40
  72. package/build/tools/test-management/registerTestPlanTool.js +55 -7
  73. package/build/tools/trace/startTraceCollectionTool.js +3 -3
  74. package/build/types/FrontendIntegration.d.ts +3 -0
  75. package/build/types/FrontendIntegration.js +3 -0
  76. package/build/types/OneClickCommands.d.ts +1 -1
  77. package/build/types/Recommendation.d.ts +20 -0
  78. package/build/types/Recommendation.js +32 -6
  79. package/build/types/RepositoryAnalysis.d.ts +131 -14
  80. package/build/types/RepositoryAnalysis.js +16 -2
  81. package/build/types/ReuseOutcome.d.ts +63 -0
  82. package/build/types/ReuseOutcome.js +33 -0
  83. package/build/types/TestExecution.d.ts +1 -0
  84. package/build/types/TestTypes.d.ts +25 -7
  85. package/build/types/TestTypes.js +22 -6
  86. package/build/types/TestbotReport.d.ts +7 -0
  87. package/build/types/index.d.ts +2 -0
  88. package/build/types/index.js +1 -0
  89. package/build/utils/AnalysisStateManager.d.ts +26 -0
  90. package/build/utils/AnalysisStateManager.js +31 -1
  91. package/build/utils/analyze-openapi.js +18 -1
  92. package/build/utils/branchDiff.d.ts +17 -1
  93. package/build/utils/branchDiff.js +99 -14
  94. package/build/utils/featureFlags.d.ts +31 -0
  95. package/build/utils/featureFlags.js +37 -0
  96. package/build/utils/frontendIntegration.js +8 -1
  97. package/build/utils/grpcMockValidation.d.ts +1 -0
  98. package/build/utils/grpcMockValidation.js +49 -0
  99. package/build/utils/httpMethodValidation.d.ts +4 -0
  100. package/build/utils/httpMethodValidation.js +15 -0
  101. package/build/utils/logger.js +1 -1
  102. package/build/utils/mockCompatibility.d.ts +49 -0
  103. package/build/utils/mockCompatibility.js +82 -0
  104. package/build/utils/pom-scope/pom-files.js +7 -0
  105. package/build/utils/pom-verify/bindings.js +11 -1
  106. package/build/utils/pom-verify/re-exports.d.ts +10 -0
  107. package/build/utils/pom-verify/re-exports.js +61 -0
  108. package/build/utils/pom-verify/resolve.d.ts +4 -1
  109. package/build/utils/pom-verify/resolve.js +7 -4
  110. package/build/utils/pom-verify/verify.d.ts +5 -0
  111. package/build/utils/pom-verify/verify.js +25 -3
  112. package/build/utils/progress.js +10 -5
  113. package/build/utils/proxy-terminal.js +3 -3
  114. package/build/utils/routeParsers.d.ts +3 -9
  115. package/build/utils/routeParsers.js +79 -4
  116. package/build/utils/utils.js +2 -2
  117. package/build/utils/versions.d.ts +4 -3
  118. package/build/utils/versions.js +3 -1
  119. package/build/utils/workspaceAuth.d.ts +46 -0
  120. package/build/utils/workspaceAuth.js +156 -1
  121. package/build/workspace/testSuites.d.ts +2 -1
  122. package/build/workspace/testSuites.js +1 -1
  123. package/build/workspace/workspace.d.ts +108 -32
  124. package/build/workspace/workspace.js +32 -4
  125. package/package.json +5 -2
  126. package/build/adapters/jestAdapter.test.d.ts +0 -1
  127. package/build/adapters/jestAdapter.test.js +0 -93
  128. package/build/adapters/mochaAdapter.test.d.ts +0 -1
  129. package/build/adapters/mochaAdapter.test.js +0 -63
  130. package/build/adapters/playwrightAdapter.test.d.ts +0 -1
  131. package/build/adapters/playwrightAdapter.test.js +0 -169
  132. package/build/adapters/pytestAdapter.test.d.ts +0 -1
  133. package/build/adapters/pytestAdapter.test.js +0 -90
  134. package/build/prompts/code-reuse.test.d.ts +0 -1
  135. package/build/prompts/code-reuse.test.js +0 -62
  136. package/build/prompts/pom-aware-code-reuse.test.d.ts +0 -1
  137. package/build/prompts/pom-aware-code-reuse.test.js +0 -11
  138. package/build/prompts/test-maintenance/drift-analysis-prompt.test.d.ts +0 -1
  139. package/build/prompts/test-maintenance/drift-analysis-prompt.test.js +0 -51
  140. package/build/prompts/test-recommendation/analysisOutputPrompt.test.d.ts +0 -1
  141. package/build/prompts/test-recommendation/analysisOutputPrompt.test.js +0 -264
  142. package/build/prompts/test-recommendation/mergeEnrichedScenarios.test.d.ts +0 -1
  143. package/build/prompts/test-recommendation/mergeEnrichedScenarios.test.js +0 -126
  144. package/build/prompts/test-recommendation/promptPlan.test.d.ts +0 -1
  145. package/build/prompts/test-recommendation/promptPlan.test.js +0 -336
  146. package/build/prompts/test-recommendation/scopeAssessment.test.d.ts +0 -1
  147. package/build/prompts/test-recommendation/scopeAssessment.test.js +0 -371
  148. package/build/prompts/test-recommendation/test-recommendation-prompt.test.d.ts +0 -1
  149. package/build/prompts/test-recommendation/test-recommendation-prompt.test.js +0 -1925
  150. package/build/prompts/testbot/testbot-prompts.test.d.ts +0 -1
  151. package/build/prompts/testbot/testbot-prompts.test.js +0 -451
  152. package/build/recommendation/budgeters/diversityBalancedBudgeter.test.d.ts +0 -1
  153. package/build/recommendation/budgeters/diversityBalancedBudgeter.test.js +0 -138
  154. package/build/recommendation/budgeters/fixedNBudgeter.test.d.ts +0 -1
  155. package/build/recommendation/budgeters/fixedNBudgeter.test.js +0 -66
  156. package/build/recommendation/discriminators.test.d.ts +0 -1
  157. package/build/recommendation/discriminators.test.js +0 -324
  158. package/build/recommendation/diversity.test.d.ts +0 -1
  159. package/build/recommendation/diversity.test.js +0 -77
  160. package/build/recommendation/planRanker.test.d.ts +0 -1
  161. package/build/recommendation/planRanker.test.js +0 -110
  162. package/build/resources/testbotResource.test.d.ts +0 -1
  163. package/build/resources/testbotResource.test.js +0 -44
  164. package/build/services/AnalyticsService.test.d.ts +0 -1
  165. package/build/services/AnalyticsService.test.js +0 -86
  166. package/build/services/ScenarioGenerationService.integration.test.d.ts +0 -1
  167. package/build/services/ScenarioGenerationService.integration.test.js +0 -162
  168. package/build/services/ScenarioGenerationService.test.d.ts +0 -1
  169. package/build/services/ScenarioGenerationService.test.js +0 -414
  170. package/build/services/TestDiscoveryService.test.d.ts +0 -1
  171. package/build/services/TestDiscoveryService.test.js +0 -983
  172. package/build/services/TestExecutionService.test.d.ts +0 -1
  173. package/build/services/TestExecutionService.test.js +0 -1032
  174. package/build/services/TestGenerationService.test.d.ts +0 -1
  175. package/build/services/TestGenerationService.test.js +0 -578
  176. package/build/tool-phase-coverage.test.d.ts +0 -1
  177. package/build/tool-phase-coverage.test.js +0 -49
  178. package/build/tools/code-refactor/codeReuseTool.test.d.ts +0 -1
  179. package/build/tools/code-refactor/codeReuseTool.test.js +0 -572
  180. package/build/tools/generate-tests/generateBatchScenarioRestTool.test.d.ts +0 -1
  181. package/build/tools/generate-tests/generateBatchScenarioRestTool.test.js +0 -433
  182. package/build/tools/generate-tests/generateContractRestTool.test.d.ts +0 -1
  183. package/build/tools/generate-tests/generateContractRestTool.test.js +0 -142
  184. package/build/tools/generate-tests/generateIntegrationRestTool.test.d.ts +0 -1
  185. package/build/tools/generate-tests/generateIntegrationRestTool.test.js +0 -159
  186. package/build/tools/generate-tests/generateLoadRestTool.test.d.ts +0 -1
  187. package/build/tools/generate-tests/generateLoadRestTool.test.js +0 -169
  188. package/build/tools/generate-tests/generateUIRestTool.test.d.ts +0 -1
  189. package/build/tools/generate-tests/generateUIRestTool.test.js +0 -32
  190. package/build/tools/generate-tests/planGuard.test.d.ts +0 -1
  191. package/build/tools/generate-tests/planGuard.test.js +0 -185
  192. package/build/tools/generate-tests/scenarioLint.test.d.ts +0 -1
  193. package/build/tools/generate-tests/scenarioLint.test.js +0 -100
  194. package/build/tools/runExistingTestsTool.test.d.ts +0 -1
  195. package/build/tools/runExistingTestsTool.test.js +0 -379
  196. package/build/tools/submitReportTool.test.d.ts +0 -1
  197. package/build/tools/submitReportTool.test.js +0 -1428
  198. package/build/tools/test-management/actionsTool.test.d.ts +0 -1
  199. package/build/tools/test-management/actionsTool.test.js +0 -436
  200. package/build/tools/test-management/analyzeChangesTool.test.d.ts +0 -1
  201. package/build/tools/test-management/analyzeChangesTool.test.js +0 -522
  202. package/build/tools/test-management/analyzeTestHealthTool.test.d.ts +0 -1
  203. package/build/tools/test-management/analyzeTestHealthTool.test.js +0 -385
  204. package/build/tools/test-management/registerTestPlanTool.test.d.ts +0 -1
  205. package/build/tools/test-management/registerTestPlanTool.test.js +0 -296
  206. package/build/tools/trace/resolveSaveStoragePath.test.d.ts +0 -1
  207. package/build/tools/trace/resolveSaveStoragePath.test.js +0 -17
  208. package/build/tools/trace/resolveSessionPaths.test.d.ts +0 -1
  209. package/build/tools/trace/resolveSessionPaths.test.js +0 -104
  210. package/build/tools/trace/sessionState.test.d.ts +0 -1
  211. package/build/tools/trace/sessionState.test.js +0 -17
  212. package/build/tools/workspace/initializeWorkspaceTool.test.d.ts +0 -1
  213. package/build/tools/workspace/initializeWorkspaceTool.test.js +0 -139
  214. package/build/tools/workspace/serviceUpsert.test.d.ts +0 -1
  215. package/build/tools/workspace/serviceUpsert.test.js +0 -50
  216. package/build/utils/AnalysisStateManager.test.d.ts +0 -1
  217. package/build/utils/AnalysisStateManager.test.js +0 -134
  218. package/build/utils/dartRouteExtractor.test.d.ts +0 -1
  219. package/build/utils/dartRouteExtractor.test.js +0 -307
  220. package/build/utils/docker.test.d.ts +0 -1
  221. package/build/utils/docker.test.js +0 -114
  222. package/build/utils/featureFlags.test.d.ts +0 -1
  223. package/build/utils/featureFlags.test.js +0 -81
  224. package/build/utils/fileWalk.test.d.ts +0 -1
  225. package/build/utils/fileWalk.test.js +0 -252
  226. package/build/utils/frontendIntegration.test.d.ts +0 -1
  227. package/build/utils/frontendIntegration.test.js +0 -229
  228. package/build/utils/frontendSelectors.test.d.ts +0 -1
  229. package/build/utils/frontendSelectors.test.js +0 -118
  230. package/build/utils/gitStaging.test.d.ts +0 -1
  231. package/build/utils/gitStaging.test.js +0 -111
  232. package/build/utils/httpDefaults.test.d.ts +0 -1
  233. package/build/utils/httpDefaults.test.js +0 -21
  234. package/build/utils/importerHop.test.d.ts +0 -1
  235. package/build/utils/importerHop.test.js +0 -469
  236. package/build/utils/pathAffinityClassification.test.d.ts +0 -1
  237. package/build/utils/pathAffinityClassification.test.js +0 -208
  238. package/build/utils/planMatchKeys.test.d.ts +0 -1
  239. package/build/utils/planMatchKeys.test.js +0 -123
  240. package/build/utils/pom-scope/index.test.d.ts +0 -1
  241. package/build/utils/pom-scope/index.test.js +0 -278
  242. package/build/utils/pom-scope/pom-files.test.d.ts +0 -1
  243. package/build/utils/pom-scope/pom-files.test.js +0 -29
  244. package/build/utils/pom-scope/scoring.test.d.ts +0 -1
  245. package/build/utils/pom-scope/scoring.test.js +0 -39
  246. package/build/utils/pom-scope/selector-extractor.test.d.ts +0 -1
  247. package/build/utils/pom-scope/selector-extractor.test.js +0 -67
  248. package/build/utils/pom-verify/bindings.test.d.ts +0 -1
  249. package/build/utils/pom-verify/bindings.test.js +0 -164
  250. package/build/utils/pom-verify/calls.test.d.ts +0 -1
  251. package/build/utils/pom-verify/calls.test.js +0 -61
  252. package/build/utils/pom-verify/resolve.test.d.ts +0 -1
  253. package/build/utils/pom-verify/resolve.test.js +0 -114
  254. package/build/utils/pom-verify/verify.test.d.ts +0 -1
  255. package/build/utils/pom-verify/verify.test.js +0 -374
  256. package/build/utils/pr-comment-parser.test.d.ts +0 -1
  257. package/build/utils/pr-comment-parser.test.js +0 -428
  258. package/build/utils/progress.test.d.ts +0 -1
  259. package/build/utils/progress.test.js +0 -37
  260. package/build/utils/projectMetadata.test.d.ts +0 -1
  261. package/build/utils/projectMetadata.test.js +0 -172
  262. package/build/utils/pythonMountPrefixes.test.d.ts +0 -1
  263. package/build/utils/pythonMountPrefixes.test.js +0 -113
  264. package/build/utils/repoScanner.test.d.ts +0 -1
  265. package/build/utils/repoScanner.test.js +0 -190
  266. package/build/utils/reportVerification.test.d.ts +0 -1
  267. package/build/utils/reportVerification.test.js +0 -185
  268. package/build/utils/routeParsers.test.d.ts +0 -1
  269. package/build/utils/routeParsers.test.js +0 -1011
  270. package/build/utils/scenarioDrafting.test.d.ts +0 -1
  271. package/build/utils/scenarioDrafting.test.js +0 -876
  272. package/build/utils/sourceRouteExtractor.test.d.ts +0 -1
  273. package/build/utils/sourceRouteExtractor.test.js +0 -738
  274. package/build/utils/telemetry.test.d.ts +0 -1
  275. package/build/utils/telemetry.test.js +0 -70
  276. package/build/utils/trace-parser.test.d.ts +0 -6
  277. package/build/utils/trace-parser.test.js +0 -140
  278. package/build/utils/uiPageEnumerator.test.d.ts +0 -1
  279. package/build/utils/uiPageEnumerator.test.js +0 -824
  280. package/build/utils/utils.test.d.ts +0 -1
  281. package/build/utils/utils.test.js +0 -103
  282. package/build/utils/walkerCharacterization.test.d.ts +0 -1
  283. package/build/utils/walkerCharacterization.test.js +0 -233
  284. package/build/utils/workspaceAuth.test.d.ts +0 -1
  285. package/build/utils/workspaceAuth.test.js +0 -260
  286. package/build/workspace/testSuites.test.d.ts +0 -1
  287. package/build/workspace/testSuites.test.js +0 -23
  288. package/build/workspace/workspace.test.d.ts +0 -1
  289. package/build/workspace/workspace.test.js +0 -264
@@ -0,0 +1,522 @@
1
+ import { z } from "zod";
2
+ import { spawn, execFile } from "child_process";
3
+ import { promisify } from "util";
4
+ import { MockProtocol } from "../types/TestTypes.js";
5
+ import { logger } from "../utils/logger.js";
6
+ const execFileAsync = promisify(execFile);
7
+ const TOOL_NAME = "skyramp_query_proxy_mocks";
8
+ // Internal safety cap on total samples fetched per protocol, guards against a
9
+ // runaway pagination loop if the worker keeps reporting hasMore.
10
+ const MAX_SAMPLES_PER_PROTOCOL = 500;
11
+ // Default worker port inside the pod (management API). See skyramp3 mgmt_api.go.
12
+ const DEFAULT_REMOTE_PORT = 35142;
13
+ // How long to wait for `kubectl port-forward` to report a bound local port.
14
+ const PORT_FORWARD_READY_TIMEOUT_MS = 15_000;
15
+ // Grace period between SIGTERM and SIGKILL when tearing a forward down.
16
+ const PORT_FORWARD_KILL_GRACE_MS = 2_000;
17
+ // Per-request timeout for the samples query so a stalled proxy can't hang the tool.
18
+ const SAMPLES_FETCH_TIMEOUT_MS = 10_000;
19
+ // Default label used by the Skyramp worker Helm chart.
20
+ const WORKER_SERVICE_SELECTOR = "app.kubernetes.io/name=skyramp-worker";
21
+ const PROTOCOLS = ["rest", "grpc", "kafka"];
22
+ // ── Pure normalizer (unit tested, no network) ────────────────────────────────
23
+ /**
24
+ * Split a gRPC full method path like "/helloworld.Greeter/SayHello" into its
25
+ * service name ("helloworld.Greeter") and method ("SayHello").
26
+ */
27
+ function parseGrpcMethod(full) {
28
+ if (!full)
29
+ return {};
30
+ const trimmed = full.replace(/^\//, "");
31
+ const slash = trimmed.lastIndexOf("/");
32
+ if (slash === -1)
33
+ return { method: trimmed };
34
+ return { service: trimmed.slice(0, slash), method: trimmed.slice(slash + 1) };
35
+ }
36
+ /**
37
+ * Derive a Kafka topic from either explicit metadata.topic or the sample path
38
+ * ("kafka-protocol/topic/orders" -> "orders").
39
+ */
40
+ function parseKafkaTopic(meta) {
41
+ if (meta.topic)
42
+ return meta.topic;
43
+ if (meta.path) {
44
+ const parts = meta.path.split("/").filter(Boolean);
45
+ return parts[parts.length - 1];
46
+ }
47
+ return undefined;
48
+ }
49
+ /**
50
+ * Combine a bare destination hostname with the separately-reported port
51
+ * (`metadata.port`) into `host:port`, which is what mock generation expects for
52
+ * the endpoint origin. Leaves the destination untouched if it already carries a
53
+ * port or if no usable port was reported.
54
+ */
55
+ export function withPort(destination, port) {
56
+ if (destination.includes(":"))
57
+ return destination;
58
+ if (!port || port <= 0)
59
+ return destination;
60
+ return `${destination}:${port}`;
61
+ }
62
+ /**
63
+ * Convert raw proxy samples for a single protocol into deduplicated mock specs
64
+ * whose fields map 1:1 onto skyramp_batch_mock_generation. Deduplication keeps
65
+ * one representative per distinct (destination, method/path/topic, status).
66
+ */
67
+ export function normalizeSamplesToMockSpecs(protocol, samples) {
68
+ const byKey = new Map();
69
+ for (const sample of samples) {
70
+ const meta = sample.metadata ?? {};
71
+ const destination = meta.destination;
72
+ if (!destination)
73
+ continue;
74
+ const host = withPort(destination, meta.port);
75
+ if (protocol === "rest") {
76
+ const scheme = meta.scheme || "http";
77
+ const path = meta.path || "";
78
+ const method = (meta.method || "GET").toUpperCase();
79
+ const statusCode = sample.response?.statusCode !== undefined
80
+ ? String(sample.response.statusCode)
81
+ : "";
82
+ const key = `${method} ${host}${path} ${statusCode}`;
83
+ if (byKey.has(key))
84
+ continue;
85
+ const requestBody = sample.request?.body;
86
+ byKey.set(key, {
87
+ protocol: MockProtocol.Rest,
88
+ endpointURL: `${scheme}://${host}${path}`,
89
+ method,
90
+ responseData: sample.response?.body || undefined,
91
+ responseStatusCode: statusCode || undefined,
92
+ requestData: requestBody || undefined,
93
+ requestAware: Boolean(requestBody),
94
+ });
95
+ }
96
+ else if (protocol === "grpc") {
97
+ const { service, method } = parseGrpcMethod(meta.method);
98
+ const key = `${host} ${meta.method || ""}`;
99
+ if (byKey.has(key))
100
+ continue;
101
+ byKey.set(key, {
102
+ protocol: MockProtocol.Grpc,
103
+ endpointURL: host,
104
+ grpcServiceName: service || meta.service || undefined,
105
+ method: method || undefined,
106
+ responseData: sample.response?.body || undefined,
107
+ requestData: sample.request?.body || undefined,
108
+ });
109
+ }
110
+ else {
111
+ // kafka
112
+ const topic = parseKafkaTopic(meta);
113
+ const isProduce = (meta.method || "").toLowerCase().includes("produce");
114
+ const key = `${host} ${topic || ""} ${isProduce ? "produce" : "consume"}`;
115
+ if (byKey.has(key))
116
+ continue;
117
+ // Kafka mocks accept exactly one of requestData (producer) or responseData
118
+ // (consumer) — never both.
119
+ const spec = {
120
+ protocol: MockProtocol.Kafka,
121
+ endpointURL: host,
122
+ kafkaTopic: topic,
123
+ };
124
+ if (isProduce) {
125
+ spec.requestData =
126
+ sample.request?.data || sample.request?.body || undefined;
127
+ }
128
+ else {
129
+ spec.responseData =
130
+ sample.response?.data || sample.response?.body || undefined;
131
+ }
132
+ byKey.set(key, spec);
133
+ }
134
+ }
135
+ return Array.from(byKey.values());
136
+ }
137
+ // ── Proxy URL normalization ──────────────────────────────────────────────────
138
+ /** Normalize a user-supplied proxy address into a scheme://host:port base. */
139
+ export function normalizeProxyBaseUrl(proxyUrl) {
140
+ let url = proxyUrl.trim();
141
+ if (!/^https?:\/\//i.test(url))
142
+ url = `http://${url}`;
143
+ const parsed = new URL(url);
144
+ // Strip any path/query — the samples endpoint is appended by the caller.
145
+ return `${parsed.protocol}//${parsed.host}`;
146
+ }
147
+ // Registry of live port-forward stop callbacks so a process-level signal can
148
+ // sweep any that are still running (see registerExitSafetyNet).
149
+ const activeForwards = new Set();
150
+ let exitHandlersRegistered = false;
151
+ function registerExitSafetyNet() {
152
+ if (exitHandlersRegistered)
153
+ return;
154
+ exitHandlersRegistered = true;
155
+ const sweep = () => {
156
+ for (const stop of Array.from(activeForwards)) {
157
+ try {
158
+ stop();
159
+ }
160
+ catch {
161
+ /* best effort */
162
+ }
163
+ }
164
+ };
165
+ process.on("exit", sweep);
166
+ process.on("SIGINT", sweep);
167
+ process.on("SIGTERM", sweep);
168
+ }
169
+ async function discoverWorkerPod(kubeconfig, namespace) {
170
+ const { stdout } = await execFileAsync("kubectl", [
171
+ "--kubeconfig",
172
+ kubeconfig,
173
+ "-n",
174
+ namespace,
175
+ "get",
176
+ "pods",
177
+ "-l",
178
+ WORKER_SERVICE_SELECTOR,
179
+ "-o",
180
+ "jsonpath={.items[0].metadata.name}",
181
+ ]);
182
+ const name = stdout.trim();
183
+ if (!name) {
184
+ throw new Error(`No pod matching '${WORKER_SERVICE_SELECTOR}' found in namespace '${namespace}'. ` +
185
+ `Pass workerService explicitly.`);
186
+ }
187
+ return name;
188
+ }
189
+ /**
190
+ * Start `kubectl port-forward` to the given target (a pod name, or "svc/<name>")
191
+ * using an OS-chosen local port (":remotePort"), resolving once kubectl reports
192
+ * the bound local port. The returned stop() kills the whole process group and is
193
+ * idempotent. Forwarding to the pod on its container port avoids depending on how
194
+ * the worker Service maps its ports (the Helm chart exposes port 80 → 35142).
195
+ */
196
+ function startPortForward(kubeconfig, namespace, target, remotePort) {
197
+ registerExitSafetyNet();
198
+ const child = spawn("kubectl", [
199
+ "--kubeconfig",
200
+ kubeconfig,
201
+ "-n",
202
+ namespace,
203
+ "port-forward",
204
+ target,
205
+ `:${remotePort}`,
206
+ ], { detached: true, stdio: ["ignore", "pipe", "pipe"] });
207
+ let stopped = false;
208
+ const stop = () => {
209
+ if (stopped)
210
+ return;
211
+ stopped = true;
212
+ activeForwards.delete(stop);
213
+ const pid = child.pid;
214
+ if (!pid)
215
+ return;
216
+ const killGroup = (signal) => {
217
+ try {
218
+ process.kill(-pid, signal);
219
+ }
220
+ catch {
221
+ // Group may already be gone, or on platforms without group semantics
222
+ // fall back to killing the direct child.
223
+ try {
224
+ child.kill(signal);
225
+ }
226
+ catch {
227
+ /* already dead */
228
+ }
229
+ }
230
+ };
231
+ killGroup("SIGTERM");
232
+ // Escalate to SIGKILL only if the process hasn't exited. child.killed just
233
+ // records that a signal was *sent* (and stays false for process-group kills
234
+ // via process.kill(-pid)), so check exitCode/signalCode for actual exit.
235
+ const timer = setTimeout(() => {
236
+ if (child.exitCode === null && child.signalCode === null)
237
+ killGroup("SIGKILL");
238
+ }, PORT_FORWARD_KILL_GRACE_MS);
239
+ timer.unref?.();
240
+ };
241
+ activeForwards.add(stop);
242
+ return new Promise((resolve, reject) => {
243
+ const timeout = setTimeout(() => {
244
+ stop();
245
+ reject(new Error(`kubectl port-forward did not become ready within ${PORT_FORWARD_READY_TIMEOUT_MS}ms`));
246
+ }, PORT_FORWARD_READY_TIMEOUT_MS);
247
+ timeout.unref?.();
248
+ let stderr = "";
249
+ child.stdout?.on("data", (chunk) => {
250
+ const text = chunk.toString();
251
+ // kubectl prints "Forwarding from 127.0.0.1:<port>" and/or "[::1]:<port>";
252
+ // accept either (some environments emit only the IPv6 line).
253
+ const match = text.match(/Forwarding from (?:127\.0\.0\.1|\[::1\]):(\d+)/);
254
+ if (match) {
255
+ clearTimeout(timeout);
256
+ resolve({ localPort: Number(match[1]), stop });
257
+ }
258
+ });
259
+ child.stderr?.on("data", (chunk) => {
260
+ stderr += chunk.toString();
261
+ });
262
+ child.on("error", (err) => {
263
+ clearTimeout(timeout);
264
+ stop();
265
+ reject(err);
266
+ });
267
+ child.on("exit", (code) => {
268
+ clearTimeout(timeout);
269
+ stop();
270
+ reject(new Error(`kubectl port-forward exited early (code ${code}): ${stderr.trim() || "no output"}`));
271
+ });
272
+ });
273
+ }
274
+ // ── Sample querying ──────────────────────────────────────────────────────────
275
+ async function queryProtocolSamples(baseUrl, protocol, pageSize, destination) {
276
+ const collected = [];
277
+ let offset = 0;
278
+ // eslint-disable-next-line no-constant-condition
279
+ while (true) {
280
+ const params = new URLSearchParams({
281
+ protocol,
282
+ limit: String(pageSize),
283
+ offset: String(offset),
284
+ });
285
+ if (destination)
286
+ params.set("destination", destination);
287
+ const url = `${baseUrl}/skyramp/proxy/samples?${params.toString()}`;
288
+ // Bound each request so a stalled proxy connection can't hang the tool.
289
+ const res = await fetch(url, {
290
+ signal: AbortSignal.timeout(SAMPLES_FETCH_TIMEOUT_MS),
291
+ });
292
+ if (!res.ok) {
293
+ const body = await res.text().catch(() => "");
294
+ throw new Error(`GET ${url} -> ${res.status} ${body.slice(0, 200)}`);
295
+ }
296
+ const data = (await res.json());
297
+ const batch = data.samples ?? [];
298
+ collected.push(...batch);
299
+ // Stop on an empty page, an explicit hasMore=false, the internal cap, or —
300
+ // ONLY when the server reports a totalCount — once we've collected them all.
301
+ // Do NOT derive `total` from collected.length: that would make the "reached
302
+ // total" check trivially true and break pagination when totalCount is absent.
303
+ const reachedReportedTotal = typeof data.totalCount === "number" &&
304
+ collected.length >= data.totalCount;
305
+ if (batch.length === 0 ||
306
+ data.hasMore === false ||
307
+ reachedReportedTotal ||
308
+ collected.length >= MAX_SAMPLES_PER_PROTOCOL) {
309
+ break;
310
+ }
311
+ offset += batch.length;
312
+ }
313
+ return collected.slice(0, MAX_SAMPLES_PER_PROTOCOL);
314
+ }
315
+ // ── Input schema ─────────────────────────────────────────────────────────────
316
+ const inputSchema = {
317
+ proxyUrl: z
318
+ .string()
319
+ .optional()
320
+ .describe("Direct HTTP address of the Skyramp worker proxy management API, as host:port or a full URL " +
321
+ "(e.g. 'localhost:35141' or 'https://skyramp.example.com'). Also covers an ingress-controller " +
322
+ "route. Mutually exclusive with kubeconfig/namespace."),
323
+ kubeconfig: z
324
+ .string()
325
+ .optional()
326
+ .describe("Absolute path to a kubeconfig file. When set (with namespace), the tool runs `kubectl port-forward` " +
327
+ "to reach the worker in-cluster. Mutually exclusive with proxyUrl."),
328
+ namespace: z
329
+ .string()
330
+ .optional()
331
+ .describe("Kubernetes namespace where the Skyramp worker is deployed. Required with kubeconfig."),
332
+ workerService: z
333
+ .string()
334
+ .optional()
335
+ .describe(`Name of the worker Kubernetes Service to port-forward to (forwards to 'svc/<name>' at remotePort). ` +
336
+ `Optional — when omitted, the tool discovers the worker POD by label '${WORKER_SERVICE_SELECTOR}' and ` +
337
+ `forwards to it directly (avoids depending on how the Service maps its ports).`),
338
+ remotePort: z
339
+ .number()
340
+ .int()
341
+ .optional()
342
+ .describe(`Worker management API port inside the pod. Defaults to ${DEFAULT_REMOTE_PORT}.`),
343
+ protocols: z
344
+ .array(z.enum(PROTOCOLS))
345
+ .optional()
346
+ .describe("Which protocols to query. Defaults to all of rest, grpc, kafka."),
347
+ destination: z
348
+ .string()
349
+ .optional()
350
+ .describe("Optional destination filter (e.g. 'ams:4000') passed through to the samples query."),
351
+ limit: z
352
+ .number()
353
+ .int()
354
+ .positive()
355
+ .optional()
356
+ .describe("Page size per samples request (default 50). Total is capped internally."),
357
+ };
358
+ const mockSpecOutputSchema = z.object({
359
+ protocol: z.nativeEnum(MockProtocol),
360
+ endpointURL: z.string(),
361
+ method: z.string().optional(),
362
+ responseData: z.string().optional(),
363
+ responseStatusCode: z.string().optional(),
364
+ requestData: z.string().optional(),
365
+ requestAware: z.boolean().optional(),
366
+ grpcServiceName: z.string().optional(),
367
+ kafkaTopic: z.string().optional(),
368
+ });
369
+ const outputSchema = {
370
+ proxyReachable: z
371
+ .boolean()
372
+ .describe("True if the proxy was reached and queried. When false, fall back to code/OpenAPI inference."),
373
+ sampleCount: z
374
+ .number()
375
+ .describe("Total raw samples retrieved across all queried protocols."),
376
+ endpointsDiscovered: z
377
+ .number()
378
+ .describe("Number of deduplicated mock specs produced."),
379
+ mockSpecs: z
380
+ .array(mockSpecOutputSchema)
381
+ .describe("Normalized mock specs ready to pass to skyramp_batch_mock_generation."),
382
+ message: z
383
+ .string()
384
+ .optional()
385
+ .describe("Human-readable status or error detail."),
386
+ };
387
+ /**
388
+ * Validate that exactly one access mode was provided and return a normalized
389
+ * descriptor. Throws a user-facing error otherwise.
390
+ */
391
+ export function resolveAccessMode(params) {
392
+ const hasUrl = Boolean(params.proxyUrl);
393
+ const hasKube = Boolean(params.kubeconfig || params.namespace);
394
+ if (hasUrl && hasKube) {
395
+ throw new Error("Provide either proxyUrl OR kubeconfig+namespace, not both.");
396
+ }
397
+ if (!hasUrl && !hasKube) {
398
+ throw new Error("No proxy access provided. Pass proxyUrl, or kubeconfig+namespace.");
399
+ }
400
+ if (hasUrl)
401
+ return { mode: "url", proxyUrl: params.proxyUrl };
402
+ if (!params.kubeconfig || !params.namespace) {
403
+ throw new Error("Kubernetes access requires both kubeconfig and namespace.");
404
+ }
405
+ return {
406
+ mode: "kube",
407
+ kubeconfig: params.kubeconfig,
408
+ namespace: params.namespace,
409
+ };
410
+ }
411
+ export function registerQueryProxyMocksTool(server) {
412
+ server.registerTool(TOOL_NAME, {
413
+ description: "Query a running Skyramp worker proxy for recorded downstream traffic and return normalized mock specs. " +
414
+ "Use during local-dev dependency discovery when a proxy is recording real traffic: the returned mockSpecs " +
415
+ "map directly onto skyramp_batch_mock_generation (real request/response bodies and status codes) instead " +
416
+ "of code/OpenAPI inference. Access the proxy via proxyUrl (host:port or full URL, including an ingress " +
417
+ "route) OR kubeconfig+namespace (the tool runs kubectl port-forward and always tears it down). If the " +
418
+ "proxy is unreachable, proxyReachable is false and you should fall back to inference — the workflow is not " +
419
+ "aborted.",
420
+ inputSchema,
421
+ outputSchema,
422
+ }, async (params) => {
423
+ const protocols = params.protocols ?? [
424
+ ...PROTOCOLS,
425
+ ];
426
+ const pageSize = params.limit ?? 50;
427
+ const destination = params.destination;
428
+ let access;
429
+ try {
430
+ access = resolveAccessMode(params);
431
+ }
432
+ catch (err) {
433
+ const message = err instanceof Error ? err.message : String(err);
434
+ return unreachable(message);
435
+ }
436
+ let baseUrl;
437
+ let portForward;
438
+ try {
439
+ if (access.mode === "url") {
440
+ baseUrl = normalizeProxyBaseUrl(access.proxyUrl);
441
+ }
442
+ else {
443
+ // If a service name is given, forward to it (remotePort must be that
444
+ // service's port). Otherwise forward to the worker pod on its container
445
+ // port, which is independent of how the Service maps its ports.
446
+ const explicitService = params.workerService;
447
+ const target = explicitService
448
+ ? `svc/${explicitService}`
449
+ : await discoverWorkerPod(access.kubeconfig, access.namespace);
450
+ const remotePort = params.remotePort ?? DEFAULT_REMOTE_PORT;
451
+ portForward = await startPortForward(access.kubeconfig, access.namespace, target, remotePort);
452
+ baseUrl = `http://localhost:${portForward.localPort}`;
453
+ }
454
+ const allSpecs = [];
455
+ let sampleCount = 0;
456
+ for (const protocol of protocols) {
457
+ const samples = await queryProtocolSamples(baseUrl, protocol, pageSize, destination);
458
+ sampleCount += samples.length;
459
+ allSpecs.push(...normalizeSamplesToMockSpecs(protocol, samples));
460
+ }
461
+ return {
462
+ structuredContent: {
463
+ proxyReachable: true,
464
+ sampleCount,
465
+ endpointsDiscovered: allSpecs.length,
466
+ mockSpecs: allSpecs,
467
+ },
468
+ content: [
469
+ { type: "text", text: formatSummary(allSpecs, sampleCount) },
470
+ ],
471
+ };
472
+ }
473
+ catch (err) {
474
+ const message = err instanceof Error ? err.message : String(err);
475
+ logger.warning("skyramp_query_proxy_mocks failed to reach proxy", {
476
+ error: message,
477
+ });
478
+ return unreachable(message);
479
+ }
480
+ finally {
481
+ // Guaranteed teardown of the port-forward on every path.
482
+ portForward?.stop();
483
+ }
484
+ });
485
+ }
486
+ function unreachable(message) {
487
+ return {
488
+ structuredContent: {
489
+ proxyReachable: false,
490
+ sampleCount: 0,
491
+ endpointsDiscovered: 0,
492
+ mockSpecs: [],
493
+ message,
494
+ },
495
+ content: [
496
+ {
497
+ type: "text",
498
+ text: `⚠️ Proxy not queried: ${message}\n\n` +
499
+ "Fall back to OpenAPI spec / implementation-code inference for downstream mock shapes.",
500
+ },
501
+ ],
502
+ };
503
+ }
504
+ function formatSummary(specs, sampleCount) {
505
+ if (specs.length === 0) {
506
+ return `Proxy reachable but no samples recorded (queried ${sampleCount}). Fall back to code/OpenAPI inference.`;
507
+ }
508
+ const lines = specs.map((s) => {
509
+ if (s.protocol === MockProtocol.Rest) {
510
+ return ` • [rest] ${s.method} ${s.endpointURL} → ${s.responseStatusCode ?? "?"}`;
511
+ }
512
+ if (s.protocol === MockProtocol.Grpc) {
513
+ return ` • [grpc] ${s.endpointURL} ${s.grpcServiceName ?? ""}/${s.method ?? ""}`;
514
+ }
515
+ return ` • [kafka] ${s.endpointURL} topic=${s.kafkaTopic ?? "?"} ${s.requestData ? "(produce)" : "(consume)"}`;
516
+ });
517
+ return [
518
+ `✅ Proxy queried: ${sampleCount} sample(s) → ${specs.length} mock spec(s).`,
519
+ "Secrets are redacted by the proxy. Pass these specs to skyramp_batch_mock_generation:",
520
+ ...lines,
521
+ ].join("\n");
522
+ }
@@ -8,6 +8,8 @@ export interface ResolvedRunConfig {
8
8
  framework: Framework;
9
9
  testRunCommand: string;
10
10
  testEnv?: Record<string, string>;
11
+ /** Repo-relative globs this suite owns; empty/absent means it accepts any selector. */
12
+ pathGlobs?: string[];
11
13
  }
12
14
  type Framework = (typeof SUPPORTED_FRAMEWORKS)[number];
13
15
  /** Binds a framework to its pure parse/buildArgs pair + how its report is
@@ -83,6 +85,35 @@ export interface RunConfigResolution {
83
85
  export declare function resolveRunConfig(config: WorkspaceConfig, opts?: {
84
86
  service?: string;
85
87
  }): RunConfigResolution;
88
+ /** Selectors partitioned into those this suite owns and those it does not. */
89
+ export interface SelectorRouting {
90
+ kept: string[];
91
+ dropped: string[];
92
+ }
93
+ /**
94
+ * Partitions `selectors` into the ones this suite owns and the ones it does not,
95
+ * using the suite's own `pathGlobs` from workspace.yml.
96
+ *
97
+ * One repo commonly hosts several runners (mocha for a core package, vitest for
98
+ * app packages, Playwright for e2e), and handing a runner another runner's spec is
99
+ * not a test failure — it is a loader crash that yields no report at all. Which
100
+ * suite owns which paths is a fact about the repo, so it is recorded as config the
101
+ * user can read and correct, rather than guessed at run time. Run 30297865488 is
102
+ * the cautionary case: routing on a guessed framework tag sent Ghost's Playwright
103
+ * specs away from the Playwright suite, and nothing ran.
104
+ *
105
+ * A suite with no globs owns everything. That is deliberate: absent globs means
106
+ * the suite is unscoped, not that it owns nothing, and narrowing on no
107
+ * information would silently shrink the run.
108
+ */
109
+ export declare function routeSelectorsToSuite(input: {
110
+ selectors: string[];
111
+ /** The suite's repo-relative globs; absent/empty means it accepts every selector. */
112
+ pathGlobs?: string[];
113
+ /** Absolute test-file paths from analyze_changes, used to canonicalize a selector. */
114
+ knownTestFiles?: string[];
115
+ workspacePath: string;
116
+ }): SelectorRouting;
86
117
  /** Combines per-suite runs into one ParsedExternalRun for the tool's summary
87
118
  * text: sums counts, concatenates results, ANDs environmentHealthy, joins the
88
119
  * unhealthy details. */