@skyramp/mcp 0.2.6 → 0.2.8-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 (248) 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 +83 -18
  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 +97 -99
  57. package/build/prompts/testbot/testbot-prompts.d.ts +26 -0
  58. package/build/prompts/testbot/testbot-prompts.js +301 -46
  59. package/build/prompts/testbot/testbot-prompts.test.d.ts +1 -0
  60. package/build/prompts/testbot/testbot-prompts.test.js +109 -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 +2 -2
  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 +26 -7
  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 +394 -0
  115. package/build/tools/submitReportTool.js +250 -41
  116. package/build/tools/submitReportTool.test.d.ts +1 -0
  117. package/build/tools/submitReportTool.test.js +511 -55
  118. package/build/tools/test-management/actionsTool.d.ts +10 -0
  119. package/build/tools/test-management/actionsTool.js +21 -7
  120. package/build/tools/test-management/analyzeChangesTool.d.ts +27 -0
  121. package/build/tools/test-management/analyzeChangesTool.js +150 -48
  122. package/build/tools/test-management/analyzeChangesTool.test.d.ts +1 -0
  123. package/build/tools/test-management/analyzeChangesTool.test.js +95 -78
  124. package/build/tools/test-management/analyzeTestHealthTool.d.ts +2 -0
  125. package/build/tools/test-management/analyzeTestHealthTool.js +16 -9
  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.js +39 -22
  139. package/build/tools/workspace/initializeWorkspaceTool.test.d.ts +1 -0
  140. package/build/tools/workspace/initializeWorkspaceTool.test.js +134 -0
  141. package/build/tools/workspace/serviceUpsert.d.ts +12 -0
  142. package/build/tools/workspace/serviceUpsert.js +23 -0
  143. package/build/tools/workspace/serviceUpsert.test.d.ts +1 -0
  144. package/build/tools/workspace/serviceUpsert.test.js +50 -0
  145. package/build/types/OneClickCommands.d.ts +68 -0
  146. package/build/types/RepositoryAnalysis.d.ts +3004 -0
  147. package/build/types/TestAnalysis.d.ts +97 -0
  148. package/build/types/TestExecution.d.ts +63 -0
  149. package/build/types/TestExecution.js +11 -1
  150. package/build/types/TestRecommendation.d.ts +12 -0
  151. package/build/types/TestTypes.d.ts +234 -0
  152. package/build/types/TestbotReport.d.ts +68 -0
  153. package/build/types/TestbotReport.js +1 -0
  154. package/build/types/index.d.ts +4 -0
  155. package/build/types/index.js +3 -0
  156. package/build/utils/AnalysisStateManager.d.ts +242 -0
  157. package/build/utils/AnalysisStateManager.js +144 -3
  158. package/build/utils/AnalysisStateManager.test.d.ts +1 -0
  159. package/build/utils/AnalysisStateManager.test.js +98 -0
  160. package/build/utils/analyze-openapi.d.ts +1 -0
  161. package/build/utils/branchDiff.d.ts +18 -0
  162. package/build/utils/dartRouteExtractor.d.ts +45 -0
  163. package/build/utils/dartRouteExtractor.test.d.ts +1 -0
  164. package/build/utils/docker.d.ts +19 -0
  165. package/build/utils/docker.test.d.ts +1 -0
  166. package/build/utils/docker.test.js +10 -9
  167. package/build/utils/featureFlags.d.ts +37 -0
  168. package/build/utils/featureFlags.test.d.ts +1 -0
  169. package/build/utils/gitStaging.d.ts +24 -0
  170. package/build/utils/gitStaging.test.d.ts +1 -0
  171. package/build/utils/gitStaging.test.js +13 -7
  172. package/build/utils/httpDefaults.d.ts +10 -0
  173. package/build/utils/httpDefaults.test.d.ts +1 -0
  174. package/build/utils/initAgent.d.ts +36 -0
  175. package/build/utils/language-helper.d.ts +6 -0
  176. package/build/utils/logger.d.ts +11 -0
  177. package/build/utils/normalizeParams.d.ts +14 -0
  178. package/build/utils/normalizeSkyrampImports.d.ts +13 -0
  179. package/build/utils/pr-comment-parser.d.ts +31 -0
  180. package/build/utils/pr-comment-parser.test.d.ts +1 -0
  181. package/build/utils/pr-comment-parser.test.js +6 -5
  182. package/build/utils/projectMetadata.d.ts +15 -0
  183. package/build/utils/projectMetadata.test.d.ts +1 -0
  184. package/build/utils/proxy-terminal.d.ts +2 -0
  185. package/build/utils/repoScanner.d.ts +27 -0
  186. package/build/utils/repoScanner.test.d.ts +1 -0
  187. package/build/utils/routeParsers.d.ts +53 -0
  188. package/build/utils/routeParsers.js +31 -38
  189. package/build/utils/routeParsers.test.d.ts +1 -0
  190. package/build/utils/scenarioDrafting.d.ts +71 -0
  191. package/build/utils/scenarioDrafting.js +23 -1
  192. package/build/utils/scenarioDrafting.test.d.ts +1 -0
  193. package/build/utils/skyrampMdContent.d.ts +5 -0
  194. package/build/utils/sourceRouteExtractor.d.ts +48 -0
  195. package/build/utils/sourceRouteExtractor.test.d.ts +1 -0
  196. package/build/utils/telemetry.d.ts +32 -0
  197. package/build/utils/telemetry.test.d.ts +1 -0
  198. package/build/utils/trace-parser.d.ts +32 -0
  199. package/build/utils/trace-parser.test.d.ts +6 -0
  200. package/build/utils/uiPageEnumerator.d.ts +146 -0
  201. package/build/utils/uiPageEnumerator.js +58 -14
  202. package/build/utils/uiPageEnumerator.test.d.ts +1 -0
  203. package/build/utils/uiPageEnumerator.test.js +223 -43
  204. package/build/utils/utils.d.ts +45 -0
  205. package/build/utils/utils.js +10 -0
  206. package/build/utils/utils.test.d.ts +1 -0
  207. package/build/utils/versions.d.ts +3 -0
  208. package/build/utils/versions.js +1 -1
  209. package/build/utils/workspaceAuth.d.ts +127 -0
  210. package/build/utils/workspaceAuth.test.d.ts +1 -0
  211. package/build/utils/workspaceAuth.test.js +25 -25
  212. package/build/workspace/index.d.ts +1 -0
  213. package/build/workspace/workspace.d.ts +390 -0
  214. package/build/workspace/workspace.js +7 -7
  215. package/build/workspace/workspace.test.d.ts +1 -0
  216. package/build/workspace/workspace.test.js +6 -6
  217. package/node_modules/playwright/lib/mcp/skyramp/loadTraceTool.js +31 -13
  218. package/node_modules/playwright/lib/mcp/skyramp/traceRecordingBackend.js +88 -15
  219. package/node_modules/playwright/lib/mcp/test/skyRampExport.js +4 -3
  220. package/node_modules/playwright/node_modules/playwright-core/ThirdPartyNotices.txt +23 -126
  221. package/node_modules/playwright/node_modules/playwright-core/lib/generated/injectedScriptSource.js +1 -1
  222. package/node_modules/playwright/node_modules/playwright-core/lib/server/recorder/recorderApp.js +79 -19
  223. package/node_modules/playwright/node_modules/playwright-core/lib/server/recorder/skyramp/aliasRemapper.js +84 -0
  224. package/node_modules/playwright/node_modules/playwright-core/lib/server/recorder/skyramp/replayEngine.js +15 -4
  225. package/node_modules/playwright/node_modules/playwright-core/lib/vite/recorder/assets/{codeMirrorModule-CPZKmjFR.js → codeMirrorModule-D0BjbCb7.js} +1 -1
  226. package/node_modules/playwright/node_modules/playwright-core/lib/vite/recorder/assets/index-lvTRGFx-.js +193 -0
  227. package/node_modules/playwright/node_modules/playwright-core/lib/vite/recorder/index.html +1 -1
  228. package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/assets/{codeMirrorModule-B0JOjboO.js → codeMirrorModule-Cqp3cwEJ.js} +1 -1
  229. package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/assets/{defaultSettingsView-1anWeyDf.js → defaultSettingsView-CYf9adZh.js} +79 -79
  230. package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/{index.Cc5029a3.js → index.B_7ywgmr.js} +1 -1
  231. package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/index.html +2 -2
  232. package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/uiMode.html +2 -2
  233. package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/{uiMode.Wo5yvvVh.js → uiMode.u4_8VnCV.js} +1 -1
  234. package/node_modules/playwright/node_modules/playwright-core/package.json +1 -1
  235. package/node_modules/playwright/node_modules/playwright-core/src/generated/injectedScriptSource.ts +1 -1
  236. package/node_modules/playwright/node_modules/playwright-core/src/server/recorder/recorderApp.ts +129 -43
  237. package/node_modules/playwright/node_modules/playwright-core/src/server/recorder/skyramp/aliasRemapper.ts +119 -0
  238. package/node_modules/playwright/node_modules/playwright-core/src/server/recorder/skyramp/replayEngine.ts +33 -10
  239. package/node_modules/playwright/package.json +1 -1
  240. package/package.json +16 -4
  241. package/build/tools/test-management/uiAnalyzeChangesTool.js +0 -155
  242. package/build/tools/test-management/uiAnalyzeChangesTool.test.js +0 -147
  243. package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/.bin/node-which +0 -52
  244. package/node_modules/playwright/node_modules/playwright-core/bundles/utils/node_modules/.bin/is-docker +0 -5
  245. package/node_modules/playwright/node_modules/playwright-core/bundles/utils/node_modules/.bin/mime +0 -46
  246. package/node_modules/playwright/node_modules/playwright-core/bundles/utils/node_modules/.bin/yaml +0 -11
  247. package/node_modules/playwright/node_modules/playwright-core/lib/vite/recorder/assets/index-CsgPR2wy.js +0 -193
  248. package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/index.CP3Y8Zsb.js +0 -2
