@yasserkhanorg/e2e-agents 1.8.3 → 1.8.5

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_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"}
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,CAsJnF;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"}
@@ -85,25 +85,37 @@ async function runPlanCrewAnalysis(plan, config, args) {
85
85
  timings: result.timings,
86
86
  };
87
87
  }
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
+ }
88
94
  function buildCrewMarkdown(crew, plan) {
89
95
  const totalCases = crew.testDesigns.reduce((n, td) => n + td.testCases.length, 0);
90
96
  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);
96
97
  const lines = [
97
- '### Crew Insights',
98
+ '### Crew Analysis — What to Test',
99
+ '',
100
+ `Crew analyzed the diff and recommends what to verify before merging.`,
98
101
  '',
99
- `Workflow: \`${crew.workflow}\``,
100
- `Impacted flows: **${crew.summary.impactedFlows}**`,
101
- `Structured test designs: **${crew.summary.testDesigns}** flows, **${totalCases}** test cases`,
102
+ `Impacted flows: **${crew.summary.impactedFlows}** | Strategy entries: **${crew.summary.strategyEntries}**`,
102
103
  ];
103
- if (gapFamilies.size > 0 && gapDesigns.length > 0) {
104
- lines.push(`Gap-focused: **${gapDesigns.length}** flows, **${gapCases}** test cases (**${gapP0Cases}** P0)`);
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)`);
105
117
  }
106
- lines.push(`Cross-impacts: **${crew.summary.crossImpacts}** (${crew.summary.highRiskCrossImpacts} high risk)`, `Estimated AI cost: **$${crew.summary.totalCostUSD.toFixed(4)}**`);
118
+ lines.push(`Estimated AI cost: **$${crew.summary.totalCostUSD.toFixed(4)}**`);
107
119
  if (crew.strategyEntries.length > 0) {
108
120
  lines.push('');
109
121
  lines.push('Top Strategy Recommendations:');
@@ -151,14 +163,18 @@ function appendCrewToSummary(baseMarkdown, crew, plan) {
151
163
  */
152
164
  function buildCrewTestPlan(crew, plan) {
153
165
  const gapFamilies = new Set((plan?.gapDetails ?? []).map((g) => g.id));
166
+ const hasTestDesigns = crew.testDesigns.length > 0;
154
167
  const totalCases = crew.testDesigns.reduce((n, td) => n + td.testCases.length, 0);
155
- // Split designs into gap-related and coverage-expansion
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
156
174
  const gapDesigns = [];
157
175
  const coveredDesigns = [];
158
176
  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) {
177
+ if (matchesGapFamily(td.flowId, td.flowName, gapFamilies)) {
162
178
  gapDesigns.push(td);
163
179
  }
164
180
  else {
@@ -168,90 +184,117 @@ function buildCrewTestPlan(crew, plan) {
168
184
  const gapCases = gapDesigns.reduce((n, td) => n + td.testCases.length, 0);
169
185
  const coveredCases = coveredDesigns.reduce((n, td) => n + td.testCases.length, 0);
170
186
  const lines = [
171
- '# Crew Test Plan',
187
+ '# Crew Test Plan — What to Verify',
172
188
  '',
173
- `> Auto-generated by e2e-agents crew (\`${crew.workflow}\` workflow)`,
189
+ '> **This is a test recommendation, not a test execution report.**',
190
+ '> Crew analyzed the code diff and identified what needs to be tested.',
191
+ '> Use this plan to guide manual QA or write automated E2E tests.',
192
+ '',
193
+ `_Auto-generated by e2e-agents crew (\`${crew.workflow}\` workflow)_`,
174
194
  '',
175
195
  '## Summary',
176
196
  '',
177
197
  `| Metric | Count |`,
178
198
  `|--------|-------|`,
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 |`,
199
+ `| Gap flows **no existing tests, must verify** | ${gapStrategies.length} flows${hasTestDesigns ? `, **${gapCases} test cases**` : ''} |`,
200
+ `| Covered flows **has tests, verify no regressions** | ${coveredStrategies.length} flows${hasTestDesigns ? `, ${coveredCases} test cases` : ''} |`,
201
+ `| Total | ${crew.strategyEntries.length} flows${hasTestDesigns ? `, ${totalCases} test cases` : ''} |`,
182
202
  `| High-risk cross-impacts | ${crew.summary.highRiskCrossImpacts} |`,
183
203
  `| AI cost | $${crew.summary.totalCostUSD.toFixed(4)} |`,
184
204
  '',
185
205
  ];
186
- // Priority action items
187
- if (gapDesigns.length > 0) {
188
- lines.push('## Priority: Gap Flows (Missing Tests)');
206
+ // ── Gap flows ──
207
+ if (gapStrategies.length > 0) {
208
+ lines.push('## Action Required: Gap Flows (No Existing Tests)');
189
209
  lines.push('');
190
- lines.push('These flows have **no existing E2E coverage** and should be addressed first.');
210
+ lines.push('These flows have **no automated E2E coverage**. Before merging, either:');
211
+ lines.push('1. **Write E2E tests** for the critical scenarios below, or');
212
+ lines.push('2. **Manually verify** each flow works as expected');
191
213
  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}`);
