@yasserkhanorg/e2e-agents 0.5.12 → 0.5.13

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;AA4BvD,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;AAuTD,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,CA4O1B"}
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;AA4BvD,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;AAyWD,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,CA4O1B"}
@@ -139,6 +139,21 @@ function isStrongCandidateMatch(flow, matchedKeywords) {
139
139
  const keywords = flowKeywords(flow);
140
140
  return keywords.length === 1 && matchedKeywords[0].length >= MIN_SINGLE_KEYWORD_LENGTH;
141
141
  }
142
+ // Stop-words excluded from content-fallback keyword matching.
143
+ const CONTENT_FALLBACK_STOP_WORDS = new Set(['and', 'for', 'the', 'to', 'of', 'on', 'at', 'with', 'in', 'a', 'an']);
144
+ // Returns raw (unfiltered) tokens for flows where flowKeywords() returns nothing.
145
+ // Used exclusively for content-title matching when all standard keywords are low-signal.
146
+ // Empty array is returned when the flow already has effective path keywords.
147
+ function contentFallbackKeywords(flow) {
148
+ if (flowKeywords(flow).length > 0) {
149
+ return [];
150
+ }
151
+ return (0, utils_js_1.uniqueTokens)([
152
+ ...(0, utils_js_1.tokenize)(flow.id || ''),
153
+ ...(0, utils_js_1.tokenize)(flow.name || ''),
154
+ ...(flow.keywords || []),
155
+ ]).filter((k) => k.length >= 3 && !CONTENT_FALLBACK_STOP_WORDS.has(k));
156
+ }
142
157
  // Extract test/describe/it title strings from file content for semantic matching.
143
158
  function extractTestTitles(content) {
144
159
  const titles = [];
@@ -214,7 +229,39 @@ function selectCandidateTests(flows, tests, maxCandidateTests) {
214
229
  }
215
230
  contentCandidates.sort((a, b) => b.score - a.score || a.path.localeCompare(b.path));
216
231
  }