@@ -0,0 +1,10 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ /**
3
+ * Compute a suggested new filename when an endpoint is renamed.
4
+ */
5
+ export declare function computeRenamedTestFile(testFile: string, renames: Array<{
6
+ oldPath: string;
7
+ newPath: string;
8
+ method: string;
9
+ }>): string | null;
10
+ export declare function registerActionsTool(server: McpServer): void;
@@ -6,6 +6,7 @@ import * as fs from "fs";
6
6
  import * as path from "path";
7
7
  import { AnalyticsService } from "../../services/AnalyticsService.js";
8
8
  import { toolError } from "../../utils/utils.js";
9
+ import { stageGeneratedPaths } from "../../utils/gitStaging.js";
9
10
  import { buildRenameStrategy, buildFileRenameStrategy, buildUpdateStrategy, buildRegenerateStrategy, buildUpdateFileInstruction, buildRegenerateFileInstruction, } from "../../prompts/test-maintenance/actionsInstructions.js";
10
11
  /**
11
12
  * Compute a suggested new filename when an endpoint is renamed.
@@ -87,6 +88,10 @@ const actionsSchema = {
87
88
  .string()
88
89
  .refine((p) => path.isAbsolute(p), { message: "stateFile must be an absolute path" })
89
90
  .describe("Path to state file from skyramp_analyze_changes"),
91
+ repository: z
92
+ .string()
93
+ .optional()
94
+ .describe("The owner/repo whose analysis section to act on (e.g. 'letsramp/api-insight'). In a multi-repo run, ALWAYS set it — the primary's owner/repo for the primary repo's tests, or a related repo's owner/repo for that repo's section — within the run-scoped stateFile. Omit only for a single-repo run."),
90
95
  recommendations: z
91
96
  .array(recommendationSchema)
92
97
  .optional()
@@ -106,12 +111,12 @@ export function registerActionsTool(server) {
106
111
  }, async (args) => {
107
112
  let errorResult;
108
113
  try {
109
- logger.info("Performing Actions (unified test-management)");
110
- // Load UnifiedAnalysisState from state file
114
+ logger.info(`Performing Actions (unified test-management)${args.repository ? ` for repository ${args.repository}` : ""}`);
115
+ // Load UnifiedAnalysisState for the requested repo section (primary when
116
+ // repository omitted/matches the root) from the run-scoped state file.
111
117
  const stateManager = StateManager.fromStatePath(args.stateFile);
112
- const stateData = await stateManager.readData();
113
- const fullState = await stateManager.readFullState();
114
- const repositoryPath = fullState?.metadata.repositoryPath || "";
118
+ const stateData = await stateManager.readRepoData(args.repository);
119
+ const repositoryPath = (await stateManager.getRepoRepositoryPath(args.repository)) || "";
115
120
  if (!stateData) {
116
121
  errorResult = toolError(`State file is empty or invalid: ${args.stateFile}. Call skyramp_analyze_changes first to generate a valid state file.`);
117
122
  return errorResult;
@@ -365,6 +370,15 @@ export function registerActionsTool(server) {
365
370
  });
366
371
  llmInstructionsObj.regenerate_strategy = buildRegenerateStrategy();
367
372
  }
373
+ // Stage UPDATE and REGENERATE target paths so testbot's re-stage step
374
+ // commits the agent's edits even when files are outside testDirectory.
375
+ const stagedPaths = [
376
+ ...updateRecommendations.map((r) => r.testFile),
377
+ ...regenerateRecommendations.map((r) => r.testFile),
378
+ ];
379
+ for (const p of stagedPaths) {
380
+ await stageGeneratedPaths(p).catch((e) => logger.warning(`Failed to stage maintained file ${p}: ${e.message}`));
381
+ }
368
382
  const llmInstructions = JSON.stringify(llmInstructionsObj, null, 2);
369
383
  const contentBlocks = [
370
384
  {
@@ -394,8 +408,8 @@ export function registerActionsTool(server) {
394
408
  finally {
395
409
  let repositoryPath = "";
396
410
  try {
397
- const fullState = await StateManager.fromStatePath(args.stateFile).readFullState();
398
- repositoryPath = fullState?.metadata.repositoryPath || "";
411
+ repositoryPath =
412
+ (await StateManager.fromStatePath(args.stateFile).getRepoRepositoryPath(args.repository)) || "";
399
413
  }
400
414
  catch (analyticsError) {
401
415
  logger.error(`Failed to read state for analytics in actions tool: ${analyticsError?.message ?? analyticsError}`, analyticsError);
@@ -0,0 +1,27 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { z } from "zod";
3
+ import { CandidateUiPage } from "../../utils/uiPageEnumerator.js";
4
+ import { TraceFile } from "../../types/RepositoryAnalysis.js";
5
+ import { ScannedEndpoint } from "../../utils/repoScanner.js";
6
+ import { TraceParseResult } from "../../utils/trace-parser.js";
7
+ /** Exported for testing: maps a parsed trace result to a TraceFile. */
8
+ export declare function buildTraceFileEntry(tracePath: string, result: TraceParseResult): TraceFile;
9
+ export declare function isSecurityRelevantDiff(diffContent: string): boolean;
10
+ export declare function filterEndpointsBySpec(scannedEndpoints: ScannedEndpoint[], specPaths: Set<string>, specPathItems: Record<string, any>, diffChangedPaths: Set<string>): ScannedEndpoint[];
11
+ export declare function isGraphQLFile(filePath: string, repositoryPath: string): Promise<boolean>;
12
+ export declare function filterUnsupportedGraphQLEndpoints(endpoints: ScannedEndpoint[], repositoryPath: string): Promise<ScannedEndpoint[]>;
13
+ export declare const analyzeChangesInputSchema: {
14
+ repositoryPath: z.ZodString;
15
+ scope: z.ZodOptional<z.ZodDefault<z.ZodEnum<["full_repo", "branch_diff"]>>>;
16
+ baseBranch: z.ZodOptional<z.ZodString>;
17
+ testDirectory: z.ZodOptional<z.ZodString>;
18
+ topN: z.ZodOptional<z.ZodDefault<z.ZodNumber>>;
19
+ maxGenerate: z.ZodOptional<z.ZodNumber>;
20
+ prNumber: z.ZodOptional<z.ZodNumber>;
21
+ repository: z.ZodOptional<z.ZodString>;
22
+ testsRepoDir: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
23
+ };
24
+ export declare const NO_UI_INSTRUCTIONS = "No UI changes detected \u2014 no blueprint capture needed.";
25
+ export declare const NO_RESOLVABLE_URLS_INSTRUCTIONS = "Frontend changes detected but no candidate URLs could be resolved (workspace baseUrl missing or no router files matched). UI recommendations will be source-grounded only.";
26
+ export declare function buildCaptureInstructions(pages: CandidateUiPage[]): string;
27
+ export declare function registerAnalyzeChangesTool(server: McpServer): void;
@@ -8,7 +8,7 @@ import { simpleGit } from "simple-git";
8
8
  import { logger } from "../../utils/logger.js";
