@skyramp/mcp 0.3.8 → 0.4.0-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 (229) hide show
  1. package/build/commands/commandLibrary.d.ts +1 -1
  2. package/build/commands/commandLibrary.js +3 -3
  3. package/build/commands/recommendTestsAndExecuteCommand.d.ts +1 -1
  4. package/build/commands/recommendTestsAndExecuteCommand.js +35 -20
  5. package/build/commands/testThisEndpointCommand.js +35 -19
  6. package/build/index.js +9 -3
  7. package/build/playwright/blueprintDigest.d.ts +15 -0
  8. package/build/playwright/blueprintDigest.js +152 -0
  9. package/build/playwright/blueprintDigestStore.d.ts +31 -0
  10. package/build/playwright/blueprintDigestStore.js +117 -0
  11. package/build/playwright/registerPlaywrightTools.js +60 -12
  12. package/build/playwright/traceRecordingPrompt.js +8 -7
  13. package/build/prompts/enhance-assertions/sharedAssertionRules.js +9 -8
  14. package/build/prompts/enhance-assertions/uiAssertionsPrompt.js +24 -2
  15. package/build/prompts/promptAssets.d.ts +20 -0
  16. package/build/prompts/promptAssets.js +55 -0
  17. package/build/prompts/sut-setup/modes/dockerComposePrompt.js +19 -5
  18. package/build/prompts/test-maintenance/actionsInstructions.d.ts +4 -0
  19. package/build/prompts/test-maintenance/actionsInstructions.js +14 -2
  20. package/build/prompts/test-maintenance/drift-analysis-prompt.d.ts +0 -10
  21. package/build/prompts/test-maintenance/drift-analysis-prompt.js +2 -11
  22. package/build/prompts/test-maintenance/uiDriftAnalysisSections.js +8 -4
  23. package/build/prompts/test-recommendation/diffExecutionPlan.d.ts +5 -22
  24. package/build/prompts/test-recommendation/diffExecutionPlan.js +37 -465
  25. package/build/prompts/test-recommendation/recommendationSections.d.ts +7 -17
  26. package/build/prompts/test-recommendation/recommendationSections.js +67 -309
  27. package/build/prompts/test-recommendation/recommendationShared.d.ts +19 -47
  28. package/build/prompts/test-recommendation/recommendationShared.js +49 -155
  29. package/build/prompts/test-recommendation/registerRecommendTestsPrompt.d.ts +0 -5
  30. package/build/prompts/test-recommendation/registerRecommendTestsPrompt.js +10 -153
  31. package/build/prompts/test-recommendation/test-recommendation-prompt.d.ts +2 -29
  32. package/build/prompts/test-recommendation/test-recommendation-prompt.js +32 -457
  33. package/build/prompts/testbot/planDeclarations.d.ts +6 -0
  34. package/build/prompts/testbot/planDeclarations.js +9 -0
  35. package/build/prompts/testbot/testbot-prompts.d.ts +8 -0
  36. package/build/prompts/testbot/testbot-prompts.js +256 -381
  37. package/build/recommendation/answers.d.ts +35 -0
  38. package/build/recommendation/answers.js +96 -0
  39. package/build/recommendation/registerPlan.d.ts +49 -0
  40. package/build/recommendation/registerPlan.js +117 -0
  41. package/build/recommendation/runVerifiers.d.ts +10 -0
  42. package/build/recommendation/runVerifiers.js +49 -0
  43. package/build/recommendation/subjectStep.d.ts +42 -0
  44. package/build/recommendation/subjectStep.js +86 -0
  45. package/build/recommendation/types.d.ts +163 -0
  46. package/build/recommendation/types.js +20 -0
  47. package/build/recommendation/verifierContracts.d.ts +382 -0
  48. package/build/recommendation/verifierContracts.js +263 -0
  49. package/build/recommendation/verifiers/changedFile.d.ts +2 -0
  50. package/build/recommendation/verifiers/changedFile.js +82 -0
  51. package/build/recommendation/verifiers/citedPath.d.ts +12 -0
  52. package/build/recommendation/verifiers/citedPath.js +35 -0
  53. package/build/recommendation/verifiers/coverage.d.ts +7 -0
  54. package/build/recommendation/verifiers/coverage.js +617 -0
  55. package/build/recommendation/verifiers/deliveredMatchesPlan.d.ts +11 -0
  56. package/build/recommendation/verifiers/deliveredMatchesPlan.js +33 -0
  57. package/build/recommendation/verifiers/endpointGrounded.d.ts +17 -0
  58. package/build/recommendation/verifiers/endpointGrounded.js +128 -0
  59. package/build/recommendation/verifiers/existingCoverage.d.ts +6 -0
  60. package/build/recommendation/verifiers/existingCoverage.js +51 -0
  61. package/build/recommendation/verifiers/expectedOutcome.d.ts +31 -0
  62. package/build/recommendation/verifiers/expectedOutcome.js +105 -0
  63. package/build/recommendation/verifiers/removedElementGuarded.d.ts +2 -0
  64. package/build/recommendation/verifiers/removedElementGuarded.js +57 -0
  65. package/build/recommendation/verifiers/reportedCategory.d.ts +26 -0
  66. package/build/recommendation/verifiers/reportedCategory.js +84 -0
  67. package/build/recommendation/verifiers/screenRoute.d.ts +10 -0
  68. package/build/recommendation/verifiers/screenRoute.js +118 -0
  69. package/build/recommendation/verifiers/statedDifference.d.ts +6 -0
  70. package/build/recommendation/verifiers/statedDifference.js +140 -0
  71. package/build/recommendation/verifiers/uiElementGrounded.d.ts +7 -0
  72. package/build/recommendation/verifiers/uiElementGrounded.js +318 -0
  73. package/build/resources/analysisResources.js +1 -114
  74. package/build/resources/testbotResource.js +23 -13
  75. package/build/services/ModularizationService.js +2 -1
  76. package/build/services/TestDiscoveryService.d.ts +3 -72
  77. package/build/services/TestDiscoveryService.js +10 -303
  78. package/build/services/containerEnv.d.ts +1 -1
  79. package/build/services/containerEnv.js +12 -0
  80. package/build/skills/fixTestImportErrorsSkill.d.ts +13 -0
  81. package/build/skills/fixTestImportErrorsSkill.js +20 -0
  82. package/build/toolNames.d.ts +1 -0
  83. package/build/toolNames.js +1 -0
  84. package/build/tools/code-refactor/enhanceAssertionsTool.js +3 -3
  85. package/build/tools/code-refactor/modularizationTool.js +2 -1
  86. package/build/tools/executeSkyrampTestTool.d.ts +80 -0
  87. package/build/tools/executeSkyrampTestTool.js +246 -19
  88. package/build/tools/generate-tests/generateBatchScenarioRestTool.js +6 -0
  89. package/build/tools/generate-tests/generateContractRestTool.js +3 -3
  90. package/build/tools/generate-tests/planGuard.d.ts +2 -2
  91. package/build/tools/generate-tests/planGuard.js +78 -18
  92. package/build/tools/one-click/oneClickTool.d.ts +0 -1
  93. package/build/tools/one-click/oneClickTool.js +0 -5
  94. package/build/tools/submitReportTool.d.ts +48 -42
  95. package/build/tools/submitReportTool.js +576 -193
  96. package/build/tools/test-management/actionsTool.js +72 -4
  97. package/build/tools/test-management/analyzeChangesTool.d.ts +144 -48
  98. package/build/tools/test-management/analyzeChangesTool.js +212 -1219
  99. package/build/tools/test-management/analyzeTestHealthTool.js +13 -24
  100. package/build/tools/test-management/index.d.ts +1 -0
  101. package/build/tools/test-management/index.js +1 -0
  102. package/build/tools/test-management/registerTestPlanTool.d.ts +795 -172
  103. package/build/tools/test-management/registerTestPlanTool.js +609 -542
  104. package/build/tools/test-management/resolveScreenTool.d.ts +75 -0
  105. package/build/tools/test-management/resolveScreenTool.js +289 -0
  106. package/build/types/BlueprintDigest.d.ts +34 -0
  107. package/build/types/BlueprintDigest.js +1 -0
  108. package/build/types/RepositoryAnalysis.d.ts +20 -1559
  109. package/build/types/RepositoryAnalysis.js +2 -58
  110. package/build/types/StepMethod.d.ts +40 -0
  111. package/build/types/StepMethod.js +77 -0
  112. package/build/types/TestAnalysis.d.ts +12 -0
  113. package/build/types/TestExecution.d.ts +4 -0
  114. package/build/types/TestRecommendation.d.ts +24 -24
  115. package/build/types/TestRecommendation.js +91 -89
  116. package/build/types/TestbotPromptOptions.d.ts +0 -4
  117. package/build/types/TestbotReport.d.ts +64 -2
  118. package/build/utils/AnalysisStateManager.d.ts +79 -113
  119. package/build/utils/AnalysisStateManager.js +147 -57
  120. package/build/utils/assertion-verify/api-shared-lints.js +1 -1
  121. package/build/utils/assertion-verify/metrics.js +85 -36
  122. package/build/utils/assertion-verify/ui-lints.d.ts +0 -5
  123. package/build/utils/assertion-verify/ui-lints.js +32 -0
  124. package/build/utils/branchDiff.d.ts +63 -31
  125. package/build/utils/branchDiff.js +242 -94
  126. package/build/utils/containedPath.d.ts +18 -0
  127. package/build/utils/containedPath.js +73 -0
  128. package/build/utils/dartRouteExtractor.d.ts +18 -34
  129. package/build/utils/dartRouteExtractor.js +101 -173
  130. package/build/utils/featureFlags.d.ts +12 -0
  131. package/build/utils/featureFlags.js +14 -0
  132. package/build/utils/frontendSelectors.d.ts +48 -27
  133. package/build/utils/frontendSelectors.js +241 -80
  134. package/build/utils/pathMatching.d.ts +2 -4
  135. package/build/utils/pathMatching.js +2 -4
  136. package/build/utils/planMatchKeys.d.ts +38 -47
  137. package/build/utils/planMatchKeys.js +143 -81
  138. package/build/utils/rebaselineSnapshots.d.ts +24 -0
  139. package/build/utils/rebaselineSnapshots.js +65 -0
  140. package/build/utils/removedUiElements.d.ts +22 -0
  141. package/build/utils/removedUiElements.js +106 -0
  142. package/build/utils/reportVerification.d.ts +2 -6
  143. package/build/utils/reportVerification.js +61 -2
  144. package/build/utils/screenRoutes.d.ts +66 -0
  145. package/build/utils/screenRoutes.js +727 -0
  146. package/build/utils/sourceRouteExtractor.js +320 -112
  147. package/build/utils/testFileClassification.d.ts +11 -2
  148. package/build/utils/testFileClassification.js +44 -2
  149. package/build/utils/testFixtures.d.ts +5 -0
  150. package/build/utils/testFixtures.js +13 -0
  151. package/build/utils/utils.d.ts +0 -1
  152. package/build/utils/utils.js +0 -11
  153. package/build/utils/versions.d.ts +3 -3
  154. package/build/utils/versions.js +1 -1
  155. package/build/workspace/workspace.d.ts +12 -12
  156. package/node_modules/playwright/lib/mcp/skyramp/assertHiddenTool.js +56 -0
  157. package/node_modules/playwright/lib/mcp/skyramp/assertTool.js +2 -1
  158. package/node_modules/playwright/lib/mcp/skyramp/loadTraceTool.js +10 -0
  159. package/node_modules/playwright/lib/mcp/skyramp/skyRampImport.js +4 -1
  160. package/node_modules/playwright/lib/mcp/skyramp/traceRecordingBackend.js +160 -1
  161. package/node_modules/playwright/lib/mcp/test/skyRampExport.js +4 -2
  162. package/node_modules/playwright/node_modules/playwright-core/lib/server/codegen/skyramp/jsonlReader.js +1 -0
  163. package/node_modules/playwright/node_modules/playwright-core/lib/server/recorder/recorderSignalProcessor.js +2 -0
  164. package/node_modules/playwright/node_modules/playwright-core/lib/server/recorder.js +5 -1
  165. package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/{index.-Id052Lr.js → index.B7KbSQcC.js} +1 -1
  166. package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/index.html +1 -1
  167. package/node_modules/playwright/node_modules/playwright-core/package.json +1 -1
  168. package/node_modules/playwright/node_modules/playwright-core/src/server/codegen/skyramp/jsonlReader.ts +1 -1
  169. package/node_modules/playwright/node_modules/playwright-core/src/server/recorder/recorderSignalProcessor.ts +7 -0
  170. package/node_modules/playwright/node_modules/playwright-core/src/server/recorder.ts +6 -1
  171. package/node_modules/playwright/package.json +1 -1
  172. package/package.json +4 -3
  173. package/plugin/.claude-plugin/plugin.json +8 -0
  174. package/plugin/plugin.json +6 -0
  175. package/plugin/prompts/declaring-a-plan.md +20 -0
  176. package/plugin/prompts/generate-tests/context-fetching.md +4 -0
  177. package/plugin/prompts/generate-tests/execution-plan.md +63 -0
  178. package/plugin/prompts/generate-tests/generation.md +108 -0
  179. package/plugin/prompts/generate-tests/path-parameters.md +1 -0
  180. package/plugin/prompts/generate-tests/reasoning-protocol.md +17 -0
  181. package/plugin/prompts/generate-tests/tool-workflow-variants.md +61 -0
  182. package/plugin/prompts/generate-tests/tool-workflows.md +65 -0
  183. package/plugin/prompts/plan-tests.md +42 -0
  184. package/plugin/prompts/testbot-task1.md +82 -0
  185. package/plugin/skills/fix-test-import-errors/SKILL.md +98 -0
  186. package/build/prompts/test-recommendation/analysisOutputPrompt.d.ts +0 -84
  187. package/build/prompts/test-recommendation/analysisOutputPrompt.js +0 -369
  188. package/build/prompts/test-recommendation/fullRepoCatalog.d.ts +0 -7
  189. package/build/prompts/test-recommendation/fullRepoCatalog.js +0 -283
  190. package/build/prompts/test-recommendation/scopeAssessment.d.ts +0 -81
  191. package/build/prompts/test-recommendation/scopeAssessment.js +0 -359
  192. package/build/recommendation/budgeters/diversityBalancedBudgeter.d.ts +0 -7
  193. package/build/recommendation/budgeters/diversityBalancedBudgeter.js +0 -105
  194. package/build/recommendation/budgeters/fixedNBudgeter.d.ts +0 -7
  195. package/build/recommendation/budgeters/fixedNBudgeter.js +0 -11
  196. package/build/recommendation/budgeters/shared.d.ts +0 -32
  197. package/build/recommendation/budgeters/shared.js +0 -246
  198. package/build/recommendation/discriminators.d.ts +0 -37
  199. package/build/recommendation/discriminators.js +0 -379
  200. package/build/recommendation/diversity.d.ts +0 -47
  201. package/build/recommendation/diversity.js +0 -101
  202. package/build/recommendation/planRanker.d.ts +0 -65
  203. package/build/recommendation/planRanker.js +0 -83
  204. package/build/recommendation/testFixtures.d.ts +0 -25
  205. package/build/recommendation/testFixtures.js +0 -45
  206. package/build/types/FrontendIntegration.d.ts +0 -28
  207. package/build/types/FrontendIntegration.js +0 -22
  208. package/build/types/Recommendation.d.ts +0 -146
  209. package/build/types/Recommendation.js +0 -74
  210. package/build/utils/changedRoutes.d.ts +0 -29
  211. package/build/utils/changedRoutes.js +0 -87
  212. package/build/utils/frontendIntegration.d.ts +0 -9
  213. package/build/utils/frontendIntegration.js +0 -243
  214. package/build/utils/importerHop.d.ts +0 -135
  215. package/build/utils/importerHop.js +0 -489
  216. package/build/utils/pathAffinityClassification.d.ts +0 -49
  217. package/build/utils/pathAffinityClassification.js +0 -180
  218. package/build/utils/pythonMountPrefixes.d.ts +0 -25
  219. package/build/utils/pythonMountPrefixes.js +0 -347
  220. package/build/utils/repoScanner.d.ts +0 -34
  221. package/build/utils/repoScanner.js +0 -300
  222. package/build/utils/routeParsers.d.ts +0 -95
  223. package/build/utils/routeParsers.js +0 -951
  224. package/build/utils/scenarioDrafting.d.ts +0 -92
  225. package/build/utils/scenarioDrafting.js +0 -951
  226. package/build/utils/subjectEndpoints.d.ts +0 -19
  227. package/build/utils/subjectEndpoints.js +0 -98
  228. package/build/utils/uiPageEnumerator.d.ts +0 -172
  229. package/build/utils/uiPageEnumerator.js +0 -474
