@skyramp/mcp 0.2.6 → 0.2.8-rc.1

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 (248) hide show
  1. package/build/commands/commandLibrary.d.ts +12 -0
  2. package/build/commands/recommendTestsAndExecuteCommand.d.ts +2 -0
  3. package/build/commands/testThisEndpointCommand.d.ts +2 -0
  4. package/build/index.d.ts +2 -0
  5. package/build/index.js +6 -4
  6. package/build/playwright/PlaywrightTraceService.d.ts +32 -0
  7. package/build/playwright/index.d.ts +3 -0
  8. package/build/playwright/registerPlaywrightTools.d.ts +8 -0
  9. package/build/playwright/registerPlaywrightTools.js +12 -11
  10. package/build/playwright/traceRecordingPrompt.d.ts +13 -0
  11. package/build/playwright/traceRecordingPrompt.js +3 -11
  12. package/build/prompts/code-reuse.d.ts +1 -0
  13. package/build/prompts/enhance-assertions/contractProviderAssertionsPrompt.d.ts +2 -0
  14. package/build/prompts/enhance-assertions/integrationAssertionsPrompt.d.ts +2 -0
  15. package/build/prompts/enhance-assertions/sharedAssertionRules.d.ts +15 -0
  16. package/build/prompts/enhance-assertions/uiAssertionsPrompt.d.ts +2 -0
  17. package/build/prompts/enhance-assertions/uiAssertionsPrompt.js +24 -5
  18. package/build/prompts/fix-error-prompt.d.ts +1 -0
  19. package/build/prompts/initialize-workspace/initializeWorkspacePrompt.d.ts +1 -0
  20. package/build/prompts/modularization/integration-test-modularization.d.ts +1 -0
  21. package/build/prompts/modularization/ui-test-modularization.d.ts +1 -0
  22. package/build/prompts/personas.d.ts +21 -0
  23. package/build/prompts/pom-aware-code-reuse.d.ts +1 -0
  24. package/build/prompts/startTraceCollectionPrompts.d.ts +2 -0
  25. package/build/prompts/sut-setup/modes/adaptWorkflowPrompt.d.ts +2 -0
  26. package/build/prompts/sut-setup/modes/adaptWorkflowPrompt.js +114 -0
  27. package/build/prompts/sut-setup/modes/dockerComposePrompt.d.ts +2 -0
  28. package/build/prompts/sut-setup/modes/dockerComposePrompt.js +292 -0
  29. package/build/prompts/sut-setup/shared.d.ts +39 -0
  30. package/build/prompts/sut-setup/shared.js +132 -0
  31. package/build/prompts/test-maintenance/actionsInstructions.d.ts +35 -0
  32. package/build/prompts/test-maintenance/drift-analysis-prompt.d.ts +4 -0
  33. package/build/prompts/test-maintenance/driftAnalysisSections.d.ts +13 -0
  34. package/build/prompts/test-maintenance/driftAnalysisSections.js +9 -3
  35. package/build/prompts/test-recommendation/analysisOutputPrompt.d.ts +84 -0
  36. package/build/prompts/test-recommendation/analysisOutputPrompt.js +1 -1
  37. package/build/prompts/test-recommendation/analysisOutputPrompt.test.d.ts +1 -0
  38. package/build/prompts/test-recommendation/analysisOutputPrompt.test.js +1 -0
  39. package/build/prompts/test-recommendation/diffExecutionPlan.d.ts +17 -0
  40. package/build/prompts/test-recommendation/diffExecutionPlan.js +83 -18
  41. package/build/prompts/test-recommendation/fullRepoCatalog.d.ts +7 -0
  42. package/build/prompts/test-recommendation/mergeEnrichedScenarios.test.d.ts +1 -0
  43. package/build/prompts/test-recommendation/mergeEnrichedScenarios.test.js +1 -0
  44. package/build/prompts/test-recommendation/promptPlan.d.ts +203 -0
  45. package/build/prompts/test-recommendation/promptPlan.test.d.ts +1 -0
  46. package/build/prompts/test-recommendation/recommendationSections.d.ts +35 -0
  47. package/build/prompts/test-recommendation/recommendationShared.d.ts +31 -0
  48. package/build/prompts/test-recommendation/registerRecommendTestsPrompt.d.ts +7 -0
  49. package/build/prompts/test-recommendation/scopeAssessment.d.ts +55 -0
  50. package/build/prompts/test-recommendation/scopeAssessment.js +2 -2
  51. package/build/prompts/test-recommendation/scopeAssessment.test.d.ts +1 -0
  52. package/build/prompts/test-recommendation/scopeAssessment.test.js +1 -0
  53. package/build/prompts/test-recommendation/test-recommendation-prompt.d.ts +6 -0
  54. package/build/prompts/test-recommendation/test-recommendation-prompt.js +4 -4
  55. package/build/prompts/test-recommendation/test-recommendation-prompt.test.d.ts +1 -0
  56. package/build/prompts/test-recommendation/test-recommendation-prompt.test.js +97 -99
  57. package/build/prompts/testbot/testbot-prompts.d.ts +26 -0
  58. package/build/prompts/testbot/testbot-prompts.js +301 -46
  59. package/build/prompts/testbot/testbot-prompts.test.d.ts +1 -0
  60. package/build/prompts/testbot/testbot-prompts.test.js +109 -117
  61. package/build/resources/analysisResources.d.ts +17 -0
  62. package/build/resources/progressResource.d.ts +2 -0
  63. package/build/resources/sutSetupResource.d.ts +2 -0
  64. package/build/resources/sutSetupResource.js +45 -0
  65. package/build/resources/testbotResource.d.ts +2 -0
  66. package/build/resources/testbotResource.js +2 -2
  67. package/build/services/AnalyticsService.d.ts +13 -0
  68. package/build/services/AnalyticsService.test.d.ts +1 -0
  69. package/build/services/AnalyticsService.test.js +86 -0
  70. package/build/services/ModularizationService.d.ts +11 -0
  71. package/build/services/ScenarioGenerationService.d.ts +37 -0
  72. package/build/services/ScenarioGenerationService.integration.test.d.ts +1 -0
  73. package/build/services/ScenarioGenerationService.integration.test.js +4 -0
  74. package/build/services/ScenarioGenerationService.test.d.ts +1 -0
  75. package/build/services/TestDiscoveryService.d.ts +128 -0
  76. package/build/services/TestDiscoveryService.js +22 -12
  77. package/build/services/TestDiscoveryService.test.d.ts +1 -0
  78. package/build/services/TestDiscoveryService.test.js +4 -3
  79. package/build/services/TestExecutionService.d.ts +55 -0
  80. package/build/services/TestExecutionService.js +158 -26
  81. package/build/services/TestExecutionService.test.d.ts +1 -0
  82. package/build/services/TestExecutionService.test.js +339 -25
  83. package/build/services/TestGenerationService.d.ts +56 -0
  84. package/build/services/TestGenerationService.test.d.ts +1 -0
  85. package/build/services/TestGenerationService.test.js +10 -8
  86. package/build/services/containerEnv.d.ts +14 -0
  87. package/build/tool-phase-coverage.test.d.ts +1 -0
  88. package/build/tool-phase-coverage.test.js +2 -0
  89. package/build/tool-phases.d.ts +38 -0
  90. package/build/tools/auth/loginTool.d.ts +2 -0
  91. package/build/tools/auth/logoutTool.d.ts +2 -0
  92. package/build/tools/code-refactor/codeReuseTool.d.ts +2 -0
  93. package/build/tools/code-refactor/enhanceAssertionsTool.d.ts +2 -0
  94. package/build/tools/code-refactor/modularizationTool.d.ts +2 -0
  95. package/build/tools/executeSkyrampTestTool.d.ts +2 -0
  96. package/build/tools/executeSkyrampTestTool.js +26 -7
  97. package/build/tools/fixErrorTool.d.ts +2 -0
  98. package/build/tools/generate-tests/generateBatchScenarioRestTool.d.ts +45 -0
  99. package/build/tools/generate-tests/generateBatchScenarioRestTool.js +1 -1
  100. package/build/tools/generate-tests/generateBatchScenarioRestTool.test.d.ts +1 -0
  101. package/build/tools/generate-tests/generateBatchScenarioRestTool.test.js +11 -9
  102. package/build/tools/generate-tests/generateContractRestTool.d.ts +31 -0
  103. package/build/tools/generate-tests/generateE2ERestTool.d.ts +13 -0
  104. package/build/tools/generate-tests/generateFuzzRestTool.d.ts +10 -0
  105. package/build/tools/generate-tests/generateIntegrationRestTool.d.ts +16 -0
  106. package/build/tools/generate-tests/generateLoadRestTool.d.ts +18 -0
  107. package/build/tools/generate-tests/generateLoadRestTool.test.d.ts +1 -0
  108. package/build/tools/generate-tests/generateMockRestTool.d.ts +64 -0
  109. package/build/tools/generate-tests/generateSmokeRestTool.d.ts +8 -0
  110. package/build/tools/generate-tests/generateUIRestTool.d.ts +14 -0
  111. package/build/tools/generate-tests/generateUIRestTool.js +1 -1
  112. package/build/tools/generate-tests/loadTestSchema.d.ts +39 -0
  113. package/build/tools/one-click/oneClickTool.d.ts +11 -0
  114. package/build/tools/submitReportTool.d.ts +394 -0
  115. package/build/tools/submitReportTool.js +250 -41
  116. package/build/tools/submitReportTool.test.d.ts +1 -0
  117. package/build/tools/submitReportTool.test.js +511 -55
  118. package/build/tools/test-management/actionsTool.d.ts +10 -0
  119. package/build/tools/test-management/actionsTool.js +21 -7
  120. package/build/tools/test-management/analyzeChangesTool.d.ts +27 -0
  121. package/build/tools/test-management/analyzeChangesTool.js +150 -48
  122. package/build/tools/test-management/analyzeChangesTool.test.d.ts +1 -0
  123. package/build/tools/test-management/analyzeChangesTool.test.js +95 -78
  124. package/build/tools/test-management/analyzeTestHealthTool.d.ts +2 -0
  125. package/build/tools/test-management/analyzeTestHealthTool.js +16 -9
  126. package/build/tools/test-management/index.d.ts +3 -0
  127. package/build/tools/test-management/index.js +0 -1
  128. package/build/tools/trace/resolveSaveStoragePath.d.ts +7 -0
  129. package/build/tools/trace/resolveSaveStoragePath.test.d.ts +1 -0
  130. package/build/tools/trace/resolveSessionPaths.d.ts +40 -0
  131. package/build/tools/trace/resolveSessionPaths.test.d.ts +1 -0
  132. package/build/tools/trace/sessionState.d.ts +2 -0
  133. package/build/tools/trace/sessionState.test.d.ts +1 -0
  134. package/build/tools/trace/startTraceCollectionTool.d.ts +2 -0
  135. package/build/tools/trace/stopTraceCollectionTool.d.ts +2 -0
  136. package/build/tools/workspace/initScanWorkspaceTool.d.ts +4 -0
  137. package/build/tools/workspace/initializeWorkspaceTool.d.ts +2 -0
  138. package/build/tools/workspace/initializeWorkspaceTool.js +39 -22
  139. package/build/tools/workspace/initializeWorkspaceTool.test.d.ts +1 -0
  140. package/build/tools/workspace/initializeWorkspaceTool.test.js +134 -0
  141. package/build/tools/workspace/serviceUpsert.d.ts +12 -0
  142. package/build/tools/workspace/serviceUpsert.js +23 -0
  143. package/build/tools/workspace/serviceUpsert.test.d.ts +1 -0
  144. package/build/tools/workspace/serviceUpsert.test.js +50 -0
  145. package/build/types/OneClickCommands.d.ts +68 -0
  146. package/build/types/RepositoryAnalysis.d.ts +3004 -0
  147. package/build/types/TestAnalysis.d.ts +97 -0
  148. package/build/types/TestExecution.d.ts +63 -0
  149. package/build/types/TestExecution.js +11 -1
  150. package/build/types/TestRecommendation.d.ts +12 -0
  151. package/build/types/TestTypes.d.ts +234 -0
  152. package/build/types/TestbotReport.d.ts +68 -0
  153. package/build/types/TestbotReport.js +1 -0
  154. package/build/types/index.d.ts +4 -0
  155. package/build/types/index.js +3 -0
  156. package/build/utils/AnalysisStateManager.d.ts +242 -0
  157. package/build/utils/AnalysisStateManager.js +144 -3
  158. package/build/utils/AnalysisStateManager.test.d.ts +1 -0
  159. package/build/utils/AnalysisStateManager.test.js +98 -0
  160. package/build/utils/analyze-openapi.d.ts +1 -0
  161. package/build/utils/branchDiff.d.ts +18 -0
  162. package/build/utils/dartRouteExtractor.d.ts +45 -0
  163. package/build/utils/dartRouteExtractor.test.d.ts +1 -0
  164. package/build/utils/docker.d.ts +19 -0
  165. package/build/utils/docker.test.d.ts +1 -0
  166. package/build/utils/docker.test.js +10 -9
  167. package/build/utils/featureFlags.d.ts +37 -0
  168. package/build/utils/featureFlags.test.d.ts +1 -0
  169. package/build/utils/gitStaging.d.ts +24 -0
  170. package/build/utils/gitStaging.test.d.ts +1 -0
  171. package/build/utils/gitStaging.test.js +13 -7
  172. package/build/utils/httpDefaults.d.ts +10 -0
  173. package/build/utils/httpDefaults.test.d.ts +1 -0
  174. package/build/utils/initAgent.d.ts +36 -0
  175. package/build/utils/language-helper.d.ts +6 -0
  176. package/build/utils/logger.d.ts +11 -0
  177. package/build/utils/normalizeParams.d.ts +14 -0
  178. package/build/utils/normalizeSkyrampImports.d.ts +13 -0
  179. package/build/utils/pr-comment-parser.d.ts +31 -0
  180. package/build/utils/pr-comment-parser.test.d.ts +1 -0
  181. package/build/utils/pr-comment-parser.test.js +6 -5
  182. package/build/utils/projectMetadata.d.ts +15 -0
  183. package/build/utils/projectMetadata.test.d.ts +1 -0
  184. package/build/utils/proxy-terminal.d.ts +2 -0
  185. package/build/utils/repoScanner.d.ts +27 -0
  186. package/build/utils/repoScanner.test.d.ts +1 -0
  187. package/build/utils/routeParsers.d.ts +53 -0
  188. package/build/utils/routeParsers.js +31 -38
  189. package/build/utils/routeParsers.test.d.ts +1 -0
  190. package/build/utils/scenarioDrafting.d.ts +71 -0
  191. package/build/utils/scenarioDrafting.js +23 -1
  192. package/build/utils/scenarioDrafting.test.d.ts +1 -0
  193. package/build/utils/skyrampMdContent.d.ts +5 -0
  194. package/build/utils/sourceRouteExtractor.d.ts +48 -0
  195. package/build/utils/sourceRouteExtractor.test.d.ts +1 -0
  196. package/build/utils/telemetry.d.ts +32 -0
  197. package/build/utils/telemetry.test.d.ts +1 -0
  198. package/build/utils/trace-parser.d.ts +32 -0
  199. package/build/utils/trace-parser.test.d.ts +6 -0
  200. package/build/utils/uiPageEnumerator.d.ts +146 -0
  201. package/build/utils/uiPageEnumerator.js +58 -14
  202. package/build/utils/uiPageEnumerator.test.d.ts +1 -0
  203. package/build/utils/uiPageEnumerator.test.js +223 -43
  204. package/build/utils/utils.d.ts +45 -0
  205. package/build/utils/utils.js +10 -0
  206. package/build/utils/utils.test.d.ts +1 -0
  207. package/build/utils/versions.d.ts +3 -0
  208. package/build/utils/versions.js +1 -1
  209. package/build/utils/workspaceAuth.d.ts +127 -0
  210. package/build/utils/workspaceAuth.test.d.ts +1 -0
  211. package/build/utils/workspaceAuth.test.js +25 -25
  212. package/build/workspace/index.d.ts +1 -0
  213. package/build/workspace/workspace.d.ts +390 -0
  214. package/build/workspace/workspace.js +7 -7
  215. package/build/workspace/workspace.test.d.ts +1 -0
  216. package/build/workspace/workspace.test.js +6 -6
  217. package/node_modules/playwright/lib/mcp/skyramp/loadTraceTool.js +31 -13
  218. package/node_modules/playwright/lib/mcp/skyramp/traceRecordingBackend.js +88 -15
  219. package/node_modules/playwright/lib/mcp/test/skyRampExport.js +4 -3
  220. package/node_modules/playwright/node_modules/playwright-core/ThirdPartyNotices.txt +23 -126
  221. package/node_modules/playwright/node_modules/playwright-core/lib/generated/injectedScriptSource.js +1 -1
  222. package/node_modules/playwright/node_modules/playwright-core/lib/server/recorder/recorderApp.js +79 -19
  223. package/node_modules/playwright/node_modules/playwright-core/lib/server/recorder/skyramp/aliasRemapper.js +84 -0
  224. package/node_modules/playwright/node_modules/playwright-core/lib/server/recorder/skyramp/replayEngine.js +15 -4
  225. package/node_modules/playwright/node_modules/playwright-core/lib/vite/recorder/assets/{codeMirrorModule-CPZKmjFR.js → codeMirrorModule-D0BjbCb7.js} +1 -1
  226. package/node_modules/playwright/node_modules/playwright-core/lib/vite/recorder/assets/index-lvTRGFx-.js +193 -0
  227. package/node_modules/playwright/node_modules/playwright-core/lib/vite/recorder/index.html +1 -1
  228. package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/assets/{codeMirrorModule-B0JOjboO.js → codeMirrorModule-Cqp3cwEJ.js} +1 -1
  229. package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/assets/{defaultSettingsView-1anWeyDf.js → defaultSettingsView-CYf9adZh.js} +79 -79
  230. package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/{index.Cc5029a3.js → index.B_7ywgmr.js} +1 -1
  231. package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/index.html +2 -2
  232. package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/uiMode.html +2 -2
  233. package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/{uiMode.Wo5yvvVh.js → uiMode.u4_8VnCV.js} +1 -1
  234. package/node_modules/playwright/node_modules/playwright-core/package.json +1 -1
  235. package/node_modules/playwright/node_modules/playwright-core/src/generated/injectedScriptSource.ts +1 -1
  236. package/node_modules/playwright/node_modules/playwright-core/src/server/recorder/recorderApp.ts +129 -43
  237. package/node_modules/playwright/node_modules/playwright-core/src/server/recorder/skyramp/aliasRemapper.ts +119 -0
  238. package/node_modules/playwright/node_modules/playwright-core/src/server/recorder/skyramp/replayEngine.ts +33 -10
  239. package/node_modules/playwright/package.json +1 -1
  240. package/package.json +16 -4
  241. package/build/tools/test-management/uiAnalyzeChangesTool.js +0 -155
  242. package/build/tools/test-management/uiAnalyzeChangesTool.test.js +0 -147
  243. package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/.bin/node-which +0 -52
  244. package/node_modules/playwright/node_modules/playwright-core/bundles/utils/node_modules/.bin/is-docker +0 -5
  245. package/node_modules/playwright/node_modules/playwright-core/bundles/utils/node_modules/.bin/mime +0 -46
  246. package/node_modules/playwright/node_modules/playwright-core/bundles/utils/node_modules/.bin/yaml +0 -11
  247. package/node_modules/playwright/node_modules/playwright-core/lib/vite/recorder/assets/index-CsgPR2wy.js +0 -193
  248. package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/index.CP3Y8Zsb.js +0 -2