214
+ for (const strategy of gapStrategies) {
215
+ const td = crew.testDesigns.find((d) => d.flowId === strategy.flowId);
216
+ lines.push(`### ${strategy.flowName}`);
199
217
  lines.push('');
200
- lines.push(`Strategy: **${approach}** | Cross-impact risk: **${risk}** | ${td.testCases.length} cases (${p0Cases.length} P0, ${p1Cases.length} P1)`);
218
+ const actionVerb = strategy.approach === 'full-test' ? 'Write full E2E test or verify manually'
219
+ : strategy.approach === 'smoke-test' ? 'Smoke-test manually or add basic E2E coverage'
220
+ : strategy.approach === 'manual-review' ? 'Manual review required'
221
+ : 'Can skip — low risk';
222
+ lines.push(`**${strategy.priority}** | Recommended: **${strategy.approach}** | Cross-impact risk: **${strategy.crossImpactRisk}**`);
223
+ lines.push(`> ${actionVerb}`);
224
+ if (strategy.rationale && !strategy.rationale.includes('Default strategy')) {
225
+ lines.push(`> Rationale: ${strategy.rationale}`);
226
+ }
227
+ if (strategy.testCategories.length > 0) {
228
+ lines.push(`> Test types to cover: ${strategy.testCategories.join(', ')}`);
229
+ }
201
230
  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('; ')}`);
231
+ // If test designs exist, show P0/P1 cases
232
+ if (td && td.testCases.length > 0) {
233
+ const p0Cases = td.testCases.filter((tc) => tc.priority === 'P0');
234
+ const p1Cases = td.testCases.filter((tc) => tc.priority === 'P1');
235
+ if (p0Cases.length > 0) {
236
+ lines.push('**P0 Must test:**');
237
+ lines.push('');
238
+ for (const tc of p0Cases) {
239
+ lines.push(`- [ ] **${tc.name}** (${tc.type})`);
240
+ if (tc.preconditions.length > 0) {
241
+ lines.push(` - Preconditions: ${tc.preconditions.join('; ')}`);
242
+ }
243
+ lines.push(` - Steps: ${tc.steps.join(' → ')}`);
244
+ lines.push(` - Expected: ${tc.expectedOutcome}`);
210
245
  }
211
- lines.push(` - Steps: ${tc.steps.join('')}`);
212
- lines.push(` - Expected: ${tc.expectedOutcome}`);
246
+ lines.push('');
213
247
  }
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}`);
248
+ if (p1Cases.length > 0) {
249
+ lines.push(`<details><summary>P1 — Should test (${p1Cases.length})</summary>`);
250
+ lines.push('');
251
+ for (const tc of p1Cases) {
252
+ lines.push(`- [ ] **${tc.name}** (${tc.type}) — ${tc.expectedOutcome}`);
253
+ }
254
+ lines.push('');
255
+ lines.push('</details>');
256
+ lines.push('');
222
257
  }
223
- lines.push('');
224
- lines.push('</details>');
225
- lines.push('');
226
258
  }
227
259
  }
228
260
  }
