e2e-ai 1.4.3 → 1.5.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.
- package/README.md +15 -19
- package/dist/cli-hjczkpxm.js +117 -0
- package/dist/cli.js +39 -25
- package/dist/mcp.js +112 -8
- package/package.json +1 -1
- package/templates/workflow.md +10 -1
- /package/agents/{init-agent.md → 0.init-agent.md} +0 -0
- /package/agents/{transcript-agent.md → 1_1.transcript-agent.md} +0 -0
- /package/agents/{scenario-agent.md → 1_2.scenario-agent.md} +0 -0
- /package/agents/{playwright-generator-agent.md → 2.playwright-generator-agent.md} +0 -0
- /package/agents/{refactor-agent.md → 3.refactor-agent.md} +0 -0
- /package/agents/{self-healing-agent.md → 4.self-healing-agent.md} +0 -0
- /package/agents/{qa-testcase-agent.md → 5.qa-testcase-agent.md} +0 -0
- /package/agents/{feature-analyzer-agent.md → 6_1.feature-analyzer-agent.md} +0 -0
- /package/agents/{scenario-planner-agent.md → 6_2.scenario-planner-agent.md} +0 -0
package/README.md
CHANGED
|
@@ -505,25 +505,21 @@ E2E_AI_API_KEY=key-... # API key for push command
|
|
|
505
505
|
|
|
506
506
|
## AI Agents
|
|
507
507
|
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
-
|
|
513
|
-
-
|
|
514
|
-
|
|
515
|
-
|
|
|
516
|
-
|
|
517
|
-
| `
|
|
518
|
-
| `
|
|
519
|
-
| `
|
|
520
|
-
| `
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
| `feature-analyzer-agent` | AST scan result | Features, workflows, components JSON | `analyze` |
|
|
524
|
-
| `scenario-planner-agent` | Features + workflows | Complete QA map with scenarios JSON | `analyze` |
|
|
525
|
-
|
|
526
|
-
You can customize agent behavior by editing the `.md` files directly. The frontmatter `model` field is the default model for that agent (overridable via `--model` or `config.llm.agentModels`).
|
|
508
|
+
Nine specialized agents live in `agents/*.md`, numbered by pipeline order. Each has a system prompt, input/output schemas, rules, and examples.
|
|
509
|
+
|
|
510
|
+
| # | File | Input | Output | Used by |
|
|
511
|
+
|---|------|-------|--------|---------|
|
|
512
|
+
| 0 | `0.init-agent` | Codebase scan | `.e2e-ai/context.md` | `init` (AI chat) |
|
|
513
|
+
| 1.1 | `1_1.transcript-agent` | codegen + transcript JSON | Structured narrative with intent mapping | `scenario` |
|
|
514
|
+
| 1.2 | `1_2.scenario-agent` | narrative + issue context | YAML test scenario | `scenario` |
|
|
515
|
+
| 2 | `2.playwright-generator-agent` | scenario + project context | `.test.ts` file | `generate` |
|
|
516
|
+
| 3 | `3.refactor-agent` | test + project context | Improved test file | `refine` |
|
|
517
|
+
| 4 | `4.self-healing-agent` | failing test + error output | Diagnosis + patched test | `heal` |
|
|
518
|
+
| 5 | `5.qa-testcase-agent` | test + scenario + issue data | QA markdown + test case JSON | `qa` |
|
|
519
|
+
| 6.1 | `6_1.feature-analyzer-agent` | AST scan result | Features, workflows, components JSON | `analyze` |
|
|
520
|
+
| 6.2 | `6_2.scenario-planner-agent` | Features + workflows | Complete QA map with scenarios JSON | `analyze` |
|
|
521
|
+
|
|
522
|
+
Agents are loaded by bare name (e.g., `loadAgent('scenario-agent')`) — the numbered prefix is resolved automatically. You can customize agent behavior by editing the `.md` files in `.e2e-ai/agents/`.
|
|
527
523
|
|
|
528
524
|
## Output Directory Structure
|
|
529
525
|
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getPackageRoot,
|
|
3
|
+
getProjectRoot
|
|
4
|
+
} from "./cli-kx32qnf3.js";
|
|
5
|
+
|
|
6
|
+
// src/agents/loadAgent.ts
|
|
7
|
+
import { readFileSync, existsSync, readdirSync } from "node:fs";
|
|
8
|
+
import { join } from "node:path";
|
|
9
|
+
function resolveAgentFile(dir, agentName) {
|
|
10
|
+
const exact = join(dir, `${agentName}.md`);
|
|
11
|
+
if (existsSync(exact))
|
|
12
|
+
return exact;
|
|
13
|
+
try {
|
|
14
|
+
const files = readdirSync(dir);
|
|
15
|
+
const suffix = `.${agentName}.md`;
|
|
16
|
+
const match = files.find((f) => f.endsWith(suffix));
|
|
17
|
+
if (match)
|
|
18
|
+
return join(dir, match);
|
|
19
|
+
} catch {}
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
function loadAgent(agentName, config) {
|
|
23
|
+
const localDir = join(getProjectRoot(), ".e2e-ai", "agents");
|
|
24
|
+
const packageDir = join(getPackageRoot(), "agents");
|
|
25
|
+
const filePath = resolveAgentFile(localDir, agentName) ?? resolveAgentFile(packageDir, agentName);
|
|
26
|
+
if (!filePath) {
|
|
27
|
+
throw new Error(`Agent file not found for "${agentName}" in ${localDir} or ${packageDir}`);
|
|
28
|
+
}
|
|
29
|
+
let content;
|
|
30
|
+
try {
|
|
31
|
+
content = readFileSync(filePath, "utf-8");
|
|
32
|
+
} catch {
|
|
33
|
+
throw new Error(`Agent file not readable: ${filePath}`);
|
|
34
|
+
}
|
|
35
|
+
const { frontmatter, body } = parseFrontmatter(content);
|
|
36
|
+
const agentConfig = extractConfig(frontmatter);
|
|
37
|
+
let systemPrompt = body;
|
|
38
|
+
if (config) {
|
|
39
|
+
const contextPath = join(getProjectRoot(), ".e2e-ai", "context.md");
|
|
40
|
+
if (existsSync(contextPath)) {
|
|
41
|
+
const projectContext = readFileSync(contextPath, "utf-8").trim();
|
|
42
|
+
if (projectContext) {
|
|
43
|
+
systemPrompt = `${body}
|
|
44
|
+
|
|
45
|
+
## Project Context
|
|
46
|
+
|
|
47
|
+
${projectContext}`;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
if (config.llm.agentModels[agentName]) {
|
|
51
|
+
agentConfig.model = config.llm.agentModels[agentName];
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
const sections = parseSections(body);
|
|
55
|
+
return {
|
|
56
|
+
name: frontmatter.agent ?? agentName,
|
|
57
|
+
systemPrompt,
|
|
58
|
+
inputSchema: sections["Input Schema"],
|
|
59
|
+
outputSchema: sections["Output Schema"],
|
|
60
|
+
rules: sections["Rules"],
|
|
61
|
+
example: sections["Example"],
|
|
62
|
+
config: agentConfig
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
function parseFrontmatter(content) {
|
|
66
|
+
const match = content.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
|
|
67
|
+
if (!match)
|
|
68
|
+
return { frontmatter: {}, body: content };
|
|
69
|
+
const frontmatter = {};
|
|
70
|
+
for (const line of match[1].split(`
|
|
71
|
+
`)) {
|
|
72
|
+
const colonIdx = line.indexOf(":");
|
|
73
|
+
if (colonIdx === -1)
|
|
74
|
+
continue;
|
|
75
|
+
const key = line.slice(0, colonIdx).trim();
|
|
76
|
+
let value = line.slice(colonIdx + 1).trim();
|
|
77
|
+
if (value.startsWith('"') && value.endsWith('"'))
|
|
78
|
+
value = value.slice(1, -1);
|
|
79
|
+
if (value === "true")
|
|
80
|
+
value = true;
|
|
81
|
+
if (value === "false")
|
|
82
|
+
value = false;
|
|
83
|
+
if (!isNaN(Number(value)) && value !== "")
|
|
84
|
+
value = Number(value);
|
|
85
|
+
frontmatter[key] = value;
|
|
86
|
+
}
|
|
87
|
+
return { frontmatter, body: match[2] };
|
|
88
|
+
}
|
|
89
|
+
function extractConfig(frontmatter) {
|
|
90
|
+
return {
|
|
91
|
+
model: frontmatter.model,
|
|
92
|
+
maxTokens: frontmatter.max_tokens ?? 4096,
|
|
93
|
+
temperature: frontmatter.temperature ?? 0.2
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
function parseSections(body) {
|
|
97
|
+
const sections = {};
|
|
98
|
+
const headingRegex = /^##\s+(.+)$/gm;
|
|
99
|
+
const headings = [];
|
|
100
|
+
let match;
|
|
101
|
+
while ((match = headingRegex.exec(body)) !== null) {
|
|
102
|
+
headings.push({ title: match[1].trim(), index: match.index });
|
|
103
|
+
}
|
|
104
|
+
const systemMatch = body.match(/^#\s+System Prompt\n([\s\S]*?)(?=\n##\s|$)/m);
|
|
105
|
+
if (systemMatch) {
|
|
106
|
+
sections["System Prompt"] = systemMatch[1].trim();
|
|
107
|
+
}
|
|
108
|
+
for (let i = 0;i < headings.length; i++) {
|
|
109
|
+
const start = headings[i].index + body.slice(headings[i].index).indexOf(`
|
|
110
|
+
`) + 1;
|
|
111
|
+
const end = i + 1 < headings.length ? headings[i + 1].index : body.length;
|
|
112
|
+
sections[headings[i].title] = body.slice(start, end).trim();
|
|
113
|
+
}
|
|
114
|
+
return sections;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export { loadAgent };
|
package/dist/cli.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
loadAgent
|
|
4
|
-
} from "./cli-
|
|
4
|
+
} from "./cli-hjczkpxm.js";
|
|
5
5
|
import {
|
|
6
6
|
getPackageRoot,
|
|
7
7
|
getProjectRoot,
|
|
@@ -9057,33 +9057,35 @@ function registerInit(program2) {
|
|
|
9057
9057
|
program2.command("init").description("Initialize e2e-ai configuration for your project").option("--non-interactive", "Skip interactive prompts, use defaults").action(async (cmdOpts) => {
|
|
9058
9058
|
const projectRoot = getProjectRoot();
|
|
9059
9059
|
const e2eDir = join13(projectRoot, ".e2e-ai");
|
|
9060
|
-
|
|
9061
|
-
const answers = cmdOpts?.nonInteractive ? getDefaultAnswers() : await askConfigQuestions();
|
|
9062
|
-
const config = buildConfigFromAnswers(answers);
|
|
9060
|
+
const nonInteractive = !!cmdOpts?.nonInteractive;
|
|
9063
9061
|
const configPath = join13(e2eDir, "config.ts");
|
|
9064
|
-
|
|
9065
|
-
|
|
9066
|
-
|
|
9067
|
-
|
|
9068
|
-
|
|
9069
|
-
|
|
9070
|
-
|
|
9071
|
-
success(`Config written: ${configPath}`);
|
|
9072
|
-
}
|
|
9062
|
+
const isReInit = fileExists(configPath);
|
|
9063
|
+
header("e2e-ai init");
|
|
9064
|
+
if (isReInit) {
|
|
9065
|
+
info(`Existing .e2e-ai/ detected — preserving config and context.
|
|
9066
|
+
`);
|
|
9067
|
+
await copyAgentsToLocal(projectRoot, nonInteractive);
|
|
9068
|
+
await copyWorkflowGuide(projectRoot, nonInteractive);
|
|
9073
9069
|
} else {
|
|
9070
|
+
const answers = nonInteractive ? getDefaultAnswers() : await askConfigQuestions();
|
|
9071
|
+
const config = buildConfigFromAnswers(answers);
|
|
9074
9072
|
writeFile(configPath, generateConfigFile(config));
|
|
9075
9073
|
success(`Config written: ${configPath}`);
|
|
9074
|
+
await copyAgentsToLocal(projectRoot, nonInteractive);
|
|
9075
|
+
await copyWorkflowGuide(projectRoot, nonInteractive);
|
|
9076
9076
|
}
|
|
9077
|
-
await copyAgentsToLocal(projectRoot, !!cmdOpts?.nonInteractive);
|
|
9078
|
-
copyWorkflowGuide(projectRoot);
|
|
9079
9077
|
console.log("");
|
|
9080
9078
|
success(`Initialization complete!
|
|
9081
9079
|
`);
|
|
9082
|
-
|
|
9083
|
-
|
|
9084
|
-
|
|
9085
|
-
|
|
9086
|
-
|
|
9080
|
+
if (!isReInit) {
|
|
9081
|
+
console.log(import_picocolors2.default.bold("Next steps:"));
|
|
9082
|
+
console.log(` 1. Use the ${import_picocolors2.default.cyan("init-agent")} in your AI tool to generate ${import_picocolors2.default.cyan(".e2e-ai/context.md")}`);
|
|
9083
|
+
console.log(` (or use the MCP server: ${import_picocolors2.default.cyan("e2e_ai_scan_codebase")} + ${import_picocolors2.default.cyan("e2e_ai_read_agent")})`);
|
|
9084
|
+
console.log(` 2. Review the generated ${import_picocolors2.default.cyan(".e2e-ai/context.md")}`);
|
|
9085
|
+
console.log(` 3. Run: ${import_picocolors2.default.cyan("e2e-ai run --key PROJ-101")}`);
|
|
9086
|
+
} else {
|
|
9087
|
+
console.log(import_picocolors2.default.dim("Config and context.md were preserved. Only agents and workflow were checked."));
|
|
9088
|
+
}
|
|
9087
9089
|
});
|
|
9088
9090
|
}
|
|
9089
9091
|
function getDefaultAnswers() {
|
|
@@ -9199,8 +9201,8 @@ async function copyAgentsToLocal(projectRoot, nonInteractive) {
|
|
|
9199
9201
|
return 0;
|
|
9200
9202
|
}
|
|
9201
9203
|
const overwrite = await dist_default4({
|
|
9202
|
-
message: `
|
|
9203
|
-
default:
|
|
9204
|
+
message: `Update agents to latest version? (${agentFiles.length} files, currently ${existingFiles.length} in .e2e-ai/agents/)`,
|
|
9205
|
+
default: true
|
|
9204
9206
|
});
|
|
9205
9207
|
if (!overwrite) {
|
|
9206
9208
|
info("Skipping agent copy");
|
|
@@ -9216,14 +9218,26 @@ async function copyAgentsToLocal(projectRoot, nonInteractive) {
|
|
|
9216
9218
|
success(`Agents copied to .e2e-ai/agents/ (${agentFiles.length} files)`);
|
|
9217
9219
|
return agentFiles.length;
|
|
9218
9220
|
}
|
|
9219
|
-
function copyWorkflowGuide(projectRoot) {
|
|
9221
|
+
async function copyWorkflowGuide(projectRoot, nonInteractive) {
|
|
9220
9222
|
const packageRoot = getPackageRoot();
|
|
9221
9223
|
const source = join13(packageRoot, "templates", "workflow.md");
|
|
9222
9224
|
const target = join13(projectRoot, ".e2e-ai", "workflow.md");
|
|
9223
9225
|
if (!existsSync2(source))
|
|
9224
9226
|
return;
|
|
9225
|
-
if (existsSync2(target))
|
|
9226
|
-
|
|
9227
|
+
if (existsSync2(target)) {
|
|
9228
|
+
if (nonInteractive) {
|
|
9229
|
+
info("Workflow guide already exists, skipping");
|
|
9230
|
+
return;
|
|
9231
|
+
}
|
|
9232
|
+
const overwrite = await dist_default4({
|
|
9233
|
+
message: "Update workflow.md to latest version?",
|
|
9234
|
+
default: true
|
|
9235
|
+
});
|
|
9236
|
+
if (!overwrite) {
|
|
9237
|
+
info("Skipping workflow guide update");
|
|
9238
|
+
return;
|
|
9239
|
+
}
|
|
9240
|
+
}
|
|
9227
9241
|
const content = readFileSync2(source, "utf-8");
|
|
9228
9242
|
writeFile(target, content);
|
|
9229
9243
|
success("Workflow guide written to .e2e-ai/workflow.md");
|
package/dist/mcp.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
loadAgent
|
|
4
|
-
} from "./cli-
|
|
4
|
+
} from "./cli-hjczkpxm.js";
|
|
5
5
|
import {
|
|
6
6
|
getPackageRoot
|
|
7
7
|
} from "./cli-kx32qnf3.js";
|
|
@@ -14856,9 +14856,9 @@ class StdioServerTransport {
|
|
|
14856
14856
|
}
|
|
14857
14857
|
|
|
14858
14858
|
// src/mcp.ts
|
|
14859
|
-
import { readFileSync as readFileSync2, existsSync as existsSync2 } from "node:fs";
|
|
14860
|
-
import { join as join2 } from "node:path";
|
|
14861
14859
|
import { execSync } from "node:child_process";
|
|
14860
|
+
import { existsSync as existsSync2, readFileSync as readFileSync2 } from "node:fs";
|
|
14861
|
+
import { join as join2 } from "node:path";
|
|
14862
14862
|
|
|
14863
14863
|
// src/utils/scan.ts
|
|
14864
14864
|
import { readdirSync, existsSync, readFileSync } from "node:fs";
|
|
@@ -14970,14 +14970,15 @@ NEVER run multiple pipeline steps at once. Each step is a separate job with its
|
|
|
14970
14970
|
## Protocol
|
|
14971
14971
|
|
|
14972
14972
|
1. **Plan first.** Call \`e2e_ai_plan_workflow\` with the user's goal. This returns a structured todo list of steps.
|
|
14973
|
-
2. **
|
|
14974
|
-
3. **
|
|
14973
|
+
2. **Check prerequisites.** The plan includes a \`ready\` boolean and \`missingPrerequisites\` array. If \`ready\` is false, show the user what's missing (API keys, config, etc.) and **wait for them to fix it** before proceeding. Do NOT attempt to execute any step while prerequisites are missing.
|
|
14974
|
+
3. **Present the plan.** Show the user the ordered step list with descriptions. Ask for confirmation or adjustments before proceeding.
|
|
14975
|
+
4. **Execute one step at a time.** For each step in the approved plan:
|
|
14975
14976
|
a. Tell the user which step you're about to run and why.
|
|
14976
14977
|
b. Call \`e2e_ai_execute_step\` with the step name and parameters.
|
|
14977
14978
|
c. Report the result to the user (success, key output, any warnings).
|
|
14978
14979
|
d. If the step fails, stop and discuss with the user before continuing.
|
|
14979
14980
|
e. Move to the next step only after the current one succeeds.
|
|
14980
|
-
|
|
14981
|
+
5. **Use subagents when available.** If your AI platform supports subagents (e.g., Claude Code Agent tool), dispatch each step as a dedicated subagent to preserve context. Each subagent should:
|
|
14981
14982
|
- Receive only the context it needs (step name, key, relevant file paths)
|
|
14982
14983
|
- Call \`e2e_ai_execute_step\` to do its work
|
|
14983
14984
|
- Return the result to the orchestrator
|
|
@@ -15095,6 +15096,87 @@ var SCANNER_PIPELINE_STEPS = [
|
|
|
15095
15096
|
}
|
|
15096
15097
|
];
|
|
15097
15098
|
var ALL_STEPS = [...TEST_PIPELINE_STEPS, ...SCANNER_PIPELINE_STEPS];
|
|
15099
|
+
var STEP_REQUIREMENTS = {
|
|
15100
|
+
record: { envVars: [] },
|
|
15101
|
+
transcribe: { envVars: [{ name: "OPENAI_API_KEY", reason: "Whisper transcription requires OpenAI API key" }] },
|
|
15102
|
+
scenario: { envVars: [
|
|
15103
|
+
{ name: "OPENAI_API_KEY", reason: "LLM calls require OpenAI API key", onlyIf: () => getProvider() === "openai" },
|
|
15104
|
+
{ name: "ANTHROPIC_API_KEY", reason: "LLM calls require Anthropic API key", onlyIf: () => getProvider() === "anthropic" }
|
|
15105
|
+
] },
|
|
15106
|
+
generate: { envVars: [
|
|
15107
|
+
{ name: "OPENAI_API_KEY", reason: "LLM calls require OpenAI API key", onlyIf: () => getProvider() === "openai" },
|
|
15108
|
+
{ name: "ANTHROPIC_API_KEY", reason: "LLM calls require Anthropic API key", onlyIf: () => getProvider() === "anthropic" }
|
|
15109
|
+
] },
|
|
15110
|
+
refine: { envVars: [
|
|
15111
|
+
{ name: "OPENAI_API_KEY", reason: "LLM calls require OpenAI API key", onlyIf: () => getProvider() === "openai" },
|
|
15112
|
+
{ name: "ANTHROPIC_API_KEY", reason: "LLM calls require Anthropic API key", onlyIf: () => getProvider() === "anthropic" }
|
|
15113
|
+
] },
|
|
15114
|
+
test: { envVars: [] },
|
|
15115
|
+
heal: { envVars: [
|
|
15116
|
+
{ name: "OPENAI_API_KEY", reason: "LLM calls require OpenAI API key", onlyIf: () => getProvider() === "openai" },
|
|
15117
|
+
{ name: "ANTHROPIC_API_KEY", reason: "LLM calls require Anthropic API key", onlyIf: () => getProvider() === "anthropic" }
|
|
15118
|
+
] },
|
|
15119
|
+
qa: { envVars: [
|
|
15120
|
+
{ name: "OPENAI_API_KEY", reason: "LLM calls require OpenAI API key", onlyIf: () => getProvider() === "openai" },
|
|
15121
|
+
{ name: "ANTHROPIC_API_KEY", reason: "LLM calls require Anthropic API key", onlyIf: () => getProvider() === "anthropic" }
|
|
15122
|
+
] },
|
|
15123
|
+
scan: { envVars: [] },
|
|
15124
|
+
analyze: { envVars: [
|
|
15125
|
+
{ name: "OPENAI_API_KEY", reason: "LLM calls require OpenAI API key", onlyIf: () => getProvider() === "openai" },
|
|
15126
|
+
{ name: "ANTHROPIC_API_KEY", reason: "LLM calls require Anthropic API key", onlyIf: () => getProvider() === "anthropic" }
|
|
15127
|
+
] },
|
|
15128
|
+
push: { envVars: [
|
|
15129
|
+
{ name: "E2E_AI_API_URL", reason: "Push requires API URL (set E2E_AI_API_URL or push.apiUrl in config)" },
|
|
15130
|
+
{ name: "E2E_AI_API_KEY", reason: "Push requires API key (set E2E_AI_API_KEY or push.apiKey in config)" }
|
|
15131
|
+
] }
|
|
15132
|
+
};
|
|
15133
|
+
function getProvider() {
|
|
15134
|
+
return process.env.AI_PROVIDER ?? "openai";
|
|
15135
|
+
}
|
|
15136
|
+
function checkPrerequisites(stepNames) {
|
|
15137
|
+
const issueMap = new Map;
|
|
15138
|
+
for (const stepName of stepNames) {
|
|
15139
|
+
const reqs = STEP_REQUIREMENTS[stepName];
|
|
15140
|
+
if (!reqs)
|
|
15141
|
+
continue;
|
|
15142
|
+
for (const envReq of reqs.envVars) {
|
|
15143
|
+
if (envReq.onlyIf && !envReq.onlyIf())
|
|
15144
|
+
continue;
|
|
15145
|
+
if (!process.env[envReq.name]) {
|
|
15146
|
+
const key = `env:${envReq.name}`;
|
|
15147
|
+
if (issueMap.has(key)) {
|
|
15148
|
+
issueMap.get(key).stepsAffected.push(stepName);
|
|
15149
|
+
} else {
|
|
15150
|
+
issueMap.set(key, {
|
|
15151
|
+
type: "env_var",
|
|
15152
|
+
name: envReq.name,
|
|
15153
|
+
reason: envReq.reason,
|
|
15154
|
+
stepsAffected: [stepName]
|
|
15155
|
+
});
|
|
15156
|
+
}
|
|
15157
|
+
}
|
|
15158
|
+
}
|
|
15159
|
+
if (reqs.files) {
|
|
15160
|
+
for (const fileReq of reqs.files) {
|
|
15161
|
+
if (!existsSync2(fileReq.path)) {
|
|
15162
|
+
const key = `file:${fileReq.path}`;
|
|
15163
|
+
if (issueMap.has(key)) {
|
|
15164
|
+
issueMap.get(key).stepsAffected.push(stepName);
|
|
15165
|
+
} else {
|
|
15166
|
+
issueMap.set(key, {
|
|
15167
|
+
type: "file",
|
|
15168
|
+
name: fileReq.label,
|
|
15169
|
+
reason: `File not found: ${fileReq.path}`,
|
|
15170
|
+
stepsAffected: [stepName]
|
|
15171
|
+
});
|
|
15172
|
+
}
|
|
15173
|
+
}
|
|
15174
|
+
}
|
|
15175
|
+
}
|
|
15176
|
+
}
|
|
15177
|
+
const missing = Array.from(issueMap.values());
|
|
15178
|
+
return { ready: missing.length === 0, missing };
|
|
15179
|
+
}
|
|
15098
15180
|
function planWorkflow(goal, options) {
|
|
15099
15181
|
const goalLower = goal.toLowerCase();
|
|
15100
15182
|
const notes = [];
|
|
@@ -15179,7 +15261,8 @@ function planWorkflow(goal, options) {
|
|
|
15179
15261
|
if (!options.key && pipeline2 === "test" && steps.length > 1) {
|
|
15180
15262
|
notes.push("No --key provided. Use --key <ISSUE-KEY> to organize files by issue.");
|
|
15181
15263
|
}
|
|
15182
|
-
|
|
15264
|
+
const prereqs = checkPrerequisites(steps.map((s) => s.name));
|
|
15265
|
+
return { goal, pipeline: pipeline2, ready: prereqs.ready, missingPrerequisites: prereqs.missing, steps, notes };
|
|
15183
15266
|
}
|
|
15184
15267
|
function executeStep(stepName, options) {
|
|
15185
15268
|
const args = [stepName];
|
|
@@ -15229,7 +15312,7 @@ ${stderr}`,
|
|
|
15229
15312
|
};
|
|
15230
15313
|
}
|
|
15231
15314
|
}
|
|
15232
|
-
var server = new McpServer({ name: "e2e-ai", version: "1.
|
|
15315
|
+
var server = new McpServer({ name: "e2e-ai", version: "1.5.0" }, { instructions: SERVER_INSTRUCTIONS });
|
|
15233
15316
|
server.registerTool("e2e_ai_scan_codebase", {
|
|
15234
15317
|
title: "Scan Codebase",
|
|
15235
15318
|
description: "Scan a project directory for test files, configs, fixtures, path aliases, and sample test content. Use this during project setup or to understand test infrastructure.",
|
|
@@ -15343,6 +15426,27 @@ server.registerTool("e2e_ai_execute_step", {
|
|
|
15343
15426
|
isError: true
|
|
15344
15427
|
};
|
|
15345
15428
|
}
|
|
15429
|
+
const prereqs = checkPrerequisites([step]);
|
|
15430
|
+
if (!prereqs.ready) {
|
|
15431
|
+
const lines = prereqs.missing.map((m) => `- ${m.type === "env_var" ? `Set ${m.name}` : m.name}: ${m.reason}`);
|
|
15432
|
+
return {
|
|
15433
|
+
content: [{
|
|
15434
|
+
type: "text",
|
|
15435
|
+
text: JSON.stringify({
|
|
15436
|
+
step,
|
|
15437
|
+
success: false,
|
|
15438
|
+
blocked: true,
|
|
15439
|
+
missingPrerequisites: prereqs.missing,
|
|
15440
|
+
message: `Cannot run "${step}" — missing prerequisites:
|
|
15441
|
+
${lines.join(`
|
|
15442
|
+
`)}
|
|
15443
|
+
|
|
15444
|
+
Ask the user to provide these before retrying.`
|
|
15445
|
+
}, null, 2)
|
|
15446
|
+
}],
|
|
15447
|
+
isError: true
|
|
15448
|
+
};
|
|
15449
|
+
}
|
|
15346
15450
|
const result = executeStep(step, { key, voice, trace, scanDir, output, extraArgs });
|
|
15347
15451
|
return {
|
|
15348
15452
|
content: [{
|
package/package.json
CHANGED
package/templates/workflow.md
CHANGED
|
@@ -232,7 +232,16 @@ After running the pipeline for `PROJ-101`:
|
|
|
232
232
|
config.ts ← your configuration
|
|
233
233
|
context.md ← project context (teach AI your conventions)
|
|
234
234
|
workflow.md ← this file
|
|
235
|
-
agents/ ← AI agent prompts (
|
|
235
|
+
agents/ ← AI agent prompts (numbered by pipeline order)
|
|
236
|
+
0.init-agent.md
|
|
237
|
+
1_1.transcript-agent.md
|
|
238
|
+
1_2.scenario-agent.md
|
|
239
|
+
2.playwright-generator-agent.md
|
|
240
|
+
3.refactor-agent.md
|
|
241
|
+
4.self-healing-agent.md
|
|
242
|
+
5.qa-testcase-agent.md
|
|
243
|
+
6_1.feature-analyzer-agent.md
|
|
244
|
+
6_2.scenario-planner-agent.md
|
|
236
245
|
PROJ-101/ ← working files (codegen, recordings)
|
|
237
246
|
|
|
238
247
|
e2e/
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|