@yasserkhanorg/e2e-agents 0.5.3 → 0.5.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":"ai_mapping.d.ts","sourceRoot":"","sources":["../../src/agent/ai_mapping.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,eAAe,CAAC;AAC9C,OAAO,KAAK,EAAC,qBAAqB,EAAC,MAAM,aAAa,CAAC;AACvD,OAAO,KAAK,EAAC,YAAY,EAAE,QAAQ,EAAC,MAAM,YAAY,CAAC;AA2BvD,MAAM,WAAW,eAAe;IAC5B,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,EAAE,OAAO,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,YAAY,EAAE,CAAC;IACzB,QAAQ,EAAE,MAAM,EAAE,CAAC;CACtB;AAoND,wBAAsB,iBAAiB,CACnC,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,qBAAqB,EAC7B,KAAK,EAAE,UAAU,EAAE,EACnB,KAAK,EAAE,QAAQ,EAAE,GAClB,OAAO,CAAC,eAAe,CAAC,CA+L1B"}
1
+ {"version":3,"file":"ai_mapping.d.ts","sourceRoot":"","sources":["../../src/agent/ai_mapping.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,eAAe,CAAC;AAC9C,OAAO,KAAK,EAAC,qBAAqB,EAAC,MAAM,aAAa,CAAC;AACvD,OAAO,KAAK,EAAC,YAAY,EAAE,QAAQ,EAAC,MAAM,YAAY,CAAC;AA2BvD,MAAM,WAAW,eAAe;IAC5B,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,EAAE,OAAO,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,YAAY,EAAE,CAAC;IACzB,QAAQ,EAAE,MAAM,EAAE,CAAC;CACtB;AAiOD,wBAAsB,iBAAiB,CACnC,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,qBAAqB,EAC7B,KAAK,EAAE,UAAU,EAAE,EACnB,KAAK,EAAE,QAAQ,EAAE,GAClB,OAAO,CAAC,eAAe,CAAC,CAqN1B"}
@@ -164,7 +164,19 @@ function selectCandidateTests(flows, tests, maxCandidateTests) {
164
164
  .sort((a, b) => b.score - a.score || a.path.localeCompare(b.path))
165
165
  .slice(0, perFlowLimit);
166
166
  if (strongCandidates.length === 0) {
167
- if (scored.length > 0) {
167
+ // Exact-name fallback: if the flow ID has no effective keywords (all tokens are
168
+ // low-signal, e.g. view_user_group_modal), look for a test whose path contains
169
+ // the exact flow ID as a directory name or filename without extension.
170
+ const exactMatchPath = normalizedTests.find((testPath) => {
171
+ const segments = testPath.split('/');
172
+ return segments.some((seg) => seg === flow.id || seg.replace(/\.spec\.[tj]sx?$/, '') === flow.id);
173
+ });
174
+ if (exactMatchPath) {
175
+ byFlow.set(flow.id, new Set([exactMatchPath]));
176
+ evidence.push({ flowId: flow.id, candidates: [{ path: exactMatchPath, score: 999, matchedKeywords: [flow.id] }] });
177
+ selected.add(exactMatchPath);
178
+ }
179
+ else if (scored.length > 0) {
168
180
  warnings.push(`AI mapping withheld weak path-only candidates for ${flow.id}; traceability evidence is required to reuse existing tests.`);
169
181
  }
170
182
  continue;
@@ -359,6 +371,25 @@ async function mapAITestsToFlows(appRoot, testsRoot, config, flows, tests) {
359
371
  matchedTests.add(testPath);
360
372
  }
361
373
  }
374
+ // Post-AI exact-name fallback: for any flow still uncovered, if a candidate test
375
+ // was matched by exact name (score 999), map it directly regardless of AI confidence.
376
+ for (const flow of prioritizedFlows) {
377
+ if (mapped.has(flow.id)) {
378
+ continue;
379
+ }
380
+ const candidates = candidateSelection.byFlow.get(flow.id);
381
+ if (!candidates) {
382
+ continue;
383
+ }
384
+ const exactMatch = Array.from(candidates).find((testPath) => {
385
+ const segments = testPath.split('/');
386
+ return segments.some((seg) => seg === flow.id || seg.replace(/\.spec\.[tj]sx?$/, '') === flow.id);
387
+ });
388
+ if (exactMatch) {
389
+ mapped.set(flow.id, [exactMatch]);
390
+ matchedTests.add(exactMatch);
391
+ }
392
+ }
362
393
  const coverage = buildCoverage(flows, mapped);
363
394
  if (mapped.size === 0) {
364
395
  warnings.push(`AI mapping returned no valid test mappings (${provider.name}).`);
package/dist/cli.js CHANGED
@@ -998,9 +998,22 @@ async function main() {
998
998
  return;
999
999
  }
1000
1000
  if (args.command === 'suggest' || args.command === 'plan') {
1001
- await (0, runner_js_1.runImpact)(config, { apply: args.apply });
1002
1001
  const reportRoot = config.testsRoot || config.path;
1003
1002
  const impactPath = (0, path_1.join)(reportRoot, '.e2e-ai-agents', 'impact.json');
1003
+ try {
1004
+ await (0, runner_js_1.runImpact)(config, { apply: args.apply });
1005
+ }
1006
+ catch (err) {
1007
+ // If impact analysis already ran (e.g. a prior CI step wrote impact.json),
1008
+ // fall back to that data rather than failing the plan step.
1009
+ if ((0, fs_1.existsSync)(impactPath)) {
1010
+ // eslint-disable-next-line no-console
1011
+ console.warn(`Impact re-run failed (${err instanceof Error ? err.message : String(err)}); using existing impact.json.`);
1012
+ }
1013
+ else {
1014
+ throw err;
1015
+ }
1016
+ }
1004
1017
  if (!(0, fs_1.existsSync)(impactPath)) {
1005
1018
  throw new Error(`Impact report not found at ${impactPath}`);
1006
1019
  }
@@ -161,7 +161,19 @@ function selectCandidateTests(flows, tests, maxCandidateTests) {
161
161
  .sort((a, b) => b.score - a.score || a.path.localeCompare(b.path))
162
162
  .slice(0, perFlowLimit);
163
163
  if (strongCandidates.length === 0) {
164
- if (scored.length > 0) {
164
+ // Exact-name fallback: if the flow ID has no effective keywords (all tokens are
165
+ // low-signal, e.g. view_user_group_modal), look for a test whose path contains
166
+ // the exact flow ID as a directory name or filename without extension.
167
+ const exactMatchPath = normalizedTests.find((testPath) => {
168
+ const segments = testPath.split('/');
169
+ return segments.some((seg) => seg === flow.id || seg.replace(/\.spec\.[tj]sx?$/, '') === flow.id);
170
+ });
171
+ if (exactMatchPath) {
172
+ byFlow.set(flow.id, new Set([exactMatchPath]));
173
+ evidence.push({ flowId: flow.id, candidates: [{ path: exactMatchPath, score: 999, matchedKeywords: [flow.id] }] });
174
+ selected.add(exactMatchPath);
175
+ }
176
+ else if (scored.length > 0) {
165
177
  warnings.push(`AI mapping withheld weak path-only candidates for ${flow.id}; traceability evidence is required to reuse existing tests.`);
166
178
  }
167
179
  continue;
@@ -356,6 +368,25 @@ export async function mapAITestsToFlows(appRoot, testsRoot, config, flows, tests
356
368
  matchedTests.add(testPath);
357
369
  }
358
370
  }
371
+ // Post-AI exact-name fallback: for any flow still uncovered, if a candidate test
372
+ // was matched by exact name (score 999), map it directly regardless of AI confidence.
373
+ for (const flow of prioritizedFlows) {
374
+ if (mapped.has(flow.id)) {
375
+ continue;
376
+ }
377
+ const candidates = candidateSelection.byFlow.get(flow.id);
378
+ if (!candidates) {
379
+ continue;
380
+ }
381
+ const exactMatch = Array.from(candidates).find((testPath) => {
382
+ const segments = testPath.split('/');
383
+ return segments.some((seg) => seg === flow.id || seg.replace(/\.spec\.[tj]sx?$/, '') === flow.id);
384
+ });
385
+ if (exactMatch) {
386
+ mapped.set(flow.id, [exactMatch]);
387
+ matchedTests.add(exactMatch);
388
+ }
389
+ }
359
390
  const coverage = buildCoverage(flows, mapped);
360
391
  if (mapped.size === 0) {
361
392
  warnings.push(`AI mapping returned no valid test mappings (${provider.name}).`);
package/dist/esm/cli.js CHANGED
@@ -996,9 +996,22 @@ async function main() {
996
996
  return;
997
997
  }
998
998
  if (args.command === 'suggest' || args.command === 'plan') {
999
- await runImpact(config, { apply: args.apply });
1000
999
  const reportRoot = config.testsRoot || config.path;
1001
1000
  const impactPath = join(reportRoot, '.e2e-ai-agents', 'impact.json');
1001
+ try {
1002
+ await runImpact(config, { apply: args.apply });
1003
+ }
1004
+ catch (err) {
1005
+ // If impact analysis already ran (e.g. a prior CI step wrote impact.json),
1006
+ // fall back to that data rather than failing the plan step.
1007
+ if (existsSync(impactPath)) {
1008
+ // eslint-disable-next-line no-console
1009
+ console.warn(`Impact re-run failed (${err instanceof Error ? err.message : String(err)}); using existing impact.json.`);
1010
+ }
1011
+ else {
1012
+ throw err;
1013
+ }
1014
+ }
1002
1015
  if (!existsSync(impactPath)) {
1003
1016
  throw new Error(`Impact report not found at ${impactPath}`);
1004
1017
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yasserkhanorg/e2e-agents",
3
- "version": "0.5.3",
3
+ "version": "0.5.5",
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",