@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,45 +1,29 @@
1
1
  /**
2
- * Dart route extraction for Flutter apps using the `go_router` package.
2
+ * Routes a Flutter app declares with `go_router` — SKYR-4341.
3
3
  *
4
- * Walks the repo's Dart sources looking for GoRoute declarations with
5
- * string-literal `path:` arguments:
4
+ * Answers one question, the same one `sourceRouteExtractor` answers for the web:
5
+ * which URL renders which file. Restored from the version `d0982a04` deleted and
6
+ * narrowed to that question — the v1 reader also carried metadata for a caller
7
+ * that ranked pages and fell back to every renderable route, and that caller is
8
+ * gone.
6
9
  *
7
10
  * GoRoute(path: '/login', builder: (ctx, st) => LoginScreen())
8
- * GoRoute(path: "/profile/:id", builder: ...)
9
11
  *
10
- * Why regex (not a Dart compiler): Dart parsers (analyzer, dart-analyzer
11
- * package) require a Dart SDK install. Regex on the GoRoute string-literal-path
12
- * subset gives ~95% coverage of real customer apps with zero install cost.
13
- * Constants like `path: _kHome` are deliberately skipped — emitting them
14
- * as a URL `_kHome` would mislead the agent worse than dropping them.
15
- *
16
- * Each route also carries metadata to support diff-matching in the caller
17
- * (findCandidatePagesByDartRoute):
18
- *
19
- * - `screenWidgets`: identifiers referenced as builder/pageBuilder targets,
20
- * e.g. `AuthorsScreen`. Resolved through the file's imports to absolute
21
- * paths in `screenFiles`.
22
- * - `screenFiles`: the imported source files those identifiers come from.
23
- * - `isRedirectOnly`: true if the GoRoute has `redirect:` but no
24
- * `builder:` or `pageBuilder:` — these don't render UI, just redirect.
25
- *
26
- * Scope choices for slice 1:
27
- * - GoRouter only (`MaterialApp.routes` and `auto_route` deferred)
28
- * - String-literal `path:` only (no constant resolution)
29
- * - Nested GoRoutes emit each declaration verbatim — the absolute parent
30
- * plus relative children. Caller may compose them later.
12
+ * Regex rather than a Dart parser: every Dart parser needs a Dart SDK on the
13
+ * host. The string-literal `path:` subset covers the shapes real apps write at
14
+ * no install cost. A computed path (`path: _kHome`) is skipped rather than
15
+ * emitted as the literal `_kHome`, which would name a URL the app never serves.
31
16
  */
32
- /** A route declaration discovered in Dart source. */
17
+ /** One route found in Dart source. Same shape as `SourceRoute`, so both readers
18
+ * feed the route table the same way. */
33
19
  export interface DartRoute {
34
- /** URL path string from the GoRoute(path: ...) declaration (verbatim). */
20
+ /** URL path from `GoRoute(path: ...)`, verbatim. */
35
21
  path: string;
36
- /** Dart file the declaration was found in (absolute path). */
22
+ /** Widget the route builds, e.g. `LoginScreen`. */
23
+ componentName: string;
24
+ /** Absolute path of the file declaring that widget, when an import names it. */
25
+ componentFile?: string;
26
+ /** Absolute path of the file holding the declaration. */
37
27
  declaredIn: string;
38
- /** Widget identifiers referenced from builder:/pageBuilder: (e.g. `AuthorsScreen`). */
39
- screenWidgets: string[];
40
- /** Resolved absolute paths for screenWidgets via the file's imports. */
41
- screenFiles: string[];
42
- /** True when GoRoute has `redirect:` but no `builder:`/`pageBuilder:` — no UI. */
43
- isRedirectOnly: boolean;
44
28
  }
45
29
  export declare function extractDartRoutes(repositoryPath: string): DartRoute[];