@@ -1,3 +1,4 @@
1
+ import { fixErrorsInstruction } from "../skills/fixTestImportErrorsSkill.js";
1
2
  import { TestType } from "../types/TestTypes.js";
2
3
  import { getModularizationPrompt as getModularizationPromptForUI } from "../prompts/modularization/ui-test-modularization.js";
3
4
  import { getModularizationPrompt as getModularizationPromptForIntegration } from "../prompts/modularization/integration-test-modularization.js";
@@ -47,7 +48,7 @@ The test file \`${params.testFile}\` will remain unchanged. No further action is
47
48
  1. Read the test file using the 'read_file' tool
48
49
  2. Create the modularized version following the instructions above
49
50
  3. Use the 'write' tool (NOT search_replace) to save the complete modularized code to the file
50
- 4. After modularization is complete, proceed to skyramp_fix_errors tool if there are any errors`,
51
+ 4. After modularization is complete, if there are any errors, ${fixErrorsInstruction()}`,
51
52
  },
52
53
  ],
53
54
  };
@@ -4,41 +4,6 @@ interface TestDiscoveryResult {
4
4
  /** Paths of external files that were deemed relevant to the PR (score > 0). */
5
5
  relevantExternalTestPaths: string[];
6
6
  }
7
- export interface TestDiscoveryOptions {
8
- /**
9
- * Resource names derived from endpoints in changed files (e.g. ["orders", "products"]).
10
- * - Non-empty array: external files partitioned by relevance; only relevant files
11
- * get full endpoint extraction. May be the sentinel `["unknown"]` when endpoints
12
- * exist but resource names are unresolvable — most files score 0 (low-relevance)
13
- * and are excluded; only the few that happen to match the sentinel are kept.
14
- * - Empty array `[]`: PR mode with no endpoints from diff or scanner — API/integration
15
- * external tests are excluded entirely; UI tests may still be promoted via changedFrontendFiles.
16
- * - `undefined`: full-repo mode — external tests capped at MAX_EXTERNAL_FULL_REPO.
17
- */
18
- changedResources?: string[];
19
- /** Raw changed symbol names (e.g. ["DeploymentCreate"]). Content-grep fallback so an
20
- * external test that references a changed type by name — but whose path/URL matches no
21
- * changed resource (e.g. a schema unit test) — is still surfaced (SKYR-3924). */
22
- changedSymbols?: string[];
23
- /** True when `changedResources` are precise (derived from specific changed schema/model
24
- * symbols, not the `["unknown"]` sentinel or a broad set). Lets the URL content-match
25
- * bucket skip the MAX_CONTENT_PROMOTED cap — a direct HTTP call to the changed endpoint
26
- * is high-confidence, and the specific resource set naturally bounds the match count. */
27
- preciseResources?: boolean;
28
- /** Repo-relative paths of the changed frontend (non-test) files — their presence is the
29
- * frontend-change signal that drives UI-test promotion (Step 2). When present, promotion
30
- * is scoped to tests whose name/path/imports are relevant to these files — a frontend PR
31
- * must not pull in the whole monorepo's UI test suite (SKYR-3941). When absent, Step 2 is
32
- * skipped (no UI promotion). */
33
- changedFrontendFiles?: string[];
34
- /** Selector literals (data-testid values, CSS class tokens) added or removed by the diff.
35
- * UI-test promotion pass 3 ("selector-edge"): a page object / spec couples to a changed
36
- * component by SELECTOR, not by filename or import, so passes 1-2 miss it. Any external
37
- * test-owned file (including page objects, which are not *.test/*.spec) whose body
38
- * references one of these literals is promoted. Both added and removed values are passed
39
- * so a page object still holding a renamed selector's OLD value is caught. */
40
- changedSelectors?: string[];
41
- }
42
7
  export declare class TestDiscoveryService {
43
8
  private readonly EXCLUDED_DIRS;
44
9
  private readonly SKYRAMP_MARKER;
@@ -50,44 +15,10 @@ export declare class TestDiscoveryService {
50
15
  * Uses fast-glob for cross-platform file scanning, then classifies discovered files
51
16
  * as Skyramp-generated tests, external tests, or not-a-test during processing.
52
17
  *
53
- * External test handling depends on `options.changedResources`:
54
- * - `string[]` with entries (PR mode, endpoints detected): partition by relevance.
55
- * - `[]` empty array (PR mode, scanner found no endpoints): skip external tests entirely
56
- * rather than flooding context with irrelevant files.
57
- * - `undefined` (full-repo mode, no diff): cap at MAX_EXTERNAL_FULL_REPO.
58
- */
59
- discoverTests(testDir: string, options?: TestDiscoveryOptions): Promise<TestDiscoveryResult>;
60
- /**
61
- * Score an external test file's relevance to a set of changed resource names.
62
- * Tokenises the last two segments of the file path (split by /, \, -, _, .)
63
- * and counts overlapping tokens with the changed resources.
64
- * Example: "test_orders_api.py" vs ["orders"] → score 1.
65
- */
66
- private scoreRelevance;
67
- /**
68
- * Derive relevance tokens from the changed frontend files, for scoring UI tests in Step 2
69
- * the same way scoreRelevance scores against changed API resources. For each file we emit:
70
- * - the basename stem (e.g. "OrderSearch" → "ordersearch") — matches co-located tests like
71
- * OrderSearch.test.tsx, the dominant convention;
72
- * - its sub-tokens, split on delimiters AND camelCase boundaries ("OrderSearch" →
73
- * "order","search") — matches hyphenated/underscored test names like order-search.spec.tsx;
74
- * - the parent directory name — the signal when the basename is generic (orders/index.tsx).
75
- * Generic tokens (index, components, utils, …) and tokens shorter than 3 chars are dropped so
76
- * a shared dir or an index file doesn't match every UI test in the repo.
77
- */
78
- private deriveFrontendResourceTokens;
79
- private readonly MAX_CONTENT_PROMOTED;
80
- private readonly MAX_UI_PROMOTED;
81
- private readonly UI_IMPORT_MATCH_WEIGHT;
82
- private readonly GENERIC_FRONTEND_TOKENS;
83
- /**
84
- * Partition external test files into relevant (score > 0) and low-relevance (score = 0).
85
- * Two-pass: filename token overlap first (primary); content-based endpoint path scan
86
- * as a capped fallback for tests misnamed relative to the endpoint they exercise
87
- * (e.g. test_checkout_flow.py testing /api/orders scores 0 by name but matches in content).
88
- * Content matches are capped at MAX_CONTENT_PROMOTED to bound token impact.
18
+ * External tests are capped at MAX_EXTERNAL_FULL_REPO: nothing narrows them by
19
+ * relevance any more.
89
20
  */
90
- private partitionByRelevance;
21
+ discoverTests(testDir: string): Promise<TestDiscoveryResult>;
91
22
  /**
92
23
  * Process test files in parallel batches with concurrency control
93
24
  * @param isExternal When true, uses external test metadata extraction
@@ -3,7 +3,6 @@ import * as path from "path";
3
3
  import { logger } from "../utils/logger.js";
4
4
  import { TestSource } from "../types/TestAnalysis.js";
5
5
  import { TestType } from "../types/TestTypes.js";
6
- import { buildPathSignatures } from "../utils/pathSignatures.js";
7
6
  import fg from "fast-glob";
8
7
  import { isDiscoveredTestFile } from "../utils/testFileClassification.js";
9
8
  export class TestDiscoveryService {
@@ -40,13 +39,10 @@ export class TestDiscoveryService {
40
39
  * Uses fast-glob for cross-platform file scanning, then classifies discovered files
41
40
  * as Skyramp-generated tests, external tests, or not-a-test during processing.
42
41
  *
43
- * External test handling depends on `options.changedResources`:
44
- * - `string[]` with entries (PR mode, endpoints detected): partition by relevance.
45
- * - `[]` empty array (PR mode, scanner found no endpoints): skip external tests entirely
46
- * rather than flooding context with irrelevant files.
47
- * - `undefined` (full-repo mode, no diff): cap at MAX_EXTERNAL_FULL_REPO.
42
+ * External tests are capped at MAX_EXTERNAL_FULL_REPO: nothing narrows them by
43
+ * relevance any more.
48
44
  */
49
- async discoverTests(testDir, options = {}) {
45
+ async discoverTests(testDir) {
50
46
  logger.info(`Starting test discovery in: ${testDir}`);
51
47
  const stats = fs.statSync(testDir, { throwIfNoEntry: false });
52
48
  if (!stats)
@@ -59,144 +55,13 @@ export class TestDiscoveryService {
59
55
  // Process Skyramp tests (content already cached from classification)
60
56
  const skyrampTests = await this.processFilesInBatches(classified.skyramp, false, classified.contentCache);
61
57
  skyrampTests.forEach(t => { t.source = TestSource.Skyramp; });
62
- // Partition external tests into relevant (full extraction) and low-relevance (name-only).
63
- //
64
- // PR mode + endpoints detected (changedResources is non-empty array):
65
- // Files whose path/name token-overlaps with the changed resource names are "relevant".
66
- // Only they get full endpoint extraction. Low-relevance files get name-only entries.
67
- // No hard cap — the relevance filter naturally bounds the read set to PR scope.
68
- // The sentinel ["unknown"] falls into this branch — most files score 0 (low-relevance)
69
- // and get name-only entries, so external coverage is preserved without context flood.
70
- //
71
- // PR mode + truly no endpoints (changedResources is empty array []):
72
- // Diff contained no endpoints at all (new, modified, or removed) — skip external
73
- // tests entirely rather than flooding the prompt with hundreds of irrelevant files.
74
- //
75
- // Full-repo mode (changedResources is undefined):
76
- // No diff context — all external files treated as potentially relevant.
77
- // Cap at MAX_EXTERNAL_FULL_REPO to avoid reading hundreds of files.
78
- const { changedResources, changedSymbols, preciseResources, changedFrontendFiles, changedSelectors } = options;
79
- let relevantExternal;
80
- // Includes .e2e.ts — Playwright/Cypress tests that navigate pages. These get
81
- // testType="e2e" from path detection but are never named *.test.ts or *.spec.ts
82
- // so without explicit promotion they'd be excluded from stateData.existingTests,
83
- // making the server-side symbol grep unable to find them.
84
- const UI_TEST_EXT = /\.(test|spec|e2e)\.(tsx|jsx|ts|js)$/;
85
- const SEC_EXT = /\.(snap|ambr|txt|yml|yaml)$/;
86
- // Step 1 — API relevance partitioning (driven by changedResources).
87
- // Two-pass: filename token overlap first, then content-based endpoint path scan
88
- // for tests that are misnamed relative to the endpoint they exercise.
89
- if (changedResources?.length) {
90
- ({ relevant: relevantExternal } =
91
- this.partitionByRelevance(classified.external, changedResources, classified.contentCache, changedSymbols, preciseResources));
92
- }
93
- else if (changedResources !== undefined) {
94
- relevantExternal = [];
95
- }
96
- else {
97
- relevantExternal = classified.external.slice(0, this.MAX_EXTERNAL_FULL_REPO);
98
- }
99
- // Step 2 — UI test promotion, driven by the presence of changed frontend files and
100
- // independent of Step 1. Scoped by relevance to those files, mirroring Step 1's API
101
- // discipline: a frontend PR must not pull in the whole monorepo's UI test suite (SKYR-3941).
102
- // Absent a changed-frontend-file list, Step 2 is skipped (no UI promotion).
103
- if (changedFrontendFiles?.length) {
104
- const alreadyRelevant = new Set(relevantExternal);
105
- const uiCandidates = classified.external.filter(f => UI_TEST_EXT.test(f) && !alreadyRelevant.has(f));
106
- // Pass 1 — filename/path relevance: tokens derived from the changed frontend files.
107
- const feTokens = this.deriveFrontendResourceTokens(changedFrontendFiles);
108
- // Pass 2 — import-edge relevance: module-path signatures of the changed files, matched
109
- // against test bodies so a test that imports a changed component (from './PriceLabel')
110
- // is caught even when its name shares no token — the recall case a filename-only filter
111
- // drops (a checkout test that renders a changed child component). Reuses the same
112
- // buildPathSignatures used by frontendIntegration/importerHop; this is Step 2's analog
113
- // of Step 1's content-scan fallback. Skip generic basenames (index/app/…): their
114
- // signature (`/index'`) would match every barrel import — those are already covered by
115
- // pass 1's parent-directory token.
116
- const importSignatures = changedFrontendFiles
117
- .filter(f => {
118
- const stem = path.basename(f).replace(/\.[^.]+$/, "").toLowerCase();
119
- return stem.length >= 3 && !this.GENERIC_FRONTEND_TOKENS.has(stem);
120
- })
121
- .flatMap(f => buildPathSignatures(f));
122
- let promoted;
123
- if (feTokens.length === 0 && importSignatures.length === 0) {
124
- // No relevance signal is derivable — every changed frontend file is a generic
125
- // entrypoint (index.tsx / app.tsx / …) whose tokens are filtered out, and none
126
- // yields an import signature. A root/entrypoint change is broadly impactful, so
127
- // fall back to promoting all UI candidates rather than degenerate to zero — still
128
- // bounded by MAX_UI_PROMOTED so it can't re-flood a large monorepo's suite.
129
- promoted = uiCandidates.slice(0, this.MAX_UI_PROMOTED);
130
- const dropped = uiCandidates.length - promoted.length;
131
- logger.info(`UI test promotion: no relevance signal from generic-only frontend changes — capped promote-all fallback (${promoted.length}/${uiCandidates.length} promoted${dropped > 0 ? `, ${dropped} dropped by MAX_UI_PROMOTED` : ""})`);
132
- }
133
- else {
134
- let scored = uiCandidates
135
- .map(f => {
136
- const nameScore = this.scoreRelevance(f, feTokens);
137
- const content = classified.contentCache.get(f) ?? "";
138
- const importsChanged = content.length > 0 && importSignatures.some(sig => content.includes(sig));
139
- return { f, score: nameScore + (importsChanged ? this.UI_IMPORT_MATCH_WEIGHT : 0) };
140
- })
141
- .filter(x => x.score > 0)
142
- .sort((a, b) => b.score - a.score)
143
- .map(x => x.f);
144
- if (scored.length > this.MAX_UI_PROMOTED) {
145
- logger.info(`UI test promotion: ${scored.length} tests matched ${changedFrontendFiles.length} changed frontend file(s); capping to ${this.MAX_UI_PROMOTED} highest-scoring (SKYR-3941 backstop)`);
146
- scored = scored.slice(0, this.MAX_UI_PROMOTED);
147
- }
148
- logger.info(`UI test promotion scoped to ${scored.length}/${uiCandidates.length} UI test(s) relevant to changed frontend files`);
149
- promoted = scored;
150
- }
151
- // Pass 3 — selector-edge: a page object / spec couples to a changed component by
152
- // SELECTOR (data-testid value or CSS class), not by filename or import, so passes
153
- // 1-2 miss it. Crucially the candidate pool here is the FULL external set, not just
154
- // UI_TEST_EXT: a page object like `table.component.ts` is classified external (via
155
- // the page-objects/ dir pattern) with content cached, but is not a *.test/*.spec
156
- // file, so it never enters `uiCandidates`. Promote any not-yet-relevant external
157
- // file whose body references a changed selector literal (matched against both the
158
- // added and removed diff values, so a page object still holding a renamed selector's
159
- // OLD value is caught). Bounded by MAX_UI_PROMOTED.
160
- // Combined cap: MAX_UI_PROMOTED is a single Step-2 ceiling, so pass 3 may only add up
161
- // to what pass 1/2 left — otherwise the two independent caps could promote 2x and
162
- // re-flood discovery (the exact failure SKYR-3941 guards against). Compute the headroom
163
- // up front and skip the O(external × selectors) content scan entirely when pass 1/2
164
- // already filled the budget.
165
- const selectorHeadroom = Math.max(0, this.MAX_UI_PROMOTED - promoted.length);
166
- if (changedSelectors?.length && selectorHeadroom > 0) {
167
- const alreadyPromoted = new Set([...alreadyRelevant, ...promoted]);
168
- const selectorMatched = classified.external
169
- .filter((f) => {
170
- if (alreadyPromoted.has(f))
171
- return false;
172
- const content = classified.contentCache.get(f) ?? "";
173
- return content.length > 0 && changedSelectors.some((sel) => content.includes(sel));
174
- })
175
- // Sort for a deterministic cap — filesystem/glob traversal order is not stable
176
- // across environments, so which files survive MAX_UI_PROMOTED must not depend on it.
177
- .sort();
178
- if (selectorMatched.length > 0) {
179
- const capped = selectorMatched.slice(0, selectorHeadroom);
180
- logger.info(`UI test promotion: ${selectorMatched.length} test/page-object file(s) reference a changed selector (pass 3, selector-edge)` +
181
- (capped.length < selectorMatched.length ? `; added ${capped.length} (MAX_UI_PROMOTED headroom after pass 1/2)` : ""));
182
- if (capped.length > 0)
183
- promoted = [...promoted, ...capped];
184
- }
185
- }
186
- const promotedSet = new Set(promoted);
187
- // Promote snapshot/fixture siblings of scored AND newly promoted UI tests.
188
- // Include promoted files so siblings are found on UI-only PRs where
189
- // relevantExternal started empty before Step 2.
190
- const relevantTestBasenames = new Set([...relevantExternal, ...promoted].filter(f => UI_TEST_EXT.test(f)).map(f => path.basename(f)));
191
- const promotedSecondary = classified.external.filter(f => !alreadyRelevant.has(f) &&
192
- !promotedSet.has(f) &&
193
- relevantTestBasenames.has(path.basename(f).replace(SEC_EXT, '')));
194
- const allPromoted = [...promoted, ...promotedSecondary];
195
- if (allPromoted.length > 0) {
196
- logger.info(`Promoting ${promoted.length} UI test + ${promotedSecondary.length} secondary file(s) for content reading`);
197
- relevantExternal = [...relevantExternal, ...allPromoted];
198
- }
199
- }
58
+ // No diff context reaches this service: its one caller passes no options, so
59
+ // every external file is potentially relevant and the cap is what bounds the
60
+ // read set.
61
+ const relevantExternal = classified.external.slice(0, this.MAX_EXTERNAL_FULL_REPO);
62
+ // There is no Step 2 any more. It promoted UI tests by matching filename tokens
63
+ // against changed FRONTEND files, a list built by classifying paths on
64
+ // extension and directory. Every external test is already loaded.
200
65
  // Free content cache for external files not selected for full extraction.
