@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
@@ -4,9 +4,9 @@ import { AnalyticsService } from "../../services/AnalyticsService.js";
4
4
  import { MAX_TESTS_TO_GENERATE, MAX_RECOMMENDATIONS, MAX_CRITICAL_TESTS, PATH_PARAM_UUID_GUIDANCE, AUTH_CONFLICT_ERROR_MSG, } from "../test-recommendation/recommendationSections.js";
5
5
  import { TASK_ANALYZE_MAINTAIN, TASK_GENERATE, TASK_SUBMIT, taskRef } from "../test-recommendation/recommendationShared.js";
6
6
  import { getTraceRecordingPromptText } from "../../playwright/traceRecordingPrompt.js";
7
- import { isContractConsumerModeEnabled } from "../../utils/featureFlags.js";
7
+ import { isContractConsumerModeEnabled, isPomReuseEnabled } from "../../utils/featureFlags.js";
8
8
  import { resolveServiceDetailsRef } from "../../utils/utils.js";
9
- import { readWorkspaceConfigRaw } from "../../utils/workspaceAuth.js";
9
+ import { buildServiceContext, readWorkspaceServices, } from "../prompt-utils.js";
10
10
  // Cached at module-load — flags are process-wide and cannot change per call.
11
11
  const CONSUMER_MODE_ENABLED = isContractConsumerModeEnabled();
12
12
  const SERVICE_REFS = resolveServiceDetailsRef();
@@ -18,6 +18,14 @@ const CONTRACT_MODE_GUIDANCE = CONSUMER_MODE_ENABLED
18
18
  For client-facing APIs consumed by frontend: add \`consumerMode: true\`.
19
19
  Both modes (\`providerMode: true, consumerMode: true\`): For diff that contains BOTH provider signals (such as new/modified endpoint handlers, route changes this service owns) AND consumer signals (outbound HTTP client calls to another service, no new endpoint handlers).`
20
20
  : ` Always add \`providerMode: true\` — the tool generates provider-side contract tests only.`;
