@bbearai/ai-executor 0.2.0 → 0.2.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.
@@ -0,0 +1,175 @@
1
+ // src/report-generator.ts
2
+ async function generateExplorationReport(anthropic, input) {
3
+ const { projectName, featureDescription, targetUrl, actions, model } = input;
4
+ const findings = actions.filter((a) => a.category !== "normal");
5
+ const passed = actions.filter((a) => a.category === "normal");
6
+ const actionableFindings = findings.map((f) => ({
7
+ title: buildFindingTitle(f),
8
+ category: f.category,
9
+ severity: f.severity || "medium",
10
+ confidence: f.confidence,
11
+ networkRequests: f.networkRequests,
12
+ consoleErrors: f.consoleLogs.filter((l) => l.level === "error"),
13
+ domContext: f.domContext,
14
+ url: targetUrl,
15
+ route: extractRoute(targetUrl),
16
+ reproSteps: buildReproSteps(actions, f.actionNumber),
17
+ screenshotUrl: "",
18
+ // Filled in by API route after upload
19
+ actionPerformed: f.action,
20
+ expectedBehavior: "Normal application behavior",
21
+ actualBehavior: f.description
22
+ }));
23
+ const tested = passed.map((a) => ({
24
+ description: a.action,
25
+ route: extractRoute(targetUrl),
26
+ status: "passed"
27
+ }));
28
+ const notTested = detectUntestable(actions);
29
+ const summaryResponse = await anthropic.messages.create({
30
+ model,
31
+ max_tokens: 500,
32
+ messages: [
33
+ {
34
+ role: "user",
35
+ content: `Summarize this exploratory QA session in 2-3 sentences.
36
+
37
+ Feature tested: "${featureDescription}"
38
+ URL: ${targetUrl}
39
+ Actions performed: ${actions.length}
40
+ Findings: ${findings.length} (${findings.filter((f) => f.severity === "critical" || f.severity === "high").length} high/critical)
41
+ Passed checks: ${passed.length}
42
+
43
+ Finding details:
44
+ ${findings.map((f) => `- [${f.severity?.toUpperCase()}] ${f.category}: ${f.description}`).join("\n")}
45
+
46
+ Be concise and factual. Focus on what was tested and the most important findings.`
47
+ }
48
+ ]
49
+ });
50
+ const summary = summaryResponse.content[0].type === "text" ? summaryResponse.content[0].text : "Exploration complete.";
51
+ const suggestedPrompt = buildSuggestedPrompt(
52
+ featureDescription,
53
+ actionableFindings,
54
+ tested,
55
+ notTested
56
+ );
57
+ const totalDuration = actions.reduce((sum, a) => sum + a.durationMs, 0);
58
+ return {
59
+ report: {
60
+ projectName,
61
+ featureDescription,
62
+ targetUrl,
63
+ exploredAt: (/* @__PURE__ */ new Date()).toISOString(),
64
+ duration: `${Math.round(totalDuration / 1e3)}s`,
65
+ actionsUsed: actions.length,
66
+ actionBudget: actions.length,
67
+ findings: actionableFindings,
68
+ tested,
69
+ notTested,
70
+ summary,
71
+ suggestedPrompt
72
+ },
73
+ tokenUsage: {
74
+ inputTokens: summaryResponse.usage.input_tokens,
75
+ outputTokens: summaryResponse.usage.output_tokens
76
+ }
77
+ };
78
+ }
79
+ function buildFindingTitle(action) {
80
+ const prefix = {
81
+ console_error: "JS Error",
82
+ broken_interaction: "Broken",
83
+ visual_anomaly: "Visual",
84
+ input_handling: "Validation",
85
+ normal: ""
86
+ };
87
+ return `${prefix[action.category] || action.category}: ${action.description.slice(0, 80)}`;
88
+ }
89
+ function extractRoute(url) {
90
+ try {
91
+ return new URL(url).pathname;
92
+ } catch {
93
+ return url;
94
+ }
95
+ }
96
+ function buildReproSteps(allActions, targetActionNumber) {
97
+ return allActions.filter((a) => a.actionNumber <= targetActionNumber).map((a) => `${a.actionNumber}. ${a.action}`);
98
+ }
99
+ function detectUntestable(actions) {
100
+ const untestable = [];
101
+ const allText = actions.map((a) => `${a.action} ${a.description}`).join(" ").toLowerCase();
102
+ if (allText.includes("file upload") || allText.includes("drag and drop")) {
103
+ untestable.push({
104
+ description: "File upload functionality",
105
+ reason: "AI cannot interact with OS file dialogs"
106
+ });
107
+ }
108
+ if (allText.includes("captcha") || allText.includes("recaptcha")) {
109
+ untestable.push({
110
+ description: "CAPTCHA verification",
111
+ reason: "AI cannot solve CAPTCHAs"
112
+ });
113
+ }
114
+ if (allText.includes("disabled") || allText.includes("permission")) {
115
+ untestable.push({
116
+ description: "Permission-gated features",
117
+ reason: "Current auth may not have required permissions"
118
+ });
119
+ }
120
+ return untestable;
121
+ }
122
+ function buildSuggestedPrompt(featureDescription, findings, tested, notTested) {
123
+ if (findings.length === 0) {
124
+ return `Exploratory QA tested "${featureDescription}" with ${tested.length} interactions \u2014 no issues found.`;
125
+ }
126
+ let prompt = `Fix these ${findings.length} issue(s) found during exploratory QA testing of "${featureDescription}":
127
+
128
+ `;
129
+ findings.forEach((f, i) => {
130
+ prompt += `${i + 1}. [${f.severity.toUpperCase()}] ${f.title}
131
+ `;
132
+ if (f.consoleErrors.length > 0) {
133
+ prompt += ` Console: ${f.consoleErrors[0].text}
134
+ `;
135
+ if (f.consoleErrors[0].source) {
136
+ prompt += ` Source: ${f.consoleErrors[0].source}
137
+ `;
138
+ }
139
+ }
140
+ if (f.networkRequests.some((r) => r.status >= 400)) {
141
+ const failed = f.networkRequests.find((r) => r.status >= 400);
142
+ if (failed) {
143
+ prompt += ` API: ${failed.method} ${failed.url} \u2192 ${failed.status}
144
+ `;
145
+ if (failed.responseBody) {
146
+ prompt += ` Response: ${failed.responseBody.slice(0, 200)}
147
+ `;
148
+ }
149
+ }
150
+ }
151
+ if (f.domContext?.selector) {
152
+ prompt += ` Element: ${f.domContext.selector}
153
+ `;
154
+ }
155
+ prompt += ` Route: ${f.route}
156
+ `;
157
+ prompt += ` Repro: ${f.reproSteps.join(" \u2192 ")}
158
+
159
+ `;
160
+ });
161
+ if (notTested.length > 0) {
162
+ prompt += `Not tested (requires manual review):
163
+ `;
164
+ notTested.forEach((n) => {
165
+ prompt += `- ${n.description}: ${n.reason}
166
+ `;
167
+ });
168
+ }
169
+ return prompt.trim();
170
+ }
171
+
172
+ export {
173
+ generateExplorationReport
174
+ };
175
+ //# sourceMappingURL=chunk-WT22IQMS.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/report-generator.ts"],"sourcesContent":["/**\n * Exploration Report Generator\n *\n * Transforms raw exploration actions into a structured report\n * optimized for Claude Code consumption. The suggestedPrompt\n * is designed to be pasted directly into Claude Code to fix issues.\n */\n\nimport Anthropic from '@anthropic-ai/sdk';\nimport {\n ExplorationAction,\n ExplorationReport,\n ActionableFinding,\n FindingCategory,\n} from './types';\n\ninterface ReportInput {\n projectName: string;\n featureDescription: string;\n targetUrl: string;\n actions: ExplorationAction[];\n model: string;\n}\n\ninterface ReportOutput {\n report: ExplorationReport;\n tokenUsage: { inputTokens: number; outputTokens: number };\n}\n\nexport async function generateExplorationReport(\n anthropic: Anthropic,\n input: ReportInput\n): Promise<ReportOutput> {\n const { projectName, featureDescription, targetUrl, actions, model } = input;\n\n // Separate findings from normal actions\n const findings = actions.filter((a) => a.category !== 'normal');\n const passed = actions.filter((a) => a.category === 'normal');\n\n // Build actionable findings\n const actionableFindings: ActionableFinding[] = findings.map((f) => ({\n title: buildFindingTitle(f),\n category: f.category as FindingCategory,\n severity: f.severity || 'medium',\n confidence: f.confidence,\n networkRequests: f.networkRequests,\n consoleErrors: f.consoleLogs.filter((l) => l.level === 'error'),\n domContext: f.domContext,\n url: targetUrl,\n route: extractRoute(targetUrl),\n reproSteps: buildReproSteps(actions, f.actionNumber),\n screenshotUrl: '', // Filled in by API route after upload\n actionPerformed: f.action,\n expectedBehavior: 'Normal application behavior',\n actualBehavior: f.description,\n }));\n\n // Build tested items\n const tested = passed.map((a) => ({\n description: a.action,\n route: extractRoute(targetUrl),\n status: 'passed' as const,\n }));\n\n // Detect what couldn't be tested\n const notTested = detectUntestable(actions);\n\n // Generate AI summary\n const summaryResponse = await anthropic.messages.create({\n model,\n max_tokens: 500,\n messages: [\n {\n role: 'user',\n content: `Summarize this exploratory QA session in 2-3 sentences.\n\nFeature tested: \"${featureDescription}\"\nURL: ${targetUrl}\nActions performed: ${actions.length}\nFindings: ${findings.length} (${findings.filter((f) => f.severity === 'critical' || f.severity === 'high').length} high/critical)\nPassed checks: ${passed.length}\n\nFinding details:\n${findings.map((f) => `- [${f.severity?.toUpperCase()}] ${f.category}: ${f.description}`).join('\\n')}\n\nBe concise and factual. Focus on what was tested and the most important findings.`,\n },\n ],\n });\n\n const summary =\n summaryResponse.content[0].type === 'text'\n ? summaryResponse.content[0].text\n : 'Exploration complete.';\n\n // Build the Claude Code prompt\n const suggestedPrompt = buildSuggestedPrompt(\n featureDescription,\n actionableFindings,\n tested,\n notTested\n );\n\n const totalDuration = actions.reduce((sum, a) => sum + a.durationMs, 0);\n\n return {\n report: {\n projectName,\n featureDescription,\n targetUrl,\n exploredAt: new Date().toISOString(),\n duration: `${Math.round(totalDuration / 1000)}s`,\n actionsUsed: actions.length,\n actionBudget: actions.length,\n findings: actionableFindings,\n tested,\n notTested,\n summary,\n suggestedPrompt,\n },\n tokenUsage: {\n inputTokens: summaryResponse.usage.input_tokens,\n outputTokens: summaryResponse.usage.output_tokens,\n },\n };\n}\n\n// ─── Helpers ────────────────────────────────────────────────────\n\nfunction buildFindingTitle(action: ExplorationAction): string {\n const prefix: Record<string, string> = {\n console_error: 'JS Error',\n broken_interaction: 'Broken',\n visual_anomaly: 'Visual',\n input_handling: 'Validation',\n normal: '',\n };\n\n return `${prefix[action.category] || action.category}: ${action.description.slice(0, 80)}`;\n}\n\nfunction extractRoute(url: string): string {\n try {\n return new URL(url).pathname;\n } catch {\n return url;\n }\n}\n\nfunction buildReproSteps(\n allActions: ExplorationAction[],\n targetActionNumber: number\n): string[] {\n return allActions\n .filter((a) => a.actionNumber <= targetActionNumber)\n .map((a) => `${a.actionNumber}. ${a.action}`);\n}\n\nfunction detectUntestable(\n actions: ExplorationAction[]\n): { description: string; reason: string }[] {\n const untestable: { description: string; reason: string }[] = [];\n\n const allText = actions\n .map((a) => `${a.action} ${a.description}`)\n .join(' ')\n .toLowerCase();\n\n if (allText.includes('file upload') || allText.includes('drag and drop')) {\n untestable.push({\n description: 'File upload functionality',\n reason: 'AI cannot interact with OS file dialogs',\n });\n }\n if (allText.includes('captcha') || allText.includes('recaptcha')) {\n untestable.push({\n description: 'CAPTCHA verification',\n reason: 'AI cannot solve CAPTCHAs',\n });\n }\n if (allText.includes('disabled') || allText.includes('permission')) {\n untestable.push({\n description: 'Permission-gated features',\n reason: 'Current auth may not have required permissions',\n });\n }\n\n return untestable;\n}\n\nfunction buildSuggestedPrompt(\n featureDescription: string,\n findings: ActionableFinding[],\n tested: { description: string; route: string }[],\n notTested: { description: string; reason: string }[]\n): string {\n if (findings.length === 0) {\n return `Exploratory QA tested \"${featureDescription}\" with ${tested.length} interactions — no issues found.`;\n }\n\n let prompt = `Fix these ${findings.length} issue(s) found during exploratory QA testing of \"${featureDescription}\":\\n\\n`;\n\n findings.forEach((f, i) => {\n prompt += `${i + 1}. [${f.severity.toUpperCase()}] ${f.title}\\n`;\n\n if (f.consoleErrors.length > 0) {\n prompt += ` Console: ${f.consoleErrors[0].text}\\n`;\n if (f.consoleErrors[0].source) {\n prompt += ` Source: ${f.consoleErrors[0].source}\\n`;\n }\n }\n\n if (f.networkRequests.some((r) => r.status >= 400)) {\n const failed = f.networkRequests.find((r) => r.status >= 400);\n if (failed) {\n prompt += ` API: ${failed.method} ${failed.url} → ${failed.status}\\n`;\n if (failed.responseBody) {\n prompt += ` Response: ${failed.responseBody.slice(0, 200)}\\n`;\n }\n }\n }\n\n if (f.domContext?.selector) {\n prompt += ` Element: ${f.domContext.selector}\\n`;\n }\n\n prompt += ` Route: ${f.route}\\n`;\n prompt += ` Repro: ${f.reproSteps.join(' → ')}\\n\\n`;\n });\n\n if (notTested.length > 0) {\n prompt += `Not tested (requires manual review):\\n`;\n notTested.forEach((n) => {\n prompt += `- ${n.description}: ${n.reason}\\n`;\n });\n }\n\n return prompt.trim();\n}\n"],"mappings":"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","names":[]}