9
9
  import { parseWorkspaceAuthType, getDefaultAuthHeader, WorkspaceAuthType, readWorkspaceConfigRaw } from "../../utils/workspaceAuth.js";
10
10
  import { AnalyticsService } from "../../services/AnalyticsService.js";
11
- import { StateManager, registerSession, storeSessionData, setTestsRepoDir, } from "../../utils/AnalysisStateManager.js";
11
+ import { StateManager, registerSession, storeSessionData, setTestsRepoDir, getActiveRunStatePath, setActiveRunStatePath, } from "../../utils/AnalysisStateManager.js";
12
12
  import { buildRecommendationPrompt } from "../../prompts/test-recommendation/test-recommendation-prompt.js";
13
13
  import { hasFlutterSdkDep, isFrontendFile, isTestFile } from "../../prompts/test-recommendation/scopeAssessment.js";
14
14
  import { enumerateCandidateUiPages } from "../../utils/uiPageEnumerator.js";
@@ -24,6 +24,32 @@ import { buildAnalysisOutputText } from "../../prompts/test-recommendation/analy
24
24
  import { parseTraceFile, discoverTraceFiles, discoverPlaywrightZips, } from "../../utils/trace-parser.js";
25
25
  import { TestSource } from "../../types/TestAnalysis.js";