217
- const allCandidates = [...strongCandidates, ...contentCandidates.slice(0, perFlowLimit)];
232
+ // Pass 2b: comprehensive fallback for all-low-signal flows.
233
+ // When flowKeywords() is empty (all tokens are low-signal), flowKeywords-based
234
+ // matching in both Pass 1 and Pass 2 yields nothing. As a last resort, search
235
+ // test titles using the raw unfiltered tokens from the flow ID/name, but require
236
+ // ALL tokens to match simultaneously — they are individually weak signals so the
237
+ // full conjunction is needed to establish behavioral coverage evidence.
238
+ const fallbackCandidates = [];
239
+ if (strongCandidates.length === 0 && contentCandidates.length === 0) {
240
+ const fallbackKws = contentFallbackKeywords(flow);
241
+ if (fallbackKws.length > 0) {
242
+ for (const testPath of normalizedTests) {
243
+ const testFile = testByNormalizedPath.get(testPath);
244
+ if (!testFile?.content) {
245
+ continue;
246
+ }
247
+ const haystack = extractTestTitles(testFile.content).toLowerCase();
248
+ if (!haystack) {
249
+ continue;
250
+ }
251
+ const matched = fallbackKws.filter((k) => haystack.includes(k));
252
+ if (matched.length < fallbackKws.length) {
253
+ continue; // all tokens must appear in at least one test title
254
+ }
255
+ fallbackCandidates.push({ path: testPath, score: matched.length, matchedKeywords: matched });
256
+ }
257
+ fallbackCandidates.sort((a, b) => b.score - a.score || a.path.localeCompare(b.path));
258
+ }
259
+ }
260
+ const allCandidates = [
261
+ ...strongCandidates,
262
+ ...contentCandidates.slice(0, perFlowLimit),
263
+ ...fallbackCandidates.slice(0, perFlowLimit),
264
+ ];
218
265
  if (allCandidates.length === 0) {
219
266
  // Exact-name fallback: if the flow ID has no effective keywords (all tokens are
220
267
  // low-signal, e.g. view_user_group_modal), look for a test whose path contains
@@ -371,7 +418,7 @@ async function mapAITestsToFlows(appRoot, testsRoot, config, flows, tests) {
371
418
  'Rules:',
372
419
  '- Keep at most 5 tests per flow.',
373
420
  '- Use exact flowId values from FLOWS.',
374
- '- Only map a test when its path clearly matches the flow scenario. Generic subsystem similarity is not enough.',
421
+ '- Map a test when you have clear evidence it covers the flow — from the file path OR from test titles in the content. Behavioral coverage via test titles is sufficient even when the filename does not exactly match the flow (e.g. search_user_post_spec.js covers search_messages if its titles assert searching for messages). Generic subsystem similarity without behavioral evidence is not enough.',
375
422
  '- A flow may only map to tests listed under FLOW_CANDIDATE_SIGNALS for that flow.',
376
423
  '- Treat single-keyword or broad subsystem overlap as insufficient evidence.',
377
424
  '- If the candidate path overlap is weak or ambiguous, return tests: [].',
@@ -136,6 +136,21 @@ function isStrongCandidateMatch(flow, matchedKeywords) {
136
136
  const keywords = flowKeywords(flow);
137
137
  return keywords.length === 1 && matchedKeywords[0].length >= MIN_SINGLE_KEYWORD_LENGTH;
138
138
  }
139
+ // Stop-words excluded from content-fallback keyword matching.
140
+ const CONTENT_FALLBACK_STOP_WORDS = new Set(['and', 'for', 'the', 'to', 'of', 'on', 'at', 'with', 'in', 'a', 'an']);
141
+ // Returns raw (unfiltered) tokens for flows where flowKeywords() returns nothing.
142
+ // Used exclusively for content-title matching when all standard keywords are low-signal.
143
+ // Empty array is returned when the flow already has effective path keywords.
144
+ function contentFallbackKeywords(flow) {
145
+ if (flowKeywords(flow).length > 0) {
146
+ return [];
147
+ }
148
+ return uniqueTokens([
149
+ ...tokenize(flow.id || ''),
150
+ ...tokenize(flow.name || ''),
151
+ ...(flow.keywords || []),
152
+ ]).filter((k) => k.length >= 3 && !CONTENT_FALLBACK_STOP_WORDS.has(k));
153
+ }
139
154
  // Extract test/describe/it title strings from file content for semantic matching.
140
155
  function extractTestTitles(content) {
141
156
  const titles = [];
@@ -211,7 +226,39 @@ function selectCandidateTests(flows, tests, maxCandidateTests) {
211
226
  }
212
227
  contentCandidates.sort((a, b) => b.score - a.score || a.path.localeCompare(b.path));
213
228
  }
214
- const allCandidates = [...strongCandidates, ...contentCandidates.slice(0, perFlowLimit)];
229
+ // Pass 2b: comprehensive fallback for all-low-signal flows.
230
+ // When flowKeywords() is empty (all tokens are low-signal), flowKeywords-based
231
+ // matching in both Pass 1 and Pass 2 yields nothing. As a last resort, search
232
+ // test titles using the raw unfiltered tokens from the flow ID/name, but require
233
+ // ALL tokens to match simultaneously — they are individually weak signals so the
234
+ // full conjunction is needed to establish behavioral coverage evidence.
235
+ const fallbackCandidates = [];
236
+ if (strongCandidates.length === 0 && contentCandidates.length === 0) {
237
+ const fallbackKws = contentFallbackKeywords(flow);
238
+ if (fallbackKws.length > 0) {
239
+ for (const testPath of normalizedTests) {
240
+ const testFile = testByNormalizedPath.get(testPath);
241
+ if (!testFile?.content) {
242
+ continue;
243
+ }
244
+ const haystack = extractTestTitles(testFile.content).toLowerCase();
245
+ if (!haystack) {
246
+ continue;
247
+ }
248
+ const matched = fallbackKws.filter((k) => haystack.includes(k));
249
+ if (matched.length < fallbackKws.length) {
250
+ continue; // all tokens must appear in at least one test title
251
+ }
252
+ fallbackCandidates.push({ path: testPath, score: matched.length, matchedKeywords: matched });
253
+ }
254
+ fallbackCandidates.sort((a, b) => b.score - a.score || a.path.localeCompare(b.path));
255
+ }
256
+ }
257
+ const allCandidates = [
258
+ ...strongCandidates,
259
+ ...contentCandidates.slice(0, perFlowLimit),
260
+ ...fallbackCandidates.slice(0, perFlowLimit),
261
+ ];
215
262
  if (allCandidates.length === 0) {
216
263
  // Exact-name fallback: if the flow ID has no effective keywords (all tokens are
217
264
  // low-signal, e.g. view_user_group_modal), look for a test whose path contains
@@ -368,7 +415,7 @@ export async function mapAITestsToFlows(appRoot, testsRoot, config, flows, tests
368
415
  'Rules:',
369
416
  '- Keep at most 5 tests per flow.',
370
417
  '- Use exact flowId values from FLOWS.',
371
- '- Only map a test when its path clearly matches the flow scenario. Generic subsystem similarity is not enough.',
418
+ '- Map a test when you have clear evidence it covers the flow — from the file path OR from test titles in the content. Behavioral coverage via test titles is sufficient even when the filename does not exactly match the flow (e.g. search_user_post_spec.js covers search_messages if its titles assert searching for messages). Generic subsystem similarity without behavioral evidence is not enough.',
372
419
  '- A flow may only map to tests listed under FLOW_CANDIDATE_SIGNALS for that flow.',
373
420
  '- Treat single-keyword or broad subsystem overlap as insufficient evidence.',
374
421
  '- If the candidate path overlap is weak or ambiguous, return tests: [].',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yasserkhanorg/e2e-agents",
3
- "version": "0.5.12",
3
+ "version": "0.5.13",
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",