e2e-ai 1.2.0 → 1.4.0

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,64 @@
1
+ ---
2
+ agent: scenario-planner-agent
3
+ ---
4
+
5
+ # System Prompt
6
+
7
+ You are a QA scenario planner. You receive a QA map (features, workflows, components) and generate test scenarios for each workflow. Scenarios cover happy paths, edge cases, validation, error handling, and permission checks.
8
+
9
+ Your output completes the QA map by adding the scenarios array, producing a full QAMapV2Payload ready for use.
10
+
11
+ ## Input Schema
12
+
13
+ You receive a JSON object with:
14
+ - `features`: array - Feature definitions from feature-analyzer-agent
15
+ - `workflows`: array - Workflow definitions with steps
16
+ - `components`: array - Component definitions
17
+
18
+ ## Output Schema
19
+
20
+ Respond with JSON only (no markdown fences, no extra text). Return the complete payload including the original features/workflows/components plus a new `scenarios` array:
21
+
22
+ ```json
23
+ {
24
+ "features": [...],
25
+ "workflows": [...],
26
+ "components": [...],
27
+ "scenarios": [
28
+ {
29
+ "id": "sc:<workflow-id>:<index>",
30
+ "workflowId": "wf:<kebab>",
31
+ "featureId": "feat:<kebab>",
32
+ "name": "Descriptive scenario name",
33
+ "description": "What this scenario verifies",
34
+ "category": "happy-path|permission|validation|error|edge-case|precondition",
35
+ "preconditions": ["User is authenticated", "Data exists"],
36
+ "steps": [
37
+ {
38
+ "order": 1,
39
+ "action": "What the user does",
40
+ "expectedResult": "What should happen"
41
+ }
42
+ ],
43
+ "expectedOutcome": "Final expected state",
44
+ "componentIds": ["comp:<kebab>"],
45
+ "workflowStepIds": ["step:<workflow>:<index>"],
46
+ "priority": "critical|high|medium|low"
47
+ }
48
+ ]
49
+ }
50
+ ```
51
+
52
+ ## Rules
53
+
54
+ 1. Generate at least one happy-path scenario per workflow
55
+ 2. For CRUD workflows: test create, read, update, delete + validation failures
56
+ 3. For multi-step workflows: test complete flow + abandonment at each step
57
+ 4. For forms: test validation (empty fields, invalid input, boundary values)
58
+ 5. For workflows with conditionalBranches: generate one scenario per branch
59
+ 6. Priority mapping: happy-path critical flows = critical, validation = high, edge-cases = medium, precondition checks = low
60
+ 7. Each scenario should have 2-8 steps, each with a verifiable expectedResult
61
+ 8. Scenario names should be descriptive: "[Feature]: [what is being tested]"
62
+ 9. Link scenarios to workflow steps via workflowStepIds
63
+ 10. Aim for 3-8 scenarios per workflow depending on complexity
64
+ 11. Output valid JSON only, no markdown code fences or surrounding text
@@ -1,9 +1,5 @@
1
1
  ---
2
2
  agent: self-healing-agent
3
- version: "1.0"
4
- model: gpt-4o
5
- max_tokens: 8192
6
- temperature: 0.2
7
3
  ---
8
4
 
9
5
  # System Prompt
@@ -1,9 +1,5 @@
1
1
  ---
2
2
  agent: transcript-agent
3
- version: "1.0"
4
- model: gpt-4o
5
- max_tokens: 4096
6
- temperature: 0.2
7
3
  ---
8
4
 
9
5
  # System Prompt
