retestkit 1.4.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 (327) hide show
  1. package/.claude/commands/openspec/apply.md +23 -0
  2. package/.claude/commands/openspec/archive.md +27 -0
  3. package/.claude/commands/openspec/proposal.md +28 -0
  4. package/.gemini/commands/openspec/apply.toml +21 -0
  5. package/.gemini/commands/openspec/archive.toml +25 -0
  6. package/.gemini/commands/openspec/proposal.toml +26 -0
  7. package/.github/prompts/openspec-apply.prompt.md +22 -0
  8. package/.github/prompts/openspec-archive.prompt.md +26 -0
  9. package/.github/prompts/openspec-proposal.prompt.md +27 -0
  10. package/.github/workflows/release.yml +33 -0
  11. package/.kilocode/workflows/openspec-apply.md +17 -0
  12. package/.kilocode/workflows/openspec-archive.md +21 -0
  13. package/.kilocode/workflows/openspec-proposal.md +22 -0
  14. package/.mcp.json +23 -0
  15. package/.opencode/command/openspec-apply.md +25 -0
  16. package/.opencode/command/openspec-archive.md +28 -0
  17. package/.opencode/command/openspec-proposal.md +30 -0
  18. package/.roo/commands/openspec-apply.md +20 -0
  19. package/.roo/commands/openspec-archive.md +24 -0
  20. package/.roo/commands/openspec-proposal.md +25 -0
  21. package/.vscode/mcp.json +23 -0
  22. package/AGENTS.md +18 -0
  23. package/CLAUDE.md +18 -0
  24. package/LICENSE +65 -0
  25. package/README.md +303 -0
  26. package/dist/config.d.ts +4 -0
  27. package/dist/config.d.ts.map +1 -0
  28. package/dist/config.js +27 -0
  29. package/dist/config.js.map +1 -0
  30. package/dist/elicitation/index.d.ts +17 -0
  31. package/dist/elicitation/index.d.ts.map +1 -0
  32. package/dist/elicitation/index.js +118 -0
  33. package/dist/elicitation/index.js.map +1 -0
  34. package/dist/elicitation/types.d.ts +35 -0
  35. package/dist/elicitation/types.d.ts.map +1 -0
  36. package/dist/elicitation/types.js +39 -0
  37. package/dist/elicitation/types.js.map +1 -0
  38. package/dist/index.d.ts +3 -0
  39. package/dist/index.d.ts.map +1 -0
  40. package/dist/index.js +76 -0
  41. package/dist/index.js.map +1 -0
  42. package/dist/lifecycle/index.d.ts +31 -0
  43. package/dist/lifecycle/index.d.ts.map +1 -0
  44. package/dist/lifecycle/index.js +61 -0
  45. package/dist/lifecycle/index.js.map +1 -0
  46. package/dist/logger.d.ts +21 -0
  47. package/dist/logger.d.ts.map +1 -0
  48. package/dist/logger.js +182 -0
  49. package/dist/logger.js.map +1 -0
  50. package/dist/playwright-client/index.d.ts +29 -0
  51. package/dist/playwright-client/index.d.ts.map +1 -0
  52. package/dist/playwright-client/index.js +288 -0
  53. package/dist/playwright-client/index.js.map +1 -0
  54. package/dist/playwright-client/types.d.ts +44 -0
  55. package/dist/playwright-client/types.d.ts.map +1 -0
  56. package/dist/playwright-client/types.js +49 -0
  57. package/dist/playwright-client/types.js.map +1 -0
  58. package/dist/progress/index.d.ts +39 -0
  59. package/dist/progress/index.d.ts.map +1 -0
  60. package/dist/progress/index.js +106 -0
  61. package/dist/progress/index.js.map +1 -0
  62. package/dist/progress/types.d.ts +24 -0
  63. package/dist/progress/types.d.ts.map +1 -0
  64. package/dist/progress/types.js +2 -0
  65. package/dist/progress/types.js.map +1 -0
  66. package/dist/prompts/index.d.ts +19 -0
  67. package/dist/prompts/index.d.ts.map +1 -0
  68. package/dist/prompts/index.js +207 -0
  69. package/dist/prompts/index.js.map +1 -0
  70. package/dist/prompts/loader.d.ts +20 -0
  71. package/dist/prompts/loader.d.ts.map +1 -0
  72. package/dist/prompts/loader.js +47 -0
  73. package/dist/prompts/loader.js.map +1 -0
  74. package/dist/resources/index.d.ts +27 -0
  75. package/dist/resources/index.d.ts.map +1 -0
  76. package/dist/resources/index.js +186 -0
  77. package/dist/resources/index.js.map +1 -0
  78. package/dist/resources/subscriptions.d.ts +10 -0
  79. package/dist/resources/subscriptions.d.ts.map +1 -0
  80. package/dist/resources/subscriptions.js +23 -0
  81. package/dist/resources/subscriptions.js.map +1 -0
  82. package/dist/sampling/index.d.ts +11 -0
  83. package/dist/sampling/index.d.ts.map +1 -0
  84. package/dist/sampling/index.js +201 -0
  85. package/dist/sampling/index.js.map +1 -0
  86. package/dist/sampling/prompts.d.ts +56 -0
  87. package/dist/sampling/prompts.d.ts.map +1 -0
  88. package/dist/sampling/prompts.js +124 -0
  89. package/dist/sampling/prompts.js.map +1 -0
  90. package/dist/sampling/types.d.ts +57 -0
  91. package/dist/sampling/types.d.ts.map +1 -0
  92. package/dist/sampling/types.js +2 -0
  93. package/dist/sampling/types.js.map +1 -0
  94. package/dist/schemas/config.d.ts +40 -0
  95. package/dist/schemas/config.d.ts.map +1 -0
  96. package/dist/schemas/config.js +30 -0
  97. package/dist/schemas/config.js.map +1 -0
  98. package/dist/security/index.d.ts +38 -0
  99. package/dist/security/index.d.ts.map +1 -0
  100. package/dist/security/index.js +281 -0
  101. package/dist/security/index.js.map +1 -0
  102. package/dist/server.d.ts +9 -0
  103. package/dist/server.d.ts.map +1 -0
  104. package/dist/server.js +142 -0
  105. package/dist/server.js.map +1 -0
  106. package/dist/test-utils/index.d.ts +6 -0
  107. package/dist/test-utils/index.d.ts.map +1 -0
  108. package/dist/test-utils/index.js +6 -0
  109. package/dist/test-utils/index.js.map +1 -0
  110. package/dist/test-utils/mock-context.d.ts +64 -0
  111. package/dist/test-utils/mock-context.d.ts.map +1 -0
  112. package/dist/test-utils/mock-context.js +347 -0
  113. package/dist/test-utils/mock-context.js.map +1 -0
  114. package/dist/test-utils/mock-playwright-client.d.ts +62 -0
  115. package/dist/test-utils/mock-playwright-client.d.ts.map +1 -0
  116. package/dist/test-utils/mock-playwright-client.js +315 -0
  117. package/dist/test-utils/mock-playwright-client.js.map +1 -0
  118. package/dist/tools/index.d.ts +4 -0
  119. package/dist/tools/index.d.ts.map +1 -0
  120. package/dist/tools/index.js +8 -0
  121. package/dist/tools/index.js.map +1 -0
  122. package/dist/tools/webtest/crawl.d.ts +46 -0
  123. package/dist/tools/webtest/crawl.d.ts.map +1 -0
  124. package/dist/tools/webtest/crawl.js +678 -0
  125. package/dist/tools/webtest/crawl.js.map +1 -0
  126. package/dist/tools/webtest/discover-features.d.ts +30 -0
  127. package/dist/tools/webtest/discover-features.d.ts.map +1 -0
  128. package/dist/tools/webtest/discover-features.js +343 -0
  129. package/dist/tools/webtest/discover-features.js.map +1 -0
  130. package/dist/tools/webtest/discover-flows.d.ts +29 -0
  131. package/dist/tools/webtest/discover-flows.d.ts.map +1 -0
  132. package/dist/tools/webtest/discover-flows.js +341 -0
  133. package/dist/tools/webtest/discover-flows.js.map +1 -0
  134. package/dist/tools/webtest/generate-tests.d.ts +54 -0
  135. package/dist/tools/webtest/generate-tests.d.ts.map +1 -0
  136. package/dist/tools/webtest/generate-tests.js +364 -0
  137. package/dist/tools/webtest/generate-tests.js.map +1 -0
  138. package/dist/tools/webtest/index.d.ts +8 -0
  139. package/dist/tools/webtest/index.d.ts.map +1 -0
  140. package/dist/tools/webtest/index.js +8 -0
  141. package/dist/tools/webtest/index.js.map +1 -0
  142. package/dist/tools/webtest/run-test-case.d.ts +28 -0
  143. package/dist/tools/webtest/run-test-case.d.ts.map +1 -0
  144. package/dist/tools/webtest/run-test-case.js +420 -0
  145. package/dist/tools/webtest/run-test-case.js.map +1 -0
  146. package/dist/tools/webtest/schemas.d.ts +175 -0
  147. package/dist/tools/webtest/schemas.d.ts.map +1 -0
  148. package/dist/tools/webtest/schemas.js +156 -0
  149. package/dist/tools/webtest/schemas.js.map +1 -0
  150. package/dist/tools/webtest/start-analysis.d.ts +16 -0
  151. package/dist/tools/webtest/start-analysis.d.ts.map +1 -0
  152. package/dist/tools/webtest/start-analysis.js +137 -0
  153. package/dist/tools/webtest/start-analysis.js.map +1 -0
  154. package/dist/transports/http.d.ts +8 -0
  155. package/dist/transports/http.d.ts.map +1 -0
  156. package/dist/transports/http.js +9 -0
  157. package/dist/transports/http.js.map +1 -0
  158. package/dist/transports/index.d.ts +14 -0
  159. package/dist/transports/index.d.ts.map +1 -0
  160. package/dist/transports/index.js +20 -0
  161. package/dist/transports/index.js.map +1 -0
  162. package/dist/transports/stdio.d.ts +4 -0
  163. package/dist/transports/stdio.d.ts.map +1 -0
  164. package/dist/transports/stdio.js +6 -0
  165. package/dist/transports/stdio.js.map +1 -0
  166. package/dist/types/capabilities.d.ts +18 -0
  167. package/dist/types/capabilities.d.ts.map +1 -0
  168. package/dist/types/capabilities.js +35 -0
  169. package/dist/types/capabilities.js.map +1 -0
  170. package/dist/types/context.d.ts +20 -0
  171. package/dist/types/context.d.ts.map +1 -0
  172. package/dist/types/context.js +2 -0
  173. package/dist/types/context.js.map +1 -0
  174. package/dist/types/tool.d.ts +10 -0
  175. package/dist/types/tool.d.ts.map +1 -0
  176. package/dist/types/tool.js +2 -0
  177. package/dist/types/tool.js.map +1 -0
  178. package/dist/workspace/index.d.ts +99 -0
  179. package/dist/workspace/index.d.ts.map +1 -0
  180. package/dist/workspace/index.js +648 -0
  181. package/dist/workspace/index.js.map +1 -0
  182. package/dist/workspace/markdown.d.ts +50 -0
  183. package/dist/workspace/markdown.d.ts.map +1 -0
  184. package/dist/workspace/markdown.js +210 -0
  185. package/dist/workspace/markdown.js.map +1 -0
  186. package/dist/workspace/types.d.ts +173 -0
  187. package/dist/workspace/types.d.ts.map +1 -0
  188. package/dist/workspace/types.js +2 -0
  189. package/dist/workspace/types.js.map +1 -0
  190. package/openspec/AGENTS.md +456 -0
  191. package/openspec/changes/archive/2025-12-18-add-hybrid-artifact-paths/proposal.md +33 -0
  192. package/openspec/changes/archive/2025-12-18-add-hybrid-artifact-paths/specs/webtest-resources/spec.md +27 -0
  193. package/openspec/changes/archive/2025-12-18-add-hybrid-artifact-paths/specs/webtest-tools/spec.md +304 -0
  194. package/openspec/changes/archive/2025-12-18-add-hybrid-artifact-paths/tasks.md +43 -0
  195. package/openspec/changes/archive/2025-12-18-add-mcp-server-foundation/design.md +209 -0
  196. package/openspec/changes/archive/2025-12-18-add-mcp-server-foundation/proposal.md +41 -0
  197. package/openspec/changes/archive/2025-12-18-add-mcp-server-foundation/specs/mcp-server-core/spec.md +183 -0
  198. package/openspec/changes/archive/2025-12-18-add-mcp-server-foundation/tasks.md +112 -0
  199. package/openspec/changes/archive/2025-12-18-add-webtest-orchestrator/design.md +333 -0
  200. package/openspec/changes/archive/2025-12-18-add-webtest-orchestrator/proposal.md +66 -0
  201. package/openspec/changes/archive/2025-12-18-add-webtest-orchestrator/specs/mcp-server-core/spec.md +129 -0
  202. package/openspec/changes/archive/2025-12-18-add-webtest-orchestrator/specs/webtest-lifecycle/spec.md +138 -0
  203. package/openspec/changes/archive/2025-12-18-add-webtest-orchestrator/specs/webtest-logging/spec.md +211 -0
  204. package/openspec/changes/archive/2025-12-18-add-webtest-orchestrator/specs/webtest-prompts/spec.md +157 -0
  205. package/openspec/changes/archive/2025-12-18-add-webtest-orchestrator/specs/webtest-resources/spec.md +213 -0
  206. package/openspec/changes/archive/2025-12-18-add-webtest-orchestrator/specs/webtest-sampling/spec.md +257 -0
  207. package/openspec/changes/archive/2025-12-18-add-webtest-orchestrator/specs/webtest-tools/spec.md +501 -0
  208. package/openspec/changes/archive/2025-12-18-add-webtest-orchestrator/tasks.md +264 -0
  209. package/openspec/changes/archive/2025-12-18-allow-analysis-of-incomplete-crawls/proposal.md +24 -0
  210. package/openspec/changes/archive/2025-12-18-allow-analysis-of-incomplete-crawls/specs/webtest-tools/spec.md +80 -0
  211. package/openspec/changes/archive/2025-12-18-allow-analysis-of-incomplete-crawls/tasks.md +8 -0
  212. package/openspec/changes/archive/2025-12-18-fix-crawl-loop-stability/design.md +90 -0
  213. package/openspec/changes/archive/2025-12-18-fix-crawl-loop-stability/proposal.md +28 -0
  214. package/openspec/changes/archive/2025-12-18-fix-crawl-loop-stability/specs/webtest-sampling/spec.md +90 -0
  215. package/openspec/changes/archive/2025-12-18-fix-crawl-loop-stability/tasks.md +33 -0
  216. package/openspec/changes/archive/2025-12-18-use-markdown-artifacts/design.md +558 -0
  217. package/openspec/changes/archive/2025-12-18-use-markdown-artifacts/proposal.md +119 -0
  218. package/openspec/changes/archive/2025-12-18-use-markdown-artifacts/specs/webtest-resources/spec.md +109 -0
  219. package/openspec/changes/archive/2025-12-18-use-markdown-artifacts/specs/webtest-tools/spec.md +121 -0
  220. package/openspec/changes/archive/2025-12-18-use-markdown-artifacts/tasks.md +133 -0
  221. package/openspec/changes/extract-prompts-to-markdown/design.md +86 -0
  222. package/openspec/changes/extract-prompts-to-markdown/proposal.md +50 -0
  223. package/openspec/changes/extract-prompts-to-markdown/specs/webtest-prompts/spec.md +74 -0
  224. package/openspec/changes/extract-prompts-to-markdown/tasks.md +40 -0
  225. package/openspec/changes/refactor-webtest-naming/design.md +95 -0
  226. package/openspec/changes/refactor-webtest-naming/proposal.md +66 -0
  227. package/openspec/changes/refactor-webtest-naming/specs/webtest-prompts/spec.md +79 -0
  228. package/openspec/changes/refactor-webtest-naming/specs/webtest-resources/spec.md +80 -0
  229. package/openspec/changes/refactor-webtest-naming/specs/webtest-sampling/spec.md +122 -0
  230. package/openspec/changes/refactor-webtest-naming/specs/webtest-tools/spec.md +113 -0
  231. package/openspec/changes/refactor-webtest-naming/tasks.md +119 -0
  232. package/openspec/changes/rename-package-to-retest/proposal.md +52 -0
  233. package/openspec/changes/rename-package-to-retest/specs/mcp-server-core/spec.md +53 -0
  234. package/openspec/changes/rename-package-to-retest/specs/retest-lifecycle/spec.md +68 -0
  235. package/openspec/changes/rename-package-to-retest/specs/retest-logging/spec.md +35 -0
  236. package/openspec/changes/rename-package-to-retest/specs/retest-prompts/spec.md +159 -0
  237. package/openspec/changes/rename-package-to-retest/specs/retest-resources/spec.md +251 -0
  238. package/openspec/changes/rename-package-to-retest/specs/retest-sampling/spec.md +99 -0
  239. package/openspec/changes/rename-package-to-retest/specs/retest-tools/spec.md +295 -0
  240. package/openspec/changes/rename-package-to-retest/tasks.md +71 -0
  241. package/openspec/project.md +31 -0
  242. package/openspec/specs/mcp-server-core/spec.md +178 -0
  243. package/openspec/specs/webtest-lifecycle/spec.md +136 -0
  244. package/openspec/specs/webtest-logging/spec.md +209 -0
  245. package/openspec/specs/webtest-prompts/spec.md +155 -0
  246. package/openspec/specs/webtest-resources/spec.md +248 -0
  247. package/openspec/specs/webtest-sampling/spec.md +344 -0
  248. package/openspec/specs/webtest-tools/spec.md +282 -0
  249. package/package.json +54 -0
  250. package/release.config.js +9 -0
  251. package/src/config.test.ts +96 -0
  252. package/src/config.ts +32 -0
  253. package/src/elicitation/index.test.ts +399 -0
  254. package/src/elicitation/index.ts +171 -0
  255. package/src/elicitation/types.ts +68 -0
  256. package/src/index.ts +83 -0
  257. package/src/lifecycle/index.test.ts +260 -0
  258. package/src/lifecycle/index.ts +101 -0
  259. package/src/logger.redaction.test.ts +322 -0
  260. package/src/logger.test.ts +123 -0
  261. package/src/logger.ts +229 -0
  262. package/src/playwright-client/index.ts +392 -0
  263. package/src/playwright-client/types.ts +99 -0
  264. package/src/progress/index.test.ts +327 -0
  265. package/src/progress/index.ts +170 -0
  266. package/src/progress/types.ts +25 -0
  267. package/src/prompts/index.test.ts +451 -0
  268. package/src/prompts/index.ts +246 -0
  269. package/src/prompts/loader.test.ts +100 -0
  270. package/src/prompts/loader.ts +59 -0
  271. package/src/prompts/templates/mcp/webtest-crawl.md +7 -0
  272. package/src/prompts/templates/mcp/webtest-discover-flows.md +11 -0
  273. package/src/prompts/templates/mcp/webtest-discover.md +12 -0
  274. package/src/prompts/templates/mcp/webtest-full-workflow.md +12 -0
  275. package/src/prompts/templates/mcp/webtest-generate-tests.md +11 -0
  276. package/src/prompts/templates/mcp/webtest-run-test.md +11 -0
  277. package/src/prompts/templates/mcp/webtest-start.md +8 -0
  278. package/src/prompts/templates/sampling/crawl-action.md +35 -0
  279. package/src/prompts/templates/sampling/feature-discovery.md +27 -0
  280. package/src/prompts/templates/sampling/flow-discovery.md +29 -0
  281. package/src/prompts/templates/sampling/page-content-wrapper.md +5 -0
  282. package/src/prompts/templates/sampling/system-prefix.md +12 -0
  283. package/src/prompts/templates/sampling/test-evaluation.md +17 -0
  284. package/src/prompts/templates/sampling/test-generation.md +31 -0
  285. package/src/resources/index.ts +250 -0
  286. package/src/resources/subscriptions.ts +37 -0
  287. package/src/sampling/index.test.ts +414 -0
  288. package/src/sampling/index.ts +286 -0
  289. package/src/sampling/prompts.ts +194 -0
  290. package/src/sampling/types.ts +60 -0
  291. package/src/schemas/config.ts +39 -0
  292. package/src/security/index.test.ts +441 -0
  293. package/src/security/index.ts +361 -0
  294. package/src/security/security-scenarios.test.ts +468 -0
  295. package/src/server.ts +211 -0
  296. package/src/test-utils/index.ts +6 -0
  297. package/src/test-utils/mock-context.ts +426 -0
  298. package/src/test-utils/mock-playwright-client.ts +422 -0
  299. package/src/tools/index.ts +11 -0
  300. package/src/tools/webtest/crawl.test.ts +834 -0
  301. package/src/tools/webtest/crawl.ts +901 -0
  302. package/src/tools/webtest/discover-features.ts +412 -0
  303. package/src/tools/webtest/discover-flows.ts +408 -0
  304. package/src/tools/webtest/generate-tests.test.ts +532 -0
  305. package/src/tools/webtest/generate-tests.ts +425 -0
  306. package/src/tools/webtest/index.ts +7 -0
  307. package/src/tools/webtest/integration.test.ts +536 -0
  308. package/src/tools/webtest/run-test-case.test.ts +659 -0
  309. package/src/tools/webtest/run-test-case.ts +508 -0
  310. package/src/tools/webtest/schemas.ts +201 -0
  311. package/src/tools/webtest/start-analysis.test.ts +151 -0
  312. package/src/tools/webtest/start-analysis.ts +158 -0
  313. package/src/transports/http.ts +19 -0
  314. package/src/transports/index.ts +30 -0
  315. package/src/transports/stdio.ts +7 -0
  316. package/src/types/capabilities.test.ts +193 -0
  317. package/src/types/capabilities.ts +50 -0
  318. package/src/types/context.ts +21 -0
  319. package/src/types/tool.ts +11 -0
  320. package/src/workspace/index.ts +945 -0
  321. package/src/workspace/markdown.ts +272 -0
  322. package/src/workspace/types.ts +186 -0
  323. package/tests/integration/server.test.ts +89 -0
  324. package/tests/integration/tools.test.ts +99 -0
  325. package/tsconfig.json +20 -0
  326. package/vitest.config.ts +9 -0
  327. package/vitest.integration.config.ts +10 -0
