@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,29 +0,0 @@
1
- import { ScenarioStep } from "../types/RepositoryAnalysis.js";
2
- /** A method+path pair extracted from an actually-changed (`+`/`-`) diff line. */
3
- export interface ChangedRoute {
4
- method: string;
5
- path: string;
6
- }
7
- /**
8
- * Extract method+path from every changed (`+`/`-`, non-header) line of a raw
9
- * unified diff. Reuses `parseRouteLine`, which already strips the leading
10
- * `+`/`-` marker and matches the same route-decorator patterns the endpoint
11
- * scanner does.
12
- */
13
- export declare function collectChangedRouteLines(diffText: string): ChangedRoute[];
14
- /**
15
- * The first step of `steps` that targets a method+path on the changed hunk, or
16
- * undefined. Returns the STEP, not a boolean, because two callers need
17
- * different things from the same comparison: the ranker asks whether any step
18
- * matches, and the subject resolver needs which ones do.
19
- *
20
- * Diff-extracted paths are local to the file's own router declaration (e.g.
21
- * "/suggestions"); scenario step paths are fully mounted (e.g.
22
- * "/api/recipes/suggestions"). A local path matches when the step path ends
23
- * with it at a segment boundary (`pathSuffixMatches`), so the cross-file mount
24
- * prefix difference (see recoverRemovedEndpointsFromBase, SKYR-4026) does not
25
- * prevent the match — while a plain `endsWith` would also wrongly match an
26
- * unrelated path that merely shares a trailing substring (e.g. "/preorders"
27
- * against "orders").
28
- */
29
- export declare function findStepOnChangedRoute(steps: ScenarioStep[] | undefined, changedRoutes: ChangedRoute[]): ScenarioStep | undefined;
@@ -1,87 +0,0 @@
1
- import { parseRouteLine, normalizeDiffPath } from "./routeParsers.js";
2
- import { isBareIdPath, pathSuffixMatches } from "./pathMatching.js";
3
- /**
4
- * Extract method+path from every changed (`+`/`-`, non-header) line of a raw
5
- * unified diff. Reuses `parseRouteLine`, which already strips the leading
6
- * `+`/`-` marker and matches the same route-decorator patterns the endpoint
7
- * scanner does.
8
- */
9
- export function collectChangedRouteLines(diffText) {
10
- const routes = [];
11
- let currentFile = "";
12
- let removedFile = "";
13
- for (const line of diffText.split("\n")) {
14
- // Track the current file from the unified-diff header so parseRouteLine
15
- // gets the real path — its UI-component guard (UI_COMPONENT_EXT) depends
16
- // on it, or a route-shaped line inside a .tsx/.jsx file (e.g. a client
17
- // router registration) gets misparsed as a changed backend route.
18
- // A deleted file has "+++ /dev/null", so the old side is the only name it
19
- // has. Without it parseRouteLine gets an empty path and every
20
- // extension-gated framework (Go, Rails, PHP) rejects the line, losing the
21
- // routes the deletion removed.
22
- if (line.startsWith("--- ")) {
23
- const spec = line.slice(4).trim().split("\t")[0];
24
- removedFile = spec === "/dev/null" ? "" : normalizeDiffPath(spec);
25
- continue;
26
- }
27
- if (line.startsWith("+++ ")) {
28
- const spec = line.slice(4).trim().split("\t")[0];
29
- currentFile = spec === "/dev/null" ? removedFile : normalizeDiffPath(spec);
30
- continue;
31
- }
32
- if (line.startsWith("diff --git") || line.startsWith("index "))
33
- continue;
34
- if (!(line.startsWith("+") || line.startsWith("-")))
35
- continue;
36
- const parsed = parseRouteLine(line, currentFile);
37
- if (parsed)
38
- routes.push({ method: parsed.method, path: parsed.path });
39
- }
40
- return routes;
41
- }
42
- /**
43
- * The first step of `steps` that targets a method+path on the changed hunk, or
44
- * undefined. Returns the STEP, not a boolean, because two callers need
45
- * different things from the same comparison: the ranker asks whether any step
46
- * matches, and the subject resolver needs which ones do.
47
- *
48
- * Diff-extracted paths are local to the file's own router declaration (e.g.
49
- * "/suggestions"); scenario step paths are fully mounted (e.g.
50
- * "/api/recipes/suggestions"). A local path matches when the step path ends
51
- * with it at a segment boundary (`pathSuffixMatches`), so the cross-file mount
52
- * prefix difference (see recoverRemovedEndpointsFromBase, SKYR-4026) does not
53
- * prevent the match — while a plain `endsWith` would also wrongly match an
54
- * unrelated path that merely shares a trailing substring (e.g. "/preorders"
55
- * against "orders").
56
- */
57
- export function findStepOnChangedRoute(steps, changedRoutes) {
58
- if (changedRoutes.length === 0)
59
- return undefined;
60
- for (const step of steps ?? []) {
61
- const stepMethod = (step.method ?? "").toUpperCase();
62
- const stepPath = (step.path ?? "").replace(/\/+$/, "");
63
- for (const route of changedRoutes) {
64
- if (route.method.toUpperCase() !== stepMethod)
65
- continue;
66
- const routePath = route.path.replace(/\/+$/, "");
67
- // A diff line declaring nothing but a bare id — `router.delete("/:id")`
68
- // from a mounted Express router, `@Delete(":id")` from NestJS — names no
69
- // resource, so it suffix-matches every `DELETE …/{id}` step. Both callers
70
- // ask "does this changed route touch this step", and the answer there is
71
- // no: for the ranker it lifted an unrelated candidate over a business-rule
72
- // one, and for subject resolution it would make every step a subject.
73
- // A NAMED parameter (`/:order_id`) still matches only its own resource,
74
- // because the helper unifies a parameter's spelling and not its name.
75
- if (isBareIdPath(routePath))
76
- continue;
77
- if (routePath === "") {
78
- if (stepPath === "" || stepPath === "/")
79
- return step;
80
- continue;
81
- }
82
- if (pathSuffixMatches(stepPath, routePath))
83
- return step;
84
- }
85
- }
86
- return undefined;
87
- }
@@ -1,9 +0,0 @@
1
- import { FrontendFileIntegration } from "../types/FrontendIntegration.js";
2
- export type { FrontendFileIntegration } from "../types/FrontendIntegration.js";
3
- /**
4
- * Checks each changed frontend file for a production importer — the
5
- * deterministic version of the grep the testbot prompt used to ask the
6
- * agent to run mid-task. See module doc for the signature-based approach
7
- * and the fail-open rationale.
8
- */
9
- export declare function checkFrontendFileIntegration(repositoryPath: string, changedFrontendFiles: string[]): FrontendFileIntegration[];
@@ -1,243 +0,0 @@
1
- /**
2
- * Server-side "frontend component integration check" — SKYR-3855.
3
- *
4
- * Deterministically answers the question the testbot prompt previously asked
5
- * the agent to answer via a mid-run grep: "is this changed frontend file
6
- * actually rendered anywhere in the running app, or is it dead/orphaned
7
- * code?" Computed here so the skip decision for UI test generation consumes
8
- * a server fact instead of an LLM judgment call made under budget pressure.
9
- *
10
- * Detection greps for the module's PATH-REFERENCE SIGNATURE — the basename
11
- * preceded by a slash and followed by a quote (`/Foo'`, `/Foo"`, `/Foo\``) —
12
- * rather than the bare component name. Imports always reference the module
13
- * path in quotes (`from './Foo'`, `require("components/Foo")`,
14
- * `import('./Foo')`), so the signature carries import-context precision
15
- * lexically: no per-line verification, no file reads in Node, and `git grep
16
- * -l -F` returns only matching paths (bounded output regardless of file
17
- * sizes). Named imports that go through a barrel resolve one hop: the
18
- * barrel's `export { Foo } from './Foo'` contains the signature, and the
19
- * barrel counts as the importer. Known miss: extensionless root-level
20
- * imports with no slash (`from 'Foo'`) — root files are route/entrypoint
21
- * short-circuited in practice.
22
- *
23
- * Fail-open by design: any scan failure reports `integrated: true` so a bug
24
- * in this heuristic can never wrongly suppress UI test generation.
25
- */
26
- import { execFileSync } from "child_process";
27
- import * as fs from "fs";
28
- import * as path from "path";
29
- import { isTestFile, isI18nLocaleFile } from "../prompts/test-recommendation/scopeAssessment.js";
30
- import { logger } from "./logger.js";
31
- import { walkDir } from "./fileWalk.js";
32
- import { buildPathSignatures, importsIndexModule, isIndexModule } from "./pathSignatures.js";
33
- import { IntegrationReason } from "../types/FrontendIntegration.js";
34
- const MAX_IMPORTERS = 10;
35
- const MAX_FILES_WALKED = 3000;
36
- const MAX_FILE_SIZE_BYTES = 512 * 1024;
37
- // Framework route/page/entrypoint conventions — mirrors the testbot prompt's
38
- // "Never apply the unintegrated heuristic to framework route/entrypoint files"
39
- // rule. These are reachable by convention, so the grep is skipped entirely.
40
- // `app` is deliberately NOT in the dir pattern: it is a common generic
41
- // monorepo directory (e.g. appsmith's top-level app/), and matching it made
42
- // the check inert for every file in such repos (eval run 28618761653).
43
- // Next.js App Router route files are still short-circuited via their
44
- // basenames (page.*, layout.*); non-route components under app/ resolve
45
- // through the importer grep like any other file.
46
- const ROUTE_DIR_PATTERN = /(^|\/)(pages|routes)(\/|$)/i;
47
- // Generic entrypoint basenames (index/main/app/_app) are only reachable-by-
48
- // convention at the repo root or directly under `src/` — e.g. Vite's
49
- // `src/main.tsx`, CRA's `src/index.tsx`, Pages Router's root `_app.tsx`.
50
- // Deeper in the tree these are ordinary component filenames (folder-per-
51
- // component layouts commonly name the component file `index.tsx`), so
52
- // matching them anywhere would short-circuit real, checkable components —
53
- // the same class of bug as the `app/` dir match (see module doc).
54
- const ROOT_ENTRYPOINT_BASENAME_PATTERN = /^(src\/)?(index|main|app|_app)\.[^/]+$/i;
55
- // `page`/`layout` are App Router-specific and only meaningful under an
56
- // `app/` segment; outside of one, a file named `page.tsx` or `layout.tsx`
57
- // has no special framework meaning and should go through the normal check.
58
- const APP_ROUTER_BASENAME_PATTERN = /(^|\/)app\/(.*\/)?(page|layout)\.[^/]+$/i;
59
- // Frontend-importer file types only. Deliberately NOT shared with
60
- // analyzeChangesTool's SOURCE_EXTS (backend-inclusive: py|java|go|…) or
61
- // repoScanner's ROUTE_FILE_PATTERN — those detect backend route handlers;
62
- // this detects files that can render a frontend component. The domains must
63
- // stay independently tunable (see the `app/` route-dir lesson above).
64
- const FRONTEND_SOURCE_EXT_PATTERN = /\.(ts|tsx|js|jsx|mjs|cjs|vue|svelte)$/i;
65
- const STORY_FILE_PATTERN = /\.stories\.[^/]+$/i;
66
- const EXCLUDED_DIR_PATTERN = /(^|\/)(node_modules|dist|build|\.git)(\/|$)/;
67
- function isRouteOrEntrypoint(file) {
68
- return (ROUTE_DIR_PATTERN.test(file) ||
69
- ROOT_ENTRYPOINT_BASENAME_PATTERN.test(file) ||
70
- APP_ROUTER_BASENAME_PATTERN.test(file));
71
- }
72
- function isExcludedImporterPath(relPath) {
73
- return (EXCLUDED_DIR_PATTERN.test(relPath) ||
74
- isTestFile(relPath) ||
75
- STORY_FILE_PATTERN.test(relPath) ||
76
- !FRONTEND_SOURCE_EXT_PATTERN.test(relPath));
77
- }
78
- /**
79
- * Runs `git grep -l -F` for the given fixed-string signatures (OR'd
80
- * together) scoped to the repository's tracked files. Returns file paths
81
- * relative to `repositoryPath` — only paths, never matched lines, so output
82
- * stays bounded no matter how large the matching files are. An exit code of
83
- * 1 means "no matches" — a normal outcome, not an error. Throws on real
84
- * failures (not a git repo, git not installed) so the caller can fall back
85
- * to a manual walk.
86
- */
87
- function gitGrepFiles(repositoryPath, signatures) {
88
- const args = ["grep", "-l", "-F", "-I"];
89
- for (const signature of signatures)
90
- args.push("-e", signature);
91
- try {
92
- const stdout = execFileSync("git", args, {
93
- cwd: repositoryPath,
94
- encoding: "utf8",
95
- maxBuffer: 20 * 1024 * 1024,
96
- });
97
- return stdout.split("\n").filter(Boolean);
98
- }
99
- catch (err) {
100
- if (err && err.status === 1)
101
- return []; // no matches — not an error
102
- throw err;
103
- }
104
- }
105
- /**
106
- * Bounded manual directory walk used only when `git grep` itself fails
107
- * (e.g. not a git repository). Caps the number and size of files scanned so
108
- * a huge repo can't turn a fallback path into a multi-minute scan or a
109
- * large allocation.
110
- *
111
- * If the walk is cut off by `MAX_FILES_WALKED` before finding any importer,
112
- * the "no importer" result can't be trusted — an importer may exist in the
113
- * unscanned remainder. Per the module's fail-open contract, that ambiguity
114
- * throws so the caller reports `scan-error` (`integrated: true`) instead of
115
- * a false "no-importers".
116
- */
117
- function manualWalkGrep(repositoryPath, signatures) {
118
- const results = [];
119
- let filesScanned = 0;
120
- let truncated = false;
121
- const shouldSkipDir = (_entry, full) => {
122
- // EXCLUDED_DIR_PATTERN matches on the "/"-normalized relative path.
123
- const rel = path.relative(repositoryPath, full).replace(/\\/g, "/");
124
- return EXCLUDED_DIR_PATTERN.test(rel + "/");
125
- };
126
- for (const [entry, abs] of walkDir(repositoryPath, { shouldSkipDir })) {
127
- if (!FRONTEND_SOURCE_EXT_PATTERN.test(entry.name))
128
- continue;
129
- if (filesScanned >= MAX_FILES_WALKED) {
130
- truncated = true;
131
- break; // stop the entire walk
132
- }
133
- filesScanned++;
134
- // Normalize to forward slashes: on Windows, path.relative() returns
135
- // "\\"-separated paths, but isTestFile assumes "/".
136
- const rel = path.relative(repositoryPath, abs).replace(/\\/g, "/");
137
- try {
138
- if (fs.statSync(abs).size > MAX_FILE_SIZE_BYTES)
139
- continue;
140
- const content = fs.readFileSync(abs, "utf-8");
141
- if (signatures.some((s) => content.includes(s)))
142
- results.push(rel);
143
- }
144
- catch {
145
- // Unreadable/vanished file — skip it, keep scanning.
146
- }
147
- }
148
- if (truncated && results.length === 0) {
149
- throw new Error(`manual-walk fallback truncated at ${MAX_FILES_WALKED} files with no importer found`);
150
- }
151
- return results;
152
- }
153
- function searchProductionImporters(repositoryPath, signatures) {
154
- try {
155
- return gitGrepFiles(repositoryPath, signatures);
156
- }
157
- catch (err) {
158
- logger.warning("git grep failed for integration check — falling back to bounded manual walk", {
159
- repositoryPath,
160
- error: err instanceof Error ? err.message : String(err),
161
- });
162
- return manualWalkGrep(repositoryPath, signatures);
163
- }
164
- }
165
- function checkOneFile(repositoryPath, file) {
166
- if (isRouteOrEntrypoint(file)) {
167
- return { file, integrated: true, importers: [], reason: IntegrationReason.RouteOrEntrypoint };
168
- }
169
- // i18n/locale content is commonly loaded via a runtime-templated path (e.g.
170
- // i18next-http-backend's `loadPath: '/locales/{{lng}}/{{ns}}.json'`) rather
171
- // than a static quoted import — the signature grep below would never find
172
- // that reference and would misreport a real, live locale file as dead code.
173
- if (isI18nLocaleFile(file)) {
174
- return { file, integrated: true, importers: [], reason: IntegrationReason.I18nLocaleContent };
175
- }
176
- // A missing changed file (e.g. deleted, or an unexpected diff shape) means
177
- // the classification can't be trusted — fail open rather than risk wrongly
178
- // suppressing UI test generation.
179
- if (!fs.existsSync(path.join(repositoryPath, file))) {
180
- logger.warning("Integration check: changed file not found on disk — reporting integrated (fail-open)", {
181
- file,
182
- });
183
- return { file, integrated: true, importers: [], reason: IntegrationReason.ScanError };
184
- }
185
- try {
186
- const signatures = buildPathSignatures(file);
187
- const candidates = searchProductionImporters(repositoryPath, signatures)
188
- .filter((candidate) => candidate !== file && !isExcludedImporterPath(candidate));
189
- const importers = isIndexModule(file)
190
- ? selectIndexModuleImporters(repositoryPath, file, candidates)
191
- : candidates.slice(0, MAX_IMPORTERS);
192
- return {
193
- file,
194
- integrated: importers.length > 0,
195
- importers,
196
- reason: importers.length > 0 ? IntegrationReason.Imported : IntegrationReason.NoImporters,
197
- };
198
- }
199
- catch (err) {
200
- logger.warning("Integration check scan failed — reporting integrated (fail-open)", {
201
- file,
202
- error: err instanceof Error ? err.message : String(err),
203
- });
204
- return { file, integrated: true, importers: [], reason: IntegrationReason.ScanError };
205
- }
206
- }
207
- /**
208
- * An index module's directory stem (`/_constants'`) is a plain string match, so a
209
- * file importing its own unrelated `./_constants` also hits. Keep only candidates
210
- * whose specifier resolves to this module's directory. Reads stop once
211
- * MAX_IMPORTERS are accepted; files over MAX_FILE_SIZE_BYTES are skipped like in
212
- * manualWalkGrep; an unreadable candidate is kept (fail-open, like the module).
213
- */
214
- function selectIndexModuleImporters(repositoryPath, file, candidates) {
215
- const accepted = [];
216
- for (const candidate of candidates) {
217
- if (accepted.length >= MAX_IMPORTERS)
218
- break;
219
- const abs = path.join(repositoryPath, candidate);
220
- let content;
221
- try {
222
- if (fs.statSync(abs).size > MAX_FILE_SIZE_BYTES)
223
- continue;
224
- content = fs.readFileSync(abs, "utf8");
225
- }
226
- catch {
227
- accepted.push(candidate);
228
- continue;
229
- }
230
- if (importsIndexModule(candidate, content, file))
231
- accepted.push(candidate);
232
- }
233
- return accepted;
234
- }
235
- /**
236
- * Checks each changed frontend file for a production importer — the
237
- * deterministic version of the grep the testbot prompt used to ask the
238
- * agent to run mid-task. See module doc for the signature-based approach
239
- * and the fail-open rationale.
240
- */
241
- export function checkFrontendFileIntegration(repositoryPath, changedFrontendFiles) {
242
- return changedFrontendFiles.map((file) => checkOneFile(repositoryPath, file));
243
- }
@@ -1,135 +0,0 @@
1
- import { sliceAddedLinesByFile } from "./branchDiff.js";
2
- import type { ScannedEndpoint } from "./repoScanner.js";
3
- /**
4
- * Derives a singular, lowercase "resource token" from a backend source file
5
- * path — e.g. "src/dtos/user.dto.ts" -> "user",
6
- * "modules/orders/order.service.ts" -> "order". Strips the file extension and
7
- * a trailing framework-layer suffix, then reuses `extractResourceFromPath`'s
8
- * "last meaningful path segment" rule by treating the remaining slash-joined
9
- * path as if it were a URL path.
10
- */
11
- export declare function deriveResourceToken(file: string): string;
12
- /**
13
- * Derives ALL plausible resource-affinity tokens for a changed file: the
14
- * filename-derived token from `deriveResourceToken` (when it isn't generic),
15
- * plus tokens split out of any class/interface/type/enum/schema symbols
16
- * declared on the file's ADDED diff lines (when `diffHunk` is supplied).
17
- *
18
- * This is the sharper anchor requested on PR #588: a generic multi-DTO
19
- * module (`schemas/actions.py` holding `class DeploymentCreate`, `class
20
- * FlowCreate`, ...) has no single filename->resource mapping, but the
21
- * declared symbol name IS the resource — `DeploymentCreate` -> "deployment".
22
- * Same problem exists for TS (`types/common.ts` holding many interfaces).
23
- *
24
- * `diffHunk` is expected to be the file's own added-line slice (e.g. one
25
- * entry's lines from `sliceAddedLinesByFile`, joined with "\n") — this
26
- * function does not re-slice a full multi-file diff itself, to avoid
27
- * duplicating that walking logic in two places.
28
- *
29
- * Deduped; capped at `MAX_RESOURCE_TOKENS` so a huge schema file with many
30
- * declared symbols can't explode the affinity filter into a near-match-all.
31
- * With no `diffHunk` (or no symbols found), the result is exactly
32
- * `[deriveResourceToken(file)]` (or `[]` if that's generic/unknown) —
33
- * identical to today's filename-only behavior.
34
- */
35
- /**
36
- * Declared symbol names changed in a unified diff — from added-line declarations
37
- * AND from the hunk-header enclosing type (so a field-only edit to an existing class,
38
- * e.g. `DeploymentCreate.work_pool_name` → required, still yields `DeploymentCreate`).
39
- * Returns raw names, used both for resource-token derivation (via `tokensFromSymbol`)
40
- * and for content-grep matching against test bodies (SKYR-3924).
41
- */
42
- export declare function extractChangedSymbols(diffHunk: string): string[];
43
- export declare function deriveResourceTokensForFile(file: string, diffHunk?: string): string[];
44
- /**
45
- * All singular resource tokens implied by an endpoint path, used for affinity
46
- * matching against a `deriveResourceToken` result. Shared by the importer-hop
47
- * and path-affinity fallbacks (SKYR-3857) — the ONLY endpoint-path to
48
- * resource-token derivation in the codebase, mirroring how
49
- * `deriveResourceTokensForFile` is the only file-side one:
50
- * - the primary (first non-skip, non-param, non-version) path segment —
51
- * catches literal action-style segments like "/users/me", which
52
- * `extractResourceFromPath`'s "last segment" rule would otherwise resolve
53
- * to "me" instead of "users". Version-like segments are excluded via
54
- * `isVersionLikeSegment` — `SKIP_PATH_SEGMENTS` covers "api"/"public" but
55
- * only v1-v3, so without it any endpoint versioned beyond v3 would derive
56
- * a nonsense token ("v4") as its resource.
57
- * - `extractResourceFromPath`'s own result, split on "_" for the sub-resource
58
- * composite form ("orders_items" -> "order", "item"). That function only
59
- * recognizes OpenAPI-style "{param}" segments (its dedup-key callers see
60
- * normalized paths), so ":id"/"[id]"-style params can flow through as its
61
- * result — filtered here rather than widening a function whose other
62
- * callers don't need it.
63
- */
64
- export declare function endpointResourceTokens(endpointPath: string): Set<string>;
65
- export interface ImporterHopResult {
66
- /** Endpoints mapped from unmatched files, deduped against `alreadyClassifiedEndpoints`. */
67
- endpoints: ScannedEndpoint[];
68
- /** Unmatched file -> endpoint paths newly mapped to it (for logging/drafting). */
69
- byFile: Record<string, string[]>;
70
- }
71
- /**
72
- * Maps unmatched changed files (from `classifyEndpointsByChangedFiles`) to
73
- * endpoints that import them, when the import target and the endpoint share
74
- * the same REST resource.
75
- *
76
- * `alreadyClassifiedEndpoints` (typically `changedEndpoints` + `newEndpoints`
77
- * from the same classification pass) is used purely for dedup — an endpoint
78
- * already classified directly is not duplicated into the returned `endpoints`
79
- * list, though the mapping is still established (harmless to skip re-adding
80
- * something already present).
81
- *
82
- * `diffContent`, when supplied, is the full unified diff — used to derive
83
- * per-file added-line hunks for `deriveResourceTokensForFile`'s symbol-based
84
- * affinity anchors (see module doc, limitation 2). Without it, affinity falls
85
- * back to the filename-only token, identical to pre-symbol-affinity behavior.
86
- */
87
- export declare function mapUnmatchedFilesToImporterEndpoints(repositoryPath: string, unmatchedFiles: string[], scannedEndpoints: ScannedEndpoint[], alreadyClassifiedEndpoints?: ScannedEndpoint[], diffContent?: string): ImporterHopResult;
88
- /** Inputs for response-shape scenario drafting, derived from an {@link ImporterHopResult}. */
89
- export interface HopResponseShapeInputs {
90
- /** Endpoints to draft response-shape scenarios for. */
91
- endpoints: ScannedEndpoint[];
92
- /** Added-field hints per hop-mapped file, for scenario descriptions. */
93
- addedFieldsByFile: Record<string, string[]>;
94
- }
95
- /**
96
- * Builds the inputs `draftResponseShapeScenarios` needs from an
97
- * {@link ImporterHopResult}, keyed off `byFile` rather than `endpoints`.
98
- *
99
- * `hopResult.endpoints` is deduped against endpoints already present in
100
- * `changedEndpoints`/`newEndpoints` (see `mapUnmatchedFilesToImporterEndpoints`),
101
- * so it can be empty — or missing entries — even when `byFile` holds valid
102
- * path mappings (e.g. a controller and the DTO it imports both changed in the
103
- * same PR: the controller's endpoint is already classified directly, so the
104
- * hop drops it from `endpoints`, but `byFile` still records the mapping).
105
- * Response-shape drafting wants every hop-mapped endpoint regardless of
106
- * whether it was newly added to the change set, so this re-derives the
107
- * endpoint list from `scannedEndpoints` filtered to the paths `byFile` maps
108
- * to, instead of relying on the deduped `endpoints` array.
109
- */
110
- export declare function buildHopResponseShapeInputs(hopResult: ImporterHopResult, scannedEndpoints: ScannedEndpoint[], diffContent: string | undefined): HopResponseShapeInputs;
111
- /**
112
- * Extracts likely property/field names from an array of raw added diff lines
113
- * (each still prefixed with the unified-diff `+` marker). Best-effort regex,
114
- * not a parser — intended to give a scenario a field name to name-drop, not
115
- * to be exhaustive.
116
- */
117
- export declare function extractFieldNamesFromAddedLines(lines: string[]): string[];
118
- export { sliceAddedLinesByFile };
119
- /**
120
- * Splits a unified diff into per-file raw sections — every line after a file's
121
- * `diff --git` header up to the next one — keyed by the `b/` (post-change) path.
122
- * Unlike `sliceAddedLinesByFile` (added lines only), this preserves the `@@`
123
- * hunk headers, so `extractChangedSymbols`' enclosing-type extraction (which
124
- * reads `@@ ... class Foo`) still works when a single file's section is passed
125
- * to `deriveResourceTokensForFile`. Use this for per-file token derivation that
126
- * must not mix symbols across files in a multi-file diff (SKYR-3924).
127
- */
128
- export declare function sliceDiffByFile(diffContent: string): Map<string, string>;
129
- /**
130
- * Computes added-field names per file for the given files, from a full
131
- * unified diff. Files with no added-line matches are omitted from the result
132
- * (an empty field list is a valid, expected outcome — the caller still drafts
133
- * a scenario, just without field hints).
134
- */
135
- export declare function computeAddedFieldsByFile(diffContent: string, files: string[]): Record<string, string[]>;