@yasserkhanorg/e2e-agents 0.6.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -193,7 +193,7 @@ function buildRecommendedTests(impact) {
193
193
  }
194
194
  return tests;
195
195
  }
196
- export function buildPlanFromImpact(impact, policyOverride) {
196
+ export function buildPlanFromImpact(impact, policyOverride, aiEnrichment) {
197
197
  const policy = { ...DEFAULT_POLICY, ...(policyOverride || {}) };
198
198
  const confidence = computeConfidence(impact);
199
199
  const runSetResult = pickRunSet(impact, confidence, policy);
@@ -201,23 +201,61 @@ export function buildPlanFromImpact(impact, policyOverride) {
201
201
  const enforcement = evaluateEnforcement(decision, policy);
202
202
  const gaps = getGaps(impact);
203
203
  const partialGaps = getPartialGaps(impact);
204
- const gapDetails = gaps.map((f) => ({
205
- id: featureLabel(f),
206
- name: featureLabel(f),
207
- priority: f.priority,
208
- reasons: [`No Playwright or Cypress tests found for ${featureLabel(f)}`],
209
- files: f.changedFiles.slice(0, 6),
210
- missingScenarios: f.userFlows.length > 0 ? f.userFlows.slice(0, 5) : undefined,
211
- }));
204
+ // Build two separate lookup maps from aiEnrichment: one by featureId, one by familyId.
205
+ // The familyId map stores only the FIRST feature encountered to avoid last-write-wins collisions.
206
+ const aiFeatureByFeatureId = new Map();
207
+ const aiFeatureByFamilyId = new Map();
208
+ if (aiEnrichment) {
209
+ for (const ef of aiEnrichment.enrichedFeatures) {
210
+ if (ef.featureId) {
211
+ aiFeatureByFeatureId.set(ef.featureId, ef);
212
+ }
213
+ if (ef.familyId && !aiFeatureByFamilyId.has(ef.familyId)) {
214
+ aiFeatureByFamilyId.set(ef.familyId, ef);
215
+ }
216
+ }
217
+ }
218
+ const gapDetails = gaps.map((f) => {
219
+ const label = featureLabel(f);
220
+ const aiFeature = f.featureId
221
+ ? (aiFeatureByFeatureId.get(f.featureId) ?? aiFeatureByFamilyId.get(f.familyId))
222
+ : aiFeatureByFamilyId.get(f.familyId);
223
+ const baseReasons = [`No Playwright or Cypress tests found for ${label}`];
224
+ const reasons = aiFeature && aiFeature.aiReasons.length > 0
225
+ ? [...baseReasons, ...aiFeature.aiReasons]
226
+ : baseReasons;
227
+ const missingScenarios = aiFeature && aiFeature.aiMissingScenarios.length > 0
228
+ ? aiFeature.aiMissingScenarios
229
+ : (f.userFlows.length > 0 ? f.userFlows.slice(0, 5) : undefined);
230
+ return {
231
+ id: label,
232
+ name: label,
233
+ priority: f.priority,
234
+ reasons,
235
+ files: f.changedFiles.slice(0, 6),
236
+ missingScenarios,
237
+ source: aiFeature ? 'ai+deterministic' : 'deterministic',
238
+ };
239
+ });
212
240
  // Add partial gaps as advisory info
213
241
  for (const f of partialGaps) {
214
- const source = f.playwrightSpecs.length > 0 ? 'Cypress' : 'Playwright';
242
+ const coverageType = f.playwrightSpecs.length > 0 ? 'Cypress' : 'Playwright';
243
+ const hasOpposite = f.playwrightSpecs.length > 0 ? 'Playwright' : 'Cypress';
244
+ const label = featureLabel(f);
245
+ const aiFeature = f.featureId
246
+ ? (aiFeatureByFeatureId.get(f.featureId) ?? aiFeatureByFamilyId.get(f.familyId))
247
+ : aiFeatureByFamilyId.get(f.familyId);
248
+ const baseReasons = [`Missing ${coverageType} tests for ${label} (has ${hasOpposite} only)`];
249
+ const reasons = aiFeature && aiFeature.aiReasons.length > 0
250
+ ? [...baseReasons, ...aiFeature.aiReasons]
251
+ : baseReasons;
215
252
  gapDetails.push({
216
- id: featureLabel(f),
217
- name: `${featureLabel(f)} (partial)`,
253
+ id: label,
254
+ name: `${label} (partial)`,
218
255
  priority: f.priority,
219
- reasons: [`Missing ${source} tests for ${featureLabel(f)} (has ${f.playwrightSpecs.length > 0 ? 'Playwright' : 'Cypress'} only)`],
256
+ reasons,
220
257
  files: f.changedFiles.slice(0, 6),
258
+ source: aiFeature ? 'ai+deterministic' : 'deterministic',
221
259
  });
222
260
  }
223
261
  const coveredFlows = impact.impactedFeatures
@@ -237,11 +275,12 @@ export function buildPlanFromImpact(impact, policyOverride) {
237
275
  const p1 = impact.impactedFeatures.filter((f) => f.priority === 'P1').length;
238
276
  const p2 = impact.impactedFeatures.filter((f) => f.priority === 'P2').length;
239
277
  const runId = `plan-${Date.now().toString(36)}`;
278
+ const planSource = aiEnrichment ? 'ai+deterministic' : 'impact';
240
279
  return {
241
280
  schemaVersion: '1.0.0',
242
281
  runId,
243
282
  generatedAt: new Date().toISOString(),
244
- source: 'impact',
283
+ source: planSource,
245
284
  runSet: runSetResult.runSet,
246
285
  confidence,
247
286
  reasons: runSetResult.reasons,
@@ -292,12 +331,18 @@ export function renderCiSummaryMarkdown(plan) {
292
331
  lines.push(`The following ${uncoveredP0P1Flows} feature(s) have no test coverage and must be covered before merge:`);
293
332
  lines.push('');
294
333
  for (const gap of plan.gapDetails.filter((g) => !g.name.includes('(partial)'))) {
295
- lines.push(`- **${gap.name}** [${gap.priority}]`);
334
+ const aiLabel = gap.source === 'ai+deterministic' ? ' ✦ AI-enriched' : '';
335
+ lines.push(`- **${gap.name}** [${gap.priority}]${aiLabel}`);
296
336
  if (gap.missingScenarios && gap.missingScenarios.length > 0) {
297
337
  for (const scenario of gap.missingScenarios) {
298
338
  lines.push(` - ${scenario}`);
299
339
  }
300
340
  }
341
+ // Show AI-provided reasons (skip the first deterministic reason which is always included)
342
+ const aiReasons = gap.reasons.slice(1);
343
+ if (aiReasons.length > 0) {
344
+ lines.push(` - *AI insight*: ${aiReasons.join('; ')}`);
345
+ }
301
346
  }
302
347
  }
303
348
  if (plan.coveredFlows.length > 0) {
@@ -154,7 +154,8 @@ export function loadRouteFamilyManifest(testsRoot, config) {
154
154
  }
155
155
  }
156
156
  if (config?.strict) {
157
- throw new Error('Route family manifest is required but not found. Create .e2e-ai-agents/route-families.json');
157
+ // eslint-disable-next-line no-console
158
+ console.warn('[e2e-agents] Route family manifest not found. The manifest is optional context for AI enrichment — create .e2e-ai-agents/route-families.json to enable family-level routing hints.');
158
159
  }
159
160
  return null;
160
161
  }
@@ -1 +1 @@
1
- {"version":3,"file":"route_families.d.ts","sourceRoot":"","sources":["../../src/knowledge/route_families.ts"],"names":[],"mappings":"AAMA,MAAM,MAAM,eAAe,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;AAEjD,MAAM,WAAW,YAAY;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;IAC3B,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,QAAQ,CAAC,EAAE,eAAe,CAAC;IAC3B,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;CACxB;AAED,MAAM,WAAW,WAAW;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;IAC3B,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,QAAQ,CAAC,EAAE,eAAe,CAAC;IAC3B,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,QAAQ,CAAC,EAAE,YAAY,EAAE,CAAC;CAC7B;AAED,MAAM,WAAW,mBAAmB;IAChC,QAAQ,EAAE,WAAW,EAAE,CAAC;IACxB,MAAM,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,WAAW;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,KAAK,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAC,CAAC,CAAC;CACvD;AAED,MAAM,WAAW,iBAAiB;IAC9B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,CAAC,EAAE,OAAO,CAAC;CACpB;AA+HD,wBAAgB,uBAAuB,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,iBAAiB,GAAG,mBAAmB,GAAG,IAAI,CAyCjH;AAED,wBAAgB,mBAAmB,CAAC,YAAY,EAAE,MAAM,EAAE,EAAE,QAAQ,EAAE,mBAAmB,GAAG,WAAW,EAAE,CAsCxG;AAED,wBAAgB,aAAa,CAAC,QAAQ,EAAE,mBAAmB,EAAE,QAAQ,EAAE,MAAM,GAAG,WAAW,GAAG,SAAS,CAEtG;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS,CAE/F;AAED,wBAAgB,qBAAqB,CACjC,QAAQ,EAAE,mBAAmB,EAC7B,OAAO,EAAE;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAC,GAC5C,MAAM,EAAE,CAYV;AAED,wBAAgB,4BAA4B,CACxC,QAAQ,EAAE,mBAAmB,EAC7B,OAAO,EAAE;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAC,GAC5C,MAAM,EAAE,CAYV;AAED,wBAAgB,qBAAqB,CACjC,QAAQ,EAAE,mBAAmB,EAC7B,OAAO,EAAE;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAC,GAC5C,eAAe,CAYjB;AAED,wBAAgB,sBAAsB,CAClC,QAAQ,EAAE,mBAAmB,EAC7B,OAAO,EAAE;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAC,GAC5C,MAAM,EAAE,CAYV;AAED,wBAAgB,mBAAmB,CAC/B,QAAQ,EAAE,mBAAmB,EAC7B,OAAO,EAAE;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAC,GAC5C,MAAM,EAAE,CAYV;AAED,wBAAgB,kBAAkB,IAAI,IAAI,CAEzC"}
1
+ {"version":3,"file":"route_families.d.ts","sourceRoot":"","sources":["../../src/knowledge/route_families.ts"],"names":[],"mappings":"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"}
@@ -166,7 +166,8 @@ function loadRouteFamilyManifest(testsRoot, config) {
166
166
  }
167
167
  }
168
168
  if (config?.strict) {
169
- throw new Error('Route family manifest is required but not found. Create .e2e-ai-agents/route-families.json');
169
+ // eslint-disable-next-line no-console
170
+ console.warn('[e2e-agents] Route family manifest not found. The manifest is optional context for AI enrichment — create .e2e-ai-agents/route-families.json to enable family-level routing hints.');
170
171
  }
171
172
  return null;
172
173
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yasserkhanorg/e2e-agents",
3
- "version": "0.6.0",
3
+ "version": "0.7.0",
4
4
  "description": "Pluggable LLM provider library for AI-powered test automation. Use Claude, Ollama, or your own LLM. Integrate with Playwright, Jest, or any test framework. MCP server for test agents, cost tracking, and hybrid provider mode.",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/esm/index.js",