@@ -6,6 +6,9 @@ import { AnalyticsService } from "../services/AnalyticsService.js";
6
6
  import { TEST_CATEGORIES, externalCategory } from "../types/TestRecommendation.js";
7
7
  import { TestType, HttpMethod } from "../types/TestTypes.js";
8
8
  import { DriftAction } from "../types/TestAnalysis.js";
9
+ import { TestExecutionStatus } from "../types/TestExecution.js";
10
+ import { StateManager } from "../utils/AnalysisStateManager.js";
11
+ import { toolError } from "../utils/utils.js";
9
12
  // Drift actions that actually modify a test file. VERIFY and IGNORE are
10
13
  // no-ops (the test was assessed but left unchanged), so they must not count
11
14
  // toward "tests maintained" telemetry.
@@ -16,17 +19,59 @@ const MAINTENANCE_CHANGE_ACTIONS = new Set([
16
19
  ]);
17
20
  const TOOL_NAME = "skyramp_submit_report";
18
21
  const DEFAULT_COMMIT_MESSAGE = "Added recommendations by Skyramp Testbot.";
22
+ // Per-repo attribution. In a multi-repo run, every report item carries the
23
+ // owner/repo it pertains to (the primary's owner/repo for primary findings, or a
24
+ // related repo's owner/repo for findings from that repo's diff). Whitespace-only
25
+ // values are normalized to undefined so a blank string is never treated as a
26
+ // distinct repo. Single-repo runs may omit it.
27
+ const repositoryField = z
28
+ .string()
29
+ .optional()
30
+ .describe("The 'owner/repo' this item pertains to (e.g. 'letsramp/api-insight'). In a multi-repo run, ALWAYS set it — the primary's owner/repo for primary-repo findings, or a related repo's owner/repo for findings derived from that repository's diff. May be omitted only in single-repo runs.");
31
+ /** Normalize a `repository` attribution: trim, and map a blank/whitespace-only
32
+ * value to undefined so it is never serialized as a distinct empty repo
33
+ * (downstream consumers treat absence as "the primary repo"). */
34
+ function normalizeRepository(item) {
35
+ const trimmed = item.repository?.trim();
36
+ return trimmed ? { ...item, repository: trimmed } : { ...item, repository: undefined };
37
+ }
19
38
  const testResultSchema = z.object({
20
39
  testType: z.nativeEnum(TestType).describe("Type of test. Do not include priority or other metadata in this field."),
21
40
  endpoint: z.string().describe("HTTP verb and path, e.g. 'GET /api/v1/products'"),
22
41
  status: z.enum(["Pass", "Fail", "Skipped"]).describe("Test execution result"),
23
42
  details: z.string().describe("One sentence — no embedded newlines, no markdown. e.g. '10.8s, products_contract_test.py' or 'failed: <one-line error summary>, products_contract_test.py'"),
43
+ videoPath: z.string().optional().describe("Path to video recording (UI/E2E tests only)"),
44
+ repository: repositoryField,
45
+ });
46
+ // Structured grounding for UI recommendations. Lifted verbatim from a
47
+ // `browser_blueprint` element captured during this run. Source of truth for
48
+ // codegen (prefers testId > stableId > role+name for locator selection),
49
+ // the verifier (cross-checks each field against captured blueprints), and
50
+ // any future tooling that wants reliable grounding data without prose
51
+ // parsing. `reasoning` is free-form prose constrained by the Blueprint
52
+ // Citation Invariant — every element cited must appear in `targetElements`.
53
+ // See testbot prompt step 4 for the full populate-and-render rules.
54
+ export const targetElementSchema = z.object({
55
+ role: z.string().describe("ARIA role (e.g. 'button', 'heading', 'textbox', 'link'). Lifted verbatim from the captured blueprint element's `role` field."),
56
+ accessibleName: z.string().describe("Computed accessible name (e.g. 'Save changes', 'Order Details'). Lifted verbatim from the captured blueprint element's `accessibleName` field. Must match the bolded name in `reasoning` character-for-character."),
57
+ testId: z.string().nullable().describe("data-testid attribute value (preferred locator handle), or null when the element has no data-testid. Lifted from the blueprint element's `testId`."),
58
+ stableId: z.string().nullable().describe("Unique HTML id attribute value (fallback locator when no testId), or null. Lifted from the blueprint element's `stableId`."),
59
+ contextText: z.array(z.string()).nullable().describe("Disambiguating row text for elements inside repeating sections (table rows, list items): the row's surrounding non-interactive text. Lifted from the blueprint repeatingElement's items[].contextText. null for non-repeating elements."),
60
+ mutability: z.enum(["mutable", "immutable", "unknown"]).optional().describe("Whether the element's content/state is expected to change between captures. 'mutable' elements are behavioral-test targets; 'immutable' are smoke-test targets. Copied from the blueprint element's `mutability`."),
61
+ widgetType: z.enum(["native", "custom", "unknown"]).optional().describe("'native' = HTML built-ins (button, input). 'custom' or 'unknown' = non-standard composites; fall back to snapshot-driven trial clicks for interaction. Copied from the blueprint element's `widgetType`."),
62
+ });
63
+ // Page metadata for a UI recommendation. Lifted from the BlueprintCapture
64
+ // the agent used to populate `targetElements`. Codegen reads `url` to drive
65
+ // page.goto(); the verifier uses `pageHash` to detect stale captures.
66
+ export const pageContextSchema = z.object({
67
+ url: z.string().describe("URL of the page where the test runs. Lifted from BlueprintCapture.url."),
68
+ pageHash: z.string().optional().describe("Opaque hash of the captured page state (BlueprintCapture.pageHash). Lets the verifier confirm the recommendation was grounded in a still-current capture."),
24
69
  });
