e2e-ai 1.1.2 → 1.2.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
+ import {
2
+ E2eAiConfigSchema
3
+ } from "./cli-ph82pe4b.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 };
@@ -0,0 +1,165 @@
1
+ import {
2
+ getPackageRoot,
3
+ getProjectRoot
4
+ } from "./cli-ba9d3pdp.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 };