@@ -0,0 +1,165 @@
1
+ import {
2
+ getPackageRoot,
3
+ getProjectRoot
4
+ } from "./cli-cqabyzv3.js";
5
+
6
+ // src/agents/loadAgent.ts
7
+ import { readFileSync, existsSync } from "node:fs";
8
+ import { join } from "node:path";
9
+ function loadAgent(agentName, config) {
10
+ const localPath = join(getProjectRoot(), ".e2e-ai", "agents", `${agentName}.md`);
11
+ const packagePath = join(getPackageRoot(), "agents", `${agentName}.md`);
12
+ const filePath = existsSync(localPath) ? localPath : packagePath;
13
+ let content;
14
+ try {
15
+ content = readFileSync(filePath, "utf-8");
16
+ } catch {
17
+ throw new Error(`Agent file not found: ${filePath}`);
18
+ }
19
+ const { frontmatter, body } = parseFrontmatter(content);
20
+ const agentConfig = extractConfig(frontmatter);
21
+ let systemPrompt = body;
22
+ if (config) {
23
+ const contextPath = join(getProjectRoot(), ".e2e-ai", "context.md");
24
+ if (existsSync(contextPath)) {
25
+ const projectContext = readFileSync(contextPath, "utf-8").trim();
26
+ if (projectContext) {
27
+ systemPrompt = `${body}
28
+
29
+ ## Project Context
30
+
31
+ ${projectContext}`;
32
+ }
33
+ }
34
+ if (config.llm.agentModels[agentName]) {
35
+ agentConfig.model = config.llm.agentModels[agentName];
36
+ }
37
+ }
38
+ const sections = parseSections(body);
39
+ return {
40
+ name: frontmatter.agent ?? agentName,
41
+ systemPrompt,
42
+ inputSchema: sections["Input Schema"],
43
+ outputSchema: sections["Output Schema"],
44
+ rules: sections["Rules"],
45
+ example: sections["Example"],
46
+ config: agentConfig
47
+ };
48
+ }
49
+ function parseFrontmatter(content) {
50
+ const match = content.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
51
+ if (!match)
52
+ return { frontmatter: {}, body: content };
53
+ const frontmatter = {};
54
+ for (const line of match[1].split(`
55
+ `)) {
56
+ const colonIdx = line.indexOf(":");
57
+ if (colonIdx === -1)
58
+ continue;
59
+ const key = line.slice(0, colonIdx).trim();
60
+ let value = line.slice(colonIdx + 1).trim();
61
+ if (value.startsWith('"') && value.endsWith('"'))
62
+ value = value.slice(1, -1);
63
+ if (value === "true")
64
+ value = true;
65
+ if (value === "false")
66
+ value = false;
67
+ if (!isNaN(Number(value)) && value !== "")
68
+ value = Number(value);
69
+ frontmatter[key] = value;
70
+ }
71
+ return { frontmatter, body: match[2] };
72
+ }
73
+ function extractConfig(frontmatter) {
74
+ return {
75
+ model: frontmatter.model,
76
+ maxTokens: frontmatter.max_tokens ?? 4096,
77
+ temperature: frontmatter.temperature ?? 0.2
78
+ };
79
+ }
80
+ function parseSections(body) {
81
+ const sections = {};
82
+ const headingRegex = /^##\s+(.+)$/gm;
83
+ const headings = [];
84
+ let match;
85
+ while ((match = headingRegex.exec(body)) !== null) {
86
+ headings.push({ title: match[1].trim(), index: match.index });
87
+ }
88
+ const systemMatch = body.match(/^#\s+System Prompt\n([\s\S]*?)(?=\n##\s|$)/m);
89
+ if (systemMatch) {
90
+ sections["System Prompt"] = systemMatch[1].trim();
91
+ }
92
+ for (let i = 0;i < headings.length; i++) {
93
+ const start = headings[i].index + body.slice(headings[i].index).indexOf(`
94
+ `) + 1;
95
+ const end = i + 1 < headings.length ? headings[i + 1].index : body.length;
96
+ sections[headings[i].title] = body.slice(start, end).trim();
97
+ }
98
+ return sections;
99
+ }
100
+
101
+ // src/utils/scan.ts
102
+ import { readdirSync, existsSync as existsSync2, readFileSync as readFileSync2 } from "node:fs";
103
+ import { join as join2, relative } from "node:path";
104
+ async function scanCodebase(root) {
105
+ const scan = {
106
+ testFiles: [],
107
+ configFiles: [],
108
+ fixtureFiles: [],
109
+ featureFiles: [],
110
+ tsconfigPaths: {},
111
+ playwrightConfig: null,
112
+ sampleTestContent: null
113
+ };
114
+ function walk(dir, depth = 0) {
115
+ if (depth > 5)
116
+ return [];
117
+ const files = [];
118
+ try {
119
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
120
+ if (entry.name.startsWith(".") || entry.name === "node_modules" || entry.name === "dist")
121
+ continue;
122
+ const full = join2(dir, entry.name);
123
+ if (entry.isDirectory()) {
124
+ files.push(...walk(full, depth + 1));
125
+ } else {
126
+ files.push(full);
127
+ }
128
+ }
129
+ } catch {}
130
+ return files;
131
+ }
132
+ const allFiles = walk(root);
133
+ for (const file of allFiles) {
134
+ const rel = relative(root, file);
135
+ if (rel.endsWith(".test.ts") || rel.endsWith(".spec.ts")) {
136
+ scan.testFiles.push(rel);
137
+ if (!scan.sampleTestContent && scan.testFiles.length <= 3) {
138
+ try {
139
+ scan.sampleTestContent = readFileSync2(file, "utf-8").slice(0, 3000);
140
+ } catch {}
141
+ }
142
+ }
143
+ if (rel.endsWith(".feature.ts"))
144
+ scan.featureFiles.push(rel);
145
+ if (rel.includes("fixture") && rel.endsWith(".ts"))
146
+ scan.fixtureFiles.push(rel);
147
+ if (rel === "playwright.config.ts" || rel === "playwright.config.js")
148
+ scan.playwrightConfig = rel;
149
+ if (rel === "tsconfig.json" || rel.endsWith("/tsconfig.json")) {
150
+ try {
151
+ const tsconfig = JSON.parse(readFileSync2(file, "utf-8"));
152
+ if (tsconfig.compilerOptions?.paths) {
153
+ scan.tsconfigPaths = { ...scan.tsconfigPaths, ...tsconfig.compilerOptions.paths };
154
+ }
155
+ } catch {}
156
+ }
157
+ }
158
+ for (const name of ["playwright.config.ts", "vitest.config.ts", "jest.config.ts", "tsconfig.json", "package.json"]) {
159
+ if (existsSync2(join2(root, name)))
160
+ scan.configFiles.push(name);
161
+ }
162
+ return scan;
163
+ }
164
+
165
+ export { loadAgent, scanCodebase };
@@ -0,0 +1,101 @@
1
+ import {
2
+ getPackageRoot,
3
+ getProjectRoot
4
+ } from "./cli-kx32qnf3.js";
5
+
6
+ // src/agents/loadAgent.ts
7
+ import { readFileSync, existsSync } from "node:fs";
8
+ import { join } from "node:path";
9
+ function loadAgent(agentName, config) {
10
+ const localPath = join(getProjectRoot(), ".e2e-ai", "agents", `${agentName}.md`);
11
+ const packagePath = join(getPackageRoot(), "agents", `${agentName}.md`);
12
+ const filePath = existsSync(localPath) ? localPath : packagePath;
13
+ let content;
14
+ try {
15
+ content = readFileSync(filePath, "utf-8");
16
+ } catch {
17
+ throw new Error(`Agent file not found: ${filePath}`);
18
+ }
19
+ const { frontmatter, body } = parseFrontmatter(content);
20
+ const agentConfig = extractConfig(frontmatter);
21
+ let systemPrompt = body;
22
+ if (config) {
23
+ const contextPath = join(getProjectRoot(), ".e2e-ai", "context.md");
24
+ if (existsSync(contextPath)) {
25
+ const projectContext = readFileSync(contextPath, "utf-8").trim();
26
+ if (projectContext) {
27
+ systemPrompt = `${body}
28
+
29
+ ## Project Context
30
+
31
+ ${projectContext}`;
32
+ }
33
+ }
34
+ if (config.llm.agentModels[agentName]) {
35
+ agentConfig.model = config.llm.agentModels[agentName];
36
+ }
37
+ }
38
+ const sections = parseSections(body);
39
+ return {
40
+ name: frontmatter.agent ?? agentName,
41
+ systemPrompt,
42
+ inputSchema: sections["Input Schema"],
43
+ outputSchema: sections["Output Schema"],
44
+ rules: sections["Rules"],
45
+ example: sections["Example"],
46
+ config: agentConfig
47
+ };
48
+ }
49
+ function parseFrontmatter(content) {
50
+ const match = content.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
51
+ if (!match)
52
+ return { frontmatter: {}, body: content };
53
+ const frontmatter = {};
54
+ for (const line of match[1].split(`
55
+ `)) {
56
+ const colonIdx = line.indexOf(":");
57
+ if (colonIdx === -1)
58
+ continue;
59
+ const key = line.slice(0, colonIdx).trim();
60
+ let value = line.slice(colonIdx + 1).trim();
61
+ if (value.startsWith('"') && value.endsWith('"'))
62
+ value = value.slice(1, -1);
63
+ if (value === "true")
64
+ value = true;
65
+ if (value === "false")
66
+ value = false;
67
+ if (!isNaN(Number(value)) && value !== "")
68
+ value = Number(value);
69
+ frontmatter[key] = value;
70
+ }
71
+ return { frontmatter, body: match[2] };
72
+ }
73
+ function extractConfig(frontmatter) {
74
+ return {
75
+ model: frontmatter.model,
76
+ maxTokens: frontmatter.max_tokens ?? 4096,
77
+ temperature: frontmatter.temperature ?? 0.2
78
+ };
79
+ }
80
+ function parseSections(body) {
81
+ const sections = {};
82
+ const headingRegex = /^##\s+(.+)$/gm;
83
+ const headings = [];
84
+ let match;
85
+ while ((match = headingRegex.exec(body)) !== null) {
86
+ headings.push({ title: match[1].trim(), index: match.index });
87
+ }
88
+ const systemMatch = body.match(/^#\s+System Prompt\n([\s\S]*?)(?=\n##\s|$)/m);
89
+ if (systemMatch) {
90
+ sections["System Prompt"] = systemMatch[1].trim();
91
+ }
92
+ for (let i = 0;i < headings.length; i++) {
93
+ const start = headings[i].index + body.slice(headings[i].index).indexOf(`
94
+ `) + 1;
95
+ const end = i + 1 < headings.length ? headings[i + 1].index : body.length;
96
+ sections[headings[i].title] = body.slice(start, end).trim();
97
+ }
98
+ return sections;
99
+ }
100
+
101
+ export { loadAgent };
@@ -0,0 +1,64 @@
1
+ import {
2
+ E2eAiConfigSchema
3
+ } from "./cli-fgp618yt.js";
4
+
5
+ // src/config/loader.ts
6
+ import { existsSync } from "node:fs";
7
+ import { dirname, join, resolve } from "node:path";
8
+ import { pathToFileURL } from "node:url";
9
+ var CONFIG_FILENAMES = ["e2e-ai.config.ts", "e2e-ai.config.js", "e2e-ai.config.mjs"];
10
+ var cachedConfig = null;
11
+ var cachedProjectRoot = null;
12
+ function findConfigDir(startDir) {
13
+ let dir = resolve(startDir);
14
+ const root = dirname(dir) === dir ? dir : undefined;
15
+ while (true) {
16
+ for (const name of CONFIG_FILENAMES) {
17
+ if (existsSync(join(dir, name))) {
18
+ return dir;
19
+ }
20
+ }
21
+ const parent = dirname(dir);
22
+ if (parent === dir || dir === root)
23
+ return null;
24
+ dir = parent;
25
+ }
26
+ }
27
+ function getProjectRoot() {
28
+ if (cachedProjectRoot)
29
+ return cachedProjectRoot;
30
+ const found = findConfigDir(process.cwd());
31
+ cachedProjectRoot = found ?? process.cwd();
32
+ return cachedProjectRoot;
33
+ }
34
+ function getPackageRoot() {
35
+ let dir = import.meta.dirname;
36
+ while (!existsSync(join(dir, "package.json"))) {
37
+ const parent = dirname(dir);
38
+ if (parent === dir)
39
+ return dir;
40
+ dir = parent;
41
+ }
42
+ return dir;
43
+ }
44
+ async function loadConfig() {
45
+ if (cachedConfig)
46
+ return cachedConfig;
47
+ const projectRoot = getProjectRoot();
48
+ let userConfig = {};
49
+ for (const name of CONFIG_FILENAMES) {
50
+ const configPath = join(projectRoot, name);
51
+ if (existsSync(configPath)) {
52
+ try {
53
+ const fileUrl = pathToFileURL(configPath).href;
54
+ const mod = await import(fileUrl);
55
+ userConfig = mod.default ?? mod;
56
+ break;
57
+ } catch {}
58
+ }
59
+ }
60
+ cachedConfig = E2eAiConfigSchema.parse(userConfig);
61
+ return cachedConfig;
62
+ }
63
+
64
+ export { getProjectRoot, getPackageRoot, loadConfig };