@skyramp/mcp 0.2.6 → 0.2.150-rc.mntnc

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 (233) hide show
  1. package/build/commands/commandLibrary.d.ts +12 -0
  2. package/build/commands/recommendTestsAndExecuteCommand.d.ts +2 -0
  3. package/build/commands/testThisEndpointCommand.d.ts +2 -0
  4. package/build/index.d.ts +2 -0
  5. package/build/index.js +6 -4
  6. package/build/playwright/PlaywrightTraceService.d.ts +32 -0
  7. package/build/playwright/index.d.ts +3 -0
  8. package/build/playwright/registerPlaywrightTools.d.ts +8 -0
  9. package/build/playwright/registerPlaywrightTools.js +12 -11
  10. package/build/playwright/traceRecordingPrompt.d.ts +13 -0
  11. package/build/playwright/traceRecordingPrompt.js +3 -11
  12. package/build/prompts/code-reuse.d.ts +1 -0
  13. package/build/prompts/enhance-assertions/contractProviderAssertionsPrompt.d.ts +2 -0
  14. package/build/prompts/enhance-assertions/integrationAssertionsPrompt.d.ts +2 -0
  15. package/build/prompts/enhance-assertions/sharedAssertionRules.d.ts +15 -0
  16. package/build/prompts/enhance-assertions/uiAssertionsPrompt.d.ts +2 -0
  17. package/build/prompts/enhance-assertions/uiAssertionsPrompt.js +24 -5
  18. package/build/prompts/fix-error-prompt.d.ts +1 -0
  19. package/build/prompts/initialize-workspace/initializeWorkspacePrompt.d.ts +1 -0
  20. package/build/prompts/modularization/integration-test-modularization.d.ts +1 -0
  21. package/build/prompts/modularization/ui-test-modularization.d.ts +1 -0
  22. package/build/prompts/personas.d.ts +21 -0
  23. package/build/prompts/pom-aware-code-reuse.d.ts +1 -0
  24. package/build/prompts/startTraceCollectionPrompts.d.ts +2 -0
  25. package/build/prompts/sut-setup/modes/adaptWorkflowPrompt.d.ts +2 -0
  26. package/build/prompts/sut-setup/modes/adaptWorkflowPrompt.js +114 -0
  27. package/build/prompts/sut-setup/modes/dockerComposePrompt.d.ts +2 -0
  28. package/build/prompts/sut-setup/modes/dockerComposePrompt.js +292 -0
  29. package/build/prompts/sut-setup/shared.d.ts +39 -0
  30. package/build/prompts/sut-setup/shared.js +132 -0
  31. package/build/prompts/test-maintenance/actionsInstructions.d.ts +35 -0
  32. package/build/prompts/test-maintenance/drift-analysis-prompt.d.ts +4 -0
  33. package/build/prompts/test-maintenance/driftAnalysisSections.d.ts +13 -0
  34. package/build/prompts/test-maintenance/driftAnalysisSections.js +9 -3
  35. package/build/prompts/test-recommendation/analysisOutputPrompt.d.ts +84 -0
  36. package/build/prompts/test-recommendation/analysisOutputPrompt.js +1 -1
  37. package/build/prompts/test-recommendation/analysisOutputPrompt.test.d.ts +1 -0
  38. package/build/prompts/test-recommendation/analysisOutputPrompt.test.js +1 -0
  39. package/build/prompts/test-recommendation/diffExecutionPlan.d.ts +17 -0
  40. package/build/prompts/test-recommendation/diffExecutionPlan.js +7 -16
  41. package/build/prompts/test-recommendation/fullRepoCatalog.d.ts +7 -0
  42. package/build/prompts/test-recommendation/mergeEnrichedScenarios.test.d.ts +1 -0
  43. package/build/prompts/test-recommendation/mergeEnrichedScenarios.test.js +1 -0
  44. package/build/prompts/test-recommendation/promptPlan.d.ts +203 -0
  45. package/build/prompts/test-recommendation/promptPlan.test.d.ts +1 -0
  46. package/build/prompts/test-recommendation/recommendationSections.d.ts +35 -0
  47. package/build/prompts/test-recommendation/recommendationShared.d.ts +31 -0
  48. package/build/prompts/test-recommendation/registerRecommendTestsPrompt.d.ts +7 -0
  49. package/build/prompts/test-recommendation/scopeAssessment.d.ts +55 -0
  50. package/build/prompts/test-recommendation/scopeAssessment.js +2 -2
  51. package/build/prompts/test-recommendation/scopeAssessment.test.d.ts +1 -0
  52. package/build/prompts/test-recommendation/scopeAssessment.test.js +1 -0
  53. package/build/prompts/test-recommendation/test-recommendation-prompt.d.ts +6 -0
  54. package/build/prompts/test-recommendation/test-recommendation-prompt.js +4 -4
  55. package/build/prompts/test-recommendation/test-recommendation-prompt.test.d.ts +1 -0
  56. package/build/prompts/test-recommendation/test-recommendation-prompt.test.js +6 -99
  57. package/build/prompts/testbot/testbot-prompts.d.ts +11 -0
  58. package/build/prompts/testbot/testbot-prompts.js +213 -42
  59. package/build/prompts/testbot/testbot-prompts.test.d.ts +1 -0
  60. package/build/prompts/testbot/testbot-prompts.test.js +15 -117
  61. package/build/resources/analysisResources.d.ts +17 -0
  62. package/build/resources/progressResource.d.ts +2 -0
  63. package/build/resources/sutSetupResource.d.ts +2 -0
  64. package/build/resources/sutSetupResource.js +45 -0
  65. package/build/resources/testbotResource.d.ts +2 -0
  66. package/build/resources/testbotResource.js +1 -1
  67. package/build/services/AnalyticsService.d.ts +13 -0
  68. package/build/services/AnalyticsService.test.d.ts +1 -0
  69. package/build/services/AnalyticsService.test.js +86 -0
  70. package/build/services/ModularizationService.d.ts +11 -0
  71. package/build/services/ScenarioGenerationService.d.ts +37 -0
  72. package/build/services/ScenarioGenerationService.integration.test.d.ts +1 -0
  73. package/build/services/ScenarioGenerationService.integration.test.js +4 -0
  74. package/build/services/ScenarioGenerationService.test.d.ts +1 -0
  75. package/build/services/TestDiscoveryService.d.ts +128 -0
  76. package/build/services/TestDiscoveryService.js +22 -12
  77. package/build/services/TestDiscoveryService.test.d.ts +1 -0
  78. package/build/services/TestDiscoveryService.test.js +4 -3
  79. package/build/services/TestExecutionService.d.ts +55 -0
  80. package/build/services/TestExecutionService.js +158 -26
  81. package/build/services/TestExecutionService.test.d.ts +1 -0
  82. package/build/services/TestExecutionService.test.js +339 -25
  83. package/build/services/TestGenerationService.d.ts +56 -0
  84. package/build/services/TestGenerationService.test.d.ts +1 -0
  85. package/build/services/TestGenerationService.test.js +10 -8
  86. package/build/services/containerEnv.d.ts +14 -0
  87. package/build/tool-phase-coverage.test.d.ts +1 -0
  88. package/build/tool-phase-coverage.test.js +2 -0
  89. package/build/tool-phases.d.ts +38 -0
  90. package/build/tools/auth/loginTool.d.ts +2 -0
  91. package/build/tools/auth/logoutTool.d.ts +2 -0
  92. package/build/tools/code-refactor/codeReuseTool.d.ts +2 -0
  93. package/build/tools/code-refactor/enhanceAssertionsTool.d.ts +2 -0
  94. package/build/tools/code-refactor/modularizationTool.d.ts +2 -0
  95. package/build/tools/executeSkyrampTestTool.d.ts +2 -0
  96. package/build/tools/executeSkyrampTestTool.js +17 -4
  97. package/build/tools/fixErrorTool.d.ts +2 -0
  98. package/build/tools/generate-tests/generateBatchScenarioRestTool.d.ts +45 -0
  99. package/build/tools/generate-tests/generateBatchScenarioRestTool.js +1 -1
  100. package/build/tools/generate-tests/generateBatchScenarioRestTool.test.d.ts +1 -0
  101. package/build/tools/generate-tests/generateBatchScenarioRestTool.test.js +11 -9
  102. package/build/tools/generate-tests/generateContractRestTool.d.ts +31 -0
  103. package/build/tools/generate-tests/generateE2ERestTool.d.ts +13 -0
  104. package/build/tools/generate-tests/generateFuzzRestTool.d.ts +10 -0
  105. package/build/tools/generate-tests/generateIntegrationRestTool.d.ts +16 -0
  106. package/build/tools/generate-tests/generateLoadRestTool.d.ts +18 -0
  107. package/build/tools/generate-tests/generateLoadRestTool.test.d.ts +1 -0
  108. package/build/tools/generate-tests/generateMockRestTool.d.ts +64 -0
  109. package/build/tools/generate-tests/generateSmokeRestTool.d.ts +8 -0
  110. package/build/tools/generate-tests/generateUIRestTool.d.ts +14 -0
  111. package/build/tools/generate-tests/generateUIRestTool.js +1 -1
  112. package/build/tools/generate-tests/loadTestSchema.d.ts +39 -0
  113. package/build/tools/one-click/oneClickTool.d.ts +11 -0
  114. package/build/tools/submitReportTool.d.ts +384 -0
  115. package/build/tools/submitReportTool.js +225 -37
  116. package/build/tools/submitReportTool.test.d.ts +1 -0
  117. package/build/tools/submitReportTool.test.js +479 -55
  118. package/build/tools/test-management/actionsTool.d.ts +10 -0
  119. package/build/tools/test-management/actionsTool.js +10 -0
  120. package/build/tools/test-management/analyzeChangesTool.d.ts +26 -0
  121. package/build/tools/test-management/analyzeChangesTool.js +128 -48
  122. package/build/tools/test-management/analyzeChangesTool.test.d.ts +1 -0
  123. package/build/tools/test-management/analyzeChangesTool.test.js +80 -73
  124. package/build/tools/test-management/analyzeTestHealthTool.d.ts +2 -0
  125. package/build/tools/test-management/analyzeTestHealthTool.js +6 -3
  126. package/build/tools/test-management/index.d.ts +3 -0
  127. package/build/tools/test-management/index.js +0 -1
  128. package/build/tools/trace/resolveSaveStoragePath.d.ts +7 -0
  129. package/build/tools/trace/resolveSaveStoragePath.test.d.ts +1 -0
  130. package/build/tools/trace/resolveSessionPaths.d.ts +40 -0
  131. package/build/tools/trace/resolveSessionPaths.test.d.ts +1 -0
  132. package/build/tools/trace/sessionState.d.ts +2 -0
  133. package/build/tools/trace/sessionState.test.d.ts +1 -0
  134. package/build/tools/trace/startTraceCollectionTool.d.ts +2 -0
  135. package/build/tools/trace/stopTraceCollectionTool.d.ts +2 -0
  136. package/build/tools/workspace/initScanWorkspaceTool.d.ts +4 -0
  137. package/build/tools/workspace/initializeWorkspaceTool.d.ts +2 -0
  138. package/build/tools/workspace/initializeWorkspaceTool.test.d.ts +1 -0
  139. package/build/tools/workspace/initializeWorkspaceTool.test.js +134 -0
  140. package/build/types/OneClickCommands.d.ts +68 -0
  141. package/build/types/RepositoryAnalysis.d.ts +3004 -0
  142. package/build/types/TestAnalysis.d.ts +97 -0
  143. package/build/types/TestExecution.d.ts +63 -0
  144. package/build/types/TestExecution.js +11 -1
  145. package/build/types/TestRecommendation.d.ts +12 -0
  146. package/build/types/TestTypes.d.ts +234 -0
  147. package/build/types/TestbotReport.d.ts +68 -0
  148. package/build/types/TestbotReport.js +1 -0
  149. package/build/types/index.d.ts +4 -0
  150. package/build/types/index.js +3 -0
  151. package/build/utils/AnalysisStateManager.d.ts +182 -0
  152. package/build/utils/AnalysisStateManager.js +10 -1
  153. package/build/utils/AnalysisStateManager.test.d.ts +1 -0
  154. package/build/utils/analyze-openapi.d.ts +1 -0
  155. package/build/utils/branchDiff.d.ts +18 -0
  156. package/build/utils/dartRouteExtractor.d.ts +45 -0
  157. package/build/utils/dartRouteExtractor.test.d.ts +1 -0
  158. package/build/utils/docker.d.ts +19 -0
  159. package/build/utils/docker.test.d.ts +1 -0
  160. package/build/utils/docker.test.js +10 -9
  161. package/build/utils/featureFlags.d.ts +37 -0
  162. package/build/utils/featureFlags.test.d.ts +1 -0
  163. package/build/utils/gitStaging.d.ts +24 -0
  164. package/build/utils/gitStaging.test.d.ts +1 -0
  165. package/build/utils/gitStaging.test.js +13 -7
  166. package/build/utils/httpDefaults.d.ts +10 -0
  167. package/build/utils/httpDefaults.test.d.ts +1 -0
  168. package/build/utils/initAgent.d.ts +36 -0
  169. package/build/utils/language-helper.d.ts +6 -0
  170. package/build/utils/logger.d.ts +11 -0
  171. package/build/utils/normalizeParams.d.ts +14 -0
  172. package/build/utils/normalizeSkyrampImports.d.ts +13 -0
  173. package/build/utils/pr-comment-parser.d.ts +31 -0
  174. package/build/utils/pr-comment-parser.test.d.ts +1 -0
  175. package/build/utils/pr-comment-parser.test.js +6 -5
  176. package/build/utils/projectMetadata.d.ts +15 -0
  177. package/build/utils/projectMetadata.test.d.ts +1 -0
  178. package/build/utils/proxy-terminal.d.ts +2 -0
  179. package/build/utils/repoScanner.d.ts +27 -0
  180. package/build/utils/repoScanner.test.d.ts +1 -0
  181. package/build/utils/routeParsers.d.ts +53 -0
  182. package/build/utils/routeParsers.js +31 -38
  183. package/build/utils/routeParsers.test.d.ts +1 -0
  184. package/build/utils/scenarioDrafting.d.ts +71 -0
  185. package/build/utils/scenarioDrafting.js +23 -1
  186. package/build/utils/scenarioDrafting.test.d.ts +1 -0
  187. package/build/utils/skyrampMdContent.d.ts +5 -0
  188. package/build/utils/sourceRouteExtractor.d.ts +48 -0
  189. package/build/utils/sourceRouteExtractor.test.d.ts +1 -0
  190. package/build/utils/telemetry.d.ts +32 -0
  191. package/build/utils/telemetry.test.d.ts +1 -0
  192. package/build/utils/trace-parser.d.ts +32 -0
  193. package/build/utils/trace-parser.test.d.ts +6 -0
  194. package/build/utils/uiPageEnumerator.d.ts +146 -0
  195. package/build/utils/uiPageEnumerator.js +58 -14
  196. package/build/utils/uiPageEnumerator.test.d.ts +1 -0
  197. package/build/utils/uiPageEnumerator.test.js +223 -43
  198. package/build/utils/utils.d.ts +45 -0
  199. package/build/utils/utils.js +10 -0
  200. package/build/utils/utils.test.d.ts +1 -0
  201. package/build/utils/versions.d.ts +3 -0
  202. package/build/utils/versions.js +1 -1
  203. package/build/utils/workspaceAuth.d.ts +127 -0
  204. package/build/utils/workspaceAuth.test.d.ts +1 -0
  205. package/build/utils/workspaceAuth.test.js +25 -25
  206. package/build/workspace/index.d.ts +1 -0
  207. package/build/workspace/workspace.d.ts +390 -0
  208. package/build/workspace/workspace.test.d.ts +1 -0
  209. package/node_modules/playwright/LICENSE +202 -0
  210. package/node_modules/playwright/NOTICE +5 -0
  211. package/node_modules/playwright/README.md +168 -0
  212. package/node_modules/playwright/lib/mcp/skyramp/traceRecordingBackend.js +13 -9
  213. package/node_modules/playwright/lib/mcp/test/skyRampExport.js +4 -3
  214. package/node_modules/playwright/node_modules/playwright-core/LICENSE +202 -0
  215. package/node_modules/playwright/node_modules/playwright-core/NOTICE +5 -0
  216. package/node_modules/playwright/node_modules/playwright-core/ThirdPartyNotices.txt +23 -126
  217. package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/assets/{codeMirrorModule-B0JOjboO.js → codeMirrorModule-B5kqh2EV.js} +1 -1
  218. package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/assets/{defaultSettingsView-1anWeyDf.js → defaultSettingsView-CZ9npQ3N.js} +79 -79
  219. package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/{index.Cc5029a3.js → index.BCnxj-_b.js} +1 -1
  220. package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/index.html +2 -2
  221. package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/{uiMode.Wo5yvvVh.js → uiMode.1Ym0Ivn8.js} +1 -1
  222. package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/uiMode.html +2 -2
  223. package/node_modules/playwright/node_modules/playwright-core/package.json +1 -1
  224. package/node_modules/playwright/package.json +1 -1
  225. package/package.json +16 -4
  226. package/build/tools/test-management/uiAnalyzeChangesTool.js +0 -155
  227. package/build/tools/test-management/uiAnalyzeChangesTool.test.js +0 -147
  228. package/node_modules/playwright/node_modules/playwright-core/.DS_Store +0 -0
  229. package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/.bin/node-which +0 -52
  230. package/node_modules/playwright/node_modules/playwright-core/bundles/utils/node_modules/.bin/is-docker +0 -5
  231. package/node_modules/playwright/node_modules/playwright-core/bundles/utils/node_modules/.bin/mime +0 -46
  232. package/node_modules/playwright/node_modules/playwright-core/bundles/utils/node_modules/.bin/yaml +0 -11
  233. package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/index.CP3Y8Zsb.js +0 -2