@@ -1,33 +1,18 @@
1
1
  /**
2
- * Dart route extraction for Flutter apps using the `go_router` package.
2
+ * Routes a Flutter app declares with `go_router` — SKYR-4341.
3
3
  *
4
- * Walks the repo's Dart sources looking for GoRoute declarations with
5
- * string-literal `path:` arguments:
4
+ * Answers one question, the same one `sourceRouteExtractor` answers for the web:
5
+ * which URL renders which file. Restored from the version `d0982a04` deleted and
6
+ * narrowed to that question — the v1 reader also carried metadata for a caller
7
+ * that ranked pages and fell back to every renderable route, and that caller is
8
+ * gone.
6
9
  *
7
10
  * GoRoute(path: '/login', builder: (ctx, st) => LoginScreen())
8
- * GoRoute(path: "/profile/:id", builder: ...)
9
11
  *
10
- * Why regex (not a Dart compiler): Dart parsers (analyzer, dart-analyzer
11
- * package) require a Dart SDK install. Regex on the GoRoute string-literal-path
12
- * subset gives ~95% coverage of real customer apps with zero install cost.
13
- * Constants like `path: _kHome` are deliberately skipped — emitting them
14
- * as a URL `_kHome` would mislead the agent worse than dropping them.
15
- *
16
- * Each route also carries metadata to support diff-matching in the caller
17
- * (findCandidatePagesByDartRoute):
18
- *
19
- * - `screenWidgets`: identifiers referenced as builder/pageBuilder targets,
20
- * e.g. `AuthorsScreen`. Resolved through the file's imports to absolute
21
- * paths in `screenFiles`.
22
- * - `screenFiles`: the imported source files those identifiers come from.
23
- * - `isRedirectOnly`: true if the GoRoute has `redirect:` but no
24
- * `builder:` or `pageBuilder:` — these don't render UI, just redirect.
25
- *
26
- * Scope choices for slice 1:
27
- * - GoRouter only (`MaterialApp.routes` and `auto_route` deferred)
28
- * - String-literal `path:` only (no constant resolution)
29
- * - Nested GoRoutes emit each declaration verbatim — the absolute parent
30
- * plus relative children. Caller may compose them later.
12
+ * Regex rather than a Dart parser: every Dart parser needs a Dart SDK on the
13
+ * host. The string-literal `path:` subset covers the shapes real apps write at
14
+ * no install cost. A computed path (`path: _kHome`) is skipped rather than
15
+ * emitted as the literal `_kHome`, which would name a URL the app never serves.
31
16
  */
32
17
  import * as fs from "fs";
33
18
  import * as path from "path";
@@ -35,35 +20,21 @@ import { collectFiles } from "./fileWalk.js";
35
20
  const MAX_CANDIDATE_FILES = 200;
36
21
  const MAX_WALK_DEPTH = 10;
37
22
  const SKIP_DIRS = new Set([
38
- "build",
39
- ".dart_tool",
40
- ".git",
41
- ".idea",
42
- "android",
43
- "ios",
44
- "macos",
45
- "linux",
46
- "windows",
47
- "web",
48
- "node_modules",
49
- "test",
50
- ".pub-cache",
23
+ "build", ".dart_tool", ".git", ".idea", "android", "ios", "macos", "linux",
24
+ "windows", "web", "node_modules", "test", ".pub-cache",
51
25
  ]);
52
26
  /**
53
- * Match a GoRoute declaration's body — from the opening `(` through to its
54
- * matching `)`. We need the body to extract `path:`, screen widgets, and
55
- * the redirect/builder distinction.
56
- *
57
- * The regex captures the literal string after `path:` and the start of the
58
- * GoRoute call body; we then walk forward manually to find the matching
59
- * close paren (regex can't match balanced parens reliably).
27
+ * A `GoRoute(` through its `path:` string literal. The lazy `[\s\S]*?` scans
28
+ * forward for the first `path:`, so a GoRoute with no path of its own can borrow
29
+ * the next declaration's — those routes are dropped downstream instead, because
30
+ * a route with no resolvable widget produces no entry.
60
31
  */