25
70
  // TODO: Unify newTestSchema and additionalRecommendationSchema into a single
26
71
  // interface that adds an `implemented: boolean` field. Both describe the same
27
72
  // concept (a test recommendation) — the only difference is whether it was
28
73
  // generated in this run or left for later. Tracked per Archit's review comment.
29
- const newTestSchema = z.object({
74
+ export const newTestSchema = z.object({
30
75
  testId: z.string().describe("Human-readable kebab-case identifier, e.g. 'contract-get-products' or 'integration-users-orders-workflow'. Format: '<testType>-<method>-<resource>' for single-endpoint tests or '<testType>-<scenario-slug>' for multi-step tests. Must be unique within the report."),
31
76
  testType: z.nativeEnum(TestType).describe("Type of test created. Do not include priority or other metadata in this field."),
32
77
  category: z.preprocess((val) => externalCategory(val), z.enum(TEST_CATEGORIES)).describe("Test category — critical categories (security_boundary, business_rule, data_integrity, breaking_change) get generation priority over workflow"),
@@ -37,6 +82,72 @@ const newTestSchema = z.object({
37
82
  traceFile: z.string().optional().describe("Path to the backend trace file if used or created"),
38
83
  frontendTrace: z.string().optional().describe("Path to the Playwright/UI trace file if used or created"),
39
84
  reasoning: z.string().describe("Why this test was created: what production risk it mitigates, what code pattern it targets, or what coverage gap it fills"),
85
+ repository: repositoryField,
86
+ targetElements: z.array(targetElementSchema).min(1).nullable().optional().describe("UI tests only: structured grounding for one or more elements the test targets. Most tests target a single element (array length 1); render-state and multi-step UI tests target several (array length 2+). Each entry must be lifted verbatim from a captured blueprint element. Set to null when blueprint capture failed (also requires '[no-blueprint-data]' marker in BOTH description and reasoning). See Blueprint Citation Invariant in testbot prompt."),
87
+ pageContext: pageContextSchema.optional().describe("UI tests only: page metadata for the test. Lifted from the BlueprintCapture used during grounding."),
88
+ }).superRefine((rec, ctx) => {
89
+ // targetElements / pageContext are UI-only.
90
+ if (rec.testType !== TestType.UI) {
91
+ for (const field of ["targetElements", "pageContext"]) {
92
+ if (rec[field] !== undefined) {
93
+ ctx.addIssue({
94
+ code: z.ZodIssueCode.custom,
95
+ path: [field],
96
+ message: `${field} is only valid for testType: 'ui'`,
97
+ });
98
+ }
99
+ }
100
+ }
101
+ else {
102
+ // description is required for UI tests (walkthrough of steps/assertions).
103
+ if (!rec.description) {
104
+ ctx.addIssue({
105
+ code: z.ZodIssueCode.custom,
106
+ path: ["description"],
107
+ message: "description is required for testType: 'ui'.",
108
+ });
109
+ }
110
+ // targetElements must be present (array or null — not omitted entirely).
111
+ if (rec.targetElements === undefined) {
112
+ ctx.addIssue({
113
+ code: z.ZodIssueCode.custom,
114
+ path: ["targetElements"],
115
+ message: "targetElements is required for testType: 'ui'. Use null when blueprint capture failed (and add '[no-blueprint-data]' to both description and reasoning).",
116
+ });
117
+ }
118
+ // pageContext is required when targetElements is an array (grounded), forbidden when null.
119
+ if (Array.isArray(rec.targetElements) && rec.pageContext === undefined) {
120
+ ctx.addIssue({
121
+ code: z.ZodIssueCode.custom,
122
+ path: ["pageContext"],
123
+ message: "pageContext is required for testType: 'ui' when targetElements is an array.",
124
+ });
125
+ }
126
+ if (rec.targetElements === null && rec.pageContext !== undefined) {
127
+ ctx.addIssue({
128
+ code: z.ZodIssueCode.custom,
129
+ path: ["pageContext"],
130
+ message: "pageContext must be omitted when targetElements is null (no blueprint data).",
131
+ });
132
+ }
133
+ // When targetElements is null, both description and reasoning must carry the '[no-blueprint-data]' marker.
134
+ if (rec.targetElements === null) {
135
+ if (!rec.reasoning.includes("[no-blueprint-data]")) {
136
+ ctx.addIssue({
137
+ code: z.ZodIssueCode.custom,
138
+ path: ["reasoning"],
139
+ message: "reasoning must contain '[no-blueprint-data]' when targetElements is null.",
140
+ });
141
+ }
142
+ if (rec.description && !rec.description.includes("[no-blueprint-data]")) {
143
+ ctx.addIssue({
144
+ code: z.ZodIssueCode.custom,
145
+ path: ["description"],
146
+ message: "description must contain '[no-blueprint-data]' when targetElements is null.",
147
+ });
148
+ }
149
+ }
150
+ }
40
151
  });
41
152
  const descriptionSchema = z.object({
42
153
  description: z.string().describe("One-line description. Do NOT prefix with the severity level — severity is a separate field. Include code logic bugs from the diff, test generation/execution failures, and environment misconfiguration."),
@@ -46,6 +157,7 @@ const descriptionSchema = z.object({
46
157
  .describe("Issue severity. critical = feature broken/unusable (e.g. page doesn't load, data corruption). " +
47
158
  "high = incorrect behavior (e.g. wrong calculation, stale data returned). " +
48
159
  "medium = minor functional gap. low = cosmetic or informational."),
160
+ repository: repositoryField,
49
161
  });
50
162
  const scenarioStepSchema = z.object({
51
163
  method: z.nativeEnum(HttpMethod).optional().describe("HTTP method. Required for API steps, omit for UI/E2E actions."),
@@ -63,12 +175,15 @@ export const additionalRecommendationSchema = z.object({
63
175
  scenarioName: z.string().optional().describe("Proposed scenario name for future generation, e.g. 'products-orders-workflow'. No file exists yet — this is a suggestion only. Omit if not applicable."),
64
176
  // TODO: replace text with max(3) and check for regression
65
177
  steps: z.array(scenarioStepSchema).describe("Ordered sequence of API/UI steps in this test scenario (at most 3). Each step must include method and path so the endpoints are explicit. Omit requestBody and responseBody from steps."),
66
- description: z.string().describe("What this test does and why it is valuable. For multi-step scenarios, list the endpoints involved (e.g. 'POST /api/v1/products POST /api/v1/orders → PATCH /api/v1/orders/{id}') so the recommendation is actionable without primaryEndpoint."),
178
+ description: z.string().describe("Walkthrough of what the test does the steps and assertions. For multi-step scenarios, list the endpoints involved. The 'why it is valuable' belongs in reasoning."),
67
179
  priority: z.preprocess((val) => (typeof val === "string" ? val.toLowerCase() : val), z.enum(["high", "medium", "low"])).describe("Priority level: high, medium, or low. First check diff relevance — does the test target an endpoint changed in this PR? HIGH: diff-relevant security/auth/error tests, cross-resource isolation for diff endpoints, CRUD lifecycle for NEW endpoints in the diff. MEDIUM: diff-relevant business-rule happy paths, multi-resource workflows involving diff endpoints, security/error tests for NON-diff endpoints. LOW: tests targeting only unchanged endpoints, trivially discoverable happy paths duplicating generated tests."),
68
180
  openApiSpec: z.string().optional().describe("Path to OpenAPI/Swagger spec file if available, e.g. 'openapi.yaml'"),
69
181
  backendTrace: z.string().optional().describe("Path to backend trace file if available, e.g. 'tests/skyramp-traces.json'. Used by integration and E2E tests."),
70
182
  frontendTrace: z.string().optional().describe("Path to Playwright/UI trace file if available, e.g. 'tests/skyramp-playwright.zip'. UI tests need this; E2E tests need both frontend and backend traces."),
71
183
  reasoning: z.string().describe("Why this test is recommended: the specific production risk, business rule, or security boundary it would validate"),
184
+ repository: repositoryField,
185
+ targetElements: z.array(targetElementSchema).min(1).nullable().optional().describe("UI tests only: structured grounding for one or more elements the test targets. Most tests target a single element (array length 1); render-state and multi-step UI tests target several (array length 2+). Each entry must be lifted verbatim from a captured blueprint element. Set to null when blueprint capture failed (also requires '[no-blueprint-data]' marker in BOTH description and reasoning). See Blueprint Citation Invariant in testbot prompt."),
186
+ pageContext: pageContextSchema.optional().describe("UI tests only: page metadata for the test. Lifted from the BlueprintCapture used during grounding."),
72
187
  }).superRefine((rec, ctx) => {
73
188
  if (rec.testType === TestType.CONTRACT && !rec.primaryEndpoint) {
74
189
  ctx.addIssue({
@@ -77,6 +192,57 @@ export const additionalRecommendationSchema = z.object({
77
192
  message: "primaryEndpoint is required for contract tests — provide the HTTP verb and path, e.g. 'PATCH /api/v1/orders/{order_id}'.",
78
193
  });
79
194
  }
195
+ // targetElements / pageContext are UI-only.
196
+ if (rec.testType !== TestType.UI) {
197
+ for (const field of ["targetElements", "pageContext"]) {
198
+ if (rec[field] !== undefined) {
199
+ ctx.addIssue({
200
+ code: z.ZodIssueCode.custom,
201
+ path: [field],
202
+ message: `${field} is only valid for testType: 'ui'`,
203
+ });
204
+ }
205
+ }
206
+ }
207
+ else {
208
+ if (rec.targetElements === undefined) {
209
+ ctx.addIssue({
210
+ code: z.ZodIssueCode.custom,
211
+ path: ["targetElements"],
212
+ message: "targetElements is required for testType: 'ui'. Use null when blueprint capture failed (and add '[no-blueprint-data]' to both description and reasoning).",
213
+ });
214
+ }
215
+ if (Array.isArray(rec.targetElements) && rec.pageContext === undefined) {
216
+ ctx.addIssue({
217
+ code: z.ZodIssueCode.custom,
218
+ path: ["pageContext"],
219
+ message: "pageContext is required for testType: 'ui' when targetElements is an array.",
220
+ });
221
+ }
222
+ if (rec.targetElements === null && rec.pageContext !== undefined) {
223
+ ctx.addIssue({
224
+ code: z.ZodIssueCode.custom,
225
+ path: ["pageContext"],
226
+ message: "pageContext must be omitted when targetElements is null (no blueprint data).",
227
+ });
228
+ }
229
+ if (rec.targetElements === null) {
230
+ if (!rec.reasoning.includes("[no-blueprint-data]")) {
231
+ ctx.addIssue({
232
+ code: z.ZodIssueCode.custom,
233
+ path: ["reasoning"],
234
+ message: "reasoning must contain '[no-blueprint-data]' when targetElements is null.",
235
+ });
236
+ }
237
+ if (!rec.description.includes("[no-blueprint-data]")) {
238
+ ctx.addIssue({
239
+ code: z.ZodIssueCode.custom,
240
+ path: ["description"],
241
+ message: "description must contain '[no-blueprint-data]' when targetElements is null.",
242
+ });
243
+ }
244
+ }
245
+ }
80
246
  });
81
247
  const testMaintenanceSchema = z.object({
82
248
  testType: z.nativeEnum(TestType).describe("Type of test."),
@@ -84,13 +250,25 @@ const testMaintenanceSchema = z.object({
84
250
  fileName: z.string().describe("Test file that was maintained, e.g. 'products_smoke_test.py'"),
85
251
  action: z.nativeEnum(DriftAction).describe("The drift action assigned to this test during maintenance triage."),
86
252
  description: z.string().describe("What was changed and why"),
87
- beforeStatus: z.enum(["Pass", "Fail", "Error"]).describe("Test result BEFORE modification"),
88
- beforeDetails: z.string().describe("Execution output/timing before modification, or 'baseline from CI workflow <name>' if a parallel workflow provided the baseline"),
89
- afterStatus: z.enum(["Pass", "Fail", "Error", "Skipped"]).describe("Test result AFTER modification"),
90
- afterDetails: z.string().describe("Execution output/timing after modification"),
91
- })
92
- .refine(m => ![DriftAction.Verify, DriftAction.Ignore].includes(m.action) || m.afterStatus === "Skipped", { message: "VERIFY and IGNORE entries must have afterStatus: 'Skipped' these actions do not modify the test file" })
93
- .refine(m => ![DriftAction.Update, DriftAction.Regenerate, DriftAction.Delete].includes(m.action) || m.afterStatus !== "Skipped", { message: "UPDATE, REGENERATE, and DELETE entries must have a real afterStatus (not Skipped) — these actions modify the test file and must be executed" });
253
+ beforeDetails: z.string().describe("One line only — no embedded newlines, no raw HTTP headers or JSON blobs. " +
254
+ "For passing runs: count and timing, e.g. '4 passed in 15.09s'. " +
255
+ "For failing runs: failure name and one-line root cause, e.g. " +
256
+ "'FAILED test_foo assert 403 got 200, auth middleware not enforced'. " +
257
+ "Empty string for VERIFY/IGNORE entries where no before-execution was run."),
258
+ afterDetails: z.string().describe("One line only no embedded newlines, no raw HTTP headers or JSON blobs. " +
259
+ "For passing runs: count and timing, e.g. '5 passed in 10.96s'. " +
260
+ "For failing runs: failure name and one-line root cause, e.g. " +
261
+ "'FAILED test_foo — check_schema fails, order_id=1 has discount from prior PATCH test'. " +
262
+ "Empty string for VERIFY/IGNORE/DELETE entries where no after-execution was run."),
263
+ // Server-populated from stateFile execution records — never supplied by the LLM.
264
+ beforeStatus: z.nativeEnum(TestExecutionStatus),
265
+ afterStatus: z.nativeEnum(TestExecutionStatus),
266
+ });
267
+ // LLM input contract: status fields are server-populated from stateFile.
268
+ const testMaintenanceLLMSchema = testMaintenanceSchema.omit({
269
+ beforeStatus: true,
270
+ afterStatus: true,
271
+ });
94
272
  /**
95
273
  * Derive per-run analytics counts from a submitted report. These power the
96
274
  * alpha-launch dashboards (tests generated/maintained, suite growth, bugs vs
@@ -114,7 +292,7 @@ function computeReportMetrics(params) {
114
292
  const recommendations = params.additionalRecommendations ?? [];
115
293
  const countBy = (items, pred) => items.filter(pred).length;
116
294
  const changedMaintenance = (params.testMaintenance ?? []).filter(isMaintenanceChange);
117
- const maintenanceRecovered = countBy(changedMaintenance, (m) => m.beforeStatus !== "Pass" && m.afterStatus === "Pass");
295
+ const maintenanceRecovered = countBy(changedMaintenance, (m) => (m.beforeStatus === TestExecutionStatus.Fail || m.beforeStatus === TestExecutionStatus.Error) && m.afterStatus === TestExecutionStatus.Pass);
118
296
  return {
119
297
  testsGenerated: String(params.newTestsCreated.length),
120
298
  testsMaintained: String(changedMaintenance.length),
@@ -130,6 +308,23 @@ function computeReportMetrics(params) {
130
308
  issuesLow: String(countBy(params.issuesFound, (i) => i.severity === "low")),
131
309
  };
132
310
  }
311
+ function deduplicateById(items) {
312
+ const seen = new Set();
313
+ const result = [];
314
+ for (const item of items) {
315
+ let id = item.testId;
316
+ if (seen.has(id)) {
317
+ let suffix = 2;
318
+ while (seen.has(`${id}-${suffix}`))
319
+ suffix++;
320
+ id = `${id}-${suffix}`;
321
+ item.testId = id;
322
+ }
323
+ seen.add(id);
324
+ result.push(item);
325
+ }
326
+ return result;
327
+ }
133
328
  export function registerSubmitReportTool(server) {
134
329
  server.registerTool(TOOL_NAME, {
135
330
  description: "Submit the final testbot report. Call this tool once after completing all test analysis, generation, and execution. " +
@@ -150,9 +345,9 @@ export function registerSubmitReportTool(server) {
150
345
  .default([])
151
346
  .describe("Recommended tests that were not generated (lower priority). Only include recommendations that add distinct coverage beyond generated tests — do not pad with variants testing the same endpoint and flow."),
152
347
  testMaintenance: z
153
- .array(testMaintenanceSchema)
348
+ .array(testMaintenanceLLMSchema)
154
349
  .optional()
155
- .describe("One entry per test assessed in the drift analysis step. Omit this field when no existing tests were found."),
350
+ .describe("One entry per test assessed in the drift analysis step, including IGNORE and VERIFY entries — these provide triage transparency even when no edits were needed. Omit this field entirely when no existing tests were found."),
156
351
  testResults: z
157
352
  .array(testResultSchema)
158
353
  .describe("List of ALL test execution results. One entry per test executed."),
@@ -175,6 +370,12 @@ export function registerSubmitReportTool(server) {
175
370
  .describe("Succinct commit message (if possible, under 72 chars) summarizing what Testbot did, " +
176
371
  "e.g. 'add contract tests for /products endpoint' or 'update smoke tests for order API changes'. " +
177
372
  "Used as both the git commit subject and the side PR title — the consumer applies truncation as needed."),
373
+ stateFile: z
374
+ .string()
375
+ .describe("Path to the state file returned by skyramp_analyze_changes. Required — " +
376
+ "contains execution results for maintenance scoring (existingTests), " +
377
+ "discovered endpoints for coverage validation (newEndpoints), and " +
378
+ "UI page context for frontend test grounding (uiContext)."),
178
379
  },
179
380
  _meta: {
180
381
  keywords: ["report", "summary", "testbot", "submit"],
@@ -182,40 +383,51 @@ export function registerSubmitReportTool(server) {
182
383
  }, async (params) => {
183
384
  const startTime = Date.now();
184
385
  let errorResult;
185
- const deduplicateById = (items) => {
186
- const seen = new Set();
187
- const result = [];
188
- for (const item of items) {
189
- let id = item.testId;
190
- if (seen.has(id)) {
191
- let suffix = 2;
192
- while (seen.has(`${id}-${suffix}`))
193
- suffix++;
194
- id = `${id}-${suffix}`;
195
- item.testId = id;
196
- }
197
- seen.add(id);
198
- result.push(item);
199
- }
200
- return result;
201
- };
202
386
  const dedupedNewTests = deduplicateById([...params.newTestsCreated]);
203
387
  const dedupedRecommendations = deduplicateById([...(params.additionalRecommendations ?? [])]);
388
+ let testMaintenance;
389
+ if (params.testMaintenance) {
390
+ try {
391
+ const stateData = await StateManager.fromStatePath(params.stateFile).readData();
392
+ if (!stateData) {
393
+ errorResult = toolError(`stateFile not found or empty: ${params.stateFile}`);
394
+ return errorResult;
395
+ }
396
+ // Status fields are stateFile-authoritative. Details are LLM-supplied when present
397
+ // (single-line, with root-cause context) and fall back to server-side extraction.
398
+ testMaintenance = params.testMaintenance.map((m) => {
399
+ const recorded = stateData.existingTests?.find((t) => t.testFile.endsWith(m.fileName) || path.basename(t.testFile) === path.basename(m.fileName));
400
+ const defaultBeforeStatus = MAINTENANCE_CHANGE_ACTIONS.has(m.action) ? TestExecutionStatus.Unknown : TestExecutionStatus.Skipped;
401
+ const defaultAfterStatus = m.action === DriftAction.Delete ? TestExecutionStatus.Skipped
402
+ : MAINTENANCE_CHANGE_ACTIONS.has(m.action) ? TestExecutionStatus.Unknown : TestExecutionStatus.Skipped;
403
+ const beforeStatus = recorded?.executionBefore?.status ?? defaultBeforeStatus;
404
+ const afterStatus = recorded?.executionAfter?.status ?? defaultAfterStatus;
405
+ logger.info(`${m.fileName}: before=${beforeStatus} after=${afterStatus}`);
406
+ return { ...m, beforeStatus, afterStatus };
407
+ });
408
+ }
409
+ catch (err) {
410
+ errorResult = toolError(`Failed to load stateFile for maintenance enrichment: ${err.message}`);
411
+ return errorResult;
412
+ }
413
+ }
204
414
  // Strip generation-artifact fields from newTestsCreated before writing.
205
415
  // scenarioFile, traceFile, frontendTrace are internal paths used during
206
416
  // generation — downstream scoring scripts don't expect them and fail if
207
417
  // they encounter these string fields while traversing the object.
208
- const sanitizedNewTests = dedupedNewTests.map(({ scenarioFile: _sf, traceFile: _tf, frontendTrace: _ft, ...rest }) => rest);
209
- const reportJson = JSON.stringify({
418
+ // Also normalize each item's `repository` (blank undefined).
419
+ const sanitizedNewTests = dedupedNewTests.map(({ scenarioFile: _sf, traceFile: _tf, frontendTrace: _ft, ...rest }) => normalizeRepository(rest));
420
+ const report = {
210
421
  businessCaseAnalysis: params.businessCaseAnalysis,
211
422
  newTestsCreated: sanitizedNewTests,
212
- additionalRecommendations: dedupedRecommendations,
213
- testMaintenance: params.testMaintenance,
214
- testResults: params.testResults,
215
- issuesFound: params.issuesFound,
423
+ additionalRecommendations: dedupedRecommendations.map(normalizeRepository),
424
+ testMaintenance,
425
+ testResults: params.testResults.map(normalizeRepository),
426
+ issuesFound: params.issuesFound.map(normalizeRepository),
216
427
  nextSteps: params.nextSteps ?? [],
217
428
  commitMessage: (params.commitMessage ?? "").replace(/[\r\n]+/g, " ").trim() || DEFAULT_COMMIT_MESSAGE,
218
- }, null, 2);
429
+ };
430
+ const reportJson = JSON.stringify(report, null, 2);
219
431
  logger.info("Submitting testbot report", {
220
432
  outputFile: params.summaryOutputFile,
221
433
  payloadBytes: reportJson.length,
@@ -242,10 +454,7 @@ export function registerSubmitReportTool(server) {
242
454
  const elapsed = Date.now() - startTime;
243
455
  const errorMessage = `Failed to write report: ${error.message}`;
244
456
  logger.error(errorMessage, { error, elapsedMs: elapsed });
245
- errorResult = {
246
- content: [{ type: "text", text: errorMessage }],
247
- isError: true,
248
- };
457
+ errorResult = toolError(errorMessage);
249
458
  return errorResult;
250
459
  }
251
460
  finally {
@@ -253,7 +462,7 @@ export function registerSubmitReportTool(server) {
253
462
  summary_output_file: params.summaryOutputFile,
254
463
  testResultCount: String(params.testResults.length),
255
464
  payloadBytes: String(reportJson.length),
256
- ...computeReportMetrics(params),
465
+ ...computeReportMetrics({ ...params, testMaintenance }),
257
466
  }).catch(() => { });
258
467
  }
259
468
  });
@@ -0,0 +1 @@
1
+ export {};