@@ -0,0 +1,408 @@
1
+ import { z } from "zod";
2
+ import type { McpTool, ToolResult } from "../../types/tool.js";
3
+ import type { ServerContext } from "../../types/context.js";
4
+ import type { WorkspaceManager } from "../../workspace/index.js";
5
+ import type { SamplingClient } from "../../sampling/index.js";
6
+ import type { ResourceManager } from "../../resources/index.js";
7
+ import { buildFlowDiscoveryPrompt } from "../../sampling/prompts.js";
8
+ import { FlowsDiscoverySchema, AnalysisIdSchema, CrawlIdSchema, FeatureSlugSchema, type FlowsDiscovery } from "./schemas.js";
9
+
10
+ export const discoverFlowsInputSchema = z.object({
11
+ analysisId: AnalysisIdSchema,
12
+ featureSlug: FeatureSlugSchema,
13
+ crawlId: CrawlIdSchema
14
+ .optional()
15
+ .describe("Specific crawl to use. If not provided, uses the most recent crawl with captured pages."),
16
+ manualDiscovery: z
17
+ .object({
18
+ featureSlug: z.string(),
19
+ flows: z.array(z.object({
20
+ id: z.string(),
21
+ name: z.string(),
22
+ description: z.string(),
23
+ entryPoint: z.string(),
24
+ steps: z.array(z.string()),
25
+ })),
26
+ suggestedAssertions: z.array(z.string()),
27
+ })
28
+ .optional()
29
+ .describe("Manual discovery result when sampling is unavailable. Provide this after executing the returned prompt."),
30
+ });
31
+
32
+ export type DiscoverFlowsInput = z.infer<typeof discoverFlowsInputSchema>;
33
+
34
+ export function createDiscoverFlowsTool(
35
+ getContext: () => ServerContext & {
36
+ workspaceManager: WorkspaceManager;
37
+ samplingClient: SamplingClient;
38
+ resourceManager: ResourceManager;
39
+ }
40
+ ): McpTool<DiscoverFlowsInput> {
41
+ return {
42
+ name: "webtest_discover_flows",
43
+ description: `Discover user flows within a specific feature.
44
+
45
+ This tool uses AI to identify user flows within a feature:
46
+ - Identifies user journeys that accomplish specific goals
47
+ - Maps entry points and step sequences for each flow
48
+ - Suggests assertions that should hold for the flows
49
+
50
+ Outputs flows.md under features/<feature-slug>/. Run webtest_discover_features first to identify features.
51
+
52
+ Requires a crawl with captured pages and a valid feature slug from features.md.`,
53
+
54
+ inputSchema: discoverFlowsInputSchema,
55
+
56
+ async handler(input: DiscoverFlowsInput): Promise<ToolResult> {
57
+ const ctx = getContext();
58
+ const { logger, workspaceManager, samplingClient, resourceManager } = ctx;
59
+
60
+ const discoveryLogger = logger.withCorrelation({
61
+ analysisId: input.analysisId,
62
+ featureSlug: input.featureSlug,
63
+ });
64
+
65
+ discoveryLogger.info("Starting flow discovery", {
66
+ featureSlug: input.featureSlug,
67
+ crawlId: input.crawlId,
68
+ });
69
+
70
+ try {
71
+ // Validate workspace exists
72
+ if (!(await workspaceManager.workspaceExists(input.analysisId))) {
73
+ return {
74
+ content: [
75
+ {
76
+ type: "text",
77
+ text: `Error: Analysis workspace "${input.analysisId}" not found.`,
78
+ },
79
+ ],
80
+ isError: true,
81
+ };
82
+ }
83
+
84
+ const workspace = await workspaceManager.readWorkspaceIndex(input.analysisId);
85
+
86
+ // Check if features have been discovered
87
+ if (!workspace.features) {
88
+ return {
89
+ content: [
90
+ {
91
+ type: "text",
92
+ text: "Error: No features found. Run webtest_discover_features first.",
93
+ },
94
+ ],
95
+ isError: true,
96
+ };
97
+ }
98
+
99
+ // Find the requested feature
100
+ const feature = workspace.features.features.find(
101
+ (f) => f.slug === input.featureSlug
102
+ );
103
+
104
+ if (!feature) {
105
+ const availableFeatures = workspace.features.features.map((f) => f.slug).join(", ");
106
+ return {
107
+ content: [
108
+ {
109
+ type: "text",
110
+ text: `Error: Feature "${input.featureSlug}" not found. Available features: ${availableFeatures}`,
111
+ },
112
+ ],
113
+ isError: true,
114
+ };
115
+ }
116
+
117
+ // Find the crawl to use
118
+ let targetCrawlId = input.crawlId;
119
+ let selectedCrawlRef: (typeof workspace.crawls)[0] | undefined;
120
+
121
+ if (!targetCrawlId) {
122
+ // Sort crawls by most recent first
123
+ const sortedCrawls = [...workspace.crawls].sort(
124
+ (a, b) =>
125
+ new Date(b.completedAt || b.startedAt).getTime() -
126
+ new Date(a.completedAt || a.startedAt).getTime()
127
+ );
128
+
129
+ const crawlsWithData = sortedCrawls.filter((c) => c.pagesVisited > 0);
130
+
131
+ if (crawlsWithData.length > 0) {
132
+ selectedCrawlRef = crawlsWithData[0];
133
+ targetCrawlId = selectedCrawlRef.crawlId;
134
+ } else if (sortedCrawls.length > 0) {
135
+ for (const crawlRef of sortedCrawls) {
136
+ try {
137
+ const crawlIndex = await workspaceManager.readCrawlIndex(
138
+ input.analysisId,
139
+ crawlRef.crawlId
140
+ );
141
+ if (crawlIndex.pages.length > 0) {
142
+ selectedCrawlRef = crawlRef;
143
+ targetCrawlId = crawlRef.crawlId;
144
+ break;
145
+ }
146
+ } catch {
147
+ // Crawl index not readable, skip
148
+ }
149
+ }
150
+ }
151
+
152
+ if (!targetCrawlId) {
153
+ return {
154
+ content: [
155
+ {
156
+ type: "text",
157
+ text: "Error: No crawls with captured data found.",
158
+ },
159
+ ],
160
+ isError: true,
161
+ };
162
+ }
163
+ } else {
164
+ selectedCrawlRef = workspace.crawls.find((c) => c.crawlId === targetCrawlId);
165
+ }
166
+
167
+ // Load crawl data
168
+ const crawlIndex = await workspaceManager.readCrawlIndex(
169
+ input.analysisId,
170
+ targetCrawlId
171
+ );
172
+
173
+ if (crawlIndex.pages.length === 0) {
174
+ return {
175
+ content: [
176
+ {
177
+ type: "text",
178
+ text: "Error: Crawl has no captured pages.",
179
+ },
180
+ ],
181
+ isError: true,
182
+ };
183
+ }
184
+
185
+ discoveryLogger.info("Discovering flows for feature", {
186
+ featureName: feature.name,
187
+ crawlId: targetCrawlId,
188
+ pageCount: crawlIndex.pages.length,
189
+ });
190
+
191
+ // Filter page snapshots relevant to the feature (by entry points)
192
+ // For now, load all pages and let the AI filter
193
+ const pageSnapshots: Array<{ url: string; content: string }> = [];
194
+
195
+ for (const page of crawlIndex.pages.slice(0, 10)) {
196
+ try {
197
+ const snapshotContent = await resourceManager.readResource(
198
+ page.snapshotUri
199
+ );
200
+ if (snapshotContent.text) {
201
+ const snapshot = JSON.parse(snapshotContent.text);
202
+ pageSnapshots.push({
203
+ url: page.url,
204
+ content: snapshot.content || "",
205
+ });
206
+ }
207
+ } catch (error) {
208
+ discoveryLogger.warn("Failed to load page snapshot", {
209
+ pageId: page.pageId,
210
+ error: error instanceof Error ? error.message : "Unknown error",
211
+ });
212
+ }
213
+ }
214
+
215
+ // Determine discovery source: manual input, sampling, or fallback prompt
216
+ let discovery: FlowsDiscovery;
217
+
218
+ if (input.manualDiscovery) {
219
+ discoveryLogger.info("Using manual discovery input");
220
+ discovery = input.manualDiscovery as FlowsDiscovery;
221
+ } else if (!samplingClient.hasSampling()) {
222
+ const prompt = buildFlowDiscoveryPrompt({
223
+ featureSlug: feature.slug,
224
+ featureName: feature.name,
225
+ featureDescription: feature.description,
226
+ featureEntities: feature.entities,
227
+ featureEntryPoints: feature.entryPoints,
228
+ pageSnapshots,
229
+ });
230
+
231
+ return {
232
+ content: [
233
+ {
234
+ type: "text",
235
+ text: JSON.stringify(
236
+ {
237
+ needsManualInput: true,
238
+ analysisId: input.analysisId,
239
+ featureSlug: input.featureSlug,
240
+ crawlId: targetCrawlId,
241
+ prompt,
242
+ expectedResponseSchema: FlowsDiscoverySchema._def,
243
+ instructions:
244
+ "Execute this prompt with your LLM, then call webtest_discover_flows again with the result in the 'manualDiscovery' parameter",
245
+ },
246
+ null,
247
+ 2
248
+ ),
249
+ },
250
+ ],
251
+ };
252
+ } else {
253
+ const samplingResult = await samplingClient.createMessage({
254
+ systemPrompt: `You are identifying user flows within the "${feature.name}" feature of a web application.
255
+ A "flow" is a user journey that accomplishes a specific goal within this feature.
256
+ Focus only on flows relevant to this specific feature.`,
257
+ userPrompt: buildFlowDiscoveryPrompt({
258
+ featureSlug: feature.slug,
259
+ featureName: feature.name,
260
+ featureDescription: feature.description,
261
+ featureEntities: feature.entities,
262
+ featureEntryPoints: feature.entryPoints,
263
+ pageSnapshots,
264
+ }),
265
+ schema: FlowsDiscoverySchema,
266
+ maxTokens: 4096,
267
+ });
268
+
269
+ if (!samplingResult.success || !samplingResult.data) {
270
+ discoveryLogger.error("Flow discovery sampling failed", {
271
+ error: samplingResult.error,
272
+ });
273
+
274
+ return {
275
+ content: [
276
+ {
277
+ type: "text",
278
+ text: `Error during flow discovery: ${samplingResult.error}`,
279
+ },
280
+ ],
281
+ isError: true,
282
+ };
283
+ }
284
+
285
+ discovery = samplingResult.data;
286
+ }
287
+
288
+ // Generate flows markdown
289
+ const markdownReport = generateFlowsMarkdown(discovery, feature);
290
+
291
+ // Build frontmatter
292
+ const frontmatter = {
293
+ featureSlug: discovery.featureSlug,
294
+ featureName: feature.name,
295
+ flows: discovery.flows,
296
+ suggestedAssertions: discovery.suggestedAssertions,
297
+ };
298
+
299
+ // Save flows
300
+ const { flowsFilePath, flowsUri, flowCount } =
301
+ await workspaceManager.saveFlows(input.analysisId, input.featureSlug, {
302
+ markdown: markdownReport,
303
+ frontmatter,
304
+ });
305
+
306
+ await resourceManager.notifyListChanged();
307
+
308
+ const result = {
309
+ analysisId: input.analysisId,
310
+ featureSlug: input.featureSlug,
311
+ crawlId: targetCrawlId,
312
+ flowsFilePath,
313
+ flowsUri,
314
+ summary: {
315
+ featureName: feature.name,
316
+ flowCount,
317
+ flows: discovery.flows.map((f) => ({
318
+ id: f.id,
319
+ name: f.name,
320
+ })),
321
+ assertionCount: discovery.suggestedAssertions.length,
322
+ },
323
+ nextSteps: [
324
+ `Use webtest_generate_tests with analysisId="${input.analysisId}" to generate test cases`,
325
+ `Review discovered flows at ${flowsUri}`,
326
+ ],
327
+ };
328
+
329
+ discoveryLogger.info("Flow discovery completed", {
330
+ featureSlug: input.featureSlug,
331
+ flowCount,
332
+ flows: discovery.flows.map((f) => f.id),
333
+ });
334
+
335
+ return {
336
+ content: [
337
+ {
338
+ type: "text",
339
+ text: JSON.stringify(result, null, 2),
340
+ },
341
+ ],
342
+ };
343
+ } catch (error) {
344
+ const message = error instanceof Error ? error.message : "Unknown error";
345
+ discoveryLogger.error("Flow discovery failed", { error: message });
346
+
347
+ return {
348
+ content: [
349
+ {
350
+ type: "text",
351
+ text: `Error during flow discovery: ${message}`,
352
+ },
353
+ ],
354
+ isError: true,
355
+ };
356
+ }
357
+ },
358
+ };
359
+ }
360
+
361
+ function generateFlowsMarkdown(
362
+ discovery: FlowsDiscovery,
363
+ feature: { name: string; slug: string; description: string }
364
+ ): string {
365
+ let md = `# User Flows: ${feature.name}
366
+
367
+ ## Feature Overview
368
+
369
+ **Slug**: \`${feature.slug}\`
370
+
371
+ ${feature.description}
372
+
373
+ ## Flows
374
+
375
+ `;
376
+
377
+ for (const flow of discovery.flows) {
378
+ md += `### ${flow.name}
379
+
380
+ **ID**: \`${flow.id}\`
381
+ **Entry Point**: ${flow.entryPoint}
382
+
383
+ ${flow.description}
384
+
385
+ **Steps**:
386
+ `;
387
+ for (let i = 0; i < flow.steps.length; i++) {
388
+ md += `${i + 1}. ${flow.steps[i]}\n`;
389
+ }
390
+ md += "\n---\n\n";
391
+ }
392
+
393
+ if (discovery.suggestedAssertions.length > 0) {
394
+ md += `## Suggested Assertions
395
+
396
+ `;
397
+ for (const assertion of discovery.suggestedAssertions) {
398
+ md += `- ${assertion}\n`;
399
+ }
400
+ md += "\n";
401
+ }
402
+
403
+ md += `---
404
+ *Generated by testing-mcp*
405
+ `;
406
+
407
+ return md;
408
+ }