@yasserkhanorg/e2e-agents 1.8.2 → 1.8.4

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;AAWD,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,gBAAgB,EAAE,IAAI,CAAC,EAAE,UAAU,GAAG,MAAM,CAwEnF;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,CA0InF;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,37 @@ async function runPlanCrewAnalysis(plan, config, args) {
84
85
  timings: result.timings,
85
86
  };
86
87
  }
87
- function buildCrewMarkdown(crew) {
88
+ /**
89
+ * Match a strategy/design entry against a set of gap family IDs.
90
+ */
91
+ function matchesGapFamily(flowId, flowName, gapFamilies) {
92
+ return Array.from(gapFamilies).some((fam) => flowId.startsWith(fam) || flowName.toLowerCase().includes(fam.replace(/_/g, ' ')));
93
+ }
94
+ function buildCrewMarkdown(crew, plan) {
95
+ const totalCases = crew.testDesigns.reduce((n, td) => n + td.testCases.length, 0);
96
+ const gapFamilies = new Set((plan?.gapDetails ?? []).map((g) => g.id));
88
97
  const lines = [
89
98
  '### Crew Insights',
90
99
  '',
91
100
  `Workflow: \`${crew.workflow}\``,
92
- `Provider override: \`${crew.providerOverride}\``,
93
101
  `Impacted flows: **${crew.summary.impactedFlows}**`,
94
102
  `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)}**`,
99
103
  ];
104
+ if (totalCases > 0) {
105
+ const gapDesigns = gapFamilies.size > 0
106
+ ? crew.testDesigns.filter((td) => matchesGapFamily(td.flowId, td.flowName, gapFamilies))
107
+ : [];
108
+ const gapCases = gapDesigns.reduce((n, td) => n + td.testCases.length, 0);
109
+ const gapP0Cases = gapDesigns.reduce((n, td) => n + td.testCases.filter((tc) => tc.priority === 'P0').length, 0);
110
+ lines.push(`Structured test designs: **${crew.summary.testDesigns}** flows, **${totalCases}** test cases`);
111
+ if (gapDesigns.length > 0) {
112
+ lines.push(`Gap-focused: **${gapDesigns.length}** flows, **${gapCases}** test cases (**${gapP0Cases}** P0)`);
113
+ }
114
+ }
115
+ if (crew.summary.crossImpacts > 0) {
116
+ lines.push(`Cross-impacts: **${crew.summary.crossImpacts}** (${crew.summary.highRiskCrossImpacts} high risk)`);
117
+ }
118
+ lines.push(`Estimated AI cost: **$${crew.summary.totalCostUSD.toFixed(4)}**`);
100
119
  if (crew.strategyEntries.length > 0) {
101
120
  lines.push('');
102
121
  lines.push('Top Strategy Recommendations:');
@@ -135,15 +154,151 @@ function buildCrewMarkdown(crew) {
135
154
  }
136
155
  return lines.join('\n');
137
156
  }
138
- function appendCrewToSummary(baseMarkdown, crew) {
139
- return `${baseMarkdown}\n\n---\n\n${buildCrewMarkdown(crew)}`;
157
+ function appendCrewToSummary(baseMarkdown, crew, plan) {
158
+ return `${baseMarkdown}\n\n---\n\n${buildCrewMarkdown(crew, plan)}`;
159
+ }
160
+ /**
161
+ * Build a full structured test plan as a Markdown document.
162
+ * Sections: gap flows first (P0 cases expanded), then covered-flow smoke tests.
163
+ */
164
+ function buildCrewTestPlan(crew, plan) {
165
+ const gapFamilies = new Set((plan?.gapDetails ?? []).map((g) => g.id));
166
+ const hasTestDesigns = crew.testDesigns.length > 0;
167
+ const totalCases = crew.testDesigns.reduce((n, td) => n + td.testCases.length, 0);
168
+ // Split strategy entries into gap-related and covered
169
+ const gapStrategies = gapFamilies.size > 0
170
+ ? crew.strategyEntries.filter((s) => matchesGapFamily(s.flowId, s.flowName, gapFamilies))
171
+ : [];
172
+ const coveredStrategies = crew.strategyEntries.filter((s) => !gapStrategies.includes(s));
173
+ // Split test designs (if present) into gap-related and covered
174
+ const gapDesigns = [];
175
+ const coveredDesigns = [];
176
+ for (const td of crew.testDesigns) {
177
+ if (matchesGapFamily(td.flowId, td.flowName, gapFamilies)) {
178
+ gapDesigns.push(td);
179
+ }
180
+ else {
181
+ coveredDesigns.push(td);
182
+ }
183
+ }
184
+ const gapCases = gapDesigns.reduce((n, td) => n + td.testCases.length, 0);
185
+ const coveredCases = coveredDesigns.reduce((n, td) => n + td.testCases.length, 0);
186
+ const lines = [
187
+ '# Crew Test Plan',
188
+ '',
189
+ `> Auto-generated by e2e-agents crew (\`${crew.workflow}\` workflow)`,
190
+ '',
191
+ '## Summary',
192
+ '',
193
+ `| Metric | Count |`,
194
+ `|--------|-------|`,
195
+ `| Gap flows (missing tests) | ${gapStrategies.length} flows${hasTestDesigns ? `, **${gapCases} test cases**` : ''} |`,
196
+ `| Covered flows (expansion) | ${coveredStrategies.length} flows${hasTestDesigns ? `, ${coveredCases} test cases` : ''} |`,
197
+ `| Total strategy entries | ${crew.strategyEntries.length} flows${hasTestDesigns ? `, ${totalCases} test cases` : ''} |`,
198
+ `| High-risk cross-impacts | ${crew.summary.highRiskCrossImpacts} |`,
199
+ `| AI cost | $${crew.summary.totalCostUSD.toFixed(4)} |`,
200
+ '',
201
+ ];
202
+ // ── Gap flows ──
203
+ if (gapStrategies.length > 0) {
204
+ lines.push('## Priority: Gap Flows (Missing Tests)');
205
+ lines.push('');
206
+ lines.push('These flows have **no existing E2E coverage** and should be addressed first.');
207
+ lines.push('');
208
+ for (const strategy of gapStrategies) {
209
+ const td = crew.testDesigns.find((d) => d.flowId === strategy.flowId);
210
+ lines.push(`### ${strategy.flowName}`);
211
+ lines.push('');
212
+ lines.push(`Strategy: **${strategy.approach}** | Priority: **${strategy.priority}** | Cross-impact risk: **${strategy.crossImpactRisk}**`);
213
+ if (strategy.rationale) {
214
+ lines.push(`> ${strategy.rationale}`);
215
+ }
216
+ if (strategy.testCategories.length > 0) {
217
+ lines.push(`Test categories: ${strategy.testCategories.join(', ')}`);
218
+ }
219
+ lines.push('');
220
+ // If test designs exist, show P0/P1 cases
221
+ if (td && td.testCases.length > 0) {
222
+ const p0Cases = td.testCases.filter((tc) => tc.priority === 'P0');
223
+ const p1Cases = td.testCases.filter((tc) => tc.priority === 'P1');
224
+ if (p0Cases.length > 0) {
225
+ lines.push('**P0 — Must test:**');
226
+ lines.push('');
227
+ for (const tc of p0Cases) {
228
+ lines.push(`- [ ] **${tc.name}** (${tc.type})`);
229
+ if (tc.preconditions.length > 0) {
230
+ lines.push(` - Preconditions: ${tc.preconditions.join('; ')}`);
231
+ }
232
+ lines.push(` - Steps: ${tc.steps.join(' → ')}`);
233
+ lines.push(` - Expected: ${tc.expectedOutcome}`);
234
+ }
235
+ lines.push('');
236
+ }
237
+ if (p1Cases.length > 0) {
238
+ lines.push(`<details><summary>P1 — Should test (${p1Cases.length})</summary>`);
239
+ lines.push('');
240
+ for (const tc of p1Cases) {
241
+ lines.push(`- [ ] **${tc.name}** (${tc.type}) — ${tc.expectedOutcome}`);
242
+ }
243
+ lines.push('');
244
+ lines.push('</details>');
245
+ lines.push('');
246
+ }
247
+ }
248
+ }
249
+ }
250
+ // ── Covered flows ──
251
+ if (coveredStrategies.length > 0) {
252
+ lines.push('## Covered Flows (Regression / Expansion)');
253
+ lines.push('');
254
+ lines.push('These flows already have specs. Verify changes haven\'t introduced regressions.');
255
+ lines.push('');
256
+ for (const strategy of coveredStrategies) {
257
+ const td = crew.testDesigns.find((d) => d.flowId === strategy.flowId);
258
+ const caseCount = td ? td.testCases.length : 0;
259
+ const detail = caseCount > 0 ? ` | ${caseCount} cases` : '';
260
+ lines.push(`<details><summary><strong>${strategy.flowName}</strong> — ${strategy.approach}${detail} (${strategy.priority})</summary>`);
261
+ lines.push('');
262
+ lines.push(`Cross-impact risk: ${strategy.crossImpactRisk}`);
263
+ if (strategy.rationale) {
264
+ lines.push(`> ${strategy.rationale}`);
265
+ }
266
+ if (strategy.testCategories.length > 0) {
267
+ lines.push(`Test categories: ${strategy.testCategories.join(', ')}`);
268
+ }
269
+ if (td && td.testCases.length > 0) {
270
+ lines.push('');
271
+ for (const tc of td.testCases) {
272
+ lines.push(`- [ ] **${tc.name}** (${tc.priority}, ${tc.type}) — ${tc.expectedOutcome}`);
273
+ }
274
+ }
275
+ lines.push('');
276
+ lines.push('</details>');
277
+ lines.push('');
278
+ }
279
+ }
280
+ // ── Cross-impacts ──
281
+ const highRisk = crew.crossImpacts.filter((ci) => ci.riskLevel === 'high');
282
+ if (highRisk.length > 0) {
283
+ lines.push('## High-Risk Cross-Impacts');
284
+ lines.push('');
285
+ lines.push('These cross-family dependencies should be verified during release testing:');
286
+ lines.push('');
287
+ for (const ci of highRisk) {
288
+ lines.push(`- **${ci.sourceFamily}** → **${ci.affectedFamily}**: ${ci.sharedDependency}`);
289
+ }
290
+ lines.push('');
291
+ }
292
+ return lines.join('\n');
140
293
  }
141
- function writeCrewArtifacts(reportRoot, crew) {
294
+ function writeCrewArtifacts(reportRoot, crew, plan) {
142
295
  const outputDir = (0, path_1.join)(reportRoot, '.e2e-ai-agents');
143
296
  (0, fs_1.mkdirSync)(outputDir, { recursive: true });
144
297
  const crewSummaryPath = (0, path_1.join)(outputDir, 'crew-summary.json');
145
298
  const crewMarkdownPath = (0, path_1.join)(outputDir, 'crew-summary.md');
299
+ const crewTestPlanPath = (0, path_1.join)(outputDir, 'crew-test-plan.md');
146
300
  (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 };
301
+ (0, fs_1.writeFileSync)(crewMarkdownPath, buildCrewMarkdown(crew, plan), 'utf-8');
302
+ (0, fs_1.writeFileSync)(crewTestPlanPath, buildCrewTestPlan(crew, plan), 'utf-8');
303
+ return { crewSummaryPath, crewMarkdownPath, crewTestPlanPath };
149
304
  }
@@ -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,37 @@ export async function runPlanCrewAnalysis(plan, config, args) {
78
78
  timings: result.timings,
79
79
  };
80
80
  }
81
- export function buildCrewMarkdown(crew) {
81
+ /**
82
+ * Match a strategy/design entry against a set of gap family IDs.
83
+ */
84
+ function matchesGapFamily(flowId, flowName, gapFamilies) {
85
+ return Array.from(gapFamilies).some((fam) => flowId.startsWith(fam) || flowName.toLowerCase().includes(fam.replace(/_/g, ' ')));
86
+ }
87
+ export function buildCrewMarkdown(crew, plan) {
88
+ const totalCases = crew.testDesigns.reduce((n, td) => n + td.testCases.length, 0);
89
+ const gapFamilies = new Set((plan?.gapDetails ?? []).map((g) => g.id));
82
90
  const lines = [
83
91
  '### Crew Insights',
84
92
  '',
85
93
  `Workflow: \`${crew.workflow}\``,
86
- `Provider override: \`${crew.providerOverride}\``,
87
94
  `Impacted flows: **${crew.summary.impactedFlows}**`,
88
95
  `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)}**`,
93
96
  ];
97
+ if (totalCases > 0) {
98
+ const gapDesigns = gapFamilies.size > 0
99
+ ? crew.testDesigns.filter((td) => matchesGapFamily(td.flowId, td.flowName, gapFamilies))
100
+ : [];
101
+ const gapCases = gapDesigns.reduce((n, td) => n + td.testCases.length, 0);
102
+ const gapP0Cases = gapDesigns.reduce((n, td) => n + td.testCases.filter((tc) => tc.priority === 'P0').length, 0);
103
+ lines.push(`Structured test designs: **${crew.summary.testDesigns}** flows, **${totalCases}** test cases`);
104
+ if (gapDesigns.length > 0) {
105
+ lines.push(`Gap-focused: **${gapDesigns.length}** flows, **${gapCases}** test cases (**${gapP0Cases}** P0)`);
106
+ }
107
+ }
108
+ if (crew.summary.crossImpacts > 0) {
109
+ lines.push(`Cross-impacts: **${crew.summary.crossImpacts}** (${crew.summary.highRiskCrossImpacts} high risk)`);
110
+ }
111
+ lines.push(`Estimated AI cost: **$${crew.summary.totalCostUSD.toFixed(4)}**`);
94
112
  if (crew.strategyEntries.length > 0) {
95
113
  lines.push('');
96
114
  lines.push('Top Strategy Recommendations:');
@@ -129,15 +147,151 @@ export function buildCrewMarkdown(crew) {
129
147
  }
130
148
  return lines.join('\n');
131
149
  }
132
- export function appendCrewToSummary(baseMarkdown, crew) {
133
- return `${baseMarkdown}\n\n---\n\n${buildCrewMarkdown(crew)}`;
150
+ export function appendCrewToSummary(baseMarkdown, crew, plan) {
151
+ return `${baseMarkdown}\n\n---\n\n${buildCrewMarkdown(crew, plan)}`;
152
+ }
153
+ /**
154
+ * Build a full structured test plan as a Markdown document.
155
+ * Sections: gap flows first (P0 cases expanded), then covered-flow smoke tests.
156
+ */
157
+ export function buildCrewTestPlan(crew, plan) {
158
+ const gapFamilies = new Set((plan?.gapDetails ?? []).map((g) => g.id));
159
+ const hasTestDesigns = crew.testDesigns.length > 0;
160
+ const totalCases = crew.testDesigns.reduce((n, td) => n + td.testCases.length, 0);
161
+ // Split strategy entries into gap-related and covered
162
+ const gapStrategies = gapFamilies.size > 0
163
+ ? crew.strategyEntries.filter((s) => matchesGapFamily(s.flowId, s.flowName, gapFamilies))
164
+ : [];
165
+ const coveredStrategies = crew.strategyEntries.filter((s) => !gapStrategies.includes(s));
166
+ // Split test designs (if present) into gap-related and covered
167
+ const gapDesigns = [];
168
+ const coveredDesigns = [];
169
+ for (const td of crew.testDesigns) {
170
+ if (matchesGapFamily(td.flowId, td.flowName, gapFamilies)) {
171
+ gapDesigns.push(td);
172
+ }
173
+ else {
174
+ coveredDesigns.push(td);
175
+ }
176
+ }
177
+ const gapCases = gapDesigns.reduce((n, td) => n + td.testCases.length, 0);
178
+ const coveredCases = coveredDesigns.reduce((n, td) => n + td.testCases.length, 0);
179
+ const lines = [
180
+ '# Crew Test Plan',
181
+ '',
182
+ `> Auto-generated by e2e-agents crew (\`${crew.workflow}\` workflow)`,
183
+ '',
184
+ '## Summary',
185
+ '',
186
+ `| Metric | Count |`,
187
+ `|--------|-------|`,
188
+ `| Gap flows (missing tests) | ${gapStrategies.length} flows${hasTestDesigns ? `, **${gapCases} test cases**` : ''} |`,
189
+ `| Covered flows (expansion) | ${coveredStrategies.length} flows${hasTestDesigns ? `, ${coveredCases} test cases` : ''} |`,
190
+ `| Total strategy entries | ${crew.strategyEntries.length} flows${hasTestDesigns ? `, ${totalCases} test cases` : ''} |`,
191
+ `| High-risk cross-impacts | ${crew.summary.highRiskCrossImpacts} |`,
192
+ `| AI cost | $${crew.summary.totalCostUSD.toFixed(4)} |`,
193
+ '',
194
+ ];
195
+ // ── Gap flows ──
196
+ if (gapStrategies.length > 0) {
197
+ lines.push('## Priority: Gap Flows (Missing Tests)');
198
+ lines.push('');
199
+ lines.push('These flows have **no existing E2E coverage** and should be addressed first.');
200
+ lines.push('');
201
+ for (const strategy of gapStrategies) {
202
+ const td = crew.testDesigns.find((d) => d.flowId === strategy.flowId);
203
+ lines.push(`### ${strategy.flowName}`);
204
+ lines.push('');
205
+ lines.push(`Strategy: **${strategy.approach}** | Priority: **${strategy.priority}** | Cross-impact risk: **${strategy.crossImpactRisk}**`);
206
+ if (strategy.rationale) {
207
+ lines.push(`> ${strategy.rationale}`);
208
+ }
209
+ if (strategy.testCategories.length > 0) {
210
+ lines.push(`Test categories: ${strategy.testCategories.join(', ')}`);
211
+ }
212
+ lines.push('');
213
+ // If test designs exist, show P0/P1 cases
214
+ if (td && td.testCases.length > 0) {
215
+ const p0Cases = td.testCases.filter((tc) => tc.priority === 'P0');
216
+ const p1Cases = td.testCases.filter((tc) => tc.priority === 'P1');
217
+ if (p0Cases.length > 0) {
218
+ lines.push('**P0 — Must test:**');
219
+ lines.push('');
220
+ for (const tc of p0Cases) {
221
+ lines.push(`- [ ] **${tc.name}** (${tc.type})`);
222
+ if (tc.preconditions.length > 0) {
223
+ lines.push(` - Preconditions: ${tc.preconditions.join('; ')}`);
224
+ }
225
+ lines.push(` - Steps: ${tc.steps.join(' → ')}`);
226
+ lines.push(` - Expected: ${tc.expectedOutcome}`);
227
+ }
228
+ lines.push('');
229
+ }
230
+ if (p1Cases.length > 0) {
231
+ lines.push(`<details><summary>P1 — Should test (${p1Cases.length})</summary>`);
232
+ lines.push('');
233
+ for (const tc of p1Cases) {
234
+ lines.push(`- [ ] **${tc.name}** (${tc.type}) — ${tc.expectedOutcome}`);
235
+ }
236
+ lines.push('');
237
+ lines.push('</details>');
238
+ lines.push('');
239
+ }
240
+ }
241
+ }
242
+ }
243
+ // ── Covered flows ──
244
+ if (coveredStrategies.length > 0) {
245
+ lines.push('## Covered Flows (Regression / Expansion)');
246
+ lines.push('');
247
+ lines.push('These flows already have specs. Verify changes haven\'t introduced regressions.');
248
+ lines.push('');
249
+ for (const strategy of coveredStrategies) {
250
+ const td = crew.testDesigns.find((d) => d.flowId === strategy.flowId);
251
+ const caseCount = td ? td.testCases.length : 0;
252
+ const detail = caseCount > 0 ? ` | ${caseCount} cases` : '';
253
+ lines.push(`<details><summary><strong>${strategy.flowName}</strong> — ${strategy.approach}${detail} (${strategy.priority})</summary>`);
254
+ lines.push('');
255
+ lines.push(`Cross-impact risk: ${strategy.crossImpactRisk}`);
256
+ if (strategy.rationale) {
257
+ lines.push(`> ${strategy.rationale}`);
258
+ }
259
+ if (strategy.testCategories.length > 0) {
260
+ lines.push(`Test categories: ${strategy.testCategories.join(', ')}`);
261
+ }
262
+ if (td && td.testCases.length > 0) {
263
+ lines.push('');
264
+ for (const tc of td.testCases) {
265
+ lines.push(`- [ ] **${tc.name}** (${tc.priority}, ${tc.type}) — ${tc.expectedOutcome}`);
266
+ }
267
+ }
268
+ lines.push('');
269
+ lines.push('</details>');
270
+ lines.push('');
271
+ }
272
+ }
273
+ // ── Cross-impacts ──
274
+ const highRisk = crew.crossImpacts.filter((ci) => ci.riskLevel === 'high');
275
+ if (highRisk.length > 0) {
276
+ lines.push('## High-Risk Cross-Impacts');
277
+ lines.push('');
278
+ lines.push('These cross-family dependencies should be verified during release testing:');
279
+ lines.push('');
280
+ for (const ci of highRisk) {
281
+ lines.push(`- **${ci.sourceFamily}** → **${ci.affectedFamily}**: ${ci.sharedDependency}`);
282
+ }
283
+ lines.push('');
284
+ }
285
+ return lines.join('\n');
134
286
  }
135
- export function writeCrewArtifacts(reportRoot, crew) {
287
+ export function writeCrewArtifacts(reportRoot, crew, plan) {
136
288
  const outputDir = join(reportRoot, '.e2e-ai-agents');
137
289
  mkdirSync(outputDir, { recursive: true });
138
290
  const crewSummaryPath = join(outputDir, 'crew-summary.json');
139
291
  const crewMarkdownPath = join(outputDir, 'crew-summary.md');
292
+ const crewTestPlanPath = join(outputDir, 'crew-test-plan.md');
140
293
  writeFileSync(crewSummaryPath, JSON.stringify(crew, null, 2), 'utf-8');
141
- writeFileSync(crewMarkdownPath, buildCrewMarkdown(crew), 'utf-8');
142
- return { crewSummaryPath, crewMarkdownPath };
294
+ writeFileSync(crewMarkdownPath, buildCrewMarkdown(crew, plan), 'utf-8');
295
+ writeFileSync(crewTestPlanPath, buildCrewTestPlan(crew, plan), 'utf-8');
296
+ return { crewSummaryPath, crewMarkdownPath, crewTestPlanPath };
143
297
  }
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.4",
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",