@yasserkhanorg/e2e-agents 1.8.3 → 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_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,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"}
@@ -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
98
  '### Crew Insights',
98
99
  '',
99
100
  `Workflow: \`${crew.workflow}\``,
100
101
  `Impacted flows: **${crew.summary.impactedFlows}**`,
101
- `Structured test designs: **${crew.summary.testDesigns}** flows, **${totalCases}** test cases`,
102
+ `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
+ }
105
114
  }
106
- lines.push(`Cross-impacts: **${crew.summary.crossImpacts}** (${crew.summary.highRiskCrossImpacts} high risk)`, `Estimated AI cost: **$${crew.summary.totalCostUSD.toFixed(4)}**`);
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)}**`);
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 {
@@ -176,77 +192,92 @@ function buildCrewTestPlan(crew, plan) {
176
192
  '',
177
193
  `| Metric | Count |`,
178
194
  `|--------|-------|`,
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 |`,
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` : ''} |`,
182
198
  `| High-risk cross-impacts | ${crew.summary.highRiskCrossImpacts} |`,
183
199
  `| AI cost | $${crew.summary.totalCostUSD.toFixed(4)} |`,
184
200
  '',
185
201
  ];
186
- // Priority action items
187
- if (gapDesigns.length > 0) {
202
+ // ── Gap flows ──
203
+ if (gapStrategies.length > 0) {
188
204
  lines.push('## Priority: Gap Flows (Missing Tests)');
189
205
  lines.push('');
190
206
  lines.push('These flows have **no existing E2E coverage** and should be addressed first.');
191
207
  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}`);
208
+ for (const strategy of gapStrategies) {
209
+ const td = crew.testDesigns.find((d) => d.flowId === strategy.flowId);
210
+ lines.push(`### ${strategy.flowName}`);
199
211
  lines.push('');
200
- lines.push(`Strategy: **${approach}** | Cross-impact risk: **${risk}** | ${td.testCases.length} cases (${p0Cases.length} P0, ${p1Cases.length} P1)`);
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
+ }
201
219
  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('; ')}`);
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}`);
210
234
  }
211
- lines.push(` - Steps: ${tc.steps.join('')}`);
212
- lines.push(` - Expected: ${tc.expectedOutcome}`);
235
+ lines.push('');
213
236
  }
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}`);
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('');
222
246
  }
223
- lines.push('');
224
- lines.push('</details>');
225
- lines.push('');
226
247
  }
227
248
  }
228
249
  }
229
- // Covered flow expansion — collapsed by default
230
- if (coveredDesigns.length > 0) {
250
+ // ── Covered flows ──
251
+ if (coveredStrategies.length > 0) {
231
252
  lines.push('## Covered Flows (Regression / Expansion)');
232
253
  lines.push('');
233
- lines.push('These flows already have specs. The test cases below extend coverage for changes in this PR.');
254
+ lines.push('These flows already have specs. Verify changes haven\'t introduced regressions.');
234
255
  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>`);
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>`);
240
261
  lines.push('');
241
- for (const tc of td.testCases) {
242
- lines.push(`- [ ] **${tc.name}** (${tc.priority}, ${tc.type}) — ${tc.expectedOutcome}`);
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
+ }
243
274
  }
244
275
  lines.push('');
245
276
  lines.push('</details>');
246
277
  lines.push('');
247
278
  }
248
279
  }
249
- // Cross-impacts section
280
+ // ── Cross-impacts ──
250
281
  const highRisk = crew.crossImpacts.filter((ci) => ci.riskLevel === 'high');
251
282
  if (highRisk.length > 0) {
252
283
  lines.push('## High-Risk Cross-Impacts');
@@ -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
91
  '### Crew Insights',
91
92
  '',
92
93
  `Workflow: \`${crew.workflow}\``,
93
94
  `Impacted flows: **${crew.summary.impactedFlows}**`,
94
- `Structured test designs: **${crew.summary.testDesigns}** flows, **${totalCases}** test cases`,
95
+ `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
+ }
98
107
  }
99
- lines.push(`Cross-impacts: **${crew.summary.crossImpacts}** (${crew.summary.highRiskCrossImpacts} high risk)`, `Estimated AI cost: **$${crew.summary.totalCostUSD.toFixed(4)}**`);
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)}**`);
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 {
@@ -169,77 +185,92 @@ export function buildCrewTestPlan(crew, plan) {
169
185
  '',
170
186
  `| Metric | Count |`,
171
187
  `|--------|-------|`,
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 |`,
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` : ''} |`,
175
191
  `| High-risk cross-impacts | ${crew.summary.highRiskCrossImpacts} |`,
176
192
  `| AI cost | $${crew.summary.totalCostUSD.toFixed(4)} |`,
177
193
  '',
178
194
  ];
179
- // Priority action items
180
- if (gapDesigns.length > 0) {
195
+ // ── Gap flows ──
196
+ if (gapStrategies.length > 0) {
181
197
  lines.push('## Priority: Gap Flows (Missing Tests)');
182
198
  lines.push('');
183
199
  lines.push('These flows have **no existing E2E coverage** and should be addressed first.');
184
200
  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}`);
201
+ for (const strategy of gapStrategies) {
202
+ const td = crew.testDesigns.find((d) => d.flowId === strategy.flowId);
203
+ lines.push(`### ${strategy.flowName}`);
192
204
  lines.push('');
193
- lines.push(`Strategy: **${approach}** | Cross-impact risk: **${risk}** | ${td.testCases.length} cases (${p0Cases.length} P0, ${p1Cases.length} P1)`);
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
+ }
194
212
  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('; ')}`);
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}`);
203
227
  }
204
- lines.push(` - Steps: ${tc.steps.join('')}`);
205
- lines.push(` - Expected: ${tc.expectedOutcome}`);
228
+ lines.push('');
206
229
  }
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}`);
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('');
215
239
  }
216
- lines.push('');
217
- lines.push('</details>');
218
- lines.push('');
219
240
  }
220
241
  }
221
242
  }
222
- // Covered flow expansion — collapsed by default
223
- if (coveredDesigns.length > 0) {
243
+ // ── Covered flows ──
244
+ if (coveredStrategies.length > 0) {
224
245
  lines.push('## Covered Flows (Regression / Expansion)');
225
246
  lines.push('');
226
- lines.push('These flows already have specs. The test cases below extend coverage for changes in this PR.');
247
+ lines.push('These flows already have specs. Verify changes haven\'t introduced regressions.');
227
248
  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>`);
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>`);
233
254
  lines.push('');
234
- for (const tc of td.testCases) {
235
- lines.push(`- [ ] **${tc.name}** (${tc.priority}, ${tc.type}) — ${tc.expectedOutcome}`);
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
+ }
236
267
  }
237
268
  lines.push('');
238
269
  lines.push('</details>');
239
270
  lines.push('');
240
271
  }
241
272
  }
242
- // Cross-impacts section
273
+ // ── Cross-impacts ──
243
274
  const highRisk = crew.crossImpacts.filter((ci) => ci.riskLevel === 'high');
244
275
  if (highRisk.length > 0) {
245
276
  lines.push('## High-Risk Cross-Impacts');
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.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",