201
66
  const relevantSet = new Set(relevantExternal);
202
67
  for (const f of classified.external) {
@@ -212,164 +77,6 @@ export class TestDiscoveryService {
212
77
  relevantExternalTestPaths: relevantExternal,
213
78
  };
214
79
  }
215
- /**
216
- * Score an external test file's relevance to a set of changed resource names.
217
- * Tokenises the last two segments of the file path (split by /, \, -, _, .)
218
- * and counts overlapping tokens with the changed resources.
219
- * Example: "test_orders_api.py" vs ["orders"] → score 1.
220
- */
221
- scoreRelevance(filePath, changedResources) {
222
- const normalized = filePath.toLowerCase().replace(/\\/g, "/");
223
- // Use the last two path segments to avoid false matches from deeply-nested dirs
224
- const segments = normalized.split("/").slice(-2).join("/");
225
- const tokens = new Set(segments.split(/[/\-_.]+/).filter(Boolean));
226
- // Expand tokens with plural/singular variants so "deployment" matches
227
- // "deployments" and vice versa. Only strip trailing "s" when the result
228
- // is ≥4 chars (avoids "pas"→"pa") and doesn't end in "ss" (avoids "pass"→"pas").
229
- const expanded = new Set(tokens);
230
- for (const t of tokens) {
231
- if (t.endsWith("s") && !t.endsWith("ss") && t.length > 4)
232
- expanded.add(t.slice(0, -1));
233
- else if (!t.endsWith("s"))
234
- expanded.add(t + "s");
235
- }
236
- return changedResources.filter(r => {
237
- const rLower = r.toLowerCase();
238
- if (expanded.has(rLower))
239
- return true;
240
- // Compound resource names (e.g. "order-items") — all parts must appear as tokens.
241
- const parts = rLower.split(/[-_]/);
242
- return parts.length > 1 && parts.every(p => p.length >= 3 && expanded.has(p));
243
- }).length;
244
- }
245
- /**
246
- * Derive relevance tokens from the changed frontend files, for scoring UI tests in Step 2
247
- * the same way scoreRelevance scores against changed API resources. For each file we emit:
248
- * - the basename stem (e.g. "OrderSearch" → "ordersearch") — matches co-located tests like
249
- * OrderSearch.test.tsx, the dominant convention;
250
- * - its sub-tokens, split on delimiters AND camelCase boundaries ("OrderSearch" →
251
- * "order","search") — matches hyphenated/underscored test names like order-search.spec.tsx;
252
- * - the parent directory name — the signal when the basename is generic (orders/index.tsx).
253
- * Generic tokens (index, components, utils, …) and tokens shorter than 3 chars are dropped so
254
- * a shared dir or an index file doesn't match every UI test in the repo.
255
- */
256
- deriveFrontendResourceTokens(changedFrontendFiles) {
257
- const out = new Set();
258
- const add = (t) => {
259
- const v = t.toLowerCase();
260
- if (v.length >= 3 && !this.GENERIC_FRONTEND_TOKENS.has(v))
261
- out.add(v);
262
- };
263
- for (const f of changedFrontendFiles) {
264
- const segs = f.replace(/\\/g, "/").split("/").filter(Boolean);
265
- const base = segs[segs.length - 1] ?? "";
266
- const stem = base.replace(/\.[^.]+$/, ""); // drop extension
267
- add(stem); // whole stem (e.g. "ordersearch", "order-search")
268
- for (const t of stem.split(/[-_.]+|(?<=[a-z0-9])(?=[A-Z])/))
269
- add(t); // sub-tokens
270
- const parent = segs[segs.length - 2];
271
- if (parent && !parent.startsWith("__"))
272
- add(parent);
273
- }
274
- return [...out];
275
- }
276
- // Max additional tests promoted via content-based scoring on top of filename matches.
277
- // Content matches are lower-confidence than filename matches — cap to avoid token bloat.
278
- MAX_CONTENT_PROMOTED = 5;
279
- // Backstop cap on scoped UI-test promotion (Step 2). The relevance filter against the
280
- // changed frontend files does the real bounding; this only guards a pathological match
281
- // (e.g. a change to a widely-shared component). Highest-scoring tests survive truncation.
282
- MAX_UI_PROMOTED = 50;
283
- // Weight added to a UI test's relevance score when it imports a changed frontend file by
284
- // module path (import-edge match, Step 2 pass 2). Set high above any filename-token score
285
- // so import matches — the strongest "this test exercises the changed code" signal —
286
- // always survive the MAX_UI_PROMOTED cap ahead of name-only matches.
287
- UI_IMPORT_MATCH_WEIGHT = 100;
288
- // Generic path/name tokens that carry no feature signal — excluded when deriving relevance
289
- // tokens from changed frontend files so a changed index.tsx or a shared "components" dir
290
- // doesn't match every UI test in the repo.
291
- GENERIC_FRONTEND_TOKENS = new Set([
292
- "index", "app", "main", "root", "styles", "style", "css", "scss", "types", "type",
293
- "constants", "utils", "util", "helpers", "helper", "components", "component", "pages",
294
- "page", "routes", "route", "src", "common", "shared", "hooks", "hook", "lib", "context",
295
- "providers", "provider", "store", "stores", "api", "test", "tests", "spec", "specs",
296
- "mocks", "mock", "fixtures", "fixture", "assets", "config",
297
- ]);
298
- /**
299
- * Partition external test files into relevant (score > 0) and low-relevance (score = 0).
300
- * Two-pass: filename token overlap first (primary); content-based endpoint path scan
301
- * as a capped fallback for tests misnamed relative to the endpoint they exercise
302
- * (e.g. test_checkout_flow.py testing /api/orders scores 0 by name but matches in content).
303
- * Content matches are capped at MAX_CONTENT_PROMOTED to bound token impact.
304
- */
305
- partitionByRelevance(files, changedResources, contentCache, changedSymbols, preciseResources) {
306
- const nameMatched = [];
307
- const symbolMatched = [];
308
- const contentCandidates = [];
309
- // Precompile content patterns once — avoids 20 resources × 150 files = 3K compilations.
310
- const contentPatterns = contentCache
311
- ? changedResources.map(r => {
312
- // Escape all regex metacharacters, then restore [-/] as a character class
313
- // for hyphens and slashes that appear in resource names like "order-items".
314
- const escaped = r.toLowerCase()
315
- .replace(/[.*+?^${}()|[\]\\]/g, "\\$&") // escape regex metacharacters
316
- .replace(/-|\//g, "[-/]"); // treat hyphens/slashes interchangeably
317
- return new RegExp(`["'\`][^"'\`]*/${escaped}`, "i");
318
- })
319
- : [];
320
- // Exact, case-sensitive word-boundary match on a changed type name (e.g.
321
- // `DeploymentCreate`). Catches an external test that references the changed schema/model
322
- // by name but whose path/URL matches no changed resource — a schema unit test
323
- // constructing it directly (SKYR-3924). High-confidence, so not subject to the content cap.
324
- const symbolPatterns = contentCache
325
- ? (changedSymbols ?? []).map(s => new RegExp(`\\b${s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\b`))
326
- : [];
327
- // Endpoint-call patterns: an HTTP-method call to a path containing the changed resource
328
- // (e.g. `client.post("/deployments/")`). A direct call to the changed endpoint is
329
- // high-confidence — distinct from a file that merely mentions the resource string.
330
- const endpointCallPatterns = contentCache
331
- ? changedResources.map(r => {
332
- const escaped = r.toLowerCase()
333
- .replace(/[.*+?^${}()|[\]\\]/g, "\\$&")
334
- .replace(/-|\//g, "[-/]");
335
- return new RegExp(`\\b(?:get|post|put|patch|delete|request|fetch)\\b[^\\n]{0,60}["'\`][^"'\`]*/${escaped}`, "i");
336
- })
337
- : [];
338
- const strongContent = [];
339
- for (const f of files) {
340
- if (this.scoreRelevance(f, changedResources) > 0) {
341
- nameMatched.push(f);
342
- continue;
343
- }
344
- const content = contentCache?.get(f) ?? "";
345
- if (!content)
346
- continue;
347
- if (symbolPatterns.some(p => p.test(content))) {
348
- symbolMatched.push(f);
349
- }
350
- else if (endpointCallPatterns.some(p => p.test(content))) {
351
- strongContent.push(f); // direct call to the changed endpoint — high-confidence
352
- }
353
- else if (contentPatterns.some(p => p.test(content))) {
354
- contentCandidates.push(f); // loose mention of the resource — low-confidence, capped
355
- }
356
- }
357
- // Precise resource sets (schema-diff preload): keep high-confidence endpoint-call matches
358
- // uncapped (bounded by the specific resource), still cap loose mentions. Otherwise cap all
359
- // content matches together (original behavior).
360
- const contentMatched = preciseResources
361
- ? [...strongContent, ...contentCandidates.slice(0, this.MAX_CONTENT_PROMOTED)]
362
- : [...strongContent, ...contentCandidates].slice(0, this.MAX_CONTENT_PROMOTED);
363
- if (symbolMatched.length > 0 || contentMatched.length > 0) {
364
- // Report what actually landed in contentMatched — the non-precise branch caps
365
- // strong+loose together, so strongContent may be truncated too. Deriving both
366
- // counts from contentMatched.length avoids a misleading negative "loose" count.
367
- const strongIn = Math.min(strongContent.length, contentMatched.length);
368
- const looseIn = contentMatched.length - strongIn;
369
- logger.info(`Relevance: ${symbolMatched.length} symbol + ${strongIn} endpoint-call + ${looseIn} loose content`);
370
- }
371
- return { relevant: [...nameMatched, ...symbolMatched, ...contentMatched] };
372
- }
373
80
  /**
374
81
  * Process test files in parallel batches with concurrency control
375
82
  * @param isExternal When true, uses external test metadata extraction
@@ -22,4 +22,4 @@ export declare function isValidEnvVarName(name: string): boolean;
22
22
  /**
23
23
  * Build the environment variable array for the Docker executor container.
24
24
  */
25
- export declare function buildContainerEnv(options: Pick<TestExecutionOptions, "token" | "language" | "useHostNetwork">, saveStoragePath?: string, hostEnv?: Record<string, string | undefined>, passthroughNames?: string[]): string[];
25
+ export declare function buildContainerEnv(options: Pick<TestExecutionOptions, "token" | "language" | "useHostNetwork" | "rebaselineSnapshots">, saveStoragePath?: string, hostEnv?: Record<string, string | undefined>, passthroughNames?: string[]): string[];
@@ -77,6 +77,18 @@ export function buildContainerEnv(options, saveStoragePath, hostEnv = process.en
77
77
  ...(options.token ? [`SKYRAMP_TEST_TOKEN=${options.token}`] : []),
78
78
  "SKYRAMP_IN_DOCKER=true",
79
79
  ];
80
+ // Visual-snapshot baselines this run replaces (SKYR-4298). SmartPlaywright
81
+ // reads SKYRAMP_UPDATE_SNAPSHOTS and re-captures each named baseline through
82
+ // its first-run path instead of comparing. Set here, first-class, because the
83
+ // SKYRAMP_ prefix is reserved from workspace passthrough below — nothing else
84
+ // can put it in the container. Omitted entirely when nothing is to refresh, so
85
+ // the default stays "compare".
86
+ const rebaseline = (options.rebaselineSnapshots ?? [])
87
+ .map((n) => n.trim())
88
+ .filter(Boolean);
89
+ if (rebaseline.length > 0) {
90
+ env.push(`SKYRAMP_UPDATE_SNAPSHOTS=${rebaseline.join(",")}`);
91
+ }
80
92
  // Skyramp-generated tests are standalone HTTP tests that never need host repo
81
93
  // conftest.py files or pytest configuration. --noconftest prevents loading any
82
94
  // conftest in the test directory tree (avoids missing deps like boto3, django).
@@ -0,0 +1,13 @@
1
+ /**
2
+ * The `fix-test-import-errors` skill ships in `plugin/skills/` at the package
3
+ * root (SKYR-4296). It carries the same instructions `skyramp_fix_errors`
4
+ * returns, so an agent that loads the plugin follows the skill and an agent
5
+ * that does not keeps calling the tool. The tool itself is unchanged.
6
+ */
7
+ export declare const FIX_TEST_IMPORT_ERRORS_SKILL = "fix-test-import-errors";
8
+ /**
9
+ * The sentence a prompt uses to send the agent to the fix-errors instructions.
10
+ * With the plugin loaded it names the skill and states the override contract:
11
+ * a project skill of the same name wins over the plugin copy.
12
+ */
13
+ export declare function fixErrorsInstruction(): string;
@@ -0,0 +1,20 @@
1
+ import { isSkillsLoaded } from "../utils/featureFlags.js";
2
+ /**
3
+ * The `fix-test-import-errors` skill ships in `plugin/skills/` at the package
4
+ * root (SKYR-4296). It carries the same instructions `skyramp_fix_errors`
5
+ * returns, so an agent that loads the plugin follows the skill and an agent
6
+ * that does not keeps calling the tool. The tool itself is unchanged.
7
+ */
8
+ export const FIX_TEST_IMPORT_ERRORS_SKILL = "fix-test-import-errors";
9
+ const FIX_ERRORS_TOOL = "skyramp_fix_errors";
10
+ /**
11
+ * The sentence a prompt uses to send the agent to the fix-errors instructions.
12
+ * With the plugin loaded it names the skill and states the override contract:
13
+ * a project skill of the same name wins over the plugin copy.
14
+ */
15
+ export function fixErrorsInstruction() {
16
+ if (isSkillsLoaded()) {
17
+ return `invoke the \`${FIX_TEST_IMPORT_ERRORS_SKILL}\` skill (a project skill with the same name takes precedence over the plugin copy)`;
18
+ }
19
+ return `call the \`${FIX_ERRORS_TOOL}\` tool`;
20
+ }
@@ -17,3 +17,4 @@ export declare const TOOL_INTEGRATION_TEST_GENERATION = "skyramp_integration_tes
17
17
  export declare const TOOL_E2E_TEST_GENERATION = "skyramp_e2e_test_generation";
18
18
  export declare const TOOL_UI_TEST_GENERATION = "skyramp_ui_test_generation";
19
19
  export declare const TOOL_REGISTER_TEST_PLAN = "skyramp_register_test_plan";
20
+ export declare const TOOL_RESOLVE_SCREEN = "skyramp_resolve_screen";
@@ -17,3 +17,4 @@ export const TOOL_INTEGRATION_TEST_GENERATION = "skyramp_integration_test_genera
17
17
  export const TOOL_E2E_TEST_GENERATION = "skyramp_e2e_test_generation";
18
18
  export const TOOL_UI_TEST_GENERATION = "skyramp_ui_test_generation";
19
19
  export const TOOL_REGISTER_TEST_PLAN = "skyramp_register_test_plan";
20
+ export const TOOL_RESOLVE_SCREEN = "skyramp_resolve_screen";
@@ -16,7 +16,7 @@ import { stageGeneratedPaths } from "../../utils/gitStaging.js";
16
16
  const TOOL_NAME = "skyramp_enhance_assertions";
17
17
  const TESTBOT_UI_CHECKS = `
18
18
  ### Additional Testbot-Specific Checks
19
- - If no suitable selector exists in the generated file for an assertion you need to add, go back and call \`browser_assert\` on the live page to record it with a valid selector, then re-export and regenerate.
19
+ - If no suitable selector exists in the generated file for an assertion you need to add, go back and call \`browser_assert\` on the live page to record it with a valid selector, then re-export and regenerate. Do not re-record an assertion that is expected to fail today; add it here with the blueprint's selector.
20
20
  - **After executing a UI test that documents a bug from \`issuesFound\`**: if it passed when you expected it to fail, the assertions are too weak — add a stronger \`expect()\` that directly targets the buggy behavior. This counts as the single allowed retry under the 2-attempt cap — do NOT re-run more than once.
21
21
  `;
22
22
  function buildAssertionInstructions(testFile, testType, enhanceType) {
@@ -44,7 +44,7 @@ function buildAutoApplyInstructions(testFile, testType, enhanceType) {
44
44
  `Read the file, apply the type-specific assertion guidance below, and write the file back directly.`,
45
45
  `Add assertions after each send_request/sendRequest status-code assertion.`,
46
46
  `Use SDK helper: Python \`skyramp.get_response_value(response, "json.path")\`, JS \`getValue(response, "json.path")\`.`,
47
- `Do NOT restructure, reformat, add comments, or change imports. Only add assertion lines.`,
47
+ `Do NOT restructure, reformat, or add comments. You may add an import when a new assertion needs a helper — from \`@skyramp/skyramp\` or from the test's own helper module. Only add assertion lines.`,
48
48
  ``,
49
49
  `Type-specific assertion guidance:`,
50
50
  typeSpecificInstructions,
@@ -169,7 +169,7 @@ export function registerEnhanceAssertionsTool(server) {
169
169
  `Shared assertion rules (apply all that fit):`,
170
170
  renderSharedAssertionRules(),
171
171
  ``,
172
- `Do NOT restructure, reformat, add comments, or change imports. Only add assertion lines.`,
172
+ `Do NOT restructure, reformat, or add comments. You may add an import when a new assertion needs a helper — from \`@skyramp/skyramp\` or from the test's own helper module. Only add assertion lines.`,
173
173
  ].join("\n");
174
174
  const result = {
175
175
  content: [{ type: "text", text: compactInstructions }],
@@ -1,4 +1,5 @@
1
1
  import { z } from "zod";
2
+ import { fixErrorsInstruction } from "../../skills/fixTestImportErrorsSkill.js";
2
3
  import fs from "fs";
3
4
  import { logger } from "../../utils/logger.js";
4
5
  import { ProgrammingLanguage, TestType } from "../../types/TestTypes.js";
@@ -39,7 +40,7 @@ export function registerModularizationTool(server) {
39
40
 
40
41
  **MANDATORY**: ONLY MODULARIZE THE TEST IF THE IS TRACE BASED FLAG IS SET TO TRUE ELSE DO NOT MODULARIZE THE TEST AND LEAVE THE TEST FILE AS IS.
41
42
  **CRITICAL**: NON TRACE BASED TESTS ARE ALREADY MODULARIZED AND DO NOT NEED MODULARIZATION.
42
- After modularization, if errors remain, call skyramp_fix_errors.
43
+ After modularization, if errors remain, ${fixErrorsInstruction()}.
43
44
 
44
45
  **CRITICAL**: This tool expects you to complete the modularization by writing the refactored code back to the file.`,
45
46
  inputSchema: modularizationSchema,