@yasserkhanorg/e2e-agents 0.5.9 → 0.5.10

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;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,CAoN1B"}
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;AA8PD,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,CA0O1B"}
@@ -12,7 +12,7 @@ const PRIORITY_RANK = {
12
12
  P1: 1,
13
13
  P2: 2,
14
14
  };
15
- const MIN_SINGLE_KEYWORD_LENGTH = 8;
15
+ const MIN_SINGLE_KEYWORD_LENGTH = 6;
16
16
  const LOW_SIGNAL_FLOW_KEYWORDS = new Set([
17
17
  'app',
18
18
  'apps',
@@ -194,7 +194,34 @@ function selectCandidateTests(flows, tests, maxCandidateTests) {
194
194
  warnings,
195
195
  };
196
196
  }
197
- function buildCoverage(flows, mapped) {
197
+ function readCandidateTestContents(testsRoot, testPaths) {
198
+ const result = [];
199
+ const maxCharsPerFile = 6000;
200
+ const maxTotalChars = 24000;
201
+ let totalChars = 0;
202
+ for (const testPath of testPaths.slice(0, 6)) {
203
+ if (totalChars >= maxTotalChars) {
204
+ break;
205
+ }
206
+ const candidates = (0, path_1.isAbsolute)(testPath) ? [testPath] : [(0, path_1.join)(testsRoot, testPath)];
207
+ for (const fullPath of candidates) {
208
+ if (!(0, fs_1.existsSync)(fullPath)) {
209
+ continue;
210
+ }
211
+ const content = (0, fs_1.readFileSync)(fullPath, 'utf-8').trim();
212
+ if (!content) {
213
+ continue;
214
+ }
215
+ const remaining = Math.max(0, maxTotalChars - totalChars);
216
+ const clipped = content.slice(0, Math.min(maxCharsPerFile, remaining));
217
+ result.push({ path: testPath, content: clipped });
218
+ totalChars += clipped.length;
219
+ break;
220
+ }
221
+ }
222
+ return result;
223
+ }
224
+ function buildCoverage(flows, mapped, scenarioGaps) {
198
225
  return flows.map((flow) => ({
199
226
  flowId: flow.id,
200
227
  flowName: flow.name,
@@ -202,6 +229,7 @@ function buildCoverage(flows, mapped) {
202
229
  coveredBy: mapped.get(flow.id) || [],
203
230
  score: (mapped.get(flow.id) || []).length,
204
231
  source: 'ai',
232
+ missingScenarios: scenarioGaps.get(flow.id) || [],
205
233
  }));
206
234
  }
207
235
  function providerFor(config) {
@@ -255,6 +283,12 @@ async function mapAITestsToFlows(appRoot, testsRoot, config, flows, tests) {
255
283
  if (contextFiles.length === 0) {
256
284
  warnings.push('AI mapping context files were not found; continuing without optional markdown context.');
257
285
  }
286
+ // Read candidate test file contents so the AI can reason about what scenarios
287
+ // are already covered and which ones are still missing.
288
+ const candidateTestContents = readCandidateTestContents(testsRoot, candidateTests);
289
+ const testContentBlock = candidateTestContents.length > 0
290
+ ? candidateTestContents.map((entry) => `### Test: ${entry.path}\n\`\`\`typescript\n${entry.content}\n\`\`\``).join('\n\n')
291
+ : 'No candidate test file contents could be read.';
258
292
  let provider;
259
293
  try {
260
294
  provider = config.provider === 'auto'
@@ -280,7 +314,7 @@ async function mapAITestsToFlows(appRoot, testsRoot, config, flows, tests) {
280
314
  'Only use tests from CANDIDATE_TESTS. Never invent paths.',
281
315
  'Prefer no mapping over a broad or generic mapping.',
282
316
  'Return strict JSON only with this shape:',
283
- '{"mappings":[{"flowId":"<flow id>","tests":["specs/..."],"reason":"short reason","confidence":0.0}]}',
317
+ '{"mappings":[{"flowId":"<flow id>","tests":["specs/..."],"reason":"short reason","confidence":0.0,"missingScenarios":["scenario description"]}]}',
284
318
  '',
285
319
  'Rules:',
286
320
  '- Keep at most 5 tests per flow.',
@@ -290,6 +324,8 @@ async function mapAITestsToFlows(appRoot, testsRoot, config, flows, tests) {
290
324
  '- Treat single-keyword or broad subsystem overlap as insufficient evidence.',
291
325
  '- If the candidate path overlap is weak or ambiguous, return tests: [].',
292
326
  '- If unsure for a flow, return tests: [].',
327
+ '- For every flow you map to tests, read CANDIDATE_TEST_CONTENT and list up to 5 specific test scenarios NOT yet covered by those tests. Write each as a short imperative statement (e.g. "Search messages with date filter"). Only include missingScenarios you can clearly identify; return [] if unsure.',
328
+ '- If tests: [], set missingScenarios: [] as well — do not invent scenarios for unmapped flows.',
293
329
  '',
294
330
  `FLOWS (${prioritizedFlows.length}):`,
295
331
  JSON.stringify(prioritizedFlows.map((flow) => ({
@@ -307,6 +343,9 @@ async function mapAITestsToFlows(appRoot, testsRoot, config, flows, tests) {
307
343
  `FLOW_CANDIDATE_SIGNALS (${candidateSelection.evidence.length}):`,
308
344
  JSON.stringify(candidateSelection.evidence, null, 2),
309
345
  '',
346
+ `CANDIDATE_TEST_CONTENT (${candidateTestContents.length} file(s)):`,
347
+ testContentBlock,
348
+ '',
310
349
  contextBlock,
311
350
  ].join('\n');
312
351
  let parsed = null;
@@ -347,6 +386,7 @@ async function mapAITestsToFlows(appRoot, testsRoot, config, flows, tests) {
347
386
  const allowedFlowIds = new Set(prioritizedFlows.map((flow) => flow.id));
348
387
  const prioritizedFlowsById = new Map(prioritizedFlows.map((flow) => [flow.id, flow]));
349
388
  const mapped = new Map();
389
+ const scenarioGaps = new Map();
350
390
  const matchedTests = new Set();
351
391
  for (const entry of parsed.mappings) {
352
392
  if (!entry || !allowedFlowIds.has(entry.flowId) || !Array.isArray(entry.tests)) {
@@ -370,6 +410,15 @@ async function mapAITestsToFlows(appRoot, testsRoot, config, flows, tests) {
370
410
  for (const testPath of valid) {
371
411
  matchedTests.add(testPath);
372
412
  }
413
+ // Store missing scenarios identified by the AI for this flow.
414
+ if (Array.isArray(entry.missingScenarios) && entry.missingScenarios.length > 0) {
415
+ const scenarios = entry.missingScenarios
416
+ .filter((s) => typeof s === 'string' && s.trim().length > 0)
417
+ .slice(0, 5);
418
+ if (scenarios.length > 0) {
419
+ scenarioGaps.set(entry.flowId, scenarios);
420
+ }
421
+ }
373
422
  }
374
423
  // Post-AI exact-name fallback: for any flow still uncovered, search all test paths
375
424
  // for a file or directory whose name exactly matches the flow ID. This handles flows
@@ -389,7 +438,7 @@ async function mapAITestsToFlows(appRoot, testsRoot, config, flows, tests) {
389
438
  matchedTests.add(exactMatch);
390
439
  }
391
440
  }
392
- const coverage = buildCoverage(flows, mapped);
441
+ const coverage = buildCoverage(flows, mapped, scenarioGaps);
393
442
  if (mapped.size === 0) {
394
443
  warnings.push(`AI mapping returned no valid test mappings (${provider.name}).`);
395
444
  }
@@ -40,6 +40,8 @@ export interface FlowImpact {
40
40
  priorityFloor?: FlowPriority;
41
41
  subsystemRiskBoost?: number;
42
42
  subsystemRiskRules?: string[];
43
+ existingTests?: string[];
44
+ missingScenarios?: string[];
43
45
  }
44
46
  export interface ImpactAnalysisResult {
45
47
  files: FileAnalysis[];
@@ -1 +1 @@
1
- {"version":3,"file":"analysis.d.ts","sourceRoot":"","sources":["../../src/agent/analysis.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAC,WAAW,EAAE,YAAY,EAAa,MAAM,aAAa,CAAC;AACvE,OAAO,EAAqE,KAAK,WAAW,EAAE,KAAK,OAAO,EAAC,MAAM,YAAY,CAAC;AAY9H,MAAM,MAAM,YAAY,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;AAE9C,MAAM,WAAW,YAAY;IACzB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,OAAO,CAAC;IAChB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,IAAI,EAAE,OAAO,CAAC;IACd,QAAQ,EAAE,OAAO,CAAC;IAClB,WAAW,EAAE,OAAO,CAAC;IACrB,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,OAAO,CAAC;IACjB,eAAe,EAAE,OAAO,CAAC;IACzB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,QAAQ,GAAG,MAAM,CAAC;IAC5B,QAAQ,EAAE,YAAY,EAAE,CAAC;IACzB,KAAK,EAAE,OAAO,EAAE,CAAC;IACjB,aAAa,CAAC,EAAE;QACZ,KAAK,EAAE,MAAM,EAAE,CAAC;QAChB,UAAU,EAAE,MAAM,CAAC;QACnB,aAAa,CAAC,EAAE,YAAY,CAAC;QAC7B,OAAO,EAAE,MAAM,EAAE,CAAC;KACrB,CAAC;CACL;AAED,MAAM,WAAW,UAAU;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,QAAQ,GAAG,MAAM,CAAC;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,YAAY,CAAC;IACvB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,QAAQ,CAAC,EAAE,YAAY,EAAE,CAAC;IAC1B,KAAK,CAAC,EAAE,OAAO,EAAE,CAAC;IAClB,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,aAAa,CAAC,EAAE,YAAY,CAAC;IAC7B,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,kBAAkB,CAAC,EAAE,MAAM,EAAE,CAAC;CACjC;AAED,MAAM,WAAW,oBAAoB;IACjC,KAAK,EAAE,YAAY,EAAE,CAAC;IACtB,KAAK,EAAE,UAAU,EAAE,CAAC;IACpB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,aAAa,EAAE;QACX,MAAM,EAAE,KAAK,CAAC;QACd,OAAO,EAAE,OAAO,CAAC;QACjB,OAAO,EAAE,MAAM,CAAC;QAChB,QAAQ,EAAE,OAAO,CAAC;QAClB,WAAW,EAAE,MAAM,CAAC;QACpB,YAAY,EAAE,MAAM,CAAC;QACrB,WAAW,EAAE,MAAM,CAAC;QACpB,YAAY,EAAE,MAAM,CAAC;KACxB,CAAC;CACL;AA6HD,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,EAAE,EAAE,MAAM,EAAE,WAAW,GAAG,oBAAoB,CA+IhH;AAED,wBAAgB,cAAc,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAE5D;AAED,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,SAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,EAAE,EAAE,OAAO,CAAC,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,CAsCnH"}
1
+ {"version":3,"file":"analysis.d.ts","sourceRoot":"","sources":["../../src/agent/analysis.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAC,WAAW,EAAE,YAAY,EAAa,MAAM,aAAa,CAAC;AACvE,OAAO,EAAqE,KAAK,WAAW,EAAE,KAAK,OAAO,EAAC,MAAM,YAAY,CAAC;AAY9H,MAAM,MAAM,YAAY,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;AAE9C,MAAM,WAAW,YAAY;IACzB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,OAAO,CAAC;IAChB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,IAAI,EAAE,OAAO,CAAC;IACd,QAAQ,EAAE,OAAO,CAAC;IAClB,WAAW,EAAE,OAAO,CAAC;IACrB,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,OAAO,CAAC;IACjB,eAAe,EAAE,OAAO,CAAC;IACzB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,QAAQ,GAAG,MAAM,CAAC;IAC5B,QAAQ,EAAE,YAAY,EAAE,CAAC;IACzB,KAAK,EAAE,OAAO,EAAE,CAAC;IACjB,aAAa,CAAC,EAAE;QACZ,KAAK,EAAE,MAAM,EAAE,CAAC;QAChB,UAAU,EAAE,MAAM,CAAC;QACnB,aAAa,CAAC,EAAE,YAAY,CAAC;QAC7B,OAAO,EAAE,MAAM,EAAE,CAAC;KACrB,CAAC;CACL;AAED,MAAM,WAAW,UAAU;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,QAAQ,GAAG,MAAM,CAAC;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,YAAY,CAAC;IACvB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,QAAQ,CAAC,EAAE,YAAY,EAAE,CAAC;IAC1B,KAAK,CAAC,EAAE,OAAO,EAAE,CAAC;IAClB,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,aAAa,CAAC,EAAE,YAAY,CAAC;IAC7B,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,kBAAkB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC9B,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IACzB,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAC;CAC/B;AAED,MAAM,WAAW,oBAAoB;IACjC,KAAK,EAAE,YAAY,EAAE,CAAC;IACtB,KAAK,EAAE,UAAU,EAAE,CAAC;IACpB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,aAAa,EAAE;QACX,MAAM,EAAE,KAAK,CAAC;QACd,OAAO,EAAE,OAAO,CAAC;QACjB,OAAO,EAAE,MAAM,CAAC;QAChB,QAAQ,EAAE,OAAO,CAAC;QAClB,WAAW,EAAE,MAAM,CAAC;QACpB,YAAY,EAAE,MAAM,CAAC;QACrB,WAAW,EAAE,MAAM,CAAC;QACpB,YAAY,EAAE,MAAM,CAAC;KACxB,CAAC;CACL;AA6HD,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,EAAE,EAAE,MAAM,EAAE,WAAW,GAAG,oBAAoB,CA+IhH;AAED,wBAAgB,cAAc,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAE5D;AAED,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,SAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,EAAE,EAAE,OAAO,CAAC,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,CAsCnH"}
@@ -18,6 +18,8 @@ export interface GapDetail {
18
18
  priority: string;
19
19
  reasons: string[];
20
20
  files: string[];
21
+ existingTests?: string[];
22
+ missingScenarios?: string[];
21
23
  }
22
24
  export interface CoveredFlowSummary {
23
25
  id: string;
@@ -1 +1 @@
1
- {"version":3,"file":"plan.d.ts","sourceRoot":"","sources":["../../src/agent/plan.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"plan.d.ts","sourceRoot":"","sources":["../../src/agent/plan.ts"],"names":[],"mappings":"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"}
@@ -265,6 +265,8 @@ function buildPlanFromImpactReport(impact, policyOverride) {
265
265
  priority: flow.priority,
266
266
  reasons: (flow.reasons || []).slice(0, 5),
267
267
  files: (flow.files || []).slice(0, 6),
268
+ existingTests: flow.existingTests && flow.existingTests.length > 0 ? flow.existingTests.slice(0, 3) : undefined,
269
+ missingScenarios: flow.missingScenarios && flow.missingScenarios.length > 0 ? flow.missingScenarios.slice(0, 5) : undefined,
268
270
  }));
269
271
  const coveredFlowIds = new Set(impact.gaps.map((g) => g.id));
270
272
  const coveredFlows = impact.coverage
@@ -1 +1 @@
1
- {"version":3,"file":"runner.d.ts","sourceRoot":"","sources":["../../src/agent/runner.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,aAAa,CAAC;AA+R7C,MAAM,WAAW,UAAU;IACvB,KAAK,EAAE,OAAO,CAAC;CAClB;AAYD,wBAAsB,SAAS,CAAC,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAuTzF;AAED,wBAAsB,MAAM,CAAC,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAoUtF"}
1
+ {"version":3,"file":"runner.d.ts","sourceRoot":"","sources":["../../src/agent/runner.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,aAAa,CAAC;AA+S7C,MAAM,WAAW,UAAU;IACvB,KAAK,EAAE,OAAO,CAAC;CAClB;AAYD,wBAAsB,SAAS,CAAC,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAwTzF;AAED,wBAAsB,MAAM,CAAC,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAqUtF"}
@@ -34,13 +34,29 @@ function ensureAppRoot(path) {
34
34
  throw new Error(`App path does not exist: ${path}`);
35
35
  }
36
36
  }
37
- function computeGaps(flows, coverageMap) {
38
- return flows.filter((flow) => {
37
+ function computeGaps(flows, coverageMap, coverage) {
38
+ const coverageByFlowId = new Map((coverage || []).map((c) => [c.flowId, c]));
39
+ return flows
40
+ .filter((flow) => {
39
41
  if (flow.priority !== 'P0' && flow.priority !== 'P1') {
40
42
  return false;
41
43
  }
42
44
  const coveredBy = coverageMap.get(flow.id) || [];
43
- return coveredBy.length === 0;
45
+ if (coveredBy.length === 0) {
46
+ return true; // no tests at all
47
+ }
48
+ // Also flag as a gap if tests exist but the AI identified missing scenarios.
49
+ const flowCoverage = coverageByFlowId.get(flow.id);
50
+ return (flowCoverage?.missingScenarios || []).length > 0;
51
+ })
52
+ .map((flow) => {
53
+ const coveredBy = coverageMap.get(flow.id) || [];
54
+ const flowCoverage = coverageByFlowId.get(flow.id);
55
+ return {
56
+ ...flow,
57
+ existingTests: coveredBy.length > 0 ? coveredBy : undefined,
58
+ missingScenarios: flowCoverage?.missingScenarios?.length ? flowCoverage.missingScenarios : undefined,
59
+ };
44
60
  });
45
61
  }
46
62
  function normalizeChangedFiles(appRoot, files) {
@@ -460,7 +476,8 @@ async function runImpact(_config, _options) {
460
476
  for (const entry of coverage) {
461
477
  coverageMap.set(entry.flowId, entry.coveredBy);
462
478
  }
463
- gaps = computeGaps(flows, coverageMap);
479
+ // Pass the full coverage array so partial gaps (tests exist but missing scenarios) are included.
480
+ gaps = computeGaps(flows, coverageMap, coverage);
464
481
  }
465
482
  if (Date.now() <= deadline) {
466
483
  testSuggestions = (0, gap_suggestions_js_1.buildGapTestSuggestions)(testsRoot, gaps, frameworkDetection.framework, testPatterns.patterns);
@@ -750,7 +767,8 @@ async function runGap(_config, _options) {
750
767
  for (const entry of coverage) {
751
768
  coverageMap.set(entry.flowId, entry.coveredBy);
752
769
  }
753
- gaps = computeGaps(flows, coverageMap);
770
+ // Pass the full coverage array so partial gaps (tests exist but missing scenarios) are included.
771
+ gaps = computeGaps(flows, coverageMap, coverage);
754
772
  }
755
773
  if (Date.now() <= deadline) {
756
774
  testSuggestions = (0, gap_suggestions_js_1.buildGapTestSuggestions)(testsRoot, gaps, frameworkDetection.framework, testPatterns.patterns);
@@ -11,6 +11,7 @@ export interface FlowCoverage {
11
11
  score: number;
12
12
  expectedTests?: string[];
13
13
  source?: 'catalog' | 'traceability' | 'heuristic' | 'ai';
14
+ missingScenarios?: string[];
14
15
  }
15
16
  export declare function discoverTests(appRoot: string, patterns: string[]): TestFile[];
16
17
  export declare function mapTestsToFlows(flows: FlowImpact[], tests: TestFile[]): FlowCoverage[];
@@ -1 +1 @@
1
- {"version":3,"file":"tests.d.ts","sourceRoot":"","sources":["../../src/agent/tests.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,eAAe,CAAC;AAG9C,MAAM,WAAW,QAAQ;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B;AAED,MAAM,WAAW,YAAY;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IACzB,MAAM,CAAC,EAAE,SAAS,GAAG,cAAc,GAAG,WAAW,GAAG,IAAI,CAAC;CAC5D;AAED,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,QAAQ,EAAE,CAkB7E;AAUD,wBAAgB,eAAe,CAAC,KAAK,EAAE,UAAU,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,GAAG,YAAY,EAAE,CAgCtF;AAuBD,wBAAgB,sBAAsB,CAClC,KAAK,EAAE,UAAU,EAAE,EACnB,SAAS,EAAE,MAAM,EACjB,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,GACnC,YAAY,EAAE,CA8BhB"}
1
+ {"version":3,"file":"tests.d.ts","sourceRoot":"","sources":["../../src/agent/tests.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,eAAe,CAAC;AAG9C,MAAM,WAAW,QAAQ;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B;AAED,MAAM,WAAW,YAAY;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IACzB,MAAM,CAAC,EAAE,SAAS,GAAG,cAAc,GAAG,WAAW,GAAG,IAAI,CAAC;IACzD,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAC;CAC/B;AAED,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,QAAQ,EAAE,CAkB7E;AAUD,wBAAgB,eAAe,CAAC,KAAK,EAAE,UAAU,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,GAAG,YAAY,EAAE,CAgCtF;AAuBD,wBAAgB,sBAAsB,CAClC,KAAK,EAAE,UAAU,EAAE,EACnB,SAAS,EAAE,MAAM,EACjB,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,GACnC,YAAY,EAAE,CA8BhB"}
@@ -9,7 +9,7 @@ const PRIORITY_RANK = {
9
9
  P1: 1,
10
10
  P2: 2,
11
11
  };
12
- const MIN_SINGLE_KEYWORD_LENGTH = 8;
12
+ const MIN_SINGLE_KEYWORD_LENGTH = 6;
13
13
  const LOW_SIGNAL_FLOW_KEYWORDS = new Set([
14
14
  'app',
15
15
  'apps',
@@ -191,7 +191,34 @@ function selectCandidateTests(flows, tests, maxCandidateTests) {
191
191
  warnings,
192
192
  };
193
193
  }
194
- function buildCoverage(flows, mapped) {
194
+ function readCandidateTestContents(testsRoot, testPaths) {
195
+ const result = [];
196
+ const maxCharsPerFile = 6000;
197
+ const maxTotalChars = 24000;
198
+ let totalChars = 0;
199
+ for (const testPath of testPaths.slice(0, 6)) {
200
+ if (totalChars >= maxTotalChars) {
201
+ break;
202
+ }
203
+ const candidates = isAbsolute(testPath) ? [testPath] : [join(testsRoot, testPath)];
204
+ for (const fullPath of candidates) {
205
+ if (!existsSync(fullPath)) {
206
+ continue;
207
+ }
208
+ const content = readFileSync(fullPath, 'utf-8').trim();
209
+ if (!content) {
210
+ continue;
211
+ }
212
+ const remaining = Math.max(0, maxTotalChars - totalChars);
213
+ const clipped = content.slice(0, Math.min(maxCharsPerFile, remaining));
214
+ result.push({ path: testPath, content: clipped });
215
+ totalChars += clipped.length;
216
+ break;
217
+ }
218
+ }
219
+ return result;
220
+ }
221
+ function buildCoverage(flows, mapped, scenarioGaps) {
195
222
  return flows.map((flow) => ({
196
223
  flowId: flow.id,
197
224
  flowName: flow.name,
@@ -199,6 +226,7 @@ function buildCoverage(flows, mapped) {
199
226
  coveredBy: mapped.get(flow.id) || [],
200
227
  score: (mapped.get(flow.id) || []).length,
201
228
  source: 'ai',
229
+ missingScenarios: scenarioGaps.get(flow.id) || [],
202
230
  }));
203
231
  }
204
232
  function providerFor(config) {
@@ -252,6 +280,12 @@ export async function mapAITestsToFlows(appRoot, testsRoot, config, flows, tests
252
280
  if (contextFiles.length === 0) {
253
281
  warnings.push('AI mapping context files were not found; continuing without optional markdown context.');
254
282
  }
283
+ // Read candidate test file contents so the AI can reason about what scenarios
284
+ // are already covered and which ones are still missing.
285
+ const candidateTestContents = readCandidateTestContents(testsRoot, candidateTests);
286
+ const testContentBlock = candidateTestContents.length > 0
287
+ ? candidateTestContents.map((entry) => `### Test: ${entry.path}\n\`\`\`typescript\n${entry.content}\n\`\`\``).join('\n\n')
288
+ : 'No candidate test file contents could be read.';
255
289
  let provider;
256
290
  try {
257
291
  provider = config.provider === 'auto'
@@ -277,7 +311,7 @@ export async function mapAITestsToFlows(appRoot, testsRoot, config, flows, tests
277
311
  'Only use tests from CANDIDATE_TESTS. Never invent paths.',
278
312
  'Prefer no mapping over a broad or generic mapping.',
279
313
  'Return strict JSON only with this shape:',
280
- '{"mappings":[{"flowId":"<flow id>","tests":["specs/..."],"reason":"short reason","confidence":0.0}]}',
314
+ '{"mappings":[{"flowId":"<flow id>","tests":["specs/..."],"reason":"short reason","confidence":0.0,"missingScenarios":["scenario description"]}]}',
281
315
  '',
282
316
  'Rules:',
283
317
  '- Keep at most 5 tests per flow.',
@@ -287,6 +321,8 @@ export async function mapAITestsToFlows(appRoot, testsRoot, config, flows, tests
287
321
  '- Treat single-keyword or broad subsystem overlap as insufficient evidence.',
288
322
  '- If the candidate path overlap is weak or ambiguous, return tests: [].',
289
323
  '- If unsure for a flow, return tests: [].',
324
+ '- For every flow you map to tests, read CANDIDATE_TEST_CONTENT and list up to 5 specific test scenarios NOT yet covered by those tests. Write each as a short imperative statement (e.g. "Search messages with date filter"). Only include missingScenarios you can clearly identify; return [] if unsure.',
325
+ '- If tests: [], set missingScenarios: [] as well — do not invent scenarios for unmapped flows.',
290
326
  '',
291
327
  `FLOWS (${prioritizedFlows.length}):`,
292
328
  JSON.stringify(prioritizedFlows.map((flow) => ({
@@ -304,6 +340,9 @@ export async function mapAITestsToFlows(appRoot, testsRoot, config, flows, tests
304
340
  `FLOW_CANDIDATE_SIGNALS (${candidateSelection.evidence.length}):`,
305
341
  JSON.stringify(candidateSelection.evidence, null, 2),
306
342
  '',
343
+ `CANDIDATE_TEST_CONTENT (${candidateTestContents.length} file(s)):`,
344
+ testContentBlock,
345
+ '',
307
346
  contextBlock,
308
347
  ].join('\n');
309
348
  let parsed = null;
@@ -344,6 +383,7 @@ export async function mapAITestsToFlows(appRoot, testsRoot, config, flows, tests
344
383
  const allowedFlowIds = new Set(prioritizedFlows.map((flow) => flow.id));
345
384
  const prioritizedFlowsById = new Map(prioritizedFlows.map((flow) => [flow.id, flow]));
346
385
  const mapped = new Map();
386
+ const scenarioGaps = new Map();
347
387
  const matchedTests = new Set();
348
388
  for (const entry of parsed.mappings) {
349
389
  if (!entry || !allowedFlowIds.has(entry.flowId) || !Array.isArray(entry.tests)) {
@@ -367,6 +407,15 @@ export async function mapAITestsToFlows(appRoot, testsRoot, config, flows, tests
367
407
  for (const testPath of valid) {
368
408
  matchedTests.add(testPath);
369
409
  }
410
+ // Store missing scenarios identified by the AI for this flow.
411
+ if (Array.isArray(entry.missingScenarios) && entry.missingScenarios.length > 0) {
412
+ const scenarios = entry.missingScenarios
413
+ .filter((s) => typeof s === 'string' && s.trim().length > 0)
414
+ .slice(0, 5);
415
+ if (scenarios.length > 0) {
416
+ scenarioGaps.set(entry.flowId, scenarios);
417
+ }
418
+ }
370
419
  }
371
420
  // Post-AI exact-name fallback: for any flow still uncovered, search all test paths
372
421
  // for a file or directory whose name exactly matches the flow ID. This handles flows
@@ -386,7 +435,7 @@ export async function mapAITestsToFlows(appRoot, testsRoot, config, flows, tests
386
435
  matchedTests.add(exactMatch);
387
436
  }
388
437
  }
389
- const coverage = buildCoverage(flows, mapped);
438
+ const coverage = buildCoverage(flows, mapped, scenarioGaps);
390
439
  if (mapped.size === 0) {
391
440
  warnings.push(`AI mapping returned no valid test mappings (${provider.name}).`);
392
441
  }
@@ -256,6 +256,8 @@ export function buildPlanFromImpactReport(impact, policyOverride) {
256
256
  priority: flow.priority,
257
257
  reasons: (flow.reasons || []).slice(0, 5),
258
258
  files: (flow.files || []).slice(0, 6),
259
+ existingTests: flow.existingTests && flow.existingTests.length > 0 ? flow.existingTests.slice(0, 3) : undefined,
260
+ missingScenarios: flow.missingScenarios && flow.missingScenarios.length > 0 ? flow.missingScenarios.slice(0, 5) : undefined,
259
261
  }));
260
262
  const coveredFlowIds = new Set(impact.gaps.map((g) => g.id));
261
263
  const coveredFlows = impact.coverage
@@ -30,13 +30,29 @@ function ensureAppRoot(path) {
30
30
  throw new Error(`App path does not exist: ${path}`);
31
31
  }
32
32
  }
33
- function computeGaps(flows, coverageMap) {
34
- return flows.filter((flow) => {
33
+ function computeGaps(flows, coverageMap, coverage) {
34
+ const coverageByFlowId = new Map((coverage || []).map((c) => [c.flowId, c]));
35
+ return flows
36
+ .filter((flow) => {
35
37
  if (flow.priority !== 'P0' && flow.priority !== 'P1') {
36
38
  return false;
37
39
  }
38
40
  const coveredBy = coverageMap.get(flow.id) || [];
39
- return coveredBy.length === 0;
41
+ if (coveredBy.length === 0) {
42
+ return true; // no tests at all
43
+ }
44
+ // Also flag as a gap if tests exist but the AI identified missing scenarios.
45
+ const flowCoverage = coverageByFlowId.get(flow.id);
46
+ return (flowCoverage?.missingScenarios || []).length > 0;
47
+ })
48
+ .map((flow) => {
49
+ const coveredBy = coverageMap.get(flow.id) || [];
50
+ const flowCoverage = coverageByFlowId.get(flow.id);
51
+ return {
52
+ ...flow,
53
+ existingTests: coveredBy.length > 0 ? coveredBy : undefined,
54
+ missingScenarios: flowCoverage?.missingScenarios?.length ? flowCoverage.missingScenarios : undefined,
55
+ };
40
56
  });
41
57
  }
42
58
  function normalizeChangedFiles(appRoot, files) {
@@ -456,7 +472,8 @@ export async function runImpact(_config, _options) {
456
472
  for (const entry of coverage) {
457
473
  coverageMap.set(entry.flowId, entry.coveredBy);
458
474
  }
459
- gaps = computeGaps(flows, coverageMap);
475
+ // Pass the full coverage array so partial gaps (tests exist but missing scenarios) are included.
476
+ gaps = computeGaps(flows, coverageMap, coverage);
460
477
  }
461
478
  if (Date.now() <= deadline) {
462
479
  testSuggestions = buildGapTestSuggestions(testsRoot, gaps, frameworkDetection.framework, testPatterns.patterns);
@@ -746,7 +763,8 @@ export async function runGap(_config, _options) {
746
763
  for (const entry of coverage) {
747
764
  coverageMap.set(entry.flowId, entry.coveredBy);
748
765
  }
749
- gaps = computeGaps(flows, coverageMap);
766
+ // Pass the full coverage array so partial gaps (tests exist but missing scenarios) are included.
767
+ gaps = computeGaps(flows, coverageMap, coverage);
750
768
  }
751
769
  if (Date.now() <= deadline) {
752
770
  testSuggestions = buildGapTestSuggestions(testsRoot, gaps, frameworkDetection.framework, testPatterns.patterns);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yasserkhanorg/e2e-agents",
3
- "version": "0.5.9",
3
+ "version": "0.5.10",
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",