26
26
  import { parsePRComments } from "../../utils/pr-comment-parser.js";
27
+ // Generic module names that appear as imports in virtually every router file.
28
+ // Matching against these would extract ALL prefix= tokens from mount files,
29
+ // flooding changedResources with unrelated resources.
30
+ const GENERIC_MODULE_NAMES = new Set([
31
+ "models", "utils", "helpers", "schemas", "types", "constants",
32
+ "config", "settings", "exceptions", "errors", "base", "common",
33
+ "app", "main", "index", "server", "init", "deps", "dependencies",
34
+ ]);
35
+ function resolveMountPrefixTokens(mountFileContents, changedFiles) {
36
+ const tokens = changedFiles.flatMap((file) => {
37
+ const mod = path.basename(file).replace(/\.[^.]+$/, "").toLowerCase();
38
+ if (GENERIC_MODULE_NAMES.has(mod))
39
+ return [];
40
+ const modRe = new RegExp(`\\b${mod.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\b`);
41
+ return mountFileContents.flatMap((content) => {
42
+ if (!modRe.test(content.toLowerCase()))
43
+ return [];
44
+ const matches = [...content.matchAll(/prefix\s*=\s*["'](\/?[^"']+)/g)];
45
+ return matches.flatMap((m) => {
46
+ const token = extractResourceFromPath(m[1]);
47
+ return token !== "unknown" ? [token] : [];
48
+ });
49
+ });
50
+ });
51
+ return [...new Set(tokens)];
52
+ }
27
53
  /** Exported for testing: maps a parsed trace result to a TraceFile. */
28
54
  export function buildTraceFileEntry(tracePath, result) {
29
55
  return {
@@ -339,17 +365,44 @@ export const analyzeChangesInputSchema = {
339
365
  .number()
340
366
  .optional()
341
367
  .describe("GitHub PR number. When provided, fetches previous TestBot comments on this PR and skips re-recommending tests already suggested in earlier commits — reduces duplicate recommendations across multiple pushes to the same PR."),
342
- stateOutputFile: z
368
+ repository: z
343
369
  .string()
344
- .refine((v) => path.isAbsolute(v), { message: "stateOutputFile must be an absolute path" })
345
370
  .optional()
346
- .describe("Absolute path where the state file should be written. When provided, overrides the default auto-generated temp path so the caller can locate it without log parsing."),
371
+ .describe("The owner/repo this call analyzes (e.g. 'letsramp/api-insight'). In a multi-repo run, ALWAYS set it — for the primary AND every related repo. All calls in one Testbot run share ONE state file automatically (you do NOT pass a file path); each repo's analysis is stored as its own section within it. The primary's analysis is kept at the root and tagged with its owner/repo; related repos are stored as keyed sections. Omit only for a single-repo run."),
347
372
  testsRepoDir: z
348
373
  .string()
349
374
  .refine((v) => path.isAbsolute(v), { message: "testsRepoDir must be an absolute path" })
350
375
  .optional()
351
376
  .describe("Absolute path to a separate test repository clone. When set, existing test discovery scans this directory instead of repositoryPath. Used in cross-repo test delivery mode where tests live in a separate repo."),
352
377
  };
378
+ // ── UI blueprint-capture instructions ──
379
+ // Moved here from the former skyramp_ui_analyze_changes pre-flight tool. These
380
+ // strings + buildCaptureInstructions are appended to the tool output so the
381
+ // agent captures browser_blueprints on the candidate UI pages after this call.
382
+ // The captures stay in its tool-result history and serve as element vocabulary
383
+ // for UI recommendation reasoning (enforced at submit time by the Blueprint
384
+ // Citation Invariant in the testbot prompt).
385
+ export const NO_UI_INSTRUCTIONS = `No UI changes detected — no blueprint capture needed.`;
386
+ export const NO_RESOLVABLE_URLS_INSTRUCTIONS = `Frontend changes detected but no candidate URLs could be resolved (workspace baseUrl missing or no router files matched). UI recommendations will be source-grounded only.`;
387
+ export function buildCaptureInstructions(pages) {
388
+ const pagesYaml = pages
389
+ .map((p, i) => ` ${i + 1}. ${p.url} (sourcedFrom: ${p.sourcedFrom.join(", ") || "(none)"}, strategy: ${p.strategy})`)
390
+ .join("\n");
391
+ return `Frontend changes detected. **Before writing any UI recommendation \`reasoning\`, capture blueprints on the candidate UI pages below.** Those captures stay in your tool-result history and serve as element vocabulary — the recommendation catalog further down gives you the authoring rules; you bring the observed elements.
392
+
393
+ **Candidate URLs:**
394
+ ${pagesYaml}
395
+
396
+ **For each candidate URL:**
397
+ - \`browser_navigate\` to the URL
398
+ - \`browser_blueprint\` to capture the page
399
+
400
+ You don't need to thread the blueprints back into a tool call — they're in your context once captured.
401
+
402
+ If a candidate URL 404s or redirects unexpectedly, navigate from the workspace baseUrl and explore (admin apps mount routes under base prefixes the source extraction can't see). If the page rendered but lacks the changed feature (gated UI: modal, dropdown, accordion), do NOT iterate further during this step — UI recs will fall back to source-grounded prose for those, and the agent's later trace recording (Task 2) will navigate into the gate via capture-act-capture.
403
+
404
+ If \`browser_blueprint\` fails on every candidate URL (app unreachable, all 404s), proceed and log an \`issuesFound\` info entry. Recommendations will be source-grounded; non-UI work is unaffected.`;
405
+ }
353
406
  export function registerAnalyzeChangesTool(server) {
354
407
  server.registerTool(TOOL_NAME, {
355
408
  annotations: {
@@ -540,6 +593,30 @@ export function registerAnalyzeChangesTool(server) {
540
593
  // testsRepoDir is a cross-repo path override — honour it when set.
541
594
  // Otherwise always scan the full repo root.
542
595
  const testDir = params.testsRepoDir ?? undefined;
596
+ const routerMountContext = grepRouterMountingContext(params.repositoryPath);
597
+ // Read contents once — used for token extraction and LLM prompt injection.
598
+ const ROUTER_INLINE_LIMIT = 4096;
599
+ const routerMountContextContents = routerMountContext.map((f) => {
600
+ try {
601
+ const abs = path.isAbsolute(f) ? f : path.join(params.repositoryPath, f);
602
+ if (fs.statSync(abs).size > ROUTER_INLINE_LIMIT * 10)
603
+ return ""; // skip very large files
604
+ return fs.readFileSync(abs, "utf-8");
605
+ }
606
+ catch {
607
+ return "";
608
+ }
609
+ });
610
+ // Size-capped subset for LLM prompt injection.
611
+ const ROUTER_INLINE_MAX_FILES = 3;
612
+ const routerFileContents = routerMountContext
613
+ .slice(0, ROUTER_INLINE_MAX_FILES)
614
+ .flatMap((f, i) => {
615
+ const content = routerMountContextContents[i] ?? "";
616
+ if (!content || content.length > ROUTER_INLINE_LIMIT)
617
+ return [];
618
+ return [{ file: f, content: content.trimEnd() }];
619
+ });
543
620
  // Compute changedResources from classified endpoints for test discovery filtering.
544
621
  // undefined → full-repo mode (no diff context)
545
622
  // [] → PR mode, no endpoints found → skip external tests
@@ -568,14 +645,22 @@ export function registerAnalyzeChangesTool(server) {
568
645
  }
569
646
  else {
570
647
  // All endpoints resolved to "unknown" (e.g. NestJS relative paths) —
571
- // extract resource names from changed file paths instead.
572
- const candidateFiles = classifiedEndpoints.unmatchedFiles.length > 0
573
- ? classifiedEndpoints.unmatchedFiles
574
- : classifiedEndpoints.changedFiles ?? [];
575
- const fromFiles = candidateFiles
576
- .map((f) => extractResourceFromPath(f.replace(/\.[^./]+$/, "").replace(FRAMEWORK_SUFFIX_RE, "")))
577
- .filter((r, i, arr) => r !== "unknown" && arr.indexOf(r) === i);
578
- changedResources = fromFiles.length > 0 ? fromFiles : ["unknown"];
648
+ // first try to extract resource names from router mount prefixes.
649
+ const changedSourceFiles = classifiedEndpoints.changedFiles ?? [];
650
+ const mountedResources = resolveMountPrefixTokens(routerMountContextContents, changedSourceFiles);
651
+ if (mountedResources.length > 0) {
652
+ changedResources = mountedResources;
653
+ }
654
+ else {
655
+ // Fall back to extracting resource names from changed file paths.
656
+ const candidateFiles = classifiedEndpoints.unmatchedFiles.length > 0
657
+ ? classifiedEndpoints.unmatchedFiles
658
+ : classifiedEndpoints.changedFiles ?? [];
659
+ const fromFiles = candidateFiles
660
+ .map((f) => extractResourceFromPath(f.replace(/\.[^./]+$/, "").replace(FRAMEWORK_SUFFIX_RE, "")))
661
+ .filter((r, i, arr) => r !== "unknown" && arr.indexOf(r) === i);
662
+ changedResources = fromFiles.length > 0 ? fromFiles : ["unknown"];
663
+ }
579
664
  }
580
665
  }
581
666
  else if (classifiedEndpoints.unmatchedFiles.length > 0) {
@@ -594,7 +679,15 @@ export function registerAnalyzeChangesTool(server) {
594
679
  changedResources = fromFiles.length > 0 ? fromFiles : ["unknown"];
595
680
  }
596
681
  else {
597
- changedResources = [];
682
+ // No endpoints and no unmatched files — the diff touches only non-route
683
+ // files (e.g. schema, model, DTO). Extract resource tokens from the
684
+ // changed file paths so tests that import the changed symbol are still
685
+ // surfaced for drift assessment. Fall back to [] only if extraction
686
+ // yields nothing (e.g. a file with a non-resource name like utils.py).
687
+ const fromChangedFiles = (classifiedEndpoints.changedFiles ?? [])
688
+ .map((f) => extractResourceFromPath(f.replace(/\.[^./]+$/, "").replace(FRAMEWORK_SUFFIX_RE, "")))
689
+ .filter((r, i, arr) => r !== "unknown" && arr.indexOf(r) === i);
690
+ changedResources = fromChangedFiles.length > 0 ? fromChangedFiles : [];
598
691
  }
599
692
  }
600
693
  let existingTests = [];
@@ -875,10 +968,15 @@ export function registerAnalyzeChangesTool(server) {
875
968
  sourceFile: m.sourceFile ?? "",
876
969
  })))
877
970
  : [];
971
+ const removedEndpointsForDrafting = classifiedEndpoints?.removedEndpoints.flatMap((ep) => ep.methods.map((m) => ({
972
+ method: m,
973
+ path: ep.path,
974
+ sourceFile: ep.sourceFile,
975
+ }))) ?? [];
878
976
  const codeInferredScenarios = draftScenariosFromEndpoints(skeletonEndpoints, scenarioDraftSeed, wsAuthType, {
879
977
  changedEndpoints: changedEndpointsForSecurityExpansion,
880
978
  securityRelevantDiff,
881
- });
979
+ }, removedEndpointsForDrafting);
882
980
  let allDraftedScenarios = codeInferredScenarios;
883
981
  if (traceResult && traceResult.userFlows.length > 0) {
884
982
  const traceScenarios = traceResult.userFlows
@@ -1081,7 +1179,6 @@ export function registerAnalyzeChangesTool(server) {
1081
1179
  // can be persisted to the state file for downstream tools (health, drift).
1082
1180
  // Without them, analyzeTestHealth would work only off the static catalog
1083
1181
  // which has wrong paths for nested resources and unsupported frameworks.
1084
- const routerMountContext = grepRouterMountingContext(params.repositoryPath);
1085
1182
  const routeLikeUnmatchedFiles = [];
1086
1183
  for (const file of classifiedEndpoints?.unmatchedFiles ?? []) {
1087
1184
  const routeLike = SOURCE_EXTS.test(file) &&
@@ -1110,25 +1207,6 @@ export function registerAnalyzeChangesTool(server) {
1110
1207
  diffFilePath = path.join(os.tmpdir(), `skyramp-diff-${sessionId}.diff`);
1111
1208
  await fs.promises.writeFile(diffFilePath, diffData.diffContent, { encoding: "utf-8", mode: 0o600 });
1112
1209
  }
1113
- // Read router mount files server-side (size-capped) so the LLM has them
1114
- // inline and doesn't need an extra read step when no spec is available.
1115
- const ROUTER_INLINE_LIMIT = 4096; // bytes — skip files larger than ~4 KB
1116
- const ROUTER_INLINE_MAX_FILES = 3;
1117
- const routerFileContents = routerMountContext
1118
- .slice(0, ROUTER_INLINE_MAX_FILES)
1119
- .flatMap((f) => {
1120
- try {
1121
- const absPath = path.isAbsolute(f) ? f : path.join(params.repositoryPath, f);
1122
- const stat = fs.statSync(absPath);
1123
- if (stat.size > ROUTER_INLINE_LIMIT)
1124
- return [];
1125
- const content = fs.readFileSync(absPath, "utf-8").trimEnd();
1126
- return [{ file: f, content }];
1127
- }
1128
- catch {
1129
- return [];
1130
- }
1131
- });
1132
1210
  // Compute UI context from the diff's changed files using the shared
1133
1211
  // `isFrontendFile` classifier. Persisting this in the stateFile lets
1134
1212
  // skyramp_analyze_test_health and the recommendation prompt consume the
@@ -1136,9 +1214,9 @@ export function registerAnalyzeChangesTool(server) {
1136
1214
  //
1137
1215
  // candidateUiPages is enumerated programmatically via the strategy
1138
1216
  // ladder in uiPageEnumerator (framework route grep, source-grounded
1139
- // routes, root fallback). The same enumeration powers the
1140
- // skyramp_ui_analyze_changes pre-flight tool that the testbot prompt
1141
- // calls before this tool, so both code paths see the same candidates.
1217
+ // routes, root fallback). The agent uses these to capture
1218
+ // browser_blueprints see uiInstructions below, which this tool returns
1219
+ // so the agent captures element vocabulary for UI rec reasoning.
1142
1220
  const uiContext = await (async () => {
1143
1221
  const changedFiles = classifiedEndpoints?.changedFiles ?? [];
1144
1222
  if (changedFiles.length === 0)
@@ -1149,9 +1227,8 @@ export function registerAnalyzeChangesTool(server) {
1149
1227
  // enumeration in.
1150
1228
  // Compute hasFlutterSdkDep once and pass it through so .dart files
1151
1229
  // in a Flutter project are recognised as frontend. See Confluence
1152
- // "Flutter support in Testbot" — this is the second budget-driving
1153
- // call site that must thread the flag (the other is
1154
- // uiAnalyzeChangesTool). Both must agree to avoid silent divergence.
1230
+ // "Flutter support in Testbot" — this is the budget-driving call site
1231
+ // that must thread the flag so a Flutter PR enters the UI pipeline.
1155
1232
  const flutterSdk = hasFlutterSdkDep(params.repositoryPath);
1156
1233
  const frontendFiles = changedFiles.filter((f) => isFrontendFile(f, { hasFlutterSdkDep: flutterSdk }) && !isTestFile(f));
1157
1234
  if (frontendFiles.length === 0)
@@ -1162,6 +1239,16 @@ export function registerAnalyzeChangesTool(server) {
1162
1239
  candidateUiPages,
1163
1240
  };
1164
1241
  })();
1242
+ // Derive the UI blueprint-capture instructions the agent acts on after
1243
+ // this call. Mirrors the branching of the former skyramp_ui_analyze_changes
1244
+ // pre-flight tool, now folded in here so the agent makes one analysis call
1245
+ // instead of two. The captures stay in the agent's tool-result history and
1246
+ // serve as element vocabulary when it writes UI recommendation reasoning.
1247
+ const uiInstructions = !uiContext || uiContext.changedFrontendFiles.length === 0
1248
+ ? NO_UI_INSTRUCTIONS
1249
+ : uiContext.candidateUiPages.length === 0
1250
+ ? NO_RESOLVABLE_URLS_INSTRUCTIONS
1251
+ : buildCaptureInstructions(uiContext.candidateUiPages);
1165
1252
  const unifiedState = {
1166
1253
  existingTests,
1167
1254
  newEndpoints: newEndpointsForDrafting,
@@ -1215,22 +1302,37 @@ export function registerAnalyzeChangesTool(server) {
1215
1302
  relevantExternalTestPaths,
1216
1303
  },
1217
1304
  };
1218
- // Clean up old state files (>24 hours) before creating new one
1219
- const stateDir = params.stateOutputFile
1220
- ? path.dirname(path.resolve(params.stateOutputFile))
1221
- : undefined;
1305
+ // Clean up old state files (>24 hours) before creating new one.
1222
1306
  try {
1223
- await StateManager.cleanupOldFiles(24, stateDir);
1307
+ await StateManager.cleanupOldFiles(24);
1224
1308
  }
1225
1309
  catch (error) {
1226
1310
  logger.warning(`Failed to cleanup old state files: ${error.message}`);
1227
1311
  }
1228
- const stateManager = new StateManager("analysis", sessionId, undefined, params.stateOutputFile);
1229
- await stateManager.writeData(unifiedState, {
1312
+ // Resolve the state file. Multi-repo (`repository` set): all calls in one
1313
+ // Testbot run share ONE state file — the first call creates it and records
1314
+ // the run anchor; later related-repo calls reuse it (no agent-provided path,
1315
+ // which previously caused filename hallucinations). Single-repo (`repository`
1316
+ // absent): always a fresh per-call file — byte-identical to prior behavior.
1317
+ const activeRunPath = params.repository
1318
+ ? getActiveRunStatePath()
1319
+ : undefined;
1320
+ const stateManager = activeRunPath
1321
+ ? StateManager.fromStatePath(activeRunPath)
1322
+ : new StateManager("analysis", sessionId);
1323
+ // `repository` identifies which repo this analysis belongs to. The primary
1324
+ // (first write of the run, or matching the root's recorded repository) is
1325
+ // stored at the root; a related repo is stored as a keyed section within the
1326
+ // same run-scoped stateFile.
1327
+ await stateManager.writeRepoData(unifiedState, {
1328
+ repo: params.repository,
1230
1329
  repositoryPath: params.repositoryPath,
1231
1330
  step: "analyze_changes",
1232
1331
  });
1233
1332
  const stateFile = stateManager.getStatePath();
1333
+ // Anchor the run's state file so subsequent related-repo calls reuse it.
1334
+ if (params.repository)
1335
+ setActiveRunStatePath(stateFile);
1234
1336
  registerSession(sessionId, stateFile);
1235
1337
  try {
1236
1338
  await server.server.sendResourceListChanged();
@@ -1348,7 +1450,7 @@ export function registerAnalyzeChangesTool(server) {
1348
1450
  content: [
1349
1451
  {
1350
1452
  type: "text",
1351
- text: `\`\`\`json\n${structuredSummary}\n\`\`\`\n\n${outputText}\n\n---\n\n## Pre-built Test Catalog — Fill in placeholders from source code, then display verbatim\n⚠️ Do NOT reformat, rename sections, or generate a new catalog. Replace \`<…from source>\` values, then show this output exactly as-is, grouped by test type.\n\n${recommendationPrompt}`,
1453
+ text: `\`\`\`json\n${structuredSummary}\n\`\`\`\n\n## UI Blueprint Capture — do this BEFORE writing UI recommendation reasoning\n${uiInstructions}\n\n${outputText}\n\n---\n\n## Pre-built Test Catalog — Fill in placeholders from source code, then display verbatim\n⚠️ Do NOT reformat, rename sections, or generate a new catalog. Replace \`<…from source>\` values, then show this output exactly as-is, grouped by test type.\n\n${recommendationPrompt}`,
1352
1454
  },
1353
1455
  ],
1354
1456
  isError: false,