@skyramp/mcp 0.2.5 → 0.2.7

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 (186) 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 +1 -2
  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 +14 -3
  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/test-maintenance/actionsInstructions.d.ts +35 -0
  26. package/build/prompts/test-maintenance/drift-analysis-prompt.d.ts +4 -0
  27. package/build/prompts/test-maintenance/driftAnalysisSections.d.ts +13 -0
  28. package/build/prompts/test-recommendation/analysisOutputPrompt.d.ts +84 -0
  29. package/build/prompts/test-recommendation/analysisOutputPrompt.test.d.ts +1 -0
  30. package/build/prompts/test-recommendation/analysisOutputPrompt.test.js +1 -0
  31. package/build/prompts/test-recommendation/diffExecutionPlan.d.ts +17 -0
  32. package/build/prompts/test-recommendation/diffExecutionPlan.js +7 -16
  33. package/build/prompts/test-recommendation/fullRepoCatalog.d.ts +7 -0
  34. package/build/prompts/test-recommendation/mergeEnrichedScenarios.test.d.ts +1 -0
  35. package/build/prompts/test-recommendation/mergeEnrichedScenarios.test.js +1 -0
  36. package/build/prompts/test-recommendation/promptPlan.d.ts +203 -0
  37. package/build/prompts/test-recommendation/promptPlan.test.d.ts +1 -0
  38. package/build/prompts/test-recommendation/recommendationSections.d.ts +35 -0
  39. package/build/prompts/test-recommendation/recommendationShared.d.ts +31 -0
  40. package/build/prompts/test-recommendation/registerRecommendTestsPrompt.d.ts +7 -0
  41. package/build/prompts/test-recommendation/scopeAssessment.d.ts +55 -0
  42. package/build/prompts/test-recommendation/scopeAssessment.js +2 -2
  43. package/build/prompts/test-recommendation/scopeAssessment.test.d.ts +1 -0
  44. package/build/prompts/test-recommendation/scopeAssessment.test.js +1 -0
  45. package/build/prompts/test-recommendation/test-recommendation-prompt.d.ts +6 -0
  46. package/build/prompts/test-recommendation/test-recommendation-prompt.js +4 -4
  47. package/build/prompts/test-recommendation/test-recommendation-prompt.test.d.ts +1 -0
  48. package/build/prompts/test-recommendation/test-recommendation-prompt.test.js +6 -99
  49. package/build/prompts/testbot/testbot-prompts.d.ts +11 -0
  50. package/build/prompts/testbot/testbot-prompts.js +198 -34
  51. package/build/prompts/testbot/testbot-prompts.test.d.ts +1 -0
  52. package/build/prompts/testbot/testbot-prompts.test.js +15 -73
  53. package/build/resources/analysisResources.d.ts +17 -0
  54. package/build/resources/progressResource.d.ts +2 -0
  55. package/build/resources/testbotResource.d.ts +2 -0
  56. package/build/services/AnalyticsService.d.ts +13 -0
  57. package/build/services/AnalyticsService.test.d.ts +1 -0
  58. package/build/services/AnalyticsService.test.js +86 -0
  59. package/build/services/ModularizationService.d.ts +11 -0
  60. package/build/services/ScenarioGenerationService.d.ts +37 -0
  61. package/build/services/ScenarioGenerationService.integration.test.d.ts +1 -0
  62. package/build/services/ScenarioGenerationService.integration.test.js +4 -0
  63. package/build/services/ScenarioGenerationService.test.d.ts +1 -0
  64. package/build/services/TestDiscoveryService.d.ts +128 -0
  65. package/build/services/TestDiscoveryService.test.d.ts +1 -0
  66. package/build/services/TestDiscoveryService.test.js +4 -3
  67. package/build/services/TestExecutionService.d.ts +46 -0
  68. package/build/services/TestExecutionService.test.d.ts +1 -0
  69. package/build/services/TestExecutionService.test.js +33 -25
  70. package/build/services/TestGenerationService.d.ts +56 -0
  71. package/build/services/TestGenerationService.test.d.ts +1 -0
  72. package/build/services/TestGenerationService.test.js +10 -8
  73. package/build/services/containerEnv.d.ts +14 -0
  74. package/build/tool-phase-coverage.test.d.ts +1 -0
  75. package/build/tool-phase-coverage.test.js +2 -0
  76. package/build/tool-phases.d.ts +38 -0
  77. package/build/tools/auth/loginTool.d.ts +2 -0
  78. package/build/tools/auth/logoutTool.d.ts +2 -0
  79. package/build/tools/code-refactor/codeReuseTool.d.ts +2 -0
  80. package/build/tools/code-refactor/enhanceAssertionsTool.d.ts +2 -0
  81. package/build/tools/code-refactor/modularizationTool.d.ts +2 -0
  82. package/build/tools/executeSkyrampTestTool.d.ts +2 -0
  83. package/build/tools/fixErrorTool.d.ts +2 -0
  84. package/build/tools/generate-tests/generateBatchScenarioRestTool.d.ts +45 -0
  85. package/build/tools/generate-tests/generateBatchScenarioRestTool.test.d.ts +1 -0
  86. package/build/tools/generate-tests/generateBatchScenarioRestTool.test.js +11 -9
  87. package/build/tools/generate-tests/generateContractRestTool.d.ts +31 -0
  88. package/build/tools/generate-tests/generateE2ERestTool.d.ts +13 -0
  89. package/build/tools/generate-tests/generateFuzzRestTool.d.ts +10 -0
  90. package/build/tools/generate-tests/generateIntegrationRestTool.d.ts +16 -0
  91. package/build/tools/generate-tests/generateLoadRestTool.d.ts +18 -0
  92. package/build/tools/generate-tests/generateLoadRestTool.test.d.ts +1 -0
  93. package/build/tools/generate-tests/generateMockRestTool.d.ts +64 -0
  94. package/build/tools/generate-tests/generateSmokeRestTool.d.ts +8 -0
  95. package/build/tools/generate-tests/generateUIRestTool.d.ts +14 -0
  96. package/build/tools/generate-tests/generateUIRestTool.js +1 -1
  97. package/build/tools/generate-tests/loadTestSchema.d.ts +39 -0
  98. package/build/tools/one-click/oneClickTool.d.ts +11 -0
  99. package/build/tools/submitReportTool.d.ts +384 -0
  100. package/build/tools/submitReportTool.js +144 -2
  101. package/build/tools/submitReportTool.test.d.ts +1 -0
  102. package/build/tools/submitReportTool.test.js +215 -5
  103. package/build/tools/test-management/actionsTool.d.ts +10 -0
  104. package/build/tools/test-management/analyzeChangesTool.d.ts +27 -0
  105. package/build/tools/test-management/analyzeChangesTool.js +44 -7
  106. package/build/tools/test-management/analyzeChangesTool.test.d.ts +1 -0
  107. package/build/tools/test-management/analyzeChangesTool.test.js +76 -39
  108. package/build/tools/test-management/analyzeTestHealthTool.d.ts +2 -0
  109. package/build/tools/test-management/index.d.ts +3 -0
  110. package/build/tools/test-management/index.js +0 -1
  111. package/build/tools/trace/resolveSaveStoragePath.d.ts +7 -0
  112. package/build/tools/trace/resolveSaveStoragePath.test.d.ts +1 -0
  113. package/build/tools/trace/resolveSessionPaths.d.ts +40 -0
  114. package/build/tools/trace/resolveSessionPaths.test.d.ts +1 -0
  115. package/build/tools/trace/sessionState.d.ts +2 -0
  116. package/build/tools/trace/sessionState.test.d.ts +1 -0
  117. package/build/tools/trace/startTraceCollectionTool.d.ts +2 -0
  118. package/build/tools/trace/stopTraceCollectionTool.d.ts +2 -0
  119. package/build/tools/workspace/initScanWorkspaceTool.d.ts +4 -0
  120. package/build/tools/workspace/initializeWorkspaceTool.d.ts +2 -0
  121. package/build/tools/workspace/initializeWorkspaceTool.test.d.ts +1 -0
  122. package/build/tools/workspace/initializeWorkspaceTool.test.js +134 -0
  123. package/build/types/OneClickCommands.d.ts +68 -0
  124. package/build/types/RepositoryAnalysis.d.ts +3004 -0
  125. package/build/types/TestAnalysis.d.ts +96 -0
  126. package/build/types/TestExecution.d.ts +53 -0
  127. package/build/types/TestRecommendation.d.ts +12 -0
  128. package/build/types/TestTypes.d.ts +234 -0
  129. package/build/utils/AnalysisStateManager.d.ts +182 -0
  130. package/build/utils/AnalysisStateManager.test.d.ts +1 -0
  131. package/build/utils/analyze-openapi.d.ts +1 -0
  132. package/build/utils/branchDiff.d.ts +18 -0
  133. package/build/utils/dartRouteExtractor.d.ts +45 -0
  134. package/build/utils/dartRouteExtractor.test.d.ts +1 -0
  135. package/build/utils/docker.d.ts +19 -0
  136. package/build/utils/docker.test.d.ts +1 -0
  137. package/build/utils/docker.test.js +10 -9
  138. package/build/utils/featureFlags.d.ts +37 -0
  139. package/build/utils/featureFlags.test.d.ts +1 -0
  140. package/build/utils/gitStaging.d.ts +24 -0
  141. package/build/utils/gitStaging.test.d.ts +1 -0
  142. package/build/utils/gitStaging.test.js +13 -7
  143. package/build/utils/httpDefaults.d.ts +10 -0
  144. package/build/utils/httpDefaults.test.d.ts +1 -0
  145. package/build/utils/initAgent.d.ts +36 -0
  146. package/build/utils/language-helper.d.ts +6 -0
  147. package/build/utils/logger.d.ts +11 -0
  148. package/build/utils/normalizeParams.d.ts +14 -0
  149. package/build/utils/normalizeSkyrampImports.d.ts +13 -0
  150. package/build/utils/pr-comment-parser.d.ts +31 -0
  151. package/build/utils/pr-comment-parser.test.d.ts +1 -0
  152. package/build/utils/pr-comment-parser.test.js +6 -5
  153. package/build/utils/projectMetadata.d.ts +15 -0
  154. package/build/utils/projectMetadata.test.d.ts +1 -0
  155. package/build/utils/proxy-terminal.d.ts +2 -0
  156. package/build/utils/repoScanner.d.ts +27 -0
  157. package/build/utils/repoScanner.test.d.ts +1 -0
  158. package/build/utils/routeParsers.d.ts +63 -0
  159. package/build/utils/routeParsers.test.d.ts +1 -0
  160. package/build/utils/scenarioDrafting.d.ts +71 -0
  161. package/build/utils/scenarioDrafting.test.d.ts +1 -0
  162. package/build/utils/skyrampMdContent.d.ts +5 -0
  163. package/build/utils/sourceRouteExtractor.d.ts +48 -0
  164. package/build/utils/sourceRouteExtractor.test.d.ts +1 -0
  165. package/build/utils/telemetry.d.ts +32 -0
  166. package/build/utils/telemetry.test.d.ts +1 -0
  167. package/build/utils/trace-parser.d.ts +32 -0
  168. package/build/utils/trace-parser.test.d.ts +6 -0
  169. package/build/utils/uiPageEnumerator.d.ts +146 -0
  170. package/build/utils/uiPageEnumerator.js +58 -14
  171. package/build/utils/uiPageEnumerator.test.d.ts +1 -0
  172. package/build/utils/uiPageEnumerator.test.js +223 -43
  173. package/build/utils/utils.d.ts +39 -0
  174. package/build/utils/utils.test.d.ts +1 -0
  175. package/build/utils/versions.d.ts +3 -0
  176. package/build/utils/versions.js +1 -1
  177. package/build/utils/workspaceAuth.d.ts +127 -0
  178. package/build/utils/workspaceAuth.test.d.ts +1 -0
  179. package/build/utils/workspaceAuth.test.js +25 -25
  180. package/build/workspace/index.d.ts +1 -0
  181. package/build/workspace/workspace.d.ts +390 -0
  182. package/build/workspace/workspace.test.d.ts +1 -0
  183. package/node_modules/playwright/lib/mcp/skyramp/traceRecordingBackend.js +14 -2
  184. package/package.json +8 -3
  185. package/build/tools/test-management/uiAnalyzeChangesTool.js +0 -155
  186. package/build/tools/test-management/uiAnalyzeChangesTool.test.js +0 -147