61
32
  const GO_ROUTE_PATH_REGEX = /\bGoRoute\s*\(\s*(?:<[^>]+>\s*)?[\s\S]*?path\s*:\s*(['"])([^'"\\]*)\1/g;
62
- /** Identifier reference inside builder:/pageBuilder: bodies — `AuthorsScreen`, `Authors`. */
33
+ /** An identifier used as a constructor inside a builder body. */
63
34
  const SCREEN_WIDGET_REGEX = /\b([A-Z][A-Za-z0-9_]*)\s*\(/g;
64
- /** Top-level Dart import: `import 'src/screens/authors.dart';` or `package:foo/bar.dart`. */
35
+ /** A top-level Dart import. */
65
36
  const IMPORT_REGEX = /^\s*import\s+['"]([^'"]+)['"]/gm;
66
- /** Identifiers we never treat as screen widgets — Flutter framework + go_router internals. */
37
+ /** Identifiers that are never a screen: Flutter and go_router internals. */
67
38
  const NON_SCREEN_IDENTIFIERS = new Set([
68
39
  "BuildContext", "GoRouterState", "GoRoute", "GoRouter", "ShellRoute",
69
40
  "StatefulShellRoute", "ShellRouteBase", "MaterialPage", "CupertinoPage",
@@ -72,54 +43,90 @@ const NON_SCREEN_IDENTIFIERS = new Set([
72
43
  "MaterialApp", "CupertinoApp", "Scaffold", "AppBar",
73
44
  ]);
74
45
  export function extractDartRoutes(repositoryPath) {
75
- const candidates = findDartFiles(repositoryPath);
76
- const seen = new Set();
77
46
  const routes = [];
78
- for (const file of candidates) {
79
- let source;
80
- try {
81
- source = fs.readFileSync(file, "utf-8");
47
+ const sources = new Map();
48
+ const read = (file) => {
49
+ if (!sources.has(file)) {
50
+ try {
51
+ sources.set(file, fs.readFileSync(file, "utf-8"));
52
+ }
53
+ catch {
54
+ sources.set(file, undefined);
55
+ }
82
56
  }
83
- catch {
57
+ return sources.get(file);
58
+ };
59
+ for (const file of findDartFiles(repositoryPath)) {
60
+ const source = read(file);
61
+ if (!source)
84
62
  continue;
85
- }
86
- const imports = collectImports(source, file, repositoryPath);
63
+ const imports = dartImports(source, file, repositoryPath);
87
64
  GO_ROUTE_PATH_REGEX.lastIndex = 0;
88
65
  let match;
89
66
  while ((match = GO_ROUTE_PATH_REGEX.exec(source)) !== null) {
90
- const pathValue = match[2];
91
- if (seen.has(pathValue))
67
+ const routePath = match[2];
68
+ // A child route states its path relative to its parent. Composing the
69
+ // hierarchy is not done here, and emitting `details` as if it were a URL
70
+ // would send the browser to a page that does not exist.
71
+ if (!routePath.startsWith("/"))
92
72
  continue;
93
- seen.add(pathValue);
94
73
  const body = extractGoRouteBody(source, match.index);
95
- const inspect = inspectGoRouteBody(body);
96
- const screenFiles = [];
97
- for (const widget of inspect.screenWidgets) {
98
- const resolved = imports.get(widget);
99
- if (resolved)
100
- screenFiles.push(resolved);
74
+ for (const componentName of screenWidgetsOf(body)) {
75
+ routes.push({
76
+ path: routePath,
77
+ componentName,
78
+ componentFile: declaringFile(componentName, imports, read),
79
+ declaredIn: file,
80
+ });
101
81
  }
102
- routes.push({
103
- path: pathValue,
104
- declaredIn: file,
105
- screenWidgets: inspect.screenWidgets,
106
- screenFiles,
107
- isRedirectOnly: inspect.isRedirectOnly,
108
- });
109
82
  }
110
83
  }
111
84
  return routes;
112
85
  }
113
86
  /**
114
- * Starting at `start` (the index of the matched `GoRoute` token), find the
115
- * end of the GoRoute call by counting balanced `(` / `)`. Stops at the
116
- * matching close paren of the outermost GoRoute(...). Strings are skipped
117
- * so we don't mis-count a `)` inside a string literal.
118
- *
119
- * Returns the call body (between the outer parens) — empty string on failure.
87
+ * The file that declares `widget`, found by reading the importing file's own
88
+ * imports for the class. The v1 reader guessed instead — it derived candidate
89
+ * identifiers from each import's filename (`authors.dart` was assumed to export
90
+ * `Authors`, `AuthorsScreen`, `AuthorsPage`, `AuthorsView`) and accepted the
91
+ * first. A guess that lands wrong names a URL the file does not render, which is
92
+ * the one answer this scan must never give.
93
+ */
94
+ function declaringFile(widget, imports, read) {
95
+ const declaration = new RegExp(`\\bclass\\s+${widget}\\b`);
96
+ for (const imported of imports) {
97
+ const source = read(imported);
98
+ if (source && declaration.test(source))
99
+ return imported;
100
+ }
101
+ return undefined;
102
+ }
103
+ /** Absolute paths of the repository files a Dart file imports. `package:` and
104
+ * `dart:` imports are not resolved, so a widget reached only that way has no
105
+ * file and its route is dropped rather than guessed. */
106
+ function dartImports(source, filePath, repositoryPath) {
107
+ const resolved = [];
108
+ const fileDir = path.dirname(filePath);
109
+ IMPORT_REGEX.lastIndex = 0;
110
+ let match;
111
+ while ((match = IMPORT_REGEX.exec(source)) !== null) {
112
+ const spec = match[1];
113
+ if (spec.startsWith("package:") || spec.startsWith("dart:"))
114
+ continue;
115
+ if (!spec.endsWith(".dart"))
116
+ continue;
117
+ const target = path.resolve(fileDir, spec);
118
+ if (!target.startsWith(repositoryPath))
119
+ continue;
120
+ if (!resolved.includes(target))
121
+ resolved.push(target);
122
+ }
123
+ return resolved;
124
+ }
125
+ /**
126
+ * The GoRoute call body, from the `(` after `GoRoute` to its matching `)`.
127
+ * String contents are skipped so a `)` inside a literal does not close the call.
120
128
  */
121
129
  function extractGoRouteBody(source, start) {
122
- // Find the first '(' after `GoRoute`
123
130
  const openParen = source.indexOf("(", start);
124
131
  if (openParen < 0)
125
132
  return "";
@@ -150,49 +157,32 @@ function extractGoRouteBody(source, start) {
150
157
  return "";
151
158
  return source.slice(openParen + 1, i - 1);
152
159
  }
153
- /**
154
- * Inspect a GoRoute body to find:
155
- * - screen widget identifiers from builder:/pageBuilder:
156
- * - whether the route is redirect-only (no builder/pageBuilder)
157
- *
158
- * The `routes:` nested GoRoute siblings are stripped before scanning so
159
- * we don't pick up child route widgets as part of the parent's screens.
160
- */
161
- function inspectGoRouteBody(body) {
162
- const builderPart = extractKeyValue(body, "builder");
163
- const pageBuilderPart = extractKeyValue(body, "pageBuilder");
164
- const redirectPart = extractKeyValue(body, "redirect");
165
- const screenWidgets = [];
166
- for (const part of [builderPart, pageBuilderPart]) {
160
+ /** Widgets a GoRoute body builds. A route with `redirect:` and no builder
161
+ * renders nothing, and yields none. */
162
+ function screenWidgetsOf(body) {
163
+ const widgets = [];
164
+ for (const key of ["builder", "pageBuilder"]) {
165
+ const part = keyValueOf(body, key);
167
166
  if (!part)
168
167
  continue;
169
168
  SCREEN_WIDGET_REGEX.lastIndex = 0;
170
169
  let m;
171
170
  while ((m = SCREEN_WIDGET_REGEX.exec(part)) !== null) {
172
- const id = m[1];
173
- if (NON_SCREEN_IDENTIFIERS.has(id))
171
+ if (NON_SCREEN_IDENTIFIERS.has(m[1]))
174
172
  continue;
175
- if (!screenWidgets.includes(id))
176
- screenWidgets.push(id);
173
+ if (!widgets.includes(m[1]))
174
+ widgets.push(m[1]);
177
175
  }
178
176
  }
179
- const hasBuilder = !!builderPart || !!pageBuilderPart;
180
- const hasRedirect = !!redirectPart;
181
- const isRedirectOnly = hasRedirect && !hasBuilder;
182
- return { screenWidgets, isRedirectOnly };
177
+ return widgets;
183
178
  }
184
- /**
185
- * Find a `key:` value in a GoRoute body. Returns the substring after `key:`
186
- * up to the next top-level comma (depth 0 brace/paren/bracket), or `null`
187
- * when the key isn't present. Strings and nested groups are skipped.
188
- */
189
- function extractKeyValue(body, key) {
179
+ /** The value of `key:` in a GoRoute body, up to the next top-level comma. */
180
+ function keyValueOf(body, key) {
190
181
  const re = new RegExp(`\\b${key}\\s*:`, "g");
191
182
  const m = re.exec(body);
192
183
  if (!m)
193
184
  return null;
194
185
  let i = m.index + m[0].length;
195
- // Skip whitespace
196
186
  while (i < body.length && /\s/.test(body[i]))
197
187
  i++;
198
188
  const start = i;
@@ -225,70 +215,8 @@ function extractKeyValue(body, key) {
225
215
  }
226
216
  return body.slice(start, i).trim();
227
217
  }
228
- /**
229
- * Build a `local-name → absolute-file-path` map from the file's imports.
230
- *
231
- * Dart imports two ways:
232
- * import 'src/screens/authors.dart'; → relative within current package
233
- * import 'package:foo/screens/x.dart'; → cross-package, not resolved here
234
- *
235
- * We only resolve relative imports (the common case for in-app screens).
236
- * The "local name" for a relative import is taken as ALL identifiers that
237
- * the file exports — but since we don't parse the imported file's exports,
238
- * we use a coarse approximation: the importing file's `import 'X.dart';`
239
- * makes available any PascalCase identifier referenced from the file's
240
- * scope. We map every identifier we see in builder/pageBuilder bodies to
241
- * the union of relative imports — first matching one wins.
242
- *
243
- * For the common Flutter pattern (one screen widget per screen file with
244
- * a matching name like `AuthorsScreen`), we approximate via filename:
245
- * import 'src/screens/authors.dart' → expose `AuthorsScreen`, `Authors`
246
- *
247
- * False positives on this approximation just mean we surface a route that
248
- * shouldn't have been; false negatives just mean we fall back to "no
249
- * diff-match" and surface all routes (the previous behavior).
250
- */
251
- function collectImports(source, filePath, repoPath) {
252
- const result = new Map();
253
- const fileDir = path.dirname(filePath);
254
- IMPORT_REGEX.lastIndex = 0;
255
- let match;
256
- while ((match = IMPORT_REGEX.exec(source)) !== null) {
257
- const spec = match[1];
258
- if (spec.startsWith("package:") || spec.startsWith("dart:"))
259
- continue;
260
- if (!spec.endsWith(".dart"))
261
- continue;
262
- const resolved = path.resolve(fileDir, spec);
263
- if (!resolved.startsWith(repoPath))
264
- continue;
265
- // Filename-based heuristic: `authors.dart` exposes `Authors`, `AuthorsScreen`,
266
- // `AuthorsScreenState`, etc. We register a few common shapes derived from
267
- // the file's basename. Conflicts (the same identifier present in two
268
- // imports) resolve to the first registration.
269
- const base = path.basename(spec, ".dart");
270
- const camel = snakeToCamel(base);
271
- const candidates = [
272
- camel,
273
- camel + "Screen",
274
- camel + "Page",
275
- camel + "View",
276
- ];
277
- for (const id of candidates) {
278
- if (!result.has(id))
279
- result.set(id, resolved);
280
- }
281
- }
282
- return result;
283
- }
284
- function snakeToCamel(s) {
285
- return s
286
- .split("_")
287
- .map((p) => p.charAt(0).toUpperCase() + p.slice(1))
288
- .join("");
289
- }
290
- function findDartFiles(repoPath) {
291
- return collectFiles(repoPath, {
218
+ function findDartFiles(repositoryPath) {
219
+ return collectFiles(repositoryPath, {
292
220
  match: (entry) => entry.name.endsWith(".dart"),
293
221
  limit: MAX_CANDIDATE_FILES,
294
222
  maxDepth: MAX_WALK_DEPTH,
@@ -97,3 +97,15 @@ export declare function isPomReuseEnabled(): boolean;
97
97
  * yields the single reuse pass it did before the feature existed.
98
98
  */
99
99
  export declare function isUtilsReuseEnabled(): boolean;
100
+ /**
101
+ * Returns true when the calling agent has the Skyramp Agent Skills plugin
102
+ * loaded (SKYRAMP_SKILLS_LOADED=1). Testbot sets this only for Claude Code
103
+ * runs that also pass `--plugin-dir` at the `plugin/` directory of this
104
+ * package (SKYR-4296).
105
+ *
106
+ * When ON, tools whose only job is to hand back instruction text that now
107
+ * lives in a skill are not registered, and the prompts that used to name
108
+ * those tools name the skill instead. Two entry points for one text would let
109
+ * the tool result override the prompt on conflict.
110
+ */
111
+ export declare function isSkillsLoaded(): boolean;
@@ -129,3 +129,17 @@ function isUtilsReuseDefaultOrg() {
129
129
  "";
130
130
  return UTILS_REUSE_DEFAULT_ORGS.includes(owner.toLowerCase());
131
131
  }
132
+ /**
133
+ * Returns true when the calling agent has the Skyramp Agent Skills plugin
134
+ * loaded (SKYRAMP_SKILLS_LOADED=1). Testbot sets this only for Claude Code
135
+ * runs that also pass `--plugin-dir` at the `plugin/` directory of this
136
+ * package (SKYR-4296).
137
+ *
138
+ * When ON, tools whose only job is to hand back instruction text that now
139
+ * lives in a skill are not registered, and the prompts that used to name
140
+ * those tools name the skill instead. Two entry points for one text would let
141
+ * the tool result override the prompt on conflict.
142
+ */
143
+ export function isSkillsLoaded() {
144
+ return process.env.SKYRAMP_SKILLS_LOADED === "1";
145
+ }
@@ -1,34 +1,55 @@
1
1
  /**
2
- * Frontend selector extraction for UI-test-maintenance discovery (Pass 3, "selector-edge").
2
+ * The `data-*` attributes and selector literals a frontend diff touched.
3
3
  *
4
- * Playwright/Cypress page objects couple to a changed component by SELECTOR — a
5
- * `data-testid` value or a CSS class token — not by filename or by `import`. So the
6
- * filename-token (pass 1) and import-edge (pass 2) promotion signals in
7
- * TestDiscoveryService Step 2 both miss them: a page object `table.component.ts` shares
8
- * no name token with the changed `CascadeFields.tsx` and does not import it — it selects
9
- * `.t--table-filter-columns-dropdown` at runtime.
4
+ * One reader now: the removal guard (SKYR-4305), which reports an element the
5
+ * diff deleted from a page that survives so the agent can assert it is gone.
10
6
  *
11
- * This module extracts the selector literals that a frontend diff added OR removed, so
12
- * discovery can promote any existing test/page-object whose body still references one of
13
- * them. Both sides of a rename are captured deliberately: a page object that has NOT been
14
- * updated still contains the OLD value, so matching the removed literal is what surfaces
15
- * the test that needs maintenance.
16
- *
17
- * This is the frontend analog of importerHop's `extractChangedSymbols` (backend type
18
- * names) — kept in its own frontend-scoped module rather than in the backend-resource
19
- * importerHop, per the codebase convention of keeping domain vocabularies local.
7
+ * This module also used to lift every selector literal a diff added or removed,
8
+ * so discovery could promote a page object whose body referenced one. That was
9
+ * the server deciding which existing tests a change touches — the external
10
+ * tests are loaded uncut up to the discovery cap now, and the agent reads them.
20
11
  */
21
- export declare const MAX_CHANGED_SELECTORS = 80;
12
+ /** A removed element's identity: the `data-*` attribute and its literal value. */
13
+ export interface RemovedAttribute {
14
+ /** Lower-cased attribute name, e.g. `data-testid`, `data-cy`, `data-section`. */
15
+ attribute: string;
16
+ value: string;
17
+ }
18
+ /** Identifying attribute VALUES that appear on `+` lines of a diff (any number of files). */
19
+ export declare function extractAddedAttributeValues(diff: string): string[];
22
20
  /**
23
- * Extract selector literals added or removed in a single file's diff slice.
21
+ * Identifying `data-*` attributes one file's diff REMOVED: literals on `-` lines, minus the
22
+ * cases where the element still renders somewhere:
23
+ *
24
+ * - **moved / extracted** — the same value appears on a `+` line of this file or, via
25
+ * `addedValuesElsewhere`, anywhere else in the diff (a component extracted into a new
26
+ * file is the common shape);
27
+ * - **not an element** — a `-` line counts only when it opens a JSX/HTML tag or is a
28
+ * wrapped attribute line of a tag opened above it, on a changed or an unchanged
29
+ * line (the tag is carried until the `>` that closes it; a `>` inside a quoted
30
+ * value, a `{…}` expression or an `=>` arrow is content and does not close it). A deleted comment, docs snippet, story arg or page-object
31
+ * `getByTestId(...)` call that merely mentions the attribute is not an element
32
+ * removal — the element itself may well survive on an unchanged context line;
33
+ * - **renamed** — a rename cannot be told apart by literal identity (old and new
34
+ * value differ by definition), so it is paired structurally: within the same hunk,
35
+ * each removed ELEMENT is paired with AT MOST ONE added element that opens the same
36
+ * tag, carries a NEW identifying attribute, and keeps every other attribute NAME
37
+ * the removed element set on its changed lines (a rename may add attributes or
38
+ * edit their values, never drop one). With the tag kept on a context line, the
39
+ * changed lines are exactly the attributes that differ, so an in-place rename
40
+ * compares only what it touched while an in-place replacement that drops the old
41
+ * element's handlers does not pair. An element whose changed lines carry nothing
42
+ * but the identifier pairs by tag alone: a relabelled rename (`clear-state` →
43
+ * `reset-session`, "Clear State" → "Reset Session") and a same-shape replacement
44
+ * are the same diff, and the tie goes to rename — a guard on a retired identifier
45
+ * passes vacuously, and the agent still reads the diff for what the list lacks. A paired added element is consumed, so one addition
46
+ * suppresses one removal, not every removal in the hunk. SKYR-4339: pairing by tag
47
+ * alone consumed a genuinely removed `<a href="#">` with a genuinely added
48
+ * `<a href="https://…">` in the same hunk (judgement eval RG03), so the guard was
49
+ * never listed.
24
50
  *
25
- * Two complementary passes over the changed (`+`/`-`) lines, excluding the `+++`/`---`
26
- * file headers:
27
- * 1. Explicit test-facing attribute/API values: `data-testid` / `data-test` /
28
- * `data-test-id` / `data-cy` / `data-qa` attribute values and `getByTestId(...)`
29
- * arguments (trusted — accepts camelCase or single-delimiter ids).
30
- * 2. Distinctive kebab / BEM class tokens anywhere on the line (>= 2 hyphens or `--`),
31
- * which catches CSS-class-based coupling (`t--table-filter-columns-dropdown`,
32
- * `ads-button--primary`).
51
+ * Feeds `uiContext.removedElements` (SKYR-4305) — the grounding for a removal guard
52
+ * (`toBeHidden()` on `getByTestId(value)` for `data-testid`, on
53
+ * `locator('[<attribute>="<value>"]')` for any other `data-*`).
33
54
  */
34
- export declare function extractChangedSelectors(fileDiff: string): string[];
55
+ export declare function extractRemovedAttributes(fileDiff: string, addedValuesElsewhere?: Iterable<string>): RemovedAttribute[];