@@ -406,21 +406,36 @@ const GENERIC_ROUTE_BASENAMES = new Set(["api", "app", "handler", "handlers", "i
406
406
  function normalizeSourcePath(filePath) {
407
407
  return filePath.replace(/\\/g, "/");
408
408
  }
409
- /**
410
- * Classify endpoints by cross-referencing `changedFiles` against `scannedEndpoints[].sourceFile`.
411
- *
412
- * This replaces `parseEndpointsFromDiff` — instead of regex-parsing diff hunks for route
413
- * annotations (fragile for 15+ frameworks), it uses the already-scanned endpoint catalog
414
- * which has full, resolved paths and concrete HTTP methods.
415
- *
416
- * For deleted files, the caller must supply endpoints recovered from the base branch
417
- * (via `parseFileEndpoints` on `git show base:<file>`) in `deletedFileEndpoints`.
418
- */
409
+ function staticPathSegments(urlPath) {
410
+ return urlPath.split("/").filter(s => s && !/^[:{[]/.test(s)).map(s => s.toLowerCase());
411
+ }
412
+ function endpointSourceResource(ep) {
413
+ const sourceSegments = ep.sourceFile.split(/[\\/]/).filter(Boolean);
414
+ const filename = sourceSegments.at(-1)?.replace(/\.[^.]+$/, "") ?? "";
415
+ const normalizedFilename = filename.replace(/^(?:old|new)[-_]/i, "").toLowerCase();
416
+ const isDynamic = /^\[.*\]$/.test(normalizedFilename) || /^\{.*\}$/.test(normalizedFilename);
417
+ if (!isDynamic && !GENERIC_ROUTE_BASENAMES.has(normalizedFilename) && normalizedFilename) {
418
+ return normalizedFilename;
419
+ }
420
+ else if (GENERIC_ROUTE_BASENAMES.has(normalizedFilename)) {
421
+ // Generic filename: walk up the directory tree for a more specific ancestor name.
422
+ const parentSegments = sourceSegments.slice(0, -1).reverse();
423
+ const contextSegment = parentSegments
424
+ .map(s => s.replace(/\.[^.]+$/, "").replace(/^(?:old|new)[-_]/i, "").toLowerCase())
425
+ .find(s => s && !GENERIC_ROUTE_BASENAMES.has(s) && !/^\[.*\]$/.test(s) && !/^\{.*\}$/.test(s));
426
+ if (contextSegment)
427
+ return `${contextSegment}/${normalizedFilename}`;
428
+ }
429
+ // Fall back to the last static segment of the endpoint's URL path.
430
+ const segments = staticPathSegments(ep.path);
431
+ return segments.at(-1) ?? normalizedFilename;
432
+ }
433
+ function endpointMovedKey(path, resource, method) {
434
+ return method ? `${path}::${resource}::${method}` : `${path}::${resource}`;
435
+ }
419
436
  export function classifyEndpointsByChangedFiles(diffData, scannedEndpoints, deletedFileEndpoints) {
420
437
  const newFileSet = new Set(diffData.newFiles.map(normalizeSourcePath));
421
438
  const deletedFileSet = new Set(diffData.deletedFiles.map(normalizeSourcePath));
422
- // Build sourceFile -> ScannedEndpoint[] map from the post-change catalog.
423
- // Git diffs use POSIX separators; scanner output can reflect OS-native paths.
424
439
  const bySourceFile = new Map();
425
440
  for (const ep of scannedEndpoints) {
426
441
  const sourceKey = normalizeSourcePath(ep.sourceFile);
@@ -435,7 +450,6 @@ export function classifyEndpointsByChangedFiles(diffData, scannedEndpoints, dele
435
450
  const unmatchedFiles = [];
436
451
  for (const file of diffData.changedFiles) {
437
452
  const fileKey = normalizeSourcePath(file);
438
- // Deleted files won't appear in scanned catalog — handled separately below
439
453
  if (deletedFileSet.has(fileKey))
440
454
  continue;
441
455
  const eps = bySourceFile.get(fileKey);
@@ -454,47 +468,26 @@ export function classifyEndpointsByChangedFiles(diffData, scannedEndpoints, dele
454
468
  // Filter out endpoints that still exist in the current catalog as moved/refactored.
455
469
  // Same router-relative paths can exist in sibling files, so only treat them as moved
456
470
  // when the source resource also matches.
457
- function endpointSourceResource(ep) {
458
- const sourceSegments = ep.sourceFile.split(/[\\/]/).filter(Boolean);
459
- const base = sourceSegments.at(-1)?.replace(/\.[^.]+$/, "") ?? "";
460
- const normalizedBase = base.replace(/^(?:old|new)[-_]/i, "").toLowerCase();
461
- const isDynamicBase = /^\[.*\]$/.test(normalizedBase) || /^\{.*\}$/.test(normalizedBase);
462
- if (normalizedBase && !isDynamicBase && !GENERIC_ROUTE_BASENAMES.has(normalizedBase)) {
463
- return normalizedBase;
464
- }
465
- if (normalizedBase && GENERIC_ROUTE_BASENAMES.has(normalizedBase)) {
466
- const contextSegment = [...sourceSegments]
467
- .slice(0, -1)
468
- .reverse()
469
- .map((segment) => segment.replace(/\.[^.]+$/, "").replace(/^(?:old|new)[-_]/i, "").toLowerCase())
470
- .find((segment) => segment && !GENERIC_ROUTE_BASENAMES.has(segment) && !/^\[.*\]$/.test(segment) && !/^\{.*\}$/.test(segment));
471
- if (contextSegment)
472
- return `${contextSegment}/${normalizedBase}`;
473
- }
474
- const staticSegments = ep.path.split("/").filter(Boolean).filter((segment) => !/^[:{[]/.test(segment));
475
- return staticSegments[staticSegments.length - 1]?.toLowerCase() ?? normalizedBase;
476
- }
477
471
  const currentMethodKeys = new Set();
478
472
  const currentPathResourceKeys = new Set();
479
473
  const currentMultiKeys = new Set();
480
- const movedKey = (path, resource, method) => method ? `${path}::${resource}::${method}` : `${path}::${resource}`;
481
474
  for (const current of scannedEndpoints) {
482
475
  const currentResource = endpointSourceResource(current);
483
- const pathResourceKey = movedKey(current.path, currentResource);
476
+ const pathResourceKey = endpointMovedKey(current.path, currentResource);
484
477
  currentPathResourceKeys.add(pathResourceKey);
485
478
  for (const method of current.methods) {
486
479
  if (method === "MULTI")
487
480
  currentMultiKeys.add(pathResourceKey);
488
- currentMethodKeys.add(movedKey(current.path, currentResource, method));
481
+ currentMethodKeys.add(endpointMovedKey(current.path, currentResource, method));
489
482
  }
490
483
  }
491
484
  function isMovedEndpoint(deleted, method) {
492
485
  const deletedResource = endpointSourceResource(deleted);
493
- const pathResourceKey = movedKey(deleted.path, deletedResource);
486
+ const pathResourceKey = endpointMovedKey(deleted.path, deletedResource);
494
487
  if (method === "MULTI")
495
488
  return currentPathResourceKeys.has(pathResourceKey);
496
489
  return currentMultiKeys.has(pathResourceKey) ||
497
- currentMethodKeys.has(movedKey(deleted.path, deletedResource, method));
490
+ currentMethodKeys.has(endpointMovedKey(deleted.path, deletedResource, method));
498
491
  }
499
492
  const removedEndpoints = (deletedFileEndpoints ?? [])
500
493
  .map((ep) => ({
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,71 @@
1
+ import { DraftedScenario } from "../types/RepositoryAnalysis.js";
2
+ import { WorkspaceAuthType } from "./workspaceAuth.js";
3
+ export declare function isRealResource(r: string): boolean;
4
+ type MethodInput = string | {
5
+ method: string;
6
+ sourceFile?: string;
7
+ interactions?: Array<{
8
+ request?: {
9
+ body?: Record<string, unknown>;
10
+ };
11
+ }>;
12
+ };
13
+ type EndpointInput = {
14
+ path: string;
15
+ methods: Array<MethodInput>;
16
+ };
17
+ type EndpointRef = {
18
+ method: string;
19
+ path: string;
20
+ sourceFile?: string;
21
+ };
22
+ type ScenarioDraftOptions = {
23
+ /** Existing endpoints touched by a security/destructive diff; used only for attack-surface expansion. */
24
+ changedEndpoints?: Array<EndpointRef>;
25
+ /** Gate sibling destructive expansion to diffs with auth/permission/admin-key signals. */
26
+ securityRelevantDiff?: boolean;
27
+ };
28
+ export type ResourceRelationships = Map<string, Set<string>>;
29
+ /**
30
+ * Infer parent→child resource relationships from two signals already present
31
+ * in the endpoint data — no source code scanning required.
32
+ *
33
+ * Signal 1 — path nesting (always available):
34
+ * /collections/{id}/links → links depends on collections
35
+ *
36
+ * Signal 2 — request body FK fields (available when trace data is merged):
37
+ * POST /orders body: { product_id: "..." } → orders depends on products
38
+ *
39
+ * Returns a map of `dependent → Set<parent>`. Only confirmed, real-resource
40
+ * pairs are included. When no signal is found the map is empty, which tells
41
+ * the caller to fall back to heuristic pairing.
42
+ */
43
+ export declare function inferResourceRelationships(endpoints: Array<EndpointInput>): ResourceRelationships;
44
+ export declare function draftScenariosFromEndpoints(endpoints: Array<EndpointInput>, newEndpoints?: Array<EndpointRef>, wsAuthType?: WorkspaceAuthType, options?: ScenarioDraftOptions, removedEndpoints?: Array<EndpointRef>): DraftedScenario[];
45
+ /**
46
+ * Draft scenarios that directly test each new endpoint in the branch diff.
47
+ *
48
+ * For each new endpoint, method-specific scenarios are produced:
49
+ * PUT/PATCH → mutation-recalc integration, boundary-values integration, contract, not-found
50
+ * POST → lifecycle integration (create + optional GET), contract, validation error
51
+ * DELETE → lifecycle integration (create + delete + 404), contract
52
+ * GET /{id} → contract, not-found
53
+ * GET /coll → contract + collection-list integration (create-then-GET) or contract-only
54
+ *
55
+ * Additionally, PUT/PATCH/DELETE methods get an auth-boundary scenario
56
+ * (category "security_boundary" → HIGH priority, not CRITICAL) so it
57
+ * lands in ADDITIONAL deterministically instead of depending on LLM
58
+ * supplement. GET is excluded (often public); POST is excluded because
59
+ * the security-boundary supplement tier covers it. Exception: execution
60
+ * endpoints (/execute, /run, /trigger, etc.) include auth-boundary for
61
+ * all non-GET methods (including POST) since they are high-value targets.
62
+ */
63
+ export declare function draftDiffDirectScenarios(newEndpoints: Array<{
64
+ method: string;
65
+ path: string;
66
+ }>, resourceGroups: Map<string, {
67
+ basePath: string;
68
+ methods: Set<string>;
69
+ paramPath?: string;
70
+ }>, wsAuthType?: WorkspaceAuthType): DraftedScenario[];
71
+ export {};
@@ -142,7 +142,7 @@ export function inferResourceRelationships(endpoints) {
142
142
  }
143
143
  return relationships;
144
144
  }
145
- export function draftScenariosFromEndpoints(endpoints, newEndpoints = [], wsAuthType, options = {}) {
145
+ export function draftScenariosFromEndpoints(endpoints, newEndpoints = [], wsAuthType, options = {}, removedEndpoints = []) {
146
146
  const scenarios = [];
147
147
  const resourceGroups = new Map();
148
148
  for (const ep of endpoints) {
@@ -191,6 +191,28 @@ export function draftScenariosFromEndpoints(endpoints, newEndpoints = [], wsAuth
191
191
  }
192
192
  scenarios.push(...draftDiffDirectScenarios(newEndpoints, resourceGroups, wsAuthType));
193
193
  scenarios.push(...draftAttackSurfaceExpansionScenarios(endpoints, options.changedEndpoints ?? [], wsAuthType, options.securityRelevantDiff ?? false));
194
+ // 404-guard scenarios for removed endpoints — absence assertions confirming
195
+ // the endpoint is gone. Single-step contract tests, no chaining needed.
196
+ for (const ep of removedEndpoints) {
197
+ scenarios.push({
198
+ scenarioName: `verify-removed-${ep.method.toLowerCase()}-${ep.path.replace(/[^a-z0-9]+/gi, "-").replace(/^-|-$/g, "")}`,
199
+ description: `Verify ${ep.method} ${ep.path} returns 404 — endpoint was removed in this PR`,
200
+ category: "breaking_change",
201
+ priority: "high",
202
+ steps: [{
203
+ order: 1,
204
+ method: ep.method,
205
+ path: ep.path,
206
+ description: `${ep.method} ${ep.path} → 404 (endpoint removed)`,
207
+ interactionType: "error",
208
+ expectedStatusCode: 404,
209
+ }],
210
+ chainingKeys: [],
211
+ requiresAuth: false,
212
+ estimatedComplexity: "simple",
213
+ source: ScenarioSource.CodeInferred,
214
+ });
215
+ }
194
216
  return capScenarios(scenarios);
195
217
  }
196
218
  const MAX_TOTAL_SCENARIOS = 30;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Skill content for skyramp.md — installed at auto-discovery paths: ~/.claude/skills/skyramp/SKILLS.md, ~/.cursor/skills/skyramp/SKILLS.md, ~/.github/skills/skyramp.md
3
+ * Follows the SKILL.md specification: https://agentskills.io/what-are-skills#the-skill-md-file
4
+ */
5
+ export declare const SKYRAMP_MD_CONTENT = "---\nname: skyramp\ndescription: Generate, execute, and maintain API tests (smoke, contract, fuzz, load, integration, E2E, UI) using Skyramp MCP tools.\n---\n\n# Skyramp\n\n## When to use this skill\nUse this skill whenever the user asks to generate, run, or maintain API tests. Always read `<workspace-root>/.skyramp/workspace.yml` first to get `language`, `framework`, `outputDir`, and `api.baseUrl` \u2014 do not ask the user for values already present.\n\n---\n\n## Tools\n\n### Workspace Setup\n1. **`skyramp_initialize_workspace`** \u2014 Create or update `.skyramp/workspace.yml` in a git repository workspace. Scan the repo for all services before calling. Required before any other Skyramp tool.\n\n### Test Generation\n2. **`skyramp_smoke_test_generation`** \u2014 Verify an endpoint is reachable and returns a valid response.\n3. **`skyramp_contract_test_generation`** \u2014 Validate implementation matches OpenAPI/Swagger schema.\n4. **`skyramp_fuzz_test_generation`** \u2014 Send malformed or boundary inputs to find edge cases and security issues.\n5. **`skyramp_load_test_generation`** \u2014 Test performance under concurrent load. Optional: `loadDuration`, `loadNumThreads`. Accepts `trace` instead of `apiSchema`/`endpointURL`.\n6. **`skyramp_integration_test_generation`** \u2014 Multi-step workflows across one or more services. Supply one of: `apiSchema`+`endpointURL`, `trace`, or `scenarioFile`. Do not combine them.\n7. **`skyramp_e2e_test_generation`** \u2014 Full user journey covering UI and backend. Requires `trace` and `playwrightInput` zip. Do not pass `apiSchema` or `endpointURL`.\n8. **`skyramp_ui_test_generation`** \u2014 UI-only tests from Playwright recordings. Requires `playwrightInput` zip.\n\n### Trace Generation\n9. **`skyramp_start_trace_collection`** \u2014 Start capturing backend traffic. Set `playwright: true` for UI or E2E tests. Use an absolute path for `outputDir`.\n10. **`skyramp_stop_trace_collection`** \u2014 Stop capture and save the trace. Use the same `outputDir` and `playwrightEnabled` values as start.\n11. **`skyramp_scenario_test_generation`** \u2014 Scenario trace generation. Describe a multi-step flow in natural language to produce a scenario file. Pass output to `skyramp_integration_test_generation` via `scenarioFile`.\n\n### Test Execution\n12. **`skyramp_execute_test`** \u2014 Run a single Skyramp-generated test file. Required: `workspacePath`, `language`, `testType`, `testFile`. Optional: `stateFile` (writes execution results back for health analysis). For multiple tests, call sequentially to avoid env var conflicts.\n\n### Test Analysis & Maintenance\n13. **`skyramp_analyze_changes`** \u2014 Unified entry point: scans endpoints, discovers tests, computes diff. Takes `repositoryPath` and `scope`. Returns `stateFile` + recommendations.\n14. **`skyramp_analyze_test_health`** \u2014 Drift and health assessment for existing tests. Takes `stateFile`. Returns LLM prompt for scoring.\n15. **`skyramp_actions`** \u2014 Execute UPDATE / REGENERATE / VERIFY / DELETE actions. Takes `stateFile`. Call after analyze_test_health.\n\n### Code Quality\n16. **`skyramp_fix_errors`** \u2014 Fix compilation or runtime errors in a generated test file.\n17. **`skyramp_modularization`** \u2014 Refactor a test file into reusable modules. Set `isTraceBased: true` for trace-based tests.\n18. **`skyramp_reuse_code`** \u2014 Pull shared helpers from other Skyramp tests. Only when `code_reuse` was `true` at generation time.\n\n### Authentication\n19. **`skyramp_login`** \u2014 Log in to the Skyramp platform.\n20. **`skyramp_logout`** \u2014 Log out from the Skyramp platform.\n\n---\n\n## Prompts\n\nPrefer invoking a prompt over manually chaining tools \u2014 prompts run the full workflow automatically.\n\n- **`skyramp_trace_prompt`** \u2014 Trace collection setup and execution.\n- **`skyramp_test_health_analysis`** \u2014 Full maintenance flow (discover \u2192 drift \u2192 health \u2192 actions).\n- **`skyramp_testbot`** \u2014 PR-scoped recommendations + maintenance + report. Required: `prTitle`, `prDescription`, `summaryOutputFile`, `repositoryPath`.\n\n---\n\n## Workflows\n\nUse these when a prompt is not available.\n\n**Generate and run a test**\nRead `.skyramp/workspace.yml` \u2192 Call appropriate generation tool \u2192 `skyramp_execute_test`\n\n**Trace-based test (integration / load / E2E / UI)**\n`skyramp_start_trace_collection` (set `playwright: true` for UI/E2E) \u2192 User exercises the app \u2192 `skyramp_stop_trace_collection` \u2192 Call target generation tool with `trace` (and `playwrightInput` for E2E/UI)\n\n**Scenario \u2192 integration test**\n`skyramp_scenario_test_generation` \u2192 `skyramp_integration_test_generation` with `scenarioFile`\n\n**Recommend tests for a PR**\n`skyramp_analyze_changes` (with `scope: \"branch_diff\"`) \u2192 follow enrichment steps \u2192 `skyramp_recommend_tests` \u2192 Generate recommended tests\n\n**Maintain existing tests**\n`skyramp_analyze_changes` \u2192 `skyramp_analyze_test_health` \u2192 *(optional)* execute tests via `skyramp_execute_test` with `stateFile` param (writes results back) \u2192 `skyramp_actions` (do not skip)\n\n---\n\n## Conventions\n\n- **`endpointURL`** \u2014 Full URL to a specific endpoint, not just the base URL. Build from `api.baseUrl` + path (e.g. `http://localhost:8000/api/v1/users`).\n- **`outputDir`** \u2014 Use absolute paths for both test output and trace collection.\n- **Mutually exclusive inputs** \u2014 `apiSchema`/`endpointURL`, `trace`, and `scenarioFile` are mutually exclusive for integration and load tests. Use exactly one.\n- **Sequential execution** \u2014 Execute tests sequentially (not in parallel) to avoid environment variable conflicts with `SKYRAMP_TEST_BASE_URL`.\n";
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Source-grounded route extraction — strategy 2 of the candidate UI page ladder.
3
+ *
4
+ * Given a repo path, walks router/App files with the TypeScript Compiler API
5
+ * to find route declarations in three forms:
6
+ *
7
+ * 1. React-style JSX route elements:
8
+ * <Route path="/cart" element={<Cart/>} />
9
+ * <SentryRoute path={WORKSPACE_URL} component={Workspaces} />
10
+ *
11
+ * 2. Vue Router object literals:
12
+ * const routes = [{ path: '/login', component: Login }, …]
13
+ *
14
+ * 3. Directus `defineModule({ id, routes: [...] })`:
15
+ * defineModule({ id: 'notifications', routes: [{ path: '', component: X }] })
16
+ * → URL = "/notifications" (module id prefix + nested path)
17
+ *
18
+ * Path attribute values are resolved through:
19
+ * - String literals (`path="/cart"`)
20
+ * - Local const declarations (`const X = "/cart"; path={X}`)
21
+ * - Cross-file imports + named exports (`import { X } from './constants'`)
22
+ * - Template literals composed of resolvable parts (`` `${BASE}/foo` ``)
23
+ *
24
+ * Why TS Compiler API rather than regex: route declarations span multiple
25
+ * lines with attributes interleaved, paths reference constants that must be
26
+ * cross-file resolved, and tag names vary (Route, SentryRoute, PrivateRoute,
27
+ * …). The AST gives us the structure for free; regex would have to be
28
+ * deliberately permissive at the cost of false matches.
29
+ */
30
+ /** A route declaration discovered in source. */
31
+ export interface SourceRoute {
32
+ /** URL path string from the route declaration. */
33
+ path: string;
34
+ /** Component identifier referenced by `element={<X/>}` or `component: X`. */
35
+ componentName: string;
36
+ /** Resolved component source file (absolute path), if the import was found. */
37
+ componentFile?: string;
38
+ /** Router file the declaration was found in (absolute path). */
39
+ declaredIn: string;
40
+ }
41
+ /**
42
+ * Walk the repo, parse candidate router files, return all route declarations.
43
+ *
44
+ * Best-effort by design: a parse error on one file shouldn't block discovery
45
+ * across the rest. Returns empty when no router files match — callers should
46
+ * treat that as "strategy 2 inapplicable" and fall through to the next one.
47
+ */
48
+ export declare function extractSourceRoutes(repositoryPath: string): SourceRoute[];
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,32 @@
1
+ export declare function getEntryPoint(): string;
2
+ /**
3
+ * Detects the CI/CD platform from environment variables.
4
+ * Returns undefined when not running in CI.
5
+ */
6
+ export declare function getCIPlatform(): string | undefined;
7
+ export interface RepositoryInfo {
8
+ repository?: string;
9
+ repositoryOwner?: string;
10
+ }
11
+ /**
12
+ * Parses "owner/repo" out of a git remote URL. Handles both HTTPS
13
+ * (https://github.com/owner/repo.git) and SSH (git@github.com:owner/repo.git)
14
+ * forms, with or without the trailing ".git". Returns undefined if no match.
15
+ */
16
+ export declare function parseRepositoryFromRemoteUrl(remoteUrl: string): RepositoryInfo | undefined;
17
+ /**
18
+ * Resolves source-repository attribution for telemetry, segmenting usage per
19
+ * customer/repo.
20
+ *
21
+ * - In GitHub Actions (testbot), reads GITHUB_REPOSITORY / GITHUB_REPOSITORY_OWNER
22
+ * which the MCP subprocess inherits — fast, no shelling out.
23
+ * - In local/IDE usage, derives "owner/repo" from `git remote get-url origin`.
24
+ * The lookup directory is the caller-supplied `cwd` (a tool's repositoryPath
25
+ * or workspacePath) when available, since the MCP server's own process.cwd()
26
+ * is set by the IDE and is not necessarily the user's repo. Falls back to
27
+ * process.cwd(). Cached per directory so git is invoked at most once each.
28
+ *
29
+ * Returns an empty object when attribution can't be determined (e.g. not a git
30
+ * repo, no origin remote).
31
+ */
32
+ export declare function getRepositoryInfo(cwd?: string): Promise<RepositoryInfo>;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,32 @@
1
+ export type TraceFormat = "skyramp" | "har" | "unknown";
2
+ interface ParsedTraceEntry {
3
+ method: string;
4
+ path: string;
5
+ statusCode: number;
6
+ requestHeaders?: Record<string, string>;
7
+ requestBody?: Record<string, any>;
8
+ queryParams?: Record<string, string[]>;
9
+ responseHeaders?: Record<string, string>;
10
+ responseBody?: Record<string, any>;
11
+ timestamp: string;
12
+ }
13
+ interface ParsedUserFlow {
14
+ flowId: string;
15
+ entries: ParsedTraceEntry[];
16
+ durationMs: number;
17
+ }
18
+ export interface TraceParseResult {
19
+ entries: ParsedTraceEntry[];
20
+ userFlows: ParsedUserFlow[];
21
+ format: TraceFormat;
22
+ }
23
+ export declare function parseTraceFile(filePath: string): Promise<TraceParseResult>;
24
+ /**
25
+ * Discover trace JSON files in a repository path.
26
+ */
27
+ export declare function discoverTraceFiles(repositoryPath: string): string[];
28
+ /**
29
+ * Discover Playwright recording zip files in a repository path.
30
+ */
31
+ export declare function discoverPlaywrightZips(repositoryPath: string): string[];
32
+ export {};
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Unit tests for trace-parser.ts — specifically the scanTraceArtifactDirs scoping
3
+ * introduced to prevent demo/fixture files (e.g. frontend/public/traces/) from being
4
+ * misidentified as testbot-generated traces.
5
+ */
6
+ export {};
@@ -0,0 +1,146 @@
1
+ /**
2
+ * Programmatic UI page enumeration — translates "which frontend files changed"
3
+ * into "which URLs should the agent capture blueprints for."
4
+ *
5
+ * Replaces the prose-driven Task 0 strategy ladder in the testbot prompt with
6
+ * deterministic code so the candidate page list lives in the stateFile and is
7
+ * reusable across analyze_changes consumers (testbot, analyze_test_health,
8
+ * future blueprint-grounded recommenders).
9
+ *
10
+ * Strategies, tried in order:
11
+ *
12
+ * Strategy 1 — Framework route grep
13
+ * Translate file-system-routed component paths directly to URLs. Covers
14
+ * Next.js (app/ and pages/), Nuxt / Vue Router file-based, and SvelteKit.
15
+ * Gated on detection of a framework config file in the repo.
16
+ *
17
+ * Strategy 2 — Source-grounded routes
18
+ * Walks router/App files with the TS Compiler API for code-defined
19
+ * route declarations like `<Route path="/cart" element={<Cart/>}>`.
20
+ * Matches each route's component file OR declaration file against the
21
+ * changed-file list (so router rewrites surface their routes even when
22
+ * the component itself is unchanged). Handles React Router string-literal
23
+ * paths, cross-file constant resolution, Vue Router object literals,
24
+ * and Directus `defineModule` aggregation. See `sourceRouteExtractor.ts`.
25
+ *
26
+ * Strategy 3 — Root fallback
27
+ * Read the workspace's frontend baseUrl and treat it as the single
28
+ * candidate page. Always available when a frontend service is configured.
29
+ */
30
+ export type CandidateUiPageStrategy = "framework-route-grep" | "source-grounded-routes" | "dart-go-router" | "root-fallback";
31
+ export interface CandidateUiPage {
32
+ /** Fully qualified URL (workspace baseUrl + framework-derived path). */
33
+ url: string;
34
+ /** Source files that suggested this page (for traceability). */
35
+ sourcedFrom: string[];
36
+ /** Which strategy enumerated it. */
37
+ strategy: CandidateUiPageStrategy;
38
+ }
39
+ /**
40
+ * File-system → URL mapping for filesystem-routed frameworks.
41
+ *
42
+ * Returns the URL path (no host) when the file is a route entrypoint, or
43
+ * null when the file isn't a route in any recognized convention.
44
+ *
45
+ * The `null` cases include component files used by routes (e.g. shared
46
+ * widgets in `components/`), API handlers (e.g. Next.js `pages/api/*`),
47
+ * and arbitrary `.ts`/`.js` modules under frontend directories. These
48
+ * are picked up by strategy 2 (import-graph walk, future) or by strategy
49
+ * 3 (root fallback).
50
+ */
51
+ export declare function frameworkFileToUrlPath(filePath: string): string | null;
52
+ /**
53
+ * Detect whether the repository is configured for filesystem-based routing.
54
+ *
55
+ * Strategy 1 maps file paths to URLs based on conventions (Next.js Pages
56
+ * Router, Nuxt, SvelteKit). Without a positive signal that the framework
57
+ * is actually configured, a repo that just *happens* to have a `pages/`
58
+ * directory (e.g. React with conventional layout, Vue with route-config
59
+ * code) produces false-positive URLs. This guard suppresses strategy 1
60
+ * for those repos so they fall through to root-fallback.
61
+ *
62
+ * Looks for framework config files in the repo root, common subdirectories,
63
+ * and the `frontendFiles[]`-derived candidate frontend roots.
64
+ */
65
+ export declare function detectsFilesystemRouting(repositoryPath: string): boolean;
66
+ /**
67
+ * Strategy 1 implementation. Filters frontend files through the framework
68
+ * route mapper; collects the URL paths that mapped successfully.
69
+ *
70
+ * Suppressed when the repo doesn't have a recognized filesystem-routing
71
+ * framework config — see `detectsFilesystemRouting`. Without that guard,
72
+ * repos with conventional `pages/` directories but code-defined routes
73
+ * (e.g. React with React Router) produce false-positive URLs.
74
+ */
75
+ export declare function findCandidatePagesByFrameworkRoute(frontendFiles: string[], baseUrl: string, repositoryPath?: string): CandidateUiPage[];
76
+ /**
77
+ * Strategy 2 implementation. Walks router/App files with the TS Compiler API
78
+ * to find route declarations (`<Route path="/cart" element={<Cart/>}>`),
79
+ * matches each declaration's component file against the changed-file list,
80
+ * and emits one CandidateUiPage per matching route.
81
+ *
82
+ * Slice 3a covers string-literal `path` attributes only — `path={CONST}` and
83
+ * Vue Router object literals are deferred to slice 3b.
84
+ *
85
+ * Returns empty when no route declarations match the changed files (which is
86
+ * also the right answer for SPAs without client-side routing — they fall
87
+ * through to root-fallback).
88
+ */
89
+ export declare function findCandidatePagesBySourceRoute(frontendFiles: string[], baseUrl: string, repositoryPath: string): CandidateUiPage[];
90
+ /**
91
+ * Strategy 2.5: walk the repo's Dart files for GoRoute(path: '...') calls.
92
+ * Gated on `hasFlutterSdkDep(repoPath)` so non-Flutter Dart projects (e.g.
93
+ * shelf_router servers) don't accidentally emit URLs.
94
+ *
95
+ * Diff-matching: each GoRoute references its screen widget via
96
+ * `builder:`/`pageBuilder:` (e.g. `AuthorsScreen`). The extractor resolves
97
+ * those identifiers through the route file's imports to absolute screen
98
+ * file paths. We surface only routes whose screen file appears in the
99
+ * changed-file list. Falls back to all-routes when no route's screen
100
+ * file matches — preserves coverage for "router untouched, screen edited"
101
+ * cases when the import-resolution heuristic doesn't find a match.
102
+ *
103
+ * Filters applied:
104
+ * 1. Drop redirect-only routes (`redirect:` with no `builder:`/`pageBuilder:`).
105
+ * These have no UI to render — navigating there is wasted work.
106
+ * 2. Drop nested relative children (paths that don't start with `/`).
107
+ * Without parent-path composition the URL is meaningless.
108
+ *
109
+ * Returns empty when the repo isn't Flutter or no GoRoute calls are
110
+ * found — caller falls through to root-fallback.
111
+ */
112
+ export declare function findCandidatePagesByDartRoute(repositoryPath: string, baseUrl: string, frontendFiles: string[]): CandidateUiPage[];
113
+ /**
114
+ * Strategy 3: when strategies 1+2 yield nothing, fall back to the workspace's
115
+ * frontend baseUrl as the single candidate page. The agent's downstream
116
+ * explore-and-discover step can then surface gated UI from the root.
117
+ *
118
+ * Returns null when the workspace has no recognizable frontend service.
119
+ */
120
+ export declare function findRootFallbackPage(repositoryPath: string): Promise<CandidateUiPage | null>;
121
+ /**
122
+ * Resolves the frontend service's baseUrl from the workspace config.
123
+ *
124
+ * Returns the baseUrl string with environment variable override precedence:
125
+ * 1. Per-service override: `SKYRAMP_TEST_SERVICE_URL_<NAME>` (where <NAME>
126
+ * is the picked frontend service's serviceName, sanitized via
127
+ * `sanitizeServiceName`)
128
+ * 2. Global override: `SKYRAMP_TEST_BASE_URL` (single service or all
129
+ * services share one URL)
130
+ * 3. Static workspace.yml value: `api.baseUrl` from the picked frontend
131
+ * service
132
+ *
133
+ * Returns null when no service has a usable URL.
134
+ *
135
+ * Exported (vs private) so analyze_changes and future tools can reuse the
136
+ * same selection without re-implementing it.
137
+ */
138
+ export declare function pickFrontendBaseUrl(repositoryPath: string): Promise<string | null>;
139
+ /**
140
+ * Run the strategy ladder in order: framework route grep (1) → source-grounded
141
+ * routes (2) → root fallback (3). Each strategy is consulted only if the
142
+ * previous one produced no candidates. Returns whatever the ladder produces
143
+ * (possibly empty if the workspace has no frontend baseUrl AND none of the
144
+ * earlier strategies matched).
145
+ */
146
+ export declare function enumerateCandidateUiPages(repositoryPath: string, frontendFiles: string[]): Promise<CandidateUiPage[]>;
@@ -311,31 +311,38 @@ export async function findRootFallbackPage(repositoryPath) {
311
311
  };
312
312
  }
313
313
  /**
314
- * Resolves the frontend service's baseUrl from the workspace config.
314
+ * Mirror of testbot.git src/services.ts exportServiceBaseUrlEnvVars sanitize().
315
+ * Keep in sync with that function (separate repo, cannot share code).
316
+ *
317
+ * Transforms a service name into the corresponding env var suffix:
318
+ * `supabase-nextjs` → `SUPABASE_NEXTJS`
319
+ * `my.app` → `MY_APP`
320
+ * `proto://app` → `PROTO___APP`
321
+ */
322
+ function sanitizeServiceName(name) {
323
+ return name.toUpperCase().replace(/[-.:\/]/g, "_");
324
+ }
325
+ /**
326
+ * Extracted from pickFrontendBaseUrl: returns the chosen frontend service
327
+ * object (not the URL). Implements the original heuristics unchanged.
315
328
  *
316
329
  * Heuristics, in order:
317
- * 1. Service whose `serviceName` ends with `-frontend` or contains `frontend`
330
+ * 1. Service whose `serviceName` contains a frontend token (`frontend`,
331
+ * `ui`, `app`, or `web`) as a `-`/`_`-delimited word
318
332
  * 2. Service whose `language` is `typescript` / `javascript` AND whose
319
333
  * `framework` is one of the common UI frameworks (react, vue, svelte,
320
334
  * next, nuxt, etc.)
321
335
  * 3. First service with a defined `api.baseUrl`
322
336
  *
323
- * Returns the baseUrl string, or null when no service has a usable URL.
324
- *
325
- * Exported (vs private) so analyze_changes and future tools can reuse the
326
- * same selection without re-implementing it.
337
+ * Returns the service object or null when no service has a usable URL.
327
338
  */
328
- export async function pickFrontendBaseUrl(repositoryPath) {
329
- const config = await readWorkspaceConfigRaw(repositoryPath);
330
- if (!config?.services || !Array.isArray(config.services))
331
- return null;
332
- const services = config.services;
339
+ function pickFrontendService(services) {
333
340
  // Heuristic 1: serviceName signals frontend.
334
341
  const byName = services.find((s) => typeof s?.serviceName === "string" &&
335
342
  /(?:^|[-_])frontend(?:[-_]|$)|(?:^|[-_])ui(?:[-_]|$)|(?:^|[-_])app(?:[-_]|$)|(?:^|[-_])web(?:[-_]|$)/.test(s.serviceName.toLowerCase()) &&
336
343
  s?.api?.baseUrl);
337
344
  if (byName)
338
- return byName.api.baseUrl;
345
+ return byName;
339
346
  // Heuristic 2: language + framework signal frontend.
340
347
  const FRONTEND_FRAMEWORKS = new Set([
341
348
  "react", "vue", "svelte", "next", "nextjs", "nuxt", "sveltekit",
@@ -349,10 +356,47 @@ export async function pickFrontendBaseUrl(repositoryPath) {
349
356
  s?.api?.baseUrl);
350
357
  });
351
358
  if (byFramework)
352
- return byFramework.api.baseUrl;
359
+ return byFramework;
353
360
  // Heuristic 3: first service with a baseUrl.
354
361
  const anyBase = services.find((s) => s?.api?.baseUrl);
355
- return anyBase?.api?.baseUrl ?? null;
362
+ return anyBase ?? null;
363
+ }
364
+ /**
365
+ * Resolves the frontend service's baseUrl from the workspace config.
366
+ *
367
+ * Returns the baseUrl string with environment variable override precedence:
368
+ * 1. Per-service override: `SKYRAMP_TEST_SERVICE_URL_<NAME>` (where <NAME>
369
+ * is the picked frontend service's serviceName, sanitized via
370
+ * `sanitizeServiceName`)
371
+ * 2. Global override: `SKYRAMP_TEST_BASE_URL` (single service or all
372
+ * services share one URL)
373
+ * 3. Static workspace.yml value: `api.baseUrl` from the picked frontend
374
+ * service
375
+ *
376
+ * Returns null when no service has a usable URL.
377
+ *
378
+ * Exported (vs private) so analyze_changes and future tools can reuse the
379
+ * same selection without re-implementing it.
380
+ */
381
+ export async function pickFrontendBaseUrl(repositoryPath) {
382
+ const config = await readWorkspaceConfigRaw(repositoryPath);
383
+ if (!config?.services || !Array.isArray(config.services))
384
+ return null;
385
+ const picked = pickFrontendService(config.services);
386
+ // 1. Per-service override (multi-service, distinct URLs).
387
+ // readWorkspaceConfigRaw may fall back to unvalidated YAML, so serviceName
388
+ // is not guaranteed to be a string — guard before sanitizing.
389
+ if (typeof picked?.serviceName === "string") {
390
+ const perSvc = process.env[`SKYRAMP_TEST_SERVICE_URL_${sanitizeServiceName(picked.serviceName)}`]?.trim();
391
+ if (perSvc)
392
+ return perSvc;
393
+ }
394
+ // 2. Global override (single service / all services share a URL).
395
+ const global = process.env.SKYRAMP_TEST_BASE_URL?.trim();
396
+ if (global)
397
+ return global;
398
+ // 3. Static workspace.yml value.
399
+ return picked?.api?.baseUrl ?? null;
356
400
  }
357
401
  // ── Composer ────────────────────────────────────────────────────────────────
358
402
  /**
@@ -0,0 +1 @@
1
+ export {};