@@ -2,6 +2,7 @@ import { z } from "zod";
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, PATH_PARAM_UUID_GUIDANCE, AUTH_CONFLICT_ERROR_MSG, } from "../test-recommendation/recommendationSections.js";
5
+ import { buildDriftAnalysisPrompt } from "../test-maintenance/drift-analysis-prompt.js";
5
6
  import { getTraceRecordingPromptText } from "../../playwright/traceRecordingPrompt.js";
6
7
  import { isContractConsumerModeEnabled } from "../../utils/featureFlags.js";
7
8
  import { resolveServiceDetailsRef } from "../../utils/utils.js";
@@ -20,9 +21,6 @@ const CONTRACT_MODE_GUIDANCE = CONSUMER_MODE_ENABLED
20
21
  export function getTestbotPrompt(prTitle, prDescription, summaryOutputFile, repositoryPath, baseBranch, maxRecommendations = MAX_RECOMMENDATIONS, maxGenerate = MAX_TESTS_TO_GENERATE, _maxCritical = MAX_CRITICAL_TESTS, // Reserved — accepted for API compat but not yet wired into prompt
21
22
  prNumber, userPrompt, services, stateOutputFile, uiCredentials, testsRepoDir) {
22
23
  maxGenerate = Math.min(Math.max(maxGenerate, 0), maxRecommendations);
23
- // TODO(SKYR-3636 follow-up): migrate Task 1 + Task 2 step bodies to PromptPlan
24
- // (src/prompts/test-recommendation/promptPlan.ts) so step numbers don't have
25
- // to be hand-maintained when steps are added or reordered.
26
24
  // For follow-up requests: emit the @skyramp-testbot header + guardrails + retrieve-recommendations step.
27
25
  // For first-run prompts: emit the full Task 1 analysis + maintenance section.
28
26
  const task1Section = userPrompt
@@ -53,27 +51,21 @@ Use those recommendations as your baseline. Only add or remove tests that the us
53
51
 
54
52
  ## Task 1: Analyze & Maintain
55
53
 
56
- 1. **Pre-flight UI enumeration.** Call \`skyramp_ui_analyze_changes\` with \`repositoryPath\`: "${repositoryPath}"${uiCredentials ? `, \`uiCredentials\`: <use the value from <ui-credentials> in your context>` : ""}. The response returns \`uiContext\` (\`changedFrontendFiles\`, \`candidateUiPages\`) and capture instructions.
54
+ 1. Call \`skyramp_analyze_changes\` with \`repositoryPath\`: "${repositoryPath}", \`scope\`: "branch_diff", \`topN\`: ${maxRecommendations}, \`maxGenerate\`: ${maxGenerate}${baseBranch ? `, \`baseBranch\`: "${baseBranch}"` : ""}${prNumber ? `, \`prNumber\`: ${prNumber}` : ""}${stateOutputFile ? `, \`stateOutputFile\`: "${stateOutputFile}"` : ""}${testsRepoDir ? `, \`testsRepoDir\`: "${testsRepoDir}"` : ""}.
57
55
 