21
+ const POM_REUSE_ENABLED = isPomReuseEnabled();
22
+ // Post-generation code-reuse step for UI tests. The POM catalog, the `verify: true`
23
+ // loop and the `.raw.bak` restore only exist on the POM-aware path — when that path
24
+ // is flagged off, `skyramp_reuse_code` returns the SkyrampUtils workflow instead, so
25
+ // the agent must not be sent looking for artifacts nothing produces.
26
+ const UI_CODE_REUSE_STEP = POM_REUSE_ENABLED
27
+ ? `3. **[MANDATORY] After \`skyramp_ui_test_generation\` with \`codeReuse: true\`**: the generation result directs you to call \`skyramp_reuse_code\` — do this BEFORE \`skyramp_enhance_assertions\`. Two outcomes: (1) a response starting "No reusable POM layer detected" — this is a normal outcome, continue immediately (do NOT retry); (2) a refactoring workflow — follow it to completion INCLUDING its verification loop (\`skyramp_reuse_code\` with \`verify: true\`), finish only when it reports PASSED. If a reused test later fails execution and the failure points at a substituted POM call, restore the saved \`<testFile>.raw.bak\` over the test file and re-run — do NOT hand-edit the customer's POM methods (this re-run counts toward the 2-attempt execution cap — prefer this restore over the generic timeout fix-up when the failing locator came from a POM substitution). **Cleanup before reporting:** \`.raw.bak\` files are internal scratch — after ALL test executions are complete (pass or fail) and before calling \`skyramp_submit_report\`, delete every \`*.raw.bak\` you created so they are not committed to the customer-facing branch. The \`skyramp-pom-catalog.md\` is NOT scratch — leave it in place (later runs reuse it).`
28
+ : `3. **[MANDATORY] After \`skyramp_ui_test_generation\` with \`codeReuse: true\`**: the generation result directs you to call \`skyramp_reuse_code\` — do this BEFORE \`skyramp_enhance_assertions\`. Follow the returned steps exactly. If it finds no existing helper functions to reuse, that is a normal outcome — leave the test file unchanged and continue immediately (do NOT retry).`;
21
29
  /**
22
30
  * Parse the JSON-encoded `relatedRepositories` argument passed via the testbot
23
31
  * prompt/resource. Returns undefined for missing/blank input or malformed JSON so the
@@ -84,7 +92,7 @@ prNumber, userPrompt, services, uiCredentials, testsRepoDir, relatedRepositories
84
92
  maintenanceBeforeExecStep = ` d. Plan-only run: skip the pre-edit baseline execution — the application is not running. Record every maintenance verdict from static drift analysis alone; execution statuses simply remain unrecorded.`;
85
93
  }
86
94
  else {
87
- maintenanceBeforeExecStep = ` d. Call \`skyramp_execute_test\` with \`phase: "before"\` and \`stateFile\` for every UPDATE/REGENERATE/DELETE test. Exclude tests marked \`[external]\` — those are baselined in step (a) via \`skyramp_run_existing_tests\`. Run them sequentially, not in parallel. This captures the pre-edit baseline — do not skip even if you expect the test to fail.`;
95
+ maintenanceBeforeExecStep = ` d. Call \`skyramp_execute_test\` with \`phase: "before"\` and \`stateFile\` for every UPDATE/REGENERATE/DELETE test. Exclude tests marked \`[external]\` — those are baselined in step 2(a) via \`skyramp_run_existing_tests\`. Run them sequentially, not in parallel. This captures the pre-edit baseline — do not skip even if you expect the test to fail.`;
88
96
  }
89
97
  // For follow-up requests: emit the @skyramp-testbot header + guardrails + retrieve-recommendations step.
90
98
  // For first-run prompts: emit the full Task 1 analysis + maintenance section.
@@ -143,12 +151,12 @@ ${hasRelatedRepos ? `
143
151
  4. **Spillover:** when a type's candidates run out, its freed slots go to the next-highest-scored candidate of any remaining type.
144
152
  Within a type, higher score wins regardless of which repo it came from. Everything not selected becomes an ADDITIONAL recommendation. This guarantees a frontend-only primary repo cannot starve a related backend repo's contract/integration tests of GENERATE slots (and vice versa). When you generate a test for a related repo's endpoint:
145
153
  - **Execute it only if that repo's service is already running and reachable.** The workflow's setup may have started multiple services; before generating an API test for a related repo, confirm its \`base_url\` (from that repo's workspace/Execution Plan) responds. If the service is unreachable, still GENERATE the test but mark its \`testResults\` status as \`Skipped\` with details "service not running in this run" — do NOT count an unreachable service as a failure.
146
- - **Write the test file into that service's own \`testDirectory\`** — the one declared for the service in the unified workspace.yml (the related repo's services were registered there in step (a), each with its \`repository\`). The \`testDirectory\` is interpreted relative to the **single delivery root** (the configured test repo if set, otherwise the primary repo), so all generated tests are delivered together by the existing single-target delivery. Do NOT invent a per-source-repo subdirectory, and do NOT write into the related repo's own checkout — it is read-only context. (If two repos happen to declare the same \`testDirectory\`, their files coexist there; the \`repository\` field on each report item — below — is what attributes ownership, not the path.)
154
+ - **Write the test file into that service's own \`testDirectory\`** — the one declared for the service in the unified workspace.yml (the related repo's services were registered there in step 1(a), each with its \`repository\`). The \`testDirectory\` is interpreted relative to the **single delivery root** (the configured test repo if set, otherwise the primary repo), so all generated tests are delivered together by the existing single-target delivery. Do NOT invent a per-source-repo subdirectory, and do NOT write into the related repo's own checkout — it is read-only context. (If two repos happen to declare the same \`testDirectory\`, their files coexist there; the \`repository\` field on each report item — below — is what attributes ownership, not the path.)
147
155
  - **Set the \`repository\` field** (\`owner/repo\`) on every such \`newTestsCreated\` / \`testResults\` item so the report attributes it to the originating repo (see Report Guidelines).` : ""}
148
156
 
149
157
  2. **Maintain existing tests:**
150
158
 
151
- a. Confirm external-test breakage before assessment. If any service in \`${repositoryPath}/.skyramp/workspace.yml\` declares test-env config (\`runtimeDetails.test*\`), call \`skyramp_run_existing_tests\` (\`mode: "confirm"\`, \`stateFile\`) on the tests \`skyramp_analyze_changes\` marked \`[external]\` — the repo's OWN suite, not the Skyramp-generated tests — before \`skyramp_analyze_test_health\`, so the run-confirmed failures fold into its assessment. Do NOT read the \`[external]\` test files to *guess* the change's impact — RUN them with \`skyramp_run_existing_tests\` to get real pass/fail; reading them is not a substitute for running them. These \`[external]\` suites also do not run through \`skyramp_execute_test\`, so skipping this leaves their status \`Unknown\`. Selector, health-gate, and self-skip behavior are in the tool description. (Skyramp-generated tests are baselined in step (d), not here.)
159
+ a. Confirm external-test breakage before assessment. If any service in \`${repositoryPath}/.skyramp/workspace.yml\` declares test-env config (\`runtimeDetails.test*\`), call \`skyramp_run_existing_tests\` (\`mode: "confirm"\`, \`stateFile\`) on the tests \`skyramp_analyze_changes\` marked \`[external]\` — the repo's OWN suite, not the Skyramp-generated tests — before \`skyramp_analyze_test_health\`, so the run-confirmed failures fold into its assessment. Do NOT read the \`[external]\` test files to *guess* the change's impact — RUN them with \`skyramp_run_existing_tests\` to get real pass/fail; reading them is not a substitute for running them. These \`[external]\` suites also do not run through \`skyramp_execute_test\`, so skipping this leaves their status \`Unknown\`. Selector, health-gate, and self-skip behavior are in the tool description. (Skyramp-generated tests are handled in step 2(d), not here.)
152
160
 
153
161
  b. Call \`skyramp_analyze_test_health\` with \`stateFile\` (from \`skyramp_analyze_changes\` output). Pass \`blueprintCaptured: true\` when \`browser_blueprint\` was called successfully earlier in this session — see the parameter description for when this applies. **Do NOT read application source files** (routes, models, controllers) — all change information you need is in the \`skyramp_analyze_changes\` output and the diff. Exception: the UI drift pre-scan (\`UI_SYMBOL_PRESCAN\`) may instruct you to read changed frontend files to extract exported symbols — follow those instructions when present.
154
162
 
@@ -158,7 +166,7 @@ ${maintenanceBeforeExecStep}
158
166
 
159
167
  e. Call \`skyramp_actions\` with \`stateFile\` (from \`skyramp_analyze_changes\` output) and apply the edits it returns.
160
168
 
161
- f. Verify external-test fixes. If step (a) ran, after applying edits in step (e) re-run the affected \`[external]\` files with \`skyramp_run_existing_tests\` (\`mode: "verify"\`, \`stateFile\`) and record each file's result as its \`afterStatus\`. A still-failing verify is surfaced in the report — do not loop.
169
+ f. Verify external-test fixes. **This step is not optional and it is the easiest one to forget — you have just edited files in step 2(e), so come back here before you move on to anything else.** It applies whenever step 2(a) reported a real pass/fail result for a file you then edited. It does NOT apply when step 2(a) returned \`skipped: true\` or \`ran: 0\` for every suite — there is no baseline to compare against, so say so in your report instead of re-running. When it applies: re-run those \`[external]\` files with \`skyramp_run_existing_tests\` (\`mode: "verify"\`, \`stateFile\`) and record each file's result as its \`afterStatus\`. Editing an \`[external]\` file that step 2(a) confirmed failing and NOT re-running it leaves your own fix unverified — you would be reporting a repair you never saw work. A still-failing verify is surfaced in the report — do not loop.
162
170
 
163
171
  3. **Code review:** From the \`skyramp_analyze_changes\` output and the existing test files you read for maintenance, note any logic bugs. Do NOT read additional source files just for code review — use what is already available from the analysis and test file reads. Common patterns to flag:
164
172
  - Computed fields not recalculated after mutation (e.g. \`total_amount\` unchanged after items are added/removed)
@@ -400,7 +408,7 @@ This is a plan-only evaluation run: the application under test is NOT running, a
400
408
  ${userPrompt ? "Generate only the tests that the user requested from the Additional Recommendations. The rules below still apply." : "Drift-based maintenance (Task 1) is complete. This step only processes the GENERATE list. Exception: if a GENERATE item targets a resource with an existing `[skyramp]` contract test, UPDATE that test file (see covered-resource handling below) — a new test case added to an existing file counts toward the budget and is reported in `newTestsCreated`."}
401
409
 
402
410
  - **MANDATORY — use the plan returned by \`skyramp_register_test_plan\` as-is**: Before generating anything, call \`skyramp_register_test_plan\` (\`stateFile\` required) with your complete candidate list — every test you would generate OR recommend, including the Execution Plan's own pre-ranked GENERATE/ADDITIONAL items and any candidate you drafted yourself, with a \`discriminator\` claim \`{kind, changedCodeAnchor}\` for candidates probing changed logic. Its returned GENERATE list — not the Execution Plan's raw pre-ranked GENERATE section — governs ADD actions from this point on. You MUST generate exactly those scenarios in the exact order listed, keeping each item's \`scenarioName\` exactly as registered — the generation tools match on it and reject renamed or substituted scenarios. If parameter grounding uncovers a distinct bug-catching scenario not already registered, generate it after all planned GENERATE items are complete and report it in \`newTestsCreated\` — this is an additional test driven by source-code analysis and does not count against the GENERATE budget.${hasRelatedRepos ? `\n - **Multi-repo exception:** this run has related repositories, so the per-repo GENERATE lists are NOT final — they are candidates re-selected by the cross-repo round-robin described in Task 1's "Cross-repo test generation". Register the pooled, type-distributed selection instead of any single repo's GENERATE list. (In single-repo runs, register the GENERATE list exactly as-is.)` : ""}
403
- - **Do not fabricate tests outside the GENERATE list.** New test files cover NEW observable surface only — a new endpoint, or a newly-integrated component/route not already covered by an existing test. Changes that only modify, delete, or add fields to an EXISTING covered endpoint or component are maintenance: handle them in ${taskRef(TASK_ANALYZE_MAINTAIN)} by UPDATE/DELETE of the existing test, never by creating a new spec. If the GENERATE list is empty (deletion-only, cosmetic, or modification-of-existing PRs with no new surface), create zero new tests and proceed to ${taskRef(TASK_SUBMIT)} — do not invent a new spec to have something to report.
411
+ - **Do not fabricate tests outside the GENERATE list provided by \`skyramp_analyze_changes\`.** Changes that only modify, delete, or add fields to an EXISTING covered endpoint or component are maintenance: handle them in ${taskRef(TASK_ANALYZE_MAINTAIN)} by UPDATE/DELETE of the existing test, never by creating a new spec. If the GENERATE list is empty, create zero new tests and proceed to ${taskRef(TASK_SUBMIT)}.
404
412
  - Scenario JSON files are always new files — always generate them for new methods. Every generated scenario JSON must have a corresponding new integration test generated from it via \`skyramp_integration_test_generation\`.
405
413
  - Covered-resource handling (aligns with Execution Plan Step 0): When a GENERATE item targets a resource that already has an existing test file covering the same endpoint:
406
414
  - If the existing test source is \`[external]\`, skip the resource entirely — the external test already provides coverage. Do NOT UPDATE, REGENERATE, or DELETE external tests.
@@ -410,11 +418,11 @@ ${userPrompt ? "Generate only the tests that the user requested from the Additio
410
418
  - UI tests: Always generate as a new file. Report in \`newTestsCreated\`.
411
419
  Keep advancing until you have created exactly as many new test files as your committed Budget Plan's generate count (at most ${maxGenerate}) OR exhausted all candidates. If your Budget Plan is 0 total, ${taskRef(TASK_GENERATE)} produces zero tests.
412
420
  - Example: If enrichment reveals that sending \`discount_value\` without \`discount_type\` silently orphans the value (a concrete bug), complete all planned GENERATE items first, then generate this discovered scenario as an extra test and report it in \`newTestsCreated\`.
413
- - Total generated: your committed Budget Plan's generate count (from the Execution Plan's Scope Assessment, at most ${maxGenerate}) is the single source of truth for how many tests to create. Process every GENERATE-tagged item in order, then backfill from ADDITIONAL candidates (highest-ranked first) until \`newTestsCreated\` reaches that generate count or all candidates are exhausted. If your Budget Plan is 0 total (the Execution Plan's zero-classified default or cosmetic-only override applies), skip generation and backfilling entirely and proceed to ${taskRef(TASK_SUBMIT)}'s zero-test report path.
414
- - **UI test priority**: If the PR scope assessment shows any UI/E2E budget OR \`uiContext.changedFrontendFiles\` is non-empty (the deterministic server signal — populated for all supported frontend file types including \`.tsx\`/\`.jsx\`/\`.vue\`/\`.svelte\`/\`.dart\`), you MUST attempt to generate at least one UI test. Use \`browser_navigate\` to the app's base URL — if the app responds, record a trace and generate the test.
421
+ - Total generated: your committed Budget Plan's generate count (from the Execution Plan's Scope Assessment, at most ${maxGenerate}) is the single source of truth for how many tests to create. Process every GENERATE-tagged item in order, then backfill from ADDITIONAL candidates (highest-ranked first) until \`newTestsCreated\` reaches that generate count or all candidates are exhausted. If your Budget Plan is 0 total, skip generation and backfilling entirely and proceed to ${taskRef(TASK_SUBMIT)}'s zero-test report path.
422
+ - **UI test priority**: \`skyramp_register_test_plan\` ends its output with a **Generation directive** that states whether this run generates UI/E2E tests and how many. Follow it as given — do not re-derive the decision from the diff or from \`uiContext\`. When it says UI/E2E generation is REQUIRED, use \`browser_navigate\` to the app's base URL, record a trace, and generate the test. (\`uiContext.changedFrontendFiles\` — the deterministic server signal, populated for all supported frontend file types including \`.tsx\`/\`.jsx\`/\`.vue\`/\`.svelte\`/\`.dart\` — tells you the PR touched the frontend, so a UI candidate belongs in the list you *register*. It never obliges you to generate.)
415
423
  **Flutter web apps:** Skyramp's Playwright tools automatically enable Flutter's accessibility semantics tree on every \`browser_navigate\` call — you do NOT need to manually click \`flt-semantics-placeholder\` or add any activation step to the trace. Do NOT log an \`issuesFound\` entry about Flutter canvas rendering or accessibility activation — this is handled transparently. **Do NOT skip test generation or abstain from recording based on what you see in the Flutter source code** (e.g. \`SemanticsBinding.ensureSemantics()\` commented out, \`IS_TESTING\` flag absent, or similar) — Skyramp enables accessibility from the browser side regardless of the app's Dart code. Proceed with \`browser_navigate\` and test recording as normal. **Start at the app's root URL** (e.g. \`{baseUrl}/\`) — do NOT \`browser_navigate\` straight to a deep sub-route (e.g. \`/authors\`, \`/orders/13\`). Flutter \`go_router\` SPAs route from the root: deep-linking on a cold page load often fails to render the expected screen (the route's widgets never mount, so the trace captures the wrong page). Load the root, let the app's own routing/auth-redirect render, then reach target screens by interaction. **After the initial login, navigate using in-app controls only** (tab buttons, links, back buttons) — do NOT call \`browser_navigate\` to a different URL after login. Flutter web apps are SPAs: a \`browser_navigate\` to a new URL after login triggers a full page reload which clears the auth session, causing redundant re-login cycles in the generated test. Use button clicks to reach target screens instead.
416
- **Skip only if one of these conditions is met:**
417
- - **(a) App is unreachable** — \`browser_navigate\` fails or connection is refused.
424
+ **When the directive requires UI/E2E generation, skip only if one of these runtime conditions is met** (the directive already settles allocation; these are the two things the server cannot know ahead of time):
425
+ - **(a) App is unreachable** — \`browser_navigate\` fails or connection is refused. This is an environment failure, NOT a decision: you were required to generate and could not. Record it in \`issuesFound\`, move the intended UI test to \`additionalRecommendations\` with the failure reason, and say plainly in the report that the test could not be recorded because the app was down, so the empty \`newTestsCreated\` is not mistaken for a deliberate no-test verdict.
418
426
  - **(b) Unintegrated non-route component** — the changed file is a leaf component (not a framework route/entrypoint) that has no integration point in the running app. **The server already computes this** — check \`uiContext.frontendFileIntegration\` in the \`skyramp_analyze_changes\` output: if it marks the changed file \`integrated: false\`, treat the component as unintegrated WITHOUT re-running the grep below (the tool output's accompanying instruction block already tells you what to do — do not substitute another page or trace). Only fall back to the manual grep procedure when \`frontendFileIntegration\` is absent (older MCP versions) or doesn't cover the changed file:
419
427
  1. Grep for the component's exported name AND its module path/filename across all production source files (excluding \`*.test.*\`, \`*.spec.*\`, \`*.stories.*\`, \`__tests__/\` directories — only production code imports count).
420
428
  2. If no production file imports, re-exports, or renders it, the component has no DOM node in the running app → unintegrated.
@@ -464,18 +472,20 @@ ${CONTRACT_MODE_GUIDANCE}
464
472
  If NO relevant trace exists, **you MUST write out your full trace plan as text BEFORE calling \`browser_navigate\`**. Do not touch the browser until the plan is written.
465
473
 
466
474
  **Browser authentication (check BEFORE navigating)**: If \`<ui-credentials>\` appears in your context above, the app requires login. Parse the credentials — one per line, two supported formats:
467
- - New format: \`username=<value>;password=<value>\` or \`username=<value>;password=<value>;role=<value>\` — fields are \`;\`-delimited key=value pairs. The \`=\` and \`;\` characters are reserved delimiters and must not appear in the values themselves.
475
+ - New format: \`;\`-delimited \`key=value\` pairs, e.g. \`username=<value>;password=<value>\` or \`username=<value>;password=<value>;tenantId=<value>\`. \`username\` and \`password\` are the standard keys; \`role=<value>\` (optional) selects among multiple credentials; **any other key is the value for an additional login-form field** (e.g. \`tenantId\`, \`companyCode\`, \`domain\`), matched to the form field by its name/label/placeholder.
476
+ - JSON format: a line may instead be a JSON object, e.g. \`{"username":"u","password":"p;=x","tenantId":"1"}\` — preferred when a value itself contains \`=\` or \`;\`.
468
477
  - Legacy format: \`username:password\` — the first \`:\` splits username from password.
478
+ These are format hints, not a strict grammar — apply judgment on ambiguous input (e.g. a \`=\` or \`;\` inside a value of the key=value form: split on the \`;\` that precedes a plausible login-field key).
469
479
 
470
480
  **Credential selection**: Use the first credential by default. When the scenario requires a specific role, find the credential whose \`role\` field matches (e.g. \`role=admin\`). If no credential matches the required role, use the first credential and add a note to \`issuesFound\` that no matching role was found.
471
481
 
472
482
  Type all values verbatim. Before navigating to ANY feature URL:
473
483
  1. \`browser_navigate\` to the login URL (e.g. \`{baseUrl}/login\`, \`/user/login\`, \`/signin\` — infer from the app's base URL and framework)
474
- 2. \`browser_snapshot\` to find the username/email and password fields
484
+ 2. \`browser_snapshot\` and enumerate every **visible, user-editable** input field in the login form — not just username/password. Match each field to a credential key by its name/label/placeholder (e.g. a tenant-ID field ↔ \`tenantId=<value>\`) BEFORE clicking anything.
475
485
  3. \`browser_type\` the username into the email/username field
476
486
  4. \`browser_type\` the password into the password field
477
- 5. If a role selector is present and a \`role\` was specified in the credential, select it before submitting
478
- 6. \`browser_click\` the submit button, then \`browser_wait_for\` redirect away from the login page
487
+ 5. \`browser_type\` each remaining form field's value from its matching credential key. If a role selector is present and a \`role\` was specified in the credential, select it before submitting.
488
+ 6. Only once **every visible required field is filled**: \`browser_click\` the submit button, then \`browser_wait_for\` redirect away from the login page. NEVER click submit with a required field still empty to "see what happens" — the recorder is a faithful capture, so a failed submit attempt gets baked into the trace and replayed by the generated test. If a visible **required** field has no matching credential key, do NOT submit: skip recording this flow and add an \`issuesFound\` entry naming the form field and the \`key=value\` pair the customer must add to the \`uiCredentials\` input in their workflow file. Optional fields with no matching key are simply left empty — they are never a reason to abort.
479
489
  7. Now navigate directly to the feature URL and begin recording
480
490
  The login steps ARE part of the trace — the generated test will authenticate automatically.
481
491
 
@@ -508,7 +518,7 @@ ${CONTRACT_MODE_GUIDANCE}
508
518
  - **\`browser_assert\` — MANDATORY**: at least one per page navigated. Call multiple assertions in the same tool call batch when checking independent elements. If you navigate to 2 pages, assert on both. Each assertion should verify a business outcome (state change, computed value, error condition) — not just that an element is visible.
509
519
  - **\`browser_visual_snapshot\` — for visual/appearance checks**: when the instruction asks to take a screenshot, capture a baseline, or verify how a page/element/region *looks* (not its text or value), call \`browser_visual_snapshot\` — it records a \`toHaveScreenshot()\` assertion so the generated test pixel-compares against a baseline on every run. Do NOT use \`browser_take_screenshot\` for this: it captures a throwaway image that is dropped at export and never appears in the generated test (use it only to view the page yourself).
510
520
  - **Wait for stable state before the second capture**: After performing an action that affects computed fields (filling a discount, submitting a form, adding an item), check the current page state before calling the second \`browser_blueprint\` (the capture after the action). If a computed field — total, price, count, derived text — still shows its initial empty or zero value (e.g. \`$0.00\`, \`0\`, \`Loading...\`, empty string), that means async data hasn't finished loading yet. Use \`browser_wait_for\` to wait up to 10 seconds for the field to update to a real value (for example, wait for the total to show a non-zero amount like \`$799.99\` instead of \`$0.00\`). Once the field shows a real value, THEN call the second \`browser_blueprint\` to capture stable state. If after 10 seconds the field still hasn't updated, skip the assertion on that field — don't capture and assert a value that hasn't loaded.
511
- If \`browser_navigate\` fails (app not running / connection refused), move to \`additionalRecommendations\` with the failure reason.
521
+ If \`browser_navigate\` fails (app not running / connection refused), apply skip condition (a) above: move the intended test to \`additionalRecommendations\` with the failure reason AND record the outage in \`issuesFound\`.
512
522
  Record at most 2-3 UI traces per run to stay within tool call budget. Quality over quantity: 1 great test is better than 3 mediocre ones — do not pad to reach the count.
513
523
  **Strategic assertions** — key checkpoints only, 3 to 5 per test:
514
524
  - **After the main action completes**: verify the outcome is visible (new item appears, form saves, confirmation shows)
@@ -522,7 +532,7 @@ ${CONTRACT_MODE_GUIDANCE}
522
532
 
523
533
  **Skip this entire section if \`uiContext\` was absent or \`changedFrontendFiles\` was empty in the \`skyramp_analyze_changes\` response** (backend-only PR). The capture-act-capture pattern is for UI trace recording only — there's no UI trace to record on a backend-only PR. Continue to the non-UI test-type instructions below.
524
534
 
525
- **Reminder — the UI test priority rule above still applies.** If the diff contains frontend/UI changes, you still MUST attempt to generate at least one UI test. Capture-act-capture is **how** you record that test, not **whether** you record one — do not substitute UI recommendations for actually recording a trace. UI recommendation reasoning was already grounded in the blueprints you captured from the UI Blueprint Capture section of \`skyramp_analyze_changes\`; Task 2's capture-act-capture is for the trace's own assertions, not for retroactively rewriting recommendation reasoning.
535
+ **Reminder — the UI test priority rule above still applies.** If the plan's Generation directive requires UI/E2E generation, you still MUST attempt to record a trace. Capture-act-capture is **how** you record that test, not **whether** you record one — do not substitute UI recommendations for actually recording a trace. (If the directive allocates no UI/E2E generation, there is nothing to record here — that is the plan's decision, not a shortcut.) UI recommendation reasoning was already grounded in the blueprints you captured from the UI Blueprint Capture section of \`skyramp_analyze_changes\`; Task 2's capture-act-capture is for the trace's own assertions, not for retroactively rewriting recommendation reasoning.
526
536
 
527
537
  This pattern produces delta-derived assertions from blueprint diffs. Diff-derived assertions catch state changes more reliably than author-inference — the diff tells you what actually changed on the page so the assertion is grounded in observable state, not in guessing what "success" looks like.
528
538
 
@@ -589,12 +599,14 @@ Do NOT use \`page.waitForTimeout()\` with fixed delays. Do NOT retry more than o
589
599
  **After generation, you MUST do exactly these steps — nothing more, nothing less:**
590
600
  1. **[MANDATORY] After \`skyramp_integration_test_generation\`**: Call \`skyramp_enhance_assertions\` with \`testFile\` set to the absolute path of the generated integration test file, \`testType: "integration"\`, and \`enhanceType: "generation"\`. Apply every instruction returned to that file.
591
601
  2. **[MANDATORY] After \`skyramp_contract_test_generation\` with \`providerMode\`**: Call \`skyramp_enhance_assertions\` with \`testFile\` set to the absolute path of the generated provider contract test file, \`testType: "contract"\`, and \`enhanceType: "generation"\`. Apply every instruction returned to that file.
592
- 3. **[MANDATORY] After \`skyramp_ui_test_generation\` with \`codeReuse: true\`**: the generation result directs you to call \`skyramp_reuse_code\` — do this BEFORE \`skyramp_enhance_assertions\`. Two outcomes: (1) a response starting "No reusable POM layer detected" — this is a normal outcome, continue immediately (do NOT retry), and note "no POM layer — reuse skipped" in that test's \`testResults\` entry \`details\`; (2) a refactoring workflow — follow it to completion INCLUDING its verification loop (\`skyramp_reuse_code\` with \`verify: true\`), finish only when it reports PASSED, and include \`verification: passed\` in that test's \`testResults\` entry \`details\` in your final report. If a reused test later fails execution and the failure points at a substituted POM call, restore the saved \`<testFile>.raw.bak\` over the test file and re-run — do NOT hand-edit the customer's POM methods (this re-run counts toward the 2-attempt execution cap — prefer this restore over the generic timeout fix-up when the failing locator came from a POM substitution).
602
+ ${UI_CODE_REUSE_STEP}
593
603
  4. **[MANDATORY] After \`skyramp_ui_test_generation\`**: Call \`skyramp_enhance_assertions\` with \`testFile\` set to the absolute path of the generated UI test file, \`testType: "ui"\`, and \`enhanceType: "generation"\`. Apply every instruction returned to that file. The HIGH-tier \`possibleAssertions\` from your second \`browser_blueprint\` captures (after each action) during trace recording are in your context — when the enhance instructions ask you to add assertions for state-changing actions, use those grounded candidates first (they contain exact computed values from the DOM delta, e.g. \`toHaveText('Total: $899.98')\`). Only fall back to deriving values from the test file or source code when no HIGH-tier candidate covers the action.
594
604
  5. **Wait**: Do NOT proceed to test execution until steps 1–4 are complete and the verification checklist in the \`skyramp_enhance_assertions\` tool result has been validated for EVERY generated test file.
595
605
  Do not make any changes other than the code-reuse refactoring (step 3) and the assertion enhancements described above. For example: do not modify auth headers, cookies, tokens, env vars, or imports that the generation tool already set correctly — those are correct by construction and changing them breaks auth or execution.
596
606
 
597
- **Final execution (mandatory):** Do NOT call \`skyramp_execute_test\` until ALL maintenance edits AND ALL new test generation/enhancement are complete. Run these calls sequentially, not in parallel. Exclude tests marked \`[external]\`.
607
+ **Execution timing:**
608
+ - **beforeStatus** (maintained tests only): execute each maintained test file **once at the start** (before any edits) to capture \`beforeStatus\`. This is the only execution allowed before edits.
609
+ - **Final execution**: Do NOT call \`skyramp_execute_test\` again until ALL maintenance edits AND ALL new test generation/enhancement are complete. Then execute every test file once — maintained files (for \`afterStatus\`) and new files together. **Execute tests SEQUENTIALLY (one at a time)** — do NOT send multiple \`skyramp_execute_test\` calls in the same tool call batch, as concurrent execution overwhelms the stdio transport and causes MCP disconnection. Exclude tests marked \`[external]\`.
598
610
  - Only report test results for files you actually ran.
599
611
  **Auth**: If \`skyramp_analyze_changes\` reports an auth token or \`SKYRAMP_TEST_TOKEN\` is set, pass it in **every** \`skyramp_execute_test\` call from the first attempt — do NOT wait for a 401/403 to discover auth is needed.`;
600
612
  }
@@ -647,38 +659,6 @@ ${getTraceRecordingPromptText({ outputDir: `${repositoryPath}/.skyramp`, modular
647
659
  // removing stateOutputFile from the prompt schema. Remove the RUNNER_TEMP branch in
648
660
  // AnalysisStateManager.ts when this is done.
649
661
  }
650
- function escapeXml(value) {
651
- return value
652
- .replaceAll('&', '&amp;')
653
- .replaceAll('<', '&lt;')
654
- .replaceAll('>', '&gt;')
655
- .replaceAll('"', '&quot;')
656
- .replaceAll("'", '&apos;');
657
- }
658
- function buildServiceContext(services) {
659
- const blocks = services.map(svc => {
660
- const parts = [`<service name="${escapeXml(svc.serviceName)}">`];
661
- if (svc.language)
662
- parts.push(` <language>${escapeXml(svc.language)}</language>`);
663
- if (svc.framework)
664
- parts.push(` <framework>${escapeXml(svc.framework)}</framework>`);
665
- if (svc.api?.baseUrl)
666
- parts.push(` <base_url>${escapeXml(svc.api.baseUrl)}</base_url>`);
667
- if (svc.testDirectory)
668
- parts.push(` <test_directory>${escapeXml(svc.testDirectory)}</test_directory>`);
669
- parts.push('</service>');
670
- return parts.join('\n');
671
- });
672
- return `<services>\n${blocks.join('\n')}\n</services>`;
673
- }
674
- /**
675
- * Read services from .skyramp/workspace.yml. Returns empty array if
676
- * the workspace file doesn't exist or can't be parsed.
677
- */
678
- export async function readWorkspaceServices(repositoryPath) {
679
- const rawConfig = await readWorkspaceConfigRaw(repositoryPath);
680
- return (rawConfig?.services ?? []);
681
- }
682
662
  export function buildWorkspaceRecoveryPrefix(repositoryPath) {
683
663
  return `IMPORTANT: The existing .skyramp/workspace.yml failed to parse or validate. Before proceeding with any tasks below, you MUST call skyramp_init_scan with workspacePath "${repositoryPath}" and force: true, then call skyramp_init_workspace with workspacePath "${repositoryPath}", the discovered services, scanToken, and force: true to regenerate the workspace file.\n\n`;
684
664
  }
