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