58
- **If the response says "No UI changes detected"** skip ahead to \`skyramp_analyze_changes\`.
56
+ **Follow all instructions returned by \`skyramp_analyze_changes\`** — its response opens with a **UI Blueprint Capture** section that tells you which candidate URLs to capture (and how) before you write any UI recommendation \`reasoning\`.${uiCredentials ? " If the section lists candidate URLs, log in once via the credentials in your <ui-credentials> context before navigating to them." : ""}
59
57
 
60
- **Otherwise:** for each candidate URL in the response${uiCredentials ? " (after logging in via the credentials provided)" : ""}, \`browser_navigate\` to the URL, then \`browser_blueprint\` to capture. The captures stay in your tool-result history — they're the element vocabulary you'll use when writing UI recommendation \`reasoning\` fields in \`skyramp_analyze_changes\`. You do NOT need to thread them back into a tool call.
61
58
 
62
- If a candidate URL 404s or redirects, navigate from the workspace baseUrl and explore. If \`browser_blueprint\` fails on every candidate, proceed to \`skyramp_analyze_changes\` and log an \`issuesFound\` info entry UI recommendations will fall back to source-grounded prose.
59
+ **Feature context check:** After capturing a blueprint per the UI Blueprint Capture instructions, cross-check whether it actually shows the changed feature. Look at the diff — what component, field, or UI element was changed? If that component only renders in a specific context (a particular collection type, a non-empty list, a specific user role, a form that only appears after a prior action), and the blueprint you captured doesn't contain it, navigate to the right context before using this blueprint for recommendations. A blueprint that doesn't show the changed feature produces recommendations that test the wrong thing — for example, recommending tests against a system collection when the PR changed behavior on custom collections. If you can't determine the right context from the diff and workspace config, note it in \`issuesFound\` and fall back to source-grounded recommendations for that URL.
63
60
 
64
- 2. Call \`skyramp_analyze_changes\` with \`repositoryPath\`: "${repositoryPath}", \`scope\`: "branch_diff", \`topN\`: ${maxRecommendations}, \`maxGenerate\`: ${maxGenerate}${baseBranch ? `, \`baseBranch\`: "${baseBranch}"` : ""}${prNumber ? `, \`prNumber\`: ${prNumber}` : ""}${stateOutputFile ? `, \`stateOutputFile\`: "${stateOutputFile}"` : ""}${testsRepoDir ? `, \`testsRepoDir\`: "${testsRepoDir}"` : ""}. Use the blueprints already in your context (from \`skyramp_ui_analyze_changes\`) to ground UI recommendation reasoning.
65
61
  **If \`skyramp_analyze_changes\` returns an error:** retry once only if the error is transient (timeout, network blip, temporary unavailability) — do NOT retry for permanent errors (invalid repository path, missing required parameter, authentication failure). If it fails again, call \`skyramp_submit_report\` with a minimal valid payload: leave all test arrays empty and add the error to \`issuesFound\`. Refer to the \`skyramp_submit_report\` schema for required fields. Do NOT attempt Task 2 without a valid stateFile.
66
- **If all changed files are non-application** (CI/CD, docs, lock files, config, or cosmetic frontend changes such as \`.css\`/\`.scss\` reformats with no observable rendering or interaction change) skip to Task 3 (Submit Report) and follow the zero-test abstention path there.
62
+ **If all changed files are non-application** (CI/CD, docs, lock files, config) skip to Task 3 (Submit Report) with empty arrays and a single \`issuesFound\` entry explaining why (same format as the zero-test path below).
67
63
 
68
- 3. **Maintain existing tests:**
64
+ 2. **Maintain existing tests:**
69
65
 
70
- a. Call \`skyramp_analyze_test_health\` with \`stateFile\` (from \`skyramp_analyze_changes\` output). **Do NOT read application source files** (routes, models, controllers) — all change information you need is in the \`skyramp_analyze_changes\` output and the diff.
66
+ ${buildDriftAnalysisPrompt()}
71
67
 
72
- b. Write \`updateInstructions\` for each UPDATE or REGENERATE test before calling \`skyramp_actions\`articulating the change first prevents file content from overriding diff-based reasoning.
73
-
74
- c. Call \`skyramp_actions\` with \`stateFile\` (from \`skyramp_analyze_changes\` output) and apply the edits it returns.
75
-
76
- 4. **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:
68
+ 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:
77
69
  - Computed fields not recalculated after mutation (e.g. \`total_amount\` unchanged after items are added/removed)
78
70
  - Incomplete CRUD: create without cleanup, update that adds new records without removing old ones
79
71
  - Missing input validation on new endpoints
@@ -81,15 +73,178 @@ Use those recommendations as your baseline. Only add or remove tests that the us
81
73
  - Incorrect arithmetic in business logic (discount calculations, price aggregation)
82
74
  Log each finding in \`issuesFound\` with a \`severity\` (critical/high/medium/low). These bugs should inform your test design in Task 2.
83
75
 
84
- 5. **Blueprint Citation Invariant** (UI test recommendations only). When \`skyramp_analyze_changes\` returned recommendations grounded in the captured blueprints from \`skyramp_ui_analyze_changes\`, every named UI element your recommendation \`reasoning\` mentionsheading text, button label, link text, role descriptionsmust correspond to an element actually present in one of those captured blueprints.
85
-
86
- Write the \`reasoning\` field in **natural prose** that names the elements as a human would describe them ("the Notifications heading", "the disabled Mark all as read button"). Do NOT use internal-identifier syntax like \`role=button, logicalName=...\` — that jargon leaks builder internals into a user-facing report.
87
-
88
- Self-check before submitting: for each UI recommendation's \`reasoning\`, every element you mention by name should appear in one of the captured blueprints. If an element name doesn't appear in any blueprint, either rewrite the reasoning around an element that IS captured, or drop the element reference and describe the test target in higher-level terms ("the empty state of the notifications page"). Do not invent element names from the PR description, source diff, or component name.
89
-
90
- **Non-UI entries (contract / integration / e2e / batch-scenario) are unaffected.** Their \`reasoning\` fields use the pre-existing formats — endpoint paths, request/response schemas, fixture chains. Do not reformat them.
91
-
92
- **No upstream captures available?** If \`skyramp_ui_analyze_changes\` produced no candidate URLs or \`browser_blueprint\` failed on every candidate, all UI recommendations fall back to source-grounded prose drawn from the diff alone. Log the failure mode once in \`issuesFound\`. Non-UI work is unaffected.
76
+ 4. **Blueprint Citation Invariant** (UI test recommendations only). Every named UI element in your recommendation must correspond to an element actually captured in one of the blueprints you captured from the UI Blueprint Capture section of \`skyramp_analyze_changes\`. For **every** UI recommendation both \`newTestsCreated[]\` entries (tests you generated) AND \`additionalRecommendations[]\` entries (deferred tests) with \`testType: "ui"\` populate **four** fields: \`description\`, \`reasoning\`, \`targetElements\`, and \`pageContext\`. \`description\` and \`reasoning\` carry different roles fill BOTH (see Field 4 below). The structured fields (\`targetElements\`, \`pageContext\`) are required on UI recs in BOTH arrays; non-UI recs (contract / integration / e2e / batch-scenario) MUST omit them.
77
+
78
+ **Field 1 \`targetElements\`** (the elements the test targets array, length 1+):
79
+ \`\`\`json
80
+ [
81
+ {
82
+ "role": "button",
83
+ "accessibleName": "Save changes",
84
+ "testId": "save-changes-btn",
85
+ "stableId": null,
86
+ "contextText": null,
87
+ "mutability": "mutable",
88
+ "widgetType": "native"
89
+ }
90
+ ]
91
+ \`\`\`
92
+ - **One element when the test has a single dominant target** (a click, a type, a single visibility check). Most tests fall here — use a length-1 array.
93
+ - **Multiple elements when the test verifies several elements together** — render-state tests (heading + input + button on a form), workflow tests (click button A, assert state appears in element B), or form-fill tests (multiple inputs + submit button). Each element is its own array entry.
94
+ - Each element's fields are lifted verbatim from a captured blueprint element. \`role\`, \`accessibleName\` are required. \`testId\`/\`stableId\`/\`contextText\` are \`null\` when not present in the capture.
95
+ - \`mutability\` — copy from \`blueprint.element.mutability\`. \`'mutable'\` = behavioral-test target; \`'immutable'\` = smoke target.
96
+ - \`widgetType\` — copy from \`blueprint.element.widgetType\`. \`'custom'\` = JavaScript-composite control (Radix, MUI, etc.) requiring click-to-open interaction; \`'native'\` = standard HTML element.
97
+ - \`contextText\` — only for elements inside repeating sections (table rows, list items). Lift from \`repeatingElement.items[].contextText\`. \`null\` otherwise.
98
+
99
+ **Field 2 — \`pageContext\`** (where the test runs):
100
+ \`\`\`json
101
+ { "url": "http://localhost:5173/orders/1", "pageHash": "10:434266447" }
102
+ \`\`\`
103
+ Lift \`url\` and \`pageHash\` from the BlueprintCapture object that fed \`targetElements\`. All entries in \`targetElements\` should be from the same page.
104
+
105
+ **Field 3 — \`reasoning\`** (one sentence — what is verified and why):
106
+
107
+ A **single sentence** (~25-50 words) that names the targeted element(s) and states what the test verifies and why it matters. There is no fixed template — write the sentence in whatever way reads naturally for the test. The constraints below are the only requirements.
108
+
109
+ - **Reference each element from \`targetElements\` by its \`accessibleName\`**. Bold each name. When the element has a \`testId\`, render it in inline code (e.g. \`save-changes-btn\`); fall back to \`stableId\` if no \`testId\`. Omit the parenthetical identifier when neither is captured. For elements inside repeating sections, include \`contextText\` parenthetically (e.g. \`(customer "Acme", total "$199.99")\`).
110
+ - **State what the test verifies or asserts** about those elements — the behavior, render state, business rule, or production risk.
111
+ - **Optionally add a "; this validates …" or "; covers …" clause** naming a formula, business rule, or source-line reference (e.g. "; this validates the \`subtotal * (1 - discountPercent/100)\` formula", "; covers the boundary clamp at EditOrderForm.tsx:42"). Skip the clause for smoke checks where there's no concrete code reference.
112
+ - **Voice matches which array the rec is in.** \`newTestsCreated\` uses descriptive voice ("verifies", "asserts", "documents"). \`additionalRecommendations\` uses recommendatory voice ("would verify", "should assert", "would cover"). Pick a sentence opening that matches; you do not need to start with a fixed phrase.
113
+ - **Multi-element recs** combine elements in one sentence using natural connectives ("and", "after clicking", "alongside") — not enumeration.
114
+ - Do NOT include \`logicalName\`, \`fingerprint\`, \`pageHash\`, \`xpath\`, or other internal identifiers — those leak builder internals into a user-facing report. (\`pageHash\` lives in \`pageContext\` for the verifier; never put it in prose.)
115
+
116
+ **Field 4 — \`description\`** (multi-sentence test walkthrough — REQUIRED for UI recs):
117
+
118
+ The \`description\` field complements \`reasoning\`. While \`reasoning\` is one sentence about what the test verifies and why, \`description\` is **2-4 sentences of free-form prose** describing the test as a walkthrough — the page navigated to, the actions taken, the assertions made, the data values used. Reads like documentation for whoever implements the test.
119
+
120
+ - For \`newTestsCreated\`: describe what the test does step by step. Example: "Navigates to /orders/1, opens the Edit Order form, enters discount=20%, submits, and asserts the real-time total updates to $799.99 before the PUT call."
121
+ - For \`additionalRecommendations\`: describe what the recommended test would do. Example: "Would navigate to a cancelled order's detail page and assert the Edit Order button is not rendered, enforcing the conditional guard added in OrderDetail.tsx."
122
+ - **\`description\` is NOT a duplicate of \`reasoning\`.** Keep them complementary: \`description\` = what the test does (walkthrough), \`reasoning\` = what's verified + why (one sentence).
123
+ - Same jargon-exclusion rules apply (no \`logicalName\` / \`pageHash\` / etc.).
124
+ - Same fallback rules apply: when no blueprint is available, prefix \`description\` and \`reasoning\` with \`[no-blueprint-data]\` and use page/feature-level prose.
125
+
126
+ **Examples (showing the full bundle — copy the structure, not the exact wording):**
127
+
128
+ *Single-element generated test (descriptive voice, with rationale clause):*
129
+ \`\`\`json
130
+ {
131
+ "testType": "ui",
132
+ "description": "Navigates to /orders/1, opens the Edit Order form, enters discount=150, submits, and asserts the validation error 'Discount must be ≤ 100' appears and the form does not close. Documents the boundary-clamp guard in EditOrderForm.tsx.",
133
+ "targetElements": [
134
+ {
135
+ "role": "button", "accessibleName": "Save changes",
136
+ "testId": "save-changes-btn", "stableId": null,
137
+ "contextText": null, "mutability": "mutable", "widgetType": "native"
138
+ }
139
+ ],
140
+ "pageContext": {
141
+ "url": "http://localhost:5173/orders/1",
142
+ "pageHash": "10:434266447"
143
+ },
144
+ "reasoning": "Verifies the **Save changes** button (\`save-changes-btn\`) shows a validation error and prevents form close when discount > 100; this validates the \`Math.min(100, Math.max(0, value))\` clamp at EditOrderForm.tsx:42."
145
+ }
146
+ \`\`\`
147
+
148
+ *Multi-element generated test (render-state, descriptive voice):*
149
+ \`\`\`json
150
+ {
151
+ "testType": "ui",
152
+ "description": "Navigates to /orders/1, clicks Edit Order, and asserts both the 'Edit Order' heading and the Discount (%) input render with discount pre-filled to 0. Smoke check before any user interaction.",
153
+ "targetElements": [
154
+ {
155
+ "role": "heading", "accessibleName": "Edit Order",
156
+ "testId": "edit-order-heading", "stableId": null,
157
+ "contextText": null, "mutability": "immutable", "widgetType": "native"
158
+ },
159
+ {
160
+ "role": "spinbutton", "accessibleName": "Discount (%)",
161
+ "testId": "edit-order-input-discount", "stableId": null,
162
+ "contextText": null, "mutability": "mutable", "widgetType": "native"
163
+ }
164
+ ],
165
+ "pageContext": {
166
+ "url": "http://localhost:5173/orders/1",
167
+ "pageHash": "13:-684516288"
168
+ },
169
+ "reasoning": "Asserts the **Edit Order** heading (\`edit-order-heading\`) and **Discount (%)** input (\`edit-order-input-discount\`) render with discount pre-filled to 0 on form mount; covers the form-mount default-value contract."
170
+ }
171
+ \`\`\`
172
+
173
+ *Negative test (asserts element is NOT rendered), recommendatory voice:*
174
+ \`\`\`json
175
+ {
176
+ "testType": "ui",
177
+ "description": "Would navigate to a cancelled order's detail page and assert the Edit Order button is not rendered. Enforces the conditional-render guard added in OrderDetail.tsx.",
178
+ "targetElements": [
179
+ {
180
+ "role": "button", "accessibleName": "Edit Order",
181
+ "testId": "edit-order-btn", "stableId": null,
182
+ "contextText": null, "mutability": "mutable", "widgetType": "native"
183
+ }
184
+ ],
185
+ "pageContext": {
186
+ "url": "http://localhost:5173/orders/cancelled-99",
187
+ "pageHash": "9:712044820"
188
+ },
189
+ "reasoning": "Would assert the **Edit Order** button (\`edit-order-btn\`) is not rendered on cancelled orders; enforces the conditional-render guard added in OrderDetail.tsx:118."
190
+ }
191
+ \`\`\`
192
+ (For negative tests, ground \`targetElements\` against a captured page where the element DOES render — so the verifier can confirm the citation is real.)
193
+
194
+ *Page-level / lifecycle test (no single dominant element), recommendatory voice:*
195
+ \`\`\`json
196
+ {
197
+ "testType": "ui",
198
+ "description": "Would walk through create → edit → cancel of an order, asserting the Order details heading reflects each state transition (Pending → Editing → Cancelled). Covers the OrderStatus state machine added in this PR.",
199
+ "targetElements": [
200
+ {
201
+ "role": "heading", "accessibleName": "Order details",
202
+ "testId": null, "stableId": null, "contextText": null,
203
+ "mutability": "immutable", "widgetType": "native"
204
+ }
205
+ ],
206
+ "pageContext": {
207
+ "url": "http://localhost:5173/orders/1",
208
+ "pageHash": "11:200115447"
209
+ },
210
+ "reasoning": "Would walk the create-edit-cancel order lifecycle and assert the **Order details** heading reflects each state transition; covers the OrderStatus state machine added in this PR."
211
+ }
212
+ \`\`\`
213
+ (When there's no single focus, anchor on the page heading or the most stable on-page element.)
214
+
215
+ *Single-element additional recommendation (recommendatory voice):*
216
+ \`\`\`json
217
+ {
218
+ "testType": "ui",
219
+ "description": "Would navigate to /admin/notifications when the inbox is empty and assert the Notifications heading renders alongside the empty-state message. Smoke check that the page handles the zero-rows case.",
220
+ "targetElements": [
221
+ {
222
+ "role": "heading", "accessibleName": "Notifications",
223
+ "testId": null, "stableId": null, "contextText": null,
224
+ "mutability": "immutable", "widgetType": "native"
225
+ }
226
+ ],
227
+ "pageContext": {
228
+ "url": "http://localhost:5173/admin/notifications",
229
+ "pageHash": "8:992103445"
230
+ },
231
+ "reasoning": "Would assert the **Notifications** heading renders alongside the empty-state message when the inbox has zero rows; covers the empty-state render path added in NotificationsPage.tsx:31."
232
+ }
233
+ \`\`\`
234
+
235
+ **Self-check before submitting (per UI rec, in both \`newTestsCreated\` and \`additionalRecommendations\`):**
236
+ - **Both \`description\` AND \`reasoning\` are populated.** \`description\` is a 2-4 sentence walkthrough; \`reasoning\` is one sentence about what's verified + why. They must not duplicate each other.
237
+ - Every entry in \`targetElements\` must appear verbatim in a captured blueprint element (matched on \`accessibleName\` + \`role\`).
238
+ - Every element name appearing in \`reasoning\` must match an entry's \`accessibleName\` exactly. Every backticked identifier in \`reasoning\` must come from a \`testId\` or \`stableId\` of an entry.
239
+ - **Voice matches the array:** \`newTestsCreated\` uses descriptive voice ("verifies", "asserts", "documents"). \`additionalRecommendations\` uses recommendatory voice ("would verify", "should assert", "would cover").
240
+ - \`pageContext.url\` must equal the URL of the BlueprintCapture you lifted \`targetElements\` from.
241
+ - Never mix two elements' fields in one \`targetElements\` entry — each entry is one captured element.
242
+ - No internal jargon in \`reasoning\` or \`description\`: no \`logicalName\`, \`fingerprint\`, \`pageHash\`, \`xpath\`.
243
+ - If any check fails, re-capture and verify, or drop that recommendation.
244
+
245
+ **Non-UI entries (contract / integration / e2e / batch-scenario) are unaffected.** Use their pre-existing reasoning formats. Do NOT add \`targetElements\` or \`pageContext\` to non-UI entries — the schema rejects them.
246
+
247
+ **No upstream captures available?** Set \`targetElements\` to \`null\`, omit \`pageContext\`, and prefix \`description\` and \`reasoning\` with \`[no-blueprint-data]\`. Use page/feature-level prose; don't cite specific element names without grounding. Apply the marker per entry, not per PR — affected recs only. Log capture failures in \`issuesFound\` (one info-severity entry per failure mode, naming counts). Don't pre-emptively fall back without attempting capture first. Non-UI work is unaffected.
93
248
 
94
249
  ---`;
95
250
  const serviceContext = services?.length ? buildServiceContext(services) : '';
@@ -122,7 +277,7 @@ ${userPrompt ? "Generate only the tests that the user requested from the Additio
122
277
  - **MANDATORY — use the pre-ranked GENERATE list as-is**: The Execution Plan's GENERATE section governs ADD actions. You MUST generate exactly those scenarios in the exact order listed. Do NOT substitute, rename, or replace a GENERATE item. If parameter grounding uncovers a distinct bug-catching scenario not already in the GENERATE or ADDITIONAL list, 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.
123
278
  - 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\`.
124
279
  - 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:
125
- - If the existing test source is \`[external]\`, skip the resource entirely — the external test already provides coverage. Do NOT generate a new parallel test file for it.
280
+ - 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.
126
281
  - If the existing test is tagged \`[skyramp]\`, apply type-specific rules:
127
282
  - Contract tests: UPDATE the existing Skyramp test file (add the new method's test cases). A new test case is a new test even if the file already exists — report in \`newTestsCreated\` and count toward the budget.
128
283
  - Integration/scenario tests: Always generate as a new file via the scenario pipeline (\`skyramp_batch_scenario_test_generation\` → \`skyramp_integration_test_generation\`), even if an existing integration test covers the same resource. A new multi-step scenario (e.g. create → PATCH → verify recalculation) is a distinct test file. Report in \`newTestsCreated\` and count toward the budget.
@@ -139,7 +294,6 @@ ${userPrompt ? "Generate only the tests that the user requested from the Additio
139
294
  2. If no production file imports, re-exports, or renders it, the component has no DOM node in the running app → unintegrated.
140
295
  3. **Exception**: if the same PR also adds a route/page file (e.g. under Next.js \`pages/\` or \`app/\`) that imports the component, the route IS the integration point — test through it.
141
296
  **Never** apply the unintegrated heuristic to framework route/entrypoint files themselves — those are always reachable by convention.
142
- - **(c) Cosmetic-only frontend change** — the frontend files in the diff are purely cosmetic with no observable rendering or interaction change (e.g. a \`.css\`/\`.scss\` reformat: property reordering, comment/whitespace edits, \`0px\`→\`0\`). \`uiContext.changedFrontendFiles\` being non-empty does NOT override this — that signal only means a frontend file changed, not that behavior changed. Take the zero-test abstention path in Task 3; do NOT record a trace just to satisfy the mandate.
143
297
  **Never** generate tests for unrelated pages as a substitute for an unintegrated component.
144
298
  This rule takes priority over generating additional backend-only tests.
145
299
  - **Always generate a test for critical bugs, even if it will fail.** When a GENERATE-tagged item targets a page or endpoint with a known bug, do NOT skip it because you expect the test to fail — a failing test that documents a bug is more valuable than a text-only description. This applies within the existing GENERATE budget; do not add extra tests beyond the plan.
@@ -226,6 +380,7 @@ ${CONTRACT_MODE_GUIDANCE}
226
380
  - \`skyramp_export_zip\` outputPath: \`${repositoryPath}/.skyramp/<test_name>_trace.zip\`
227
381
  - \`skyramp_ui_test_generation\`: set \`modularizeCode: false\`
228
382
  - **\`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.
383
+ - **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.
229
384
  If \`browser_navigate\` fails (app not running / connection refused), move to \`additionalRecommendations\` with the failure reason.
230
385
  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.
231
386
  **Strategic assertions** — key checkpoints only, 3 to 5 per test:
@@ -238,9 +393,9 @@ ${CONTRACT_MODE_GUIDANCE}
238
393
 
239
394
  **Capture-act-capture (applies only when recording a UI trace):**
240
395
 
241
- **Skip this entire section if \`uiContext\` was absent or \`changedFrontendFiles\` was empty in the \`skyramp_ui_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.
396
+ **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.
242
397
 
243
- **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 upstream blueprints from \`skyramp_ui_analyze_changes\`; Task 2's capture-act-capture is for the trace's own assertions, not for retroactively rewriting recommendation reasoning.
398
+ **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.
244
399
 
245
400
  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.
246
401
 
@@ -254,15 +409,23 @@ ${CONTRACT_MODE_GUIDANCE}
254
409
 
255
410
  1. **Before** the action: \`browser_blueprint\`. Identify the semantic target by \`role\`, \`accessibleName\`, and \`stableId\`/\`testId\`.
256
411
 
257
- 2. If the target's \`widgetType\` is \`"custom"\` or \`"unknown"\`: \`browser_widget_contract_lookup\` with the element's \`fingerprint\` and \`ref\`. On \`"found"\`, execute the contract steps. On \`"needs_inference"\`, fall through to snapshot-driven trial clicks (\`browser_wait_for\` between retries). Inference-and-cache is out of scope for this slice, so don't attempt to synthesize and cache contracts.
412
+ 2. If the target's \`widgetType\` is \`"custom"\` or \`"unknown"\`, it is a JavaScript-composite control (e.g. a Radix/MUI combobox, a date picker, a custom multi-select) that does not behave like a native HTML element. Do NOT call \`browser_fill\` or \`browser_select_option\` directly on it. Instead: click the trigger element first, use \`browser_wait_for\` to wait for the overlay/dropdown to appear, then interact with its contents. Use \`browser_snapshot\` to inspect what appeared before choosing what to click next.
258
413
 
259
414
  3. Execute the action via \`browser_click\` / \`browser_type\` / \`browser_navigate\`. The \`ref\` comes from \`browser_snapshot\` as today.
260
415
 
261
416
  4. **After** the action: \`browser_blueprint\` again. The response shape depends on whether the action navigated:
262
- - **Same URL (modal/tab/in-place mutation):** \`{ isFullCapture: false, pageHash, previousPageHash, delta, possibleAssertions }\`. The \`delta\` field contains \`elementsAdded\`, \`elementsRemoved\`, \`textChanges\`, \`repeatingCountChanges\`. The \`possibleAssertions\` field is a mechanical translation of those entries into Playwright \`expect(...)\` candidates — see step 5. You do **not** need to call \`browser_blueprint_diff\` here — that tool is only for cross-URL comparisons. An empty delta (all arrays empty) is itself a meaningful signal: the action did not change observable DOM (e.g. a silent failure the test should catch).
263
- - **Navigated to a new URL** (e.g. router transition, link click, programmatic \`browser_navigate\`): \`{ isFullCapture: true, pageHash, blueprint }\` — a fresh full capture of the new page. No \`possibleAssertions\` here (no delta to translate). If you need a structured cross-URL diff, call \`browser_blueprint_diff(beforeBlueprint, afterBlueprint)\` explicitly; otherwise search the new blueprint for the elements your assertion will target.
417
+ - **Same URL (modal/tab/in-place mutation):** \`{ isFullCapture: false, pageHash, previousPageHash, delta, possibleAssertions }\`. The \`delta\` field contains \`elementsAdded\`, \`elementsRemoved\`, \`textChanges\`, \`repeatingCountChanges\`. The \`possibleAssertions\` field is a mechanical translation of those entries into Playwright \`expect(...)\` candidates — see step 5. An empty delta (all arrays empty) is itself a meaningful signal: the action did not change observable DOM (e.g. a silent failure the test should catch).
418
+ - **Navigated to a new URL** (e.g. router transition, link click, programmatic \`browser_navigate\`): \`{ isFullCapture: true, pageHash, blueprint }\` — a fresh full capture of the new page. No \`possibleAssertions\` here (no delta to translate). Search the new blueprint for the elements your assertion will target.
419
+
420
+ 5. **The second \`browser_blueprint\` response (the capture after the action) includes a \`possibleAssertions[]\` array — mechanical translations of delta entries into ready-to-use Playwright \`expect(...)\` candidates.** Each entry has \`{ code, rationale, tier }\`. Use tier to drive your assertion choices:
421
+
422
+ - **HIGH** — always assert on these. HIGH candidates translate state-observable changes: text content that updated (\`toHaveText\`), URL transitions (\`toHaveURL\`), count changes (\`toHaveCount\`). If a HIGH-tier candidate exists after an action, it is the primary assertion for that action. (If you followed the stable-state rule above and waited for real data before capturing, HIGH candidates will already contain real values — not loading state.)
423
+ - **MEDIUM** — use as supplementary assertions. MEDIUM candidates translate structural changes: an element appeared (\`toBeVisible\`) or disappeared (\`not.toBeVisible\`) after the action. Useful alongside HIGH assertions but insufficient alone for state-changing actions.
424
+ - **LOW** — treat as context only. LOW candidates are incidental visibility checks on elements that happened to be on-page. Do not use LOW candidates as the sole assertion on a state-changing action.
425
+
426
+ **If no HIGH-tier candidate exists after a state-changing action** (form submit, button click that mutates data), write a targeted assertion yourself — look in the second blueprint capture for computed values, status fields, or derived text that changed. One well-targeted \`toHaveText\` or \`toHaveValue\` beats five \`toBeVisible\` checks.
264
427
 
265
- 5. **The AFTER response includes a \`possibleAssertions[]\` array — these are mechanical translations of delta entries into Playwright \`expect(...)\` candidates, available if any of them happen to match the assertion you'd write anyway.** Each entry has \`{ code, rationale, tier }\` where \`code\` is ready-to-use, \`rationale\` explains the source delta entry, and \`tier\` is HIGH/MEDIUM/LOW. **Read them, but do not feel obligated to use them.** They are heavily biased toward visibility checks (\`toBeVisible\` / \`not.toBeVisible\`), which are often shallow assertions — a passing visibility check does not mean the feature works. The right assertion target depends on what the test is *for*: if you're testing a state-changing action (form submit, button click that mutates data), prefer assertions on the post-action state (computed values, count changes, server-derived fields). Use a \`possibleAssertions\` candidate when its \`code\` already expresses what you would have written; ignore the array entirely when none of the candidates match the test's actual purpose. Adding visibility assertions just because they're available reduces test value; one well-targeted assertion beats five visibility checks of incidental DOM elements (modal scaffolding, navigation chrome). The pre-existing rule still applies: **at least one \`browser_assert\` per page navigated, verifying a business outcome — not just that an element is visible.**
428
+ **The pre-existing rule still applies:** at least one \`browser_assert\` per page navigated, verifying a business outcome — not just that an element is visible.
266
429
 
267
430
  **The Blueprint Citation Invariant applies during recording too.** Every assertion you emit cites element names — those names must come from blueprint captures, not invention. For N user-intent-level actions, the reference target is N+1 \`browser_blueprint\` calls (the first returns full, the rest return deltas). Traces that follow the pattern produce assertions grounded in observable state changes; traces that skip captures fall back to author-inferred assertions and risk citing names that don't exist in the rendered DOM.
268
431
 
@@ -299,7 +462,7 @@ Do NOT use \`page.waitForTimeout()\` with fixed delays. Do NOT retry more than o
299
462
  **After generation, you MUST do exactly these steps — nothing more, nothing less:**
300
463
  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.
301
464
  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.
302
- 3. **[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.
465
+ 3. **[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.
303
466
  4. **Wait**: Do NOT proceed to test execution until steps 1–3 are complete and the verification checklist in the \`skyramp_enhance_assertions\` tool result has been validated for EVERY generated test file.
304
467
  Do not make any changes other than 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.
305
468
 
@@ -336,6 +499,7 @@ Call \`skyramp_submit_report\` with \`summaryOutputFile\`: "${summaryOutputFile}
336
499
  - **additionalRecommendations**: AT MOST ${maxRecommendations - maxGenerate} items.
337
500
  - For \`testType: "contract"\` entries: **\`primaryEndpoint\` is required** (e.g. \`"GET /api/v1/users/{user_id}"\`). The tool will reject the submission without it — do not omit it or you will be forced to resubmit.
338
501
  - For \`testType: "integration"\` or \`"e2e"\` entries: omit \`primaryEndpoint\` — use \`description\` to list the endpoints involved instead.
502
+ - **testMaintenance**: Use \`[]\` **only** if no existing Skyramp tests were found in the repository. If existing tests were found (any score), include one entry per test. Set \`action\` to the exact drift action you chose from the Action Decision Matrix (\`UPDATE\`, \`REGENERATE\`, \`DELETE\`, \`VERIFY\`, or \`IGNORE\`). For UPDATE/REGENERATE/DELETE tests that were modified and executed, populate all fields from real before/after execution results. For VERIFY/IGNORE tests (not modified), derive \`beforeStatus\` from the \`skyramp_analyze_test_health\` health score (typically \`"Pass"\` if drift score is 0 and no health issues were flagged), set \`afterStatus\` to \`"Skipped"\`, and use \`afterDetails\` to explain why (e.g. "IGNORE: drift score 0 — endpoint not modified in this PR"). Do **not** add entries for tests that were not returned by the health analysis.
339
503
 
340
504
  ---
341
505
 
@@ -0,0 +1 @@
1
+ export {};
@@ -1,13 +1,14 @@
1
- jest.mock("../../workspace/workspace.js", () => ({
1
+ import { jest } from "@jest/globals";
2
+ jest.unstable_mockModule("../../workspace/workspace.js", () => ({
2
3
  WorkspaceConfigManager: jest.fn(),
3
4
  }));
4
- jest.mock("../../services/AnalyticsService.js", () => ({
5
+ jest.unstable_mockModule("../../services/AnalyticsService.js", () => ({
5
6
  AnalyticsService: { pushMCPToolEvent: jest.fn() },
6
7
  }));
7
- jest.mock("../../playwright/traceRecordingPrompt.js", () => ({
8
+ jest.unstable_mockModule("../../playwright/traceRecordingPrompt.js", () => ({
8
9
  getTraceRecordingPromptText: () => "",
9
10
  }));
10
- import { getTestbotPrompt } from "./testbot-prompts.js";
11
+ const { getTestbotPrompt, buildWorkspaceRecoveryPrefix } = await import("./testbot-prompts.js");
11
12
  // Minimal args to invoke getTestbotPrompt — only services matter for these tests
12
13
  const baseArgs = {
13
14
  prTitle: "Test PR",
@@ -216,13 +217,11 @@ describe("drift analysis — runtime tool call (step 3)", () => {
216
217
  const prompt = basePrompt();
217
218
  expect(prompt).toContain("skyramp_actions");
218
219
  });
219
- it("step 3 appears inside Task 1, before Task 2", () => {
220
+ it("skyramp_analyze_test_health is referenced in the prompt", () => {
221
+ // After the unified maintenance refactor, analyze_test_health is referenced
222
+ // in the testMaintenance submission guidance rather than as an explicit step.
220
223
  const prompt = basePrompt();
221
- const task1Pos = prompt.indexOf("## Task 1");
222
- const healthPos = prompt.indexOf("skyramp_analyze_test_health");
223
- const task2Pos = prompt.indexOf("## Task 2");
224
- expect(healthPos).toBeGreaterThan(task1Pos);
225
- expect(healthPos).toBeLessThan(task2Pos);
224
+ expect(prompt).toContain("skyramp_analyze_test_health");
226
225
  });
227
226
  it("does not contain the build-time embedded drift_analysis_rules content (Action Decision Tree)", () => {
228
227
  // The rules are now fetched at runtime via skyramp_analyze_test_health —
@@ -237,57 +236,7 @@ describe("drift analysis — runtime tool call (step 3)", () => {
237
236
  expect(prompt).not.toContain("You are acting as a Skyramp Integration Architect");
238
237
  });
239
238
  });
240
- describe("UI grounding via Task 2 capture-act-capture", () => {
241
- it("surfaces uiContext as guidance, not a contract", () => {
242
- const prompt = getTestbotPrompt(baseArgs.prTitle, baseArgs.prDescription, baseArgs.summaryOutputFile, baseArgs.repositoryPath);
243
- // uiContext fields are explained inline so the agent knows what to do with
244
- // them. Step 1 provides candidate URLs but gives fallback instructions
245
- // ("navigate from the workspace baseUrl and explore") for 404s/redirects,
246
- // treating candidates as guidance not a rigid contract.
247
- expect(prompt).toContain("uiContext");
248
- expect(prompt).toContain("candidateUiPages");
249
- expect(prompt).toContain("changedFrontendFiles");
250
- expect(prompt).toMatch(/navigate from the workspace baseUrl and explore/i);
251
- });
252
- it("step 5 enforces Blueprint Citation Invariant in natural prose", () => {
253
- const prompt = getTestbotPrompt(baseArgs.prTitle, baseArgs.prDescription, baseArgs.summaryOutputFile, baseArgs.repositoryPath);
254
- // Step 5 is the citation-invariant guardrail, not a "fill in tuples"
255
- // post-processing step (slice 4 cleanup: recs are grounded upstream).
256
- expect(prompt).toContain("Blueprint Citation Invariant");
257
- // Reasoning must be natural prose, NOT internal-identifier syntax.
258
- expect(prompt).toMatch(/natural prose/i);
259
- expect(prompt).toMatch(/internal-identifier syntax/i);
260
- });
261
- it("Task 2 no longer instructs the agent to fill in tuples post-hoc", () => {
262
- const prompt = getTestbotPrompt(baseArgs.prTitle, baseArgs.prDescription, baseArgs.summaryOutputFile, baseArgs.repositoryPath);
263
- // After slice 4 cleanup: Task 2 captures are for trace recording's own
264
- // assertions, not for retroactively rewriting recommendation reasoning.
265
- // The phrase "fill in tuples" must NOT appear anywhere in the prompt.
266
- expect(prompt).not.toMatch(/fill in tuples/i);
267
- expect(prompt).not.toMatch(/return to step 5 and fill/i);
268
- });
269
- it("Task 2 step 5 mentions possibleAssertions as available, NOT as required (slice 5.5 softening)", () => {
270
- const prompt = getTestbotPrompt(baseArgs.prTitle, baseArgs.prDescription, baseArgs.summaryOutputFile, baseArgs.repositoryPath);
271
- // Slice 5: AFTER-action browser_blueprint response includes
272
- // possibleAssertions[] — mechanically translated candidates.
273
- expect(prompt).toContain("possibleAssertions");
274
- // Slice 5.5: prompt explicitly tells the agent NOT to feel obligated.
275
- // Two P09 runs with the prior "emit at least one" directive showed the
276
- // agent over-using shallow visibility assertions at the expense of
277
- // integration-test depth. The softened version says: read them, use
278
- // when they happen to match what you'd write anyway, ignore otherwise.
279
- expect(prompt).toMatch(/do not feel obligated/i);
280
- expect(prompt).toMatch(/biased toward visibility/i);
281
- // The candidate format is still documented.
282
- expect(prompt).toMatch(/\bcode\b.*\brationale\b.*\btier\b/i);
283
- // The pre-existing "at least one browser_assert per page navigated"
284
- // rule should be preserved (it's about meaningful business-outcome
285
- // assertions, not about possibleAssertions).
286
- expect(prompt).toMatch(/at least one .browser_assert. per page navigated/i);
287
- });
288
- });
289
239
  describe("buildWorkspaceRecoveryPrefix", () => {
290
- const { buildWorkspaceRecoveryPrefix } = require("./testbot-prompts.js");
291
240
  it("includes repositoryPath in both init_scan and init_workspace instructions", () => {
292
241
  const prefix = buildWorkspaceRecoveryPrefix("/home/user/repo");
293
242
  expect(prefix).toContain('skyramp_init_scan with workspacePath "/home/user/repo"');
@@ -348,20 +297,13 @@ describe("testsRepoDir in getTestbotPrompt", () => {
348
297
  });
349
298
  });
350
299
  describe("testbot prompt blueprint-grounded recommendations (slice 4)", () => {
351
- it("instructs the agent to call skyramp_ui_analyze_changes before skyramp_analyze_changes", () => {
352
- const prompt = getTestbotPrompt(baseArgs.prTitle, baseArgs.prDescription, baseArgs.summaryOutputFile, baseArgs.repositoryPath);
353
- const uiacIdx = prompt.indexOf("skyramp_ui_analyze_changes");
354
- const acIdx = prompt.indexOf("skyramp_analyze_changes");
355
- expect(uiacIdx).toBeGreaterThan(-1);
356
- expect(acIdx).toBeGreaterThan(uiacIdx);
357
- });
358
- it("Task 1 step 1 instructs the agent to capture blueprints (without threading them through a param)", () => {
300
+ it("uses a single skyramp_analyze_changes call (no separate pre-flight tool, no blueprint threading)", () => {
359
301
  const prompt = getTestbotPrompt(baseArgs.prTitle, baseArgs.prDescription, baseArgs.summaryOutputFile, baseArgs.repositoryPath);
360
- // Captures stay in tool-result history; analyze_changes returns the
361
- // authoring rules and the agent supplies the captured vocabulary.
362
- expect(prompt).toMatch(/browser_blueprint`?\s*to capture/i);
363
- expect(prompt).toMatch(/tool-result history/i);
364
- // Make sure we removed the old capturedBlueprints threading directive.
302
+ // The standalone pre-flight tool is gone its work folded into analyze_changes.
303
+ expect(prompt).not.toContain("skyramp_ui_analyze_changes");
304
+ expect(prompt).toContain("skyramp_analyze_changes");
305
+ // Captures stay in tool-result history — the old capturedBlueprints
306
+ // threading directive must be gone.
365
307
  expect(prompt).not.toMatch(/capturedBlueprints/);
366
308
  });