@@ -723,7 +703,7 @@ export function registerTestbotPrompt(server) {
723
703
  uiCredentials: z
724
704
  .string()
725
705
  .optional()
726
- .describe("Browser login credentials for UI test recording. One credential per line. Supported formats: 'username=<val>;password=<val>' or 'username=<val>;password=<val>;role=<val>' (role optional), or legacy 'username:password'. Note: = and ; are reserved delimiters in the new format and must not appear in values. Injected into the prompt as a <ui-credentials> block so the agent logs in before recording traces."),
706
+ .describe("Browser login credentials for UI test recording. One credential per line. Supported formats: ';'-delimited key=value pairs — 'username=<val>;password=<val>' plus any additional login-form fields the app needs (e.g. ';tenantId=<val>') and optional ';role=<val>' for credential selection — a JSON object per line (preferred when a value contains = or ;), or legacy 'username:password'. The string is passed to the agent opaquely; formats are hints the agent interprets, not a parsed grammar. Injected into the prompt as a <ui-credentials> block so the agent fills every login-form field and logs in before recording traces."),
727
707
  workspaceValidationFailed: z
728
708
  .boolean()
729
709
  .default(false)
@@ -9,12 +9,22 @@ export interface RankOptions {
9
9
  * the agent's priority tag as a ranking input.
10
10
  */
11
11
  carveOutCategories?: ScenarioCategory[];
12
+ /**
13
+ * Raw PR diff text. When supplied, a candidate whose scenario references a
14
+ * method+path that appears on an actual changed diff line (`+`/`-`) floats
15
+ * ahead of a same-tier candidate that doesn't — e.g. the one endpoint really
16
+ * removed by a PR outranks other same-category "verify-removed-*" candidates
17
+ * for endpoints merely adjacent in the same file (SKYR-4026).
18
+ */
19
+ diffText?: string;
12
20
  }
13
21
  /** Context for {@link selectPlan}: the budget context plus the demotion channel
14
22
  * the register-plan tool fills from discriminator verification. */
15
23
  export interface SelectPlanContext extends BudgetContext {
16
24
  /** Demoted claims (failed discriminator verification) to surface on the result. */
17
25
  demotions?: Demotion[];
26
+ /** Threaded into {@link rankCandidates} as `RankOptions.diffText`. */
27
+ diffText?: string;
18
28
  }
19
29
  /**
20
30
  * Rank test candidates for the register-plan checkpoint. Pure and fully
@@ -28,9 +38,12 @@ export interface SelectPlanContext extends BudgetContext {
28
38
  * 2. Verified discriminators — candidates whose declared discriminator survived
29
39
  * `validateDiscriminator` (marked via `verifiedDiscriminator`) float ahead of
30
40
  * unverified peers in the same tier.
31
- * 3. `CATEGORY_PRIORITY` mapped over `scenario.category` (CRITICAL > HIGH >
41
+ * 3. Diff-hunk proximity — candidates referencing a method+path that appears
42
+ * on an actual changed diff line float ahead of same-tier candidates that
43
+ * don't (SKYR-4026). Only applied when `opts.diffText` is supplied.
44
+ * 4. `CATEGORY_PRIORITY` mapped over `scenario.category` (CRITICAL > HIGH >
32
45
  * MEDIUM > LOW).
33
- * 4. Stable tiebreak on `candidateId` for determinism.
46
+ * 5. Stable tiebreak on `candidateId` for determinism.
34
47
  *
35
48
  * IMPORTANT: the agent-supplied `priority` field (`scenario.priority`) is
36
49
  * DELIBERATELY IGNORED here. Round-1 experiments found it systematically
@@ -1,5 +1,6 @@
1
1
  import { CATEGORY_PRIORITY, PriorityTier } from "../types/TestRecommendation.js";
2
2
  import { diversityBalancedBudgeter } from "./budgeters/diversityBalancedBudgeter.js";
3
+ import { parseRouteLine, normalizeDiffPath } from "../utils/routeParsers.js";
3
4
  const PRIORITY_RANK = {
4
5
  CRITICAL: 0,
5
6
  HIGH: 1,
@@ -19,9 +20,12 @@ const DEFAULT_CARVE_OUT_CATEGORIES = Object.keys(CATEGORY_PRIORITY).filter((cate
19
20
  * 2. Verified discriminators — candidates whose declared discriminator survived
20
21
  * `validateDiscriminator` (marked via `verifiedDiscriminator`) float ahead of
21
22
  * unverified peers in the same tier.
22
- * 3. `CATEGORY_PRIORITY` mapped over `scenario.category` (CRITICAL > HIGH >
23
+ * 3. Diff-hunk proximity — candidates referencing a method+path that appears
24
+ * on an actual changed diff line float ahead of same-tier candidates that
25
+ * don't (SKYR-4026). Only applied when `opts.diffText` is supplied.
26
+ * 4. `CATEGORY_PRIORITY` mapped over `scenario.category` (CRITICAL > HIGH >
23
27
  * MEDIUM > LOW).
24
- * 4. Stable tiebreak on `candidateId` for determinism.
28
+ * 5. Stable tiebreak on `candidateId` for determinism.
25
29
  *
26
30
  * IMPORTANT: the agent-supplied `priority` field (`scenario.priority`) is
27
31
  * DELIBERATELY IGNORED here. Round-1 experiments found it systematically
@@ -32,7 +36,11 @@ const DEFAULT_CARVE_OUT_CATEGORIES = Object.keys(CATEGORY_PRIORITY).filter((cate
32
36
  */
33
37
  export function rankCandidates(candidates, opts = {}) {
34
38
  const carveOutSet = new Set(opts.carveOutCategories ?? DEFAULT_CARVE_OUT_CATEGORIES);
35
- return [...candidates].sort((a, b) => compareRank(a, b, carveOutSet));
39
+ const changedRoutes = opts.diffText ? collectChangedRouteLines(opts.diffText) : [];
40
+ const onHunk = new Set(candidates
41
+ .filter((c) => scenarioOnChangedHunk(c.scenario, changedRoutes))
42
+ .map((c) => c.candidateId));
43
+ return [...candidates].sort((a, b) => compareRank(a, b, carveOutSet, onHunk));
36
44
  }
37
45
  /**
38
46
  * Convenience composition: rank the candidates, split GENERATE vs ADDITIONAL via
@@ -42,11 +50,11 @@ export function rankCandidates(candidates, opts = {}) {
42
50
  * passing the failed claims through `ctx.demotions`.
43
51
  */
44
52
  export function selectPlan(candidates, ctx, opts = {}) {
45
- const ranked = rankCandidates(candidates, opts);
53
+ const ranked = rankCandidates(candidates, { ...opts, diffText: opts.diffText ?? ctx.diffText });
46
54
  const result = diversityBalancedBudgeter.select(ranked, ctx);
47
55
  return { ...result, demotions: ctx.demotions ?? [] };
48
56
  }
49
- function compareRank(a, b, carveOutSet) {
57
+ function compareRank(a, b, carveOutSet, onHunk) {
50
58
  const carveA = carveOutSet.has(a.scenario?.category) ? 0 : 1;
51
59
  const carveB = carveOutSet.has(b.scenario?.category) ? 0 : 1;
52
60
  if (carveA !== carveB)
@@ -55,6 +63,10 @@ function compareRank(a, b, carveOutSet) {
55
63
  const verifiedB = b.verifiedDiscriminator ? 0 : 1;
56
64
  if (verifiedA !== verifiedB)
57
65
  return verifiedA - verifiedB;
66
+ const hunkA = onHunk.has(a.candidateId) ? 0 : 1;
67
+ const hunkB = onHunk.has(b.candidateId) ? 0 : 1;
68
+ if (hunkA !== hunkB)
69
+ return hunkA - hunkB;
58
70
  const catRankA = categoryRank(a);
59
71
  const catRankB = categoryRank(b);
60
72
  if (catRankA !== catRankB)
@@ -65,3 +77,62 @@ function categoryRank(candidate) {
65
77
  const tier = CATEGORY_PRIORITY[candidate.scenario?.category] ?? PriorityTier.LOW;
66
78
  return PRIORITY_RANK[tier];
67
79
  }
80
+ /**
81
+ * Extract method+path from every changed (`+`/`-`, non-header) line of a raw
82
+ * unified diff. Reuses `parseRouteLine`, which already strips the leading
83
+ * `+`/`-` marker and matches the same route-decorator patterns the endpoint
84
+ * scanner does.
85
+ */
86
+ function collectChangedRouteLines(diffText) {
87
+ const routes = [];
88
+ let currentFile = "";
89
+ for (const line of diffText.split("\n")) {
90
+ // Track the current file from the unified-diff header so parseRouteLine
91
+ // gets the real path — its UI-component guard (UI_COMPONENT_EXT) depends
92
+ // on it, or a route-shaped line inside a .tsx/.jsx file (e.g. a client
93
+ // router registration) gets misparsed as a changed backend route.
94
+ if (line.startsWith("+++ ")) {
95
+ const spec = line.slice(4).trim().split("\t")[0];
96
+ currentFile = spec === "/dev/null" ? "" : normalizeDiffPath(spec);
97
+ continue;
98
+ }
99
+ if (line.startsWith("--- ") || line.startsWith("diff --git") || line.startsWith("index "))
100
+ continue;
101
+ if (!(line.startsWith("+") || line.startsWith("-")))
102
+ continue;
103
+ const parsed = parseRouteLine(line, currentFile);
104
+ if (parsed)
105
+ routes.push({ method: parsed.method, path: parsed.path });
106
+ }
107
+ return routes;
108
+ }
109
+ /**
110
+ * Whether any step of `scenario` targets a method+path on the changed hunk.
111
+ * Diff-extracted paths are local to the file's own router declaration (e.g.
112
+ * "/suggestions"); scenario step paths are fully mounted (e.g.
113
+ * "/api/recipes/suggestions") once drafted from a scanned/recovered endpoint.
114
+ * A local path matches when the step path ends with it, so the cross-file
115
+ * mount prefix difference (see recoverRemovedEndpointsFromBase, SKYR-4026)
116
+ * doesn't prevent the match.
117
+ */
118
+ function scenarioOnChangedHunk(scenario, changedRoutes) {
119
+ if (changedRoutes.length === 0)
120
+ return false;
121
+ for (const step of scenario.steps ?? []) {
122
+ const stepMethod = (step.method ?? "").toUpperCase();
123
+ const stepPath = (step.path ?? "").replace(/\/+$/, "");
124
+ for (const route of changedRoutes) {
125
+ if (route.method.toUpperCase() !== stepMethod)
126
+ continue;
127
+ const routePath = route.path.replace(/\/+$/, "");
128
+ if (routePath === "") {
129
+ if (stepPath === "" || stepPath === "/")
130
+ return true;
131
+ continue;
132
+ }
133
+ if (stepPath === routePath || stepPath.endsWith(routePath))
134
+ return true;
135
+ }
136
+ }
137
+ return false;
138
+ }
@@ -2,7 +2,8 @@ import { ResourceTemplate, } from "@modelcontextprotocol/sdk/server/mcp.js";
2
2
  import { logger } from "../utils/logger.js";
3
3
  import { AnalyticsService } from "../services/AnalyticsService.js";
4
4
  import { MAX_TESTS_TO_GENERATE, MAX_RECOMMENDATIONS, MAX_CRITICAL_TESTS, } from "../prompts/test-recommendation/recommendationSections.js";
5
- import { getTestbotPrompt, readWorkspaceServices, parseRelatedRepositories, } from "../prompts/testbot/testbot-prompts.js";
5
+ import { getTestbotPrompt, parseRelatedRepositories, } from "../prompts/testbot/testbot-prompts.js";
6
+ import { readWorkspaceServices } from "../prompts/prompt-utils.js";
6
7
  export function registerTestbotResource(server) {
7
8
  logger.info("Registering testbot resource");
8
9
  // RFC 6570 {+rest} (reserved expansion) captures the entire query string
@@ -1,7 +1,7 @@
1
1
  import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
2
2
  export declare class AnalyticsService {
3
3
  static pushTestGenerationToolEvent(toolName: string, result: CallToolResult, params: Record<string, any>): Promise<void>;
4
- static pushMCPToolEvent(toolName: string, result: CallToolResult | undefined, params: Record<string, string>): Promise<void>;
4
+ static pushMCPToolEvent(toolName: string, result: CallToolResult | undefined, params: Record<string, any>): Promise<void>;
5
5
  /**
6
6
  * Track server crash events
7
7
  */
@@ -1,5 +1,6 @@
1
1
  import { TestExecutionResult, BatchExecutionResult, TestExecutionOptions, ProgressCallback } from "../types/TestExecution.js";
2
- export declare const EXECUTOR_DOCKER_IMAGE = "skyramp/executor:v1.3.36";
2
+ import { EXECUTOR_DOCKER_IMAGE } from "../utils/versions.js";
3
+ export { EXECUTOR_DOCKER_IMAGE };
3
4
  export declare const PLAYWRIGHT_CONFIG_FILES: string[];
4
5
  export declare const EXCLUDED_MOUNT_ITEMS: string[];
5
6
  export declare const TEST_REPO_CONTAINER_SUBDIR = ".skyramp-test-repo";
@@ -9,11 +9,11 @@ import { logger } from "../utils/logger.js";
9
9
  import { TestExecutionStatus, } from "../types/TestExecution.js";
10
10
  import { TestType } from "../types/TestTypes.js";
11
11
  import { buildContainerEnv } from "./containerEnv.js";
12
- import { SKYRAMP_IMAGE_VERSION } from "../utils/versions.js";
12
+ import { EXECUTOR_DOCKER_IMAGE } from "../utils/versions.js";
13
13
  import { walkDir } from "../utils/fileWalk.js";
14
+ export { EXECUTOR_DOCKER_IMAGE };
14
15
  const DEFAULT_TIMEOUT = 300000; // 5 minutes
15
16
  const MAX_CONCURRENT_EXECUTIONS = 5;
16
- export const EXECUTOR_DOCKER_IMAGE = `skyramp/executor:${SKYRAMP_IMAGE_VERSION}`;
17
17
  const DOCKER_PLATFORM = "linux/amd64";
18
18
  const EXECUTION_PROGRESS_INTERVAL = 10000; // 10 seconds between progress updates during execution
19
19
  // Temp file with valid empty JSON — used instead of /dev/null for .json config files
@@ -565,10 +565,15 @@ export class TestExecutionService {
565
565
  testFilePath,
566
566
  options.testType,
567
567
  ];
568
+ const networkMode = options.dockerNetwork?.trim()
569
+ ? options.dockerNetwork.trim()
570
+ : options.useHostNetwork && process.platform === "linux"
571
+ ? "host"
572
+ : undefined;
568
573
  // Prepare host config with mounts
569
574
  const hostConfig = {
570
575
  ExtraHosts: ["host.docker.internal:host-gateway"],
571
- ...(options.useHostNetwork && process.platform === "linux" ? { NetworkMode: "host" } : {}),
576
+ ...(networkMode ? { NetworkMode: networkMode } : {}),
572
577
  Mounts: [
573
578
  {
574
579
  Type: "bind",
@@ -1,6 +1,6 @@
1
1
  import { SkyrampClient } from "@skyramp/skyramp";
2
2
  import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
3
- import { TestType } from "../types/TestTypes.js";
3
+ import { TestType, MOCK_TYPE } from "../types/TestTypes.js";
4
4
  export interface BaseTestParams {
5
5
  endpointURL?: string;
6
6
  method?: string;
@@ -50,7 +50,7 @@ export declare abstract class TestGenerationService {
50
50
  generateTest(params: BaseTestParams & Record<string, any>): Promise<CallToolResult>;
51
51
  protected validateInputs(params: BaseTestParams): CallToolResult;
52
52
  protected abstract buildGenerationOptions(params: BaseTestParams & Record<string, any>): any;
53
- protected abstract getTestType(): TestType;
53
+ protected abstract getTestType(): TestType | typeof MOCK_TYPE;
54
54
  protected handleApiAnalysis(params: BaseTestParams): Promise<CallToolResult | null>;
55
55
  private static readonly STANDARD_HEADERS;
56
56
  private static simpleWildcardMatch;
@@ -2,10 +2,11 @@ import path from "path";
2
2
  import fs from "fs";
3
3
  import { SkyrampClient } from "@skyramp/skyramp";
4
4
  import { analyzeOpenAPIWithGivenEndpoint } from "../utils/analyze-openapi.js";
5
- import { isAuthorizationHeaderName, KNOWN_AUTH_HEADERS, getWorkspaceAuthConfig, getDefaultAuthHeader, getAuthScheme, WorkspaceAuthType, getWorkspaceSkipTLSVerify, } from "../utils/workspaceAuth.js";
5
+ import { isAuthorizationHeaderName, KNOWN_AUTH_HEADERS, resolveAuthFromWorkspace, getWorkspaceSkipTLSVerify, getWorkspaceDefaultQueryParams, mergeQueryParamsString, } from "../utils/workspaceAuth.js";
6
6
  import { getPathParameterValidationError, OUTPUT_DIR_FIELD_NAME, PATH_PARAMS_FIELD_NAME, QUERY_PARAMS_FIELD_NAME, FORM_PARAMS_FIELD_NAME, validateParams, validatePath, validateRequestData, } from "../utils/utils.js";
7
7
  import { getEntryPoint } from "../utils/telemetry.js";
8
8
  import { getLanguageSteps } from "../utils/language-helper.js";
9
+ import { TestType } from "../types/TestTypes.js";
9
10
  import { logger } from "../utils/logger.js";
10
11
  import { normalizeLanguageParams } from "../utils/normalizeParams.js";
11
12
  import { stageGeneratedPaths, resolveOutputDir } from "../utils/gitStaging.js";
@@ -24,6 +25,19 @@ export function deriveEffectiveFramework(language, framework) {
24
25
  const lang = (language ?? "").trim().toLowerCase();
25
26
  return lang === "typescript" || lang === "javascript" ? "playwright" : "";
26
27
  }
28
+ /**
29
+ * Test types whose generation is driven by a recorded Playwright trace, not a
30
+ * live query-string request the same way contract/fuzz/smoke/load/integration
31
+ * are. Their schemas (uiTestSchema, e2eTestSchema) don't expose a queryParams
32
+ * field at all, so there's no way for a caller to override or even know this
33
+ * field exists — injecting an unrequested, uneditable workspace
34
+ * defaultQueryParams value into that path is not what the design intends
35
+ * (SKYR-4050).
36
+ */
37
+ const SKIP_DEFAULT_QUERY_PARAMS_TEST_TYPES = new Set([
38
+ TestType.UI,
39
+ TestType.E2E,
40
+ ]);
27
41
  export class TestGenerationService {
28
42
  client;
29
43
  constructor() {
@@ -316,29 +330,15 @@ The generated test file remains unchanged and ready to use as-is.
316
330
  async executeGeneration(generateOptions) {
317
331
  try {
318
332
  // Auto-resolve auth from workspace config when authHeader was not explicitly provided.
319
- // undefined = omit (auto-resolve); "" = explicit skip-auth for unauthenticated endpoints.
333
+ // undefined = auto-resolve; "" = caller explicitly said skip-auth.
320
334
  if (generateOptions.authHeader === undefined) {
321
335
  try {
322
336
  const repoPath = generateOptions.outputDir || process.cwd();
323
- const wsAuth = await getWorkspaceAuthConfig(repoPath);
324
- let resolvedHeader = wsAuth.authHeader ?? getDefaultAuthHeader(wsAuth.authType);
325
- if (!resolvedHeader && wsAuth.authScheme !== undefined) {
326
- resolvedHeader = "Authorization";
327
- }
328
- if (wsAuth.authType === WorkspaceAuthType.ApiKey && !resolvedHeader) {
329
- logger.warning("workspace.yml has authType: apiKey but no authHeader is set — " +
330
- "requests will be sent without an auth header and will likely receive 401/403. " +
331
- "Add authHeader: <X-Your-Key-Header> to .skyramp/workspace.yml.");
332
- }
333
- if (resolvedHeader && wsAuth.authType !== WorkspaceAuthType.None) {
334
- logger.info("authHeader not provided — resolved from workspace config", {
335
- authHeader: resolvedHeader,
336
- authType: wsAuth.authType,
337
- });
338
- generateOptions.authHeader = resolvedHeader;
339
- if (isAuthorizationHeaderName(resolvedHeader)) {
340
- generateOptions.authScheme =
341
- generateOptions.authScheme ?? wsAuth.authScheme ?? getAuthScheme(wsAuth.authType);
337
+ const resolved = await resolveAuthFromWorkspace(repoPath, generateOptions.authHeader, generateOptions.authScheme);
338
+ if (resolved) {
339
+ generateOptions.authHeader = resolved.authHeader;
340
+ if (resolved.authScheme !== undefined) {
341
+ generateOptions.authScheme = resolved.authScheme;
342
342
  }
343
343
  }
344
344
  }
@@ -364,6 +364,24 @@ The generated test file remains unchanged and ready to use as-is.
364
364
  logger.warning("Could not resolve skipTLSVerify from workspace config");
365
365
  }
366
366
  }
367
+ // Workspace-declared default query params (SKYR-4050): api.defaultQueryParams
368
+ // are attached to every generated request for the service. Existing
369
+ // queryParams entries (explicit caller values) always win on a key collision.
370
+ if (!SKIP_DEFAULT_QUERY_PARAMS_TEST_TYPES.has(this.getTestType())) {
371
+ try {
372
+ const repoPath = generateOptions.outputDir || process.cwd();
373
+ const defaults = await getWorkspaceDefaultQueryParams(repoPath);
374
+ if (defaults) {
375
+ generateOptions.queryParams = mergeQueryParamsString(generateOptions.queryParams, defaults);
376
+ logger.info("Merged workspace defaultQueryParams into queryParams", {
377
+ keys: Object.keys(defaults),
378
+ });
379
+ }
380
+ }
381
+ catch {
382
+ logger.warning("Could not resolve default query params from workspace config");
383
+ }
384
+ }
367
385
  if (generateOptions.traceFilePath) {
368
386
  const traceAuth = this.extractAuthFromTrace(generateOptions.traceFilePath, generateOptions.generateInclude, generateOptions.generateExclude);
369
387
  if (traceAuth) {
@@ -14,7 +14,9 @@ export function rewriteLocalhostForDocker(url) {
14
14
  */
15
15
  export function buildContainerEnv(options, saveStoragePath, hostEnv = process.env) {
16
16
  const env = [
17
- `SKYRAMP_TEST_TOKEN=${options.token || ""}`,
17
+ // Omit entirely when empty so os.getenv() returns None in the test —
18
+ // unauthenticated endpoints won't send an empty auth header (E7).
19
+ ...(options.token ? [`SKYRAMP_TEST_TOKEN=${options.token}`] : []),
18
20
  "SKYRAMP_IN_DOCKER=true",
19
21
  ];
20
22
  // Skyramp-generated tests are standalone HTTP tests that never need host repo
@@ -11,6 +11,9 @@ export const TOOL_PHASE_MAP = {
11
11
  skyramp_ui_test_generation: "generating",
12
12
  skyramp_batch_scenario_test_generation: "generating",
13
13
  skyramp_mock_generation: "generating",
14
+ skyramp_batch_mock_generation: "generating",
15
+ skyramp_generate_enriched_integration_test: "generating",
16
+ skyramp_enrich_test_with_mocks: "generating",
14
17
  skyramp_execute_test: { before: "maintaining", after: "executing" },
15
18
  skyramp_run_existing_tests: { before: "maintaining", after: "executing" },
16
19
  skyramp_analyze_test_health: "maintaining",
@@ -31,6 +34,7 @@ export const TOOLS_WITHOUT_PHASE = new Set([
31
34
  "skyramp_init_scan",
32
35
  "skyramp_init_workspace",
33
36
  "skyramp_one_click_tool",
37
+ "skyramp_setup_local_dev_worker",
34
38
  "skyramp_actions",
35
39
  "skyramp_start_trace_collection",
36
40
  "skyramp_stop_trace_collection",
@@ -38,4 +42,6 @@ export const TOOLS_WITHOUT_PHASE = new Set([
38
42
  "skyramp_modularization",
39
43
  "skyramp_reuse_code",
40
44
  "skyramp_enhance_assertions",
45
+ "skyramp_preflight_mock_check",
46
+ "skyramp_query_proxy_mocks",
41
47
  ]);