229
- // Covered flow expansion — collapsed by default
230
- if (coveredDesigns.length > 0) {
231
- lines.push('## Covered Flows (Regression / Expansion)');
261
+ // ── Covered flows ──
262
+ if (coveredStrategies.length > 0) {
263
+ lines.push('## Regression Check: Covered Flows (Already Have Tests)');
232
264
  lines.push('');
233
- lines.push('These flows already have specs. The test cases below extend coverage for changes in this PR.');
265
+ lines.push('These flows have existing E2E specs. The existing tests should catch regressions automatically.');
266
+ lines.push('**No manual action required** unless CI tests fail on these flows.');
234
267
  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>`);
268
+ for (const strategy of coveredStrategies) {
269
+ const td = crew.testDesigns.find((d) => d.flowId === strategy.flowId);
270
+ const caseCount = td ? td.testCases.length : 0;
271
+ const detail = caseCount > 0 ? ` | ${caseCount} cases` : '';
272
+ lines.push(`<details><summary><strong>${strategy.flowName}</strong> — ${strategy.approach}${detail} (${strategy.priority})</summary>`);
240
273
  lines.push('');
241
- for (const tc of td.testCases) {
242
- lines.push(`- [ ] **${tc.name}** (${tc.priority}, ${tc.type}) — ${tc.expectedOutcome}`);
274
+ lines.push(`Existing tests should cover this. Cross-impact risk: ${strategy.crossImpactRisk}`);
275
+ if (strategy.rationale && !strategy.rationale.includes('Default strategy')) {
276
+ lines.push(`> ${strategy.rationale}`);
277
+ }
278
+ if (strategy.testCategories.length > 0) {
279
+ lines.push(`Test categories: ${strategy.testCategories.join(', ')}`);
280
+ }
281
+ if (td && td.testCases.length > 0) {
282
+ lines.push('');
283
+ for (const tc of td.testCases) {
284
+ lines.push(`- [ ] **${tc.name}** (${tc.priority}, ${tc.type}) — ${tc.expectedOutcome}`);
285
+ }
243
286
  }
244
287
  lines.push('');
245
288
  lines.push('</details>');
246
289
  lines.push('');
247
290
  }
248
291
  }
249
- // Cross-impacts section
292
+ // ── Cross-impacts ──
250
293
  const highRisk = crew.crossImpacts.filter((ci) => ci.riskLevel === 'high');
251
294
  if (highRisk.length > 0) {
252
- lines.push('## High-Risk Cross-Impacts');
295
+ lines.push('## High-Risk Cross-Impacts — Verify Before Release');
253
296
  lines.push('');
254
- lines.push('These cross-family dependencies should be verified during release testing:');
297
+ lines.push('Changes in one area may break these related areas. Manually verify or ensure E2E tests cover both sides:');
255
298
  lines.push('');
256
299
  for (const ci of highRisk) {
257
300
  lines.push(`- **${ci.sourceFamily}** → **${ci.affectedFamily}**: ${ci.sharedDependency}`);
@@ -78,25 +78,37 @@ export async function runPlanCrewAnalysis(plan, config, args) {
78
78
  timings: result.timings,
79
79
  };
80
80
  }
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
+ }
81
87
  export function buildCrewMarkdown(crew, plan) {
82
88
  const totalCases = crew.testDesigns.reduce((n, td) => n + td.testCases.length, 0);
83
89
  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);
89
90
  const lines = [
90
- '### Crew Insights',
91
+ '### Crew Analysis — What to Test',
92
+ '',
93
+ `Crew analyzed the diff and recommends what to verify before merging.`,
91
94
  '',
92
- `Workflow: \`${crew.workflow}\``,
93
- `Impacted flows: **${crew.summary.impactedFlows}**`,
94
- `Structured test designs: **${crew.summary.testDesigns}** flows, **${totalCases}** test cases`,
95
+ `Impacted flows: **${crew.summary.impactedFlows}** | Strategy entries: **${crew.summary.strategyEntries}**`,
95
96
  ];
96
- if (gapFamilies.size > 0 && gapDesigns.length > 0) {
97
- lines.push(`Gap-focused: **${gapDesigns.length}** flows, **${gapCases}** test cases (**${gapP0Cases}** P0)`);
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)`);
98
110
  }
99
- lines.push(`Cross-impacts: **${crew.summary.crossImpacts}** (${crew.summary.highRiskCrossImpacts} high risk)`, `Estimated AI cost: **$${crew.summary.totalCostUSD.toFixed(4)}**`);
111
+ lines.push(`Estimated AI cost: **$${crew.summary.totalCostUSD.toFixed(4)}**`);
100
112
  if (crew.strategyEntries.length > 0) {
101
113
  lines.push('');
102
114
  lines.push('Top Strategy Recommendations:');
@@ -144,14 +156,18 @@ export function appendCrewToSummary(baseMarkdown, crew, plan) {
144
156
  */
145
157
  export function buildCrewTestPlan(crew, plan) {
146
158
  const gapFamilies = new Set((plan?.gapDetails ?? []).map((g) => g.id));
159
+ const hasTestDesigns = crew.testDesigns.length > 0;
147
160
  const totalCases = crew.testDesigns.reduce((n, td) => n + td.testCases.length, 0);
148
- // Split designs into gap-related and coverage-expansion
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
149
167
  const gapDesigns = [];
150
168
  const coveredDesigns = [];
151
169
  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) {
170
+ if (matchesGapFamily(td.flowId, td.flowName, gapFamilies)) {
155
171
  gapDesigns.push(td);
156
172
  }
157
173
  else {
@@ -161,90 +177,117 @@ export function buildCrewTestPlan(crew, plan) {
161
177
  const gapCases = gapDesigns.reduce((n, td) => n + td.testCases.length, 0);
162
178
  const coveredCases = coveredDesigns.reduce((n, td) => n + td.testCases.length, 0);
163
179
  const lines = [
164
- '# Crew Test Plan',
180
+ '# Crew Test Plan — What to Verify',
165
181
  '',
166
- `> Auto-generated by e2e-agents crew (\`${crew.workflow}\` workflow)`,
182
+ '> **This is a test recommendation, not a test execution report.**',
183
+ '> Crew analyzed the code diff and identified what needs to be tested.',
184
+ '> Use this plan to guide manual QA or write automated E2E tests.',
185
+ '',
186
+ `_Auto-generated by e2e-agents crew (\`${crew.workflow}\` workflow)_`,
167
187
  '',
168
188
  '## Summary',
169
189
  '',
170
190
  `| Metric | Count |`,
171
191
  `|--------|-------|`,
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 |`,
192
+ `| Gap flows **no existing tests, must verify** | ${gapStrategies.length} flows${hasTestDesigns ? `, **${gapCases} test cases**` : ''} |`,
193
+ `| Covered flows **has tests, verify no regressions** | ${coveredStrategies.length} flows${hasTestDesigns ? `, ${coveredCases} test cases` : ''} |`,
194
+ `| Total | ${crew.strategyEntries.length} flows${hasTestDesigns ? `, ${totalCases} test cases` : ''} |`,
175
195
  `| High-risk cross-impacts | ${crew.summary.highRiskCrossImpacts} |`,
176
196
  `| AI cost | $${crew.summary.totalCostUSD.toFixed(4)} |`,
177
197
  '',
178
198
  ];
179
- // Priority action items
180
- if (gapDesigns.length > 0) {
181
- lines.push('## Priority: Gap Flows (Missing Tests)');
199
+ // ── Gap flows ──
200
+ if (gapStrategies.length > 0) {
201
+ lines.push('## Action Required: Gap Flows (No Existing Tests)');
182
202
  lines.push('');
183
- lines.push('These flows have **no existing E2E coverage** and should be addressed first.');
203
+ lines.push('These flows have **no automated E2E coverage**. Before merging, either:');
204
+ lines.push('1. **Write E2E tests** for the critical scenarios below, or');
205
+ lines.push('2. **Manually verify** each flow works as expected');
184
206
  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}`);
207
+ for (const strategy of gapStrategies) {
208
+ const td = crew.testDesigns.find((d) => d.flowId === strategy.flowId);
209
+ lines.push(`### ${strategy.flowName}`);
192
210
  lines.push('');
193
- lines.push(`Strategy: **${approach}** | Cross-impact risk: **${risk}** | ${td.testCases.length} cases (${p0Cases.length} P0, ${p1Cases.length} P1)`);
211
+ const actionVerb = strategy.approach === 'full-test' ? 'Write full E2E test or verify manually'
212
+ : strategy.approach === 'smoke-test' ? 'Smoke-test manually or add basic E2E coverage'
213
+ : strategy.approach === 'manual-review' ? 'Manual review required'
214
+ : 'Can skip — low risk';
215
+ lines.push(`**${strategy.priority}** | Recommended: **${strategy.approach}** | Cross-impact risk: **${strategy.crossImpactRisk}**`);
216
+ lines.push(`> ${actionVerb}`);
217
+ if (strategy.rationale && !strategy.rationale.includes('Default strategy')) {
218
+ lines.push(`> Rationale: ${strategy.rationale}`);
219
+ }
220
+ if (strategy.testCategories.length > 0) {
221
+ lines.push(`> Test types to cover: ${strategy.testCategories.join(', ')}`);
222
+ }
194
223
  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('; ')}`);
224
+ // If test designs exist, show P0/P1 cases
225
+ if (td && td.testCases.length > 0) {
226
+ const p0Cases = td.testCases.filter((tc) => tc.priority === 'P0');
227
+ const p1Cases = td.testCases.filter((tc) => tc.priority === 'P1');
228
+ if (p0Cases.length > 0) {
229
+ lines.push('**P0 Must test:**');
230
+ lines.push('');
231
+ for (const tc of p0Cases) {
232
+ lines.push(`- [ ] **${tc.name}** (${tc.type})`);
233
+ if (tc.preconditions.length > 0) {
234
+ lines.push(` - Preconditions: ${tc.preconditions.join('; ')}`);
235
+ }
236
+ lines.push(` - Steps: ${tc.steps.join(' → ')}`);
237
+ lines.push(` - Expected: ${tc.expectedOutcome}`);
203
238
  }
204
- lines.push(` - Steps: ${tc.steps.join('')}`);
205
- lines.push(` - Expected: ${tc.expectedOutcome}`);
239
+ lines.push('');
206
240
  }
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}`);
241
+ if (p1Cases.length > 0) {
242
+ lines.push(`<details><summary>P1 — Should test (${p1Cases.length})</summary>`);
243
+ lines.push('');
244
+ for (const tc of p1Cases) {
245
+ lines.push(`- [ ] **${tc.name}** (${tc.type}) — ${tc.expectedOutcome}`);
246
+ }
247
+ lines.push('');
248
+ lines.push('</details>');
249
+ lines.push('');
215
250
  }