367
309
  // Flutter support — both the generalised UI trigger wording and the
@@ -0,0 +1,17 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare const ANALYSIS_URI_PREFIX = "skyramp://analysis";
3
+ /**
4
+ * Register MCP Resources for analysis data access.
5
+ *
6
+ * Resources provide read-only, token-efficient access to analysis data
7
+ * via sessionId. The state file is an internal backing store.
8
+ *
9
+ * URI scheme:
10
+ * skyramp://analysis/{sessionId}/summary
11
+ * skyramp://analysis/{sessionId}/endpoints
12
+ * skyramp://analysis/{sessionId}/endpoints/{+path}
13
+ * skyramp://analysis/{sessionId}/endpoints/{+path}/{method}
14
+ * skyramp://analysis/{sessionId}/scenarios
15
+ * skyramp://analysis/{sessionId}/diff
16
+ */
17
+ export declare function registerAnalysisResources(server: McpServer): void;
@@ -0,0 +1,2 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare function registerProgressResource(server: McpServer): void;
@@ -0,0 +1,2 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare function registerTestbotResource(server: McpServer): void;
@@ -0,0 +1,13 @@
1
+ import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
2
+ export declare class AnalyticsService {
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>;
5
+ /**
6
+ * Track server crash events
7
+ */
8
+ static pushServerCrashEvent(crashType: string, errorMessage: string, errorStack?: string): Promise<void>;
9
+ /**
10
+ * Track tool timeout events
11
+ */
12
+ static pushToolTimeoutEvent(toolName: string, timeoutMs: number, params: Record<string, string>): Promise<void>;
13
+ }
@@ -0,0 +1 @@
1
+ export {};