@yasserkhanorg/e2e-agents 1.8.2 → 1.8.3

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.
@@ -1 +1 @@
1
- {"version":3,"file":"plan.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/plan.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,uBAAuB,CAAC;AAMzD,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,aAAa,CAAC;AAE5C,wBAAsB,cAAc,CAAC,IAAI,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,GAAG,SAAS,EAAE,MAAM,EAAE,UAAU,CAAC,OAAO,aAAa,CAAC,CAAC,QAAQ,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CA2GxJ"}
1
+ {"version":3,"file":"plan.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/plan.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,uBAAuB,CAAC;AAMzD,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,aAAa,CAAC;AAE5C,wBAAsB,cAAc,CAAC,IAAI,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,GAAG,SAAS,EAAE,MAAM,EAAE,UAAU,CAAC,OAAO,aAAa,CAAC,CAAC,QAAQ,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CA+GxJ"}
@@ -53,6 +53,7 @@ async function runPlanCommand(args, autoConfig, config) {
53
53
  let combinedSummaryMarkdown = ciSummaryMarkdown;
54
54
  let crewSummaryPath = '';
55
55
  let crewMarkdownPath = '';
56
+ let crewTestPlanPath = '';
56
57
  if (args.crew) {
57
58
  try {
58
59
  const crew = await (0, plan_crew_js_1.runPlanCrewAnalysis)(plan, config, args);
@@ -60,10 +61,11 @@ async function runPlanCommand(args, autoConfig, config) {
60
61
  ...plan,
61
62
  crew,
62
63
  };
63
- combinedSummaryMarkdown = (0, plan_crew_js_1.appendCrewToSummary)(ciSummaryMarkdown, crew);
64
- const artifacts = (0, plan_crew_js_1.writeCrewArtifacts)(reportRoot, crew);
64
+ combinedSummaryMarkdown = (0, plan_crew_js_1.appendCrewToSummary)(ciSummaryMarkdown, crew, plan);
65
+ const artifacts = (0, plan_crew_js_1.writeCrewArtifacts)(reportRoot, crew, plan);
65
66
  crewSummaryPath = artifacts.crewSummaryPath;
66
67
  crewMarkdownPath = artifacts.crewMarkdownPath;
68
+ crewTestPlanPath = artifacts.crewTestPlanPath;
67
69
  (0, fs_1.writeFileSync)(planPath, JSON.stringify(planReport, null, 2), 'utf-8');
68
70
  }
69
71
  catch (error) {
@@ -95,6 +97,7 @@ async function runPlanCommand(args, autoConfig, config) {
95
97
  (0, fs_1.appendFileSync)(ghaOutput, `crew_workflow=${planReport.crew?.workflow || ''}\n`);
96
98
  (0, fs_1.appendFileSync)(ghaOutput, `crew_summary_path=${crewSummaryPath}\n`);
97
99
  (0, fs_1.appendFileSync)(ghaOutput, `crew_markdown_path=${crewMarkdownPath}\n`);
100
+ (0, fs_1.appendFileSync)(ghaOutput, `crew_test_plan_path=${crewTestPlanPath}\n`);
98
101
  (0, fs_1.appendFileSync)(ghaOutput, `crew_impacted_flows=${planReport.crew?.summary.impactedFlows || 0}\n`);
99
102
  (0, fs_1.appendFileSync)(ghaOutput, `crew_strategy_entries=${planReport.crew?.summary.strategyEntries || 0}\n`);
100
103
  (0, fs_1.appendFileSync)(ghaOutput, `crew_test_designs=${planReport.crew?.summary.testDesigns || 0}\n`);
@@ -107,6 +110,7 @@ async function runPlanCommand(args, autoConfig, config) {
107
110
  if (planReport.crew) {
108
111
  console.log(`Crew workflow: ${planReport.crew.workflow} (impactedFlows=${planReport.crew.summary.impactedFlows}, strategyEntries=${planReport.crew.summary.strategyEntries}, testDesigns=${planReport.crew.summary.testDesigns})`);
109
112
  console.log(`Crew summary: ${crewSummaryPath}`);
113
+ console.log(`Crew test plan: ${crewTestPlanPath}`);
110
114
  }
111
115
  console.log(`Plan metrics: ${metricsSummaryPath}`);
112
116
  const failOnLegacyFlag = args.failOnMustAddTests && planReport.decision.action === 'must-add-tests';
@@ -2,10 +2,16 @@ import type { AgentConfig } from '../../agent/config.js';
2
2
  import type { CrewPlanInsights, PlanReport } from '../../agent/plan.js';
3
3
  import type { ParsedArgs } from '../types.js';
4
4
  export declare function runPlanCrewAnalysis(plan: PlanReport, config: AgentConfig, args: ParsedArgs): Promise<CrewPlanInsights>;
5
- export declare function buildCrewMarkdown(crew: CrewPlanInsights): string;
6
- export declare function appendCrewToSummary(baseMarkdown: string, crew: CrewPlanInsights): string;
7
- export declare function writeCrewArtifacts(reportRoot: string, crew: CrewPlanInsights): {
5
+ export declare function buildCrewMarkdown(crew: CrewPlanInsights, plan?: PlanReport): string;
6
+ export declare function appendCrewToSummary(baseMarkdown: string, crew: CrewPlanInsights, plan?: PlanReport): string;
7
+ /**
8
+ * Build a full structured test plan as a Markdown document.
9
+ * Sections: gap flows first (P0 cases expanded), then covered-flow smoke tests.
10
+ */
11
+ export declare function buildCrewTestPlan(crew: CrewPlanInsights, plan?: PlanReport): string;
12
+ export declare function writeCrewArtifacts(reportRoot: string, crew: CrewPlanInsights, plan?: PlanReport): {
8
13
  crewSummaryPath: string;
9
14
  crewMarkdownPath: string;
15
+ crewTestPlanPath: string;
10
16
  };
11
17
  //# sourceMappingURL=plan_crew.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"plan_crew.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/plan_crew.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,uBAAuB,CAAC;AACvD,OAAO,KAAK,EAAC,gBAAgB,EAAE,UAAU,EAAC,MAAM,qBAAqB,CAAC;AAStE,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,aAAa,CAAC;AAgC5C,wBAAsB,mBAAmB,CAAC,IAAI,EAAE,UAAU,EAAE,MAAM,EAAE,WAAW,EAAE,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAkD5H;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,gBAAgB,GAAG,MAAM,CAwDhE;AAED,wBAAgB,mBAAmB,CAAC,YAAY,EAAE,MAAM,EAAE,IAAI,EAAE,gBAAgB,GAAG,MAAM,CAExF;AAED,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,gBAAgB,GAAG;IAAC,eAAe,EAAE,MAAM,CAAC;IAAC,gBAAgB,EAAE,MAAM,CAAA;CAAC,CAWlI"}
1
+ {"version":3,"file":"plan_crew.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/plan_crew.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,uBAAuB,CAAC;AACvD,OAAO,KAAK,EAAC,gBAAgB,EAAE,UAAU,EAAC,MAAM,qBAAqB,CAAC;AAUtE,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,aAAa,CAAC;AAgC5C,wBAAsB,mBAAmB,CAAC,IAAI,EAAE,UAAU,EAAE,MAAM,EAAE,WAAW,EAAE,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAkD5H;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,gBAAgB,EAAE,IAAI,CAAC,EAAE,UAAU,GAAG,MAAM,CAuEnF;AAED,wBAAgB,mBAAmB,CAAC,YAAY,EAAE,MAAM,EAAE,IAAI,EAAE,gBAAgB,EAAE,IAAI,CAAC,EAAE,UAAU,GAAG,MAAM,CAE3G;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,gBAAgB,EAAE,IAAI,CAAC,EAAE,UAAU,GAAG,MAAM,CAuHnF;AAED,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,gBAAgB,EAAE,IAAI,CAAC,EAAE,UAAU,GAAG;IAAC,eAAe,EAAE,MAAM,CAAC;IAAC,gBAAgB,EAAE,MAAM,CAAC;IAAC,gBAAgB,EAAE,MAAM,CAAA;CAAC,CAa/K"}
@@ -5,6 +5,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
5
5
  exports.runPlanCrewAnalysis = runPlanCrewAnalysis;
6
6
  exports.buildCrewMarkdown = buildCrewMarkdown;
7
7
  exports.appendCrewToSummary = appendCrewToSummary;
8
+ exports.buildCrewTestPlan = buildCrewTestPlan;
8
9
  exports.writeCrewArtifacts = writeCrewArtifacts;
9
10
  const fs_1 = require("fs");
10
11
  const path_1 = require("path");
@@ -84,19 +85,25 @@ async function runPlanCrewAnalysis(plan, config, args) {
84
85
  timings: result.timings,
85
86
  };
86
87
  }
87
- function buildCrewMarkdown(crew) {
88
+ function buildCrewMarkdown(crew, plan) {
89
+ const totalCases = crew.testDesigns.reduce((n, td) => n + td.testCases.length, 0);
90
+ const gapFamilies = new Set((plan?.gapDetails ?? []).map((g) => g.id));
91
+ const gapDesigns = gapFamilies.size > 0
92
+ ? crew.testDesigns.filter((td) => Array.from(gapFamilies).some((fam) => td.flowId.startsWith(fam) || td.flowName.toLowerCase().includes(fam.replace(/_/g, ' '))))
93
+ : [];
94
+ const gapCases = gapDesigns.reduce((n, td) => n + td.testCases.length, 0);
95
+ const gapP0Cases = gapDesigns.reduce((n, td) => n + td.testCases.filter((tc) => tc.priority === 'P0').length, 0);
88
96
  const lines = [
89
97
  '### Crew Insights',
90
98
  '',
91
99
  `Workflow: \`${crew.workflow}\``,
92
- `Provider override: \`${crew.providerOverride}\``,
93
100
  `Impacted flows: **${crew.summary.impactedFlows}**`,
94
- `Strategy entries: **${crew.summary.strategyEntries}**`,
95
- `Structured test designs: **${crew.summary.testDesigns}**`,
96
- `Cross-impacts: **${crew.summary.crossImpacts}** (${crew.summary.highRiskCrossImpacts} high risk)`,
97
- `Findings: **${crew.summary.findings}**`,
98
- `Estimated AI cost: **$${crew.summary.totalCostUSD.toFixed(4)}**`,
101
+ `Structured test designs: **${crew.summary.testDesigns}** flows, **${totalCases}** test cases`,
99
102
  ];
103
+ if (gapFamilies.size > 0 && gapDesigns.length > 0) {
104
+ lines.push(`Gap-focused: **${gapDesigns.length}** flows, **${gapCases}** test cases (**${gapP0Cases}** P0)`);
105
+ }
106
+ lines.push(`Cross-impacts: **${crew.summary.crossImpacts}** (${crew.summary.highRiskCrossImpacts} high risk)`, `Estimated AI cost: **$${crew.summary.totalCostUSD.toFixed(4)}**`);
100
107
  if (crew.strategyEntries.length > 0) {
101
108
  lines.push('');
102
109
  lines.push('Top Strategy Recommendations:');
@@ -135,15 +142,132 @@ function buildCrewMarkdown(crew) {
135
142
  }
136
143
  return lines.join('\n');
137
144
  }
138
- function appendCrewToSummary(baseMarkdown, crew) {
139
- return `${baseMarkdown}\n\n---\n\n${buildCrewMarkdown(crew)}`;
145
+ function appendCrewToSummary(baseMarkdown, crew, plan) {
146
+ return `${baseMarkdown}\n\n---\n\n${buildCrewMarkdown(crew, plan)}`;
147
+ }
148
+ /**
149
+ * Build a full structured test plan as a Markdown document.
150
+ * Sections: gap flows first (P0 cases expanded), then covered-flow smoke tests.
151
+ */
152
+ function buildCrewTestPlan(crew, plan) {
153
+ const gapFamilies = new Set((plan?.gapDetails ?? []).map((g) => g.id));
154
+ const totalCases = crew.testDesigns.reduce((n, td) => n + td.testCases.length, 0);
155
+ // Split designs into gap-related and coverage-expansion
156
+ const gapDesigns = [];
157
+ const coveredDesigns = [];
158
+ for (const td of crew.testDesigns) {
159
+ // Match by flowId prefix against gap family ids
160
+ const isGap = Array.from(gapFamilies).some((fam) => td.flowId.startsWith(fam) || td.flowName.toLowerCase().includes(fam.replace(/_/g, ' ')));
161
+ if (isGap) {
162
+ gapDesigns.push(td);
163
+ }
164
+ else {
165
+ coveredDesigns.push(td);
166
+ }
167
+ }
168
+ const gapCases = gapDesigns.reduce((n, td) => n + td.testCases.length, 0);
169
+ const coveredCases = coveredDesigns.reduce((n, td) => n + td.testCases.length, 0);
170
+ const lines = [
171
+ '# Crew Test Plan',
172
+ '',
173
+ `> Auto-generated by e2e-agents crew (\`${crew.workflow}\` workflow)`,
174
+ '',
175
+ '## Summary',
176
+ '',
177
+ `| Metric | Count |`,
178
+ `|--------|-------|`,
179
+ `| Gap flows (missing tests) | ${gapDesigns.length} flows, **${gapCases} test cases** |`,
180
+ `| Covered flows (expansion) | ${coveredDesigns.length} flows, ${coveredCases} test cases |`,
181
+ `| Total | ${crew.testDesigns.length} flows, ${totalCases} test cases |`,
182
+ `| High-risk cross-impacts | ${crew.summary.highRiskCrossImpacts} |`,
183
+ `| AI cost | $${crew.summary.totalCostUSD.toFixed(4)} |`,
184
+ '',
185
+ ];
186
+ // Priority action items
187
+ if (gapDesigns.length > 0) {
188
+ lines.push('## Priority: Gap Flows (Missing Tests)');
189
+ lines.push('');
190
+ lines.push('These flows have **no existing E2E coverage** and should be addressed first.');
191
+ lines.push('');
192
+ for (const td of gapDesigns) {
193
+ const strategy = crew.strategyEntries.find((s) => s.flowId === td.flowId);
194
+ const approach = strategy?.approach ?? 'full-test';
195
+ const risk = strategy?.crossImpactRisk ?? 'unknown';
196
+ const p0Cases = td.testCases.filter((tc) => tc.priority === 'P0');
197
+ const p1Cases = td.testCases.filter((tc) => tc.priority === 'P1');
198
+ lines.push(`### ${td.flowName}`);
199
+ lines.push('');
200
+ lines.push(`Strategy: **${approach}** | Cross-impact risk: **${risk}** | ${td.testCases.length} cases (${p0Cases.length} P0, ${p1Cases.length} P1)`);
201
+ lines.push('');
202
+ // Show P0 cases expanded
203
+ if (p0Cases.length > 0) {
204
+ lines.push('**P0 — Must test:**');
205
+ lines.push('');
206
+ for (const tc of p0Cases) {
207
+ lines.push(`- [ ] **${tc.name}** (${tc.type})`);
208
+ if (tc.preconditions.length > 0) {
209
+ lines.push(` - Preconditions: ${tc.preconditions.join('; ')}`);
210
+ }
211
+ lines.push(` - Steps: ${tc.steps.join(' → ')}`);
212
+ lines.push(` - Expected: ${tc.expectedOutcome}`);
213
+ }
214
+ lines.push('');
215
+ }
216
+ // Show P1 as a collapsed checklist
217
+ if (p1Cases.length > 0) {
218
+ lines.push(`<details><summary>P1 — Should test (${p1Cases.length})</summary>`);
219
+ lines.push('');
220
+ for (const tc of p1Cases) {
221
+ lines.push(`- [ ] **${tc.name}** (${tc.type}) — ${tc.expectedOutcome}`);
222
+ }
223
+ lines.push('');
224
+ lines.push('</details>');
225
+ lines.push('');
226
+ }
227
+ }
228
+ }
229
+ // Covered flow expansion — collapsed by default
230
+ if (coveredDesigns.length > 0) {
231
+ lines.push('## Covered Flows (Regression / Expansion)');
232
+ lines.push('');
233
+ lines.push('These flows already have specs. The test cases below extend coverage for changes in this PR.');
234
+ lines.push('');
235
+ for (const td of coveredDesigns) {
236
+ const strategy = crew.strategyEntries.find((s) => s.flowId === td.flowId);
237
+ const approach = strategy?.approach ?? 'smoke-test';
238
+ const p0Count = td.testCases.filter((tc) => tc.priority === 'P0').length;
239
+ lines.push(`<details><summary><strong>${td.flowName}</strong> — ${approach} | ${td.testCases.length} cases (${p0Count} P0)</summary>`);
240
+ lines.push('');
241
+ for (const tc of td.testCases) {
242
+ lines.push(`- [ ] **${tc.name}** (${tc.priority}, ${tc.type}) — ${tc.expectedOutcome}`);
243
+ }
244
+ lines.push('');
245
+ lines.push('</details>');
246
+ lines.push('');
247
+ }
248
+ }
249
+ // Cross-impacts section
250
+ const highRisk = crew.crossImpacts.filter((ci) => ci.riskLevel === 'high');
251
+ if (highRisk.length > 0) {
252
+ lines.push('## High-Risk Cross-Impacts');
253
+ lines.push('');
254
+ lines.push('These cross-family dependencies should be verified during release testing:');
255
+ lines.push('');
256
+ for (const ci of highRisk) {
257
+ lines.push(`- **${ci.sourceFamily}** → **${ci.affectedFamily}**: ${ci.sharedDependency}`);
258
+ }
259
+ lines.push('');
260
+ }
261
+ return lines.join('\n');
140
262
  }
141
- function writeCrewArtifacts(reportRoot, crew) {
263
+ function writeCrewArtifacts(reportRoot, crew, plan) {
142
264
  const outputDir = (0, path_1.join)(reportRoot, '.e2e-ai-agents');
143
265
  (0, fs_1.mkdirSync)(outputDir, { recursive: true });
144
266
  const crewSummaryPath = (0, path_1.join)(outputDir, 'crew-summary.json');
145
267
  const crewMarkdownPath = (0, path_1.join)(outputDir, 'crew-summary.md');
268
+ const crewTestPlanPath = (0, path_1.join)(outputDir, 'crew-test-plan.md');
146
269
  (0, fs_1.writeFileSync)(crewSummaryPath, JSON.stringify(crew, null, 2), 'utf-8');
147
- (0, fs_1.writeFileSync)(crewMarkdownPath, buildCrewMarkdown(crew), 'utf-8');
148
- return { crewSummaryPath, crewMarkdownPath };
270
+ (0, fs_1.writeFileSync)(crewMarkdownPath, buildCrewMarkdown(crew, plan), 'utf-8');
271
+ (0, fs_1.writeFileSync)(crewTestPlanPath, buildCrewTestPlan(crew, plan), 'utf-8');
272
+ return { crewSummaryPath, crewMarkdownPath, crewTestPlanPath };
149
273
  }
@@ -50,6 +50,7 @@ export async function runPlanCommand(args, autoConfig, config) {
50
50
  let combinedSummaryMarkdown = ciSummaryMarkdown;
51
51
  let crewSummaryPath = '';
52
52
  let crewMarkdownPath = '';
53
+ let crewTestPlanPath = '';
53
54
  if (args.crew) {
54
55
  try {
55
56
  const crew = await runPlanCrewAnalysis(plan, config, args);
@@ -57,10 +58,11 @@ export async function runPlanCommand(args, autoConfig, config) {
57
58
  ...plan,
58
59
  crew,
59
60
  };
60
- combinedSummaryMarkdown = appendCrewToSummary(ciSummaryMarkdown, crew);
61
- const artifacts = writeCrewArtifacts(reportRoot, crew);
61
+ combinedSummaryMarkdown = appendCrewToSummary(ciSummaryMarkdown, crew, plan);
62
+ const artifacts = writeCrewArtifacts(reportRoot, crew, plan);
62
63
  crewSummaryPath = artifacts.crewSummaryPath;
63
64
  crewMarkdownPath = artifacts.crewMarkdownPath;
65
+ crewTestPlanPath = artifacts.crewTestPlanPath;
64
66
  writeFileSync(planPath, JSON.stringify(planReport, null, 2), 'utf-8');
65
67
  }
66
68
  catch (error) {
@@ -92,6 +94,7 @@ export async function runPlanCommand(args, autoConfig, config) {
92
94
  appendFileSync(ghaOutput, `crew_workflow=${planReport.crew?.workflow || ''}\n`);
93
95
  appendFileSync(ghaOutput, `crew_summary_path=${crewSummaryPath}\n`);
94
96
  appendFileSync(ghaOutput, `crew_markdown_path=${crewMarkdownPath}\n`);
97
+ appendFileSync(ghaOutput, `crew_test_plan_path=${crewTestPlanPath}\n`);
95
98
  appendFileSync(ghaOutput, `crew_impacted_flows=${planReport.crew?.summary.impactedFlows || 0}\n`);
96
99
  appendFileSync(ghaOutput, `crew_strategy_entries=${planReport.crew?.summary.strategyEntries || 0}\n`);
97
100
  appendFileSync(ghaOutput, `crew_test_designs=${planReport.crew?.summary.testDesigns || 0}\n`);
@@ -104,6 +107,7 @@ export async function runPlanCommand(args, autoConfig, config) {
104
107
  if (planReport.crew) {
105
108
  console.log(`Crew workflow: ${planReport.crew.workflow} (impactedFlows=${planReport.crew.summary.impactedFlows}, strategyEntries=${planReport.crew.summary.strategyEntries}, testDesigns=${planReport.crew.summary.testDesigns})`);
106
109
  console.log(`Crew summary: ${crewSummaryPath}`);
110
+ console.log(`Crew test plan: ${crewTestPlanPath}`);
107
111
  }
108
112
  console.log(`Plan metrics: ${metricsSummaryPath}`);
109
113
  const failOnLegacyFlag = args.failOnMustAddTests && planReport.decision.action === 'must-add-tests';
@@ -78,19 +78,25 @@ export async function runPlanCrewAnalysis(plan, config, args) {
78
78
  timings: result.timings,
79
79
  };
80
80
  }
81
- export function buildCrewMarkdown(crew) {
81
+ export function buildCrewMarkdown(crew, plan) {
82
+ const totalCases = crew.testDesigns.reduce((n, td) => n + td.testCases.length, 0);
83
+ const gapFamilies = new Set((plan?.gapDetails ?? []).map((g) => g.id));
84
+ const gapDesigns = gapFamilies.size > 0
85
+ ? crew.testDesigns.filter((td) => Array.from(gapFamilies).some((fam) => td.flowId.startsWith(fam) || td.flowName.toLowerCase().includes(fam.replace(/_/g, ' '))))
86
+ : [];
87
+ const gapCases = gapDesigns.reduce((n, td) => n + td.testCases.length, 0);
88
+ const gapP0Cases = gapDesigns.reduce((n, td) => n + td.testCases.filter((tc) => tc.priority === 'P0').length, 0);
82
89
  const lines = [
83
90
  '### Crew Insights',
84
91
  '',
85
92
  `Workflow: \`${crew.workflow}\``,
86
- `Provider override: \`${crew.providerOverride}\``,
87
93
  `Impacted flows: **${crew.summary.impactedFlows}**`,
88
- `Strategy entries: **${crew.summary.strategyEntries}**`,
89
- `Structured test designs: **${crew.summary.testDesigns}**`,
90
- `Cross-impacts: **${crew.summary.crossImpacts}** (${crew.summary.highRiskCrossImpacts} high risk)`,
91
- `Findings: **${crew.summary.findings}**`,
92
- `Estimated AI cost: **$${crew.summary.totalCostUSD.toFixed(4)}**`,
94
+ `Structured test designs: **${crew.summary.testDesigns}** flows, **${totalCases}** test cases`,
93
95
  ];
96
+ if (gapFamilies.size > 0 && gapDesigns.length > 0) {
97
+ lines.push(`Gap-focused: **${gapDesigns.length}** flows, **${gapCases}** test cases (**${gapP0Cases}** P0)`);
98
+ }
99
+ lines.push(`Cross-impacts: **${crew.summary.crossImpacts}** (${crew.summary.highRiskCrossImpacts} high risk)`, `Estimated AI cost: **$${crew.summary.totalCostUSD.toFixed(4)}**`);
94
100
  if (crew.strategyEntries.length > 0) {
95
101
  lines.push('');
96
102
  lines.push('Top Strategy Recommendations:');
@@ -129,15 +135,132 @@ export function buildCrewMarkdown(crew) {
129
135
  }
130
136
  return lines.join('\n');
131
137
  }
132
- export function appendCrewToSummary(baseMarkdown, crew) {
133
- return `${baseMarkdown}\n\n---\n\n${buildCrewMarkdown(crew)}`;
138
+ export function appendCrewToSummary(baseMarkdown, crew, plan) {
139
+ return `${baseMarkdown}\n\n---\n\n${buildCrewMarkdown(crew, plan)}`;
140
+ }
141
+ /**
142
+ * Build a full structured test plan as a Markdown document.
143
+ * Sections: gap flows first (P0 cases expanded), then covered-flow smoke tests.
144
+ */
145
+ export function buildCrewTestPlan(crew, plan) {
146
+ const gapFamilies = new Set((plan?.gapDetails ?? []).map((g) => g.id));
147
+ const totalCases = crew.testDesigns.reduce((n, td) => n + td.testCases.length, 0);
148
+ // Split designs into gap-related and coverage-expansion
149
+ const gapDesigns = [];
150
+ const coveredDesigns = [];
151
+ for (const td of crew.testDesigns) {
152
+ // Match by flowId prefix against gap family ids
153
+ const isGap = Array.from(gapFamilies).some((fam) => td.flowId.startsWith(fam) || td.flowName.toLowerCase().includes(fam.replace(/_/g, ' ')));
154
+ if (isGap) {
155
+ gapDesigns.push(td);
156
+ }
157
+ else {
158
+ coveredDesigns.push(td);
159
+ }
160
+ }
161
+ const gapCases = gapDesigns.reduce((n, td) => n + td.testCases.length, 0);
162
+ const coveredCases = coveredDesigns.reduce((n, td) => n + td.testCases.length, 0);
163
+ const lines = [
164
+ '# Crew Test Plan',
165
+ '',
166
+ `> Auto-generated by e2e-agents crew (\`${crew.workflow}\` workflow)`,
167
+ '',
168
+ '## Summary',
169
+ '',
170
+ `| Metric | Count |`,
171
+ `|--------|-------|`,
172
+ `| Gap flows (missing tests) | ${gapDesigns.length} flows, **${gapCases} test cases** |`,
173
+ `| Covered flows (expansion) | ${coveredDesigns.length} flows, ${coveredCases} test cases |`,
174
+ `| Total | ${crew.testDesigns.length} flows, ${totalCases} test cases |`,
175
+ `| High-risk cross-impacts | ${crew.summary.highRiskCrossImpacts} |`,
176
+ `| AI cost | $${crew.summary.totalCostUSD.toFixed(4)} |`,
177
+ '',
178
+ ];
179
+ // Priority action items
180
+ if (gapDesigns.length > 0) {
181
+ lines.push('## Priority: Gap Flows (Missing Tests)');
182
+ lines.push('');
183
+ lines.push('These flows have **no existing E2E coverage** and should be addressed first.');
184
+ lines.push('');
185
+ for (const td of gapDesigns) {
186
+ const strategy = crew.strategyEntries.find((s) => s.flowId === td.flowId);
187
+ const approach = strategy?.approach ?? 'full-test';
188
+ const risk = strategy?.crossImpactRisk ?? 'unknown';
189
+ const p0Cases = td.testCases.filter((tc) => tc.priority === 'P0');
190
+ const p1Cases = td.testCases.filter((tc) => tc.priority === 'P1');
191
+ lines.push(`### ${td.flowName}`);
192
+ lines.push('');
193
+ lines.push(`Strategy: **${approach}** | Cross-impact risk: **${risk}** | ${td.testCases.length} cases (${p0Cases.length} P0, ${p1Cases.length} P1)`);
194
+ lines.push('');
195
+ // Show P0 cases expanded
196
+ if (p0Cases.length > 0) {
197
+ lines.push('**P0 — Must test:**');
198
+ lines.push('');
199
+ for (const tc of p0Cases) {
200
+ lines.push(`- [ ] **${tc.name}** (${tc.type})`);
201
+ if (tc.preconditions.length > 0) {
202
+ lines.push(` - Preconditions: ${tc.preconditions.join('; ')}`);
203
+ }
204
+ lines.push(` - Steps: ${tc.steps.join(' → ')}`);
205
+ lines.push(` - Expected: ${tc.expectedOutcome}`);
206
+ }
207
+ lines.push('');
208
+ }
209
+ // Show P1 as a collapsed checklist
210
+ if (p1Cases.length > 0) {
211
+ lines.push(`<details><summary>P1 — Should test (${p1Cases.length})</summary>`);
212
+ lines.push('');
213
+ for (const tc of p1Cases) {
214
+ lines.push(`- [ ] **${tc.name}** (${tc.type}) — ${tc.expectedOutcome}`);
215
+ }
216
+ lines.push('');
217
+ lines.push('</details>');
218
+ lines.push('');
219
+ }
220
+ }
221
+ }
222
+ // Covered flow expansion — collapsed by default
223
+ if (coveredDesigns.length > 0) {
224
+ lines.push('## Covered Flows (Regression / Expansion)');
225
+ lines.push('');
226
+ lines.push('These flows already have specs. The test cases below extend coverage for changes in this PR.');
227
+ lines.push('');
228
+ for (const td of coveredDesigns) {
229
+ const strategy = crew.strategyEntries.find((s) => s.flowId === td.flowId);
230
+ const approach = strategy?.approach ?? 'smoke-test';
231
+ const p0Count = td.testCases.filter((tc) => tc.priority === 'P0').length;
232
+ lines.push(`<details><summary><strong>${td.flowName}</strong> — ${approach} | ${td.testCases.length} cases (${p0Count} P0)</summary>`);
233
+ lines.push('');
234
+ for (const tc of td.testCases) {
235
+ lines.push(`- [ ] **${tc.name}** (${tc.priority}, ${tc.type}) — ${tc.expectedOutcome}`);
236
+ }
237
+ lines.push('');
238
+ lines.push('</details>');
239
+ lines.push('');
240
+ }
241
+ }
242
+ // Cross-impacts section
243
+ const highRisk = crew.crossImpacts.filter((ci) => ci.riskLevel === 'high');
244
+ if (highRisk.length > 0) {
245
+ lines.push('## High-Risk Cross-Impacts');
246
+ lines.push('');
247
+ lines.push('These cross-family dependencies should be verified during release testing:');
248
+ lines.push('');
249
+ for (const ci of highRisk) {
250
+ lines.push(`- **${ci.sourceFamily}** → **${ci.affectedFamily}**: ${ci.sharedDependency}`);
251
+ }
252
+ lines.push('');
253
+ }
254
+ return lines.join('\n');
134
255
  }
135
- export function writeCrewArtifacts(reportRoot, crew) {
256
+ export function writeCrewArtifacts(reportRoot, crew, plan) {
136
257
  const outputDir = join(reportRoot, '.e2e-ai-agents');
137
258
  mkdirSync(outputDir, { recursive: true });
138
259
  const crewSummaryPath = join(outputDir, 'crew-summary.json');
139
260
  const crewMarkdownPath = join(outputDir, 'crew-summary.md');
261
+ const crewTestPlanPath = join(outputDir, 'crew-test-plan.md');
140
262
  writeFileSync(crewSummaryPath, JSON.stringify(crew, null, 2), 'utf-8');
141
- writeFileSync(crewMarkdownPath, buildCrewMarkdown(crew), 'utf-8');
142
- return { crewSummaryPath, crewMarkdownPath };
263
+ writeFileSync(crewMarkdownPath, buildCrewMarkdown(crew, plan), 'utf-8');
264
+ writeFileSync(crewTestPlanPath, buildCrewTestPlan(crew, plan), 'utf-8');
265
+ return { crewSummaryPath, crewMarkdownPath, crewTestPlanPath };
143
266
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yasserkhanorg/e2e-agents",
3
- "version": "1.8.2",
3
+ "version": "1.8.3",
4
4
  "description": "AI-powered E2E test impact analysis, generation, and healing. Analyzes code changes to identify affected Playwright tests, detects coverage gaps, and generates or repairs specs using pluggable LLM providers (Claude, OpenAI, Ollama). Includes MCP server, traceability, and CI/CD integration.",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/esm/index.js",