216
- lines.push('');
217
- lines.push('</details>');
218
- lines.push('');
219
251
  }
220
252
  }
221
253
  }
222
- // Covered flow expansion — collapsed by default
223
- if (coveredDesigns.length > 0) {
224
- lines.push('## Covered Flows (Regression / Expansion)');
254
+ // ── Covered flows ──
255
+ if (coveredStrategies.length > 0) {
256
+ lines.push('## Regression Check: Covered Flows (Already Have Tests)');
225
257
  lines.push('');
226
- lines.push('These flows already have specs. The test cases below extend coverage for changes in this PR.');
258
+ lines.push('These flows have existing E2E specs. The existing tests should catch regressions automatically.');
259
+ lines.push('**No manual action required** unless CI tests fail on these flows.');
227
260
  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>`);
261
+ for (const strategy of coveredStrategies) {
262
+ const td = crew.testDesigns.find((d) => d.flowId === strategy.flowId);
263
+ const caseCount = td ? td.testCases.length : 0;
264
+ const detail = caseCount > 0 ? ` | ${caseCount} cases` : '';
265
+ lines.push(`<details><summary><strong>${strategy.flowName}</strong> — ${strategy.approach}${detail} (${strategy.priority})</summary>`);
233
266
  lines.push('');
234
- for (const tc of td.testCases) {
235
- lines.push(`- [ ] **${tc.name}** (${tc.priority}, ${tc.type}) — ${tc.expectedOutcome}`);
267
+ lines.push(`Existing tests should cover this. Cross-impact risk: ${strategy.crossImpactRisk}`);
268
+ if (strategy.rationale && !strategy.rationale.includes('Default strategy')) {
269
+ lines.push(`> ${strategy.rationale}`);
270
+ }
271
+ if (strategy.testCategories.length > 0) {
272
+ lines.push(`Test categories: ${strategy.testCategories.join(', ')}`);
273
+ }
274
+ if (td && td.testCases.length > 0) {
275
+ lines.push('');
276
+ for (const tc of td.testCases) {
277
+ lines.push(`- [ ] **${tc.name}** (${tc.priority}, ${tc.type}) — ${tc.expectedOutcome}`);
278
+ }
236
279
  }
237
280
  lines.push('');
238
281
  lines.push('</details>');
239
282
  lines.push('');
240
283
  }
241
284
  }
242
- // Cross-impacts section
285
+ // ── Cross-impacts ──
243
286
  const highRisk = crew.crossImpacts.filter((ci) => ci.riskLevel === 'high');
244
287
  if (highRisk.length > 0) {
245
- lines.push('## High-Risk Cross-Impacts');
288
+ lines.push('## High-Risk Cross-Impacts — Verify Before Release');
246
289
  lines.push('');
247
- lines.push('These cross-family dependencies should be verified during release testing:');
290
+ lines.push('Changes in one area may break these related areas. Manually verify or ensure E2E tests cover both sides:');
248
291
  lines.push('');
249
292
  for (const ci of highRisk) {
250
293
  lines.push(`- **${ci.sourceFamily}** → **${ci.affectedFamily}**: ${ci.sharedDependency}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yasserkhanorg/e2e-agents",
3
- "version": "1.8.3",
3
+ "version": "1.8.5",
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",